@opengeni/db 0.28.9 → 0.36.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/attached-browser-devices.d.ts +42 -0
- package/dist/attempt-tool-catalogs.d.ts +16 -0
- package/dist/browser-auth.d.ts +299 -0
- package/dist/browser-downloads.d.ts +51 -0
- package/dist/browser-identities.d.ts +137 -0
- package/dist/browser-sessions.d.ts +354 -0
- package/dist/browser-state-artifacts.d.ts +91 -0
- package/dist/canonical-human-identities.d.ts +48 -0
- package/dist/canonical-human-identities.js +28 -0
- package/dist/canonical-human-identities.js.map +1 -0
- package/dist/capability-components.d.ts +37 -0
- package/dist/capability-integrations.d.ts +194 -0
- package/dist/chunk-5KTIEDX7.js +13200 -0
- package/dist/chunk-5KTIEDX7.js.map +1 -0
- package/dist/chunk-CQZ4QSP5.js +995 -0
- package/dist/chunk-CQZ4QSP5.js.map +1 -0
- package/dist/chunk-F32YT7MP.js +117 -0
- package/dist/chunk-F32YT7MP.js.map +1 -0
- package/dist/chunk-JC5QPLUM.js +666 -0
- package/dist/chunk-JC5QPLUM.js.map +1 -0
- package/dist/chunk-P3RE4D2B.js +3535 -0
- package/dist/chunk-P3RE4D2B.js.map +1 -0
- package/dist/chunk-TAUTWOS5.js +259 -0
- package/dist/chunk-TAUTWOS5.js.map +1 -0
- package/dist/chunk-VKLB2ZGI.js +2389 -0
- package/dist/chunk-VKLB2ZGI.js.map +1 -0
- package/dist/codemode-operations.d.ts +117 -0
- package/dist/codex-token-resolver.d.ts +2 -2
- package/dist/company-profile-schema.d.ts +798 -0
- package/dist/company-profile.d.ts +268 -0
- package/dist/computer-sessions.d.ts +109 -0
- package/dist/connection-token-resolver.d.ts +9 -4
- package/dist/database.d.ts +57 -3
- package/dist/editable-artifact-durable-export.d.ts +153 -0
- package/dist/editable-artifact-durable-export.js +788 -0
- package/dist/editable-artifact-durable-export.js.map +1 -0
- package/dist/editable-artifact-materialization.d.ts +123 -0
- package/dist/editable-artifacts-schema.d.ts +4152 -0
- package/dist/editable-artifacts.d.ts +610 -0
- package/dist/editable-artifacts.js +26 -0
- package/dist/editable-artifacts.js.map +1 -0
- package/dist/generated-images.d.ts +139 -0
- package/dist/index.d.ts +1122 -126
- package/dist/index.js +42329 -18407
- package/dist/index.js.map +1 -1
- package/dist/insights.d.ts +73 -10
- package/dist/integration-bindings.d.ts +70 -0
- package/dist/integration-facets.d.ts +99 -0
- package/dist/interaction-revisions.d.ts +14 -0
- package/dist/interaction-schema.d.ts +6036 -0
- package/dist/knowledge-source-sync-schema.d.ts +1679 -0
- package/dist/knowledge-source-sync.d.ts +274 -0
- package/dist/memory-governance.d.ts +2 -2
- package/dist/memory-slack-delivery.d.ts +101 -0
- package/dist/new-session-drafts.d.ts +2 -2
- package/dist/pack-components.d.ts +76 -0
- package/dist/pack-installations.d.ts +86 -0
- package/dist/persistence-errors.d.ts +3 -3
- package/dist/plugin-packages.d.ts +126 -0
- package/dist/preference-registry.d.ts +1 -1
- package/dist/provision-roles.d.ts +9 -1
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +19 -6
- package/dist/schema.d.ts +22654 -9359
- package/dist/schema.js +209 -3
- package/dist/scoped-knowledge.d.ts +97 -1
- package/dist/session-control.d.ts +33 -7
- package/dist/session-queue-commands.d.ts +19 -11
- package/dist/session-realtime-context.d.ts +2 -2
- package/dist/session-realtime-ledger.d.ts +4 -4
- package/dist/session-realtime-mirror.d.ts +2 -2
- package/dist/session-realtime-state.d.ts +1 -1
- package/dist/session-realtime-terminal.d.ts +1 -1
- package/dist/session-realtime.d.ts +7 -7
- package/dist/session-tenancy.d.ts +44 -0
- package/dist/session-tenancy.js +16 -0
- package/dist/session-tenancy.js.map +1 -0
- package/dist/session-tool-call-settlement.d.ts +1 -1
- package/dist/slack-task-policy-schema.d.ts +847 -0
- package/dist/slack-task-policy.d.ts +65 -0
- package/dist/slack-user-link-access.d.ts +54 -0
- package/dist/task-notes-schema.d.ts +924 -0
- package/dist/task-notes.d.ts +85 -0
- package/dist/transcription-recordings.d.ts +2 -2
- package/dist/turn-initiator.d.ts +2 -2
- package/dist/video-generation.d.ts +225 -0
- package/dist/video-generation.js +68 -0
- package/dist/video-generation.js.map +1 -0
- package/dist/workspace-artifacts.d.ts +1 -1
- package/dist/workspace-instruction-policies.d.ts +1 -1
- package/dist/workspace-learning-policy-schema.d.ts +932 -0
- package/dist/workspace-learning-policy.d.ts +161 -0
- package/dist/xai-subscription.d.ts +326 -0
- package/drizzle/0183_model_call_provider_cost_estimates.sql +39 -0
- package/drizzle/0184_sandbox_drain_teardown_fence.sql +682 -0
- package/drizzle/0185_temporal_schedule_cleanup_outbox.sql +168 -0
- package/drizzle/0186_sandbox_capture_provider_contract.sql +400 -0
- package/drizzle/0187_generated_image_artifacts.sql +173 -0
- package/drizzle/0188_image_generation_retention_failure.sql +25 -0
- package/drizzle/0189_retained_session_image_formats.sql +13 -0
- package/drizzle/0190_timeline_annotations.sql +31 -0
- package/drizzle/0191_editable_artifact_engine.sql +3853 -0
- package/drizzle/0192_editable_artifact_live_tickets.sql +338 -0
- package/drizzle/0193_editable_artifact_authorization.sql +190 -0
- package/drizzle/0194_editable_artifact_durable_exports.sql +160 -0
- package/drizzle/0195_editable_artifact_import_authorization.sql +203 -0
- package/drizzle/0196_rig_provider_images.sql +152 -0
- package/drizzle/0197_knowledge_source_sync_schedules.sql +523 -0
- package/drizzle/0198_memory_slack_publication_delivery.sql +393 -0
- package/drizzle/0199_workspace_learning_policy.sql +980 -0
- package/drizzle/0201_company_profile_authority.sql +849 -0
- package/drizzle/0202_document_index_checkpoints.sql +85 -0
- package/drizzle/0203_durable_video_generation.sql +353 -0
- package/drizzle/0204_video_generation_funding.sql +44 -0
- package/drizzle/0205_attempt_tool_catalogs.sql +230 -0
- package/drizzle/0206_browser_sessions.sql +646 -0
- package/drizzle/0207_browser_identities.sql +379 -0
- package/drizzle/0208_attached_browser_devices.sql +165 -0
- package/drizzle/0209_computer_sessions.sql +575 -0
- package/drizzle/0210_browser_auth_network_interventions.sql +441 -0
- package/drizzle/0211_editable_artifact_session_links.sql +58 -0
- package/drizzle/0212_browser_state_transfer_hardening.sql +560 -0
- package/drizzle/0212_slack_installation_bindings.sql +348 -0
- package/drizzle/0213_browser_interaction_authority.sql +67 -0
- package/drizzle/0213_slack_user_link_access_requests.sql +177 -0
- package/drizzle/0214_browser_download_saves.sql +34 -0
- package/drizzle/0214_session_activity_commit_gate.sql +262 -0
- package/drizzle/0215_browser_controller_host.sql +385 -0
- package/drizzle/0215_capabilities_platform.sql +980 -0
- package/drizzle/0216_browser_auth_health_evidence.sql +137 -0
- package/drizzle/0216_pack_component_ownership.sql +235 -0
- package/drizzle/0217_capability_definition_delete_authority.sql +32 -0
- package/drizzle/0217_external_browser_auth_operations.sql +22 -0
- package/drizzle/0218_organization_tenancy_foundation.sql +299 -0
- package/drizzle/0219_organization_tenancy_managed_human_provisioning.sql +317 -0
- package/drizzle/0219_site_auth_maintenance_sessions.sql +234 -0
- package/drizzle/0220_memory_slack_append_only_cascade.sql +38 -0
- package/drizzle/0220_session_channels.sql +54 -0
- package/drizzle/0221_sessions_channel_index.sql +5 -0
- package/drizzle/0222_session_visibility_authority_epochs.sql +158 -0
- package/drizzle/0222_sessions_channel_fk.sql +89 -0
- package/drizzle/0223_pending_tool_event_output.sql +55 -0
- package/drizzle/0223_sessions_channel_fk_validate.sql +118 -0
- package/drizzle/0224_slack_post_outcome_reconciliation.sql +123 -0
- package/drizzle/0225_session_visibility_fork_activation.sql +1363 -0
- package/drizzle/0226_personal_codex_authority_foundation.sql +365 -0
- package/drizzle/0227_slack_native_actions.sql +145 -0
- package/drizzle/0228_interaction_controller_data_plane.sql +14 -0
- package/drizzle/0228_slack_task_policy.sql +400 -0
- package/drizzle/0229_slack_inbox_file_fact.sql +10 -0
- package/drizzle/0230_user_scoped_variable_sets_rigs.sql +84 -0
- package/drizzle/0231_integration_definition_identity_cutover.sql +271 -0
- package/drizzle/0232_integration_facet_authority_cutover.sql +610 -0
- package/drizzle/0233_skill_and_integration_authority_cutover.sql +1162 -0
- package/drizzle/0234_xai_subscription_authority.sql +1139 -0
- package/drizzle/0235_canonical_human_login_bindings.sql +920 -0
- package/drizzle/0236_browser_identity_lifecycle.sql +39 -0
- package/drizzle/0236_session_visibility_slack_policy.sql +72 -0
- package/drizzle/0237_interaction_transition_reaper.sql +263 -0
- package/drizzle/0238_goal_persistence_policy.sql +471 -0
- package/drizzle/0238_supergrok_realtime_model.sql +29 -0
- package/drizzle/0239_supergrok_video_funding.sql +50 -0
- package/drizzle/0239_task_tree_notes.sql +990 -0
- package/package.json +26 -5
- package/src/attached-browser-devices.ts +371 -0
- package/src/attempt-tool-catalogs.ts +131 -0
- package/src/browser-auth.ts +4373 -0
- package/src/browser-downloads.ts +517 -0
- package/src/browser-identities.ts +1385 -0
- package/src/browser-sessions.ts +2294 -0
- package/src/browser-state-artifacts.ts +449 -0
- package/src/canonical-human-identities.ts +317 -0
- package/src/capability-components.ts +166 -0
- package/src/capability-integrations.ts +1571 -0
- package/src/codemode-operations.ts +674 -0
- package/src/company-profile-schema.ts +143 -0
- package/src/company-profile.ts +968 -0
- package/src/computer-sessions.ts +1130 -0
- package/src/connection-token-resolver.ts +270 -25
- package/src/database.ts +468 -24
- package/src/editable-artifact-durable-export.ts +1201 -0
- package/src/editable-artifact-materialization.ts +893 -0
- package/src/editable-artifacts-schema.ts +879 -0
- package/src/editable-artifacts.ts +5404 -0
- package/src/generated-images.ts +740 -0
- package/src/index.ts +9963 -1465
- package/src/insights.ts +251 -23
- package/src/integration-bindings.ts +455 -0
- package/src/integration-facets.ts +862 -0
- package/src/interaction-revisions.ts +70 -0
- package/src/interaction-schema.ts +1606 -0
- package/src/knowledge-source-sync-schema.ts +192 -0
- package/src/knowledge-source-sync.ts +1922 -0
- package/src/memory-slack-delivery.ts +801 -0
- package/src/pack-components.ts +954 -0
- package/src/pack-installations.ts +886 -0
- package/src/persistence-errors.ts +4 -7
- package/src/plugin-packages.ts +1186 -0
- package/src/provision-roles.ts +534 -1
- package/src/runtime-posture-cli.ts +9 -0
- package/src/runtime-posture.ts +721 -5
- package/src/schema.ts +6270 -3159
- package/src/scoped-knowledge-schema.ts +1 -1
- package/src/scoped-knowledge.ts +655 -3
- package/src/session-control.ts +130 -42
- package/src/session-queue-commands.ts +265 -15
- package/src/session-realtime-context.ts +2 -2
- package/src/session-realtime-ledger.ts +3 -3
- package/src/session-realtime.ts +7 -7
- package/src/session-tenancy.ts +188 -0
- package/src/session-tool-call-settlement.ts +18 -3
- package/src/slack-task-policy-schema.ts +130 -0
- package/src/slack-task-policy.ts +341 -0
- package/src/slack-user-link-access.ts +702 -0
- package/src/task-notes-schema.ts +135 -0
- package/src/task-notes.ts +178 -0
- package/src/video-generation.ts +1530 -0
- package/src/workspace-artifacts.ts +1 -1
- package/src/workspace-learning-policy-schema.ts +139 -0
- package/src/workspace-learning-policy.ts +595 -0
- package/src/xai-subscription.ts +1887 -0
- package/dist/chunk-P6CUHXQZ.js +0 -7248
- package/dist/chunk-P6CUHXQZ.js.map +0 -1
- package/dist/chunk-VHBFHMPC.js +0 -1219
- package/dist/chunk-VHBFHMPC.js.map +0 -1
|
@@ -0,0 +1,920 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Organization-independent canonical human identity and verified login-binding
|
|
3
|
+
-- authority. Identity never implies organization/workspace membership or
|
|
4
|
+
-- resource access. Mutations are lifecycle-only and revoke Better Auth sessions
|
|
5
|
+
-- in the same transaction that advances the monotonic auth revision.
|
|
6
|
+
|
|
7
|
+
SET LOCAL lock_timeout = '5s';
|
|
8
|
+
SET LOCAL statement_timeout = '10min';
|
|
9
|
+
|
|
10
|
+
CREATE TABLE "canonical_human_identities" (
|
|
11
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
12
|
+
"display_name" text NOT NULL,
|
|
13
|
+
"status" text NOT NULL DEFAULT 'active',
|
|
14
|
+
"recovery_state" text NOT NULL DEFAULT 'ready',
|
|
15
|
+
"identity_revision" bigint NOT NULL DEFAULT 1,
|
|
16
|
+
"auth_revision" bigint NOT NULL DEFAULT 1,
|
|
17
|
+
"active_login_binding_id" uuid,
|
|
18
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
19
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
20
|
+
CONSTRAINT "canonical_human_identities_display_name_check" CHECK (
|
|
21
|
+
length(btrim("display_name")) BETWEEN 1 AND 256
|
|
22
|
+
),
|
|
23
|
+
CONSTRAINT "canonical_human_identities_status_check" CHECK (
|
|
24
|
+
"status" IN ('active', 'recovery_required', 'disputed', 'disabled')
|
|
25
|
+
),
|
|
26
|
+
CONSTRAINT "canonical_human_identities_recovery_state_check" CHECK (
|
|
27
|
+
"recovery_state" IN ('ready', 'recovery_required', 'lost_factor', 'disputed', 'disabled')
|
|
28
|
+
),
|
|
29
|
+
CONSTRAINT "canonical_human_identities_revision_check" CHECK (
|
|
30
|
+
"identity_revision" > 0 AND "auth_revision" > 0
|
|
31
|
+
),
|
|
32
|
+
CONSTRAINT "canonical_human_identities_state_check" CHECK (
|
|
33
|
+
("status" = 'active' AND "recovery_state" = 'ready')
|
|
34
|
+
OR ("status" = 'recovery_required' AND "recovery_state" IN ('recovery_required', 'lost_factor'))
|
|
35
|
+
OR ("status" = 'disputed' AND "recovery_state" = 'disputed')
|
|
36
|
+
OR ("status" = 'disabled' AND "recovery_state" = 'disabled')
|
|
37
|
+
)
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
CREATE TABLE "canonical_human_identity_subjects" (
|
|
41
|
+
"auth_user_id" text PRIMARY KEY REFERENCES "auth_users"("id") ON DELETE RESTRICT,
|
|
42
|
+
"identity_id" uuid NOT NULL REFERENCES "canonical_human_identities"("id") ON DELETE RESTRICT,
|
|
43
|
+
"status" text NOT NULL DEFAULT 'active',
|
|
44
|
+
"revision" bigint NOT NULL DEFAULT 1,
|
|
45
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
46
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
47
|
+
CONSTRAINT "canonical_human_identity_subjects_user_check" CHECK (
|
|
48
|
+
length(btrim("auth_user_id")) BETWEEN 1 AND 1024
|
|
49
|
+
),
|
|
50
|
+
CONSTRAINT "canonical_human_identity_subjects_status_check" CHECK (
|
|
51
|
+
"status" IN ('active', 'revoked')
|
|
52
|
+
),
|
|
53
|
+
CONSTRAINT "canonical_human_identity_subjects_revision_check" CHECK ("revision" > 0)
|
|
54
|
+
);
|
|
55
|
+
CREATE INDEX "canonical_human_identity_subjects_identity_idx"
|
|
56
|
+
ON "canonical_human_identity_subjects" ("identity_id", "status");
|
|
57
|
+
|
|
58
|
+
CREATE TABLE "canonical_human_login_bindings" (
|
|
59
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
60
|
+
"identity_id" uuid NOT NULL REFERENCES "canonical_human_identities"("id") ON DELETE RESTRICT,
|
|
61
|
+
"provider_id" text NOT NULL,
|
|
62
|
+
"provider_account_id" text NOT NULL,
|
|
63
|
+
"status" text NOT NULL DEFAULT 'active',
|
|
64
|
+
"revision" bigint NOT NULL DEFAULT 1,
|
|
65
|
+
"verified_at" timestamptz NOT NULL DEFAULT now(),
|
|
66
|
+
"last_verified_at" timestamptz NOT NULL DEFAULT now(),
|
|
67
|
+
"revoked_at" timestamptz,
|
|
68
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
69
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
70
|
+
CONSTRAINT "canonical_human_login_bindings_provider_check" CHECK (
|
|
71
|
+
"provider_id" = lower(btrim("provider_id"))
|
|
72
|
+
AND length("provider_id") BETWEEN 1 AND 128
|
|
73
|
+
AND "provider_id" ~ '^[a-z0-9](?:[a-z0-9._:-]*[a-z0-9])?$'
|
|
74
|
+
),
|
|
75
|
+
CONSTRAINT "canonical_human_login_bindings_account_check" CHECK (
|
|
76
|
+
length(btrim("provider_account_id")) BETWEEN 1 AND 1024
|
|
77
|
+
),
|
|
78
|
+
CONSTRAINT "canonical_human_login_bindings_status_check" CHECK (
|
|
79
|
+
"status" IN ('active', 'recovery_pending', 'stale', 'disputed', 'revoked')
|
|
80
|
+
),
|
|
81
|
+
CONSTRAINT "canonical_human_login_bindings_revision_check" CHECK ("revision" > 0),
|
|
82
|
+
CONSTRAINT "canonical_human_login_bindings_revocation_check" CHECK (
|
|
83
|
+
("status" = 'revoked' AND "revoked_at" IS NOT NULL)
|
|
84
|
+
OR ("status" <> 'revoked' AND "revoked_at" IS NULL)
|
|
85
|
+
)
|
|
86
|
+
);
|
|
87
|
+
CREATE UNIQUE INDEX "canonical_human_login_bindings_provider_account_idx"
|
|
88
|
+
ON "canonical_human_login_bindings" ("provider_id", "provider_account_id");
|
|
89
|
+
CREATE INDEX "canonical_human_login_bindings_identity_status_idx"
|
|
90
|
+
ON "canonical_human_login_bindings" ("identity_id", "status", "created_at", "id");
|
|
91
|
+
|
|
92
|
+
ALTER TABLE "canonical_human_identities"
|
|
93
|
+
ADD CONSTRAINT "canonical_human_identities_active_binding_fk"
|
|
94
|
+
FOREIGN KEY ("active_login_binding_id")
|
|
95
|
+
REFERENCES "canonical_human_login_bindings"("id") ON DELETE RESTRICT;
|
|
96
|
+
|
|
97
|
+
CREATE TABLE "canonical_human_identity_operations" (
|
|
98
|
+
"operation_id" uuid PRIMARY KEY,
|
|
99
|
+
"identity_id" uuid NOT NULL REFERENCES "canonical_human_identities"("id") ON DELETE RESTRICT,
|
|
100
|
+
"actor_auth_user_id" text NOT NULL REFERENCES "auth_users"("id") ON DELETE RESTRICT,
|
|
101
|
+
"operation_type" text NOT NULL,
|
|
102
|
+
"binding_id" uuid REFERENCES "canonical_human_login_bindings"("id") ON DELETE RESTRICT,
|
|
103
|
+
"provider_id" text,
|
|
104
|
+
"provider_account_id" text,
|
|
105
|
+
"expected_identity_revision" bigint NOT NULL,
|
|
106
|
+
"result_identity_revision" bigint NOT NULL,
|
|
107
|
+
"result_auth_revision" bigint NOT NULL,
|
|
108
|
+
"outcome" text NOT NULL,
|
|
109
|
+
"reason" text NOT NULL,
|
|
110
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
111
|
+
CONSTRAINT "canonical_human_identity_operations_type_check" CHECK (
|
|
112
|
+
"operation_type" IN ('link', 'unlink', 'begin_recovery', 'recover')
|
|
113
|
+
),
|
|
114
|
+
CONSTRAINT "canonical_human_identity_operations_outcome_check" CHECK (
|
|
115
|
+
"outcome" IN ('applied', 'lost_factor', 'disputed')
|
|
116
|
+
),
|
|
117
|
+
CONSTRAINT "canonical_human_identity_operations_revision_check" CHECK (
|
|
118
|
+
"expected_identity_revision" > 0
|
|
119
|
+
AND "result_identity_revision" > 0
|
|
120
|
+
AND "result_auth_revision" > 0
|
|
121
|
+
),
|
|
122
|
+
CONSTRAINT "canonical_human_identity_operations_reason_check" CHECK (
|
|
123
|
+
length(btrim("reason")) BETWEEN 1 AND 512
|
|
124
|
+
)
|
|
125
|
+
);
|
|
126
|
+
CREATE INDEX "canonical_human_identity_operations_identity_created_idx"
|
|
127
|
+
ON "canonical_human_identity_operations" ("identity_id", "created_at", "operation_id");
|
|
128
|
+
|
|
129
|
+
ALTER TABLE "auth_sessions"
|
|
130
|
+
ADD COLUMN "identity_id" uuid,
|
|
131
|
+
ADD COLUMN "identity_revision" bigint,
|
|
132
|
+
ADD COLUMN "auth_revision" bigint;
|
|
133
|
+
|
|
134
|
+
DO $canonical_human_backfill$
|
|
135
|
+
DECLARE
|
|
136
|
+
user_row record;
|
|
137
|
+
identity_id uuid;
|
|
138
|
+
BEGIN
|
|
139
|
+
FOR user_row IN
|
|
140
|
+
SELECT id, name, email, created_at, updated_at
|
|
141
|
+
FROM auth_users
|
|
142
|
+
ORDER BY id
|
|
143
|
+
LOOP
|
|
144
|
+
INSERT INTO canonical_human_identities (
|
|
145
|
+
display_name,
|
|
146
|
+
created_at,
|
|
147
|
+
updated_at
|
|
148
|
+
) VALUES (
|
|
149
|
+
left(coalesce(nullif(btrim(user_row.name), ''), user_row.email), 256),
|
|
150
|
+
user_row.created_at,
|
|
151
|
+
user_row.updated_at
|
|
152
|
+
)
|
|
153
|
+
RETURNING id INTO identity_id;
|
|
154
|
+
|
|
155
|
+
INSERT INTO canonical_human_identity_subjects (
|
|
156
|
+
auth_user_id,
|
|
157
|
+
identity_id,
|
|
158
|
+
created_at,
|
|
159
|
+
updated_at
|
|
160
|
+
) VALUES (
|
|
161
|
+
user_row.id,
|
|
162
|
+
identity_id,
|
|
163
|
+
user_row.created_at,
|
|
164
|
+
user_row.updated_at
|
|
165
|
+
);
|
|
166
|
+
END LOOP;
|
|
167
|
+
|
|
168
|
+
INSERT INTO canonical_human_login_bindings (
|
|
169
|
+
identity_id,
|
|
170
|
+
provider_id,
|
|
171
|
+
provider_account_id,
|
|
172
|
+
verified_at,
|
|
173
|
+
last_verified_at,
|
|
174
|
+
created_at,
|
|
175
|
+
updated_at
|
|
176
|
+
)
|
|
177
|
+
SELECT
|
|
178
|
+
subject_row.identity_id,
|
|
179
|
+
lower(identity_row.provider_id),
|
|
180
|
+
identity_row.account_id,
|
|
181
|
+
identity_row.created_at,
|
|
182
|
+
greatest(identity_row.created_at, identity_row.updated_at),
|
|
183
|
+
identity_row.created_at,
|
|
184
|
+
identity_row.updated_at
|
|
185
|
+
FROM auth_identities identity_row
|
|
186
|
+
INNER JOIN canonical_human_identity_subjects subject_row
|
|
187
|
+
ON subject_row.auth_user_id = identity_row.user_id
|
|
188
|
+
ORDER BY identity_row.created_at, identity_row.id;
|
|
189
|
+
|
|
190
|
+
UPDATE canonical_human_identities identity_row
|
|
191
|
+
SET active_login_binding_id = (
|
|
192
|
+
SELECT binding.id
|
|
193
|
+
FROM canonical_human_login_bindings binding
|
|
194
|
+
WHERE binding.identity_id = identity_row.id
|
|
195
|
+
AND binding.status = 'active'
|
|
196
|
+
ORDER BY binding.created_at, binding.id
|
|
197
|
+
LIMIT 1
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
UPDATE auth_sessions session_row
|
|
201
|
+
SET identity_id = subject_row.identity_id,
|
|
202
|
+
identity_revision = identity_row.identity_revision,
|
|
203
|
+
auth_revision = identity_row.auth_revision
|
|
204
|
+
FROM canonical_human_identity_subjects subject_row
|
|
205
|
+
INNER JOIN canonical_human_identities identity_row
|
|
206
|
+
ON identity_row.id = subject_row.identity_id
|
|
207
|
+
WHERE subject_row.auth_user_id = session_row.user_id
|
|
208
|
+
AND subject_row.status = 'active';
|
|
209
|
+
END
|
|
210
|
+
$canonical_human_backfill$;
|
|
211
|
+
|
|
212
|
+
ALTER TABLE "auth_sessions"
|
|
213
|
+
ALTER COLUMN "identity_id" SET NOT NULL,
|
|
214
|
+
ALTER COLUMN "identity_revision" SET NOT NULL,
|
|
215
|
+
ALTER COLUMN "auth_revision" SET NOT NULL,
|
|
216
|
+
ADD CONSTRAINT "auth_sessions_canonical_identity_fk"
|
|
217
|
+
FOREIGN KEY ("identity_id") REFERENCES "canonical_human_identities"("id") ON DELETE CASCADE,
|
|
218
|
+
ADD CONSTRAINT "auth_sessions_canonical_identity_revision_check" CHECK (
|
|
219
|
+
"identity_revision" > 0 AND "auth_revision" > 0
|
|
220
|
+
);
|
|
221
|
+
CREATE INDEX "auth_sessions_identity_revision_idx"
|
|
222
|
+
ON "auth_sessions" ("identity_id", "auth_revision", "identity_revision");
|
|
223
|
+
|
|
224
|
+
ALTER TABLE "canonical_human_identities" ENABLE ROW LEVEL SECURITY;
|
|
225
|
+
ALTER TABLE "canonical_human_identities" FORCE ROW LEVEL SECURITY;
|
|
226
|
+
ALTER TABLE "canonical_human_identity_subjects" ENABLE ROW LEVEL SECURITY;
|
|
227
|
+
ALTER TABLE "canonical_human_identity_subjects" FORCE ROW LEVEL SECURITY;
|
|
228
|
+
ALTER TABLE "canonical_human_login_bindings" ENABLE ROW LEVEL SECURITY;
|
|
229
|
+
ALTER TABLE "canonical_human_login_bindings" FORCE ROW LEVEL SECURITY;
|
|
230
|
+
ALTER TABLE "canonical_human_identity_operations" ENABLE ROW LEVEL SECURITY;
|
|
231
|
+
ALTER TABLE "canonical_human_identity_operations" FORCE ROW LEVEL SECURITY;
|
|
232
|
+
|
|
233
|
+
CREATE POLICY canonical_human_identity_lifecycle ON "canonical_human_identities"
|
|
234
|
+
USING (current_setting('opengeni.canonical_human_identity_lifecycle', true) = 'active')
|
|
235
|
+
WITH CHECK (current_setting('opengeni.canonical_human_identity_lifecycle', true) = 'active');
|
|
236
|
+
CREATE POLICY canonical_human_identity_lifecycle ON "canonical_human_identity_subjects"
|
|
237
|
+
USING (current_setting('opengeni.canonical_human_identity_lifecycle', true) = 'active')
|
|
238
|
+
WITH CHECK (current_setting('opengeni.canonical_human_identity_lifecycle', true) = 'active');
|
|
239
|
+
CREATE POLICY canonical_human_identity_lifecycle ON "canonical_human_login_bindings"
|
|
240
|
+
USING (current_setting('opengeni.canonical_human_identity_lifecycle', true) = 'active')
|
|
241
|
+
WITH CHECK (current_setting('opengeni.canonical_human_identity_lifecycle', true) = 'active');
|
|
242
|
+
CREATE POLICY canonical_human_identity_lifecycle ON "canonical_human_identity_operations"
|
|
243
|
+
USING (current_setting('opengeni.canonical_human_identity_lifecycle', true) = 'active')
|
|
244
|
+
WITH CHECK (current_setting('opengeni.canonical_human_identity_lifecycle', true) = 'active');
|
|
245
|
+
|
|
246
|
+
DO $canonical_human_identity_routines$
|
|
247
|
+
DECLARE
|
|
248
|
+
data_schema text := current_schema();
|
|
249
|
+
BEGIN
|
|
250
|
+
EXECUTE format($ddl$
|
|
251
|
+
CREATE OR REPLACE FUNCTION %1$I.ensure_canonical_human_identity(
|
|
252
|
+
p_auth_user_id text,
|
|
253
|
+
p_display_name text
|
|
254
|
+
)
|
|
255
|
+
RETURNS TABLE (
|
|
256
|
+
identity_id uuid,
|
|
257
|
+
identity_revision bigint,
|
|
258
|
+
auth_revision bigint,
|
|
259
|
+
identity_status text
|
|
260
|
+
)
|
|
261
|
+
LANGUAGE plpgsql
|
|
262
|
+
SECURITY DEFINER
|
|
263
|
+
SET search_path = %1$I, pg_catalog
|
|
264
|
+
AS $body$
|
|
265
|
+
DECLARE
|
|
266
|
+
subject_row canonical_human_identity_subjects%%ROWTYPE;
|
|
267
|
+
identity_row canonical_human_identities%%ROWTYPE;
|
|
268
|
+
normalized_name text;
|
|
269
|
+
stale_count integer;
|
|
270
|
+
previous_marker text := pg_catalog.current_setting(
|
|
271
|
+
'opengeni.canonical_human_identity_lifecycle', true
|
|
272
|
+
);
|
|
273
|
+
BEGIN
|
|
274
|
+
IF p_auth_user_id IS NULL
|
|
275
|
+
OR p_auth_user_id <> pg_catalog.btrim(p_auth_user_id)
|
|
276
|
+
OR pg_catalog.length(p_auth_user_id) NOT BETWEEN 1 AND 1024
|
|
277
|
+
THEN
|
|
278
|
+
RAISE EXCEPTION 'canonical human auth user is invalid' USING ERRCODE = '42501';
|
|
279
|
+
END IF;
|
|
280
|
+
normalized_name := pg_catalog.left(pg_catalog.btrim(p_display_name), 256);
|
|
281
|
+
IF normalized_name IS NULL OR pg_catalog.length(normalized_name) < 1 THEN
|
|
282
|
+
RAISE EXCEPTION 'canonical human display name is invalid' USING ERRCODE = '22023';
|
|
283
|
+
END IF;
|
|
284
|
+
IF NOT EXISTS (SELECT 1 FROM auth_users WHERE id = p_auth_user_id) THEN
|
|
285
|
+
RAISE EXCEPTION 'canonical human auth user was not found' USING ERRCODE = 'P0002';
|
|
286
|
+
END IF;
|
|
287
|
+
|
|
288
|
+
PERFORM pg_catalog.pg_advisory_xact_lock(
|
|
289
|
+
pg_catalog.hashtextextended('canonical-human-user:' || p_auth_user_id, 0)
|
|
290
|
+
);
|
|
291
|
+
PERFORM pg_catalog.set_config(
|
|
292
|
+
'opengeni.canonical_human_identity_lifecycle', 'active', true
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
SELECT * INTO subject_row
|
|
296
|
+
FROM canonical_human_identity_subjects
|
|
297
|
+
WHERE auth_user_id = p_auth_user_id
|
|
298
|
+
FOR UPDATE;
|
|
299
|
+
|
|
300
|
+
IF NOT FOUND THEN
|
|
301
|
+
INSERT INTO canonical_human_identities (display_name)
|
|
302
|
+
VALUES (normalized_name)
|
|
303
|
+
RETURNING * INTO identity_row;
|
|
304
|
+
INSERT INTO canonical_human_identity_subjects (auth_user_id, identity_id)
|
|
305
|
+
VALUES (p_auth_user_id, identity_row.id)
|
|
306
|
+
RETURNING * INTO subject_row;
|
|
307
|
+
ELSE
|
|
308
|
+
IF subject_row.status <> 'active' THEN
|
|
309
|
+
RAISE EXCEPTION 'canonical human subject is not active' USING ERRCODE = '42501';
|
|
310
|
+
END IF;
|
|
311
|
+
SELECT * INTO identity_row
|
|
312
|
+
FROM canonical_human_identities
|
|
313
|
+
WHERE id = subject_row.identity_id
|
|
314
|
+
FOR UPDATE;
|
|
315
|
+
END IF;
|
|
316
|
+
|
|
317
|
+
IF identity_row.display_name IS DISTINCT FROM normalized_name THEN
|
|
318
|
+
UPDATE canonical_human_identities AS renamed_identity
|
|
319
|
+
SET display_name = normalized_name,
|
|
320
|
+
identity_revision = renamed_identity.identity_revision + 1,
|
|
321
|
+
updated_at = pg_catalog.clock_timestamp()
|
|
322
|
+
WHERE renamed_identity.id = identity_row.id
|
|
323
|
+
RETURNING * INTO identity_row;
|
|
324
|
+
END IF;
|
|
325
|
+
|
|
326
|
+
WITH stale AS (
|
|
327
|
+
UPDATE canonical_human_login_bindings binding
|
|
328
|
+
SET status = 'stale',
|
|
329
|
+
revision = binding.revision + 1,
|
|
330
|
+
updated_at = pg_catalog.clock_timestamp()
|
|
331
|
+
WHERE binding.identity_id = identity_row.id
|
|
332
|
+
AND binding.status = 'active'
|
|
333
|
+
AND NOT EXISTS (
|
|
334
|
+
SELECT 1
|
|
335
|
+
FROM auth_identities auth_identity
|
|
336
|
+
WHERE auth_identity.user_id = p_auth_user_id
|
|
337
|
+
AND lower(auth_identity.provider_id) = binding.provider_id
|
|
338
|
+
AND auth_identity.account_id = binding.provider_account_id
|
|
339
|
+
)
|
|
340
|
+
RETURNING 1
|
|
341
|
+
)
|
|
342
|
+
SELECT count(*)::integer INTO stale_count FROM stale;
|
|
343
|
+
|
|
344
|
+
IF stale_count > 0 THEN
|
|
345
|
+
UPDATE canonical_human_identities AS stale_identity
|
|
346
|
+
SET status = 'recovery_required',
|
|
347
|
+
recovery_state = 'recovery_required',
|
|
348
|
+
identity_revision = stale_identity.identity_revision + 1,
|
|
349
|
+
auth_revision = stale_identity.auth_revision + 1,
|
|
350
|
+
active_login_binding_id = (
|
|
351
|
+
SELECT binding.id
|
|
352
|
+
FROM canonical_human_login_bindings binding
|
|
353
|
+
WHERE binding.identity_id = identity_row.id
|
|
354
|
+
AND binding.status = 'active'
|
|
355
|
+
ORDER BY binding.created_at, binding.id
|
|
356
|
+
LIMIT 1
|
|
357
|
+
),
|
|
358
|
+
updated_at = pg_catalog.clock_timestamp()
|
|
359
|
+
WHERE stale_identity.id = identity_row.id
|
|
360
|
+
RETURNING * INTO identity_row;
|
|
361
|
+
DELETE FROM auth_sessions WHERE identity_id = identity_row.id;
|
|
362
|
+
END IF;
|
|
363
|
+
|
|
364
|
+
PERFORM pg_catalog.set_config(
|
|
365
|
+
'opengeni.canonical_human_identity_lifecycle',
|
|
366
|
+
CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END,
|
|
367
|
+
true
|
|
368
|
+
);
|
|
369
|
+
identity_id := identity_row.id;
|
|
370
|
+
identity_revision := identity_row.identity_revision;
|
|
371
|
+
auth_revision := identity_row.auth_revision;
|
|
372
|
+
identity_status := identity_row.status;
|
|
373
|
+
RETURN NEXT;
|
|
374
|
+
EXCEPTION WHEN OTHERS THEN
|
|
375
|
+
PERFORM pg_catalog.set_config(
|
|
376
|
+
'opengeni.canonical_human_identity_lifecycle',
|
|
377
|
+
CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END,
|
|
378
|
+
true
|
|
379
|
+
);
|
|
380
|
+
RAISE;
|
|
381
|
+
END;
|
|
382
|
+
$body$;
|
|
383
|
+
$ddl$, data_schema);
|
|
384
|
+
|
|
385
|
+
EXECUTE format($ddl$
|
|
386
|
+
CREATE OR REPLACE FUNCTION %1$I.validate_canonical_human_session(
|
|
387
|
+
p_auth_session_id text,
|
|
388
|
+
p_auth_user_id text,
|
|
389
|
+
p_allow_recovery boolean DEFAULT false
|
|
390
|
+
)
|
|
391
|
+
RETURNS boolean
|
|
392
|
+
LANGUAGE plpgsql
|
|
393
|
+
SECURITY DEFINER
|
|
394
|
+
SET search_path = %1$I, pg_catalog
|
|
395
|
+
AS $body$
|
|
396
|
+
DECLARE
|
|
397
|
+
valid boolean;
|
|
398
|
+
revisions_match boolean;
|
|
399
|
+
current_status text;
|
|
400
|
+
previous_marker text := pg_catalog.current_setting(
|
|
401
|
+
'opengeni.canonical_human_identity_lifecycle', true
|
|
402
|
+
);
|
|
403
|
+
BEGIN
|
|
404
|
+
PERFORM pg_catalog.set_config(
|
|
405
|
+
'opengeni.canonical_human_identity_lifecycle', 'active', true
|
|
406
|
+
);
|
|
407
|
+
SELECT
|
|
408
|
+
session_row.identity_revision = identity_row.identity_revision
|
|
409
|
+
AND session_row.auth_revision = identity_row.auth_revision,
|
|
410
|
+
identity_row.status
|
|
411
|
+
INTO revisions_match, current_status
|
|
412
|
+
FROM auth_sessions session_row
|
|
413
|
+
INNER JOIN canonical_human_identity_subjects subject_row
|
|
414
|
+
ON subject_row.auth_user_id = session_row.user_id
|
|
415
|
+
AND subject_row.identity_id = session_row.identity_id
|
|
416
|
+
AND subject_row.status = 'active'
|
|
417
|
+
INNER JOIN canonical_human_identities identity_row
|
|
418
|
+
ON identity_row.id = session_row.identity_id
|
|
419
|
+
WHERE session_row.id = p_auth_session_id
|
|
420
|
+
AND session_row.user_id = p_auth_user_id
|
|
421
|
+
AND subject_row.status = 'active';
|
|
422
|
+
valid := coalesce(revisions_match, false) AND (
|
|
423
|
+
current_status = 'active'
|
|
424
|
+
OR (p_allow_recovery AND current_status = 'recovery_required')
|
|
425
|
+
);
|
|
426
|
+
IF NOT valid AND (
|
|
427
|
+
revisions_match IS DISTINCT FROM true
|
|
428
|
+
OR current_status IS NULL
|
|
429
|
+
OR current_status IN ('disputed', 'disabled')
|
|
430
|
+
) THEN
|
|
431
|
+
DELETE FROM auth_sessions
|
|
432
|
+
WHERE id = p_auth_session_id AND user_id = p_auth_user_id;
|
|
433
|
+
END IF;
|
|
434
|
+
PERFORM pg_catalog.set_config(
|
|
435
|
+
'opengeni.canonical_human_identity_lifecycle',
|
|
436
|
+
CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END,
|
|
437
|
+
true
|
|
438
|
+
);
|
|
439
|
+
RETURN valid;
|
|
440
|
+
EXCEPTION WHEN OTHERS THEN
|
|
441
|
+
PERFORM pg_catalog.set_config(
|
|
442
|
+
'opengeni.canonical_human_identity_lifecycle',
|
|
443
|
+
CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END,
|
|
444
|
+
true
|
|
445
|
+
);
|
|
446
|
+
RAISE;
|
|
447
|
+
END;
|
|
448
|
+
$body$;
|
|
449
|
+
$ddl$, data_schema);
|
|
450
|
+
|
|
451
|
+
EXECUTE format($ddl$
|
|
452
|
+
CREATE OR REPLACE FUNCTION %1$I.get_canonical_human_identity_projection(
|
|
453
|
+
p_auth_user_id text
|
|
454
|
+
)
|
|
455
|
+
RETURNS jsonb
|
|
456
|
+
LANGUAGE plpgsql
|
|
457
|
+
SECURITY DEFINER
|
|
458
|
+
SET search_path = %1$I, pg_catalog
|
|
459
|
+
AS $body$
|
|
460
|
+
DECLARE
|
|
461
|
+
result jsonb;
|
|
462
|
+
previous_marker text := pg_catalog.current_setting(
|
|
463
|
+
'opengeni.canonical_human_identity_lifecycle', true
|
|
464
|
+
);
|
|
465
|
+
BEGIN
|
|
466
|
+
PERFORM pg_catalog.set_config(
|
|
467
|
+
'opengeni.canonical_human_identity_lifecycle', 'active', true
|
|
468
|
+
);
|
|
469
|
+
SELECT pg_catalog.jsonb_build_object(
|
|
470
|
+
'activeIdentity', pg_catalog.jsonb_build_object(
|
|
471
|
+
'id', identity_row.id,
|
|
472
|
+
'displayName', identity_row.display_name,
|
|
473
|
+
'status', identity_row.status,
|
|
474
|
+
'identityRevision', identity_row.identity_revision,
|
|
475
|
+
'authRevision', identity_row.auth_revision,
|
|
476
|
+
'activeLoginBindingId', identity_row.active_login_binding_id,
|
|
477
|
+
'recoveryState', identity_row.recovery_state,
|
|
478
|
+
'createdAt', pg_catalog.to_char(
|
|
479
|
+
identity_row.created_at AT TIME ZONE 'UTC',
|
|
480
|
+
'YYYY-MM-DD"T"HH24:MI:SS.US"Z"'
|
|
481
|
+
),
|
|
482
|
+
'updatedAt', pg_catalog.to_char(
|
|
483
|
+
identity_row.updated_at AT TIME ZONE 'UTC',
|
|
484
|
+
'YYYY-MM-DD"T"HH24:MI:SS.US"Z"'
|
|
485
|
+
)
|
|
486
|
+
),
|
|
487
|
+
'loginBindings', coalesce((
|
|
488
|
+
SELECT pg_catalog.jsonb_agg(
|
|
489
|
+
pg_catalog.jsonb_build_object(
|
|
490
|
+
'id', binding.id,
|
|
491
|
+
'providerId', binding.provider_id,
|
|
492
|
+
'providerAccountId', binding.provider_account_id,
|
|
493
|
+
'status', binding.status,
|
|
494
|
+
'revision', binding.revision,
|
|
495
|
+
'verifiedAt', pg_catalog.to_char(
|
|
496
|
+
binding.verified_at AT TIME ZONE 'UTC',
|
|
497
|
+
'YYYY-MM-DD"T"HH24:MI:SS.US"Z"'
|
|
498
|
+
),
|
|
499
|
+
'lastVerifiedAt', pg_catalog.to_char(
|
|
500
|
+
binding.last_verified_at AT TIME ZONE 'UTC',
|
|
501
|
+
'YYYY-MM-DD"T"HH24:MI:SS.US"Z"'
|
|
502
|
+
),
|
|
503
|
+
'revokedAt', pg_catalog.to_char(
|
|
504
|
+
binding.revoked_at AT TIME ZONE 'UTC',
|
|
505
|
+
'YYYY-MM-DD"T"HH24:MI:SS.US"Z"'
|
|
506
|
+
),
|
|
507
|
+
'createdAt', pg_catalog.to_char(
|
|
508
|
+
binding.created_at AT TIME ZONE 'UTC',
|
|
509
|
+
'YYYY-MM-DD"T"HH24:MI:SS.US"Z"'
|
|
510
|
+
),
|
|
511
|
+
'updatedAt', pg_catalog.to_char(
|
|
512
|
+
binding.updated_at AT TIME ZONE 'UTC',
|
|
513
|
+
'YYYY-MM-DD"T"HH24:MI:SS.US"Z"'
|
|
514
|
+
)
|
|
515
|
+
) ORDER BY binding.created_at, binding.id
|
|
516
|
+
)
|
|
517
|
+
FROM canonical_human_login_bindings binding
|
|
518
|
+
WHERE binding.identity_id = identity_row.id
|
|
519
|
+
), '[]'::jsonb)
|
|
520
|
+
) INTO result
|
|
521
|
+
FROM canonical_human_identity_subjects subject_row
|
|
522
|
+
INNER JOIN canonical_human_identities identity_row
|
|
523
|
+
ON identity_row.id = subject_row.identity_id
|
|
524
|
+
WHERE subject_row.auth_user_id = p_auth_user_id
|
|
525
|
+
AND subject_row.status = 'active';
|
|
526
|
+
PERFORM pg_catalog.set_config(
|
|
527
|
+
'opengeni.canonical_human_identity_lifecycle',
|
|
528
|
+
CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END,
|
|
529
|
+
true
|
|
530
|
+
);
|
|
531
|
+
RETURN result;
|
|
532
|
+
EXCEPTION WHEN OTHERS THEN
|
|
533
|
+
PERFORM pg_catalog.set_config(
|
|
534
|
+
'opengeni.canonical_human_identity_lifecycle',
|
|
535
|
+
CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END,
|
|
536
|
+
true
|
|
537
|
+
);
|
|
538
|
+
RAISE;
|
|
539
|
+
END;
|
|
540
|
+
$body$;
|
|
541
|
+
$ddl$, data_schema);
|
|
542
|
+
|
|
543
|
+
EXECUTE format($ddl$
|
|
544
|
+
CREATE OR REPLACE FUNCTION %1$I.apply_canonical_human_identity_operation(
|
|
545
|
+
p_operation_id uuid,
|
|
546
|
+
p_auth_user_id text,
|
|
547
|
+
p_expected_identity_revision bigint,
|
|
548
|
+
p_operation_type text,
|
|
549
|
+
p_binding_id uuid,
|
|
550
|
+
p_provider_id text,
|
|
551
|
+
p_provider_account_id text,
|
|
552
|
+
p_reason text
|
|
553
|
+
)
|
|
554
|
+
RETURNS TABLE (
|
|
555
|
+
outcome text,
|
|
556
|
+
identity_id uuid,
|
|
557
|
+
identity_revision bigint,
|
|
558
|
+
auth_revision bigint
|
|
559
|
+
)
|
|
560
|
+
LANGUAGE plpgsql
|
|
561
|
+
SECURITY DEFINER
|
|
562
|
+
SET search_path = %1$I, pg_catalog
|
|
563
|
+
AS $body$
|
|
564
|
+
DECLARE
|
|
565
|
+
subject_row canonical_human_identity_subjects%%ROWTYPE;
|
|
566
|
+
identity_row canonical_human_identities%%ROWTYPE;
|
|
567
|
+
binding_row canonical_human_login_bindings%%ROWTYPE;
|
|
568
|
+
prior_operation canonical_human_identity_operations%%ROWTYPE;
|
|
569
|
+
normalized_provider text;
|
|
570
|
+
active_count integer;
|
|
571
|
+
replacement_binding_id uuid;
|
|
572
|
+
previous_marker text := pg_catalog.current_setting(
|
|
573
|
+
'opengeni.canonical_human_identity_lifecycle', true
|
|
574
|
+
);
|
|
575
|
+
BEGIN
|
|
576
|
+
IF p_operation_id IS NULL
|
|
577
|
+
OR p_expected_identity_revision IS NULL
|
|
578
|
+
OR p_expected_identity_revision < 1
|
|
579
|
+
OR p_operation_type NOT IN ('link', 'unlink', 'begin_recovery', 'recover')
|
|
580
|
+
OR p_reason IS NULL
|
|
581
|
+
OR p_reason <> pg_catalog.btrim(p_reason)
|
|
582
|
+
OR pg_catalog.length(p_reason) NOT BETWEEN 1 AND 512
|
|
583
|
+
THEN
|
|
584
|
+
RAISE EXCEPTION 'canonical human identity operation is invalid' USING ERRCODE = '22023';
|
|
585
|
+
END IF;
|
|
586
|
+
normalized_provider := lower(pg_catalog.btrim(p_provider_id));
|
|
587
|
+
IF p_operation_type = 'link' AND (
|
|
588
|
+
normalized_provider IS NULL
|
|
589
|
+
OR pg_catalog.length(normalized_provider) NOT BETWEEN 1 AND 128
|
|
590
|
+
OR normalized_provider !~ '^[a-z0-9](?:[a-z0-9._:-]*[a-z0-9])?$'
|
|
591
|
+
OR p_provider_account_id IS NULL
|
|
592
|
+
OR p_provider_account_id <> pg_catalog.btrim(p_provider_account_id)
|
|
593
|
+
OR pg_catalog.length(p_provider_account_id) NOT BETWEEN 1 AND 1024
|
|
594
|
+
) THEN
|
|
595
|
+
RAISE EXCEPTION 'canonical human login binding is invalid' USING ERRCODE = '22023';
|
|
596
|
+
END IF;
|
|
597
|
+
IF p_operation_type <> 'link' AND p_binding_id IS NULL THEN
|
|
598
|
+
RAISE EXCEPTION 'canonical human login binding id is required' USING ERRCODE = '22023';
|
|
599
|
+
END IF;
|
|
600
|
+
|
|
601
|
+
PERFORM pg_catalog.set_config(
|
|
602
|
+
'opengeni.canonical_human_identity_lifecycle', 'active', true
|
|
603
|
+
);
|
|
604
|
+
SELECT * INTO prior_operation
|
|
605
|
+
FROM canonical_human_identity_operations
|
|
606
|
+
WHERE operation_id = p_operation_id;
|
|
607
|
+
IF FOUND THEN
|
|
608
|
+
IF prior_operation.actor_auth_user_id IS DISTINCT FROM p_auth_user_id
|
|
609
|
+
OR prior_operation.operation_type IS DISTINCT FROM p_operation_type
|
|
610
|
+
OR prior_operation.binding_id IS DISTINCT FROM p_binding_id
|
|
611
|
+
OR prior_operation.provider_id IS DISTINCT FROM normalized_provider
|
|
612
|
+
OR prior_operation.provider_account_id IS DISTINCT FROM p_provider_account_id
|
|
613
|
+
OR prior_operation.expected_identity_revision IS DISTINCT FROM p_expected_identity_revision
|
|
614
|
+
OR prior_operation.reason IS DISTINCT FROM p_reason
|
|
615
|
+
THEN
|
|
616
|
+
RAISE EXCEPTION 'canonical human operation id was reused' USING ERRCODE = '23505';
|
|
617
|
+
END IF;
|
|
618
|
+
outcome := prior_operation.outcome;
|
|
619
|
+
identity_id := prior_operation.identity_id;
|
|
620
|
+
identity_revision := prior_operation.result_identity_revision;
|
|
621
|
+
auth_revision := prior_operation.result_auth_revision;
|
|
622
|
+
PERFORM pg_catalog.set_config(
|
|
623
|
+
'opengeni.canonical_human_identity_lifecycle',
|
|
624
|
+
CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END,
|
|
625
|
+
true
|
|
626
|
+
);
|
|
627
|
+
RETURN NEXT;
|
|
628
|
+
RETURN;
|
|
629
|
+
END IF;
|
|
630
|
+
|
|
631
|
+
SELECT * INTO subject_row
|
|
632
|
+
FROM canonical_human_identity_subjects
|
|
633
|
+
WHERE auth_user_id = p_auth_user_id
|
|
634
|
+
AND status = 'active';
|
|
635
|
+
IF NOT FOUND THEN
|
|
636
|
+
RAISE EXCEPTION 'canonical human subject was not found' USING ERRCODE = 'P0002';
|
|
637
|
+
END IF;
|
|
638
|
+
SELECT * INTO identity_row
|
|
639
|
+
FROM canonical_human_identities
|
|
640
|
+
WHERE id = subject_row.identity_id
|
|
641
|
+
FOR UPDATE;
|
|
642
|
+
IF identity_row.identity_revision <> p_expected_identity_revision THEN
|
|
643
|
+
RAISE EXCEPTION 'canonical human identity revision conflict' USING ERRCODE = '40001';
|
|
644
|
+
END IF;
|
|
645
|
+
IF identity_row.status IN ('disputed', 'disabled') THEN
|
|
646
|
+
RAISE EXCEPTION 'canonical human identity is not mutable' USING ERRCODE = '42501';
|
|
647
|
+
END IF;
|
|
648
|
+
IF p_operation_type = 'link' AND identity_row.status = 'recovery_required' THEN
|
|
649
|
+
RAISE EXCEPTION 'canonical human identity requires explicit recovery completion'
|
|
650
|
+
USING ERRCODE = '42501';
|
|
651
|
+
END IF;
|
|
652
|
+
|
|
653
|
+
IF p_operation_type = 'link' THEN
|
|
654
|
+
IF NOT EXISTS (
|
|
655
|
+
SELECT 1 FROM auth_identities
|
|
656
|
+
WHERE user_id = p_auth_user_id
|
|
657
|
+
AND lower(provider_id) = normalized_provider
|
|
658
|
+
AND account_id = p_provider_account_id
|
|
659
|
+
) THEN
|
|
660
|
+
RAISE EXCEPTION 'login binding is not verified by the authentication provider'
|
|
661
|
+
USING ERRCODE = '42501';
|
|
662
|
+
END IF;
|
|
663
|
+
PERFORM pg_catalog.pg_advisory_xact_lock(
|
|
664
|
+
pg_catalog.hashtextextended(
|
|
665
|
+
'canonical-human-binding:' || normalized_provider || ':' || p_provider_account_id,
|
|
666
|
+
0
|
|
667
|
+
)
|
|
668
|
+
);
|
|
669
|
+
SELECT * INTO binding_row
|
|
670
|
+
FROM canonical_human_login_bindings
|
|
671
|
+
WHERE provider_id = normalized_provider
|
|
672
|
+
AND provider_account_id = p_provider_account_id
|
|
673
|
+
FOR UPDATE;
|
|
674
|
+
IF FOUND AND binding_row.identity_id <> identity_row.id THEN
|
|
675
|
+
UPDATE canonical_human_login_bindings
|
|
676
|
+
SET status = 'disputed',
|
|
677
|
+
revision = revision + 1,
|
|
678
|
+
revoked_at = NULL,
|
|
679
|
+
updated_at = pg_catalog.clock_timestamp()
|
|
680
|
+
WHERE id = binding_row.id;
|
|
681
|
+
UPDATE canonical_human_identities AS disputed_identity
|
|
682
|
+
SET status = 'disputed',
|
|
683
|
+
recovery_state = 'disputed',
|
|
684
|
+
identity_revision = disputed_identity.identity_revision + 1,
|
|
685
|
+
auth_revision = disputed_identity.auth_revision + 1,
|
|
686
|
+
updated_at = pg_catalog.clock_timestamp()
|
|
687
|
+
WHERE disputed_identity.id IN (identity_row.id, binding_row.identity_id);
|
|
688
|
+
DELETE FROM auth_sessions AS disputed_session
|
|
689
|
+
WHERE disputed_session.identity_id IN (identity_row.id, binding_row.identity_id);
|
|
690
|
+
SELECT * INTO identity_row
|
|
691
|
+
FROM canonical_human_identities WHERE id = identity_row.id;
|
|
692
|
+
outcome := 'disputed';
|
|
693
|
+
ELSE
|
|
694
|
+
IF NOT FOUND THEN
|
|
695
|
+
INSERT INTO canonical_human_login_bindings (
|
|
696
|
+
identity_id, provider_id, provider_account_id
|
|
697
|
+
) VALUES (
|
|
698
|
+
identity_row.id, normalized_provider, p_provider_account_id
|
|
699
|
+
) RETURNING * INTO binding_row;
|
|
700
|
+
ELSE
|
|
701
|
+
UPDATE canonical_human_login_bindings
|
|
702
|
+
SET status = 'active',
|
|
703
|
+
revision = revision + 1,
|
|
704
|
+
last_verified_at = pg_catalog.clock_timestamp(),
|
|
705
|
+
revoked_at = NULL,
|
|
706
|
+
updated_at = pg_catalog.clock_timestamp()
|
|
707
|
+
WHERE id = binding_row.id
|
|
708
|
+
RETURNING * INTO binding_row;
|
|
709
|
+
END IF;
|
|
710
|
+
UPDATE canonical_human_identities AS linked_identity
|
|
711
|
+
SET status = 'active',
|
|
712
|
+
recovery_state = 'ready',
|
|
713
|
+
active_login_binding_id = binding_row.id,
|
|
714
|
+
identity_revision = linked_identity.identity_revision + 1,
|
|
715
|
+
auth_revision = linked_identity.auth_revision + 1,
|
|
716
|
+
updated_at = pg_catalog.clock_timestamp()
|
|
717
|
+
WHERE linked_identity.id = identity_row.id
|
|
718
|
+
RETURNING * INTO identity_row;
|
|
719
|
+
DELETE FROM auth_sessions AS linked_session
|
|
720
|
+
WHERE linked_session.identity_id = identity_row.id;
|
|
721
|
+
outcome := 'applied';
|
|
722
|
+
END IF;
|
|
723
|
+
ELSE
|
|
724
|
+
SELECT * INTO binding_row
|
|
725
|
+
FROM canonical_human_login_bindings AS requested_binding
|
|
726
|
+
WHERE requested_binding.id = p_binding_id
|
|
727
|
+
AND requested_binding.identity_id = identity_row.id
|
|
728
|
+
FOR UPDATE;
|
|
729
|
+
IF NOT FOUND THEN
|
|
730
|
+
RAISE EXCEPTION 'canonical human login binding was not found' USING ERRCODE = 'P0002';
|
|
731
|
+
END IF;
|
|
732
|
+
|
|
733
|
+
IF p_operation_type = 'unlink' THEN
|
|
734
|
+
SELECT count(*)::integer INTO active_count
|
|
735
|
+
FROM canonical_human_login_bindings AS remaining_binding
|
|
736
|
+
WHERE remaining_binding.identity_id = identity_row.id
|
|
737
|
+
AND remaining_binding.status = 'active'
|
|
738
|
+
AND remaining_binding.id <> binding_row.id;
|
|
739
|
+
IF active_count < 1 THEN
|
|
740
|
+
UPDATE canonical_human_login_bindings
|
|
741
|
+
SET status = 'recovery_pending',
|
|
742
|
+
revision = revision + 1,
|
|
743
|
+
updated_at = pg_catalog.clock_timestamp()
|
|
744
|
+
WHERE id = binding_row.id;
|
|
745
|
+
UPDATE canonical_human_identities AS lost_factor_identity
|
|
746
|
+
SET status = 'recovery_required',
|
|
747
|
+
recovery_state = 'lost_factor',
|
|
748
|
+
active_login_binding_id = NULL,
|
|
749
|
+
identity_revision = lost_factor_identity.identity_revision + 1,
|
|
750
|
+
auth_revision = lost_factor_identity.auth_revision + 1,
|
|
751
|
+
updated_at = pg_catalog.clock_timestamp()
|
|
752
|
+
WHERE lost_factor_identity.id = identity_row.id
|
|
753
|
+
RETURNING * INTO identity_row;
|
|
754
|
+
outcome := 'lost_factor';
|
|
755
|
+
ELSE
|
|
756
|
+
DELETE FROM auth_identities
|
|
757
|
+
WHERE user_id = p_auth_user_id
|
|
758
|
+
AND lower(provider_id) = binding_row.provider_id
|
|
759
|
+
AND account_id = binding_row.provider_account_id;
|
|
760
|
+
UPDATE canonical_human_login_bindings
|
|
761
|
+
SET status = 'revoked',
|
|
762
|
+
revision = revision + 1,
|
|
763
|
+
revoked_at = pg_catalog.clock_timestamp(),
|
|
764
|
+
updated_at = pg_catalog.clock_timestamp()
|
|
765
|
+
WHERE id = binding_row.id;
|
|
766
|
+
SELECT replacement_binding.id INTO replacement_binding_id
|
|
767
|
+
FROM canonical_human_login_bindings AS replacement_binding
|
|
768
|
+
WHERE replacement_binding.identity_id = identity_row.id
|
|
769
|
+
AND replacement_binding.status = 'active'
|
|
770
|
+
ORDER BY replacement_binding.created_at, replacement_binding.id LIMIT 1;
|
|
771
|
+
UPDATE canonical_human_identities AS unlinked_identity
|
|
772
|
+
SET active_login_binding_id = replacement_binding_id,
|
|
773
|
+
identity_revision = unlinked_identity.identity_revision + 1,
|
|
774
|
+
auth_revision = unlinked_identity.auth_revision + 1,
|
|
775
|
+
updated_at = pg_catalog.clock_timestamp()
|
|
776
|
+
WHERE unlinked_identity.id = identity_row.id
|
|
777
|
+
RETURNING * INTO identity_row;
|
|
778
|
+
outcome := 'applied';
|
|
779
|
+
END IF;
|
|
780
|
+
ELSIF p_operation_type = 'begin_recovery' THEN
|
|
781
|
+
UPDATE canonical_human_login_bindings
|
|
782
|
+
SET status = 'recovery_pending',
|
|
783
|
+
revision = revision + 1,
|
|
784
|
+
updated_at = pg_catalog.clock_timestamp()
|
|
785
|
+
WHERE id = binding_row.id;
|
|
786
|
+
UPDATE canonical_human_identities AS recovering_identity
|
|
787
|
+
SET status = 'recovery_required',
|
|
788
|
+
recovery_state = 'recovery_required',
|
|
789
|
+
active_login_binding_id = NULL,
|
|
790
|
+
identity_revision = recovering_identity.identity_revision + 1,
|
|
791
|
+
auth_revision = recovering_identity.auth_revision + 1,
|
|
792
|
+
updated_at = pg_catalog.clock_timestamp()
|
|
793
|
+
WHERE recovering_identity.id = identity_row.id
|
|
794
|
+
RETURNING * INTO identity_row;
|
|
795
|
+
outcome := 'applied';
|
|
796
|
+
ELSE
|
|
797
|
+
IF NOT EXISTS (
|
|
798
|
+
SELECT 1 FROM auth_identities
|
|
799
|
+
WHERE user_id = p_auth_user_id
|
|
800
|
+
AND lower(provider_id) = binding_row.provider_id
|
|
801
|
+
AND account_id = binding_row.provider_account_id
|
|
802
|
+
) THEN
|
|
803
|
+
RAISE EXCEPTION 'login binding has not been reverified'
|
|
804
|
+
USING ERRCODE = '42501';
|
|
805
|
+
END IF;
|
|
806
|
+
UPDATE canonical_human_login_bindings
|
|
807
|
+
SET status = 'active',
|
|
808
|
+
revision = revision + 1,
|
|
809
|
+
last_verified_at = pg_catalog.clock_timestamp(),
|
|
810
|
+
revoked_at = NULL,
|
|
811
|
+
updated_at = pg_catalog.clock_timestamp()
|
|
812
|
+
WHERE id = binding_row.id;
|
|
813
|
+
UPDATE canonical_human_identities AS recovered_identity
|
|
814
|
+
SET status = 'active',
|
|
815
|
+
recovery_state = 'ready',
|
|
816
|
+
active_login_binding_id = binding_row.id,
|
|
817
|
+
identity_revision = recovered_identity.identity_revision + 1,
|
|
818
|
+
auth_revision = recovered_identity.auth_revision + 1,
|
|
819
|
+
updated_at = pg_catalog.clock_timestamp()
|
|
820
|
+
WHERE recovered_identity.id = identity_row.id
|
|
821
|
+
RETURNING * INTO identity_row;
|
|
822
|
+
outcome := 'applied';
|
|
823
|
+
END IF;
|
|
824
|
+
DELETE FROM auth_sessions AS invalidated_session
|
|
825
|
+
WHERE invalidated_session.identity_id = identity_row.id;
|
|
826
|
+
END IF;
|
|
827
|
+
|
|
828
|
+
INSERT INTO canonical_human_identity_operations (
|
|
829
|
+
operation_id,
|
|
830
|
+
identity_id,
|
|
831
|
+
actor_auth_user_id,
|
|
832
|
+
operation_type,
|
|
833
|
+
binding_id,
|
|
834
|
+
provider_id,
|
|
835
|
+
provider_account_id,
|
|
836
|
+
expected_identity_revision,
|
|
837
|
+
result_identity_revision,
|
|
838
|
+
result_auth_revision,
|
|
839
|
+
outcome,
|
|
840
|
+
reason
|
|
841
|
+
) VALUES (
|
|
842
|
+
p_operation_id,
|
|
843
|
+
identity_row.id,
|
|
844
|
+
p_auth_user_id,
|
|
845
|
+
p_operation_type,
|
|
846
|
+
CASE WHEN p_operation_type = 'link' THEN binding_row.id ELSE p_binding_id END,
|
|
847
|
+
CASE WHEN p_operation_type = 'link' THEN normalized_provider ELSE NULL END,
|
|
848
|
+
CASE WHEN p_operation_type = 'link' THEN p_provider_account_id ELSE NULL END,
|
|
849
|
+
p_expected_identity_revision,
|
|
850
|
+
identity_row.identity_revision,
|
|
851
|
+
identity_row.auth_revision,
|
|
852
|
+
outcome,
|
|
853
|
+
p_reason
|
|
854
|
+
);
|
|
855
|
+
|
|
856
|
+
identity_id := identity_row.id;
|
|
857
|
+
identity_revision := identity_row.identity_revision;
|
|
858
|
+
auth_revision := identity_row.auth_revision;
|
|
859
|
+
PERFORM pg_catalog.set_config(
|
|
860
|
+
'opengeni.canonical_human_identity_lifecycle',
|
|
861
|
+
CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END,
|
|
862
|
+
true
|
|
863
|
+
);
|
|
864
|
+
RETURN NEXT;
|
|
865
|
+
EXCEPTION WHEN OTHERS THEN
|
|
866
|
+
PERFORM pg_catalog.set_config(
|
|
867
|
+
'opengeni.canonical_human_identity_lifecycle',
|
|
868
|
+
CASE WHEN previous_marker IS NULL THEN '' ELSE previous_marker END,
|
|
869
|
+
true
|
|
870
|
+
);
|
|
871
|
+
RAISE;
|
|
872
|
+
END;
|
|
873
|
+
$body$;
|
|
874
|
+
$ddl$, data_schema);
|
|
875
|
+
|
|
876
|
+
EXECUTE format(
|
|
877
|
+
'REVOKE ALL ON FUNCTION %I.ensure_canonical_human_identity(text,text) FROM PUBLIC',
|
|
878
|
+
data_schema
|
|
879
|
+
);
|
|
880
|
+
EXECUTE format(
|
|
881
|
+
'REVOKE ALL ON FUNCTION %I.validate_canonical_human_session(text,text,boolean) FROM PUBLIC',
|
|
882
|
+
data_schema
|
|
883
|
+
);
|
|
884
|
+
EXECUTE format(
|
|
885
|
+
'REVOKE ALL ON FUNCTION %I.get_canonical_human_identity_projection(text) FROM PUBLIC',
|
|
886
|
+
data_schema
|
|
887
|
+
);
|
|
888
|
+
EXECUTE format(
|
|
889
|
+
'REVOKE ALL ON FUNCTION %I.apply_canonical_human_identity_operation(uuid,text,bigint,text,uuid,text,text,text) FROM PUBLIC',
|
|
890
|
+
data_schema
|
|
891
|
+
);
|
|
892
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
893
|
+
EXECUTE format(
|
|
894
|
+
'GRANT EXECUTE ON FUNCTION %I.ensure_canonical_human_identity(text,text) TO opengeni_app',
|
|
895
|
+
data_schema
|
|
896
|
+
);
|
|
897
|
+
EXECUTE format(
|
|
898
|
+
'GRANT EXECUTE ON FUNCTION %I.validate_canonical_human_session(text,text,boolean) TO opengeni_app',
|
|
899
|
+
data_schema
|
|
900
|
+
);
|
|
901
|
+
EXECUTE format(
|
|
902
|
+
'GRANT EXECUTE ON FUNCTION %I.get_canonical_human_identity_projection(text) TO opengeni_app',
|
|
903
|
+
data_schema
|
|
904
|
+
);
|
|
905
|
+
EXECUTE format(
|
|
906
|
+
'GRANT EXECUTE ON FUNCTION %I.apply_canonical_human_identity_operation(uuid,text,bigint,text,uuid,text,text,text) TO opengeni_app',
|
|
907
|
+
data_schema
|
|
908
|
+
);
|
|
909
|
+
END IF;
|
|
910
|
+
END
|
|
911
|
+
$canonical_human_identity_routines$;
|
|
912
|
+
|
|
913
|
+
COMMENT ON TABLE "canonical_human_identities" IS
|
|
914
|
+
'Organization-independent canonical human identity; never organization or workspace authority.';
|
|
915
|
+
COMMENT ON TABLE "canonical_human_login_bindings" IS
|
|
916
|
+
'Verified provider/account login bindings with deterministic recovery, dispute, stale, and revocation states.';
|
|
917
|
+
COMMENT ON TABLE "canonical_human_identity_operations" IS
|
|
918
|
+
'Append-only identity lifecycle audit and idempotency ledger; contains no organization or resource metadata.';
|
|
919
|
+
COMMENT ON COLUMN "auth_sessions"."auth_revision" IS
|
|
920
|
+
'Canonical-human authentication fence stamped at session creation; mismatch invalidates the session immediately.';
|