@logto/schemas 1.16.0 → 1.17.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.
- package/alterations/1.17.0-1715826336-add-default-user-role-config.ts +18 -0
- package/alterations/1.17.0-1715829731-rename-data-hook-schema-update-event.ts +120 -0
- package/alterations/1.17.0-1716278409-remove-internal-role-database-policies.ts +37 -0
- package/alterations/1.17.0-1716291265-create-pre-configured-m-api-role.ts +92 -0
- package/alterations/1.17.0-1717148078-remove-service-log-reference.ts +19 -0
- package/alterations/utils/1716643968-id-generation.ts +46 -0
- package/alterations-js/1.17.0-1715826336-add-default-user-role-config.d.ts +3 -0
- package/alterations-js/1.17.0-1715826336-add-default-user-role-config.js +14 -0
- package/alterations-js/1.17.0-1715829731-rename-data-hook-schema-update-event.d.ts +3 -0
- package/alterations-js/1.17.0-1715829731-rename-data-hook-schema-update-event.js +96 -0
- package/alterations-js/1.17.0-1716278409-remove-internal-role-database-policies.d.ts +3 -0
- package/alterations-js/1.17.0-1716278409-remove-internal-role-database-policies.js +33 -0
- package/alterations-js/1.17.0-1716291265-create-pre-configured-m-api-role.d.ts +7 -0
- package/alterations-js/1.17.0-1716291265-create-pre-configured-m-api-role.js +77 -0
- package/alterations-js/1.17.0-1717148078-remove-service-log-reference.d.ts +3 -0
- package/alterations-js/1.17.0-1717148078-remove-service-log-reference.js +15 -0
- package/alterations-js/utils/1716643968-id-generation.d.ts +19 -0
- package/alterations-js/utils/1716643968-id-generation.js +26 -0
- package/lib/db-entries/role.d.ts +5 -1
- package/lib/db-entries/role.js +4 -0
- package/lib/foundations/jsonb-types/hooks.d.ts +18 -16
- package/lib/foundations/jsonb-types/hooks.js +20 -17
- package/lib/models/tenants.d.ts +0 -21
- package/lib/models/tenants.js +0 -3
- package/lib/seeds/cloud-api.js +1 -0
- package/lib/seeds/management-api.d.ts +4 -0
- package/lib/seeds/management-api.js +10 -0
- package/lib/types/hook.d.ts +67 -6
- package/lib/types/mapi-proxy.js +1 -0
- package/lib/types/user.d.ts +5 -1
- package/lib/types/user.js +5 -1
- package/lib/utils/role.d.ts +2 -0
- package/lib/utils/role.js +2 -0
- package/package.json +6 -5
- package/tables/_after_all.sql +0 -27
- package/tables/roles.sql +2 -0
- package/tables/service_logs.sql +1 -2
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
|
|
3
|
+
import type { AlterationScript } from '../lib/types/alteration.js';
|
|
4
|
+
|
|
5
|
+
const alteration: AlterationScript = {
|
|
6
|
+
up: async (pool) => {
|
|
7
|
+
await pool.query(sql`
|
|
8
|
+
alter table roles add column is_default boolean not null default false;
|
|
9
|
+
`);
|
|
10
|
+
},
|
|
11
|
+
down: async (pool) => {
|
|
12
|
+
await pool.query(sql`
|
|
13
|
+
alter table roles drop column is_default;
|
|
14
|
+
`);
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default alteration;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
|
|
3
|
+
import type { AlterationScript } from '../lib/types/alteration.js';
|
|
4
|
+
|
|
5
|
+
enum DataHookSchema {
|
|
6
|
+
User = 'User',
|
|
7
|
+
Role = 'Role',
|
|
8
|
+
Scope = 'Scope',
|
|
9
|
+
Organization = 'Organization',
|
|
10
|
+
OrganizationRole = 'OrganizationRole',
|
|
11
|
+
OrganizationScope = 'OrganizationScope',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type OldSchemaUpdateEvent = `${DataHookSchema}.${'Updated'}`;
|
|
15
|
+
type NewSchemaUpdateEvent = `${DataHookSchema}.Data.Updated`;
|
|
16
|
+
|
|
17
|
+
const oldSchemaUpdateEvents = Object.freeze([
|
|
18
|
+
'User.Updated',
|
|
19
|
+
'Role.Updated',
|
|
20
|
+
'Scope.Updated',
|
|
21
|
+
'Organization.Updated',
|
|
22
|
+
'OrganizationRole.Updated',
|
|
23
|
+
'OrganizationScope.Updated',
|
|
24
|
+
] satisfies OldSchemaUpdateEvent[]);
|
|
25
|
+
|
|
26
|
+
const newSchemaUpdateEvents = Object.freeze([
|
|
27
|
+
'User.Data.Updated',
|
|
28
|
+
'Role.Data.Updated',
|
|
29
|
+
'Scope.Data.Updated',
|
|
30
|
+
'Organization.Data.Updated',
|
|
31
|
+
'OrganizationRole.Data.Updated',
|
|
32
|
+
'OrganizationScope.Data.Updated',
|
|
33
|
+
] as const satisfies NewSchemaUpdateEvent[]);
|
|
34
|
+
|
|
35
|
+
const updateMap: { [key in OldSchemaUpdateEvent]: NewSchemaUpdateEvent } = {
|
|
36
|
+
'User.Updated': 'User.Data.Updated',
|
|
37
|
+
'Role.Updated': 'Role.Data.Updated',
|
|
38
|
+
'Scope.Updated': 'Scope.Data.Updated',
|
|
39
|
+
'Organization.Updated': 'Organization.Data.Updated',
|
|
40
|
+
'OrganizationRole.Updated': 'OrganizationRole.Data.Updated',
|
|
41
|
+
'OrganizationScope.Updated': 'OrganizationScope.Data.Updated',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const reverseMap: { [key in NewSchemaUpdateEvent]: OldSchemaUpdateEvent } = {
|
|
45
|
+
'User.Data.Updated': 'User.Updated',
|
|
46
|
+
'Role.Data.Updated': 'Role.Updated',
|
|
47
|
+
'Scope.Data.Updated': 'Scope.Updated',
|
|
48
|
+
'Organization.Data.Updated': 'Organization.Updated',
|
|
49
|
+
'OrganizationRole.Data.Updated': 'OrganizationRole.Updated',
|
|
50
|
+
'OrganizationScope.Data.Updated': 'OrganizationScope.Updated',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// This alteration script filters all the hook's events jsonb column to replace all the old schema update events with the new schema update events.
|
|
54
|
+
|
|
55
|
+
const isOldSchemaUpdateEvent = (event: string): event is OldSchemaUpdateEvent =>
|
|
56
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
57
|
+
oldSchemaUpdateEvents.includes(event as OldSchemaUpdateEvent);
|
|
58
|
+
|
|
59
|
+
const isNewSchemaUpdateEvent = (event: string): event is NewSchemaUpdateEvent =>
|
|
60
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
61
|
+
newSchemaUpdateEvents.includes(event as NewSchemaUpdateEvent);
|
|
62
|
+
|
|
63
|
+
const alteration: AlterationScript = {
|
|
64
|
+
up: async (pool) => {
|
|
65
|
+
const { rows: hooks } = await pool.query<{ id: string; events: string[] }>(sql`
|
|
66
|
+
select id, events
|
|
67
|
+
from hooks
|
|
68
|
+
`);
|
|
69
|
+
|
|
70
|
+
const hooksToBeUpdate = hooks.filter(({ events }) => {
|
|
71
|
+
return oldSchemaUpdateEvents.some((oldEvent) => events.includes(oldEvent));
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
await Promise.all(
|
|
75
|
+
hooksToBeUpdate.map(async ({ id, events }) => {
|
|
76
|
+
const updateEvents = events.reduce<string[]>((accumulator, event) => {
|
|
77
|
+
if (isOldSchemaUpdateEvent(event)) {
|
|
78
|
+
return [...accumulator, updateMap[event]];
|
|
79
|
+
}
|
|
80
|
+
return [...accumulator, event];
|
|
81
|
+
}, []);
|
|
82
|
+
|
|
83
|
+
await pool.query(sql`
|
|
84
|
+
update hooks
|
|
85
|
+
set events = ${JSON.stringify(updateEvents)}
|
|
86
|
+
where id = ${id};
|
|
87
|
+
`);
|
|
88
|
+
})
|
|
89
|
+
);
|
|
90
|
+
},
|
|
91
|
+
down: async (pool) => {
|
|
92
|
+
const { rows: hooks } = await pool.query<{ id: string; events: string[] }>(sql`
|
|
93
|
+
select id, events
|
|
94
|
+
from hooks
|
|
95
|
+
`);
|
|
96
|
+
|
|
97
|
+
const hooksToBeUpdate = hooks.filter(({ events }) => {
|
|
98
|
+
return newSchemaUpdateEvents.some((newEvent) => events.includes(newEvent));
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
await Promise.all(
|
|
102
|
+
hooksToBeUpdate.map(async ({ id, events }) => {
|
|
103
|
+
const updateEvents = events.reduce<string[]>((accumulator, event) => {
|
|
104
|
+
if (isNewSchemaUpdateEvent(event)) {
|
|
105
|
+
return [...accumulator, reverseMap[event]];
|
|
106
|
+
}
|
|
107
|
+
return [...accumulator, event];
|
|
108
|
+
}, []);
|
|
109
|
+
|
|
110
|
+
await pool.query(sql`
|
|
111
|
+
update hooks
|
|
112
|
+
set events = ${JSON.stringify(updateEvents)}
|
|
113
|
+
where id = ${id};
|
|
114
|
+
`);
|
|
115
|
+
})
|
|
116
|
+
);
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
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
|
+
up: async (pool) => {
|
|
7
|
+
await pool.query(sql`
|
|
8
|
+
drop policy if exists roles_select on roles;
|
|
9
|
+
drop policy if exists roles_modification on roles;
|
|
10
|
+
create policy roles_modification on roles using (true);
|
|
11
|
+
|
|
12
|
+
drop policy if exists roles_scopes_select on roles_scopes;
|
|
13
|
+
drop policy if exists roles_scopes_modification on roles_scopes;
|
|
14
|
+
create policy roles_scopes_modification on roles_scopes using (true);
|
|
15
|
+
`);
|
|
16
|
+
},
|
|
17
|
+
down: async (pool) => {
|
|
18
|
+
await pool.query(sql`
|
|
19
|
+
create policy roles_select on roles
|
|
20
|
+
for select using (true);
|
|
21
|
+
|
|
22
|
+
drop policy roles_modification on roles;
|
|
23
|
+
create policy roles_modification on roles
|
|
24
|
+
using (not starts_with(name, '#internal:'));
|
|
25
|
+
|
|
26
|
+
-- Restrict role - scope modification
|
|
27
|
+
create policy roles_scopes_select on roles_scopes
|
|
28
|
+
for select using (true);
|
|
29
|
+
|
|
30
|
+
drop policy roles_scopes_modification on roles_scopes;
|
|
31
|
+
create policy roles_scopes_modification on roles_scopes
|
|
32
|
+
using (not starts_with((select roles.name from roles where roles.id = role_id), '#internal:'));
|
|
33
|
+
`);
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default alteration;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { yes } from '@silverhand/essentials';
|
|
2
|
+
import { sql } from '@silverhand/slonik';
|
|
3
|
+
|
|
4
|
+
import type { AlterationScript } from '../lib/types/alteration.js';
|
|
5
|
+
|
|
6
|
+
import { generateStandardId } from './utils/1716643968-id-generation.js';
|
|
7
|
+
|
|
8
|
+
const isCi = yes(process.env.CI);
|
|
9
|
+
|
|
10
|
+
const defaultTenantId = 'default';
|
|
11
|
+
const defaultTenantManagementApiIndicator = `https://${defaultTenantId}.logto.app/api`;
|
|
12
|
+
const roleName = 'Logto Management API access';
|
|
13
|
+
const roleDescription = 'This default role grants access to the Logto management API.';
|
|
14
|
+
enum RoleType {
|
|
15
|
+
MachineToMachine = 'MachineToMachine',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
enum PredefinedScope {
|
|
19
|
+
All = 'all',
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* This script is to create a pre-configured Management API M2M role for new users.
|
|
24
|
+
* This script is **only for CI**, since we won't create this role for existing users, so this script is not applicable for existing db data.
|
|
25
|
+
*/
|
|
26
|
+
const alteration: AlterationScript = {
|
|
27
|
+
up: async (pool) => {
|
|
28
|
+
if (!isCi) {
|
|
29
|
+
console.info(
|
|
30
|
+
"Skipping the alteration script `next-1716291265-create-pre-configured-m-api-role.ts` since it's should not be applied to existing db data."
|
|
31
|
+
);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Only affect the `default` tenant, since this is the only tenant in the OSS version and the initial tenant in the cloud version.
|
|
37
|
+
* So we only need to care about this role for the `default` tenant.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
const roleId = generateStandardId();
|
|
41
|
+
|
|
42
|
+
await pool.query(sql`
|
|
43
|
+
insert into roles (id, tenant_id, name, description, type)
|
|
44
|
+
values (
|
|
45
|
+
${roleId},
|
|
46
|
+
${defaultTenantId},
|
|
47
|
+
${roleName},
|
|
48
|
+
${roleDescription},
|
|
49
|
+
${RoleType.MachineToMachine}
|
|
50
|
+
);
|
|
51
|
+
`);
|
|
52
|
+
|
|
53
|
+
// Assign Logto Management API permission `all` to the Logto Management API M2M role
|
|
54
|
+
await pool.query(sql`
|
|
55
|
+
insert into roles_scopes (id, role_id, scope_id, tenant_id)
|
|
56
|
+
values (
|
|
57
|
+
${generateStandardId()},
|
|
58
|
+
${roleId},
|
|
59
|
+
(
|
|
60
|
+
select scopes.id
|
|
61
|
+
from scopes
|
|
62
|
+
join resources on
|
|
63
|
+
scopes.tenant_id = resources.tenant_id and
|
|
64
|
+
scopes.resource_id = resources.id
|
|
65
|
+
where resources.indicator = ${defaultTenantManagementApiIndicator}
|
|
66
|
+
and scopes.name = ${PredefinedScope.All}
|
|
67
|
+
and scopes.tenant_id = ${defaultTenantId}
|
|
68
|
+
),
|
|
69
|
+
${defaultTenantId}
|
|
70
|
+
)
|
|
71
|
+
`);
|
|
72
|
+
},
|
|
73
|
+
down: async (pool) => {
|
|
74
|
+
if (!isCi) {
|
|
75
|
+
console.info(
|
|
76
|
+
"Skipping the down script `next-1716291265-create-pre-configured-m-api-role.ts` since it's should not be applied to production db."
|
|
77
|
+
);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Delete the created role
|
|
82
|
+
await pool.query(sql`
|
|
83
|
+
delete from roles
|
|
84
|
+
where tenant_id = ${defaultTenantId}
|
|
85
|
+
and name = ${roleName}
|
|
86
|
+
and description = ${roleDescription}
|
|
87
|
+
and type = ${RoleType.MachineToMachine}
|
|
88
|
+
`);
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
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 service_logs drop constraint service_logs_tenant_id_fkey;
|
|
9
|
+
`);
|
|
10
|
+
},
|
|
11
|
+
down: async (pool) => {
|
|
12
|
+
await pool.query(sql`
|
|
13
|
+
alter table service_logs add constraint service_logs_tenant_id_fkey
|
|
14
|
+
foreign key (tenant_id) references tenants(id) on update cascade on delete cascade;
|
|
15
|
+
`);
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default alteration;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is forked from `@logto/shared` 3.1.0 to avoid alteration scripts to depend on outer packages.
|
|
3
|
+
*/
|
|
4
|
+
import { customAlphabet } from 'nanoid';
|
|
5
|
+
|
|
6
|
+
const lowercaseAlphabet = '0123456789abcdefghijklmnopqrstuvwxyz';
|
|
7
|
+
const alphabet = `${lowercaseAlphabet}ABCDEFGHIJKLMNOPQRSTUVWXYZ` as const;
|
|
8
|
+
|
|
9
|
+
type BuildIdGenerator = {
|
|
10
|
+
/**
|
|
11
|
+
* Build a nanoid generator function uses numbers (0-9), lowercase letters (a-z), and uppercase letters (A-Z) as the alphabet.
|
|
12
|
+
* @param size The default id length for the generator.
|
|
13
|
+
*/
|
|
14
|
+
(size: number): ReturnType<typeof customAlphabet>;
|
|
15
|
+
/**
|
|
16
|
+
* Build a nanoid generator function uses numbers (0-9) and lowercase letters (a-z) as the alphabet.
|
|
17
|
+
* @param size The default id length for the generator.
|
|
18
|
+
*/
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/unified-signatures
|
|
20
|
+
(size: number, includingUppercase: false): ReturnType<typeof customAlphabet>;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const buildIdGenerator: BuildIdGenerator = (size: number, includingUppercase = true) =>
|
|
24
|
+
customAlphabet(includingUppercase ? alphabet : lowercaseAlphabet, size);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Generate a standard id with 21 characters, including lowercase letters and numbers.
|
|
28
|
+
*
|
|
29
|
+
* @see {@link lowercaseAlphabet}
|
|
30
|
+
*/
|
|
31
|
+
export const generateStandardId = buildIdGenerator(21, false);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Generate a standard short id with 12 characters, including lowercase letters and numbers.
|
|
35
|
+
*
|
|
36
|
+
* @see {@link lowercaseAlphabet}
|
|
37
|
+
*/
|
|
38
|
+
export const generateStandardShortId = buildIdGenerator(12, false);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Generate a standard secret with 32 characters, including uppercase letters, lowercase
|
|
42
|
+
* letters, and numbers.
|
|
43
|
+
*
|
|
44
|
+
* @see {@link alphabet}
|
|
45
|
+
*/
|
|
46
|
+
export const generateStandardSecret = buildIdGenerator(32);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
const alteration = {
|
|
3
|
+
up: async (pool) => {
|
|
4
|
+
await pool.query(sql `
|
|
5
|
+
alter table roles add column is_default boolean not null default false;
|
|
6
|
+
`);
|
|
7
|
+
},
|
|
8
|
+
down: async (pool) => {
|
|
9
|
+
await pool.query(sql `
|
|
10
|
+
alter table roles drop column is_default;
|
|
11
|
+
`);
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
export default alteration;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
var DataHookSchema;
|
|
3
|
+
(function (DataHookSchema) {
|
|
4
|
+
DataHookSchema["User"] = "User";
|
|
5
|
+
DataHookSchema["Role"] = "Role";
|
|
6
|
+
DataHookSchema["Scope"] = "Scope";
|
|
7
|
+
DataHookSchema["Organization"] = "Organization";
|
|
8
|
+
DataHookSchema["OrganizationRole"] = "OrganizationRole";
|
|
9
|
+
DataHookSchema["OrganizationScope"] = "OrganizationScope";
|
|
10
|
+
})(DataHookSchema || (DataHookSchema = {}));
|
|
11
|
+
const oldSchemaUpdateEvents = Object.freeze([
|
|
12
|
+
'User.Updated',
|
|
13
|
+
'Role.Updated',
|
|
14
|
+
'Scope.Updated',
|
|
15
|
+
'Organization.Updated',
|
|
16
|
+
'OrganizationRole.Updated',
|
|
17
|
+
'OrganizationScope.Updated',
|
|
18
|
+
]);
|
|
19
|
+
const newSchemaUpdateEvents = Object.freeze([
|
|
20
|
+
'User.Data.Updated',
|
|
21
|
+
'Role.Data.Updated',
|
|
22
|
+
'Scope.Data.Updated',
|
|
23
|
+
'Organization.Data.Updated',
|
|
24
|
+
'OrganizationRole.Data.Updated',
|
|
25
|
+
'OrganizationScope.Data.Updated',
|
|
26
|
+
]);
|
|
27
|
+
const updateMap = {
|
|
28
|
+
'User.Updated': 'User.Data.Updated',
|
|
29
|
+
'Role.Updated': 'Role.Data.Updated',
|
|
30
|
+
'Scope.Updated': 'Scope.Data.Updated',
|
|
31
|
+
'Organization.Updated': 'Organization.Data.Updated',
|
|
32
|
+
'OrganizationRole.Updated': 'OrganizationRole.Data.Updated',
|
|
33
|
+
'OrganizationScope.Updated': 'OrganizationScope.Data.Updated',
|
|
34
|
+
};
|
|
35
|
+
const reverseMap = {
|
|
36
|
+
'User.Data.Updated': 'User.Updated',
|
|
37
|
+
'Role.Data.Updated': 'Role.Updated',
|
|
38
|
+
'Scope.Data.Updated': 'Scope.Updated',
|
|
39
|
+
'Organization.Data.Updated': 'Organization.Updated',
|
|
40
|
+
'OrganizationRole.Data.Updated': 'OrganizationRole.Updated',
|
|
41
|
+
'OrganizationScope.Data.Updated': 'OrganizationScope.Updated',
|
|
42
|
+
};
|
|
43
|
+
// This alteration script filters all the hook's events jsonb column to replace all the old schema update events with the new schema update events.
|
|
44
|
+
const isOldSchemaUpdateEvent = (event) =>
|
|
45
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
46
|
+
oldSchemaUpdateEvents.includes(event);
|
|
47
|
+
const isNewSchemaUpdateEvent = (event) =>
|
|
48
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
49
|
+
newSchemaUpdateEvents.includes(event);
|
|
50
|
+
const alteration = {
|
|
51
|
+
up: async (pool) => {
|
|
52
|
+
const { rows: hooks } = await pool.query(sql `
|
|
53
|
+
select id, events
|
|
54
|
+
from hooks
|
|
55
|
+
`);
|
|
56
|
+
const hooksToBeUpdate = hooks.filter(({ events }) => {
|
|
57
|
+
return oldSchemaUpdateEvents.some((oldEvent) => events.includes(oldEvent));
|
|
58
|
+
});
|
|
59
|
+
await Promise.all(hooksToBeUpdate.map(async ({ id, events }) => {
|
|
60
|
+
const updateEvents = events.reduce((accumulator, event) => {
|
|
61
|
+
if (isOldSchemaUpdateEvent(event)) {
|
|
62
|
+
return [...accumulator, updateMap[event]];
|
|
63
|
+
}
|
|
64
|
+
return [...accumulator, event];
|
|
65
|
+
}, []);
|
|
66
|
+
await pool.query(sql `
|
|
67
|
+
update hooks
|
|
68
|
+
set events = ${JSON.stringify(updateEvents)}
|
|
69
|
+
where id = ${id};
|
|
70
|
+
`);
|
|
71
|
+
}));
|
|
72
|
+
},
|
|
73
|
+
down: async (pool) => {
|
|
74
|
+
const { rows: hooks } = await pool.query(sql `
|
|
75
|
+
select id, events
|
|
76
|
+
from hooks
|
|
77
|
+
`);
|
|
78
|
+
const hooksToBeUpdate = hooks.filter(({ events }) => {
|
|
79
|
+
return newSchemaUpdateEvents.some((newEvent) => events.includes(newEvent));
|
|
80
|
+
});
|
|
81
|
+
await Promise.all(hooksToBeUpdate.map(async ({ id, events }) => {
|
|
82
|
+
const updateEvents = events.reduce((accumulator, event) => {
|
|
83
|
+
if (isNewSchemaUpdateEvent(event)) {
|
|
84
|
+
return [...accumulator, reverseMap[event]];
|
|
85
|
+
}
|
|
86
|
+
return [...accumulator, event];
|
|
87
|
+
}, []);
|
|
88
|
+
await pool.query(sql `
|
|
89
|
+
update hooks
|
|
90
|
+
set events = ${JSON.stringify(updateEvents)}
|
|
91
|
+
where id = ${id};
|
|
92
|
+
`);
|
|
93
|
+
}));
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
export default alteration;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
const alteration = {
|
|
3
|
+
up: async (pool) => {
|
|
4
|
+
await pool.query(sql `
|
|
5
|
+
drop policy if exists roles_select on roles;
|
|
6
|
+
drop policy if exists roles_modification on roles;
|
|
7
|
+
create policy roles_modification on roles using (true);
|
|
8
|
+
|
|
9
|
+
drop policy if exists roles_scopes_select on roles_scopes;
|
|
10
|
+
drop policy if exists roles_scopes_modification on roles_scopes;
|
|
11
|
+
create policy roles_scopes_modification on roles_scopes using (true);
|
|
12
|
+
`);
|
|
13
|
+
},
|
|
14
|
+
down: async (pool) => {
|
|
15
|
+
await pool.query(sql `
|
|
16
|
+
create policy roles_select on roles
|
|
17
|
+
for select using (true);
|
|
18
|
+
|
|
19
|
+
drop policy roles_modification on roles;
|
|
20
|
+
create policy roles_modification on roles
|
|
21
|
+
using (not starts_with(name, '#internal:'));
|
|
22
|
+
|
|
23
|
+
-- Restrict role - scope modification
|
|
24
|
+
create policy roles_scopes_select on roles_scopes
|
|
25
|
+
for select using (true);
|
|
26
|
+
|
|
27
|
+
drop policy roles_scopes_modification on roles_scopes;
|
|
28
|
+
create policy roles_scopes_modification on roles_scopes
|
|
29
|
+
using (not starts_with((select roles.name from roles where roles.id = role_id), '#internal:'));
|
|
30
|
+
`);
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
export default alteration;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AlterationScript } from '../lib/types/alteration.js';
|
|
2
|
+
/**
|
|
3
|
+
* This script is to create a pre-configured Management API M2M role for new users.
|
|
4
|
+
* This script is **only for CI**, since we won't create this role for existing users, so this script is not applicable for existing db data.
|
|
5
|
+
*/
|
|
6
|
+
declare const alteration: AlterationScript;
|
|
7
|
+
export default alteration;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { yes } from '@silverhand/essentials';
|
|
2
|
+
import { sql } from '@silverhand/slonik';
|
|
3
|
+
import { generateStandardId } from './utils/1716643968-id-generation.js';
|
|
4
|
+
const isCi = yes(process.env.CI);
|
|
5
|
+
const defaultTenantId = 'default';
|
|
6
|
+
const defaultTenantManagementApiIndicator = `https://${defaultTenantId}.logto.app/api`;
|
|
7
|
+
const roleName = 'Logto Management API access';
|
|
8
|
+
const roleDescription = 'This default role grants access to the Logto management API.';
|
|
9
|
+
var RoleType;
|
|
10
|
+
(function (RoleType) {
|
|
11
|
+
RoleType["MachineToMachine"] = "MachineToMachine";
|
|
12
|
+
})(RoleType || (RoleType = {}));
|
|
13
|
+
var PredefinedScope;
|
|
14
|
+
(function (PredefinedScope) {
|
|
15
|
+
PredefinedScope["All"] = "all";
|
|
16
|
+
})(PredefinedScope || (PredefinedScope = {}));
|
|
17
|
+
/**
|
|
18
|
+
* This script is to create a pre-configured Management API M2M role for new users.
|
|
19
|
+
* This script is **only for CI**, since we won't create this role for existing users, so this script is not applicable for existing db data.
|
|
20
|
+
*/
|
|
21
|
+
const alteration = {
|
|
22
|
+
up: async (pool) => {
|
|
23
|
+
if (!isCi) {
|
|
24
|
+
console.info("Skipping the alteration script `next-1716291265-create-pre-configured-m-api-role.ts` since it's should not be applied to existing db data.");
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Only affect the `default` tenant, since this is the only tenant in the OSS version and the initial tenant in the cloud version.
|
|
29
|
+
* So we only need to care about this role for the `default` tenant.
|
|
30
|
+
*/
|
|
31
|
+
const roleId = generateStandardId();
|
|
32
|
+
await pool.query(sql `
|
|
33
|
+
insert into roles (id, tenant_id, name, description, type)
|
|
34
|
+
values (
|
|
35
|
+
${roleId},
|
|
36
|
+
${defaultTenantId},
|
|
37
|
+
${roleName},
|
|
38
|
+
${roleDescription},
|
|
39
|
+
${RoleType.MachineToMachine}
|
|
40
|
+
);
|
|
41
|
+
`);
|
|
42
|
+
// Assign Logto Management API permission `all` to the Logto Management API M2M role
|
|
43
|
+
await pool.query(sql `
|
|
44
|
+
insert into roles_scopes (id, role_id, scope_id, tenant_id)
|
|
45
|
+
values (
|
|
46
|
+
${generateStandardId()},
|
|
47
|
+
${roleId},
|
|
48
|
+
(
|
|
49
|
+
select scopes.id
|
|
50
|
+
from scopes
|
|
51
|
+
join resources on
|
|
52
|
+
scopes.tenant_id = resources.tenant_id and
|
|
53
|
+
scopes.resource_id = resources.id
|
|
54
|
+
where resources.indicator = ${defaultTenantManagementApiIndicator}
|
|
55
|
+
and scopes.name = ${PredefinedScope.All}
|
|
56
|
+
and scopes.tenant_id = ${defaultTenantId}
|
|
57
|
+
),
|
|
58
|
+
${defaultTenantId}
|
|
59
|
+
)
|
|
60
|
+
`);
|
|
61
|
+
},
|
|
62
|
+
down: async (pool) => {
|
|
63
|
+
if (!isCi) {
|
|
64
|
+
console.info("Skipping the down script `next-1716291265-create-pre-configured-m-api-role.ts` since it's should not be applied to production db.");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// Delete the created role
|
|
68
|
+
await pool.query(sql `
|
|
69
|
+
delete from roles
|
|
70
|
+
where tenant_id = ${defaultTenantId}
|
|
71
|
+
and name = ${roleName}
|
|
72
|
+
and description = ${roleDescription}
|
|
73
|
+
and type = ${RoleType.MachineToMachine}
|
|
74
|
+
`);
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
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 service_logs drop constraint service_logs_tenant_id_fkey;
|
|
6
|
+
`);
|
|
7
|
+
},
|
|
8
|
+
down: async (pool) => {
|
|
9
|
+
await pool.query(sql `
|
|
10
|
+
alter table service_logs add constraint service_logs_tenant_id_fkey
|
|
11
|
+
foreign key (tenant_id) references tenants(id) on update cascade on delete cascade;
|
|
12
|
+
`);
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
export default alteration;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a standard id with 21 characters, including lowercase letters and numbers.
|
|
3
|
+
*
|
|
4
|
+
* @see {@link lowercaseAlphabet}
|
|
5
|
+
*/
|
|
6
|
+
export declare const generateStandardId: (size?: number | undefined) => string;
|
|
7
|
+
/**
|
|
8
|
+
* Generate a standard short id with 12 characters, including lowercase letters and numbers.
|
|
9
|
+
*
|
|
10
|
+
* @see {@link lowercaseAlphabet}
|
|
11
|
+
*/
|
|
12
|
+
export declare const generateStandardShortId: (size?: number | undefined) => string;
|
|
13
|
+
/**
|
|
14
|
+
* Generate a standard secret with 32 characters, including uppercase letters, lowercase
|
|
15
|
+
* letters, and numbers.
|
|
16
|
+
*
|
|
17
|
+
* @see {@link alphabet}
|
|
18
|
+
*/
|
|
19
|
+
export declare const generateStandardSecret: (size?: number | undefined) => string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is forked from `@logto/shared` 3.1.0 to avoid alteration scripts to depend on outer packages.
|
|
3
|
+
*/
|
|
4
|
+
import { customAlphabet } from 'nanoid';
|
|
5
|
+
const lowercaseAlphabet = '0123456789abcdefghijklmnopqrstuvwxyz';
|
|
6
|
+
const alphabet = `${lowercaseAlphabet}ABCDEFGHIJKLMNOPQRSTUVWXYZ`;
|
|
7
|
+
const buildIdGenerator = (size, includingUppercase = true) => customAlphabet(includingUppercase ? alphabet : lowercaseAlphabet, size);
|
|
8
|
+
/**
|
|
9
|
+
* Generate a standard id with 21 characters, including lowercase letters and numbers.
|
|
10
|
+
*
|
|
11
|
+
* @see {@link lowercaseAlphabet}
|
|
12
|
+
*/
|
|
13
|
+
export const generateStandardId = buildIdGenerator(21, false);
|
|
14
|
+
/**
|
|
15
|
+
* Generate a standard short id with 12 characters, including lowercase letters and numbers.
|
|
16
|
+
*
|
|
17
|
+
* @see {@link lowercaseAlphabet}
|
|
18
|
+
*/
|
|
19
|
+
export const generateStandardShortId = buildIdGenerator(12, false);
|
|
20
|
+
/**
|
|
21
|
+
* Generate a standard secret with 32 characters, including uppercase letters, lowercase
|
|
22
|
+
* letters, and numbers.
|
|
23
|
+
*
|
|
24
|
+
* @see {@link alphabet}
|
|
25
|
+
*/
|
|
26
|
+
export const generateStandardSecret = buildIdGenerator(32);
|
package/lib/db-entries/role.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export type CreateRole = {
|
|
|
11
11
|
name: string;
|
|
12
12
|
description: string;
|
|
13
13
|
type?: RoleType;
|
|
14
|
+
/** If the role is the default role for a new user. Should be ignored for `MachineToMachine` roles. */
|
|
15
|
+
isDefault?: boolean;
|
|
14
16
|
};
|
|
15
17
|
export type Role = {
|
|
16
18
|
tenantId: string;
|
|
@@ -18,6 +20,8 @@ export type Role = {
|
|
|
18
20
|
name: string;
|
|
19
21
|
description: string;
|
|
20
22
|
type: RoleType;
|
|
23
|
+
/** If the role is the default role for a new user. Should be ignored for `MachineToMachine` roles. */
|
|
24
|
+
isDefault: boolean;
|
|
21
25
|
};
|
|
22
|
-
export type RoleKeys = 'tenantId' | 'id' | 'name' | 'description' | 'type';
|
|
26
|
+
export type RoleKeys = 'tenantId' | 'id' | 'name' | 'description' | 'type' | 'isDefault';
|
|
23
27
|
export declare const Roles: GeneratedSchema<RoleKeys, CreateRole, Role, 'roles', 'role'>;
|
package/lib/db-entries/role.js
CHANGED
|
@@ -7,6 +7,7 @@ const createGuard = z.object({
|
|
|
7
7
|
name: z.string().min(1).max(128),
|
|
8
8
|
description: z.string().min(1).max(128),
|
|
9
9
|
type: z.nativeEnum(RoleType).optional(),
|
|
10
|
+
isDefault: z.boolean().optional(),
|
|
10
11
|
});
|
|
11
12
|
const guard = z.object({
|
|
12
13
|
tenantId: z.string().max(21),
|
|
@@ -14,6 +15,7 @@ const guard = z.object({
|
|
|
14
15
|
name: z.string().min(1).max(128),
|
|
15
16
|
description: z.string().min(1).max(128),
|
|
16
17
|
type: z.nativeEnum(RoleType),
|
|
18
|
+
isDefault: z.boolean(),
|
|
17
19
|
});
|
|
18
20
|
export const Roles = Object.freeze({
|
|
19
21
|
table: 'roles',
|
|
@@ -24,6 +26,7 @@ export const Roles = Object.freeze({
|
|
|
24
26
|
name: 'name',
|
|
25
27
|
description: 'description',
|
|
26
28
|
type: 'type',
|
|
29
|
+
isDefault: 'is_default',
|
|
27
30
|
},
|
|
28
31
|
fieldKeys: [
|
|
29
32
|
'tenantId',
|
|
@@ -31,6 +34,7 @@ export const Roles = Object.freeze({
|
|
|
31
34
|
'name',
|
|
32
35
|
'description',
|
|
33
36
|
'type',
|
|
37
|
+
'isDefault',
|
|
34
38
|
],
|
|
35
39
|
createGuard,
|
|
36
40
|
guard,
|
|
@@ -10,7 +10,7 @@ export declare enum InteractionHookEvent {
|
|
|
10
10
|
PostSignIn = "PostSignIn",
|
|
11
11
|
PostResetPassword = "PostResetPassword"
|
|
12
12
|
}
|
|
13
|
-
declare enum DataHookSchema {
|
|
13
|
+
export declare enum DataHookSchema {
|
|
14
14
|
User = "User",
|
|
15
15
|
Role = "Role",
|
|
16
16
|
Scope = "Scope",
|
|
@@ -20,19 +20,21 @@ declare enum DataHookSchema {
|
|
|
20
20
|
}
|
|
21
21
|
declare enum DataHookBasicMutationType {
|
|
22
22
|
Created = "Created",
|
|
23
|
-
Deleted = "Deleted"
|
|
23
|
+
Deleted = "Deleted"
|
|
24
|
+
}
|
|
25
|
+
declare enum DataHookDetailMutationType {
|
|
24
26
|
Updated = "Updated"
|
|
25
27
|
}
|
|
26
28
|
type BasicDataHookEvent = `${DataHookSchema}.${DataHookBasicMutationType}`;
|
|
27
|
-
type CustomDataHookMutableSchema = `${DataHookSchema.User}.SuspensionStatus` | `${DataHookSchema.Role}.Scopes` | `${DataHookSchema.Organization}.Membership` | `${DataHookSchema.OrganizationRole}.Scopes`;
|
|
28
|
-
type DataHookPropertyUpdateEvent = `${CustomDataHookMutableSchema}.${
|
|
29
|
+
type CustomDataHookMutableSchema = `${DataHookSchema}.Data` | `${DataHookSchema.User}.SuspensionStatus` | `${DataHookSchema.Role}.Scopes` | `${DataHookSchema.Organization}.Membership` | `${DataHookSchema.OrganizationRole}.Scopes`;
|
|
30
|
+
type DataHookPropertyUpdateEvent = `${CustomDataHookMutableSchema}.${DataHookDetailMutationType.Updated}`;
|
|
29
31
|
export type DataHookEvent = BasicDataHookEvent | DataHookPropertyUpdateEvent;
|
|
30
32
|
/** The hook event values that can be registered. */
|
|
31
|
-
export declare const hookEvents: readonly [InteractionHookEvent.PostRegister, InteractionHookEvent.PostSignIn, InteractionHookEvent.PostResetPassword, "User.Created", "User.Deleted", "User.Updated", "User.SuspensionStatus.Updated", "Role.Created", "Role.Deleted", "Role.Updated", "Role.Scopes.Updated", "Scope.Created", "Scope.Deleted", "Scope.Updated", "Organization.Created", "Organization.Deleted", "Organization.Updated", "Organization.Membership.Updated", "OrganizationRole.Created", "OrganizationRole.Deleted", "OrganizationRole.Updated", "OrganizationRole.Scopes.Updated", "OrganizationScope.Created", "OrganizationScope.Deleted", "OrganizationScope.Updated"];
|
|
33
|
+
export declare const hookEvents: readonly [InteractionHookEvent.PostRegister, InteractionHookEvent.PostSignIn, InteractionHookEvent.PostResetPassword, "User.Created", "User.Deleted", "User.Data.Updated", "User.SuspensionStatus.Updated", "Role.Created", "Role.Deleted", "Role.Data.Updated", "Role.Scopes.Updated", "Scope.Created", "Scope.Deleted", "Scope.Data.Updated", "Organization.Created", "Organization.Deleted", "Organization.Data.Updated", "Organization.Membership.Updated", "OrganizationRole.Created", "OrganizationRole.Deleted", "OrganizationRole.Data.Updated", "OrganizationRole.Scopes.Updated", "OrganizationScope.Created", "OrganizationScope.Deleted", "OrganizationScope.Data.Updated"];
|
|
32
34
|
/** The type of hook event values that can be registered. */
|
|
33
35
|
export type HookEvent = (typeof hookEvents)[number];
|
|
34
|
-
export declare const hookEventGuard: z.ZodEnum<[InteractionHookEvent.PostRegister, InteractionHookEvent.PostSignIn, InteractionHookEvent.PostResetPassword, "User.Created", "User.Deleted", "User.Updated", "User.SuspensionStatus.Updated", "Role.Created", "Role.Deleted", "Role.Updated", "Role.Scopes.Updated", "Scope.Created", "Scope.Deleted", "Scope.Updated", "Organization.Created", "Organization.Deleted", "Organization.Updated", "Organization.Membership.Updated", "OrganizationRole.Created", "OrganizationRole.Deleted", "OrganizationRole.Updated", "OrganizationRole.Scopes.Updated", "OrganizationScope.Created", "OrganizationScope.Deleted", "OrganizationScope.Updated"]>;
|
|
35
|
-
export declare const hookEventsGuard: z.ZodArray<z.ZodEnum<[InteractionHookEvent.PostRegister, InteractionHookEvent.PostSignIn, InteractionHookEvent.PostResetPassword, "User.Created", "User.Deleted", "User.Updated", "User.SuspensionStatus.Updated", "Role.Created", "Role.Deleted", "Role.Updated", "Role.Scopes.Updated", "Scope.Created", "Scope.Deleted", "Scope.Updated", "Organization.Created", "Organization.Deleted", "Organization.Updated", "Organization.Membership.Updated", "OrganizationRole.Created", "OrganizationRole.Deleted", "OrganizationRole.Updated", "OrganizationRole.Scopes.Updated", "OrganizationScope.Created", "OrganizationScope.Deleted", "OrganizationScope.Updated"]>, "many">;
|
|
36
|
+
export declare const hookEventGuard: z.ZodEnum<[InteractionHookEvent.PostRegister, InteractionHookEvent.PostSignIn, InteractionHookEvent.PostResetPassword, "User.Created", "User.Deleted", "User.Data.Updated", "User.SuspensionStatus.Updated", "Role.Created", "Role.Deleted", "Role.Data.Updated", "Role.Scopes.Updated", "Scope.Created", "Scope.Deleted", "Scope.Data.Updated", "Organization.Created", "Organization.Deleted", "Organization.Data.Updated", "Organization.Membership.Updated", "OrganizationRole.Created", "OrganizationRole.Deleted", "OrganizationRole.Data.Updated", "OrganizationRole.Scopes.Updated", "OrganizationScope.Created", "OrganizationScope.Deleted", "OrganizationScope.Data.Updated"]>;
|
|
37
|
+
export declare const hookEventsGuard: z.ZodArray<z.ZodEnum<[InteractionHookEvent.PostRegister, InteractionHookEvent.PostSignIn, InteractionHookEvent.PostResetPassword, "User.Created", "User.Deleted", "User.Data.Updated", "User.SuspensionStatus.Updated", "Role.Created", "Role.Deleted", "Role.Data.Updated", "Role.Scopes.Updated", "Scope.Created", "Scope.Deleted", "Scope.Data.Updated", "Organization.Created", "Organization.Deleted", "Organization.Data.Updated", "Organization.Membership.Updated", "OrganizationRole.Created", "OrganizationRole.Deleted", "OrganizationRole.Data.Updated", "OrganizationRole.Scopes.Updated", "OrganizationScope.Created", "OrganizationScope.Deleted", "OrganizationScope.Data.Updated"]>, "many">;
|
|
36
38
|
export type HookEvents = z.infer<typeof hookEventsGuard>;
|
|
37
39
|
export declare const interactionHookEventGuard: z.ZodNativeEnum<typeof InteractionHookEvent>;
|
|
38
40
|
/**
|
|
@@ -68,31 +70,31 @@ export type HookConfig = z.infer<typeof hookConfigGuard>;
|
|
|
68
70
|
export declare const managementApiHooksRegistration: Readonly<{
|
|
69
71
|
'POST /users': "User.Created";
|
|
70
72
|
'DELETE /users/:userId': "User.Deleted";
|
|
71
|
-
'PATCH /users/:userId': "User.Updated";
|
|
72
|
-
'PATCH /users/:userId/custom-data': "User.Updated";
|
|
73
|
-
'PATCH /users/:userId/profile': "User.Updated";
|
|
74
|
-
'PATCH /users/:userId/password': "User.Updated";
|
|
73
|
+
'PATCH /users/:userId': "User.Data.Updated";
|
|
74
|
+
'PATCH /users/:userId/custom-data': "User.Data.Updated";
|
|
75
|
+
'PATCH /users/:userId/profile': "User.Data.Updated";
|
|
76
|
+
'PATCH /users/:userId/password': "User.Data.Updated";
|
|
75
77
|
'PATCH /users/:userId/is-suspended': "User.SuspensionStatus.Updated";
|
|
76
78
|
'POST /roles': "Role.Created";
|
|
77
79
|
'DELETE /roles/:id': "Role.Deleted";
|
|
78
|
-
'PATCH /roles/:id': "Role.Updated";
|
|
80
|
+
'PATCH /roles/:id': "Role.Data.Updated";
|
|
79
81
|
'POST /roles/:id/scopes': "Role.Scopes.Updated";
|
|
80
82
|
'DELETE /roles/:id/scopes/:scopeId': "Role.Scopes.Updated";
|
|
81
83
|
'POST /resources/:resourceId/scopes': "Scope.Created";
|
|
82
84
|
'DELETE /resources/:resourceId/scopes/:scopeId': "Scope.Deleted";
|
|
83
|
-
'PATCH /resources/:resourceId/scopes/:scopeId': "Scope.Updated";
|
|
85
|
+
'PATCH /resources/:resourceId/scopes/:scopeId': "Scope.Data.Updated";
|
|
84
86
|
'POST /organizations': "Organization.Created";
|
|
85
87
|
'DELETE /organizations/:id': "Organization.Deleted";
|
|
86
|
-
'PATCH /organizations/:id': "Organization.Updated";
|
|
88
|
+
'PATCH /organizations/:id': "Organization.Data.Updated";
|
|
87
89
|
'PUT /organizations/:id/users': "Organization.Membership.Updated";
|
|
88
90
|
'POST /organizations/:id/users': "Organization.Membership.Updated";
|
|
89
91
|
'DELETE /organizations/:id/users/:userId': "Organization.Membership.Updated";
|
|
90
92
|
'POST /organization-roles': "OrganizationRole.Created";
|
|
91
93
|
'DELETE /organization-roles/:id': "OrganizationRole.Deleted";
|
|
92
|
-
'PATCH /organization-roles/:id': "OrganizationRole.Updated";
|
|
94
|
+
'PATCH /organization-roles/:id': "OrganizationRole.Data.Updated";
|
|
93
95
|
'POST /organization-scopes': "OrganizationScope.Created";
|
|
94
96
|
'DELETE /organization-scopes/:id': "OrganizationScope.Deleted";
|
|
95
|
-
'PATCH /organization-scopes/:id': "OrganizationScope.Updated";
|
|
97
|
+
'PATCH /organization-scopes/:id': "OrganizationScope.Data.Updated";
|
|
96
98
|
'PUT /organization-roles/:id/scopes': "OrganizationRole.Scopes.Updated";
|
|
97
99
|
'POST /organization-roles/:id/scopes': "OrganizationRole.Scopes.Updated";
|
|
98
100
|
'DELETE /organization-roles/:id/scopes/:organizationScopeId': "OrganizationRole.Scopes.Updated";
|
|
@@ -13,7 +13,7 @@ export var InteractionHookEvent;
|
|
|
13
13
|
InteractionHookEvent["PostResetPassword"] = "PostResetPassword";
|
|
14
14
|
})(InteractionHookEvent || (InteractionHookEvent = {}));
|
|
15
15
|
// DataHookEvent
|
|
16
|
-
var DataHookSchema;
|
|
16
|
+
export var DataHookSchema;
|
|
17
17
|
(function (DataHookSchema) {
|
|
18
18
|
DataHookSchema["User"] = "User";
|
|
19
19
|
DataHookSchema["Role"] = "Role";
|
|
@@ -26,8 +26,11 @@ var DataHookBasicMutationType;
|
|
|
26
26
|
(function (DataHookBasicMutationType) {
|
|
27
27
|
DataHookBasicMutationType["Created"] = "Created";
|
|
28
28
|
DataHookBasicMutationType["Deleted"] = "Deleted";
|
|
29
|
-
DataHookBasicMutationType["Updated"] = "Updated";
|
|
30
29
|
})(DataHookBasicMutationType || (DataHookBasicMutationType = {}));
|
|
30
|
+
var DataHookDetailMutationType;
|
|
31
|
+
(function (DataHookDetailMutationType) {
|
|
32
|
+
DataHookDetailMutationType["Updated"] = "Updated";
|
|
33
|
+
})(DataHookDetailMutationType || (DataHookDetailMutationType = {}));
|
|
31
34
|
/** The hook event values that can be registered. */
|
|
32
35
|
export const hookEvents = Object.freeze([
|
|
33
36
|
InteractionHookEvent.PostRegister,
|
|
@@ -35,26 +38,26 @@ export const hookEvents = Object.freeze([
|
|
|
35
38
|
InteractionHookEvent.PostResetPassword,
|
|
36
39
|
'User.Created',
|
|
37
40
|
'User.Deleted',
|
|
38
|
-
'User.Updated',
|
|
41
|
+
'User.Data.Updated',
|
|
39
42
|
'User.SuspensionStatus.Updated',
|
|
40
43
|
'Role.Created',
|
|
41
44
|
'Role.Deleted',
|
|
42
|
-
'Role.Updated',
|
|
45
|
+
'Role.Data.Updated',
|
|
43
46
|
'Role.Scopes.Updated',
|
|
44
47
|
'Scope.Created',
|
|
45
48
|
'Scope.Deleted',
|
|
46
|
-
'Scope.Updated',
|
|
49
|
+
'Scope.Data.Updated',
|
|
47
50
|
'Organization.Created',
|
|
48
51
|
'Organization.Deleted',
|
|
49
|
-
'Organization.Updated',
|
|
52
|
+
'Organization.Data.Updated',
|
|
50
53
|
'Organization.Membership.Updated',
|
|
51
54
|
'OrganizationRole.Created',
|
|
52
55
|
'OrganizationRole.Deleted',
|
|
53
|
-
'OrganizationRole.Updated',
|
|
56
|
+
'OrganizationRole.Data.Updated',
|
|
54
57
|
'OrganizationRole.Scopes.Updated',
|
|
55
58
|
'OrganizationScope.Created',
|
|
56
59
|
'OrganizationScope.Deleted',
|
|
57
|
-
'OrganizationScope.Updated',
|
|
60
|
+
'OrganizationScope.Data.Updated',
|
|
58
61
|
]);
|
|
59
62
|
export const hookEventGuard = z.enum(hookEvents);
|
|
60
63
|
export const hookEventsGuard = hookEventGuard.array();
|
|
@@ -84,31 +87,31 @@ export const hookConfigGuard = z.object({
|
|
|
84
87
|
export const managementApiHooksRegistration = Object.freeze({
|
|
85
88
|
'POST /users': 'User.Created',
|
|
86
89
|
'DELETE /users/:userId': 'User.Deleted',
|
|
87
|
-
'PATCH /users/:userId': 'User.Updated',
|
|
88
|
-
'PATCH /users/:userId/custom-data': 'User.Updated',
|
|
89
|
-
'PATCH /users/:userId/profile': 'User.Updated',
|
|
90
|
-
'PATCH /users/:userId/password': 'User.Updated',
|
|
90
|
+
'PATCH /users/:userId': 'User.Data.Updated',
|
|
91
|
+
'PATCH /users/:userId/custom-data': 'User.Data.Updated',
|
|
92
|
+
'PATCH /users/:userId/profile': 'User.Data.Updated',
|
|
93
|
+
'PATCH /users/:userId/password': 'User.Data.Updated',
|
|
91
94
|
'PATCH /users/:userId/is-suspended': 'User.SuspensionStatus.Updated',
|
|
92
95
|
'POST /roles': 'Role.Created',
|
|
93
96
|
'DELETE /roles/:id': 'Role.Deleted',
|
|
94
|
-
'PATCH /roles/:id': 'Role.Updated',
|
|
97
|
+
'PATCH /roles/:id': 'Role.Data.Updated',
|
|
95
98
|
'POST /roles/:id/scopes': 'Role.Scopes.Updated',
|
|
96
99
|
'DELETE /roles/:id/scopes/:scopeId': 'Role.Scopes.Updated',
|
|
97
100
|
'POST /resources/:resourceId/scopes': 'Scope.Created',
|
|
98
101
|
'DELETE /resources/:resourceId/scopes/:scopeId': 'Scope.Deleted',
|
|
99
|
-
'PATCH /resources/:resourceId/scopes/:scopeId': 'Scope.Updated',
|
|
102
|
+
'PATCH /resources/:resourceId/scopes/:scopeId': 'Scope.Data.Updated',
|
|
100
103
|
'POST /organizations': 'Organization.Created',
|
|
101
104
|
'DELETE /organizations/:id': 'Organization.Deleted',
|
|
102
|
-
'PATCH /organizations/:id': 'Organization.Updated',
|
|
105
|
+
'PATCH /organizations/:id': 'Organization.Data.Updated',
|
|
103
106
|
'PUT /organizations/:id/users': 'Organization.Membership.Updated',
|
|
104
107
|
'POST /organizations/:id/users': 'Organization.Membership.Updated',
|
|
105
108
|
'DELETE /organizations/:id/users/:userId': 'Organization.Membership.Updated',
|
|
106
109
|
'POST /organization-roles': 'OrganizationRole.Created',
|
|
107
110
|
'DELETE /organization-roles/:id': 'OrganizationRole.Deleted',
|
|
108
|
-
'PATCH /organization-roles/:id': 'OrganizationRole.Updated',
|
|
111
|
+
'PATCH /organization-roles/:id': 'OrganizationRole.Data.Updated',
|
|
109
112
|
'POST /organization-scopes': 'OrganizationScope.Created',
|
|
110
113
|
'DELETE /organization-scopes/:id': 'OrganizationScope.Deleted',
|
|
111
|
-
'PATCH /organization-scopes/:id': 'OrganizationScope.Updated',
|
|
114
|
+
'PATCH /organization-scopes/:id': 'OrganizationScope.Data.Updated',
|
|
112
115
|
'PUT /organization-roles/:id/scopes': 'OrganizationRole.Scopes.Updated',
|
|
113
116
|
'POST /organization-roles/:id/scopes': 'OrganizationRole.Scopes.Updated',
|
|
114
117
|
'DELETE /organization-roles/:id/scopes/:organizationScopeId': 'OrganizationRole.Scopes.Updated',
|
package/lib/models/tenants.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { InferModelType } from '@withtyped/server/model';
|
|
2
|
-
import { z } from 'zod';
|
|
3
2
|
import { TenantTag } from '../types/tenant.js';
|
|
4
3
|
export declare const Tenants: import("@withtyped/server/lib/model/index.js").default<"tenants", {
|
|
5
4
|
id: string;
|
|
@@ -11,23 +10,3 @@ export declare const Tenants: import("@withtyped/server/lib/model/index.js").def
|
|
|
11
10
|
isSuspended: boolean;
|
|
12
11
|
}, "name" | "createdAt" | "isSuspended" | "tag", "createdAt">;
|
|
13
12
|
export type TenantModel = InferModelType<typeof Tenants>;
|
|
14
|
-
export declare const tenantInfoGuard: z.ZodObject<{
|
|
15
|
-
name: z.ZodType<string, z.ZodTypeDef, string>;
|
|
16
|
-
id: z.ZodType<string, z.ZodTypeDef, string>;
|
|
17
|
-
isSuspended: z.ZodType<boolean, z.ZodTypeDef, boolean>;
|
|
18
|
-
tag: z.ZodType<TenantTag, z.ZodTypeDef, TenantTag>;
|
|
19
|
-
indicator: z.ZodString;
|
|
20
|
-
}, z.UnknownKeysParam, z.ZodTypeAny, {
|
|
21
|
-
name: string;
|
|
22
|
-
id: string;
|
|
23
|
-
indicator: string;
|
|
24
|
-
isSuspended: boolean;
|
|
25
|
-
tag: TenantTag;
|
|
26
|
-
}, {
|
|
27
|
-
name: string;
|
|
28
|
-
id: string;
|
|
29
|
-
indicator: string;
|
|
30
|
-
isSuspended: boolean;
|
|
31
|
-
tag: TenantTag;
|
|
32
|
-
}>;
|
|
33
|
-
export type TenantInfo = z.infer<typeof tenantInfoGuard>;
|
package/lib/models/tenants.js
CHANGED
|
@@ -20,6 +20,3 @@ export const Tenants = createModel(
|
|
|
20
20
|
`, 'public')
|
|
21
21
|
.extend('tag', z.nativeEnum(TenantTag))
|
|
22
22
|
.extend('createdAt', { readonly: true });
|
|
23
|
-
export const tenantInfoGuard = Tenants.guard('model')
|
|
24
|
-
.pick({ id: true, name: true, tag: true, isSuspended: true })
|
|
25
|
-
.extend({ indicator: z.string() });
|
package/lib/seeds/cloud-api.js
CHANGED
|
@@ -61,4 +61,5 @@ export const createTenantApplicationRole = () => ({
|
|
|
61
61
|
name: AdminTenantRole.TenantApplication,
|
|
62
62
|
description: 'The role for M2M applications that represent a user tenant and send requests to Logto Cloud.',
|
|
63
63
|
type: RoleType.MachineToMachine,
|
|
64
|
+
isDefault: false,
|
|
64
65
|
});
|
|
@@ -120,3 +120,7 @@ export declare const createMeApiInAdminTenant: () => Readonly<{
|
|
|
120
120
|
type: RoleType.User;
|
|
121
121
|
};
|
|
122
122
|
}>;
|
|
123
|
+
/**
|
|
124
|
+
* Create a pre-configured M2M role for Management API access.
|
|
125
|
+
*/
|
|
126
|
+
export declare const createPreConfiguredManagementApiAccessRole: (tenantId: string) => CreateRole;
|
|
@@ -131,3 +131,13 @@ export const createMeApiInAdminTenant = () => {
|
|
|
131
131
|
},
|
|
132
132
|
});
|
|
133
133
|
};
|
|
134
|
+
/**
|
|
135
|
+
* Create a pre-configured M2M role for Management API access.
|
|
136
|
+
*/
|
|
137
|
+
export const createPreConfiguredManagementApiAccessRole = (tenantId) => ({
|
|
138
|
+
tenantId,
|
|
139
|
+
id: generateStandardId(),
|
|
140
|
+
description: 'This default role grants access to the Logto management API.',
|
|
141
|
+
name: 'Logto Management API access',
|
|
142
|
+
type: RoleType.MachineToMachine,
|
|
143
|
+
});
|
package/lib/types/hook.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { type Application, type User } from '../db-entries/index.js';
|
|
3
|
+
import { type DataHookEvent, type InteractionHookEvent } from '../foundations/index.js';
|
|
4
|
+
import { type InteractionEvent } from './interactions.js';
|
|
5
|
+
import { type userInfoSelectFields } from './user.js';
|
|
2
6
|
declare const hookExecutionStatsGuard: z.ZodObject<{
|
|
3
7
|
successCount: z.ZodNumber;
|
|
4
8
|
requestCount: z.ZodNumber;
|
|
@@ -24,8 +28,8 @@ export declare const hookResponseGuard: z.ZodObject<{
|
|
|
24
28
|
headers?: Record<string, string> | undefined;
|
|
25
29
|
retries?: number | undefined;
|
|
26
30
|
}>;
|
|
27
|
-
event: z.ZodType<"User.Created" | "User.Deleted" | "
|
|
28
|
-
events: z.ZodType<("User.Created" | "User.Deleted" | "
|
|
31
|
+
event: z.ZodType<"User.Created" | "User.Deleted" | "Role.Created" | "Role.Deleted" | "Scope.Created" | "Scope.Deleted" | "Organization.Created" | "Organization.Deleted" | "OrganizationRole.Created" | "OrganizationRole.Deleted" | "OrganizationScope.Created" | "OrganizationScope.Deleted" | "User.Data.Updated" | "Role.Data.Updated" | "Scope.Data.Updated" | "Organization.Data.Updated" | "OrganizationRole.Data.Updated" | "OrganizationScope.Data.Updated" | "User.SuspensionStatus.Updated" | "Role.Scopes.Updated" | "Organization.Membership.Updated" | "OrganizationRole.Scopes.Updated" | InteractionHookEvent | null, z.ZodTypeDef, "User.Created" | "User.Deleted" | "Role.Created" | "Role.Deleted" | "Scope.Created" | "Scope.Deleted" | "Organization.Created" | "Organization.Deleted" | "OrganizationRole.Created" | "OrganizationRole.Deleted" | "OrganizationScope.Created" | "OrganizationScope.Deleted" | "User.Data.Updated" | "Role.Data.Updated" | "Scope.Data.Updated" | "Organization.Data.Updated" | "OrganizationRole.Data.Updated" | "OrganizationScope.Data.Updated" | "User.SuspensionStatus.Updated" | "Role.Scopes.Updated" | "Organization.Membership.Updated" | "OrganizationRole.Scopes.Updated" | InteractionHookEvent | null>;
|
|
32
|
+
events: z.ZodType<("User.Created" | "User.Deleted" | "Role.Created" | "Role.Deleted" | "Scope.Created" | "Scope.Deleted" | "Organization.Created" | "Organization.Deleted" | "OrganizationRole.Created" | "OrganizationRole.Deleted" | "OrganizationScope.Created" | "OrganizationScope.Deleted" | "User.Data.Updated" | "Role.Data.Updated" | "Scope.Data.Updated" | "Organization.Data.Updated" | "OrganizationRole.Data.Updated" | "OrganizationScope.Data.Updated" | "User.SuspensionStatus.Updated" | "Role.Scopes.Updated" | "Organization.Membership.Updated" | "OrganizationRole.Scopes.Updated" | InteractionHookEvent)[], z.ZodTypeDef, ("User.Created" | "User.Deleted" | "Role.Created" | "Role.Deleted" | "Scope.Created" | "Scope.Deleted" | "Organization.Created" | "Organization.Deleted" | "OrganizationRole.Created" | "OrganizationRole.Deleted" | "OrganizationScope.Created" | "OrganizationScope.Deleted" | "User.Data.Updated" | "Role.Data.Updated" | "Scope.Data.Updated" | "Organization.Data.Updated" | "OrganizationRole.Data.Updated" | "OrganizationScope.Data.Updated" | "User.SuspensionStatus.Updated" | "Role.Scopes.Updated" | "Organization.Membership.Updated" | "OrganizationRole.Scopes.Updated" | InteractionHookEvent)[]>;
|
|
29
33
|
signingKey: z.ZodType<string, z.ZodTypeDef, string>;
|
|
30
34
|
enabled: z.ZodType<boolean, z.ZodTypeDef, boolean>;
|
|
31
35
|
executionStats: z.ZodObject<{
|
|
@@ -48,8 +52,8 @@ export declare const hookResponseGuard: z.ZodObject<{
|
|
|
48
52
|
headers?: Record<string, string> | undefined;
|
|
49
53
|
retries?: number | undefined;
|
|
50
54
|
};
|
|
51
|
-
event: "User.Created" | "User.Deleted" | "
|
|
52
|
-
events: ("User.Created" | "User.Deleted" | "
|
|
55
|
+
event: "User.Created" | "User.Deleted" | "Role.Created" | "Role.Deleted" | "Scope.Created" | "Scope.Deleted" | "Organization.Created" | "Organization.Deleted" | "OrganizationRole.Created" | "OrganizationRole.Deleted" | "OrganizationScope.Created" | "OrganizationScope.Deleted" | "User.Data.Updated" | "Role.Data.Updated" | "Scope.Data.Updated" | "Organization.Data.Updated" | "OrganizationRole.Data.Updated" | "OrganizationScope.Data.Updated" | "User.SuspensionStatus.Updated" | "Role.Scopes.Updated" | "Organization.Membership.Updated" | "OrganizationRole.Scopes.Updated" | InteractionHookEvent | null;
|
|
56
|
+
events: ("User.Created" | "User.Deleted" | "Role.Created" | "Role.Deleted" | "Scope.Created" | "Scope.Deleted" | "Organization.Created" | "Organization.Deleted" | "OrganizationRole.Created" | "OrganizationRole.Deleted" | "OrganizationScope.Created" | "OrganizationScope.Deleted" | "User.Data.Updated" | "Role.Data.Updated" | "Scope.Data.Updated" | "Organization.Data.Updated" | "OrganizationRole.Data.Updated" | "OrganizationScope.Data.Updated" | "User.SuspensionStatus.Updated" | "Role.Scopes.Updated" | "Organization.Membership.Updated" | "OrganizationRole.Scopes.Updated" | InteractionHookEvent)[];
|
|
53
57
|
signingKey: string;
|
|
54
58
|
enabled: boolean;
|
|
55
59
|
executionStats: {
|
|
@@ -66,8 +70,8 @@ export declare const hookResponseGuard: z.ZodObject<{
|
|
|
66
70
|
headers?: Record<string, string> | undefined;
|
|
67
71
|
retries?: number | undefined;
|
|
68
72
|
};
|
|
69
|
-
event: "User.Created" | "User.Deleted" | "
|
|
70
|
-
events: ("User.Created" | "User.Deleted" | "
|
|
73
|
+
event: "User.Created" | "User.Deleted" | "Role.Created" | "Role.Deleted" | "Scope.Created" | "Scope.Deleted" | "Organization.Created" | "Organization.Deleted" | "OrganizationRole.Created" | "OrganizationRole.Deleted" | "OrganizationScope.Created" | "OrganizationScope.Deleted" | "User.Data.Updated" | "Role.Data.Updated" | "Scope.Data.Updated" | "Organization.Data.Updated" | "OrganizationRole.Data.Updated" | "OrganizationScope.Data.Updated" | "User.SuspensionStatus.Updated" | "Role.Scopes.Updated" | "Organization.Membership.Updated" | "OrganizationRole.Scopes.Updated" | InteractionHookEvent | null;
|
|
74
|
+
events: ("User.Created" | "User.Deleted" | "Role.Created" | "Role.Deleted" | "Scope.Created" | "Scope.Deleted" | "Organization.Created" | "Organization.Deleted" | "OrganizationRole.Created" | "OrganizationRole.Deleted" | "OrganizationScope.Created" | "OrganizationScope.Deleted" | "User.Data.Updated" | "Role.Data.Updated" | "Scope.Data.Updated" | "Organization.Data.Updated" | "OrganizationRole.Data.Updated" | "OrganizationScope.Data.Updated" | "User.SuspensionStatus.Updated" | "Role.Scopes.Updated" | "Organization.Membership.Updated" | "OrganizationRole.Scopes.Updated" | InteractionHookEvent)[];
|
|
71
75
|
signingKey: string;
|
|
72
76
|
enabled: boolean;
|
|
73
77
|
executionStats: {
|
|
@@ -87,4 +91,61 @@ export declare const hookTestErrorResponseDataGuard: z.ZodObject<{
|
|
|
87
91
|
responseBody: string;
|
|
88
92
|
}>;
|
|
89
93
|
export type HookTestErrorResponseData = z.infer<typeof hookTestErrorResponseDataGuard>;
|
|
94
|
+
/**
|
|
95
|
+
* The interaction API context for triggering InteractionHook and DataHook events.
|
|
96
|
+
* In the `koaInteractionHooks` middleware,
|
|
97
|
+
* we will store the context before processing the interaction and consume it after the interaction is processed if needed.
|
|
98
|
+
*/
|
|
99
|
+
export type InteractionApiMetadata = {
|
|
100
|
+
/** The application ID if the hook is triggered by interaction API. */
|
|
101
|
+
applicationId?: string;
|
|
102
|
+
/** The session ID if the hook is triggered by interaction API. */
|
|
103
|
+
sessionId?: string;
|
|
104
|
+
/** The InteractionEvent if the hook is triggered by interaction API. */
|
|
105
|
+
interactionEvent: InteractionEvent;
|
|
106
|
+
};
|
|
107
|
+
type InteractionApiContextPayload = {
|
|
108
|
+
/** Fetch application detail by application ID before sending the hook event */
|
|
109
|
+
application?: Pick<Application, 'id' | 'type' | 'name' | 'description'>;
|
|
110
|
+
sessionId?: string;
|
|
111
|
+
interactionEvent?: InteractionEvent;
|
|
112
|
+
};
|
|
113
|
+
export type InteractionHookEventPayload = {
|
|
114
|
+
event: InteractionHookEvent;
|
|
115
|
+
createdAt: string;
|
|
116
|
+
hookId: string;
|
|
117
|
+
userAgent?: string;
|
|
118
|
+
userIp?: string;
|
|
119
|
+
/** InteractionHook result */
|
|
120
|
+
userId?: string;
|
|
121
|
+
/** Fetch user detail by user ID before sending the hook event */
|
|
122
|
+
user?: Pick<User, (typeof userInfoSelectFields)[number]>;
|
|
123
|
+
} & InteractionApiContextPayload & Record<string, unknown>;
|
|
124
|
+
/**
|
|
125
|
+
* The API context for management API triggered data hooks.
|
|
126
|
+
* In the `koaManagementApiHooks` middleware,
|
|
127
|
+
* we will store the context of management API requests that triggers the DataHook events.
|
|
128
|
+
* Can't put it in the DataHookMetadata because the matched API context is only available after the request is processed.
|
|
129
|
+
*/
|
|
130
|
+
export type ManagementApiContext = {
|
|
131
|
+
/** Request route params. */
|
|
132
|
+
params?: Record<string, string>;
|
|
133
|
+
/** Request route path. */
|
|
134
|
+
path: string;
|
|
135
|
+
/** Matched route used as the identifier to trigger the hook. */
|
|
136
|
+
matchedRoute?: string;
|
|
137
|
+
/** Request method. */
|
|
138
|
+
method: string;
|
|
139
|
+
/** Response status code. */
|
|
140
|
+
status: number;
|
|
141
|
+
};
|
|
142
|
+
export type DataHookEventPayload = {
|
|
143
|
+
event: DataHookEvent;
|
|
144
|
+
createdAt: string;
|
|
145
|
+
hookId: string;
|
|
146
|
+
ip?: string;
|
|
147
|
+
userAgent?: string;
|
|
148
|
+
data?: unknown;
|
|
149
|
+
} & Partial<InteractionApiContextPayload> & Partial<ManagementApiContext> & Record<string, unknown>;
|
|
150
|
+
export type HookEventPayload = InteractionHookEventPayload | DataHookEventPayload;
|
|
90
151
|
export {};
|
package/lib/types/mapi-proxy.js
CHANGED
|
@@ -27,6 +27,7 @@ export const getMapiProxyRole = (tenantId) => Object.freeze({
|
|
|
27
27
|
name: `machine:mapi:${tenantId}`,
|
|
28
28
|
description: `Machine-to-machine role for accessing Management API of tenant '${tenantId}'.`,
|
|
29
29
|
type: RoleType.MachineToMachine,
|
|
30
|
+
isDefault: false,
|
|
30
31
|
});
|
|
31
32
|
/**
|
|
32
33
|
* Given a tenant ID, return the application create data for the mapi proxy. The proxy will use the
|
package/lib/types/user.d.ts
CHANGED
|
@@ -361,7 +361,11 @@ export declare const userMfaVerificationResponseGuard: z.ZodArray<z.ZodObject<{
|
|
|
361
361
|
remainCodes?: number | undefined;
|
|
362
362
|
}>, "many">;
|
|
363
363
|
export type UserMfaVerificationResponse = z.infer<typeof userMfaVerificationResponseGuard>;
|
|
364
|
-
/**
|
|
364
|
+
/**
|
|
365
|
+
* Internal read-only roles for user tenants.
|
|
366
|
+
*
|
|
367
|
+
* @deprecated We don't use internal roles anymore.
|
|
368
|
+
*/
|
|
365
369
|
export declare enum InternalRole {
|
|
366
370
|
/**
|
|
367
371
|
* Internal admin role for Machine-to-Machine apps in Logto user tenants.
|
package/lib/types/user.js
CHANGED
|
@@ -33,7 +33,11 @@ export const userMfaVerificationResponseGuard = z
|
|
|
33
33
|
remainCodes: z.number().optional(),
|
|
34
34
|
})
|
|
35
35
|
.array();
|
|
36
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Internal read-only roles for user tenants.
|
|
38
|
+
*
|
|
39
|
+
* @deprecated We don't use internal roles anymore.
|
|
40
|
+
*/
|
|
37
41
|
export var InternalRole;
|
|
38
42
|
(function (InternalRole) {
|
|
39
43
|
/**
|
package/lib/utils/role.d.ts
CHANGED
package/lib/utils/role.js
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
|
+
/** @deprecated We don't restrict roles in the database anymore. */
|
|
1
2
|
export const internalRolePrefix = '#internal:';
|
|
3
|
+
/** @deprecated We don't restrict roles in the database anymore. */
|
|
2
4
|
export const isInternalRole = (roleName) => roleName.startsWith(internalRolePrefix);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logto/schemas",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.0",
|
|
4
4
|
"author": "Silverhand Inc. <contact@silverhand.io>",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@silverhand/eslint-config": "6.0.1",
|
|
28
|
-
"@silverhand/essentials": "^2.9.
|
|
28
|
+
"@silverhand/essentials": "^2.9.1",
|
|
29
29
|
"@silverhand/slonik": "31.0.0-beta.2",
|
|
30
30
|
"@silverhand/ts-config": "6.0.0",
|
|
31
31
|
"@types/inquirer": "^9.0.0",
|
|
@@ -64,12 +64,13 @@
|
|
|
64
64
|
"prettier": "@silverhand/eslint-config/.prettierrc",
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"@logto/connector-kit": "^3.0.0",
|
|
67
|
-
"@logto/core-kit": "^2.
|
|
67
|
+
"@logto/core-kit": "^2.5.0",
|
|
68
68
|
"@logto/language-kit": "^1.1.0",
|
|
69
|
-
"@logto/phrases": "^1.
|
|
69
|
+
"@logto/phrases": "^1.11.0",
|
|
70
70
|
"@logto/phrases-experience": "^1.6.1",
|
|
71
71
|
"@logto/shared": "^3.1.1",
|
|
72
|
-
"@withtyped/server": "^0.13.6"
|
|
72
|
+
"@withtyped/server": "^0.13.6",
|
|
73
|
+
"nanoid": "^5.0.1"
|
|
73
74
|
},
|
|
74
75
|
"peerDependencies": {
|
|
75
76
|
"zod": "^3.22.4"
|
package/tables/_after_all.sql
CHANGED
|
@@ -32,30 +32,3 @@ revoke all privileges
|
|
|
32
32
|
revoke all privileges
|
|
33
33
|
on table service_logs
|
|
34
34
|
from logto_tenant_${database};
|
|
35
|
-
|
|
36
|
-
---- Create policies to make internal roles read-only ----
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Note:
|
|
40
|
-
*
|
|
41
|
-
* Internal roles have scope preset and they are read-only, but we do not
|
|
42
|
-
* limit user or application assignment since it's business logic.
|
|
43
|
-
*/
|
|
44
|
-
|
|
45
|
-
-- Restrict direct role modification
|
|
46
|
-
create policy roles_select on roles
|
|
47
|
-
for select using (true);
|
|
48
|
-
|
|
49
|
-
drop policy roles_modification on roles;
|
|
50
|
-
create policy roles_modification on roles
|
|
51
|
-
using (not starts_with(name, '#internal:'));
|
|
52
|
-
|
|
53
|
-
-- Restrict role - scope modification
|
|
54
|
-
create policy roles_scopes_select on roles_scopes
|
|
55
|
-
for select using (true);
|
|
56
|
-
|
|
57
|
-
drop policy roles_scopes_modification on roles_scopes;
|
|
58
|
-
create policy roles_scopes_modification on roles_scopes
|
|
59
|
-
using (not starts_with((select roles.name from roles where roles.id = role_id), '#internal:'));
|
|
60
|
-
|
|
61
|
-
---- TODO: Make internal API Resources read-only ----
|
package/tables/roles.sql
CHANGED
|
@@ -9,6 +9,8 @@ create table roles (
|
|
|
9
9
|
name varchar(128) not null,
|
|
10
10
|
description varchar(128) not null,
|
|
11
11
|
type role_type not null default 'User',
|
|
12
|
+
/** If the role is the default role for a new user. Should be ignored for `MachineToMachine` roles. */
|
|
13
|
+
is_default boolean not null default false,
|
|
12
14
|
primary key (id),
|
|
13
15
|
constraint roles__name
|
|
14
16
|
unique (tenant_id, name)
|
package/tables/service_logs.sql
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
create table service_logs (
|
|
2
2
|
id varchar(21) not null,
|
|
3
|
-
tenant_id varchar(21) not null
|
|
4
|
-
references tenants (id) on update cascade on delete cascade,
|
|
3
|
+
tenant_id varchar(21) not null,
|
|
5
4
|
type varchar(64) not null,
|
|
6
5
|
payload jsonb /* @use JsonObject */ not null default '{}'::jsonb,
|
|
7
6
|
created_at timestamptz not null default(now()),
|