@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.
- package/CHANGELOG.md +17 -0
- package/changelog/v1.json +5 -0
- package/package.json +1 -1
- package/packages/database/src/server/models/__tests__/nextauth.test.ts +556 -0
- package/packages/utils/src/server/__tests__/auth.test.ts +1 -1
- package/packages/utils/src/server/auth.ts +2 -2
- package/src/app/(backend)/api/webhooks/logto/route.ts +0 -9
- package/src/config/auth.ts +0 -4
- package/src/libs/next-auth/adapter/index.ts +201 -103
- package/src/libs/next-auth/auth.config.ts +10 -22
- package/src/libs/next-auth/edge.ts +26 -0
- package/src/libs/next-auth/index.ts +24 -11
- package/src/libs/trpc/edge/context.ts +2 -2
- package/src/libs/trpc/lambda/context.ts +2 -2
- package/src/middleware.ts +2 -2
- package/src/server/routers/lambda/user.test.ts +17 -4
- package/src/server/routers/lambda/user.ts +15 -6
- package/src/server/services/nextAuthUser/index.test.ts +108 -0
- package/src/server/services/nextAuthUser/index.ts +6 -282
- package/src/app/(backend)/api/auth/adapter/route.ts +0 -137
- /package/src/{server/services/nextAuthUser → libs/next-auth/adapter}/utils.ts +0 -0
@@ -4,175 +4,273 @@ import type {
|
|
4
4
|
AdapterUser,
|
5
5
|
VerificationToken,
|
6
6
|
} from '@auth/core/adapters';
|
7
|
-
import
|
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 {
|
12
|
-
import
|
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
|
-
|
16
|
+
import {
|
17
|
+
mapAdapterUserToLobeUser,
|
18
|
+
mapAuthenticatorQueryResutlToAdapterAuthenticator,
|
19
|
+
mapLobeUserToAdapterUser,
|
20
|
+
partialMapAdapterUserToLobeUser,
|
21
|
+
} from './utils';
|
15
22
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
76
|
-
|
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(
|
79
|
-
|
80
|
-
|
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
|
84
|
-
return
|
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
|
-
|
88
|
-
|
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
|
-
|
92
|
-
|
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
|
97
|
-
|
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
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
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
|
111
|
-
|
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
|
119
|
-
|
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
|
-
|
124
|
-
|
125
|
-
return
|
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
|
130
|
-
|
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
|
135
|
-
|
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
|
140
|
-
|
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
|
145
|
-
|
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
|
-
|
151
|
-
|
152
|
-
|
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
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
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
|
165
|
-
|
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
|
170
|
-
|
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
|
-
|
175
|
-
|
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 {
|
4
|
-
import { LobeNextAuthDbAdapter } from './adapter';
|
5
|
-
import { getAuthConfig } from '@/config/auth';
|
3
|
+
import { authEnv } from '@/config/auth';
|
6
4
|
|
7
|
-
|
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
|
-
|
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
|
-
|
12
|
+
if (validProvider) return validProvider.provider;
|
21
13
|
|
22
|
-
|
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
|
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
|
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
|
17
|
-
*
|
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(
|
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:
|
57
|
+
const { default: NextAuthEdge } = await import('@/libs/next-auth/edge');
|
58
58
|
|
59
|
-
const session = await
|
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:
|
164
|
+
const { default: NextAuthEdge } = await import('@/libs/next-auth/edge');
|
165
165
|
|
166
|
-
const session = await
|
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
|
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 =
|
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(
|
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(
|
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
|
-
|
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
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
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
|
// 服务端上传头像
|