cauthy-auth 0.10.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 +54 -5
- package/dist/client.d.ts +54 -5
- package/dist/client.js +113 -11
- package/dist/client.js.map +1 -1
- package/dist/client.mjs +112 -11
- package/dist/client.mjs.map +1 -1
- package/dist/index.js +115 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +115 -5
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +61 -5
- package/dist/react.d.ts +61 -5
- package/dist/react.js +198 -17
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +197 -17
- 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 +111 -11
- package/dist/svelte.js.map +1 -1
- package/dist/svelte.mjs +111 -11
- 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 +111 -11
- package/dist/vue.js.map +1 -1
- package/dist/vue.mjs +111 -11
- 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 = {};
|
|
@@ -143,6 +210,28 @@ function createAuthClient(options = {}) {
|
|
|
143
210
|
if (o.locale) q.set("locale", o.locale);
|
|
144
211
|
return `${base}/login?${q}`;
|
|
145
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
|
+
}
|
|
146
235
|
return {
|
|
147
236
|
signIn: {
|
|
148
237
|
email: (f) => post("/sign-in/email", { email: f.email, password: f.password }, firstProblem(checkEmail(f.email), checkPassword(f.password))),
|
|
@@ -151,8 +240,16 @@ function createAuthClient(options = {}) {
|
|
|
151
240
|
magicLinkVerify: (f) => post("/sign-in/magic-link/verify", { token: f.token }, null),
|
|
152
241
|
emailCode: (f) => post("/sign-in/email-code", { code: f.code }, checkCode(f.code)),
|
|
153
242
|
passkeyUrl,
|
|
154
|
-
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
|
+
}
|
|
155
251
|
window.location.assign(passkeyUrl(o));
|
|
252
|
+
return { ok: true, status: "redirected" };
|
|
156
253
|
},
|
|
157
254
|
socialUrl,
|
|
158
255
|
social(provider, o) {
|
|
@@ -196,7 +293,16 @@ function createAuthClient(options = {}) {
|
|
|
196
293
|
const body = await read("/me/sessions");
|
|
197
294
|
return body && Array.isArray(body.sessions) ? body.sessions : [];
|
|
198
295
|
},
|
|
199
|
-
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)
|
|
200
306
|
},
|
|
201
307
|
async getSession() {
|
|
202
308
|
try {
|
|
@@ -211,14 +317,7 @@ function createAuthClient(options = {}) {
|
|
|
211
317
|
looked ??= read("/look").then((wire) => wire && typeof wire === "object" ? fromWire(wire) : null);
|
|
212
318
|
return looked;
|
|
213
319
|
},
|
|
214
|
-
|
|
215
|
-
try {
|
|
216
|
-
const res = await doFetch(`${base}/methods`, { credentials: "same-origin", headers: { accept: "application/json" } });
|
|
217
|
-
if (res.ok) return await res.json();
|
|
218
|
-
} catch {
|
|
219
|
-
}
|
|
220
|
-
return { providers: [], signUp: true, magicLink: false, passkey: false };
|
|
221
|
-
}
|
|
320
|
+
methods
|
|
222
321
|
};
|
|
223
322
|
}
|
|
224
323
|
|
|
@@ -325,6 +424,13 @@ var TEXTS = {
|
|
|
325
424
|
keepCodes: "Keep these codes somewhere safe. Each works once, and they are not shown again.",
|
|
326
425
|
passkeys: "Passkeys",
|
|
327
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",
|
|
328
434
|
sessions: "Where you are signed in",
|
|
329
435
|
signOutEverywhere: "Sign out everywhere",
|
|
330
436
|
unknownDevice: "Unknown device",
|
|
@@ -355,7 +461,10 @@ var TEXTS = {
|
|
|
355
461
|
wrong_password: "The current password is not right.",
|
|
356
462
|
step_up_required: "Enter your current password.",
|
|
357
463
|
weak_password: "Choose a stronger password.",
|
|
358
|
-
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."
|
|
359
468
|
}
|
|
360
469
|
},
|
|
361
470
|
es: {
|
|
@@ -404,6 +513,13 @@ var TEXTS = {
|
|
|
404
513
|
keepCodes: "Guarda estos c\xF3digos en un lugar seguro. Cada uno sirve una vez y no se vuelven a mostrar.",
|
|
405
514
|
passkeys: "Llaves de acceso",
|
|
406
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",
|
|
407
523
|
sessions: "D\xF3nde tienes la sesi\xF3n abierta",
|
|
408
524
|
signOutEverywhere: "Cerrar sesi\xF3n en todas partes",
|
|
409
525
|
unknownDevice: "Dispositivo desconocido",
|
|
@@ -434,7 +550,10 @@ var TEXTS = {
|
|
|
434
550
|
wrong_password: "La contrase\xF1a actual no es correcta.",
|
|
435
551
|
step_up_required: "Escribe tu contrase\xF1a actual.",
|
|
436
552
|
weak_password: "Elige una contrase\xF1a m\xE1s fuerte.",
|
|
437
|
-
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."
|
|
438
557
|
}
|
|
439
558
|
}
|
|
440
559
|
};
|
|
@@ -572,6 +691,15 @@ function SignIn(props) {
|
|
|
572
691
|
if (r.status === "mfa_required") return setStep("code");
|
|
573
692
|
f.done();
|
|
574
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
|
+
};
|
|
575
703
|
const sendLink = async () => {
|
|
576
704
|
f.setBusy(true);
|
|
577
705
|
f.setError(null);
|
|
@@ -606,7 +734,7 @@ function SignIn(props) {
|
|
|
606
734
|
" ",
|
|
607
735
|
p.name
|
|
608
736
|
] }, p.id)),
|
|
609
|
-
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 }),
|
|
610
738
|
((methods?.providers.length ?? 0) > 0 || methods?.passkey) && /* @__PURE__ */ jsx("p", { className: f.cx("separator"), children: f.t.orWithEmail }),
|
|
611
739
|
/* @__PURE__ */ jsx(Field, { f, name: "email", type: "email", label: f.t.email, value: email, autoComplete: "username", required: true, onChange: setEmail }),
|
|
612
740
|
/* @__PURE__ */ jsx(
|
|
@@ -792,10 +920,7 @@ function Account(props) {
|
|
|
792
920
|
account.hasPassword && /* @__PURE__ */ jsx(PasswordPart, { props }),
|
|
793
921
|
/* @__PURE__ */ jsx(LinkedPart, { props }),
|
|
794
922
|
/* @__PURE__ */ jsx(SecondStepPart, { props, account, onChanged: (mfaEnabled) => setAccount({ ...account, mfaEnabled }) }),
|
|
795
|
-
/* @__PURE__ */
|
|
796
|
-
/* @__PURE__ */ jsx("h2", { className: cx("heading"), children: t.passkeys }),
|
|
797
|
-
/* @__PURE__ */ jsx("a", { className: cx("link"), href: accountUrl(), children: t.managePasskeys })
|
|
798
|
-
] }),
|
|
923
|
+
/* @__PURE__ */ jsx(PasskeysPart, { props, account, accountUrl: accountUrl(), when }),
|
|
799
924
|
/* @__PURE__ */ jsxs("section", { className: cx("section"), children: [
|
|
800
925
|
/* @__PURE__ */ jsx("h2", { className: cx("heading"), children: t.sessions }),
|
|
801
926
|
/* @__PURE__ */ jsx("ul", { className: cx("sessions"), children: sessions.map((s, i) => /* @__PURE__ */ jsxs("li", { className: cx("session"), children: [
|
|
@@ -809,6 +934,60 @@ function Account(props) {
|
|
|
809
934
|
] })
|
|
810
935
|
] });
|
|
811
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
|
+
}
|
|
812
991
|
function ProfilePart({ props, account, onSaved }) {
|
|
813
992
|
const auth = useAuth();
|
|
814
993
|
const { f, done, setDone, run, general } = useSection(props);
|
|
@@ -1097,6 +1276,7 @@ export {
|
|
|
1097
1276
|
SignUp,
|
|
1098
1277
|
VerifyEmail,
|
|
1099
1278
|
can,
|
|
1279
|
+
canUsePasskeys,
|
|
1100
1280
|
createAuthClient,
|
|
1101
1281
|
definePolicy,
|
|
1102
1282
|
useAuth,
|