// 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(); }); } async function init() { 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 }; })();