fix(openrouter): nettoyer la clé API souillée par un copier-coller

OpenRouter répondait « 401 Missing Authentication header », ce qui laisse
croire à un en-tête absent alors qu'il était bien envoyé.

Vérification faite contre l'API : ce message n'apparaît pas quand la clé
est invalide (l'API répond alors « User not found »), mais quand le jeton
placé derrière « Bearer » n'a pas la forme d'une clé — préfixe « Bearer »
recopié depuis un exemple, guillemets laissés autour de la valeur, ou
caractère invisible ramassé lors d'un copier-coller. String.trim() ne
retire pas ces derniers.

La clé est désormais nettoyée à l'enregistrement et à la lecture ; ce
second point remet en marche les clés déjà stockées sans ressaisie.

En cas de 401, le message indique en plus la longueur de la clé et si
elle commence par « sk-or- », sans jamais en révéler le contenu.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018uUPZ1GNpEkYB1dpXHYPek
This commit is contained in:
Claude committed 2026-08-12 08:14:47 +00:00
1 parent 26cddb4d01
commit c097c5c8f3
2 files changed
+56 -6

No files matched your search

+8 -1
View File
@@ -9,7 +9,7 @@ import multer from "multer";
import bcrypt from "bcrypt";
import passport from "./auth";
import { z } from "zod";
import { openRouterService, describeGenerationError } from "./services/openrouter";
import { openRouterService, describeGenerationError, sanitizeApiKey } from "./services/openrouter";
import { minioService as cloudinaryService, buildMinioUrl } from "./services/minio";
import { insertPostSchema, insertScheduledPostSchema, insertSocialPageSchema, insertAiGenerationSchema, insertCloudinaryConfigSchema, updateCloudinaryConfigSchema, insertOpenrouterConfigSchema, updateOpenrouterConfigSchema, insertUserSchema, postMedia, type SocialPage } from "@shared/schema";
import type { User, InsertUser, ScheduledPost } from "@shared/schema";
@@ -1853,6 +1853,13 @@ export async function registerRoutes(app: Express): Promise<Server> {
// Check if config already exists
const existingConfig = await storage.getOpenrouterConfig(userId);
// Une clé collée depuis un navigateur traîne souvent un « Bearer », des
// guillemets ou un caractère invisible. Stockée telle quelle, elle fait
// répondre « Missing Authentication header » à OpenRouter.
if (typeof req.body?.apiKey === 'string') {
req.body.apiKey = sanitizeApiKey(req.body.apiKey);
}
let config;
if (existingConfig) {
// Pour les mises à jour, utiliser le schéma qui rend apiKey optionnel