From 8f68ae17c75a5051993c45552ce541da6ca92ddf Mon Sep 17 00:00:00 2001 From: Hashibutogarasu Date: Mon, 9 Mar 2026 03:03:56 +0900 Subject: [PATCH] fix: fixed runtime error on nestjs version 1.4.x --- src/firebase-auth-plugin.ts | 126 ++++++++++++++---------------------- 1 file changed, 48 insertions(+), 78 deletions(-) diff --git a/src/firebase-auth-plugin.ts b/src/firebase-auth-plugin.ts index 2fa0b5c..5d8b868 100644 --- a/src/firebase-auth-plugin.ts +++ b/src/firebase-auth-plugin.ts @@ -1,19 +1,10 @@ -import type { BetterAuthPlugin } from "better-auth"; -import { APIError, createAuthEndpoint } from "better-auth/api"; -import { createAuthMiddleware } from "better-auth/plugins"; +import type { BetterAuthPlugin, GenericEndpointContext } from "better-auth"; +import { APIError, createAuthEndpoint, createAuthMiddleware } from "better-auth/api"; import type { FirebaseOptions } from "firebase/app"; import { getAuth } from "firebase-admin/auth"; import type { AuthResponse, FirebaseAuthPluginOptions } from "./types"; -// Context type for Better Auth endpoints and hooks -// Using any for adapter to maintain compatibility with Better Auth's DBAdapter type -type Context = { - context: { - adapter: any; - internalAdapter: any; - }; - json: (data: any) => Promise | Response; -}; +type Context = GenericEndpointContext; type DecodedToken = { uid: string; @@ -30,90 +21,69 @@ const createOrUpdateUser = async ( idToken: string, sessionExpiresInDays: number = 7, ): Promise => { - const { adapter, internalAdapter } = ctx.context; - - // First, try to find user by existing Firebase account - // Check if adapter has getAccount method (some adapters may not support it) - let account = null; - if (typeof adapter.getAccount === "function") { - try { - account = await adapter.getAccount({ - provider: "firebase", - providerAccountId: decodedToken.uid, - }); - } catch { - // Account doesn't exist, continue - } - } + const { internalAdapter } = ctx.context; let user = null; - if (account) { - // User exists with this Firebase account - user = await internalAdapter.getUser({ id: account.userId }); - } else if (decodedToken.email) { - // Try to find user by email - user = await internalAdapter.getUser({ - email: decodedToken.email, - }); + const oauthUser = await internalAdapter.findOAuthUser( + decodedToken.email || "", + decodedToken.uid, + "firebase", + ); + + user = oauthUser?.user || null; + const account = oauthUser?.linkedAccount || null; + + if (!user && decodedToken.email) { + const existingUser = await internalAdapter.findUserByEmail(decodedToken.email); + user = existingUser?.user || null; } if (!user) { user = await internalAdapter.createUser({ - email: decodedToken.email || null, - name: decodedToken.name || null, - image: decodedToken.picture || null, + email: decodedToken.email || "", + name: decodedToken.name || "", + image: decodedToken.picture || undefined, emailVerified: decodedToken.email_verified || false, }); } else { - user = await internalAdapter.updateUser({ - id: user.id, + user = await internalAdapter.updateUser(user.id, { name: decodedToken.name || user.name, image: decodedToken.picture || user.image, emailVerified: decodedToken.email_verified ?? user.emailVerified, }); } - // Use upsertAccount if available, otherwise fall back to createAccount - const accountData = { - provider: "firebase", - providerAccountId: decodedToken.uid, - userId: user.id, - accessToken: idToken, - expiresAt: decodedToken.exp ? new Date(decodedToken.exp * 1000) : null, - }; - - if (typeof adapter.upsertAccount === "function") { - await adapter.upsertAccount(accountData); - } else if (typeof adapter.createAccount === "function") { - try { - await adapter.createAccount(accountData); - } catch (error) { - // If account already exists, ignore the error (account already exists) - // This handles cases where the account was created in a previous sign-in - if ( - error instanceof Error && - !error.message.includes("already exists") && - !error.message.includes("unique constraint") && - !error.message.includes("duplicate") - ) { - throw error; - } - } + if (!account) { + await internalAdapter.linkAccount({ + providerId: "firebase", + accountId: decodedToken.uid, + userId: user.id, + idToken: idToken, + accessTokenExpiresAt: decodedToken.exp ? new Date(decodedToken.exp * 1000) : undefined, + }); + } else { + await internalAdapter.updateAccount(account.id, { + idToken: idToken, + accessTokenExpiresAt: decodedToken.exp ? new Date(decodedToken.exp * 1000) : undefined, + }); } - const session = await internalAdapter.createSession({ - userId: user.id, - expiresAt: new Date( - Date.now() + 1000 * 60 * 60 * 24 * sessionExpiresInDays, - ), - }); + const session = await internalAdapter.createSession( + user.id, + undefined, + { + expiresAt: new Date( + Date.now() + 1000 * 60 * 60 * 24 * sessionExpiresInDays, + ), + } + ); return { user: { id: user.id, email: user.email, name: user.name, - image: user.image, + image: user.image || null, }, session: { id: session.id, @@ -130,7 +100,7 @@ const getFirebaseApp = async ( const apps = firebaseApp.getApps(); return apps.length === 0 ? firebaseApp.initializeApp(firebaseConfig, "better-auth-firebase") - : apps[0]!; + : apps[0]; }; export const firebaseAuthPlugin = ( @@ -415,8 +385,10 @@ export const firebaseAuthPlugin = ( ); } + type MiddleWareCtx = Parameters[0]>[0]; + const handleEmailAuth = async ( - ctx: Parameters[0]>[0], + ctx: MiddleWareCtx, isSignUp: boolean, ) => { const { email, password, name } = ctx.body as { @@ -441,9 +413,7 @@ export const firebaseAuthPlugin = ( const app = await getFirebaseApp(firebaseConfig); const auth = getAuth(app); - let userCredential: - | Awaited> - | Awaited>; + let userCredential: Awaited>; if (isSignUp) { userCredential = await createUserWithEmailAndPassword(