From af12527731648fe88e9c8e7f11ef9713a2ab72ec Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 06:35:35 +0000 Subject: [PATCH] =?UTF-8?q?fix(s=C3=A9curit=C3=A9):=20r=C3=A9server=20les?= =?UTF-8?q?=20actions=20des=20Param=C3=A8tres=20aux=20administrateurs=20et?= =?UTF-8?q?=20masquer=20les=20secrets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Les actions serveur de src/features/settings/actions.ts étaient appelables par n'importe quel utilisateur connecté : la restriction de /settings dans le middleware ne protège pas une action serveur. Un simple utilisateur pouvait lire l'URL PostgreSQL, le mot de passe Qlik et les clés IA, réécrire la configuration (puis refreshDb()), ou faire appeler une URL arbitraire au serveur via les boutons « Tester ». - requireAdmin() partagé dans src/lib/auth.ts, appelé en tête de chaque action des Paramètres ; il remplace les deux copies locales d'ensureAdmin() de user-actions.ts et api-key-actions.ts. - getSavedDatabaseConfig ne renvoie plus aucun secret : URL sans mot de passe, mot de passe Qlik et clés IA remplacés par des booléens has*. - Un mot de passe laissé vide conserve celui enregistré (enregistrement et test), uniquement pour le même hôte/utilisateur afin qu'il ne puisse pas être envoyé à un autre serveur. - saveDatabaseSettings n'écrase plus les clés et modèles IA quand la page n'envoie que l'URL (ils étaient effacés à chaque sauvegarde). - Le diagnostic de l'API FF passe dans lib/api-ff-client (diagnoseFfApi) : /api/ff-status reste accessible à tous sans exposer l'action admin. - Le mot de passe PostgreSQL n'est plus persisté dans le localStorage. Les routes /api/admin/** et sync-settings.ts (module serveur, pas une action) étaient déjà correctement protégées : inchangées. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01RkfUui3qGg2zmu3uHWjNXJ --- src/app/(dashboard)/settings/page.tsx | 29 +++- src/app/api/ff-status/route.ts | 4 +- src/features/admin/api/api-key-actions.ts | 18 +- src/features/admin/api/user-actions.ts | 24 +-- src/features/settings/actions.ts | 154 +++++++++++------- .../settings/store/use-db-settings-store.ts | 2 + src/lib/api-ff-client.ts | 55 +++++++ src/lib/auth.ts | 15 ++ 8 files changed, 205 insertions(+), 96 deletions(-) diff --git a/src/app/(dashboard)/settings/page.tsx b/src/app/(dashboard)/settings/page.tsx index 2e3cd79..73051d3 100644 --- a/src/app/(dashboard)/settings/page.tsx +++ b/src/app/(dashboard)/settings/page.tsx @@ -171,6 +171,8 @@ function QlikSettingsSection() { const [host, setHost] = useState(""); const [user, setUser] = useState(""); const [password, setPassword] = useState(""); + // Le mot de passe enregistré n'est jamais renvoyé : on sait seulement qu'il existe. + const [hasSavedPassword, setHasSavedPassword] = useState(false); const [showPwd, setShowPwd] = useState(false); const [saving, setSaving] = useState(false); const [testing, setTesting] = useState(false); @@ -181,7 +183,7 @@ function QlikSettingsSection() { if (!c) return; setHost(c.qlikHost ?? ""); setUser(c.qlikUser ?? ""); - setPassword(c.qlikPassword ?? ""); + setHasSavedPassword(c.hasQlikPassword); }); }, []); @@ -190,6 +192,10 @@ function QlikSettingsSection() { setStatus(null); const res = await saveQlikSettings(host.trim(), user.trim(), password); setSaving(false); + if (res.success && password) { + setHasSavedPassword(true); + setPassword(""); + } setStatus(res.success ? { type: "ok", msg: "Réglages Qlik enregistrés" } : { type: "err", msg: res.error || "Erreur" }); }; @@ -211,9 +217,9 @@ function QlikSettingsSection() { setUser(e.target.value)} className="apple-input font-mono" /> - +
- setPassword(e.target.value)} className="apple-input font-mono pr-10" /> -