The header toggle alone is easy to miss — admin elevation bypasses
WORM zones and ACL silently, so an admin who forgot they were
elevated could write into received/ or issued/ thinking they were
operating under their normal grants.
Two reinforcing affordances when the zddc-elevate cookie is set:
- body.is-elevated paints a 3px red outline around the entire page,
visible from any scroll position and inside any tool surface.
- A sticky red banner sits across the top with a pulsing dot, an
explicit warning ("write access bypasses WORM and ACL safeguards"),
and a one-click "Drop admin" button that clears the cookie + reloads
so the user can disarm without hunting for the corner toggle.
Both render on every page load via shared/elevation.js — applies to
every tool that includes the elevation slot, plus any tool that loads
the shared bundle even without a toggle host (the iframed classifier
inside browse's grid mode, etc.). Wired before the access fetch so
the banner appears immediately instead of waiting on /.profile/access.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
148 lines
6.3 KiB
JavaScript
148 lines
6.3 KiB
JavaScript
// 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 =
|
|
'<input type="checkbox" id="elevation-checkbox"'
|
|
+ (elevated ? ' checked' : '') + '>'
|
|
+ '<label for="elevation-checkbox" class="elevation-toggle__label">'
|
|
+ 'Admin</label>';
|
|
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 =
|
|
'<span class="elevation-banner__dot" aria-hidden="true"></span>'
|
|
+ '<span class="elevation-banner__msg">'
|
|
+ 'Admin mode is on — write access bypasses WORM and ACL safeguards.'
|
|
+ '</span>'
|
|
+ '<button type="button" class="elevation-banner__off" id="elevation-banner-off">'
|
|
+ 'Drop admin'
|
|
+ '</button>';
|
|
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 };
|
|
})();
|