cauthy-auth 0.27.0 → 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/cli.js +3 -1
- package/dist/client.d.mts +12 -3
- package/dist/client.d.ts +12 -3
- package/dist/client.js +132 -17
- package/dist/client.js.map +1 -1
- package/dist/client.mjs +126 -16
- package/dist/client.mjs.map +1 -1
- package/dist/index.d.mts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +152 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +147 -20
- 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 +150 -23
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +150 -23
- 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 +120 -15
- package/dist/svelte.js.map +1 -1
- package/dist/svelte.mjs +120 -15
- 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 +120 -15
- package/dist/vue.js.map +1 -1
- package/dist/vue.mjs +120 -15
- package/dist/vue.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/validate-BTRit1O1.d.mts +0 -19
- package/dist/validate-BTRit1O1.d.ts +0 -19
package/dist/index.mjs
CHANGED
|
@@ -495,7 +495,9 @@ function createSystem(given = {}) {
|
|
|
495
495
|
async function fail(res, what) {
|
|
496
496
|
const body = await res.json().catch(() => null);
|
|
497
497
|
const first = body?.errors?.[0];
|
|
498
|
-
|
|
498
|
+
const code = typeof body?.error === "string" ? body.error : first?.code;
|
|
499
|
+
const message = typeof body?.error === "string" ? body.error_description : first?.message;
|
|
500
|
+
throw new CauthyApiError(res.status, code ?? "error", `Cauthy refused ${what} (${res.status}): ${message ?? res.statusText}`);
|
|
499
501
|
}
|
|
500
502
|
let held;
|
|
501
503
|
let asking;
|
|
@@ -605,9 +607,113 @@ function createSystem(given = {}) {
|
|
|
605
607
|
}
|
|
606
608
|
|
|
607
609
|
// src/validate.ts
|
|
608
|
-
var MIN_PASSWORD =
|
|
609
|
-
var MAX_PASSWORD =
|
|
610
|
+
var MIN_PASSWORD = 12;
|
|
611
|
+
var MAX_PASSWORD = 128;
|
|
612
|
+
var FLOOR_PASSWORD = 8;
|
|
610
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
|
+
}
|
|
611
717
|
var EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
612
718
|
function checkEmail(value) {
|
|
613
719
|
const v = typeof value === "string" ? value.trim() : "";
|
|
@@ -620,13 +726,12 @@ function checkPassword(value) {
|
|
|
620
726
|
if (value.length > MAX_PASSWORD) return { field: "password", code: "too_long", message: "That password is too long." };
|
|
621
727
|
return null;
|
|
622
728
|
}
|
|
623
|
-
function checkNewPassword(value) {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
}
|
|
629
|
-
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) };
|
|
630
735
|
}
|
|
631
736
|
function checkName(value) {
|
|
632
737
|
if (value === void 0 || value === null || value === "") return null;
|
|
@@ -1085,7 +1190,7 @@ function createCauthy(given = {}) {
|
|
|
1085
1190
|
connectedAt: String(d?.created_at ?? ""),
|
|
1086
1191
|
lastUsedAt: String(d?.last_used_at ?? "")
|
|
1087
1192
|
});
|
|
1088
|
-
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);
|
|
1089
1194
|
const invalid = (p) => problem(400, p.code, p.message, p.field);
|
|
1090
1195
|
function ownRequest(request) {
|
|
1091
1196
|
const origin = request.headers.get("origin");
|
|
@@ -1143,7 +1248,8 @@ function createCauthy(given = {}) {
|
|
|
1143
1248
|
const b = res.status === 204 || res.status === 202 ? null : await res.json().catch(() => null);
|
|
1144
1249
|
if (res.ok) return { status: res.status, data: b?.data ?? b };
|
|
1145
1250
|
const first = b?.errors?.[0];
|
|
1146
|
-
|
|
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 } : {} } };
|
|
1147
1253
|
} catch {
|
|
1148
1254
|
return { status: 503, data: null, error: { code: "unavailable", message: "Sign-in is unavailable right now. Try again in a moment." } };
|
|
1149
1255
|
}
|
|
@@ -1171,6 +1277,19 @@ function createCauthy(given = {}) {
|
|
|
1171
1277
|
return json({ status: "signed_in", session: await toPublic(toSession(s)), ...more }, 200, h);
|
|
1172
1278
|
}
|
|
1173
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
|
+
}
|
|
1174
1293
|
async function ownRoute(request, url, route) {
|
|
1175
1294
|
const post = request.method === "POST";
|
|
1176
1295
|
const own = [
|
|
@@ -1248,6 +1367,7 @@ function createCauthy(given = {}) {
|
|
|
1248
1367
|
ask(request, "GET", "/v1/auth/sso/connections")
|
|
1249
1368
|
]);
|
|
1250
1369
|
const list = Array.isArray(providers.data) ? providers.data : [];
|
|
1370
|
+
const password = keepPolicy(settings);
|
|
1251
1371
|
const here = place.data?.here === true;
|
|
1252
1372
|
return json({
|
|
1253
1373
|
providers: list.map((p) => ({ id: String(p.provider ?? p.id ?? ""), name: String(p.label ?? p.name ?? p.provider ?? "") })).filter((p) => p.id !== ""),
|
|
@@ -1263,7 +1383,9 @@ function createCauthy(given = {}) {
|
|
|
1263
1383
|
sso: Array.isArray(sso.data) && sso.data.some((c) => c?.discoverable === true),
|
|
1264
1384
|
// The terms a sign-up accepts and the consents it answers, when the workspace asks any.
|
|
1265
1385
|
...settings.data?.legal ? { legal: legalOf(settings.data.legal) } : {},
|
|
1266
|
-
...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 } : {}
|
|
1267
1389
|
});
|
|
1268
1390
|
}
|
|
1269
1391
|
if (route === "/sign-in/sso" && !post) {
|
|
@@ -1553,13 +1675,13 @@ function createCauthy(given = {}) {
|
|
|
1553
1675
|
return json({ status: "sent", sentTo: String(res.data?.sent_to ?? ""), channel: String(res.data?.channel ?? "sms") });
|
|
1554
1676
|
}
|
|
1555
1677
|
if (route === "/sign-up/email") {
|
|
1556
|
-
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));
|
|
1557
1679
|
if (bad) return invalid(bad);
|
|
1558
1680
|
const email = String(b.email).trim();
|
|
1559
1681
|
const organization = typeof b.organization === "string" && b.organization.trim() !== "" ? { organization: { name: b.organization.trim() } } : {};
|
|
1560
1682
|
const legal = { ...b.acceptLegal === true ? { accept_legal: true } : {}, ...b.consents && typeof b.consents === "object" ? { consents: b.consents } : {} };
|
|
1561
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);
|
|
1562
|
-
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);
|
|
1563
1685
|
if (made.data?.verification_required === true) return json({ status: "verification_required", email });
|
|
1564
1686
|
const res = await ask(request, "POST", "/v1/auth/login", { email, password: b.password });
|
|
1565
1687
|
if (res.error || res.data?.mfa_required === true) return json({ status: "verification_required", email });
|
|
@@ -1575,7 +1697,7 @@ function createCauthy(given = {}) {
|
|
|
1575
1697
|
const mine = await signedInAs(request);
|
|
1576
1698
|
if ("response" in mine) return mine.response;
|
|
1577
1699
|
const as = (path, payload, method = "POST") => ask(request, method, path, payload, void 0, mine.token);
|
|
1578
|
-
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);
|
|
1579
1701
|
const password = typeof b.password === "string" ? b.password : "";
|
|
1580
1702
|
if (route === "/me") {
|
|
1581
1703
|
const bad = checkName(b.name);
|
|
@@ -1596,7 +1718,7 @@ function createCauthy(given = {}) {
|
|
|
1596
1718
|
return json({ status: "accepted" }, 200, mine.headers);
|
|
1597
1719
|
}
|
|
1598
1720
|
if (route === "/me/password") {
|
|
1599
|
-
const bad =
|
|
1721
|
+
const bad = await newPasswordProblem(request, b.newPassword);
|
|
1600
1722
|
if (bad) return problem(400, bad.code, bad.message, "newPassword");
|
|
1601
1723
|
const res = await as("/v1/auth/me/password", { current_password: password, new_password: b.newPassword });
|
|
1602
1724
|
if (res.error) return refused(res, res.error.code === "wrong_password" ? "password" : res.error.code === "weak_password" ? "newPassword" : void 0);
|
|
@@ -1689,7 +1811,7 @@ function createCauthy(given = {}) {
|
|
|
1689
1811
|
return json({ status: "removed", id }, 200, mine.headers);
|
|
1690
1812
|
}
|
|
1691
1813
|
if (route === "/me/upgrade") {
|
|
1692
|
-
const bad = firstProblem(checkName(b.name), checkEmail(b.email),
|
|
1814
|
+
const bad = firstProblem(checkName(b.name), checkEmail(b.email), await newPasswordProblem(request, b.password));
|
|
1693
1815
|
if (bad) return invalid(bad);
|
|
1694
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() : "" });
|
|
1695
1817
|
if (res.error) return refused(res, res.error.code === "weak_password" ? "password" : "email");
|
|
@@ -1756,10 +1878,10 @@ function createCauthy(given = {}) {
|
|
|
1756
1878
|
const badLink = () => problem(422, "invalid_token", "This link is not valid, or it has expired.");
|
|
1757
1879
|
if (route === "/reset-password") {
|
|
1758
1880
|
if (!isToken(b.token)) return badLink();
|
|
1759
|
-
const bad =
|
|
1881
|
+
const bad = await newPasswordProblem(request, b.password);
|
|
1760
1882
|
if (bad) return invalid(bad);
|
|
1761
1883
|
const res = await ask(request, "POST", "/v1/auth/password/change", { token: String(b.token), new_password: b.password });
|
|
1762
|
-
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);
|
|
1763
1885
|
return json({ status: "changed" });
|
|
1764
1886
|
}
|
|
1765
1887
|
if (route === "/verify-email") {
|
|
@@ -2132,6 +2254,7 @@ export {
|
|
|
2132
2254
|
CauthyConfigError,
|
|
2133
2255
|
CauthyTokenError,
|
|
2134
2256
|
CauthyWebhookError,
|
|
2257
|
+
DEFAULT_PASSWORD_POLICY,
|
|
2135
2258
|
MIN_PASSWORD,
|
|
2136
2259
|
can,
|
|
2137
2260
|
checkCode,
|
|
@@ -2143,6 +2266,10 @@ export {
|
|
|
2143
2266
|
createCauthy,
|
|
2144
2267
|
createSystem,
|
|
2145
2268
|
definePolicy,
|
|
2269
|
+
describePasswordPolicy,
|
|
2270
|
+
explainPasswordProblems,
|
|
2271
|
+
passwordPolicyOf,
|
|
2272
|
+
passwordProblems,
|
|
2146
2273
|
policySource,
|
|
2147
2274
|
pullPolicy,
|
|
2148
2275
|
pushPolicy,
|