@lobehub/chat 0.159.0 → 0.159.2

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 CHANGED
@@ -2,6 +2,64 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 0.159.2](https://github.com/lobehub/lobe-chat/compare/v0.159.1...v0.159.2)
6
+
7
+ <sup>Released on **2024-05-14**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Dragging text mistakenly as image.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: Dragging text mistakenly as image, closes [#2111](https://github.com/lobehub/lobe-chat/issues/2111) ([3c047ef](https://github.com/lobehub/lobe-chat/commit/3c047ef))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ### [Version 0.159.1](https://github.com/lobehub/lobe-chat/compare/v0.159.0...v0.159.1)
31
+
32
+ <sup>Released on **2024-05-14**</sup>
33
+
34
+ #### ♻ Code Refactoring
35
+
36
+ - **misc**: Move next-auth hooks to user store actions.
37
+
38
+ #### 🐛 Bug Fixes
39
+
40
+ - **misc**: Pin `antd@5.17.0` to fix build error.
41
+
42
+ <br/>
43
+
44
+ <details>
45
+ <summary><kbd>Improvements and Fixes</kbd></summary>
46
+
47
+ #### Code refactoring
48
+
49
+ - **misc**: Move next-auth hooks to user store actions, closes [#2364](https://github.com/lobehub/lobe-chat/issues/2364) ([6dbcd70](https://github.com/lobehub/lobe-chat/commit/6dbcd70))
50
+
51
+ #### What's fixed
52
+
53
+ - **misc**: Pin `antd@5.17.0` to fix build error, closes [#2483](https://github.com/lobehub/lobe-chat/issues/2483) ([aa03833](https://github.com/lobehub/lobe-chat/commit/aa03833))
54
+
55
+ </details>
56
+
57
+ <div align="right">
58
+
59
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
60
+
61
+ </div>
62
+
5
63
  ## [Version 0.159.0](https://github.com/lobehub/lobe-chat/compare/v0.158.2...v0.159.0)
6
64
 
7
65
  <sup>Released on **2024-05-14**</sup>
package/README.zh-CN.md CHANGED
@@ -131,7 +131,6 @@
131
131
  - **Minimax**: 接入了 Minimax 的 AI 模型,包括 MoE 模型 **abab6**,提供了更多的选择空间。[了解更多](https://www.minimaxi.com/)
132
132
  - **DeepSeek**: 接入了 DeepSeek 的 AI 模型,包括最新的 **DeepSeek-V2**,提供兼顾性能与价格的模型。[了解更多](https://www.deepseek.com/)
133
133
 
134
-
135
134
  同时,我们也在计划支持更多的模型服务商,如 Replicate 和 Perplexity 等,以进一步丰富我们的服务商库。如果你希望让 LobeChat 支持你喜爱的服务商,欢迎加入我们的[社区讨论](https://github.com/lobehub/lobe-chat/discussions/1284)。
136
135
 
137
136
  <div align="right">
@@ -25,6 +25,15 @@ By setting the environment variables NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY and CLERK
25
25
 
26
26
  ## Next Auth
27
27
 
28
+ Before using NextAuth, please set the following variables in LobeChat's environment variables:
29
+
30
+ | Environment Variable | Type | Description |
31
+ | --- | --- | --- |
32
+ | `NEXT_AUTH_SECRET` | Required | The key used to encrypt Auth.js session tokens. You can use the following command: `openssl rand -base64 32`, or visit `https://generate-secret.vercel.app/32` to generate the key. |
33
+ | `ACCESS_CODE` | Required | Add a password to access this service. You can set a sufficiently long random password to "disable" access code authorization. |
34
+ | `NEXTAUTH_URL` | Optional | This URL specifies the callback address for Auth.js when performing OAuth verification. Set this only if the default generated redirect address is incorrect. `https://example.com/api/auth` |
35
+ | `NEXT_AUTH_SSO_PROVIDERS` | Optional | This environment variable is used to enable multiple identity verification sources simultaneously, separated by commas, for example, `auth0,azure-ad,authentik`. |
36
+
28
37
  Currently supported identity verification services include:
29
38
 
30
39
  <Cards>
@@ -22,6 +22,15 @@ LobeChat 与 Clerk 做了深度集成,能够为用户提供一个更加安全
22
22
 
23
23
  ## Next Auth
24
24
 
25
+ 在使用 NextAuth 之前,请先在 LobeChat 的环境变量中设置以下变量:
26
+
27
+ | 环境变量 | 类型 | 描述 |
28
+ | --- | --- | --- |
29
+ | `NEXT_AUTH_SECRET` | 必选 | 用于加密 Auth.js 会话令牌的密钥。您可以使用以下命令: `openssl rand -base64 32`,或者访问 `https://generate-secret.vercel.app/32` 生成秘钥。 |
30
+ | `ACCESS_CODE` | 必选 | 添加访问此服务的密码,你可以设置一个足够长的随机密码以 “禁用” 访问码授权 |
31
+ | `NEXTAUTH_URL` | 可选 | 该 URL 用于指定 Auth.js 在执行 OAuth 验证时的回调地址,当默认生成的重定向地址发生不正确时才需要设置。`https://example.com/api/auth` |
32
+ | `NEXT_AUTH_SSO_PROVIDERS` | 可选 | 该环境变量用于同时启用多个身份验证源,以逗号 `,` 分割,例如 `auth0,azure-ad,authentik`。 |
33
+
25
34
  目前支持的身份验证服务有:
26
35
 
27
36
  <Cards>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.159.0",
3
+ "version": "0.159.2",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -108,7 +108,7 @@
108
108
  "@vercel/speed-insights": "^1.0.10",
109
109
  "ahooks": "^3.7.11",
110
110
  "ai": "3.0.19",
111
- "antd": "^5.17.0",
111
+ "antd": "5.17.0",
112
112
  "antd-style": "^3.6.2",
113
113
  "brotli-wasm": "^3.0.0",
114
114
  "chroma-js": "^2.4.2",
@@ -61,7 +61,12 @@ const useStyles = createStyles(({ css, token, stylish }) => {
61
61
  });
62
62
 
63
63
  const handleDragOver = (e: DragEvent) => {
64
- e.preventDefault();
64
+ if (!e.dataTransfer?.items || e.dataTransfer.items.length === 0) return;
65
+
66
+ const isFile = e.dataTransfer.types.includes('Files');
67
+ if (isFile) {
68
+ e.preventDefault();
69
+ }
65
70
  };
66
71
 
67
72
  const DragUpload = memo(() => {
@@ -92,43 +97,55 @@ const DragUpload = memo(() => {
92
97
  };
93
98
 
94
99
  const handleDragEnter = (e: DragEvent) => {
95
- e.preventDefault();
100
+ if (!e.dataTransfer?.items || e.dataTransfer.items.length === 0) return;
96
101
 
97
- dragCounter.current += 1;
98
- if (e.dataTransfer?.items && e.dataTransfer.items.length > 0) {
102
+ const isFile = e.dataTransfer.types.includes('Files');
103
+ if (isFile) {
104
+ dragCounter.current += 1;
105
+ e.preventDefault();
99
106
  setIsDragging(true);
100
107
  }
101
108
  };
102
109
 
103
110
  const handleDragLeave = (e: DragEvent) => {
104
- e.preventDefault();
111
+ if (!e.dataTransfer?.items || e.dataTransfer.items.length === 0) return;
105
112
 
106
- // reset counter
107
- dragCounter.current -= 1;
113
+ const isFile = e.dataTransfer.types.includes('Files');
114
+ if (isFile) {
115
+ e.preventDefault();
108
116
 
109
- if (dragCounter.current === 0) {
110
- setIsDragging(false);
117
+ // reset counter
118
+ dragCounter.current -= 1;
119
+
120
+ if (dragCounter.current === 0) {
121
+ setIsDragging(false);
122
+ }
111
123
  }
112
124
  };
113
125
 
114
126
  const handleDrop = async (e: DragEvent) => {
115
- e.preventDefault();
116
- // reset counter
117
- dragCounter.current = 0;
127
+ if (!e.dataTransfer?.items || e.dataTransfer.items.length === 0) return;
118
128
 
119
- setIsDragging(false);
129
+ const isFile = e.dataTransfer.types.includes('Files');
130
+ if (isFile) {
131
+ e.preventDefault();
120
132
 
121
- // get filesList
122
- // TODO: support folder files upload
123
- const files = e.dataTransfer?.files;
133
+ // reset counter
134
+ dragCounter.current = 0;
124
135
 
125
- // upload files
126
- uploadImages(files);
136
+ setIsDragging(false);
137
+
138
+ // get filesList
139
+ // TODO: support folder files upload
140
+ const files = e.dataTransfer?.files;
141
+
142
+ // upload files
143
+ uploadImages(files);
144
+ }
127
145
  };
128
146
 
129
147
  const handlePaste = (event: ClipboardEvent) => {
130
148
  // get files from clipboard
131
-
132
149
  const files = event.clipboardData?.files;
133
150
 
134
151
  uploadImages(files);
@@ -3,7 +3,6 @@
3
3
  import { Form, type ItemGroup } from '@lobehub/ui';
4
4
  import { App, Button, Input } from 'antd';
5
5
  import isEqual from 'fast-deep-equal';
6
- import { signIn, signOut } from 'next-auth/react';
7
6
  import { memo, useCallback } from 'react';
8
7
  import { useTranslation } from 'react-i18next';
9
8
 
@@ -12,6 +11,8 @@ import { FORM_STYLE } from '@/const/layoutTokens';
12
11
  import { DEFAULT_SETTINGS } from '@/const/settings';
13
12
  import { useChatStore } from '@/store/chat';
14
13
  import { useFileStore } from '@/store/file';
14
+ import { useServerConfigStore } from '@/store/serverConfig';
15
+ import { serverConfigSelectors } from '@/store/serverConfig/selectors';
15
16
  import { useSessionStore } from '@/store/session';
16
17
  import { useToolStore } from '@/store/tool';
17
18
  import { useUserStore } from '@/store/user';
@@ -19,16 +20,13 @@ import { settingsSelectors, userProfileSelectors } from '@/store/user/selectors'
19
20
 
20
21
  type SettingItemGroup = ItemGroup;
21
22
 
22
- export interface SettingsCommonProps {
23
- showAccessCodeConfig: boolean;
24
- showOAuthLogin?: boolean;
25
- }
26
-
27
- const Common = memo<SettingsCommonProps>(({ showAccessCodeConfig, showOAuthLogin }) => {
23
+ const Common = memo(() => {
28
24
  const { t } = useTranslation('setting');
29
25
  const [form] = Form.useForm();
30
26
 
31
27
  const isSignedIn = useUserStore((s) => s.isSignedIn);
28
+ const showAccessCodeConfig = useServerConfigStore(serverConfigSelectors.enabledAccessCode);
29
+ const showOAuthLogin = useServerConfigStore(serverConfigSelectors.enabledOAuthSSO);
32
30
  const user = useUserStore(userProfileSelectors.userProfile, isEqual);
33
31
 
34
32
  const [clearSessions, clearSessionGroups] = useSessionStore((s) => [
@@ -42,7 +40,12 @@ const Common = memo<SettingsCommonProps>(({ showAccessCodeConfig, showOAuthLogin
42
40
  const [removeAllFiles] = useFileStore((s) => [s.removeAllFiles]);
43
41
  const removeAllPlugins = useToolStore((s) => s.removeAllPlugins);
44
42
  const settings = useUserStore(settingsSelectors.currentSettings, isEqual);
45
- const [setSettings, resetSettings] = useUserStore((s) => [s.setSettings, s.resetSettings]);
43
+ const [setSettings, resetSettings, signIn, signOut] = useUserStore((s) => [
44
+ s.setSettings,
45
+ s.resetSettings,
46
+ s.openLogin,
47
+ s.logout,
48
+ ]);
46
49
 
47
50
  const { message, modal } = App.useApp();
48
51
 
@@ -1,19 +1,11 @@
1
- import { authEnv } from '@/config/auth';
2
- import { getServerConfig } from '@/config/server';
3
-
4
1
  import Common from './features/Common';
5
2
  import Theme from './features/Theme';
6
3
 
7
4
  const Page = () => {
8
- const { SHOW_ACCESS_CODE_CONFIG } = getServerConfig();
9
-
10
5
  return (
11
6
  <>
12
7
  <Theme />
13
- <Common
14
- showAccessCodeConfig={SHOW_ACCESS_CODE_CONFIG}
15
- showOAuthLogin={authEnv.NEXT_PUBLIC_ENABLE_NEXT_AUTH}
16
- />
8
+ <Common />
17
9
  </>
18
10
  );
19
11
  };
@@ -1,20 +1,22 @@
1
1
  import { Icon } from '@lobehub/ui';
2
2
  import { App, Button } from 'antd';
3
3
  import { ScanFace } from 'lucide-react';
4
- import { signIn, signOut } from 'next-auth/react';
5
4
  import { memo, useCallback } from 'react';
6
5
  import { useTranslation } from 'react-i18next';
7
6
  import { Center, Flexbox } from 'react-layout-kit';
8
7
 
9
- import { useOAuthSession } from '@/hooks/useOAuthSession';
10
8
  import { useChatStore } from '@/store/chat';
9
+ import { useUserStore } from '@/store/user';
10
+ import { authSelectors, userProfileSelectors } from '@/store/user/selectors';
11
11
 
12
12
  import { FormAction } from './style';
13
13
 
14
14
  const OAuthForm = memo<{ id: string }>(({ id }) => {
15
15
  const { t } = useTranslation('error');
16
16
 
17
- const { user, isOAuthLoggedIn } = useOAuthSession();
17
+ const [signIn, signOut] = useUserStore((s) => [s.openLogin, s.logout]);
18
+ const user = useUserStore(userProfileSelectors.userProfile);
19
+ const isOAuthLoggedIn = useUserStore(authSelectors.isLoginWithAuth);
18
20
 
19
21
  const [resend, deleteMessage] = useChatStore((s) => [s.regenerateMessage, s.deleteMessage]);
20
22
 
@@ -38,7 +40,7 @@ const OAuthForm = memo<{ id: string }>(({ id }) => {
38
40
  avatar={isOAuthLoggedIn ? '✅' : '🕵️‍♂️'}
39
41
  description={
40
42
  isOAuthLoggedIn
41
- ? `${t('unlock.oauth.welcome')} ${user?.name}`
43
+ ? `${t('unlock.oauth.welcome')} ${user?.fullName || ''}`
42
44
  : t('unlock.oauth.description')
43
45
  }
44
46
  title={isOAuthLoggedIn ? t('unlock.oauth.success') : t('unlock.oauth.title')}
@@ -30,6 +30,7 @@ describe('UserAvatar', () => {
30
30
 
31
31
  act(() => {
32
32
  useUserStore.setState({
33
+ enableAuth: () => true,
33
34
  isSignedIn: true,
34
35
  user: { avatar: mockAvatar, id: 'abc', username: mockUsername },
35
36
  });
@@ -45,7 +46,11 @@ describe('UserAvatar', () => {
45
46
  const mockUsername = 'testuser';
46
47
 
47
48
  act(() => {
48
- useUserStore.setState({ isSignedIn: true, user: { id: 'bbb', username: mockUsername } });
49
+ useUserStore.setState({
50
+ enableAuth: () => true,
51
+ isSignedIn: true,
52
+ user: { id: 'bbb', username: mockUsername },
53
+ });
49
54
  });
50
55
 
51
56
  render(<UserAvatar />);
@@ -54,7 +59,7 @@ describe('UserAvatar', () => {
54
59
 
55
60
  it('should show LobeChat and default avatar when the user is not logged in and enable auth', () => {
56
61
  act(() => {
57
- useUserStore.setState({ isSignedIn: false, user: undefined });
62
+ useUserStore.setState({ enableAuth: () => true, isSignedIn: false, user: undefined });
58
63
  });
59
64
 
60
65
  render(<UserAvatar />);
@@ -67,7 +72,7 @@ describe('UserAvatar', () => {
67
72
  it('should show LobeChat and default avatar when the user is not logged in and disabled auth', () => {
68
73
  enableAuth = false;
69
74
  act(() => {
70
- useUserStore.setState({ isSignedIn: false, user: undefined });
75
+ useUserStore.setState({ enableAuth: () => false, isSignedIn: false, user: undefined });
71
76
  });
72
77
 
73
78
  render(<UserAvatar />);
@@ -64,7 +64,7 @@ afterEach(() => {
64
64
  describe('useMenu', () => {
65
65
  it('should provide correct menu items when user is logged in with auth', () => {
66
66
  act(() => {
67
- useUserStore.setState({ isSignedIn: true });
67
+ useUserStore.setState({ isSignedIn: true, enableAuth: () => true });
68
68
  });
69
69
  enableAuth = true;
70
70
  enableClerk = false;
@@ -104,7 +104,7 @@ describe('useMenu', () => {
104
104
 
105
105
  it('should provide correct menu items when user is logged in without auth', () => {
106
106
  act(() => {
107
- useUserStore.setState({ isSignedIn: false });
107
+ useUserStore.setState({ isSignedIn: false, enableAuth: () => false });
108
108
  });
109
109
  enableAuth = false;
110
110
 
@@ -123,7 +123,7 @@ describe('useMenu', () => {
123
123
 
124
124
  it('should provide correct menu items when user is not logged in', () => {
125
125
  act(() => {
126
- useUserStore.setState({ isSignedIn: false });
126
+ useUserStore.setState({ isSignedIn: false, enableAuth: () => true });
127
127
  });
128
128
  enableAuth = true;
129
129
 
@@ -44,10 +44,3 @@ export const {
44
44
  handlers: { GET, POST },
45
45
  auth,
46
46
  } = nextAuth;
47
-
48
- declare module '@auth/core/jwt' {
49
- // Returned by the `jwt` callback and `auth`, when using JWT sessions
50
- interface JWT {
51
- userId?: string;
52
- }
53
- }
@@ -13,6 +13,7 @@ import { parseAgentConfig } from './parseDefaultAgent';
13
13
 
14
14
  export const getServerGlobalConfig = () => {
15
15
  const {
16
+ ACCESS_CODES,
16
17
  ENABLE_LANGFUSE,
17
18
 
18
19
  DEFAULT_AGENT_CONFIG,
@@ -49,6 +50,7 @@ export const getServerGlobalConfig = () => {
49
50
  config: parseAgentConfig(DEFAULT_AGENT_CONFIG),
50
51
  },
51
52
 
53
+ enabledAccessCode: ACCESS_CODES?.length > 0,
52
54
  enabledOAuthSSO: enableNextAuth,
53
55
  languageModel: {
54
56
  anthropic: {
@@ -6,6 +6,7 @@ export const featureFlagsSelectors = (s: ServerConfigStore) =>
6
6
  mapFeatureFlagsEnvToState(s.featureFlags);
7
7
 
8
8
  export const serverConfigSelectors = {
9
+ enabledAccessCode: (s: ServerConfigStore) => !!s.serverConfig?.enabledAccessCode,
9
10
  enabledOAuthSSO: (s: ServerConfigStore) => s.serverConfig.enabledOAuthSSO,
10
11
  enabledTelemetryChat: (s: ServerConfigStore) => s.serverConfig.telemetry.langfuse || false,
11
12
  isMobile: (s: ServerConfigStore) => s.isMobile || false,
@@ -43,6 +43,16 @@ afterEach(() => {
43
43
  enableClerk = false;
44
44
  });
45
45
 
46
+ /**
47
+ * Mock nextauth 库相关方法
48
+ */
49
+ vi.mock('next-auth/react', async () => {
50
+ return {
51
+ signIn: vi.fn(),
52
+ signOut: vi.fn(),
53
+ };
54
+ });
55
+
46
56
  describe('createAuthSlice', () => {
47
57
  describe('refreshUserConfig', () => {
48
58
  it('should refresh user config', async () => {
@@ -162,6 +172,32 @@ describe('createAuthSlice', () => {
162
172
 
163
173
  expect(clerkSignOutMock).not.toHaveBeenCalled();
164
174
  });
175
+
176
+ it('should call next-auth signOut when NextAuth is enabled', async () => {
177
+ useUserStore.setState({ enabledNextAuth: () => true });
178
+
179
+ const { result } = renderHook(() => useUserStore());
180
+
181
+ await act(async () => {
182
+ await result.current.logout();
183
+ });
184
+
185
+ const { signOut } = await import('next-auth/react');
186
+
187
+ expect(signOut).toHaveBeenCalled();
188
+ });
189
+
190
+ it('should not call next-auth signOut when NextAuth is disabled', async () => {
191
+ const { result } = renderHook(() => useUserStore());
192
+
193
+ await act(async () => {
194
+ await result.current.logout();
195
+ });
196
+
197
+ const { signOut } = await import('next-auth/react');
198
+
199
+ expect(signOut).not.toHaveBeenCalled();
200
+ });
165
201
  });
166
202
 
167
203
  describe('openLogin', () => {
@@ -190,6 +226,31 @@ describe('createAuthSlice', () => {
190
226
 
191
227
  expect(clerkSignInMock).not.toHaveBeenCalled();
192
228
  });
229
+
230
+ it('should call next-auth signIn when NextAuth is enabled', async () => {
231
+ useUserStore.setState({ enabledNextAuth: () => true });
232
+
233
+ const { result } = renderHook(() => useUserStore());
234
+
235
+ await act(async () => {
236
+ await result.current.openLogin();
237
+ });
238
+
239
+ const { signIn } = await import('next-auth/react');
240
+
241
+ expect(signIn).toHaveBeenCalled();
242
+ });
243
+ it('should not call next-auth signIn when NextAuth is disabled', async () => {
244
+ const { result } = renderHook(() => useUserStore());
245
+
246
+ await act(async () => {
247
+ await result.current.openLogin();
248
+ });
249
+
250
+ const { signIn } = await import('next-auth/react');
251
+
252
+ expect(signIn).not.toHaveBeenCalled();
253
+ });
193
254
  });
194
255
 
195
256
  describe('openUserProfile', () => {
@@ -1,7 +1,7 @@
1
1
  import useSWR, { SWRResponse, mutate } from 'swr';
2
2
  import { StateCreator } from 'zustand/vanilla';
3
3
 
4
- import { enableClerk, enableNextAuth } from '@/const/auth';
4
+ import { enableClerk } from '@/const/auth';
5
5
  import { UserConfig, userService } from '@/services/user';
6
6
  import { switchLang } from '@/utils/client/switchLang';
7
7
  import { setNamespace } from '@/utils/storeDebug';
@@ -13,8 +13,9 @@ const n = setNamespace('auth');
13
13
  const USER_CONFIG_FETCH_KEY = 'fetchUserConfig';
14
14
 
15
15
  export interface UserAuthAction {
16
+ enableAuth: () => boolean;
17
+ enabledNextAuth: () => boolean;
16
18
  getUserConfig: () => void;
17
- login: () => Promise<void>;
18
19
  /**
19
20
  * universal logout method
20
21
  */
@@ -24,8 +25,8 @@ export interface UserAuthAction {
24
25
  */
25
26
  openLogin: () => Promise<void>;
26
27
  openUserProfile: () => Promise<void>;
27
- refreshUserConfig: () => Promise<void>;
28
28
 
29
+ refreshUserConfig: () => Promise<void>;
29
30
  useFetchUserConfig: (initServer: boolean) => SWRResponse<UserConfig | undefined>;
30
31
  }
31
32
 
@@ -35,13 +36,15 @@ export const createAuthSlice: StateCreator<
35
36
  [],
36
37
  UserAuthAction
37
38
  > = (set, get) => ({
39
+ enableAuth: () => {
40
+ return enableClerk || get()?.enabledNextAuth();
41
+ },
42
+ enabledNextAuth: () => {
43
+ return !!get()?.serverConfig.enabledOAuthSSO;
44
+ },
38
45
  getUserConfig: () => {
39
46
  console.log(n('userconfig'));
40
47
  },
41
- login: async () => {
42
- // TODO: 针对开启 next-auth 的场景,需要在这里调用登录方法
43
- console.log(n('login'));
44
- },
45
48
  logout: async () => {
46
49
  if (enableClerk) {
47
50
  get().clerkSignOut?.({ redirectUrl: location.toString() });
@@ -49,9 +52,10 @@ export const createAuthSlice: StateCreator<
49
52
  return;
50
53
  }
51
54
 
55
+ const enableNextAuth = get().enabledNextAuth();
52
56
  if (enableNextAuth) {
53
- // TODO: 针对开启 next-auth 的场景,需要在这里调用登录方法
54
- console.log(n('logout'));
57
+ const { signOut } = await import('next-auth/react');
58
+ signOut();
55
59
  }
56
60
  },
57
61
  openLogin: async () => {
@@ -63,20 +67,19 @@ export const createAuthSlice: StateCreator<
63
67
  return;
64
68
  }
65
69
 
70
+ const enableNextAuth = get().enabledNextAuth();
66
71
  if (enableNextAuth) {
67
- // TODO: 针对开启 next-auth 的场景,需要在这里调用登录方法
72
+ const { signIn } = await import('next-auth/react');
73
+ signIn();
68
74
  }
69
75
  },
76
+
70
77
  openUserProfile: async () => {
71
78
  if (enableClerk) {
72
79
  get().clerkOpenUserProfile?.();
73
80
 
74
81
  return;
75
82
  }
76
-
77
- if (enableNextAuth) {
78
- // TODO: 针对开启 next-auth 的场景,需要在这里调用打开 profile 页
79
- }
80
83
  },
81
84
  refreshUserConfig: async () => {
82
85
  await mutate([USER_CONFIG_FETCH_KEY, true]);
@@ -84,7 +87,6 @@ export const createAuthSlice: StateCreator<
84
87
  // when get the user config ,refresh the model provider list to the latest
85
88
  get().refreshModelProviderList();
86
89
  },
87
-
88
90
  useFetchUserConfig: (initServer) =>
89
91
  useSWR<UserConfig | undefined>(
90
92
  [USER_CONFIG_FETCH_KEY, initServer],
@@ -31,6 +31,7 @@ describe('userProfileSelectors', () => {
31
31
  const store: UserStore = {
32
32
  isSignedIn: false,
33
33
  user: null,
34
+ enableAuth: () => false,
34
35
  } as unknown as UserStore;
35
36
 
36
37
  expect(userProfileSelectors.nickName(store)).toBe('userPanel.defaultNickname');
@@ -43,6 +44,7 @@ describe('userProfileSelectors', () => {
43
44
  const store: UserStore = {
44
45
  isSignedIn: true,
45
46
  user: { fullName: 'John Doe' },
47
+ enableAuth: () => true,
46
48
  } as UserStore;
47
49
 
48
50
  expect(userProfileSelectors.nickName(store)).toBe('John Doe');
@@ -52,6 +54,7 @@ describe('userProfileSelectors', () => {
52
54
  const store: UserStore = {
53
55
  isSignedIn: true,
54
56
  user: { username: 'johndoe' },
57
+ enableAuth: () => true,
55
58
  } as UserStore;
56
59
 
57
60
  expect(userProfileSelectors.nickName(store)).toBe('johndoe');
@@ -60,7 +63,11 @@ describe('userProfileSelectors', () => {
60
63
  it('should return anonymous nickname when not signed in', () => {
61
64
  enableAuth = true;
62
65
 
63
- const store: UserStore = { isSignedIn: false, user: null } as unknown as UserStore;
66
+ const store: UserStore = {
67
+ enableAuth: () => true,
68
+ isSignedIn: false,
69
+ user: null,
70
+ } as unknown as UserStore;
64
71
 
65
72
  expect(userProfileSelectors.nickName(store)).toBe('userPanel.anonymousNickName');
66
73
  expect(t).toHaveBeenCalledWith('userPanel.anonymousNickName', { ns: 'common' });
@@ -74,6 +81,7 @@ describe('userProfileSelectors', () => {
74
81
  const store: UserStore = {
75
82
  isSignedIn: false,
76
83
  user: null,
84
+ enableAuth: () => false,
77
85
  } as unknown as UserStore;
78
86
 
79
87
  expect(userProfileSelectors.username(store)).toBe('LobeChat');
@@ -83,13 +91,18 @@ describe('userProfileSelectors', () => {
83
91
  const store: UserStore = {
84
92
  isSignedIn: true,
85
93
  user: { username: 'johndoe' },
94
+ enableAuth: () => true,
86
95
  } as UserStore;
87
96
 
88
97
  expect(userProfileSelectors.username(store)).toBe('johndoe');
89
98
  });
90
99
 
91
100
  it('should return "anonymous" when not signed in', () => {
92
- const store: UserStore = { isSignedIn: false, user: null } as unknown as UserStore;
101
+ const store: UserStore = {
102
+ enableAuth: () => true,
103
+ isSignedIn: false,
104
+ user: null,
105
+ } as unknown as UserStore;
93
106
 
94
107
  expect(userProfileSelectors.username(store)).toBe('anonymous');
95
108
  });
@@ -103,6 +116,7 @@ describe('authSelectors', () => {
103
116
 
104
117
  const store: UserStore = {
105
118
  isSignedIn: false,
119
+ enableAuth: () => false,
106
120
  } as UserStore;
107
121
 
108
122
  expect(authSelectors.isLogin(store)).toBe(true);
@@ -111,6 +125,7 @@ describe('authSelectors', () => {
111
125
  it('should return true when signed in', () => {
112
126
  const store: UserStore = {
113
127
  isSignedIn: true,
128
+ enableAuth: () => true,
114
129
  } as UserStore;
115
130
 
116
131
  expect(authSelectors.isLogin(store)).toBe(true);
@@ -119,6 +134,7 @@ describe('authSelectors', () => {
119
134
  it('should return false when not signed in and auth is enabled', () => {
120
135
  const store: UserStore = {
121
136
  isSignedIn: false,
137
+ enableAuth: () => true,
122
138
  } as UserStore;
123
139
 
124
140
  expect(authSelectors.isLogin(store)).toBe(false);
@@ -1,13 +1,13 @@
1
1
  import { t } from 'i18next';
2
2
 
3
- import { enableAuth, enableClerk } from '@/const/auth';
3
+ import { enableClerk } from '@/const/auth';
4
4
  import { UserStore } from '@/store/user';
5
5
  import { LobeUser } from '@/types/user';
6
6
 
7
7
  const DEFAULT_USERNAME = 'LobeChat';
8
8
 
9
9
  const nickName = (s: UserStore) => {
10
- if (!enableAuth) return t('userPanel.defaultNickname', { ns: 'common' });
10
+ if (!s.enableAuth()) return t('userPanel.defaultNickname', { ns: 'common' });
11
11
 
12
12
  if (s.isSignedIn) return s.user?.fullName || s.user?.username;
13
13
 
@@ -15,7 +15,7 @@ const nickName = (s: UserStore) => {
15
15
  };
16
16
 
17
17
  const username = (s: UserStore) => {
18
- if (!enableAuth) return DEFAULT_USERNAME;
18
+ if (!s.enableAuth()) return DEFAULT_USERNAME;
19
19
 
20
20
  if (s.isSignedIn) return s.user?.username;
21
21
 
@@ -35,7 +35,7 @@ export const userProfileSelectors = {
35
35
  */
36
36
  const isLogin = (s: UserStore) => {
37
37
  // 如果没有开启鉴权,说明不需要登录,默认是登录态
38
- if (!enableAuth) return true;
38
+ if (!s.enableAuth()) return true;
39
39
 
40
40
  return s.isSignedIn;
41
41
  };
@@ -0,0 +1,23 @@
1
+ import { type DefaultSession } from 'next-auth';
2
+
3
+ declare module 'next-auth' {
4
+ /**
5
+ * Returned by `useSession`, `auth`, contains information about the active session.
6
+ */
7
+ interface Session {
8
+ user: {
9
+ firstName?: string;
10
+ } & DefaultSession['user'];
11
+ }
12
+ /**
13
+ * More types can be extends here
14
+ * ref: https://authjs.dev/getting-started/typescript
15
+ */
16
+ }
17
+
18
+ declare module '@auth/core/jwt' {
19
+ /** Returned by the `jwt` callback and `auth`, when using JWT sessions */
20
+ interface JWT {
21
+ userId: string;
22
+ }
23
+ }
@@ -15,6 +15,7 @@ export interface ServerModelProviderConfig {
15
15
 
16
16
  export interface GlobalServerConfig {
17
17
  defaultAgent?: DeepPartial<GlobalDefaultAgent>;
18
+ enabledAccessCode?: boolean;
18
19
  enabledOAuthSSO?: boolean;
19
20
  languageModel?: Partial<Record<GlobalLLMProviderKey, ServerModelProviderConfig>>;
20
21
  telemetry: {
@@ -1,24 +0,0 @@
1
- import { User } from '@auth/core/types';
2
- import { SessionContextValue, useSession } from 'next-auth/react';
3
- import { useMemo } from 'react';
4
-
5
- interface OAuthSession {
6
- isOAuthLoggedIn: boolean;
7
- user?: User | null;
8
- }
9
-
10
- export const useOAuthSession = () => {
11
- let authSession: SessionContextValue | null;
12
- try {
13
- // refs: https://github.com/lobehub/lobe-chat/pull/1286
14
- // eslint-disable-next-line react-hooks/rules-of-hooks
15
- authSession = useSession();
16
- } catch {
17
- authSession = null;
18
- }
19
-
20
- const { data: session, status } = authSession || {};
21
- const isOAuthLoggedIn = (status === 'authenticated' && session && !!session.user) || false;
22
-
23
- return useMemo<OAuthSession>(() => ({ isOAuthLoggedIn, user: session?.user }), [session, status]);
24
- };