Fix: ai_provider reads actual DB schema (config_key/config_value rows)

Root cause: ai_provider.py assumed a single JSON blob in an
'encrypted_value' column, but IntegrationConfig stores each credential
as a separate row with config_key + config_value columns.

Fixes:
- Query groups rows by provider, decrypts each config_value separately
- Correct decrypt_value(secret_key, ciphertext) argument order
- Filter by user_id so providers are per-user
- Callers (ai_chat.py, chat()) now pass user_id through
This commit is contained in:
Claude 2026-06-22 00:27:19 +00:00
parent 5294745704
commit f2954af400
2 changed files with 24 additions and 12 deletions

View file

@ -54,7 +54,7 @@ async def ai_status(
db: AsyncSession = Depends(get_db), db: AsyncSession = Depends(get_db),
): ):
"""Check which AI provider is configured.""" """Check which AI provider is configured."""
provider = await ai_provider.get_active_ai_provider(db) provider = await ai_provider.get_active_ai_provider(db, user_id=user.id)
if provider: if provider:
return { return {
"configured": True, "configured": True,

View file

@ -55,22 +55,34 @@ RULES:
- Never fabricate data only reference what's in the context.""" - Never fabricate data only reference what's in the context."""
async def get_active_ai_provider(db: AsyncSession) -> dict | None: async def get_active_ai_provider(db: AsyncSession, user_id=None) -> dict | None:
"""Find the first configured AI provider.""" """Find the first configured AI provider for the given user."""
result = await db.execute( query = select(IntegrationConfig).where(
select(IntegrationConfig).where(
IntegrationConfig.provider.in_(list(AI_PRESETS.keys())) IntegrationConfig.provider.in_(list(AI_PRESETS.keys()))
) )
) if user_id is not None:
configs = result.scalars().all() query = query.where(IntegrationConfig.user_id == user_id)
for config in configs: result = await db.execute(query)
provider_name = config.provider rows = result.scalars().all()
# Group rows by provider: {"gemini": {"api_key": "decrypted_value"}}
providers: dict[str, dict[str, str]] = {}
for row in rows:
if row.provider not in providers:
providers[row.provider] = {}
try: try:
creds = json.loads(decrypt_value(config.encrypted_value, settings.secret_key)) providers[row.provider][row.config_key] = decrypt_value(
settings.secret_key, row.config_value
)
except Exception: except Exception:
continue continue
# Return the first provider that has at least one credential
for provider_name, creds in providers.items():
if not creds:
continue
preset = AI_PRESETS.get(provider_name, AI_PRESETS["custom_ai"]) preset = AI_PRESETS.get(provider_name, AI_PRESETS["custom_ai"])
api_key = creds.get("api_key", "") api_key = creds.get("api_key", "")
base_url = creds.get("endpoint_url", preset["base_url"]) or preset["base_url"] base_url = creds.get("endpoint_url", preset["base_url"]) or preset["base_url"]
@ -127,7 +139,7 @@ async def chat(
db: AsyncSession, db: AsyncSession,
) -> AsyncGenerator[str, None]: ) -> AsyncGenerator[str, None]:
"""Route chat to the configured AI provider with streaming.""" """Route chat to the configured AI provider with streaming."""
provider = await get_active_ai_provider(db) provider = await get_active_ai_provider(db, user_id=user_id)
if not provider: if not provider:
yield "No AI provider configured. Go to Settings → Integrations to connect one (OpenAI, OpenRouter, Ollama, etc.)." yield "No AI provider configured. Go to Settings → Integrations to connect one (OpenAI, OpenRouter, Ollama, etc.)."
return return