Skip to content

Protected superadmin, re-asserted on every deploy - #14

Merged
Adron merged 1 commit into
mainfrom
feature/protected-superadmin
Oct 20, 2026
Merged

Adron merged 1 commit into
mainfrom
feature/protected-superadmin

Conversation

@Adron

@Adron Adron commented Oct 20, 2026

Copy link
Copy Markdown
Member

Summary

The platform owner's account (SUPERADMIN_EMAIL, set to the owner's address as a repository variable) always exists as an active superadmin and can't be locked out.

  • Migration 0004_protected_superadmin.sql: users.protected plus database triggers refusing suspend, delete, un-protect, email/test-mode change, superadmin-role removal, and erasure for a protected User, from any caller. app.protected_user_maintenance = 'on' lifts it for one transaction (ownership hand-over). Numbered 0004 so it ships ahead of Implement push notifications and the notification inbox #12's 0003_push.sql; migrations apply any unrecorded version in order, and the two touch disjoint tables.
  • Every deploy re-asserts it: cmd/migrate runs EnsureProtectedSuperadmin when SUPERADMIN_EMAIL is set; deploy.yml passes the variable. Created without a password; never changes an existing one.
  • API: 409 user_protected for delete, suspend, status/email PATCH, erasure, and removing role_platform_superadmin.
  • Docs: Users → The protected superadmin, Database Schema → users, NFR → Bootstrapping, README.

Test plan

  • TestProtectedSuperadmin: idempotent ensure, every API path refused, direct SQL refused, maintenance override, next ensure restores
  • Full go test ./..., gofmt, go vet
  • CI green

🤖 Generated with Claude Code

The platform owner's account must always exist and never be lockable out.

- Migration 0004: users.protected, plus triggers that refuse, for every
  caller, any change that would lock a protected User out: suspend,
  soft- or hard-delete, clear protected, change email or test_mode, drop
  its superadmin role, or schedule its erasure. SET LOCAL
  app.protected_user_maintenance = 'on' lifts it for one transaction, for
  handing ownership over. Numbered 0004 to ship ahead of 0003_push.
- EnsureProtectedSuperadmin: idempotent; creates the account (active,
  verified, no password) or restores it to active + protected + superadmin.
  Never touches an existing password.
- cmd/migrate runs it after migrations when SUPERADMIN_EMAIL is set, and
  deploy.yml passes the SUPERADMIN_EMAIL repository variable, so every
  deploy re-asserts it.
- The API returns 409 user_protected instead of a failed write for delete,
  suspend, status/email PATCH, erasure, and superadmin role removal.
- Docs: Users → The protected superadmin, Database Schema → users, NFR →
  Bootstrapping, README one-time setup step 6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Adron
Adron merged commit 6e063b5 into main Oct 20, 2026
7 of 8 checks passed
@Adron
Adron deleted the feature/protected-superadmin branch October 20, 2026 21:45
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