SPB Git

spb/uqo-chat

Public
3.4 KB · 110 lines python
1"""JWT helpers, password-less auth utilities and FastAPI dependencies."""23from __future__ import annotations45import hashlib6import secrets7from datetime import UTC, datetime, timedelta8from typing import Any910from fastapi import Depends, HTTPException, Request, status11from jose import JWTError, jwt1213from app.core.config import Settings, get_settings1415ALGO = "HS256"16COOKIE_NAME = "uqo_session"171819def hash_user_id(user_id: str) -> str:20    return hashlib.sha256(user_id.encode()).hexdigest()212223def create_token(sub: str, role: str, settings: Settings, ttl: timedelta | None = None,24                 kind: str = "access") -> str:25    now = datetime.now(UTC)26    exp = now + (ttl or timedelta(hours=settings.JWT_TTL_HOURS))27    payload: dict[str, Any] = {"sub": sub, "role": role, "kind": kind, "iat": now, "exp": exp}28    return jwt.encode(payload, settings.JWT_SECRET.get_secret_value(), algorithm=ALGO)293031def decode_token(token: str, settings: Settings) -> dict[str, Any] | None:32    try:33        return jwt.decode(token, settings.JWT_SECRET.get_secret_value(), algorithms=[ALGO])34    except JWTError:35        return None363738def new_magic_token() -> str:39    return secrets.token_urlsafe(32)404142def role_for_email(email: str, settings: Settings) -> str:43    e = email.lower()44    if e in settings.admin_emails:45        return "admin"46    if e in settings.professor_emails:47        return "professor"48    return "student"495051def email_allowed(email: str, settings: Settings) -> bool:52    e = email.lower().strip()53    if "@" not in e:54        return False55    if e in settings.invited_emails or e in settings.professor_emails or e in settings.admin_emails:56        return True57    domain = e.split("@", 1)[1]58    return domain in settings.allowed_domains596061class AuthUser:62    def __init__(self, user_id: str, role: str) -> None:63        self.id = user_id64        self.role = role6566    @property67    def is_professor(self) -> bool:68        return self.role in {"professor", "admin"}6970    @property71    def is_admin(self) -> bool:72        return self.role == "admin"737475def _extract_token(request: Request) -> str | None:76    auth = request.headers.get("authorization", "")77    if auth.lower().startswith("bearer "):78        return auth[7:].strip()79    return request.cookies.get(COOKIE_NAME)808182async def get_current_user(request: Request, settings: Settings = Depends(get_settings)) -> AuthUser:83    token = _extract_token(request)84    if not token:85        raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail="Connexion requise.")86    payload = decode_token(token, settings)87    if not payload or payload.get("kind") != "access":88        raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail="Session expirée. Reconnecte-toi.")89    return AuthUser(str(payload["sub"]), str(payload.get("role", "student")))909192async def get_optional_user(request: Request,93                            settings: Settings = Depends(get_settings)) -> AuthUser | None:94    try:95        return await get_current_user(request, settings)96    except HTTPException:97        return None9899100async def require_professor(user: AuthUser = Depends(get_current_user)) -> AuthUser:101    if not user.is_professor:102        raise HTTPException(status.HTTP_403_FORBIDDEN, detail="Réservé au professeur.")103    return user104105106async def require_admin(user: AuthUser = Depends(get_current_user)) -> AuthUser:107    if not user.is_admin:108        raise HTTPException(status.HTTP_403_FORBIDDEN, detail="Réservé à l'administrateur.")109    return user110
Files · cd08d441f7c277e8fedd6f3d3be82f02b44553ba
Menu

Theme