@logto/schemas 1.41.0 → 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 (59) hide show
  1. package/alterations/1.42.0-1783916980-add-domain-verification-files.ts +20 -0
  2. package/alterations/1.42.0-1784619793-add-service-logs-created-at-index.ts +33 -0
  3. package/alterations/1.42.0-1784620000-add-saml-sso-connector-signing-keys-table.ts +38 -0
  4. package/alterations/utils/1704934999-tables.ts +22 -1
  5. package/alterations-js/1.42.0-1783916980-add-domain-verification-files.js +16 -0
  6. package/alterations-js/1.42.0-1784619793-add-service-logs-created-at-index.js +29 -0
  7. package/alterations-js/1.42.0-1784620000-add-saml-sso-connector-signing-keys-table.js +33 -0
  8. package/alterations-js/utils/1704934999-tables.js +19 -1
  9. package/lib/consts/experience.d.ts +1 -0
  10. package/lib/consts/experience.js +1 -0
  11. package/lib/db-entries/domain.d.ts +4 -2
  12. package/lib/db-entries/domain.js +5 -1
  13. package/lib/db-entries/index.d.ts +1 -0
  14. package/lib/db-entries/index.js +1 -0
  15. package/lib/db-entries/saml-sso-connector-signing-key.d.ts +32 -0
  16. package/lib/db-entries/saml-sso-connector-signing-key.js +49 -0
  17. package/lib/foundations/jsonb-types/custom-domain.d.ts +57 -0
  18. package/lib/foundations/jsonb-types/custom-domain.js +51 -0
  19. package/lib/foundations/jsonb-types/custom-domain.test.d.ts +1 -0
  20. package/lib/foundations/jsonb-types/custom-domain.test.js +38 -0
  21. package/lib/foundations/jsonb-types/hooks.d.ts +4 -4
  22. package/lib/foundations/jsonb-types/hooks.js +1 -0
  23. package/lib/foundations/jsonb-types/one-time-tokens.d.ts +5 -0
  24. package/lib/foundations/jsonb-types/one-time-tokens.js +2 -0
  25. package/lib/foundations/jsonb-types/sign-in-experience.d.ts +16 -1
  26. package/lib/foundations/jsonb-types/sign-in-experience.js +1 -0
  27. package/lib/types/application.d.ts +131 -1
  28. package/lib/types/application.js +8 -0
  29. package/lib/types/custom-profile-fields.d.ts +1 -1
  30. package/lib/types/domain.d.ts +9 -0
  31. package/lib/types/hook.d.ts +9 -2
  32. package/lib/types/interaction-event.d.ts +11 -0
  33. package/lib/types/interaction-event.js +12 -0
  34. package/lib/types/interactions.d.ts +2 -11
  35. package/lib/types/interactions.js +2 -11
  36. package/lib/types/log/action.d.ts +7 -0
  37. package/lib/types/log/action.js +6 -0
  38. package/lib/types/log/index.d.ts +5 -2
  39. package/lib/types/log/index.js +1 -0
  40. package/lib/types/logto-config/action.d.ts +963 -0
  41. package/lib/types/logto-config/action.js +77 -0
  42. package/lib/types/logto-config/index.d.ts +32 -19
  43. package/lib/types/logto-config/index.js +7 -7
  44. package/lib/types/logto-config/index.test.js +9 -9
  45. package/lib/types/logto-config/jwt-customizer.d.ts +29 -0
  46. package/lib/types/organization.d.ts +136 -1
  47. package/lib/types/organization.js +3 -2
  48. package/lib/types/sign-in-experience.d.ts +2 -4
  49. package/lib/types/sign-in-experience.js +1 -1
  50. package/lib/types/sso-connector.d.ts +58 -0
  51. package/lib/types/sso-connector.js +12 -0
  52. package/lib/types/user-sessions.d.ts +78 -29
  53. package/lib/types/verification-records/one-time-token-verification.d.ts +5 -0
  54. package/package.json +5 -5
  55. package/tables/domains.sql +1 -0
  56. package/tables/saml_sso_connector_signing_keys.sql +21 -0
  57. package/tables/service_logs.sql +5 -0
  58. package/lib/types/logto-config/inline-hook.d.ts +0 -76
  59. package/lib/types/logto-config/inline-hook.js +0 -25
@@ -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,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;
@@ -1,10 +1,28 @@
1
1
  import { sql } from '@silverhand/slonik';
2
2
  const getId = (value) => sql.identifier([value]);
3
+ /**
4
+ * Returns the suffix used in the base Logto role name for the current database
5
+ * (`logto_tenant_<suffix>`).
6
+ *
7
+ * Historically the suffix was the raw database name (which may contain hyphens). A later
8
+ * convention normalised it by replacing hyphens with underscores. To remain compatible with
9
+ * both conventions we probe `pg_roles` for the normalised name first and fall back to the
10
+ * raw database name for legacy installations.
11
+ */
3
12
  const getDatabaseName = async (pool) => {
4
13
  const { currentDatabase } = await pool.one(sql `
5
14
  select current_database();
6
15
  `);
7
- return currentDatabase.replaceAll('-', '_');
16
+ const normalized = currentDatabase.replaceAll('-', '_');
17
+ if (normalized === currentDatabase) {
18
+ return currentDatabase;
19
+ }
20
+ const { exists } = await pool.one(sql `
21
+ select exists(
22
+ select 1 from pg_roles where rolname = ${'logto_tenant_' + normalized}
23
+ ) as exists;
24
+ `);
25
+ return exists ? normalized : currentDatabase;
8
26
  };
9
27
  /**
10
28
  * A function to call after the table is created. It will apply the necessary row-level security
@@ -10,6 +10,7 @@ export declare const experience: Readonly<{
10
10
  readonly identifierRegister: "identifier-register";
11
11
  readonly switchAccount: "switch-account";
12
12
  readonly oneTimeToken: "one-time-token";
13
+ readonly accountSuspended: "account-suspended";
13
14
  }>;
14
15
  }>;
15
16
  /** `postMessage` sender tag from admin Console sign-in experience live preview. */
@@ -9,6 +9,7 @@ const routes = Object.freeze({
9
9
  identifierRegister: 'identifier-register',
10
10
  switchAccount: 'switch-account',
11
11
  oneTimeToken: 'one-time-token',
12
+ accountSuspended: 'account-suspended',
12
13
  });
13
14
  export const experience = Object.freeze({
14
15
  routes,
@@ -1,4 +1,4 @@
1
- import { DomainStatus, DomainDnsRecords, CloudflareData, GeneratedSchema } from './../foundations/index.js';
1
+ import { DomainStatus, DomainDnsRecords, DomainVerificationFiles, CloudflareData, GeneratedSchema } from './../foundations/index.js';
2
2
  /**
3
3
  *
4
4
  * @remarks This is a type for database creation.
@@ -11,6 +11,7 @@ export type CreateDomain = {
11
11
  status?: DomainStatus;
12
12
  errorMessage?: string | null;
13
13
  dnsRecords?: DomainDnsRecords;
14
+ verificationFiles?: DomainVerificationFiles;
14
15
  cloudflareData?: CloudflareData | null;
15
16
  updatedAt?: number;
16
17
  createdAt?: number;
@@ -22,9 +23,10 @@ export type Domain = {
22
23
  status: DomainStatus;
23
24
  errorMessage: string | null;
24
25
  dnsRecords: DomainDnsRecords;
26
+ verificationFiles: DomainVerificationFiles;
25
27
  cloudflareData: CloudflareData | null;
26
28
  updatedAt: number;
27
29
  createdAt: number;
28
30
  };
29
- export type DomainKeys = 'tenantId' | 'id' | 'domain' | 'status' | 'errorMessage' | 'dnsRecords' | 'cloudflareData' | 'updatedAt' | 'createdAt';
31
+ export type DomainKeys = 'tenantId' | 'id' | 'domain' | 'status' | 'errorMessage' | 'dnsRecords' | 'verificationFiles' | 'cloudflareData' | 'updatedAt' | 'createdAt';
30
32
  export declare const Domains: GeneratedSchema<DomainKeys, CreateDomain, Domain, 'domains', 'domain'>;
@@ -1,6 +1,6 @@
1
1
  // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2
2
  import { z } from 'zod';
3
- import { domainStatusGuard, domainDnsRecordsGuard, cloudflareDataGuard } from './../foundations/index.js';
3
+ import { domainStatusGuard, domainDnsRecordsGuard, domainVerificationFilesGuard, cloudflareDataGuard } from './../foundations/index.js';
4
4
  const createGuard = z.object({
5
5
  tenantId: z.string().max(21).optional(),
6
6
  id: z.string().min(1).max(21),
@@ -8,6 +8,7 @@ const createGuard = z.object({
8
8
  status: domainStatusGuard.optional(),
9
9
  errorMessage: z.string().max(1024).nullable().optional(),
10
10
  dnsRecords: domainDnsRecordsGuard.optional(),
11
+ verificationFiles: domainVerificationFilesGuard.optional(),
11
12
  cloudflareData: cloudflareDataGuard.nullable().optional(),
12
13
  updatedAt: z.number().optional(),
13
14
  createdAt: z.number().optional(),
@@ -19,6 +20,7 @@ const guard = z.object({
19
20
  status: domainStatusGuard,
20
21
  errorMessage: z.string().max(1024).nullable(),
21
22
  dnsRecords: domainDnsRecordsGuard,
23
+ verificationFiles: domainVerificationFilesGuard,
22
24
  cloudflareData: cloudflareDataGuard.nullable(),
23
25
  updatedAt: z.number(),
24
26
  createdAt: z.number(),
@@ -33,6 +35,7 @@ export const Domains = Object.freeze({
33
35
  status: 'status',
34
36
  errorMessage: 'error_message',
35
37
  dnsRecords: 'dns_records',
38
+ verificationFiles: 'verification_files',
36
39
  cloudflareData: 'cloudflare_data',
37
40
  updatedAt: 'updated_at',
38
41
  createdAt: 'created_at',
@@ -44,6 +47,7 @@ export const Domains = Object.freeze({
44
47
  'status',
45
48
  'errorMessage',
46
49
  'dnsRecords',
50
+ 'verificationFiles',
47
51
  'cloudflareData',
48
52
  'updatedAt',
49
53
  'createdAt',
@@ -55,6 +55,7 @@ export * from './roles-scope.js';
55
55
  export * from './saml-application-config.js';
56
56
  export * from './saml-application-secret.js';
57
57
  export * from './saml-application-session.js';
58
+ export * from './saml-sso-connector-signing-key.js';
58
59
  export * from './scope.js';
59
60
  export * from './secret-enterprise-sso-connector-relation.js';
60
61
  export * from './secret-social-connector-relation.js';
@@ -56,6 +56,7 @@ export * from './roles-scope.js';
56
56
  export * from './saml-application-config.js';
57
57
  export * from './saml-application-secret.js';
58
58
  export * from './saml-application-session.js';
59
+ export * from './saml-sso-connector-signing-key.js';
59
60
  export * from './scope.js';
60
61
  export * from './secret-enterprise-sso-connector-relation.js';
61
62
  export * from './secret-social-connector-relation.js';
@@ -0,0 +1,32 @@
1
+ import { GeneratedSchema } from './../foundations/index.js';
2
+ /**
3
+ *
4
+ * @remarks This is a type for database creation.
5
+ * @see {@link SamlSsoConnectorSigningKey} for the original type.
6
+ */
7
+ export type CreateSamlSsoConnectorSigningKey = {
8
+ id: string;
9
+ tenantId?: string;
10
+ ssoConnectorId: string;
11
+ /** The SP private key (plaintext PEM) used to sign the SAML AuthnRequest. Tenant-isolated by RLS and never returned by the API. */
12
+ privateKey: string;
13
+ /** The PEM-encoded self-signed SP certificate matching the private key. Public and safe to expose. */
14
+ certificate: string;
15
+ createdAt?: number;
16
+ expiresAt: number;
17
+ active: boolean;
18
+ };
19
+ export type SamlSsoConnectorSigningKey = {
20
+ id: string;
21
+ tenantId: string;
22
+ ssoConnectorId: string;
23
+ /** The SP private key (plaintext PEM) used to sign the SAML AuthnRequest. Tenant-isolated by RLS and never returned by the API. */
24
+ privateKey: string;
25
+ /** The PEM-encoded self-signed SP certificate matching the private key. Public and safe to expose. */
26
+ certificate: string;
27
+ createdAt: number;
28
+ expiresAt: number;
29
+ active: boolean;
30
+ };
31
+ export type SamlSsoConnectorSigningKeyKeys = 'id' | 'tenantId' | 'ssoConnectorId' | 'privateKey' | 'certificate' | 'createdAt' | 'expiresAt' | 'active';
32
+ export declare const SamlSsoConnectorSigningKeys: GeneratedSchema<SamlSsoConnectorSigningKeyKeys, CreateSamlSsoConnectorSigningKey, SamlSsoConnectorSigningKey, 'saml_sso_connector_signing_keys', 'saml_sso_connector_signing_key'>;
@@ -0,0 +1,49 @@
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
+ ssoConnectorId: z.string().min(1).max(128),
7
+ privateKey: z.string().min(1),
8
+ certificate: z.string().min(1),
9
+ createdAt: z.number().optional(),
10
+ expiresAt: z.number(),
11
+ active: z.boolean(),
12
+ });
13
+ const guard = z.object({
14
+ id: z.string().min(1).max(21),
15
+ tenantId: z.string().max(21),
16
+ ssoConnectorId: z.string().min(1).max(128),
17
+ privateKey: z.string().min(1),
18
+ certificate: z.string().min(1),
19
+ createdAt: z.number(),
20
+ expiresAt: z.number(),
21
+ active: z.boolean(),
22
+ });
23
+ export const SamlSsoConnectorSigningKeys = Object.freeze({
24
+ table: 'saml_sso_connector_signing_keys',
25
+ tableSingular: 'saml_sso_connector_signing_key',
26
+ fields: {
27
+ id: 'id',
28
+ tenantId: 'tenant_id',
29
+ ssoConnectorId: 'sso_connector_id',
30
+ privateKey: 'private_key',
31
+ certificate: 'certificate',
32
+ createdAt: 'created_at',
33
+ expiresAt: 'expires_at',
34
+ active: 'active',
35
+ },
36
+ fieldKeys: [
37
+ 'id',
38
+ 'tenantId',
39
+ 'ssoConnectorId',
40
+ 'privateKey',
41
+ 'certificate',
42
+ 'createdAt',
43
+ 'expiresAt',
44
+ 'active',
45
+ ],
46
+ createGuard,
47
+ guard,
48
+ updateGuard: guard.partial(),
49
+ });
@@ -1,4 +1,61 @@
1
1
  import { z } from 'zod';
2
+ export declare enum DomainVerificationFileContentType {
3
+ Text = "text/plain",
4
+ Json = "application/json"
5
+ }
6
+ export declare const domainVerificationFilePathGuard: z.ZodEffects<z.ZodString, string, string>;
7
+ export declare const domainVerificationFileGuard: z.ZodEffects<z.ZodObject<{
8
+ path: z.ZodEffects<z.ZodString, string, string>;
9
+ content: z.ZodString;
10
+ contentType: z.ZodNativeEnum<typeof DomainVerificationFileContentType>;
11
+ }, "strip", z.ZodTypeAny, {
12
+ path: string;
13
+ content: string;
14
+ contentType: DomainVerificationFileContentType;
15
+ }, {
16
+ path: string;
17
+ content: string;
18
+ contentType: DomainVerificationFileContentType;
19
+ }>, {
20
+ path: string;
21
+ content: string;
22
+ contentType: DomainVerificationFileContentType;
23
+ }, {
24
+ path: string;
25
+ content: string;
26
+ contentType: DomainVerificationFileContentType;
27
+ }>;
28
+ export type DomainVerificationFile = z.infer<typeof domainVerificationFileGuard>;
29
+ export declare const domainVerificationFilesGuard: z.ZodEffects<z.ZodArray<z.ZodEffects<z.ZodObject<{
30
+ path: z.ZodEffects<z.ZodString, string, string>;
31
+ content: z.ZodString;
32
+ contentType: z.ZodNativeEnum<typeof DomainVerificationFileContentType>;
33
+ }, "strip", z.ZodTypeAny, {
34
+ path: string;
35
+ content: string;
36
+ contentType: DomainVerificationFileContentType;
37
+ }, {
38
+ path: string;
39
+ content: string;
40
+ contentType: DomainVerificationFileContentType;
41
+ }>, {
42
+ path: string;
43
+ content: string;
44
+ contentType: DomainVerificationFileContentType;
45
+ }, {
46
+ path: string;
47
+ content: string;
48
+ contentType: DomainVerificationFileContentType;
49
+ }>, "many">, {
50
+ path: string;
51
+ content: string;
52
+ contentType: DomainVerificationFileContentType;
53
+ }[], {
54
+ path: string;
55
+ content: string;
56
+ contentType: DomainVerificationFileContentType;
57
+ }[]>;
58
+ export type DomainVerificationFiles = z.infer<typeof domainVerificationFilesGuard>;
2
59
  export declare const domainDnsRecordGuard: z.ZodObject<{
3
60
  name: z.ZodString;
4
61
  type: z.ZodString;
@@ -1,4 +1,55 @@
1
1
  import { z } from 'zod';
2
+ export var DomainVerificationFileContentType;
3
+ (function (DomainVerificationFileContentType) {
4
+ DomainVerificationFileContentType["Text"] = "text/plain";
5
+ DomainVerificationFileContentType["Json"] = "application/json";
6
+ })(DomainVerificationFileContentType || (DomainVerificationFileContentType = {}));
7
+ export const domainVerificationFilePathGuard = z
8
+ .string()
9
+ .min(1)
10
+ .max(256)
11
+ .refine((value) => /^\/[\w-][\w.-]*\.[\w-][\w.-]*$/.test(value) ||
12
+ /^\/\.well-known(?:\/[\w-][\w.-]*)+$/.test(value), 'The path must point to a file with an extension at the domain root or to a path under /.well-known/.');
13
+ export const domainVerificationFileGuard = z
14
+ .object({
15
+ path: domainVerificationFilePathGuard,
16
+ content: z
17
+ .string()
18
+ .min(1)
19
+ .max(16 * 1024),
20
+ contentType: z.nativeEnum(DomainVerificationFileContentType),
21
+ })
22
+ .superRefine(({ content, contentType }, context) => {
23
+ if (contentType !== DomainVerificationFileContentType.Json) {
24
+ return;
25
+ }
26
+ try {
27
+ JSON.parse(content);
28
+ }
29
+ catch {
30
+ context.addIssue({
31
+ code: z.ZodIssueCode.custom,
32
+ message: 'The content must be valid JSON for the application/json content type.',
33
+ path: ['content'],
34
+ });
35
+ }
36
+ });
37
+ export const domainVerificationFilesGuard = domainVerificationFileGuard
38
+ .array()
39
+ .max(10)
40
+ .superRefine((files, context) => {
41
+ const paths = new Set();
42
+ for (const [index, file] of files.entries()) {
43
+ if (paths.has(file.path)) {
44
+ context.addIssue({
45
+ code: z.ZodIssueCode.custom,
46
+ message: 'Verification file paths must be unique.',
47
+ path: [index, 'path'],
48
+ });
49
+ }
50
+ paths.add(file.path);
51
+ }
52
+ });
2
53
  export const domainDnsRecordGuard = z.object({
3
54
  name: z.string(),
4
55
  type: z.string(),
@@ -0,0 +1,38 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { DomainVerificationFileContentType, domainVerificationFilesGuard, } from './custom-domain.js';
3
+ describe('domainVerificationFilesGuard', () => {
4
+ it.each([
5
+ '/MP_verify_abc123.txt',
6
+ '/_domainconnect.txt',
7
+ '/-verification.txt',
8
+ '/google-site-verification.html',
9
+ '/.well-known/apple-developer-merchantid-domain-association',
10
+ '/.well-known/acme-challenge/token_123',
11
+ '/.well-known/acme-challenge/-token_123',
12
+ '/.well-known/acme-challenge/_token_123',
13
+ ])('accepts verification file path %s', (path) => {
14
+ expect(domainVerificationFilesGuard.safeParse([
15
+ { path, content: 'verification-content', contentType: 'text/plain' },
16
+ ]).success).toBe(true);
17
+ });
18
+ it.each(['/api/file.txt', '/../secret.txt', '/nested/file.txt', '/file', '/file%2Etxt'])('rejects verification file path %s', (path) => {
19
+ expect(domainVerificationFilesGuard.safeParse([
20
+ { path, content: 'verification-content', contentType: 'text/plain' },
21
+ ]).success).toBe(false);
22
+ });
23
+ it('requires valid JSON for JSON responses', () => {
24
+ expect(domainVerificationFilesGuard.safeParse([
25
+ {
26
+ path: '/.well-known/verification.json',
27
+ content: '{invalid-json}',
28
+ contentType: DomainVerificationFileContentType.Json,
29
+ },
30
+ ]).success).toBe(false);
31
+ });
32
+ it('rejects duplicate paths', () => {
33
+ expect(domainVerificationFilesGuard.safeParse([
34
+ { path: '/verify.txt', content: 'one', contentType: 'text/plain' },
35
+ { path: '/verify.txt', content: 'two', contentType: 'text/plain' },
36
+ ]).success).toBe(false);
37
+ });
38
+ });
@@ -30,14 +30,14 @@ declare enum DataHookDetailMutationType {
30
30
  type BasicDataHookEvent = `${DataHookSchema}.${DataHookBasicMutationType}`;
31
31
  type CustomDataHookMutableSchema = `${DataHookSchema}.Data` | `${DataHookSchema.User}.SuspensionStatus` | `${DataHookSchema.Role}.Scopes` | `${DataHookSchema.Organization}.Membership` | `${DataHookSchema.OrganizationRole}.Scopes`;
32
32
  type DataHookPropertyUpdateEvent = `${CustomDataHookMutableSchema}.${DataHookDetailMutationType.Updated}`;
33
- export type ExceptionHookEvent = 'Identifier.Lockout' | 'Message.RateLimited';
33
+ export type ExceptionHookEvent = 'Identifier.Lockout' | 'Message.RateLimited' | 'Grant.LimitExceeded';
34
34
  export type DataHookEvent = BasicDataHookEvent | DataHookPropertyUpdateEvent;
35
35
  /** The hook event values that can be registered. */
36
- export declare const hookEvents: readonly [InteractionHookEvent.PostRegister, InteractionHookEvent.PostSignIn, InteractionHookEvent.PostSignInAdaptiveMfaTriggered, InteractionHookEvent.PostResetPassword, "User.Created", "User.Deleted", "User.Data.Updated", "User.SuspensionStatus.Updated", "Role.Created", "Role.Deleted", "Role.Data.Updated", "Role.Scopes.Updated", "Scope.Created", "Scope.Deleted", "Scope.Data.Updated", "Organization.Created", "Organization.Deleted", "Organization.Data.Updated", "Organization.Membership.Updated", "OrganizationRole.Created", "OrganizationRole.Deleted", "OrganizationRole.Data.Updated", "OrganizationRole.Scopes.Updated", "OrganizationScope.Created", "OrganizationScope.Deleted", "OrganizationScope.Data.Updated", "Identifier.Lockout", "Message.RateLimited"];
36
+ export declare const hookEvents: readonly [InteractionHookEvent.PostRegister, InteractionHookEvent.PostSignIn, InteractionHookEvent.PostSignInAdaptiveMfaTriggered, InteractionHookEvent.PostResetPassword, "User.Created", "User.Deleted", "User.Data.Updated", "User.SuspensionStatus.Updated", "Role.Created", "Role.Deleted", "Role.Data.Updated", "Role.Scopes.Updated", "Scope.Created", "Scope.Deleted", "Scope.Data.Updated", "Organization.Created", "Organization.Deleted", "Organization.Data.Updated", "Organization.Membership.Updated", "OrganizationRole.Created", "OrganizationRole.Deleted", "OrganizationRole.Data.Updated", "OrganizationRole.Scopes.Updated", "OrganizationScope.Created", "OrganizationScope.Deleted", "OrganizationScope.Data.Updated", "Identifier.Lockout", "Message.RateLimited", "Grant.LimitExceeded"];
37
37
  /** The type of hook event values that can be registered. */
38
38
  export type HookEvent = (typeof hookEvents)[number];
39
- export declare const hookEventGuard: z.ZodEnum<[InteractionHookEvent.PostRegister, InteractionHookEvent.PostSignIn, InteractionHookEvent.PostSignInAdaptiveMfaTriggered, InteractionHookEvent.PostResetPassword, "User.Created", "User.Deleted", "User.Data.Updated", "User.SuspensionStatus.Updated", "Role.Created", "Role.Deleted", "Role.Data.Updated", "Role.Scopes.Updated", "Scope.Created", "Scope.Deleted", "Scope.Data.Updated", "Organization.Created", "Organization.Deleted", "Organization.Data.Updated", "Organization.Membership.Updated", "OrganizationRole.Created", "OrganizationRole.Deleted", "OrganizationRole.Data.Updated", "OrganizationRole.Scopes.Updated", "OrganizationScope.Created", "OrganizationScope.Deleted", "OrganizationScope.Data.Updated", "Identifier.Lockout", "Message.RateLimited"]>;
40
- export declare const hookEventsGuard: z.ZodArray<z.ZodEnum<[InteractionHookEvent.PostRegister, InteractionHookEvent.PostSignIn, InteractionHookEvent.PostSignInAdaptiveMfaTriggered, InteractionHookEvent.PostResetPassword, "User.Created", "User.Deleted", "User.Data.Updated", "User.SuspensionStatus.Updated", "Role.Created", "Role.Deleted", "Role.Data.Updated", "Role.Scopes.Updated", "Scope.Created", "Scope.Deleted", "Scope.Data.Updated", "Organization.Created", "Organization.Deleted", "Organization.Data.Updated", "Organization.Membership.Updated", "OrganizationRole.Created", "OrganizationRole.Deleted", "OrganizationRole.Data.Updated", "OrganizationRole.Scopes.Updated", "OrganizationScope.Created", "OrganizationScope.Deleted", "OrganizationScope.Data.Updated", "Identifier.Lockout", "Message.RateLimited"]>, "many">;
39
+ export declare const hookEventGuard: z.ZodEnum<[InteractionHookEvent.PostRegister, InteractionHookEvent.PostSignIn, InteractionHookEvent.PostSignInAdaptiveMfaTriggered, InteractionHookEvent.PostResetPassword, "User.Created", "User.Deleted", "User.Data.Updated", "User.SuspensionStatus.Updated", "Role.Created", "Role.Deleted", "Role.Data.Updated", "Role.Scopes.Updated", "Scope.Created", "Scope.Deleted", "Scope.Data.Updated", "Organization.Created", "Organization.Deleted", "Organization.Data.Updated", "Organization.Membership.Updated", "OrganizationRole.Created", "OrganizationRole.Deleted", "OrganizationRole.Data.Updated", "OrganizationRole.Scopes.Updated", "OrganizationScope.Created", "OrganizationScope.Deleted", "OrganizationScope.Data.Updated", "Identifier.Lockout", "Message.RateLimited", "Grant.LimitExceeded"]>;
40
+ export declare const hookEventsGuard: z.ZodArray<z.ZodEnum<[InteractionHookEvent.PostRegister, InteractionHookEvent.PostSignIn, InteractionHookEvent.PostSignInAdaptiveMfaTriggered, InteractionHookEvent.PostResetPassword, "User.Created", "User.Deleted", "User.Data.Updated", "User.SuspensionStatus.Updated", "Role.Created", "Role.Deleted", "Role.Data.Updated", "Role.Scopes.Updated", "Scope.Created", "Scope.Deleted", "Scope.Data.Updated", "Organization.Created", "Organization.Deleted", "Organization.Data.Updated", "Organization.Membership.Updated", "OrganizationRole.Created", "OrganizationRole.Deleted", "OrganizationRole.Data.Updated", "OrganizationRole.Scopes.Updated", "OrganizationScope.Created", "OrganizationScope.Deleted", "OrganizationScope.Data.Updated", "Identifier.Lockout", "Message.RateLimited", "Grant.LimitExceeded"]>, "many">;
41
41
  export type HookEvents = z.infer<typeof hookEventsGuard>;
42
42
  export declare const interactionHookEventGuard: z.ZodNativeEnum<typeof InteractionHookEvent>;
43
43
  /**
@@ -63,6 +63,7 @@ export const hookEvents = Object.freeze([
63
63
  'OrganizationScope.Data.Updated',
64
64
  'Identifier.Lockout',
65
65
  'Message.RateLimited',
66
+ 'Grant.LimitExceeded',
66
67
  ]);
67
68
  export const hookEventGuard = z.enum(hookEvents);
68
69
  export const hookEventsGuard = hookEventGuard.array();
@@ -1,13 +1,18 @@
1
1
  import { z } from 'zod';
2
+ import { type InteractionEvent } from '../../types/interaction-event.js';
2
3
  export type OneTimeTokenContext = {
3
4
  jitOrganizationIds?: string[];
5
+ interactionEvent?: InteractionEvent;
4
6
  };
5
7
  export declare const oneTimeTokenContextGuard: z.ZodObject<{
6
8
  jitOrganizationIds: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
9
+ interactionEvent: z.ZodOptional<z.ZodNativeEnum<typeof InteractionEvent>>;
7
10
  }, "strip", z.ZodTypeAny, {
8
11
  jitOrganizationIds?: string[] | undefined;
12
+ interactionEvent?: InteractionEvent | undefined;
9
13
  }, {
10
14
  jitOrganizationIds?: string[] | undefined;
15
+ interactionEvent?: InteractionEvent | undefined;
11
16
  }>;
12
17
  export declare enum OneTimeTokenStatus {
13
18
  Active = "active",
@@ -1,7 +1,9 @@
1
1
  import { z } from 'zod';
2
+ import { eventGuard } from '../../types/interaction-event.js';
2
3
  export const oneTimeTokenContextGuard = z
3
4
  .object({
4
5
  jitOrganizationIds: z.string().array(),
6
+ interactionEvent: eventGuard,
5
7
  })
6
8
  .partial();
7
9
  export var OneTimeTokenStatus;