mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-21 14:08:10 +02:00
feat(openrouter): validate API key at save time
The stored key that caused the persistent 401s turned out to be only 9 characters long — an accepted-but-truncated paste. Prevent this class of problem at the source: - require a minimum key length in the zod schema - verify new keys against OpenRouter's /api/v1/key endpoint on save, rejecting explicit 401/403 (network errors don't block saving) - return zod validation messages as 400 instead of a generic 500 - surface the server's error message in the settings toast (desktop and mobile) instead of a generic failure text
This commit is contained in:
5 files changed
+48
-6
No files matched your search
@@ -157,10 +157,17 @@ export default function SettingsMobile() {
|
|||||||
});
|
});
|
||||||
setOpenrouterApiKey("");
|
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({
|
toast({
|
||||||
title: "Erreur",
|
title: "Erreur",
|
||||||
description: "Impossible de sauvegarder la configuration OpenRouter",
|
description,
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -161,10 +161,17 @@ export default function Settings() {
|
|||||||
});
|
});
|
||||||
setOpenrouterApiKey(""); // Clear the API key after saving
|
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({
|
toast({
|
||||||
title: "Erreur",
|
title: "Erreur",
|
||||||
description: "Impossible de sauvegarder la configuration OpenRouter",
|
description,
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
+13
-1
@@ -1844,6 +1844,10 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
return res.status(400).json({ error: "Clé API OpenRouter manquante. Veuillez entrer une clé API valide." });
|
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);
|
config = await storage.updateOpenrouterConfig(userId, finalData);
|
||||||
} else {
|
} else {
|
||||||
// Pour les créations, exiger tous les champs
|
// Pour les créations, exiger tous les champs
|
||||||
@@ -1851,14 +1855,22 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
...req.body,
|
...req.body,
|
||||||
userId,
|
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);
|
config = await storage.createOpenrouterConfig(configData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't send the API key back
|
// Don't send the API key back
|
||||||
const { apiKey, ...safeConfig } = config;
|
const { apiKey, ...safeConfig } = config;
|
||||||
res.json(safeConfig);
|
res.json(safeConfig);
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
console.error("Error saving OpenRouter config:", error);
|
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" });
|
res.status(500).json({ error: "Failed to save OpenRouter config" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -215,6 +215,22 @@ VERSION 3 - ÉMOTIONNELLE:
|
|||||||
return text.charAt(0).toUpperCase() + text.slice(1).toLowerCase();
|
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<boolean> {
|
||||||
|
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<any[]> {
|
async getAvailableModels(): Promise<any[]> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch("https://openrouter.ai/api/v1/models", {
|
const response = await fetch("https://openrouter.ai/api/v1/models", {
|
||||||
|
|||||||
+1
-1
@@ -394,7 +394,7 @@ export const insertOpenrouterConfigSchema = createInsertSchema(openrouterConfig)
|
|||||||
createdAt: true,
|
createdAt: true,
|
||||||
updatedAt: true,
|
updatedAt: true,
|
||||||
}).extend({
|
}).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({
|
export const updateOpenrouterConfigSchema = insertOpenrouterConfigSchema.partial({
|
||||||
|
|||||||
Reference in new issue
Block a user