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/styles.css CHANGED
@@ -122,6 +122,12 @@
122
122
  color: var(--cauthy-error);
123
123
  }
124
124
 
125
+ /* What a new password must be, under its field. */
126
+ :where([data-cauthy] .cauthy-hint) {
127
+ font-size: 12px;
128
+ color: var(--cauthy-text-muted);
129
+ }
130
+
125
131
  /* ── Buttons ─────────────────────────────────────────────────────────────── */
126
132
 
127
133
  :where([data-cauthy] .cauthy-button),
package/dist/svelte.d.mts CHANGED
@@ -3,7 +3,7 @@ export { Account, AccountSession, AuthClient, AuthError, AuthMethods, AuthResult
3
3
  import { i as PublicSession } from './system-BxMsSIu1.mjs';
4
4
  export { P as PermissionOf, f as Policy, R as RoleOf, k as SessionOrganization, m as SessionUser, p as can, r as definePolicy } from './system-BxMsSIu1.mjs';
5
5
  import './theme.mjs';
6
- import './validate-YbEfIzfl.mjs';
6
+ import './validate-sEXG82po.mjs';
7
7
  import 'jose';
8
8
 
9
9
  /** A value Svelte can read with `$`: its store contract. */
package/dist/svelte.d.ts CHANGED
@@ -3,7 +3,7 @@ export { Account, AccountSession, AuthClient, AuthError, AuthMethods, AuthResult
3
3
  import { i as PublicSession } from './system-DTIeDXDP.js';
4
4
  export { P as PermissionOf, f as Policy, R as RoleOf, k as SessionOrganization, m as SessionUser, p as can, r as definePolicy } from './system-DTIeDXDP.js';
5
5
  import './theme.js';
6
- import './validate-YbEfIzfl.js';
6
+ import './validate-sEXG82po.js';
7
7
  import 'jose';
8
8
 
9
9
  /** A value Svelte can read with `$`: its store contract. */
package/dist/svelte.js CHANGED
@@ -146,7 +146,111 @@ function fromWire(wire) {
146
146
  // src/validate.ts
147
147
  var MIN_PASSWORD = 12;
148
148
  var MAX_PASSWORD = 128;
149
+ var FLOOR_PASSWORD = 8;
149
150
  var MAX_NAME = 120;
151
+ var CLASSES = ["lowercase", "uppercase", "digit", "symbol"];
152
+ var DEFAULT_PASSWORD_POLICY = Object.freeze({ min: MIN_PASSWORD, max: MAX_PASSWORD, requires: [], min_classes: 0, reject_breached: false });
153
+ var int = (v, fallback) => typeof v === "number" && Number.isFinite(v) ? Math.trunc(v) : fallback;
154
+ function passwordPolicyOf(raw) {
155
+ const r = raw && typeof raw === "object" ? raw : {};
156
+ const min = Math.min(Math.max(int(r.min ?? r.min_length, MIN_PASSWORD) || MIN_PASSWORD, FLOOR_PASSWORD), MAX_PASSWORD);
157
+ const max = Math.max(Math.min(int(r.max ?? r.max_length, MAX_PASSWORD) || MAX_PASSWORD, MAX_PASSWORD), min);
158
+ const asked = Array.isArray(r.requires) ? r.requires : [];
159
+ return {
160
+ min,
161
+ max,
162
+ requires: CLASSES.filter((c) => asked.includes(c)),
163
+ min_classes: Math.min(Math.max(int(r.min_classes ?? r.minClasses, 0), 0), CLASSES.length),
164
+ reject_breached: (r.reject_breached ?? r.rejectBreached) === true
165
+ };
166
+ }
167
+ function utf8Length(v) {
168
+ let n = 0;
169
+ for (const ch of v) {
170
+ const c = ch.codePointAt(0);
171
+ n += c < 128 ? 1 : c < 2048 ? 2 : c < 65536 ? 3 : 4;
172
+ }
173
+ return n;
174
+ }
175
+ function classesOf(v) {
176
+ const has = /* @__PURE__ */ new Set();
177
+ for (const ch of v) {
178
+ if (/\p{Ll}/u.test(ch)) has.add("lowercase");
179
+ else if (/\p{Lu}/u.test(ch)) has.add("uppercase");
180
+ else if (/\p{Nd}/u.test(ch)) has.add("digit");
181
+ else if (!/\p{L}/u.test(ch)) has.add("symbol");
182
+ }
183
+ return has;
184
+ }
185
+ function passwordProblems(value, policy = DEFAULT_PASSWORD_POLICY) {
186
+ const p = passwordPolicyOf(policy);
187
+ const out = [];
188
+ const length = utf8Length(value);
189
+ if (length < p.min) out.push("too_short");
190
+ if (length > p.max) out.push("too_long");
191
+ const has = classesOf(value);
192
+ for (const c of p.requires) if (!has.has(c)) out.push(`needs_${c}`);
193
+ if (p.min_classes > 0 && CLASSES.filter((c) => has.has(c)).length < p.min_classes) out.push("needs_classes");
194
+ return out;
195
+ }
196
+ var RULES = {
197
+ en: {
198
+ between: "Between {min} and {max} characters.",
199
+ requires: "Include {kinds}.",
200
+ and: "and",
201
+ classes: "Use at least {n} of these: lowercase letters, uppercase letters, digits, symbols.",
202
+ breached: "Passwords found in known data breaches are not accepted.",
203
+ kinds: { lowercase: "a lowercase letter", uppercase: "an uppercase letter", digit: "a digit", symbol: "a symbol" },
204
+ reasons: {
205
+ too_short: "Use at least {min} characters.",
206
+ too_long: "Use at most {max} characters.",
207
+ needs_lowercase: "Include a lowercase letter.",
208
+ needs_uppercase: "Include an uppercase letter.",
209
+ needs_digit: "Include a digit.",
210
+ needs_symbol: "Include a symbol.",
211
+ needs_classes: "Use at least {n} of these: lowercase letters, uppercase letters, digits, symbols.",
212
+ breached: "This password appears in a known data breach. Choose a different one."
213
+ }
214
+ },
215
+ es: {
216
+ between: "Entre {min} y {max} caracteres.",
217
+ requires: "Incluye {kinds}.",
218
+ and: "y",
219
+ classes: "Usa al menos {n} de estos tipos: min\xFAsculas, may\xFAsculas, n\xFAmeros, s\xEDmbolos.",
220
+ breached: "No se aceptan contrase\xF1as que aparecen en filtraciones de datos conocidas.",
221
+ kinds: { lowercase: "una min\xFAscula", uppercase: "una may\xFAscula", digit: "un n\xFAmero", symbol: "un s\xEDmbolo" },
222
+ reasons: {
223
+ too_short: "Usa al menos {min} caracteres.",
224
+ too_long: "Usa como m\xE1ximo {max} caracteres.",
225
+ needs_lowercase: "Incluye una min\xFAscula.",
226
+ needs_uppercase: "Incluye una may\xFAscula.",
227
+ needs_digit: "Incluye un n\xFAmero.",
228
+ needs_symbol: "Incluye un s\xEDmbolo.",
229
+ needs_classes: "Usa al menos {n} de estos tipos: min\xFAsculas, may\xFAsculas, n\xFAmeros, s\xEDmbolos.",
230
+ breached: "Esta contrase\xF1a aparece en una filtraci\xF3n de datos conocida. Elige otra."
231
+ }
232
+ }
233
+ };
234
+ var fill = (s, p) => s.replace("{min}", String(p.min)).replace("{max}", String(p.max)).replace("{n}", String(p.min_classes));
235
+ function describePasswordPolicy(policy = DEFAULT_PASSWORD_POLICY, locale = "en") {
236
+ const p = passwordPolicyOf(policy);
237
+ const t = RULES[locale] ?? RULES.en;
238
+ const parts = [fill(t.between, p)];
239
+ if (p.requires.length > 0) {
240
+ const kinds = p.requires.map((c) => t.kinds[c]);
241
+ const listed = kinds.length === 1 ? kinds[0] : `${kinds.slice(0, -1).join(", ")} ${t.and} ${kinds[kinds.length - 1]}`;
242
+ parts.push(t.requires.replace("{kinds}", listed));
243
+ }
244
+ if (p.min_classes > 0) parts.push(fill(t.classes, p));
245
+ if (p.reject_breached) parts.push(t.breached);
246
+ return parts.join(" ");
247
+ }
248
+ function explainPasswordProblems(reasons, policy = DEFAULT_PASSWORD_POLICY, locale = "en") {
249
+ const p = passwordPolicyOf(policy);
250
+ const t = RULES[locale] ?? RULES.en;
251
+ const said = reasons.filter((r) => r in t.reasons).map((r) => fill(t.reasons[r], p));
252
+ return said.length > 0 ? said.join(" ") : describePasswordPolicy(p, locale);
253
+ }
150
254
  var EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
151
255
  function checkEmail(value) {
152
256
  const v = typeof value === "string" ? value.trim() : "";
@@ -159,13 +263,12 @@ function checkPassword(value) {
159
263
  if (value.length > MAX_PASSWORD) return { field: "password", code: "too_long", message: "That password is too long." };
160
264
  return null;
161
265
  }
162
- function checkNewPassword(value) {
163
- const basic = checkPassword(value);
164
- if (basic) return basic;
165
- if (value.length < MIN_PASSWORD) {
166
- return { field: "password", code: "too_short", message: `Use at least ${MIN_PASSWORD} characters.` };
167
- }
168
- return null;
266
+ function checkNewPassword(value, policy) {
267
+ if (typeof value !== "string" || value === "") return { field: "password", code: "required", message: "Enter your password." };
268
+ const p = policy ? passwordPolicyOf(policy) : DEFAULT_PASSWORD_POLICY;
269
+ const reasons = passwordProblems(value, p);
270
+ if (reasons.length === 0) return null;
271
+ return { field: "password", code: reasons[0], message: explainPasswordProblems(reasons, p) };
169
272
  }
170
273
  function checkName(value) {
171
274
  if (value === void 0 || value === null || value === "") return null;
@@ -228,7 +331,7 @@ function createAuthClient(options = {}) {
228
331
  const body = await res.json().catch(() => null);
229
332
  if (res.ok && body && typeof body.status === "string") return { ok: true, ...body };
230
333
  const e = body?.error;
231
- return { ok: false, error: e && typeof e.code === "string" ? { code: e.code, message: String(e.message ?? ""), ...e.field ? { field: e.field } : {} } : UNAVAILABLE };
334
+ 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 };
232
335
  } catch {
233
336
  return { ok: false, error: UNAVAILABLE };
234
337
  }
@@ -256,12 +359,14 @@ function createAuthClient(options = {}) {
256
359
  if (o.locale) q.set("locale", o.locale);
257
360
  return `${base}/login?${q}`;
258
361
  };
362
+ let policy;
259
363
  async function methods() {
260
364
  try {
261
365
  const res = await doFetch(`${base}/methods`, { credentials: "same-origin", headers: { accept: "application/json" } });
262
366
  if (res.ok) {
263
367
  const m = await res.json();
264
- return { providers: [], signUp: true, magicLink: false, passkey: false, ...m, passkeyHere: m.passkeyHere === true, passkeyOnCauthy: m.passkeyOnCauthy ?? m.passkey === true, sso: m.sso === true };
368
+ if (m.password) policy = passwordPolicyOf(m.password);
369
+ 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 };
265
370
  }
266
371
  } catch {
267
372
  }
@@ -405,12 +510,12 @@ function createAuthClient(options = {}) {
405
510
  email: (f) => post(
406
511
  "/sign-up/email",
407
512
  { email: f.email, password: f.password, name: f.name, organization: f.organization, acceptLegal: f.acceptLegal, consents: f.consents },
408
- firstProblem(checkName(f.name), checkEmail(f.email), checkNewPassword(f.password), checkOrganization(f.organization)),
513
+ firstProblem(checkName(f.name), checkEmail(f.email), checkNewPassword(f.password, policy), checkOrganization(f.organization)),
409
514
  f.captchaToken
410
515
  )
411
516
  },
412
517
  forgotPassword: (f) => post("/forgot-password", { email: f.email }, checkEmail(f.email), f.captchaToken),
413
- resetPassword: (f) => post("/reset-password", { token: f.token, password: f.password }, checkNewPassword(f.password)),
518
+ resetPassword: (f) => post("/reset-password", { token: f.token, password: f.password }, checkNewPassword(f.password, policy)),
414
519
  verifyEmail: (f) => post("/verify-email", { token: f.token }, null),
415
520
  signOut: () => post("/sign-out", {}, null),
416
521
  organizations: {
@@ -444,7 +549,7 @@ function createAuthClient(options = {}) {
444
549
  },
445
550
  acceptLegal: (f = {}) => post("/me/legal/accept", { kinds: f.kinds ?? [] }, null),
446
551
  changePassword: (f) => {
447
- const bad = checkNewPassword(f.newPassword);
552
+ const bad = checkNewPassword(f.newPassword, policy);
448
553
  return post("/me/password", { password: f.password, newPassword: f.newPassword }, bad ? { ...bad, field: "newPassword" } : null);
449
554
  },
450
555
  mfa: {
@@ -475,7 +580,7 @@ function createAuthClient(options = {}) {
475
580
  return body && Array.isArray(body.sessions) ? body.sessions : [];
476
581
  },
477
582
  signOutEverywhere: () => post("/me/sessions/end", {}, null),
478
- upgrade: (f) => post("/me/upgrade", { email: f.email, password: f.password, name: f.name ?? "" }, firstProblem(checkName(f.name), checkEmail(f.email), checkNewPassword(f.password))),
583
+ 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))),
479
584
  upgradeUrl,
480
585
  upgradeWith(provider, o) {
481
586
  window.location.assign(upgradeUrl(provider, o));