cauthy-auth 0.9.0 → 0.11.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 +61 -5
- package/dist/client.d.ts +61 -5
- package/dist/client.js +118 -12
- package/dist/client.js.map +1 -1
- package/dist/client.mjs +117 -12
- package/dist/client.mjs.map +1 -1
- package/dist/index.js +121 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +121 -6
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +68 -5
- package/dist/react.d.ts +68 -5
- package/dist/react.js +203 -18
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +202 -18
- package/dist/react.mjs.map +1 -1
- package/dist/svelte.d.mts +1 -1
- package/dist/svelte.d.ts +1 -1
- package/dist/svelte.js +116 -12
- package/dist/svelte.js.map +1 -1
- package/dist/svelte.mjs +116 -12
- package/dist/svelte.mjs.map +1 -1
- package/dist/vue.d.mts +1 -1
- package/dist/vue.d.ts +1 -1
- package/dist/vue.js +116 -12
- package/dist/vue.js.map +1 -1
- package/dist/vue.mjs +116 -12
- package/dist/vue.mjs.map +1 -1
- package/package.json +1 -1
package/dist/react.mjs
CHANGED
|
@@ -3,6 +3,73 @@
|
|
|
3
3
|
// src/react.tsx
|
|
4
4
|
import { createContext, useCallback, useContext, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState2 } from "react";
|
|
5
5
|
|
|
6
|
+
// src/passkey.ts
|
|
7
|
+
function canUsePasskeys() {
|
|
8
|
+
return typeof window !== "undefined" && typeof window.PublicKeyCredential === "function" && typeof navigator !== "undefined" && typeof navigator.credentials?.get === "function";
|
|
9
|
+
}
|
|
10
|
+
function bytes(text2) {
|
|
11
|
+
const b64 = text2.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(text2.length / 4) * 4, "=");
|
|
12
|
+
const bin = atob(b64);
|
|
13
|
+
const out = new Uint8Array(bin.length);
|
|
14
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
15
|
+
return out.buffer;
|
|
16
|
+
}
|
|
17
|
+
function text(buf) {
|
|
18
|
+
if (!buf) return null;
|
|
19
|
+
const view = new Uint8Array(buf);
|
|
20
|
+
let bin = "";
|
|
21
|
+
for (let i = 0; i < view.byteLength; i++) bin += String.fromCharCode(view[i]);
|
|
22
|
+
return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
23
|
+
}
|
|
24
|
+
var withId = (list) => (list ?? []).map((c) => ({ ...c, id: bytes(c.id) }));
|
|
25
|
+
async function getPasskey(options) {
|
|
26
|
+
const publicKey = { ...options, challenge: bytes(options.challenge), allowCredentials: withId(options.allowCredentials) };
|
|
27
|
+
const cred = await navigator.credentials.get({ publicKey });
|
|
28
|
+
if (cred === null) throw new DOMException("No passkey was chosen.", "NotAllowedError");
|
|
29
|
+
const r = cred.response;
|
|
30
|
+
return {
|
|
31
|
+
id: cred.id,
|
|
32
|
+
rawId: text(cred.rawId),
|
|
33
|
+
type: cred.type,
|
|
34
|
+
response: {
|
|
35
|
+
authenticatorData: text(r.authenticatorData),
|
|
36
|
+
clientDataJSON: text(r.clientDataJSON),
|
|
37
|
+
signature: text(r.signature),
|
|
38
|
+
userHandle: text(r.userHandle)
|
|
39
|
+
},
|
|
40
|
+
clientExtensionResults: cred.getClientExtensionResults?.() ?? {}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
async function makePasskey(options) {
|
|
44
|
+
const publicKey = {
|
|
45
|
+
...options,
|
|
46
|
+
challenge: bytes(options.challenge),
|
|
47
|
+
user: { ...options.user, id: bytes(options.user.id) },
|
|
48
|
+
excludeCredentials: withId(options.excludeCredentials)
|
|
49
|
+
};
|
|
50
|
+
const cred = await navigator.credentials.create({ publicKey });
|
|
51
|
+
if (cred === null) throw new DOMException("No passkey was made.", "NotAllowedError");
|
|
52
|
+
const r = cred.response;
|
|
53
|
+
return {
|
|
54
|
+
id: cred.id,
|
|
55
|
+
rawId: text(cred.rawId),
|
|
56
|
+
type: cred.type,
|
|
57
|
+
response: {
|
|
58
|
+
clientDataJSON: text(r.clientDataJSON),
|
|
59
|
+
attestationObject: text(r.attestationObject),
|
|
60
|
+
transports: typeof r.getTransports === "function" ? r.getTransports() : []
|
|
61
|
+
},
|
|
62
|
+
clientExtensionResults: cred.getClientExtensionResults?.() ?? {}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function ceremonyError(e) {
|
|
66
|
+
const name = e instanceof Error || e instanceof DOMException ? e.name : "";
|
|
67
|
+
if (name === "NotAllowedError" || name === "AbortError") return { code: "cancelled", message: "The passkey was not used. Try again when you are ready." };
|
|
68
|
+
if (name === "InvalidStateError") return { code: "passkey_exists", message: "This device already has a passkey for this account." };
|
|
69
|
+
if (name === "SecurityError") return { code: "passkey_unavailable", message: "Passkeys cannot be used on this page." };
|
|
70
|
+
return { code: "passkey_failed", message: "The passkey could not be used. Try again, or sign in another way." };
|
|
71
|
+
}
|
|
72
|
+
|
|
6
73
|
// src/theme.ts
|
|
7
74
|
function themeVariables(theme) {
|
|
8
75
|
const vars = {};
|
|
@@ -105,13 +172,17 @@ var UNAVAILABLE = { code: "unavailable", message: "Sign-in is unavailable right
|
|
|
105
172
|
function createAuthClient(options = {}) {
|
|
106
173
|
const base = (options.basePath ?? "/api/auth").replace(/\/+$/, "");
|
|
107
174
|
const doFetch = (...args) => (options.fetch ?? fetch)(...args);
|
|
175
|
+
const locale = () => {
|
|
176
|
+
const said = typeof options.locale === "function" ? options.locale() : options.locale;
|
|
177
|
+
return (said ?? (typeof document === "undefined" ? "" : document.documentElement?.lang) ?? "").trim();
|
|
178
|
+
};
|
|
108
179
|
async function post(path, form, bad) {
|
|
109
180
|
if (bad) return { ok: false, error: { code: bad.code, message: bad.message, field: bad.field } };
|
|
110
181
|
try {
|
|
111
182
|
const res = await doFetch(`${base}${path}`, {
|
|
112
183
|
method: "POST",
|
|
113
184
|
credentials: "same-origin",
|
|
114
|
-
headers: { "content-type": "application/json", accept: "application/json" },
|
|
185
|
+
headers: { "content-type": "application/json", accept: "application/json", ...locale() ? { "x-cauthy-locale": locale() } : {} },
|
|
115
186
|
body: JSON.stringify(form)
|
|
116
187
|
});
|
|
117
188
|
const body = await res.json().catch(() => null);
|
|
@@ -139,6 +210,28 @@ function createAuthClient(options = {}) {
|
|
|
139
210
|
if (o.locale) q.set("locale", o.locale);
|
|
140
211
|
return `${base}/login?${q}`;
|
|
141
212
|
};
|
|
213
|
+
async function methods() {
|
|
214
|
+
try {
|
|
215
|
+
const res = await doFetch(`${base}/methods`, { credentials: "same-origin", headers: { accept: "application/json" } });
|
|
216
|
+
if (res.ok) {
|
|
217
|
+
const m = await res.json();
|
|
218
|
+
return { providers: [], signUp: true, magicLink: false, passkey: false, ...m, passkeyHere: m.passkeyHere === true, passkeyOnCauthy: m.passkeyOnCauthy ?? m.passkey === true };
|
|
219
|
+
}
|
|
220
|
+
} catch {
|
|
221
|
+
}
|
|
222
|
+
return { providers: [], signUp: true, magicLink: false, passkey: false, passkeyHere: false, passkeyOnCauthy: false };
|
|
223
|
+
}
|
|
224
|
+
async function ceremony(begin, finish, form, run, more = {}) {
|
|
225
|
+
const begun = await post(begin, form, null);
|
|
226
|
+
if (!begun.ok) return begun;
|
|
227
|
+
let credential;
|
|
228
|
+
try {
|
|
229
|
+
credential = await run(begun.options);
|
|
230
|
+
} catch (e) {
|
|
231
|
+
return { ok: false, error: ceremonyError(e) };
|
|
232
|
+
}
|
|
233
|
+
return post(finish, { credential, ...more }, null);
|
|
234
|
+
}
|
|
142
235
|
return {
|
|
143
236
|
signIn: {
|
|
144
237
|
email: (f) => post("/sign-in/email", { email: f.email, password: f.password }, firstProblem(checkEmail(f.email), checkPassword(f.password))),
|
|
@@ -147,8 +240,16 @@ function createAuthClient(options = {}) {
|
|
|
147
240
|
magicLinkVerify: (f) => post("/sign-in/magic-link/verify", { token: f.token }, null),
|
|
148
241
|
emailCode: (f) => post("/sign-in/email-code", { code: f.code }, checkCode(f.code)),
|
|
149
242
|
passkeyUrl,
|
|
150
|
-
passkey(o) {
|
|
243
|
+
async passkey(o) {
|
|
244
|
+
const m = await methods();
|
|
245
|
+
if (m.passkeyHere && canUsePasskeys()) {
|
|
246
|
+
const r = await ceremony("/sign-in/passkey/begin", "/sign-in/passkey/finish", {}, getPasskey);
|
|
247
|
+
if (r.ok || r.error.code !== "passkey_unavailable" || !m.passkeyOnCauthy) return r;
|
|
248
|
+
} else if (!m.passkeyOnCauthy) {
|
|
249
|
+
return { ok: false, error: { code: "passkey_unavailable", message: "Passkeys cannot be used here." } };
|
|
250
|
+
}
|
|
151
251
|
window.location.assign(passkeyUrl(o));
|
|
252
|
+
return { ok: true, status: "redirected" };
|
|
152
253
|
},
|
|
153
254
|
socialUrl,
|
|
154
255
|
social(provider, o) {
|
|
@@ -192,7 +293,16 @@ function createAuthClient(options = {}) {
|
|
|
192
293
|
const body = await read("/me/sessions");
|
|
193
294
|
return body && Array.isArray(body.sessions) ? body.sessions : [];
|
|
194
295
|
},
|
|
195
|
-
signOutEverywhere: () => post("/me/sessions/end", {}, null)
|
|
296
|
+
signOutEverywhere: () => post("/me/sessions/end", {}, null),
|
|
297
|
+
async passkeys() {
|
|
298
|
+
const body = await read("/me/passkeys");
|
|
299
|
+
return body && Array.isArray(body.passkeys) ? body.passkeys : [];
|
|
300
|
+
},
|
|
301
|
+
addPasskey(f = {}) {
|
|
302
|
+
if (!canUsePasskeys()) return Promise.resolve({ ok: false, error: { code: "passkey_unavailable", message: "This browser cannot make passkeys." } });
|
|
303
|
+
return ceremony("/me/passkeys/add/begin", "/me/passkeys/add/finish", { password: f.password ?? "" }, makePasskey, { name: f.name ?? "" });
|
|
304
|
+
},
|
|
305
|
+
removePasskey: (id) => post("/me/passkeys/remove", { id }, null)
|
|
196
306
|
},
|
|
197
307
|
async getSession() {
|
|
198
308
|
try {
|
|
@@ -207,14 +317,7 @@ function createAuthClient(options = {}) {
|
|
|
207
317
|
looked ??= read("/look").then((wire) => wire && typeof wire === "object" ? fromWire(wire) : null);
|
|
208
318
|
return looked;
|
|
209
319
|
},
|
|
210
|
-
|
|
211
|
-
try {
|
|
212
|
-
const res = await doFetch(`${base}/methods`, { credentials: "same-origin", headers: { accept: "application/json" } });
|
|
213
|
-
if (res.ok) return await res.json();
|
|
214
|
-
} catch {
|
|
215
|
-
}
|
|
216
|
-
return { providers: [], signUp: true, magicLink: false, passkey: false };
|
|
217
|
-
}
|
|
320
|
+
methods
|
|
218
321
|
};
|
|
219
322
|
}
|
|
220
323
|
|
|
@@ -321,6 +424,13 @@ var TEXTS = {
|
|
|
321
424
|
keepCodes: "Keep these codes somewhere safe. Each works once, and they are not shown again.",
|
|
322
425
|
passkeys: "Passkeys",
|
|
323
426
|
managePasskeys: "Add or remove a passkey",
|
|
427
|
+
passkeysHelp: "Sign in with your fingerprint, your face or your device's lock, without a password.",
|
|
428
|
+
noPasskeys: "No passkey yet.",
|
|
429
|
+
passkeyName: "A name for this device (optional)",
|
|
430
|
+
addPasskey: "Add a passkey on this device",
|
|
431
|
+
removePasskey: "Remove",
|
|
432
|
+
passkeyAdded: "The passkey was added. Next time, sign in with it.",
|
|
433
|
+
passkeyUnnamed: "Passkey",
|
|
324
434
|
sessions: "Where you are signed in",
|
|
325
435
|
signOutEverywhere: "Sign out everywhere",
|
|
326
436
|
unknownDevice: "Unknown device",
|
|
@@ -351,7 +461,10 @@ var TEXTS = {
|
|
|
351
461
|
wrong_password: "The current password is not right.",
|
|
352
462
|
step_up_required: "Enter your current password.",
|
|
353
463
|
weak_password: "Choose a stronger password.",
|
|
354
|
-
unauthenticated: "Sign in first."
|
|
464
|
+
unauthenticated: "Sign in first.",
|
|
465
|
+
passkey_failed: "The passkey could not be used. Try again, or sign in another way.",
|
|
466
|
+
passkey_exists: "This device already has a passkey for your account.",
|
|
467
|
+
passkey_unavailable: "Passkeys cannot be used here."
|
|
355
468
|
}
|
|
356
469
|
},
|
|
357
470
|
es: {
|
|
@@ -400,6 +513,13 @@ var TEXTS = {
|
|
|
400
513
|
keepCodes: "Guarda estos c\xF3digos en un lugar seguro. Cada uno sirve una vez y no se vuelven a mostrar.",
|
|
401
514
|
passkeys: "Llaves de acceso",
|
|
402
515
|
managePasskeys: "Agregar o quitar una llave de acceso",
|
|
516
|
+
passkeysHelp: "Entra con tu huella, tu rostro o el bloqueo de tu dispositivo, sin contrase\xF1a.",
|
|
517
|
+
noPasskeys: "Todav\xEDa no tienes llaves de acceso.",
|
|
518
|
+
passkeyName: "Un nombre para este dispositivo (opcional)",
|
|
519
|
+
addPasskey: "Agregar una llave en este dispositivo",
|
|
520
|
+
removePasskey: "Quitar",
|
|
521
|
+
passkeyAdded: "La llave de acceso qued\xF3 agregada. La pr\xF3xima vez, entra con ella.",
|
|
522
|
+
passkeyUnnamed: "Llave de acceso",
|
|
403
523
|
sessions: "D\xF3nde tienes la sesi\xF3n abierta",
|
|
404
524
|
signOutEverywhere: "Cerrar sesi\xF3n en todas partes",
|
|
405
525
|
unknownDevice: "Dispositivo desconocido",
|
|
@@ -430,7 +550,10 @@ var TEXTS = {
|
|
|
430
550
|
wrong_password: "La contrase\xF1a actual no es correcta.",
|
|
431
551
|
step_up_required: "Escribe tu contrase\xF1a actual.",
|
|
432
552
|
weak_password: "Elige una contrase\xF1a m\xE1s fuerte.",
|
|
433
|
-
unauthenticated: "Inicia sesi\xF3n primero."
|
|
553
|
+
unauthenticated: "Inicia sesi\xF3n primero.",
|
|
554
|
+
passkey_failed: "No se pudo usar la llave de acceso. Intenta de nuevo o entra de otra forma.",
|
|
555
|
+
passkey_exists: "Este dispositivo ya tiene una llave de acceso de tu cuenta.",
|
|
556
|
+
passkey_unavailable: "Aqu\xED no se pueden usar llaves de acceso."
|
|
434
557
|
}
|
|
435
558
|
}
|
|
436
559
|
};
|
|
@@ -568,6 +691,15 @@ function SignIn(props) {
|
|
|
568
691
|
if (r.status === "mfa_required") return setStep("code");
|
|
569
692
|
f.done();
|
|
570
693
|
};
|
|
694
|
+
const usePasskey = async () => {
|
|
695
|
+
f.setBusy(true);
|
|
696
|
+
f.setError(null);
|
|
697
|
+
const r = await auth.signIn.passkey({ returnTo: props.returnTo, locale: props.locale });
|
|
698
|
+
if (r.ok && r.status === "redirected") return;
|
|
699
|
+
f.setBusy(false);
|
|
700
|
+
if (!r.ok) return r.error.code === "cancelled" ? void 0 : f.setError(r.error);
|
|
701
|
+
f.done();
|
|
702
|
+
};
|
|
571
703
|
const sendLink = async () => {
|
|
572
704
|
f.setBusy(true);
|
|
573
705
|
f.setError(null);
|
|
@@ -602,7 +734,7 @@ function SignIn(props) {
|
|
|
602
734
|
" ",
|
|
603
735
|
p.name
|
|
604
736
|
] }, p.id)),
|
|
605
|
-
methods?.passkey && /* @__PURE__ */ jsx(f.Button, { type: "button", className: f.cx("providerButton"), onClick: () =>
|
|
737
|
+
methods?.passkey && /* @__PURE__ */ jsx(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => void usePasskey(), children: f.t.usePasskey }),
|
|
606
738
|
((methods?.providers.length ?? 0) > 0 || methods?.passkey) && /* @__PURE__ */ jsx("p", { className: f.cx("separator"), children: f.t.orWithEmail }),
|
|
607
739
|
/* @__PURE__ */ jsx(Field, { f, name: "email", type: "email", label: f.t.email, value: email, autoComplete: "username", required: true, onChange: setEmail }),
|
|
608
740
|
/* @__PURE__ */ jsx(
|
|
@@ -788,10 +920,7 @@ function Account(props) {
|
|
|
788
920
|
account.hasPassword && /* @__PURE__ */ jsx(PasswordPart, { props }),
|
|
789
921
|
/* @__PURE__ */ jsx(LinkedPart, { props }),
|
|
790
922
|
/* @__PURE__ */ jsx(SecondStepPart, { props, account, onChanged: (mfaEnabled) => setAccount({ ...account, mfaEnabled }) }),
|
|
791
|
-
/* @__PURE__ */
|
|
792
|
-
/* @__PURE__ */ jsx("h2", { className: cx("heading"), children: t.passkeys }),
|
|
793
|
-
/* @__PURE__ */ jsx("a", { className: cx("link"), href: accountUrl(), children: t.managePasskeys })
|
|
794
|
-
] }),
|
|
923
|
+
/* @__PURE__ */ jsx(PasskeysPart, { props, account, accountUrl: accountUrl(), when }),
|
|
795
924
|
/* @__PURE__ */ jsxs("section", { className: cx("section"), children: [
|
|
796
925
|
/* @__PURE__ */ jsx("h2", { className: cx("heading"), children: t.sessions }),
|
|
797
926
|
/* @__PURE__ */ jsx("ul", { className: cx("sessions"), children: sessions.map((s, i) => /* @__PURE__ */ jsxs("li", { className: cx("session"), children: [
|
|
@@ -805,6 +934,60 @@ function Account(props) {
|
|
|
805
934
|
] })
|
|
806
935
|
] });
|
|
807
936
|
}
|
|
937
|
+
function PasskeysPart({ props, account, accountUrl, when }) {
|
|
938
|
+
const auth = useAuth();
|
|
939
|
+
const { f, done, setDone, run, general } = useSection(props);
|
|
940
|
+
const [here, setHere] = useState(null);
|
|
941
|
+
const [list, setList] = useState([]);
|
|
942
|
+
const [name, setName] = useState("");
|
|
943
|
+
const [password, setPassword] = useState("");
|
|
944
|
+
useEffect(() => {
|
|
945
|
+
void auth.methods().then((m) => {
|
|
946
|
+
const can2 = m.passkeyHere && canUsePasskeys();
|
|
947
|
+
setHere(can2);
|
|
948
|
+
if (can2) void auth.account.passkeys().then(setList);
|
|
949
|
+
});
|
|
950
|
+
}, []);
|
|
951
|
+
if (here === null) return null;
|
|
952
|
+
if (!here) {
|
|
953
|
+
return /* @__PURE__ */ jsxs("section", { className: f.cx("section"), children: [
|
|
954
|
+
/* @__PURE__ */ jsx("h2", { className: f.cx("heading"), children: f.t.passkeys }),
|
|
955
|
+
/* @__PURE__ */ jsx("a", { className: f.cx("link"), href: accountUrl, children: f.t.managePasskeys })
|
|
956
|
+
] });
|
|
957
|
+
}
|
|
958
|
+
const add = async (e) => {
|
|
959
|
+
e.preventDefault();
|
|
960
|
+
const r = await run(() => auth.account.addPasskey({ name, password }));
|
|
961
|
+
if (!r.ok) {
|
|
962
|
+
if (r.error.code === "cancelled") f.setError(null);
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
setList([...list, r.passkey]);
|
|
966
|
+
setName("");
|
|
967
|
+
setPassword("");
|
|
968
|
+
setDone(f.t.passkeyAdded);
|
|
969
|
+
};
|
|
970
|
+
const remove = async (id) => {
|
|
971
|
+
const r = await run(() => auth.account.removePasskey(id));
|
|
972
|
+
if (r.ok) setList(list.filter((p) => p.id !== id));
|
|
973
|
+
};
|
|
974
|
+
return /* @__PURE__ */ jsxs("form", { className: f.cx("section"), onSubmit: add, noValidate: true, children: [
|
|
975
|
+
/* @__PURE__ */ jsx("h2", { className: f.cx("heading"), children: f.t.passkeys }),
|
|
976
|
+
/* @__PURE__ */ jsx("p", { className: f.cx("notice"), children: f.t.passkeysHelp }),
|
|
977
|
+
general && /* @__PURE__ */ jsx("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
|
|
978
|
+
list.length === 0 ? /* @__PURE__ */ jsx("p", { className: f.cx("notice"), children: f.t.noPasskeys }) : /* @__PURE__ */ jsx("ul", { className: f.cx("sessions"), children: list.map((p) => /* @__PURE__ */ jsxs("li", { className: f.cx("session"), children: [
|
|
979
|
+
p.name || f.t.passkeyUnnamed,
|
|
980
|
+
" \xB7 ",
|
|
981
|
+
when(p.createdAt),
|
|
982
|
+
" ",
|
|
983
|
+
/* @__PURE__ */ jsx(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => void remove(p.id), children: f.t.removePasskey })
|
|
984
|
+
] }, p.id)) }),
|
|
985
|
+
/* @__PURE__ */ jsx(Field, { f, name: "name", type: "text", label: f.t.passkeyName, value: name, autoComplete: "off", onChange: setName }),
|
|
986
|
+
account.hasPassword && /* @__PURE__ */ jsx(Field, { f, name: "password", type: "password", label: f.t.currentPassword, value: password, autoComplete: "current-password", required: true, onChange: setPassword }),
|
|
987
|
+
/* @__PURE__ */ jsx(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.addPasskey }),
|
|
988
|
+
done && /* @__PURE__ */ jsx("p", { role: "status", className: f.cx("notice"), children: done })
|
|
989
|
+
] });
|
|
990
|
+
}
|
|
808
991
|
function ProfilePart({ props, account, onSaved }) {
|
|
809
992
|
const auth = useAuth();
|
|
810
993
|
const { f, done, setDone, run, general } = useSection(props);
|
|
@@ -1093,6 +1276,7 @@ export {
|
|
|
1093
1276
|
SignUp,
|
|
1094
1277
|
VerifyEmail,
|
|
1095
1278
|
can,
|
|
1279
|
+
canUsePasskeys,
|
|
1096
1280
|
createAuthClient,
|
|
1097
1281
|
definePolicy,
|
|
1098
1282
|
useAuth,
|