@better-auth/passkey 1.7.0-beta.1 → 1.7.0-beta.10

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 { 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-DD5Lute1.mjs";
1
+ import { a as Passkey, c as PasskeyOptions, d as WebAuthnChallengeValue, l as PasskeyRegistrationOptions, n as PASSKEY_ERROR_CODES, o as PasskeyAuthenticationOptions, s as PasskeyExtensionsResolver, t as passkey, u as PasskeyRegistrationUser } from "./index-z0BvlclO.mjs";
2
2
  import { AuthenticationExtensionsClientInputs, AuthenticationExtensionsClientOutputs, AuthenticationResponseJSON, RegistrationResponseJSON } from "@simplewebauthn/server";
3
3
  import * as better_auth_client0 from "better-auth/client";
4
4
  import * as nanostores from "nanostores";
@@ -39,6 +39,14 @@ declare const getPasskeyActions: ($fetch: BetterFetch, {
39
39
  user: User;
40
40
  };
41
41
  error: null;
42
+ } | {
43
+ data: null;
44
+ error: {
45
+ code: string;
46
+ message: string;
47
+ status: number;
48
+ statusText: string;
49
+ };
42
50
  } | {
43
51
  webauthn: {
44
52
  response: AuthenticationResponseJSON;
@@ -60,14 +68,6 @@ declare const getPasskeyActions: ($fetch: BetterFetch, {
60
68
  user: User;
61
69
  };
62
70
  error: null;
63
- } | {
64
- data: null;
65
- error: {
66
- code: string;
67
- message: string;
68
- status: number;
69
- statusText: string;
70
- };
71
71
  }>;
72
72
  };
73
73
  passkey: {
@@ -165,6 +165,14 @@ declare const passkeyClient: () => {
165
165
  user: User;
166
166
  };
167
167
  error: null;
168
+ } | {
169
+ data: null;
170
+ error: {
171
+ code: string;
172
+ message: string;
173
+ status: number;
174
+ statusText: string;
175
+ };
168
176
  } | {
169
177
  webauthn: {
170
178
  response: AuthenticationResponseJSON;
@@ -186,14 +194,6 @@ declare const passkeyClient: () => {
186
194
  user: User;
187
195
  };
188
196
  error: null;
189
- } | {
190
- data: null;
191
- error: {
192
- code: string;
193
- message: string;
194
- status: number;
195
- statusText: string;
196
- };
197
197
  }>;
198
198
  };
199
199
  passkey: {
package/dist/client.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { n as PASSKEY_ERROR_CODES, t as PACKAGE_VERSION } from "./version-BricTBPQ.mjs";
1
+ import { n as PASSKEY_ERROR_CODES, t as PACKAGE_VERSION } from "./version-vleaxnrJ.mjs";
2
2
  import { WebAuthnError, startAuthentication, startRegistration } from "@simplewebauthn/browser";
3
3
  import { useAuthQuery } from "better-auth/client";
4
4
  import { atom } from "nanostores";
@@ -10,18 +10,31 @@ const getPasskeyActions = ($fetch, { $listPasskeys, $store }) => {
10
10
  throw: false
11
11
  });
12
12
  if (!response.data) return response;
13
+ const mergedExtensions = response.data.extensions || opts?.extensions ? {
14
+ ...response.data.extensions || {},
15
+ ...opts?.extensions || {}
16
+ } : void 0;
17
+ let res;
13
18
  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
+ res = await startAuthentication({
19
20
  optionsJSON: {
20
21
  ...response.data,
21
22
  extensions: mergedExtensions
22
23
  },
23
24
  useBrowserAutofill: opts?.autoFill
24
25
  });
26
+ } catch (err) {
27
+ return {
28
+ data: null,
29
+ error: {
30
+ code: err instanceof WebAuthnError ? err.code : "AUTH_CANCELLED",
31
+ message: PASSKEY_ERROR_CODES.AUTH_CANCELLED.message,
32
+ status: 400,
33
+ statusText: "BAD_REQUEST"
34
+ }
35
+ };
36
+ }
37
+ try {
25
38
  const { clientExtensionResults, ...responseBody } = res;
26
39
  const verified = await $fetch("/passkey/verify-authentication", {
27
40
  body: { response: responseBody },
@@ -100,7 +100,13 @@ interface PasskeyRegistrationOptions {
100
100
  }) => Awaitable<PasskeyRegistrationUser>) | undefined;
101
101
  /**
102
102
  * Callback after a successful registration verification.
103
- * Useful for user linking or auditing.
103
+ * Useful for user linking, auditing, or labeling the passkey.
104
+ *
105
+ * Return `userId` to attribute the passkey to a different user. Return `name`
106
+ * to set the stored label when the client did not provide one; the AAGUID is
107
+ * available via `verification.registrationInfo?.aaguid`. A non-empty
108
+ * client-supplied name always takes precedence over the returned `name`;
109
+ * whitespace-only input is treated as absent.
104
110
  */
105
111
  afterVerification?: ((args: {
106
112
  ctx: GenericEndpointContext;
@@ -110,6 +116,7 @@ interface PasskeyRegistrationOptions {
110
116
  context?: string | null | undefined;
111
117
  }) => Awaitable<{
112
118
  userId?: string;
119
+ name?: string;
113
120
  } | void>) | undefined;
114
121
  /**
115
122
  * Optional WebAuthn extensions to include in registration options.
@@ -196,6 +203,30 @@ type Passkey = {
196
203
  aaguid?: string | undefined;
197
204
  };
198
205
  //#endregion
206
+ //#region src/authenticator-metadata.d.ts
207
+ /**
208
+ * Best-effort map of common authenticator AAGUIDs to a human-readable provider
209
+ * name, for labeling passkeys in management UIs.
210
+ *
211
+ * An AAGUID identifies an authenticator *model* (not a device or a user) and is
212
+ * present only in the registration response. Better Auth stores it on every
213
+ * passkey row and returns it from `listPasskeys`, so a display label can be
214
+ * resolved wherever passkeys are rendered.
215
+ *
216
+ * This list is intentionally small and not authoritative. Many authenticators
217
+ * are missing, and privacy-preserving platforms report an all-zero AAGUID
218
+ * (`00000000-0000-0000-0000-000000000000`) that matches nothing here. Notably,
219
+ * Apple devices zero the AAGUID under the default `attestation: "none"` flow, so
220
+ * the Apple entries below only appear in attested or managed contexts. For full
221
+ * coverage, resolve against the community-maintained source instead:
222
+ *
223
+ * - https://github.com/passkeydeveloper/passkey-authenticator-aaguids
224
+ *
225
+ * Names mirror that source verbatim.
226
+ */
227
+ declare const commonAuthenticatorNames: Record<string, string>;
228
+ declare const getAuthenticatorName: (aaguid: string | null | undefined) => string | undefined;
229
+ //#endregion
199
230
  //#region src/error-codes.d.ts
200
231
  declare const PASSKEY_ERROR_CODES: {
201
232
  CHALLENGE_NOT_FOUND: better_auth0.RawError<"CHALLENGE_NOT_FOUND">;
@@ -250,7 +281,7 @@ declare const passkey: (options?: PasskeyOptions | undefined) => {
250
281
  image?: string | null | undefined;
251
282
  };
252
283
  };
253
- }>)[] | undefined;
284
+ }>)[];
254
285
  query: zod.ZodOptional<zod.ZodObject<{
255
286
  authenticatorAttachment: zod.ZodOptional<zod.ZodEnum<{
256
287
  platform: "platform";
@@ -263,25 +294,10 @@ declare const passkey: (options?: PasskeyOptions | undefined) => {
263
294
  openapi: {
264
295
  operationId: string;
265
296
  description: string;
297
+ parameters: better_call0.OpenAPIParameter[];
266
298
  responses: {
267
299
  200: {
268
300
  description: string;
269
- parameters: {
270
- query: {
271
- authenticatorAttachment: {
272
- description: string;
273
- required: boolean;
274
- };
275
- name: {
276
- description: string;
277
- required: boolean;
278
- };
279
- context: {
280
- description: string;
281
- required: boolean;
282
- };
283
- };
284
- };
285
301
  content: {
286
302
  "application/json": {
287
303
  schema: {
@@ -503,7 +519,7 @@ declare const passkey: (options?: PasskeyOptions | undefined) => {
503
519
  image?: string | null | undefined;
504
520
  };
505
521
  };
506
- }>)[] | undefined;
522
+ }>)[];
507
523
  metadata: {
508
524
  openapi: {
509
525
  operationId: string;
@@ -573,6 +589,15 @@ declare const passkey: (options?: PasskeyOptions | undefined) => {
573
589
  ipAddress?: string | null | undefined;
574
590
  userAgent?: string | null | undefined;
575
591
  };
592
+ user: {
593
+ id: string;
594
+ createdAt: Date;
595
+ updatedAt: Date;
596
+ email: string;
597
+ emailVerified: boolean;
598
+ name: string;
599
+ image?: string | null | undefined;
600
+ };
576
601
  }>;
577
602
  listPasskeys: better_call0.StrictEndpoint<"/passkey/list-user-passkeys", {
578
603
  method: "GET";
@@ -808,4 +833,4 @@ declare const passkey: (options?: PasskeyOptions | undefined) => {
808
833
  options: PasskeyOptions | undefined;
809
834
  };
810
835
  //#endregion
811
- 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 };
836
+ export { Passkey as a, PasskeyOptions as c, WebAuthnChallengeValue as d, getAuthenticatorName as i, PasskeyRegistrationOptions as l, PASSKEY_ERROR_CODES as n, PasskeyAuthenticationOptions as o, commonAuthenticatorNames as r, PasskeyExtensionsResolver as s, passkey as t, PasskeyRegistrationUser as u };
package/dist/index.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { n as PASSKEY_ERROR_CODES, o as PasskeyOptions, r as Passkey, t as passkey } from "./index-DD5Lute1.mjs";
2
- export { PASSKEY_ERROR_CODES, Passkey, PasskeyOptions, passkey };
1
+ import { a as Passkey, c as PasskeyOptions, i as getAuthenticatorName, n as PASSKEY_ERROR_CODES, r as commonAuthenticatorNames, t as passkey } from "./index-z0BvlclO.mjs";
2
+ export { PASSKEY_ERROR_CODES, Passkey, PasskeyOptions, commonAuthenticatorNames, getAuthenticatorName, passkey };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { n as PASSKEY_ERROR_CODES, t as PACKAGE_VERSION } from "./version-BricTBPQ.mjs";
1
+ import { n as PASSKEY_ERROR_CODES, t as PACKAGE_VERSION } from "./version-vleaxnrJ.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";
@@ -52,33 +52,46 @@ const generatePasskeyQuerySchema = z.object({
52
52
  name: z.string().optional(),
53
53
  context: z.string().optional()
54
54
  }).optional();
55
+ const generatePasskeyRegistrationOptionsOpenAPIParameters = [
56
+ {
57
+ name: "authenticatorAttachment",
58
+ in: "query",
59
+ required: false,
60
+ description: `Type of authenticator to use for registration.
61
+ "platform" for device-specific authenticators,
62
+ "cross-platform" for authenticators that can be used across devices.`,
63
+ schema: {
64
+ type: "string",
65
+ enum: ["platform", "cross-platform"]
66
+ }
67
+ },
68
+ {
69
+ name: "name",
70
+ in: "query",
71
+ required: false,
72
+ description: `Optional custom name for the passkey.
73
+ This can help identify the passkey when managing multiple credentials.`,
74
+ schema: { type: "string" }
75
+ },
76
+ {
77
+ name: "context",
78
+ in: "query",
79
+ required: false,
80
+ description: "Optional context for passkey-first registration flows.",
81
+ schema: { type: "string" }
82
+ }
83
+ ];
55
84
  const generatePasskeyRegistrationOptions = (opts, { maxAgeInSeconds }) => {
56
85
  return createAuthEndpoint("/passkey/generate-register-options", {
57
86
  method: "GET",
58
- use: opts.registration?.requireSession ?? true ? [freshSessionMiddleware] : void 0,
87
+ use: opts.registration?.requireSession ?? true ? [freshSessionMiddleware] : [],
59
88
  query: generatePasskeyQuerySchema,
60
89
  metadata: { openapi: {
61
90
  operationId: "generatePasskeyRegistrationOptions",
62
91
  description: "Generate registration options for a new passkey",
92
+ parameters: generatePasskeyRegistrationOptionsOpenAPIParameters,
63
93
  responses: { 200: {
64
94
  description: "Success",
65
- parameters: { query: {
66
- authenticatorAttachment: {
67
- description: `Type of authenticator to use for registration.
68
- "platform" for device-specific authenticators,
69
- "cross-platform" for authenticators that can be used across devices.`,
70
- required: false
71
- },
72
- name: {
73
- description: `Optional custom name for the passkey.
74
- This can help identify the passkey when managing multiple credentials.`,
75
- required: false
76
- },
77
- context: {
78
- description: "Optional context for passkey-first registration flows.",
79
- required: false
80
- }
81
- } },
82
95
  content: { "application/json": { schema: {
83
96
  type: "object",
84
97
  properties: {
@@ -178,6 +191,7 @@ const generatePasskeyRegistrationOptions = (opts, { maxAgeInSeconds }) => {
178
191
  await ctx.context.internalAdapter.createVerificationValue({
179
192
  identifier: verificationToken,
180
193
  value: JSON.stringify({
194
+ type: "registration",
181
195
  expectedChallenge: options.challenge,
182
196
  userData: {
183
197
  id: user.id,
@@ -268,6 +282,7 @@ const generatePasskeyAuthenticationOptions = (opts, { maxAgeInSeconds }) => crea
268
282
  })) } : {}
269
283
  });
270
284
  const data = {
285
+ type: "authentication",
271
286
  expectedChallenge: options.challenge,
272
287
  userData: { id: session?.user.id || "" }
273
288
  };
@@ -287,14 +302,14 @@ const generatePasskeyAuthenticationOptions = (opts, { maxAgeInSeconds }) => crea
287
302
  });
288
303
  const verifyPasskeyRegistrationBodySchema = z.object({
289
304
  response: z.any(),
290
- name: z.string().meta({ description: "Name of the passkey" }).optional()
305
+ name: z.string().trim().meta({ description: "Name of the passkey" }).optional()
291
306
  });
292
307
  const verifyPasskeyRegistration = (options) => {
293
308
  const requireSession = options.registration?.requireSession ?? true;
294
309
  return createAuthEndpoint("/passkey/verify-registration", {
295
310
  method: "POST",
296
311
  body: verifyPasskeyRegistrationBodySchema,
297
- use: requireSession ? [freshSessionMiddleware] : void 0,
312
+ use: requireSession ? [freshSessionMiddleware] : [],
298
313
  metadata: { openapi: {
299
314
  operationId: "passkeyVerifyRegistration",
300
315
  description: "Verify registration of a new passkey",
@@ -313,9 +328,10 @@ const verifyPasskeyRegistration = (options) => {
313
328
  const webAuthnCookie = ctx.context.createAuthCookie(options.advanced.webAuthnChallengeCookie);
314
329
  const verificationToken = await ctx.getSignedCookie(webAuthnCookie.name, ctx.context.secret);
315
330
  if (!verificationToken) throw APIError.from("BAD_REQUEST", PASSKEY_ERROR_CODES.CHALLENGE_NOT_FOUND);
316
- const data = await ctx.context.internalAdapter.findVerificationValue(verificationToken);
331
+ const data = await ctx.context.internalAdapter.consumeVerificationValue(verificationToken);
317
332
  if (!data) throw APIError.from("BAD_REQUEST", PASSKEY_ERROR_CODES.CHALLENGE_NOT_FOUND);
318
- const { expectedChallenge, userData, context } = JSON.parse(data.value);
333
+ const { type: ceremony, expectedChallenge, userData, context } = JSON.parse(data.value);
334
+ if (ceremony !== "registration") throw APIError.from("BAD_REQUEST", PASSKEY_ERROR_CODES.CHALLENGE_NOT_FOUND);
319
335
  const session = requireSession ? ctx.context.session : await getSessionFromCtx(ctx);
320
336
  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
337
  try {
@@ -335,6 +351,7 @@ const verifyPasskeyRegistration = (options) => {
335
351
  displayName: userData.displayName
336
352
  };
337
353
  let targetUserId = resolvedUser.id;
354
+ let resolvedName = ctx.body.name || void 0;
338
355
  if (options.registration?.afterVerification) {
339
356
  const result = await options.registration.afterVerification({
340
357
  ctx,
@@ -348,16 +365,18 @@ const verifyPasskeyRegistration = (options) => {
348
365
  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
366
  targetUserId = result.userId;
350
367
  }
368
+ if (!resolvedName) resolvedName = result?.name?.trim() || void 0;
351
369
  }
370
+ if (!targetUserId) throw APIError.from("BAD_REQUEST", PASSKEY_ERROR_CODES.RESOLVED_USER_INVALID);
352
371
  const pubKey = base64.encode(credential.publicKey);
353
372
  const newPasskey = {
354
- name: ctx.body.name,
373
+ name: resolvedName,
355
374
  userId: targetUserId,
356
375
  credentialID: credential.id,
357
376
  publicKey: pubKey,
358
377
  counter: credential.counter,
359
378
  deviceType: credentialDeviceType,
360
- transports: resp.response.transports.join(","),
379
+ transports: resp.response.transports?.join(",") ?? "",
361
380
  backedUp: credentialBackedUp,
362
381
  createdAt: /* @__PURE__ */ new Date(),
363
382
  aaguid
@@ -366,9 +385,9 @@ const verifyPasskeyRegistration = (options) => {
366
385
  model: "passkey",
367
386
  data: newPasskey
368
387
  });
369
- await ctx.context.internalAdapter.deleteVerificationByIdentifier(verificationToken);
370
388
  return ctx.json(newPasskeyRes, { status: 200 });
371
389
  } catch (e) {
390
+ if (e instanceof APIError) throw e;
372
391
  ctx.context.logger.error("Failed to verify registration", e);
373
392
  throw APIError.from("INTERNAL_SERVER_ERROR", PASSKEY_ERROR_CODES.FAILED_TO_VERIFY_REGISTRATION);
374
393
  }
@@ -402,9 +421,10 @@ const verifyPasskeyAuthentication = (options) => createAuthEndpoint("/passkey/ve
402
421
  const webAuthnCookie = ctx.context.createAuthCookie(options.advanced.webAuthnChallengeCookie);
403
422
  const verificationToken = await ctx.getSignedCookie(webAuthnCookie.name, ctx.context.secret);
404
423
  if (!verificationToken) throw APIError.from("BAD_REQUEST", PASSKEY_ERROR_CODES.CHALLENGE_NOT_FOUND);
405
- const data = await ctx.context.internalAdapter.findVerificationValue(verificationToken);
424
+ const data = await ctx.context.internalAdapter.consumeVerificationValue(verificationToken);
406
425
  if (!data) throw APIError.from("BAD_REQUEST", PASSKEY_ERROR_CODES.CHALLENGE_NOT_FOUND);
407
- const { expectedChallenge } = JSON.parse(data.value);
426
+ const { type: ceremony, expectedChallenge } = JSON.parse(data.value);
427
+ if (ceremony !== "authentication") throw APIError.from("BAD_REQUEST", PASSKEY_ERROR_CODES.CHALLENGE_NOT_FOUND);
408
428
  const passkey = await ctx.context.adapter.findOne({
409
429
  model: "passkey",
410
430
  where: [{
@@ -450,9 +470,12 @@ const verifyPasskeyAuthentication = (options) => createAuthEndpoint("/passkey/ve
450
470
  session: s,
451
471
  user
452
472
  });
453
- await ctx.context.internalAdapter.deleteVerificationByIdentifier(verificationToken);
454
- return ctx.json({ session: s }, { status: 200 });
473
+ return ctx.json({
474
+ session: s,
475
+ user
476
+ }, { status: 200 });
455
477
  } catch (e) {
478
+ if (e instanceof APIError) throw e;
456
479
  ctx.context.logger.error("Failed to verify authentication", e);
457
480
  throw APIError.from("BAD_REQUEST", PASSKEY_ERROR_CODES.AUTHENTICATION_FAILED);
458
481
  }
@@ -573,7 +596,7 @@ const updatePasskey = createAuthEndpoint("/passkey/update-passkey", {
573
596
  method: "POST",
574
597
  body: z.object({
575
598
  id: z.string().meta({ description: `The ID of the passkey which will be updated. Eg: \"passkey-id\"` }),
576
- name: z.string().meta({ description: `The new name which the passkey will be updated to. Eg: \"my-new-passkey-name\"` })
599
+ name: z.string().trim().min(1).meta({ description: `The new name which the passkey will be updated to. Eg: \"my-new-passkey-name\"` })
577
600
  }),
578
601
  use: [sessionMiddleware, requireResourceOwnership({
579
602
  model: "passkey",
@@ -657,6 +680,63 @@ const schema = { passkey: { fields: {
657
680
  }
658
681
  } } };
659
682
  //#endregion
683
+ //#region src/authenticator-metadata.ts
684
+ /**
685
+ * Best-effort map of common authenticator AAGUIDs to a human-readable provider
686
+ * name, for labeling passkeys in management UIs.
687
+ *
688
+ * An AAGUID identifies an authenticator *model* (not a device or a user) and is
689
+ * present only in the registration response. Better Auth stores it on every
690
+ * passkey row and returns it from `listPasskeys`, so a display label can be
691
+ * resolved wherever passkeys are rendered.
692
+ *
693
+ * This list is intentionally small and not authoritative. Many authenticators
694
+ * are missing, and privacy-preserving platforms report an all-zero AAGUID
695
+ * (`00000000-0000-0000-0000-000000000000`) that matches nothing here. Notably,
696
+ * Apple devices zero the AAGUID under the default `attestation: "none"` flow, so
697
+ * the Apple entries below only appear in attested or managed contexts. For full
698
+ * coverage, resolve against the community-maintained source instead:
699
+ *
700
+ * - https://github.com/passkeydeveloper/passkey-authenticator-aaguids
701
+ *
702
+ * Names mirror that source verbatim.
703
+ */
704
+ const commonAuthenticatorNames = {
705
+ "ea9b8d66-4d01-1d21-3ce4-b6b48cb575d4": "Google Password Manager",
706
+ "fbfc3007-154e-4ecc-8c0b-6e020557d7bd": "Apple Passwords",
707
+ "dd4ec289-e01d-41c9-bb89-70fa845d4bf2": "iCloud Keychain (Managed)",
708
+ "08987058-cadc-4b81-b6e1-30de50dcbe96": "Windows Hello",
709
+ "9ddd1817-af5a-4672-a2b9-3e3dd95000a9": "Windows Hello",
710
+ "6028b017-b1d4-4c02-b4b3-afcdafc96bb2": "Windows Hello",
711
+ "bada5566-a7aa-401f-bd96-45619a55120d": "1Password",
712
+ "d548826e-79b4-db40-a3d8-11116f7e8349": "Bitwarden",
713
+ "531126d6-e717-415c-9320-3d9aa6981239": "Dashlane",
714
+ "b78a0a55-6ef8-d246-a042-ba0f6d55050c": "LastPass",
715
+ "b84e4048-15dc-4dd0-8640-f4f60813c8af": "NordPass",
716
+ "50726f74-6f6e-5061-7373-50726f746f6e": "Proton Pass",
717
+ "0ea242b4-43c4-4a1b-8b17-dd6d0b6baec6": "Keeper",
718
+ "53414d53-554e-4700-0000-000000000000": "Samsung Pass"
719
+ };
720
+ /**
721
+ * Resolve a best-effort provider name for an authenticator AAGUID.
722
+ *
723
+ * Returns `undefined` when the AAGUID is unknown, empty, or the all-zero value
724
+ * reported by privacy-preserving platforms. Casing and surrounding whitespace
725
+ * are normalized before lookup.
726
+ *
727
+ * @example
728
+ * ```ts
729
+ * const label = passkey.name || getAuthenticatorName(passkey.aaguid) || "Passkey";
730
+ * ```
731
+ */
732
+ const ANONYMOUS_AAGUID = "00000000-0000-0000-0000-000000000000";
733
+ const getAuthenticatorName = (aaguid) => {
734
+ const normalized = aaguid?.trim().toLowerCase();
735
+ if (!normalized || normalized === ANONYMOUS_AAGUID) return void 0;
736
+ const name = commonAuthenticatorNames[normalized];
737
+ return typeof name === "string" ? name : void 0;
738
+ };
739
+ //#endregion
660
740
  //#region src/index.ts
661
741
  const MAX_AGE_IN_SECONDS = 300;
662
742
  const passkey = (options) => {
@@ -686,4 +766,4 @@ const passkey = (options) => {
686
766
  };
687
767
  };
688
768
  //#endregion
689
- export { PASSKEY_ERROR_CODES, passkey };
769
+ export { PASSKEY_ERROR_CODES, commonAuthenticatorNames, getAuthenticatorName, passkey };
@@ -18,6 +18,6 @@ const PASSKEY_ERROR_CODES = defineErrorCodes({
18
18
  });
19
19
  //#endregion
20
20
  //#region src/version.ts
21
- const PACKAGE_VERSION = "1.7.0-beta.1";
21
+ const PACKAGE_VERSION = "1.7.0-beta.10";
22
22
  //#endregion
23
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.7.0-beta.1",
3
+ "version": "1.7.0-beta.10",
4
4
  "description": "Passkey plugin for Better Auth",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -49,22 +49,23 @@
49
49
  }
50
50
  },
51
51
  "dependencies": {
52
- "@simplewebauthn/browser": "^13.2.2",
53
- "@simplewebauthn/server": "^13.2.3",
52
+ "@simplewebauthn/browser": "^13.3.0",
53
+ "@simplewebauthn/server": "^13.3.1",
54
54
  "zod": "^4.3.6"
55
55
  },
56
56
  "devDependencies": {
57
+ "nanostores": "^1.3.0",
57
58
  "tsdown": "0.21.1",
58
- "@better-auth/core": "1.7.0-beta.1",
59
- "better-auth": "1.7.0-beta.1"
59
+ "@better-auth/core": "1.7.0-beta.10",
60
+ "better-auth": "1.7.0-beta.10"
60
61
  },
61
62
  "peerDependencies": {
62
- "@better-auth/utils": "0.4.0",
63
- "@better-fetch/fetch": "1.1.21",
64
- "better-call": "1.3.5",
63
+ "@better-auth/utils": "0.4.2",
64
+ "@better-fetch/fetch": "1.3.1",
65
+ "better-call": "1.3.7",
65
66
  "nanostores": "^1.0.1",
66
- "@better-auth/core": "^1.7.0-beta.1",
67
- "better-auth": "^1.7.0-beta.1"
67
+ "@better-auth/core": "^1.7.0-beta.10",
68
+ "better-auth": "^1.7.0-beta.10"
68
69
  },
69
70
  "scripts": {
70
71
  "build": "tsdown",