Skip to content

feat(admin): allow one admin or several, and hand the admin role over (D1) - #491

Merged
huyplb merged 1 commit into
mainfrom
feat/admin-policy
Oct 10, 2026
Merged

huyplb merged 1 commit into
mainfrom
feat/admin-policy

Conversation

@huyplb

@huyplb huyplb commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

D1 of the workspaces plan, which this PR also adds.

What changes

  • Admin → Access control → App users gets an Admins: One / Several control.
  • With one, these are refused with 409 while an active admin exists: adding an account as admin, promoting one, reactivating an old admin. The rule lives in RbacModule / AuthModule.insertUser, so every caller hits it.
  • Make admin (shown to the admin under one) hands the role over in a single UPDATE … CASE; the previous admin becomes an owner.
  • Switching to one is refused while several admins are active, and the error names them.
  • Default, recorded once at startup: one for new installs and installs with a single admin, several for installs that already have more. FOX_ADMIN_POLICY=one|several overrides it and makes the control read-only.
  • New routes: GET/PUT /api/admin/policy, POST /api/admin/users/:id/transfer-admin (added to the HTTP contract table, 116 → 119).

Tests

  • admin-policy.service.test.ts (23): defaults, each gate, switching, transfer. Each gate was A/B-checked: removing it fails its test.
  • Component tests for the control, locked admin options, Make admin, env lock; helper tests.
  • New e2e admin-policy.test.ts, run against the dev server: 3 passed, the one-policy case skipped because the dev DB has several admins.

Review

Self-review fixed: a 403 banner after a successful transfer, the success message not shown on App users, re-saving Active on an active admin being refused, and hint wording. Left as is: a transfer to an invited account (SSO accounts never set a password, so blocking it would block them), and a check-then-write race (the metadata store has no transactions).

🤖 Generated with Claude Code


Note

High Risk
Changes authorization and admin-account lifecycle (role promotion, reactivation, transfer) with security-sensitive 409 behavior; a noted check-then-write race remains without DB transactions.

Overview
Adds an install-wide one admin vs several policy (D1 from the new workspaces plan doc), wired through server enforcement, admin UI, and tests.

Server: auth.admin_policy is read/written via GET/PUT /api/admin/policy, with FOX_ADMIN_POLICY override and a one-time startup backfill (one for ≤1 active admin, several if more). Under one, assertAdminSlotFree blocks creating/promoting/reactivating a second admin (409 via AdminPolicyError). POST /api/admin/users/:id/transfer-admin atomically promotes another user and demotes the caller (default owner).

Web: App users shows Admins: One / Several, disables direct admin role picks when the slot is taken, and under one shows Make admin (hand-over with confirm) instead of promotion; env-locked policy is read-only.

Tests: New server suite, component/helper coverage, e2e admin-policy.test.ts in the full e2e runner, HTTP contract count 116→119, and new data-testids.

Reviewed by Cursor Bugbot for commit fb2ed79. Bugbot is set up for automated code reviews on this repo. Configure here.

An admin chooses whether the install allows one admin or several. With
one, adding an admin, promoting one or reactivating an old admin is
refused (409) while an active admin exists; Make admin hands the role
over in one UPDATE, the previous admin becoming an owner.

New installs and installs with a single admin default to one; installs
that already have several keep several. FOX_ADMIN_POLICY=one|several
overrides it. Switching to one is refused while several admins are
active, and the error names them.

Also adds the workspaces plan (D1 of docs/plans/2026-10-09-workspaces-
and-admin-policy.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cursor

cursor Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: e30cb660-e419-47eb-b2c0-b018ebe6840e)

@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Test IDs

Added (5)

  • admin-policy
  • admin-policy-hint
  • admin-policy-{…}
  • admin-transfer-admin-{…}
  • admin-users-status

{…} is a part filled in at run time. The full tree: docs/testing/TEST_IDS.md.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant