From f327e336c8f89aa92ac497c5fc2dd2a193a20187 Mon Sep 17 00:00:00 2001 From: Taesu <166604494+bytaesu@users.noreply.github.com> Date: Thu, 6 Nov 2025 02:05:39 +0900 Subject: [PATCH] refactor: split the `passkey` plugin into a separate package (#5769) --- demo/nextjs/lib/auth-client.ts | 2 +- demo/nextjs/lib/auth.ts | 2 +- demo/nextjs/package.json | 1 + docs/components/landing/hero.tsx | 9 +- docs/content/docs/plugins/passkey.mdx | 58 +++--- .../tsconfig-declaration/package.json | 1 + .../fixtures/tsconfig-declaration/src/demo.ts | 2 +- packages/better-auth/package.json | 10 - .../better-auth/src/client/client.test.ts | 4 +- .../better-auth/src/client/plugins/index.ts | 2 - packages/better-auth/tsdown.config.ts | 1 - packages/cli/package.json | 1 + packages/cli/src/commands/init.ts | 4 +- packages/cli/test/generate-all-db.test.ts | 2 +- packages/passkey/README.md | 54 +++++ packages/passkey/package.json | 73 +++++++ .../plugins/passkey => passkey/src}/client.ts | 9 +- packages/passkey/src/error-codes.ts | 12 ++ .../plugins/passkey => passkey/src}/index.ts | 184 +++--------------- .../passkey => passkey/src}/passkey.test.ts | 4 +- packages/passkey/src/schema.ts | 52 +++++ packages/passkey/src/types.ts | 74 +++++++ packages/passkey/src/utils.ts | 7 + packages/passkey/tsconfig.json | 15 ++ packages/passkey/tsdown.config.ts | 16 ++ pnpm-lock.yaml | 100 ++++++---- tsconfig.json | 3 + 27 files changed, 454 insertions(+), 248 deletions(-) create mode 100644 packages/passkey/README.md create mode 100644 packages/passkey/package.json rename packages/{better-auth/src/plugins/passkey => passkey/src}/client.ts (95%) create mode 100644 packages/passkey/src/error-codes.ts rename packages/{better-auth/src/plugins/passkey => passkey/src}/index.ts (84%) rename packages/{better-auth/src/plugins/passkey => passkey/src}/passkey.test.ts (96%) create mode 100644 packages/passkey/src/schema.ts create mode 100644 packages/passkey/src/types.ts create mode 100644 packages/passkey/src/utils.ts create mode 100644 packages/passkey/tsconfig.json create mode 100644 packages/passkey/tsdown.config.ts diff --git a/demo/nextjs/lib/auth-client.ts b/demo/nextjs/lib/auth-client.ts index ba399e8773..66f9a03555 100644 --- a/demo/nextjs/lib/auth-client.ts +++ b/demo/nextjs/lib/auth-client.ts @@ -1,3 +1,4 @@ +import { passkeyClient } from "@better-auth/passkey/client"; import { stripeClient } from "@better-auth/stripe/client"; import { adminClient, @@ -8,7 +9,6 @@ import { oidcClient, oneTapClient, organizationClient, - passkeyClient, twoFactorClient, } from "better-auth/client/plugins"; import { createAuthClient } from "better-auth/react"; diff --git a/demo/nextjs/lib/auth.ts b/demo/nextjs/lib/auth.ts index dc0051be26..82e041ab1e 100644 --- a/demo/nextjs/lib/auth.ts +++ b/demo/nextjs/lib/auth.ts @@ -1,3 +1,4 @@ +import { passkey } from "@better-auth/passkey"; import { sso } from "@better-auth/sso"; import { stripe } from "@better-auth/stripe"; import { LibsqlDialect } from "@libsql/kysely-libsql"; @@ -16,7 +17,6 @@ import { organization, twoFactor, } from "better-auth/plugins"; -import { passkey } from "better-auth/plugins/passkey"; import { MysqlDialect } from "kysely"; import { createPool } from "mysql2/promise"; import { Stripe } from "stripe"; diff --git a/demo/nextjs/package.json b/demo/nextjs/package.json index a26945e677..1ef3e93d90 100644 --- a/demo/nextjs/package.json +++ b/demo/nextjs/package.json @@ -13,6 +13,7 @@ "dependencies": { "@better-auth/sso": "workspace:*", "@better-auth/stripe": "workspace:*", + "@better-auth/passkey": "workspace:*", "@hookform/resolvers": "^5.2.1", "@libsql/client": "^0.8.1", "@libsql/kysely-libsql": "^0.4.1", diff --git a/docs/components/landing/hero.tsx b/docs/components/landing/hero.tsx index 40ed0f4128..acf0f739ba 100644 --- a/docs/components/landing/hero.tsx +++ b/docs/components/landing/hero.tsx @@ -161,15 +161,18 @@ const tabs: { name: "auth.ts" | "client.ts"; code: string }[] = [ enabled: true, }, plugins: [ - organization(), - twoFactor(), + organization(), + twoFactor(), ] })`, }, { name: "client.ts", code: `const client = createAuthClient({ - plugins: [passkeyClient()] + plugins: [ + organizationClient(), + twoFactorClient(), + ] });`, }, ]; diff --git a/docs/content/docs/plugins/passkey.mdx b/docs/content/docs/plugins/passkey.mdx index 30d933ccf9..1a70c9b367 100644 --- a/docs/content/docs/plugins/passkey.mdx +++ b/docs/content/docs/plugins/passkey.mdx @@ -10,39 +10,20 @@ The passkey plugin implementation is powered by [SimpleWebAuthn](https://simplew ## Installation + + ### Install the plugin + + ```package-install + npm install @better-auth/passkey + ``` + ### Add the plugin to your auth config To add the passkey plugin to your auth config, you need to import the plugin and pass it to the `plugins` option of the auth instance. - **Options** - - `rpID`: A unique identifier for your website based on your auth server origin. 'localhost' is okay for local dev. RP ID can be formed by discarding zero or more labels from the left of its effective domain - until it hits an effective TLD. So `www.example.com` can use the RP IDs `www.example.com` or `example.com`. But not `com`, because that's an eTLD. - - `rpName`: Human-readable title for your website - - `origin`: The origin URL at which your better-auth server is hosted. `http://localhost` and `http://localhost:PORT` are also valid. Do **NOT** include any trailing / - - `authenticatorSelection`: Allows customization of WebAuthn authenticator selection criteria. Leave unspecified for default settings. - - `authenticatorAttachment`: Specifies the type of authenticator - - `platform`: Authenticator is attached to the platform (e.g., fingerprint reader) - - `cross-platform`: Authenticator is not attached to the platform (e.g., security key) - - Default: `not set` (both platform and cross-platform allowed, with platform preferred) - - `residentKey`: Determines credential storage behavior. - - `required`: User MUST store credentials on the authenticator (highest security) - - `preferred`: Encourages credential storage but not mandatory - - `discouraged`: No credential storage required (fastest experience) - - Default: `preferred` - - `userVerification`: Controls biometric/PIN verification during authentication: - - `required`: User MUST verify identity (highest security) - - `preferred`: Verification encouraged but not mandatory - - `discouraged`: No verification required (fastest experience) - - Default: `preferred` - - ```ts title="auth.ts" import { betterAuth } from "better-auth" - import { passkey } from "better-auth/plugins/passkey" // [!code highlight] + import { passkey } from "@better-auth/passkey" // [!code highlight] export const auth = betterAuth({ plugins: [ // [!code highlight] @@ -75,7 +56,7 @@ The passkey plugin implementation is powered by [SimpleWebAuthn](https://simplew ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/client" - import { passkeyClient } from "better-auth/client/plugins" + import { passkeyClient } from "@better-auth/passkey/client" export const authClient = createAuthClient({ plugins: [ // [!code highlight] @@ -340,6 +321,23 @@ until it hits an effective TLD. So `www.example.com` can use the RP IDs `www.exa **origin**: The origin URL at which your better-auth server is hosted. `http://localhost` and `http://localhost:PORT` are also valid. Do NOT include any trailing /. -**authenticatorSelection**: Allows customization of WebAuthn authenticator selection criteria. When unspecified, both platform and cross-platform authenticators are allowed with `preferred` settings for `residentKey` and `userVerification`. +**authenticatorSelection**: Allows customization of WebAuthn authenticator selection criteria. Leave unspecified for default settings. -**aaguid**: (optional) Authenticator Attestation GUID. This is a unique identifier for the passkey provider (device or authenticator type) and can be used to identify the type of passkey device used during registration or authentication. \ No newline at end of file +- `authenticatorAttachment`: Specifies the type of authenticator + - `platform`: Authenticator is attached to the platform (e.g., fingerprint reader) + - `cross-platform`: Authenticator is not attached to the platform (e.g., security key) + - Default: `not set` (both platform and cross-platform allowed, with platform preferred) +- `residentKey`: Determines credential storage behavior. + - `required`: User MUST store credentials on the authenticator (highest security) + - `preferred`: Encourages credential storage but not mandatory + - `discouraged`: No credential storage required (fastest experience) + - Default: `preferred` +- `userVerification`: Controls biometric/PIN verification during authentication: + - `required`: User MUST verify identity (highest security) + - `preferred`: Verification encouraged but not mandatory + - `discouraged`: No verification required (fastest experience) + - Default: `preferred` + +**advanced**: Advanced options + +- `webAuthnChallengeCookie`: Cookie name for storing WebAuthn challenge ID during authentication flow (Default: `better-auth-passkey`) diff --git a/e2e/smoke/test/fixtures/tsconfig-declaration/package.json b/e2e/smoke/test/fixtures/tsconfig-declaration/package.json index 5d285600ad..d549c6794e 100644 --- a/e2e/smoke/test/fixtures/tsconfig-declaration/package.json +++ b/e2e/smoke/test/fixtures/tsconfig-declaration/package.json @@ -8,6 +8,7 @@ "dependencies": { "@better-auth/sso": "workspace:*", "@better-auth/stripe": "workspace:*", + "@better-auth/passkey": "workspace:*", "better-auth": "workspace:*", "stripe": "^18.5.0" } diff --git a/e2e/smoke/test/fixtures/tsconfig-declaration/src/demo.ts b/e2e/smoke/test/fixtures/tsconfig-declaration/src/demo.ts index 5422e24b1f..fe8eadbd6f 100644 --- a/e2e/smoke/test/fixtures/tsconfig-declaration/src/demo.ts +++ b/e2e/smoke/test/fixtures/tsconfig-declaration/src/demo.ts @@ -1,3 +1,4 @@ +import { passkey } from "@better-auth/passkey"; import { sso } from "@better-auth/sso"; import { stripe } from "@better-auth/stripe"; import { betterAuth } from "better-auth"; @@ -15,7 +16,6 @@ import { organization, twoFactor, } from "better-auth/plugins"; -import { passkey } from "better-auth/plugins/passkey"; import { Stripe } from "stripe"; export const auth = betterAuth({ diff --git a/packages/better-auth/package.json b/packages/better-auth/package.json index 8e939ebfd3..a2ef515254 100644 --- a/packages/better-auth/package.json +++ b/packages/better-auth/package.json @@ -257,11 +257,6 @@ "types": "./dist/plugins/one-time-token/index.d.ts", "default": "./dist/plugins/one-time-token/index.js" }, - "./plugins/passkey": { - "better-auth-dev-source": "./src/plugins/passkey/index.ts", - "types": "./dist/plugins/passkey/index.d.ts", - "default": "./dist/plugins/passkey/index.js" - }, "./plugins/phone-number": { "better-auth-dev-source": "./src/plugins/phone-number/index.ts", "types": "./dist/plugins/phone-number/index.d.ts", @@ -416,9 +411,6 @@ "plugins/organization/access": [ "./dist/plugins/organization/access/index.d.ts" ], - "plugins/passkey": [ - "./dist/plugins/passkey/index.d.ts" - ], "plugins/phone-number": [ "./dist/plugins/phone-number/index.d.ts" ], @@ -443,8 +435,6 @@ "@better-fetch/fetch": "catalog:", "@noble/ciphers": "^2.0.0", "@noble/hashes": "^2.0.0", - "@simplewebauthn/browser": "^13.1.2", - "@simplewebauthn/server": "^13.1.2", "@standard-schema/spec": "^1.0.0", "better-call": "catalog:", "defu": "^6.1.4", diff --git a/packages/better-auth/src/client/client.test.ts b/packages/better-auth/src/client/client.test.ts index d1469f66ef..df1f071d10 100644 --- a/packages/better-auth/src/client/client.test.ts +++ b/packages/better-auth/src/client/client.test.ts @@ -8,7 +8,7 @@ import { describe, expect, expectTypeOf, it, vi } from "vitest"; import type { Ref } from "vue"; import { twoFactorClient } from "../plugins"; import type { Session, SessionQueryParams } from "../types"; -import { organizationClient, passkeyClient } from "./plugins"; +import { emailOTPClient, organizationClient } from "./plugins"; import { createAuthClient as createReactClient } from "./react"; import { createAuthClient as createSolidClient } from "./solid"; import { createAuthClient as createSvelteClient } from "./svelte"; @@ -278,7 +278,7 @@ describe("type", () => { it("should infer session react", () => { const client = createReactClient({ - plugins: [organizationClient(), twoFactorClient(), passkeyClient()], + plugins: [organizationClient(), twoFactorClient(), emailOTPClient()], }); const $infer = client.$Infer.Session; expectTypeOf().toEqualTypeOf<{ diff --git a/packages/better-auth/src/client/plugins/index.ts b/packages/better-auth/src/client/plugins/index.ts index 49011b7294..deed6573cf 100644 --- a/packages/better-auth/src/client/plugins/index.ts +++ b/packages/better-auth/src/client/plugins/index.ts @@ -1,4 +1,3 @@ -export type * from "@simplewebauthn/server"; export * from "../../plugins/additional-fields/client"; export * from "../../plugins/admin/client"; export * from "../../plugins/anonymous/client"; @@ -15,7 +14,6 @@ export * from "../../plugins/oidc-provider/client"; export * from "../../plugins/one-tap/client"; export * from "../../plugins/one-time-token/client"; export * from "../../plugins/organization/client"; -export * from "../../plugins/passkey/client"; export * from "../../plugins/phone-number/client"; export * from "../../plugins/siwe/client"; export * from "../../plugins/two-factor/client"; diff --git a/packages/better-auth/tsdown.config.ts b/packages/better-auth/tsdown.config.ts index aae5de88df..42b27590e3 100644 --- a/packages/better-auth/tsdown.config.ts +++ b/packages/better-auth/tsdown.config.ts @@ -51,7 +51,6 @@ export default defineConfig({ "./src/plugins/oauth-proxy/index.ts", "./src/plugins/organization/index.ts", "./src/plugins/organization/access/index.ts", - "./src/plugins/passkey/index.ts", "./src/plugins/phone-number/index.ts", "./src/plugins/two-factor/index.ts", "./src/plugins/username/index.ts", diff --git a/packages/cli/package.json b/packages/cli/package.json index 5452de9fcb..2d9307bf1f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -34,6 +34,7 @@ "exports": "./dist/index.js", "bin": "./dist/index.js", "devDependencies": { + "@better-auth/passkey": "workspace:*", "@types/semver": "^7.7.1", "@types/better-sqlite3": "^7.6.13", "@types/prompts": "^2.4.9", diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 99fd7f047b..bdd12df5fe 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -97,8 +97,8 @@ export const supportedPlugins = [ id: "passkey", name: "passkey", clientName: "passkeyClient", - path: `better-auth/plugins/passkey`, - clientPath: "better-auth/client/plugins", + path: `@better-auth/passkey`, + clientPath: "@better-auth/passkey/client", }, { id: "generic-oauth", diff --git a/packages/cli/test/generate-all-db.test.ts b/packages/cli/test/generate-all-db.test.ts index 4a884753cc..bed3c260d2 100644 --- a/packages/cli/test/generate-all-db.test.ts +++ b/packages/cli/test/generate-all-db.test.ts @@ -1,7 +1,7 @@ +import { passkey } from "@better-auth/passkey"; import type { BetterAuthOptions } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { twoFactor, username } from "better-auth/plugins"; -import { passkey } from "better-auth/plugins/passkey"; import { describe, expect, it } from "vitest"; import { generateDrizzleSchema } from "../src/generators/drizzle"; diff --git a/packages/passkey/README.md b/packages/passkey/README.md new file mode 100644 index 0000000000..ecd517a1e4 --- /dev/null +++ b/packages/passkey/README.md @@ -0,0 +1,54 @@ +# Better Auth Passkey Plugin + +## Installation + +```bash +# Using npm +npm install better-auth @better-auth/passkey + +# Using yarn +yarn add better-auth @better-auth/passkey + +# Using pnpm +pnpm add better-auth @better-auth/passkey + +# Using bun +bun add better-auth @better-auth/passkey +``` + +## Usage + +### Server + +```typescript +import { betterAuth } from "better-auth"; +import { passkey } from "@better-auth/passkey"; + +export const auth = betterAuth({ + plugins: [ + passkey({ + rpID: "example.com", + rpName: "My App", + }), + ], +}); +``` + +### Client + +```typescript +import { createAuthClient } from "better-auth/client"; +import { passkeyClient } from "@better-auth/passkey/client"; + +export const authClient = createAuthClient({ + plugins: [passkeyClient()], +}); +``` + +## Documentation + +For more information, visit the [Better Auth Passkey documentation](https://better-auth.com/docs/plugins/passkey). + +## License + +MIT \ No newline at end of file diff --git a/packages/passkey/package.json b/packages/passkey/package.json new file mode 100644 index 0000000000..d7b3260140 --- /dev/null +++ b/packages/passkey/package.json @@ -0,0 +1,73 @@ +{ + "name": "@better-auth/passkey", + "version": "1.4.0-beta.15", + "type": "module", + "description": "Passkey plugin for Better Auth", + "main": "dist/index.js", + "module": "dist/index.js", + "publishConfig": { + "access": "public" + }, + "scripts": { + "test": "vitest", + "build": "tsdown", + "dev": "tsdown --watch", + "typecheck": "tsc --project tsconfig.json" + }, + "exports": { + ".": { + "better-auth-dev-source": "./src/index.ts", + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "./client": { + "better-auth-dev-source": "./src/client.ts", + "types": "./dist/client.d.ts", + "default": "./dist/client.js" + } + }, + "typesVersions": { + "*": { + "*": [ + "./dist/index.d.ts" + ], + "client": [ + "./dist/client.d.ts" + ] + } + }, + "devDependencies": { + "@better-auth/core": "workspace:*", + "better-auth": "workspace:*", + "tsdown": "catalog:" + }, + "dependencies": { + "@simplewebauthn/browser": "^13.1.2", + "@simplewebauthn/server": "^13.1.2", + "zod": "^4.1.5" + }, + "peerDependencies": { + "nanostores": "^1.0.1", + "@better-auth/utils": "0.3.0", + "better-call": "catalog:", + "@better-fetch/fetch": "catalog:", + "@better-auth/core": "workspace:*", + "better-auth": "workspace:*" + }, + "files": [ + "dist" + ], + "repository": { + "type": "git", + "url": "https://github.com/better-auth/better-auth", + "directory": "packages/passkey" + }, + "homepage": "https://www.better-auth.com/docs/plugins/passkey", + "keywords": [ + "auth", + "passkey", + "typescript", + "better-auth" + ], + "license": "MIT" +} diff --git a/packages/better-auth/src/plugins/passkey/client.ts b/packages/passkey/src/client.ts similarity index 95% rename from packages/better-auth/src/plugins/passkey/client.ts rename to packages/passkey/src/client.ts index ca36dfd75f..2f4ae6588a 100644 --- a/packages/better-auth/src/plugins/passkey/client.ts +++ b/packages/passkey/src/client.ts @@ -9,10 +9,11 @@ import { startRegistration, WebAuthnError, } from "@simplewebauthn/browser"; +import { useAuthQuery } from "better-auth/client"; +import type { Session, User } from "better-auth/types"; import { atom } from "nanostores"; -import { useAuthQuery } from "../../client"; -import type { Session, User } from "../../types"; -import type { Passkey, passkey as passkeyPl } from "."; +import type { passkey } from "."; +import type { Passkey } from "./types"; export const getPasskeyActions = ( $fetch: BetterFetch, @@ -216,7 +217,7 @@ export const passkeyClient = () => { const $listPasskeys = atom(); return { id: "passkey", - $InferServerPlugin: {} as ReturnType, + $InferServerPlugin: {} as ReturnType, getActions: ($fetch, $store) => getPasskeyActions($fetch, { $listPasskeys, diff --git a/packages/passkey/src/error-codes.ts b/packages/passkey/src/error-codes.ts new file mode 100644 index 0000000000..71ad1ed1f7 --- /dev/null +++ b/packages/passkey/src/error-codes.ts @@ -0,0 +1,12 @@ +import { defineErrorCodes } from "@better-auth/core/utils"; + +export const PASSKEY_ERROR_CODES = defineErrorCodes({ + CHALLENGE_NOT_FOUND: "Challenge not found", + YOU_ARE_NOT_ALLOWED_TO_REGISTER_THIS_PASSKEY: + "You are not allowed to register this passkey", + FAILED_TO_VERIFY_REGISTRATION: "Failed to verify registration", + PASSKEY_NOT_FOUND: "Passkey not found", + AUTHENTICATION_FAILED: "Authentication failed", + UNABLE_TO_CREATE_SESSION: "Unable to create session", + FAILED_TO_UPDATE_PASSKEY: "Failed to update passkey", +}); diff --git a/packages/better-auth/src/plugins/passkey/index.ts b/packages/passkey/src/index.ts similarity index 84% rename from packages/better-auth/src/plugins/passkey/index.ts rename to packages/passkey/src/index.ts index 77e76f8145..6d6475b896 100644 --- a/packages/better-auth/src/plugins/passkey/index.ts +++ b/packages/passkey/src/index.ts @@ -1,12 +1,9 @@ import type { BetterAuthPlugin } from "@better-auth/core"; import { createAuthEndpoint } from "@better-auth/core/api"; -import type { BetterAuthPluginDBSchema } from "@better-auth/core/db"; -import { defineErrorCodes } from "@better-auth/core/utils"; import { base64 } from "@better-auth/utils/base64"; import type { AuthenticationResponseJSON, AuthenticatorTransportFuture, - CredentialDeviceType, PublicKeyCredentialCreationOptionsJSON, } from "@simplewebauthn/server"; import { @@ -15,97 +12,21 @@ import { verifyAuthenticationResponse, verifyRegistrationResponse, } from "@simplewebauthn/server"; +import { generateId } from "better-auth"; +import { + freshSessionMiddleware, + getSessionFromCtx, + sessionMiddleware, +} from "better-auth/api"; +import { setSessionCookie } from "better-auth/cookies"; +import { generateRandomString } from "better-auth/crypto"; +import { mergeSchema } from "better-auth/db"; import { APIError } from "better-call"; import * as z from "zod"; -import { sessionMiddleware } from "../../api"; -import { freshSessionMiddleware, getSessionFromCtx } from "../../api/routes"; -import { setSessionCookie } from "../../cookies"; -import { generateRandomString } from "../../crypto/random"; -import { mergeSchema } from "../../db/schema"; -import type { InferOptionSchema } from "../../types/plugins"; -import { generateId } from "../../utils"; - -interface WebAuthnChallengeValue { - expectedChallenge: string; - userData: { - id: string; - }; -} - -const ERROR_CODES = defineErrorCodes({ - CHALLENGE_NOT_FOUND: "Challenge not found", - YOU_ARE_NOT_ALLOWED_TO_REGISTER_THIS_PASSKEY: - "You are not allowed to register this passkey", - FAILED_TO_VERIFY_REGISTRATION: "Failed to verify registration", - PASSKEY_NOT_FOUND: "Passkey not found", - AUTHENTICATION_FAILED: "Authentication failed", - UNABLE_TO_CREATE_SESSION: "Unable to create session", - FAILED_TO_UPDATE_PASSKEY: "Failed to update passkey", -}); - -function getRpID(options: PasskeyOptions, baseURL?: string | undefined) { - return ( - options.rpID || (baseURL ? new URL(baseURL).hostname : "localhost") // default rpID - ); -} - -export interface PasskeyOptions { - /** - * A unique identifier for your website. 'localhost' is okay for - * local dev - * - * @default "localhost" - */ - rpID?: string | undefined; - /** - * Human-readable title for your website - * - * @default "Better Auth" - */ - rpName?: string | undefined; - /** - * The URL at which registrations and authentications should occur. - * `http://localhost` and `http://localhost:PORT` are also valid. - * Do NOT include any trailing / - * - * if this isn't provided. The client itself will - * pass this value. - */ - origin?: (string | string[] | null) | undefined; - - /** - * Allow customization of the authenticatorSelection options - * during passkey registration. - */ - authenticatorSelection?: AuthenticatorSelectionCriteria | undefined; - - /** - * Advanced options - */ - advanced?: - | { - webAuthnChallengeCookie?: string; - } - | undefined; - /** - * Schema for the passkey model - */ - schema?: InferOptionSchema | undefined; -} - -export type Passkey = { - id: string; - name?: string | undefined; - publicKey: string; - userId: string; - credentialID: string; - counter: number; - deviceType: CredentialDeviceType; - backedUp: boolean; - transports?: string | undefined; - createdAt: Date; - aaguid?: string | undefined; -}; +import { PASSKEY_ERROR_CODES } from "./error-codes"; +import { schema } from "./schema"; +import type { Passkey, PasskeyOptions, WebAuthnChallengeValue } from "./types"; +import { getRpID } from "./utils"; export const passkey = (options?: PasskeyOptions | undefined) => { const opts = { @@ -531,7 +452,7 @@ export const passkey = (options?: PasskeyOptions | undefined) => { ); if (!challengeId) { throw new APIError("BAD_REQUEST", { - message: ERROR_CODES.CHALLENGE_NOT_FOUND, + message: PASSKEY_ERROR_CODES.CHALLENGE_NOT_FOUND, }); } @@ -550,7 +471,8 @@ export const passkey = (options?: PasskeyOptions | undefined) => { if (userData.id !== ctx.context.session.user.id) { throw new APIError("UNAUTHORIZED", { - message: ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_REGISTER_THIS_PASSKEY, + message: + PASSKEY_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_REGISTER_THIS_PASSKEY, }); } @@ -604,7 +526,7 @@ export const passkey = (options?: PasskeyOptions | undefined) => { } catch (e) { console.log(e); throw new APIError("INTERNAL_SERVER_ERROR", { - message: ERROR_CODES.FAILED_TO_VERIFY_REGISTRATION, + message: PASSKEY_ERROR_CODES.FAILED_TO_VERIFY_REGISTRATION, }); } }, @@ -664,7 +586,7 @@ export const passkey = (options?: PasskeyOptions | undefined) => { ); if (!challengeId) { throw new APIError("BAD_REQUEST", { - message: ERROR_CODES.CHALLENGE_NOT_FOUND, + message: PASSKEY_ERROR_CODES.CHALLENGE_NOT_FOUND, }); } @@ -674,7 +596,7 @@ export const passkey = (options?: PasskeyOptions | undefined) => { ); if (!data) { throw new APIError("BAD_REQUEST", { - message: ERROR_CODES.CHALLENGE_NOT_FOUND, + message: PASSKEY_ERROR_CODES.CHALLENGE_NOT_FOUND, }); } const { expectedChallenge } = JSON.parse( @@ -691,7 +613,7 @@ export const passkey = (options?: PasskeyOptions | undefined) => { }); if (!passkey) { throw new APIError("UNAUTHORIZED", { - message: ERROR_CODES.PASSKEY_NOT_FOUND, + message: PASSKEY_ERROR_CODES.PASSKEY_NOT_FOUND, }); } try { @@ -713,7 +635,7 @@ export const passkey = (options?: PasskeyOptions | undefined) => { const { verified } = verification; if (!verified) throw new APIError("UNAUTHORIZED", { - message: ERROR_CODES.AUTHENTICATION_FAILED, + message: PASSKEY_ERROR_CODES.AUTHENTICATION_FAILED, }); await ctx.context.adapter.update({ @@ -733,7 +655,7 @@ export const passkey = (options?: PasskeyOptions | undefined) => { ); if (!s) { throw new APIError("INTERNAL_SERVER_ERROR", { - message: ERROR_CODES.UNABLE_TO_CREATE_SESSION, + message: PASSKEY_ERROR_CODES.UNABLE_TO_CREATE_SESSION, }); } const user = await ctx.context.internalAdapter.findUserById( @@ -759,7 +681,7 @@ export const passkey = (options?: PasskeyOptions | undefined) => { } catch (e) { ctx.context.logger.error("Failed to verify authentication", e); throw new APIError("BAD_REQUEST", { - message: ERROR_CODES.AUTHENTICATION_FAILED, + message: PASSKEY_ERROR_CODES.AUTHENTICATION_FAILED, }); } }, @@ -956,13 +878,14 @@ export const passkey = (options?: PasskeyOptions | undefined) => { if (!passkey) { throw new APIError("NOT_FOUND", { - message: ERROR_CODES.PASSKEY_NOT_FOUND, + message: PASSKEY_ERROR_CODES.PASSKEY_NOT_FOUND, }); } if (passkey.userId !== ctx.context.session.user.id) { throw new APIError("UNAUTHORIZED", { - message: ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_REGISTER_THIS_PASSKEY, + message: + PASSKEY_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_REGISTER_THIS_PASSKEY, }); } @@ -981,7 +904,7 @@ export const passkey = (options?: PasskeyOptions | undefined) => { if (!updatedPasskey) { throw new APIError("INTERNAL_SERVER_ERROR", { - message: ERROR_CODES.FAILED_TO_UPDATE_PASSKEY, + message: PASSKEY_ERROR_CODES.FAILED_TO_UPDATE_PASSKEY, }); } return ctx.json( @@ -996,57 +919,8 @@ export const passkey = (options?: PasskeyOptions | undefined) => { ), }, schema: mergeSchema(schema, options?.schema), - $ERROR_CODES: ERROR_CODES, + $ERROR_CODES: PASSKEY_ERROR_CODES, } satisfies BetterAuthPlugin; }; -const schema = { - passkey: { - fields: { - name: { - type: "string", - required: false, - }, - publicKey: { - type: "string", - required: true, - }, - userId: { - type: "string", - references: { - model: "user", - field: "id", - }, - required: true, - }, - credentialID: { - type: "string", - required: true, - }, - counter: { - type: "number", - required: true, - }, - deviceType: { - type: "string", - required: true, - }, - backedUp: { - type: "boolean", - required: true, - }, - transports: { - type: "string", - required: false, - }, - createdAt: { - type: "date", - required: false, - }, - aaguid: { - type: "string", - required: false, - }, - }, - }, -} satisfies BetterAuthPluginDBSchema; +export type { Passkey, PasskeyOptions }; diff --git a/packages/better-auth/src/plugins/passkey/passkey.test.ts b/packages/passkey/src/passkey.test.ts similarity index 96% rename from packages/better-auth/src/plugins/passkey/passkey.test.ts rename to packages/passkey/src/passkey.test.ts index bdef936e7b..2e2754bb2a 100644 --- a/packages/better-auth/src/plugins/passkey/passkey.test.ts +++ b/packages/passkey/src/passkey.test.ts @@ -1,6 +1,6 @@ +import { createAuthClient } from "better-auth/client"; +import { getTestInstanceMemory as getTestInstance } from "better-auth/test"; import { describe, expect, it } from "vitest"; -import { createAuthClient } from "../../client"; -import { getTestInstance } from "../../test-utils/test-instance"; import { type Passkey, passkey } from "."; import { passkeyClient } from "./client"; diff --git a/packages/passkey/src/schema.ts b/packages/passkey/src/schema.ts new file mode 100644 index 0000000000..7acf261e37 --- /dev/null +++ b/packages/passkey/src/schema.ts @@ -0,0 +1,52 @@ +import type { BetterAuthPluginDBSchema } from "@better-auth/core/db"; + +export const schema = { + passkey: { + fields: { + name: { + type: "string", + required: false, + }, + publicKey: { + type: "string", + required: true, + }, + userId: { + type: "string", + references: { + model: "user", + field: "id", + }, + required: true, + }, + credentialID: { + type: "string", + required: true, + }, + counter: { + type: "number", + required: true, + }, + deviceType: { + type: "string", + required: true, + }, + backedUp: { + type: "boolean", + required: true, + }, + transports: { + type: "string", + required: false, + }, + createdAt: { + type: "date", + required: false, + }, + aaguid: { + type: "string", + required: false, + }, + }, + }, +} satisfies BetterAuthPluginDBSchema; diff --git a/packages/passkey/src/types.ts b/packages/passkey/src/types.ts new file mode 100644 index 0000000000..9f33ab40fd --- /dev/null +++ b/packages/passkey/src/types.ts @@ -0,0 +1,74 @@ +import type { CredentialDeviceType } from "@simplewebauthn/server"; +import type { InferOptionSchema } from "better-auth/types"; +import type { schema } from "./schema"; + +/** + * @internal + */ +export interface WebAuthnChallengeValue { + expectedChallenge: string; + userData: { + id: string; + }; +} + +export interface PasskeyOptions { + /** + * A unique identifier for your website. 'localhost' is okay for + * local dev + * + * @default "localhost" + */ + rpID?: string | undefined; + /** + * Human-readable title for your website + * + * @default "Better Auth" + */ + rpName?: string | undefined; + /** + * The URL at which registrations and authentications should occur. + * `http://localhost` and `http://localhost:PORT` are also valid. + * Do NOT include any trailing / + * + * if this isn't provided. The client itself will + * pass this value. + */ + origin?: (string | string[] | null) | undefined; + /** + * Allow customization of the authenticatorSelection options + * during passkey registration. + */ + authenticatorSelection?: AuthenticatorSelectionCriteria | undefined; + /** + * Advanced options + */ + advanced?: + | { + /** + * Cookie name for storing WebAuthn challenge ID during authentication flow + * + * @default "better-auth-passkey" + */ + webAuthnChallengeCookie?: string; + } + | undefined; + /** + * Schema for the passkey model + */ + schema?: InferOptionSchema | undefined; +} + +export type Passkey = { + id: string; + name?: string | undefined; + publicKey: string; + userId: string; + credentialID: string; + counter: number; + deviceType: CredentialDeviceType; + backedUp: boolean; + transports?: string | undefined; + createdAt: Date; + aaguid?: string | undefined; +}; diff --git a/packages/passkey/src/utils.ts b/packages/passkey/src/utils.ts new file mode 100644 index 0000000000..6724d40eec --- /dev/null +++ b/packages/passkey/src/utils.ts @@ -0,0 +1,7 @@ +import type { PasskeyOptions } from "./types"; + +export function getRpID(options: PasskeyOptions, baseURL?: string | undefined) { + return ( + options.rpID || (baseURL ? new URL(baseURL).hostname : "localhost") // default rpID + ); +} diff --git a/packages/passkey/tsconfig.json b/packages/passkey/tsconfig.json new file mode 100644 index 0000000000..b61bfac631 --- /dev/null +++ b/packages/passkey/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "lib": ["esnext", "dom", "dom.iterable"] + }, + "include": ["./src"], + "references": [ + { + "path": "../better-auth/tsconfig.json" + }, + { + "path": "../core/tsconfig.json" + } + ] +} diff --git a/packages/passkey/tsdown.config.ts b/packages/passkey/tsdown.config.ts new file mode 100644 index 0000000000..d086d2767a --- /dev/null +++ b/packages/passkey/tsdown.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from "tsdown"; + +export default defineConfig({ + dts: { build: true, incremental: true }, + format: ["esm"], + entry: ["./src/index.ts", "./src/client.ts"], + external: [ + "nanostores", + "@better-auth/utils", + "better-call", + "@better-fetch/fetch", + "@better-auth/core", + "better-auth", + ], + treeshake: true, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1bc3205e37..e1a26bbfaf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -206,6 +206,9 @@ importers: demo/nextjs: dependencies: + '@better-auth/passkey': + specifier: workspace:* + version: link:../../packages/passkey '@better-auth/sso': specifier: workspace:* version: link:../../packages/sso @@ -352,7 +355,7 @@ importers: version: 12.23.12(react-dom@19.2.0(react@19.2.0))(react@19.2.0) geist: specifier: ^1.4.2 - version: 1.4.2(next@16.0.1(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.90.0)) + version: 1.4.2(next@16.0.1(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.90.0)) input-otp: specifier: ^1.4.2 version: 1.4.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -639,7 +642,7 @@ importers: version: 15.8.3(@oramacloud/client@2.1.4)(@tanstack/react-router@1.131.27(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(algoliasearch@5.36.0)(lucide-react@0.542.0(react@19.2.0))(next@16.0.1(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.90.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(tailwindcss@4.1.13) geist: specifier: ^1.4.2 - version: 1.4.2(next@16.0.1(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.90.0)) + version: 1.4.2(next@16.0.1(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.90.0)) gray-matter: specifier: ^4.0.3 version: 4.0.3 @@ -873,6 +876,9 @@ importers: e2e/smoke/test/fixtures/tsconfig-declaration: dependencies: + '@better-auth/passkey': + specifier: workspace:* + version: link:../../../../../packages/passkey '@better-auth/sso': specifier: workspace:* version: link:../../../../../packages/sso @@ -946,12 +952,6 @@ importers: '@noble/hashes': specifier: ^2.0.0 version: 2.0.0 - '@simplewebauthn/browser': - specifier: ^13.1.2 - version: 13.1.2 - '@simplewebauthn/server': - specifier: ^13.1.2 - version: 13.1.2 '@standard-schema/spec': specifier: ^1.0.0 version: 1.0.0 @@ -1161,6 +1161,9 @@ importers: specifier: ^4.1.5 version: 4.1.5 devDependencies: + '@better-auth/passkey': + specifier: workspace:* + version: link:../passkey '@types/better-sqlite3': specifier: ^7.6.13 version: 7.6.13 @@ -1263,6 +1266,40 @@ importers: specifier: 'catalog:' version: 0.15.11(typescript@5.9.3) + packages/passkey: + dependencies: + '@better-auth/utils': + specifier: 0.3.0 + version: 0.3.0 + '@better-fetch/fetch': + specifier: 'catalog:' + version: 1.1.18 + '@simplewebauthn/browser': + specifier: ^13.1.2 + version: 13.1.2 + '@simplewebauthn/server': + specifier: ^13.1.2 + version: 13.1.2 + better-call: + specifier: 'catalog:' + version: 1.0.24 + nanostores: + specifier: ^1.0.1 + version: 1.0.1 + zod: + specifier: ^4.1.5 + version: 4.1.5 + devDependencies: + '@better-auth/core': + specifier: workspace:* + version: link:../core + better-auth: + specifier: workspace:* + version: link:../better-auth + tsdown: + specifier: 'catalog:' + version: 0.15.11(typescript@5.9.3) + packages/sso: dependencies: '@better-fetch/fetch': @@ -13695,7 +13732,7 @@ snapshots: '@babel/helper-member-expression-to-functions@7.27.1': dependencies: '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color @@ -13728,7 +13765,7 @@ snapshots: '@babel/helper-optimise-call-expression@7.27.1': dependencies: - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 '@babel/helper-plugin-utils@7.27.1': {} @@ -15367,7 +15404,7 @@ snapshots: postcss: 8.4.49 resolve-from: 5.0.0 optionalDependencies: - expo: 54.0.21(@babel/core@7.28.4)(@expo/metro-runtime@6.1.2)(expo-router@6.0.14)(graphql@16.11.0)(react-native@0.80.2(@babel/core@7.28.4)(@react-native-community/cli@20.0.1(typescript@5.9.3))(@types/react@19.2.2)(react@19.2.0))(react@19.2.0) + expo: 54.0.21(@babel/core@7.28.4)(@expo/metro-runtime@6.1.2)(expo-router@6.0.14)(graphql@16.11.0)(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.1(typescript@5.9.3))(@react-native/metro-config@0.81.0(@babel/core@7.28.4))(@types/react@19.2.2)(react@19.2.0))(react@19.2.0) transitivePeerDependencies: - bufferutil - supports-color @@ -15452,7 +15489,7 @@ snapshots: '@expo/json-file': 10.0.7 '@react-native/normalize-colors': 0.81.5 debug: 4.4.3 - expo: 54.0.21(@babel/core@7.28.4)(@expo/metro-runtime@6.1.2)(expo-router@6.0.14)(graphql@16.11.0)(react-native@0.80.2(@babel/core@7.28.4)(@react-native-community/cli@20.0.1(typescript@5.9.3))(@types/react@19.2.2)(react@19.2.0))(react@19.2.0) + expo: 54.0.21(@babel/core@7.28.4)(@expo/metro-runtime@6.1.2)(expo-router@6.0.14)(graphql@16.11.0)(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.1(typescript@5.9.3))(@react-native/metro-config@0.81.0(@babel/core@7.28.4))(@types/react@19.2.2)(react@19.2.0))(react@19.2.0) resolve-from: 5.0.0 semver: 7.7.3 xml2js: 0.6.0 @@ -17676,9 +17713,7 @@ snapshots: metro-runtime: 0.83.3 transitivePeerDependencies: - '@babel/core' - - bufferutil - supports-color - - utf-8-validate optional: true '@react-native/normalize-colors@0.74.89': {} @@ -17960,7 +17995,7 @@ snapshots: estree-walker: 2.0.2 fdir: 6.5.0(picomatch@4.0.3) is-reference: 1.2.1 - magic-string: 0.30.19 + magic-string: 0.30.21 picomatch: 4.0.3 optionalDependencies: rollup: 4.52.5 @@ -17969,7 +18004,7 @@ snapshots: dependencies: '@rollup/pluginutils': 5.2.0(rollup@4.52.5) estree-walker: 2.0.2 - magic-string: 0.30.19 + magic-string: 0.30.21 optionalDependencies: rollup: 4.52.5 @@ -17992,7 +18027,7 @@ snapshots: '@rollup/plugin-replace@6.0.2(rollup@4.52.5)': dependencies: '@rollup/pluginutils': 5.2.0(rollup@4.52.5) - magic-string: 0.30.19 + magic-string: 0.30.21 optionalDependencies: rollup: 4.52.5 @@ -18630,7 +18665,7 @@ snapshots: '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.4) '@babel/template': 7.27.2 '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 '@tanstack/directive-functions-plugin': 1.131.2(vite@7.1.11(@types/node@24.9.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) babel-dead-code-elimination: 1.0.10 tiny-invariant: 1.3.3 @@ -18658,7 +18693,7 @@ snapshots: dependencies: '@babel/code-frame': 7.26.2 '@babel/core': 7.28.4 - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 '@tanstack/router-core': 1.131.27 '@tanstack/router-generator': 1.131.27 '@tanstack/router-plugin': 1.131.27(@tanstack/react-router@1.131.27(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(vite-plugin-solid@2.11.8(solid-js@1.9.9)(vite@7.1.11(@types/node@24.9.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)))(vite@7.1.11(@types/node@24.9.2)(jiti@2.6.1)(less@4.4.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) @@ -19222,7 +19257,7 @@ snapshots: '@vue/compiler-core@3.5.19': dependencies: - '@babel/parser': 7.28.4 + '@babel/parser': 7.28.5 '@vue/shared': 3.5.19 entities: 4.5.0 estree-walker: 2.0.2 @@ -19669,7 +19704,7 @@ snapshots: resolve-from: 5.0.0 optionalDependencies: '@babel/runtime': 7.28.4 - expo: 54.0.21(@babel/core@7.28.4)(@expo/metro-runtime@6.1.2)(expo-router@6.0.14)(graphql@16.11.0)(react-native@0.80.2(@babel/core@7.28.4)(@react-native-community/cli@20.0.1(typescript@5.9.3))(@types/react@19.2.2)(react@19.2.0))(react@19.2.0) + expo: 54.0.21(@babel/core@7.28.4)(@expo/metro-runtime@6.1.2)(expo-router@6.0.14)(graphql@16.11.0)(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.1(typescript@5.9.3))(@react-native/metro-config@0.81.0(@babel/core@7.28.4))(@types/react@19.2.2)(react@19.2.0))(react@19.2.0) transitivePeerDependencies: - '@babel/core' - supports-color @@ -19716,7 +19751,7 @@ snapshots: '@better-auth/utils': 0.3.0 '@better-fetch/fetch': 1.1.18 rou3: 0.5.1 - set-cookie-parser: 2.7.1 + set-cookie-parser: 2.7.2 uncrypto: 0.1.3 better-opn@3.0.2: @@ -21159,7 +21194,7 @@ snapshots: expo-keep-awake@15.0.7(expo@54.0.21)(react@19.2.0): dependencies: - expo: 54.0.21(@babel/core@7.28.4)(@expo/metro-runtime@6.1.2)(expo-router@6.0.14)(graphql@16.11.0)(react-native@0.80.2(@babel/core@7.28.4)(@react-native-community/cli@20.0.1(typescript@5.9.3))(@types/react@19.2.2)(react@19.2.0))(react@19.2.0) + expo: 54.0.21(@babel/core@7.28.4)(@expo/metro-runtime@6.1.2)(expo-router@6.0.14)(graphql@16.11.0)(react-native@0.81.5(@babel/core@7.28.4)(@react-native-community/cli@20.0.1(typescript@5.9.3))(@react-native/metro-config@0.81.0(@babel/core@7.28.4))(@types/react@19.2.2)(react@19.2.0))(react@19.2.0) react: 19.2.0 expo-linking@7.1.7(expo@54.0.21)(react-native@0.80.2(@babel/core@7.28.4)(@react-native-community/cli@20.0.1(typescript@5.9.3))(@types/react@19.2.2)(react@19.2.0))(react@19.2.0): @@ -21811,7 +21846,7 @@ snapshots: function-bind@1.1.2: {} - geist@1.4.2(next@16.0.1(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.90.0)): + geist@1.4.2(next@16.0.1(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.90.0)): dependencies: next: 16.0.1(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.90.0) @@ -22947,8 +22982,8 @@ snapshots: magicast@0.2.11: dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 recast: 0.23.11 magicast@0.3.5: @@ -23567,11 +23602,11 @@ snapshots: dependencies: '@babel/code-frame': 7.27.1 '@babel/core': 7.28.4 - '@babel/generator': 7.28.3 - '@babel/parser': 7.28.4 + '@babel/generator': 7.28.5 + '@babel/parser': 7.28.5 '@babel/template': 7.27.2 '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 accepts: 1.3.8 chalk: 4.1.2 ci-info: 2.0.0 @@ -26145,8 +26180,7 @@ snapshots: set-cookie-parser@2.7.1: {} - set-cookie-parser@2.7.2: - optional: true + set-cookie-parser@2.7.2: {} setimmediate@1.0.5: {} @@ -26982,7 +27016,7 @@ snapshots: escape-string-regexp: 5.0.0 estree-walker: 3.0.3 local-pkg: 1.1.2 - magic-string: 0.30.19 + magic-string: 0.30.21 mlly: 1.7.4 pathe: 2.0.3 picomatch: 4.0.3 @@ -27105,7 +27139,7 @@ snapshots: unwasm@0.3.11: dependencies: knitwork: 1.2.0 - magic-string: 0.30.19 + magic-string: 0.30.21 mlly: 1.7.4 pathe: 2.0.3 pkg-types: 2.3.0 diff --git a/tsconfig.json b/tsconfig.json index ac84be6aa2..af38a33910 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,6 +18,9 @@ { "path": "./packages/expo/tsconfig.json" }, + { + "path": "./packages/passkey/tsconfig.json" + }, { "path": "./packages/sso/tsconfig.json" },