cauthy-auth 0.27.1 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.mts +12 -3
- package/dist/client.d.ts +12 -3
- package/dist/client.js +130 -15
- package/dist/client.js.map +1 -1
- package/dist/client.mjs +124 -14
- package/dist/client.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +147 -17
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +142 -17
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +27 -1
- package/dist/react.d.ts +27 -1
- package/dist/react.js +146 -19
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +146 -19
- package/dist/react.mjs.map +1 -1
- package/dist/styles.css +6 -0
- package/dist/svelte.d.mts +1 -1
- package/dist/svelte.d.ts +1 -1
- package/dist/svelte.js +118 -13
- package/dist/svelte.js.map +1 -1
- package/dist/svelte.mjs +118 -13
- package/dist/svelte.mjs.map +1 -1
- package/dist/validate-sEXG82po.d.mts +66 -0
- package/dist/validate-sEXG82po.d.ts +66 -0
- package/dist/vue.d.mts +1 -1
- package/dist/vue.d.ts +1 -1
- package/dist/vue.js +118 -13
- package/dist/vue.js.map +1 -1
- package/dist/vue.mjs +118 -13
- package/dist/vue.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/validate-YbEfIzfl.d.mts +0 -19
- package/dist/validate-YbEfIzfl.d.ts +0 -19
package/dist/index.mjs
CHANGED
|
@@ -609,7 +609,111 @@ function createSystem(given = {}) {
|
|
|
609
609
|
// src/validate.ts
|
|
610
610
|
var MIN_PASSWORD = 12;
|
|
611
611
|
var MAX_PASSWORD = 128;
|
|
612
|
+
var FLOOR_PASSWORD = 8;
|
|
612
613
|
var MAX_NAME = 120;
|
|
614
|
+
var CLASSES = ["lowercase", "uppercase", "digit", "symbol"];
|
|
615
|
+
var DEFAULT_PASSWORD_POLICY = Object.freeze({ min: MIN_PASSWORD, max: MAX_PASSWORD, requires: [], min_classes: 0, reject_breached: false });
|
|
616
|
+
var int = (v, fallback) => typeof v === "number" && Number.isFinite(v) ? Math.trunc(v) : fallback;
|
|
617
|
+
function passwordPolicyOf(raw) {
|
|
618
|
+
const r = raw && typeof raw === "object" ? raw : {};
|
|
619
|
+
const min = Math.min(Math.max(int(r.min ?? r.min_length, MIN_PASSWORD) || MIN_PASSWORD, FLOOR_PASSWORD), MAX_PASSWORD);
|
|
620
|
+
const max = Math.max(Math.min(int(r.max ?? r.max_length, MAX_PASSWORD) || MAX_PASSWORD, MAX_PASSWORD), min);
|
|
621
|
+
const asked = Array.isArray(r.requires) ? r.requires : [];
|
|
622
|
+
return {
|
|
623
|
+
min,
|
|
624
|
+
max,
|
|
625
|
+
requires: CLASSES.filter((c) => asked.includes(c)),
|
|
626
|
+
min_classes: Math.min(Math.max(int(r.min_classes ?? r.minClasses, 0), 0), CLASSES.length),
|
|
627
|
+
reject_breached: (r.reject_breached ?? r.rejectBreached) === true
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
function utf8Length(v) {
|
|
631
|
+
let n = 0;
|
|
632
|
+
for (const ch of v) {
|
|
633
|
+
const c = ch.codePointAt(0);
|
|
634
|
+
n += c < 128 ? 1 : c < 2048 ? 2 : c < 65536 ? 3 : 4;
|
|
635
|
+
}
|
|
636
|
+
return n;
|
|
637
|
+
}
|
|
638
|
+
function classesOf(v) {
|
|
639
|
+
const has = /* @__PURE__ */ new Set();
|
|
640
|
+
for (const ch of v) {
|
|
641
|
+
if (/\p{Ll}/u.test(ch)) has.add("lowercase");
|
|
642
|
+
else if (/\p{Lu}/u.test(ch)) has.add("uppercase");
|
|
643
|
+
else if (/\p{Nd}/u.test(ch)) has.add("digit");
|
|
644
|
+
else if (!/\p{L}/u.test(ch)) has.add("symbol");
|
|
645
|
+
}
|
|
646
|
+
return has;
|
|
647
|
+
}
|
|
648
|
+
function passwordProblems(value, policy = DEFAULT_PASSWORD_POLICY) {
|
|
649
|
+
const p = passwordPolicyOf(policy);
|
|
650
|
+
const out2 = [];
|
|
651
|
+
const length = utf8Length(value);
|
|
652
|
+
if (length < p.min) out2.push("too_short");
|
|
653
|
+
if (length > p.max) out2.push("too_long");
|
|
654
|
+
const has = classesOf(value);
|
|
655
|
+
for (const c of p.requires) if (!has.has(c)) out2.push(`needs_${c}`);
|
|
656
|
+
if (p.min_classes > 0 && CLASSES.filter((c) => has.has(c)).length < p.min_classes) out2.push("needs_classes");
|
|
657
|
+
return out2;
|
|
658
|
+
}
|
|
659
|
+
var RULES = {
|
|
660
|
+
en: {
|
|
661
|
+
between: "Between {min} and {max} characters.",
|
|
662
|
+
requires: "Include {kinds}.",
|
|
663
|
+
and: "and",
|
|
664
|
+
classes: "Use at least {n} of these: lowercase letters, uppercase letters, digits, symbols.",
|
|
665
|
+
breached: "Passwords found in known data breaches are not accepted.",
|
|
666
|
+
kinds: { lowercase: "a lowercase letter", uppercase: "an uppercase letter", digit: "a digit", symbol: "a symbol" },
|
|
667
|
+
reasons: {
|
|
668
|
+
too_short: "Use at least {min} characters.",
|
|
669
|
+
too_long: "Use at most {max} characters.",
|
|
670
|
+
needs_lowercase: "Include a lowercase letter.",
|
|
671
|
+
needs_uppercase: "Include an uppercase letter.",
|
|
672
|
+
needs_digit: "Include a digit.",
|
|
673
|
+
needs_symbol: "Include a symbol.",
|
|
674
|
+
needs_classes: "Use at least {n} of these: lowercase letters, uppercase letters, digits, symbols.",
|
|
675
|
+
breached: "This password appears in a known data breach. Choose a different one."
|
|
676
|
+
}
|
|
677
|
+
},
|
|
678
|
+
es: {
|
|
679
|
+
between: "Entre {min} y {max} caracteres.",
|
|
680
|
+
requires: "Incluye {kinds}.",
|
|
681
|
+
and: "y",
|
|
682
|
+
classes: "Usa al menos {n} de estos tipos: min\xFAsculas, may\xFAsculas, n\xFAmeros, s\xEDmbolos.",
|
|
683
|
+
breached: "No se aceptan contrase\xF1as que aparecen en filtraciones de datos conocidas.",
|
|
684
|
+
kinds: { lowercase: "una min\xFAscula", uppercase: "una may\xFAscula", digit: "un n\xFAmero", symbol: "un s\xEDmbolo" },
|
|
685
|
+
reasons: {
|
|
686
|
+
too_short: "Usa al menos {min} caracteres.",
|
|
687
|
+
too_long: "Usa como m\xE1ximo {max} caracteres.",
|
|
688
|
+
needs_lowercase: "Incluye una min\xFAscula.",
|
|
689
|
+
needs_uppercase: "Incluye una may\xFAscula.",
|
|
690
|
+
needs_digit: "Incluye un n\xFAmero.",
|
|
691
|
+
needs_symbol: "Incluye un s\xEDmbolo.",
|
|
692
|
+
needs_classes: "Usa al menos {n} de estos tipos: min\xFAsculas, may\xFAsculas, n\xFAmeros, s\xEDmbolos.",
|
|
693
|
+
breached: "Esta contrase\xF1a aparece en una filtraci\xF3n de datos conocida. Elige otra."
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
var fill = (s, p) => s.replace("{min}", String(p.min)).replace("{max}", String(p.max)).replace("{n}", String(p.min_classes));
|
|
698
|
+
function describePasswordPolicy(policy = DEFAULT_PASSWORD_POLICY, locale = "en") {
|
|
699
|
+
const p = passwordPolicyOf(policy);
|
|
700
|
+
const t = RULES[locale] ?? RULES.en;
|
|
701
|
+
const parts = [fill(t.between, p)];
|
|
702
|
+
if (p.requires.length > 0) {
|
|
703
|
+
const kinds = p.requires.map((c) => t.kinds[c]);
|
|
704
|
+
const listed = kinds.length === 1 ? kinds[0] : `${kinds.slice(0, -1).join(", ")} ${t.and} ${kinds[kinds.length - 1]}`;
|
|
705
|
+
parts.push(t.requires.replace("{kinds}", listed));
|
|
706
|
+
}
|
|
707
|
+
if (p.min_classes > 0) parts.push(fill(t.classes, p));
|
|
708
|
+
if (p.reject_breached) parts.push(t.breached);
|
|
709
|
+
return parts.join(" ");
|
|
710
|
+
}
|
|
711
|
+
function explainPasswordProblems(reasons, policy = DEFAULT_PASSWORD_POLICY, locale = "en") {
|
|
712
|
+
const p = passwordPolicyOf(policy);
|
|
713
|
+
const t = RULES[locale] ?? RULES.en;
|
|
714
|
+
const said = reasons.filter((r) => r in t.reasons).map((r) => fill(t.reasons[r], p));
|
|
715
|
+
return said.length > 0 ? said.join(" ") : describePasswordPolicy(p, locale);
|
|
716
|
+
}
|
|
613
717
|
var EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
614
718
|
function checkEmail(value) {
|
|
615
719
|
const v = typeof value === "string" ? value.trim() : "";
|
|
@@ -622,13 +726,12 @@ function checkPassword(value) {
|
|
|
622
726
|
if (value.length > MAX_PASSWORD) return { field: "password", code: "too_long", message: "That password is too long." };
|
|
623
727
|
return null;
|
|
624
728
|
}
|
|
625
|
-
function checkNewPassword(value) {
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
}
|
|
631
|
-
return null;
|
|
729
|
+
function checkNewPassword(value, policy) {
|
|
730
|
+
if (typeof value !== "string" || value === "") return { field: "password", code: "required", message: "Enter your password." };
|
|
731
|
+
const p = policy ? passwordPolicyOf(policy) : DEFAULT_PASSWORD_POLICY;
|
|
732
|
+
const reasons = passwordProblems(value, p);
|
|
733
|
+
if (reasons.length === 0) return null;
|
|
734
|
+
return { field: "password", code: reasons[0], message: explainPasswordProblems(reasons, p) };
|
|
632
735
|
}
|
|
633
736
|
function checkName(value) {
|
|
634
737
|
if (value === void 0 || value === null || value === "") return null;
|
|
@@ -1087,7 +1190,7 @@ function createCauthy(given = {}) {
|
|
|
1087
1190
|
connectedAt: String(d?.created_at ?? ""),
|
|
1088
1191
|
lastUsedAt: String(d?.last_used_at ?? "")
|
|
1089
1192
|
});
|
|
1090
|
-
const problem = (status2, code, message, field, headers) => json({ error: { code, message, ...field ? { field } : {} } }, status2, headers);
|
|
1193
|
+
const problem = (status2, code, message, field, headers, reasons) => json({ error: { code, message, ...field ? { field } : {}, ...reasons && reasons.length > 0 ? { reasons } : {} } }, status2, headers);
|
|
1091
1194
|
const invalid = (p) => problem(400, p.code, p.message, p.field);
|
|
1092
1195
|
function ownRequest(request) {
|
|
1093
1196
|
const origin = request.headers.get("origin");
|
|
@@ -1145,7 +1248,8 @@ function createCauthy(given = {}) {
|
|
|
1145
1248
|
const b = res.status === 204 || res.status === 202 ? null : await res.json().catch(() => null);
|
|
1146
1249
|
if (res.ok) return { status: res.status, data: b?.data ?? b };
|
|
1147
1250
|
const first = b?.errors?.[0];
|
|
1148
|
-
|
|
1251
|
+
const reasons = Array.isArray(first?.meta?.reasons) ? first.meta.reasons.map(String) : void 0;
|
|
1252
|
+
return { status: res.status, data: null, error: { code: String(first?.code ?? "error"), message: String(first?.message ?? "The request could not be completed."), ...reasons ? { reasons } : {} } };
|
|
1149
1253
|
} catch {
|
|
1150
1254
|
return { status: 503, data: null, error: { code: "unavailable", message: "Sign-in is unavailable right now. Try again in a moment." } };
|
|
1151
1255
|
}
|
|
@@ -1173,6 +1277,19 @@ function createCauthy(given = {}) {
|
|
|
1173
1277
|
return json({ status: "signed_in", session: await toPublic(toSession(s)), ...more }, 200, h);
|
|
1174
1278
|
}
|
|
1175
1279
|
let lookCache = null;
|
|
1280
|
+
let policyCache = null;
|
|
1281
|
+
const keepPolicy = (settings) => {
|
|
1282
|
+
policyCache = { until: Date.now() + LOOK_TTL_MS, policy: settings.error || !settings.data?.password_policy ? null : passwordPolicyOf(settings.data.password_policy) };
|
|
1283
|
+
return policyCache.policy;
|
|
1284
|
+
};
|
|
1285
|
+
async function workspacePolicy(request) {
|
|
1286
|
+
if (policyCache !== null && policyCache.until >= Date.now()) return policyCache.policy;
|
|
1287
|
+
return keepPolicy(await ask(request, "GET", "/v1/auth/settings"));
|
|
1288
|
+
}
|
|
1289
|
+
async function newPasswordProblem(request, value) {
|
|
1290
|
+
const policy = await workspacePolicy(request);
|
|
1291
|
+
return checkNewPassword(value, policy ?? { min: 8, max: 128, requires: [], min_classes: 0, reject_breached: false });
|
|
1292
|
+
}
|
|
1176
1293
|
async function ownRoute(request, url, route) {
|
|
1177
1294
|
const post = request.method === "POST";
|
|
1178
1295
|
const own = [
|
|
@@ -1250,6 +1367,7 @@ function createCauthy(given = {}) {
|
|
|
1250
1367
|
ask(request, "GET", "/v1/auth/sso/connections")
|
|
1251
1368
|
]);
|
|
1252
1369
|
const list = Array.isArray(providers.data) ? providers.data : [];
|
|
1370
|
+
const password = keepPolicy(settings);
|
|
1253
1371
|
const here = place.data?.here === true;
|
|
1254
1372
|
return json({
|
|
1255
1373
|
providers: list.map((p) => ({ id: String(p.provider ?? p.id ?? ""), name: String(p.label ?? p.name ?? p.provider ?? "") })).filter((p) => p.id !== ""),
|
|
@@ -1265,7 +1383,9 @@ function createCauthy(given = {}) {
|
|
|
1265
1383
|
sso: Array.isArray(sso.data) && sso.data.some((c) => c?.discoverable === true),
|
|
1266
1384
|
// The terms a sign-up accepts and the consents it answers, when the workspace asks any.
|
|
1267
1385
|
...settings.data?.legal ? { legal: legalOf(settings.data.legal) } : {},
|
|
1268
|
-
...settings.data?.captcha_enabled === true && settings.data?.captcha_site_key ? { captchaSiteKey: String(settings.data.captcha_site_key) } : {}
|
|
1386
|
+
...settings.data?.captcha_enabled === true && settings.data?.captcha_site_key ? { captchaSiteKey: String(settings.data.captcha_site_key) } : {},
|
|
1387
|
+
// What a new password must be: for the form to say it and check it.
|
|
1388
|
+
...password ? { password } : {}
|
|
1269
1389
|
});
|
|
1270
1390
|
}
|
|
1271
1391
|
if (route === "/sign-in/sso" && !post) {
|
|
@@ -1555,13 +1675,13 @@ function createCauthy(given = {}) {
|
|
|
1555
1675
|
return json({ status: "sent", sentTo: String(res.data?.sent_to ?? ""), channel: String(res.data?.channel ?? "sms") });
|
|
1556
1676
|
}
|
|
1557
1677
|
if (route === "/sign-up/email") {
|
|
1558
|
-
const bad = firstProblem(checkName(b.name), checkEmail(b.email),
|
|
1678
|
+
const bad = firstProblem(checkName(b.name), checkEmail(b.email), await newPasswordProblem(request, b.password), checkOrganization(b.organization));
|
|
1559
1679
|
if (bad) return invalid(bad);
|
|
1560
1680
|
const email = String(b.email).trim();
|
|
1561
1681
|
const organization = typeof b.organization === "string" && b.organization.trim() !== "" ? { organization: { name: b.organization.trim() } } : {};
|
|
1562
1682
|
const legal = { ...b.acceptLegal === true ? { accept_legal: true } : {}, ...b.consents && typeof b.consents === "object" ? { consents: b.consents } : {} };
|
|
1563
1683
|
const made = await ask(request, "POST", "/v1/auth/register", { email, password: b.password, display_name: typeof b.name === "string" ? b.name.trim() : "", ...organization, ...legal }, pages.verifyEmail);
|
|
1564
|
-
if (made.error) return problem(made.status, made.error.code, made.error.message);
|
|
1684
|
+
if (made.error) return problem(made.status, made.error.code, made.error.message, made.error.code === "weak_password" ? "password" : void 0, void 0, made.error.reasons);
|
|
1565
1685
|
if (made.data?.verification_required === true) return json({ status: "verification_required", email });
|
|
1566
1686
|
const res = await ask(request, "POST", "/v1/auth/login", { email, password: b.password });
|
|
1567
1687
|
if (res.error || res.data?.mfa_required === true) return json({ status: "verification_required", email });
|
|
@@ -1577,7 +1697,7 @@ function createCauthy(given = {}) {
|
|
|
1577
1697
|
const mine = await signedInAs(request);
|
|
1578
1698
|
if ("response" in mine) return mine.response;
|
|
1579
1699
|
const as = (path, payload, method = "POST") => ask(request, method, path, payload, void 0, mine.token);
|
|
1580
|
-
const refused = (res, field) => problem(res.status, res.error.code, res.error.message, field, mine.headers);
|
|
1700
|
+
const refused = (res, field) => problem(res.status, res.error.code, res.error.message, field, mine.headers, res.error.reasons);
|
|
1581
1701
|
const password = typeof b.password === "string" ? b.password : "";
|
|
1582
1702
|
if (route === "/me") {
|
|
1583
1703
|
const bad = checkName(b.name);
|
|
@@ -1598,7 +1718,7 @@ function createCauthy(given = {}) {
|
|
|
1598
1718
|
return json({ status: "accepted" }, 200, mine.headers);
|
|
1599
1719
|
}
|
|
1600
1720
|
if (route === "/me/password") {
|
|
1601
|
-
const bad =
|
|
1721
|
+
const bad = await newPasswordProblem(request, b.newPassword);
|
|
1602
1722
|
if (bad) return problem(400, bad.code, bad.message, "newPassword");
|
|
1603
1723
|
const res = await as("/v1/auth/me/password", { current_password: password, new_password: b.newPassword });
|
|
1604
1724
|
if (res.error) return refused(res, res.error.code === "wrong_password" ? "password" : res.error.code === "weak_password" ? "newPassword" : void 0);
|
|
@@ -1691,7 +1811,7 @@ function createCauthy(given = {}) {
|
|
|
1691
1811
|
return json({ status: "removed", id }, 200, mine.headers);
|
|
1692
1812
|
}
|
|
1693
1813
|
if (route === "/me/upgrade") {
|
|
1694
|
-
const bad = firstProblem(checkName(b.name), checkEmail(b.email),
|
|
1814
|
+
const bad = firstProblem(checkName(b.name), checkEmail(b.email), await newPasswordProblem(request, b.password));
|
|
1695
1815
|
if (bad) return invalid(bad);
|
|
1696
1816
|
const res = await as("/v1/auth/anonymous/upgrade", { email: String(b.email).trim(), password: b.password, display_name: typeof b.name === "string" ? b.name.trim() : "" });
|
|
1697
1817
|
if (res.error) return refused(res, res.error.code === "weak_password" ? "password" : "email");
|
|
@@ -1758,10 +1878,10 @@ function createCauthy(given = {}) {
|
|
|
1758
1878
|
const badLink = () => problem(422, "invalid_token", "This link is not valid, or it has expired.");
|
|
1759
1879
|
if (route === "/reset-password") {
|
|
1760
1880
|
if (!isToken(b.token)) return badLink();
|
|
1761
|
-
const bad =
|
|
1881
|
+
const bad = await newPasswordProblem(request, b.password);
|
|
1762
1882
|
if (bad) return invalid(bad);
|
|
1763
1883
|
const res = await ask(request, "POST", "/v1/auth/password/change", { token: String(b.token), new_password: b.password });
|
|
1764
|
-
if (res.error) return problem(res.status, res.error.code, res.error.message, res.error.code === "weak_password" ? "password" : void 0);
|
|
1884
|
+
if (res.error) return problem(res.status, res.error.code, res.error.message, res.error.code === "weak_password" ? "password" : void 0, void 0, res.error.reasons);
|
|
1765
1885
|
return json({ status: "changed" });
|
|
1766
1886
|
}
|
|
1767
1887
|
if (route === "/verify-email") {
|
|
@@ -2134,6 +2254,7 @@ export {
|
|
|
2134
2254
|
CauthyConfigError,
|
|
2135
2255
|
CauthyTokenError,
|
|
2136
2256
|
CauthyWebhookError,
|
|
2257
|
+
DEFAULT_PASSWORD_POLICY,
|
|
2137
2258
|
MIN_PASSWORD,
|
|
2138
2259
|
can,
|
|
2139
2260
|
checkCode,
|
|
@@ -2145,6 +2266,10 @@ export {
|
|
|
2145
2266
|
createCauthy,
|
|
2146
2267
|
createSystem,
|
|
2147
2268
|
definePolicy,
|
|
2269
|
+
describePasswordPolicy,
|
|
2270
|
+
explainPasswordProblems,
|
|
2271
|
+
passwordPolicyOf,
|
|
2272
|
+
passwordProblems,
|
|
2148
2273
|
policySource,
|
|
2149
2274
|
pullPolicy,
|
|
2150
2275
|
pushPolicy,
|