cauthy-auth 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.mts +60 -1
- package/dist/client.d.ts +60 -1
- package/dist/client.js +16 -7
- package/dist/client.js.map +1 -1
- package/dist/client.mjs +16 -7
- package/dist/client.mjs.map +1 -1
- package/dist/index.js +54 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +54 -1
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +74 -1
- package/dist/react.d.ts +74 -1
- package/dist/react.js +216 -12
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +216 -12
- package/dist/react.mjs.map +1 -1
- package/dist/styles.css +5 -0
- package/dist/svelte.js +16 -7
- package/dist/svelte.js.map +1 -1
- package/dist/svelte.mjs +16 -7
- package/dist/svelte.mjs.map +1 -1
- package/dist/vue.js +16 -7
- package/dist/vue.js.map +1 -1
- package/dist/vue.mjs +16 -7
- package/dist/vue.mjs.map +1 -1
- package/package.json +1 -1
package/dist/react.mjs
CHANGED
|
@@ -203,13 +203,13 @@ function createAuthClient(options = {}) {
|
|
|
203
203
|
const said = typeof options.locale === "function" ? options.locale() : options.locale;
|
|
204
204
|
return (said ?? (typeof document === "undefined" ? "" : document.documentElement?.lang) ?? "").trim();
|
|
205
205
|
};
|
|
206
|
-
async function post(path, form, bad) {
|
|
206
|
+
async function post(path, form, bad, captchaToken) {
|
|
207
207
|
if (bad) return { ok: false, error: { code: bad.code, message: bad.message, field: bad.field } };
|
|
208
208
|
try {
|
|
209
209
|
const res = await doFetch(`${base}${path}`, {
|
|
210
210
|
method: "POST",
|
|
211
211
|
credentials: "same-origin",
|
|
212
|
-
headers: { "content-type": "application/json", accept: "application/json", ...locale() ? { "x-cauthy-locale": locale() } : {} },
|
|
212
|
+
headers: { "content-type": "application/json", accept: "application/json", ...locale() ? { "x-cauthy-locale": locale() } : {}, ...captchaToken ? { "x-cauthy-captcha": captchaToken } : {} },
|
|
213
213
|
body: JSON.stringify(form)
|
|
214
214
|
});
|
|
215
215
|
const body = await res.json().catch(() => null);
|
|
@@ -261,9 +261,11 @@ function createAuthClient(options = {}) {
|
|
|
261
261
|
}
|
|
262
262
|
return {
|
|
263
263
|
signIn: {
|
|
264
|
-
email: (f) => post("/sign-in/email", { email: f.email, password: f.password }, firstProblem(checkEmail(f.email), checkPassword(f.password))),
|
|
264
|
+
email: (f) => post("/sign-in/email", { email: f.email, password: f.password }, firstProblem(checkEmail(f.email), checkPassword(f.password)), f.captchaToken),
|
|
265
265
|
mfa: (f) => post("/sign-in/mfa", { code: f.code }, checkCode(f.code)),
|
|
266
|
-
|
|
266
|
+
mfaMethods: () => post("/sign-in/mfa/methods", {}, null),
|
|
267
|
+
mfaSendCode: () => post("/sign-in/mfa/send", {}, null),
|
|
268
|
+
magicLink: (f) => post("/sign-in/magic-link", { email: f.email }, checkEmail(f.email), f.captchaToken),
|
|
267
269
|
magicLinkVerify: (f) => post("/sign-in/magic-link/verify", { token: f.token }, null),
|
|
268
270
|
emailCode: (f) => post("/sign-in/email-code", { code: f.code }, checkCode(f.code)),
|
|
269
271
|
passkeyUrl,
|
|
@@ -344,9 +346,9 @@ function createAuthClient(options = {}) {
|
|
|
344
346
|
}
|
|
345
347
|
},
|
|
346
348
|
signUp: {
|
|
347
|
-
email: (f) => post("/sign-up/email", { email: f.email, password: f.password, name: f.name }, firstProblem(checkName(f.name), checkEmail(f.email), checkNewPassword(f.password)))
|
|
349
|
+
email: (f) => post("/sign-up/email", { email: f.email, password: f.password, name: f.name }, firstProblem(checkName(f.name), checkEmail(f.email), checkNewPassword(f.password)), f.captchaToken)
|
|
348
350
|
},
|
|
349
|
-
forgotPassword: (f) => post("/forgot-password", { email: f.email }, checkEmail(f.email)),
|
|
351
|
+
forgotPassword: (f) => post("/forgot-password", { email: f.email }, checkEmail(f.email), f.captchaToken),
|
|
350
352
|
resetPassword: (f) => post("/reset-password", { token: f.token, password: f.password }, checkNewPassword(f.password)),
|
|
351
353
|
verifyEmail: (f) => post("/verify-email", { token: f.token }, null),
|
|
352
354
|
signOut: () => post("/sign-out", {}, null),
|
|
@@ -364,7 +366,14 @@ function createAuthClient(options = {}) {
|
|
|
364
366
|
setup: (f) => post("/me/mfa/setup", { password: f.password }, null),
|
|
365
367
|
verify: (f) => post("/me/mfa/verify", { code: f.code }, checkCode(f.code)),
|
|
366
368
|
disable: (f) => post("/me/mfa/disable", { password: f.password }, null),
|
|
367
|
-
recoveryCodes: (f) => post("/me/mfa/recovery-codes", { password: f.password }, null)
|
|
369
|
+
recoveryCodes: (f) => post("/me/mfa/recovery-codes", { password: f.password }, null),
|
|
370
|
+
async factors() {
|
|
371
|
+
const body = await read("/me/mfa/factors");
|
|
372
|
+
return body && typeof body.totp === "boolean" ? body : null;
|
|
373
|
+
},
|
|
374
|
+
addPhone: (f) => post("/me/mfa/phone", { phone: f.phone, channel: f.channel, password: f.password }, null),
|
|
375
|
+
verifyPhone: (f) => post("/me/mfa/phone/verify", { code: f.code }, checkCode(f.code)),
|
|
376
|
+
removePhone: (f) => post("/me/mfa/phone/remove", { password: f.password }, null)
|
|
368
377
|
},
|
|
369
378
|
changeEmail: (f) => post("/me/email", { email: f.email, password: f.password }, checkEmail(f.email)),
|
|
370
379
|
async identities() {
|
|
@@ -482,6 +491,18 @@ var TEXTS = {
|
|
|
482
491
|
linkSent: "If that address has an account, a link to choose a new password is on its way.",
|
|
483
492
|
codeHelp: "Enter the code from your authenticator app, or a recovery code.",
|
|
484
493
|
verify: "Verify",
|
|
494
|
+
codeHelpPhone: "We send a code to your phone. Ask for it, then type it here, or use a recovery code.",
|
|
495
|
+
sendCodeSms: "Send a code by SMS to {to}",
|
|
496
|
+
sendCodeWhatsapp: "Send a code by WhatsApp to {to}",
|
|
497
|
+
codeSent: "We sent a code to {to}. Type it here.",
|
|
498
|
+
phone: "Phone, with its country code",
|
|
499
|
+
phoneHelp: "Get your codes by SMS or WhatsApp instead of an app.",
|
|
500
|
+
phoneOn: "Codes go to {to}.",
|
|
501
|
+
phoneOff: "No phone yet.",
|
|
502
|
+
addPhone: "Add phone",
|
|
503
|
+
removePhone: "Remove phone",
|
|
504
|
+
bySms: "SMS",
|
|
505
|
+
byWhatsapp: "WhatsApp",
|
|
485
506
|
checkEmail: "Almost there: open the link we sent to confirm your address, then sign in.",
|
|
486
507
|
backToSignIn: "Back to sign in",
|
|
487
508
|
emailMeALink: "Email me a sign-in link",
|
|
@@ -549,6 +570,13 @@ var TEXTS = {
|
|
|
549
570
|
step_up_required: "Enter your current password.",
|
|
550
571
|
weak_password: "Choose a stronger password.",
|
|
551
572
|
unauthenticated: "Sign in first.",
|
|
573
|
+
phone_refused: "The code could not be sent to that number.",
|
|
574
|
+
too_many_codes: "Enough codes were sent. Use the last one, or sign in again.",
|
|
575
|
+
phone_unavailable: "Codes cannot be sent that way here.",
|
|
576
|
+
phone_already_set: "Remove the phone you have first.",
|
|
577
|
+
phone_codes_unavailable: "Codes cannot be sent right now. Try again in a moment.",
|
|
578
|
+
captcha_required: "Complete the check that you are not a bot, then try again.",
|
|
579
|
+
captcha_failed: "The check that you are not a bot did not pass. Try it again.",
|
|
552
580
|
passkey_failed: "The passkey could not be used. Try again, or sign in another way.",
|
|
553
581
|
passkey_exists: "This device already has a passkey for your account.",
|
|
554
582
|
passkey_unavailable: "Passkeys cannot be used here."
|
|
@@ -571,6 +599,18 @@ var TEXTS = {
|
|
|
571
599
|
linkSent: "Si ese correo tiene una cuenta, ya va en camino un enlace para elegir una contrase\xF1a nueva.",
|
|
572
600
|
codeHelp: "Escribe el c\xF3digo de tu aplicaci\xF3n de autenticaci\xF3n, o un c\xF3digo de recuperaci\xF3n.",
|
|
573
601
|
verify: "Verificar",
|
|
602
|
+
codeHelpPhone: "Te enviamos un c\xF3digo a tu tel\xE9fono. P\xEDdelo y escr\xEDbelo aqu\xED, o usa un c\xF3digo de recuperaci\xF3n.",
|
|
603
|
+
sendCodeSms: "Enviar un c\xF3digo por SMS a {to}",
|
|
604
|
+
sendCodeWhatsapp: "Enviar un c\xF3digo por WhatsApp a {to}",
|
|
605
|
+
codeSent: "Te enviamos un c\xF3digo a {to}. Escr\xEDbelo aqu\xED.",
|
|
606
|
+
phone: "Tel\xE9fono, con su c\xF3digo de pa\xEDs",
|
|
607
|
+
phoneHelp: "Recibe tus c\xF3digos por SMS o WhatsApp en vez de una app.",
|
|
608
|
+
phoneOn: "Los c\xF3digos van a {to}.",
|
|
609
|
+
phoneOff: "Todav\xEDa no tienes un tel\xE9fono.",
|
|
610
|
+
addPhone: "Agregar tel\xE9fono",
|
|
611
|
+
removePhone: "Quitar tel\xE9fono",
|
|
612
|
+
bySms: "SMS",
|
|
613
|
+
byWhatsapp: "WhatsApp",
|
|
574
614
|
checkEmail: "Ya casi: abre el enlace que te enviamos para confirmar tu correo y luego entra.",
|
|
575
615
|
backToSignIn: "Volver a entrar",
|
|
576
616
|
emailMeALink: "Env\xEDame un enlace para entrar",
|
|
@@ -638,6 +678,13 @@ var TEXTS = {
|
|
|
638
678
|
step_up_required: "Escribe tu contrase\xF1a actual.",
|
|
639
679
|
weak_password: "Elige una contrase\xF1a m\xE1s fuerte.",
|
|
640
680
|
unauthenticated: "Inicia sesi\xF3n primero.",
|
|
681
|
+
phone_refused: "No se pudo enviar el c\xF3digo a ese n\xFAmero.",
|
|
682
|
+
too_many_codes: "Ya se enviaron suficientes c\xF3digos. Usa el \xFAltimo o vuelve a entrar.",
|
|
683
|
+
phone_unavailable: "Aqu\xED no se pueden enviar c\xF3digos de esa forma.",
|
|
684
|
+
phone_already_set: "Primero quita el tel\xE9fono que tienes.",
|
|
685
|
+
phone_codes_unavailable: "Ahora no se pueden enviar c\xF3digos. Int\xE9ntalo en un momento.",
|
|
686
|
+
captcha_required: "Completa la verificaci\xF3n de que no eres un robot y vuelve a intentarlo.",
|
|
687
|
+
captcha_failed: "La verificaci\xF3n de que no eres un robot no pas\xF3. Int\xE9ntalo de nuevo.",
|
|
641
688
|
passkey_failed: "No se pudo usar la llave de acceso. Intenta de nuevo o entra de otra forma.",
|
|
642
689
|
passkey_exists: "Este dispositivo ya tiene una llave de acceso de tu cuenta.",
|
|
643
690
|
passkey_unavailable: "Aqu\xED no se pueden usar llaves de acceso."
|
|
@@ -682,6 +729,63 @@ function useRoot(props) {
|
|
|
682
729
|
if (props.unstyled) return {};
|
|
683
730
|
return { "data-cauthy": "", style: vars };
|
|
684
731
|
}
|
|
732
|
+
var turnstileScript = null;
|
|
733
|
+
function loadTurnstile() {
|
|
734
|
+
const ready = () => window.turnstile;
|
|
735
|
+
if (ready()) return Promise.resolve(ready());
|
|
736
|
+
turnstileScript ??= new Promise((resolve, reject) => {
|
|
737
|
+
const script = document.createElement("script");
|
|
738
|
+
script.src = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
|
|
739
|
+
script.async = true;
|
|
740
|
+
script.onload = () => ready() ? resolve(ready()) : reject(new Error("turnstile did not load"));
|
|
741
|
+
script.onerror = () => {
|
|
742
|
+
turnstileScript = null;
|
|
743
|
+
reject(new Error("turnstile did not load"));
|
|
744
|
+
};
|
|
745
|
+
document.head.appendChild(script);
|
|
746
|
+
});
|
|
747
|
+
return turnstileScript;
|
|
748
|
+
}
|
|
749
|
+
function useChallenge(siteKey, locale) {
|
|
750
|
+
const box = useRef(null);
|
|
751
|
+
const widget = useRef(null);
|
|
752
|
+
const [token, setToken] = useState("");
|
|
753
|
+
useEffect(() => {
|
|
754
|
+
if (!siteKey || !box.current) return;
|
|
755
|
+
let gone = false;
|
|
756
|
+
void loadTurnstile().then((api) => {
|
|
757
|
+
if (gone || !box.current) return;
|
|
758
|
+
const id = api.render(box.current, {
|
|
759
|
+
sitekey: siteKey,
|
|
760
|
+
language: locale ?? "auto",
|
|
761
|
+
size: "flexible",
|
|
762
|
+
callback: (t) => setToken(t),
|
|
763
|
+
"expired-callback": () => setToken(""),
|
|
764
|
+
"error-callback": () => setToken("")
|
|
765
|
+
});
|
|
766
|
+
widget.current = { api, id };
|
|
767
|
+
}).catch(() => {
|
|
768
|
+
});
|
|
769
|
+
return () => {
|
|
770
|
+
gone = true;
|
|
771
|
+
if (widget.current) widget.current.api.remove(widget.current.id);
|
|
772
|
+
widget.current = null;
|
|
773
|
+
};
|
|
774
|
+
}, [siteKey, locale]);
|
|
775
|
+
const renew = () => {
|
|
776
|
+
setToken("");
|
|
777
|
+
if (widget.current) widget.current.api.reset(widget.current.id);
|
|
778
|
+
};
|
|
779
|
+
const node = siteKey ? /* @__PURE__ */ jsx("div", { ref: box, className: "cauthy-challenge" }) : null;
|
|
780
|
+
return { node, token: token || void 0, renew };
|
|
781
|
+
}
|
|
782
|
+
function useMethods(auth) {
|
|
783
|
+
const [methods, setMethods] = useState(null);
|
|
784
|
+
useEffect(() => {
|
|
785
|
+
void auth.methods().then(setMethods);
|
|
786
|
+
}, []);
|
|
787
|
+
return methods;
|
|
788
|
+
}
|
|
685
789
|
function useForm(props) {
|
|
686
790
|
const t = { ...TEXTS[props.locale ?? "en"], ...props.texts, errors: { ...TEXTS[props.locale ?? "en"].errors, ...props.texts?.errors } };
|
|
687
791
|
const cx = (slot) => props.classNames?.[slot] ?? `cauthy-${slot}`;
|
|
@@ -754,6 +858,23 @@ function SignIn(props) {
|
|
|
754
858
|
const [password, setPassword] = useState("");
|
|
755
859
|
const [code, setCode] = useState("");
|
|
756
860
|
const [step, setStep] = useState("password");
|
|
861
|
+
const challenge = useChallenge(methods?.captchaSiteKey, props.locale);
|
|
862
|
+
const [phoneWay, setPhoneWay] = useState(null);
|
|
863
|
+
const [phoneSent, setPhoneSent] = useState("");
|
|
864
|
+
useEffect(() => {
|
|
865
|
+
if (step !== "code") return;
|
|
866
|
+
void auth.signIn.mfaMethods().then((r) => {
|
|
867
|
+
if (r.ok) setPhoneWay({ totp: r.totp, phone: r.phone });
|
|
868
|
+
});
|
|
869
|
+
}, [step]);
|
|
870
|
+
const sendCode = async () => {
|
|
871
|
+
f.setBusy(true);
|
|
872
|
+
f.setError(null);
|
|
873
|
+
const r = await auth.signIn.mfaSendCode();
|
|
874
|
+
f.setBusy(false);
|
|
875
|
+
if (!r.ok) return f.setError(r.error);
|
|
876
|
+
setPhoneSent(r.sentTo);
|
|
877
|
+
};
|
|
757
878
|
const autofill = useRef(null);
|
|
758
879
|
const [offer, setOffer] = useState(0);
|
|
759
880
|
useEffect(() => {
|
|
@@ -788,8 +909,9 @@ function SignIn(props) {
|
|
|
788
909
|
autofill.current?.abort();
|
|
789
910
|
f.setBusy(true);
|
|
790
911
|
f.setError(null);
|
|
791
|
-
const r = step === "code" ? await auth.signIn.mfa({ code }) : step === "sent" ? await auth.signIn.emailCode({ code }) : await auth.signIn.email({ email, password });
|
|
912
|
+
const r = step === "code" ? await auth.signIn.mfa({ code }) : step === "sent" ? await auth.signIn.emailCode({ code }) : await auth.signIn.email({ email, password, captchaToken: challenge.token });
|
|
792
913
|
f.setBusy(false);
|
|
914
|
+
if (step === "password") challenge.renew();
|
|
793
915
|
if (!r.ok) {
|
|
794
916
|
setOffer((n) => n + 1);
|
|
795
917
|
return f.setError(r.error);
|
|
@@ -812,8 +934,9 @@ function SignIn(props) {
|
|
|
812
934
|
autofill.current?.abort();
|
|
813
935
|
f.setBusy(true);
|
|
814
936
|
f.setError(null);
|
|
815
|
-
const r = await auth.signIn.magicLink({ email });
|
|
937
|
+
const r = await auth.signIn.magicLink({ email, captchaToken: challenge.token });
|
|
816
938
|
f.setBusy(false);
|
|
939
|
+
challenge.renew();
|
|
817
940
|
if (!r.ok) return f.setError(r.error);
|
|
818
941
|
setStep("sent");
|
|
819
942
|
};
|
|
@@ -829,9 +952,11 @@ function SignIn(props) {
|
|
|
829
952
|
] });
|
|
830
953
|
}
|
|
831
954
|
if (step === "code") {
|
|
955
|
+
const to = phoneWay?.phone?.number ?? "";
|
|
832
956
|
return /* @__PURE__ */ jsxs("form", { className: f.cx("form"), ...f.root, onSubmit: submit, noValidate: true, children: [
|
|
833
|
-
/* @__PURE__ */ jsx("p", { className: f.cx("notice"), children: f.t.codeHelp }),
|
|
957
|
+
/* @__PURE__ */ jsx("p", { className: f.cx("notice"), children: phoneSent ? f.t.codeSent.replace("{to}", phoneSent) : phoneWay?.phone && !phoneWay.totp ? f.t.codeHelpPhone : f.t.codeHelp }),
|
|
834
958
|
general && /* @__PURE__ */ jsx("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
|
|
959
|
+
phoneWay?.phone && /* @__PURE__ */ jsx(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => void sendCode(), children: (phoneWay.phone.channel === "whatsapp" ? f.t.sendCodeWhatsapp : f.t.sendCodeSms).replace("{to}", to) }),
|
|
835
960
|
/* @__PURE__ */ jsx(Field, { f, name: "code", type: "text", label: f.t.code, value: code, autoComplete: "one-time-code", required: true, onChange: setCode }),
|
|
836
961
|
/* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.verify })
|
|
837
962
|
] });
|
|
@@ -860,6 +985,7 @@ function SignIn(props) {
|
|
|
860
985
|
aside: props.links?.forgotPassword ? /* @__PURE__ */ jsx("a", { className: f.cx("link"), href: props.links.forgotPassword, children: f.t.forgotPassword }) : void 0
|
|
861
986
|
}
|
|
862
987
|
),
|
|
988
|
+
challenge.node,
|
|
863
989
|
/* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.signIn }),
|
|
864
990
|
methods?.magicLink && /* @__PURE__ */ jsx(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => void sendLink(), children: f.t.emailMeALink }),
|
|
865
991
|
methods?.signUp !== false && props.links?.signUp && /* @__PURE__ */ jsxs("p", { className: f.cx("footer"), children: [
|
|
@@ -891,12 +1017,14 @@ function SignUp(props) {
|
|
|
891
1017
|
const [email, setEmail] = useState("");
|
|
892
1018
|
const [password, setPassword] = useState("");
|
|
893
1019
|
const [sent, setSent] = useState(false);
|
|
1020
|
+
const challenge = useChallenge(useMethods(auth)?.captchaSiteKey, props.locale);
|
|
894
1021
|
const submit = async (e) => {
|
|
895
1022
|
e.preventDefault();
|
|
896
1023
|
f.setBusy(true);
|
|
897
1024
|
f.setError(null);
|
|
898
|
-
const r = await auth.signUp.email({ name, email, password });
|
|
1025
|
+
const r = await auth.signUp.email({ name, email, password, captchaToken: challenge.token });
|
|
899
1026
|
f.setBusy(false);
|
|
1027
|
+
challenge.renew();
|
|
900
1028
|
if (!r.ok) return f.setError(r.error);
|
|
901
1029
|
if (r.status === "verification_required") return setSent(true);
|
|
902
1030
|
f.done();
|
|
@@ -913,6 +1041,7 @@ function SignUp(props) {
|
|
|
913
1041
|
/* @__PURE__ */ jsx(Field, { f, name: "name", type: "text", label: f.t.name, value: name, autoComplete: "name", onChange: setName }),
|
|
914
1042
|
/* @__PURE__ */ jsx(Field, { f, name: "email", type: "email", label: f.t.email, value: email, autoComplete: "username", required: true, onChange: setEmail }),
|
|
915
1043
|
/* @__PURE__ */ jsx(Field, { f, name: "password", type: "password", label: f.t.password, value: password, autoComplete: "new-password", required: true, onChange: setPassword }),
|
|
1044
|
+
challenge.node,
|
|
916
1045
|
/* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.signUp }),
|
|
917
1046
|
props.links?.signIn && /* @__PURE__ */ jsxs("p", { className: f.cx("footer"), children: [
|
|
918
1047
|
f.t.haveAccount,
|
|
@@ -926,12 +1055,14 @@ function ForgotPassword(props) {
|
|
|
926
1055
|
const f = useForm(props);
|
|
927
1056
|
const [email, setEmail] = useState("");
|
|
928
1057
|
const [sent, setSent] = useState(false);
|
|
1058
|
+
const challenge = useChallenge(useMethods(auth)?.captchaSiteKey, props.locale);
|
|
929
1059
|
const submit = async (e) => {
|
|
930
1060
|
e.preventDefault();
|
|
931
1061
|
f.setBusy(true);
|
|
932
1062
|
f.setError(null);
|
|
933
|
-
const r = await auth.forgotPassword({ email });
|
|
1063
|
+
const r = await auth.forgotPassword({ email, captchaToken: challenge.token });
|
|
934
1064
|
f.setBusy(false);
|
|
1065
|
+
challenge.renew();
|
|
935
1066
|
if (!r.ok) return f.setError(r.error);
|
|
936
1067
|
setSent(true);
|
|
937
1068
|
};
|
|
@@ -945,6 +1076,7 @@ function ForgotPassword(props) {
|
|
|
945
1076
|
return /* @__PURE__ */ jsxs("form", { className: f.cx("form"), ...f.root, onSubmit: submit, noValidate: true, children: [
|
|
946
1077
|
general && /* @__PURE__ */ jsx("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
|
|
947
1078
|
/* @__PURE__ */ jsx(Field, { f, name: "email", type: "email", label: f.t.email, value: email, autoComplete: "username", required: true, onChange: setEmail }),
|
|
1079
|
+
challenge.node,
|
|
948
1080
|
/* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.sendLink }),
|
|
949
1081
|
props.links?.signIn && /* @__PURE__ */ jsx("p", { className: f.cx("footer"), children: /* @__PURE__ */ jsx("a", { className: f.cx("link"), href: props.links.signIn, children: f.t.backToSignIn }) })
|
|
950
1082
|
] });
|
|
@@ -1044,6 +1176,7 @@ function Account(props) {
|
|
|
1044
1176
|
account.hasPassword && /* @__PURE__ */ jsx(PasswordPart, { props }),
|
|
1045
1177
|
/* @__PURE__ */ jsx(LinkedPart, { props }),
|
|
1046
1178
|
/* @__PURE__ */ jsx(SecondStepPart, { props, account, onChanged: (mfaEnabled) => setAccount({ ...account, mfaEnabled }) }),
|
|
1179
|
+
/* @__PURE__ */ jsx(PhonePart, { props, account, onChanged: (mfaEnabled) => setAccount({ ...account, mfaEnabled }) }),
|
|
1047
1180
|
/* @__PURE__ */ jsx(PasskeysPart, { props, account, accountUrl: accountUrl(), when }),
|
|
1048
1181
|
/* @__PURE__ */ jsxs("section", { className: cx("section"), children: [
|
|
1049
1182
|
/* @__PURE__ */ jsx("h2", { className: cx("heading"), children: t.sessions }),
|
|
@@ -1273,6 +1406,77 @@ function SecondStepPart({ props, account, onChanged }) {
|
|
|
1273
1406
|
account.mfaEnabled && /* @__PURE__ */ jsx(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => void renew(), children: f.t.newRecoveryCodes })
|
|
1274
1407
|
] });
|
|
1275
1408
|
}
|
|
1409
|
+
function PhonePart({ props, account, onChanged }) {
|
|
1410
|
+
const auth = useAuth();
|
|
1411
|
+
const { f, run, general } = useSection(props);
|
|
1412
|
+
const [ways, setWays] = useState(null);
|
|
1413
|
+
const [number, setNumber] = useState("");
|
|
1414
|
+
const [channel, setChannel] = useState("sms");
|
|
1415
|
+
const [password, setPassword] = useState("");
|
|
1416
|
+
const [code, setCode] = useState("");
|
|
1417
|
+
const [sentTo, setSentTo] = useState("");
|
|
1418
|
+
const [codes, setCodes] = useState(null);
|
|
1419
|
+
const load = () => auth.account.mfa.factors().then((w) => {
|
|
1420
|
+
setWays(w);
|
|
1421
|
+
if (w && w.phoneChannels.length > 0 && !w.phoneChannels.includes(channel)) setChannel(w.phoneChannels[0]);
|
|
1422
|
+
});
|
|
1423
|
+
useEffect(() => {
|
|
1424
|
+
void load();
|
|
1425
|
+
}, []);
|
|
1426
|
+
if (!ways || ways.phoneChannels.length === 0) return null;
|
|
1427
|
+
const add = async (e) => {
|
|
1428
|
+
e.preventDefault();
|
|
1429
|
+
const r = await run(() => auth.account.mfa.addPhone({ phone: number, channel, password }));
|
|
1430
|
+
if (r.ok) setSentTo(r.sentTo);
|
|
1431
|
+
};
|
|
1432
|
+
const confirm = async (e) => {
|
|
1433
|
+
e.preventDefault();
|
|
1434
|
+
const r = await run(() => auth.account.mfa.verifyPhone({ code }));
|
|
1435
|
+
if (!r.ok) return;
|
|
1436
|
+
setSentTo("");
|
|
1437
|
+
setCode("");
|
|
1438
|
+
setPassword("");
|
|
1439
|
+
if (r.recoveryCodes.length > 0) setCodes(r.recoveryCodes);
|
|
1440
|
+
onChanged(true);
|
|
1441
|
+
await load();
|
|
1442
|
+
};
|
|
1443
|
+
const remove = async (e) => {
|
|
1444
|
+
e.preventDefault();
|
|
1445
|
+
const r = await run(() => auth.account.mfa.removePhone({ password }));
|
|
1446
|
+
if (!r.ok) return;
|
|
1447
|
+
setPassword("");
|
|
1448
|
+
await load();
|
|
1449
|
+
const left = await auth.account.mfa.factors();
|
|
1450
|
+
onChanged(Boolean(left?.totp));
|
|
1451
|
+
};
|
|
1452
|
+
const passwordField = account.hasPassword && /* @__PURE__ */ jsx(Field, { f, name: "password", type: "password", label: f.t.currentPassword, value: password, autoComplete: "current-password", required: true, onChange: setPassword });
|
|
1453
|
+
if (sentTo) {
|
|
1454
|
+
return /* @__PURE__ */ jsxs("form", { className: f.cx("section"), onSubmit: confirm, noValidate: true, children: [
|
|
1455
|
+
/* @__PURE__ */ jsx("h2", { className: f.cx("heading"), children: f.t.phone }),
|
|
1456
|
+
/* @__PURE__ */ jsx("p", { className: f.cx("notice"), children: f.t.codeSent.replace("{to}", sentTo) }),
|
|
1457
|
+
general && /* @__PURE__ */ jsx("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
|
|
1458
|
+
/* @__PURE__ */ jsx(Field, { f, name: "code", type: "text", label: f.t.code, value: code, autoComplete: "one-time-code", required: true, onChange: setCode }),
|
|
1459
|
+
/* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.verify })
|
|
1460
|
+
] });
|
|
1461
|
+
}
|
|
1462
|
+
return /* @__PURE__ */ jsxs("form", { className: f.cx("section"), onSubmit: ways.phone ? remove : add, noValidate: true, children: [
|
|
1463
|
+
/* @__PURE__ */ jsx("h2", { className: f.cx("heading"), children: f.t.phone }),
|
|
1464
|
+
/* @__PURE__ */ jsx("p", { className: f.cx("notice"), children: ways.phone ? f.t.phoneOn.replace("{to}", ways.phone.number) : `${f.t.phoneOff} ${f.t.phoneHelp}` }),
|
|
1465
|
+
codes && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1466
|
+
/* @__PURE__ */ jsx("p", { className: f.cx("notice"), children: f.t.keepCodes }),
|
|
1467
|
+
/* @__PURE__ */ jsx("ul", { className: f.cx("codes"), children: codes.map((c) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("code", { children: c }) }, c)) })
|
|
1468
|
+
] }),
|
|
1469
|
+
general && /* @__PURE__ */ jsx("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
|
|
1470
|
+
!ways.phone && /* @__PURE__ */ jsx(Field, { f, name: "phone", type: "tel", label: f.t.phone, value: number, autoComplete: "tel", required: true, onChange: setNumber }),
|
|
1471
|
+
!ways.phone && ways.phoneChannels.length > 1 && /* @__PURE__ */ jsx("div", { className: f.cx("field"), role: "radiogroup", children: ways.phoneChannels.map((c) => /* @__PURE__ */ jsxs("label", { className: f.cx("label"), children: [
|
|
1472
|
+
/* @__PURE__ */ jsx("input", { type: "radio", name: "channel", value: c, checked: channel === c, onChange: () => setChannel(c) }),
|
|
1473
|
+
" ",
|
|
1474
|
+
c === "whatsapp" ? f.t.byWhatsapp : f.t.bySms
|
|
1475
|
+
] }, c)) }),
|
|
1476
|
+
passwordField,
|
|
1477
|
+
/* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: ways.phone ? f.t.removePhone : f.t.addPhone })
|
|
1478
|
+
] });
|
|
1479
|
+
}
|
|
1276
1480
|
function SignOutEverywhere({ props }) {
|
|
1277
1481
|
const auth = useAuth();
|
|
1278
1482
|
const { f, run, general } = useSection(props);
|