Files
UmbraTrans/umbratrans_web/migrations/README.md
umbra audio b474995414 Add phase-1 account management backend slice
Implements the account and user-management system behind the new web UI
against development repositories only. Nothing here connects to, reads, or
modifies the production umbracontrol database, and no account is created in
any approved environment.

Account service and API:
- Local temporary-password and OIDC enrollment, separated by type.
- Argon2id credentials, must-change enforcement, server-side sessions with
  hashed tokens, idle and absolute deadlines, rotation and revocation.
- Administrator account management: list, get, create, suspend, reactivate,
  deactivate, revoke sessions, org-scoped audit view.
- Hidden-prompt operator command for the first administrator, refusing
  production mode and refusing to run without a TTY.

Temporary passwords are never accepted from a client. The request schema has
no password field and refuses unknown fields; the administrator path generates
the one-time password in process, hashes it immediately, and returns it once in
the creation response. The account can reach no route, including
/api/auth/me, until it replaces that password, after which the one-time value
no longer authenticates.

Security posture: fail-closed configuration, Secure cookies in production,
double-submit CSRF, constant-time token comparison, generic authentication
errors, audit detail allow-listing, and logger-tree secret redaction.

Reviewed but unapplied: migrations/0001_phase1_identity.sql adds
users.username, a separate user_credentials table, and additive session
columns, with a rollback and open review questions. The PostgreSQL adapter is
deliberately absent, so the application refuses to start in production rather
than run against an unreviewed store.

Verification: 169 tests pass (148 new, 21 pre-existing); ruff check, ruff
format, compileall, and make web-check are clean.
2026-09-26 08:29:23 -07:00

3.3 KiB

Phase-1 identity migrations

Status: proposed, never applied, awaiting review.

The SQL in this directory has not been executed against any database, including any development database. It exists so a reviewer can assess the phase-1 identity change before an operator runs it under a change ticket.

File Purpose
0001_phase1_identity.sql Forward migration: users.username, user_credentials, additive sessions columns
0001_phase1_identity_rollback.sql Destructive rollback with prerequisites and verification queries

What the forward migration changes

Exactly three things, matching docs/umbratrans-backend-design.md:

  1. users.username — nullable, unique, citext (case-insensitive). Existing rows stay NULL until an operator supplies an explicit mapping.
  2. user_credentials — one credential row per user, with credential_type in (local_password, oidc), an Argon2id password_hash that is required for local_password and forbidden for oidc, must_change_password, failed_login_count, locked_until, last_login_at, password_changed_at, and timestamps.
  3. sessions — additive columns mfa_verified_at, idle_deadline_at, last_seen_at. Nothing is dropped, renamed, or retyped.

It creates no credential rows, backfills no username, and changes no organization, project, role, permission, invitation, or audit table.

Review questions the file cannot answer itself

These are BLOCKER-level unknowns. This workspace has no access to the production schema, so the file states its assumptions instead of guessing.

  • Does users have a uuid primary key named id, and no existing column named username?
  • What is the name of the "current user" RLS helper (app.current_user_id(), a request-scoped GUC, or something else), and can a client influence it?
  • Are users and sessions already under RLS, and with FORCE?
  • What are the migration role and application role names?
  • Is citext available to the migration role? If not, use a lower(...) unique index instead of a case-sensitive text column.
  • Does a generic set_updated_at() trigger helper exist, or does the application maintain updated_at?
  • Is sessions large enough that CREATE INDEX must be replaced with CREATE INDEX CONCURRENTLY outside the transaction?
  • Which existing sessions column should backfill last_seen_at, if any?

Gates that must pass before approval

  • Logical backup taken, checksummed, and restore-tested on a scratch database.
  • Migration role and application role privileges verified.
  • RLS tests covering two organizations and two projects, including FORCE ROW LEVEL SECURITY and a client-controlled-GUC escalation attempt.
  • Account-management tests passing against an isolated PostgreSQL database with the PostgreSQL adapter implemented.
  • Rollback rehearsed on the scratch database.

Until then, production PostgreSQL stays untouched and the application runs against a development repository.

Data classification

  • user_credentials.password_hash — authentication secret (hash only). Never selected into a report, an endpoint response, or a log.
  • sessions.token_hash — authentication secret (hash only), unchanged by this migration.
  • users.username and users.email — personal data. Included in data-export and retention reviews for the control plane.