@logto/schemas 1.43.0 → 1.45.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 (106) hide show
  1. package/alterations/1.44.0-1788946735-add-saml-authn-request-config.ts +18 -0
  2. package/alterations/1.44.0-1789000000-extend-user-id-length.ts +61 -0
  3. package/alterations/1.45.0-1791531333-add-tenant-viewer-role.ts +38 -0
  4. package/alterations-js/1.44.0-1788946735-add-saml-authn-request-config.js +14 -0
  5. package/alterations-js/1.44.0-1789000000-extend-user-id-length.js +47 -0
  6. package/alterations-js/1.45.0-1791531333-add-tenant-viewer-role.js +32 -0
  7. package/lib/consts/experience.d.ts +3 -0
  8. package/lib/consts/experience.js +3 -0
  9. package/lib/consts/index.d.ts +1 -0
  10. package/lib/consts/index.js +1 -0
  11. package/lib/consts/license.d.ts +222 -0
  12. package/lib/consts/license.js +81 -0
  13. package/lib/consts/license.test.d.ts +1 -0
  14. package/lib/consts/license.test.js +96 -0
  15. package/lib/consts/oidc.d.ts +15 -1
  16. package/lib/consts/oidc.js +11 -0
  17. package/lib/consts/oidc.test.d.ts +1 -0
  18. package/lib/consts/oidc.test.js +15 -0
  19. package/lib/consts/subscriptions.d.ts +13 -1
  20. package/lib/consts/subscriptions.js +12 -0
  21. package/lib/db-entries/aggregated-daily-active-user.js +2 -2
  22. package/lib/db-entries/application-access-control-user-relation.js +2 -2
  23. package/lib/db-entries/application-user-consent-organization.js +2 -2
  24. package/lib/db-entries/cimd-grant-organization.js +2 -2
  25. package/lib/db-entries/daily-active-user.js +2 -2
  26. package/lib/db-entries/oidc-session-extension.js +2 -2
  27. package/lib/db-entries/organization-invitation.js +4 -4
  28. package/lib/db-entries/organization-role-user-relation.js +2 -2
  29. package/lib/db-entries/organization-user-relation.js +2 -2
  30. package/lib/db-entries/personal-access-token.js +2 -2
  31. package/lib/db-entries/saml-application-config.d.ts +4 -2
  32. package/lib/db-entries/saml-application-config.js +5 -1
  33. package/lib/db-entries/secret.js +2 -2
  34. package/lib/db-entries/subject-token.js +2 -2
  35. package/lib/db-entries/trusted-device.js +2 -2
  36. package/lib/db-entries/user-geo-location.js +2 -2
  37. package/lib/db-entries/user-sign-in-country.js +2 -2
  38. package/lib/db-entries/user-sso-identity.js +2 -2
  39. package/lib/db-entries/user.js +2 -2
  40. package/lib/db-entries/users-role.js +2 -2
  41. package/lib/db-entries/verification-record.js +2 -2
  42. package/lib/db-entries/verification-status.js +2 -2
  43. package/lib/foundations/jsonb-types/captcha.d.ts +47 -1
  44. package/lib/foundations/jsonb-types/captcha.js +20 -0
  45. package/lib/foundations/jsonb-types/hooks.d.ts +0 -3
  46. package/lib/foundations/jsonb-types/hooks.js +2 -6
  47. package/lib/foundations/jsonb-types/saml-application-configs.d.ts +23 -0
  48. package/lib/foundations/jsonb-types/saml-application-configs.js +16 -0
  49. package/lib/foundations/jsonb-types/saml-application-configs.test.js +24 -1
  50. package/lib/types/authentication-context.d.ts +224 -0
  51. package/lib/types/authentication-context.js +238 -0
  52. package/lib/types/authentication-context.test.d.ts +1 -0
  53. package/lib/types/authentication-context.test.js +186 -0
  54. package/lib/types/cookie.d.ts +4 -0
  55. package/lib/types/cookie.js +6 -1
  56. package/lib/types/index.d.ts +1 -0
  57. package/lib/types/index.js +1 -0
  58. package/lib/types/interactions.d.ts +161 -0
  59. package/lib/types/interactions.js +23 -0
  60. package/lib/types/log/interaction.d.ts +15 -2
  61. package/lib/types/log/interaction.js +5 -0
  62. package/lib/types/logto-config/index.d.ts +47 -0
  63. package/lib/types/logto-config/index.js +1 -0
  64. package/lib/types/logto-config/jwt-customizer.d.ts +92 -0
  65. package/lib/types/saml-application.d.ts +55 -1
  66. package/lib/types/saml-application.js +7 -1
  67. package/lib/types/sign-in-experience.d.ts +11 -7
  68. package/lib/types/sign-in-experience.js +2 -2
  69. package/lib/types/ssr.d.ts +2 -0
  70. package/lib/types/system.d.ts +57 -5
  71. package/lib/types/system.js +36 -0
  72. package/lib/types/system.test.d.ts +1 -0
  73. package/lib/types/system.test.js +26 -0
  74. package/lib/types/tenant-organization.d.ts +4 -2
  75. package/lib/types/tenant-organization.js +6 -2
  76. package/lib/types/tenant-organization.test.d.ts +1 -0
  77. package/lib/types/tenant-organization.test.js +16 -0
  78. package/lib/types/user-sessions.d.ts +156 -0
  79. package/lib/types/verification-records/code-verification.d.ts +17 -0
  80. package/lib/types/verification-records/code-verification.js +1 -0
  81. package/lib/utils/management-api.d.ts +5 -0
  82. package/lib/utils/management-api.js +7 -1
  83. package/lib/utils/oidc-private-key.test.js +44 -0
  84. package/package.json +7 -7
  85. package/tables/aggregated_daily_active_users.sql +1 -1
  86. package/tables/application_access_control_user_relations.sql +1 -1
  87. package/tables/application_user_consent_organizations.sql +1 -1
  88. package/tables/cimd_grant_organizations.sql +1 -1
  89. package/tables/daily_active_users.sql +1 -1
  90. package/tables/oidc_session_extensions.sql +1 -1
  91. package/tables/organization_invitations.sql +2 -2
  92. package/tables/organization_role_user_relations.sql +1 -1
  93. package/tables/organization_user_relations.sql +1 -1
  94. package/tables/personal_access_tokens.sql +1 -1
  95. package/tables/saml_application_configs.sql +1 -0
  96. package/tables/secrets.sql +1 -1
  97. package/tables/subject_tokens.sql +1 -1
  98. package/tables/trusted_devices.sql +1 -1
  99. package/tables/user_geo_locations.sql +1 -1
  100. package/tables/user_sign_in_countries.sql +1 -1
  101. package/tables/user_sso_identities.sql +1 -1
  102. package/tables/users.sql +1 -1
  103. package/tables/users_roles.sql +1 -1
  104. package/tables/verification_records.sql +1 -1
  105. package/tables/verification_statuses.sql +1 -1
  106. package/LICENSE +0 -373
@@ -3,6 +3,7 @@ export var CaptchaType;
3
3
  (function (CaptchaType) {
4
4
  CaptchaType["RecaptchaEnterprise"] = "RecaptchaEnterprise";
5
5
  CaptchaType["Turnstile"] = "Turnstile";
6
+ CaptchaType["Cap"] = "Cap";
6
7
  })(CaptchaType || (CaptchaType = {}));
7
8
  export var RecaptchaEnterpriseMode;
8
9
  (function (RecaptchaEnterpriseMode) {
@@ -21,8 +22,27 @@ export const recaptchaEnterpriseConfigGuard = z.object({
21
22
  projectId: z.string(),
22
23
  domain: z.string().optional(),
23
24
  mode: z.nativeEnum(RecaptchaEnterpriseMode).optional(),
25
+ scoreThreshold: z.number().min(0).max(1).optional(),
26
+ });
27
+ /**
28
+ * Config of a self-hosted Cap Standalone instance.
29
+ *
30
+ * @see https://capjs.js.org/guide/standalone/
31
+ */
32
+ export const capConfigGuard = z.object({
33
+ type: z.literal(CaptchaType.Cap),
34
+ /** The public base URL of the Cap Standalone instance, e.g. `https://cap.example.com`. */
35
+ endpoint: z
36
+ .string()
37
+ .url()
38
+ .refine((value) => ['http:', 'https:'].includes(new URL(value).protocol), {
39
+ message: 'The endpoint must be an HTTP(S) URL.',
40
+ }),
41
+ siteKey: z.string(),
42
+ secretKey: z.string(),
24
43
  });
25
44
  export const captchaConfigGuard = z.discriminatedUnion('type', [
26
45
  turnstileConfigGuard,
27
46
  recaptchaEnterpriseConfigGuard,
47
+ capConfigGuard,
28
48
  ]);
@@ -33,9 +33,6 @@ type CustomDataHookMutableSchema = `${Exclude<DataHookSchema, DataHookSchema.Tru
33
33
  type DataHookPropertyUpdateEvent = `${CustomDataHookMutableSchema}.${DataHookDetailMutationType.Updated}`;
34
34
  export type ExceptionHookEvent = 'Identifier.Lockout' | 'Message.RateLimited' | 'Grant.LimitExceeded';
35
35
  export type DataHookEvent = BasicDataHookEvent | DataHookPropertyUpdateEvent;
36
- /** Data hook events that are available only while their feature is under development. */
37
- export declare const devFeatureHookEvents: readonly ["TrustedDevice.Created", "TrustedDevice.Deleted"];
38
- export type DevFeatureHookEvent = (typeof devFeatureHookEvents)[number];
39
36
  /** The hook event values that can be registered. */
40
37
  export declare const hookEvents: readonly [InteractionHookEvent.PostRegister, InteractionHookEvent.PostSignIn, InteractionHookEvent.PostSignInAdaptiveMfaTriggered, InteractionHookEvent.PostResetPassword, "User.Created", "User.Deleted", "User.Data.Updated", "User.SuspensionStatus.Updated", "TrustedDevice.Created", "TrustedDevice.Deleted", "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"];
41
38
  /** The type of hook event values that can be registered. */
@@ -34,11 +34,6 @@ var DataHookDetailMutationType;
34
34
  (function (DataHookDetailMutationType) {
35
35
  DataHookDetailMutationType["Updated"] = "Updated";
36
36
  })(DataHookDetailMutationType || (DataHookDetailMutationType = {}));
37
- /** Data hook events that are available only while their feature is under development. */
38
- export const devFeatureHookEvents = Object.freeze([
39
- 'TrustedDevice.Created',
40
- 'TrustedDevice.Deleted',
41
- ]);
42
37
  /** The hook event values that can be registered. */
43
38
  export const hookEvents = Object.freeze([
44
39
  InteractionHookEvent.PostRegister,
@@ -49,7 +44,8 @@ export const hookEvents = Object.freeze([
49
44
  'User.Deleted',
50
45
  'User.Data.Updated',
51
46
  'User.SuspensionStatus.Updated',
52
- ...devFeatureHookEvents,
47
+ 'TrustedDevice.Created',
48
+ 'TrustedDevice.Deleted',
53
49
  'Role.Created',
54
50
  'Role.Deleted',
55
51
  'Role.Data.Updated',
@@ -49,6 +49,29 @@ export declare const samlEncryptionGuard: z.ZodEffects<z.ZodObject<{
49
49
  certificate?: string | undefined;
50
50
  }>;
51
51
  export type SamlEncryption = z.input<typeof samlEncryptionGuard>;
52
+ /** Authentication and request-signature policy for a SAML application. */
53
+ export declare const samlAuthnRequestConfigGuard: z.ZodEffects<z.ZodObject<{
54
+ forceAuthn: z.ZodOptional<z.ZodBoolean>;
55
+ requireSignedAuthnRequests: z.ZodOptional<z.ZodBoolean>;
56
+ signingCertificate: z.ZodOptional<z.ZodString>;
57
+ }, "strip", z.ZodTypeAny, {
58
+ forceAuthn?: boolean | undefined;
59
+ requireSignedAuthnRequests?: boolean | undefined;
60
+ signingCertificate?: string | undefined;
61
+ }, {
62
+ forceAuthn?: boolean | undefined;
63
+ requireSignedAuthnRequests?: boolean | undefined;
64
+ signingCertificate?: string | undefined;
65
+ }>, {
66
+ forceAuthn?: boolean | undefined;
67
+ requireSignedAuthnRequests?: boolean | undefined;
68
+ signingCertificate?: string | undefined;
69
+ }, {
70
+ forceAuthn?: boolean | undefined;
71
+ requireSignedAuthnRequests?: boolean | undefined;
72
+ signingCertificate?: string | undefined;
73
+ }>;
74
+ export type SamlAuthnRequestConfig = z.infer<typeof samlAuthnRequestConfigGuard>;
52
75
  export declare enum NameIdFormat {
53
76
  /** Uses unique and persistent identifiers for the user. */
54
77
  Persistent = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
@@ -28,6 +28,22 @@ export const samlEncryptionGuard = z
28
28
  return z.NEVER;
29
29
  }
30
30
  });
31
+ /** Authentication and request-signature policy for a SAML application. */
32
+ export const samlAuthnRequestConfigGuard = z
33
+ .object({
34
+ forceAuthn: z.boolean().optional(),
35
+ requireSignedAuthnRequests: z.boolean().optional(),
36
+ signingCertificate: z.string().trim().min(1).optional(),
37
+ })
38
+ .superRefine(({ requireSignedAuthnRequests, signingCertificate }, ctx) => {
39
+ if (requireSignedAuthnRequests && !signingCertificate) {
40
+ ctx.addIssue({
41
+ code: z.ZodIssueCode.custom,
42
+ path: ['signingCertificate'],
43
+ message: '`signingCertificate` is required when `requireSignedAuthnRequests` is `true`',
44
+ });
45
+ }
46
+ });
31
47
  export var NameIdFormat;
32
48
  (function (NameIdFormat) {
33
49
  /** Uses unique and persistent identifiers for the user. */
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { samlEncryptionGuard } from './saml-application-configs.js';
2
+ import { samlEncryptionGuard, samlAuthnRequestConfigGuard } from './saml-application-configs.js';
3
3
  describe('samlEncryptionGuard', () => {
4
4
  // Test valid configurations
5
5
  it('should pass when encryption is disabled', () => {
@@ -47,3 +47,26 @@ describe('samlEncryptionGuard', () => {
47
47
  }
48
48
  });
49
49
  });
50
+ describe('samlAuthnRequestConfigGuard', () => {
51
+ it('keeps the per-application force-login setting', () => {
52
+ expect(samlAuthnRequestConfigGuard.parse({ forceAuthn: true })).toEqual({ forceAuthn: true });
53
+ });
54
+ it('allows default session reuse', () => {
55
+ expect(samlAuthnRequestConfigGuard.parse({})).toEqual({});
56
+ });
57
+ it('rejects a non-boolean force-login setting', () => {
58
+ expect(samlAuthnRequestConfigGuard.safeParse({ forceAuthn: 'true' }).success).toBe(false);
59
+ });
60
+ });
61
+ describe('signed AuthnRequest policy', () => {
62
+ it('requires a signing certificate when signatures are required', () => {
63
+ expect(samlAuthnRequestConfigGuard.safeParse({ requireSignedAuthnRequests: true }).success).toBe(false);
64
+ });
65
+ it('keeps the configured trust certificate', () => {
66
+ const config = { requireSignedAuthnRequests: true, signingCertificate: 'certificate' };
67
+ expect(samlAuthnRequestConfigGuard.parse(config)).toEqual(config);
68
+ });
69
+ it.each(['', ' '])('rejects an empty certificate: %j', (signingCertificate) => {
70
+ expect(samlAuthnRequestConfigGuard.safeParse({ signingCertificate }).success).toBe(false);
71
+ });
72
+ });
@@ -0,0 +1,224 @@
1
+ /**
2
+ * @file The authoritative vocabulary for the authentication context Logto records on a sign-in or
3
+ * step-up: the supported Authentication Context Class Reference (ACR) values, their satisfaction
4
+ * relation, and the Authentication Methods References (AMR) each verification contributes.
5
+ *
6
+ * @see {@link https://openid.net/specs/openid-connect-core-1_0.html#IDToken | OpenID Connect Core `acr` / `amr`}
7
+ * @see {@link https://www.rfc-editor.org/rfc/rfc8176.html | RFC 8176 Authentication Method Reference Values}
8
+ */
9
+ import { z } from 'zod';
10
+ import { VerificationType } from './verification-records/verification-type.js';
11
+ /** The Logto-defined ACR values. Only these can be requested through `acr_values`. */
12
+ export declare enum LogtoAcr {
13
+ /**
14
+ * At least one active factor that Logto can directly verify: password, the user's primary
15
+ * email / phone verification code, or an enrolled MFA factor. Social and enterprise SSO never
16
+ * satisfy this class.
17
+ */
18
+ FirstFactor = "urn:logto:acr:1fa",
19
+ /**
20
+ * A Logto-verifiable {@link LogtoAcr.FirstFactor} context plus an `mfa`-class record from a
21
+ * different factor, or WebAuthn / passkey with required user verification alone.
22
+ */
23
+ Mfa = "urn:logto:acr:mfa"
24
+ }
25
+ /** The ACR values advertised in Discovery as `acr_values_supported`, in the provider order. */
26
+ export declare const logtoAcrValues: readonly [LogtoAcr.FirstFactor, LogtoAcr.Mfa];
27
+ /** Whether a value, e.g. one of the requested `acr_values` or a session `acr`, is a Logto ACR. */
28
+ export declare const isLogtoAcr: (value: unknown) => value is LogtoAcr;
29
+ /**
30
+ * Whether an achieved ACR, e.g. the `acr` recorded on the OIDC session, satisfies a required
31
+ * class. An achieved value that is not a Logto ACR, including an absent one, satisfies nothing.
32
+ */
33
+ export declare const acrSatisfies: (achieved: unknown, required: LogtoAcr) => boolean;
34
+ /** How Experience handles a login prompt that carries a requested authentication context. */
35
+ export declare enum AuthenticationContextMode {
36
+ /**
37
+ * An authenticated OIDC session exists but does not satisfy the request: the subject is pinned
38
+ * from the session and the interaction asks only for the missing assurance.
39
+ */
40
+ StepUp = "stepUp"
41
+ }
42
+ /**
43
+ * The authentication context the OIDC interaction policy writes into the login prompt details
44
+ * when the authorization request carries supported `acr_values`. Experience reads it at
45
+ * interaction creation; `interactions.url` routes on {@link RequestedAuthenticationContext.mode}.
46
+ */
47
+ export type RequestedAuthenticationContext = {
48
+ /** The supported classes from `acr_values`, in the caller's order. */
49
+ requestedAcrValues: LogtoAcr[];
50
+ /**
51
+ * The first requested class, the minimum the interaction must reach. Present only in step-up
52
+ * mode; a sign-in without a session applies the same selection rule once the user is identified.
53
+ */
54
+ selectedAcr?: LogtoAcr;
55
+ /** Present only when an authenticated session pins the subject. */
56
+ mode?: AuthenticationContextMode;
57
+ };
58
+ export declare const requestedAuthenticationContextGuard: z.ZodObject<{
59
+ requestedAcrValues: z.ZodArray<z.ZodNativeEnum<typeof LogtoAcr>, "many">;
60
+ selectedAcr: z.ZodOptional<z.ZodNativeEnum<typeof LogtoAcr>>;
61
+ mode: z.ZodOptional<z.ZodNativeEnum<typeof AuthenticationContextMode>>;
62
+ }, "strip", z.ZodTypeAny, {
63
+ requestedAcrValues: LogtoAcr[];
64
+ mode?: AuthenticationContextMode | undefined;
65
+ selectedAcr?: LogtoAcr | undefined;
66
+ }, {
67
+ requestedAcrValues: LogtoAcr[];
68
+ mode?: AuthenticationContextMode | undefined;
69
+ selectedAcr?: LogtoAcr | undefined;
70
+ }>;
71
+ /**
72
+ * The login prompt details that carry a {@link RequestedAuthenticationContext}. Absent when the
73
+ * authorization request carried no supported `acr_values`.
74
+ */
75
+ export type LoginPromptAuthenticationContextDetails = {
76
+ authenticationContext?: RequestedAuthenticationContext;
77
+ };
78
+ export declare const loginPromptAuthenticationContextDetailsGuard: z.ZodObject<{
79
+ authenticationContext: z.ZodOptional<z.ZodObject<{
80
+ requestedAcrValues: z.ZodArray<z.ZodNativeEnum<typeof LogtoAcr>, "many">;
81
+ selectedAcr: z.ZodOptional<z.ZodNativeEnum<typeof LogtoAcr>>;
82
+ mode: z.ZodOptional<z.ZodNativeEnum<typeof AuthenticationContextMode>>;
83
+ }, "strip", z.ZodTypeAny, {
84
+ requestedAcrValues: LogtoAcr[];
85
+ mode?: AuthenticationContextMode | undefined;
86
+ selectedAcr?: LogtoAcr | undefined;
87
+ }, {
88
+ requestedAcrValues: LogtoAcr[];
89
+ mode?: AuthenticationContextMode | undefined;
90
+ selectedAcr?: LogtoAcr | undefined;
91
+ }>>;
92
+ }, "strip", z.ZodTypeAny, {
93
+ authenticationContext?: {
94
+ requestedAcrValues: LogtoAcr[];
95
+ mode?: AuthenticationContextMode | undefined;
96
+ selectedAcr?: LogtoAcr | undefined;
97
+ } | undefined;
98
+ }, {
99
+ authenticationContext?: {
100
+ requestedAcrValues: LogtoAcr[];
101
+ mode?: AuthenticationContextMode | undefined;
102
+ selectedAcr?: LogtoAcr | undefined;
103
+ } | undefined;
104
+ }>;
105
+ /**
106
+ * The AMR values Logto emits. All except {@link AuthenticationMethodReference.Federated} are
107
+ * registered in RFC 8176; `fed` is the de facto industry value for authentication delegated to an
108
+ * upstream identity provider and is the only unregistered value Logto emits.
109
+ */
110
+ export declare enum AuthenticationMethodReference {
111
+ /** Password-based authentication. */
112
+ Password = "pwd",
113
+ /** One-time password: email verification code, TOTP, backup code, or one-time token. */
114
+ Otp = "otp",
115
+ /** Verification code delivered by SMS. */
116
+ Sms = "sms",
117
+ /** Proof-of-possession of a key, e.g. WebAuthn. */
118
+ ProofOfPossession = "pop",
119
+ /** User presence / verification by the authenticator, e.g. WebAuthn user verification. */
120
+ UserPresence = "user",
121
+ /** Multiple-factor authentication; appended whenever the achieved ACR is {@link LogtoAcr.Mfa}. */
122
+ Mfa = "mfa",
123
+ /** Federated authentication assertion from a social or enterprise SSO provider. */
124
+ Federated = "fed"
125
+ }
126
+ /**
127
+ * The role a proof can fill when proofs are combined into an ACR. This is not the ACR a proof
128
+ * reaches on its own: any proof with a class reaches {@link LogtoAcr.FirstFactor} alone, and the
129
+ * class only decides which side of the pair the proof can stand on when
130
+ * {@link LogtoAcr.Mfa} is derived. That level needs a `1fa`-role proof and an `mfa`-role proof of
131
+ * two different factors, so two `mfa`-class factors, such as a TOTP and a backup code, never reach
132
+ * it together.
133
+ */
134
+ export declare enum AuthenticationFactorClass {
135
+ /** Fills the `1fa` role: a password or a primary email / phone code. */
136
+ FirstFactor = "1fa",
137
+ /**
138
+ * Fills the `mfa` role: TOTP, a backup code or an MFA email / phone code. Alone it reaches
139
+ * {@link LogtoAcr.FirstFactor}; paired with a `1fa`-role proof of a different factor it reaches
140
+ * {@link LogtoAcr.Mfa}.
141
+ */
142
+ Mfa = "mfa",
143
+ /**
144
+ * Fills both roles by itself, so it reaches {@link LogtoAcr.Mfa} alone: a user-verified WebAuthn
145
+ * authenticator is possession plus user verification in one act. Kept as a class rather than a
146
+ * special case on the factor so that every "can fill this role" check stays a class comparison
147
+ * and this table stays the single place that decides.
148
+ */
149
+ Both = "both"
150
+ }
151
+ /** Classify a verification type; see {@link authenticationFactorClasses}. */
152
+ export declare const getAuthenticationFactorClass: (type: VerificationType) => AuthenticationFactorClass | undefined;
153
+ /**
154
+ * The factor a verification record is a proof of. Repeated proofs of one factor count once, and
155
+ * {@link LogtoAcr.Mfa} needs a `1fa`-class and an `mfa`-class proof of two different factors: a
156
+ * one-time token and an MFA email code are two proofs of the same mailbox, not two factors.
157
+ */
158
+ export declare enum AuthenticationFactor {
159
+ Password = "password",
160
+ Email = "email",
161
+ Phone = "phone",
162
+ Totp = "totp",
163
+ BackupCode = "backupCode",
164
+ WebAuthn = "webAuthn",
165
+ Federated = "federated"
166
+ }
167
+ /** The factor a verification type is a proof of; see {@link authenticationFactors}. */
168
+ export declare const getAuthenticationFactor: (type: VerificationType) => AuthenticationFactor;
169
+ /** The AMR values a verification type contributes; see {@link authenticationMethodReferences}. */
170
+ export declare const getAuthenticationMethodReferences: (type: VerificationType) => readonly AuthenticationMethodReference[];
171
+ /**
172
+ * Build the `amr` claim from the references each counted proof contributes and the ACR they
173
+ * achieved: the union in first-seen order, with `mfa` always last, present whenever a proof carries
174
+ * it (WebAuthn) or the achieved ACR is {@link LogtoAcr.Mfa}.
175
+ */
176
+ export declare const buildAuthenticationMethodReferences: (references: Iterable<readonly AuthenticationMethodReference[]>, achievedAcr?: LogtoAcr) => AuthenticationMethodReference[];
177
+ /**
178
+ * Why a proof was recorded: the touchpoint that consumed the credential. Aggregation into `acr` /
179
+ * `amr` ignores the role; it exists for the MFA gate and for step-up freshness, and never reaches
180
+ * a token.
181
+ */
182
+ export declare enum AuthenticationProofRole {
183
+ /** `createUser()` created the account from the record. */
184
+ Create = "create",
185
+ /** `identifyUser()` identified the user with the record. */
186
+ Identify = "identify",
187
+ /** The credential or factor was written to the account by this interaction. */
188
+ Bind = "bind",
189
+ /** An MFA challenge was answered with the record. */
190
+ Mfa = "mfa"
191
+ }
192
+ /**
193
+ * A proof that the identified user authenticated in the interaction, recorded at the single
194
+ * touchpoint that consumed the credential. What counts is decided where the interaction relies on
195
+ * the credential, never by inspecting verification records afterwards.
196
+ */
197
+ export type AuthenticationProof = {
198
+ /** The verification record id, or `password` for a password established through the profile. */
199
+ id: string;
200
+ factor: AuthenticationFactor;
201
+ /** Absent for a federated proof, which contributes `fed` to AMR and nothing to the ACR. */
202
+ class?: AuthenticationFactorClass;
203
+ amr: AuthenticationMethodReference[];
204
+ role: AuthenticationProofRole;
205
+ };
206
+ export declare const authenticationProofGuard: z.ZodObject<{
207
+ id: z.ZodString;
208
+ factor: z.ZodNativeEnum<typeof AuthenticationFactor>;
209
+ class: z.ZodOptional<z.ZodNativeEnum<typeof AuthenticationFactorClass>>;
210
+ amr: z.ZodArray<z.ZodNativeEnum<typeof AuthenticationMethodReference>, "many">;
211
+ role: z.ZodNativeEnum<typeof AuthenticationProofRole>;
212
+ }, "strip", z.ZodTypeAny, {
213
+ id: string;
214
+ role: AuthenticationProofRole;
215
+ factor: AuthenticationFactor;
216
+ amr: AuthenticationMethodReference[];
217
+ class?: AuthenticationFactorClass | undefined;
218
+ }, {
219
+ id: string;
220
+ role: AuthenticationProofRole;
221
+ factor: AuthenticationFactor;
222
+ amr: AuthenticationMethodReference[];
223
+ class?: AuthenticationFactorClass | undefined;
224
+ }>;
@@ -0,0 +1,238 @@
1
+ /**
2
+ * @file The authoritative vocabulary for the authentication context Logto records on a sign-in or
3
+ * step-up: the supported Authentication Context Class Reference (ACR) values, their satisfaction
4
+ * relation, and the Authentication Methods References (AMR) each verification contributes.
5
+ *
6
+ * @see {@link https://openid.net/specs/openid-connect-core-1_0.html#IDToken | OpenID Connect Core `acr` / `amr`}
7
+ * @see {@link https://www.rfc-editor.org/rfc/rfc8176.html | RFC 8176 Authentication Method Reference Values}
8
+ */
9
+ import { z } from 'zod';
10
+ import { VerificationType } from './verification-records/verification-type.js';
11
+ /** The Logto-defined ACR values. Only these can be requested through `acr_values`. */
12
+ export var LogtoAcr;
13
+ (function (LogtoAcr) {
14
+ /**
15
+ * At least one active factor that Logto can directly verify: password, the user's primary
16
+ * email / phone verification code, or an enrolled MFA factor. Social and enterprise SSO never
17
+ * satisfy this class.
18
+ */
19
+ LogtoAcr["FirstFactor"] = "urn:logto:acr:1fa";
20
+ /**
21
+ * A Logto-verifiable {@link LogtoAcr.FirstFactor} context plus an `mfa`-class record from a
22
+ * different factor, or WebAuthn / passkey with required user verification alone.
23
+ */
24
+ LogtoAcr["Mfa"] = "urn:logto:acr:mfa";
25
+ })(LogtoAcr || (LogtoAcr = {}));
26
+ /** The ACR values advertised in Discovery as `acr_values_supported`, in the provider order. */
27
+ export const logtoAcrValues = Object.freeze([LogtoAcr.FirstFactor, LogtoAcr.Mfa]);
28
+ const logtoAcrSet = new Set(logtoAcrValues);
29
+ /** Whether a value, e.g. one of the requested `acr_values` or a session `acr`, is a Logto ACR. */
30
+ export const isLogtoAcr = (value) => typeof value === 'string' && logtoAcrSet.has(value);
31
+ /**
32
+ * The classes each Logto ACR satisfies. Satisfaction is this explicit map, never the provider's
33
+ * exact match: {@link LogtoAcr.Mfa} is a {@link LogtoAcr.FirstFactor} context plus more, so it
34
+ * satisfies both classes, while {@link LogtoAcr.FirstFactor} satisfies only itself.
35
+ */
36
+ const satisfiedAcrs = Object.freeze({
37
+ [LogtoAcr.FirstFactor]: [LogtoAcr.FirstFactor],
38
+ [LogtoAcr.Mfa]: [LogtoAcr.Mfa, LogtoAcr.FirstFactor],
39
+ });
40
+ /**
41
+ * Whether an achieved ACR, e.g. the `acr` recorded on the OIDC session, satisfies a required
42
+ * class. An achieved value that is not a Logto ACR, including an absent one, satisfies nothing.
43
+ */
44
+ export const acrSatisfies = (achieved, required) => isLogtoAcr(achieved) && satisfiedAcrs[achieved].includes(required);
45
+ /** How Experience handles a login prompt that carries a requested authentication context. */
46
+ export var AuthenticationContextMode;
47
+ (function (AuthenticationContextMode) {
48
+ /**
49
+ * An authenticated OIDC session exists but does not satisfy the request: the subject is pinned
50
+ * from the session and the interaction asks only for the missing assurance.
51
+ */
52
+ AuthenticationContextMode["StepUp"] = "stepUp";
53
+ })(AuthenticationContextMode || (AuthenticationContextMode = {}));
54
+ export const requestedAuthenticationContextGuard = z.object({
55
+ requestedAcrValues: z.nativeEnum(LogtoAcr).array(),
56
+ selectedAcr: z.nativeEnum(LogtoAcr).optional(),
57
+ mode: z.nativeEnum(AuthenticationContextMode).optional(),
58
+ });
59
+ export const loginPromptAuthenticationContextDetailsGuard = z.object({
60
+ authenticationContext: requestedAuthenticationContextGuard.optional(),
61
+ });
62
+ /**
63
+ * The AMR values Logto emits. All except {@link AuthenticationMethodReference.Federated} are
64
+ * registered in RFC 8176; `fed` is the de facto industry value for authentication delegated to an
65
+ * upstream identity provider and is the only unregistered value Logto emits.
66
+ */
67
+ export var AuthenticationMethodReference;
68
+ (function (AuthenticationMethodReference) {
69
+ /** Password-based authentication. */
70
+ AuthenticationMethodReference["Password"] = "pwd";
71
+ /** One-time password: email verification code, TOTP, backup code, or one-time token. */
72
+ AuthenticationMethodReference["Otp"] = "otp";
73
+ /** Verification code delivered by SMS. */
74
+ AuthenticationMethodReference["Sms"] = "sms";
75
+ /** Proof-of-possession of a key, e.g. WebAuthn. */
76
+ AuthenticationMethodReference["ProofOfPossession"] = "pop";
77
+ /** User presence / verification by the authenticator, e.g. WebAuthn user verification. */
78
+ AuthenticationMethodReference["UserPresence"] = "user";
79
+ /** Multiple-factor authentication; appended whenever the achieved ACR is {@link LogtoAcr.Mfa}. */
80
+ AuthenticationMethodReference["Mfa"] = "mfa";
81
+ /** Federated authentication assertion from a social or enterprise SSO provider. */
82
+ AuthenticationMethodReference["Federated"] = "fed";
83
+ })(AuthenticationMethodReference || (AuthenticationMethodReference = {}));
84
+ /**
85
+ * The role a proof can fill when proofs are combined into an ACR. This is not the ACR a proof
86
+ * reaches on its own: any proof with a class reaches {@link LogtoAcr.FirstFactor} alone, and the
87
+ * class only decides which side of the pair the proof can stand on when
88
+ * {@link LogtoAcr.Mfa} is derived. That level needs a `1fa`-role proof and an `mfa`-role proof of
89
+ * two different factors, so two `mfa`-class factors, such as a TOTP and a backup code, never reach
90
+ * it together.
91
+ */
92
+ export var AuthenticationFactorClass;
93
+ (function (AuthenticationFactorClass) {
94
+ /** Fills the `1fa` role: a password or a primary email / phone code. */
95
+ AuthenticationFactorClass["FirstFactor"] = "1fa";
96
+ /**
97
+ * Fills the `mfa` role: TOTP, a backup code or an MFA email / phone code. Alone it reaches
98
+ * {@link LogtoAcr.FirstFactor}; paired with a `1fa`-role proof of a different factor it reaches
99
+ * {@link LogtoAcr.Mfa}.
100
+ */
101
+ AuthenticationFactorClass["Mfa"] = "mfa";
102
+ /**
103
+ * Fills both roles by itself, so it reaches {@link LogtoAcr.Mfa} alone: a user-verified WebAuthn
104
+ * authenticator is possession plus user verification in one act. Kept as a class rather than a
105
+ * special case on the factor so that every "can fill this role" check stays a class comparison
106
+ * and this table stays the single place that decides.
107
+ */
108
+ AuthenticationFactorClass["Both"] = "both";
109
+ })(AuthenticationFactorClass || (AuthenticationFactorClass = {}));
110
+ /**
111
+ * The class each verification type contributes: `1fa`-class, `mfa`-class, `both`, or `undefined`
112
+ * for social / enterprise SSO, which contribute `fed` to AMR but nothing to the ACR.
113
+ *
114
+ * The record is keyed by every {@link VerificationType}, so a new type fails at compile time until
115
+ * it is mapped here. Logto requires user verification for every WebAuthn ceremony, registration and
116
+ * authentication alike, so a verified WebAuthn record is user-verified by construction.
117
+ */
118
+ const authenticationFactorClasses = Object.freeze({
119
+ [VerificationType.Password]: AuthenticationFactorClass.FirstFactor,
120
+ [VerificationType.EmailVerificationCode]: AuthenticationFactorClass.FirstFactor,
121
+ [VerificationType.PhoneVerificationCode]: AuthenticationFactorClass.FirstFactor,
122
+ [VerificationType.OneTimeToken]: AuthenticationFactorClass.FirstFactor,
123
+ // A password established by a registration; it is a proof once `createUser()` consumes it.
124
+ [VerificationType.NewPasswordIdentity]: AuthenticationFactorClass.FirstFactor,
125
+ [VerificationType.TOTP]: AuthenticationFactorClass.Mfa,
126
+ [VerificationType.MfaEmailVerificationCode]: AuthenticationFactorClass.Mfa,
127
+ [VerificationType.MfaPhoneVerificationCode]: AuthenticationFactorClass.Mfa,
128
+ [VerificationType.BackupCode]: AuthenticationFactorClass.Mfa,
129
+ [VerificationType.WebAuthn]: AuthenticationFactorClass.Both,
130
+ [VerificationType.SignInPasskey]: AuthenticationFactorClass.Both,
131
+ [VerificationType.Social]: undefined,
132
+ [VerificationType.EnterpriseSso]: undefined,
133
+ });
134
+ /** Classify a verification type; see {@link authenticationFactorClasses}. */
135
+ export const getAuthenticationFactorClass = (type) => authenticationFactorClasses[type];
136
+ /**
137
+ * The factor a verification record is a proof of. Repeated proofs of one factor count once, and
138
+ * {@link LogtoAcr.Mfa} needs a `1fa`-class and an `mfa`-class proof of two different factors: a
139
+ * one-time token and an MFA email code are two proofs of the same mailbox, not two factors.
140
+ */
141
+ export var AuthenticationFactor;
142
+ (function (AuthenticationFactor) {
143
+ AuthenticationFactor["Password"] = "password";
144
+ AuthenticationFactor["Email"] = "email";
145
+ AuthenticationFactor["Phone"] = "phone";
146
+ AuthenticationFactor["Totp"] = "totp";
147
+ AuthenticationFactor["BackupCode"] = "backupCode";
148
+ AuthenticationFactor["WebAuthn"] = "webAuthn";
149
+ AuthenticationFactor["Federated"] = "federated";
150
+ })(AuthenticationFactor || (AuthenticationFactor = {}));
151
+ /** Keyed by every {@link VerificationType}, so a new type fails at compile time until mapped. */
152
+ const authenticationFactors = Object.freeze({
153
+ [VerificationType.Password]: AuthenticationFactor.Password,
154
+ [VerificationType.NewPasswordIdentity]: AuthenticationFactor.Password,
155
+ [VerificationType.EmailVerificationCode]: AuthenticationFactor.Email,
156
+ [VerificationType.MfaEmailVerificationCode]: AuthenticationFactor.Email,
157
+ [VerificationType.OneTimeToken]: AuthenticationFactor.Email,
158
+ [VerificationType.PhoneVerificationCode]: AuthenticationFactor.Phone,
159
+ [VerificationType.MfaPhoneVerificationCode]: AuthenticationFactor.Phone,
160
+ [VerificationType.TOTP]: AuthenticationFactor.Totp,
161
+ [VerificationType.BackupCode]: AuthenticationFactor.BackupCode,
162
+ [VerificationType.WebAuthn]: AuthenticationFactor.WebAuthn,
163
+ [VerificationType.SignInPasskey]: AuthenticationFactor.WebAuthn,
164
+ [VerificationType.Social]: AuthenticationFactor.Federated,
165
+ [VerificationType.EnterpriseSso]: AuthenticationFactor.Federated,
166
+ });
167
+ /** The factor a verification type is a proof of; see {@link authenticationFactors}. */
168
+ export const getAuthenticationFactor = (type) => authenticationFactors[type];
169
+ /**
170
+ * The AMR values a single verification type contributes, per the step-up tech design. The record
171
+ * is keyed by every {@link VerificationType}, so a new type fails at compile time until it is
172
+ * mapped here.
173
+ *
174
+ * The trailing `mfa` marker for an achieved {@link LogtoAcr.Mfa} is added by
175
+ * {@link buildAuthenticationMethodReferences}, not here; WebAuthn carries it inherently because a
176
+ * user-verified passkey is a multi-factor authenticator on its own.
177
+ */
178
+ const authenticationMethodReferences = Object.freeze({
179
+ [VerificationType.Password]: [AuthenticationMethodReference.Password],
180
+ [VerificationType.NewPasswordIdentity]: [AuthenticationMethodReference.Password],
181
+ [VerificationType.EmailVerificationCode]: [AuthenticationMethodReference.Otp],
182
+ [VerificationType.OneTimeToken]: [AuthenticationMethodReference.Otp],
183
+ [VerificationType.TOTP]: [AuthenticationMethodReference.Otp],
184
+ [VerificationType.MfaEmailVerificationCode]: [AuthenticationMethodReference.Otp],
185
+ [VerificationType.BackupCode]: [AuthenticationMethodReference.Otp],
186
+ [VerificationType.PhoneVerificationCode]: [AuthenticationMethodReference.Sms],
187
+ [VerificationType.MfaPhoneVerificationCode]: [AuthenticationMethodReference.Sms],
188
+ [VerificationType.WebAuthn]: [
189
+ AuthenticationMethodReference.ProofOfPossession,
190
+ AuthenticationMethodReference.UserPresence,
191
+ AuthenticationMethodReference.Mfa,
192
+ ],
193
+ [VerificationType.SignInPasskey]: [
194
+ AuthenticationMethodReference.ProofOfPossession,
195
+ AuthenticationMethodReference.UserPresence,
196
+ AuthenticationMethodReference.Mfa,
197
+ ],
198
+ [VerificationType.Social]: [AuthenticationMethodReference.Federated],
199
+ [VerificationType.EnterpriseSso]: [AuthenticationMethodReference.Federated],
200
+ });
201
+ /** The AMR values a verification type contributes; see {@link authenticationMethodReferences}. */
202
+ export const getAuthenticationMethodReferences = (type) => authenticationMethodReferences[type];
203
+ /**
204
+ * Build the `amr` claim from the references each counted proof contributes and the ACR they
205
+ * achieved: the union in first-seen order, with `mfa` always last, present whenever a proof carries
206
+ * it (WebAuthn) or the achieved ACR is {@link LogtoAcr.Mfa}.
207
+ */
208
+ export const buildAuthenticationMethodReferences = (references, achievedAcr) => {
209
+ const flattened = [...references].flat();
210
+ const hasMfa = achievedAcr === LogtoAcr.Mfa || flattened.includes(AuthenticationMethodReference.Mfa);
211
+ return [
212
+ ...new Set(flattened.filter((reference) => reference !== AuthenticationMethodReference.Mfa)),
213
+ ...(hasMfa ? [AuthenticationMethodReference.Mfa] : []),
214
+ ];
215
+ };
216
+ /**
217
+ * Why a proof was recorded: the touchpoint that consumed the credential. Aggregation into `acr` /
218
+ * `amr` ignores the role; it exists for the MFA gate and for step-up freshness, and never reaches
219
+ * a token.
220
+ */
221
+ export var AuthenticationProofRole;
222
+ (function (AuthenticationProofRole) {
223
+ /** `createUser()` created the account from the record. */
224
+ AuthenticationProofRole["Create"] = "create";
225
+ /** `identifyUser()` identified the user with the record. */
226
+ AuthenticationProofRole["Identify"] = "identify";
227
+ /** The credential or factor was written to the account by this interaction. */
228
+ AuthenticationProofRole["Bind"] = "bind";
229
+ /** An MFA challenge was answered with the record. */
230
+ AuthenticationProofRole["Mfa"] = "mfa";
231
+ })(AuthenticationProofRole || (AuthenticationProofRole = {}));
232
+ export const authenticationProofGuard = z.object({
233
+ id: z.string().min(1),
234
+ factor: z.nativeEnum(AuthenticationFactor),
235
+ class: z.nativeEnum(AuthenticationFactorClass).optional(),
236
+ amr: z.nativeEnum(AuthenticationMethodReference).array().min(1),
237
+ role: z.nativeEnum(AuthenticationProofRole),
238
+ });
@@ -0,0 +1 @@
1
+ export {};