@tula-tech/contract 0.0.0-stage → 0.1.0-alpha.1
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/LICENSE +202 -0
- package/README.md +38 -2
- package/dist/custom-claims.d.ts +161 -0
- package/dist/custom-claims.js +28 -0
- package/dist/device-binding.d.ts +217 -0
- package/dist/device-binding.js +24 -0
- package/dist/error-codes.d.ts +386 -0
- package/dist/error-codes.js +10 -0
- package/dist/event-types.d.ts +107 -0
- package/dist/event-types.js +10 -0
- package/dist/headers.d.ts +223 -0
- package/dist/headers.js +36 -0
- package/dist/index.d.ts +10384 -0
- package/dist/index.js +3912 -0
- package/dist/issuer.d.ts +31 -0
- package/dist/issuer.js +8 -0
- package/dist/password-rules.d.ts +452 -0
- package/dist/password-rules.js +10 -0
- package/dist/redirect-url.d.ts +223 -0
- package/dist/redirect-url.js +26 -0
- package/dist/shared/chunk-2xak18qy.js +56 -0
- package/dist/shared/chunk-4rb5jsfz.js +102 -0
- package/dist/shared/chunk-4stpmame.js +58 -0
- package/dist/shared/chunk-7yqzghgm.js +108 -0
- package/dist/shared/chunk-9pvdavrw.js +9 -0
- package/dist/shared/chunk-cce3bhet.js +251 -0
- package/dist/shared/chunk-pcn6ax54.js +195 -0
- package/dist/shared/chunk-q309e9bk.js +19 -0
- package/dist/shared/chunk-r202nxfw.js +438 -0
- package/dist/shared/chunk-sc6qg3d2.js +219 -0
- package/dist/theme.d.ts +227 -0
- package/dist/theme.js +16 -0
- package/dist/webhook-signature.d.ts +103 -0
- package/dist/webhook-signature.js +22 -0
- package/openapi.json +30076 -0
- package/package.json +79 -4
package/dist/index.js
ADDED
|
@@ -0,0 +1,3912 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ACTIVITY_TYPES2,
|
|
3
|
+
EVENT_SCHEMA_VERSION2,
|
|
4
|
+
EVENT_TARGET_TYPES2
|
|
5
|
+
} from "./shared/chunk-7yqzghgm.js";
|
|
6
|
+
import {
|
|
7
|
+
COMMON_PASSWORDS2,
|
|
8
|
+
DEFAULT_SPECIAL_CHARS2,
|
|
9
|
+
normalizePassword2,
|
|
10
|
+
evaluatePassword2
|
|
11
|
+
} from "./shared/chunk-sc6qg3d2.js";
|
|
12
|
+
import {
|
|
13
|
+
CUSTOM_CLAIMS_CLAIM2,
|
|
14
|
+
RESERVED_CLAIM_NAMES2,
|
|
15
|
+
MAX_CUSTOM_CLAIMS_BYTES2,
|
|
16
|
+
MAX_JWT_TEMPLATES2,
|
|
17
|
+
MAX_JWT_TEMPLATE_CLAIMS2,
|
|
18
|
+
MAX_CUSTOM_CLAIM_KEY_LENGTH2,
|
|
19
|
+
MAX_CUSTOM_CLAIM_CONSTANT_LENGTH2,
|
|
20
|
+
isCustomClaimKey2,
|
|
21
|
+
isCustomClaimValue2,
|
|
22
|
+
customClaimsBytes2,
|
|
23
|
+
checkCustomClaims2,
|
|
24
|
+
readCustomClaims2
|
|
25
|
+
} from "./shared/chunk-4rb5jsfz.js";
|
|
26
|
+
import {
|
|
27
|
+
DPOP_HEADER2,
|
|
28
|
+
DPOP_NONCE_HEADER2,
|
|
29
|
+
DPOP_PROOF_TYPE2,
|
|
30
|
+
DPOP_ALGORITHMS2,
|
|
31
|
+
MAX_DPOP_PROOF_LENGTH2,
|
|
32
|
+
isDevicePublicJwk2,
|
|
33
|
+
isKeyThumbprint2,
|
|
34
|
+
jwkThumbprint2,
|
|
35
|
+
createDpopProof2,
|
|
36
|
+
generateSoftwareDeviceKey2
|
|
37
|
+
} from "./shared/chunk-4stpmame.js";
|
|
38
|
+
import {
|
|
39
|
+
EMAIL_TEMPLATE_KINDS2,
|
|
40
|
+
EMAIL_TEMPLATE_PLACEHOLDERS2,
|
|
41
|
+
EMAIL_TEMPLATE_DIGIT_PLACEHOLDERS2,
|
|
42
|
+
EMAIL_TEMPLATE_RULES2,
|
|
43
|
+
MAX_EMAIL_SUBJECT_LENGTH2,
|
|
44
|
+
MAX_EMAIL_BODY_LENGTH2,
|
|
45
|
+
MAX_EMAIL_TEMPLATES_BYTES2,
|
|
46
|
+
parseEmailTemplate2,
|
|
47
|
+
emailTemplateParagraphs2,
|
|
48
|
+
hasHiddenCharacter2,
|
|
49
|
+
withoutHiddenCharacters2,
|
|
50
|
+
visibleEmailText2,
|
|
51
|
+
withoutInvisibleCharacters2,
|
|
52
|
+
hasInvisibleCharacter2,
|
|
53
|
+
EMAIL_LINK_SCHEMES2,
|
|
54
|
+
readsAsLink2,
|
|
55
|
+
emailTemplateProblems2,
|
|
56
|
+
isEmailTemplateKind2,
|
|
57
|
+
emailTemplatesBytes2,
|
|
58
|
+
readStoredEmailTemplates2,
|
|
59
|
+
REDIRECT_SCHEMES_NEVER_CUSTOM2,
|
|
60
|
+
REDIRECT_SCHEME_FAMILIES_NEVER_CUSTOM2,
|
|
61
|
+
hasForbiddenRedirectCharacter2,
|
|
62
|
+
redirectUrlKind2,
|
|
63
|
+
isRedirectUrl2,
|
|
64
|
+
isCustomSchemeRedirectUrl2,
|
|
65
|
+
OAUTH_PROVIDERS_WITH_PKCE2,
|
|
66
|
+
OAUTH_PROVIDERS_WITHOUT_PKCE2,
|
|
67
|
+
bindsCodeWithPkce2,
|
|
68
|
+
CUSTOM_SCHEME_REDIRECT_REFUSALS2,
|
|
69
|
+
customSchemeRedirectRefusal2
|
|
70
|
+
} from "./shared/chunk-r202nxfw.js";
|
|
71
|
+
import {
|
|
72
|
+
ERROR_DEFINITIONS2,
|
|
73
|
+
ERROR_CODES2,
|
|
74
|
+
errorDefinition2
|
|
75
|
+
} from "./shared/chunk-cce3bhet.js";
|
|
76
|
+
import {
|
|
77
|
+
environmentIssuer2,
|
|
78
|
+
jwksUrl2
|
|
79
|
+
} from "./shared/chunk-9pvdavrw.js";
|
|
80
|
+
import {
|
|
81
|
+
PUBLISHABLE_KEY_HEADER2,
|
|
82
|
+
CLIENT_HEADER2,
|
|
83
|
+
SESSION_PROFILE_HEADER2,
|
|
84
|
+
FLOW_ATTEMPT_HEADER2,
|
|
85
|
+
CAN_STILL_SIGN_IN_HEADER2,
|
|
86
|
+
CONFIG_MANAGED_BY_HEADER2,
|
|
87
|
+
CONFIG_HASH_HEADER2,
|
|
88
|
+
CONFIG_UNMANAGED2,
|
|
89
|
+
DASHBOARD_HEADER2,
|
|
90
|
+
DASHBOARD_HEADER_VALUE2,
|
|
91
|
+
ENVIRONMENT_HEADER2,
|
|
92
|
+
DASHBOARD_SESSION_COOKIE2,
|
|
93
|
+
EMAIL_LINK_TOKEN_PARAM2,
|
|
94
|
+
EMAIL_LINK_ATTEMPT_PARAM2,
|
|
95
|
+
OAUTH_TICKET_PARAM2,
|
|
96
|
+
OAUTH_ERROR_PARAM2
|
|
97
|
+
} from "./shared/chunk-q309e9bk.js";
|
|
98
|
+
import {
|
|
99
|
+
THEME_TOKENS2,
|
|
100
|
+
DEFAULT_THEME2,
|
|
101
|
+
darkCssVariable2,
|
|
102
|
+
isValidThemeValue2,
|
|
103
|
+
themeToCssVariables2,
|
|
104
|
+
contrastRatio2
|
|
105
|
+
} from "./shared/chunk-pcn6ax54.js";
|
|
106
|
+
import {
|
|
107
|
+
WEBHOOK_ID_HEADER2,
|
|
108
|
+
WEBHOOK_TIMESTAMP_HEADER2,
|
|
109
|
+
WEBHOOK_SIGNATURE_HEADER2,
|
|
110
|
+
WEBHOOK_SECRET_PREFIX2,
|
|
111
|
+
WEBHOOK_SIGNATURE_VERSION2,
|
|
112
|
+
WEBHOOK_TOLERANCE_SECONDS2,
|
|
113
|
+
webhookSecretBytes2,
|
|
114
|
+
formatWebhookSecret2,
|
|
115
|
+
signWebhook2
|
|
116
|
+
} from "./shared/chunk-2xak18qy.js";
|
|
117
|
+
|
|
118
|
+
// src/audit.ts
|
|
119
|
+
import { z as z3 } from "zod";
|
|
120
|
+
|
|
121
|
+
// src/user.ts
|
|
122
|
+
import { z as z2 } from "zod";
|
|
123
|
+
|
|
124
|
+
// src/passkey.ts
|
|
125
|
+
import { z } from "zod";
|
|
126
|
+
var MAX_PASSKEYS_PER_USER = 10;
|
|
127
|
+
var MAX_PASSKEY_NAME_LENGTH = 64;
|
|
128
|
+
var PASSKEY_CHALLENGE_TTL_MS = 300000;
|
|
129
|
+
var PASSKEY_ALGORITHMS = [-7, -8, -257];
|
|
130
|
+
var Base64Url = z.string().min(1).max(16384).regex(/^[A-Za-z0-9_-]+$/);
|
|
131
|
+
var Transports = z.array(z.string().max(32)).max(16);
|
|
132
|
+
var CredentialDescriptor = z.object({
|
|
133
|
+
type: z.literal("public-key"),
|
|
134
|
+
id: z.string(),
|
|
135
|
+
transports: z.array(z.string()).optional()
|
|
136
|
+
});
|
|
137
|
+
var PasskeySchema = z.object({
|
|
138
|
+
id: z.string(),
|
|
139
|
+
name: z.string(),
|
|
140
|
+
synced: z.boolean(),
|
|
141
|
+
createdAt: z.iso.datetime(),
|
|
142
|
+
lastUsedAt: z.iso.datetime().nullable()
|
|
143
|
+
}).meta({ ref: "Passkey" });
|
|
144
|
+
var PasskeyListSchema = z.object({ passkeys: z.array(PasskeySchema) }).meta({ ref: "PasskeyList" });
|
|
145
|
+
var PasskeyCreationOptionsSchema = z.object({
|
|
146
|
+
rp: z.object({ id: z.string(), name: z.string() }),
|
|
147
|
+
user: z.object({ id: z.string(), name: z.string(), displayName: z.string() }),
|
|
148
|
+
challenge: z.string(),
|
|
149
|
+
pubKeyCredParams: z.array(z.object({ type: z.literal("public-key"), alg: z.number().int() })),
|
|
150
|
+
timeout: z.number().int(),
|
|
151
|
+
excludeCredentials: z.array(CredentialDescriptor),
|
|
152
|
+
authenticatorSelection: z.object({
|
|
153
|
+
residentKey: z.literal("required"),
|
|
154
|
+
requireResidentKey: z.literal(true),
|
|
155
|
+
userVerification: z.literal("required")
|
|
156
|
+
}),
|
|
157
|
+
attestation: z.literal("none")
|
|
158
|
+
}).meta({ ref: "PasskeyCreationOptions" });
|
|
159
|
+
var PasskeyRequestOptionsSchema = z.object({
|
|
160
|
+
challenge: z.string(),
|
|
161
|
+
timeout: z.number().int(),
|
|
162
|
+
rpId: z.string(),
|
|
163
|
+
userVerification: z.literal("required"),
|
|
164
|
+
allowCredentials: z.array(CredentialDescriptor).optional()
|
|
165
|
+
}).meta({ ref: "PasskeyRequestOptions" });
|
|
166
|
+
var PasskeyRegistrationCredentialSchema = z.looseObject({
|
|
167
|
+
id: Base64Url.max(1366),
|
|
168
|
+
rawId: Base64Url.max(1366),
|
|
169
|
+
type: z.literal("public-key"),
|
|
170
|
+
response: z.looseObject({
|
|
171
|
+
clientDataJSON: Base64Url,
|
|
172
|
+
attestationObject: Base64Url,
|
|
173
|
+
transports: Transports.optional()
|
|
174
|
+
})
|
|
175
|
+
}).meta({ ref: "PasskeyRegistrationCredential" });
|
|
176
|
+
var PasskeyAssertionCredentialSchema = z.looseObject({
|
|
177
|
+
id: Base64Url.max(1366),
|
|
178
|
+
rawId: Base64Url.max(1366),
|
|
179
|
+
type: z.literal("public-key"),
|
|
180
|
+
response: z.looseObject({
|
|
181
|
+
clientDataJSON: Base64Url,
|
|
182
|
+
authenticatorData: Base64Url,
|
|
183
|
+
signature: Base64Url,
|
|
184
|
+
userHandle: Base64Url.max(128).nullish()
|
|
185
|
+
})
|
|
186
|
+
}).meta({ ref: "PasskeyAssertionCredential" });
|
|
187
|
+
var PasskeyName = z.string().trim().min(1).max(MAX_PASSKEY_NAME_LENGTH);
|
|
188
|
+
var PasskeyRegisterRequestSchema = z.object({ credential: PasskeyRegistrationCredentialSchema, name: PasskeyName.optional() }).meta({ ref: "PasskeyRegisterRequest" });
|
|
189
|
+
var PasskeyRenameRequestSchema = z.object({ name: PasskeyName }).meta({ ref: "PasskeyRenameRequest" });
|
|
190
|
+
var PasskeySignInRequestSchema = z.object({ credential: PasskeyAssertionCredentialSchema }).meta({ ref: "PasskeySignInRequest" });
|
|
191
|
+
|
|
192
|
+
// src/user.ts
|
|
193
|
+
var MAX_PAGE_SIZE = 100;
|
|
194
|
+
var DEFAULT_PAGE_SIZE = 20;
|
|
195
|
+
var PaginationMetaSchema = z2.object({
|
|
196
|
+
totalCount: z2.number().int().min(0),
|
|
197
|
+
totalPages: z2.number().int().min(0),
|
|
198
|
+
page: z2.number().int().min(1),
|
|
199
|
+
perPage: z2.number().int().min(1)
|
|
200
|
+
}).meta({ ref: "PaginationMeta" });
|
|
201
|
+
var UserSchema = z2.object({
|
|
202
|
+
id: z2.string(),
|
|
203
|
+
email: z2.string().nullable(),
|
|
204
|
+
emailVerifiedAt: z2.iso.datetime().nullable(),
|
|
205
|
+
firstName: z2.string().nullable(),
|
|
206
|
+
lastName: z2.string().nullable(),
|
|
207
|
+
bannedAt: z2.iso.datetime().nullable(),
|
|
208
|
+
lastSignInAt: z2.iso.datetime().nullable(),
|
|
209
|
+
createdAt: z2.iso.datetime(),
|
|
210
|
+
phoneNumber: z2.string().nullable().optional(),
|
|
211
|
+
phoneNumberVerifiedAt: z2.iso.datetime().nullable().optional()
|
|
212
|
+
}).meta({ ref: "User" });
|
|
213
|
+
var CurrentUserSchema = UserSchema.extend({
|
|
214
|
+
hasPassword: z2.boolean()
|
|
215
|
+
}).meta({ ref: "CurrentUser" });
|
|
216
|
+
var UserAuthenticationSchema = z2.object({
|
|
217
|
+
hasPassword: z2.boolean(),
|
|
218
|
+
emailVerified: z2.boolean(),
|
|
219
|
+
identities: z2.array(z2.object({ provider: z2.string(), linkedAt: z2.iso.datetime() })),
|
|
220
|
+
factors: z2.array(z2.object({ type: z2.string(), confirmedAt: z2.iso.datetime() })),
|
|
221
|
+
backupCodesRemaining: z2.number().int().min(0),
|
|
222
|
+
passkeys: z2.array(PasskeySchema),
|
|
223
|
+
canSignInWithoutPasskeys: z2.boolean()
|
|
224
|
+
}).meta({ ref: "UserAuthentication" });
|
|
225
|
+
var UserListSchema = z2.object({ meta: PaginationMetaSchema, data: z2.array(UserSchema) }).meta({ ref: "UserList" });
|
|
226
|
+
var UserSortSchema = z2.enum(["createdAt", "-createdAt", "email", "-email", "lastSignInAt", "-lastSignInAt"]).meta({ ref: "UserSort" });
|
|
227
|
+
var CreateUserRequestSchema = z2.object({
|
|
228
|
+
email: z2.string().max(320),
|
|
229
|
+
password: z2.string().max(1024).optional(),
|
|
230
|
+
firstName: z2.string().trim().max(100).optional(),
|
|
231
|
+
lastName: z2.string().trim().max(100).optional(),
|
|
232
|
+
emailVerified: z2.boolean().optional()
|
|
233
|
+
}).meta({ ref: "CreateUserRequest" });
|
|
234
|
+
var SetPasswordRequestSchema = z2.object({ password: z2.string().max(1024) }).meta({ ref: "SetPasswordRequest" });
|
|
235
|
+
var ChangePasswordRequestSchema = z2.object({
|
|
236
|
+
currentPassword: z2.string().max(1024),
|
|
237
|
+
newPassword: z2.string().max(1024)
|
|
238
|
+
}).meta({ ref: "ChangePasswordRequest" });
|
|
239
|
+
var PhoneNumberRequestSchema = z2.object({ phoneNumber: z2.string().max(64) }).meta({ ref: "PhoneNumberRequest" });
|
|
240
|
+
var PhoneCodeSentSchema = z2.object({
|
|
241
|
+
destination: z2.string(),
|
|
242
|
+
expiresAt: z2.iso.datetime()
|
|
243
|
+
}).meta({ ref: "PhoneCodeSent" });
|
|
244
|
+
var PhoneNumberVerifyRequestSchema = z2.object({ code: z2.string().regex(/^\d{6}$/) }).meta({ ref: "PhoneNumberVerifyRequest" });
|
|
245
|
+
var EmailAddressRequestSchema = z2.object({ email: z2.string().max(320) }).meta({ ref: "EmailAddressRequest" });
|
|
246
|
+
var EmailCodeSentSchema = z2.object({
|
|
247
|
+
destination: z2.string(),
|
|
248
|
+
expiresAt: z2.iso.datetime()
|
|
249
|
+
}).meta({ ref: "EmailCodeSent" });
|
|
250
|
+
var EmailAddressVerifyRequestSchema = z2.object({ code: z2.string().regex(/^\d{6}$/) }).meta({ ref: "EmailAddressVerifyRequest" });
|
|
251
|
+
var SetUserEmailRequestSchema = z2.strictObject({ email: z2.string().max(320) }).meta({ ref: "SetUserEmailRequest" });
|
|
252
|
+
|
|
253
|
+
// src/audit.ts
|
|
254
|
+
var ActivityTypeSchema = z3.enum(ACTIVITY_TYPES2).meta({ ref: "ActivityType" });
|
|
255
|
+
var AUDIT_ACTOR_TYPES = ["user", "admin", "system", "agent", "instance_admin"];
|
|
256
|
+
var INSTANCE_ACTIVITY_TYPES = [
|
|
257
|
+
"instance.signed_in",
|
|
258
|
+
"instance.sign_in_failed",
|
|
259
|
+
"instance.signed_out",
|
|
260
|
+
"workspace.created",
|
|
261
|
+
"project.created",
|
|
262
|
+
"project.renamed",
|
|
263
|
+
"environment.created"
|
|
264
|
+
];
|
|
265
|
+
var InstanceActivityTypeSchema = z3.enum(INSTANCE_ACTIVITY_TYPES).meta({ ref: "InstanceActivityType" });
|
|
266
|
+
var INSTANCE_AUDIT_TARGET_TYPES = ["workspace", "project", "environment"];
|
|
267
|
+
var AUDIT_TARGET_TYPES = [
|
|
268
|
+
"user",
|
|
269
|
+
"session",
|
|
270
|
+
"api_key",
|
|
271
|
+
"signing_key",
|
|
272
|
+
"environment",
|
|
273
|
+
"webhook_endpoint",
|
|
274
|
+
"hook",
|
|
275
|
+
"native_app",
|
|
276
|
+
"proposal"
|
|
277
|
+
];
|
|
278
|
+
var AuditLogSchema = z3.object({
|
|
279
|
+
id: z3.string(),
|
|
280
|
+
action: z3.string(),
|
|
281
|
+
actor: z3.object({
|
|
282
|
+
type: z3.string(),
|
|
283
|
+
id: z3.string().nullable()
|
|
284
|
+
}),
|
|
285
|
+
target: z3.object({ type: z3.string(), id: z3.string() }).nullable(),
|
|
286
|
+
ipAddress: z3.string().nullable(),
|
|
287
|
+
userAgent: z3.string().nullable(),
|
|
288
|
+
metadata: z3.record(z3.string(), z3.unknown()),
|
|
289
|
+
occurredAt: z3.iso.datetime()
|
|
290
|
+
}).meta({ ref: "AuditLog" });
|
|
291
|
+
var AuditLogListSchema = z3.object({ meta: PaginationMetaSchema, data: z3.array(AuditLogSchema) }).meta({ ref: "AuditLogList" });
|
|
292
|
+
// src/duration.ts
|
|
293
|
+
import { z as z4 } from "zod";
|
|
294
|
+
var UNIT_MS = { s: 1000, m: 60000, h: 3600000, d: 86400000 };
|
|
295
|
+
var MAX_DURATION_MS = 3650 * UNIT_MS.d;
|
|
296
|
+
var DURATION_PATTERN = /^([1-9]\d{0,6})(s|m|h|d)$/;
|
|
297
|
+
function parse(duration) {
|
|
298
|
+
const match = DURATION_PATTERN.exec(duration);
|
|
299
|
+
if (!match) {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
const [, amount, unit] = match;
|
|
303
|
+
const ms = Number(amount) * UNIT_MS[unit];
|
|
304
|
+
return ms <= MAX_DURATION_MS ? ms : null;
|
|
305
|
+
}
|
|
306
|
+
var DurationSchema = z4.string().refine((value) => parse(value) !== null, {
|
|
307
|
+
message: 'Use a positive number followed by s, m, h or d, up to 3650d (e.g. "30d")'
|
|
308
|
+
}).meta({ ref: "Duration" });
|
|
309
|
+
function durationToMs(duration) {
|
|
310
|
+
const ms = parse(duration);
|
|
311
|
+
if (ms === null) {
|
|
312
|
+
throw new Error(`Invalid duration "${duration}"`);
|
|
313
|
+
}
|
|
314
|
+
return ms;
|
|
315
|
+
}
|
|
316
|
+
// src/email-template-schema.ts
|
|
317
|
+
import { z as z5 } from "zod";
|
|
318
|
+
var EmailTemplateSchema = z5.strictObject({
|
|
319
|
+
subject: z5.string().max(MAX_EMAIL_SUBJECT_LENGTH2).optional(),
|
|
320
|
+
body: z5.string().max(MAX_EMAIL_BODY_LENGTH2).optional()
|
|
321
|
+
}).refine((template) => template.subject !== undefined || template.body !== undefined, {
|
|
322
|
+
message: "a template sets a subject, a body or both"
|
|
323
|
+
}).meta({ ref: "EmailTemplate" });
|
|
324
|
+
var shape = Object.fromEntries(EMAIL_TEMPLATE_KINDS2.map((kind) => [kind, EmailTemplateSchema.optional()]));
|
|
325
|
+
var EmailTemplatesSchema = z5.strictObject(shape).superRefine((templates, context) => {
|
|
326
|
+
for (const kind of EMAIL_TEMPLATE_KINDS2) {
|
|
327
|
+
const template = templates[kind];
|
|
328
|
+
if (template === undefined) {
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
for (const problem of emailTemplateProblems2(kind, template)) {
|
|
332
|
+
context.addIssue({
|
|
333
|
+
code: "custom",
|
|
334
|
+
path: [kind, problem.field],
|
|
335
|
+
message: problem.message
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if (emailTemplatesBytes2(templates) > MAX_EMAIL_TEMPLATES_BYTES2) {
|
|
340
|
+
context.addIssue({
|
|
341
|
+
code: "custom",
|
|
342
|
+
path: [],
|
|
343
|
+
message: `the templates together must take at most ${MAX_EMAIL_TEMPLATES_BYTES2} bytes`
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}).meta({ ref: "EmailTemplates" });
|
|
347
|
+
var EmailSettingsSchema = z5.strictObject({ templates: EmailTemplatesSchema.default({}) }).meta({ ref: "EmailSettings" });
|
|
348
|
+
// src/environment-settings.ts
|
|
349
|
+
import { z as z11 } from "zod";
|
|
350
|
+
|
|
351
|
+
// src/password-policy.ts
|
|
352
|
+
import { z as z6 } from "zod";
|
|
353
|
+
var MAX_PASSWORD_HISTORY = 24;
|
|
354
|
+
var PasswordPolicySchema = z6.object({
|
|
355
|
+
preset: z6.enum(["recommended", "strict", "legacy", "custom"]),
|
|
356
|
+
minLength: z6.number().int().min(1).max(256),
|
|
357
|
+
maxLength: z6.number().int().min(8).max(1024),
|
|
358
|
+
requireLowercase: z6.boolean(),
|
|
359
|
+
requireUppercase: z6.boolean(),
|
|
360
|
+
requireNumber: z6.boolean(),
|
|
361
|
+
requireSpecial: z6.boolean(),
|
|
362
|
+
minCharacterClasses: z6.number().int().min(0).max(4),
|
|
363
|
+
specialChars: z6.string().min(1),
|
|
364
|
+
disallowUserInfo: z6.boolean(),
|
|
365
|
+
disallowCommon: z6.boolean(),
|
|
366
|
+
breachCheck: z6.enum(["off", "warn", "block"]),
|
|
367
|
+
maxRepeatedChars: z6.number().int().min(1).nullable(),
|
|
368
|
+
blockSequences: z6.boolean(),
|
|
369
|
+
history: z6.number().int().min(0).max(MAX_PASSWORD_HISTORY),
|
|
370
|
+
expiryDays: z6.number().int().min(1).nullable()
|
|
371
|
+
}).refine((p) => p.maxLength >= p.minLength, {
|
|
372
|
+
message: "maxLength must be at least minLength",
|
|
373
|
+
path: ["maxLength"]
|
|
374
|
+
}).meta({ ref: "PasswordPolicy" });
|
|
375
|
+
var BASE = {
|
|
376
|
+
minLength: 10,
|
|
377
|
+
maxLength: 128,
|
|
378
|
+
requireLowercase: false,
|
|
379
|
+
requireUppercase: false,
|
|
380
|
+
requireNumber: false,
|
|
381
|
+
requireSpecial: false,
|
|
382
|
+
minCharacterClasses: 0,
|
|
383
|
+
specialChars: DEFAULT_SPECIAL_CHARS2,
|
|
384
|
+
disallowUserInfo: true,
|
|
385
|
+
disallowCommon: true,
|
|
386
|
+
breachCheck: "block",
|
|
387
|
+
maxRepeatedChars: null,
|
|
388
|
+
blockSequences: false,
|
|
389
|
+
history: 0,
|
|
390
|
+
expiryDays: null
|
|
391
|
+
};
|
|
392
|
+
var PASSWORD_POLICY_PRESETS = {
|
|
393
|
+
recommended: { ...BASE, preset: "recommended" },
|
|
394
|
+
strict: {
|
|
395
|
+
...BASE,
|
|
396
|
+
preset: "strict",
|
|
397
|
+
minLength: 12,
|
|
398
|
+
requireLowercase: true,
|
|
399
|
+
requireUppercase: true,
|
|
400
|
+
requireNumber: true,
|
|
401
|
+
requireSpecial: true,
|
|
402
|
+
maxRepeatedChars: 3,
|
|
403
|
+
blockSequences: true,
|
|
404
|
+
history: 5
|
|
405
|
+
},
|
|
406
|
+
legacy: {
|
|
407
|
+
...BASE,
|
|
408
|
+
preset: "legacy",
|
|
409
|
+
minLength: 8,
|
|
410
|
+
requireLowercase: true,
|
|
411
|
+
requireUppercase: true,
|
|
412
|
+
requireNumber: true,
|
|
413
|
+
requireSpecial: true,
|
|
414
|
+
breachCheck: "warn",
|
|
415
|
+
expiryDays: 90
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
// src/phone.ts
|
|
420
|
+
var MIN_PHONE_NUMBER_DIGITS = 8;
|
|
421
|
+
var MAX_PHONE_NUMBER_DIGITS = 15;
|
|
422
|
+
var E164 = new RegExp(`^\\+[1-9][0-9]{${MIN_PHONE_NUMBER_DIGITS - 1},${MAX_PHONE_NUMBER_DIGITS - 1}}$`);
|
|
423
|
+
var SEPARATORS = /[ ()-]/g;
|
|
424
|
+
function parsePhoneNumber(input) {
|
|
425
|
+
const number = input.replace(SEPARATORS, "");
|
|
426
|
+
return E164.test(number) ? number : null;
|
|
427
|
+
}
|
|
428
|
+
function maskPhoneNumber(phoneNumber) {
|
|
429
|
+
return `***${phoneNumber.slice(-2)}`;
|
|
430
|
+
}
|
|
431
|
+
var COUNTRY_CALLING_PREFIXES = {
|
|
432
|
+
AD: ["376"],
|
|
433
|
+
AE: ["971"],
|
|
434
|
+
AF: ["93"],
|
|
435
|
+
AG: ["1268"],
|
|
436
|
+
AI: ["1264"],
|
|
437
|
+
AL: ["355"],
|
|
438
|
+
AM: ["374"],
|
|
439
|
+
AO: ["244"],
|
|
440
|
+
AR: ["54"],
|
|
441
|
+
AS: ["1684"],
|
|
442
|
+
AT: ["43"],
|
|
443
|
+
AU: ["61"],
|
|
444
|
+
AW: ["297"],
|
|
445
|
+
AX: ["358"],
|
|
446
|
+
AZ: ["994"],
|
|
447
|
+
BA: ["387"],
|
|
448
|
+
BB: ["1246"],
|
|
449
|
+
BD: ["880"],
|
|
450
|
+
BE: ["32"],
|
|
451
|
+
BF: ["226"],
|
|
452
|
+
BG: ["359"],
|
|
453
|
+
BH: ["973"],
|
|
454
|
+
BI: ["257"],
|
|
455
|
+
BJ: ["229"],
|
|
456
|
+
BL: ["590"],
|
|
457
|
+
BM: ["1441"],
|
|
458
|
+
BN: ["673"],
|
|
459
|
+
BO: ["591"],
|
|
460
|
+
BQ: ["5993", "5994", "5997"],
|
|
461
|
+
BR: ["55"],
|
|
462
|
+
BS: ["1242"],
|
|
463
|
+
BT: ["975"],
|
|
464
|
+
BW: ["267"],
|
|
465
|
+
BY: ["375"],
|
|
466
|
+
BZ: ["501"],
|
|
467
|
+
CA: ["1"],
|
|
468
|
+
CC: ["61"],
|
|
469
|
+
CD: ["243"],
|
|
470
|
+
CF: ["236"],
|
|
471
|
+
CG: ["242"],
|
|
472
|
+
CH: ["41"],
|
|
473
|
+
CI: ["225"],
|
|
474
|
+
CK: ["682"],
|
|
475
|
+
CL: ["56"],
|
|
476
|
+
CM: ["237"],
|
|
477
|
+
CN: ["86"],
|
|
478
|
+
CO: ["57"],
|
|
479
|
+
CR: ["506"],
|
|
480
|
+
CU: ["53"],
|
|
481
|
+
CV: ["238"],
|
|
482
|
+
CW: ["5999"],
|
|
483
|
+
CX: ["61"],
|
|
484
|
+
CY: ["357"],
|
|
485
|
+
CZ: ["420"],
|
|
486
|
+
DE: ["49"],
|
|
487
|
+
DJ: ["253"],
|
|
488
|
+
DK: ["45"],
|
|
489
|
+
DM: ["1767"],
|
|
490
|
+
DO: ["1809", "1829", "1849"],
|
|
491
|
+
DZ: ["213"],
|
|
492
|
+
EC: ["593"],
|
|
493
|
+
EE: ["372"],
|
|
494
|
+
EG: ["20"],
|
|
495
|
+
EH: ["212"],
|
|
496
|
+
ER: ["291"],
|
|
497
|
+
ES: ["34"],
|
|
498
|
+
ET: ["251"],
|
|
499
|
+
FI: ["358"],
|
|
500
|
+
FJ: ["679"],
|
|
501
|
+
FK: ["500"],
|
|
502
|
+
FM: ["691"],
|
|
503
|
+
FO: ["298"],
|
|
504
|
+
FR: ["33"],
|
|
505
|
+
GA: ["241"],
|
|
506
|
+
GB: ["44"],
|
|
507
|
+
GD: ["1473"],
|
|
508
|
+
GE: ["995"],
|
|
509
|
+
GF: ["594"],
|
|
510
|
+
GG: ["44"],
|
|
511
|
+
GH: ["233"],
|
|
512
|
+
GI: ["350"],
|
|
513
|
+
GL: ["299"],
|
|
514
|
+
GM: ["220"],
|
|
515
|
+
GN: ["224"],
|
|
516
|
+
GP: ["590"],
|
|
517
|
+
GQ: ["240"],
|
|
518
|
+
GR: ["30"],
|
|
519
|
+
GT: ["502"],
|
|
520
|
+
GU: ["1671"],
|
|
521
|
+
GW: ["245"],
|
|
522
|
+
GY: ["592"],
|
|
523
|
+
HK: ["852"],
|
|
524
|
+
HN: ["504"],
|
|
525
|
+
HR: ["385"],
|
|
526
|
+
HT: ["509"],
|
|
527
|
+
HU: ["36"],
|
|
528
|
+
ID: ["62"],
|
|
529
|
+
IE: ["353"],
|
|
530
|
+
IL: ["972"],
|
|
531
|
+
IM: ["44"],
|
|
532
|
+
IN: ["91"],
|
|
533
|
+
IO: ["246"],
|
|
534
|
+
IQ: ["964"],
|
|
535
|
+
IR: ["98"],
|
|
536
|
+
IS: ["354"],
|
|
537
|
+
IT: ["39"],
|
|
538
|
+
JE: ["44"],
|
|
539
|
+
JM: ["1658", "1876"],
|
|
540
|
+
JO: ["962"],
|
|
541
|
+
JP: ["81"],
|
|
542
|
+
KE: ["254"],
|
|
543
|
+
KG: ["996"],
|
|
544
|
+
KH: ["855"],
|
|
545
|
+
KI: ["686"],
|
|
546
|
+
KM: ["269"],
|
|
547
|
+
KN: ["1869"],
|
|
548
|
+
KP: ["850"],
|
|
549
|
+
KR: ["82"],
|
|
550
|
+
KW: ["965"],
|
|
551
|
+
KY: ["1345"],
|
|
552
|
+
KZ: ["76", "77"],
|
|
553
|
+
LA: ["856"],
|
|
554
|
+
LB: ["961"],
|
|
555
|
+
LC: ["1758"],
|
|
556
|
+
LI: ["423"],
|
|
557
|
+
LK: ["94"],
|
|
558
|
+
LR: ["231"],
|
|
559
|
+
LS: ["266"],
|
|
560
|
+
LT: ["370"],
|
|
561
|
+
LU: ["352"],
|
|
562
|
+
LV: ["371"],
|
|
563
|
+
LY: ["218"],
|
|
564
|
+
MA: ["212"],
|
|
565
|
+
MC: ["377"],
|
|
566
|
+
MD: ["373"],
|
|
567
|
+
ME: ["382"],
|
|
568
|
+
MF: ["590"],
|
|
569
|
+
MG: ["261"],
|
|
570
|
+
MH: ["692"],
|
|
571
|
+
MK: ["389"],
|
|
572
|
+
ML: ["223"],
|
|
573
|
+
MM: ["95"],
|
|
574
|
+
MN: ["976"],
|
|
575
|
+
MO: ["853"],
|
|
576
|
+
MP: ["1670"],
|
|
577
|
+
MQ: ["596"],
|
|
578
|
+
MR: ["222"],
|
|
579
|
+
MS: ["1664"],
|
|
580
|
+
MT: ["356"],
|
|
581
|
+
MU: ["230"],
|
|
582
|
+
MV: ["960"],
|
|
583
|
+
MW: ["265"],
|
|
584
|
+
MX: ["52"],
|
|
585
|
+
MY: ["60"],
|
|
586
|
+
MZ: ["258"],
|
|
587
|
+
NA: ["264"],
|
|
588
|
+
NC: ["687"],
|
|
589
|
+
NE: ["227"],
|
|
590
|
+
NF: ["672"],
|
|
591
|
+
NG: ["234"],
|
|
592
|
+
NI: ["505"],
|
|
593
|
+
NL: ["31"],
|
|
594
|
+
NO: ["47"],
|
|
595
|
+
NP: ["977"],
|
|
596
|
+
NR: ["674"],
|
|
597
|
+
NU: ["683"],
|
|
598
|
+
NZ: ["64"],
|
|
599
|
+
OM: ["968"],
|
|
600
|
+
PA: ["507"],
|
|
601
|
+
PE: ["51"],
|
|
602
|
+
PF: ["689"],
|
|
603
|
+
PG: ["675"],
|
|
604
|
+
PH: ["63"],
|
|
605
|
+
PK: ["92"],
|
|
606
|
+
PL: ["48"],
|
|
607
|
+
PM: ["508"],
|
|
608
|
+
PR: ["1787", "1939"],
|
|
609
|
+
PS: ["970"],
|
|
610
|
+
PT: ["351"],
|
|
611
|
+
PW: ["680"],
|
|
612
|
+
PY: ["595"],
|
|
613
|
+
QA: ["974"],
|
|
614
|
+
RE: ["262"],
|
|
615
|
+
RO: ["40"],
|
|
616
|
+
RS: ["381"],
|
|
617
|
+
RU: ["7"],
|
|
618
|
+
RW: ["250"],
|
|
619
|
+
SA: ["966"],
|
|
620
|
+
SB: ["677"],
|
|
621
|
+
SC: ["248"],
|
|
622
|
+
SD: ["249"],
|
|
623
|
+
SE: ["46"],
|
|
624
|
+
SG: ["65"],
|
|
625
|
+
SH: ["290"],
|
|
626
|
+
SI: ["386"],
|
|
627
|
+
SJ: ["47"],
|
|
628
|
+
SK: ["421"],
|
|
629
|
+
SL: ["232"],
|
|
630
|
+
SM: ["378"],
|
|
631
|
+
SN: ["221"],
|
|
632
|
+
SO: ["252"],
|
|
633
|
+
SR: ["597"],
|
|
634
|
+
SS: ["211"],
|
|
635
|
+
ST: ["239"],
|
|
636
|
+
SV: ["503"],
|
|
637
|
+
SX: ["1721"],
|
|
638
|
+
SY: ["963"],
|
|
639
|
+
SZ: ["268"],
|
|
640
|
+
TC: ["1649"],
|
|
641
|
+
TD: ["235"],
|
|
642
|
+
TG: ["228"],
|
|
643
|
+
TH: ["66"],
|
|
644
|
+
TJ: ["992"],
|
|
645
|
+
TK: ["690"],
|
|
646
|
+
TL: ["670"],
|
|
647
|
+
TM: ["993"],
|
|
648
|
+
TN: ["216"],
|
|
649
|
+
TO: ["676"],
|
|
650
|
+
TR: ["90"],
|
|
651
|
+
TT: ["1868"],
|
|
652
|
+
TV: ["688"],
|
|
653
|
+
TW: ["886"],
|
|
654
|
+
TZ: ["255"],
|
|
655
|
+
UA: ["380"],
|
|
656
|
+
UG: ["256"],
|
|
657
|
+
US: ["1"],
|
|
658
|
+
UY: ["598"],
|
|
659
|
+
UZ: ["998"],
|
|
660
|
+
VA: ["39"],
|
|
661
|
+
VC: ["1784"],
|
|
662
|
+
VE: ["58"],
|
|
663
|
+
VG: ["1284"],
|
|
664
|
+
VI: ["1340"],
|
|
665
|
+
VN: ["84"],
|
|
666
|
+
VU: ["678"],
|
|
667
|
+
WF: ["681"],
|
|
668
|
+
WS: ["685"],
|
|
669
|
+
XK: ["383"],
|
|
670
|
+
YE: ["967"],
|
|
671
|
+
YT: ["262"],
|
|
672
|
+
ZA: ["27"],
|
|
673
|
+
ZM: ["260"],
|
|
674
|
+
ZW: ["263"]
|
|
675
|
+
};
|
|
676
|
+
var SMS_COUNTRIES = Object.keys(COUNTRY_CALLING_PREFIXES).sort();
|
|
677
|
+
var COUNTRIES_BY_PREFIX = (() => {
|
|
678
|
+
const byPrefix = new Map;
|
|
679
|
+
for (const country of SMS_COUNTRIES) {
|
|
680
|
+
for (const prefix of COUNTRY_CALLING_PREFIXES[country] ?? []) {
|
|
681
|
+
byPrefix.set(prefix, [...byPrefix.get(prefix) ?? [], country]);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return byPrefix;
|
|
685
|
+
})();
|
|
686
|
+
var LONGEST_PREFIX = Math.max(...[...COUNTRIES_BY_PREFIX.keys()].map((prefix) => prefix.length));
|
|
687
|
+
function isSmsCountry(value) {
|
|
688
|
+
return Object.hasOwn(COUNTRY_CALLING_PREFIXES, value);
|
|
689
|
+
}
|
|
690
|
+
function phoneNumberCountries(phoneNumber) {
|
|
691
|
+
const prefix = longestPrefix(phoneNumber);
|
|
692
|
+
return prefix === null ? [] : COUNTRIES_BY_PREFIX.get(prefix) ?? [];
|
|
693
|
+
}
|
|
694
|
+
function longestPrefix(phoneNumber) {
|
|
695
|
+
const digits = phoneNumber.replace(/^\+/, "");
|
|
696
|
+
for (let length = LONGEST_PREFIX;length > 0; length -= 1) {
|
|
697
|
+
const prefix = digits.slice(0, length);
|
|
698
|
+
if (COUNTRIES_BY_PREFIX.has(prefix)) {
|
|
699
|
+
return prefix;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return null;
|
|
703
|
+
}
|
|
704
|
+
function isPhoneNumberAllowed(phoneNumber, allowedCountries) {
|
|
705
|
+
return phoneNumberCountries(phoneNumber).some((country) => allowedCountries.includes(country));
|
|
706
|
+
}
|
|
707
|
+
var DEFAULT_SMS_DAILY_MESSAGE_LIMIT = 500;
|
|
708
|
+
var MAX_SMS_DAILY_MESSAGE_LIMIT = 1e6;
|
|
709
|
+
var SMS_PREFIX_MAX_DIGITS = 4;
|
|
710
|
+
function smsPrefixCountries(prefix) {
|
|
711
|
+
return COUNTRIES_BY_PREFIX.get(prefix.replace(/^\+/, "")) ?? [];
|
|
712
|
+
}
|
|
713
|
+
var SMS_PREFIX_HOURLY_SHARE = 10;
|
|
714
|
+
var SMS_ENVIRONMENT_HOURLY_SHARE = 4;
|
|
715
|
+
function smsCostLimits(dailyMessageLimit) {
|
|
716
|
+
const perDay = Number.isInteger(dailyMessageLimit) && dailyMessageLimit >= 1 ? dailyMessageLimit : 1;
|
|
717
|
+
return {
|
|
718
|
+
prefixPerHour: Math.ceil(perDay / SMS_PREFIX_HOURLY_SHARE),
|
|
719
|
+
environmentPerHour: Math.ceil(perDay / SMS_ENVIRONMENT_HOURLY_SHARE),
|
|
720
|
+
perDay
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
function phoneNumberPrefix(phoneNumber) {
|
|
724
|
+
const prefix = longestPrefix(phoneNumber);
|
|
725
|
+
return prefix === null ? null : `+${prefix}`;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// src/session-profile.ts
|
|
729
|
+
import { z as z9 } from "zod";
|
|
730
|
+
|
|
731
|
+
// src/jwt-template.ts
|
|
732
|
+
import { z as z8 } from "zod";
|
|
733
|
+
|
|
734
|
+
// src/session.ts
|
|
735
|
+
import { z as z7 } from "zod";
|
|
736
|
+
var REFRESH_TOKEN_PREFIX = "tula_rt_";
|
|
737
|
+
var SessionClientSchema = z7.enum(["web", "ios", "android", "server"]).meta({ ref: "SessionClient" });
|
|
738
|
+
var SessionSchema = z7.object({
|
|
739
|
+
id: z7.string(),
|
|
740
|
+
client: SessionClientSchema,
|
|
741
|
+
userAgent: z7.string().nullable(),
|
|
742
|
+
ipAddress: z7.string().nullable(),
|
|
743
|
+
createdAt: z7.iso.datetime(),
|
|
744
|
+
lastActiveAt: z7.iso.datetime(),
|
|
745
|
+
expiresAt: z7.iso.datetime(),
|
|
746
|
+
current: z7.boolean(),
|
|
747
|
+
deviceBound: z7.boolean()
|
|
748
|
+
}).meta({ ref: "Session" });
|
|
749
|
+
var AdminSessionSchema = SessionSchema.extend({
|
|
750
|
+
userId: z7.string(),
|
|
751
|
+
active: z7.boolean()
|
|
752
|
+
}).meta({ ref: "AdminSession" });
|
|
753
|
+
var SessionListSchema = z7.object({ data: z7.array(SessionSchema) }).meta({ ref: "SessionList" });
|
|
754
|
+
var RefreshTokenRequestSchema = z7.object({ refreshToken: z7.string().max(512).optional() }).meta({ ref: "RefreshTokenRequest" });
|
|
755
|
+
var MAX_VERIFIED_TOKEN_LENGTH = 4096;
|
|
756
|
+
var VerifySessionRequestSchema = z7.object({ token: z7.string().min(1).max(MAX_VERIFIED_TOKEN_LENGTH) }).meta({ ref: "VerifySessionRequest" });
|
|
757
|
+
var RevokedSessionsSchema = z7.object({ revoked: z7.number().int().min(0) }).meta({ ref: "RevokedSessions" });
|
|
758
|
+
|
|
759
|
+
// src/jwt-template.ts
|
|
760
|
+
var JWT_TEMPLATE_SOURCES = [
|
|
761
|
+
"user.email",
|
|
762
|
+
"user.email_verified",
|
|
763
|
+
"user.created_at",
|
|
764
|
+
"session.client",
|
|
765
|
+
"session.created_at"
|
|
766
|
+
];
|
|
767
|
+
var JwtTemplateSourceSchema = z8.enum(JWT_TEMPLATE_SOURCES).meta({ ref: "JwtTemplateSource" });
|
|
768
|
+
var MAX_EMAIL_LENGTH = 320;
|
|
769
|
+
var MAX_EMAIL_CLAIM_BYTES = 2 * MAX_EMAIL_LENGTH + 2;
|
|
770
|
+
var MAX_BOOLEAN_BYTES = 5;
|
|
771
|
+
var MAX_TIME_BYTES = String(Number.MAX_SAFE_INTEGER).length;
|
|
772
|
+
var MAX_CLIENT_BYTES = Math.max(...SessionClientSchema.options.map((kind) => kind.length)) + 2;
|
|
773
|
+
var SOURCE_MAX_BYTES = {
|
|
774
|
+
"user.email": MAX_EMAIL_CLAIM_BYTES,
|
|
775
|
+
"user.email_verified": MAX_BOOLEAN_BYTES,
|
|
776
|
+
"user.created_at": MAX_TIME_BYTES,
|
|
777
|
+
"session.client": MAX_CLIENT_BYTES,
|
|
778
|
+
"session.created_at": MAX_TIME_BYTES
|
|
779
|
+
};
|
|
780
|
+
var UNPRINTABLE = /[\p{Cc}\p{Zl}\p{Zp}]/u;
|
|
781
|
+
var Constant = z8.union([
|
|
782
|
+
z8.string().max(MAX_CUSTOM_CLAIM_CONSTANT_LENGTH2).refine((value) => !UNPRINTABLE.test(value), {
|
|
783
|
+
message: "must not contain control characters or line breaks"
|
|
784
|
+
}),
|
|
785
|
+
z8.number(),
|
|
786
|
+
z8.boolean()
|
|
787
|
+
]);
|
|
788
|
+
var JwtTemplateClaimSchema = z8.union([z8.strictObject({ from: JwtTemplateSourceSchema }), z8.strictObject({ value: Constant })]).meta({ ref: "JwtTemplateClaim" });
|
|
789
|
+
var CLAIM_KEY_RULE = `a claim key is ASCII letters, digits and underscores, not starting with a digit, at most ${MAX_CUSTOM_CLAIM_KEY_LENGTH2} characters, and not a reserved claim name`;
|
|
790
|
+
function keyBytes(key) {
|
|
791
|
+
return customClaimsBytes2({ [key]: 0 }) - 3;
|
|
792
|
+
}
|
|
793
|
+
function valueMaxBytes(claim) {
|
|
794
|
+
if ("from" in claim) {
|
|
795
|
+
return SOURCE_MAX_BYTES[claim.from];
|
|
796
|
+
}
|
|
797
|
+
const constant = claim.value;
|
|
798
|
+
return customClaimsBytes2({ k: constant }) - '{"k":}'.length;
|
|
799
|
+
}
|
|
800
|
+
function jwtTemplateMaxBytes(template) {
|
|
801
|
+
const claims = Object.entries(template.claims);
|
|
802
|
+
const separators = Math.max(0, claims.length - 1);
|
|
803
|
+
return claims.reduce((bytes, [key, claim]) => bytes + keyBytes(key) + valueMaxBytes(claim), 2 + separators);
|
|
804
|
+
}
|
|
805
|
+
var fields = {
|
|
806
|
+
claims: z8.record(z8.string().refine(isCustomClaimKey2, { message: CLAIM_KEY_RULE }), JwtTemplateClaimSchema).default({})
|
|
807
|
+
};
|
|
808
|
+
function fewEnough(template) {
|
|
809
|
+
return Object.keys(template.claims).length <= MAX_JWT_TEMPLATE_CLAIMS2;
|
|
810
|
+
}
|
|
811
|
+
function smallEnough(template) {
|
|
812
|
+
return jwtTemplateMaxBytes(template) <= MAX_CUSTOM_CLAIMS_BYTES2;
|
|
813
|
+
}
|
|
814
|
+
var JwtTemplateSchema = z8.strictObject(fields).refine(fewEnough, {
|
|
815
|
+
message: `at most ${MAX_JWT_TEMPLATE_CLAIMS2} claims`,
|
|
816
|
+
path: ["claims"]
|
|
817
|
+
}).refine(smallEnough, {
|
|
818
|
+
message: `the claims could take more than ${MAX_CUSTOM_CLAIMS_BYTES2} bytes`,
|
|
819
|
+
path: ["claims"]
|
|
820
|
+
}).meta({ ref: "JwtTemplate" });
|
|
821
|
+
function readStoredJwtTemplate(stored) {
|
|
822
|
+
if (typeof stored !== "object" || stored === null || Array.isArray(stored)) {
|
|
823
|
+
return null;
|
|
824
|
+
}
|
|
825
|
+
const { claims: raw } = stored;
|
|
826
|
+
const claims = {};
|
|
827
|
+
if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
|
|
828
|
+
for (const key of Object.keys(raw)) {
|
|
829
|
+
const claim = JwtTemplateClaimSchema.safeParse(raw[key]);
|
|
830
|
+
if (isCustomClaimKey2(key) && claim.success) {
|
|
831
|
+
claims[key] = claim.data;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
const template = { claims };
|
|
836
|
+
return fewEnough(template) && smallEnough(template) ? template : null;
|
|
837
|
+
}
|
|
838
|
+
function jwtTemplateOfProfile(settings, profile) {
|
|
839
|
+
const name = profile.jwtTemplate;
|
|
840
|
+
if (name === null || !Object.hasOwn(settings.jwtTemplates, name)) {
|
|
841
|
+
return null;
|
|
842
|
+
}
|
|
843
|
+
const template = settings.jwtTemplates[name];
|
|
844
|
+
return template ? { name, template } : null;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// src/session-profile.ts
|
|
848
|
+
var SessionTypeSchema = z9.enum(["hybrid", "stateful"]).meta({ ref: "SessionType" });
|
|
849
|
+
var MIN_ACCESS_TOKEN_TTL = "30s";
|
|
850
|
+
var MAX_ACCESS_TOKEN_TTL = "15m";
|
|
851
|
+
var MIN_IDLE_TIMEOUT = "1m";
|
|
852
|
+
var MAX_SESSION_TIMEOUT = "365d";
|
|
853
|
+
var MIN_REUSE_GRACE_PERIOD = "10s";
|
|
854
|
+
var MAX_REUSE_GRACE_PERIOD = "60s";
|
|
855
|
+
var MIN_STEP_UP_AFTER = "1m";
|
|
856
|
+
var MAX_STEP_UP_AFTER = "24h";
|
|
857
|
+
var DEFAULT_STEP_UP_AFTER = "10m";
|
|
858
|
+
var MAX_CUSTOM_SESSION_PROFILES = 10;
|
|
859
|
+
var MAX_SESSION_PROFILE_NAME_LENGTH = 32;
|
|
860
|
+
var MAX_SESSIONS_PER_USER = 100;
|
|
861
|
+
var DeviceBindingPolicySchema = z9.enum(["none", "optional", "required"]).meta({ ref: "DeviceBindingPolicy" });
|
|
862
|
+
var BUILT_IN_SESSION_PROFILES = ["web", "mobile"];
|
|
863
|
+
function defaultDeviceBinding(name) {
|
|
864
|
+
return name === "web" ? "none" : "optional";
|
|
865
|
+
}
|
|
866
|
+
var PROFILE_NAME = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
867
|
+
function isSessionProfileName(name) {
|
|
868
|
+
return typeof name === "string" && name.length <= MAX_SESSION_PROFILE_NAME_LENGTH && PROFILE_NAME.test(name);
|
|
869
|
+
}
|
|
870
|
+
var SessionProfileNameSchema = z9.string().max(MAX_SESSION_PROFILE_NAME_LENGTH).regex(PROFILE_NAME).meta({ ref: "SessionProfileName" });
|
|
871
|
+
function ms(value) {
|
|
872
|
+
return DurationSchema.safeParse(value).success ? durationToMs(value) : Number.NaN;
|
|
873
|
+
}
|
|
874
|
+
function within(value, min, max) {
|
|
875
|
+
return ms(value) >= durationToMs(min) && ms(value) <= durationToMs(max);
|
|
876
|
+
}
|
|
877
|
+
function bounded(min, max) {
|
|
878
|
+
return DurationSchema.refine((value) => within(value, min, max), {
|
|
879
|
+
message: `must be between ${min} and ${max}`
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
var profileFields = {
|
|
883
|
+
type: SessionTypeSchema.default("hybrid"),
|
|
884
|
+
accessTokenTtl: bounded(MIN_ACCESS_TOKEN_TTL, MAX_ACCESS_TOKEN_TTL).default("60s"),
|
|
885
|
+
idleTimeout: bounded(MIN_IDLE_TIMEOUT, MAX_SESSION_TIMEOUT).default("7d"),
|
|
886
|
+
absoluteTimeout: bounded(MIN_IDLE_TIMEOUT, MAX_SESSION_TIMEOUT).nullable().default("30d"),
|
|
887
|
+
stepUpAfter: bounded(MIN_STEP_UP_AFTER, MAX_STEP_UP_AFTER).nullable().default(null),
|
|
888
|
+
clientSelectable: z9.boolean().default(false),
|
|
889
|
+
jwtTemplate: SessionProfileNameSchema.nullable().default(null),
|
|
890
|
+
deviceBinding: DeviceBindingPolicySchema.default("optional")
|
|
891
|
+
};
|
|
892
|
+
var webProfileFields = {
|
|
893
|
+
...profileFields,
|
|
894
|
+
deviceBinding: DeviceBindingPolicySchema.default("none")
|
|
895
|
+
};
|
|
896
|
+
var refreshFields = {
|
|
897
|
+
reuseGracePeriod: bounded(MIN_REUSE_GRACE_PERIOD, MAX_REUSE_GRACE_PERIOD).nullable().default("10s")
|
|
898
|
+
};
|
|
899
|
+
var idleWithinAbsolute = {
|
|
900
|
+
message: "must not be shorter than idleTimeout",
|
|
901
|
+
path: ["absoluteTimeout"]
|
|
902
|
+
};
|
|
903
|
+
function idleFits(profile) {
|
|
904
|
+
return profile.absoluteTimeout === null || !(ms(profile.idleTimeout) > ms(profile.absoluteTimeout));
|
|
905
|
+
}
|
|
906
|
+
var accessWithinIdle = {
|
|
907
|
+
message: "must not be longer than idleTimeout",
|
|
908
|
+
path: ["accessTokenTtl"]
|
|
909
|
+
};
|
|
910
|
+
function accessFits(profile) {
|
|
911
|
+
return !(ms(profile.accessTokenTtl) > ms(profile.idleTimeout));
|
|
912
|
+
}
|
|
913
|
+
var SessionProfileSchema = z9.strictObject({ ...profileFields, refresh: z9.strictObject(refreshFields).prefault({}) }).refine(idleFits, idleWithinAbsolute).refine(accessFits, accessWithinIdle).meta({ ref: "SessionProfile" });
|
|
914
|
+
var WebSessionProfileSchema = z9.strictObject({ ...webProfileFields, refresh: z9.strictObject(refreshFields).prefault({}) }).refine(idleFits, idleWithinAbsolute).refine(accessFits, accessWithinIdle).meta({ ref: "WebSessionProfile" });
|
|
915
|
+
var StoredProfile = z9.object({ ...profileFields, refresh: z9.object(refreshFields).prefault({}) }).refine(idleFits, idleWithinAbsolute);
|
|
916
|
+
var StoredWebProfile = z9.object({ ...webProfileFields, refresh: z9.object(refreshFields).prefault({}) }).refine(idleFits, idleWithinAbsolute);
|
|
917
|
+
var DEFAULT_WEB_SESSION_PROFILE = WebSessionProfileSchema.parse({});
|
|
918
|
+
var DEFAULT_MOBILE_SESSION_PROFILE = SessionProfileSchema.parse({});
|
|
919
|
+
var SessionLimitActionSchema = z9.enum(["end_oldest", "refuse_newest"]).meta({ ref: "SessionLimitAction" });
|
|
920
|
+
function isBuiltIn(name) {
|
|
921
|
+
return BUILT_IN_SESSION_PROFILES.includes(name);
|
|
922
|
+
}
|
|
923
|
+
function customNames(profiles) {
|
|
924
|
+
return Object.keys(profiles).filter((name) => !isBuiltIn(name));
|
|
925
|
+
}
|
|
926
|
+
var PROFILE_SCHEMAS = {
|
|
927
|
+
strict: { web: WebSessionProfileSchema, other: SessionProfileSchema },
|
|
928
|
+
stored: { web: StoredWebProfile, other: StoredProfile }
|
|
929
|
+
};
|
|
930
|
+
function profilesOf(kind) {
|
|
931
|
+
const { web, other } = PROFILE_SCHEMAS[kind];
|
|
932
|
+
return z9.object({ web: web.prefault({}), mobile: other.prefault({}) }).catchall(other).refine((profiles) => customNames(profiles).every(isSessionProfileName), {
|
|
933
|
+
message: `a profile name is lowercase letters, digits and single hyphens, starting with a letter, at most ${MAX_SESSION_PROFILE_NAME_LENGTH} characters`
|
|
934
|
+
}).refine((profiles) => customNames(profiles).length <= MAX_CUSTOM_SESSION_PROFILES, {
|
|
935
|
+
message: `at most ${MAX_CUSTOM_SESSION_PROFILES} profiles besides web and mobile`
|
|
936
|
+
}).refine((profiles) => profiles.mobile.type === "hybrid", {
|
|
937
|
+
message: "the mobile profile must be hybrid: a stateful session is a browser cookie",
|
|
938
|
+
path: ["mobile", "type"]
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
var TEMPLATE_NAME_RULE = `a template name is lowercase letters, digits and single hyphens, starting with a letter, at most ${MAX_SESSION_PROFILE_NAME_LENGTH} characters`;
|
|
942
|
+
var JwtTemplates = z9.record(z9.string().refine(isSessionProfileName, { message: TEMPLATE_NAME_RULE }), JwtTemplateSchema).refine((templates) => Object.keys(templates).length <= MAX_JWT_TEMPLATES2, {
|
|
943
|
+
message: `at most ${MAX_JWT_TEMPLATES2} templates`
|
|
944
|
+
}).default({});
|
|
945
|
+
var StoredJwtTemplates = z9.unknown().optional().transform((stored) => {
|
|
946
|
+
const templates = {};
|
|
947
|
+
if (typeof stored !== "object" || stored === null || Array.isArray(stored)) {
|
|
948
|
+
return templates;
|
|
949
|
+
}
|
|
950
|
+
for (const name of Object.keys(stored)) {
|
|
951
|
+
const template = readStoredJwtTemplate(stored[name]);
|
|
952
|
+
if (template && isSessionProfileName(name) && Object.keys(templates).length < MAX_JWT_TEMPLATES2) {
|
|
953
|
+
templates[name] = template;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
return templates;
|
|
957
|
+
});
|
|
958
|
+
function templatesExist(settings, context) {
|
|
959
|
+
for (const [name, profile] of Object.entries(settings.profiles)) {
|
|
960
|
+
const { jwtTemplate } = profile;
|
|
961
|
+
if (jwtTemplate !== null && !jwtTemplateOfProfile(settings, { jwtTemplate })) {
|
|
962
|
+
context.addIssue({
|
|
963
|
+
code: "custom",
|
|
964
|
+
message: "names a JWT template that does not exist",
|
|
965
|
+
path: ["profiles", name, "jwtTemplate"]
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
var limitFields = {
|
|
971
|
+
maxPerUser: z9.number().int().min(1).max(MAX_SESSIONS_PER_USER).nullable().default(null),
|
|
972
|
+
onLimit: SessionLimitActionSchema.default("end_oldest")
|
|
973
|
+
};
|
|
974
|
+
var SessionSettingsSchema = z9.strictObject({
|
|
975
|
+
profiles: profilesOf("strict").prefault({}),
|
|
976
|
+
...limitFields,
|
|
977
|
+
jwtTemplates: JwtTemplates
|
|
978
|
+
}).superRefine(templatesExist).meta({ ref: "SessionSettings" });
|
|
979
|
+
var StoredSessionSettingsSchema = z9.object({
|
|
980
|
+
profiles: profilesOf("stored").prefault({}),
|
|
981
|
+
...limitFields,
|
|
982
|
+
jwtTemplates: StoredJwtTemplates
|
|
983
|
+
});
|
|
984
|
+
function builtInSessionProfile(client) {
|
|
985
|
+
return client === "web" ? "web" : "mobile";
|
|
986
|
+
}
|
|
987
|
+
function named(settings, name) {
|
|
988
|
+
const profile = Object.hasOwn(settings.profiles, name) ? settings.profiles[name] : undefined;
|
|
989
|
+
return profile ? { name, profile } : null;
|
|
990
|
+
}
|
|
991
|
+
function builtIn(settings, client) {
|
|
992
|
+
const name = builtInSessionProfile(client);
|
|
993
|
+
return { name, profile: settings.profiles[name] };
|
|
994
|
+
}
|
|
995
|
+
function resolveSessionProfile(settings, request) {
|
|
996
|
+
const fallback = builtIn(settings, request.client);
|
|
997
|
+
if (!request.requested || isBuiltIn(request.requested)) {
|
|
998
|
+
return fallback;
|
|
999
|
+
}
|
|
1000
|
+
const asked = named(settings, request.requested);
|
|
1001
|
+
if (!asked?.profile.clientSelectable) {
|
|
1002
|
+
return fallback;
|
|
1003
|
+
}
|
|
1004
|
+
return asked.profile.type === "stateful" && request.client !== "web" ? fallback : asked;
|
|
1005
|
+
}
|
|
1006
|
+
function profileOfSession(settings, session) {
|
|
1007
|
+
if (isBuiltIn(session.profile)) {
|
|
1008
|
+
return builtIn(settings, session.client);
|
|
1009
|
+
}
|
|
1010
|
+
return named(settings, session.profile) ?? builtIn(settings, session.client);
|
|
1011
|
+
}
|
|
1012
|
+
function stepUpWindowSeconds(settings, profileName) {
|
|
1013
|
+
const profile = profileName === undefined ? null : named(settings, profileName)?.profile;
|
|
1014
|
+
return durationToMs(profile?.stepUpAfter ?? DEFAULT_STEP_UP_AFTER) / 1000;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// src/sms-template.ts
|
|
1018
|
+
var SMS_TEMPLATE_KINDS = ["phone_verification", "sign_in", "second_factor"];
|
|
1019
|
+
var SMS_TEMPLATE_PLACEHOLDERS = ["appName", "code"];
|
|
1020
|
+
var CODE = { required: ["code"], optional: ["appName"] };
|
|
1021
|
+
var SMS_TEMPLATE_RULES = {
|
|
1022
|
+
phone_verification: CODE,
|
|
1023
|
+
sign_in: CODE,
|
|
1024
|
+
second_factor: CODE
|
|
1025
|
+
};
|
|
1026
|
+
var MAX_SMS_TEMPLATE_LENGTH = 140;
|
|
1027
|
+
var UNPRINTABLE2 = /[\p{Cc}\p{Zl}\p{Zp}]/u;
|
|
1028
|
+
var DIGIT_RUN = /\p{Nd}{4}/u;
|
|
1029
|
+
var CODE_LINE_MARK = /(?:^|[^\p{L}\p{N}])[@#]/u;
|
|
1030
|
+
var WORD_CHARACTER = /[\p{L}\p{N}\p{M}]/u;
|
|
1031
|
+
var LETTER = /^\p{L}/u;
|
|
1032
|
+
var MAX_REPORTED_NAME_LENGTH = 40;
|
|
1033
|
+
function smsTemplateProblems(kind, template) {
|
|
1034
|
+
const { text } = template;
|
|
1035
|
+
const rules = SMS_TEMPLATE_RULES[kind];
|
|
1036
|
+
const problem = (code, message, placeholder) => ({
|
|
1037
|
+
field: "text",
|
|
1038
|
+
code,
|
|
1039
|
+
message,
|
|
1040
|
+
...placeholder !== undefined && { placeholder }
|
|
1041
|
+
});
|
|
1042
|
+
if (text.length > MAX_SMS_TEMPLATE_LENGTH) {
|
|
1043
|
+
return [problem("too_long", `must be at most ${MAX_SMS_TEMPLATE_LENGTH} characters`)];
|
|
1044
|
+
}
|
|
1045
|
+
const seen = withoutInvisibleCharacters2(text);
|
|
1046
|
+
if (seen.trim() === "") {
|
|
1047
|
+
return [problem("empty", "must not be empty")];
|
|
1048
|
+
}
|
|
1049
|
+
if (UNPRINTABLE2.test(text)) {
|
|
1050
|
+
return [problem("control_character", "must be one line, with no control characters")];
|
|
1051
|
+
}
|
|
1052
|
+
if (hasHiddenCharacter2(text)) {
|
|
1053
|
+
return [
|
|
1054
|
+
problem("hidden_character", "must not contain text-direction controls, private-use or unassigned characters, or half a surrogate pair")
|
|
1055
|
+
];
|
|
1056
|
+
}
|
|
1057
|
+
const tokens = parseEmailTemplate2(seen);
|
|
1058
|
+
if (parseEmailTemplate2(text) === null || tokens === null) {
|
|
1059
|
+
return [
|
|
1060
|
+
problem("malformed_braces", "a brace must be part of a placeholder written as {{name}}, with no space inside")
|
|
1061
|
+
];
|
|
1062
|
+
}
|
|
1063
|
+
const problems = [];
|
|
1064
|
+
const allowed = new Set([...rules.required, ...rules.optional]);
|
|
1065
|
+
const counts = new Map;
|
|
1066
|
+
for (const token of tokens) {
|
|
1067
|
+
if ("placeholder" in token) {
|
|
1068
|
+
counts.set(token.placeholder, (counts.get(token.placeholder) ?? 0) + 1);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
for (const [written, count] of counts) {
|
|
1072
|
+
if (!allowed.has(written)) {
|
|
1073
|
+
const name = written.length > MAX_REPORTED_NAME_LENGTH ? `${written.slice(0, MAX_REPORTED_NAME_LENGTH)}…` : written;
|
|
1074
|
+
problems.push(problem("unknown_placeholder", `{{${name}}} is not a placeholder of this message`, name));
|
|
1075
|
+
} else if (count > 1) {
|
|
1076
|
+
problems.push(problem("repeated_placeholder", `{{${written}}} must be written only once`, written));
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
for (const name of rules.required) {
|
|
1080
|
+
if (!counts.has(name)) {
|
|
1081
|
+
problems.push(problem("missing_placeholder", `the text must contain {{${name}}}`, name));
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
const touches = tokens.some((token, index) => {
|
|
1085
|
+
if (!("placeholder" in token)) {
|
|
1086
|
+
return false;
|
|
1087
|
+
}
|
|
1088
|
+
const [before, after] = [tokens[index - 1], tokens[index + 1]];
|
|
1089
|
+
return before !== undefined && (!("text" in before) || WORD_CHARACTER.test(lastOf(before.text))) || after !== undefined && (!("text" in after) || WORD_CHARACTER.test(firstOf(after.text)));
|
|
1090
|
+
});
|
|
1091
|
+
if (touches) {
|
|
1092
|
+
problems.push(problem("placeholder_touches_text", "a placeholder must stand alone: no letter, digit, combining mark or other placeholder directly before or after it"));
|
|
1093
|
+
}
|
|
1094
|
+
const standIns = tokens.map((token) => ("text" in token) ? token.text : "x").join("");
|
|
1095
|
+
if (DIGIT_RUN.test(standIns)) {
|
|
1096
|
+
problems.push(problem("digit_run", "must not contain four or more digits in a row: only the code may"));
|
|
1097
|
+
}
|
|
1098
|
+
if (CODE_LINE_MARK.test(standIns.normalize("NFKC"))) {
|
|
1099
|
+
problems.push(problem("imitates_code_line", "a word must not start with @ or #: that is how the line the server adds is recognised"));
|
|
1100
|
+
}
|
|
1101
|
+
if (readsAsLink2(standIns)) {
|
|
1102
|
+
problems.push(problem("reads_as_link", "must not contain a link, an address or a domain name (put a space after a full stop)"));
|
|
1103
|
+
}
|
|
1104
|
+
const [first] = tokens;
|
|
1105
|
+
if (first === undefined || !("text" in first) || !LETTER.test(first.text)) {
|
|
1106
|
+
problems.push(problem("leading_non_letter", "must start with a letter of its own, not with a placeholder"));
|
|
1107
|
+
}
|
|
1108
|
+
return problems;
|
|
1109
|
+
}
|
|
1110
|
+
function firstOf(text) {
|
|
1111
|
+
return String.fromCodePoint(text.codePointAt(0) ?? 32);
|
|
1112
|
+
}
|
|
1113
|
+
function lastOf(text) {
|
|
1114
|
+
return [...text.slice(-2)].at(-1) ?? " ";
|
|
1115
|
+
}
|
|
1116
|
+
function isSmsTemplateKind(value) {
|
|
1117
|
+
return SMS_TEMPLATE_KINDS.includes(value);
|
|
1118
|
+
}
|
|
1119
|
+
function readStoredSmsTemplates(stored) {
|
|
1120
|
+
const templates = {};
|
|
1121
|
+
const dropped = [];
|
|
1122
|
+
if (typeof stored !== "object" || stored === null || Array.isArray(stored)) {
|
|
1123
|
+
return { templates, dropped, unknown: stored === undefined || stored === null ? 0 : 1 };
|
|
1124
|
+
}
|
|
1125
|
+
const unknown = Object.keys(stored).filter((key) => !isSmsTemplateKind(key)).length;
|
|
1126
|
+
for (const kind of SMS_TEMPLATE_KINDS) {
|
|
1127
|
+
if (!Object.hasOwn(stored, kind)) {
|
|
1128
|
+
continue;
|
|
1129
|
+
}
|
|
1130
|
+
const written = stored[kind];
|
|
1131
|
+
const text = typeof written === "object" && written !== null && !Array.isArray(written) ? written.text : undefined;
|
|
1132
|
+
if (typeof text === "string" && smsTemplateProblems(kind, { text }).length === 0) {
|
|
1133
|
+
templates[kind] = { text };
|
|
1134
|
+
} else {
|
|
1135
|
+
dropped.push(kind);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
return { templates, dropped, unknown };
|
|
1139
|
+
}
|
|
1140
|
+
var GSM7_BASIC = `@£$¥èéùìòÇ
|
|
1141
|
+
Øø\rÅåΔ_ΦΓΛΩΠΨΣΘΞ ÆæßÉ!"#¤%&'()*+,-./0123456789:;<=>?` + "¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ¿abcdefghijklmnopqrstuvwxyzäöñüà";
|
|
1142
|
+
var GSM7_EXTENDED = "\f^{}\\[~]|€";
|
|
1143
|
+
function smsSegments(text) {
|
|
1144
|
+
let septets = 0;
|
|
1145
|
+
let gsm = true;
|
|
1146
|
+
for (const character of text) {
|
|
1147
|
+
if (GSM7_BASIC.includes(character)) {
|
|
1148
|
+
septets += 1;
|
|
1149
|
+
} else if (GSM7_EXTENDED.includes(character)) {
|
|
1150
|
+
septets += 2;
|
|
1151
|
+
} else {
|
|
1152
|
+
gsm = false;
|
|
1153
|
+
break;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
if (gsm) {
|
|
1157
|
+
return {
|
|
1158
|
+
encoding: "gsm7",
|
|
1159
|
+
units: septets,
|
|
1160
|
+
segments: septets <= 160 ? 1 : Math.ceil(septets / 153)
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
const units = text.length;
|
|
1164
|
+
return { encoding: "ucs2", units, segments: units <= 70 ? 1 : Math.ceil(units / 67) };
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
// src/sms-template-schema.ts
|
|
1168
|
+
import { z as z10 } from "zod";
|
|
1169
|
+
var SmsTemplateSchema = z10.strictObject({ text: z10.string().max(MAX_SMS_TEMPLATE_LENGTH) }).meta({ ref: "SmsTemplate" });
|
|
1170
|
+
var shape2 = Object.fromEntries(SMS_TEMPLATE_KINDS.map((kind) => [kind, SmsTemplateSchema.optional()]));
|
|
1171
|
+
var SmsTemplatesSchema = z10.strictObject(shape2).superRefine((templates, context) => {
|
|
1172
|
+
for (const kind of SMS_TEMPLATE_KINDS) {
|
|
1173
|
+
const template = templates[kind];
|
|
1174
|
+
if (template === undefined) {
|
|
1175
|
+
continue;
|
|
1176
|
+
}
|
|
1177
|
+
for (const problem of smsTemplateProblems(kind, template)) {
|
|
1178
|
+
context.addIssue({
|
|
1179
|
+
code: "custom",
|
|
1180
|
+
path: [kind, problem.field],
|
|
1181
|
+
message: problem.message
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
}).meta({ ref: "SmsTemplates" });
|
|
1186
|
+
|
|
1187
|
+
// src/environment-settings.ts
|
|
1188
|
+
var DEFAULT_APP_NAME = "Tula";
|
|
1189
|
+
var MAX_APP_NAME_LENGTH = 64;
|
|
1190
|
+
var MAX_ALLOWED_ORIGINS = 50;
|
|
1191
|
+
var MAX_ALLOWED_REDIRECT_URLS = 100;
|
|
1192
|
+
var MIN_PASSWORD_MIN_LENGTH = 8;
|
|
1193
|
+
var MAX_AUDIT_RETENTION_DAYS = 3650;
|
|
1194
|
+
var UNPRINTABLE3 = /[\p{Cc}\p{Zl}\p{Zp}]/u;
|
|
1195
|
+
var LABEL = "[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?";
|
|
1196
|
+
var HOST = `(?:${LABEL}(?:\\.${LABEL})*|\\[[0-9a-f:.]{2,45}\\])`;
|
|
1197
|
+
var PORT = "(?::([1-9][0-9]{0,4}))?";
|
|
1198
|
+
var HTTPS_ORIGIN = new RegExp(`^https://${HOST}${PORT}$`);
|
|
1199
|
+
var LOOPBACK_ORIGIN = new RegExp(`^http://(?:localhost|127\\.0\\.0\\.1|\\[::1\\])${PORT}$`);
|
|
1200
|
+
function isWebOrigin(value) {
|
|
1201
|
+
const matched = HTTPS_ORIGIN.exec(value) ?? LOOPBACK_ORIGIN.exec(value);
|
|
1202
|
+
if (!matched) {
|
|
1203
|
+
return false;
|
|
1204
|
+
}
|
|
1205
|
+
const port = matched[1] === undefined ? null : Number(matched[1]);
|
|
1206
|
+
const defaultPort = value.startsWith("https://") ? 443 : 80;
|
|
1207
|
+
return port === null || port <= 65535 && port !== defaultPort;
|
|
1208
|
+
}
|
|
1209
|
+
function distinct(values) {
|
|
1210
|
+
return new Set(values).size === values.length;
|
|
1211
|
+
}
|
|
1212
|
+
var WebOriginSchema = z11.string().max(255).refine(isWebOrigin, {
|
|
1213
|
+
message: "must be an origin such as https://app.example.com (lowercase, no path, no wildcard; http only for localhost)"
|
|
1214
|
+
}).meta({ ref: "WebOrigin" });
|
|
1215
|
+
var RedirectUrlSchema = z11.string().max(2048).refine(isRedirectUrl2, {
|
|
1216
|
+
message: "must be an absolute https URL (http only for localhost) with no credentials, fragment or wildcard, or a custom scheme with a full stop such as com.example.app:/oauth (no query, no fragment)"
|
|
1217
|
+
}).meta({ ref: "RedirectUrl" });
|
|
1218
|
+
var App = z11.object({
|
|
1219
|
+
name: z11.string().trim().min(1).max(MAX_APP_NAME_LENGTH).refine((name) => !UNPRINTABLE3.test(name), {
|
|
1220
|
+
message: "must not contain control characters or line breaks"
|
|
1221
|
+
}).default(DEFAULT_APP_NAME),
|
|
1222
|
+
supportEmail: z11.email().max(254).nullable().default(null)
|
|
1223
|
+
});
|
|
1224
|
+
var AppInput = App.strict().refine((app) => !hasHiddenCharacter2(app.name), {
|
|
1225
|
+
path: ["name"],
|
|
1226
|
+
message: "must not contain text-direction controls, private-use or unassigned characters, or half a surrogate pair"
|
|
1227
|
+
});
|
|
1228
|
+
var PasswordMethod = z11.object({ enabled: z11.boolean().default(true) });
|
|
1229
|
+
var OptionalMethod = z11.object({ enabled: z11.boolean().default(false) });
|
|
1230
|
+
var SIGN_IN_METHODS_WITHOUT_SIGN_UP = ["smsCode"];
|
|
1231
|
+
var AT_LEAST_ONE_SIGN_IN_METHOD = "at least one sign-in method must stay enabled";
|
|
1232
|
+
function hasEnabledSignInMethod(settings) {
|
|
1233
|
+
return Object.entries(settings.signIn.methods).some(([name, method]) => method.enabled && !SIGN_IN_METHODS_WITHOUT_SIGN_UP.includes(name));
|
|
1234
|
+
}
|
|
1235
|
+
var linkNeedsCode = {
|
|
1236
|
+
message: "emailLink needs emailCode to be enabled as well",
|
|
1237
|
+
path: ["emailLink", "enabled"]
|
|
1238
|
+
};
|
|
1239
|
+
function linkHasCode(methods) {
|
|
1240
|
+
return !methods.emailLink.enabled || methods.emailCode.enabled;
|
|
1241
|
+
}
|
|
1242
|
+
var SignUpPasswordModeSchema = z11.enum(["required", "optional"]).meta({ ref: "SignUpPasswordMode" });
|
|
1243
|
+
var SignUp = z11.object({
|
|
1244
|
+
password: SignUpPasswordModeSchema.default("required")
|
|
1245
|
+
});
|
|
1246
|
+
var passwordlessNeedsCode = {
|
|
1247
|
+
message: "an optional sign-up password needs signIn.methods.emailCode to be enabled",
|
|
1248
|
+
path: ["signUp", "password"]
|
|
1249
|
+
};
|
|
1250
|
+
function passwordlessHasCode(settings) {
|
|
1251
|
+
return settings.signUp.password === "required" || settings.signIn.methods.emailCode.enabled;
|
|
1252
|
+
}
|
|
1253
|
+
var AllowedOrigins = z11.array(WebOriginSchema).max(MAX_ALLOWED_ORIGINS).refine(distinct, { message: "must not list an origin twice" });
|
|
1254
|
+
var Urls = z11.object({
|
|
1255
|
+
allowedOrigins: AllowedOrigins.default([]),
|
|
1256
|
+
allowedRedirectUrls: z11.array(RedirectUrlSchema).max(MAX_ALLOWED_REDIRECT_URLS).refine(distinct, { message: "must not list a URL twice" }).default([])
|
|
1257
|
+
});
|
|
1258
|
+
var Audit = z11.object({
|
|
1259
|
+
retentionDays: z11.number().int().min(1).max(MAX_AUDIT_RETENTION_DAYS).nullable().default(null)
|
|
1260
|
+
});
|
|
1261
|
+
var Agents = z11.object({
|
|
1262
|
+
proposalsInProduction: z11.boolean().default(false),
|
|
1263
|
+
testSignIn: z11.boolean().nullable().default(null)
|
|
1264
|
+
});
|
|
1265
|
+
var Notifications = z11.object({
|
|
1266
|
+
passwordChanged: z11.boolean().default(true),
|
|
1267
|
+
newSignIn: z11.boolean().default(true),
|
|
1268
|
+
mfaChanged: z11.boolean().default(true),
|
|
1269
|
+
identityChanged: z11.boolean().default(true)
|
|
1270
|
+
});
|
|
1271
|
+
var MfaPolicySchema = z11.enum(["off", "optional", "required"]).meta({ ref: "MfaPolicy" });
|
|
1272
|
+
var Mfa = z11.object({
|
|
1273
|
+
policy: MfaPolicySchema.default("optional"),
|
|
1274
|
+
smsCode: z11.object({ enabled: z11.boolean().default(false) }).prefault({})
|
|
1275
|
+
});
|
|
1276
|
+
var MfaInput = Mfa.extend({
|
|
1277
|
+
smsCode: z11.strictObject({ enabled: z11.boolean().default(false) }).prefault({})
|
|
1278
|
+
}).strict();
|
|
1279
|
+
var RP_ID = new RegExp(`^(?:localhost|${LABEL}(?:\\.${LABEL})+)$`);
|
|
1280
|
+
function isRelyingPartyId(value) {
|
|
1281
|
+
return value.length <= 253 && RP_ID.test(value) && !/(?:^|\.)[0-9]+$/.test(value);
|
|
1282
|
+
}
|
|
1283
|
+
function originMatchesRelyingParty(origin, rpId) {
|
|
1284
|
+
let host;
|
|
1285
|
+
try {
|
|
1286
|
+
host = new URL(origin).hostname;
|
|
1287
|
+
} catch {
|
|
1288
|
+
return false;
|
|
1289
|
+
}
|
|
1290
|
+
return host === rpId || host.endsWith(`.${rpId}`);
|
|
1291
|
+
}
|
|
1292
|
+
var Passkeys = z11.object({
|
|
1293
|
+
rpId: z11.string().refine(isRelyingPartyId, {
|
|
1294
|
+
message: "must be a domain such as example.com, or localhost (no scheme, port or path)"
|
|
1295
|
+
}).nullable().default(null)
|
|
1296
|
+
});
|
|
1297
|
+
var passkeyNeedsRpId = {
|
|
1298
|
+
message: "passkey needs passkeys.rpId to be set",
|
|
1299
|
+
path: ["signIn", "methods", "passkey", "enabled"]
|
|
1300
|
+
};
|
|
1301
|
+
function passkeyHasRpId(settings) {
|
|
1302
|
+
return !settings.signIn.methods.passkey.enabled || settings.passkeys.rpId !== null;
|
|
1303
|
+
}
|
|
1304
|
+
var SmsCountrySchema = z11.string().refine(isSmsCountry, {
|
|
1305
|
+
message: "must be an ISO 3166-1 alpha-2 country code in upper case, such as DE"
|
|
1306
|
+
}).meta({ ref: "SmsCountry" });
|
|
1307
|
+
var SmsCountries = z11.array(SmsCountrySchema).max(SMS_COUNTRIES.length).refine(distinct, { message: "must not list a country twice" });
|
|
1308
|
+
var Sms = z11.object({
|
|
1309
|
+
enabled: z11.boolean().default(false),
|
|
1310
|
+
allowedCountries: SmsCountries.default([]),
|
|
1311
|
+
dailyMessageLimit: z11.number().int().min(1).max(MAX_SMS_DAILY_MESSAGE_LIMIT).default(DEFAULT_SMS_DAILY_MESSAGE_LIMIT),
|
|
1312
|
+
templates: SmsTemplatesSchema.default({})
|
|
1313
|
+
});
|
|
1314
|
+
var password = PasswordPolicySchema.default(PASSWORD_POLICY_PRESETS.recommended);
|
|
1315
|
+
var version = z11.literal(1).default(1);
|
|
1316
|
+
var SignIn = z11.strictObject({
|
|
1317
|
+
methods: z11.strictObject({
|
|
1318
|
+
password: PasswordMethod.strict().prefault({}),
|
|
1319
|
+
emailCode: OptionalMethod.strict().prefault({}),
|
|
1320
|
+
emailLink: OptionalMethod.strict().prefault({}),
|
|
1321
|
+
passkey: OptionalMethod.strict().prefault({}),
|
|
1322
|
+
smsCode: OptionalMethod.strict().prefault({})
|
|
1323
|
+
}).refine(linkHasCode, linkNeedsCode).prefault({})
|
|
1324
|
+
}).prefault({});
|
|
1325
|
+
var minLengthFloor = {
|
|
1326
|
+
message: `must be at least ${MIN_PASSWORD_MIN_LENGTH}`,
|
|
1327
|
+
path: ["password", "minLength"]
|
|
1328
|
+
};
|
|
1329
|
+
var EnvironmentSettingsSchema = z11.strictObject({
|
|
1330
|
+
version,
|
|
1331
|
+
app: App.strict().prefault({}),
|
|
1332
|
+
password,
|
|
1333
|
+
signIn: SignIn,
|
|
1334
|
+
signUp: SignUp.strict().prefault({}),
|
|
1335
|
+
urls: Urls.strict().prefault({}),
|
|
1336
|
+
audit: Audit.strict().prefault({}),
|
|
1337
|
+
agents: Agents.strict().prefault({}),
|
|
1338
|
+
notifications: Notifications.strict().prefault({}),
|
|
1339
|
+
mfa: MfaInput.prefault({}),
|
|
1340
|
+
passkeys: Passkeys.strict().prefault({}),
|
|
1341
|
+
sessions: SessionSettingsSchema.prefault({}),
|
|
1342
|
+
sms: Sms.strict().prefault({}),
|
|
1343
|
+
emails: EmailSettingsSchema.prefault({})
|
|
1344
|
+
}).refine((settings) => settings.password.minLength >= MIN_PASSWORD_MIN_LENGTH, minLengthFloor).refine(passwordlessHasCode, passwordlessNeedsCode).refine(passkeyHasRpId, passkeyNeedsRpId).meta({ ref: "EnvironmentSettings" });
|
|
1345
|
+
var EnvironmentSettingsInputSchema = z11.strictObject({
|
|
1346
|
+
version,
|
|
1347
|
+
app: AppInput.prefault({}),
|
|
1348
|
+
password: PasswordPolicySchema.optional(),
|
|
1349
|
+
signIn: SignIn,
|
|
1350
|
+
signUp: SignUp.strict().prefault({}),
|
|
1351
|
+
urls: z11.strictObject({
|
|
1352
|
+
allowedOrigins: AllowedOrigins.optional(),
|
|
1353
|
+
allowedRedirectUrls: Urls.shape.allowedRedirectUrls
|
|
1354
|
+
}).prefault({}),
|
|
1355
|
+
audit: Audit.strict().prefault({}),
|
|
1356
|
+
agents: Agents.strict().prefault({}),
|
|
1357
|
+
notifications: Notifications.strict().prefault({}),
|
|
1358
|
+
mfa: MfaInput.prefault({}),
|
|
1359
|
+
passkeys: Passkeys.strict().prefault({}),
|
|
1360
|
+
sessions: SessionSettingsSchema.prefault({}),
|
|
1361
|
+
sms: Sms.strict().prefault({}),
|
|
1362
|
+
emails: EmailSettingsSchema.prefault({})
|
|
1363
|
+
}).refine((settings) => settings.password === undefined || settings.password.minLength >= MIN_PASSWORD_MIN_LENGTH, minLengthFloor).refine(passwordlessHasCode, passwordlessNeedsCode).refine(passkeyHasRpId, passkeyNeedsRpId).meta({ ref: "EnvironmentSettingsInput" });
|
|
1364
|
+
var Stored = z11.object({
|
|
1365
|
+
version,
|
|
1366
|
+
app: App.prefault({}),
|
|
1367
|
+
password,
|
|
1368
|
+
signIn: z11.object({
|
|
1369
|
+
methods: z11.object({
|
|
1370
|
+
password: PasswordMethod.prefault({}),
|
|
1371
|
+
emailCode: OptionalMethod.prefault({}),
|
|
1372
|
+
emailLink: OptionalMethod.prefault({}),
|
|
1373
|
+
passkey: OptionalMethod.prefault({}),
|
|
1374
|
+
smsCode: OptionalMethod.prefault({})
|
|
1375
|
+
}).prefault({})
|
|
1376
|
+
}).prefault({}),
|
|
1377
|
+
signUp: SignUp.prefault({}),
|
|
1378
|
+
urls: Urls.prefault({}),
|
|
1379
|
+
audit: Audit.prefault({}),
|
|
1380
|
+
agents: Agents.prefault({}),
|
|
1381
|
+
notifications: Notifications.prefault({}),
|
|
1382
|
+
mfa: Mfa.prefault({}),
|
|
1383
|
+
passkeys: Passkeys.prefault({}),
|
|
1384
|
+
sessions: StoredSessionSettingsSchema.prefault({}),
|
|
1385
|
+
sms: Sms.prefault({}),
|
|
1386
|
+
emails: EmailSettingsSchema.prefault({})
|
|
1387
|
+
});
|
|
1388
|
+
var DEFAULT_ENVIRONMENT_SETTINGS = EnvironmentSettingsSchema.parse({});
|
|
1389
|
+
var LIST_RULES = {
|
|
1390
|
+
urls: {
|
|
1391
|
+
allowedOrigins: { entry: WebOriginSchema, max: MAX_ALLOWED_ORIGINS },
|
|
1392
|
+
allowedRedirectUrls: { entry: RedirectUrlSchema, max: MAX_ALLOWED_REDIRECT_URLS }
|
|
1393
|
+
},
|
|
1394
|
+
sms: {
|
|
1395
|
+
allowedCountries: { entry: SmsCountrySchema, max: SMS_COUNTRIES.length }
|
|
1396
|
+
}
|
|
1397
|
+
};
|
|
1398
|
+
function usable(list, rule) {
|
|
1399
|
+
if (!Array.isArray(list)) {
|
|
1400
|
+
return { kept: [], dropped: 1 };
|
|
1401
|
+
}
|
|
1402
|
+
const kept = new Set;
|
|
1403
|
+
for (const entry of list) {
|
|
1404
|
+
if (kept.size < rule.max && rule.entry.safeParse(entry).success) {
|
|
1405
|
+
kept.add(entry);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
return { kept: [...kept], dropped: list.length - kept.size };
|
|
1409
|
+
}
|
|
1410
|
+
function readStoredEnvironmentSettings(stored) {
|
|
1411
|
+
if (typeof stored !== "object" || stored === null) {
|
|
1412
|
+
return {
|
|
1413
|
+
settings: Stored.parse(stored),
|
|
1414
|
+
dropped: 0,
|
|
1415
|
+
droppedEmailTemplates: [],
|
|
1416
|
+
unknownEmailTemplates: 0,
|
|
1417
|
+
droppedSmsTemplates: [],
|
|
1418
|
+
unknownSmsTemplates: 0
|
|
1419
|
+
};
|
|
1420
|
+
}
|
|
1421
|
+
const emails = readStoredEmailTemplates2(stored.emails?.templates);
|
|
1422
|
+
const sections = {};
|
|
1423
|
+
let dropped = 0;
|
|
1424
|
+
for (const [name, rules] of Object.entries(LIST_RULES)) {
|
|
1425
|
+
const section = stored[name];
|
|
1426
|
+
if (typeof section !== "object" || section === null) {
|
|
1427
|
+
sections[name] = undefined;
|
|
1428
|
+
continue;
|
|
1429
|
+
}
|
|
1430
|
+
const lists = {};
|
|
1431
|
+
for (const [key, rule] of Object.entries(rules)) {
|
|
1432
|
+
const list = section[key];
|
|
1433
|
+
if (list !== undefined) {
|
|
1434
|
+
const result = usable(list, rule);
|
|
1435
|
+
lists[key] = result.kept;
|
|
1436
|
+
dropped += result.dropped;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
sections[name] = { ...section, ...lists };
|
|
1440
|
+
}
|
|
1441
|
+
const smsSection = sections.sms;
|
|
1442
|
+
const sms = readStoredSmsTemplates(smsSection?.templates);
|
|
1443
|
+
if (smsSection !== undefined) {
|
|
1444
|
+
sections.sms = { ...smsSection, templates: sms.templates };
|
|
1445
|
+
}
|
|
1446
|
+
return {
|
|
1447
|
+
settings: Stored.parse({ ...stored, ...sections, emails: { templates: emails.templates } }),
|
|
1448
|
+
dropped,
|
|
1449
|
+
droppedEmailTemplates: emails.dropped,
|
|
1450
|
+
unknownEmailTemplates: emails.unknown,
|
|
1451
|
+
droppedSmsTemplates: sms.dropped,
|
|
1452
|
+
unknownSmsTemplates: sms.unknown
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
function parseStoredEnvironmentSettings(stored) {
|
|
1456
|
+
return readStoredEnvironmentSettings(stored).settings;
|
|
1457
|
+
}
|
|
1458
|
+
var CONFIG_TOOL_PATTERN = /^[a-z0-9][a-z0-9._-]{0,31}$/;
|
|
1459
|
+
var CONFIG_HASH_PATTERN = /^sha256:[0-9a-f]{64}$/;
|
|
1460
|
+
var SettingsManagedBySchema = z11.object({
|
|
1461
|
+
tool: z11.string().regex(CONFIG_TOOL_PATTERN),
|
|
1462
|
+
configHash: z11.string().regex(CONFIG_HASH_PATTERN),
|
|
1463
|
+
at: z11.iso.datetime(),
|
|
1464
|
+
revision: z11.number().int().min(1),
|
|
1465
|
+
drifted: z11.boolean()
|
|
1466
|
+
}).meta({ ref: "SettingsManagedBy" });
|
|
1467
|
+
var ClientConfigSchema = z11.object({
|
|
1468
|
+
app: z11.object({ name: z11.string(), supportEmail: z11.string().nullable() }),
|
|
1469
|
+
signIn: z11.object({
|
|
1470
|
+
methods: z11.array(z11.string()),
|
|
1471
|
+
oauth: z11.array(z11.string()).optional()
|
|
1472
|
+
}),
|
|
1473
|
+
signUp: z11.object({ password: SignUpPasswordModeSchema }).optional(),
|
|
1474
|
+
password: PasswordPolicySchema,
|
|
1475
|
+
mfa: z11.object({ policy: MfaPolicySchema, smsCode: z11.boolean().optional() }).optional(),
|
|
1476
|
+
phone: z11.object({ enabled: z11.boolean() }).optional()
|
|
1477
|
+
}).meta({ ref: "ClientConfig" });
|
|
1478
|
+
// src/errors.ts
|
|
1479
|
+
import { z as z12 } from "zod";
|
|
1480
|
+
var ErrorCodeSchema = z12.enum(ERROR_CODES2).meta({ ref: "ErrorCode" });
|
|
1481
|
+
var ErrorParamsSchema = z12.record(z12.string(), z12.union([z12.string(), z12.number(), z12.boolean()])).meta({ ref: "ErrorParams" });
|
|
1482
|
+
var FieldErrorSchema = z12.object({
|
|
1483
|
+
field: z12.string(),
|
|
1484
|
+
code: ErrorCodeSchema,
|
|
1485
|
+
message: z12.string(),
|
|
1486
|
+
params: ErrorParamsSchema.optional()
|
|
1487
|
+
}).meta({ ref: "FieldError" });
|
|
1488
|
+
var ErrorEnvelopeSchema = z12.object({
|
|
1489
|
+
status: z12.number().int(),
|
|
1490
|
+
code: ErrorCodeSchema,
|
|
1491
|
+
detail: z12.string(),
|
|
1492
|
+
params: ErrorParamsSchema.optional(),
|
|
1493
|
+
errors: z12.array(FieldErrorSchema).optional()
|
|
1494
|
+
}).meta({ ref: "ErrorEnvelope" });
|
|
1495
|
+
// src/event-fixtures.ts
|
|
1496
|
+
var USER = "0199c2f4-7a10-7c3e-9b1a-5d2e8f4a6c01";
|
|
1497
|
+
var SESSION = "0199c2f4-7a11-7d42-8e0b-1c9a3b7d5e02";
|
|
1498
|
+
var API_KEY = "0199c2f4-7a12-7a55-b3c4-6f1e2d8a9b03";
|
|
1499
|
+
var ENVIRONMENT = "0199c2f4-7a13-7b66-a4d5-7e2f3c9b0a04";
|
|
1500
|
+
var PASSKEY = "0199c2f4-7a14-7c77-95e6-8f3a4d0c1b05";
|
|
1501
|
+
var SIGNING_KEY = "0199c2f4-7a15-7d88-86f7-9a4b5e1d2c06";
|
|
1502
|
+
var WEBHOOK_ENDPOINT = "0199c2f4-7a18-7abb-99ca-cd7e8b4a5f09";
|
|
1503
|
+
var HOOK = "0199c2f4-7a19-7bcc-8adb-de8f9c5b6a10";
|
|
1504
|
+
var NATIVE_APP = "0199c2f4-7a1a-7cdd-9bec-ef9a0d6c7b11";
|
|
1505
|
+
var PROPOSAL = "0199c2f4-7a1b-7dee-8cfd-f0ab1e7d8c12";
|
|
1506
|
+
var AGENT = "0199c2f4-7a1c-7eff-9d0e-01bc2f8e9d13";
|
|
1507
|
+
var schemaVersion = EVENT_SCHEMA_VERSION2;
|
|
1508
|
+
var occurredAt = "2026-10-08T09:30:00.000Z";
|
|
1509
|
+
var user = { type: "user", id: USER };
|
|
1510
|
+
var admin = { type: "admin", id: API_KEY };
|
|
1511
|
+
var system = { type: "system", id: null };
|
|
1512
|
+
var agent = { type: "agent", id: AGENT };
|
|
1513
|
+
var aboutUser = { type: "user", id: USER };
|
|
1514
|
+
var aboutSession = { type: "session", id: SESSION };
|
|
1515
|
+
var aboutApiKey = { type: "api_key", id: API_KEY };
|
|
1516
|
+
var aboutEnvironment = { type: "environment", id: ENVIRONMENT };
|
|
1517
|
+
var aboutWebhookEndpoint = { type: "webhook_endpoint", id: WEBHOOK_ENDPOINT };
|
|
1518
|
+
var aboutHook = { type: "hook", id: HOOK };
|
|
1519
|
+
var aboutNativeApp = { type: "native_app", id: NATIVE_APP };
|
|
1520
|
+
var aboutProposal = { type: "proposal", id: PROPOSAL };
|
|
1521
|
+
var proposed = { operation: "ban_user", targetType: "user", targetId: USER };
|
|
1522
|
+
function eventId(n) {
|
|
1523
|
+
return `0199c2f5-0000-7000-8000-${String(n).padStart(12, "0")}`;
|
|
1524
|
+
}
|
|
1525
|
+
var EVENT_FIXTURES = {
|
|
1526
|
+
"user.created": {
|
|
1527
|
+
id: eventId(1),
|
|
1528
|
+
type: "user.created",
|
|
1529
|
+
schemaVersion,
|
|
1530
|
+
occurredAt,
|
|
1531
|
+
actor: user,
|
|
1532
|
+
target: aboutUser,
|
|
1533
|
+
data: {
|
|
1534
|
+
method: "sign_up",
|
|
1535
|
+
emailVerified: true,
|
|
1536
|
+
passwordless: true,
|
|
1537
|
+
hookBypassed: true,
|
|
1538
|
+
test: true
|
|
1539
|
+
}
|
|
1540
|
+
},
|
|
1541
|
+
"user.email_verified": {
|
|
1542
|
+
id: eventId(2),
|
|
1543
|
+
type: "user.email_verified",
|
|
1544
|
+
schemaVersion,
|
|
1545
|
+
occurredAt,
|
|
1546
|
+
actor: user,
|
|
1547
|
+
target: aboutUser,
|
|
1548
|
+
data: {}
|
|
1549
|
+
},
|
|
1550
|
+
"user.banned": {
|
|
1551
|
+
id: eventId(3),
|
|
1552
|
+
type: "user.banned",
|
|
1553
|
+
schemaVersion,
|
|
1554
|
+
occurredAt,
|
|
1555
|
+
actor: agent,
|
|
1556
|
+
target: aboutUser,
|
|
1557
|
+
data: { proposalId: PROPOSAL, approvedBy: admin }
|
|
1558
|
+
},
|
|
1559
|
+
"user.unbanned": {
|
|
1560
|
+
id: eventId(4),
|
|
1561
|
+
type: "user.unbanned",
|
|
1562
|
+
schemaVersion,
|
|
1563
|
+
occurredAt,
|
|
1564
|
+
actor: admin,
|
|
1565
|
+
target: aboutUser,
|
|
1566
|
+
data: {}
|
|
1567
|
+
},
|
|
1568
|
+
"user.deleted": {
|
|
1569
|
+
id: eventId(5),
|
|
1570
|
+
type: "user.deleted",
|
|
1571
|
+
schemaVersion,
|
|
1572
|
+
occurredAt,
|
|
1573
|
+
actor: admin,
|
|
1574
|
+
target: aboutUser,
|
|
1575
|
+
data: {}
|
|
1576
|
+
},
|
|
1577
|
+
"user.password_changed": {
|
|
1578
|
+
id: eventId(6),
|
|
1579
|
+
type: "user.password_changed",
|
|
1580
|
+
schemaVersion,
|
|
1581
|
+
occurredAt,
|
|
1582
|
+
actor: user,
|
|
1583
|
+
target: aboutUser,
|
|
1584
|
+
data: { method: "email_verification", created: false, removed: true }
|
|
1585
|
+
},
|
|
1586
|
+
"user.mfa_enabled": {
|
|
1587
|
+
id: eventId(7),
|
|
1588
|
+
type: "user.mfa_enabled",
|
|
1589
|
+
schemaVersion,
|
|
1590
|
+
occurredAt,
|
|
1591
|
+
actor: user,
|
|
1592
|
+
target: aboutUser,
|
|
1593
|
+
data: { method: "totp" }
|
|
1594
|
+
},
|
|
1595
|
+
"user.mfa_disabled": {
|
|
1596
|
+
id: eventId(8),
|
|
1597
|
+
type: "user.mfa_disabled",
|
|
1598
|
+
schemaVersion,
|
|
1599
|
+
occurredAt,
|
|
1600
|
+
actor: admin,
|
|
1601
|
+
target: aboutUser,
|
|
1602
|
+
data: { method: "admin_reset" }
|
|
1603
|
+
},
|
|
1604
|
+
"user.backup_codes_regenerated": {
|
|
1605
|
+
id: eventId(9),
|
|
1606
|
+
type: "user.backup_codes_regenerated",
|
|
1607
|
+
schemaVersion,
|
|
1608
|
+
occurredAt,
|
|
1609
|
+
actor: user,
|
|
1610
|
+
target: aboutUser,
|
|
1611
|
+
data: {}
|
|
1612
|
+
},
|
|
1613
|
+
"user.backup_code_used": {
|
|
1614
|
+
id: eventId(10),
|
|
1615
|
+
type: "user.backup_code_used",
|
|
1616
|
+
schemaVersion,
|
|
1617
|
+
occurredAt,
|
|
1618
|
+
actor: user,
|
|
1619
|
+
target: aboutUser,
|
|
1620
|
+
data: {}
|
|
1621
|
+
},
|
|
1622
|
+
"user.identity_linked": {
|
|
1623
|
+
id: eventId(11),
|
|
1624
|
+
type: "user.identity_linked",
|
|
1625
|
+
schemaVersion,
|
|
1626
|
+
occurredAt,
|
|
1627
|
+
actor: user,
|
|
1628
|
+
target: aboutUser,
|
|
1629
|
+
data: { provider: "google", method: "auto" }
|
|
1630
|
+
},
|
|
1631
|
+
"user.identity_unlinked": {
|
|
1632
|
+
id: eventId(12),
|
|
1633
|
+
type: "user.identity_unlinked",
|
|
1634
|
+
schemaVersion,
|
|
1635
|
+
occurredAt,
|
|
1636
|
+
actor: user,
|
|
1637
|
+
target: aboutUser,
|
|
1638
|
+
data: { provider: "github" }
|
|
1639
|
+
},
|
|
1640
|
+
"user.passkey_added": {
|
|
1641
|
+
id: eventId(13),
|
|
1642
|
+
type: "user.passkey_added",
|
|
1643
|
+
schemaVersion,
|
|
1644
|
+
occurredAt,
|
|
1645
|
+
actor: user,
|
|
1646
|
+
target: aboutUser,
|
|
1647
|
+
data: { passkeyId: PASSKEY, synced: true }
|
|
1648
|
+
},
|
|
1649
|
+
"user.passkey_renamed": {
|
|
1650
|
+
id: eventId(14),
|
|
1651
|
+
type: "user.passkey_renamed",
|
|
1652
|
+
schemaVersion,
|
|
1653
|
+
occurredAt,
|
|
1654
|
+
actor: user,
|
|
1655
|
+
target: aboutUser,
|
|
1656
|
+
data: { passkeyId: PASSKEY }
|
|
1657
|
+
},
|
|
1658
|
+
"user.passkey_removed": {
|
|
1659
|
+
id: eventId(15),
|
|
1660
|
+
type: "user.passkey_removed",
|
|
1661
|
+
schemaVersion,
|
|
1662
|
+
occurredAt,
|
|
1663
|
+
actor: user,
|
|
1664
|
+
target: aboutUser,
|
|
1665
|
+
data: { passkeyId: PASSKEY, method: "user" }
|
|
1666
|
+
},
|
|
1667
|
+
"user.passkey_counter_regressed": {
|
|
1668
|
+
id: eventId(16),
|
|
1669
|
+
type: "user.passkey_counter_regressed",
|
|
1670
|
+
schemaVersion,
|
|
1671
|
+
occurredAt,
|
|
1672
|
+
actor: system,
|
|
1673
|
+
target: aboutUser,
|
|
1674
|
+
data: { passkeyId: PASSKEY }
|
|
1675
|
+
},
|
|
1676
|
+
"user.email_added": {
|
|
1677
|
+
id: eventId(44),
|
|
1678
|
+
type: "user.email_added",
|
|
1679
|
+
schemaVersion,
|
|
1680
|
+
occurredAt,
|
|
1681
|
+
actor: user,
|
|
1682
|
+
target: aboutUser,
|
|
1683
|
+
data: { method: "self", verified: true }
|
|
1684
|
+
},
|
|
1685
|
+
"user.phone_number_added": {
|
|
1686
|
+
id: eventId(36),
|
|
1687
|
+
type: "user.phone_number_added",
|
|
1688
|
+
schemaVersion,
|
|
1689
|
+
occurredAt,
|
|
1690
|
+
actor: user,
|
|
1691
|
+
target: aboutUser,
|
|
1692
|
+
data: {}
|
|
1693
|
+
},
|
|
1694
|
+
"user.phone_number_removed": {
|
|
1695
|
+
id: eventId(37),
|
|
1696
|
+
type: "user.phone_number_removed",
|
|
1697
|
+
schemaVersion,
|
|
1698
|
+
occurredAt,
|
|
1699
|
+
actor: user,
|
|
1700
|
+
target: aboutUser,
|
|
1701
|
+
data: {}
|
|
1702
|
+
},
|
|
1703
|
+
"user.sms_factor_enabled": {
|
|
1704
|
+
id: eventId(39),
|
|
1705
|
+
type: "user.sms_factor_enabled",
|
|
1706
|
+
schemaVersion,
|
|
1707
|
+
occurredAt,
|
|
1708
|
+
actor: user,
|
|
1709
|
+
target: aboutUser,
|
|
1710
|
+
data: {}
|
|
1711
|
+
},
|
|
1712
|
+
"user.sms_factor_removed": {
|
|
1713
|
+
id: eventId(40),
|
|
1714
|
+
type: "user.sms_factor_removed",
|
|
1715
|
+
schemaVersion,
|
|
1716
|
+
occurredAt,
|
|
1717
|
+
actor: user,
|
|
1718
|
+
target: aboutUser,
|
|
1719
|
+
data: { method: "phone_number_removed" }
|
|
1720
|
+
},
|
|
1721
|
+
"session.created": {
|
|
1722
|
+
id: eventId(17),
|
|
1723
|
+
type: "session.created",
|
|
1724
|
+
schemaVersion,
|
|
1725
|
+
occurredAt,
|
|
1726
|
+
actor: user,
|
|
1727
|
+
target: aboutSession,
|
|
1728
|
+
data: {
|
|
1729
|
+
userId: USER,
|
|
1730
|
+
client: "web",
|
|
1731
|
+
hookBypassed: true,
|
|
1732
|
+
claimsHookBypassed: true,
|
|
1733
|
+
deviceBound: true
|
|
1734
|
+
}
|
|
1735
|
+
},
|
|
1736
|
+
"session.revoked": {
|
|
1737
|
+
id: eventId(18),
|
|
1738
|
+
type: "session.revoked",
|
|
1739
|
+
schemaVersion,
|
|
1740
|
+
occurredAt,
|
|
1741
|
+
actor: user,
|
|
1742
|
+
target: aboutSession,
|
|
1743
|
+
data: { userId: USER, reason: "sign_out" }
|
|
1744
|
+
},
|
|
1745
|
+
"session.reuse_detected": {
|
|
1746
|
+
id: eventId(19),
|
|
1747
|
+
type: "session.reuse_detected",
|
|
1748
|
+
schemaVersion,
|
|
1749
|
+
occurredAt,
|
|
1750
|
+
actor: system,
|
|
1751
|
+
target: aboutSession,
|
|
1752
|
+
data: { userId: USER, reason: "reuse_detected" }
|
|
1753
|
+
},
|
|
1754
|
+
"session.stepped_up": {
|
|
1755
|
+
id: eventId(20),
|
|
1756
|
+
type: "session.stepped_up",
|
|
1757
|
+
schemaVersion,
|
|
1758
|
+
occurredAt,
|
|
1759
|
+
actor: user,
|
|
1760
|
+
target: aboutSession,
|
|
1761
|
+
data: { userId: USER, methods: ["otp", "mfa"], claimsHookBypassed: true }
|
|
1762
|
+
},
|
|
1763
|
+
"api_key.created": {
|
|
1764
|
+
id: eventId(21),
|
|
1765
|
+
type: "api_key.created",
|
|
1766
|
+
schemaVersion,
|
|
1767
|
+
occurredAt,
|
|
1768
|
+
actor: admin,
|
|
1769
|
+
target: aboutApiKey,
|
|
1770
|
+
data: { kind: "secret" }
|
|
1771
|
+
},
|
|
1772
|
+
"api_key.revoked": {
|
|
1773
|
+
id: eventId(22),
|
|
1774
|
+
type: "api_key.revoked",
|
|
1775
|
+
schemaVersion,
|
|
1776
|
+
occurredAt,
|
|
1777
|
+
actor: admin,
|
|
1778
|
+
target: aboutApiKey,
|
|
1779
|
+
data: {}
|
|
1780
|
+
},
|
|
1781
|
+
"signing_key.rotated": {
|
|
1782
|
+
id: eventId(23),
|
|
1783
|
+
type: "signing_key.rotated",
|
|
1784
|
+
schemaVersion,
|
|
1785
|
+
occurredAt,
|
|
1786
|
+
actor: admin,
|
|
1787
|
+
target: { type: "signing_key", id: SIGNING_KEY },
|
|
1788
|
+
data: {
|
|
1789
|
+
retiredKeyId: "0199c2f4-7a16-7e99-97a8-ab5c6f2e3d07",
|
|
1790
|
+
nextKeyId: "0199c2f4-7a17-7faa-88b9-bc6d7a3f4e08"
|
|
1791
|
+
}
|
|
1792
|
+
},
|
|
1793
|
+
"environment.settings_updated": {
|
|
1794
|
+
id: eventId(24),
|
|
1795
|
+
type: "environment.settings_updated",
|
|
1796
|
+
schemaVersion,
|
|
1797
|
+
occurredAt,
|
|
1798
|
+
actor: admin,
|
|
1799
|
+
target: aboutEnvironment,
|
|
1800
|
+
data: {
|
|
1801
|
+
revision: 4,
|
|
1802
|
+
changed: ["password.minLength", "urls.allowedOrigins"],
|
|
1803
|
+
weakened: true,
|
|
1804
|
+
managedBy: "tula-apply",
|
|
1805
|
+
outsideConfig: false
|
|
1806
|
+
}
|
|
1807
|
+
},
|
|
1808
|
+
"oauth_provider.updated": {
|
|
1809
|
+
id: eventId(25),
|
|
1810
|
+
type: "oauth_provider.updated",
|
|
1811
|
+
schemaVersion,
|
|
1812
|
+
occurredAt,
|
|
1813
|
+
actor: admin,
|
|
1814
|
+
target: aboutEnvironment,
|
|
1815
|
+
data: {
|
|
1816
|
+
provider: "google",
|
|
1817
|
+
changed: ["clientId", "secret", "additionalClientIds", "enabled"],
|
|
1818
|
+
created: true,
|
|
1819
|
+
additionalClientIdCount: 2,
|
|
1820
|
+
weakened: true,
|
|
1821
|
+
dependentAccounts: 12,
|
|
1822
|
+
dependentAccountsIncomplete: true
|
|
1823
|
+
}
|
|
1824
|
+
},
|
|
1825
|
+
"oauth_provider.deleted": {
|
|
1826
|
+
id: eventId(26),
|
|
1827
|
+
type: "oauth_provider.deleted",
|
|
1828
|
+
schemaVersion,
|
|
1829
|
+
occurredAt,
|
|
1830
|
+
actor: admin,
|
|
1831
|
+
target: aboutEnvironment,
|
|
1832
|
+
data: {
|
|
1833
|
+
provider: "apple",
|
|
1834
|
+
weakened: true,
|
|
1835
|
+
dependentAccounts: 3,
|
|
1836
|
+
dependentAccountsIncomplete: true
|
|
1837
|
+
}
|
|
1838
|
+
},
|
|
1839
|
+
"webhook_endpoint.created": {
|
|
1840
|
+
id: eventId(27),
|
|
1841
|
+
type: "webhook_endpoint.created",
|
|
1842
|
+
schemaVersion,
|
|
1843
|
+
occurredAt,
|
|
1844
|
+
actor: admin,
|
|
1845
|
+
target: aboutWebhookEndpoint,
|
|
1846
|
+
data: { eventTypes: 3, enabled: true }
|
|
1847
|
+
},
|
|
1848
|
+
"webhook_endpoint.updated": {
|
|
1849
|
+
id: eventId(28),
|
|
1850
|
+
type: "webhook_endpoint.updated",
|
|
1851
|
+
schemaVersion,
|
|
1852
|
+
occurredAt,
|
|
1853
|
+
actor: admin,
|
|
1854
|
+
target: aboutWebhookEndpoint,
|
|
1855
|
+
data: { changed: ["url", "eventTypes", "enabled"] }
|
|
1856
|
+
},
|
|
1857
|
+
"webhook_endpoint.deleted": {
|
|
1858
|
+
id: eventId(29),
|
|
1859
|
+
type: "webhook_endpoint.deleted",
|
|
1860
|
+
schemaVersion,
|
|
1861
|
+
occurredAt,
|
|
1862
|
+
actor: admin,
|
|
1863
|
+
target: aboutWebhookEndpoint,
|
|
1864
|
+
data: {}
|
|
1865
|
+
},
|
|
1866
|
+
"webhook_endpoint.disabled": {
|
|
1867
|
+
id: eventId(30),
|
|
1868
|
+
type: "webhook_endpoint.disabled",
|
|
1869
|
+
schemaVersion,
|
|
1870
|
+
occurredAt,
|
|
1871
|
+
actor: system,
|
|
1872
|
+
target: aboutWebhookEndpoint,
|
|
1873
|
+
data: { reason: "failing" }
|
|
1874
|
+
},
|
|
1875
|
+
"webhook_endpoint.secret_rotated": {
|
|
1876
|
+
id: eventId(31),
|
|
1877
|
+
type: "webhook_endpoint.secret_rotated",
|
|
1878
|
+
schemaVersion,
|
|
1879
|
+
occurredAt,
|
|
1880
|
+
actor: admin,
|
|
1881
|
+
target: aboutWebhookEndpoint,
|
|
1882
|
+
data: { rotationOverlapEndsAt: "2026-10-09T09:30:00.000Z" }
|
|
1883
|
+
},
|
|
1884
|
+
"webhook_endpoint.previous_secret_revoked": {
|
|
1885
|
+
id: eventId(32),
|
|
1886
|
+
type: "webhook_endpoint.previous_secret_revoked",
|
|
1887
|
+
schemaVersion,
|
|
1888
|
+
occurredAt,
|
|
1889
|
+
actor: admin,
|
|
1890
|
+
target: aboutWebhookEndpoint,
|
|
1891
|
+
data: {}
|
|
1892
|
+
},
|
|
1893
|
+
"hook.created": {
|
|
1894
|
+
id: eventId(33),
|
|
1895
|
+
type: "hook.created",
|
|
1896
|
+
schemaVersion,
|
|
1897
|
+
occurredAt,
|
|
1898
|
+
actor: admin,
|
|
1899
|
+
target: aboutHook,
|
|
1900
|
+
data: { point: "before_sign_up", enabled: true, failureMode: "allow", weakened: true }
|
|
1901
|
+
},
|
|
1902
|
+
"hook.updated": {
|
|
1903
|
+
id: eventId(34),
|
|
1904
|
+
type: "hook.updated",
|
|
1905
|
+
schemaVersion,
|
|
1906
|
+
occurredAt,
|
|
1907
|
+
actor: admin,
|
|
1908
|
+
target: aboutHook,
|
|
1909
|
+
data: { point: "before_sign_up", changed: ["enabled", "failureMode"], weakened: true }
|
|
1910
|
+
},
|
|
1911
|
+
"hook.deleted": {
|
|
1912
|
+
id: eventId(35),
|
|
1913
|
+
type: "hook.deleted",
|
|
1914
|
+
schemaVersion,
|
|
1915
|
+
occurredAt,
|
|
1916
|
+
actor: admin,
|
|
1917
|
+
target: aboutHook,
|
|
1918
|
+
data: { point: "before_sign_up", weakened: true }
|
|
1919
|
+
},
|
|
1920
|
+
"native_app.created": {
|
|
1921
|
+
id: eventId(36),
|
|
1922
|
+
type: "native_app.created",
|
|
1923
|
+
schemaVersion,
|
|
1924
|
+
occurredAt,
|
|
1925
|
+
actor: admin,
|
|
1926
|
+
target: aboutNativeApp,
|
|
1927
|
+
data: { platform: "android", fingerprints: 1, appLinkPaths: 1, weakened: true }
|
|
1928
|
+
},
|
|
1929
|
+
"native_app.updated": {
|
|
1930
|
+
id: eventId(37),
|
|
1931
|
+
type: "native_app.updated",
|
|
1932
|
+
schemaVersion,
|
|
1933
|
+
occurredAt,
|
|
1934
|
+
actor: admin,
|
|
1935
|
+
target: aboutNativeApp,
|
|
1936
|
+
data: {
|
|
1937
|
+
platform: "android",
|
|
1938
|
+
changed: ["sha256CertFingerprints"],
|
|
1939
|
+
fingerprints: 2,
|
|
1940
|
+
appLinkPaths: 1,
|
|
1941
|
+
weakened: true
|
|
1942
|
+
}
|
|
1943
|
+
},
|
|
1944
|
+
"native_app.deleted": {
|
|
1945
|
+
id: eventId(38),
|
|
1946
|
+
type: "native_app.deleted",
|
|
1947
|
+
schemaVersion,
|
|
1948
|
+
occurredAt,
|
|
1949
|
+
actor: admin,
|
|
1950
|
+
target: aboutNativeApp,
|
|
1951
|
+
data: { platform: "android" }
|
|
1952
|
+
},
|
|
1953
|
+
"session.refresh_proof_refused": {
|
|
1954
|
+
id: eventId(39),
|
|
1955
|
+
type: "session.refresh_proof_refused",
|
|
1956
|
+
schemaVersion,
|
|
1957
|
+
occurredAt,
|
|
1958
|
+
actor: system,
|
|
1959
|
+
target: aboutSession,
|
|
1960
|
+
data: { userId: USER, reason: "wrong_key", suppressedInPreviousMinute: 3 }
|
|
1961
|
+
},
|
|
1962
|
+
"proposal.created": {
|
|
1963
|
+
id: eventId(40),
|
|
1964
|
+
type: "proposal.created",
|
|
1965
|
+
schemaVersion,
|
|
1966
|
+
occurredAt,
|
|
1967
|
+
actor: agent,
|
|
1968
|
+
target: aboutProposal,
|
|
1969
|
+
data: proposed
|
|
1970
|
+
},
|
|
1971
|
+
"proposal.approved": {
|
|
1972
|
+
id: eventId(41),
|
|
1973
|
+
type: "proposal.approved",
|
|
1974
|
+
schemaVersion,
|
|
1975
|
+
occurredAt,
|
|
1976
|
+
actor: admin,
|
|
1977
|
+
target: aboutProposal,
|
|
1978
|
+
data: { ...proposed, proposedBy: AGENT }
|
|
1979
|
+
},
|
|
1980
|
+
"proposal.executed": {
|
|
1981
|
+
id: eventId(42),
|
|
1982
|
+
type: "proposal.executed",
|
|
1983
|
+
schemaVersion,
|
|
1984
|
+
occurredAt,
|
|
1985
|
+
actor: agent,
|
|
1986
|
+
target: aboutProposal,
|
|
1987
|
+
data: { ...proposed, approvedBy: admin, changed: true }
|
|
1988
|
+
},
|
|
1989
|
+
"proposal.failed": {
|
|
1990
|
+
id: eventId(43),
|
|
1991
|
+
type: "proposal.failed",
|
|
1992
|
+
schemaVersion,
|
|
1993
|
+
occurredAt,
|
|
1994
|
+
actor: agent,
|
|
1995
|
+
target: aboutProposal,
|
|
1996
|
+
data: { ...proposed, approvedBy: admin, reason: "target_not_found" }
|
|
1997
|
+
},
|
|
1998
|
+
"sign_in_test.started": {
|
|
1999
|
+
id: eventId(44),
|
|
2000
|
+
type: "sign_in_test.started",
|
|
2001
|
+
schemaVersion,
|
|
2002
|
+
occurredAt,
|
|
2003
|
+
actor: agent,
|
|
2004
|
+
target: aboutEnvironment,
|
|
2005
|
+
data: {}
|
|
2006
|
+
},
|
|
2007
|
+
"sign_in_test.finished": {
|
|
2008
|
+
id: eventId(45),
|
|
2009
|
+
type: "sign_in_test.finished",
|
|
2010
|
+
schemaVersion,
|
|
2011
|
+
occurredAt,
|
|
2012
|
+
actor: agent,
|
|
2013
|
+
target: aboutEnvironment,
|
|
2014
|
+
data: { outcome: "passed", cleanup: "deleted", userId: USER }
|
|
2015
|
+
}
|
|
2016
|
+
};
|
|
2017
|
+
// src/events.ts
|
|
2018
|
+
import { z as z20 } from "zod";
|
|
2019
|
+
|
|
2020
|
+
// src/hook.ts
|
|
2021
|
+
import { z as z16 } from "zod";
|
|
2022
|
+
|
|
2023
|
+
// src/oauth.ts
|
|
2024
|
+
import { z as z14 } from "zod";
|
|
2025
|
+
|
|
2026
|
+
// src/flow.ts
|
|
2027
|
+
import { z as z13 } from "zod";
|
|
2028
|
+
var SecondFactorMethodSchema = z13.enum(["totp", "passkey", "backup_code", "sms_code"]).meta({ ref: "SecondFactorMethod" });
|
|
2029
|
+
var PreparedSecondFactorMethodSchema = z13.enum(["sms_code"]).meta({ ref: "PreparedSecondFactorMethod" });
|
|
2030
|
+
var FirstFactorStrategySchema = z13.enum([
|
|
2031
|
+
"password",
|
|
2032
|
+
"email_code",
|
|
2033
|
+
"email_link",
|
|
2034
|
+
"passkey",
|
|
2035
|
+
"sms_code",
|
|
2036
|
+
"oauth_google",
|
|
2037
|
+
"oauth_github",
|
|
2038
|
+
"oauth_apple",
|
|
2039
|
+
"oauth_microsoft",
|
|
2040
|
+
"oauth_discord",
|
|
2041
|
+
"oauth_linkedin",
|
|
2042
|
+
"oauth_x",
|
|
2043
|
+
"oauth_facebook"
|
|
2044
|
+
]).meta({ ref: "FirstFactorStrategy" });
|
|
2045
|
+
var FactorEnrolmentMethodSchema = z13.enum(["totp"]).meta({ ref: "FactorEnrolmentMethod" });
|
|
2046
|
+
var EmailVerificationStrategySchema = z13.enum(["email_code", "email_link"]).meta({ ref: "EmailVerificationStrategy" });
|
|
2047
|
+
var PreparedFirstFactorStrategySchema = z13.enum(["email_code", "email_link", "sms_code"]).meta({ ref: "PreparedFirstFactorStrategy" });
|
|
2048
|
+
var NewPasswordReasonSchema = z13.enum(["expired"]).meta({ ref: "NewPasswordReason" });
|
|
2049
|
+
var FlowStepSchema = z13.discriminatedUnion("status", [
|
|
2050
|
+
z13.object({ status: z13.literal("needs_identifier") }),
|
|
2051
|
+
z13.object({ status: z13.literal("needs_password") }),
|
|
2052
|
+
z13.object({
|
|
2053
|
+
status: z13.literal("needs_first_factor"),
|
|
2054
|
+
strategies: z13.array(FirstFactorStrategySchema).min(1),
|
|
2055
|
+
prepared: z13.object({ strategy: PreparedFirstFactorStrategySchema, destination: z13.string() }).optional()
|
|
2056
|
+
}),
|
|
2057
|
+
z13.object({
|
|
2058
|
+
status: z13.literal("needs_email_verification"),
|
|
2059
|
+
destination: z13.string(),
|
|
2060
|
+
strategies: z13.array(EmailVerificationStrategySchema).min(1)
|
|
2061
|
+
}),
|
|
2062
|
+
z13.object({
|
|
2063
|
+
status: z13.literal("needs_new_password"),
|
|
2064
|
+
destination: z13.string(),
|
|
2065
|
+
strategies: z13.array(EmailVerificationStrategySchema),
|
|
2066
|
+
reason: NewPasswordReasonSchema.optional()
|
|
2067
|
+
}).refine((step) => step.reason === undefined === step.strategies.length > 0, {
|
|
2068
|
+
message: "strategies is empty exactly when a reason is given",
|
|
2069
|
+
path: ["strategies"]
|
|
2070
|
+
}),
|
|
2071
|
+
z13.object({
|
|
2072
|
+
status: z13.literal("needs_second_factor"),
|
|
2073
|
+
options: z13.array(SecondFactorMethodSchema).min(1),
|
|
2074
|
+
prepared: z13.object({ method: PreparedSecondFactorMethodSchema, destination: z13.string() }).optional()
|
|
2075
|
+
}),
|
|
2076
|
+
z13.object({
|
|
2077
|
+
status: z13.literal("needs_factor_enrolment"),
|
|
2078
|
+
methods: z13.array(FactorEnrolmentMethodSchema).min(1)
|
|
2079
|
+
}),
|
|
2080
|
+
z13.object({
|
|
2081
|
+
status: z13.literal("complete"),
|
|
2082
|
+
userId: z13.string(),
|
|
2083
|
+
sessionId: z13.string()
|
|
2084
|
+
})
|
|
2085
|
+
]).meta({ ref: "FlowStep" });
|
|
2086
|
+
var FlowKindSchema = z13.enum(["sign_in", "sign_up", "password_reset"]).meta({ ref: "FlowKind" });
|
|
2087
|
+
var SessionTokensSchema = z13.object({
|
|
2088
|
+
sessionId: z13.string(),
|
|
2089
|
+
accessToken: z13.string().optional(),
|
|
2090
|
+
accessTokenExpiresAt: z13.iso.datetime().optional(),
|
|
2091
|
+
refreshToken: z13.string().optional()
|
|
2092
|
+
}).meta({ ref: "SessionTokens" });
|
|
2093
|
+
var FlowAttemptSchema = z13.object({
|
|
2094
|
+
id: z13.string(),
|
|
2095
|
+
kind: FlowKindSchema,
|
|
2096
|
+
expiresAt: z13.iso.datetime(),
|
|
2097
|
+
step: FlowStepSchema,
|
|
2098
|
+
attemptSecret: z13.string().optional(),
|
|
2099
|
+
linkBinding: z13.string().optional(),
|
|
2100
|
+
session: SessionTokensSchema.optional(),
|
|
2101
|
+
backupCodes: z13.array(z13.string()).optional(),
|
|
2102
|
+
backupCodesRemaining: z13.number().int().min(0).optional()
|
|
2103
|
+
}).meta({ ref: "FlowAttempt" });
|
|
2104
|
+
var SignUpRequestSchema = z13.object({
|
|
2105
|
+
email: z13.string().max(320),
|
|
2106
|
+
password: z13.string().max(1024).optional(),
|
|
2107
|
+
firstName: z13.string().trim().max(100).optional(),
|
|
2108
|
+
lastName: z13.string().trim().max(100).optional()
|
|
2109
|
+
}).meta({ ref: "SignUpRequest" });
|
|
2110
|
+
var SignInStartRequestSchema = z13.object({ identifier: z13.string().max(320) }).meta({ ref: "SignInStartRequest" });
|
|
2111
|
+
var PasswordAttemptRequestSchema = z13.object({ password: z13.string().max(1024) }).meta({ ref: "PasswordAttemptRequest" });
|
|
2112
|
+
var MAX_LINK_FIELD_LENGTH = 2048;
|
|
2113
|
+
var FirstFactorPrepareRequestSchema = z13.object({
|
|
2114
|
+
strategy: PreparedFirstFactorStrategySchema,
|
|
2115
|
+
redirectUrl: z13.string().max(MAX_LINK_FIELD_LENGTH).optional()
|
|
2116
|
+
}).meta({ ref: "FirstFactorPrepareRequest" });
|
|
2117
|
+
var FirstFactorAttemptRequestSchema = z13.discriminatedUnion("strategy", [
|
|
2118
|
+
z13.object({ strategy: z13.literal("email_code"), code: z13.string().regex(/^\d{6}$/) }),
|
|
2119
|
+
z13.object({ strategy: z13.literal("email_link") }),
|
|
2120
|
+
z13.object({ strategy: z13.literal("sms_code"), code: z13.string().regex(/^\d{6}$/) })
|
|
2121
|
+
]).meta({ ref: "FirstFactorAttemptRequest" });
|
|
2122
|
+
var EmailLinkRequestSchema = z13.object({
|
|
2123
|
+
token: z13.string().min(1).max(MAX_LINK_FIELD_LENGTH),
|
|
2124
|
+
attemptId: z13.uuid(),
|
|
2125
|
+
binding: z13.string().max(MAX_LINK_FIELD_LENGTH).optional()
|
|
2126
|
+
}).meta({ ref: "EmailLinkRequest" });
|
|
2127
|
+
var EmailLinkResultSchema = z13.object({ status: z13.literal("verified") }).meta({ ref: "EmailLinkResult" });
|
|
2128
|
+
var VerifyEmailRequestSchema = z13.object({ code: z13.string().regex(/^\d{6}$/) }).meta({ ref: "VerifyEmailRequest" });
|
|
2129
|
+
var MAX_BACKUP_CODE_INPUT_LENGTH = 64;
|
|
2130
|
+
var SecondFactorRequestSchema = z13.discriminatedUnion("method", [
|
|
2131
|
+
z13.object({ method: z13.literal("totp"), code: z13.string().regex(/^\d{6}$/) }),
|
|
2132
|
+
z13.object({ method: z13.literal("sms_code"), code: z13.string().regex(/^\d{6}$/) }),
|
|
2133
|
+
z13.object({
|
|
2134
|
+
method: z13.literal("backup_code"),
|
|
2135
|
+
code: z13.string().min(1).max(MAX_BACKUP_CODE_INPUT_LENGTH)
|
|
2136
|
+
}),
|
|
2137
|
+
z13.object({ method: z13.literal("passkey"), credential: PasskeyAssertionCredentialSchema })
|
|
2138
|
+
]).meta({ ref: "SecondFactorRequest" });
|
|
2139
|
+
var SecondFactorPrepareRequestSchema = z13.object({ method: PreparedSecondFactorMethodSchema }).meta({ ref: "SecondFactorPrepareRequest" });
|
|
2140
|
+
var PasskeySignInStartSchema = z13.object({ attempt: FlowAttemptSchema, options: PasskeyRequestOptionsSchema }).meta({ ref: "PasskeySignInStart" });
|
|
2141
|
+
var PasswordResetStartRequestSchema = z13.object({ email: z13.string().max(320) }).meta({ ref: "PasswordResetStartRequest" });
|
|
2142
|
+
var PasswordResetRequestSchema = z13.object({ code: z13.string().regex(/^\d{6}$/), password: z13.string().max(1024) }).meta({ ref: "PasswordResetRequest" });
|
|
2143
|
+
var NewPasswordRequestSchema = z13.object({ password: z13.string().max(1024) }).meta({ ref: "NewPasswordRequest" });
|
|
2144
|
+
|
|
2145
|
+
// src/oauth.ts
|
|
2146
|
+
var OAUTH_PROVIDERS = [
|
|
2147
|
+
"google",
|
|
2148
|
+
"github",
|
|
2149
|
+
"apple",
|
|
2150
|
+
"microsoft",
|
|
2151
|
+
"discord",
|
|
2152
|
+
"linkedin",
|
|
2153
|
+
"x",
|
|
2154
|
+
"facebook"
|
|
2155
|
+
];
|
|
2156
|
+
var OAuthProviderSchema = z14.enum(OAUTH_PROVIDERS).meta({ ref: "OAuthProvider" });
|
|
2157
|
+
var OAUTH_PROVIDERS_WITHOUT_ADDRESS = [
|
|
2158
|
+
"x",
|
|
2159
|
+
"facebook"
|
|
2160
|
+
];
|
|
2161
|
+
function givesNoAddress(provider) {
|
|
2162
|
+
return OAUTH_PROVIDERS_WITHOUT_ADDRESS.includes(provider);
|
|
2163
|
+
}
|
|
2164
|
+
var MICROSOFT_TENANT_ALIASES = ["common", "organizations", "consumers"];
|
|
2165
|
+
var GUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
2166
|
+
var MicrosoftTenantSchema = z14.string().trim().toLowerCase().refine((value) => MICROSOFT_TENANT_ALIASES.includes(value) || GUID.test(value), { message: "must be common, organizations, consumers or a tenant id (a GUID)" }).meta({ ref: "MicrosoftTenant" });
|
|
2167
|
+
var ID_TOKEN_PROVIDERS = [
|
|
2168
|
+
"google",
|
|
2169
|
+
"apple"
|
|
2170
|
+
];
|
|
2171
|
+
var ID_TOKEN_CLIENT_KINDS = {
|
|
2172
|
+
google: ["ios", "android"],
|
|
2173
|
+
apple: ["ios"]
|
|
2174
|
+
};
|
|
2175
|
+
function takesAdditionalClientIds(provider) {
|
|
2176
|
+
return provider === "google";
|
|
2177
|
+
}
|
|
2178
|
+
var IdTokenProviderSchema = z14.enum(ID_TOKEN_PROVIDERS).meta({ ref: "IdTokenProvider" });
|
|
2179
|
+
var MAX_ADDITIONAL_CLIENT_IDS = 8;
|
|
2180
|
+
var GOOGLE_CLIENT_ID = /^[0-9]{1,32}(-[0-9a-z]{1,64})?\.apps\.googleusercontent\.com$/;
|
|
2181
|
+
function isGoogleClientId(value) {
|
|
2182
|
+
return value.length <= 128 && GOOGLE_CLIENT_ID.test(value);
|
|
2183
|
+
}
|
|
2184
|
+
var AdditionalClientIdsSchema = z14.array(z14.string().refine(isGoogleClientId, {
|
|
2185
|
+
message: "must be a Google OAuth client id (…apps.googleusercontent.com)"
|
|
2186
|
+
})).max(MAX_ADDITIONAL_CLIENT_IDS).refine((ids) => new Set(ids).size === ids.length, { message: "must not repeat a client id" }).meta({ ref: "AdditionalClientIds" });
|
|
2187
|
+
function ownClientIdAmong(clientId, additionalClientIds) {
|
|
2188
|
+
return additionalClientIds.indexOf(clientId);
|
|
2189
|
+
}
|
|
2190
|
+
var DEPENDENT_ACCOUNTS_SCAN_LIMIT = 1000;
|
|
2191
|
+
var OAuthProviderDependentsSchema = z14.object({
|
|
2192
|
+
provider: OAuthProviderSchema,
|
|
2193
|
+
accounts: z14.number().int().min(0).max(DEPENDENT_ACCOUNTS_SCAN_LIMIT),
|
|
2194
|
+
more: z14.boolean()
|
|
2195
|
+
}).meta({ ref: "OAuthProviderDependents" });
|
|
2196
|
+
function oauthProviderWeakenings(before, after) {
|
|
2197
|
+
const weakened = [];
|
|
2198
|
+
const had = new Set(before?.additionalClientIds ?? []);
|
|
2199
|
+
if ((after?.additionalClientIds ?? []).some((id) => !had.has(id))) {
|
|
2200
|
+
weakened.push("additionalClientIds");
|
|
2201
|
+
}
|
|
2202
|
+
const dependents = before?.dependents;
|
|
2203
|
+
if (before?.enabled === true && (after === null || after.enabled === false) && dependents !== undefined && (dependents.accounts > 0 || dependents.more)) {
|
|
2204
|
+
weakened.push("enabled");
|
|
2205
|
+
}
|
|
2206
|
+
return weakened;
|
|
2207
|
+
}
|
|
2208
|
+
var MAX_ID_TOKEN_LENGTH = 8192;
|
|
2209
|
+
var MAX_ID_TOKEN_NAME_LENGTH = 100;
|
|
2210
|
+
var IdTokenStartRequestSchema = z14.strictObject({ provider: IdTokenProviderSchema }).meta({ ref: "IdTokenStartRequest" });
|
|
2211
|
+
var IdTokenStartSchema = z14.object({ attempt: FlowAttemptSchema, nonce: z14.string() }).meta({ ref: "IdTokenStart" });
|
|
2212
|
+
var IdTokenExchangeRequestSchema = z14.strictObject({
|
|
2213
|
+
idToken: z14.string().min(1).max(MAX_ID_TOKEN_LENGTH),
|
|
2214
|
+
givenName: z14.string().max(MAX_ID_TOKEN_NAME_LENGTH).optional(),
|
|
2215
|
+
familyName: z14.string().max(MAX_ID_TOKEN_NAME_LENGTH).optional()
|
|
2216
|
+
}).meta({ ref: "IdTokenExchangeRequest" });
|
|
2217
|
+
var MAX_OAUTH_FIELD_LENGTH = 2048;
|
|
2218
|
+
var OAuthStartRequestSchema = z14.object({
|
|
2219
|
+
provider: OAuthProviderSchema,
|
|
2220
|
+
redirectUrl: z14.string().max(MAX_OAUTH_FIELD_LENGTH)
|
|
2221
|
+
}).meta({ ref: "OAuthStartRequest" });
|
|
2222
|
+
var OAuthStartSchema = z14.object({
|
|
2223
|
+
attempt: FlowAttemptSchema,
|
|
2224
|
+
authorizationUrl: z14.url(),
|
|
2225
|
+
binding: z14.string()
|
|
2226
|
+
}).meta({ ref: "OAuthStart" });
|
|
2227
|
+
var OAuthExchangeRequestSchema = z14.object({
|
|
2228
|
+
ticket: z14.string().min(1).max(MAX_OAUTH_FIELD_LENGTH),
|
|
2229
|
+
attemptId: z14.uuid(),
|
|
2230
|
+
binding: z14.string().max(MAX_OAUTH_FIELD_LENGTH).optional()
|
|
2231
|
+
}).meta({ ref: "OAuthExchangeRequest" });
|
|
2232
|
+
var IdentitySchema = z14.object({
|
|
2233
|
+
id: z14.string(),
|
|
2234
|
+
provider: z14.string(),
|
|
2235
|
+
createdAt: z14.iso.datetime()
|
|
2236
|
+
}).meta({ ref: "Identity" });
|
|
2237
|
+
var IdentityListSchema = z14.object({ data: z14.array(IdentitySchema) }).meta({ ref: "IdentityList" });
|
|
2238
|
+
var IdentityLinkStartSchema = z14.object({
|
|
2239
|
+
attemptId: z14.string(),
|
|
2240
|
+
expiresAt: z14.iso.datetime(),
|
|
2241
|
+
authorizationUrl: z14.url(),
|
|
2242
|
+
binding: z14.string()
|
|
2243
|
+
}).meta({ ref: "IdentityLinkStart" });
|
|
2244
|
+
var OAuthProviderSettingsSchema = z14.object({
|
|
2245
|
+
provider: OAuthProviderSchema,
|
|
2246
|
+
configured: z14.boolean(),
|
|
2247
|
+
enabled: z14.boolean(),
|
|
2248
|
+
clientId: z14.string().nullable(),
|
|
2249
|
+
teamId: z14.string().nullable(),
|
|
2250
|
+
keyId: z14.string().nullable(),
|
|
2251
|
+
tenant: z14.string().nullable(),
|
|
2252
|
+
additionalClientIds: z14.array(z14.string()).default([]),
|
|
2253
|
+
callbackUrl: z14.url(),
|
|
2254
|
+
updatedAt: z14.iso.datetime().nullable()
|
|
2255
|
+
}).meta({ ref: "OAuthProviderSettings" });
|
|
2256
|
+
var OAuthProviderSettingsListSchema = z14.object({ data: z14.array(OAuthProviderSettingsSchema) }).meta({ ref: "OAuthProviderSettingsList" });
|
|
2257
|
+
var credential = (max) => z14.string().trim().min(1).max(max);
|
|
2258
|
+
var OAuthProviderUpdateSchema = z14.strictObject({
|
|
2259
|
+
clientId: credential(512),
|
|
2260
|
+
clientSecret: credential(2048).optional(),
|
|
2261
|
+
teamId: credential(64).optional(),
|
|
2262
|
+
keyId: credential(64).optional(),
|
|
2263
|
+
privateKey: credential(8192).optional(),
|
|
2264
|
+
tenant: MicrosoftTenantSchema.optional(),
|
|
2265
|
+
additionalClientIds: AdditionalClientIdsSchema.optional(),
|
|
2266
|
+
enabled: z14.boolean().default(true)
|
|
2267
|
+
}).meta({ ref: "OAuthProviderUpdate" });
|
|
2268
|
+
|
|
2269
|
+
// src/webhook.ts
|
|
2270
|
+
import { z as z15 } from "zod";
|
|
2271
|
+
var MAX_WEBHOOK_URL_LENGTH = 2048;
|
|
2272
|
+
var MAX_WEBHOOK_ENDPOINTS = 10;
|
|
2273
|
+
var url = () => z15.string().min(1).max(MAX_WEBHOOK_URL_LENGTH).regex(/^[^\s\u0000-\u001f\u007f]+$/, "Must not contain spaces or control characters.");
|
|
2274
|
+
var eventTypes = () => z15.array(z15.enum(ACTIVITY_TYPES2)).min(1).max(ACTIVITY_TYPES2.length).refine((types) => new Set(types).size === types.length, {
|
|
2275
|
+
message: "An event type is listed more than once."
|
|
2276
|
+
});
|
|
2277
|
+
var WebhookEndpointSchema = z15.object({
|
|
2278
|
+
id: z15.uuid(),
|
|
2279
|
+
url: z15.string(),
|
|
2280
|
+
eventTypes: z15.array(z15.string()),
|
|
2281
|
+
enabled: z15.boolean(),
|
|
2282
|
+
disabledReason: z15.string().nullable(),
|
|
2283
|
+
failingSince: z15.iso.datetime().nullable(),
|
|
2284
|
+
lastFailedAt: z15.iso.datetime().nullable(),
|
|
2285
|
+
rotationOverlapEndsAt: z15.iso.datetime().nullable(),
|
|
2286
|
+
createdAt: z15.iso.datetime(),
|
|
2287
|
+
updatedAt: z15.iso.datetime()
|
|
2288
|
+
}).meta({ ref: "WebhookEndpoint" });
|
|
2289
|
+
var CreatedWebhookEndpointSchema = WebhookEndpointSchema.extend({
|
|
2290
|
+
secret: z15.string().describe("The signing secret. Store it now: it is shown only once.")
|
|
2291
|
+
}).meta({ ref: "CreatedWebhookEndpoint" });
|
|
2292
|
+
var RotatedWebhookSecretSchema = WebhookEndpointSchema.extend({
|
|
2293
|
+
secret: z15.string().describe("The new signing secret. Store it now: it is shown only once."),
|
|
2294
|
+
rotationOverlapEndsAt: z15.iso.datetime().describe("When the previous secret stops signing. Until then deliveries carry both signatures.")
|
|
2295
|
+
}).meta({ ref: "RotatedWebhookSecret" });
|
|
2296
|
+
var WEBHOOK_ROTATION_REFUSALS = [
|
|
2297
|
+
"rotation_in_progress",
|
|
2298
|
+
"no_rotation_in_progress",
|
|
2299
|
+
"secret_unreadable"
|
|
2300
|
+
];
|
|
2301
|
+
var WebhookEndpointListSchema = z15.object({ data: z15.array(WebhookEndpointSchema) }).meta({ ref: "WebhookEndpointList" });
|
|
2302
|
+
var CreateWebhookEndpointRequestSchema = z15.strictObject({
|
|
2303
|
+
url: url(),
|
|
2304
|
+
eventTypes: eventTypes(),
|
|
2305
|
+
enabled: z15.boolean().default(true)
|
|
2306
|
+
}).meta({ ref: "CreateWebhookEndpointRequest" });
|
|
2307
|
+
var UpdateWebhookEndpointRequestSchema = z15.strictObject({
|
|
2308
|
+
url: url().optional(),
|
|
2309
|
+
eventTypes: eventTypes().optional(),
|
|
2310
|
+
enabled: z15.boolean().optional()
|
|
2311
|
+
}).refine((update) => Object.values(update).some((value) => value !== undefined), {
|
|
2312
|
+
message: "Name at least one field to change."
|
|
2313
|
+
}).meta({ ref: "UpdateWebhookEndpointRequest" });
|
|
2314
|
+
var WEBHOOK_REDELIVER_REFUSALS = [
|
|
2315
|
+
"delivery_pending",
|
|
2316
|
+
"endpoint_disabled",
|
|
2317
|
+
"event_gone",
|
|
2318
|
+
"attempt_limit"
|
|
2319
|
+
];
|
|
2320
|
+
var WEBHOOK_DELIVERY_STATES = ["pending", "delivered", "failed"];
|
|
2321
|
+
var WebhookDeliveryAttemptSchema = z15.object({
|
|
2322
|
+
attempt: z15.number().int().min(1),
|
|
2323
|
+
attemptedAt: z15.iso.datetime(),
|
|
2324
|
+
statusCode: z15.number().int().nullable(),
|
|
2325
|
+
durationMs: z15.number().int().min(0),
|
|
2326
|
+
failureReason: z15.string().nullable()
|
|
2327
|
+
}).meta({ ref: "WebhookDeliveryAttempt" });
|
|
2328
|
+
var WebhookDeliverySchema = z15.object({
|
|
2329
|
+
id: z15.uuid(),
|
|
2330
|
+
endpointId: z15.uuid(),
|
|
2331
|
+
eventId: z15.uuid().nullable(),
|
|
2332
|
+
eventType: z15.string(),
|
|
2333
|
+
test: z15.boolean(),
|
|
2334
|
+
state: z15.string(),
|
|
2335
|
+
attemptCount: z15.number().int().min(0),
|
|
2336
|
+
nextAttemptAt: z15.iso.datetime().nullable(),
|
|
2337
|
+
lastAttemptAt: z15.iso.datetime().nullable(),
|
|
2338
|
+
statusCode: z15.number().int().nullable(),
|
|
2339
|
+
failureReason: z15.string().nullable(),
|
|
2340
|
+
completedAt: z15.iso.datetime().nullable(),
|
|
2341
|
+
createdAt: z15.iso.datetime()
|
|
2342
|
+
}).meta({ ref: "WebhookDelivery" });
|
|
2343
|
+
var WebhookDeliveryDetailSchema = WebhookDeliverySchema.extend({
|
|
2344
|
+
attempts: z15.array(WebhookDeliveryAttemptSchema)
|
|
2345
|
+
}).meta({ ref: "WebhookDeliveryDetail" });
|
|
2346
|
+
var WebhookDeliveryListSchema = z15.object({ meta: PaginationMetaSchema, data: z15.array(WebhookDeliverySchema) }).meta({ ref: "WebhookDeliveryList" });
|
|
2347
|
+
var SendTestWebhookRequestSchema = z15.strictObject({ eventType: z15.enum(ACTIVITY_TYPES2) }).meta({ ref: "SendTestWebhookRequest" });
|
|
2348
|
+
var WebhookSendResultSchema = z15.object({
|
|
2349
|
+
deliveryId: z15.uuid(),
|
|
2350
|
+
outcome: z15.enum(["delivered", "failed"]),
|
|
2351
|
+
statusCode: z15.number().int().nullable(),
|
|
2352
|
+
durationMs: z15.number().int().min(0),
|
|
2353
|
+
failureReason: z15.string().nullable()
|
|
2354
|
+
}).meta({ ref: "WebhookSendResult" });
|
|
2355
|
+
|
|
2356
|
+
// src/hook.ts
|
|
2357
|
+
var HOOK_POINTS = ["before_sign_up", "before_session", "before_token"];
|
|
2358
|
+
var HookPointSchema = z16.enum(HOOK_POINTS).meta({ ref: "HookPoint" });
|
|
2359
|
+
var HOOK_QUESTION_TYPES = {
|
|
2360
|
+
before_sign_up: "hook.before_sign_up",
|
|
2361
|
+
before_session: "hook.before_session",
|
|
2362
|
+
before_token: "hook.before_token"
|
|
2363
|
+
};
|
|
2364
|
+
var HOOK_ANSWER_KINDS = {
|
|
2365
|
+
before_sign_up: "decision",
|
|
2366
|
+
before_session: "decision",
|
|
2367
|
+
before_token: "claims"
|
|
2368
|
+
};
|
|
2369
|
+
var HOOK_SCHEMA_VERSION = 1;
|
|
2370
|
+
var HOOK_DEFAULT_DEADLINE_MS = 2000;
|
|
2371
|
+
var HOOK_MIN_DEADLINE_MS = 100;
|
|
2372
|
+
var HOOK_MAX_DEADLINE_MS = 5000;
|
|
2373
|
+
var HOOK_FAILURE_MODES = ["deny", "allow"];
|
|
2374
|
+
var HOOK_FAILURE_REASONS = [
|
|
2375
|
+
"invalid_url",
|
|
2376
|
+
"invalid_request",
|
|
2377
|
+
"scheme_not_allowed",
|
|
2378
|
+
"resolve_failed",
|
|
2379
|
+
"address_not_allowed",
|
|
2380
|
+
"connection_failed",
|
|
2381
|
+
"timeout",
|
|
2382
|
+
"response_too_large",
|
|
2383
|
+
"status_not_ok",
|
|
2384
|
+
"answer_invalid",
|
|
2385
|
+
"secret_unreadable",
|
|
2386
|
+
"claims_invalid",
|
|
2387
|
+
"claims_too_large"
|
|
2388
|
+
];
|
|
2389
|
+
var HOOK_OUTCOMES = ["allowed", "denied", "failed", "timed_out"];
|
|
2390
|
+
var HOOK_OUTCOME_WINDOW_HOURS = 24;
|
|
2391
|
+
function hookFailureOutcome(reason) {
|
|
2392
|
+
return reason === "timeout" ? "timed_out" : "failed";
|
|
2393
|
+
}
|
|
2394
|
+
function hookLastCallFailed(lastOutcome) {
|
|
2395
|
+
return lastOutcome === "failed" || lastOutcome === "timed_out";
|
|
2396
|
+
}
|
|
2397
|
+
var HOOK_FIELDS = ["url", "enabled", "deadlineMs", "failureMode"];
|
|
2398
|
+
var HOOK_MAX_DENIAL_CODE_LENGTH = 64;
|
|
2399
|
+
var HOOK_DENIAL_CODE_PATTERN = /^[a-z0-9_]{1,64}$/;
|
|
2400
|
+
var url2 = () => z16.string().min(1).max(MAX_WEBHOOK_URL_LENGTH).regex(/^[^\s\u0000-\u001f\u007f]+$/, "Must not contain spaces or control characters.");
|
|
2401
|
+
var deadlineMs = () => z16.number().int().min(HOOK_MIN_DEADLINE_MS).max(HOOK_MAX_DEADLINE_MS);
|
|
2402
|
+
var HookRecentOutcomesSchema = z16.object({
|
|
2403
|
+
since: z16.iso.datetime(),
|
|
2404
|
+
allowed: z16.number().int().min(0),
|
|
2405
|
+
denied: z16.number().int().min(0),
|
|
2406
|
+
failed: z16.number().int().min(0),
|
|
2407
|
+
timedOut: z16.number().int().min(0),
|
|
2408
|
+
lastCallAt: z16.iso.datetime().nullable(),
|
|
2409
|
+
lastOutcome: z16.string().nullable()
|
|
2410
|
+
}).meta({ ref: "HookRecentOutcomes" });
|
|
2411
|
+
var HookSchema = z16.object({
|
|
2412
|
+
id: z16.uuid(),
|
|
2413
|
+
point: z16.string(),
|
|
2414
|
+
url: z16.string(),
|
|
2415
|
+
enabled: z16.boolean(),
|
|
2416
|
+
deadlineMs: z16.number().int(),
|
|
2417
|
+
failureMode: z16.string(),
|
|
2418
|
+
lastFailedAt: z16.iso.datetime().nullable(),
|
|
2419
|
+
lastFailureReason: z16.string().nullable(),
|
|
2420
|
+
recentOutcomes: HookRecentOutcomesSchema,
|
|
2421
|
+
createdAt: z16.iso.datetime(),
|
|
2422
|
+
updatedAt: z16.iso.datetime()
|
|
2423
|
+
}).meta({ ref: "Hook" });
|
|
2424
|
+
var CreatedHookSchema = HookSchema.extend({
|
|
2425
|
+
secret: z16.string().describe("The signing secret. Store it now: it is shown only once.")
|
|
2426
|
+
}).meta({ ref: "CreatedHook" });
|
|
2427
|
+
var HookListSchema = z16.object({ data: z16.array(HookSchema) }).meta({ ref: "HookList" });
|
|
2428
|
+
var CreateHookRequestSchema = z16.strictObject({
|
|
2429
|
+
point: z16.enum(HOOK_POINTS),
|
|
2430
|
+
url: url2(),
|
|
2431
|
+
enabled: z16.boolean().default(true),
|
|
2432
|
+
deadlineMs: deadlineMs().default(HOOK_DEFAULT_DEADLINE_MS),
|
|
2433
|
+
failureMode: z16.enum(HOOK_FAILURE_MODES).default("deny")
|
|
2434
|
+
}).meta({ ref: "CreateHookRequest" });
|
|
2435
|
+
var UpdateHookRequestSchema = z16.strictObject({
|
|
2436
|
+
url: url2().optional(),
|
|
2437
|
+
enabled: z16.boolean().optional(),
|
|
2438
|
+
deadlineMs: deadlineMs().optional(),
|
|
2439
|
+
failureMode: z16.enum(HOOK_FAILURE_MODES).optional()
|
|
2440
|
+
}).refine((update) => Object.values(update).some((value) => value !== undefined), {
|
|
2441
|
+
message: "Name at least one field to change."
|
|
2442
|
+
}).meta({ ref: "UpdateHookRequest" });
|
|
2443
|
+
var HOOK_SIGN_UP_METHODS = [
|
|
2444
|
+
"password",
|
|
2445
|
+
"passwordless",
|
|
2446
|
+
...OAUTH_PROVIDERS.map((provider) => `oauth_${provider}`)
|
|
2447
|
+
];
|
|
2448
|
+
var HookBeforeSignUpDataSchema = z16.strictObject({
|
|
2449
|
+
email: z16.string().min(3).max(320).nullable(),
|
|
2450
|
+
method: z16.enum(HOOK_SIGN_UP_METHODS),
|
|
2451
|
+
client: SessionClientSchema,
|
|
2452
|
+
ipAddress: z16.string().max(64).nullable()
|
|
2453
|
+
}).meta({
|
|
2454
|
+
ref: "HookBeforeSignUpData",
|
|
2455
|
+
description: "What a hook is told about a sign-up before the account is created."
|
|
2456
|
+
});
|
|
2457
|
+
var id = () => z16.uuid();
|
|
2458
|
+
var profile = () => z16.string().min(1).max(32);
|
|
2459
|
+
var amr = () => z16.array(z16.string().regex(/^[a-z][a-z0-9_]{0,31}$/)).max(16);
|
|
2460
|
+
var HookBeforeSessionDataSchema = z16.strictObject({
|
|
2461
|
+
userId: id(),
|
|
2462
|
+
client: SessionClientSchema,
|
|
2463
|
+
profile: profile(),
|
|
2464
|
+
amr: amr(),
|
|
2465
|
+
signUp: z16.boolean(),
|
|
2466
|
+
ipAddress: z16.string().max(64).nullable()
|
|
2467
|
+
}).meta({
|
|
2468
|
+
ref: "HookBeforeSessionData",
|
|
2469
|
+
description: "What a hook is told about a sign-in before its session is created."
|
|
2470
|
+
});
|
|
2471
|
+
var HookBeforeTokenDataSchema = z16.strictObject({
|
|
2472
|
+
userId: id(),
|
|
2473
|
+
sessionId: id(),
|
|
2474
|
+
client: SessionClientSchema,
|
|
2475
|
+
profile: profile(),
|
|
2476
|
+
amr: amr()
|
|
2477
|
+
}).meta({
|
|
2478
|
+
ref: "HookBeforeTokenData",
|
|
2479
|
+
description: "What a hook is told about a session before its token is issued."
|
|
2480
|
+
});
|
|
2481
|
+
function question(point, data, meta) {
|
|
2482
|
+
return z16.strictObject({
|
|
2483
|
+
id: z16.uuid(),
|
|
2484
|
+
type: z16.literal(HOOK_QUESTION_TYPES[point]),
|
|
2485
|
+
schemaVersion: z16.literal(HOOK_SCHEMA_VERSION),
|
|
2486
|
+
occurredAt: z16.iso.datetime(),
|
|
2487
|
+
data
|
|
2488
|
+
}).meta(meta);
|
|
2489
|
+
}
|
|
2490
|
+
var HOOK_QUESTION_SCHEMAS = {
|
|
2491
|
+
before_sign_up: question("before_sign_up", HookBeforeSignUpDataSchema, {
|
|
2492
|
+
ref: "HookBeforeSignUpQuestion",
|
|
2493
|
+
description: "What the server posts, signed, to the hook registered for `before_sign_up`, before it creates an account by a sign-up. Not an event: nothing has happened yet."
|
|
2494
|
+
}),
|
|
2495
|
+
before_session: question("before_session", HookBeforeSessionDataSchema, {
|
|
2496
|
+
ref: "HookBeforeSessionQuestion",
|
|
2497
|
+
description: "What the server posts, signed, to the hook registered for `before_session`, after every factor of a sign-in was proven and before it creates the session. Not an event: nothing has happened yet."
|
|
2498
|
+
}),
|
|
2499
|
+
before_token: question("before_token", HookBeforeTokenDataSchema, {
|
|
2500
|
+
ref: "HookBeforeTokenQuestion",
|
|
2501
|
+
description: "What the server posts, signed, to the hook registered for `before_token`, when a session is created and each time its user proves a factor again. The answer is the claims the session’s tokens carry until the next question. Not an event."
|
|
2502
|
+
})
|
|
2503
|
+
};
|
|
2504
|
+
var HookQuestionSchema = z16.discriminatedUnion("type", [
|
|
2505
|
+
HOOK_QUESTION_SCHEMAS.before_sign_up,
|
|
2506
|
+
HOOK_QUESTION_SCHEMAS.before_session,
|
|
2507
|
+
HOOK_QUESTION_SCHEMAS.before_token
|
|
2508
|
+
]).meta({ ref: "HookQuestion" });
|
|
2509
|
+
var HOOK_QUESTION_FIXTURES = {
|
|
2510
|
+
before_sign_up: {
|
|
2511
|
+
id: "0199c2f6-0000-7000-8000-000000000001",
|
|
2512
|
+
type: "hook.before_sign_up",
|
|
2513
|
+
schemaVersion: HOOK_SCHEMA_VERSION,
|
|
2514
|
+
occurredAt: "2026-10-08T09:30:00.000Z",
|
|
2515
|
+
data: {
|
|
2516
|
+
email: "ada@example.com",
|
|
2517
|
+
method: "password",
|
|
2518
|
+
client: "web",
|
|
2519
|
+
ipAddress: "203.0.113.7"
|
|
2520
|
+
}
|
|
2521
|
+
},
|
|
2522
|
+
before_session: {
|
|
2523
|
+
id: "0199c2f6-0000-7000-8000-000000000002",
|
|
2524
|
+
type: "hook.before_session",
|
|
2525
|
+
schemaVersion: HOOK_SCHEMA_VERSION,
|
|
2526
|
+
occurredAt: "2026-10-08T09:30:00.000Z",
|
|
2527
|
+
data: {
|
|
2528
|
+
userId: "0199c2f4-7a10-7c3e-9b1a-5d2e8f4a6c01",
|
|
2529
|
+
client: "web",
|
|
2530
|
+
profile: "web",
|
|
2531
|
+
amr: ["pwd", "otp", "mfa"],
|
|
2532
|
+
signUp: false,
|
|
2533
|
+
ipAddress: "203.0.113.7"
|
|
2534
|
+
}
|
|
2535
|
+
},
|
|
2536
|
+
before_token: {
|
|
2537
|
+
id: "0199c2f6-0000-7000-8000-000000000003",
|
|
2538
|
+
type: "hook.before_token",
|
|
2539
|
+
schemaVersion: HOOK_SCHEMA_VERSION,
|
|
2540
|
+
occurredAt: "2026-10-08T09:30:00.000Z",
|
|
2541
|
+
data: {
|
|
2542
|
+
userId: "0199c2f4-7a10-7c3e-9b1a-5d2e8f4a6c01",
|
|
2543
|
+
sessionId: "0199c2f4-7a12-7d4f-8c2b-6e3f9a5b7d02",
|
|
2544
|
+
client: "web",
|
|
2545
|
+
profile: "web",
|
|
2546
|
+
amr: ["pwd", "otp", "mfa"]
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
};
|
|
2550
|
+
var HookAnswerSchema = z16.discriminatedUnion("decision", [
|
|
2551
|
+
z16.strictObject({ decision: z16.literal("allow") }),
|
|
2552
|
+
z16.strictObject({
|
|
2553
|
+
decision: z16.literal("deny"),
|
|
2554
|
+
code: z16.string().regex(HOOK_DENIAL_CODE_PATTERN).optional()
|
|
2555
|
+
})
|
|
2556
|
+
]).meta({ ref: "HookAnswer" });
|
|
2557
|
+
var HookClaimsAnswerSchema = z16.strictObject({
|
|
2558
|
+
claims: z16.record(z16.string(), z16.union([z16.string(), z16.number(), z16.boolean()])).refine((claims) => ("claims" in checkCustomClaims2(claims)), {
|
|
2559
|
+
message: `Every key must be a custom claim key that is not reserved, every value one string, number or boolean, and the whole at most ${MAX_CUSTOM_CLAIMS_BYTES2} bytes as JSON.`
|
|
2560
|
+
})
|
|
2561
|
+
}).meta({ ref: "HookClaimsAnswer" });
|
|
2562
|
+
function readHookClaimsAnswer(body) {
|
|
2563
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) {
|
|
2564
|
+
return { problem: "answer_invalid" };
|
|
2565
|
+
}
|
|
2566
|
+
const keys = Object.keys(body);
|
|
2567
|
+
if (keys.length !== 1 || keys[0] !== "claims") {
|
|
2568
|
+
return { problem: "answer_invalid" };
|
|
2569
|
+
}
|
|
2570
|
+
const { claims } = body;
|
|
2571
|
+
if (typeof claims !== "object" || claims === null || Array.isArray(claims)) {
|
|
2572
|
+
return { problem: "answer_invalid" };
|
|
2573
|
+
}
|
|
2574
|
+
const checked = checkCustomClaims2(claims);
|
|
2575
|
+
if ("claims" in checked) {
|
|
2576
|
+
return checked;
|
|
2577
|
+
}
|
|
2578
|
+
return { problem: checked.problem === "too_large" ? "claims_too_large" : "claims_invalid" };
|
|
2579
|
+
}
|
|
2580
|
+
function hookWeakenings(was, is) {
|
|
2581
|
+
const weakened = [];
|
|
2582
|
+
if (was?.enabled && !is?.enabled) {
|
|
2583
|
+
weakened.push("enabled");
|
|
2584
|
+
}
|
|
2585
|
+
if (is?.failureMode === "allow" && was?.failureMode !== "allow") {
|
|
2586
|
+
weakened.push("failureMode");
|
|
2587
|
+
}
|
|
2588
|
+
return weakened;
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
// src/native-app.ts
|
|
2592
|
+
import { z as z17 } from "zod";
|
|
2593
|
+
var NATIVE_APP_PLATFORMS = ["ios", "android"];
|
|
2594
|
+
var MAX_NATIVE_APPS = 20;
|
|
2595
|
+
var MAX_CERT_FINGERPRINTS = 10;
|
|
2596
|
+
var MAX_BUNDLE_ID_LENGTH = 155;
|
|
2597
|
+
var MAX_PACKAGE_NAME_LENGTH = 255;
|
|
2598
|
+
var MAX_APP_LINK_PATHS = 10;
|
|
2599
|
+
var MAX_APP_LINK_PATH_LENGTH = 255;
|
|
2600
|
+
var APP_LINK_PATH_PATTERN = /^(?:\/[A-Za-z0-9._~-]+)+$/;
|
|
2601
|
+
function isAppLinkPath(path) {
|
|
2602
|
+
return path.length <= MAX_APP_LINK_PATH_LENGTH && APP_LINK_PATH_PATTERN.test(path) && !path.split("/").some((segment) => segment === "." || segment === "..");
|
|
2603
|
+
}
|
|
2604
|
+
function normalizeAppLinkPaths(paths) {
|
|
2605
|
+
return [...new Set(paths.filter(isAppLinkPath))].sort();
|
|
2606
|
+
}
|
|
2607
|
+
var APPLE_TEAM_ID_PATTERN = /^[A-Z0-9]{10}$/;
|
|
2608
|
+
var BUNDLE_ID_PATTERN = /^[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+$/;
|
|
2609
|
+
var PACKAGE_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_]*(?:\.[A-Za-z][A-Za-z0-9_]*)+$/;
|
|
2610
|
+
var CERT_FINGERPRINT_PATTERN = /^(?:[0-9A-F]{2}:){31}[0-9A-F]{2}$/;
|
|
2611
|
+
var CERT_FINGERPRINT_INPUT_PATTERN = /^(?:(?:[0-9A-Fa-f]{2}:){31}[0-9A-Fa-f]{2}|[0-9A-Fa-f]{64})$/;
|
|
2612
|
+
function normalizeCertFingerprint(input) {
|
|
2613
|
+
if (!CERT_FINGERPRINT_INPUT_PATTERN.test(input)) {
|
|
2614
|
+
return null;
|
|
2615
|
+
}
|
|
2616
|
+
const hex = input.replaceAll(":", "").toUpperCase();
|
|
2617
|
+
return (hex.match(/.{2}/g) ?? []).join(":");
|
|
2618
|
+
}
|
|
2619
|
+
function normalizeCertFingerprints(fingerprints) {
|
|
2620
|
+
const set = new Set;
|
|
2621
|
+
for (const fingerprint of fingerprints) {
|
|
2622
|
+
const normal = normalizeCertFingerprint(fingerprint);
|
|
2623
|
+
if (normal) {
|
|
2624
|
+
set.add(normal);
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
return [...set].sort();
|
|
2628
|
+
}
|
|
2629
|
+
var ANDROID_APK_KEY_HASH_PREFIX = "android:apk-key-hash:";
|
|
2630
|
+
function androidApkKeyHashOrigin(fingerprint) {
|
|
2631
|
+
const normal = normalizeCertFingerprint(fingerprint);
|
|
2632
|
+
if (normal === null) {
|
|
2633
|
+
return null;
|
|
2634
|
+
}
|
|
2635
|
+
const bytes = normal.split(":").map((pair) => Number.parseInt(pair, 16));
|
|
2636
|
+
const base64 = btoa(String.fromCharCode(...bytes));
|
|
2637
|
+
const base64url = base64.replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
|
2638
|
+
return `${ANDROID_APK_KEY_HASH_PREFIX}${base64url}`;
|
|
2639
|
+
}
|
|
2640
|
+
var teamId = () => z17.string().regex(APPLE_TEAM_ID_PATTERN, "Must be the ten upper-case letters and digits of an Apple team.");
|
|
2641
|
+
var bundleId = () => z17.string().max(MAX_BUNDLE_ID_LENGTH).regex(BUNDLE_ID_PATTERN, "Must be a bundle id such as com.example.app: letters, digits and hyphens in segments joined by periods.");
|
|
2642
|
+
var packageName = () => z17.string().max(MAX_PACKAGE_NAME_LENGTH).regex(PACKAGE_NAME_PATTERN, "Must be a package name such as com.example.app: two or more segments joined by periods, each starting with a letter.");
|
|
2643
|
+
var fingerprints = () => z17.array(z17.string().max(95).regex(CERT_FINGERPRINT_INPUT_PATTERN, "Must be a SHA-256 fingerprint: 32 bytes as hex, with or without colons.")).min(1).max(MAX_CERT_FINGERPRINTS).refine((list) => normalizeCertFingerprints(list).length === list.length, {
|
|
2644
|
+
message: "Name each fingerprint once."
|
|
2645
|
+
});
|
|
2646
|
+
var appLinkPaths = () => z17.array(z17.string().max(MAX_APP_LINK_PATH_LENGTH).refine(isAppLinkPath, {
|
|
2647
|
+
message: "Must be one exact path such as /oauth/callback: letters, digits and . _ ~ - in segments after a slash, with no wildcard, query or trailing slash."
|
|
2648
|
+
})).max(MAX_APP_LINK_PATHS).refine((list) => new Set(list).size === list.length, { message: "Name each path once." });
|
|
2649
|
+
var NATIVE_APP_FIELDS = ["teamId", "sha256CertFingerprints", "appLinkPaths"];
|
|
2650
|
+
var stamps = { id: z17.uuid(), createdAt: z17.iso.datetime(), updatedAt: z17.iso.datetime() };
|
|
2651
|
+
var IosAppSchema = z17.object({
|
|
2652
|
+
...stamps,
|
|
2653
|
+
platform: z17.literal("ios"),
|
|
2654
|
+
teamId: z17.string(),
|
|
2655
|
+
bundleId: z17.string(),
|
|
2656
|
+
appLinkPaths: z17.array(z17.string())
|
|
2657
|
+
}).meta({ ref: "IosApp" });
|
|
2658
|
+
var AndroidAppSchema = z17.object({
|
|
2659
|
+
...stamps,
|
|
2660
|
+
platform: z17.literal("android"),
|
|
2661
|
+
packageName: z17.string(),
|
|
2662
|
+
sha256CertFingerprints: z17.array(z17.string()),
|
|
2663
|
+
appLinkPaths: z17.array(z17.string())
|
|
2664
|
+
}).meta({ ref: "AndroidApp" });
|
|
2665
|
+
var NativeAppSchema = z17.discriminatedUnion("platform", [IosAppSchema, AndroidAppSchema]).meta({ ref: "NativeApp" });
|
|
2666
|
+
var NativeAppListSchema = z17.object({ data: z17.array(NativeAppSchema) }).meta({ ref: "NativeAppList" });
|
|
2667
|
+
var IosAppIdentitySchema = z17.strictObject({
|
|
2668
|
+
platform: z17.literal("ios"),
|
|
2669
|
+
teamId: teamId(),
|
|
2670
|
+
bundleId: bundleId(),
|
|
2671
|
+
appLinkPaths: appLinkPaths().optional()
|
|
2672
|
+
}).meta({ ref: "IosAppIdentity" });
|
|
2673
|
+
var AndroidAppIdentitySchema = z17.strictObject({
|
|
2674
|
+
platform: z17.literal("android"),
|
|
2675
|
+
packageName: packageName(),
|
|
2676
|
+
sha256CertFingerprints: fingerprints(),
|
|
2677
|
+
appLinkPaths: appLinkPaths().optional()
|
|
2678
|
+
}).meta({ ref: "AndroidAppIdentity" });
|
|
2679
|
+
var CreateNativeAppRequestSchema = z17.discriminatedUnion("platform", [IosAppIdentitySchema, AndroidAppIdentitySchema]).meta({ ref: "CreateNativeAppRequest" });
|
|
2680
|
+
var UpdateNativeAppRequestSchema = z17.strictObject({
|
|
2681
|
+
teamId: teamId().optional(),
|
|
2682
|
+
sha256CertFingerprints: fingerprints().optional(),
|
|
2683
|
+
appLinkPaths: appLinkPaths().optional()
|
|
2684
|
+
}).refine((update) => Object.values(update).some((value) => value !== undefined), {
|
|
2685
|
+
message: "Name at least one field to change."
|
|
2686
|
+
}).meta({ ref: "UpdateNativeAppRequest" });
|
|
2687
|
+
var ASSET_LINKS_RELATIONS = ["delegate_permission/common.get_login_creds"];
|
|
2688
|
+
var ASSET_LINKS_APP_LINK_RELATION = "delegate_permission/common.handle_all_urls";
|
|
2689
|
+
var AppleAppSiteAssociationSchema = z17.strictObject({
|
|
2690
|
+
webcredentials: z17.strictObject({
|
|
2691
|
+
apps: z17.array(z17.string()).min(1)
|
|
2692
|
+
}).optional(),
|
|
2693
|
+
applinks: z17.strictObject({
|
|
2694
|
+
details: z17.array(z17.strictObject({
|
|
2695
|
+
appIDs: z17.array(z17.string()).length(1),
|
|
2696
|
+
components: z17.array(z17.strictObject({ "/": z17.string() })).min(1)
|
|
2697
|
+
})).min(1)
|
|
2698
|
+
}).optional()
|
|
2699
|
+
}).meta({ ref: "AppleAppSiteAssociation" });
|
|
2700
|
+
var AssetLinksSchema = z17.array(z17.strictObject({
|
|
2701
|
+
relation: z17.array(z17.string()).min(1),
|
|
2702
|
+
target: z17.strictObject({
|
|
2703
|
+
namespace: z17.literal("android_app"),
|
|
2704
|
+
package_name: z17.string(),
|
|
2705
|
+
sha256_cert_fingerprints: z17.array(z17.string()).min(1)
|
|
2706
|
+
})
|
|
2707
|
+
})).meta({ ref: "AssetLinks" });
|
|
2708
|
+
function nativeAppIdentifier(app) {
|
|
2709
|
+
return app.platform === "ios" ? app.bundleId : app.packageName;
|
|
2710
|
+
}
|
|
2711
|
+
function appleAppSiteAssociation(apps) {
|
|
2712
|
+
const ios = apps.filter((app) => app.platform === "ios").map((app) => ({
|
|
2713
|
+
id: `${app.teamId}.${app.bundleId}`,
|
|
2714
|
+
paths: normalizeAppLinkPaths(app.appLinkPaths ?? [])
|
|
2715
|
+
})).sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
2716
|
+
if (ios.length === 0) {
|
|
2717
|
+
return {};
|
|
2718
|
+
}
|
|
2719
|
+
const details = ios.filter((app) => app.paths.length > 0).map((app) => ({ appIDs: [app.id], components: app.paths.map((path) => ({ "/": path })) }));
|
|
2720
|
+
return {
|
|
2721
|
+
webcredentials: { apps: ios.map((app) => app.id) },
|
|
2722
|
+
...details.length > 0 && { applinks: { details } }
|
|
2723
|
+
};
|
|
2724
|
+
}
|
|
2725
|
+
function assetLinks(apps) {
|
|
2726
|
+
return apps.filter((app) => app.platform === "android").sort((a, b) => a.packageName < b.packageName ? -1 : a.packageName > b.packageName ? 1 : 0).map((app) => ({
|
|
2727
|
+
relation: [
|
|
2728
|
+
...ASSET_LINKS_RELATIONS,
|
|
2729
|
+
...normalizeAppLinkPaths(app.appLinkPaths ?? []).length > 0 ? [ASSET_LINKS_APP_LINK_RELATION] : []
|
|
2730
|
+
],
|
|
2731
|
+
target: {
|
|
2732
|
+
namespace: "android_app",
|
|
2733
|
+
package_name: app.packageName,
|
|
2734
|
+
sha256_cert_fingerprints: [...app.sha256CertFingerprints]
|
|
2735
|
+
}
|
|
2736
|
+
}));
|
|
2737
|
+
}
|
|
2738
|
+
function gainedAppLinkPath(was, is) {
|
|
2739
|
+
const before = new Set(normalizeAppLinkPaths(was?.appLinkPaths ?? []));
|
|
2740
|
+
return normalizeAppLinkPaths(is.appLinkPaths ?? []).some((path) => !before.has(path));
|
|
2741
|
+
}
|
|
2742
|
+
function nativeAppWeakenings(was, is) {
|
|
2743
|
+
if (!is) {
|
|
2744
|
+
return [];
|
|
2745
|
+
}
|
|
2746
|
+
const paths = gainedAppLinkPath(was, is) ? ["appLinkPaths"] : [];
|
|
2747
|
+
if (!was) {
|
|
2748
|
+
return ["app", ...paths];
|
|
2749
|
+
}
|
|
2750
|
+
if (was.platform === "ios" && is.platform === "ios") {
|
|
2751
|
+
return [...was.teamId === is.teamId ? [] : ["teamId"], ...paths];
|
|
2752
|
+
}
|
|
2753
|
+
if (was.platform === "android" && is.platform === "android") {
|
|
2754
|
+
const before = new Set(normalizeCertFingerprints(was.sha256CertFingerprints));
|
|
2755
|
+
const gained = normalizeCertFingerprints(is.sha256CertFingerprints).some((fingerprint) => !before.has(fingerprint));
|
|
2756
|
+
return [...gained ? ["sha256CertFingerprints"] : [], ...paths];
|
|
2757
|
+
}
|
|
2758
|
+
return ["app"];
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2761
|
+
// src/proposal.ts
|
|
2762
|
+
import { z as z18 } from "zod";
|
|
2763
|
+
var PROPOSAL_OPERATIONS = [
|
|
2764
|
+
"ban_user",
|
|
2765
|
+
"unban_user",
|
|
2766
|
+
"end_session",
|
|
2767
|
+
"disable_oauth_provider"
|
|
2768
|
+
];
|
|
2769
|
+
var PROPOSAL_TARGET_TYPES = ["user", "session", "oauth_provider"];
|
|
2770
|
+
var PROPOSAL_OPERATION_TARGETS = {
|
|
2771
|
+
ban_user: "user",
|
|
2772
|
+
unban_user: "user",
|
|
2773
|
+
end_session: "session",
|
|
2774
|
+
disable_oauth_provider: "oauth_provider"
|
|
2775
|
+
};
|
|
2776
|
+
var PROPOSAL_STATUSES = ["pending", "expired", "approved", "executed", "failed"];
|
|
2777
|
+
var PROPOSAL_FAILURE_REASONS = [
|
|
2778
|
+
"target_not_found",
|
|
2779
|
+
"error",
|
|
2780
|
+
"incomplete",
|
|
2781
|
+
"last_sign_in_method"
|
|
2782
|
+
];
|
|
2783
|
+
var PROPOSAL_EXPIRY_REASONS = ["time", "agent_key_revoked"];
|
|
2784
|
+
var PROPOSAL_APPROVAL_REFUSALS = [
|
|
2785
|
+
"expired",
|
|
2786
|
+
"already_decided",
|
|
2787
|
+
"mismatch",
|
|
2788
|
+
"agent_key_revoked",
|
|
2789
|
+
"production_environment"
|
|
2790
|
+
];
|
|
2791
|
+
var PROPOSAL_CREATION_REFUSALS = ["production_environment"];
|
|
2792
|
+
var PROPOSAL_APPROVER_TYPES = ["admin", "instance_admin"];
|
|
2793
|
+
var ProposalOperationSchema = z18.enum(PROPOSAL_OPERATIONS).meta({ ref: "ProposalOperation" });
|
|
2794
|
+
var ProposalStatusSchema = z18.enum(PROPOSAL_STATUSES).meta({ ref: "ProposalStatus" });
|
|
2795
|
+
function isProposalTargetId(type, id) {
|
|
2796
|
+
const providers = OAUTH_PROVIDERS;
|
|
2797
|
+
if (type === "oauth_provider") {
|
|
2798
|
+
return providers.includes(id);
|
|
2799
|
+
}
|
|
2800
|
+
return id === id.toLowerCase() && z18.uuid().safeParse(id).success;
|
|
2801
|
+
}
|
|
2802
|
+
var ProposalTargetSchema = z18.strictObject({
|
|
2803
|
+
type: z18.enum(PROPOSAL_TARGET_TYPES),
|
|
2804
|
+
id: z18.union([z18.uuid(), z18.enum(OAUTH_PROVIDERS)])
|
|
2805
|
+
}).refine((target) => isProposalTargetId(target.type, target.id), {
|
|
2806
|
+
path: ["id"],
|
|
2807
|
+
message: "must name a target of that type"
|
|
2808
|
+
}).meta({ ref: "ProposalTarget" });
|
|
2809
|
+
var ProposalApproverSchema = z18.object({
|
|
2810
|
+
type: z18.enum(PROPOSAL_APPROVER_TYPES),
|
|
2811
|
+
id: z18.string().min(1).max(128).nullable()
|
|
2812
|
+
}).meta({ ref: "ProposalApprover" });
|
|
2813
|
+
function targetIsOfOperation(request) {
|
|
2814
|
+
return PROPOSAL_OPERATION_TARGETS[request.operation] === request.target.type;
|
|
2815
|
+
}
|
|
2816
|
+
var TARGET_OF_OPERATION = {
|
|
2817
|
+
path: ["target", "type"],
|
|
2818
|
+
message: "must be the kind of target the operation acts on"
|
|
2819
|
+
};
|
|
2820
|
+
var CreateProposalRequestSchema = z18.strictObject({ operation: ProposalOperationSchema, target: ProposalTargetSchema }).refine(targetIsOfOperation, TARGET_OF_OPERATION).meta({ ref: "CreateProposalRequest" });
|
|
2821
|
+
var ApproveProposalRequestSchema = z18.strictObject({ operation: ProposalOperationSchema, target: ProposalTargetSchema }).refine(targetIsOfOperation, TARGET_OF_OPERATION).meta({ ref: "ApproveProposalRequest" });
|
|
2822
|
+
var ProposalSchema = z18.object({
|
|
2823
|
+
id: z18.uuid(),
|
|
2824
|
+
operation: ProposalOperationSchema,
|
|
2825
|
+
target: ProposalTargetSchema,
|
|
2826
|
+
summary: z18.string(),
|
|
2827
|
+
status: ProposalStatusSchema,
|
|
2828
|
+
proposedBy: z18.object({ type: z18.literal("agent"), id: z18.uuid() }),
|
|
2829
|
+
createdAt: z18.iso.datetime(),
|
|
2830
|
+
expiresAt: z18.iso.datetime(),
|
|
2831
|
+
approvedBy: ProposalApproverSchema.nullable(),
|
|
2832
|
+
approvedAt: z18.iso.datetime().nullable(),
|
|
2833
|
+
failureReason: z18.enum(PROPOSAL_FAILURE_REASONS).nullable(),
|
|
2834
|
+
expiryReason: z18.enum(PROPOSAL_EXPIRY_REASONS).nullable(),
|
|
2835
|
+
changed: z18.boolean().optional()
|
|
2836
|
+
}).meta({ ref: "Proposal" });
|
|
2837
|
+
var ProposalListSchema = z18.object({ data: z18.array(ProposalSchema) }).meta({ ref: "ProposalList" });
|
|
2838
|
+
var SUMMARIES = {
|
|
2839
|
+
ban_user: (id) => `Ban the user ${id}: every session of theirs ends now, and they cannot sign in until an administrator lifts the ban.`,
|
|
2840
|
+
unban_user: (id) => `Lift the ban on the user ${id}: they can sign in again. The sessions that ended when they were banned stay ended.`,
|
|
2841
|
+
end_session: (id) => `End the session ${id}: whoever holds it is signed out now and has to sign in again. The user’s other sessions and the account are left as they are.`,
|
|
2842
|
+
disable_oauth_provider: (id) => `Switch off sign-in with the provider ${id}: nobody can sign in or sign up with it until an administrator switches it on again. Its credentials are kept and nothing is removed. It is refused if it would leave the environment with no way to sign in.`
|
|
2843
|
+
};
|
|
2844
|
+
function proposalSummary(operation, target) {
|
|
2845
|
+
if (PROPOSAL_OPERATION_TARGETS[operation] !== target.type || !isProposalTargetId(target.type, target.id)) {
|
|
2846
|
+
throw new TypeError("a proposal’s target is named by its id");
|
|
2847
|
+
}
|
|
2848
|
+
return SUMMARIES[operation](target.id);
|
|
2849
|
+
}
|
|
2850
|
+
|
|
2851
|
+
// src/test-address.ts
|
|
2852
|
+
var TEST_ADDRESS_DOMAIN = "test.tula.invalid";
|
|
2853
|
+
var TEST_ADDRESS_LOCAL_PREFIX = "tula-test-";
|
|
2854
|
+
function isTestAddress(address) {
|
|
2855
|
+
const at = address.lastIndexOf("@");
|
|
2856
|
+
if (at < 0) {
|
|
2857
|
+
return false;
|
|
2858
|
+
}
|
|
2859
|
+
const domain = address.slice(at + 1).trim().toLowerCase().replace(/\.$/, "");
|
|
2860
|
+
return domain === TEST_ADDRESS_DOMAIN || domain.endsWith(`.${TEST_ADDRESS_DOMAIN}`);
|
|
2861
|
+
}
|
|
2862
|
+
function testAddress(hex) {
|
|
2863
|
+
if (!/^[0-9a-f]{32}$/.test(hex)) {
|
|
2864
|
+
throw new Error("a test address is made from 32 hexadecimal characters");
|
|
2865
|
+
}
|
|
2866
|
+
return `${TEST_ADDRESS_LOCAL_PREFIX}${hex}@${TEST_ADDRESS_DOMAIN}`;
|
|
2867
|
+
}
|
|
2868
|
+
var SIGN_IN_TEST_STEPS = [
|
|
2869
|
+
"sign_up",
|
|
2870
|
+
"verify_email",
|
|
2871
|
+
"sign_out",
|
|
2872
|
+
"sign_in",
|
|
2873
|
+
"first_factor",
|
|
2874
|
+
"revoke_session",
|
|
2875
|
+
"delete_user"
|
|
2876
|
+
];
|
|
2877
|
+
var SIGN_IN_TEST_STEP_OUTCOMES = ["passed", "failed", "not_run"];
|
|
2878
|
+
var SIGN_IN_TEST_REASONS = [
|
|
2879
|
+
"earlier_step",
|
|
2880
|
+
"second_factor_required",
|
|
2881
|
+
"email_cooldown",
|
|
2882
|
+
"no_provable_method",
|
|
2883
|
+
"no_session",
|
|
2884
|
+
"timeout",
|
|
2885
|
+
"cancelled"
|
|
2886
|
+
];
|
|
2887
|
+
var SIGN_IN_TEST_OUTCOMES = ["passed", "failed", "incomplete"];
|
|
2888
|
+
var SIGN_IN_TEST_CLEANUPS = ["deleted", "failed", "none"];
|
|
2889
|
+
var SIGN_IN_TEST_REFUSALS = ["test_sign_in_disabled"];
|
|
2890
|
+
function testSignInAllowed(setting, kind) {
|
|
2891
|
+
return setting ?? kind !== "production";
|
|
2892
|
+
}
|
|
2893
|
+
|
|
2894
|
+
// src/tokens.ts
|
|
2895
|
+
import { z as z19 } from "zod";
|
|
2896
|
+
var ACCESS_TOKEN_VERSION = 1;
|
|
2897
|
+
var ACCESS_TOKEN_ALGORITHM = "EdDSA";
|
|
2898
|
+
var PUBLISHABLE_KEY_PREFIX = "tula_pk_";
|
|
2899
|
+
var SECRET_KEY_PREFIX = "tula_sk_";
|
|
2900
|
+
var AGENT_KEY_PREFIX = "tula_ak_";
|
|
2901
|
+
var AUTHENTICATION_METHODS = [
|
|
2902
|
+
"pwd",
|
|
2903
|
+
"email",
|
|
2904
|
+
"sms",
|
|
2905
|
+
"otp",
|
|
2906
|
+
"backup_code",
|
|
2907
|
+
"mfa",
|
|
2908
|
+
"hwk",
|
|
2909
|
+
"swk",
|
|
2910
|
+
"user"
|
|
2911
|
+
];
|
|
2912
|
+
var STEP_UP_MAX_AGE_SECONDS = 600;
|
|
2913
|
+
var AccessTokenClaimsSchema = z19.object({
|
|
2914
|
+
iss: z19.string(),
|
|
2915
|
+
sub: z19.string(),
|
|
2916
|
+
aud: z19.string(),
|
|
2917
|
+
sid: z19.string(),
|
|
2918
|
+
pid: z19.string(),
|
|
2919
|
+
eid: z19.string(),
|
|
2920
|
+
iat: z19.number().int(),
|
|
2921
|
+
exp: z19.number().int(),
|
|
2922
|
+
v: z19.literal(ACCESS_TOKEN_VERSION),
|
|
2923
|
+
auth_time: z19.number().int().optional(),
|
|
2924
|
+
amr: z19.array(z19.string()).optional(),
|
|
2925
|
+
sp: z19.string().optional(),
|
|
2926
|
+
cnf: z19.object({ jkt: z19.string() }).optional(),
|
|
2927
|
+
[CUSTOM_CLAIMS_CLAIM2]: z19.record(z19.string(), z19.union([z19.string(), z19.number(), z19.boolean()])).optional()
|
|
2928
|
+
}).meta({ ref: "AccessTokenClaims" });
|
|
2929
|
+
var JwkSchema = z19.object({
|
|
2930
|
+
kty: z19.literal("OKP"),
|
|
2931
|
+
crv: z19.literal("Ed25519"),
|
|
2932
|
+
x: z19.string(),
|
|
2933
|
+
kid: z19.string(),
|
|
2934
|
+
alg: z19.literal(ACCESS_TOKEN_ALGORITHM),
|
|
2935
|
+
use: z19.literal("sig")
|
|
2936
|
+
}).meta({ ref: "Jwk" });
|
|
2937
|
+
var JwksSchema = z19.object({ keys: z19.array(JwkSchema) }).meta({ ref: "Jwks" });
|
|
2938
|
+
|
|
2939
|
+
// src/events.ts
|
|
2940
|
+
var id2 = () => z20.uuid();
|
|
2941
|
+
function data(name, description, shape) {
|
|
2942
|
+
return z20.object(shape).meta({ ref: `${name}EventData`, description });
|
|
2943
|
+
}
|
|
2944
|
+
function proposalIsWhole(change) {
|
|
2945
|
+
return change.proposalId === undefined === (change.approvedBy === undefined);
|
|
2946
|
+
}
|
|
2947
|
+
var PROPOSAL_IS_WHOLE = { message: "`proposalId` and `approvedBy` go together" };
|
|
2948
|
+
var carriedOut = () => ({
|
|
2949
|
+
proposalId: id2().optional(),
|
|
2950
|
+
approvedBy: ProposalApproverSchema.optional()
|
|
2951
|
+
});
|
|
2952
|
+
function passkeyRemovalIsWhole(removal) {
|
|
2953
|
+
const named = removal.passkeyId !== undefined;
|
|
2954
|
+
const outcome = removal.canStillSignIn !== undefined;
|
|
2955
|
+
return removal.method === "user" ? named && !outcome : outcome && !named;
|
|
2956
|
+
}
|
|
2957
|
+
var provider = OAuthProviderSchema;
|
|
2958
|
+
var dependentAccounts = z20.number().int().min(0).max(DEPENDENT_ACCOUNTS_SCAN_LIMIT);
|
|
2959
|
+
var proposed2 = {
|
|
2960
|
+
operation: z20.enum(PROPOSAL_OPERATIONS),
|
|
2961
|
+
targetType: z20.enum(PROPOSAL_TARGET_TYPES),
|
|
2962
|
+
targetId: z20.union([id2(), provider])
|
|
2963
|
+
};
|
|
2964
|
+
var WEBHOOK_ENDPOINT_FIELDS = ["url", "eventTypes", "enabled"];
|
|
2965
|
+
var WEBHOOK_DISABLED_REASONS = ["failing", "gone"];
|
|
2966
|
+
var MAX_CHANGED_SETTINGS = 256;
|
|
2967
|
+
var MAX_SETTING_NAME_LENGTH = 128;
|
|
2968
|
+
var EVENT_DATA_SCHEMAS = {
|
|
2969
|
+
"user.created": data("UserCreated", "A user account was created.", {
|
|
2970
|
+
method: z20.enum([
|
|
2971
|
+
"admin",
|
|
2972
|
+
"sign_up",
|
|
2973
|
+
"oauth_google",
|
|
2974
|
+
"oauth_github",
|
|
2975
|
+
"oauth_apple",
|
|
2976
|
+
"oauth_microsoft",
|
|
2977
|
+
"oauth_discord",
|
|
2978
|
+
"oauth_linkedin",
|
|
2979
|
+
"oauth_x",
|
|
2980
|
+
"oauth_facebook"
|
|
2981
|
+
]),
|
|
2982
|
+
emailVerified: z20.boolean(),
|
|
2983
|
+
passwordless: z20.boolean().optional(),
|
|
2984
|
+
hookBypassed: z20.boolean().optional(),
|
|
2985
|
+
test: z20.boolean().optional()
|
|
2986
|
+
}),
|
|
2987
|
+
"user.email_verified": data("UserEmailVerified", "A user proved their email address.", {}),
|
|
2988
|
+
"user.banned": z20.object({
|
|
2989
|
+
proposalId: id2().optional(),
|
|
2990
|
+
approvedBy: ProposalApproverSchema.optional()
|
|
2991
|
+
}).refine(proposalIsWhole, PROPOSAL_IS_WHOLE).meta({
|
|
2992
|
+
ref: "UserBannedEventData",
|
|
2993
|
+
description: "A user was banned; their sessions end. With `proposalId` and `approvedBy` (both or neither) when the ban carried out an agent’s proposal a human approved."
|
|
2994
|
+
}),
|
|
2995
|
+
"user.unbanned": z20.object(carriedOut()).refine(proposalIsWhole, PROPOSAL_IS_WHOLE).meta({
|
|
2996
|
+
ref: "UserUnbannedEventData",
|
|
2997
|
+
description: "A user’s ban was lifted. With `proposalId` and `approvedBy` (both or neither) when that carried out an agent’s proposal a human approved."
|
|
2998
|
+
}),
|
|
2999
|
+
"user.deleted": data("UserDeleted", "A user account was deleted.", {}),
|
|
3000
|
+
"user.password_changed": data("UserPasswordChanged", "A user’s password was set, replaced or removed.", {
|
|
3001
|
+
method: z20.enum(["admin_reset", "self", "reset", "email_verification"]),
|
|
3002
|
+
created: z20.boolean().optional(),
|
|
3003
|
+
removed: z20.boolean().optional()
|
|
3004
|
+
}),
|
|
3005
|
+
"user.mfa_enabled": data("UserMfaEnabled", "A user turned two-step verification on.", {
|
|
3006
|
+
method: z20.enum(["totp"])
|
|
3007
|
+
}),
|
|
3008
|
+
"user.mfa_disabled": data("UserMfaDisabled", "A user’s two-step verification was removed.", {
|
|
3009
|
+
method: z20.enum(["self", "admin_reset", "enrolment_incomplete"])
|
|
3010
|
+
}),
|
|
3011
|
+
"user.backup_codes_regenerated": data("UserBackupCodesRegenerated", "A user replaced their backup codes; the earlier set no longer works.", {}),
|
|
3012
|
+
"user.backup_code_used": data("UserBackupCodeUsed", "A user proved their second factor with a backup code.", {}),
|
|
3013
|
+
"user.identity_linked": data("UserIdentityLinked", "A provider account was connected to a user.", {
|
|
3014
|
+
provider,
|
|
3015
|
+
method: z20.enum(["auto", "profile"])
|
|
3016
|
+
}),
|
|
3017
|
+
"user.identity_unlinked": data("UserIdentityUnlinked", "A provider account was disconnected from a user.", { provider }),
|
|
3018
|
+
"user.passkey_added": data("UserPasskeyAdded", "A user registered a passkey.", {
|
|
3019
|
+
passkeyId: id2(),
|
|
3020
|
+
synced: z20.boolean()
|
|
3021
|
+
}),
|
|
3022
|
+
"user.passkey_renamed": data("UserPasskeyRenamed", "A user renamed a passkey.", {
|
|
3023
|
+
passkeyId: id2()
|
|
3024
|
+
}),
|
|
3025
|
+
"user.passkey_removed": z20.object({
|
|
3026
|
+
method: z20.enum(["user", "admin_reset"]),
|
|
3027
|
+
passkeyId: id2().optional(),
|
|
3028
|
+
canStillSignIn: z20.boolean().optional()
|
|
3029
|
+
}).refine(passkeyRemovalIsWhole, {
|
|
3030
|
+
message: "`passkeyId` goes with `user`, `canStillSignIn` with `admin_reset`"
|
|
3031
|
+
}).meta({
|
|
3032
|
+
ref: "UserPasskeyRemovedEventData",
|
|
3033
|
+
description: "A passkey was removed by its owner (`method: user`, with `passkeyId`), or every passkey of a user by an admin reset (`method: admin_reset`, with `canStillSignIn`)."
|
|
3034
|
+
}),
|
|
3035
|
+
"user.passkey_counter_regressed": data("UserPasskeyCounterRegressed", "A passkey’s signature counter did not grow: it may have been cloned. The sign-in was refused.", { passkeyId: id2() }),
|
|
3036
|
+
"user.email_added": data("UserEmailAdded", "An email address was stored on a user who had none. The address is not in the event.", {
|
|
3037
|
+
method: z20.enum(["self", "admin"]),
|
|
3038
|
+
verified: z20.boolean()
|
|
3039
|
+
}),
|
|
3040
|
+
"user.phone_number_added": data("UserPhoneNumberAdded", "A user verified a phone number, which is now the account’s (replacing an earlier one).", {}),
|
|
3041
|
+
"user.phone_number_removed": data("UserPhoneNumberRemoved", "A user took the phone number off their account.", {}),
|
|
3042
|
+
"user.sms_factor_enabled": data("UserSmsFactorEnabled", "A user made a code texted to their phone number their second factor.", {}),
|
|
3043
|
+
"user.sms_factor_removed": data("UserSmsFactorRemoved", "A texted code is no longer a user’s second factor.", {
|
|
3044
|
+
method: z20.enum(["self", "admin_reset", "phone_number_removed", "phone_number_changed"])
|
|
3045
|
+
}),
|
|
3046
|
+
"session.created": data("SessionCreated", "A user signed in.", {
|
|
3047
|
+
userId: id2(),
|
|
3048
|
+
client: SessionClientSchema,
|
|
3049
|
+
hookBypassed: z20.boolean().optional(),
|
|
3050
|
+
claimsHookBypassed: z20.boolean().optional(),
|
|
3051
|
+
deviceBound: z20.boolean().optional()
|
|
3052
|
+
}),
|
|
3053
|
+
"session.revoked": z20.object({
|
|
3054
|
+
userId: id2(),
|
|
3055
|
+
reason: z20.enum([
|
|
3056
|
+
"sign_out",
|
|
3057
|
+
"revoked_by_user",
|
|
3058
|
+
"revoked_by_admin",
|
|
3059
|
+
"password_changed",
|
|
3060
|
+
"user_banned",
|
|
3061
|
+
"mfa_changed",
|
|
3062
|
+
"session_limit"
|
|
3063
|
+
]),
|
|
3064
|
+
...carriedOut()
|
|
3065
|
+
}).refine(proposalIsWhole, PROPOSAL_IS_WHOLE).meta({
|
|
3066
|
+
ref: "SessionRevokedEventData",
|
|
3067
|
+
description: "A session was ended before it expired. With `proposalId` and `approvedBy` (both or neither) when ending it carried out an agent’s proposal a human approved."
|
|
3068
|
+
}),
|
|
3069
|
+
"session.reuse_detected": data("SessionReuseDetected", "A rotated refresh token was presented again: the session was ended as possibly stolen.", { userId: id2(), reason: z20.literal("reuse_detected") }),
|
|
3070
|
+
"session.stepped_up": data("SessionSteppedUp", "A signed-in user proved a factor again for a session.", {
|
|
3071
|
+
userId: id2(),
|
|
3072
|
+
methods: z20.array(z20.enum(AUTHENTICATION_METHODS)).max(AUTHENTICATION_METHODS.length),
|
|
3073
|
+
claimsHookBypassed: z20.boolean().optional()
|
|
3074
|
+
}),
|
|
3075
|
+
"session.refresh_proof_refused": data("SessionRefreshProofRefused", "A refresh of a device-bound session came without a valid proof of its key. The session was not ended and no token was rotated.", {
|
|
3076
|
+
userId: id2(),
|
|
3077
|
+
reason: z20.enum(["missing", "invalid", "wrong_key", "replayed"]),
|
|
3078
|
+
suppressedInPreviousMinute: z20.number().int().min(0)
|
|
3079
|
+
}),
|
|
3080
|
+
"api_key.created": data("ApiKeyCreated", "An API key was created.", {
|
|
3081
|
+
kind: z20.enum(["publishable", "secret", "agent"])
|
|
3082
|
+
}),
|
|
3083
|
+
"api_key.revoked": data("ApiKeyRevoked", "An API key was revoked.", {}),
|
|
3084
|
+
"signing_key.rotated": data("SigningKeyRotated", "An environment’s access-token signing keys were rotated. The target is the new active key.", { retiredKeyId: id2(), nextKeyId: id2() }),
|
|
3085
|
+
"environment.settings_updated": data("EnvironmentSettingsUpdated", "An environment’s settings were replaced.", {
|
|
3086
|
+
revision: z20.number().int().positive(),
|
|
3087
|
+
changed: z20.array(z20.string().max(MAX_SETTING_NAME_LENGTH).regex(/^[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*$/)).max(MAX_CHANGED_SETTINGS),
|
|
3088
|
+
weakened: z20.boolean().optional(),
|
|
3089
|
+
managedBy: z20.string().regex(CONFIG_TOOL_PATTERN).nullable().optional(),
|
|
3090
|
+
outsideConfig: z20.boolean().optional()
|
|
3091
|
+
}),
|
|
3092
|
+
"oauth_provider.updated": z20.object({
|
|
3093
|
+
provider,
|
|
3094
|
+
changed: z20.array(z20.enum([
|
|
3095
|
+
"clientId",
|
|
3096
|
+
"secret",
|
|
3097
|
+
"teamId",
|
|
3098
|
+
"keyId",
|
|
3099
|
+
"tenant",
|
|
3100
|
+
"additionalClientIds",
|
|
3101
|
+
"enabled"
|
|
3102
|
+
])).max(7),
|
|
3103
|
+
created: z20.boolean().optional(),
|
|
3104
|
+
additionalClientIdCount: z20.number().int().min(0).max(MAX_ADDITIONAL_CLIENT_IDS).optional(),
|
|
3105
|
+
weakened: z20.boolean().optional(),
|
|
3106
|
+
dependentAccounts: dependentAccounts.optional(),
|
|
3107
|
+
dependentAccountsIncomplete: z20.boolean().optional(),
|
|
3108
|
+
...carriedOut()
|
|
3109
|
+
}).refine(proposalIsWhole, PROPOSAL_IS_WHOLE).meta({
|
|
3110
|
+
ref: "OAuthProviderUpdatedEventData",
|
|
3111
|
+
description: "An OAuth provider’s credentials were set or changed, or it was switched on or off. With `proposalId` and `approvedBy` (both or neither) when switching it off carried out an agent’s proposal a human approved."
|
|
3112
|
+
}),
|
|
3113
|
+
"oauth_provider.deleted": data("OAuthProviderDeleted", "An OAuth provider’s credentials were removed.", {
|
|
3114
|
+
provider,
|
|
3115
|
+
weakened: z20.boolean().optional(),
|
|
3116
|
+
dependentAccounts: dependentAccounts.optional(),
|
|
3117
|
+
dependentAccountsIncomplete: z20.boolean().optional()
|
|
3118
|
+
}),
|
|
3119
|
+
"webhook_endpoint.created": data("WebhookEndpointCreated", "A webhook endpoint was registered. Its address and signing secret are not in the event.", {
|
|
3120
|
+
eventTypes: z20.number().int().min(1).max(ACTIVITY_TYPES2.length),
|
|
3121
|
+
enabled: z20.boolean()
|
|
3122
|
+
}),
|
|
3123
|
+
"webhook_endpoint.updated": data("WebhookEndpointUpdated", "A webhook endpoint was changed.", {
|
|
3124
|
+
changed: z20.array(z20.enum(WEBHOOK_ENDPOINT_FIELDS)).min(1).max(WEBHOOK_ENDPOINT_FIELDS.length)
|
|
3125
|
+
}),
|
|
3126
|
+
"webhook_endpoint.deleted": data("WebhookEndpointDeleted", "A webhook endpoint was removed; nothing more is delivered to it.", {}),
|
|
3127
|
+
"webhook_endpoint.disabled": data("WebhookEndpointDisabled", "The server switched a webhook endpoint off: its deliveries kept failing, or it answered 410 Gone. Nothing is delivered to it until it is switched on again.", {
|
|
3128
|
+
reason: z20.enum(WEBHOOK_DISABLED_REASONS)
|
|
3129
|
+
}),
|
|
3130
|
+
"webhook_endpoint.secret_rotated": data("WebhookEndpointSecretRotated", "A webhook endpoint’s signing secret was replaced. Deliveries carry a signature for the new secret and one for the previous secret until the time given; after it, for the new one only. Neither secret, nor any part of one, is in the event.", {
|
|
3131
|
+
rotationOverlapEndsAt: z20.iso.datetime()
|
|
3132
|
+
}),
|
|
3133
|
+
"webhook_endpoint.previous_secret_revoked": data("WebhookEndpointPreviousSecretRevoked", "An administrator ended the overlap of a secret rotation early: the endpoint’s previous signing secret stopped signing at once and was deleted. Deliveries carry a signature for the current secret only.", {}),
|
|
3134
|
+
"hook.created": data("HookCreated", "A hook was registered. Its address and signing secret are not in the event.", {
|
|
3135
|
+
point: z20.enum(HOOK_POINTS),
|
|
3136
|
+
enabled: z20.boolean(),
|
|
3137
|
+
failureMode: z20.enum(HOOK_FAILURE_MODES),
|
|
3138
|
+
weakened: z20.boolean().optional()
|
|
3139
|
+
}),
|
|
3140
|
+
"hook.updated": data("HookUpdated", "A hook was changed.", {
|
|
3141
|
+
point: z20.enum(HOOK_POINTS),
|
|
3142
|
+
changed: z20.array(z20.enum(HOOK_FIELDS)).min(1).max(HOOK_FIELDS.length),
|
|
3143
|
+
weakened: z20.boolean().optional()
|
|
3144
|
+
}),
|
|
3145
|
+
"hook.deleted": data("HookDeleted", "A hook was removed; it is not asked any more.", {
|
|
3146
|
+
point: z20.enum(HOOK_POINTS),
|
|
3147
|
+
weakened: z20.boolean().optional()
|
|
3148
|
+
}),
|
|
3149
|
+
"native_app.created": data("NativeAppCreated", "A native app was registered: the association files served for the environment name it from now on. Its bundle id or package name, its team and its fingerprints are not in the event.", {
|
|
3150
|
+
platform: z20.enum(NATIVE_APP_PLATFORMS),
|
|
3151
|
+
fingerprints: z20.number().int().min(0).max(MAX_CERT_FINGERPRINTS),
|
|
3152
|
+
appLinkPaths: z20.number().int().min(0).max(MAX_APP_LINK_PATHS).optional(),
|
|
3153
|
+
weakened: z20.boolean().optional()
|
|
3154
|
+
}),
|
|
3155
|
+
"native_app.updated": data("NativeAppUpdated", "A registered native app was changed.", {
|
|
3156
|
+
platform: z20.enum(NATIVE_APP_PLATFORMS),
|
|
3157
|
+
changed: z20.array(z20.enum(NATIVE_APP_FIELDS)).min(1).max(NATIVE_APP_FIELDS.length),
|
|
3158
|
+
fingerprints: z20.number().int().min(0).max(MAX_CERT_FINGERPRINTS),
|
|
3159
|
+
appLinkPaths: z20.number().int().min(0).max(MAX_APP_LINK_PATHS).optional(),
|
|
3160
|
+
weakened: z20.boolean().optional()
|
|
3161
|
+
}),
|
|
3162
|
+
"native_app.deleted": data("NativeAppDeleted", "A registered native app was removed: the association files no longer name it.", { platform: z20.enum(NATIVE_APP_PLATFORMS) }),
|
|
3163
|
+
"proposal.created": data("ProposalCreated", "An agent asked for a change as a proposal. Nothing has changed: a human has to approve it.", proposed2),
|
|
3164
|
+
"proposal.approved": data("ProposalApproved", "A human approved an agent’s proposal. The actor is the human; the operation follows.", {
|
|
3165
|
+
...proposed2,
|
|
3166
|
+
proposedBy: id2()
|
|
3167
|
+
}),
|
|
3168
|
+
"proposal.executed": data("ProposalExecuted", "An approved proposal’s operation was carried out. The actor is the agent that asked.", {
|
|
3169
|
+
...proposed2,
|
|
3170
|
+
approvedBy: ProposalApproverSchema,
|
|
3171
|
+
changed: z20.boolean().optional()
|
|
3172
|
+
}),
|
|
3173
|
+
"proposal.failed": data("ProposalFailed", "An approved proposal’s operation was not carried out. The proposal is spent all the same.", {
|
|
3174
|
+
...proposed2,
|
|
3175
|
+
approvedBy: ProposalApproverSchema,
|
|
3176
|
+
reason: z20.enum(PROPOSAL_FAILURE_REASONS)
|
|
3177
|
+
}),
|
|
3178
|
+
"sign_in_test.started": data("SignInTestStarted", "A sign-in test began: the server is about to sign a throwaway user up and in through the environment’s flow, and delete it.", {}),
|
|
3179
|
+
"sign_in_test.finished": data("SignInTestFinished", "A sign-in test ended. `userId` is the throwaway user it made (absent when no account was made); `cleanup` says whether that user was deleted.", {
|
|
3180
|
+
outcome: z20.enum(SIGN_IN_TEST_OUTCOMES),
|
|
3181
|
+
cleanup: z20.enum(SIGN_IN_TEST_CLEANUPS),
|
|
3182
|
+
userId: id2().optional()
|
|
3183
|
+
})
|
|
3184
|
+
};
|
|
3185
|
+
var EventActorSchema = z20.object({ type: z20.enum(AUDIT_ACTOR_TYPES), id: z20.string().min(1).max(128).nullable() }).meta({ ref: "EventActor" });
|
|
3186
|
+
function envelope(type) {
|
|
3187
|
+
const details = EVENT_DATA_SCHEMAS[type];
|
|
3188
|
+
const { ref, description } = z20.globalRegistry.get(details) ?? {};
|
|
3189
|
+
return z20.object({
|
|
3190
|
+
id: id2(),
|
|
3191
|
+
type: z20.literal(type),
|
|
3192
|
+
schemaVersion: z20.literal(EVENT_SCHEMA_VERSION2),
|
|
3193
|
+
occurredAt: z20.iso.datetime(),
|
|
3194
|
+
actor: EventActorSchema,
|
|
3195
|
+
target: z20.object({ type: z20.literal(EVENT_TARGET_TYPES2[type]), id: id2() }),
|
|
3196
|
+
data: details,
|
|
3197
|
+
test: z20.literal(true).optional()
|
|
3198
|
+
}).meta({ ref: String(ref).replace(/Data$/, ""), description });
|
|
3199
|
+
}
|
|
3200
|
+
var envelopes = ACTIVITY_TYPES2.map((type) => [type, envelope(type)]);
|
|
3201
|
+
var EVENT_SCHEMAS = Object.fromEntries(envelopes);
|
|
3202
|
+
var TulaEventSchema = z20.discriminatedUnion("type", envelopes.map(([, schema]) => schema)).meta({ ref: "TulaEvent" });
|
|
3203
|
+
// src/mfa.ts
|
|
3204
|
+
import { z as z21 } from "zod";
|
|
3205
|
+
var TOTP_DIGITS = 6;
|
|
3206
|
+
var TOTP_PERIOD_SECONDS = 30;
|
|
3207
|
+
var BACKUP_CODE_COUNT = 10;
|
|
3208
|
+
var FactorsSchema = z21.object({
|
|
3209
|
+
totp: z21.object({ enabled: z21.boolean(), confirmedAt: z21.iso.datetime().nullable() }),
|
|
3210
|
+
backupCodes: z21.object({ remaining: z21.number().int().min(0) }),
|
|
3211
|
+
sms: z21.object({
|
|
3212
|
+
enabled: z21.boolean(),
|
|
3213
|
+
enabledAt: z21.iso.datetime().nullable(),
|
|
3214
|
+
inUse: z21.boolean(),
|
|
3215
|
+
available: z21.boolean()
|
|
3216
|
+
}).optional()
|
|
3217
|
+
}).meta({ ref: "Factors" });
|
|
3218
|
+
var SmsFactorCodeSchema = z21.object({
|
|
3219
|
+
method: z21.literal("sms_code"),
|
|
3220
|
+
destination: z21.string(),
|
|
3221
|
+
expiresAt: z21.iso.datetime()
|
|
3222
|
+
}).meta({ ref: "SmsFactorCode" });
|
|
3223
|
+
var SmsFactorConfirmRequestSchema = z21.object({ code: z21.string().regex(/^\d{6}$/) }).meta({ ref: "SmsFactorConfirmRequest" });
|
|
3224
|
+
var TotpEnrolmentSchema = z21.object({ secret: z21.string(), uri: z21.string() }).meta({ ref: "TotpEnrolment" });
|
|
3225
|
+
var TotpConfirmRequestSchema = z21.object({ code: z21.string().regex(/^\d{6}$/) }).meta({ ref: "TotpConfirmRequest" });
|
|
3226
|
+
var BackupCodesSchema = z21.object({ codes: z21.array(z21.string()) }).meta({ ref: "BackupCodes" });
|
|
3227
|
+
var StepUpMethodSchema = z21.enum(["password", "totp", "backup_code", "email_code", "passkey", "sms_code"]).meta({ ref: "StepUpMethod" });
|
|
3228
|
+
var StepUpRequestSchema = z21.discriminatedUnion("method", [
|
|
3229
|
+
z21.object({ method: z21.literal("password"), password: z21.string().max(1024) }),
|
|
3230
|
+
z21.object({ method: z21.literal("totp"), code: z21.string().regex(/^\d{6}$/) }),
|
|
3231
|
+
z21.object({ method: z21.literal("backup_code"), code: z21.string().min(1).max(64) }),
|
|
3232
|
+
z21.object({ method: z21.literal("email_code"), code: z21.string().regex(/^\d{6}$/) }),
|
|
3233
|
+
z21.object({ method: z21.literal("passkey"), credential: PasskeyAssertionCredentialSchema }),
|
|
3234
|
+
z21.object({ method: z21.literal("sms_code"), code: z21.string().regex(/^\d{6}$/) })
|
|
3235
|
+
]).meta({ ref: "StepUpRequest" });
|
|
3236
|
+
var StepUpEmailCodeSchema = z21.object({
|
|
3237
|
+
method: z21.literal("email_code"),
|
|
3238
|
+
destination: z21.string(),
|
|
3239
|
+
expiresAt: z21.iso.datetime()
|
|
3240
|
+
}).meta({ ref: "StepUpEmailCode" });
|
|
3241
|
+
// src/settings-weakening.ts
|
|
3242
|
+
var SWITCHED_RULES = [
|
|
3243
|
+
"requireLowercase",
|
|
3244
|
+
"requireUppercase",
|
|
3245
|
+
"requireNumber",
|
|
3246
|
+
"requireSpecial",
|
|
3247
|
+
"disallowUserInfo",
|
|
3248
|
+
"disallowCommon",
|
|
3249
|
+
"blockSequences"
|
|
3250
|
+
];
|
|
3251
|
+
var BREACH_CHECK_STRENGTH = {
|
|
3252
|
+
off: 0,
|
|
3253
|
+
warn: 1,
|
|
3254
|
+
block: 2
|
|
3255
|
+
};
|
|
3256
|
+
var NOTICES = [
|
|
3257
|
+
"passwordChanged",
|
|
3258
|
+
"newSignIn",
|
|
3259
|
+
"mfaChanged",
|
|
3260
|
+
"identityChanged"
|
|
3261
|
+
];
|
|
3262
|
+
var MFA_POLICY_STRENGTH = {
|
|
3263
|
+
off: 0,
|
|
3264
|
+
optional: 1,
|
|
3265
|
+
required: 2
|
|
3266
|
+
};
|
|
3267
|
+
var DEVICE_BINDING_STRENGTH = {
|
|
3268
|
+
none: 0,
|
|
3269
|
+
optional: 1,
|
|
3270
|
+
required: 2
|
|
3271
|
+
};
|
|
3272
|
+
function bindsLess(is, than) {
|
|
3273
|
+
return DEVICE_BINDING_STRENGTH[is.deviceBinding] < DEVICE_BINDING_STRENGTH[than.deviceBinding];
|
|
3274
|
+
}
|
|
3275
|
+
function span(duration, none) {
|
|
3276
|
+
return duration === null ? none : durationToMs(duration);
|
|
3277
|
+
}
|
|
3278
|
+
function looser(is, than) {
|
|
3279
|
+
const never = Number.POSITIVE_INFINITY;
|
|
3280
|
+
const stepUp = durationToMs(DEFAULT_STEP_UP_AFTER);
|
|
3281
|
+
return span(is.idleTimeout, never) > span(than.idleTimeout, never) || span(is.absoluteTimeout, never) > span(than.absoluteTimeout, never) || span(is.accessTokenTtl, never) > span(than.accessTokenTtl, never) || span(is.refresh.reuseGracePeriod, 0) > span(than.refresh.reuseGracePeriod, 0) || span(is.stepUpAfter, stepUp) > span(than.stepUpAfter, stepUp);
|
|
3282
|
+
}
|
|
3283
|
+
function passwordWeakenings(was, is) {
|
|
3284
|
+
const repeats = (policy) => policy.maxRepeatedChars ?? Number.POSITIVE_INFINITY;
|
|
3285
|
+
const fields = [];
|
|
3286
|
+
if (is.minLength < was.minLength) {
|
|
3287
|
+
fields.push("minLength");
|
|
3288
|
+
}
|
|
3289
|
+
if (BREACH_CHECK_STRENGTH[is.breachCheck] < BREACH_CHECK_STRENGTH[was.breachCheck]) {
|
|
3290
|
+
fields.push("breachCheck");
|
|
3291
|
+
}
|
|
3292
|
+
fields.push(...SWITCHED_RULES.filter((rule) => was[rule] && !is[rule]));
|
|
3293
|
+
if (is.minCharacterClasses < was.minCharacterClasses) {
|
|
3294
|
+
fields.push("minCharacterClasses");
|
|
3295
|
+
}
|
|
3296
|
+
if (repeats(is) > repeats(was)) {
|
|
3297
|
+
fields.push("maxRepeatedChars");
|
|
3298
|
+
}
|
|
3299
|
+
if (is.history < was.history) {
|
|
3300
|
+
fields.push("history");
|
|
3301
|
+
}
|
|
3302
|
+
return fields.map((field) => `password.${field}`);
|
|
3303
|
+
}
|
|
3304
|
+
function shorterRetention(was, is) {
|
|
3305
|
+
return is !== null && (was === null || is < was);
|
|
3306
|
+
}
|
|
3307
|
+
function claimsOf(settings, profile) {
|
|
3308
|
+
return jwtTemplateOfProfile(settings, profile)?.template.claims ?? {};
|
|
3309
|
+
}
|
|
3310
|
+
function claimsLost(was, is) {
|
|
3311
|
+
return Object.entries(was).some(([key, claim]) => !Object.hasOwn(is, key) || JSON.stringify(is[key]) !== JSON.stringify(claim));
|
|
3312
|
+
}
|
|
3313
|
+
function sessionWeakenings(before, after) {
|
|
3314
|
+
const paths = [];
|
|
3315
|
+
if (before.maxPerUser !== null && (after.maxPerUser === null || after.maxPerUser > before.maxPerUser)) {
|
|
3316
|
+
paths.push("sessions.maxPerUser");
|
|
3317
|
+
}
|
|
3318
|
+
for (const [name, was] of Object.entries(before.profiles)) {
|
|
3319
|
+
const is = Object.hasOwn(after.profiles, name) ? after.profiles[name] ?? was : null;
|
|
3320
|
+
const weaker = is ? looser(is, was) || is.clientSelectable && !was.clientSelectable : looser(after.profiles.web, was);
|
|
3321
|
+
if (weaker) {
|
|
3322
|
+
paths.push(`sessions.profiles.${name}`);
|
|
3323
|
+
}
|
|
3324
|
+
if (claimsLost(claimsOf(before, was), claimsOf(after, is ?? after.profiles.web))) {
|
|
3325
|
+
paths.push(`sessions.profiles.${name}.jwtTemplate`);
|
|
3326
|
+
}
|
|
3327
|
+
if (bindsLess(is ?? after.profiles.mobile, was)) {
|
|
3328
|
+
paths.push(`sessions.profiles.${name}.deviceBinding`);
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3331
|
+
for (const [name, is] of Object.entries(after.profiles)) {
|
|
3332
|
+
if (Object.hasOwn(before.profiles, name) || !is.clientSelectable) {
|
|
3333
|
+
continue;
|
|
3334
|
+
}
|
|
3335
|
+
if (looser(is, after.profiles.web)) {
|
|
3336
|
+
paths.push(`sessions.profiles.${name}`);
|
|
3337
|
+
}
|
|
3338
|
+
if (after.profiles.mobile.deviceBinding === "required" && bindsLess(is, after.profiles.mobile)) {
|
|
3339
|
+
paths.push(`sessions.profiles.${name}.deviceBinding`);
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
return paths;
|
|
3343
|
+
}
|
|
3344
|
+
function smsWeakenings(before, after) {
|
|
3345
|
+
return after.dailyMessageLimit > before.dailyMessageLimit ? ["sms.dailyMessageLimit"] : [];
|
|
3346
|
+
}
|
|
3347
|
+
function textsGo(settings) {
|
|
3348
|
+
return settings.sms.enabled && settings.sms.allowedCountries.length > 0;
|
|
3349
|
+
}
|
|
3350
|
+
function countryAdded(before, after) {
|
|
3351
|
+
const had = new Set(before.sms.allowedCountries);
|
|
3352
|
+
return after.sms.allowedCountries.some((country) => !had.has(country));
|
|
3353
|
+
}
|
|
3354
|
+
function smsSignsIn(settings) {
|
|
3355
|
+
return settings.signIn.methods.smsCode.enabled && textsGo(settings);
|
|
3356
|
+
}
|
|
3357
|
+
function smsSignInWeakenings(before, after) {
|
|
3358
|
+
if (!smsSignsIn(after)) {
|
|
3359
|
+
return [];
|
|
3360
|
+
}
|
|
3361
|
+
if (!smsSignsIn(before)) {
|
|
3362
|
+
return ["signIn.methods.smsCode"];
|
|
3363
|
+
}
|
|
3364
|
+
return countryAdded(before, after) ? ["sms.allowedCountries"] : [];
|
|
3365
|
+
}
|
|
3366
|
+
function smsSecondStepWeakenings(before, after) {
|
|
3367
|
+
if (!after.mfa.smsCode.enabled || after.mfa.policy !== "required") {
|
|
3368
|
+
return [];
|
|
3369
|
+
}
|
|
3370
|
+
if (!before.mfa.smsCode.enabled) {
|
|
3371
|
+
return ["mfa.smsCode"];
|
|
3372
|
+
}
|
|
3373
|
+
if (!textsGo(after)) {
|
|
3374
|
+
return [];
|
|
3375
|
+
}
|
|
3376
|
+
if (!textsGo(before)) {
|
|
3377
|
+
return ["mfa.smsCode"];
|
|
3378
|
+
}
|
|
3379
|
+
return countryAdded(before, after) ? ["sms.allowedCountries"] : [];
|
|
3380
|
+
}
|
|
3381
|
+
function redirectWeakenings(before, after) {
|
|
3382
|
+
const had = new Set(before.allowedRedirectUrls);
|
|
3383
|
+
return after.allowedRedirectUrls.some((url) => !had.has(url) && isCustomSchemeRedirectUrl2(url)) ? ["urls.allowedRedirectUrls"] : [];
|
|
3384
|
+
}
|
|
3385
|
+
function settingsWeakenings(before, after) {
|
|
3386
|
+
const paths = passwordWeakenings(before.password, after.password);
|
|
3387
|
+
if (shorterRetention(before.audit.retentionDays, after.audit.retentionDays)) {
|
|
3388
|
+
paths.push("audit.retentionDays");
|
|
3389
|
+
}
|
|
3390
|
+
if (!before.agents.proposalsInProduction && after.agents.proposalsInProduction) {
|
|
3391
|
+
paths.push("agents.proposalsInProduction");
|
|
3392
|
+
}
|
|
3393
|
+
for (const notice of NOTICES) {
|
|
3394
|
+
if (before.notifications[notice] && !after.notifications[notice]) {
|
|
3395
|
+
paths.push(`notifications.${notice}`);
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
if (MFA_POLICY_STRENGTH[after.mfa.policy] < MFA_POLICY_STRENGTH[before.mfa.policy]) {
|
|
3399
|
+
paths.push("mfa.policy");
|
|
3400
|
+
}
|
|
3401
|
+
const secondStep = smsSecondStepWeakenings(before, after);
|
|
3402
|
+
paths.push(...secondStep.filter((path) => path === "mfa.smsCode"));
|
|
3403
|
+
paths.push(...sessionWeakenings(before.sessions, after.sessions));
|
|
3404
|
+
paths.push(...smsWeakenings(before.sms, after.sms));
|
|
3405
|
+
const signIn = smsSignInWeakenings(before, after);
|
|
3406
|
+
paths.push(...signIn);
|
|
3407
|
+
if (secondStep.includes("sms.allowedCountries") && !signIn.includes("sms.allowedCountries")) {
|
|
3408
|
+
paths.push("sms.allowedCountries");
|
|
3409
|
+
}
|
|
3410
|
+
paths.push(...redirectWeakenings(before.urls, after.urls));
|
|
3411
|
+
const wasAllowed = before.agents.testSignIn;
|
|
3412
|
+
const isAllowed = after.agents.testSignIn;
|
|
3413
|
+
if (isAllowed === true && wasAllowed !== true || isAllowed === null && wasAllowed === false) {
|
|
3414
|
+
paths.push("agents.testSignIn");
|
|
3415
|
+
}
|
|
3416
|
+
return paths;
|
|
3417
|
+
}
|
|
3418
|
+
// src/sign-in-test.ts
|
|
3419
|
+
import * as z22 from "zod";
|
|
3420
|
+
var SignInTestStepResultSchema = z22.strictObject({
|
|
3421
|
+
step: z22.enum(SIGN_IN_TEST_STEPS),
|
|
3422
|
+
outcome: z22.enum(SIGN_IN_TEST_STEP_OUTCOMES),
|
|
3423
|
+
durationMs: z22.number().int().min(0),
|
|
3424
|
+
code: ErrorCodeSchema.optional(),
|
|
3425
|
+
reason: z22.enum(SIGN_IN_TEST_REASONS).optional()
|
|
3426
|
+
}).meta({ ref: "SignInTestStepResult" });
|
|
3427
|
+
var SignInTestReportSchema = z22.strictObject({
|
|
3428
|
+
outcome: z22.enum(SIGN_IN_TEST_OUTCOMES),
|
|
3429
|
+
steps: z22.array(SignInTestStepResultSchema).length(SIGN_IN_TEST_STEPS.length),
|
|
3430
|
+
userId: z22.uuid().nullable(),
|
|
3431
|
+
cleanup: z22.enum(SIGN_IN_TEST_CLEANUPS)
|
|
3432
|
+
}).meta({ ref: "SignInTestReport" });
|
|
3433
|
+
// src/sms.ts
|
|
3434
|
+
import { z as z23 } from "zod";
|
|
3435
|
+
var SMS_USAGE_MAX_DAYS = 30;
|
|
3436
|
+
var SMS_USAGE_DEFAULT_DAYS = 7;
|
|
3437
|
+
var SMS_USAGE_MAX_PREFIXES = 100;
|
|
3438
|
+
var count = z23.number().int().min(0);
|
|
3439
|
+
var SmsPrefixUsageSchema = z23.object({
|
|
3440
|
+
prefix: z23.string().regex(new RegExp(`^\\+[0-9]{1,${SMS_PREFIX_MAX_DIGITS}}$`)),
|
|
3441
|
+
sent: count,
|
|
3442
|
+
used: count,
|
|
3443
|
+
unused: count
|
|
3444
|
+
}).meta({ ref: "SmsPrefixUsage" });
|
|
3445
|
+
var SmsUsageSchema = z23.object({
|
|
3446
|
+
since: z23.iso.date(),
|
|
3447
|
+
days: z23.number().int().min(1).max(SMS_USAGE_MAX_DAYS),
|
|
3448
|
+
sent: count,
|
|
3449
|
+
used: count,
|
|
3450
|
+
unused: count,
|
|
3451
|
+
prefixes: z23.array(SmsPrefixUsageSchema).max(SMS_USAGE_MAX_PREFIXES),
|
|
3452
|
+
truncated: z23.boolean()
|
|
3453
|
+
}).meta({ ref: "SmsUsage" });
|
|
3454
|
+
export {
|
|
3455
|
+
ACCESS_TOKEN_ALGORITHM,
|
|
3456
|
+
ACCESS_TOKEN_VERSION,
|
|
3457
|
+
ACTIVITY_TYPES2 as ACTIVITY_TYPES,
|
|
3458
|
+
AGENT_KEY_PREFIX,
|
|
3459
|
+
ANDROID_APK_KEY_HASH_PREFIX,
|
|
3460
|
+
APPLE_TEAM_ID_PATTERN,
|
|
3461
|
+
APP_LINK_PATH_PATTERN,
|
|
3462
|
+
ASSET_LINKS_APP_LINK_RELATION,
|
|
3463
|
+
ASSET_LINKS_RELATIONS,
|
|
3464
|
+
AT_LEAST_ONE_SIGN_IN_METHOD,
|
|
3465
|
+
AUDIT_ACTOR_TYPES,
|
|
3466
|
+
AUDIT_TARGET_TYPES,
|
|
3467
|
+
AUTHENTICATION_METHODS,
|
|
3468
|
+
AccessTokenClaimsSchema,
|
|
3469
|
+
ActivityTypeSchema,
|
|
3470
|
+
AdditionalClientIdsSchema,
|
|
3471
|
+
AdminSessionSchema,
|
|
3472
|
+
AndroidAppIdentitySchema,
|
|
3473
|
+
AndroidAppSchema,
|
|
3474
|
+
AppleAppSiteAssociationSchema,
|
|
3475
|
+
ApproveProposalRequestSchema,
|
|
3476
|
+
AssetLinksSchema,
|
|
3477
|
+
AuditLogListSchema,
|
|
3478
|
+
AuditLogSchema,
|
|
3479
|
+
BACKUP_CODE_COUNT,
|
|
3480
|
+
BUILT_IN_SESSION_PROFILES,
|
|
3481
|
+
BUNDLE_ID_PATTERN,
|
|
3482
|
+
BackupCodesSchema,
|
|
3483
|
+
CAN_STILL_SIGN_IN_HEADER2 as CAN_STILL_SIGN_IN_HEADER,
|
|
3484
|
+
CERT_FINGERPRINT_PATTERN,
|
|
3485
|
+
CLIENT_HEADER2 as CLIENT_HEADER,
|
|
3486
|
+
COMMON_PASSWORDS2 as COMMON_PASSWORDS,
|
|
3487
|
+
CONFIG_HASH_HEADER2 as CONFIG_HASH_HEADER,
|
|
3488
|
+
CONFIG_HASH_PATTERN,
|
|
3489
|
+
CONFIG_MANAGED_BY_HEADER2 as CONFIG_MANAGED_BY_HEADER,
|
|
3490
|
+
CONFIG_TOOL_PATTERN,
|
|
3491
|
+
CONFIG_UNMANAGED2 as CONFIG_UNMANAGED,
|
|
3492
|
+
COUNTRY_CALLING_PREFIXES,
|
|
3493
|
+
CUSTOM_CLAIMS_CLAIM2 as CUSTOM_CLAIMS_CLAIM,
|
|
3494
|
+
CUSTOM_SCHEME_REDIRECT_REFUSALS2 as CUSTOM_SCHEME_REDIRECT_REFUSALS,
|
|
3495
|
+
ChangePasswordRequestSchema,
|
|
3496
|
+
ClientConfigSchema,
|
|
3497
|
+
CreateHookRequestSchema,
|
|
3498
|
+
CreateNativeAppRequestSchema,
|
|
3499
|
+
CreateProposalRequestSchema,
|
|
3500
|
+
CreateUserRequestSchema,
|
|
3501
|
+
CreateWebhookEndpointRequestSchema,
|
|
3502
|
+
CreatedHookSchema,
|
|
3503
|
+
CreatedWebhookEndpointSchema,
|
|
3504
|
+
CurrentUserSchema,
|
|
3505
|
+
DASHBOARD_HEADER2 as DASHBOARD_HEADER,
|
|
3506
|
+
DASHBOARD_HEADER_VALUE2 as DASHBOARD_HEADER_VALUE,
|
|
3507
|
+
DASHBOARD_SESSION_COOKIE2 as DASHBOARD_SESSION_COOKIE,
|
|
3508
|
+
DEFAULT_APP_NAME,
|
|
3509
|
+
DEFAULT_ENVIRONMENT_SETTINGS,
|
|
3510
|
+
DEFAULT_MOBILE_SESSION_PROFILE,
|
|
3511
|
+
DEFAULT_PAGE_SIZE,
|
|
3512
|
+
DEFAULT_SMS_DAILY_MESSAGE_LIMIT,
|
|
3513
|
+
DEFAULT_SPECIAL_CHARS2 as DEFAULT_SPECIAL_CHARS,
|
|
3514
|
+
DEFAULT_STEP_UP_AFTER,
|
|
3515
|
+
DEFAULT_THEME2 as DEFAULT_THEME,
|
|
3516
|
+
DEFAULT_WEB_SESSION_PROFILE,
|
|
3517
|
+
DEPENDENT_ACCOUNTS_SCAN_LIMIT,
|
|
3518
|
+
DPOP_ALGORITHMS2 as DPOP_ALGORITHMS,
|
|
3519
|
+
DPOP_HEADER2 as DPOP_HEADER,
|
|
3520
|
+
DPOP_NONCE_HEADER2 as DPOP_NONCE_HEADER,
|
|
3521
|
+
DPOP_PROOF_TYPE2 as DPOP_PROOF_TYPE,
|
|
3522
|
+
DeviceBindingPolicySchema,
|
|
3523
|
+
DurationSchema,
|
|
3524
|
+
EMAIL_LINK_ATTEMPT_PARAM2 as EMAIL_LINK_ATTEMPT_PARAM,
|
|
3525
|
+
EMAIL_LINK_SCHEMES2 as EMAIL_LINK_SCHEMES,
|
|
3526
|
+
EMAIL_LINK_TOKEN_PARAM2 as EMAIL_LINK_TOKEN_PARAM,
|
|
3527
|
+
EMAIL_TEMPLATE_DIGIT_PLACEHOLDERS2 as EMAIL_TEMPLATE_DIGIT_PLACEHOLDERS,
|
|
3528
|
+
EMAIL_TEMPLATE_KINDS2 as EMAIL_TEMPLATE_KINDS,
|
|
3529
|
+
EMAIL_TEMPLATE_PLACEHOLDERS2 as EMAIL_TEMPLATE_PLACEHOLDERS,
|
|
3530
|
+
EMAIL_TEMPLATE_RULES2 as EMAIL_TEMPLATE_RULES,
|
|
3531
|
+
ENVIRONMENT_HEADER2 as ENVIRONMENT_HEADER,
|
|
3532
|
+
ERROR_CODES2 as ERROR_CODES,
|
|
3533
|
+
ERROR_DEFINITIONS2 as ERROR_DEFINITIONS,
|
|
3534
|
+
EVENT_DATA_SCHEMAS,
|
|
3535
|
+
EVENT_FIXTURES,
|
|
3536
|
+
EVENT_SCHEMAS,
|
|
3537
|
+
EVENT_SCHEMA_VERSION2 as EVENT_SCHEMA_VERSION,
|
|
3538
|
+
EVENT_TARGET_TYPES2 as EVENT_TARGET_TYPES,
|
|
3539
|
+
EmailAddressRequestSchema,
|
|
3540
|
+
EmailAddressVerifyRequestSchema,
|
|
3541
|
+
EmailCodeSentSchema,
|
|
3542
|
+
EmailLinkRequestSchema,
|
|
3543
|
+
EmailLinkResultSchema,
|
|
3544
|
+
EmailSettingsSchema,
|
|
3545
|
+
EmailTemplateSchema,
|
|
3546
|
+
EmailTemplatesSchema,
|
|
3547
|
+
EmailVerificationStrategySchema,
|
|
3548
|
+
EnvironmentSettingsInputSchema,
|
|
3549
|
+
EnvironmentSettingsSchema,
|
|
3550
|
+
ErrorCodeSchema,
|
|
3551
|
+
ErrorEnvelopeSchema,
|
|
3552
|
+
ErrorParamsSchema,
|
|
3553
|
+
EventActorSchema,
|
|
3554
|
+
FLOW_ATTEMPT_HEADER2 as FLOW_ATTEMPT_HEADER,
|
|
3555
|
+
FactorEnrolmentMethodSchema,
|
|
3556
|
+
FactorsSchema,
|
|
3557
|
+
FieldErrorSchema,
|
|
3558
|
+
FirstFactorAttemptRequestSchema,
|
|
3559
|
+
FirstFactorPrepareRequestSchema,
|
|
3560
|
+
FirstFactorStrategySchema,
|
|
3561
|
+
FlowAttemptSchema,
|
|
3562
|
+
FlowKindSchema,
|
|
3563
|
+
FlowStepSchema,
|
|
3564
|
+
HOOK_ANSWER_KINDS,
|
|
3565
|
+
HOOK_DEFAULT_DEADLINE_MS,
|
|
3566
|
+
HOOK_DENIAL_CODE_PATTERN,
|
|
3567
|
+
HOOK_FAILURE_MODES,
|
|
3568
|
+
HOOK_FAILURE_REASONS,
|
|
3569
|
+
HOOK_FIELDS,
|
|
3570
|
+
HOOK_MAX_DEADLINE_MS,
|
|
3571
|
+
HOOK_MAX_DENIAL_CODE_LENGTH,
|
|
3572
|
+
HOOK_MIN_DEADLINE_MS,
|
|
3573
|
+
HOOK_OUTCOMES,
|
|
3574
|
+
HOOK_OUTCOME_WINDOW_HOURS,
|
|
3575
|
+
HOOK_POINTS,
|
|
3576
|
+
HOOK_QUESTION_FIXTURES,
|
|
3577
|
+
HOOK_QUESTION_SCHEMAS,
|
|
3578
|
+
HOOK_QUESTION_TYPES,
|
|
3579
|
+
HOOK_SCHEMA_VERSION,
|
|
3580
|
+
HOOK_SIGN_UP_METHODS,
|
|
3581
|
+
HookAnswerSchema,
|
|
3582
|
+
HookBeforeSessionDataSchema,
|
|
3583
|
+
HookBeforeSignUpDataSchema,
|
|
3584
|
+
HookBeforeTokenDataSchema,
|
|
3585
|
+
HookClaimsAnswerSchema,
|
|
3586
|
+
HookListSchema,
|
|
3587
|
+
HookPointSchema,
|
|
3588
|
+
HookQuestionSchema,
|
|
3589
|
+
HookRecentOutcomesSchema,
|
|
3590
|
+
HookSchema,
|
|
3591
|
+
ID_TOKEN_CLIENT_KINDS,
|
|
3592
|
+
ID_TOKEN_PROVIDERS,
|
|
3593
|
+
INSTANCE_ACTIVITY_TYPES,
|
|
3594
|
+
INSTANCE_AUDIT_TARGET_TYPES,
|
|
3595
|
+
IdTokenExchangeRequestSchema,
|
|
3596
|
+
IdTokenProviderSchema,
|
|
3597
|
+
IdTokenStartRequestSchema,
|
|
3598
|
+
IdTokenStartSchema,
|
|
3599
|
+
IdentityLinkStartSchema,
|
|
3600
|
+
IdentityListSchema,
|
|
3601
|
+
IdentitySchema,
|
|
3602
|
+
InstanceActivityTypeSchema,
|
|
3603
|
+
IosAppIdentitySchema,
|
|
3604
|
+
IosAppSchema,
|
|
3605
|
+
JWT_TEMPLATE_SOURCES,
|
|
3606
|
+
JwkSchema,
|
|
3607
|
+
JwksSchema,
|
|
3608
|
+
JwtTemplateClaimSchema,
|
|
3609
|
+
JwtTemplateSchema,
|
|
3610
|
+
JwtTemplateSourceSchema,
|
|
3611
|
+
MAX_ACCESS_TOKEN_TTL,
|
|
3612
|
+
MAX_ADDITIONAL_CLIENT_IDS,
|
|
3613
|
+
MAX_ALLOWED_ORIGINS,
|
|
3614
|
+
MAX_ALLOWED_REDIRECT_URLS,
|
|
3615
|
+
MAX_APP_LINK_PATHS,
|
|
3616
|
+
MAX_APP_LINK_PATH_LENGTH,
|
|
3617
|
+
MAX_APP_NAME_LENGTH,
|
|
3618
|
+
MAX_AUDIT_RETENTION_DAYS,
|
|
3619
|
+
MAX_BUNDLE_ID_LENGTH,
|
|
3620
|
+
MAX_CERT_FINGERPRINTS,
|
|
3621
|
+
MAX_CHANGED_SETTINGS,
|
|
3622
|
+
MAX_CUSTOM_CLAIMS_BYTES2 as MAX_CUSTOM_CLAIMS_BYTES,
|
|
3623
|
+
MAX_CUSTOM_CLAIM_CONSTANT_LENGTH2 as MAX_CUSTOM_CLAIM_CONSTANT_LENGTH,
|
|
3624
|
+
MAX_CUSTOM_CLAIM_KEY_LENGTH2 as MAX_CUSTOM_CLAIM_KEY_LENGTH,
|
|
3625
|
+
MAX_CUSTOM_SESSION_PROFILES,
|
|
3626
|
+
MAX_DPOP_PROOF_LENGTH2 as MAX_DPOP_PROOF_LENGTH,
|
|
3627
|
+
MAX_DURATION_MS,
|
|
3628
|
+
MAX_EMAIL_BODY_LENGTH2 as MAX_EMAIL_BODY_LENGTH,
|
|
3629
|
+
MAX_EMAIL_CLAIM_BYTES,
|
|
3630
|
+
MAX_EMAIL_SUBJECT_LENGTH2 as MAX_EMAIL_SUBJECT_LENGTH,
|
|
3631
|
+
MAX_EMAIL_TEMPLATES_BYTES2 as MAX_EMAIL_TEMPLATES_BYTES,
|
|
3632
|
+
MAX_ID_TOKEN_LENGTH,
|
|
3633
|
+
MAX_ID_TOKEN_NAME_LENGTH,
|
|
3634
|
+
MAX_JWT_TEMPLATES2 as MAX_JWT_TEMPLATES,
|
|
3635
|
+
MAX_JWT_TEMPLATE_CLAIMS2 as MAX_JWT_TEMPLATE_CLAIMS,
|
|
3636
|
+
MAX_NATIVE_APPS,
|
|
3637
|
+
MAX_PACKAGE_NAME_LENGTH,
|
|
3638
|
+
MAX_PAGE_SIZE,
|
|
3639
|
+
MAX_PASSKEYS_PER_USER,
|
|
3640
|
+
MAX_PASSKEY_NAME_LENGTH,
|
|
3641
|
+
MAX_PASSWORD_HISTORY,
|
|
3642
|
+
MAX_PHONE_NUMBER_DIGITS,
|
|
3643
|
+
MAX_REUSE_GRACE_PERIOD,
|
|
3644
|
+
MAX_SESSIONS_PER_USER,
|
|
3645
|
+
MAX_SESSION_PROFILE_NAME_LENGTH,
|
|
3646
|
+
MAX_SESSION_TIMEOUT,
|
|
3647
|
+
MAX_SETTING_NAME_LENGTH,
|
|
3648
|
+
MAX_SMS_DAILY_MESSAGE_LIMIT,
|
|
3649
|
+
MAX_SMS_TEMPLATE_LENGTH,
|
|
3650
|
+
MAX_STEP_UP_AFTER,
|
|
3651
|
+
MAX_VERIFIED_TOKEN_LENGTH,
|
|
3652
|
+
MAX_WEBHOOK_ENDPOINTS,
|
|
3653
|
+
MAX_WEBHOOK_URL_LENGTH,
|
|
3654
|
+
MICROSOFT_TENANT_ALIASES,
|
|
3655
|
+
MIN_ACCESS_TOKEN_TTL,
|
|
3656
|
+
MIN_IDLE_TIMEOUT,
|
|
3657
|
+
MIN_PASSWORD_MIN_LENGTH,
|
|
3658
|
+
MIN_PHONE_NUMBER_DIGITS,
|
|
3659
|
+
MIN_REUSE_GRACE_PERIOD,
|
|
3660
|
+
MIN_STEP_UP_AFTER,
|
|
3661
|
+
MfaPolicySchema,
|
|
3662
|
+
MicrosoftTenantSchema,
|
|
3663
|
+
NATIVE_APP_FIELDS,
|
|
3664
|
+
NATIVE_APP_PLATFORMS,
|
|
3665
|
+
NativeAppListSchema,
|
|
3666
|
+
NativeAppSchema,
|
|
3667
|
+
NewPasswordReasonSchema,
|
|
3668
|
+
NewPasswordRequestSchema,
|
|
3669
|
+
OAUTH_ERROR_PARAM2 as OAUTH_ERROR_PARAM,
|
|
3670
|
+
OAUTH_PROVIDERS,
|
|
3671
|
+
OAUTH_PROVIDERS_WITHOUT_ADDRESS,
|
|
3672
|
+
OAUTH_PROVIDERS_WITHOUT_PKCE2 as OAUTH_PROVIDERS_WITHOUT_PKCE,
|
|
3673
|
+
OAUTH_PROVIDERS_WITH_PKCE2 as OAUTH_PROVIDERS_WITH_PKCE,
|
|
3674
|
+
OAUTH_TICKET_PARAM2 as OAUTH_TICKET_PARAM,
|
|
3675
|
+
OAuthExchangeRequestSchema,
|
|
3676
|
+
OAuthProviderDependentsSchema,
|
|
3677
|
+
OAuthProviderSchema,
|
|
3678
|
+
OAuthProviderSettingsListSchema,
|
|
3679
|
+
OAuthProviderSettingsSchema,
|
|
3680
|
+
OAuthProviderUpdateSchema,
|
|
3681
|
+
OAuthStartRequestSchema,
|
|
3682
|
+
OAuthStartSchema,
|
|
3683
|
+
PACKAGE_NAME_PATTERN,
|
|
3684
|
+
PASSKEY_ALGORITHMS,
|
|
3685
|
+
PASSKEY_CHALLENGE_TTL_MS,
|
|
3686
|
+
PASSWORD_POLICY_PRESETS,
|
|
3687
|
+
PROPOSAL_APPROVAL_REFUSALS,
|
|
3688
|
+
PROPOSAL_APPROVER_TYPES,
|
|
3689
|
+
PROPOSAL_CREATION_REFUSALS,
|
|
3690
|
+
PROPOSAL_EXPIRY_REASONS,
|
|
3691
|
+
PROPOSAL_FAILURE_REASONS,
|
|
3692
|
+
PROPOSAL_OPERATIONS,
|
|
3693
|
+
PROPOSAL_OPERATION_TARGETS,
|
|
3694
|
+
PROPOSAL_STATUSES,
|
|
3695
|
+
PROPOSAL_TARGET_TYPES,
|
|
3696
|
+
PUBLISHABLE_KEY_HEADER2 as PUBLISHABLE_KEY_HEADER,
|
|
3697
|
+
PUBLISHABLE_KEY_PREFIX,
|
|
3698
|
+
PaginationMetaSchema,
|
|
3699
|
+
PasskeyAssertionCredentialSchema,
|
|
3700
|
+
PasskeyCreationOptionsSchema,
|
|
3701
|
+
PasskeyListSchema,
|
|
3702
|
+
PasskeyRegisterRequestSchema,
|
|
3703
|
+
PasskeyRegistrationCredentialSchema,
|
|
3704
|
+
PasskeyRenameRequestSchema,
|
|
3705
|
+
PasskeyRequestOptionsSchema,
|
|
3706
|
+
PasskeySchema,
|
|
3707
|
+
PasskeySignInRequestSchema,
|
|
3708
|
+
PasskeySignInStartSchema,
|
|
3709
|
+
PasswordAttemptRequestSchema,
|
|
3710
|
+
PasswordPolicySchema,
|
|
3711
|
+
PasswordResetRequestSchema,
|
|
3712
|
+
PasswordResetStartRequestSchema,
|
|
3713
|
+
PhoneCodeSentSchema,
|
|
3714
|
+
PhoneNumberRequestSchema,
|
|
3715
|
+
PhoneNumberVerifyRequestSchema,
|
|
3716
|
+
PreparedFirstFactorStrategySchema,
|
|
3717
|
+
PreparedSecondFactorMethodSchema,
|
|
3718
|
+
ProposalApproverSchema,
|
|
3719
|
+
ProposalListSchema,
|
|
3720
|
+
ProposalOperationSchema,
|
|
3721
|
+
ProposalSchema,
|
|
3722
|
+
ProposalStatusSchema,
|
|
3723
|
+
ProposalTargetSchema,
|
|
3724
|
+
REDIRECT_SCHEMES_NEVER_CUSTOM2 as REDIRECT_SCHEMES_NEVER_CUSTOM,
|
|
3725
|
+
REDIRECT_SCHEME_FAMILIES_NEVER_CUSTOM2 as REDIRECT_SCHEME_FAMILIES_NEVER_CUSTOM,
|
|
3726
|
+
REFRESH_TOKEN_PREFIX,
|
|
3727
|
+
RESERVED_CLAIM_NAMES2 as RESERVED_CLAIM_NAMES,
|
|
3728
|
+
RedirectUrlSchema,
|
|
3729
|
+
RefreshTokenRequestSchema,
|
|
3730
|
+
RevokedSessionsSchema,
|
|
3731
|
+
RotatedWebhookSecretSchema,
|
|
3732
|
+
SECRET_KEY_PREFIX,
|
|
3733
|
+
SESSION_PROFILE_HEADER2 as SESSION_PROFILE_HEADER,
|
|
3734
|
+
SIGN_IN_METHODS_WITHOUT_SIGN_UP,
|
|
3735
|
+
SIGN_IN_TEST_CLEANUPS,
|
|
3736
|
+
SIGN_IN_TEST_OUTCOMES,
|
|
3737
|
+
SIGN_IN_TEST_REASONS,
|
|
3738
|
+
SIGN_IN_TEST_REFUSALS,
|
|
3739
|
+
SIGN_IN_TEST_STEPS,
|
|
3740
|
+
SIGN_IN_TEST_STEP_OUTCOMES,
|
|
3741
|
+
SMS_COUNTRIES,
|
|
3742
|
+
SMS_ENVIRONMENT_HOURLY_SHARE,
|
|
3743
|
+
SMS_PREFIX_HOURLY_SHARE,
|
|
3744
|
+
SMS_PREFIX_MAX_DIGITS,
|
|
3745
|
+
SMS_TEMPLATE_KINDS,
|
|
3746
|
+
SMS_TEMPLATE_PLACEHOLDERS,
|
|
3747
|
+
SMS_TEMPLATE_RULES,
|
|
3748
|
+
SMS_USAGE_DEFAULT_DAYS,
|
|
3749
|
+
SMS_USAGE_MAX_DAYS,
|
|
3750
|
+
SMS_USAGE_MAX_PREFIXES,
|
|
3751
|
+
STEP_UP_MAX_AGE_SECONDS,
|
|
3752
|
+
SecondFactorMethodSchema,
|
|
3753
|
+
SecondFactorPrepareRequestSchema,
|
|
3754
|
+
SecondFactorRequestSchema,
|
|
3755
|
+
SendTestWebhookRequestSchema,
|
|
3756
|
+
SessionClientSchema,
|
|
3757
|
+
SessionLimitActionSchema,
|
|
3758
|
+
SessionListSchema,
|
|
3759
|
+
SessionProfileNameSchema,
|
|
3760
|
+
SessionProfileSchema,
|
|
3761
|
+
SessionSchema,
|
|
3762
|
+
SessionSettingsSchema,
|
|
3763
|
+
SessionTokensSchema,
|
|
3764
|
+
SessionTypeSchema,
|
|
3765
|
+
SetPasswordRequestSchema,
|
|
3766
|
+
SetUserEmailRequestSchema,
|
|
3767
|
+
SettingsManagedBySchema,
|
|
3768
|
+
SignInStartRequestSchema,
|
|
3769
|
+
SignInTestReportSchema,
|
|
3770
|
+
SignInTestStepResultSchema,
|
|
3771
|
+
SignUpPasswordModeSchema,
|
|
3772
|
+
SignUpRequestSchema,
|
|
3773
|
+
SmsCountrySchema,
|
|
3774
|
+
SmsFactorCodeSchema,
|
|
3775
|
+
SmsFactorConfirmRequestSchema,
|
|
3776
|
+
SmsPrefixUsageSchema,
|
|
3777
|
+
SmsTemplateSchema,
|
|
3778
|
+
SmsTemplatesSchema,
|
|
3779
|
+
SmsUsageSchema,
|
|
3780
|
+
StepUpEmailCodeSchema,
|
|
3781
|
+
StepUpMethodSchema,
|
|
3782
|
+
StepUpRequestSchema,
|
|
3783
|
+
StoredSessionSettingsSchema,
|
|
3784
|
+
TEST_ADDRESS_DOMAIN,
|
|
3785
|
+
TEST_ADDRESS_LOCAL_PREFIX,
|
|
3786
|
+
THEME_TOKENS2 as THEME_TOKENS,
|
|
3787
|
+
TOTP_DIGITS,
|
|
3788
|
+
TOTP_PERIOD_SECONDS,
|
|
3789
|
+
TotpConfirmRequestSchema,
|
|
3790
|
+
TotpEnrolmentSchema,
|
|
3791
|
+
TulaEventSchema,
|
|
3792
|
+
UpdateHookRequestSchema,
|
|
3793
|
+
UpdateNativeAppRequestSchema,
|
|
3794
|
+
UpdateWebhookEndpointRequestSchema,
|
|
3795
|
+
UserAuthenticationSchema,
|
|
3796
|
+
UserListSchema,
|
|
3797
|
+
UserSchema,
|
|
3798
|
+
UserSortSchema,
|
|
3799
|
+
VerifyEmailRequestSchema,
|
|
3800
|
+
VerifySessionRequestSchema,
|
|
3801
|
+
WEBHOOK_DELIVERY_STATES,
|
|
3802
|
+
WEBHOOK_DISABLED_REASONS,
|
|
3803
|
+
WEBHOOK_ENDPOINT_FIELDS,
|
|
3804
|
+
WEBHOOK_ID_HEADER2 as WEBHOOK_ID_HEADER,
|
|
3805
|
+
WEBHOOK_REDELIVER_REFUSALS,
|
|
3806
|
+
WEBHOOK_ROTATION_REFUSALS,
|
|
3807
|
+
WEBHOOK_SECRET_PREFIX2 as WEBHOOK_SECRET_PREFIX,
|
|
3808
|
+
WEBHOOK_SIGNATURE_HEADER2 as WEBHOOK_SIGNATURE_HEADER,
|
|
3809
|
+
WEBHOOK_SIGNATURE_VERSION2 as WEBHOOK_SIGNATURE_VERSION,
|
|
3810
|
+
WEBHOOK_TIMESTAMP_HEADER2 as WEBHOOK_TIMESTAMP_HEADER,
|
|
3811
|
+
WEBHOOK_TOLERANCE_SECONDS2 as WEBHOOK_TOLERANCE_SECONDS,
|
|
3812
|
+
WebOriginSchema,
|
|
3813
|
+
WebSessionProfileSchema,
|
|
3814
|
+
WebhookDeliveryAttemptSchema,
|
|
3815
|
+
WebhookDeliveryDetailSchema,
|
|
3816
|
+
WebhookDeliveryListSchema,
|
|
3817
|
+
WebhookDeliverySchema,
|
|
3818
|
+
WebhookEndpointListSchema,
|
|
3819
|
+
WebhookEndpointSchema,
|
|
3820
|
+
WebhookSendResultSchema,
|
|
3821
|
+
androidApkKeyHashOrigin,
|
|
3822
|
+
appleAppSiteAssociation,
|
|
3823
|
+
assetLinks,
|
|
3824
|
+
bindsCodeWithPkce2 as bindsCodeWithPkce,
|
|
3825
|
+
builtInSessionProfile,
|
|
3826
|
+
checkCustomClaims2 as checkCustomClaims,
|
|
3827
|
+
contrastRatio2 as contrastRatio,
|
|
3828
|
+
createDpopProof2 as createDpopProof,
|
|
3829
|
+
customClaimsBytes2 as customClaimsBytes,
|
|
3830
|
+
customSchemeRedirectRefusal2 as customSchemeRedirectRefusal,
|
|
3831
|
+
darkCssVariable2 as darkCssVariable,
|
|
3832
|
+
defaultDeviceBinding,
|
|
3833
|
+
durationToMs,
|
|
3834
|
+
emailTemplateParagraphs2 as emailTemplateParagraphs,
|
|
3835
|
+
emailTemplateProblems2 as emailTemplateProblems,
|
|
3836
|
+
emailTemplatesBytes2 as emailTemplatesBytes,
|
|
3837
|
+
environmentIssuer2 as environmentIssuer,
|
|
3838
|
+
errorDefinition2 as errorDefinition,
|
|
3839
|
+
evaluatePassword2 as evaluatePassword,
|
|
3840
|
+
formatWebhookSecret2 as formatWebhookSecret,
|
|
3841
|
+
generateSoftwareDeviceKey2 as generateSoftwareDeviceKey,
|
|
3842
|
+
givesNoAddress,
|
|
3843
|
+
hasEnabledSignInMethod,
|
|
3844
|
+
hasForbiddenRedirectCharacter2 as hasForbiddenRedirectCharacter,
|
|
3845
|
+
hasHiddenCharacter2 as hasHiddenCharacter,
|
|
3846
|
+
hasInvisibleCharacter2 as hasInvisibleCharacter,
|
|
3847
|
+
hookFailureOutcome,
|
|
3848
|
+
hookLastCallFailed,
|
|
3849
|
+
hookWeakenings,
|
|
3850
|
+
isAppLinkPath,
|
|
3851
|
+
isCustomClaimKey2 as isCustomClaimKey,
|
|
3852
|
+
isCustomClaimValue2 as isCustomClaimValue,
|
|
3853
|
+
isCustomSchemeRedirectUrl2 as isCustomSchemeRedirectUrl,
|
|
3854
|
+
isDevicePublicJwk2 as isDevicePublicJwk,
|
|
3855
|
+
isEmailTemplateKind2 as isEmailTemplateKind,
|
|
3856
|
+
isGoogleClientId,
|
|
3857
|
+
isKeyThumbprint2 as isKeyThumbprint,
|
|
3858
|
+
isPhoneNumberAllowed,
|
|
3859
|
+
isProposalTargetId,
|
|
3860
|
+
isRedirectUrl2 as isRedirectUrl,
|
|
3861
|
+
isRelyingPartyId,
|
|
3862
|
+
isSessionProfileName,
|
|
3863
|
+
isSmsCountry,
|
|
3864
|
+
isSmsTemplateKind,
|
|
3865
|
+
isTestAddress,
|
|
3866
|
+
isValidThemeValue2 as isValidThemeValue,
|
|
3867
|
+
jwkThumbprint2 as jwkThumbprint,
|
|
3868
|
+
jwksUrl2 as jwksUrl,
|
|
3869
|
+
jwtTemplateMaxBytes,
|
|
3870
|
+
jwtTemplateOfProfile,
|
|
3871
|
+
maskPhoneNumber,
|
|
3872
|
+
nativeAppIdentifier,
|
|
3873
|
+
nativeAppWeakenings,
|
|
3874
|
+
normalizeAppLinkPaths,
|
|
3875
|
+
normalizeCertFingerprint,
|
|
3876
|
+
normalizeCertFingerprints,
|
|
3877
|
+
normalizePassword2 as normalizePassword,
|
|
3878
|
+
oauthProviderWeakenings,
|
|
3879
|
+
originMatchesRelyingParty,
|
|
3880
|
+
ownClientIdAmong,
|
|
3881
|
+
parseEmailTemplate2 as parseEmailTemplate,
|
|
3882
|
+
parsePhoneNumber,
|
|
3883
|
+
parseStoredEnvironmentSettings,
|
|
3884
|
+
phoneNumberCountries,
|
|
3885
|
+
phoneNumberPrefix,
|
|
3886
|
+
profileOfSession,
|
|
3887
|
+
proposalSummary,
|
|
3888
|
+
readCustomClaims2 as readCustomClaims,
|
|
3889
|
+
readHookClaimsAnswer,
|
|
3890
|
+
readStoredEmailTemplates2 as readStoredEmailTemplates,
|
|
3891
|
+
readStoredEnvironmentSettings,
|
|
3892
|
+
readStoredJwtTemplate,
|
|
3893
|
+
readStoredSmsTemplates,
|
|
3894
|
+
readsAsLink2 as readsAsLink,
|
|
3895
|
+
redirectUrlKind2 as redirectUrlKind,
|
|
3896
|
+
resolveSessionProfile,
|
|
3897
|
+
settingsWeakenings,
|
|
3898
|
+
signWebhook2 as signWebhook,
|
|
3899
|
+
smsCostLimits,
|
|
3900
|
+
smsPrefixCountries,
|
|
3901
|
+
smsSegments,
|
|
3902
|
+
smsTemplateProblems,
|
|
3903
|
+
stepUpWindowSeconds,
|
|
3904
|
+
takesAdditionalClientIds,
|
|
3905
|
+
testAddress,
|
|
3906
|
+
testSignInAllowed,
|
|
3907
|
+
themeToCssVariables2 as themeToCssVariables,
|
|
3908
|
+
visibleEmailText2 as visibleEmailText,
|
|
3909
|
+
webhookSecretBytes2 as webhookSecretBytes,
|
|
3910
|
+
withoutHiddenCharacters2 as withoutHiddenCharacters,
|
|
3911
|
+
withoutInvisibleCharacters2 as withoutInvisibleCharacters
|
|
3912
|
+
};
|