From 6e7ed8e041c641abe6e4cc8f4b83fd3501b44221 Mon Sep 17 00:00:00 2001 From: Archicratia Date: Thu, 12 Feb 2026 09:46:30 +0100 Subject: [PATCH] Gate 'Proposer' to editors via /_auth/whoami --- src/layouts/EditionLayout.astro | 75 +++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/src/layouts/EditionLayout.astro b/src/layouts/EditionLayout.astro index 4001165..773ba3b 100644 --- a/src/layouts/EditionLayout.astro +++ b/src/layouts/EditionLayout.astro @@ -379,6 +379,60 @@ const GITEA_REPO = import.meta.env.PUBLIC_GITEA_REPO ?? ""; const FULL_TEXT_SOFT_LIMIT = 1600; const URL_HARD_LIMIT = 6500; + // ✅ Gate par groupe (Traefik→Authelia→LLDAP via /_auth/whoami) + const WHOAMI_PATH = "/_auth/whoami"; + const PROPOSE_REQUIRED_GROUP = "editors"; + + let _authInfoPromise = null; + + function parseWhoamiLine(text, key) { + const re = new RegExp(`^${key}:\\s*(.*)$`, "mi"); + const m = String(text || "").match(re); + return (m?.[1] ?? "").trim(); + } + + async function getAuthInfo() { + if (_authInfoPromise) return _authInfoPromise; + + _authInfoPromise = (async () => { + const res = await fetch(`${WHOAMI_PATH}?_=${Date.now()}`, { + credentials: "include", + cache: "no-store", + redirect: "follow", + }); + + const text = await res.text().catch(() => ""); + + const groups = parseWhoamiLine(text, "Remote-Groups") + .split(",") + .map((s) => s.trim()) + .filter(Boolean); + + return { + ok: Boolean(res && res.ok), + user: parseWhoamiLine(text, "Remote-User"), + name: parseWhoamiLine(text, "Remote-Name"), + email: parseWhoamiLine(text, "Remote-Email"), + groups, + raw: text, + }; + })().catch(() => ({ + ok: false, + user: "", + name: "", + email: "", + groups: [], + raw: "", + })); + + return _authInfoPromise; + } + + // Promise unique : est-on editor ? + const isEditorP = giteaReady + ? getAuthInfo().then((info) => info.groups.includes(PROPOSE_REQUIRED_GROUP)).catch(() => false) + : Promise.resolve(false); + const quoteBlock = (s) => String(s || "") .split(/\r?\n/) @@ -493,6 +547,10 @@ const GITEA_REPO = import.meta.env.PUBLIC_GITEA_REPO ?? ""; propose.textContent = "Proposer"; propose.setAttribute("aria-label", "Proposer une correction sur Gitea"); + // ✅ fail-closed DUR : inline-style (ne peut pas être overridé par ton CSS) + propose.style.display = "none"; + propose.dataset.requiresGroup = PROPOSE_REQUIRED_GROUP; + const raw = (p.textContent || "").trim().replace(/\s+/g, " "); const excerpt = raw.length > 420 ? (raw.slice(0, 420) + "…") : raw; @@ -506,9 +564,6 @@ const GITEA_REPO = import.meta.env.PUBLIC_GITEA_REPO ?? ""; propose.dataset.url = issueUrl; propose.dataset.full = raw; - // ❌ PAS de target=_blank ici - // ❌ PAS de rel noopener ici - row.appendChild(propose); } @@ -544,6 +599,20 @@ const GITEA_REPO = import.meta.env.PUBLIC_GITEA_REPO ?? ""; p.appendChild(tools); } + // ✅ Après insertion : on autorise Proposer seulement si groupe editors + if (giteaReady) { + isEditorP.then((ok) => { + const els = document.querySelectorAll(".para-propose"); + for (const el of els) { + if (ok) el.style.display = ""; + else el.remove(); + } + }).catch(() => { + // fail-closed + document.querySelectorAll(".para-propose").forEach((el) => el.remove()); + }); + } + // Auto-checkpoint let lastAuto = 0; function writeLastSeen(id) {