1791158400_users.sql 4.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. -- Users, scope catalog, per-user entitlements, redirect-URI catalog, and
  2. -- per-client URI attachments (plan Scope line 31 / line 33).
  3. --
  4. -- Forward-only: deterministic, no DROP / down path. This migration creates
  5. -- schema ONLY and seeds ZERO rows: the scope catalog, users, and redirect URIs
  6. -- are all created at runtime via CLI / `config sync` / `admin bootstrap` /
  7. -- DCR. Two deliberate omissions:
  8. -- * no failed-login/lockout columns: those counters are security-ephemeral
  9. -- and live in Redis (todo 11), never Postgres;
  10. -- * the TOTP secret is stored ENCRYPTED as `bytea` (todo: TOTP encryption
  11. -- key from the KeyRing), never as a plaintext column.
  12. --
  13. -- `users.id` PK and `oauth_clients.client_id` (task 8) are the FK targets for
  14. -- the join tables below, so this migration's version is later than 1791072000.
  15. -- Case-insensitive email-as-username requires the `citext` type. The extension
  16. -- is created here (idempotently) because this is the first migration that needs
  17. -- it; PostgreSQL requires superuser/trusted-extension privileges to install it.
  18. CREATE EXTENSION IF NOT EXISTS citext;
  19. CREATE TABLE users (
  20. id uuid NOT NULL,
  21. email citext NOT NULL,
  22. password_hash text NOT NULL,
  23. totp_secret_enc bytea NULL,
  24. totp_enabled bool NOT NULL DEFAULT false,
  25. totp_recovery_code_hashes jsonb NULL,
  26. is_admin bool NOT NULL DEFAULT false,
  27. must_change_password bool NOT NULL DEFAULT false,
  28. password_changed_at timestamptz NULL,
  29. disabled bool NOT NULL DEFAULT false,
  30. created_at timestamptz NOT NULL,
  31. updated_at timestamptz NOT NULL,
  32. CONSTRAINT users_pkey PRIMARY KEY (id),
  33. -- citext comparison makes this case-insensitive: `A@x.com` and `a@x.com`
  34. -- collide, so one account per email regardless of case.
  35. CONSTRAINT ux_users_email UNIQUE (email)
  36. );
  37. -- Runtime-managed scope catalog, created EMPTY (todo 9 has no seed rows).
  38. -- The enabled rows are the single source of truth for `scopes_supported`.
  39. CREATE TABLE scopes (
  40. name text NOT NULL,
  41. description text NOT NULL DEFAULT '',
  42. enabled bool NOT NULL DEFAULT true,
  43. created_at timestamptz NOT NULL,
  44. updated_at timestamptz NOT NULL,
  45. CONSTRAINT scopes_pkey PRIMARY KEY (name)
  46. );
  47. -- Per-user entitlements. New users start with none (explicit grants only).
  48. CREATE TABLE user_scopes (
  49. user_id uuid NOT NULL,
  50. scope_name text NOT NULL,
  51. granted_at timestamptz NOT NULL,
  52. granted_by uuid NULL,
  53. CONSTRAINT user_scopes_pkey PRIMARY KEY (user_id, scope_name),
  54. CONSTRAINT fk_user_scopes_user FOREIGN KEY (user_id)
  55. REFERENCES users(id) ON DELETE CASCADE,
  56. CONSTRAINT fk_user_scopes_scope FOREIGN KEY (scope_name)
  57. REFERENCES scopes(name)
  58. );
  59. -- Runtime-managed redirect-URI catalog. `uri` is stored normalized-exact and
  60. -- is globally unique; clients attach to entries via `client_redirect_uris`.
  61. -- Redirect URIs attach to clients, never to users.
  62. CREATE TABLE redirect_uris (
  63. id uuid NOT NULL,
  64. uri text NOT NULL,
  65. enabled bool NOT NULL DEFAULT true,
  66. description text NOT NULL DEFAULT '',
  67. created_by uuid NULL,
  68. created_at timestamptz NOT NULL,
  69. updated_at timestamptz NOT NULL,
  70. CONSTRAINT redirect_uris_pkey PRIMARY KEY (id),
  71. CONSTRAINT ux_redirect_uris_uri UNIQUE (uri)
  72. );
  73. -- Join between a client and a catalog redirect URI. Composite PK makes an
  74. -- attachment unique; the client FK needs a unique CONSTRAINT (task 8 provides
  75. -- `ux_oauth_clients_client_id`, not merely a unique index).
  76. CREATE TABLE client_redirect_uris (
  77. client_id text NOT NULL,
  78. redirect_uri_id uuid NOT NULL,
  79. attached_at timestamptz NOT NULL,
  80. attached_by uuid NULL,
  81. CONSTRAINT client_redirect_uris_pkey PRIMARY KEY (client_id, redirect_uri_id),
  82. CONSTRAINT fk_client_redirect_uris_client FOREIGN KEY (client_id)
  83. REFERENCES oauth_clients(client_id) ON DELETE CASCADE,
  84. CONSTRAINT fk_client_redirect_uris_uri FOREIGN KEY (redirect_uri_id)
  85. REFERENCES redirect_uris(id) ON DELETE CASCADE
  86. );