@logto/schemas 1.6.0 → 1.8.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/LICENSE +0 -6
- package/alterations/1.7.0-1688375200-sync-cloud-m2m-to-logto-config.ts +85 -0
- package/alterations/1.7.0-1688613459-remove-m2m-credentials-from-existing-logto-email-connector-config.ts +88 -0
- package/alterations/1.7.0-1688627407-daily-active-users.ts +61 -0
- package/alterations/1.8.0-1692088012-add-is-suspend-column-to-tenants-table.ts +18 -0
- package/alterations/1.8.0-1692194751-add-affiliate-scopes.ts +57 -0
- package/alterations-js/1.7.0-1688375200-sync-cloud-m2m-to-logto-config.d.ts +3 -0
- package/alterations-js/1.7.0-1688375200-sync-cloud-m2m-to-logto-config.js +42 -0
- package/alterations-js/1.7.0-1688613459-remove-m2m-credentials-from-existing-logto-email-connector-config.d.ts +3 -0
- package/alterations-js/1.7.0-1688613459-remove-m2m-credentials-from-existing-logto-email-connector-config.js +55 -0
- package/alterations-js/1.7.0-1688627407-daily-active-users.d.ts +3 -0
- package/alterations-js/1.7.0-1688627407-daily-active-users.js +53 -0
- package/alterations-js/1.8.0-1692088012-add-is-suspend-column-to-tenants-table.d.ts +3 -0
- package/alterations-js/1.8.0-1692088012-add-is-suspend-column-to-tenants-table.js +14 -0
- package/alterations-js/1.8.0-1692194751-add-affiliate-scopes.d.ts +3 -0
- package/alterations-js/1.8.0-1692194751-add-affiliate-scopes.js +48 -0
- package/lib/consts/index.d.ts +1 -0
- package/lib/consts/index.js +1 -0
- package/lib/consts/tenant.d.ts +1 -0
- package/lib/consts/tenant.js +1 -0
- package/lib/db-entries/daily-active-user.d.ts +14 -0
- package/lib/db-entries/daily-active-user.js +32 -0
- package/lib/db-entries/index.d.ts +1 -0
- package/lib/db-entries/index.js +1 -0
- package/lib/foundations/jsonb-types.js +1 -1
- package/lib/models/tenants.d.ts +25 -4
- package/lib/models/tenants.js +5 -3
- package/lib/seeds/cloud-api.d.ts +5 -1
- package/lib/seeds/cloud-api.js +6 -0
- package/lib/seeds/logto-config.d.ts +6 -1
- package/lib/seeds/logto-config.js +10 -0
- package/lib/types/connector.d.ts +14 -5
- package/lib/types/connector.js +1 -0
- package/lib/types/log/index.d.ts +3 -1
- package/lib/types/logto-config.d.ts +16 -0
- package/lib/types/logto-config.js +8 -0
- package/lib/types/system.d.ts +11 -50
- package/lib/types/system.js +4 -21
- package/package.json +8 -8
- package/tables/daily_active_users.sql +13 -0
package/LICENSE
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
Portions of this software are licensed as follows:
|
|
2
|
-
|
|
3
|
-
* All content that resides under the "packages/cloud" directory of this repository, if that directory exists, is licensed under the license defined in "packages/cloud/LICENSE" (Elastic-2.0).
|
|
4
|
-
* All third party components incorporated into this software are licensed under the original license provided by the owner of the applicable component.
|
|
5
|
-
* Content outside of the above mentioned directories or restrictions above is available under the "MPL-2.0" license as defined below.
|
|
6
|
-
|
|
7
1
|
Mozilla Public License Version 2.0
|
|
8
2
|
==================================
|
|
9
3
|
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { sql } from 'slonik';
|
|
2
|
+
|
|
3
|
+
import type { AlterationScript } from '../lib/types/alteration.js';
|
|
4
|
+
|
|
5
|
+
const adminTenantId = 'admin';
|
|
6
|
+
|
|
7
|
+
const cloudServiceApplicationName = 'Cloud Service';
|
|
8
|
+
|
|
9
|
+
const cloudConnectionResourceIndicator = 'https://cloud.logto.io/api';
|
|
10
|
+
|
|
11
|
+
enum ApplicationType {
|
|
12
|
+
Native = 'Native',
|
|
13
|
+
SPA = 'SPA',
|
|
14
|
+
Traditional = 'Traditional',
|
|
15
|
+
MachineToMachine = 'MachineToMachine',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const cloudConnectionConfigKey = 'cloudConnection';
|
|
19
|
+
|
|
20
|
+
type Application = {
|
|
21
|
+
tenantId: string;
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
secret: string;
|
|
25
|
+
description: string;
|
|
26
|
+
type: ApplicationType;
|
|
27
|
+
oidcClientMetadata: unknown;
|
|
28
|
+
customClientMetadata: {
|
|
29
|
+
tenantId: string;
|
|
30
|
+
};
|
|
31
|
+
createdAt: number;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type CloudConnectionConfig = {
|
|
35
|
+
tenantId: string;
|
|
36
|
+
key: string;
|
|
37
|
+
value: unknown;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const alteration: AlterationScript = {
|
|
41
|
+
up: async (pool) => {
|
|
42
|
+
const { rows } = await pool.query<Application>(
|
|
43
|
+
sql`select * from applications where type = ${ApplicationType.MachineToMachine} and tenant_id = ${adminTenantId} and name = ${cloudServiceApplicationName}`
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const { rows: existingCloudConnections } = await pool.query<CloudConnectionConfig>(sql`
|
|
47
|
+
select * from logto_configs where key = ${cloudConnectionConfigKey}
|
|
48
|
+
`);
|
|
49
|
+
const tenantIdsWithExistingRecords = new Set(
|
|
50
|
+
existingCloudConnections.map(({ tenantId }) => tenantId)
|
|
51
|
+
);
|
|
52
|
+
const filteredRows = rows.filter(
|
|
53
|
+
({ customClientMetadata: { tenantId } }) => !tenantIdsWithExistingRecords.has(tenantId)
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
if (filteredRows.length === 0) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
await pool.query(sql`
|
|
61
|
+
insert into logto_configs (tenant_id, key, value) values ${sql.join(
|
|
62
|
+
filteredRows.map(({ id, secret, customClientMetadata }) => {
|
|
63
|
+
const { tenantId } = customClientMetadata;
|
|
64
|
+
const cloudConnectionValue = {
|
|
65
|
+
appId: id,
|
|
66
|
+
appSecret: secret,
|
|
67
|
+
resource: cloudConnectionResourceIndicator,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return sql`(${tenantId}, ${cloudConnectionConfigKey}, ${JSON.stringify(
|
|
71
|
+
cloudConnectionValue
|
|
72
|
+
)})`;
|
|
73
|
+
}),
|
|
74
|
+
sql`,`
|
|
75
|
+
)}
|
|
76
|
+
`);
|
|
77
|
+
},
|
|
78
|
+
down: async (pool) => {
|
|
79
|
+
await pool.query(sql`
|
|
80
|
+
delete from logto_configs where key = ${cloudConnectionConfigKey}
|
|
81
|
+
`);
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export default alteration;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { GlobalValues } from '@logto/shared';
|
|
2
|
+
import { appendPath } from '@silverhand/essentials';
|
|
3
|
+
import { sql } from 'slonik';
|
|
4
|
+
|
|
5
|
+
import type { AlterationScript } from '../lib/types/alteration.js';
|
|
6
|
+
|
|
7
|
+
type M2mCredentials = {
|
|
8
|
+
appSecret: string;
|
|
9
|
+
appId: string;
|
|
10
|
+
endpoint: string;
|
|
11
|
+
tokenEndpoint: string;
|
|
12
|
+
resource: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type EmailServiceConnector = {
|
|
16
|
+
tenantId: string;
|
|
17
|
+
config: Partial<M2mCredentials> & Record<string, unknown>;
|
|
18
|
+
};
|
|
19
|
+
type CloudConnectionData = {
|
|
20
|
+
tenantId: string;
|
|
21
|
+
value: { appSecret: string; appId: string; resource: string };
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
enum ServiceConnector {
|
|
25
|
+
Email = 'logto-email',
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const cloudConnectionKey = 'cloudConnection';
|
|
29
|
+
|
|
30
|
+
const alteration: AlterationScript = {
|
|
31
|
+
up: async (pool) => {
|
|
32
|
+
const { rows: rawConnectors } = await pool.query<EmailServiceConnector>(sql`
|
|
33
|
+
select tenant_id, config from connectors where connector_id = ${ServiceConnector.Email};
|
|
34
|
+
`);
|
|
35
|
+
const connectors = rawConnectors.map((rawConnector) => {
|
|
36
|
+
const {
|
|
37
|
+
tenantId,
|
|
38
|
+
config: { appSecret, appId, endpoint, tokenEndpoint, resource, ...rest },
|
|
39
|
+
} = rawConnector;
|
|
40
|
+
return { tenantId, config: rest };
|
|
41
|
+
});
|
|
42
|
+
for (const connector of connectors) {
|
|
43
|
+
const { tenantId, config } = connector;
|
|
44
|
+
// eslint-disable-next-line no-await-in-loop
|
|
45
|
+
await pool.query(sql`
|
|
46
|
+
update connectors set config = ${JSON.stringify(
|
|
47
|
+
config
|
|
48
|
+
)} where tenant_id = ${tenantId} and connector_id = ${ServiceConnector.Email};
|
|
49
|
+
`);
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
down: async (pool) => {
|
|
53
|
+
const { rows: cloudConnections } = await pool.query<CloudConnectionData>(sql`
|
|
54
|
+
select tenant_id, value from logto_configs where key = ${cloudConnectionKey};
|
|
55
|
+
`);
|
|
56
|
+
|
|
57
|
+
/** Get `endpoint` and `tokenEndpoints` */
|
|
58
|
+
const globalValues = new GlobalValues();
|
|
59
|
+
const { cloudUrlSet, adminUrlSet } = globalValues;
|
|
60
|
+
const endpoint = appendPath(cloudUrlSet.endpoint, 'api').toString();
|
|
61
|
+
const tokenEndpoint = appendPath(adminUrlSet.endpoint, 'oidc/token').toString();
|
|
62
|
+
|
|
63
|
+
const { rows: rawEmailServiceConnectors } = await pool.query<EmailServiceConnector>(sql`
|
|
64
|
+
select tenant_id, config from connectors where connector_id = ${ServiceConnector.Email};
|
|
65
|
+
`);
|
|
66
|
+
const tenantIdsWithM2mCredentials = new Set(cloudConnections.map(({ tenantId }) => tenantId));
|
|
67
|
+
const emailServiceConnectors = rawEmailServiceConnectors.filter(({ tenantId }) =>
|
|
68
|
+
tenantIdsWithM2mCredentials.has(tenantId)
|
|
69
|
+
);
|
|
70
|
+
for (const emailServiceConnector of emailServiceConnectors) {
|
|
71
|
+
const { tenantId: currentTenantId, config } = emailServiceConnector;
|
|
72
|
+
const newConfig = {
|
|
73
|
+
...config,
|
|
74
|
+
endpoint,
|
|
75
|
+
tokenEndpoint,
|
|
76
|
+
...cloudConnections.find(({ tenantId }) => tenantId === currentTenantId)?.value,
|
|
77
|
+
};
|
|
78
|
+
// eslint-disable-next-line no-await-in-loop
|
|
79
|
+
await pool.query(sql`
|
|
80
|
+
update connectors set config = ${JSON.stringify(
|
|
81
|
+
newConfig
|
|
82
|
+
)} where tenant_id = ${currentTenantId} and connector_id = ${ServiceConnector.Email};
|
|
83
|
+
`);
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
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
|
+
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 daily_active_users (
|
|
22
|
+
id varchar(21) not null,
|
|
23
|
+
tenant_id varchar(21) not null
|
|
24
|
+
references tenants (id) on update cascade on delete cascade,
|
|
25
|
+
user_id varchar(21) not null,
|
|
26
|
+
date timestamptz not null,
|
|
27
|
+
primary key (id),
|
|
28
|
+
constraint daily_active_users__user_id_date
|
|
29
|
+
unique (user_id, date)
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
create index daily_active_users__id
|
|
33
|
+
on daily_active_users (tenant_id, id);
|
|
34
|
+
|
|
35
|
+
create trigger set_tenant_id before insert on daily_active_users
|
|
36
|
+
for each row execute procedure set_tenant_id();
|
|
37
|
+
|
|
38
|
+
alter table daily_active_users enable row level security;
|
|
39
|
+
|
|
40
|
+
create policy daily_active_users_tenant_id on daily_active_users
|
|
41
|
+
as restrictive
|
|
42
|
+
using (tenant_id = (select id from tenants where db_user = current_user));
|
|
43
|
+
create policy daily_active_users_modification on daily_active_users
|
|
44
|
+
using (true);
|
|
45
|
+
|
|
46
|
+
grant select, insert, update, delete on daily_active_users to ${baseRoleId};
|
|
47
|
+
`);
|
|
48
|
+
},
|
|
49
|
+
down: async (pool) => {
|
|
50
|
+
await pool.query(sql`
|
|
51
|
+
drop policy daily_active_users_tenant_id on daily_active_users;
|
|
52
|
+
drop policy daily_active_users_modification on daily_active_users;
|
|
53
|
+
|
|
54
|
+
alter table daily_active_users disable row level security;
|
|
55
|
+
|
|
56
|
+
drop table daily_active_users;
|
|
57
|
+
`);
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export default alteration;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { sql } from 'slonik';
|
|
2
|
+
|
|
3
|
+
import type { AlterationScript } from '../lib/types/alteration.js';
|
|
4
|
+
|
|
5
|
+
const alteration: AlterationScript = {
|
|
6
|
+
up: async (pool) => {
|
|
7
|
+
await pool.query(sql`
|
|
8
|
+
alter table tenants add column is_suspended boolean not null default false;
|
|
9
|
+
`);
|
|
10
|
+
},
|
|
11
|
+
down: async (pool) => {
|
|
12
|
+
await pool.query(sql`
|
|
13
|
+
alter table tenants drop column is_suspended;
|
|
14
|
+
`);
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default alteration;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { generateStandardId } from '@logto/shared/universal';
|
|
2
|
+
import { sql } from 'slonik';
|
|
3
|
+
|
|
4
|
+
import type { AlterationScript } from '../lib/types/alteration.js';
|
|
5
|
+
|
|
6
|
+
const adminTenantId = 'admin';
|
|
7
|
+
|
|
8
|
+
const alteration: AlterationScript = {
|
|
9
|
+
up: async (pool) => {
|
|
10
|
+
// Get `resourceId` of the admin tenant's resource whose indicator is `https://cloud.logto.io/api`.
|
|
11
|
+
const { id: resourceId } = await pool.one<{ id: string }>(sql`
|
|
12
|
+
select id from resources
|
|
13
|
+
where tenant_id = ${adminTenantId}
|
|
14
|
+
and indicator = 'https://cloud.logto.io/api'
|
|
15
|
+
`);
|
|
16
|
+
|
|
17
|
+
const { id: roleId } = await pool.one<{ id: string }>(sql`
|
|
18
|
+
select id from roles
|
|
19
|
+
where tenant_id = ${adminTenantId}
|
|
20
|
+
and name = 'admin:admin'
|
|
21
|
+
`);
|
|
22
|
+
|
|
23
|
+
const createAffiliateId = generateStandardId();
|
|
24
|
+
const manageAffiliateId = generateStandardId();
|
|
25
|
+
|
|
26
|
+
await pool.query(sql`
|
|
27
|
+
insert into scopes (tenant_id, id, name, description, resource_id)
|
|
28
|
+
values (
|
|
29
|
+
${adminTenantId},
|
|
30
|
+
${createAffiliateId},
|
|
31
|
+
'create:affiliate',
|
|
32
|
+
'Allow creating new affiliates and logs.',
|
|
33
|
+
${resourceId}
|
|
34
|
+
), (
|
|
35
|
+
${adminTenantId},
|
|
36
|
+
${manageAffiliateId},
|
|
37
|
+
'manage:affiliate',
|
|
38
|
+
'Allow managing affiliates, including create, update, and delete.',
|
|
39
|
+
${resourceId}
|
|
40
|
+
);
|
|
41
|
+
`);
|
|
42
|
+
|
|
43
|
+
await pool.query(sql`
|
|
44
|
+
insert into roles_scopes (tenant_id, id, role_id, scope_id) values
|
|
45
|
+
(${adminTenantId}, ${generateStandardId()}, ${roleId}, ${createAffiliateId}),
|
|
46
|
+
(${adminTenantId}, ${generateStandardId()}, ${roleId}, ${manageAffiliateId});
|
|
47
|
+
`);
|
|
48
|
+
},
|
|
49
|
+
down: async (pool) => {
|
|
50
|
+
await pool.query(sql`
|
|
51
|
+
delete from scopes
|
|
52
|
+
where tenant_id = ${adminTenantId} and name = any(array['create:affiliate', 'manage:affiliate']);
|
|
53
|
+
`);
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export default alteration;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { sql } from 'slonik';
|
|
2
|
+
const adminTenantId = 'admin';
|
|
3
|
+
const cloudServiceApplicationName = 'Cloud Service';
|
|
4
|
+
const cloudConnectionResourceIndicator = 'https://cloud.logto.io/api';
|
|
5
|
+
var ApplicationType;
|
|
6
|
+
(function (ApplicationType) {
|
|
7
|
+
ApplicationType["Native"] = "Native";
|
|
8
|
+
ApplicationType["SPA"] = "SPA";
|
|
9
|
+
ApplicationType["Traditional"] = "Traditional";
|
|
10
|
+
ApplicationType["MachineToMachine"] = "MachineToMachine";
|
|
11
|
+
})(ApplicationType || (ApplicationType = {}));
|
|
12
|
+
const cloudConnectionConfigKey = 'cloudConnection';
|
|
13
|
+
const alteration = {
|
|
14
|
+
up: async (pool) => {
|
|
15
|
+
const { rows } = await pool.query(sql `select * from applications where type = ${ApplicationType.MachineToMachine} and tenant_id = ${adminTenantId} and name = ${cloudServiceApplicationName}`);
|
|
16
|
+
const { rows: existingCloudConnections } = await pool.query(sql `
|
|
17
|
+
select * from logto_configs where key = ${cloudConnectionConfigKey}
|
|
18
|
+
`);
|
|
19
|
+
const tenantIdsWithExistingRecords = new Set(existingCloudConnections.map(({ tenantId }) => tenantId));
|
|
20
|
+
const filteredRows = rows.filter(({ customClientMetadata: { tenantId } }) => !tenantIdsWithExistingRecords.has(tenantId));
|
|
21
|
+
if (filteredRows.length === 0) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
await pool.query(sql `
|
|
25
|
+
insert into logto_configs (tenant_id, key, value) values ${sql.join(filteredRows.map(({ id, secret, customClientMetadata }) => {
|
|
26
|
+
const { tenantId } = customClientMetadata;
|
|
27
|
+
const cloudConnectionValue = {
|
|
28
|
+
appId: id,
|
|
29
|
+
appSecret: secret,
|
|
30
|
+
resource: cloudConnectionResourceIndicator,
|
|
31
|
+
};
|
|
32
|
+
return sql `(${tenantId}, ${cloudConnectionConfigKey}, ${JSON.stringify(cloudConnectionValue)})`;
|
|
33
|
+
}), sql `,`)}
|
|
34
|
+
`);
|
|
35
|
+
},
|
|
36
|
+
down: async (pool) => {
|
|
37
|
+
await pool.query(sql `
|
|
38
|
+
delete from logto_configs where key = ${cloudConnectionConfigKey}
|
|
39
|
+
`);
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
export default alteration;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { GlobalValues } from '@logto/shared';
|
|
2
|
+
import { appendPath } from '@silverhand/essentials';
|
|
3
|
+
import { sql } from 'slonik';
|
|
4
|
+
var ServiceConnector;
|
|
5
|
+
(function (ServiceConnector) {
|
|
6
|
+
ServiceConnector["Email"] = "logto-email";
|
|
7
|
+
})(ServiceConnector || (ServiceConnector = {}));
|
|
8
|
+
const cloudConnectionKey = 'cloudConnection';
|
|
9
|
+
const alteration = {
|
|
10
|
+
up: async (pool) => {
|
|
11
|
+
const { rows: rawConnectors } = await pool.query(sql `
|
|
12
|
+
select tenant_id, config from connectors where connector_id = ${ServiceConnector.Email};
|
|
13
|
+
`);
|
|
14
|
+
const connectors = rawConnectors.map((rawConnector) => {
|
|
15
|
+
const { tenantId, config: { appSecret, appId, endpoint, tokenEndpoint, resource, ...rest }, } = rawConnector;
|
|
16
|
+
return { tenantId, config: rest };
|
|
17
|
+
});
|
|
18
|
+
for (const connector of connectors) {
|
|
19
|
+
const { tenantId, config } = connector;
|
|
20
|
+
// eslint-disable-next-line no-await-in-loop
|
|
21
|
+
await pool.query(sql `
|
|
22
|
+
update connectors set config = ${JSON.stringify(config)} where tenant_id = ${tenantId} and connector_id = ${ServiceConnector.Email};
|
|
23
|
+
`);
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
down: async (pool) => {
|
|
27
|
+
const { rows: cloudConnections } = await pool.query(sql `
|
|
28
|
+
select tenant_id, value from logto_configs where key = ${cloudConnectionKey};
|
|
29
|
+
`);
|
|
30
|
+
/** Get `endpoint` and `tokenEndpoints` */
|
|
31
|
+
const globalValues = new GlobalValues();
|
|
32
|
+
const { cloudUrlSet, adminUrlSet } = globalValues;
|
|
33
|
+
const endpoint = appendPath(cloudUrlSet.endpoint, 'api').toString();
|
|
34
|
+
const tokenEndpoint = appendPath(adminUrlSet.endpoint, 'oidc/token').toString();
|
|
35
|
+
const { rows: rawEmailServiceConnectors } = await pool.query(sql `
|
|
36
|
+
select tenant_id, config from connectors where connector_id = ${ServiceConnector.Email};
|
|
37
|
+
`);
|
|
38
|
+
const tenantIdsWithM2mCredentials = new Set(cloudConnections.map(({ tenantId }) => tenantId));
|
|
39
|
+
const emailServiceConnectors = rawEmailServiceConnectors.filter(({ tenantId }) => tenantIdsWithM2mCredentials.has(tenantId));
|
|
40
|
+
for (const emailServiceConnector of emailServiceConnectors) {
|
|
41
|
+
const { tenantId: currentTenantId, config } = emailServiceConnector;
|
|
42
|
+
const newConfig = {
|
|
43
|
+
...config,
|
|
44
|
+
endpoint,
|
|
45
|
+
tokenEndpoint,
|
|
46
|
+
...cloudConnections.find(({ tenantId }) => tenantId === currentTenantId)?.value,
|
|
47
|
+
};
|
|
48
|
+
// eslint-disable-next-line no-await-in-loop
|
|
49
|
+
await pool.query(sql `
|
|
50
|
+
update connectors set config = ${JSON.stringify(newConfig)} where tenant_id = ${currentTenantId} and connector_id = ${ServiceConnector.Email};
|
|
51
|
+
`);
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
export default alteration;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { sql } from 'slonik';
|
|
2
|
+
const getId = (value) => sql.identifier([value]);
|
|
3
|
+
const getDatabaseName = async (pool) => {
|
|
4
|
+
const { currentDatabase } = await pool.one(sql `
|
|
5
|
+
select current_database();
|
|
6
|
+
`);
|
|
7
|
+
return currentDatabase.replaceAll('-', '_');
|
|
8
|
+
};
|
|
9
|
+
const alteration = {
|
|
10
|
+
up: async (pool) => {
|
|
11
|
+
const database = await getDatabaseName(pool);
|
|
12
|
+
const baseRoleId = getId(`logto_tenant_${database}`);
|
|
13
|
+
await pool.query(sql `
|
|
14
|
+
create table daily_active_users (
|
|
15
|
+
id varchar(21) not null,
|
|
16
|
+
tenant_id varchar(21) not null
|
|
17
|
+
references tenants (id) on update cascade on delete cascade,
|
|
18
|
+
user_id varchar(21) not null,
|
|
19
|
+
date timestamptz not null,
|
|
20
|
+
primary key (id),
|
|
21
|
+
constraint daily_active_users__user_id_date
|
|
22
|
+
unique (user_id, date)
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
create index daily_active_users__id
|
|
26
|
+
on daily_active_users (tenant_id, id);
|
|
27
|
+
|
|
28
|
+
create trigger set_tenant_id before insert on daily_active_users
|
|
29
|
+
for each row execute procedure set_tenant_id();
|
|
30
|
+
|
|
31
|
+
alter table daily_active_users enable row level security;
|
|
32
|
+
|
|
33
|
+
create policy daily_active_users_tenant_id on daily_active_users
|
|
34
|
+
as restrictive
|
|
35
|
+
using (tenant_id = (select id from tenants where db_user = current_user));
|
|
36
|
+
create policy daily_active_users_modification on daily_active_users
|
|
37
|
+
using (true);
|
|
38
|
+
|
|
39
|
+
grant select, insert, update, delete on daily_active_users to ${baseRoleId};
|
|
40
|
+
`);
|
|
41
|
+
},
|
|
42
|
+
down: async (pool) => {
|
|
43
|
+
await pool.query(sql `
|
|
44
|
+
drop policy daily_active_users_tenant_id on daily_active_users;
|
|
45
|
+
drop policy daily_active_users_modification on daily_active_users;
|
|
46
|
+
|
|
47
|
+
alter table daily_active_users disable row level security;
|
|
48
|
+
|
|
49
|
+
drop table daily_active_users;
|
|
50
|
+
`);
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
export default alteration;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { sql } from 'slonik';
|
|
2
|
+
const alteration = {
|
|
3
|
+
up: async (pool) => {
|
|
4
|
+
await pool.query(sql `
|
|
5
|
+
alter table tenants add column is_suspended boolean not null default false;
|
|
6
|
+
`);
|
|
7
|
+
},
|
|
8
|
+
down: async (pool) => {
|
|
9
|
+
await pool.query(sql `
|
|
10
|
+
alter table tenants drop column is_suspended;
|
|
11
|
+
`);
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
export default alteration;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { generateStandardId } from '@logto/shared/universal';
|
|
2
|
+
import { sql } from 'slonik';
|
|
3
|
+
const adminTenantId = 'admin';
|
|
4
|
+
const alteration = {
|
|
5
|
+
up: async (pool) => {
|
|
6
|
+
// Get `resourceId` of the admin tenant's resource whose indicator is `https://cloud.logto.io/api`.
|
|
7
|
+
const { id: resourceId } = await pool.one(sql `
|
|
8
|
+
select id from resources
|
|
9
|
+
where tenant_id = ${adminTenantId}
|
|
10
|
+
and indicator = 'https://cloud.logto.io/api'
|
|
11
|
+
`);
|
|
12
|
+
const { id: roleId } = await pool.one(sql `
|
|
13
|
+
select id from roles
|
|
14
|
+
where tenant_id = ${adminTenantId}
|
|
15
|
+
and name = 'admin:admin'
|
|
16
|
+
`);
|
|
17
|
+
const createAffiliateId = generateStandardId();
|
|
18
|
+
const manageAffiliateId = generateStandardId();
|
|
19
|
+
await pool.query(sql `
|
|
20
|
+
insert into scopes (tenant_id, id, name, description, resource_id)
|
|
21
|
+
values (
|
|
22
|
+
${adminTenantId},
|
|
23
|
+
${createAffiliateId},
|
|
24
|
+
'create:affiliate',
|
|
25
|
+
'Allow creating new affiliates and logs.',
|
|
26
|
+
${resourceId}
|
|
27
|
+
), (
|
|
28
|
+
${adminTenantId},
|
|
29
|
+
${manageAffiliateId},
|
|
30
|
+
'manage:affiliate',
|
|
31
|
+
'Allow managing affiliates, including create, update, and delete.',
|
|
32
|
+
${resourceId}
|
|
33
|
+
);
|
|
34
|
+
`);
|
|
35
|
+
await pool.query(sql `
|
|
36
|
+
insert into roles_scopes (tenant_id, id, role_id, scope_id) values
|
|
37
|
+
(${adminTenantId}, ${generateStandardId()}, ${roleId}, ${createAffiliateId}),
|
|
38
|
+
(${adminTenantId}, ${generateStandardId()}, ${roleId}, ${manageAffiliateId});
|
|
39
|
+
`);
|
|
40
|
+
},
|
|
41
|
+
down: async (pool) => {
|
|
42
|
+
await pool.query(sql `
|
|
43
|
+
delete from scopes
|
|
44
|
+
where tenant_id = ${adminTenantId} and name = any(array['create:affiliate', 'manage:affiliate']);
|
|
45
|
+
`);
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
export default alteration;
|
package/lib/consts/index.d.ts
CHANGED
package/lib/consts/index.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const maxFreeTenantLimit = 10;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const maxFreeTenantLimit = 10;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { GeneratedSchema } from './../foundations/index.js';
|
|
2
|
+
export type CreateDailyActiveUser = {
|
|
3
|
+
id: string;
|
|
4
|
+
tenantId?: string;
|
|
5
|
+
userId: string;
|
|
6
|
+
date: number;
|
|
7
|
+
};
|
|
8
|
+
export type DailyActiveUser = {
|
|
9
|
+
id: string;
|
|
10
|
+
tenantId: string;
|
|
11
|
+
userId: string;
|
|
12
|
+
date: number;
|
|
13
|
+
};
|
|
14
|
+
export declare const DailyActiveUsers: GeneratedSchema<CreateDailyActiveUser, DailyActiveUser>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
const createGuard = z.object({
|
|
4
|
+
id: z.string().min(1).max(21),
|
|
5
|
+
tenantId: z.string().max(21).optional(),
|
|
6
|
+
userId: z.string().min(1).max(21),
|
|
7
|
+
date: z.number(),
|
|
8
|
+
});
|
|
9
|
+
const guard = z.object({
|
|
10
|
+
id: z.string().min(1).max(21),
|
|
11
|
+
tenantId: z.string().max(21),
|
|
12
|
+
userId: z.string().min(1).max(21),
|
|
13
|
+
date: z.number(),
|
|
14
|
+
});
|
|
15
|
+
export const DailyActiveUsers = Object.freeze({
|
|
16
|
+
table: 'daily_active_users',
|
|
17
|
+
tableSingular: 'daily_active_user',
|
|
18
|
+
fields: {
|
|
19
|
+
id: 'id',
|
|
20
|
+
tenantId: 'tenant_id',
|
|
21
|
+
userId: 'user_id',
|
|
22
|
+
date: 'date',
|
|
23
|
+
},
|
|
24
|
+
fieldKeys: [
|
|
25
|
+
'id',
|
|
26
|
+
'tenantId',
|
|
27
|
+
'userId',
|
|
28
|
+
'date',
|
|
29
|
+
],
|
|
30
|
+
createGuard,
|
|
31
|
+
guard,
|
|
32
|
+
});
|
|
@@ -7,6 +7,7 @@ export * from './application.js';
|
|
|
7
7
|
export * from './applications-role.js';
|
|
8
8
|
export * from './connector.js';
|
|
9
9
|
export * from './custom-phrase.js';
|
|
10
|
+
export * from './daily-active-user.js';
|
|
10
11
|
export * from './domain.js';
|
|
11
12
|
export * from './hook.js';
|
|
12
13
|
export * from './log.js';
|
package/lib/db-entries/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export * from './application.js';
|
|
|
8
8
|
export * from './applications-role.js';
|
|
9
9
|
export * from './connector.js';
|
|
10
10
|
export * from './custom-phrase.js';
|
|
11
|
+
export * from './daily-active-user.js';
|
|
11
12
|
export * from './domain.js';
|
|
12
13
|
export * from './hook.js';
|
|
13
14
|
export * from './log.js';
|
|
@@ -65,7 +65,7 @@ export var CustomClientMetadataKey;
|
|
|
65
65
|
CustomClientMetadataKey["RotateRefreshToken"] = "rotateRefreshToken";
|
|
66
66
|
})(CustomClientMetadataKey || (CustomClientMetadataKey = {}));
|
|
67
67
|
export const customClientMetadataGuard = z.object({
|
|
68
|
-
[CustomClientMetadataKey.CorsAllowedOrigins]: z.string().
|
|
68
|
+
[CustomClientMetadataKey.CorsAllowedOrigins]: z.string().min(1).array().optional(),
|
|
69
69
|
[CustomClientMetadataKey.IdTokenTtl]: z.number().optional(),
|
|
70
70
|
[CustomClientMetadataKey.RefreshTokenTtl]: z.number().optional(),
|
|
71
71
|
[CustomClientMetadataKey.RefreshTokenTtlInDays]: z.number().int().min(1).max(90).optional(),
|
package/lib/models/tenants.d.ts
CHANGED
|
@@ -12,9 +12,30 @@ export declare const Tenants: import("@withtyped/server/model").default<"tenants
|
|
|
12
12
|
name: string;
|
|
13
13
|
tag: TenantTag;
|
|
14
14
|
createdAt: Date;
|
|
15
|
-
|
|
15
|
+
isSuspended: boolean;
|
|
16
|
+
}, "name" | "createdAt" | "isSuspended" | "tag", "createdAt">;
|
|
16
17
|
export type TenantModel = InferModelType<typeof Tenants>;
|
|
17
|
-
export
|
|
18
|
+
export declare const tenantInfoGuard: z.ZodObject<z.extendShape<Pick<{
|
|
19
|
+
id: z.ZodType<string, z.ZodTypeDef, string>;
|
|
20
|
+
dbUser: z.ZodType<string | null, z.ZodTypeDef, string | null>;
|
|
21
|
+
dbUserPassword: z.ZodType<string | null, z.ZodTypeDef, string | null>;
|
|
22
|
+
name: z.ZodType<string, z.ZodTypeDef, string>;
|
|
23
|
+
tag: z.ZodType<TenantTag, z.ZodTypeDef, TenantTag>;
|
|
24
|
+
createdAt: z.ZodType<Date, z.ZodTypeDef, Date>;
|
|
25
|
+
isSuspended: z.ZodType<boolean, z.ZodTypeDef, boolean>;
|
|
26
|
+
}, "name" | "id" | "isSuspended" | "tag">, {
|
|
27
|
+
indicator: z.ZodString;
|
|
28
|
+
}>, "strip", z.ZodTypeAny, {
|
|
29
|
+
name: string;
|
|
30
|
+
id: string;
|
|
18
31
|
indicator: string;
|
|
19
|
-
|
|
20
|
-
|
|
32
|
+
isSuspended: boolean;
|
|
33
|
+
tag: TenantTag;
|
|
34
|
+
}, {
|
|
35
|
+
name: string;
|
|
36
|
+
id: string;
|
|
37
|
+
indicator: string;
|
|
38
|
+
isSuspended: boolean;
|
|
39
|
+
tag: TenantTag;
|
|
40
|
+
}>;
|
|
41
|
+
export type TenantInfo = z.infer<typeof tenantInfoGuard>;
|
package/lib/models/tenants.js
CHANGED
|
@@ -6,7 +6,8 @@ export var TenantTag;
|
|
|
6
6
|
TenantTag["Staging"] = "staging";
|
|
7
7
|
TenantTag["Production"] = "production";
|
|
8
8
|
})(TenantTag || (TenantTag = {}));
|
|
9
|
-
export const Tenants = createModel(
|
|
9
|
+
export const Tenants = createModel(
|
|
10
|
+
/* Sql */ `
|
|
10
11
|
/* init_order = 0 */
|
|
11
12
|
create table tenants (
|
|
12
13
|
id varchar(21) not null,
|
|
@@ -15,14 +16,15 @@ export const Tenants = createModel(/* sql */ `
|
|
|
15
16
|
name varchar(128) not null default 'My Project',
|
|
16
17
|
tag varchar(64) not null default '${TenantTag.Development}',
|
|
17
18
|
created_at timestamptz not null default(now()),
|
|
19
|
+
is_suspended boolean not null default false,
|
|
18
20
|
primary key (id),
|
|
19
21
|
constraint tenants__db_user
|
|
20
22
|
unique (db_user)
|
|
21
23
|
);
|
|
22
24
|
/* no_after_each */
|
|
23
|
-
|
|
25
|
+
`, 'public')
|
|
24
26
|
.extend('tag', z.nativeEnum(TenantTag))
|
|
25
27
|
.extend('createdAt', { readonly: true });
|
|
26
28
|
export const tenantInfoGuard = Tenants.guard('model')
|
|
27
|
-
.pick({ id: true, name: true, tag: true })
|
|
29
|
+
.pick({ id: true, name: true, tag: true, isSuspended: true })
|
|
28
30
|
.extend({ indicator: z.string() });
|
package/lib/seeds/cloud-api.d.ts
CHANGED
|
@@ -10,7 +10,11 @@ export declare enum CloudScope {
|
|
|
10
10
|
/** The user can update or delete its own tenants. */
|
|
11
11
|
ManageTenantSelf = "manage:tenant:self",
|
|
12
12
|
SendSms = "send:sms",
|
|
13
|
-
SendEmail = "send:email"
|
|
13
|
+
SendEmail = "send:email",
|
|
14
|
+
/** The user can see and manage affiliates, including create, update, and delete. */
|
|
15
|
+
ManageAffiliate = "manage:affiliate",
|
|
16
|
+
/** The user can create new affiliates and logs. */
|
|
17
|
+
CreateAffiliate = "create:affiliate"
|
|
14
18
|
}
|
|
15
19
|
export declare const createCloudApi: () => readonly [UpdateAdminData, ...CreateScope[]];
|
|
16
20
|
export declare const createTenantApplicationRole: () => Readonly<Role>;
|
package/lib/seeds/cloud-api.js
CHANGED
|
@@ -13,6 +13,10 @@ export var CloudScope;
|
|
|
13
13
|
CloudScope["ManageTenantSelf"] = "manage:tenant:self";
|
|
14
14
|
CloudScope["SendSms"] = "send:sms";
|
|
15
15
|
CloudScope["SendEmail"] = "send:email";
|
|
16
|
+
/** The user can see and manage affiliates, including create, update, and delete. */
|
|
17
|
+
CloudScope["ManageAffiliate"] = "manage:affiliate";
|
|
18
|
+
/** The user can create new affiliates and logs. */
|
|
19
|
+
CloudScope["CreateAffiliate"] = "create:affiliate";
|
|
16
20
|
})(CloudScope || (CloudScope = {}));
|
|
17
21
|
export const createCloudApi = () => {
|
|
18
22
|
const resourceId = generateStandardId();
|
|
@@ -43,6 +47,8 @@ export const createCloudApi = () => {
|
|
|
43
47
|
buildScope(CloudScope.ManageTenant, 'Allow managing existing tenants, including create without limitation, update, and delete.'),
|
|
44
48
|
buildScope(CloudScope.SendEmail, 'Allow sending emails. This scope is only available to M2M application.'),
|
|
45
49
|
buildScope(CloudScope.SendSms, 'Allow sending SMS. This scope is only available to M2M application.'),
|
|
50
|
+
buildScope(CloudScope.CreateAffiliate, 'Allow creating new affiliates and logs.'),
|
|
51
|
+
buildScope(CloudScope.ManageAffiliate, 'Allow managing affiliates, including create, update, and delete.'),
|
|
46
52
|
]);
|
|
47
53
|
};
|
|
48
54
|
export const createTenantApplicationRole = () => ({
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import type { AdminConsoleData } from '../types/index.js';
|
|
1
|
+
import type { AdminConsoleData, CloudConnectionData } from '../types/index.js';
|
|
2
2
|
import { LogtoTenantConfigKey } from '../types/index.js';
|
|
3
3
|
export declare const createDefaultAdminConsoleConfig: (forTenantId: string) => Readonly<{
|
|
4
4
|
tenantId: string;
|
|
5
5
|
key: LogtoTenantConfigKey;
|
|
6
6
|
value: AdminConsoleData;
|
|
7
7
|
}>;
|
|
8
|
+
export declare const createCloudConnectionConfig: (forTenantId: string, appId: string, appSecret: string) => Readonly<{
|
|
9
|
+
tenantId: string;
|
|
10
|
+
key: LogtoTenantConfigKey;
|
|
11
|
+
value: CloudConnectionData;
|
|
12
|
+
}>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { LogtoTenantConfigKey } from '../types/index.js';
|
|
2
|
+
import { cloudApiIndicator } from './cloud-api.js';
|
|
2
3
|
export const createDefaultAdminConsoleConfig = (forTenantId) => Object.freeze({
|
|
3
4
|
tenantId: forTenantId,
|
|
4
5
|
key: LogtoTenantConfigKey.AdminConsole,
|
|
@@ -13,3 +14,12 @@ export const createDefaultAdminConsoleConfig = (forTenantId) => Object.freeze({
|
|
|
13
14
|
m2mApplicationCreated: false,
|
|
14
15
|
},
|
|
15
16
|
});
|
|
17
|
+
export const createCloudConnectionConfig = (forTenantId, appId, appSecret) => Object.freeze({
|
|
18
|
+
tenantId: forTenantId,
|
|
19
|
+
key: LogtoTenantConfigKey.CloudConnection,
|
|
20
|
+
value: {
|
|
21
|
+
appId,
|
|
22
|
+
appSecret,
|
|
23
|
+
resource: cloudApiIndicator,
|
|
24
|
+
},
|
|
25
|
+
});
|
package/lib/types/connector.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export declare const connectorResponseGuard: z.ZodObject<z.extendShape<z.extendS
|
|
|
9
9
|
connectorId: z.ZodType<string, z.ZodTypeDef, string>;
|
|
10
10
|
config: z.ZodType<import("@withtyped/server").JsonObject, z.ZodTypeDef, import("@withtyped/server").JsonObject>;
|
|
11
11
|
metadata: z.ZodType<{
|
|
12
|
+
[x: string]: unknown;
|
|
12
13
|
target?: string | undefined;
|
|
13
14
|
name?: ({
|
|
14
15
|
en: string;
|
|
@@ -139,6 +140,7 @@ export declare const connectorResponseGuard: z.ZodObject<z.extendShape<z.extendS
|
|
|
139
140
|
logo?: string | undefined;
|
|
140
141
|
logoDark?: string | null | undefined;
|
|
141
142
|
}, z.ZodTypeDef, {
|
|
143
|
+
[x: string]: unknown;
|
|
142
144
|
target?: string | undefined;
|
|
143
145
|
name?: ({
|
|
144
146
|
en: string;
|
|
@@ -900,6 +902,7 @@ export declare const connectorResponseGuard: z.ZodObject<z.extendShape<z.extendS
|
|
|
900
902
|
type: z.ZodNativeEnum<typeof ConnectorType>;
|
|
901
903
|
isDemo: z.ZodOptional<z.ZodBoolean>;
|
|
902
904
|
extraInfo: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
905
|
+
usage: z.ZodOptional<z.ZodNumber>;
|
|
903
906
|
}>, "strip", z.ZodTypeAny, {
|
|
904
907
|
isStandard?: boolean | undefined;
|
|
905
908
|
configTemplate?: string | undefined;
|
|
@@ -938,6 +941,7 @@ export declare const connectorResponseGuard: z.ZodObject<z.extendShape<z.extendS
|
|
|
938
941
|
})[] | undefined;
|
|
939
942
|
isDemo?: boolean | undefined;
|
|
940
943
|
extraInfo?: Record<string, unknown> | undefined;
|
|
944
|
+
usage?: number | undefined;
|
|
941
945
|
type: ConnectorType;
|
|
942
946
|
name: {
|
|
943
947
|
en: string;
|
|
@@ -1196,6 +1200,7 @@ export declare const connectorResponseGuard: z.ZodObject<z.extendShape<z.extendS
|
|
|
1196
1200
|
connectorId: string;
|
|
1197
1201
|
config: import("@withtyped/server").JsonObject;
|
|
1198
1202
|
metadata: {
|
|
1203
|
+
[x: string]: unknown;
|
|
1199
1204
|
target?: string | undefined;
|
|
1200
1205
|
name?: ({
|
|
1201
1206
|
en: string;
|
|
@@ -1327,9 +1332,9 @@ export declare const connectorResponseGuard: z.ZodObject<z.extendShape<z.extendS
|
|
|
1327
1332
|
logoDark?: string | null | undefined;
|
|
1328
1333
|
};
|
|
1329
1334
|
target: string;
|
|
1335
|
+
platform: import("@logto/connector-kit").ConnectorPlatform | null;
|
|
1330
1336
|
logo: string;
|
|
1331
1337
|
logoDark: string | null;
|
|
1332
|
-
platform: import("@logto/connector-kit").ConnectorPlatform | null;
|
|
1333
1338
|
readme: string;
|
|
1334
1339
|
}, {
|
|
1335
1340
|
isStandard?: boolean | undefined;
|
|
@@ -1369,6 +1374,7 @@ export declare const connectorResponseGuard: z.ZodObject<z.extendShape<z.extendS
|
|
|
1369
1374
|
})[] | undefined;
|
|
1370
1375
|
isDemo?: boolean | undefined;
|
|
1371
1376
|
extraInfo?: Record<string, unknown> | undefined;
|
|
1377
|
+
usage?: number | undefined;
|
|
1372
1378
|
type: ConnectorType;
|
|
1373
1379
|
name: {
|
|
1374
1380
|
en: string;
|
|
@@ -1627,6 +1633,7 @@ export declare const connectorResponseGuard: z.ZodObject<z.extendShape<z.extendS
|
|
|
1627
1633
|
connectorId: string;
|
|
1628
1634
|
config: import("@withtyped/server").JsonObject;
|
|
1629
1635
|
metadata: {
|
|
1636
|
+
[x: string]: unknown;
|
|
1630
1637
|
target?: string | undefined;
|
|
1631
1638
|
name?: ({
|
|
1632
1639
|
en: string;
|
|
@@ -1758,9 +1765,9 @@ export declare const connectorResponseGuard: z.ZodObject<z.extendShape<z.extendS
|
|
|
1758
1765
|
logoDark?: string | null | undefined;
|
|
1759
1766
|
};
|
|
1760
1767
|
target: string;
|
|
1768
|
+
platform: import("@logto/connector-kit").ConnectorPlatform | null;
|
|
1761
1769
|
logo: string;
|
|
1762
1770
|
logoDark: string | null;
|
|
1763
|
-
platform: import("@logto/connector-kit").ConnectorPlatform | null;
|
|
1764
1771
|
readme: string;
|
|
1765
1772
|
}>;
|
|
1766
1773
|
export type ConnectorResponse = z.infer<typeof connectorResponseGuard>;
|
|
@@ -2393,7 +2400,8 @@ export declare const connectorFactoryResponseGuard: z.ZodObject<z.extendShape<{
|
|
|
2393
2400
|
key: string;
|
|
2394
2401
|
label: string;
|
|
2395
2402
|
}>]>, "many">>;
|
|
2396
|
-
}>, "strip", z.
|
|
2403
|
+
}>, "strip", z.ZodUnknown, {
|
|
2404
|
+
[x: string]: unknown;
|
|
2397
2405
|
isStandard?: boolean | undefined;
|
|
2398
2406
|
configTemplate?: string | undefined;
|
|
2399
2407
|
formItems?: ({
|
|
@@ -2685,11 +2693,12 @@ export declare const connectorFactoryResponseGuard: z.ZodObject<z.extendShape<{
|
|
|
2685
2693
|
"zz-TR"?: string | undefined;
|
|
2686
2694
|
};
|
|
2687
2695
|
target: string;
|
|
2696
|
+
platform: import("@logto/connector-kit").ConnectorPlatform | null;
|
|
2688
2697
|
logo: string;
|
|
2689
2698
|
logoDark: string | null;
|
|
2690
|
-
platform: import("@logto/connector-kit").ConnectorPlatform | null;
|
|
2691
2699
|
readme: string;
|
|
2692
2700
|
}, {
|
|
2701
|
+
[x: string]: unknown;
|
|
2693
2702
|
isStandard?: boolean | undefined;
|
|
2694
2703
|
configTemplate?: string | undefined;
|
|
2695
2704
|
formItems?: ({
|
|
@@ -2981,9 +2990,9 @@ export declare const connectorFactoryResponseGuard: z.ZodObject<z.extendShape<{
|
|
|
2981
2990
|
"zz-TR"?: string | undefined;
|
|
2982
2991
|
};
|
|
2983
2992
|
target: string;
|
|
2993
|
+
platform: import("@logto/connector-kit").ConnectorPlatform | null;
|
|
2984
2994
|
logo: string;
|
|
2985
2995
|
logoDark: string | null;
|
|
2986
|
-
platform: import("@logto/connector-kit").ConnectorPlatform | null;
|
|
2987
2996
|
readme: string;
|
|
2988
2997
|
}>;
|
|
2989
2998
|
export type ConnectorFactoryResponse = z.infer<typeof connectorFactoryResponseGuard>;
|
package/lib/types/connector.js
CHANGED
|
@@ -15,6 +15,7 @@ export const connectorResponseGuard = Connectors.guard
|
|
|
15
15
|
type: z.nativeEnum(ConnectorType),
|
|
16
16
|
isDemo: z.boolean().optional(),
|
|
17
17
|
extraInfo: z.record(z.unknown()).optional(),
|
|
18
|
+
usage: z.number().optional(),
|
|
18
19
|
}));
|
|
19
20
|
export const connectorFactoryResponseGuard = z
|
|
20
21
|
.object({
|
package/lib/types/log/index.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export * as token from './token.js';
|
|
|
6
6
|
export * as hook from './hook.js';
|
|
7
7
|
/** Fallback for empty or unrecognized log keys. */
|
|
8
8
|
export declare const LogKeyUnknown = "Unknown";
|
|
9
|
+
export type AuditLogKey = typeof LogKeyUnknown | interaction.LogKey | token.LogKey;
|
|
10
|
+
export type WebhookLogKey = hook.LogKey;
|
|
9
11
|
/**
|
|
10
12
|
* The union type of all available log keys.
|
|
11
13
|
* Note duplicate keys are allowed but should be avoided.
|
|
@@ -13,4 +15,4 @@ export declare const LogKeyUnknown = "Unknown";
|
|
|
13
15
|
* @see {@link interaction.LogKey} for interaction log keys.
|
|
14
16
|
* @see {@link token.LogKey} for token log keys.
|
|
15
17
|
**/
|
|
16
|
-
export type LogKey =
|
|
18
|
+
export type LogKey = AuditLogKey | WebhookLogKey;
|
|
@@ -40,13 +40,29 @@ export declare const adminConsoleDataGuard: z.ZodObject<{
|
|
|
40
40
|
m2mApplicationCreated: boolean;
|
|
41
41
|
}>;
|
|
42
42
|
export type AdminConsoleData = z.infer<typeof adminConsoleDataGuard>;
|
|
43
|
+
export declare const cloudConnectionDataGuard: z.ZodObject<{
|
|
44
|
+
appId: z.ZodString;
|
|
45
|
+
appSecret: z.ZodString;
|
|
46
|
+
resource: z.ZodString;
|
|
47
|
+
}, "strip", z.ZodTypeAny, {
|
|
48
|
+
resource: string;
|
|
49
|
+
appId: string;
|
|
50
|
+
appSecret: string;
|
|
51
|
+
}, {
|
|
52
|
+
resource: string;
|
|
53
|
+
appId: string;
|
|
54
|
+
appSecret: string;
|
|
55
|
+
}>;
|
|
56
|
+
export type CloudConnectionData = z.infer<typeof cloudConnectionDataGuard>;
|
|
43
57
|
export declare enum LogtoTenantConfigKey {
|
|
44
58
|
AdminConsole = "adminConsole",
|
|
59
|
+
CloudConnection = "cloudConnection",
|
|
45
60
|
/** The URL to redirect when session not found in Sign-in Experience. */
|
|
46
61
|
SessionNotFoundRedirectUrl = "sessionNotFoundRedirectUrl"
|
|
47
62
|
}
|
|
48
63
|
export type LogtoTenantConfigType = {
|
|
49
64
|
[LogtoTenantConfigKey.AdminConsole]: AdminConsoleData;
|
|
65
|
+
[LogtoTenantConfigKey.CloudConnection]: CloudConnectionData;
|
|
50
66
|
[LogtoTenantConfigKey.SessionNotFoundRedirectUrl]: {
|
|
51
67
|
url: string;
|
|
52
68
|
};
|
|
@@ -21,14 +21,22 @@ export const adminConsoleDataGuard = z.object({
|
|
|
21
21
|
roleCreated: z.boolean(),
|
|
22
22
|
m2mApplicationCreated: z.boolean(),
|
|
23
23
|
});
|
|
24
|
+
/* --- Logto tenant cloud connection config --- */
|
|
25
|
+
export const cloudConnectionDataGuard = z.object({
|
|
26
|
+
appId: z.string(),
|
|
27
|
+
appSecret: z.string(),
|
|
28
|
+
resource: z.string(),
|
|
29
|
+
});
|
|
24
30
|
export var LogtoTenantConfigKey;
|
|
25
31
|
(function (LogtoTenantConfigKey) {
|
|
26
32
|
LogtoTenantConfigKey["AdminConsole"] = "adminConsole";
|
|
33
|
+
LogtoTenantConfigKey["CloudConnection"] = "cloudConnection";
|
|
27
34
|
/** The URL to redirect when session not found in Sign-in Experience. */
|
|
28
35
|
LogtoTenantConfigKey["SessionNotFoundRedirectUrl"] = "sessionNotFoundRedirectUrl";
|
|
29
36
|
})(LogtoTenantConfigKey || (LogtoTenantConfigKey = {}));
|
|
30
37
|
export const logtoTenantConfigGuard = Object.freeze({
|
|
31
38
|
[LogtoTenantConfigKey.AdminConsole]: adminConsoleDataGuard,
|
|
39
|
+
[LogtoTenantConfigKey.CloudConnection]: cloudConnectionDataGuard,
|
|
32
40
|
[LogtoTenantConfigKey.SessionNotFoundRedirectUrl]: z.object({ url: z.string() }),
|
|
33
41
|
});
|
|
34
42
|
export const logtoConfigKeys = Object.freeze([
|
package/lib/types/system.d.ts
CHANGED
|
@@ -67,87 +67,48 @@ export declare const storageProviderGuard: Readonly<{
|
|
|
67
67
|
export declare enum EmailServiceProvider {
|
|
68
68
|
SendGrid = "SendGrid"
|
|
69
69
|
}
|
|
70
|
-
/**
|
|
71
|
-
* `General` is now used as a fallback scenario.
|
|
72
|
-
* This will be extended in the future since we will send different emails for
|
|
73
|
-
* different purposes (such as webhook that inform users of suspicious account activities).
|
|
74
|
-
*/
|
|
75
|
-
export declare enum OtherEmailTemplate {
|
|
76
|
-
General = "General"
|
|
77
|
-
}
|
|
78
|
-
export declare const otherEmailTemplateGuard: z.ZodNativeEnum<typeof OtherEmailTemplate>;
|
|
79
70
|
export declare const sendgridEmailServiceDataGuard: z.ZodObject<{
|
|
80
|
-
fromName: z.ZodString;
|
|
81
|
-
fromEmail: z.ZodString;
|
|
82
|
-
templates: z.ZodRecord<z.ZodUnion<[z.ZodNativeEnum<typeof import("@logto/connector-kit").VerificationCodeType>, z.ZodNativeEnum<typeof OtherEmailTemplate>]>, z.ZodObject<{
|
|
83
|
-
subject: z.ZodString;
|
|
84
|
-
content: z.ZodString;
|
|
85
|
-
}, "strip", z.ZodTypeAny, {
|
|
86
|
-
subject: string;
|
|
87
|
-
content: string;
|
|
88
|
-
}, {
|
|
89
|
-
subject: string;
|
|
90
|
-
content: string;
|
|
91
|
-
}>>;
|
|
92
71
|
provider: z.ZodLiteral<EmailServiceProvider>;
|
|
93
72
|
apiKey: z.ZodString;
|
|
73
|
+
templateId: z.ZodString;
|
|
74
|
+
fromName: z.ZodString;
|
|
75
|
+
fromEmail: z.ZodString;
|
|
94
76
|
}, "strip", z.ZodTypeAny, {
|
|
95
77
|
provider: EmailServiceProvider;
|
|
96
78
|
apiKey: string;
|
|
79
|
+
templateId: string;
|
|
97
80
|
fromName: string;
|
|
98
81
|
fromEmail: string;
|
|
99
|
-
templates: Partial<Record<OtherEmailTemplate.General | import("@logto/connector-kit").VerificationCodeType, {
|
|
100
|
-
subject: string;
|
|
101
|
-
content: string;
|
|
102
|
-
}>>;
|
|
103
82
|
}, {
|
|
104
83
|
provider: EmailServiceProvider;
|
|
105
84
|
apiKey: string;
|
|
85
|
+
templateId: string;
|
|
106
86
|
fromName: string;
|
|
107
87
|
fromEmail: string;
|
|
108
|
-
templates: Partial<Record<OtherEmailTemplate.General | import("@logto/connector-kit").VerificationCodeType, {
|
|
109
|
-
subject: string;
|
|
110
|
-
content: string;
|
|
111
|
-
}>>;
|
|
112
88
|
}>;
|
|
113
89
|
export type SendgridEmailServiceData = z.infer<typeof sendgridEmailServiceDataGuard>;
|
|
114
90
|
export declare const emailServiceDataGuard: z.ZodDiscriminatedUnion<"provider", [z.ZodObject<{
|
|
115
|
-
fromName: z.ZodString;
|
|
116
|
-
fromEmail: z.ZodString;
|
|
117
|
-
templates: z.ZodRecord<z.ZodUnion<[z.ZodNativeEnum<typeof import("@logto/connector-kit").VerificationCodeType>, z.ZodNativeEnum<typeof OtherEmailTemplate>]>, z.ZodObject<{
|
|
118
|
-
subject: z.ZodString;
|
|
119
|
-
content: z.ZodString;
|
|
120
|
-
}, "strip", z.ZodTypeAny, {
|
|
121
|
-
subject: string;
|
|
122
|
-
content: string;
|
|
123
|
-
}, {
|
|
124
|
-
subject: string;
|
|
125
|
-
content: string;
|
|
126
|
-
}>>;
|
|
127
91
|
provider: z.ZodLiteral<EmailServiceProvider>;
|
|
128
92
|
apiKey: z.ZodString;
|
|
93
|
+
templateId: z.ZodString;
|
|
94
|
+
fromName: z.ZodString;
|
|
95
|
+
fromEmail: z.ZodString;
|
|
129
96
|
}, "strip", z.ZodTypeAny, {
|
|
130
97
|
provider: EmailServiceProvider;
|
|
131
98
|
apiKey: string;
|
|
99
|
+
templateId: string;
|
|
132
100
|
fromName: string;
|
|
133
101
|
fromEmail: string;
|
|
134
|
-
templates: Partial<Record<OtherEmailTemplate.General | import("@logto/connector-kit").VerificationCodeType, {
|
|
135
|
-
subject: string;
|
|
136
|
-
content: string;
|
|
137
|
-
}>>;
|
|
138
102
|
}, {
|
|
139
103
|
provider: EmailServiceProvider;
|
|
140
104
|
apiKey: string;
|
|
105
|
+
templateId: string;
|
|
141
106
|
fromName: string;
|
|
142
107
|
fromEmail: string;
|
|
143
|
-
templates: Partial<Record<OtherEmailTemplate.General | import("@logto/connector-kit").VerificationCodeType, {
|
|
144
|
-
subject: string;
|
|
145
|
-
content: string;
|
|
146
|
-
}>>;
|
|
147
108
|
}>]>;
|
|
148
109
|
export type EmailServiceData = z.infer<typeof emailServiceDataGuard>;
|
|
149
110
|
export declare enum EmailServiceProviderKey {
|
|
150
|
-
EmailServiceProvider = "
|
|
111
|
+
EmailServiceProvider = "emailServiceProvider"
|
|
151
112
|
}
|
|
152
113
|
export type EmailServiceProviderType = {
|
|
153
114
|
[EmailServiceProviderKey.EmailServiceProvider]: EmailServiceData;
|
package/lib/types/system.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { verificationCodeTypeGuard } from '@logto/connector-kit';
|
|
2
1
|
import { z } from 'zod';
|
|
3
2
|
// Alteration state
|
|
4
3
|
export var AlterationStateKey;
|
|
@@ -48,35 +47,19 @@ export var EmailServiceProvider;
|
|
|
48
47
|
(function (EmailServiceProvider) {
|
|
49
48
|
EmailServiceProvider["SendGrid"] = "SendGrid";
|
|
50
49
|
})(EmailServiceProvider || (EmailServiceProvider = {}));
|
|
51
|
-
/**
|
|
52
|
-
* `General` is now used as a fallback scenario.
|
|
53
|
-
* This will be extended in the future since we will send different emails for
|
|
54
|
-
* different purposes (such as webhook that inform users of suspicious account activities).
|
|
55
|
-
*/
|
|
56
|
-
export var OtherEmailTemplate;
|
|
57
|
-
(function (OtherEmailTemplate) {
|
|
58
|
-
OtherEmailTemplate["General"] = "General";
|
|
59
|
-
})(OtherEmailTemplate || (OtherEmailTemplate = {}));
|
|
60
|
-
export const otherEmailTemplateGuard = z.nativeEnum(OtherEmailTemplate);
|
|
61
|
-
const emailServiceBasicConfig = {
|
|
62
|
-
fromName: z.string(),
|
|
63
|
-
fromEmail: z.string(),
|
|
64
|
-
templates: z.record(verificationCodeTypeGuard.or(otherEmailTemplateGuard), z.object({
|
|
65
|
-
subject: z.string(),
|
|
66
|
-
content: z.string(),
|
|
67
|
-
})),
|
|
68
|
-
};
|
|
69
50
|
export const sendgridEmailServiceDataGuard = z.object({
|
|
70
51
|
provider: z.literal(EmailServiceProvider.SendGrid),
|
|
71
52
|
apiKey: z.string(),
|
|
72
|
-
|
|
53
|
+
templateId: z.string(),
|
|
54
|
+
fromName: z.string(),
|
|
55
|
+
fromEmail: z.string(),
|
|
73
56
|
});
|
|
74
57
|
export const emailServiceDataGuard = z.discriminatedUnion('provider', [
|
|
75
58
|
sendgridEmailServiceDataGuard,
|
|
76
59
|
]);
|
|
77
60
|
export var EmailServiceProviderKey;
|
|
78
61
|
(function (EmailServiceProviderKey) {
|
|
79
|
-
EmailServiceProviderKey["EmailServiceProvider"] = "
|
|
62
|
+
EmailServiceProviderKey["EmailServiceProvider"] = "emailServiceProvider";
|
|
80
63
|
})(EmailServiceProviderKey || (EmailServiceProviderKey = {}));
|
|
81
64
|
export const emailServiceProviderGuard = Object.freeze({
|
|
82
65
|
[EmailServiceProviderKey.EmailServiceProvider]: emailServiceDataGuard,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logto/schemas",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"author": "Silverhand Inc. <contact@silverhand.io>",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -24,20 +24,20 @@
|
|
|
24
24
|
"node": "^18.12.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@silverhand/eslint-config": "
|
|
27
|
+
"@silverhand/eslint-config": "4.0.1",
|
|
28
28
|
"@silverhand/essentials": "^2.5.0",
|
|
29
|
-
"@silverhand/ts-config": "
|
|
29
|
+
"@silverhand/ts-config": "4.0.0",
|
|
30
30
|
"@types/inquirer": "^9.0.0",
|
|
31
31
|
"@types/jest": "^29.4.0",
|
|
32
32
|
"@types/node": "^18.11.18",
|
|
33
33
|
"@types/pluralize": "^0.0.29",
|
|
34
|
-
"camelcase": "^
|
|
34
|
+
"camelcase": "^8.0.0",
|
|
35
35
|
"chalk": "^5.0.0",
|
|
36
|
-
"eslint": "^8.
|
|
36
|
+
"eslint": "^8.44.0",
|
|
37
37
|
"jest": "^29.5.0",
|
|
38
|
-
"lint-staged": "^
|
|
38
|
+
"lint-staged": "^14.0.0",
|
|
39
39
|
"pluralize": "^8.0.0",
|
|
40
|
-
"prettier": "^
|
|
40
|
+
"prettier": "^3.0.0",
|
|
41
41
|
"roarr": "^7.11.0",
|
|
42
42
|
"slonik": "^30.0.0",
|
|
43
43
|
"slonik-sql-tag-raw": "^1.1.4",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"@logto/phrases": "^1.4.1",
|
|
71
71
|
"@logto/phrases-ui": "^1.2.0",
|
|
72
72
|
"@logto/shared": "^2.0.0",
|
|
73
|
-
"@withtyped/server": "^0.
|
|
73
|
+
"@withtyped/server": "^0.12.9"
|
|
74
74
|
},
|
|
75
75
|
"peerDependencies": {
|
|
76
76
|
"zod": "^3.20.2"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
create table daily_active_users (
|
|
2
|
+
id varchar(21) not null,
|
|
3
|
+
tenant_id varchar(21) not null
|
|
4
|
+
references tenants (id) on update cascade on delete cascade,
|
|
5
|
+
user_id varchar(21) not null,
|
|
6
|
+
date timestamptz not null,
|
|
7
|
+
primary key (id),
|
|
8
|
+
constraint daily_active_users__user_id_date
|
|
9
|
+
unique (user_id, date)
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
create index daily_active_users__id
|
|
13
|
+
on daily_active_users (tenant_id, id);
|