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.d.mts
CHANGED
|
@@ -212,6 +212,12 @@ type AuthField = 'email' | 'password' | 'newPassword' | 'name' | 'code';
|
|
|
212
212
|
|
|
213
213
|
/** Whether this browser can use a passkey at all. */
|
|
214
214
|
declare function canUsePasskeys(): boolean;
|
|
215
|
+
/**
|
|
216
|
+
* Whether this browser offers passkeys in the autofill of a field marked
|
|
217
|
+
* `autocomplete="username webauthn"`, so the person picks one without
|
|
218
|
+
* looking for a button.
|
|
219
|
+
*/
|
|
220
|
+
declare function canAutofillPasskeys(): Promise<boolean>;
|
|
215
221
|
|
|
216
222
|
/** What went wrong, in a form the screen can show. */
|
|
217
223
|
interface AuthError {
|
|
@@ -426,6 +432,34 @@ interface AuthClient {
|
|
|
426
432
|
}): Promise<AuthResult<'signed_in', {
|
|
427
433
|
session: PublicSession;
|
|
428
434
|
}> | AuthResult<'redirected'>>;
|
|
435
|
+
/**
|
|
436
|
+
* Offers the person's passkeys in the browser's autofill of the email
|
|
437
|
+
* field, which must carry `autocomplete="username webauthn"`, and signs in
|
|
438
|
+
* with the one they pick. It waits until they pick one or `signal`
|
|
439
|
+
* aborts: abort it before calling `passkey` or sending the form.
|
|
440
|
+
* `cancelled`: aborted, or this browser or page cannot (then there is
|
|
441
|
+
* nothing to offer, and the passkey button still works).
|
|
442
|
+
*/
|
|
443
|
+
passkeyAutofill(options?: {
|
|
444
|
+
signal?: AbortSignal;
|
|
445
|
+
}): Promise<AuthResult<'signed_in', {
|
|
446
|
+
session: PublicSession;
|
|
447
|
+
}>>;
|
|
448
|
+
/**
|
|
449
|
+
* Google One Tap: Google's prompt over the page offers the Google account
|
|
450
|
+
* the browser already has, and signs in with it in one tap, as the Google
|
|
451
|
+
* sign-in does (the same account). Needs the workspace's Google sign-in,
|
|
452
|
+
* and the page's address among the Google client's authorized JavaScript
|
|
453
|
+
* origins. Waits until the person picks an account or closes the prompt
|
|
454
|
+
* (`cancelled`), or `signal` aborts. `one_tap_unavailable`: the workspace
|
|
455
|
+
* has no Google sign-in.
|
|
456
|
+
*/
|
|
457
|
+
googleOneTap(options?: {
|
|
458
|
+
signal?: AbortSignal;
|
|
459
|
+
context?: 'signin' | 'signup' | 'use';
|
|
460
|
+
}): Promise<AuthResult<'signed_in', {
|
|
461
|
+
session: PublicSession;
|
|
462
|
+
}>>;
|
|
429
463
|
/** Where `passkey` sends the browser when this page cannot use the passkey itself. */
|
|
430
464
|
passkeyUrl(options?: {
|
|
431
465
|
returnTo?: string;
|
|
@@ -623,6 +657,21 @@ interface AuthFormProps {
|
|
|
623
657
|
};
|
|
624
658
|
}
|
|
625
659
|
declare function SignIn(props: AuthFormProps): react.JSX.Element;
|
|
660
|
+
interface GoogleOneTapProps {
|
|
661
|
+
/** Where to go once signed in. Without it (and without `onSuccess`), the page stays and its session updates. */
|
|
662
|
+
returnTo?: string;
|
|
663
|
+
/** Called once signed in, instead of going to `returnTo`. */
|
|
664
|
+
onSuccess?: () => void;
|
|
665
|
+
/** The prompt's words: sign in (default), sign up, or use the application. */
|
|
666
|
+
context?: 'signin' | 'signup' | 'use';
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Google One Tap: put it on any page, and a person who is not signed in sees
|
|
670
|
+
* Google's prompt with the Google account the browser has; one tap signs them
|
|
671
|
+
* in. It draws nothing of its own, and shows nothing to somebody signed in or
|
|
672
|
+
* where the workspace has no Google sign-in.
|
|
673
|
+
*/
|
|
674
|
+
declare function GoogleOneTap(props: GoogleOneTapProps): null;
|
|
626
675
|
declare function SignUp(props: AuthFormProps): react.JSX.Element;
|
|
627
676
|
declare function ForgotPassword(props: AuthFormProps): react.JSX.Element;
|
|
628
677
|
/** The page a "choose a new password" link opens: `pages.resetPassword`. */
|
|
@@ -685,4 +734,4 @@ declare function useSession(): SessionContext;
|
|
|
685
734
|
*/
|
|
686
735
|
declare function useAuth(): AuthClient & Pick<SessionContext, 'session' | 'status' | 'refresh'>;
|
|
687
736
|
|
|
688
|
-
export { Account, type AccountClient, type Account$1 as AccountDetails, type AccountProps, type AccountSession, type AuthClient, type AuthError, type AuthFormProps, type AuthMethods, type AuthResult, type AuthTexts, CauthyProvider, type CauthyProviderProps, ForgotPassword, type JsonValue, type LinkedIdentity, type Passkey, type PermissionOf, type Policy, type PublicSession, ResetPassword, type RoleOf, type SessionContext, type SessionOrganization, type SessionUser, SignIn, SignUp, VerifyEmail, can, canUsePasskeys, createAuthClient, definePolicy, useAuth, useSession };
|
|
737
|
+
export { Account, type AccountClient, type Account$1 as AccountDetails, type AccountProps, type AccountSession, type AuthClient, type AuthError, type AuthFormProps, type AuthMethods, type AuthResult, type AuthTexts, CauthyProvider, type CauthyProviderProps, ForgotPassword, GoogleOneTap, type GoogleOneTapProps, type JsonValue, type LinkedIdentity, type Passkey, type PermissionOf, type Policy, type PublicSession, ResetPassword, type RoleOf, type SessionContext, type SessionOrganization, type SessionUser, SignIn, SignUp, VerifyEmail, can, canAutofillPasskeys, canUsePasskeys, createAuthClient, definePolicy, useAuth, useSession };
|
package/dist/react.d.ts
CHANGED
|
@@ -212,6 +212,12 @@ type AuthField = 'email' | 'password' | 'newPassword' | 'name' | 'code';
|
|
|
212
212
|
|
|
213
213
|
/** Whether this browser can use a passkey at all. */
|
|
214
214
|
declare function canUsePasskeys(): boolean;
|
|
215
|
+
/**
|
|
216
|
+
* Whether this browser offers passkeys in the autofill of a field marked
|
|
217
|
+
* `autocomplete="username webauthn"`, so the person picks one without
|
|
218
|
+
* looking for a button.
|
|
219
|
+
*/
|
|
220
|
+
declare function canAutofillPasskeys(): Promise<boolean>;
|
|
215
221
|
|
|
216
222
|
/** What went wrong, in a form the screen can show. */
|
|
217
223
|
interface AuthError {
|
|
@@ -426,6 +432,34 @@ interface AuthClient {
|
|
|
426
432
|
}): Promise<AuthResult<'signed_in', {
|
|
427
433
|
session: PublicSession;
|
|
428
434
|
}> | AuthResult<'redirected'>>;
|
|
435
|
+
/**
|
|
436
|
+
* Offers the person's passkeys in the browser's autofill of the email
|
|
437
|
+
* field, which must carry `autocomplete="username webauthn"`, and signs in
|
|
438
|
+
* with the one they pick. It waits until they pick one or `signal`
|
|
439
|
+
* aborts: abort it before calling `passkey` or sending the form.
|
|
440
|
+
* `cancelled`: aborted, or this browser or page cannot (then there is
|
|
441
|
+
* nothing to offer, and the passkey button still works).
|
|
442
|
+
*/
|
|
443
|
+
passkeyAutofill(options?: {
|
|
444
|
+
signal?: AbortSignal;
|
|
445
|
+
}): Promise<AuthResult<'signed_in', {
|
|
446
|
+
session: PublicSession;
|
|
447
|
+
}>>;
|
|
448
|
+
/**
|
|
449
|
+
* Google One Tap: Google's prompt over the page offers the Google account
|
|
450
|
+
* the browser already has, and signs in with it in one tap, as the Google
|
|
451
|
+
* sign-in does (the same account). Needs the workspace's Google sign-in,
|
|
452
|
+
* and the page's address among the Google client's authorized JavaScript
|
|
453
|
+
* origins. Waits until the person picks an account or closes the prompt
|
|
454
|
+
* (`cancelled`), or `signal` aborts. `one_tap_unavailable`: the workspace
|
|
455
|
+
* has no Google sign-in.
|
|
456
|
+
*/
|
|
457
|
+
googleOneTap(options?: {
|
|
458
|
+
signal?: AbortSignal;
|
|
459
|
+
context?: 'signin' | 'signup' | 'use';
|
|
460
|
+
}): Promise<AuthResult<'signed_in', {
|
|
461
|
+
session: PublicSession;
|
|
462
|
+
}>>;
|
|
429
463
|
/** Where `passkey` sends the browser when this page cannot use the passkey itself. */
|
|
430
464
|
passkeyUrl(options?: {
|
|
431
465
|
returnTo?: string;
|
|
@@ -623,6 +657,21 @@ interface AuthFormProps {
|
|
|
623
657
|
};
|
|
624
658
|
}
|
|
625
659
|
declare function SignIn(props: AuthFormProps): react.JSX.Element;
|
|
660
|
+
interface GoogleOneTapProps {
|
|
661
|
+
/** Where to go once signed in. Without it (and without `onSuccess`), the page stays and its session updates. */
|
|
662
|
+
returnTo?: string;
|
|
663
|
+
/** Called once signed in, instead of going to `returnTo`. */
|
|
664
|
+
onSuccess?: () => void;
|
|
665
|
+
/** The prompt's words: sign in (default), sign up, or use the application. */
|
|
666
|
+
context?: 'signin' | 'signup' | 'use';
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Google One Tap: put it on any page, and a person who is not signed in sees
|
|
670
|
+
* Google's prompt with the Google account the browser has; one tap signs them
|
|
671
|
+
* in. It draws nothing of its own, and shows nothing to somebody signed in or
|
|
672
|
+
* where the workspace has no Google sign-in.
|
|
673
|
+
*/
|
|
674
|
+
declare function GoogleOneTap(props: GoogleOneTapProps): null;
|
|
626
675
|
declare function SignUp(props: AuthFormProps): react.JSX.Element;
|
|
627
676
|
declare function ForgotPassword(props: AuthFormProps): react.JSX.Element;
|
|
628
677
|
/** The page a "choose a new password" link opens: `pages.resetPassword`. */
|
|
@@ -685,4 +734,4 @@ declare function useSession(): SessionContext;
|
|
|
685
734
|
*/
|
|
686
735
|
declare function useAuth(): AuthClient & Pick<SessionContext, 'session' | 'status' | 'refresh'>;
|
|
687
736
|
|
|
688
|
-
export { Account, type AccountClient, type Account$1 as AccountDetails, type AccountProps, type AccountSession, type AuthClient, type AuthError, type AuthFormProps, type AuthMethods, type AuthResult, type AuthTexts, CauthyProvider, type CauthyProviderProps, ForgotPassword, type JsonValue, type LinkedIdentity, type Passkey, type PermissionOf, type Policy, type PublicSession, ResetPassword, type RoleOf, type SessionContext, type SessionOrganization, type SessionUser, SignIn, SignUp, VerifyEmail, can, canUsePasskeys, createAuthClient, definePolicy, useAuth, useSession };
|
|
737
|
+
export { Account, type AccountClient, type Account$1 as AccountDetails, type AccountProps, type AccountSession, type AuthClient, type AuthError, type AuthFormProps, type AuthMethods, type AuthResult, type AuthTexts, CauthyProvider, type CauthyProviderProps, ForgotPassword, GoogleOneTap, type GoogleOneTapProps, type JsonValue, type LinkedIdentity, type Passkey, type PermissionOf, type Policy, type PublicSession, ResetPassword, type RoleOf, type SessionContext, type SessionOrganization, type SessionUser, SignIn, SignUp, VerifyEmail, can, canAutofillPasskeys, canUsePasskeys, createAuthClient, definePolicy, useAuth, useSession };
|
package/dist/react.js
CHANGED
|
@@ -24,11 +24,13 @@ __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,
|
|
32
34
|
canUsePasskeys: () => canUsePasskeys,
|
|
33
35
|
createAuthClient: () => createAuthClient,
|
|
34
36
|
definePolicy: () => definePolicy,
|
|
@@ -42,6 +44,15 @@ var import_react3 = require("react");
|
|
|
42
44
|
function canUsePasskeys() {
|
|
43
45
|
return typeof window !== "undefined" && typeof window.PublicKeyCredential === "function" && typeof navigator !== "undefined" && typeof navigator.credentials?.get === "function";
|
|
44
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
|
+
}
|
|
45
56
|
function bytes(text2) {
|
|
46
57
|
const b64 = text2.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(text2.length / 4) * 4, "=");
|
|
47
58
|
const bin = atob(b64);
|
|
@@ -57,9 +68,9 @@ function text(buf) {
|
|
|
57
68
|
return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
58
69
|
}
|
|
59
70
|
var withId = (list) => (list ?? []).map((c) => ({ ...c, id: bytes(c.id) }));
|
|
60
|
-
async function getPasskey(options) {
|
|
71
|
+
async function getPasskey(options, how = {}) {
|
|
61
72
|
const publicKey = { ...options, challenge: bytes(options.challenge), allowCredentials: withId(options.allowCredentials) };
|
|
62
|
-
const cred = await navigator.credentials.get({ publicKey });
|
|
73
|
+
const cred = await navigator.credentials.get({ publicKey, ...how });
|
|
63
74
|
if (cred === null) throw new DOMException("No passkey was chosen.", "NotAllowedError");
|
|
64
75
|
const r = cred.response;
|
|
65
76
|
return {
|
|
@@ -203,6 +214,24 @@ function firstProblem(...checks) {
|
|
|
203
214
|
}
|
|
204
215
|
|
|
205
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;
|
|
206
235
|
var UNAVAILABLE = { code: "unavailable", message: "Sign-in is unavailable right now. Try again in a moment." };
|
|
207
236
|
function createAuthClient(options = {}) {
|
|
208
237
|
const base = (options.basePath ?? "/api/auth").replace(/\/+$/, "");
|
|
@@ -286,6 +315,66 @@ function createAuthClient(options = {}) {
|
|
|
286
315
|
window.location.assign(passkeyUrl(o));
|
|
287
316
|
return { ok: true, status: "redirected" };
|
|
288
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);
|
|
377
|
+
},
|
|
289
378
|
socialUrl,
|
|
290
379
|
social(provider, o) {
|
|
291
380
|
window.location.assign(socialUrl(provider, o));
|
|
@@ -702,6 +791,8 @@ function SignIn(props) {
|
|
|
702
791
|
const [password, setPassword] = (0, import_react.useState)("");
|
|
703
792
|
const [code, setCode] = (0, import_react.useState)("");
|
|
704
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);
|
|
705
796
|
(0, import_react.useEffect)(() => {
|
|
706
797
|
void auth.methods().then(setMethods);
|
|
707
798
|
const back = returnedError();
|
|
@@ -716,26 +807,46 @@ function SignIn(props) {
|
|
|
716
807
|
setStep("password");
|
|
717
808
|
});
|
|
718
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]);
|
|
719
823
|
const submit = async (e) => {
|
|
720
824
|
e.preventDefault();
|
|
825
|
+
autofill.current?.abort();
|
|
721
826
|
f.setBusy(true);
|
|
722
827
|
f.setError(null);
|
|
723
828
|
const r = step === "code" ? await auth.signIn.mfa({ code }) : step === "sent" ? await auth.signIn.emailCode({ code }) : await auth.signIn.email({ email, password });
|
|
724
829
|
f.setBusy(false);
|
|
725
|
-
if (!r.ok)
|
|
830
|
+
if (!r.ok) {
|
|
831
|
+
setOffer((n) => n + 1);
|
|
832
|
+
return f.setError(r.error);
|
|
833
|
+
}
|
|
726
834
|
if (r.status === "mfa_required") return setStep("code");
|
|
727
835
|
f.done();
|
|
728
836
|
};
|
|
729
837
|
const usePasskey = async () => {
|
|
838
|
+
autofill.current?.abort();
|
|
730
839
|
f.setBusy(true);
|
|
731
840
|
f.setError(null);
|
|
732
841
|
const r = await auth.signIn.passkey({ returnTo: props.returnTo, locale: props.locale });
|
|
733
842
|
if (r.ok && r.status === "redirected") return;
|
|
734
843
|
f.setBusy(false);
|
|
844
|
+
if (!r.ok) setOffer((n) => n + 1);
|
|
735
845
|
if (!r.ok) return r.error.code === "cancelled" ? void 0 : f.setError(r.error);
|
|
736
846
|
f.done();
|
|
737
847
|
};
|
|
738
848
|
const sendLink = async () => {
|
|
849
|
+
autofill.current?.abort();
|
|
739
850
|
f.setBusy(true);
|
|
740
851
|
f.setError(null);
|
|
741
852
|
const r = await auth.signIn.magicLink({ email });
|
|
@@ -771,7 +882,7 @@ function SignIn(props) {
|
|
|
771
882
|
] }, p.id)),
|
|
772
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 }),
|
|
773
884
|
((methods?.providers.length ?? 0) > 0 || methods?.passkey) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: f.cx("separator"), children: f.t.orWithEmail }),
|
|
774
|
-
/* @__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 }),
|
|
775
886
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
776
887
|
Field,
|
|
777
888
|
{
|
|
@@ -795,6 +906,21 @@ function SignIn(props) {
|
|
|
795
906
|
] })
|
|
796
907
|
] });
|
|
797
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
|
+
}
|
|
798
924
|
function SignUp(props) {
|
|
799
925
|
const auth = useAuth();
|
|
800
926
|
const f = useForm(props);
|
|
@@ -1286,7 +1412,16 @@ function useAuth() {
|
|
|
1286
1412
|
};
|
|
1287
1413
|
return {
|
|
1288
1414
|
...client,
|
|
1289
|
-
signIn: {
|
|
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
|
+
},
|
|
1290
1425
|
signUp: { email: (f) => then(client.signUp.email(f)) },
|
|
1291
1426
|
signOut: () => then(client.signOut()),
|
|
1292
1427
|
account: {
|
|
@@ -1307,11 +1442,13 @@ function useAuth() {
|
|
|
1307
1442
|
Account,
|
|
1308
1443
|
CauthyProvider,
|
|
1309
1444
|
ForgotPassword,
|
|
1445
|
+
GoogleOneTap,
|
|
1310
1446
|
ResetPassword,
|
|
1311
1447
|
SignIn,
|
|
1312
1448
|
SignUp,
|
|
1313
1449
|
VerifyEmail,
|
|
1314
1450
|
can,
|
|
1451
|
+
canAutofillPasskeys,
|
|
1315
1452
|
canUsePasskeys,
|
|
1316
1453
|
createAuthClient,
|
|
1317
1454
|
definePolicy,
|