From c795a5807ed29e22552c9496ab392b6c9e4ef9de Mon Sep 17 00:00:00 2001 From: Alexander-D-Karpov Date: Fri, 10 Jun 2022 23:33:40 +0300 Subject: [PATCH] added jit for refresh token, refresh access token endpoint --- common/__init__.py | 0 common/generators.py | 8 ++++++++ game/api/v1/serializers.py | 29 ++++++++++++++++++++++++++++- game/api/v1/views.py | 14 ++++++++++++-- game/authentication.py | 12 ++++++++++-- game/models.py | 13 +++++++++++++ game/services/jwt.py | 4 ---- game/urls.py | 2 ++ 8 files changed, 73 insertions(+), 9 deletions(-) create mode 100644 common/__init__.py create mode 100644 common/generators.py diff --git a/common/__init__.py b/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/common/generators.py b/common/generators.py new file mode 100644 index 0000000..b81f420 --- /dev/null +++ b/common/generators.py @@ -0,0 +1,8 @@ +import string +import secrets + + +def generate_charset(length: int): + return "".join( + secrets.choice(string.digits + string.ascii_letters) for _ in range(length) + ) diff --git a/game/api/v1/serializers.py b/game/api/v1/serializers.py index acff60f..784ad93 100644 --- a/game/api/v1/serializers.py +++ b/game/api/v1/serializers.py @@ -1,7 +1,8 @@ from rest_framework import serializers from rest_framework.exceptions import ValidationError -from game.models import Hero, Player, HeroInDeck, Deck +from game.models import Hero, Player, HeroInDeck, Deck, PlayerAuthSession +from game.services.jwt import read_jwt class CreateHeroSerializer(serializers.ModelSerializer): @@ -113,3 +114,29 @@ class GetDeckSerializer(serializers.ModelSerializer): class Meta: model = Deck fields = ("player", "heroes") + + +class ObtainTokenPairSerializer(serializers.Serializer): + refresh_token = serializers.CharField(max_length=300) + + def __init__(self, instance=None, data=None, **kwargs): + super().__init__(instance, data, **kwargs) + self.player_id = None + + def validate_refresh_token(self, value): + payload = read_jwt(value) + if not payload: + raise ValidationError("Token is incorrect or expired") + + if "jit" not in payload: + raise ValidationError("Token is incorrect") + + jit = payload["jit"] + + try: + session = PlayerAuthSession.objects.get(jit=jit) + except PlayerAuthSession.DoesNotExist: + return ValidationError("Incorrect user session") + + self.player_id = session.player.id + return value diff --git a/game/api/v1/views.py b/game/api/v1/views.py index ee3b564..d0cb1c0 100644 --- a/game/api/v1/views.py +++ b/game/api/v1/views.py @@ -19,6 +19,7 @@ from game.api.v1.serializers import ( ListHeroSerializer, CreateDeckSerializer, GetDeckSerializer, + ObtainTokenPairSerializer, ) from game.services.jwt import sign_jwt @@ -75,9 +76,8 @@ class PlayerCreateView(GenericAPIView, CreateModelMixin): serializer.is_valid(raise_exception=True) instance = self.perform_create(serializer) - # TODO: add JTI to refresh token access_jwt = sign_jwt({"id": instance.id, "type": "access"}, t_life=3600) - refresh_jwt = sign_jwt({"id": instance.id, "type": "refresh"}) + refresh_jwt = sign_jwt({"jit": instance.get_auth_session(), "type": "refresh"}) return Response( { "access_token": access_jwt, @@ -151,3 +151,13 @@ class RetireUpdateDeleteDeckView( "id", flat=True ) ) + + +class RefreshAuthKey(GenericAPIView): + serializer_class = ObtainTokenPairSerializer + + def post(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + access_jwt = sign_jwt({"id": serializer.player_id, "type": "access"}, t_life=3600) + return Response({"access_token": access_jwt}, status=status.HTTP_200_OK) diff --git a/game/authentication.py b/game/authentication.py index 026177f..75b1ea3 100644 --- a/game/authentication.py +++ b/game/authentication.py @@ -1,3 +1,4 @@ +from jwt import DecodeError from rest_framework import authentication from rest_framework import exceptions from .models import Player @@ -12,13 +13,20 @@ class PlayerAuthentication(authentication.BaseAuthentication): ): raise exceptions.AuthenticationFailed("No credentials provided.") - t = read_jwt(token) + try: + t = read_jwt(token) + except DecodeError: + raise exceptions.AuthenticationFailed("Token is incorrect") + if not t: raise exceptions.AuthenticationFailed("Token is incorrect of expired") - if "id" not in t: + if "id" not in t and "type" not in t: raise exceptions.AuthenticationFailed("No user data") + if t["type"] != "access": + raise exceptions.AuthenticationFailed("Incorrect token type") + try: user = Player.objects.get(id=int(t["id"])) except Player.DoesNotExist: diff --git a/game/models.py b/game/models.py index c65c3aa..24379de 100644 --- a/game/models.py +++ b/game/models.py @@ -11,6 +11,8 @@ from django.core.validators import ( from django.db import models from django.conf import settings +from common.generators import generate_charset + HER0_TYPES = [("WIZARD", "wizard"), ("ARCHER", "archer"), ("WARRIOR", "warrior")] @@ -31,6 +33,7 @@ class Player(models.Model): ): """saves user and creates deck for him with 16 heroes""" super(Player, self).save() + PlayerAuthSession.objects.create(player=self) deck = Deck.objects.create(player=self) types = ( ["ARCHER" for _ in range(4)] @@ -61,6 +64,9 @@ class Player(models.Model): def get_last_deck(self): return Deck.objects.filter(player=self).last() + def get_auth_session(self): + return PlayerAuthSession.objects.get(player=self).jit + def __str__(self): return self.name @@ -155,3 +161,10 @@ class HeroInDeck(models.Model): db_table = "hero_in_deck" verbose_name = "Hero in deck" verbose_name_plural = "Heroes in decks" + + +class PlayerAuthSession(models.Model): + player = models.OneToOneField( + Player, unique_for_month=True, on_delete=models.CASCADE + ) + jit = models.CharField(max_length=30, default=generate_charset(30)) diff --git a/game/services/jwt.py b/game/services/jwt.py index ad8c462..87f7e16 100644 --- a/game/services/jwt.py +++ b/game/services/jwt.py @@ -43,7 +43,3 @@ def read_jwt(token: str) -> dict | bool: payload.pop("exp", None) return payload - - -def generate_refresh_token(payload: dict) -> str: - return sign_jwt(payload) diff --git a/game/urls.py b/game/urls.py index 7f4fee1..f322001 100644 --- a/game/urls.py +++ b/game/urls.py @@ -6,11 +6,13 @@ from game.api.v1.views import ( PlayerCreateView, DeckCreateView, RetireUpdateDeleteDeckView, + RefreshAuthKey, ) urlpatterns = [ path("v1/hero/", ListCreateHeroView.as_view(), name="hero_api_create"), path("v1/hero/", RetrieveHeroView.as_view(), name="hero_api_retrieve"), + path("v1/player/refresh", RefreshAuthKey.as_view(), name="player_create_api"), path("v1/player/", PlayerCreateView.as_view(), name="player_create_api"), path("v1/deck/", DeckCreateView.as_view(), name="deck_create_api"), path(