cauthy-auth 0.10.0 → 0.12.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.js CHANGED
@@ -24,11 +24,14 @@ __export(react_exports, {
24
24
  Account: () => Account,
25
25
  CauthyProvider: () => CauthyProvider,
26
26
  ForgotPassword: () => ForgotPassword,
27
+ GoogleOneTap: () => GoogleOneTap,
27
28
  ResetPassword: () => ResetPassword,
28
29
  SignIn: () => SignIn,
29
30
  SignUp: () => SignUp,
30
31
  VerifyEmail: () => VerifyEmail,
31
32
  can: () => can,
33
+ canAutofillPasskeys: () => canAutofillPasskeys,
34
+ canUsePasskeys: () => canUsePasskeys,
32
35
  createAuthClient: () => createAuthClient,
33
36
  definePolicy: () => definePolicy,
34
37
  useAuth: () => useAuth,
@@ -37,6 +40,82 @@ __export(react_exports, {
37
40
  module.exports = __toCommonJS(react_exports);
38
41
  var import_react3 = require("react");
39
42
 
43
+ // src/passkey.ts
44
+ function canUsePasskeys() {
45
+ return typeof window !== "undefined" && typeof window.PublicKeyCredential === "function" && typeof navigator !== "undefined" && typeof navigator.credentials?.get === "function";
46
+ }
47
+ async function canAutofillPasskeys() {
48
+ if (!canUsePasskeys()) return false;
49
+ const pkc = window.PublicKeyCredential;
50
+ try {
51
+ return typeof pkc.isConditionalMediationAvailable === "function" && await pkc.isConditionalMediationAvailable() === true;
52
+ } catch {
53
+ return false;
54
+ }
55
+ }
56
+ function bytes(text2) {
57
+ const b64 = text2.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(text2.length / 4) * 4, "=");
58
+ const bin = atob(b64);
59
+ const out = new Uint8Array(bin.length);
60
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
61
+ return out.buffer;
62
+ }
63
+ function text(buf) {
64
+ if (!buf) return null;
65
+ const view = new Uint8Array(buf);
66
+ let bin = "";
67
+ for (let i = 0; i < view.byteLength; i++) bin += String.fromCharCode(view[i]);
68
+ return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
69
+ }
70
+ var withId = (list) => (list ?? []).map((c) => ({ ...c, id: bytes(c.id) }));
71
+ async function getPasskey(options, how = {}) {
72
+ const publicKey = { ...options, challenge: bytes(options.challenge), allowCredentials: withId(options.allowCredentials) };
73
+ const cred = await navigator.credentials.get({ publicKey, ...how });
74
+ if (cred === null) throw new DOMException("No passkey was chosen.", "NotAllowedError");
75
+ const r = cred.response;
76
+ return {
77
+ id: cred.id,
78
+ rawId: text(cred.rawId),
79
+ type: cred.type,
80
+ response: {
81
+ authenticatorData: text(r.authenticatorData),
82
+ clientDataJSON: text(r.clientDataJSON),
83
+ signature: text(r.signature),
84
+ userHandle: text(r.userHandle)
85
+ },
86
+ clientExtensionResults: cred.getClientExtensionResults?.() ?? {}
87
+ };
88
+ }
89
+ async function makePasskey(options) {
90
+ const publicKey = {
91
+ ...options,
92
+ challenge: bytes(options.challenge),
93
+ user: { ...options.user, id: bytes(options.user.id) },
94
+ excludeCredentials: withId(options.excludeCredentials)
95
+ };
96
+ const cred = await navigator.credentials.create({ publicKey });
97
+ if (cred === null) throw new DOMException("No passkey was made.", "NotAllowedError");
98
+ const r = cred.response;
99
+ return {
100
+ id: cred.id,
101
+ rawId: text(cred.rawId),
102
+ type: cred.type,
103
+ response: {
104
+ clientDataJSON: text(r.clientDataJSON),
105
+ attestationObject: text(r.attestationObject),
106
+ transports: typeof r.getTransports === "function" ? r.getTransports() : []
107
+ },
108
+ clientExtensionResults: cred.getClientExtensionResults?.() ?? {}
109
+ };
110
+ }
111
+ function ceremonyError(e) {
112
+ const name = e instanceof Error || e instanceof DOMException ? e.name : "";
113
+ if (name === "NotAllowedError" || name === "AbortError") return { code: "cancelled", message: "The passkey was not used. Try again when you are ready." };
114
+ if (name === "InvalidStateError") return { code: "passkey_exists", message: "This device already has a passkey for this account." };
115
+ if (name === "SecurityError") return { code: "passkey_unavailable", message: "Passkeys cannot be used on this page." };
116
+ return { code: "passkey_failed", message: "The passkey could not be used. Try again, or sign in another way." };
117
+ }
118
+
40
119
  // src/theme.ts
41
120
  function themeVariables(theme) {
42
121
  const vars = {};
@@ -135,6 +214,24 @@ function firstProblem(...checks) {
135
214
  }
136
215
 
137
216
  // src/client.ts
217
+ var googleScript = null;
218
+ function loadGoogleIdentity() {
219
+ const ready = () => window.google?.accounts?.id;
220
+ if (ready()) return Promise.resolve(ready());
221
+ googleScript ??= new Promise((resolve, reject) => {
222
+ const script = document.createElement("script");
223
+ script.src = "https://accounts.google.com/gsi/client";
224
+ script.async = true;
225
+ script.onload = () => ready() ? resolve(ready()) : reject(new Error("google identity services did not load"));
226
+ script.onerror = () => {
227
+ googleScript = null;
228
+ reject(new Error("google identity services did not load"));
229
+ };
230
+ document.head.appendChild(script);
231
+ });
232
+ return googleScript;
233
+ }
234
+ var PASSKEY_SECONDS = 300;
138
235
  var UNAVAILABLE = { code: "unavailable", message: "Sign-in is unavailable right now. Try again in a moment." };
139
236
  function createAuthClient(options = {}) {
140
237
  const base = (options.basePath ?? "/api/auth").replace(/\/+$/, "");
@@ -177,6 +274,28 @@ function createAuthClient(options = {}) {
177
274
  if (o.locale) q.set("locale", o.locale);
178
275
  return `${base}/login?${q}`;
179
276
  };
277
+ async function methods() {
278
+ try {
279
+ const res = await doFetch(`${base}/methods`, { credentials: "same-origin", headers: { accept: "application/json" } });
280
+ if (res.ok) {
281
+ const m = await res.json();
282
+ return { providers: [], signUp: true, magicLink: false, passkey: false, ...m, passkeyHere: m.passkeyHere === true, passkeyOnCauthy: m.passkeyOnCauthy ?? m.passkey === true };
283
+ }
284
+ } catch {
285
+ }
286
+ return { providers: [], signUp: true, magicLink: false, passkey: false, passkeyHere: false, passkeyOnCauthy: false };
287
+ }
288
+ async function ceremony(begin, finish, form, run, more = {}) {
289
+ const begun = await post(begin, form, null);
290
+ if (!begun.ok) return begun;
291
+ let credential;
292
+ try {
293
+ credential = await run(begun.options);
294
+ } catch (e) {
295
+ return { ok: false, error: ceremonyError(e) };
296
+ }
297
+ return post(finish, { credential, ...more }, null);
298
+ }
180
299
  return {
181
300
  signIn: {
182
301
  email: (f) => post("/sign-in/email", { email: f.email, password: f.password }, firstProblem(checkEmail(f.email), checkPassword(f.password))),
@@ -185,8 +304,76 @@ function createAuthClient(options = {}) {
185
304
  magicLinkVerify: (f) => post("/sign-in/magic-link/verify", { token: f.token }, null),
186
305
  emailCode: (f) => post("/sign-in/email-code", { code: f.code }, checkCode(f.code)),
187
306
  passkeyUrl,
188
- passkey(o) {
307
+ async passkey(o) {
308
+ const m = await methods();
309
+ if (m.passkeyHere && canUsePasskeys()) {
310
+ const r = await ceremony("/sign-in/passkey/begin", "/sign-in/passkey/finish", {}, getPasskey);
311
+ if (r.ok || r.error.code !== "passkey_unavailable" || !m.passkeyOnCauthy) return r;
312
+ } else if (!m.passkeyOnCauthy) {
313
+ return { ok: false, error: { code: "passkey_unavailable", message: "Passkeys cannot be used here." } };
314
+ }
189
315
  window.location.assign(passkeyUrl(o));
316
+ return { ok: true, status: "redirected" };
317
+ },
318
+ async passkeyAutofill(o = {}) {
319
+ const cancelled = { ok: false, error: { code: "cancelled", message: "No passkey was chosen." } };
320
+ const m = await methods();
321
+ if (!m.passkeyHere || !await canAutofillPasskeys()) return cancelled;
322
+ for (; ; ) {
323
+ if (o.signal?.aborted) return cancelled;
324
+ const begun = await post("/sign-in/passkey/begin", {}, null);
325
+ if (!begun.ok) return begun;
326
+ const renew = new AbortController();
327
+ const stop = () => renew.abort();
328
+ o.signal?.addEventListener("abort", stop, { once: true });
329
+ const timer = setTimeout(stop, (PASSKEY_SECONDS - 30) * 1e3);
330
+ let credential;
331
+ try {
332
+ credential = await getPasskey(begun.options, { mediation: "conditional", signal: renew.signal });
333
+ } catch (e) {
334
+ if (o.signal?.aborted) return cancelled;
335
+ if (renew.signal.aborted) continue;
336
+ return { ok: false, error: ceremonyError(e) };
337
+ } finally {
338
+ clearTimeout(timer);
339
+ o.signal?.removeEventListener("abort", stop);
340
+ }
341
+ return post("/sign-in/passkey/finish", { credential }, null);
342
+ }
343
+ },
344
+ async googleOneTap(o = {}) {
345
+ const cancelled = { ok: false, error: { code: "cancelled", message: "No Google account was chosen." } };
346
+ if (typeof window === "undefined" || o.signal?.aborted) return cancelled;
347
+ const begun = await post("/sign-in/google-one-tap/begin", {}, null);
348
+ if (!begun.ok) return begun;
349
+ let gis;
350
+ try {
351
+ gis = await loadGoogleIdentity();
352
+ } catch {
353
+ return { ok: false, error: { code: "unavailable", message: "Google could not be reached. Try again, or sign in another way." } };
354
+ }
355
+ const credential = await new Promise((done) => {
356
+ o.signal?.addEventListener("abort", () => {
357
+ gis.cancel();
358
+ done(null);
359
+ }, { once: true });
360
+ gis.initialize({
361
+ client_id: begun.clientId,
362
+ nonce: begun.nonce,
363
+ context: o.context ?? "signin",
364
+ callback: (r) => done(r.credential ?? null),
365
+ auto_select: false,
366
+ cancel_on_tap_outside: true,
367
+ itp_support: true,
368
+ use_fedcm_for_prompt: true
369
+ });
370
+ gis.prompt((moment) => {
371
+ if (moment.isNotDisplayed?.() || moment.isSkippedMoment?.()) done(null);
372
+ else if (moment.isDismissedMoment?.() && moment.getDismissedReason?.() !== "credential_returned") done(null);
373
+ });
374
+ });
375
+ if (credential === null) return cancelled;
376
+ return post("/sign-in/google-one-tap", { credential }, null);
190
377
  },
191
378
  socialUrl,
192
379
  social(provider, o) {
@@ -230,7 +417,16 @@ function createAuthClient(options = {}) {
230
417
  const body = await read("/me/sessions");
231
418
  return body && Array.isArray(body.sessions) ? body.sessions : [];
232
419
  },
233
- signOutEverywhere: () => post("/me/sessions/end", {}, null)
420
+ signOutEverywhere: () => post("/me/sessions/end", {}, null),
421
+ async passkeys() {
422
+ const body = await read("/me/passkeys");
423
+ return body && Array.isArray(body.passkeys) ? body.passkeys : [];
424
+ },
425
+ addPasskey(f = {}) {
426
+ if (!canUsePasskeys()) return Promise.resolve({ ok: false, error: { code: "passkey_unavailable", message: "This browser cannot make passkeys." } });
427
+ return ceremony("/me/passkeys/add/begin", "/me/passkeys/add/finish", { password: f.password ?? "" }, makePasskey, { name: f.name ?? "" });
428
+ },
429
+ removePasskey: (id) => post("/me/passkeys/remove", { id }, null)
234
430
  },
235
431
  async getSession() {
236
432
  try {
@@ -245,14 +441,7 @@ function createAuthClient(options = {}) {
245
441
  looked ??= read("/look").then((wire) => wire && typeof wire === "object" ? fromWire(wire) : null);
246
442
  return looked;
247
443
  },
248
- async methods() {
249
- try {
250
- const res = await doFetch(`${base}/methods`, { credentials: "same-origin", headers: { accept: "application/json" } });
251
- if (res.ok) return await res.json();
252
- } catch {
253
- }
254
- return { providers: [], signUp: true, magicLink: false, passkey: false };
255
- }
444
+ methods
256
445
  };
257
446
  }
258
447
 
@@ -359,6 +548,13 @@ var TEXTS = {
359
548
  keepCodes: "Keep these codes somewhere safe. Each works once, and they are not shown again.",
360
549
  passkeys: "Passkeys",
361
550
  managePasskeys: "Add or remove a passkey",
551
+ passkeysHelp: "Sign in with your fingerprint, your face or your device's lock, without a password.",
552
+ noPasskeys: "No passkey yet.",
553
+ passkeyName: "A name for this device (optional)",
554
+ addPasskey: "Add a passkey on this device",
555
+ removePasskey: "Remove",
556
+ passkeyAdded: "The passkey was added. Next time, sign in with it.",
557
+ passkeyUnnamed: "Passkey",
362
558
  sessions: "Where you are signed in",
363
559
  signOutEverywhere: "Sign out everywhere",
364
560
  unknownDevice: "Unknown device",
@@ -389,7 +585,10 @@ var TEXTS = {
389
585
  wrong_password: "The current password is not right.",
390
586
  step_up_required: "Enter your current password.",
391
587
  weak_password: "Choose a stronger password.",
392
- unauthenticated: "Sign in first."
588
+ unauthenticated: "Sign in first.",
589
+ passkey_failed: "The passkey could not be used. Try again, or sign in another way.",
590
+ passkey_exists: "This device already has a passkey for your account.",
591
+ passkey_unavailable: "Passkeys cannot be used here."
393
592
  }
394
593
  },
395
594
  es: {
@@ -438,6 +637,13 @@ var TEXTS = {
438
637
  keepCodes: "Guarda estos c\xF3digos en un lugar seguro. Cada uno sirve una vez y no se vuelven a mostrar.",
439
638
  passkeys: "Llaves de acceso",
440
639
  managePasskeys: "Agregar o quitar una llave de acceso",
640
+ passkeysHelp: "Entra con tu huella, tu rostro o el bloqueo de tu dispositivo, sin contrase\xF1a.",
641
+ noPasskeys: "Todav\xEDa no tienes llaves de acceso.",
642
+ passkeyName: "Un nombre para este dispositivo (opcional)",
643
+ addPasskey: "Agregar una llave en este dispositivo",
644
+ removePasskey: "Quitar",
645
+ passkeyAdded: "La llave de acceso qued\xF3 agregada. La pr\xF3xima vez, entra con ella.",
646
+ passkeyUnnamed: "Llave de acceso",
441
647
  sessions: "D\xF3nde tienes la sesi\xF3n abierta",
442
648
  signOutEverywhere: "Cerrar sesi\xF3n en todas partes",
443
649
  unknownDevice: "Dispositivo desconocido",
@@ -468,7 +674,10 @@ var TEXTS = {
468
674
  wrong_password: "La contrase\xF1a actual no es correcta.",
469
675
  step_up_required: "Escribe tu contrase\xF1a actual.",
470
676
  weak_password: "Elige una contrase\xF1a m\xE1s fuerte.",
471
- unauthenticated: "Inicia sesi\xF3n primero."
677
+ unauthenticated: "Inicia sesi\xF3n primero.",
678
+ passkey_failed: "No se pudo usar la llave de acceso. Intenta de nuevo o entra de otra forma.",
679
+ passkey_exists: "Este dispositivo ya tiene una llave de acceso de tu cuenta.",
680
+ passkey_unavailable: "Aqu\xED no se pueden usar llaves de acceso."
472
681
  }
473
682
  }
474
683
  };
@@ -582,6 +791,8 @@ function SignIn(props) {
582
791
  const [password, setPassword] = (0, import_react.useState)("");
583
792
  const [code, setCode] = (0, import_react.useState)("");
584
793
  const [step, setStep] = (0, import_react.useState)("password");
794
+ const autofill = (0, import_react.useRef)(null);
795
+ const [offer, setOffer] = (0, import_react.useState)(0);
585
796
  (0, import_react.useEffect)(() => {
586
797
  void auth.methods().then(setMethods);
587
798
  const back = returnedError();
@@ -596,17 +807,46 @@ function SignIn(props) {
596
807
  setStep("password");
597
808
  });
598
809
  });
810
+ (0, import_react.useEffect)(() => {
811
+ if (step !== "password" || !methods?.passkeyHere) return;
812
+ const ctl = new AbortController();
813
+ autofill.current = ctl;
814
+ void auth.signIn.passkeyAutofill({ signal: ctl.signal }).then((r) => {
815
+ if (ctl.signal.aborted) return;
816
+ if (r.ok) return f.done();
817
+ if (r.error.code === "cancelled") return;
818
+ f.setError(r.error);
819
+ if (r.error.code === "passkey_failed" || r.error.code === "step_expired") setOffer((n) => n + 1);
820
+ });
821
+ return () => ctl.abort();
822
+ }, [step, methods, offer]);
599
823
  const submit = async (e) => {
600
824
  e.preventDefault();
825
+ autofill.current?.abort();
601
826
  f.setBusy(true);
602
827
  f.setError(null);
603
828
  const r = step === "code" ? await auth.signIn.mfa({ code }) : step === "sent" ? await auth.signIn.emailCode({ code }) : await auth.signIn.email({ email, password });
604
829
  f.setBusy(false);
605
- if (!r.ok) return f.setError(r.error);
830
+ if (!r.ok) {
831
+ setOffer((n) => n + 1);
832
+ return f.setError(r.error);
833
+ }
606
834
  if (r.status === "mfa_required") return setStep("code");
607
835
  f.done();
608
836
  };
837
+ const usePasskey = async () => {
838
+ autofill.current?.abort();
839
+ f.setBusy(true);
840
+ f.setError(null);
841
+ const r = await auth.signIn.passkey({ returnTo: props.returnTo, locale: props.locale });
842
+ if (r.ok && r.status === "redirected") return;
843
+ f.setBusy(false);
844
+ if (!r.ok) setOffer((n) => n + 1);
845
+ if (!r.ok) return r.error.code === "cancelled" ? void 0 : f.setError(r.error);
846
+ f.done();
847
+ };
609
848
  const sendLink = async () => {
849
+ autofill.current?.abort();
610
850
  f.setBusy(true);
611
851
  f.setError(null);
612
852
  const r = await auth.signIn.magicLink({ email });
@@ -640,9 +880,9 @@ function SignIn(props) {
640
880
  " ",
641
881
  p.name
642
882
  ] }, p.id)),
643
- methods?.passkey && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "button", className: f.cx("providerButton"), onClick: () => auth.signIn.passkey({ returnTo: props.returnTo, locale: props.locale }), children: f.t.usePasskey }),
883
+ methods?.passkey && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => void usePasskey(), children: f.t.usePasskey }),
644
884
  ((methods?.providers.length ?? 0) > 0 || methods?.passkey) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: f.cx("separator"), children: f.t.orWithEmail }),
645
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "email", type: "email", label: f.t.email, value: email, autoComplete: "username", required: true, onChange: setEmail }),
885
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "email", type: "email", label: f.t.email, value: email, autoComplete: methods?.passkeyHere ? "username webauthn" : "username", required: true, onChange: setEmail }),
646
886
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
647
887
  Field,
648
888
  {
@@ -666,6 +906,21 @@ function SignIn(props) {
666
906
  ] })
667
907
  ] });
668
908
  }
909
+ function GoogleOneTap(props) {
910
+ const auth = useAuth();
911
+ const unauthenticated = auth.status === "unauthenticated";
912
+ (0, import_react.useEffect)(() => {
913
+ if (!unauthenticated) return;
914
+ const ctl = new AbortController();
915
+ void auth.signIn.googleOneTap({ signal: ctl.signal, context: props.context }).then((r) => {
916
+ if (!r.ok || ctl.signal.aborted) return;
917
+ if (props.onSuccess) props.onSuccess();
918
+ else if (props.returnTo) window.location.assign(props.returnTo);
919
+ });
920
+ return () => ctl.abort();
921
+ }, [unauthenticated]);
922
+ return null;
923
+ }
669
924
  function SignUp(props) {
670
925
  const auth = useAuth();
671
926
  const f = useForm(props);
@@ -826,10 +1081,7 @@ function Account(props) {
826
1081
  account.hasPassword && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PasswordPart, { props }),
827
1082
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkedPart, { props }),
828
1083
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SecondStepPart, { props, account, onChanged: (mfaEnabled) => setAccount({ ...account, mfaEnabled }) }),
829
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: cx("section"), children: [
830
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: cx("heading"), children: t.passkeys }),
831
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { className: cx("link"), href: accountUrl(), children: t.managePasskeys })
832
- ] }),
1084
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PasskeysPart, { props, account, accountUrl: accountUrl(), when }),
833
1085
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: cx("section"), children: [
834
1086
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: cx("heading"), children: t.sessions }),
835
1087
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: cx("sessions"), children: sessions.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { className: cx("session"), children: [
@@ -843,6 +1095,60 @@ function Account(props) {
843
1095
  ] })
844
1096
  ] });
845
1097
  }
1098
+ function PasskeysPart({ props, account, accountUrl, when }) {
1099
+ const auth = useAuth();
1100
+ const { f, done, setDone, run, general } = useSection(props);
1101
+ const [here, setHere] = (0, import_react.useState)(null);
1102
+ const [list, setList] = (0, import_react.useState)([]);
1103
+ const [name, setName] = (0, import_react.useState)("");
1104
+ const [password, setPassword] = (0, import_react.useState)("");
1105
+ (0, import_react.useEffect)(() => {
1106
+ void auth.methods().then((m) => {
1107
+ const can2 = m.passkeyHere && canUsePasskeys();
1108
+ setHere(can2);
1109
+ if (can2) void auth.account.passkeys().then(setList);
1110
+ });
1111
+ }, []);
1112
+ if (here === null) return null;
1113
+ if (!here) {
1114
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: f.cx("section"), children: [
1115
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: f.cx("heading"), children: f.t.passkeys }),
1116
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { className: f.cx("link"), href: accountUrl, children: f.t.managePasskeys })
1117
+ ] });
1118
+ }
1119
+ const add = async (e) => {
1120
+ e.preventDefault();
1121
+ const r = await run(() => auth.account.addPasskey({ name, password }));
1122
+ if (!r.ok) {
1123
+ if (r.error.code === "cancelled") f.setError(null);
1124
+ return;
1125
+ }
1126
+ setList([...list, r.passkey]);
1127
+ setName("");
1128
+ setPassword("");
1129
+ setDone(f.t.passkeyAdded);
1130
+ };
1131
+ const remove = async (id) => {
1132
+ const r = await run(() => auth.account.removePasskey(id));
1133
+ if (r.ok) setList(list.filter((p) => p.id !== id));
1134
+ };
1135
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { className: f.cx("section"), onSubmit: add, noValidate: true, children: [
1136
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: f.cx("heading"), children: f.t.passkeys }),
1137
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: f.cx("notice"), children: f.t.passkeysHelp }),
1138
+ general && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
1139
+ list.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: f.cx("notice"), children: f.t.noPasskeys }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: f.cx("sessions"), children: list.map((p) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { className: f.cx("session"), children: [
1140
+ p.name || f.t.passkeyUnnamed,
1141
+ " \xB7 ",
1142
+ when(p.createdAt),
1143
+ " ",
1144
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => void remove(p.id), children: f.t.removePasskey })
1145
+ ] }, p.id)) }),
1146
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "name", type: "text", label: f.t.passkeyName, value: name, autoComplete: "off", onChange: setName }),
1147
+ account.hasPassword && /* @__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 }),
1148
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.addPasskey }),
1149
+ done && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "status", className: f.cx("notice"), children: done })
1150
+ ] });
1151
+ }
846
1152
  function ProfilePart({ props, account, onSaved }) {
847
1153
  const auth = useAuth();
848
1154
  const { f, done, setDone, run, general } = useSection(props);
@@ -1106,7 +1412,16 @@ function useAuth() {
1106
1412
  };
1107
1413
  return {
1108
1414
  ...client,
1109
- signIn: { ...client.signIn, email: (f) => then(client.signIn.email(f)), mfa: (f) => then(client.signIn.mfa(f)), magicLinkVerify: (f) => then(client.signIn.magicLinkVerify(f)), emailCode: (f) => then(client.signIn.emailCode(f)) },
1415
+ signIn: {
1416
+ ...client.signIn,
1417
+ email: (f) => then(client.signIn.email(f)),
1418
+ mfa: (f) => then(client.signIn.mfa(f)),
1419
+ magicLinkVerify: (f) => then(client.signIn.magicLinkVerify(f)),
1420
+ emailCode: (f) => then(client.signIn.emailCode(f)),
1421
+ passkey: (o) => then(client.signIn.passkey(o)),
1422
+ passkeyAutofill: (o) => then(client.signIn.passkeyAutofill(o)),
1423
+ googleOneTap: (o) => then(client.signIn.googleOneTap(o))
1424
+ },
1110
1425
  signUp: { email: (f) => then(client.signUp.email(f)) },
1111
1426
  signOut: () => then(client.signOut()),
1112
1427
  account: {
@@ -1127,11 +1442,14 @@ function useAuth() {
1127
1442
  Account,
1128
1443
  CauthyProvider,
1129
1444
  ForgotPassword,
1445
+ GoogleOneTap,
1130
1446
  ResetPassword,
1131
1447
  SignIn,
1132
1448
  SignUp,
1133
1449
  VerifyEmail,
1134
1450
  can,
1451
+ canAutofillPasskeys,
1452
+ canUsePasskeys,
1135
1453
  createAuthClient,
1136
1454
  definePolicy,
1137
1455
  useAuth,