cauthy-auth 0.12.0 → 0.14.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/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
- magicLink: (f) => post("/sign-in/magic-link", { email: f.email }, checkEmail(f.email)),
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() {
@@ -381,6 +390,11 @@ function createAuthClient(options = {}) {
381
390
  return body && Array.isArray(body.sessions) ? body.sessions : [];
382
391
  },
383
392
  signOutEverywhere: () => post("/me/sessions/end", {}, null),
393
+ async agents() {
394
+ const body = await read("/me/agents");
395
+ return body && Array.isArray(body.agents) ? body.agents : [];
396
+ },
397
+ disconnectAgent: (id) => post("/me/agents/disconnect", { id }, null),
384
398
  async passkeys() {
385
399
  const body = await read("/me/passkeys");
386
400
  return body && Array.isArray(body.passkeys) ? body.passkeys : [];
@@ -482,6 +496,18 @@ var TEXTS = {
482
496
  linkSent: "If that address has an account, a link to choose a new password is on its way.",
483
497
  codeHelp: "Enter the code from your authenticator app, or a recovery code.",
484
498
  verify: "Verify",
499
+ codeHelpPhone: "We send a code to your phone. Ask for it, then type it here, or use a recovery code.",
500
+ sendCodeSms: "Send a code by SMS to {to}",
501
+ sendCodeWhatsapp: "Send a code by WhatsApp to {to}",
502
+ codeSent: "We sent a code to {to}. Type it here.",
503
+ phone: "Phone, with its country code",
504
+ phoneHelp: "Get your codes by SMS or WhatsApp instead of an app.",
505
+ phoneOn: "Codes go to {to}.",
506
+ phoneOff: "No phone yet.",
507
+ addPhone: "Add phone",
508
+ removePhone: "Remove phone",
509
+ bySms: "SMS",
510
+ byWhatsapp: "WhatsApp",
485
511
  checkEmail: "Almost there: open the link we sent to confirm your address, then sign in.",
486
512
  backToSignIn: "Back to sign in",
487
513
  emailMeALink: "Email me a sign-in link",
@@ -520,6 +546,10 @@ var TEXTS = {
520
546
  passkeyUnnamed: "Passkey",
521
547
  sessions: "Where you are signed in",
522
548
  signOutEverywhere: "Sign out everywhere",
549
+ agents: "AI agents",
550
+ agentsHelp: "Agents you allowed to act for you. Disconnect one and it loses its access within minutes.",
551
+ disconnect: "Disconnect",
552
+ anAgent: "An AI agent",
523
553
  unknownDevice: "Unknown device",
524
554
  emailAddress: "Email address",
525
555
  newEmail: "New email address",
@@ -549,6 +579,13 @@ var TEXTS = {
549
579
  step_up_required: "Enter your current password.",
550
580
  weak_password: "Choose a stronger password.",
551
581
  unauthenticated: "Sign in first.",
582
+ phone_refused: "The code could not be sent to that number.",
583
+ too_many_codes: "Enough codes were sent. Use the last one, or sign in again.",
584
+ phone_unavailable: "Codes cannot be sent that way here.",
585
+ phone_already_set: "Remove the phone you have first.",
586
+ phone_codes_unavailable: "Codes cannot be sent right now. Try again in a moment.",
587
+ captcha_required: "Complete the check that you are not a bot, then try again.",
588
+ captcha_failed: "The check that you are not a bot did not pass. Try it again.",
552
589
  passkey_failed: "The passkey could not be used. Try again, or sign in another way.",
553
590
  passkey_exists: "This device already has a passkey for your account.",
554
591
  passkey_unavailable: "Passkeys cannot be used here."
@@ -571,6 +608,18 @@ var TEXTS = {
571
608
  linkSent: "Si ese correo tiene una cuenta, ya va en camino un enlace para elegir una contrase\xF1a nueva.",
572
609
  codeHelp: "Escribe el c\xF3digo de tu aplicaci\xF3n de autenticaci\xF3n, o un c\xF3digo de recuperaci\xF3n.",
573
610
  verify: "Verificar",
611
+ 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.",
612
+ sendCodeSms: "Enviar un c\xF3digo por SMS a {to}",
613
+ sendCodeWhatsapp: "Enviar un c\xF3digo por WhatsApp a {to}",
614
+ codeSent: "Te enviamos un c\xF3digo a {to}. Escr\xEDbelo aqu\xED.",
615
+ phone: "Tel\xE9fono, con su c\xF3digo de pa\xEDs",
616
+ phoneHelp: "Recibe tus c\xF3digos por SMS o WhatsApp en vez de una app.",
617
+ phoneOn: "Los c\xF3digos van a {to}.",
618
+ phoneOff: "Todav\xEDa no tienes un tel\xE9fono.",
619
+ addPhone: "Agregar tel\xE9fono",
620
+ removePhone: "Quitar tel\xE9fono",
621
+ bySms: "SMS",
622
+ byWhatsapp: "WhatsApp",
574
623
  checkEmail: "Ya casi: abre el enlace que te enviamos para confirmar tu correo y luego entra.",
575
624
  backToSignIn: "Volver a entrar",
576
625
  emailMeALink: "Env\xEDame un enlace para entrar",
@@ -609,6 +658,10 @@ var TEXTS = {
609
658
  passkeyUnnamed: "Llave de acceso",
610
659
  sessions: "D\xF3nde tienes la sesi\xF3n abierta",
611
660
  signOutEverywhere: "Cerrar sesi\xF3n en todas partes",
661
+ agents: "Agentes de IA",
662
+ agentsHelp: "Los agentes a los que permitiste actuar por ti. Si desconectas uno, pierde el acceso en pocos minutos.",
663
+ disconnect: "Desconectar",
664
+ anAgent: "Un agente de IA",
612
665
  unknownDevice: "Dispositivo desconocido",
613
666
  emailAddress: "Correo",
614
667
  newEmail: "Correo nuevo",
@@ -638,6 +691,13 @@ var TEXTS = {
638
691
  step_up_required: "Escribe tu contrase\xF1a actual.",
639
692
  weak_password: "Elige una contrase\xF1a m\xE1s fuerte.",
640
693
  unauthenticated: "Inicia sesi\xF3n primero.",
694
+ phone_refused: "No se pudo enviar el c\xF3digo a ese n\xFAmero.",
695
+ too_many_codes: "Ya se enviaron suficientes c\xF3digos. Usa el \xFAltimo o vuelve a entrar.",
696
+ phone_unavailable: "Aqu\xED no se pueden enviar c\xF3digos de esa forma.",
697
+ phone_already_set: "Primero quita el tel\xE9fono que tienes.",
698
+ phone_codes_unavailable: "Ahora no se pueden enviar c\xF3digos. Int\xE9ntalo en un momento.",
699
+ captcha_required: "Completa la verificaci\xF3n de que no eres un robot y vuelve a intentarlo.",
700
+ captcha_failed: "La verificaci\xF3n de que no eres un robot no pas\xF3. Int\xE9ntalo de nuevo.",
641
701
  passkey_failed: "No se pudo usar la llave de acceso. Intenta de nuevo o entra de otra forma.",
642
702
  passkey_exists: "Este dispositivo ya tiene una llave de acceso de tu cuenta.",
643
703
  passkey_unavailable: "Aqu\xED no se pueden usar llaves de acceso."
@@ -682,6 +742,63 @@ function useRoot(props) {
682
742
  if (props.unstyled) return {};
683
743
  return { "data-cauthy": "", style: vars };
684
744
  }
745
+ var turnstileScript = null;
746
+ function loadTurnstile() {
747
+ const ready = () => window.turnstile;
748
+ if (ready()) return Promise.resolve(ready());
749
+ turnstileScript ??= new Promise((resolve, reject) => {
750
+ const script = document.createElement("script");
751
+ script.src = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
752
+ script.async = true;
753
+ script.onload = () => ready() ? resolve(ready()) : reject(new Error("turnstile did not load"));
754
+ script.onerror = () => {
755
+ turnstileScript = null;
756
+ reject(new Error("turnstile did not load"));
757
+ };
758
+ document.head.appendChild(script);
759
+ });
760
+ return turnstileScript;
761
+ }
762
+ function useChallenge(siteKey, locale) {
763
+ const box = useRef(null);
764
+ const widget = useRef(null);
765
+ const [token, setToken] = useState("");
766
+ useEffect(() => {
767
+ if (!siteKey || !box.current) return;
768
+ let gone = false;
769
+ void loadTurnstile().then((api) => {
770
+ if (gone || !box.current) return;
771
+ const id = api.render(box.current, {
772
+ sitekey: siteKey,
773
+ language: locale ?? "auto",
774
+ size: "flexible",
775
+ callback: (t) => setToken(t),
776
+ "expired-callback": () => setToken(""),
777
+ "error-callback": () => setToken("")
778
+ });
779
+ widget.current = { api, id };
780
+ }).catch(() => {
781
+ });
782
+ return () => {
783
+ gone = true;
784
+ if (widget.current) widget.current.api.remove(widget.current.id);
785
+ widget.current = null;
786
+ };
787
+ }, [siteKey, locale]);
788
+ const renew = () => {
789
+ setToken("");
790
+ if (widget.current) widget.current.api.reset(widget.current.id);
791
+ };
792
+ const node = siteKey ? /* @__PURE__ */ jsx("div", { ref: box, className: "cauthy-challenge" }) : null;
793
+ return { node, token: token || void 0, renew };
794
+ }
795
+ function useMethods(auth) {
796
+ const [methods, setMethods] = useState(null);
797
+ useEffect(() => {
798
+ void auth.methods().then(setMethods);
799
+ }, []);
800
+ return methods;
801
+ }
685
802
  function useForm(props) {
686
803
  const t = { ...TEXTS[props.locale ?? "en"], ...props.texts, errors: { ...TEXTS[props.locale ?? "en"].errors, ...props.texts?.errors } };
687
804
  const cx = (slot) => props.classNames?.[slot] ?? `cauthy-${slot}`;
@@ -754,6 +871,23 @@ function SignIn(props) {
754
871
  const [password, setPassword] = useState("");
755
872
  const [code, setCode] = useState("");
756
873
  const [step, setStep] = useState("password");
874
+ const challenge = useChallenge(methods?.captchaSiteKey, props.locale);
875
+ const [phoneWay, setPhoneWay] = useState(null);
876
+ const [phoneSent, setPhoneSent] = useState("");
877
+ useEffect(() => {
878
+ if (step !== "code") return;
879
+ void auth.signIn.mfaMethods().then((r) => {
880
+ if (r.ok) setPhoneWay({ totp: r.totp, phone: r.phone });
881
+ });
882
+ }, [step]);
883
+ const sendCode = async () => {
884
+ f.setBusy(true);
885
+ f.setError(null);
886
+ const r = await auth.signIn.mfaSendCode();
887
+ f.setBusy(false);
888
+ if (!r.ok) return f.setError(r.error);
889
+ setPhoneSent(r.sentTo);
890
+ };
757
891
  const autofill = useRef(null);
758
892
  const [offer, setOffer] = useState(0);
759
893
  useEffect(() => {
@@ -788,8 +922,9 @@ function SignIn(props) {
788
922
  autofill.current?.abort();
789
923
  f.setBusy(true);
790
924
  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 });
925
+ 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
926
  f.setBusy(false);
927
+ if (step === "password") challenge.renew();
793
928
  if (!r.ok) {
794
929
  setOffer((n) => n + 1);
795
930
  return f.setError(r.error);
@@ -812,8 +947,9 @@ function SignIn(props) {
812
947
  autofill.current?.abort();
813
948
  f.setBusy(true);
814
949
  f.setError(null);
815
- const r = await auth.signIn.magicLink({ email });
950
+ const r = await auth.signIn.magicLink({ email, captchaToken: challenge.token });
816
951
  f.setBusy(false);
952
+ challenge.renew();
817
953
  if (!r.ok) return f.setError(r.error);
818
954
  setStep("sent");
819
955
  };
@@ -829,9 +965,11 @@ function SignIn(props) {
829
965
  ] });
830
966
  }
831
967
  if (step === "code") {
968
+ const to = phoneWay?.phone?.number ?? "";
832
969
  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 }),
970
+ /* @__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
971
  general && /* @__PURE__ */ jsx("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
972
+ 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
973
  /* @__PURE__ */ jsx(Field, { f, name: "code", type: "text", label: f.t.code, value: code, autoComplete: "one-time-code", required: true, onChange: setCode }),
836
974
  /* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.verify })
837
975
  ] });
@@ -860,6 +998,7 @@ function SignIn(props) {
860
998
  aside: props.links?.forgotPassword ? /* @__PURE__ */ jsx("a", { className: f.cx("link"), href: props.links.forgotPassword, children: f.t.forgotPassword }) : void 0
861
999
  }
862
1000
  ),
1001
+ challenge.node,
863
1002
  /* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.signIn }),
864
1003
  methods?.magicLink && /* @__PURE__ */ jsx(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => void sendLink(), children: f.t.emailMeALink }),
865
1004
  methods?.signUp !== false && props.links?.signUp && /* @__PURE__ */ jsxs("p", { className: f.cx("footer"), children: [
@@ -891,12 +1030,14 @@ function SignUp(props) {
891
1030
  const [email, setEmail] = useState("");
892
1031
  const [password, setPassword] = useState("");
893
1032
  const [sent, setSent] = useState(false);
1033
+ const challenge = useChallenge(useMethods(auth)?.captchaSiteKey, props.locale);
894
1034
  const submit = async (e) => {
895
1035
  e.preventDefault();
896
1036
  f.setBusy(true);
897
1037
  f.setError(null);
898
- const r = await auth.signUp.email({ name, email, password });
1038
+ const r = await auth.signUp.email({ name, email, password, captchaToken: challenge.token });
899
1039
  f.setBusy(false);
1040
+ challenge.renew();
900
1041
  if (!r.ok) return f.setError(r.error);
901
1042
  if (r.status === "verification_required") return setSent(true);
902
1043
  f.done();
@@ -913,6 +1054,7 @@ function SignUp(props) {
913
1054
  /* @__PURE__ */ jsx(Field, { f, name: "name", type: "text", label: f.t.name, value: name, autoComplete: "name", onChange: setName }),
914
1055
  /* @__PURE__ */ jsx(Field, { f, name: "email", type: "email", label: f.t.email, value: email, autoComplete: "username", required: true, onChange: setEmail }),
915
1056
  /* @__PURE__ */ jsx(Field, { f, name: "password", type: "password", label: f.t.password, value: password, autoComplete: "new-password", required: true, onChange: setPassword }),
1057
+ challenge.node,
916
1058
  /* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.signUp }),
917
1059
  props.links?.signIn && /* @__PURE__ */ jsxs("p", { className: f.cx("footer"), children: [
918
1060
  f.t.haveAccount,
@@ -926,12 +1068,14 @@ function ForgotPassword(props) {
926
1068
  const f = useForm(props);
927
1069
  const [email, setEmail] = useState("");
928
1070
  const [sent, setSent] = useState(false);
1071
+ const challenge = useChallenge(useMethods(auth)?.captchaSiteKey, props.locale);
929
1072
  const submit = async (e) => {
930
1073
  e.preventDefault();
931
1074
  f.setBusy(true);
932
1075
  f.setError(null);
933
- const r = await auth.forgotPassword({ email });
1076
+ const r = await auth.forgotPassword({ email, captchaToken: challenge.token });
934
1077
  f.setBusy(false);
1078
+ challenge.renew();
935
1079
  if (!r.ok) return f.setError(r.error);
936
1080
  setSent(true);
937
1081
  };
@@ -945,6 +1089,7 @@ function ForgotPassword(props) {
945
1089
  return /* @__PURE__ */ jsxs("form", { className: f.cx("form"), ...f.root, onSubmit: submit, noValidate: true, children: [
946
1090
  general && /* @__PURE__ */ jsx("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
947
1091
  /* @__PURE__ */ jsx(Field, { f, name: "email", type: "email", label: f.t.email, value: email, autoComplete: "username", required: true, onChange: setEmail }),
1092
+ challenge.node,
948
1093
  /* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.sendLink }),
949
1094
  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
1095
  ] });
@@ -1044,7 +1189,9 @@ function Account(props) {
1044
1189
  account.hasPassword && /* @__PURE__ */ jsx(PasswordPart, { props }),
1045
1190
  /* @__PURE__ */ jsx(LinkedPart, { props }),
1046
1191
  /* @__PURE__ */ jsx(SecondStepPart, { props, account, onChanged: (mfaEnabled) => setAccount({ ...account, mfaEnabled }) }),
1192
+ /* @__PURE__ */ jsx(PhonePart, { props, account, onChanged: (mfaEnabled) => setAccount({ ...account, mfaEnabled }) }),
1047
1193
  /* @__PURE__ */ jsx(PasskeysPart, { props, account, accountUrl: accountUrl(), when }),
1194
+ /* @__PURE__ */ jsx(AgentsPart, { props, when }),
1048
1195
  /* @__PURE__ */ jsxs("section", { className: cx("section"), children: [
1049
1196
  /* @__PURE__ */ jsx("h2", { className: cx("heading"), children: t.sessions }),
1050
1197
  /* @__PURE__ */ jsx("ul", { className: cx("sessions"), children: sessions.map((s, i) => /* @__PURE__ */ jsxs("li", { className: cx("session"), children: [
@@ -1273,6 +1420,106 @@ function SecondStepPart({ props, account, onChanged }) {
1273
1420
  account.mfaEnabled && /* @__PURE__ */ jsx(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => void renew(), children: f.t.newRecoveryCodes })
1274
1421
  ] });
1275
1422
  }
1423
+ function PhonePart({ props, account, onChanged }) {
1424
+ const auth = useAuth();
1425
+ const { f, run, general } = useSection(props);
1426
+ const [ways, setWays] = useState(null);
1427
+ const [number, setNumber] = useState("");
1428
+ const [channel, setChannel] = useState("sms");
1429
+ const [password, setPassword] = useState("");
1430
+ const [code, setCode] = useState("");
1431
+ const [sentTo, setSentTo] = useState("");
1432
+ const [codes, setCodes] = useState(null);
1433
+ const load = () => auth.account.mfa.factors().then((w) => {
1434
+ setWays(w);
1435
+ if (w && w.phoneChannels.length > 0 && !w.phoneChannels.includes(channel)) setChannel(w.phoneChannels[0]);
1436
+ });
1437
+ useEffect(() => {
1438
+ void load();
1439
+ }, []);
1440
+ if (!ways || ways.phoneChannels.length === 0) return null;
1441
+ const add = async (e) => {
1442
+ e.preventDefault();
1443
+ const r = await run(() => auth.account.mfa.addPhone({ phone: number, channel, password }));
1444
+ if (r.ok) setSentTo(r.sentTo);
1445
+ };
1446
+ const confirm = async (e) => {
1447
+ e.preventDefault();
1448
+ const r = await run(() => auth.account.mfa.verifyPhone({ code }));
1449
+ if (!r.ok) return;
1450
+ setSentTo("");
1451
+ setCode("");
1452
+ setPassword("");
1453
+ if (r.recoveryCodes.length > 0) setCodes(r.recoveryCodes);
1454
+ onChanged(true);
1455
+ await load();
1456
+ };
1457
+ const remove = async (e) => {
1458
+ e.preventDefault();
1459
+ const r = await run(() => auth.account.mfa.removePhone({ password }));
1460
+ if (!r.ok) return;
1461
+ setPassword("");
1462
+ await load();
1463
+ const left = await auth.account.mfa.factors();
1464
+ onChanged(Boolean(left?.totp));
1465
+ };
1466
+ 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 });
1467
+ if (sentTo) {
1468
+ return /* @__PURE__ */ jsxs("form", { className: f.cx("section"), onSubmit: confirm, noValidate: true, children: [
1469
+ /* @__PURE__ */ jsx("h2", { className: f.cx("heading"), children: f.t.phone }),
1470
+ /* @__PURE__ */ jsx("p", { className: f.cx("notice"), children: f.t.codeSent.replace("{to}", sentTo) }),
1471
+ general && /* @__PURE__ */ jsx("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
1472
+ /* @__PURE__ */ jsx(Field, { f, name: "code", type: "text", label: f.t.code, value: code, autoComplete: "one-time-code", required: true, onChange: setCode }),
1473
+ /* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.verify })
1474
+ ] });
1475
+ }
1476
+ return /* @__PURE__ */ jsxs("form", { className: f.cx("section"), onSubmit: ways.phone ? remove : add, noValidate: true, children: [
1477
+ /* @__PURE__ */ jsx("h2", { className: f.cx("heading"), children: f.t.phone }),
1478
+ /* @__PURE__ */ jsx("p", { className: f.cx("notice"), children: ways.phone ? f.t.phoneOn.replace("{to}", ways.phone.number) : `${f.t.phoneOff} ${f.t.phoneHelp}` }),
1479
+ codes && /* @__PURE__ */ jsxs(Fragment, { children: [
1480
+ /* @__PURE__ */ jsx("p", { className: f.cx("notice"), children: f.t.keepCodes }),
1481
+ /* @__PURE__ */ jsx("ul", { className: f.cx("codes"), children: codes.map((c) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("code", { children: c }) }, c)) })
1482
+ ] }),
1483
+ general && /* @__PURE__ */ jsx("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
1484
+ !ways.phone && /* @__PURE__ */ jsx(Field, { f, name: "phone", type: "tel", label: f.t.phone, value: number, autoComplete: "tel", required: true, onChange: setNumber }),
1485
+ !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: [
1486
+ /* @__PURE__ */ jsx("input", { type: "radio", name: "channel", value: c, checked: channel === c, onChange: () => setChannel(c) }),
1487
+ " ",
1488
+ c === "whatsapp" ? f.t.byWhatsapp : f.t.bySms
1489
+ ] }, c)) }),
1490
+ passwordField,
1491
+ /* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: ways.phone ? f.t.removePhone : f.t.addPhone })
1492
+ ] });
1493
+ }
1494
+ function AgentsPart({ props, when }) {
1495
+ const auth = useAuth();
1496
+ const { f, run, general } = useSection(props);
1497
+ const [list, setList] = useState([]);
1498
+ useEffect(() => {
1499
+ void auth.account.agents().then(setList);
1500
+ }, []);
1501
+ if (list.length === 0) return null;
1502
+ const disconnect = async (id) => {
1503
+ const r = await run(() => auth.account.disconnectAgent(id));
1504
+ if (r.ok) setList(list.filter((a) => a.id !== id));
1505
+ };
1506
+ return /* @__PURE__ */ jsxs("section", { className: f.cx("section"), children: [
1507
+ /* @__PURE__ */ jsx("h2", { className: f.cx("heading"), children: f.t.agents }),
1508
+ /* @__PURE__ */ jsx("p", { className: f.cx("notice"), children: f.t.agentsHelp }),
1509
+ general && /* @__PURE__ */ jsx("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
1510
+ /* @__PURE__ */ jsx("ul", { className: f.cx("sessions"), children: list.map((a) => /* @__PURE__ */ jsxs("li", { className: f.cx("session"), children: [
1511
+ a.name || f.t.anAgent,
1512
+ a.redirectHost ? ` (${a.redirectHost})` : "",
1513
+ " \u2192 ",
1514
+ a.resource.name,
1515
+ a.scopes.length > 0 ? ` \xB7 ${a.scopes.map((s) => s.description || s.name).join(", ")}` : "",
1516
+ " \xB7 ",
1517
+ when(a.lastUsedAt),
1518
+ " ",
1519
+ /* @__PURE__ */ jsx(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => void disconnect(a.id), children: f.t.disconnect })
1520
+ ] }, a.id)) })
1521
+ ] });
1522
+ }
1276
1523
  function SignOutEverywhere({ props }) {
1277
1524
  const auth = useAuth();
1278
1525
  const { f, run, general } = useSection(props);