feat(admin): add create_site_admin.py for bootstrapping a super_admin #167
Reference in New Issue
Block a user
Delete Branch "feat/site-admin-script"
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
Adds a committed CLI script for creating or promoting a site-wide
super_adminon any environment. Solves the prod bootstrap case — prod has no admin user today, dev'sseed_test_users.pyonly runs in dev, self-serve is still gated, and even when it flips on, signup createsownerroles notsuper_admins.Behavior
--email <addr>required. Normalized to lowercase.Account+ super-adminUseras the account owner.email_verified_atstamped,password_hash=NULL(so first login must go through the reset flow — no password ever on disk or shell history).is_super_admin=trueand backfillsemail_verified_atif null. Idempotent.--send-resetmints a JWT, persists the hash inpassword_reset_tokens, and emails the link viaEmailService.send_password_reset_email. Email send is best-effort — fallback URL printed to stdout so a misconfigured email service never blocks the bootstrap.--print-resetskips the email and just prints the URL. Useful when email infra is not yet configured on a fresh env.--promote-onlyskips user creation. Only promotes an existing user. Skips reset issuance unless the existing user has no password.Uses
ADMIN_DATABASE_URL(BYPASSRLS) — required because theuserstable is RLS-enabled and the script has no tenant context at bootstrap.Smoke test (dev)
All three paths exercised in dev container:
--promote-onlyon a user with no password — promoted in place, reset link issued.Cleaned up the dev test user after.
Intended prod use
Once Stripe live-mode is sorted (separately blocked on EIN as of 2026-05-12) and Railway prod env is set up, run:
Reset link arrives at
michael@resolutionflow.com, user clicks, sets a password, logs in as super_admin.Test plan
--helpparses and imports cleanly inside the backend container.--promote-onlypath in dev.railway runwhen EIN/Stripe blockers clear.Co-Authored-By: Claude Opus 4.7
Idempotent CLI script that creates or promotes a site-wide super_admin on any environment. Solves the prod bootstrap case where no admin exists yet — dev's seed_test_users.py only runs in dev, self-serve signup is still gated, and even when enabled, signup creates owner roles, not super_admins. The script: - Reads --email (required), normalizes to lowercase. - If user does not exist: creates an Account + super_admin User as the account owner, with email_verified_at stamped at creation and password_hash=NULL (forces the reset flow on first login). - If user exists: promotes is_super_admin=true and backfills email_verified_at if null. Idempotent — re-running is safe. - Mints a password-reset JWT, stores the token hash in password_reset_tokens, and either emails the link (--send-reset) or prints it to stdout (--print-reset). Email send is best-effort with a fallback URL on stdout so a misconfigured EmailService never blocks login. - --promote-only flag: skips creation, only promotes an existing user. Useful for promoting an already-self-served user without triggering an unnecessary reset. Uses ADMIN_DATABASE_URL when set (BYPASSRLS — required because users is RLS-enabled and the script has no tenant context at bootstrap). Smoke-tested in dev against all three paths: fresh create, re-run idempotency on the same email, --promote-only on an existing user with no password. Intended invocation on prod, once Stripe/EIN unblocks: railway run python -m scripts.create_site_admin \ --email michael@resolutionflow.com \ --send-reset Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>