@logto/schemas 1.10.0 → 1.10.1

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 (144) hide show
  1. package/alterations/1.10.1-1695647183-update-private-key-type.ts +108 -0
  2. package/alterations/1.10.1-1696657546-organization-tables.ts +150 -0
  3. package/alterations/1.10.1-1697683802-add-sso-connectors-table.ts +66 -0
  4. package/alterations/1.10.1-1698646271-add-organization-created-flag.ts +75 -0
  5. package/alterations/1.10.1-1698820410-add-user-sso-identities-table.ts +61 -0
  6. package/alterations/1.10.1-1698910485-user-logto-data.ts +20 -0
  7. package/alterations-js/1.10.1-1695647183-update-private-key-type.d.ts +3 -0
  8. package/alterations-js/1.10.1-1695647183-update-private-key-type.js +50 -0
  9. package/alterations-js/1.10.1-1696657546-organization-tables.d.ts +3 -0
  10. package/alterations-js/1.10.1-1696657546-organization-tables.js +136 -0
  11. package/alterations-js/1.10.1-1697683802-add-sso-connectors-table.d.ts +3 -0
  12. package/alterations-js/1.10.1-1697683802-add-sso-connectors-table.js +58 -0
  13. package/alterations-js/1.10.1-1698646271-add-organization-created-flag.d.ts +3 -0
  14. package/alterations-js/1.10.1-1698646271-add-organization-created-flag.js +26 -0
  15. package/alterations-js/1.10.1-1698820410-add-user-sso-identities-table.d.ts +4 -0
  16. package/alterations-js/1.10.1-1698820410-add-user-sso-identities-table.js +53 -0
  17. package/alterations-js/1.10.1-1698910485-user-logto-data.d.ts +3 -0
  18. package/alterations-js/1.10.1-1698910485-user-logto-data.js +16 -0
  19. package/lib/db-entries/application.d.ts +7 -1
  20. package/lib/db-entries/application.js +1 -0
  21. package/lib/db-entries/applications-role.d.ts +7 -1
  22. package/lib/db-entries/applications-role.js +1 -0
  23. package/lib/db-entries/connector.d.ts +7 -1
  24. package/lib/db-entries/connector.js +1 -0
  25. package/lib/db-entries/custom-phrase.d.ts +7 -1
  26. package/lib/db-entries/custom-phrase.js +1 -0
  27. package/lib/db-entries/daily-active-user.d.ts +7 -1
  28. package/lib/db-entries/daily-active-user.js +1 -0
  29. package/lib/db-entries/domain.d.ts +7 -1
  30. package/lib/db-entries/domain.js +1 -0
  31. package/lib/db-entries/hook.d.ts +7 -1
  32. package/lib/db-entries/hook.js +1 -0
  33. package/lib/db-entries/index.d.ts +8 -0
  34. package/lib/db-entries/index.js +8 -0
  35. package/lib/db-entries/log.d.ts +7 -1
  36. package/lib/db-entries/log.js +1 -0
  37. package/lib/db-entries/logto-config.d.ts +10 -4
  38. package/lib/db-entries/logto-config.js +4 -3
  39. package/lib/db-entries/oidc-model-instance.d.ts +7 -1
  40. package/lib/db-entries/oidc-model-instance.js +1 -0
  41. package/lib/db-entries/organization-role-scope-relation.d.ts +20 -0
  42. package/lib/db-entries/organization-role-scope-relation.js +29 -0
  43. package/lib/db-entries/organization-role-user-relation.d.ts +22 -0
  44. package/lib/db-entries/organization-role-user-relation.js +33 -0
  45. package/lib/db-entries/organization-role.d.ts +28 -0
  46. package/lib/db-entries/organization-role.js +33 -0
  47. package/lib/db-entries/organization-scope.d.ts +28 -0
  48. package/lib/db-entries/organization-scope.js +33 -0
  49. package/lib/db-entries/organization-user-relation.d.ts +20 -0
  50. package/lib/db-entries/organization-user-relation.js +29 -0
  51. package/lib/db-entries/organization.d.ts +32 -0
  52. package/lib/db-entries/organization.js +37 -0
  53. package/lib/db-entries/passcode.d.ts +7 -1
  54. package/lib/db-entries/passcode.js +1 -0
  55. package/lib/db-entries/resource.d.ts +7 -1
  56. package/lib/db-entries/resource.js +1 -0
  57. package/lib/db-entries/role.d.ts +7 -1
  58. package/lib/db-entries/role.js +1 -0
  59. package/lib/db-entries/roles-scope.d.ts +7 -1
  60. package/lib/db-entries/roles-scope.js +1 -0
  61. package/lib/db-entries/scope.d.ts +7 -1
  62. package/lib/db-entries/scope.js +1 -0
  63. package/lib/db-entries/sentinel-activity.d.ts +7 -1
  64. package/lib/db-entries/sentinel-activity.js +1 -0
  65. package/lib/db-entries/service-log.d.ts +7 -1
  66. package/lib/db-entries/service-log.js +1 -0
  67. package/lib/db-entries/sign-in-experience.d.ts +7 -1
  68. package/lib/db-entries/sign-in-experience.js +1 -0
  69. package/lib/db-entries/sso-connector.d.ts +50 -0
  70. package/lib/db-entries/sso-connector.js +58 -0
  71. package/lib/db-entries/system.d.ts +7 -1
  72. package/lib/db-entries/system.js +1 -0
  73. package/lib/db-entries/user-sso-identity.d.ts +30 -0
  74. package/lib/db-entries/user-sso-identity.js +46 -0
  75. package/lib/db-entries/user.d.ts +9 -1
  76. package/lib/db-entries/user.js +5 -0
  77. package/lib/db-entries/users-role.d.ts +7 -1
  78. package/lib/db-entries/users-role.js +1 -0
  79. package/lib/db-entries/verification-status.d.ts +7 -1
  80. package/lib/db-entries/verification-status.js +1 -0
  81. package/lib/foundations/index.d.ts +1 -1
  82. package/lib/foundations/index.js +1 -1
  83. package/lib/foundations/jsonb-types/custom-domain.d.ts +134 -0
  84. package/lib/foundations/jsonb-types/custom-domain.js +36 -0
  85. package/lib/foundations/jsonb-types/hooks.d.ts +32 -0
  86. package/lib/foundations/jsonb-types/hooks.js +24 -0
  87. package/lib/foundations/jsonb-types/index.d.ts +15 -0
  88. package/lib/foundations/jsonb-types/index.js +16 -0
  89. package/lib/foundations/jsonb-types/logs.d.ts +106 -0
  90. package/lib/foundations/jsonb-types/logs.js +20 -0
  91. package/lib/foundations/jsonb-types/oidc-module.d.ts +80 -0
  92. package/lib/foundations/jsonb-types/oidc-module.js +54 -0
  93. package/lib/foundations/jsonb-types/phrases.d.ts +5 -0
  94. package/lib/foundations/jsonb-types/phrases.js +2 -0
  95. package/lib/foundations/jsonb-types/sentinel.d.ts +27 -0
  96. package/lib/foundations/jsonb-types/sentinel.js +28 -0
  97. package/lib/foundations/jsonb-types/sign-in-experience.d.ts +118 -0
  98. package/lib/foundations/jsonb-types/sign-in-experience.js +56 -0
  99. package/lib/foundations/jsonb-types/sso-connector.d.ts +14 -0
  100. package/lib/foundations/jsonb-types/sso-connector.js +6 -0
  101. package/lib/foundations/jsonb-types/users.d.ts +285 -0
  102. package/lib/foundations/jsonb-types/users.js +47 -0
  103. package/lib/foundations/schemas.d.ts +11 -13
  104. package/lib/models/tenants.d.ts +7 -11
  105. package/lib/seeds/logto-config.js +1 -0
  106. package/lib/types/application.d.ts +51 -1
  107. package/lib/types/application.js +7 -1
  108. package/lib/types/connector.d.ts +516 -2360
  109. package/lib/types/domain.d.ts +65 -27
  110. package/lib/types/hook.d.ts +15 -16
  111. package/lib/types/index.d.ts +3 -0
  112. package/lib/types/index.js +3 -0
  113. package/lib/types/interactions.d.ts +502 -10
  114. package/lib/types/interactions.js +83 -5
  115. package/lib/types/log/interaction.d.ts +4 -3
  116. package/lib/types/log/interaction.js +1 -0
  117. package/lib/types/logto-config.d.ts +50 -2
  118. package/lib/types/logto-config.js +30 -3
  119. package/lib/types/mfa.d.ts +211 -0
  120. package/lib/types/mfa.js +62 -0
  121. package/lib/types/organization.d.ts +44 -0
  122. package/lib/types/organization.js +20 -0
  123. package/lib/types/role.d.ts +5 -3
  124. package/lib/types/scope.d.ts +12 -27
  125. package/lib/types/sso-connector.d.ts +21 -0
  126. package/lib/types/sso-connector.js +10 -0
  127. package/lib/types/system.d.ts +26 -7
  128. package/lib/types/system.js +8 -0
  129. package/lib/types/user-assets.d.ts +2 -2
  130. package/lib/types/user.d.ts +209 -66
  131. package/lib/types/user.js +8 -2
  132. package/package.json +6 -6
  133. package/tables/logto_configs.sql +1 -1
  134. package/tables/organization_role_scope_relations.sql +12 -0
  135. package/tables/organization_role_user_relations.sql +14 -0
  136. package/tables/organization_roles.sql +19 -0
  137. package/tables/organization_scopes.sql +19 -0
  138. package/tables/organization_user_relations.sql +12 -0
  139. package/tables/organizations.sql +19 -0
  140. package/tables/sso_connectors.sql +29 -0
  141. package/tables/user_sso_identities.sql +17 -0
  142. package/tables/users.sql +1 -0
  143. package/lib/foundations/jsonb-types.d.ts +0 -673
  144. package/lib/foundations/jsonb-types.js +0 -260
@@ -0,0 +1,108 @@
1
+ import { generateStandardId } from '@logto/shared';
2
+ import type { DatabaseTransactionConnection } from 'slonik';
3
+ import { sql } from 'slonik';
4
+
5
+ import type { AlterationScript } from '../lib/types/alteration.js';
6
+
7
+ const targetConfigKeys = ['oidc.cookieKeys', 'oidc.privateKeys'];
8
+
9
+ type OldPrivateKeyData = {
10
+ tenantId: string;
11
+ value: string[];
12
+ };
13
+
14
+ type PrivateKey = {
15
+ id: string;
16
+ value: string;
17
+ createdAt: number;
18
+ };
19
+
20
+ type NewPrivateKeyData = {
21
+ tenantId: string;
22
+ value: PrivateKey[];
23
+ };
24
+
25
+ /**
26
+ * Alternate string array private signing keys to JSON array
27
+ * "oidc.cookieKeys": string[] -> PrivateKey[]
28
+ * "oidc.privateKeys": string[] -> PrivateKey[]
29
+ * @param configKey oidc.cookieKeys | oidc.privateKeys
30
+ * @param logtoConfig existing private key data for a specific tenant
31
+ * @param pool postgres database connection pool
32
+ */
33
+ const alterPrivateKeysInLogtoConfig = async (
34
+ configKey: string,
35
+ logtoConfig: OldPrivateKeyData,
36
+ pool: DatabaseTransactionConnection
37
+ ) => {
38
+ const { tenantId, value: oldPrivateKey } = logtoConfig;
39
+
40
+ // Use tenant creation time as `createdAt` timestamp for new private keys
41
+ const tenantData = await pool.maybeOne<{ createdAt: number }>(
42
+ sql`select * from tenants where id = ${tenantId}`
43
+ );
44
+ const newPrivateKeyData: PrivateKey[] = oldPrivateKey.map((key) => ({
45
+ id: generateStandardId(),
46
+ value: key,
47
+ createdAt: Math.floor((tenantData?.createdAt ?? Date.now()) / 1000),
48
+ }));
49
+
50
+ await pool.query(
51
+ sql`update logto_configs set value = ${JSON.stringify(
52
+ newPrivateKeyData
53
+ )} where tenant_id = ${tenantId} and key = ${configKey}`
54
+ );
55
+ };
56
+
57
+ /**
58
+ * Rollback JSON array private signing keys to string array
59
+ * "oidc.cookieKeys": PrivateKey[] -> string[]
60
+ * "oidc.privateKeys": PrivateKey[] -> string[]
61
+ * @param configKey oidc.cookieKeys | oidc.privateKeys
62
+ * @param logtoConfig new private key data for a specific tenant
63
+ * @param pool postgres database connection pool
64
+ */
65
+ const rollbackPrivateKeysInLogtoConfig = async (
66
+ configKey: string,
67
+ logtoConfig: NewPrivateKeyData,
68
+ pool: DatabaseTransactionConnection
69
+ ) => {
70
+ const { tenantId, value: newPrivateKeyData } = logtoConfig;
71
+
72
+ const oldPrivateKeys = newPrivateKeyData.map(({ value }) => value);
73
+
74
+ await pool.query(
75
+ sql`update logto_configs set value = ${JSON.stringify(
76
+ oldPrivateKeys
77
+ )} where tenant_id = ${tenantId} and key = ${configKey}`
78
+ );
79
+ };
80
+
81
+ const alteration: AlterationScript = {
82
+ up: async (pool) => {
83
+ await Promise.all(
84
+ targetConfigKeys.map(async (configKey) => {
85
+ const rows = await pool.many<OldPrivateKeyData>(
86
+ sql`select * from logto_configs where key = ${configKey}`
87
+ );
88
+ await Promise.all(
89
+ rows.map(async (row) => alterPrivateKeysInLogtoConfig(configKey, row, pool))
90
+ );
91
+ })
92
+ );
93
+ },
94
+ down: async (pool) => {
95
+ await Promise.all(
96
+ targetConfigKeys.map(async (configKey) => {
97
+ const rows = await pool.many<NewPrivateKeyData>(
98
+ sql`select * from logto_configs where key = ${configKey}`
99
+ );
100
+ await Promise.all(
101
+ rows.map(async (row) => rollbackPrivateKeysInLogtoConfig(configKey, row, pool))
102
+ );
103
+ })
104
+ );
105
+ },
106
+ };
107
+
108
+ export default alteration;
@@ -0,0 +1,150 @@
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 organizations (
39
+ tenant_id varchar(21) not null
40
+ references tenants (id) on update cascade on delete cascade,
41
+ /** The globally unique identifier of the organization. */
42
+ id varchar(21) not null,
43
+ /** The organization's name for display. */
44
+ name varchar(128) not null,
45
+ /** A brief description of the organization. */
46
+ description varchar(256),
47
+ /** When the organization was created. */
48
+ created_at timestamptz not null default(now()),
49
+ primary key (id)
50
+ );
51
+
52
+ create index organizations__id
53
+ on organizations (tenant_id, id);
54
+ `);
55
+ await enableRls(pool, database, 'organizations');
56
+
57
+ await pool.query(sql`
58
+ create table organization_roles (
59
+ tenant_id varchar(21) not null
60
+ references tenants (id) on update cascade on delete cascade,
61
+ /** The globally unique identifier of the organization role. */
62
+ id varchar(21) not null,
63
+ /** The organization role's name, unique within the organization template. */
64
+ name varchar(128) not null,
65
+ /** A brief description of the organization role. */
66
+ description varchar(256),
67
+ primary key (id),
68
+ constraint organization_roles__name
69
+ unique (tenant_id, name)
70
+ );
71
+
72
+ create index organization_roles__id
73
+ on organization_roles (tenant_id, id);
74
+ `);
75
+ await enableRls(pool, database, 'organization_roles');
76
+
77
+ await pool.query(sql`
78
+ create table organization_scopes (
79
+ tenant_id varchar(21) not null
80
+ references tenants (id) on update cascade on delete cascade,
81
+ /** The globally unique identifier of the organization scope. */
82
+ id varchar(21) not null,
83
+ /** The organization scope's name, unique within the organization template. */
84
+ name varchar(128) not null,
85
+ /** A brief description of the organization scope. */
86
+ description varchar(256),
87
+ primary key (id),
88
+ constraint organization_scopes__name
89
+ unique (tenant_id, name)
90
+ );
91
+
92
+ create index organization_scopes__id
93
+ on organization_scopes (tenant_id, id);
94
+ `);
95
+ await enableRls(pool, database, 'organization_scopes');
96
+
97
+ await pool.query(sql`
98
+ create table organization_role_user_relations (
99
+ tenant_id varchar(21) not null
100
+ references tenants (id) on update cascade on delete cascade,
101
+ organization_id varchar(21) not null
102
+ references organizations (id) on update cascade on delete cascade,
103
+ organization_role_id varchar(21) not null
104
+ references organization_roles (id) on update cascade on delete cascade,
105
+ user_id varchar(21) not null
106
+ references users (id) on update cascade on delete cascade,
107
+ primary key (tenant_id, organization_id, organization_role_id, user_id)
108
+ );
109
+ `);
110
+ await enableRls(pool, database, 'organization_role_user_relations');
111
+
112
+ await pool.query(sql`
113
+ create table organization_role_scope_relations (
114
+ tenant_id varchar(21) not null
115
+ references tenants (id) on update cascade on delete cascade,
116
+ organization_role_id varchar(21) not null
117
+ references organization_roles (id) on update cascade on delete cascade,
118
+ organization_scope_id varchar(21) not null
119
+ references organization_scopes (id) on update cascade on delete cascade,
120
+ primary key (tenant_id, organization_role_id, organization_scope_id)
121
+ );
122
+ `);
123
+ await enableRls(pool, database, 'organization_role_scope_relations');
124
+
125
+ await pool.query(sql`
126
+ create table organization_user_relations (
127
+ tenant_id varchar(21) not null
128
+ references tenants (id) on update cascade on delete cascade,
129
+ organization_id varchar(21) not null
130
+ references organizations (id) on update cascade on delete cascade,
131
+ user_id varchar(21) not null
132
+ references users (id) on update cascade on delete cascade,
133
+ primary key (tenant_id, organization_id, user_id)
134
+ );
135
+ `);
136
+ await enableRls(pool, database, 'organization_user_relations');
137
+ },
138
+ down: async (pool) => {
139
+ await pool.query(sql`
140
+ drop table organization_role_scope_relations;
141
+ drop table organization_role_user_relations;
142
+ drop table organization_scopes;
143
+ drop table organization_roles;
144
+ drop table organization_user_relations;
145
+ drop table organizations;
146
+ `);
147
+ },
148
+ };
149
+
150
+ export default alteration;
@@ -0,0 +1,66 @@
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 alteration: AlterationScript = {
16
+ up: async (pool) => {
17
+ const database = await getDatabaseName(pool);
18
+ const baseRoleId = getId(`logto_tenant_${database}`);
19
+
20
+ await pool.query(sql`
21
+ create table sso_connectors (
22
+ tenant_id varchar(21) not null
23
+ references tenants (id) on update cascade on delete cascade,
24
+ id varchar(128) not null,
25
+ provider_name varchar(128) not null,
26
+ connector_name varchar(128) not null,
27
+ config jsonb not null default '{}'::jsonb,
28
+ domains jsonb not null default '[]'::jsonb,
29
+ branding jsonb not null default '{}'::jsonb,
30
+ sync_profile boolean not null default FALSE,
31
+ sso_only boolean not null default FALSE,
32
+ created_at timestamptz not null default(now()),
33
+ primary key (id)
34
+ );
35
+
36
+ create index sso_connectors__id
37
+ on sso_connectors (tenant_id, id);
38
+
39
+ create index sso_connectors__id__provider_name
40
+ on sso_connectors (tenant_id, id, provider_name);
41
+
42
+ create trigger set_tenant_id before insert on sso_connectors
43
+ for each row execute procedure set_tenant_id();
44
+
45
+ alter table sso_connectors enable row level security;
46
+
47
+ create policy sso_connectors_tenant_id on sso_connectors
48
+ as restrictive
49
+ using (tenant_id = (select id from tenants where db_user = current_user));
50
+
51
+ create policy sso_connectors_modification on sso_connectors
52
+ using (true);
53
+
54
+ grant select, insert, update, delete on sso_connectors to ${baseRoleId};
55
+ `);
56
+ },
57
+ down: async (pool) => {
58
+ await pool.query(sql`
59
+ drop policy sso_connectors_modification on sso_connectors;
60
+ drop policy sso_connectors_tenant_id on sso_connectors;
61
+ drop table sso_connectors;
62
+ `);
63
+ },
64
+ };
65
+
66
+ export default alteration;
@@ -0,0 +1,75 @@
1
+ import type { DatabaseTransactionConnection } from 'slonik';
2
+ import { sql } from 'slonik';
3
+
4
+ import type { AlterationScript } from '../lib/types/alteration.js';
5
+
6
+ const adminConsoleConfigKey = 'adminConsole';
7
+
8
+ type OldAdminConsoleData = {
9
+ signInExperienceCustomized: boolean;
10
+ } & Record<string, unknown>;
11
+
12
+ type OldLogtoAdminConsoleConfig = {
13
+ tenantId: string;
14
+ value: OldAdminConsoleData;
15
+ };
16
+
17
+ type NewAdminConsoleData = {
18
+ signInExperienceCustomized: boolean;
19
+ organizationCreated: boolean;
20
+ } & Record<string, unknown>;
21
+
22
+ type NewLogtoAdminConsoleConfig = {
23
+ tenantId: string;
24
+ value: NewAdminConsoleData;
25
+ };
26
+
27
+ const alterAdminConsoleData = async (
28
+ logtoConfig: OldLogtoAdminConsoleConfig,
29
+ pool: DatabaseTransactionConnection
30
+ ) => {
31
+ const { tenantId, value: oldAdminConsoleConfig } = logtoConfig;
32
+
33
+ const newAdminConsoleData: NewAdminConsoleData = {
34
+ ...oldAdminConsoleConfig,
35
+ organizationCreated: false,
36
+ };
37
+
38
+ await pool.query(
39
+ sql`update logto_configs set value = ${JSON.stringify(
40
+ newAdminConsoleData
41
+ )} where tenant_id = ${tenantId} and key = ${adminConsoleConfigKey}`
42
+ );
43
+ };
44
+
45
+ const rollbackAdminConsoleData = async (
46
+ logtoConfig: NewLogtoAdminConsoleConfig,
47
+ pool: DatabaseTransactionConnection
48
+ ) => {
49
+ const { tenantId, value: newAdminConsoleConfig } = logtoConfig;
50
+
51
+ const { organizationCreated, ...oldAdminConsoleData } = newAdminConsoleConfig;
52
+
53
+ await pool.query(
54
+ sql`update logto_configs set value = ${JSON.stringify(
55
+ oldAdminConsoleData
56
+ )} where tenant_id = ${tenantId} and key = ${adminConsoleConfigKey}`
57
+ );
58
+ };
59
+
60
+ const alteration: AlterationScript = {
61
+ up: async (pool) => {
62
+ const rows = await pool.many<OldLogtoAdminConsoleConfig>(
63
+ sql`select * from logto_configs where key = ${adminConsoleConfigKey}`
64
+ );
65
+ await Promise.all(rows.map(async (row) => alterAdminConsoleData(row, pool)));
66
+ },
67
+ down: async (pool) => {
68
+ const rows = await pool.many<NewLogtoAdminConsoleConfig>(
69
+ sql`select * from logto_configs where key = ${adminConsoleConfigKey}`
70
+ );
71
+ await Promise.all(rows.map(async (row) => rollbackAdminConsoleData(row, pool)));
72
+ },
73
+ };
74
+
75
+ 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 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
+ /** The alteration script for adding `sso_identities` column to the users table. */
16
+ const alteration: AlterationScript = {
17
+ up: async (pool) => {
18
+ const database = await getDatabaseName(pool);
19
+ const baseRoleId = getId(`logto_tenant_${database}`);
20
+
21
+ await pool.query(sql`
22
+ create table user_sso_identities (
23
+ tenant_id varchar(21) not null
24
+ references tenants (id) on update cascade on delete cascade,
25
+ id varchar(21) not null,
26
+ user_id varchar(12) not null
27
+ references users (id) on update cascade on delete cascade,
28
+ issuer varchar(256) not null,
29
+ identity_id varchar(128) not null,
30
+ detail jsonb not null default '{}'::jsonb,
31
+ created_at timestamp not null default(now()),
32
+ primary key (id),
33
+ constraint user_sso_identities__issuer__identity_id
34
+ unique (tenant_id, issuer, identity_id)
35
+ );
36
+
37
+ create trigger set_tenant_id before insert on user_sso_identities
38
+ for each row execute procedure set_tenant_id();
39
+
40
+ alter table user_sso_identities enable row level security;
41
+
42
+ create policy user_sso_identities_tenant_id on user_sso_identities
43
+ as restrictive
44
+ using (tenant_id = (select id from tenants where db_user = current_user));
45
+
46
+ create policy user_sso_identities_modification on user_sso_identities
47
+ using (true);
48
+
49
+ grant select, insert, update, delete on user_sso_identities to ${baseRoleId};
50
+ `);
51
+ },
52
+ down: async (pool) => {
53
+ await pool.query(sql`
54
+ drop policy user_sso_identities_modification on user_sso_identities;
55
+ drop policy user_sso_identities_tenant_id on user_sso_identities;
56
+ drop table user_sso_identities;
57
+ `);
58
+ },
59
+ };
60
+
61
+ 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 users
9
+ add column if not exists logto_config jsonb not null default '{}'::jsonb;
10
+ `);
11
+ },
12
+ down: async (pool) => {
13
+ await pool.query(sql`
14
+ alter table users
15
+ drop column logto_config;
16
+ `);
17
+ },
18
+ };
19
+
20
+ export default alteration;
@@ -0,0 +1,3 @@
1
+ import type { AlterationScript } from '../lib/types/alteration.js';
2
+ declare const alteration: AlterationScript;
3
+ export default alteration;
@@ -0,0 +1,50 @@
1
+ import { generateStandardId } from '@logto/shared';
2
+ import { sql } from 'slonik';
3
+ const targetConfigKeys = ['oidc.cookieKeys', 'oidc.privateKeys'];
4
+ /**
5
+ * Alternate string array private signing keys to JSON array
6
+ * "oidc.cookieKeys": string[] -> PrivateKey[]
7
+ * "oidc.privateKeys": string[] -> PrivateKey[]
8
+ * @param configKey oidc.cookieKeys | oidc.privateKeys
9
+ * @param logtoConfig existing private key data for a specific tenant
10
+ * @param pool postgres database connection pool
11
+ */
12
+ const alterPrivateKeysInLogtoConfig = async (configKey, logtoConfig, pool) => {
13
+ const { tenantId, value: oldPrivateKey } = logtoConfig;
14
+ // Use tenant creation time as `createdAt` timestamp for new private keys
15
+ const tenantData = await pool.maybeOne(sql `select * from tenants where id = ${tenantId}`);
16
+ const newPrivateKeyData = oldPrivateKey.map((key) => ({
17
+ id: generateStandardId(),
18
+ value: key,
19
+ createdAt: Math.floor((tenantData?.createdAt ?? Date.now()) / 1000),
20
+ }));
21
+ await pool.query(sql `update logto_configs set value = ${JSON.stringify(newPrivateKeyData)} where tenant_id = ${tenantId} and key = ${configKey}`);
22
+ };
23
+ /**
24
+ * Rollback JSON array private signing keys to string array
25
+ * "oidc.cookieKeys": PrivateKey[] -> string[]
26
+ * "oidc.privateKeys": PrivateKey[] -> string[]
27
+ * @param configKey oidc.cookieKeys | oidc.privateKeys
28
+ * @param logtoConfig new private key data for a specific tenant
29
+ * @param pool postgres database connection pool
30
+ */
31
+ const rollbackPrivateKeysInLogtoConfig = async (configKey, logtoConfig, pool) => {
32
+ const { tenantId, value: newPrivateKeyData } = logtoConfig;
33
+ const oldPrivateKeys = newPrivateKeyData.map(({ value }) => value);
34
+ await pool.query(sql `update logto_configs set value = ${JSON.stringify(oldPrivateKeys)} where tenant_id = ${tenantId} and key = ${configKey}`);
35
+ };
36
+ const alteration = {
37
+ up: async (pool) => {
38
+ await Promise.all(targetConfigKeys.map(async (configKey) => {
39
+ const rows = await pool.many(sql `select * from logto_configs where key = ${configKey}`);
40
+ await Promise.all(rows.map(async (row) => alterPrivateKeysInLogtoConfig(configKey, row, pool)));
41
+ }));
42
+ },
43
+ down: async (pool) => {
44
+ await Promise.all(targetConfigKeys.map(async (configKey) => {
45
+ const rows = await pool.many(sql `select * from logto_configs where key = ${configKey}`);
46
+ await Promise.all(rows.map(async (row) => rollbackPrivateKeysInLogtoConfig(configKey, row, pool)));
47
+ }));
48
+ },
49
+ };
50
+ export default alteration;
@@ -0,0 +1,3 @@
1
+ import type { AlterationScript } from '../lib/types/alteration.js';
2
+ declare const alteration: AlterationScript;
3
+ export default alteration;
@@ -0,0 +1,136 @@
1
+ import { sql } from 'slonik';
2
+ const getDatabaseName = async (pool) => {
3
+ const { currentDatabase } = await pool.one(sql `
4
+ select current_database();
5
+ `);
6
+ return currentDatabase.replaceAll('-', '_');
7
+ };
8
+ const enableRls = async (pool, database, table) => {
9
+ const baseRoleId = sql.identifier([`logto_tenant_${database}`]);
10
+ await pool.query(sql `
11
+ create trigger set_tenant_id before insert on ${sql.identifier([table])}
12
+ for each row execute procedure set_tenant_id();
13
+
14
+ alter table ${sql.identifier([table])} enable row level security;
15
+
16
+ create policy ${sql.identifier([`${table}_tenant_id`])} on ${sql.identifier([table])}
17
+ as restrictive
18
+ using (tenant_id = (select id from tenants where db_user = current_user));
19
+
20
+ create policy ${sql.identifier([`${table}_modification`])} on ${sql.identifier([table])}
21
+ using (true);
22
+
23
+ grant select, insert, update, delete on ${sql.identifier([table])} to ${baseRoleId};
24
+ `);
25
+ };
26
+ const alteration = {
27
+ up: async (pool) => {
28
+ const database = await getDatabaseName(pool);
29
+ await pool.query(sql `
30
+ create table organizations (
31
+ tenant_id varchar(21) not null
32
+ references tenants (id) on update cascade on delete cascade,
33
+ /** The globally unique identifier of the organization. */
34
+ id varchar(21) not null,
35
+ /** The organization's name for display. */
36
+ name varchar(128) not null,
37
+ /** A brief description of the organization. */
38
+ description varchar(256),
39
+ /** When the organization was created. */
40
+ created_at timestamptz not null default(now()),
41
+ primary key (id)
42
+ );
43
+
44
+ create index organizations__id
45
+ on organizations (tenant_id, id);
46
+ `);
47
+ await enableRls(pool, database, 'organizations');
48
+ await pool.query(sql `
49
+ create table organization_roles (
50
+ tenant_id varchar(21) not null
51
+ references tenants (id) on update cascade on delete cascade,
52
+ /** The globally unique identifier of the organization role. */
53
+ id varchar(21) not null,
54
+ /** The organization role's name, unique within the organization template. */
55
+ name varchar(128) not null,
56
+ /** A brief description of the organization role. */
57
+ description varchar(256),
58
+ primary key (id),
59
+ constraint organization_roles__name
60
+ unique (tenant_id, name)
61
+ );
62
+
63
+ create index organization_roles__id
64
+ on organization_roles (tenant_id, id);
65
+ `);
66
+ await enableRls(pool, database, 'organization_roles');
67
+ await pool.query(sql `
68
+ create table organization_scopes (
69
+ tenant_id varchar(21) not null
70
+ references tenants (id) on update cascade on delete cascade,
71
+ /** The globally unique identifier of the organization scope. */
72
+ id varchar(21) not null,
73
+ /** The organization scope's name, unique within the organization template. */
74
+ name varchar(128) not null,
75
+ /** A brief description of the organization scope. */
76
+ description varchar(256),
77
+ primary key (id),
78
+ constraint organization_scopes__name
79
+ unique (tenant_id, name)
80
+ );
81
+
82
+ create index organization_scopes__id
83
+ on organization_scopes (tenant_id, id);
84
+ `);
85
+ await enableRls(pool, database, 'organization_scopes');
86
+ await pool.query(sql `
87
+ create table organization_role_user_relations (
88
+ tenant_id varchar(21) not null
89
+ references tenants (id) on update cascade on delete cascade,
90
+ organization_id varchar(21) not null
91
+ references organizations (id) on update cascade on delete cascade,
92
+ organization_role_id varchar(21) not null
93
+ references organization_roles (id) on update cascade on delete cascade,
94
+ user_id varchar(21) not null
95
+ references users (id) on update cascade on delete cascade,
96
+ primary key (tenant_id, organization_id, organization_role_id, user_id)
97
+ );
98
+ `);
99
+ await enableRls(pool, database, 'organization_role_user_relations');
100
+ await pool.query(sql `
101
+ create table organization_role_scope_relations (
102
+ tenant_id varchar(21) not null
103
+ references tenants (id) on update cascade on delete cascade,
104
+ organization_role_id varchar(21) not null
105
+ references organization_roles (id) on update cascade on delete cascade,
106
+ organization_scope_id varchar(21) not null
107
+ references organization_scopes (id) on update cascade on delete cascade,
108
+ primary key (tenant_id, organization_role_id, organization_scope_id)
109
+ );
110
+ `);
111
+ await enableRls(pool, database, 'organization_role_scope_relations');
112
+ await pool.query(sql `
113
+ create table organization_user_relations (
114
+ tenant_id varchar(21) not null
115
+ references tenants (id) on update cascade on delete cascade,
116
+ organization_id varchar(21) not null
117
+ references organizations (id) on update cascade on delete cascade,
118
+ user_id varchar(21) not null
119
+ references users (id) on update cascade on delete cascade,
120
+ primary key (tenant_id, organization_id, user_id)
121
+ );
122
+ `);
123
+ await enableRls(pool, database, 'organization_user_relations');
124
+ },
125
+ down: async (pool) => {
126
+ await pool.query(sql `
127
+ drop table organization_role_scope_relations;
128
+ drop table organization_role_user_relations;
129
+ drop table organization_scopes;
130
+ drop table organization_roles;
131
+ drop table organization_user_relations;
132
+ drop table organizations;
133
+ `);
134
+ },
135
+ };
136
+ export default alteration;
@@ -0,0 +1,3 @@
1
+ import type { AlterationScript } from '../lib/types/alteration.js';
2
+ declare const alteration: AlterationScript;
3
+ export default alteration;