@lobehub/chat 1.21.11 → 1.21.12
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 +25 -0
- package/package.json +2 -2
- package/src/app/(backend)/api/chat/[provider]/route.test.ts +6 -2
- package/src/app/(backend)/middleware/auth/index.test.ts +5 -2
- package/src/app/(backend)/middleware/auth/index.ts +2 -1
- package/src/app/(backend)/middleware/auth/utils.test.ts +1 -38
- package/src/app/(backend)/middleware/auth/utils.ts +1 -33
- package/src/app/(backend)/webapi/plugin/gateway/route.ts +1 -1
- package/src/libs/trpc/middleware/jwtPayload.test.ts +1 -1
- package/src/libs/trpc/middleware/jwtPayload.ts +1 -1
- package/src/libs/trpc/middleware/keyVaults.ts +1 -1
- package/src/utils/server/jwt.test.ts +62 -0
- package/src/utils/server/jwt.ts +32 -0
package/CHANGELOG.md
CHANGED
@@ -2,6 +2,31 @@
|
|
2
2
|
|
3
3
|
# Changelog
|
4
4
|
|
5
|
+
### [Version 1.21.12](https://github.com/lobehub/lobe-chat/compare/v1.21.11...v1.21.12)
|
6
|
+
|
7
|
+
<sup>Released on **2024-10-11**</sup>
|
8
|
+
|
9
|
+
#### ♻ Code Refactoring
|
10
|
+
|
11
|
+
- **misc**: Refactor the jwt code.
|
12
|
+
|
13
|
+
<br/>
|
14
|
+
|
15
|
+
<details>
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
17
|
+
|
18
|
+
#### Code refactoring
|
19
|
+
|
20
|
+
- **misc**: Refactor the jwt code, closes [#4322](https://github.com/lobehub/lobe-chat/issues/4322) ([b7258b9](https://github.com/lobehub/lobe-chat/commit/b7258b9))
|
21
|
+
|
22
|
+
</details>
|
23
|
+
|
24
|
+
<div align="right">
|
25
|
+
|
26
|
+
[](#readme-top)
|
27
|
+
|
28
|
+
</div>
|
29
|
+
|
5
30
|
### [Version 1.21.11](https://github.com/lobehub/lobe-chat/compare/v1.21.10...v1.21.11)
|
6
31
|
|
7
32
|
<sup>Released on **2024-10-11**</sup>
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.21.
|
3
|
+
"version": "1.21.12",
|
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",
|
@@ -101,7 +101,7 @@
|
|
101
101
|
"dependencies": {
|
102
102
|
"@ant-design/icons": "^5.4.0",
|
103
103
|
"@ant-design/pro-components": "^2.7.10",
|
104
|
-
"@anthropic-ai/sdk": "^0.
|
104
|
+
"@anthropic-ai/sdk": "^0.29.0",
|
105
105
|
"@auth/core": "^0.34.2",
|
106
106
|
"@aws-sdk/client-bedrock-runtime": "^3.637.0",
|
107
107
|
"@aws-sdk/client-s3": "^3.637.0",
|
@@ -2,10 +2,11 @@
|
|
2
2
|
import { getAuth } from '@clerk/nextjs/server';
|
3
3
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
4
4
|
|
5
|
-
import { checkAuthMethod
|
5
|
+
import { checkAuthMethod } from '@/app/(backend)/middleware/auth/utils';
|
6
6
|
import { LOBE_CHAT_AUTH_HEADER, OAUTH_AUTHORIZED } from '@/const/auth';
|
7
7
|
import { AgentRuntime, LobeRuntimeAI } from '@/libs/agent-runtime';
|
8
8
|
import { ChatErrorType } from '@/types/fetch';
|
9
|
+
import { getJWTPayload } from '@/utils/server/jwt';
|
9
10
|
|
10
11
|
import { POST } from './route';
|
11
12
|
|
@@ -14,10 +15,13 @@ vi.mock('@clerk/nextjs/server', () => ({
|
|
14
15
|
}));
|
15
16
|
|
16
17
|
vi.mock('@/app/(backend)/middleware/auth/utils', () => ({
|
17
|
-
getJWTPayload: vi.fn(),
|
18
18
|
checkAuthMethod: vi.fn(),
|
19
19
|
}));
|
20
20
|
|
21
|
+
vi.mock('@/utils/server/jwt', () => ({
|
22
|
+
getJWTPayload: vi.fn(),
|
23
|
+
}));
|
24
|
+
|
21
25
|
// 定义一个变量来存储 enableAuth 的值
|
22
26
|
let enableClerk = false;
|
23
27
|
|
@@ -1,12 +1,12 @@
|
|
1
|
-
import { getAuth } from '@clerk/nextjs/server';
|
2
1
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
3
2
|
|
4
3
|
import { AgentRuntimeError } from '@/libs/agent-runtime';
|
5
4
|
import { ChatErrorType } from '@/types/fetch';
|
6
5
|
import { createErrorResponse } from '@/utils/errorResponse';
|
6
|
+
import { getJWTPayload } from '@/utils/server/jwt';
|
7
7
|
|
8
8
|
import { RequestHandler, checkAuth } from './index';
|
9
|
-
import { checkAuthMethod
|
9
|
+
import { checkAuthMethod } from './utils';
|
10
10
|
|
11
11
|
vi.mock('@clerk/nextjs/server', () => ({
|
12
12
|
getAuth: vi.fn(),
|
@@ -18,6 +18,9 @@ vi.mock('@/utils/errorResponse', () => ({
|
|
18
18
|
|
19
19
|
vi.mock('./utils', () => ({
|
20
20
|
checkAuthMethod: vi.fn(),
|
21
|
+
}));
|
22
|
+
|
23
|
+
vi.mock('@/utils/server/jwt', () => ({
|
21
24
|
getJWTPayload: vi.fn(),
|
22
25
|
}));
|
23
26
|
|
@@ -6,8 +6,9 @@ import { JWTPayload, LOBE_CHAT_AUTH_HEADER, OAUTH_AUTHORIZED, enableClerk } from
|
|
6
6
|
import { AgentRuntime, AgentRuntimeError, ChatCompletionErrorPayload } from '@/libs/agent-runtime';
|
7
7
|
import { ChatErrorType } from '@/types/fetch';
|
8
8
|
import { createErrorResponse } from '@/utils/errorResponse';
|
9
|
+
import { getJWTPayload } from '@/utils/server/jwt';
|
9
10
|
|
10
|
-
import { checkAuthMethod
|
11
|
+
import { checkAuthMethod } from './utils';
|
11
12
|
|
12
13
|
type CreateRuntime = (jwtPayload: JWTPayload) => AgentRuntime;
|
13
14
|
type RequestOptions = { createRuntime?: CreateRuntime; params: { provider: string } };
|
@@ -2,9 +2,8 @@ import { type AuthObject } from '@clerk/backend';
|
|
2
2
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
3
3
|
|
4
4
|
import { getAppConfig } from '@/config/app';
|
5
|
-
import { NON_HTTP_PREFIX } from '@/const/auth';
|
6
5
|
|
7
|
-
import { checkAuthMethod
|
6
|
+
import { checkAuthMethod } from './utils';
|
8
7
|
|
9
8
|
let enableClerkMock = false;
|
10
9
|
let enableNextAuthMock = false;
|
@@ -27,42 +26,6 @@ vi.mock('@/config/app', () => ({
|
|
27
26
|
getAppConfig: vi.fn(),
|
28
27
|
}));
|
29
28
|
|
30
|
-
describe('getJWTPayload', () => {
|
31
|
-
it('should parse JWT payload for non-HTTPS token', async () => {
|
32
|
-
const token = `${NON_HTTP_PREFIX}.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ`;
|
33
|
-
const payload = await getJWTPayload(token);
|
34
|
-
expect(payload).toEqual({
|
35
|
-
sub: '1234567890',
|
36
|
-
name: 'John Doe',
|
37
|
-
iat: 1516239022,
|
38
|
-
});
|
39
|
-
});
|
40
|
-
|
41
|
-
it('should verify and parse JWT payload for HTTPS token', async () => {
|
42
|
-
const token =
|
43
|
-
'eyJhbGciOiJIUzI1NiJ9.eyJhY2Nlc3NDb2RlIjoiIiwidXNlcklkIjoiMDAxMzYyYzMtNDhjNS00NjM1LWJkM2ItODM3YmZmZjU4ZmMwIiwiYXBpS2V5IjoiYWJjIiwiZW5kcG9pbnQiOiJhYmMiLCJpYXQiOjE3MTY4MDIyMjUsImV4cCI6MTAwMDAwMDAwMDE3MTY4MDIwMDB9.FF0FxsE8Cajs-_hv5GD0TNUDwvekAkI9l_LL_IOPdGQ';
|
44
|
-
const payload = await getJWTPayload(token);
|
45
|
-
expect(payload).toEqual({
|
46
|
-
accessCode: '',
|
47
|
-
apiKey: 'abc',
|
48
|
-
endpoint: 'abc',
|
49
|
-
exp: 10000000001716802000,
|
50
|
-
iat: 1716802225,
|
51
|
-
userId: '001362c3-48c5-4635-bd3b-837bfff58fc0',
|
52
|
-
});
|
53
|
-
});
|
54
|
-
|
55
|
-
it('should not verify success and parse JWT payload for dated token', async () => {
|
56
|
-
const token =
|
57
|
-
'eyJhbGciOiJIUzI1NiJ9.eyJhY2Nlc3NDb2RlIjoiIiwidXNlcklkIjoiYWY3M2JhODktZjFhMy00YjliLWEwM2QtZGViZmZlMzE4NmQxIiwiYXBpS2V5IjoiYWJjIiwiZW5kcG9pbnQiOiJhYmMiLCJpYXQiOjE3MTY3OTk5ODAsImV4cCI6MTcxNjgwMDA4MH0.8AGFsLcwyrQG82kVUYOGFXHIwihm2n16ctyArKW9100';
|
58
|
-
try {
|
59
|
-
await getJWTPayload(token);
|
60
|
-
} catch (e) {
|
61
|
-
expect(e).toEqual(new TypeError('"exp" claim timestamp check failed'));
|
62
|
-
}
|
63
|
-
});
|
64
|
-
});
|
65
|
-
|
66
29
|
describe('checkAuthMethod', () => {
|
67
30
|
beforeEach(() => {
|
68
31
|
vi.mocked(getAppConfig).mockReturnValue({
|
@@ -1,42 +1,10 @@
|
|
1
1
|
import { type AuthObject } from '@clerk/backend';
|
2
|
-
import { importJWK, jwtVerify } from 'jose';
|
3
2
|
|
4
3
|
import { getAppConfig } from '@/config/app';
|
5
|
-
import {
|
6
|
-
JWTPayload,
|
7
|
-
JWT_SECRET_KEY,
|
8
|
-
NON_HTTP_PREFIX,
|
9
|
-
enableClerk,
|
10
|
-
enableNextAuth,
|
11
|
-
} from '@/const/auth';
|
4
|
+
import { enableClerk, enableNextAuth } from '@/const/auth';
|
12
5
|
import { AgentRuntimeError } from '@/libs/agent-runtime';
|
13
6
|
import { ChatErrorType } from '@/types/fetch';
|
14
7
|
|
15
|
-
export const getJWTPayload = async (token: string): Promise<JWTPayload> => {
|
16
|
-
//如果是 HTTP 协议发起的请求,直接解析 token
|
17
|
-
// 这是一个非常 hack 的解决方案,未来要找更好的解决方案来处理这个问题
|
18
|
-
// refs: https://github.com/lobehub/lobe-chat/pull/1238
|
19
|
-
if (token.startsWith(NON_HTTP_PREFIX)) {
|
20
|
-
const jwtParts = token.split('.');
|
21
|
-
|
22
|
-
const payload = jwtParts[1];
|
23
|
-
|
24
|
-
return JSON.parse(atob(payload));
|
25
|
-
}
|
26
|
-
|
27
|
-
const encoder = new TextEncoder();
|
28
|
-
const secretKey = await crypto.subtle.digest('SHA-256', encoder.encode(JWT_SECRET_KEY));
|
29
|
-
|
30
|
-
const jwkSecretKey = await importJWK(
|
31
|
-
{ k: Buffer.from(secretKey).toString('base64'), kty: 'oct' },
|
32
|
-
'HS256',
|
33
|
-
);
|
34
|
-
|
35
|
-
const { payload } = await jwtVerify(token, jwkSecretKey);
|
36
|
-
|
37
|
-
return payload as JWTPayload;
|
38
|
-
};
|
39
|
-
|
40
8
|
interface CheckAuthParams {
|
41
9
|
accessCode?: string;
|
42
10
|
apiKey?: string;
|
@@ -1,7 +1,6 @@
|
|
1
1
|
import { PluginRequestPayload } from '@lobehub/chat-plugin-sdk';
|
2
2
|
import { createGatewayOnEdgeRuntime } from '@lobehub/chat-plugins-gateway';
|
3
3
|
|
4
|
-
import { getJWTPayload } from '@/app/(backend)/middleware/auth/utils';
|
5
4
|
import { getAppConfig } from '@/config/app';
|
6
5
|
import { LOBE_CHAT_AUTH_HEADER, OAUTH_AUTHORIZED, enableNextAuth } from '@/const/auth';
|
7
6
|
import { LOBE_CHAT_TRACE_ID, TraceNameMap } from '@/const/trace';
|
@@ -9,6 +8,7 @@ import { AgentRuntimeError } from '@/libs/agent-runtime';
|
|
9
8
|
import { TraceClient } from '@/libs/traces';
|
10
9
|
import { ChatErrorType, ErrorType } from '@/types/fetch';
|
11
10
|
import { createErrorResponse } from '@/utils/errorResponse';
|
11
|
+
import { getJWTPayload } from '@/utils/server/jwt';
|
12
12
|
import { getTracePayload } from '@/utils/trace';
|
13
13
|
|
14
14
|
import { parserPluginSettings } from './settings';
|
@@ -2,10 +2,10 @@
|
|
2
2
|
import { TRPCError } from '@trpc/server';
|
3
3
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
4
4
|
|
5
|
-
import * as utils from '@/app/(backend)/middleware/auth/utils';
|
6
5
|
import { createCallerFactory } from '@/libs/trpc';
|
7
6
|
import { trpc } from '@/libs/trpc/init';
|
8
7
|
import { AuthContext, createContextInner } from '@/server/context';
|
8
|
+
import * as utils from '@/utils/server/jwt';
|
9
9
|
|
10
10
|
import { jwtPayloadChecker } from './jwtPayload';
|
11
11
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { TRPCError } from '@trpc/server';
|
2
2
|
|
3
|
-
import { getJWTPayload } from '@/app/(backend)/middleware/auth/utils';
|
4
3
|
import { trpc } from '@/libs/trpc/init';
|
4
|
+
import { getJWTPayload } from '@/utils/server/jwt';
|
5
5
|
|
6
6
|
export const jwtPayloadChecker = trpc.middleware(async (opts) => {
|
7
7
|
const { ctx } = opts;
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { TRPCError } from '@trpc/server';
|
2
2
|
|
3
|
-
import { getJWTPayload } from '@/app/(backend)/middleware/auth/utils';
|
4
3
|
import { trpc } from '@/libs/trpc/init';
|
4
|
+
import { getJWTPayload } from '@/utils/server/jwt';
|
5
5
|
|
6
6
|
export const keyVaults = trpc.middleware(async (opts) => {
|
7
7
|
const { ctx } = opts;
|
@@ -0,0 +1,62 @@
|
|
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('@/config/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).toEqual(new TypeError('"exp" claim timestamp check failed'));
|
60
|
+
}
|
61
|
+
});
|
62
|
+
});
|
@@ -0,0 +1,32 @@
|
|
1
|
+
import { importJWK, jwtVerify } from 'jose';
|
2
|
+
|
3
|
+
import {
|
4
|
+
JWTPayload,
|
5
|
+
JWT_SECRET_KEY,
|
6
|
+
NON_HTTP_PREFIX,
|
7
|
+
} from '@/const/auth';
|
8
|
+
|
9
|
+
export const getJWTPayload = async (token: string): Promise<JWTPayload> => {
|
10
|
+
//如果是 HTTP 协议发起的请求,直接解析 token
|
11
|
+
// 这是一个非常 hack 的解决方案,未来要找更好的解决方案来处理这个问题
|
12
|
+
// refs: https://github.com/lobehub/lobe-chat/pull/1238
|
13
|
+
if (token.startsWith(NON_HTTP_PREFIX)) {
|
14
|
+
const jwtParts = token.split('.');
|
15
|
+
|
16
|
+
const payload = jwtParts[1];
|
17
|
+
|
18
|
+
return JSON.parse(atob(payload));
|
19
|
+
}
|
20
|
+
|
21
|
+
const encoder = new TextEncoder();
|
22
|
+
const secretKey = await crypto.subtle.digest('SHA-256', encoder.encode(JWT_SECRET_KEY));
|
23
|
+
|
24
|
+
const jwkSecretKey = await importJWK(
|
25
|
+
{ k: Buffer.from(secretKey).toString('base64'), kty: 'oct' },
|
26
|
+
'HS256',
|
27
|
+
);
|
28
|
+
|
29
|
+
const { payload } = await jwtVerify(token, jwkSecretKey);
|
30
|
+
|
31
|
+
return payload as JWTPayload;
|
32
|
+
};
|