from flask import Blueprint, request, jsonify from api import generate_api_key from db import get_db from api import require_auth from db.models.APIKey import APIKey api_key_bp = Blueprint('apikey', __name__, url_prefix='/api/apikey') # An API key must never be able to request a role broader than what the # product defines, regardless of what the request body asks for. ALLOWED_API_KEY_ROLES = {'admin', 'creator', 'user'} @api_key_bp.route('/', methods=['POST']) @require_auth(roles=['admin']) def create_key(): data = request.get_json(silent=True) if not data or 'name' not in data: return jsonify({"error": "Name is required"}), 400 roles = data.get('roles', []) if not isinstance(roles, list) or any(r not in ALLOWED_API_KEY_ROLES for r in roles): return jsonify({"error": f"roles must be a subset of {sorted(ALLOWED_API_KEY_ROLES)}"}), 400 try: with get_db() as session: apikey = APIKey(key=generate_api_key(), name=data['name'], roles=roles) session.add(apikey) session.commit() result = apikey.to_dict() return jsonify(result), 201 except Exception as e: return jsonify({"error": str(e)}), 500 @api_key_bp.route('/', methods=['DELETE']) @require_auth(roles=['admin']) def revoke_key(key): # Query and mutate within the same session, otherwise the update is # performed on a detached instance and silently never persists. with get_db() as session: api_key = session.query(APIKey).filter_by(key=key, is_active=True).first() if not api_key: return jsonify({"error": "API key not found"}), 404 api_key.is_active = False session.commit() return jsonify({"message": "API key revoked successfully"}), 200