import logging import os import sys from functools import wraps from flask import request, jsonify logger = logging.getLogger("ai_router") TOKEN_BROKER_URL = os.environ.get( "TOKEN_BROKER_URL", "http://localhost:8783" ) def verify_token_via_broker(token: str) -> dict: """Verify an API token via the token-broker /verify endpoint.""" import requests try: resp = requests.post( f"{TOKEN_BROKER_URL}/api/v1/tokens/verify", json={"token": token}, timeout=10, ) if resp.status_code == 200: data = resp.json() if data.get("valid"): return data return {} except requests.RequestException as e: logger.warning(f"Token broker unreachable: {e}") return {} def require_auth(f): """Decorator: validate Bearer or X-API-Key via token-broker.""" @wraps(f) def decorated(*args, **kwargs): auth_header = request.headers.get("Authorization", "") api_key = request.headers.get("X-API-Key", "") raw_token = "" if auth_header.startswith("Bearer "): raw_token = auth_header.split(" ", 1)[1] elif api_key: raw_token = api_key if not raw_token: return jsonify({"error": "Authentication required"}), 401 payload = verify_token_via_broker(raw_token) if not payload or not payload.get("valid"): return jsonify({"error": "Invalid or expired token"}), 401 request.current_user = { "user_id": payload.get("user_id"), "token_id": payload.get("token_id"), "scopes": payload.get("scopes", []), } return f(*args, **kwargs) return decorated def admin_required(f): """Decorator: require admin scope on the authenticated token.""" @wraps(f) def decorated(*args, **kwargs): auth_header = request.headers.get("Authorization", "") api_key = request.headers.get("X-API-Key", "") raw_token = "" if auth_header.startswith("Bearer "): raw_token = auth_header.split(" ", 1)[1] elif api_key: raw_token = api_key if not raw_token: return jsonify({"error": "Authentication required"}), 401 payload = verify_token_via_broker(raw_token) if not payload or not payload.get("valid"): return jsonify({"error": "Invalid or expired token"}), 401 scopes = payload.get("scopes", []) if "admin" not in scopes and "ai_router_admin" not in scopes: return jsonify({"error": "Admin access required"}), 403 request.current_user = { "user_id": payload.get("user_id"), "token_id": payload.get("token_id"), "scopes": scopes, } return f(*args, **kwargs) return decorated