@better-auth/passkey 1.4.6-beta.2 → 1.4.6-beta.6

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,4 @@
1
- import { n as Passkey, t as passkey } from "./index-DhzUw_n7.mjs";
1
+ import { i as WebAuthnChallengeValue, n as Passkey, r as PasskeyOptions, t as passkey } from "./index-HkgxjeuE.mjs";
2
2
  import * as better_auth0 from "better-auth";
3
3
  import * as nanostores0 from "nanostores";
4
4
  import { atom } from "nanostores";
@@ -6,6 +6,7 @@ import * as _better_fetch_fetch0 from "@better-fetch/fetch";
6
6
  import { BetterFetch } from "@better-fetch/fetch";
7
7
  import { ClientFetchOption, ClientStore } from "@better-auth/core";
8
8
  import { Session, User } from "better-auth/types";
9
+ export * from "@simplewebauthn/server";
9
10
 
10
11
  //#region src/client.d.ts
11
12
  declare const getPasskeyActions: ($fetch: BetterFetch, {
@@ -194,7 +195,7 @@ declare const passkeyClient: () => {
194
195
  "/passkey/authenticate": "POST";
195
196
  };
196
197
  atomListeners: ({
197
- matcher(path: string): path is "/passkey/verify-registration" | "/passkey/delete-passkey" | "/passkey/update-passkey" | "/sign-out";
198
+ matcher(path: string): path is "/passkey/delete-passkey" | "/passkey/update-passkey" | "/passkey/verify-registration" | "/sign-out";
198
199
  signal: "$listPasskeys";
199
200
  } | {
200
201
  matcher: (path: string) => path is "/passkey/verify-authentication";
@@ -202,4 +203,4 @@ declare const passkeyClient: () => {
202
203
  })[];
203
204
  };
204
205
  //#endregion
205
- export { getPasskeyActions, passkeyClient };
206
+ export { Passkey, PasskeyOptions, WebAuthnChallengeValue, getPasskeyActions, passkeyClient };
@@ -1,7 +1,8 @@
1
1
  import * as _simplewebauthn_server0 from "@simplewebauthn/server";
2
- import { AuthenticationResponseJSON, CredentialDeviceType, PublicKeyCredentialCreationOptionsJSON } from "@simplewebauthn/server";
2
+ import { CredentialDeviceType } from "@simplewebauthn/server";
3
+ import * as better_auth0 from "better-auth";
3
4
  import * as better_call0 from "better-call";
4
- import * as z from "zod";
5
+ import * as zod0 from "zod";
5
6
  import { InferOptionSchema } from "better-auth/types";
6
7
 
7
8
  //#region src/schema.d.ts
@@ -59,7 +60,15 @@ declare const schema: {
59
60
  };
60
61
  //#endregion
61
62
  //#region src/types.d.ts
62
-
63
+ /**
64
+ * @internal
65
+ */
66
+ interface WebAuthnChallengeValue {
67
+ expectedChallenge: string;
68
+ userData: {
69
+ id: string;
70
+ };
71
+ }
63
72
  interface PasskeyOptions {
64
73
  /**
65
74
  * A unique identifier for your website. 'localhost' is okay for
@@ -147,13 +156,13 @@ declare const passkey: (options?: PasskeyOptions | undefined) => {
147
156
  };
148
157
  };
149
158
  }>)[];
150
- query: z.ZodOptional<z.ZodObject<{
151
- authenticatorAttachment: z.ZodOptional<z.ZodEnum<{
159
+ query: zod0.ZodOptional<zod0.ZodObject<{
160
+ authenticatorAttachment: zod0.ZodOptional<zod0.ZodEnum<{
152
161
  platform: "platform";
153
162
  "cross-platform": "cross-platform";
154
163
  }>>;
155
- name: z.ZodOptional<z.ZodString>;
156
- }, z.core.$strip>>;
164
+ name: zod0.ZodOptional<zod0.ZodString>;
165
+ }, better_auth0.$strip>>;
157
166
  metadata: {
158
167
  openapi: {
159
168
  operationId: string;
@@ -273,7 +282,7 @@ declare const passkey: (options?: PasskeyOptions | undefined) => {
273
282
  };
274
283
  } & {
275
284
  use: any[];
276
- }, PublicKeyCredentialCreationOptionsJSON>;
285
+ }, _simplewebauthn_server0.PublicKeyCredentialCreationOptionsJSON>;
277
286
  generatePasskeyAuthenticationOptions: better_call0.StrictEndpoint<"/passkey/generate-authenticate-options", {
278
287
  method: "GET";
279
288
  metadata: {
@@ -372,10 +381,10 @@ declare const passkey: (options?: PasskeyOptions | undefined) => {
372
381
  }, _simplewebauthn_server0.PublicKeyCredentialRequestOptionsJSON>;
373
382
  verifyPasskeyRegistration: better_call0.StrictEndpoint<"/passkey/verify-registration", {
374
383
  method: "POST";
375
- body: z.ZodObject<{
376
- response: z.ZodAny;
377
- name: z.ZodOptional<z.ZodString>;
378
- }, z.core.$strip>;
384
+ body: zod0.ZodObject<{
385
+ response: zod0.ZodAny;
386
+ name: zod0.ZodOptional<zod0.ZodString>;
387
+ }, better_auth0.$strip>;
379
388
  use: ((inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<{
380
389
  session: {
381
390
  session: Record<string, any> & {
@@ -425,9 +434,9 @@ declare const passkey: (options?: PasskeyOptions | undefined) => {
425
434
  }, Passkey | null>;
426
435
  verifyPasskeyAuthentication: better_call0.StrictEndpoint<"/passkey/verify-authentication", {
427
436
  method: "POST";
428
- body: z.ZodObject<{
429
- response: z.ZodRecord<z.ZodAny, z.ZodAny>;
430
- }, z.core.$strip>;
437
+ body: zod0.ZodObject<{
438
+ response: zod0.ZodRecord<zod0.ZodAny, zod0.ZodAny>;
439
+ }, better_auth0.$strip>;
431
440
  metadata: {
432
441
  openapi: {
433
442
  operationId: string;
@@ -455,7 +464,7 @@ declare const passkey: (options?: PasskeyOptions | undefined) => {
455
464
  };
456
465
  $Infer: {
457
466
  body: {
458
- response: AuthenticationResponseJSON;
467
+ response: _simplewebauthn_server0.AuthenticationResponseJSON;
459
468
  };
460
469
  };
461
470
  };
@@ -473,21 +482,6 @@ declare const passkey: (options?: PasskeyOptions | undefined) => {
473
482
  userAgent?: string | null | undefined;
474
483
  };
475
484
  }>;
476
- /**
477
- * ### Endpoint
478
- *
479
- * GET `/passkey/list-user-passkeys`
480
- *
481
- * ### API Methods
482
- *
483
- * **server:**
484
- * `auth.api.listPasskeys`
485
- *
486
- * **client:**
487
- * `authClient.passkey.listUserPasskeys`
488
- *
489
- * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/passkey#api-method-passkey-list-user-passkeys)
490
- */
491
485
  listPasskeys: better_call0.StrictEndpoint<"/passkey/list-user-passkeys", {
492
486
  method: "GET";
493
487
  use: ((inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<{
@@ -538,26 +532,11 @@ declare const passkey: (options?: PasskeyOptions | undefined) => {
538
532
  } & {
539
533
  use: any[];
540
534
  }, Passkey[]>;
541
- /**
542
- * ### Endpoint
543
- *
544
- * POST `/passkey/delete-passkey`
545
- *
546
- * ### API Methods
547
- *
548
- * **server:**
549
- * `auth.api.deletePasskey`
550
- *
551
- * **client:**
552
- * `authClient.passkey.deletePasskey`
553
- *
554
- * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/passkey#api-method-passkey-delete-passkey)
555
- */
556
535
  deletePasskey: better_call0.StrictEndpoint<"/passkey/delete-passkey", {
557
536
  method: "POST";
558
- body: z.ZodObject<{
559
- id: z.ZodString;
560
- }, z.core.$strip>;
537
+ body: zod0.ZodObject<{
538
+ id: zod0.ZodString;
539
+ }, better_auth0.$strip>;
561
540
  use: ((inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<{
562
541
  session: {
563
542
  session: Record<string, any> & {
@@ -610,27 +589,12 @@ declare const passkey: (options?: PasskeyOptions | undefined) => {
610
589
  }, {
611
590
  status: boolean;
612
591
  }>;
613
- /**
614
- * ### Endpoint
615
- *
616
- * POST `/passkey/update-passkey`
617
- *
618
- * ### API Methods
619
- *
620
- * **server:**
621
- * `auth.api.updatePasskey`
622
- *
623
- * **client:**
624
- * `authClient.passkey.updatePasskey`
625
- *
626
- * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/passkey#api-method-passkey-update-passkey)
627
- */
628
592
  updatePasskey: better_call0.StrictEndpoint<"/passkey/update-passkey", {
629
593
  method: "POST";
630
- body: z.ZodObject<{
631
- id: z.ZodString;
632
- name: z.ZodString;
633
- }, z.core.$strip>;
594
+ body: zod0.ZodObject<{
595
+ id: zod0.ZodString;
596
+ name: zod0.ZodString;
597
+ }, better_auth0.$strip>;
634
598
  use: ((inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<{
635
599
  session: {
636
600
  session: Record<string, any> & {
@@ -746,4 +710,4 @@ declare const passkey: (options?: PasskeyOptions | undefined) => {
746
710
  };
747
711
  };
748
712
  //#endregion
749
- export { Passkey as n, PasskeyOptions as r, passkey as t };
713
+ export { WebAuthnChallengeValue as i, Passkey as n, PasskeyOptions as r, passkey as t };
package/dist/index.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { n as Passkey, r as PasskeyOptions, t as passkey } from "./index-DhzUw_n7.mjs";
1
+ import { n as Passkey, r as PasskeyOptions, t as passkey } from "./index-HkgxjeuE.mjs";
2
2
  export { Passkey, PasskeyOptions, passkey };
package/dist/index.mjs CHANGED
@@ -1,3 +1,5 @@
1
+ import { mergeSchema } from "better-auth/db";
2
+ import { defineErrorCodes } from "@better-auth/core/utils";
1
3
  import { createAuthEndpoint } from "@better-auth/core/api";
2
4
  import { base64 } from "@better-auth/utils/base64";
3
5
  import { generateAuthenticationOptions, generateRegistrationOptions, verifyAuthenticationResponse, verifyRegistrationResponse } from "@simplewebauthn/server";
@@ -5,10 +7,8 @@ import { generateId } from "better-auth";
5
7
  import { freshSessionMiddleware, getSessionFromCtx, sessionMiddleware } from "better-auth/api";
6
8
  import { setSessionCookie } from "better-auth/cookies";
7
9
  import { generateRandomString } from "better-auth/crypto";
8
- import { mergeSchema } from "better-auth/db";
9
10
  import { APIError } from "better-call";
10
11
  import * as z from "zod";
11
- import { defineErrorCodes } from "@better-auth/core/utils";
12
12
 
13
13
  //#region src/error-codes.ts
14
14
  const PASSKEY_ERROR_CODES = defineErrorCodes({
@@ -21,6 +21,530 @@ const PASSKEY_ERROR_CODES = defineErrorCodes({
21
21
  FAILED_TO_UPDATE_PASSKEY: "Failed to update passkey"
22
22
  });
23
23
 
24
+ //#endregion
25
+ //#region src/utils.ts
26
+ function getRpID(options, baseURL) {
27
+ return options.rpID || (baseURL ? new URL(baseURL).hostname : "localhost");
28
+ }
29
+
30
+ //#endregion
31
+ //#region src/routes.ts
32
+ const generatePasskeyQuerySchema = z.object({
33
+ authenticatorAttachment: z.enum(["platform", "cross-platform"]).optional(),
34
+ name: z.string().optional()
35
+ }).optional();
36
+ const generatePasskeyRegistrationOptions = (opts, { maxAgeInSeconds, expirationTime }) => createAuthEndpoint("/passkey/generate-register-options", {
37
+ method: "GET",
38
+ use: [freshSessionMiddleware],
39
+ query: generatePasskeyQuerySchema,
40
+ metadata: { openapi: {
41
+ operationId: "generatePasskeyRegistrationOptions",
42
+ description: "Generate registration options for a new passkey",
43
+ responses: { 200: {
44
+ description: "Success",
45
+ parameters: { query: {
46
+ authenticatorAttachment: {
47
+ description: `Type of authenticator to use for registration.
48
+ "platform" for device-specific authenticators,
49
+ "cross-platform" for authenticators that can be used across devices.`,
50
+ required: false
51
+ },
52
+ name: {
53
+ description: `Optional custom name for the passkey.
54
+ This can help identify the passkey when managing multiple credentials.`,
55
+ required: false
56
+ }
57
+ } },
58
+ content: { "application/json": { schema: {
59
+ type: "object",
60
+ properties: {
61
+ challenge: { type: "string" },
62
+ rp: {
63
+ type: "object",
64
+ properties: {
65
+ name: { type: "string" },
66
+ id: { type: "string" }
67
+ }
68
+ },
69
+ user: {
70
+ type: "object",
71
+ properties: {
72
+ id: { type: "string" },
73
+ name: { type: "string" },
74
+ displayName: { type: "string" }
75
+ }
76
+ },
77
+ pubKeyCredParams: {
78
+ type: "array",
79
+ items: {
80
+ type: "object",
81
+ properties: {
82
+ type: { type: "string" },
83
+ alg: { type: "number" }
84
+ }
85
+ }
86
+ },
87
+ timeout: { type: "number" },
88
+ excludeCredentials: {
89
+ type: "array",
90
+ items: {
91
+ type: "object",
92
+ properties: {
93
+ id: { type: "string" },
94
+ type: { type: "string" },
95
+ transports: {
96
+ type: "array",
97
+ items: { type: "string" }
98
+ }
99
+ }
100
+ }
101
+ },
102
+ authenticatorSelection: {
103
+ type: "object",
104
+ properties: {
105
+ authenticatorAttachment: { type: "string" },
106
+ requireResidentKey: { type: "boolean" },
107
+ userVerification: { type: "string" }
108
+ }
109
+ },
110
+ attestation: { type: "string" },
111
+ extensions: { type: "object" }
112
+ }
113
+ } } }
114
+ } }
115
+ } }
116
+ }, async (ctx) => {
117
+ const { session } = ctx.context;
118
+ const userPasskeys = await ctx.context.adapter.findMany({
119
+ model: "passkey",
120
+ where: [{
121
+ field: "userId",
122
+ value: session.user.id
123
+ }]
124
+ });
125
+ const userID = new TextEncoder().encode(generateRandomString(32, "a-z", "0-9"));
126
+ let options;
127
+ options = await generateRegistrationOptions({
128
+ rpName: opts.rpName || ctx.context.appName,
129
+ rpID: getRpID(opts, ctx.context.options.baseURL),
130
+ userID,
131
+ userName: ctx.query?.name || session.user.email || session.user.id,
132
+ userDisplayName: session.user.email || session.user.id,
133
+ attestationType: "none",
134
+ excludeCredentials: userPasskeys.map((passkey$1) => ({
135
+ id: passkey$1.credentialID,
136
+ transports: passkey$1.transports?.split(",")
137
+ })),
138
+ authenticatorSelection: {
139
+ residentKey: "preferred",
140
+ userVerification: "preferred",
141
+ ...opts.authenticatorSelection || {},
142
+ ...ctx.query?.authenticatorAttachment ? { authenticatorAttachment: ctx.query.authenticatorAttachment } : {}
143
+ }
144
+ });
145
+ const id = generateId(32);
146
+ const webAuthnCookie = ctx.context.createAuthCookie(opts.advanced.webAuthnChallengeCookie);
147
+ await ctx.setSignedCookie(webAuthnCookie.name, id, ctx.context.secret, {
148
+ ...webAuthnCookie.attributes,
149
+ maxAge: maxAgeInSeconds
150
+ });
151
+ await ctx.context.internalAdapter.createVerificationValue({
152
+ identifier: id,
153
+ value: JSON.stringify({
154
+ expectedChallenge: options.challenge,
155
+ userData: { id: session.user.id }
156
+ }),
157
+ expiresAt: expirationTime
158
+ });
159
+ return ctx.json(options, { status: 200 });
160
+ });
161
+ const generatePasskeyAuthenticationOptions = (opts, { maxAgeInSeconds, expirationTime }) => createAuthEndpoint("/passkey/generate-authenticate-options", {
162
+ method: "GET",
163
+ metadata: { openapi: {
164
+ operationId: "passkeyGenerateAuthenticateOptions",
165
+ description: "Generate authentication options for a passkey",
166
+ responses: { 200: {
167
+ description: "Success",
168
+ content: { "application/json": { schema: {
169
+ type: "object",
170
+ properties: {
171
+ challenge: { type: "string" },
172
+ rp: {
173
+ type: "object",
174
+ properties: {
175
+ name: { type: "string" },
176
+ id: { type: "string" }
177
+ }
178
+ },
179
+ user: {
180
+ type: "object",
181
+ properties: {
182
+ id: { type: "string" },
183
+ name: { type: "string" },
184
+ displayName: { type: "string" }
185
+ }
186
+ },
187
+ timeout: { type: "number" },
188
+ allowCredentials: {
189
+ type: "array",
190
+ items: {
191
+ type: "object",
192
+ properties: {
193
+ id: { type: "string" },
194
+ type: { type: "string" },
195
+ transports: {
196
+ type: "array",
197
+ items: { type: "string" }
198
+ }
199
+ }
200
+ }
201
+ },
202
+ userVerification: { type: "string" },
203
+ authenticatorSelection: {
204
+ type: "object",
205
+ properties: {
206
+ authenticatorAttachment: { type: "string" },
207
+ requireResidentKey: { type: "boolean" },
208
+ userVerification: { type: "string" }
209
+ }
210
+ },
211
+ extensions: { type: "object" }
212
+ }
213
+ } } }
214
+ } }
215
+ } }
216
+ }, async (ctx) => {
217
+ const session = await getSessionFromCtx(ctx);
218
+ let userPasskeys = [];
219
+ if (session) userPasskeys = await ctx.context.adapter.findMany({
220
+ model: "passkey",
221
+ where: [{
222
+ field: "userId",
223
+ value: session.user.id
224
+ }]
225
+ });
226
+ const options = await generateAuthenticationOptions({
227
+ rpID: getRpID(opts, ctx.context.options.baseURL),
228
+ userVerification: "preferred",
229
+ ...userPasskeys.length ? { allowCredentials: userPasskeys.map((passkey$1) => ({
230
+ id: passkey$1.credentialID,
231
+ transports: passkey$1.transports?.split(",")
232
+ })) } : {}
233
+ });
234
+ const data = {
235
+ expectedChallenge: options.challenge,
236
+ userData: { id: session?.user.id || "" }
237
+ };
238
+ const id = generateId(32);
239
+ const webAuthnCookie = ctx.context.createAuthCookie(opts.advanced.webAuthnChallengeCookie);
240
+ await ctx.setSignedCookie(webAuthnCookie.name, id, ctx.context.secret, {
241
+ ...webAuthnCookie.attributes,
242
+ maxAge: maxAgeInSeconds
243
+ });
244
+ await ctx.context.internalAdapter.createVerificationValue({
245
+ identifier: id,
246
+ value: JSON.stringify(data),
247
+ expiresAt: expirationTime
248
+ });
249
+ return ctx.json(options, { status: 200 });
250
+ });
251
+ const verifyPasskeyRegistrationBodySchema = z.object({
252
+ response: z.any(),
253
+ name: z.string().meta({ description: "Name of the passkey" }).optional()
254
+ });
255
+ const verifyPasskeyRegistration = (options) => createAuthEndpoint("/passkey/verify-registration", {
256
+ method: "POST",
257
+ body: verifyPasskeyRegistrationBodySchema,
258
+ use: [freshSessionMiddleware],
259
+ metadata: { openapi: {
260
+ operationId: "passkeyVerifyRegistration",
261
+ description: "Verify registration of a new passkey",
262
+ responses: {
263
+ 200: {
264
+ description: "Success",
265
+ content: { "application/json": { schema: { $ref: "#/components/schemas/Passkey" } } }
266
+ },
267
+ 400: { description: "Bad request" }
268
+ }
269
+ } }
270
+ }, async (ctx) => {
271
+ const origin = options?.origin || ctx.headers?.get("origin") || "";
272
+ if (!origin) return ctx.json(null, { status: 400 });
273
+ const resp = ctx.body.response;
274
+ const webAuthnCookie = ctx.context.createAuthCookie(options.advanced.webAuthnChallengeCookie);
275
+ const challengeId = await ctx.getSignedCookie(webAuthnCookie.name, ctx.context.secret);
276
+ if (!challengeId) throw new APIError("BAD_REQUEST", { message: PASSKEY_ERROR_CODES.CHALLENGE_NOT_FOUND });
277
+ const data = await ctx.context.internalAdapter.findVerificationValue(challengeId);
278
+ if (!data) return ctx.json(null, { status: 400 });
279
+ const { expectedChallenge, userData } = JSON.parse(data.value);
280
+ if (userData.id !== ctx.context.session.user.id) throw new APIError("UNAUTHORIZED", { message: PASSKEY_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_REGISTER_THIS_PASSKEY });
281
+ try {
282
+ const { verified, registrationInfo } = await verifyRegistrationResponse({
283
+ response: resp,
284
+ expectedChallenge,
285
+ expectedOrigin: origin,
286
+ expectedRPID: getRpID(options, ctx.context.options.baseURL),
287
+ requireUserVerification: false
288
+ });
289
+ if (!verified || !registrationInfo) return ctx.json(null, { status: 400 });
290
+ const { aaguid, credentialDeviceType, credentialBackedUp, credential, credentialType } = registrationInfo;
291
+ const pubKey = base64.encode(credential.publicKey);
292
+ const newPasskey = {
293
+ name: ctx.body.name,
294
+ userId: userData.id,
295
+ credentialID: credential.id,
296
+ publicKey: pubKey,
297
+ counter: credential.counter,
298
+ deviceType: credentialDeviceType,
299
+ transports: resp.response.transports.join(","),
300
+ backedUp: credentialBackedUp,
301
+ createdAt: /* @__PURE__ */ new Date(),
302
+ aaguid
303
+ };
304
+ const newPasskeyRes = await ctx.context.adapter.create({
305
+ model: "passkey",
306
+ data: newPasskey
307
+ });
308
+ return ctx.json(newPasskeyRes, { status: 200 });
309
+ } catch (e) {
310
+ console.log(e);
311
+ throw new APIError("INTERNAL_SERVER_ERROR", { message: PASSKEY_ERROR_CODES.FAILED_TO_VERIFY_REGISTRATION });
312
+ }
313
+ });
314
+ const verifyPasskeyAuthenticationBodySchema = z.object({ response: z.record(z.any(), z.any()) });
315
+ const verifyPasskeyAuthentication = (options) => createAuthEndpoint("/passkey/verify-authentication", {
316
+ method: "POST",
317
+ body: verifyPasskeyAuthenticationBodySchema,
318
+ metadata: {
319
+ openapi: {
320
+ operationId: "passkeyVerifyAuthentication",
321
+ description: "Verify authentication of a passkey",
322
+ responses: { 200: {
323
+ description: "Success",
324
+ content: { "application/json": { schema: {
325
+ type: "object",
326
+ properties: {
327
+ session: { $ref: "#/components/schemas/Session" },
328
+ user: { $ref: "#/components/schemas/User" }
329
+ }
330
+ } } }
331
+ } }
332
+ },
333
+ $Infer: { body: {} }
334
+ }
335
+ }, async (ctx) => {
336
+ const origin = options?.origin || ctx.headers?.get("origin") || "";
337
+ if (!origin) throw new APIError("BAD_REQUEST", { message: "origin missing" });
338
+ const resp = ctx.body.response;
339
+ const webAuthnCookie = ctx.context.createAuthCookie(options.advanced.webAuthnChallengeCookie);
340
+ const challengeId = await ctx.getSignedCookie(webAuthnCookie.name, ctx.context.secret);
341
+ if (!challengeId) throw new APIError("BAD_REQUEST", { message: PASSKEY_ERROR_CODES.CHALLENGE_NOT_FOUND });
342
+ const data = await ctx.context.internalAdapter.findVerificationValue(challengeId);
343
+ if (!data) throw new APIError("BAD_REQUEST", { message: PASSKEY_ERROR_CODES.CHALLENGE_NOT_FOUND });
344
+ const { expectedChallenge } = JSON.parse(data.value);
345
+ const passkey$1 = await ctx.context.adapter.findOne({
346
+ model: "passkey",
347
+ where: [{
348
+ field: "credentialID",
349
+ value: resp.id
350
+ }]
351
+ });
352
+ if (!passkey$1) throw new APIError("UNAUTHORIZED", { message: PASSKEY_ERROR_CODES.PASSKEY_NOT_FOUND });
353
+ try {
354
+ const verification = await verifyAuthenticationResponse({
355
+ response: resp,
356
+ expectedChallenge,
357
+ expectedOrigin: origin,
358
+ expectedRPID: getRpID(options, ctx.context.options.baseURL),
359
+ credential: {
360
+ id: passkey$1.credentialID,
361
+ publicKey: base64.decode(passkey$1.publicKey),
362
+ counter: passkey$1.counter,
363
+ transports: passkey$1.transports?.split(",")
364
+ },
365
+ requireUserVerification: false
366
+ });
367
+ const { verified } = verification;
368
+ if (!verified) throw new APIError("UNAUTHORIZED", { message: PASSKEY_ERROR_CODES.AUTHENTICATION_FAILED });
369
+ await ctx.context.adapter.update({
370
+ model: "passkey",
371
+ where: [{
372
+ field: "id",
373
+ value: passkey$1.id
374
+ }],
375
+ update: { counter: verification.authenticationInfo.newCounter }
376
+ });
377
+ const s = await ctx.context.internalAdapter.createSession(passkey$1.userId);
378
+ if (!s) throw new APIError("INTERNAL_SERVER_ERROR", { message: PASSKEY_ERROR_CODES.UNABLE_TO_CREATE_SESSION });
379
+ const user = await ctx.context.internalAdapter.findUserById(passkey$1.userId);
380
+ if (!user) throw new APIError("INTERNAL_SERVER_ERROR", { message: "User not found" });
381
+ await setSessionCookie(ctx, {
382
+ session: s,
383
+ user
384
+ });
385
+ return ctx.json({ session: s }, { status: 200 });
386
+ } catch (e) {
387
+ ctx.context.logger.error("Failed to verify authentication", e);
388
+ throw new APIError("BAD_REQUEST", { message: PASSKEY_ERROR_CODES.AUTHENTICATION_FAILED });
389
+ }
390
+ });
391
+ /**
392
+ * ### Endpoint
393
+ *
394
+ * GET `/passkey/list-user-passkeys`
395
+ *
396
+ * ### API Methods
397
+ *
398
+ * **server:**
399
+ * `auth.api.listPasskeys`
400
+ *
401
+ * **client:**
402
+ * `authClient.passkey.listUserPasskeys`
403
+ *
404
+ * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/passkey#api-method-passkey-list-user-passkeys)
405
+ */
406
+ const listPasskeys = createAuthEndpoint("/passkey/list-user-passkeys", {
407
+ method: "GET",
408
+ use: [sessionMiddleware],
409
+ metadata: { openapi: {
410
+ description: "List all passkeys for the authenticated user",
411
+ responses: { "200": {
412
+ description: "Passkeys retrieved successfully",
413
+ content: { "application/json": { schema: {
414
+ type: "array",
415
+ items: {
416
+ $ref: "#/components/schemas/Passkey",
417
+ required: [
418
+ "id",
419
+ "userId",
420
+ "publicKey",
421
+ "createdAt",
422
+ "updatedAt"
423
+ ]
424
+ },
425
+ description: "Array of passkey objects associated with the user"
426
+ } } }
427
+ } }
428
+ } }
429
+ }, async (ctx) => {
430
+ const passkeys = await ctx.context.adapter.findMany({
431
+ model: "passkey",
432
+ where: [{
433
+ field: "userId",
434
+ value: ctx.context.session.user.id
435
+ }]
436
+ });
437
+ return ctx.json(passkeys, { status: 200 });
438
+ });
439
+ const deletePasskeyBodySchema = z.object({ id: z.string().meta({ description: "The ID of the passkey to delete. Eg: \"some-passkey-id\"" }) });
440
+ /**
441
+ * ### Endpoint
442
+ *
443
+ * POST `/passkey/delete-passkey`
444
+ *
445
+ * ### API Methods
446
+ *
447
+ * **server:**
448
+ * `auth.api.deletePasskey`
449
+ *
450
+ * **client:**
451
+ * `authClient.passkey.deletePasskey`
452
+ *
453
+ * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/passkey#api-method-passkey-delete-passkey)
454
+ */
455
+ const deletePasskey = createAuthEndpoint("/passkey/delete-passkey", {
456
+ method: "POST",
457
+ body: deletePasskeyBodySchema,
458
+ use: [sessionMiddleware],
459
+ metadata: { openapi: {
460
+ description: "Delete a specific passkey",
461
+ responses: { "200": {
462
+ description: "Passkey deleted successfully",
463
+ content: { "application/json": { schema: {
464
+ type: "object",
465
+ properties: { status: {
466
+ type: "boolean",
467
+ description: "Indicates whether the deletion was successful"
468
+ } },
469
+ required: ["status"]
470
+ } } }
471
+ } }
472
+ } }
473
+ }, async (ctx) => {
474
+ const passkey$1 = await ctx.context.adapter.findOne({
475
+ model: "passkey",
476
+ where: [{
477
+ field: "id",
478
+ value: ctx.body.id
479
+ }]
480
+ });
481
+ if (!passkey$1) throw new APIError("NOT_FOUND", { message: PASSKEY_ERROR_CODES.PASSKEY_NOT_FOUND });
482
+ if (passkey$1.userId !== ctx.context.session.user.id) throw new APIError("UNAUTHORIZED");
483
+ await ctx.context.adapter.delete({
484
+ model: "passkey",
485
+ where: [{
486
+ field: "id",
487
+ value: passkey$1.id
488
+ }]
489
+ });
490
+ return ctx.json({ status: true });
491
+ });
492
+ const updatePassKeyBodySchema = z.object({
493
+ id: z.string().meta({ description: `The ID of the passkey which will be updated. Eg: \"passkey-id\"` }),
494
+ name: z.string().meta({ description: `The new name which the passkey will be updated to. Eg: \"my-new-passkey-name\"` })
495
+ });
496
+ /**
497
+ * ### Endpoint
498
+ *
499
+ * POST `/passkey/update-passkey`
500
+ *
501
+ * ### API Methods
502
+ *
503
+ * **server:**
504
+ * `auth.api.updatePasskey`
505
+ *
506
+ * **client:**
507
+ * `authClient.passkey.updatePasskey`
508
+ *
509
+ * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/passkey#api-method-passkey-update-passkey)
510
+ */
511
+ const updatePasskey = createAuthEndpoint("/passkey/update-passkey", {
512
+ method: "POST",
513
+ body: updatePassKeyBodySchema,
514
+ use: [sessionMiddleware],
515
+ metadata: { openapi: {
516
+ description: "Update a specific passkey's name",
517
+ responses: { "200": {
518
+ description: "Passkey updated successfully",
519
+ content: { "application/json": { schema: {
520
+ type: "object",
521
+ properties: { passkey: { $ref: "#/components/schemas/Passkey" } },
522
+ required: ["passkey"]
523
+ } } }
524
+ } }
525
+ } }
526
+ }, async (ctx) => {
527
+ const passkey$1 = await ctx.context.adapter.findOne({
528
+ model: "passkey",
529
+ where: [{
530
+ field: "id",
531
+ value: ctx.body.id
532
+ }]
533
+ });
534
+ if (!passkey$1) throw new APIError("NOT_FOUND", { message: PASSKEY_ERROR_CODES.PASSKEY_NOT_FOUND });
535
+ if (passkey$1.userId !== ctx.context.session.user.id) throw new APIError("UNAUTHORIZED", { message: PASSKEY_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_REGISTER_THIS_PASSKEY });
536
+ const updatedPasskey = await ctx.context.adapter.update({
537
+ model: "passkey",
538
+ where: [{
539
+ field: "id",
540
+ value: ctx.body.id
541
+ }],
542
+ update: { name: ctx.body.name }
543
+ });
544
+ if (!updatedPasskey) throw new APIError("INTERNAL_SERVER_ERROR", { message: PASSKEY_ERROR_CODES.FAILED_TO_UPDATE_PASSKEY });
545
+ return ctx.json({ passkey: updatedPasskey }, { status: 200 });
546
+ });
547
+
24
548
  //#endregion
25
549
  //#region src/schema.ts
26
550
  const schema = { passkey: { fields: {
@@ -72,12 +596,6 @@ const schema = { passkey: { fields: {
72
596
  }
73
597
  } } };
74
598
 
75
- //#endregion
76
- //#region src/utils.ts
77
- function getRpID(options, baseURL) {
78
- return options.rpID || (baseURL ? new URL(baseURL).hostname : "localhost");
79
- }
80
-
81
599
  //#endregion
82
600
  //#region src/index.ts
83
601
  const passkey = (options) => {
@@ -95,471 +613,19 @@ const passkey = (options) => {
95
613
  return {
96
614
  id: "passkey",
97
615
  endpoints: {
98
- generatePasskeyRegistrationOptions: createAuthEndpoint("/passkey/generate-register-options", {
99
- method: "GET",
100
- use: [freshSessionMiddleware],
101
- query: z.object({
102
- authenticatorAttachment: z.enum(["platform", "cross-platform"]).optional(),
103
- name: z.string().optional()
104
- }).optional(),
105
- metadata: { openapi: {
106
- operationId: "generatePasskeyRegistrationOptions",
107
- description: "Generate registration options for a new passkey",
108
- responses: { 200: {
109
- description: "Success",
110
- parameters: { query: {
111
- authenticatorAttachment: {
112
- description: `Type of authenticator to use for registration.
113
- "platform" for device-specific authenticators,
114
- "cross-platform" for authenticators that can be used across devices.`,
115
- required: false
116
- },
117
- name: {
118
- description: `Optional custom name for the passkey.
119
- This can help identify the passkey when managing multiple credentials.`,
120
- required: false
121
- }
122
- } },
123
- content: { "application/json": { schema: {
124
- type: "object",
125
- properties: {
126
- challenge: { type: "string" },
127
- rp: {
128
- type: "object",
129
- properties: {
130
- name: { type: "string" },
131
- id: { type: "string" }
132
- }
133
- },
134
- user: {
135
- type: "object",
136
- properties: {
137
- id: { type: "string" },
138
- name: { type: "string" },
139
- displayName: { type: "string" }
140
- }
141
- },
142
- pubKeyCredParams: {
143
- type: "array",
144
- items: {
145
- type: "object",
146
- properties: {
147
- type: { type: "string" },
148
- alg: { type: "number" }
149
- }
150
- }
151
- },
152
- timeout: { type: "number" },
153
- excludeCredentials: {
154
- type: "array",
155
- items: {
156
- type: "object",
157
- properties: {
158
- id: { type: "string" },
159
- type: { type: "string" },
160
- transports: {
161
- type: "array",
162
- items: { type: "string" }
163
- }
164
- }
165
- }
166
- },
167
- authenticatorSelection: {
168
- type: "object",
169
- properties: {
170
- authenticatorAttachment: { type: "string" },
171
- requireResidentKey: { type: "boolean" },
172
- userVerification: { type: "string" }
173
- }
174
- },
175
- attestation: { type: "string" },
176
- extensions: { type: "object" }
177
- }
178
- } } }
179
- } }
180
- } }
181
- }, async (ctx) => {
182
- const { session } = ctx.context;
183
- const userPasskeys = await ctx.context.adapter.findMany({
184
- model: "passkey",
185
- where: [{
186
- field: "userId",
187
- value: session.user.id
188
- }]
189
- });
190
- const userID = new TextEncoder().encode(generateRandomString(32, "a-z", "0-9"));
191
- let options$1;
192
- options$1 = await generateRegistrationOptions({
193
- rpName: opts.rpName || ctx.context.appName,
194
- rpID: getRpID(opts, ctx.context.options.baseURL),
195
- userID,
196
- userName: ctx.query?.name || session.user.email || session.user.id,
197
- userDisplayName: session.user.email || session.user.id,
198
- attestationType: "none",
199
- excludeCredentials: userPasskeys.map((passkey$1) => ({
200
- id: passkey$1.credentialID,
201
- transports: passkey$1.transports?.split(",")
202
- })),
203
- authenticatorSelection: {
204
- residentKey: "preferred",
205
- userVerification: "preferred",
206
- ...opts.authenticatorSelection || {},
207
- ...ctx.query?.authenticatorAttachment ? { authenticatorAttachment: ctx.query.authenticatorAttachment } : {}
208
- }
209
- });
210
- const id = generateId(32);
211
- const webAuthnCookie = ctx.context.createAuthCookie(opts.advanced.webAuthnChallengeCookie);
212
- await ctx.setSignedCookie(webAuthnCookie.name, id, ctx.context.secret, {
213
- ...webAuthnCookie.attributes,
214
- maxAge: maxAgeInSeconds
215
- });
216
- await ctx.context.internalAdapter.createVerificationValue({
217
- identifier: id,
218
- value: JSON.stringify({
219
- expectedChallenge: options$1.challenge,
220
- userData: { id: session.user.id }
221
- }),
222
- expiresAt: expirationTime
223
- });
224
- return ctx.json(options$1, { status: 200 });
616
+ generatePasskeyRegistrationOptions: generatePasskeyRegistrationOptions(opts, {
617
+ maxAgeInSeconds,
618
+ expirationTime
225
619
  }),
226
- generatePasskeyAuthenticationOptions: createAuthEndpoint("/passkey/generate-authenticate-options", {
227
- method: "GET",
228
- metadata: { openapi: {
229
- operationId: "passkeyGenerateAuthenticateOptions",
230
- description: "Generate authentication options for a passkey",
231
- responses: { 200: {
232
- description: "Success",
233
- content: { "application/json": { schema: {
234
- type: "object",
235
- properties: {
236
- challenge: { type: "string" },
237
- rp: {
238
- type: "object",
239
- properties: {
240
- name: { type: "string" },
241
- id: { type: "string" }
242
- }
243
- },
244
- user: {
245
- type: "object",
246
- properties: {
247
- id: { type: "string" },
248
- name: { type: "string" },
249
- displayName: { type: "string" }
250
- }
251
- },
252
- timeout: { type: "number" },
253
- allowCredentials: {
254
- type: "array",
255
- items: {
256
- type: "object",
257
- properties: {
258
- id: { type: "string" },
259
- type: { type: "string" },
260
- transports: {
261
- type: "array",
262
- items: { type: "string" }
263
- }
264
- }
265
- }
266
- },
267
- userVerification: { type: "string" },
268
- authenticatorSelection: {
269
- type: "object",
270
- properties: {
271
- authenticatorAttachment: { type: "string" },
272
- requireResidentKey: { type: "boolean" },
273
- userVerification: { type: "string" }
274
- }
275
- },
276
- extensions: { type: "object" }
277
- }
278
- } } }
279
- } }
280
- } }
281
- }, async (ctx) => {
282
- const session = await getSessionFromCtx(ctx);
283
- let userPasskeys = [];
284
- if (session) userPasskeys = await ctx.context.adapter.findMany({
285
- model: "passkey",
286
- where: [{
287
- field: "userId",
288
- value: session.user.id
289
- }]
290
- });
291
- const options$1 = await generateAuthenticationOptions({
292
- rpID: getRpID(opts, ctx.context.options.baseURL),
293
- userVerification: "preferred",
294
- ...userPasskeys.length ? { allowCredentials: userPasskeys.map((passkey$1) => ({
295
- id: passkey$1.credentialID,
296
- transports: passkey$1.transports?.split(",")
297
- })) } : {}
298
- });
299
- const data = {
300
- expectedChallenge: options$1.challenge,
301
- userData: { id: session?.user.id || "" }
302
- };
303
- const id = generateId(32);
304
- const webAuthnCookie = ctx.context.createAuthCookie(opts.advanced.webAuthnChallengeCookie);
305
- await ctx.setSignedCookie(webAuthnCookie.name, id, ctx.context.secret, {
306
- ...webAuthnCookie.attributes,
307
- maxAge: maxAgeInSeconds
308
- });
309
- await ctx.context.internalAdapter.createVerificationValue({
310
- identifier: id,
311
- value: JSON.stringify(data),
312
- expiresAt: expirationTime
313
- });
314
- return ctx.json(options$1, { status: 200 });
620
+ generatePasskeyAuthenticationOptions: generatePasskeyAuthenticationOptions(opts, {
621
+ maxAgeInSeconds,
622
+ expirationTime
315
623
  }),
316
- verifyPasskeyRegistration: createAuthEndpoint("/passkey/verify-registration", {
317
- method: "POST",
318
- body: z.object({
319
- response: z.any(),
320
- name: z.string().meta({ description: "Name of the passkey" }).optional()
321
- }),
322
- use: [freshSessionMiddleware],
323
- metadata: { openapi: {
324
- operationId: "passkeyVerifyRegistration",
325
- description: "Verify registration of a new passkey",
326
- responses: {
327
- 200: {
328
- description: "Success",
329
- content: { "application/json": { schema: { $ref: "#/components/schemas/Passkey" } } }
330
- },
331
- 400: { description: "Bad request" }
332
- }
333
- } }
334
- }, async (ctx) => {
335
- const origin = options?.origin || ctx.headers?.get("origin") || "";
336
- if (!origin) return ctx.json(null, { status: 400 });
337
- const resp = ctx.body.response;
338
- const webAuthnCookie = ctx.context.createAuthCookie(opts.advanced.webAuthnChallengeCookie);
339
- const challengeId = await ctx.getSignedCookie(webAuthnCookie.name, ctx.context.secret);
340
- if (!challengeId) throw new APIError("BAD_REQUEST", { message: PASSKEY_ERROR_CODES.CHALLENGE_NOT_FOUND });
341
- const data = await ctx.context.internalAdapter.findVerificationValue(challengeId);
342
- if (!data) return ctx.json(null, { status: 400 });
343
- const { expectedChallenge, userData } = JSON.parse(data.value);
344
- if (userData.id !== ctx.context.session.user.id) throw new APIError("UNAUTHORIZED", { message: PASSKEY_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_REGISTER_THIS_PASSKEY });
345
- try {
346
- const { verified, registrationInfo } = await verifyRegistrationResponse({
347
- response: resp,
348
- expectedChallenge,
349
- expectedOrigin: origin,
350
- expectedRPID: getRpID(opts, ctx.context.options.baseURL),
351
- requireUserVerification: false
352
- });
353
- if (!verified || !registrationInfo) return ctx.json(null, { status: 400 });
354
- const { aaguid, credentialDeviceType, credentialBackedUp, credential, credentialType } = registrationInfo;
355
- const pubKey = base64.encode(credential.publicKey);
356
- const newPasskey = {
357
- name: ctx.body.name,
358
- userId: userData.id,
359
- credentialID: credential.id,
360
- publicKey: pubKey,
361
- counter: credential.counter,
362
- deviceType: credentialDeviceType,
363
- transports: resp.response.transports.join(","),
364
- backedUp: credentialBackedUp,
365
- createdAt: /* @__PURE__ */ new Date(),
366
- aaguid
367
- };
368
- const newPasskeyRes = await ctx.context.adapter.create({
369
- model: "passkey",
370
- data: newPasskey
371
- });
372
- return ctx.json(newPasskeyRes, { status: 200 });
373
- } catch (e) {
374
- console.log(e);
375
- throw new APIError("INTERNAL_SERVER_ERROR", { message: PASSKEY_ERROR_CODES.FAILED_TO_VERIFY_REGISTRATION });
376
- }
377
- }),
378
- verifyPasskeyAuthentication: createAuthEndpoint("/passkey/verify-authentication", {
379
- method: "POST",
380
- body: z.object({ response: z.record(z.any(), z.any()) }),
381
- metadata: {
382
- openapi: {
383
- operationId: "passkeyVerifyAuthentication",
384
- description: "Verify authentication of a passkey",
385
- responses: { 200: {
386
- description: "Success",
387
- content: { "application/json": { schema: {
388
- type: "object",
389
- properties: {
390
- session: { $ref: "#/components/schemas/Session" },
391
- user: { $ref: "#/components/schemas/User" }
392
- }
393
- } } }
394
- } }
395
- },
396
- $Infer: { body: {} }
397
- }
398
- }, async (ctx) => {
399
- const origin = options?.origin || ctx.headers?.get("origin") || "";
400
- if (!origin) throw new APIError("BAD_REQUEST", { message: "origin missing" });
401
- const resp = ctx.body.response;
402
- const webAuthnCookie = ctx.context.createAuthCookie(opts.advanced.webAuthnChallengeCookie);
403
- const challengeId = await ctx.getSignedCookie(webAuthnCookie.name, ctx.context.secret);
404
- if (!challengeId) throw new APIError("BAD_REQUEST", { message: PASSKEY_ERROR_CODES.CHALLENGE_NOT_FOUND });
405
- const data = await ctx.context.internalAdapter.findVerificationValue(challengeId);
406
- if (!data) throw new APIError("BAD_REQUEST", { message: PASSKEY_ERROR_CODES.CHALLENGE_NOT_FOUND });
407
- const { expectedChallenge } = JSON.parse(data.value);
408
- const passkey$1 = await ctx.context.adapter.findOne({
409
- model: "passkey",
410
- where: [{
411
- field: "credentialID",
412
- value: resp.id
413
- }]
414
- });
415
- if (!passkey$1) throw new APIError("UNAUTHORIZED", { message: PASSKEY_ERROR_CODES.PASSKEY_NOT_FOUND });
416
- try {
417
- const verification = await verifyAuthenticationResponse({
418
- response: resp,
419
- expectedChallenge,
420
- expectedOrigin: origin,
421
- expectedRPID: getRpID(opts, ctx.context.options.baseURL),
422
- credential: {
423
- id: passkey$1.credentialID,
424
- publicKey: base64.decode(passkey$1.publicKey),
425
- counter: passkey$1.counter,
426
- transports: passkey$1.transports?.split(",")
427
- },
428
- requireUserVerification: false
429
- });
430
- const { verified } = verification;
431
- if (!verified) throw new APIError("UNAUTHORIZED", { message: PASSKEY_ERROR_CODES.AUTHENTICATION_FAILED });
432
- await ctx.context.adapter.update({
433
- model: "passkey",
434
- where: [{
435
- field: "id",
436
- value: passkey$1.id
437
- }],
438
- update: { counter: verification.authenticationInfo.newCounter }
439
- });
440
- const s = await ctx.context.internalAdapter.createSession(passkey$1.userId);
441
- if (!s) throw new APIError("INTERNAL_SERVER_ERROR", { message: PASSKEY_ERROR_CODES.UNABLE_TO_CREATE_SESSION });
442
- const user = await ctx.context.internalAdapter.findUserById(passkey$1.userId);
443
- if (!user) throw new APIError("INTERNAL_SERVER_ERROR", { message: "User not found" });
444
- await setSessionCookie(ctx, {
445
- session: s,
446
- user
447
- });
448
- return ctx.json({ session: s }, { status: 200 });
449
- } catch (e) {
450
- ctx.context.logger.error("Failed to verify authentication", e);
451
- throw new APIError("BAD_REQUEST", { message: PASSKEY_ERROR_CODES.AUTHENTICATION_FAILED });
452
- }
453
- }),
454
- listPasskeys: createAuthEndpoint("/passkey/list-user-passkeys", {
455
- method: "GET",
456
- use: [sessionMiddleware],
457
- metadata: { openapi: {
458
- description: "List all passkeys for the authenticated user",
459
- responses: { "200": {
460
- description: "Passkeys retrieved successfully",
461
- content: { "application/json": { schema: {
462
- type: "array",
463
- items: {
464
- $ref: "#/components/schemas/Passkey",
465
- required: [
466
- "id",
467
- "userId",
468
- "publicKey",
469
- "createdAt",
470
- "updatedAt"
471
- ]
472
- },
473
- description: "Array of passkey objects associated with the user"
474
- } } }
475
- } }
476
- } }
477
- }, async (ctx) => {
478
- const passkeys = await ctx.context.adapter.findMany({
479
- model: "passkey",
480
- where: [{
481
- field: "userId",
482
- value: ctx.context.session.user.id
483
- }]
484
- });
485
- return ctx.json(passkeys, { status: 200 });
486
- }),
487
- deletePasskey: createAuthEndpoint("/passkey/delete-passkey", {
488
- method: "POST",
489
- body: z.object({ id: z.string().meta({ description: "The ID of the passkey to delete. Eg: \"some-passkey-id\"" }) }),
490
- use: [sessionMiddleware],
491
- metadata: { openapi: {
492
- description: "Delete a specific passkey",
493
- responses: { "200": {
494
- description: "Passkey deleted successfully",
495
- content: { "application/json": { schema: {
496
- type: "object",
497
- properties: { status: {
498
- type: "boolean",
499
- description: "Indicates whether the deletion was successful"
500
- } },
501
- required: ["status"]
502
- } } }
503
- } }
504
- } }
505
- }, async (ctx) => {
506
- const passkey$1 = await ctx.context.adapter.findOne({
507
- model: "passkey",
508
- where: [{
509
- field: "id",
510
- value: ctx.body.id
511
- }]
512
- });
513
- if (!passkey$1) throw new APIError("NOT_FOUND", { message: PASSKEY_ERROR_CODES.PASSKEY_NOT_FOUND });
514
- if (passkey$1.userId !== ctx.context.session.user.id) throw new APIError("UNAUTHORIZED");
515
- await ctx.context.adapter.delete({
516
- model: "passkey",
517
- where: [{
518
- field: "id",
519
- value: passkey$1.id
520
- }]
521
- });
522
- return ctx.json({ status: true });
523
- }),
524
- updatePasskey: createAuthEndpoint("/passkey/update-passkey", {
525
- method: "POST",
526
- body: z.object({
527
- id: z.string().meta({ description: `The ID of the passkey which will be updated. Eg: \"passkey-id\"` }),
528
- name: z.string().meta({ description: `The new name which the passkey will be updated to. Eg: \"my-new-passkey-name\"` })
529
- }),
530
- use: [sessionMiddleware],
531
- metadata: { openapi: {
532
- description: "Update a specific passkey's name",
533
- responses: { "200": {
534
- description: "Passkey updated successfully",
535
- content: { "application/json": { schema: {
536
- type: "object",
537
- properties: { passkey: { $ref: "#/components/schemas/Passkey" } },
538
- required: ["passkey"]
539
- } } }
540
- } }
541
- } }
542
- }, async (ctx) => {
543
- const passkey$1 = await ctx.context.adapter.findOne({
544
- model: "passkey",
545
- where: [{
546
- field: "id",
547
- value: ctx.body.id
548
- }]
549
- });
550
- if (!passkey$1) throw new APIError("NOT_FOUND", { message: PASSKEY_ERROR_CODES.PASSKEY_NOT_FOUND });
551
- if (passkey$1.userId !== ctx.context.session.user.id) throw new APIError("UNAUTHORIZED", { message: PASSKEY_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_REGISTER_THIS_PASSKEY });
552
- const updatedPasskey = await ctx.context.adapter.update({
553
- model: "passkey",
554
- where: [{
555
- field: "id",
556
- value: ctx.body.id
557
- }],
558
- update: { name: ctx.body.name }
559
- });
560
- if (!updatedPasskey) throw new APIError("INTERNAL_SERVER_ERROR", { message: PASSKEY_ERROR_CODES.FAILED_TO_UPDATE_PASSKEY });
561
- return ctx.json({ passkey: updatedPasskey }, { status: 200 });
562
- })
624
+ verifyPasskeyRegistration: verifyPasskeyRegistration(opts),
625
+ verifyPasskeyAuthentication: verifyPasskeyAuthentication(opts),
626
+ listPasskeys,
627
+ deletePasskey,
628
+ updatePasskey
563
629
  },
564
630
  schema: mergeSchema(schema, options?.schema),
565
631
  $ERROR_CODES: PASSKEY_ERROR_CODES
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@better-auth/passkey",
3
- "version": "1.4.6-beta.2",
3
+ "version": "1.4.6-beta.6",
4
4
  "type": "module",
5
5
  "description": "Passkey plugin for Better Auth",
6
6
  "main": "dist/index.mjs",
7
7
  "module": "dist/index.mjs",
8
+ "types": "dist/index.d.mts",
8
9
  "publishConfig": {
9
10
  "access": "public"
10
11
  },
@@ -31,9 +32,9 @@
31
32
  }
32
33
  },
33
34
  "devDependencies": {
34
- "tsdown": "^0.16.0",
35
- "@better-auth/core": "1.4.6-beta.2",
36
- "better-auth": "1.4.6-beta.2"
35
+ "tsdown": "^0.17.0",
36
+ "@better-auth/core": "1.4.6-beta.6",
37
+ "better-auth": "1.4.6-beta.6"
37
38
  },
38
39
  "dependencies": {
39
40
  "@simplewebauthn/browser": "^13.1.2",
@@ -43,17 +44,17 @@
43
44
  "peerDependencies": {
44
45
  "@better-auth/utils": "0.3.0",
45
46
  "@better-fetch/fetch": "1.1.18",
46
- "better-call": "1.1.4",
47
+ "better-call": "1.1.5",
47
48
  "nanostores": "^1.0.1",
48
- "@better-auth/core": "1.4.6-beta.2",
49
- "better-auth": "1.4.6-beta.2"
49
+ "@better-auth/core": "1.4.6-beta.6",
50
+ "better-auth": "1.4.6-beta.6"
50
51
  },
51
52
  "files": [
52
53
  "dist"
53
54
  ],
54
55
  "repository": {
55
56
  "type": "git",
56
- "url": "https://github.com/better-auth/better-auth",
57
+ "url": "git+https://github.com/better-auth/better-auth.git",
57
58
  "directory": "packages/passkey"
58
59
  },
59
60
  "homepage": "https://www.better-auth.com/docs/plugins/passkey",
@@ -66,6 +67,7 @@
66
67
  "license": "MIT",
67
68
  "scripts": {
68
69
  "test": "vitest",
70
+ "coverage": "vitest run --coverage",
69
71
  "lint:package": "publint run --strict",
70
72
  "build": "tsdown",
71
73
  "dev": "tsdown --watch",