@lobehub/chat 1.122.5 → 1.122.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.
@@ -4,175 +4,273 @@ import type {
4
4
  AdapterUser,
5
5
  VerificationToken,
6
6
  } from '@auth/core/adapters';
7
- import debug from 'debug';
7
+ import { and, eq } from 'drizzle-orm';
8
+ import type { NeonDatabase } from 'drizzle-orm/neon-serverless';
8
9
  import { Adapter, AdapterAccount } from 'next-auth/adapters';
9
- import urlJoin from 'url-join';
10
10
 
11
- import { serverDBEnv } from '@/config/db';
12
- import { appEnv } from '@/envs/app';
11
+ import { UserModel } from '@/database/models/user';
12
+ import * as schema from '@/database/schemas';
13
+ import { AgentService } from '@/server/services/agent';
14
+ import { merge } from '@/utils/merge';
13
15
 
14
- const log = debug('lobe-next-auth:adapter');
16
+ import {
17
+ mapAdapterUserToLobeUser,
18
+ mapAuthenticatorQueryResutlToAdapterAuthenticator,
19
+ mapLobeUserToAdapterUser,
20
+ partialMapAdapterUserToLobeUser,
21
+ } from './utils';
15
22
 
16
- interface BackendAdapterResponse {
17
- data?: any;
18
- error?: string;
19
- success: boolean;
20
- }
21
-
22
- // Due to use direct HTTP Post, the date string cannot parse automatically
23
- export const dateKeys = ['expires', 'emailVerified'];
23
+ const {
24
+ nextauthAccounts,
25
+ nextauthAuthenticators,
26
+ nextauthSessions,
27
+ nextauthVerificationTokens,
28
+ users,
29
+ } = schema;
24
30
 
25
31
  /**
26
32
  * @description LobeNextAuthDbAdapter is implemented to handle the database operations
27
33
  * for NextAuth, this function do the same things as `src/app/api/webhooks/clerk/route.ts`
28
34
  * @returns {Adapter}
29
35
  */
30
- export function LobeNextAuthDbAdapter(): Adapter {
31
- const baseUrl = appEnv.APP_URL;
32
-
33
- // Ensure the baseUrl is set, otherwise throw an error
34
- if (!baseUrl) {
35
- throw new Error('LobeNextAuthDbAdapter: APP_URL is not set in environment variables');
36
- }
37
- const interactionUrl = urlJoin(baseUrl, '/api/auth/adapter');
38
- log(`LobeNextAuthDbAdapter initialized with url: ${interactionUrl}`);
39
-
40
- // Ensure serverDBEnv.KEY_VAULTS_SECRET is set, otherwise throw an error
41
- if (!serverDBEnv.KEY_VAULTS_SECRET) {
42
- throw new Error('LobeNextAuthDbAdapter: KEY_VAULTS_SECRET is not set in environment variables');
43
- }
44
-
45
- const fetcher = (action: string, data: any) =>
46
- fetch(interactionUrl, {
47
- body: JSON.stringify({ action, data }),
48
- headers: {
49
- 'Authorization': `Bearer ${serverDBEnv.KEY_VAULTS_SECRET}`,
50
- 'Content-Type': 'application/json',
51
- },
52
- method: 'POST',
53
- });
54
- const postProcessor = async (res: Response) => {
55
- const data = (await res.json()) as BackendAdapterResponse;
56
- log('LobeNextAuthDbAdapter: postProcessor called with data:', data);
57
- if (!data.success) {
58
- log('LobeNextAuthDbAdapter: Error in postProcessor:');
59
- log(data);
60
- throw new Error(`LobeNextAuthDbAdapter: ${data.error}`);
61
- }
62
- if (data?.data) {
63
- for (const key of dateKeys) {
64
- if (data.data[key]) {
65
- data.data[key] = new Date(data.data[key]);
66
- continue;
67
- }
68
- }
69
- }
70
- return data.data;
71
- };
72
-
36
+ export function LobeNextAuthDbAdapter(serverDB: NeonDatabase<typeof schema>): Adapter {
73
37
  return {
74
38
  async createAuthenticator(authenticator): Promise<AdapterAuthenticator> {
75
- const data = await fetcher('createAuthenticator', authenticator);
76
- return await postProcessor(data);
39
+ const result = await serverDB
40
+ .insert(nextauthAuthenticators)
41
+ .values(authenticator)
42
+ .returning()
43
+ .then((res) => res[0] ?? undefined);
44
+ if (!result) throw new Error('LobeNextAuthDbAdapter: Failed to create authenticator');
45
+ return mapAuthenticatorQueryResutlToAdapterAuthenticator(result);
77
46
  },
78
- async createSession(session): Promise<AdapterSession> {
79
- const data = await fetcher('createSession', session);
80
- return await postProcessor(data);
47
+ async createSession(data): Promise<AdapterSession> {
48
+ return serverDB
49
+ .insert(nextauthSessions)
50
+ .values(data)
51
+ .returning()
52
+ .then((res) => res[0]);
81
53
  },
82
54
  async createUser(user): Promise<AdapterUser> {
83
- const data = await fetcher('createUser', user);
84
- return await postProcessor(data);
55
+ const { id, name, email, emailVerified, image, providerAccountId } = user;
56
+ // return the user if it already exists
57
+ let existingUser =
58
+ email && typeof email === 'string' && email.trim()
59
+ ? await UserModel.findByEmail(serverDB, email)
60
+ : undefined;
61
+ // If the user is not found by email, try to find by providerAccountId
62
+ if (!existingUser && providerAccountId) {
63
+ existingUser = await UserModel.findById(serverDB, providerAccountId);
64
+ }
65
+ if (existingUser) {
66
+ const adapterUser = mapLobeUserToAdapterUser(existingUser);
67
+ return adapterUser;
68
+ }
69
+
70
+ // create a new user if it does not exist
71
+ // Use id from provider if it exists, otherwise use id assigned by next-auth
72
+ // ref: https://github.com/lobehub/lobe-chat/pull/2935
73
+ const uid = providerAccountId ?? id;
74
+ await UserModel.createUser(
75
+ serverDB,
76
+ mapAdapterUserToLobeUser({
77
+ email,
78
+ emailVerified,
79
+ // Use providerAccountId as userid to identify if the user exists in a SSO provider
80
+ id: uid,
81
+ image,
82
+ name,
83
+ }),
84
+ );
85
+
86
+ // 3. Create an inbox session for the user
87
+ const agentService = new AgentService(serverDB, uid);
88
+ await agentService.createInbox();
89
+
90
+ return { ...user, id: uid };
85
91
  },
86
92
  async createVerificationToken(data): Promise<VerificationToken | null | undefined> {
87
- const result = await fetcher('createVerificationToken', data);
88
- return await postProcessor(result);
93
+ return serverDB
94
+ .insert(nextauthVerificationTokens)
95
+ .values(data)
96
+ .returning()
97
+ .then((res) => res[0]);
89
98
  },
90
99
  async deleteSession(sessionToken): Promise<AdapterSession | null | undefined> {
91
- const result = await fetcher('deleteSession', sessionToken);
92
- await postProcessor(result);
100
+ await serverDB
101
+ .delete(nextauthSessions)
102
+ .where(eq(nextauthSessions.sessionToken, sessionToken));
93
103
  return;
94
104
  },
95
105
  async deleteUser(id): Promise<AdapterUser | null | undefined> {
96
- const result = await fetcher('deleteUser', id);
97
- await postProcessor(result);
106
+ const user = await UserModel.findById(serverDB, id);
107
+ if (!user) throw new Error('NextAuth: Delete User not found');
108
+
109
+ await UserModel.deleteUser(serverDB, id);
98
110
  return;
99
111
  },
100
112
 
101
113
  async getAccount(providerAccountId, provider): Promise<AdapterAccount | null> {
102
- const data = await fetcher('getAccount', {
103
- provider,
104
- providerAccountId,
105
- });
106
- return await postProcessor(data);
114
+ return serverDB
115
+ .select()
116
+ .from(nextauthAccounts)
117
+ .where(
118
+ and(
119
+ eq(nextauthAccounts.provider, provider),
120
+ eq(nextauthAccounts.providerAccountId, providerAccountId),
121
+ ),
122
+ )
123
+ .then((res) => res[0] ?? null) as Promise<AdapterAccount | null>;
107
124
  },
108
125
 
109
126
  async getAuthenticator(credentialID): Promise<AdapterAuthenticator | null> {
110
- const result = await fetcher('getAuthenticator', credentialID);
111
- return await postProcessor(result);
127
+ const result = await serverDB
128
+ .select()
129
+ .from(nextauthAuthenticators)
130
+ .where(eq(nextauthAuthenticators.credentialID, credentialID))
131
+ .then((res) => res[0] ?? null);
132
+ if (!result) throw new Error('LobeNextAuthDbAdapter: Failed to get authenticator');
133
+ return mapAuthenticatorQueryResutlToAdapterAuthenticator(result);
112
134
  },
113
135
 
114
136
  async getSessionAndUser(sessionToken): Promise<{
115
137
  session: AdapterSession;
116
138
  user: AdapterUser;
117
139
  } | null> {
118
- const result = await fetcher('getSessionAndUser', sessionToken);
119
- return await postProcessor(result);
140
+ const result = await serverDB
141
+ .select({
142
+ session: nextauthSessions,
143
+ user: users,
144
+ })
145
+ .from(nextauthSessions)
146
+ .where(eq(nextauthSessions.sessionToken, sessionToken))
147
+ .innerJoin(users, eq(users.id, nextauthSessions.userId))
148
+ .then((res) => (res.length > 0 ? res[0] : null));
149
+
150
+ if (!result) return null;
151
+ const adapterUser = mapLobeUserToAdapterUser(result.user);
152
+ if (!adapterUser) return null;
153
+ return {
154
+ session: result.session,
155
+ user: adapterUser,
156
+ };
120
157
  },
121
158
 
122
159
  async getUser(id): Promise<AdapterUser | null> {
123
- log('getUser called with id:', id);
124
- const result = await fetcher('getUser', id);
125
- return await postProcessor(result);
160
+ const lobeUser = await UserModel.findById(serverDB, id);
161
+ if (!lobeUser) return null;
162
+ return mapLobeUserToAdapterUser(lobeUser);
126
163
  },
127
164
 
128
165
  async getUserByAccount(account): Promise<AdapterUser | null> {
129
- const data = await fetcher('getUserByAccount', account);
130
- return await postProcessor(data);
166
+ const result = await serverDB
167
+ .select({
168
+ account: nextauthAccounts,
169
+ users,
170
+ })
171
+ .from(nextauthAccounts)
172
+ .innerJoin(users, eq(nextauthAccounts.userId, users.id))
173
+ .where(
174
+ and(
175
+ eq(nextauthAccounts.provider, account.provider),
176
+ eq(nextauthAccounts.providerAccountId, account.providerAccountId),
177
+ ),
178
+ )
179
+ .then((res) => res[0]);
180
+
181
+ return result?.users ? mapLobeUserToAdapterUser(result.users) : null;
131
182
  },
132
183
 
133
184
  async getUserByEmail(email): Promise<AdapterUser | null> {
134
- const data = await fetcher('getUserByEmail', email);
135
- return await postProcessor(data);
185
+ const lobeUser =
186
+ email && typeof email === 'string' && email.trim()
187
+ ? await UserModel.findByEmail(serverDB, email)
188
+ : undefined;
189
+ return lobeUser ? mapLobeUserToAdapterUser(lobeUser) : null;
136
190
  },
137
191
 
138
192
  async linkAccount(data): Promise<AdapterAccount | null | undefined> {
139
- const result = await fetcher('linkAccount', data);
140
- return await postProcessor(result);
193
+ const [account] = await serverDB
194
+ .insert(nextauthAccounts)
195
+ .values(data as any)
196
+ .returning();
197
+ if (!account) throw new Error('NextAuthAccountModel: Failed to create account');
198
+ // TODO Update type annotation
199
+ return account as any;
141
200
  },
142
201
 
143
202
  async listAuthenticatorsByUserId(userId): Promise<AdapterAuthenticator[]> {
144
- const result = await fetcher('listAuthenticatorsByUserId', userId);
145
- return await postProcessor(result);
203
+ const result = await serverDB
204
+ .select()
205
+ .from(nextauthAuthenticators)
206
+ .where(eq(nextauthAuthenticators.userId, userId))
207
+ .then((res) => res);
208
+ if (result.length === 0)
209
+ throw new Error('LobeNextAuthDbAdapter: Failed to get authenticator list');
210
+ return result.map((r) => mapAuthenticatorQueryResutlToAdapterAuthenticator(r));
146
211
  },
147
212
 
148
213
  // @ts-ignore: The return type is {Promise<void> | Awaitable<AdapterAccount | undefined>}
149
214
  async unlinkAccount(account): Promise<void | AdapterAccount | undefined> {
150
- const result = await fetcher('unlinkAccount', account);
151
- await postProcessor(result);
152
- return;
215
+ await serverDB
216
+ .delete(nextauthAccounts)
217
+ .where(
218
+ and(
219
+ eq(nextauthAccounts.provider, account.provider),
220
+ eq(nextauthAccounts.providerAccountId, account.providerAccountId),
221
+ ),
222
+ );
153
223
  },
154
224
 
155
225
  async updateAuthenticatorCounter(credentialID, counter): Promise<AdapterAuthenticator> {
156
- const result = await fetcher('updateAuthenticatorCounter', {
157
- counter,
158
- credentialID,
159
- });
160
- return await postProcessor(result);
226
+ const result = await serverDB
227
+ .update(nextauthAuthenticators)
228
+ .set({ counter })
229
+ .where(eq(nextauthAuthenticators.credentialID, credentialID))
230
+ .returning()
231
+ .then((res) => res[0]);
232
+ if (!result) throw new Error('LobeNextAuthDbAdapter: Failed to update authenticator counter');
233
+ return mapAuthenticatorQueryResutlToAdapterAuthenticator(result);
161
234
  },
162
235
 
163
236
  async updateSession(data): Promise<AdapterSession | null | undefined> {
164
- const result = await fetcher('updateSession', data);
165
- return await postProcessor(result);
237
+ const res = await serverDB
238
+ .update(nextauthSessions)
239
+ .set(data)
240
+ .where(eq(nextauthSessions.sessionToken, data.sessionToken))
241
+ .returning();
242
+ return res[0];
166
243
  },
167
244
 
168
245
  async updateUser(user): Promise<AdapterUser> {
169
- const result = await fetcher('updateUser', user);
170
- return await postProcessor(result);
246
+ const lobeUser = await UserModel.findById(serverDB, user?.id);
247
+ if (!lobeUser) throw new Error('NextAuth: User not found');
248
+ const userModel = new UserModel(serverDB, user.id);
249
+
250
+ const updatedUser = await userModel.updateUser({
251
+ ...partialMapAdapterUserToLobeUser(user),
252
+ });
253
+ if (!updatedUser) throw new Error('NextAuth: Failed to update user');
254
+
255
+ // merge new user data with old user data
256
+ const newAdapterUser = mapLobeUserToAdapterUser(lobeUser);
257
+ if (!newAdapterUser) {
258
+ throw new Error('NextAuth: Failed to map user data to adapter user');
259
+ }
260
+ return merge(newAdapterUser, user);
171
261
  },
172
262
 
173
263
  async useVerificationToken(identifier_token): Promise<VerificationToken | null> {
174
- const result = await fetcher('useVerificationToken', identifier_token);
175
- return await postProcessor(result);
264
+ return serverDB
265
+ .delete(nextauthVerificationTokens)
266
+ .where(
267
+ and(
268
+ eq(nextauthVerificationTokens.identifier, identifier_token.identifier),
269
+ eq(nextauthVerificationTokens.token, identifier_token.token),
270
+ ),
271
+ )
272
+ .returning()
273
+ .then((res) => (res.length > 0 ? res[0] : null));
176
274
  },
177
275
  };
178
276
  }
@@ -1,32 +1,23 @@
1
1
  import type { NextAuthConfig } from 'next-auth';
2
2
 
3
- import { ssoProviders } from './sso-providers';
4
- import { LobeNextAuthDbAdapter } from './adapter';
5
- import { getAuthConfig } from '@/config/auth';
3
+ import { authEnv } from '@/config/auth';
6
4
 
7
- const {
8
- NEXT_AUTH_DEBUG,
9
- NEXT_AUTH_SECRET,
10
- NEXT_AUTH_SSO_SESSION_STRATEGY,
11
- NEXT_AUTH_SSO_PROVIDERS,
12
- NEXT_PUBLIC_ENABLE_NEXT_AUTH
13
- } = getAuthConfig();
5
+ import { ssoProviders } from './sso-providers';
14
6
 
15
7
  export const initSSOProviders = () => {
16
- return NEXT_PUBLIC_ENABLE_NEXT_AUTH
17
- ? NEXT_AUTH_SSO_PROVIDERS.split(/[,,]/).map((provider) => {
18
- const validProvider = ssoProviders.find((item) => item.id === provider.trim());
8
+ return authEnv.NEXT_PUBLIC_ENABLE_NEXT_AUTH
9
+ ? authEnv.NEXT_AUTH_SSO_PROVIDERS.split(/[,,]/).map((provider) => {
10
+ const validProvider = ssoProviders.find((item) => item.id === provider.trim());
19
11
 
20
- if (validProvider) return validProvider.provider;
12
+ if (validProvider) return validProvider.provider;
21
13
 
22
- throw new Error(`[NextAuth] provider ${provider} is not supported`);
23
- })
14
+ throw new Error(`[NextAuth] provider ${provider} is not supported`);
15
+ })
24
16
  : [];
25
17
  };
26
18
 
27
19
  // Notice this is only an object, not a full Auth.js instance
28
20
  export default {
29
- adapter: LobeNextAuthDbAdapter(),
30
21
  callbacks: {
31
22
  // Note: Data processing order of callback: authorize --> jwt --> session
32
23
  async jwt({ token, user }) {
@@ -48,15 +39,12 @@ export default {
48
39
  return session;
49
40
  },
50
41
  },
51
- debug: NEXT_AUTH_DEBUG,
42
+ debug: authEnv.NEXT_AUTH_DEBUG,
52
43
  pages: {
53
44
  error: '/next-auth/error',
54
45
  signIn: '/next-auth/signin',
55
46
  },
56
47
  providers: initSSOProviders(),
57
- secret: NEXT_AUTH_SECRET,
58
- session: {
59
- strategy: NEXT_AUTH_SSO_SESSION_STRATEGY,
60
- },
48
+ secret: authEnv.NEXT_AUTH_SECRET,
61
49
  trustHost: process.env?.AUTH_TRUST_HOST ? process.env.AUTH_TRUST_HOST === 'true' : true,
62
50
  } satisfies NextAuthConfig;
@@ -0,0 +1,26 @@
1
+ import NextAuth from 'next-auth';
2
+
3
+ import authConfig from './auth.config';
4
+
5
+ /**
6
+ * NextAuth initialization without Database adapter
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import NextAuthEdge from '@/libs/next-auth/edge';
11
+ * const { auth } = NextAuthEdge;
12
+ * ```
13
+ *
14
+ * @note
15
+ * We currently use `jwt` strategy for session management.
16
+ * So you don't need to import `signIn` or `signOut` from
17
+ * this module, just import from `next-auth` directly.
18
+ *
19
+ * Inside react component
20
+ * @example
21
+ * ```ts
22
+ * import { signOut } from 'next-auth/react';
23
+ * signOut();
24
+ * ```
25
+ */
26
+ export default NextAuth(authConfig);
@@ -1,20 +1,33 @@
1
1
  import NextAuth from 'next-auth';
2
2
 
3
- import authConfig from './auth.config';
3
+ import { getServerDBConfig } from '@/config/db';
4
+ import { serverDB } from '@/database/server';
5
+
6
+ import { LobeNextAuthDbAdapter } from './adapter';
7
+ import config from './auth.config';
8
+
9
+ const { NEXT_PUBLIC_ENABLED_SERVER_SERVICE } = getServerDBConfig();
4
10
 
5
11
  /**
6
- * NextAuth initialization without Database adapter
7
- *
8
- * @note
9
- * We currently use `jwt` strategy for session management.
10
- * So you don't need to import `signIn` or `signOut` from
11
- * this module, just import from `next-auth` directly.
12
+ * NextAuth initialization with Database adapter
12
13
  *
13
- * Inside react component
14
14
  * @example
15
15
  * ```ts
16
- * import { signOut } from 'next-auth/react';
17
- * signOut();
16
+ * import NextAuthNode from '@/libs/next-auth';
17
+ * const { handlers } = NextAuthNode;
18
18
  * ```
19
+ *
20
+ * @note
21
+ * If you meet the edge runtime compatible problem,
22
+ * you can import from `@/libs/next-auth/edge` which is not initial with the database adapter.
23
+ *
24
+ * The difference and usage of the two different NextAuth modules is can be
25
+ * ref to: https://github.com/lobehub/lobe-chat/pull/2935
19
26
  */
20
- export default NextAuth(authConfig);
27
+ export default NextAuth({
28
+ ...config,
29
+ adapter: NEXT_PUBLIC_ENABLED_SERVER_SERVICE ? LobeNextAuthDbAdapter(serverDB) : undefined,
30
+ session: {
31
+ strategy: 'jwt',
32
+ },
33
+ });
@@ -54,9 +54,9 @@ export const createEdgeContext = async (request: NextRequest): Promise<EdgeConte
54
54
 
55
55
  if (enableNextAuth) {
56
56
  try {
57
- const { default: NextAuth } = await import('@/libs/next-auth');
57
+ const { default: NextAuthEdge } = await import('@/libs/next-auth/edge');
58
58
 
59
- const session = await NextAuth.auth();
59
+ const session = await NextAuthEdge.auth();
60
60
  if (session && session?.user?.id) {
61
61
  auth = session.user;
62
62
  userId = session.user.id;
@@ -161,9 +161,9 @@ export const createLambdaContext = async (request: NextRequest): Promise<LambdaC
161
161
  if (enableNextAuth) {
162
162
  log('Attempting NextAuth authentication');
163
163
  try {
164
- const { default: NextAuth } = await import('@/libs/next-auth');
164
+ const { default: NextAuthEdge } = await import('@/libs/next-auth/edge');
165
165
 
166
- const session = await NextAuth.auth();
166
+ const session = await NextAuthEdge.auth();
167
167
  if (session && session?.user?.id) {
168
168
  auth = session.user;
169
169
  userId = session.user.id;
package/src/middleware.ts CHANGED
@@ -10,7 +10,7 @@ import { OAUTH_AUTHORIZED } from '@/const/auth';
10
10
  import { LOBE_LOCALE_COOKIE } from '@/const/locale';
11
11
  import { LOBE_THEME_APPEARANCE } from '@/const/theme';
12
12
  import { appEnv } from '@/envs/app';
13
- import NextAuth from '@/libs/next-auth';
13
+ import NextAuthEdge from '@/libs/next-auth/edge';
14
14
  import { Locales } from '@/locales/resources';
15
15
 
16
16
  import { oidcEnv } from './envs/oidc';
@@ -170,7 +170,7 @@ const isProtectedRoute = createRouteMatcher([
170
170
  ]);
171
171
 
172
172
  // Initialize an Edge compatible NextAuth middleware
173
- const nextAuthMiddleware = NextAuth.auth((req) => {
173
+ const nextAuthMiddleware = NextAuthEdge.auth(async (req) => {
174
174
  logNextAuth('NextAuth middleware processing request: %s %s', req.method, req.url);
175
175
 
176
176
  const response = defaultMiddleware(req);
@@ -6,8 +6,8 @@ import { MessageModel } from '@/database/models/message';
6
6
  import { SessionModel } from '@/database/models/session';
7
7
  import { UserModel, UserNotFoundError } from '@/database/models/user';
8
8
  import { serverDB } from '@/database/server';
9
+ import { LobeNextAuthDbAdapter } from '@/libs/next-auth/adapter';
9
10
  import { KeyVaultsGateKeeper } from '@/server/modules/KeyVaultsEncrypt';
10
- import { NextAuthUserService } from '@/server/services/nextAuthUser';
11
11
  import { UserService } from '@/server/services/user';
12
12
 
13
13
  import { userRouter } from './user';
@@ -24,10 +24,10 @@ vi.mock('@/database/server', () => ({
24
24
  vi.mock('@/database/models/message');
25
25
  vi.mock('@/database/models/session');
26
26
  vi.mock('@/database/models/user');
27
+ vi.mock('@/libs/next-auth/adapter');
27
28
  vi.mock('@/server/modules/KeyVaultsEncrypt');
28
29
  vi.mock('@/server/modules/S3');
29
30
  vi.mock('@/server/services/user');
30
- vi.mock('@/server/services/nextAuthUser');
31
31
  vi.mock('@/const/auth', () => ({
32
32
  enableClerk: true,
33
33
  }));
@@ -221,7 +221,7 @@ describe('userRouter', () => {
221
221
  type: 'oauth',
222
222
  };
223
223
 
224
- vi.mocked(NextAuthUserService).mockReturnValue({
224
+ vi.mocked(LobeNextAuthDbAdapter).mockReturnValue({
225
225
  getAccount: vi.fn().mockResolvedValue(mockAccount),
226
226
  unlinkAccount: vi.fn().mockResolvedValue(undefined),
227
227
  } as any);
@@ -237,7 +237,7 @@ describe('userRouter', () => {
237
237
  providerAccountId: '123',
238
238
  };
239
239
 
240
- vi.mocked(NextAuthUserService).mockReturnValue({
240
+ vi.mocked(LobeNextAuthDbAdapter).mockReturnValue({
241
241
  getAccount: vi.fn().mockResolvedValue(null),
242
242
  unlinkAccount: vi.fn(),
243
243
  } as any);
@@ -246,6 +246,19 @@ describe('userRouter', () => {
246
246
  userRouter.createCaller({ ...mockCtx }).unlinkSSOProvider(mockInput),
247
247
  ).rejects.toThrow('The account does not exist');
248
248
  });
249
+
250
+ it('should throw error if adapter methods are not implemented', async () => {
251
+ const mockInput = {
252
+ provider: 'google',
253
+ providerAccountId: '123',
254
+ };
255
+
256
+ vi.mocked(LobeNextAuthDbAdapter).mockReturnValue({} as any);
257
+
258
+ await expect(
259
+ userRouter.createCaller({ ...mockCtx }).unlinkSSOProvider(mockInput),
260
+ ).rejects.toThrow('The method in LobeNextAuthDbAdapter `unlinkAccount` is not implemented');
261
+ });
249
262
  });
250
263
 
251
264
  describe('updateSettings', () => {
@@ -9,12 +9,12 @@ import { SessionModel } from '@/database/models/session';
9
9
  import { UserModel, UserNotFoundError } from '@/database/models/user';
10
10
  import { ClerkAuth } from '@/libs/clerk-auth';
11
11
  import { pino } from '@/libs/logger';
12
+ import { LobeNextAuthDbAdapter } from '@/libs/next-auth/adapter';
12
13
  import { authedProcedure, router } from '@/libs/trpc/lambda';
13
14
  import { serverDatabase } from '@/libs/trpc/lambda/middleware';
14
15
  import { KeyVaultsGateKeeper } from '@/server/modules/KeyVaultsEncrypt';
15
16
  import { S3 } from '@/server/modules/S3';
16
17
  import { FileService } from '@/server/services/file';
17
- import { NextAuthUserService } from '@/server/services/nextAuthUser';
18
18
  import { UserService } from '@/server/services/user';
19
19
  import {
20
20
  NextAuthAccountSchame,
@@ -29,7 +29,7 @@ const userProcedure = authedProcedure.use(serverDatabase).use(async ({ ctx, next
29
29
  ctx: {
30
30
  clerkAuth: new ClerkAuth(),
31
31
  fileService: new FileService(ctx.serverDB, ctx.userId),
32
- nextAuthUserService: new NextAuthUserService(ctx.serverDB),
32
+ nextAuthDbAdapter: LobeNextAuthDbAdapter(ctx.serverDB),
33
33
  userModel: new UserModel(ctx.serverDB, ctx.userId),
34
34
  },
35
35
  });
@@ -134,10 +134,19 @@ export const userRouter = router({
134
134
 
135
135
  unlinkSSOProvider: userProcedure.input(NextAuthAccountSchame).mutation(async ({ ctx, input }) => {
136
136
  const { provider, providerAccountId } = input;
137
- const account = await ctx.nextAuthUserService.getAccount(providerAccountId, provider);
138
- // The userId can either get from ctx.nextAuth?.id or ctx.userId
139
- if (!account || account.userId !== ctx.userId) throw new Error('The account does not exist');
140
- await ctx.nextAuthUserService.unlinkAccount({ provider, providerAccountId });
137
+ if (
138
+ ctx.nextAuthDbAdapter?.unlinkAccount &&
139
+ typeof ctx.nextAuthDbAdapter.unlinkAccount === 'function' &&
140
+ ctx.nextAuthDbAdapter?.getAccount &&
141
+ typeof ctx.nextAuthDbAdapter.getAccount === 'function'
142
+ ) {
143
+ const account = await ctx.nextAuthDbAdapter.getAccount(providerAccountId, provider);
144
+ // The userId can either get from ctx.nextAuth?.id or ctx.userId
145
+ if (!account || account.userId !== ctx.userId) throw new Error('The account does not exist');
146
+ await ctx.nextAuthDbAdapter.unlinkAccount({ provider, providerAccountId });
147
+ } else {
148
+ throw new Error('The method in LobeNextAuthDbAdapter `unlinkAccount` is not implemented');
149
+ }
141
150
  }),
142
151
 
143
152
  // 服务端上传头像