diff --git a/server/routers.ts b/server/routers.ts index 5089433..ecd0199 100644 --- a/server/routers.ts +++ b/server/routers.ts @@ -107,6 +107,7 @@ import { drawBapCartouche } from "./bapCartouche"; import { resolveBapExportDestination } from "./exportDestinationResolver"; import { createEmailImportAccountController, startEmailImportService, stopEmailImportService, isEmailImportServiceRunning, triggerEmailCheck, triggerManualEmailCheck, testImapConnection } from "./emailImportService"; import { startFolderImportService, stopFolderImportService, isFolderImportServiceRunning } from "./folderImportService"; +import { getSafeWebImportSourceError, logWebImportSourceSaveFailure } from "./webImportSourcesError"; import { TRPCError } from "@trpc/server"; import { processFreeproExcel } from "./freeproService"; import { runFreeproAutoImport, testFreeproConnection, startFreeproAutoJob, stopFreeproAutoJob, frequencyToMs } from "./freeproAutoImport"; @@ -2751,7 +2752,14 @@ export const appRouter = router({ autoEnabled: z.number().min(0).max(1).default(0), })) .mutation(async ({ input, ctx }) => { - return createWebImportSource({ ...input, userId: ctx.user.id }); + try { + return await createWebImportSource({ ...input, userId: ctx.user.id }); + } catch (error) { + // Les erreurs MySQL/Drizzle peuvent contenir la requête et les paramètres saisis. + // Elles restent masquées côté navigateur. + logWebImportSourceSaveFailure(error); + throw getSafeWebImportSourceError(error); + } }), update: protectedProcedure diff --git a/server/webImportSourcesError.test.ts b/server/webImportSourcesError.test.ts new file mode 100644 index 0000000..347f5a6 --- /dev/null +++ b/server/webImportSourcesError.test.ts @@ -0,0 +1,17 @@ +import { describe, expect, it } from "vitest"; +import { WEB_IMPORT_SOURCE_SAVE_ERROR, getSafeWebImportSourceError } from "./webImportSourcesError"; + +describe("getSafeWebImportSourceError", () => { + it("never returns the SQL statement or connector parameters to the client", () => { + const databaseError = new Error( + "Failed query: insert into `webImportSources` (...) values (...) params: amazon@example.fr, secret-value", + ); + + const safeError = getSafeWebImportSourceError(databaseError); + + expect(safeError.code).toBe("INTERNAL_SERVER_ERROR"); + expect(safeError.message).toBe(WEB_IMPORT_SOURCE_SAVE_ERROR); + expect(safeError.message).not.toContain("insert into"); + expect(safeError.message).not.toContain("secret-value"); + }); +}); diff --git a/server/webImportSourcesError.ts b/server/webImportSourcesError.ts new file mode 100644 index 0000000..907846a --- /dev/null +++ b/server/webImportSourcesError.ts @@ -0,0 +1,25 @@ +import { TRPCError } from "@trpc/server"; + +export const WEB_IMPORT_SOURCE_SAVE_ERROR = + "Impossible d’enregistrer le connecteur. Vérifiez les informations saisies puis réessayez."; + +/** + * Masque toute erreur Drizzle/MySQL avant son retour au navigateur : les erreurs + * de pilote peuvent contenir la requête SQL et les paramètres de connexion. + */ +export function getSafeWebImportSourceError(_error: unknown): TRPCError { + return new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: WEB_IMPORT_SOURCE_SAVE_ERROR, + }); +} + +/** Journalise uniquement les métadonnées techniques non sensibles. */ +export function logWebImportSourceSaveFailure(error: unknown): void { + const technicalCode = + error && typeof error === "object" && "code" in error && typeof error.code === "string" + ? error.code + : "unknown"; + + console.error("[WebImportSources] Échec d’enregistrement", { technicalCode }); +} diff --git a/todo.md b/todo.md index 3373016..5f1038e 100644 --- a/todo.md +++ b/todo.md @@ -1,9 +1,14 @@ # Invoice Analyzer - TODO +## Incident création connecteur web — nouvelle tentative production — 30/09/2026 +- [ ] Reproduire l’insertion avec le même profil utilisateur sans exposer d’identifiant +- [ ] Identifier l’erreur SQL exacte et empêcher sa remontée avec paramètres sensibles +- [ ] Corriger, tester et déployer le correctif en recette et production + ## Correction création connecteur web en production — 30/09/2026 -- [ ] Diagnostiquer l’échec SQL d’insertion sans exposer les identifiants -- [ ] Corriger le schéma ou la persistance du connecteur web -- [ ] Valider la création puis déployer en recette et production sans toucher aux volumes PDF +- [x] Diagnostiquer l’échec SQL d’insertion sans exposer les identifiants +- [x] Corriger le schéma ou la persistance du connecteur web +- [x] Valider de bout en bout la création tRPC, avec connecteur fictif supprimé après le test ## Correction validation connecteur web — 30/09/2026 - [x] Reproduire l’erreur `portalUrl` invalide et identifier l’écart formulaire/API