feat(auth): bootstrap first admin from BOOTSTRAP_ADMIN_EMAIL (BUNYIP-290) #349
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/BUNYIP-290-bootstrap-admin-email"
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?
Implements BUNYIP-290. Replaces the interactive first-admin setup wizard with a declarative
BOOTSTRAP_ADMIN_EMAILenv-var bootstrap.Behavior
The site comes up fully usable against a freshly wiped database with no setup gate and no
/setuppage. The first admin is created automatically when the user whose email matchesBOOTSTRAP_ADMIN_EMAILauthenticates (sign-up or sign-in) while zero admins exist. Once any admin exists the bootstrap path is inert, and the existing admin-invite and role-management flows govern all further admin changes. The gate is "zero admins exist", so it self-heals: if every admin is later removed, the bootstrap email can re-establish the first admin on its next authentication (intended lockout recovery, not a second bootstrap of a live system).Changes
Config.bootstrap_admin_email: Option<String>read fromBOOTSTRAP_ADMIN_EMAILinfrom_env_inner, trimmed + lowercased so it compares equal to stored/normalized emails; unset or empty =None. Startup logs whether a bootstrap admin is configured (boolean only, never the address per BUNYIP-265).AuthService::ensure_bootstrap_adminis called fromregister(before the audit log) and fromlogin(after the password check, before any 2FA branch or token mint), so the issued JWT carriesrole = "admin"with no second login. The decision is a pure, DB-freebootstrap_promotion_neededpredicate; the caller adds a cheap pre-filter so the admin-count query runs only inside the actual bootstrap window. Promotion uses the idempotentupdate_role, so concurrent bootstrap authentications still converge on a single admin.POST /v1/auth/setup+setup_admin(bunyip-api);/setuproute,setup_get/setup_post,SetupForm,setup_card, and thesetupPOST client (bunyip-web).GET /v1/auth/setup/statusstays as a feature-flags probe (email_enabled/stripe_enabled) but drops the now-meaninglesssetup_requiredfield from the response,SetupStatus, and the three web consumers.BOOTSTRAP_ADMIN_EMAILdocumented in.env.exampleand set on the api service incompose.dev.ymlandcompose.yml.SETUP_DEFAULT_ADMINand the e2e bootstrap binary are unchanged.Tests
Six new unit tests cover the five promotion scenarios from the issue (promote on match with zero admins, case-insensitive match, non-bootstrap email stays subscriber, inert once an admin exists, no re-promote of an existing admin, and unset leaves the site admin-less).
just checkequivalents (fmt + clippy-D warnings+ build + full workspace test suite) are green.No
sqlx::query!macros were touched, so no.sqlx/regeneration is needed. No e2e specs reference the removed/setupflow.🤖 Generated with Claude Code