added jit for refresh token, refresh access token endpoint

This commit is contained in:
Alexander Karpov 2022-06-10 23:33:40 +03:00
parent 1bb56c25fe
commit c795a5807e
8 changed files with 73 additions and 9 deletions

0
common/__init__.py Normal file
View File

8
common/generators.py Normal file
View File

@ -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)
)

View File

@ -1,7 +1,8 @@
from rest_framework import serializers from rest_framework import serializers
from rest_framework.exceptions import ValidationError 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): class CreateHeroSerializer(serializers.ModelSerializer):
@ -113,3 +114,29 @@ class GetDeckSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Deck model = Deck
fields = ("player", "heroes") 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

View File

@ -19,6 +19,7 @@ from game.api.v1.serializers import (
ListHeroSerializer, ListHeroSerializer,
CreateDeckSerializer, CreateDeckSerializer,
GetDeckSerializer, GetDeckSerializer,
ObtainTokenPairSerializer,
) )
from game.services.jwt import sign_jwt from game.services.jwt import sign_jwt
@ -75,9 +76,8 @@ class PlayerCreateView(GenericAPIView, CreateModelMixin):
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
instance = self.perform_create(serializer) instance = self.perform_create(serializer)
# TODO: add JTI to refresh token
access_jwt = sign_jwt({"id": instance.id, "type": "access"}, t_life=3600) 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( return Response(
{ {
"access_token": access_jwt, "access_token": access_jwt,
@ -151,3 +151,13 @@ class RetireUpdateDeleteDeckView(
"id", flat=True "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)

View File

@ -1,3 +1,4 @@
from jwt import DecodeError
from rest_framework import authentication from rest_framework import authentication
from rest_framework import exceptions from rest_framework import exceptions
from .models import Player from .models import Player
@ -12,13 +13,20 @@ class PlayerAuthentication(authentication.BaseAuthentication):
): ):
raise exceptions.AuthenticationFailed("No credentials provided.") raise exceptions.AuthenticationFailed("No credentials provided.")
try:
t = read_jwt(token) t = read_jwt(token)
except DecodeError:
raise exceptions.AuthenticationFailed("Token is incorrect")
if not t: if not t:
raise exceptions.AuthenticationFailed("Token is incorrect of expired") 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") raise exceptions.AuthenticationFailed("No user data")
if t["type"] != "access":
raise exceptions.AuthenticationFailed("Incorrect token type")
try: try:
user = Player.objects.get(id=int(t["id"])) user = Player.objects.get(id=int(t["id"]))
except Player.DoesNotExist: except Player.DoesNotExist:

View File

@ -11,6 +11,8 @@ from django.core.validators import (
from django.db import models from django.db import models
from django.conf import settings from django.conf import settings
from common.generators import generate_charset
HER0_TYPES = [("WIZARD", "wizard"), ("ARCHER", "archer"), ("WARRIOR", "warrior")] 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""" """saves user and creates deck for him with 16 heroes"""
super(Player, self).save() super(Player, self).save()
PlayerAuthSession.objects.create(player=self)
deck = Deck.objects.create(player=self) deck = Deck.objects.create(player=self)
types = ( types = (
["ARCHER" for _ in range(4)] ["ARCHER" for _ in range(4)]
@ -61,6 +64,9 @@ class Player(models.Model):
def get_last_deck(self): def get_last_deck(self):
return Deck.objects.filter(player=self).last() return Deck.objects.filter(player=self).last()
def get_auth_session(self):
return PlayerAuthSession.objects.get(player=self).jit
def __str__(self): def __str__(self):
return self.name return self.name
@ -155,3 +161,10 @@ class HeroInDeck(models.Model):
db_table = "hero_in_deck" db_table = "hero_in_deck"
verbose_name = "Hero in deck" verbose_name = "Hero in deck"
verbose_name_plural = "Heroes in decks" 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))

View File

@ -43,7 +43,3 @@ def read_jwt(token: str) -> dict | bool:
payload.pop("exp", None) payload.pop("exp", None)
return payload return payload
def generate_refresh_token(payload: dict) -> str:
return sign_jwt(payload)

View File

@ -6,11 +6,13 @@ from game.api.v1.views import (
PlayerCreateView, PlayerCreateView,
DeckCreateView, DeckCreateView,
RetireUpdateDeleteDeckView, RetireUpdateDeleteDeckView,
RefreshAuthKey,
) )
urlpatterns = [ urlpatterns = [
path("v1/hero/", ListCreateHeroView.as_view(), name="hero_api_create"), path("v1/hero/", ListCreateHeroView.as_view(), name="hero_api_create"),
path("v1/hero/<uuid:uuid>", RetrieveHeroView.as_view(), name="hero_api_retrieve"), path("v1/hero/<uuid:uuid>", 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/player/", PlayerCreateView.as_view(), name="player_create_api"),
path("v1/deck/", DeckCreateView.as_view(), name="deck_create_api"), path("v1/deck/", DeckCreateView.as_view(), name="deck_create_api"),
path( path(