// shared/elevation.js — admin elevation toggle. // // Sudo-style model: admins behave as normal users by default; clicking // the header toggle elevates the session so admin escape hatches (WORM // bypass, .zddc edit authority, profile admin scaffolds) start firing. // State is carried in a `zddc-elevate=1` cookie that the server reads // via handler.ACLMiddleware → zddc.Principal{Elevated}. // // Only renders the toggle when /.profile/access reports the caller has // some admin scope — a non-admin sees nothing, which keeps the chrome // quiet for the common case. The toggle fades in once access loads so // non-admins never even see the affordance flash. // // Click flow: set/clear the cookie, then reload the page so the server // sees the new state on the next render. The reload is intentional — // admin scaffolds in tool HTML are server-rendered for some tools, so // a soft state flip on the client alone wouldn't reach those. (function () { 'use strict'; if (!window.zddc) window.zddc = {}; if (window.zddc.elevation) return; var COOKIE_NAME = 'zddc-elevate'; function isElevated() { var parts = document.cookie.split(';'); for (var i = 0; i < parts.length; i++) { var kv = parts[i].trim().split('='); if (kv[0] === COOKIE_NAME && kv[1] === '1') return true; } return false; } function setElevated(on) { if (on) { // SameSite=Lax blocks cross-site form-post / image-tag CSRF // shapes. Max-Age caps the elevation window so a forgotten // tab doesn't leave admin powers active indefinitely (sudo's // 5-minute precedent informs the number — 30 minutes is a // reasonable trade between annoyance and exposure). document.cookie = COOKIE_NAME + '=1; Path=/; SameSite=Lax; Max-Age=1800'; } else { document.cookie = COOKIE_NAME + '=; Path=/; SameSite=Lax; Max-Age=0'; } } async function fetchAccess() { try { var resp = await fetch('/.profile/access', { headers: { 'Accept': 'application/json' }, credentials: 'same-origin', cache: 'no-cache' }); if (!resp.ok) return null; return await resp.json(); } catch (_e) { return null; } } function render(host, elevated) { host.classList.remove('hidden'); host.innerHTML = '' + ''; var cb = host.querySelector('#elevation-checkbox'); cb.addEventListener('change', function () { setElevated(cb.checked); // Hard reload so server-rendered admin surfaces (profile // page scaffolds, hidden-entry listings) catch up. URL // and scroll state are preserved by the browser's normal // back-forward cache rules. window.location.reload(); }); } // Page-wide affordances when elevation is active. The toggle alone // is easy to miss — admin mode silently bypasses WORM and ACL // restrictions, which produces surprising "I shouldn't have been // able to do that" moments. A body class + a sticky banner with a // one-click disable make the armed state unmistakable. function applyArmedChrome(elevated) { var b = document.body; if (!b) return; if (elevated) b.classList.add('is-elevated'); else b.classList.remove('is-elevated'); var banner = document.getElementById('elevation-banner'); if (elevated) { if (!banner) { banner = document.createElement('div'); banner.id = 'elevation-banner'; banner.className = 'elevation-banner'; banner.setAttribute('role', 'alert'); banner.innerHTML = '' + '' + 'Admin mode is on — write access bypasses WORM and ACL safeguards.' + '' + ''; document.body.insertBefore(banner, document.body.firstChild); var off = banner.querySelector('#elevation-banner-off'); if (off) off.addEventListener('click', function () { setElevated(false); window.location.reload(); }); } } else if (banner) { banner.parentNode.removeChild(banner); } } async function init() { // Body chrome applies on every page load whether or not the // header has a toggle slot — the banner needs to surface in // tools / pages that don't host the toggle (e.g. iframed // classifier inside browse's grid mode), so the user can't // accidentally write through an elevated context elsewhere. applyArmedChrome(isElevated()); var host = document.getElementById('elevation-toggle'); if (!host) return; // tool doesn't include the slot yet — no-op var access = await fetchAccess(); if (!access) return; // anonymous / endpoint missing — no-op // Surface ONLY for users who have admin authority somewhere. // /.profile/access ships `can_elevate` as an elevation- // INDEPENDENT signal — true for any user named in any admin // list, regardless of current cookie state. The other flags // (is_super_admin, has_any_admin_scope) reflect EFFECTIVE // authority and would be false for an un-elevated admin // who hasn't toggled yet — so we can't gate on those. if (!access.can_elevate) return; render(host, isElevated()); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } window.zddc.elevation = { isElevated: isElevated, setElevated: setElevated }; })();