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/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { C as Cauthy, a as CauthyEvent, b as CauthyWebhookError, R as Resolved, V as VerifyWebhookOptions, c as createCauthy, v as verifyWebhook } from './cauthy-XkIbG5gE.mjs';
2
2
  export { C as CauthyApiError, a as CauthyConfigError, b as CauthyOptions, c as CauthySystem, J as JsonValue, N as NewMember, O as Organization, d as OrganizationMember, P as PermissionOf, e as Permitted, f as Policy, g as PolicyPermission, h as PolicyRole, i as PublicSession, j as PushPolicyOptions, R as RoleOf, S as Session, k as SessionOrganization, l as SessionStatus, m as SessionUser, n as SystemKey, o as SystemOptions, p as can, q as createSystem, r as definePolicy, s as policySource, t as pullPolicy, u as pushPolicy } from './system-BxMsSIu1.mjs';
3
3
  export { CauthyTheme, ImportedTheme, PushThemeOptions, StackmythThemeOptions, StackmythTokens, ThemeFont, ThemeTexts, pushTheme, themeFromStackmyth, tokensFromStackmyth } from './theme.mjs';
4
- export { A as AuthField, F as FieldProblem, M as MIN_PASSWORD, c as checkCode, a as checkEmail, b as checkName, d as checkNewPassword, e as checkPassword } from './validate-YbEfIzfl.mjs';
4
+ export { A as AuthField, D as DEFAULT_PASSWORD_POLICY, F as FieldProblem, M as MIN_PASSWORD, P as PasswordClass, a as PasswordLocale, b as PasswordPolicy, c as PasswordReason, d as checkCode, e as checkEmail, f as checkName, g as checkNewPassword, h as checkPassword, i as describePasswordPolicy, j as explainPasswordProblems, p as passwordPolicyOf, k as passwordProblems } from './validate-sEXG82po.mjs';
5
5
  import { JWTVerifyGetKey } from 'jose';
6
6
 
7
7
  interface ConnectOptions {
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { C as Cauthy, a as CauthyEvent, b as CauthyWebhookError, R as Resolved, V as VerifyWebhookOptions, c as createCauthy, v as verifyWebhook } from './cauthy-DGAy45G2.js';
2
2
  export { C as CauthyApiError, a as CauthyConfigError, b as CauthyOptions, c as CauthySystem, J as JsonValue, N as NewMember, O as Organization, d as OrganizationMember, P as PermissionOf, e as Permitted, f as Policy, g as PolicyPermission, h as PolicyRole, i as PublicSession, j as PushPolicyOptions, R as RoleOf, S as Session, k as SessionOrganization, l as SessionStatus, m as SessionUser, n as SystemKey, o as SystemOptions, p as can, q as createSystem, r as definePolicy, s as policySource, t as pullPolicy, u as pushPolicy } from './system-DTIeDXDP.js';
3
3
  export { CauthyTheme, ImportedTheme, PushThemeOptions, StackmythThemeOptions, StackmythTokens, ThemeFont, ThemeTexts, pushTheme, themeFromStackmyth, tokensFromStackmyth } from './theme.js';
4
- export { A as AuthField, F as FieldProblem, M as MIN_PASSWORD, c as checkCode, a as checkEmail, b as checkName, d as checkNewPassword, e as checkPassword } from './validate-YbEfIzfl.js';
4
+ export { A as AuthField, D as DEFAULT_PASSWORD_POLICY, F as FieldProblem, M as MIN_PASSWORD, P as PasswordClass, a as PasswordLocale, b as PasswordPolicy, c as PasswordReason, d as checkCode, e as checkEmail, f as checkName, g as checkNewPassword, h as checkPassword, i as describePasswordPolicy, j as explainPasswordProblems, p as passwordPolicyOf, k as passwordProblems } from './validate-sEXG82po.js';
5
5
  import { JWTVerifyGetKey } from 'jose';
6
6
 
7
7
  interface ConnectOptions {
package/dist/index.js CHANGED
@@ -24,6 +24,7 @@ __export(src_exports, {
24
24
  CauthyConfigError: () => CauthyConfigError,
25
25
  CauthyTokenError: () => CauthyTokenError,
26
26
  CauthyWebhookError: () => CauthyWebhookError,
27
+ DEFAULT_PASSWORD_POLICY: () => DEFAULT_PASSWORD_POLICY,
27
28
  MIN_PASSWORD: () => MIN_PASSWORD,
28
29
  can: () => can,
29
30
  checkCode: () => checkCode,
@@ -35,6 +36,10 @@ __export(src_exports, {
35
36
  createCauthy: () => createCauthy,
36
37
  createSystem: () => createSystem,
37
38
  definePolicy: () => definePolicy,
39
+ describePasswordPolicy: () => describePasswordPolicy,
40
+ explainPasswordProblems: () => explainPasswordProblems,
41
+ passwordPolicyOf: () => passwordPolicyOf,
42
+ passwordProblems: () => passwordProblems,
38
43
  policySource: () => policySource,
39
44
  pullPolicy: () => pullPolicy,
40
45
  pushPolicy: () => pushPolicy,
@@ -657,7 +662,111 @@ function createSystem(given = {}) {
657
662
  // src/validate.ts
658
663
  var MIN_PASSWORD = 12;
659
664
  var MAX_PASSWORD = 128;
665
+ var FLOOR_PASSWORD = 8;
660
666
  var MAX_NAME = 120;
667
+ var CLASSES = ["lowercase", "uppercase", "digit", "symbol"];
668
+ var DEFAULT_PASSWORD_POLICY = Object.freeze({ min: MIN_PASSWORD, max: MAX_PASSWORD, requires: [], min_classes: 0, reject_breached: false });
669
+ var int = (v, fallback) => typeof v === "number" && Number.isFinite(v) ? Math.trunc(v) : fallback;
670
+ function passwordPolicyOf(raw) {
671
+ const r = raw && typeof raw === "object" ? raw : {};
672
+ const min = Math.min(Math.max(int(r.min ?? r.min_length, MIN_PASSWORD) || MIN_PASSWORD, FLOOR_PASSWORD), MAX_PASSWORD);
673
+ const max = Math.max(Math.min(int(r.max ?? r.max_length, MAX_PASSWORD) || MAX_PASSWORD, MAX_PASSWORD), min);
674
+ const asked = Array.isArray(r.requires) ? r.requires : [];
675
+ return {
676
+ min,
677
+ max,
678
+ requires: CLASSES.filter((c) => asked.includes(c)),
679
+ min_classes: Math.min(Math.max(int(r.min_classes ?? r.minClasses, 0), 0), CLASSES.length),
680
+ reject_breached: (r.reject_breached ?? r.rejectBreached) === true
681
+ };
682
+ }
683
+ function utf8Length(v) {
684
+ let n = 0;
685
+ for (const ch of v) {
686
+ const c = ch.codePointAt(0);
687
+ n += c < 128 ? 1 : c < 2048 ? 2 : c < 65536 ? 3 : 4;
688
+ }
689
+ return n;
690
+ }
691
+ function classesOf(v) {
692
+ const has = /* @__PURE__ */ new Set();
693
+ for (const ch of v) {
694
+ if (/\p{Ll}/u.test(ch)) has.add("lowercase");
695
+ else if (/\p{Lu}/u.test(ch)) has.add("uppercase");
696
+ else if (/\p{Nd}/u.test(ch)) has.add("digit");
697
+ else if (!/\p{L}/u.test(ch)) has.add("symbol");
698
+ }
699
+ return has;
700
+ }
701
+ function passwordProblems(value, policy = DEFAULT_PASSWORD_POLICY) {
702
+ const p = passwordPolicyOf(policy);
703
+ const out2 = [];
704
+ const length = utf8Length(value);
705
+ if (length < p.min) out2.push("too_short");
706
+ if (length > p.max) out2.push("too_long");
707
+ const has = classesOf(value);
708
+ for (const c of p.requires) if (!has.has(c)) out2.push(`needs_${c}`);
709
+ if (p.min_classes > 0 && CLASSES.filter((c) => has.has(c)).length < p.min_classes) out2.push("needs_classes");
710
+ return out2;
711
+ }
712
+ var RULES = {
713
+ en: {
714
+ between: "Between {min} and {max} characters.",
715
+ requires: "Include {kinds}.",
716
+ and: "and",
717
+ classes: "Use at least {n} of these: lowercase letters, uppercase letters, digits, symbols.",
718
+ breached: "Passwords found in known data breaches are not accepted.",
719
+ kinds: { lowercase: "a lowercase letter", uppercase: "an uppercase letter", digit: "a digit", symbol: "a symbol" },
720
+ reasons: {
721
+ too_short: "Use at least {min} characters.",
722
+ too_long: "Use at most {max} characters.",
723
+ needs_lowercase: "Include a lowercase letter.",
724
+ needs_uppercase: "Include an uppercase letter.",
725
+ needs_digit: "Include a digit.",
726
+ needs_symbol: "Include a symbol.",
727
+ needs_classes: "Use at least {n} of these: lowercase letters, uppercase letters, digits, symbols.",
728
+ breached: "This password appears in a known data breach. Choose a different one."
729
+ }
730
+ },
731
+ es: {
732
+ between: "Entre {min} y {max} caracteres.",
733
+ requires: "Incluye {kinds}.",
734
+ and: "y",
735
+ classes: "Usa al menos {n} de estos tipos: min\xFAsculas, may\xFAsculas, n\xFAmeros, s\xEDmbolos.",
736
+ breached: "No se aceptan contrase\xF1as que aparecen en filtraciones de datos conocidas.",
737
+ kinds: { lowercase: "una min\xFAscula", uppercase: "una may\xFAscula", digit: "un n\xFAmero", symbol: "un s\xEDmbolo" },
738
+ reasons: {
739
+ too_short: "Usa al menos {min} caracteres.",
740
+ too_long: "Usa como m\xE1ximo {max} caracteres.",
741
+ needs_lowercase: "Incluye una min\xFAscula.",
742
+ needs_uppercase: "Incluye una may\xFAscula.",
743
+ needs_digit: "Incluye un n\xFAmero.",
744
+ needs_symbol: "Incluye un s\xEDmbolo.",
745
+ needs_classes: "Usa al menos {n} de estos tipos: min\xFAsculas, may\xFAsculas, n\xFAmeros, s\xEDmbolos.",
746
+ breached: "Esta contrase\xF1a aparece en una filtraci\xF3n de datos conocida. Elige otra."
747
+ }
748
+ }
749
+ };
750
+ var fill = (s, p) => s.replace("{min}", String(p.min)).replace("{max}", String(p.max)).replace("{n}", String(p.min_classes));
751
+ function describePasswordPolicy(policy = DEFAULT_PASSWORD_POLICY, locale = "en") {
752
+ const p = passwordPolicyOf(policy);
753
+ const t = RULES[locale] ?? RULES.en;
754
+ const parts = [fill(t.between, p)];
755
+ if (p.requires.length > 0) {
756
+ const kinds = p.requires.map((c) => t.kinds[c]);
757
+ const listed = kinds.length === 1 ? kinds[0] : `${kinds.slice(0, -1).join(", ")} ${t.and} ${kinds[kinds.length - 1]}`;
758
+ parts.push(t.requires.replace("{kinds}", listed));
759
+ }
760
+ if (p.min_classes > 0) parts.push(fill(t.classes, p));
761
+ if (p.reject_breached) parts.push(t.breached);
762
+ return parts.join(" ");
763
+ }
764
+ function explainPasswordProblems(reasons, policy = DEFAULT_PASSWORD_POLICY, locale = "en") {
765
+ const p = passwordPolicyOf(policy);
766
+ const t = RULES[locale] ?? RULES.en;
767
+ const said = reasons.filter((r) => r in t.reasons).map((r) => fill(t.reasons[r], p));
768
+ return said.length > 0 ? said.join(" ") : describePasswordPolicy(p, locale);
769
+ }
661
770
  var EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
662
771
  function checkEmail(value) {
663
772
  const v = typeof value === "string" ? value.trim() : "";
@@ -670,13 +779,12 @@ function checkPassword(value) {
670
779
  if (value.length > MAX_PASSWORD) return { field: "password", code: "too_long", message: "That password is too long." };
671
780
  return null;
672
781
  }
673
- function checkNewPassword(value) {
674
- const basic = checkPassword(value);
675
- if (basic) return basic;
676
- if (value.length < MIN_PASSWORD) {
677
- return { field: "password", code: "too_short", message: `Use at least ${MIN_PASSWORD} characters.` };
678
- }
679
- return null;
782
+ function checkNewPassword(value, policy) {
783
+ if (typeof value !== "string" || value === "") return { field: "password", code: "required", message: "Enter your password." };
784
+ const p = policy ? passwordPolicyOf(policy) : DEFAULT_PASSWORD_POLICY;
785
+ const reasons = passwordProblems(value, p);
786
+ if (reasons.length === 0) return null;
787
+ return { field: "password", code: reasons[0], message: explainPasswordProblems(reasons, p) };
680
788
  }
681
789
  function checkName(value) {
682
790
  if (value === void 0 || value === null || value === "") return null;
@@ -1135,7 +1243,7 @@ function createCauthy(given = {}) {
1135
1243
  connectedAt: String(d?.created_at ?? ""),
1136
1244
  lastUsedAt: String(d?.last_used_at ?? "")
1137
1245
  });
1138
- const problem = (status2, code, message, field, headers) => json({ error: { code, message, ...field ? { field } : {} } }, status2, headers);
1246
+ const problem = (status2, code, message, field, headers, reasons) => json({ error: { code, message, ...field ? { field } : {}, ...reasons && reasons.length > 0 ? { reasons } : {} } }, status2, headers);
1139
1247
  const invalid = (p) => problem(400, p.code, p.message, p.field);
1140
1248
  function ownRequest(request) {
1141
1249
  const origin = request.headers.get("origin");
@@ -1193,7 +1301,8 @@ function createCauthy(given = {}) {
1193
1301
  const b = res.status === 204 || res.status === 202 ? null : await res.json().catch(() => null);
1194
1302
  if (res.ok) return { status: res.status, data: b?.data ?? b };
1195
1303
  const first = b?.errors?.[0];
1196
- return { status: res.status, data: null, error: { code: String(first?.code ?? "error"), message: String(first?.message ?? "The request could not be completed.") } };
1304
+ const reasons = Array.isArray(first?.meta?.reasons) ? first.meta.reasons.map(String) : void 0;
1305
+ return { status: res.status, data: null, error: { code: String(first?.code ?? "error"), message: String(first?.message ?? "The request could not be completed."), ...reasons ? { reasons } : {} } };
1197
1306
  } catch {
1198
1307
  return { status: 503, data: null, error: { code: "unavailable", message: "Sign-in is unavailable right now. Try again in a moment." } };
1199
1308
  }
@@ -1221,6 +1330,19 @@ function createCauthy(given = {}) {
1221
1330
  return json({ status: "signed_in", session: await toPublic(toSession(s)), ...more }, 200, h);
1222
1331
  }
1223
1332
  let lookCache = null;
1333
+ let policyCache = null;
1334
+ const keepPolicy = (settings) => {
1335
+ policyCache = { until: Date.now() + LOOK_TTL_MS, policy: settings.error || !settings.data?.password_policy ? null : passwordPolicyOf(settings.data.password_policy) };
1336
+ return policyCache.policy;
1337
+ };
1338
+ async function workspacePolicy(request) {
1339
+ if (policyCache !== null && policyCache.until >= Date.now()) return policyCache.policy;
1340
+ return keepPolicy(await ask(request, "GET", "/v1/auth/settings"));
1341
+ }
1342
+ async function newPasswordProblem(request, value) {
1343
+ const policy = await workspacePolicy(request);
1344
+ return checkNewPassword(value, policy ?? { min: 8, max: 128, requires: [], min_classes: 0, reject_breached: false });
1345
+ }
1224
1346
  async function ownRoute(request, url, route) {
1225
1347
  const post = request.method === "POST";
1226
1348
  const own = [
@@ -1298,6 +1420,7 @@ function createCauthy(given = {}) {
1298
1420
  ask(request, "GET", "/v1/auth/sso/connections")
1299
1421
  ]);
1300
1422
  const list = Array.isArray(providers.data) ? providers.data : [];
1423
+ const password = keepPolicy(settings);
1301
1424
  const here = place.data?.here === true;
1302
1425
  return json({
1303
1426
  providers: list.map((p) => ({ id: String(p.provider ?? p.id ?? ""), name: String(p.label ?? p.name ?? p.provider ?? "") })).filter((p) => p.id !== ""),
@@ -1313,7 +1436,9 @@ function createCauthy(given = {}) {
1313
1436
  sso: Array.isArray(sso.data) && sso.data.some((c) => c?.discoverable === true),
1314
1437
  // The terms a sign-up accepts and the consents it answers, when the workspace asks any.
1315
1438
  ...settings.data?.legal ? { legal: legalOf(settings.data.legal) } : {},
1316
- ...settings.data?.captcha_enabled === true && settings.data?.captcha_site_key ? { captchaSiteKey: String(settings.data.captcha_site_key) } : {}
1439
+ ...settings.data?.captcha_enabled === true && settings.data?.captcha_site_key ? { captchaSiteKey: String(settings.data.captcha_site_key) } : {},
1440
+ // What a new password must be: for the form to say it and check it.
1441
+ ...password ? { password } : {}
1317
1442
  });
1318
1443
  }
1319
1444
  if (route === "/sign-in/sso" && !post) {
@@ -1603,13 +1728,13 @@ function createCauthy(given = {}) {
1603
1728
  return json({ status: "sent", sentTo: String(res.data?.sent_to ?? ""), channel: String(res.data?.channel ?? "sms") });
1604
1729
  }
1605
1730
  if (route === "/sign-up/email") {
1606
- const bad = firstProblem(checkName(b.name), checkEmail(b.email), checkNewPassword(b.password), checkOrganization(b.organization));
1731
+ const bad = firstProblem(checkName(b.name), checkEmail(b.email), await newPasswordProblem(request, b.password), checkOrganization(b.organization));
1607
1732
  if (bad) return invalid(bad);
1608
1733
  const email = String(b.email).trim();
1609
1734
  const organization = typeof b.organization === "string" && b.organization.trim() !== "" ? { organization: { name: b.organization.trim() } } : {};
1610
1735
  const legal = { ...b.acceptLegal === true ? { accept_legal: true } : {}, ...b.consents && typeof b.consents === "object" ? { consents: b.consents } : {} };
1611
1736
  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);
1612
- if (made.error) return problem(made.status, made.error.code, made.error.message);
1737
+ 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);
1613
1738
  if (made.data?.verification_required === true) return json({ status: "verification_required", email });
1614
1739
  const res = await ask(request, "POST", "/v1/auth/login", { email, password: b.password });
1615
1740
  if (res.error || res.data?.mfa_required === true) return json({ status: "verification_required", email });
@@ -1625,7 +1750,7 @@ function createCauthy(given = {}) {
1625
1750
  const mine = await signedInAs(request);
1626
1751
  if ("response" in mine) return mine.response;
1627
1752
  const as = (path, payload, method = "POST") => ask(request, method, path, payload, void 0, mine.token);
1628
- const refused = (res, field) => problem(res.status, res.error.code, res.error.message, field, mine.headers);
1753
+ const refused = (res, field) => problem(res.status, res.error.code, res.error.message, field, mine.headers, res.error.reasons);
1629
1754
  const password = typeof b.password === "string" ? b.password : "";
1630
1755
  if (route === "/me") {
1631
1756
  const bad = checkName(b.name);
@@ -1646,7 +1771,7 @@ function createCauthy(given = {}) {
1646
1771
  return json({ status: "accepted" }, 200, mine.headers);
1647
1772
  }
1648
1773
  if (route === "/me/password") {
1649
- const bad = checkNewPassword(b.newPassword);
1774
+ const bad = await newPasswordProblem(request, b.newPassword);
1650
1775
  if (bad) return problem(400, bad.code, bad.message, "newPassword");
1651
1776
  const res = await as("/v1/auth/me/password", { current_password: password, new_password: b.newPassword });
1652
1777
  if (res.error) return refused(res, res.error.code === "wrong_password" ? "password" : res.error.code === "weak_password" ? "newPassword" : void 0);
@@ -1739,7 +1864,7 @@ function createCauthy(given = {}) {
1739
1864
  return json({ status: "removed", id }, 200, mine.headers);
1740
1865
  }
1741
1866
  if (route === "/me/upgrade") {
1742
- const bad = firstProblem(checkName(b.name), checkEmail(b.email), checkNewPassword(b.password));
1867
+ const bad = firstProblem(checkName(b.name), checkEmail(b.email), await newPasswordProblem(request, b.password));
1743
1868
  if (bad) return invalid(bad);
1744
1869
  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() : "" });
1745
1870
  if (res.error) return refused(res, res.error.code === "weak_password" ? "password" : "email");
@@ -1806,10 +1931,10 @@ function createCauthy(given = {}) {
1806
1931
  const badLink = () => problem(422, "invalid_token", "This link is not valid, or it has expired.");
1807
1932
  if (route === "/reset-password") {
1808
1933
  if (!isToken(b.token)) return badLink();
1809
- const bad = checkNewPassword(b.password);
1934
+ const bad = await newPasswordProblem(request, b.password);
1810
1935
  if (bad) return invalid(bad);
1811
1936
  const res = await ask(request, "POST", "/v1/auth/password/change", { token: String(b.token), new_password: b.password });
1812
- if (res.error) return problem(res.status, res.error.code, res.error.message, res.error.code === "weak_password" ? "password" : void 0);
1937
+ 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);
1813
1938
  return json({ status: "changed" });
1814
1939
  }
1815
1940
  if (route === "/verify-email") {
@@ -2183,6 +2308,7 @@ async function verifyToken(from, options = {}) {
2183
2308
  CauthyConfigError,
2184
2309
  CauthyTokenError,
2185
2310
  CauthyWebhookError,
2311
+ DEFAULT_PASSWORD_POLICY,
2186
2312
  MIN_PASSWORD,
2187
2313
  can,
2188
2314
  checkCode,
@@ -2194,6 +2320,10 @@ async function verifyToken(from, options = {}) {
2194
2320
  createCauthy,
2195
2321
  createSystem,
2196
2322
  definePolicy,
2323
+ describePasswordPolicy,
2324
+ explainPasswordProblems,
2325
+ passwordPolicyOf,
2326
+ passwordProblems,
2197
2327
  policySource,
2198
2328
  pullPolicy,
2199
2329
  pushPolicy,