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.
112 lines
5.2 KiB
PL/PgSQL
112 lines
5.2 KiB
PL/PgSQL
-- =====================================================================
|
|
-- Rollback for: UmbraTrans phase-1 identity migration
|
|
-- (users.username, user_credentials, additive session columns)
|
|
-- =====================================================================
|
|
--
|
|
-- STATUS: PROPOSED / NOT APPLIED / NOT APPROVED
|
|
--
|
|
-- This file has never been executed against any database.
|
|
--
|
|
-- DESTRUCTIVE. Read the warnings before running anything.
|
|
--
|
|
-- * DROP TABLE public.user_credentials DESTROYS every credential row that
|
|
-- exists at rollback time. Accounts created or re-enrolled after the
|
|
-- forward migration lose their only authentication material and cannot
|
|
-- sign in until an operator re-provisions them through the operator
|
|
-- path. There is no recovery short of restoring a backup.
|
|
-- * DROP CONSTRAINT users_username_key / the backing index removes case
|
|
-- insensitive uniqueness. The username VALUES are kept by this rollback;
|
|
-- drop the column only if the mapping must be discarded.
|
|
-- * Dropping the additive session columns discards MFA, idle deadline, and
|
|
-- last-seen history. An application build that still writes those columns
|
|
-- will fail after the rollback, so roll the application back first.
|
|
--
|
|
-- PREREQUISITES
|
|
-- [ ] Forward migration 0001 confirmed applied (see its section 5 output)
|
|
-- [ ] Fresh pg_dump -Fc backup taken and checksummed AFTER the forward
|
|
-- migration, so the post-migration state is recoverable
|
|
-- [ ] Restore rehearsed on a scratch database
|
|
-- [ ] Application rolled back to a build that does not use the new columns
|
|
-- [ ] Maintenance window agreed (the sessions DDL takes ACCESS EXCLUSIVE)
|
|
--
|
|
-- ---------------------------------------------------------------------
|
|
-- 0. Transaction guard rails (same limits as the forward migration)
|
|
-- ---------------------------------------------------------------------
|
|
|
|
BEGIN;
|
|
|
|
SET LOCAL lock_timeout = '3s';
|
|
SET LOCAL statement_timeout = '60s';
|
|
SET LOCAL idle_in_transaction_session_timeout = '30s';
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- 1. Record what will be lost, before it is lost
|
|
-- ---------------------------------------------------------------------
|
|
|
|
-- SELECT count(*) AS credentials_to_be_dropped FROM public.user_credentials;
|
|
-- SELECT credential_type, count(*) FROM public.user_credentials GROUP BY 1;
|
|
-- SELECT count(*) AS live_sessions FROM public.sessions WHERE revoked_at IS NULL;
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- 2. Additive session columns
|
|
-- ---------------------------------------------------------------------
|
|
-- Drop the indexes first; they are not required to drop the columns, but
|
|
-- removing them explicitly keeps the rollback symmetric with the forward file.
|
|
|
|
DROP INDEX IF EXISTS public.sessions_idle_deadline_at_idx;
|
|
DROP INDEX IF EXISTS public.sessions_user_id_idx;
|
|
|
|
ALTER TABLE public.sessions
|
|
DROP COLUMN IF EXISTS idle_deadline_at,
|
|
DROP COLUMN IF EXISTS mfa_verified_at,
|
|
DROP COLUMN IF EXISTS last_seen_at;
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- 3. user_credentials
|
|
-- ---------------------------------------------------------------------
|
|
-- The table and its constraints disappear with it. Any credential that is not
|
|
-- recoverable from a backup is gone: re-provision affected accounts through
|
|
-- umbratrans-bootstrap-admin (operator path) after the rollback is verified.
|
|
|
|
DROP TABLE IF EXISTS public.user_credentials;
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- 4. users.username
|
|
-- ---------------------------------------------------------------------
|
|
-- Values are preserved by default. Uncomment the second statement only when
|
|
-- the mapping must be discarded as well.
|
|
|
|
ALTER TABLE public.users DROP CONSTRAINT IF EXISTS users_username_key;
|
|
DROP INDEX IF EXISTS public.users_username_key;
|
|
|
|
-- ALTER TABLE public.users DROP COLUMN IF EXISTS username;
|
|
|
|
-- citext is left installed: dropping an extension can cascade to dependent
|
|
-- objects, and an unused extension is harmless. Remove it only in a separate,
|
|
-- explicitly reviewed change.
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- 5. POST-ROLLBACK VERIFICATION
|
|
-- ---------------------------------------------------------------------
|
|
--
|
|
-- \d+ public.users
|
|
-- \d+ public.sessions
|
|
--
|
|
-- SELECT to_regclass('public.user_credentials') AS user_credentials; -- NULL
|
|
-- SELECT count(*) FROM pg_policies WHERE schemaname = 'public'
|
|
-- AND tablename = 'user_credentials'; -- 0
|
|
-- SELECT count(*) FROM public.sessions WHERE idle_deadline_at IS NOT NULL; -- 0
|
|
--
|
|
-- -- the pre-existing schema must be unchanged apart from this rollback
|
|
-- SELECT column_name, data_type FROM information_schema.columns
|
|
-- WHERE table_schema = 'public' AND table_name IN ('users', 'sessions')
|
|
-- ORDER BY table_name, ordinal_position;
|
|
--
|
|
-- -- re-run the control-plane integrity checks that were captured before the
|
|
-- -- forward migration and compare row counts for organizations, projects,
|
|
-- -- roles, permissions, user_roles, invitations, and audit_events
|
|
--
|
|
-- Roll forward again only after a new review of 0001_phase1_identity.sql.
|
|
|
|
COMMIT;
|