@lobehub/chat 1.79.7 → 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.
Files changed (121) hide show
  1. package/.eslintrc.js +1 -0
  2. package/CHANGELOG.md +58 -0
  3. package/changelog/v1.json +18 -0
  4. package/docs/development/database-schema.dbml +119 -0
  5. package/locales/ar/models.json +12 -0
  6. package/locales/ar/oauth.json +40 -0
  7. package/locales/bg-BG/models.json +12 -0
  8. package/locales/bg-BG/oauth.json +40 -0
  9. package/locales/de-DE/models.json +12 -0
  10. package/locales/de-DE/oauth.json +40 -0
  11. package/locales/en-US/models.json +12 -0
  12. package/locales/en-US/oauth.json +40 -0
  13. package/locales/es-ES/models.json +12 -0
  14. package/locales/es-ES/oauth.json +40 -0
  15. package/locales/fa-IR/models.json +12 -0
  16. package/locales/fa-IR/oauth.json +40 -0
  17. package/locales/fr-FR/models.json +12 -0
  18. package/locales/fr-FR/oauth.json +40 -0
  19. package/locales/it-IT/models.json +12 -0
  20. package/locales/it-IT/oauth.json +40 -0
  21. package/locales/ja-JP/models.json +12 -0
  22. package/locales/ja-JP/oauth.json +40 -0
  23. package/locales/ko-KR/models.json +12 -0
  24. package/locales/ko-KR/oauth.json +40 -0
  25. package/locales/nl-NL/models.json +12 -0
  26. package/locales/nl-NL/oauth.json +40 -0
  27. package/locales/pl-PL/models.json +12 -0
  28. package/locales/pl-PL/oauth.json +40 -0
  29. package/locales/pt-BR/models.json +12 -0
  30. package/locales/pt-BR/oauth.json +40 -0
  31. package/locales/ru-RU/models.json +12 -0
  32. package/locales/ru-RU/oauth.json +40 -0
  33. package/locales/tr-TR/models.json +12 -0
  34. package/locales/tr-TR/oauth.json +40 -0
  35. package/locales/vi-VN/models.json +12 -0
  36. package/locales/vi-VN/oauth.json +40 -0
  37. package/locales/zh-CN/models.json +12 -0
  38. package/locales/zh-CN/oauth.json +40 -0
  39. package/locales/zh-TW/models.json +12 -0
  40. package/locales/zh-TW/oauth.json +40 -0
  41. package/package.json +4 -1
  42. package/scripts/generate-oidc-jwk.mjs +59 -0
  43. package/scripts/migrateServerDB/index.ts +3 -1
  44. package/src/app/(backend)/oidc/[...oidc]/route.ts +96 -0
  45. package/src/app/(backend)/oidc/consent/route.ts +131 -0
  46. package/src/app/(backend)/trpc/async/[trpc]/route.ts +1 -1
  47. package/src/app/(backend)/trpc/edge/[trpc]/route.ts +2 -2
  48. package/src/app/(backend)/trpc/lambda/[trpc]/route.ts +2 -2
  49. package/src/app/(backend)/trpc/tools/[trpc]/route.ts +2 -2
  50. package/src/app/[variants]/(main)/files/[id]/page.tsx +1 -1
  51. package/src/app/[variants]/oauth/consent/[uid]/Client.tsx +224 -0
  52. package/src/app/[variants]/oauth/consent/[uid]/ClientError.tsx +46 -0
  53. package/src/app/[variants]/oauth/consent/[uid]/failed/page.tsx +36 -0
  54. package/src/app/[variants]/oauth/consent/[uid]/page.tsx +69 -0
  55. package/src/app/[variants]/oauth/consent/[uid]/success/page.tsx +30 -0
  56. package/src/components/Branding/ProductLogo/index.tsx +6 -1
  57. package/src/config/aiModels/openai.ts +63 -41
  58. package/src/database/client/migrations.json +27 -8
  59. package/src/database/migrations/0020_add_oidc.sql +124 -0
  60. package/src/database/migrations/meta/0020_snapshot.json +4975 -0
  61. package/src/database/migrations/meta/_journal.json +7 -0
  62. package/src/database/repositories/tableViewer/index.test.ts +1 -1
  63. package/src/database/schemas/index.ts +1 -0
  64. package/src/database/schemas/oidc.ts +158 -0
  65. package/src/database/server/models/__tests__/adapter.test.ts +499 -0
  66. package/src/envs/oidc.ts +18 -0
  67. package/src/libs/agent-runtime/azureOpenai/index.ts +4 -1
  68. package/src/libs/agent-runtime/utils/streams/protocol.ts +2 -4
  69. package/src/libs/oidc-provider/adapter.ts +541 -0
  70. package/src/libs/oidc-provider/config.ts +52 -0
  71. package/src/libs/oidc-provider/http-adapter.ts +311 -0
  72. package/src/libs/oidc-provider/interaction-policy.ts +37 -0
  73. package/src/libs/oidc-provider/provider.ts +288 -0
  74. package/src/libs/trpc/async/init.ts +1 -1
  75. package/src/{server → libs/trpc/edge}/context.ts +2 -2
  76. package/src/libs/trpc/{index.ts → edge/index.ts} +8 -8
  77. package/src/libs/trpc/{init.ts → edge/init.ts} +2 -2
  78. package/src/libs/trpc/{middleware → edge/middleware}/jwtPayload.test.ts +3 -3
  79. package/src/libs/trpc/{middleware → edge/middleware}/jwtPayload.ts +3 -2
  80. package/src/libs/trpc/lambda/context.ts +70 -0
  81. package/src/libs/trpc/lambda/index.ts +39 -1
  82. package/src/libs/trpc/lambda/init.ts +26 -0
  83. package/src/libs/trpc/lambda/middleware/index.ts +2 -0
  84. package/src/libs/trpc/{middleware → lambda/middleware}/keyVaults.ts +2 -1
  85. package/src/libs/trpc/lambda/{serverDatabase.ts → middleware/serverDatabase.ts} +2 -1
  86. package/src/libs/trpc/middleware/userAuth.test.ts +3 -3
  87. package/src/libs/trpc/middleware/userAuth.ts +1 -1
  88. package/src/libs/trpc/mock.ts +7 -0
  89. package/src/locales/default/index.ts +2 -0
  90. package/src/locales/default/oauth.ts +43 -0
  91. package/src/middleware.ts +94 -6
  92. package/src/server/routers/edge/appStatus.ts +1 -1
  93. package/src/server/routers/edge/config/index.test.ts +2 -3
  94. package/src/server/routers/edge/config/index.ts +1 -1
  95. package/src/server/routers/edge/index.ts +1 -1
  96. package/src/server/routers/edge/upload.ts +1 -1
  97. package/src/server/routers/lambda/_template.ts +2 -2
  98. package/src/server/routers/lambda/agent.ts +2 -2
  99. package/src/server/routers/lambda/aiModel.ts +2 -2
  100. package/src/server/routers/lambda/aiProvider.ts +2 -2
  101. package/src/server/routers/lambda/chunk.ts +2 -3
  102. package/src/server/routers/lambda/exporter.ts +2 -2
  103. package/src/server/routers/lambda/file.ts +2 -2
  104. package/src/server/routers/lambda/importer.ts +2 -2
  105. package/src/server/routers/lambda/index.ts +1 -1
  106. package/src/server/routers/lambda/knowledgeBase.ts +2 -2
  107. package/src/server/routers/lambda/message.ts +2 -2
  108. package/src/server/routers/lambda/plugin.ts +2 -2
  109. package/src/server/routers/lambda/ragEval.ts +2 -3
  110. package/src/server/routers/lambda/session.ts +2 -2
  111. package/src/server/routers/lambda/sessionGroup.ts +2 -2
  112. package/src/server/routers/lambda/thread.ts +2 -2
  113. package/src/server/routers/lambda/topic.ts +2 -2
  114. package/src/server/routers/lambda/user.ts +2 -2
  115. package/src/server/routers/tools/__tests__/search.test.ts +2 -2
  116. package/src/server/routers/tools/index.ts +1 -1
  117. package/src/server/routers/tools/search.ts +2 -1
  118. package/src/server/services/oidc/index.ts +64 -0
  119. package/src/server/services/oidc/oidcProvider.ts +25 -0
  120. package/src/server/mock.ts +0 -8
  121. /package/src/{server/asyncContext.ts → libs/trpc/async/context.ts} +0 -0
@@ -0,0 +1,40 @@
1
+ {
2
+ "consent": {
3
+ "buttons": {
4
+ "accept": "授權",
5
+ "deny": "拒絕"
6
+ },
7
+ "description": "應用 {{clientName}} 申請您的帳戶授權",
8
+ "error": {
9
+ "sessionInvalid": {
10
+ "message": "授權會話已過期或無效,請重新發起授權流程。",
11
+ "title": "授權會話無效"
12
+ },
13
+ "title": "發生錯誤",
14
+ "unsupportedInteraction": {
15
+ "message": "不支援的互動類型: {promptName}",
16
+ "title": "不支援的互動類型"
17
+ }
18
+ },
19
+ "permissionsTitle": "請求以下權限:",
20
+ "redirectUri": "授權成功後將重定向到",
21
+ "scope": {
22
+ "email": "訪問您的電子郵件地址",
23
+ "offline_access": "允許客戶端訪問您的資料",
24
+ "openid": "使用您的 LobeChat 帳戶進行身份驗證",
25
+ "profile": "訪問您的基本資料信息(名稱、頭像等)",
26
+ "sync-read": "讀取您的同步資料",
27
+ "sync-write": "寫入並更新您的同步資料"
28
+ },
29
+ "title": "授權 {{clientName}}"
30
+ },
31
+ "failed": {
32
+ "backToHome": "返回首頁",
33
+ "subTitle": "您已拒絕授權應用訪問您的 LobeChat 帳戶",
34
+ "title": "授權被拒絕"
35
+ },
36
+ "success": {
37
+ "subTitle": "您已成功授權應用訪問您的 LobeChat 帳戶,可以關閉該頁面了",
38
+ "title": "授權成功"
39
+ }
40
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.79.7",
3
+ "version": "1.79.9",
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",
@@ -188,6 +188,7 @@
188
188
  "jose": "^5.10.0",
189
189
  "js-sha256": "^0.11.0",
190
190
  "jsonl-parse-stringify": "^1.0.3",
191
+ "keyv": "^4.5.4",
191
192
  "langchain": "^0.3.19",
192
193
  "langfuse": "^3.37.1",
193
194
  "langfuse-core": "^3.37.1",
@@ -204,6 +205,7 @@
204
205
  "numeral": "^2.0.6",
205
206
  "nuqs": "^2.4.1",
206
207
  "officeparser": "^5.1.1",
208
+ "oidc-provider": "^8.4.0",
207
209
  "ollama": "^0.5.14",
208
210
  "openai": "^4.91.1",
209
211
  "openapi-fetch": "^0.9.8",
@@ -286,6 +288,7 @@
286
288
  "@types/lodash-es": "^4.17.12",
287
289
  "@types/node": "^22.13.17",
288
290
  "@types/numeral": "^2.0.5",
291
+ "@types/oidc-provider": "^8.8.1",
289
292
  "@types/pg": "^8.11.11",
290
293
  "@types/react": "^19.1.0",
291
294
  "@types/react-dom": "^19.1.1",
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * OIDC JWKS 密钥生成脚本
4
+ * 用于生成 OIDC Provider 使用的 RSA 密钥对并转换为 JWKS 格式
5
+ *
6
+ * 使用方法:
7
+ * node scripts/generate-oidc-jwk.mjs
8
+ *
9
+ * 将输出的单行 JSON 字符串设置为环境变量 OIDC_JWKS_KEY
10
+ */
11
+ import crypto from 'node:crypto';
12
+ import { exportJWK, generateKeyPair } from 'jose';
13
+
14
+ // 生成密钥 ID
15
+ function generateKeyId() {
16
+ return crypto.randomBytes(8).toString('hex');
17
+ }
18
+
19
+ async function generateJwks() {
20
+ try {
21
+ console.error('正在生成 RSA 密钥对...');
22
+
23
+ // 生成 RS256 密钥对
24
+ const { privateKey } = await generateKeyPair('RS256');
25
+
26
+ // 导出为 JWK 格式
27
+ const jwk = await exportJWK(privateKey);
28
+
29
+ // 添加必要的字段
30
+ jwk.use = 'sig'; // 用途: 签名
31
+ jwk.kid = generateKeyId(); // 密钥 ID
32
+ jwk.alg = 'RS256'; // 算法
33
+
34
+ // 创建 JWKS (JSON Web Key Set)
35
+ const jwks = { keys: [jwk] };
36
+
37
+ // 转换为JSON字符串
38
+ const jwksString = JSON.stringify(jwks);
39
+
40
+ // 输出 JWKS JSON 单行字符串
41
+ console.log(jwksString);
42
+
43
+ // 控制台提示
44
+ console.error('\n✅ JWKS 已生成');
45
+ console.error('请将上面输出的 JSON 字符串直接设置为环境变量 OIDC_JWKS_KEY');
46
+ console.error('例如在 .env 文件中添加:');
47
+ console.error('\n> 环境变量配置行 (可直接复制):');
48
+ console.error(`OIDC_JWKS_KEY='${jwksString}'`);
49
+ console.error('\n⚠️ 重要: 请妥善保管此密钥,它用于签署所有 OIDC 令牌');
50
+
51
+ return jwks;
52
+ } catch (error) {
53
+ console.error('❌ 生成 JWKS 时出错:', error);
54
+ process.exit(1);
55
+ }
56
+ }
57
+
58
+ // 执行主函数
59
+ generateJwks();
@@ -3,7 +3,6 @@ import { migrate as neonMigrate } from 'drizzle-orm/neon-serverless/migrator';
3
3
  import { migrate as nodeMigrate } from 'drizzle-orm/node-postgres/migrator';
4
4
  import { join } from 'node:path';
5
5
 
6
- import { serverDB } from '../../src/database/server';
7
6
  import { DB_FAIL_INIT_HINT, PGVECTOR_HINT } from './errorHint';
8
7
 
9
8
  // Read the `.env` file if it exists, or a file specified by the
@@ -13,7 +12,10 @@ dotenv.config();
13
12
  const migrationsFolder = join(__dirname, '../../src/database/migrations');
14
13
 
15
14
  const isDesktop = process.env.NEXT_PUBLIC_IS_DESKTOP_APP === '1';
15
+
16
16
  const runMigrations = async () => {
17
+ const { serverDB } = await import('../../src/database/server');
18
+
17
19
  if (process.env.DATABASE_DRIVER === 'node') {
18
20
  await nodeMigrate(serverDB, { migrationsFolder });
19
21
  } else {
@@ -0,0 +1,96 @@
1
+ import debug from 'debug';
2
+ import { NextRequest, NextResponse } from 'next/server';
3
+ import { URL } from 'node:url';
4
+
5
+ import { oidcEnv } from '@/envs/oidc';
6
+ import { createNodeRequest, createNodeResponse } from '@/libs/oidc-provider/http-adapter';
7
+ import { getOIDCProvider } from '@/server/services/oidc/oidcProvider';
8
+
9
+ const log = debug('lobe-oidc:route'); // Create a debug instance with a namespace
10
+
11
+ const handler = async (req: NextRequest) => {
12
+ const requestUrl = new URL(req.url);
13
+ log(`Received ${req.method.toUpperCase()} request: %s %s`, req.method, req.url);
14
+ log('Path: %s, Pathname: %s', requestUrl.pathname, requestUrl.pathname);
15
+
16
+ // 声明响应收集器
17
+ let responseCollector;
18
+
19
+ try {
20
+ if (!oidcEnv.ENABLE_OIDC) {
21
+ log('OIDC is not enabled');
22
+ return new NextResponse('OIDC is not enabled', { status: 404 });
23
+ }
24
+
25
+ // 获取 OIDC Provider 实例
26
+ const provider = await getOIDCProvider();
27
+
28
+ log(`Calling provider.callback() for ${req.method}`); // Log the method
29
+ await new Promise<void>((resolve, reject) => {
30
+ // <-- Make promise callback async
31
+ let middleware: any;
32
+ try {
33
+ log('Attempting to get middleware from provider.callback()');
34
+ middleware = provider.callback();
35
+ log('Successfully obtained middleware function.');
36
+ } catch (syncError) {
37
+ log('SYNC ERROR during provider.callback() call itself: %O', syncError);
38
+ reject(syncError);
39
+ return;
40
+ }
41
+
42
+ // 使用辅助方法创建响应收集器
43
+ responseCollector = createNodeResponse(resolve);
44
+ const nodeResponse = responseCollector.nodeResponse;
45
+
46
+ // 使用辅助方法创建 Node.js 请求对象,现在需要 await
47
+ createNodeRequest(req).then((nodeRequest) => {
48
+ log('Calling the obtained middleware...');
49
+ middleware(nodeRequest, nodeResponse, (error?: Error) => {
50
+ log('Middleware callback function HAS BEEN EXECUTED.');
51
+ if (error) {
52
+ log('Middleware error reported via callback: %O', error);
53
+ reject(error);
54
+ } else {
55
+ log(
56
+ 'Middleware completed successfully via callback (may be redundant if .end() was called).',
57
+ );
58
+ resolve();
59
+ }
60
+ });
61
+ log('Middleware call initiated, waiting for its callback OR nodeResponse.end()...');
62
+ });
63
+ });
64
+
65
+ log('Promise surrounding middleware call resolved.');
66
+
67
+ // 访问最终的响应状态
68
+ if (!responseCollector) {
69
+ throw new Error('ResponseCollector was not initialized.');
70
+ }
71
+
72
+ const {
73
+ responseStatus: finalStatus,
74
+ responseBody: finalBody,
75
+ responseHeaders: finalHeaders,
76
+ } = responseCollector;
77
+
78
+ log('Final Response Status: %d', finalStatus);
79
+ log('Final Response Headers: %O', finalHeaders);
80
+
81
+ return new NextResponse(finalBody, {
82
+ // eslint-disable-next-line no-undef
83
+ headers: finalHeaders as HeadersInit,
84
+ status: finalStatus,
85
+ });
86
+ } catch (error) {
87
+ log(`Error handling OIDC ${req.method} request: %O`, error); // Log method in error
88
+ return new NextResponse(`Internal Server Error: ${(error as Error).message}`, { status: 500 });
89
+ }
90
+ };
91
+
92
+ export const GET = handler;
93
+ export const POST = handler;
94
+ export const PUT = handler;
95
+ export const DELETE = handler;
96
+ export const PATCH = handler;
@@ -0,0 +1,131 @@
1
+ import debug from 'debug';
2
+ import { NextRequest, NextResponse } from 'next/server';
3
+
4
+ import { OIDCService } from '@/server/services/oidc';
5
+ import { getUserAuth } from '@/utils/server/auth';
6
+
7
+ const log = debug('lobe-oidc:consent');
8
+
9
+ export async function POST(request: NextRequest) {
10
+ log('Received POST request for /oidc/consent, URL: %s', request.url);
11
+ try {
12
+ const formData = await request.formData();
13
+ const consent = formData.get('consent') as string;
14
+ const uid = formData.get('uid') as string;
15
+
16
+ log('POST /oauth/consent - uid=%s, choice=%s', uid, consent);
17
+
18
+ const oidcService = await OIDCService.initialize();
19
+
20
+ let details;
21
+ try {
22
+ details = await oidcService.getInteractionDetails(uid);
23
+ log(
24
+ 'Interaction details found - prompt=%s, client=%s',
25
+ details.prompt.name,
26
+ details.params.client_id,
27
+ );
28
+ } catch (error) {
29
+ log(
30
+ 'Error: Interaction details not found - %s',
31
+ error instanceof Error ? error.message : 'unknown error',
32
+ );
33
+ if (error instanceof Error && error.message.includes('interaction session not found')) {
34
+ return NextResponse.json(
35
+ {
36
+ error: 'invalid_request',
37
+ error_description:
38
+ 'Authorization session expired or invalid, please restart the authorization flow',
39
+ },
40
+ { status: 400 },
41
+ );
42
+ }
43
+ throw error;
44
+ }
45
+
46
+ const { prompt } = details;
47
+ let result;
48
+ if (consent === 'accept') {
49
+ log(`User accepted the request, Handling 'login' prompt`);
50
+ const { userId } = await getUserAuth();
51
+ log('Obtained userId: %s', userId);
52
+
53
+ if (details.prompt.name === 'login') {
54
+ result = {
55
+ login: { accountId: userId, remember: true },
56
+ };
57
+ } else {
58
+ log(`Handling 'consent' prompt`);
59
+
60
+ // 1. 获取必要的 ID
61
+ const clientId = details.params.client_id as string;
62
+
63
+ // 2. 查找或创建 Grant 对象
64
+ const grant = await oidcService.findOrCreateGrants(userId!, clientId, details.grantId);
65
+
66
+ // 3. 将用户同意的 scopes 和 claims 添加到 Grant 对象
67
+ // 这些信息通常在 details.prompt.details 中
68
+ const missingOIDCScope = (prompt.details.missingOIDCScope as string[]) || [];
69
+ if (missingOIDCScope) {
70
+ grant.addOIDCScope(missingOIDCScope.join(' '));
71
+ log('Added OIDC scopes to grant: %s', missingOIDCScope.join(' '));
72
+ }
73
+ const missingOIDCClaims = (prompt.details.missingOIDCClaims as string[]) || [];
74
+ if (missingOIDCClaims) {
75
+ grant.addOIDCClaims(missingOIDCClaims);
76
+ log('Added OIDC claims to grant: %s', missingOIDCClaims.join(' '));
77
+ }
78
+
79
+ const missingResourceScopes =
80
+ (prompt.details.missingResourceScopes as Record<string, string[]>) || {};
81
+ if (missingResourceScopes) {
82
+ for (const [indicator, scopes] of Object.entries(missingResourceScopes)) {
83
+ grant.addResourceScope(indicator, scopes.join(' '));
84
+ log('Added resource scopes for %s to grant: %s', indicator, scopes.join(' '));
85
+ }
86
+ }
87
+ // 如果使用了 RAR (Rich Authorization Requests),也需要添加到 grant
88
+ // if (prompt.details.rar) {
89
+ // prompt.details.rar.forEach(detail => grant.addRar(detail));
90
+ // }
91
+
92
+ // 4. 保存 Grant 对象以获取其 jti (grantId)
93
+ const newGrantId = await grant.save();
94
+ log('Saved grant with ID: %s', newGrantId);
95
+
96
+ // 5. 准备包含 grantId 的 result
97
+ result = { consent: { grantId: newGrantId } };
98
+
99
+ log('Consent result prepared with grantId');
100
+ }
101
+ log('User %s the authorization', consent);
102
+ } else {
103
+ log('User rejected the request');
104
+ result = {
105
+ error: 'access_denied',
106
+ error_description: 'User denied the authorization request',
107
+ };
108
+ log('User %s the authorization', consent);
109
+ }
110
+
111
+ log('Interaction Result: %O', result);
112
+
113
+ const internalRedirectUrlString = await oidcService.getInteractionResult(uid, result);
114
+ log('OIDC Provider internal redirect URL string: %s', internalRedirectUrlString);
115
+
116
+ return NextResponse.redirect(internalRedirectUrlString, {
117
+ headers: request.headers,
118
+ status: 303,
119
+ });
120
+ } catch (error) {
121
+ log('Error processing consent: %s', error instanceof Error ? error.message : 'unknown error');
122
+ console.error('Error processing consent:', error);
123
+ return NextResponse.json(
124
+ {
125
+ error: 'server_error',
126
+ error_description: 'Error processing consent',
127
+ },
128
+ { status: 500 },
129
+ );
130
+ }
131
+ }
@@ -2,7 +2,7 @@ import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
2
2
  import type { NextRequest } from 'next/server';
3
3
 
4
4
  import { pino } from '@/libs/logger';
5
- import { createAsyncRouteContext } from '@/server/asyncContext';
5
+ import { createAsyncRouteContext } from '@/libs/trpc/async/context';
6
6
  import { asyncRouter } from '@/server/routers/async';
7
7
 
8
8
  export const maxDuration = 60;
@@ -2,7 +2,7 @@ import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
2
2
  import type { NextRequest } from 'next/server';
3
3
 
4
4
  import { pino } from '@/libs/logger';
5
- import { createContext } from '@/server/context';
5
+ import { createEdgeContext } from '@/libs/trpc/edge/context';
6
6
  import { edgeRouter } from '@/server/routers/edge';
7
7
 
8
8
  export const runtime = 'edge';
@@ -12,7 +12,7 @@ const handler = (req: NextRequest) =>
12
12
  /**
13
13
  * @link https://trpc.io/docs/v11/context
14
14
  */
15
- createContext: () => createContext(req),
15
+ createContext: () => createEdgeContext(req),
16
16
 
17
17
  endpoint: '/trpc/edge',
18
18
 
@@ -2,7 +2,7 @@ import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
2
2
  import type { NextRequest } from 'next/server';
3
3
 
4
4
  import { pino } from '@/libs/logger';
5
- import { createContext } from '@/server/context';
5
+ import { createLambdaContext } from '@/libs/trpc/lambda/context';
6
6
  import { lambdaRouter } from '@/server/routers/lambda';
7
7
 
8
8
  const handler = (req: NextRequest) =>
@@ -10,7 +10,7 @@ const handler = (req: NextRequest) =>
10
10
  /**
11
11
  * @link https://trpc.io/docs/v11/context
12
12
  */
13
- createContext: () => createContext(req),
13
+ createContext: () => createLambdaContext(req),
14
14
 
15
15
  endpoint: '/trpc/lambda',
16
16
 
@@ -2,7 +2,7 @@ import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
2
2
  import type { NextRequest } from 'next/server';
3
3
 
4
4
  import { pino } from '@/libs/logger';
5
- import { createContext } from '@/server/context';
5
+ import { createLambdaContext } from '@/libs/trpc/lambda/context';
6
6
  import { toolsRouter } from '@/server/routers/tools';
7
7
 
8
8
  const handler = (req: NextRequest) =>
@@ -10,7 +10,7 @@ const handler = (req: NextRequest) =>
10
10
  /**
11
11
  * @link https://trpc.io/docs/v11/context
12
12
  */
13
- createContext: () => createContext(req),
13
+ createContext: () => createLambdaContext(req),
14
14
 
15
15
  endpoint: '/trpc/tools',
16
16
 
@@ -2,7 +2,7 @@ import { notFound } from 'next/navigation';
2
2
  import { Flexbox } from 'react-layout-kit';
3
3
 
4
4
  import FileViewer from '@/features/FileViewer';
5
- import { createCallerFactory } from '@/libs/trpc';
5
+ import { createCallerFactory } from '@/libs/trpc/lambda';
6
6
  import { lambdaRouter } from '@/server/routers/lambda';
7
7
  import { PagePropsWithId } from '@/types/next';
8
8
  import { getUserAuth } from '@/utils/server/auth';
@@ -0,0 +1,224 @@
1
+ 'use client';
2
+
3
+ import { Icon } from '@lobehub/ui';
4
+ import { Button, Card, Divider, Typography } from 'antd';
5
+ import { createStyles } from 'antd-style';
6
+ import { Link2Icon, ServerIcon } from 'lucide-react';
7
+ import Image from 'next/image';
8
+ import React, { memo } from 'react';
9
+ import { useTranslation } from 'react-i18next';
10
+ import { Center, Flexbox } from 'react-layout-kit';
11
+
12
+ import { ProductLogo } from '@/components/Branding';
13
+
14
+ interface ClientProps {
15
+ clientId: string;
16
+ clientMetadata: {
17
+ clientName?: string;
18
+ logo?: string;
19
+ };
20
+
21
+ redirectUri?: string;
22
+ scopes: string[];
23
+ uid: string;
24
+ }
25
+
26
+ const { Title, Text, Paragraph } = Typography;
27
+
28
+ const useStyles = createStyles(({ css, token }) => ({
29
+ authButton: css`
30
+ width: 100%;
31
+ height: 40px;
32
+ border-radius: ${token.borderRadius}px;
33
+ font-weight: 500;
34
+ `,
35
+ cancelButton: css`
36
+ width: 100%;
37
+ height: 40px;
38
+ border-color: ${token.colorBorderSecondary};
39
+ border-radius: ${token.borderRadius}px;
40
+
41
+ font-weight: 500;
42
+ color: ${token.colorTextBase};
43
+
44
+ background-color: transparent;
45
+ `,
46
+ card: css`
47
+ width: 100%;
48
+ max-width: 500px;
49
+ border-color: ${token.colorBorderSecondary};
50
+ border-radius: 12px;
51
+
52
+ background-color: ${token.colorBgContainer};
53
+ `,
54
+ connector: css`
55
+ width: 40px;
56
+ height: 40px;
57
+ `,
58
+ connectorLine: css`
59
+ width: 32px;
60
+ height: 1px;
61
+ background-color: ${token.colorBorderSecondary};
62
+ `,
63
+ container: css`
64
+ width: 100%;
65
+ min-height: 100vh;
66
+ color: ${token.colorTextBase};
67
+ background-color: ${token.colorBgLayout};
68
+ `,
69
+ icon: css`
70
+ overflow: hidden;
71
+ display: flex;
72
+ align-items: center;
73
+ justify-content: center;
74
+
75
+ width: 64px;
76
+ height: 64px;
77
+ border: 1px solid ${token.colorBorderSecondary};
78
+ border-radius: 16px;
79
+
80
+ background-color: ${token.colorBgElevated};
81
+ `,
82
+ iconContainer: css`
83
+ display: flex;
84
+ align-items: center;
85
+ justify-content: center;
86
+ `,
87
+ lobeIcon: css`
88
+ overflow: hidden;
89
+ display: flex;
90
+ align-items: center;
91
+ justify-content: center;
92
+
93
+ width: 64px;
94
+ height: 64px;
95
+ border-radius: 50%;
96
+
97
+ background-color: ${token.colorBgElevated};
98
+ `,
99
+ scope: css`
100
+ margin-block: 8px;
101
+ padding: 12px;
102
+ border-radius: 6px;
103
+ background: ${token.colorFillQuaternary};
104
+ `,
105
+ scopes: css`
106
+ width: 100%;
107
+ `,
108
+ title: css`
109
+ margin-block-end: ${token.marginLG}px;
110
+ color: ${token.colorTextBase};
111
+ text-align: center;
112
+ `,
113
+ }));
114
+
115
+ /**
116
+ * 获取 Scope 的描述
117
+ */
118
+ function getScopeDescription(scope: string, t: any): string {
119
+ return t(`consent.scope.${scope.replace(':', '-')}`, scope);
120
+ }
121
+
122
+ const ConsentClient = memo<ClientProps>(
123
+ ({ uid, clientId, scopes, clientMetadata, redirectUri }) => {
124
+ const { styles, theme } = useStyles();
125
+ const { t } = useTranslation('oauth');
126
+
127
+ const clientDisplayName = clientMetadata?.clientName || clientId;
128
+ return (
129
+ <Center className={styles.container} gap={16}>
130
+ <Flexbox gap={40}>
131
+ <Flexbox align={'center'} gap={12} horizontal justify={'center'}>
132
+ <div className={styles.icon}>
133
+ {clientMetadata?.logo ? (
134
+ <Image
135
+ alt={clientDisplayName}
136
+ height={56}
137
+ src={clientMetadata?.logo}
138
+ unoptimized
139
+ width={56}
140
+ />
141
+ ) : (
142
+ <Icon icon={ServerIcon} />
143
+ )}
144
+ </div>
145
+ <div className={styles.connectorLine} />
146
+ <Center className={styles.connector}>
147
+ <Icon icon={Link2Icon} style={{ color: theme.colorTextSecondary, fontSize: 20 }} />
148
+ </Center>
149
+ <div className={styles.connectorLine} />
150
+ <div className={styles.lobeIcon}>
151
+ <ProductLogo height={48} style={{ objectFit: 'cover' }} width={48} />
152
+ </div>
153
+ </Flexbox>
154
+
155
+ <Title className={styles.title} level={3}>
156
+ {t('consent.title', { clientName: clientDisplayName })}
157
+ </Title>
158
+ </Flexbox>
159
+ <Card className={styles.card}>
160
+ <Flexbox gap={8}>
161
+ <Flexbox gap={12}>
162
+ <Paragraph>{t('consent.description', { clientName: clientDisplayName })}</Paragraph>
163
+
164
+ <div className={styles.scopes}>
165
+ <Paragraph type={'secondary'}>{t('consent.permissionsTitle')}</Paragraph>
166
+ {scopes.map((scope) => (
167
+ <div className={styles.scope} key={scope}>
168
+ <Text>{getScopeDescription(scope, t)}</Text>
169
+ </div>
170
+ ))}
171
+ </div>
172
+
173
+ <Divider dashed />
174
+ <Flexbox gap={16}>
175
+ <form action="/oidc/consent" method="post" style={{ width: '100%' }}>
176
+ <input name="uid" type="hidden" value={uid} />
177
+ <Flexbox gap={12} horizontal>
178
+ <Button
179
+ className={styles.cancelButton}
180
+ htmlType="submit"
181
+ name="consent"
182
+ value="deny"
183
+ >
184
+ {t('consent.buttons.deny')}
185
+ </Button>
186
+ <Button
187
+ className={styles.authButton}
188
+ htmlType="submit"
189
+ name="consent"
190
+ type="primary"
191
+ value="accept"
192
+ >
193
+ {t('consent.buttons.accept')}
194
+ </Button>
195
+ </Flexbox>
196
+ </form>
197
+ <Center>
198
+ <div style={{ color: theme.colorTextTertiary, fontSize: 12, height: '18px' }}>
199
+ {t('consent.redirectUri')}
200
+ </div>
201
+ <div>
202
+ <div
203
+ style={{
204
+ color: theme.colorTextSecondary,
205
+ fontSize: 12,
206
+ height: '18px',
207
+ }}
208
+ >
209
+ {redirectUri}
210
+ </div>
211
+ </div>
212
+ </Center>
213
+ </Flexbox>
214
+ </Flexbox>
215
+ </Flexbox>
216
+ </Card>
217
+ </Center>
218
+ );
219
+ },
220
+ );
221
+
222
+ ConsentClient.displayName = 'ConsentClient';
223
+
224
+ export default ConsentClient;