@opengeni/db 3.2.0-canary.0 → 3.4.0-canary.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.js +3 -3
- package/dist/{chunk-SIBYBA2D.js → chunk-6DPJA36C.js} +30 -11
- package/dist/chunk-6DPJA36C.js.map +1 -0
- package/dist/{chunk-KRLPEMLH.js → chunk-C2AFP7SB.js} +184 -6
- package/dist/chunk-C2AFP7SB.js.map +1 -0
- package/dist/{chunk-AZHW7R5V.js → chunk-H7MXYG7R.js} +12 -8
- package/dist/chunk-H7MXYG7R.js.map +1 -0
- package/dist/{chunk-2PRS4J5Q.js → chunk-HY3F5Q4E.js} +3 -3
- package/dist/{chunk-NAB6FKFN.js → chunk-O6FTELKH.js} +3 -3
- package/dist/{chunk-UZNXG44K.js → chunk-PYMY3AJW.js} +324 -8
- package/dist/chunk-PYMY3AJW.js.map +1 -0
- package/dist/{chunk-6HMM2R5T.js → chunk-TRBOWUG3.js} +98 -4
- package/dist/chunk-TRBOWUG3.js.map +1 -0
- package/dist/{chunk-E6CLMKEE.js → chunk-V3TRUXFJ.js} +2 -2
- package/dist/{chunk-2EH4WRHL.js → chunk-ZWRIPCTR.js} +2 -2
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/governed-learning-activation.d.ts +16 -0
- package/dist/index.d.ts +108 -7
- package/dist/index.js +3605 -1417
- package/dist/index.js.map +1 -1
- package/dist/insights-model-bundle.d.ts +30 -0
- package/dist/insights-usage-bundle.d.ts +32 -0
- package/dist/insights.d.ts +10 -2
- package/dist/lossless-json.d.ts +1 -1
- package/dist/managed-user-setup.d.ts +82 -0
- package/dist/memory-domain.d.ts +2 -2
- package/dist/migrate.d.ts +3 -1
- package/dist/migrate.js +1 -1
- package/dist/new-session-drafts.d.ts +4 -4
- package/dist/organization-membership-lifecycle.d.ts +60 -1
- package/dist/pr-review.d.ts +44 -3
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +12 -7
- package/dist/schema.d.ts +839 -101
- package/dist/schema.js +9 -1
- package/dist/session-background-commands.js +3 -3
- package/dist/session-tenancy.d.ts +12 -1
- package/dist/session-tenancy.js +3 -3
- package/dist/video-generation.js +3 -3
- package/drizzle/0348_named_signup_and_user_setup.sql +838 -0
- package/drizzle/0349_greenfield_session_tenancy_activation.sql +792 -0
- package/drizzle/0350_organization_shared_workspace_administration.sql +889 -0
- package/drizzle/0351_organization_user_setup_delivery.sql +978 -0
- package/drizzle/0352_session_variable_set_attachments.sql +1661 -0
- package/drizzle/0353_automatic_session_title_policy_fence.sql +476 -0
- package/drizzle/0354_automatic_session_title_quarantine_index.sql +9 -0
- package/drizzle/0355_automatic_session_title_quarantine.sql +92 -0
- package/drizzle/0356_set_based_insights_session_visibility.sql +237 -0
- package/drizzle/0357_rig_platform_base_only.sql +44 -0
- package/drizzle/0358_pr_review_managed_github_app.sql +108 -0
- package/drizzle/0359_insights_force_rls_read_capability.sql +936 -0
- package/drizzle/0360_organization_identity_confirmation_prompt.sql +103 -0
- package/drizzle/0361_remember_knowledge_memory_materialization.sql +249 -0
- package/package.json +6 -6
- package/src/database.ts +12 -8
- package/src/governed-learning-activation.ts +74 -2
- package/src/index.ts +879 -92
- package/src/insights-model-bundle.ts +681 -0
- package/src/insights-usage-bundle.ts +240 -0
- package/src/insights.ts +265 -152
- package/src/lossless-json.ts +2 -1
- package/src/managed-user-setup.ts +316 -0
- package/src/memory-domain.ts +3 -3
- package/src/migrate.ts +38 -14
- package/src/new-session-drafts.ts +22 -15
- package/src/organization-membership-lifecycle.ts +253 -1
- package/src/pr-review.ts +449 -6
- package/src/provision-roles.ts +92 -0
- package/src/runtime-posture.ts +247 -4
- package/src/schema.ts +210 -4
- package/src/session-tenancy.ts +120 -2
- package/dist/chunk-6HMM2R5T.js.map +0 -1
- package/dist/chunk-AZHW7R5V.js.map +0 -1
- package/dist/chunk-KRLPEMLH.js.map +0 -1
- package/dist/chunk-SIBYBA2D.js.map +0 -1
- package/dist/chunk-UZNXG44K.js.map +0 -1
- /package/dist/{chunk-2PRS4J5Q.js.map → chunk-HY3F5Q4E.js.map} +0 -0
- /package/dist/{chunk-NAB6FKFN.js.map → chunk-O6FTELKH.js.map} +0 -0
- /package/dist/{chunk-E6CLMKEE.js.map → chunk-V3TRUXFJ.js.map} +0 -0
- /package/dist/{chunk-2EH4WRHL.js.map → chunk-ZWRIPCTR.js.map} +0 -0
|
@@ -0,0 +1,978 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Durable, idempotent invited-user setup delivery. Setup bearers remain
|
|
3
|
+
-- digest-only and are derived by the application only after a durable claim.
|
|
4
|
+
|
|
5
|
+
CREATE TABLE organization_user_setup_deliveries (
|
|
6
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
7
|
+
account_id uuid NOT NULL REFERENCES managed_accounts(id) ON DELETE CASCADE,
|
|
8
|
+
invitation_id uuid NOT NULL,
|
|
9
|
+
invitation_operation_id uuid NOT NULL,
|
|
10
|
+
created_by_membership_id uuid NOT NULL,
|
|
11
|
+
setup_intent_id uuid REFERENCES organization_user_setup_intents(id) ON DELETE RESTRICT,
|
|
12
|
+
provider_key text NOT NULL UNIQUE,
|
|
13
|
+
recipient_email text NOT NULL,
|
|
14
|
+
recipient_name text,
|
|
15
|
+
organization_name text NOT NULL,
|
|
16
|
+
organization_role text NOT NULL,
|
|
17
|
+
shared_workspace_access jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
18
|
+
token_digest text,
|
|
19
|
+
payload_digest text,
|
|
20
|
+
state text NOT NULL DEFAULT 'pending',
|
|
21
|
+
revision bigint NOT NULL DEFAULT 1,
|
|
22
|
+
claim_holder_id uuid,
|
|
23
|
+
claim_expires_at timestamptz,
|
|
24
|
+
attempt_count integer NOT NULL DEFAULT 0,
|
|
25
|
+
provider_idempotency_scope text,
|
|
26
|
+
provider_retry_safe_until timestamptz,
|
|
27
|
+
provider_retry_window_started_at timestamptz,
|
|
28
|
+
provider_started_at timestamptz,
|
|
29
|
+
unresolved_provider_outcome_at timestamptz,
|
|
30
|
+
error_class text,
|
|
31
|
+
provider_message_id text,
|
|
32
|
+
sent_at timestamptz,
|
|
33
|
+
failed_at timestamptz,
|
|
34
|
+
outcome_unknown_at timestamptz,
|
|
35
|
+
revoked_at timestamptz,
|
|
36
|
+
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
|
37
|
+
updated_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
|
38
|
+
CONSTRAINT organization_user_setup_deliveries_invitation_fk
|
|
39
|
+
FOREIGN KEY (invitation_id, account_id)
|
|
40
|
+
REFERENCES organization_membership_invitations(id, account_id) ON DELETE RESTRICT,
|
|
41
|
+
CONSTRAINT organization_user_setup_deliveries_creator_fk
|
|
42
|
+
FOREIGN KEY (created_by_membership_id, account_id)
|
|
43
|
+
REFERENCES organization_memberships(id, account_id) ON DELETE RESTRICT,
|
|
44
|
+
CONSTRAINT organization_user_setup_deliveries_invitation_uq
|
|
45
|
+
UNIQUE (account_id, invitation_id),
|
|
46
|
+
CONSTRAINT organization_user_setup_deliveries_id_account_uq
|
|
47
|
+
UNIQUE (id, account_id),
|
|
48
|
+
CONSTRAINT organization_user_setup_deliveries_role_check
|
|
49
|
+
CHECK (organization_role IN ('owner', 'admin', 'member')),
|
|
50
|
+
CONSTRAINT organization_user_setup_deliveries_state_check
|
|
51
|
+
CHECK (state IN ('pending', 'sent', 'failed', 'outcome_unknown', 'revoked')),
|
|
52
|
+
CONSTRAINT organization_user_setup_deliveries_digest_check CHECK (
|
|
53
|
+
(token_digest IS NULL OR token_digest ~ '^[0-9a-f]{64}$')
|
|
54
|
+
AND (payload_digest IS NULL OR payload_digest ~ '^[0-9a-f]{64}$')
|
|
55
|
+
),
|
|
56
|
+
CONSTRAINT organization_user_setup_deliveries_bounds_check CHECK (
|
|
57
|
+
recipient_email = lower(btrim(recipient_email))
|
|
58
|
+
AND octet_length(convert_to(recipient_email, 'UTF8')) BETWEEN 3 AND 320
|
|
59
|
+
AND (recipient_name IS NULL OR octet_length(convert_to(recipient_name, 'UTF8')) BETWEEN 1 AND 120)
|
|
60
|
+
AND octet_length(convert_to(organization_name, 'UTF8')) BETWEEN 1 AND 120
|
|
61
|
+
AND octet_length(convert_to(provider_key, 'UTF8')) BETWEEN 1 AND 200
|
|
62
|
+
AND (
|
|
63
|
+
provider_idempotency_scope IS NULL
|
|
64
|
+
OR (
|
|
65
|
+
octet_length(convert_to(provider_idempotency_scope, 'UTF8')) BETWEEN 1 AND 200
|
|
66
|
+
AND provider_idempotency_scope ~ '^[a-z0-9][a-z0-9:._-]*$'
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
AND jsonb_typeof(shared_workspace_access) = 'array'
|
|
70
|
+
AND jsonb_array_length(shared_workspace_access) <= 100
|
|
71
|
+
AND (error_class IS NULL OR octet_length(convert_to(error_class, 'UTF8')) BETWEEN 1 AND 64)
|
|
72
|
+
AND (provider_message_id IS NULL OR octet_length(convert_to(provider_message_id, 'UTF8')) BETWEEN 1 AND 255)
|
|
73
|
+
AND attempt_count >= 0
|
|
74
|
+
AND revision > 0
|
|
75
|
+
),
|
|
76
|
+
CONSTRAINT organization_user_setup_deliveries_claim_check CHECK (
|
|
77
|
+
(claim_holder_id IS NULL AND claim_expires_at IS NULL)
|
|
78
|
+
OR (claim_holder_id IS NOT NULL AND claim_expires_at IS NOT NULL)
|
|
79
|
+
),
|
|
80
|
+
CONSTRAINT organization_user_setup_deliveries_provider_fence_check CHECK (
|
|
81
|
+
(
|
|
82
|
+
provider_idempotency_scope IS NULL
|
|
83
|
+
AND provider_retry_safe_until IS NULL
|
|
84
|
+
AND provider_retry_window_started_at IS NULL
|
|
85
|
+
)
|
|
86
|
+
OR (
|
|
87
|
+
provider_idempotency_scope IS NOT NULL
|
|
88
|
+
AND provider_retry_safe_until IS NOT NULL
|
|
89
|
+
AND provider_retry_window_started_at IS NOT NULL
|
|
90
|
+
AND provider_retry_safe_until >= provider_retry_window_started_at
|
|
91
|
+
)
|
|
92
|
+
),
|
|
93
|
+
CONSTRAINT organization_user_setup_deliveries_unresolved_outcome_check CHECK (
|
|
94
|
+
unresolved_provider_outcome_at IS NULL
|
|
95
|
+
OR (
|
|
96
|
+
provider_idempotency_scope IS NOT NULL
|
|
97
|
+
AND state IN ('pending', 'outcome_unknown')
|
|
98
|
+
)
|
|
99
|
+
),
|
|
100
|
+
CONSTRAINT organization_user_setup_deliveries_terminal_check CHECK (
|
|
101
|
+
(state = 'pending' AND sent_at IS NULL AND failed_at IS NULL
|
|
102
|
+
AND outcome_unknown_at IS NULL AND revoked_at IS NULL)
|
|
103
|
+
OR (state = 'sent' AND sent_at IS NOT NULL AND failed_at IS NULL
|
|
104
|
+
AND outcome_unknown_at IS NULL AND revoked_at IS NULL)
|
|
105
|
+
OR (state = 'failed' AND sent_at IS NULL AND failed_at IS NOT NULL
|
|
106
|
+
AND outcome_unknown_at IS NULL AND revoked_at IS NULL)
|
|
107
|
+
OR (state = 'outcome_unknown' AND sent_at IS NULL AND failed_at IS NULL
|
|
108
|
+
AND outcome_unknown_at IS NOT NULL AND revoked_at IS NULL)
|
|
109
|
+
OR (state = 'revoked' AND revoked_at IS NOT NULL)
|
|
110
|
+
)
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
CREATE INDEX organization_user_setup_deliveries_account_updated_idx
|
|
114
|
+
ON organization_user_setup_deliveries (account_id, updated_at DESC, id DESC);
|
|
115
|
+
CREATE UNIQUE INDEX organization_user_setup_deliveries_token_digest_uq
|
|
116
|
+
ON organization_user_setup_deliveries (token_digest) WHERE token_digest IS NOT NULL;
|
|
117
|
+
|
|
118
|
+
CREATE TABLE organization_user_setup_delivery_attempts (
|
|
119
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
120
|
+
account_id uuid NOT NULL REFERENCES managed_accounts(id) ON DELETE CASCADE,
|
|
121
|
+
delivery_id uuid NOT NULL,
|
|
122
|
+
operation_id uuid NOT NULL,
|
|
123
|
+
attempt_number integer NOT NULL,
|
|
124
|
+
claim_holder_id uuid NOT NULL,
|
|
125
|
+
claimed_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
|
126
|
+
claim_expires_at timestamptz NOT NULL,
|
|
127
|
+
provider_started_at timestamptz,
|
|
128
|
+
result text,
|
|
129
|
+
error_class text,
|
|
130
|
+
provider_message_id text,
|
|
131
|
+
settled_at timestamptz,
|
|
132
|
+
CONSTRAINT organization_user_setup_delivery_attempts_operation_uq
|
|
133
|
+
UNIQUE (delivery_id, operation_id),
|
|
134
|
+
CONSTRAINT organization_user_setup_delivery_attempts_delivery_fk
|
|
135
|
+
FOREIGN KEY (delivery_id, account_id)
|
|
136
|
+
REFERENCES organization_user_setup_deliveries(id, account_id) ON DELETE RESTRICT,
|
|
137
|
+
CONSTRAINT organization_user_setup_delivery_attempts_number_uq
|
|
138
|
+
UNIQUE (delivery_id, attempt_number),
|
|
139
|
+
CONSTRAINT organization_user_setup_delivery_attempts_bounds_check CHECK (
|
|
140
|
+
attempt_number > 0
|
|
141
|
+
AND claim_expires_at > claimed_at
|
|
142
|
+
AND (result IS NULL OR result IN ('sent', 'failed', 'outcome_unknown', 'revoked'))
|
|
143
|
+
AND (error_class IS NULL OR octet_length(convert_to(error_class, 'UTF8')) BETWEEN 1 AND 64)
|
|
144
|
+
AND (provider_message_id IS NULL OR octet_length(convert_to(provider_message_id, 'UTF8')) BETWEEN 1 AND 255)
|
|
145
|
+
),
|
|
146
|
+
CONSTRAINT organization_user_setup_delivery_attempts_settlement_check CHECK (
|
|
147
|
+
(result IS NULL AND settled_at IS NULL)
|
|
148
|
+
OR (result IS NOT NULL AND settled_at IS NOT NULL)
|
|
149
|
+
)
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
ALTER TABLE organization_user_setup_deliveries ENABLE ROW LEVEL SECURITY;
|
|
153
|
+
ALTER TABLE organization_user_setup_deliveries FORCE ROW LEVEL SECURITY;
|
|
154
|
+
CREATE POLICY organization_tenancy_lifecycle ON organization_user_setup_deliveries
|
|
155
|
+
USING (current_setting('opengeni.organization_tenancy_lifecycle', true)
|
|
156
|
+
= 'organization_membership_lifecycle')
|
|
157
|
+
WITH CHECK (current_setting('opengeni.organization_tenancy_lifecycle', true)
|
|
158
|
+
= 'organization_membership_lifecycle');
|
|
159
|
+
|
|
160
|
+
ALTER TABLE organization_user_setup_delivery_attempts ENABLE ROW LEVEL SECURITY;
|
|
161
|
+
ALTER TABLE organization_user_setup_delivery_attempts FORCE ROW LEVEL SECURITY;
|
|
162
|
+
CREATE POLICY organization_tenancy_lifecycle ON organization_user_setup_delivery_attempts
|
|
163
|
+
USING (current_setting('opengeni.organization_tenancy_lifecycle', true)
|
|
164
|
+
= 'organization_membership_lifecycle')
|
|
165
|
+
WITH CHECK (current_setting('opengeni.organization_tenancy_lifecycle', true)
|
|
166
|
+
= 'organization_membership_lifecycle');
|
|
167
|
+
|
|
168
|
+
CREATE FUNCTION opengeni_private.organization_user_setup_delivery_json(
|
|
169
|
+
p_delivery organization_user_setup_deliveries
|
|
170
|
+
) RETURNS jsonb
|
|
171
|
+
LANGUAGE sql STABLE
|
|
172
|
+
SET search_path = pg_catalog
|
|
173
|
+
AS $body$
|
|
174
|
+
SELECT pg_catalog.jsonb_build_object(
|
|
175
|
+
'id', p_delivery.id,
|
|
176
|
+
'state', CASE
|
|
177
|
+
WHEN p_delivery.state = 'pending'
|
|
178
|
+
AND p_delivery.claim_expires_at <= pg_catalog.clock_timestamp()
|
|
179
|
+
THEN CASE
|
|
180
|
+
WHEN p_delivery.provider_started_at IS NULL
|
|
181
|
+
AND p_delivery.unresolved_provider_outcome_at IS NULL
|
|
182
|
+
THEN 'failed'
|
|
183
|
+
ELSE 'outcome_unknown'
|
|
184
|
+
END
|
|
185
|
+
ELSE p_delivery.state
|
|
186
|
+
END,
|
|
187
|
+
'attemptCount', p_delivery.attempt_count,
|
|
188
|
+
'revision', p_delivery.revision,
|
|
189
|
+
'errorClass', CASE
|
|
190
|
+
WHEN p_delivery.state = 'pending'
|
|
191
|
+
AND p_delivery.claim_expires_at <= pg_catalog.clock_timestamp()
|
|
192
|
+
THEN CASE
|
|
193
|
+
WHEN p_delivery.provider_started_at IS NULL
|
|
194
|
+
AND p_delivery.unresolved_provider_outcome_at IS NULL
|
|
195
|
+
THEN 'claim_expired_before_provider'
|
|
196
|
+
WHEN p_delivery.provider_started_at IS NULL
|
|
197
|
+
THEN 'prior_provider_outcome_unresolved'
|
|
198
|
+
ELSE 'provider_started_claim_expired'
|
|
199
|
+
END
|
|
200
|
+
ELSE p_delivery.error_class
|
|
201
|
+
END,
|
|
202
|
+
'retryState', CASE
|
|
203
|
+
WHEN p_delivery.state = 'failed' THEN 'available'
|
|
204
|
+
WHEN p_delivery.state = 'pending'
|
|
205
|
+
AND p_delivery.claim_expires_at <= pg_catalog.clock_timestamp()
|
|
206
|
+
AND p_delivery.provider_started_at IS NULL
|
|
207
|
+
AND p_delivery.unresolved_provider_outcome_at IS NULL
|
|
208
|
+
THEN 'available'
|
|
209
|
+
WHEN (
|
|
210
|
+
p_delivery.state = 'outcome_unknown'
|
|
211
|
+
OR (
|
|
212
|
+
p_delivery.state = 'pending'
|
|
213
|
+
AND p_delivery.claim_expires_at <= pg_catalog.clock_timestamp()
|
|
214
|
+
AND (
|
|
215
|
+
p_delivery.provider_started_at IS NOT NULL
|
|
216
|
+
OR p_delivery.unresolved_provider_outcome_at IS NOT NULL
|
|
217
|
+
)
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
THEN CASE
|
|
221
|
+
WHEN p_delivery.provider_retry_safe_until IS NOT NULL
|
|
222
|
+
AND p_delivery.provider_retry_safe_until > pg_catalog.clock_timestamp()
|
|
223
|
+
THEN 'available'
|
|
224
|
+
ELSE 'reconciliation_required'
|
|
225
|
+
END
|
|
226
|
+
ELSE 'unavailable'
|
|
227
|
+
END,
|
|
228
|
+
'sentAt', p_delivery.sent_at,
|
|
229
|
+
'updatedAt', p_delivery.updated_at
|
|
230
|
+
)
|
|
231
|
+
$body$;
|
|
232
|
+
|
|
233
|
+
CREATE OR REPLACE FUNCTION opengeni_private.organization_invitation_row_json(
|
|
234
|
+
p_invitation organization_membership_invitations
|
|
235
|
+
) RETURNS jsonb
|
|
236
|
+
LANGUAGE sql STABLE
|
|
237
|
+
SET search_path FROM CURRENT
|
|
238
|
+
AS $body$
|
|
239
|
+
SELECT pg_catalog.jsonb_build_object(
|
|
240
|
+
'id', p_invitation.id,
|
|
241
|
+
'organizationId', p_invitation.account_id,
|
|
242
|
+
'targetEmail', p_invitation.target_email,
|
|
243
|
+
'targetName', p_invitation.target_name,
|
|
244
|
+
'initialWorkspaceIds', p_invitation.initial_workspace_ids,
|
|
245
|
+
'role', p_invitation.role,
|
|
246
|
+
'status', CASE
|
|
247
|
+
WHEN p_invitation.status = 'pending'
|
|
248
|
+
AND p_invitation.expires_at <= pg_catalog.clock_timestamp()
|
|
249
|
+
THEN 'expired'
|
|
250
|
+
ELSE p_invitation.status
|
|
251
|
+
END,
|
|
252
|
+
'revision', p_invitation.revision,
|
|
253
|
+
'expiresAt', p_invitation.expires_at,
|
|
254
|
+
'acceptedMembershipId', p_invitation.accepted_membership_id,
|
|
255
|
+
'createdAt', p_invitation.created_at,
|
|
256
|
+
'updatedAt', p_invitation.updated_at,
|
|
257
|
+
'delivery', (
|
|
258
|
+
SELECT opengeni_private.organization_user_setup_delivery_json(delivery)
|
|
259
|
+
FROM organization_user_setup_deliveries delivery
|
|
260
|
+
WHERE delivery.account_id = p_invitation.account_id
|
|
261
|
+
AND delivery.invitation_id = p_invitation.id
|
|
262
|
+
)
|
|
263
|
+
)
|
|
264
|
+
$body$;
|
|
265
|
+
|
|
266
|
+
CREATE FUNCTION claim_organization_user_setup_delivery(p_command jsonb)
|
|
267
|
+
RETURNS jsonb
|
|
268
|
+
LANGUAGE plpgsql SECURITY DEFINER
|
|
269
|
+
SET search_path FROM CURRENT
|
|
270
|
+
AS $body$
|
|
271
|
+
DECLARE
|
|
272
|
+
account_id_value uuid := nullif(p_command ->> 'organizationId', '')::uuid;
|
|
273
|
+
actor_subject text := p_command ->> 'actorSubjectId';
|
|
274
|
+
invitation_id_value uuid := nullif(p_command ->> 'invitationId', '')::uuid;
|
|
275
|
+
invitation_operation_id_value uuid := nullif(p_command ->> 'invitationOperationId', '')::uuid;
|
|
276
|
+
operation_id_value uuid := nullif(p_command ->> 'operationId', '')::uuid;
|
|
277
|
+
actor organization_memberships%ROWTYPE;
|
|
278
|
+
invitation organization_membership_invitations%ROWTYPE;
|
|
279
|
+
delivery organization_user_setup_deliveries%ROWTYPE;
|
|
280
|
+
attempt organization_user_setup_delivery_attempts%ROWTYPE;
|
|
281
|
+
normalized_email text;
|
|
282
|
+
workspace_snapshot jsonb;
|
|
283
|
+
now_value timestamptz := clock_timestamp();
|
|
284
|
+
delivery_id_value uuid;
|
|
285
|
+
verified_invitation_operation_id uuid;
|
|
286
|
+
BEGIN
|
|
287
|
+
IF account_id_value IS NULL
|
|
288
|
+
OR account_id_value IS DISTINCT FROM opengeni_private.current_account_id()
|
|
289
|
+
OR actor_subject IS NULL
|
|
290
|
+
OR actor_subject IS DISTINCT FROM opengeni_private.current_subject_id()
|
|
291
|
+
OR invitation_id_value IS NULL
|
|
292
|
+
OR operation_id_value IS NULL
|
|
293
|
+
THEN
|
|
294
|
+
RAISE EXCEPTION 'organization setup delivery authority is invalid' USING ERRCODE = '42501';
|
|
295
|
+
END IF;
|
|
296
|
+
PERFORM set_config(
|
|
297
|
+
'opengeni.organization_tenancy_lifecycle', 'organization_membership_lifecycle', true
|
|
298
|
+
);
|
|
299
|
+
SELECT lower(btrim(candidate.target_email)) INTO normalized_email
|
|
300
|
+
FROM organization_membership_invitations candidate
|
|
301
|
+
WHERE candidate.account_id = account_id_value AND candidate.id = invitation_id_value;
|
|
302
|
+
IF normalized_email IS NULL THEN
|
|
303
|
+
RAISE EXCEPTION 'organization invitation is unavailable' USING ERRCODE = 'P0002';
|
|
304
|
+
END IF;
|
|
305
|
+
PERFORM pg_advisory_xact_lock(
|
|
306
|
+
hashtextextended('organization-invitation-email:' || normalized_email, 0)
|
|
307
|
+
);
|
|
308
|
+
PERFORM pg_advisory_xact_lock(
|
|
309
|
+
hashtextextended('organization-membership:' || account_id_value::text, 0)
|
|
310
|
+
);
|
|
311
|
+
PERFORM 1 FROM managed_accounts account
|
|
312
|
+
WHERE account.id = account_id_value FOR KEY SHARE;
|
|
313
|
+
IF NOT FOUND THEN
|
|
314
|
+
RAISE EXCEPTION 'organization not found' USING ERRCODE = 'P0002';
|
|
315
|
+
END IF;
|
|
316
|
+
SELECT * INTO actor FROM organization_memberships membership
|
|
317
|
+
WHERE membership.account_id = account_id_value
|
|
318
|
+
AND membership.subject_id = actor_subject FOR UPDATE;
|
|
319
|
+
IF NOT FOUND OR actor.status <> 'active' OR actor.role NOT IN ('owner', 'admin') THEN
|
|
320
|
+
RAISE EXCEPTION 'organization administration required' USING ERRCODE = '42501';
|
|
321
|
+
END IF;
|
|
322
|
+
SELECT * INTO invitation FROM organization_membership_invitations candidate
|
|
323
|
+
WHERE candidate.account_id = account_id_value AND candidate.id = invitation_id_value
|
|
324
|
+
FOR UPDATE;
|
|
325
|
+
IF NOT FOUND THEN
|
|
326
|
+
RAISE EXCEPTION 'organization invitation is unavailable' USING ERRCODE = 'P0002';
|
|
327
|
+
END IF;
|
|
328
|
+
|
|
329
|
+
SELECT coalesce(jsonb_agg(jsonb_build_object(
|
|
330
|
+
'workspaceId', workspace.id,
|
|
331
|
+
'workspaceName', workspace.name,
|
|
332
|
+
'role', 'member'
|
|
333
|
+
) ORDER BY lower(workspace.name), workspace.id), '[]'::jsonb)
|
|
334
|
+
INTO workspace_snapshot
|
|
335
|
+
FROM unnest(invitation.initial_workspace_ids) requested(workspace_id)
|
|
336
|
+
JOIN workspaces workspace
|
|
337
|
+
ON workspace.account_id = account_id_value AND workspace.id = requested.workspace_id
|
|
338
|
+
WHERE NOT EXISTS (
|
|
339
|
+
SELECT 1 FROM organization_memberships personal_owner
|
|
340
|
+
WHERE personal_owner.account_id = account_id_value
|
|
341
|
+
AND personal_owner.personal_workspace_id = workspace.id
|
|
342
|
+
);
|
|
343
|
+
IF jsonb_array_length(workspace_snapshot) <> cardinality(invitation.initial_workspace_ids) THEN
|
|
344
|
+
RAISE EXCEPTION 'organization invitation workspace snapshot is invalid'
|
|
345
|
+
USING ERRCODE = '55000';
|
|
346
|
+
END IF;
|
|
347
|
+
|
|
348
|
+
SELECT * INTO delivery FROM organization_user_setup_deliveries candidate
|
|
349
|
+
WHERE candidate.account_id = account_id_value
|
|
350
|
+
AND candidate.invitation_id = invitation_id_value FOR UPDATE;
|
|
351
|
+
IF NOT FOUND THEN
|
|
352
|
+
-- An authorized retry can recover a crash after the invitation commit but
|
|
353
|
+
-- before this journal was created. Resolve the immutable invite receipt
|
|
354
|
+
-- server-side; a supplied operation id must still match it exactly.
|
|
355
|
+
SELECT receipt.operation_id INTO verified_invitation_operation_id
|
|
356
|
+
FROM organization_membership_operation_receipts receipt
|
|
357
|
+
WHERE receipt.account_id = account_id_value
|
|
358
|
+
AND receipt.action = 'invite'
|
|
359
|
+
AND receipt.result ->> 'id' = invitation_id_value::text
|
|
360
|
+
AND (
|
|
361
|
+
invitation_operation_id_value IS NULL
|
|
362
|
+
OR receipt.operation_id = invitation_operation_id_value
|
|
363
|
+
)
|
|
364
|
+
LIMIT 1;
|
|
365
|
+
IF verified_invitation_operation_id IS NULL THEN
|
|
366
|
+
RAISE EXCEPTION 'organization invitation operation is unavailable'
|
|
367
|
+
USING ERRCODE = '42501';
|
|
368
|
+
END IF;
|
|
369
|
+
invitation_operation_id_value := verified_invitation_operation_id;
|
|
370
|
+
delivery_id_value := gen_random_uuid();
|
|
371
|
+
INSERT INTO organization_user_setup_deliveries (
|
|
372
|
+
id, account_id, invitation_id, invitation_operation_id,
|
|
373
|
+
created_by_membership_id, provider_key, recipient_email, recipient_name,
|
|
374
|
+
organization_name, organization_role, shared_workspace_access
|
|
375
|
+
) SELECT
|
|
376
|
+
delivery_id_value, account_id_value, invitation.id, invitation_operation_id_value,
|
|
377
|
+
invitation.created_by_membership_id,
|
|
378
|
+
'opengeni-organization-setup-v1-' || delivery_id_value::text,
|
|
379
|
+
normalized_email, invitation.target_name, account.name, invitation.role,
|
|
380
|
+
workspace_snapshot
|
|
381
|
+
FROM managed_accounts account WHERE account.id = account_id_value
|
|
382
|
+
RETURNING * INTO delivery;
|
|
383
|
+
ELSIF invitation_operation_id_value IS NOT NULL
|
|
384
|
+
AND delivery.invitation_operation_id IS DISTINCT FROM invitation_operation_id_value
|
|
385
|
+
THEN
|
|
386
|
+
RAISE EXCEPTION 'organization setup delivery identity changed' USING ERRCODE = '23505';
|
|
387
|
+
END IF;
|
|
388
|
+
|
|
389
|
+
-- Reconcile an expired lease before returning a terminal invitation. An
|
|
390
|
+
-- invitation can expire or be accepted after a worker has claimed it; once
|
|
391
|
+
-- that lease expires, leaving the attempt open would permanently retain a
|
|
392
|
+
-- live-looking claim that no administrator can safely recover.
|
|
393
|
+
IF delivery.claim_holder_id IS NOT NULL AND delivery.claim_expires_at <= now_value THEN
|
|
394
|
+
UPDATE organization_user_setup_delivery_attempts SET
|
|
395
|
+
result = CASE WHEN provider_started_at IS NULL THEN 'failed' ELSE 'outcome_unknown' END,
|
|
396
|
+
error_class = CASE
|
|
397
|
+
WHEN provider_started_at IS NULL THEN 'claim_expired_before_provider'
|
|
398
|
+
ELSE 'provider_started_claim_expired'
|
|
399
|
+
END,
|
|
400
|
+
settled_at = now_value
|
|
401
|
+
WHERE delivery_id = delivery.id AND claim_holder_id = delivery.claim_holder_id
|
|
402
|
+
AND result IS NULL;
|
|
403
|
+
UPDATE organization_user_setup_deliveries SET
|
|
404
|
+
state = CASE
|
|
405
|
+
WHEN provider_started_at IS NULL AND unresolved_provider_outcome_at IS NULL
|
|
406
|
+
THEN 'failed'
|
|
407
|
+
ELSE 'outcome_unknown'
|
|
408
|
+
END,
|
|
409
|
+
failed_at = CASE
|
|
410
|
+
WHEN provider_started_at IS NULL AND unresolved_provider_outcome_at IS NULL
|
|
411
|
+
THEN now_value
|
|
412
|
+
ELSE NULL
|
|
413
|
+
END,
|
|
414
|
+
outcome_unknown_at = CASE
|
|
415
|
+
WHEN provider_started_at IS NOT NULL OR unresolved_provider_outcome_at IS NOT NULL
|
|
416
|
+
THEN coalesce(unresolved_provider_outcome_at, now_value)
|
|
417
|
+
ELSE NULL
|
|
418
|
+
END,
|
|
419
|
+
unresolved_provider_outcome_at = CASE
|
|
420
|
+
WHEN provider_started_at IS NOT NULL
|
|
421
|
+
THEN coalesce(unresolved_provider_outcome_at, now_value)
|
|
422
|
+
ELSE unresolved_provider_outcome_at
|
|
423
|
+
END,
|
|
424
|
+
error_class = CASE
|
|
425
|
+
WHEN provider_started_at IS NULL AND unresolved_provider_outcome_at IS NULL
|
|
426
|
+
THEN 'claim_expired_before_provider'
|
|
427
|
+
WHEN provider_started_at IS NULL THEN 'prior_provider_outcome_unresolved'
|
|
428
|
+
ELSE 'provider_started_claim_expired'
|
|
429
|
+
END,
|
|
430
|
+
claim_holder_id = NULL, claim_expires_at = NULL,
|
|
431
|
+
revision = revision + 1, updated_at = now_value
|
|
432
|
+
WHERE id = delivery.id RETURNING * INTO delivery;
|
|
433
|
+
END IF;
|
|
434
|
+
|
|
435
|
+
IF invitation.status <> 'pending' OR invitation.expires_at <= now_value THEN
|
|
436
|
+
IF invitation.status = 'revoked' AND delivery.state <> 'revoked' THEN
|
|
437
|
+
UPDATE organization_user_setup_deliveries SET
|
|
438
|
+
state = 'revoked', revision = revision + 1, revoked_at = now_value,
|
|
439
|
+
claim_holder_id = NULL, claim_expires_at = NULL,
|
|
440
|
+
unresolved_provider_outcome_at = NULL,
|
|
441
|
+
error_class = NULL, updated_at = now_value
|
|
442
|
+
WHERE id = delivery.id RETURNING * INTO delivery;
|
|
443
|
+
END IF;
|
|
444
|
+
RETURN jsonb_build_object(
|
|
445
|
+
'claimed', false,
|
|
446
|
+
'delivery', opengeni_private.organization_user_setup_delivery_json(delivery)
|
|
447
|
+
);
|
|
448
|
+
END IF;
|
|
449
|
+
IF delivery.state IN ('sent', 'revoked') THEN
|
|
450
|
+
RETURN jsonb_build_object(
|
|
451
|
+
'claimed', false,
|
|
452
|
+
'delivery', opengeni_private.organization_user_setup_delivery_json(delivery)
|
|
453
|
+
);
|
|
454
|
+
END IF;
|
|
455
|
+
|
|
456
|
+
SELECT * INTO attempt FROM organization_user_setup_delivery_attempts candidate
|
|
457
|
+
WHERE candidate.delivery_id = delivery.id AND candidate.operation_id = operation_id_value;
|
|
458
|
+
IF FOUND THEN
|
|
459
|
+
RETURN jsonb_build_object(
|
|
460
|
+
'claimed', false,
|
|
461
|
+
'delivery', opengeni_private.organization_user_setup_delivery_json(delivery),
|
|
462
|
+
'attemptId', attempt.id,
|
|
463
|
+
'claimHolderId', attempt.claim_holder_id,
|
|
464
|
+
'invitationId', delivery.invitation_id,
|
|
465
|
+
'providerKey', delivery.provider_key,
|
|
466
|
+
'recipientEmail', delivery.recipient_email,
|
|
467
|
+
'recipientName', delivery.recipient_name,
|
|
468
|
+
'organizationName', delivery.organization_name,
|
|
469
|
+
'organizationRole', delivery.organization_role,
|
|
470
|
+
'sharedWorkspaceAccess', delivery.shared_workspace_access,
|
|
471
|
+
'expiresAt', invitation.expires_at
|
|
472
|
+
);
|
|
473
|
+
END IF;
|
|
474
|
+
|
|
475
|
+
IF delivery.unresolved_provider_outcome_at IS NOT NULL
|
|
476
|
+
AND (
|
|
477
|
+
delivery.provider_retry_safe_until IS NULL
|
|
478
|
+
OR delivery.provider_retry_safe_until <= now_value
|
|
479
|
+
)
|
|
480
|
+
THEN
|
|
481
|
+
RAISE EXCEPTION 'organization setup delivery requires provider reconciliation'
|
|
482
|
+
USING ERRCODE = '55000';
|
|
483
|
+
END IF;
|
|
484
|
+
|
|
485
|
+
IF delivery.claim_holder_id IS NOT NULL THEN
|
|
486
|
+
RAISE EXCEPTION 'organization setup delivery is already claimed' USING ERRCODE = '55000';
|
|
487
|
+
END IF;
|
|
488
|
+
|
|
489
|
+
INSERT INTO organization_user_setup_delivery_attempts (
|
|
490
|
+
account_id, delivery_id, operation_id, attempt_number,
|
|
491
|
+
claim_holder_id, claim_expires_at
|
|
492
|
+
) VALUES (
|
|
493
|
+
account_id_value, delivery.id, operation_id_value, delivery.attempt_count + 1,
|
|
494
|
+
gen_random_uuid(), now_value + interval '2 minutes'
|
|
495
|
+
) RETURNING * INTO attempt;
|
|
496
|
+
UPDATE organization_user_setup_deliveries SET
|
|
497
|
+
state = 'pending', revision = revision + 1,
|
|
498
|
+
claim_holder_id = attempt.claim_holder_id,
|
|
499
|
+
claim_expires_at = attempt.claim_expires_at,
|
|
500
|
+
attempt_count = attempt.attempt_number,
|
|
501
|
+
provider_started_at = NULL, error_class = NULL, provider_message_id = NULL,
|
|
502
|
+
sent_at = NULL, failed_at = NULL, outcome_unknown_at = NULL, revoked_at = NULL,
|
|
503
|
+
updated_at = now_value
|
|
504
|
+
WHERE id = delivery.id RETURNING * INTO delivery;
|
|
505
|
+
RETURN jsonb_build_object(
|
|
506
|
+
'claimed', true,
|
|
507
|
+
'delivery', opengeni_private.organization_user_setup_delivery_json(delivery),
|
|
508
|
+
'attemptId', attempt.id,
|
|
509
|
+
'claimHolderId', attempt.claim_holder_id,
|
|
510
|
+
'invitationId', delivery.invitation_id,
|
|
511
|
+
'providerKey', delivery.provider_key,
|
|
512
|
+
'recipientEmail', delivery.recipient_email,
|
|
513
|
+
'recipientName', delivery.recipient_name,
|
|
514
|
+
'organizationName', delivery.organization_name,
|
|
515
|
+
'organizationRole', delivery.organization_role,
|
|
516
|
+
'sharedWorkspaceAccess', delivery.shared_workspace_access,
|
|
517
|
+
'expiresAt', invitation.expires_at
|
|
518
|
+
);
|
|
519
|
+
END
|
|
520
|
+
$body$;
|
|
521
|
+
|
|
522
|
+
CREATE FUNCTION prepare_organization_user_setup_delivery(p_command jsonb)
|
|
523
|
+
RETURNS jsonb
|
|
524
|
+
LANGUAGE plpgsql SECURITY DEFINER
|
|
525
|
+
SET search_path FROM CURRENT
|
|
526
|
+
AS $body$
|
|
527
|
+
DECLARE
|
|
528
|
+
delivery_id_value uuid := nullif(p_command ->> 'deliveryId', '')::uuid;
|
|
529
|
+
attempt_id_value uuid := nullif(p_command ->> 'attemptId', '')::uuid;
|
|
530
|
+
claim_holder_id_value uuid := nullif(p_command ->> 'claimHolderId', '')::uuid;
|
|
531
|
+
actor_subject text := p_command ->> 'actorSubjectId';
|
|
532
|
+
token_digest_value text := p_command ->> 'tokenDigest';
|
|
533
|
+
payload_digest_value text := p_command ->> 'payloadDigest';
|
|
534
|
+
provider_idempotency_scope_value text := btrim(p_command ->> 'providerIdempotencyScope');
|
|
535
|
+
provider_idempotency_retention_seconds_value integer :=
|
|
536
|
+
nullif(p_command ->> 'providerIdempotencyRetentionSeconds', '')::integer;
|
|
537
|
+
delivery organization_user_setup_deliveries%ROWTYPE;
|
|
538
|
+
invitation organization_membership_invitations%ROWTYPE;
|
|
539
|
+
setup organization_user_setup_intents%ROWTYPE;
|
|
540
|
+
normalized_email text;
|
|
541
|
+
account_id_value uuid;
|
|
542
|
+
invitation_id_value uuid;
|
|
543
|
+
now_value timestamptz := clock_timestamp();
|
|
544
|
+
BEGIN
|
|
545
|
+
IF delivery_id_value IS NULL OR attempt_id_value IS NULL OR claim_holder_id_value IS NULL
|
|
546
|
+
OR actor_subject IS NULL OR actor_subject IS DISTINCT FROM opengeni_private.current_subject_id()
|
|
547
|
+
OR token_digest_value !~ '^[0-9a-f]{64}$'
|
|
548
|
+
OR payload_digest_value !~ '^[0-9a-f]{64}$'
|
|
549
|
+
OR provider_idempotency_scope_value IS NULL
|
|
550
|
+
OR provider_idempotency_scope_value !~ '^[a-z0-9][a-z0-9:._-]*$'
|
|
551
|
+
OR octet_length(convert_to(provider_idempotency_scope_value, 'UTF8')) > 200
|
|
552
|
+
OR provider_idempotency_retention_seconds_value IS NULL
|
|
553
|
+
OR provider_idempotency_retention_seconds_value NOT BETWEEN 0 AND 31536000
|
|
554
|
+
THEN
|
|
555
|
+
RAISE EXCEPTION 'organization setup delivery preparation is invalid' USING ERRCODE = '42501';
|
|
556
|
+
END IF;
|
|
557
|
+
PERFORM set_config(
|
|
558
|
+
'opengeni.organization_tenancy_lifecycle', 'organization_membership_lifecycle', true
|
|
559
|
+
);
|
|
560
|
+
SELECT recipient_email, account_id, invitation_id
|
|
561
|
+
INTO normalized_email, account_id_value, invitation_id_value
|
|
562
|
+
FROM organization_user_setup_deliveries WHERE id = delivery_id_value;
|
|
563
|
+
IF normalized_email IS NULL THEN
|
|
564
|
+
RAISE EXCEPTION 'organization setup delivery is unavailable' USING ERRCODE = 'P0002';
|
|
565
|
+
END IF;
|
|
566
|
+
IF account_id_value IS DISTINCT FROM opengeni_private.current_account_id() THEN
|
|
567
|
+
RAISE EXCEPTION 'organization setup delivery authority is invalid' USING ERRCODE = '42501';
|
|
568
|
+
END IF;
|
|
569
|
+
PERFORM pg_advisory_xact_lock(hashtextextended(
|
|
570
|
+
'organization-invitation-email:' || normalized_email, 0
|
|
571
|
+
));
|
|
572
|
+
PERFORM pg_advisory_xact_lock(hashtextextended(
|
|
573
|
+
'organization-membership:' || account_id_value::text, 0
|
|
574
|
+
));
|
|
575
|
+
PERFORM 1 FROM managed_accounts account WHERE account.id = account_id_value FOR KEY SHARE;
|
|
576
|
+
PERFORM 1 FROM organization_memberships membership
|
|
577
|
+
WHERE membership.account_id = account_id_value AND membership.subject_id = actor_subject
|
|
578
|
+
AND membership.status = 'active' AND membership.role IN ('owner', 'admin') FOR UPDATE;
|
|
579
|
+
IF NOT FOUND THEN
|
|
580
|
+
RAISE EXCEPTION 'organization administration required' USING ERRCODE = '42501';
|
|
581
|
+
END IF;
|
|
582
|
+
SELECT * INTO invitation FROM organization_membership_invitations candidate
|
|
583
|
+
WHERE candidate.account_id = account_id_value AND candidate.id = invitation_id_value
|
|
584
|
+
FOR UPDATE;
|
|
585
|
+
IF NOT FOUND THEN
|
|
586
|
+
RAISE EXCEPTION 'organization invitation is unavailable' USING ERRCODE = 'P0002';
|
|
587
|
+
END IF;
|
|
588
|
+
SELECT * INTO delivery FROM organization_user_setup_deliveries candidate
|
|
589
|
+
WHERE candidate.account_id = account_id_value
|
|
590
|
+
AND candidate.id = delivery_id_value
|
|
591
|
+
AND candidate.invitation_id = invitation.id
|
|
592
|
+
FOR UPDATE;
|
|
593
|
+
IF NOT FOUND THEN
|
|
594
|
+
RAISE EXCEPTION 'organization setup delivery is unavailable' USING ERRCODE = 'P0002';
|
|
595
|
+
END IF;
|
|
596
|
+
IF delivery.state <> 'pending'
|
|
597
|
+
OR delivery.claim_holder_id IS DISTINCT FROM claim_holder_id_value
|
|
598
|
+
OR delivery.claim_expires_at <= now_value
|
|
599
|
+
OR invitation.status <> 'pending'
|
|
600
|
+
OR invitation.expires_at <= now_value
|
|
601
|
+
THEN
|
|
602
|
+
RAISE EXCEPTION 'organization setup delivery claim is unavailable' USING ERRCODE = '55000';
|
|
603
|
+
END IF;
|
|
604
|
+
PERFORM 1 FROM organization_user_setup_delivery_attempts attempt
|
|
605
|
+
WHERE attempt.id = attempt_id_value AND attempt.delivery_id = delivery.id
|
|
606
|
+
AND attempt.claim_holder_id = claim_holder_id_value AND attempt.result IS NULL
|
|
607
|
+
FOR UPDATE;
|
|
608
|
+
IF NOT FOUND THEN
|
|
609
|
+
RAISE EXCEPTION 'organization setup delivery attempt is unavailable' USING ERRCODE = '55000';
|
|
610
|
+
END IF;
|
|
611
|
+
IF delivery.token_digest IS NOT NULL
|
|
612
|
+
AND delivery.token_digest IS DISTINCT FROM token_digest_value
|
|
613
|
+
THEN
|
|
614
|
+
RAISE EXCEPTION 'organization setup bearer changed under retry' USING ERRCODE = '23505';
|
|
615
|
+
END IF;
|
|
616
|
+
IF delivery.provider_idempotency_scope IS NOT NULL
|
|
617
|
+
AND delivery.provider_idempotency_scope IS DISTINCT FROM provider_idempotency_scope_value
|
|
618
|
+
THEN
|
|
619
|
+
RAISE EXCEPTION 'organization setup provider scope changed under retry'
|
|
620
|
+
USING ERRCODE = '23505';
|
|
621
|
+
END IF;
|
|
622
|
+
IF delivery.payload_digest IS NOT NULL
|
|
623
|
+
AND delivery.payload_digest IS DISTINCT FROM payload_digest_value
|
|
624
|
+
THEN
|
|
625
|
+
RAISE EXCEPTION 'organization setup payload changed under retry' USING ERRCODE = '23505';
|
|
626
|
+
END IF;
|
|
627
|
+
IF delivery.unresolved_provider_outcome_at IS NOT NULL
|
|
628
|
+
AND (
|
|
629
|
+
delivery.provider_retry_safe_until IS NULL
|
|
630
|
+
OR delivery.provider_retry_safe_until <= now_value
|
|
631
|
+
)
|
|
632
|
+
THEN
|
|
633
|
+
RAISE EXCEPTION 'organization setup delivery requires provider reconciliation'
|
|
634
|
+
USING ERRCODE = '55000';
|
|
635
|
+
END IF;
|
|
636
|
+
-- The public 0348 seam remains creator-bound. A later active administrator
|
|
637
|
+
-- may retry this creator-bound durable delivery, so create its immutable
|
|
638
|
+
-- setup intent only after the invitation -> delivery -> attempt locks above.
|
|
639
|
+
INSERT INTO organization_user_setup_intents (
|
|
640
|
+
account_id, invitation_id, token_digest, expires_at
|
|
641
|
+
) VALUES (
|
|
642
|
+
delivery.account_id, delivery.invitation_id, token_digest_value, invitation.expires_at
|
|
643
|
+
) ON CONFLICT (account_id, invitation_id) DO NOTHING;
|
|
644
|
+
SELECT * INTO setup FROM organization_user_setup_intents candidate
|
|
645
|
+
WHERE candidate.account_id = delivery.account_id
|
|
646
|
+
AND candidate.invitation_id = delivery.invitation_id;
|
|
647
|
+
IF NOT FOUND
|
|
648
|
+
OR setup.token_digest IS DISTINCT FROM token_digest_value
|
|
649
|
+
OR setup.expires_at IS DISTINCT FROM invitation.expires_at
|
|
650
|
+
THEN
|
|
651
|
+
RAISE EXCEPTION 'organization user setup intent changed under retry'
|
|
652
|
+
USING ERRCODE = '23505';
|
|
653
|
+
END IF;
|
|
654
|
+
UPDATE organization_user_setup_deliveries SET
|
|
655
|
+
setup_intent_id = setup.id,
|
|
656
|
+
token_digest = token_digest_value,
|
|
657
|
+
payload_digest = payload_digest_value,
|
|
658
|
+
provider_idempotency_scope = coalesce(
|
|
659
|
+
provider_idempotency_scope, provider_idempotency_scope_value
|
|
660
|
+
),
|
|
661
|
+
provider_retry_safe_until = CASE
|
|
662
|
+
WHEN unresolved_provider_outcome_at IS NULL
|
|
663
|
+
THEN now_value + make_interval(secs => provider_idempotency_retention_seconds_value)
|
|
664
|
+
ELSE provider_retry_safe_until
|
|
665
|
+
END,
|
|
666
|
+
provider_retry_window_started_at = CASE
|
|
667
|
+
WHEN unresolved_provider_outcome_at IS NULL THEN now_value
|
|
668
|
+
ELSE coalesce(provider_retry_window_started_at, now_value)
|
|
669
|
+
END,
|
|
670
|
+
provider_started_at = now_value,
|
|
671
|
+
revision = revision + 1,
|
|
672
|
+
updated_at = now_value
|
|
673
|
+
WHERE id = delivery.id RETURNING * INTO delivery;
|
|
674
|
+
UPDATE organization_user_setup_delivery_attempts SET provider_started_at = now_value
|
|
675
|
+
WHERE id = attempt_id_value;
|
|
676
|
+
RETURN jsonb_build_object(
|
|
677
|
+
'providerKey', delivery.provider_key,
|
|
678
|
+
'recipientEmail', delivery.recipient_email,
|
|
679
|
+
'recipientName', delivery.recipient_name,
|
|
680
|
+
'organizationName', delivery.organization_name,
|
|
681
|
+
'organizationRole', delivery.organization_role,
|
|
682
|
+
'sharedWorkspaceAccess', delivery.shared_workspace_access
|
|
683
|
+
);
|
|
684
|
+
END
|
|
685
|
+
$body$;
|
|
686
|
+
|
|
687
|
+
CREATE FUNCTION settle_organization_user_setup_delivery(p_command jsonb)
|
|
688
|
+
RETURNS jsonb
|
|
689
|
+
LANGUAGE plpgsql SECURITY DEFINER
|
|
690
|
+
SET search_path FROM CURRENT
|
|
691
|
+
AS $body$
|
|
692
|
+
DECLARE
|
|
693
|
+
delivery_id_value uuid := nullif(p_command ->> 'deliveryId', '')::uuid;
|
|
694
|
+
attempt_id_value uuid := nullif(p_command ->> 'attemptId', '')::uuid;
|
|
695
|
+
claim_holder_id_value uuid := nullif(p_command ->> 'claimHolderId', '')::uuid;
|
|
696
|
+
actor_subject text := p_command ->> 'actorSubjectId';
|
|
697
|
+
outcome_value text := p_command ->> 'outcome';
|
|
698
|
+
error_class_value text := nullif(btrim(p_command ->> 'errorClass'), '');
|
|
699
|
+
provider_message_id_value text := nullif(btrim(p_command ->> 'providerMessageId'), '');
|
|
700
|
+
delivery organization_user_setup_deliveries%ROWTYPE;
|
|
701
|
+
invitation organization_membership_invitations%ROWTYPE;
|
|
702
|
+
now_value timestamptz := clock_timestamp();
|
|
703
|
+
BEGIN
|
|
704
|
+
IF delivery_id_value IS NULL OR attempt_id_value IS NULL OR claim_holder_id_value IS NULL
|
|
705
|
+
OR actor_subject IS NULL OR actor_subject IS DISTINCT FROM opengeni_private.current_subject_id()
|
|
706
|
+
OR outcome_value NOT IN ('sent', 'failed', 'outcome_unknown')
|
|
707
|
+
OR (outcome_value <> 'sent' AND (
|
|
708
|
+
error_class_value IS NULL OR octet_length(convert_to(error_class_value, 'UTF8')) > 64
|
|
709
|
+
))
|
|
710
|
+
OR (provider_message_id_value IS NOT NULL
|
|
711
|
+
AND octet_length(convert_to(provider_message_id_value, 'UTF8')) > 255)
|
|
712
|
+
THEN
|
|
713
|
+
RAISE EXCEPTION 'organization setup delivery settlement is invalid' USING ERRCODE = '22023';
|
|
714
|
+
END IF;
|
|
715
|
+
PERFORM set_config(
|
|
716
|
+
'opengeni.organization_tenancy_lifecycle', 'organization_membership_lifecycle', true
|
|
717
|
+
);
|
|
718
|
+
SELECT * INTO delivery FROM organization_user_setup_deliveries candidate
|
|
719
|
+
WHERE candidate.id = delivery_id_value;
|
|
720
|
+
IF NOT FOUND THEN
|
|
721
|
+
RAISE EXCEPTION 'organization setup delivery is unavailable' USING ERRCODE = 'P0002';
|
|
722
|
+
END IF;
|
|
723
|
+
IF delivery.account_id IS DISTINCT FROM opengeni_private.current_account_id() THEN
|
|
724
|
+
RAISE EXCEPTION 'organization setup delivery authority is invalid' USING ERRCODE = '42501';
|
|
725
|
+
END IF;
|
|
726
|
+
PERFORM pg_advisory_xact_lock(hashtextextended(
|
|
727
|
+
'organization-invitation-email:' || delivery.recipient_email, 0
|
|
728
|
+
));
|
|
729
|
+
PERFORM pg_advisory_xact_lock(hashtextextended(
|
|
730
|
+
'organization-membership:' || delivery.account_id::text, 0
|
|
731
|
+
));
|
|
732
|
+
PERFORM 1 FROM managed_accounts account WHERE account.id = delivery.account_id FOR KEY SHARE;
|
|
733
|
+
PERFORM 1 FROM organization_memberships membership
|
|
734
|
+
WHERE membership.account_id = delivery.account_id AND membership.subject_id = actor_subject
|
|
735
|
+
AND membership.status = 'active' AND membership.role IN ('owner', 'admin') FOR UPDATE;
|
|
736
|
+
IF NOT FOUND THEN
|
|
737
|
+
RAISE EXCEPTION 'organization administration required' USING ERRCODE = '42501';
|
|
738
|
+
END IF;
|
|
739
|
+
SELECT * INTO invitation FROM organization_membership_invitations candidate
|
|
740
|
+
WHERE candidate.account_id = delivery.account_id AND candidate.id = delivery.invitation_id
|
|
741
|
+
FOR UPDATE;
|
|
742
|
+
SELECT * INTO delivery FROM organization_user_setup_deliveries candidate
|
|
743
|
+
WHERE candidate.id = delivery_id_value FOR UPDATE;
|
|
744
|
+
IF delivery.state = 'revoked' AND delivery.claim_holder_id IS NULL THEN
|
|
745
|
+
PERFORM 1 FROM organization_user_setup_delivery_attempts attempt
|
|
746
|
+
WHERE attempt.id = attempt_id_value AND attempt.delivery_id = delivery.id
|
|
747
|
+
AND attempt.claim_holder_id = claim_holder_id_value AND attempt.result = 'revoked';
|
|
748
|
+
IF FOUND THEN
|
|
749
|
+
RETURN opengeni_private.organization_user_setup_delivery_json(delivery);
|
|
750
|
+
END IF;
|
|
751
|
+
END IF;
|
|
752
|
+
IF delivery.claim_holder_id IS DISTINCT FROM claim_holder_id_value THEN
|
|
753
|
+
RAISE EXCEPTION 'organization setup delivery claim changed' USING ERRCODE = '40001';
|
|
754
|
+
END IF;
|
|
755
|
+
PERFORM 1 FROM organization_user_setup_delivery_attempts attempt
|
|
756
|
+
WHERE attempt.id = attempt_id_value AND attempt.delivery_id = delivery.id
|
|
757
|
+
AND attempt.claim_holder_id = claim_holder_id_value AND attempt.result IS NULL
|
|
758
|
+
AND attempt.provider_started_at IS NOT NULL
|
|
759
|
+
FOR UPDATE;
|
|
760
|
+
IF NOT FOUND OR delivery.provider_started_at IS NULL THEN
|
|
761
|
+
RAISE EXCEPTION 'organization setup delivery attempt changed' USING ERRCODE = '40001';
|
|
762
|
+
END IF;
|
|
763
|
+
UPDATE organization_user_setup_delivery_attempts SET
|
|
764
|
+
result = CASE WHEN invitation.status = 'pending' THEN outcome_value ELSE 'revoked' END,
|
|
765
|
+
error_class = error_class_value,
|
|
766
|
+
provider_message_id = provider_message_id_value,
|
|
767
|
+
settled_at = now_value
|
|
768
|
+
WHERE id = attempt_id_value;
|
|
769
|
+
IF invitation.status <> 'pending' OR delivery.state = 'revoked' THEN
|
|
770
|
+
UPDATE organization_user_setup_deliveries SET
|
|
771
|
+
state = 'revoked', revoked_at = coalesce(revoked_at, now_value),
|
|
772
|
+
unresolved_provider_outcome_at = NULL,
|
|
773
|
+
claim_holder_id = NULL, claim_expires_at = NULL,
|
|
774
|
+
revision = revision + 1, updated_at = now_value
|
|
775
|
+
WHERE id = delivery.id RETURNING * INTO delivery;
|
|
776
|
+
ELSE
|
|
777
|
+
UPDATE organization_user_setup_deliveries SET
|
|
778
|
+
state = outcome_value,
|
|
779
|
+
error_class = CASE WHEN outcome_value = 'sent' THEN NULL ELSE error_class_value END,
|
|
780
|
+
provider_message_id = provider_message_id_value,
|
|
781
|
+
sent_at = CASE WHEN outcome_value = 'sent' THEN now_value ELSE NULL END,
|
|
782
|
+
failed_at = CASE WHEN outcome_value = 'failed' THEN now_value ELSE NULL END,
|
|
783
|
+
outcome_unknown_at = CASE WHEN outcome_value = 'outcome_unknown' THEN now_value ELSE NULL END,
|
|
784
|
+
unresolved_provider_outcome_at = CASE
|
|
785
|
+
WHEN outcome_value = 'outcome_unknown'
|
|
786
|
+
THEN coalesce(unresolved_provider_outcome_at, now_value)
|
|
787
|
+
ELSE NULL
|
|
788
|
+
END,
|
|
789
|
+
revoked_at = NULL,
|
|
790
|
+
claim_holder_id = NULL, claim_expires_at = NULL,
|
|
791
|
+
revision = revision + 1, updated_at = now_value
|
|
792
|
+
WHERE id = delivery.id RETURNING * INTO delivery;
|
|
793
|
+
END IF;
|
|
794
|
+
RETURN opengeni_private.organization_user_setup_delivery_json(delivery);
|
|
795
|
+
END
|
|
796
|
+
$body$;
|
|
797
|
+
|
|
798
|
+
CREATE FUNCTION get_organization_invitation_for_administration(
|
|
799
|
+
p_account_id uuid,
|
|
800
|
+
p_actor_subject_id text,
|
|
801
|
+
p_invitation_id uuid
|
|
802
|
+
) RETURNS jsonb
|
|
803
|
+
LANGUAGE plpgsql SECURITY DEFINER
|
|
804
|
+
SET search_path FROM CURRENT
|
|
805
|
+
AS $body$
|
|
806
|
+
DECLARE
|
|
807
|
+
invitation organization_membership_invitations%ROWTYPE;
|
|
808
|
+
BEGIN
|
|
809
|
+
IF p_account_id IS NULL
|
|
810
|
+
OR p_account_id IS DISTINCT FROM opengeni_private.current_account_id()
|
|
811
|
+
OR p_actor_subject_id IS NULL
|
|
812
|
+
OR p_actor_subject_id IS DISTINCT FROM opengeni_private.current_subject_id()
|
|
813
|
+
OR p_invitation_id IS NULL
|
|
814
|
+
THEN
|
|
815
|
+
RAISE EXCEPTION 'organization invitation authority is invalid' USING ERRCODE = '42501';
|
|
816
|
+
END IF;
|
|
817
|
+
PERFORM set_config(
|
|
818
|
+
'opengeni.organization_tenancy_lifecycle', 'organization_membership_lifecycle', true
|
|
819
|
+
);
|
|
820
|
+
PERFORM 1 FROM organization_memberships membership
|
|
821
|
+
WHERE membership.account_id = p_account_id
|
|
822
|
+
AND membership.subject_id = p_actor_subject_id
|
|
823
|
+
AND membership.status = 'active'
|
|
824
|
+
AND membership.role IN ('owner', 'admin');
|
|
825
|
+
IF NOT FOUND THEN
|
|
826
|
+
RAISE EXCEPTION 'organization administration required' USING ERRCODE = '42501';
|
|
827
|
+
END IF;
|
|
828
|
+
SELECT * INTO invitation FROM organization_membership_invitations candidate
|
|
829
|
+
WHERE candidate.account_id = p_account_id AND candidate.id = p_invitation_id;
|
|
830
|
+
IF NOT FOUND THEN
|
|
831
|
+
RAISE EXCEPTION 'organization invitation is unavailable' USING ERRCODE = 'P0002';
|
|
832
|
+
END IF;
|
|
833
|
+
RETURN opengeni_private.organization_invitation_row_json(invitation);
|
|
834
|
+
END
|
|
835
|
+
$body$;
|
|
836
|
+
|
|
837
|
+
CREATE FUNCTION preview_organization_user_setup(p_token_digest text)
|
|
838
|
+
RETURNS jsonb
|
|
839
|
+
LANGUAGE plpgsql SECURITY DEFINER
|
|
840
|
+
SET search_path FROM CURRENT
|
|
841
|
+
AS $body$
|
|
842
|
+
DECLARE
|
|
843
|
+
delivery organization_user_setup_deliveries%ROWTYPE;
|
|
844
|
+
invitation organization_membership_invitations%ROWTYPE;
|
|
845
|
+
setup organization_user_setup_intents%ROWTYPE;
|
|
846
|
+
BEGIN
|
|
847
|
+
IF p_token_digest IS NULL OR p_token_digest !~ '^[0-9a-f]{64}$' THEN
|
|
848
|
+
RETURN jsonb_build_object('state', 'unavailable');
|
|
849
|
+
END IF;
|
|
850
|
+
PERFORM set_config(
|
|
851
|
+
'opengeni.organization_tenancy_lifecycle', 'organization_membership_lifecycle', true
|
|
852
|
+
);
|
|
853
|
+
SELECT * INTO delivery FROM organization_user_setup_deliveries candidate
|
|
854
|
+
WHERE candidate.token_digest = p_token_digest;
|
|
855
|
+
IF NOT FOUND THEN RETURN jsonb_build_object('state', 'unavailable'); END IF;
|
|
856
|
+
SELECT * INTO setup FROM organization_user_setup_intents candidate
|
|
857
|
+
WHERE candidate.id = delivery.setup_intent_id;
|
|
858
|
+
IF NOT FOUND THEN RETURN jsonb_build_object('state', 'unavailable'); END IF;
|
|
859
|
+
IF setup.status = 'completed' THEN
|
|
860
|
+
RETURN jsonb_build_object('state', 'completed');
|
|
861
|
+
END IF;
|
|
862
|
+
SELECT * INTO invitation FROM organization_membership_invitations candidate
|
|
863
|
+
WHERE candidate.account_id = delivery.account_id AND candidate.id = delivery.invitation_id;
|
|
864
|
+
IF NOT FOUND THEN RETURN jsonb_build_object('state', 'unavailable'); END IF;
|
|
865
|
+
IF invitation.status = 'revoked' OR delivery.state = 'revoked' THEN
|
|
866
|
+
RETURN jsonb_build_object('state', 'revoked');
|
|
867
|
+
END IF;
|
|
868
|
+
IF invitation.status = 'accepted' THEN RETURN jsonb_build_object('state', 'completed'); END IF;
|
|
869
|
+
IF invitation.status <> 'pending' THEN RETURN jsonb_build_object('state', 'unavailable'); END IF;
|
|
870
|
+
IF invitation.expires_at <= clock_timestamp() OR setup.expires_at <= clock_timestamp() THEN
|
|
871
|
+
RETURN jsonb_build_object('state', 'expired');
|
|
872
|
+
END IF;
|
|
873
|
+
RETURN jsonb_build_object(
|
|
874
|
+
'state', 'pending',
|
|
875
|
+
'organizationId', delivery.account_id,
|
|
876
|
+
'organizationName', delivery.organization_name,
|
|
877
|
+
'targetEmail', delivery.recipient_email,
|
|
878
|
+
'targetName', delivery.recipient_name,
|
|
879
|
+
'organizationRole', delivery.organization_role,
|
|
880
|
+
'sharedWorkspaceAccess', delivery.shared_workspace_access,
|
|
881
|
+
'expiresAt', invitation.expires_at
|
|
882
|
+
);
|
|
883
|
+
END
|
|
884
|
+
$body$;
|
|
885
|
+
|
|
886
|
+
CREATE FUNCTION sync_organization_user_setup_delivery_revocation()
|
|
887
|
+
RETURNS trigger
|
|
888
|
+
LANGUAGE plpgsql
|
|
889
|
+
SET search_path FROM CURRENT
|
|
890
|
+
AS $body$
|
|
891
|
+
DECLARE
|
|
892
|
+
delivery organization_user_setup_deliveries%ROWTYPE;
|
|
893
|
+
now_value timestamptz := clock_timestamp();
|
|
894
|
+
BEGIN
|
|
895
|
+
IF NEW.status = 'revoked' AND OLD.status IS DISTINCT FROM NEW.status THEN
|
|
896
|
+
SELECT * INTO delivery FROM organization_user_setup_deliveries candidate
|
|
897
|
+
WHERE candidate.account_id = NEW.account_id AND candidate.invitation_id = NEW.id
|
|
898
|
+
FOR UPDATE;
|
|
899
|
+
IF NOT FOUND THEN RETURN NULL; END IF;
|
|
900
|
+
UPDATE organization_user_setup_delivery_attempts SET
|
|
901
|
+
result = 'revoked', settled_at = now_value
|
|
902
|
+
WHERE delivery_id = delivery.id
|
|
903
|
+
AND claim_holder_id = delivery.claim_holder_id
|
|
904
|
+
AND result IS NULL;
|
|
905
|
+
UPDATE organization_user_setup_deliveries SET
|
|
906
|
+
state = 'revoked', revoked_at = coalesce(revoked_at, now_value),
|
|
907
|
+
claim_holder_id = NULL, claim_expires_at = NULL,
|
|
908
|
+
unresolved_provider_outcome_at = NULL,
|
|
909
|
+
error_class = NULL, revision = revision + 1, updated_at = now_value
|
|
910
|
+
WHERE id = delivery.id AND state <> 'revoked';
|
|
911
|
+
END IF;
|
|
912
|
+
RETURN NULL;
|
|
913
|
+
END
|
|
914
|
+
$body$;
|
|
915
|
+
|
|
916
|
+
CREATE TRIGGER organization_user_setup_delivery_revocation
|
|
917
|
+
AFTER UPDATE OF status ON organization_membership_invitations
|
|
918
|
+
FOR EACH ROW EXECUTE FUNCTION sync_organization_user_setup_delivery_revocation();
|
|
919
|
+
|
|
920
|
+
DO $pin_and_grant$
|
|
921
|
+
DECLARE data_schema text := current_schema();
|
|
922
|
+
BEGIN
|
|
923
|
+
EXECUTE format(
|
|
924
|
+
'ALTER FUNCTION opengeni_private.organization_invitation_row_json(%I.organization_membership_invitations) SET search_path = pg_catalog, %I, pg_temp',
|
|
925
|
+
data_schema, data_schema
|
|
926
|
+
);
|
|
927
|
+
EXECUTE format(
|
|
928
|
+
'ALTER FUNCTION %I.claim_organization_user_setup_delivery(jsonb) SET search_path = pg_catalog, %I, pg_temp',
|
|
929
|
+
data_schema, data_schema
|
|
930
|
+
);
|
|
931
|
+
EXECUTE format(
|
|
932
|
+
'ALTER FUNCTION %I.prepare_organization_user_setup_delivery(jsonb) SET search_path = pg_catalog, %I, pg_temp',
|
|
933
|
+
data_schema, data_schema
|
|
934
|
+
);
|
|
935
|
+
EXECUTE format(
|
|
936
|
+
'ALTER FUNCTION %I.settle_organization_user_setup_delivery(jsonb) SET search_path = pg_catalog, %I, pg_temp',
|
|
937
|
+
data_schema, data_schema
|
|
938
|
+
);
|
|
939
|
+
EXECUTE format(
|
|
940
|
+
'ALTER FUNCTION %I.preview_organization_user_setup(text) SET search_path = pg_catalog, %I, pg_temp',
|
|
941
|
+
data_schema, data_schema
|
|
942
|
+
);
|
|
943
|
+
EXECUTE format(
|
|
944
|
+
'ALTER FUNCTION %I.get_organization_invitation_for_administration(uuid,text,uuid) SET search_path = pg_catalog, %I, pg_temp',
|
|
945
|
+
data_schema, data_schema
|
|
946
|
+
);
|
|
947
|
+
EXECUTE format(
|
|
948
|
+
'ALTER FUNCTION %I.sync_organization_user_setup_delivery_revocation() SET search_path = pg_catalog, %I, pg_temp',
|
|
949
|
+
data_schema, data_schema
|
|
950
|
+
);
|
|
951
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
952
|
+
EXECUTE format('REVOKE ALL ON TABLE %I.organization_user_setup_deliveries FROM opengeni_app', data_schema);
|
|
953
|
+
EXECUTE format('REVOKE ALL ON TABLE %I.organization_user_setup_delivery_attempts FROM opengeni_app', data_schema);
|
|
954
|
+
EXECUTE format('GRANT EXECUTE ON FUNCTION %I.claim_organization_user_setup_delivery(jsonb) TO opengeni_app', data_schema);
|
|
955
|
+
EXECUTE format('GRANT EXECUTE ON FUNCTION %I.prepare_organization_user_setup_delivery(jsonb) TO opengeni_app', data_schema);
|
|
956
|
+
EXECUTE format('GRANT EXECUTE ON FUNCTION %I.settle_organization_user_setup_delivery(jsonb) TO opengeni_app', data_schema);
|
|
957
|
+
EXECUTE format('GRANT EXECUTE ON FUNCTION %I.preview_organization_user_setup(text) TO opengeni_app', data_schema);
|
|
958
|
+
EXECUTE format('GRANT EXECUTE ON FUNCTION %I.get_organization_invitation_for_administration(uuid,text,uuid) TO opengeni_app', data_schema);
|
|
959
|
+
END IF;
|
|
960
|
+
END
|
|
961
|
+
$pin_and_grant$;
|
|
962
|
+
|
|
963
|
+
REVOKE ALL ON TABLE organization_user_setup_deliveries FROM PUBLIC;
|
|
964
|
+
REVOKE ALL ON TABLE organization_user_setup_delivery_attempts FROM PUBLIC;
|
|
965
|
+
REVOKE ALL ON FUNCTION opengeni_private.organization_user_setup_delivery_json(
|
|
966
|
+
organization_user_setup_deliveries
|
|
967
|
+
) FROM PUBLIC;
|
|
968
|
+
REVOKE ALL ON FUNCTION claim_organization_user_setup_delivery(jsonb) FROM PUBLIC;
|
|
969
|
+
REVOKE ALL ON FUNCTION prepare_organization_user_setup_delivery(jsonb) FROM PUBLIC;
|
|
970
|
+
REVOKE ALL ON FUNCTION settle_organization_user_setup_delivery(jsonb) FROM PUBLIC;
|
|
971
|
+
REVOKE ALL ON FUNCTION preview_organization_user_setup(text) FROM PUBLIC;
|
|
972
|
+
REVOKE ALL ON FUNCTION get_organization_invitation_for_administration(uuid,text,uuid) FROM PUBLIC;
|
|
973
|
+
REVOKE ALL ON FUNCTION sync_organization_user_setup_delivery_revocation() FROM PUBLIC;
|
|
974
|
+
|
|
975
|
+
COMMENT ON TABLE organization_user_setup_deliveries IS
|
|
976
|
+
'Digest-only durable projection for invitation setup email claims, stable provider identity, and reconciliation.';
|
|
977
|
+
COMMENT ON TABLE organization_user_setup_delivery_attempts IS
|
|
978
|
+
'Lifecycle-only immutable-attempt journal for invitation setup delivery provider boundaries.';
|