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 Feb 21, 2025
1 parent dbf3893 commit 15b2279
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 0 deletions.
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
25 changes: 25 additions & 0 deletions python/nav/models/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

from nav.models.fields import VarcharField
from nav.models.profiles import Account
from nav.web.jwtgen import is_active


class APIToken(models.Model):
Expand Down Expand Up @@ -66,3 +67,27 @@ 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)
expires = models.DateTimeField()
activates = models.DateTimeField()
last_used = models.DateTimeField(null=True)
revoked = models.BooleanField(default=False)
hash = VarcharField()

def __str__(self):
return self.name

def is_active(self) -> bool:
"""Returns True if the token is active. A token is considered active when
`expires` is in the future and `activates` is in the past or matches
the current time.
"""
return is_active(self.expires.timestamp(), self.activates.timestamp())

class Meta(object):
db_table = 'jwtrefreshtoken'
10 changes: 10 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,10 @@
CREATE TABLE manage.JWTRefreshToken (
id SERIAL PRIMARY KEY,
name VARCHAR NOT NULL UNIQUE,
description VARCHAR,
expires TIMESTAMP NOT NULL,
activates TIMESTAMP NOT NULL,
last_used TIMESTAMP,
revoked BOOLEAN NOT NULL DEFAULT FALSE,
hash VARCHAR NOT NULL
);
15 changes: 15 additions & 0 deletions python/nav/web/jwtgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,18 @@ def _generate_token(
new_token, JWTConf().get_nav_private_key(), algorithm="RS256"
)
return encoded_token


def is_active(exp: str, nbf: str) -> bool:
"""
Takes `exp` and `nbf` as POSIX timestamps. These represent the claims of a JWT token.
`exp` should be the expiration time of the token and `nbf` should be the time when
the token becomes active.
Returns True if `exp` is in the future and `nbf` is in the past or matches
the current time.
"""
now = datetime.now()
expires = datetime.fromtimestamp(exp)
activates = datetime.fromtimestamp(nbf)
return now >= activates and now < expires
58 changes: 58 additions & 0 deletions tests/unittests/models/jwtrefreshtoken_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from datetime import datetime, timedelta

from nav.models.api import JWTRefreshToken


class TestIsActive:
def test_should_return_false_if_token_activates_in_the_future(self):
now = datetime.now()
token = JWTRefreshToken(
name="testtoken",
hash="dummyhash",
expires=now + timedelta(hours=1),
activates=now + timedelta(hours=1),
)
assert not token.is_active()

def test_should_return_false_if_token_expires_in_the_past(self):
now = datetime.now()
token = JWTRefreshToken(
name="testtoken",
hash="dummyhash",
expires=now - timedelta(hours=1),
activates=now - timedelta(hours=1),
)
assert not token.is_active()

def test_should_return_true_if_token_activates_in_the_past_and_expires_in_the_future(
self,
):
now = datetime.now()
token = JWTRefreshToken(
name="testtoken",
hash="dummyhash",
expires=now + timedelta(hours=1),
activates=now - timedelta(hours=1),
)
assert token.is_active()

def test_should_return_true_if_token_activates_now_and_expires_in_the_future(self):
now = datetime.now()
token = JWTRefreshToken(
name="testtoken",
hash="dummyhash",
expires=now + timedelta(hours=1),
activates=now,
)
assert token.is_active()


def test_string_representation_should_match_name():
now = datetime.now()
token = JWTRefreshToken(
name="testtoken",
hash="dummyhash",
expires=now + timedelta(hours=1),
activates=now - timedelta(hours=1),
)
assert str(token) == token.name

0 comments on commit 15b2279

Please sign in to comment.