@logto/schemas 1.34.0 → 1.35.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.35.0-1764580455-remove-daily-active-users-foreign-key.ts +69 -0
- package/alterations/1.35.0-1764580589-create-aggregated-daily-active-users-table.ts +51 -0
- package/alterations/1.35.0-1764653048-update-daily-token-usage-mau-support.ts +37 -0
- package/alterations/1.35.0-1765183934-add-logs-created-at-id-index.ts +39 -0
- package/alterations/1.35.0-1765255453-update-saml-session-relay-state-to-varchar-512.ts +31 -0
- package/alterations/1.35.0-1765631949-drop-redundant-logs-id-index.ts +47 -0
- package/alterations/1.35.0-1766028646-grant-tenants-table-tag-column-read-permission.ts +39 -0
- package/alterations-js/1.35.0-1764580455-remove-daily-active-users-foreign-key.js +57 -0
- package/alterations-js/1.35.0-1764580589-create-aggregated-daily-active-users-table.js +40 -0
- package/alterations-js/1.35.0-1764653048-update-daily-token-usage-mau-support.js +31 -0
- package/alterations-js/1.35.0-1765183934-add-logs-created-at-id-index.js +35 -0
- package/alterations-js/1.35.0-1765255453-update-saml-session-relay-state-to-varchar-512.js +25 -0
- package/alterations-js/1.35.0-1765631949-drop-redundant-logs-id-index.js +43 -0
- package/alterations-js/1.35.0-1766028646-grant-tenants-table-tag-column-read-permission.js +31 -0
- package/lib/consts/product-event.d.ts +0 -12
- package/lib/consts/product-event.js +0 -13
- package/lib/db-entries/aggregated-daily-active-user.d.ts +22 -0
- package/lib/db-entries/aggregated-daily-active-user.js +33 -0
- package/lib/db-entries/daily-token-usage.d.ts +5 -1
- package/lib/db-entries/daily-token-usage.js +8 -0
- package/lib/db-entries/index.d.ts +1 -0
- package/lib/db-entries/index.js +1 -0
- package/lib/db-entries/saml-application-session.js +2 -2
- package/lib/foundations/jsonb-types/captcha.d.ts +16 -0
- package/lib/foundations/jsonb-types/captcha.js +7 -0
- package/lib/types/alteration.d.ts +11 -1
- package/lib/types/interactions.d.ts +14 -5
- package/lib/types/interactions.js +10 -4
- package/lib/types/sign-in-experience.d.ts +13 -1
- package/lib/types/sign-in-experience.js +3 -1
- package/lib/types/user.d.ts +3 -0
- package/lib/types/user.js +1 -0
- package/package.json +4 -4
- package/tables/_after_all.sql +1 -1
- package/tables/aggregated_daily_active_users.sql +16 -0
- package/tables/daily_active_users.sql +9 -4
- package/tables/daily_token_usage.sql +3 -2
- package/tables/logs.sql +3 -3
- package/tables/saml_application_sessions.sql +1 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
|
|
3
|
+
import type { AlterationScript } from '../lib/types/alteration.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Remove foreign key constraint from daily_active_users table to allow
|
|
7
|
+
* historical billing data to persist even after tenant deletion.
|
|
8
|
+
* This supports the MAU-based billing system requirements.
|
|
9
|
+
*
|
|
10
|
+
* Index optimizations:
|
|
11
|
+
* 1. Removes redundant (tenant_id, id) index (id is already primary key)
|
|
12
|
+
* 2. Adds optimized index (tenant_id, date, user_id) for aggregation queries
|
|
13
|
+
* 3. Replaces problematic partial index with BRIN index
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const alteration: AlterationScript = {
|
|
17
|
+
up: async (pool) => {
|
|
18
|
+
// Drop the existing foreign key constraint
|
|
19
|
+
await pool.query(sql`
|
|
20
|
+
alter table daily_active_users
|
|
21
|
+
drop constraint if exists daily_active_users_tenant_id_fkey
|
|
22
|
+
`);
|
|
23
|
+
|
|
24
|
+
// Remove the redundant (tenant_id, id) index since id is already primary key
|
|
25
|
+
await pool.query(sql`
|
|
26
|
+
drop index if exists daily_active_users__id
|
|
27
|
+
`);
|
|
28
|
+
|
|
29
|
+
// Add optimized index for aggregation queries with better write performance
|
|
30
|
+
await pool.query(sql`
|
|
31
|
+
create index daily_active_users__tenant_date_user
|
|
32
|
+
on daily_active_users (tenant_id, date, user_id)
|
|
33
|
+
`);
|
|
34
|
+
|
|
35
|
+
// Add BRIN index for time-series date range queries
|
|
36
|
+
// Optimized for sequential data insertion and range scans (date >= ?)
|
|
37
|
+
await pool.query(sql`
|
|
38
|
+
create index daily_active_users__date_brin
|
|
39
|
+
on daily_active_users using brin (date)
|
|
40
|
+
`);
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
down: async (pool) => {
|
|
44
|
+
// Drop the new indexes we created
|
|
45
|
+
await pool.query(sql`
|
|
46
|
+
drop index if exists daily_active_users__date_brin
|
|
47
|
+
`);
|
|
48
|
+
|
|
49
|
+
await pool.query(sql`
|
|
50
|
+
drop index if exists daily_active_users__tenant_date_user
|
|
51
|
+
`);
|
|
52
|
+
|
|
53
|
+
// Recreate the original redundant index
|
|
54
|
+
await pool.query(sql`
|
|
55
|
+
create index daily_active_users__id
|
|
56
|
+
on daily_active_users (tenant_id, id)
|
|
57
|
+
`);
|
|
58
|
+
|
|
59
|
+
// Recreate the foreign key constraint
|
|
60
|
+
await pool.query(sql`
|
|
61
|
+
alter table daily_active_users
|
|
62
|
+
add constraint daily_active_users_tenant_id_fkey
|
|
63
|
+
foreign key (tenant_id) references tenants(id)
|
|
64
|
+
on update cascade on delete cascade
|
|
65
|
+
`);
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export default alteration;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
|
|
3
|
+
import type { AlterationScript } from '../lib/types/alteration.js';
|
|
4
|
+
|
|
5
|
+
import { applyTableRls, dropTableRls } from './utils/1704934999-tables.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Create aggregated_daily_active_users table for MAU-based billing system.
|
|
9
|
+
* This table consolidates daily user activities for efficient billing calculations.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const alteration: AlterationScript = {
|
|
13
|
+
up: async (pool) => {
|
|
14
|
+
// Create the aggregated daily active users table
|
|
15
|
+
await pool.query(sql`
|
|
16
|
+
create table aggregated_daily_active_users (
|
|
17
|
+
tenant_id varchar(21) not null,
|
|
18
|
+
activity_date date not null,
|
|
19
|
+
user_id varchar(21) not null,
|
|
20
|
+
activity_count integer not null,
|
|
21
|
+
primary key (tenant_id, activity_date, user_id)
|
|
22
|
+
);
|
|
23
|
+
`);
|
|
24
|
+
|
|
25
|
+
// Index for billing cycle range queries
|
|
26
|
+
await pool.query(sql`
|
|
27
|
+
create index aggregated_daily_active_users__tenant_date
|
|
28
|
+
on aggregated_daily_active_users (tenant_id, activity_date);
|
|
29
|
+
`);
|
|
30
|
+
|
|
31
|
+
// Index for tenant-specific user activity queries
|
|
32
|
+
await pool.query(sql`
|
|
33
|
+
create index aggregated_daily_active_users__tenant_user_date
|
|
34
|
+
on aggregated_daily_active_users (tenant_id, user_id, activity_date desc);
|
|
35
|
+
`);
|
|
36
|
+
|
|
37
|
+
await applyTableRls(pool, 'aggregated_daily_active_users');
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
down: async (pool) => {
|
|
41
|
+
// Drop RLS policies first
|
|
42
|
+
await dropTableRls(pool, 'aggregated_daily_active_users');
|
|
43
|
+
|
|
44
|
+
// Drop the table and all its indexes
|
|
45
|
+
await pool.query(sql`
|
|
46
|
+
drop table aggregated_daily_active_users;
|
|
47
|
+
`);
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
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
|
+
// Add new columns for user and m2m token usage tracking
|
|
8
|
+
await pool.query(sql`
|
|
9
|
+
alter table daily_token_usage
|
|
10
|
+
add column user_token_usage bigint not null default 0,
|
|
11
|
+
add column m2m_token_usage bigint not null default 0;
|
|
12
|
+
`);
|
|
13
|
+
|
|
14
|
+
// Remove foreign key constraint to support enterprise tenant deletion requirements
|
|
15
|
+
await pool.query(sql`
|
|
16
|
+
alter table daily_token_usage
|
|
17
|
+
drop constraint if exists daily_token_usage_tenant_id_fkey;
|
|
18
|
+
`);
|
|
19
|
+
},
|
|
20
|
+
down: async (pool) => {
|
|
21
|
+
// Remove the new columns
|
|
22
|
+
await pool.query(sql`
|
|
23
|
+
alter table daily_token_usage
|
|
24
|
+
drop column if exists user_token_usage,
|
|
25
|
+
drop column if exists m2m_token_usage;
|
|
26
|
+
`);
|
|
27
|
+
|
|
28
|
+
// Re-add the foreign key constraint
|
|
29
|
+
await pool.query(sql`
|
|
30
|
+
alter table daily_token_usage
|
|
31
|
+
add constraint daily_token_usage_tenant_id_fkey
|
|
32
|
+
foreign key (tenant_id) references tenants (id) on update cascade on delete cascade;
|
|
33
|
+
`);
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default alteration;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
|
|
3
|
+
import type { AlterationScript } from '../lib/types/alteration.js';
|
|
4
|
+
|
|
5
|
+
const alteration: AlterationScript = {
|
|
6
|
+
beforeUp: async (pool) => {
|
|
7
|
+
/**
|
|
8
|
+
* Use 'if not exists' to ensure idempotency.
|
|
9
|
+
* If the subsequent transaction in 'up' fails, the migration can be safely re-run without failing on an already created index.
|
|
10
|
+
*/
|
|
11
|
+
await pool.query(sql`
|
|
12
|
+
create index concurrently if not exists logs__created_at_id
|
|
13
|
+
on logs (tenant_id, created_at, id);
|
|
14
|
+
`);
|
|
15
|
+
},
|
|
16
|
+
up: async () => {
|
|
17
|
+
/**
|
|
18
|
+
* The index on the logs table must be created outside of a transaction to avoid table locks.
|
|
19
|
+
* 'concurrently' cannot be used inside a transaction, so this up is intentionally left empty.
|
|
20
|
+
*/
|
|
21
|
+
},
|
|
22
|
+
beforeDown: async (pool) => {
|
|
23
|
+
/**
|
|
24
|
+
* Use 'if exists' to ensure idempotency. If the subsequent transaction in 'down' fails,
|
|
25
|
+
* the rollback can be safely re-run without failing on a non-existent index.
|
|
26
|
+
*/
|
|
27
|
+
await pool.query(sql`
|
|
28
|
+
drop index concurrently if exists logs__created_at_id;
|
|
29
|
+
`);
|
|
30
|
+
},
|
|
31
|
+
down: async () => {
|
|
32
|
+
/**
|
|
33
|
+
* The index on the logs table must be dropped outside of a transaction to avoid table locks.
|
|
34
|
+
* 'concurrently' cannot be used inside a transaction, so this down is intentionally left empty.
|
|
35
|
+
*/
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default alteration;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
|
|
3
|
+
import type { AlterationScript } from '../lib/types/alteration.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Update saml_application_sessions.relay_state column type from varchar(256) to text
|
|
7
|
+
* to support longer relay state values that may be used in SAML authentication flows.
|
|
8
|
+
*
|
|
9
|
+
* The relay state parameter in SAML can contain arbitrary data that needs to be
|
|
10
|
+
* preserved and returned to the service provider, and 256 characters may not be
|
|
11
|
+
* sufficient for all use cases.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const alteration: AlterationScript = {
|
|
15
|
+
up: async (pool) => {
|
|
16
|
+
await pool.query(sql`
|
|
17
|
+
alter table saml_application_sessions
|
|
18
|
+
alter column relay_state type varchar(512)
|
|
19
|
+
`);
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
down: async (pool) => {
|
|
23
|
+
// The down migration may fail or cause data loss if any existing relay_state values exceed 256 characters. Consider adding a USING clause to safely truncate values during rollback, this ensures the rollback operation won't fail due to data that's too long for the varchar(256) constraint.
|
|
24
|
+
await pool.query(sql`
|
|
25
|
+
alter table saml_application_sessions
|
|
26
|
+
alter column relay_state type varchar(256) using left(relay_state, 256)
|
|
27
|
+
`);
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default alteration;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
|
|
3
|
+
import type { AlterationScript } from '../lib/types/alteration.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The `logs__id` index on (tenant_id, id) is redundant because:
|
|
7
|
+
* 1. `id` is already the primary key, which can efficiently lookup by id
|
|
8
|
+
* 2. `logs__created_at_id` index on (tenant_id, created_at, id) already covers tenant_id queries
|
|
9
|
+
*
|
|
10
|
+
* Removing this index reduces write IOPS during log deletion operations,
|
|
11
|
+
* as each DELETE no longer needs to update this redundant index.
|
|
12
|
+
*/
|
|
13
|
+
const alteration: AlterationScript = {
|
|
14
|
+
beforeUp: async (pool) => {
|
|
15
|
+
/**
|
|
16
|
+
* Use 'if exists' to ensure idempotency.
|
|
17
|
+
* 'concurrently' avoids table locks during index drop.
|
|
18
|
+
*/
|
|
19
|
+
await pool.query(sql`
|
|
20
|
+
drop index concurrently if exists logs__id;
|
|
21
|
+
`);
|
|
22
|
+
},
|
|
23
|
+
up: async () => {
|
|
24
|
+
/**
|
|
25
|
+
* The index must be dropped outside of a transaction to avoid table locks.
|
|
26
|
+
* 'concurrently' cannot be used inside a transaction, so this up is intentionally left empty.
|
|
27
|
+
*/
|
|
28
|
+
},
|
|
29
|
+
beforeDown: async (pool) => {
|
|
30
|
+
/**
|
|
31
|
+
* Recreate the index if rolling back.
|
|
32
|
+
* Use 'if not exists' to ensure idempotency.
|
|
33
|
+
*/
|
|
34
|
+
await pool.query(sql`
|
|
35
|
+
create index concurrently if not exists logs__id
|
|
36
|
+
on logs (tenant_id, id);
|
|
37
|
+
`);
|
|
38
|
+
},
|
|
39
|
+
down: async () => {
|
|
40
|
+
/**
|
|
41
|
+
* The index must be created outside of a transaction to avoid table locks.
|
|
42
|
+
* 'concurrently' cannot be used inside a transaction, so this down is intentionally left empty.
|
|
43
|
+
*/
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export default alteration;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type CommonQueryMethods, sql } from '@silverhand/slonik';
|
|
2
|
+
|
|
3
|
+
import type { AlterationScript } from '../lib/types/alteration.js';
|
|
4
|
+
|
|
5
|
+
const getDatabaseName = async (pool: CommonQueryMethods) => {
|
|
6
|
+
const { currentDatabase } = await pool.one<{ currentDatabase: string }>(sql`
|
|
7
|
+
select current_database();
|
|
8
|
+
`);
|
|
9
|
+
|
|
10
|
+
return currentDatabase.replaceAll('-', '_');
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Grant read permission to the tag column in the tenants table to the logto_tenant_<databaseName> role.
|
|
15
|
+
*/
|
|
16
|
+
const alteration: AlterationScript = {
|
|
17
|
+
up: async (pool) => {
|
|
18
|
+
const databaseName = await getDatabaseName(pool);
|
|
19
|
+
const baseRoleId = sql.identifier([`logto_tenant_${databaseName}`]);
|
|
20
|
+
|
|
21
|
+
await pool.query(sql`
|
|
22
|
+
grant select (tag)
|
|
23
|
+
on table tenants
|
|
24
|
+
to ${baseRoleId}
|
|
25
|
+
`);
|
|
26
|
+
},
|
|
27
|
+
down: async (pool) => {
|
|
28
|
+
const databaseName = await getDatabaseName(pool);
|
|
29
|
+
const baseRoleId = sql.identifier([`logto_tenant_${databaseName}`]);
|
|
30
|
+
|
|
31
|
+
await pool.query(sql`
|
|
32
|
+
revoke select (tag)
|
|
33
|
+
on table tenants
|
|
34
|
+
from ${baseRoleId}
|
|
35
|
+
`);
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default alteration;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
/**
|
|
3
|
+
* Remove foreign key constraint from daily_active_users table to allow
|
|
4
|
+
* historical billing data to persist even after tenant deletion.
|
|
5
|
+
* This supports the MAU-based billing system requirements.
|
|
6
|
+
*
|
|
7
|
+
* Index optimizations:
|
|
8
|
+
* 1. Removes redundant (tenant_id, id) index (id is already primary key)
|
|
9
|
+
* 2. Adds optimized index (tenant_id, date, user_id) for aggregation queries
|
|
10
|
+
* 3. Replaces problematic partial index with BRIN index
|
|
11
|
+
*/
|
|
12
|
+
const alteration = {
|
|
13
|
+
up: async (pool) => {
|
|
14
|
+
// Drop the existing foreign key constraint
|
|
15
|
+
await pool.query(sql `
|
|
16
|
+
alter table daily_active_users
|
|
17
|
+
drop constraint if exists daily_active_users_tenant_id_fkey
|
|
18
|
+
`);
|
|
19
|
+
// Remove the redundant (tenant_id, id) index since id is already primary key
|
|
20
|
+
await pool.query(sql `
|
|
21
|
+
drop index if exists daily_active_users__id
|
|
22
|
+
`);
|
|
23
|
+
// Add optimized index for aggregation queries with better write performance
|
|
24
|
+
await pool.query(sql `
|
|
25
|
+
create index daily_active_users__tenant_date_user
|
|
26
|
+
on daily_active_users (tenant_id, date, user_id)
|
|
27
|
+
`);
|
|
28
|
+
// Add BRIN index for time-series date range queries
|
|
29
|
+
// Optimized for sequential data insertion and range scans (date >= ?)
|
|
30
|
+
await pool.query(sql `
|
|
31
|
+
create index daily_active_users__date_brin
|
|
32
|
+
on daily_active_users using brin (date)
|
|
33
|
+
`);
|
|
34
|
+
},
|
|
35
|
+
down: async (pool) => {
|
|
36
|
+
// Drop the new indexes we created
|
|
37
|
+
await pool.query(sql `
|
|
38
|
+
drop index if exists daily_active_users__date_brin
|
|
39
|
+
`);
|
|
40
|
+
await pool.query(sql `
|
|
41
|
+
drop index if exists daily_active_users__tenant_date_user
|
|
42
|
+
`);
|
|
43
|
+
// Recreate the original redundant index
|
|
44
|
+
await pool.query(sql `
|
|
45
|
+
create index daily_active_users__id
|
|
46
|
+
on daily_active_users (tenant_id, id)
|
|
47
|
+
`);
|
|
48
|
+
// Recreate the foreign key constraint
|
|
49
|
+
await pool.query(sql `
|
|
50
|
+
alter table daily_active_users
|
|
51
|
+
add constraint daily_active_users_tenant_id_fkey
|
|
52
|
+
foreign key (tenant_id) references tenants(id)
|
|
53
|
+
on update cascade on delete cascade
|
|
54
|
+
`);
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
export default alteration;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
import { applyTableRls, dropTableRls } from './utils/1704934999-tables.js';
|
|
3
|
+
/**
|
|
4
|
+
* Create aggregated_daily_active_users table for MAU-based billing system.
|
|
5
|
+
* This table consolidates daily user activities for efficient billing calculations.
|
|
6
|
+
*/
|
|
7
|
+
const alteration = {
|
|
8
|
+
up: async (pool) => {
|
|
9
|
+
// Create the aggregated daily active users table
|
|
10
|
+
await pool.query(sql `
|
|
11
|
+
create table aggregated_daily_active_users (
|
|
12
|
+
tenant_id varchar(21) not null,
|
|
13
|
+
activity_date date not null,
|
|
14
|
+
user_id varchar(21) not null,
|
|
15
|
+
activity_count integer not null,
|
|
16
|
+
primary key (tenant_id, activity_date, user_id)
|
|
17
|
+
);
|
|
18
|
+
`);
|
|
19
|
+
// Index for billing cycle range queries
|
|
20
|
+
await pool.query(sql `
|
|
21
|
+
create index aggregated_daily_active_users__tenant_date
|
|
22
|
+
on aggregated_daily_active_users (tenant_id, activity_date);
|
|
23
|
+
`);
|
|
24
|
+
// Index for tenant-specific user activity queries
|
|
25
|
+
await pool.query(sql `
|
|
26
|
+
create index aggregated_daily_active_users__tenant_user_date
|
|
27
|
+
on aggregated_daily_active_users (tenant_id, user_id, activity_date desc);
|
|
28
|
+
`);
|
|
29
|
+
await applyTableRls(pool, 'aggregated_daily_active_users');
|
|
30
|
+
},
|
|
31
|
+
down: async (pool) => {
|
|
32
|
+
// Drop RLS policies first
|
|
33
|
+
await dropTableRls(pool, 'aggregated_daily_active_users');
|
|
34
|
+
// Drop the table and all its indexes
|
|
35
|
+
await pool.query(sql `
|
|
36
|
+
drop table aggregated_daily_active_users;
|
|
37
|
+
`);
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
export default alteration;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
const alteration = {
|
|
3
|
+
up: async (pool) => {
|
|
4
|
+
// Add new columns for user and m2m token usage tracking
|
|
5
|
+
await pool.query(sql `
|
|
6
|
+
alter table daily_token_usage
|
|
7
|
+
add column user_token_usage bigint not null default 0,
|
|
8
|
+
add column m2m_token_usage bigint not null default 0;
|
|
9
|
+
`);
|
|
10
|
+
// Remove foreign key constraint to support enterprise tenant deletion requirements
|
|
11
|
+
await pool.query(sql `
|
|
12
|
+
alter table daily_token_usage
|
|
13
|
+
drop constraint if exists daily_token_usage_tenant_id_fkey;
|
|
14
|
+
`);
|
|
15
|
+
},
|
|
16
|
+
down: async (pool) => {
|
|
17
|
+
// Remove the new columns
|
|
18
|
+
await pool.query(sql `
|
|
19
|
+
alter table daily_token_usage
|
|
20
|
+
drop column if exists user_token_usage,
|
|
21
|
+
drop column if exists m2m_token_usage;
|
|
22
|
+
`);
|
|
23
|
+
// Re-add the foreign key constraint
|
|
24
|
+
await pool.query(sql `
|
|
25
|
+
alter table daily_token_usage
|
|
26
|
+
add constraint daily_token_usage_tenant_id_fkey
|
|
27
|
+
foreign key (tenant_id) references tenants (id) on update cascade on delete cascade;
|
|
28
|
+
`);
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
export default alteration;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
const alteration = {
|
|
3
|
+
beforeUp: async (pool) => {
|
|
4
|
+
/**
|
|
5
|
+
* Use 'if not exists' to ensure idempotency.
|
|
6
|
+
* If the subsequent transaction in 'up' fails, the migration can be safely re-run without failing on an already created index.
|
|
7
|
+
*/
|
|
8
|
+
await pool.query(sql `
|
|
9
|
+
create index concurrently if not exists logs__created_at_id
|
|
10
|
+
on logs (tenant_id, created_at, id);
|
|
11
|
+
`);
|
|
12
|
+
},
|
|
13
|
+
up: async () => {
|
|
14
|
+
/**
|
|
15
|
+
* The index on the logs table must be created outside of a transaction to avoid table locks.
|
|
16
|
+
* 'concurrently' cannot be used inside a transaction, so this up is intentionally left empty.
|
|
17
|
+
*/
|
|
18
|
+
},
|
|
19
|
+
beforeDown: async (pool) => {
|
|
20
|
+
/**
|
|
21
|
+
* Use 'if exists' to ensure idempotency. If the subsequent transaction in 'down' fails,
|
|
22
|
+
* the rollback can be safely re-run without failing on a non-existent index.
|
|
23
|
+
*/
|
|
24
|
+
await pool.query(sql `
|
|
25
|
+
drop index concurrently if exists logs__created_at_id;
|
|
26
|
+
`);
|
|
27
|
+
},
|
|
28
|
+
down: async () => {
|
|
29
|
+
/**
|
|
30
|
+
* The index on the logs table must be dropped outside of a transaction to avoid table locks.
|
|
31
|
+
* 'concurrently' cannot be used inside a transaction, so this down is intentionally left empty.
|
|
32
|
+
*/
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
export default alteration;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
/**
|
|
3
|
+
* Update saml_application_sessions.relay_state column type from varchar(256) to text
|
|
4
|
+
* to support longer relay state values that may be used in SAML authentication flows.
|
|
5
|
+
*
|
|
6
|
+
* The relay state parameter in SAML can contain arbitrary data that needs to be
|
|
7
|
+
* preserved and returned to the service provider, and 256 characters may not be
|
|
8
|
+
* sufficient for all use cases.
|
|
9
|
+
*/
|
|
10
|
+
const alteration = {
|
|
11
|
+
up: async (pool) => {
|
|
12
|
+
await pool.query(sql `
|
|
13
|
+
alter table saml_application_sessions
|
|
14
|
+
alter column relay_state type varchar(512)
|
|
15
|
+
`);
|
|
16
|
+
},
|
|
17
|
+
down: async (pool) => {
|
|
18
|
+
// The down migration may fail or cause data loss if any existing relay_state values exceed 256 characters. Consider adding a USING clause to safely truncate values during rollback, this ensures the rollback operation won't fail due to data that's too long for the varchar(256) constraint.
|
|
19
|
+
await pool.query(sql `
|
|
20
|
+
alter table saml_application_sessions
|
|
21
|
+
alter column relay_state type varchar(256) using left(relay_state, 256)
|
|
22
|
+
`);
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
export default alteration;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
/**
|
|
3
|
+
* The `logs__id` index on (tenant_id, id) is redundant because:
|
|
4
|
+
* 1. `id` is already the primary key, which can efficiently lookup by id
|
|
5
|
+
* 2. `logs__created_at_id` index on (tenant_id, created_at, id) already covers tenant_id queries
|
|
6
|
+
*
|
|
7
|
+
* Removing this index reduces write IOPS during log deletion operations,
|
|
8
|
+
* as each DELETE no longer needs to update this redundant index.
|
|
9
|
+
*/
|
|
10
|
+
const alteration = {
|
|
11
|
+
beforeUp: async (pool) => {
|
|
12
|
+
/**
|
|
13
|
+
* Use 'if exists' to ensure idempotency.
|
|
14
|
+
* 'concurrently' avoids table locks during index drop.
|
|
15
|
+
*/
|
|
16
|
+
await pool.query(sql `
|
|
17
|
+
drop index concurrently if exists logs__id;
|
|
18
|
+
`);
|
|
19
|
+
},
|
|
20
|
+
up: async () => {
|
|
21
|
+
/**
|
|
22
|
+
* The index must be dropped outside of a transaction to avoid table locks.
|
|
23
|
+
* 'concurrently' cannot be used inside a transaction, so this up is intentionally left empty.
|
|
24
|
+
*/
|
|
25
|
+
},
|
|
26
|
+
beforeDown: async (pool) => {
|
|
27
|
+
/**
|
|
28
|
+
* Recreate the index if rolling back.
|
|
29
|
+
* Use 'if not exists' to ensure idempotency.
|
|
30
|
+
*/
|
|
31
|
+
await pool.query(sql `
|
|
32
|
+
create index concurrently if not exists logs__id
|
|
33
|
+
on logs (tenant_id, id);
|
|
34
|
+
`);
|
|
35
|
+
},
|
|
36
|
+
down: async () => {
|
|
37
|
+
/**
|
|
38
|
+
* The index must be created outside of a transaction to avoid table locks.
|
|
39
|
+
* 'concurrently' cannot be used inside a transaction, so this down is intentionally left empty.
|
|
40
|
+
*/
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
export default alteration;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
const getDatabaseName = async (pool) => {
|
|
3
|
+
const { currentDatabase } = await pool.one(sql `
|
|
4
|
+
select current_database();
|
|
5
|
+
`);
|
|
6
|
+
return currentDatabase.replaceAll('-', '_');
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Grant read permission to the tag column in the tenants table to the logto_tenant_<databaseName> role.
|
|
10
|
+
*/
|
|
11
|
+
const alteration = {
|
|
12
|
+
up: async (pool) => {
|
|
13
|
+
const databaseName = await getDatabaseName(pool);
|
|
14
|
+
const baseRoleId = sql.identifier([`logto_tenant_${databaseName}`]);
|
|
15
|
+
await pool.query(sql `
|
|
16
|
+
grant select (tag)
|
|
17
|
+
on table tenants
|
|
18
|
+
to ${baseRoleId}
|
|
19
|
+
`);
|
|
20
|
+
},
|
|
21
|
+
down: async (pool) => {
|
|
22
|
+
const databaseName = await getDatabaseName(pool);
|
|
23
|
+
const baseRoleId = sql.identifier([`logto_tenant_${databaseName}`]);
|
|
24
|
+
await pool.query(sql `
|
|
25
|
+
revoke select (tag)
|
|
26
|
+
on table tenants
|
|
27
|
+
from ${baseRoleId}
|
|
28
|
+
`);
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
export default alteration;
|
|
@@ -46,7 +46,6 @@ export declare enum ProductEvent {
|
|
|
46
46
|
DeveloperCreated = "developer created",
|
|
47
47
|
/** A user has been deleted in the admin tenant. */
|
|
48
48
|
DeveloperDeleted = "developer deleted",
|
|
49
|
-
AccessTokenIssued = "access token issued",
|
|
50
49
|
AppCreated = "app created",
|
|
51
50
|
AppDeleted = "app deleted",
|
|
52
51
|
RoleCreated = "role created",
|
|
@@ -86,14 +85,3 @@ export declare const tenantEventDistinctId = "TENANT_EVENT";
|
|
|
86
85
|
* identifying the user who initiated the Management API request proxied by the cloud service.
|
|
87
86
|
*/
|
|
88
87
|
export declare const cloudUserIdHeader = "logto-cloud-user-id";
|
|
89
|
-
/**
|
|
90
|
-
* The types of access tokens issued by Logto.
|
|
91
|
-
*
|
|
92
|
-
* Note that this is for internal use only and is different from other technical definitions of
|
|
93
|
-
* token types.
|
|
94
|
-
*/
|
|
95
|
-
export declare enum ProductAccessTokenType {
|
|
96
|
-
Unknown = "unknown",
|
|
97
|
-
User = "user",
|
|
98
|
-
ClientCredentials = "client_credentials"
|
|
99
|
-
}
|
|
@@ -47,7 +47,6 @@ export var ProductEvent;
|
|
|
47
47
|
ProductEvent["DeveloperCreated"] = "developer created";
|
|
48
48
|
/** A user has been deleted in the admin tenant. */
|
|
49
49
|
ProductEvent["DeveloperDeleted"] = "developer deleted";
|
|
50
|
-
ProductEvent["AccessTokenIssued"] = "access token issued";
|
|
51
50
|
ProductEvent["AppCreated"] = "app created";
|
|
52
51
|
ProductEvent["AppDeleted"] = "app deleted";
|
|
53
52
|
ProductEvent["RoleCreated"] = "role created";
|
|
@@ -88,15 +87,3 @@ export const tenantEventDistinctId = 'TENANT_EVENT';
|
|
|
88
87
|
* identifying the user who initiated the Management API request proxied by the cloud service.
|
|
89
88
|
*/
|
|
90
89
|
export const cloudUserIdHeader = 'logto-cloud-user-id';
|
|
91
|
-
/**
|
|
92
|
-
* The types of access tokens issued by Logto.
|
|
93
|
-
*
|
|
94
|
-
* Note that this is for internal use only and is different from other technical definitions of
|
|
95
|
-
* token types.
|
|
96
|
-
*/
|
|
97
|
-
export var ProductAccessTokenType;
|
|
98
|
-
(function (ProductAccessTokenType) {
|
|
99
|
-
ProductAccessTokenType["Unknown"] = "unknown";
|
|
100
|
-
ProductAccessTokenType["User"] = "user";
|
|
101
|
-
ProductAccessTokenType["ClientCredentials"] = "client_credentials";
|
|
102
|
-
})(ProductAccessTokenType || (ProductAccessTokenType = {}));
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { GeneratedSchema } from './../foundations/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* This table is used to store aggregated data of daily active users for each tenant. A daily job summarizes data from the daily active users table and inserts it into this table, or removes expired data. Therefore, we should not directly manipulate this table, except for "read" operations.
|
|
4
|
+
*
|
|
5
|
+
* @remarks This is a type for database creation.
|
|
6
|
+
* @see {@link AggregatedDailyActiveUser} for the original type.
|
|
7
|
+
*/
|
|
8
|
+
export type CreateAggregatedDailyActiveUser = {
|
|
9
|
+
tenantId?: string;
|
|
10
|
+
activityDate: number;
|
|
11
|
+
userId: string;
|
|
12
|
+
activityCount: number;
|
|
13
|
+
};
|
|
14
|
+
/** This table is used to store aggregated data of daily active users for each tenant. A daily job summarizes data from the daily active users table and inserts it into this table, or removes expired data. Therefore, we should not directly manipulate this table, except for "read" operations. */
|
|
15
|
+
export type AggregatedDailyActiveUser = {
|
|
16
|
+
tenantId: string;
|
|
17
|
+
activityDate: number;
|
|
18
|
+
userId: string;
|
|
19
|
+
activityCount: number;
|
|
20
|
+
};
|
|
21
|
+
export type AggregatedDailyActiveUserKeys = 'tenantId' | 'activityDate' | 'userId' | 'activityCount';
|
|
22
|
+
export declare const AggregatedDailyActiveUsers: GeneratedSchema<AggregatedDailyActiveUserKeys, CreateAggregatedDailyActiveUser, AggregatedDailyActiveUser, 'aggregated_daily_active_users', 'aggregated_daily_active_user'>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
const createGuard = z.object({
|
|
4
|
+
tenantId: z.string().max(21).optional(),
|
|
5
|
+
activityDate: z.number(),
|
|
6
|
+
userId: z.string().min(1).max(21),
|
|
7
|
+
activityCount: z.number(),
|
|
8
|
+
});
|
|
9
|
+
const guard = z.object({
|
|
10
|
+
tenantId: z.string().max(21),
|
|
11
|
+
activityDate: z.number(),
|
|
12
|
+
userId: z.string().min(1).max(21),
|
|
13
|
+
activityCount: z.number(),
|
|
14
|
+
});
|
|
15
|
+
export const AggregatedDailyActiveUsers = Object.freeze({
|
|
16
|
+
table: 'aggregated_daily_active_users',
|
|
17
|
+
tableSingular: 'aggregated_daily_active_user',
|
|
18
|
+
fields: {
|
|
19
|
+
tenantId: 'tenant_id',
|
|
20
|
+
activityDate: 'activity_date',
|
|
21
|
+
userId: 'user_id',
|
|
22
|
+
activityCount: 'activity_count',
|
|
23
|
+
},
|
|
24
|
+
fieldKeys: [
|
|
25
|
+
'tenantId',
|
|
26
|
+
'activityDate',
|
|
27
|
+
'userId',
|
|
28
|
+
'activityCount',
|
|
29
|
+
],
|
|
30
|
+
createGuard,
|
|
31
|
+
guard,
|
|
32
|
+
updateGuard: guard.partial(),
|
|
33
|
+
});
|
|
@@ -8,13 +8,17 @@ export type CreateDailyTokenUsage = {
|
|
|
8
8
|
id: string;
|
|
9
9
|
tenantId?: string;
|
|
10
10
|
usage?: number;
|
|
11
|
+
userTokenUsage?: number;
|
|
12
|
+
m2mTokenUsage?: number;
|
|
11
13
|
date: number;
|
|
12
14
|
};
|
|
13
15
|
export type DailyTokenUsage = {
|
|
14
16
|
id: string;
|
|
15
17
|
tenantId: string;
|
|
16
18
|
usage: number;
|
|
19
|
+
userTokenUsage: number;
|
|
20
|
+
m2mTokenUsage: number;
|
|
17
21
|
date: number;
|
|
18
22
|
};
|
|
19
|
-
export type DailyTokenUsageKeys = 'id' | 'tenantId' | 'usage' | 'date';
|
|
23
|
+
export type DailyTokenUsageKeys = 'id' | 'tenantId' | 'usage' | 'userTokenUsage' | 'm2mTokenUsage' | 'date';
|
|
20
24
|
export declare const DailyTokenUsage: GeneratedSchema<DailyTokenUsageKeys, CreateDailyTokenUsage, DailyTokenUsage, 'daily_token_usage', 'daily_token_usage'>;
|
|
@@ -4,12 +4,16 @@ const createGuard = z.object({
|
|
|
4
4
|
id: z.string().min(1).max(21),
|
|
5
5
|
tenantId: z.string().max(21).optional(),
|
|
6
6
|
usage: z.number().optional(),
|
|
7
|
+
userTokenUsage: z.number().optional(),
|
|
8
|
+
m2mTokenUsage: z.number().optional(),
|
|
7
9
|
date: z.number(),
|
|
8
10
|
});
|
|
9
11
|
const guard = z.object({
|
|
10
12
|
id: z.string().min(1).max(21),
|
|
11
13
|
tenantId: z.string().max(21),
|
|
12
14
|
usage: z.number(),
|
|
15
|
+
userTokenUsage: z.number(),
|
|
16
|
+
m2mTokenUsage: z.number(),
|
|
13
17
|
date: z.number(),
|
|
14
18
|
});
|
|
15
19
|
export const DailyTokenUsage = Object.freeze({
|
|
@@ -19,12 +23,16 @@ export const DailyTokenUsage = Object.freeze({
|
|
|
19
23
|
id: 'id',
|
|
20
24
|
tenantId: 'tenant_id',
|
|
21
25
|
usage: 'usage',
|
|
26
|
+
userTokenUsage: 'user_token_usage',
|
|
27
|
+
m2mTokenUsage: 'm2m_token_usage',
|
|
22
28
|
date: 'date',
|
|
23
29
|
},
|
|
24
30
|
fieldKeys: [
|
|
25
31
|
'id',
|
|
26
32
|
'tenantId',
|
|
27
33
|
'usage',
|
|
34
|
+
'userTokenUsage',
|
|
35
|
+
'm2mTokenUsage',
|
|
28
36
|
'date',
|
|
29
37
|
],
|
|
30
38
|
createGuard,
|
|
@@ -4,6 +4,7 @@ export * from './-after-each.js';
|
|
|
4
4
|
export * from './-before-all.js';
|
|
5
5
|
export * from './-function.js';
|
|
6
6
|
export * from './account-center.js';
|
|
7
|
+
export * from './aggregated-daily-active-user.js';
|
|
7
8
|
export * from './application-secret.js';
|
|
8
9
|
export * from './application-sign-in-experience.js';
|
|
9
10
|
export * from './application-user-consent-organization-resource-scope.js';
|
package/lib/db-entries/index.js
CHANGED
|
@@ -5,6 +5,7 @@ export * from './-after-each.js';
|
|
|
5
5
|
export * from './-before-all.js';
|
|
6
6
|
export * from './-function.js';
|
|
7
7
|
export * from './account-center.js';
|
|
8
|
+
export * from './aggregated-daily-active-user.js';
|
|
8
9
|
export * from './application-secret.js';
|
|
9
10
|
export * from './application-sign-in-experience.js';
|
|
10
11
|
export * from './application-user-consent-organization-resource-scope.js';
|
|
@@ -6,7 +6,7 @@ const createGuard = z.object({
|
|
|
6
6
|
applicationId: z.string().min(1).max(21),
|
|
7
7
|
samlRequestId: z.string().min(1).max(128),
|
|
8
8
|
oidcState: z.string().max(32).nullable().optional(),
|
|
9
|
-
relayState: z.string().max(
|
|
9
|
+
relayState: z.string().max(512).nullable().optional(),
|
|
10
10
|
rawAuthRequest: z.string().min(1),
|
|
11
11
|
createdAt: z.number().optional(),
|
|
12
12
|
expiresAt: z.number(),
|
|
@@ -17,7 +17,7 @@ const guard = z.object({
|
|
|
17
17
|
applicationId: z.string().min(1).max(21),
|
|
18
18
|
samlRequestId: z.string().min(1).max(128),
|
|
19
19
|
oidcState: z.string().max(32).nullable(),
|
|
20
|
-
relayState: z.string().max(
|
|
20
|
+
relayState: z.string().max(512).nullable(),
|
|
21
21
|
rawAuthRequest: z.string().min(1),
|
|
22
22
|
createdAt: z.number(),
|
|
23
23
|
expiresAt: z.number(),
|
|
@@ -3,6 +3,10 @@ export declare enum CaptchaType {
|
|
|
3
3
|
RecaptchaEnterprise = "RecaptchaEnterprise",
|
|
4
4
|
Turnstile = "Turnstile"
|
|
5
5
|
}
|
|
6
|
+
export declare enum RecaptchaEnterpriseMode {
|
|
7
|
+
Invisible = "invisible",
|
|
8
|
+
Checkbox = "checkbox"
|
|
9
|
+
}
|
|
6
10
|
export declare const turnstileConfigGuard: z.ZodObject<{
|
|
7
11
|
type: z.ZodLiteral<CaptchaType.Turnstile>;
|
|
8
12
|
siteKey: z.ZodString;
|
|
@@ -22,16 +26,22 @@ export declare const recaptchaEnterpriseConfigGuard: z.ZodObject<{
|
|
|
22
26
|
siteKey: z.ZodString;
|
|
23
27
|
secretKey: z.ZodString;
|
|
24
28
|
projectId: z.ZodString;
|
|
29
|
+
domain: z.ZodOptional<z.ZodString>;
|
|
30
|
+
mode: z.ZodOptional<z.ZodNativeEnum<typeof RecaptchaEnterpriseMode>>;
|
|
25
31
|
}, "strip", z.ZodTypeAny, {
|
|
26
32
|
type: CaptchaType.RecaptchaEnterprise;
|
|
27
33
|
siteKey: string;
|
|
28
34
|
secretKey: string;
|
|
29
35
|
projectId: string;
|
|
36
|
+
domain?: string | undefined;
|
|
37
|
+
mode?: RecaptchaEnterpriseMode | undefined;
|
|
30
38
|
}, {
|
|
31
39
|
type: CaptchaType.RecaptchaEnterprise;
|
|
32
40
|
siteKey: string;
|
|
33
41
|
secretKey: string;
|
|
34
42
|
projectId: string;
|
|
43
|
+
domain?: string | undefined;
|
|
44
|
+
mode?: RecaptchaEnterpriseMode | undefined;
|
|
35
45
|
}>;
|
|
36
46
|
export type RecaptchaEnterpriseConfig = z.infer<typeof recaptchaEnterpriseConfigGuard>;
|
|
37
47
|
export declare const captchaConfigGuard: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
@@ -51,15 +61,21 @@ export declare const captchaConfigGuard: z.ZodDiscriminatedUnion<"type", [z.ZodO
|
|
|
51
61
|
siteKey: z.ZodString;
|
|
52
62
|
secretKey: z.ZodString;
|
|
53
63
|
projectId: z.ZodString;
|
|
64
|
+
domain: z.ZodOptional<z.ZodString>;
|
|
65
|
+
mode: z.ZodOptional<z.ZodNativeEnum<typeof RecaptchaEnterpriseMode>>;
|
|
54
66
|
}, "strip", z.ZodTypeAny, {
|
|
55
67
|
type: CaptchaType.RecaptchaEnterprise;
|
|
56
68
|
siteKey: string;
|
|
57
69
|
secretKey: string;
|
|
58
70
|
projectId: string;
|
|
71
|
+
domain?: string | undefined;
|
|
72
|
+
mode?: RecaptchaEnterpriseMode | undefined;
|
|
59
73
|
}, {
|
|
60
74
|
type: CaptchaType.RecaptchaEnterprise;
|
|
61
75
|
siteKey: string;
|
|
62
76
|
secretKey: string;
|
|
63
77
|
projectId: string;
|
|
78
|
+
domain?: string | undefined;
|
|
79
|
+
mode?: RecaptchaEnterpriseMode | undefined;
|
|
64
80
|
}>]>;
|
|
65
81
|
export type CaptchaConfig = z.infer<typeof captchaConfigGuard>;
|
|
@@ -4,6 +4,11 @@ export var CaptchaType;
|
|
|
4
4
|
CaptchaType["RecaptchaEnterprise"] = "RecaptchaEnterprise";
|
|
5
5
|
CaptchaType["Turnstile"] = "Turnstile";
|
|
6
6
|
})(CaptchaType || (CaptchaType = {}));
|
|
7
|
+
export var RecaptchaEnterpriseMode;
|
|
8
|
+
(function (RecaptchaEnterpriseMode) {
|
|
9
|
+
RecaptchaEnterpriseMode["Invisible"] = "invisible";
|
|
10
|
+
RecaptchaEnterpriseMode["Checkbox"] = "checkbox";
|
|
11
|
+
})(RecaptchaEnterpriseMode || (RecaptchaEnterpriseMode = {}));
|
|
7
12
|
export const turnstileConfigGuard = z.object({
|
|
8
13
|
type: z.literal(CaptchaType.Turnstile),
|
|
9
14
|
siteKey: z.string(),
|
|
@@ -14,6 +19,8 @@ export const recaptchaEnterpriseConfigGuard = z.object({
|
|
|
14
19
|
siteKey: z.string(),
|
|
15
20
|
secretKey: z.string(),
|
|
16
21
|
projectId: z.string(),
|
|
22
|
+
domain: z.string().optional(),
|
|
23
|
+
mode: z.nativeEnum(RecaptchaEnterpriseMode).optional(),
|
|
17
24
|
});
|
|
18
25
|
export const captchaConfigGuard = z.discriminatedUnion('type', [
|
|
19
26
|
turnstileConfigGuard,
|
|
@@ -1,5 +1,15 @@
|
|
|
1
|
-
import type { DatabaseTransactionConnection } from '@silverhand/slonik';
|
|
1
|
+
import type { CommonQueryMethods, DatabaseTransactionConnection } from '@silverhand/slonik';
|
|
2
2
|
export type AlterationScript = {
|
|
3
|
+
/**
|
|
4
|
+
* Optional hook that runs before `up` outside of a transaction.
|
|
5
|
+
* Use for operations that cannot be wrapped in a transaction (e.g., CREATE INDEX CONCURRENTLY).
|
|
6
|
+
*/
|
|
7
|
+
beforeUp?: (connection: CommonQueryMethods) => Promise<void>;
|
|
8
|
+
/**
|
|
9
|
+
* Optional hook that runs before `down` outside of a transaction.
|
|
10
|
+
* Use for operations that cannot be wrapped in a transaction (e.g., DROP INDEX CONCURRENTLY).
|
|
11
|
+
*/
|
|
12
|
+
beforeDown?: (connection: CommonQueryMethods) => Promise<void>;
|
|
3
13
|
up: (connection: DatabaseTransactionConnection) => Promise<void>;
|
|
4
14
|
down: (connection: DatabaseTransactionConnection) => Promise<void>;
|
|
5
15
|
};
|
|
@@ -45,16 +45,25 @@ export type VerificationCodeIdentifier<T extends VerificationCodeSignInIdentifie
|
|
|
45
45
|
type: T;
|
|
46
46
|
value: string;
|
|
47
47
|
};
|
|
48
|
-
export declare const verificationCodeIdentifierGuard: z.ZodObject<{
|
|
49
|
-
type: z.
|
|
48
|
+
export declare const verificationCodeIdentifierGuard: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
49
|
+
type: z.ZodLiteral<SignInIdentifier.Email>;
|
|
50
50
|
value: z.ZodString;
|
|
51
51
|
}, "strip", z.ZodTypeAny, {
|
|
52
52
|
value: string;
|
|
53
|
-
type: SignInIdentifier.Email
|
|
53
|
+
type: SignInIdentifier.Email;
|
|
54
54
|
}, {
|
|
55
55
|
value: string;
|
|
56
|
-
type: SignInIdentifier.Email
|
|
57
|
-
}
|
|
56
|
+
type: SignInIdentifier.Email;
|
|
57
|
+
}>, z.ZodObject<{
|
|
58
|
+
type: z.ZodLiteral<SignInIdentifier.Phone>;
|
|
59
|
+
value: z.ZodString;
|
|
60
|
+
}, "strip", z.ZodTypeAny, {
|
|
61
|
+
value: string;
|
|
62
|
+
type: SignInIdentifier.Phone;
|
|
63
|
+
}, {
|
|
64
|
+
value: string;
|
|
65
|
+
type: SignInIdentifier.Phone;
|
|
66
|
+
}>]>;
|
|
58
67
|
/** Payload type for `POST /api/experience/verification/{social|sso}/:connectorId/authorization-uri`. */
|
|
59
68
|
export type SocialAuthorizationUrlPayload = {
|
|
60
69
|
state: string;
|
|
@@ -21,10 +21,16 @@ export const interactionIdentifierGuard = z.object({
|
|
|
21
21
|
type: z.nativeEnum(SignInIdentifier),
|
|
22
22
|
value: z.string(),
|
|
23
23
|
});
|
|
24
|
-
export const verificationCodeIdentifierGuard = z.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
export const verificationCodeIdentifierGuard = z.discriminatedUnion('type', [
|
|
25
|
+
z.object({
|
|
26
|
+
type: z.literal(SignInIdentifier.Email),
|
|
27
|
+
value: z.string().regex(emailRegEx),
|
|
28
|
+
}),
|
|
29
|
+
z.object({
|
|
30
|
+
type: z.literal(SignInIdentifier.Phone),
|
|
31
|
+
value: z.string().regex(phoneRegEx),
|
|
32
|
+
}),
|
|
33
|
+
]);
|
|
28
34
|
export const socialAuthorizationUrlPayloadGuard = z.object({
|
|
29
35
|
state: z.string(),
|
|
30
36
|
redirectUri: z.string(),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ConnectorMetadata, type GoogleOneTapConfig } from '@logto/connector-kit';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { type CustomProfileField, type SignInExperience } from '../db-entries/index.js';
|
|
4
|
-
import { CaptchaType } from '../foundations/jsonb-types/index.js';
|
|
4
|
+
import { CaptchaType, RecaptchaEnterpriseMode } from '../foundations/jsonb-types/index.js';
|
|
5
5
|
import { type SsoConnectorMetadata } from './sso-connector.js';
|
|
6
6
|
type ForgotPassword = {
|
|
7
7
|
phone: boolean;
|
|
@@ -32,6 +32,8 @@ export type FullSignInExperience = Omit<SignInExperience, 'forgotPasswordMethods
|
|
|
32
32
|
captchaConfig?: {
|
|
33
33
|
type: CaptchaType;
|
|
34
34
|
siteKey: string;
|
|
35
|
+
domain?: string;
|
|
36
|
+
mode?: RecaptchaEnterpriseMode;
|
|
35
37
|
};
|
|
36
38
|
customProfileFields?: Readonly<CustomProfileField[]>;
|
|
37
39
|
};
|
|
@@ -674,12 +676,18 @@ export declare const fullSignInExperienceGuard: z.ZodObject<Omit<{
|
|
|
674
676
|
captchaConfig: z.ZodOptional<z.ZodObject<{
|
|
675
677
|
type: z.ZodNativeEnum<typeof CaptchaType>;
|
|
676
678
|
siteKey: z.ZodString;
|
|
679
|
+
domain: z.ZodOptional<z.ZodString>;
|
|
680
|
+
mode: z.ZodOptional<z.ZodNativeEnum<typeof RecaptchaEnterpriseMode>>;
|
|
677
681
|
}, "strip", z.ZodTypeAny, {
|
|
678
682
|
type: CaptchaType;
|
|
679
683
|
siteKey: string;
|
|
684
|
+
domain?: string | undefined;
|
|
685
|
+
mode?: RecaptchaEnterpriseMode | undefined;
|
|
680
686
|
}, {
|
|
681
687
|
type: CaptchaType;
|
|
682
688
|
siteKey: string;
|
|
689
|
+
domain?: string | undefined;
|
|
690
|
+
mode?: RecaptchaEnterpriseMode | undefined;
|
|
683
691
|
}>>;
|
|
684
692
|
customProfileFields: z.ZodArray<import("../index.js").Guard<CustomProfileField>, "many">;
|
|
685
693
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -873,6 +881,8 @@ export declare const fullSignInExperienceGuard: z.ZodObject<Omit<{
|
|
|
873
881
|
captchaConfig?: {
|
|
874
882
|
type: CaptchaType;
|
|
875
883
|
siteKey: string;
|
|
884
|
+
domain?: string | undefined;
|
|
885
|
+
mode?: RecaptchaEnterpriseMode | undefined;
|
|
876
886
|
} | undefined;
|
|
877
887
|
}, {
|
|
878
888
|
id: string;
|
|
@@ -1065,6 +1075,8 @@ export declare const fullSignInExperienceGuard: z.ZodObject<Omit<{
|
|
|
1065
1075
|
captchaConfig?: {
|
|
1066
1076
|
type: CaptchaType;
|
|
1067
1077
|
siteKey: string;
|
|
1078
|
+
domain?: string | undefined;
|
|
1079
|
+
mode?: RecaptchaEnterpriseMode | undefined;
|
|
1068
1080
|
} | undefined;
|
|
1069
1081
|
}>;
|
|
1070
1082
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { connectorMetadataGuard, googleOneTapConfigGuard, } from '@logto/connector-kit';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { CustomProfileFields, SignInExperiences, } from '../db-entries/index.js';
|
|
4
|
-
import { CaptchaType } from '../foundations/jsonb-types/index.js';
|
|
4
|
+
import { CaptchaType, RecaptchaEnterpriseMode } from '../foundations/jsonb-types/index.js';
|
|
5
5
|
import { ssoConnectorMetadataGuard } from './sso-connector.js';
|
|
6
6
|
export const fullSignInExperienceGuard = SignInExperiences.guard
|
|
7
7
|
.omit({ forgotPasswordMethods: true })
|
|
@@ -25,6 +25,8 @@ export const fullSignInExperienceGuard = SignInExperiences.guard
|
|
|
25
25
|
.object({
|
|
26
26
|
type: z.nativeEnum(CaptchaType),
|
|
27
27
|
siteKey: z.string(),
|
|
28
|
+
domain: z.string().optional(),
|
|
29
|
+
mode: z.nativeEnum(RecaptchaEnterpriseMode).optional(),
|
|
28
30
|
})
|
|
29
31
|
.optional(),
|
|
30
32
|
customProfileFields: CustomProfileFields.guard.array(),
|
package/lib/types/user.d.ts
CHANGED
|
@@ -316,6 +316,7 @@ export type UserProfileResponse = z.infer<typeof userProfileResponseGuard>;
|
|
|
316
316
|
export declare const userMfaVerificationResponseGuard: z.ZodArray<z.ZodObject<{
|
|
317
317
|
id: z.ZodString;
|
|
318
318
|
createdAt: z.ZodString;
|
|
319
|
+
lastUsedAt: z.ZodOptional<z.ZodString>;
|
|
319
320
|
type: z.ZodNativeEnum<typeof MfaFactor>;
|
|
320
321
|
agent: z.ZodOptional<z.ZodString>;
|
|
321
322
|
name: z.ZodOptional<z.ZodString>;
|
|
@@ -325,6 +326,7 @@ export declare const userMfaVerificationResponseGuard: z.ZodArray<z.ZodObject<{
|
|
|
325
326
|
id: string;
|
|
326
327
|
createdAt: string;
|
|
327
328
|
name?: string | undefined;
|
|
329
|
+
lastUsedAt?: string | undefined;
|
|
328
330
|
agent?: string | undefined;
|
|
329
331
|
remainCodes?: number | undefined;
|
|
330
332
|
}, {
|
|
@@ -332,6 +334,7 @@ export declare const userMfaVerificationResponseGuard: z.ZodArray<z.ZodObject<{
|
|
|
332
334
|
id: string;
|
|
333
335
|
createdAt: string;
|
|
334
336
|
name?: string | undefined;
|
|
337
|
+
lastUsedAt?: string | undefined;
|
|
335
338
|
agent?: string | undefined;
|
|
336
339
|
remainCodes?: number | undefined;
|
|
337
340
|
}>, "many">;
|
package/lib/types/user.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logto/schemas",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.35.0",
|
|
4
4
|
"author": "Silverhand Inc. <contact@silverhand.io>",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -65,11 +65,11 @@
|
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"@withtyped/server": "^0.14.0",
|
|
67
67
|
"nanoid": "^5.0.9",
|
|
68
|
-
"@logto/connector-kit": "^4.
|
|
68
|
+
"@logto/connector-kit": "^4.7.0",
|
|
69
69
|
"@logto/core-kit": "^2.6.1",
|
|
70
|
-
"@logto/
|
|
71
|
-
"@logto/phrases": "^1.23.0",
|
|
70
|
+
"@logto/phrases": "^1.24.0",
|
|
72
71
|
"@logto/phrases-experience": "^1.12.0",
|
|
72
|
+
"@logto/language-kit": "^1.2.0",
|
|
73
73
|
"@logto/shared": "^3.3.0"
|
|
74
74
|
},
|
|
75
75
|
"peerDependencies": {
|
package/tables/_after_all.sql
CHANGED
|
@@ -13,7 +13,7 @@ revoke all privileges
|
|
|
13
13
|
from logto_tenant_${database};
|
|
14
14
|
|
|
15
15
|
-- Allow limited select to perform the RLS policy query in `after_each` (using select ... from tenants ...)
|
|
16
|
-
grant select (id, db_user, is_suspended)
|
|
16
|
+
grant select (id, db_user, is_suspended, tag)
|
|
17
17
|
on table tenants
|
|
18
18
|
to logto_tenant_${database};
|
|
19
19
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** This table is used to store aggregated data of daily active users for each tenant. A daily job summarizes data from the daily active users table and inserts it into this table, or removes expired data. Therefore, we should not directly manipulate this table, except for "read" operations. */
|
|
2
|
+
create table aggregated_daily_active_users (
|
|
3
|
+
tenant_id varchar(21) not null,
|
|
4
|
+
activity_date date not null,
|
|
5
|
+
user_id varchar(21) not null,
|
|
6
|
+
activity_count integer not null,
|
|
7
|
+
primary key (tenant_id, activity_date, user_id)
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
-- Index for billing cycle range queries
|
|
11
|
+
create index aggregated_daily_active_users__tenant_date
|
|
12
|
+
on aggregated_daily_active_users (tenant_id, activity_date);
|
|
13
|
+
|
|
14
|
+
-- Index for tenant-specific user activity queries
|
|
15
|
+
create index aggregated_daily_active_users__tenant_user_date
|
|
16
|
+
on aggregated_daily_active_users (tenant_id, user_id, activity_date desc);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
create table daily_active_users (
|
|
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
|
user_id varchar(21) not null,
|
|
6
5
|
date timestamptz not null default (now()),
|
|
7
6
|
primary key (id),
|
|
@@ -9,8 +8,14 @@ create table daily_active_users (
|
|
|
9
8
|
unique (user_id, date)
|
|
10
9
|
);
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
-- Optimized index for aggregation queries with better write performance
|
|
12
|
+
create index daily_active_users__tenant_date_user
|
|
13
|
+
on daily_active_users (tenant_id, date, user_id);
|
|
14
|
+
|
|
15
|
+
-- BRIN index for time-series date range queries
|
|
16
|
+
-- Optimized for sequential data insertion and range scans (date >= ?)
|
|
17
|
+
create index daily_active_users__date_brin
|
|
18
|
+
on daily_active_users using brin (date);
|
|
14
19
|
|
|
15
20
|
create index daily_active_users__date
|
|
16
21
|
on daily_active_users (tenant_id, date);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
create table daily_token_usage (
|
|
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
|
usage bigint not null default(0),
|
|
5
|
+
user_token_usage bigint not null default(0),
|
|
6
|
+
m2m_token_usage bigint not null default(0),
|
|
6
7
|
date timestamptz not null,
|
|
7
8
|
primary key (id)
|
|
8
9
|
);
|
package/tables/logs.sql
CHANGED
|
@@ -10,9 +10,6 @@ create table logs (
|
|
|
10
10
|
primary key (id)
|
|
11
11
|
);
|
|
12
12
|
|
|
13
|
-
create index logs__id
|
|
14
|
-
on logs (tenant_id, id);
|
|
15
|
-
|
|
16
13
|
create index logs__key
|
|
17
14
|
on logs (tenant_id, key);
|
|
18
15
|
|
|
@@ -24,3 +21,6 @@ create index logs__application_id
|
|
|
24
21
|
|
|
25
22
|
create index logs__hook_id
|
|
26
23
|
on logs (tenant_id, (payload->>'hookId'));
|
|
24
|
+
|
|
25
|
+
create index logs__created_at_id
|
|
26
|
+
on logs (tenant_id, created_at, id);
|
|
@@ -12,7 +12,7 @@ create table saml_application_sessions (
|
|
|
12
12
|
/** The identifier of the OIDC auth request state. */
|
|
13
13
|
oidc_state varchar(32),
|
|
14
14
|
/** The relay state of the SAML auth request. */
|
|
15
|
-
relay_state varchar(
|
|
15
|
+
relay_state varchar(512),
|
|
16
16
|
/** The raw request of the SAML auth request. */
|
|
17
17
|
raw_auth_request text not null,
|
|
18
18
|
created_at timestamptz not null default(now()),
|