@lobehub/chat 0.153.0 → 0.154.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.
Files changed (160) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/docs/self-hosting/advanced/authentication.mdx +25 -18
  3. package/docs/self-hosting/advanced/authentication.zh-CN.mdx +26 -17
  4. package/docs/self-hosting/advanced/model-list.mdx +2 -2
  5. package/docs/self-hosting/advanced/model-list.zh-CN.mdx +2 -2
  6. package/docs/self-hosting/environment-variables/auth.mdx +48 -39
  7. package/docs/self-hosting/environment-variables/auth.zh-CN.mdx +49 -43
  8. package/locales/ar/auth.json +6 -0
  9. package/locales/ar/clerk.json +769 -0
  10. package/locales/ar/common.json +2 -0
  11. package/locales/ar/error.json +8 -0
  12. package/locales/bg-BG/auth.json +6 -0
  13. package/locales/bg-BG/clerk.json +769 -0
  14. package/locales/bg-BG/common.json +2 -0
  15. package/locales/bg-BG/error.json +8 -0
  16. package/locales/de-DE/auth.json +6 -0
  17. package/locales/de-DE/clerk.json +769 -0
  18. package/locales/de-DE/common.json +2 -0
  19. package/locales/de-DE/error.json +8 -0
  20. package/locales/en-US/auth.json +6 -0
  21. package/locales/en-US/clerk.json +769 -0
  22. package/locales/en-US/common.json +2 -0
  23. package/locales/en-US/error.json +8 -0
  24. package/locales/es-ES/auth.json +6 -0
  25. package/locales/es-ES/clerk.json +769 -0
  26. package/locales/es-ES/common.json +2 -0
  27. package/locales/es-ES/error.json +8 -0
  28. package/locales/fr-FR/auth.json +6 -0
  29. package/locales/fr-FR/clerk.json +769 -0
  30. package/locales/fr-FR/common.json +2 -0
  31. package/locales/fr-FR/error.json +8 -0
  32. package/locales/it-IT/auth.json +6 -0
  33. package/locales/it-IT/clerk.json +769 -0
  34. package/locales/it-IT/common.json +2 -0
  35. package/locales/it-IT/error.json +8 -0
  36. package/locales/ja-JP/auth.json +6 -0
  37. package/locales/ja-JP/clerk.json +769 -0
  38. package/locales/ja-JP/common.json +2 -0
  39. package/locales/ja-JP/error.json +8 -0
  40. package/locales/ko-KR/auth.json +6 -0
  41. package/locales/ko-KR/clerk.json +769 -0
  42. package/locales/ko-KR/common.json +2 -0
  43. package/locales/ko-KR/error.json +8 -0
  44. package/locales/nl-NL/auth.json +6 -0
  45. package/locales/nl-NL/clerk.json +769 -0
  46. package/locales/nl-NL/common.json +2 -0
  47. package/locales/nl-NL/error.json +8 -0
  48. package/locales/pl-PL/auth.json +6 -0
  49. package/locales/pl-PL/clerk.json +769 -0
  50. package/locales/pl-PL/common.json +2 -0
  51. package/locales/pl-PL/error.json +8 -0
  52. package/locales/pt-BR/auth.json +6 -0
  53. package/locales/pt-BR/clerk.json +769 -0
  54. package/locales/pt-BR/common.json +2 -0
  55. package/locales/pt-BR/error.json +8 -0
  56. package/locales/ru-RU/auth.json +6 -0
  57. package/locales/ru-RU/clerk.json +769 -0
  58. package/locales/ru-RU/common.json +2 -0
  59. package/locales/ru-RU/error.json +8 -0
  60. package/locales/tr-TR/auth.json +6 -0
  61. package/locales/tr-TR/clerk.json +769 -0
  62. package/locales/tr-TR/common.json +2 -0
  63. package/locales/tr-TR/error.json +8 -0
  64. package/locales/vi-VN/auth.json +6 -0
  65. package/locales/vi-VN/clerk.json +769 -0
  66. package/locales/vi-VN/common.json +2 -0
  67. package/locales/vi-VN/error.json +8 -0
  68. package/locales/zh-CN/auth.json +6 -0
  69. package/locales/zh-CN/clerk.json +769 -0
  70. package/locales/zh-CN/common.json +2 -0
  71. package/locales/zh-CN/error.json +8 -0
  72. package/locales/zh-TW/auth.json +6 -0
  73. package/locales/zh-TW/clerk.json +769 -0
  74. package/locales/zh-TW/common.json +2 -0
  75. package/locales/zh-TW/error.json +8 -0
  76. package/package.json +5 -1
  77. package/src/app/(auth)/layout.tsx +19 -0
  78. package/src/app/(auth)/login/[[...login]]/PageTitle.tsx +13 -0
  79. package/src/app/(auth)/login/[[...login]]/page.tsx +14 -0
  80. package/src/app/(auth)/profile/[[...slugs]]/PageTitle.tsx +13 -0
  81. package/src/app/(auth)/profile/[[...slugs]]/page.tsx +14 -0
  82. package/src/app/(auth)/signup/[[...signup]]/PageTitle.tsx +13 -0
  83. package/src/app/(auth)/signup/[[...signup]]/page.tsx +14 -0
  84. package/src/app/(main)/(mobile)/me/features/AvatarBanner.tsx +12 -40
  85. package/src/app/(main)/(mobile)/me/features/Header.tsx +37 -0
  86. package/src/app/(main)/(mobile)/me/features/style.ts +29 -0
  87. package/src/app/(main)/(mobile)/me/layout.tsx +7 -1
  88. package/src/app/(main)/(mobile)/me/loading.tsx +22 -7
  89. package/src/app/(main)/(mobile)/me/page.tsx +4 -5
  90. package/src/app/(main)/@nav/_layout/Mobile.tsx +2 -2
  91. package/src/app/(main)/market/@detail/features/{AgentDetailContent/index.tsx → AgentDetailContent.tsx} +6 -12
  92. package/src/app/(main)/market/@detail/features/Banner.tsx +46 -0
  93. package/src/app/(main)/market/@detail/features/{AgentDetailContent/Header.tsx → Header.tsx} +4 -18
  94. package/src/app/(main)/market/@detail/features/{AgentDetailContent/Loading.tsx → Loading.tsx} +5 -4
  95. package/src/app/(main)/market/@detail/features/{AgentDetailContent/style.ts → style.ts} +0 -3
  96. package/src/app/(main)/market/features/AgentCard/AgentCardBanner.tsx +13 -8
  97. package/src/app/(main)/market/loading.tsx +13 -1
  98. package/src/app/(main)/settings/common/features/Common.tsx +1 -1
  99. package/src/app/(main)/settings/common/features/Theme/index.tsx +2 -0
  100. package/src/app/(main)/settings/common/index.tsx +7 -3
  101. package/src/app/api/chat/[provider]/route.test.ts +76 -3
  102. package/src/app/api/chat/auth/index.test.ts +77 -0
  103. package/src/app/api/chat/auth/index.ts +19 -3
  104. package/src/app/api/chat/auth/utils.ts +31 -9
  105. package/src/app/api/plugin/gateway/route.ts +3 -3
  106. package/src/components/Cell/Divider.tsx +2 -0
  107. package/src/components/Cell/index.tsx +5 -1
  108. package/src/components/server/MobileNavLayout.tsx +1 -0
  109. package/src/config/auth.ts +146 -0
  110. package/src/config/server/index.ts +1 -3
  111. package/src/const/auth.ts +7 -0
  112. package/src/features/Conversation/Error/ClerkLogin/index.tsx +47 -0
  113. package/src/features/Conversation/Error/index.tsx +5 -0
  114. package/src/features/Conversation/components/InboxWelcome/AgentsSuggest.tsx +15 -6
  115. package/src/features/Conversation/components/InboxWelcome/QuestionSuggest.tsx +9 -4
  116. package/src/features/Conversation/components/InboxWelcome/index.tsx +9 -20
  117. package/src/features/DataImporter/index.tsx +4 -1
  118. package/src/features/User/PlanTag.tsx +45 -0
  119. package/src/features/User/UserAvatar.tsx +11 -5
  120. package/src/features/User/UserInfo.tsx +30 -12
  121. package/src/features/User/UserLoginOrSignup.tsx +23 -0
  122. package/src/features/User/UserPanel/PanelContent.tsx +74 -0
  123. package/src/features/User/UserPanel/UpgradeBadge.tsx +19 -0
  124. package/src/features/User/UserPanel/index.tsx +8 -27
  125. package/src/features/User/UserPanel/useMenu.tsx +49 -21
  126. package/src/features/User/__tests__/PanelContent.test.tsx +151 -0
  127. package/src/features/User/__tests__/UserAvatar.test.tsx +78 -0
  128. package/src/hooks/useGreeting/greetingTime.ts +14 -0
  129. package/src/hooks/useGreeting/index.ts +16 -0
  130. package/src/layout/AuthProvider/Clerk/UserUpdater.tsx +41 -0
  131. package/src/layout/AuthProvider/Clerk/index.tsx +26 -0
  132. package/src/layout/AuthProvider/Clerk/useAppearance.ts +118 -0
  133. package/src/layout/AuthProvider/index.tsx +8 -4
  134. package/src/libs/next-auth/index.ts +4 -6
  135. package/src/libs/next-auth/sso-providers/auth0.ts +4 -6
  136. package/src/libs/next-auth/sso-providers/authentik.ts +4 -6
  137. package/src/libs/next-auth/sso-providers/azure-ad.ts +4 -6
  138. package/src/libs/next-auth/sso-providers/github.ts +3 -5
  139. package/src/libs/next-auth/sso-providers/zitadel.ts +4 -6
  140. package/src/locales/default/auth.ts +6 -0
  141. package/src/locales/default/clerk.ts +782 -0
  142. package/src/locales/default/common.ts +4 -1
  143. package/src/locales/default/error.ts +8 -0
  144. package/src/locales/default/index.ts +4 -0
  145. package/src/middleware.ts +19 -6
  146. package/src/server/globalConfig/index.ts +2 -2
  147. package/src/store/user/selectors.ts +1 -1
  148. package/src/store/user/slices/auth/action.test.ts +105 -0
  149. package/src/store/user/slices/auth/action.ts +40 -5
  150. package/src/store/user/slices/auth/initialState.ts +15 -0
  151. package/src/store/user/slices/auth/selectors.test.ts +127 -0
  152. package/src/store/user/slices/auth/selectors.ts +38 -1
  153. package/src/store/user/slices/settings/selectors/selectors.test.ts +25 -0
  154. package/src/store/user/slices/settings/selectors/settings.ts +6 -0
  155. package/src/styles/mobileHeader.ts +7 -0
  156. package/src/types/fetch.ts +1 -0
  157. package/src/config/server/auth.ts +0 -71
  158. package/src/features/User/UserPanel/Popover.tsx +0 -35
  159. /package/src/app/(main)/market/@detail/features/{AgentDetailContent/Comment.tsx → Comment.tsx} +0 -0
  160. /package/src/app/(main)/market/@detail/features/{AgentDetailContent/TokenTag.tsx → TokenTag.tsx} +0 -0
@@ -1,3 +1,15 @@
1
+ 'use client';
2
+
1
3
  import { Skeleton } from 'antd';
4
+ import { Flexbox } from 'react-layout-kit';
2
5
 
3
- export default () => <Skeleton paragraph={{ rows: 8 }} style={{ marginBlock: 24 }} title={false} />;
6
+ export default () => (
7
+ <Flexbox gap={16}>
8
+ <Skeleton.Input active block />
9
+ <Skeleton paragraph={{ rows: 8 }} style={{ marginBlock: 24 }} title={false} />
10
+ <Skeleton.Button active />
11
+ <Skeleton paragraph={{ rows: 8 }} style={{ marginBlock: 24 }} title={false} />
12
+ <Skeleton.Button active />
13
+ <Skeleton paragraph={{ rows: 8 }} style={{ marginBlock: 24 }} title={false} />
14
+ </Flexbox>
15
+ );
@@ -21,7 +21,7 @@ type SettingItemGroup = ItemGroup;
21
21
 
22
22
  export interface SettingsCommonProps {
23
23
  showAccessCodeConfig: boolean;
24
- showOAuthLogin: boolean;
24
+ showOAuthLogin?: boolean;
25
25
  }
26
26
 
27
27
  const Common = memo<SettingsCommonProps>(({ showAccessCodeConfig, showOAuthLogin }) => {
@@ -8,6 +8,7 @@ import { memo } from 'react';
8
8
  import { useTranslation } from 'react-i18next';
9
9
 
10
10
  import { useSyncSettings } from '@/app/(main)/settings/hooks/useSyncSettings';
11
+ import { enableAuth } from '@/const/auth';
11
12
  import { FORM_STYLE } from '@/const/layoutTokens';
12
13
  import { imageUrl } from '@/const/url';
13
14
  import AvatarWithUpload from '@/features/AvatarWithUpload';
@@ -32,6 +33,7 @@ const Theme = memo(() => {
32
33
  children: [
33
34
  {
34
35
  children: <AvatarWithUpload />,
36
+ hidden: enableAuth,
35
37
  label: t('settingTheme.avatar.title'),
36
38
  minWidth: undefined,
37
39
  },
@@ -1,15 +1,19 @@
1
+ import { authEnv } from '@/config/auth';
1
2
  import { getServerConfig } from '@/config/server';
2
3
 
3
4
  import Common from './features/Common';
4
5
  import Theme from './features/Theme';
5
6
 
6
- const { SHOW_ACCESS_CODE_CONFIG, ENABLE_OAUTH_SSO } = getServerConfig();
7
-
8
7
  const Page = () => {
8
+ const { SHOW_ACCESS_CODE_CONFIG } = getServerConfig();
9
+
9
10
  return (
10
11
  <>
11
12
  <Theme />
12
- <Common showAccessCodeConfig={SHOW_ACCESS_CODE_CONFIG} showOAuthLogin={ENABLE_OAUTH_SSO} />
13
+ <Common
14
+ showAccessCodeConfig={SHOW_ACCESS_CODE_CONFIG}
15
+ showOAuthLogin={authEnv.NEXT_PUBLIC_ENABLE_NEXT_AUTH}
16
+ />
13
17
  </>
14
18
  );
15
19
  };
@@ -1,18 +1,37 @@
1
1
  // @vitest-environment edge-runtime
2
+ import { getAuth } from '@clerk/nextjs/server';
2
3
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
4
 
4
5
  import { LOBE_CHAT_AUTH_HEADER, OAUTH_AUTHORIZED } from '@/const/auth';
5
6
  import { AgentRuntime, LobeRuntimeAI } from '@/libs/agent-runtime';
6
7
  import { ChatErrorType } from '@/types/fetch';
7
8
 
8
- import { getJWTPayload } from '../auth/utils';
9
+ import { checkAuthMethod, getJWTPayload } from '../auth/utils';
9
10
  import { POST } from './route';
10
11
 
12
+ vi.mock('@clerk/nextjs/server', () => ({
13
+ getAuth: vi.fn(),
14
+ }));
15
+
11
16
  vi.mock('../auth/utils', () => ({
12
17
  getJWTPayload: vi.fn(),
13
18
  checkAuthMethod: vi.fn(),
14
19
  }));
15
20
 
21
+ // 定义一个变量来存储 enableAuth 的值
22
+ let enableClerk = false;
23
+
24
+ // 模拟 @/const/auth 模块
25
+ vi.mock('@/const/auth', async (importOriginal) => {
26
+ const modules = await importOriginal();
27
+ return {
28
+ ...(modules as any),
29
+ get enableClerk() {
30
+ return enableClerk;
31
+ },
32
+ };
33
+ });
34
+
16
35
  // 模拟请求和响应
17
36
  let request: Request;
18
37
  beforeEach(() => {
@@ -29,15 +48,16 @@ beforeEach(() => {
29
48
  afterEach(() => {
30
49
  // 清除模拟调用历史
31
50
  vi.clearAllMocks();
51
+ enableClerk = false;
32
52
  });
33
53
 
34
54
  describe('POST handler', () => {
35
- describe(' init chat model', () => {
55
+ describe('init chat model', () => {
36
56
  it('should initialize AgentRuntime correctly with valid authorization', async () => {
37
57
  const mockParams = { provider: 'test-provider' };
38
58
 
39
59
  // 设置 getJWTPayload 和 initAgentRuntimeWithUserPayload 的模拟返回值
40
- vi.mocked(getJWTPayload).mockResolvedValue({
60
+ vi.mocked(getJWTPayload).mockResolvedValueOnce({
41
61
  accessCode: 'test-access-code',
42
62
  apiKey: 'test-api-key',
43
63
  azureApiVersion: 'v1',
@@ -76,6 +96,46 @@ describe('POST handler', () => {
76
96
  errorType: 401,
77
97
  });
78
98
  });
99
+
100
+ it('should have pass clerk Auth when enable clerk', async () => {
101
+ enableClerk = true;
102
+
103
+ vi.mocked(getJWTPayload).mockResolvedValueOnce({
104
+ accessCode: 'test-access-code',
105
+ apiKey: 'test-api-key',
106
+ azureApiVersion: 'v1',
107
+ });
108
+
109
+ const mockParams = { provider: 'test-provider' };
110
+ // 设置 initAgentRuntimeWithUserPayload 的模拟返回值
111
+ vi.mocked(getAuth).mockReturnValue({} as any);
112
+ vi.mocked(checkAuthMethod).mockReset();
113
+
114
+ const mockRuntime: LobeRuntimeAI = { baseURL: 'abc', chat: vi.fn() };
115
+
116
+ vi.spyOn(AgentRuntime, 'initializeWithProviderOptions').mockResolvedValue(
117
+ new AgentRuntime(mockRuntime),
118
+ );
119
+
120
+ const request = new Request(new URL('https://test.com'), {
121
+ method: 'POST',
122
+ body: JSON.stringify({ model: 'test-model' }),
123
+ headers: {
124
+ [LOBE_CHAT_AUTH_HEADER]: 'some-valid-token',
125
+ [OAUTH_AUTHORIZED]: '1',
126
+ },
127
+ });
128
+
129
+ await POST(request, { params: mockParams });
130
+
131
+ expect(checkAuthMethod).toBeCalledWith({
132
+ accessCode: 'test-access-code',
133
+ apiKey: 'test-api-key',
134
+ clerkAuth: {},
135
+ nextAuthAuthorized: true,
136
+ });
137
+ });
138
+
79
139
  it('should return InternalServerError error when throw a unknown error', async () => {
80
140
  const mockParams = { provider: 'test-provider' };
81
141
  vi.mocked(getJWTPayload).mockRejectedValueOnce(new Error('unknown error'));
@@ -95,6 +155,12 @@ describe('POST handler', () => {
95
155
 
96
156
  describe('chat', () => {
97
157
  it('should correctly handle chat completion with valid payload', async () => {
158
+ vi.mocked(getJWTPayload).mockResolvedValueOnce({
159
+ accessCode: 'test-access-code',
160
+ apiKey: 'test-api-key',
161
+ azureApiVersion: 'v1',
162
+ });
163
+
98
164
  const mockParams = { provider: 'test-provider' };
99
165
  const mockChatPayload = { message: 'Hello, world!' };
100
166
  request = new Request(new URL('https://test.com'), {
@@ -114,6 +180,13 @@ describe('POST handler', () => {
114
180
  });
115
181
 
116
182
  it('should return an error response when chat completion fails', async () => {
183
+ // 设置 getJWTPayload 和 initAgentRuntimeWithUserPayload 的模拟返回值
184
+ vi.mocked(getJWTPayload).mockResolvedValueOnce({
185
+ accessCode: 'test-access-code',
186
+ apiKey: 'test-api-key',
187
+ azureApiVersion: 'v1',
188
+ });
189
+
117
190
  const mockParams = { provider: 'test-provider' };
118
191
  const mockChatPayload = { message: 'Hello, world!' };
119
192
  request = new Request(new URL('https://test.com'), {
@@ -0,0 +1,77 @@
1
+ import { getAuth } from '@clerk/nextjs/server';
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+
4
+ import { createErrorResponse } from '@/app/api/errorResponse';
5
+ import { AgentRuntimeError } from '@/libs/agent-runtime';
6
+ import { ChatErrorType } from '@/types/fetch';
7
+
8
+ import { RequestHandler, checkAuth } from './index';
9
+ import { checkAuthMethod, getJWTPayload } from './utils';
10
+
11
+ vi.mock('@clerk/nextjs/server', () => ({
12
+ getAuth: vi.fn(),
13
+ }));
14
+
15
+ vi.mock('@/app/api/errorResponse', () => ({
16
+ createErrorResponse: vi.fn(),
17
+ }));
18
+
19
+ vi.mock('./utils', () => ({
20
+ checkAuthMethod: vi.fn(),
21
+ getJWTPayload: vi.fn(),
22
+ }));
23
+
24
+ describe('checkAuth', () => {
25
+ const mockHandler: RequestHandler = vi.fn();
26
+ const mockRequest = new Request('https://example.com');
27
+ const mockOptions = { params: { provider: 'mock' } };
28
+
29
+ beforeEach(() => {
30
+ vi.clearAllMocks();
31
+ });
32
+
33
+ afterEach(() => {
34
+ vi.resetAllMocks();
35
+ });
36
+
37
+ it('should return unauthorized error if no authorization header', async () => {
38
+ await checkAuth(mockHandler)(mockRequest, mockOptions);
39
+
40
+ expect(createErrorResponse).toHaveBeenCalledWith(ChatErrorType.Unauthorized, {
41
+ error: AgentRuntimeError.createError(ChatErrorType.Unauthorized),
42
+ provider: 'mock',
43
+ });
44
+ expect(mockHandler).not.toHaveBeenCalled();
45
+ });
46
+
47
+ it('should return error response on getJWTPayload error', async () => {
48
+ const mockError = AgentRuntimeError.createError(ChatErrorType.Unauthorized);
49
+ mockRequest.headers.set('Authorization', 'invalid');
50
+ vi.mocked(getJWTPayload).mockRejectedValueOnce(mockError);
51
+
52
+ await checkAuth(mockHandler)(mockRequest, mockOptions);
53
+
54
+ expect(createErrorResponse).toHaveBeenCalledWith(ChatErrorType.Unauthorized, {
55
+ error: mockError,
56
+ provider: 'mock',
57
+ });
58
+ expect(mockHandler).not.toHaveBeenCalled();
59
+ });
60
+
61
+ it('should return error response on checkAuthMethod error', async () => {
62
+ const mockError = AgentRuntimeError.createError(ChatErrorType.Unauthorized);
63
+ mockRequest.headers.set('Authorization', 'valid');
64
+ vi.mocked(getJWTPayload).mockResolvedValueOnce({});
65
+ vi.mocked(checkAuthMethod).mockImplementationOnce(() => {
66
+ throw mockError;
67
+ });
68
+
69
+ await checkAuth(mockHandler)(mockRequest, mockOptions);
70
+
71
+ expect(createErrorResponse).toHaveBeenCalledWith(ChatErrorType.Unauthorized, {
72
+ error: mockError,
73
+ provider: 'mock',
74
+ });
75
+ expect(mockHandler).not.toHaveBeenCalled();
76
+ });
77
+ });
@@ -1,5 +1,9 @@
1
+ import { AuthObject } from '@clerk/backend/internal';
2
+ import { getAuth } from '@clerk/nextjs/server';
3
+ import { NextRequest } from 'next/server';
4
+
1
5
  import { createErrorResponse } from '@/app/api/errorResponse';
2
- import { JWTPayload, LOBE_CHAT_AUTH_HEADER, OAUTH_AUTHORIZED } from '@/const/auth';
6
+ import { JWTPayload, LOBE_CHAT_AUTH_HEADER, OAUTH_AUTHORIZED, enableClerk } from '@/const/auth';
3
7
  import { AgentRuntimeError, ChatCompletionErrorPayload } from '@/libs/agent-runtime';
4
8
  import { ChatErrorType } from '@/types/fetch';
5
9
 
@@ -23,9 +27,21 @@ export const checkAuth =
23
27
 
24
28
  if (!authorization) throw AgentRuntimeError.createError(ChatErrorType.Unauthorized);
25
29
 
26
- // check the Auth With payload
30
+ // check the Auth With payload and clerk auth
31
+ let clerkAuth = {} as AuthObject;
32
+
33
+ if (enableClerk) {
34
+ clerkAuth = getAuth(req as NextRequest);
35
+ }
36
+
27
37
  jwtPayload = await getJWTPayload(authorization);
28
- checkAuthMethod(jwtPayload.accessCode, jwtPayload.apiKey, oauthAuthorized);
38
+
39
+ checkAuthMethod({
40
+ accessCode: jwtPayload.accessCode,
41
+ apiKey: jwtPayload.apiKey,
42
+ clerkAuth,
43
+ nextAuthAuthorized: oauthAuthorized,
44
+ });
29
45
  } catch (e) {
30
46
  const {
31
47
  errorType = ChatErrorType.InternalServerError,
@@ -1,7 +1,14 @@
1
+ import { type AuthObject } from '@clerk/backend/internal';
1
2
  import { importJWK, jwtVerify } from 'jose';
2
3
 
3
4
  import { getServerConfig } from '@/config/server';
4
- import { JWTPayload, JWT_SECRET_KEY, NON_HTTP_PREFIX } from '@/const/auth';
5
+ import {
6
+ JWTPayload,
7
+ JWT_SECRET_KEY,
8
+ NON_HTTP_PREFIX,
9
+ enableClerk,
10
+ enableNextAuth,
11
+ } from '@/const/auth';
5
12
  import { AgentRuntimeError } from '@/libs/agent-runtime';
6
13
  import { ChatErrorType } from '@/types/fetch';
7
14
 
@@ -30,6 +37,12 @@ export const getJWTPayload = async (token: string): Promise<JWTPayload> => {
30
37
  return payload as JWTPayload;
31
38
  };
32
39
 
40
+ interface CheckAuthParams {
41
+ accessCode?: string;
42
+ apiKey?: string;
43
+ clerkAuth: AuthObject;
44
+ nextAuthAuthorized?: boolean;
45
+ }
33
46
  /**
34
47
  * Check if the provided access code is valid, a user API key should be used or the OAuth 2 header is provided.
35
48
  *
@@ -38,19 +51,28 @@ export const getJWTPayload = async (token: string): Promise<JWTPayload> => {
38
51
  * @param {boolean} oauthAuthorized - Whether the OAuth 2 header is provided.
39
52
  * @throws {AgentRuntimeError} If the access code is invalid and no user API key is provided.
40
53
  */
41
- export const checkAuthMethod = (
42
- accessCode?: string,
43
- apiKey?: string,
44
- oauthAuthorized?: boolean,
45
- ) => {
46
- const { ACCESS_CODES, ENABLE_OAUTH_SSO } = getServerConfig();
54
+ export const checkAuthMethod = ({
55
+ apiKey,
56
+ nextAuthAuthorized,
57
+ accessCode,
58
+ clerkAuth,
59
+ }: CheckAuthParams) => {
60
+ // clerk auth handler
61
+ if (enableClerk) {
62
+ // if there is no userId, means the use is not login, just throw error
63
+ if (!clerkAuth?.userId) throw AgentRuntimeError.createError(ChatErrorType.InvalidClerkUser);
64
+ // if the user is login, just return
65
+ else return;
66
+ }
47
67
 
48
- // if OAuth 2 header is provided
49
- if (ENABLE_OAUTH_SSO && oauthAuthorized) return;
68
+ // if next auth handler is provided
69
+ if (enableNextAuth && nextAuthAuthorized) return;
50
70
 
51
71
  // if apiKey exist
52
72
  if (apiKey) return;
53
73
 
74
+ const { ACCESS_CODES } = getServerConfig();
75
+
54
76
  // if accessCode doesn't exist
55
77
  if (!ACCESS_CODES.length) return;
56
78
 
@@ -4,7 +4,7 @@ import { createGatewayOnEdgeRuntime } from '@lobehub/chat-plugins-gateway';
4
4
  import { getJWTPayload } from '@/app/api/chat/auth/utils';
5
5
  import { createErrorResponse } from '@/app/api/errorResponse';
6
6
  import { getServerConfig } from '@/config/server';
7
- import { LOBE_CHAT_AUTH_HEADER, OAUTH_AUTHORIZED } from '@/const/auth';
7
+ import { LOBE_CHAT_AUTH_HEADER, OAUTH_AUTHORIZED, enableNextAuth } from '@/const/auth';
8
8
  import { LOBE_CHAT_TRACE_ID, TraceNameMap } from '@/const/trace';
9
9
  import { AgentRuntimeError } from '@/libs/agent-runtime';
10
10
  import { TraceClient } from '@/libs/traces';
@@ -14,13 +14,13 @@ import { getTracePayload } from '@/utils/trace';
14
14
  import { parserPluginSettings } from './settings';
15
15
 
16
16
  const checkAuth = (accessCode: string | null, oauthAuthorized: boolean | null) => {
17
- const { ACCESS_CODES, PLUGIN_SETTINGS, ENABLE_OAUTH_SSO } = getServerConfig();
17
+ const { ACCESS_CODES, PLUGIN_SETTINGS } = getServerConfig();
18
18
 
19
19
  // if there is no plugin settings, just skip the auth
20
20
  if (!PLUGIN_SETTINGS) return { auth: true };
21
21
 
22
22
  // If authorized by oauth
23
- if (oauthAuthorized && ENABLE_OAUTH_SSO) return { auth: true };
23
+ if (oauthAuthorized && enableNextAuth) return { auth: true };
24
24
 
25
25
  // if accessCode doesn't exist
26
26
  if (!ACCESS_CODES.length) return { auth: true };
@@ -5,6 +5,8 @@ import { memo } from 'react';
5
5
 
6
6
  const useStyles = createStyles(
7
7
  ({ css, token, isDarkMode }) => css`
8
+ flex: none;
9
+ width: 100%;
8
10
  height: 6px;
9
11
  background: ${isDarkMode ? token.colorBgContainer : token.colorBgLayout};
10
12
  `,
@@ -8,7 +8,11 @@ const { Item } = List;
8
8
  const useStyles = createStyles(({ css, token, isDarkMode }) => ({
9
9
  container: css`
10
10
  position: relative;
11
- padding-block: 16px !important;
11
+
12
+ gap: 12px;
13
+
14
+ padding: 16px !important;
15
+
12
16
  background: ${isDarkMode ? token.colorBgLayout : token.colorBgContainer};
13
17
  border-radius: 0;
14
18
  `,
@@ -38,6 +38,7 @@ const MobileContentLayout = ({
38
38
  {header}
39
39
  <Flexbox
40
40
  height="100%"
41
+ id={'lobe-mobile-scroll-container'}
41
42
  style={{
42
43
  overflowX: 'hidden',
43
44
  overflowY: 'auto',
@@ -0,0 +1,146 @@
1
+ /* eslint-disable sort-keys-fix/sort-keys-fix , typescript-sort-keys/interface */
2
+ import { createEnv } from '@t3-oss/env-nextjs';
3
+ import { z } from 'zod';
4
+
5
+ declare global {
6
+ // eslint-disable-next-line @typescript-eslint/no-namespace
7
+ namespace NodeJS {
8
+ interface ProcessEnv {
9
+ // ===== Clerk ===== //
10
+ NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY?: string;
11
+ CLERK_SECRET_KEY?: string;
12
+ CLERK_WEBHOOK_SECRET?: string;
13
+
14
+ // ===== Next Auth ===== //
15
+ /**
16
+ * @deprecated
17
+ */
18
+ ENABLE_OAUTH_SSO?: string;
19
+ NEXT_AUTH_SECRET?: string;
20
+ /**
21
+ * @deprecated
22
+ */
23
+ SSO_PROVIDERS?: string;
24
+ NEXT_AUTH_SSO_PROVIDERS?: string;
25
+
26
+ AUTH0_CLIENT_ID?: string;
27
+ AUTH0_CLIENT_SECRET?: string;
28
+ AUTH0_ISSUER?: string;
29
+
30
+ // Github
31
+ GITHUB_CLIENT_ID?: string;
32
+ GITHUB_CLIENT_SECRET?: string;
33
+
34
+ // Azure AD
35
+ AZURE_AD_CLIENT_ID?: string;
36
+ AZURE_AD_CLIENT_SECRET?: string;
37
+ AZURE_AD_TENANT_ID?: string;
38
+
39
+ // AUTHENTIK
40
+ AUTHENTIK_CLIENT_ID?: string;
41
+ AUTHENTIK_CLIENT_SECRET?: string;
42
+ AUTHENTIK_ISSUER?: string;
43
+
44
+ // ZITADEL
45
+ ZITADEL_CLIENT_ID?: string;
46
+ ZITADEL_CLIENT_SECRET?: string;
47
+ ZITADEL_ISSUER?: string;
48
+ }
49
+ }
50
+ }
51
+
52
+ export const getAuthConfig = () => {
53
+ if (process.env.ENABLE_OAUTH_SSO) {
54
+ console.warn(
55
+ '`ENABLE_OAUTH_SSO` is deprecated and will be removed in LobeChat 1.0. just set `NEXT_AUTH_SECRET` enough',
56
+ );
57
+ }
58
+
59
+ if (process.env.SSO_PROVIDERS) {
60
+ console.warn(
61
+ '`SSO_PROVIDERS` is deprecated and will be removed in LobeChat 1.0. Please replace with `NEXT_AUTH_SSO_PROVIDERS`',
62
+ );
63
+ }
64
+
65
+ return createEnv({
66
+ client: {
67
+ NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().optional(),
68
+ /**
69
+ * whether to enabled clerk
70
+ */
71
+ NEXT_PUBLIC_ENABLE_CLERK_AUTH: z.boolean().optional(),
72
+
73
+ NEXT_PUBLIC_ENABLE_NEXT_AUTH: z.boolean().optional(),
74
+ },
75
+ server: {
76
+ // Clerk
77
+ CLERK_SECRET_KEY: z.string().optional(),
78
+
79
+ // NEXT-AUTH
80
+ NEXT_AUTH_SECRET: z.string().optional(),
81
+ NEXT_AUTH_SSO_PROVIDERS: z.string().optional().default('auth0'),
82
+
83
+ // Auth0
84
+ AUTH0_CLIENT_ID: z.string().optional(),
85
+ AUTH0_CLIENT_SECRET: z.string().optional(),
86
+ AUTH0_ISSUER: z.string().optional(),
87
+
88
+ // Github
89
+ GITHUB_CLIENT_ID: z.string().optional(),
90
+ GITHUB_CLIENT_SECRET: z.string().optional(),
91
+
92
+ // Azure AD
93
+ AZURE_AD_CLIENT_ID: z.string().optional(),
94
+ AZURE_AD_CLIENT_SECRET: z.string().optional(),
95
+ AZURE_AD_TENANT_ID: z.string().optional(),
96
+
97
+ // AUTHENTIK
98
+ AUTHENTIK_CLIENT_ID: z.string().optional(),
99
+ AUTHENTIK_CLIENT_SECRET: z.string().optional(),
100
+ AUTHENTIK_ISSUER: z.string().optional(),
101
+
102
+ // ZITADEL
103
+ ZITADEL_CLIENT_ID: z.string().optional(),
104
+ ZITADEL_CLIENT_SECRET: z.string().optional(),
105
+ ZITADEL_ISSUER: z.string().optional(),
106
+ },
107
+
108
+ runtimeEnv: {
109
+ // Clerk
110
+ NEXT_PUBLIC_ENABLE_CLERK_AUTH: !!process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
111
+ NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
112
+ CLERK_SECRET_KEY: process.env.CLERK_SECRET_KEY,
113
+
114
+ // Next Auth
115
+ NEXT_PUBLIC_ENABLE_NEXT_AUTH: !!process.env.NEXT_AUTH_SECRET || process.env.ENABLE_OAUTH_SSO,
116
+ NEXT_AUTH_SSO_PROVIDERS: process.env.NEXT_AUTH_SSO_PROVIDERS || process.env.SSO_PROVIDERS,
117
+ NEXT_AUTH_SECRET: process.env.NEXT_AUTH_SECRET,
118
+
119
+ // Auth0
120
+ AUTH0_CLIENT_ID: process.env.AUTH0_CLIENT_ID,
121
+ AUTH0_CLIENT_SECRET: process.env.AUTH0_CLIENT_SECRET,
122
+ AUTH0_ISSUER: process.env.AUTH0_ISSUER,
123
+
124
+ // Github
125
+ GITHUB_CLIENT_ID: process.env.GITHUB_CLIENT_ID,
126
+ GITHUB_CLIENT_SECRET: process.env.GITHUB_CLIENT_SECRET,
127
+
128
+ // Azure AD
129
+ AZURE_AD_CLIENT_ID: process.env.AZURE_AD_CLIENT_ID,
130
+ AZURE_AD_CLIENT_SECRET: process.env.AZURE_AD_CLIENT_SECRET,
131
+ AZURE_AD_TENANT_ID: process.env.AZURE_AD_TENANT_ID,
132
+
133
+ // AUTHENTIK
134
+ AUTHENTIK_CLIENT_ID: process.env.AUTHENTIK_CLIENT_ID,
135
+ AUTHENTIK_CLIENT_SECRET: process.env.AUTHENTIK_CLIENT_SECRET,
136
+ AUTHENTIK_ISSUER: process.env.AUTHENTIK_ISSUER,
137
+
138
+ // ZITADEL
139
+ ZITADEL_CLIENT_ID: process.env.ZITADEL_CLIENT_ID,
140
+ ZITADEL_CLIENT_SECRET: process.env.ZITADEL_CLIENT_SECRET,
141
+ ZITADEL_ISSUER: process.env.ZITADEL_ISSUER,
142
+ },
143
+ });
144
+ };
145
+
146
+ export const authEnv = getAuthConfig();
@@ -1,6 +1,5 @@
1
1
  import { getAnalyticsConfig } from './analytics';
2
2
  import { getAppConfig } from './app';
3
- import { getAuthConfig } from './auth';
4
3
  import { getProviderConfig } from './provider';
5
4
 
6
5
  export const getServerConfig = () => {
@@ -10,8 +9,7 @@ export const getServerConfig = () => {
10
9
 
11
10
  const provider = getProviderConfig();
12
11
  const app = getAppConfig();
13
- const auth = getAuthConfig();
14
12
  const analytics = getAnalyticsConfig();
15
13
 
16
- return { ...provider, ...app, ...analytics, ...auth };
14
+ return { ...provider, ...app, ...analytics };
17
15
  };
package/src/const/auth.ts CHANGED
@@ -1,3 +1,10 @@
1
+ import { authEnv } from '@/config/auth';
2
+
3
+ export const enableClerk = authEnv.NEXT_PUBLIC_ENABLE_CLERK_AUTH;
4
+ export const enableNextAuth = authEnv.NEXT_PUBLIC_ENABLE_NEXT_AUTH;
5
+ export const enableAuth =
6
+ authEnv.NEXT_PUBLIC_ENABLE_CLERK_AUTH || authEnv.NEXT_PUBLIC_ENABLE_NEXT_AUTH;
7
+
1
8
  export const LOBE_CHAT_AUTH_HEADER = 'X-lobe-chat-auth';
2
9
 
3
10
  export const OAUTH_AUTHORIZED = 'X-oauth-authorized';
@@ -0,0 +1,47 @@
1
+ import { Button } from 'antd';
2
+ import { memo } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+
5
+ import UserLoginOrSignup from '@/features/User/UserLoginOrSignup';
6
+ import { useGreeting } from '@/hooks/useGreeting';
7
+ import { useChatStore } from '@/store/chat';
8
+ import { useUserStore } from '@/store/user';
9
+ import { userProfileSelectors } from '@/store/user/selectors';
10
+
11
+ import { ErrorActionContainer, FormAction } from '../style';
12
+
13
+ const ClerkLogin = memo<{ id: string }>(({ id }) => {
14
+ const { t } = useTranslation('error');
15
+ const [openSignIn, isSignedIn] = useUserStore((s) => [s.openLogin, s.isSignedIn]);
16
+ const greeting = useGreeting();
17
+ const nickName = useUserStore(userProfileSelectors.nickName);
18
+ const [resend, deleteMessage] = useChatStore((s) => [s.internalResendMessage, s.deleteMessage]);
19
+
20
+ return (
21
+ <ErrorActionContainer>
22
+ {isSignedIn ? (
23
+ <FormAction
24
+ avatar={'🌟'}
25
+ description={t('clerkAuth.loginSuccess.desc', { greeting })}
26
+ title={t('clerkAuth.loginSuccess.title', { nickName })}
27
+ >
28
+ <Button
29
+ block
30
+ onClick={() => {
31
+ resend(id);
32
+ deleteMessage(id);
33
+ }}
34
+ size={'large'}
35
+ type={'primary'}
36
+ >
37
+ {t('clerkAuth.loginSuccess.action')}
38
+ </Button>
39
+ </FormAction>
40
+ ) : (
41
+ <UserLoginOrSignup onClick={openSignIn} />
42
+ )}
43
+ </ErrorActionContainer>
44
+ );
45
+ });
46
+
47
+ export default ClerkLogin;
@@ -6,6 +6,7 @@ import { AgentRuntimeErrorType, ILobeAgentRuntimeErrorType } from '@/libs/agent-
6
6
  import { ChatErrorType, ErrorType } from '@/types/fetch';
7
7
  import { ChatMessage, ChatMessageError } from '@/types/message';
8
8
 
9
+ import ClerkLogin from './ClerkLogin';
9
10
  import ErrorJsonViewer from './ErrorJsonViewer';
10
11
  import InvalidAPIKey from './InvalidAPIKey';
11
12
  import InvalidAccessCode from './InvalidAccessCode';
@@ -63,6 +64,10 @@ const ErrorMessageExtra = memo<{ data: ChatMessage }>(({ data }) => {
63
64
  return <OllamaBizError {...data} />;
64
65
  }
65
66
 
67
+ case ChatErrorType.InvalidClerkUser: {
68
+ return <ClerkLogin id={data.id} />;
69
+ }
70
+
66
71
  case ChatErrorType.InvalidAccessCode: {
67
72
  return <InvalidAccessCode id={data.id} provider={data.error?.body?.provider} />;
68
73
  }