@logto/schemas 1.11.0 → 1.13.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 (158) hide show
  1. package/alterations/1.12.0-1700031616-update-org-role-foreign-keys.ts +35 -0
  2. package/alterations/1.12.0-1701054133-add-unique-constraint-to-the-sso-connector-name.ts +21 -0
  3. package/alterations/1.12.0-1701245520-add-single-sign-on-enabled-flag-to-sie.ts +20 -0
  4. package/alterations/1.13.0-1702274830-add-new-third-party-column-to-applications-table.ts +20 -0
  5. package/alterations/1.13.0-1702372401-add-application-permissions-tables.ts +93 -0
  6. package/alterations/1.13.0-1702544178-sync-tenant-orgs.ts +296 -0
  7. package/alterations/1.13.0-1702871078-protected-application-type.ts +24 -0
  8. package/alterations/1.13.0-1702877515-protected-app-configs.ts +18 -0
  9. package/alterations/1.13.0-1702978120-application-sign-in-experience-table.ts +61 -0
  10. package/alterations/1.13.0-1703229996-daily-token-usage.ts +62 -0
  11. package/alterations/1.13.0-1703230000-update-tenant-roles.ts +94 -0
  12. package/alterations/1.13.0-1704692973-remove-legacy-resources.ts +147 -0
  13. package/alterations/1.13.0-1704934999-add-magic-links-table.ts +37 -0
  14. package/alterations/1.13.0-1704935001-add-organization-invitation-tables.ts +78 -0
  15. package/alterations/1.13.0-1705288654-add-application-user-consent-organizations-table.ts +62 -0
  16. package/alterations/1.13.0-1705991158-update-invitation-indices.ts +32 -0
  17. package/alterations/1.13.0-1706449174-update-organization-invitation-column.ts +24 -0
  18. package/alterations/1.13.0-1706510290-protected-app-host-index.ts +21 -0
  19. package/alterations/1.13.0-1706512952-restore-get-started-page.ts +17 -0
  20. package/alterations/1.13.0-1706528755-remove-magic-links.ts +46 -0
  21. package/alterations/1.13.0-1706585206-protected-app-custom-domain-unique.ts +21 -0
  22. package/alterations/utils/1704934999-tables.ts +49 -0
  23. package/alterations/utils/README.md +9 -0
  24. package/alterations-js/1.12.0-1700031616-update-org-role-foreign-keys.d.ts +3 -0
  25. package/alterations-js/1.12.0-1700031616-update-org-role-foreign-keys.js +31 -0
  26. package/alterations-js/1.12.0-1701054133-add-unique-constraint-to-the-sso-connector-name.d.ts +3 -0
  27. package/alterations-js/1.12.0-1701054133-add-unique-constraint-to-the-sso-connector-name.js +17 -0
  28. package/alterations-js/1.12.0-1701245520-add-single-sign-on-enabled-flag-to-sie.d.ts +3 -0
  29. package/alterations-js/1.12.0-1701245520-add-single-sign-on-enabled-flag-to-sie.js +16 -0
  30. package/alterations-js/1.13.0-1702274830-add-new-third-party-column-to-applications-table.d.ts +3 -0
  31. package/alterations-js/1.13.0-1702274830-add-new-third-party-column-to-applications-table.js +16 -0
  32. package/alterations-js/1.13.0-1702372401-add-application-permissions-tables.d.ts +3 -0
  33. package/alterations-js/1.13.0-1702372401-add-application-permissions-tables.js +79 -0
  34. package/alterations-js/1.13.0-1702544178-sync-tenant-orgs.d.ts +18 -0
  35. package/alterations-js/1.13.0-1702544178-sync-tenant-orgs.js +225 -0
  36. package/alterations-js/1.13.0-1702871078-protected-application-type.d.ts +3 -0
  37. package/alterations-js/1.13.0-1702871078-protected-application-type.js +20 -0
  38. package/alterations-js/1.13.0-1702877515-protected-app-configs.d.ts +3 -0
  39. package/alterations-js/1.13.0-1702877515-protected-app-configs.js +14 -0
  40. package/alterations-js/1.13.0-1702978120-application-sign-in-experience-table.d.ts +3 -0
  41. package/alterations-js/1.13.0-1702978120-application-sign-in-experience-table.js +51 -0
  42. package/alterations-js/1.13.0-1703229996-daily-token-usage.d.ts +3 -0
  43. package/alterations-js/1.13.0-1703229996-daily-token-usage.js +51 -0
  44. package/alterations-js/1.13.0-1703230000-update-tenant-roles.d.ts +11 -0
  45. package/alterations-js/1.13.0-1703230000-update-tenant-roles.js +87 -0
  46. package/alterations-js/1.13.0-1704692973-remove-legacy-resources.d.ts +3 -0
  47. package/alterations-js/1.13.0-1704692973-remove-legacy-resources.js +124 -0
  48. package/alterations-js/1.13.0-1704934999-add-magic-links-table.d.ts +3 -0
  49. package/alterations-js/1.13.0-1704934999-add-magic-links-table.js +32 -0
  50. package/alterations-js/1.13.0-1704935001-add-organization-invitation-tables.d.ts +3 -0
  51. package/alterations-js/1.13.0-1704935001-add-organization-invitation-tables.js +72 -0
  52. package/alterations-js/1.13.0-1705288654-add-application-user-consent-organizations-table.d.ts +3 -0
  53. package/alterations-js/1.13.0-1705288654-add-application-user-consent-organizations-table.js +52 -0
  54. package/alterations-js/1.13.0-1705991158-update-invitation-indices.d.ts +7 -0
  55. package/alterations-js/1.13.0-1705991158-update-invitation-indices.js +27 -0
  56. package/alterations-js/1.13.0-1706449174-update-organization-invitation-column.d.ts +3 -0
  57. package/alterations-js/1.13.0-1706449174-update-organization-invitation-column.js +20 -0
  58. package/alterations-js/1.13.0-1706510290-protected-app-host-index.d.ts +3 -0
  59. package/alterations-js/1.13.0-1706510290-protected-app-host-index.js +17 -0
  60. package/alterations-js/1.13.0-1706512952-restore-get-started-page.d.ts +3 -0
  61. package/alterations-js/1.13.0-1706512952-restore-get-started-page.js +13 -0
  62. package/alterations-js/1.13.0-1706528755-remove-magic-links.d.ts +3 -0
  63. package/alterations-js/1.13.0-1706528755-remove-magic-links.js +41 -0
  64. package/alterations-js/1.13.0-1706585206-protected-app-custom-domain-unique.d.ts +3 -0
  65. package/alterations-js/1.13.0-1706585206-protected-app-custom-domain-unique.js +17 -0
  66. package/alterations-js/utils/1704934999-tables.d.ts +11 -0
  67. package/alterations-js/utils/1704934999-tables.js +43 -0
  68. package/lib/consts/index.d.ts +1 -0
  69. package/lib/consts/index.js +1 -0
  70. package/lib/consts/subscriptions.d.ts +6 -0
  71. package/lib/consts/subscriptions.js +7 -0
  72. package/lib/db-entries/application-sign-in-experience.d.ts +26 -0
  73. package/lib/db-entries/application-sign-in-experience.js +42 -0
  74. package/lib/db-entries/application-user-consent-organization-scope.d.ts +24 -0
  75. package/lib/db-entries/application-user-consent-organization-scope.js +29 -0
  76. package/lib/db-entries/application-user-consent-organization.d.ts +22 -0
  77. package/lib/db-entries/application-user-consent-organization.js +33 -0
  78. package/lib/db-entries/application-user-consent-resource-scope.d.ts +24 -0
  79. package/lib/db-entries/application-user-consent-resource-scope.js +29 -0
  80. package/lib/db-entries/application-user-consent-user-scope.d.ts +24 -0
  81. package/lib/db-entries/application-user-consent-user-scope.js +29 -0
  82. package/lib/db-entries/application.d.ts +6 -2
  83. package/lib/db-entries/application.js +9 -1
  84. package/lib/db-entries/custom-types.d.ts +8 -1
  85. package/lib/db-entries/custom-types.js +8 -0
  86. package/lib/db-entries/daily-token-usage.d.ts +20 -0
  87. package/lib/db-entries/daily-token-usage.js +33 -0
  88. package/lib/db-entries/index.d.ts +8 -0
  89. package/lib/db-entries/index.js +8 -0
  90. package/lib/db-entries/organization-invitation-role-relation.d.ts +24 -0
  91. package/lib/db-entries/organization-invitation-role-relation.js +29 -0
  92. package/lib/db-entries/organization-invitation.d.ts +53 -0
  93. package/lib/db-entries/organization-invitation.js +58 -0
  94. package/lib/db-entries/sign-in-experience.d.ts +3 -1
  95. package/lib/db-entries/sign-in-experience.js +4 -0
  96. package/lib/db-entries/sso-connector.d.ts +2 -2
  97. package/lib/foundations/jsonb-types/applications.d.ts +380 -0
  98. package/lib/foundations/jsonb-types/applications.js +29 -0
  99. package/lib/foundations/jsonb-types/custom-domain.d.ts +37 -89
  100. package/lib/foundations/jsonb-types/custom-domain.js +4 -9
  101. package/lib/foundations/jsonb-types/index.d.ts +1 -0
  102. package/lib/foundations/jsonb-types/index.js +1 -0
  103. package/lib/foundations/jsonb-types/sso-connector.d.ts +3 -0
  104. package/lib/foundations/jsonb-types/sso-connector.js +1 -0
  105. package/lib/models/tenants.d.ts +1 -1
  106. package/lib/seeds/application.d.ts +1 -1
  107. package/lib/seeds/application.js +3 -1
  108. package/lib/seeds/cloud-api.d.ts +0 -2
  109. package/lib/seeds/cloud-api.js +0 -3
  110. package/lib/seeds/management-api.d.ts +77 -6
  111. package/lib/seeds/management-api.js +14 -10
  112. package/lib/types/application.d.ts +576 -1
  113. package/lib/types/application.js +42 -1
  114. package/lib/types/connector.js +1 -1
  115. package/lib/types/consent.d.ts +568 -0
  116. package/lib/types/consent.js +47 -0
  117. package/lib/types/domain.d.ts +21 -65
  118. package/lib/types/hook.d.ts +1 -0
  119. package/lib/types/index.d.ts +3 -0
  120. package/lib/types/index.js +3 -0
  121. package/lib/types/interactions.d.ts +6 -6
  122. package/lib/types/logto-config.d.ts +32 -6
  123. package/lib/types/logto-config.js +12 -2
  124. package/lib/types/mapi-proxy.d.ts +30 -0
  125. package/lib/types/mapi-proxy.js +49 -0
  126. package/lib/types/organization.d.ts +10 -1
  127. package/lib/types/organization.js +4 -1
  128. package/lib/types/sso-connector.d.ts +63 -66
  129. package/lib/types/sso-connector.js +41 -7
  130. package/lib/types/system.d.ts +28 -1
  131. package/lib/types/system.js +17 -0
  132. package/lib/types/tenant-organization.d.ts +107 -0
  133. package/lib/types/tenant-organization.js +145 -0
  134. package/lib/types/tenant.d.ts +0 -1
  135. package/lib/types/tenant.js +2 -1
  136. package/lib/types/user-assets.d.ts +5 -5
  137. package/lib/types/user-assets.js +1 -0
  138. package/lib/types/user.d.ts +17 -15
  139. package/lib/types/user.js +2 -2
  140. package/lib/utils/domain.d.ts +10 -0
  141. package/lib/utils/domain.js +28 -0
  142. package/lib/utils/domain.test.d.ts +1 -0
  143. package/lib/utils/domain.test.js +34 -0
  144. package/lib/utils/index.d.ts +1 -0
  145. package/lib/utils/index.js +1 -0
  146. package/package.json +15 -15
  147. package/tables/application_sign_in_experiences.sql +15 -0
  148. package/tables/application_user_consent_organization_scopes.sql +14 -0
  149. package/tables/application_user_consent_organizations.sql +16 -0
  150. package/tables/application_user_consent_resource_scopes.sql +14 -0
  151. package/tables/application_user_consent_user_scopes.sql +13 -0
  152. package/tables/applications.sql +16 -1
  153. package/tables/daily_token_usage.sql +11 -0
  154. package/tables/organization_invitation_role_relations.sql +14 -0
  155. package/tables/organization_invitations.sql +36 -0
  156. package/tables/organization_role_user_relations.sql +8 -6
  157. package/tables/sign_in_experiences.sql +1 -0
  158. package/tables/sso_connectors.sql +4 -2
@@ -0,0 +1,35 @@
1
+ import { sql } from '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 organization_role_user_relations
9
+ drop constraint organization_role_user_relations_organization_id_fkey;
10
+ alter table organization_role_user_relations
11
+ drop constraint organization_role_user_relations_user_id_fkey;
12
+ alter table organization_role_user_relations
13
+ add foreign key (tenant_id, organization_id, user_id)
14
+ references organization_user_relations (tenant_id, organization_id, user_id)
15
+ on update cascade on delete cascade;
16
+ `);
17
+ },
18
+ down: async (pool) => {
19
+ await pool.query(sql`
20
+ alter table organization_role_user_relations
21
+ -- The constraint name is strange because it's generated by Postgres and it has a 63 character limit
22
+ drop constraint organization_role_user_relati_tenant_id_organization_id_us_fkey;
23
+ alter table organization_role_user_relations
24
+ add foreign key (organization_id)
25
+ references organizations (id)
26
+ on update cascade on delete cascade;
27
+ alter table organization_role_user_relations
28
+ add foreign key (user_id)
29
+ references users (id)
30
+ on update cascade on delete cascade;
31
+ `);
32
+ },
33
+ };
34
+
35
+ export default alteration;
@@ -0,0 +1,21 @@
1
+ import { sql } from '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 sso_connectors
9
+ add constraint sso_connectors__connector_name__unique
10
+ unique (tenant_id, connector_name);
11
+ `);
12
+ },
13
+ down: async (pool) => {
14
+ await pool.query(sql`
15
+ alter table sso_connectors
16
+ drop constraint sso_connectors__connector_name__unique;
17
+ `);
18
+ },
19
+ };
20
+
21
+ export default alteration;
@@ -0,0 +1,20 @@
1
+ import { sql } from '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 single_sign_on_enabled boolean not null default false;
10
+ `);
11
+ },
12
+ down: async (pool) => {
13
+ await pool.query(sql`
14
+ alter table sign_in_experiences
15
+ drop column single_sign_on_enabled;
16
+ `);
17
+ },
18
+ };
19
+
20
+ export default alteration;
@@ -0,0 +1,20 @@
1
+ import { sql } from '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 applications add is_third_party boolean not null default false;
9
+ create index applications__is_third_party on applications (tenant_id, is_third_party);
10
+ `);
11
+ },
12
+ down: async (pool) => {
13
+ await pool.query(sql`
14
+ drop index applications__is_third_party;
15
+ alter table applications drop is_third_party;
16
+ `);
17
+ },
18
+ };
19
+
20
+ export default alteration;
@@ -0,0 +1,93 @@
1
+ import { type CommonQueryMethods, sql } from 'slonik';
2
+
3
+ import type { AlterationScript } from '../lib/types/alteration.js';
4
+
5
+ const getDatabaseName = async (pool: CommonQueryMethods) => {
6
+ const { currentDatabase } = await pool.one<{ currentDatabase: string }>(sql`
7
+ select current_database();
8
+ `);
9
+
10
+ return currentDatabase.replaceAll('-', '_');
11
+ };
12
+
13
+ const enableRls = async (pool: CommonQueryMethods, database: string, table: string) => {
14
+ const baseRoleId = sql.identifier([`logto_tenant_${database}`]);
15
+
16
+ await pool.query(sql`
17
+ create trigger set_tenant_id before insert on ${sql.identifier([table])}
18
+ for each row execute procedure set_tenant_id();
19
+
20
+ alter table ${sql.identifier([table])} enable row level security;
21
+
22
+ create policy ${sql.identifier([`${table}_tenant_id`])} on ${sql.identifier([table])}
23
+ as restrictive
24
+ using (tenant_id = (select id from tenants where db_user = current_user));
25
+
26
+ create policy ${sql.identifier([`${table}_modification`])} on ${sql.identifier([table])}
27
+ using (true);
28
+
29
+ grant select, insert, update, delete on ${sql.identifier([table])} to ${baseRoleId};
30
+ `);
31
+ };
32
+
33
+ const alteration: AlterationScript = {
34
+ up: async (pool) => {
35
+ const database = await getDatabaseName(pool);
36
+
37
+ await pool.query(sql`
38
+ create table application_user_consent_resource_scopes (
39
+ tenant_id varchar(21) not null
40
+ references tenants (id) on update cascade on delete cascade,
41
+ /** The globally unique identifier of the application. */
42
+ application_id varchar(21) not null
43
+ references applications (id) on update cascade on delete cascade,
44
+ /** The globally unique identifier of the resource scope. */
45
+ scope_id varchar(21) not null
46
+ references scopes (id) on update cascade on delete cascade,
47
+ primary key (application_id, scope_id)
48
+ );
49
+ `);
50
+
51
+ await enableRls(pool, database, 'application_user_consent_resource_scopes');
52
+
53
+ await pool.query(sql`
54
+ create table application_user_consent_organization_scopes (
55
+ tenant_id varchar(21) not null
56
+ references tenants (id) on update cascade on delete cascade,
57
+ /** The globally unique identifier of the application. */
58
+ application_id varchar(21) not null
59
+ references applications (id) on update cascade on delete cascade,
60
+ /** The globally unique identifier of the organization scope. */
61
+ organization_scope_id varchar(21) not null
62
+ references organization_scopes (id) on update cascade on delete cascade,
63
+ primary key (application_id, organization_scope_id)
64
+ );
65
+ `);
66
+
67
+ await enableRls(pool, database, 'application_user_consent_organization_scopes');
68
+
69
+ await pool.query(sql`
70
+ create table application_user_consent_user_scopes (
71
+ tenant_id varchar(21) not null
72
+ references tenants (id) on update cascade on delete cascade,
73
+ /** The globally unique identifier of the application. */
74
+ application_id varchar(21) not null
75
+ references applications (id) on update cascade on delete cascade,
76
+ /** The unique UserScope enum value @see (@logto/core-kit) for reference */
77
+ user_scope varchar(64) not null,
78
+ primary key (application_id, user_scope)
79
+ );
80
+ `);
81
+
82
+ await enableRls(pool, database, 'application_user_consent_user_scopes');
83
+ },
84
+ down: async (pool) => {
85
+ await pool.query(sql`
86
+ drop table application_user_consent_resource_scopes;
87
+ drop table application_user_consent_organization_scopes;
88
+ drop table application_user_consent_user_scopes;
89
+ `);
90
+ },
91
+ };
92
+
93
+ export default alteration;
@@ -0,0 +1,296 @@
1
+ /**
2
+ * @fileoverview A preparation for the update of using organizations to manage tenants in the admin
3
+ * tenant. This script will do the following in the admin tenant:
4
+ *
5
+ * 1. Create organization template roles and scopes.
6
+ * 2. Create organizations for existing tenants.
7
+ * 3. Add membership records and assign organization roles to existing users.
8
+ * 4. Create machine-to-machine Management API role for each tenant.
9
+ * 5. Create the corresponding machine-to-machine app for each tenant, and assign the Management API role to it.
10
+ *
11
+ * The `down` script will revert the changes.
12
+ *
13
+ * NOTE: In order to avoid unnecessary dirty data, it's recommended disabling the registration of
14
+ * new tenants before running this script and deploying the changes.
15
+ */
16
+
17
+ import { ConsoleLog, generateStandardId } from '@logto/shared';
18
+ import { sql } from 'slonik';
19
+
20
+ import { type AlterationScript } from '../lib/types/alteration.js';
21
+
22
+ const adminTenantId = 'admin';
23
+ const consoleLog = new ConsoleLog();
24
+
25
+ const alteration: AlterationScript = {
26
+ up: async (transaction) => {
27
+ consoleLog.info('=== Sync tenant organizations ===');
28
+
29
+ consoleLog.info('Create organization template roles and scopes');
30
+ await transaction.query(sql`
31
+ insert into public.organization_roles (id, tenant_id, name, description)
32
+ values
33
+ ('owner', ${adminTenantId}, 'owner', 'Owner of the tenant, who has all permissions.'),
34
+ ('admin', ${adminTenantId}, 'admin', 'Admin of the tenant, who has all permissions except managing the tenant settings.'),
35
+ ('member', ${adminTenantId}, 'member', 'Member of the tenant, who has limited permissions on reading and writing the tenant data.');
36
+ `);
37
+ await transaction.query(sql`
38
+ insert into public.organization_scopes (id, tenant_id, name, description)
39
+ values
40
+ ('read-data', ${adminTenantId}, 'read:data', 'Read the tenant data.'),
41
+ ('write-data', ${adminTenantId}, 'write:data', 'Write the tenant data, including creating and updating the tenant.'),
42
+ ('delete-data', ${adminTenantId}, 'delete:data', 'Delete data of the tenant.'),
43
+ ('invite-member', ${adminTenantId}, 'invite:member', 'Invite members to the tenant.'),
44
+ ('remove-member', ${adminTenantId}, 'remove:member', 'Remove members from the tenant.'),
45
+ ('update-member-role', ${adminTenantId}, 'update:member:role', 'Update the role of a member in the tenant.'),
46
+ ('manage-tenant', ${adminTenantId}, 'manage:tenant', 'Manage the tenant settings, including name, billing, etc.');
47
+ `);
48
+ await transaction.query(sql`
49
+ insert into public.organization_role_scope_relations (tenant_id, organization_role_id, organization_scope_id)
50
+ values
51
+ (${adminTenantId}, 'owner', 'read-data'),
52
+ (${adminTenantId}, 'owner', 'write-data'),
53
+ (${adminTenantId}, 'owner', 'delete-data'),
54
+ (${adminTenantId}, 'owner', 'invite-member'),
55
+ (${adminTenantId}, 'owner', 'remove-member'),
56
+ (${adminTenantId}, 'owner', 'update-member-role'),
57
+ (${adminTenantId}, 'owner', 'manage-tenant'),
58
+ (${adminTenantId}, 'admin', 'read-data'),
59
+ (${adminTenantId}, 'admin', 'write-data'),
60
+ (${adminTenantId}, 'admin', 'delete-data'),
61
+ (${adminTenantId}, 'admin', 'invite-member'),
62
+ (${adminTenantId}, 'admin', 'remove-member'),
63
+ (${adminTenantId}, 'admin', 'update-member-role'),
64
+ (${adminTenantId}, 'member', 'read-data'),
65
+ (${adminTenantId}, 'member', 'write-data'),
66
+ (${adminTenantId}, 'member', 'invite-member')
67
+ `);
68
+
69
+ consoleLog.info('Create organizations for existing tenants');
70
+ const tenants = await transaction.any<{ id: string }>(sql`
71
+ select id
72
+ from public.tenants;
73
+ `);
74
+ await transaction.query(sql`
75
+ insert into public.organizations (id, tenant_id, name)
76
+ values
77
+ ${sql.join(
78
+ tenants.map(
79
+ (tenant) => sql`(${`t-${tenant.id}`}, ${adminTenantId}, ${`Tenant ${tenant.id}`})`
80
+ ),
81
+ sql`, `
82
+ )};
83
+ `);
84
+
85
+ const usersRoles = await transaction.any<{ userId: string; roleName: string }>(sql`
86
+ select
87
+ public.users.id as "userId",
88
+ public.roles.name as "roleName"
89
+ from public.users
90
+ join public.users_roles on public.users_roles.user_id = public.users.id
91
+ join public.roles on public.roles.id = public.users_roles.role_id
92
+ where public.roles.tenant_id = ${adminTenantId}
93
+ and public.roles.name like '%:admin';
94
+ `);
95
+
96
+ if (usersRoles.length === 0) {
97
+ consoleLog.warn(
98
+ 'No existing admin users found, skip adding membership records for tenant organizations.'
99
+ );
100
+ } else {
101
+ consoleLog.info('Add membership records and assign organization roles to existing users');
102
+
103
+ // Add membership records
104
+ await transaction.query(sql`
105
+ insert into public.organization_user_relations (tenant_id, organization_id, user_id)
106
+ values
107
+ ${sql.join(
108
+ usersRoles.map(
109
+ (userRole) =>
110
+ sql`(${adminTenantId}, ${`t-${userRole.roleName.slice(0, -6)}`}, ${
111
+ userRole.userId
112
+ })`
113
+ ),
114
+ sql`, `
115
+ )};
116
+ `);
117
+ // We treat all existing users as the owner of the tenant
118
+ await transaction.query(sql`
119
+ insert into public.organization_role_user_relations (tenant_id, organization_id, user_id, organization_role_id)
120
+ values
121
+ ${sql.join(
122
+ usersRoles.map(
123
+ (userRole) =>
124
+ sql`
125
+ (
126
+ ${adminTenantId},
127
+ ${`t-${userRole.roleName.slice(0, -6)}`},
128
+ ${userRole.userId},
129
+ 'owner'
130
+ )
131
+ `
132
+ ),
133
+ sql`, `
134
+ )};
135
+ `);
136
+ }
137
+
138
+ consoleLog.info('Create machine-to-machine Management API role for each tenant');
139
+ await transaction.query(sql`
140
+ insert into public.roles (id, tenant_id, name, description, type)
141
+ values
142
+ ${sql.join(
143
+ tenants.map(
144
+ (tenant) =>
145
+ sql`
146
+ (
147
+ ${`m-${tenant.id}`},
148
+ ${adminTenantId},
149
+ ${`machine:mapi:${tenant.id}`},
150
+ ${`Machine-to-machine role for accessing Management API of tenant '${tenant.id}'.`},
151
+ 'MachineToMachine'
152
+ )
153
+ `
154
+ ),
155
+ sql`, `
156
+ )};
157
+ `);
158
+
159
+ const managementApiScopes = await transaction.any<{ id: string; indicator: string }>(sql`
160
+ select public.scopes.id, public.resources.indicator
161
+ from public.resources
162
+ join public.scopes on public.scopes.resource_id = public.resources.id
163
+ where public.resources.indicator like 'https://%.logto.app/api'
164
+ and public.scopes.name = 'all'
165
+ and public.resources.tenant_id = ${adminTenantId};
166
+ `);
167
+
168
+ const assertScopeId = (forTenantId: string) => {
169
+ const scope = managementApiScopes.find(
170
+ (scope) => scope.indicator === `https://${forTenantId}.logto.app/api`
171
+ );
172
+ if (!scope) {
173
+ throw new Error(`Cannot find Management API scope for tenant '${forTenantId}'.`);
174
+ }
175
+ return scope.id;
176
+ };
177
+
178
+ // Insert role - scope relations
179
+ await transaction.query(sql`
180
+ insert into public.roles_scopes (tenant_id, id, role_id, scope_id)
181
+ values
182
+ ${sql.join(
183
+ tenants.map(
184
+ (tenant) =>
185
+ sql`
186
+ (
187
+ ${adminTenantId},
188
+ ${generateStandardId()},
189
+ ${`m-${tenant.id}`},
190
+ ${assertScopeId(tenant.id)}
191
+ )
192
+ `
193
+ ),
194
+ sql`, `
195
+ )};
196
+ `);
197
+
198
+ consoleLog.info(
199
+ 'Create the corresponding machine-to-machine app for each tenant, and assign the Management API role to it'
200
+ );
201
+ await transaction.query(sql`
202
+ insert into public.applications (id, tenant_id, secret, name, description, type, oidc_client_metadata)
203
+ values
204
+ ${sql.join(
205
+ tenants.map(
206
+ (tenant) =>
207
+ sql`
208
+ (
209
+ ${`m-${tenant.id}`},
210
+ ${adminTenantId},
211
+ ${generateStandardId(32)},
212
+ ${`Management API access for ${tenant.id}`},
213
+ ${`Machine-to-machine app for accessing Management API of tenant '${tenant.id}'.`},
214
+ 'MachineToMachine',
215
+ ${sql.jsonb({
216
+ redirectUris: [],
217
+ postLogoutRedirectUris: [],
218
+ })}
219
+ )
220
+ `
221
+ ),
222
+ sql`, `
223
+ )};
224
+ `);
225
+ await transaction.query(sql`
226
+ insert into public.applications_roles (tenant_id, id, application_id, role_id)
227
+ values
228
+ ${sql.join(
229
+ tenants.map(
230
+ (tenant) =>
231
+ sql`
232
+ (
233
+ ${adminTenantId},
234
+ ${generateStandardId()},
235
+ ${`m-${tenant.id}`},
236
+ ${`m-${tenant.id}`}
237
+ )
238
+ `
239
+ ),
240
+ sql`, `
241
+ )};
242
+ `);
243
+
244
+ consoleLog.info('=== Sync tenant organizations done ===');
245
+ },
246
+ down: async (transaction) => {
247
+ consoleLog.info('=== Revert sync tenant organizations ===');
248
+
249
+ consoleLog.info('Remove machine-to-machine apps');
250
+ await transaction.query(sql`
251
+ delete from public.applications
252
+ where public.applications.tenant_id = ${adminTenantId}
253
+ and public.applications.id like 'm-%';
254
+ `);
255
+
256
+ consoleLog.info('Remove machine-to-machine roles');
257
+ await transaction.query(sql`
258
+ delete from public.roles
259
+ where public.roles.tenant_id = ${adminTenantId}
260
+ and public.roles.id like 'm-%';
261
+ `);
262
+
263
+ consoleLog.info('Remove organizations');
264
+ await transaction.query(sql`
265
+ delete from public.organizations
266
+ where public.organizations.tenant_id = ${adminTenantId}
267
+ and public.organizations.id like 't-%';
268
+ `);
269
+
270
+ consoleLog.info('Remove organization roles');
271
+ await transaction.query(sql`
272
+ delete from public.organization_roles
273
+ where public.organization_roles.tenant_id = ${adminTenantId}
274
+ and public.organization_roles.id in ('owner', 'admin', 'member');
275
+ `);
276
+
277
+ consoleLog.info('Remove organization scopes');
278
+ await transaction.query(sql`
279
+ delete from public.organization_scopes
280
+ where public.organization_scopes.tenant_id = ${adminTenantId}
281
+ and public.organization_scopes.id in (
282
+ 'read-data',
283
+ 'write-data',
284
+ 'delete-data',
285
+ 'invite-member',
286
+ 'remove-member',
287
+ 'update-member-role',
288
+ 'manage-tenant'
289
+ );
290
+ `);
291
+
292
+ consoleLog.info('=== Revert sync tenant organizations done ===');
293
+ },
294
+ };
295
+
296
+ export default alteration;
@@ -0,0 +1,24 @@
1
+ import { sql } from '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 type application_type add value 'Protected';
9
+ `);
10
+ },
11
+ down: async (pool) => {
12
+ await pool.query(sql`
13
+ create type application_type_new as enum ('Native', 'SPA', 'Traditional', 'MachineToMachine');
14
+ delete from applications where "type"='Protected';
15
+ alter table applications
16
+ alter column "type" type application_type_new
17
+ using ("type"::text::application_type_new);
18
+ drop type application_type;
19
+ alter type application_type_new rename to application_type;
20
+ `);
21
+ },
22
+ };
23
+
24
+ export default alteration;
@@ -0,0 +1,18 @@
1
+ import { sql } from '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 applications add protected_app_metadata jsonb;
9
+ `);
10
+ },
11
+ down: async (pool) => {
12
+ await pool.query(sql`
13
+ alter table applications drop protected_app_metadata;
14
+ `);
15
+ },
16
+ };
17
+
18
+ export default alteration;
@@ -0,0 +1,61 @@
1
+ import { type CommonQueryMethods, sql } from 'slonik';
2
+
3
+ import type { AlterationScript } from '../lib/types/alteration.js';
4
+
5
+ const getDatabaseName = async (pool: CommonQueryMethods) => {
6
+ const { currentDatabase } = await pool.one<{ currentDatabase: string }>(sql`
7
+ select current_database();
8
+ `);
9
+
10
+ return currentDatabase.replaceAll('-', '_');
11
+ };
12
+
13
+ const enableRls = async (pool: CommonQueryMethods, database: string, table: string) => {
14
+ const baseRoleId = sql.identifier([`logto_tenant_${database}`]);
15
+
16
+ await pool.query(sql`
17
+ create trigger set_tenant_id before insert on ${sql.identifier([table])}
18
+ for each row execute procedure set_tenant_id();
19
+
20
+ alter table ${sql.identifier([table])} enable row level security;
21
+
22
+ create policy ${sql.identifier([`${table}_tenant_id`])} on ${sql.identifier([table])}
23
+ as restrictive
24
+ using (tenant_id = (select id from tenants where db_user = current_user));
25
+
26
+ create policy ${sql.identifier([`${table}_modification`])} on ${sql.identifier([table])}
27
+ using (true);
28
+
29
+ grant select, insert, update, delete on ${sql.identifier([table])} to ${baseRoleId};
30
+ `);
31
+ };
32
+
33
+ const alteration: AlterationScript = {
34
+ up: async (pool) => {
35
+ const database = await getDatabaseName(pool);
36
+
37
+ await pool.query(sql`
38
+ create table application_sign_in_experiences (
39
+ tenant_id varchar(21) not null
40
+ references tenants (id) on update cascade on delete cascade,
41
+ application_id varchar(21) not null
42
+ references applications (id) on update cascade on delete cascade,
43
+ branding jsonb /* @use Branding */ not null default '{}'::jsonb,
44
+ terms_of_use_url varchar(2048),
45
+ privacy_policy_url varchar(2048),
46
+ display_name varchar(256),
47
+
48
+ primary key (tenant_id, application_id)
49
+ );
50
+ `);
51
+
52
+ await enableRls(pool, database, 'application_sign_in_experiences');
53
+ },
54
+ down: async (pool) => {
55
+ await pool.query(sql`
56
+ drop table application_sign_in_experiences;
57
+ `);
58
+ },
59
+ };
60
+
61
+ export default alteration;
@@ -0,0 +1,62 @@
1
+ import { type CommonQueryMethods, sql } from 'slonik';
2
+
3
+ import type { AlterationScript } from '../lib/types/alteration.js';
4
+
5
+ const getId = (value: string) => sql.identifier([value]);
6
+
7
+ const getDatabaseName = async (pool: CommonQueryMethods) => {
8
+ const { currentDatabase } = await pool.one<{ currentDatabase: string }>(sql`
9
+ select current_database();
10
+ `);
11
+
12
+ return currentDatabase.replaceAll('-', '_');
13
+ };
14
+
15
+ const enableRls = async (pool: CommonQueryMethods, database: string, table: string) => {
16
+ const baseRoleId = sql.identifier([`logto_tenant_${database}`]);
17
+
18
+ await pool.query(sql`
19
+ create trigger set_tenant_id before insert on ${sql.identifier([table])}
20
+ for each row execute procedure set_tenant_id();
21
+
22
+ alter table ${sql.identifier([table])} enable row level security;
23
+
24
+ create policy ${sql.identifier([`${table}_tenant_id`])} on ${sql.identifier([table])}
25
+ as restrictive
26
+ using (tenant_id = (select id from tenants where db_user = current_user));
27
+
28
+ create policy ${sql.identifier([`${table}_modification`])} on ${sql.identifier([table])}
29
+ using (true);
30
+
31
+ grant select, insert, update, delete on ${sql.identifier([table])} to ${baseRoleId};
32
+ `);
33
+ };
34
+
35
+ const alteration: AlterationScript = {
36
+ up: async (pool) => {
37
+ const database = await getDatabaseName(pool);
38
+
39
+ await pool.query(sql`
40
+ create table daily_token_usage (
41
+ id varchar(21) not null,
42
+ tenant_id varchar(21) not null
43
+ references tenants (id) on update cascade on delete cascade,
44
+ usage bigint not null default(0),
45
+ date timestamptz not null,
46
+ primary key (id)
47
+ );
48
+
49
+ create unique index daily_token_usage__date
50
+ on daily_token_usage (tenant_id, date);
51
+ `);
52
+
53
+ await enableRls(pool, database, 'daily_token_usage');
54
+ },
55
+ down: async (pool) => {
56
+ await pool.query(sql`
57
+ drop table daily_token_usage;
58
+ `);
59
+ },
60
+ };
61
+
62
+ export default alteration;