@logto/schemas 1.40.1 → 1.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/alterations/1.41.0-1779864280-add-password-expiration-policy.ts +23 -0
  2. package/alterations/1.41.0-1779864281-add-is-password-expired-to-users.ts +18 -0
  3. package/alterations/1.41.0-1780358400-drop-oidc-model-instances-legacy-grant-id-index.ts +25 -0
  4. package/alterations/1.41.0-1780381219-add-username-policy.ts +41 -0
  5. package/alterations/1.41.0-1780643665-set-sign-up-profile-fields-default.ts +20 -0
  6. package/alterations/1.41.0-1780906060-add-verification-code-policy.ts +19 -0
  7. package/alterations/1.41.0-1781689400-add-sentinel-activities-created-at-index.ts +25 -0
  8. package/alterations/1.41.0-1782354362-set-admin-account-center-profile-fields.ts +28 -0
  9. package/alterations/1.41.0-1782375106-cover-service-logs-tenant-type-index-with-created-at.ts +36 -0
  10. package/alterations/1.42.0-1783916980-add-domain-verification-files.ts +20 -0
  11. package/alterations/1.42.0-1784619793-add-service-logs-created-at-index.ts +33 -0
  12. package/alterations/1.42.0-1784620000-add-saml-sso-connector-signing-keys-table.ts +38 -0
  13. package/alterations/utils/1704934999-tables.ts +22 -1
  14. package/alterations-js/1.41.0-1779864280-add-password-expiration-policy.js +19 -0
  15. package/alterations-js/1.41.0-1779864281-add-is-password-expired-to-users.js +14 -0
  16. package/alterations-js/1.41.0-1780358400-drop-oidc-model-instances-legacy-grant-id-index.js +21 -0
  17. package/alterations-js/1.41.0-1780381219-add-username-policy.js +37 -0
  18. package/alterations-js/1.41.0-1780643665-set-sign-up-profile-fields-default.js +16 -0
  19. package/alterations-js/1.41.0-1780906060-add-verification-code-policy.js +15 -0
  20. package/alterations-js/1.41.0-1781689400-add-sentinel-activities-created-at-index.js +21 -0
  21. package/alterations-js/1.41.0-1782354362-set-admin-account-center-profile-fields.js +23 -0
  22. package/alterations-js/1.41.0-1782375106-cover-service-logs-tenant-type-index-with-created-at.js +32 -0
  23. package/alterations-js/1.42.0-1783916980-add-domain-verification-files.js +16 -0
  24. package/alterations-js/1.42.0-1784619793-add-service-logs-created-at-index.js +29 -0
  25. package/alterations-js/1.42.0-1784620000-add-saml-sso-connector-signing-keys-table.js +33 -0
  26. package/alterations-js/utils/1704934999-tables.js +19 -1
  27. package/lib/consts/experience.d.ts +3 -0
  28. package/lib/consts/experience.js +3 -0
  29. package/lib/consts/index.d.ts +2 -0
  30. package/lib/consts/index.js +2 -0
  31. package/lib/consts/message-rate-limit.d.ts +65 -0
  32. package/lib/consts/message-rate-limit.js +29 -0
  33. package/lib/consts/message-rate-limit.test.d.ts +1 -0
  34. package/lib/consts/message-rate-limit.test.js +20 -0
  35. package/lib/consts/verification-code.d.ts +10 -0
  36. package/lib/consts/verification-code.js +10 -0
  37. package/lib/db-entries/domain.d.ts +4 -2
  38. package/lib/db-entries/domain.js +5 -1
  39. package/lib/db-entries/index.d.ts +1 -0
  40. package/lib/db-entries/index.js +1 -0
  41. package/lib/db-entries/saml-sso-connector-signing-key.d.ts +32 -0
  42. package/lib/db-entries/saml-sso-connector-signing-key.js +49 -0
  43. package/lib/db-entries/sign-in-experience.d.ts +10 -4
  44. package/lib/db-entries/sign-in-experience.js +13 -1
  45. package/lib/db-entries/user.d.ts +5 -1
  46. package/lib/db-entries/user.js +8 -0
  47. package/lib/foundations/jsonb-types/account-centers.d.ts +3 -0
  48. package/lib/foundations/jsonb-types/account-centers.js +1 -0
  49. package/lib/foundations/jsonb-types/custom-domain.d.ts +57 -0
  50. package/lib/foundations/jsonb-types/custom-domain.js +51 -0
  51. package/lib/foundations/jsonb-types/custom-domain.test.d.ts +1 -0
  52. package/lib/foundations/jsonb-types/custom-domain.test.js +38 -0
  53. package/lib/foundations/jsonb-types/hooks.d.ts +4 -4
  54. package/lib/foundations/jsonb-types/hooks.js +2 -0
  55. package/lib/foundations/jsonb-types/one-time-tokens.d.ts +5 -0
  56. package/lib/foundations/jsonb-types/one-time-tokens.js +2 -0
  57. package/lib/foundations/jsonb-types/sentinel.d.ts +16 -1
  58. package/lib/foundations/jsonb-types/sentinel.js +15 -0
  59. package/lib/foundations/jsonb-types/sign-in-experience.d.ts +90 -3
  60. package/lib/foundations/jsonb-types/sign-in-experience.js +20 -0
  61. package/lib/foundations/jsonb-types/sign-in-experience.test.js +49 -1
  62. package/lib/foundations/jsonb-types/users.d.ts +9 -0
  63. package/lib/foundations/jsonb-types/users.js +1 -0
  64. package/lib/seeds/account-center.js +1 -0
  65. package/lib/seeds/sign-in-experience.js +1 -0
  66. package/lib/seeds/sign-in-experience.test.js +5 -1
  67. package/lib/types/application.d.ts +131 -1
  68. package/lib/types/application.js +8 -0
  69. package/lib/types/consent.d.ts +8 -0
  70. package/lib/types/custom-profile-fields.d.ts +5 -1
  71. package/lib/types/domain.d.ts +9 -0
  72. package/lib/types/hook.d.ts +9 -2
  73. package/lib/types/interaction-event.d.ts +11 -0
  74. package/lib/types/interaction-event.js +12 -0
  75. package/lib/types/interactions.d.ts +2 -11
  76. package/lib/types/interactions.js +5 -12
  77. package/lib/types/log/action.d.ts +7 -0
  78. package/lib/types/log/action.js +6 -0
  79. package/lib/types/log/index.d.ts +5 -2
  80. package/lib/types/log/index.js +1 -0
  81. package/lib/types/logto-config/action.d.ts +963 -0
  82. package/lib/types/logto-config/action.js +77 -0
  83. package/lib/types/logto-config/index.d.ts +91 -13
  84. package/lib/types/logto-config/index.js +12 -0
  85. package/lib/types/logto-config/index.test.js +25 -1
  86. package/lib/types/logto-config/jwt-customizer.d.ts +162 -1
  87. package/lib/types/logto-config/jwt-customizer.js +14 -0
  88. package/lib/types/organization.d.ts +136 -1
  89. package/lib/types/organization.js +3 -2
  90. package/lib/types/saml-application.d.ts +3 -0
  91. package/lib/types/saml-application.js +3 -0
  92. package/lib/types/sign-in-experience.d.ts +11 -4
  93. package/lib/types/sign-in-experience.js +1 -1
  94. package/lib/types/sso-connector.d.ts +58 -0
  95. package/lib/types/sso-connector.js +12 -0
  96. package/lib/types/ssr.d.ts +11 -0
  97. package/lib/types/user-assets.d.ts +10 -0
  98. package/lib/types/user-assets.js +17 -0
  99. package/lib/types/user-sessions.d.ts +309 -34
  100. package/lib/types/user-sessions.js +5 -0
  101. package/lib/types/user.d.ts +15 -0
  102. package/lib/types/user.js +1 -0
  103. package/lib/types/verification-records/one-time-token-verification.d.ts +5 -0
  104. package/package.json +8 -8
  105. package/tables/domains.sql +1 -0
  106. package/tables/oidc_model_instances.sql +0 -8
  107. package/tables/saml_sso_connector_signing_keys.sql +21 -0
  108. package/tables/sentinel_activities.sql +4 -0
  109. package/tables/service_logs.sql +7 -2
  110. package/tables/sign_in_experiences.sql +15 -2
  111. package/tables/users.sql +7 -0
@@ -0,0 +1,23 @@
1
+ import { sql } from '@silverhand/slonik';
2
+
3
+ import type { AlterationScript } from '../lib/types/alteration.js';
4
+
5
+ const alteration: AlterationScript = {
6
+ up: async (pool) => {
7
+ await pool.query(sql`
8
+ alter table users
9
+ add column password_updated_at timestamptz;
10
+
11
+ alter table sign_in_experiences
12
+ add column password_expiration jsonb not null default '{}'::jsonb;
13
+ `);
14
+ },
15
+ down: async (pool) => {
16
+ await pool.query(sql`
17
+ alter table users drop column password_updated_at;
18
+ alter table sign_in_experiences drop column password_expiration;
19
+ `);
20
+ },
21
+ };
22
+
23
+ export default alteration;
@@ -0,0 +1,18 @@
1
+ import { sql } from '@silverhand/slonik';
2
+
3
+ import type { AlterationScript } from '../lib/types/alteration.js';
4
+
5
+ const alteration: AlterationScript = {
6
+ up: async (pool) => {
7
+ await pool.query(sql`
8
+ alter table users add column is_password_expired boolean not null default false;
9
+ `);
10
+ },
11
+ down: async (pool) => {
12
+ await pool.query(sql`
13
+ alter table users drop column is_password_expired;
14
+ `);
15
+ },
16
+ };
17
+
18
+ export default alteration;
@@ -0,0 +1,25 @@
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
+ await pool.query(sql`
8
+ drop index concurrently if exists oidc_model_instances__model_name_payload_grant_id;
9
+ `);
10
+ },
11
+ up: async () => {
12
+ /** `concurrently` cannot be used inside a transaction. */
13
+ },
14
+ beforeDown: async (pool) => {
15
+ await pool.query(sql`
16
+ create index concurrently if not exists oidc_model_instances__model_name_payload_grant_id
17
+ on oidc_model_instances (tenant_id, model_name, (payload->>'grantId'));
18
+ `);
19
+ },
20
+ down: async () => {
21
+ /** `concurrently` cannot be used inside a transaction. */
22
+ },
23
+ };
24
+
25
+ export default alteration;
@@ -0,0 +1,41 @@
1
+ import { sql } from '@silverhand/slonik';
2
+
3
+ import type { AlterationScript } from '../lib/types/alteration.js';
4
+
5
+ const alteration: AlterationScript = {
6
+ // `create index concurrently` cannot run in a transaction; the runner wraps up/down in one.
7
+ beforeUp: async (pool) => {
8
+ await pool.query(sql`
9
+ create index concurrently users__tenant_lower_username
10
+ on users (tenant_id, lower(username))
11
+ where username is not null;
12
+ `);
13
+ },
14
+ up: async (pool) => {
15
+ // The default must be inlined: DDL cannot use bound parameters.
16
+ await pool.query(sql`
17
+ alter table sign_in_experiences
18
+ add column username_policy jsonb not null default '{
19
+ "caseSensitive": true,
20
+ "minLength": 1,
21
+ "maxLength": 128,
22
+ "allowedChars": {
23
+ "lowercase": true,
24
+ "uppercase": true,
25
+ "numbers": true,
26
+ "underscore": true
27
+ }
28
+ }'::jsonb;
29
+ `);
30
+ },
31
+ beforeDown: async (pool) => {
32
+ await pool.query(sql`drop index concurrently users__tenant_lower_username;`);
33
+ },
34
+ down: async (pool) => {
35
+ await pool.query(sql`
36
+ alter table sign_in_experiences drop column username_policy;
37
+ `);
38
+ },
39
+ };
40
+
41
+ export default alteration;
@@ -0,0 +1,20 @@
1
+ import { sql } from '@silverhand/slonik';
2
+
3
+ import type { AlterationScript } from '../lib/types/alteration.js';
4
+
5
+ const alteration: AlterationScript = {
6
+ up: async (pool) => {
7
+ await pool.query(sql`
8
+ alter table sign_in_experiences
9
+ alter column sign_up_profile_fields set default '[]'::jsonb;
10
+ `);
11
+ },
12
+ down: async (pool) => {
13
+ await pool.query(sql`
14
+ alter table sign_in_experiences
15
+ alter column sign_up_profile_fields drop default;
16
+ `);
17
+ },
18
+ };
19
+
20
+ export default alteration;
@@ -0,0 +1,19 @@
1
+ import { sql } from '@silverhand/slonik';
2
+
3
+ import type { AlterationScript } from '../lib/types/alteration.js';
4
+
5
+ const alteration: AlterationScript = {
6
+ up: async (pool) => {
7
+ await pool.query(sql`
8
+ alter table sign_in_experiences
9
+ add column if not exists verification_code_policy jsonb not null default '{}'::jsonb;
10
+ `);
11
+ },
12
+ down: async (pool) => {
13
+ await pool.query(sql`
14
+ alter table sign_in_experiences drop column if exists verification_code_policy;
15
+ `);
16
+ },
17
+ };
18
+
19
+ export default alteration;
@@ -0,0 +1,25 @@
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
+ await pool.query(sql`
8
+ create index concurrently if not exists sentinel_activities__created_at
9
+ on sentinel_activities (created_at);
10
+ `);
11
+ },
12
+ up: async () => {
13
+ /** `concurrently` cannot be used inside a transaction. */
14
+ },
15
+ beforeDown: async (pool) => {
16
+ await pool.query(sql`
17
+ drop index concurrently if exists sentinel_activities__created_at;
18
+ `);
19
+ },
20
+ down: async () => {
21
+ /** `concurrently` cannot be used inside a transaction. */
22
+ },
23
+ };
24
+
25
+ export default alteration;
@@ -0,0 +1,28 @@
1
+ import { sql } from '@silverhand/slonik';
2
+
3
+ import type { AlterationScript } from '../lib/types/alteration.js';
4
+
5
+ const adminTenantId = 'admin';
6
+
7
+ const alteration: AlterationScript = {
8
+ up: async (pool) => {
9
+ await pool.query(sql`
10
+ update account_centers
11
+ set profile_fields = '[{"name": "name"}, {"name": "avatar"}]'::jsonb
12
+ where tenant_id = ${adminTenantId}
13
+ and id = 'default'
14
+ and profile_fields is null
15
+ `);
16
+ },
17
+ down: async (pool) => {
18
+ await pool.query(sql`
19
+ update account_centers
20
+ set profile_fields = null
21
+ where tenant_id = ${adminTenantId}
22
+ and id = 'default'
23
+ and profile_fields = '[{"name": "name"}, {"name": "avatar"}]'::jsonb
24
+ `);
25
+ },
26
+ };
27
+
28
+ export default alteration;
@@ -0,0 +1,36 @@
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
+ // Cover the windowed hosted-email usage count (`where tenant_id=? and type=? and created_at>=?`) by
8
+ // adding `created_at` to the existing `(tenant_id, type)` index, then drop the now-redundant
9
+ // 2-column index (a left-prefix subset of the new one). `concurrently` keeps the write path
10
+ // unlocked — this unbounded table is written on every send.
11
+ await pool.query(sql`
12
+ create index concurrently if not exists service_logs__tenant_id__type__created_at
13
+ on service_logs (tenant_id, type, created_at);
14
+ `);
15
+ await pool.query(sql`
16
+ drop index concurrently if exists service_logs__tenant_id__type;
17
+ `);
18
+ },
19
+ up: async () => {
20
+ /** `concurrently` cannot be used inside a transaction. */
21
+ },
22
+ beforeDown: async (pool) => {
23
+ await pool.query(sql`
24
+ create index concurrently if not exists service_logs__tenant_id__type
25
+ on service_logs (tenant_id, type);
26
+ `);
27
+ await pool.query(sql`
28
+ drop index concurrently if exists service_logs__tenant_id__type__created_at;
29
+ `);
30
+ },
31
+ down: async () => {
32
+ /** `concurrently` cannot be used inside a transaction. */
33
+ },
34
+ };
35
+
36
+ export default alteration;
@@ -0,0 +1,20 @@
1
+ import { sql } from '@silverhand/slonik';
2
+
3
+ import type { AlterationScript } from '../lib/types/alteration.js';
4
+
5
+ const alteration: AlterationScript = {
6
+ up: async (pool) => {
7
+ await pool.query(sql`
8
+ alter table domains
9
+ add column verification_files jsonb not null default '[]'::jsonb;
10
+ `);
11
+ },
12
+ down: async (pool) => {
13
+ await pool.query(sql`
14
+ alter table domains
15
+ drop column verification_files;
16
+ `);
17
+ },
18
+ };
19
+
20
+ export default alteration;
@@ -0,0 +1,33 @@
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
+ // Support the age-based `service_logs` retention prune's global oldest-first scan
8
+ // (`where created_at < ? order by created_at asc limit ?`). The existing
9
+ // `(tenant_id, type, created_at)` index leads with `tenant_id`, so it can't drive a
10
+ // tenant-agnostic time-range scan. `concurrently` keeps the write path unlocked — this
11
+ // unbounded table is written on every send.
12
+ //
13
+ // No `if not exists`: fail loud if the index already exists (e.g. a leftover invalid build from a
14
+ // prior failed concurrent creation) instead of silently skipping.
15
+ await pool.query(sql`
16
+ create index concurrently service_logs__created_at
17
+ on service_logs (created_at);
18
+ `);
19
+ },
20
+ up: async () => {
21
+ /** `concurrently` cannot be used inside a transaction. */
22
+ },
23
+ beforeDown: async (pool) => {
24
+ await pool.query(sql`
25
+ drop index concurrently if exists service_logs__created_at;
26
+ `);
27
+ },
28
+ down: async () => {
29
+ /** `concurrently` cannot be used inside a transaction. */
30
+ },
31
+ };
32
+
33
+ export default alteration;
@@ -0,0 +1,38 @@
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
+ const alteration: AlterationScript = {
8
+ up: async (pool) => {
9
+ await pool.query(sql`
10
+ create table saml_sso_connector_signing_keys (
11
+ id varchar(21) not null,
12
+ tenant_id varchar(21) not null
13
+ references tenants (id) on update cascade on delete cascade,
14
+ sso_connector_id varchar(128) not null
15
+ references sso_connectors (id) on update cascade on delete cascade,
16
+ private_key text not null,
17
+ certificate text not null,
18
+ created_at timestamptz not null default now(),
19
+ expires_at timestamptz not null,
20
+ active boolean not null,
21
+ primary key (tenant_id, sso_connector_id, id)
22
+ );
23
+
24
+ create unique index saml_sso_connector_signing_keys__unique_active
25
+ on saml_sso_connector_signing_keys (tenant_id, sso_connector_id, active)
26
+ where active;
27
+ `);
28
+ await applyTableRls(pool, 'saml_sso_connector_signing_keys');
29
+ },
30
+ down: async (pool) => {
31
+ await dropTableRls(pool, 'saml_sso_connector_signing_keys');
32
+ await pool.query(sql`
33
+ drop table saml_sso_connector_signing_keys;
34
+ `);
35
+ },
36
+ };
37
+
38
+ export default alteration;
@@ -2,12 +2,33 @@ import { type CommonQueryMethods, sql } from '@silverhand/slonik';
2
2
 
3
3
  const getId = (value: string) => sql.identifier([value]);
4
4
 
5
+ /**
6
+ * Returns the suffix used in the base Logto role name for the current database
7
+ * (`logto_tenant_<suffix>`).
8
+ *
9
+ * Historically the suffix was the raw database name (which may contain hyphens). A later
10
+ * convention normalised it by replacing hyphens with underscores. To remain compatible with
11
+ * both conventions we probe `pg_roles` for the normalised name first and fall back to the
12
+ * raw database name for legacy installations.
13
+ */
5
14
  const getDatabaseName = async (pool: CommonQueryMethods) => {
6
15
  const { currentDatabase } = await pool.one<{ currentDatabase: string }>(sql`
7
16
  select current_database();
8
17
  `);
9
18
 
10
- return currentDatabase.replaceAll('-', '_');
19
+ const normalized = currentDatabase.replaceAll('-', '_');
20
+
21
+ if (normalized === currentDatabase) {
22
+ return currentDatabase;
23
+ }
24
+
25
+ const { exists } = await pool.one<{ exists: boolean }>(sql`
26
+ select exists(
27
+ select 1 from pg_roles where rolname = ${'logto_tenant_' + normalized}
28
+ ) as exists;
29
+ `);
30
+
31
+ return exists ? normalized : currentDatabase;
11
32
  };
12
33
 
13
34
  /**
@@ -0,0 +1,19 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ const alteration = {
3
+ up: async (pool) => {
4
+ await pool.query(sql `
5
+ alter table users
6
+ add column password_updated_at timestamptz;
7
+
8
+ alter table sign_in_experiences
9
+ add column password_expiration jsonb not null default '{}'::jsonb;
10
+ `);
11
+ },
12
+ down: async (pool) => {
13
+ await pool.query(sql `
14
+ alter table users drop column password_updated_at;
15
+ alter table sign_in_experiences drop column password_expiration;
16
+ `);
17
+ },
18
+ };
19
+ export default alteration;
@@ -0,0 +1,14 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ const alteration = {
3
+ up: async (pool) => {
4
+ await pool.query(sql `
5
+ alter table users add column is_password_expired boolean not null default false;
6
+ `);
7
+ },
8
+ down: async (pool) => {
9
+ await pool.query(sql `
10
+ alter table users drop column is_password_expired;
11
+ `);
12
+ },
13
+ };
14
+ export default alteration;
@@ -0,0 +1,21 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ const alteration = {
3
+ beforeUp: async (pool) => {
4
+ await pool.query(sql `
5
+ drop index concurrently if exists oidc_model_instances__model_name_payload_grant_id;
6
+ `);
7
+ },
8
+ up: async () => {
9
+ /** `concurrently` cannot be used inside a transaction. */
10
+ },
11
+ beforeDown: async (pool) => {
12
+ await pool.query(sql `
13
+ create index concurrently if not exists oidc_model_instances__model_name_payload_grant_id
14
+ on oidc_model_instances (tenant_id, model_name, (payload->>'grantId'));
15
+ `);
16
+ },
17
+ down: async () => {
18
+ /** `concurrently` cannot be used inside a transaction. */
19
+ },
20
+ };
21
+ export default alteration;
@@ -0,0 +1,37 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ const alteration = {
3
+ // `create index concurrently` cannot run in a transaction; the runner wraps up/down in one.
4
+ beforeUp: async (pool) => {
5
+ await pool.query(sql `
6
+ create index concurrently users__tenant_lower_username
7
+ on users (tenant_id, lower(username))
8
+ where username is not null;
9
+ `);
10
+ },
11
+ up: async (pool) => {
12
+ // The default must be inlined: DDL cannot use bound parameters.
13
+ await pool.query(sql `
14
+ alter table sign_in_experiences
15
+ add column username_policy jsonb not null default '{
16
+ "caseSensitive": true,
17
+ "minLength": 1,
18
+ "maxLength": 128,
19
+ "allowedChars": {
20
+ "lowercase": true,
21
+ "uppercase": true,
22
+ "numbers": true,
23
+ "underscore": true
24
+ }
25
+ }'::jsonb;
26
+ `);
27
+ },
28
+ beforeDown: async (pool) => {
29
+ await pool.query(sql `drop index concurrently users__tenant_lower_username;`);
30
+ },
31
+ down: async (pool) => {
32
+ await pool.query(sql `
33
+ alter table sign_in_experiences drop column username_policy;
34
+ `);
35
+ },
36
+ };
37
+ export default alteration;
@@ -0,0 +1,16 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ const alteration = {
3
+ up: async (pool) => {
4
+ await pool.query(sql `
5
+ alter table sign_in_experiences
6
+ alter column sign_up_profile_fields set default '[]'::jsonb;
7
+ `);
8
+ },
9
+ down: async (pool) => {
10
+ await pool.query(sql `
11
+ alter table sign_in_experiences
12
+ alter column sign_up_profile_fields drop default;
13
+ `);
14
+ },
15
+ };
16
+ export default alteration;
@@ -0,0 +1,15 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ const alteration = {
3
+ up: async (pool) => {
4
+ await pool.query(sql `
5
+ alter table sign_in_experiences
6
+ add column if not exists verification_code_policy jsonb not null default '{}'::jsonb;
7
+ `);
8
+ },
9
+ down: async (pool) => {
10
+ await pool.query(sql `
11
+ alter table sign_in_experiences drop column if exists verification_code_policy;
12
+ `);
13
+ },
14
+ };
15
+ export default alteration;
@@ -0,0 +1,21 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ const alteration = {
3
+ beforeUp: async (pool) => {
4
+ await pool.query(sql `
5
+ create index concurrently if not exists sentinel_activities__created_at
6
+ on sentinel_activities (created_at);
7
+ `);
8
+ },
9
+ up: async () => {
10
+ /** `concurrently` cannot be used inside a transaction. */
11
+ },
12
+ beforeDown: async (pool) => {
13
+ await pool.query(sql `
14
+ drop index concurrently if exists sentinel_activities__created_at;
15
+ `);
16
+ },
17
+ down: async () => {
18
+ /** `concurrently` cannot be used inside a transaction. */
19
+ },
20
+ };
21
+ export default alteration;
@@ -0,0 +1,23 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ const adminTenantId = 'admin';
3
+ const alteration = {
4
+ up: async (pool) => {
5
+ await pool.query(sql `
6
+ update account_centers
7
+ set profile_fields = '[{"name": "name"}, {"name": "avatar"}]'::jsonb
8
+ where tenant_id = ${adminTenantId}
9
+ and id = 'default'
10
+ and profile_fields is null
11
+ `);
12
+ },
13
+ down: async (pool) => {
14
+ await pool.query(sql `
15
+ update account_centers
16
+ set profile_fields = null
17
+ where tenant_id = ${adminTenantId}
18
+ and id = 'default'
19
+ and profile_fields = '[{"name": "name"}, {"name": "avatar"}]'::jsonb
20
+ `);
21
+ },
22
+ };
23
+ export default alteration;
@@ -0,0 +1,32 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ const alteration = {
3
+ beforeUp: async (pool) => {
4
+ // Cover the windowed hosted-email usage count (`where tenant_id=? and type=? and created_at>=?`) by
5
+ // adding `created_at` to the existing `(tenant_id, type)` index, then drop the now-redundant
6
+ // 2-column index (a left-prefix subset of the new one). `concurrently` keeps the write path
7
+ // unlocked — this unbounded table is written on every send.
8
+ await pool.query(sql `
9
+ create index concurrently if not exists service_logs__tenant_id__type__created_at
10
+ on service_logs (tenant_id, type, created_at);
11
+ `);
12
+ await pool.query(sql `
13
+ drop index concurrently if exists service_logs__tenant_id__type;
14
+ `);
15
+ },
16
+ up: async () => {
17
+ /** `concurrently` cannot be used inside a transaction. */
18
+ },
19
+ beforeDown: async (pool) => {
20
+ await pool.query(sql `
21
+ create index concurrently if not exists service_logs__tenant_id__type
22
+ on service_logs (tenant_id, type);
23
+ `);
24
+ await pool.query(sql `
25
+ drop index concurrently if exists service_logs__tenant_id__type__created_at;
26
+ `);
27
+ },
28
+ down: async () => {
29
+ /** `concurrently` cannot be used inside a transaction. */
30
+ },
31
+ };
32
+ export default alteration;
@@ -0,0 +1,16 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ const alteration = {
3
+ up: async (pool) => {
4
+ await pool.query(sql `
5
+ alter table domains
6
+ add column verification_files jsonb not null default '[]'::jsonb;
7
+ `);
8
+ },
9
+ down: async (pool) => {
10
+ await pool.query(sql `
11
+ alter table domains
12
+ drop column verification_files;
13
+ `);
14
+ },
15
+ };
16
+ export default alteration;
@@ -0,0 +1,29 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ const alteration = {
3
+ beforeUp: async (pool) => {
4
+ // Support the age-based `service_logs` retention prune's global oldest-first scan
5
+ // (`where created_at < ? order by created_at asc limit ?`). The existing
6
+ // `(tenant_id, type, created_at)` index leads with `tenant_id`, so it can't drive a
7
+ // tenant-agnostic time-range scan. `concurrently` keeps the write path unlocked — this
8
+ // unbounded table is written on every send.
9
+ //
10
+ // No `if not exists`: fail loud if the index already exists (e.g. a leftover invalid build from a
11
+ // prior failed concurrent creation) instead of silently skipping.
12
+ await pool.query(sql `
13
+ create index concurrently service_logs__created_at
14
+ on service_logs (created_at);
15
+ `);
16
+ },
17
+ up: async () => {
18
+ /** `concurrently` cannot be used inside a transaction. */
19
+ },
20
+ beforeDown: async (pool) => {
21
+ await pool.query(sql `
22
+ drop index concurrently if exists service_logs__created_at;
23
+ `);
24
+ },
25
+ down: async () => {
26
+ /** `concurrently` cannot be used inside a transaction. */
27
+ },
28
+ };
29
+ export default alteration;
@@ -0,0 +1,33 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ import { applyTableRls, dropTableRls } from './utils/1704934999-tables.js';
3
+ const alteration = {
4
+ up: async (pool) => {
5
+ await pool.query(sql `
6
+ create table saml_sso_connector_signing_keys (
7
+ id varchar(21) not null,
8
+ tenant_id varchar(21) not null
9
+ references tenants (id) on update cascade on delete cascade,
10
+ sso_connector_id varchar(128) not null
11
+ references sso_connectors (id) on update cascade on delete cascade,
12
+ private_key text not null,
13
+ certificate text not null,
14
+ created_at timestamptz not null default now(),
15
+ expires_at timestamptz not null,
16
+ active boolean not null,
17
+ primary key (tenant_id, sso_connector_id, id)
18
+ );
19
+
20
+ create unique index saml_sso_connector_signing_keys__unique_active
21
+ on saml_sso_connector_signing_keys (tenant_id, sso_connector_id, active)
22
+ where active;
23
+ `);
24
+ await applyTableRls(pool, 'saml_sso_connector_signing_keys');
25
+ },
26
+ down: async (pool) => {
27
+ await dropTableRls(pool, 'saml_sso_connector_signing_keys');
28
+ await pool.query(sql `
29
+ drop table saml_sso_connector_signing_keys;
30
+ `);
31
+ },
32
+ };
33
+ export default alteration;