@lobehub/chat 1.105.1 → 1.105.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.
Files changed (60) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/changelog/v1.json +9 -0
  3. package/locales/ar/auth.json +54 -0
  4. package/locales/bg-BG/auth.json +54 -0
  5. package/locales/de-DE/auth.json +54 -0
  6. package/locales/en-US/auth.json +54 -0
  7. package/locales/es-ES/auth.json +54 -0
  8. package/locales/fa-IR/auth.json +54 -0
  9. package/locales/fr-FR/auth.json +54 -0
  10. package/locales/it-IT/auth.json +54 -0
  11. package/locales/ja-JP/auth.json +54 -0
  12. package/locales/ko-KR/auth.json +54 -0
  13. package/locales/nl-NL/auth.json +54 -0
  14. package/locales/pl-PL/auth.json +54 -0
  15. package/locales/pt-BR/auth.json +54 -0
  16. package/locales/ru-RU/auth.json +54 -0
  17. package/locales/tr-TR/auth.json +54 -0
  18. package/locales/vi-VN/auth.json +54 -0
  19. package/locales/zh-CN/auth.json +54 -0
  20. package/locales/zh-TW/auth.json +54 -0
  21. package/package.json +2 -2
  22. package/src/app/(backend)/middleware/auth/index.test.ts +5 -5
  23. package/src/app/(backend)/middleware/auth/index.ts +6 -6
  24. package/src/app/(backend)/webapi/chat/[provider]/route.test.ts +11 -9
  25. package/src/app/(backend)/webapi/plugin/gateway/route.ts +2 -2
  26. package/src/app/sitemap.tsx +1 -10
  27. package/src/config/aiModels/giteeai.ts +269 -2
  28. package/src/config/aiModels/siliconcloud.ts +24 -2
  29. package/src/config/aiModels/stepfun.ts +67 -2
  30. package/src/config/aiModels/volcengine.ts +56 -2
  31. package/src/config/aiModels/wenxin.ts +62 -2
  32. package/src/config/aiModels/xai.ts +19 -2
  33. package/src/const/auth.ts +2 -3
  34. package/src/libs/model-runtime/ModelRuntime.test.ts +3 -3
  35. package/src/libs/trpc/async/context.ts +3 -3
  36. package/src/libs/trpc/edge/context.ts +7 -2
  37. package/src/libs/trpc/edge/middleware/jwtPayload.test.ts +4 -4
  38. package/src/libs/trpc/edge/middleware/jwtPayload.ts +2 -2
  39. package/src/libs/trpc/lambda/context.ts +2 -2
  40. package/src/libs/trpc/lambda/middleware/keyVaults.ts +2 -2
  41. package/src/server/modules/AgentRuntime/index.test.ts +28 -25
  42. package/src/server/modules/AgentRuntime/index.ts +3 -3
  43. package/src/server/routers/async/caller.ts +2 -2
  44. package/src/server/routers/lambda/market/index.ts +0 -14
  45. package/src/server/routers/tools/search.test.ts +2 -2
  46. package/src/server/services/chunk/index.ts +3 -3
  47. package/src/server/services/discover/index.ts +0 -13
  48. package/src/server/sitemap.test.ts +0 -52
  49. package/src/server/sitemap.ts +1 -38
  50. package/src/services/_auth.ts +3 -3
  51. package/src/services/discover.ts +0 -4
  52. package/src/store/discover/slices/mcp/action.ts +0 -8
  53. package/src/utils/client/xor-obfuscation.test.ts +370 -0
  54. package/src/utils/client/xor-obfuscation.ts +39 -0
  55. package/src/utils/server/xor.test.ts +123 -0
  56. package/src/utils/server/xor.ts +42 -0
  57. package/src/utils/jwt.test.ts +0 -27
  58. package/src/utils/jwt.ts +0 -37
  59. package/src/utils/server/jwt.test.ts +0 -62
  60. package/src/utils/server/jwt.ts +0 -28
@@ -0,0 +1,123 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { obfuscatePayloadWithXOR } from '@/utils/client/xor-obfuscation';
4
+
5
+ import { getXorPayload } from './xor';
6
+
7
+ describe('getXorPayload', () => {
8
+ it('should correctly decode XOR obfuscated payload with user data', () => {
9
+ const originalPayload = {
10
+ userId: '001362c3-48c5-4635-bd3b-837bfff58fc0',
11
+ accessCode: 'test-access-code',
12
+ apiKey: 'test-api-key',
13
+ baseURL: 'https://api.example.com',
14
+ };
15
+
16
+ // 使用客户端的混淆函数生成token
17
+ const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload);
18
+
19
+ // 使用服务端的解码函数解码
20
+ const decodedPayload = getXorPayload(obfuscatedToken);
21
+
22
+ expect(decodedPayload).toEqual(originalPayload);
23
+ });
24
+
25
+ it('should correctly decode XOR obfuscated payload with minimal data', () => {
26
+ const originalPayload = {
27
+ userId: '12345',
28
+ };
29
+
30
+ const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload);
31
+ const decodedPayload = getXorPayload(obfuscatedToken);
32
+
33
+ expect(decodedPayload).toEqual(originalPayload);
34
+ });
35
+
36
+ it('should correctly decode XOR obfuscated payload with AWS credentials', () => {
37
+ const originalPayload = {
38
+ userId: 'aws-user-123',
39
+ awsAccessKeyId: 'AKIAIOSFODNN7EXAMPLE',
40
+ awsSecretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
41
+ awsRegion: 'us-east-1',
42
+ awsSessionToken: 'session-token-example',
43
+ };
44
+
45
+ const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload);
46
+ const decodedPayload = getXorPayload(obfuscatedToken);
47
+
48
+ expect(decodedPayload).toEqual(originalPayload);
49
+ });
50
+
51
+ it('should correctly decode XOR obfuscated payload with Azure data', () => {
52
+ const originalPayload = {
53
+ userId: 'azure-user-456',
54
+ apiKey: 'azure-api-key',
55
+ baseURL: 'https://your-resource.openai.azure.com',
56
+ azureApiVersion: '2024-02-15-preview',
57
+ };
58
+
59
+ const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload);
60
+ const decodedPayload = getXorPayload(obfuscatedToken);
61
+
62
+ expect(decodedPayload).toEqual(originalPayload);
63
+ });
64
+
65
+ it('should correctly decode XOR obfuscated payload with Cloudflare data', () => {
66
+ const originalPayload = {
67
+ userId: 'cf-user-789',
68
+ apiKey: 'cloudflare-api-key',
69
+ cloudflareBaseURLOrAccountID: 'account-id-example',
70
+ };
71
+
72
+ const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload);
73
+ const decodedPayload = getXorPayload(obfuscatedToken);
74
+
75
+ expect(decodedPayload).toEqual(originalPayload);
76
+ });
77
+
78
+ it('should handle empty payload correctly', () => {
79
+ const originalPayload = {};
80
+
81
+ const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload);
82
+ const decodedPayload = getXorPayload(obfuscatedToken);
83
+
84
+ expect(decodedPayload).toEqual(originalPayload);
85
+ });
86
+
87
+ it('should handle payload with undefined values', () => {
88
+ const originalPayload = {
89
+ userId: 'test-user',
90
+ accessCode: undefined,
91
+ apiKey: 'test-key',
92
+ };
93
+
94
+ const obfuscatedToken = obfuscatePayloadWithXOR(originalPayload);
95
+ const decodedPayload = getXorPayload(obfuscatedToken);
96
+
97
+ expect(decodedPayload).toEqual(originalPayload);
98
+ });
99
+
100
+ it('should throw error for invalid base64 token', () => {
101
+ const invalidToken = 'invalid-base64-token!@#';
102
+
103
+ expect(() => getXorPayload(invalidToken)).toThrow(SyntaxError);
104
+ });
105
+
106
+ it('should throw error for token that cannot be parsed as JSON', () => {
107
+ // 创建一个能正确base64解码但不是有效JSON的token
108
+ const invalidJsonString = 'this is not json';
109
+ const invalidJsonBytes = new TextEncoder().encode(invalidJsonString);
110
+ const keyBytes = new TextEncoder().encode('LobeHub · LobeHub');
111
+
112
+ // 进行XOR处理
113
+ const result = new Uint8Array(invalidJsonBytes.length);
114
+ for (const [i, datum] of invalidJsonBytes.entries()) {
115
+ result[i] = datum ^ keyBytes[i % keyBytes.length];
116
+ }
117
+
118
+ // 转换为base64
119
+ const invalidToken = Buffer.from(result).toString('base64');
120
+
121
+ expect(() => getXorPayload(invalidToken)).toThrow(SyntaxError);
122
+ });
123
+ });
@@ -0,0 +1,42 @@
1
+ import { ClientSecretPayload, SECRET_XOR_KEY } from '@/const/auth';
2
+
3
+ /**
4
+ * 将 Base64 字符串转换为 Uint8Array
5
+ */
6
+ const base64ToUint8Array = (base64: string): Uint8Array => {
7
+ // Node.js 环境下直接使用 Buffer
8
+ return Buffer.from(base64, 'base64');
9
+ };
10
+
11
+ /**
12
+ * 对 Uint8Array 进行 XOR 运算 (与客户端的 xorProcess 函数相同)
13
+ */
14
+ const xorProcess = (data: Uint8Array, key: Uint8Array): Uint8Array => {
15
+ const result = new Uint8Array(data.length);
16
+ for (const [i, datum] of data.entries()) {
17
+ result[i] = datum ^ key[i % key.length];
18
+ }
19
+ return result;
20
+ };
21
+
22
+ /**
23
+ * 将 Uint8Array 转换为字符串 (UTF-8 解码)
24
+ */
25
+ const uint8ArrayToString = (arr: Uint8Array): string => {
26
+ return new TextDecoder().decode(arr);
27
+ };
28
+
29
+ export const getXorPayload = (token: string): ClientSecretPayload => {
30
+ const keyBytes = new TextEncoder().encode(SECRET_XOR_KEY);
31
+
32
+ // 1. Base64 解码
33
+ const base64DecodedBytes = base64ToUint8Array(token);
34
+
35
+ // 2. XOR 解混淆
36
+ const xorDecryptedBytes = xorProcess(base64DecodedBytes, keyBytes);
37
+
38
+ // 3. 转换为字符串并解析 JSON
39
+ const decodedJsonString = uint8ArrayToString(xorDecryptedBytes);
40
+
41
+ return JSON.parse(decodedJsonString) as ClientSecretPayload;
42
+ };
@@ -1,27 +0,0 @@
1
- // @vitest-environment node
2
- import { describe, expect, it } from 'vitest';
3
-
4
- import { NON_HTTP_PREFIX } from '@/const/auth';
5
-
6
- import { createJWT } from './jwt';
7
-
8
- describe('createJWT', () => {
9
- it('should create a JWT token', async () => {
10
- const payload = { test: 'test' };
11
- const token = await createJWT(payload);
12
- expect(token).toBeTruthy();
13
- });
14
- it('should return a token with NON_HTTP_PREFIX when crypto.subtle does not exist', async () => {
15
- const originalCryptoSubtle = crypto.subtle;
16
- // @ts-ignore
17
- crypto['subtle'] = undefined;
18
-
19
- const payload = { test: 'test' };
20
- const token = await createJWT(payload);
21
-
22
- expect(token.startsWith(NON_HTTP_PREFIX)).toBeTruthy();
23
-
24
- // @ts-ignore
25
- crypto['subtle'] = originalCryptoSubtle;
26
- });
27
- });
package/src/utils/jwt.ts DELETED
@@ -1,37 +0,0 @@
1
- import { SignJWT, importJWK } from 'jose';
2
-
3
- import { JWT_SECRET_KEY, NON_HTTP_PREFIX } from '@/const/auth';
4
-
5
- export const createJWT = async <T>(payload: T) => {
6
- const now = Math.floor(Date.now() / 1000);
7
- const duration = 100; // 100s
8
-
9
- const encoder = new TextEncoder();
10
-
11
- // fix the issue that crypto.subtle is not available in non-HTTPS environment
12
- // refs: https://github.com/lobehub/lobe-chat/pull/1238
13
- if (!crypto.subtle) {
14
- const buffer = encoder.encode(JSON.stringify(payload));
15
-
16
- return `${NON_HTTP_PREFIX}.${Buffer.from(buffer).toString('base64')}`;
17
- }
18
-
19
- // create a secret key
20
- const secretKey = await crypto.subtle.digest('SHA-256', encoder.encode(JWT_SECRET_KEY));
21
-
22
- // get the JWK from the secret key
23
- const jwkSecretKey = await importJWK(
24
- {
25
- k: Buffer.from(secretKey).toString('base64'),
26
- kty: 'oct',
27
- },
28
- 'HS256',
29
- );
30
-
31
- // 创建JWT
32
- return new SignJWT(payload as Record<string, any>)
33
- .setProtectedHeader({ alg: 'HS256' })
34
- .setIssuedAt(now) // 设置JWT的iat(签发时间)声明
35
- .setExpirationTime(now + duration) // 设置 JWT 的 exp(过期时间)为 100 s
36
- .sign(jwkSecretKey);
37
- };
@@ -1,62 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest';
2
-
3
- import { NON_HTTP_PREFIX } from '@/const/auth';
4
-
5
- import { getJWTPayload } from './jwt';
6
-
7
- let enableClerkMock = false;
8
- let enableNextAuthMock = false;
9
-
10
- vi.mock('@/const/auth', async (importOriginal) => {
11
- const data = await importOriginal();
12
-
13
- return {
14
- ...(data as any),
15
- get enableClerk() {
16
- return enableClerkMock;
17
- },
18
- get enableNextAuth() {
19
- return enableNextAuthMock;
20
- },
21
- };
22
- });
23
-
24
- vi.mock('@/envs/app', () => ({
25
- getAppConfig: vi.fn(),
26
- }));
27
-
28
- describe('getJWTPayload', () => {
29
- it('should parse JWT payload for non-HTTPS token', async () => {
30
- const token = `${NON_HTTP_PREFIX}.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ`;
31
- const payload = await getJWTPayload(token);
32
- expect(payload).toEqual({
33
- sub: '1234567890',
34
- name: 'John Doe',
35
- iat: 1516239022,
36
- });
37
- });
38
-
39
- it('should verify and parse JWT payload for HTTPS token', async () => {
40
- const token =
41
- 'eyJhbGciOiJIUzI1NiJ9.eyJhY2Nlc3NDb2RlIjoiIiwidXNlcklkIjoiMDAxMzYyYzMtNDhjNS00NjM1LWJkM2ItODM3YmZmZjU4ZmMwIiwiYXBpS2V5IjoiYWJjIiwiZW5kcG9pbnQiOiJhYmMiLCJpYXQiOjE3MTY4MDIyMjUsImV4cCI6MTAwMDAwMDAwMDE3MTY4MDIwMDB9.FF0FxsE8Cajs-_hv5GD0TNUDwvekAkI9l_LL_IOPdGQ';
42
- const payload = await getJWTPayload(token);
43
- expect(payload).toEqual({
44
- accessCode: '',
45
- apiKey: 'abc',
46
- endpoint: 'abc',
47
- exp: 10000000001716802000,
48
- iat: 1716802225,
49
- userId: '001362c3-48c5-4635-bd3b-837bfff58fc0',
50
- });
51
- });
52
-
53
- it('should not verify success and parse JWT payload for dated token', async () => {
54
- const token =
55
- 'eyJhbGciOiJIUzI1NiJ9.eyJhY2Nlc3NDb2RlIjoiIiwidXNlcklkIjoiYWY3M2JhODktZjFhMy00YjliLWEwM2QtZGViZmZlMzE4NmQxIiwiYXBpS2V5IjoiYWJjIiwiZW5kcG9pbnQiOiJhYmMiLCJpYXQiOjE3MTY3OTk5ODAsImV4cCI6MTcxNjgwMDA4MH0.8AGFsLcwyrQG82kVUYOGFXHIwihm2n16ctyArKW9100';
56
- try {
57
- await getJWTPayload(token);
58
- } catch (e) {
59
- expect((e as Error).message).toEqual('"exp" claim timestamp check failed');
60
- }
61
- });
62
- });
@@ -1,28 +0,0 @@
1
- import { importJWK, jwtVerify } from 'jose';
2
-
3
- import { JWTPayload, JWT_SECRET_KEY, NON_HTTP_PREFIX } from '@/const/auth';
4
-
5
- export const getJWTPayload = async (token: string): Promise<JWTPayload> => {
6
- //如果是 HTTP 协议发起的请求,直接解析 token
7
- // 这是一个非常 hack 的解决方案,未来要找更好的解决方案来处理这个问题
8
- // refs: https://github.com/lobehub/lobe-chat/pull/1238
9
- if (token.startsWith(NON_HTTP_PREFIX)) {
10
- const jwtParts = token.split('.');
11
-
12
- const payload = jwtParts[1];
13
-
14
- return JSON.parse(atob(payload));
15
- }
16
-
17
- const encoder = new TextEncoder();
18
- const secretKey = await crypto.subtle.digest('SHA-256', encoder.encode(JWT_SECRET_KEY));
19
-
20
- const jwkSecretKey = await importJWK(
21
- { k: Buffer.from(secretKey).toString('base64'), kty: 'oct' },
22
- 'HS256',
23
- );
24
-
25
- const { payload } = await jwtVerify(token, jwkSecretKey);
26
-
27
- return payload as JWTPayload;
28
- };