@lobehub/chat 0.153.1 → 0.154.1
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/docs/self-hosting/advanced/authentication.mdx +25 -18
- package/docs/self-hosting/advanced/authentication.zh-CN.mdx +26 -17
- package/docs/self-hosting/advanced/model-list.mdx +2 -2
- package/docs/self-hosting/advanced/model-list.zh-CN.mdx +2 -2
- package/docs/self-hosting/environment-variables/auth.mdx +48 -39
- package/docs/self-hosting/environment-variables/auth.zh-CN.mdx +49 -43
- package/locales/ar/auth.json +6 -0
- package/locales/ar/clerk.json +769 -0
- package/locales/ar/common.json +1 -0
- package/locales/ar/error.json +8 -0
- package/locales/bg-BG/auth.json +6 -0
- package/locales/bg-BG/clerk.json +769 -0
- package/locales/bg-BG/common.json +1 -0
- package/locales/bg-BG/error.json +8 -0
- package/locales/de-DE/auth.json +6 -0
- package/locales/de-DE/clerk.json +769 -0
- package/locales/de-DE/common.json +1 -0
- package/locales/de-DE/error.json +8 -0
- package/locales/en-US/auth.json +6 -0
- package/locales/en-US/clerk.json +769 -0
- package/locales/en-US/common.json +1 -0
- package/locales/en-US/error.json +8 -0
- package/locales/es-ES/auth.json +6 -0
- package/locales/es-ES/clerk.json +769 -0
- package/locales/es-ES/common.json +1 -0
- package/locales/es-ES/error.json +8 -0
- package/locales/fr-FR/auth.json +6 -0
- package/locales/fr-FR/clerk.json +769 -0
- package/locales/fr-FR/common.json +1 -0
- package/locales/fr-FR/error.json +8 -0
- package/locales/it-IT/auth.json +6 -0
- package/locales/it-IT/clerk.json +769 -0
- package/locales/it-IT/common.json +1 -0
- package/locales/it-IT/error.json +8 -0
- package/locales/ja-JP/auth.json +6 -0
- package/locales/ja-JP/clerk.json +769 -0
- package/locales/ja-JP/common.json +1 -0
- package/locales/ja-JP/error.json +8 -0
- package/locales/ko-KR/auth.json +6 -0
- package/locales/ko-KR/clerk.json +769 -0
- package/locales/ko-KR/common.json +1 -0
- package/locales/ko-KR/error.json +8 -0
- package/locales/nl-NL/auth.json +6 -0
- package/locales/nl-NL/clerk.json +769 -0
- package/locales/nl-NL/common.json +1 -0
- package/locales/nl-NL/error.json +8 -0
- package/locales/pl-PL/auth.json +6 -0
- package/locales/pl-PL/clerk.json +769 -0
- package/locales/pl-PL/common.json +1 -0
- package/locales/pl-PL/error.json +8 -0
- package/locales/pt-BR/auth.json +6 -0
- package/locales/pt-BR/clerk.json +769 -0
- package/locales/pt-BR/common.json +1 -0
- package/locales/pt-BR/error.json +8 -0
- package/locales/ru-RU/auth.json +6 -0
- package/locales/ru-RU/clerk.json +769 -0
- package/locales/ru-RU/common.json +1 -0
- package/locales/ru-RU/error.json +8 -0
- package/locales/tr-TR/auth.json +6 -0
- package/locales/tr-TR/clerk.json +769 -0
- package/locales/tr-TR/common.json +1 -0
- package/locales/tr-TR/error.json +8 -0
- package/locales/vi-VN/auth.json +6 -0
- package/locales/vi-VN/clerk.json +769 -0
- package/locales/vi-VN/common.json +1 -0
- package/locales/vi-VN/error.json +8 -0
- package/locales/zh-CN/auth.json +6 -0
- package/locales/zh-CN/clerk.json +769 -0
- package/locales/zh-CN/common.json +1 -0
- package/locales/zh-CN/error.json +8 -0
- package/locales/zh-TW/auth.json +6 -0
- package/locales/zh-TW/clerk.json +769 -0
- package/locales/zh-TW/common.json +1 -0
- package/locales/zh-TW/error.json +8 -0
- package/package.json +10 -5
- package/src/app/(auth)/layout.tsx +19 -0
- package/src/app/(auth)/login/[[...login]]/PageTitle.tsx +13 -0
- package/src/app/(auth)/login/[[...login]]/page.tsx +14 -0
- package/src/app/(auth)/profile/[[...slugs]]/PageTitle.tsx +13 -0
- package/src/app/(auth)/profile/[[...slugs]]/page.tsx +14 -0
- package/src/app/(auth)/signup/[[...signup]]/PageTitle.tsx +13 -0
- package/src/app/(auth)/signup/[[...signup]]/page.tsx +14 -0
- package/src/app/(main)/settings/common/features/Common.tsx +1 -1
- package/src/app/(main)/settings/common/features/Theme/index.tsx +2 -0
- package/src/app/(main)/settings/common/index.tsx +7 -3
- package/src/app/api/chat/[provider]/route.test.ts +76 -3
- package/src/app/api/chat/auth/index.test.ts +77 -0
- package/src/app/api/chat/auth/index.ts +19 -3
- package/src/app/api/chat/auth/utils.ts +31 -9
- package/src/app/api/plugin/gateway/route.ts +3 -3
- package/src/config/auth.ts +146 -0
- package/src/config/server/index.ts +1 -3
- package/src/const/auth.ts +7 -0
- package/src/features/AgentSetting/AgentMeta/AutoGenerateInput.tsx +1 -2
- package/src/features/AgentSetting/AgentMeta/AutoGenerateSelect.tsx +1 -1
- package/src/features/AgentSetting/AgentMeta/index.tsx +7 -2
- package/src/features/Conversation/Error/ClerkLogin/index.tsx +47 -0
- package/src/features/Conversation/Error/index.tsx +5 -0
- package/src/features/Conversation/components/InboxWelcome/index.tsx +4 -17
- package/src/features/DataImporter/index.tsx +2 -0
- package/src/features/User/UserAvatar.tsx +11 -5
- package/src/features/User/UserInfo.tsx +8 -7
- package/src/features/User/UserLoginOrSignup.tsx +23 -0
- package/src/features/User/UserPanel/PanelContent.tsx +74 -0
- package/src/features/User/UserPanel/UpgradeBadge.tsx +19 -0
- package/src/features/User/UserPanel/index.tsx +8 -27
- package/src/features/User/UserPanel/useMenu.tsx +49 -21
- package/src/features/User/__tests__/PanelContent.test.tsx +151 -0
- package/src/features/User/__tests__/UserAvatar.test.tsx +78 -0
- package/src/hooks/useGreeting/greetingTime.ts +14 -0
- package/src/hooks/useGreeting/index.ts +16 -0
- package/src/hooks/useTokenCount.ts +1 -0
- package/src/layout/AuthProvider/Clerk/UserUpdater.tsx +41 -0
- package/src/layout/AuthProvider/Clerk/index.tsx +26 -0
- package/src/layout/AuthProvider/Clerk/useAppearance.ts +118 -0
- package/src/layout/AuthProvider/index.tsx +8 -4
- package/src/layout/GlobalProvider/AppTheme.tsx +1 -1
- package/src/libs/next-auth/index.ts +4 -6
- package/src/libs/next-auth/sso-providers/auth0.ts +4 -6
- package/src/libs/next-auth/sso-providers/authentik.ts +4 -6
- package/src/libs/next-auth/sso-providers/azure-ad.ts +4 -6
- package/src/libs/next-auth/sso-providers/github.ts +3 -5
- package/src/libs/next-auth/sso-providers/zitadel.ts +4 -6
- package/src/locales/default/auth.ts +6 -0
- package/src/locales/default/clerk.ts +782 -0
- package/src/locales/default/common.ts +3 -0
- package/src/locales/default/error.ts +8 -0
- package/src/locales/default/index.ts +4 -0
- package/src/middleware.ts +19 -6
- package/src/server/globalConfig/index.ts +2 -2
- package/src/store/user/selectors.ts +1 -1
- package/src/store/user/slices/auth/action.test.ts +105 -0
- package/src/store/user/slices/auth/action.ts +40 -5
- package/src/store/user/slices/auth/initialState.ts +15 -0
- package/src/store/user/slices/auth/selectors.test.ts +127 -0
- package/src/store/user/slices/auth/selectors.ts +38 -1
- package/src/store/user/slices/settings/selectors/selectors.test.ts +25 -0
- package/src/store/user/slices/settings/selectors/settings.ts +6 -0
- package/src/styles/antdOverride.ts +5 -0
- package/src/types/fetch.ts +1 -0
- package/src/config/server/auth.ts +0 -71
- package/src/features/User/UserPanel/Popover.tsx +0 -35
package/locales/zh-TW/error.json
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
+
"clerkAuth": {
|
|
3
|
+
"loginSuccess": {
|
|
4
|
+
"action": "繼續會話",
|
|
5
|
+
"desc": "{{greeting}},很高興能夠繼續為你服務。讓我們接著剛剛的話題聊下去吧",
|
|
6
|
+
"title": "歡迎回來, {{nickName}}"
|
|
7
|
+
}
|
|
8
|
+
},
|
|
2
9
|
"error": {
|
|
3
10
|
"backHome": "返回首頁",
|
|
4
11
|
"desc": "待會再試試,或者回到已知的世界",
|
|
@@ -54,6 +61,7 @@
|
|
|
54
61
|
"InvalidAnthropicAPIKey": "Anthropic API 金鑰不正確或為空,請檢查 Anthropic API 金鑰後重試",
|
|
55
62
|
"InvalidAzureAPIKey": "Azure API Key 不正確或為空,請檢查 Azure API Key 後重試",
|
|
56
63
|
"InvalidBedrockCredentials": "Bedrock 驗證未通過,請檢查 AccessKeyId/SecretAccessKey 後重試",
|
|
64
|
+
"InvalidClerkUser": "很抱歉,你當前尚未登錄,請先登錄或註冊帳號後繼續操作",
|
|
57
65
|
"InvalidGoogleAPIKey": "Google API Key 不正確或為空,請檢查 Google API Key 後重試",
|
|
58
66
|
"InvalidGroqAPIKey": "Groq API 金鑰不正確或為空,請檢查 Groq API 金鑰後重試",
|
|
59
67
|
"InvalidMinimaxAPIKey": "Minimax API 金鑰不正確或為空,請檢查 Minimax API 金鑰後重試",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/chat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.154.1",
|
|
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",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"build:analyze": "ANALYZE=true next build",
|
|
33
33
|
"build:docker": "DOCKER=true next build && npm run build-sitemap",
|
|
34
34
|
"dev": "next dev -p 3010",
|
|
35
|
+
"dev:clerk-proxy": "ngrok http http://localhost:3011",
|
|
35
36
|
"docs:i18n": "lobe-i18n md && npm run workflow:docs && npm run lint:mdx",
|
|
36
37
|
"docs:seo": "lobe-seo && npm run workflow:mdx && npm run lint:mdx",
|
|
37
38
|
"i18n": "npm run workflow:i18n && lobe-i18n",
|
|
@@ -84,9 +85,13 @@
|
|
|
84
85
|
"@anthropic-ai/sdk": "^0.18.0",
|
|
85
86
|
"@auth/core": "0.28.0",
|
|
86
87
|
"@aws-sdk/client-bedrock-runtime": "^3.565.0",
|
|
87
|
-
"@azure/openai": "1.0.0-beta.12",
|
|
88
|
+
"@azure/openai": "^1.0.0-beta.12",
|
|
88
89
|
"@cfworker/json-schema": "^1.12.8",
|
|
89
|
-
"@
|
|
90
|
+
"@clerk/backend": "^1.1.1",
|
|
91
|
+
"@clerk/localizations": "2.0.0",
|
|
92
|
+
"@clerk/nextjs": "^5.0.6",
|
|
93
|
+
"@clerk/themes": "^2.0.0",
|
|
94
|
+
"@google/generative-ai": "^0.10.0",
|
|
90
95
|
"@icons-pack/react-simple-icons": "^9.4.1",
|
|
91
96
|
"@lobehub/chat-plugin-sdk": "latest",
|
|
92
97
|
"@lobehub/chat-plugins-gateway": "latest",
|
|
@@ -179,8 +184,8 @@
|
|
|
179
184
|
"@next/bundle-analyzer": "^14.2.3",
|
|
180
185
|
"@next/eslint-plugin-next": "^14.2.3",
|
|
181
186
|
"@peculiar/webcrypto": "^1.4.6",
|
|
182
|
-
"@testing-library/jest-dom": "6.4.
|
|
183
|
-
"@testing-library/react": "^15.0.
|
|
187
|
+
"@testing-library/jest-dom": "^6.4.5",
|
|
188
|
+
"@testing-library/react": "^15.0.6",
|
|
184
189
|
"@types/chroma-js": "^2.4.4",
|
|
185
190
|
"@types/debug": "^4.1.12",
|
|
186
191
|
"@types/diff": "^5.2.0",
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { notFound } from 'next/navigation';
|
|
2
|
+
import { PropsWithChildren } from 'react';
|
|
3
|
+
import { Center, Flexbox } from 'react-layout-kit';
|
|
4
|
+
|
|
5
|
+
import { enableClerk } from '@/const/auth';
|
|
6
|
+
|
|
7
|
+
const Page = ({ children }: PropsWithChildren) => {
|
|
8
|
+
if (!enableClerk) return notFound();
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<Flexbox height={'100%'} width={'100%'}>
|
|
12
|
+
<Center height={'100%'} width={'100%'}>
|
|
13
|
+
{children}
|
|
14
|
+
</Center>
|
|
15
|
+
</Flexbox>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default Page;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { memo } from 'react';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
|
|
6
|
+
import PageTitle from '@/components/PageTitle';
|
|
7
|
+
|
|
8
|
+
const Title = memo(() => {
|
|
9
|
+
const { t } = useTranslation('auth');
|
|
10
|
+
|
|
11
|
+
return <PageTitle title={t('login')} />;
|
|
12
|
+
});
|
|
13
|
+
export default Title;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { memo } from 'react';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
|
|
6
|
+
import PageTitle from '@/components/PageTitle';
|
|
7
|
+
|
|
8
|
+
const Title = memo(() => {
|
|
9
|
+
const { t } = useTranslation('auth');
|
|
10
|
+
|
|
11
|
+
return <PageTitle title={t('signup')} />;
|
|
12
|
+
});
|
|
13
|
+
export default Title;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { memo } from 'react';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
|
|
6
|
+
import PageTitle from '@/components/PageTitle';
|
|
7
|
+
|
|
8
|
+
const Title = memo(() => {
|
|
9
|
+
const { t } = useTranslation('auth');
|
|
10
|
+
|
|
11
|
+
return <PageTitle title={t('signup')} />;
|
|
12
|
+
});
|
|
13
|
+
export default Title;
|
|
@@ -21,7 +21,7 @@ type SettingItemGroup = ItemGroup;
|
|
|
21
21
|
|
|
22
22
|
export interface SettingsCommonProps {
|
|
23
23
|
showAccessCodeConfig: boolean;
|
|
24
|
-
showOAuthLogin
|
|
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
|
|
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('
|
|
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).
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
49
|
-
if (
|
|
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
|
|
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 &&
|
|
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 };
|