from __future__ import annotations import functools from quart import session, redirect, url_for, abort, g from roundtable.models.users import UserRole _ROLE_ORDER = [UserRole.viewer, UserRole.operator, UserRole.admin] def require_role(minimum_role: UserRole): """Decorator: requires authenticated user with at least minimum_role. Also allows access for validated share-token requests (viewer level only). """ def decorator(f): @functools.wraps(f) async def wrapper(*args, **kwargs): # Share-token requests bypass session auth for viewer-level endpoints if getattr(g, "share_viewer", False) and minimum_role == UserRole.viewer: return await f(*args, **kwargs) user_id = session.get("user_id") if not user_id: return redirect(url_for("auth.login")) user_role = UserRole(session.get("user_role", "viewer")) if _ROLE_ORDER.index(user_role) < _ROLE_ORDER.index(minimum_role): abort(403) return await f(*args, **kwargs) return wrapper return decorator def login_user(user) -> None: """Write user identity into the session.""" session["user_id"] = user.id session["user_role"] = user.role.value session["username"] = user.username def logout_user() -> None: session.clear() def current_user_id() -> str | None: return session.get("user_id")