@lobehub/chat 1.105.0 → 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 (62) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/changelog/v1.json +18 -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/qwen.ts +207 -2
  29. package/src/config/aiModels/siliconcloud.ts +24 -2
  30. package/src/config/aiModels/stepfun.ts +67 -2
  31. package/src/config/aiModels/volcengine.ts +56 -2
  32. package/src/config/aiModels/wenxin.ts +62 -2
  33. package/src/config/aiModels/xai.ts +19 -2
  34. package/src/const/auth.ts +2 -3
  35. package/src/libs/model-runtime/ModelRuntime.test.ts +3 -3
  36. package/src/libs/model-runtime/qwen/createImage.ts +29 -9
  37. package/src/libs/trpc/async/context.ts +3 -3
  38. package/src/libs/trpc/edge/context.ts +7 -2
  39. package/src/libs/trpc/edge/middleware/jwtPayload.test.ts +4 -4
  40. package/src/libs/trpc/edge/middleware/jwtPayload.ts +2 -2
  41. package/src/libs/trpc/lambda/context.ts +2 -2
  42. package/src/libs/trpc/lambda/middleware/keyVaults.ts +2 -2
  43. package/src/server/modules/AgentRuntime/index.test.ts +28 -25
  44. package/src/server/modules/AgentRuntime/index.ts +3 -3
  45. package/src/server/routers/async/caller.ts +2 -2
  46. package/src/server/routers/lambda/market/index.ts +0 -14
  47. package/src/server/routers/tools/search.test.ts +2 -2
  48. package/src/server/services/chunk/index.ts +3 -3
  49. package/src/server/services/discover/index.ts +0 -13
  50. package/src/server/sitemap.test.ts +0 -52
  51. package/src/server/sitemap.ts +1 -38
  52. package/src/services/_auth.ts +3 -3
  53. package/src/services/discover.ts +0 -4
  54. package/src/store/discover/slices/mcp/action.ts +0 -8
  55. package/src/utils/client/xor-obfuscation.test.ts +370 -0
  56. package/src/utils/client/xor-obfuscation.ts +39 -0
  57. package/src/utils/server/xor.test.ts +123 -0
  58. package/src/utils/server/xor.ts +42 -0
  59. package/src/utils/jwt.test.ts +0 -27
  60. package/src/utils/jwt.ts +0 -37
  61. package/src/utils/server/jwt.test.ts +0 -62
  62. package/src/utils/server/jwt.ts +0 -28
@@ -0,0 +1,370 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { SECRET_XOR_KEY } from '@/const/auth';
4
+
5
+ import { obfuscatePayloadWithXOR } from './xor-obfuscation';
6
+
7
+ describe('xor-obfuscation', () => {
8
+ describe('obfuscatePayloadWithXOR', () => {
9
+ it('应该对简单字符串进行混淆并返回Base64字符串', () => {
10
+ const payload = 'hello world';
11
+ const result = obfuscatePayloadWithXOR(payload);
12
+
13
+ // 验证返回值是字符串
14
+ expect(typeof result).toBe('string');
15
+
16
+ // 验证返回值是有效的Base64字符串
17
+ expect(() => atob(result)).not.toThrow();
18
+
19
+ // 验证结果长度大于0
20
+ expect(result.length).toBeGreaterThan(0);
21
+ });
22
+
23
+ it('应该对JSON对象进行混淆', () => {
24
+ const payload = { name: 'test', value: 123, active: true };
25
+ const result = obfuscatePayloadWithXOR(payload);
26
+
27
+ // 验证返回值是字符串
28
+ expect(typeof result).toBe('string');
29
+
30
+ // 验证返回值是有效的Base64字符串
31
+ expect(() => atob(result)).not.toThrow();
32
+ });
33
+
34
+ it('应该对数组进行混淆', () => {
35
+ const payload = [1, 2, 3, 'test', { nested: true }];
36
+ const result = obfuscatePayloadWithXOR(payload);
37
+
38
+ // 验证返回值是字符串
39
+ expect(typeof result).toBe('string');
40
+
41
+ // 验证返回值是有效的Base64字符串
42
+ expect(() => atob(result)).not.toThrow();
43
+ });
44
+
45
+ it('应该对复杂嵌套对象进行混淆', () => {
46
+ const payload = {
47
+ user: {
48
+ id: 123,
49
+ profile: {
50
+ name: 'John Doe',
51
+ settings: {
52
+ theme: 'dark',
53
+ notifications: true,
54
+ preferences: ['email', 'sms'],
55
+ },
56
+ },
57
+ },
58
+ tokens: ['abc123', 'def456'],
59
+ metadata: null,
60
+ };
61
+ const result = obfuscatePayloadWithXOR(payload);
62
+
63
+ // 验证返回值是字符串
64
+ expect(typeof result).toBe('string');
65
+
66
+ // 验证返回值是有效的Base64字符串
67
+ expect(() => atob(result)).not.toThrow();
68
+ });
69
+
70
+ it('相同的输入应该产生相同的输出', () => {
71
+ const payload = { test: 'consistent' };
72
+ const result1 = obfuscatePayloadWithXOR(payload);
73
+ const result2 = obfuscatePayloadWithXOR(payload);
74
+
75
+ expect(result1).toBe(result2);
76
+ });
77
+
78
+ it('不同的输入应该产生不同的输出', () => {
79
+ const payload1 = { test: 'value1' };
80
+ const payload2 = { test: 'value2' };
81
+
82
+ const result1 = obfuscatePayloadWithXOR(payload1);
83
+ const result2 = obfuscatePayloadWithXOR(payload2);
84
+
85
+ expect(result1).not.toBe(result2);
86
+ });
87
+
88
+ it('应该处理包含特殊字符的字符串', () => {
89
+ const payload = 'Hello! @#$%^&*()_+-=[]{}|;:,.<>?/~`"\'\\';
90
+ const result = obfuscatePayloadWithXOR(payload);
91
+
92
+ // 验证返回值是字符串
93
+ expect(typeof result).toBe('string');
94
+
95
+ // 验证返回值是有效的Base64字符串
96
+ expect(() => atob(result)).not.toThrow();
97
+ });
98
+
99
+ it('应该处理包含Unicode字符的字符串', () => {
100
+ const payload = '你好世界 🌍 émojis 日本語 한국어';
101
+ const result = obfuscatePayloadWithXOR(payload);
102
+
103
+ // 验证返回值是字符串
104
+ expect(typeof result).toBe('string');
105
+
106
+ // 验证返回值是有效的Base64字符串
107
+ expect(() => atob(result)).not.toThrow();
108
+ });
109
+
110
+ it('应该处理空字符串', () => {
111
+ const payload = '';
112
+ const result = obfuscatePayloadWithXOR(payload);
113
+
114
+ // 验证返回值是字符串
115
+ expect(typeof result).toBe('string');
116
+
117
+ // 验证返回值是有效的Base64字符串
118
+ expect(() => atob(result)).not.toThrow();
119
+ });
120
+
121
+ it('应该处理空对象', () => {
122
+ const payload = {};
123
+ const result = obfuscatePayloadWithXOR(payload);
124
+
125
+ // 验证返回值是字符串
126
+ expect(typeof result).toBe('string');
127
+
128
+ // 验证返回值是有效的Base64字符串
129
+ expect(() => atob(result)).not.toThrow();
130
+ });
131
+
132
+ it('应该处理空数组', () => {
133
+ const result = obfuscatePayloadWithXOR([]);
134
+
135
+ // 验证返回值是字符串
136
+ expect(typeof result).toBe('string');
137
+
138
+ // 验证返回值是有效的Base64字符串
139
+ expect(() => atob(result)).not.toThrow();
140
+ });
141
+
142
+ it('应该处理null值', () => {
143
+ const payload = null;
144
+ const result = obfuscatePayloadWithXOR(payload);
145
+
146
+ // 验证返回值是字符串
147
+ expect(typeof result).toBe('string');
148
+
149
+ // 验证返回值是有效的Base64字符串
150
+ expect(() => atob(result)).not.toThrow();
151
+ });
152
+
153
+ it('应该处理数字', () => {
154
+ const payload = 42;
155
+ const result = obfuscatePayloadWithXOR(payload);
156
+
157
+ // 验证返回值是字符串
158
+ expect(typeof result).toBe('string');
159
+
160
+ // 验证返回值是有效的Base64字符串
161
+ expect(() => atob(result)).not.toThrow();
162
+ });
163
+
164
+ it('应该处理布尔值', () => {
165
+ const payloadTrue = true;
166
+ const payloadFalse = false;
167
+
168
+ const resultTrue = obfuscatePayloadWithXOR(payloadTrue);
169
+ const resultFalse = obfuscatePayloadWithXOR(payloadFalse);
170
+
171
+ // 验证返回值是字符串
172
+ expect(typeof resultTrue).toBe('string');
173
+ expect(typeof resultFalse).toBe('string');
174
+
175
+ // 验证返回值是有效的Base64字符串
176
+ expect(() => atob(resultTrue)).not.toThrow();
177
+ expect(() => atob(resultFalse)).not.toThrow();
178
+
179
+ // 验证不同布尔值产生不同结果
180
+ expect(resultTrue).not.toBe(resultFalse);
181
+ });
182
+
183
+ it('应该处理包含特殊JSON字符的对象', () => {
184
+ const payload = {
185
+ quotes: '"double quotes"',
186
+ singleQuotes: "'single quotes'",
187
+ backslash: 'back\\slash',
188
+ newline: 'line1\nline2',
189
+ tab: 'col1\tcol2',
190
+ unicode: '\u0041\u0042\u0043',
191
+ };
192
+ const result = obfuscatePayloadWithXOR(payload);
193
+
194
+ // 验证返回值是字符串
195
+ expect(typeof result).toBe('string');
196
+
197
+ // 验证返回值是有效的Base64字符串
198
+ expect(() => atob(result)).not.toThrow();
199
+ });
200
+
201
+ it('应该处理很长的字符串', () => {
202
+ const payload = 'a'.repeat(10000);
203
+ const result = obfuscatePayloadWithXOR(payload);
204
+
205
+ // 验证返回值是字符串
206
+ expect(typeof result).toBe('string');
207
+
208
+ // 验证返回值是有效的Base64字符串
209
+ expect(() => atob(result)).not.toThrow();
210
+
211
+ // 验证结果长度合理(Base64编码后长度应该大约是原始长度的4/3)
212
+ expect(result.length).toBeGreaterThan(0);
213
+ });
214
+
215
+ it('应该产生不同长度输入的不同输出长度', () => {
216
+ const shortPayload = 'short';
217
+ const longPayload = 'this is a much longer string that should produce different output';
218
+
219
+ const shortResult = obfuscatePayloadWithXOR(shortPayload);
220
+ const longResult = obfuscatePayloadWithXOR(longPayload);
221
+
222
+ // 较长的输入应该产生较长的输出
223
+ expect(longResult.length).toBeGreaterThan(shortResult.length);
224
+ });
225
+
226
+ it('应该验证输出是有效的Base64格式', () => {
227
+ const payload = { test: 'base64 validation' };
228
+ const result = obfuscatePayloadWithXOR(payload);
229
+
230
+ // 验证Base64格式的正则表达式
231
+ const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
232
+ expect(base64Regex.test(result)).toBe(true);
233
+ });
234
+
235
+ it('应该处理包含循环引用的对象(通过JSON.stringify处理)', () => {
236
+ // JSON.stringify 会抛出错误处理循环引用,但我们测试正常情况
237
+ const payload = {
238
+ id: 1,
239
+ name: 'test',
240
+ nested: {
241
+ back: 'reference',
242
+ },
243
+ };
244
+
245
+ const result = obfuscatePayloadWithXOR(payload);
246
+ expect(typeof result).toBe('string');
247
+ expect(() => atob(result)).not.toThrow();
248
+ });
249
+
250
+ it('应该对undefined值进行处理', () => {
251
+ const payload = undefined;
252
+ const result = obfuscatePayloadWithXOR(payload);
253
+
254
+ // 验证返回值是字符串
255
+ expect(typeof result).toBe('string');
256
+
257
+ // 验证返回值是有效的Base64字符串
258
+ expect(() => atob(result)).not.toThrow();
259
+ });
260
+
261
+ it('应该对包含函数的对象进行处理(函数会被JSON.stringify忽略)', () => {
262
+ const payload = {
263
+ name: 'test',
264
+ fn: function () {
265
+ return 'test';
266
+ },
267
+ arrow: () => 'arrow',
268
+ value: 123,
269
+ };
270
+
271
+ const result = obfuscatePayloadWithXOR(payload);
272
+ expect(typeof result).toBe('string');
273
+ expect(() => atob(result)).not.toThrow();
274
+ });
275
+
276
+ it('应该确保XOR操作的确定性', () => {
277
+ const payload = 'deterministic test';
278
+ const results: any[] = [];
279
+
280
+ // 多次运行相同输入
281
+ for (let i = 0; i < 10; i++) {
282
+ results.push(obfuscatePayloadWithXOR(payload));
283
+ }
284
+
285
+ // 所有结果应该相同
286
+ expect(results.every((result) => result === results[0])).toBe(true);
287
+ });
288
+
289
+ it('应该处理包含日期对象的数据', () => {
290
+ const payload = {
291
+ timestamp: new Date('2024-01-01T00:00:00Z'),
292
+ created: new Date(),
293
+ name: 'date test',
294
+ };
295
+
296
+ const result = obfuscatePayloadWithXOR(payload);
297
+ expect(typeof result).toBe('string');
298
+ expect(() => atob(result)).not.toThrow();
299
+ });
300
+
301
+ it('应该处理包含Symbol的对象(Symbol会被JSON.stringify忽略)', () => {
302
+ const sym = Symbol('test');
303
+ const payload = {
304
+ name: 'symbol test',
305
+ [sym]: 'symbol value',
306
+ normalKey: 'normal value',
307
+ };
308
+
309
+ const result = obfuscatePayloadWithXOR(payload);
310
+ expect(typeof result).toBe('string');
311
+ expect(() => atob(result)).not.toThrow();
312
+ });
313
+
314
+ it('应该验证混淆后的数据长度合理性', () => {
315
+ const originalPayload = { test: 'length check' };
316
+ const originalJSON = JSON.stringify(originalPayload);
317
+ const result = obfuscatePayloadWithXOR(originalPayload);
318
+
319
+ // Base64 编码后的长度通常是原始长度的 4/3 倍(向上取整到4的倍数)
320
+ const expectedMinLength = Math.ceil((originalJSON.length * 4) / 3 / 4) * 4;
321
+ expect(result.length).toBeGreaterThanOrEqual(expectedMinLength - 4); // 允许一些误差
322
+ });
323
+
324
+ it('应该验证XOR操作的正确性(通过逆向操作)', () => {
325
+ const originalPayload = { message: 'XOR test', value: 42 };
326
+ const obfuscatedResult = obfuscatePayloadWithXOR(originalPayload);
327
+
328
+ // 手动实现逆向操作来验证 XOR 操作的正确性
329
+ const base64Decoded = atob(obfuscatedResult);
330
+ const xoredBytes = new Uint8Array(base64Decoded.length);
331
+ for (let i = 0; i < base64Decoded.length; i++) {
332
+ xoredBytes[i] = base64Decoded.charCodeAt(i);
333
+ }
334
+
335
+ // 使用相同的密钥进行逆向 XOR 操作
336
+ const keyBytes = new TextEncoder().encode(SECRET_XOR_KEY);
337
+ const decodedBytes = new Uint8Array(xoredBytes.length);
338
+ for (let i = 0; i < xoredBytes.length; i++) {
339
+ decodedBytes[i] = xoredBytes[i] ^ keyBytes[i % keyBytes.length];
340
+ }
341
+
342
+ // 将结果转换回字符串
343
+ const decodedString = new TextDecoder().decode(decodedBytes);
344
+ const decodedPayload = JSON.parse(decodedString);
345
+
346
+ // 验证解码后的数据与原始数据相同
347
+ expect(decodedPayload).toEqual(originalPayload);
348
+ });
349
+
350
+ it('应该验证不同输入产生不同的Base64输出', () => {
351
+ const payloads = [
352
+ 'test1',
353
+ 'test2',
354
+ { key: 'value1' },
355
+ { key: 'value2' },
356
+ [1, 2, 3],
357
+ [4, 5, 6],
358
+ ];
359
+
360
+ const results = payloads.map((payload) => obfuscatePayloadWithXOR(payload));
361
+
362
+ // 验证所有结果都不相同
363
+ for (let i = 0; i < results.length; i++) {
364
+ for (let j = i + 1; j < results.length; j++) {
365
+ expect(results[i]).not.toBe(results[j]);
366
+ }
367
+ }
368
+ });
369
+ });
370
+ });
@@ -0,0 +1,39 @@
1
+ import { SECRET_XOR_KEY } from '@/const/auth';
2
+
3
+ /**
4
+ * 将字符串转换为 Uint8Array (UTF-8 编码)
5
+ */
6
+ const stringToUint8Array = (str: string): Uint8Array => {
7
+ return new TextEncoder().encode(str);
8
+ };
9
+
10
+ /**
11
+ * 对 Uint8Array 进行 XOR 运算
12
+ * @param data 要处理的 Uint8Array
13
+ * @param key 用于 XOR 的密钥 (Uint8Array)
14
+ * @returns 经过 XOR 运算的 Uint8Array
15
+ */
16
+ const xorProcess = (data: Uint8Array, key: Uint8Array): Uint8Array => {
17
+ const result = new Uint8Array(data.length);
18
+ for (const [i, datum] of data.entries()) {
19
+ result[i] = datum ^ key[i % key.length]; // 密钥循环使用
20
+ }
21
+ return result;
22
+ };
23
+
24
+ /**
25
+ * 对 payload 进行 XOR 混淆并 Base64 编码
26
+ * @param payload 要混淆的 JSON 对象
27
+ * @returns Base64 编码后的混淆字符串
28
+ */
29
+ export const obfuscatePayloadWithXOR = <T>(payload: T): string => {
30
+ const jsonString = JSON.stringify(payload);
31
+ const dataBytes = stringToUint8Array(jsonString);
32
+ const keyBytes = stringToUint8Array(SECRET_XOR_KEY);
33
+
34
+ const xoredBytes = xorProcess(dataBytes, keyBytes);
35
+
36
+ // 将 Uint8Array 转换为 Base64 字符串
37
+ // 浏览器环境 btoa 只能处理 Latin-1 字符,所以需要先转换为适合 btoa 的字符串
38
+ return btoa(String.fromCharCode(...xoredBytes));
39
+ };
@@ -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
- };