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
@@ -1,6 +1,6 @@
1
1
  import { IncomingMessage, ServerResponse } from 'node:http';
2
- import { C as Cauthy } from './cauthy-XkIbG5gE.mjs';
3
- import { S as Session } from './system-BxMsSIu1.mjs';
2
+ import { C as Cauthy } from './cauthy-CTVg4kvo.mjs';
3
+ import { S as Session } from './system-CVBW_AV8.mjs';
4
4
  import 'jose';
5
5
  import './theme.mjs';
6
6
 
package/dist/express.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { IncomingMessage, ServerResponse } from 'node:http';
2
- import { C as Cauthy } from './cauthy-DGAy45G2.js';
3
- import { S as Session } from './system-DTIeDXDP.js';
2
+ import { C as Cauthy } from './cauthy-BAwAJChs.js';
3
+ import { S as Session } from './system-Bn8uckvQ.js';
4
4
  import 'jose';
5
5
  import './theme.js';
6
6
 
@@ -1,6 +1,6 @@
1
1
  import { FastifyReply, FastifyPluginAsync } from 'fastify';
2
- import { C as Cauthy } from './cauthy-XkIbG5gE.mjs';
3
- import { S as Session } from './system-BxMsSIu1.mjs';
2
+ import { C as Cauthy } from './cauthy-CTVg4kvo.mjs';
3
+ import { S as Session } from './system-CVBW_AV8.mjs';
4
4
  import 'jose';
5
5
  import './theme.mjs';
6
6
 
package/dist/fastify.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { FastifyReply, FastifyPluginAsync } from 'fastify';
2
- import { C as Cauthy } from './cauthy-DGAy45G2.js';
3
- import { S as Session } from './system-DTIeDXDP.js';
2
+ import { C as Cauthy } from './cauthy-BAwAJChs.js';
3
+ import { S as Session } from './system-Bn8uckvQ.js';
4
4
  import 'jose';
5
5
  import './theme.js';
6
6
 
package/dist/hono.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { MiddlewareHandler } from 'hono';
2
- import { C as Cauthy } from './cauthy-XkIbG5gE.mjs';
3
- import { S as Session } from './system-BxMsSIu1.mjs';
2
+ import { C as Cauthy } from './cauthy-CTVg4kvo.mjs';
3
+ import { S as Session } from './system-CVBW_AV8.mjs';
4
4
  import 'jose';
5
5
  import './theme.mjs';
6
6
 
package/dist/hono.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { MiddlewareHandler } from 'hono';
2
- import { C as Cauthy } from './cauthy-DGAy45G2.js';
3
- import { S as Session } from './system-DTIeDXDP.js';
2
+ import { C as Cauthy } from './cauthy-BAwAJChs.js';
3
+ import { S as Session } from './system-Bn8uckvQ.js';
4
4
  import 'jose';
5
5
  import './theme.js';
6
6
 
package/dist/index.d.mts CHANGED
@@ -1,7 +1,7 @@
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
- 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';
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-CTVg4kvo.mjs';
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-CVBW_AV8.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
- 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
- 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';
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-BAwAJChs.js';
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-Bn8uckvQ.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;
@@ -791,6 +899,13 @@ function providerIn(segment) {
791
899
  function env2(name) {
792
900
  return typeof process !== "undefined" ? process.env?.[name] : void 0;
793
901
  }
902
+ function hostOf(origin) {
903
+ try {
904
+ return new URL(origin).hostname;
905
+ } catch {
906
+ return void 0;
907
+ }
908
+ }
794
909
  function b64url(bytes) {
795
910
  let s = "";
796
911
  for (const b of bytes) s += String.fromCharCode(b);
@@ -824,6 +939,10 @@ function createCauthy(given = {}) {
824
939
  if (!/^https?:\/\//.test(issuer)) throw new CauthyConfigError("issuer (CAUTHY_ISSUER) must be Cauthy's address, e.g. https://id.example.com");
825
940
  if (clientId === "") throw new CauthyConfigError("clientId (CAUTHY_CLIENT_ID) is required");
826
941
  if (secret.length < 32) throw new CauthyConfigError("secret (CAUTHY_SECRET) must be at least 32 characters: it encrypts the session cookie");
942
+ const baseURL = given.baseURL ?? env2("CAUTHY_BASE_URL");
943
+ if (baseURL !== void 0 && baseURL !== "" && !/^https?:\/\/[^/]+\/*$/.test(baseURL)) {
944
+ throw new CauthyConfigError("baseURL (CAUTHY_BASE_URL) must be the application's public origin, e.g. https://app.example.com");
945
+ }
827
946
  const basePath = "/" + (given.basePath ?? "/api/auth").replace(/^\/+|\/+$/g, "");
828
947
  const afterSignIn = localPath(given.afterSignIn, "/");
829
948
  const afterSignOut = localPath(given.afterSignOut, "/");
@@ -839,7 +958,12 @@ function createCauthy(given = {}) {
839
958
  verifyEmail: given.pages?.verifyEmail ? localPath(given.pages.verifyEmail, "/") : void 0
840
959
  };
841
960
  const ownSystem = createSystem({ issuer, clientId, clientSecret, key: given.systemKey, fetch: given.fetch });
842
- const originOf = (request) => withoutTrailingSlashes(given.baseURL ?? new URL(request.url).origin);
961
+ const originOf = (request) => {
962
+ if (baseURL) return withoutTrailingSlashes(baseURL);
963
+ const url = new URL(request.url);
964
+ const proto = request.headers.get("x-forwarded-proto")?.split(",")[0]?.trim().toLowerCase();
965
+ return proto === "https" || proto === "http" ? `${proto}://${url.host}` : url.origin;
966
+ };
843
967
  const attrs = (request, maxAge) => ({ secure: originOf(request).startsWith("https://"), maxAge });
844
968
  async function token(form) {
845
969
  const body2 = new URLSearchParams({ client_id: clientId, ...form });
@@ -1135,11 +1259,20 @@ function createCauthy(given = {}) {
1135
1259
  connectedAt: String(d?.created_at ?? ""),
1136
1260
  lastUsedAt: String(d?.last_used_at ?? "")
1137
1261
  });
1138
- const problem = (status2, code, message, field, headers) => json({ error: { code, message, ...field ? { field } : {} } }, status2, headers);
1262
+ const problem = (status2, code, message, field, headers, reasons) => json({ error: { code, message, ...field ? { field } : {}, ...reasons && reasons.length > 0 ? { reasons } : {} } }, status2, headers);
1139
1263
  const invalid = (p) => problem(400, p.code, p.message, p.field);
1264
+ let explained = false;
1140
1265
  function ownRequest(request) {
1141
1266
  const origin = request.headers.get("origin");
1142
- if (origin !== null) return origin === originOf(request) || origin === new URL(request.url).origin;
1267
+ if (origin !== null) {
1268
+ const own = originOf(request);
1269
+ if (origin === own || origin === new URL(request.url).origin) return true;
1270
+ if (!explained && hostOf(origin) === new URL(own).hostname) {
1271
+ explained = true;
1272
+ console.warn(`cauthy-auth: refused a request from ${origin} as cross_site: this application sees itself as ${own}. Behind a proxy? Set CAUTHY_BASE_URL (or baseURL) to the public origin.`);
1273
+ }
1274
+ return false;
1275
+ }
1143
1276
  return (request.headers.get("sec-fetch-site") ?? "same-origin") === "same-origin";
1144
1277
  }
1145
1278
  async function body(request) {
@@ -1193,7 +1326,8 @@ function createCauthy(given = {}) {
1193
1326
  const b = res.status === 204 || res.status === 202 ? null : await res.json().catch(() => null);
1194
1327
  if (res.ok) return { status: res.status, data: b?.data ?? b };
1195
1328
  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.") } };
1329
+ const reasons = Array.isArray(first?.meta?.reasons) ? first.meta.reasons.map(String) : void 0;
1330
+ 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
1331
  } catch {
1198
1332
  return { status: 503, data: null, error: { code: "unavailable", message: "Sign-in is unavailable right now. Try again in a moment." } };
1199
1333
  }
@@ -1221,6 +1355,19 @@ function createCauthy(given = {}) {
1221
1355
  return json({ status: "signed_in", session: await toPublic(toSession(s)), ...more }, 200, h);
1222
1356
  }
1223
1357
  let lookCache = null;
1358
+ let policyCache = null;
1359
+ const keepPolicy = (settings) => {
1360
+ policyCache = { until: Date.now() + LOOK_TTL_MS, policy: settings.error || !settings.data?.password_policy ? null : passwordPolicyOf(settings.data.password_policy) };
1361
+ return policyCache.policy;
1362
+ };
1363
+ async function workspacePolicy(request) {
1364
+ if (policyCache !== null && policyCache.until >= Date.now()) return policyCache.policy;
1365
+ return keepPolicy(await ask(request, "GET", "/v1/auth/settings"));
1366
+ }
1367
+ async function newPasswordProblem(request, value) {
1368
+ const policy = await workspacePolicy(request);
1369
+ return checkNewPassword(value, policy ?? { min: 8, max: 128, requires: [], min_classes: 0, reject_breached: false });
1370
+ }
1224
1371
  async function ownRoute(request, url, route) {
1225
1372
  const post = request.method === "POST";
1226
1373
  const own = [
@@ -1298,6 +1445,7 @@ function createCauthy(given = {}) {
1298
1445
  ask(request, "GET", "/v1/auth/sso/connections")
1299
1446
  ]);
1300
1447
  const list = Array.isArray(providers.data) ? providers.data : [];
1448
+ const password = keepPolicy(settings);
1301
1449
  const here = place.data?.here === true;
1302
1450
  return json({
1303
1451
  providers: list.map((p) => ({ id: String(p.provider ?? p.id ?? ""), name: String(p.label ?? p.name ?? p.provider ?? "") })).filter((p) => p.id !== ""),
@@ -1313,7 +1461,9 @@ function createCauthy(given = {}) {
1313
1461
  sso: Array.isArray(sso.data) && sso.data.some((c) => c?.discoverable === true),
1314
1462
  // The terms a sign-up accepts and the consents it answers, when the workspace asks any.
1315
1463
  ...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) } : {}
1464
+ ...settings.data?.captcha_enabled === true && settings.data?.captcha_site_key ? { captchaSiteKey: String(settings.data.captcha_site_key) } : {},
1465
+ // What a new password must be: for the form to say it and check it.
1466
+ ...password ? { password } : {}
1317
1467
  });
1318
1468
  }
1319
1469
  if (route === "/sign-in/sso" && !post) {
@@ -1603,13 +1753,13 @@ function createCauthy(given = {}) {
1603
1753
  return json({ status: "sent", sentTo: String(res.data?.sent_to ?? ""), channel: String(res.data?.channel ?? "sms") });
1604
1754
  }
1605
1755
  if (route === "/sign-up/email") {
1606
- const bad = firstProblem(checkName(b.name), checkEmail(b.email), checkNewPassword(b.password), checkOrganization(b.organization));
1756
+ const bad = firstProblem(checkName(b.name), checkEmail(b.email), await newPasswordProblem(request, b.password), checkOrganization(b.organization));
1607
1757
  if (bad) return invalid(bad);
1608
1758
  const email = String(b.email).trim();
1609
1759
  const organization = typeof b.organization === "string" && b.organization.trim() !== "" ? { organization: { name: b.organization.trim() } } : {};
1610
1760
  const legal = { ...b.acceptLegal === true ? { accept_legal: true } : {}, ...b.consents && typeof b.consents === "object" ? { consents: b.consents } : {} };
1611
1761
  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);
1762
+ 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
1763
  if (made.data?.verification_required === true) return json({ status: "verification_required", email });
1614
1764
  const res = await ask(request, "POST", "/v1/auth/login", { email, password: b.password });
1615
1765
  if (res.error || res.data?.mfa_required === true) return json({ status: "verification_required", email });
@@ -1625,7 +1775,7 @@ function createCauthy(given = {}) {
1625
1775
  const mine = await signedInAs(request);
1626
1776
  if ("response" in mine) return mine.response;
1627
1777
  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);
1778
+ const refused = (res, field) => problem(res.status, res.error.code, res.error.message, field, mine.headers, res.error.reasons);
1629
1779
  const password = typeof b.password === "string" ? b.password : "";
1630
1780
  if (route === "/me") {
1631
1781
  const bad = checkName(b.name);
@@ -1646,7 +1796,7 @@ function createCauthy(given = {}) {
1646
1796
  return json({ status: "accepted" }, 200, mine.headers);
1647
1797
  }
1648
1798
  if (route === "/me/password") {
1649
- const bad = checkNewPassword(b.newPassword);
1799
+ const bad = await newPasswordProblem(request, b.newPassword);
1650
1800
  if (bad) return problem(400, bad.code, bad.message, "newPassword");
1651
1801
  const res = await as("/v1/auth/me/password", { current_password: password, new_password: b.newPassword });
1652
1802
  if (res.error) return refused(res, res.error.code === "wrong_password" ? "password" : res.error.code === "weak_password" ? "newPassword" : void 0);
@@ -1739,7 +1889,7 @@ function createCauthy(given = {}) {
1739
1889
  return json({ status: "removed", id }, 200, mine.headers);
1740
1890
  }
1741
1891
  if (route === "/me/upgrade") {
1742
- const bad = firstProblem(checkName(b.name), checkEmail(b.email), checkNewPassword(b.password));
1892
+ const bad = firstProblem(checkName(b.name), checkEmail(b.email), await newPasswordProblem(request, b.password));
1743
1893
  if (bad) return invalid(bad);
1744
1894
  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
1895
  if (res.error) return refused(res, res.error.code === "weak_password" ? "password" : "email");
@@ -1806,10 +1956,10 @@ function createCauthy(given = {}) {
1806
1956
  const badLink = () => problem(422, "invalid_token", "This link is not valid, or it has expired.");
1807
1957
  if (route === "/reset-password") {
1808
1958
  if (!isToken(b.token)) return badLink();
1809
- const bad = checkNewPassword(b.password);
1959
+ const bad = await newPasswordProblem(request, b.password);
1810
1960
  if (bad) return invalid(bad);
1811
1961
  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);
1962
+ 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
1963
  return json({ status: "changed" });
1814
1964
  }
1815
1965
  if (route === "/verify-email") {
@@ -2183,6 +2333,7 @@ async function verifyToken(from, options = {}) {
2183
2333
  CauthyConfigError,
2184
2334
  CauthyTokenError,
2185
2335
  CauthyWebhookError,
2336
+ DEFAULT_PASSWORD_POLICY,
2186
2337
  MIN_PASSWORD,
2187
2338
  can,
2188
2339
  checkCode,
@@ -2194,6 +2345,10 @@ async function verifyToken(from, options = {}) {
2194
2345
  createCauthy,
2195
2346
  createSystem,
2196
2347
  definePolicy,
2348
+ describePasswordPolicy,
2349
+ explainPasswordProblems,
2350
+ passwordPolicyOf,
2351
+ passwordProblems,
2197
2352
  policySource,
2198
2353
  pullPolicy,
2199
2354
  pushPolicy,