@tula-tech/contract 0.0.0-stage → 0.1.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 +37 -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
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
// src/email-template.ts
|
|
2
|
+
var EMAIL_TEMPLATE_KINDS2 = [
|
|
3
|
+
"email_verification",
|
|
4
|
+
"password_reset",
|
|
5
|
+
"sign_in",
|
|
6
|
+
"step_up",
|
|
7
|
+
"account_exists",
|
|
8
|
+
"no_account",
|
|
9
|
+
"no_account_sign_in",
|
|
10
|
+
"password_changed",
|
|
11
|
+
"password_added",
|
|
12
|
+
"password_reset_completed",
|
|
13
|
+
"password_added_by_reset",
|
|
14
|
+
"password_set_by_admin",
|
|
15
|
+
"password_added_by_admin",
|
|
16
|
+
"password_removed",
|
|
17
|
+
"new_sign_in",
|
|
18
|
+
"mfa_enabled",
|
|
19
|
+
"mfa_disabled",
|
|
20
|
+
"mfa_reset_by_admin",
|
|
21
|
+
"backup_codes_regenerated",
|
|
22
|
+
"backup_code_used",
|
|
23
|
+
"sms_factor_enabled",
|
|
24
|
+
"sms_factor_removed",
|
|
25
|
+
"passkey_added",
|
|
26
|
+
"passkey_removed",
|
|
27
|
+
"identity_linked",
|
|
28
|
+
"identity_unlinked",
|
|
29
|
+
"email_in_use",
|
|
30
|
+
"email_added"
|
|
31
|
+
];
|
|
32
|
+
var EMAIL_TEMPLATE_PLACEHOLDERS2 = [
|
|
33
|
+
"appName",
|
|
34
|
+
"code",
|
|
35
|
+
"link",
|
|
36
|
+
"expiresInMinutes",
|
|
37
|
+
"device",
|
|
38
|
+
"time",
|
|
39
|
+
"provider",
|
|
40
|
+
"backupCodesLeft"
|
|
41
|
+
];
|
|
42
|
+
var EMAIL_TEMPLATE_DIGIT_PLACEHOLDERS2 = [
|
|
43
|
+
"code",
|
|
44
|
+
"expiresInMinutes",
|
|
45
|
+
"time",
|
|
46
|
+
"backupCodesLeft"
|
|
47
|
+
];
|
|
48
|
+
var CODE = {
|
|
49
|
+
category: "code",
|
|
50
|
+
required: ["code"],
|
|
51
|
+
optional: ["appName", "expiresInMinutes"]
|
|
52
|
+
};
|
|
53
|
+
var DECOY = { category: "notice", required: [], optional: ["appName"] };
|
|
54
|
+
var NOTICE = {
|
|
55
|
+
category: "notice",
|
|
56
|
+
required: [],
|
|
57
|
+
optional: ["appName", "time"]
|
|
58
|
+
};
|
|
59
|
+
var EMAIL_TEMPLATE_RULES2 = {
|
|
60
|
+
email_verification: CODE,
|
|
61
|
+
password_reset: CODE,
|
|
62
|
+
sign_in: { ...CODE, required: ["code", "link"] },
|
|
63
|
+
step_up: CODE,
|
|
64
|
+
account_exists: DECOY,
|
|
65
|
+
no_account: DECOY,
|
|
66
|
+
no_account_sign_in: DECOY,
|
|
67
|
+
password_changed: NOTICE,
|
|
68
|
+
password_added: NOTICE,
|
|
69
|
+
password_reset_completed: NOTICE,
|
|
70
|
+
password_added_by_reset: NOTICE,
|
|
71
|
+
password_set_by_admin: NOTICE,
|
|
72
|
+
password_added_by_admin: NOTICE,
|
|
73
|
+
password_removed: NOTICE,
|
|
74
|
+
new_sign_in: { ...NOTICE, optional: ["appName", "time", "device"] },
|
|
75
|
+
mfa_enabled: NOTICE,
|
|
76
|
+
mfa_disabled: NOTICE,
|
|
77
|
+
mfa_reset_by_admin: NOTICE,
|
|
78
|
+
backup_codes_regenerated: NOTICE,
|
|
79
|
+
backup_code_used: { ...NOTICE, optional: ["appName", "time", "backupCodesLeft"] },
|
|
80
|
+
sms_factor_enabled: NOTICE,
|
|
81
|
+
sms_factor_removed: NOTICE,
|
|
82
|
+
passkey_added: NOTICE,
|
|
83
|
+
passkey_removed: NOTICE,
|
|
84
|
+
identity_linked: { ...NOTICE, optional: ["appName", "time", "provider"] },
|
|
85
|
+
identity_unlinked: { ...NOTICE, optional: ["appName", "time", "provider"] },
|
|
86
|
+
email_in_use: DECOY,
|
|
87
|
+
email_added: NOTICE
|
|
88
|
+
};
|
|
89
|
+
var MAX_EMAIL_SUBJECT_LENGTH2 = 200;
|
|
90
|
+
var MAX_EMAIL_BODY_LENGTH2 = 2000;
|
|
91
|
+
var MAX_EMAIL_TEMPLATES_BYTES2 = 40 * 1024;
|
|
92
|
+
var PLACEHOLDER = /\{\{([A-Za-z][A-Za-z0-9]*)\}\}/g;
|
|
93
|
+
function parseEmailTemplate2(text) {
|
|
94
|
+
const tokens = [];
|
|
95
|
+
let from = 0;
|
|
96
|
+
for (const match of text.matchAll(PLACEHOLDER)) {
|
|
97
|
+
if (match.index > from) {
|
|
98
|
+
tokens.push({ text: text.slice(from, match.index) });
|
|
99
|
+
}
|
|
100
|
+
tokens.push({ placeholder: match[1] });
|
|
101
|
+
from = match.index + match[0].length;
|
|
102
|
+
}
|
|
103
|
+
if (from < text.length) {
|
|
104
|
+
tokens.push({ text: text.slice(from) });
|
|
105
|
+
}
|
|
106
|
+
const stray = tokens.some((token) => ("text" in token) && /[{}]/.test(token.text));
|
|
107
|
+
return stray ? null : tokens;
|
|
108
|
+
}
|
|
109
|
+
function emailTemplateParagraphs2(body) {
|
|
110
|
+
return body.split(/\n[^\S\n]*\n/).map((paragraph) => paragraph.trim()).filter((paragraph) => paragraph !== "");
|
|
111
|
+
}
|
|
112
|
+
var SUBJECT_UNPRINTABLE = /[\p{Cc}\p{Zl}\p{Zp}]/u;
|
|
113
|
+
var BODY_UNPRINTABLE = /[^\P{Cc}\n]|[\p{Zl}\p{Zp}]/u;
|
|
114
|
+
var HIDDEN = /[\u{202A}-\u{202E}\u{2066}-\u{2069}\u{200E}\u{200F}\u{061C}\p{Co}\p{Cn}\p{Cs}]/u;
|
|
115
|
+
function hasHiddenCharacter2(text) {
|
|
116
|
+
return HIDDEN.test(text);
|
|
117
|
+
}
|
|
118
|
+
function withoutHiddenCharacters2(text) {
|
|
119
|
+
return text.replace(new RegExp(HIDDEN.source, "gu"), "");
|
|
120
|
+
}
|
|
121
|
+
var INVISIBLE = /[\p{Cf}\p{Variation_Selector}\p{Default_Ignorable_Code_Point}]/gu;
|
|
122
|
+
function visibleEmailText2(text) {
|
|
123
|
+
return withoutInvisibleCharacters2(text).trim();
|
|
124
|
+
}
|
|
125
|
+
function withoutInvisibleCharacters2(text) {
|
|
126
|
+
return text.replace(INVISIBLE, "");
|
|
127
|
+
}
|
|
128
|
+
var ANY_INVISIBLE = new RegExp(INVISIBLE.source, "u");
|
|
129
|
+
function hasInvisibleCharacter2(text) {
|
|
130
|
+
return ANY_INVISIBLE.test(text);
|
|
131
|
+
}
|
|
132
|
+
var EMAIL_LINK_SCHEMES2 = [
|
|
133
|
+
"mailto",
|
|
134
|
+
"tel",
|
|
135
|
+
"sms",
|
|
136
|
+
"smsto",
|
|
137
|
+
"mms",
|
|
138
|
+
"xmpp",
|
|
139
|
+
"sip",
|
|
140
|
+
"sips",
|
|
141
|
+
"facetime",
|
|
142
|
+
"facetime-audio",
|
|
143
|
+
"skype",
|
|
144
|
+
"callto",
|
|
145
|
+
"whatsapp",
|
|
146
|
+
"tg",
|
|
147
|
+
"viber",
|
|
148
|
+
"signal",
|
|
149
|
+
"msteams",
|
|
150
|
+
"geo",
|
|
151
|
+
"maps",
|
|
152
|
+
"data",
|
|
153
|
+
"javascript",
|
|
154
|
+
"vbscript",
|
|
155
|
+
"file",
|
|
156
|
+
"blob",
|
|
157
|
+
"intent"
|
|
158
|
+
];
|
|
159
|
+
var SCHEME = new RegExp(`://|(?<![\\p{L}\\p{N}_-])(?:${EMAIL_LINK_SCHEMES2.join("|")}):(?=\\S)`, "iu");
|
|
160
|
+
var WWW = /(?:^|[^\p{L}\p{N}\p{M}])w\p{M}*w\p{M}*w\p{M}*[.。]/iu;
|
|
161
|
+
var DOMAIN = /[\p{L}\p{N}]\p{M}*[.。]\p{L}[\p{L}\p{M}]+/u;
|
|
162
|
+
var IPV4 = /\p{Nd}{1,3}(?:[.。]\p{Nd}{1,3}){3}/u;
|
|
163
|
+
function readsAsLink2(text) {
|
|
164
|
+
const seen = text.normalize("NFKC").replace(INVISIBLE, "");
|
|
165
|
+
return SCHEME.test(seen) || WWW.test(seen) || DOMAIN.test(seen) || IPV4.test(seen);
|
|
166
|
+
}
|
|
167
|
+
var MAX_REPORTED_NAME_LENGTH = 40;
|
|
168
|
+
function placeholdersOf(tokens) {
|
|
169
|
+
return tokens.flatMap((token) => ("placeholder" in token) ? [token.placeholder] : []);
|
|
170
|
+
}
|
|
171
|
+
function withStandIns(tokens) {
|
|
172
|
+
return tokens.map((token) => ("text" in token) ? token.text : "x").join("");
|
|
173
|
+
}
|
|
174
|
+
function withLeadingStandIns(tokens) {
|
|
175
|
+
const digits = EMAIL_TEMPLATE_DIGIT_PLACEHOLDERS2;
|
|
176
|
+
return tokens.map((token) => ("text" in token) ? token.text : digits.includes(token.placeholder) ? "1" : "x").join("");
|
|
177
|
+
}
|
|
178
|
+
function fieldProblems(kind, field, text) {
|
|
179
|
+
const rules = EMAIL_TEMPLATE_RULES2[kind];
|
|
180
|
+
const problem = (code, message, placeholder) => ({
|
|
181
|
+
field,
|
|
182
|
+
code,
|
|
183
|
+
message,
|
|
184
|
+
...placeholder !== undefined && { placeholder }
|
|
185
|
+
});
|
|
186
|
+
const max = field === "subject" ? MAX_EMAIL_SUBJECT_LENGTH2 : MAX_EMAIL_BODY_LENGTH2;
|
|
187
|
+
if (text.length > max) {
|
|
188
|
+
return [problem("too_long", `must be at most ${max} characters`)];
|
|
189
|
+
}
|
|
190
|
+
if (visibleEmailText2(text) === "") {
|
|
191
|
+
return [problem("empty", "must not be empty")];
|
|
192
|
+
}
|
|
193
|
+
if ((field === "subject" ? SUBJECT_UNPRINTABLE : BODY_UNPRINTABLE).test(text)) {
|
|
194
|
+
return [
|
|
195
|
+
problem("control_character", field === "subject" ? "must not contain control characters or line breaks" : "must not contain control characters (lines end in \\n)")
|
|
196
|
+
];
|
|
197
|
+
}
|
|
198
|
+
if (hasHiddenCharacter2(text)) {
|
|
199
|
+
return [
|
|
200
|
+
problem("hidden_character", "must not contain text-direction controls, private-use or unassigned characters, or half a surrogate pair")
|
|
201
|
+
];
|
|
202
|
+
}
|
|
203
|
+
const tokens = parseEmailTemplate2(text);
|
|
204
|
+
if (tokens === null) {
|
|
205
|
+
return [
|
|
206
|
+
problem("malformed_braces", "a brace must be part of a placeholder written as {{name}}, with no space inside")
|
|
207
|
+
];
|
|
208
|
+
}
|
|
209
|
+
const problems = [];
|
|
210
|
+
const allowed = new Set([...rules.required, ...rules.optional]);
|
|
211
|
+
if (field === "subject") {
|
|
212
|
+
allowed.delete("link");
|
|
213
|
+
}
|
|
214
|
+
const named = new Set(placeholdersOf(tokens));
|
|
215
|
+
for (const written of named) {
|
|
216
|
+
if (!allowed.has(written)) {
|
|
217
|
+
const name = written.length > MAX_REPORTED_NAME_LENGTH ? `${written.slice(0, MAX_REPORTED_NAME_LENGTH)}…` : written;
|
|
218
|
+
problems.push(problem("unknown_placeholder", `{{${name}}} is not a placeholder of this ${field === "subject" ? "subject" : "message"}`, name));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (field === "body") {
|
|
222
|
+
for (const name of rules.required) {
|
|
223
|
+
if (!named.has(name)) {
|
|
224
|
+
problems.push(problem("missing_placeholder", `the body must contain {{${name}}}`, name));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const together = emailTemplateParagraphs2(text).some((paragraph) => {
|
|
228
|
+
const inside = new Set(placeholdersOf(parseEmailTemplate2(paragraph) ?? []));
|
|
229
|
+
return inside.has("link") && inside.has("code");
|
|
230
|
+
});
|
|
231
|
+
if (together) {
|
|
232
|
+
problems.push(problem("link_beside_code", "{{link}} and {{code}} must be in different paragraphs: the paragraph of a link is left out of a message that has none"));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (readsAsLink2(withStandIns(tokens))) {
|
|
236
|
+
problems.push(problem("reads_as_link", "must not contain a link, an address or a domain name (put a space after a full stop)"));
|
|
237
|
+
}
|
|
238
|
+
if (rules.category === "notice" && field === "subject" && /^\p{Nd}/u.test(visibleEmailText2(withLeadingStandIns(tokens)))) {
|
|
239
|
+
problems.push(problem("leading_digit", "the subject of a notice must not start with a digit, or with a placeholder that is always a number or a time"));
|
|
240
|
+
}
|
|
241
|
+
return problems;
|
|
242
|
+
}
|
|
243
|
+
function emailTemplateProblems2(kind, template) {
|
|
244
|
+
return [
|
|
245
|
+
...template.subject === undefined ? [] : fieldProblems(kind, "subject", template.subject),
|
|
246
|
+
...template.body === undefined ? [] : fieldProblems(kind, "body", template.body)
|
|
247
|
+
];
|
|
248
|
+
}
|
|
249
|
+
function isEmailTemplateKind2(value) {
|
|
250
|
+
return EMAIL_TEMPLATE_KINDS2.includes(value);
|
|
251
|
+
}
|
|
252
|
+
function emailTemplatesBytes2(templates) {
|
|
253
|
+
return new TextEncoder().encode(JSON.stringify(templates)).length;
|
|
254
|
+
}
|
|
255
|
+
function storedTemplate(kind, stored) {
|
|
256
|
+
if (typeof stored !== "object" || stored === null || Array.isArray(stored)) {
|
|
257
|
+
return { template: null, whole: false };
|
|
258
|
+
}
|
|
259
|
+
const written = stored;
|
|
260
|
+
const template = {};
|
|
261
|
+
let whole = written.subject !== undefined || written.body !== undefined;
|
|
262
|
+
for (const field of ["subject", "body"]) {
|
|
263
|
+
const value = written[field];
|
|
264
|
+
if (value === undefined) {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (typeof value === "string" && emailTemplateProblems2(kind, { [field]: value }).length === 0) {
|
|
268
|
+
template[field] = value;
|
|
269
|
+
} else {
|
|
270
|
+
whole = false;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const kept = template.subject !== undefined || template.body !== undefined;
|
|
274
|
+
return { template: kept ? template : null, whole };
|
|
275
|
+
}
|
|
276
|
+
function readStoredEmailTemplates2(stored) {
|
|
277
|
+
const templates = {};
|
|
278
|
+
const dropped = [];
|
|
279
|
+
if (typeof stored !== "object" || stored === null || Array.isArray(stored)) {
|
|
280
|
+
return { templates, dropped, unknown: stored === undefined || stored === null ? 0 : 1 };
|
|
281
|
+
}
|
|
282
|
+
const unknown = Object.keys(stored).filter((key) => !isEmailTemplateKind2(key)).length;
|
|
283
|
+
for (const key of EMAIL_TEMPLATE_KINDS2) {
|
|
284
|
+
if (!Object.hasOwn(stored, key)) {
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
const { template, whole } = storedTemplate(key, stored[key]);
|
|
288
|
+
if (!whole) {
|
|
289
|
+
dropped.push(key);
|
|
290
|
+
}
|
|
291
|
+
if (template !== null) {
|
|
292
|
+
templates[key] = template;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (emailTemplatesBytes2(templates) > MAX_EMAIL_TEMPLATES_BYTES2) {
|
|
296
|
+
const lost = new Set([...dropped, ...kindsOf(templates)]);
|
|
297
|
+
return {
|
|
298
|
+
templates: {},
|
|
299
|
+
dropped: EMAIL_TEMPLATE_KINDS2.filter((kind) => lost.has(kind)),
|
|
300
|
+
unknown
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
return { templates, dropped, unknown };
|
|
304
|
+
}
|
|
305
|
+
function kindsOf(templates) {
|
|
306
|
+
return EMAIL_TEMPLATE_KINDS2.filter((kind) => Object.hasOwn(templates, kind));
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// src/redirect-url.ts
|
|
310
|
+
var REDIRECT_SCHEMES_NEVER_CUSTOM2 = [
|
|
311
|
+
"http",
|
|
312
|
+
"https",
|
|
313
|
+
"javascript",
|
|
314
|
+
"vbscript",
|
|
315
|
+
"data",
|
|
316
|
+
"file",
|
|
317
|
+
"filesystem",
|
|
318
|
+
"blob",
|
|
319
|
+
"about",
|
|
320
|
+
"mailto",
|
|
321
|
+
"tel",
|
|
322
|
+
"sms",
|
|
323
|
+
"facetime",
|
|
324
|
+
"geo",
|
|
325
|
+
"maps",
|
|
326
|
+
"intent",
|
|
327
|
+
"content",
|
|
328
|
+
"android-app",
|
|
329
|
+
"market",
|
|
330
|
+
"itms",
|
|
331
|
+
"itms-apps",
|
|
332
|
+
"itms-services",
|
|
333
|
+
"app-settings",
|
|
334
|
+
"ms-settings",
|
|
335
|
+
"x-apple.systempreferences",
|
|
336
|
+
"ftp",
|
|
337
|
+
"ws",
|
|
338
|
+
"wss",
|
|
339
|
+
"chrome",
|
|
340
|
+
"chrome-extension",
|
|
341
|
+
"moz-extension",
|
|
342
|
+
"resource",
|
|
343
|
+
"jar",
|
|
344
|
+
"cid",
|
|
345
|
+
"mid",
|
|
346
|
+
"view-source"
|
|
347
|
+
];
|
|
348
|
+
var REDIRECT_SCHEME_FAMILIES_NEVER_CUSTOM2 = [
|
|
349
|
+
"microsoft.windows.",
|
|
350
|
+
"x-apple.",
|
|
351
|
+
"com.apple."
|
|
352
|
+
];
|
|
353
|
+
var LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
354
|
+
var CUSTOM_SCHEME = /^[a-z][a-z0-9-]*(?:\.[a-z0-9-]+)+$/;
|
|
355
|
+
var CUSTOM_REST = /^(?:\/[A-Za-z0-9._~-]*)+$/;
|
|
356
|
+
function customSchemeOf(value) {
|
|
357
|
+
const colon = value.indexOf(":");
|
|
358
|
+
if (colon <= 0) {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
const scheme = value.slice(0, colon);
|
|
362
|
+
const rest = value.slice(colon + 1);
|
|
363
|
+
if (!CUSTOM_SCHEME.test(scheme) || REDIRECT_SCHEMES_NEVER_CUSTOM2.includes(scheme) || REDIRECT_SCHEME_FAMILIES_NEVER_CUSTOM2.some((family) => scheme.startsWith(family)) || !CUSTOM_REST.test(rest) || rest.split("/").some((segment) => segment === "." || segment === "..")) {
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
return scheme;
|
|
367
|
+
}
|
|
368
|
+
var WEB_NEVER = /[\u{0}-\u{1f}\u{7f}-\u{9f}\\]/u;
|
|
369
|
+
function hasForbiddenRedirectCharacter2(value) {
|
|
370
|
+
return value.includes("*") || /\s/.test(value) || WEB_NEVER.test(value) || hasInvisibleCharacter2(value) || hasHiddenCharacter2(value);
|
|
371
|
+
}
|
|
372
|
+
function webKindOf(value) {
|
|
373
|
+
if (!(value.startsWith("https://") || value.startsWith("http://"))) {
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
if (hasForbiddenRedirectCharacter2(value)) {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
let url;
|
|
380
|
+
try {
|
|
381
|
+
url = new URL(value);
|
|
382
|
+
} catch {
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
if (url.username !== "" || url.password !== "" || url.hash !== "") {
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
if (url.protocol === "https:") {
|
|
389
|
+
return "https";
|
|
390
|
+
}
|
|
391
|
+
return url.protocol === "http:" && LOOPBACK_HOSTS.has(url.hostname) ? "loopback" : null;
|
|
392
|
+
}
|
|
393
|
+
function redirectUrlKind2(value) {
|
|
394
|
+
if (/^https?:/i.test(value)) {
|
|
395
|
+
return webKindOf(value);
|
|
396
|
+
}
|
|
397
|
+
return customSchemeOf(value) === null ? null : "custom_scheme";
|
|
398
|
+
}
|
|
399
|
+
function isRedirectUrl2(value) {
|
|
400
|
+
return redirectUrlKind2(value) !== null;
|
|
401
|
+
}
|
|
402
|
+
function isCustomSchemeRedirectUrl2(value) {
|
|
403
|
+
return redirectUrlKind2(value) === "custom_scheme";
|
|
404
|
+
}
|
|
405
|
+
var OAUTH_PROVIDERS_WITH_PKCE2 = [
|
|
406
|
+
"google",
|
|
407
|
+
"github",
|
|
408
|
+
"microsoft",
|
|
409
|
+
"discord",
|
|
410
|
+
"x"
|
|
411
|
+
];
|
|
412
|
+
var OAUTH_PROVIDERS_WITHOUT_PKCE2 = [
|
|
413
|
+
"apple",
|
|
414
|
+
"linkedin",
|
|
415
|
+
"facebook"
|
|
416
|
+
];
|
|
417
|
+
function bindsCodeWithPkce2(provider) {
|
|
418
|
+
return OAUTH_PROVIDERS_WITH_PKCE2.includes(provider);
|
|
419
|
+
}
|
|
420
|
+
var CUSTOM_SCHEME_REDIRECT_REFUSALS2 = [
|
|
421
|
+
"provider_without_pkce",
|
|
422
|
+
"client_not_native",
|
|
423
|
+
"not_a_provider_sign_in"
|
|
424
|
+
];
|
|
425
|
+
function customSchemeRedirectRefusal2(url, use) {
|
|
426
|
+
if (!isCustomSchemeRedirectUrl2(url)) {
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
if (use.provider === undefined) {
|
|
430
|
+
return "not_a_provider_sign_in";
|
|
431
|
+
}
|
|
432
|
+
if (!bindsCodeWithPkce2(use.provider)) {
|
|
433
|
+
return "provider_without_pkce";
|
|
434
|
+
}
|
|
435
|
+
return use.client === "ios" || use.client === "android" ? null : "client_not_native";
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export { EMAIL_TEMPLATE_KINDS2, EMAIL_TEMPLATE_PLACEHOLDERS2, EMAIL_TEMPLATE_DIGIT_PLACEHOLDERS2, EMAIL_TEMPLATE_RULES2, MAX_EMAIL_SUBJECT_LENGTH2, MAX_EMAIL_BODY_LENGTH2, MAX_EMAIL_TEMPLATES_BYTES2, parseEmailTemplate2, emailTemplateParagraphs2, hasHiddenCharacter2, withoutHiddenCharacters2, visibleEmailText2, withoutInvisibleCharacters2, hasInvisibleCharacter2, EMAIL_LINK_SCHEMES2, readsAsLink2, emailTemplateProblems2, isEmailTemplateKind2, emailTemplatesBytes2, readStoredEmailTemplates2, REDIRECT_SCHEMES_NEVER_CUSTOM2, REDIRECT_SCHEME_FAMILIES_NEVER_CUSTOM2, hasForbiddenRedirectCharacter2, redirectUrlKind2, isRedirectUrl2, isCustomSchemeRedirectUrl2, OAUTH_PROVIDERS_WITH_PKCE2, OAUTH_PROVIDERS_WITHOUT_PKCE2, bindsCodeWithPkce2, CUSTOM_SCHEME_REDIRECT_REFUSALS2, customSchemeRedirectRefusal2 };
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// src/common-passwords.ts
|
|
2
|
+
var COMMON_PASSWORDS2 = new Set([
|
|
3
|
+
"123456",
|
|
4
|
+
"123456789",
|
|
5
|
+
"12345678",
|
|
6
|
+
"password",
|
|
7
|
+
"qwerty123",
|
|
8
|
+
"qwerty1",
|
|
9
|
+
"111111",
|
|
10
|
+
"12345",
|
|
11
|
+
"secret",
|
|
12
|
+
"123123",
|
|
13
|
+
"1234567890",
|
|
14
|
+
"1234567",
|
|
15
|
+
"000000",
|
|
16
|
+
"qwerty",
|
|
17
|
+
"abc123",
|
|
18
|
+
"password1",
|
|
19
|
+
"iloveyou",
|
|
20
|
+
"11111111",
|
|
21
|
+
"dragon",
|
|
22
|
+
"monkey",
|
|
23
|
+
"123321",
|
|
24
|
+
"qwertyuiop",
|
|
25
|
+
"654321",
|
|
26
|
+
"666666",
|
|
27
|
+
"987654321",
|
|
28
|
+
"superman",
|
|
29
|
+
"1q2w3e4r",
|
|
30
|
+
"1q2w3e4r5t",
|
|
31
|
+
"qwe123",
|
|
32
|
+
"asdfghjkl",
|
|
33
|
+
"football",
|
|
34
|
+
"baseball",
|
|
35
|
+
"welcome",
|
|
36
|
+
"welcome1",
|
|
37
|
+
"letmein",
|
|
38
|
+
"princess",
|
|
39
|
+
"sunshine",
|
|
40
|
+
"master",
|
|
41
|
+
"shadow",
|
|
42
|
+
"ashley",
|
|
43
|
+
"michael",
|
|
44
|
+
"jennifer",
|
|
45
|
+
"trustno1",
|
|
46
|
+
"passw0rd",
|
|
47
|
+
"password123",
|
|
48
|
+
"password1234",
|
|
49
|
+
"admin",
|
|
50
|
+
"admin123",
|
|
51
|
+
"administrator",
|
|
52
|
+
"login",
|
|
53
|
+
"starwars",
|
|
54
|
+
"whatever",
|
|
55
|
+
"freedom",
|
|
56
|
+
"hello123",
|
|
57
|
+
"charlie",
|
|
58
|
+
"donald",
|
|
59
|
+
"batman",
|
|
60
|
+
"zaq12wsx",
|
|
61
|
+
"qazwsx",
|
|
62
|
+
"1qaz2wsx",
|
|
63
|
+
"solo",
|
|
64
|
+
"loveme",
|
|
65
|
+
"mustang",
|
|
66
|
+
"access",
|
|
67
|
+
"flower",
|
|
68
|
+
"hottie",
|
|
69
|
+
"ninja",
|
|
70
|
+
"azerty",
|
|
71
|
+
"password!",
|
|
72
|
+
"p@ssw0rd",
|
|
73
|
+
"p@ssword",
|
|
74
|
+
"changeme",
|
|
75
|
+
"default",
|
|
76
|
+
"test1234",
|
|
77
|
+
"testtest",
|
|
78
|
+
"computer",
|
|
79
|
+
"internet",
|
|
80
|
+
"samsung",
|
|
81
|
+
"google",
|
|
82
|
+
"liverpool",
|
|
83
|
+
"chelsea",
|
|
84
|
+
"arsenal",
|
|
85
|
+
"pokemon",
|
|
86
|
+
"minecraft",
|
|
87
|
+
"summer2024",
|
|
88
|
+
"winter2024",
|
|
89
|
+
"spring2025",
|
|
90
|
+
"summer2025",
|
|
91
|
+
"autumn2025",
|
|
92
|
+
"winter2025",
|
|
93
|
+
"summer2026",
|
|
94
|
+
"qwerty12345",
|
|
95
|
+
"iloveyou1",
|
|
96
|
+
"aa123456",
|
|
97
|
+
"abcd1234",
|
|
98
|
+
"a1b2c3d4",
|
|
99
|
+
"zxcvbnm",
|
|
100
|
+
"asdfasdf",
|
|
101
|
+
"fuckyou",
|
|
102
|
+
"killer",
|
|
103
|
+
"jordan23",
|
|
104
|
+
"michelle",
|
|
105
|
+
"daniel",
|
|
106
|
+
"thomas",
|
|
107
|
+
"hunter2",
|
|
108
|
+
"letmein123",
|
|
109
|
+
"welcome123"
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
// src/password-rules.ts
|
|
113
|
+
var DEFAULT_SPECIAL_CHARS2 = "!@#$%^&*()-_=+[]{};:,.?/\\|'\"`~<>";
|
|
114
|
+
function normalizePassword2(password) {
|
|
115
|
+
return password.normalize("NFC");
|
|
116
|
+
}
|
|
117
|
+
var LOWER = /\p{Ll}/u;
|
|
118
|
+
var UPPER = /\p{Lu}/u;
|
|
119
|
+
var NUMBER = /\p{Nd}/u;
|
|
120
|
+
var SEQUENCE_LENGTH = 4;
|
|
121
|
+
var MIN_USER_INFO_LENGTH = 3;
|
|
122
|
+
function longestRun(chars) {
|
|
123
|
+
let longest = 0;
|
|
124
|
+
let current = 0;
|
|
125
|
+
for (let i = 0;i < chars.length; i++) {
|
|
126
|
+
current = i > 0 && chars[i] === chars[i - 1] ? current + 1 : 1;
|
|
127
|
+
longest = Math.max(longest, current);
|
|
128
|
+
}
|
|
129
|
+
return longest;
|
|
130
|
+
}
|
|
131
|
+
function hasSequence(chars) {
|
|
132
|
+
const codes = chars.map((c) => c.toLowerCase().codePointAt(0) ?? 0);
|
|
133
|
+
let ascending = 1;
|
|
134
|
+
let descending = 1;
|
|
135
|
+
for (let i = 1;i < codes.length; i++) {
|
|
136
|
+
const step = (codes[i] ?? 0) - (codes[i - 1] ?? 0);
|
|
137
|
+
ascending = step === 1 ? ascending + 1 : 1;
|
|
138
|
+
descending = step === -1 ? descending + 1 : 1;
|
|
139
|
+
if (ascending >= SEQUENCE_LENGTH || descending >= SEQUENCE_LENGTH) {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
function userInfoTokens(info) {
|
|
146
|
+
const localPart = info.email?.split("@")[0];
|
|
147
|
+
return [localPart, info.firstName, info.lastName, info.username].map((value) => value ? normalizePassword2(value).trim().toLowerCase() : undefined).filter((value) => !!value && value.length >= MIN_USER_INFO_LENGTH);
|
|
148
|
+
}
|
|
149
|
+
function evaluatePassword2(policy, password, userInfo = {}) {
|
|
150
|
+
const chars = [...normalizePassword2(password)];
|
|
151
|
+
const special = new Set([...policy.specialChars]);
|
|
152
|
+
const hasLower = chars.some((c) => LOWER.test(c));
|
|
153
|
+
const hasUpper = chars.some((c) => UPPER.test(c));
|
|
154
|
+
const hasNumber = chars.some((c) => NUMBER.test(c));
|
|
155
|
+
const hasSpecial = chars.some((c) => special.has(c));
|
|
156
|
+
const checks = [];
|
|
157
|
+
checks.push({
|
|
158
|
+
rule: "min_length",
|
|
159
|
+
code: "password.too_short",
|
|
160
|
+
passed: chars.length >= policy.minLength,
|
|
161
|
+
params: { min: policy.minLength }
|
|
162
|
+
});
|
|
163
|
+
checks.push({
|
|
164
|
+
rule: "max_length",
|
|
165
|
+
code: "password.too_long",
|
|
166
|
+
passed: chars.length <= policy.maxLength,
|
|
167
|
+
params: { max: policy.maxLength }
|
|
168
|
+
});
|
|
169
|
+
if (policy.requireLowercase) {
|
|
170
|
+
checks.push({ rule: "lowercase", code: "password.missing_lowercase", passed: hasLower });
|
|
171
|
+
}
|
|
172
|
+
if (policy.requireUppercase) {
|
|
173
|
+
checks.push({ rule: "uppercase", code: "password.missing_uppercase", passed: hasUpper });
|
|
174
|
+
}
|
|
175
|
+
if (policy.requireNumber) {
|
|
176
|
+
checks.push({ rule: "number", code: "password.missing_number", passed: hasNumber });
|
|
177
|
+
}
|
|
178
|
+
if (policy.requireSpecial) {
|
|
179
|
+
checks.push({ rule: "special", code: "password.missing_special", passed: hasSpecial });
|
|
180
|
+
}
|
|
181
|
+
if (policy.minCharacterClasses > 0) {
|
|
182
|
+
const classes = [hasLower, hasUpper, hasNumber, hasSpecial].filter(Boolean).length;
|
|
183
|
+
checks.push({
|
|
184
|
+
rule: "character_classes",
|
|
185
|
+
code: "password.too_few_character_classes",
|
|
186
|
+
passed: classes >= policy.minCharacterClasses,
|
|
187
|
+
params: { min: policy.minCharacterClasses }
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
if (policy.disallowUserInfo) {
|
|
191
|
+
const lower = chars.join("").toLowerCase();
|
|
192
|
+
checks.push({
|
|
193
|
+
rule: "user_info",
|
|
194
|
+
code: "password.contains_user_info",
|
|
195
|
+
passed: !userInfoTokens(userInfo).some((token) => lower.includes(token))
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
if (policy.disallowCommon) {
|
|
199
|
+
checks.push({
|
|
200
|
+
rule: "common",
|
|
201
|
+
code: "password.common",
|
|
202
|
+
passed: !COMMON_PASSWORDS2.has(chars.join("").toLowerCase())
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
if (policy.maxRepeatedChars !== null) {
|
|
206
|
+
checks.push({
|
|
207
|
+
rule: "repeated_characters",
|
|
208
|
+
code: "password.repeated_characters",
|
|
209
|
+
passed: longestRun(chars) <= policy.maxRepeatedChars,
|
|
210
|
+
params: { max: policy.maxRepeatedChars }
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
if (policy.blockSequences) {
|
|
214
|
+
checks.push({ rule: "sequence", code: "password.sequence", passed: !hasSequence(chars) });
|
|
215
|
+
}
|
|
216
|
+
return { ok: checks.every((check) => check.passed), checks };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export { COMMON_PASSWORDS2, DEFAULT_SPECIAL_CHARS2, normalizePassword2, evaluatePassword2 };
|