feat(admin): add create_site_admin.py for bootstrapping a super_admin #167

Merged
chihlasm merged 1 commits from feat/site-admin-script into main 2026-05-12 06:17:32 +00:00
Owner

Summary

Adds a committed CLI script for creating or promoting a site-wide super_admin on any environment. Solves the prod bootstrap case — prod has no admin user today, dev's seed_test_users.py only runs in dev, self-serve is still gated, and even when it flips on, signup creates owner roles not super_admins.

Behavior

  • --email <addr> required. Normalized to lowercase.
  • If the user does not exist: creates an Account + super-admin User as the account owner. email_verified_at stamped, password_hash=NULL (so first login must go through the reset flow — no password ever on disk or shell history).
  • If the user exists: promotes is_super_admin=true and backfills email_verified_at if null. Idempotent.
  • --send-reset mints a JWT, persists the hash in password_reset_tokens, and emails the link via EmailService.send_password_reset_email. Email send is best-effort — fallback URL printed to stdout so a misconfigured email service never blocks the bootstrap.
  • --print-reset skips the email and just prints the URL. Useful when email infra is not yet configured on a fresh env.
  • --promote-only skips user creation. Only promotes an existing user. Skips reset issuance unless the existing user has no password.

Uses ADMIN_DATABASE_URL (BYPASSRLS) — required because the users table is RLS-enabled and the script has no tenant context at bootstrap.

Smoke test (dev)

All three paths exercised in dev container:

  • Fresh create on a new email — Account + User + reset link emitted.
  • Re-run on same email — SKIP message, fresh reset link minted.
  • --promote-only on 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:

railway run python -m scripts.create_site_admin --email michael@resolutionflow.com --send-reset

Reset link arrives at michael@resolutionflow.com, user clicks, sets a password, logs in as super_admin.

Test plan

  • --help parses and imports cleanly inside the backend container.
  • Fresh create on a new email in dev.
  • Idempotent re-run on the same email in dev.
  • --promote-only path in dev.
  • After merge, run against prod via railway run when EIN/Stripe blockers clear.

Co-Authored-By: Claude Opus 4.7

## Summary Adds a committed CLI script for creating or promoting a site-wide `super_admin` on any environment. Solves the prod bootstrap case — prod has no admin user today, dev's `seed_test_users.py` only runs in dev, self-serve is still gated, and even when it flips on, signup creates `owner` roles not `super_admins`. ## Behavior - `--email <addr>` required. Normalized to lowercase. - If the user does not exist: creates an `Account` + super-admin `User` as the account owner. `email_verified_at` stamped, `password_hash=NULL` (so first login must go through the reset flow — no password ever on disk or shell history). - If the user exists: promotes `is_super_admin=true` and backfills `email_verified_at` if null. Idempotent. - `--send-reset` mints a JWT, persists the hash in `password_reset_tokens`, and emails the link via `EmailService.send_password_reset_email`. Email send is best-effort — fallback URL printed to stdout so a misconfigured email service never blocks the bootstrap. - `--print-reset` skips the email and just prints the URL. Useful when email infra is not yet configured on a fresh env. - `--promote-only` skips user creation. Only promotes an existing user. Skips reset issuance unless the existing user has no password. Uses `ADMIN_DATABASE_URL` (BYPASSRLS) — required because the `users` table is RLS-enabled and the script has no tenant context at bootstrap. ## Smoke test (dev) All three paths exercised in dev container: - Fresh create on a new email — Account + User + reset link emitted. - Re-run on same email — SKIP message, fresh reset link minted. - `--promote-only` on 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: ``` railway run python -m scripts.create_site_admin --email michael@resolutionflow.com --send-reset ``` Reset link arrives at `michael@resolutionflow.com`, user clicks, sets a password, logs in as super_admin. ## Test plan - [x] `--help` parses and imports cleanly inside the backend container. - [x] Fresh create on a new email in dev. - [x] Idempotent re-run on the same email in dev. - [x] `--promote-only` path in dev. - [ ] After merge, run against prod via `railway run` when EIN/Stripe blockers clear. Co-Authored-By: Claude Opus 4.7
chihlasm added 1 commit 2026-05-12 05:59:11 +00:00
feat(admin): add create_site_admin.py for bootstrapping a super_admin
All checks were successful
CI / frontend (pull_request) Successful in 6m23s
Mirror to GitHub / mirror (push) Successful in 5s
CI / backend (pull_request) Successful in 10m10s
CI / e2e (pull_request) Successful in 9m14s
3a3844b68e
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>
chihlasm merged commit e50a2150d5 into main 2026-05-12 06:17:32 +00:00
chihlasm deleted branch feat/site-admin-script 2026-05-12 06:17:32 +00:00
Sign in to join this conversation.