Skip to content

Commit 7dd65f0

Browse files
authored
🐛 fix: fix webapi proxy with clerk (#8479)
* fix webapi proxy with clerk * Update jwt.ts
1 parent ecf1fdc commit 7dd65f0

File tree

4 files changed

+59
-22
lines changed

4 files changed

+59
-22
lines changed

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

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

4-
import { JWTPayload, LOBE_CHAT_AUTH_HEADER, OAUTH_AUTHORIZED, enableClerk } from '@/const/auth';
4+
import {
5+
JWTPayload,
6+
LOBE_CHAT_AUTH_HEADER,
7+
LOBE_CHAT_OIDC_AUTH_HEADER,
8+
OAUTH_AUTHORIZED,
9+
enableClerk,
10+
} from '@/const/auth';
511
import { ClerkAuth } from '@/libs/clerk-auth';
612
import { AgentRuntime, AgentRuntimeError, ChatCompletionErrorPayload } from '@/libs/model-runtime';
13+
import { validateOIDCJWT } from '@/libs/oidc-provider/jwt';
714
import { ChatErrorType } from '@/types/fetch';
815
import { createErrorResponse } from '@/utils/errorResponse';
916
import { getJWTPayload } from '@/utils/server/jwt';
@@ -50,12 +57,26 @@ export const checkAuth =
5057

5158
jwtPayload = await getJWTPayload(authorization);
5259

53-
checkAuthMethod({
54-
accessCode: jwtPayload.accessCode,
55-
apiKey: jwtPayload.apiKey,
56-
clerkAuth,
57-
nextAuthAuthorized: oauthAuthorized,
58-
});
60+
const oidcAuthorization = req.headers.get(LOBE_CHAT_OIDC_AUTH_HEADER);
61+
let isUseOidcAuth = false;
62+
if (!!oidcAuthorization) {
63+
const oidc = await validateOIDCJWT(oidcAuthorization);
64+
65+
isUseOidcAuth = true;
66+
67+
jwtPayload = {
68+
...jwtPayload,
69+
userId: oidc.userId,
70+
};
71+
}
72+
73+
if (!isUseOidcAuth)
74+
checkAuthMethod({
75+
accessCode: jwtPayload.accessCode,
76+
apiKey: jwtPayload.apiKey,
77+
clerkAuth,
78+
nextAuthAuthorized: oauthAuthorized,
79+
});
5980
} catch (e) {
6081
const params = await options.params;
6182

src/const/auth.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export const enableNextAuth = authEnv.NEXT_PUBLIC_ENABLE_NEXT_AUTH;
55
export const enableAuth = enableClerk || enableNextAuth || false;
66

77
export const LOBE_CHAT_AUTH_HEADER = 'X-lobe-chat-auth';
8+
export const LOBE_CHAT_OIDC_AUTH_HEADER = 'Oidc-Auth';
89

910
export const OAUTH_AUTHORIZED = 'X-oauth-authorized';
1011

src/libs/oidc-provider/jwt.ts

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,7 @@ export const getJWKS = (): object => {
4141
}
4242
};
4343

44-
/**
45-
* 从环境变量中获取 JWKS 并提取第一个 RSA 密钥
46-
*/
47-
const getJWKSPublicKey = async () => {
44+
const getVerificationKey = async () => {
4845
try {
4946
const jwksString = oidcEnv.OIDC_JWKS_KEY;
5047

@@ -58,20 +55,32 @@ const getJWKSPublicKey = async () => {
5855
throw new Error('JWKS 格式无效: 缺少或为空的 keys 数组');
5956
}
6057

61-
// 查找 RS256 算法的 RSA 密钥
62-
const rsaKey = jwks.keys.find((key: any) => key.alg === 'RS256' && key.kty === 'RSA');
63-
64-
if (!rsaKey) {
58+
const privateRsaKey = jwks.keys.find((key: any) => key.alg === 'RS256' && key.kty === 'RSA');
59+
if (!privateRsaKey) {
6560
throw new Error('JWKS 中没有找到 RS256 算法的 RSA 密钥');
6661
}
6762

68-
// 导入 JWK 为公钥
69-
const publicKey = await importJWK(rsaKey, 'RS256');
63+
// 创建一个只包含公钥组件的“纯净”JWK对象。
64+
// RSA公钥的关键字段是 kty, n, e。其他如 kid, alg, use 也是公共的。
65+
const publicKeyJwk = {
66+
alg: privateRsaKey.alg,
67+
e: privateRsaKey.e,
68+
kid: privateRsaKey.kid,
69+
kty: privateRsaKey.kty,
70+
n: privateRsaKey.n,
71+
use: privateRsaKey.use,
72+
};
73+
74+
// 移除任何可能存在的 undefined 字段,保持对象干净
75+
Object.keys(publicKeyJwk).forEach(
76+
(key) => (publicKeyJwk as any)[key] === undefined && delete (publicKeyJwk as any)[key],
77+
);
7078

71-
return publicKey;
79+
// 现在,无论在哪个环境下,`importJWK` 都会将这个对象正确地识别为一个公钥。
80+
return await importJWK(publicKeyJwk, 'RS256');
7281
} catch (error) {
7382
log('获取 JWKS 公钥失败: %O', error);
74-
throw new Error(`JWKS 公钥获取失败: ${(error as Error).message}`);
83+
throw new Error(`JWKS 公key获取失败: ${(error as Error).message}`);
7584
}
7685
};
7786

@@ -85,7 +94,7 @@ export const validateOIDCJWT = async (token: string) => {
8594
log('开始验证 OIDC JWT token');
8695

8796
// 获取公钥
88-
const publicKey = await getJWKSPublicKey();
97+
const publicKey = await getVerificationKey();
8998

9099
// 验证 JWT
91100
const { payload } = await jwtVerify(token, publicKey, {

src/libs/trpc/lambda/context.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@ import debug from 'debug';
33
import { User } from 'next-auth';
44
import { NextRequest } from 'next/server';
55

6-
import { JWTPayload, LOBE_CHAT_AUTH_HEADER, enableClerk, enableNextAuth } from '@/const/auth';
6+
import {
7+
JWTPayload,
8+
LOBE_CHAT_AUTH_HEADER,
9+
LOBE_CHAT_OIDC_AUTH_HEADER,
10+
enableClerk,
11+
enableNextAuth,
12+
} from '@/const/auth';
713
import { oidcEnv } from '@/envs/oidc';
814
import { ClerkAuth, IClerkAuth } from '@/libs/clerk-auth';
915
import { validateOIDCJWT } from '@/libs/oidc-provider/jwt';
@@ -102,7 +108,7 @@ export const createLambdaContext = async (request: NextRequest): Promise<LambdaC
102108
if (oidcEnv.ENABLE_OIDC) {
103109
log('OIDC enabled, attempting OIDC authentication');
104110
const standardAuthorization = request.headers.get('Authorization');
105-
const oidcAuthToken = request.headers.get('Oidc-Auth');
111+
const oidcAuthToken = request.headers.get(LOBE_CHAT_OIDC_AUTH_HEADER);
106112
log('Standard Authorization header: %s', standardAuthorization ? 'exists' : 'not found');
107113
log('Oidc-Auth header: %s', oidcAuthToken ? 'exists' : 'not found');
108114

0 commit comments

Comments
 (0)