@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.
- package/dist/canonical-human-identities.d.ts +20 -0
- package/dist/canonical-human-identities.js +7 -3
- package/dist/{chunk-ZWRIPCTR.js → chunk-2NT476HD.js} +2 -2
- package/dist/{chunk-PYMY3AJW.js → chunk-744DFC65.js} +202 -7
- package/dist/chunk-744DFC65.js.map +1 -0
- package/dist/{chunk-O6FTELKH.js → chunk-CDMMSUWJ.js} +3 -3
- package/dist/chunk-G3732M3T.js +299 -0
- package/dist/chunk-G3732M3T.js.map +1 -0
- package/dist/{chunk-TRBOWUG3.js → chunk-IW6O6G7R.js} +2 -2
- package/dist/{chunk-HY3F5Q4E.js → chunk-Q5YUGQVK.js} +3 -3
- package/dist/{chunk-V3TRUXFJ.js → chunk-UGDVSQ6U.js} +72 -19
- package/dist/chunk-UGDVSQ6U.js.map +1 -0
- package/dist/{chunk-C2AFP7SB.js → chunk-UJ2EW3QI.js} +6664 -6051
- package/dist/chunk-UJ2EW3QI.js.map +1 -0
- package/dist/{chunk-H7MXYG7R.js → chunk-WW7AJ5IC.js} +2 -2
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/index.d.ts +44 -6
- package/dist/index.js +2914 -1535
- package/dist/index.js.map +1 -1
- package/dist/managed-auth-session-set-schema.d.ts +1399 -0
- package/dist/managed-auth-session-sets.d.ts +145 -0
- package/dist/managed-auth-session-sets.js +50 -0
- package/dist/managed-auth-session-sets.js.map +1 -0
- package/dist/organization-recovery-schema.d.ts +2144 -0
- package/dist/organization-recovery.d.ts +186 -0
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +7 -4
- package/dist/schema.d.ts +9 -6
- package/dist/schema.js +43 -1
- package/dist/session-background-commands.js +3 -3
- package/dist/session-control.d.ts +5 -3
- package/dist/session-queue-commands.d.ts +2 -0
- package/dist/session-tenancy.js +3 -3
- package/dist/video-generation.js +3 -3
- package/dist/work-claims-schema.d.ts +975 -0
- package/dist/work-claims.d.ts +83 -0
- package/drizzle/0362_managed_auth_session_sets.sql +2347 -0
- package/drizzle/0363_organization_recovery_custody.sql +2418 -0
- package/drizzle/0364_workspace_learning_policy_snapshot_lock_order.sql +221 -0
- package/drizzle/0365_permission_scoped_work_claims.sql +1306 -0
- package/drizzle/0366_session_discovery_title_search_index.sql +5 -0
- package/drizzle/0367_session_discovery_goal_search_index.sql +5 -0
- package/drizzle/0368_session_discovery_claim_search_index.sql +6 -0
- package/package.json +10 -6
- package/src/canonical-human-identities.ts +105 -27
- package/src/index.ts +1471 -433
- package/src/managed-auth-session-set-schema.ts +176 -0
- package/src/managed-auth-session-sets.ts +430 -0
- package/src/organization-recovery-schema.ts +277 -0
- package/src/organization-recovery.ts +424 -0
- package/src/provision-roles.ts +67 -0
- package/src/runtime-posture.ts +144 -6
- package/src/schema.ts +3 -0
- package/src/session-control.ts +255 -16
- package/src/session-queue-commands.ts +10 -1
- package/src/session-tool-call-settlement.ts +6 -1
- package/src/work-claims-schema.ts +148 -0
- package/src/work-claims.ts +200 -0
- package/dist/chunk-C2AFP7SB.js.map +0 -1
- package/dist/chunk-PYMY3AJW.js.map +0 -1
- package/dist/chunk-V3TRUXFJ.js.map +0 -1
- /package/dist/{chunk-ZWRIPCTR.js.map → chunk-2NT476HD.js.map} +0 -0
- /package/dist/{chunk-O6FTELKH.js.map → chunk-CDMMSUWJ.js.map} +0 -0
- /package/dist/{chunk-TRBOWUG3.js.map → chunk-IW6O6G7R.js.map} +0 -0
- /package/dist/{chunk-HY3F5Q4E.js.map → chunk-Q5YUGQVK.js.map} +0 -0
- /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.';
|