Skip to content

Commit 06b7164

Browse files
authored
♻️ refactor: refactor jose-JWT to xor obfuscation (#8595)
* refactor jose-jwt to xor obfuscation * rename JWTPayload type to ClientSecretPayload * fix tests * revert next version
1 parent 5069e87 commit 06b7164

File tree

27 files changed

+665
-236
lines changed

27 files changed

+665
-236
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@
196196
"i18next-resources-to-backend": "^1.2.1",
197197
"idb-keyval": "^6.2.2",
198198
"immer": "^10.1.1",
199-
"jose": "^5.10.0",
199+
"jose": "^6.0.12",
200200
"js-sha256": "^0.11.1",
201201
"jsonl-parse-stringify": "^1.0.3",
202202
"keyv": "^4.5.4",

src/app/(backend)/middleware/auth/index.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
33
import { AgentRuntimeError } from '@/libs/model-runtime';
44
import { ChatErrorType } from '@/types/fetch';
55
import { createErrorResponse } from '@/utils/errorResponse';
6-
import { getJWTPayload } from '@/utils/server/jwt';
6+
import { getXorPayload } from '@/utils/server/xor';
77

88
import { RequestHandler, checkAuth } from './index';
99
import { checkAuthMethod } from './utils';
@@ -20,8 +20,8 @@ vi.mock('./utils', () => ({
2020
checkAuthMethod: vi.fn(),
2121
}));
2222

23-
vi.mock('@/utils/server/jwt', () => ({
24-
getJWTPayload: vi.fn(),
23+
vi.mock('@/utils/server/xor', () => ({
24+
getXorPayload: vi.fn(),
2525
}));
2626

2727
describe('checkAuth', () => {
@@ -50,7 +50,7 @@ describe('checkAuth', () => {
5050
it('should return error response on getJWTPayload error', async () => {
5151
const mockError = AgentRuntimeError.createError(ChatErrorType.Unauthorized);
5252
mockRequest.headers.set('Authorization', 'invalid');
53-
vi.mocked(getJWTPayload).mockRejectedValueOnce(mockError);
53+
vi.mocked(getXorPayload).mockRejectedValueOnce(mockError);
5454

5555
await checkAuth(mockHandler)(mockRequest, mockOptions);
5656

@@ -64,7 +64,7 @@ describe('checkAuth', () => {
6464
it('should return error response on checkAuthMethod error', async () => {
6565
const mockError = AgentRuntimeError.createError(ChatErrorType.Unauthorized);
6666
mockRequest.headers.set('Authorization', 'valid');
67-
vi.mocked(getJWTPayload).mockResolvedValueOnce({});
67+
vi.mocked(getXorPayload).mockResolvedValueOnce({});
6868
vi.mocked(checkAuthMethod).mockImplementationOnce(() => {
6969
throw mockError;
7070
});

src/app/(backend)/middleware/auth/index.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { AuthObject } from '@clerk/backend';
22
import { NextRequest } from 'next/server';
33

44
import {
5-
JWTPayload,
5+
ClientSecretPayload,
66
LOBE_CHAT_AUTH_HEADER,
77
LOBE_CHAT_OIDC_AUTH_HEADER,
88
OAUTH_AUTHORIZED,
@@ -13,18 +13,18 @@ import { AgentRuntime, AgentRuntimeError, ChatCompletionErrorPayload } from '@/l
1313
import { validateOIDCJWT } from '@/libs/oidc-provider/jwt';
1414
import { ChatErrorType } from '@/types/fetch';
1515
import { createErrorResponse } from '@/utils/errorResponse';
16-
import { getJWTPayload } from '@/utils/server/jwt';
16+
import { getXorPayload } from '@/utils/server/xor';
1717

1818
import { checkAuthMethod } from './utils';
1919

20-
type CreateRuntime = (jwtPayload: JWTPayload) => AgentRuntime;
20+
type CreateRuntime = (jwtPayload: ClientSecretPayload) => AgentRuntime;
2121
type RequestOptions = { createRuntime?: CreateRuntime; params: Promise<{ provider: string }> };
2222

2323
export type RequestHandler = (
2424
req: Request,
2525
options: RequestOptions & {
2626
createRuntime?: CreateRuntime;
27-
jwtPayload: JWTPayload;
27+
jwtPayload: ClientSecretPayload;
2828
},
2929
) => Promise<Response>;
3030

@@ -36,7 +36,7 @@ export const checkAuth =
3636
return handler(req, { ...options, jwtPayload: { userId: 'DEV_USER' } });
3737
}
3838

39-
let jwtPayload: JWTPayload;
39+
let jwtPayload: ClientSecretPayload;
4040

4141
try {
4242
// get Authorization from header
@@ -55,7 +55,7 @@ export const checkAuth =
5555
clerkAuth = data.clerkAuth;
5656
}
5757

58-
jwtPayload = await getJWTPayload(authorization);
58+
jwtPayload = getXorPayload(authorization);
5959

6060
const oidcAuthorization = req.headers.get(LOBE_CHAT_OIDC_AUTH_HEADER);
6161
let isUseOidcAuth = false;

src/app/(backend)/webapi/chat/[provider]/route.test.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { checkAuthMethod } from '@/app/(backend)/middleware/auth/utils';
66
import { LOBE_CHAT_AUTH_HEADER, OAUTH_AUTHORIZED } from '@/const/auth';
77
import { AgentRuntime, LobeRuntimeAI } from '@/libs/model-runtime';
88
import { ChatErrorType } from '@/types/fetch';
9-
import { getJWTPayload } from '@/utils/server/jwt';
9+
import { getXorPayload } from '@/utils/server/xor';
1010

1111
import { POST } from './route';
1212

@@ -18,8 +18,8 @@ vi.mock('@/app/(backend)/middleware/auth/utils', () => ({
1818
checkAuthMethod: vi.fn(),
1919
}));
2020

21-
vi.mock('@/utils/server/jwt', () => ({
22-
getJWTPayload: vi.fn(),
21+
vi.mock('@/utils/server/xor', () => ({
22+
getXorPayload: vi.fn(),
2323
}));
2424

2525
// 定义一个变量来存储 enableAuth 的值
@@ -61,7 +61,7 @@ describe('POST handler', () => {
6161
const mockParams = Promise.resolve({ provider: 'test-provider' });
6262

6363
// 设置 getJWTPayload 和 initAgentRuntimeWithUserPayload 的模拟返回值
64-
vi.mocked(getJWTPayload).mockResolvedValueOnce({
64+
vi.mocked(getXorPayload).mockReturnValueOnce({
6565
accessCode: 'test-access-code',
6666
apiKey: 'test-api-key',
6767
azureApiVersion: 'v1',
@@ -78,7 +78,7 @@ describe('POST handler', () => {
7878
await POST(request as unknown as Request, { params: mockParams });
7979

8080
// 验证是否正确调用了模拟函数
81-
expect(getJWTPayload).toHaveBeenCalledWith('Bearer some-valid-token');
81+
expect(getXorPayload).toHaveBeenCalledWith('Bearer some-valid-token');
8282
expect(spy).toHaveBeenCalledWith('test-provider', expect.anything());
8383
});
8484

@@ -104,7 +104,7 @@ describe('POST handler', () => {
104104
it('should have pass clerk Auth when enable clerk', async () => {
105105
enableClerk = true;
106106

107-
vi.mocked(getJWTPayload).mockResolvedValueOnce({
107+
vi.mocked(getXorPayload).mockReturnValueOnce({
108108
accessCode: 'test-access-code',
109109
apiKey: 'test-api-key',
110110
azureApiVersion: 'v1',
@@ -142,7 +142,9 @@ describe('POST handler', () => {
142142

143143
it('should return InternalServerError error when throw a unknown error', async () => {
144144
const mockParams = Promise.resolve({ provider: 'test-provider' });
145-
vi.mocked(getJWTPayload).mockRejectedValueOnce(new Error('unknown error'));
145+
vi.mocked(getXorPayload).mockImplementationOnce(() => {
146+
throw new Error('unknown error');
147+
});
146148

147149
const response = await POST(request, { params: mockParams });
148150

@@ -159,7 +161,7 @@ describe('POST handler', () => {
159161

160162
describe('chat', () => {
161163
it('should correctly handle chat completion with valid payload', async () => {
162-
vi.mocked(getJWTPayload).mockResolvedValueOnce({
164+
vi.mocked(getXorPayload).mockReturnValueOnce({
163165
accessCode: 'test-access-code',
164166
apiKey: 'test-api-key',
165167
azureApiVersion: 'v1',
@@ -189,7 +191,7 @@ describe('POST handler', () => {
189191

190192
it('should return an error response when chat completion fails', async () => {
191193
// 设置 getJWTPayload 和 initAgentRuntimeWithUserPayload 的模拟返回值
192-
vi.mocked(getJWTPayload).mockResolvedValueOnce({
194+
vi.mocked(getXorPayload).mockReturnValueOnce({
193195
accessCode: 'test-access-code',
194196
apiKey: 'test-api-key',
195197
azureApiVersion: 'v1',

src/app/(backend)/webapi/plugin/gateway/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { AgentRuntimeError } from '@/libs/model-runtime';
88
import { TraceClient } from '@/libs/traces';
99
import { ChatErrorType, ErrorType } from '@/types/fetch';
1010
import { createErrorResponse } from '@/utils/errorResponse';
11-
import { getJWTPayload } from '@/utils/server/jwt';
11+
import { getXorPayload } from '@/utils/server/xor';
1212
import { getTracePayload } from '@/utils/trace';
1313

1414
import { parserPluginSettings } from './settings';
@@ -44,7 +44,7 @@ export const POST = async (req: Request) => {
4444
if (!authorization) throw AgentRuntimeError.createError(ChatErrorType.Unauthorized);
4545

4646
const oauthAuthorized = !!req.headers.get(OAUTH_AUTHORIZED);
47-
const payload = await getJWTPayload(authorization);
47+
const payload = getXorPayload(authorization);
4848

4949
const result = checkAuth(payload.accessCode!, oauthAuthorized);
5050

src/const/auth.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@ export const LOBE_CHAT_OIDC_AUTH_HEADER = 'Oidc-Auth';
99

1010
export const OAUTH_AUTHORIZED = 'X-oauth-authorized';
1111

12-
export const JWT_SECRET_KEY = 'LobeHub · LobeChat';
13-
export const NON_HTTP_PREFIX = 'http_nosafe';
12+
export const SECRET_XOR_KEY = 'LobeHub · LobeHub';
1413

1514
/* eslint-disable typescript-sort-keys/interface */
16-
export interface JWTPayload {
15+
export interface ClientSecretPayload {
1716
/**
1817
* password
1918
*/

src/libs/model-runtime/ModelRuntime.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { LangfuseGenerationClient, LangfuseTraceClient } from 'langfuse-core';
44
import { beforeEach, describe, expect, it, vi } from 'vitest';
55

66
import * as langfuseCfg from '@/config/langfuse';
7-
import { JWTPayload } from '@/const/auth';
7+
import { ClientSecretPayload } from '@/const/auth';
88
import { TraceNameMap } from '@/const/trace';
99
import { AgentRuntime, ChatStreamPayload, LobeOpenAI, ModelProvider } from '@/libs/model-runtime';
1010
import { providerRuntimeMap } from '@/libs/model-runtime/runtimeMap';
@@ -51,7 +51,7 @@ const specialProviders = [
5151
const testRuntime = (providerId: string, payload?: any) => {
5252
describe(`${providerId} provider runtime`, () => {
5353
it('should initialize correctly', async () => {
54-
const jwtPayload: JWTPayload = { apiKey: 'user-key', ...payload };
54+
const jwtPayload: ClientSecretPayload = { apiKey: 'user-key', ...payload };
5555
const runtime = await AgentRuntime.initializeWithProvider(providerId, jwtPayload);
5656

5757
// @ts-ignore
@@ -66,7 +66,7 @@ const testRuntime = (providerId: string, payload?: any) => {
6666

6767
let mockModelRuntime: AgentRuntime;
6868
beforeEach(async () => {
69-
const jwtPayload: JWTPayload = { apiKey: 'user-openai-key', baseURL: 'user-endpoint' };
69+
const jwtPayload: ClientSecretPayload = { apiKey: 'user-openai-key', baseURL: 'user-endpoint' };
7070
mockModelRuntime = await AgentRuntime.initializeWithProvider(ModelProvider.OpenAI, jwtPayload);
7171
});
7272

src/libs/trpc/async/context.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import debug from 'debug';
22
import { NextRequest } from 'next/server';
33

4-
import { JWTPayload, LOBE_CHAT_AUTH_HEADER } from '@/const/auth';
4+
import { ClientSecretPayload, LOBE_CHAT_AUTH_HEADER } from '@/const/auth';
55
import { LobeChatDatabase } from '@/database/type';
66
import { KeyVaultsGateKeeper } from '@/server/modules/KeyVaultsEncrypt';
77

88
const log = debug('lobe-async:context');
99

1010
export interface AsyncAuthContext {
11-
jwtPayload: JWTPayload;
11+
jwtPayload: ClientSecretPayload;
1212
secret: string;
1313
serverDB?: LobeChatDatabase;
1414
userId?: string | null;
@@ -19,7 +19,7 @@ export interface AsyncAuthContext {
1919
* This is useful for testing when we don't want to mock Next.js' request/response
2020
*/
2121
export const createAsyncContextInner = async (params?: {
22-
jwtPayload?: JWTPayload;
22+
jwtPayload?: ClientSecretPayload;
2323
secret?: string;
2424
userId?: string | null;
2525
}): Promise<AsyncAuthContext> => ({

src/libs/trpc/edge/context.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
import { User } from 'next-auth';
22
import { NextRequest } from 'next/server';
33

4-
import { JWTPayload, LOBE_CHAT_AUTH_HEADER, enableClerk, enableNextAuth } from '@/const/auth';
4+
import {
5+
ClientSecretPayload,
6+
LOBE_CHAT_AUTH_HEADER,
7+
enableClerk,
8+
enableNextAuth,
9+
} from '@/const/auth';
510
import { ClerkAuth, IClerkAuth } from '@/libs/clerk-auth';
611

712
export interface AuthContext {
813
authorizationHeader?: string | null;
914
clerkAuth?: IClerkAuth;
10-
jwtPayload?: JWTPayload | null;
15+
jwtPayload?: ClientSecretPayload | null;
1116
nextAuth?: User;
1217
userId?: string | null;
1318
}

src/libs/trpc/edge/middleware/jwtPayload.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
55
import { createCallerFactory } from '@/libs/trpc/edge';
66
import { AuthContext, createContextInner } from '@/libs/trpc/edge/context';
77
import { edgeTrpc as trpc } from '@/libs/trpc/edge/init';
8-
import * as utils from '@/utils/server/jwt';
8+
import * as utils from '@/utils/server/xor';
99

1010
import { jwtPayloadChecker } from './jwtPayload';
1111

@@ -40,7 +40,7 @@ describe('passwordChecker middleware', () => {
4040
it('should call next with jwtPayload in context if access code is correct', async () => {
4141
const jwtPayload = { accessCode: '123' };
4242

43-
vi.spyOn(utils, 'getJWTPayload').mockResolvedValue(jwtPayload);
43+
vi.spyOn(utils, 'getXorPayload').mockResolvedValue(jwtPayload);
4444

4545
ctx = await createContextInner({ authorizationHeader: 'Bearer token' });
4646
router = createCaller(ctx);
@@ -52,7 +52,7 @@ describe('passwordChecker middleware', () => {
5252

5353
it('should call next with jwtPayload in context if no access codes are set', async () => {
5454
const jwtPayload = {};
55-
vi.spyOn(utils, 'getJWTPayload').mockResolvedValue(jwtPayload);
55+
vi.spyOn(utils, 'getXorPayload').mockResolvedValue(jwtPayload);
5656

5757
ctx = await createContextInner({ authorizationHeader: 'Bearer token' });
5858
router = createCaller(ctx);
@@ -63,7 +63,7 @@ describe('passwordChecker middleware', () => {
6363
});
6464
it('should call next with jwtPayload in context if access codes is undefined', async () => {
6565
const jwtPayload = {};
66-
vi.spyOn(utils, 'getJWTPayload').mockResolvedValue(jwtPayload);
66+
vi.spyOn(utils, 'getXorPayload').mockResolvedValue(jwtPayload);
6767

6868
ctx = await createContextInner({ authorizationHeader: 'Bearer token' });
6969
router = createCaller(ctx);

0 commit comments

Comments
 (0)