mirror of
https://github.com/evgen-app/chess_rpg_backend.git
synced 2024-11-22 09:37:05 +03:00
added jwt auth, base player logic
This commit is contained in:
parent
00387092df
commit
80a63fe252
|
@ -15,19 +15,18 @@ from pathlib import Path
|
|||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'django-insecure-%_8sy196w4hzo9^cp9(@r=i+amh47r4mxfhq_(ok&=c(@%bhmk'
|
||||
TOKEN_EXP = 2678400 # 31 day
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
|
@ -76,7 +75,6 @@ TEMPLATES = [
|
|||
|
||||
WSGI_APPLICATION = 'chess_backend.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
|
||||
|
||||
|
@ -87,7 +85,6 @@ DATABASES = {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators
|
||||
|
||||
|
@ -106,7 +103,6 @@ AUTH_PASSWORD_VALIDATORS = [
|
|||
},
|
||||
]
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/4.0/topics/i18n/
|
||||
|
||||
|
|
25
game/authentication.py
Normal file
25
game/authentication.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
from rest_framework import authentication
|
||||
from rest_framework import exceptions
|
||||
from .models import Player
|
||||
from .services.jwt import read_jwt
|
||||
|
||||
|
||||
class PlayerAuthentication(authentication.BaseAuthentication):
|
||||
def authenticate(self, request):
|
||||
token = request.headers["Authorization"]
|
||||
if not token:
|
||||
raise exceptions.AuthenticationFailed("No credentials provided.")
|
||||
|
||||
t = read_jwt(token)
|
||||
if not t:
|
||||
raise exceptions.AuthenticationFailed("Token is incorrect of expired")
|
||||
|
||||
if "id" not in t:
|
||||
raise exceptions.AuthenticationFailed("No user data")
|
||||
|
||||
try:
|
||||
user = Player.objects.get(id=int(t["id"]))
|
||||
except Player.DoesNotExist:
|
||||
raise exceptions.AuthenticationFailed("No such user")
|
||||
|
||||
return user, None
|
|
@ -1,7 +1,15 @@
|
|||
import random
|
||||
import uuid
|
||||
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
from django.core.files import File
|
||||
from django.core.validators import (
|
||||
MinValueValidator,
|
||||
MaxValueValidator,
|
||||
MinLengthValidator,
|
||||
MaxLengthValidator,
|
||||
)
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
|
||||
HER0_TYPES = [("WIZARD", "wizard"), ("ARCHER", "archer"), ("WARRIOR", "warrior")]
|
||||
|
||||
|
@ -9,17 +17,47 @@ HER0_TYPES = [("WIZARD", "wizard"), ("ARCHER", "archer"), ("WARRIOR", "warrior")
|
|||
class Player(models.Model):
|
||||
"""base model to handle and store users"""
|
||||
|
||||
# TODO: connect real TON wallet
|
||||
ton_wallet = models.CharField(max_length=50, verbose_name="TON wallet")
|
||||
ton_wallet = models.CharField(
|
||||
verbose_name="TON wallet",
|
||||
validators=[MinLengthValidator(48), MaxLengthValidator(48)],
|
||||
max_length=48,
|
||||
unique=True,
|
||||
)
|
||||
name = models.CharField(max_length=100, blank=True)
|
||||
added = models.DateTimeField(auto_now_add=True)
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def save(
|
||||
self, force_insert=False, force_update=False, using=None, update_fields=None
|
||||
):
|
||||
super(Player, self).save()
|
||||
types = (
|
||||
["ARCHER" for _ in range(4)]
|
||||
+ ["WARRIOR" for _ in range(6)]
|
||||
+ ["WIZARD" for _ in range(2)]
|
||||
+ [random.choice(HER0_TYPES)[0] for _ in range(4)]
|
||||
)
|
||||
for t in types:
|
||||
hero = Hero()
|
||||
hero.player = self
|
||||
hero.type = t
|
||||
|
||||
with open("/home/sanspie/Projects/chess_rpg_backend/media/dummy.jpg", "rb+") as file:
|
||||
hero.idle_img = File(file, name="dummy.jpg")
|
||||
hero.attack_img = File(file, name="dummy.jpg")
|
||||
hero.die_img = File(file, name="dummy.jpg")
|
||||
|
||||
hero.health = random.randint(0, 10)
|
||||
hero.attack = random.randint(0, 10)
|
||||
hero.speed = random.randint(0, 10)
|
||||
|
||||
hero.save()
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
indexes = [models.Index(fields=["ton_wallet"])]
|
||||
ordering = ["-added"]
|
||||
ordering = ["-created"]
|
||||
verbose_name = "player"
|
||||
verbose_name_plural = "players"
|
||||
|
||||
|
@ -28,12 +66,12 @@ class Hero(models.Model):
|
|||
"""Model to store heroes and their stats, connected to player"""
|
||||
|
||||
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
|
||||
# player = models.ForeignKey(
|
||||
# Player,
|
||||
# on_delete=models.CASCADE,
|
||||
# related_name="heroes",
|
||||
# related_query_name="hero",
|
||||
# )
|
||||
player = models.ForeignKey(
|
||||
Player,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="heroes",
|
||||
related_query_name="hero",
|
||||
)
|
||||
added = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
type = models.CharField(blank=False, choices=HER0_TYPES, max_length=7)
|
||||
|
@ -43,6 +81,9 @@ class Hero(models.Model):
|
|||
health = models.IntegerField(
|
||||
validators=[MinValueValidator(0), MaxValueValidator(10)], blank=False
|
||||
)
|
||||
attack = models.IntegerField(
|
||||
validators=[MinValueValidator(0), MaxValueValidator(10)], blank=False
|
||||
)
|
||||
speed = models.IntegerField(
|
||||
validators=[MinValueValidator(0), MaxValueValidator(10)], blank=False
|
||||
)
|
||||
|
|
|
@ -1,12 +1,20 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from game.models import Hero
|
||||
from game.models import Hero, Player
|
||||
|
||||
|
||||
class CreateHeroSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Hero
|
||||
fields = ("type", "idle_img", "attack_img", "die_img", "health", "speed")
|
||||
fields = (
|
||||
"type",
|
||||
"idle_img",
|
||||
"attack_img",
|
||||
"die_img",
|
||||
"health",
|
||||
"attack",
|
||||
"speed",
|
||||
)
|
||||
|
||||
|
||||
class GetHeroSerializer(serializers.ModelSerializer):
|
||||
|
@ -19,5 +27,12 @@ class GetHeroSerializer(serializers.ModelSerializer):
|
|||
"attack_img",
|
||||
"die_img",
|
||||
"health",
|
||||
"attack",
|
||||
"speed",
|
||||
)
|
||||
|
||||
|
||||
class CreatePlayerView(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Player
|
||||
fields = ("ton_wallet", "name")
|
||||
|
|
0
game/services/__init__.py
Normal file
0
game/services/__init__.py
Normal file
49
game/services/jwt.py
Normal file
49
game/services/jwt.py
Normal file
|
@ -0,0 +1,49 @@
|
|||
import jwt
|
||||
import pytz
|
||||
|
||||
from datetime import datetime
|
||||
from django.conf import settings
|
||||
from jwt import ExpiredSignatureError, InvalidSignatureError
|
||||
|
||||
TIMEZONE = pytz.timezone("Europe/Moscow")
|
||||
|
||||
|
||||
def sign_jwt(data: dict, t_life: None | int = None) -> str:
|
||||
"""generate and sign jwt with iat and exp using data from settings"""
|
||||
iat = int(datetime.now(tz=TIMEZONE).timestamp())
|
||||
exp = iat + settings.TOKEN_EXP if not t_life else iat + t_life
|
||||
payload = {"iat": iat, "exp": exp}
|
||||
for nm, el in data.items():
|
||||
if nm not in ["iat", "exp"]:
|
||||
payload[nm] = el
|
||||
|
||||
secret = settings.SECRET_KEY
|
||||
token = jwt.encode(payload=payload, key=secret)
|
||||
return token
|
||||
|
||||
|
||||
def read_jwt(token: str) -> dict | bool:
|
||||
"""reads jwt, validates it and return payload if correct"""
|
||||
header_data = jwt.get_unverified_header(token)
|
||||
secret = settings.SECRET_KEY
|
||||
try:
|
||||
payload = jwt.decode(token, key=secret, algorithms=[header_data["alg"]])
|
||||
except ExpiredSignatureError as e:
|
||||
return False
|
||||
except InvalidSignatureError as e:
|
||||
return False
|
||||
|
||||
if "exp" not in payload:
|
||||
return False
|
||||
|
||||
if int(datetime.now(tz=TIMEZONE).timestamp()) > payload["exp"]:
|
||||
return False
|
||||
|
||||
payload.pop("iat", None)
|
||||
payload.pop("exp", None)
|
||||
|
||||
return payload
|
||||
|
||||
|
||||
def generate_refresh_token(payload: dict) -> str:
|
||||
return sign_jwt(payload)
|
|
@ -1,8 +1,9 @@
|
|||
from django.urls import path
|
||||
|
||||
from game.views import CreateHeroView, RetrieveHeroView
|
||||
from game.views import CreateHeroView, RetrieveHeroView, PlayerCreateView
|
||||
|
||||
urlpatterns = [
|
||||
path("hero/", CreateHeroView.as_view(), name="hero_api_create"),
|
||||
path("hero/<uuid:uuid>", RetrieveHeroView.as_view(), name="hero_api_retrieve"),
|
||||
path("player/", PlayerCreateView.as_view(), name="player_create_api"),
|
||||
]
|
|
@ -1,15 +1,17 @@
|
|||
from rest_framework import status
|
||||
|
||||
from rest_framework.generics import GenericAPIView, UpdateAPIView
|
||||
from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin
|
||||
from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin, ListModelMixin
|
||||
from rest_framework.response import Response
|
||||
|
||||
from game.authentication import PlayerAuthentication
|
||||
from game.models import Hero
|
||||
from game.serializers import CreateHeroSerializer, GetHeroSerializer
|
||||
from game.serializers import CreateHeroSerializer, GetHeroSerializer, CreatePlayerView
|
||||
from game.services.jwt import sign_jwt
|
||||
|
||||
|
||||
class CreateHeroView(GenericAPIView, CreateModelMixin):
|
||||
serializer_class = CreateHeroSerializer
|
||||
class CreateHeroView(GenericAPIView, CreateModelMixin, ListModelMixin):
|
||||
authentication_classes = (PlayerAuthentication,)
|
||||
|
||||
def perform_create(self, serializer):
|
||||
return serializer.save()
|
||||
|
@ -20,9 +22,22 @@ class CreateHeroView(GenericAPIView, CreateModelMixin):
|
|||
instance = self.perform_create(serializer)
|
||||
return Response({"uuid": instance.uuid}, status=status.HTTP_201_CREATED)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
return self.list(request, *args, **kwargs)
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.request.method == "GET":
|
||||
return GetHeroSerializer
|
||||
else:
|
||||
return CreateHeroSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
return Hero.objects.filter(player_id=self.request.user.id)
|
||||
|
||||
|
||||
class RetrieveHeroView(RetrieveModelMixin, UpdateAPIView, GenericAPIView):
|
||||
serializer_class = GetHeroSerializer
|
||||
authentication_classes = (PlayerAuthentication,)
|
||||
lookup_field = "uuid"
|
||||
queryset = Hero.objects.all()
|
||||
|
||||
|
@ -34,3 +49,20 @@ class RetrieveHeroView(RetrieveModelMixin, UpdateAPIView, GenericAPIView):
|
|||
|
||||
def patch(self, request, *args, **kwargs):
|
||||
return self.partial_update(request, *args, **kwargs)
|
||||
|
||||
|
||||
class PlayerCreateView(GenericAPIView, CreateModelMixin):
|
||||
serializer_class = CreatePlayerView
|
||||
|
||||
def perform_create(self, serializer):
|
||||
return serializer.save()
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
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"})
|
||||
return Response({"access_token": access_jwt, "refresh_token": refresh_jwt}, status=status.HTTP_201_CREATED)
|
||||
|
|
|
@ -4,3 +4,4 @@ djangorestframework==3.13.1
|
|||
Pillow==9.1.1
|
||||
pytz==2022.1
|
||||
sqlparse==0.4.2
|
||||
PyJWT==2.4.0
|
Loading…
Reference in New Issue
Block a user