@logto/schemas 1.42.0 → 1.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/alterations/1.43.0-1785393731-add-oidc-model-instances-account-id-partial-index.ts +33 -0
  2. package/alterations/1.43.0-1785481546-add-tenant-aware-scope-indexes.ts +33 -0
  3. package/alterations/1.43.0-1785488115-add-cimd-permission-ceiling-tables.ts +77 -0
  4. package/alterations/1.43.0-1785722831-add-oidc-session-extensions-account-id-index.ts +27 -0
  5. package/alterations/1.43.0-1785835991-add-cimd-client-identifier-columns.ts +31 -0
  6. package/alterations/1.43.0-1786325989-add-cimd-grant-organizations-table.ts +54 -0
  7. package/alterations/1.43.0-1786333495-add-trusted-devices.ts +57 -0
  8. package/alterations/1.43.0-1786373004-tighten-cimd-grant-organizations-pkey.ts +28 -0
  9. package/alterations/1.43.0-1786414712-add-service-logs-email-indexes.ts +34 -0
  10. package/alterations/1.43.0-1786431364-add-cimd-grant-client-snapshots-table.ts +43 -0
  11. package/alterations/1.44.0-1788946735-add-saml-authn-request-config.ts +18 -0
  12. package/alterations/1.44.0-1789000000-extend-user-id-length.ts +61 -0
  13. package/alterations-js/1.43.0-1785393731-add-oidc-model-instances-account-id-partial-index.js +28 -0
  14. package/alterations-js/1.43.0-1785481546-add-tenant-aware-scope-indexes.js +28 -0
  15. package/alterations-js/1.43.0-1785488115-add-cimd-permission-ceiling-tables.js +69 -0
  16. package/alterations-js/1.43.0-1785722831-add-oidc-session-extensions-account-id-index.js +23 -0
  17. package/alterations-js/1.43.0-1785835991-add-cimd-client-identifier-columns.js +27 -0
  18. package/alterations-js/1.43.0-1786325989-add-cimd-grant-organizations-table.js +49 -0
  19. package/alterations-js/1.43.0-1786333495-add-trusted-devices.js +50 -0
  20. package/alterations-js/1.43.0-1786373004-tighten-cimd-grant-organizations-pkey.js +24 -0
  21. package/alterations-js/1.43.0-1786414712-add-service-logs-email-indexes.js +30 -0
  22. package/alterations-js/1.43.0-1786431364-add-cimd-grant-client-snapshots-table.js +38 -0
  23. package/alterations-js/1.44.0-1788946735-add-saml-authn-request-config.js +14 -0
  24. package/alterations-js/1.44.0-1789000000-extend-user-id-length.js +47 -0
  25. package/lib/consts/experience.d.ts +3 -0
  26. package/lib/consts/experience.js +3 -0
  27. package/lib/consts/index.d.ts +2 -0
  28. package/lib/consts/index.js +2 -0
  29. package/lib/consts/license.d.ts +222 -0
  30. package/lib/consts/license.js +81 -0
  31. package/lib/consts/license.test.d.ts +1 -0
  32. package/lib/consts/license.test.js +96 -0
  33. package/lib/consts/oidc.d.ts +15 -1
  34. package/lib/consts/oidc.js +11 -0
  35. package/lib/consts/oidc.test.d.ts +1 -0
  36. package/lib/consts/oidc.test.js +15 -0
  37. package/lib/consts/product-event.d.ts +5 -1
  38. package/lib/consts/product-event.js +4 -0
  39. package/lib/consts/social-state.d.ts +12 -0
  40. package/lib/consts/social-state.js +12 -0
  41. package/lib/consts/subscriptions.d.ts +13 -1
  42. package/lib/consts/subscriptions.js +12 -0
  43. package/lib/db-entries/aggregated-daily-active-user.js +2 -2
  44. package/lib/db-entries/application-access-control-user-relation.js +2 -2
  45. package/lib/db-entries/application-user-consent-organization.js +2 -2
  46. package/lib/db-entries/cimd-grant-client-snapshot.d.ts +32 -0
  47. package/lib/db-entries/cimd-grant-client-snapshot.js +45 -0
  48. package/lib/db-entries/cimd-grant-organization.d.ts +30 -0
  49. package/lib/db-entries/cimd-grant-organization.js +37 -0
  50. package/lib/db-entries/cimd-organization-resource-scope.d.ts +20 -0
  51. package/lib/db-entries/cimd-organization-resource-scope.js +25 -0
  52. package/lib/db-entries/cimd-organization-scope.d.ts +20 -0
  53. package/lib/db-entries/cimd-organization-scope.js +25 -0
  54. package/lib/db-entries/cimd-resource-scope.d.ts +20 -0
  55. package/lib/db-entries/cimd-resource-scope.js +25 -0
  56. package/lib/db-entries/cimd-user-scope.d.ts +20 -0
  57. package/lib/db-entries/cimd-user-scope.js +25 -0
  58. package/lib/db-entries/daily-active-user.js +2 -2
  59. package/lib/db-entries/index.d.ts +7 -0
  60. package/lib/db-entries/index.js +7 -0
  61. package/lib/db-entries/oidc-session-extension.d.ts +3 -1
  62. package/lib/db-entries/oidc-session-extension.js +6 -2
  63. package/lib/db-entries/organization-invitation.js +4 -4
  64. package/lib/db-entries/organization-role-user-relation.js +2 -2
  65. package/lib/db-entries/organization-user-relation.js +2 -2
  66. package/lib/db-entries/organization.d.ts +5 -1
  67. package/lib/db-entries/organization.js +4 -0
  68. package/lib/db-entries/personal-access-token.js +2 -2
  69. package/lib/db-entries/saml-application-config.d.ts +4 -2
  70. package/lib/db-entries/saml-application-config.js +5 -1
  71. package/lib/db-entries/secret.js +2 -2
  72. package/lib/db-entries/sign-in-experience.d.ts +4 -2
  73. package/lib/db-entries/sign-in-experience.js +5 -1
  74. package/lib/db-entries/subject-token.js +2 -2
  75. package/lib/db-entries/trusted-device.d.ts +36 -0
  76. package/lib/db-entries/trusted-device.js +62 -0
  77. package/lib/db-entries/user-geo-location.js +2 -2
  78. package/lib/db-entries/user-sign-in-country.js +2 -2
  79. package/lib/db-entries/user-sso-identity.js +2 -2
  80. package/lib/db-entries/user.d.ts +3 -1
  81. package/lib/db-entries/user.js +6 -2
  82. package/lib/db-entries/users-role.js +2 -2
  83. package/lib/db-entries/verification-record.js +2 -2
  84. package/lib/db-entries/verification-status.js +2 -2
  85. package/lib/foundations/jsonb-types/account-centers.d.ts +3 -0
  86. package/lib/foundations/jsonb-types/account-centers.js +1 -0
  87. package/lib/foundations/jsonb-types/account-centers.test.d.ts +1 -0
  88. package/lib/foundations/jsonb-types/account-centers.test.js +13 -0
  89. package/lib/foundations/jsonb-types/captcha.d.ts +47 -1
  90. package/lib/foundations/jsonb-types/captcha.js +20 -0
  91. package/lib/foundations/jsonb-types/hooks.d.ts +5 -4
  92. package/lib/foundations/jsonb-types/hooks.js +3 -0
  93. package/lib/foundations/jsonb-types/logs.d.ts +18 -0
  94. package/lib/foundations/jsonb-types/logs.js +6 -0
  95. package/lib/foundations/jsonb-types/saml-application-configs.d.ts +23 -0
  96. package/lib/foundations/jsonb-types/saml-application-configs.js +16 -0
  97. package/lib/foundations/jsonb-types/saml-application-configs.test.js +24 -1
  98. package/lib/foundations/jsonb-types/sign-in-experience.d.ts +23 -3
  99. package/lib/foundations/jsonb-types/sign-in-experience.js +9 -0
  100. package/lib/foundations/jsonb-types/sign-in-experience.test.js +17 -1
  101. package/lib/types/authentication-context.d.ts +224 -0
  102. package/lib/types/authentication-context.js +238 -0
  103. package/lib/types/authentication-context.test.d.ts +1 -0
  104. package/lib/types/authentication-context.test.js +186 -0
  105. package/lib/types/connector.d.ts +8 -0
  106. package/lib/types/consent.d.ts +16 -190
  107. package/lib/types/consent.js +10 -1
  108. package/lib/types/cookie.d.ts +4 -0
  109. package/lib/types/cookie.js +6 -1
  110. package/lib/types/custom-profile-fields.d.ts +1 -0
  111. package/lib/types/hook.d.ts +31 -6
  112. package/lib/types/index.d.ts +2 -0
  113. package/lib/types/index.js +2 -0
  114. package/lib/types/interactions.d.ts +161 -0
  115. package/lib/types/interactions.js +23 -0
  116. package/lib/types/log/index.d.ts +5 -2
  117. package/lib/types/log/index.js +1 -0
  118. package/lib/types/log/interaction.d.ts +15 -2
  119. package/lib/types/log/interaction.js +5 -0
  120. package/lib/types/log/trusted-device.d.ts +7 -0
  121. package/lib/types/log/trusted-device.js +6 -0
  122. package/lib/types/logto-config/action.d.ts +1 -1
  123. package/lib/types/logto-config/action.js +1 -1
  124. package/lib/types/logto-config/index.d.ts +83 -7
  125. package/lib/types/logto-config/index.js +14 -0
  126. package/lib/types/logto-config/index.test.js +23 -0
  127. package/lib/types/logto-config/jwt-customizer.d.ts +123 -10
  128. package/lib/types/logto-config/jwt-customizer.js +1 -1
  129. package/lib/types/logto-config/oidc-provider.d.ts +2 -2
  130. package/lib/types/saml-application.d.ts +55 -1
  131. package/lib/types/saml-application.js +7 -1
  132. package/lib/types/service-log.d.ts +14 -1
  133. package/lib/types/service-log.js +13 -0
  134. package/lib/types/sign-in-experience.d.ts +20 -6
  135. package/lib/types/sign-in-experience.js +1 -0
  136. package/lib/types/ssr.d.ts +2 -0
  137. package/lib/types/system.d.ts +59 -7
  138. package/lib/types/system.js +36 -0
  139. package/lib/types/system.test.d.ts +1 -0
  140. package/lib/types/system.test.js +26 -0
  141. package/lib/types/trusted-device.d.ts +67 -0
  142. package/lib/types/trusted-device.js +16 -0
  143. package/lib/types/user-sessions.d.ts +195 -209
  144. package/lib/types/user-sessions.js +9 -4
  145. package/lib/types/user.d.ts +10 -3
  146. package/lib/types/user.js +1 -0
  147. package/lib/types/verification-records/code-verification.d.ts +17 -0
  148. package/lib/types/verification-records/code-verification.js +1 -0
  149. package/lib/utils/cimd.d.ts +14 -0
  150. package/lib/utils/cimd.js +14 -0
  151. package/lib/utils/cimd.test.d.ts +1 -0
  152. package/lib/utils/cimd.test.js +9 -0
  153. package/lib/utils/index.d.ts +1 -0
  154. package/lib/utils/index.js +1 -0
  155. package/lib/utils/oidc-private-key.test.js +44 -0
  156. package/package.json +9 -9
  157. package/tables/aggregated_daily_active_users.sql +1 -1
  158. package/tables/application_access_control_user_relations.sql +1 -1
  159. package/tables/application_user_consent_organizations.sql +1 -1
  160. package/tables/cimd_grant_client_snapshots.sql +27 -0
  161. package/tables/cimd_grant_organizations.sql +38 -0
  162. package/tables/cimd_organization_resource_scopes.sql +16 -0
  163. package/tables/cimd_organization_scopes.sql +15 -0
  164. package/tables/cimd_resource_scopes.sql +15 -0
  165. package/tables/cimd_user_scopes.sql +13 -0
  166. package/tables/daily_active_users.sql +1 -1
  167. package/tables/oidc_model_instances.sql +5 -0
  168. package/tables/oidc_session_extensions.sql +6 -1
  169. package/tables/organization_invitations.sql +2 -2
  170. package/tables/organization_role_user_relations.sql +1 -1
  171. package/tables/organization_scopes.sql +4 -0
  172. package/tables/organization_user_relations.sql +1 -1
  173. package/tables/organizations.sql +2 -0
  174. package/tables/personal_access_tokens.sql +1 -1
  175. package/tables/saml_application_configs.sql +1 -0
  176. package/tables/scopes.sql +4 -0
  177. package/tables/secrets.sql +1 -1
  178. package/tables/service_logs.sql +14 -0
  179. package/tables/sign_in_experiences.sql +1 -0
  180. package/tables/subject_tokens.sql +1 -1
  181. package/tables/trusted_devices.sql +25 -0
  182. package/tables/user_geo_locations.sql +1 -1
  183. package/tables/user_sign_in_countries.sql +1 -1
  184. package/tables/user_sso_identities.sql +1 -1
  185. package/tables/users.sql +2 -1
  186. package/tables/users_roles.sql +1 -1
  187. package/tables/verification_records.sql +1 -1
  188. package/tables/verification_statuses.sql +1 -1
  189. package/LICENSE +0 -373
@@ -0,0 +1,33 @@
1
+ import { sql } from '@silverhand/slonik';
2
+
3
+ import type { AlterationScript } from '../lib/types/alteration.js';
4
+
5
+ const alteration: AlterationScript = {
6
+ beforeUp: async (pool) => {
7
+ // Add index to optimize revoking access tokens and refresh tokens by accountId.
8
+ // See the index comment in tables/oidc_model_instances.sql for the predicate rationale.
9
+ await pool.query(sql`
10
+ create index concurrently oidc_model_instances__model_name_payload_account_id_partial
11
+ on oidc_model_instances (tenant_id, model_name, (payload->>'accountId'))
12
+ where payload ? 'accountId';
13
+ `);
14
+
15
+ // Collect expression statistics right away instead of waiting for autovacuum.
16
+ await pool.query(sql`
17
+ analyze oidc_model_instances;
18
+ `);
19
+ },
20
+ up: async () => {
21
+ /** 'concurrently' cannot be used inside a transaction, so this up is intentionally left empty. */
22
+ },
23
+ beforeDown: async (pool) => {
24
+ await pool.query(sql`
25
+ drop index concurrently oidc_model_instances__model_name_payload_account_id_partial;
26
+ `);
27
+ },
28
+ down: async () => {
29
+ /** 'concurrently' cannot be used inside a transaction, so this down is intentionally left empty. */
30
+ },
31
+ };
32
+
33
+ export default alteration;
@@ -0,0 +1,33 @@
1
+ import { sql } from '@silverhand/slonik';
2
+
3
+ import type { AlterationScript } from '../lib/types/alteration.js';
4
+
5
+ const alteration: AlterationScript = {
6
+ beforeUp: async (pool) => {
7
+ await pool.query(sql`
8
+ create unique index concurrently scopes__tenant_id_id
9
+ on scopes (tenant_id, id);
10
+ `);
11
+
12
+ await pool.query(sql`
13
+ create unique index concurrently organization_scopes__tenant_id_id
14
+ on organization_scopes (tenant_id, id);
15
+ `);
16
+ },
17
+ up: async () => {
18
+ // The indexes must be created outside of a transaction.
19
+ },
20
+ beforeDown: async (pool) => {
21
+ await pool.query(sql`
22
+ drop index concurrently if exists organization_scopes__tenant_id_id;
23
+ `);
24
+ await pool.query(sql`
25
+ drop index concurrently if exists scopes__tenant_id_id;
26
+ `);
27
+ },
28
+ down: async () => {
29
+ // The indexes must be dropped outside of a transaction.
30
+ },
31
+ };
32
+
33
+ export default alteration;
@@ -0,0 +1,77 @@
1
+ import { sql } from '@silverhand/slonik';
2
+
3
+ import type { AlterationScript } from '../lib/types/alteration.js';
4
+
5
+ import { applyTableRls, dropTableRls } from './utils/1704934999-tables.js';
6
+
7
+ /**
8
+ * Add the tenant-level permission ceiling tables for client ID metadata document (CIMD) clients.
9
+ * The ceiling is tenant-wide because CIMD clients are unregistered and have no per-client record
10
+ * to attach configuration to. The tables reference the tenant and scope tables directly (not the
11
+ * CIMD config row), so disabling CIMD does not wipe the permission settings.
12
+ * Rows are an allowlist: an empty table allows nothing (deny-all), and an unrestricted state is not
13
+ * expressible.
14
+ */
15
+ const alteration: AlterationScript = {
16
+ up: async (pool) => {
17
+ await pool.query(sql`
18
+ create table cimd_user_scopes (
19
+ tenant_id varchar(21) not null
20
+ references tenants (id) on update cascade on delete cascade,
21
+ user_scope varchar(64) not null,
22
+ primary key (tenant_id, user_scope)
23
+ );
24
+ `);
25
+ await applyTableRls(pool, 'cimd_user_scopes');
26
+
27
+ await pool.query(sql`
28
+ create table cimd_resource_scopes (
29
+ tenant_id varchar(21) not null
30
+ references tenants (id) on update cascade on delete cascade,
31
+ scope_id varchar(21) not null,
32
+ primary key (tenant_id, scope_id),
33
+ foreign key (tenant_id, scope_id)
34
+ references scopes (tenant_id, id) on update cascade on delete cascade
35
+ );
36
+ `);
37
+ await applyTableRls(pool, 'cimd_resource_scopes');
38
+
39
+ await pool.query(sql`
40
+ create table cimd_organization_scopes (
41
+ tenant_id varchar(21) not null
42
+ references tenants (id) on update cascade on delete cascade,
43
+ organization_scope_id varchar(21) not null,
44
+ primary key (tenant_id, organization_scope_id),
45
+ foreign key (tenant_id, organization_scope_id)
46
+ references organization_scopes (tenant_id, id) on update cascade on delete cascade
47
+ );
48
+ `);
49
+ await applyTableRls(pool, 'cimd_organization_scopes');
50
+
51
+ await pool.query(sql`
52
+ create table cimd_organization_resource_scopes (
53
+ tenant_id varchar(21) not null
54
+ references tenants (id) on update cascade on delete cascade,
55
+ scope_id varchar(21) not null,
56
+ primary key (tenant_id, scope_id),
57
+ foreign key (tenant_id, scope_id)
58
+ references scopes (tenant_id, id) on update cascade on delete cascade
59
+ );
60
+ `);
61
+ await applyTableRls(pool, 'cimd_organization_resource_scopes');
62
+ },
63
+ down: async (pool) => {
64
+ await dropTableRls(pool, 'cimd_organization_resource_scopes');
65
+ await dropTableRls(pool, 'cimd_organization_scopes');
66
+ await dropTableRls(pool, 'cimd_resource_scopes');
67
+ await dropTableRls(pool, 'cimd_user_scopes');
68
+ await pool.query(sql`
69
+ drop table cimd_organization_resource_scopes;
70
+ drop table cimd_organization_scopes;
71
+ drop table cimd_resource_scopes;
72
+ drop table cimd_user_scopes;
73
+ `);
74
+ },
75
+ };
76
+
77
+ export default alteration;
@@ -0,0 +1,27 @@
1
+ import { sql } from '@silverhand/slonik';
2
+
3
+ import type { AlterationScript } from '../lib/types/alteration.js';
4
+
5
+ const alteration: AlterationScript = {
6
+ beforeUp: async (pool) => {
7
+ // Add index to optimize deleting session extensions by accountId, including the
8
+ // cascades from the users table. See the index comment in tables/oidc_session_extensions.sql.
9
+ await pool.query(sql`
10
+ create index concurrently oidc_session_extensions__account_id
11
+ on oidc_session_extensions (account_id);
12
+ `);
13
+ },
14
+ up: async () => {
15
+ /** 'concurrently' cannot be used inside a transaction, so this up is intentionally left empty. */
16
+ },
17
+ beforeDown: async (pool) => {
18
+ await pool.query(sql`
19
+ drop index concurrently oidc_session_extensions__account_id;
20
+ `);
21
+ },
22
+ down: async () => {
23
+ /** 'concurrently' cannot be used inside a transaction, so this down is intentionally left empty. */
24
+ },
25
+ };
26
+
27
+ export default alteration;
@@ -0,0 +1,31 @@
1
+ import { sql } from '@silverhand/slonik';
2
+
3
+ import type { AlterationScript } from '../lib/types/alteration.js';
4
+
5
+ /**
6
+ * Registered application IDs (21 chars, referencing `applications`) and CIMD (client ID metadata
7
+ * document) client identifier URLs are different value spaces, so each gets its own column instead
8
+ * of sharing one.
9
+ */
10
+ const alteration: AlterationScript = {
11
+ up: async (pool) => {
12
+ await pool.query(sql`
13
+ alter table users
14
+ add column cimd_client_id varchar(2048);
15
+
16
+ alter table oidc_session_extensions
17
+ add column cimd_client_id varchar(2048);
18
+ `);
19
+ },
20
+ down: async (pool) => {
21
+ await pool.query(sql`
22
+ alter table users
23
+ drop column cimd_client_id;
24
+
25
+ alter table oidc_session_extensions
26
+ drop column cimd_client_id;
27
+ `);
28
+ },
29
+ };
30
+
31
+ export default alteration;
@@ -0,0 +1,54 @@
1
+ import { sql } from '@silverhand/slonik';
2
+
3
+ import type { AlterationScript } from '../lib/types/alteration.js';
4
+
5
+ import { applyTableRls, dropTableRls } from './utils/1704934999-tables.js';
6
+
7
+ /**
8
+ * Add the grant-scoped organization authorization table for client ID metadata document (CIMD)
9
+ * clients. CIMD clients are unregistered URL identities with no deletion lifecycle and
10
+ * transferable ownership, so organization access follows the single authorization instead of a
11
+ * long-lived cross-grant relation: rows cascade away with the Grant row (revocation hard-deletes
12
+ * it) and with the user's organization membership.
13
+ */
14
+ const alteration: AlterationScript = {
15
+ up: async (pool) => {
16
+ await pool.query(sql`
17
+ create table cimd_grant_organizations (
18
+ tenant_id varchar(21) not null
19
+ references tenants (id) on update cascade on delete cascade,
20
+ grant_id varchar(128) not null,
21
+ grant_model_name varchar(64) not null default 'Grant',
22
+ organization_id varchar(21) not null,
23
+ user_id varchar(21) not null,
24
+ primary key (tenant_id, grant_id, organization_id),
25
+ constraint cimd_grant_organizations__grant_model_name
26
+ check (grant_model_name = 'Grant'),
27
+ foreign key (tenant_id, grant_model_name, grant_id)
28
+ references oidc_model_instances (tenant_id, model_name, id)
29
+ on update cascade on delete cascade,
30
+ foreign key (tenant_id, organization_id, user_id)
31
+ references organization_user_relations (tenant_id, organization_id, user_id)
32
+ on update cascade on delete cascade
33
+ );
34
+ `);
35
+ /**
36
+ * Backs the membership FK's cascade lookup; the PK leaves `organization_id` outside its
37
+ * usable prefix. The table is created empty in this transaction, so a plain (non-concurrent)
38
+ * index build is free.
39
+ */
40
+ await pool.query(sql`
41
+ create index cimd_grant_organizations__organization_id_user_id
42
+ on cimd_grant_organizations (tenant_id, organization_id, user_id);
43
+ `);
44
+ await applyTableRls(pool, 'cimd_grant_organizations');
45
+ },
46
+ down: async (pool) => {
47
+ await dropTableRls(pool, 'cimd_grant_organizations');
48
+ await pool.query(sql`
49
+ drop table cimd_grant_organizations;
50
+ `);
51
+ },
52
+ };
53
+
54
+ export default alteration;
@@ -0,0 +1,57 @@
1
+ import { sql } from '@silverhand/slonik';
2
+
3
+ import type { AlterationScript } from '../lib/types/alteration.js';
4
+
5
+ import { applyTableRls, dropTableRls } from './utils/1704934999-tables.js';
6
+
7
+ const alteration: AlterationScript = {
8
+ up: async (pool) => {
9
+ await pool.query(sql`
10
+ create table trusted_devices (
11
+ tenant_id varchar(21) not null
12
+ references tenants (id) on update cascade on delete cascade,
13
+ id varchar(21) not null,
14
+ user_id varchar(12) not null
15
+ references users (id) on update cascade on delete cascade,
16
+ secret_hash bytea not null,
17
+ user_agent text,
18
+ ip text,
19
+ country varchar(16),
20
+ city text,
21
+ created_at timestamptz not null default(now()),
22
+ last_used_at timestamptz not null default(now()),
23
+ expires_at timestamptz not null,
24
+ primary key (id)
25
+ );
26
+
27
+ create index trusted_devices__tenant_user_expires_at
28
+ on trusted_devices (tenant_id, user_id, expires_at);
29
+
30
+ create index trusted_devices__tenant_expires_at
31
+ on trusted_devices (tenant_id, expires_at);
32
+
33
+ alter table sign_in_experiences
34
+ add column trusted_device jsonb not null default '{}'::jsonb;
35
+
36
+ alter table organizations
37
+ add column is_trusted_device_allowed boolean not null default true;
38
+ `);
39
+
40
+ await applyTableRls(pool, 'trusted_devices');
41
+ },
42
+ down: async (pool) => {
43
+ await dropTableRls(pool, 'trusted_devices');
44
+
45
+ await pool.query(sql`
46
+ drop table trusted_devices;
47
+
48
+ alter table sign_in_experiences
49
+ drop column trusted_device;
50
+
51
+ alter table organizations
52
+ drop column is_trusted_device_allowed;
53
+ `);
54
+ },
55
+ };
56
+
57
+ export default alteration;
@@ -0,0 +1,28 @@
1
+ import { sql } from '@silverhand/slonik';
2
+
3
+ import type { AlterationScript } from '../lib/types/alteration.js';
4
+
5
+ /**
6
+ * Tighten the `cimd_grant_organizations` primary key to `(tenant_id, grant_id)` so the schema
7
+ * itself enforces the at-most-one-organization-per-grant invariant that token issuance relies on,
8
+ * instead of leaving it to the consent flow's composition. The table is dev-feature-gated and
9
+ * empty in every environment, so swapping the constraint needs no data migration.
10
+ */
11
+ const alteration: AlterationScript = {
12
+ up: async (pool) => {
13
+ await pool.query(sql`
14
+ alter table cimd_grant_organizations
15
+ drop constraint cimd_grant_organizations_pkey,
16
+ add primary key (tenant_id, grant_id);
17
+ `);
18
+ },
19
+ down: async (pool) => {
20
+ await pool.query(sql`
21
+ alter table cimd_grant_organizations
22
+ drop constraint cimd_grant_organizations_pkey,
23
+ add primary key (tenant_id, grant_id, organization_id);
24
+ `);
25
+ },
26
+ };
27
+
28
+ export default alteration;
@@ -0,0 +1,34 @@
1
+ import { sql } from '@silverhand/slonik';
2
+
3
+ import type { AlterationScript } from '../lib/types/alteration.js';
4
+
5
+ const alteration: AlterationScript = {
6
+ beforeUp: async (pool) => {
7
+ await pool.query(sql`
8
+ create index concurrently service_logs__email__tenant_id__created_at__id
9
+ on service_logs (tenant_id, created_at desc, id desc)
10
+ where type in ('sendEmail', 'sendEmailFailed');
11
+ `);
12
+ await pool.query(sql`
13
+ create index concurrently service_logs__email__tenant_id__recipient__created_at__id
14
+ on service_logs (tenant_id, lower(payload->'data'->>'to'), created_at desc, id desc)
15
+ where type in ('sendEmail', 'sendEmailFailed');
16
+ `);
17
+ },
18
+ up: async () => {
19
+ /** 'concurrently' cannot be used inside a transaction, so this up is intentionally left empty. */
20
+ },
21
+ beforeDown: async (pool) => {
22
+ await pool.query(sql`
23
+ drop index concurrently service_logs__email__tenant_id__recipient__created_at__id;
24
+ `);
25
+ await pool.query(sql`
26
+ drop index concurrently service_logs__email__tenant_id__created_at__id;
27
+ `);
28
+ },
29
+ down: async () => {
30
+ /** 'concurrently' cannot be used inside a transaction, so this down is intentionally left empty. */
31
+ },
32
+ };
33
+
34
+ export default alteration;
@@ -0,0 +1,43 @@
1
+ import { sql } from '@silverhand/slonik';
2
+
3
+ import type { AlterationScript } from '../lib/types/alteration.js';
4
+
5
+ import { applyTableRls, dropTableRls } from './utils/1704934999-tables.js';
6
+
7
+ /**
8
+ * Add the per-grant client display snapshot table for client ID metadata document (CIMD) clients.
9
+ * CIMD clients are unregistered URL identities without an `applications` row, and an unvetted
10
+ * client can rewrite its hosted document at any time — so the grant list renders the snapshot
11
+ * captured at consent instead of refetching the document. Rows cascade away with the Grant row.
12
+ */
13
+ const alteration: AlterationScript = {
14
+ up: async (pool) => {
15
+ await pool.query(sql`
16
+ create table cimd_grant_client_snapshots (
17
+ tenant_id varchar(21) not null
18
+ references tenants (id) on update cascade on delete cascade,
19
+ grant_id varchar(128) not null,
20
+ grant_model_name varchar(64) not null default 'Grant',
21
+ client_id varchar(2048) not null,
22
+ name varchar(256),
23
+ logo_uri varchar(2048),
24
+ created_at timestamptz not null default(now()),
25
+ primary key (tenant_id, grant_id),
26
+ constraint cimd_grant_client_snapshots__grant_model_name
27
+ check (grant_model_name = 'Grant'),
28
+ foreign key (tenant_id, grant_model_name, grant_id)
29
+ references oidc_model_instances (tenant_id, model_name, id)
30
+ on update cascade on delete cascade
31
+ );
32
+ `);
33
+ await applyTableRls(pool, 'cimd_grant_client_snapshots');
34
+ },
35
+ down: async (pool) => {
36
+ await dropTableRls(pool, 'cimd_grant_client_snapshots');
37
+ await pool.query(sql`
38
+ drop table cimd_grant_client_snapshots;
39
+ `);
40
+ },
41
+ };
42
+
43
+ export default alteration;
@@ -0,0 +1,18 @@
1
+ import { sql } from '@silverhand/slonik';
2
+
3
+ import type { AlterationScript } from '../lib/types/alteration.js';
4
+
5
+ const alteration: AlterationScript = {
6
+ up: async (pool) => {
7
+ await pool.query(sql`
8
+ alter table saml_application_configs add column authn_request_config jsonb;
9
+ `);
10
+ },
11
+ down: async (pool) => {
12
+ await pool.query(sql`
13
+ alter table saml_application_configs drop column authn_request_config;
14
+ `);
15
+ },
16
+ };
17
+
18
+ export default alteration;
@@ -0,0 +1,61 @@
1
+ import { sql } from '@silverhand/slonik';
2
+
3
+ import type { AlterationScript } from '../lib/types/alteration.js';
4
+
5
+ /**
6
+ * Widen `users.id` and every column that references it to varchar(128).
7
+ *
8
+ * Users migrated from other identity providers may carry over their original IDs, which are
9
+ * often longer than the 12 or 21 characters previously allowed.
10
+ */
11
+
12
+ type Column = [table: string, column: string, originalLength: 12 | 21];
13
+
14
+ const columns: Column[] = [
15
+ ['users', 'id', 12],
16
+ ['user_sign_in_countries', 'user_id', 12],
17
+ ['user_geo_locations', 'user_id', 12],
18
+ ['trusted_devices', 'user_id', 12],
19
+ ['user_sso_identities', 'user_id', 12],
20
+ ['oidc_session_extensions', 'account_id', 12],
21
+ ['users_roles', 'user_id', 21],
22
+ ['organization_user_relations', 'user_id', 21],
23
+ ['organization_role_user_relations', 'user_id', 21],
24
+ ['organization_invitations', 'inviter_id', 21],
25
+ ['organization_invitations', 'accepted_user_id', 21],
26
+ ['subject_tokens', 'user_id', 21],
27
+ ['verification_statuses', 'user_id', 21],
28
+ ['verification_records', 'user_id', 21],
29
+ ['secrets', 'user_id', 21],
30
+ ['personal_access_tokens', 'user_id', 21],
31
+ ['application_access_control_user_relations', 'user_id', 21],
32
+ ['application_user_consent_organizations', 'user_id', 21],
33
+ ['cimd_grant_organizations', 'user_id', 21],
34
+ ['daily_active_users', 'user_id', 21],
35
+ ['aggregated_daily_active_users', 'user_id', 21],
36
+ ];
37
+
38
+ const alteration: AlterationScript = {
39
+ up: async (pool) => {
40
+ for (const [table, column] of columns) {
41
+ // eslint-disable-next-line no-await-in-loop -- DDL statements must run sequentially
42
+ await pool.query(sql`
43
+ alter table ${sql.identifier([table])}
44
+ alter column ${sql.identifier([column])} type varchar(128)
45
+ `);
46
+ }
47
+ },
48
+ down: async (pool) => {
49
+ // Shrinking fails if any existing value is longer than the original limit; there is no
50
+ // safe way to truncate an ID, so let Postgres reject the rollback in that case.
51
+ for (const [table, column, originalLength] of columns) {
52
+ // eslint-disable-next-line no-await-in-loop -- DDL statements must run sequentially
53
+ await pool.query(sql`
54
+ alter table ${sql.identifier([table])}
55
+ alter column ${sql.identifier([column])} type varchar(${sql.raw(String(originalLength))})
56
+ `);
57
+ }
58
+ },
59
+ };
60
+
61
+ export default alteration;
@@ -0,0 +1,28 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ const alteration = {
3
+ beforeUp: async (pool) => {
4
+ // Add index to optimize revoking access tokens and refresh tokens by accountId.
5
+ // See the index comment in tables/oidc_model_instances.sql for the predicate rationale.
6
+ await pool.query(sql `
7
+ create index concurrently oidc_model_instances__model_name_payload_account_id_partial
8
+ on oidc_model_instances (tenant_id, model_name, (payload->>'accountId'))
9
+ where payload ? 'accountId';
10
+ `);
11
+ // Collect expression statistics right away instead of waiting for autovacuum.
12
+ await pool.query(sql `
13
+ analyze oidc_model_instances;
14
+ `);
15
+ },
16
+ up: async () => {
17
+ /** 'concurrently' cannot be used inside a transaction, so this up is intentionally left empty. */
18
+ },
19
+ beforeDown: async (pool) => {
20
+ await pool.query(sql `
21
+ drop index concurrently oidc_model_instances__model_name_payload_account_id_partial;
22
+ `);
23
+ },
24
+ down: async () => {
25
+ /** 'concurrently' cannot be used inside a transaction, so this down is intentionally left empty. */
26
+ },
27
+ };
28
+ export default alteration;
@@ -0,0 +1,28 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ const alteration = {
3
+ beforeUp: async (pool) => {
4
+ await pool.query(sql `
5
+ create unique index concurrently scopes__tenant_id_id
6
+ on scopes (tenant_id, id);
7
+ `);
8
+ await pool.query(sql `
9
+ create unique index concurrently organization_scopes__tenant_id_id
10
+ on organization_scopes (tenant_id, id);
11
+ `);
12
+ },
13
+ up: async () => {
14
+ // The indexes must be created outside of a transaction.
15
+ },
16
+ beforeDown: async (pool) => {
17
+ await pool.query(sql `
18
+ drop index concurrently if exists organization_scopes__tenant_id_id;
19
+ `);
20
+ await pool.query(sql `
21
+ drop index concurrently if exists scopes__tenant_id_id;
22
+ `);
23
+ },
24
+ down: async () => {
25
+ // The indexes must be dropped outside of a transaction.
26
+ },
27
+ };
28
+ export default alteration;
@@ -0,0 +1,69 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ import { applyTableRls, dropTableRls } from './utils/1704934999-tables.js';
3
+ /**
4
+ * Add the tenant-level permission ceiling tables for client ID metadata document (CIMD) clients.
5
+ * The ceiling is tenant-wide because CIMD clients are unregistered and have no per-client record
6
+ * to attach configuration to. The tables reference the tenant and scope tables directly (not the
7
+ * CIMD config row), so disabling CIMD does not wipe the permission settings.
8
+ * Rows are an allowlist: an empty table allows nothing (deny-all), and an unrestricted state is not
9
+ * expressible.
10
+ */
11
+ const alteration = {
12
+ up: async (pool) => {
13
+ await pool.query(sql `
14
+ create table cimd_user_scopes (
15
+ tenant_id varchar(21) not null
16
+ references tenants (id) on update cascade on delete cascade,
17
+ user_scope varchar(64) not null,
18
+ primary key (tenant_id, user_scope)
19
+ );
20
+ `);
21
+ await applyTableRls(pool, 'cimd_user_scopes');
22
+ await pool.query(sql `
23
+ create table cimd_resource_scopes (
24
+ tenant_id varchar(21) not null
25
+ references tenants (id) on update cascade on delete cascade,
26
+ scope_id varchar(21) not null,
27
+ primary key (tenant_id, scope_id),
28
+ foreign key (tenant_id, scope_id)
29
+ references scopes (tenant_id, id) on update cascade on delete cascade
30
+ );
31
+ `);
32
+ await applyTableRls(pool, 'cimd_resource_scopes');
33
+ await pool.query(sql `
34
+ create table cimd_organization_scopes (
35
+ tenant_id varchar(21) not null
36
+ references tenants (id) on update cascade on delete cascade,
37
+ organization_scope_id varchar(21) not null,
38
+ primary key (tenant_id, organization_scope_id),
39
+ foreign key (tenant_id, organization_scope_id)
40
+ references organization_scopes (tenant_id, id) on update cascade on delete cascade
41
+ );
42
+ `);
43
+ await applyTableRls(pool, 'cimd_organization_scopes');
44
+ await pool.query(sql `
45
+ create table cimd_organization_resource_scopes (
46
+ tenant_id varchar(21) not null
47
+ references tenants (id) on update cascade on delete cascade,
48
+ scope_id varchar(21) not null,
49
+ primary key (tenant_id, scope_id),
50
+ foreign key (tenant_id, scope_id)
51
+ references scopes (tenant_id, id) on update cascade on delete cascade
52
+ );
53
+ `);
54
+ await applyTableRls(pool, 'cimd_organization_resource_scopes');
55
+ },
56
+ down: async (pool) => {
57
+ await dropTableRls(pool, 'cimd_organization_resource_scopes');
58
+ await dropTableRls(pool, 'cimd_organization_scopes');
59
+ await dropTableRls(pool, 'cimd_resource_scopes');
60
+ await dropTableRls(pool, 'cimd_user_scopes');
61
+ await pool.query(sql `
62
+ drop table cimd_organization_resource_scopes;
63
+ drop table cimd_organization_scopes;
64
+ drop table cimd_resource_scopes;
65
+ drop table cimd_user_scopes;
66
+ `);
67
+ },
68
+ };
69
+ export default alteration;
@@ -0,0 +1,23 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ const alteration = {
3
+ beforeUp: async (pool) => {
4
+ // Add index to optimize deleting session extensions by accountId, including the
5
+ // cascades from the users table. See the index comment in tables/oidc_session_extensions.sql.
6
+ await pool.query(sql `
7
+ create index concurrently oidc_session_extensions__account_id
8
+ on oidc_session_extensions (account_id);
9
+ `);
10
+ },
11
+ up: async () => {
12
+ /** 'concurrently' cannot be used inside a transaction, so this up is intentionally left empty. */
13
+ },
14
+ beforeDown: async (pool) => {
15
+ await pool.query(sql `
16
+ drop index concurrently oidc_session_extensions__account_id;
17
+ `);
18
+ },
19
+ down: async () => {
20
+ /** 'concurrently' cannot be used inside a transaction, so this down is intentionally left empty. */
21
+ },
22
+ };
23
+ export default alteration;