🚧 WIP
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
"""API dependencies: current user, RBAC guards, correlation IDs."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Depends, HTTPException, Request, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.db import get_db
|
||||
from app.models import ManagementRole, ManagementSession, ManagementUser
|
||||
|
||||
SESSION_COOKIE = "mqtt_mgmt_session"
|
||||
|
||||
|
||||
def _expires_at_aware(mgmt_session: ManagementSession) -> datetime:
|
||||
"""Normalize expiry to aware UTC (sqlite returns naive datetimes)."""
|
||||
from datetime import UTC
|
||||
|
||||
expires_at = mgmt_session.expires_at
|
||||
if expires_at.tzinfo is None:
|
||||
return expires_at.replace(tzinfo=UTC)
|
||||
return expires_at
|
||||
|
||||
|
||||
def _get_request_session(
|
||||
request: Request, db: Session
|
||||
) -> tuple[ManagementUser, ManagementSession]:
|
||||
token = request.cookies.get(SESSION_COOKIE)
|
||||
if not token:
|
||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "not authenticated")
|
||||
try:
|
||||
session_id = uuid.UUID(token)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "invalid session") from exc
|
||||
|
||||
from datetime import UTC, datetime
|
||||
|
||||
mgmt_session = db.get(ManagementSession, session_id)
|
||||
if (
|
||||
mgmt_session is None
|
||||
or mgmt_session.revoked
|
||||
or _expires_at_aware(mgmt_session) < datetime.now(UTC)
|
||||
):
|
||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "session expired")
|
||||
user = db.get(ManagementUser, mgmt_session.user_id)
|
||||
if user is None or not user.enabled:
|
||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "user disabled")
|
||||
return user, mgmt_session
|
||||
|
||||
|
||||
def get_current_user(
|
||||
request: Request, db: Annotated[Session, Depends(get_db)]
|
||||
) -> ManagementUser:
|
||||
user, _ = _get_request_session(request, db)
|
||||
return user
|
||||
|
||||
|
||||
def require_roles(*roles: ManagementRole):
|
||||
"""Dependency factory enforcing one of the given management roles."""
|
||||
|
||||
def _guard(
|
||||
user: Annotated[ManagementUser, Depends(get_current_user)],
|
||||
) -> ManagementUser:
|
||||
if user.role == ManagementRole.SUPER_ADMIN:
|
||||
return user
|
||||
if user.role not in roles:
|
||||
raise HTTPException(status.HTTP_403_FORBIDDEN, "insufficient role")
|
||||
return user
|
||||
|
||||
return _guard
|
||||
|
||||
|
||||
CurrentUser = Annotated[ManagementUser, Depends(get_current_user)]
|
||||
DbSession = Annotated[Session, Depends(get_db)]
|
||||
Reference in New Issue
Block a user