f37dc9fa76
FastAPI + Postgres backend with fastapi-users auth, games/characters CRUD, and a WebSocket chat endpoint where an x.ai Grok DM narrates play, rolls dice, and maintains character sheets via tool calls. React + Tailwind frontend covers registration/login, game list and creation, participation-code join flow, character viewer, and a real-time chat session view. Docker Compose skeleton (Postgres + backend) included; Caddy/frontend container wiring is deferred to the deploy milestone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
69 lines
2.1 KiB
Python
69 lines
2.1 KiB
Python
import uuid
|
|
from collections.abc import AsyncGenerator
|
|
|
|
from fastapi import Depends, Request
|
|
from fastapi_users import BaseUserManager, FastAPIUsers, UUIDIDMixin
|
|
from fastapi_users.authentication import (
|
|
AuthenticationBackend,
|
|
BearerTransport,
|
|
JWTStrategy,
|
|
)
|
|
from fastapi_users.db import SQLAlchemyUserDatabase
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.auth.email import send_email
|
|
from app.config import settings
|
|
from app.db import get_async_session
|
|
from app.models.user import User
|
|
|
|
JWT_LIFETIME_SECONDS = 60 * 60 * 24 * 7 # 7 days
|
|
|
|
|
|
async def get_user_db(
|
|
session: AsyncSession = Depends(get_async_session),
|
|
) -> AsyncGenerator[SQLAlchemyUserDatabase, None]:
|
|
yield SQLAlchemyUserDatabase(session, User)
|
|
|
|
|
|
class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):
|
|
reset_password_token_secret = settings.reset_password_secret
|
|
verification_token_secret = settings.verification_token_secret
|
|
|
|
async def on_after_forgot_password(
|
|
self, user: User, token: str, request: Request | None = None
|
|
) -> None:
|
|
reset_url = f"{settings.frontend_origin}/reset-password?token={token}"
|
|
await send_email(
|
|
to=user.email,
|
|
subject="Passwort zurücksetzen — DungeonsDragons",
|
|
body=(
|
|
f"Hallo {user.name},\n\n"
|
|
f"klicke auf den folgenden Link, um dein Passwort zurückzusetzen:\n{reset_url}\n\n"
|
|
"Falls du das nicht angefordert hast, ignoriere diese E-Mail."
|
|
),
|
|
)
|
|
|
|
|
|
async def get_user_manager(
|
|
user_db: SQLAlchemyUserDatabase = Depends(get_user_db),
|
|
) -> AsyncGenerator[UserManager, None]:
|
|
yield UserManager(user_db)
|
|
|
|
|
|
bearer_transport = BearerTransport(tokenUrl="auth/jwt/login")
|
|
|
|
|
|
def get_jwt_strategy() -> JWTStrategy:
|
|
return JWTStrategy(secret=settings.jwt_secret, lifetime_seconds=JWT_LIFETIME_SECONDS)
|
|
|
|
|
|
auth_backend = AuthenticationBackend(
|
|
name="jwt",
|
|
transport=bearer_transport,
|
|
get_strategy=get_jwt_strategy,
|
|
)
|
|
|
|
fastapi_users = FastAPIUsers[User, uuid.UUID](get_user_manager, [auth_backend])
|
|
|
|
current_active_user = fastapi_users.current_user(active=True)
|