@opengeni/db 3.4.0-canary.2 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/canonical-human-identities.d.ts +20 -0
  2. package/dist/canonical-human-identities.js +7 -3
  3. package/dist/{chunk-ZWRIPCTR.js → chunk-2NT476HD.js} +2 -2
  4. package/dist/{chunk-PYMY3AJW.js → chunk-744DFC65.js} +202 -7
  5. package/dist/chunk-744DFC65.js.map +1 -0
  6. package/dist/{chunk-O6FTELKH.js → chunk-CDMMSUWJ.js} +3 -3
  7. package/dist/chunk-G3732M3T.js +299 -0
  8. package/dist/chunk-G3732M3T.js.map +1 -0
  9. package/dist/{chunk-TRBOWUG3.js → chunk-IW6O6G7R.js} +2 -2
  10. package/dist/{chunk-HY3F5Q4E.js → chunk-Q5YUGQVK.js} +3 -3
  11. package/dist/{chunk-V3TRUXFJ.js → chunk-UGDVSQ6U.js} +72 -19
  12. package/dist/chunk-UGDVSQ6U.js.map +1 -0
  13. package/dist/{chunk-C2AFP7SB.js → chunk-UJ2EW3QI.js} +6664 -6051
  14. package/dist/chunk-UJ2EW3QI.js.map +1 -0
  15. package/dist/{chunk-H7MXYG7R.js → chunk-WW7AJ5IC.js} +2 -2
  16. package/dist/editable-artifact-durable-export.js +2 -2
  17. package/dist/editable-artifacts.js +3 -3
  18. package/dist/index.d.ts +44 -6
  19. package/dist/index.js +2914 -1535
  20. package/dist/index.js.map +1 -1
  21. package/dist/managed-auth-session-set-schema.d.ts +1399 -0
  22. package/dist/managed-auth-session-sets.d.ts +145 -0
  23. package/dist/managed-auth-session-sets.js +50 -0
  24. package/dist/managed-auth-session-sets.js.map +1 -0
  25. package/dist/organization-recovery-schema.d.ts +2144 -0
  26. package/dist/organization-recovery.d.ts +186 -0
  27. package/dist/provision-roles.js +1 -1
  28. package/dist/runtime-posture.d.ts +7 -4
  29. package/dist/schema.d.ts +9 -6
  30. package/dist/schema.js +43 -1
  31. package/dist/session-background-commands.js +3 -3
  32. package/dist/session-control.d.ts +5 -3
  33. package/dist/session-queue-commands.d.ts +2 -0
  34. package/dist/session-tenancy.js +3 -3
  35. package/dist/video-generation.js +3 -3
  36. package/dist/work-claims-schema.d.ts +975 -0
  37. package/dist/work-claims.d.ts +83 -0
  38. package/drizzle/0362_managed_auth_session_sets.sql +2347 -0
  39. package/drizzle/0363_organization_recovery_custody.sql +2418 -0
  40. package/drizzle/0364_workspace_learning_policy_snapshot_lock_order.sql +221 -0
  41. package/drizzle/0365_permission_scoped_work_claims.sql +1306 -0
  42. package/drizzle/0366_session_discovery_title_search_index.sql +5 -0
  43. package/drizzle/0367_session_discovery_goal_search_index.sql +5 -0
  44. package/drizzle/0368_session_discovery_claim_search_index.sql +6 -0
  45. package/package.json +10 -6
  46. package/src/canonical-human-identities.ts +105 -27
  47. package/src/index.ts +1471 -433
  48. package/src/managed-auth-session-set-schema.ts +176 -0
  49. package/src/managed-auth-session-sets.ts +430 -0
  50. package/src/organization-recovery-schema.ts +277 -0
  51. package/src/organization-recovery.ts +424 -0
  52. package/src/provision-roles.ts +67 -0
  53. package/src/runtime-posture.ts +144 -6
  54. package/src/schema.ts +3 -0
  55. package/src/session-control.ts +255 -16
  56. package/src/session-queue-commands.ts +10 -1
  57. package/src/session-tool-call-settlement.ts +6 -1
  58. package/src/work-claims-schema.ts +148 -0
  59. package/src/work-claims.ts +200 -0
  60. package/dist/chunk-C2AFP7SB.js.map +0 -1
  61. package/dist/chunk-PYMY3AJW.js.map +0 -1
  62. package/dist/chunk-V3TRUXFJ.js.map +0 -1
  63. /package/dist/{chunk-ZWRIPCTR.js.map → chunk-2NT476HD.js.map} +0 -0
  64. /package/dist/{chunk-O6FTELKH.js.map → chunk-CDMMSUWJ.js.map} +0 -0
  65. /package/dist/{chunk-TRBOWUG3.js.map → chunk-IW6O6G7R.js.map} +0 -0
  66. /package/dist/{chunk-HY3F5Q4E.js.map → chunk-Q5YUGQVK.js.map} +0 -0
  67. /package/dist/{chunk-H7MXYG7R.js.map → chunk-WW7AJ5IC.js.map} +0 -0
@@ -0,0 +1,2347 @@
1
+ -- deployment-mode: rolling
2
+ -- Hash-only browser session-set authority and provider-neutral login slots.
3
+ -- Broker activation is deployment-owned and remains off in repository defaults.
4
+
5
+ SET LOCAL lock_timeout = '5s';
6
+ SET LOCAL statement_timeout = '10min';
7
+
8
+ ALTER TABLE "auth_sessions"
9
+ ADD COLUMN "login_binding_id" uuid,
10
+ ADD COLUMN "login_binding_revision" bigint;
11
+
12
+ -- The migration principal is a non-superuser table owner. Open an owner-only
13
+ -- visibility window so the exact-binding preflight cannot pass vacuously under
14
+ -- FORCE RLS; ordinary runtime roles remain policy-bound throughout.
15
+ ALTER TABLE "canonical_human_login_bindings" NO FORCE ROW LEVEL SECURITY;
16
+ DO $managed_auth_session_binding_backfill$
17
+ DECLARE
18
+ previous_canonical_marker text := pg_catalog.current_setting(
19
+ 'opengeni.canonical_human_identity_lifecycle', true
20
+ );
21
+ BEGIN
22
+ PERFORM pg_catalog.set_config(
23
+ 'opengeni.canonical_human_identity_lifecycle', 'active', true
24
+ );
25
+ IF EXISTS (
26
+ SELECT 1
27
+ FROM auth_sessions auth_session
28
+ WHERE (
29
+ SELECT count(*)
30
+ FROM auth_identities auth_identity
31
+ INNER JOIN canonical_human_login_bindings binding
32
+ ON binding.identity_id = auth_session.identity_id
33
+ AND binding.provider_id = lower(auth_identity.provider_id)
34
+ AND binding.provider_account_id = auth_identity.account_id
35
+ AND binding.status IN ('active', 'recovery_pending')
36
+ WHERE auth_identity.user_id = auth_session.user_id
37
+ AND lower(auth_identity.provider_id) = 'credential'
38
+ ) <> 1
39
+ ) THEN
40
+ RAISE EXCEPTION 'every live auth session must prove exactly one active login binding'
41
+ USING ERRCODE = '55000';
42
+ END IF;
43
+
44
+ WITH exact_bindings AS (
45
+ SELECT auth_session.id AS auth_session_id,
46
+ min(binding.id::text)::uuid AS login_binding_id,
47
+ min(binding.revision) AS login_binding_revision
48
+ FROM auth_sessions auth_session
49
+ INNER JOIN auth_identities auth_identity
50
+ ON auth_identity.user_id = auth_session.user_id
51
+ AND lower(auth_identity.provider_id) = 'credential'
52
+ INNER JOIN canonical_human_login_bindings binding
53
+ ON binding.identity_id = auth_session.identity_id
54
+ AND binding.provider_id = lower(auth_identity.provider_id)
55
+ AND binding.provider_account_id = auth_identity.account_id
56
+ AND binding.status IN ('active', 'recovery_pending')
57
+ GROUP BY auth_session.id
58
+ HAVING count(*) = 1
59
+ )
60
+ UPDATE auth_sessions auth_session
61
+ SET login_binding_id = exact_binding.id,
62
+ login_binding_revision = exact_binding.revision
63
+ FROM (
64
+ SELECT auth_session_id, login_binding_id AS id, login_binding_revision AS revision
65
+ FROM exact_bindings
66
+ ) exact_binding
67
+ WHERE auth_session.id = exact_binding.auth_session_id;
68
+ PERFORM pg_catalog.set_config(
69
+ 'opengeni.canonical_human_identity_lifecycle',
70
+ CASE WHEN previous_canonical_marker IS NULL THEN '' ELSE previous_canonical_marker END,
71
+ true
72
+ );
73
+ EXCEPTION WHEN OTHERS THEN
74
+ PERFORM pg_catalog.set_config(
75
+ 'opengeni.canonical_human_identity_lifecycle',
76
+ CASE WHEN previous_canonical_marker IS NULL THEN '' ELSE previous_canonical_marker END,
77
+ true
78
+ );
79
+ RAISE;
80
+ END
81
+ $managed_auth_session_binding_backfill$;
82
+ ALTER TABLE "canonical_human_login_bindings" FORCE ROW LEVEL SECURITY;
83
+
84
+ DO $managed_auth_session_exact_binding_stamp_install$
85
+ DECLARE
86
+ data_schema text := current_schema();
87
+ BEGIN
88
+ EXECUTE format($ddl$
89
+ CREATE OR REPLACE FUNCTION %1$I.get_canonical_human_exact_login_binding(
90
+ p_auth_user_id text,
91
+ p_provider_id text
92
+ ) RETURNS jsonb
93
+ LANGUAGE plpgsql SECURITY DEFINER
94
+ SET search_path = pg_catalog, %1$I, pg_temp
95
+ AS $body$
96
+ DECLARE
97
+ binding_count integer;
98
+ result jsonb;
99
+ previous_canonical_marker text := pg_catalog.current_setting(
100
+ 'opengeni.canonical_human_identity_lifecycle', true
101
+ );
102
+ BEGIN
103
+ IF p_auth_user_id IS NULL OR pg_catalog.btrim(p_auth_user_id) = ''
104
+ OR p_provider_id IS NULL OR pg_catalog.btrim(p_provider_id) = ''
105
+ THEN
106
+ RAISE EXCEPTION 'canonical human exact login binding input is invalid'
107
+ USING ERRCODE = '22023';
108
+ END IF;
109
+ PERFORM pg_catalog.set_config(
110
+ 'opengeni.canonical_human_identity_lifecycle', 'active', true
111
+ );
112
+ SELECT pg_catalog.count(*)::integer,
113
+ CASE WHEN pg_catalog.count(*) = 1 THEN pg_catalog.jsonb_build_object(
114
+ 'id', pg_catalog.min(binding.id::text),
115
+ 'identityId', pg_catalog.min(binding.identity_id::text),
116
+ 'revision', pg_catalog.min(binding.revision)::text,
117
+ 'status', pg_catalog.min(binding.status)
118
+ ) ELSE NULL END
119
+ INTO binding_count, result
120
+ FROM auth_identities auth_identity
121
+ INNER JOIN canonical_human_identity_subjects subject_row
122
+ ON subject_row.auth_user_id = auth_identity.user_id
123
+ AND subject_row.status = 'active'
124
+ INNER JOIN canonical_human_login_bindings binding
125
+ ON binding.identity_id = subject_row.identity_id
126
+ AND binding.provider_id = lower(auth_identity.provider_id)
127
+ AND binding.provider_account_id = auth_identity.account_id
128
+ WHERE auth_identity.user_id = p_auth_user_id
129
+ AND lower(auth_identity.provider_id) = lower(p_provider_id);
130
+ PERFORM pg_catalog.set_config(
131
+ 'opengeni.canonical_human_identity_lifecycle',
132
+ CASE WHEN previous_canonical_marker IS NULL THEN '' ELSE previous_canonical_marker END,
133
+ true
134
+ );
135
+ RETURN CASE WHEN binding_count = 1 THEN result ELSE NULL END;
136
+ EXCEPTION WHEN OTHERS THEN
137
+ PERFORM pg_catalog.set_config(
138
+ 'opengeni.canonical_human_identity_lifecycle',
139
+ CASE WHEN previous_canonical_marker IS NULL THEN '' ELSE previous_canonical_marker END,
140
+ true
141
+ );
142
+ RAISE;
143
+ END;
144
+ $body$;
145
+ $ddl$, data_schema);
146
+
147
+ EXECUTE format($ddl$
148
+ CREATE OR REPLACE FUNCTION %1$I.managed_auth_session_exact_binding_stamp()
149
+ RETURNS trigger
150
+ LANGUAGE plpgsql SECURITY DEFINER
151
+ SET search_path = pg_catalog, %1$I, pg_temp
152
+ AS $body$
153
+ DECLARE
154
+ exact_binding_id uuid;
155
+ exact_binding_revision bigint;
156
+ binding_count integer;
157
+ previous_canonical_marker text := pg_catalog.current_setting(
158
+ 'opengeni.canonical_human_identity_lifecycle', true
159
+ );
160
+ BEGIN
161
+ PERFORM pg_catalog.set_config(
162
+ 'opengeni.canonical_human_identity_lifecycle', 'active', true
163
+ );
164
+ IF NEW.login_binding_id IS NULL THEN
165
+ SELECT min(binding.id::text)::uuid, min(binding.revision), count(*)::integer
166
+ INTO exact_binding_id, exact_binding_revision, binding_count
167
+ FROM auth_identities auth_identity
168
+ INNER JOIN canonical_human_login_bindings binding
169
+ ON binding.identity_id = NEW.identity_id
170
+ AND binding.provider_id = lower(auth_identity.provider_id)
171
+ AND binding.provider_account_id = auth_identity.account_id
172
+ AND binding.status IN ('active', 'recovery_pending')
173
+ WHERE auth_identity.user_id = NEW.user_id
174
+ AND lower(auth_identity.provider_id) = 'credential';
175
+ ELSE
176
+ SELECT min(binding.id::text)::uuid, min(binding.revision), count(*)::integer
177
+ INTO exact_binding_id, exact_binding_revision, binding_count
178
+ FROM auth_identities auth_identity
179
+ INNER JOIN canonical_human_login_bindings binding
180
+ ON binding.id = NEW.login_binding_id
181
+ AND binding.identity_id = NEW.identity_id
182
+ AND binding.provider_id = lower(auth_identity.provider_id)
183
+ AND binding.provider_account_id = auth_identity.account_id
184
+ AND binding.status IN ('active', 'recovery_pending')
185
+ WHERE auth_identity.user_id = NEW.user_id;
186
+ END IF;
187
+ IF binding_count <> 1 OR exact_binding_id IS NULL OR exact_binding_revision IS NULL THEN
188
+ RAISE EXCEPTION 'auth session must prove exactly one login binding'
189
+ USING ERRCODE = '42501';
190
+ END IF;
191
+ IF NEW.login_binding_id IS NOT NULL AND NEW.login_binding_id <> exact_binding_id THEN
192
+ RAISE EXCEPTION 'auth session login binding does not match the authenticated credential'
193
+ USING ERRCODE = '42501';
194
+ END IF;
195
+ IF NEW.login_binding_revision IS NOT NULL
196
+ AND NEW.login_binding_revision <> exact_binding_revision
197
+ THEN
198
+ RAISE EXCEPTION 'auth session login binding revision is stale' USING ERRCODE = '42501';
199
+ END IF;
200
+ NEW.login_binding_id := exact_binding_id;
201
+ NEW.login_binding_revision := exact_binding_revision;
202
+ PERFORM pg_catalog.set_config(
203
+ 'opengeni.canonical_human_identity_lifecycle',
204
+ CASE WHEN previous_canonical_marker IS NULL THEN '' ELSE previous_canonical_marker END,
205
+ true
206
+ );
207
+ RETURN NEW;
208
+ EXCEPTION WHEN OTHERS THEN
209
+ PERFORM pg_catalog.set_config(
210
+ 'opengeni.canonical_human_identity_lifecycle',
211
+ CASE WHEN previous_canonical_marker IS NULL THEN '' ELSE previous_canonical_marker END,
212
+ true
213
+ );
214
+ RAISE;
215
+ END;
216
+ $body$;
217
+ $ddl$, data_schema);
218
+ EXECUTE format(
219
+ 'CREATE TRIGGER managed_auth_session_exact_binding_stamp BEFORE INSERT ON %I.auth_sessions '
220
+ || 'FOR EACH ROW EXECUTE FUNCTION %I.managed_auth_session_exact_binding_stamp()',
221
+ data_schema, data_schema
222
+ );
223
+ EXECUTE format(
224
+ 'REVOKE ALL ON FUNCTION %I.managed_auth_session_exact_binding_stamp() FROM PUBLIC',
225
+ data_schema
226
+ );
227
+ END
228
+ $managed_auth_session_exact_binding_stamp_install$;
229
+
230
+ ALTER TABLE "auth_sessions"
231
+ ALTER COLUMN "login_binding_id" SET NOT NULL,
232
+ ALTER COLUMN "login_binding_revision" SET NOT NULL,
233
+ ADD CONSTRAINT "auth_sessions_login_binding_fk"
234
+ FOREIGN KEY ("login_binding_id") REFERENCES "canonical_human_login_bindings"("id") ON DELETE RESTRICT,
235
+ ADD CONSTRAINT "auth_sessions_login_binding_revision_check" CHECK ("login_binding_revision" > 0);
236
+ CREATE INDEX "auth_sessions_login_binding_revision_idx"
237
+ ON "auth_sessions" ("login_binding_id", "login_binding_revision");
238
+
239
+ CREATE TABLE "managed_auth_browser_installations" (
240
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
241
+ "authority_hash" text NOT NULL UNIQUE,
242
+ "created_at" timestamptz NOT NULL DEFAULT now(),
243
+ "last_seen_at" timestamptz NOT NULL DEFAULT now(),
244
+ "idle_expires_at" timestamptz NOT NULL DEFAULT (now() + interval '30 days'),
245
+ "absolute_expires_at" timestamptz NOT NULL DEFAULT (now() + interval '180 days'),
246
+ "revoked_at" timestamptz,
247
+ CONSTRAINT "managed_auth_browser_installations_hash_check" CHECK (
248
+ "authority_hash" ~ '^[0-9a-f]{64}$'
249
+ ),
250
+ CONSTRAINT "managed_auth_browser_installations_expiry_check" CHECK (
251
+ "idle_expires_at" > "created_at"
252
+ AND "idle_expires_at" <= "absolute_expires_at"
253
+ AND "absolute_expires_at" <= "created_at" + interval '180 days'
254
+ )
255
+ );
256
+
257
+ CREATE TABLE "managed_auth_session_sets" (
258
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
259
+ "installation_id" uuid NOT NULL UNIQUE
260
+ REFERENCES "managed_auth_browser_installations"("id") ON DELETE RESTRICT,
261
+ "authority_hash" text NOT NULL UNIQUE,
262
+ "csrf_hash" text NOT NULL,
263
+ "generation" bigint NOT NULL DEFAULT 1,
264
+ "actor_epoch" bigint NOT NULL DEFAULT 1,
265
+ "selected_slot_id" uuid,
266
+ "state" text NOT NULL DEFAULT 'ready',
267
+ "created_at" timestamptz NOT NULL DEFAULT now(),
268
+ "updated_at" timestamptz NOT NULL DEFAULT now(),
269
+ "idle_expires_at" timestamptz NOT NULL DEFAULT (now() + interval '30 days'),
270
+ "absolute_expires_at" timestamptz NOT NULL DEFAULT (now() + interval '180 days'),
271
+ "revoked_at" timestamptz,
272
+ CONSTRAINT "managed_auth_session_sets_hash_check" CHECK (
273
+ "authority_hash" ~ '^[0-9a-f]{64}$' AND "csrf_hash" ~ '^[0-9a-f]{64}$'
274
+ ),
275
+ CONSTRAINT "managed_auth_session_sets_generation_check" CHECK (
276
+ "generation" > 0 AND "actor_epoch" > 0
277
+ ),
278
+ CONSTRAINT "managed_auth_session_sets_state_check" CHECK (
279
+ "state" IN ('ready', 'actor_change_required')
280
+ ),
281
+ CONSTRAINT "managed_auth_session_sets_expiry_check" CHECK (
282
+ "idle_expires_at" > "created_at"
283
+ AND "idle_expires_at" <= "absolute_expires_at"
284
+ AND "absolute_expires_at" <= "created_at" + interval '180 days'
285
+ )
286
+ );
287
+
288
+ CREATE TABLE "managed_auth_login_slots" (
289
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
290
+ "session_set_id" uuid NOT NULL
291
+ REFERENCES "managed_auth_session_sets"("id") ON DELETE CASCADE,
292
+ "auth_session_id" text REFERENCES "auth_sessions"("id") ON DELETE SET NULL,
293
+ "auth_user_id" text NOT NULL REFERENCES "auth_users"("id") ON DELETE RESTRICT,
294
+ "identity_id" uuid NOT NULL
295
+ REFERENCES "canonical_human_identities"("id") ON DELETE RESTRICT,
296
+ "login_binding_id" uuid NOT NULL
297
+ REFERENCES "canonical_human_login_bindings"("id") ON DELETE RESTRICT,
298
+ "identity_revision" bigint NOT NULL,
299
+ "auth_revision" bigint NOT NULL,
300
+ "login_binding_revision" bigint NOT NULL,
301
+ "display_name" text NOT NULL,
302
+ "verified_email" text NOT NULL,
303
+ "status" text NOT NULL DEFAULT 'active',
304
+ "created_at" timestamptz NOT NULL DEFAULT now(),
305
+ "updated_at" timestamptz NOT NULL DEFAULT now(),
306
+ "revoked_at" timestamptz,
307
+ CONSTRAINT "managed_auth_login_slots_revision_check" CHECK (
308
+ "identity_revision" > 0 AND "auth_revision" > 0 AND "login_binding_revision" > 0
309
+ ),
310
+ CONSTRAINT "managed_auth_login_slots_display_check" CHECK (
311
+ length(btrim("display_name")) BETWEEN 1 AND 256
312
+ AND length(btrim("verified_email")) BETWEEN 3 AND 320
313
+ ),
314
+ CONSTRAINT "managed_auth_login_slots_status_check" CHECK (
315
+ "status" IN ('active', 'reauth_required', 'revoked')
316
+ ),
317
+ CONSTRAINT "managed_auth_login_slots_revocation_check" CHECK (
318
+ ("status" = 'revoked' AND "revoked_at" IS NOT NULL)
319
+ OR ("status" <> 'revoked' AND "revoked_at" IS NULL)
320
+ )
321
+ );
322
+ CREATE UNIQUE INDEX "managed_auth_login_slots_live_binding_idx"
323
+ ON "managed_auth_login_slots" ("session_set_id", "login_binding_id")
324
+ WHERE "status" <> 'revoked';
325
+ CREATE UNIQUE INDEX "managed_auth_login_slots_live_session_idx"
326
+ ON "managed_auth_login_slots" ("auth_session_id")
327
+ WHERE "auth_session_id" IS NOT NULL AND "status" <> 'revoked';
328
+ CREATE INDEX "managed_auth_login_slots_set_status_idx"
329
+ ON "managed_auth_login_slots" ("session_set_id", "status", "created_at", "id");
330
+ ALTER TABLE "managed_auth_login_slots"
331
+ ADD CONSTRAINT "managed_auth_login_slots_id_set_unique" UNIQUE ("id", "session_set_id");
332
+
333
+ ALTER TABLE "managed_auth_session_sets"
334
+ ADD CONSTRAINT "managed_auth_session_sets_selected_slot_fk"
335
+ FOREIGN KEY ("selected_slot_id", "id")
336
+ REFERENCES "managed_auth_login_slots"("id", "session_set_id") ON DELETE RESTRICT;
337
+
338
+ CREATE TABLE "managed_auth_login_return_intents" (
339
+ "id" uuid PRIMARY KEY,
340
+ "session_set_id" uuid NOT NULL
341
+ REFERENCES "managed_auth_session_sets"("id") ON DELETE CASCADE,
342
+ "path" text NOT NULL,
343
+ "expires_at" timestamptz NOT NULL,
344
+ "consumed_at" timestamptz,
345
+ "created_at" timestamptz NOT NULL DEFAULT now(),
346
+ CONSTRAINT "managed_auth_login_return_intents_path_check" CHECK (
347
+ octet_length("path") BETWEEN 1 AND 2048
348
+ AND left("path", 1) = '/'
349
+ AND left("path", 2) <> '//'
350
+ AND position('?' in "path") = 0
351
+ AND position('#' in "path") = 0
352
+ AND position(E'\\' in "path") = 0
353
+ AND "path" !~ '[[:cntrl:]]'
354
+ AND "path" ~* '^/(sessions/[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|workspaces/[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}(/sessions(/[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})?)?)$'
355
+ ),
356
+ CONSTRAINT "managed_auth_login_return_intents_expiry_check" CHECK (
357
+ "expires_at" > "created_at"
358
+ )
359
+ );
360
+
361
+ CREATE TABLE "managed_auth_login_transactions" (
362
+ "id" uuid PRIMARY KEY,
363
+ "session_set_id" uuid NOT NULL
364
+ REFERENCES "managed_auth_session_sets"("id") ON DELETE CASCADE,
365
+ "secret_hash" text NOT NULL,
366
+ "kind" text NOT NULL,
367
+ "target_slot_id" uuid REFERENCES "managed_auth_login_slots"("id") ON DELETE RESTRICT,
368
+ "expected_identity_id" uuid
369
+ REFERENCES "canonical_human_identities"("id") ON DELETE RESTRICT,
370
+ "expected_login_binding_id" uuid
371
+ REFERENCES "canonical_human_login_bindings"("id") ON DELETE RESTRICT,
372
+ "expected_identity_revision" bigint,
373
+ "expected_auth_revision" bigint,
374
+ "expected_login_binding_revision" bigint,
375
+ "expected_generation" bigint NOT NULL,
376
+ "return_intent_id" uuid
377
+ REFERENCES "managed_auth_login_return_intents"("id") ON DELETE RESTRICT,
378
+ "status" text NOT NULL DEFAULT 'pending',
379
+ "expires_at" timestamptz NOT NULL,
380
+ "consumed_at" timestamptz,
381
+ "created_at" timestamptz NOT NULL DEFAULT now(),
382
+ CONSTRAINT "managed_auth_login_transactions_hash_check" CHECK (
383
+ "secret_hash" ~ '^[0-9a-f]{64}$'
384
+ ),
385
+ CONSTRAINT "managed_auth_login_transactions_kind_check" CHECK (
386
+ "kind" IN ('add', 'reauth')
387
+ ),
388
+ CONSTRAINT "managed_auth_login_transactions_status_check" CHECK (
389
+ "status" IN ('pending', 'consumed', 'cancelled')
390
+ ),
391
+ CONSTRAINT "managed_auth_login_transactions_generation_check" CHECK (
392
+ "expected_generation" > 0
393
+ AND ("expected_identity_revision" IS NULL OR "expected_identity_revision" > 0)
394
+ AND ("expected_auth_revision" IS NULL OR "expected_auth_revision" > 0)
395
+ AND ("expected_login_binding_revision" IS NULL OR "expected_login_binding_revision" > 0)
396
+ ),
397
+ CONSTRAINT "managed_auth_login_transactions_target_check" CHECK (
398
+ ("kind" = 'add' AND "target_slot_id" IS NULL
399
+ AND "expected_identity_id" IS NULL AND "expected_login_binding_id" IS NULL
400
+ AND "expected_identity_revision" IS NULL AND "expected_auth_revision" IS NULL
401
+ AND "expected_login_binding_revision" IS NULL)
402
+ OR ("kind" = 'reauth' AND "target_slot_id" IS NOT NULL
403
+ AND "expected_identity_id" IS NOT NULL AND "expected_login_binding_id" IS NOT NULL
404
+ AND "expected_identity_revision" IS NOT NULL AND "expected_auth_revision" IS NOT NULL
405
+ AND "expected_login_binding_revision" IS NOT NULL)
406
+ ),
407
+ CONSTRAINT "managed_auth_login_transactions_expiry_check" CHECK (
408
+ "expires_at" > "created_at"
409
+ )
410
+ );
411
+ CREATE INDEX "managed_auth_login_transactions_set_status_idx"
412
+ ON "managed_auth_login_transactions" ("session_set_id", "status", "expires_at");
413
+
414
+ ALTER TABLE "auth_sessions"
415
+ ADD COLUMN "managed_auth_login_transaction_id" uuid
416
+ REFERENCES "managed_auth_login_transactions"("id") ON DELETE SET NULL;
417
+ CREATE INDEX "auth_sessions_managed_auth_login_transaction_idx"
418
+ ON "auth_sessions" ("managed_auth_login_transaction_id", "created_at")
419
+ WHERE "managed_auth_login_transaction_id" IS NOT NULL;
420
+ CREATE INDEX "auth_sessions_managed_auth_expiry_idx"
421
+ ON "auth_sessions" ("expires_at", "id");
422
+
423
+ CREATE TABLE "managed_auth_session_set_operations" (
424
+ "operation_id" uuid PRIMARY KEY,
425
+ "session_set_id" uuid NOT NULL
426
+ REFERENCES "managed_auth_session_sets"("id") ON DELETE RESTRICT,
427
+ "operation_type" text NOT NULL,
428
+ "request_digest" text NOT NULL,
429
+ "expected_generation" bigint NOT NULL,
430
+ "result_generation" bigint NOT NULL,
431
+ "result_actor_epoch" bigint NOT NULL,
432
+ "target_slot_id" uuid,
433
+ "replacement_slot_id" uuid,
434
+ "outcome" text NOT NULL,
435
+ "result" jsonb NOT NULL,
436
+ "created_at" timestamptz NOT NULL DEFAULT now(),
437
+ CONSTRAINT "managed_auth_session_set_operations_type_check" CHECK (
438
+ "operation_type" IN (
439
+ 'bootstrap', 'begin_add', 'begin_reauth', 'complete_add', 'complete_reauth',
440
+ 'cancel_transaction', 'select', 'logout_one', 'logout_all'
441
+ )
442
+ ),
443
+ CONSTRAINT "managed_auth_session_set_operations_digest_check" CHECK (
444
+ "request_digest" ~ '^[0-9a-f]{64}$'
445
+ ),
446
+ CONSTRAINT "managed_auth_session_set_operations_generation_check" CHECK (
447
+ "expected_generation" > 0 AND "result_generation" > 0 AND "result_actor_epoch" > 0
448
+ ),
449
+ CONSTRAINT "managed_auth_session_set_operations_outcome_check" CHECK (
450
+ "outcome" IN ('applied', 'converged')
451
+ ),
452
+ CONSTRAINT "managed_auth_session_set_operations_result_check" CHECK (
453
+ pg_catalog.jsonb_typeof("result") = 'object'
454
+ AND octet_length("result"::text) <= 16384
455
+ )
456
+ );
457
+ CREATE INDEX "managed_auth_session_set_operations_set_created_idx"
458
+ ON "managed_auth_session_set_operations" ("session_set_id", "created_at", "operation_id");
459
+
460
+ CREATE TABLE "managed_auth_actor_mutation_leases" (
461
+ "session_set_id" uuid NOT NULL
462
+ REFERENCES "managed_auth_session_sets"("id") ON DELETE CASCADE,
463
+ "request_id" uuid NOT NULL,
464
+ "actor_epoch" bigint NOT NULL,
465
+ "acquired_at" timestamptz NOT NULL DEFAULT now(),
466
+ "expires_at" timestamptz NOT NULL,
467
+ PRIMARY KEY ("session_set_id", "request_id"),
468
+ CONSTRAINT "managed_auth_actor_mutation_leases_epoch_check" CHECK ("actor_epoch" > 0),
469
+ CONSTRAINT "managed_auth_actor_mutation_leases_expiry_check" CHECK (
470
+ "expires_at" > "acquired_at"
471
+ AND "expires_at" <= "acquired_at" + interval '15 minutes'
472
+ )
473
+ );
474
+ CREATE INDEX "managed_auth_actor_mutation_leases_expiry_idx"
475
+ ON "managed_auth_actor_mutation_leases" ("expires_at", "session_set_id");
476
+
477
+ CREATE TABLE "managed_auth_login_transaction_rate_limits" (
478
+ "scope_kind" text NOT NULL,
479
+ "scope_hash" text NOT NULL,
480
+ "window_started_at" timestamptz NOT NULL,
481
+ "attempt_count" integer NOT NULL,
482
+ "expires_at" timestamptz NOT NULL,
483
+ PRIMARY KEY ("scope_kind", "scope_hash"),
484
+ CONSTRAINT "managed_auth_login_transaction_rate_limits_scope_check" CHECK (
485
+ "scope_kind" IN ('global', 'client', 'set')
486
+ AND "scope_hash" ~ '^[0-9a-f]{64}$'
487
+ ),
488
+ CONSTRAINT "managed_auth_login_transaction_rate_limits_count_check" CHECK (
489
+ "attempt_count" > 0 AND "expires_at" > "window_started_at"
490
+ )
491
+ );
492
+ CREATE INDEX "managed_auth_login_transaction_rate_limits_expiry_idx"
493
+ ON "managed_auth_login_transaction_rate_limits" ("expires_at", "scope_kind", "scope_hash");
494
+
495
+ ALTER TABLE "managed_auth_browser_installations" ENABLE ROW LEVEL SECURITY;
496
+ ALTER TABLE "managed_auth_browser_installations" FORCE ROW LEVEL SECURITY;
497
+ ALTER TABLE "managed_auth_session_sets" ENABLE ROW LEVEL SECURITY;
498
+ ALTER TABLE "managed_auth_session_sets" FORCE ROW LEVEL SECURITY;
499
+ ALTER TABLE "managed_auth_login_slots" ENABLE ROW LEVEL SECURITY;
500
+ ALTER TABLE "managed_auth_login_slots" FORCE ROW LEVEL SECURITY;
501
+ ALTER TABLE "managed_auth_login_return_intents" ENABLE ROW LEVEL SECURITY;
502
+ ALTER TABLE "managed_auth_login_return_intents" FORCE ROW LEVEL SECURITY;
503
+ ALTER TABLE "managed_auth_login_transactions" ENABLE ROW LEVEL SECURITY;
504
+ ALTER TABLE "managed_auth_login_transactions" FORCE ROW LEVEL SECURITY;
505
+ ALTER TABLE "managed_auth_session_set_operations" ENABLE ROW LEVEL SECURITY;
506
+ ALTER TABLE "managed_auth_session_set_operations" FORCE ROW LEVEL SECURITY;
507
+ ALTER TABLE "managed_auth_actor_mutation_leases" ENABLE ROW LEVEL SECURITY;
508
+ ALTER TABLE "managed_auth_actor_mutation_leases" FORCE ROW LEVEL SECURITY;
509
+ ALTER TABLE "managed_auth_login_transaction_rate_limits" ENABLE ROW LEVEL SECURITY;
510
+ ALTER TABLE "managed_auth_login_transaction_rate_limits" FORCE ROW LEVEL SECURITY;
511
+
512
+ CREATE POLICY managed_auth_session_set_lifecycle ON "managed_auth_session_sets"
513
+ USING (current_setting('opengeni.managed_auth_session_set_lifecycle', true) = 'active')
514
+ WITH CHECK (current_setting('opengeni.managed_auth_session_set_lifecycle', true) = 'active');
515
+ CREATE POLICY managed_auth_session_set_lifecycle ON "managed_auth_browser_installations"
516
+ USING (current_setting('opengeni.managed_auth_session_set_lifecycle', true) = 'active')
517
+ WITH CHECK (current_setting('opengeni.managed_auth_session_set_lifecycle', true) = 'active');
518
+ CREATE POLICY managed_auth_session_set_lifecycle ON "managed_auth_login_slots"
519
+ USING (current_setting('opengeni.managed_auth_session_set_lifecycle', true) = 'active')
520
+ WITH CHECK (current_setting('opengeni.managed_auth_session_set_lifecycle', true) = 'active');
521
+ CREATE POLICY managed_auth_session_set_lifecycle ON "managed_auth_login_return_intents"
522
+ USING (current_setting('opengeni.managed_auth_session_set_lifecycle', true) = 'active')
523
+ WITH CHECK (current_setting('opengeni.managed_auth_session_set_lifecycle', true) = 'active');
524
+ CREATE POLICY managed_auth_session_set_lifecycle ON "managed_auth_login_transactions"
525
+ USING (current_setting('opengeni.managed_auth_session_set_lifecycle', true) = 'active')
526
+ WITH CHECK (current_setting('opengeni.managed_auth_session_set_lifecycle', true) = 'active');
527
+ CREATE POLICY managed_auth_session_set_lifecycle ON "managed_auth_session_set_operations"
528
+ USING (current_setting('opengeni.managed_auth_session_set_lifecycle', true) = 'active')
529
+ WITH CHECK (current_setting('opengeni.managed_auth_session_set_lifecycle', true) = 'active');
530
+ CREATE POLICY managed_auth_session_set_lifecycle ON "managed_auth_actor_mutation_leases"
531
+ USING (current_setting('opengeni.managed_auth_session_set_lifecycle', true) = 'active')
532
+ WITH CHECK (current_setting('opengeni.managed_auth_session_set_lifecycle', true) = 'active');
533
+ CREATE POLICY managed_auth_session_set_lifecycle ON "managed_auth_login_transaction_rate_limits"
534
+ USING (current_setting('opengeni.managed_auth_session_set_lifecycle', true) = 'active')
535
+ WITH CHECK (current_setting('opengeni.managed_auth_session_set_lifecycle', true) = 'active');
536
+
537
+ CREATE OR REPLACE FUNCTION managed_auth_session_set_operations_append_only()
538
+ RETURNS trigger LANGUAGE plpgsql AS $body$
539
+ BEGIN
540
+ IF TG_OP = 'DELETE'
541
+ AND pg_catalog.current_setting('opengeni.managed_auth_session_set_purge', true) = 'active'
542
+ AND NOT EXISTS (
543
+ SELECT 1 FROM managed_auth_login_slots slot
544
+ WHERE slot.session_set_id = OLD.session_set_id
545
+ )
546
+ THEN
547
+ RETURN OLD;
548
+ END IF;
549
+ RAISE EXCEPTION 'managed auth session-set operations are append-only' USING ERRCODE = '42501';
550
+ END
551
+ $body$;
552
+ CREATE TRIGGER managed_auth_session_set_operations_append_only
553
+ BEFORE UPDATE OR DELETE ON "managed_auth_session_set_operations"
554
+ FOR EACH ROW EXECUTE FUNCTION managed_auth_session_set_operations_append_only();
555
+
556
+ DO $managed_auth_session_set_routines$
557
+ DECLARE
558
+ data_schema text := current_schema();
559
+ BEGIN
560
+ EXECUTE format($ddl$
561
+ CREATE OR REPLACE FUNCTION %1$I.managed_auth_session_set_authority_state(
562
+ p_authority_hash text
563
+ ) RETURNS text
564
+ LANGUAGE plpgsql SECURITY DEFINER
565
+ SET search_path = pg_catalog, %1$I, pg_temp
566
+ AS $body$
567
+ DECLARE
568
+ authority_state text;
569
+ previous_marker text := pg_catalog.current_setting(
570
+ 'opengeni.managed_auth_session_set_lifecycle', true
571
+ );
572
+ BEGIN
573
+ IF p_authority_hash IS NULL OR p_authority_hash !~ '^[0-9a-f]{64}$' THEN
574
+ RAISE EXCEPTION 'managed auth session-set authority is invalid' USING ERRCODE = '22023';
575
+ END IF;
576
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', 'active', true);
577
+ SELECT CASE
578
+ WHEN session_set.id IS NULL AND installation.id IS NULL THEN 'absent'
579
+ WHEN session_set.revoked_at IS NULL AND installation.revoked_at IS NULL
580
+ AND session_set.idle_expires_at > pg_catalog.clock_timestamp()
581
+ AND session_set.absolute_expires_at > pg_catalog.clock_timestamp()
582
+ AND installation.idle_expires_at > pg_catalog.clock_timestamp()
583
+ AND installation.absolute_expires_at > pg_catalog.clock_timestamp()
584
+ THEN 'active' ELSE 'retired' END
585
+ INTO authority_state
586
+ FROM (SELECT p_authority_hash AS authority_hash) input
587
+ LEFT JOIN managed_auth_browser_installations installation
588
+ ON installation.authority_hash = input.authority_hash
589
+ LEFT JOIN managed_auth_session_sets session_set
590
+ ON session_set.authority_hash = input.authority_hash;
591
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
592
+ RETURN coalesce(authority_state, 'absent');
593
+ EXCEPTION WHEN OTHERS THEN
594
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
595
+ RAISE;
596
+ END;
597
+ $body$;
598
+ $ddl$, data_schema);
599
+
600
+ EXECUTE format($ddl$
601
+ CREATE OR REPLACE FUNCTION %1$I.managed_auth_session_set_snapshot(
602
+ p_authority_hash text,
603
+ p_mode text DEFAULT 'legacy',
604
+ p_include_internal boolean DEFAULT false,
605
+ p_allow_recovery boolean DEFAULT false,
606
+ p_read_only boolean DEFAULT false
607
+ ) RETURNS jsonb
608
+ LANGUAGE plpgsql SECURITY DEFINER
609
+ SET search_path = pg_catalog, %1$I, pg_temp
610
+ AS $body$
611
+ DECLARE
612
+ set_row managed_auth_session_sets%%ROWTYPE;
613
+ changed_count integer := 0;
614
+ selected_lost boolean := false;
615
+ result jsonb;
616
+ previous_marker text := pg_catalog.current_setting(
617
+ 'opengeni.managed_auth_session_set_lifecycle', true
618
+ );
619
+ previous_canonical_marker text := pg_catalog.current_setting(
620
+ 'opengeni.canonical_human_identity_lifecycle', true
621
+ );
622
+ BEGIN
623
+ IF p_authority_hash IS NULL OR p_authority_hash !~ '^[0-9a-f]{64}$'
624
+ OR p_mode NOT IN ('legacy', 'dual', 'broker')
625
+ THEN
626
+ RAISE EXCEPTION 'managed auth session-set authority is invalid' USING ERRCODE = '22023';
627
+ END IF;
628
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', 'active', true);
629
+ PERFORM pg_catalog.set_config('opengeni.canonical_human_identity_lifecycle', 'active', true);
630
+ SELECT session_set.* INTO set_row
631
+ FROM managed_auth_session_sets session_set
632
+ INNER JOIN managed_auth_browser_installations installation
633
+ ON installation.id = session_set.installation_id
634
+ WHERE session_set.authority_hash = p_authority_hash
635
+ FOR UPDATE OF session_set, installation;
636
+ IF NOT FOUND THEN
637
+ PERFORM pg_catalog.set_config(
638
+ 'opengeni.managed_auth_session_set_lifecycle',
639
+ CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true
640
+ );
641
+ PERFORM pg_catalog.set_config('opengeni.canonical_human_identity_lifecycle', CASE WHEN previous_canonical_marker IS NULL THEN '' ELSE previous_canonical_marker END, true);
642
+ RETURN NULL;
643
+ END IF;
644
+ IF set_row.revoked_at IS NOT NULL OR EXISTS (
645
+ SELECT 1 FROM managed_auth_browser_installations installation
646
+ WHERE installation.id = set_row.installation_id AND installation.revoked_at IS NOT NULL
647
+ ) THEN
648
+ PERFORM pg_catalog.set_config(
649
+ 'opengeni.managed_auth_session_set_lifecycle',
650
+ CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true
651
+ );
652
+ PERFORM pg_catalog.set_config('opengeni.canonical_human_identity_lifecycle', CASE WHEN previous_canonical_marker IS NULL THEN '' ELSE previous_canonical_marker END, true);
653
+ RETURN NULL;
654
+ END IF;
655
+ IF set_row.idle_expires_at <= pg_catalog.clock_timestamp()
656
+ OR set_row.absolute_expires_at <= pg_catalog.clock_timestamp()
657
+ OR EXISTS (
658
+ SELECT 1 FROM managed_auth_browser_installations installation
659
+ WHERE installation.id = set_row.installation_id
660
+ AND (installation.idle_expires_at <= pg_catalog.clock_timestamp()
661
+ OR installation.absolute_expires_at <= pg_catalog.clock_timestamp())
662
+ )
663
+ THEN
664
+ IF p_read_only THEN
665
+ PERFORM pg_catalog.set_config(
666
+ 'opengeni.managed_auth_session_set_lifecycle',
667
+ CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true
668
+ );
669
+ PERFORM pg_catalog.set_config('opengeni.canonical_human_identity_lifecycle', CASE WHEN previous_canonical_marker IS NULL THEN '' ELSE previous_canonical_marker END, true);
670
+ RETURN NULL;
671
+ END IF;
672
+ DELETE FROM managed_auth_actor_mutation_leases
673
+ WHERE session_set_id = set_row.id AND expires_at <= pg_catalog.clock_timestamp();
674
+ IF EXISTS (
675
+ SELECT 1 FROM managed_auth_actor_mutation_leases
676
+ WHERE session_set_id = set_row.id AND actor_epoch = set_row.actor_epoch
677
+ ) THEN
678
+ RAISE EXCEPTION 'managed auth actor has an in-flight mutation' USING ERRCODE = '55P03';
679
+ END IF;
680
+ DELETE FROM auth_sessions auth_session USING managed_auth_login_slots slot
681
+ WHERE slot.session_set_id = set_row.id AND slot.status <> 'revoked'
682
+ AND auth_session.id = slot.auth_session_id;
683
+ UPDATE managed_auth_login_slots SET status = 'revoked', auth_session_id = NULL,
684
+ revoked_at = pg_catalog.clock_timestamp(), updated_at = pg_catalog.clock_timestamp()
685
+ WHERE session_set_id = set_row.id AND status <> 'revoked';
686
+ UPDATE managed_auth_session_sets SET revoked_at = pg_catalog.clock_timestamp(),
687
+ state = 'actor_change_required', updated_at = pg_catalog.clock_timestamp()
688
+ WHERE id = set_row.id;
689
+ UPDATE managed_auth_browser_installations SET revoked_at = pg_catalog.clock_timestamp(),
690
+ last_seen_at = pg_catalog.clock_timestamp()
691
+ WHERE id = set_row.installation_id;
692
+ PERFORM pg_catalog.set_config(
693
+ 'opengeni.managed_auth_session_set_lifecycle',
694
+ CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true
695
+ );
696
+ PERFORM pg_catalog.set_config('opengeni.canonical_human_identity_lifecycle', CASE WHEN previous_canonical_marker IS NULL THEN '' ELSE previous_canonical_marker END, true);
697
+ RETURN NULL;
698
+ END IF;
699
+ IF NOT p_read_only THEN
700
+ UPDATE managed_auth_browser_installations installation SET
701
+ last_seen_at = pg_catalog.clock_timestamp(),
702
+ idle_expires_at = least(pg_catalog.clock_timestamp() + interval '30 days', installation.absolute_expires_at)
703
+ WHERE installation.id = set_row.installation_id;
704
+ UPDATE managed_auth_session_sets session_set SET
705
+ idle_expires_at = least(pg_catalog.clock_timestamp() + interval '30 days', session_set.absolute_expires_at),
706
+ updated_at = pg_catalog.clock_timestamp()
707
+ WHERE session_set.id = set_row.id RETURNING * INTO set_row;
708
+ END IF;
709
+
710
+ SELECT EXISTS (
711
+ SELECT 1
712
+ FROM managed_auth_login_slots slot
713
+ LEFT JOIN auth_sessions auth_session ON auth_session.id = slot.auth_session_id
714
+ LEFT JOIN canonical_human_identities identity_row ON identity_row.id = slot.identity_id
715
+ LEFT JOIN canonical_human_login_bindings binding ON binding.id = slot.login_binding_id
716
+ LEFT JOIN canonical_human_identity_subjects subject_row
717
+ ON subject_row.auth_user_id = slot.auth_user_id
718
+ AND subject_row.identity_id = slot.identity_id
719
+ WHERE slot.id = set_row.selected_slot_id AND slot.status = 'active'
720
+ AND NOT (
721
+ auth_session.id IS NOT NULL
722
+ AND auth_session.user_id = slot.auth_user_id
723
+ AND auth_session.expires_at > pg_catalog.clock_timestamp()
724
+ AND auth_session.identity_id = slot.identity_id
725
+ AND auth_session.identity_revision = slot.identity_revision
726
+ AND auth_session.auth_revision = slot.auth_revision
727
+ AND auth_session.login_binding_id = slot.login_binding_id
728
+ AND auth_session.login_binding_revision = slot.login_binding_revision
729
+ AND identity_row.identity_revision = slot.identity_revision
730
+ AND identity_row.auth_revision = slot.auth_revision
731
+ AND binding.identity_id = slot.identity_id
732
+ AND binding.revision = slot.login_binding_revision
733
+ AND subject_row.status = 'active'
734
+ AND (
735
+ (identity_row.status = 'active' AND binding.status = 'active')
736
+ OR (
737
+ p_allow_recovery
738
+ AND identity_row.status = 'recovery_required'
739
+ AND binding.status = 'recovery_pending'
740
+ )
741
+ )
742
+ )
743
+ ) INTO selected_lost;
744
+
745
+ IF NOT p_read_only AND selected_lost THEN
746
+ DELETE FROM managed_auth_actor_mutation_leases
747
+ WHERE session_set_id = set_row.id AND expires_at <= pg_catalog.clock_timestamp();
748
+ IF EXISTS (
749
+ SELECT 1 FROM managed_auth_actor_mutation_leases
750
+ WHERE session_set_id = set_row.id AND actor_epoch = set_row.actor_epoch
751
+ ) THEN
752
+ RAISE EXCEPTION 'managed auth actor has an in-flight mutation' USING ERRCODE = '55P03';
753
+ END IF;
754
+ END IF;
755
+
756
+ IF NOT p_read_only THEN
757
+ WITH invalid AS (
758
+ SELECT slot.id, slot.auth_session_id, slot.id = set_row.selected_slot_id AS was_selected
759
+ FROM managed_auth_login_slots slot
760
+ LEFT JOIN auth_sessions auth_session ON auth_session.id = slot.auth_session_id
761
+ LEFT JOIN canonical_human_identities identity_row ON identity_row.id = slot.identity_id
762
+ LEFT JOIN canonical_human_login_bindings binding ON binding.id = slot.login_binding_id
763
+ LEFT JOIN canonical_human_identity_subjects subject_row
764
+ ON subject_row.auth_user_id = slot.auth_user_id
765
+ AND subject_row.identity_id = slot.identity_id
766
+ WHERE slot.session_set_id = set_row.id AND slot.status = 'active'
767
+ AND NOT (
768
+ slot.id = set_row.selected_slot_id
769
+ AND identity_row.status = 'recovery_required'
770
+ AND binding.id = slot.login_binding_id
771
+ AND binding.identity_id = slot.identity_id
772
+ AND binding.status = 'recovery_pending'
773
+ AND subject_row.status = 'active'
774
+ )
775
+ AND NOT (
776
+ auth_session.id IS NOT NULL
777
+ AND auth_session.user_id = slot.auth_user_id
778
+ AND auth_session.expires_at > pg_catalog.clock_timestamp()
779
+ AND auth_session.identity_id = slot.identity_id
780
+ AND auth_session.identity_revision = slot.identity_revision
781
+ AND auth_session.auth_revision = slot.auth_revision
782
+ AND auth_session.login_binding_id = slot.login_binding_id
783
+ AND auth_session.login_binding_revision = slot.login_binding_revision
784
+ AND (
785
+ identity_row.status = 'active'
786
+ OR (
787
+ p_allow_recovery
788
+ AND slot.id = set_row.selected_slot_id
789
+ AND identity_row.status = 'recovery_required'
790
+ )
791
+ )
792
+ AND identity_row.identity_revision = slot.identity_revision
793
+ AND identity_row.auth_revision = slot.auth_revision
794
+ AND binding.identity_id = slot.identity_id
795
+ AND (
796
+ binding.status = 'active'
797
+ OR (
798
+ p_allow_recovery
799
+ AND slot.id = set_row.selected_slot_id
800
+ AND identity_row.status = 'recovery_required'
801
+ AND binding.status = 'recovery_pending'
802
+ )
803
+ )
804
+ AND binding.revision = slot.login_binding_revision
805
+ AND subject_row.status = 'active'
806
+ )
807
+ ), stale AS (
808
+ UPDATE managed_auth_login_slots slot
809
+ SET status = 'reauth_required', auth_session_id = NULL,
810
+ updated_at = pg_catalog.clock_timestamp()
811
+ FROM invalid
812
+ WHERE slot.id = invalid.id
813
+ RETURNING invalid.was_selected, invalid.auth_session_id
814
+ ), deleted_sessions AS (
815
+ DELETE FROM auth_sessions auth_session USING stale
816
+ WHERE auth_session.id = stale.auth_session_id
817
+ RETURNING auth_session.id
818
+ )
819
+ SELECT count(*)::integer, coalesce(bool_or(was_selected), false)
820
+ INTO changed_count, selected_lost FROM stale;
821
+
822
+ IF changed_count > 0 THEN
823
+ UPDATE managed_auth_session_sets changed_set
824
+ SET generation = changed_set.generation + 1,
825
+ actor_epoch = changed_set.actor_epoch + CASE WHEN selected_lost THEN 1 ELSE 0 END,
826
+ selected_slot_id = CASE WHEN selected_lost THEN NULL ELSE changed_set.selected_slot_id END,
827
+ state = CASE WHEN selected_lost THEN 'actor_change_required' ELSE changed_set.state END,
828
+ updated_at = pg_catalog.clock_timestamp()
829
+ WHERE changed_set.id = set_row.id RETURNING * INTO set_row;
830
+ END IF;
831
+ END IF;
832
+
833
+ SELECT pg_catalog.jsonb_build_object(
834
+ 'projection', pg_catalog.jsonb_build_object(
835
+ 'mode', p_mode,
836
+ 'generation', set_row.generation::text,
837
+ 'actorEpoch', set_row.actor_epoch::text,
838
+ 'selectedSlotId', CASE
839
+ WHEN selected_lost AND NOT p_allow_recovery THEN NULL
840
+ ELSE set_row.selected_slot_id
841
+ END,
842
+ 'state', CASE WHEN selected_lost THEN 'actor_change_required' ELSE set_row.state END,
843
+ 'slots', coalesce((
844
+ SELECT pg_catalog.jsonb_agg(pg_catalog.jsonb_build_object(
845
+ 'id', slot.id,
846
+ 'displayName', slot.display_name,
847
+ 'verifiedClaim', pg_catalog.jsonb_build_object(
848
+ 'kind', 'email', 'value', slot.verified_email
849
+ ),
850
+ 'state', CASE
851
+ WHEN slot.status = 'active'
852
+ AND auth_session.id IS NOT NULL
853
+ AND auth_session.user_id = slot.auth_user_id
854
+ AND auth_session.expires_at > pg_catalog.clock_timestamp()
855
+ AND auth_session.identity_id = slot.identity_id
856
+ AND auth_session.identity_revision = slot.identity_revision
857
+ AND auth_session.auth_revision = slot.auth_revision
858
+ AND auth_session.login_binding_id = slot.login_binding_id
859
+ AND auth_session.login_binding_revision = slot.login_binding_revision
860
+ AND identity_row.status = 'active'
861
+ AND binding.status = 'active'
862
+ AND identity_row.identity_revision = slot.identity_revision
863
+ AND identity_row.auth_revision = slot.auth_revision
864
+ AND binding.identity_id = slot.identity_id
865
+ AND binding.revision = slot.login_binding_revision
866
+ AND subject_row.status = 'active'
867
+ THEN 'active' ELSE 'reauth_required' END
868
+ ) ORDER BY slot.created_at, slot.id)
869
+ FROM managed_auth_login_slots slot
870
+ LEFT JOIN auth_sessions auth_session ON auth_session.id = slot.auth_session_id
871
+ LEFT JOIN canonical_human_identities identity_row ON identity_row.id = slot.identity_id
872
+ LEFT JOIN canonical_human_login_bindings binding ON binding.id = slot.login_binding_id
873
+ LEFT JOIN canonical_human_identity_subjects subject_row
874
+ ON subject_row.auth_user_id = slot.auth_user_id
875
+ AND subject_row.identity_id = slot.identity_id
876
+ WHERE slot.session_set_id = set_row.id AND slot.status <> 'revoked'
877
+ ), '[]'::jsonb)
878
+ ),
879
+ 'selected', CASE WHEN p_include_internal THEN (
880
+ SELECT pg_catalog.jsonb_build_object(
881
+ 'slotId', slot.id, 'authSessionId', auth_session.id,
882
+ 'authUserId', slot.auth_user_id, 'token', auth_session.token,
883
+ 'email', auth_user.email, 'name', auth_user.name,
884
+ 'emailVerified', auth_user.email_verified
885
+ )
886
+ FROM managed_auth_login_slots slot
887
+ INNER JOIN auth_sessions auth_session ON auth_session.id = slot.auth_session_id
888
+ INNER JOIN auth_users auth_user ON auth_user.id = slot.auth_user_id
889
+ INNER JOIN canonical_human_identities identity_row ON identity_row.id = slot.identity_id
890
+ INNER JOIN canonical_human_login_bindings binding ON binding.id = slot.login_binding_id
891
+ INNER JOIN canonical_human_identity_subjects subject_row
892
+ ON subject_row.auth_user_id = slot.auth_user_id
893
+ AND subject_row.identity_id = slot.identity_id
894
+ WHERE slot.id = set_row.selected_slot_id AND slot.status = 'active'
895
+ AND auth_session.user_id = slot.auth_user_id
896
+ AND auth_session.expires_at > pg_catalog.clock_timestamp()
897
+ AND auth_session.identity_id = slot.identity_id
898
+ AND auth_session.identity_revision = slot.identity_revision
899
+ AND auth_session.auth_revision = slot.auth_revision
900
+ AND auth_session.login_binding_id = slot.login_binding_id
901
+ AND auth_session.login_binding_revision = slot.login_binding_revision
902
+ AND identity_row.identity_revision = slot.identity_revision
903
+ AND identity_row.auth_revision = slot.auth_revision
904
+ AND binding.identity_id = slot.identity_id
905
+ AND binding.revision = slot.login_binding_revision
906
+ AND subject_row.status = 'active'
907
+ AND (
908
+ (identity_row.status = 'active' AND binding.status = 'active')
909
+ OR (
910
+ p_allow_recovery
911
+ AND identity_row.status = 'recovery_required'
912
+ AND binding.status = 'recovery_pending'
913
+ )
914
+ )
915
+ ) ELSE NULL END,
916
+ 'internalSlots', CASE WHEN p_include_internal THEN coalesce((
917
+ SELECT pg_catalog.jsonb_agg(pg_catalog.jsonb_build_object(
918
+ 'slotId', slot.id, 'authSessionId', auth_session.id,
919
+ 'authUserId', slot.auth_user_id, 'token', auth_session.token,
920
+ 'email', auth_user.email, 'name', auth_user.name,
921
+ 'emailVerified', auth_user.email_verified
922
+ ) ORDER BY slot.created_at, slot.id)
923
+ FROM managed_auth_login_slots slot
924
+ INNER JOIN auth_sessions auth_session ON auth_session.id = slot.auth_session_id
925
+ INNER JOIN auth_users auth_user ON auth_user.id = slot.auth_user_id
926
+ INNER JOIN canonical_human_identities identity_row ON identity_row.id = slot.identity_id
927
+ INNER JOIN canonical_human_login_bindings binding ON binding.id = slot.login_binding_id
928
+ INNER JOIN canonical_human_identity_subjects subject_row
929
+ ON subject_row.auth_user_id = slot.auth_user_id
930
+ AND subject_row.identity_id = slot.identity_id
931
+ WHERE slot.session_set_id = set_row.id AND slot.status = 'active'
932
+ AND auth_session.user_id = slot.auth_user_id
933
+ AND auth_session.expires_at > pg_catalog.clock_timestamp()
934
+ AND auth_session.identity_id = slot.identity_id
935
+ AND auth_session.identity_revision = slot.identity_revision
936
+ AND auth_session.auth_revision = slot.auth_revision
937
+ AND auth_session.login_binding_id = slot.login_binding_id
938
+ AND auth_session.login_binding_revision = slot.login_binding_revision
939
+ AND identity_row.identity_revision = slot.identity_revision
940
+ AND identity_row.auth_revision = slot.auth_revision
941
+ AND binding.identity_id = slot.identity_id
942
+ AND binding.revision = slot.login_binding_revision
943
+ AND subject_row.status = 'active'
944
+ AND (
945
+ (identity_row.status = 'active' AND binding.status = 'active')
946
+ OR (
947
+ p_allow_recovery
948
+ AND slot.id = set_row.selected_slot_id
949
+ AND identity_row.status = 'recovery_required'
950
+ AND binding.status = 'recovery_pending'
951
+ )
952
+ )
953
+ ), '[]'::jsonb) ELSE '[]'::jsonb END
954
+ ) INTO result;
955
+ PERFORM pg_catalog.set_config(
956
+ 'opengeni.managed_auth_session_set_lifecycle',
957
+ CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true
958
+ );
959
+ PERFORM pg_catalog.set_config('opengeni.canonical_human_identity_lifecycle', CASE WHEN previous_canonical_marker IS NULL THEN '' ELSE previous_canonical_marker END, true);
960
+ RETURN result;
961
+ EXCEPTION WHEN OTHERS THEN
962
+ PERFORM pg_catalog.set_config(
963
+ 'opengeni.managed_auth_session_set_lifecycle',
964
+ CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true
965
+ );
966
+ PERFORM pg_catalog.set_config('opengeni.canonical_human_identity_lifecycle', CASE WHEN previous_canonical_marker IS NULL THEN '' ELSE previous_canonical_marker END, true);
967
+ RAISE;
968
+ END;
969
+ $body$;
970
+ $ddl$, data_schema);
971
+
972
+ EXECUTE format($ddl$
973
+ CREATE OR REPLACE FUNCTION %1$I.managed_auth_session_set_bootstrap(
974
+ p_authority_hash text, p_csrf_hash text, p_auth_session_id text,
975
+ p_mode text, p_operation_id uuid, p_request_digest text,
976
+ p_expected_generation bigint, p_expected_actor_epoch bigint
977
+ ) RETURNS jsonb
978
+ LANGUAGE plpgsql SECURITY DEFINER
979
+ SET search_path = pg_catalog, %1$I, pg_temp
980
+ AS $body$
981
+ DECLARE
982
+ set_row managed_auth_session_sets%%ROWTYPE;
983
+ session_row record;
984
+ slot_id uuid;
985
+ installation_uuid uuid;
986
+ result jsonb;
987
+ prior_operation managed_auth_session_set_operations%%ROWTYPE;
988
+ previous_marker text := pg_catalog.current_setting(
989
+ 'opengeni.managed_auth_session_set_lifecycle', true
990
+ );
991
+ previous_canonical_marker text := pg_catalog.current_setting(
992
+ 'opengeni.canonical_human_identity_lifecycle', true
993
+ );
994
+ BEGIN
995
+ IF p_authority_hash !~ '^[0-9a-f]{64}$' OR p_csrf_hash !~ '^[0-9a-f]{64}$'
996
+ OR p_request_digest !~ '^[0-9a-f]{64}$' OR p_operation_id IS NULL
997
+ OR p_expected_generation < 1 OR p_expected_actor_epoch < 1
998
+ OR p_mode NOT IN ('legacy', 'dual', 'broker')
999
+ THEN RAISE EXCEPTION 'managed auth session-set bootstrap is invalid' USING ERRCODE = '22023';
1000
+ END IF;
1001
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', 'active', true);
1002
+ PERFORM pg_catalog.set_config('opengeni.canonical_human_identity_lifecycle', 'active', true);
1003
+ IF p_auth_session_id IS NOT NULL THEN
1004
+ -- All first-adoption contenders for one provider session take the same
1005
+ -- transaction-scoped fence before reading operation or slot ownership.
1006
+ -- This leaves the ordinary session_set -> slot -> auth_session lock order
1007
+ -- untouched while making a concurrent loser observe the winner's receipt.
1008
+ PERFORM pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
1009
+ 'managed-auth-bootstrap:' || p_auth_session_id, 0
1010
+ ));
1011
+ END IF;
1012
+ SELECT * INTO prior_operation FROM managed_auth_session_set_operations
1013
+ WHERE operation_id = p_operation_id;
1014
+ IF FOUND THEN
1015
+ IF prior_operation.operation_type <> 'bootstrap'
1016
+ OR prior_operation.request_digest <> p_request_digest
1017
+ THEN RAISE EXCEPTION 'managed auth operation id was reused' USING ERRCODE = '23505'; END IF;
1018
+ SELECT session_set.* INTO set_row FROM managed_auth_session_sets session_set
1019
+ INNER JOIN managed_auth_browser_installations installation
1020
+ ON installation.id = session_set.installation_id
1021
+ WHERE session_set.id = prior_operation.session_set_id
1022
+ AND session_set.authority_hash = p_authority_hash
1023
+ AND session_set.csrf_hash = p_csrf_hash
1024
+ AND session_set.revoked_at IS NULL AND installation.revoked_at IS NULL
1025
+ FOR UPDATE OF session_set, installation;
1026
+ IF NOT FOUND THEN RAISE EXCEPTION 'managed auth operation authority mismatch' USING ERRCODE = '42501'; END IF;
1027
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
1028
+ PERFORM pg_catalog.set_config('opengeni.canonical_human_identity_lifecycle', CASE WHEN previous_canonical_marker IS NULL THEN '' ELSE previous_canonical_marker END, true);
1029
+ RETURN prior_operation.result;
1030
+ END IF;
1031
+ IF p_auth_session_id IS NOT NULL AND EXISTS (
1032
+ SELECT 1 FROM managed_auth_login_slots slot
1033
+ WHERE slot.auth_session_id = p_auth_session_id AND slot.status <> 'revoked'
1034
+ ) THEN
1035
+ RAISE EXCEPTION 'managed auth bootstrap session is already adopted'
1036
+ USING ERRCODE = '42501';
1037
+ END IF;
1038
+ INSERT INTO managed_auth_browser_installations (authority_hash)
1039
+ VALUES (p_authority_hash)
1040
+ ON CONFLICT (authority_hash) DO NOTHING;
1041
+ SELECT id INTO installation_uuid FROM managed_auth_browser_installations
1042
+ WHERE authority_hash = p_authority_hash AND revoked_at IS NULL FOR UPDATE;
1043
+ IF NOT FOUND THEN RAISE EXCEPTION 'managed auth browser installation is unavailable' USING ERRCODE = '42501'; END IF;
1044
+ INSERT INTO managed_auth_session_sets (installation_id, authority_hash, csrf_hash)
1045
+ VALUES (installation_uuid, p_authority_hash, p_csrf_hash)
1046
+ ON CONFLICT (authority_hash) DO NOTHING;
1047
+ SELECT * INTO set_row FROM managed_auth_session_sets
1048
+ WHERE authority_hash = p_authority_hash AND revoked_at IS NULL FOR UPDATE;
1049
+ IF NOT FOUND OR set_row.csrf_hash <> p_csrf_hash THEN
1050
+ RAISE EXCEPTION 'managed auth session-set authority denied' USING ERRCODE = '42501';
1051
+ END IF;
1052
+ SELECT * INTO prior_operation FROM managed_auth_session_set_operations
1053
+ WHERE operation_id = p_operation_id;
1054
+ IF FOUND THEN
1055
+ IF prior_operation.session_set_id <> set_row.id
1056
+ OR prior_operation.operation_type <> 'bootstrap'
1057
+ OR prior_operation.request_digest <> p_request_digest
1058
+ THEN RAISE EXCEPTION 'managed auth operation id was reused' USING ERRCODE = '23505'; END IF;
1059
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
1060
+ PERFORM pg_catalog.set_config('opengeni.canonical_human_identity_lifecycle', CASE WHEN previous_canonical_marker IS NULL THEN '' ELSE previous_canonical_marker END, true);
1061
+ RETURN prior_operation.result;
1062
+ END IF;
1063
+ IF managed_auth_session_set_snapshot(p_authority_hash, p_mode, false, false) IS NULL THEN
1064
+ RAISE EXCEPTION 'managed auth session-set authority expired' USING ERRCODE = '42501';
1065
+ END IF;
1066
+ SELECT * INTO set_row FROM managed_auth_session_sets WHERE id = set_row.id FOR UPDATE;
1067
+ IF set_row.generation <> p_expected_generation
1068
+ OR set_row.actor_epoch <> p_expected_actor_epoch
1069
+ THEN RAISE EXCEPTION 'managed auth session-set generation conflict' USING ERRCODE = '40001'; END IF;
1070
+ IF p_auth_session_id IS NOT NULL AND NOT EXISTS (
1071
+ SELECT 1 FROM managed_auth_login_slots WHERE session_set_id = set_row.id
1072
+ ) THEN
1073
+ SELECT auth_session.id, auth_session.user_id, auth_session.identity_id,
1074
+ auth_session.identity_revision, auth_session.auth_revision,
1075
+ auth_session.login_binding_id,
1076
+ auth_session.login_binding_revision, identity_row.display_name,
1077
+ auth_user.email
1078
+ INTO session_row
1079
+ FROM auth_sessions auth_session
1080
+ INNER JOIN auth_users auth_user ON auth_user.id = auth_session.user_id
1081
+ INNER JOIN canonical_human_identities identity_row
1082
+ ON identity_row.id = auth_session.identity_id
1083
+ INNER JOIN canonical_human_login_bindings binding
1084
+ ON binding.id = auth_session.login_binding_id
1085
+ AND binding.identity_id = auth_session.identity_id
1086
+ INNER JOIN canonical_human_identity_subjects subject_row
1087
+ ON subject_row.auth_user_id = auth_session.user_id
1088
+ AND subject_row.identity_id = auth_session.identity_id
1089
+ WHERE auth_session.id = p_auth_session_id
1090
+ AND auth_session.expires_at > pg_catalog.clock_timestamp()
1091
+ AND auth_session.identity_revision = identity_row.identity_revision
1092
+ AND auth_session.auth_revision = identity_row.auth_revision
1093
+ AND auth_session.login_binding_revision = binding.revision
1094
+ AND identity_row.status = 'active' AND binding.status = 'active'
1095
+ AND subject_row.status = 'active';
1096
+ IF NOT FOUND THEN RAISE EXCEPTION 'managed auth bootstrap session is unavailable' USING ERRCODE = '42501'; END IF;
1097
+ INSERT INTO managed_auth_login_slots (
1098
+ session_set_id, auth_session_id, auth_user_id, identity_id, login_binding_id,
1099
+ identity_revision, auth_revision, login_binding_revision, display_name, verified_email
1100
+ ) VALUES (
1101
+ set_row.id, session_row.id, session_row.user_id, session_row.identity_id,
1102
+ session_row.login_binding_id, session_row.identity_revision, session_row.auth_revision,
1103
+ session_row.login_binding_revision, session_row.display_name, session_row.email
1104
+ ) RETURNING id INTO slot_id;
1105
+ UPDATE managed_auth_session_sets SET selected_slot_id = slot_id, updated_at = pg_catalog.clock_timestamp()
1106
+ WHERE id = set_row.id RETURNING * INTO set_row;
1107
+ END IF;
1108
+ result := managed_auth_session_set_snapshot(p_authority_hash, p_mode, false, false);
1109
+ SELECT * INTO set_row FROM managed_auth_session_sets WHERE id = set_row.id FOR UPDATE;
1110
+ INSERT INTO managed_auth_session_set_operations (
1111
+ operation_id, session_set_id, operation_type, request_digest,
1112
+ expected_generation, result_generation, result_actor_epoch, outcome, result
1113
+ ) VALUES (
1114
+ p_operation_id, set_row.id, 'bootstrap', p_request_digest,
1115
+ p_expected_generation, set_row.generation, set_row.actor_epoch, 'applied', result
1116
+ );
1117
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
1118
+ PERFORM pg_catalog.set_config('opengeni.canonical_human_identity_lifecycle', CASE WHEN previous_canonical_marker IS NULL THEN '' ELSE previous_canonical_marker END, true);
1119
+ RETURN result;
1120
+ EXCEPTION WHEN OTHERS THEN
1121
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
1122
+ PERFORM pg_catalog.set_config('opengeni.canonical_human_identity_lifecycle', CASE WHEN previous_canonical_marker IS NULL THEN '' ELSE previous_canonical_marker END, true);
1123
+ RAISE;
1124
+ END;
1125
+ $body$;
1126
+ $ddl$, data_schema);
1127
+
1128
+ EXECUTE format($ddl$
1129
+ CREATE OR REPLACE FUNCTION %1$I.managed_auth_login_transaction_rate_limit_take(
1130
+ p_client_scope_hash text, p_set_scope_hash text
1131
+ ) RETURNS void
1132
+ LANGUAGE plpgsql SECURITY DEFINER
1133
+ SET search_path = pg_catalog, %1$I, pg_temp
1134
+ AS $body$
1135
+ DECLARE
1136
+ rate_window timestamptz;
1137
+ set_rate_window timestamptz;
1138
+ global_attempts integer;
1139
+ client_attempts integer;
1140
+ set_attempts integer;
1141
+ previous_marker text := pg_catalog.current_setting(
1142
+ 'opengeni.managed_auth_session_set_lifecycle', true
1143
+ );
1144
+ BEGIN
1145
+ IF p_client_scope_hash !~ '^[0-9a-f]{64}$'
1146
+ OR p_set_scope_hash !~ '^[0-9a-f]{64}$'
1147
+ THEN
1148
+ RAISE EXCEPTION 'managed auth login-transaction rate scope is invalid'
1149
+ USING ERRCODE = '22023';
1150
+ END IF;
1151
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', 'active', true);
1152
+ rate_window := pg_catalog.date_bin(
1153
+ interval '10 minutes', pg_catalog.clock_timestamp(),
1154
+ timestamptz '2001-01-01 00:00:00+00'
1155
+ );
1156
+ set_rate_window := pg_catalog.date_bin(
1157
+ interval '1 day', pg_catalog.clock_timestamp(),
1158
+ timestamptz '2001-01-01 00:00:00+00'
1159
+ );
1160
+ INSERT INTO managed_auth_login_transaction_rate_limits (
1161
+ scope_kind, scope_hash, window_started_at, attempt_count, expires_at
1162
+ ) VALUES (
1163
+ 'global', pg_catalog.repeat('0', 64), rate_window, 1,
1164
+ rate_window + interval '20 minutes'
1165
+ )
1166
+ ON CONFLICT (scope_kind, scope_hash) DO UPDATE SET
1167
+ window_started_at = EXCLUDED.window_started_at,
1168
+ attempt_count = CASE
1169
+ WHEN managed_auth_login_transaction_rate_limits.window_started_at = EXCLUDED.window_started_at
1170
+ THEN managed_auth_login_transaction_rate_limits.attempt_count + 1 ELSE 1 END,
1171
+ expires_at = EXCLUDED.expires_at
1172
+ RETURNING attempt_count INTO global_attempts;
1173
+ IF global_attempts > 500 THEN
1174
+ RAISE EXCEPTION 'managed auth global login-transaction rate limit exceeded'
1175
+ USING ERRCODE = 'P0004';
1176
+ END IF;
1177
+
1178
+ INSERT INTO managed_auth_login_transaction_rate_limits (
1179
+ scope_kind, scope_hash, window_started_at, attempt_count, expires_at
1180
+ ) VALUES (
1181
+ 'client', p_client_scope_hash, rate_window, 1, rate_window + interval '20 minutes'
1182
+ )
1183
+ ON CONFLICT (scope_kind, scope_hash) DO UPDATE SET
1184
+ window_started_at = EXCLUDED.window_started_at,
1185
+ attempt_count = CASE
1186
+ WHEN managed_auth_login_transaction_rate_limits.window_started_at = EXCLUDED.window_started_at
1187
+ THEN managed_auth_login_transaction_rate_limits.attempt_count + 1 ELSE 1 END,
1188
+ expires_at = EXCLUDED.expires_at
1189
+ RETURNING attempt_count INTO client_attempts;
1190
+ IF client_attempts > 8 THEN
1191
+ RAISE EXCEPTION 'managed auth client login-transaction rate limit exceeded'
1192
+ USING ERRCODE = 'P0004';
1193
+ END IF;
1194
+
1195
+ INSERT INTO managed_auth_login_transaction_rate_limits (
1196
+ scope_kind, scope_hash, window_started_at, attempt_count, expires_at
1197
+ ) VALUES (
1198
+ 'set', p_set_scope_hash, set_rate_window, 1, set_rate_window + interval '2 days'
1199
+ )
1200
+ ON CONFLICT (scope_kind, scope_hash) DO UPDATE SET
1201
+ window_started_at = EXCLUDED.window_started_at,
1202
+ attempt_count = CASE
1203
+ WHEN managed_auth_login_transaction_rate_limits.window_started_at = EXCLUDED.window_started_at
1204
+ THEN managed_auth_login_transaction_rate_limits.attempt_count + 1 ELSE 1 END,
1205
+ expires_at = EXCLUDED.expires_at
1206
+ RETURNING attempt_count INTO set_attempts;
1207
+ IF set_attempts > 16 THEN
1208
+ RAISE EXCEPTION 'managed auth session-set login-transaction rate limit exceeded'
1209
+ USING ERRCODE = 'P0004';
1210
+ END IF;
1211
+ PERFORM pg_catalog.set_config(
1212
+ 'opengeni.managed_auth_session_set_lifecycle',
1213
+ CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true
1214
+ );
1215
+ EXCEPTION WHEN OTHERS THEN
1216
+ PERFORM pg_catalog.set_config(
1217
+ 'opengeni.managed_auth_session_set_lifecycle',
1218
+ CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true
1219
+ );
1220
+ RAISE;
1221
+ END;
1222
+ $body$;
1223
+ $ddl$, data_schema);
1224
+
1225
+ EXECUTE format($ddl$
1226
+ CREATE OR REPLACE FUNCTION %1$I.managed_auth_session_set_begin_transaction(
1227
+ p_authority_hash text, p_csrf_hash text, p_rate_scope_hash text, p_operation_id uuid,
1228
+ p_request_digest text, p_expected_generation bigint, p_transaction_id uuid,
1229
+ p_expected_actor_epoch bigint,
1230
+ p_transaction_secret_hash text, p_kind text, p_target_slot_id uuid,
1231
+ p_return_intent_id uuid, p_return_path text, p_expires_at timestamptz
1232
+ ) RETURNS jsonb
1233
+ LANGUAGE plpgsql SECURITY DEFINER
1234
+ SET search_path = pg_catalog, %1$I, pg_temp
1235
+ AS $body$
1236
+ DECLARE
1237
+ set_row managed_auth_session_sets%%ROWTYPE;
1238
+ slot_row managed_auth_login_slots%%ROWTYPE;
1239
+ prior_operation managed_auth_session_set_operations%%ROWTYPE;
1240
+ installation_uuid uuid;
1241
+ result jsonb;
1242
+ is_preauth boolean;
1243
+ transaction_rate_taken boolean := false;
1244
+ previous_marker text := pg_catalog.current_setting('opengeni.managed_auth_session_set_lifecycle', true);
1245
+ BEGIN
1246
+ IF p_authority_hash !~ '^[0-9a-f]{64}$' OR p_csrf_hash !~ '^[0-9a-f]{64}$'
1247
+ OR p_rate_scope_hash !~ '^[0-9a-f]{64}$'
1248
+ OR p_request_digest !~ '^[0-9a-f]{64}$' OR p_transaction_secret_hash !~ '^[0-9a-f]{64}$'
1249
+ OR p_operation_id IS NULL
1250
+ OR p_kind NOT IN ('add', 'reauth') OR p_expected_generation < 1 OR p_expected_actor_epoch < 1
1251
+ OR p_expires_at <= pg_catalog.clock_timestamp() OR p_expires_at > pg_catalog.clock_timestamp() + interval '10 minutes'
1252
+ OR ((p_kind = 'reauth') <> (p_target_slot_id IS NOT NULL))
1253
+ OR ((p_return_intent_id IS NULL) <> (p_return_path IS NULL))
1254
+ THEN RAISE EXCEPTION 'managed auth login transaction is invalid' USING ERRCODE = '22023'; END IF;
1255
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', 'active', true);
1256
+ SELECT * INTO set_row FROM managed_auth_session_sets
1257
+ WHERE authority_hash = p_authority_hash FOR UPDATE;
1258
+ IF NOT FOUND THEN
1259
+ IF p_kind <> 'add' OR p_expected_generation <> 1 OR p_expected_actor_epoch <> 1
1260
+ THEN RAISE EXCEPTION 'managed auth session-set authority denied' USING ERRCODE = '42501'; END IF;
1261
+ PERFORM managed_auth_login_transaction_rate_limit_take(
1262
+ p_rate_scope_hash, p_authority_hash
1263
+ );
1264
+ transaction_rate_taken := true;
1265
+ INSERT INTO managed_auth_browser_installations (authority_hash)
1266
+ VALUES (p_authority_hash)
1267
+ ON CONFLICT (authority_hash) DO NOTHING;
1268
+ SELECT id INTO installation_uuid FROM managed_auth_browser_installations
1269
+ WHERE authority_hash = p_authority_hash AND revoked_at IS NULL FOR UPDATE;
1270
+ IF NOT FOUND THEN
1271
+ RAISE EXCEPTION 'managed auth browser installation is unavailable' USING ERRCODE = '42501';
1272
+ END IF;
1273
+ INSERT INTO managed_auth_session_sets (installation_id, authority_hash, csrf_hash)
1274
+ VALUES (installation_uuid, p_authority_hash, p_csrf_hash)
1275
+ ON CONFLICT (authority_hash) DO NOTHING;
1276
+ SELECT * INTO set_row FROM managed_auth_session_sets
1277
+ WHERE authority_hash = p_authority_hash AND revoked_at IS NULL FOR UPDATE;
1278
+ UPDATE managed_auth_browser_installations SET
1279
+ idle_expires_at = least(p_expires_at, absolute_expires_at),
1280
+ last_seen_at = pg_catalog.clock_timestamp()
1281
+ WHERE id = set_row.installation_id;
1282
+ UPDATE managed_auth_session_sets SET
1283
+ idle_expires_at = least(p_expires_at, absolute_expires_at),
1284
+ updated_at = pg_catalog.clock_timestamp()
1285
+ WHERE id = set_row.id RETURNING * INTO set_row;
1286
+ END IF;
1287
+ IF NOT FOUND OR set_row.csrf_hash <> p_csrf_hash THEN
1288
+ RAISE EXCEPTION 'managed auth session-set authority denied' USING ERRCODE = '42501';
1289
+ END IF;
1290
+ SELECT * INTO prior_operation FROM managed_auth_session_set_operations WHERE operation_id = p_operation_id;
1291
+ IF FOUND THEN
1292
+ IF prior_operation.session_set_id <> set_row.id OR prior_operation.request_digest <> p_request_digest
1293
+ OR prior_operation.operation_type <> 'begin_' || p_kind
1294
+ THEN RAISE EXCEPTION 'managed auth operation id was reused' USING ERRCODE = '23505'; END IF;
1295
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
1296
+ RETURN prior_operation.result;
1297
+ END IF;
1298
+ SELECT NOT EXISTS (
1299
+ SELECT 1 FROM managed_auth_login_slots slot WHERE slot.session_set_id = set_row.id
1300
+ ) INTO is_preauth;
1301
+ IF NOT transaction_rate_taken THEN
1302
+ PERFORM managed_auth_login_transaction_rate_limit_take(
1303
+ p_rate_scope_hash, p_authority_hash
1304
+ );
1305
+ END IF;
1306
+ UPDATE managed_auth_login_transactions SET
1307
+ status = 'cancelled', consumed_at = pg_catalog.clock_timestamp()
1308
+ WHERE session_set_id = set_row.id AND status = 'pending'
1309
+ AND expires_at <= pg_catalog.clock_timestamp();
1310
+ IF EXISTS (
1311
+ SELECT 1 FROM managed_auth_login_transactions
1312
+ WHERE session_set_id = set_row.id AND status = 'pending'
1313
+ AND expires_at > pg_catalog.clock_timestamp()
1314
+ ) THEN
1315
+ RAISE EXCEPTION 'managed auth login transaction is already pending'
1316
+ USING ERRCODE = 'P0004';
1317
+ END IF;
1318
+ IF is_preauth THEN
1319
+ UPDATE managed_auth_browser_installations SET
1320
+ idle_expires_at = least(
1321
+ greatest(idle_expires_at, p_expires_at), absolute_expires_at
1322
+ ), last_seen_at = pg_catalog.clock_timestamp()
1323
+ WHERE id = set_row.installation_id;
1324
+ UPDATE managed_auth_session_sets SET
1325
+ idle_expires_at = least(
1326
+ greatest(idle_expires_at, p_expires_at), absolute_expires_at
1327
+ ), updated_at = pg_catalog.clock_timestamp()
1328
+ WHERE id = set_row.id RETURNING * INTO set_row;
1329
+ END IF;
1330
+ IF set_row.revoked_at IS NOT NULL
1331
+ OR managed_auth_session_set_snapshot(
1332
+ p_authority_hash, 'dual', false, false, is_preauth
1333
+ ) IS NULL
1334
+ THEN RAISE EXCEPTION 'managed auth session-set authority expired' USING ERRCODE = '42501'; END IF;
1335
+ SELECT * INTO set_row FROM managed_auth_session_sets WHERE id = set_row.id FOR UPDATE;
1336
+ IF set_row.generation <> p_expected_generation OR set_row.actor_epoch <> p_expected_actor_epoch
1337
+ THEN RAISE EXCEPTION 'managed auth session-set generation conflict' USING ERRCODE = '40001'; END IF;
1338
+ IF p_kind = 'reauth' THEN
1339
+ SELECT * INTO slot_row FROM managed_auth_login_slots
1340
+ WHERE id = p_target_slot_id AND session_set_id = set_row.id AND status <> 'revoked' FOR UPDATE;
1341
+ IF NOT FOUND THEN RAISE EXCEPTION 'managed auth login slot is unavailable' USING ERRCODE = 'P0002'; END IF;
1342
+ END IF;
1343
+ IF p_return_intent_id IS NOT NULL THEN
1344
+ INSERT INTO managed_auth_login_return_intents (id, session_set_id, path, expires_at)
1345
+ VALUES (p_return_intent_id, set_row.id, p_return_path, p_expires_at);
1346
+ END IF;
1347
+ INSERT INTO managed_auth_login_transactions (
1348
+ id, session_set_id, secret_hash, kind, target_slot_id,
1349
+ expected_identity_id, expected_login_binding_id,
1350
+ expected_identity_revision, expected_auth_revision, expected_login_binding_revision,
1351
+ expected_generation,
1352
+ return_intent_id, expires_at
1353
+ ) VALUES (
1354
+ p_transaction_id, set_row.id, p_transaction_secret_hash, p_kind, p_target_slot_id,
1355
+ CASE WHEN p_kind = 'reauth' THEN slot_row.identity_id ELSE NULL END,
1356
+ CASE WHEN p_kind = 'reauth' THEN slot_row.login_binding_id ELSE NULL END,
1357
+ CASE WHEN p_kind = 'reauth' THEN slot_row.identity_revision ELSE NULL END,
1358
+ CASE WHEN p_kind = 'reauth' THEN slot_row.auth_revision ELSE NULL END,
1359
+ CASE WHEN p_kind = 'reauth' THEN slot_row.login_binding_revision ELSE NULL END,
1360
+ p_expected_generation, p_return_intent_id, p_expires_at
1361
+ );
1362
+ result := pg_catalog.jsonb_build_object(
1363
+ 'id', p_transaction_id, 'kind', p_kind,
1364
+ 'expiresAt', p_expires_at, 'returnIntentId', p_return_intent_id
1365
+ );
1366
+ INSERT INTO managed_auth_session_set_operations (
1367
+ operation_id, session_set_id, operation_type, request_digest,
1368
+ expected_generation, result_generation, result_actor_epoch, target_slot_id, outcome, result
1369
+ ) VALUES (
1370
+ p_operation_id, set_row.id, 'begin_' || p_kind, p_request_digest,
1371
+ set_row.generation, set_row.generation, set_row.actor_epoch, p_target_slot_id, 'applied', result
1372
+ );
1373
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
1374
+ RETURN result;
1375
+ EXCEPTION WHEN OTHERS THEN
1376
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
1377
+ RAISE;
1378
+ END;
1379
+ $body$;
1380
+ $ddl$, data_schema);
1381
+
1382
+ EXECUTE format($ddl$
1383
+ CREATE OR REPLACE FUNCTION %1$I.managed_auth_session_set_complete_transaction(
1384
+ p_authority_hash text, p_csrf_hash text, p_operation_id uuid,
1385
+ p_request_digest text, p_expected_generation bigint,
1386
+ p_expected_actor_epoch bigint,
1387
+ p_transaction_id uuid, p_transaction_secret_hash text, p_auth_session_id text,
1388
+ p_mode text
1389
+ ) RETURNS jsonb
1390
+ LANGUAGE plpgsql SECURITY DEFINER
1391
+ SET search_path = pg_catalog, %1$I, pg_temp
1392
+ AS $body$
1393
+ DECLARE
1394
+ set_row managed_auth_session_sets%%ROWTYPE;
1395
+ tx_row managed_auth_login_transactions%%ROWTYPE;
1396
+ session_row record;
1397
+ slot_row managed_auth_login_slots%%ROWTYPE;
1398
+ prior_operation managed_auth_session_set_operations%%ROWTYPE;
1399
+ old_auth_session_id text;
1400
+ slot_id uuid;
1401
+ selected_rebound boolean := false;
1402
+ return_path text;
1403
+ result jsonb;
1404
+ previous_marker text := pg_catalog.current_setting('opengeni.managed_auth_session_set_lifecycle', true);
1405
+ previous_canonical_marker text := pg_catalog.current_setting('opengeni.canonical_human_identity_lifecycle', true);
1406
+ BEGIN
1407
+ IF p_request_digest !~ '^[0-9a-f]{64}$' OR p_transaction_secret_hash !~ '^[0-9a-f]{64}$'
1408
+ OR p_expected_generation < 1 OR p_expected_actor_epoch < 1
1409
+ OR p_mode NOT IN ('legacy', 'dual', 'broker')
1410
+ THEN RAISE EXCEPTION 'managed auth transaction completion is invalid' USING ERRCODE = '22023'; END IF;
1411
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', 'active', true);
1412
+ PERFORM pg_catalog.set_config('opengeni.canonical_human_identity_lifecycle', 'active', true);
1413
+ SELECT * INTO set_row FROM managed_auth_session_sets
1414
+ WHERE authority_hash = p_authority_hash FOR UPDATE;
1415
+ IF NOT FOUND OR set_row.csrf_hash <> p_csrf_hash THEN RAISE EXCEPTION 'managed auth session-set authority denied' USING ERRCODE = '42501'; END IF;
1416
+ SELECT * INTO prior_operation FROM managed_auth_session_set_operations WHERE operation_id = p_operation_id;
1417
+ IF FOUND THEN
1418
+ IF prior_operation.session_set_id <> set_row.id OR prior_operation.request_digest <> p_request_digest
1419
+ OR prior_operation.operation_type NOT IN ('complete_add', 'complete_reauth')
1420
+ THEN RAISE EXCEPTION 'managed auth operation id was reused' USING ERRCODE = '23505'; END IF;
1421
+ DELETE FROM auth_sessions replay_session
1422
+ WHERE replay_session.id = p_auth_session_id
1423
+ AND NOT EXISTS (
1424
+ SELECT 1 FROM managed_auth_login_slots slot
1425
+ WHERE slot.id = prior_operation.target_slot_id
1426
+ AND slot.auth_session_id = replay_session.id
1427
+ );
1428
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
1429
+ PERFORM pg_catalog.set_config('opengeni.canonical_human_identity_lifecycle', CASE WHEN previous_canonical_marker IS NULL THEN '' ELSE previous_canonical_marker END, true);
1430
+ RETURN prior_operation.result;
1431
+ END IF;
1432
+ IF set_row.revoked_at IS NOT NULL
1433
+ OR managed_auth_session_set_snapshot(p_authority_hash, p_mode, false, false) IS NULL
1434
+ THEN RAISE EXCEPTION 'managed auth session-set authority expired' USING ERRCODE = '42501'; END IF;
1435
+ SELECT * INTO set_row FROM managed_auth_session_sets WHERE id = set_row.id FOR UPDATE;
1436
+ IF set_row.generation <> p_expected_generation OR set_row.actor_epoch <> p_expected_actor_epoch
1437
+ THEN RAISE EXCEPTION 'managed auth session-set generation conflict' USING ERRCODE = '40001'; END IF;
1438
+ SELECT * INTO tx_row FROM managed_auth_login_transactions
1439
+ WHERE id = p_transaction_id AND session_set_id = set_row.id FOR UPDATE;
1440
+ IF NOT FOUND OR tx_row.secret_hash <> p_transaction_secret_hash OR tx_row.status <> 'pending'
1441
+ OR tx_row.expires_at <= pg_catalog.clock_timestamp() OR tx_row.expected_generation <> p_expected_generation
1442
+ THEN RAISE EXCEPTION 'managed auth login transaction is unavailable' USING ERRCODE = '42501'; END IF;
1443
+ SELECT auth_session.id, auth_session.user_id, auth_session.identity_id,
1444
+ auth_session.identity_revision, auth_session.auth_revision,
1445
+ auth_session.login_binding_id,
1446
+ auth_session.login_binding_revision, identity_row.display_name, auth_user.email
1447
+ INTO session_row
1448
+ FROM auth_sessions auth_session
1449
+ INNER JOIN auth_users auth_user ON auth_user.id = auth_session.user_id
1450
+ INNER JOIN canonical_human_identities identity_row ON identity_row.id = auth_session.identity_id
1451
+ INNER JOIN canonical_human_login_bindings binding
1452
+ ON binding.id = auth_session.login_binding_id
1453
+ AND binding.identity_id = auth_session.identity_id
1454
+ INNER JOIN canonical_human_identity_subjects subject_row
1455
+ ON subject_row.auth_user_id = auth_session.user_id AND subject_row.identity_id = auth_session.identity_id
1456
+ WHERE auth_session.id = p_auth_session_id AND auth_session.expires_at > pg_catalog.clock_timestamp()
1457
+ AND auth_session.identity_revision = identity_row.identity_revision
1458
+ AND auth_session.auth_revision = identity_row.auth_revision
1459
+ AND auth_session.login_binding_revision = binding.revision
1460
+ AND subject_row.status = 'active'
1461
+ AND (
1462
+ (identity_row.status = 'active' AND binding.status = 'active')
1463
+ OR (
1464
+ tx_row.kind = 'reauth'
1465
+ AND auth_session.identity_id = tx_row.expected_identity_id
1466
+ AND auth_session.login_binding_id = tx_row.expected_login_binding_id
1467
+ AND identity_row.status = 'recovery_required'
1468
+ AND binding.status = 'recovery_pending'
1469
+ )
1470
+ );
1471
+ IF NOT FOUND THEN RAISE EXCEPTION 'managed auth provider session is unavailable' USING ERRCODE = '42501'; END IF;
1472
+ IF tx_row.kind = 'reauth' THEN
1473
+ IF session_row.identity_id <> tx_row.expected_identity_id
1474
+ OR session_row.login_binding_id <> tx_row.expected_login_binding_id
1475
+ OR session_row.identity_revision <> tx_row.expected_identity_revision
1476
+ OR session_row.auth_revision <> tx_row.expected_auth_revision
1477
+ OR session_row.login_binding_revision <> tx_row.expected_login_binding_revision
1478
+ THEN RAISE EXCEPTION 'managed auth reauthentication binding mismatch' USING ERRCODE = '42501'; END IF;
1479
+ SELECT * INTO slot_row FROM managed_auth_login_slots
1480
+ WHERE id = tx_row.target_slot_id AND session_set_id = set_row.id AND status <> 'revoked' FOR UPDATE;
1481
+ IF NOT FOUND THEN RAISE EXCEPTION 'managed auth login slot is unavailable' USING ERRCODE = 'P0002'; END IF;
1482
+ ELSE
1483
+ SELECT * INTO slot_row FROM managed_auth_login_slots
1484
+ WHERE session_set_id = set_row.id
1485
+ AND login_binding_id = session_row.login_binding_id
1486
+ AND status <> 'revoked' FOR UPDATE;
1487
+ IF FOUND THEN
1488
+ RAISE EXCEPTION 'managed auth login binding already has a slot' USING ERRCODE = 'P0003';
1489
+ END IF;
1490
+ IF (
1491
+ SELECT count(*) FROM managed_auth_login_slots WHERE session_set_id = set_row.id AND status <> 'revoked'
1492
+ ) >= 8 THEN RAISE EXCEPTION 'managed auth login slot limit reached' USING ERRCODE = '54000'; END IF;
1493
+ END IF;
1494
+ IF tx_row.kind = 'reauth' THEN
1495
+ old_auth_session_id := slot_row.auth_session_id;
1496
+ slot_id := slot_row.id;
1497
+ selected_rebound := slot_row.id = set_row.selected_slot_id;
1498
+ IF selected_rebound THEN
1499
+ DELETE FROM managed_auth_actor_mutation_leases
1500
+ WHERE session_set_id = set_row.id AND expires_at <= pg_catalog.clock_timestamp();
1501
+ IF EXISTS (
1502
+ SELECT 1 FROM managed_auth_actor_mutation_leases
1503
+ WHERE session_set_id = set_row.id AND actor_epoch = set_row.actor_epoch
1504
+ ) THEN
1505
+ RAISE EXCEPTION 'managed auth actor has an in-flight mutation'
1506
+ USING ERRCODE = '55P03';
1507
+ END IF;
1508
+ END IF;
1509
+ UPDATE managed_auth_login_slots SET
1510
+ auth_session_id = session_row.id, auth_user_id = session_row.user_id,
1511
+ identity_id = session_row.identity_id, login_binding_id = session_row.login_binding_id,
1512
+ identity_revision = session_row.identity_revision, auth_revision = session_row.auth_revision,
1513
+ login_binding_revision = session_row.login_binding_revision,
1514
+ display_name = session_row.display_name, verified_email = session_row.email,
1515
+ status = 'active', revoked_at = NULL, updated_at = pg_catalog.clock_timestamp()
1516
+ WHERE id = slot_row.id;
1517
+ ELSE
1518
+ INSERT INTO managed_auth_login_slots (
1519
+ session_set_id, auth_session_id, auth_user_id, identity_id, login_binding_id,
1520
+ identity_revision, auth_revision, login_binding_revision, display_name, verified_email
1521
+ ) VALUES (
1522
+ set_row.id, session_row.id, session_row.user_id, session_row.identity_id,
1523
+ session_row.login_binding_id, session_row.identity_revision, session_row.auth_revision,
1524
+ session_row.login_binding_revision, session_row.display_name, session_row.email
1525
+ ) RETURNING id INTO slot_id;
1526
+ END IF;
1527
+ UPDATE managed_auth_login_transactions SET status = 'consumed', consumed_at = pg_catalog.clock_timestamp()
1528
+ WHERE id = tx_row.id;
1529
+ IF tx_row.return_intent_id IS NOT NULL THEN
1530
+ UPDATE managed_auth_login_return_intents SET consumed_at = pg_catalog.clock_timestamp()
1531
+ WHERE id = tx_row.return_intent_id AND consumed_at IS NULL AND expires_at > pg_catalog.clock_timestamp()
1532
+ RETURNING path INTO return_path;
1533
+ END IF;
1534
+ UPDATE managed_auth_session_sets changed_set SET
1535
+ generation = changed_set.generation + 1,
1536
+ actor_epoch = changed_set.actor_epoch + CASE WHEN selected_rebound THEN 1 ELSE 0 END,
1537
+ state = CASE WHEN selected_rebound THEN 'ready' ELSE changed_set.state END,
1538
+ updated_at = pg_catalog.clock_timestamp()
1539
+ WHERE changed_set.id = set_row.id RETURNING * INTO set_row;
1540
+ IF old_auth_session_id IS NOT NULL AND old_auth_session_id <> session_row.id THEN
1541
+ DELETE FROM auth_sessions WHERE id = old_auth_session_id;
1542
+ END IF;
1543
+ result := managed_auth_session_set_snapshot(p_authority_hash, p_mode, false, false)
1544
+ || pg_catalog.jsonb_build_object('returnIntent', return_path);
1545
+ SELECT * INTO set_row FROM managed_auth_session_sets WHERE id = set_row.id FOR UPDATE;
1546
+ INSERT INTO managed_auth_session_set_operations (
1547
+ operation_id, session_set_id, operation_type, request_digest,
1548
+ expected_generation, result_generation, result_actor_epoch, target_slot_id, outcome, result
1549
+ ) VALUES (
1550
+ p_operation_id, set_row.id, 'complete_' || tx_row.kind, p_request_digest,
1551
+ p_expected_generation, set_row.generation, set_row.actor_epoch, slot_id, 'applied', result
1552
+ );
1553
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
1554
+ PERFORM pg_catalog.set_config('opengeni.canonical_human_identity_lifecycle', CASE WHEN previous_canonical_marker IS NULL THEN '' ELSE previous_canonical_marker END, true);
1555
+ RETURN result;
1556
+ EXCEPTION WHEN OTHERS THEN
1557
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
1558
+ PERFORM pg_catalog.set_config('opengeni.canonical_human_identity_lifecycle', CASE WHEN previous_canonical_marker IS NULL THEN '' ELSE previous_canonical_marker END, true);
1559
+ RAISE;
1560
+ END;
1561
+ $body$;
1562
+ $ddl$, data_schema);
1563
+
1564
+ EXECUTE format($ddl$
1565
+ CREATE OR REPLACE FUNCTION %1$I.managed_auth_session_set_mutate(
1566
+ p_authority_hash text, p_csrf_hash text, p_operation_id uuid,
1567
+ p_request_digest text, p_expected_generation bigint, p_expected_actor_epoch bigint,
1568
+ p_operation_type text,
1569
+ p_target_slot_id uuid, p_replacement_slot_id uuid,
1570
+ p_transaction_id uuid, p_transaction_secret_hash text, p_mode text
1571
+ ) RETURNS jsonb
1572
+ LANGUAGE plpgsql SECURITY DEFINER
1573
+ SET search_path = pg_catalog, %1$I, pg_temp
1574
+ AS $body$
1575
+ DECLARE
1576
+ set_row managed_auth_session_sets%%ROWTYPE;
1577
+ target_slot managed_auth_login_slots%%ROWTYPE;
1578
+ replacement_slot managed_auth_login_slots%%ROWTYPE;
1579
+ tx_row managed_auth_login_transactions%%ROWTYPE;
1580
+ prior_operation managed_auth_session_set_operations%%ROWTYPE;
1581
+ was_selected boolean := false;
1582
+ result jsonb;
1583
+ previous_marker text := pg_catalog.current_setting('opengeni.managed_auth_session_set_lifecycle', true);
1584
+ BEGIN
1585
+ IF p_request_digest !~ '^[0-9a-f]{64}$' OR p_expected_generation < 1
1586
+ OR p_expected_actor_epoch < 1
1587
+ OR p_operation_type NOT IN ('cancel_transaction', 'select', 'logout_one', 'logout_all')
1588
+ OR p_mode NOT IN ('legacy', 'dual', 'broker')
1589
+ THEN RAISE EXCEPTION 'managed auth session-set mutation is invalid' USING ERRCODE = '22023'; END IF;
1590
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', 'active', true);
1591
+ SELECT * INTO set_row FROM managed_auth_session_sets
1592
+ WHERE authority_hash = p_authority_hash FOR UPDATE;
1593
+ IF NOT FOUND OR set_row.csrf_hash <> p_csrf_hash THEN RAISE EXCEPTION 'managed auth session-set authority denied' USING ERRCODE = '42501'; END IF;
1594
+ SELECT * INTO prior_operation FROM managed_auth_session_set_operations WHERE operation_id = p_operation_id;
1595
+ IF FOUND THEN
1596
+ IF prior_operation.session_set_id <> set_row.id OR prior_operation.request_digest <> p_request_digest
1597
+ OR prior_operation.operation_type <> p_operation_type
1598
+ THEN RAISE EXCEPTION 'managed auth operation id was reused' USING ERRCODE = '23505'; END IF;
1599
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
1600
+ RETURN prior_operation.result;
1601
+ END IF;
1602
+ IF set_row.revoked_at IS NOT NULL THEN RAISE EXCEPTION 'managed auth session-set authority denied' USING ERRCODE = '42501'; END IF;
1603
+ IF managed_auth_session_set_snapshot(p_authority_hash, p_mode, false, false) IS NULL THEN
1604
+ RAISE EXCEPTION 'managed auth session-set authority expired' USING ERRCODE = '42501';
1605
+ END IF;
1606
+ SELECT * INTO set_row FROM managed_auth_session_sets WHERE id = set_row.id FOR UPDATE;
1607
+ IF set_row.generation <> p_expected_generation OR set_row.actor_epoch <> p_expected_actor_epoch
1608
+ THEN RAISE EXCEPTION 'managed auth session-set generation conflict' USING ERRCODE = '40001'; END IF;
1609
+ IF p_operation_type = 'cancel_transaction' THEN
1610
+ IF p_transaction_secret_hash !~ '^[0-9a-f]{64}$' THEN RAISE EXCEPTION 'managed auth transaction secret is invalid' USING ERRCODE = '22023'; END IF;
1611
+ SELECT * INTO tx_row FROM managed_auth_login_transactions
1612
+ WHERE id = p_transaction_id AND session_set_id = set_row.id FOR UPDATE;
1613
+ IF NOT FOUND OR tx_row.secret_hash <> p_transaction_secret_hash OR tx_row.status <> 'pending'
1614
+ THEN RAISE EXCEPTION 'managed auth login transaction is unavailable' USING ERRCODE = '42501'; END IF;
1615
+ UPDATE managed_auth_login_transactions SET status = 'cancelled', consumed_at = pg_catalog.clock_timestamp()
1616
+ WHERE id = tx_row.id;
1617
+ UPDATE managed_auth_login_return_intents SET consumed_at = pg_catalog.clock_timestamp()
1618
+ WHERE id = tx_row.return_intent_id AND consumed_at IS NULL;
1619
+ ELSIF p_operation_type = 'select' THEN
1620
+ SELECT * INTO target_slot FROM managed_auth_login_slots
1621
+ WHERE id = p_target_slot_id AND session_set_id = set_row.id AND status = 'active'
1622
+ AND auth_session_id IS NOT NULL FOR UPDATE;
1623
+ IF NOT FOUND THEN RAISE EXCEPTION 'managed auth login slot is unavailable' USING ERRCODE = 'P0002'; END IF;
1624
+ DELETE FROM managed_auth_actor_mutation_leases
1625
+ WHERE session_set_id = set_row.id AND expires_at <= pg_catalog.clock_timestamp();
1626
+ IF EXISTS (
1627
+ SELECT 1 FROM managed_auth_actor_mutation_leases
1628
+ WHERE session_set_id = set_row.id AND actor_epoch = set_row.actor_epoch
1629
+ ) THEN
1630
+ RAISE EXCEPTION 'managed auth actor has an in-flight mutation' USING ERRCODE = '55P03';
1631
+ END IF;
1632
+ UPDATE managed_auth_session_sets SET selected_slot_id = target_slot.id,
1633
+ generation = generation + 1, actor_epoch = actor_epoch + 1,
1634
+ state = 'ready', updated_at = pg_catalog.clock_timestamp()
1635
+ WHERE id = set_row.id RETURNING * INTO set_row;
1636
+ ELSIF p_operation_type = 'logout_one' THEN
1637
+ SELECT * INTO target_slot FROM managed_auth_login_slots
1638
+ WHERE id = p_target_slot_id AND session_set_id = set_row.id AND status <> 'revoked' FOR UPDATE;
1639
+ IF NOT FOUND THEN RAISE EXCEPTION 'managed auth login slot is unavailable' USING ERRCODE = 'P0002'; END IF;
1640
+ was_selected := target_slot.id = set_row.selected_slot_id;
1641
+ IF NOT was_selected AND p_replacement_slot_id IS NOT NULL THEN
1642
+ RAISE EXCEPTION 'replacement is valid only for the selected slot' USING ERRCODE = '22023';
1643
+ END IF;
1644
+ IF p_replacement_slot_id IS NOT NULL THEN
1645
+ SELECT * INTO replacement_slot FROM managed_auth_login_slots
1646
+ WHERE id = p_replacement_slot_id AND session_set_id = set_row.id
1647
+ AND id <> target_slot.id AND status = 'active' AND auth_session_id IS NOT NULL FOR UPDATE;
1648
+ IF NOT FOUND THEN RAISE EXCEPTION 'managed auth replacement slot is unavailable' USING ERRCODE = 'P0002'; END IF;
1649
+ END IF;
1650
+ IF was_selected THEN
1651
+ DELETE FROM managed_auth_actor_mutation_leases
1652
+ WHERE session_set_id = set_row.id AND expires_at <= pg_catalog.clock_timestamp();
1653
+ IF EXISTS (
1654
+ SELECT 1 FROM managed_auth_actor_mutation_leases
1655
+ WHERE session_set_id = set_row.id AND actor_epoch = set_row.actor_epoch
1656
+ ) THEN
1657
+ RAISE EXCEPTION 'managed auth actor has an in-flight mutation' USING ERRCODE = '55P03';
1658
+ END IF;
1659
+ END IF;
1660
+ UPDATE managed_auth_login_slots SET status = 'revoked', auth_session_id = NULL,
1661
+ revoked_at = pg_catalog.clock_timestamp(), updated_at = pg_catalog.clock_timestamp()
1662
+ WHERE id = target_slot.id;
1663
+ IF target_slot.auth_session_id IS NOT NULL THEN DELETE FROM auth_sessions WHERE id = target_slot.auth_session_id; END IF;
1664
+ UPDATE managed_auth_session_sets SET
1665
+ selected_slot_id = CASE WHEN was_selected THEN p_replacement_slot_id ELSE selected_slot_id END,
1666
+ generation = generation + 1,
1667
+ actor_epoch = actor_epoch + CASE WHEN was_selected THEN 1 ELSE 0 END,
1668
+ state = CASE
1669
+ WHEN was_selected AND p_replacement_slot_id IS NULL THEN 'actor_change_required'
1670
+ WHEN was_selected THEN 'ready'
1671
+ ELSE state
1672
+ END,
1673
+ updated_at = pg_catalog.clock_timestamp()
1674
+ WHERE id = set_row.id RETURNING * INTO set_row;
1675
+ ELSE
1676
+ DELETE FROM managed_auth_actor_mutation_leases
1677
+ WHERE session_set_id = set_row.id AND expires_at <= pg_catalog.clock_timestamp();
1678
+ IF EXISTS (
1679
+ SELECT 1 FROM managed_auth_actor_mutation_leases
1680
+ WHERE session_set_id = set_row.id AND actor_epoch = set_row.actor_epoch
1681
+ ) THEN
1682
+ RAISE EXCEPTION 'managed auth actor has an in-flight mutation' USING ERRCODE = '55P03';
1683
+ END IF;
1684
+ DELETE FROM auth_sessions auth_session USING managed_auth_login_slots slot
1685
+ WHERE slot.session_set_id = set_row.id AND slot.status <> 'revoked'
1686
+ AND auth_session.id = slot.auth_session_id;
1687
+ UPDATE managed_auth_login_slots SET status = 'revoked', auth_session_id = NULL,
1688
+ revoked_at = pg_catalog.clock_timestamp(), updated_at = pg_catalog.clock_timestamp()
1689
+ WHERE session_set_id = set_row.id AND status <> 'revoked';
1690
+ UPDATE managed_auth_session_sets SET selected_slot_id = NULL,
1691
+ generation = generation + 1, actor_epoch = actor_epoch + 1,
1692
+ state = 'actor_change_required', revoked_at = pg_catalog.clock_timestamp(),
1693
+ updated_at = pg_catalog.clock_timestamp()
1694
+ WHERE id = set_row.id RETURNING * INTO set_row;
1695
+ UPDATE managed_auth_browser_installations SET revoked_at = pg_catalog.clock_timestamp(),
1696
+ last_seen_at = pg_catalog.clock_timestamp()
1697
+ WHERE id = set_row.installation_id AND revoked_at IS NULL;
1698
+ END IF;
1699
+ result := CASE WHEN p_operation_type = 'logout_all' THEN
1700
+ pg_catalog.jsonb_build_object(
1701
+ 'generation', set_row.generation::text,
1702
+ 'actorEpoch', set_row.actor_epoch::text,
1703
+ 'state', 'logged_out'
1704
+ )
1705
+ ELSE managed_auth_session_set_snapshot(p_authority_hash, p_mode, false, false) END;
1706
+ SELECT * INTO set_row FROM managed_auth_session_sets WHERE id = set_row.id FOR UPDATE;
1707
+ INSERT INTO managed_auth_session_set_operations (
1708
+ operation_id, session_set_id, operation_type, request_digest,
1709
+ expected_generation, result_generation, result_actor_epoch,
1710
+ target_slot_id, replacement_slot_id, outcome, result
1711
+ ) VALUES (
1712
+ p_operation_id, set_row.id, p_operation_type, p_request_digest,
1713
+ p_expected_generation, set_row.generation, set_row.actor_epoch,
1714
+ p_target_slot_id, p_replacement_slot_id, 'applied', result
1715
+ );
1716
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
1717
+ RETURN result;
1718
+ EXCEPTION WHEN OTHERS THEN
1719
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
1720
+ RAISE;
1721
+ END;
1722
+ $body$;
1723
+ $ddl$, data_schema);
1724
+
1725
+ EXECUTE format($ddl$
1726
+ CREATE OR REPLACE FUNCTION %1$I.managed_auth_actor_mutation_fence(
1727
+ p_authority_hash text, p_actor_epoch bigint, p_request_id uuid
1728
+ ) RETURNS boolean
1729
+ LANGUAGE plpgsql SECURITY DEFINER
1730
+ SET search_path = pg_catalog, %1$I, pg_temp
1731
+ AS $body$
1732
+ DECLARE
1733
+ set_row managed_auth_session_sets%%ROWTYPE;
1734
+ previous_marker text := pg_catalog.current_setting(
1735
+ 'opengeni.managed_auth_session_set_lifecycle', true
1736
+ );
1737
+ BEGIN
1738
+ IF p_authority_hash !~ '^[0-9a-f]{64}$' OR p_actor_epoch < 1 OR p_request_id IS NULL
1739
+ THEN RAISE EXCEPTION 'managed auth mutation fence is invalid' USING ERRCODE = '22023'; END IF;
1740
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', 'active', true);
1741
+ SELECT * INTO set_row FROM managed_auth_session_sets
1742
+ WHERE authority_hash = p_authority_hash AND revoked_at IS NULL FOR UPDATE;
1743
+ IF NOT FOUND OR set_row.actor_epoch <> p_actor_epoch OR NOT EXISTS (
1744
+ SELECT 1 FROM managed_auth_actor_mutation_leases lease
1745
+ WHERE lease.session_set_id = set_row.id AND lease.request_id = p_request_id
1746
+ AND lease.actor_epoch = p_actor_epoch AND lease.expires_at > pg_catalog.clock_timestamp()
1747
+ ) THEN
1748
+ RAISE EXCEPTION 'managed auth actor changed before fenced mutation' USING ERRCODE = '40001';
1749
+ END IF;
1750
+ DELETE FROM managed_auth_actor_mutation_leases
1751
+ WHERE session_set_id = set_row.id AND expires_at <= pg_catalog.clock_timestamp();
1752
+ IF EXISTS (
1753
+ SELECT 1 FROM managed_auth_actor_mutation_leases lease
1754
+ WHERE lease.session_set_id = set_row.id AND lease.actor_epoch = p_actor_epoch
1755
+ AND lease.request_id <> p_request_id
1756
+ ) THEN
1757
+ RAISE EXCEPTION 'managed auth actor has an in-flight mutation' USING ERRCODE = '55P03';
1758
+ END IF;
1759
+ PERFORM pg_catalog.set_config(
1760
+ 'opengeni.managed_auth_actor_mutation_request_id', p_request_id::text, true
1761
+ );
1762
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
1763
+ RETURN true;
1764
+ EXCEPTION WHEN OTHERS THEN
1765
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
1766
+ RAISE;
1767
+ END;
1768
+ $body$;
1769
+ $ddl$, data_schema);
1770
+
1771
+ EXECUTE format($ddl$
1772
+ CREATE OR REPLACE FUNCTION %1$I.managed_auth_session_slot_invalidation()
1773
+ RETURNS trigger
1774
+ LANGUAGE plpgsql SECURITY DEFINER
1775
+ SET search_path = pg_catalog, %1$I, pg_temp
1776
+ AS $body$
1777
+ DECLARE
1778
+ slot_row managed_auth_login_slots%%ROWTYPE;
1779
+ set_row managed_auth_session_sets%%ROWTYPE;
1780
+ identity_status text;
1781
+ binding_status text;
1782
+ current_identity_revision bigint;
1783
+ current_auth_revision bigint;
1784
+ current_binding_revision bigint;
1785
+ recovery_transition boolean;
1786
+ own_request_text text := pg_catalog.current_setting(
1787
+ 'opengeni.managed_auth_actor_mutation_request_id', true
1788
+ );
1789
+ own_request_id uuid;
1790
+ previous_marker text := pg_catalog.current_setting(
1791
+ 'opengeni.managed_auth_session_set_lifecycle', true
1792
+ );
1793
+ previous_canonical_marker text := pg_catalog.current_setting(
1794
+ 'opengeni.canonical_human_identity_lifecycle', true
1795
+ );
1796
+ BEGIN
1797
+ IF own_request_text ~ '^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
1798
+ THEN own_request_id := own_request_text::uuid; END IF;
1799
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', 'active', true);
1800
+ PERFORM pg_catalog.set_config('opengeni.canonical_human_identity_lifecycle', 'active', true);
1801
+ FOR slot_row IN
1802
+ SELECT * FROM managed_auth_login_slots
1803
+ WHERE auth_session_id = OLD.id AND status <> 'revoked'
1804
+ ORDER BY session_set_id, id
1805
+ LOOP
1806
+ SELECT * INTO set_row FROM managed_auth_session_sets
1807
+ WHERE id = slot_row.session_set_id FOR UPDATE;
1808
+ DELETE FROM managed_auth_actor_mutation_leases
1809
+ WHERE session_set_id = set_row.id AND expires_at <= pg_catalog.clock_timestamp();
1810
+ IF EXISTS (
1811
+ SELECT 1 FROM managed_auth_actor_mutation_leases lease
1812
+ WHERE lease.session_set_id = set_row.id AND lease.actor_epoch = set_row.actor_epoch
1813
+ AND (own_request_id IS NULL OR lease.request_id <> own_request_id)
1814
+ ) THEN
1815
+ RAISE EXCEPTION 'managed auth actor has an in-flight mutation' USING ERRCODE = '55P03';
1816
+ END IF;
1817
+ IF own_request_id IS NOT NULL THEN
1818
+ DELETE FROM managed_auth_actor_mutation_leases
1819
+ WHERE session_set_id = set_row.id AND request_id = own_request_id
1820
+ AND actor_epoch = set_row.actor_epoch;
1821
+ END IF;
1822
+ SELECT identity_row.status, binding.status, identity_row.identity_revision,
1823
+ identity_row.auth_revision, binding.revision
1824
+ INTO identity_status, binding_status, current_identity_revision,
1825
+ current_auth_revision, current_binding_revision
1826
+ FROM canonical_human_identities identity_row
1827
+ INNER JOIN canonical_human_login_bindings binding
1828
+ ON binding.id = slot_row.login_binding_id
1829
+ AND binding.identity_id = identity_row.id
1830
+ WHERE identity_row.id = slot_row.identity_id;
1831
+ recovery_transition := slot_row.id = set_row.selected_slot_id
1832
+ AND identity_status = 'recovery_required' AND binding_status = 'recovery_pending';
1833
+ UPDATE managed_auth_login_slots SET
1834
+ auth_session_id = NULL,
1835
+ status = CASE WHEN recovery_transition THEN 'active' ELSE 'reauth_required' END,
1836
+ identity_revision = CASE WHEN recovery_transition THEN current_identity_revision ELSE identity_revision END,
1837
+ auth_revision = CASE WHEN recovery_transition THEN current_auth_revision ELSE auth_revision END,
1838
+ login_binding_revision = CASE WHEN recovery_transition THEN current_binding_revision ELSE login_binding_revision END,
1839
+ updated_at = pg_catalog.clock_timestamp()
1840
+ WHERE id = slot_row.id;
1841
+ UPDATE managed_auth_session_sets changed_set SET
1842
+ generation = changed_set.generation + 1,
1843
+ actor_epoch = changed_set.actor_epoch
1844
+ + CASE WHEN slot_row.id = set_row.selected_slot_id THEN 1 ELSE 0 END,
1845
+ selected_slot_id = CASE
1846
+ WHEN slot_row.id = set_row.selected_slot_id AND NOT recovery_transition THEN NULL
1847
+ ELSE changed_set.selected_slot_id END,
1848
+ state = CASE
1849
+ WHEN slot_row.id = set_row.selected_slot_id THEN 'actor_change_required'
1850
+ ELSE changed_set.state END,
1851
+ updated_at = pg_catalog.clock_timestamp()
1852
+ WHERE changed_set.id = set_row.id;
1853
+ END LOOP;
1854
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
1855
+ PERFORM pg_catalog.set_config('opengeni.canonical_human_identity_lifecycle', CASE WHEN previous_canonical_marker IS NULL THEN '' ELSE previous_canonical_marker END, true);
1856
+ RETURN OLD;
1857
+ EXCEPTION WHEN OTHERS THEN
1858
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
1859
+ PERFORM pg_catalog.set_config('opengeni.canonical_human_identity_lifecycle', CASE WHEN previous_canonical_marker IS NULL THEN '' ELSE previous_canonical_marker END, true);
1860
+ RAISE;
1861
+ END;
1862
+ $body$;
1863
+ $ddl$, data_schema);
1864
+ EXECUTE format(
1865
+ 'CREATE TRIGGER managed_auth_session_slot_invalidation BEFORE DELETE ON %I.auth_sessions '
1866
+ || 'FOR EACH ROW EXECUTE FUNCTION %I.managed_auth_session_slot_invalidation()',
1867
+ data_schema, data_schema
1868
+ );
1869
+ EXECUTE format(
1870
+ 'REVOKE ALL ON FUNCTION %I.managed_auth_session_slot_invalidation() FROM PUBLIC',
1871
+ data_schema
1872
+ );
1873
+
1874
+ EXECUTE format($ddl$
1875
+ CREATE OR REPLACE FUNCTION %1$I.managed_auth_actor_mutation_lease_acquire(
1876
+ p_authority_hash text, p_actor_epoch bigint, p_request_id uuid, p_lease_seconds integer
1877
+ ) RETURNS timestamptz
1878
+ LANGUAGE plpgsql SECURITY DEFINER
1879
+ SET search_path = pg_catalog, %1$I, pg_temp
1880
+ AS $body$
1881
+ DECLARE
1882
+ set_row managed_auth_session_sets%%ROWTYPE;
1883
+ lease_expiry timestamptz;
1884
+ previous_marker text := pg_catalog.current_setting(
1885
+ 'opengeni.managed_auth_session_set_lifecycle', true
1886
+ );
1887
+ BEGIN
1888
+ IF p_authority_hash !~ '^[0-9a-f]{64}$' OR p_actor_epoch < 1
1889
+ OR p_request_id IS NULL OR p_lease_seconds NOT BETWEEN 30 AND 900
1890
+ THEN RAISE EXCEPTION 'managed auth mutation lease is invalid' USING ERRCODE = '22023'; END IF;
1891
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', 'active', true);
1892
+ SELECT session_set.* INTO set_row
1893
+ FROM managed_auth_session_sets session_set
1894
+ INNER JOIN managed_auth_browser_installations installation
1895
+ ON installation.id = session_set.installation_id
1896
+ WHERE session_set.authority_hash = p_authority_hash
1897
+ AND session_set.revoked_at IS NULL AND installation.revoked_at IS NULL
1898
+ AND session_set.idle_expires_at > pg_catalog.clock_timestamp()
1899
+ AND session_set.absolute_expires_at > pg_catalog.clock_timestamp()
1900
+ AND installation.idle_expires_at > pg_catalog.clock_timestamp()
1901
+ AND installation.absolute_expires_at > pg_catalog.clock_timestamp()
1902
+ FOR UPDATE OF session_set, installation;
1903
+ IF NOT FOUND THEN
1904
+ RAISE EXCEPTION 'managed auth session-set authority denied' USING ERRCODE = '42501';
1905
+ END IF;
1906
+ IF set_row.actor_epoch <> p_actor_epoch THEN
1907
+ RAISE EXCEPTION 'managed auth actor changed before mutation lease'
1908
+ USING ERRCODE = '40001';
1909
+ END IF;
1910
+ IF set_row.selected_slot_id IS NULL OR NOT EXISTS (
1911
+ SELECT 1 FROM managed_auth_login_slots slot
1912
+ WHERE slot.id = set_row.selected_slot_id AND slot.session_set_id = set_row.id
1913
+ AND slot.status = 'active' AND slot.auth_session_id IS NOT NULL
1914
+ ) THEN
1915
+ RAISE EXCEPTION 'managed auth selected actor is unavailable' USING ERRCODE = '42501';
1916
+ END IF;
1917
+ DELETE FROM managed_auth_actor_mutation_leases
1918
+ WHERE session_set_id = set_row.id AND expires_at <= pg_catalog.clock_timestamp();
1919
+ lease_expiry := pg_catalog.clock_timestamp() + pg_catalog.make_interval(secs => p_lease_seconds);
1920
+ INSERT INTO managed_auth_actor_mutation_leases (
1921
+ session_set_id, request_id, actor_epoch, acquired_at, expires_at
1922
+ ) VALUES (
1923
+ set_row.id, p_request_id, p_actor_epoch, pg_catalog.clock_timestamp(), lease_expiry
1924
+ )
1925
+ ON CONFLICT (session_set_id, request_id) DO UPDATE SET
1926
+ actor_epoch = EXCLUDED.actor_epoch,
1927
+ acquired_at = EXCLUDED.acquired_at,
1928
+ expires_at = EXCLUDED.expires_at
1929
+ WHERE managed_auth_actor_mutation_leases.actor_epoch = EXCLUDED.actor_epoch;
1930
+ IF NOT FOUND THEN
1931
+ RAISE EXCEPTION 'managed auth mutation lease identity changed' USING ERRCODE = '40001';
1932
+ END IF;
1933
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
1934
+ RETURN lease_expiry;
1935
+ EXCEPTION WHEN OTHERS THEN
1936
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
1937
+ RAISE;
1938
+ END;
1939
+ $body$;
1940
+ $ddl$, data_schema);
1941
+
1942
+ EXECUTE format($ddl$
1943
+ CREATE OR REPLACE FUNCTION %1$I.managed_auth_actor_mutation_lease_release(
1944
+ p_authority_hash text, p_request_id uuid
1945
+ ) RETURNS boolean
1946
+ LANGUAGE plpgsql SECURITY DEFINER
1947
+ SET search_path = pg_catalog, %1$I, pg_temp
1948
+ AS $body$
1949
+ DECLARE
1950
+ released boolean;
1951
+ previous_marker text := pg_catalog.current_setting(
1952
+ 'opengeni.managed_auth_session_set_lifecycle', true
1953
+ );
1954
+ BEGIN
1955
+ IF p_authority_hash !~ '^[0-9a-f]{64}$' OR p_request_id IS NULL THEN RETURN false; END IF;
1956
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', 'active', true);
1957
+ DELETE FROM managed_auth_actor_mutation_leases lease
1958
+ USING managed_auth_session_sets session_set
1959
+ WHERE lease.session_set_id = session_set.id
1960
+ AND session_set.authority_hash = p_authority_hash
1961
+ AND lease.request_id = p_request_id;
1962
+ released := FOUND;
1963
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
1964
+ RETURN released;
1965
+ EXCEPTION WHEN OTHERS THEN
1966
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
1967
+ RAISE;
1968
+ END;
1969
+ $body$;
1970
+ $ddl$, data_schema);
1971
+
1972
+ EXECUTE format($ddl$
1973
+ CREATE OR REPLACE FUNCTION %1$I.managed_auth_session_set_operation_receipt(
1974
+ p_authority_hash text, p_operation_id uuid, p_request_digest text, p_operation_kind text
1975
+ ) RETURNS jsonb
1976
+ LANGUAGE plpgsql SECURITY DEFINER
1977
+ SET search_path = pg_catalog, %1$I, pg_temp
1978
+ AS $body$
1979
+ DECLARE
1980
+ operation_row managed_auth_session_set_operations%%ROWTYPE;
1981
+ set_row managed_auth_session_sets%%ROWTYPE;
1982
+ previous_marker text := pg_catalog.current_setting(
1983
+ 'opengeni.managed_auth_session_set_lifecycle', true
1984
+ );
1985
+ BEGIN
1986
+ IF p_authority_hash !~ '^[0-9a-f]{64}$' OR p_operation_id IS NULL
1987
+ OR p_request_digest !~ '^[0-9a-f]{64}$' OR p_operation_kind <> 'complete'
1988
+ THEN RETURN NULL; END IF;
1989
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', 'active', true);
1990
+ SELECT operation.* INTO operation_row FROM managed_auth_session_set_operations operation
1991
+ WHERE operation.operation_id = p_operation_id
1992
+ AND operation.request_digest = p_request_digest
1993
+ AND operation.operation_type IN ('complete_add', 'complete_reauth');
1994
+ IF NOT FOUND THEN
1995
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
1996
+ RETURN NULL;
1997
+ END IF;
1998
+ SELECT session_set.* INTO set_row FROM managed_auth_session_sets session_set
1999
+ INNER JOIN managed_auth_browser_installations installation
2000
+ ON installation.id = session_set.installation_id
2001
+ WHERE session_set.id = operation_row.session_set_id
2002
+ AND session_set.authority_hash = p_authority_hash
2003
+ FOR UPDATE OF session_set, installation;
2004
+ IF NOT FOUND THEN
2005
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
2006
+ RETURN NULL;
2007
+ END IF;
2008
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
2009
+ RETURN operation_row.result;
2010
+ EXCEPTION WHEN OTHERS THEN
2011
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
2012
+ RAISE;
2013
+ END;
2014
+ $body$;
2015
+ $ddl$, data_schema);
2016
+
2017
+ EXECUTE format($ddl$
2018
+ CREATE OR REPLACE FUNCTION %1$I.managed_auth_adopted_session_snapshot(
2019
+ p_auth_session_id text
2020
+ ) RETURNS jsonb
2021
+ LANGUAGE plpgsql SECURITY DEFINER
2022
+ SET search_path = pg_catalog, %1$I, pg_temp
2023
+ AS $body$
2024
+ DECLARE
2025
+ set_row managed_auth_session_sets%%ROWTYPE;
2026
+ snapshot jsonb;
2027
+ selected jsonb;
2028
+ previous_marker text := pg_catalog.current_setting(
2029
+ 'opengeni.managed_auth_session_set_lifecycle', true
2030
+ );
2031
+ BEGIN
2032
+ IF p_auth_session_id IS NULL OR pg_catalog.length(p_auth_session_id) NOT BETWEEN 1 AND 255
2033
+ THEN RETURN NULL; END IF;
2034
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', 'active', true);
2035
+ SELECT session_set.* INTO set_row
2036
+ FROM managed_auth_login_slots slot
2037
+ INNER JOIN managed_auth_session_sets session_set ON session_set.id = slot.session_set_id
2038
+ WHERE slot.auth_session_id = p_auth_session_id AND slot.status <> 'revoked';
2039
+ IF NOT FOUND THEN
2040
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
2041
+ RETURN NULL;
2042
+ END IF;
2043
+ snapshot := managed_auth_session_set_snapshot(
2044
+ set_row.authority_hash, 'dual', true, false, true
2045
+ );
2046
+ selected := CASE
2047
+ WHEN snapshot->'selected'->>'authSessionId' = p_auth_session_id
2048
+ THEN snapshot->'selected' ELSE NULL END;
2049
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
2050
+ RETURN pg_catalog.jsonb_build_object(
2051
+ 'authorityHash', set_row.authority_hash,
2052
+ 'actorEpoch', coalesce(snapshot->'projection'->>'actorEpoch', set_row.actor_epoch::text),
2053
+ 'selected', selected
2054
+ );
2055
+ EXCEPTION WHEN OTHERS THEN
2056
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
2057
+ RAISE;
2058
+ END;
2059
+ $body$;
2060
+ $ddl$, data_schema);
2061
+
2062
+ EXECUTE format($ddl$
2063
+ CREATE OR REPLACE FUNCTION %1$I.managed_auth_actor_mutation_lease_validate(
2064
+ p_authority_hash text, p_actor_epoch bigint, p_request_id uuid
2065
+ ) RETURNS boolean
2066
+ LANGUAGE plpgsql SECURITY DEFINER
2067
+ SET search_path = pg_catalog, %1$I, pg_temp
2068
+ AS $body$
2069
+ DECLARE
2070
+ set_row managed_auth_session_sets%%ROWTYPE;
2071
+ valid boolean := false;
2072
+ previous_marker text := pg_catalog.current_setting(
2073
+ 'opengeni.managed_auth_session_set_lifecycle', true
2074
+ );
2075
+ BEGIN
2076
+ IF p_authority_hash !~ '^[0-9a-f]{64}$' OR p_actor_epoch < 1 OR p_request_id IS NULL
2077
+ THEN RETURN false; END IF;
2078
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', 'active', true);
2079
+ SELECT session_set.* INTO set_row
2080
+ FROM managed_auth_session_sets session_set
2081
+ INNER JOIN managed_auth_browser_installations installation
2082
+ ON installation.id = session_set.installation_id
2083
+ WHERE session_set.authority_hash = p_authority_hash
2084
+ FOR UPDATE OF session_set, installation;
2085
+ IF FOUND THEN
2086
+ SELECT set_row.revoked_at IS NULL
2087
+ AND set_row.actor_epoch = p_actor_epoch
2088
+ AND set_row.idle_expires_at > pg_catalog.clock_timestamp()
2089
+ AND set_row.absolute_expires_at > pg_catalog.clock_timestamp()
2090
+ AND installation.revoked_at IS NULL
2091
+ AND installation.idle_expires_at > pg_catalog.clock_timestamp()
2092
+ AND installation.absolute_expires_at > pg_catalog.clock_timestamp()
2093
+ AND EXISTS (
2094
+ SELECT 1 FROM managed_auth_actor_mutation_leases lease
2095
+ WHERE lease.session_set_id = set_row.id
2096
+ AND lease.request_id = p_request_id
2097
+ AND lease.actor_epoch = p_actor_epoch
2098
+ AND lease.expires_at > pg_catalog.clock_timestamp()
2099
+ )
2100
+ INTO valid
2101
+ FROM managed_auth_browser_installations installation
2102
+ WHERE installation.id = set_row.installation_id;
2103
+ END IF;
2104
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
2105
+ RETURN coalesce(valid, false);
2106
+ EXCEPTION WHEN OTHERS THEN
2107
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
2108
+ RAISE;
2109
+ END;
2110
+ $body$;
2111
+ $ddl$, data_schema);
2112
+
2113
+ EXECUTE format($ddl$
2114
+ CREATE OR REPLACE FUNCTION %1$I.managed_auth_isolated_session_reap(
2115
+ p_limit integer DEFAULT 100
2116
+ ) RETURNS integer
2117
+ LANGUAGE plpgsql SECURITY DEFINER
2118
+ SET search_path = pg_catalog, %1$I, pg_temp
2119
+ AS $body$
2120
+ DECLARE
2121
+ reaped integer := 0;
2122
+ previous_marker text := pg_catalog.current_setting(
2123
+ 'opengeni.managed_auth_session_set_lifecycle', true
2124
+ );
2125
+ previous_canonical_marker text := pg_catalog.current_setting(
2126
+ 'opengeni.canonical_human_identity_lifecycle', true
2127
+ );
2128
+ BEGIN
2129
+ IF p_limit NOT BETWEEN 1 AND 500 THEN
2130
+ RAISE EXCEPTION 'managed auth orphan reap limit is invalid' USING ERRCODE = '22023';
2131
+ END IF;
2132
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', 'active', true);
2133
+ PERFORM pg_catalog.set_config('opengeni.canonical_human_identity_lifecycle', 'active', true);
2134
+ WITH victims AS (
2135
+ SELECT auth_session.id
2136
+ FROM auth_sessions auth_session
2137
+ LEFT JOIN managed_auth_login_transactions login_transaction
2138
+ ON login_transaction.id = auth_session.managed_auth_login_transaction_id
2139
+ WHERE NOT EXISTS (
2140
+ SELECT 1 FROM managed_auth_login_slots slot
2141
+ WHERE slot.auth_session_id = auth_session.id AND slot.status <> 'revoked'
2142
+ )
2143
+ AND (
2144
+ auth_session.expires_at <= pg_catalog.clock_timestamp()
2145
+ OR (
2146
+ auth_session.managed_auth_login_transaction_id IS NOT NULL
2147
+ AND (
2148
+ login_transaction.id IS NULL OR login_transaction.status <> 'pending'
2149
+ OR login_transaction.expires_at <= pg_catalog.clock_timestamp()
2150
+ )
2151
+ )
2152
+ )
2153
+ ORDER BY auth_session.created_at, auth_session.id
2154
+ FOR UPDATE OF auth_session SKIP LOCKED
2155
+ LIMIT p_limit
2156
+ ), deleted AS (
2157
+ DELETE FROM auth_sessions auth_session USING victims
2158
+ WHERE auth_session.id = victims.id
2159
+ RETURNING auth_session.id
2160
+ )
2161
+ SELECT count(*)::integer INTO reaped FROM deleted;
2162
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
2163
+ PERFORM pg_catalog.set_config('opengeni.canonical_human_identity_lifecycle', CASE WHEN previous_canonical_marker IS NULL THEN '' ELSE previous_canonical_marker END, true);
2164
+ RETURN reaped;
2165
+ EXCEPTION WHEN OTHERS THEN
2166
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
2167
+ PERFORM pg_catalog.set_config('opengeni.canonical_human_identity_lifecycle', CASE WHEN previous_canonical_marker IS NULL THEN '' ELSE previous_canonical_marker END, true);
2168
+ RAISE;
2169
+ END;
2170
+ $body$;
2171
+ $ddl$, data_schema);
2172
+
2173
+ EXECUTE format($ddl$
2174
+ CREATE OR REPLACE FUNCTION %1$I.managed_auth_expired_session_set_reap(
2175
+ p_limit integer DEFAULT 100
2176
+ ) RETURNS integer
2177
+ LANGUAGE plpgsql SECURITY DEFINER
2178
+ SET search_path = pg_catalog, %1$I, pg_temp
2179
+ AS $body$
2180
+ DECLARE
2181
+ set_row managed_auth_session_sets%%ROWTYPE;
2182
+ retired integer := 0;
2183
+ previous_marker text := pg_catalog.current_setting(
2184
+ 'opengeni.managed_auth_session_set_lifecycle', true
2185
+ );
2186
+ previous_canonical_marker text := pg_catalog.current_setting(
2187
+ 'opengeni.canonical_human_identity_lifecycle', true
2188
+ );
2189
+ previous_purge_marker text := pg_catalog.current_setting(
2190
+ 'opengeni.managed_auth_session_set_purge', true
2191
+ );
2192
+ BEGIN
2193
+ IF p_limit NOT BETWEEN 1 AND 500 THEN
2194
+ RAISE EXCEPTION 'managed auth expired-set reap limit is invalid' USING ERRCODE = '22023';
2195
+ END IF;
2196
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', 'active', true);
2197
+ PERFORM pg_catalog.set_config('opengeni.canonical_human_identity_lifecycle', 'active', true);
2198
+ FOR set_row IN
2199
+ SELECT session_set.*
2200
+ FROM managed_auth_session_sets session_set
2201
+ INNER JOIN managed_auth_browser_installations installation
2202
+ ON installation.id = session_set.installation_id
2203
+ WHERE session_set.revoked_at IS NULL
2204
+ AND (
2205
+ session_set.idle_expires_at <= pg_catalog.clock_timestamp()
2206
+ OR session_set.absolute_expires_at <= pg_catalog.clock_timestamp()
2207
+ OR installation.revoked_at IS NOT NULL
2208
+ OR installation.idle_expires_at <= pg_catalog.clock_timestamp()
2209
+ OR installation.absolute_expires_at <= pg_catalog.clock_timestamp()
2210
+ )
2211
+ ORDER BY session_set.absolute_expires_at, session_set.id
2212
+ FOR UPDATE OF session_set, installation SKIP LOCKED
2213
+ LIMIT p_limit
2214
+ LOOP
2215
+ DELETE FROM managed_auth_actor_mutation_leases
2216
+ WHERE session_set_id = set_row.id AND expires_at <= pg_catalog.clock_timestamp();
2217
+ IF EXISTS (
2218
+ SELECT 1 FROM managed_auth_actor_mutation_leases lease
2219
+ WHERE lease.session_set_id = set_row.id AND lease.expires_at > pg_catalog.clock_timestamp()
2220
+ ) THEN CONTINUE; END IF;
2221
+ IF NOT EXISTS (
2222
+ SELECT 1 FROM managed_auth_login_slots slot WHERE slot.session_set_id = set_row.id
2223
+ ) THEN
2224
+ UPDATE managed_auth_login_transactions SET
2225
+ status = 'cancelled', consumed_at = coalesce(
2226
+ consumed_at, pg_catalog.clock_timestamp()
2227
+ )
2228
+ WHERE session_set_id = set_row.id AND status = 'pending'
2229
+ AND expires_at <= pg_catalog.clock_timestamp();
2230
+ IF NOT EXISTS (
2231
+ SELECT 1 FROM managed_auth_login_transactions
2232
+ WHERE session_set_id = set_row.id AND status = 'pending'
2233
+ ) THEN
2234
+ PERFORM pg_catalog.set_config(
2235
+ 'opengeni.managed_auth_session_set_purge', 'active', true
2236
+ );
2237
+ DELETE FROM auth_sessions auth_session
2238
+ USING managed_auth_login_transactions login_transaction
2239
+ WHERE login_transaction.session_set_id = set_row.id
2240
+ AND auth_session.managed_auth_login_transaction_id = login_transaction.id;
2241
+ DELETE FROM managed_auth_login_transactions WHERE session_set_id = set_row.id;
2242
+ DELETE FROM managed_auth_login_return_intents WHERE session_set_id = set_row.id;
2243
+ DELETE FROM managed_auth_session_set_operations WHERE session_set_id = set_row.id;
2244
+ DELETE FROM managed_auth_session_sets WHERE id = set_row.id;
2245
+ DELETE FROM managed_auth_browser_installations WHERE id = set_row.installation_id;
2246
+ PERFORM pg_catalog.set_config(
2247
+ 'opengeni.managed_auth_session_set_purge',
2248
+ CASE WHEN previous_purge_marker IS NULL THEN '' ELSE previous_purge_marker END, true
2249
+ );
2250
+ retired := retired + 1;
2251
+ CONTINUE;
2252
+ END IF;
2253
+ END IF;
2254
+ DELETE FROM auth_sessions auth_session USING managed_auth_login_slots slot
2255
+ WHERE slot.session_set_id = set_row.id AND slot.status <> 'revoked'
2256
+ AND auth_session.id = slot.auth_session_id;
2257
+ UPDATE managed_auth_login_slots SET
2258
+ auth_session_id = NULL, status = 'revoked',
2259
+ revoked_at = coalesce(revoked_at, pg_catalog.clock_timestamp()),
2260
+ updated_at = pg_catalog.clock_timestamp()
2261
+ WHERE session_set_id = set_row.id AND status <> 'revoked';
2262
+ UPDATE managed_auth_session_sets SET
2263
+ revoked_at = coalesce(revoked_at, pg_catalog.clock_timestamp()),
2264
+ selected_slot_id = NULL, state = 'actor_change_required',
2265
+ updated_at = pg_catalog.clock_timestamp()
2266
+ WHERE id = set_row.id;
2267
+ UPDATE managed_auth_browser_installations SET
2268
+ revoked_at = coalesce(revoked_at, pg_catalog.clock_timestamp()),
2269
+ last_seen_at = pg_catalog.clock_timestamp()
2270
+ WHERE id = set_row.installation_id;
2271
+ retired := retired + 1;
2272
+ END LOOP;
2273
+ -- Begin-transaction locks a retained set before the global/client/set
2274
+ -- admission rows. Keep reaping in that same direction: never retain a
2275
+ -- rate-row lock while attempting to acquire a session-set lock.
2276
+ WITH victims AS (
2277
+ SELECT scope_kind, scope_hash FROM managed_auth_login_transaction_rate_limits
2278
+ WHERE expires_at <= pg_catalog.clock_timestamp()
2279
+ ORDER BY expires_at, scope_kind, scope_hash
2280
+ FOR UPDATE SKIP LOCKED LIMIT p_limit
2281
+ )
2282
+ DELETE FROM managed_auth_login_transaction_rate_limits rate_limit USING victims
2283
+ WHERE rate_limit.scope_kind = victims.scope_kind
2284
+ AND rate_limit.scope_hash = victims.scope_hash;
2285
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
2286
+ PERFORM pg_catalog.set_config('opengeni.canonical_human_identity_lifecycle', CASE WHEN previous_canonical_marker IS NULL THEN '' ELSE previous_canonical_marker END, true);
2287
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_purge', CASE WHEN previous_purge_marker IS NULL THEN '' ELSE previous_purge_marker END, true);
2288
+ RETURN retired;
2289
+ EXCEPTION WHEN OTHERS THEN
2290
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_lifecycle', CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END, true);
2291
+ PERFORM pg_catalog.set_config('opengeni.canonical_human_identity_lifecycle', CASE WHEN previous_canonical_marker IS NULL THEN '' ELSE previous_canonical_marker END, true);
2292
+ PERFORM pg_catalog.set_config('opengeni.managed_auth_session_set_purge', CASE WHEN previous_purge_marker IS NULL THEN '' ELSE previous_purge_marker END, true);
2293
+ RAISE;
2294
+ END;
2295
+ $body$;
2296
+ $ddl$, data_schema);
2297
+
2298
+ FOR data_schema IN SELECT current_schema() LOOP
2299
+ EXECUTE format('REVOKE ALL ON FUNCTION %I.get_canonical_human_exact_login_binding(text,text) FROM PUBLIC', data_schema);
2300
+ EXECUTE format('REVOKE ALL ON FUNCTION %I.managed_auth_session_set_authority_state(text) FROM PUBLIC', data_schema);
2301
+ EXECUTE format('REVOKE ALL ON FUNCTION %I.managed_auth_session_set_snapshot(text,text,boolean,boolean,boolean) FROM PUBLIC', data_schema);
2302
+ EXECUTE format('REVOKE ALL ON FUNCTION %I.managed_auth_session_set_bootstrap(text,text,text,text,uuid,text,bigint,bigint) FROM PUBLIC', data_schema);
2303
+ EXECUTE format('REVOKE ALL ON FUNCTION %I.managed_auth_login_transaction_rate_limit_take(text,text) FROM PUBLIC', data_schema);
2304
+ EXECUTE format('REVOKE ALL ON FUNCTION %I.managed_auth_session_set_begin_transaction(text,text,text,uuid,text,bigint,uuid,bigint,text,text,uuid,uuid,text,timestamptz) FROM PUBLIC', data_schema);
2305
+ EXECUTE format('REVOKE ALL ON FUNCTION %I.managed_auth_session_set_complete_transaction(text,text,uuid,text,bigint,bigint,uuid,text,text,text) FROM PUBLIC', data_schema);
2306
+ EXECUTE format('REVOKE ALL ON FUNCTION %I.managed_auth_session_set_mutate(text,text,uuid,text,bigint,bigint,text,uuid,uuid,uuid,text,text) FROM PUBLIC', data_schema);
2307
+ EXECUTE format('REVOKE ALL ON FUNCTION %I.managed_auth_actor_mutation_fence(text,bigint,uuid) FROM PUBLIC', data_schema);
2308
+ EXECUTE format('REVOKE ALL ON FUNCTION %I.managed_auth_actor_mutation_lease_acquire(text,bigint,uuid,integer) FROM PUBLIC', data_schema);
2309
+ EXECUTE format('REVOKE ALL ON FUNCTION %I.managed_auth_actor_mutation_lease_release(text,uuid) FROM PUBLIC', data_schema);
2310
+ EXECUTE format('REVOKE ALL ON FUNCTION %I.managed_auth_actor_mutation_lease_validate(text,bigint,uuid) FROM PUBLIC', data_schema);
2311
+ EXECUTE format('REVOKE ALL ON FUNCTION %I.managed_auth_adopted_session_snapshot(text) FROM PUBLIC', data_schema);
2312
+ EXECUTE format('REVOKE ALL ON FUNCTION %I.managed_auth_isolated_session_reap(integer) FROM PUBLIC', data_schema);
2313
+ EXECUTE format('REVOKE ALL ON FUNCTION %I.managed_auth_expired_session_set_reap(integer) FROM PUBLIC', data_schema);
2314
+ EXECUTE format('REVOKE ALL ON FUNCTION %I.managed_auth_session_set_operation_receipt(text,uuid,text,text) FROM PUBLIC', data_schema);
2315
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
2316
+ EXECUTE format('GRANT EXECUTE ON FUNCTION %I.get_canonical_human_exact_login_binding(text,text) TO opengeni_app', data_schema);
2317
+ EXECUTE format('GRANT EXECUTE ON FUNCTION %I.managed_auth_session_set_authority_state(text) TO opengeni_app', data_schema);
2318
+ EXECUTE format('GRANT EXECUTE ON FUNCTION %I.managed_auth_session_set_snapshot(text,text,boolean,boolean,boolean) TO opengeni_app', data_schema);
2319
+ EXECUTE format('GRANT EXECUTE ON FUNCTION %I.managed_auth_session_set_bootstrap(text,text,text,text,uuid,text,bigint,bigint) TO opengeni_app', data_schema);
2320
+ EXECUTE format('GRANT EXECUTE ON FUNCTION %I.managed_auth_session_set_begin_transaction(text,text,text,uuid,text,bigint,uuid,bigint,text,text,uuid,uuid,text,timestamptz) TO opengeni_app', data_schema);
2321
+ EXECUTE format('GRANT EXECUTE ON FUNCTION %I.managed_auth_session_set_complete_transaction(text,text,uuid,text,bigint,bigint,uuid,text,text,text) TO opengeni_app', data_schema);
2322
+ EXECUTE format('GRANT EXECUTE ON FUNCTION %I.managed_auth_session_set_mutate(text,text,uuid,text,bigint,bigint,text,uuid,uuid,uuid,text,text) TO opengeni_app', data_schema);
2323
+ EXECUTE format('GRANT EXECUTE ON FUNCTION %I.managed_auth_actor_mutation_fence(text,bigint,uuid) TO opengeni_app', data_schema);
2324
+ EXECUTE format('GRANT EXECUTE ON FUNCTION %I.managed_auth_actor_mutation_lease_acquire(text,bigint,uuid,integer) TO opengeni_app', data_schema);
2325
+ EXECUTE format('GRANT EXECUTE ON FUNCTION %I.managed_auth_actor_mutation_lease_release(text,uuid) TO opengeni_app', data_schema);
2326
+ EXECUTE format('GRANT EXECUTE ON FUNCTION %I.managed_auth_actor_mutation_lease_validate(text,bigint,uuid) TO opengeni_app', data_schema);
2327
+ EXECUTE format('GRANT EXECUTE ON FUNCTION %I.managed_auth_adopted_session_snapshot(text) TO opengeni_app', data_schema);
2328
+ EXECUTE format('GRANT EXECUTE ON FUNCTION %I.managed_auth_isolated_session_reap(integer) TO opengeni_app', data_schema);
2329
+ EXECUTE format('GRANT EXECUTE ON FUNCTION %I.managed_auth_expired_session_set_reap(integer) TO opengeni_app', data_schema);
2330
+ EXECUTE format('GRANT EXECUTE ON FUNCTION %I.managed_auth_session_set_operation_receipt(text,uuid,text,text) TO opengeni_app', data_schema);
2331
+ END IF;
2332
+ END LOOP;
2333
+ END
2334
+ $managed_auth_session_set_routines$;
2335
+
2336
+ REVOKE ALL ON FUNCTION managed_auth_session_set_operations_append_only() FROM PUBLIC;
2337
+
2338
+ COMMENT ON TABLE "managed_auth_session_sets" IS
2339
+ 'Hash-only browser installation/session-set authority with explicit generation and actor-epoch fences.';
2340
+ COMMENT ON TABLE "managed_auth_login_slots" IS
2341
+ 'Bounded provider-neutral login slots; references Better Auth sessions server-side and never duplicates their tokens.';
2342
+ COMMENT ON TABLE "managed_auth_login_transactions" IS
2343
+ 'One-use isolated add/reauth transactions; only SHA-256 secret digests are durable.';
2344
+ COMMENT ON TABLE "managed_auth_login_transaction_rate_limits" IS
2345
+ 'Bounded multi-replica global, secret-hashed client, and session-set rate buckets for browser login transactions.';
2346
+ COMMENT ON TABLE "managed_auth_session_set_operations" IS
2347
+ 'Append-only, secret-free idempotency and security evidence; expired slot-free pre-auth sets are physically purged.';