@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 CHANGED
@@ -1,4 +1,5 @@
1
- import { a as WebAuthnChallengeValue, i as PasskeyOptions, n as PASSKEY_ERROR_CODES, r as Passkey, t as passkey } from "./index-BfRDyiNp.mjs";
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: better_auth0.RawError<"AUTH_CANCELLED">;
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
- data: null;
83
- error: {
84
- code: "ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED";
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 | better_auth0.RawError<"UNKNOWN_ERROR">;
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: better_auth0.RawError<"AUTH_CANCELLED">;
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
- data: null;
182
- error: {
183
- code: "ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED";
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 | better_auth0.RawError<"UNKNOWN_ERROR">;
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 { t as PASSKEY_ERROR_CODES } from "./error-codes-BwAsYefH.mjs";
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: await startAuthentication({
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: options.data,
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: res,
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 { WebAuthnChallengeValue as a, PasskeyOptions as i, PASSKEY_ERROR_CODES as n, Passkey as r, passkey as t };
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 { i as PasskeyOptions, n as PASSKEY_ERROR_CODES, r as Passkey, t as passkey } from "./index-BfRDyiNp.mjs";
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 { t as PASSKEY_ERROR_CODES } from "./error-codes-BwAsYefH.mjs";
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 }) => createAuthEndpoint("/passkey/generate-register-options", {
22
- method: "GET",
23
- use: [freshSessionMiddleware],
24
- query: generatePasskeyQuerySchema,
25
- metadata: { openapi: {
26
- operationId: "generatePasskeyRegistrationOptions",
27
- description: "Generate registration options for a new passkey",
28
- responses: { 200: {
29
- description: "Success",
30
- parameters: { query: {
31
- authenticatorAttachment: {
32
- description: `Type of authenticator to use for registration.
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
- required: false
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
- user: {
55
- type: "object",
56
- properties: {
57
- id: { type: "string" },
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
- pubKeyCredParams: {
63
- type: "array",
64
- items: {
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
- type: { type: "string" },
68
- alg: { type: "number" }
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
- type: { type: "string" },
80
- transports: {
81
- type: "array",
82
- items: { type: "string" }
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
- authenticatorSelection: {
88
- type: "object",
89
- properties: {
90
- authenticatorAttachment: { type: "string" },
91
- requireResidentKey: { type: "boolean" },
92
- userVerification: { type: "string" }
93
- }
94
- },
95
- attestation: { type: "string" },
96
- extensions: { type: "object" }
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
- }, async (ctx) => {
102
- const { session } = ctx.context;
103
- const userPasskeys = await ctx.context.adapter.findMany({
104
- model: "passkey",
105
- where: [{
106
- field: "userId",
107
- value: session.user.id
108
- }]
109
- });
110
- const userID = new TextEncoder().encode(generateRandomString(32, "a-z", "0-9"));
111
- const baseURLString = typeof ctx.context.options.baseURL === "string" ? ctx.context.options.baseURL : void 0;
112
- const options = await generateRegistrationOptions({
113
- rpName: opts.rpName || ctx.context.appName,
114
- rpID: getRpID(opts, baseURLString),
115
- userID,
116
- userName: ctx.query?.name || session.user.email || session.user.id,
117
- userDisplayName: session.user.email || session.user.id,
118
- attestationType: "none",
119
- excludeCredentials: userPasskeys.map((passkey) => ({
120
- id: passkey.credentialID,
121
- transports: passkey.transports?.split(",")
122
- })),
123
- authenticatorSelection: {
124
- residentKey: "preferred",
125
- userVerification: "preferred",
126
- ...opts.authenticatorSelection || {},
127
- ...ctx.query?.authenticatorAttachment ? { authenticatorAttachment: ctx.query.authenticatorAttachment } : {}
128
- }
129
- });
130
- const verificationToken = generateRandomString(32);
131
- const webAuthnCookie = ctx.context.createAuthCookie(opts.advanced.webAuthnChallengeCookie);
132
- await ctx.setSignedCookie(webAuthnCookie.name, verificationToken, ctx.context.secret, {
133
- ...webAuthnCookie.attributes,
134
- maxAge: maxAgeInSeconds
135
- });
136
- const expirationTime = new Date(Date.now() + maxAgeInSeconds * 1e3);
137
- await ctx.context.internalAdapter.createVerificationValue({
138
- identifier: verificationToken,
139
- value: JSON.stringify({
140
- expectedChallenge: options.challenge,
141
- userData: { id: session.user.id }
142
- }),
143
- expiresAt: expirationTime
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
- return ctx.json(options, { status: 200 });
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, typeof ctx.context.options.baseURL === "string" ? ctx.context.options.baseURL : void 0),
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) => createAuthEndpoint("/passkey/verify-registration", {
243
- method: "POST",
244
- body: verifyPasskeyRegistrationBodySchema,
245
- use: [freshSessionMiddleware],
246
- metadata: { openapi: {
247
- operationId: "passkeyVerifyRegistration",
248
- description: "Verify registration of a new passkey",
249
- responses: {
250
- 200: {
251
- description: "Success",
252
- content: { "application/json": { schema: { $ref: "#/components/schemas/Passkey" } } }
253
- },
254
- 400: { description: "Bad request" }
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
- }, async (ctx) => {
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
- export { PASSKEY_ERROR_CODES as t };
18
-
19
- //# sourceMappingURL=error-codes-BwAsYefH.mjs.map
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.5.7-beta.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.5.7-beta.1",
58
- "better-auth": "1.5.7-beta.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.3.1",
62
+ "@better-auth/utils": "0.4.0",
62
63
  "@better-fetch/fetch": "1.1.21",
63
- "better-call": "2.0.2",
64
+ "better-call": "2.0.3",
64
65
  "nanostores": "^1.0.1",
65
- "@better-auth/core": "1.5.7-beta.1",
66
- "better-auth": "1.5.7-beta.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",
@@ -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"}
@@ -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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"}