@lobehub/chat 1.80.1 → 1.80.3
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/next.config.ts +5 -1
- package/package.json +1 -1
- package/src/app/[variants]/oauth/consent/[uid]/Client.tsx +36 -23
- package/src/app/[variants]/oauth/consent/[uid]/page.tsx +2 -0
- package/src/config/aiModels/azure.ts +79 -1
- package/src/config/aiModels/azureai.ts +181 -0
- package/src/config/aiModels/google.ts +36 -2
- package/src/config/aiModels/groq.ts +31 -3
- package/src/config/aiModels/hunyuan.ts +54 -18
- package/src/config/aiModels/moonshot.ts +17 -17
- package/src/config/aiModels/novita.ts +25 -30
- package/src/config/aiModels/siliconcloud.ts +80 -2
- package/src/config/aiModels/stepfun.ts +40 -31
- package/src/config/aiModels/tencentcloud.ts +7 -6
- package/src/config/aiModels/volcengine.ts +1 -0
- package/src/config/aiModels/zhipu.ts +91 -27
- package/src/const/settings/knowledge.ts +2 -2
- package/src/database/models/user.ts +13 -1
- package/src/layout/AuthProvider/NextAuth/UserUpdater.tsx +18 -11
- package/src/libs/oidc-provider/adapter.ts +5 -6
- package/src/libs/oidc-provider/config.ts +0 -3
- package/src/libs/oidc-provider/provider.ts +1 -0
- package/src/libs/trpc/edge/index.ts +0 -4
- package/src/libs/trpc/lambda/context.ts +90 -6
- package/src/libs/trpc/lambda/index.ts +2 -1
- package/src/libs/trpc/lambda/middleware/oidcAuth.ts +14 -0
- package/src/libs/trpc/middleware/userAuth.ts +2 -4
- package/src/server/routers/lambda/user.ts +9 -2
- package/src/server/services/oidc/index.ts +71 -0
- package/src/services/user/client.ts +5 -2
- package/src/store/user/slices/common/action.ts +9 -2
- package/src/types/user/index.ts +6 -1
- package/src/utils/parseModels.test.ts +19 -3
- package/src/utils/server/__tests__/auth.test.ts +45 -1
- package/src/utils/server/auth.ts +26 -2
@@ -8,7 +8,6 @@ const zhipuChatModels: AIChatModelCard[] = [
|
|
8
8
|
contextWindowTokens: 16_384,
|
9
9
|
description: 'GLM-Zero-Preview具备强大的复杂推理能力,在逻辑推理、数学、编程等领域表现优异。',
|
10
10
|
displayName: 'GLM-Zero-Preview',
|
11
|
-
enabled: true,
|
12
11
|
id: 'glm-zero-preview',
|
13
12
|
pricing: {
|
14
13
|
currency: 'CNY',
|
@@ -17,6 +16,67 @@ const zhipuChatModels: AIChatModelCard[] = [
|
|
17
16
|
},
|
18
17
|
type: 'chat',
|
19
18
|
},
|
19
|
+
{
|
20
|
+
abilities: {
|
21
|
+
reasoning: true,
|
22
|
+
search: true,
|
23
|
+
},
|
24
|
+
contextWindowTokens: 32_000,
|
25
|
+
description: '推理模型: 具备强大推理能力,适用于需要深度推理的任务。',
|
26
|
+
displayName: 'GLM-Z1-Air',
|
27
|
+
id: 'glm-z1-air',
|
28
|
+
maxOutput: 30_000,
|
29
|
+
pricing: {
|
30
|
+
currency: 'CNY',
|
31
|
+
input: 0.5,
|
32
|
+
output: 0.5,
|
33
|
+
},
|
34
|
+
settings: {
|
35
|
+
searchImpl: 'params',
|
36
|
+
},
|
37
|
+
type: 'chat',
|
38
|
+
},
|
39
|
+
{
|
40
|
+
abilities: {
|
41
|
+
reasoning: true,
|
42
|
+
search: true,
|
43
|
+
},
|
44
|
+
contextWindowTokens: 32_000,
|
45
|
+
description: '极速推理:具有超快的推理速度和强大的推理效果。',
|
46
|
+
displayName: 'GLM-Z1-AirX',
|
47
|
+
id: 'glm-z1-airx',
|
48
|
+
maxOutput: 30_000,
|
49
|
+
pricing: {
|
50
|
+
currency: 'CNY',
|
51
|
+
input: 5,
|
52
|
+
output: 5,
|
53
|
+
},
|
54
|
+
settings: {
|
55
|
+
searchImpl: 'params',
|
56
|
+
},
|
57
|
+
type: 'chat',
|
58
|
+
},
|
59
|
+
{
|
60
|
+
abilities: {
|
61
|
+
reasoning: true,
|
62
|
+
search: true,
|
63
|
+
},
|
64
|
+
contextWindowTokens: 32_000,
|
65
|
+
description: 'GLM-Z1 系列具备强大的复杂推理能力,在逻辑推理、数学、编程等领域表现优异。最大上下文长度为32K。',
|
66
|
+
displayName: 'GLM-Z1-Flash',
|
67
|
+
enabled: true,
|
68
|
+
id: 'glm-z1-flash',
|
69
|
+
maxOutput: 30_000,
|
70
|
+
pricing: {
|
71
|
+
currency: 'CNY',
|
72
|
+
input: 0,
|
73
|
+
output: 0,
|
74
|
+
},
|
75
|
+
settings: {
|
76
|
+
searchImpl: 'params',
|
77
|
+
},
|
78
|
+
type: 'chat',
|
79
|
+
},
|
20
80
|
{
|
21
81
|
abilities: {
|
22
82
|
functionCall: true,
|
@@ -24,9 +84,10 @@ const zhipuChatModels: AIChatModelCard[] = [
|
|
24
84
|
},
|
25
85
|
contextWindowTokens: 128_000,
|
26
86
|
description: 'GLM-4-Flash 是处理简单任务的理想选择,速度最快且免费。',
|
27
|
-
displayName: 'GLM-4-Flash',
|
87
|
+
displayName: 'GLM-4-Flash-250414',
|
28
88
|
enabled: true,
|
29
|
-
id: 'glm-4-flash',
|
89
|
+
id: 'glm-4-flash-250414',
|
90
|
+
maxOutput: 4000,
|
30
91
|
pricing: {
|
31
92
|
currency: 'CNY',
|
32
93
|
input: 0,
|
@@ -45,8 +106,8 @@ const zhipuChatModels: AIChatModelCard[] = [
|
|
45
106
|
contextWindowTokens: 128_000,
|
46
107
|
description: 'GLM-4-FlashX 是Flash的增强版本,超快推理速度。',
|
47
108
|
displayName: 'GLM-4-FlashX',
|
48
|
-
enabled: true,
|
49
109
|
id: 'glm-4-flashx',
|
110
|
+
maxOutput: 4000,
|
50
111
|
pricing: {
|
51
112
|
currency: 'CNY',
|
52
113
|
input: 0.1,
|
@@ -66,6 +127,7 @@ const zhipuChatModels: AIChatModelCard[] = [
|
|
66
127
|
description: 'GLM-4-Long 支持超长文本输入,适合记忆型任务与大规模文档处理。',
|
67
128
|
displayName: 'GLM-4-Long',
|
68
129
|
id: 'glm-4-long',
|
130
|
+
maxOutput: 4000,
|
69
131
|
pricing: {
|
70
132
|
currency: 'CNY',
|
71
133
|
input: 1,
|
@@ -81,15 +143,15 @@ const zhipuChatModels: AIChatModelCard[] = [
|
|
81
143
|
functionCall: true,
|
82
144
|
search: true,
|
83
145
|
},
|
84
|
-
contextWindowTokens:
|
146
|
+
contextWindowTokens: 32_000,
|
85
147
|
description: 'GLM-4-Air 是性价比高的版本,性能接近GLM-4,提供快速度和实惠的价格。',
|
86
|
-
displayName: 'GLM-4-Air',
|
87
|
-
|
88
|
-
|
148
|
+
displayName: 'GLM-4-Air-250414',
|
149
|
+
id: 'glm-4-air-250414',
|
150
|
+
maxOutput: 4000,
|
89
151
|
pricing: {
|
90
152
|
currency: 'CNY',
|
91
|
-
input:
|
92
|
-
output:
|
153
|
+
input: 0.5,
|
154
|
+
output: 0.5,
|
93
155
|
},
|
94
156
|
settings: {
|
95
157
|
searchImpl: 'params',
|
@@ -104,8 +166,8 @@ const zhipuChatModels: AIChatModelCard[] = [
|
|
104
166
|
contextWindowTokens: 8192,
|
105
167
|
description: 'GLM-4-AirX 提供 GLM-4-Air 的高效版本,推理速度可达其2.6倍。',
|
106
168
|
displayName: 'GLM-4-AirX',
|
107
|
-
enabled: true,
|
108
169
|
id: 'glm-4-airx',
|
170
|
+
maxOutput: 4000,
|
109
171
|
pricing: {
|
110
172
|
currency: 'CNY',
|
111
173
|
input: 10,
|
@@ -144,8 +206,8 @@ const zhipuChatModels: AIChatModelCard[] = [
|
|
144
206
|
contextWindowTokens: 128_000,
|
145
207
|
description: 'GLM-4-Plus 作为高智能旗舰,具备强大的处理长文本和复杂任务的能力,性能全面提升。',
|
146
208
|
displayName: 'GLM-4-Plus',
|
147
|
-
enabled: true,
|
148
209
|
id: 'glm-4-plus',
|
210
|
+
maxOutput: 4000,
|
149
211
|
pricing: {
|
150
212
|
currency: 'CNY',
|
151
213
|
input: 50,
|
@@ -164,7 +226,7 @@ const zhipuChatModels: AIChatModelCard[] = [
|
|
164
226
|
contextWindowTokens: 128_000,
|
165
227
|
description: 'GLM-4-0520 是最新模型版本,专为高度复杂和多样化任务设计,表现卓越。',
|
166
228
|
displayName: 'GLM-4-0520',
|
167
|
-
id: 'glm-4-0520',
|
229
|
+
id: 'glm-4-0520', // 弃用时间 2025年12月30日
|
168
230
|
pricing: {
|
169
231
|
currency: 'CNY',
|
170
232
|
input: 100,
|
@@ -183,7 +245,7 @@ const zhipuChatModels: AIChatModelCard[] = [
|
|
183
245
|
contextWindowTokens: 128_000,
|
184
246
|
description: 'GLM-4 是发布于2024年1月的旧旗舰版本,目前已被更强的 GLM-4-0520 取代。',
|
185
247
|
displayName: 'GLM-4',
|
186
|
-
id: 'glm-4',
|
248
|
+
id: 'glm-4', // 弃用时间 2025年6月30日
|
187
249
|
pricing: {
|
188
250
|
currency: 'CNY',
|
189
251
|
input: 100,
|
@@ -198,7 +260,7 @@ const zhipuChatModels: AIChatModelCard[] = [
|
|
198
260
|
abilities: {
|
199
261
|
vision: true,
|
200
262
|
},
|
201
|
-
contextWindowTokens:
|
263
|
+
contextWindowTokens: 4096,
|
202
264
|
description:
|
203
265
|
'GLM-4V-Flash 专注于高效的单一图像理解,适用于快速图像解析的场景,例如实时图像分析或批量图像处理。',
|
204
266
|
displayName: 'GLM-4V-Flash',
|
@@ -218,13 +280,12 @@ const zhipuChatModels: AIChatModelCard[] = [
|
|
218
280
|
},
|
219
281
|
contextWindowTokens: 8192,
|
220
282
|
description: 'GLM-4V-Plus 具备对视频内容及多图片的理解能力,适合多模态任务。',
|
221
|
-
displayName: 'GLM-4V-Plus',
|
222
|
-
|
223
|
-
id: 'glm-4v-plus',
|
283
|
+
displayName: 'GLM-4V-Plus-0111',
|
284
|
+
id: 'glm-4v-plus-0111',
|
224
285
|
pricing: {
|
225
286
|
currency: 'CNY',
|
226
|
-
input:
|
227
|
-
output:
|
287
|
+
input: 4,
|
288
|
+
output: 4,
|
228
289
|
},
|
229
290
|
type: 'chat',
|
230
291
|
},
|
@@ -232,7 +293,7 @@ const zhipuChatModels: AIChatModelCard[] = [
|
|
232
293
|
abilities: {
|
233
294
|
vision: true,
|
234
295
|
},
|
235
|
-
contextWindowTokens:
|
296
|
+
contextWindowTokens: 4096,
|
236
297
|
description: 'GLM-4V 提供强大的图像理解与推理能力,支持多种视觉任务。',
|
237
298
|
displayName: 'GLM-4V',
|
238
299
|
id: 'glm-4v',
|
@@ -249,6 +310,7 @@ const zhipuChatModels: AIChatModelCard[] = [
|
|
249
310
|
'CodeGeeX-4 是强大的AI编程助手,支持多种编程语言的智能问答与代码补全,提升开发效率。',
|
250
311
|
displayName: 'CodeGeeX-4',
|
251
312
|
id: 'codegeex-4',
|
313
|
+
maxOutput: 32_000,
|
252
314
|
pricing: {
|
253
315
|
currency: 'CNY',
|
254
316
|
input: 0.1,
|
@@ -257,14 +319,15 @@ const zhipuChatModels: AIChatModelCard[] = [
|
|
257
319
|
type: 'chat',
|
258
320
|
},
|
259
321
|
{
|
260
|
-
contextWindowTokens:
|
261
|
-
description: 'CharGLM-
|
262
|
-
displayName: 'CharGLM-
|
263
|
-
id: 'charglm-
|
322
|
+
contextWindowTokens: 8192,
|
323
|
+
description: 'CharGLM-4 专为角色扮演与情感陪伴设计,支持超长多轮记忆与个性化对话,应用广泛。',
|
324
|
+
displayName: 'CharGLM-4',
|
325
|
+
id: 'charglm-4',
|
326
|
+
maxOutput: 4000,
|
264
327
|
pricing: {
|
265
328
|
currency: 'CNY',
|
266
|
-
input:
|
267
|
-
output:
|
329
|
+
input: 1,
|
330
|
+
output: 1,
|
268
331
|
},
|
269
332
|
type: 'chat',
|
270
333
|
},
|
@@ -273,6 +336,7 @@ const zhipuChatModels: AIChatModelCard[] = [
|
|
273
336
|
description: 'Emohaa 是心理模型,具备专业咨询能力,帮助用户理解情感问题。',
|
274
337
|
displayName: 'Emohaa',
|
275
338
|
id: 'emohaa',
|
339
|
+
maxOutput: 4000,
|
276
340
|
pricing: {
|
277
341
|
currency: 'CNY',
|
278
342
|
input: 15,
|
@@ -2,7 +2,7 @@ import { FilesConfig, FilesConfigItem } from '@/types/user/settings/filesConfig'
|
|
2
2
|
|
3
3
|
import {
|
4
4
|
DEFAULT_EMBEDDING_MODEL,
|
5
|
-
|
5
|
+
DEFAULT_EMBEDDING_PROVIDER,
|
6
6
|
DEFAULT_RERANK_MODEL,
|
7
7
|
DEFAULT_RERANK_PROVIDER,
|
8
8
|
DEFAULT_RERANK_QUERY_MODE,
|
@@ -10,7 +10,7 @@ import {
|
|
10
10
|
|
11
11
|
export const DEFAULT_FILE_EMBEDDING_MODEL_ITEM: FilesConfigItem = {
|
12
12
|
model: DEFAULT_EMBEDDING_MODEL,
|
13
|
-
provider:
|
13
|
+
provider: DEFAULT_EMBEDDING_PROVIDER,
|
14
14
|
};
|
15
15
|
|
16
16
|
export const DEFAULT_FILE_RERANK_MODEL_ITEM: FilesConfigItem = {
|
@@ -62,10 +62,15 @@ export class UserModel {
|
|
62
62
|
getUserState = async (decryptor: DecryptUserKeyVaults) => {
|
63
63
|
const result = await this.db
|
64
64
|
.select({
|
65
|
+
avatar: users.avatar,
|
66
|
+
email: users.email,
|
67
|
+
firstName: users.firstName,
|
68
|
+
fullName: users.fullName,
|
65
69
|
isOnboarded: users.isOnboarded,
|
70
|
+
lastName: users.lastName,
|
66
71
|
preference: users.preference,
|
67
|
-
|
68
72
|
settingsDefaultAgent: userSettings.defaultAgent,
|
73
|
+
|
69
74
|
settingsGeneral: userSettings.general,
|
70
75
|
settingsHotkey: userSettings.hotkey,
|
71
76
|
settingsKeyVaults: userSettings.keyVaults,
|
@@ -73,6 +78,7 @@ export class UserModel {
|
|
73
78
|
settingsSystemAgent: userSettings.systemAgent,
|
74
79
|
settingsTTS: userSettings.tts,
|
75
80
|
settingsTool: userSettings.tool,
|
81
|
+
username: users.username,
|
76
82
|
})
|
77
83
|
.from(users)
|
78
84
|
.where(eq(users.id, this.userId))
|
@@ -105,10 +111,16 @@ export class UserModel {
|
|
105
111
|
};
|
106
112
|
|
107
113
|
return {
|
114
|
+
avatar: state.avatar || undefined,
|
115
|
+
email: state.email || undefined,
|
116
|
+
firstName: state.firstName || undefined,
|
117
|
+
fullName: state.fullName || undefined,
|
108
118
|
isOnboarded: state.isOnboarded,
|
119
|
+
lastName: state.lastName || undefined,
|
109
120
|
preference: state.preference as UserPreference,
|
110
121
|
settings,
|
111
122
|
userId: this.userId,
|
123
|
+
username: state.username || undefined,
|
112
124
|
};
|
113
125
|
};
|
114
126
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
'use client';
|
2
2
|
|
3
3
|
import { useSession } from 'next-auth/react';
|
4
|
-
import { memo } from 'react';
|
4
|
+
import { memo, useEffect } from 'react';
|
5
5
|
import { createStoreUpdater } from 'zustand-utils';
|
6
6
|
|
7
7
|
import { useUserStore } from '@/store/user';
|
@@ -17,20 +17,27 @@ const UserUpdater = memo(() => {
|
|
17
17
|
const nextUser = session?.user;
|
18
18
|
const useStoreUpdater = createStoreUpdater(useUserStore);
|
19
19
|
|
20
|
-
const lobeUser = {
|
21
|
-
avatar: nextUser?.image,
|
22
|
-
email: nextUser?.email,
|
23
|
-
fullName: nextUser?.name,
|
24
|
-
id: nextUser?.id,
|
25
|
-
} as LobeUser;
|
26
|
-
|
27
20
|
useStoreUpdater('isLoaded', isLoaded);
|
28
|
-
useStoreUpdater('user', lobeUser);
|
29
21
|
useStoreUpdater('isSignedIn', isSignedIn);
|
30
|
-
|
31
22
|
useStoreUpdater('nextSession', session);
|
32
|
-
useStoreUpdater('nextUser', nextUser);
|
33
23
|
|
24
|
+
// 使用 useEffect 处理需要保持同步的用户数据
|
25
|
+
useEffect(() => {
|
26
|
+
if (nextUser) {
|
27
|
+
const userAvatar = useUserStore.getState().user?.avatar;
|
28
|
+
|
29
|
+
const lobeUser = {
|
30
|
+
// 头像使用设置的,而不是从 next-auth 中获取
|
31
|
+
avatar: userAvatar || '',
|
32
|
+
email: nextUser.email,
|
33
|
+
fullName: nextUser.name,
|
34
|
+
id: nextUser.id,
|
35
|
+
} as LobeUser;
|
36
|
+
|
37
|
+
// 更新用户相关数据
|
38
|
+
useUserStore.setState({ nextUser: nextUser, user: lobeUser });
|
39
|
+
}
|
40
|
+
}, [nextUser]);
|
34
41
|
return null;
|
35
42
|
});
|
36
43
|
|
@@ -22,9 +22,10 @@ class OIDCAdapter {
|
|
22
22
|
private name: string;
|
23
23
|
|
24
24
|
constructor(name: string, db: LobeChatDatabase) {
|
25
|
+
log('[%s] Constructor called with name: %s', name, name);
|
26
|
+
|
25
27
|
this.name = name;
|
26
28
|
this.db = db;
|
27
|
-
log('Creating adapter for model: %s', name);
|
28
29
|
}
|
29
30
|
|
30
31
|
/**
|
@@ -530,12 +531,10 @@ class OIDCAdapter {
|
|
530
531
|
/**
|
531
532
|
* 创建适配器工厂
|
532
533
|
*/
|
533
|
-
static createAdapterFactory(db: LobeChatDatabase) {
|
534
|
+
static createAdapterFactory = (db: LobeChatDatabase) => {
|
534
535
|
log('Creating adapter factory with database instance');
|
535
|
-
return
|
536
|
-
|
537
|
-
};
|
538
|
-
}
|
536
|
+
return (name: string) => new OIDCAdapter(name, db);
|
537
|
+
};
|
539
538
|
}
|
540
539
|
|
541
540
|
export { OIDCAdapter as DrizzleAdapter };
|
@@ -11,9 +11,6 @@ export const defaultClients: ClientMetadata[] = [
|
|
11
11
|
// 仅支持授权码流程
|
12
12
|
grant_types: ['authorization_code', 'refresh_token'],
|
13
13
|
|
14
|
-
// 明确指明是原生应用
|
15
|
-
isFirstParty: true,
|
16
|
-
|
17
14
|
logo_uri: 'https://hub-apac-1.lobeobjects.space/lobehub-desktop-icon.png',
|
18
15
|
|
19
16
|
// 桌面端注册的自定义协议回调(使用反向域名格式)
|
@@ -272,6 +272,7 @@ export const createOIDCProvider = async (db: LobeChatDatabase): Promise<Provider
|
|
272
272
|
const baseUrl = urlJoin(appEnv.APP_URL!, '/oidc');
|
273
273
|
|
274
274
|
const provider = new Provider(baseUrl, configuration);
|
275
|
+
provider.proxy = true;
|
275
276
|
|
276
277
|
provider.on('server_error', (ctx, err) => {
|
277
278
|
logProvider('OIDC Provider Server Error: %O', err); // Use logProvider
|
@@ -10,7 +10,6 @@
|
|
10
10
|
import { DESKTOP_USER_ID } from '@/const/desktop';
|
11
11
|
import { isDesktop } from '@/const/version';
|
12
12
|
|
13
|
-
import { userAuth } from '../middleware/userAuth';
|
14
13
|
import { edgeTrpc } from './init';
|
15
14
|
import { jwtPayloadChecker } from './middleware/jwtPayload';
|
16
15
|
|
@@ -30,9 +29,6 @@ export const publicProcedure = edgeTrpc.procedure.use(({ next, ctx }) => {
|
|
30
29
|
});
|
31
30
|
});
|
32
31
|
|
33
|
-
// procedure that asserts that the user is logged in
|
34
|
-
export const authedProcedure = edgeTrpc.procedure.use(userAuth);
|
35
|
-
|
36
32
|
// procedure that asserts that the user add the password
|
37
33
|
export const passwordProcedure = edgeTrpc.procedure.use(jwtPayloadChecker);
|
38
34
|
|
@@ -1,14 +1,31 @@
|
|
1
|
+
import debug from 'debug';
|
1
2
|
import { User } from 'next-auth';
|
2
3
|
import { NextRequest } from 'next/server';
|
3
4
|
|
4
5
|
import { JWTPayload, LOBE_CHAT_AUTH_HEADER, enableClerk, enableNextAuth } from '@/const/auth';
|
6
|
+
import { oidcEnv } from '@/envs/oidc';
|
5
7
|
import { ClerkAuth, IClerkAuth } from '@/libs/clerk-auth';
|
8
|
+
import { extractBearerToken } from '@/utils/server/auth';
|
9
|
+
|
10
|
+
// Create context logger namespace
|
11
|
+
const log = debug('lobe-trpc:lambda:context');
|
12
|
+
|
13
|
+
export interface OIDCAuth {
|
14
|
+
// Other OIDC information that might be needed (optional, as payload contains all info)
|
15
|
+
[key: string]: any;
|
16
|
+
// OIDC token data (now the complete payload)
|
17
|
+
payload: any;
|
18
|
+
// User ID
|
19
|
+
sub: string;
|
20
|
+
}
|
6
21
|
|
7
22
|
export interface AuthContext {
|
8
23
|
authorizationHeader?: string | null;
|
9
24
|
clerkAuth?: IClerkAuth;
|
10
25
|
jwtPayload?: JWTPayload | null;
|
11
26
|
nextAuth?: User;
|
27
|
+
// Add OIDC authentication information
|
28
|
+
oidcAuth?: OIDCAuth | null;
|
12
29
|
userId?: string | null;
|
13
30
|
}
|
14
31
|
|
@@ -20,13 +37,18 @@ export const createContextInner = async (params?: {
|
|
20
37
|
authorizationHeader?: string | null;
|
21
38
|
clerkAuth?: IClerkAuth;
|
22
39
|
nextAuth?: User;
|
40
|
+
oidcAuth?: OIDCAuth | null;
|
23
41
|
userId?: string | null;
|
24
|
-
}): Promise<AuthContext> =>
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
42
|
+
}): Promise<AuthContext> => {
|
43
|
+
log('createContextInner called with params: %O', params);
|
44
|
+
return {
|
45
|
+
authorizationHeader: params?.authorizationHeader,
|
46
|
+
clerkAuth: params?.clerkAuth,
|
47
|
+
nextAuth: params?.nextAuth,
|
48
|
+
oidcAuth: params?.oidcAuth,
|
49
|
+
userId: params?.userId,
|
50
|
+
};
|
51
|
+
};
|
30
52
|
|
31
53
|
export type LambdaContext = Awaited<ReturnType<typeof createContextInner>>;
|
32
54
|
|
@@ -35,23 +57,76 @@ export type LambdaContext = Awaited<ReturnType<typeof createContextInner>>;
|
|
35
57
|
* @link https://trpc.io/docs/v11/context
|
36
58
|
*/
|
37
59
|
export const createLambdaContext = async (request: NextRequest): Promise<LambdaContext> => {
|
60
|
+
log('createLambdaContext called for request');
|
38
61
|
// for API-response caching see https://trpc.io/docs/v11/caching
|
39
62
|
|
40
63
|
const authorization = request.headers.get(LOBE_CHAT_AUTH_HEADER);
|
64
|
+
log('LobeChat Authorization header: %s', authorization ? 'exists' : 'not found');
|
41
65
|
|
42
66
|
let userId;
|
43
67
|
let auth;
|
68
|
+
let oidcAuth = null;
|
69
|
+
|
70
|
+
// Prioritize checking the standard Authorization header for OIDC Bearer Token validation
|
71
|
+
if (oidcEnv.ENABLE_OIDC) {
|
72
|
+
log('OIDC enabled, attempting OIDC authentication');
|
73
|
+
const standardAuthorization = request.headers.get('Authorization');
|
74
|
+
log('Standard Authorization header: %s', standardAuthorization ? 'exists' : 'not found');
|
75
|
+
|
76
|
+
try {
|
77
|
+
// Use extractBearerToken from utils
|
78
|
+
const bearerToken = extractBearerToken(standardAuthorization);
|
79
|
+
|
80
|
+
log('Extracted Bearer Token: %s', bearerToken ? 'valid' : 'invalid');
|
81
|
+
if (bearerToken) {
|
82
|
+
const { OIDCService } = await import('@/server/services/oidc');
|
83
|
+
|
84
|
+
// Initialize OIDC service
|
85
|
+
log('Initializing OIDC service');
|
86
|
+
const oidcService = await OIDCService.initialize();
|
87
|
+
// Validate token using OIDCService
|
88
|
+
log('Validating OIDC token');
|
89
|
+
const tokenInfo = await oidcService.validateToken(bearerToken);
|
90
|
+
oidcAuth = {
|
91
|
+
payload: tokenInfo.tokenData,
|
92
|
+
...tokenInfo.tokenData, // Spread payload into oidcAuth
|
93
|
+
sub: tokenInfo.userId, // Use tokenData as payload
|
94
|
+
};
|
95
|
+
userId = tokenInfo.userId;
|
96
|
+
log('OIDC authentication successful, userId: %s', userId);
|
97
|
+
|
98
|
+
// If OIDC authentication is successful, return context immediately
|
99
|
+
log('OIDC authentication successful, creating context and returning');
|
100
|
+
return createContextInner({
|
101
|
+
// Preserve original LobeChat Authorization Header (if any)
|
102
|
+
authorizationHeader: authorization,
|
103
|
+
oidcAuth,
|
104
|
+
userId,
|
105
|
+
});
|
106
|
+
}
|
107
|
+
} catch (error) {
|
108
|
+
// If OIDC authentication fails, log error and continue with other authentication methods
|
109
|
+
if (standardAuthorization?.startsWith('Bearer ')) {
|
110
|
+
log('OIDC authentication failed, error: %O', error);
|
111
|
+
console.error('OIDC authentication failed, trying other methods:', error);
|
112
|
+
}
|
113
|
+
}
|
114
|
+
}
|
44
115
|
|
116
|
+
// If OIDC is not enabled or validation fails, try LobeChat custom Header and other authentication methods
|
45
117
|
if (enableClerk) {
|
118
|
+
log('Attempting Clerk authentication');
|
46
119
|
const clerkAuth = new ClerkAuth();
|
47
120
|
const result = clerkAuth.getAuthFromRequest(request);
|
48
121
|
auth = result.clerkAuth;
|
49
122
|
userId = result.userId;
|
123
|
+
log('Clerk authentication result, userId: %s', userId || 'not authenticated');
|
50
124
|
|
51
125
|
return createContextInner({ authorizationHeader: authorization, clerkAuth: auth, userId });
|
52
126
|
}
|
53
127
|
|
54
128
|
if (enableNextAuth) {
|
129
|
+
log('Attempting NextAuth authentication');
|
55
130
|
try {
|
56
131
|
const { default: NextAuthEdge } = await import('@/libs/next-auth/edge');
|
57
132
|
|
@@ -59,12 +134,21 @@ export const createLambdaContext = async (request: NextRequest): Promise<LambdaC
|
|
59
134
|
if (session && session?.user?.id) {
|
60
135
|
auth = session.user;
|
61
136
|
userId = session.user.id;
|
137
|
+
log('NextAuth authentication successful, userId: %s', userId);
|
138
|
+
} else {
|
139
|
+
log('NextAuth authentication failed, no valid session');
|
62
140
|
}
|
63
141
|
return createContextInner({ authorizationHeader: authorization, nextAuth: auth, userId });
|
64
142
|
} catch (e) {
|
143
|
+
log('NextAuth authentication error: %O', e);
|
65
144
|
console.error('next auth err', e);
|
66
145
|
}
|
67
146
|
}
|
68
147
|
|
148
|
+
// Final return, userId may be undefined
|
149
|
+
log(
|
150
|
+
'All authentication methods attempted, returning final context, userId: %s',
|
151
|
+
userId || 'not authenticated',
|
152
|
+
);
|
69
153
|
return createContextInner({ authorizationHeader: authorization, userId });
|
70
154
|
};
|
@@ -12,6 +12,7 @@ import { isDesktop } from '@/const/version';
|
|
12
12
|
|
13
13
|
import { userAuth } from '../middleware/userAuth';
|
14
14
|
import { trpc } from './init';
|
15
|
+
import { oidcAuth } from './middleware/oidcAuth';
|
15
16
|
|
16
17
|
/**
|
17
18
|
* Create a router
|
@@ -30,7 +31,7 @@ export const publicProcedure = trpc.procedure.use(({ next, ctx }) => {
|
|
30
31
|
});
|
31
32
|
|
32
33
|
// procedure that asserts that the user is logged in
|
33
|
-
export const authedProcedure = trpc.procedure.use(userAuth);
|
34
|
+
export const authedProcedure = trpc.procedure.use(oidcAuth).use(userAuth);
|
34
35
|
|
35
36
|
/**
|
36
37
|
* Create a server-side caller
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import { trpc } from '../init';
|
2
|
+
|
3
|
+
export const oidcAuth = trpc.middleware(async (opts) => {
|
4
|
+
const { ctx, next } = opts;
|
5
|
+
|
6
|
+
// 检查 OIDC 认证
|
7
|
+
if (ctx.oidcAuth) {
|
8
|
+
return next({
|
9
|
+
ctx: { oidcAuth: ctx.oidcAuth, userId: ctx.oidcAuth.sub },
|
10
|
+
});
|
11
|
+
}
|
12
|
+
|
13
|
+
return next();
|
14
|
+
});
|
@@ -26,9 +26,7 @@ export const userAuth = trpc.middleware(async (opts) => {
|
|
26
26
|
}
|
27
27
|
|
28
28
|
return opts.next({
|
29
|
-
|
30
|
-
|
31
|
-
userId: ctx.userId,
|
32
|
-
},
|
29
|
+
// ✅ user value is known to be non-null now
|
30
|
+
ctx: { userId: ctx.userId },
|
33
31
|
});
|
34
32
|
});
|
@@ -85,17 +85,24 @@ export const userRouter = router({
|
|
85
85
|
const hasExtraSession = await sessionModel.hasMoreThanN(1);
|
86
86
|
|
87
87
|
return {
|
88
|
+
avatar: state.avatar,
|
88
89
|
canEnablePWAGuide: hasMoreThan4Messages,
|
89
90
|
canEnableTrace: hasMoreThan4Messages,
|
91
|
+
email: state.email,
|
92
|
+
firstName: state.firstName,
|
93
|
+
|
94
|
+
fullName: state.fullName,
|
95
|
+
|
90
96
|
// 有消息,或者创建过助手,则认为有 conversation
|
91
97
|
hasConversation: hasAnyMessages || hasExtraSession,
|
92
|
-
|
93
98
|
// always return true for community version
|
94
99
|
isOnboard: state.isOnboarded || true,
|
100
|
+
lastName: state.lastName,
|
95
101
|
preference: state.preference as UserPreference,
|
96
102
|
settings: state.settings,
|
97
103
|
userId: ctx.userId,
|
98
|
-
|
104
|
+
username: state.username,
|
105
|
+
} satisfies UserInitializationState;
|
99
106
|
}),
|
100
107
|
|
101
108
|
makeUserOnboarded: userProcedure.mutation(async ({ ctx }) => {
|