@lobehub/chat 1.122.6 → 1.123.0
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 +50 -0
- package/changelog/v1.json +18 -0
- package/package.json +2 -2
- package/packages/model-bank/package.json +1 -0
- package/packages/model-bank/src/aiModels/index.ts +3 -1
- package/packages/model-bank/src/aiModels/newapi.ts +11 -0
- package/packages/model-runtime/src/RouterRuntime/createRuntime.test.ts +60 -0
- package/packages/model-runtime/src/RouterRuntime/createRuntime.ts +6 -3
- package/packages/model-runtime/src/index.ts +1 -0
- package/packages/model-runtime/src/newapi/index.test.ts +618 -0
- package/packages/model-runtime/src/newapi/index.ts +245 -0
- package/packages/model-runtime/src/runtimeMap.ts +2 -0
- package/packages/model-runtime/src/types/type.ts +1 -0
- package/packages/types/src/user/settings/keyVaults.ts +1 -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/auth/adapter/route.ts +137 -0
- package/src/app/(backend)/api/webhooks/logto/route.ts +9 -0
- package/src/app/[variants]/(main)/settings/provider/(detail)/newapi/page.tsx +27 -0
- package/src/config/auth.ts +4 -0
- package/src/config/modelProviders/index.ts +3 -0
- package/src/config/modelProviders/newapi.ts +17 -0
- package/src/libs/next-auth/adapter/index.ts +103 -201
- package/src/libs/next-auth/auth.config.ts +22 -5
- package/src/libs/next-auth/index.ts +11 -24
- package/src/libs/trpc/edge/context.ts +2 -2
- package/src/libs/trpc/lambda/context.ts +2 -2
- package/src/locales/default/modelProvider.ts +26 -0
- package/src/middleware.ts +2 -2
- package/src/server/routers/lambda/user.test.ts +4 -17
- package/src/server/routers/lambda/user.ts +6 -15
- package/src/server/services/nextAuthUser/index.ts +282 -6
- package/packages/database/src/server/models/__tests__/nextauth.test.ts +0 -556
- package/src/libs/next-auth/edge.ts +0 -26
- package/src/server/services/nextAuthUser/index.test.ts +0 -108
- /package/{.env.development → .env.example.development} +0 -0
- /package/src/{libs/next-auth/adapter → server/services/nextAuthUser}/utils.ts +0 -0
@@ -4,273 +4,175 @@ import type {
|
|
4
4
|
AdapterUser,
|
5
5
|
VerificationToken,
|
6
6
|
} from '@auth/core/adapters';
|
7
|
-
import
|
8
|
-
import type { NeonDatabase } from 'drizzle-orm/neon-serverless';
|
7
|
+
import debug from 'debug';
|
9
8
|
import { Adapter, AdapterAccount } from 'next-auth/adapters';
|
9
|
+
import urlJoin from 'url-join';
|
10
10
|
|
11
|
-
import {
|
12
|
-
import
|
13
|
-
import { AgentService } from '@/server/services/agent';
|
14
|
-
import { merge } from '@/utils/merge';
|
11
|
+
import { serverDBEnv } from '@/config/db';
|
12
|
+
import { appEnv } from '@/envs/app';
|
15
13
|
|
16
|
-
|
17
|
-
mapAdapterUserToLobeUser,
|
18
|
-
mapAuthenticatorQueryResutlToAdapterAuthenticator,
|
19
|
-
mapLobeUserToAdapterUser,
|
20
|
-
partialMapAdapterUserToLobeUser,
|
21
|
-
} from './utils';
|
14
|
+
const log = debug('lobe-next-auth:adapter');
|
22
15
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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'];
|
30
24
|
|
31
25
|
/**
|
32
26
|
* @description LobeNextAuthDbAdapter is implemented to handle the database operations
|
33
27
|
* for NextAuth, this function do the same things as `src/app/api/webhooks/clerk/route.ts`
|
34
28
|
* @returns {Adapter}
|
35
29
|
*/
|
36
|
-
export function LobeNextAuthDbAdapter(
|
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
|
+
|
37
73
|
return {
|
38
74
|
async createAuthenticator(authenticator): Promise<AdapterAuthenticator> {
|
39
|
-
const
|
40
|
-
|
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);
|
75
|
+
const data = await fetcher('createAuthenticator', authenticator);
|
76
|
+
return await postProcessor(data);
|
46
77
|
},
|
47
|
-
async createSession(
|
48
|
-
|
49
|
-
|
50
|
-
.values(data)
|
51
|
-
.returning()
|
52
|
-
.then((res) => res[0]);
|
78
|
+
async createSession(session): Promise<AdapterSession> {
|
79
|
+
const data = await fetcher('createSession', session);
|
80
|
+
return await postProcessor(data);
|
53
81
|
},
|
54
82
|
async createUser(user): Promise<AdapterUser> {
|
55
|
-
const
|
56
|
-
|
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 };
|
83
|
+
const data = await fetcher('createUser', user);
|
84
|
+
return await postProcessor(data);
|
91
85
|
},
|
92
86
|
async createVerificationToken(data): Promise<VerificationToken | null | undefined> {
|
93
|
-
|
94
|
-
|
95
|
-
.values(data)
|
96
|
-
.returning()
|
97
|
-
.then((res) => res[0]);
|
87
|
+
const result = await fetcher('createVerificationToken', data);
|
88
|
+
return await postProcessor(result);
|
98
89
|
},
|
99
90
|
async deleteSession(sessionToken): Promise<AdapterSession | null | undefined> {
|
100
|
-
await
|
101
|
-
|
102
|
-
.where(eq(nextauthSessions.sessionToken, sessionToken));
|
91
|
+
const result = await fetcher('deleteSession', sessionToken);
|
92
|
+
await postProcessor(result);
|
103
93
|
return;
|
104
94
|
},
|
105
95
|
async deleteUser(id): Promise<AdapterUser | null | undefined> {
|
106
|
-
const
|
107
|
-
|
108
|
-
|
109
|
-
await UserModel.deleteUser(serverDB, id);
|
96
|
+
const result = await fetcher('deleteUser', id);
|
97
|
+
await postProcessor(result);
|
110
98
|
return;
|
111
99
|
},
|
112
100
|
|
113
101
|
async getAccount(providerAccountId, provider): Promise<AdapterAccount | null> {
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
eq(nextauthAccounts.provider, provider),
|
120
|
-
eq(nextauthAccounts.providerAccountId, providerAccountId),
|
121
|
-
),
|
122
|
-
)
|
123
|
-
.then((res) => res[0] ?? null) as Promise<AdapterAccount | null>;
|
102
|
+
const data = await fetcher('getAccount', {
|
103
|
+
provider,
|
104
|
+
providerAccountId,
|
105
|
+
});
|
106
|
+
return await postProcessor(data);
|
124
107
|
},
|
125
108
|
|
126
109
|
async getAuthenticator(credentialID): Promise<AdapterAuthenticator | null> {
|
127
|
-
const result = await
|
128
|
-
|
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);
|
110
|
+
const result = await fetcher('getAuthenticator', credentialID);
|
111
|
+
return await postProcessor(result);
|
134
112
|
},
|
135
113
|
|
136
114
|
async getSessionAndUser(sessionToken): Promise<{
|
137
115
|
session: AdapterSession;
|
138
116
|
user: AdapterUser;
|
139
117
|
} | null> {
|
140
|
-
const result = await
|
141
|
-
|
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
|
-
};
|
118
|
+
const result = await fetcher('getSessionAndUser', sessionToken);
|
119
|
+
return await postProcessor(result);
|
157
120
|
},
|
158
121
|
|
159
122
|
async getUser(id): Promise<AdapterUser | null> {
|
160
|
-
|
161
|
-
|
162
|
-
return
|
123
|
+
log('getUser called with id:', id);
|
124
|
+
const result = await fetcher('getUser', id);
|
125
|
+
return await postProcessor(result);
|
163
126
|
},
|
164
127
|
|
165
128
|
async getUserByAccount(account): Promise<AdapterUser | null> {
|
166
|
-
const
|
167
|
-
|
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;
|
129
|
+
const data = await fetcher('getUserByAccount', account);
|
130
|
+
return await postProcessor(data);
|
182
131
|
},
|
183
132
|
|
184
133
|
async getUserByEmail(email): Promise<AdapterUser | null> {
|
185
|
-
const
|
186
|
-
|
187
|
-
? await UserModel.findByEmail(serverDB, email)
|
188
|
-
: undefined;
|
189
|
-
return lobeUser ? mapLobeUserToAdapterUser(lobeUser) : null;
|
134
|
+
const data = await fetcher('getUserByEmail', email);
|
135
|
+
return await postProcessor(data);
|
190
136
|
},
|
191
137
|
|
192
138
|
async linkAccount(data): Promise<AdapterAccount | null | undefined> {
|
193
|
-
const
|
194
|
-
|
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;
|
139
|
+
const result = await fetcher('linkAccount', data);
|
140
|
+
return await postProcessor(result);
|
200
141
|
},
|
201
142
|
|
202
143
|
async listAuthenticatorsByUserId(userId): Promise<AdapterAuthenticator[]> {
|
203
|
-
const result = await
|
204
|
-
|
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));
|
144
|
+
const result = await fetcher('listAuthenticatorsByUserId', userId);
|
145
|
+
return await postProcessor(result);
|
211
146
|
},
|
212
147
|
|
213
148
|
// @ts-ignore: The return type is {Promise<void> | Awaitable<AdapterAccount | undefined>}
|
214
149
|
async unlinkAccount(account): Promise<void | AdapterAccount | undefined> {
|
215
|
-
await
|
216
|
-
|
217
|
-
|
218
|
-
and(
|
219
|
-
eq(nextauthAccounts.provider, account.provider),
|
220
|
-
eq(nextauthAccounts.providerAccountId, account.providerAccountId),
|
221
|
-
),
|
222
|
-
);
|
150
|
+
const result = await fetcher('unlinkAccount', account);
|
151
|
+
await postProcessor(result);
|
152
|
+
return;
|
223
153
|
},
|
224
154
|
|
225
155
|
async updateAuthenticatorCounter(credentialID, counter): Promise<AdapterAuthenticator> {
|
226
|
-
const result = await
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
.then((res) => res[0]);
|
232
|
-
if (!result) throw new Error('LobeNextAuthDbAdapter: Failed to update authenticator counter');
|
233
|
-
return mapAuthenticatorQueryResutlToAdapterAuthenticator(result);
|
156
|
+
const result = await fetcher('updateAuthenticatorCounter', {
|
157
|
+
counter,
|
158
|
+
credentialID,
|
159
|
+
});
|
160
|
+
return await postProcessor(result);
|
234
161
|
},
|
235
162
|
|
236
163
|
async updateSession(data): Promise<AdapterSession | null | undefined> {
|
237
|
-
const
|
238
|
-
|
239
|
-
.set(data)
|
240
|
-
.where(eq(nextauthSessions.sessionToken, data.sessionToken))
|
241
|
-
.returning();
|
242
|
-
return res[0];
|
164
|
+
const result = await fetcher('updateSession', data);
|
165
|
+
return await postProcessor(result);
|
243
166
|
},
|
244
167
|
|
245
168
|
async updateUser(user): Promise<AdapterUser> {
|
246
|
-
const
|
247
|
-
|
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);
|
169
|
+
const result = await fetcher('updateUser', user);
|
170
|
+
return await postProcessor(result);
|
261
171
|
},
|
262
172
|
|
263
173
|
async useVerificationToken(identifier_token): Promise<VerificationToken | null> {
|
264
|
-
|
265
|
-
|
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));
|
174
|
+
const result = await fetcher('useVerificationToken', identifier_token);
|
175
|
+
return await postProcessor(result);
|
274
176
|
},
|
275
177
|
};
|
276
178
|
}
|
@@ -1,12 +1,24 @@
|
|
1
1
|
import type { NextAuthConfig } from 'next-auth';
|
2
2
|
|
3
|
-
import {
|
3
|
+
import { getAuthConfig } from '@/config/auth';
|
4
|
+
import { getServerDBConfig } from '@/config/db';
|
4
5
|
|
6
|
+
import { LobeNextAuthDbAdapter } from './adapter';
|
5
7
|
import { ssoProviders } from './sso-providers';
|
6
8
|
|
9
|
+
const {
|
10
|
+
NEXT_AUTH_DEBUG,
|
11
|
+
NEXT_AUTH_SECRET,
|
12
|
+
NEXT_AUTH_SSO_SESSION_STRATEGY,
|
13
|
+
NEXT_AUTH_SSO_PROVIDERS,
|
14
|
+
NEXT_PUBLIC_ENABLE_NEXT_AUTH,
|
15
|
+
} = getAuthConfig();
|
16
|
+
|
17
|
+
const { NEXT_PUBLIC_ENABLED_SERVER_SERVICE } = getServerDBConfig();
|
18
|
+
|
7
19
|
export const initSSOProviders = () => {
|
8
|
-
return
|
9
|
-
?
|
20
|
+
return NEXT_PUBLIC_ENABLE_NEXT_AUTH
|
21
|
+
? NEXT_AUTH_SSO_PROVIDERS.split(/[,,]/).map((provider) => {
|
10
22
|
const validProvider = ssoProviders.find((item) => item.id === provider.trim());
|
11
23
|
|
12
24
|
if (validProvider) return validProvider.provider;
|
@@ -18,6 +30,7 @@ export const initSSOProviders = () => {
|
|
18
30
|
|
19
31
|
// Notice this is only an object, not a full Auth.js instance
|
20
32
|
export default {
|
33
|
+
adapter: NEXT_PUBLIC_ENABLED_SERVER_SERVICE ? LobeNextAuthDbAdapter() : undefined,
|
21
34
|
callbacks: {
|
22
35
|
// Note: Data processing order of callback: authorize --> jwt --> session
|
23
36
|
async jwt({ token, user }) {
|
@@ -39,12 +52,16 @@ export default {
|
|
39
52
|
return session;
|
40
53
|
},
|
41
54
|
},
|
42
|
-
debug:
|
55
|
+
debug: NEXT_AUTH_DEBUG,
|
43
56
|
pages: {
|
44
57
|
error: '/next-auth/error',
|
45
58
|
signIn: '/next-auth/signin',
|
46
59
|
},
|
47
60
|
providers: initSSOProviders(),
|
48
|
-
secret:
|
61
|
+
secret: NEXT_AUTH_SECRET,
|
62
|
+
session: {
|
63
|
+
// Force use JWT if server service is disabled
|
64
|
+
strategy: NEXT_PUBLIC_ENABLED_SERVER_SERVICE ? NEXT_AUTH_SSO_SESSION_STRATEGY : 'jwt',
|
65
|
+
},
|
49
66
|
trustHost: process.env?.AUTH_TRUST_HOST ? process.env.AUTH_TRUST_HOST === 'true' : true,
|
50
67
|
} satisfies NextAuthConfig;
|
@@ -1,33 +1,20 @@
|
|
1
1
|
import NextAuth from 'next-auth';
|
2
2
|
|
3
|
-
import
|
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();
|
3
|
+
import authConfig from './auth.config';
|
10
4
|
|
11
5
|
/**
|
12
|
-
* NextAuth initialization
|
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.
|
13
12
|
*
|
13
|
+
* Inside react component
|
14
14
|
* @example
|
15
15
|
* ```ts
|
16
|
-
* import
|
17
|
-
*
|
16
|
+
* import { signOut } from 'next-auth/react';
|
17
|
+
* signOut();
|
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
|
26
19
|
*/
|
27
|
-
export default NextAuth(
|
28
|
-
...config,
|
29
|
-
adapter: NEXT_PUBLIC_ENABLED_SERVER_SERVICE ? LobeNextAuthDbAdapter(serverDB) : undefined,
|
30
|
-
session: {
|
31
|
-
strategy: 'jwt',
|
32
|
-
},
|
33
|
-
});
|
20
|
+
export default NextAuth(authConfig);
|
@@ -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: NextAuth } = await import('@/libs/next-auth');
|
58
58
|
|
59
|
-
const session = await
|
59
|
+
const session = await NextAuth.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: NextAuth } = await import('@/libs/next-auth');
|
165
165
|
|
166
|
-
const session = await
|
166
|
+
const session = await NextAuth.auth();
|
167
167
|
if (session && session?.user?.id) {
|
168
168
|
auth = session.user;
|
169
169
|
userId = session.user.id;
|
@@ -156,6 +156,28 @@ export default {
|
|
156
156
|
searchProviders: '搜索服务商...',
|
157
157
|
sort: '自定义排序',
|
158
158
|
},
|
159
|
+
newapi: {
|
160
|
+
apiKey: {
|
161
|
+
desc: 'New API 平台提供的 API 密钥',
|
162
|
+
placeholder: 'New API API 密钥',
|
163
|
+
required: 'API 密钥是必需的',
|
164
|
+
title: 'API 密钥',
|
165
|
+
},
|
166
|
+
apiUrl: {
|
167
|
+
desc: 'New API 服务的 API 地址,大部分时候需要带 /v1',
|
168
|
+
title: 'API 地址',
|
169
|
+
},
|
170
|
+
enabled: {
|
171
|
+
title: '启用 New API',
|
172
|
+
},
|
173
|
+
models: {
|
174
|
+
batchSelect: '批量选择模型 ({{count}} 个)',
|
175
|
+
fetch: '获取模型列表',
|
176
|
+
selected: '已选择的模型',
|
177
|
+
title: '可用模型',
|
178
|
+
},
|
179
|
+
title: 'New API',
|
180
|
+
},
|
159
181
|
ollama: {
|
160
182
|
checker: {
|
161
183
|
desc: '测试代理地址是否正确填写',
|
@@ -188,6 +210,10 @@ export default {
|
|
188
210
|
},
|
189
211
|
},
|
190
212
|
providerModels: {
|
213
|
+
batchSelect: {
|
214
|
+
selected: '已选择 {{count}} 个模型',
|
215
|
+
title: '批量选择',
|
216
|
+
},
|
191
217
|
config: {
|
192
218
|
aesGcm: '您的秘钥与代理地址等将使用 <1>AES-GCM</1> 加密算法进行加密',
|
193
219
|
apiKey: {
|
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 NextAuth from '@/libs/next-auth';
|
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 = NextAuth.auth((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';
|
10
9
|
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');
|
28
27
|
vi.mock('@/server/modules/KeyVaultsEncrypt');
|
29
28
|
vi.mock('@/server/modules/S3');
|
30
29
|
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(NextAuthUserService).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(NextAuthUserService).mockReturnValue({
|
241
241
|
getAccount: vi.fn().mockResolvedValue(null),
|
242
242
|
unlinkAccount: vi.fn(),
|
243
243
|
} as any);
|
@@ -246,19 +246,6 @@ 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
|
-
});
|
262
249
|
});
|
263
250
|
|
264
251
|
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';
|
13
12
|
import { authedProcedure, router } from '@/libs/trpc/lambda';
|
14
13
|
import { serverDatabase } from '@/libs/trpc/lambda/middleware';
|
15
14
|
import { KeyVaultsGateKeeper } from '@/server/modules/KeyVaultsEncrypt';
|
16
15
|
import { S3 } from '@/server/modules/S3';
|
17
16
|
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
|
+
nextAuthUserService: new NextAuthUserService(ctx.serverDB),
|
33
33
|
userModel: new UserModel(ctx.serverDB, ctx.userId),
|
34
34
|
},
|
35
35
|
});
|
@@ -134,19 +134,10 @@ 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
|
-
|
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
|
-
}
|
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 });
|
150
141
|
}),
|
151
142
|
|
152
143
|
// 服务端上传头像
|