@lobehub/chat 1.79.8 → 1.79.9
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/.eslintrc.js +1 -0
- package/CHANGELOG.md +33 -0
- package/changelog/v1.json +9 -0
- package/locales/ar/oauth.json +7 -6
- package/locales/bg-BG/oauth.json +7 -6
- package/locales/de-DE/oauth.json +7 -6
- package/locales/en-US/oauth.json +7 -6
- package/locales/es-ES/oauth.json +7 -6
- package/locales/fa-IR/oauth.json +7 -6
- package/locales/fr-FR/oauth.json +7 -6
- package/locales/it-IT/oauth.json +7 -6
- package/locales/ja-JP/oauth.json +7 -6
- package/locales/ko-KR/oauth.json +7 -6
- package/locales/nl-NL/oauth.json +7 -6
- package/locales/pl-PL/oauth.json +7 -6
- package/locales/pt-BR/oauth.json +7 -6
- package/locales/ru-RU/oauth.json +7 -6
- package/locales/tr-TR/oauth.json +7 -6
- package/locales/vi-VN/oauth.json +7 -6
- package/locales/zh-CN/oauth.json +7 -6
- package/locales/zh-TW/oauth.json +7 -6
- package/package.json +1 -1
- package/src/app/(backend)/oidc/[...oidc]/route.ts +27 -201
- package/src/app/(backend)/oidc/consent/route.ts +58 -24
- package/src/app/(backend)/trpc/async/[trpc]/route.ts +1 -1
- package/src/app/(backend)/trpc/edge/[trpc]/route.ts +2 -2
- package/src/app/(backend)/trpc/lambda/[trpc]/route.ts +2 -2
- package/src/app/(backend)/trpc/tools/[trpc]/route.ts +2 -2
- package/src/app/[variants]/(main)/files/[id]/page.tsx +1 -1
- package/src/app/[variants]/oauth/consent/[uid]/Client.tsx +184 -57
- package/src/app/[variants]/oauth/consent/[uid]/ClientError.tsx +46 -0
- package/src/app/[variants]/oauth/consent/[uid]/page.tsx +19 -21
- package/src/components/Branding/ProductLogo/index.tsx +6 -1
- package/src/config/aiModels/openai.ts +63 -41
- package/src/database/server/models/__tests__/adapter.test.ts +1 -5
- package/src/libs/oidc-provider/adapter.ts +47 -0
- package/src/libs/oidc-provider/config.ts +4 -5
- package/src/libs/oidc-provider/http-adapter.ts +60 -28
- package/src/libs/oidc-provider/provider.ts +41 -13
- package/src/libs/trpc/async/init.ts +1 -1
- package/src/{server → libs/trpc/edge}/context.ts +2 -2
- package/src/libs/trpc/{index.ts → edge/index.ts} +8 -8
- package/src/libs/trpc/{init.ts → edge/init.ts} +2 -2
- package/src/libs/trpc/{middleware → edge/middleware}/jwtPayload.test.ts +3 -3
- package/src/libs/trpc/{middleware → edge/middleware}/jwtPayload.ts +3 -2
- package/src/libs/trpc/lambda/context.ts +70 -0
- package/src/libs/trpc/lambda/index.ts +39 -1
- package/src/libs/trpc/lambda/init.ts +26 -0
- package/src/libs/trpc/lambda/middleware/index.ts +2 -0
- package/src/libs/trpc/{middleware → lambda/middleware}/keyVaults.ts +2 -1
- package/src/libs/trpc/lambda/{serverDatabase.ts → middleware/serverDatabase.ts} +2 -1
- package/src/libs/trpc/middleware/userAuth.test.ts +3 -3
- package/src/libs/trpc/middleware/userAuth.ts +1 -1
- package/src/libs/trpc/mock.ts +7 -0
- package/src/locales/default/oauth.ts +8 -6
- package/src/server/routers/edge/appStatus.ts +1 -1
- package/src/server/routers/edge/config/index.test.ts +2 -3
- package/src/server/routers/edge/config/index.ts +1 -1
- package/src/server/routers/edge/index.ts +1 -1
- package/src/server/routers/edge/upload.ts +1 -1
- package/src/server/routers/lambda/_template.ts +2 -2
- package/src/server/routers/lambda/agent.ts +2 -2
- package/src/server/routers/lambda/aiModel.ts +2 -2
- package/src/server/routers/lambda/aiProvider.ts +2 -2
- package/src/server/routers/lambda/chunk.ts +2 -3
- package/src/server/routers/lambda/exporter.ts +2 -2
- package/src/server/routers/lambda/file.ts +2 -2
- package/src/server/routers/lambda/importer.ts +2 -2
- package/src/server/routers/lambda/index.ts +1 -1
- package/src/server/routers/lambda/knowledgeBase.ts +2 -2
- package/src/server/routers/lambda/message.ts +2 -2
- package/src/server/routers/lambda/plugin.ts +2 -2
- package/src/server/routers/lambda/ragEval.ts +2 -3
- package/src/server/routers/lambda/session.ts +2 -2
- package/src/server/routers/lambda/sessionGroup.ts +2 -2
- package/src/server/routers/lambda/thread.ts +2 -2
- package/src/server/routers/lambda/topic.ts +2 -2
- package/src/server/routers/lambda/user.ts +2 -2
- package/src/server/routers/tools/__tests__/search.test.ts +2 -2
- package/src/server/routers/tools/index.ts +1 -1
- package/src/server/routers/tools/search.ts +2 -1
- package/src/server/services/oidc/index.ts +36 -1
- package/src/server/services/oidc/oidcProvider.ts +1 -3
- package/src/server/mock.ts +0 -8
- /package/src/{server/asyncContext.ts → libs/trpc/async/context.ts} +0 -0
@@ -5,27 +5,26 @@ import { ClientMetadata } from 'oidc-provider';
|
|
5
5
|
*/
|
6
6
|
export const defaultClients: ClientMetadata[] = [
|
7
7
|
{
|
8
|
-
// 公共客户端,令牌端点无需认证
|
9
8
|
application_type: 'native',
|
10
9
|
client_id: 'lobehub-desktop',
|
11
|
-
|
10
|
+
client_name: 'LobeHub Desktop',
|
12
11
|
// 仅支持授权码流程
|
13
12
|
grant_types: ['authorization_code', 'refresh_token'],
|
13
|
+
|
14
14
|
// 明确指明是原生应用
|
15
15
|
isFirstParty: true,
|
16
16
|
|
17
|
-
|
17
|
+
logo_uri: 'https://hub-apac-1.lobeobjects.space/lobehub-desktop-icon.png',
|
18
18
|
|
19
19
|
// 桌面端注册的自定义协议回调(使用反向域名格式)
|
20
20
|
post_logout_redirect_uris: ['com.lobehub.desktop://auth/logout/callback'],
|
21
21
|
|
22
|
-
// 公共客户端,无密钥
|
23
22
|
redirect_uris: ['com.lobehub.desktop://auth/callback', 'https://oauthdebugger.com/debug'],
|
24
23
|
|
25
24
|
// 支持授权码获取令牌和刷新令牌
|
26
25
|
response_types: ['code'],
|
27
26
|
|
28
|
-
//
|
27
|
+
// 标记为公共客户端客户端,无密钥
|
29
28
|
token_endpoint_auth_method: 'none',
|
30
29
|
},
|
31
30
|
];
|
@@ -22,22 +22,13 @@ export const convertHeadersToNodeHeaders = (nextHeaders: Headers): Record<string
|
|
22
22
|
/**
|
23
23
|
* 创建用于 OIDC Provider 的 Node.js HTTP 请求对象
|
24
24
|
* @param req Next.js 请求对象
|
25
|
-
* @param pathPrefix 路径前缀
|
26
|
-
* @param bodyText 可选的请求体文本,用于 POST 请求
|
27
25
|
*/
|
28
|
-
export const createNodeRequest = (
|
29
|
-
req: NextRequest,
|
30
|
-
pathPrefix: string = '/oidc',
|
31
|
-
bodyText?: string,
|
32
|
-
): IncomingMessage => {
|
26
|
+
export const createNodeRequest = async (req: NextRequest): Promise<IncomingMessage> => {
|
33
27
|
// 构建 URL 对象
|
34
28
|
const url = new URL(req.url);
|
35
29
|
|
36
30
|
// 计算相对于前缀的路径
|
37
31
|
let providerPath = url.pathname;
|
38
|
-
if (pathPrefix && url.pathname.startsWith(pathPrefix)) {
|
39
|
-
providerPath = url.pathname.slice(pathPrefix.length);
|
40
|
-
}
|
41
32
|
|
42
33
|
// 确保路径始终以/开头
|
43
34
|
if (!providerPath.startsWith('/')) {
|
@@ -47,37 +38,74 @@ export const createNodeRequest = (
|
|
47
38
|
log('Creating Node.js request from Next.js request');
|
48
39
|
log('Original path: %s, Provider path: %s', url.pathname, providerPath);
|
49
40
|
|
41
|
+
// Attempt to parse and attach body for relevant methods
|
42
|
+
let parsedBody: any = undefined;
|
43
|
+
const methodsWithBody = ['POST', 'PUT', 'PATCH', 'DELETE'];
|
44
|
+
if (methodsWithBody.includes(req.method)) {
|
45
|
+
const contentType = req.headers.get('content-type')?.split(';')[0]; // Get content type without charset etc.
|
46
|
+
log(`Attempting to parse body for ${req.method} with Content-Type: ${contentType}`);
|
47
|
+
try {
|
48
|
+
// Check if body exists and has size before attempting to parse
|
49
|
+
if (req.body && req.headers.get('content-length') !== '0') {
|
50
|
+
if (contentType === 'application/x-www-form-urlencoded') {
|
51
|
+
const formData = await req.formData();
|
52
|
+
parsedBody = {};
|
53
|
+
formData.forEach((value, key) => {
|
54
|
+
// If a key appears multiple times, keep the last one (standard form behavior)
|
55
|
+
// Or convert to array if oidc-provider expects it:
|
56
|
+
// if (parsedBody[key]) {
|
57
|
+
// if (!Array.isArray(parsedBody[key])) parsedBody[key] = [parsedBody[key]];
|
58
|
+
// parsedBody[key].push(value);
|
59
|
+
// } else {
|
60
|
+
// parsedBody[key] = value;
|
61
|
+
// }
|
62
|
+
parsedBody[key] = value;
|
63
|
+
});
|
64
|
+
log('Parsed form data body: %O', parsedBody);
|
65
|
+
} else if (contentType === 'application/json') {
|
66
|
+
parsedBody = await req.json();
|
67
|
+
log('Parsed JSON body: %O', parsedBody);
|
68
|
+
} else {
|
69
|
+
log(`Body parsing skipped for Content-Type: ${contentType}. Trying text() as fallback.`);
|
70
|
+
// Fallback: try reading as text if content type is unknown but body exists
|
71
|
+
parsedBody = await req.text();
|
72
|
+
log('Parsed body as text fallback.');
|
73
|
+
}
|
74
|
+
} else {
|
75
|
+
log('Request has no body or content-length is 0, skipping parsing.');
|
76
|
+
}
|
77
|
+
} catch (error) {
|
78
|
+
log('Error parsing request body: %O', error);
|
79
|
+
// Keep parsedBody as undefined, let oidc-provider handle the potential issue
|
80
|
+
}
|
81
|
+
}
|
50
82
|
const nodeRequest = {
|
51
83
|
// 基本属性
|
52
84
|
headers: convertHeadersToNodeHeaders(req.headers),
|
85
|
+
|
53
86
|
method: req.method,
|
54
|
-
// 模拟可读流行为
|
87
|
+
// 模拟可读流行为 (oidc-provider might not rely on this if body is pre-parsed)
|
55
88
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
56
89
|
on: (event: string, handler: Function) => {
|
57
|
-
if (event === 'data' && bodyText) {
|
58
|
-
handler(bodyText);
|
59
|
-
}
|
60
90
|
if (event === 'end') {
|
91
|
+
// Simulate end immediately as body is already processed or will be attached
|
61
92
|
handler();
|
62
93
|
}
|
63
94
|
},
|
64
|
-
|
65
|
-
url: providerPath + url.search,
|
66
|
-
|
67
|
-
// POST 请求所需属性
|
68
|
-
...(bodyText && {
|
69
|
-
body: bodyText,
|
70
|
-
readable: true,
|
71
|
-
}),
|
72
|
-
|
73
95
|
// 添加 Node.js 服务器所期望的额外属性
|
74
96
|
socket: {
|
75
97
|
remoteAddress: req.headers.get('x-forwarded-for') || '127.0.0.1',
|
76
98
|
},
|
77
|
-
|
99
|
+
url: providerPath + url.search,
|
100
|
+
...(parsedBody !== undefined && { body: parsedBody }), // Attach body if it exists
|
101
|
+
};
|
78
102
|
|
79
103
|
log('Node.js request created with method %s and path %s', nodeRequest.method, nodeRequest.url);
|
80
|
-
|
104
|
+
if (nodeRequest.body) {
|
105
|
+
log('Attached parsed body to Node.js request.');
|
106
|
+
}
|
107
|
+
// Cast back to IncomingMessage for the function's return signature
|
108
|
+
return nodeRequest as unknown as IncomingMessage;
|
81
109
|
};
|
82
110
|
|
83
111
|
/**
|
@@ -143,6 +171,12 @@ export const createNodeResponse = (resolvePromise: () => void): ResponseCollecto
|
|
143
171
|
|
144
172
|
headersSent: false,
|
145
173
|
|
174
|
+
removeHeader: (name: string) => {
|
175
|
+
const lowerName = name.toLowerCase();
|
176
|
+
log('Removing header: %s', lowerName);
|
177
|
+
delete state.responseHeaders[lowerName];
|
178
|
+
},
|
179
|
+
|
146
180
|
setHeader: (name: string, value: string | string[]) => {
|
147
181
|
const lowerName = name.toLowerCase();
|
148
182
|
log('Setting header: %s = %s', lowerName, value);
|
@@ -198,8 +232,6 @@ export const createContextForInteractionDetails = async (
|
|
198
232
|
uid: string,
|
199
233
|
): Promise<{ req: IncomingMessage; res: ServerResponse }> => {
|
200
234
|
log('Creating context for interaction details for uid: %s', uid);
|
201
|
-
|
202
|
-
// 使用APP_URL环境变量来构建URL基础部分
|
203
235
|
const baseUrl = appEnv.APP_URL!;
|
204
236
|
log('Using base URL: %s', baseUrl);
|
205
237
|
|
@@ -260,7 +292,7 @@ export const createContextForInteractionDetails = async (
|
|
260
292
|
|
261
293
|
// 4. 使用 createNodeRequest 创建模拟的 Node.js IncomingMessage
|
262
294
|
// pathPrefix 设置为 '/' 因为我们的 URL 已经是 Provider 期望的路径格式 /interaction/:uid
|
263
|
-
const req: IncomingMessage = createNodeRequest(mockNextRequest
|
295
|
+
const req: IncomingMessage = await createNodeRequest(mockNextRequest);
|
264
296
|
// @ts-ignore - 将解析出的 cookies 附加到模拟的 Node.js 请求上
|
265
297
|
req.cookies = realCookies;
|
266
298
|
log('Node.js IncomingMessage created, attached real cookies');
|
@@ -1,6 +1,8 @@
|
|
1
1
|
import debug from 'debug';
|
2
2
|
import Provider, { Configuration, KoaContextWithOIDC } from 'oidc-provider';
|
3
|
+
import urlJoin from 'url-join';
|
3
4
|
|
5
|
+
import { appEnv } from '@/config/app';
|
4
6
|
import { serverDBEnv } from '@/config/db';
|
5
7
|
import { UserModel } from '@/database/models/user';
|
6
8
|
import { LobeChatDatabase } from '@/database/type';
|
@@ -61,18 +63,9 @@ const getCookieKeys = () => {
|
|
61
63
|
/**
|
62
64
|
* 创建 OIDC Provider 实例
|
63
65
|
* @param db - 数据库实例
|
64
|
-
* @param baseUrl - 服务部署的基础URL
|
65
66
|
* @returns 配置好的 OIDC Provider 实例
|
66
67
|
*/
|
67
|
-
export const createOIDCProvider = async (
|
68
|
-
db: LobeChatDatabase,
|
69
|
-
baseUrl: string,
|
70
|
-
): Promise<Provider> => {
|
71
|
-
const issuerUrl = `${baseUrl}/oidc`;
|
72
|
-
if (!issuerUrl) {
|
73
|
-
throw new Error('Base URL is required for OIDC Provider');
|
74
|
-
}
|
75
|
-
|
68
|
+
export const createOIDCProvider = async (db: LobeChatDatabase): Promise<Provider> => {
|
76
69
|
// 获取 JWKS
|
77
70
|
const jwks = getJWKS();
|
78
71
|
|
@@ -85,13 +78,41 @@ export const createOIDCProvider = async (
|
|
85
78
|
// 4. Claims 定义
|
86
79
|
claims: defaultClaims,
|
87
80
|
|
81
|
+
// 新增:客户端 CORS 控制逻辑
|
82
|
+
clientBasedCORS(ctx, origin, client) {
|
83
|
+
// 检查客户端是否允许此来源
|
84
|
+
// 一个常见的策略是允许所有已注册的 redirect_uris 的来源
|
85
|
+
if (!client || !client.redirectUris) {
|
86
|
+
logProvider('clientBasedCORS: No client or redirectUris found, denying origin: %s', origin);
|
87
|
+
return false; // 如果没有客户端或重定向URI,则拒绝
|
88
|
+
}
|
89
|
+
|
90
|
+
const allowed = client.redirectUris.some((uri) => {
|
91
|
+
try {
|
92
|
+
// 比较来源 (scheme, hostname, port)
|
93
|
+
return new URL(uri).origin === origin;
|
94
|
+
} catch {
|
95
|
+
// 如果 redirect_uri 不是有效的 URL (例如自定义协议),则跳过
|
96
|
+
return false;
|
97
|
+
}
|
98
|
+
});
|
99
|
+
|
100
|
+
logProvider(
|
101
|
+
'clientBasedCORS check for origin [%s] and client [%s]: %s',
|
102
|
+
origin,
|
103
|
+
client.clientId,
|
104
|
+
allowed ? 'Allowed' : 'Denied',
|
105
|
+
);
|
106
|
+
return allowed;
|
107
|
+
},
|
108
|
+
|
88
109
|
// 1. 客户端配置
|
89
110
|
clients: defaultClients,
|
90
111
|
|
91
112
|
// 7. Cookie 配置
|
92
113
|
cookies: {
|
93
114
|
keys: cookieKeys,
|
94
|
-
long: { signed: true },
|
115
|
+
long: { path: '/', signed: true },
|
95
116
|
short: { path: '/', signed: true },
|
96
117
|
},
|
97
118
|
|
@@ -107,7 +128,6 @@ export const createOIDCProvider = async (
|
|
107
128
|
rpInitiatedLogout: { enabled: true },
|
108
129
|
userinfo: { enabled: true },
|
109
130
|
},
|
110
|
-
|
111
131
|
// 10. 账户查找
|
112
132
|
async findAccount(ctx: KoaContextWithOIDC, id: string) {
|
113
133
|
logProvider('findAccount called for id: %s', id);
|
@@ -183,6 +203,7 @@ export const createOIDCProvider = async (
|
|
183
203
|
return undefined;
|
184
204
|
}
|
185
205
|
},
|
206
|
+
|
186
207
|
// 9. 交互策略
|
187
208
|
interactions: {
|
188
209
|
policy: createInteractionPolicy(),
|
@@ -225,6 +246,11 @@ export const createOIDCProvider = async (
|
|
225
246
|
// 新增:启用 Refresh Token 轮换
|
226
247
|
rotateRefreshToken: true,
|
227
248
|
|
249
|
+
routes: {
|
250
|
+
authorization: '/oidc/auth',
|
251
|
+
end_session: '/oidc/session/end',
|
252
|
+
token: '/oidc/token',
|
253
|
+
},
|
228
254
|
// 3. Scopes 定义
|
229
255
|
scopes: defaultScopes,
|
230
256
|
|
@@ -243,7 +269,9 @@ export const createOIDCProvider = async (
|
|
243
269
|
};
|
244
270
|
|
245
271
|
// 创建提供者实例
|
246
|
-
const
|
272
|
+
const baseUrl = urlJoin(appEnv.APP_URL!, '/oidc');
|
273
|
+
|
274
|
+
const provider = new Provider(baseUrl, configuration);
|
247
275
|
|
248
276
|
provider.on('server_error', (ctx, err) => {
|
249
277
|
logProvider('OIDC Provider Server Error: %O', err); // Use logProvider
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { initTRPC } from '@trpc/server';
|
2
2
|
import superjson from 'superjson';
|
3
3
|
|
4
|
-
import { AsyncContext } from '
|
4
|
+
import { AsyncContext } from './context';
|
5
5
|
|
6
6
|
export const asyncTrpc = initTRPC.context<AsyncContext>().create({
|
7
7
|
errorFormatter({ shape }) {
|
@@ -28,13 +28,13 @@ export const createContextInner = async (params?: {
|
|
28
28
|
userId: params?.userId,
|
29
29
|
});
|
30
30
|
|
31
|
-
export type
|
31
|
+
export type EdgeContext = Awaited<ReturnType<typeof createContextInner>>;
|
32
32
|
|
33
33
|
/**
|
34
34
|
* Creates context for an incoming request
|
35
35
|
* @link https://trpc.io/docs/v11/context
|
36
36
|
*/
|
37
|
-
export const
|
37
|
+
export const createEdgeContext = async (request: NextRequest): Promise<EdgeContext> => {
|
38
38
|
// for API-response caching see https://trpc.io/docs/v11/caching
|
39
39
|
|
40
40
|
const authorization = request.headers.get(LOBE_CHAT_AUTH_HEADER);
|
@@ -10,40 +10,40 @@
|
|
10
10
|
import { DESKTOP_USER_ID } from '@/const/desktop';
|
11
11
|
import { isDesktop } from '@/const/version';
|
12
12
|
|
13
|
-
import {
|
13
|
+
import { userAuth } from '../middleware/userAuth';
|
14
|
+
import { edgeTrpc } from './init';
|
14
15
|
import { jwtPayloadChecker } from './middleware/jwtPayload';
|
15
|
-
import { userAuth } from './middleware/userAuth';
|
16
16
|
|
17
17
|
/**
|
18
18
|
* Create a router
|
19
19
|
* @link https://trpc.io/docs/v11/router
|
20
20
|
*/
|
21
|
-
export const router =
|
21
|
+
export const router = edgeTrpc.router;
|
22
22
|
|
23
23
|
/**
|
24
24
|
* Create an unprotected procedure
|
25
25
|
* @link https://trpc.io/docs/v11/procedures
|
26
26
|
**/
|
27
|
-
export const publicProcedure =
|
27
|
+
export const publicProcedure = edgeTrpc.procedure.use(({ next, ctx }) => {
|
28
28
|
return next({
|
29
29
|
ctx: { userId: isDesktop ? DESKTOP_USER_ID : ctx.userId },
|
30
30
|
});
|
31
31
|
});
|
32
32
|
|
33
33
|
// procedure that asserts that the user is logged in
|
34
|
-
export const authedProcedure =
|
34
|
+
export const authedProcedure = edgeTrpc.procedure.use(userAuth);
|
35
35
|
|
36
36
|
// procedure that asserts that the user add the password
|
37
|
-
export const passwordProcedure =
|
37
|
+
export const passwordProcedure = edgeTrpc.procedure.use(jwtPayloadChecker);
|
38
38
|
|
39
39
|
/**
|
40
40
|
* Merge multiple routers together
|
41
41
|
* @link https://trpc.io/docs/v11/merging-routers
|
42
42
|
*/
|
43
|
-
export const mergeRouters =
|
43
|
+
export const mergeRouters = edgeTrpc.mergeRouters;
|
44
44
|
|
45
45
|
/**
|
46
46
|
* Create a server-side caller
|
47
47
|
* @link https://trpc.io/docs/v11/server/server-side-calls
|
48
48
|
*/
|
49
|
-
export const createCallerFactory =
|
49
|
+
export const createCallerFactory = edgeTrpc.createCallerFactory;
|
@@ -10,9 +10,9 @@
|
|
10
10
|
import { initTRPC } from '@trpc/server';
|
11
11
|
import superjson from 'superjson';
|
12
12
|
|
13
|
-
import type {
|
13
|
+
import type { EdgeContext } from './context';
|
14
14
|
|
15
|
-
export const
|
15
|
+
export const edgeTrpc = initTRPC.context<EdgeContext>().create({
|
16
16
|
/**
|
17
17
|
* @link https://trpc.io/docs/v11/error-formatting
|
18
18
|
*/
|
@@ -2,9 +2,9 @@
|
|
2
2
|
import { TRPCError } from '@trpc/server';
|
3
3
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
4
4
|
|
5
|
-
import { createCallerFactory } from '@/libs/trpc';
|
6
|
-
import {
|
7
|
-
import {
|
5
|
+
import { createCallerFactory } from '@/libs/trpc/edge';
|
6
|
+
import { AuthContext, createContextInner } from '@/libs/trpc/edge/context';
|
7
|
+
import { edgeTrpc as trpc } from '@/libs/trpc/edge/init';
|
8
8
|
import * as utils from '@/utils/server/jwt';
|
9
9
|
|
10
10
|
import { jwtPayloadChecker } from './jwtPayload';
|
@@ -1,9 +1,10 @@
|
|
1
1
|
import { TRPCError } from '@trpc/server';
|
2
2
|
|
3
|
-
import { trpc } from '@/libs/trpc/init';
|
4
3
|
import { getJWTPayload } from '@/utils/server/jwt';
|
5
4
|
|
6
|
-
|
5
|
+
import { edgeTrpc } from '../init';
|
6
|
+
|
7
|
+
export const jwtPayloadChecker = edgeTrpc.middleware(async (opts) => {
|
7
8
|
const { ctx } = opts;
|
8
9
|
|
9
10
|
if (!ctx.authorizationHeader) throw new TRPCError({ code: 'UNAUTHORIZED' });
|
@@ -0,0 +1,70 @@
|
|
1
|
+
import { User } from 'next-auth';
|
2
|
+
import { NextRequest } from 'next/server';
|
3
|
+
|
4
|
+
import { JWTPayload, LOBE_CHAT_AUTH_HEADER, enableClerk, enableNextAuth } from '@/const/auth';
|
5
|
+
import { ClerkAuth, IClerkAuth } from '@/libs/clerk-auth';
|
6
|
+
|
7
|
+
export interface AuthContext {
|
8
|
+
authorizationHeader?: string | null;
|
9
|
+
clerkAuth?: IClerkAuth;
|
10
|
+
jwtPayload?: JWTPayload | null;
|
11
|
+
nextAuth?: User;
|
12
|
+
userId?: string | null;
|
13
|
+
}
|
14
|
+
|
15
|
+
/**
|
16
|
+
* Inner function for `createContext` where we create the context.
|
17
|
+
* This is useful for testing when we don't want to mock Next.js' request/response
|
18
|
+
*/
|
19
|
+
export const createContextInner = async (params?: {
|
20
|
+
authorizationHeader?: string | null;
|
21
|
+
clerkAuth?: IClerkAuth;
|
22
|
+
nextAuth?: User;
|
23
|
+
userId?: string | null;
|
24
|
+
}): Promise<AuthContext> => ({
|
25
|
+
authorizationHeader: params?.authorizationHeader,
|
26
|
+
clerkAuth: params?.clerkAuth,
|
27
|
+
nextAuth: params?.nextAuth,
|
28
|
+
userId: params?.userId,
|
29
|
+
});
|
30
|
+
|
31
|
+
export type LambdaContext = Awaited<ReturnType<typeof createContextInner>>;
|
32
|
+
|
33
|
+
/**
|
34
|
+
* Creates context for an incoming request
|
35
|
+
* @link https://trpc.io/docs/v11/context
|
36
|
+
*/
|
37
|
+
export const createLambdaContext = async (request: NextRequest): Promise<LambdaContext> => {
|
38
|
+
// for API-response caching see https://trpc.io/docs/v11/caching
|
39
|
+
|
40
|
+
const authorization = request.headers.get(LOBE_CHAT_AUTH_HEADER);
|
41
|
+
|
42
|
+
let userId;
|
43
|
+
let auth;
|
44
|
+
|
45
|
+
if (enableClerk) {
|
46
|
+
const clerkAuth = new ClerkAuth();
|
47
|
+
const result = clerkAuth.getAuthFromRequest(request);
|
48
|
+
auth = result.clerkAuth;
|
49
|
+
userId = result.userId;
|
50
|
+
|
51
|
+
return createContextInner({ authorizationHeader: authorization, clerkAuth: auth, userId });
|
52
|
+
}
|
53
|
+
|
54
|
+
if (enableNextAuth) {
|
55
|
+
try {
|
56
|
+
const { default: NextAuthEdge } = await import('@/libs/next-auth/edge');
|
57
|
+
|
58
|
+
const session = await NextAuthEdge.auth();
|
59
|
+
if (session && session?.user?.id) {
|
60
|
+
auth = session.user;
|
61
|
+
userId = session.user.id;
|
62
|
+
}
|
63
|
+
return createContextInner({ authorizationHeader: authorization, nextAuth: auth, userId });
|
64
|
+
} catch (e) {
|
65
|
+
console.error('next auth err', e);
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
return createContextInner({ authorizationHeader: authorization, userId });
|
70
|
+
};
|
@@ -1 +1,39 @@
|
|
1
|
-
|
1
|
+
/**
|
2
|
+
* This is your entry point to setup the root configuration for tRPC on the server.
|
3
|
+
* - `initTRPC` should only be used once per app.
|
4
|
+
* - We export only the functionality that we use so we can enforce which base procedures should be used
|
5
|
+
*
|
6
|
+
* Learn how to create protected base procedures and other things below:
|
7
|
+
* @link https://trpc.io/docs/v11/router
|
8
|
+
* @link https://trpc.io/docs/v11/procedures
|
9
|
+
*/
|
10
|
+
import { DESKTOP_USER_ID } from '@/const/desktop';
|
11
|
+
import { isDesktop } from '@/const/version';
|
12
|
+
|
13
|
+
import { userAuth } from '../middleware/userAuth';
|
14
|
+
import { trpc } from './init';
|
15
|
+
|
16
|
+
/**
|
17
|
+
* Create a router
|
18
|
+
* @link https://trpc.io/docs/v11/router
|
19
|
+
*/
|
20
|
+
export const router = trpc.router;
|
21
|
+
|
22
|
+
/**
|
23
|
+
* Create an unprotected procedure
|
24
|
+
* @link https://trpc.io/docs/v11/procedures
|
25
|
+
**/
|
26
|
+
export const publicProcedure = trpc.procedure.use(({ next, ctx }) => {
|
27
|
+
return next({
|
28
|
+
ctx: { userId: isDesktop ? DESKTOP_USER_ID : ctx.userId },
|
29
|
+
});
|
30
|
+
});
|
31
|
+
|
32
|
+
// procedure that asserts that the user is logged in
|
33
|
+
export const authedProcedure = trpc.procedure.use(userAuth);
|
34
|
+
|
35
|
+
/**
|
36
|
+
* Create a server-side caller
|
37
|
+
* @link https://trpc.io/docs/v11/server/server-side-calls
|
38
|
+
*/
|
39
|
+
export const createCallerFactory = trpc.createCallerFactory;
|
@@ -0,0 +1,26 @@
|
|
1
|
+
/**
|
2
|
+
* This is your entry point to setup the root configuration for tRPC on the server.
|
3
|
+
* - `initTRPC` should only be used once per app.
|
4
|
+
* - We export only the functionality that we use so we can enforce which base procedures should be used
|
5
|
+
*
|
6
|
+
* Learn how to create protected base procedures and other things below:
|
7
|
+
* @link https://trpc.io/docs/v11/router
|
8
|
+
* @link https://trpc.io/docs/v11/procedures
|
9
|
+
*/
|
10
|
+
import { initTRPC } from '@trpc/server';
|
11
|
+
import superjson from 'superjson';
|
12
|
+
|
13
|
+
import type { LambdaContext } from './context';
|
14
|
+
|
15
|
+
export const trpc = initTRPC.context<LambdaContext>().create({
|
16
|
+
/**
|
17
|
+
* @link https://trpc.io/docs/v11/error-formatting
|
18
|
+
*/
|
19
|
+
errorFormatter({ shape }) {
|
20
|
+
return shape;
|
21
|
+
},
|
22
|
+
/**
|
23
|
+
* @link https://trpc.io/docs/v11/data-transformers
|
24
|
+
*/
|
25
|
+
transformer: superjson,
|
26
|
+
});
|
@@ -1,8 +1,9 @@
|
|
1
1
|
import { TRPCError } from '@trpc/server';
|
2
2
|
|
3
|
-
import { trpc } from '@/libs/trpc/init';
|
4
3
|
import { getJWTPayload } from '@/utils/server/jwt';
|
5
4
|
|
5
|
+
import { trpc } from '../init';
|
6
|
+
|
6
7
|
export const keyVaults = trpc.middleware(async (opts) => {
|
7
8
|
const { ctx } = opts;
|
8
9
|
|
@@ -1,10 +1,10 @@
|
|
1
1
|
import { TRPCError } from '@trpc/server';
|
2
2
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
3
3
|
|
4
|
-
import { createCallerFactory } from '@/libs/trpc';
|
5
|
-
import { AuthContext, createContextInner } from '@/
|
4
|
+
import { createCallerFactory } from '@/libs/trpc/lambda';
|
5
|
+
import { AuthContext, createContextInner } from '@/libs/trpc/lambda/context';
|
6
6
|
|
7
|
-
import { trpc } from '../init';
|
7
|
+
import { trpc } from '../lambda/init';
|
8
8
|
import { userAuth } from './userAuth';
|
9
9
|
|
10
10
|
const appRouter = trpc.router({
|
@@ -4,7 +4,7 @@ import { enableClerk } from '@/const/auth';
|
|
4
4
|
import { DESKTOP_USER_ID } from '@/const/desktop';
|
5
5
|
import { isDesktop } from '@/const/version';
|
6
6
|
|
7
|
-
import { trpc } from '../init';
|
7
|
+
import { trpc } from '../lambda/init';
|
8
8
|
|
9
9
|
export const userAuth = trpc.middleware(async (opts) => {
|
10
10
|
const { ctx } = opts;
|
@@ -4,7 +4,8 @@ const oauth = {
|
|
4
4
|
accept: '授权',
|
5
5
|
deny: '拒绝',
|
6
6
|
},
|
7
|
-
description: '应用 {
|
7
|
+
description: '应用 {{clientName}} 申请您的账户授权',
|
8
|
+
|
8
9
|
error: {
|
9
10
|
sessionInvalid: {
|
10
11
|
message: '授权会话已过期或无效,请重新发起授权流程。',
|
@@ -16,16 +17,17 @@ const oauth = {
|
|
16
17
|
title: '不支持的交互类型',
|
17
18
|
},
|
18
19
|
},
|
19
|
-
permissionsTitle: '
|
20
|
+
permissionsTitle: '请求以下权限:',
|
21
|
+
redirectUri: '授权成功后将重定向到',
|
20
22
|
scope: {
|
21
23
|
'email': '访问您的电子邮件地址',
|
22
|
-
'offline_access': '
|
24
|
+
'offline_access': '允许客户端访问您的数据',
|
23
25
|
'openid': '使用您的 LobeChat 账户进行身份验证',
|
24
26
|
'profile': '访问您的基本资料信息(名称、头像等)',
|
25
|
-
'sync
|
26
|
-
'sync
|
27
|
+
'sync-read': '读取您的同步数据',
|
28
|
+
'sync-write': '写入并更新您的同步数据',
|
27
29
|
},
|
28
|
-
title: '
|
30
|
+
title: '授权 {{clientName}}',
|
29
31
|
},
|
30
32
|
failed: {
|
31
33
|
backToHome: '返回首页',
|
@@ -4,9 +4,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
4
|
/**
|
5
5
|
* This file contains the root router of your tRPC-backend
|
6
6
|
*/
|
7
|
-
import { createCallerFactory } from '@/libs/trpc';
|
8
|
-
import { AuthContext, createContextInner } from '@/
|
9
|
-
import { GlobalServerConfig } from '@/types/serverConfig';
|
7
|
+
import { createCallerFactory } from '@/libs/trpc/edge';
|
8
|
+
import { AuthContext, createContextInner } from '@/libs/trpc/edge/context';
|
10
9
|
|
11
10
|
import { configRouter } from './index';
|
12
11
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { getServerFeatureFlagsValue } from '@/config/featureFlags';
|
2
|
-
import { publicProcedure, router } from '@/libs/trpc';
|
2
|
+
import { publicProcedure, router } from '@/libs/trpc/edge';
|
3
3
|
import { getServerDefaultAgentConfig, getServerGlobalConfig } from '@/server/globalConfig';
|
4
4
|
import { GlobalRuntimeConfig } from '@/types/serverConfig';
|
5
5
|
|