diff --git a/ai-hub/app/api/routes/mcp.py b/ai-hub/app/api/routes/mcp.py index d3dac41..43833e3 100644 --- a/ai-hub/app/api/routes/mcp.py +++ b/ai-hub/app/api/routes/mcp.py @@ -20,6 +20,7 @@ import json import uuid import logging +import jwt from typing import Optional, List, Annotated from fastapi import APIRouter, HTTPException, Request, Query, Header from fastapi.responses import JSONResponse, StreamingResponse @@ -55,24 +56,28 @@ is_jwt = "." in token - # 1. OIDC Mode: Strictly require and verify JWT + # 1. OIDC Mode: Support both OIDC (RS256) and Internal (HS256) JWTs if settings.OIDC_ENABLED: if not is_jwt: - logger.warning(f"[MCP] Rejected plain UUID token in OIDC mode. Use ID tokens instead.") + logger.warning(f"[MCP] Rejected non-JWT token in OIDC mode.") raise HTTPException( status_code=401, - detail="Authentication required: Provide a valid OIDC ID Token (JWT). Plain UUIDs are deprecated for security." + detail="Authentication required: Provide a valid JWT." ) try: + # Try internal HS256 first + unverified = jwt.decode(token, options={"verify_signature": False}) + if unverified.get("iss") == "cortex-hub-internal": + decoded = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"]) + return decoded.get("sub") + + # Fallback to OIDC RS256 user = await services.auth_service.verify_id_token(token, db) return user.id except Exception as e: logger.error(f"[MCP] JWT verification failed: {e}") - # Emergency fallback for local dev ONLY if secret key is default - if settings.SECRET_KEY in ["dev", "generate-me", "dev-secret-key-1337"]: - return token - raise HTTPException(status_code=401, detail=f"Invalid ID token: {str(e)}") + raise HTTPException(status_code=401, detail=f"Invalid token: {str(e)}") # 2. Legacy/Bootstrap Mode: Accept plain user_id (Identity Claim) # This is only active when OIDC is not configured. diff --git a/frontend/src/services/api/aiService.js b/frontend/src/services/api/aiService.js index 3450f87..f01eea1 100644 --- a/frontend/src/services/api/aiService.js +++ b/frontend/src/services/api/aiService.js @@ -23,11 +23,10 @@ * Sends a text prompt to the LLM endpoint and gets a streaming text response (SSE). */ export const chatWithAI = async (sessionId, prompt, providerName = "gemini", onMessage = null) => { - const userId = getUserId(); - const response = await fetch(`${API_BASE_URL}/sessions/${sessionId}/chat`, { + const response = await fetchWithAuth(`/sessions/${sessionId}/chat`, { method: "POST", - headers: { "Content-Type": "application/json", "X-User-ID": userId }, - body: JSON.stringify({ prompt: prompt, provider_name: providerName }), + body: { prompt: prompt, provider_name: providerName }, + raw: true }); if (!response.ok) { @@ -95,20 +94,16 @@ * Streams speech from the TTS endpoint and processes each chunk. */ export const streamSpeech = async (text, onData, onDone, providerName = null) => { - const userId = getUserId(); try { - let url = `${API_BASE_URL}/speech?stream=true&as_wav=false`; + let url = `/speech?stream=true&as_wav=false`; if (providerName) { url += `&provider_name=${encodeURIComponent(providerName)}`; } - const response = await fetch(url, { + const response = await fetchWithAuth(url, { method: "POST", - headers: { "Content-Type": "application/json", "X-User-ID": userId }, - body: JSON.stringify({ text }), - }).catch(err => { - console.error("Fetch transport error:", err); - throw new Error(`Network transport failed: ${err.message}`); + body: { text }, + raw: true }); if (!response.ok) {