No durable mechanism to bootstrap the first admin user (admin pages Forbidden after first login) #28
Labels
No labels
bug
enhancement
pr-split
question
security
transaction-matcher
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
momsse/octant#28
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
There is no durable way to bootstrap the first admin user. A freshly-authenticated user (first Discord/Google login) is created with zero permissions, and every
/admin/*operation is gated by a permission policy (users:manage,roles:manage,groups:manage,permissions:manage,audit:read,security:audit). Granting those permissions itself requiresroles:manage/permissions:manage— a chicken-and-egg: nobody can grant the first admin from inside the app.Observed during a local smoke of
apps/backoffice: after logging in, every admin list page returnsForbiddenError(theResultHandlererror path renders correctly). The dashboard, profile and session pages work because they are self-service (no admin permission required).How permissions are resolved today
apps/api/src/auth/auth-middleware.layer.tsbuilds theVisitorfromEffectivePermissionsQuery(userId), which reads (seepackages/infrastructure/postgres-authorization/.../effective-permissions.query.adapter.ts):Current (manual, unsatisfactory) workaround
To unblock local testing we manually seeded the read models:
This unblocks the policy middleware, but it is incomplete and inconsistent: it writes only the lookup read models, not the role/permission aggregates (event store) nor
read.role_directory. As a result thecheckUserPermissionsadmin query (which reads a richer path with sources) still reports "No effective permission" for the same user, even though the middleware grants access. Bypassing the write side leaves read paths out of sync.What we should decide
A durable, write-side-correct bootstrap. Options to weigh:
adminrole aggregate (with all management permissions as real permission aggregates) via the domain commands, then assigns it — so every projection/read path is consistent.BOOTSTRAP_ADMIN_EMAILS; on login (or on boot) the matching user is granted the admin role through the normal command path. Idempotent.pnpm ... grant-admin <email>) issuing the real authorization commands.Whatever we pick must go through the aggregates/commands (not raw read-model inserts) so the middleware query,
checkUserPermissions,role_directory, and audit all stay consistent.Context
split/13-backoffice). Not a bug in that PR — a missing platform capability surfaced by it.