Admins can change passwords. Request user confirmation to open gate

This commit is contained in:
Ettore
2026-05-06 11:22:43 +02:00
parent 2b598279d0
commit da97027606
7 changed files with 127 additions and 9 deletions

View File

@@ -27,7 +27,7 @@ async function api(method, path, body) {
if (res.status === 401) {
clearToken();
showLogin();
throw new Error("Session expired.");
throw new Error("Session expired or invalid credentials");
}
if (!res.ok) {
const j = await res.json().catch(() => null);
@@ -53,7 +53,9 @@ function showLogin() {
function showAdmin() {
document.getElementById("login-view").classList.add("hidden");
document.getElementById("admin-view").classList.remove("hidden");
const isAdmin = _tokenPayload().scope === "admin";
const payload = _tokenPayload();
const isAdmin = payload.scope === "admin";
document.getElementById("header-username").textContent = payload.sub || "";
document.querySelectorAll(".admin-only").forEach(el => {
el.style.display = isAdmin ? "" : "none";
});
@@ -504,11 +506,21 @@ async function loadAdmins() {
const tr = document.createElement("tr");
tr.innerHTML = `
<td>${esc(u.username)}${u.username === me ? ' <span class="badge badge-green" style="font-size:.75em">you</span>' : ""} ${roleBadge}</td>
<td style="text-align:right">
<td style="text-align:right;display:flex;gap:.5rem;justify-content:flex-end">
<button class="btn btn-ghost" style="font-size:.8rem;padding:.35rem .9rem" data-chpw="${esc(u.username)}">Change password</button>
${u.username !== me ? `<button class="btn btn-danger" style="font-size:.8rem;padding:.35rem .9rem" data-del-admin="${esc(u.username)}">Delete</button>` : ""}
</td>`;
tbody.appendChild(tr);
}
tbody.querySelectorAll("[data-chpw]").forEach(btn => {
btn.addEventListener("click", () => {
document.getElementById("chpw-username").value = btn.dataset.chpw;
document.getElementById("chpw-new").value = "";
document.getElementById("chpw-confirm").value = "";
document.getElementById("chpw-error").classList.add("hidden");
document.getElementById("chpw-modal").classList.remove("hidden");
});
});
tbody.querySelectorAll("[data-del-admin]").forEach(btn => {
btn.addEventListener("click", async () => {
if (!confirm(`Delete admin "${btn.dataset.delAdmin}"?`)) return;
@@ -549,6 +561,32 @@ document.getElementById("admin-form").addEventListener("submit", async e => {
}
});
// ── Change password modal ─────────────────────────────────────────────────────
document.getElementById("chpw-cancel").addEventListener("click", () => {
document.getElementById("chpw-modal").classList.add("hidden");
});
document.getElementById("chpw-form").addEventListener("submit", async e => {
e.preventDefault();
const username = document.getElementById("chpw-username").value;
const newPw = document.getElementById("chpw-new").value;
const confirm = document.getElementById("chpw-confirm").value;
const errEl = document.getElementById("chpw-error");
errEl.classList.add("hidden");
if (newPw !== confirm) {
errEl.textContent = "Passwords do not match";
errEl.classList.remove("hidden");
return;
}
try {
await api("PATCH", `/api/admin/admins/${encodeURIComponent(username)}/password`, { new_password: newPw });
document.getElementById("chpw-modal").classList.add("hidden");
showToast("Password updated");
} catch (e) {
errEl.textContent = e.message;
errEl.classList.remove("hidden");
}
});
// ── Load all data ─────────────────────────────────────────────────────────────
function loadAllData() {
const isAdmin = _tokenPayload().scope === "admin";