Sprint 0: FastAPI-Skeleton, vollständiges DB-Schema (Alembic), Auth (JWT/bcrypt), Rollen-Dependency, CI
CI / backend-tests (push) Failing after 1s
CI / backend-tests (push) Failing after 1s
- Vollständiges Ziel-Schema aus Prompt 20 als initiale Migration (inkl. Karte-13-Vorbereitung: UUID-PKs, systemknoten) - Seed-Migration für Hauptserver-Datensatz (Sprintplan E6) - JWT-Login + /auth/me, require_roles-Dependency (Prompt 05 Berechtigungsmatrix) - Tests: health, login/me, Rollen-Ablehnung/-Zulassung (S0-Abnahmekriterien) - Gitea-Actions-CI: install -> migrate -> pytest mit Coverage-Gate 50% Nur Code/Config erzeugt, nicht lokal installiert oder ausgeführt (Deployment-Regel). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
"""Konfiguration aus Umgebungsvariablen (.env auf dem Zielsystem, nie committen)."""
|
||||
|
||||
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
|
||||
|
||||
database_url: str = "postgresql+asyncpg://mabea:changeme@localhost:5432/mabea"
|
||||
jwt_secret_key: str = "change-me-to-a-long-random-value"
|
||||
jwt_algorithm: str = "HS256"
|
||||
access_token_expire_minutes: int = 480
|
||||
|
||||
# ID des systemknoten-Datensatzes mit typ='haupt' (Sprintplan E6).
|
||||
systemknoten_id: int = 1
|
||||
|
||||
|
||||
settings = Settings()
|
||||
@@ -0,0 +1,33 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
import jwt
|
||||
from passlib.context import CryptContext
|
||||
|
||||
from app.core.app_settings import settings
|
||||
|
||||
_pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
|
||||
|
||||
def hash_password(password: str) -> str:
|
||||
return _pwd_context.hash(password)
|
||||
|
||||
|
||||
def verify_password(plain_password: str, password_hash: str) -> bool:
|
||||
return _pwd_context.verify(plain_password, password_hash)
|
||||
|
||||
|
||||
def create_access_token(*, subject: str, roles: list[str]) -> str:
|
||||
expire = datetime.now(timezone.utc) + timedelta(minutes=settings.access_token_expire_minutes)
|
||||
payload = {"sub": subject, "roles": roles, "exp": expire}
|
||||
return jwt.encode(payload, settings.jwt_secret_key, algorithm=settings.jwt_algorithm)
|
||||
|
||||
|
||||
class InvalidTokenError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def decode_access_token(token: str) -> dict:
|
||||
try:
|
||||
return jwt.decode(token, settings.jwt_secret_key, algorithms=[settings.jwt_algorithm])
|
||||
except jwt.PyJWTError as exc:
|
||||
raise InvalidTokenError(str(exc)) from exc
|
||||
Reference in New Issue
Block a user