@lobehub/chat 1.79.6 → 1.79.8

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.
Files changed (74) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/docs/development/database-schema.dbml +119 -0
  4. package/docs/self-hosting/advanced/online-search.mdx +63 -0
  5. package/locales/ar/models.json +12 -0
  6. package/locales/ar/oauth.json +39 -0
  7. package/locales/bg-BG/models.json +12 -0
  8. package/locales/bg-BG/oauth.json +39 -0
  9. package/locales/de-DE/models.json +12 -0
  10. package/locales/de-DE/oauth.json +39 -0
  11. package/locales/en-US/models.json +12 -0
  12. package/locales/en-US/oauth.json +39 -0
  13. package/locales/es-ES/models.json +12 -0
  14. package/locales/es-ES/oauth.json +39 -0
  15. package/locales/fa-IR/models.json +12 -0
  16. package/locales/fa-IR/oauth.json +39 -0
  17. package/locales/fr-FR/models.json +12 -0
  18. package/locales/fr-FR/oauth.json +39 -0
  19. package/locales/it-IT/models.json +12 -0
  20. package/locales/it-IT/oauth.json +39 -0
  21. package/locales/ja-JP/models.json +12 -0
  22. package/locales/ja-JP/oauth.json +39 -0
  23. package/locales/ko-KR/models.json +12 -0
  24. package/locales/ko-KR/oauth.json +39 -0
  25. package/locales/nl-NL/models.json +12 -0
  26. package/locales/nl-NL/oauth.json +39 -0
  27. package/locales/pl-PL/models.json +12 -0
  28. package/locales/pl-PL/oauth.json +39 -0
  29. package/locales/pt-BR/models.json +12 -0
  30. package/locales/pt-BR/oauth.json +39 -0
  31. package/locales/ru-RU/models.json +12 -0
  32. package/locales/ru-RU/oauth.json +39 -0
  33. package/locales/tr-TR/models.json +12 -0
  34. package/locales/tr-TR/oauth.json +39 -0
  35. package/locales/vi-VN/models.json +12 -0
  36. package/locales/vi-VN/oauth.json +39 -0
  37. package/locales/zh-CN/models.json +12 -0
  38. package/locales/zh-CN/oauth.json +39 -0
  39. package/locales/zh-TW/models.json +12 -0
  40. package/locales/zh-TW/oauth.json +39 -0
  41. package/package.json +5 -2
  42. package/scripts/generate-oidc-jwk.mjs +59 -0
  43. package/scripts/migrateServerDB/index.ts +3 -1
  44. package/src/app/(backend)/oidc/[...oidc]/route.ts +270 -0
  45. package/src/app/(backend)/oidc/consent/route.ts +97 -0
  46. package/src/app/[variants]/oauth/consent/[uid]/Client.tsx +97 -0
  47. package/src/app/[variants]/oauth/consent/[uid]/failed/page.tsx +36 -0
  48. package/src/app/[variants]/oauth/consent/[uid]/page.tsx +71 -0
  49. package/src/app/[variants]/oauth/consent/[uid]/success/page.tsx +30 -0
  50. package/src/const/hotkeys.ts +2 -2
  51. package/src/const/trace.ts +1 -0
  52. package/src/database/client/migrations.json +27 -8
  53. package/src/database/migrations/0020_add_oidc.sql +124 -0
  54. package/src/database/migrations/meta/0020_snapshot.json +4975 -0
  55. package/src/database/migrations/meta/_journal.json +7 -0
  56. package/src/database/repositories/tableViewer/index.test.ts +1 -1
  57. package/src/database/schemas/index.ts +1 -0
  58. package/src/database/schemas/oidc.ts +158 -0
  59. package/src/database/server/models/__tests__/adapter.test.ts +503 -0
  60. package/src/envs/oidc.ts +18 -0
  61. package/src/libs/agent-runtime/azureOpenai/index.ts +4 -1
  62. package/src/libs/agent-runtime/utils/streams/protocol.ts +2 -4
  63. package/src/libs/oidc-provider/adapter.ts +494 -0
  64. package/src/libs/oidc-provider/config.ts +53 -0
  65. package/src/libs/oidc-provider/http-adapter.ts +279 -0
  66. package/src/libs/oidc-provider/interaction-policy.ts +37 -0
  67. package/src/libs/oidc-provider/provider.ts +260 -0
  68. package/src/locales/default/index.ts +2 -0
  69. package/src/locales/default/oauth.ts +41 -0
  70. package/src/middleware.ts +94 -6
  71. package/src/server/services/oidc/index.ts +29 -0
  72. package/src/server/services/oidc/oidcProvider.ts +27 -0
  73. package/src/store/chat/slices/aiChat/actions/memory.ts +6 -1
  74. package/src/types/hotkey.ts +54 -3
package/src/middleware.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
2
+ import debug from 'debug';
2
3
  import { NextRequest, NextResponse } from 'next/server';
3
4
  import { UAParser } from 'ua-parser-js';
4
5
  import urlJoin from 'url-join';
@@ -14,6 +15,15 @@ import { parseDefaultThemeFromCountry } from '@/utils/server/geo';
14
15
  import { RouteVariants } from '@/utils/server/routeVariants';
15
16
 
16
17
  import { OAUTH_AUTHORIZED } from './const/auth';
18
+ import { oidcEnv } from './envs/oidc';
19
+
20
+ // Create debug logger instances
21
+ const logDefault = debug('lobe-middleware:default');
22
+ const logNextAuth = debug('lobe-middleware:next-auth');
23
+ const logClerk = debug('lobe-middleware:clerk');
24
+
25
+ // OIDC session pre-sync constant
26
+ const OIDC_SESSION_HEADER = 'x-oidc-session-sync';
17
27
 
18
28
  export const config = {
19
29
  matcher: [
@@ -37,19 +47,25 @@ export const config = {
37
47
  '/login(.*)',
38
48
  '/signup(.*)',
39
49
  '/next-auth/(.*)',
50
+ '/oauth(.*)',
51
+ '/oidc(.*)',
40
52
  // ↓ cloud ↓
41
53
  ],
42
54
  };
43
55
 
56
+ const backendApiEndpoints = ['/api', '/trpc', '/webapi', '/oidc'];
57
+
44
58
  const defaultMiddleware = (request: NextRequest) => {
45
59
  const url = new URL(request.url);
60
+ logDefault('Processing request: %s %s', request.method, request.url);
46
61
 
47
62
  // skip all api requests
48
- if (['/api', '/trpc', '/webapi'].some((path) => url.pathname.startsWith(path))) {
63
+ if (backendApiEndpoints.some((path) => url.pathname.startsWith(path))) {
64
+ logDefault('Skipping API request: %s', url.pathname);
49
65
  return NextResponse.next();
50
66
  }
51
67
 
52
- // 1. cookie 中读取用户偏好
68
+ // 1. Read user preferences from cookies
53
69
  const theme =
54
70
  request.cookies.get(LOBE_THEME_APPEARANCE)?.value || parseDefaultThemeFromCountry(request);
55
71
 
@@ -62,16 +78,36 @@ const defaultMiddleware = (request: NextRequest) => {
62
78
 
63
79
  const device = new UAParser(ua || '').getDevice();
64
80
 
65
- // 2. 创建规范化的偏好值
81
+ logDefault('User preferences: %O', {
82
+ browserLanguage,
83
+ deviceType: device.type,
84
+ hasCookies: {
85
+ locale: !!request.cookies.get(LOBE_LOCALE_COOKIE)?.value,
86
+ theme: !!request.cookies.get(LOBE_THEME_APPEARANCE)?.value,
87
+ },
88
+ locale,
89
+ theme,
90
+ });
91
+
92
+ // 2. Create normalized preference values
66
93
  const route = RouteVariants.serializeVariants({
67
94
  isMobile: device.type === 'mobile',
68
95
  locale,
69
96
  theme,
70
97
  });
71
98
 
99
+ logDefault('Serialized route variant: %s', route);
100
+
72
101
  // if app is in docker, rewrite to self container
73
102
  // https://github.com/lobehub/lobe-chat/issues/5876
74
103
  if (appEnv.MIDDLEWARE_REWRITE_THROUGH_LOCAL) {
104
+ logDefault('Local container rewrite enabled: %O', {
105
+ host: '127.0.0.1',
106
+ original: url.toString(),
107
+ port: process.env.PORT || '3210',
108
+ protocol: 'http',
109
+ });
110
+
75
111
  url.protocol = 'http';
76
112
  url.host = '127.0.0.1';
77
113
  url.port = process.env.PORT || '3210';
@@ -86,7 +122,12 @@ const defaultMiddleware = (request: NextRequest) => {
86
122
  ? urlJoin(url.origin, nextPathname)
87
123
  : nextPathname;
88
124
 
89
- console.log(`[rewrite] ${url.pathname} -> ${nextURL}`);
125
+ logDefault('URL rewrite: %O', {
126
+ isLocalRewrite: appEnv.MIDDLEWARE_REWRITE_THROUGH_LOCAL,
127
+ nextPathname: nextPathname,
128
+ nextURL: nextURL,
129
+ originalPathname: url.pathname,
130
+ });
90
131
 
91
132
  url.pathname = nextPathname;
92
133
 
@@ -95,6 +136,8 @@ const defaultMiddleware = (request: NextRequest) => {
95
136
 
96
137
  // Initialize an Edge compatible NextAuth middleware
97
138
  const nextAuthMiddleware = NextAuthEdge.auth((req) => {
139
+ logNextAuth('NextAuth middleware processing request: %s %s', req.method, req.url);
140
+
98
141
  const response = defaultMiddleware(req);
99
142
 
100
143
  // Just check if session exists
@@ -104,10 +147,25 @@ const nextAuthMiddleware = NextAuthEdge.auth((req) => {
104
147
  // refs: https://github.com/lobehub/lobe-chat/pull/1323
105
148
  const isLoggedIn = !!session?.expires;
106
149
 
150
+ logNextAuth('NextAuth session status: %O', {
151
+ expires: session?.expires,
152
+ isLoggedIn,
153
+ userId: session?.user?.id,
154
+ });
155
+
107
156
  // Remove & amend OAuth authorized header
108
157
  response.headers.delete(OAUTH_AUTHORIZED);
109
158
  if (isLoggedIn) {
159
+ logNextAuth('Setting auth header: %s = %s', OAUTH_AUTHORIZED, 'true');
110
160
  response.headers.set(OAUTH_AUTHORIZED, 'true');
161
+
162
+ // If OIDC is enabled and user is logged in, add OIDC session pre-sync header
163
+ if (oidcEnv.ENABLE_OIDC && session?.user?.id) {
164
+ logNextAuth('OIDC session pre-sync: Setting %s = %s', OIDC_SESSION_HEADER, session.user.id);
165
+ response.headers.set(OIDC_SESSION_HEADER, session.user.id);
166
+ }
167
+ } else {
168
+ logNextAuth('Not logged in, no auth header set');
111
169
  }
112
170
 
113
171
  return response;
@@ -122,9 +180,33 @@ const isProtectedRoute = createRouteMatcher([
122
180
 
123
181
  const clerkAuthMiddleware = clerkMiddleware(
124
182
  async (auth, req) => {
125
- if (isProtectedRoute(req)) await auth.protect();
183
+ logClerk('Clerk middleware processing request: %s %s', req.method, req.url);
184
+
185
+ const isProtected = isProtectedRoute(req);
186
+ logClerk('Route protection status: %s, %s', req.url, isProtected ? 'protected' : 'public');
187
+
188
+ if (isProtected) {
189
+ logClerk('Protecting route: %s', req.url);
190
+ await auth.protect();
191
+ }
126
192
 
127
- return defaultMiddleware(req);
193
+ const response = defaultMiddleware(req);
194
+
195
+ const data = await auth();
196
+ logClerk('Clerk auth status: %O', {
197
+ isSignedIn: !!data.userId,
198
+ userId: data.userId,
199
+ });
200
+
201
+ // If OIDC is enabled and Clerk user is logged in, add OIDC session pre-sync header
202
+ if (oidcEnv.ENABLE_OIDC && data.userId) {
203
+ logClerk('OIDC session pre-sync: Setting %s = %s', OIDC_SESSION_HEADER, data.userId);
204
+ response.headers.set(OIDC_SESSION_HEADER, data.userId);
205
+ } else if (oidcEnv.ENABLE_OIDC) {
206
+ logClerk('No Clerk user detected, not setting OIDC session sync header');
207
+ }
208
+
209
+ return response;
128
210
  },
129
211
  {
130
212
  // https://github.com/lobehub/lobe-chat/pull/3084
@@ -134,6 +216,12 @@ const clerkAuthMiddleware = clerkMiddleware(
134
216
  },
135
217
  );
136
218
 
219
+ logDefault('Middleware configuration: %O', {
220
+ enableClerk: authEnv.NEXT_PUBLIC_ENABLE_CLERK_AUTH,
221
+ enableNextAuth: authEnv.NEXT_PUBLIC_ENABLE_NEXT_AUTH,
222
+ enableOIDC: oidcEnv.ENABLE_OIDC,
223
+ });
224
+
137
225
  export default authEnv.NEXT_PUBLIC_ENABLE_CLERK_AUTH
138
226
  ? clerkAuthMiddleware
139
227
  : authEnv.NEXT_PUBLIC_ENABLE_NEXT_AUTH
@@ -0,0 +1,29 @@
1
+ import { createContextForInteractionDetails } from '@/libs/oidc-provider/http-adapter';
2
+ import { OIDCProvider } from '@/libs/oidc-provider/provider';
3
+
4
+ import { getOIDCProvider } from './oidcProvider';
5
+
6
+ export class OIDCService {
7
+ private provider: OIDCProvider;
8
+
9
+ constructor(provider: OIDCProvider) {
10
+ this.provider = provider;
11
+ }
12
+ static async initialize() {
13
+ const provider = await getOIDCProvider();
14
+
15
+ return new OIDCService(provider);
16
+ }
17
+
18
+ async getInteractionDetails(uid: string) {
19
+ const { req, res } = await createContextForInteractionDetails(uid);
20
+ return this.provider.interactionDetails(req, res);
21
+ }
22
+
23
+ async getInteractionResult(uid: string, result: any) {
24
+ const { req, res } = await createContextForInteractionDetails(uid);
25
+ return this.provider.interactionResult(req, res, result, { mergeWithLastSubmission: true });
26
+ }
27
+ }
28
+
29
+ export { getOIDCProvider } from './oidcProvider';
@@ -0,0 +1,27 @@
1
+ import { appEnv } from '@/config/app';
2
+ import { getDBInstance } from '@/database/core/web-server';
3
+ import { oidcEnv } from '@/envs/oidc';
4
+ import { OIDCProvider, createOIDCProvider } from '@/libs/oidc-provider/provider';
5
+
6
+ /**
7
+ * OIDC Provider 实例
8
+ */
9
+ let provider: OIDCProvider;
10
+
11
+ /**
12
+ * 获取 OIDC Provider 实例
13
+ * @returns OIDC Provider 实例
14
+ */
15
+ export const getOIDCProvider = async (): Promise<OIDCProvider> => {
16
+ if (!provider) {
17
+ if (!oidcEnv.ENABLE_OIDC) {
18
+ throw new Error('OIDC is not enabled. Set ENABLE_OIDC=1 to enable it.');
19
+ }
20
+
21
+ const baseUrl = appEnv.APP_URL!;
22
+ const db = getDBInstance();
23
+ provider = await createOIDCProvider(db, baseUrl);
24
+ }
25
+
26
+ return provider;
27
+ };
@@ -1,6 +1,7 @@
1
1
  import { StateCreator } from 'zustand/vanilla';
2
2
 
3
3
  import { chainSummaryHistory } from '@/chains/summaryHistory';
4
+ import { TraceNameMap } from '@/const/trace';
4
5
  import { chatService } from '@/services/chat';
5
6
  import { topicService } from '@/services/topic';
6
7
  import { ChatStore } from '@/store/chat';
@@ -29,8 +30,12 @@ export const chatMemory: StateCreator<
29
30
  onFinish: async (text) => {
30
31
  historySummary = text;
31
32
  },
32
-
33
33
  params: { ...chainSummaryHistory(messages), model, provider, stream: false },
34
+ trace: {
35
+ sessionId: get().activeId,
36
+ topicId: get().activeTopicId,
37
+ traceName: TraceNameMap.SummaryHistoryMessages,
38
+ },
34
39
  });
35
40
 
36
41
  await topicService.updateTopic(topicId, {
@@ -1,8 +1,59 @@
1
- import { KeyMapEnum } from '@lobehub/ui/es/Hotkey';
2
-
3
1
  export const KeyEnum = {
4
- ...KeyMapEnum,
2
+ Alt: 'alt',
3
+ Backquote: 'backquote',
4
+ // `
5
+ Backslash: 'backslash',
6
+ // \
7
+ Backspace: 'backspace',
8
+ BracketLeft: 'bracketleft',
9
+ // [
10
+ BracketRight: 'bracketright',
11
+ // ]
12
+ Comma: 'comma',
13
+ // ,
14
+ Ctrl: 'ctrl',
15
+ Down: 'down',
16
+ Enter: 'enter',
17
+ Equal: 'equal',
18
+ // =
19
+ Esc: 'esc',
20
+ Left: 'left',
21
+ LeftClick: 'left-click',
22
+ LeftDoubleClick: 'left-double-click',
23
+ Meta: 'meta',
24
+ // Command on Mac, Win on Win
25
+ MiddleClick: 'middle-click',
26
+ Minus: 'minus',
27
+ // -
28
+ Mod: 'mod',
29
+
5
30
  Number: '1-9',
31
+
32
+ // Command on Mac, Ctrl on Win
33
+ Period: 'period',
34
+
35
+ // .
36
+ Plus: 'equal',
37
+
38
+ // +
39
+ QuestionMark: 'slash',
40
+
41
+ // ?
42
+ Quote: 'quote',
43
+ // '
44
+ Right: 'right',
45
+ RightClick: 'right-click',
46
+ RightDoubleClick: 'right-double-click',
47
+
48
+ Semicolon: 'semicolon',
49
+ // ;
50
+ Shift: 'shift',
51
+
52
+ Slash: 'slash',
53
+ // /
54
+ Space: 'space',
55
+ Tab: 'tab',
56
+ Up: 'up',
6
57
  } as const;
7
58
 
8
59
  export const HotkeyEnum = {