@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,27 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ /**
3
+ * Registered application IDs (21 chars, referencing `applications`) and CIMD (client ID metadata
4
+ * document) client identifier URLs are different value spaces, so each gets its own column instead
5
+ * of sharing one.
6
+ */
7
+ const alteration = {
8
+ up: async (pool) => {
9
+ await pool.query(sql `
10
+ alter table users
11
+ add column cimd_client_id varchar(2048);
12
+
13
+ alter table oidc_session_extensions
14
+ add column cimd_client_id varchar(2048);
15
+ `);
16
+ },
17
+ down: async (pool) => {
18
+ await pool.query(sql `
19
+ alter table users
20
+ drop column cimd_client_id;
21
+
22
+ alter table oidc_session_extensions
23
+ drop column cimd_client_id;
24
+ `);
25
+ },
26
+ };
27
+ export default alteration;
@@ -0,0 +1,49 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ import { applyTableRls, dropTableRls } from './utils/1704934999-tables.js';
3
+ /**
4
+ * Add the grant-scoped organization authorization table for client ID metadata document (CIMD)
5
+ * clients. CIMD clients are unregistered URL identities with no deletion lifecycle and
6
+ * transferable ownership, so organization access follows the single authorization instead of a
7
+ * long-lived cross-grant relation: rows cascade away with the Grant row (revocation hard-deletes
8
+ * it) and with the user's organization membership.
9
+ */
10
+ const alteration = {
11
+ up: async (pool) => {
12
+ await pool.query(sql `
13
+ create table cimd_grant_organizations (
14
+ tenant_id varchar(21) not null
15
+ references tenants (id) on update cascade on delete cascade,
16
+ grant_id varchar(128) not null,
17
+ grant_model_name varchar(64) not null default 'Grant',
18
+ organization_id varchar(21) not null,
19
+ user_id varchar(21) not null,
20
+ primary key (tenant_id, grant_id, organization_id),
21
+ constraint cimd_grant_organizations__grant_model_name
22
+ check (grant_model_name = 'Grant'),
23
+ foreign key (tenant_id, grant_model_name, grant_id)
24
+ references oidc_model_instances (tenant_id, model_name, id)
25
+ on update cascade on delete cascade,
26
+ foreign key (tenant_id, organization_id, user_id)
27
+ references organization_user_relations (tenant_id, organization_id, user_id)
28
+ on update cascade on delete cascade
29
+ );
30
+ `);
31
+ /**
32
+ * Backs the membership FK's cascade lookup; the PK leaves `organization_id` outside its
33
+ * usable prefix. The table is created empty in this transaction, so a plain (non-concurrent)
34
+ * index build is free.
35
+ */
36
+ await pool.query(sql `
37
+ create index cimd_grant_organizations__organization_id_user_id
38
+ on cimd_grant_organizations (tenant_id, organization_id, user_id);
39
+ `);
40
+ await applyTableRls(pool, 'cimd_grant_organizations');
41
+ },
42
+ down: async (pool) => {
43
+ await dropTableRls(pool, 'cimd_grant_organizations');
44
+ await pool.query(sql `
45
+ drop table cimd_grant_organizations;
46
+ `);
47
+ },
48
+ };
49
+ export default alteration;
@@ -0,0 +1,50 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ import { applyTableRls, dropTableRls } from './utils/1704934999-tables.js';
3
+ const alteration = {
4
+ up: async (pool) => {
5
+ await pool.query(sql `
6
+ create table trusted_devices (
7
+ tenant_id varchar(21) not null
8
+ references tenants (id) on update cascade on delete cascade,
9
+ id varchar(21) not null,
10
+ user_id varchar(12) not null
11
+ references users (id) on update cascade on delete cascade,
12
+ secret_hash bytea not null,
13
+ user_agent text,
14
+ ip text,
15
+ country varchar(16),
16
+ city text,
17
+ created_at timestamptz not null default(now()),
18
+ last_used_at timestamptz not null default(now()),
19
+ expires_at timestamptz not null,
20
+ primary key (id)
21
+ );
22
+
23
+ create index trusted_devices__tenant_user_expires_at
24
+ on trusted_devices (tenant_id, user_id, expires_at);
25
+
26
+ create index trusted_devices__tenant_expires_at
27
+ on trusted_devices (tenant_id, expires_at);
28
+
29
+ alter table sign_in_experiences
30
+ add column trusted_device jsonb not null default '{}'::jsonb;
31
+
32
+ alter table organizations
33
+ add column is_trusted_device_allowed boolean not null default true;
34
+ `);
35
+ await applyTableRls(pool, 'trusted_devices');
36
+ },
37
+ down: async (pool) => {
38
+ await dropTableRls(pool, 'trusted_devices');
39
+ await pool.query(sql `
40
+ drop table trusted_devices;
41
+
42
+ alter table sign_in_experiences
43
+ drop column trusted_device;
44
+
45
+ alter table organizations
46
+ drop column is_trusted_device_allowed;
47
+ `);
48
+ },
49
+ };
50
+ export default alteration;
@@ -0,0 +1,24 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ /**
3
+ * Tighten the `cimd_grant_organizations` primary key to `(tenant_id, grant_id)` so the schema
4
+ * itself enforces the at-most-one-organization-per-grant invariant that token issuance relies on,
5
+ * instead of leaving it to the consent flow's composition. The table is dev-feature-gated and
6
+ * empty in every environment, so swapping the constraint needs no data migration.
7
+ */
8
+ const alteration = {
9
+ up: async (pool) => {
10
+ await pool.query(sql `
11
+ alter table cimd_grant_organizations
12
+ drop constraint cimd_grant_organizations_pkey,
13
+ add primary key (tenant_id, grant_id);
14
+ `);
15
+ },
16
+ down: async (pool) => {
17
+ await pool.query(sql `
18
+ alter table cimd_grant_organizations
19
+ drop constraint cimd_grant_organizations_pkey,
20
+ add primary key (tenant_id, grant_id, organization_id);
21
+ `);
22
+ },
23
+ };
24
+ export default alteration;
@@ -0,0 +1,30 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ const alteration = {
3
+ beforeUp: async (pool) => {
4
+ await pool.query(sql `
5
+ create index concurrently service_logs__email__tenant_id__created_at__id
6
+ on service_logs (tenant_id, created_at desc, id desc)
7
+ where type in ('sendEmail', 'sendEmailFailed');
8
+ `);
9
+ await pool.query(sql `
10
+ create index concurrently service_logs__email__tenant_id__recipient__created_at__id
11
+ on service_logs (tenant_id, lower(payload->'data'->>'to'), created_at desc, id desc)
12
+ where type in ('sendEmail', 'sendEmailFailed');
13
+ `);
14
+ },
15
+ up: async () => {
16
+ /** 'concurrently' cannot be used inside a transaction, so this up is intentionally left empty. */
17
+ },
18
+ beforeDown: async (pool) => {
19
+ await pool.query(sql `
20
+ drop index concurrently service_logs__email__tenant_id__recipient__created_at__id;
21
+ `);
22
+ await pool.query(sql `
23
+ drop index concurrently service_logs__email__tenant_id__created_at__id;
24
+ `);
25
+ },
26
+ down: async () => {
27
+ /** 'concurrently' cannot be used inside a transaction, so this down is intentionally left empty. */
28
+ },
29
+ };
30
+ export default alteration;
@@ -0,0 +1,38 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ import { applyTableRls, dropTableRls } from './utils/1704934999-tables.js';
3
+ /**
4
+ * Add the per-grant client display snapshot table for client ID metadata document (CIMD) clients.
5
+ * CIMD clients are unregistered URL identities without an `applications` row, and an unvetted
6
+ * client can rewrite its hosted document at any time — so the grant list renders the snapshot
7
+ * captured at consent instead of refetching the document. Rows cascade away with the Grant row.
8
+ */
9
+ const alteration = {
10
+ up: async (pool) => {
11
+ await pool.query(sql `
12
+ create table cimd_grant_client_snapshots (
13
+ tenant_id varchar(21) not null
14
+ references tenants (id) on update cascade on delete cascade,
15
+ grant_id varchar(128) not null,
16
+ grant_model_name varchar(64) not null default 'Grant',
17
+ client_id varchar(2048) not null,
18
+ name varchar(256),
19
+ logo_uri varchar(2048),
20
+ created_at timestamptz not null default(now()),
21
+ primary key (tenant_id, grant_id),
22
+ constraint cimd_grant_client_snapshots__grant_model_name
23
+ check (grant_model_name = 'Grant'),
24
+ foreign key (tenant_id, grant_model_name, grant_id)
25
+ references oidc_model_instances (tenant_id, model_name, id)
26
+ on update cascade on delete cascade
27
+ );
28
+ `);
29
+ await applyTableRls(pool, 'cimd_grant_client_snapshots');
30
+ },
31
+ down: async (pool) => {
32
+ await dropTableRls(pool, 'cimd_grant_client_snapshots');
33
+ await pool.query(sql `
34
+ drop table cimd_grant_client_snapshots;
35
+ `);
36
+ },
37
+ };
38
+ export default alteration;
@@ -0,0 +1,14 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ const alteration = {
3
+ up: async (pool) => {
4
+ await pool.query(sql `
5
+ alter table saml_application_configs add column authn_request_config jsonb;
6
+ `);
7
+ },
8
+ down: async (pool) => {
9
+ await pool.query(sql `
10
+ alter table saml_application_configs drop column authn_request_config;
11
+ `);
12
+ },
13
+ };
14
+ export default alteration;
@@ -0,0 +1,47 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ const columns = [
3
+ ['users', 'id', 12],
4
+ ['user_sign_in_countries', 'user_id', 12],
5
+ ['user_geo_locations', 'user_id', 12],
6
+ ['trusted_devices', 'user_id', 12],
7
+ ['user_sso_identities', 'user_id', 12],
8
+ ['oidc_session_extensions', 'account_id', 12],
9
+ ['users_roles', 'user_id', 21],
10
+ ['organization_user_relations', 'user_id', 21],
11
+ ['organization_role_user_relations', 'user_id', 21],
12
+ ['organization_invitations', 'inviter_id', 21],
13
+ ['organization_invitations', 'accepted_user_id', 21],
14
+ ['subject_tokens', 'user_id', 21],
15
+ ['verification_statuses', 'user_id', 21],
16
+ ['verification_records', 'user_id', 21],
17
+ ['secrets', 'user_id', 21],
18
+ ['personal_access_tokens', 'user_id', 21],
19
+ ['application_access_control_user_relations', 'user_id', 21],
20
+ ['application_user_consent_organizations', 'user_id', 21],
21
+ ['cimd_grant_organizations', 'user_id', 21],
22
+ ['daily_active_users', 'user_id', 21],
23
+ ['aggregated_daily_active_users', 'user_id', 21],
24
+ ];
25
+ const alteration = {
26
+ up: async (pool) => {
27
+ for (const [table, column] of columns) {
28
+ // eslint-disable-next-line no-await-in-loop -- DDL statements must run sequentially
29
+ await pool.query(sql `
30
+ alter table ${sql.identifier([table])}
31
+ alter column ${sql.identifier([column])} type varchar(128)
32
+ `);
33
+ }
34
+ },
35
+ down: async (pool) => {
36
+ // Shrinking fails if any existing value is longer than the original limit; there is no
37
+ // safe way to truncate an ID, so let Postgres reject the rollback in that case.
38
+ for (const [table, column, originalLength] of columns) {
39
+ // eslint-disable-next-line no-await-in-loop -- DDL statements must run sequentially
40
+ await pool.query(sql `
41
+ alter table ${sql.identifier([table])}
42
+ alter column ${sql.identifier([column])} type varchar(${sql.raw(String(originalLength))})
43
+ `);
44
+ }
45
+ },
46
+ };
47
+ export default alteration;
@@ -11,6 +11,9 @@ export declare const experience: Readonly<{
11
11
  readonly switchAccount: "switch-account";
12
12
  readonly oneTimeToken: "one-time-token";
13
13
  readonly accountSuspended: "account-suspended";
14
+ readonly trustedDevice: "trusted-device";
15
+ /** Pure step-up: an authenticated session pins the subject and only the missing assurance is asked. */
16
+ readonly stepUp: "step-up";
14
17
  }>;
15
18
  }>;
16
19
  /** `postMessage` sender tag from admin Console sign-in experience live preview. */
@@ -10,6 +10,9 @@ const routes = Object.freeze({
10
10
  switchAccount: 'switch-account',
11
11
  oneTimeToken: 'one-time-token',
12
12
  accountSuspended: 'account-suspended',
13
+ trustedDevice: 'trusted-device',
14
+ /** Pure step-up: an authenticated session pins the subject and only the missing assurance is asked. */
15
+ stepUp: 'step-up',
13
16
  });
14
17
  export const experience = Object.freeze({
15
18
  routes,
@@ -10,3 +10,5 @@ export * from './message-rate-limit.js';
10
10
  export * from './verification-code.js';
11
11
  export * from './product-event.js';
12
12
  export * from './application.js';
13
+ export * from './social-state.js';
14
+ export * from './license.js';
@@ -10,3 +10,5 @@ export * from './message-rate-limit.js';
10
10
  export * from './verification-code.js';
11
11
  export * from './product-event.js';
12
12
  export * from './application.js';
13
+ export * from './social-state.js';
14
+ export * from './license.js';
@@ -0,0 +1,222 @@
1
+ import { z } from 'zod';
2
+ import { ReservedPlanId } from './subscriptions.js';
3
+ /**
4
+ * The deployment a license key is valid for.
5
+ *
6
+ * Logto signs one key per environment for the same license, identical except for this claim, so a
7
+ * customer can run their staging or CI deployment without sharing the production key. Which key is
8
+ * installed where is the customer's declaration; Logto does not verify it.
9
+ */
10
+ export declare enum LicenseEnv {
11
+ Production = "production",
12
+ NonProduction = "non-production"
13
+ }
14
+ /** The plans a license key can grant. A Cloud-only plan is never a valid license `plan`. */
15
+ export declare const selfHostedPlanIds: readonly [ReservedPlanId.SelfHostedPro, ReservedPlanId.SelfHostedEnterprise];
16
+ /** The plan a license key grants. See {@link selfHostedPlanIds}. */
17
+ export type SelfHostedPlanId = (typeof selfHostedPlanIds)[number];
18
+ /**
19
+ * The entitlements of a self-hosted instance: the features a license unlocks, plus the numeric
20
+ * quotas it raises.
21
+ *
22
+ * The names follow the license payload rather than the Cloud `SubscriptionQuota` vocabulary
23
+ * (`bringYourUi`, not `bringYourUiEnabled`): these values are resolved locally from an installed
24
+ * key and never travel through the Cloud subscription API or its usage reporting.
25
+ * `samlApplicationsLimit` is the one name shared with Cloud, and it keeps the same meaning there —
26
+ * `null` is unlimited, a number is the hard cap.
27
+ */
28
+ export type LicenseQuota = {
29
+ /** Whether the sign-in experience may hide the Logto branding. */
30
+ hideLogtoBranding: boolean;
31
+ /** Whether custom sign-in experience assets and a custom CSP may be configured. */
32
+ bringYourUi: boolean;
33
+ /** Whether IdP-initiated SSO may be configured on an enterprise SSO connector. */
34
+ idpInitiatedSso: boolean;
35
+ /** Whether the instance's Console may be administered by more than one member. */
36
+ consoleCollaboration: boolean;
37
+ /** Whether the sign-in experience MFA policy may be set to mandatory. */
38
+ mandatoryMfa: boolean;
39
+ /**
40
+ * Whether Logto's hosted email service may be used.
41
+ *
42
+ * Always `false` for {@link ReservedPlanId.SelfHostedPro}. The Enterprise offering is deferred,
43
+ * so the flag is carried from the first signed key onwards and simply never set, which keeps the
44
+ * signed shape stable when Enterprise ships.
45
+ */
46
+ hostedEmail: boolean;
47
+ /** How many SAML applications the instance may create. `null` means unlimited. */
48
+ samlApplicationsLimit: number | null;
49
+ };
50
+ export declare const licenseQuotaGuard: z.ZodObject<{
51
+ hideLogtoBranding: z.ZodBoolean;
52
+ bringYourUi: z.ZodBoolean;
53
+ idpInitiatedSso: z.ZodBoolean;
54
+ consoleCollaboration: z.ZodBoolean;
55
+ mandatoryMfa: z.ZodBoolean;
56
+ hostedEmail: z.ZodBoolean;
57
+ samlApplicationsLimit: z.ZodNullable<z.ZodNumber>;
58
+ }, "strip", z.ZodTypeAny, {
59
+ hideLogtoBranding: boolean;
60
+ bringYourUi: boolean;
61
+ idpInitiatedSso: boolean;
62
+ consoleCollaboration: boolean;
63
+ mandatoryMfa: boolean;
64
+ hostedEmail: boolean;
65
+ samlApplicationsLimit: number | null;
66
+ }, {
67
+ hideLogtoBranding: boolean;
68
+ bringYourUi: boolean;
69
+ idpInitiatedSso: boolean;
70
+ consoleCollaboration: boolean;
71
+ mandatoryMfa: boolean;
72
+ hostedEmail: boolean;
73
+ samlApplicationsLimit: number | null;
74
+ }>;
75
+ /**
76
+ * The entitlements a license key grants, as they appear in its payload.
77
+ *
78
+ * Every field is optional: a key only carries what it changes, and an absent field falls back to
79
+ * {@link ossDefaultQuota}. Keeping it partial means a key signed before a new entitlement existed
80
+ * still verifies on a newer Logto, and the missing entitlement stays locked rather than breaking
81
+ * the whole license. Use {@link resolveLicenseQuota} to apply the overrides.
82
+ */
83
+ export declare const licenseQuotaOverridesGuard: z.ZodObject<{
84
+ hideLogtoBranding: z.ZodOptional<z.ZodBoolean>;
85
+ bringYourUi: z.ZodOptional<z.ZodBoolean>;
86
+ idpInitiatedSso: z.ZodOptional<z.ZodBoolean>;
87
+ consoleCollaboration: z.ZodOptional<z.ZodBoolean>;
88
+ mandatoryMfa: z.ZodOptional<z.ZodBoolean>;
89
+ hostedEmail: z.ZodOptional<z.ZodBoolean>;
90
+ samlApplicationsLimit: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
91
+ }, "strip", z.ZodTypeAny, {
92
+ hideLogtoBranding?: boolean | undefined;
93
+ bringYourUi?: boolean | undefined;
94
+ idpInitiatedSso?: boolean | undefined;
95
+ consoleCollaboration?: boolean | undefined;
96
+ mandatoryMfa?: boolean | undefined;
97
+ hostedEmail?: boolean | undefined;
98
+ samlApplicationsLimit?: number | null | undefined;
99
+ }, {
100
+ hideLogtoBranding?: boolean | undefined;
101
+ bringYourUi?: boolean | undefined;
102
+ idpInitiatedSso?: boolean | undefined;
103
+ consoleCollaboration?: boolean | undefined;
104
+ mandatoryMfa?: boolean | undefined;
105
+ hostedEmail?: boolean | undefined;
106
+ samlApplicationsLimit?: number | null | undefined;
107
+ }>;
108
+ export type LicenseQuotaOverrides = z.infer<typeof licenseQuotaOverridesGuard>;
109
+ /**
110
+ * The payload of a license key, i.e. the claims of the Ed25519-signed JWT an operator installs on a
111
+ * self-hosted instance.
112
+ *
113
+ * The signature is what makes the payload trustworthy, so none of it is editable after issuing.
114
+ * Unknown claims are stripped rather than rejected, so a newer license service can add one without
115
+ * invalidating the key on an older Logto.
116
+ */
117
+ export type LicensePayload = {
118
+ /** The plan the license grants. */
119
+ plan: SelfHostedPlanId;
120
+ /** The deployment the key is valid for. */
121
+ env: LicenseEnv;
122
+ /** The Logto customer the license was issued to, for support and renewal lookups. */
123
+ customerId: string;
124
+ /** The license's own identifier, shared by its production and non-production keys. */
125
+ licenseId: string;
126
+ /** When the key was signed, in seconds since the Unix epoch. */
127
+ iat: number;
128
+ /** When the key expires, in seconds since the Unix epoch. */
129
+ exp: number;
130
+ /** The entitlements the license grants on top of {@link ossDefaultQuota}. */
131
+ quota: LicenseQuotaOverrides;
132
+ };
133
+ export declare const licensePayloadGuard: z.ZodObject<{
134
+ plan: z.ZodEnum<[ReservedPlanId.SelfHostedPro, ReservedPlanId.SelfHostedEnterprise]>;
135
+ env: z.ZodNativeEnum<typeof LicenseEnv>;
136
+ customerId: z.ZodString;
137
+ licenseId: z.ZodString;
138
+ iat: z.ZodNumber;
139
+ exp: z.ZodNumber;
140
+ quota: z.ZodObject<{
141
+ hideLogtoBranding: z.ZodOptional<z.ZodBoolean>;
142
+ bringYourUi: z.ZodOptional<z.ZodBoolean>;
143
+ idpInitiatedSso: z.ZodOptional<z.ZodBoolean>;
144
+ consoleCollaboration: z.ZodOptional<z.ZodBoolean>;
145
+ mandatoryMfa: z.ZodOptional<z.ZodBoolean>;
146
+ hostedEmail: z.ZodOptional<z.ZodBoolean>;
147
+ samlApplicationsLimit: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
148
+ }, "strip", z.ZodTypeAny, {
149
+ hideLogtoBranding?: boolean | undefined;
150
+ bringYourUi?: boolean | undefined;
151
+ idpInitiatedSso?: boolean | undefined;
152
+ consoleCollaboration?: boolean | undefined;
153
+ mandatoryMfa?: boolean | undefined;
154
+ hostedEmail?: boolean | undefined;
155
+ samlApplicationsLimit?: number | null | undefined;
156
+ }, {
157
+ hideLogtoBranding?: boolean | undefined;
158
+ bringYourUi?: boolean | undefined;
159
+ idpInitiatedSso?: boolean | undefined;
160
+ consoleCollaboration?: boolean | undefined;
161
+ mandatoryMfa?: boolean | undefined;
162
+ hostedEmail?: boolean | undefined;
163
+ samlApplicationsLimit?: number | null | undefined;
164
+ }>;
165
+ }, "strip", z.ZodTypeAny, {
166
+ exp: number;
167
+ iat: number;
168
+ plan: ReservedPlanId.SelfHostedPro | ReservedPlanId.SelfHostedEnterprise;
169
+ env: LicenseEnv;
170
+ customerId: string;
171
+ licenseId: string;
172
+ quota: {
173
+ hideLogtoBranding?: boolean | undefined;
174
+ bringYourUi?: boolean | undefined;
175
+ idpInitiatedSso?: boolean | undefined;
176
+ consoleCollaboration?: boolean | undefined;
177
+ mandatoryMfa?: boolean | undefined;
178
+ hostedEmail?: boolean | undefined;
179
+ samlApplicationsLimit?: number | null | undefined;
180
+ };
181
+ }, {
182
+ exp: number;
183
+ iat: number;
184
+ plan: ReservedPlanId.SelfHostedPro | ReservedPlanId.SelfHostedEnterprise;
185
+ env: LicenseEnv;
186
+ customerId: string;
187
+ licenseId: string;
188
+ quota: {
189
+ hideLogtoBranding?: boolean | undefined;
190
+ bringYourUi?: boolean | undefined;
191
+ idpInitiatedSso?: boolean | undefined;
192
+ consoleCollaboration?: boolean | undefined;
193
+ mandatoryMfa?: boolean | undefined;
194
+ hostedEmail?: boolean | undefined;
195
+ samlApplicationsLimit?: number | null | undefined;
196
+ };
197
+ }>;
198
+ /**
199
+ * The entitlements of a self-hosted instance with no license key installed, i.e. today's OSS.
200
+ *
201
+ * Every feature is locked and SAML applications keep the long-standing cap of 3. Core falls back to
202
+ * this when no valid license is installed, and Console displays it, so there is exactly one
203
+ * definition of "self-hosted, no license".
204
+ */
205
+ export declare const ossDefaultQuota: Readonly<{
206
+ hideLogtoBranding: false;
207
+ bringYourUi: false;
208
+ idpInitiatedSso: false;
209
+ consoleCollaboration: false;
210
+ mandatoryMfa: false;
211
+ hostedEmail: false;
212
+ samlApplicationsLimit: number;
213
+ }>;
214
+ /**
215
+ * Resolve the effective entitlements of a self-hosted instance by applying a license key's
216
+ * overrides on top of {@link ossDefaultQuota}. Without a key, the OSS defaults are the answer.
217
+ *
218
+ * Core and Console both call this so they can never disagree on what a license grants. The fields
219
+ * are applied one by one on purpose: `samlApplicationsLimit` is nullable and `null` means
220
+ * unlimited, so a `??` merge would silently turn an unlimited license back into the OSS cap.
221
+ */
222
+ export declare const resolveLicenseQuota: (overrides?: LicenseQuotaOverrides) => LicenseQuota;
@@ -0,0 +1,81 @@
1
+ import { z } from 'zod';
2
+ import { ReservedPlanId } from './subscriptions.js';
3
+ /**
4
+ * The deployment a license key is valid for.
5
+ *
6
+ * Logto signs one key per environment for the same license, identical except for this claim, so a
7
+ * customer can run their staging or CI deployment without sharing the production key. Which key is
8
+ * installed where is the customer's declaration; Logto does not verify it.
9
+ */
10
+ export var LicenseEnv;
11
+ (function (LicenseEnv) {
12
+ LicenseEnv["Production"] = "production";
13
+ LicenseEnv["NonProduction"] = "non-production";
14
+ })(LicenseEnv || (LicenseEnv = {}));
15
+ /** The plans a license key can grant. A Cloud-only plan is never a valid license `plan`. */
16
+ export const selfHostedPlanIds = Object.freeze([
17
+ ReservedPlanId.SelfHostedPro,
18
+ ReservedPlanId.SelfHostedEnterprise,
19
+ ]);
20
+ export const licenseQuotaGuard = z.object({
21
+ hideLogtoBranding: z.boolean(),
22
+ bringYourUi: z.boolean(),
23
+ idpInitiatedSso: z.boolean(),
24
+ consoleCollaboration: z.boolean(),
25
+ mandatoryMfa: z.boolean(),
26
+ hostedEmail: z.boolean(),
27
+ samlApplicationsLimit: z.number().int().nonnegative().nullable(),
28
+ });
29
+ /**
30
+ * The entitlements a license key grants, as they appear in its payload.
31
+ *
32
+ * Every field is optional: a key only carries what it changes, and an absent field falls back to
33
+ * {@link ossDefaultQuota}. Keeping it partial means a key signed before a new entitlement existed
34
+ * still verifies on a newer Logto, and the missing entitlement stays locked rather than breaking
35
+ * the whole license. Use {@link resolveLicenseQuota} to apply the overrides.
36
+ */
37
+ export const licenseQuotaOverridesGuard = licenseQuotaGuard.partial();
38
+ export const licensePayloadGuard = z.object({
39
+ plan: z.enum(selfHostedPlanIds),
40
+ env: z.nativeEnum(LicenseEnv),
41
+ customerId: z.string(),
42
+ licenseId: z.string(),
43
+ iat: z.number().int().nonnegative(),
44
+ exp: z.number().int().nonnegative(),
45
+ quota: licenseQuotaOverridesGuard,
46
+ });
47
+ /**
48
+ * The entitlements of a self-hosted instance with no license key installed, i.e. today's OSS.
49
+ *
50
+ * Every feature is locked and SAML applications keep the long-standing cap of 3. Core falls back to
51
+ * this when no valid license is installed, and Console displays it, so there is exactly one
52
+ * definition of "self-hosted, no license".
53
+ */
54
+ export const ossDefaultQuota = Object.freeze({
55
+ hideLogtoBranding: false,
56
+ bringYourUi: false,
57
+ idpInitiatedSso: false,
58
+ consoleCollaboration: false,
59
+ mandatoryMfa: false,
60
+ hostedEmail: false,
61
+ samlApplicationsLimit: 3,
62
+ });
63
+ /**
64
+ * Resolve the effective entitlements of a self-hosted instance by applying a license key's
65
+ * overrides on top of {@link ossDefaultQuota}. Without a key, the OSS defaults are the answer.
66
+ *
67
+ * Core and Console both call this so they can never disagree on what a license grants. The fields
68
+ * are applied one by one on purpose: `samlApplicationsLimit` is nullable and `null` means
69
+ * unlimited, so a `??` merge would silently turn an unlimited license back into the OSS cap.
70
+ */
71
+ export const resolveLicenseQuota = (overrides = {}) => ({
72
+ hideLogtoBranding: overrides.hideLogtoBranding ?? ossDefaultQuota.hideLogtoBranding,
73
+ bringYourUi: overrides.bringYourUi ?? ossDefaultQuota.bringYourUi,
74
+ idpInitiatedSso: overrides.idpInitiatedSso ?? ossDefaultQuota.idpInitiatedSso,
75
+ consoleCollaboration: overrides.consoleCollaboration ?? ossDefaultQuota.consoleCollaboration,
76
+ mandatoryMfa: overrides.mandatoryMfa ?? ossDefaultQuota.mandatoryMfa,
77
+ hostedEmail: overrides.hostedEmail ?? ossDefaultQuota.hostedEmail,
78
+ samlApplicationsLimit: overrides.samlApplicationsLimit === undefined
79
+ ? ossDefaultQuota.samlApplicationsLimit
80
+ : overrides.samlApplicationsLimit,
81
+ });
@@ -0,0 +1 @@
1
+ export {};