- Route group (app) holds AuthProvider + Sidebar, so /login and /setup
render without app chrome and never mount the provider.
- AuthProvider drops the removed auth_enabled flag and exposes
{user, org, isAdmin}.
- New login page (password + SSO), first-run setup page, and org settings
page with a members table and the OIDC provider form.
- Settings page and the Organization nav entry are gated on role, since
/api/settings now 403s for members.
- GET /api/org/oidc gains client_secret_set so the UI can show whether a
secret is stored; the secret itself is still never serialized, and an
empty submitted value still means "keep the stored one".
- Fix logout: the sidebar linked to /auth/logout with a GET, but the route
is POST-only, so logout was 404ing.
Review follow-ups on e5363a6:
- MigrateSettingsOrg no longer guesses via the "default" slug. One org
means stamp that org; zero orgs means synthesise Default; more than
one means leave it alone and log, since guessing would hand one org
another's SMTP config and ESO token.
- EnsureSettingsIndexes failure is now fatal. Without the unique index
on org_id, GetSettings returns an arbitrary duplicate; without the one
on the token hash, ResolveSecretsReadToken picks an arbitrary org.
- Name the token-hash index explicitly so it stops colliding with the
legacy name DropOne targets, and exclude the empty string from the
partial filter.
- Log retention: distinguish a missing run doc from a Mongo error, so a
transient failure skips the directory rather than purging it at the
30-day default.
- Offline sweep: fresh context per org, log-and-continue on a per-org
error, plus a final pass for servers whose org no longer exists.
- ESO handler 401s on an empty token-derived org rather than querying
org_id "".
The settings collection was a single global document, so every org
shared one SMTP config, alert config, retention policy and ESO read
token. GetSecretGroupDecryptedAny then flattened every org's secrets
for a group into one map, meaning any tenant's token read every
tenant's secrets.
- settings gains org_id; GetSettings/SaveSettings/RotateSecretsReadToken/
GetWorkflowLogRetentionDays all take orgID
- VerifySecretsReadToken replaced by ResolveSecretsReadToken, which
resolves the org from the presented token's hash; the ESO endpoint
derives its org from the token rather than a session, since it is
called machine-to-machine
- GetSecretGroupDecryptedAny deleted in favour of the org-scoped variant
- settings and token-rotation routes now require owner/admin
- offline sweep and log retention resolve org per server / per run
- migration 0002 stamps the legacy settings doc with the default org
Note: /api/settings now 403s for members; the web settings page needs a
matching role check.
Review of the org-scoping pass found that org_id on a query filter
protects the row you look up, but does nothing when a handler accepts a
foreign resource ID as data and a downstream unscoped query consumes it.
- AssignKey: verify key and server both belong to the org
- BuildAuthorizedKeys: resolve server first, scope assignments and keys
to that server's org (was honouring foreign assignment rows)
- Workflows: validate TargetServerIDs on create/update and re-check at
trigger time
- Monitor incidents/uptime handlers: gate on org-scoped GetMonitor
- GetChannels: take orgID; validate channel_ids on monitor create/update
- Secret and default-step unique indexes: scope to org_id so a second
org no longer hits E11000
- DeleteServer/DeleteMonitor: scope cascading deletes
Threads org_id through every admin-facing service function (servers, keys,
assignments, secrets, workflows/steps/runs, monitors, channels, audit),
adds RequireRole middleware, and wires /api/org user + OIDC management
routes. Agent/scheduler paths keep unique-key signatures and resolve org
from the loaded record; internal-only helpers (getServerByID,
getRunByID, getMonitorByID) preserve those call sites.