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.
- package/README.md +1 -1
- package/dist/{cauthy-DGAy45G2.d.ts → cauthy-BAwAJChs.d.ts} +1 -1
- package/dist/{cauthy-XkIbG5gE.d.mts → cauthy-CTVg4kvo.d.mts} +1 -1
- package/dist/client.d.mts +13 -4
- package/dist/client.d.ts +13 -4
- package/dist/client.js +130 -15
- package/dist/client.js.map +1 -1
- package/dist/client.mjs +124 -14
- package/dist/client.mjs.map +1 -1
- package/dist/express.d.mts +2 -2
- package/dist/express.d.ts +2 -2
- package/dist/fastify.d.mts +2 -2
- package/dist/fastify.d.ts +2 -2
- package/dist/hono.d.mts +2 -2
- package/dist/hono.d.ts +2 -2
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +174 -19
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +169 -19
- package/dist/index.mjs.map +1 -1
- package/dist/next.d.mts +2 -2
- package/dist/next.d.ts +2 -2
- package/dist/react.d.mts +27 -1
- package/dist/react.d.ts +27 -1
- package/dist/react.js +146 -19
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +146 -19
- package/dist/react.mjs.map +1 -1
- package/dist/styles.css +6 -0
- package/dist/svelte.d.mts +3 -3
- package/dist/svelte.d.ts +3 -3
- package/dist/svelte.js +118 -13
- package/dist/svelte.js.map +1 -1
- package/dist/svelte.mjs +118 -13
- package/dist/svelte.mjs.map +1 -1
- package/dist/{system-DTIeDXDP.d.ts → system-Bn8uckvQ.d.ts} +5 -3
- package/dist/{system-BxMsSIu1.d.mts → system-CVBW_AV8.d.mts} +5 -3
- package/dist/validate-sEXG82po.d.mts +66 -0
- package/dist/validate-sEXG82po.d.ts +66 -0
- package/dist/vue.d.mts +3 -3
- package/dist/vue.d.ts +3 -3
- package/dist/vue.js +118 -13
- package/dist/vue.js.map +1 -1
- package/dist/vue.mjs +118 -13
- package/dist/vue.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/validate-YbEfIzfl.d.mts +0 -19
- package/dist/validate-YbEfIzfl.d.ts +0 -19
package/dist/next.d.mts
CHANGED
package/dist/next.d.ts
CHANGED
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
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
|
|
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":
|
|
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
|
] });
|