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.mjs CHANGED
@@ -118,7 +118,111 @@ function fromWire(wire) {
118
118
  // src/validate.ts
119
119
  var MIN_PASSWORD = 12;
120
120
  var MAX_PASSWORD = 128;
121
+ var FLOOR_PASSWORD = 8;
121
122
  var MAX_NAME = 120;
123
+ var CLASSES = ["lowercase", "uppercase", "digit", "symbol"];
124
+ var DEFAULT_PASSWORD_POLICY = Object.freeze({ min: MIN_PASSWORD, max: MAX_PASSWORD, requires: [], min_classes: 0, reject_breached: false });
125
+ var int = (v, fallback) => typeof v === "number" && Number.isFinite(v) ? Math.trunc(v) : fallback;
126
+ function passwordPolicyOf(raw) {
127
+ const r = raw && typeof raw === "object" ? raw : {};
128
+ const min = Math.min(Math.max(int(r.min ?? r.min_length, MIN_PASSWORD) || MIN_PASSWORD, FLOOR_PASSWORD), MAX_PASSWORD);
129
+ const max = Math.max(Math.min(int(r.max ?? r.max_length, MAX_PASSWORD) || MAX_PASSWORD, MAX_PASSWORD), min);
130
+ const asked = Array.isArray(r.requires) ? r.requires : [];
131
+ return {
132
+ min,
133
+ max,
134
+ requires: CLASSES.filter((c) => asked.includes(c)),
135
+ min_classes: Math.min(Math.max(int(r.min_classes ?? r.minClasses, 0), 0), CLASSES.length),
136
+ reject_breached: (r.reject_breached ?? r.rejectBreached) === true
137
+ };
138
+ }
139
+ function utf8Length(v) {
140
+ let n = 0;
141
+ for (const ch of v) {
142
+ const c = ch.codePointAt(0);
143
+ n += c < 128 ? 1 : c < 2048 ? 2 : c < 65536 ? 3 : 4;
144
+ }
145
+ return n;
146
+ }
147
+ function classesOf(v) {
148
+ const has = /* @__PURE__ */ new Set();
149
+ for (const ch of v) {
150
+ if (/\p{Ll}/u.test(ch)) has.add("lowercase");
151
+ else if (/\p{Lu}/u.test(ch)) has.add("uppercase");
152
+ else if (/\p{Nd}/u.test(ch)) has.add("digit");
153
+ else if (!/\p{L}/u.test(ch)) has.add("symbol");
154
+ }
155
+ return has;
156
+ }
157
+ function passwordProblems(value, policy = DEFAULT_PASSWORD_POLICY) {
158
+ const p = passwordPolicyOf(policy);
159
+ const out = [];
160
+ const length = utf8Length(value);
161
+ if (length < p.min) out.push("too_short");
162
+ if (length > p.max) out.push("too_long");
163
+ const has = classesOf(value);
164
+ for (const c of p.requires) if (!has.has(c)) out.push(`needs_${c}`);
165
+ if (p.min_classes > 0 && CLASSES.filter((c) => has.has(c)).length < p.min_classes) out.push("needs_classes");
166
+ return out;
167
+ }
168
+ var RULES = {
169
+ en: {
170
+ between: "Between {min} and {max} characters.",
171
+ requires: "Include {kinds}.",
172
+ and: "and",
173
+ classes: "Use at least {n} of these: lowercase letters, uppercase letters, digits, symbols.",
174
+ breached: "Passwords found in known data breaches are not accepted.",
175
+ kinds: { lowercase: "a lowercase letter", uppercase: "an uppercase letter", digit: "a digit", symbol: "a symbol" },
176
+ reasons: {
177
+ too_short: "Use at least {min} characters.",
178
+ too_long: "Use at most {max} characters.",
179
+ needs_lowercase: "Include a lowercase letter.",
180
+ needs_uppercase: "Include an uppercase letter.",
181
+ needs_digit: "Include a digit.",
182
+ needs_symbol: "Include a symbol.",
183
+ needs_classes: "Use at least {n} of these: lowercase letters, uppercase letters, digits, symbols.",
184
+ breached: "This password appears in a known data breach. Choose a different one."
185
+ }
186
+ },
187
+ es: {
188
+ between: "Entre {min} y {max} caracteres.",
189
+ requires: "Incluye {kinds}.",
190
+ and: "y",
191
+ classes: "Usa al menos {n} de estos tipos: min\xFAsculas, may\xFAsculas, n\xFAmeros, s\xEDmbolos.",
192
+ breached: "No se aceptan contrase\xF1as que aparecen en filtraciones de datos conocidas.",
193
+ kinds: { lowercase: "una min\xFAscula", uppercase: "una may\xFAscula", digit: "un n\xFAmero", symbol: "un s\xEDmbolo" },
194
+ reasons: {
195
+ too_short: "Usa al menos {min} caracteres.",
196
+ too_long: "Usa como m\xE1ximo {max} caracteres.",
197
+ needs_lowercase: "Incluye una min\xFAscula.",
198
+ needs_uppercase: "Incluye una may\xFAscula.",
199
+ needs_digit: "Incluye un n\xFAmero.",
200
+ needs_symbol: "Incluye un s\xEDmbolo.",
201
+ needs_classes: "Usa al menos {n} de estos tipos: min\xFAsculas, may\xFAsculas, n\xFAmeros, s\xEDmbolos.",
202
+ breached: "Esta contrase\xF1a aparece en una filtraci\xF3n de datos conocida. Elige otra."
203
+ }
204
+ }
205
+ };
206
+ var fill = (s, p) => s.replace("{min}", String(p.min)).replace("{max}", String(p.max)).replace("{n}", String(p.min_classes));
207
+ function describePasswordPolicy(policy = DEFAULT_PASSWORD_POLICY, locale = "en") {
208
+ const p = passwordPolicyOf(policy);
209
+ const t = RULES[locale] ?? RULES.en;
210
+ const parts = [fill(t.between, p)];
211
+ if (p.requires.length > 0) {
212
+ const kinds = p.requires.map((c) => t.kinds[c]);
213
+ const listed = kinds.length === 1 ? kinds[0] : `${kinds.slice(0, -1).join(", ")} ${t.and} ${kinds[kinds.length - 1]}`;
214
+ parts.push(t.requires.replace("{kinds}", listed));
215
+ }
216
+ if (p.min_classes > 0) parts.push(fill(t.classes, p));
217
+ if (p.reject_breached) parts.push(t.breached);
218
+ return parts.join(" ");
219
+ }
220
+ function explainPasswordProblems(reasons, policy = DEFAULT_PASSWORD_POLICY, locale = "en") {
221
+ const p = passwordPolicyOf(policy);
222
+ const t = RULES[locale] ?? RULES.en;
223
+ const said = reasons.filter((r) => r in t.reasons).map((r) => fill(t.reasons[r], p));
224
+ return said.length > 0 ? said.join(" ") : describePasswordPolicy(p, locale);
225
+ }
122
226
  var EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
123
227
  function checkEmail(value) {
124
228
  const v = typeof value === "string" ? value.trim() : "";
@@ -131,13 +235,12 @@ function checkPassword(value) {
131
235
  if (value.length > MAX_PASSWORD) return { field: "password", code: "too_long", message: "That password is too long." };
132
236
  return null;
133
237
  }
134
- function checkNewPassword(value) {
135
- const basic = checkPassword(value);
136
- if (basic) return basic;
137
- if (value.length < MIN_PASSWORD) {
138
- return { field: "password", code: "too_short", message: `Use at least ${MIN_PASSWORD} characters.` };
139
- }
140
- return null;
238
+ function checkNewPassword(value, policy) {
239
+ if (typeof value !== "string" || value === "") return { field: "password", code: "required", message: "Enter your password." };
240
+ const p = policy ? passwordPolicyOf(policy) : DEFAULT_PASSWORD_POLICY;
241
+ const reasons = passwordProblems(value, p);
242
+ if (reasons.length === 0) return null;
243
+ return { field: "password", code: reasons[0], message: explainPasswordProblems(reasons, p) };
141
244
  }
142
245
  function checkName(value) {
143
246
  if (value === void 0 || value === null || value === "") return null;
@@ -308,7 +411,7 @@ function createAuthClient(options = {}) {
308
411
  const body = await res.json().catch(() => null);
309
412
  if (res.ok && body && typeof body.status === "string") return { ok: true, ...body };
310
413
  const e = body?.error;
311
- return { ok: false, error: e && typeof e.code === "string" ? { code: e.code, message: String(e.message ?? ""), ...e.field ? { field: e.field } : {} } : UNAVAILABLE };
414
+ return { ok: false, error: e && typeof e.code === "string" ? { code: e.code, message: String(e.message ?? ""), ...e.field ? { field: e.field } : {}, ...Array.isArray(e.reasons) ? { reasons: e.reasons.map(String) } : {} } : UNAVAILABLE };
312
415
  } catch {
313
416
  return { ok: false, error: UNAVAILABLE };
314
417
  }
@@ -336,12 +439,14 @@ function createAuthClient(options = {}) {
336
439
  if (o.locale) q.set("locale", o.locale);
337
440
  return `${base}/login?${q}`;
338
441
  };
442
+ let policy;
339
443
  async function methods() {
340
444
  try {
341
445
  const res = await doFetch(`${base}/methods`, { credentials: "same-origin", headers: { accept: "application/json" } });
342
446
  if (res.ok) {
343
447
  const m = await res.json();
344
- return { providers: [], signUp: true, magicLink: false, passkey: false, ...m, passkeyHere: m.passkeyHere === true, passkeyOnCauthy: m.passkeyOnCauthy ?? m.passkey === true, sso: m.sso === true };
448
+ if (m.password) policy = passwordPolicyOf(m.password);
449
+ return { providers: [], signUp: true, magicLink: false, passkey: false, ...m, ...policy ? { password: policy } : {}, passkeyHere: m.passkeyHere === true, passkeyOnCauthy: m.passkeyOnCauthy ?? m.passkey === true, sso: m.sso === true };
345
450
  }
346
451
  } catch {
347
452
  }
@@ -485,12 +590,12 @@ function createAuthClient(options = {}) {
485
590
  email: (f) => post(
486
591
  "/sign-up/email",
487
592
  { email: f.email, password: f.password, name: f.name, organization: f.organization, acceptLegal: f.acceptLegal, consents: f.consents },
488
- firstProblem(checkName(f.name), checkEmail(f.email), checkNewPassword(f.password), checkOrganization(f.organization)),
593
+ firstProblem(checkName(f.name), checkEmail(f.email), checkNewPassword(f.password, policy), checkOrganization(f.organization)),
489
594
  f.captchaToken
490
595
  )
491
596
  },
492
597
  forgotPassword: (f) => post("/forgot-password", { email: f.email }, checkEmail(f.email), f.captchaToken),
493
- resetPassword: (f) => post("/reset-password", { token: f.token, password: f.password }, checkNewPassword(f.password)),
598
+ resetPassword: (f) => post("/reset-password", { token: f.token, password: f.password }, checkNewPassword(f.password, policy)),
494
599
  verifyEmail: (f) => post("/verify-email", { token: f.token }, null),
495
600
  signOut: () => post("/sign-out", {}, null),
496
601
  organizations: {
@@ -524,7 +629,7 @@ function createAuthClient(options = {}) {
524
629
  },
525
630
  acceptLegal: (f = {}) => post("/me/legal/accept", { kinds: f.kinds ?? [] }, null),
526
631
  changePassword: (f) => {
527
- const bad = checkNewPassword(f.newPassword);
632
+ const bad = checkNewPassword(f.newPassword, policy);
528
633
  return post("/me/password", { password: f.password, newPassword: f.newPassword }, bad ? { ...bad, field: "newPassword" } : null);
529
634
  },
530
635
  mfa: {
@@ -555,7 +660,7 @@ function createAuthClient(options = {}) {
555
660
  return body && Array.isArray(body.sessions) ? body.sessions : [];
556
661
  },
557
662
  signOutEverywhere: () => post("/me/sessions/end", {}, null),
558
- upgrade: (f) => post("/me/upgrade", { email: f.email, password: f.password, name: f.name ?? "" }, firstProblem(checkName(f.name), checkEmail(f.email), checkNewPassword(f.password))),
663
+ upgrade: (f) => post("/me/upgrade", { email: f.email, password: f.password, name: f.name ?? "" }, firstProblem(checkName(f.name), checkEmail(f.email), checkNewPassword(f.password, policy))),
559
664
  upgradeUrl,
560
665
  upgradeWith(provider, o) {
561
666
  window.location.assign(upgradeUrl(provider, o));
@@ -592,6 +697,7 @@ function createAuthClient(options = {}) {
592
697
  };
593
698
  }
594
699
  export {
700
+ DEFAULT_PASSWORD_POLICY,
595
701
  MIN_PASSWORD,
596
702
  canAutofillPasskeys,
597
703
  canUsePasskeys,
@@ -601,6 +707,10 @@ export {
601
707
  checkNewPassword,
602
708
  checkPassword,
603
709
  createAuthClient,
604
- createSessionStore
710
+ createSessionStore,
711
+ describePasswordPolicy,
712
+ explainPasswordProblems,
713
+ passwordPolicyOf,
714
+ passwordProblems
605
715
  };
606
716
  //# sourceMappingURL=client.mjs.map