cauthy-auth 0.11.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/README.md +3 -2
- package/dist/cli.js +299 -20
- package/dist/client.d.mts +35 -1
- package/dist/client.d.ts +35 -1
- package/dist/client.js +91 -2
- package/dist/client.js.map +1 -1
- package/dist/client.mjs +90 -2
- package/dist/client.mjs.map +1 -1
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +13 -0
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +50 -1
- package/dist/react.d.ts +50 -1
- package/dist/react.js +142 -5
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +140 -5
- package/dist/react.mjs.map +1 -1
- package/dist/svelte.js +89 -2
- package/dist/svelte.js.map +1 -1
- package/dist/svelte.mjs +89 -2
- package/dist/svelte.mjs.map +1 -1
- package/dist/vue.js +89 -2
- package/dist/vue.js.map +1 -1
- package/dist/vue.mjs +89 -2
- package/dist/vue.mjs.map +1 -1
- package/package.json +1 -1
package/dist/react.mjs
CHANGED
|
@@ -7,6 +7,15 @@ import { createContext, useCallback, useContext, useEffect as useEffect2, useMem
|
|
|
7
7
|
function canUsePasskeys() {
|
|
8
8
|
return typeof window !== "undefined" && typeof window.PublicKeyCredential === "function" && typeof navigator !== "undefined" && typeof navigator.credentials?.get === "function";
|
|
9
9
|
}
|
|
10
|
+
async function canAutofillPasskeys() {
|
|
11
|
+
if (!canUsePasskeys()) return false;
|
|
12
|
+
const pkc = window.PublicKeyCredential;
|
|
13
|
+
try {
|
|
14
|
+
return typeof pkc.isConditionalMediationAvailable === "function" && await pkc.isConditionalMediationAvailable() === true;
|
|
15
|
+
} catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
10
19
|
function bytes(text2) {
|
|
11
20
|
const b64 = text2.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(text2.length / 4) * 4, "=");
|
|
12
21
|
const bin = atob(b64);
|
|
@@ -22,9 +31,9 @@ function text(buf) {
|
|
|
22
31
|
return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
23
32
|
}
|
|
24
33
|
var withId = (list) => (list ?? []).map((c) => ({ ...c, id: bytes(c.id) }));
|
|
25
|
-
async function getPasskey(options) {
|
|
34
|
+
async function getPasskey(options, how = {}) {
|
|
26
35
|
const publicKey = { ...options, challenge: bytes(options.challenge), allowCredentials: withId(options.allowCredentials) };
|
|
27
|
-
const cred = await navigator.credentials.get({ publicKey });
|
|
36
|
+
const cred = await navigator.credentials.get({ publicKey, ...how });
|
|
28
37
|
if (cred === null) throw new DOMException("No passkey was chosen.", "NotAllowedError");
|
|
29
38
|
const r = cred.response;
|
|
30
39
|
return {
|
|
@@ -168,6 +177,24 @@ function firstProblem(...checks) {
|
|
|
168
177
|
}
|
|
169
178
|
|
|
170
179
|
// src/client.ts
|
|
180
|
+
var googleScript = null;
|
|
181
|
+
function loadGoogleIdentity() {
|
|
182
|
+
const ready = () => window.google?.accounts?.id;
|
|
183
|
+
if (ready()) return Promise.resolve(ready());
|
|
184
|
+
googleScript ??= new Promise((resolve, reject) => {
|
|
185
|
+
const script = document.createElement("script");
|
|
186
|
+
script.src = "https://accounts.google.com/gsi/client";
|
|
187
|
+
script.async = true;
|
|
188
|
+
script.onload = () => ready() ? resolve(ready()) : reject(new Error("google identity services did not load"));
|
|
189
|
+
script.onerror = () => {
|
|
190
|
+
googleScript = null;
|
|
191
|
+
reject(new Error("google identity services did not load"));
|
|
192
|
+
};
|
|
193
|
+
document.head.appendChild(script);
|
|
194
|
+
});
|
|
195
|
+
return googleScript;
|
|
196
|
+
}
|
|
197
|
+
var PASSKEY_SECONDS = 300;
|
|
171
198
|
var UNAVAILABLE = { code: "unavailable", message: "Sign-in is unavailable right now. Try again in a moment." };
|
|
172
199
|
function createAuthClient(options = {}) {
|
|
173
200
|
const base = (options.basePath ?? "/api/auth").replace(/\/+$/, "");
|
|
@@ -251,6 +278,66 @@ function createAuthClient(options = {}) {
|
|
|
251
278
|
window.location.assign(passkeyUrl(o));
|
|
252
279
|
return { ok: true, status: "redirected" };
|
|
253
280
|
},
|
|
281
|
+
async passkeyAutofill(o = {}) {
|
|
282
|
+
const cancelled = { ok: false, error: { code: "cancelled", message: "No passkey was chosen." } };
|
|
283
|
+
const m = await methods();
|
|
284
|
+
if (!m.passkeyHere || !await canAutofillPasskeys()) return cancelled;
|
|
285
|
+
for (; ; ) {
|
|
286
|
+
if (o.signal?.aborted) return cancelled;
|
|
287
|
+
const begun = await post("/sign-in/passkey/begin", {}, null);
|
|
288
|
+
if (!begun.ok) return begun;
|
|
289
|
+
const renew = new AbortController();
|
|
290
|
+
const stop = () => renew.abort();
|
|
291
|
+
o.signal?.addEventListener("abort", stop, { once: true });
|
|
292
|
+
const timer = setTimeout(stop, (PASSKEY_SECONDS - 30) * 1e3);
|
|
293
|
+
let credential;
|
|
294
|
+
try {
|
|
295
|
+
credential = await getPasskey(begun.options, { mediation: "conditional", signal: renew.signal });
|
|
296
|
+
} catch (e) {
|
|
297
|
+
if (o.signal?.aborted) return cancelled;
|
|
298
|
+
if (renew.signal.aborted) continue;
|
|
299
|
+
return { ok: false, error: ceremonyError(e) };
|
|
300
|
+
} finally {
|
|
301
|
+
clearTimeout(timer);
|
|
302
|
+
o.signal?.removeEventListener("abort", stop);
|
|
303
|
+
}
|
|
304
|
+
return post("/sign-in/passkey/finish", { credential }, null);
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
async googleOneTap(o = {}) {
|
|
308
|
+
const cancelled = { ok: false, error: { code: "cancelled", message: "No Google account was chosen." } };
|
|
309
|
+
if (typeof window === "undefined" || o.signal?.aborted) return cancelled;
|
|
310
|
+
const begun = await post("/sign-in/google-one-tap/begin", {}, null);
|
|
311
|
+
if (!begun.ok) return begun;
|
|
312
|
+
let gis;
|
|
313
|
+
try {
|
|
314
|
+
gis = await loadGoogleIdentity();
|
|
315
|
+
} catch {
|
|
316
|
+
return { ok: false, error: { code: "unavailable", message: "Google could not be reached. Try again, or sign in another way." } };
|
|
317
|
+
}
|
|
318
|
+
const credential = await new Promise((done) => {
|
|
319
|
+
o.signal?.addEventListener("abort", () => {
|
|
320
|
+
gis.cancel();
|
|
321
|
+
done(null);
|
|
322
|
+
}, { once: true });
|
|
323
|
+
gis.initialize({
|
|
324
|
+
client_id: begun.clientId,
|
|
325
|
+
nonce: begun.nonce,
|
|
326
|
+
context: o.context ?? "signin",
|
|
327
|
+
callback: (r) => done(r.credential ?? null),
|
|
328
|
+
auto_select: false,
|
|
329
|
+
cancel_on_tap_outside: true,
|
|
330
|
+
itp_support: true,
|
|
331
|
+
use_fedcm_for_prompt: true
|
|
332
|
+
});
|
|
333
|
+
gis.prompt((moment) => {
|
|
334
|
+
if (moment.isNotDisplayed?.() || moment.isSkippedMoment?.()) done(null);
|
|
335
|
+
else if (moment.isDismissedMoment?.() && moment.getDismissedReason?.() !== "credential_returned") done(null);
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
if (credential === null) return cancelled;
|
|
339
|
+
return post("/sign-in/google-one-tap", { credential }, null);
|
|
340
|
+
},
|
|
254
341
|
socialUrl,
|
|
255
342
|
social(provider, o) {
|
|
256
343
|
window.location.assign(socialUrl(provider, o));
|
|
@@ -667,6 +754,8 @@ function SignIn(props) {
|
|
|
667
754
|
const [password, setPassword] = useState("");
|
|
668
755
|
const [code, setCode] = useState("");
|
|
669
756
|
const [step, setStep] = useState("password");
|
|
757
|
+
const autofill = useRef(null);
|
|
758
|
+
const [offer, setOffer] = useState(0);
|
|
670
759
|
useEffect(() => {
|
|
671
760
|
void auth.methods().then(setMethods);
|
|
672
761
|
const back = returnedError();
|
|
@@ -681,26 +770,46 @@ function SignIn(props) {
|
|
|
681
770
|
setStep("password");
|
|
682
771
|
});
|
|
683
772
|
});
|
|
773
|
+
useEffect(() => {
|
|
774
|
+
if (step !== "password" || !methods?.passkeyHere) return;
|
|
775
|
+
const ctl = new AbortController();
|
|
776
|
+
autofill.current = ctl;
|
|
777
|
+
void auth.signIn.passkeyAutofill({ signal: ctl.signal }).then((r) => {
|
|
778
|
+
if (ctl.signal.aborted) return;
|
|
779
|
+
if (r.ok) return f.done();
|
|
780
|
+
if (r.error.code === "cancelled") return;
|
|
781
|
+
f.setError(r.error);
|
|
782
|
+
if (r.error.code === "passkey_failed" || r.error.code === "step_expired") setOffer((n) => n + 1);
|
|
783
|
+
});
|
|
784
|
+
return () => ctl.abort();
|
|
785
|
+
}, [step, methods, offer]);
|
|
684
786
|
const submit = async (e) => {
|
|
685
787
|
e.preventDefault();
|
|
788
|
+
autofill.current?.abort();
|
|
686
789
|
f.setBusy(true);
|
|
687
790
|
f.setError(null);
|
|
688
791
|
const r = step === "code" ? await auth.signIn.mfa({ code }) : step === "sent" ? await auth.signIn.emailCode({ code }) : await auth.signIn.email({ email, password });
|
|
689
792
|
f.setBusy(false);
|
|
690
|
-
if (!r.ok)
|
|
793
|
+
if (!r.ok) {
|
|
794
|
+
setOffer((n) => n + 1);
|
|
795
|
+
return f.setError(r.error);
|
|
796
|
+
}
|
|
691
797
|
if (r.status === "mfa_required") return setStep("code");
|
|
692
798
|
f.done();
|
|
693
799
|
};
|
|
694
800
|
const usePasskey = async () => {
|
|
801
|
+
autofill.current?.abort();
|
|
695
802
|
f.setBusy(true);
|
|
696
803
|
f.setError(null);
|
|
697
804
|
const r = await auth.signIn.passkey({ returnTo: props.returnTo, locale: props.locale });
|
|
698
805
|
if (r.ok && r.status === "redirected") return;
|
|
699
806
|
f.setBusy(false);
|
|
807
|
+
if (!r.ok) setOffer((n) => n + 1);
|
|
700
808
|
if (!r.ok) return r.error.code === "cancelled" ? void 0 : f.setError(r.error);
|
|
701
809
|
f.done();
|
|
702
810
|
};
|
|
703
811
|
const sendLink = async () => {
|
|
812
|
+
autofill.current?.abort();
|
|
704
813
|
f.setBusy(true);
|
|
705
814
|
f.setError(null);
|
|
706
815
|
const r = await auth.signIn.magicLink({ email });
|
|
@@ -736,7 +845,7 @@ function SignIn(props) {
|
|
|
736
845
|
] }, p.id)),
|
|
737
846
|
methods?.passkey && /* @__PURE__ */ jsx(f.Button, { type: "button", disabled: f.busy, className: f.cx("providerButton"), onClick: () => void usePasskey(), children: f.t.usePasskey }),
|
|
738
847
|
((methods?.providers.length ?? 0) > 0 || methods?.passkey) && /* @__PURE__ */ jsx("p", { className: f.cx("separator"), children: f.t.orWithEmail }),
|
|
739
|
-
/* @__PURE__ */ jsx(Field, { f, name: "email", type: "email", label: f.t.email, value: email, autoComplete: "username", required: true, onChange: setEmail }),
|
|
848
|
+
/* @__PURE__ */ jsx(Field, { f, name: "email", type: "email", label: f.t.email, value: email, autoComplete: methods?.passkeyHere ? "username webauthn" : "username", required: true, onChange: setEmail }),
|
|
740
849
|
/* @__PURE__ */ jsx(
|
|
741
850
|
Field,
|
|
742
851
|
{
|
|
@@ -760,6 +869,21 @@ function SignIn(props) {
|
|
|
760
869
|
] })
|
|
761
870
|
] });
|
|
762
871
|
}
|
|
872
|
+
function GoogleOneTap(props) {
|
|
873
|
+
const auth = useAuth();
|
|
874
|
+
const unauthenticated = auth.status === "unauthenticated";
|
|
875
|
+
useEffect(() => {
|
|
876
|
+
if (!unauthenticated) return;
|
|
877
|
+
const ctl = new AbortController();
|
|
878
|
+
void auth.signIn.googleOneTap({ signal: ctl.signal, context: props.context }).then((r) => {
|
|
879
|
+
if (!r.ok || ctl.signal.aborted) return;
|
|
880
|
+
if (props.onSuccess) props.onSuccess();
|
|
881
|
+
else if (props.returnTo) window.location.assign(props.returnTo);
|
|
882
|
+
});
|
|
883
|
+
return () => ctl.abort();
|
|
884
|
+
}, [unauthenticated]);
|
|
885
|
+
return null;
|
|
886
|
+
}
|
|
763
887
|
function SignUp(props) {
|
|
764
888
|
const auth = useAuth();
|
|
765
889
|
const f = useForm(props);
|
|
@@ -1251,7 +1375,16 @@ function useAuth() {
|
|
|
1251
1375
|
};
|
|
1252
1376
|
return {
|
|
1253
1377
|
...client,
|
|
1254
|
-
signIn: {
|
|
1378
|
+
signIn: {
|
|
1379
|
+
...client.signIn,
|
|
1380
|
+
email: (f) => then(client.signIn.email(f)),
|
|
1381
|
+
mfa: (f) => then(client.signIn.mfa(f)),
|
|
1382
|
+
magicLinkVerify: (f) => then(client.signIn.magicLinkVerify(f)),
|
|
1383
|
+
emailCode: (f) => then(client.signIn.emailCode(f)),
|
|
1384
|
+
passkey: (o) => then(client.signIn.passkey(o)),
|
|
1385
|
+
passkeyAutofill: (o) => then(client.signIn.passkeyAutofill(o)),
|
|
1386
|
+
googleOneTap: (o) => then(client.signIn.googleOneTap(o))
|
|
1387
|
+
},
|
|
1255
1388
|
signUp: { email: (f) => then(client.signUp.email(f)) },
|
|
1256
1389
|
signOut: () => then(client.signOut()),
|
|
1257
1390
|
account: {
|
|
@@ -1271,11 +1404,13 @@ export {
|
|
|
1271
1404
|
Account,
|
|
1272
1405
|
CauthyProvider,
|
|
1273
1406
|
ForgotPassword,
|
|
1407
|
+
GoogleOneTap,
|
|
1274
1408
|
ResetPassword,
|
|
1275
1409
|
SignIn,
|
|
1276
1410
|
SignUp,
|
|
1277
1411
|
VerifyEmail,
|
|
1278
1412
|
can,
|
|
1413
|
+
canAutofillPasskeys,
|
|
1279
1414
|
canUsePasskeys,
|
|
1280
1415
|
createAuthClient,
|
|
1281
1416
|
definePolicy,
|