Skip to content

Commit

Permalink
Add model for JWT refresh token
Browse files Browse the repository at this point in the history
  • Loading branch information
stveit committed Jan 29, 2025
1 parent dbf3893 commit d16dda5
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 1 deletion.
1 change: 1 addition & 0 deletions changelog.d/3268.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add database model for JWT refresh tokens
26 changes: 25 additions & 1 deletion python/nav/models/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@
#
"""Models for the NAV API"""

from datetime import datetime
from datetime import datetime, timezone

from django.contrib.postgres.fields import HStoreField
from django.db import models
from django.urls import reverse
from django.db.models import JSONField

from nav.models.fields import VarcharField
from nav.models.profiles import Account
Expand Down Expand Up @@ -66,3 +67,26 @@ def get_absolute_url(self):

class Meta(object):
db_table = 'apitoken'


class JWTRefreshToken(models.Model):

name = VarcharField(unique=True)
description = models.TextField(null=True, blank=True)
data = JSONField()
hash = VarcharField()

def __str__(self):
return self.name

def is_active(self) -> bool:
"""True if token is active. A token is considered active when
the nbf claim is in the past and the exp claim is in the future
"""
now = datetime.now(tz=timezone.utc)
nbf = datetime.fromtimestamp(self.data['nbf'], tz=timezone.utc)
exp = datetime.fromtimestamp(self.data['exp'], tz=timezone.utc)
return now >= nbf and now < exp

class Meta(object):
db_table = 'jwtrefreshtoken'
7 changes: 7 additions & 0 deletions python/nav/models/sql/changes/sc.05.13.0001.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CREATE TABLE manage.JWTRefreshToken (
id SERIAL PRIMARY KEY,
data JSON NOT NULL,
name VARCHAR NOT NULL UNIQUE,
description VARCHAR,
hash VARCHAR NOT NULL
);
57 changes: 57 additions & 0 deletions tests/integration/models/jwtrefreshtoken_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from typing import Generator
import pytest
from datetime import datetime, timedelta, timezone

from nav.models.api import JWTRefreshToken


class TestIsActive:
def test_should_return_false_if_nbf_is_in_the_future(self, token):
now = datetime.now(tz=timezone.utc)
token.data['nbf'] = (now + timedelta(hours=1)).timestamp()
token.data['exp'] = (now + timedelta(hours=1)).timestamp()
assert not token.is_active()

def test_should_return_false_if_exp_is_in_the_past(self, token):
now = datetime.now(tz=timezone.utc)
token.data['nbf'] = (now - timedelta(hours=1)).timestamp()
token.data['exp'] = (now - timedelta(hours=1)).timestamp()
assert not token.is_active()

def test_should_return_true_if_nbf_is_in_the_past_and_exp_is_in_the_future(
self, token
):
now = datetime.now(tz=timezone.utc)
token.data['nbf'] = (now - timedelta(hours=1)).timestamp()
token.data['exp'] = (now + timedelta(hours=1)).timestamp()
assert token.is_active()


def test_string_representation_should_match_token(token):
assert str(token) == token.name


@pytest.fixture()
def token(data) -> Generator[JWTRefreshToken, None, None]:
refresh_token = JWTRefreshToken(
name="testtoken",
description="this is a test token",
data=data,
hash="dummyhash",
)
refresh_token.save()
yield refresh_token
refresh_token.delete()


@pytest.fixture()
def data() -> dict:
data = {
"exp": 1516339022,
"nbf": 1516239022,
"iat": 1516239022,
"aud": "nav",
"iss": "nav",
"token_type": "refresh_token",
}
return data

0 comments on commit d16dda5

Please sign in to comment.