@lobehub/lobehub 2.0.0-next.220 ā 2.0.0-next.222
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/.github/workflows/claude-auto-testing.yml +6 -3
- package/.github/workflows/claude-dedupe-issues.yml +8 -1
- package/.github/workflows/claude-issue-triage.yml +8 -14
- package/.github/workflows/claude-translate-comments.yml +6 -3
- package/.github/workflows/claude-translator.yml +12 -14
- package/.github/workflows/claude.yml +10 -20
- package/.github/workflows/test.yml +26 -0
- package/.i18nrc.js +4 -2
- package/CHANGELOG.md +66 -0
- package/apps/desktop/src/main/core/browser/Browser.ts +48 -20
- package/apps/desktop/src/main/core/browser/__tests__/Browser.test.ts +1 -0
- package/changelog/v1.json +21 -0
- package/docs/glossary.md +11 -0
- package/locales/zh-CN/components.json +1 -0
- package/locales/zh-CN/topic.json +5 -5
- package/package.json +3 -3
- package/packages/const/src/index.ts +0 -1
- package/packages/memory-user-memory/package.json +1 -0
- package/packages/memory-user-memory/src/extractors/context.test.ts +3 -2
- package/packages/memory-user-memory/src/extractors/experience.test.ts +3 -2
- package/packages/memory-user-memory/src/extractors/identity.test.ts +23 -6
- package/packages/memory-user-memory/src/extractors/preference.test.ts +3 -2
- package/packages/memory-user-memory/vitest.config.ts +4 -0
- package/packages/model-runtime/src/providers/replicate/index.ts +1 -1
- package/packages/ssrf-safe-fetch/index.test.ts +2 -2
- package/packages/ssrf-safe-fetch/package.json +3 -2
- package/packages/types/src/serverConfig.ts +2 -0
- package/packages/utils/package.json +1 -1
- package/packages/utils/src/client/xor-obfuscation.test.ts +32 -32
- package/packages/utils/src/client/xor-obfuscation.ts +3 -4
- package/packages/utils/src/imageToBase64.ts +1 -1
- package/packages/utils/src/server/__tests__/auth.test.ts +1 -1
- package/packages/utils/src/server/auth.ts +1 -1
- package/packages/utils/src/server/xor.test.ts +9 -7
- package/packages/utils/src/server/xor.ts +1 -1
- package/packages/web-crawler/package.json +1 -1
- package/packages/web-crawler/src/crawImpl/__tests__/naive.test.ts +1 -1
- package/packages/web-crawler/src/crawImpl/naive.ts +1 -1
- package/scripts/prebuild.mts +58 -1
- package/src/app/(backend)/api/auth/[...all]/route.ts +2 -1
- package/src/app/(backend)/middleware/auth/index.ts +3 -3
- package/src/app/(backend)/middleware/auth/utils.test.ts +1 -1
- package/src/app/(backend)/middleware/auth/utils.ts +1 -1
- package/src/app/(backend)/webapi/chat/[provider]/route.test.ts +2 -2
- package/src/app/(backend)/webapi/models/[provider]/route.test.ts +1 -1
- package/src/app/(backend)/webapi/plugin/gateway/route.ts +1 -1
- package/src/app/(backend)/webapi/proxy/route.ts +1 -1
- package/src/app/[variants]/(auth)/login/[[...login]]/page.tsx +1 -1
- package/src/app/[variants]/(auth)/reset-password/layout.tsx +1 -1
- package/src/app/[variants]/(auth)/signin/layout.tsx +1 -1
- package/src/app/[variants]/(auth)/signin/useSignIn.ts +2 -2
- package/src/app/[variants]/(auth)/signup/[[...signup]]/page.tsx +1 -1
- package/src/app/[variants]/(auth)/signup/[[...signup]]/useSignUp.tsx +12 -6
- package/src/app/[variants]/(auth)/verify-email/layout.tsx +1 -1
- package/src/app/[variants]/(main)/settings/profile/features/AvatarRow.tsx +1 -1
- package/src/app/[variants]/(main)/settings/security/index.tsx +1 -1
- package/src/app/[variants]/(mobile)/me/(home)/__tests__/UserBanner.test.tsx +1 -1
- package/src/app/[variants]/(mobile)/me/(home)/__tests__/useCategory.test.tsx +1 -1
- package/src/app/[variants]/(mobile)/me/(home)/features/UserBanner.tsx +1 -1
- package/src/app/[variants]/(mobile)/settings/_layout/Header.tsx +1 -1
- package/src/components/ModelSelect/index.tsx +103 -72
- package/src/envs/auth.ts +30 -9
- package/src/features/Conversation/Messages/AssistantGroup/components/EditState.tsx +15 -32
- package/src/features/Conversation/Messages/AssistantGroup/index.tsx +9 -7
- package/src/features/EditorModal/EditorCanvas.tsx +31 -50
- package/src/features/EditorModal/TextareCanvas.tsx +3 -1
- package/src/features/EditorModal/index.tsx +14 -4
- package/src/features/ModelSwitchPanel/components/Footer.tsx +42 -0
- package/src/features/ModelSwitchPanel/components/List/MultipleProvidersModelItem.tsx +103 -0
- package/src/features/ModelSwitchPanel/components/List/SingleProviderModelItem.tsx +24 -0
- package/src/features/ModelSwitchPanel/components/List/VirtualItemRenderer.tsx +180 -0
- package/src/features/ModelSwitchPanel/components/List/index.tsx +99 -0
- package/src/features/ModelSwitchPanel/components/PanelContent.tsx +77 -0
- package/src/features/ModelSwitchPanel/components/Toolbar.tsx +54 -0
- package/src/features/ModelSwitchPanel/const.ts +29 -0
- package/src/features/ModelSwitchPanel/hooks/useBuildVirtualItems.ts +122 -0
- package/src/features/ModelSwitchPanel/hooks/useCurrentModelName.ts +18 -0
- package/src/features/ModelSwitchPanel/hooks/useDelayedRender.ts +18 -0
- package/src/features/ModelSwitchPanel/hooks/useModelAndProvider.ts +14 -0
- package/src/features/ModelSwitchPanel/hooks/usePanelHandlers.ts +33 -0
- package/src/features/ModelSwitchPanel/hooks/usePanelSize.ts +33 -0
- package/src/features/ModelSwitchPanel/hooks/usePanelState.ts +20 -0
- package/src/features/ModelSwitchPanel/index.tsx +25 -706
- package/src/features/ModelSwitchPanel/styles.ts +58 -0
- package/src/features/ModelSwitchPanel/types.ts +73 -0
- package/src/features/ModelSwitchPanel/utils.ts +24 -0
- package/src/features/User/UserPanel/PanelContent.tsx +1 -1
- package/src/features/User/__tests__/PanelContent.test.tsx +1 -1
- package/src/features/User/__tests__/UserAvatar.test.tsx +1 -1
- package/src/features/User/__tests__/useMenu.test.tsx +1 -1
- package/src/layout/GlobalProvider/StoreInitialization.tsx +2 -1
- package/src/libs/better-auth/auth-client.ts +7 -3
- package/src/libs/better-auth/define-config.ts +2 -2
- package/src/libs/next/proxy/define-config.ts +1 -2
- package/src/libs/oidc-provider/provider.test.ts +1 -1
- package/src/libs/trpc/async/context.ts +1 -1
- package/src/libs/trpc/lambda/context.ts +7 -8
- package/src/libs/trpc/middleware/userAuth.ts +1 -1
- package/src/libs/trusted-client/getSessionUser.ts +1 -1
- package/src/locales/default/components.ts +1 -0
- package/src/server/globalConfig/index.ts +2 -0
- package/src/server/routers/async/caller.ts +1 -1
- package/src/server/routers/lambda/__tests__/user.test.ts +2 -2
- package/src/server/routers/lambda/notebook.ts +4 -2
- package/src/server/routers/lambda/user.ts +2 -1
- package/src/services/_auth.ts +3 -3
- package/src/services/chat/index.ts +1 -1
- package/src/services/chat/mecha/contextEngineering.ts +1 -1
- package/src/services/notebook.ts +2 -0
- package/src/store/global/initialState.ts +10 -0
- package/src/store/global/selectors/systemStatus.ts +5 -0
- package/src/store/serverConfig/selectors.ts +5 -1
- package/src/store/tool/slices/builtin/executors/lobe-web-browsing.ts +2 -0
- package/src/store/tool/slices/mcpStore/action.ts +74 -75
- package/src/store/user/slices/auth/action.test.ts +1 -1
- package/src/store/user/slices/auth/action.ts +1 -1
- package/src/store/user/slices/auth/initialState.ts +1 -1
- package/src/store/user/slices/auth/selectors.test.ts +1 -1
- package/src/store/user/slices/auth/selectors.ts +1 -1
- package/src/store/user/slices/common/action.ts +1 -1
- package/src/store/userMemory/slices/context/action.ts +6 -6
- package/glossary.json +0 -8
- package/packages/const/src/auth.ts +0 -14
|
@@ -40,14 +40,31 @@ describe('IdentityExtractor', () => {
|
|
|
40
40
|
|
|
41
41
|
it('uses structuredCall to invoke the runtime and parse structured results', async () => {
|
|
42
42
|
const extractor = new IdentityExtractor(extractorConfig);
|
|
43
|
+
// Mock data matching IdentityActionsSchema structure
|
|
43
44
|
const structuredResult = {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
add: [
|
|
46
|
+
{
|
|
47
|
+
details: null,
|
|
48
|
+
memoryCategory: 'personal',
|
|
49
|
+
memoryLayer: 'identity',
|
|
50
|
+
memoryType: 'fact',
|
|
51
|
+
summary: 'New identity summary',
|
|
52
|
+
tags: ['tag'],
|
|
53
|
+
title: 'New identity',
|
|
54
|
+
withIdentity: {
|
|
55
|
+
description: 'New identity description',
|
|
56
|
+
episodicDate: null,
|
|
57
|
+
extractedLabels: ['tag'],
|
|
58
|
+
relationship: 'self',
|
|
59
|
+
role: 'developer',
|
|
60
|
+
scoreConfidence: 0.8,
|
|
61
|
+
sourceEvidence: null,
|
|
62
|
+
type: 'personal',
|
|
63
|
+
},
|
|
49
64
|
},
|
|
50
|
-
|
|
65
|
+
],
|
|
66
|
+
remove: null,
|
|
67
|
+
update: null,
|
|
51
68
|
};
|
|
52
69
|
(runtimeMock.generateObject as any) = vi.fn().mockResolvedValue(structuredResult);
|
|
53
70
|
|
|
@@ -3,7 +3,7 @@ import type { ModelRuntime } from '@lobechat/model-runtime';
|
|
|
3
3
|
import { readFile } from 'node:fs/promises';
|
|
4
4
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { memoryTypeValues } from '../schemas';
|
|
7
7
|
import type { ExtractorTemplateProps } from '../types';
|
|
8
8
|
import { PreferenceExtractor } from './preference';
|
|
9
9
|
|
|
@@ -40,7 +40,8 @@ describe('PreferenceExtractor', () => {
|
|
|
40
40
|
|
|
41
41
|
expect(memories.type).toBe('array');
|
|
42
42
|
expect(memoryItem.properties.memoryLayer.const).toBe('preference');
|
|
43
|
-
|
|
43
|
+
// memoryCategory is a plain string in schema, not an enum
|
|
44
|
+
expect(memoryItem.properties.memoryCategory.type).toBe('string');
|
|
44
45
|
expect(memoryItem.properties.memoryType.enum).toEqual(memoryTypeValues);
|
|
45
46
|
expect((schema?.schema as any).additionalProperties).toBe(false);
|
|
46
47
|
});
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
1
2
|
import { defineConfig } from 'vitest/config';
|
|
2
3
|
|
|
3
4
|
export default defineConfig({
|
|
4
5
|
test: {
|
|
6
|
+
alias: {
|
|
7
|
+
'@': resolve(__dirname, '../../src'),
|
|
8
|
+
},
|
|
5
9
|
coverage: {
|
|
6
10
|
reporter: ['text', 'json', 'lcov', 'text-summary'],
|
|
7
11
|
},
|
|
@@ -160,7 +160,7 @@ export class LobeReplicateAI implements LobeRuntimeAI {
|
|
|
160
160
|
'[Replicate createImage] Local URL detected, will fetch and upload as data',
|
|
161
161
|
);
|
|
162
162
|
try {
|
|
163
|
-
const { ssrfSafeFetch } = await import('ssrf-safe-fetch');
|
|
163
|
+
const { ssrfSafeFetch } = await import('@lobechat/ssrf-safe-fetch');
|
|
164
164
|
const imageResponse = await ssrfSafeFetch(imageUrl);
|
|
165
165
|
if (!imageResponse.ok) {
|
|
166
166
|
throw new Error(
|
|
@@ -117,7 +117,7 @@ describe('ssrfSafeFetch', () => {
|
|
|
117
117
|
});
|
|
118
118
|
|
|
119
119
|
it('should allow private IPs when SSRF_ALLOW_PRIVATE_IP_ADDRESS is true', async () => {
|
|
120
|
-
process.env.SSRF_ALLOW_PRIVATE_IP_ADDRESS = '
|
|
120
|
+
process.env.SSRF_ALLOW_PRIVATE_IP_ADDRESS = '1';
|
|
121
121
|
|
|
122
122
|
const mockResponse = createMockResponse();
|
|
123
123
|
mockFetch.mockResolvedValue(mockResponse);
|
|
@@ -277,7 +277,7 @@ describe('ssrfSafeFetch', () => {
|
|
|
277
277
|
describe('integration scenarios', () => {
|
|
278
278
|
it('should work with complex request configurations', async () => {
|
|
279
279
|
process.env.SSRF_ALLOW_IP_ADDRESS_LIST = '127.0.0.1';
|
|
280
|
-
process.env.SSRF_ALLOW_PRIVATE_IP_ADDRESS = '
|
|
280
|
+
process.env.SSRF_ALLOW_PRIVATE_IP_ADDRESS = '1';
|
|
281
281
|
|
|
282
282
|
const mockResponse = createMockResponse({
|
|
283
283
|
// @ts-ignore
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "ssrf-safe-fetch",
|
|
2
|
+
"name": "@lobechat/ssrf-safe-fetch",
|
|
3
3
|
"version": "1.0.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "SSRF-safe fetch implementation with browser/node conditional exports",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
},
|
|
13
13
|
"main": "index.ts",
|
|
14
14
|
"scripts": {
|
|
15
|
-
"test": "vitest run"
|
|
15
|
+
"test": "vitest run",
|
|
16
|
+
"test:coverage": "vitest --coverage --silent='passed-only'"
|
|
16
17
|
},
|
|
17
18
|
"dependencies": {
|
|
18
19
|
"node-fetch": "^3.3.2",
|
|
@@ -49,7 +49,9 @@ export type ServerLanguageModel = Partial<Record<GlobalLLMProviderKey, ServerMod
|
|
|
49
49
|
export interface GlobalServerConfig {
|
|
50
50
|
aiProvider: ServerLanguageModel;
|
|
51
51
|
defaultAgent?: PartialDeep<UserDefaultAgent>;
|
|
52
|
+
enableEmailVerification?: boolean;
|
|
52
53
|
enableKlavis?: boolean;
|
|
54
|
+
enableMagicLink?: boolean;
|
|
53
55
|
enableMarketTrustedClient?: boolean;
|
|
54
56
|
enableUploadFileToServer?: boolean;
|
|
55
57
|
enabledAccessCode?: boolean;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
|
|
3
|
-
import { SECRET_XOR_KEY } from '@/
|
|
3
|
+
import { SECRET_XOR_KEY } from '@/envs/auth';
|
|
4
4
|
|
|
5
5
|
import { obfuscatePayloadWithXOR } from './xor-obfuscation';
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ describe('xor-obfuscation', () => {
|
|
|
8
8
|
describe('obfuscatePayloadWithXOR', () => {
|
|
9
9
|
it('åŗčÆ„åÆ¹ē®åå符串čæč”ę··ę·å¹¶čæåBase64å符串', () => {
|
|
10
10
|
const payload = 'hello world';
|
|
11
|
-
const result = obfuscatePayloadWithXOR(payload);
|
|
11
|
+
const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
|
|
12
12
|
|
|
13
13
|
// éŖčÆčæåå¼ęÆå符串
|
|
14
14
|
expect(typeof result).toBe('string');
|
|
@@ -22,7 +22,7 @@ describe('xor-obfuscation', () => {
|
|
|
22
22
|
|
|
23
23
|
it('åŗčÆ„åÆ¹JSON对豔čæč”ę··ę·', () => {
|
|
24
24
|
const payload = { name: 'test', value: 123, active: true };
|
|
25
|
-
const result = obfuscatePayloadWithXOR(payload);
|
|
25
|
+
const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
|
|
26
26
|
|
|
27
27
|
// éŖčÆčæåå¼ęÆå符串
|
|
28
28
|
expect(typeof result).toBe('string');
|
|
@@ -33,7 +33,7 @@ describe('xor-obfuscation', () => {
|
|
|
33
33
|
|
|
34
34
|
it('åŗčÆ„åÆ¹ę°ē»čæč”ę··ę·', () => {
|
|
35
35
|
const payload = [1, 2, 3, 'test', { nested: true }];
|
|
36
|
-
const result = obfuscatePayloadWithXOR(payload);
|
|
36
|
+
const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
|
|
37
37
|
|
|
38
38
|
// éŖčÆčæåå¼ęÆå符串
|
|
39
39
|
expect(typeof result).toBe('string');
|
|
@@ -58,7 +58,7 @@ describe('xor-obfuscation', () => {
|
|
|
58
58
|
tokens: ['abc123', 'def456'],
|
|
59
59
|
metadata: null,
|
|
60
60
|
};
|
|
61
|
-
const result = obfuscatePayloadWithXOR(payload);
|
|
61
|
+
const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
|
|
62
62
|
|
|
63
63
|
// éŖčÆčæåå¼ęÆå符串
|
|
64
64
|
expect(typeof result).toBe('string');
|
|
@@ -69,8 +69,8 @@ describe('xor-obfuscation', () => {
|
|
|
69
69
|
|
|
70
70
|
it('ēøåēč¾å
„åŗčÆ„äŗ§ēēøåēč¾åŗ', () => {
|
|
71
71
|
const payload = { test: 'consistent' };
|
|
72
|
-
const result1 = obfuscatePayloadWithXOR(payload);
|
|
73
|
-
const result2 = obfuscatePayloadWithXOR(payload);
|
|
72
|
+
const result1 = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
|
|
73
|
+
const result2 = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
|
|
74
74
|
|
|
75
75
|
expect(result1).toBe(result2);
|
|
76
76
|
});
|
|
@@ -79,15 +79,15 @@ describe('xor-obfuscation', () => {
|
|
|
79
79
|
const payload1 = { test: 'value1' };
|
|
80
80
|
const payload2 = { test: 'value2' };
|
|
81
81
|
|
|
82
|
-
const result1 = obfuscatePayloadWithXOR(payload1);
|
|
83
|
-
const result2 = obfuscatePayloadWithXOR(payload2);
|
|
82
|
+
const result1 = obfuscatePayloadWithXOR(payload1, SECRET_XOR_KEY);
|
|
83
|
+
const result2 = obfuscatePayloadWithXOR(payload2, SECRET_XOR_KEY);
|
|
84
84
|
|
|
85
85
|
expect(result1).not.toBe(result2);
|
|
86
86
|
});
|
|
87
87
|
|
|
88
88
|
it('åŗčÆ„å¤ēå
å«ē¹ę®å符ēå符串', () => {
|
|
89
89
|
const payload = 'Hello! @#$%^&*()_+-=[]{}|;:,.<>?/~`"\'\\';
|
|
90
|
-
const result = obfuscatePayloadWithXOR(payload);
|
|
90
|
+
const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
|
|
91
91
|
|
|
92
92
|
// éŖčÆčæåå¼ęÆå符串
|
|
93
93
|
expect(typeof result).toBe('string');
|
|
@@ -98,7 +98,7 @@ describe('xor-obfuscation', () => {
|
|
|
98
98
|
|
|
99
99
|
it('åŗčÆ„å¤ēå
å«Unicodeå符ēå符串', () => {
|
|
100
100
|
const payload = 'ä½ å„½äøē š Ć©mojis ę„ę¬čŖ ķźµģ“';
|
|
101
|
-
const result = obfuscatePayloadWithXOR(payload);
|
|
101
|
+
const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
|
|
102
102
|
|
|
103
103
|
// éŖčÆčæåå¼ęÆå符串
|
|
104
104
|
expect(typeof result).toBe('string');
|
|
@@ -109,7 +109,7 @@ describe('xor-obfuscation', () => {
|
|
|
109
109
|
|
|
110
110
|
it('åŗčÆ„å¤ē空å符串', () => {
|
|
111
111
|
const payload = '';
|
|
112
|
-
const result = obfuscatePayloadWithXOR(payload);
|
|
112
|
+
const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
|
|
113
113
|
|
|
114
114
|
// éŖčÆčæåå¼ęÆå符串
|
|
115
115
|
expect(typeof result).toBe('string');
|
|
@@ -120,7 +120,7 @@ describe('xor-obfuscation', () => {
|
|
|
120
120
|
|
|
121
121
|
it('åŗčÆ„å¤ē空对豔', () => {
|
|
122
122
|
const payload = {};
|
|
123
|
-
const result = obfuscatePayloadWithXOR(payload);
|
|
123
|
+
const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
|
|
124
124
|
|
|
125
125
|
// éŖčÆčæåå¼ęÆå符串
|
|
126
126
|
expect(typeof result).toBe('string');
|
|
@@ -130,7 +130,7 @@ describe('xor-obfuscation', () => {
|
|
|
130
130
|
});
|
|
131
131
|
|
|
132
132
|
it('åŗčÆ„å¤ē空ę°ē»', () => {
|
|
133
|
-
const result = obfuscatePayloadWithXOR([]);
|
|
133
|
+
const result = obfuscatePayloadWithXOR([], SECRET_XOR_KEY);
|
|
134
134
|
|
|
135
135
|
// éŖčÆčæåå¼ęÆå符串
|
|
136
136
|
expect(typeof result).toBe('string');
|
|
@@ -141,7 +141,7 @@ describe('xor-obfuscation', () => {
|
|
|
141
141
|
|
|
142
142
|
it('åŗčÆ„å¤ēnullå¼', () => {
|
|
143
143
|
const payload = null;
|
|
144
|
-
const result = obfuscatePayloadWithXOR(payload);
|
|
144
|
+
const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
|
|
145
145
|
|
|
146
146
|
// éŖčÆčæåå¼ęÆå符串
|
|
147
147
|
expect(typeof result).toBe('string');
|
|
@@ -152,7 +152,7 @@ describe('xor-obfuscation', () => {
|
|
|
152
152
|
|
|
153
153
|
it('åŗčÆ„å¤ēę°å', () => {
|
|
154
154
|
const payload = 42;
|
|
155
|
-
const result = obfuscatePayloadWithXOR(payload);
|
|
155
|
+
const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
|
|
156
156
|
|
|
157
157
|
// éŖčÆčæåå¼ęÆå符串
|
|
158
158
|
expect(typeof result).toBe('string');
|
|
@@ -165,8 +165,8 @@ describe('xor-obfuscation', () => {
|
|
|
165
165
|
const payloadTrue = true;
|
|
166
166
|
const payloadFalse = false;
|
|
167
167
|
|
|
168
|
-
const resultTrue = obfuscatePayloadWithXOR(payloadTrue);
|
|
169
|
-
const resultFalse = obfuscatePayloadWithXOR(payloadFalse);
|
|
168
|
+
const resultTrue = obfuscatePayloadWithXOR(payloadTrue, SECRET_XOR_KEY);
|
|
169
|
+
const resultFalse = obfuscatePayloadWithXOR(payloadFalse, SECRET_XOR_KEY);
|
|
170
170
|
|
|
171
171
|
// éŖčÆčæåå¼ęÆå符串
|
|
172
172
|
expect(typeof resultTrue).toBe('string');
|
|
@@ -189,7 +189,7 @@ describe('xor-obfuscation', () => {
|
|
|
189
189
|
tab: 'col1\tcol2',
|
|
190
190
|
unicode: '\u0041\u0042\u0043',
|
|
191
191
|
};
|
|
192
|
-
const result = obfuscatePayloadWithXOR(payload);
|
|
192
|
+
const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
|
|
193
193
|
|
|
194
194
|
// éŖčÆčæåå¼ęÆå符串
|
|
195
195
|
expect(typeof result).toBe('string');
|
|
@@ -200,7 +200,7 @@ describe('xor-obfuscation', () => {
|
|
|
200
200
|
|
|
201
201
|
it('åŗčÆ„å¤ēå¾éæēå符串', () => {
|
|
202
202
|
const payload = 'a'.repeat(10000);
|
|
203
|
-
const result = obfuscatePayloadWithXOR(payload);
|
|
203
|
+
const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
|
|
204
204
|
|
|
205
205
|
// éŖčÆčæåå¼ęÆå符串
|
|
206
206
|
expect(typeof result).toBe('string');
|
|
@@ -216,8 +216,8 @@ describe('xor-obfuscation', () => {
|
|
|
216
216
|
const shortPayload = 'short';
|
|
217
217
|
const longPayload = 'this is a much longer string that should produce different output';
|
|
218
218
|
|
|
219
|
-
const shortResult = obfuscatePayloadWithXOR(shortPayload);
|
|
220
|
-
const longResult = obfuscatePayloadWithXOR(longPayload);
|
|
219
|
+
const shortResult = obfuscatePayloadWithXOR(shortPayload, SECRET_XOR_KEY);
|
|
220
|
+
const longResult = obfuscatePayloadWithXOR(longPayload, SECRET_XOR_KEY);
|
|
221
221
|
|
|
222
222
|
// č¾éæēč¾å
„åŗčÆ„äŗ§ēč¾éæēč¾åŗ
|
|
223
223
|
expect(longResult.length).toBeGreaterThan(shortResult.length);
|
|
@@ -225,7 +225,7 @@ describe('xor-obfuscation', () => {
|
|
|
225
225
|
|
|
226
226
|
it('åŗčÆ„éŖčÆč¾åŗęÆęęēBase64ę ¼å¼', () => {
|
|
227
227
|
const payload = { test: 'base64 validation' };
|
|
228
|
-
const result = obfuscatePayloadWithXOR(payload);
|
|
228
|
+
const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
|
|
229
229
|
|
|
230
230
|
// éŖčÆBase64ę ¼å¼ēę£å蔨达å¼
|
|
231
231
|
const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
@@ -242,14 +242,14 @@ describe('xor-obfuscation', () => {
|
|
|
242
242
|
},
|
|
243
243
|
};
|
|
244
244
|
|
|
245
|
-
const result = obfuscatePayloadWithXOR(payload);
|
|
245
|
+
const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
|
|
246
246
|
expect(typeof result).toBe('string');
|
|
247
247
|
expect(() => atob(result)).not.toThrow();
|
|
248
248
|
});
|
|
249
249
|
|
|
250
250
|
it('åŗčÆ„åÆ¹undefinedå¼čæč”å¤ē', () => {
|
|
251
251
|
const payload = undefined;
|
|
252
|
-
const result = obfuscatePayloadWithXOR(payload);
|
|
252
|
+
const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
|
|
253
253
|
|
|
254
254
|
// éŖčÆčæåå¼ęÆå符串
|
|
255
255
|
expect(typeof result).toBe('string');
|
|
@@ -268,7 +268,7 @@ describe('xor-obfuscation', () => {
|
|
|
268
268
|
value: 123,
|
|
269
269
|
};
|
|
270
270
|
|
|
271
|
-
const result = obfuscatePayloadWithXOR(payload);
|
|
271
|
+
const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
|
|
272
272
|
expect(typeof result).toBe('string');
|
|
273
273
|
expect(() => atob(result)).not.toThrow();
|
|
274
274
|
});
|
|
@@ -279,7 +279,7 @@ describe('xor-obfuscation', () => {
|
|
|
279
279
|
|
|
280
280
|
// å¤ę¬”čæč”ēøåč¾å
„
|
|
281
281
|
for (let i = 0; i < 10; i++) {
|
|
282
|
-
results.push(obfuscatePayloadWithXOR(payload));
|
|
282
|
+
results.push(obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY));
|
|
283
283
|
}
|
|
284
284
|
|
|
285
285
|
// ęęē»ęåŗčÆ„ēøå
|
|
@@ -293,7 +293,7 @@ describe('xor-obfuscation', () => {
|
|
|
293
293
|
name: 'date test',
|
|
294
294
|
};
|
|
295
295
|
|
|
296
|
-
const result = obfuscatePayloadWithXOR(payload);
|
|
296
|
+
const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
|
|
297
297
|
expect(typeof result).toBe('string');
|
|
298
298
|
expect(() => atob(result)).not.toThrow();
|
|
299
299
|
});
|
|
@@ -306,7 +306,7 @@ describe('xor-obfuscation', () => {
|
|
|
306
306
|
normalKey: 'normal value',
|
|
307
307
|
};
|
|
308
308
|
|
|
309
|
-
const result = obfuscatePayloadWithXOR(payload);
|
|
309
|
+
const result = obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY);
|
|
310
310
|
expect(typeof result).toBe('string');
|
|
311
311
|
expect(() => atob(result)).not.toThrow();
|
|
312
312
|
});
|
|
@@ -314,7 +314,7 @@ describe('xor-obfuscation', () => {
|
|
|
314
314
|
it('åŗčÆ„éŖčÆę··ę·åēę°ę®éæåŗ¦åēę§', () => {
|
|
315
315
|
const originalPayload = { test: 'length check' };
|
|
316
316
|
const originalJSON = JSON.stringify(originalPayload);
|
|
317
|
-
const result = obfuscatePayloadWithXOR(originalPayload);
|
|
317
|
+
const result = obfuscatePayloadWithXOR(originalPayload, SECRET_XOR_KEY);
|
|
318
318
|
|
|
319
319
|
// Base64 ē¼ē åēéæåŗ¦éåøøęÆåå§éæåŗ¦ē 4/3 åļ¼åäøåę“å°4ēåę°ļ¼
|
|
320
320
|
const expectedMinLength = Math.ceil((originalJSON.length * 4) / 3 / 4) * 4;
|
|
@@ -323,7 +323,7 @@ describe('xor-obfuscation', () => {
|
|
|
323
323
|
|
|
324
324
|
it('åŗčÆ„éŖčÆXORęä½ēę£ē”®ę§ļ¼éčæéåęä½ļ¼', () => {
|
|
325
325
|
const originalPayload = { message: 'XOR test', value: 42 };
|
|
326
|
-
const obfuscatedResult = obfuscatePayloadWithXOR(originalPayload);
|
|
326
|
+
const obfuscatedResult = obfuscatePayloadWithXOR(originalPayload, SECRET_XOR_KEY);
|
|
327
327
|
|
|
328
328
|
// ęåØå®ē°éåęä½ę„éŖčÆ XOR ęä½ēę£ē”®ę§
|
|
329
329
|
const base64Decoded = atob(obfuscatedResult);
|
|
@@ -357,7 +357,7 @@ describe('xor-obfuscation', () => {
|
|
|
357
357
|
[4, 5, 6],
|
|
358
358
|
];
|
|
359
359
|
|
|
360
|
-
const results = payloads.map((payload) => obfuscatePayloadWithXOR(payload));
|
|
360
|
+
const results = payloads.map((payload) => obfuscatePayloadWithXOR(payload, SECRET_XOR_KEY));
|
|
361
361
|
|
|
362
362
|
// éŖčÆęęē»ęé½äøēøå
|
|
363
363
|
for (let i = 0; i < results.length; i++) {
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { SECRET_XOR_KEY } from '@/const/auth';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Convert string to Uint8Array (UTF-8 encoding)
|
|
5
3
|
*/
|
|
@@ -24,12 +22,13 @@ const xorProcess = (data: Uint8Array, key: Uint8Array): Uint8Array => {
|
|
|
24
22
|
/**
|
|
25
23
|
* Obfuscate payload with XOR and encode to Base64
|
|
26
24
|
* @param payload The JSON object to obfuscate
|
|
25
|
+
* @param secretKey The key used for XOR obfuscation
|
|
27
26
|
* @returns The obfuscated string encoded in Base64
|
|
28
27
|
*/
|
|
29
|
-
export const obfuscatePayloadWithXOR = <T>(payload: T): string => {
|
|
28
|
+
export const obfuscatePayloadWithXOR = <T>(payload: T, secretKey: string): string => {
|
|
30
29
|
const jsonString = JSON.stringify(payload);
|
|
31
30
|
const dataBytes = stringToUint8Array(jsonString);
|
|
32
|
-
const keyBytes = stringToUint8Array(
|
|
31
|
+
const keyBytes = stringToUint8Array(secretKey);
|
|
33
32
|
|
|
34
33
|
const xoredBytes = xorProcess(dataBytes, keyBytes);
|
|
35
34
|
|
|
@@ -48,7 +48,7 @@ export const imageUrlToBase64 = async (
|
|
|
48
48
|
|
|
49
49
|
// Use SSRF-safe fetch on server-side to prevent SSRF attacks
|
|
50
50
|
const res = isServer
|
|
51
|
-
? await import('ssrf-safe-fetch').then((m) => m.ssrfSafeFetch(imageUrl))
|
|
51
|
+
? await import('@lobechat/ssrf-safe-fetch').then((m) => m.ssrfSafeFetch(imageUrl))
|
|
52
52
|
: await fetch(imageUrl);
|
|
53
53
|
|
|
54
54
|
const blob = await res.blob();
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
|
|
3
|
+
import { SECRET_XOR_KEY } from '@/envs/auth';
|
|
4
|
+
|
|
3
5
|
import { obfuscatePayloadWithXOR } from '../client/xor-obfuscation';
|
|
4
6
|
import { getXorPayload } from './xor';
|
|
5
7
|
|
|
@@ -12,7 +14,7 @@ describe('getXorPayload', () => {
|
|
|
12
14
|
};
|
|
13
15
|
|
|
14
16
|
// 使ēØå®¢ę·ē«Æēę··ę·å½ę°ēętoken
|
|
15
|
-
const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload);
|
|
17
|
+
const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload, SECRET_XOR_KEY);
|
|
16
18
|
|
|
17
19
|
// 使ēØęå”端ēč§£ē å½ę°č§£ē
|
|
18
20
|
const decodedPayload = getXorPayload(obfuscatedToken);
|
|
@@ -25,7 +27,7 @@ describe('getXorPayload', () => {
|
|
|
25
27
|
userId: '12345',
|
|
26
28
|
};
|
|
27
29
|
|
|
28
|
-
const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload);
|
|
30
|
+
const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload, SECRET_XOR_KEY);
|
|
29
31
|
const decodedPayload = getXorPayload(obfuscatedToken);
|
|
30
32
|
|
|
31
33
|
expect(decodedPayload).toEqual(originalPayload);
|
|
@@ -40,7 +42,7 @@ describe('getXorPayload', () => {
|
|
|
40
42
|
awsSessionToken: 'session-token-example',
|
|
41
43
|
};
|
|
42
44
|
|
|
43
|
-
const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload);
|
|
45
|
+
const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload, SECRET_XOR_KEY);
|
|
44
46
|
const decodedPayload = getXorPayload(obfuscatedToken);
|
|
45
47
|
|
|
46
48
|
expect(decodedPayload).toEqual(originalPayload);
|
|
@@ -54,7 +56,7 @@ describe('getXorPayload', () => {
|
|
|
54
56
|
azureApiVersion: '2024-02-15-preview',
|
|
55
57
|
};
|
|
56
58
|
|
|
57
|
-
const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload);
|
|
59
|
+
const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload, SECRET_XOR_KEY);
|
|
58
60
|
const decodedPayload = getXorPayload(obfuscatedToken);
|
|
59
61
|
|
|
60
62
|
expect(decodedPayload).toEqual(originalPayload);
|
|
@@ -67,7 +69,7 @@ describe('getXorPayload', () => {
|
|
|
67
69
|
cloudflareBaseURLOrAccountID: 'account-id-example',
|
|
68
70
|
};
|
|
69
71
|
|
|
70
|
-
const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload);
|
|
72
|
+
const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload, SECRET_XOR_KEY);
|
|
71
73
|
const decodedPayload = getXorPayload(obfuscatedToken);
|
|
72
74
|
|
|
73
75
|
expect(decodedPayload).toEqual(originalPayload);
|
|
@@ -76,7 +78,7 @@ describe('getXorPayload', () => {
|
|
|
76
78
|
it('should handle empty payload correctly', () => {
|
|
77
79
|
const originalPayload = {};
|
|
78
80
|
|
|
79
|
-
const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload);
|
|
81
|
+
const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload, SECRET_XOR_KEY);
|
|
80
82
|
const decodedPayload = getXorPayload(obfuscatedToken);
|
|
81
83
|
|
|
82
84
|
expect(decodedPayload).toEqual(originalPayload);
|
|
@@ -89,7 +91,7 @@ describe('getXorPayload', () => {
|
|
|
89
91
|
apiKey: 'test-key',
|
|
90
92
|
};
|
|
91
93
|
|
|
92
|
-
const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload);
|
|
94
|
+
const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload, SECRET_XOR_KEY);
|
|
93
95
|
const decodedPayload = getXorPayload(obfuscatedToken);
|
|
94
96
|
|
|
95
97
|
expect(decodedPayload).toEqual(originalPayload);
|
package/scripts/prebuild.mts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
1
2
|
import * as dotenv from 'dotenv';
|
|
2
3
|
import dotenvExpand from 'dotenv-expand';
|
|
3
4
|
import { existsSync } from 'node:fs';
|
|
@@ -14,6 +15,61 @@ if (isDesktop) {
|
|
|
14
15
|
} else {
|
|
15
16
|
dotenvExpand.expand(dotenv.config());
|
|
16
17
|
}
|
|
18
|
+
|
|
19
|
+
// Auth flags - use process.env directly for build-time dead code elimination
|
|
20
|
+
const enableClerk =
|
|
21
|
+
process.env.NEXT_PUBLIC_ENABLE_CLERK_AUTH === '1'
|
|
22
|
+
? true
|
|
23
|
+
: !!process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY;
|
|
24
|
+
const enableBetterAuth = process.env.NEXT_PUBLIC_ENABLE_BETTER_AUTH === '1';
|
|
25
|
+
const enableNextAuth = process.env.NEXT_PUBLIC_ENABLE_NEXT_AUTH === '1';
|
|
26
|
+
const enableAuth = enableClerk || enableBetterAuth || enableNextAuth || false;
|
|
27
|
+
|
|
28
|
+
const getCommandVersion = (command: string): string | null => {
|
|
29
|
+
try {
|
|
30
|
+
return execSync(`${command} --version`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] })
|
|
31
|
+
.trim()
|
|
32
|
+
.split('\n')[0];
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const printEnvInfo = () => {
|
|
39
|
+
console.log('\nš Build Environment Info:');
|
|
40
|
+
console.log('ā'.repeat(50));
|
|
41
|
+
|
|
42
|
+
// Runtime versions
|
|
43
|
+
console.log(` Node.js: ${process.version}`);
|
|
44
|
+
console.log(` npm: ${getCommandVersion('npm') ?? 'not installed'}`);
|
|
45
|
+
|
|
46
|
+
const bunVersion = getCommandVersion('bun');
|
|
47
|
+
if (bunVersion) console.log(` bun: ${bunVersion}`);
|
|
48
|
+
|
|
49
|
+
const pnpmVersion = getCommandVersion('pnpm');
|
|
50
|
+
if (pnpmVersion) console.log(` pnpm: ${pnpmVersion}`);
|
|
51
|
+
|
|
52
|
+
// Auth-related env vars
|
|
53
|
+
console.log('\n Auth Environment Variables:');
|
|
54
|
+
console.log(` NEXT_PUBLIC_AUTH_URL: ${process.env.NEXT_PUBLIC_AUTH_URL ?? '(not set)'}`);
|
|
55
|
+
console.log(` NEXTAUTH_URL: ${process.env.NEXTAUTH_URL ?? '(not set)'}`);
|
|
56
|
+
console.log(` APP_URL: ${process.env.APP_URL ?? '(not set)'}`);
|
|
57
|
+
console.log(` VERCEL_URL: ${process.env.VERCEL_URL ?? '(not set)'}`);
|
|
58
|
+
console.log(` AUTH_EMAIL_VERIFICATION: ${process.env.AUTH_EMAIL_VERIFICATION ?? '(not set)'}`);
|
|
59
|
+
console.log(` ENABLE_MAGIC_LINK: ${process.env.ENABLE_MAGIC_LINK ?? '(not set)'}`);
|
|
60
|
+
console.log(` AUTH_SECRET: ${process.env.AUTH_SECRET ? 'ā set' : 'ā not set'}`);
|
|
61
|
+
console.log(` KEY_VAULTS_SECRET: ${process.env.KEY_VAULTS_SECRET ? 'ā set' : 'ā not set'}`);
|
|
62
|
+
|
|
63
|
+
// Auth flags
|
|
64
|
+
console.log('\n Auth Flags:');
|
|
65
|
+
console.log(` enableClerk: ${enableClerk}`);
|
|
66
|
+
console.log(` enableBetterAuth: ${enableBetterAuth}`);
|
|
67
|
+
console.log(` enableNextAuth: ${enableNextAuth}`);
|
|
68
|
+
console.log(` enableAuth: ${enableAuth}`);
|
|
69
|
+
|
|
70
|
+
console.log('ā'.repeat(50));
|
|
71
|
+
};
|
|
72
|
+
|
|
17
73
|
// å建éč¦ęé¤ēē¹ę§ę å°
|
|
18
74
|
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
|
19
75
|
const partialBuildPages = [
|
|
@@ -105,8 +161,9 @@ export const runPrebuild = async (targetDir: string = 'src') => {
|
|
|
105
161
|
const isMainModule = process.argv[1] === fileURLToPath(import.meta.url);
|
|
106
162
|
|
|
107
163
|
if (isMainModule) {
|
|
164
|
+
printEnvInfo();
|
|
108
165
|
// ę§č”å é¤ęä½
|
|
109
|
-
console.log('
|
|
166
|
+
console.log('\nStarting prebuild cleanup...');
|
|
110
167
|
await runPrebuild();
|
|
111
168
|
console.log('Prebuild cleanup completed.');
|
|
112
169
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { enableBetterAuth, enableNextAuth } from '@lobechat/const';
|
|
2
1
|
import type { NextRequest } from 'next/server';
|
|
3
2
|
|
|
3
|
+
import { enableBetterAuth, enableNextAuth } from '@/envs/auth';
|
|
4
|
+
|
|
4
5
|
const createHandler = async () => {
|
|
5
6
|
if (enableBetterAuth) {
|
|
6
7
|
const [{ toNextJsHandler }, { auth }] = await Promise.all([
|
|
@@ -8,15 +8,15 @@ import { ChatErrorType, type ClientSecretPayload } from '@lobechat/types';
|
|
|
8
8
|
import { getXorPayload } from '@lobechat/utils/server';
|
|
9
9
|
import { type NextRequest } from 'next/server';
|
|
10
10
|
|
|
11
|
+
import { getServerDB } from '@/database/core/db-adaptor';
|
|
12
|
+
import { type LobeChatDatabase } from '@/database/type';
|
|
11
13
|
import {
|
|
12
14
|
LOBE_CHAT_AUTH_HEADER,
|
|
13
15
|
LOBE_CHAT_OIDC_AUTH_HEADER,
|
|
14
16
|
OAUTH_AUTHORIZED,
|
|
15
17
|
enableBetterAuth,
|
|
16
18
|
enableClerk,
|
|
17
|
-
} from '@/
|
|
18
|
-
import { getServerDB } from '@/database/core/db-adaptor';
|
|
19
|
-
import { type LobeChatDatabase } from '@/database/type';
|
|
19
|
+
} from '@/envs/auth';
|
|
20
20
|
import { ClerkAuth } from '@/libs/clerk-auth';
|
|
21
21
|
import { validateOIDCJWT } from '@/libs/oidc-provider/jwt';
|
|
22
22
|
import { createErrorResponse } from '@/utils/errorResponse';
|