Skip to content

Commit c2568eb

Browse files
committed
Add model for JWT refresh token
1 parent dbf3893 commit c2568eb

File tree

4 files changed

+90
-1
lines changed

4 files changed

+90
-1
lines changed

changelog.d/3268.added.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add database model for JWT refresh tokens

python/nav/models/api.py

+25-1
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@
1515
#
1616
"""Models for the NAV API"""
1717

18-
from datetime import datetime
18+
from datetime import datetime, timezone
1919

2020
from django.contrib.postgres.fields import HStoreField
2121
from django.db import models
2222
from django.urls import reverse
23+
from django.db.models import JSONField
2324

2425
from nav.models.fields import VarcharField
2526
from nav.models.profiles import Account
@@ -66,3 +67,26 @@ def get_absolute_url(self):
6667

6768
class Meta(object):
6869
db_table = 'apitoken'
70+
71+
72+
class JWTRefreshToken(models.Model):
73+
74+
name = VarcharField(unique=True)
75+
description = models.TextField(null=True, blank=True)
76+
data = JSONField()
77+
hash = VarcharField()
78+
79+
def __str__(self):
80+
return self.name
81+
82+
def is_active(self) -> bool:
83+
"""True if token is active. A token is considered active when
84+
the nbf claim is in the past and the exp claim is in the future
85+
"""
86+
now = datetime.now(tz=timezone.utc)
87+
nbf = datetime.fromtimestamp(self.data['nbf'], tz=timezone.utc)
88+
exp = datetime.fromtimestamp(self.data['exp'], tz=timezone.utc)
89+
return now >= nbf and now < exp
90+
91+
class Meta(object):
92+
db_table = 'jwtrefreshtoken'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CREATE TABLE manage.JWTRefreshToken (
2+
id SERIAL PRIMARY KEY,
3+
data JSONB NOT NULL,
4+
name VARCHAR NOT NULL UNIQUE,
5+
description VARCHAR,
6+
hash VARCHAR NOT NULL
7+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from typing import Generator
2+
import pytest
3+
from datetime import datetime, timedelta, timezone
4+
5+
from nav.models.api import JWTRefreshToken
6+
7+
8+
class TestIsActive:
9+
def test_should_return_false_if_nbf_is_in_the_future(self, token):
10+
now = datetime.now(tz=timezone.utc)
11+
token.data['nbf'] = (now + timedelta(hours=1)).timestamp()
12+
token.data['exp'] = (now + timedelta(hours=1)).timestamp()
13+
assert not token.is_active()
14+
15+
def test_should_return_false_if_exp_is_in_the_past(self, token):
16+
now = datetime.now(tz=timezone.utc)
17+
token.data['nbf'] = (now - timedelta(hours=1)).timestamp()
18+
token.data['exp'] = (now - timedelta(hours=1)).timestamp()
19+
assert not token.is_active()
20+
21+
def test_should_return_true_if_nbf_is_in_the_past_and_exp_is_in_the_future(
22+
self, token
23+
):
24+
now = datetime.now(tz=timezone.utc)
25+
token.data['nbf'] = (now - timedelta(hours=1)).timestamp()
26+
token.data['exp'] = (now + timedelta(hours=1)).timestamp()
27+
assert token.is_active()
28+
29+
30+
def test_string_representation_should_match_token(token):
31+
assert str(token) == token.name
32+
33+
34+
@pytest.fixture()
35+
def token(data) -> Generator[JWTRefreshToken, None, None]:
36+
refresh_token = JWTRefreshToken(
37+
name="testtoken",
38+
description="this is a test token",
39+
data=data,
40+
hash="dummyhash",
41+
)
42+
refresh_token.save()
43+
yield refresh_token
44+
refresh_token.delete()
45+
46+
47+
@pytest.fixture()
48+
def data() -> dict:
49+
data = {
50+
"exp": 1516339022,
51+
"nbf": 1516239022,
52+
"iat": 1516239022,
53+
"aud": "nav",
54+
"iss": "nav",
55+
"token_type": "refresh_token",
56+
}
57+
return data

0 commit comments

Comments
 (0)