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 13fac10
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 1 deletion.
1 change: 1 addition & 0 deletions changelog.d/+.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
);
53 changes: 53 additions & 0 deletions tests/integration/models/jwtrefreshtoken_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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) -> JWTRefreshToken:
return JWTRefreshToken(
name="testtoken",
description="this is a test token",
data=data,
hash="dummyhash",
)


@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 13fac10

Please sign in to comment.