@seamless-auth/types 0.29.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/schemas/auth/auth.schema.d.ts +3 -0
- package/dist/schemas/auth/auth.schema.d.ts.map +1 -1
- package/dist/schemas/auth/auth.schema.js +2 -0
- package/dist/schemas/auth/auth.schema.js.map +1 -1
- package/dist/schemas/authEvent/schema.d.ts +43 -4
- package/dist/schemas/authEvent/schema.d.ts.map +1 -1
- package/dist/schemas/authEvent/schema.js +14 -1
- package/dist/schemas/authEvent/schema.js.map +1 -1
- package/dist/schemas/oauth/schema.d.ts +1 -0
- package/dist/schemas/oauth/schema.d.ts.map +1 -1
- package/dist/schemas/policy/schema.d.ts +555 -0
- package/dist/schemas/policy/schema.d.ts.map +1 -0
- package/dist/schemas/policy/schema.js +125 -0
- package/dist/schemas/policy/schema.js.map +1 -0
- package/dist/schemas/recovery/schema.d.ts +130 -0
- package/dist/schemas/recovery/schema.d.ts.map +1 -0
- package/dist/schemas/recovery/schema.js +81 -0
- package/dist/schemas/recovery/schema.js.map +1 -0
- package/dist/schemas/systemConfig/schema.d.ts +63 -2
- package/dist/schemas/systemConfig/schema.d.ts.map +1 -1
- package/dist/schemas/systemConfig/schema.js +51 -5
- package/dist/schemas/systemConfig/schema.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +2 -0
- package/src/schemas/auth/auth.schema.ts +2 -0
- package/src/schemas/authEvent/schema.ts +14 -1
- package/src/schemas/policy/schema.ts +168 -0
- package/src/schemas/recovery/schema.ts +117 -0
- package/src/schemas/systemConfig/schema.ts +68 -5
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
import { IsoDate } from '../../shared.js';
|
|
4
|
+
import { SystemConfigSchema } from '../systemConfig/schema.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The system config keys that decide who can sign in, and how. These are the ones a
|
|
8
|
+
* change can lock people out with, so each published change is kept as a version that
|
|
9
|
+
* can be restored in one step, and can be staged as a draft and dry-run first.
|
|
10
|
+
*/
|
|
11
|
+
export const POLICY_KEYS = [
|
|
12
|
+
'login_methods',
|
|
13
|
+
'passkey_login_fallback_enabled',
|
|
14
|
+
'phishing_resistant_only',
|
|
15
|
+
'prompt_passkey_enrollment',
|
|
16
|
+
'authenticator_policy',
|
|
17
|
+
'lockout_policy',
|
|
18
|
+
'recovery_codes',
|
|
19
|
+
'account_recovery',
|
|
20
|
+
] as const;
|
|
21
|
+
|
|
22
|
+
export const PolicyKeySchema = z.enum(POLICY_KEYS);
|
|
23
|
+
|
|
24
|
+
export type PolicyKey = z.infer<typeof PolicyKeySchema>;
|
|
25
|
+
|
|
26
|
+
export const PolicyDocumentSchema = SystemConfigSchema.pick({
|
|
27
|
+
login_methods: true,
|
|
28
|
+
passkey_login_fallback_enabled: true,
|
|
29
|
+
phishing_resistant_only: true,
|
|
30
|
+
prompt_passkey_enrollment: true,
|
|
31
|
+
authenticator_policy: true,
|
|
32
|
+
lockout_policy: true,
|
|
33
|
+
recovery_codes: true,
|
|
34
|
+
account_recovery: true,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export type PolicyDocument = z.infer<typeof PolicyDocumentSchema>;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* How a version came to be. Drafts are optional: a whole-config PATCH that touches a
|
|
41
|
+
* policy key still applies immediately, and is recorded as a `config_patch` version so
|
|
42
|
+
* it can be rolled back like any other.
|
|
43
|
+
*/
|
|
44
|
+
export const PolicyVersionSourceSchema = z.enum([
|
|
45
|
+
'publish',
|
|
46
|
+
'rollback',
|
|
47
|
+
'config_patch',
|
|
48
|
+
'bootstrap',
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
export type PolicyVersionSource = z.infer<typeof PolicyVersionSourceSchema>;
|
|
52
|
+
|
|
53
|
+
export const PolicyVersionSchema = z.object({
|
|
54
|
+
version: z.number().int().positive(),
|
|
55
|
+
policy: PolicyDocumentSchema,
|
|
56
|
+
source: PolicyVersionSourceSchema,
|
|
57
|
+
publishedAt: IsoDate,
|
|
58
|
+
/** User id of the administrator, or null for a version the server wrote itself. */
|
|
59
|
+
publishedBy: z.string().nullable(),
|
|
60
|
+
/** The version a rollback restored. */
|
|
61
|
+
rolledBackTo: z.number().int().positive().optional(),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export type PolicyVersion = z.infer<typeof PolicyVersionSchema>;
|
|
65
|
+
|
|
66
|
+
export const PolicyDraftSchema = z.object({
|
|
67
|
+
policy: PolicyDocumentSchema,
|
|
68
|
+
/** The published version the draft was started from. */
|
|
69
|
+
basedOnVersion: z.number().int().positive(),
|
|
70
|
+
updatedAt: IsoDate,
|
|
71
|
+
updatedBy: z.string().nullable(),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
export type PolicyDraft = z.infer<typeof PolicyDraftSchema>;
|
|
75
|
+
|
|
76
|
+
/** `GET /system-config/policy`. */
|
|
77
|
+
export const PolicyStateResponseSchema = z.object({
|
|
78
|
+
published: PolicyVersionSchema,
|
|
79
|
+
draft: PolicyDraftSchema.nullable(),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
export type PolicyStateResponse = z.infer<typeof PolicyStateResponseSchema>;
|
|
83
|
+
|
|
84
|
+
/** `PUT /system-config/policy/draft`. */
|
|
85
|
+
export const PolicyDraftSaveRequestSchema = z
|
|
86
|
+
.object({
|
|
87
|
+
policy: PolicyDocumentSchema,
|
|
88
|
+
})
|
|
89
|
+
.strict();
|
|
90
|
+
|
|
91
|
+
export type PolicyDraftSaveRequest = z.infer<typeof PolicyDraftSaveRequestSchema>;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* `POST /system-config/policy/publish`. `expectedVersion` is the published version the
|
|
95
|
+
* caller reviewed; publishing answers `409` if another version was published since, so
|
|
96
|
+
* a stale draft cannot silently undo someone else's change.
|
|
97
|
+
*/
|
|
98
|
+
export const PolicyPublishRequestSchema = z
|
|
99
|
+
.object({
|
|
100
|
+
expectedVersion: z.number().int().positive(),
|
|
101
|
+
})
|
|
102
|
+
.strict();
|
|
103
|
+
|
|
104
|
+
export type PolicyPublishRequest = z.infer<typeof PolicyPublishRequestSchema>;
|
|
105
|
+
|
|
106
|
+
/** `POST /system-config/policy/rollback`: publishes a copy of `version` as a new version. */
|
|
107
|
+
export const PolicyRollbackRequestSchema = z
|
|
108
|
+
.object({
|
|
109
|
+
version: z.number().int().positive(),
|
|
110
|
+
expectedVersion: z.number().int().positive(),
|
|
111
|
+
})
|
|
112
|
+
.strict();
|
|
113
|
+
|
|
114
|
+
export type PolicyRollbackRequest = z.infer<typeof PolicyRollbackRequestSchema>;
|
|
115
|
+
|
|
116
|
+
/** `GET /system-config/policy/versions`, newest first. */
|
|
117
|
+
export const PolicyVersionsResponseSchema = z.object({
|
|
118
|
+
versions: z.array(PolicyVersionSchema),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
export type PolicyVersionsResponse = z.infer<typeof PolicyVersionsResponseSchema>;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* `POST /system-config/policy/dry-run`. Without `policy`, evaluates the current draft.
|
|
125
|
+
*/
|
|
126
|
+
export const PolicyDryRunRequestSchema = z
|
|
127
|
+
.object({
|
|
128
|
+
policy: PolicyDocumentSchema.optional(),
|
|
129
|
+
days: z.number().int().min(1).max(30).default(7),
|
|
130
|
+
})
|
|
131
|
+
.strict();
|
|
132
|
+
|
|
133
|
+
export type PolicyDryRunRequest = z.infer<typeof PolicyDryRunRequestSchema>;
|
|
134
|
+
|
|
135
|
+
export const PolicyDryRunReasonSchema = z.enum([
|
|
136
|
+
'method_disabled',
|
|
137
|
+
'phishing_resistant_required',
|
|
138
|
+
'fallback_disabled',
|
|
139
|
+
'synced_passkey_blocked',
|
|
140
|
+
'authenticator_not_allowed',
|
|
141
|
+
'user_verification_required',
|
|
142
|
+
]);
|
|
143
|
+
|
|
144
|
+
export type PolicyDryRunReason = z.infer<typeof PolicyDryRunReasonSchema>;
|
|
145
|
+
|
|
146
|
+
export const PolicyDryRunUserSchema = z.object({
|
|
147
|
+
userId: z.string(),
|
|
148
|
+
email: z.string(),
|
|
149
|
+
/** Sign-ins in the window this policy would have refused. */
|
|
150
|
+
signIns: z.number().int().nonnegative(),
|
|
151
|
+
reasons: z.array(PolicyDryRunReasonSchema).min(1),
|
|
152
|
+
/** True when none of the user's enrolled factors would still sign them in. */
|
|
153
|
+
lockedOut: z.boolean(),
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
export type PolicyDryRunUser = z.infer<typeof PolicyDryRunUserSchema>;
|
|
157
|
+
|
|
158
|
+
export const PolicyDryRunResponseSchema = z.object({
|
|
159
|
+
window: z.object({ from: IsoDate, to: IsoDate }),
|
|
160
|
+
signInsEvaluated: z.number().int().nonnegative(),
|
|
161
|
+
usersEvaluated: z.number().int().nonnegative(),
|
|
162
|
+
wouldFail: z.array(PolicyDryRunUserSchema),
|
|
163
|
+
usersLockedOut: z.number().int().nonnegative(),
|
|
164
|
+
/** Break-glass accounts are exempt from policy, so they are counted, never refused. */
|
|
165
|
+
breakGlassAccounts: z.number().int().nonnegative(),
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
export type PolicyDryRunResponse = z.infer<typeof PolicyDryRunResponseSchema>;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
import { IsoDate } from '../../shared.js';
|
|
4
|
+
import { NextStepSchema, RefreshSuccessResponseSchema } from '../auth/auth.schema.js';
|
|
5
|
+
import { AuthDeliverySchema } from '../messaging/schema.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Ten base32 characters, shown as `XXXXX-XXXXX`. Accepted with or without the dash
|
|
9
|
+
* and in either case, because people copy them from paper.
|
|
10
|
+
*/
|
|
11
|
+
export const RecoveryCodeSchema = z
|
|
12
|
+
.string()
|
|
13
|
+
.trim()
|
|
14
|
+
.regex(/^[A-Za-z2-7]{5}-?[A-Za-z2-7]{5}$/, { message: 'Not a recovery code' });
|
|
15
|
+
|
|
16
|
+
export type RecoveryCode = z.infer<typeof RecoveryCodeSchema>;
|
|
17
|
+
|
|
18
|
+
/** `GET /recovery-codes`. `createdAt` is null until the user has generated a set. */
|
|
19
|
+
export const RecoveryCodesStatusSchema = z.object({
|
|
20
|
+
enabled: z.boolean(),
|
|
21
|
+
remaining: z.number().int().nonnegative(),
|
|
22
|
+
total: z.number().int().nonnegative(),
|
|
23
|
+
createdAt: IsoDate.nullable(),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export type RecoveryCodesStatus = z.infer<typeof RecoveryCodesStatusSchema>;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* `POST /recovery-codes`. The only time the codes are ever returned; the server keeps
|
|
30
|
+
* them hashed. Generating a set invalidates the previous one.
|
|
31
|
+
*/
|
|
32
|
+
export const RecoveryCodesGeneratedSchema = z.object({
|
|
33
|
+
message: z.string(),
|
|
34
|
+
codes: z.array(RecoveryCodeSchema).min(1),
|
|
35
|
+
createdAt: IsoDate,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export type RecoveryCodesGenerated = z.infer<typeof RecoveryCodesGeneratedSchema>;
|
|
39
|
+
|
|
40
|
+
/** `POST /recovery-codes/redeem`, with the pre-auth token from `/login`. */
|
|
41
|
+
export const RecoveryCodeRedeemRequestSchema = z.object({
|
|
42
|
+
code: RecoveryCodeSchema,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export type RecoveryCodeRedeemRequest = z.infer<typeof RecoveryCodeRedeemRequestSchema>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The session a recovery issues. `recovery: true` marks a restricted session: until
|
|
49
|
+
* the user enrolls a new passkey or TOTP, the API answers `403 enrollment_required` on
|
|
50
|
+
* everything but enrollment, recovery codes, `/users/me` and logout, and the server
|
|
51
|
+
* adapters' `requireAuth` refuse it unless told otherwise. Enrolling rotates it into a
|
|
52
|
+
* normal session.
|
|
53
|
+
*/
|
|
54
|
+
export const RecoverySessionResponseSchema = RefreshSuccessResponseSchema.omit({
|
|
55
|
+
sessionId: true,
|
|
56
|
+
}).extend({
|
|
57
|
+
recovery: z.literal(true),
|
|
58
|
+
nextStep: NextStepSchema,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
export type RecoverySessionResponse = z.infer<typeof RecoverySessionResponseSchema>;
|
|
62
|
+
|
|
63
|
+
export const AccountRecoveryChannelSchema = z.enum(['email', 'phone']);
|
|
64
|
+
|
|
65
|
+
export type AccountRecoveryChannel = z.infer<typeof AccountRecoveryChannelSchema>;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* `POST /recovery/start`, with the pre-auth token from `/login`. Sends a code to the
|
|
69
|
+
* verified address on file for that channel. The answer is the same whether or not
|
|
70
|
+
* the account has one, so it does not say which channels an account has.
|
|
71
|
+
*/
|
|
72
|
+
export const AccountRecoveryStartRequestSchema = z.object({
|
|
73
|
+
channel: AccountRecoveryChannelSchema.default('email'),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
export type AccountRecoveryStartRequest = z.infer<typeof AccountRecoveryStartRequestSchema>;
|
|
77
|
+
|
|
78
|
+
export const AccountRecoveryStartResponseSchema = z.object({
|
|
79
|
+
message: z.string(),
|
|
80
|
+
delivery: AuthDeliverySchema.optional(),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
export type AccountRecoveryStartResponse = z.infer<typeof AccountRecoveryStartResponseSchema>;
|
|
84
|
+
|
|
85
|
+
/** `POST /recovery/verify`: the code from `/recovery/start` opens the request. */
|
|
86
|
+
export const AccountRecoveryVerifyRequestSchema = z.object({
|
|
87
|
+
code: z.string().trim().min(1),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
export type AccountRecoveryVerifyRequest = z.infer<typeof AccountRecoveryVerifyRequestSchema>;
|
|
91
|
+
|
|
92
|
+
export const AccountRecoveryStateSchema = z.enum(['pending', 'ready', 'cancelled', 'completed']);
|
|
93
|
+
|
|
94
|
+
export type AccountRecoveryState = z.infer<typeof AccountRecoveryStateSchema>;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* `POST /recovery/verify` and `GET /recovery/status`. `pending` until `completesAt`,
|
|
98
|
+
* then `ready`; every other channel and active session was told when it opened, and
|
|
99
|
+
* any of them can cancel it.
|
|
100
|
+
*/
|
|
101
|
+
export const AccountRecoveryStatusSchema = z.object({
|
|
102
|
+
state: AccountRecoveryStateSchema,
|
|
103
|
+
requestedAt: IsoDate,
|
|
104
|
+
completesAt: IsoDate,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
export type AccountRecoveryStatus = z.infer<typeof AccountRecoveryStatusSchema>;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* `POST /recovery/cancel`. Either the token from the notice sent to the other
|
|
111
|
+
* channels, or no body with an access session for the account.
|
|
112
|
+
*/
|
|
113
|
+
export const AccountRecoveryCancelRequestSchema = z.object({
|
|
114
|
+
cancelToken: z.string().trim().min(1).optional(),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
export type AccountRecoveryCancelRequest = z.infer<typeof AccountRecoveryCancelRequestSchema>;
|
|
@@ -45,6 +45,24 @@ export const RedirectTargetSchema = z.url().refine(
|
|
|
45
45
|
|
|
46
46
|
export type RedirectTarget = z.infer<typeof RedirectTargetSchema>;
|
|
47
47
|
|
|
48
|
+
// Provider endpoints the server fetches, and the authorization URL the browser is sent
|
|
49
|
+
// to. `z.url()` alone accepts any scheme, including `javascript:`.
|
|
50
|
+
const ProviderUrlSchema = z.url({ protocol: /^https?$/ });
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The auth server reads a provider's secret from this env var, so the name is held to
|
|
54
|
+
* the shape of an OAuth client secret: configuration cannot point the token exchange at
|
|
55
|
+
* any other secret the server holds (GHSA-qx82-28gf-w744).
|
|
56
|
+
*/
|
|
57
|
+
export const OAuthClientSecretEnvSchema = z
|
|
58
|
+
.string()
|
|
59
|
+
.trim()
|
|
60
|
+
.regex(/^[A-Z][A-Z0-9_]{0,59}_CLIENT_SECRET$/, {
|
|
61
|
+
message: 'Must be an env var name ending in _CLIENT_SECRET, for example GOOGLE_CLIENT_SECRET',
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export type OAuthClientSecretEnv = z.infer<typeof OAuthClientSecretEnvSchema>;
|
|
65
|
+
|
|
48
66
|
export const OAuthProviderConfigSchema = z.object({
|
|
49
67
|
id: OAuthProviderIdSchema,
|
|
50
68
|
name: z.string().trim().min(1).max(80),
|
|
@@ -52,10 +70,10 @@ export const OAuthProviderConfigSchema = z.object({
|
|
|
52
70
|
clientId: z.string().trim().min(1),
|
|
53
71
|
// The secret itself never lives in config; this names the environment
|
|
54
72
|
// variable the server reads it from.
|
|
55
|
-
clientSecretEnv:
|
|
56
|
-
authorizationUrl:
|
|
57
|
-
tokenUrl:
|
|
58
|
-
userInfoUrl:
|
|
73
|
+
clientSecretEnv: OAuthClientSecretEnvSchema,
|
|
74
|
+
authorizationUrl: ProviderUrlSchema,
|
|
75
|
+
tokenUrl: ProviderUrlSchema,
|
|
76
|
+
userInfoUrl: ProviderUrlSchema,
|
|
59
77
|
scopes: z.array(z.string().trim().min(1)).default([]),
|
|
60
78
|
redirectUri: z.url().optional(),
|
|
61
79
|
redirectUris: z.array(z.url()).default([]),
|
|
@@ -73,7 +91,7 @@ export const OAuthProviderConfigSchema = z.object({
|
|
|
73
91
|
* read for the profile claims instead of calling `userInfoUrl`.
|
|
74
92
|
*/
|
|
75
93
|
issuer: z.url().optional(),
|
|
76
|
-
jwksUri:
|
|
94
|
+
jwksUri: ProviderUrlSchema.optional(),
|
|
77
95
|
/**
|
|
78
96
|
* Links a first sign-in to a user imported under this source (see
|
|
79
97
|
* `ImportUsersRequestSchema`) whose external id equals the ID token claim at
|
|
@@ -277,6 +295,41 @@ export const DefaultAuthenticatorPolicy = {
|
|
|
277
295
|
|
|
278
296
|
const DurationSchema = z.string().regex(/^\d+[smhd]$/);
|
|
279
297
|
|
|
298
|
+
const DURATION_SECONDS = { s: 1, m: 60, h: 3600, d: 86400 } as const;
|
|
299
|
+
|
|
300
|
+
function durationSeconds(value: string) {
|
|
301
|
+
const unit = value.slice(-1) as keyof typeof DURATION_SECONDS;
|
|
302
|
+
return Number(value.slice(0, -1)) * DURATION_SECONDS[unit];
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Single-use recovery codes. On by default: a user only has codes after generating
|
|
307
|
+
* them, so the switch only matters once someone has. Not a login method, so no
|
|
308
|
+
* `login_methods` change can take a user's way back in away from them.
|
|
309
|
+
*/
|
|
310
|
+
export const RecoveryCodesPolicySchema = z.object({
|
|
311
|
+
enabled: z.boolean().default(true),
|
|
312
|
+
/** Ask for a set of codes as part of first passkey enrollment. */
|
|
313
|
+
requireAtEnrollment: z.boolean().default(false),
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
export type RecoveryCodesPolicy = z.infer<typeof RecoveryCodesPolicySchema>;
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Self-service recovery through a verified email or phone, completed only after
|
|
320
|
+
* `delay` and announced on every other channel and session so the owner can cancel.
|
|
321
|
+
* Off by default: it is a way into an account that does not need its factors.
|
|
322
|
+
*/
|
|
323
|
+
export const AccountRecoveryPolicySchema = z.object({
|
|
324
|
+
enabled: z.boolean().default(false),
|
|
325
|
+
delay: DurationSchema.refine(
|
|
326
|
+
(value) => durationSeconds(value) >= 3600 && durationSeconds(value) <= 30 * 86400,
|
|
327
|
+
{ message: 'delay must be between 1h and 30d' },
|
|
328
|
+
).default('24h'),
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
export type AccountRecoveryPolicy = z.infer<typeof AccountRecoveryPolicySchema>;
|
|
332
|
+
|
|
280
333
|
export const SystemConfigSchema = z.object({
|
|
281
334
|
app_name: z.string().min(3),
|
|
282
335
|
default_roles: z.array(RoleNameSchema).min(1),
|
|
@@ -305,6 +358,10 @@ export const SystemConfigSchema = z.object({
|
|
|
305
358
|
authenticator_policy: AuthenticatorPolicySchema.default(() =>
|
|
306
359
|
AuthenticatorPolicySchema.parse({}),
|
|
307
360
|
),
|
|
361
|
+
recovery_codes: RecoveryCodesPolicySchema.default(() => RecoveryCodesPolicySchema.parse({})),
|
|
362
|
+
account_recovery: AccountRecoveryPolicySchema.default(() =>
|
|
363
|
+
AccountRecoveryPolicySchema.parse({}),
|
|
364
|
+
),
|
|
308
365
|
|
|
309
366
|
access_token_ttl: z.string().regex(/^\d+[smhd]$/),
|
|
310
367
|
|
|
@@ -377,6 +434,8 @@ export const SystemConfigPatchSchema = z
|
|
|
377
434
|
oauth_providers: z.array(OAuthProviderConfigSchema).optional(),
|
|
378
435
|
lockout_policy: LockoutPolicySchema.optional(),
|
|
379
436
|
authenticator_policy: AuthenticatorPolicySchema.optional(),
|
|
437
|
+
recovery_codes: RecoveryCodesPolicySchema.optional(),
|
|
438
|
+
account_recovery: AccountRecoveryPolicySchema.optional(),
|
|
380
439
|
access_token_ttl: SystemConfigSchema.shape.access_token_ttl.optional(),
|
|
381
440
|
session_idle_ttl: DurationSchema.optional(),
|
|
382
441
|
refresh_token_ttl: SystemConfigSchema.shape.refresh_token_ttl.optional(),
|
|
@@ -511,6 +570,10 @@ export type AvailableRolesResponse = z.infer<typeof AvailableRolesResponseSchema
|
|
|
511
570
|
*/
|
|
512
571
|
export const PublicSystemConfigResponseSchema = z.object({
|
|
513
572
|
loginMethods: z.array(LoginMethodSchema).min(1),
|
|
573
|
+
// Optional so a client still parses a server from before v0.20. Lets the sign-in
|
|
574
|
+
// screens offer "use a recovery code" and "can't sign in?" only where they work.
|
|
575
|
+
recoveryCodes: z.boolean().optional(),
|
|
576
|
+
accountRecovery: z.boolean().optional(),
|
|
514
577
|
});
|
|
515
578
|
|
|
516
579
|
export type PublicSystemConfigResponse = z.infer<typeof PublicSystemConfigResponseSchema>;
|