feat: add bearer token auth to _check_auth, falls back to session
Checks Authorization: Bearer header first, hashes token, looks up in api_keys. Read-only keys get 403 on non-GET. Admin routes inaccessible to non-admin key owners. Session path unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user