cauthy-auth 0.27.1 → 0.28.1

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.
Files changed (49) hide show
  1. package/README.md +1 -1
  2. package/dist/{cauthy-DGAy45G2.d.ts → cauthy-BAwAJChs.d.ts} +1 -1
  3. package/dist/{cauthy-XkIbG5gE.d.mts → cauthy-CTVg4kvo.d.mts} +1 -1
  4. package/dist/client.d.mts +13 -4
  5. package/dist/client.d.ts +13 -4
  6. package/dist/client.js +130 -15
  7. package/dist/client.js.map +1 -1
  8. package/dist/client.mjs +124 -14
  9. package/dist/client.mjs.map +1 -1
  10. package/dist/express.d.mts +2 -2
  11. package/dist/express.d.ts +2 -2
  12. package/dist/fastify.d.mts +2 -2
  13. package/dist/fastify.d.ts +2 -2
  14. package/dist/hono.d.mts +2 -2
  15. package/dist/hono.d.ts +2 -2
  16. package/dist/index.d.mts +3 -3
  17. package/dist/index.d.ts +3 -3
  18. package/dist/index.js +174 -19
  19. package/dist/index.js.map +1 -1
  20. package/dist/index.mjs +169 -19
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/next.d.mts +2 -2
  23. package/dist/next.d.ts +2 -2
  24. package/dist/react.d.mts +27 -1
  25. package/dist/react.d.ts +27 -1
  26. package/dist/react.js +146 -19
  27. package/dist/react.js.map +1 -1
  28. package/dist/react.mjs +146 -19
  29. package/dist/react.mjs.map +1 -1
  30. package/dist/styles.css +6 -0
  31. package/dist/svelte.d.mts +3 -3
  32. package/dist/svelte.d.ts +3 -3
  33. package/dist/svelte.js +118 -13
  34. package/dist/svelte.js.map +1 -1
  35. package/dist/svelte.mjs +118 -13
  36. package/dist/svelte.mjs.map +1 -1
  37. package/dist/{system-DTIeDXDP.d.ts → system-Bn8uckvQ.d.ts} +5 -3
  38. package/dist/{system-BxMsSIu1.d.mts → system-CVBW_AV8.d.mts} +5 -3
  39. package/dist/validate-sEXG82po.d.mts +66 -0
  40. package/dist/validate-sEXG82po.d.ts +66 -0
  41. package/dist/vue.d.mts +3 -3
  42. package/dist/vue.d.ts +3 -3
  43. package/dist/vue.js +118 -13
  44. package/dist/vue.js.map +1 -1
  45. package/dist/vue.mjs +118 -13
  46. package/dist/vue.mjs.map +1 -1
  47. package/package.json +1 -1
  48. package/dist/validate-YbEfIzfl.d.mts +0 -19
  49. package/dist/validate-YbEfIzfl.d.ts +0 -19
package/dist/next.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as Cauthy } from './cauthy-XkIbG5gE.mjs';
2
- import { S as Session } from './system-BxMsSIu1.mjs';
1
+ import { C as Cauthy } from './cauthy-CTVg4kvo.mjs';
2
+ import { S as Session } from './system-CVBW_AV8.mjs';
3
3
  import 'jose';
4
4
  import './theme.mjs';
5
5
 
package/dist/next.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as Cauthy } from './cauthy-DGAy45G2.js';
2
- import { S as Session } from './system-DTIeDXDP.js';
1
+ import { C as Cauthy } from './cauthy-BAwAJChs.js';
2
+ import { S as Session } from './system-Bn8uckvQ.js';
3
3
  import 'jose';
4
4
  import './theme.js';
5
5
 
package/dist/react.d.mts CHANGED
@@ -211,6 +211,23 @@ interface PublicSession {
211
211
  }
212
212
 
213
213
  type AuthField = 'email' | 'password' | 'newPassword' | 'name' | 'code' | 'organization';
214
+ /** A kind of character a workspace's password policy can ask for. */
215
+ type PasswordClass = 'lowercase' | 'uppercase' | 'digit' | 'symbol';
216
+ /**
217
+ * What a new password must be in the workspace, as `methods().password` says
218
+ * it. Length is counted in bytes of UTF-8, as Cauthy counts it. A symbol is
219
+ * any character that is neither a letter nor a digit, a space included.
220
+ */
221
+ interface PasswordPolicy {
222
+ min: number;
223
+ max: number;
224
+ /** The kinds the password must each carry at least once. */
225
+ requires: PasswordClass[];
226
+ /** At least this many of the four kinds; 0 asks none. */
227
+ min_classes: number;
228
+ /** Cauthy refuses a password found in a published breach (checked by Cauthy alone). */
229
+ reject_breached: boolean;
230
+ }
214
231
 
215
232
  /** Whether this browser can use a passkey at all. */
216
233
  declare function canUsePasskeys(): boolean;
@@ -228,6 +245,8 @@ interface AuthError {
228
245
  message: string;
229
246
  /** The input it is about, when it is about one. */
230
247
  field?: AuthField;
248
+ /** `weak_password`: the rules of the workspace's password policy it failed (`too_short`, `needs_digit`, …). */
249
+ reasons?: string[];
231
250
  }
232
251
  type AuthResult<S extends string, More = unknown> = ({
233
252
  ok: true;
@@ -295,6 +314,13 @@ interface AuthMethods {
295
314
  required: boolean;
296
315
  }[];
297
316
  };
317
+ /**
318
+ * What a new password must be in the workspace. Once `methods()` has
319
+ * answered, this client checks new passwords against it before sending
320
+ * them; until then, against the defaults (12 to 128 characters). Absent
321
+ * from a server that does not say.
322
+ */
323
+ password?: PasswordPolicy;
298
324
  }
299
325
  /** A legal document the person owes: not yet accepted in its current version. */
300
326
  interface PendingLegal {
@@ -1006,7 +1032,7 @@ interface AuthTexts {
1006
1032
  /** By error code; one missing shows what the server said. */
1007
1033
  errors: Record<string, string>;
1008
1034
  }
1009
- type Slot = 'form' | 'field' | 'label' | 'input' | 'button' | 'providerButton' | 'link' | 'error' | 'fieldError' | 'notice' | 'separator' | 'footer' | 'check' | 'account' | 'section' | 'heading' | 'secret' | 'codes' | 'sessions' | 'session';
1035
+ type Slot = 'form' | 'field' | 'label' | 'input' | 'button' | 'providerButton' | 'link' | 'error' | 'fieldError' | 'hint' | 'notice' | 'separator' | 'footer' | 'check' | 'account' | 'section' | 'heading' | 'secret' | 'codes' | 'sessions' | 'session';
1010
1036
  interface AuthFormProps {
1011
1037
  /** The language of the built-in sentences. Default: `en`. */
1012
1038
  locale?: 'en' | 'es';
package/dist/react.d.ts CHANGED
@@ -211,6 +211,23 @@ interface PublicSession {
211
211
  }
212
212
 
213
213
  type AuthField = 'email' | 'password' | 'newPassword' | 'name' | 'code' | 'organization';
214
+ /** A kind of character a workspace's password policy can ask for. */
215
+ type PasswordClass = 'lowercase' | 'uppercase' | 'digit' | 'symbol';
216
+ /**
217
+ * What a new password must be in the workspace, as `methods().password` says
218
+ * it. Length is counted in bytes of UTF-8, as Cauthy counts it. A symbol is
219
+ * any character that is neither a letter nor a digit, a space included.
220
+ */
221
+ interface PasswordPolicy {
222
+ min: number;
223
+ max: number;
224
+ /** The kinds the password must each carry at least once. */
225
+ requires: PasswordClass[];
226
+ /** At least this many of the four kinds; 0 asks none. */
227
+ min_classes: number;
228
+ /** Cauthy refuses a password found in a published breach (checked by Cauthy alone). */
229
+ reject_breached: boolean;
230
+ }
214
231
 
215
232
  /** Whether this browser can use a passkey at all. */
216
233
  declare function canUsePasskeys(): boolean;
@@ -228,6 +245,8 @@ interface AuthError {
228
245
  message: string;
229
246
  /** The input it is about, when it is about one. */
230
247
  field?: AuthField;
248
+ /** `weak_password`: the rules of the workspace's password policy it failed (`too_short`, `needs_digit`, …). */
249
+ reasons?: string[];
231
250
  }
232
251
  type AuthResult<S extends string, More = unknown> = ({
233
252
  ok: true;
@@ -295,6 +314,13 @@ interface AuthMethods {
295
314
  required: boolean;
296
315
  }[];
297
316
  };
317
+ /**
318
+ * What a new password must be in the workspace. Once `methods()` has
319
+ * answered, this client checks new passwords against it before sending
320
+ * them; until then, against the defaults (12 to 128 characters). Absent
321
+ * from a server that does not say.
322
+ */
323
+ password?: PasswordPolicy;
298
324
  }
299
325
  /** A legal document the person owes: not yet accepted in its current version. */
300
326
  interface PendingLegal {
@@ -1006,7 +1032,7 @@ interface AuthTexts {
1006
1032
  /** By error code; one missing shows what the server said. */
1007
1033
  errors: Record<string, string>;
1008
1034
  }
1009
- type Slot = 'form' | 'field' | 'label' | 'input' | 'button' | 'providerButton' | 'link' | 'error' | 'fieldError' | 'notice' | 'separator' | 'footer' | 'check' | 'account' | 'section' | 'heading' | 'secret' | 'codes' | 'sessions' | 'session';
1035
+ type Slot = 'form' | 'field' | 'label' | 'input' | 'button' | 'providerButton' | 'link' | 'error' | 'fieldError' | 'hint' | 'notice' | 'separator' | 'footer' | 'check' | 'account' | 'section' | 'heading' | 'secret' | 'codes' | 'sessions' | 'session';
1010
1036
  interface AuthFormProps {
1011
1037
  /** The language of the built-in sentences. Default: `en`. */
1012
1038
  locale?: 'en' | 'es';
package/dist/react.js CHANGED
@@ -185,7 +185,111 @@ function fromWire(wire) {
185
185
  // src/validate.ts
186
186
  var MIN_PASSWORD = 12;
187
187
  var MAX_PASSWORD = 128;
188
+ var FLOOR_PASSWORD = 8;
188
189
  var MAX_NAME = 120;
190
+ var CLASSES = ["lowercase", "uppercase", "digit", "symbol"];
191
+ var DEFAULT_PASSWORD_POLICY = Object.freeze({ min: MIN_PASSWORD, max: MAX_PASSWORD, requires: [], min_classes: 0, reject_breached: false });
192
+ var int = (v, fallback) => typeof v === "number" && Number.isFinite(v) ? Math.trunc(v) : fallback;
193
+ function passwordPolicyOf(raw) {
194
+ const r = raw && typeof raw === "object" ? raw : {};
195
+ const min = Math.min(Math.max(int(r.min ?? r.min_length, MIN_PASSWORD) || MIN_PASSWORD, FLOOR_PASSWORD), MAX_PASSWORD);
196
+ const max = Math.max(Math.min(int(r.max ?? r.max_length, MAX_PASSWORD) || MAX_PASSWORD, MAX_PASSWORD), min);
197
+ const asked = Array.isArray(r.requires) ? r.requires : [];
198
+ return {
199
+ min,
200
+ max,
201
+ requires: CLASSES.filter((c) => asked.includes(c)),
202
+ min_classes: Math.min(Math.max(int(r.min_classes ?? r.minClasses, 0), 0), CLASSES.length),
203
+ reject_breached: (r.reject_breached ?? r.rejectBreached) === true
204
+ };
205
+ }
206
+ function utf8Length(v) {
207
+ let n = 0;
208
+ for (const ch of v) {
209
+ const c = ch.codePointAt(0);
210
+ n += c < 128 ? 1 : c < 2048 ? 2 : c < 65536 ? 3 : 4;
211
+ }
212
+ return n;
213
+ }
214
+ function classesOf(v) {
215
+ const has = /* @__PURE__ */ new Set();
216
+ for (const ch of v) {
217
+ if (/\p{Ll}/u.test(ch)) has.add("lowercase");
218
+ else if (/\p{Lu}/u.test(ch)) has.add("uppercase");
219
+ else if (/\p{Nd}/u.test(ch)) has.add("digit");
220
+ else if (!/\p{L}/u.test(ch)) has.add("symbol");
221
+ }
222
+ return has;
223
+ }
224
+ function passwordProblems(value, policy = DEFAULT_PASSWORD_POLICY) {
225
+ const p = passwordPolicyOf(policy);
226
+ const out = [];
227
+ const length = utf8Length(value);
228
+ if (length < p.min) out.push("too_short");
229
+ if (length > p.max) out.push("too_long");
230
+ const has = classesOf(value);
231
+ for (const c of p.requires) if (!has.has(c)) out.push(`needs_${c}`);
232
+ if (p.min_classes > 0 && CLASSES.filter((c) => has.has(c)).length < p.min_classes) out.push("needs_classes");
233
+ return out;
234
+ }
235
+ var RULES = {
236
+ en: {
237
+ between: "Between {min} and {max} characters.",
238
+ requires: "Include {kinds}.",
239
+ and: "and",
240
+ classes: "Use at least {n} of these: lowercase letters, uppercase letters, digits, symbols.",
241
+ breached: "Passwords found in known data breaches are not accepted.",
242
+ kinds: { lowercase: "a lowercase letter", uppercase: "an uppercase letter", digit: "a digit", symbol: "a symbol" },
243
+ reasons: {
244
+ too_short: "Use at least {min} characters.",
245
+ too_long: "Use at most {max} characters.",
246
+ needs_lowercase: "Include a lowercase letter.",
247
+ needs_uppercase: "Include an uppercase letter.",
248
+ needs_digit: "Include a digit.",
249
+ needs_symbol: "Include a symbol.",
250
+ needs_classes: "Use at least {n} of these: lowercase letters, uppercase letters, digits, symbols.",
251
+ breached: "This password appears in a known data breach. Choose a different one."
252
+ }
253
+ },
254
+ es: {
255
+ between: "Entre {min} y {max} caracteres.",
256
+ requires: "Incluye {kinds}.",
257
+ and: "y",
258
+ classes: "Usa al menos {n} de estos tipos: min\xFAsculas, may\xFAsculas, n\xFAmeros, s\xEDmbolos.",
259
+ breached: "No se aceptan contrase\xF1as que aparecen en filtraciones de datos conocidas.",
260
+ kinds: { lowercase: "una min\xFAscula", uppercase: "una may\xFAscula", digit: "un n\xFAmero", symbol: "un s\xEDmbolo" },
261
+ reasons: {
262
+ too_short: "Usa al menos {min} caracteres.",
263
+ too_long: "Usa como m\xE1ximo {max} caracteres.",
264
+ needs_lowercase: "Incluye una min\xFAscula.",
265
+ needs_uppercase: "Incluye una may\xFAscula.",
266
+ needs_digit: "Incluye un n\xFAmero.",
267
+ needs_symbol: "Incluye un s\xEDmbolo.",
268
+ needs_classes: "Usa al menos {n} de estos tipos: min\xFAsculas, may\xFAsculas, n\xFAmeros, s\xEDmbolos.",
269
+ breached: "Esta contrase\xF1a aparece en una filtraci\xF3n de datos conocida. Elige otra."
270
+ }
271
+ }
272
+ };
273
+ var fill = (s, p) => s.replace("{min}", String(p.min)).replace("{max}", String(p.max)).replace("{n}", String(p.min_classes));
274
+ function describePasswordPolicy(policy = DEFAULT_PASSWORD_POLICY, locale = "en") {
275
+ const p = passwordPolicyOf(policy);
276
+ const t = RULES[locale] ?? RULES.en;
277
+ const parts = [fill(t.between, p)];
278
+ if (p.requires.length > 0) {
279
+ const kinds = p.requires.map((c) => t.kinds[c]);
280
+ const listed = kinds.length === 1 ? kinds[0] : `${kinds.slice(0, -1).join(", ")} ${t.and} ${kinds[kinds.length - 1]}`;
281
+ parts.push(t.requires.replace("{kinds}", listed));
282
+ }
283
+ if (p.min_classes > 0) parts.push(fill(t.classes, p));
284
+ if (p.reject_breached) parts.push(t.breached);
285
+ return parts.join(" ");
286
+ }
287
+ function explainPasswordProblems(reasons, policy = DEFAULT_PASSWORD_POLICY, locale = "en") {
288
+ const p = passwordPolicyOf(policy);
289
+ const t = RULES[locale] ?? RULES.en;
290
+ const said = reasons.filter((r) => r in t.reasons).map((r) => fill(t.reasons[r], p));
291
+ return said.length > 0 ? said.join(" ") : describePasswordPolicy(p, locale);
292
+ }
189
293
  var EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
190
294
  function checkEmail(value) {
191
295
  const v = typeof value === "string" ? value.trim() : "";
@@ -198,13 +302,12 @@ function checkPassword(value) {
198
302
  if (value.length > MAX_PASSWORD) return { field: "password", code: "too_long", message: "That password is too long." };
199
303
  return null;
200
304
  }
201
- function checkNewPassword(value) {
202
- const basic = checkPassword(value);
203
- if (basic) return basic;
204
- if (value.length < MIN_PASSWORD) {
205
- return { field: "password", code: "too_short", message: `Use at least ${MIN_PASSWORD} characters.` };
206
- }
207
- return null;
305
+ function checkNewPassword(value, policy) {
306
+ if (typeof value !== "string" || value === "") return { field: "password", code: "required", message: "Enter your password." };
307
+ const p = policy ? passwordPolicyOf(policy) : DEFAULT_PASSWORD_POLICY;
308
+ const reasons = passwordProblems(value, p);
309
+ if (reasons.length === 0) return null;
310
+ return { field: "password", code: reasons[0], message: explainPasswordProblems(reasons, p) };
208
311
  }
209
312
  function checkName(value) {
210
313
  if (value === void 0 || value === null || value === "") return null;
@@ -267,7 +370,7 @@ function createAuthClient(options = {}) {
267
370
  const body = await res.json().catch(() => null);
268
371
  if (res.ok && body && typeof body.status === "string") return { ok: true, ...body };
269
372
  const e = body?.error;
270
- return { ok: false, error: e && typeof e.code === "string" ? { code: e.code, message: String(e.message ?? ""), ...e.field ? { field: e.field } : {} } : UNAVAILABLE };
373
+ 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 };
271
374
  } catch {
272
375
  return { ok: false, error: UNAVAILABLE };
273
376
  }
@@ -295,12 +398,14 @@ function createAuthClient(options = {}) {
295
398
  if (o.locale) q.set("locale", o.locale);
296
399
  return `${base}/login?${q}`;
297
400
  };
401
+ let policy;
298
402
  async function methods() {
299
403
  try {
300
404
  const res = await doFetch(`${base}/methods`, { credentials: "same-origin", headers: { accept: "application/json" } });
301
405
  if (res.ok) {
302
406
  const m = await res.json();
303
- return { providers: [], signUp: true, magicLink: false, passkey: false, ...m, passkeyHere: m.passkeyHere === true, passkeyOnCauthy: m.passkeyOnCauthy ?? m.passkey === true, sso: m.sso === true };
407
+ if (m.password) policy = passwordPolicyOf(m.password);
408
+ 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 };
304
409
  }
305
410
  } catch {
306
411
  }
@@ -444,12 +549,12 @@ function createAuthClient(options = {}) {
444
549
  email: (f) => post(
445
550
  "/sign-up/email",
446
551
  { email: f.email, password: f.password, name: f.name, organization: f.organization, acceptLegal: f.acceptLegal, consents: f.consents },
447
- firstProblem(checkName(f.name), checkEmail(f.email), checkNewPassword(f.password), checkOrganization(f.organization)),
552
+ firstProblem(checkName(f.name), checkEmail(f.email), checkNewPassword(f.password, policy), checkOrganization(f.organization)),
448
553
  f.captchaToken
449
554
  )
450
555
  },
451
556
  forgotPassword: (f) => post("/forgot-password", { email: f.email }, checkEmail(f.email), f.captchaToken),
452
- resetPassword: (f) => post("/reset-password", { token: f.token, password: f.password }, checkNewPassword(f.password)),
557
+ resetPassword: (f) => post("/reset-password", { token: f.token, password: f.password }, checkNewPassword(f.password, policy)),
453
558
  verifyEmail: (f) => post("/verify-email", { token: f.token }, null),
454
559
  signOut: () => post("/sign-out", {}, null),
455
560
  organizations: {
@@ -483,7 +588,7 @@ function createAuthClient(options = {}) {
483
588
  },
484
589
  acceptLegal: (f = {}) => post("/me/legal/accept", { kinds: f.kinds ?? [] }, null),
485
590
  changePassword: (f) => {
486
- const bad = checkNewPassword(f.newPassword);
591
+ const bad = checkNewPassword(f.newPassword, policy);
487
592
  return post("/me/password", { password: f.password, newPassword: f.newPassword }, bad ? { ...bad, field: "newPassword" } : null);
488
593
  },
489
594
  mfa: {
@@ -514,7 +619,7 @@ function createAuthClient(options = {}) {
514
619
  return body && Array.isArray(body.sessions) ? body.sessions : [];
515
620
  },
516
621
  signOutEverywhere: () => post("/me/sessions/end", {}, null),
517
- upgrade: (f) => post("/me/upgrade", { email: f.email, password: f.password, name: f.name ?? "" }, firstProblem(checkName(f.name), checkEmail(f.email), checkNewPassword(f.password))),
622
+ 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))),
518
623
  upgradeUrl,
519
624
  upgradeWith(provider, o) {
520
625
  window.location.assign(upgradeUrl(provider, o));
@@ -1036,18 +1141,36 @@ function useForm(props) {
1036
1141
  const Input = props.components?.Input ?? PlainInput;
1037
1142
  const [error, setError] = (0, import_react.useState)(null);
1038
1143
  const [busy, setBusy] = (0, import_react.useState)(false);
1039
- const say = (e) => t.errors[e.code] ?? e.message;
1144
+ const [policy, setPolicy] = (0, import_react.useState)(void 0);
1145
+ const locale = props.locale ?? "en";
1146
+ const say = (e) => {
1147
+ if (props.texts?.errors?.[e.code] === void 0 && isPasswordRule(e)) return explainPasswordProblems(e.reasons ?? [e.code], policy, locale);
1148
+ return t.errors[e.code] ?? e.message;
1149
+ };
1150
+ const passwordRule = describePasswordPolicy(policy, locale);
1040
1151
  const done = (to) => {
1041
1152
  if (props.onSuccess) props.onSuccess();
1042
1153
  else window.location.assign(sitePath(to) ?? sameOrigin(props.returnTo) ?? "/");
1043
1154
  };
1044
1155
  const root = useRoot(props);
1045
- return { t, cx, Button, Input, error, setError, busy, setBusy, say, done, root };
1156
+ return { t, cx, Button, Input, error, setError, busy, setBusy, say, done, root, policy, setPolicy, passwordRule };
1157
+ }
1158
+ var PASSWORD_RULES = ["needs_lowercase", "needs_uppercase", "needs_digit", "needs_symbol", "needs_classes", "breached"];
1159
+ function isPasswordRule(e) {
1160
+ if (e.code === "weak_password" || PASSWORD_RULES.includes(e.code)) return true;
1161
+ return (e.code === "too_short" || e.code === "too_long") && (e.field === "password" || e.field === "newPassword");
1162
+ }
1163
+ function usePasswordPolicy(f, methods) {
1164
+ const { setPolicy } = f;
1165
+ (0, import_react.useEffect)(() => {
1166
+ if (methods?.password) setPolicy(methods.password);
1167
+ }, [methods, setPolicy]);
1046
1168
  }
1047
1169
  function Field(p) {
1048
1170
  const id = (0, import_react.useId)();
1049
1171
  const { f } = p;
1050
1172
  const bad = f.error?.field === p.name ? f.error : null;
1173
+ const described = [bad ? `${id}-error` : "", p.hint ? `${id}-hint` : ""].filter(Boolean).join(" ");
1051
1174
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: f.cx("field"), children: [
1052
1175
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: f.cx("label"), htmlFor: id, children: [
1053
1176
  p.label,
@@ -1064,10 +1187,11 @@ function Field(p) {
1064
1187
  required: p.required,
1065
1188
  className: f.cx("input"),
1066
1189
  "aria-invalid": bad ? true : void 0,
1067
- "aria-describedby": bad ? `${id}-error` : void 0,
1190
+ "aria-describedby": described || void 0,
1068
1191
  onChange: (e) => p.onChange(e.target.value)
1069
1192
  }
1070
1193
  ),
1194
+ p.hint && !bad && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { id: `${id}-hint`, className: f.cx("hint"), children: p.hint }),
1071
1195
  bad && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { id: `${id}-error`, role: "alert", className: f.cx("fieldError"), children: f.say(bad) })
1072
1196
  ] });
1073
1197
  }
@@ -1328,6 +1452,7 @@ function SignUp(props) {
1328
1452
  const [password, setPassword] = (0, import_react.useState)("");
1329
1453
  const [sent, setSent] = (0, import_react.useState)(false);
1330
1454
  const methods = useMethods(auth);
1455
+ usePasswordPolicy(f, methods);
1331
1456
  const challenge = useChallenge(methods?.captchaSiteKey, props.locale);
1332
1457
  const [acceptLegal, setAcceptLegal] = (0, import_react.useState)(false);
1333
1458
  const [consents, setConsents] = (0, import_react.useState)({});
@@ -1362,7 +1487,7 @@ function SignUp(props) {
1362
1487
  methods?.organization?.ask && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "organization", type: "text", label: f.t.company, value: organization, autoComplete: "organization", required: methods.organization.required, onChange: setOrganization }),
1363
1488
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "name", type: "text", label: f.t.name, value: name, autoComplete: "name", onChange: setName }),
1364
1489
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "email", type: "email", label: f.t.email, value: email, autoComplete: "username", required: true, onChange: setEmail }),
1365
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "password", type: "password", label: f.t.password, value: password, autoComplete: "new-password", required: true, onChange: setPassword }),
1490
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "password", type: "password", label: f.t.password, value: password, autoComplete: "new-password", required: true, onChange: setPassword, hint: f.passwordRule }),
1366
1491
  legal && legal.documents.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: f.cx("check"), children: [
1367
1492
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", name: "acceptLegal", checked: acceptLegal, required: true, onChange: (e) => setAcceptLegal(e.target.checked) }),
1368
1493
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
@@ -1428,6 +1553,7 @@ function ResetPassword(props) {
1428
1553
  const [token, setToken] = (0, import_react.useState)(null);
1429
1554
  const [password, setPassword] = (0, import_react.useState)("");
1430
1555
  const [changed, setChanged] = (0, import_react.useState)(false);
1556
+ usePasswordPolicy(f, useMethods(auth));
1431
1557
  useMailedToken("token", (t) => {
1432
1558
  setToken(t ?? "");
1433
1559
  if (!t) f.setError({ code: "invalid_token", message: "This link is not valid, or it has expired." });
@@ -1457,7 +1583,7 @@ function ResetPassword(props) {
1457
1583
  }
1458
1584
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { className: f.cx("form"), ...f.root, onSubmit: submit, noValidate: true, children: [
1459
1585
  general && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
1460
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "password", type: "password", label: f.t.newPassword, value: password, autoComplete: "new-password", required: true, onChange: setPassword }),
1586
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "password", type: "password", label: f.t.newPassword, value: password, autoComplete: "new-password", required: true, onChange: setPassword, hint: f.passwordRule }),
1461
1587
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "submit", disabled: f.busy || token === null, className: f.cx("button"), children: f.t.savePassword })
1462
1588
  ] });
1463
1589
  }
@@ -1798,6 +1924,7 @@ function PasswordPart({ props }) {
1798
1924
  const { f, done, setDone, run, general } = useSection(props);
1799
1925
  const [password, setPassword] = (0, import_react.useState)("");
1800
1926
  const [newPassword, setNewPassword] = (0, import_react.useState)("");
1927
+ usePasswordPolicy(f, useMethods(auth));
1801
1928
  const submit = async (e) => {
1802
1929
  e.preventDefault();
1803
1930
  const r = await run(() => auth.account.changePassword({ password, newPassword }));
@@ -1810,7 +1937,7 @@ function PasswordPart({ props }) {
1810
1937
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: f.cx("heading"), children: f.t.changePassword }),
1811
1938
  general && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
1812
1939
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "password", type: "password", label: f.t.currentPassword, value: password, autoComplete: "current-password", required: true, onChange: setPassword }),
1813
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "newPassword", type: "password", label: f.t.newPassword, value: newPassword, autoComplete: "new-password", required: true, onChange: setNewPassword }),
1940
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "newPassword", type: "password", label: f.t.newPassword, value: newPassword, autoComplete: "new-password", required: true, onChange: setNewPassword, hint: f.passwordRule }),
1814
1941
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.changePassword }),
1815
1942
  done && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "status", className: f.cx("notice"), children: done })
1816
1943
  ] });