diff --git a/src/fabledassistant/auth.py b/src/fabledassistant/auth.py index 3006219..a8a492c 100644 --- a/src/fabledassistant/auth.py +++ b/src/fabledassistant/auth.py @@ -1,13 +1,35 @@ import functools -from quart import g, jsonify, session +from quart import g, jsonify, request, session from fabledassistant.services.auth import get_user_by_id +from fabledassistant.services.api_keys import lookup_key def _check_auth(f, required_role: str | None = None): @functools.wraps(f) async def decorated(*args, **kwargs): + # --- Bearer token path --- + auth_header = request.headers.get("Authorization", "") + if auth_header.startswith("Bearer "): + raw_key = auth_header[len("Bearer "):] + api_key = await lookup_key(raw_key) + if api_key is None: + return jsonify({"error": "Invalid or revoked API key"}), 401 + user = await get_user_by_id(api_key.user_id) + if not user: + return jsonify({"error": "User not found"}), 401 + # Scope enforcement: read-only keys cannot mutate + if api_key.scope == "read" and request.method not in ("GET", "HEAD", "OPTIONS"): + return jsonify({"error": "Read-only key cannot perform write operations"}), 403 + # Role check (admin_required routes) + if required_role and user.role != required_role: + return jsonify({"error": "Admin access required"}), 403 + g.user = user + g.api_key = api_key + return await f(*args, **kwargs) + + # --- Session path (unchanged) --- user_id = session.get("user_id") if not user_id: return jsonify({"error": "Authentication required"}), 401