auth-provisioning.ts829 lines · main
| 1 | /** |
| 2 | * SQL emitter for the briven auth customer-schema tables. |
| 3 | * |
| 4 | * Called once per project, the first time a customer clicks **Enable Auth** |
| 5 | * in the dashboard. Emits a single transactional batch of statements that: |
| 6 | * 1. ensures the `citext` extension exists in the project's schema |
| 7 | * 2. creates the six `_briven_auth_*` tables |
| 8 | * 3. creates the supporting indexes |
| 9 | * |
| 10 | * Shape matches BUILD_PLAN.md §3 exactly. Drizzle model definitions live |
| 11 | * at `apps/api/src/db/auth-customer-schema.ts` and stay the source of |
| 12 | * truth for application-layer reads; this emitter is the source of truth |
| 13 | * for the DDL that lands in the customer schema. |
| 14 | * |
| 15 | * Why not run drizzle-kit migrate? Customer schemas are dynamic — one |
| 16 | * per project — and drizzle-kit's snapshot/diff workflow is built for a |
| 17 | * fixed schema set. Hand-emitting the DDL keeps the provisioning step |
| 18 | * idempotent (IF NOT EXISTS everywhere) and free of the drizzle-kit TTY |
| 19 | * blocker tracked in road-to-ga.md §2.9. |
| 20 | * |
| 21 | * Idempotency: every statement is `IF NOT EXISTS`. Re-running on an |
| 22 | * already-provisioned schema is a no-op, which matters because the |
| 23 | * provisioning route is idempotent by design (BUILD_PLAN.md §4 admin |
| 24 | * endpoint `POST /v1/projects/:id/auth/enable`). |
| 25 | */ |
| 26 | |
| 27 | /** |
| 28 | * DDL for `_briven_auth_jwks` — Better Auth's jwt-plugin key store (model |
| 29 | * `jwks`: id, publicKey, privateKey, createdAt, expiresAt). The private key |
| 30 | * is encrypted at rest by Better Auth with the instance secret before it is |
| 31 | * written, so the column holds ciphertext JSON, never a raw key. |
| 32 | * |
| 33 | * Exported separately (not just inside `renderAuthProvisioningSql`) because |
| 34 | * the jwks table postdates many live projects: `auth-tenant-pool.ts` re-runs |
| 35 | * this single idempotent statement on tenant-instance boot so existing |
| 36 | * projects self-heal without a re-provisioning step. New tables via |
| 37 | * `CREATE TABLE IF NOT EXISTS` are safe on DoltGres (ALTER ADD COLUMN |
| 38 | * IF NOT EXISTS is not — never retrofit columns onto existing tables). |
| 39 | */ |
| 40 | export const AUTH_JWKS_TABLE_SQL = `CREATE TABLE IF NOT EXISTS "_briven_auth_jwks" ( |
| 41 | id text PRIMARY KEY, |
| 42 | public_key text NOT NULL, |
| 43 | private_key text NOT NULL, |
| 44 | created_at timestamptz NOT NULL DEFAULT now(), |
| 45 | expires_at timestamptz |
| 46 | )` |
| 47 | .replace(/\s+/g, ' ') |
| 48 | .trim(); |
| 49 | |
| 50 | /** |
| 51 | * Tables that older "Enable Auth" projects may be missing after schema growth. |
| 52 | * CREATE TABLE IF NOT EXISTS only — safe on Doltgres (no ALTER IF NOT EXISTS). |
| 53 | * Called on every tenant auth instance boot so magic-link / OTP / passkey |
| 54 | * do not 500 on schema drift (Mavi Pay incident 2026-07-21: |
| 55 | * missing `_briven_auth_email_templates` + `two_factor_enabled` column). |
| 56 | */ |
| 57 | export const AUTH_SELF_HEAL_TABLE_SQL: readonly string[] = [ |
| 58 | AUTH_JWKS_TABLE_SQL, |
| 59 | `CREATE TABLE IF NOT EXISTS "_briven_auth_email_templates" ( |
| 60 | id text PRIMARY KEY, |
| 61 | name text NOT NULL, |
| 62 | subject text NOT NULL, |
| 63 | html text NOT NULL, |
| 64 | text text, |
| 65 | active boolean NOT NULL DEFAULT true, |
| 66 | created_at timestamptz NOT NULL DEFAULT now(), |
| 67 | updated_at timestamptz NOT NULL DEFAULT now() |
| 68 | )`, |
| 69 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_email_templates_name_uniq" |
| 70 | ON "_briven_auth_email_templates" (name)`, |
| 71 | `CREATE TABLE IF NOT EXISTS "_briven_auth_two_factors" ( |
| 72 | id text PRIMARY KEY, |
| 73 | secret text NOT NULL, |
| 74 | backup_codes text NOT NULL, |
| 75 | user_id text NOT NULL REFERENCES "_briven_auth_users"(id) ON DELETE CASCADE, |
| 76 | verified boolean NOT NULL DEFAULT true, |
| 77 | created_at timestamptz NOT NULL DEFAULT now(), |
| 78 | updated_at timestamptz NOT NULL DEFAULT now() |
| 79 | )`, |
| 80 | `CREATE INDEX IF NOT EXISTS "_briven_auth_two_factors_user_idx" |
| 81 | ON "_briven_auth_two_factors" (user_id)`, |
| 82 | `CREATE TABLE IF NOT EXISTS "_briven_auth_passkeys" ( |
| 83 | id text PRIMARY KEY, |
| 84 | name text, |
| 85 | public_key text NOT NULL, |
| 86 | user_id text NOT NULL REFERENCES "_briven_auth_users"(id) ON DELETE CASCADE, |
| 87 | credential_id text NOT NULL, |
| 88 | counter bigint NOT NULL DEFAULT 0, |
| 89 | device_type text NOT NULL DEFAULT 'unknown', |
| 90 | backed_up boolean NOT NULL DEFAULT false, |
| 91 | transports text, |
| 92 | aaguid text, |
| 93 | created_at timestamptz NOT NULL DEFAULT now(), |
| 94 | updated_at timestamptz NOT NULL DEFAULT now() |
| 95 | )`, |
| 96 | `CREATE INDEX IF NOT EXISTS "_briven_auth_passkeys_user_idx" |
| 97 | ON "_briven_auth_passkeys" (user_id)`, |
| 98 | ].map((stmt) => stmt.replace(/\s+/g, ' ').trim()); |
| 99 | |
| 100 | /** Columns Better Auth passkey plugin requires that older tables may lack. */ |
| 101 | const PASSKEY_COLUMN_HEALS: readonly { column: string; sql: string }[] = [ |
| 102 | { |
| 103 | column: 'device_type', |
| 104 | sql: `ALTER TABLE "_briven_auth_passkeys" ADD COLUMN device_type text NOT NULL DEFAULT 'unknown'`, |
| 105 | }, |
| 106 | { |
| 107 | column: 'backed_up', |
| 108 | sql: `ALTER TABLE "_briven_auth_passkeys" ADD COLUMN backed_up boolean NOT NULL DEFAULT false`, |
| 109 | }, |
| 110 | { |
| 111 | column: 'transports', |
| 112 | sql: `ALTER TABLE "_briven_auth_passkeys" ADD COLUMN transports text`, |
| 113 | }, |
| 114 | { |
| 115 | column: 'aaguid', |
| 116 | sql: `ALTER TABLE "_briven_auth_passkeys" ADD COLUMN aaguid text`, |
| 117 | }, |
| 118 | ]; |
| 119 | |
| 120 | /** |
| 121 | * Minimal pg-like client for self-heal (pool or client with `.query`). |
| 122 | */ |
| 123 | export interface AuthSchemaQueryClient { |
| 124 | query(sql: string, params?: unknown[]): Promise<unknown>; |
| 125 | } |
| 126 | |
| 127 | /** |
| 128 | * Ensure critical auth tables + the `two_factor_enabled` user column exist. |
| 129 | * Doltgres does not support `ADD COLUMN IF NOT EXISTS`, so we probe |
| 130 | * information_schema then issue a bare ADD COLUMN when missing. |
| 131 | * |
| 132 | * Failures are logged by the caller — individual statements continue so one |
| 133 | * bad DDL does not block the rest. |
| 134 | */ |
| 135 | export async function ensureTenantAuthSchema( |
| 136 | client: AuthSchemaQueryClient, |
| 137 | ): Promise<{ tablesOk: number; columnAdded: boolean }> { |
| 138 | let tablesOk = 0; |
| 139 | for (const sql of AUTH_SELF_HEAL_TABLE_SQL) { |
| 140 | try { |
| 141 | await client.query(sql); |
| 142 | tablesOk += 1; |
| 143 | } catch { |
| 144 | // continue — table may already exist under a partial shape |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | let columnAdded = false; |
| 149 | try { |
| 150 | const probe = (await client.query( |
| 151 | `SELECT 1 AS ok FROM information_schema.columns |
| 152 | WHERE table_schema = 'public' |
| 153 | AND table_name = '_briven_auth_users' |
| 154 | AND column_name = 'two_factor_enabled' |
| 155 | LIMIT 1`, |
| 156 | )) as { rows?: Array<{ ok: number }> }; |
| 157 | const hasCol = Array.isArray(probe?.rows) && probe.rows.length > 0; |
| 158 | if (!hasCol) { |
| 159 | await client.query( |
| 160 | `ALTER TABLE "_briven_auth_users" |
| 161 | ADD COLUMN two_factor_enabled boolean NOT NULL DEFAULT false`, |
| 162 | ); |
| 163 | columnAdded = true; |
| 164 | } |
| 165 | } catch { |
| 166 | // users table missing entirely (auth not provisioned) — skip |
| 167 | } |
| 168 | |
| 169 | // Passkey plugin fields (deviceType / backedUp / transports / aaguid) |
| 170 | for (const heal of PASSKEY_COLUMN_HEALS) { |
| 171 | try { |
| 172 | const probe = (await client.query( |
| 173 | `SELECT 1 AS ok FROM information_schema.columns |
| 174 | WHERE table_schema = 'public' |
| 175 | AND table_name = '_briven_auth_passkeys' |
| 176 | AND column_name = $1 |
| 177 | LIMIT 1`, |
| 178 | [heal.column], |
| 179 | )) as { rows?: Array<{ ok: number }> }; |
| 180 | const hasCol = Array.isArray(probe?.rows) && probe.rows.length > 0; |
| 181 | if (!hasCol) { |
| 182 | await client.query(heal.sql); |
| 183 | columnAdded = true; |
| 184 | } |
| 185 | } catch { |
| 186 | // passkeys table missing — CREATE TABLE above should have created it |
| 187 | } |
| 188 | } |
| 189 | |
| 190 | return { tablesOk, columnAdded }; |
| 191 | } |
| 192 | |
| 193 | /** |
| 194 | * Emit the full DDL batch for a project's auth tables. Caller wraps it in |
| 195 | * `runInProjectDatabase(projectId, tx)` — the connection is bound to the |
| 196 | * project's own DoltGres database (`proj_<id>`, public schema), so no |
| 197 | * search_path is needed. Tables are shaped to Better Auth's schema (S2.1b). |
| 198 | */ |
| 199 | export function renderAuthProvisioningSql(): string[] { |
| 200 | return [ |
| 201 | // _briven_auth_users — Better-Auth user shape (sprint S2.1b). |
| 202 | // DoltGres has no `citext`/`CREATE EXTENSION` and no expression indexes |
| 203 | // (`(lower(email))` fails with "two different columns with the same name"). |
| 204 | // Email is stored `text` + UNIQUE(email); app layer normalizes to lowercase |
| 205 | // on write (see auth-security normalizeEmail / Better Auth paths). |
| 206 | // `email_verified` is BOOLEAN — what Better Auth reads/writes. |
| 207 | `CREATE TABLE IF NOT EXISTS "_briven_auth_users" ( |
| 208 | id text PRIMARY KEY, |
| 209 | name text, |
| 210 | email text NOT NULL, |
| 211 | email_verified boolean NOT NULL DEFAULT false, |
| 212 | image text, |
| 213 | two_factor_enabled boolean NOT NULL DEFAULT false, |
| 214 | created_at timestamptz NOT NULL DEFAULT now(), |
| 215 | updated_at timestamptz NOT NULL DEFAULT now() |
| 216 | )`, |
| 217 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_users_email_uniq" |
| 218 | ON "_briven_auth_users" (email)`, |
| 219 | |
| 220 | // _briven_auth_sessions — Better-Auth session shape. `ip_address` exists for |
| 221 | // compatibility but IP tracking is disabled (privacy), so it stays null. |
| 222 | `CREATE TABLE IF NOT EXISTS "_briven_auth_sessions" ( |
| 223 | id text PRIMARY KEY, |
| 224 | user_id text NOT NULL REFERENCES "_briven_auth_users"(id) ON DELETE CASCADE, |
| 225 | token text NOT NULL, |
| 226 | expires_at timestamptz NOT NULL, |
| 227 | ip_address text, |
| 228 | user_agent text, |
| 229 | created_at timestamptz NOT NULL DEFAULT now(), |
| 230 | updated_at timestamptz NOT NULL DEFAULT now() |
| 231 | )`, |
| 232 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_sessions_token_uniq" |
| 233 | ON "_briven_auth_sessions" (token)`, |
| 234 | `CREATE INDEX IF NOT EXISTS "_briven_auth_sessions_user_idx" |
| 235 | ON "_briven_auth_sessions" (user_id)`, |
| 236 | `CREATE INDEX IF NOT EXISTS "_briven_auth_sessions_expires_idx" |
| 237 | ON "_briven_auth_sessions" (expires_at)`, |
| 238 | |
| 239 | // _briven_auth_accounts — Better-Auth account shape. The email/password |
| 240 | // "credential" account stores the password hash in `password`. OAuth token |
| 241 | // columns are present but unused in v1 (no social providers wired). |
| 242 | `CREATE TABLE IF NOT EXISTS "_briven_auth_accounts" ( |
| 243 | id text PRIMARY KEY, |
| 244 | user_id text NOT NULL REFERENCES "_briven_auth_users"(id) ON DELETE CASCADE, |
| 245 | account_id text NOT NULL, |
| 246 | provider_id text NOT NULL, |
| 247 | access_token text, |
| 248 | refresh_token text, |
| 249 | id_token text, |
| 250 | access_token_expires_at timestamptz, |
| 251 | refresh_token_expires_at timestamptz, |
| 252 | scope text, |
| 253 | password text, |
| 254 | created_at timestamptz NOT NULL DEFAULT now(), |
| 255 | updated_at timestamptz NOT NULL DEFAULT now() |
| 256 | )`, |
| 257 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_accounts_provider_pair_uniq" |
| 258 | ON "_briven_auth_accounts" (provider_id, account_id)`, |
| 259 | `CREATE INDEX IF NOT EXISTS "_briven_auth_accounts_user_idx" |
| 260 | ON "_briven_auth_accounts" (user_id)`, |
| 261 | |
| 262 | // _briven_auth_verification_tokens — Better-Auth verification shape. |
| 263 | // Better Auth creates + consumes these rows directly ({identifier,value}). |
| 264 | `CREATE TABLE IF NOT EXISTS "_briven_auth_verification_tokens" ( |
| 265 | id text PRIMARY KEY, |
| 266 | identifier text NOT NULL, |
| 267 | value text NOT NULL, |
| 268 | expires_at timestamptz NOT NULL, |
| 269 | created_at timestamptz NOT NULL DEFAULT now(), |
| 270 | updated_at timestamptz NOT NULL DEFAULT now() |
| 271 | )`, |
| 272 | `CREATE INDEX IF NOT EXISTS "_briven_auth_verif_identifier_idx" |
| 273 | ON "_briven_auth_verification_tokens" (identifier)`, |
| 274 | `CREATE INDEX IF NOT EXISTS "_briven_auth_verif_expires_idx" |
| 275 | ON "_briven_auth_verification_tokens" (expires_at)`, |
| 276 | |
| 277 | // _briven_auth_audit_log |
| 278 | `CREATE TABLE IF NOT EXISTS "_briven_auth_audit_log" ( |
| 279 | id text PRIMARY KEY, |
| 280 | user_id text REFERENCES "_briven_auth_users"(id) ON DELETE SET NULL, |
| 281 | action text NOT NULL, |
| 282 | ip_address_hash text, |
| 283 | user_agent text, |
| 284 | metadata jsonb NOT NULL DEFAULT '{}'::jsonb, |
| 285 | occurred_at timestamptz NOT NULL DEFAULT now() |
| 286 | )`, |
| 287 | `CREATE INDEX IF NOT EXISTS "_briven_auth_audit_user_occurred_idx" |
| 288 | ON "_briven_auth_audit_log" (user_id, occurred_at DESC)`, |
| 289 | `CREATE INDEX IF NOT EXISTS "_briven_auth_audit_action_occurred_idx" |
| 290 | ON "_briven_auth_audit_log" (action, occurred_at DESC)`, |
| 291 | |
| 292 | // _briven_auth_jwks — jwt-plugin signing keys (shared constant above so |
| 293 | // the tenant-pool boot ensure and this batch never drift). |
| 294 | AUTH_JWKS_TABLE_SQL, |
| 295 | |
| 296 | // ─── Organizations (Phase 2 — Clerk-competitor) ───────────────────────── |
| 297 | `CREATE TABLE IF NOT EXISTS "_briven_auth_orgs" ( |
| 298 | id text PRIMARY KEY, |
| 299 | name text NOT NULL, |
| 300 | slug text NOT NULL, |
| 301 | logo text, |
| 302 | metadata jsonb NOT NULL DEFAULT '{}'::jsonb, |
| 303 | created_at timestamptz NOT NULL DEFAULT now(), |
| 304 | updated_at timestamptz NOT NULL DEFAULT now() |
| 305 | )`, |
| 306 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_orgs_slug_uniq" |
| 307 | ON "_briven_auth_orgs" (slug)`, |
| 308 | |
| 309 | `CREATE TABLE IF NOT EXISTS "_briven_auth_org_members" ( |
| 310 | id text PRIMARY KEY, |
| 311 | org_id text NOT NULL REFERENCES "_briven_auth_orgs"(id) ON DELETE CASCADE, |
| 312 | user_id text NOT NULL REFERENCES "_briven_auth_users"(id) ON DELETE CASCADE, |
| 313 | role text NOT NULL DEFAULT 'member', |
| 314 | created_at timestamptz NOT NULL DEFAULT now(), |
| 315 | updated_at timestamptz NOT NULL DEFAULT now() |
| 316 | )`, |
| 317 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_org_members_pair_uniq" |
| 318 | ON "_briven_auth_org_members" (org_id, user_id)`, |
| 319 | `CREATE INDEX IF NOT EXISTS "_briven_auth_org_members_user_idx" |
| 320 | ON "_briven_auth_org_members" (user_id)`, |
| 321 | |
| 322 | `CREATE TABLE IF NOT EXISTS "_briven_auth_org_invites" ( |
| 323 | id text PRIMARY KEY, |
| 324 | org_id text NOT NULL REFERENCES "_briven_auth_orgs"(id) ON DELETE CASCADE, |
| 325 | email text NOT NULL, |
| 326 | role text NOT NULL DEFAULT 'member', |
| 327 | token text NOT NULL, |
| 328 | expires_at timestamptz NOT NULL, |
| 329 | invited_by text REFERENCES "_briven_auth_users"(id) ON DELETE SET NULL, |
| 330 | accepted_at timestamptz, |
| 331 | created_at timestamptz NOT NULL DEFAULT now(), |
| 332 | updated_at timestamptz NOT NULL DEFAULT now() |
| 333 | )`, |
| 334 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_org_invites_token_uniq" |
| 335 | ON "_briven_auth_org_invites" (token)`, |
| 336 | `CREATE INDEX IF NOT EXISTS "_briven_auth_org_invites_org_idx" |
| 337 | ON "_briven_auth_org_invites" (org_id)`, |
| 338 | |
| 339 | // ─── Two-Factor (Phase 3) ─────────────────────────────────────────────── |
| 340 | `CREATE TABLE IF NOT EXISTS "_briven_auth_two_factors" ( |
| 341 | id text PRIMARY KEY, |
| 342 | secret text NOT NULL, |
| 343 | backup_codes text NOT NULL, |
| 344 | user_id text NOT NULL REFERENCES "_briven_auth_users"(id) ON DELETE CASCADE, |
| 345 | verified boolean NOT NULL DEFAULT true, |
| 346 | created_at timestamptz NOT NULL DEFAULT now(), |
| 347 | updated_at timestamptz NOT NULL DEFAULT now() |
| 348 | )`, |
| 349 | `CREATE INDEX IF NOT EXISTS "_briven_auth_two_factors_user_idx" |
| 350 | ON "_briven_auth_two_factors" (user_id)`, |
| 351 | |
| 352 | // ─── Passkeys (Phase 3) ───────────────────────────────────────────────── |
| 353 | `CREATE TABLE IF NOT EXISTS "_briven_auth_passkeys" ( |
| 354 | id text PRIMARY KEY, |
| 355 | name text, |
| 356 | public_key text NOT NULL, |
| 357 | user_id text NOT NULL REFERENCES "_briven_auth_users"(id) ON DELETE CASCADE, |
| 358 | credential_id text NOT NULL, |
| 359 | counter bigint NOT NULL DEFAULT 0, |
| 360 | device_type text NOT NULL DEFAULT 'unknown', |
| 361 | backed_up boolean NOT NULL DEFAULT false, |
| 362 | transports text, |
| 363 | aaguid text, |
| 364 | created_at timestamptz NOT NULL DEFAULT now(), |
| 365 | updated_at timestamptz NOT NULL DEFAULT now() |
| 366 | )`, |
| 367 | `CREATE INDEX IF NOT EXISTS "_briven_auth_passkeys_user_idx" |
| 368 | ON "_briven_auth_passkeys" (user_id)`, |
| 369 | |
| 370 | // ─── User Security (Phase 1 — suspensions, bans) ──────────────────────── |
| 371 | `CREATE TABLE IF NOT EXISTS "_briven_auth_user_security" ( |
| 372 | id text PRIMARY KEY, |
| 373 | user_id text NOT NULL REFERENCES "_briven_auth_users"(id) ON DELETE CASCADE, |
| 374 | suspended_at timestamptz, |
| 375 | suspended_reason text, |
| 376 | banned_at timestamptz, |
| 377 | banned_reason text, |
| 378 | ban_expires_at timestamptz, |
| 379 | created_at timestamptz NOT NULL DEFAULT now(), |
| 380 | updated_at timestamptz NOT NULL DEFAULT now() |
| 381 | )`, |
| 382 | `CREATE INDEX IF NOT EXISTS "_briven_auth_user_security_user_idx" |
| 383 | ON "_briven_auth_user_security" (user_id)`, |
| 384 | |
| 385 | // ─── Waitlist (Phase 1 — waitlist mode) ───────────────────────────────── |
| 386 | `CREATE TABLE IF NOT EXISTS "_briven_auth_waitlist" ( |
| 387 | id text PRIMARY KEY, |
| 388 | email text NOT NULL, |
| 389 | name text, |
| 390 | status text NOT NULL DEFAULT 'pending', |
| 391 | approved_at timestamptz, |
| 392 | approved_by text, |
| 393 | rejected_at timestamptz, |
| 394 | rejected_reason text, |
| 395 | created_at timestamptz NOT NULL DEFAULT now(), |
| 396 | updated_at timestamptz NOT NULL DEFAULT now() |
| 397 | )`, |
| 398 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_waitlist_email_idx" |
| 399 | ON "_briven_auth_waitlist" (email)`, |
| 400 | `CREATE INDEX IF NOT EXISTS "_briven_auth_waitlist_status_idx" |
| 401 | ON "_briven_auth_waitlist" (status)`, |
| 402 | |
| 403 | // ─── Session Activity (Phase 2 — inactivity timeout) ──────────────────── |
| 404 | `CREATE TABLE IF NOT EXISTS "_briven_auth_session_activity" ( |
| 405 | id text PRIMARY KEY, |
| 406 | session_id text NOT NULL REFERENCES "_briven_auth_sessions"(id) ON DELETE CASCADE, |
| 407 | last_active_at timestamptz NOT NULL DEFAULT now(), |
| 408 | created_at timestamptz NOT NULL DEFAULT now(), |
| 409 | updated_at timestamptz NOT NULL DEFAULT now() |
| 410 | )`, |
| 411 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_session_activity_session_idx" |
| 412 | ON "_briven_auth_session_activity" (session_id)`, |
| 413 | |
| 414 | // ─── Device Tracking (Gap Fix #6) ─────────────────────────────────────── |
| 415 | `CREATE TABLE IF NOT EXISTS "_briven_auth_devices" ( |
| 416 | id text PRIMARY KEY, |
| 417 | user_id text NOT NULL REFERENCES "_briven_auth_users"(id) ON DELETE CASCADE, |
| 418 | fingerprint text NOT NULL, |
| 419 | user_agent text, |
| 420 | created_at timestamptz NOT NULL DEFAULT now(), |
| 421 | updated_at timestamptz NOT NULL DEFAULT now() |
| 422 | )`, |
| 423 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_devices_user_fingerprint_uniq" |
| 424 | ON "_briven_auth_devices" (user_id, fingerprint)`, |
| 425 | `CREATE INDEX IF NOT EXISTS "_briven_auth_devices_user_idx" |
| 426 | ON "_briven_auth_devices" (user_id)`, |
| 427 | |
| 428 | // ─── User Metadata (Phase 3) ──────────────────────────────────────────── |
| 429 | `CREATE TABLE IF NOT EXISTS "_briven_auth_user_metadata" ( |
| 430 | id text PRIMARY KEY, |
| 431 | user_id text NOT NULL REFERENCES "_briven_auth_users"(id) ON DELETE CASCADE, |
| 432 | public_metadata jsonb NOT NULL DEFAULT '{}'::jsonb, |
| 433 | private_metadata jsonb NOT NULL DEFAULT '{}'::jsonb, |
| 434 | created_at timestamptz NOT NULL DEFAULT now(), |
| 435 | updated_at timestamptz NOT NULL DEFAULT now() |
| 436 | )`, |
| 437 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_user_metadata_user_idx" |
| 438 | ON "_briven_auth_user_metadata" (user_id)`, |
| 439 | |
| 440 | // ─── User Emails (Phase 3 — multiple emails per user) ─────────────────── |
| 441 | // `"primary"` must be quoted — unquoted PRIMARY is reserved SQL, and |
| 442 | // DoltGres errors with: at or near "boolean": syntax error (2026-07-20). |
| 443 | `CREATE TABLE IF NOT EXISTS "_briven_auth_user_emails" ( |
| 444 | id text PRIMARY KEY, |
| 445 | user_id text NOT NULL REFERENCES "_briven_auth_users"(id) ON DELETE CASCADE, |
| 446 | email text NOT NULL, |
| 447 | verified boolean NOT NULL DEFAULT false, |
| 448 | "primary" boolean NOT NULL DEFAULT false, |
| 449 | created_at timestamptz NOT NULL DEFAULT now(), |
| 450 | updated_at timestamptz NOT NULL DEFAULT now() |
| 451 | )`, |
| 452 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_user_emails_user_email_uniq" |
| 453 | ON "_briven_auth_user_emails" (user_id, email)`, |
| 454 | |
| 455 | // ─── Sign-in Tokens (Phase 3 — single-use programmatic session creation) |
| 456 | `CREATE TABLE IF NOT EXISTS "_briven_auth_signin_tokens" ( |
| 457 | id text PRIMARY KEY, |
| 458 | user_id text NOT NULL REFERENCES "_briven_auth_users"(id) ON DELETE CASCADE, |
| 459 | token_hash text NOT NULL, |
| 460 | expires_at timestamptz NOT NULL, |
| 461 | used_at timestamptz, |
| 462 | created_at timestamptz NOT NULL DEFAULT now(), |
| 463 | updated_at timestamptz NOT NULL DEFAULT now() |
| 464 | )`, |
| 465 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_signin_tokens_hash_uniq" |
| 466 | ON "_briven_auth_signin_tokens" (token_hash)`, |
| 467 | `CREATE INDEX IF NOT EXISTS "_briven_auth_signin_tokens_user_idx" |
| 468 | ON "_briven_auth_signin_tokens" (user_id)`, |
| 469 | `CREATE INDEX IF NOT EXISTS "_briven_auth_signin_tokens_expires_idx" |
| 470 | ON "_briven_auth_signin_tokens" (expires_at)`, |
| 471 | |
| 472 | // ─── Organization Roles (Phase 4 — custom roles & permissions) |
| 473 | `CREATE TABLE IF NOT EXISTS "_briven_auth_org_roles" ( |
| 474 | id text PRIMARY KEY, |
| 475 | org_id text NOT NULL REFERENCES "_briven_auth_orgs"(id) ON DELETE CASCADE, |
| 476 | name text NOT NULL, |
| 477 | permissions jsonb NOT NULL DEFAULT '[]'::jsonb, |
| 478 | is_system boolean NOT NULL DEFAULT false, |
| 479 | created_at timestamptz NOT NULL DEFAULT now(), |
| 480 | updated_at timestamptz NOT NULL DEFAULT now() |
| 481 | )`, |
| 482 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_org_roles_org_name_uniq" |
| 483 | ON "_briven_auth_org_roles" (org_id, name)`, |
| 484 | |
| 485 | // ─── Organization Domains (Phase 4 — domain verification + auto-join) |
| 486 | `CREATE TABLE IF NOT EXISTS "_briven_auth_org_domains" ( |
| 487 | id text PRIMARY KEY, |
| 488 | org_id text NOT NULL REFERENCES "_briven_auth_orgs"(id) ON DELETE CASCADE, |
| 489 | domain text NOT NULL, |
| 490 | verification_token text NOT NULL, |
| 491 | verified_at timestamptz, |
| 492 | auto_join_enabled boolean NOT NULL DEFAULT false, |
| 493 | created_at timestamptz NOT NULL DEFAULT now(), |
| 494 | updated_at timestamptz NOT NULL DEFAULT now() |
| 495 | )`, |
| 496 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_org_domains_org_domain_uniq" |
| 497 | ON "_briven_auth_org_domains" (org_id, domain)`, |
| 498 | |
| 499 | // ─── Organization Membership Requests (Phase 4 — request to join) |
| 500 | `CREATE TABLE IF NOT EXISTS "_briven_auth_org_membership_requests" ( |
| 501 | id text PRIMARY KEY, |
| 502 | org_id text NOT NULL REFERENCES "_briven_auth_orgs"(id) ON DELETE CASCADE, |
| 503 | user_id text NOT NULL REFERENCES "_briven_auth_users"(id) ON DELETE CASCADE, |
| 504 | status text NOT NULL DEFAULT 'pending', |
| 505 | message text, |
| 506 | requested_at timestamptz NOT NULL DEFAULT now(), |
| 507 | resolved_at timestamptz, |
| 508 | resolved_by text REFERENCES "_briven_auth_users"(id) ON DELETE SET NULL, |
| 509 | created_at timestamptz NOT NULL DEFAULT now(), |
| 510 | updated_at timestamptz NOT NULL DEFAULT now() |
| 511 | )`, |
| 512 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_org_membership_requests_org_user_uniq" |
| 513 | ON "_briven_auth_org_membership_requests" (org_id, user_id)`, |
| 514 | `CREATE INDEX IF NOT EXISTS "_briven_auth_org_membership_requests_org_status_idx" |
| 515 | ON "_briven_auth_org_membership_requests" (org_id, status)`, |
| 516 | |
| 517 | // ─── Session Active Organization (Phase 4 — org switch without re-auth) |
| 518 | `CREATE TABLE IF NOT EXISTS "_briven_auth_session_orgs" ( |
| 519 | id text PRIMARY KEY, |
| 520 | session_id text NOT NULL REFERENCES "_briven_auth_sessions"(id) ON DELETE CASCADE, |
| 521 | org_id text NOT NULL REFERENCES "_briven_auth_orgs"(id) ON DELETE CASCADE, |
| 522 | created_at timestamptz NOT NULL DEFAULT now(), |
| 523 | updated_at timestamptz NOT NULL DEFAULT now() |
| 524 | )`, |
| 525 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_session_orgs_session_uniq" |
| 526 | ON "_briven_auth_session_orgs" (session_id)`, |
| 527 | |
| 528 | // ─── Enterprise SSO Connections (Phase 5 — SAML + OIDC) |
| 529 | `CREATE TABLE IF NOT EXISTS "_briven_auth_sso_connections" ( |
| 530 | id text PRIMARY KEY, |
| 531 | name text NOT NULL, |
| 532 | provider_type text NOT NULL, |
| 533 | config jsonb NOT NULL DEFAULT '{}'::jsonb, |
| 534 | domains jsonb NOT NULL DEFAULT '[]'::jsonb, |
| 535 | jit_enabled boolean NOT NULL DEFAULT true, |
| 536 | deactivated_at timestamptz, |
| 537 | created_at timestamptz NOT NULL DEFAULT now(), |
| 538 | updated_at timestamptz NOT NULL DEFAULT now() |
| 539 | )`, |
| 540 | `CREATE INDEX IF NOT EXISTS "_briven_auth_sso_connections_name_idx" |
| 541 | ON "_briven_auth_sso_connections" (name)`, |
| 542 | |
| 543 | // ─── SSO Session Tracking (Phase 5 — deprovisioning) |
| 544 | `CREATE TABLE IF NOT EXISTS "_briven_auth_sso_sessions" ( |
| 545 | id text PRIMARY KEY, |
| 546 | session_id text NOT NULL REFERENCES "_briven_auth_sessions"(id) ON DELETE CASCADE, |
| 547 | connection_id text NOT NULL REFERENCES "_briven_auth_sso_connections"(id) ON DELETE CASCADE, |
| 548 | created_at timestamptz NOT NULL DEFAULT now(), |
| 549 | updated_at timestamptz NOT NULL DEFAULT now() |
| 550 | )`, |
| 551 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_sso_sessions_session_uniq" |
| 552 | ON "_briven_auth_sso_sessions" (session_id)`, |
| 553 | `CREATE INDEX IF NOT EXISTS "_briven_auth_sso_sessions_conn_idx" |
| 554 | ON "_briven_auth_sso_sessions" (connection_id)`, |
| 555 | |
| 556 | // ─── Impersonation Sessions (Phase 6.2 — support impersonation audit) |
| 557 | `CREATE TABLE IF NOT EXISTS "_briven_auth_impersonation_sessions" ( |
| 558 | id text PRIMARY KEY, |
| 559 | session_id text NOT NULL REFERENCES "_briven_auth_sessions"(id) ON DELETE CASCADE, |
| 560 | impersonated_by text NOT NULL, |
| 561 | target_user_id text NOT NULL, |
| 562 | stopped_at timestamptz, |
| 563 | created_at timestamptz NOT NULL DEFAULT now(), |
| 564 | updated_at timestamptz NOT NULL DEFAULT now() |
| 565 | )`, |
| 566 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_impersonation_sessions_session_uniq" |
| 567 | ON "_briven_auth_impersonation_sessions" (session_id)`, |
| 568 | |
| 569 | // ─── Application Logs (Phase 6.3 — structured operational logs) |
| 570 | `CREATE TABLE IF NOT EXISTS "_briven_auth_app_logs" ( |
| 571 | id text PRIMARY KEY, |
| 572 | level text NOT NULL, |
| 573 | action text NOT NULL, |
| 574 | message text NOT NULL, |
| 575 | metadata jsonb NOT NULL DEFAULT '{}'::jsonb, |
| 576 | created_at timestamptz NOT NULL DEFAULT now() |
| 577 | )`, |
| 578 | `CREATE INDEX IF NOT EXISTS "_briven_auth_app_logs_level_created_idx" |
| 579 | ON "_briven_auth_app_logs" (level, created_at DESC)`, |
| 580 | `CREATE INDEX IF NOT EXISTS "_briven_auth_app_logs_action_created_idx" |
| 581 | ON "_briven_auth_app_logs" (action, created_at DESC)`, |
| 582 | |
| 583 | // ─── Compliance Metadata (Phase 6.6 — SOC 2 / HIPAA / GDPR) |
| 584 | `CREATE TABLE IF NOT EXISTS "_briven_auth_compliance" ( |
| 585 | id text PRIMARY KEY, |
| 586 | soc2_controls_url text, |
| 587 | hipaa_baa_signed_at timestamptz, |
| 588 | hipaa_baa_signed_by text, |
| 589 | gdpr_dpa_signed_at timestamptz, |
| 590 | gdpr_dpa_signed_by text, |
| 591 | encryption_at_rest_enabled boolean NOT NULL DEFAULT true, |
| 592 | created_at timestamptz NOT NULL DEFAULT now(), |
| 593 | updated_at timestamptz NOT NULL DEFAULT now() |
| 594 | )`, |
| 595 | |
| 596 | // ─── JWT Templates (Phase 7.1 — named claim sets for custom tokens) |
| 597 | `CREATE TABLE IF NOT EXISTS "_briven_auth_jwt_templates" ( |
| 598 | id text PRIMARY KEY, |
| 599 | name text NOT NULL, |
| 600 | claims jsonb NOT NULL DEFAULT '{}'::jsonb, |
| 601 | created_at timestamptz NOT NULL DEFAULT now(), |
| 602 | updated_at timestamptz NOT NULL DEFAULT now() |
| 603 | )`, |
| 604 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_jwt_templates_name_uniq" |
| 605 | ON "_briven_auth_jwt_templates" (name)`, |
| 606 | |
| 607 | // ─── Custom JWT Signing Keys (Phase 7.1 — independent from Better Auth jwks) |
| 608 | `CREATE TABLE IF NOT EXISTS "_briven_auth_custom_jwks" ( |
| 609 | id text PRIMARY KEY, |
| 610 | public_key text NOT NULL, |
| 611 | private_key text NOT NULL, |
| 612 | created_at timestamptz NOT NULL DEFAULT now(), |
| 613 | expires_at timestamptz |
| 614 | )`, |
| 615 | |
| 616 | // ─── User Usernames (Phase 7.3 — username-based authentication) |
| 617 | `CREATE TABLE IF NOT EXISTS "_briven_auth_user_usernames" ( |
| 618 | id text PRIMARY KEY, |
| 619 | user_id text NOT NULL REFERENCES "_briven_auth_users"(id) ON DELETE CASCADE, |
| 620 | username text NOT NULL, |
| 621 | created_at timestamptz NOT NULL DEFAULT now(), |
| 622 | updated_at timestamptz NOT NULL DEFAULT now() |
| 623 | )`, |
| 624 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_user_usernames_username_uniq" |
| 625 | ON "_briven_auth_user_usernames" (username)`, |
| 626 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_user_usernames_user_idx" |
| 627 | ON "_briven_auth_user_usernames" (user_id)`, |
| 628 | |
| 629 | // ─── Testing Tokens (Phase 7.4 — E2E test bypass tokens) |
| 630 | `CREATE TABLE IF NOT EXISTS "_briven_auth_test_tokens" ( |
| 631 | id text PRIMARY KEY, |
| 632 | user_id text NOT NULL REFERENCES "_briven_auth_users"(id) ON DELETE CASCADE, |
| 633 | token_hash text NOT NULL, |
| 634 | name text, |
| 635 | expires_at timestamptz NOT NULL, |
| 636 | created_at timestamptz NOT NULL DEFAULT now(), |
| 637 | updated_at timestamptz NOT NULL DEFAULT now() |
| 638 | )`, |
| 639 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_test_tokens_hash_uniq" |
| 640 | ON "_briven_auth_test_tokens" (token_hash)`, |
| 641 | `CREATE INDEX IF NOT EXISTS "_briven_auth_test_tokens_user_idx" |
| 642 | ON "_briven_auth_test_tokens" (user_id)`, |
| 643 | |
| 644 | // ─── Email Templates (Phase 7.5 — per-tenant transactional email overrides) |
| 645 | `CREATE TABLE IF NOT EXISTS "_briven_auth_email_templates" ( |
| 646 | id text PRIMARY KEY, |
| 647 | name text NOT NULL, |
| 648 | subject text NOT NULL, |
| 649 | html text NOT NULL, |
| 650 | text text, |
| 651 | active boolean NOT NULL DEFAULT true, |
| 652 | created_at timestamptz NOT NULL DEFAULT now(), |
| 653 | updated_at timestamptz NOT NULL DEFAULT now() |
| 654 | )`, |
| 655 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_email_templates_name_uniq" |
| 656 | ON "_briven_auth_email_templates" (name)`, |
| 657 | |
| 658 | // ─── OIDC State Store (Gap Fix #3 — OIDC enterprise authorization flow) |
| 659 | `CREATE TABLE IF NOT EXISTS "_briven_auth_oidc_states" ( |
| 660 | id text PRIMARY KEY, |
| 661 | state text NOT NULL, |
| 662 | nonce text NOT NULL, |
| 663 | connection_id text NOT NULL, |
| 664 | expires_at timestamptz NOT NULL, |
| 665 | created_at timestamptz NOT NULL DEFAULT now() |
| 666 | )`, |
| 667 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_oidc_states_state_uniq" |
| 668 | ON "_briven_auth_oidc_states" (state)`, |
| 669 | |
| 670 | // ─── Password Policy (Gap Fix #13 — password complexity & expiration) |
| 671 | `CREATE TABLE IF NOT EXISTS "_briven_auth_password_policy" ( |
| 672 | id text PRIMARY KEY, |
| 673 | min_length bigint NOT NULL DEFAULT 8, |
| 674 | require_uppercase boolean NOT NULL DEFAULT false, |
| 675 | require_lowercase boolean NOT NULL DEFAULT false, |
| 676 | require_number boolean NOT NULL DEFAULT false, |
| 677 | require_special boolean NOT NULL DEFAULT false, |
| 678 | max_age_days bigint, |
| 679 | prevent_reuse bigint NOT NULL DEFAULT 0, |
| 680 | created_at timestamptz NOT NULL DEFAULT now(), |
| 681 | updated_at timestamptz NOT NULL DEFAULT now() |
| 682 | )`, |
| 683 | |
| 684 | // ─── Password History (Gap Fix #13 — prevent password reuse) |
| 685 | `CREATE TABLE IF NOT EXISTS "_briven_auth_password_history" ( |
| 686 | id text PRIMARY KEY, |
| 687 | user_id text NOT NULL REFERENCES "_briven_auth_users"(id) ON DELETE CASCADE, |
| 688 | password_hash text NOT NULL, |
| 689 | created_at timestamptz NOT NULL DEFAULT now() |
| 690 | )`, |
| 691 | `CREATE INDEX IF NOT EXISTS "_briven_auth_password_history_user_idx" |
| 692 | ON "_briven_auth_password_history" (user_id)`, |
| 693 | |
| 694 | // ─── Force password reset (Sprint S3 — admin-flagged next sign-in) ───── |
| 695 | `CREATE TABLE IF NOT EXISTS "_briven_auth_password_force_reset" ( |
| 696 | user_id text PRIMARY KEY REFERENCES "_briven_auth_users"(id) ON DELETE CASCADE, |
| 697 | reason text, |
| 698 | forced_at timestamptz NOT NULL DEFAULT now() |
| 699 | )`, |
| 700 | |
| 701 | // ─── SCIM 2.0 (Phase 9 — enterprise directory provisioning) ──────────── |
| 702 | ...AUTH_SCIM_DDL_SQL, |
| 703 | |
| 704 | ].map((stmt) => stmt.replace(/\s+/g, ' ').trim()); |
| 705 | } |
| 706 | |
| 707 | /** |
| 708 | * SCIM tables — also exported so existing tenants can self-heal without a |
| 709 | * full re-Enable Auth (same pattern as AUTH_JWKS_TABLE_SQL). |
| 710 | */ |
| 711 | export const AUTH_SCIM_DDL_SQL: readonly string[] = [ |
| 712 | `CREATE TABLE IF NOT EXISTS "_briven_auth_scim_tokens" ( |
| 713 | id text PRIMARY KEY, |
| 714 | name text NOT NULL, |
| 715 | hash text NOT NULL, |
| 716 | prefix text NOT NULL, |
| 717 | suffix text NOT NULL, |
| 718 | last_used_at timestamptz, |
| 719 | created_at timestamptz NOT NULL DEFAULT now(), |
| 720 | revoked_at timestamptz |
| 721 | )`, |
| 722 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_scim_tokens_hash_uniq" |
| 723 | ON "_briven_auth_scim_tokens" (hash)`, |
| 724 | `CREATE TABLE IF NOT EXISTS "_briven_auth_scim_users" ( |
| 725 | id text PRIMARY KEY, |
| 726 | user_id text NOT NULL REFERENCES "_briven_auth_users"(id) ON DELETE CASCADE, |
| 727 | external_id text, |
| 728 | user_name text NOT NULL, |
| 729 | active boolean NOT NULL DEFAULT true, |
| 730 | display_name text, |
| 731 | raw jsonb NOT NULL DEFAULT '{}'::jsonb, |
| 732 | created_at timestamptz NOT NULL DEFAULT now(), |
| 733 | updated_at timestamptz NOT NULL DEFAULT now() |
| 734 | )`, |
| 735 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_scim_users_user_uniq" |
| 736 | ON "_briven_auth_scim_users" (user_id)`, |
| 737 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_scim_users_username_uniq" |
| 738 | ON "_briven_auth_scim_users" (user_name)`, |
| 739 | `CREATE INDEX IF NOT EXISTS "_briven_auth_scim_users_external_idx" |
| 740 | ON "_briven_auth_scim_users" (external_id)`, |
| 741 | `CREATE TABLE IF NOT EXISTS "_briven_auth_scim_groups" ( |
| 742 | id text PRIMARY KEY, |
| 743 | display_name text NOT NULL, |
| 744 | external_id text, |
| 745 | active boolean NOT NULL DEFAULT true, |
| 746 | raw jsonb NOT NULL DEFAULT '{}'::jsonb, |
| 747 | created_at timestamptz NOT NULL DEFAULT now(), |
| 748 | updated_at timestamptz NOT NULL DEFAULT now() |
| 749 | )`, |
| 750 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_scim_groups_name_uniq" |
| 751 | ON "_briven_auth_scim_groups" (display_name)`, |
| 752 | `CREATE TABLE IF NOT EXISTS "_briven_auth_scim_group_members" ( |
| 753 | group_id text NOT NULL REFERENCES "_briven_auth_scim_groups"(id) ON DELETE CASCADE, |
| 754 | member_id text NOT NULL, |
| 755 | created_at timestamptz NOT NULL DEFAULT now(), |
| 756 | PRIMARY KEY (group_id, member_id) |
| 757 | )`, |
| 758 | // SCIM group displayName → Briven org + role (Phase 9.2) |
| 759 | `CREATE TABLE IF NOT EXISTS "_briven_auth_scim_role_maps" ( |
| 760 | id text PRIMARY KEY, |
| 761 | display_name text NOT NULL, |
| 762 | org_id text NOT NULL, |
| 763 | role text NOT NULL DEFAULT 'member', |
| 764 | created_at timestamptz NOT NULL DEFAULT now(), |
| 765 | updated_at timestamptz NOT NULL DEFAULT now() |
| 766 | )`, |
| 767 | `CREATE UNIQUE INDEX IF NOT EXISTS "_briven_auth_scim_role_maps_name_uniq" |
| 768 | ON "_briven_auth_scim_role_maps" (display_name)`, |
| 769 | // OIDC PKCE verifier storage (never ALTER oidc_states — new table) |
| 770 | `CREATE TABLE IF NOT EXISTS "_briven_auth_oidc_pkce" ( |
| 771 | state text PRIMARY KEY, |
| 772 | code_verifier text NOT NULL, |
| 773 | redirect_to text, |
| 774 | expires_at timestamptz NOT NULL, |
| 775 | created_at timestamptz NOT NULL DEFAULT now() |
| 776 | )`, |
| 777 | ].map((stmt) => stmt.replace(/\s+/g, ' ').trim()); |
| 778 | |
| 779 | /** |
| 780 | * Names of every table this emitter creates. Useful for the "is auth |
| 781 | * provisioned?" probe + for the `disable` endpoint's drop-without-data |
| 782 | * path (which intentionally does NOT drop — disable just flips the |
| 783 | * meta flag). |
| 784 | */ |
| 785 | export const AUTH_TABLES = [ |
| 786 | '_briven_auth_users', |
| 787 | '_briven_auth_sessions', |
| 788 | '_briven_auth_accounts', |
| 789 | '_briven_auth_verification_tokens', |
| 790 | '_briven_auth_audit_log', |
| 791 | '_briven_auth_jwks', |
| 792 | '_briven_auth_orgs', |
| 793 | '_briven_auth_org_members', |
| 794 | '_briven_auth_org_invites', |
| 795 | '_briven_auth_two_factors', |
| 796 | '_briven_auth_passkeys', |
| 797 | '_briven_auth_user_security', |
| 798 | '_briven_auth_waitlist', |
| 799 | '_briven_auth_session_activity', |
| 800 | '_briven_auth_devices', |
| 801 | '_briven_auth_user_metadata', |
| 802 | '_briven_auth_user_emails', |
| 803 | '_briven_auth_signin_tokens', |
| 804 | '_briven_auth_org_roles', |
| 805 | '_briven_auth_org_domains', |
| 806 | '_briven_auth_org_membership_requests', |
| 807 | '_briven_auth_session_orgs', |
| 808 | '_briven_auth_sso_connections', |
| 809 | '_briven_auth_sso_sessions', |
| 810 | '_briven_auth_impersonation_sessions', |
| 811 | '_briven_auth_app_logs', |
| 812 | '_briven_auth_compliance', |
| 813 | '_briven_auth_jwt_templates', |
| 814 | '_briven_auth_custom_jwks', |
| 815 | '_briven_auth_user_usernames', |
| 816 | '_briven_auth_test_tokens', |
| 817 | '_briven_auth_email_templates', |
| 818 | '_briven_auth_oidc_states', |
| 819 | '_briven_auth_password_policy', |
| 820 | '_briven_auth_password_history', |
| 821 | '_briven_auth_password_force_reset', |
| 822 | '_briven_auth_scim_tokens', |
| 823 | '_briven_auth_scim_users', |
| 824 | '_briven_auth_scim_groups', |
| 825 | '_briven_auth_scim_group_members', |
| 826 | '_briven_auth_scim_role_maps', |
| 827 | '_briven_auth_oidc_pkce', |
| 828 | ] as const; |
| 829 | export type AuthTableName = (typeof AUTH_TABLES)[number]; |