v0.1.0 — Réécriture complète de OhmStreaming
Nouvelle version réécrite de zéro : recherche multi-sources (Vostfree, French-Manga), extraction 2 niveaux, proxy vidéo intégré, streaming/téléchargement HLS, métadonnées Kitsu, bibliothèque locale, comptes JWT + administration, découverte fusionnée, indexeur Torznab (Sonarr/Prowlarr).
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
"""Routes d'authentification (cookies httponly, adaptées à l'UI htmx)."""
|
||||
|
||||
import logging
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, Form, HTTPException, Request, Response, status
|
||||
from fastapi.responses import RedirectResponse
|
||||
|
||||
from app import auth
|
||||
from app.auth import User
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/auth", tags=["auth"])
|
||||
|
||||
ACCESS_COOKIE = "ohm_access"
|
||||
REFRESH_COOKIE = "ohm_refresh"
|
||||
|
||||
|
||||
def set_auth_cookies(response: Response, user: User, refresh_token: str) -> None:
|
||||
response.set_cookie(
|
||||
ACCESS_COOKIE, auth.create_access_token(user), httponly=True, samesite="lax"
|
||||
)
|
||||
response.set_cookie(REFRESH_COOKIE, refresh_token, httponly=True, samesite="lax", path="/")
|
||||
|
||||
|
||||
def clear_auth_cookies(response: Response) -> None:
|
||||
response.delete_cookie(ACCESS_COOKIE)
|
||||
response.delete_cookie(REFRESH_COOKIE, path="/")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- dépendances
|
||||
|
||||
|
||||
async def current_user(request: Request, response: Response) -> User:
|
||||
"""Utilisateur courant via cookie d'accès ; tente un refresh si expiré."""
|
||||
token = request.cookies.get(ACCESS_COOKIE)
|
||||
if token:
|
||||
payload = auth.decode_access_token(token)
|
||||
if payload:
|
||||
user = await auth.get_user(int(payload["sub"]))
|
||||
if user:
|
||||
return user
|
||||
|
||||
refresh = request.cookies.get(REFRESH_COOKIE)
|
||||
if refresh:
|
||||
user = await auth.use_refresh_token(refresh)
|
||||
if user:
|
||||
new_refresh = await auth.create_refresh_token(user.id)
|
||||
set_auth_cookies(response, user, new_refresh)
|
||||
return user
|
||||
|
||||
raise HTTPException(status.HTTP_303_SEE_OTHER, headers={"Location": "/login"})
|
||||
|
||||
|
||||
async def require_admin(user: Annotated[User, Depends(current_user)]) -> User:
|
||||
if not user.is_admin:
|
||||
raise HTTPException(status.HTTP_403_FORBIDDEN, detail="Droits administrateur requis")
|
||||
return user
|
||||
|
||||
|
||||
CurrentUser = Annotated[User, Depends(current_user)]
|
||||
AdminUser = Annotated[User, Depends(require_admin)]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- routes
|
||||
|
||||
|
||||
@router.post("/register")
|
||||
async def register(
|
||||
response: Response,
|
||||
username: Annotated[str, Form(min_length=3, max_length=32)],
|
||||
password: Annotated[str, Form(min_length=6)],
|
||||
) -> RedirectResponse:
|
||||
row = await auth.db.fetchone("SELECT id FROM users WHERE username = ?", (username.strip(),))
|
||||
if row is not None:
|
||||
raise HTTPException(status.HTTP_409_CONFLICT, detail="Nom d'utilisateur déjà pris")
|
||||
user = await auth.create_user(username, password)
|
||||
refresh = await auth.create_refresh_token(user.id)
|
||||
redirect = RedirectResponse("/", status.HTTP_303_SEE_OTHER)
|
||||
set_auth_cookies(redirect, user, refresh)
|
||||
return redirect
|
||||
|
||||
|
||||
@router.post("/login")
|
||||
async def login(
|
||||
response: Response,
|
||||
username: Annotated[str, Form()],
|
||||
password: Annotated[str, Form()],
|
||||
) -> RedirectResponse:
|
||||
user = await auth.authenticate(username, password)
|
||||
if user is None:
|
||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail="Identifiants invalides")
|
||||
refresh = await auth.create_refresh_token(user.id)
|
||||
redirect = RedirectResponse("/", status.HTTP_303_SEE_OTHER)
|
||||
set_auth_cookies(redirect, user, refresh)
|
||||
return redirect
|
||||
|
||||
|
||||
@router.post("/logout")
|
||||
async def logout(user: CurrentUser) -> RedirectResponse:
|
||||
await auth.revoke_all_refresh_tokens(user.id)
|
||||
redirect = RedirectResponse("/login", status.HTTP_303_SEE_OTHER)
|
||||
clear_auth_cookies(redirect)
|
||||
return redirect
|
||||
|
||||
|
||||
@router.get("/me")
|
||||
async def me(user: CurrentUser) -> dict:
|
||||
return {"id": user.id, "username": user.username, "is_admin": user.is_admin}
|
||||
Reference in New Issue
Block a user