UNPKG

@lobehub/chat

Version:

Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.

277 lines (251 loc) • 9.73 kB
import type { AdapterAuthenticator, AdapterSession, AdapterUser, VerificationToken, } from '@auth/core/adapters'; import { and, eq } from 'drizzle-orm/expressions'; import type { NeonDatabase } from 'drizzle-orm/neon-serverless'; import { Adapter, AdapterAccount } from 'next-auth/adapters'; import { UserModel } from '@/database/models/user'; import * as schema from '@/database/schemas'; import { AgentService } from '@/server/services/agent'; import { merge } from '@/utils/merge'; import { mapAdapterUserToLobeUser, mapAuthenticatorQueryResutlToAdapterAuthenticator, mapLobeUserToAdapterUser, partialMapAdapterUserToLobeUser, } from './utils'; const { nextauthAccounts, nextauthAuthenticators, nextauthSessions, nextauthVerificationTokens, users, } = schema; /** * @description LobeNextAuthDbAdapter is implemented to handle the database operations * for NextAuth, this function do the same things as `src/app/api/webhooks/clerk/route.ts` * @returns {Adapter} */ export function LobeNextAuthDbAdapter(serverDB: NeonDatabase<typeof schema>): Adapter { return { async createAuthenticator(authenticator): Promise<AdapterAuthenticator> { const result = await serverDB .insert(nextauthAuthenticators) .values(authenticator) .returning() .then((res) => res[0] ?? undefined); if (!result) throw new Error('LobeNextAuthDbAdapter: Failed to create authenticator'); return mapAuthenticatorQueryResutlToAdapterAuthenticator(result); }, async createSession(data): Promise<AdapterSession> { return serverDB .insert(nextauthSessions) .values(data) .returning() .then((res) => res[0]); }, async createUser(user): Promise<AdapterUser> { const { id, name, email, emailVerified, image, providerAccountId } = user; // return the user if it already exists let existingUser = email && typeof email === 'string' && email.trim() ? await UserModel.findByEmail(serverDB, email) : undefined; // If the user is not found by email, try to find by providerAccountId if (!existingUser && providerAccountId) { existingUser = await UserModel.findById(serverDB, providerAccountId); } if (existingUser) { const adapterUser = mapLobeUserToAdapterUser(existingUser); return adapterUser; } // create a new user if it does not exist // Use id from provider if it exists, otherwise use id assigned by next-auth // ref: https://github.com/lobehub/lobe-chat/pull/2935 const uid = providerAccountId ?? id; await UserModel.createUser( serverDB, mapAdapterUserToLobeUser({ email, emailVerified, // Use providerAccountId as userid to identify if the user exists in a SSO provider id: uid, image, name, }), ); // 3. Create an inbox session for the user const agentService = new AgentService(serverDB, uid); await agentService.createInbox(); return { ...user, id: uid }; }, async createVerificationToken(data): Promise<VerificationToken | null | undefined> { return serverDB .insert(nextauthVerificationTokens) .values(data) .returning() .then((res) => res[0]); }, async deleteSession(sessionToken): Promise<AdapterSession | null | undefined> { await serverDB .delete(nextauthSessions) .where(eq(nextauthSessions.sessionToken, sessionToken)); return; }, async deleteUser(id): Promise<AdapterUser | null | undefined> { const user = await UserModel.findById(serverDB, id); if (!user) throw new Error('NextAuth: Delete User not found'); await UserModel.deleteUser(serverDB, id); return; }, async getAccount(providerAccountId, provider): Promise<AdapterAccount | null> { return serverDB .select() .from(nextauthAccounts) .where( and( eq(nextauthAccounts.provider, provider), eq(nextauthAccounts.providerAccountId, providerAccountId), ), ) .then((res) => res[0] ?? null) as Promise<AdapterAccount | null>; }, async getAuthenticator(credentialID): Promise<AdapterAuthenticator | null> { const result = await serverDB .select() .from(nextauthAuthenticators) .where(eq(nextauthAuthenticators.credentialID, credentialID)) .then((res) => res[0] ?? null); if (!result) throw new Error('LobeNextAuthDbAdapter: Failed to get authenticator'); return mapAuthenticatorQueryResutlToAdapterAuthenticator(result); }, async getSessionAndUser(sessionToken): Promise<{ session: AdapterSession; user: AdapterUser; } | null> { const result = await serverDB .select({ session: nextauthSessions, user: users, }) .from(nextauthSessions) .where(eq(nextauthSessions.sessionToken, sessionToken)) .innerJoin(users, eq(users.id, nextauthSessions.userId)) .then((res) => (res.length > 0 ? res[0] : null)); if (!result) return null; const adapterUser = mapLobeUserToAdapterUser(result.user); if (!adapterUser) return null; return { session: result.session, user: adapterUser, }; }, async getUser(id): Promise<AdapterUser | null> { const lobeUser = await UserModel.findById(serverDB, id); if (!lobeUser) return null; return mapLobeUserToAdapterUser(lobeUser); }, async getUserByAccount(account): Promise<AdapterUser | null> { const result = await serverDB .select({ account: nextauthAccounts, users, }) .from(nextauthAccounts) .innerJoin(users, eq(nextauthAccounts.userId, users.id)) .where( and( eq(nextauthAccounts.provider, account.provider), eq(nextauthAccounts.providerAccountId, account.providerAccountId), ), ) .then((res) => res[0]); return result?.users ? mapLobeUserToAdapterUser(result.users) : null; }, async getUserByEmail(email): Promise<AdapterUser | null> { const lobeUser = email && typeof email === 'string' && email.trim() ? await UserModel.findByEmail(serverDB, email) : undefined; return lobeUser ? mapLobeUserToAdapterUser(lobeUser) : null; }, async linkAccount(data): Promise<AdapterAccount | null | undefined> { const [account] = await serverDB .insert(nextauthAccounts) .values(data as any) .returning(); if (!account) throw new Error('NextAuthAccountModel: Failed to create account'); // TODO Update type annotation return account as any; }, async listAuthenticatorsByUserId(userId): Promise<AdapterAuthenticator[]> { const result = await serverDB .select() .from(nextauthAuthenticators) .where(eq(nextauthAuthenticators.userId, userId)) .then((res) => res); if (result.length === 0) throw new Error('LobeNextAuthDbAdapter: Failed to get authenticator list'); return result.map((r) => mapAuthenticatorQueryResutlToAdapterAuthenticator(r)); }, // @ts-ignore: The return type is {Promise<void> | Awaitable<AdapterAccount | undefined>} async unlinkAccount(account): Promise<void | AdapterAccount | undefined> { await serverDB .delete(nextauthAccounts) .where( and( eq(nextauthAccounts.provider, account.provider), eq(nextauthAccounts.providerAccountId, account.providerAccountId), ), ); }, async updateAuthenticatorCounter(credentialID, counter): Promise<AdapterAuthenticator> { const result = await serverDB .update(nextauthAuthenticators) .set({ counter }) .where(eq(nextauthAuthenticators.credentialID, credentialID)) .returning() .then((res) => res[0]); if (!result) throw new Error('LobeNextAuthDbAdapter: Failed to update authenticator counter'); return mapAuthenticatorQueryResutlToAdapterAuthenticator(result); }, async updateSession(data): Promise<AdapterSession | null | undefined> { const res = await serverDB .update(nextauthSessions) .set(data) .where(eq(nextauthSessions.sessionToken, data.sessionToken)) .returning(); return res[0]; }, async updateUser(user): Promise<AdapterUser> { const lobeUser = await UserModel.findById(serverDB, user?.id); if (!lobeUser) throw new Error('NextAuth: User not found'); const userModel = new UserModel(serverDB, user.id); const updatedUser = await userModel.updateUser({ ...partialMapAdapterUserToLobeUser(user), }); if (!updatedUser) throw new Error('NextAuth: Failed to update user'); // merge new user data with old user data const newAdapterUser = mapLobeUserToAdapterUser(lobeUser); if (!newAdapterUser) { throw new Error('NextAuth: Failed to map user data to adapter user'); } return merge(newAdapterUser, user); }, async useVerificationToken(identifier_token): Promise<VerificationToken | null> { return serverDB .delete(nextauthVerificationTokens) .where( and( eq(nextauthVerificationTokens.identifier, identifier_token.identifier), eq(nextauthVerificationTokens.token, identifier_token.token), ), ) .returning() .then((res) => (res.length > 0 ? res[0] : null)); }, }; }