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.d.mts
CHANGED
|
@@ -210,6 +210,9 @@ interface PublicSession {
|
|
|
210
210
|
|
|
211
211
|
type AuthField = 'email' | 'password' | 'newPassword' | 'name' | 'code';
|
|
212
212
|
|
|
213
|
+
/** Whether this browser can use a passkey at all. */
|
|
214
|
+
declare function canUsePasskeys(): boolean;
|
|
215
|
+
|
|
213
216
|
/** What went wrong, in a form the screen can show. */
|
|
214
217
|
interface AuthError {
|
|
215
218
|
/** Stable, for translating: `invalid_credentials`, `invalid_email`, `too_short`, `email_taken`, `unavailable`… */
|
|
@@ -235,8 +238,29 @@ interface AuthMethods {
|
|
|
235
238
|
/** Whether people may create an account themselves. */
|
|
236
239
|
signUp: boolean;
|
|
237
240
|
magicLink: boolean;
|
|
238
|
-
/** Whether to offer a passkey.
|
|
241
|
+
/** Whether to offer a passkey. */
|
|
239
242
|
passkey: boolean;
|
|
243
|
+
/**
|
|
244
|
+
* Whether the passkey is used on this page itself. False: `signIn.passkey`
|
|
245
|
+
* sends the person to Cauthy's page for that step alone, and they come back
|
|
246
|
+
* signed in. That happens only when this page is outside the domain the
|
|
247
|
+
* workspace's passkeys belong to.
|
|
248
|
+
*/
|
|
249
|
+
passkeyHere: boolean;
|
|
250
|
+
/**
|
|
251
|
+
* Whether Cauthy's page can use the passkey, for that step alone: where a
|
|
252
|
+
* page that cannot, or a browser that refuses to here, sends the person.
|
|
253
|
+
*/
|
|
254
|
+
passkeyOnCauthy: boolean;
|
|
255
|
+
}
|
|
256
|
+
/** A passkey of the signed-in account. Moments are UTC. */
|
|
257
|
+
interface Passkey {
|
|
258
|
+
id: string;
|
|
259
|
+
/** What the person called it ("My laptop"), or "". */
|
|
260
|
+
name: string;
|
|
261
|
+
createdAt: string;
|
|
262
|
+
/** When it last signed the person in, or null. */
|
|
263
|
+
lastUsedAt: string | null;
|
|
240
264
|
}
|
|
241
265
|
/** A person's own account, as their account screen shows it. */
|
|
242
266
|
interface Account$1 {
|
|
@@ -340,6 +364,22 @@ interface AccountClient {
|
|
|
340
364
|
sessions(): Promise<AccountSession[]>;
|
|
341
365
|
/** Signs the account out everywhere, here too. */
|
|
342
366
|
signOutEverywhere(): Promise<AuthResult<'signed_out'>>;
|
|
367
|
+
/** The account's passkeys. */
|
|
368
|
+
passkeys(): Promise<Passkey[]>;
|
|
369
|
+
/**
|
|
370
|
+
* Makes a passkey on this device, on this page: the browser asks the person
|
|
371
|
+
* for their fingerprint, face or screen lock. An account with a password
|
|
372
|
+
* proves it again first. `cancelled`: the person closed the browser's prompt.
|
|
373
|
+
*/
|
|
374
|
+
addPasskey(form?: {
|
|
375
|
+
password?: string;
|
|
376
|
+
name?: string;
|
|
377
|
+
}): Promise<AuthResult<'added', {
|
|
378
|
+
passkey: Passkey;
|
|
379
|
+
}>>;
|
|
380
|
+
removePasskey(id: string): Promise<AuthResult<'removed', {
|
|
381
|
+
id: string;
|
|
382
|
+
}>>;
|
|
343
383
|
}
|
|
344
384
|
interface AuthClient {
|
|
345
385
|
signIn: {
|
|
@@ -372,12 +412,21 @@ interface AuthClient {
|
|
|
372
412
|
}): Promise<AuthResult<'signed_in', {
|
|
373
413
|
session: PublicSession;
|
|
374
414
|
}>>;
|
|
375
|
-
/**
|
|
415
|
+
/**
|
|
416
|
+
* Signs in with a passkey, on this page: the browser asks the person for
|
|
417
|
+
* their fingerprint, face or screen lock. When this page cannot use the
|
|
418
|
+
* workspace's passkeys (`methods().passkeyHere` is false), the browser goes
|
|
419
|
+
* to Cauthy's page for that step alone and comes back signed in, at
|
|
420
|
+
* `returnTo`: then the answer is `redirected`. `cancelled`: the person
|
|
421
|
+
* closed the browser's prompt.
|
|
422
|
+
*/
|
|
376
423
|
passkey(options?: {
|
|
377
424
|
returnTo?: string;
|
|
378
425
|
locale?: string;
|
|
379
|
-
}):
|
|
380
|
-
|
|
426
|
+
}): Promise<AuthResult<'signed_in', {
|
|
427
|
+
session: PublicSession;
|
|
428
|
+
}> | AuthResult<'redirected'>>;
|
|
429
|
+
/** Where `passkey` sends the browser when this page cannot use the passkey itself. */
|
|
381
430
|
passkeyUrl(options?: {
|
|
382
431
|
returnTo?: string;
|
|
383
432
|
locale?: string;
|
|
@@ -489,6 +538,13 @@ interface AuthTexts {
|
|
|
489
538
|
keepCodes: string;
|
|
490
539
|
passkeys: string;
|
|
491
540
|
managePasskeys: string;
|
|
541
|
+
passkeysHelp: string;
|
|
542
|
+
noPasskeys: string;
|
|
543
|
+
passkeyName: string;
|
|
544
|
+
addPasskey: string;
|
|
545
|
+
removePasskey: string;
|
|
546
|
+
passkeyAdded: string;
|
|
547
|
+
passkeyUnnamed: string;
|
|
492
548
|
sessions: string;
|
|
493
549
|
signOutEverywhere: string;
|
|
494
550
|
unknownDevice: string;
|
|
@@ -629,4 +685,4 @@ declare function useSession(): SessionContext;
|
|
|
629
685
|
*/
|
|
630
686
|
declare function useAuth(): AuthClient & Pick<SessionContext, 'session' | 'status' | 'refresh'>;
|
|
631
687
|
|
|
632
|
-
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 PermissionOf, type Policy, type PublicSession, ResetPassword, type RoleOf, type SessionContext, type SessionOrganization, type SessionUser, SignIn, SignUp, VerifyEmail, can, createAuthClient, definePolicy, useAuth, useSession };
|
|
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 };
|
package/dist/react.d.ts
CHANGED
|
@@ -210,6 +210,9 @@ interface PublicSession {
|
|
|
210
210
|
|
|
211
211
|
type AuthField = 'email' | 'password' | 'newPassword' | 'name' | 'code';
|
|
212
212
|
|
|
213
|
+
/** Whether this browser can use a passkey at all. */
|
|
214
|
+
declare function canUsePasskeys(): boolean;
|
|
215
|
+
|
|
213
216
|
/** What went wrong, in a form the screen can show. */
|
|
214
217
|
interface AuthError {
|
|
215
218
|
/** Stable, for translating: `invalid_credentials`, `invalid_email`, `too_short`, `email_taken`, `unavailable`… */
|
|
@@ -235,8 +238,29 @@ interface AuthMethods {
|
|
|
235
238
|
/** Whether people may create an account themselves. */
|
|
236
239
|
signUp: boolean;
|
|
237
240
|
magicLink: boolean;
|
|
238
|
-
/** Whether to offer a passkey.
|
|
241
|
+
/** Whether to offer a passkey. */
|
|
239
242
|
passkey: boolean;
|
|
243
|
+
/**
|
|
244
|
+
* Whether the passkey is used on this page itself. False: `signIn.passkey`
|
|
245
|
+
* sends the person to Cauthy's page for that step alone, and they come back
|
|
246
|
+
* signed in. That happens only when this page is outside the domain the
|
|
247
|
+
* workspace's passkeys belong to.
|
|
248
|
+
*/
|
|
249
|
+
passkeyHere: boolean;
|
|
250
|
+
/**
|
|
251
|
+
* Whether Cauthy's page can use the passkey, for that step alone: where a
|
|
252
|
+
* page that cannot, or a browser that refuses to here, sends the person.
|
|
253
|
+
*/
|
|
254
|
+
passkeyOnCauthy: boolean;
|
|
255
|
+
}
|
|
256
|
+
/** A passkey of the signed-in account. Moments are UTC. */
|
|
257
|
+
interface Passkey {
|
|
258
|
+
id: string;
|
|
259
|
+
/** What the person called it ("My laptop"), or "". */
|
|
260
|
+
name: string;
|
|
261
|
+
createdAt: string;
|
|
262
|
+
/** When it last signed the person in, or null. */
|
|
263
|
+
lastUsedAt: string | null;
|
|
240
264
|
}
|
|
241
265
|
/** A person's own account, as their account screen shows it. */
|
|
242
266
|
interface Account$1 {
|
|
@@ -340,6 +364,22 @@ interface AccountClient {
|
|
|
340
364
|
sessions(): Promise<AccountSession[]>;
|
|
341
365
|
/** Signs the account out everywhere, here too. */
|
|
342
366
|
signOutEverywhere(): Promise<AuthResult<'signed_out'>>;
|
|
367
|
+
/** The account's passkeys. */
|
|
368
|
+
passkeys(): Promise<Passkey[]>;
|
|
369
|
+
/**
|
|
370
|
+
* Makes a passkey on this device, on this page: the browser asks the person
|
|
371
|
+
* for their fingerprint, face or screen lock. An account with a password
|
|
372
|
+
* proves it again first. `cancelled`: the person closed the browser's prompt.
|
|
373
|
+
*/
|
|
374
|
+
addPasskey(form?: {
|
|
375
|
+
password?: string;
|
|
376
|
+
name?: string;
|
|
377
|
+
}): Promise<AuthResult<'added', {
|
|
378
|
+
passkey: Passkey;
|
|
379
|
+
}>>;
|
|
380
|
+
removePasskey(id: string): Promise<AuthResult<'removed', {
|
|
381
|
+
id: string;
|
|
382
|
+
}>>;
|
|
343
383
|
}
|
|
344
384
|
interface AuthClient {
|
|
345
385
|
signIn: {
|
|
@@ -372,12 +412,21 @@ interface AuthClient {
|
|
|
372
412
|
}): Promise<AuthResult<'signed_in', {
|
|
373
413
|
session: PublicSession;
|
|
374
414
|
}>>;
|
|
375
|
-
/**
|
|
415
|
+
/**
|
|
416
|
+
* Signs in with a passkey, on this page: the browser asks the person for
|
|
417
|
+
* their fingerprint, face or screen lock. When this page cannot use the
|
|
418
|
+
* workspace's passkeys (`methods().passkeyHere` is false), the browser goes
|
|
419
|
+
* to Cauthy's page for that step alone and comes back signed in, at
|
|
420
|
+
* `returnTo`: then the answer is `redirected`. `cancelled`: the person
|
|
421
|
+
* closed the browser's prompt.
|
|
422
|
+
*/
|
|
376
423
|
passkey(options?: {
|
|
377
424
|
returnTo?: string;
|
|
378
425
|
locale?: string;
|
|
379
|
-
}):
|
|
380
|
-
|
|
426
|
+
}): Promise<AuthResult<'signed_in', {
|
|
427
|
+
session: PublicSession;
|
|
428
|
+
}> | AuthResult<'redirected'>>;
|
|
429
|
+
/** Where `passkey` sends the browser when this page cannot use the passkey itself. */
|
|
381
430
|
passkeyUrl(options?: {
|
|
382
431
|
returnTo?: string;
|
|
383
432
|
locale?: string;
|
|
@@ -489,6 +538,13 @@ interface AuthTexts {
|
|
|
489
538
|
keepCodes: string;
|
|
490
539
|
passkeys: string;
|
|
491
540
|
managePasskeys: string;
|
|
541
|
+
passkeysHelp: string;
|
|
542
|
+
noPasskeys: string;
|
|
543
|
+
passkeyName: string;
|
|
544
|
+
addPasskey: string;
|
|
545
|
+
removePasskey: string;
|
|
546
|
+
passkeyAdded: string;
|
|
547
|
+
passkeyUnnamed: string;
|
|
492
548
|
sessions: string;
|
|
493
549
|
signOutEverywhere: string;
|
|
494
550
|
unknownDevice: string;
|
|
@@ -629,4 +685,4 @@ declare function useSession(): SessionContext;
|
|
|
629
685
|
*/
|
|
630
686
|
declare function useAuth(): AuthClient & Pick<SessionContext, 'session' | 'status' | 'refresh'>;
|
|
631
687
|
|
|
632
|
-
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 PermissionOf, type Policy, type PublicSession, ResetPassword, type RoleOf, type SessionContext, type SessionOrganization, type SessionUser, SignIn, SignUp, VerifyEmail, can, createAuthClient, definePolicy, useAuth, useSession };
|
|
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 };
|
package/dist/react.js
CHANGED
|
@@ -29,6 +29,7 @@ __export(react_exports, {
|
|
|
29
29
|
SignUp: () => SignUp,
|
|
30
30
|
VerifyEmail: () => VerifyEmail,
|
|
31
31
|
can: () => can,
|
|
32
|
+
canUsePasskeys: () => canUsePasskeys,
|
|
32
33
|
createAuthClient: () => createAuthClient,
|
|
33
34
|
definePolicy: () => definePolicy,
|
|
34
35
|
useAuth: () => useAuth,
|
|
@@ -37,6 +38,73 @@ __export(react_exports, {
|
|
|
37
38
|
module.exports = __toCommonJS(react_exports);
|
|
38
39
|
var import_react3 = require("react");
|
|
39
40
|
|
|
41
|
+
// src/passkey.ts
|
|
42
|
+
function canUsePasskeys() {
|
|
43
|
+
return typeof window !== "undefined" && typeof window.PublicKeyCredential === "function" && typeof navigator !== "undefined" && typeof navigator.credentials?.get === "function";
|
|
44
|
+
}
|
|
45
|
+
function bytes(text2) {
|
|
46
|
+
const b64 = text2.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(text2.length / 4) * 4, "=");
|
|
47
|
+
const bin = atob(b64);
|
|
48
|
+
const out = new Uint8Array(bin.length);
|
|
49
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
50
|
+
return out.buffer;
|
|
51
|
+
}
|
|
52
|
+
function text(buf) {
|
|
53
|
+
if (!buf) return null;
|
|
54
|
+
const view = new Uint8Array(buf);
|
|
55
|
+
let bin = "";
|
|
56
|
+
for (let i = 0; i < view.byteLength; i++) bin += String.fromCharCode(view[i]);
|
|
57
|
+
return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
58
|
+
}
|
|
59
|
+
var withId = (list) => (list ?? []).map((c) => ({ ...c, id: bytes(c.id) }));
|
|
60
|
+
async function getPasskey(options) {
|
|
61
|
+
const publicKey = { ...options, challenge: bytes(options.challenge), allowCredentials: withId(options.allowCredentials) };
|
|
62
|
+
const cred = await navigator.credentials.get({ publicKey });
|
|
63
|
+
if (cred === null) throw new DOMException("No passkey was chosen.", "NotAllowedError");
|
|
64
|
+
const r = cred.response;
|
|
65
|
+
return {
|
|
66
|
+
id: cred.id,
|
|
67
|
+
rawId: text(cred.rawId),
|
|
68
|
+
type: cred.type,
|
|
69
|
+
response: {
|
|
70
|
+
authenticatorData: text(r.authenticatorData),
|
|
71
|
+
clientDataJSON: text(r.clientDataJSON),
|
|
72
|
+
signature: text(r.signature),
|
|
73
|
+
userHandle: text(r.userHandle)
|
|
74
|
+
},
|
|
75
|
+
clientExtensionResults: cred.getClientExtensionResults?.() ?? {}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
async function makePasskey(options) {
|
|
79
|
+
const publicKey = {
|
|
80
|
+
...options,
|
|
81
|
+
challenge: bytes(options.challenge),
|
|
82
|
+
user: { ...options.user, id: bytes(options.user.id) },
|
|
83
|
+
excludeCredentials: withId(options.excludeCredentials)
|
|
84
|
+
};
|
|
85
|
+
const cred = await navigator.credentials.create({ publicKey });
|
|
86
|
+
if (cred === null) throw new DOMException("No passkey was made.", "NotAllowedError");
|
|
87
|
+
const r = cred.response;
|
|
88
|
+
return {
|
|
89
|
+
id: cred.id,
|
|
90
|
+
rawId: text(cred.rawId),
|
|
91
|
+
type: cred.type,
|
|
92
|
+
response: {
|
|
93
|
+
clientDataJSON: text(r.clientDataJSON),
|
|
94
|
+
attestationObject: text(r.attestationObject),
|
|
95
|
+
transports: typeof r.getTransports === "function" ? r.getTransports() : []
|
|
96
|
+
},
|
|
97
|
+
clientExtensionResults: cred.getClientExtensionResults?.() ?? {}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function ceremonyError(e) {
|
|
101
|
+
const name = e instanceof Error || e instanceof DOMException ? e.name : "";
|
|
102
|
+
if (name === "NotAllowedError" || name === "AbortError") return { code: "cancelled", message: "The passkey was not used. Try again when you are ready." };
|
|
103
|
+
if (name === "InvalidStateError") return { code: "passkey_exists", message: "This device already has a passkey for this account." };
|
|
104
|
+
if (name === "SecurityError") return { code: "passkey_unavailable", message: "Passkeys cannot be used on this page." };
|
|
105
|
+
return { code: "passkey_failed", message: "The passkey could not be used. Try again, or sign in another way." };
|
|
106
|
+
}
|
|
107
|
+
|
|
40
108
|
// src/theme.ts
|
|
41
109
|
function themeVariables(theme) {
|
|
42
110
|
const vars = {};
|
|
@@ -177,6 +245,28 @@ function createAuthClient(options = {}) {
|
|
|
177
245
|
if (o.locale) q.set("locale", o.locale);
|
|
178
246
|
return `${base}/login?${q}`;
|
|
179
247
|
};
|
|
248
|
+
async function methods() {
|
|
249
|
+
try {
|
|
250
|
+
const res = await doFetch(`${base}/methods`, { credentials: "same-origin", headers: { accept: "application/json" } });
|
|
251
|
+
if (res.ok) {
|
|
252
|
+
const m = await res.json();
|
|
253
|
+
return { providers: [], signUp: true, magicLink: false, passkey: false, ...m, passkeyHere: m.passkeyHere === true, passkeyOnCauthy: m.passkeyOnCauthy ?? m.passkey === true };
|
|
254
|
+
}
|
|
255
|
+
} catch {
|
|
256
|
+
}
|
|
257
|
+
return { providers: [], signUp: true, magicLink: false, passkey: false, passkeyHere: false, passkeyOnCauthy: false };
|
|
258
|
+
}
|
|
259
|
+
async function ceremony(begin, finish, form, run, more = {}) {
|
|
260
|
+
const begun = await post(begin, form, null);
|
|
261
|
+
if (!begun.ok) return begun;
|
|
262
|
+
let credential;
|
|
263
|
+
try {
|
|
264
|
+
credential = await run(begun.options);
|
|
265
|
+
} catch (e) {
|
|
266
|
+
return { ok: false, error: ceremonyError(e) };
|
|
267
|
+
}
|
|
268
|
+
return post(finish, { credential, ...more }, null);
|
|
269
|
+
}
|
|
180
270
|
return {
|
|
181
271
|
signIn: {
|
|
182
272
|
email: (f) => post("/sign-in/email", { email: f.email, password: f.password }, firstProblem(checkEmail(f.email), checkPassword(f.password))),
|
|
@@ -185,8 +275,16 @@ function createAuthClient(options = {}) {
|
|
|
185
275
|
magicLinkVerify: (f) => post("/sign-in/magic-link/verify", { token: f.token }, null),
|
|
186
276
|
emailCode: (f) => post("/sign-in/email-code", { code: f.code }, checkCode(f.code)),
|
|
187
277
|
passkeyUrl,
|
|
188
|
-
passkey(o) {
|
|
278
|
+
async passkey(o) {
|
|
279
|
+
const m = await methods();
|
|
280
|
+
if (m.passkeyHere && canUsePasskeys()) {
|
|
281
|
+
const r = await ceremony("/sign-in/passkey/begin", "/sign-in/passkey/finish", {}, getPasskey);
|
|
282
|
+
if (r.ok || r.error.code !== "passkey_unavailable" || !m.passkeyOnCauthy) return r;
|
|
283
|
+
} else if (!m.passkeyOnCauthy) {
|
|
284
|
+
return { ok: false, error: { code: "passkey_unavailable", message: "Passkeys cannot be used here." } };
|
|
285
|
+
}
|
|
189
286
|
window.location.assign(passkeyUrl(o));
|
|
287
|
+
return { ok: true, status: "redirected" };
|
|
190
288
|
},
|
|
191
289
|
socialUrl,
|
|
192
290
|
social(provider, o) {
|
|
@@ -230,7 +328,16 @@ function createAuthClient(options = {}) {
|
|
|
230
328
|
const body = await read("/me/sessions");
|
|
231
329
|
return body && Array.isArray(body.sessions) ? body.sessions : [];
|
|
232
330
|
},
|
|
233
|
-
signOutEverywhere: () => post("/me/sessions/end", {}, null)
|
|
331
|
+
signOutEverywhere: () => post("/me/sessions/end", {}, null),
|
|
332
|
+
async passkeys() {
|
|
333
|
+
const body = await read("/me/passkeys");
|
|
334
|
+
return body && Array.isArray(body.passkeys) ? body.passkeys : [];
|
|
335
|
+
},
|
|
336
|
+
addPasskey(f = {}) {
|
|
337
|
+
if (!canUsePasskeys()) return Promise.resolve({ ok: false, error: { code: "passkey_unavailable", message: "This browser cannot make passkeys." } });
|
|
338
|
+
return ceremony("/me/passkeys/add/begin", "/me/passkeys/add/finish", { password: f.password ?? "" }, makePasskey, { name: f.name ?? "" });
|
|
339
|
+
},
|
|
340
|
+
removePasskey: (id) => post("/me/passkeys/remove", { id }, null)
|
|
234
341
|
},
|
|
235
342
|
async getSession() {
|
|
236
343
|
try {
|
|
@@ -245,14 +352,7 @@ function createAuthClient(options = {}) {
|
|
|
245
352
|
looked ??= read("/look").then((wire) => wire && typeof wire === "object" ? fromWire(wire) : null);
|
|
246
353
|
return looked;
|
|
247
354
|
},
|
|
248
|
-
|
|
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
|
-
}
|
|
355
|
+
methods
|
|
256
356
|
};
|
|
257
357
|
}
|
|
258
358
|
|
|
@@ -359,6 +459,13 @@ var TEXTS = {
|
|
|
359
459
|
keepCodes: "Keep these codes somewhere safe. Each works once, and they are not shown again.",
|
|
360
460
|
passkeys: "Passkeys",
|
|
361
461
|
managePasskeys: "Add or remove a passkey",
|
|
462
|
+
passkeysHelp: "Sign in with your fingerprint, your face or your device's lock, without a password.",
|
|
463
|
+
noPasskeys: "No passkey yet.",
|
|
464
|
+
passkeyName: "A name for this device (optional)",
|
|
465
|
+
addPasskey: "Add a passkey on this device",
|
|
466
|
+
removePasskey: "Remove",
|
|
467
|
+
passkeyAdded: "The passkey was added. Next time, sign in with it.",
|
|
468
|
+
passkeyUnnamed: "Passkey",
|
|
362
469
|
sessions: "Where you are signed in",
|
|
363
470
|
signOutEverywhere: "Sign out everywhere",
|
|
364
471
|
unknownDevice: "Unknown device",
|
|
@@ -389,7 +496,10 @@ var TEXTS = {
|
|
|
389
496
|
wrong_password: "The current password is not right.",
|
|
390
497
|
step_up_required: "Enter your current password.",
|
|
391
498
|
weak_password: "Choose a stronger password.",
|
|
392
|
-
unauthenticated: "Sign in first."
|
|
499
|
+
unauthenticated: "Sign in first.",
|
|
500
|
+
passkey_failed: "The passkey could not be used. Try again, or sign in another way.",
|
|
501
|
+
passkey_exists: "This device already has a passkey for your account.",
|
|
502
|
+
passkey_unavailable: "Passkeys cannot be used here."
|
|
393
503
|
}
|
|
394
504
|
},
|
|
395
505
|
es: {
|
|
@@ -438,6 +548,13 @@ var TEXTS = {
|
|
|
438
548
|
keepCodes: "Guarda estos c\xF3digos en un lugar seguro. Cada uno sirve una vez y no se vuelven a mostrar.",
|
|
439
549
|
passkeys: "Llaves de acceso",
|
|
440
550
|
managePasskeys: "Agregar o quitar una llave de acceso",
|
|
551
|
+
passkeysHelp: "Entra con tu huella, tu rostro o el bloqueo de tu dispositivo, sin contrase\xF1a.",
|
|
552
|
+
noPasskeys: "Todav\xEDa no tienes llaves de acceso.",
|
|
553
|
+
passkeyName: "Un nombre para este dispositivo (opcional)",
|
|
554
|
+
addPasskey: "Agregar una llave en este dispositivo",
|
|
555
|
+
removePasskey: "Quitar",
|
|
556
|
+
passkeyAdded: "La llave de acceso qued\xF3 agregada. La pr\xF3xima vez, entra con ella.",
|
|
557
|
+
passkeyUnnamed: "Llave de acceso",
|
|
441
558
|
sessions: "D\xF3nde tienes la sesi\xF3n abierta",
|
|
442
559
|
signOutEverywhere: "Cerrar sesi\xF3n en todas partes",
|
|
443
560
|
unknownDevice: "Dispositivo desconocido",
|
|
@@ -468,7 +585,10 @@ var TEXTS = {
|
|
|
468
585
|
wrong_password: "La contrase\xF1a actual no es correcta.",
|
|
469
586
|
step_up_required: "Escribe tu contrase\xF1a actual.",
|
|
470
587
|
weak_password: "Elige una contrase\xF1a m\xE1s fuerte.",
|
|
471
|
-
unauthenticated: "Inicia sesi\xF3n primero."
|
|
588
|
+
unauthenticated: "Inicia sesi\xF3n primero.",
|
|
589
|
+
passkey_failed: "No se pudo usar la llave de acceso. Intenta de nuevo o entra de otra forma.",
|
|
590
|
+
passkey_exists: "Este dispositivo ya tiene una llave de acceso de tu cuenta.",
|
|
591
|
+
passkey_unavailable: "Aqu\xED no se pueden usar llaves de acceso."
|
|
472
592
|
}
|
|
473
593
|
}
|
|
474
594
|
};
|
|
@@ -606,6 +726,15 @@ function SignIn(props) {
|
|
|
606
726
|
if (r.status === "mfa_required") return setStep("code");
|
|
607
727
|
f.done();
|
|
608
728
|
};
|
|
729
|
+
const usePasskey = async () => {
|
|
730
|
+
f.setBusy(true);
|
|
731
|
+
f.setError(null);
|
|
732
|
+
const r = await auth.signIn.passkey({ returnTo: props.returnTo, locale: props.locale });
|
|
733
|
+
if (r.ok && r.status === "redirected") return;
|
|
734
|
+
f.setBusy(false);
|
|
735
|
+
if (!r.ok) return r.error.code === "cancelled" ? void 0 : f.setError(r.error);
|
|
736
|
+
f.done();
|
|
737
|
+
};
|
|
609
738
|
const sendLink = async () => {
|
|
610
739
|
f.setBusy(true);
|
|
611
740
|
f.setError(null);
|
|
@@ -640,7 +769,7 @@ function SignIn(props) {
|
|
|
640
769
|
" ",
|
|
641
770
|
p.name
|
|
642
771
|
] }, p.id)),
|
|
643
|
-
methods?.passkey && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "button", className: f.cx("providerButton"), onClick: () =>
|
|
772
|
+
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
773
|
((methods?.providers.length ?? 0) > 0 || methods?.passkey) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: f.cx("separator"), children: f.t.orWithEmail }),
|
|
645
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 }),
|
|
646
775
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
@@ -826,10 +955,7 @@ function Account(props) {
|
|
|
826
955
|
account.hasPassword && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PasswordPart, { props }),
|
|
827
956
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(LinkedPart, { props }),
|
|
828
957
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SecondStepPart, { props, account, onChanged: (mfaEnabled) => setAccount({ ...account, mfaEnabled }) }),
|
|
829
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
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
|
-
] }),
|
|
958
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PasskeysPart, { props, account, accountUrl: accountUrl(), when }),
|
|
833
959
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: cx("section"), children: [
|
|
834
960
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: cx("heading"), children: t.sessions }),
|
|
835
961
|
/* @__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 +969,60 @@ function Account(props) {
|
|
|
843
969
|
] })
|
|
844
970
|
] });
|
|
845
971
|
}
|
|
972
|
+
function PasskeysPart({ props, account, accountUrl, when }) {
|
|
973
|
+
const auth = useAuth();
|
|
974
|
+
const { f, done, setDone, run, general } = useSection(props);
|
|
975
|
+
const [here, setHere] = (0, import_react.useState)(null);
|
|
976
|
+
const [list, setList] = (0, import_react.useState)([]);
|
|
977
|
+
const [name, setName] = (0, import_react.useState)("");
|
|
978
|
+
const [password, setPassword] = (0, import_react.useState)("");
|
|
979
|
+
(0, import_react.useEffect)(() => {
|
|
980
|
+
void auth.methods().then((m) => {
|
|
981
|
+
const can2 = m.passkeyHere && canUsePasskeys();
|
|
982
|
+
setHere(can2);
|
|
983
|
+
if (can2) void auth.account.passkeys().then(setList);
|
|
984
|
+
});
|
|
985
|
+
}, []);
|
|
986
|
+
if (here === null) return null;
|
|
987
|
+
if (!here) {
|
|
988
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: f.cx("section"), children: [
|
|
989
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: f.cx("heading"), children: f.t.passkeys }),
|
|
990
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { className: f.cx("link"), href: accountUrl, children: f.t.managePasskeys })
|
|
991
|
+
] });
|
|
992
|
+
}
|
|
993
|
+
const add = async (e) => {
|
|
994
|
+
e.preventDefault();
|
|
995
|
+
const r = await run(() => auth.account.addPasskey({ name, password }));
|
|
996
|
+
if (!r.ok) {
|
|
997
|
+
if (r.error.code === "cancelled") f.setError(null);
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
setList([...list, r.passkey]);
|
|
1001
|
+
setName("");
|
|
1002
|
+
setPassword("");
|
|
1003
|
+
setDone(f.t.passkeyAdded);
|
|
1004
|
+
};
|
|
1005
|
+
const remove = async (id) => {
|
|
1006
|
+
const r = await run(() => auth.account.removePasskey(id));
|
|
1007
|
+
if (r.ok) setList(list.filter((p) => p.id !== id));
|
|
1008
|
+
};
|
|
1009
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { className: f.cx("section"), onSubmit: add, noValidate: true, children: [
|
|
1010
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: f.cx("heading"), children: f.t.passkeys }),
|
|
1011
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: f.cx("notice"), children: f.t.passkeysHelp }),
|
|
1012
|
+
general && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "alert", className: f.cx("error"), children: f.say(general) }),
|
|
1013
|
+
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: [
|
|
1014
|
+
p.name || f.t.passkeyUnnamed,
|
|
1015
|
+
" \xB7 ",
|
|
1016
|
+
when(p.createdAt),
|
|
1017
|
+
" ",
|
|
1018
|
+
/* @__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 })
|
|
1019
|
+
] }, p.id)) }),
|
|
1020
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "name", type: "text", label: f.t.passkeyName, value: name, autoComplete: "off", onChange: setName }),
|
|
1021
|
+
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 }),
|
|
1022
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "submit", disabled: f.busy, className: f.cx("button"), children: f.t.addPasskey }),
|
|
1023
|
+
done && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "status", className: f.cx("notice"), children: done })
|
|
1024
|
+
] });
|
|
1025
|
+
}
|
|
846
1026
|
function ProfilePart({ props, account, onSaved }) {
|
|
847
1027
|
const auth = useAuth();
|
|
848
1028
|
const { f, done, setDone, run, general } = useSection(props);
|
|
@@ -1132,6 +1312,7 @@ function useAuth() {
|
|
|
1132
1312
|
SignUp,
|
|
1133
1313
|
VerifyEmail,
|
|
1134
1314
|
can,
|
|
1315
|
+
canUsePasskeys,
|
|
1135
1316
|
createAuthClient,
|
|
1136
1317
|
definePolicy,
|
|
1137
1318
|
useAuth,
|