Skip to content

Commit

Permalink
Merge pull request #152 from sphrak/feature/131-return-expiration-dat…
Browse files Browse the repository at this point in the history
…etime-after-login

fixes #131 -- return expiry date on successful login
  • Loading branch information
belugame authored Jan 11, 2019
2 parents 1e2adba + b417192 commit 5111fc8
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 53 deletions.
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
4.0.0
=====

**BREAKING** This is a major release version because it
breaks the existing API.

Changes have been made to the `create()` method on the `AuthToken` model.
It now returns the model instance and the raw `token` instead
of just the `token` to allow the `expiry` field to be included in the
success response.

Model field of `AuthToken` has been renamed from `expires` to `expiry`
to remain consistent across the code base. This patch requires you
to run a migration.

Depending on your usage you might have to adjust your code
to fit these new changes.

- `AuthToken` model field has been changed from `expires` to `expiry`
- Successful login now always returns a `expiry` field for when the token expires

3.6.0
=====

Expand Down
19 changes: 19 additions & 0 deletions docs/changes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# Changelog

## 4.0.0

**BREAKING** This is a major release version because it
breaks the existing API.
Changes have been made to the `create()` method on the `AuthToken` model.
It now returns the model instance and the raw `token` instead
of just the `token` to allow the `expiry` field to be included in the
success response.

Model field of `AuthToken` has been renamed from `expires` to `expiry`
to remain consistent across the code base. This patch requires you
to run a migration.

Depending on your usage you might have to adjust your code
to fit these new changes.

- `AuthToken` model field has been changed from `expires` to `expiry`
- Successful login now always returns a `expiry` field for when the token expires

## 3.6.0

- The user serializer for each `LoginView`is now dynamic
Expand Down
2 changes: 2 additions & 0 deletions docs/views.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ helper methods:
---
When the endpoint authenticates a request, a json object will be returned
containing the `token` key along with the actual value for the key by default.
The success response also includes a `expiry` key with a timestamp for when
the token expires.

> *This is because `USER_SERIALIZER` setting is `None` by default.*
Expand Down
16 changes: 8 additions & 8 deletions knox/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,19 +72,19 @@ def authenticate_credentials(self, token):
except (TypeError, binascii.Error):
raise exceptions.AuthenticationFailed(msg)
if compare_digest(digest, auth_token.digest):
if knox_settings.AUTO_REFRESH and auth_token.expires:
if knox_settings.AUTO_REFRESH and auth_token.expiry:
self.renew_token(auth_token)
return self.validate_user(auth_token)
raise exceptions.AuthenticationFailed(msg)

def renew_token(self, auth_token):
current_expiry = auth_token.expires
current_expiry = auth_token.expiry
new_expiry = timezone.now() + knox_settings.TOKEN_TTL
auth_token.expires = new_expiry
auth_token.expiry = new_expiry
# Throttle refreshing of token to avoid db writes
delta = (new_expiry - current_expiry).total_seconds()
if delta > knox_settings.MIN_REFRESH_INTERVAL:
auth_token.save(update_fields=('expires',))
auth_token.save(update_fields=('expiry',))

def validate_user(self, auth_token):
if not auth_token.user.is_active:
Expand All @@ -97,14 +97,14 @@ def authenticate_header(self, request):

def _cleanup_token(self, auth_token):
for other_token in auth_token.user.auth_token_set.all():
if other_token.digest != auth_token.digest and other_token.expires:
if other_token.expires < timezone.now():
if other_token.digest != auth_token.digest and other_token.expiry:
if other_token.expiry < timezone.now():
other_token.delete()
username = other_token.user.get_username()
token_expired.send(sender=self.__class__,
username=username, source="other_token")
if auth_token.expires is not None:
if auth_token.expires < timezone.now():
if auth_token.expiry is not None:
if auth_token.expiry < timezone.now():
username = auth_token.user.get_username()
auth_token.delete()
token_expired.send(sender=self.__class__,
Expand Down
18 changes: 18 additions & 0 deletions knox/migrations/0007_auto_20190111_0542.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 2.1 on 2019-01-11 05:42

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('knox', '0006_auto_20160818_0932'),
]

operations = [
migrations.RenameField(
model_name='authtoken',
old_name='expires',
new_name='expiry',
),
]
15 changes: 7 additions & 8 deletions knox/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,18 @@


class AuthTokenManager(models.Manager):
def create(self, user, expires=knox_settings.TOKEN_TTL):
def create(self, user, expiry=knox_settings.TOKEN_TTL):
token = crypto.create_token_string()
salt = crypto.create_salt_string()
digest = crypto.hash_token(token, salt)

if expires is not None:
expires = timezone.now() + expires
if expiry is not None:
expiry = timezone.now() + expiry

super(AuthTokenManager, self).create(
instance = super(AuthTokenManager, self).create(
token_key=token[:CONSTANTS.TOKEN_KEY_LENGTH], digest=digest,
salt=salt, user=user, expires=expires)
# Note only the token - not the AuthToken object - is returned
return token
salt=salt, user=user, expiry=expiry)
return instance, token


class AuthToken(models.Model):
Expand All @@ -37,7 +36,7 @@ class AuthToken(models.Model):
user = models.ForeignKey(User, null=False, blank=False,
related_name='auth_token_set', on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
expires = models.DateTimeField(null=True, blank=True)
expiry = models.DateTimeField(null=True, blank=True)

def __str__(self):
return '%s : %s' % (self.digest, self.user)
24 changes: 13 additions & 11 deletions knox/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,26 +31,28 @@ def post(self, request, format=None):
token_limit_per_user = self.get_token_limit_per_user()
if token_limit_per_user is not None:
now = timezone.now()
token = request.user.auth_token_set.filter(expires__gt=now)
token = request.user.auth_token_set.filter(expiry__gt=now)
if token.count() >= token_limit_per_user:
return Response(
{"error": "Maximum amount of tokens allowed per user exceeded."},
status=status.HTTP_403_FORBIDDEN
)
token_ttl = self.get_token_ttl()
token = AuthToken.objects.create(request.user, token_ttl)
instance, token = AuthToken.objects.create(request.user, token_ttl)
user_logged_in.send(sender=request.user.__class__,
request=request, user=request.user)
UserSerializer = self.get_user_serializer_class()
if UserSerializer is None:
return Response({
'token': token
})
context = self.get_context()
return Response({
'user': UserSerializer(request.user, context=context).data,
'token': token,
})

data = {
'expiry': instance.expiry,
'token': token
}
if UserSerializer is not None:
data["user"] = UserSerializer(
request.user,
context=self.get_context()
).data
return Response(data)


class LogoutView(APIView):
Expand Down
Loading

0 comments on commit 5111fc8

Please sign in to comment.