@logto/schemas 1.35.0 → 1.37.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 (85) hide show
  1. package/alterations/1.36.0-1767193412-allow-token-exchange.ts +34 -0
  2. package/alterations/1.36.0-1767859553-passkey-sign-in.ts +21 -0
  3. package/alterations/1.36.0-1768192304-enable-account-center-for-admin-tenant.ts +32 -0
  4. package/alterations/1.36.0-1768464306-enable-mfa-for-admin-tenant.ts +30 -0
  5. package/alterations/1.36.0-1768758295-add-user-geo-location.ts +32 -0
  6. package/alterations/1.36.0-1768891516-add-user-sign-in-countries-table.ts +33 -0
  7. package/alterations/1.36.0-1769067642-add-adaptive-mfa-configuration.ts +19 -0
  8. package/alterations/1.36.0-1769172677-enable-organization-mfa-policy-for-admin-tenant.ts +31 -0
  9. package/alterations/1.37.0-1770295353-add-default-id-token-config.ts +30 -0
  10. package/alterations/1.37.0-1770361004-add-oidc-model-instances-session-account-id-indexes.ts +37 -0
  11. package/alterations/1.37.0-1770362227-add-client-id-column-to-oidc-session-extensions-table.ts +20 -0
  12. package/alterations-js/1.36.0-1767193412-allow-token-exchange.js +30 -0
  13. package/alterations-js/1.36.0-1767859553-passkey-sign-in.js +17 -0
  14. package/alterations-js/1.36.0-1768192304-enable-account-center-for-admin-tenant.js +27 -0
  15. package/alterations-js/1.36.0-1768464306-enable-mfa-for-admin-tenant.js +25 -0
  16. package/alterations-js/1.36.0-1768758295-add-user-geo-location.js +27 -0
  17. package/alterations-js/1.36.0-1768891516-add-user-sign-in-countries-table.js +28 -0
  18. package/alterations-js/1.36.0-1769067642-add-adaptive-mfa-configuration.js +15 -0
  19. package/alterations-js/1.36.0-1769172677-enable-organization-mfa-policy-for-admin-tenant.js +26 -0
  20. package/alterations-js/1.37.0-1770295353-add-default-id-token-config.js +23 -0
  21. package/alterations-js/1.37.0-1770361004-add-oidc-model-instances-session-account-id-indexes.js +31 -0
  22. package/alterations-js/1.37.0-1770362227-add-client-id-column-to-oidc-session-extensions-table.js +16 -0
  23. package/lib/db-entries/index.d.ts +2 -0
  24. package/lib/db-entries/index.js +2 -0
  25. package/lib/db-entries/oidc-session-extension.d.ts +3 -1
  26. package/lib/db-entries/oidc-session-extension.js +4 -0
  27. package/lib/db-entries/sign-in-experience.d.ts +6 -2
  28. package/lib/db-entries/sign-in-experience.js +9 -1
  29. package/lib/db-entries/user-geo-location.d.ts +24 -0
  30. package/lib/db-entries/user-geo-location.js +37 -0
  31. package/lib/db-entries/user-sign-in-country.d.ts +24 -0
  32. package/lib/db-entries/user-sign-in-country.js +33 -0
  33. package/lib/foundations/jsonb-types/account-centers.d.ts +3 -0
  34. package/lib/foundations/jsonb-types/account-centers.js +1 -0
  35. package/lib/foundations/jsonb-types/custom-profile-fields.d.ts +8 -8
  36. package/lib/foundations/jsonb-types/hooks.d.ts +4 -3
  37. package/lib/foundations/jsonb-types/hooks.js +2 -0
  38. package/lib/foundations/jsonb-types/logs.d.ts +700 -0
  39. package/lib/foundations/jsonb-types/logs.js +51 -0
  40. package/lib/foundations/jsonb-types/oidc-module.d.ts +343 -3
  41. package/lib/foundations/jsonb-types/oidc-module.js +57 -3
  42. package/lib/foundations/jsonb-types/saml-application-configs.d.ts +1 -1
  43. package/lib/foundations/jsonb-types/sentinel.d.ts +13 -1
  44. package/lib/foundations/jsonb-types/sentinel.js +12 -0
  45. package/lib/foundations/jsonb-types/sign-in-experience.d.ts +59 -0
  46. package/lib/foundations/jsonb-types/sign-in-experience.js +11 -0
  47. package/lib/seeds/account-center.d.ts +6 -0
  48. package/lib/seeds/account-center.js +24 -0
  49. package/lib/seeds/cloud-api.d.ts +3 -1
  50. package/lib/seeds/cloud-api.js +2 -0
  51. package/lib/seeds/logto-config.d.ts +6 -1
  52. package/lib/seeds/logto-config.js +11 -0
  53. package/lib/seeds/sign-in-experience.js +6 -1
  54. package/lib/types/application.d.ts +6 -0
  55. package/lib/types/consent.d.ts +4 -0
  56. package/lib/types/custom-profile-fields.d.ts +36 -36
  57. package/lib/types/hook.d.ts +2 -2
  58. package/lib/types/index.d.ts +1 -0
  59. package/lib/types/index.js +1 -0
  60. package/lib/types/log/index.d.ts +12 -6
  61. package/lib/types/log/interaction.d.ts +5 -1
  62. package/lib/types/logto-config/index.d.ts +1190 -91
  63. package/lib/types/logto-config/index.js +9 -0
  64. package/lib/types/logto-config/jwt-customizer.d.ts +2059 -136
  65. package/lib/types/logto-config/jwt-customizer.js +22 -2
  66. package/lib/types/logto-config/jwt-customizer.test.js +27 -1
  67. package/lib/types/logto-config/oidc-provider.d.ts +8 -8
  68. package/lib/types/saml-application.d.ts +7 -7
  69. package/lib/types/sign-in-experience.d.ts +6 -0
  70. package/lib/types/user-logto-config.d.ts +49 -0
  71. package/lib/types/user-logto-config.js +23 -0
  72. package/lib/types/user-sessions.d.ts +3208 -0
  73. package/lib/types/user-sessions.js +26 -0
  74. package/lib/types/user.d.ts +7 -7
  75. package/lib/types/verification-records/verification-type.d.ts +1 -0
  76. package/lib/types/verification-records/verification-type.js +1 -0
  77. package/lib/types/verification-records/web-authn-verification.d.ts +145 -8
  78. package/lib/types/verification-records/web-authn-verification.js +17 -3
  79. package/package.json +5 -5
  80. package/tables/oidc_model_instances.sql +7 -0
  81. package/tables/oidc_session_extensions.sql +1 -0
  82. package/tables/sign_in_experiences.sql +2 -0
  83. package/tables/user_geo_locations.sql +14 -0
  84. package/tables/user_sign_in_countries.sql +16 -0
  85. package/tables/users.sql +3 -0
@@ -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
+ up: async (pool) => {
7
+ /**
8
+ * For backward compatibility, set allowTokenExchange = true for existing first-party
9
+ * Traditional, Native, and SPA applications.
10
+ * M2M applications were never allowed to use token exchange before this feature.
11
+ *
12
+ * The admin-console should keep token exchange disabled, so we skip it here.
13
+ */
14
+ await pool.query(sql`
15
+ update applications
16
+ set custom_client_metadata = custom_client_metadata || '{"allowTokenExchange": true}'::jsonb
17
+ where is_third_party = false
18
+ and type in ('Traditional', 'Native', 'SPA')
19
+ and id != 'admin-console';
20
+ `);
21
+ },
22
+ down: async (pool) => {
23
+ // Remove allowTokenExchange only from applications that were updated in the `up` migration
24
+ await pool.query(sql`
25
+ update applications
26
+ set custom_client_metadata = custom_client_metadata - 'allowTokenExchange'
27
+ where is_third_party = false
28
+ and type in ('Traditional', 'Native', 'SPA')
29
+ and id != 'admin-console';
30
+ `);
31
+ },
32
+ };
33
+
34
+ export default alteration;
@@ -0,0 +1,21 @@
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 sign_in_experiences
9
+ add column passkey_sign_in jsonb /* @use PasskeySignIn */ not null default '{}'::jsonb;
10
+ create index users_mfa_verifications_gin on users using gin (mfa_verifications jsonb_path_ops);
11
+ `);
12
+ },
13
+ down: async (pool) => {
14
+ await pool.query(sql`
15
+ alter table sign_in_experiences drop column passkey_sign_in;
16
+ drop index users_mfa_verifications_gin;
17
+ `);
18
+ },
19
+ };
20
+
21
+ export default alteration;
@@ -0,0 +1,32 @@
1
+ import { sql } from '@silverhand/slonik';
2
+
3
+ import type { AlterationScript } from '../lib/types/alteration.js';
4
+
5
+ const adminTenantId = 'admin';
6
+
7
+ /**
8
+ * Enable the account center for the admin tenant and set all fields to Edit.
9
+ * This allows the console profile page to use the Account API.
10
+ */
11
+ const alteration: AlterationScript = {
12
+ up: async (pool) => {
13
+ await pool.query(sql`
14
+ update account_centers
15
+ set enabled = true,
16
+ fields = '{"name": "Edit", "avatar": "Edit", "profile": "Edit", "email": "Edit", "phone": "Edit", "password": "Edit", "username": "Edit", "social": "Edit", "customData": "Edit", "mfa": "Edit"}'::jsonb
17
+ where tenant_id = ${adminTenantId}
18
+ and id = 'default'
19
+ `);
20
+ },
21
+ down: async (pool) => {
22
+ await pool.query(sql`
23
+ update account_centers
24
+ set enabled = false,
25
+ fields = '{}'::jsonb
26
+ where tenant_id = ${adminTenantId}
27
+ and id = 'default'
28
+ `);
29
+ },
30
+ };
31
+
32
+ export default alteration;
@@ -0,0 +1,30 @@
1
+ import { sql } from '@silverhand/slonik';
2
+
3
+ import type { AlterationScript } from '../lib/types/alteration.js';
4
+
5
+ const adminTenantId = 'admin';
6
+
7
+ /**
8
+ * Enable MFA (TOTP and WebAuthn) for the admin tenant with NoPrompt policy.
9
+ * This allows users to optionally set up MFA via the account center.
10
+ */
11
+ const alteration: AlterationScript = {
12
+ up: async (pool) => {
13
+ await pool.query(sql`
14
+ update sign_in_experiences
15
+ set mfa = '{"factors":["Totp","WebAuthn","BackupCode"],"policy":"NoPrompt"}'::jsonb
16
+ where tenant_id = ${adminTenantId}
17
+ and id = 'default'
18
+ `);
19
+ },
20
+ down: async (pool) => {
21
+ await pool.query(sql`
22
+ update sign_in_experiences
23
+ set mfa = '{"factors":[],"policy":"UserControlled"}'::jsonb
24
+ where tenant_id = ${adminTenantId}
25
+ and id = 'default'
26
+ `);
27
+ },
28
+ };
29
+
30
+ export default alteration;
@@ -0,0 +1,32 @@
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 user_geo_locations (
11
+ tenant_id varchar(21) not null
12
+ references tenants (id) on update cascade on delete cascade,
13
+ user_id varchar(12) not null
14
+ references users (id) on update cascade on delete cascade,
15
+ latitude numeric(9,6),
16
+ longitude numeric(9,6),
17
+ updated_at timestamptz not null default now(),
18
+ primary key (tenant_id, user_id),
19
+ check ((latitude is null) = (longitude is null))
20
+ );
21
+ `);
22
+ await applyTableRls(pool, 'user_geo_locations');
23
+ },
24
+ down: async (pool) => {
25
+ await dropTableRls(pool, 'user_geo_locations');
26
+ await pool.query(sql`
27
+ drop table user_geo_locations;
28
+ `);
29
+ },
30
+ };
31
+
32
+ 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
+ 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 user_sign_in_countries (
11
+ tenant_id varchar(21) not null
12
+ references tenants (id) on update cascade on delete cascade,
13
+ user_id varchar(12) not null
14
+ references users (id) on update cascade on delete cascade,
15
+ country varchar(16) not null,
16
+ last_sign_in_at timestamptz not null default(now()),
17
+ primary key (tenant_id, user_id, country)
18
+ );
19
+
20
+ create index user_sign_in_countries__tenant_user_last_sign_in_at
21
+ on user_sign_in_countries (tenant_id, user_id, last_sign_in_at desc);
22
+ `);
23
+ await applyTableRls(pool, 'user_sign_in_countries');
24
+ },
25
+ down: async (pool) => {
26
+ await dropTableRls(pool, 'user_sign_in_countries');
27
+ await pool.query(sql`
28
+ drop table user_sign_in_countries;
29
+ `);
30
+ },
31
+ };
32
+
33
+ export default alteration;
@@ -0,0 +1,19 @@
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 sign_in_experiences
9
+ add column adaptive_mfa jsonb not null default '{}'::jsonb;
10
+ `);
11
+ },
12
+ down: async (pool) => {
13
+ await pool.query(sql`
14
+ alter table sign_in_experiences drop column adaptive_mfa;
15
+ `);
16
+ },
17
+ };
18
+
19
+ 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
+ const adminTenantId = 'admin';
6
+
7
+ /**
8
+ * Enable organization required MFA policy for the admin tenant.
9
+ * This allows tenant admins to require MFA for all tenant members
10
+ * by setting `isMfaRequired: true` on the organization.
11
+ */
12
+ const alteration: AlterationScript = {
13
+ up: async (pool) => {
14
+ await pool.query(sql`
15
+ update sign_in_experiences
16
+ set mfa = jsonb_set(mfa, '{organizationRequiredMfaPolicy}', '"Mandatory"')
17
+ where tenant_id = ${adminTenantId}
18
+ and id = 'default'
19
+ `);
20
+ },
21
+ down: async (pool) => {
22
+ await pool.query(sql`
23
+ update sign_in_experiences
24
+ set mfa = mfa - 'organizationRequiredMfaPolicy'
25
+ where tenant_id = ${adminTenantId}
26
+ and id = 'default'
27
+ `);
28
+ },
29
+ };
30
+
31
+ export default alteration;
@@ -0,0 +1,30 @@
1
+ import { sql } from '@silverhand/slonik';
2
+
3
+ import type { AlterationScript } from '../lib/types/alteration.js';
4
+
5
+ const idTokenConfigKey = 'idToken';
6
+
7
+ const defaultIdTokenConfig = Object.freeze({
8
+ enabledExtendedClaims: ['roles', 'organizations', 'organization_roles'],
9
+ });
10
+
11
+ const alteration: AlterationScript = {
12
+ up: async (pool) => {
13
+ const tenants = await pool.any<{ id: string }>(sql`select id from tenants`);
14
+
15
+ for (const { id: tenantId } of tenants) {
16
+ // eslint-disable-next-line no-await-in-loop
17
+ await pool.query(sql`
18
+ insert into logto_configs (tenant_id, key, value)
19
+ values (${tenantId}, ${idTokenConfigKey}, ${sql.jsonb(defaultIdTokenConfig)})
20
+ `);
21
+ }
22
+ },
23
+ down: async (pool) => {
24
+ await pool.query(sql`
25
+ delete from logto_configs where key = ${idTokenConfigKey}
26
+ `);
27
+ },
28
+ };
29
+
30
+ export default alteration;
@@ -0,0 +1,37 @@
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 the query performance for cleaning up expired OIDC model instances.
8
+ await pool.query(sql`
9
+ create index concurrently if not exists oidc_model_instances__expires_at
10
+ on oidc_model_instances (tenant_id, expires_at);
11
+ `);
12
+
13
+ // Add index to optimize the query performance for querying non-expired session instances by accountId.
14
+ await pool.query(sql`
15
+ create index concurrently if not exists oidc_model_instances__session_payload_account_id_expires_at
16
+ on oidc_model_instances (tenant_id, (payload->>'accountId'), expires_at)
17
+ WHERE model_name = 'Session';
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 if exists oidc_model_instances__expires_at;
26
+ `);
27
+
28
+ await pool.query(sql`
29
+ drop index concurrently if exists oidc_model_instances__session_payload_account_id_expires_at;
30
+ `);
31
+ },
32
+ down: async () => {
33
+ /** 'concurrently' cannot be used inside a transaction, so this up is intentionally left empty. */
34
+ },
35
+ };
36
+
37
+ export default alteration;
@@ -0,0 +1,20 @@
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 oidc_session_extensions
9
+ add column client_id varchar(21) null
10
+ `);
11
+ },
12
+ down: async (pool) => {
13
+ await pool.query(sql`
14
+ alter table oidc_session_extensions
15
+ drop column client_id
16
+ `);
17
+ },
18
+ };
19
+
20
+ export default alteration;
@@ -0,0 +1,30 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ const alteration = {
3
+ up: async (pool) => {
4
+ /**
5
+ * For backward compatibility, set allowTokenExchange = true for existing first-party
6
+ * Traditional, Native, and SPA applications.
7
+ * M2M applications were never allowed to use token exchange before this feature.
8
+ *
9
+ * The admin-console should keep token exchange disabled, so we skip it here.
10
+ */
11
+ await pool.query(sql `
12
+ update applications
13
+ set custom_client_metadata = custom_client_metadata || '{"allowTokenExchange": true}'::jsonb
14
+ where is_third_party = false
15
+ and type in ('Traditional', 'Native', 'SPA')
16
+ and id != 'admin-console';
17
+ `);
18
+ },
19
+ down: async (pool) => {
20
+ // Remove allowTokenExchange only from applications that were updated in the `up` migration
21
+ await pool.query(sql `
22
+ update applications
23
+ set custom_client_metadata = custom_client_metadata - 'allowTokenExchange'
24
+ where is_third_party = false
25
+ and type in ('Traditional', 'Native', 'SPA')
26
+ and id != 'admin-console';
27
+ `);
28
+ },
29
+ };
30
+ export default alteration;
@@ -0,0 +1,17 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ const alteration = {
3
+ up: async (pool) => {
4
+ await pool.query(sql `
5
+ alter table sign_in_experiences
6
+ add column passkey_sign_in jsonb /* @use PasskeySignIn */ not null default '{}'::jsonb;
7
+ create index users_mfa_verifications_gin on users using gin (mfa_verifications jsonb_path_ops);
8
+ `);
9
+ },
10
+ down: async (pool) => {
11
+ await pool.query(sql `
12
+ alter table sign_in_experiences drop column passkey_sign_in;
13
+ drop index users_mfa_verifications_gin;
14
+ `);
15
+ },
16
+ };
17
+ export default alteration;
@@ -0,0 +1,27 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ const adminTenantId = 'admin';
3
+ /**
4
+ * Enable the account center for the admin tenant and set all fields to Edit.
5
+ * This allows the console profile page to use the Account API.
6
+ */
7
+ const alteration = {
8
+ up: async (pool) => {
9
+ await pool.query(sql `
10
+ update account_centers
11
+ set enabled = true,
12
+ fields = '{"name": "Edit", "avatar": "Edit", "profile": "Edit", "email": "Edit", "phone": "Edit", "password": "Edit", "username": "Edit", "social": "Edit", "customData": "Edit", "mfa": "Edit"}'::jsonb
13
+ where tenant_id = ${adminTenantId}
14
+ and id = 'default'
15
+ `);
16
+ },
17
+ down: async (pool) => {
18
+ await pool.query(sql `
19
+ update account_centers
20
+ set enabled = false,
21
+ fields = '{}'::jsonb
22
+ where tenant_id = ${adminTenantId}
23
+ and id = 'default'
24
+ `);
25
+ },
26
+ };
27
+ export default alteration;
@@ -0,0 +1,25 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ const adminTenantId = 'admin';
3
+ /**
4
+ * Enable MFA (TOTP and WebAuthn) for the admin tenant with NoPrompt policy.
5
+ * This allows users to optionally set up MFA via the account center.
6
+ */
7
+ const alteration = {
8
+ up: async (pool) => {
9
+ await pool.query(sql `
10
+ update sign_in_experiences
11
+ set mfa = '{"factors":["Totp","WebAuthn","BackupCode"],"policy":"NoPrompt"}'::jsonb
12
+ where tenant_id = ${adminTenantId}
13
+ and id = 'default'
14
+ `);
15
+ },
16
+ down: async (pool) => {
17
+ await pool.query(sql `
18
+ update sign_in_experiences
19
+ set mfa = '{"factors":[],"policy":"UserControlled"}'::jsonb
20
+ where tenant_id = ${adminTenantId}
21
+ and id = 'default'
22
+ `);
23
+ },
24
+ };
25
+ export default alteration;
@@ -0,0 +1,27 @@
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 user_geo_locations (
7
+ tenant_id varchar(21) not null
8
+ references tenants (id) on update cascade on delete cascade,
9
+ user_id varchar(12) not null
10
+ references users (id) on update cascade on delete cascade,
11
+ latitude numeric(9,6),
12
+ longitude numeric(9,6),
13
+ updated_at timestamptz not null default now(),
14
+ primary key (tenant_id, user_id),
15
+ check ((latitude is null) = (longitude is null))
16
+ );
17
+ `);
18
+ await applyTableRls(pool, 'user_geo_locations');
19
+ },
20
+ down: async (pool) => {
21
+ await dropTableRls(pool, 'user_geo_locations');
22
+ await pool.query(sql `
23
+ drop table user_geo_locations;
24
+ `);
25
+ },
26
+ };
27
+ export default alteration;
@@ -0,0 +1,28 @@
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 user_sign_in_countries (
7
+ tenant_id varchar(21) not null
8
+ references tenants (id) on update cascade on delete cascade,
9
+ user_id varchar(12) not null
10
+ references users (id) on update cascade on delete cascade,
11
+ country varchar(16) not null,
12
+ last_sign_in_at timestamptz not null default(now()),
13
+ primary key (tenant_id, user_id, country)
14
+ );
15
+
16
+ create index user_sign_in_countries__tenant_user_last_sign_in_at
17
+ on user_sign_in_countries (tenant_id, user_id, last_sign_in_at desc);
18
+ `);
19
+ await applyTableRls(pool, 'user_sign_in_countries');
20
+ },
21
+ down: async (pool) => {
22
+ await dropTableRls(pool, 'user_sign_in_countries');
23
+ await pool.query(sql `
24
+ drop table user_sign_in_countries;
25
+ `);
26
+ },
27
+ };
28
+ export default alteration;
@@ -0,0 +1,15 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ const alteration = {
3
+ up: async (pool) => {
4
+ await pool.query(sql `
5
+ alter table sign_in_experiences
6
+ add column adaptive_mfa jsonb not null default '{}'::jsonb;
7
+ `);
8
+ },
9
+ down: async (pool) => {
10
+ await pool.query(sql `
11
+ alter table sign_in_experiences drop column adaptive_mfa;
12
+ `);
13
+ },
14
+ };
15
+ export default alteration;
@@ -0,0 +1,26 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ const adminTenantId = 'admin';
3
+ /**
4
+ * Enable organization required MFA policy for the admin tenant.
5
+ * This allows tenant admins to require MFA for all tenant members
6
+ * by setting `isMfaRequired: true` on the organization.
7
+ */
8
+ const alteration = {
9
+ up: async (pool) => {
10
+ await pool.query(sql `
11
+ update sign_in_experiences
12
+ set mfa = jsonb_set(mfa, '{organizationRequiredMfaPolicy}', '"Mandatory"')
13
+ where tenant_id = ${adminTenantId}
14
+ and id = 'default'
15
+ `);
16
+ },
17
+ down: async (pool) => {
18
+ await pool.query(sql `
19
+ update sign_in_experiences
20
+ set mfa = mfa - 'organizationRequiredMfaPolicy'
21
+ where tenant_id = ${adminTenantId}
22
+ and id = 'default'
23
+ `);
24
+ },
25
+ };
26
+ export default alteration;
@@ -0,0 +1,23 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ const idTokenConfigKey = 'idToken';
3
+ const defaultIdTokenConfig = Object.freeze({
4
+ enabledExtendedClaims: ['roles', 'organizations', 'organization_roles'],
5
+ });
6
+ const alteration = {
7
+ up: async (pool) => {
8
+ const tenants = await pool.any(sql `select id from tenants`);
9
+ for (const { id: tenantId } of tenants) {
10
+ // eslint-disable-next-line no-await-in-loop
11
+ await pool.query(sql `
12
+ insert into logto_configs (tenant_id, key, value)
13
+ values (${tenantId}, ${idTokenConfigKey}, ${sql.jsonb(defaultIdTokenConfig)})
14
+ `);
15
+ }
16
+ },
17
+ down: async (pool) => {
18
+ await pool.query(sql `
19
+ delete from logto_configs where key = ${idTokenConfigKey}
20
+ `);
21
+ },
22
+ };
23
+ export default alteration;
@@ -0,0 +1,31 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ const alteration = {
3
+ beforeUp: async (pool) => {
4
+ // Add index to optimize the query performance for cleaning up expired OIDC model instances.
5
+ await pool.query(sql `
6
+ create index concurrently if not exists oidc_model_instances__expires_at
7
+ on oidc_model_instances (tenant_id, expires_at);
8
+ `);
9
+ // Add index to optimize the query performance for querying non-expired session instances by accountId.
10
+ await pool.query(sql `
11
+ create index concurrently if not exists oidc_model_instances__session_payload_account_id_expires_at
12
+ on oidc_model_instances (tenant_id, (payload->>'accountId'), expires_at)
13
+ WHERE model_name = 'Session';
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 if exists oidc_model_instances__expires_at;
22
+ `);
23
+ await pool.query(sql `
24
+ drop index concurrently if exists oidc_model_instances__session_payload_account_id_expires_at;
25
+ `);
26
+ },
27
+ down: async () => {
28
+ /** 'concurrently' cannot be used inside a transaction, so this up is intentionally left empty. */
29
+ },
30
+ };
31
+ export default alteration;
@@ -0,0 +1,16 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ const alteration = {
3
+ up: async (pool) => {
4
+ await pool.query(sql `
5
+ alter table oidc_session_extensions
6
+ add column client_id varchar(21) null
7
+ `);
8
+ },
9
+ down: async (pool) => {
10
+ await pool.query(sql `
11
+ alter table oidc_session_extensions
12
+ drop column client_id
13
+ `);
14
+ },
15
+ };
16
+ export default alteration;
@@ -62,6 +62,8 @@ export * from './sso-connector-idp-initiated-auth-config.js';
62
62
  export * from './sso-connector.js';
63
63
  export * from './subject-token.js';
64
64
  export * from './system.js';
65
+ export * from './user-geo-location.js';
66
+ export * from './user-sign-in-country.js';
65
67
  export * from './user-sso-identity.js';
66
68
  export * from './user.js';
67
69
  export * from './users-role.js';
@@ -63,6 +63,8 @@ export * from './sso-connector-idp-initiated-auth-config.js';
63
63
  export * from './sso-connector.js';
64
64
  export * from './subject-token.js';
65
65
  export * from './system.js';
66
+ export * from './user-geo-location.js';
67
+ export * from './user-sign-in-country.js';
66
68
  export * from './user-sso-identity.js';
67
69
  export * from './user.js';
68
70
  export * from './users-role.js';
@@ -9,6 +9,7 @@ export type CreateOidcSessionExtension = {
9
9
  sessionUid: string;
10
10
  accountId: string;
11
11
  lastSubmission?: JsonObject;
12
+ clientId?: string | null;
12
13
  createdAt?: number;
13
14
  updatedAt?: number;
14
15
  };
@@ -17,8 +18,9 @@ export type OidcSessionExtension = {
17
18
  sessionUid: string;
18
19
  accountId: string;
19
20
  lastSubmission: JsonObject;
21
+ clientId: string | null;
20
22
  createdAt: number;
21
23
  updatedAt: number;
22
24
  };
23
- export type OidcSessionExtensionKeys = 'tenantId' | 'sessionUid' | 'accountId' | 'lastSubmission' | 'createdAt' | 'updatedAt';
25
+ export type OidcSessionExtensionKeys = 'tenantId' | 'sessionUid' | 'accountId' | 'lastSubmission' | 'clientId' | 'createdAt' | 'updatedAt';
24
26
  export declare const OidcSessionExtensions: GeneratedSchema<OidcSessionExtensionKeys, CreateOidcSessionExtension, OidcSessionExtension, 'oidc_session_extensions', 'oidc_session_extension'>;