With more than one admin, every setting in the panel was editable by all of them — prompts, model policy, retrieval budgets, SMTP, email templates. ADMIN_LOCKDOWN separates running the service from changing how it behaves. It is an environment variable on purpose: a setting could be switched off by the very admin it restrains, so lifting this needs host access and a restart. The server is the control. One gate refuses configuration writes rather than a check in each of the fifteen write routes, because that list grows and a route added later would quietly miss it. Reads always pass — lockdown hides nothing. Day-to-day operation stays available: invitations, announcements, registration, feature flags, and the test endpoints, which persist nothing. A setting invented later is locked until someone deliberately makes it editable, rather than defaulting to open. The panel disables what it cannot save and says why, but that is courtesy; the refusal is what enforces it. Two things this taught me, both fixed: my first version painted the panel from an IIFE, which the module conventions forbid, and fetched the whole config a second time just to read one flag — breaking the test that pins admin loaders firing exactly once. The state now rides on the invites response the panel already requests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
79 lines
2.9 KiB
JavaScript
79 lines
2.9 KiB
JavaScript
// ============================================================
|
|
// ADMIN LOCKDOWN
|
|
// With several admins, everything in the admin panel is editable by all of
|
|
// them — prompts, model policy, retrieval limits, SMTP. Lockdown separates
|
|
// "runs the service day to day" from "changes how the service behaves".
|
|
//
|
|
// It is an environment variable, deliberately, not a setting. A setting could
|
|
// be switched off by any admin, which would make it decoration. Only someone
|
|
// with access to the host and a restart can lift it.
|
|
//
|
|
// Locked settings stay visible — an admin should be able to see how the system
|
|
// is configured — but the field is read-only and the server refuses the write.
|
|
// The refusal is the real control; the read-only field is only courtesy.
|
|
// ============================================================
|
|
|
|
// Everything that changes how the assistant answers, what it costs, or how mail
|
|
// leaves the building.
|
|
var LOCKED_PREFIXES = Object.freeze([
|
|
'prompt.', // every prompt, including the clinical ones
|
|
'clinical_assistant.', // models, allowlists, retrieval limits, budgets
|
|
'models.', // model policy: default, custom, enabled set
|
|
'tts.',
|
|
'stt.',
|
|
'embeddings.',
|
|
'smtp.', // where mail goes and who it authenticates as
|
|
'email.' // the templates that mail sends
|
|
]);
|
|
|
|
// Day-to-day operation stays with ordinary admins.
|
|
var EDITABLE_WHEN_LOCKED = Object.freeze([
|
|
'announcement.',
|
|
'registration_enabled',
|
|
'registration_invite_only',
|
|
'feature.',
|
|
'site.'
|
|
]);
|
|
|
|
function enabled(env) {
|
|
var raw = (env || process.env).ADMIN_LOCKDOWN;
|
|
return String(raw == null ? '' : raw).toLowerCase() === 'true';
|
|
}
|
|
|
|
// A key is locked when lockdown is on and it is not on the day-to-day list.
|
|
// Anything unrecognised is locked: a setting added later should need a
|
|
// deliberate decision to become editable, rather than defaulting to open.
|
|
function isLocked(key, env) {
|
|
if (!enabled(env)) return false;
|
|
var name = String(key || '');
|
|
for (var i = 0; i < EDITABLE_WHEN_LOCKED.length; i++) {
|
|
var allowed = EDITABLE_WHEN_LOCKED[i];
|
|
if (name === allowed || (allowed.endsWith('.') && name.startsWith(allowed))) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// What the admin UI needs to render itself correctly.
|
|
function state(env) {
|
|
return {
|
|
enabled: enabled(env),
|
|
lockedPrefixes: LOCKED_PREFIXES.slice(),
|
|
editablePrefixes: EDITABLE_WHEN_LOCKED.slice(),
|
|
reason: 'ADMIN_LOCKDOWN is set on the server. Locked settings can only be changed by someone with host access.'
|
|
};
|
|
}
|
|
|
|
// One message, so every refusal reads the same wherever it comes from.
|
|
function refusal(key) {
|
|
return 'This setting is locked. The server is in admin lockdown (ADMIN_LOCKDOWN), ' +
|
|
'so "' + key + '" can only be changed by someone with host access.';
|
|
}
|
|
|
|
module.exports = {
|
|
LOCKED_PREFIXES,
|
|
EDITABLE_WHEN_LOCKED,
|
|
enabled,
|
|
isLocked,
|
|
state,
|
|
refusal
|
|
};
|