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.
@@ -0,0 +1,66 @@
1
+ type AuthField = 'email' | 'password' | 'newPassword' | 'name' | 'code' | 'organization';
2
+ /** A value of a form that cannot be sent as it is. */
3
+ interface FieldProblem {
4
+ field: AuthField;
5
+ /**
6
+ * Stable, for translating: `required`, `invalid_email`, `too_short`,
7
+ * `too_long`, and for a new password the rule of the workspace it fails:
8
+ * `needs_lowercase`, `needs_uppercase`, `needs_digit`, `needs_symbol`,
9
+ * `needs_classes`.
10
+ */
11
+ code: 'required' | 'invalid_email' | PasswordReason;
12
+ message: string;
13
+ }
14
+ /** The shortest password Cauthy takes by default: a workspace's policy may ask otherwise (`methods().password`). */
15
+ declare const MIN_PASSWORD = 12;
16
+ /** A kind of character a workspace's password policy can ask for. */
17
+ type PasswordClass = 'lowercase' | 'uppercase' | 'digit' | 'symbol';
18
+ /** Why a new password is refused: the API's `weak_password` `meta.reasons`. */
19
+ type PasswordReason = 'too_short' | 'too_long' | 'needs_lowercase' | 'needs_uppercase' | 'needs_digit' | 'needs_symbol' | 'needs_classes' | 'breached';
20
+ /**
21
+ * What a new password must be in the workspace, as `methods().password` says
22
+ * it. Length is counted in bytes of UTF-8, as Cauthy counts it. A symbol is
23
+ * any character that is neither a letter nor a digit, a space included.
24
+ */
25
+ interface PasswordPolicy {
26
+ min: number;
27
+ max: number;
28
+ /** The kinds the password must each carry at least once. */
29
+ requires: PasswordClass[];
30
+ /** At least this many of the four kinds; 0 asks none. */
31
+ min_classes: number;
32
+ /** Cauthy refuses a password found in a published breach (checked by Cauthy alone). */
33
+ reject_breached: boolean;
34
+ }
35
+ /** The policy of a workspace that set none: what every one asked before they could choose. */
36
+ declare const DEFAULT_PASSWORD_POLICY: PasswordPolicy;
37
+ /**
38
+ * A password policy as Cauthy sent it — `{ min_length, max_length, … }` from
39
+ * the sign-in settings, or `{ min, max, … }` from `methods()` — made safe to
40
+ * use: the defaults where nothing is said, and never outside 8–128.
41
+ */
42
+ declare function passwordPolicyOf(raw: unknown): PasswordPolicy;
43
+ /**
44
+ * Every rule of the policy a new password fails, as the API names them;
45
+ * empty when it meets them all. Whether it was in a breach only Cauthy knows.
46
+ */
47
+ declare function passwordProblems(value: string, policy?: PasswordPolicy): PasswordReason[];
48
+ /** The languages the rule is said in. */
49
+ type PasswordLocale = 'en' | 'es';
50
+ /** What a new password must be, said for the person choosing it: "Between 12 and 128 characters. Include a digit." */
51
+ declare function describePasswordPolicy(policy?: PasswordPolicy, locale?: PasswordLocale): string;
52
+ /** Why a new password was refused, rule by rule, for the person who typed it. */
53
+ declare function explainPasswordProblems(reasons: readonly string[], policy?: PasswordPolicy, locale?: PasswordLocale): string;
54
+ declare function checkEmail(value: unknown): FieldProblem | null;
55
+ /** A password being typed to sign in: only that there is one. */
56
+ declare function checkPassword(value: unknown): FieldProblem | null;
57
+ /**
58
+ * A password being chosen for a new account, or a new one for an account.
59
+ * Held to the workspace's policy when it is given (`methods().password`),
60
+ * otherwise to the defaults: 12 to 128 characters.
61
+ */
62
+ declare function checkNewPassword(value: unknown, policy?: PasswordPolicy | null): FieldProblem | null;
63
+ declare function checkName(value: unknown): FieldProblem | null;
64
+ declare function checkCode(value: unknown): FieldProblem | null;
65
+
66
+ export { type AuthField as A, DEFAULT_PASSWORD_POLICY as D, type FieldProblem as F, MIN_PASSWORD as M, type PasswordClass as P, type PasswordLocale as a, type PasswordPolicy as b, type PasswordReason as c, checkCode as d, checkEmail as e, checkName as f, checkNewPassword as g, checkPassword as h, describePasswordPolicy as i, explainPasswordProblems as j, passwordProblems as k, passwordPolicyOf as p };
@@ -0,0 +1,66 @@
1
+ type AuthField = 'email' | 'password' | 'newPassword' | 'name' | 'code' | 'organization';
2
+ /** A value of a form that cannot be sent as it is. */
3
+ interface FieldProblem {
4
+ field: AuthField;
5
+ /**
6
+ * Stable, for translating: `required`, `invalid_email`, `too_short`,
7
+ * `too_long`, and for a new password the rule of the workspace it fails:
8
+ * `needs_lowercase`, `needs_uppercase`, `needs_digit`, `needs_symbol`,
9
+ * `needs_classes`.
10
+ */
11
+ code: 'required' | 'invalid_email' | PasswordReason;
12
+ message: string;
13
+ }
14
+ /** The shortest password Cauthy takes by default: a workspace's policy may ask otherwise (`methods().password`). */
15
+ declare const MIN_PASSWORD = 12;
16
+ /** A kind of character a workspace's password policy can ask for. */
17
+ type PasswordClass = 'lowercase' | 'uppercase' | 'digit' | 'symbol';
18
+ /** Why a new password is refused: the API's `weak_password` `meta.reasons`. */
19
+ type PasswordReason = 'too_short' | 'too_long' | 'needs_lowercase' | 'needs_uppercase' | 'needs_digit' | 'needs_symbol' | 'needs_classes' | 'breached';
20
+ /**
21
+ * What a new password must be in the workspace, as `methods().password` says
22
+ * it. Length is counted in bytes of UTF-8, as Cauthy counts it. A symbol is
23
+ * any character that is neither a letter nor a digit, a space included.
24
+ */
25
+ interface PasswordPolicy {
26
+ min: number;
27
+ max: number;
28
+ /** The kinds the password must each carry at least once. */
29
+ requires: PasswordClass[];
30
+ /** At least this many of the four kinds; 0 asks none. */
31
+ min_classes: number;
32
+ /** Cauthy refuses a password found in a published breach (checked by Cauthy alone). */
33
+ reject_breached: boolean;
34
+ }
35
+ /** The policy of a workspace that set none: what every one asked before they could choose. */
36
+ declare const DEFAULT_PASSWORD_POLICY: PasswordPolicy;
37
+ /**
38
+ * A password policy as Cauthy sent it — `{ min_length, max_length, … }` from
39
+ * the sign-in settings, or `{ min, max, … }` from `methods()` — made safe to
40
+ * use: the defaults where nothing is said, and never outside 8–128.
41
+ */
42
+ declare function passwordPolicyOf(raw: unknown): PasswordPolicy;
43
+ /**
44
+ * Every rule of the policy a new password fails, as the API names them;
45
+ * empty when it meets them all. Whether it was in a breach only Cauthy knows.
46
+ */
47
+ declare function passwordProblems(value: string, policy?: PasswordPolicy): PasswordReason[];
48
+ /** The languages the rule is said in. */
49
+ type PasswordLocale = 'en' | 'es';
50
+ /** What a new password must be, said for the person choosing it: "Between 12 and 128 characters. Include a digit." */
51
+ declare function describePasswordPolicy(policy?: PasswordPolicy, locale?: PasswordLocale): string;
52
+ /** Why a new password was refused, rule by rule, for the person who typed it. */
53
+ declare function explainPasswordProblems(reasons: readonly string[], policy?: PasswordPolicy, locale?: PasswordLocale): string;
54
+ declare function checkEmail(value: unknown): FieldProblem | null;
55
+ /** A password being typed to sign in: only that there is one. */
56
+ declare function checkPassword(value: unknown): FieldProblem | null;
57
+ /**
58
+ * A password being chosen for a new account, or a new one for an account.
59
+ * Held to the workspace's policy when it is given (`methods().password`),
60
+ * otherwise to the defaults: 12 to 128 characters.
61
+ */
62
+ declare function checkNewPassword(value: unknown, policy?: PasswordPolicy | null): FieldProblem | null;
63
+ declare function checkName(value: unknown): FieldProblem | null;
64
+ declare function checkCode(value: unknown): FieldProblem | null;
65
+
66
+ export { type AuthField as A, DEFAULT_PASSWORD_POLICY as D, type FieldProblem as F, MIN_PASSWORD as M, type PasswordClass as P, type PasswordLocale as a, type PasswordPolicy as b, type PasswordReason as c, checkCode as d, checkEmail as e, checkName as f, checkNewPassword as g, checkPassword as h, describePasswordPolicy as i, explainPasswordProblems as j, passwordProblems as k, passwordPolicyOf as p };
package/dist/vue.d.mts CHANGED
@@ -4,7 +4,7 @@ export { Account, AccountSession, AuthError, AuthMethods, AuthResult, Passkey, P
4
4
  import { i as PublicSession } from './system-BxMsSIu1.mjs';
5
5
  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';
6
6
  import './theme.mjs';
7
- import './validate-YbEfIzfl.mjs';
7
+ import './validate-sEXG82po.mjs';
8
8
  import 'jose';
9
9
 
10
10
  interface CauthyVueSession extends Pick<SessionStore, 'refresh' | 'signIn' | 'signOut' | 'accountUrl' | 'basePath'> {
package/dist/vue.d.ts CHANGED
@@ -4,7 +4,7 @@ export { Account, AccountSession, AuthError, AuthMethods, AuthResult, Passkey, P
4
4
  import { i as PublicSession } from './system-DTIeDXDP.js';
5
5
  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';
6
6
  import './theme.js';
7
- import './validate-YbEfIzfl.js';
7
+ import './validate-sEXG82po.js';
8
8
  import 'jose';
9
9
 
10
10
  interface CauthyVueSession extends Pick<SessionStore, 'refresh' | 'signIn' | 'signOut' | 'accountUrl' | 'basePath'> {
package/dist/vue.js CHANGED
@@ -149,7 +149,111 @@ function fromWire(wire) {
149
149
  // src/validate.ts
150
150
  var MIN_PASSWORD = 12;
151
151
  var MAX_PASSWORD = 128;
152
+ var FLOOR_PASSWORD = 8;
152
153
  var MAX_NAME = 120;
154
+ var CLASSES = ["lowercase", "uppercase", "digit", "symbol"];
155
+ var DEFAULT_PASSWORD_POLICY = Object.freeze({ min: MIN_PASSWORD, max: MAX_PASSWORD, requires: [], min_classes: 0, reject_breached: false });
156
+ var int = (v, fallback) => typeof v === "number" && Number.isFinite(v) ? Math.trunc(v) : fallback;
157
+ function passwordPolicyOf(raw) {
158
+ const r = raw && typeof raw === "object" ? raw : {};
159
+ const min = Math.min(Math.max(int(r.min ?? r.min_length, MIN_PASSWORD) || MIN_PASSWORD, FLOOR_PASSWORD), MAX_PASSWORD);
160
+ const max = Math.max(Math.min(int(r.max ?? r.max_length, MAX_PASSWORD) || MAX_PASSWORD, MAX_PASSWORD), min);
161
+ const asked = Array.isArray(r.requires) ? r.requires : [];
162
+ return {
163
+ min,
164
+ max,
165
+ requires: CLASSES.filter((c) => asked.includes(c)),
166
+ min_classes: Math.min(Math.max(int(r.min_classes ?? r.minClasses, 0), 0), CLASSES.length),
167
+ reject_breached: (r.reject_breached ?? r.rejectBreached) === true
168
+ };
169
+ }
170
+ function utf8Length(v) {
171
+ let n = 0;
172
+ for (const ch of v) {
173
+ const c = ch.codePointAt(0);
174
+ n += c < 128 ? 1 : c < 2048 ? 2 : c < 65536 ? 3 : 4;
175
+ }
176
+ return n;
177
+ }
178
+ function classesOf(v) {
179
+ const has = /* @__PURE__ */ new Set();
180
+ for (const ch of v) {
181
+ if (/\p{Ll}/u.test(ch)) has.add("lowercase");
182
+ else if (/\p{Lu}/u.test(ch)) has.add("uppercase");
183
+ else if (/\p{Nd}/u.test(ch)) has.add("digit");
184
+ else if (!/\p{L}/u.test(ch)) has.add("symbol");
185
+ }
186
+ return has;
187
+ }
188
+ function passwordProblems(value, policy = DEFAULT_PASSWORD_POLICY) {
189
+ const p = passwordPolicyOf(policy);
190
+ const out = [];
191
+ const length = utf8Length(value);
192
+ if (length < p.min) out.push("too_short");
193
+ if (length > p.max) out.push("too_long");
194
+ const has = classesOf(value);
195
+ for (const c of p.requires) if (!has.has(c)) out.push(`needs_${c}`);
196
+ if (p.min_classes > 0 && CLASSES.filter((c) => has.has(c)).length < p.min_classes) out.push("needs_classes");
197
+ return out;
198
+ }
199
+ var RULES = {
200
+ en: {
201
+ between: "Between {min} and {max} characters.",
202
+ requires: "Include {kinds}.",
203
+ and: "and",
204
+ classes: "Use at least {n} of these: lowercase letters, uppercase letters, digits, symbols.",
205
+ breached: "Passwords found in known data breaches are not accepted.",
206
+ kinds: { lowercase: "a lowercase letter", uppercase: "an uppercase letter", digit: "a digit", symbol: "a symbol" },
207
+ reasons: {
208
+ too_short: "Use at least {min} characters.",
209
+ too_long: "Use at most {max} characters.",
210
+ needs_lowercase: "Include a lowercase letter.",
211
+ needs_uppercase: "Include an uppercase letter.",
212
+ needs_digit: "Include a digit.",
213
+ needs_symbol: "Include a symbol.",
214
+ needs_classes: "Use at least {n} of these: lowercase letters, uppercase letters, digits, symbols.",
215
+ breached: "This password appears in a known data breach. Choose a different one."
216
+ }
217
+ },
218
+ es: {
219
+ between: "Entre {min} y {max} caracteres.",
220
+ requires: "Incluye {kinds}.",
221
+ and: "y",
222
+ classes: "Usa al menos {n} de estos tipos: min\xFAsculas, may\xFAsculas, n\xFAmeros, s\xEDmbolos.",
223
+ breached: "No se aceptan contrase\xF1as que aparecen en filtraciones de datos conocidas.",
224
+ kinds: { lowercase: "una min\xFAscula", uppercase: "una may\xFAscula", digit: "un n\xFAmero", symbol: "un s\xEDmbolo" },
225
+ reasons: {
226
+ too_short: "Usa al menos {min} caracteres.",
227
+ too_long: "Usa como m\xE1ximo {max} caracteres.",
228
+ needs_lowercase: "Incluye una min\xFAscula.",
229
+ needs_uppercase: "Incluye una may\xFAscula.",
230
+ needs_digit: "Incluye un n\xFAmero.",
231
+ needs_symbol: "Incluye un s\xEDmbolo.",
232
+ needs_classes: "Usa al menos {n} de estos tipos: min\xFAsculas, may\xFAsculas, n\xFAmeros, s\xEDmbolos.",
233
+ breached: "Esta contrase\xF1a aparece en una filtraci\xF3n de datos conocida. Elige otra."
234
+ }
235
+ }
236
+ };
237
+ var fill = (s, p) => s.replace("{min}", String(p.min)).replace("{max}", String(p.max)).replace("{n}", String(p.min_classes));
238
+ function describePasswordPolicy(policy = DEFAULT_PASSWORD_POLICY, locale = "en") {
239
+ const p = passwordPolicyOf(policy);
240
+ const t = RULES[locale] ?? RULES.en;
241
+ const parts = [fill(t.between, p)];
242
+ if (p.requires.length > 0) {
243
+ const kinds = p.requires.map((c) => t.kinds[c]);
244
+ const listed = kinds.length === 1 ? kinds[0] : `${kinds.slice(0, -1).join(", ")} ${t.and} ${kinds[kinds.length - 1]}`;
245
+ parts.push(t.requires.replace("{kinds}", listed));
246
+ }
247
+ if (p.min_classes > 0) parts.push(fill(t.classes, p));
248
+ if (p.reject_breached) parts.push(t.breached);
249
+ return parts.join(" ");
250
+ }
251
+ function explainPasswordProblems(reasons, policy = DEFAULT_PASSWORD_POLICY, locale = "en") {
252
+ const p = passwordPolicyOf(policy);
253
+ const t = RULES[locale] ?? RULES.en;
254
+ const said = reasons.filter((r) => r in t.reasons).map((r) => fill(t.reasons[r], p));
255
+ return said.length > 0 ? said.join(" ") : describePasswordPolicy(p, locale);
256
+ }
153
257
  var EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
154
258
  function checkEmail(value) {
155
259
  const v = typeof value === "string" ? value.trim() : "";
@@ -162,13 +266,12 @@ function checkPassword(value) {
162
266
  if (value.length > MAX_PASSWORD) return { field: "password", code: "too_long", message: "That password is too long." };
163
267
  return null;
164
268
  }
165
- function checkNewPassword(value) {
166
- const basic = checkPassword(value);
167
- if (basic) return basic;
168
- if (value.length < MIN_PASSWORD) {
169
- return { field: "password", code: "too_short", message: `Use at least ${MIN_PASSWORD} characters.` };
170
- }
171
- return null;
269
+ function checkNewPassword(value, policy) {
270
+ if (typeof value !== "string" || value === "") return { field: "password", code: "required", message: "Enter your password." };
271
+ const p = policy ? passwordPolicyOf(policy) : DEFAULT_PASSWORD_POLICY;
272
+ const reasons = passwordProblems(value, p);
273
+ if (reasons.length === 0) return null;
274
+ return { field: "password", code: reasons[0], message: explainPasswordProblems(reasons, p) };
172
275
  }
173
276
  function checkName(value) {
174
277
  if (value === void 0 || value === null || value === "") return null;
@@ -231,7 +334,7 @@ function createAuthClient(options = {}) {
231
334
  const body = await res.json().catch(() => null);
232
335
  if (res.ok && body && typeof body.status === "string") return { ok: true, ...body };
233
336
  const e = body?.error;
234
- return { ok: false, error: e && typeof e.code === "string" ? { code: e.code, message: String(e.message ?? ""), ...e.field ? { field: e.field } : {} } : UNAVAILABLE };
337
+ 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 };
235
338
  } catch {
236
339
  return { ok: false, error: UNAVAILABLE };
237
340
  }
@@ -259,12 +362,14 @@ function createAuthClient(options = {}) {
259
362
  if (o.locale) q.set("locale", o.locale);
260
363
  return `${base}/login?${q}`;
261
364
  };
365
+ let policy;
262
366
  async function methods() {
263
367
  try {
264
368
  const res = await doFetch(`${base}/methods`, { credentials: "same-origin", headers: { accept: "application/json" } });
265
369
  if (res.ok) {
266
370
  const m = await res.json();
267
- return { providers: [], signUp: true, magicLink: false, passkey: false, ...m, passkeyHere: m.passkeyHere === true, passkeyOnCauthy: m.passkeyOnCauthy ?? m.passkey === true, sso: m.sso === true };
371
+ if (m.password) policy = passwordPolicyOf(m.password);
372
+ 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 };
268
373
  }
269
374
  } catch {
270
375
  }
@@ -408,12 +513,12 @@ function createAuthClient(options = {}) {
408
513
  email: (f) => post(
409
514
  "/sign-up/email",
410
515
  { email: f.email, password: f.password, name: f.name, organization: f.organization, acceptLegal: f.acceptLegal, consents: f.consents },
411
- firstProblem(checkName(f.name), checkEmail(f.email), checkNewPassword(f.password), checkOrganization(f.organization)),
516
+ firstProblem(checkName(f.name), checkEmail(f.email), checkNewPassword(f.password, policy), checkOrganization(f.organization)),
412
517
  f.captchaToken
413
518
  )
414
519
  },
415
520
  forgotPassword: (f) => post("/forgot-password", { email: f.email }, checkEmail(f.email), f.captchaToken),
416
- resetPassword: (f) => post("/reset-password", { token: f.token, password: f.password }, checkNewPassword(f.password)),
521
+ resetPassword: (f) => post("/reset-password", { token: f.token, password: f.password }, checkNewPassword(f.password, policy)),
417
522
  verifyEmail: (f) => post("/verify-email", { token: f.token }, null),
418
523
  signOut: () => post("/sign-out", {}, null),
419
524
  organizations: {
@@ -447,7 +552,7 @@ function createAuthClient(options = {}) {
447
552
  },
448
553
  acceptLegal: (f = {}) => post("/me/legal/accept", { kinds: f.kinds ?? [] }, null),
449
554
  changePassword: (f) => {
450
- const bad = checkNewPassword(f.newPassword);
555
+ const bad = checkNewPassword(f.newPassword, policy);
451
556
  return post("/me/password", { password: f.password, newPassword: f.newPassword }, bad ? { ...bad, field: "newPassword" } : null);
452
557
  },
453
558
  mfa: {
@@ -478,7 +583,7 @@ function createAuthClient(options = {}) {
478
583
  return body && Array.isArray(body.sessions) ? body.sessions : [];
479
584
  },
480
585
  signOutEverywhere: () => post("/me/sessions/end", {}, null),
481
- upgrade: (f) => post("/me/upgrade", { email: f.email, password: f.password, name: f.name ?? "" }, firstProblem(checkName(f.name), checkEmail(f.email), checkNewPassword(f.password))),
586
+ 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))),
482
587
  upgradeUrl,
483
588
  upgradeWith(provider, o) {
484
589
  window.location.assign(upgradeUrl(provider, o));