module ApiAuthenticatable extend ActiveSupport::Concern included do before_action :authenticate_api_key! end private def authenticate_api_key! api_key = extract_api_key return render_unauthorized("Missing API key") unless api_key key_digest = Digest::SHA256.hexdigest(api_key) if api_key.start_with?("gw_") authenticate_gateway(key_digest) else authenticate_client_api_key(key_digest) end end def authenticate_gateway(key_digest) @current_gateway = Gateway.find_by(api_key_digest: key_digest, active: true) unless @current_gateway render_unauthorized("Invalid gateway API key") end end def authenticate_client_api_key(key_digest) @current_api_key = ApiKey.find_by(key_digest: key_digest, active: true) unless @current_api_key render_unauthorized("Invalid API key") return end # Check if key has expired if @current_api_key.expires_at.present? && @current_api_key.expires_at < Time.current render_unauthorized("API key has expired") return end @current_api_key.touch(:last_used_at) end def extract_api_key auth_header = request.headers["Authorization"] return nil unless auth_header # Support both "Bearer token" and just "token" auth_header.sub(/^Bearer\s+/, "") end def render_unauthorized(message = "Unauthorized") render json: { error: message }, status: :unauthorized end def current_gateway @current_gateway end def current_api_key @current_api_key end def require_permission(permission) unless @current_api_key&.can?(permission) render json: { error: "Insufficient permissions" }, status: :forbidden end end end