diff --git a/client/src/pages/mobile/settings.tsx b/client/src/pages/mobile/settings.tsx index dbaa01e..1667894 100644 --- a/client/src/pages/mobile/settings.tsx +++ b/client/src/pages/mobile/settings.tsx @@ -157,10 +157,17 @@ export default function SettingsMobile() { }); setOpenrouterApiKey(""); }, - onError: () => { + onError: (error: Error) => { + let description = "Impossible de sauvegarder la configuration OpenRouter"; + const body = error.message.match(/^\d+:\s*(.+)$/)?.[1]; + if (body) { + try { + description = JSON.parse(body).error || description; + } catch { /* garder le message générique */ } + } toast({ title: "Erreur", - description: "Impossible de sauvegarder la configuration OpenRouter", + description, variant: "destructive", }); }, diff --git a/client/src/pages/settings.tsx b/client/src/pages/settings.tsx index c74a96d..98275cc 100644 --- a/client/src/pages/settings.tsx +++ b/client/src/pages/settings.tsx @@ -161,10 +161,17 @@ export default function Settings() { }); setOpenrouterApiKey(""); // Clear the API key after saving }, - onError: () => { + onError: (error: Error) => { + let description = "Impossible de sauvegarder la configuration OpenRouter"; + const body = error.message.match(/^\d+:\s*(.+)$/)?.[1]; + if (body) { + try { + description = JSON.parse(body).error || description; + } catch { /* garder le message générique */ } + } toast({ title: "Erreur", - description: "Impossible de sauvegarder la configuration OpenRouter", + description, variant: "destructive", }); }, diff --git a/server/routes.ts b/server/routes.ts index 49c8163..95b9a6c 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -1844,6 +1844,10 @@ export async function registerRoutes(app: Express): Promise { return res.status(400).json({ error: "Clé API OpenRouter manquante. Veuillez entrer une clé API valide." }); } + if (incomingKey && !(await openRouterService.verifyApiKey(incomingKey))) { + return res.status(400).json({ error: "Clé API refusée par OpenRouter. Vérifiez que vous avez collé la clé complète (sk-or-v1-...)." }); + } + config = await storage.updateOpenrouterConfig(userId, finalData); } else { // Pour les créations, exiger tous les champs @@ -1851,14 +1855,22 @@ export async function registerRoutes(app: Express): Promise { ...req.body, userId, }); + + if (!(await openRouterService.verifyApiKey(configData.apiKey))) { + return res.status(400).json({ error: "Clé API refusée par OpenRouter. Vérifiez que vous avez collé la clé complète (sk-or-v1-...)." }); + } + config = await storage.createOpenrouterConfig(configData); } // Don't send the API key back const { apiKey, ...safeConfig } = config; res.json(safeConfig); - } catch (error) { + } catch (error: any) { console.error("Error saving OpenRouter config:", error); + if (error.name === 'ZodError') { + return res.status(400).json({ error: error.errors[0]?.message || "Données invalides" }); + } res.status(500).json({ error: "Failed to save OpenRouter config" }); } }); diff --git a/server/services/openrouter.ts b/server/services/openrouter.ts index 2137458..55e86b9 100644 --- a/server/services/openrouter.ts +++ b/server/services/openrouter.ts @@ -215,6 +215,22 @@ VERSION 3 - ÉMOTIONNELLE: return text.charAt(0).toUpperCase() + text.slice(1).toLowerCase(); } + /** + * Vérifie qu'une clé API est acceptée par OpenRouter. + * Retourne false uniquement sur un rejet d'authentification explicite (401/403) ; + * en cas d'erreur réseau, on laisse passer pour ne pas bloquer la sauvegarde. + */ + async verifyApiKey(apiKey: string): Promise { + try { + const response = await fetch("https://openrouter.ai/api/v1/key", { + headers: { "Authorization": `Bearer ${apiKey}` } + }); + return response.status !== 401 && response.status !== 403; + } catch { + return true; + } + } + async getAvailableModels(): Promise { try { const response = await fetch("https://openrouter.ai/api/v1/models", { diff --git a/shared/schema.ts b/shared/schema.ts index e8ff5ad..100c817 100644 --- a/shared/schema.ts +++ b/shared/schema.ts @@ -394,7 +394,7 @@ export const insertOpenrouterConfigSchema = createInsertSchema(openrouterConfig) createdAt: true, updatedAt: true, }).extend({ - apiKey: z.string().trim().min(1, "La clé API ne peut pas être vide"), + apiKey: z.string().trim().min(20, "La clé API OpenRouter semble incomplète. Collez la clé entière (elle commence par sk-or-v1- et fait environ 73 caractères)."), }); export const updateOpenrouterConfigSchema = insertOpenrouterConfigSchema.partial({