Compare commits
7 Commits
docs/norma
...
docs/runbo
| Author | SHA1 | Date | |
|---|---|---|---|
| add688602a | |||
| 3f3c717185 | |||
| b5f32da0c8 | |||
| 6e7ed8e041 | |||
| 90f79a7ee7 | |||
| b5663891a1 | |||
| a74b95e775 |
@@ -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 d’UI “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) {
|
||||
|
||||
Reference in New Issue
Block a user