Compare commits

..

7 Commits

Author SHA1 Message Date
add688602a Merge pull request 'Fix: Proposer gate non-destructive + show/hide consistent' (#75) from fix/proposer-non-destructif into main
All checks were successful
CI / build-and-anchors (push) Successful in 1m29s
SMOKE / smoke (push) Successful in 14s
Reviewed-on: #75
2026-02-12 15:37:28 +01:00
3f3c717185 Fix: Proposer gate non-destructive + show/hide consistent
All checks were successful
CI / build-and-anchors (push) Successful in 1m39s
SMOKE / smoke (push) Successful in 11s
2026-02-12 15:28:58 +01:00
b5f32da0c8 Merge pull request 'Gate 'Proposer' to editors via /_auth/whoami' (#74) from feat/proposer-editors-only into main
All checks were successful
CI / build-and-anchors (push) Successful in 1m29s
SMOKE / smoke (push) Successful in 14s
Reviewed-on: #74
2026-02-12 09:47:03 +01:00
6e7ed8e041 Gate 'Proposer' to editors via /_auth/whoami
All checks were successful
CI / build-and-anchors (push) Successful in 1m57s
SMOKE / smoke (push) Successful in 13s
2026-02-12 09:46:30 +01:00
90f79a7ee7 Merge pull request 'Supprimer docs/SESSION_BILAN_CI_RUNNER_DNS_2026-01.md' (#71) from archicratia-patch-1 into main
All checks were successful
CI / build-and-anchors (push) Successful in 1m36s
SMOKE / smoke (push) Successful in 24s
Reviewed-on: #71
2026-02-02 12:13:12 +01:00
b5663891a1 Supprimer docs/SESSION_BILAN_CI_RUNNER_DNS_2026-01.md
All checks were successful
CI / build-and-anchors (push) Successful in 1m56s
SMOKE / smoke (push) Successful in 14s
2026-02-02 12:12:53 +01:00
a74b95e775 Merge pull request 'docs: normalisation md + diagnostics dedup + LEGACY strict' (#70) from docs/normalisation2-md into main
Some checks failed
CI / build-and-anchors (push) Has been cancelled
SMOKE / smoke (push) Has been cancelled
Reviewed-on: #70
2026-02-02 12:10:03 +01:00
2 changed files with 97 additions and 4 deletions

View File

@@ -379,6 +379,64 @@ 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((err) => {
console.warn("[proposer] whoami fetch failed", err);
return {
ok: false,
user: "",
name: "",
email: "",
groups: [],
raw: "",
};
});
return _authInfoPromise;
}
// Promise unique : est-on editor ?
// ⚠️ On reste fail-closed, mais NON destructif (on ne supprime pas sur erreur réseau)
const isEditorP = giteaReady
? getAuthInfo().then((info) => info.groups.includes(PROPOSE_REQUIRED_GROUP))
: Promise.resolve(false);
const quoteBlock = (s) =>
String(s || "")
.split(/\r?\n/)
@@ -447,6 +505,24 @@ const GITEA_REPO = import.meta.env.PUBLIC_GITEA_REPO ?? "";
// ==========================
const paras = Array.from(document.querySelectorAll('.reading p[id^="p-"]'));
// Petit helper : fail-closed mais réversible (hide ≠ remove)
function hidePropose(el) {
try {
el.hidden = true;
el.style.display = "none";
el.setAttribute("aria-hidden", "true");
el.setAttribute("tabindex", "-1");
} catch {}
}
function showPropose(el) {
try {
el.hidden = false;
el.style.display = "";
el.removeAttribute("aria-hidden");
el.removeAttribute("tabindex");
} catch {}
}
for (const p of paras) {
if (p.querySelector(".para-tools")) continue;
@@ -493,12 +569,16 @@ const GITEA_REPO = import.meta.env.PUBLIC_GITEA_REPO ?? "";
propose.textContent = "Proposer";
propose.setAttribute("aria-label", "Proposer une correction sur Gitea");
// ✅ fail-closed (mais NON destructif)
propose.dataset.requiresGroup = PROPOSE_REQUIRED_GROUP;
hidePropose(propose);
const raw = (p.textContent || "").trim().replace(/\s+/g, " ");
const excerpt = raw.length > 420 ? (raw.slice(0, 420) + "…") : raw;
const issueUrl = buildIssueURL(p.id, raw, excerpt);
// Lien fallback (si JS casse totalement)
// Lien fallback (si JS modal casse totalement)
propose.href = issueUrl;
// ✅ Marqueurs pour ProposeModal (interception 2 étapes)
@@ -506,9 +586,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 +621,22 @@ const GITEA_REPO = import.meta.env.PUBLIC_GITEA_REPO ?? "";
p.appendChild(tools);
}
// ✅ Après insertion : on autorise Proposer seulement si groupe editors
// - ok=false => remove (pas dUI “Proposer” pour les non-éditeurs)
// - erreur fetch => on garde HIDDEN (non destructif) ; un reload pourra réussir
if (giteaReady) {
isEditorP.then((ok) => {
const els = document.querySelectorAll(".para-propose");
for (const el of els) {
if (ok) showPropose(el);
else el.remove();
}
}).catch((err) => {
console.warn("[proposer] gate failed; keeping Proposer hidden", err);
document.querySelectorAll(".para-propose").forEach((el) => hidePropose(el));
});
}
// Auto-checkpoint
let lastAuto = 0;
function writeLastSeen(id) {