@better-auth/passkey 1.5.7-beta.1 → 1.6.0-beta.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 +91 -35
- package/dist/client.mjs +45 -14
- package/dist/{index-BfRDyiNp.d.mts → index-BzKpmgHh.d.mts} +82 -3
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +255 -176
- package/dist/{error-codes-BwAsYefH.mjs → version-B7zkjZSd.mjs} +8 -4
- package/package.json +9 -8
- package/dist/client.mjs.map +0 -1
- package/dist/error-codes-BwAsYefH.mjs.map +0 -1
- package/dist/index.mjs.map +0 -1
package/dist/client.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as PasskeyExtensionsResolver, c as PasskeyRegistrationUser, i as PasskeyAuthenticationOptions, l as WebAuthnChallengeValue, n as PASSKEY_ERROR_CODES, o as PasskeyOptions, r as Passkey, s as PasskeyRegistrationOptions, t as passkey } from "./index-BzKpmgHh.mjs";
|
|
2
|
+
import { AuthenticationExtensionsClientInputs, AuthenticationExtensionsClientOutputs, AuthenticationResponseJSON, RegistrationResponseJSON } from "@simplewebauthn/server";
|
|
2
3
|
import * as better_auth_client0 from "better-auth/client";
|
|
3
4
|
import * as nanostores from "nanostores";
|
|
4
5
|
import { atom } from "nanostores";
|
|
@@ -22,6 +23,8 @@ declare const getPasskeyActions: ($fetch: BetterFetch, {
|
|
|
22
23
|
*/
|
|
23
24
|
passkey: (opts?: {
|
|
24
25
|
autoFill?: boolean;
|
|
26
|
+
extensions?: AuthenticationExtensionsClientInputs;
|
|
27
|
+
returnWebAuthnResponse?: boolean;
|
|
25
28
|
fetchOptions?: ClientFetchOption;
|
|
26
29
|
} | undefined, options?: ClientFetchOption | undefined) => Promise<{
|
|
27
30
|
data: null;
|
|
@@ -36,11 +39,32 @@ declare const getPasskeyActions: ($fetch: BetterFetch, {
|
|
|
36
39
|
user: User;
|
|
37
40
|
};
|
|
38
41
|
error: null;
|
|
42
|
+
} | {
|
|
43
|
+
webauthn: {
|
|
44
|
+
response: AuthenticationResponseJSON;
|
|
45
|
+
clientExtensionResults: AuthenticationExtensionsClientOutputs;
|
|
46
|
+
};
|
|
47
|
+
data: null;
|
|
48
|
+
error: {
|
|
49
|
+
message?: string | undefined;
|
|
50
|
+
status: number;
|
|
51
|
+
statusText: string;
|
|
52
|
+
};
|
|
53
|
+
} | {
|
|
54
|
+
webauthn: {
|
|
55
|
+
response: AuthenticationResponseJSON;
|
|
56
|
+
clientExtensionResults: AuthenticationExtensionsClientOutputs;
|
|
57
|
+
};
|
|
58
|
+
data: {
|
|
59
|
+
session: Session;
|
|
60
|
+
user: User;
|
|
61
|
+
};
|
|
62
|
+
error: null;
|
|
39
63
|
} | {
|
|
40
64
|
data: null;
|
|
41
65
|
error: {
|
|
42
66
|
code: string;
|
|
43
|
-
message:
|
|
67
|
+
message: string;
|
|
44
68
|
status: number;
|
|
45
69
|
statusText: string;
|
|
46
70
|
};
|
|
@@ -62,12 +86,24 @@ declare const getPasskeyActions: ($fetch: BetterFetch, {
|
|
|
62
86
|
* platform and cross-platform allowed, with platform preferred.
|
|
63
87
|
*/
|
|
64
88
|
authenticatorAttachment?: "platform" | "cross-platform";
|
|
89
|
+
/**
|
|
90
|
+
* Optional context for passkey-first registration flows.
|
|
91
|
+
*/
|
|
92
|
+
context?: string | null;
|
|
93
|
+
/**
|
|
94
|
+
* Optional WebAuthn extensions to include during registration.
|
|
95
|
+
*/
|
|
96
|
+
extensions?: AuthenticationExtensionsClientInputs;
|
|
65
97
|
/**
|
|
66
98
|
* Try to silently create a passkey with the password manager that the user just signed
|
|
67
99
|
* in with.
|
|
68
100
|
* @default false
|
|
69
101
|
*/
|
|
70
102
|
useAutoRegister?: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Return WebAuthn response and extension results.
|
|
105
|
+
*/
|
|
106
|
+
returnWebAuthnResponse?: boolean;
|
|
71
107
|
} | undefined, fetchOpts?: ClientFetchOption | undefined) => Promise<{
|
|
72
108
|
data: null;
|
|
73
109
|
error: {
|
|
@@ -79,26 +115,17 @@ declare const getPasskeyActions: ($fetch: BetterFetch, {
|
|
|
79
115
|
data: Passkey;
|
|
80
116
|
error: null;
|
|
81
117
|
} | {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
message: better_auth0.RawError<"PREVIOUSLY_REGISTERED">;
|
|
86
|
-
status: number;
|
|
87
|
-
statusText: string;
|
|
88
|
-
};
|
|
89
|
-
} | {
|
|
90
|
-
data: null;
|
|
91
|
-
error: {
|
|
92
|
-
code: "ERROR_CEREMONY_ABORTED";
|
|
93
|
-
message: better_auth0.RawError<"REGISTRATION_CANCELLED">;
|
|
94
|
-
status: number;
|
|
95
|
-
statusText: string;
|
|
118
|
+
webauthn: {
|
|
119
|
+
response: RegistrationResponseJSON;
|
|
120
|
+
clientExtensionResults: AuthenticationExtensionsClientOutputs;
|
|
96
121
|
};
|
|
122
|
+
data: Passkey;
|
|
123
|
+
error: null;
|
|
97
124
|
} | {
|
|
98
125
|
data: null;
|
|
99
126
|
error: {
|
|
100
127
|
code: string;
|
|
101
|
-
message: string
|
|
128
|
+
message: string;
|
|
102
129
|
status: number;
|
|
103
130
|
statusText: string;
|
|
104
131
|
};
|
|
@@ -113,6 +140,7 @@ declare const getPasskeyActions: ($fetch: BetterFetch, {
|
|
|
113
140
|
};
|
|
114
141
|
declare const passkeyClient: () => {
|
|
115
142
|
id: "passkey";
|
|
143
|
+
version: string;
|
|
116
144
|
$InferServerPlugin: ReturnType<typeof passkey>;
|
|
117
145
|
getActions: ($fetch: BetterFetch, $store: ClientStore) => {
|
|
118
146
|
signIn: {
|
|
@@ -121,6 +149,8 @@ declare const passkeyClient: () => {
|
|
|
121
149
|
*/
|
|
122
150
|
passkey: (opts?: {
|
|
123
151
|
autoFill?: boolean;
|
|
152
|
+
extensions?: AuthenticationExtensionsClientInputs;
|
|
153
|
+
returnWebAuthnResponse?: boolean;
|
|
124
154
|
fetchOptions?: ClientFetchOption;
|
|
125
155
|
} | undefined, options?: ClientFetchOption | undefined) => Promise<{
|
|
126
156
|
data: null;
|
|
@@ -135,11 +165,32 @@ declare const passkeyClient: () => {
|
|
|
135
165
|
user: User;
|
|
136
166
|
};
|
|
137
167
|
error: null;
|
|
168
|
+
} | {
|
|
169
|
+
webauthn: {
|
|
170
|
+
response: AuthenticationResponseJSON;
|
|
171
|
+
clientExtensionResults: AuthenticationExtensionsClientOutputs;
|
|
172
|
+
};
|
|
173
|
+
data: null;
|
|
174
|
+
error: {
|
|
175
|
+
message?: string | undefined;
|
|
176
|
+
status: number;
|
|
177
|
+
statusText: string;
|
|
178
|
+
};
|
|
179
|
+
} | {
|
|
180
|
+
webauthn: {
|
|
181
|
+
response: AuthenticationResponseJSON;
|
|
182
|
+
clientExtensionResults: AuthenticationExtensionsClientOutputs;
|
|
183
|
+
};
|
|
184
|
+
data: {
|
|
185
|
+
session: Session;
|
|
186
|
+
user: User;
|
|
187
|
+
};
|
|
188
|
+
error: null;
|
|
138
189
|
} | {
|
|
139
190
|
data: null;
|
|
140
191
|
error: {
|
|
141
192
|
code: string;
|
|
142
|
-
message:
|
|
193
|
+
message: string;
|
|
143
194
|
status: number;
|
|
144
195
|
statusText: string;
|
|
145
196
|
};
|
|
@@ -161,12 +212,24 @@ declare const passkeyClient: () => {
|
|
|
161
212
|
* platform and cross-platform allowed, with platform preferred.
|
|
162
213
|
*/
|
|
163
214
|
authenticatorAttachment?: "platform" | "cross-platform";
|
|
215
|
+
/**
|
|
216
|
+
* Optional context for passkey-first registration flows.
|
|
217
|
+
*/
|
|
218
|
+
context?: string | null;
|
|
219
|
+
/**
|
|
220
|
+
* Optional WebAuthn extensions to include during registration.
|
|
221
|
+
*/
|
|
222
|
+
extensions?: AuthenticationExtensionsClientInputs;
|
|
164
223
|
/**
|
|
165
224
|
* Try to silently create a passkey with the password manager that the user just signed
|
|
166
225
|
* in with.
|
|
167
226
|
* @default false
|
|
168
227
|
*/
|
|
169
228
|
useAutoRegister?: boolean;
|
|
229
|
+
/**
|
|
230
|
+
* Return WebAuthn response and extension results.
|
|
231
|
+
*/
|
|
232
|
+
returnWebAuthnResponse?: boolean;
|
|
170
233
|
} | undefined, fetchOpts?: ClientFetchOption | undefined) => Promise<{
|
|
171
234
|
data: null;
|
|
172
235
|
error: {
|
|
@@ -178,26 +241,17 @@ declare const passkeyClient: () => {
|
|
|
178
241
|
data: Passkey;
|
|
179
242
|
error: null;
|
|
180
243
|
} | {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
message: better_auth0.RawError<"PREVIOUSLY_REGISTERED">;
|
|
185
|
-
status: number;
|
|
186
|
-
statusText: string;
|
|
187
|
-
};
|
|
188
|
-
} | {
|
|
189
|
-
data: null;
|
|
190
|
-
error: {
|
|
191
|
-
code: "ERROR_CEREMONY_ABORTED";
|
|
192
|
-
message: better_auth0.RawError<"REGISTRATION_CANCELLED">;
|
|
193
|
-
status: number;
|
|
194
|
-
statusText: string;
|
|
244
|
+
webauthn: {
|
|
245
|
+
response: RegistrationResponseJSON;
|
|
246
|
+
clientExtensionResults: AuthenticationExtensionsClientOutputs;
|
|
195
247
|
};
|
|
248
|
+
data: Passkey;
|
|
249
|
+
error: null;
|
|
196
250
|
} | {
|
|
197
251
|
data: null;
|
|
198
252
|
error: {
|
|
199
253
|
code: string;
|
|
200
|
-
message: string
|
|
254
|
+
message: string;
|
|
201
255
|
status: number;
|
|
202
256
|
statusText: string;
|
|
203
257
|
};
|
|
@@ -237,8 +291,10 @@ declare const passkeyClient: () => {
|
|
|
237
291
|
REGISTRATION_CANCELLED: better_auth0.RawError<"REGISTRATION_CANCELLED">;
|
|
238
292
|
AUTH_CANCELLED: better_auth0.RawError<"AUTH_CANCELLED">;
|
|
239
293
|
UNKNOWN_ERROR: better_auth0.RawError<"UNKNOWN_ERROR">;
|
|
294
|
+
SESSION_REQUIRED: better_auth0.RawError<"SESSION_REQUIRED">;
|
|
295
|
+
RESOLVE_USER_REQUIRED: better_auth0.RawError<"RESOLVE_USER_REQUIRED">;
|
|
296
|
+
RESOLVED_USER_INVALID: better_auth0.RawError<"RESOLVED_USER_INVALID">;
|
|
240
297
|
};
|
|
241
298
|
};
|
|
242
299
|
//#endregion
|
|
243
|
-
export { PASSKEY_ERROR_CODES, Passkey, PasskeyOptions, WebAuthnChallengeValue, getPasskeyActions, passkeyClient };
|
|
244
|
-
//# sourceMappingURL=client.d.mts.map
|
|
300
|
+
export { PASSKEY_ERROR_CODES, Passkey, PasskeyAuthenticationOptions, PasskeyExtensionsResolver, PasskeyOptions, PasskeyRegistrationOptions, PasskeyRegistrationUser, WebAuthnChallengeValue, getPasskeyActions, passkeyClient };
|
package/dist/client.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as PASSKEY_ERROR_CODES, t as PACKAGE_VERSION } from "./version-B7zkjZSd.mjs";
|
|
2
2
|
import { WebAuthnError, startAuthentication, startRegistration } from "@simplewebauthn/browser";
|
|
3
3
|
import { useAuthQuery } from "better-auth/client";
|
|
4
4
|
import { atom } from "nanostores";
|
|
@@ -11,11 +11,20 @@ const getPasskeyActions = ($fetch, { $listPasskeys, $store }) => {
|
|
|
11
11
|
});
|
|
12
12
|
if (!response.data) return response;
|
|
13
13
|
try {
|
|
14
|
+
const mergedExtensions = response.data.extensions || opts?.extensions ? {
|
|
15
|
+
...response.data.extensions || {},
|
|
16
|
+
...opts?.extensions || {}
|
|
17
|
+
} : void 0;
|
|
18
|
+
const res = await startAuthentication({
|
|
19
|
+
optionsJSON: {
|
|
20
|
+
...response.data,
|
|
21
|
+
extensions: mergedExtensions
|
|
22
|
+
},
|
|
23
|
+
useBrowserAutofill: opts?.autoFill
|
|
24
|
+
});
|
|
25
|
+
const { clientExtensionResults, ...responseBody } = res;
|
|
14
26
|
const verified = await $fetch("/passkey/verify-authentication", {
|
|
15
|
-
body: { response:
|
|
16
|
-
optionsJSON: response.data,
|
|
17
|
-
useBrowserAutofill: opts?.autoFill
|
|
18
|
-
}) },
|
|
27
|
+
body: { response: responseBody },
|
|
19
28
|
...opts?.fetchOptions,
|
|
20
29
|
...options,
|
|
21
30
|
method: "POST",
|
|
@@ -23,6 +32,13 @@ const getPasskeyActions = ($fetch, { $listPasskeys, $store }) => {
|
|
|
23
32
|
});
|
|
24
33
|
$listPasskeys.set(Math.random());
|
|
25
34
|
$store.notify("$sessionSignal");
|
|
35
|
+
if (opts?.returnWebAuthnResponse) return {
|
|
36
|
+
...verified,
|
|
37
|
+
webauthn: {
|
|
38
|
+
response: res,
|
|
39
|
+
clientExtensionResults
|
|
40
|
+
}
|
|
41
|
+
};
|
|
26
42
|
return verified;
|
|
27
43
|
} catch (err) {
|
|
28
44
|
console.error(`[Better Auth] Error verifying passkey`, err);
|
|
@@ -30,7 +46,7 @@ const getPasskeyActions = ($fetch, { $listPasskeys, $store }) => {
|
|
|
30
46
|
data: null,
|
|
31
47
|
error: {
|
|
32
48
|
code: "AUTH_CANCELLED",
|
|
33
|
-
message: PASSKEY_ERROR_CODES.AUTH_CANCELLED,
|
|
49
|
+
message: PASSKEY_ERROR_CODES.AUTH_CANCELLED.message,
|
|
34
50
|
status: 400,
|
|
35
51
|
statusText: "BAD_REQUEST"
|
|
36
52
|
}
|
|
@@ -42,21 +58,30 @@ const getPasskeyActions = ($fetch, { $listPasskeys, $store }) => {
|
|
|
42
58
|
method: "GET",
|
|
43
59
|
query: {
|
|
44
60
|
...opts?.authenticatorAttachment && { authenticatorAttachment: opts.authenticatorAttachment },
|
|
45
|
-
...opts?.name && { name: opts.name }
|
|
61
|
+
...opts?.name && { name: opts.name },
|
|
62
|
+
...opts?.context && { context: opts.context }
|
|
46
63
|
},
|
|
47
64
|
throw: false
|
|
48
65
|
});
|
|
49
66
|
if (!options.data) return options;
|
|
50
67
|
try {
|
|
68
|
+
const mergedExtensions = options.data.extensions || opts?.extensions ? {
|
|
69
|
+
...options.data.extensions || {},
|
|
70
|
+
...opts?.extensions || {}
|
|
71
|
+
} : void 0;
|
|
51
72
|
const res = await startRegistration({
|
|
52
|
-
optionsJSON:
|
|
73
|
+
optionsJSON: {
|
|
74
|
+
...options.data,
|
|
75
|
+
extensions: mergedExtensions
|
|
76
|
+
},
|
|
53
77
|
useAutoRegister: opts?.useAutoRegister
|
|
54
78
|
});
|
|
79
|
+
const { clientExtensionResults, ...responseBody } = res;
|
|
55
80
|
const verified = await $fetch("/passkey/verify-registration", {
|
|
56
81
|
...opts?.fetchOptions,
|
|
57
82
|
...fetchOpts,
|
|
58
83
|
body: {
|
|
59
|
-
response:
|
|
84
|
+
response: responseBody,
|
|
60
85
|
name: opts?.name
|
|
61
86
|
},
|
|
62
87
|
method: "POST",
|
|
@@ -64,6 +89,13 @@ const getPasskeyActions = ($fetch, { $listPasskeys, $store }) => {
|
|
|
64
89
|
});
|
|
65
90
|
if (!verified.data) return verified;
|
|
66
91
|
$listPasskeys.set(Math.random());
|
|
92
|
+
if (opts?.returnWebAuthnResponse) return {
|
|
93
|
+
...verified,
|
|
94
|
+
webauthn: {
|
|
95
|
+
response: res,
|
|
96
|
+
clientExtensionResults
|
|
97
|
+
}
|
|
98
|
+
};
|
|
67
99
|
return verified;
|
|
68
100
|
} catch (e) {
|
|
69
101
|
if (e instanceof WebAuthnError) {
|
|
@@ -71,7 +103,7 @@ const getPasskeyActions = ($fetch, { $listPasskeys, $store }) => {
|
|
|
71
103
|
data: null,
|
|
72
104
|
error: {
|
|
73
105
|
code: e.code,
|
|
74
|
-
message: PASSKEY_ERROR_CODES.PREVIOUSLY_REGISTERED,
|
|
106
|
+
message: PASSKEY_ERROR_CODES.PREVIOUSLY_REGISTERED.message,
|
|
75
107
|
status: 400,
|
|
76
108
|
statusText: "BAD_REQUEST"
|
|
77
109
|
}
|
|
@@ -80,7 +112,7 @@ const getPasskeyActions = ($fetch, { $listPasskeys, $store }) => {
|
|
|
80
112
|
data: null,
|
|
81
113
|
error: {
|
|
82
114
|
code: e.code,
|
|
83
|
-
message: PASSKEY_ERROR_CODES.REGISTRATION_CANCELLED,
|
|
115
|
+
message: PASSKEY_ERROR_CODES.REGISTRATION_CANCELLED.message,
|
|
84
116
|
status: 400,
|
|
85
117
|
statusText: "BAD_REQUEST"
|
|
86
118
|
}
|
|
@@ -99,7 +131,7 @@ const getPasskeyActions = ($fetch, { $listPasskeys, $store }) => {
|
|
|
99
131
|
data: null,
|
|
100
132
|
error: {
|
|
101
133
|
code: "UNKNOWN_ERROR",
|
|
102
|
-
message: e instanceof Error ? e.message : PASSKEY_ERROR_CODES.UNKNOWN_ERROR,
|
|
134
|
+
message: e instanceof Error ? e.message : PASSKEY_ERROR_CODES.UNKNOWN_ERROR.message,
|
|
103
135
|
status: 500,
|
|
104
136
|
statusText: "INTERNAL_SERVER_ERROR"
|
|
105
137
|
}
|
|
@@ -116,6 +148,7 @@ const passkeyClient = () => {
|
|
|
116
148
|
const $listPasskeys = atom();
|
|
117
149
|
return {
|
|
118
150
|
id: "passkey",
|
|
151
|
+
version: PACKAGE_VERSION,
|
|
119
152
|
$InferServerPlugin: {},
|
|
120
153
|
getActions: ($fetch, $store) => getPasskeyActions($fetch, {
|
|
121
154
|
$listPasskeys,
|
|
@@ -145,5 +178,3 @@ const passkeyClient = () => {
|
|
|
145
178
|
};
|
|
146
179
|
//#endregion
|
|
147
180
|
export { PASSKEY_ERROR_CODES, getPasskeyActions, passkeyClient };
|
|
148
|
-
|
|
149
|
-
//# sourceMappingURL=client.mjs.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as _simplewebauthn_server0 from "@simplewebauthn/server";
|
|
2
|
-
import { CredentialDeviceType } from "@simplewebauthn/server";
|
|
2
|
+
import { AuthenticationExtensionsClientInputs, AuthenticationResponseJSON, CredentialDeviceType, RegistrationResponseJSON, VerifiedAuthenticationResponse, VerifiedRegistrationResponse } from "@simplewebauthn/server";
|
|
3
3
|
import * as better_auth0 from "better-auth";
|
|
4
|
+
import { GenericEndpointContext } from "@better-auth/core";
|
|
4
5
|
import { InferOptionSchema } from "better-auth/types";
|
|
5
6
|
import * as better_call0 from "better-call";
|
|
6
7
|
|
|
@@ -66,7 +67,66 @@ interface WebAuthnChallengeValue {
|
|
|
66
67
|
expectedChallenge: string;
|
|
67
68
|
userData: {
|
|
68
69
|
id: string;
|
|
70
|
+
name?: string | undefined;
|
|
71
|
+
displayName?: string | undefined;
|
|
69
72
|
};
|
|
73
|
+
context?: string | null;
|
|
74
|
+
}
|
|
75
|
+
type Awaitable<T> = T | Promise<T>;
|
|
76
|
+
interface PasskeyRegistrationUser {
|
|
77
|
+
id: string;
|
|
78
|
+
name: string;
|
|
79
|
+
displayName?: string | undefined;
|
|
80
|
+
}
|
|
81
|
+
type PasskeyExtensionsResolver = AuthenticationExtensionsClientInputs | ((args: {
|
|
82
|
+
ctx: GenericEndpointContext;
|
|
83
|
+
}) => Awaitable<AuthenticationExtensionsClientInputs | undefined>);
|
|
84
|
+
interface PasskeyRegistrationOptions {
|
|
85
|
+
/**
|
|
86
|
+
* Require an authenticated session for passkey registration.
|
|
87
|
+
*
|
|
88
|
+
* @default true
|
|
89
|
+
*/
|
|
90
|
+
requireSession?: boolean | undefined;
|
|
91
|
+
/**
|
|
92
|
+
* Resolve the user when session is not available.
|
|
93
|
+
* Required when `requireSession` is false and no session exists.
|
|
94
|
+
*/
|
|
95
|
+
resolveUser?: ((args: {
|
|
96
|
+
ctx: GenericEndpointContext;
|
|
97
|
+
context?: string | null | undefined;
|
|
98
|
+
}) => Awaitable<PasskeyRegistrationUser>) | undefined;
|
|
99
|
+
/**
|
|
100
|
+
* Callback after a successful registration verification.
|
|
101
|
+
* Useful for user linking or auditing.
|
|
102
|
+
*/
|
|
103
|
+
afterVerification?: ((args: {
|
|
104
|
+
ctx: GenericEndpointContext;
|
|
105
|
+
verification: VerifiedRegistrationResponse;
|
|
106
|
+
user: PasskeyRegistrationUser;
|
|
107
|
+
clientData: RegistrationResponseJSON;
|
|
108
|
+
context?: string | null | undefined;
|
|
109
|
+
}) => Awaitable<{
|
|
110
|
+
userId?: string;
|
|
111
|
+
} | void>) | undefined;
|
|
112
|
+
/**
|
|
113
|
+
* Optional WebAuthn extensions to include in registration options.
|
|
114
|
+
*/
|
|
115
|
+
extensions?: PasskeyExtensionsResolver | undefined;
|
|
116
|
+
}
|
|
117
|
+
interface PasskeyAuthenticationOptions {
|
|
118
|
+
/**
|
|
119
|
+
* Optional WebAuthn extensions to include in authentication options.
|
|
120
|
+
*/
|
|
121
|
+
extensions?: PasskeyExtensionsResolver | undefined;
|
|
122
|
+
/**
|
|
123
|
+
* Callback after a successful authentication verification.
|
|
124
|
+
*/
|
|
125
|
+
afterVerification?: ((args: {
|
|
126
|
+
ctx: GenericEndpointContext;
|
|
127
|
+
verification: VerifiedAuthenticationResponse;
|
|
128
|
+
clientData: AuthenticationResponseJSON;
|
|
129
|
+
}) => Awaitable<void>) | undefined;
|
|
70
130
|
}
|
|
71
131
|
interface PasskeyOptions {
|
|
72
132
|
/**
|
|
@@ -111,6 +171,14 @@ interface PasskeyOptions {
|
|
|
111
171
|
* Schema for the passkey model
|
|
112
172
|
*/
|
|
113
173
|
schema?: InferOptionSchema<typeof schema> | undefined;
|
|
174
|
+
/**
|
|
175
|
+
* Registration behavior overrides
|
|
176
|
+
*/
|
|
177
|
+
registration?: PasskeyRegistrationOptions | undefined;
|
|
178
|
+
/**
|
|
179
|
+
* Authentication behavior overrides
|
|
180
|
+
*/
|
|
181
|
+
authentication?: PasskeyAuthenticationOptions | undefined;
|
|
114
182
|
}
|
|
115
183
|
type Passkey = {
|
|
116
184
|
id: string;
|
|
@@ -139,6 +207,9 @@ declare const PASSKEY_ERROR_CODES: {
|
|
|
139
207
|
REGISTRATION_CANCELLED: better_auth0.RawError<"REGISTRATION_CANCELLED">;
|
|
140
208
|
AUTH_CANCELLED: better_auth0.RawError<"AUTH_CANCELLED">;
|
|
141
209
|
UNKNOWN_ERROR: better_auth0.RawError<"UNKNOWN_ERROR">;
|
|
210
|
+
SESSION_REQUIRED: better_auth0.RawError<"SESSION_REQUIRED">;
|
|
211
|
+
RESOLVE_USER_REQUIRED: better_auth0.RawError<"RESOLVE_USER_REQUIRED">;
|
|
212
|
+
RESOLVED_USER_INVALID: better_auth0.RawError<"RESOLVED_USER_INVALID">;
|
|
142
213
|
};
|
|
143
214
|
//#endregion
|
|
144
215
|
//#region src/index.d.ts
|
|
@@ -151,10 +222,12 @@ declare module "@better-auth/core" {
|
|
|
151
222
|
}
|
|
152
223
|
declare const passkey: (options?: PasskeyOptions | undefined) => {
|
|
153
224
|
id: "passkey";
|
|
225
|
+
version: string;
|
|
154
226
|
endpoints: {
|
|
155
227
|
generatePasskeyRegistrationOptions: better_call0.Endpoint<"/passkey/generate-register-options", "GET", undefined, {
|
|
156
228
|
authenticatorAttachment?: "platform" | "cross-platform" | undefined;
|
|
157
229
|
name?: string | undefined;
|
|
230
|
+
context?: string | undefined;
|
|
158
231
|
} | undefined, [better_call0.Middleware<(inputContext: Record<string, any>) => Promise<{
|
|
159
232
|
session: {
|
|
160
233
|
session: Record<string, any> & {
|
|
@@ -194,6 +267,10 @@ declare const passkey: (options?: PasskeyOptions | undefined) => {
|
|
|
194
267
|
description: string;
|
|
195
268
|
required: boolean;
|
|
196
269
|
};
|
|
270
|
+
context: {
|
|
271
|
+
description: string;
|
|
272
|
+
required: boolean;
|
|
273
|
+
};
|
|
197
274
|
};
|
|
198
275
|
};
|
|
199
276
|
content: {
|
|
@@ -684,9 +761,11 @@ declare const passkey: (options?: PasskeyOptions | undefined) => {
|
|
|
684
761
|
REGISTRATION_CANCELLED: better_auth0.RawError<"REGISTRATION_CANCELLED">;
|
|
685
762
|
AUTH_CANCELLED: better_auth0.RawError<"AUTH_CANCELLED">;
|
|
686
763
|
UNKNOWN_ERROR: better_auth0.RawError<"UNKNOWN_ERROR">;
|
|
764
|
+
SESSION_REQUIRED: better_auth0.RawError<"SESSION_REQUIRED">;
|
|
765
|
+
RESOLVE_USER_REQUIRED: better_auth0.RawError<"RESOLVE_USER_REQUIRED">;
|
|
766
|
+
RESOLVED_USER_INVALID: better_auth0.RawError<"RESOLVED_USER_INVALID">;
|
|
687
767
|
};
|
|
688
768
|
options: PasskeyOptions | undefined;
|
|
689
769
|
};
|
|
690
770
|
//#endregion
|
|
691
|
-
export {
|
|
692
|
-
//# sourceMappingURL=index-BfRDyiNp.d.mts.map
|
|
771
|
+
export { PasskeyExtensionsResolver as a, PasskeyRegistrationUser as c, PasskeyAuthenticationOptions as i, WebAuthnChallengeValue as l, PASSKEY_ERROR_CODES as n, PasskeyOptions as o, Passkey as r, PasskeyRegistrationOptions as s, passkey as t };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as PASSKEY_ERROR_CODES, o as PasskeyOptions, r as Passkey, t as passkey } from "./index-BzKpmgHh.mjs";
|
|
2
2
|
export { PASSKEY_ERROR_CODES, Passkey, PasskeyOptions, passkey };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as PASSKEY_ERROR_CODES, t as PACKAGE_VERSION } from "./version-B7zkjZSd.mjs";
|
|
2
2
|
import { mergeSchema } from "better-auth/db";
|
|
3
3
|
import { createAuthEndpoint } from "@better-auth/core/api";
|
|
4
4
|
import { APIError } from "@better-auth/core/error";
|
|
@@ -14,136 +14,183 @@ function getRpID(options, baseURL) {
|
|
|
14
14
|
}
|
|
15
15
|
//#endregion
|
|
16
16
|
//#region src/routes.ts
|
|
17
|
+
const resolveExtensions = async (extensions, ctx) => {
|
|
18
|
+
if (!extensions) return;
|
|
19
|
+
if (typeof extensions === "function") return await extensions({ ctx });
|
|
20
|
+
return extensions;
|
|
21
|
+
};
|
|
22
|
+
const resolveRegistrationUser = async (opts, ctx) => {
|
|
23
|
+
if (opts.registration?.requireSession ?? true) {
|
|
24
|
+
const session = ctx.context?.session;
|
|
25
|
+
if (!session?.user?.id) throw APIError.from("UNAUTHORIZED", PASSKEY_ERROR_CODES.SESSION_REQUIRED);
|
|
26
|
+
const sessionName = session.user.email || session.user.id;
|
|
27
|
+
return {
|
|
28
|
+
id: session.user.id,
|
|
29
|
+
name: sessionName,
|
|
30
|
+
displayName: sessionName
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const session = await getSessionFromCtx(ctx);
|
|
34
|
+
if (session?.user?.id) {
|
|
35
|
+
const sessionName = session.user.email || session.user.id;
|
|
36
|
+
return {
|
|
37
|
+
id: session.user.id,
|
|
38
|
+
name: sessionName,
|
|
39
|
+
displayName: sessionName
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (!opts.registration?.resolveUser) throw APIError.from("BAD_REQUEST", PASSKEY_ERROR_CODES.RESOLVE_USER_REQUIRED);
|
|
43
|
+
const resolvedUser = await opts.registration.resolveUser({
|
|
44
|
+
ctx,
|
|
45
|
+
context: ctx.query?.context ?? null
|
|
46
|
+
});
|
|
47
|
+
if (!resolvedUser?.id || !resolvedUser?.name) throw APIError.from("BAD_REQUEST", PASSKEY_ERROR_CODES.RESOLVED_USER_INVALID);
|
|
48
|
+
return resolvedUser;
|
|
49
|
+
};
|
|
17
50
|
const generatePasskeyQuerySchema = z.object({
|
|
18
51
|
authenticatorAttachment: z.enum(["platform", "cross-platform"]).optional(),
|
|
19
|
-
name: z.string().optional()
|
|
52
|
+
name: z.string().optional(),
|
|
53
|
+
context: z.string().optional()
|
|
20
54
|
}).optional();
|
|
21
|
-
const generatePasskeyRegistrationOptions = (opts, { maxAgeInSeconds }) =>
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
55
|
+
const generatePasskeyRegistrationOptions = (opts, { maxAgeInSeconds }) => {
|
|
56
|
+
return createAuthEndpoint("/passkey/generate-register-options", {
|
|
57
|
+
method: "GET",
|
|
58
|
+
use: opts.registration?.requireSession ?? true ? [freshSessionMiddleware] : void 0,
|
|
59
|
+
query: generatePasskeyQuerySchema,
|
|
60
|
+
metadata: { openapi: {
|
|
61
|
+
operationId: "generatePasskeyRegistrationOptions",
|
|
62
|
+
description: "Generate registration options for a new passkey",
|
|
63
|
+
responses: { 200: {
|
|
64
|
+
description: "Success",
|
|
65
|
+
parameters: { query: {
|
|
66
|
+
authenticatorAttachment: {
|
|
67
|
+
description: `Type of authenticator to use for registration.
|
|
33
68
|
"platform" for device-specific authenticators,
|
|
34
69
|
"cross-platform" for authenticators that can be used across devices.`,
|
|
35
|
-
|
|
36
|
-
},
|
|
37
|
-
name: {
|
|
38
|
-
description: `Optional custom name for the passkey.
|
|
39
|
-
This can help identify the passkey when managing multiple credentials.`,
|
|
40
|
-
required: false
|
|
41
|
-
}
|
|
42
|
-
} },
|
|
43
|
-
content: { "application/json": { schema: {
|
|
44
|
-
type: "object",
|
|
45
|
-
properties: {
|
|
46
|
-
challenge: { type: "string" },
|
|
47
|
-
rp: {
|
|
48
|
-
type: "object",
|
|
49
|
-
properties: {
|
|
50
|
-
name: { type: "string" },
|
|
51
|
-
id: { type: "string" }
|
|
52
|
-
}
|
|
70
|
+
required: false
|
|
53
71
|
},
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
name: { type: "string" },
|
|
59
|
-
displayName: { type: "string" }
|
|
60
|
-
}
|
|
72
|
+
name: {
|
|
73
|
+
description: `Optional custom name for the passkey.
|
|
74
|
+
This can help identify the passkey when managing multiple credentials.`,
|
|
75
|
+
required: false
|
|
61
76
|
},
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
77
|
+
context: {
|
|
78
|
+
description: "Optional context for passkey-first registration flows.",
|
|
79
|
+
required: false
|
|
80
|
+
}
|
|
81
|
+
} },
|
|
82
|
+
content: { "application/json": { schema: {
|
|
83
|
+
type: "object",
|
|
84
|
+
properties: {
|
|
85
|
+
challenge: { type: "string" },
|
|
86
|
+
rp: {
|
|
65
87
|
type: "object",
|
|
66
88
|
properties: {
|
|
67
|
-
|
|
68
|
-
|
|
89
|
+
name: { type: "string" },
|
|
90
|
+
id: { type: "string" }
|
|
69
91
|
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
timeout: { type: "number" },
|
|
73
|
-
excludeCredentials: {
|
|
74
|
-
type: "array",
|
|
75
|
-
items: {
|
|
92
|
+
},
|
|
93
|
+
user: {
|
|
76
94
|
type: "object",
|
|
77
95
|
properties: {
|
|
78
96
|
id: { type: "string" },
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
97
|
+
name: { type: "string" },
|
|
98
|
+
displayName: { type: "string" }
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
pubKeyCredParams: {
|
|
102
|
+
type: "array",
|
|
103
|
+
items: {
|
|
104
|
+
type: "object",
|
|
105
|
+
properties: {
|
|
106
|
+
type: { type: "string" },
|
|
107
|
+
alg: { type: "number" }
|
|
83
108
|
}
|
|
84
109
|
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
110
|
+
},
|
|
111
|
+
timeout: { type: "number" },
|
|
112
|
+
excludeCredentials: {
|
|
113
|
+
type: "array",
|
|
114
|
+
items: {
|
|
115
|
+
type: "object",
|
|
116
|
+
properties: {
|
|
117
|
+
id: { type: "string" },
|
|
118
|
+
type: { type: "string" },
|
|
119
|
+
transports: {
|
|
120
|
+
type: "array",
|
|
121
|
+
items: { type: "string" }
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
authenticatorSelection: {
|
|
127
|
+
type: "object",
|
|
128
|
+
properties: {
|
|
129
|
+
authenticatorAttachment: { type: "string" },
|
|
130
|
+
requireResidentKey: { type: "boolean" },
|
|
131
|
+
userVerification: { type: "string" }
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
attestation: { type: "string" },
|
|
135
|
+
extensions: { type: "object" }
|
|
136
|
+
}
|
|
137
|
+
} } }
|
|
138
|
+
} }
|
|
99
139
|
} }
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
140
|
+
}, async (ctx) => {
|
|
141
|
+
const user = await resolveRegistrationUser(opts, ctx);
|
|
142
|
+
const userPasskeys = await ctx.context.adapter.findMany({
|
|
143
|
+
model: "passkey",
|
|
144
|
+
where: [{
|
|
145
|
+
field: "userId",
|
|
146
|
+
value: user.id
|
|
147
|
+
}]
|
|
148
|
+
});
|
|
149
|
+
const registrationExtensions = await resolveExtensions(opts.registration?.extensions, ctx);
|
|
150
|
+
const userID = new TextEncoder().encode(generateRandomString(32, "a-z", "0-9"));
|
|
151
|
+
const baseURLString = typeof ctx.context.options.baseURL === "string" ? ctx.context.options.baseURL : void 0;
|
|
152
|
+
const options = await generateRegistrationOptions({
|
|
153
|
+
rpName: opts.rpName || ctx.context.appName,
|
|
154
|
+
rpID: getRpID(opts, baseURLString),
|
|
155
|
+
userID,
|
|
156
|
+
userName: ctx.query?.name || user.name || user.id,
|
|
157
|
+
userDisplayName: user.displayName || user.name || user.id,
|
|
158
|
+
attestationType: "none",
|
|
159
|
+
excludeCredentials: userPasskeys.map((passkey) => ({
|
|
160
|
+
id: passkey.credentialID,
|
|
161
|
+
transports: passkey.transports?.split(",")
|
|
162
|
+
})),
|
|
163
|
+
authenticatorSelection: {
|
|
164
|
+
residentKey: "preferred",
|
|
165
|
+
userVerification: "preferred",
|
|
166
|
+
...opts.authenticatorSelection || {},
|
|
167
|
+
...ctx.query?.authenticatorAttachment ? { authenticatorAttachment: ctx.query.authenticatorAttachment } : {}
|
|
168
|
+
},
|
|
169
|
+
extensions: registrationExtensions
|
|
170
|
+
});
|
|
171
|
+
const verificationToken = generateRandomString(32);
|
|
172
|
+
const webAuthnCookie = ctx.context.createAuthCookie(opts.advanced.webAuthnChallengeCookie);
|
|
173
|
+
await ctx.setSignedCookie(webAuthnCookie.name, verificationToken, ctx.context.secret, {
|
|
174
|
+
...webAuthnCookie.attributes,
|
|
175
|
+
maxAge: maxAgeInSeconds
|
|
176
|
+
});
|
|
177
|
+
const expirationTime = new Date(Date.now() + maxAgeInSeconds * 1e3);
|
|
178
|
+
await ctx.context.internalAdapter.createVerificationValue({
|
|
179
|
+
identifier: verificationToken,
|
|
180
|
+
value: JSON.stringify({
|
|
181
|
+
expectedChallenge: options.challenge,
|
|
182
|
+
userData: {
|
|
183
|
+
id: user.id,
|
|
184
|
+
name: user.name,
|
|
185
|
+
displayName: user.displayName
|
|
186
|
+
},
|
|
187
|
+
context: ctx.query?.context ?? null
|
|
188
|
+
}),
|
|
189
|
+
expiresAt: expirationTime
|
|
190
|
+
});
|
|
191
|
+
return ctx.json(options, { status: 200 });
|
|
144
192
|
});
|
|
145
|
-
|
|
146
|
-
});
|
|
193
|
+
};
|
|
147
194
|
const generatePasskeyAuthenticationOptions = (opts, { maxAgeInSeconds }) => createAuthEndpoint("/passkey/generate-authenticate-options", {
|
|
148
195
|
method: "GET",
|
|
149
196
|
metadata: { openapi: {
|
|
@@ -209,9 +256,12 @@ const generatePasskeyAuthenticationOptions = (opts, { maxAgeInSeconds }) => crea
|
|
|
209
256
|
value: session.user.id
|
|
210
257
|
}]
|
|
211
258
|
});
|
|
259
|
+
const baseURLString = typeof ctx.context.options.baseURL === "string" ? ctx.context.options.baseURL : void 0;
|
|
260
|
+
const authenticationExtensions = await resolveExtensions(opts.authentication?.extensions, ctx);
|
|
212
261
|
const options = await generateAuthenticationOptions({
|
|
213
|
-
rpID: getRpID(opts,
|
|
262
|
+
rpID: getRpID(opts, baseURLString),
|
|
214
263
|
userVerification: "preferred",
|
|
264
|
+
extensions: authenticationExtensions,
|
|
215
265
|
...userPasskeys.length ? { allowCredentials: userPasskeys.map((passkey) => ({
|
|
216
266
|
id: passkey.credentialID,
|
|
217
267
|
transports: passkey.transports?.split(",")
|
|
@@ -239,66 +289,91 @@ const verifyPasskeyRegistrationBodySchema = z.object({
|
|
|
239
289
|
response: z.any(),
|
|
240
290
|
name: z.string().meta({ description: "Name of the passkey" }).optional()
|
|
241
291
|
});
|
|
242
|
-
const verifyPasskeyRegistration = (options) =>
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
292
|
+
const verifyPasskeyRegistration = (options) => {
|
|
293
|
+
const requireSession = options.registration?.requireSession ?? true;
|
|
294
|
+
return createAuthEndpoint("/passkey/verify-registration", {
|
|
295
|
+
method: "POST",
|
|
296
|
+
body: verifyPasskeyRegistrationBodySchema,
|
|
297
|
+
use: requireSession ? [freshSessionMiddleware] : void 0,
|
|
298
|
+
metadata: { openapi: {
|
|
299
|
+
operationId: "passkeyVerifyRegistration",
|
|
300
|
+
description: "Verify registration of a new passkey",
|
|
301
|
+
responses: {
|
|
302
|
+
200: {
|
|
303
|
+
description: "Success",
|
|
304
|
+
content: { "application/json": { schema: { $ref: "#/components/schemas/Passkey" } } }
|
|
305
|
+
},
|
|
306
|
+
400: { description: "Bad request" }
|
|
307
|
+
}
|
|
308
|
+
} }
|
|
309
|
+
}, async (ctx) => {
|
|
310
|
+
const origin = options?.origin || ctx.headers?.get("origin") || "";
|
|
311
|
+
if (!origin) throw APIError.from("BAD_REQUEST", PASSKEY_ERROR_CODES.FAILED_TO_VERIFY_REGISTRATION);
|
|
312
|
+
const resp = ctx.body.response;
|
|
313
|
+
const webAuthnCookie = ctx.context.createAuthCookie(options.advanced.webAuthnChallengeCookie);
|
|
314
|
+
const verificationToken = await ctx.getSignedCookie(webAuthnCookie.name, ctx.context.secret);
|
|
315
|
+
if (!verificationToken) throw APIError.from("BAD_REQUEST", PASSKEY_ERROR_CODES.CHALLENGE_NOT_FOUND);
|
|
316
|
+
const data = await ctx.context.internalAdapter.findVerificationValue(verificationToken);
|
|
317
|
+
if (!data) throw APIError.from("BAD_REQUEST", PASSKEY_ERROR_CODES.CHALLENGE_NOT_FOUND);
|
|
318
|
+
const { expectedChallenge, userData, context } = JSON.parse(data.value);
|
|
319
|
+
const session = requireSession ? ctx.context.session : await getSessionFromCtx(ctx);
|
|
320
|
+
if (session?.user?.id && userData.id !== session.user.id) throw APIError.from("UNAUTHORIZED", PASSKEY_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_REGISTER_THIS_PASSKEY);
|
|
321
|
+
try {
|
|
322
|
+
const verification = await verifyRegistrationResponse({
|
|
323
|
+
response: resp,
|
|
324
|
+
expectedChallenge,
|
|
325
|
+
expectedOrigin: origin,
|
|
326
|
+
expectedRPID: getRpID(options, typeof ctx.context.options.baseURL === "string" ? ctx.context.options.baseURL : void 0),
|
|
327
|
+
requireUserVerification: false
|
|
328
|
+
});
|
|
329
|
+
const { verified, registrationInfo } = verification;
|
|
330
|
+
if (!verified || !registrationInfo) throw APIError.from("BAD_REQUEST", PASSKEY_ERROR_CODES.FAILED_TO_VERIFY_REGISTRATION);
|
|
331
|
+
const { aaguid, credentialDeviceType, credentialBackedUp, credential } = registrationInfo;
|
|
332
|
+
const resolvedUser = {
|
|
333
|
+
id: userData.id,
|
|
334
|
+
name: userData.name || userData.id,
|
|
335
|
+
displayName: userData.displayName
|
|
336
|
+
};
|
|
337
|
+
let targetUserId = resolvedUser.id;
|
|
338
|
+
if (options.registration?.afterVerification) {
|
|
339
|
+
const result = await options.registration.afterVerification({
|
|
340
|
+
ctx,
|
|
341
|
+
verification,
|
|
342
|
+
user: resolvedUser,
|
|
343
|
+
clientData: resp,
|
|
344
|
+
context
|
|
345
|
+
});
|
|
346
|
+
if (result?.userId) {
|
|
347
|
+
if (typeof result.userId !== "string" || !result.userId) throw APIError.from("BAD_REQUEST", PASSKEY_ERROR_CODES.RESOLVED_USER_INVALID);
|
|
348
|
+
if (session?.user?.id && result.userId !== session.user.id) throw APIError.from("UNAUTHORIZED", PASSKEY_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_REGISTER_THIS_PASSKEY);
|
|
349
|
+
targetUserId = result.userId;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
const pubKey = base64.encode(credential.publicKey);
|
|
353
|
+
const newPasskey = {
|
|
354
|
+
name: ctx.body.name,
|
|
355
|
+
userId: targetUserId,
|
|
356
|
+
credentialID: credential.id,
|
|
357
|
+
publicKey: pubKey,
|
|
358
|
+
counter: credential.counter,
|
|
359
|
+
deviceType: credentialDeviceType,
|
|
360
|
+
transports: resp.response.transports.join(","),
|
|
361
|
+
backedUp: credentialBackedUp,
|
|
362
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
363
|
+
aaguid
|
|
364
|
+
};
|
|
365
|
+
const newPasskeyRes = await ctx.context.adapter.create({
|
|
366
|
+
model: "passkey",
|
|
367
|
+
data: newPasskey
|
|
368
|
+
});
|
|
369
|
+
await ctx.context.internalAdapter.deleteVerificationByIdentifier(verificationToken);
|
|
370
|
+
return ctx.json(newPasskeyRes, { status: 200 });
|
|
371
|
+
} catch (e) {
|
|
372
|
+
ctx.context.logger.error("Failed to verify registration", e);
|
|
373
|
+
throw APIError.from("INTERNAL_SERVER_ERROR", PASSKEY_ERROR_CODES.FAILED_TO_VERIFY_REGISTRATION);
|
|
255
374
|
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
const origin = options?.origin || ctx.headers?.get("origin") || "";
|
|
259
|
-
if (!origin) throw APIError.from("BAD_REQUEST", PASSKEY_ERROR_CODES.FAILED_TO_VERIFY_REGISTRATION);
|
|
260
|
-
const resp = ctx.body.response;
|
|
261
|
-
const webAuthnCookie = ctx.context.createAuthCookie(options.advanced.webAuthnChallengeCookie);
|
|
262
|
-
const verificationToken = await ctx.getSignedCookie(webAuthnCookie.name, ctx.context.secret);
|
|
263
|
-
if (!verificationToken) throw APIError.from("BAD_REQUEST", PASSKEY_ERROR_CODES.CHALLENGE_NOT_FOUND);
|
|
264
|
-
const data = await ctx.context.internalAdapter.findVerificationValue(verificationToken);
|
|
265
|
-
if (!data) throw APIError.from("BAD_REQUEST", PASSKEY_ERROR_CODES.CHALLENGE_NOT_FOUND);
|
|
266
|
-
const { expectedChallenge, userData } = JSON.parse(data.value);
|
|
267
|
-
if (userData.id !== ctx.context.session.user.id) throw APIError.from("UNAUTHORIZED", PASSKEY_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_REGISTER_THIS_PASSKEY);
|
|
268
|
-
try {
|
|
269
|
-
const { verified, registrationInfo } = await verifyRegistrationResponse({
|
|
270
|
-
response: resp,
|
|
271
|
-
expectedChallenge,
|
|
272
|
-
expectedOrigin: origin,
|
|
273
|
-
expectedRPID: getRpID(options, typeof ctx.context.options.baseURL === "string" ? ctx.context.options.baseURL : void 0),
|
|
274
|
-
requireUserVerification: false
|
|
275
|
-
});
|
|
276
|
-
if (!verified || !registrationInfo) throw APIError.from("BAD_REQUEST", PASSKEY_ERROR_CODES.FAILED_TO_VERIFY_REGISTRATION);
|
|
277
|
-
const { aaguid, credentialDeviceType, credentialBackedUp, credential } = registrationInfo;
|
|
278
|
-
const pubKey = base64.encode(credential.publicKey);
|
|
279
|
-
const newPasskey = {
|
|
280
|
-
name: ctx.body.name,
|
|
281
|
-
userId: userData.id,
|
|
282
|
-
credentialID: credential.id,
|
|
283
|
-
publicKey: pubKey,
|
|
284
|
-
counter: credential.counter,
|
|
285
|
-
deviceType: credentialDeviceType,
|
|
286
|
-
transports: resp.response.transports.join(","),
|
|
287
|
-
backedUp: credentialBackedUp,
|
|
288
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
289
|
-
aaguid
|
|
290
|
-
};
|
|
291
|
-
const newPasskeyRes = await ctx.context.adapter.create({
|
|
292
|
-
model: "passkey",
|
|
293
|
-
data: newPasskey
|
|
294
|
-
});
|
|
295
|
-
await ctx.context.internalAdapter.deleteVerificationByIdentifier(verificationToken);
|
|
296
|
-
return ctx.json(newPasskeyRes, { status: 200 });
|
|
297
|
-
} catch (e) {
|
|
298
|
-
ctx.context.logger.error("Failed to verify registration", e);
|
|
299
|
-
throw APIError.from("INTERNAL_SERVER_ERROR", PASSKEY_ERROR_CODES.FAILED_TO_VERIFY_REGISTRATION);
|
|
300
|
-
}
|
|
301
|
-
});
|
|
375
|
+
});
|
|
376
|
+
};
|
|
302
377
|
const verifyPasskeyAuthenticationBodySchema = z.object({ response: z.record(z.any(), z.any()) });
|
|
303
378
|
const verifyPasskeyAuthentication = (options) => createAuthEndpoint("/passkey/verify-authentication", {
|
|
304
379
|
method: "POST",
|
|
@@ -354,6 +429,11 @@ const verifyPasskeyAuthentication = (options) => createAuthEndpoint("/passkey/ve
|
|
|
354
429
|
});
|
|
355
430
|
const { verified } = verification;
|
|
356
431
|
if (!verified) throw APIError.from("UNAUTHORIZED", PASSKEY_ERROR_CODES.AUTHENTICATION_FAILED);
|
|
432
|
+
if (options.authentication?.afterVerification) await options.authentication.afterVerification({
|
|
433
|
+
ctx,
|
|
434
|
+
verification,
|
|
435
|
+
clientData: resp
|
|
436
|
+
});
|
|
357
437
|
await ctx.context.adapter.update({
|
|
358
438
|
model: "passkey",
|
|
359
439
|
where: [{
|
|
@@ -595,6 +675,7 @@ const passkey = (options) => {
|
|
|
595
675
|
};
|
|
596
676
|
return {
|
|
597
677
|
id: "passkey",
|
|
678
|
+
version: PACKAGE_VERSION,
|
|
598
679
|
endpoints: {
|
|
599
680
|
generatePasskeyRegistrationOptions: generatePasskeyRegistrationOptions(opts, { maxAgeInSeconds: MAX_AGE_IN_SECONDS }),
|
|
600
681
|
generatePasskeyAuthenticationOptions: generatePasskeyAuthenticationOptions(opts, { maxAgeInSeconds: MAX_AGE_IN_SECONDS }),
|
|
@@ -611,5 +692,3 @@ const passkey = (options) => {
|
|
|
611
692
|
};
|
|
612
693
|
//#endregion
|
|
613
694
|
export { PASSKEY_ERROR_CODES, passkey };
|
|
614
|
-
|
|
615
|
-
//# sourceMappingURL=index.mjs.map
|
|
@@ -11,9 +11,13 @@ const PASSKEY_ERROR_CODES = defineErrorCodes({
|
|
|
11
11
|
PREVIOUSLY_REGISTERED: "Previously registered",
|
|
12
12
|
REGISTRATION_CANCELLED: "Registration cancelled",
|
|
13
13
|
AUTH_CANCELLED: "Auth cancelled",
|
|
14
|
-
UNKNOWN_ERROR: "Unknown error"
|
|
14
|
+
UNKNOWN_ERROR: "Unknown error",
|
|
15
|
+
SESSION_REQUIRED: "Passkey registration requires an authenticated session",
|
|
16
|
+
RESOLVE_USER_REQUIRED: "Passkey registration requires either an authenticated session or a resolveUser callback when requireSession is false",
|
|
17
|
+
RESOLVED_USER_INVALID: "Resolved user is invalid"
|
|
15
18
|
});
|
|
16
19
|
//#endregion
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
//#
|
|
20
|
+
//#region src/version.ts
|
|
21
|
+
const PACKAGE_VERSION = "1.6.0-beta.0";
|
|
22
|
+
//#endregion
|
|
23
|
+
export { PASSKEY_ERROR_CODES as n, PACKAGE_VERSION as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-auth/passkey",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0-beta.0",
|
|
4
4
|
"description": "Passkey plugin for Better Auth",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"publishConfig": {
|
|
20
20
|
"access": "public"
|
|
21
21
|
},
|
|
22
|
+
"sideEffects": false,
|
|
22
23
|
"files": [
|
|
23
24
|
"dist"
|
|
24
25
|
],
|
|
@@ -54,21 +55,21 @@
|
|
|
54
55
|
},
|
|
55
56
|
"devDependencies": {
|
|
56
57
|
"tsdown": "0.21.1",
|
|
57
|
-
"@better-auth/core": "1.
|
|
58
|
-
"better-auth": "1.
|
|
58
|
+
"@better-auth/core": "1.6.0-beta.0",
|
|
59
|
+
"better-auth": "1.6.0-beta.0"
|
|
59
60
|
},
|
|
60
61
|
"peerDependencies": {
|
|
61
|
-
"@better-auth/utils": "0.
|
|
62
|
+
"@better-auth/utils": "0.4.0",
|
|
62
63
|
"@better-fetch/fetch": "1.1.21",
|
|
63
|
-
"better-call": "2.0.
|
|
64
|
+
"better-call": "2.0.3",
|
|
64
65
|
"nanostores": "^1.0.1",
|
|
65
|
-
"@better-auth/core": "1.
|
|
66
|
-
"better-auth": "1.
|
|
66
|
+
"@better-auth/core": "^1.6.0-beta.0",
|
|
67
|
+
"better-auth": "^1.6.0-beta.0"
|
|
67
68
|
},
|
|
68
69
|
"scripts": {
|
|
69
70
|
"build": "tsdown",
|
|
70
71
|
"dev": "tsdown --watch",
|
|
71
|
-
"lint:package": "publint run --strict",
|
|
72
|
+
"lint:package": "publint run --strict --pack false",
|
|
72
73
|
"lint:types": "attw --profile esm-only --pack .",
|
|
73
74
|
"typecheck": "tsc --project tsconfig.json",
|
|
74
75
|
"test": "vitest",
|
package/dist/client.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"client.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["import type {\n\tBetterAuthClientPlugin,\n\tClientFetchOption,\n\tClientStore,\n} from \"@better-auth/core\";\nimport type { BetterFetch } from \"@better-fetch/fetch\";\nimport type {\n\tPublicKeyCredentialCreationOptionsJSON,\n\tPublicKeyCredentialRequestOptionsJSON,\n} from \"@simplewebauthn/browser\";\nimport {\n\tstartAuthentication,\n\tstartRegistration,\n\tWebAuthnError,\n} from \"@simplewebauthn/browser\";\nimport { useAuthQuery } from \"better-auth/client\";\nimport type { Session, User } from \"better-auth/types\";\nimport { atom } from \"nanostores\";\nimport type { passkey } from \".\";\nimport { PASSKEY_ERROR_CODES } from \"./error-codes\";\nimport type { Passkey } from \"./types\";\n\nexport const getPasskeyActions = (\n\t$fetch: BetterFetch,\n\t{\n\t\t$listPasskeys,\n\t\t$store,\n\t}: {\n\t\t$listPasskeys: ReturnType<typeof atom<any>>;\n\t\t$store: ClientStore;\n\t},\n) => {\n\tconst signInPasskey = async (\n\t\topts?:\n\t\t\t| {\n\t\t\t\t\tautoFill?: boolean;\n\t\t\t\t\tfetchOptions?: ClientFetchOption;\n\t\t\t }\n\t\t\t| undefined,\n\t\toptions?: ClientFetchOption | undefined,\n\t) => {\n\t\tconst response = await $fetch<PublicKeyCredentialRequestOptionsJSON>(\n\t\t\t\"/passkey/generate-authenticate-options\",\n\t\t\t{\n\t\t\t\tmethod: \"GET\",\n\t\t\t\tthrow: false,\n\t\t\t},\n\t\t);\n\t\tif (!response.data) {\n\t\t\treturn response;\n\t\t}\n\t\ttry {\n\t\t\tconst res = await startAuthentication({\n\t\t\t\toptionsJSON: response.data,\n\t\t\t\tuseBrowserAutofill: opts?.autoFill,\n\t\t\t});\n\t\t\tconst verified = await $fetch<{\n\t\t\t\tsession: Session;\n\t\t\t\tuser: User;\n\t\t\t}>(\"/passkey/verify-authentication\", {\n\t\t\t\tbody: {\n\t\t\t\t\tresponse: res,\n\t\t\t\t},\n\t\t\t\t...opts?.fetchOptions,\n\t\t\t\t...options,\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tthrow: false,\n\t\t\t});\n\t\t\t$listPasskeys.set(Math.random());\n\t\t\t$store.notify(\"$sessionSignal\");\n\n\t\t\treturn verified;\n\t\t} catch (err) {\n\t\t\t// Error logs ran on the front-end\n\t\t\tconsole.error(`[Better Auth] Error verifying passkey`, err);\n\t\t\treturn {\n\t\t\t\tdata: null,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"AUTH_CANCELLED\",\n\t\t\t\t\tmessage: PASSKEY_ERROR_CODES.AUTH_CANCELLED,\n\t\t\t\t\tstatus: 400,\n\t\t\t\t\tstatusText: \"BAD_REQUEST\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t};\n\n\tconst registerPasskey = async (\n\t\topts?:\n\t\t\t| {\n\t\t\t\t\tfetchOptions?: ClientFetchOption;\n\t\t\t\t\t/**\n\t\t\t\t\t * The name of the passkey. This is used to\n\t\t\t\t\t * identify the passkey in the UI.\n\t\t\t\t\t */\n\t\t\t\t\tname?: string;\n\n\t\t\t\t\t/**\n\t\t\t\t\t * The type of attachment for the passkey. Defaults to both\n\t\t\t\t\t * platform and cross-platform allowed, with platform preferred.\n\t\t\t\t\t */\n\t\t\t\t\tauthenticatorAttachment?: \"platform\" | \"cross-platform\";\n\n\t\t\t\t\t/**\n\t\t\t\t\t * Try to silently create a passkey with the password manager that the user just signed\n\t\t\t\t\t * in with.\n\t\t\t\t\t * @default false\n\t\t\t\t\t */\n\t\t\t\t\tuseAutoRegister?: boolean;\n\t\t\t }\n\t\t\t| undefined,\n\t\tfetchOpts?: ClientFetchOption | undefined,\n\t) => {\n\t\tconst options = await $fetch<PublicKeyCredentialCreationOptionsJSON>(\n\t\t\t\"/passkey/generate-register-options\",\n\t\t\t{\n\t\t\t\tmethod: \"GET\",\n\t\t\t\tquery: {\n\t\t\t\t\t...(opts?.authenticatorAttachment && {\n\t\t\t\t\t\tauthenticatorAttachment: opts.authenticatorAttachment,\n\t\t\t\t\t}),\n\t\t\t\t\t...(opts?.name && {\n\t\t\t\t\t\tname: opts.name,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t\tthrow: false,\n\t\t\t},\n\t\t);\n\n\t\tif (!options.data) {\n\t\t\treturn options;\n\t\t}\n\t\ttry {\n\t\t\tconst res = await startRegistration({\n\t\t\t\toptionsJSON: options.data,\n\t\t\t\tuseAutoRegister: opts?.useAutoRegister,\n\t\t\t});\n\t\t\tconst verified = await $fetch<Passkey>(\"/passkey/verify-registration\", {\n\t\t\t\t...opts?.fetchOptions,\n\t\t\t\t...fetchOpts,\n\t\t\t\tbody: {\n\t\t\t\t\tresponse: res,\n\t\t\t\t\tname: opts?.name,\n\t\t\t\t},\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tthrow: false,\n\t\t\t});\n\n\t\t\tif (!verified.data) {\n\t\t\t\treturn verified;\n\t\t\t}\n\t\t\t$listPasskeys.set(Math.random());\n\t\t\treturn verified;\n\t\t} catch (e) {\n\t\t\tif (e instanceof WebAuthnError) {\n\t\t\t\tif (e.code === \"ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED\") {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tdata: null,\n\t\t\t\t\t\terror: {\n\t\t\t\t\t\t\tcode: e.code,\n\t\t\t\t\t\t\tmessage: PASSKEY_ERROR_CODES.PREVIOUSLY_REGISTERED,\n\t\t\t\t\t\t\tstatus: 400,\n\t\t\t\t\t\t\tstatusText: \"BAD_REQUEST\",\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (e.code === \"ERROR_CEREMONY_ABORTED\") {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tdata: null,\n\t\t\t\t\t\terror: {\n\t\t\t\t\t\t\tcode: e.code,\n\t\t\t\t\t\t\tmessage: PASSKEY_ERROR_CODES.REGISTRATION_CANCELLED,\n\t\t\t\t\t\t\tstatus: 400,\n\t\t\t\t\t\t\tstatusText: \"BAD_REQUEST\",\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tdata: null,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcode: e.code,\n\t\t\t\t\t\tmessage: e.message,\n\t\t\t\t\t\tstatus: 400,\n\t\t\t\t\t\tstatusText: \"BAD_REQUEST\",\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tdata: null,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"UNKNOWN_ERROR\",\n\t\t\t\t\tmessage:\n\t\t\t\t\t\te instanceof Error ? e.message : PASSKEY_ERROR_CODES.UNKNOWN_ERROR,\n\t\t\t\t\tstatus: 500,\n\t\t\t\t\tstatusText: \"INTERNAL_SERVER_ERROR\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t};\n\n\treturn {\n\t\tsignIn: {\n\t\t\t/**\n\t\t\t * Sign in with a registered passkey\n\t\t\t */\n\t\t\tpasskey: signInPasskey,\n\t\t},\n\t\tpasskey: {\n\t\t\t/**\n\t\t\t * Add a passkey to the user account\n\t\t\t */\n\t\t\taddPasskey: registerPasskey,\n\t\t},\n\t\t/**\n\t\t * Inferred Internal Types\n\t\t */\n\t\t$Infer: {} as {\n\t\t\tPasskey: Passkey;\n\t\t},\n\t};\n};\n\nexport const passkeyClient = () => {\n\tconst $listPasskeys = atom<any>();\n\treturn {\n\t\tid: \"passkey\",\n\t\t$InferServerPlugin: {} as ReturnType<typeof passkey>,\n\t\tgetActions: ($fetch, $store) =>\n\t\t\tgetPasskeyActions($fetch, {\n\t\t\t\t$listPasskeys,\n\t\t\t\t$store,\n\t\t\t}),\n\t\tgetAtoms($fetch) {\n\t\t\tconst listPasskeys = useAuthQuery<Passkey[]>(\n\t\t\t\t$listPasskeys,\n\t\t\t\t\"/passkey/list-user-passkeys\",\n\t\t\t\t$fetch,\n\t\t\t\t{\n\t\t\t\t\tmethod: \"GET\",\n\t\t\t\t},\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tlistPasskeys,\n\t\t\t\t$listPasskeys,\n\t\t\t};\n\t\t},\n\t\tpathMethods: {\n\t\t\t\"/passkey/register\": \"POST\",\n\t\t\t\"/passkey/authenticate\": \"POST\",\n\t\t},\n\t\tatomListeners: [\n\t\t\t{\n\t\t\t\tmatcher(path) {\n\t\t\t\t\treturn (\n\t\t\t\t\t\tpath === \"/passkey/verify-registration\" ||\n\t\t\t\t\t\tpath === \"/passkey/delete-passkey\" ||\n\t\t\t\t\t\tpath === \"/passkey/update-passkey\" ||\n\t\t\t\t\t\tpath === \"/sign-out\"\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t\tsignal: \"$listPasskeys\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tmatcher: (path) => path === \"/passkey/verify-authentication\",\n\t\t\t\tsignal: \"$sessionSignal\",\n\t\t\t},\n\t\t],\n\t\t$ERROR_CODES: PASSKEY_ERROR_CODES,\n\t} satisfies BetterAuthClientPlugin;\n};\n\nexport type * from \"@simplewebauthn/server\";\nexport * from \"./error-codes\";\nexport type * from \"./types\";\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"error-codes-BwAsYefH.mjs","names":[],"sources":["../src/error-codes.ts"],"sourcesContent":["import { defineErrorCodes } from \"@better-auth/core/utils/error-codes\";\n\nexport const PASSKEY_ERROR_CODES = defineErrorCodes({\n\tCHALLENGE_NOT_FOUND: \"Challenge not found\",\n\tYOU_ARE_NOT_ALLOWED_TO_REGISTER_THIS_PASSKEY:\n\t\t\"You are not allowed to register this passkey\",\n\tFAILED_TO_VERIFY_REGISTRATION: \"Failed to verify registration\",\n\tPASSKEY_NOT_FOUND: \"Passkey not found\",\n\tAUTHENTICATION_FAILED: \"Authentication failed\",\n\tUNABLE_TO_CREATE_SESSION: \"Unable to create session\",\n\tFAILED_TO_UPDATE_PASSKEY: \"Failed to update passkey\",\n\tPREVIOUSLY_REGISTERED: \"Previously registered\",\n\tREGISTRATION_CANCELLED: \"Registration cancelled\",\n\tAUTH_CANCELLED: \"Auth cancelled\",\n\tUNKNOWN_ERROR: \"Unknown error\",\n});\n"],"mappings":";;AAEA,MAAa,sBAAsB,iBAAiB;CACnD,qBAAqB;CACrB,8CACC;CACD,+BAA+B;CAC/B,mBAAmB;CACnB,uBAAuB;CACvB,0BAA0B;CAC1B,0BAA0B;CAC1B,uBAAuB;CACvB,wBAAwB;CACxB,gBAAgB;CAChB,eAAe;CACf,CAAC"}
|
package/dist/index.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/utils.ts","../src/routes.ts","../src/schema.ts","../src/index.ts"],"sourcesContent":["import type { PasskeyOptions } from \"./types\";\n\nexport function getRpID(options: PasskeyOptions, baseURL?: string | undefined) {\n\treturn (\n\t\toptions.rpID || (baseURL ? new URL(baseURL).hostname : \"localhost\") // default rpID\n\t);\n}\n","import { createAuthEndpoint } from \"@better-auth/core/api\";\nimport { APIError } from \"@better-auth/core/error\";\nimport { base64 } from \"@better-auth/utils/base64\";\nimport type {\n\tAuthenticationResponseJSON,\n\tAuthenticatorTransportFuture,\n} from \"@simplewebauthn/server\";\nimport {\n\tgenerateAuthenticationOptions,\n\tgenerateRegistrationOptions,\n\tverifyAuthenticationResponse,\n\tverifyRegistrationResponse,\n} from \"@simplewebauthn/server\";\nimport {\n\tfreshSessionMiddleware,\n\tgetSessionFromCtx,\n\tsessionMiddleware,\n} from \"better-auth/api\";\nimport { setSessionCookie } from \"better-auth/cookies\";\nimport { generateRandomString } from \"better-auth/crypto\";\nimport * as z from \"zod\";\nimport { PASSKEY_ERROR_CODES } from \"./error-codes\";\nimport type { Passkey, PasskeyOptions, WebAuthnChallengeValue } from \"./types\";\nimport { getRpID } from \"./utils\";\n\ntype WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };\n\ntype RequiredPassKeyOptions = WithRequired<PasskeyOptions, \"advanced\"> & {\n\tadvanced: Required<PasskeyOptions[\"advanced\"]>;\n};\n\nconst generatePasskeyQuerySchema = z\n\t.object({\n\t\tauthenticatorAttachment: z.enum([\"platform\", \"cross-platform\"]).optional(),\n\t\tname: z.string().optional(),\n\t})\n\t.optional();\n\nexport const generatePasskeyRegistrationOptions = (\n\topts: RequiredPassKeyOptions,\n\t{ maxAgeInSeconds }: { maxAgeInSeconds: number },\n) =>\n\tcreateAuthEndpoint(\n\t\t\"/passkey/generate-register-options\",\n\t\t{\n\t\t\tmethod: \"GET\",\n\t\t\tuse: [freshSessionMiddleware],\n\t\t\tquery: generatePasskeyQuerySchema,\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\toperationId: \"generatePasskeyRegistrationOptions\",\n\t\t\t\t\tdescription: \"Generate registration options for a new passkey\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t200: {\n\t\t\t\t\t\t\tdescription: \"Success\",\n\t\t\t\t\t\t\tparameters: {\n\t\t\t\t\t\t\t\tquery: {\n\t\t\t\t\t\t\t\t\tauthenticatorAttachment: {\n\t\t\t\t\t\t\t\t\t\tdescription: `Type of authenticator to use for registration.\n \"platform\" for device-specific authenticators,\n \"cross-platform\" for authenticators that can be used across devices.`,\n\t\t\t\t\t\t\t\t\t\trequired: false,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tname: {\n\t\t\t\t\t\t\t\t\t\tdescription: `Optional custom name for the passkey.\n This can help identify the passkey when managing multiple credentials.`,\n\t\t\t\t\t\t\t\t\t\trequired: false,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tchallenge: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\trp: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\t\t\tname: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\tid: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tuser: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\t\t\tid: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\tname: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\tdisplayName: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tpubKeyCredParams: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"array\",\n\t\t\t\t\t\t\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\talg: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\ttimeout: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\texcludeCredentials: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"array\",\n\t\t\t\t\t\t\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tid: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttransports: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"array\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tauthenticatorSelection: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\t\t\tauthenticatorAttachment: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\trequireResidentKey: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\tuserVerification: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tattestation: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\n\t\t\t\t\t\t\t\t\t\t\textensions: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst { session } = ctx.context;\n\t\t\tconst userPasskeys = await ctx.context.adapter.findMany<Passkey>({\n\t\t\t\tmodel: \"passkey\",\n\t\t\t\twhere: [\n\t\t\t\t\t{\n\t\t\t\t\t\tfield: \"userId\",\n\t\t\t\t\t\tvalue: session.user.id,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t});\n\t\t\tconst userID = new TextEncoder().encode(\n\t\t\t\tgenerateRandomString(32, \"a-z\", \"0-9\"),\n\t\t\t);\n\t\t\tconst baseURLString =\n\t\t\t\ttypeof ctx.context.options.baseURL === \"string\"\n\t\t\t\t\t? ctx.context.options.baseURL\n\t\t\t\t\t: undefined;\n\t\t\tconst options = await generateRegistrationOptions({\n\t\t\t\trpName: opts.rpName || ctx.context.appName,\n\t\t\t\trpID: getRpID(opts, baseURLString),\n\t\t\t\tuserID,\n\t\t\t\tuserName: ctx.query?.name || session.user.email || session.user.id,\n\t\t\t\tuserDisplayName: session.user.email || session.user.id,\n\t\t\t\tattestationType: \"none\",\n\t\t\t\texcludeCredentials: userPasskeys.map((passkey) => ({\n\t\t\t\t\tid: passkey.credentialID,\n\t\t\t\t\ttransports: passkey.transports?.split(\n\t\t\t\t\t\t\",\",\n\t\t\t\t\t) as AuthenticatorTransportFuture[],\n\t\t\t\t})),\n\t\t\t\tauthenticatorSelection: {\n\t\t\t\t\tresidentKey: \"preferred\",\n\t\t\t\t\tuserVerification: \"preferred\",\n\t\t\t\t\t...(opts.authenticatorSelection || {}),\n\t\t\t\t\t...(ctx.query?.authenticatorAttachment\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\tauthenticatorAttachment: ctx.query.authenticatorAttachment,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t: {}),\n\t\t\t\t},\n\t\t\t});\n\t\t\tconst verificationToken = generateRandomString(32);\n\t\t\tconst webAuthnCookie = ctx.context.createAuthCookie(\n\t\t\t\topts.advanced.webAuthnChallengeCookie,\n\t\t\t);\n\t\t\tawait ctx.setSignedCookie(\n\t\t\t\twebAuthnCookie.name,\n\t\t\t\tverificationToken,\n\t\t\t\tctx.context.secret,\n\t\t\t\t{\n\t\t\t\t\t...webAuthnCookie.attributes,\n\t\t\t\t\tmaxAge: maxAgeInSeconds,\n\t\t\t\t},\n\t\t\t);\n\t\t\tconst expirationTime = new Date(Date.now() + maxAgeInSeconds * 1000);\n\t\t\tawait ctx.context.internalAdapter.createVerificationValue({\n\t\t\t\tidentifier: verificationToken,\n\t\t\t\tvalue: JSON.stringify({\n\t\t\t\t\texpectedChallenge: options.challenge,\n\t\t\t\t\tuserData: {\n\t\t\t\t\t\tid: session.user.id,\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\texpiresAt: expirationTime,\n\t\t\t});\n\t\t\treturn ctx.json(options, {\n\t\t\t\tstatus: 200,\n\t\t\t});\n\t\t},\n\t);\n\nexport const generatePasskeyAuthenticationOptions = (\n\topts: RequiredPassKeyOptions,\n\t{ maxAgeInSeconds }: { maxAgeInSeconds: number },\n) =>\n\tcreateAuthEndpoint(\n\t\t\"/passkey/generate-authenticate-options\",\n\t\t{\n\t\t\tmethod: \"GET\",\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\toperationId: \"passkeyGenerateAuthenticateOptions\",\n\t\t\t\t\tdescription: \"Generate authentication options for a passkey\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t200: {\n\t\t\t\t\t\t\tdescription: \"Success\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tchallenge: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\trp: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\t\t\tname: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\tid: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tuser: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\t\t\tid: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\tname: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\tdisplayName: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\ttimeout: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"number\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tallowCredentials: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"array\",\n\t\t\t\t\t\t\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tid: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttransports: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"array\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tuserVerification: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tauthenticatorSelection: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\t\t\tauthenticatorAttachment: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\trequireResidentKey: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\tuserVerification: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\textensions: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst session = await getSessionFromCtx(ctx);\n\t\t\tlet userPasskeys: Passkey[] = [];\n\t\t\tif (session) {\n\t\t\t\tuserPasskeys = await ctx.context.adapter.findMany<Passkey>({\n\t\t\t\t\tmodel: \"passkey\",\n\t\t\t\t\twhere: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tfield: \"userId\",\n\t\t\t\t\t\t\tvalue: session.user.id,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst baseURLString =\n\t\t\t\ttypeof ctx.context.options.baseURL === \"string\"\n\t\t\t\t\t? ctx.context.options.baseURL\n\t\t\t\t\t: undefined;\n\t\t\tconst options = await generateAuthenticationOptions({\n\t\t\t\trpID: getRpID(opts, baseURLString),\n\t\t\t\tuserVerification: \"preferred\",\n\t\t\t\t...(userPasskeys.length\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tallowCredentials: userPasskeys.map((passkey) => ({\n\t\t\t\t\t\t\t\tid: passkey.credentialID,\n\t\t\t\t\t\t\t\ttransports: passkey.transports?.split(\n\t\t\t\t\t\t\t\t\t\",\",\n\t\t\t\t\t\t\t\t) as AuthenticatorTransportFuture[],\n\t\t\t\t\t\t\t})),\n\t\t\t\t\t\t}\n\t\t\t\t\t: {}),\n\t\t\t});\n\t\t\tconst data = {\n\t\t\t\texpectedChallenge: options.challenge,\n\t\t\t\tuserData: {\n\t\t\t\t\tid: session?.user.id || \"\",\n\t\t\t\t},\n\t\t\t};\n\t\t\tconst verificationToken = generateRandomString(32);\n\t\t\tconst webAuthnCookie = ctx.context.createAuthCookie(\n\t\t\t\topts.advanced.webAuthnChallengeCookie,\n\t\t\t);\n\t\t\tawait ctx.setSignedCookie(\n\t\t\t\twebAuthnCookie.name,\n\t\t\t\tverificationToken,\n\t\t\t\tctx.context.secret,\n\t\t\t\t{\n\t\t\t\t\t...webAuthnCookie.attributes,\n\t\t\t\t\tmaxAge: maxAgeInSeconds,\n\t\t\t\t},\n\t\t\t);\n\t\t\tconst expirationTime = new Date(Date.now() + maxAgeInSeconds * 1000);\n\t\t\tawait ctx.context.internalAdapter.createVerificationValue({\n\t\t\t\tidentifier: verificationToken,\n\t\t\t\tvalue: JSON.stringify(data),\n\t\t\t\texpiresAt: expirationTime,\n\t\t\t});\n\t\t\treturn ctx.json(options, {\n\t\t\t\tstatus: 200,\n\t\t\t});\n\t\t},\n\t);\n\nconst verifyPasskeyRegistrationBodySchema = z.object({\n\tresponse: z.any(),\n\tname: z\n\t\t.string()\n\t\t.meta({\n\t\t\tdescription: \"Name of the passkey\",\n\t\t})\n\t\t.optional(),\n});\n\nexport const verifyPasskeyRegistration = (options: RequiredPassKeyOptions) =>\n\tcreateAuthEndpoint(\n\t\t\"/passkey/verify-registration\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: verifyPasskeyRegistrationBodySchema,\n\t\t\tuse: [freshSessionMiddleware],\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\toperationId: \"passkeyVerifyRegistration\",\n\t\t\t\t\tdescription: \"Verify registration of a new passkey\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t200: {\n\t\t\t\t\t\t\tdescription: \"Success\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\t$ref: \"#/components/schemas/Passkey\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t400: {\n\t\t\t\t\t\t\tdescription: \"Bad request\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst origin = options?.origin || ctx.headers?.get(\"origin\") || \"\";\n\t\t\tif (!origin) {\n\t\t\t\tthrow APIError.from(\n\t\t\t\t\t\"BAD_REQUEST\",\n\t\t\t\t\tPASSKEY_ERROR_CODES.FAILED_TO_VERIFY_REGISTRATION,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst resp = ctx.body.response;\n\t\t\tconst webAuthnCookie = ctx.context.createAuthCookie(\n\t\t\t\toptions.advanced.webAuthnChallengeCookie,\n\t\t\t);\n\t\t\tconst verificationToken = await ctx.getSignedCookie(\n\t\t\t\twebAuthnCookie.name,\n\t\t\t\tctx.context.secret,\n\t\t\t);\n\t\t\tif (!verificationToken) {\n\t\t\t\tthrow APIError.from(\n\t\t\t\t\t\"BAD_REQUEST\",\n\t\t\t\t\tPASSKEY_ERROR_CODES.CHALLENGE_NOT_FOUND,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst data =\n\t\t\t\tawait ctx.context.internalAdapter.findVerificationValue(\n\t\t\t\t\tverificationToken,\n\t\t\t\t);\n\t\t\tif (!data) {\n\t\t\t\tthrow APIError.from(\n\t\t\t\t\t\"BAD_REQUEST\",\n\t\t\t\t\tPASSKEY_ERROR_CODES.CHALLENGE_NOT_FOUND,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst { expectedChallenge, userData } = JSON.parse(\n\t\t\t\tdata.value,\n\t\t\t) as WebAuthnChallengeValue;\n\n\t\t\tif (userData.id !== ctx.context.session.user.id) {\n\t\t\t\tthrow APIError.from(\n\t\t\t\t\t\"UNAUTHORIZED\",\n\t\t\t\t\tPASSKEY_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_REGISTER_THIS_PASSKEY,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst verifyBaseURL =\n\t\t\t\t\ttypeof ctx.context.options.baseURL === \"string\"\n\t\t\t\t\t\t? ctx.context.options.baseURL\n\t\t\t\t\t\t: undefined;\n\t\t\t\tconst verification = await verifyRegistrationResponse({\n\t\t\t\t\tresponse: resp,\n\t\t\t\t\texpectedChallenge,\n\t\t\t\t\texpectedOrigin: origin,\n\t\t\t\t\texpectedRPID: getRpID(options, verifyBaseURL),\n\t\t\t\t\trequireUserVerification: false,\n\t\t\t\t});\n\t\t\t\tconst { verified, registrationInfo } = verification;\n\t\t\t\tif (!verified || !registrationInfo) {\n\t\t\t\t\tthrow APIError.from(\n\t\t\t\t\t\t\"BAD_REQUEST\",\n\t\t\t\t\t\tPASSKEY_ERROR_CODES.FAILED_TO_VERIFY_REGISTRATION,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tconst { aaguid, credentialDeviceType, credentialBackedUp, credential } =\n\t\t\t\t\tregistrationInfo;\n\t\t\t\tconst pubKey = base64.encode(credential.publicKey);\n\t\t\t\tconst newPasskey: Omit<Passkey, \"id\"> = {\n\t\t\t\t\tname: ctx.body.name,\n\t\t\t\t\tuserId: userData.id,\n\t\t\t\t\tcredentialID: credential.id,\n\t\t\t\t\tpublicKey: pubKey,\n\t\t\t\t\tcounter: credential.counter,\n\t\t\t\t\tdeviceType: credentialDeviceType,\n\t\t\t\t\ttransports: resp.response.transports.join(\",\"),\n\t\t\t\t\tbackedUp: credentialBackedUp,\n\t\t\t\t\tcreatedAt: new Date(),\n\t\t\t\t\taaguid: aaguid,\n\t\t\t\t};\n\t\t\t\tconst newPasskeyRes = await ctx.context.adapter.create<\n\t\t\t\t\tOmit<Passkey, \"id\">,\n\t\t\t\t\tPasskey\n\t\t\t\t>({\n\t\t\t\t\tmodel: \"passkey\",\n\t\t\t\t\tdata: newPasskey,\n\t\t\t\t});\n\t\t\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\t\t\tverificationToken,\n\t\t\t\t);\n\t\t\t\treturn ctx.json(newPasskeyRes, {\n\t\t\t\t\tstatus: 200,\n\t\t\t\t});\n\t\t\t} catch (e) {\n\t\t\t\tctx.context.logger.error(\"Failed to verify registration\", e);\n\t\t\t\tthrow APIError.from(\n\t\t\t\t\t\"INTERNAL_SERVER_ERROR\",\n\t\t\t\t\tPASSKEY_ERROR_CODES.FAILED_TO_VERIFY_REGISTRATION,\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\t);\n\nconst verifyPasskeyAuthenticationBodySchema = z.object({\n\tresponse: z.record(z.any(), z.any()),\n});\n\nexport const verifyPasskeyAuthentication = (options: RequiredPassKeyOptions) =>\n\tcreateAuthEndpoint(\n\t\t\"/passkey/verify-authentication\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: verifyPasskeyAuthenticationBodySchema,\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\toperationId: \"passkeyVerifyAuthentication\",\n\t\t\t\t\tdescription: \"Verify authentication of a passkey\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t200: {\n\t\t\t\t\t\t\tdescription: \"Success\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tsession: {\n\t\t\t\t\t\t\t\t\t\t\t\t$ref: \"#/components/schemas/Session\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tuser: {\n\t\t\t\t\t\t\t\t\t\t\t\t$ref: \"#/components/schemas/User\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t$Infer: {\n\t\t\t\t\tbody: {} as {\n\t\t\t\t\t\tresponse: AuthenticationResponseJSON;\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst origin = options?.origin || ctx.headers?.get(\"origin\") || \"\";\n\t\t\tif (!origin) {\n\t\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\t\tmessage: \"origin missing\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst resp = ctx.body.response;\n\t\t\tconst webAuthnCookie = ctx.context.createAuthCookie(\n\t\t\t\toptions.advanced.webAuthnChallengeCookie,\n\t\t\t);\n\t\t\tconst verificationToken = await ctx.getSignedCookie(\n\t\t\t\twebAuthnCookie.name,\n\t\t\t\tctx.context.secret,\n\t\t\t);\n\t\t\tif (!verificationToken) {\n\t\t\t\tthrow APIError.from(\n\t\t\t\t\t\"BAD_REQUEST\",\n\t\t\t\t\tPASSKEY_ERROR_CODES.CHALLENGE_NOT_FOUND,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst data =\n\t\t\t\tawait ctx.context.internalAdapter.findVerificationValue(\n\t\t\t\t\tverificationToken,\n\t\t\t\t);\n\t\t\tif (!data) {\n\t\t\t\tthrow APIError.from(\n\t\t\t\t\t\"BAD_REQUEST\",\n\t\t\t\t\tPASSKEY_ERROR_CODES.CHALLENGE_NOT_FOUND,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst { expectedChallenge } = JSON.parse(\n\t\t\t\tdata.value,\n\t\t\t) as WebAuthnChallengeValue;\n\t\t\tconst passkey = await ctx.context.adapter.findOne<Passkey>({\n\t\t\t\tmodel: \"passkey\",\n\t\t\t\twhere: [\n\t\t\t\t\t{\n\t\t\t\t\t\tfield: \"credentialID\",\n\t\t\t\t\t\tvalue: resp.id,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t});\n\t\t\tif (!passkey) {\n\t\t\t\tthrow APIError.from(\n\t\t\t\t\t\"UNAUTHORIZED\",\n\t\t\t\t\tPASSKEY_ERROR_CODES.PASSKEY_NOT_FOUND,\n\t\t\t\t);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst authBaseURL =\n\t\t\t\t\ttypeof ctx.context.options.baseURL === \"string\"\n\t\t\t\t\t\t? ctx.context.options.baseURL\n\t\t\t\t\t\t: undefined;\n\t\t\t\tconst verification = await verifyAuthenticationResponse({\n\t\t\t\t\tresponse: resp as AuthenticationResponseJSON,\n\t\t\t\t\texpectedChallenge,\n\t\t\t\t\texpectedOrigin: origin,\n\t\t\t\t\texpectedRPID: getRpID(options, authBaseURL),\n\t\t\t\t\tcredential: {\n\t\t\t\t\t\tid: passkey.credentialID,\n\t\t\t\t\t\tpublicKey: base64.decode(passkey.publicKey),\n\t\t\t\t\t\tcounter: passkey.counter,\n\t\t\t\t\t\ttransports: passkey.transports?.split(\n\t\t\t\t\t\t\t\",\",\n\t\t\t\t\t\t) as AuthenticatorTransportFuture[],\n\t\t\t\t\t},\n\t\t\t\t\trequireUserVerification: false,\n\t\t\t\t});\n\t\t\t\tconst { verified } = verification;\n\t\t\t\tif (!verified)\n\t\t\t\t\tthrow APIError.from(\n\t\t\t\t\t\t\"UNAUTHORIZED\",\n\t\t\t\t\t\tPASSKEY_ERROR_CODES.AUTHENTICATION_FAILED,\n\t\t\t\t\t);\n\n\t\t\t\tawait ctx.context.adapter.update<Passkey>({\n\t\t\t\t\tmodel: \"passkey\",\n\t\t\t\t\twhere: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tfield: \"id\",\n\t\t\t\t\t\t\tvalue: passkey.id,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tupdate: {\n\t\t\t\t\t\tcounter: verification.authenticationInfo.newCounter,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\tconst s = await ctx.context.internalAdapter.createSession(\n\t\t\t\t\tpasskey.userId,\n\t\t\t\t);\n\t\t\t\tif (!s) {\n\t\t\t\t\tthrow APIError.from(\n\t\t\t\t\t\t\"INTERNAL_SERVER_ERROR\",\n\t\t\t\t\t\tPASSKEY_ERROR_CODES.UNABLE_TO_CREATE_SESSION,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tconst user = await ctx.context.internalAdapter.findUserById(\n\t\t\t\t\tpasskey.userId,\n\t\t\t\t);\n\t\t\t\tif (!user) {\n\t\t\t\t\tthrow new APIError(\"INTERNAL_SERVER_ERROR\", {\n\t\t\t\t\t\tmessage: \"User not found\",\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tawait setSessionCookie(ctx, {\n\t\t\t\t\tsession: s,\n\t\t\t\t\tuser,\n\t\t\t\t});\n\t\t\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\t\t\tverificationToken,\n\t\t\t\t);\n\n\t\t\t\treturn ctx.json(\n\t\t\t\t\t{\n\t\t\t\t\t\tsession: s,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tstatus: 200,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t} catch (e) {\n\t\t\t\tctx.context.logger.error(\"Failed to verify authentication\", e);\n\t\t\t\tthrow APIError.from(\n\t\t\t\t\t\"BAD_REQUEST\",\n\t\t\t\t\tPASSKEY_ERROR_CODES.AUTHENTICATION_FAILED,\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\t);\n\n/**\n * ### Endpoint\n *\n * GET `/passkey/list-user-passkeys`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.listPasskeys`\n *\n * **client:**\n * `authClient.passkey.listUserPasskeys`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/passkey#api-method-passkey-list-user-passkeys)\n */\nexport const listPasskeys = createAuthEndpoint(\n\t\"/passkey/list-user-passkeys\",\n\t{\n\t\tmethod: \"GET\",\n\t\tuse: [sessionMiddleware],\n\t\tmetadata: {\n\t\t\topenapi: {\n\t\t\t\tdescription: \"List all passkeys for the authenticated user\",\n\t\t\t\tresponses: {\n\t\t\t\t\t\"200\": {\n\t\t\t\t\t\tdescription: \"Passkeys retrieved successfully\",\n\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\ttype: \"array\",\n\t\t\t\t\t\t\t\t\titems: {\n\t\t\t\t\t\t\t\t\t\t$ref: \"#/components/schemas/Passkey\",\n\t\t\t\t\t\t\t\t\t\trequired: [\n\t\t\t\t\t\t\t\t\t\t\t\"id\",\n\t\t\t\t\t\t\t\t\t\t\t\"userId\",\n\t\t\t\t\t\t\t\t\t\t\t\"publicKey\",\n\t\t\t\t\t\t\t\t\t\t\t\"createdAt\",\n\t\t\t\t\t\t\t\t\t\t\t\"updatedAt\",\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\"Array of passkey objects associated with the user\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t},\n\tasync (ctx) => {\n\t\tconst passkeys = await ctx.context.adapter.findMany<Passkey>({\n\t\t\tmodel: \"passkey\",\n\t\t\twhere: [{ field: \"userId\", value: ctx.context.session.user.id }],\n\t\t});\n\t\treturn ctx.json(passkeys, {\n\t\t\tstatus: 200,\n\t\t});\n\t},\n);\n\nconst deletePasskeyBodySchema = z.object({\n\tid: z.string().meta({\n\t\tdescription: 'The ID of the passkey to delete. Eg: \"some-passkey-id\"',\n\t}),\n});\n\n/**\n * ### Endpoint\n *\n * POST `/passkey/delete-passkey`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.deletePasskey`\n *\n * **client:**\n * `authClient.passkey.deletePasskey`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/passkey#api-method-passkey-delete-passkey)\n */\nexport const deletePasskey = createAuthEndpoint(\n\t\"/passkey/delete-passkey\",\n\t{\n\t\tmethod: \"POST\",\n\t\tbody: deletePasskeyBodySchema,\n\t\tuse: [sessionMiddleware],\n\t\tmetadata: {\n\t\t\topenapi: {\n\t\t\t\tdescription: \"Delete a specific passkey\",\n\t\t\t\tresponses: {\n\t\t\t\t\t\"200\": {\n\t\t\t\t\t\tdescription: \"Passkey deleted successfully\",\n\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\tstatus: {\n\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\"Indicates whether the deletion was successful\",\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\trequired: [\"status\"],\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t},\n\tasync (ctx) => {\n\t\tconst passkey = await ctx.context.adapter.findOne<Passkey>({\n\t\t\tmodel: \"passkey\",\n\t\t\twhere: [\n\t\t\t\t{\n\t\t\t\t\tfield: \"id\",\n\t\t\t\t\tvalue: ctx.body.id,\n\t\t\t\t},\n\t\t\t],\n\t\t});\n\t\tif (!passkey) {\n\t\t\tthrow APIError.from(\"NOT_FOUND\", PASSKEY_ERROR_CODES.PASSKEY_NOT_FOUND);\n\t\t}\n\t\tif (passkey.userId !== ctx.context.session.user.id) {\n\t\t\tthrow new APIError(\"UNAUTHORIZED\");\n\t\t}\n\t\tawait ctx.context.adapter.delete({\n\t\t\tmodel: \"passkey\",\n\t\t\twhere: [{ field: \"id\", value: passkey.id }],\n\t\t});\n\t\treturn ctx.json({\n\t\t\tstatus: true,\n\t\t});\n\t},\n);\n\nconst updatePassKeyBodySchema = z.object({\n\tid: z.string().meta({\n\t\tdescription: `The ID of the passkey which will be updated. Eg: \\\"passkey-id\\\"`,\n\t}),\n\tname: z.string().meta({\n\t\tdescription: `The new name which the passkey will be updated to. Eg: \\\"my-new-passkey-name\\\"`,\n\t}),\n});\n\n/**\n * ### Endpoint\n *\n * POST `/passkey/update-passkey`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.updatePasskey`\n *\n * **client:**\n * `authClient.passkey.updatePasskey`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/passkey#api-method-passkey-update-passkey)\n */\nexport const updatePasskey = createAuthEndpoint(\n\t\"/passkey/update-passkey\",\n\t{\n\t\tmethod: \"POST\",\n\t\tbody: updatePassKeyBodySchema,\n\t\tuse: [sessionMiddleware],\n\t\tmetadata: {\n\t\t\topenapi: {\n\t\t\t\tdescription: \"Update a specific passkey's name\",\n\t\t\t\tresponses: {\n\t\t\t\t\t\"200\": {\n\t\t\t\t\t\tdescription: \"Passkey updated successfully\",\n\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\tpasskey: {\n\t\t\t\t\t\t\t\t\t\t\t$ref: \"#/components/schemas/Passkey\",\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\trequired: [\"passkey\"],\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t},\n\tasync (ctx) => {\n\t\tconst passkey = await ctx.context.adapter.findOne<Passkey>({\n\t\t\tmodel: \"passkey\",\n\t\t\twhere: [\n\t\t\t\t{\n\t\t\t\t\tfield: \"id\",\n\t\t\t\t\tvalue: ctx.body.id,\n\t\t\t\t},\n\t\t\t],\n\t\t});\n\n\t\tif (!passkey) {\n\t\t\tthrow APIError.from(\"NOT_FOUND\", PASSKEY_ERROR_CODES.PASSKEY_NOT_FOUND);\n\t\t}\n\n\t\tif (passkey.userId !== ctx.context.session.user.id) {\n\t\t\tthrow APIError.from(\n\t\t\t\t\"UNAUTHORIZED\",\n\t\t\t\tPASSKEY_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_REGISTER_THIS_PASSKEY,\n\t\t\t);\n\t\t}\n\n\t\tconst updatedPasskey = await ctx.context.adapter.update<Passkey>({\n\t\t\tmodel: \"passkey\",\n\t\t\twhere: [\n\t\t\t\t{\n\t\t\t\t\tfield: \"id\",\n\t\t\t\t\tvalue: ctx.body.id,\n\t\t\t\t},\n\t\t\t],\n\t\t\tupdate: {\n\t\t\t\tname: ctx.body.name,\n\t\t\t},\n\t\t});\n\n\t\tif (!updatedPasskey) {\n\t\t\tthrow APIError.from(\n\t\t\t\t\"INTERNAL_SERVER_ERROR\",\n\t\t\t\tPASSKEY_ERROR_CODES.FAILED_TO_UPDATE_PASSKEY,\n\t\t\t);\n\t\t}\n\t\treturn ctx.json(\n\t\t\t{\n\t\t\t\tpasskey: updatedPasskey,\n\t\t\t},\n\t\t\t{\n\t\t\t\tstatus: 200,\n\t\t\t},\n\t\t);\n\t},\n);\n","import type { BetterAuthPluginDBSchema } from \"@better-auth/core/db\";\n\nexport const schema = {\n\tpasskey: {\n\t\tfields: {\n\t\t\tname: {\n\t\t\t\ttype: \"string\",\n\t\t\t\trequired: false,\n\t\t\t},\n\t\t\tpublicKey: {\n\t\t\t\ttype: \"string\",\n\t\t\t\trequired: true,\n\t\t\t},\n\t\t\tuserId: {\n\t\t\t\ttype: \"string\",\n\t\t\t\treferences: {\n\t\t\t\t\tmodel: \"user\",\n\t\t\t\t\tfield: \"id\",\n\t\t\t\t},\n\t\t\t\trequired: true,\n\t\t\t\tindex: true,\n\t\t\t},\n\t\t\tcredentialID: {\n\t\t\t\ttype: \"string\",\n\t\t\t\trequired: true,\n\t\t\t\tindex: true,\n\t\t\t},\n\t\t\tcounter: {\n\t\t\t\ttype: \"number\",\n\t\t\t\trequired: true,\n\t\t\t},\n\t\t\tdeviceType: {\n\t\t\t\ttype: \"string\",\n\t\t\t\trequired: true,\n\t\t\t},\n\t\t\tbackedUp: {\n\t\t\t\ttype: \"boolean\",\n\t\t\t\trequired: true,\n\t\t\t},\n\t\t\ttransports: {\n\t\t\t\ttype: \"string\",\n\t\t\t\trequired: false,\n\t\t\t},\n\t\t\tcreatedAt: {\n\t\t\t\ttype: \"date\",\n\t\t\t\trequired: false,\n\t\t\t},\n\t\t\taaguid: {\n\t\t\t\ttype: \"string\",\n\t\t\t\trequired: false,\n\t\t\t},\n\t\t},\n\t},\n} satisfies BetterAuthPluginDBSchema;\n","import type { BetterAuthPlugin } from \"@better-auth/core\";\nimport { mergeSchema } from \"better-auth/db\";\nimport { PASSKEY_ERROR_CODES } from \"./error-codes\";\nimport {\n\tdeletePasskey,\n\tgeneratePasskeyAuthenticationOptions,\n\tgeneratePasskeyRegistrationOptions,\n\tlistPasskeys,\n\tupdatePasskey,\n\tverifyPasskeyAuthentication,\n\tverifyPasskeyRegistration,\n} from \"./routes\";\nimport { schema } from \"./schema\";\nimport type { Passkey, PasskeyOptions } from \"./types\";\n\ndeclare module \"@better-auth/core\" {\n\tinterface BetterAuthPluginRegistry<AuthOptions, Options> {\n\t\tpasskey: {\n\t\t\tcreator: typeof passkey;\n\t\t};\n\t}\n}\n\nexport { PASSKEY_ERROR_CODES } from \"./error-codes\";\n\nconst MAX_AGE_IN_SECONDS = 60 * 5; // 5 minutes\n\nexport const passkey = (options?: PasskeyOptions | undefined) => {\n\tconst opts = {\n\t\torigin: null,\n\t\t...options,\n\t\tadvanced: {\n\t\t\twebAuthnChallengeCookie: \"better-auth-passkey\",\n\t\t\t...options?.advanced,\n\t\t},\n\t};\n\n\treturn {\n\t\tid: \"passkey\",\n\t\tendpoints: {\n\t\t\tgeneratePasskeyRegistrationOptions: generatePasskeyRegistrationOptions(\n\t\t\t\topts,\n\t\t\t\t{ maxAgeInSeconds: MAX_AGE_IN_SECONDS },\n\t\t\t),\n\t\t\tgeneratePasskeyAuthenticationOptions:\n\t\t\t\tgeneratePasskeyAuthenticationOptions(opts, {\n\t\t\t\t\tmaxAgeInSeconds: MAX_AGE_IN_SECONDS,\n\t\t\t\t}),\n\t\t\tverifyPasskeyRegistration: verifyPasskeyRegistration(opts),\n\t\t\tverifyPasskeyAuthentication: verifyPasskeyAuthentication(opts),\n\t\t\tlistPasskeys,\n\t\t\tdeletePasskey,\n\t\t\tupdatePasskey,\n\t\t},\n\t\tschema: mergeSchema(schema, options?.schema),\n\t\t$ERROR_CODES: PASSKEY_ERROR_CODES,\n\t\toptions,\n\t} satisfies BetterAuthPlugin;\n};\n\nexport type { Passkey, PasskeyOptions };\n"],"mappings":"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"}
|