Added API-keys for live game data

This commit is contained in:
Mawoka
2022-09-19 21:29:04 +02:00
parent fe3dde3f3a
commit e2814c7944
6 changed files with 114 additions and 21 deletions
+16 -3
View File
@@ -1,7 +1,7 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
import uuid
from datetime import timedelta
from typing import Dict
from typing import Optional
@@ -18,9 +18,9 @@ from jose import JWTError, jwt
from passlib.hash import argon2
from classquiz.cache import get_cache
from classquiz.config import settings
from classquiz.config import settings, redis
from datetime import datetime
from classquiz.db.models import User, TokenData
from classquiz.db.models import User, TokenData, ApiKey
settings = settings()
@@ -148,3 +148,16 @@ async def check_token(token: str = Depends(oauth2_scheme)):
except JWTError:
raise credentials_exception
return token_data.email
async def check_api_key(key: str) -> uuid.UUID | None:
redis_res = await redis.get(f"apikey:{key}")
if redis_res is None:
key2 = await ApiKey.objects.get_or_none(key=key)
if key2 is None:
return None
else:
await redis.set(f"apikey:{key}", key2.user.id.hex, ex=3600)
return key2.user.id
else:
return uuid.UUID(redis_res)
+11
View File
@@ -43,6 +43,16 @@ class User(ormar.Model):
use_enum_values = True
class ApiKey(ormar.Model):
key: str = ormar.String(max_length=48, min_length=48, primary_key=True)
user: Optional[User] = ormar.ForeignKey(User)
class Meta:
tablename = "api_keys"
metadata = metadata
database = database
class UserSession(ormar.Model):
"""
The user session model for user-sessions
@@ -151,6 +161,7 @@ class TokenData(BaseModel):
class PlayGame(BaseModel):
quiz_id: uuid.UUID | str
description: str
user_id: uuid.UUID
title: str
questions: list[QuizQuestion]
game_id: uuid.UUID
+25 -16
View File
@@ -7,7 +7,6 @@ import re
import uuid
from datetime import datetime
from random import randint
from typing import Dict
import ormar.exceptions
@@ -17,7 +16,7 @@ from fastapi.responses import JSONResponse, StreamingResponse
from pydantic import ValidationError, BaseModel
import bleach
from classquiz.auth import get_current_user
from classquiz.auth import get_current_user, check_api_key
from classquiz.config import redis, settings, storage, meilisearch
from classquiz.db.models import Quiz, QuizInput, User, PlayGame, GameSession, GameAnswer1, GameAnswer2
from classquiz.kahoot_importer.import_quiz import import_quiz
@@ -113,6 +112,7 @@ async def start_quiz(
captcha_enabled=captcha_enabled,
cover_image=quiz.cover_image,
game_mode=game_mode,
user_id=user.id,
)
await redis.set(f"game:{str(game.game_pin)}", game.json(), ex=18000)
return {**quiz.dict(exclude={"id"}), **game.dict(exclude={"questions"})}
@@ -260,12 +260,15 @@ class GetLiveDataResponse(BaseModel):
player_count: int
@router.get("/live", response_model=GetLiveDataResponse)
async def get_live_game_data(game_pin: int):
@router.get("/live", response_model=GetLiveDataResponse, tags=["live"])
async def get_live_game_data(game_pin: int, api_key: str):
user_id = await check_api_key(api_key)
redis_res = await redis.get(f"game:{game_pin}")
if redis_res is None:
raise HTTPException(status_code=404, detail="Game not found")
if redis_res is None or user_id is None:
raise HTTPException(status_code=404, detail="Game not found or API key not found")
game = PlayGame.parse_raw(redis_res)
if game.user_id != user_id:
raise HTTPException(status_code=404, detail="Game not found or API key not found")
data = GameSession.parse_raw(await redis.get(f"game_session:{game_pin}"))
for i in range(0, len(game.questions)):
res = await redis.get(f"game_session:{game_pin}:{i}")
@@ -279,7 +282,7 @@ async def get_live_game_data(game_pin: int):
return GetLiveDataResponse(quiz=game, data=data, player_count=player_count)
@router.get("/live/user_count")
@router.get("/live/user_count", tags=["live"])
async def get_game_user_count(game_pin: int) -> dict[str, int]:
# if redis_res is None:
# raise HTTPException(status_code=404, detail="Game not found")
@@ -287,13 +290,16 @@ async def get_game_user_count(game_pin: int) -> dict[str, int]:
return {"player_count": player_count}
@router.get("/live/players", response_model=GameSession)
async def get_game_session(game_pin: int):
@router.get("/live/players", response_model=GameSession, tags=["live"])
async def get_game_session(game_pin: int, api_key: str):
user_id = await check_api_key(api_key)
redis_res = await redis.get(f"game_session:{game_pin}")
if redis_res is None:
raise HTTPException(status_code=404, detail="Game not found")
if redis_res is None or user_id is None:
raise HTTPException(status_code=404, detail="Game not found or API key not found")
data = GameSession.parse_raw(redis_res)
game = PlayGame.parse_raw(await redis.get(f"game:{game_pin}"))
if game.user_id != user_id:
raise HTTPException(status_code=404, detail="Game not found or API key not found")
for i in range(0, len(game.questions)):
res = await redis.get(f"game_session:{game_pin}:{i}")
if res is None:
@@ -305,12 +311,15 @@ async def get_game_session(game_pin: int):
return data
@router.post("/live/set_question")
async def set_next_question(game_pin: int, question_number: int):
@router.post("/live/set_question", tags=["live"])
async def set_next_question(game_pin: int, question_number: int, api_key: str):
user_id = await check_api_key(api_key)
redis_res = await redis.get(f"game:{game_pin}")
if redis_res is None:
raise HTTPException(status_code=404, detail="Game not found")
game_data = PlayGame.parse_raw(await redis.get(f"game:{game_pin}"))
if redis_res is None or user_id is None:
raise HTTPException(status_code=404, detail="Game not found or API key not found")
game_data = PlayGame.parse_raw(redis_res)
if game_data.user_id != user_id:
raise HTTPException(status_code=404, detail="Game not found or API key not found")
game_data.current_question = question_number
await redis.set(f"game:{game_pin}", game_data.json())
await sio.emit(
+23 -1
View File
@@ -31,7 +31,7 @@ from classquiz.config import redis, settings, meilisearch
import uuid
import bleach
from pydantic import BaseModel
from classquiz.db.models import User, UserSession, UpdatePassword, Token, Quiz
from classquiz.db.models import User, UserSession, UpdatePassword, Token, Quiz, ApiKey
from classquiz.emails import send_register_email, send_forgotten_password_email
settings = settings()
@@ -312,3 +312,25 @@ async def get_email_from_jwt(data: GetEmailFromJWT):
except JWTError as e:
print(e)
raise HTTPException(status_code=401)
@router.post("/api_keys", response_model=ApiKey, response_model_include={"key"})
async def generate_api_key(user: User = Depends(get_current_user)):
key = ApiKey(key=os.urandom(24).hex(), user=user)
await key.save()
return key.dict(include={"key"})
@router.get("/api_keys", response_model=list[ApiKey], response_model_include={"key"})
async def list_api_keys(user: User = Depends(get_current_user)):
keys = await ApiKey.objects.filter(user=user).all()
return keys
@router.delete("/api_keys")
async def delete_api_key(api_key: str, user: User = Depends(get_current_user)):
key = await ApiKey.objects.get_or_none(key=api_key)
if key is None:
raise HTTPException(status_code=404, detail="Key not found")
await redis.delete(f"apikey:{key.key}")
await key.delete()
+4 -1
View File
@@ -77,7 +77,10 @@ async def join_game(sid: str, data: dict):
await sio.save_session(sid, session)
await sio.emit(
"joined_game",
{**json.loads(game_data.json(exclude={"quiz_id", "questions"})), "question_count": len(game_data.questions)},
{
**json.loads(game_data.json(exclude={"quiz_id", "questions", "user_id"})),
"question_count": len(game_data.questions),
},
room=sid,
)
redis_res = await redis.get(f"game_session:{data.game_pin}")
@@ -0,0 +1,35 @@
"""added apikey
Revision ID: ec6cf07ff68a
Revises: cda6903dfc0c
Create Date: 2022-09-19 21:04:37.967466
"""
from alembic import op
import sqlalchemy as sa
import ormar
# revision identifiers, used by Alembic.
revision = "ec6cf07ff68a"
down_revision = "cda6903dfc0c"
branch_labels = None
depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"api_keys",
sa.Column("key", sa.String(length=48), nullable=False),
sa.Column("user", ormar.fields.sqlalchemy_uuid.CHAR(32), nullable=True),
sa.ForeignKeyConstraint(["user"], ["users.id"], name="fk_api_keys_users_id_user"),
sa.PrimaryKeyConstraint("key"),
)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("api_keys")
# ### end Alembic commands ###