@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
@@ -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';
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"];
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"]>;
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"]>, "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
  /**
@@ -62,6 +62,8 @@ export const hookEvents = Object.freeze([
62
62
  'OrganizationScope.Deleted',
63
63
  'OrganizationScope.Data.Updated',
64
64
  'Identifier.Lockout',
65
+ 'Message.RateLimited',
66
+ 'Grant.LimitExceeded',
65
67
  ]);
66
68
  export const hookEventGuard = z.enum(hookEvents);
67
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;
@@ -39,7 +39,22 @@ export declare enum SentinelActivityAction {
39
39
  /**
40
40
  * The subject tries to pass a backup code MFA verification.
41
41
  */
42
- MfaBackupCode = "MfaBackupCode"
42
+ MfaBackupCode = "MfaBackupCode",
43
+ /**
44
+ * A verification code is sent to the target (email/phone), e.g. sign-in,
45
+ * register, forgot-password, or identifier-binding codes.
46
+ *
47
+ * Used by the message rate limit to throttle sends, distinct from the
48
+ * failure-based lockout actions above.
49
+ */
50
+ VerificationCodeSend = "VerificationCodeSend",
51
+ /**
52
+ * A non-verification-code message is sent to the target (email/phone),
53
+ * e.g. an organization invitation.
54
+ *
55
+ * Used by the message rate limit to throttle sends.
56
+ */
57
+ MessageSend = "MessageSend"
43
58
  }
44
59
  export declare const sentinelActivityActionGuard: z.ZodNativeEnum<typeof SentinelActivityAction>;
45
60
  export type SentinelActivityPayload = Record<string, unknown>;
@@ -42,6 +42,21 @@ export var SentinelActivityAction;
42
42
  * The subject tries to pass a backup code MFA verification.
43
43
  */
44
44
  SentinelActivityAction["MfaBackupCode"] = "MfaBackupCode";
45
+ /**
46
+ * A verification code is sent to the target (email/phone), e.g. sign-in,
47
+ * register, forgot-password, or identifier-binding codes.
48
+ *
49
+ * Used by the message rate limit to throttle sends, distinct from the
50
+ * failure-based lockout actions above.
51
+ */
52
+ SentinelActivityAction["VerificationCodeSend"] = "VerificationCodeSend";
53
+ /**
54
+ * A non-verification-code message is sent to the target (email/phone),
55
+ * e.g. an organization invitation.
56
+ *
57
+ * Used by the message rate limit to throttle sends.
58
+ */
59
+ SentinelActivityAction["MessageSend"] = "MessageSend";
45
60
  })(SentinelActivityAction || (SentinelActivityAction = {}));
46
61
  export const sentinelActivityActionGuard = z.nativeEnum(SentinelActivityAction);
47
62
  export const sentinelActivityPayloadGuard = z.record(z.unknown());
@@ -193,6 +193,7 @@ export declare const signInGuard: z.ZodObject<{
193
193
  }[];
194
194
  }>;
195
195
  export type SignIn = z.infer<typeof signInGuard>;
196
+ export { defaultUsernamePolicy, usernamePolicyGuard, type UsernamePolicy } from '@logto/core-kit';
196
197
  export type SocialSignIn = {
197
198
  /**
198
199
  * If account linking should be performed when a user signs in with a social identity that is new
@@ -359,36 +360,73 @@ export declare const sentinelPolicyGuard: z.ZodObject<{
359
360
  lockoutDuration?: number | undefined;
360
361
  }>;
361
362
  /**
362
- * Email blocklist policy.
363
+ * Verification code policy.
363
364
  *
364
365
  * @remarks
365
- * This policy is used to block specific email addresses or domains from signing up.
366
+ * This policy controls the expiration duration and maximum retry attempts for verification codes.
366
367
  */
368
+ export type VerificationCodePolicy = {
369
+ /**
370
+ * The duration in seconds that a verification code remains valid.
371
+ * @default 600 (10 minutes)
372
+ */
373
+ expirationDuration?: number;
374
+ /**
375
+ * Maximum number of failed verification attempts allowed before the code is invalidated.
376
+ * @default 10
377
+ */
378
+ maxRetryAttempts?: number;
379
+ };
380
+ export declare const verificationCodePolicyGuard: z.ZodObject<{
381
+ expirationDuration: z.ZodOptional<z.ZodNumber>;
382
+ maxRetryAttempts: z.ZodOptional<z.ZodNumber>;
383
+ }, "strip", z.ZodTypeAny, {
384
+ expirationDuration?: number | undefined;
385
+ maxRetryAttempts?: number | undefined;
386
+ }, {
387
+ expirationDuration?: number | undefined;
388
+ maxRetryAttempts?: number | undefined;
389
+ }>;
367
390
  export type EmailBlocklistPolicy = {
368
391
  blockDisposableAddresses?: boolean;
369
392
  blockSubaddressing?: boolean;
370
393
  /**
371
- * Custom blocklist of email addresses or domains.
394
+ * Custom allowlist of email addresses, domains, or wildcard email address patterns.
395
+ *
396
+ * The matching semantics are the same as {@link customBlocklist}.
397
+ */
398
+ customAllowlist?: string[];
399
+ /**
400
+ * Custom blocklist of email addresses, domains, or wildcard email address patterns.
372
401
  *
373
402
  * @example
374
403
  * Email address: abc@xyx.com
375
404
  *
376
405
  * @example
377
406
  * Domain name: @xyz.com
407
+ *
408
+ * @example
409
+ * Wildcard email address pattern: foo*@example.com
410
+ *
411
+ * @example
412
+ * Wildcard domain pattern: @*.example.com
378
413
  */
379
414
  customBlocklist?: string[];
380
415
  };
381
416
  export declare const emailBlocklistPolicyGuard: z.ZodObject<{
382
417
  blockDisposableAddresses: z.ZodOptional<z.ZodBoolean>;
383
418
  blockSubaddressing: z.ZodOptional<z.ZodBoolean>;
419
+ customAllowlist: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
384
420
  customBlocklist: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
385
421
  }, "strip", z.ZodTypeAny, {
386
422
  blockDisposableAddresses?: boolean | undefined;
387
423
  blockSubaddressing?: boolean | undefined;
424
+ customAllowlist?: string[] | undefined;
388
425
  customBlocklist?: string[] | undefined;
389
426
  }, {
390
427
  blockDisposableAddresses?: boolean | undefined;
391
428
  blockSubaddressing?: boolean | undefined;
429
+ customAllowlist?: string[] | undefined;
392
430
  customBlocklist?: string[] | undefined;
393
431
  }>;
394
432
  export declare enum ForgotPasswordMethod {
@@ -441,4 +479,53 @@ export declare const signUpProfileFieldsGuard: z.ZodArray<z.ZodObject<{
441
479
  name: string;
442
480
  }>, "many">;
443
481
  export type SignUpProfileFields = z.infer<typeof signUpProfileFieldsGuard>;
482
+ /**
483
+ * Password lifecycle policy for configuring password expiration and rotation.
484
+ *
485
+ * @remarks
486
+ * This policy is evaluated server-side during sign-in (after local password verification) to determine
487
+ * whether the user's password has expired.
488
+ *
489
+ * If the password age >= `validPeriodDays`, the sign-in is blocked and the user must reset their
490
+ * password before continuing.
491
+ */
492
+ export type PasswordExpirationPolicy = {
493
+ /**
494
+ * Whether the password expiration policy is enabled.
495
+ * @default false
496
+ */
497
+ enabled?: false;
498
+ } | {
499
+ enabled: true;
500
+ /**
501
+ * Number of days a password is valid before it expires and the user is
502
+ * forced to reset it on sign-in.
503
+ */
504
+ validPeriodDays: number;
505
+ /**
506
+ * Epoch milliseconds when the policy was enabled. Used as the expiry anchor for users
507
+ * that have no `passwordUpdatedAt` (e.g. legacy accounts), so enabling the policy grants
508
+ * them a full valid period instead of expiring them against their account creation date.
509
+ */
510
+ enabledAt?: number;
511
+ };
512
+ export declare const passwordExpirationPolicyGuard: z.ZodUnion<[z.ZodObject<{
513
+ enabled: z.ZodOptional<z.ZodLiteral<false>>;
514
+ }, "strip", z.ZodTypeAny, {
515
+ enabled?: false | undefined;
516
+ }, {
517
+ enabled?: false | undefined;
518
+ }>, z.ZodObject<{
519
+ enabled: z.ZodLiteral<true>;
520
+ validPeriodDays: z.ZodNumber;
521
+ enabledAt: z.ZodOptional<z.ZodNumber>;
522
+ }, "strip", z.ZodTypeAny, {
523
+ enabled: true;
524
+ validPeriodDays: number;
525
+ enabledAt?: number | undefined;
526
+ }, {
527
+ enabled: true;
528
+ validPeriodDays: number;
529
+ enabledAt?: number | undefined;
530
+ }>]>;
444
531
  export { customUiCspGuard, type CustomUiCsp } from '@logto/core-kit';
@@ -62,6 +62,9 @@ export const signInGuard = z.object({
62
62
  })
63
63
  .array(),
64
64
  });
65
+ // The username policy is owned by @logto/core-kit (like passwordPolicyGuard) so core, experience,
66
+ // and console share one source of truth; re-exported here for the `@use UsernamePolicy` column.
67
+ export { defaultUsernamePolicy, usernamePolicyGuard } from '@logto/core-kit';
65
68
  export const socialSignInGuard = z.object({
66
69
  automaticAccountLinking: z.boolean().optional(),
67
70
  skipRequiredIdentifiers: z.boolean().optional(),
@@ -120,9 +123,14 @@ export const sentinelPolicyGuard = z.object({
120
123
  maxAttempts: z.number().optional(),
121
124
  lockoutDuration: z.number().optional(),
122
125
  });
126
+ export const verificationCodePolicyGuard = z.object({
127
+ expirationDuration: z.number().int().min(60).max(3600).optional(),
128
+ maxRetryAttempts: z.number().int().min(1).max(100).optional(),
129
+ });
123
130
  export const emailBlocklistPolicyGuard = z.object({
124
131
  blockDisposableAddresses: z.boolean().optional(),
125
132
  blockSubaddressing: z.boolean().optional(),
133
+ customAllowlist: z.string().array().optional(),
126
134
  customBlocklist: z.string().array().optional(),
127
135
  });
128
136
  export var ForgotPasswordMethod;
@@ -142,4 +150,16 @@ export const signUpProfileFieldItemGuard = z.object({
142
150
  name: z.string(),
143
151
  });
144
152
  export const signUpProfileFieldsGuard = z.array(signUpProfileFieldItemGuard);
153
+ // Intentionally not `.strict()` for backward compatibility: legacy rows may carry removed fields
154
+ // (e.g. `reminderPeriodDays`), which are stripped on parse instead of rejected.
155
+ export const passwordExpirationPolicyGuard = z.union([
156
+ z.object({
157
+ enabled: z.literal(false).optional(),
158
+ }),
159
+ z.object({
160
+ enabled: z.literal(true),
161
+ validPeriodDays: z.number().int().min(1),
162
+ enabledAt: z.number().int().nonnegative().optional(),
163
+ }),
164
+ ]);
145
165
  export { customUiCspGuard } from '@logto/core-kit';
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { customUiCspGuard } from './sign-in-experience.js';
2
+ import { customUiCspGuard, passwordExpirationPolicyGuard } from './sign-in-experience.js';
3
3
  describe('customUiCspGuard', () => {
4
4
  it.each([
5
5
  {},
@@ -16,3 +16,51 @@ describe('customUiCspGuard', () => {
16
16
  expect(customUiCspGuard.safeParse({ imgSrc: ['https://example.com'] }).success).toBe(false);
17
17
  });
18
18
  });
19
+ describe('passwordExpirationPolicyGuard', () => {
20
+ it.each([
21
+ [{}, {}],
22
+ [{ enabled: false }, { enabled: false }],
23
+ ])('accepts disabled policy %p', (value, expected) => {
24
+ expect(passwordExpirationPolicyGuard.parse(value)).toEqual(expected);
25
+ });
26
+ it('accepts enabled policy with a valid period', () => {
27
+ expect(passwordExpirationPolicyGuard.parse({
28
+ enabled: true,
29
+ validPeriodDays: 30,
30
+ })).toEqual({
31
+ enabled: true,
32
+ validPeriodDays: 30,
33
+ });
34
+ });
35
+ it('accepts enabled policy with an enabledAt timestamp', () => {
36
+ expect(passwordExpirationPolicyGuard.parse({
37
+ enabled: true,
38
+ validPeriodDays: 30,
39
+ enabledAt: 1_700_000_000_000,
40
+ })).toEqual({
41
+ enabled: true,
42
+ validPeriodDays: 30,
43
+ enabledAt: 1_700_000_000_000,
44
+ });
45
+ });
46
+ it('strips unknown keys from legacy rows (e.g. the removed reminderPeriodDays)', () => {
47
+ expect(passwordExpirationPolicyGuard.parse({
48
+ enabled: true,
49
+ validPeriodDays: 30,
50
+ reminderPeriodDays: 5,
51
+ })).toEqual({
52
+ enabled: true,
53
+ validPeriodDays: 30,
54
+ });
55
+ });
56
+ it.each([
57
+ { enabled: true },
58
+ { enabled: true, validPeriodDays: 0 },
59
+ { enabled: true, validPeriodDays: 1.5 },
60
+ { enabled: true, reminderPeriodDays: 5 },
61
+ { enabled: true, validPeriodDays: 30, enabledAt: -1 },
62
+ { enabled: true, validPeriodDays: 30, enabledAt: 1.5 },
63
+ ])('rejects invalid policy %p', (value) => {
64
+ expect(passwordExpirationPolicyGuard.safeParse(value).success).toBe(false);
65
+ });
66
+ });
@@ -150,6 +150,7 @@ export declare const baseMfaVerification: {
150
150
  };
151
151
  export declare const mfaVerificationTotp: z.ZodObject<{
152
152
  key: z.ZodString;
153
+ lastUsedTimeStep: z.ZodOptional<z.ZodNumber>;
153
154
  id: z.ZodString;
154
155
  createdAt: z.ZodString;
155
156
  lastUsedAt: z.ZodOptional<z.ZodString>;
@@ -159,12 +160,14 @@ export declare const mfaVerificationTotp: z.ZodObject<{
159
160
  id: string;
160
161
  key: string;
161
162
  createdAt: string;
163
+ lastUsedTimeStep?: number | undefined;
162
164
  lastUsedAt?: string | undefined;
163
165
  }, {
164
166
  type: MfaFactor.TOTP;
165
167
  id: string;
166
168
  key: string;
167
169
  createdAt: string;
170
+ lastUsedTimeStep?: number | undefined;
168
171
  lastUsedAt?: string | undefined;
169
172
  }>;
170
173
  export type MfaVerificationTotp = z.infer<typeof mfaVerificationTotp>;
@@ -244,6 +247,7 @@ export declare const mfaVerificationBackupCode: z.ZodObject<{
244
247
  export type MfaVerificationBackupCode = z.infer<typeof mfaVerificationBackupCode>;
245
248
  export declare const mfaVerificationGuard: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
246
249
  key: z.ZodString;
250
+ lastUsedTimeStep: z.ZodOptional<z.ZodNumber>;
247
251
  id: z.ZodString;
248
252
  createdAt: z.ZodString;
249
253
  lastUsedAt: z.ZodOptional<z.ZodString>;
@@ -253,12 +257,14 @@ export declare const mfaVerificationGuard: z.ZodDiscriminatedUnion<"type", [z.Zo
253
257
  id: string;
254
258
  key: string;
255
259
  createdAt: string;
260
+ lastUsedTimeStep?: number | undefined;
256
261
  lastUsedAt?: string | undefined;
257
262
  }, {
258
263
  type: MfaFactor.TOTP;
259
264
  id: string;
260
265
  key: string;
261
266
  createdAt: string;
267
+ lastUsedTimeStep?: number | undefined;
262
268
  lastUsedAt?: string | undefined;
263
269
  }>, z.ZodObject<{
264
270
  rpId: z.ZodOptional<z.ZodString>;
@@ -333,6 +339,7 @@ export declare const mfaVerificationGuard: z.ZodDiscriminatedUnion<"type", [z.Zo
333
339
  export type MfaVerification = z.infer<typeof mfaVerificationGuard>;
334
340
  export declare const mfaVerificationsGuard: z.ZodArray<z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
335
341
  key: z.ZodString;
342
+ lastUsedTimeStep: z.ZodOptional<z.ZodNumber>;
336
343
  id: z.ZodString;
337
344
  createdAt: z.ZodString;
338
345
  lastUsedAt: z.ZodOptional<z.ZodString>;
@@ -342,12 +349,14 @@ export declare const mfaVerificationsGuard: z.ZodArray<z.ZodDiscriminatedUnion<"
342
349
  id: string;
343
350
  key: string;
344
351
  createdAt: string;
352
+ lastUsedTimeStep?: number | undefined;
345
353
  lastUsedAt?: string | undefined;
346
354
  }, {
347
355
  type: MfaFactor.TOTP;
348
356
  id: string;
349
357
  key: string;
350
358
  createdAt: string;
359
+ lastUsedTimeStep?: number | undefined;
351
360
  lastUsedAt?: string | undefined;
352
361
  }>, z.ZodObject<{
353
362
  rpId: z.ZodOptional<z.ZodString>;
@@ -40,6 +40,7 @@ export const mfaVerificationTotp = z.object({
40
40
  type: z.literal(MfaFactor.TOTP),
41
41
  ...baseMfaVerification,
42
42
  key: z.string(),
43
+ lastUsedTimeStep: z.number().int().nonnegative().optional(),
43
44
  });
44
45
  export const webAuthnTransportGuard = z.enum([
45
46
  'usb',
@@ -27,4 +27,5 @@ export const createAdminTenantAccountCenter = () => Object.freeze({
27
27
  customData: AccountCenterControlValue.Edit,
28
28
  mfa: AccountCenterControlValue.Edit,
29
29
  },
30
+ profileFields: [{ name: 'name' }, { name: 'avatar' }],
30
31
  });
@@ -42,6 +42,7 @@ export const createDefaultSignInExperience = (forTenantId, isCloud) => Object.fr
42
42
  customCss: null,
43
43
  customContent: {},
44
44
  customUiAssets: null,
45
+ signUpProfileFields: null,
45
46
  passwordPolicy: {},
46
47
  mfa: {
47
48
  factors: [],
@@ -19,9 +19,13 @@ describe('createAdminTenantSignInExperience', () => {
19
19
  expect(row.tenantId).toBe(adminTenantId);
20
20
  });
21
21
  });
22
- describe('createDefaultSignInExperience (unchanged contract)', () => {
22
+ describe('createDefaultSignInExperience', () => {
23
23
  it('still has a two-parameter signature and seeds an empty passwordPolicy', () => {
24
24
  const row = createDefaultSignInExperience('some-tenant-id', false);
25
25
  expect(row.passwordPolicy).toEqual({});
26
26
  });
27
+ it('keeps legacy null signUpProfileFields for seeded default tenants', () => {
28
+ const row = createDefaultSignInExperience('some-tenant-id', false);
29
+ expect(row.signUpProfileFields).toBeNull();
30
+ });
27
31
  });