From b736fc9c819d155ba2480a0ebafc7b458a93f405 Mon Sep 17 00:00:00 2001 From: steven_li Date: Tue, 16 Jun 2026 10:17:30 +0800 Subject: [PATCH] =?UTF-8?q?feat(auth-portal):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E9=83=A8=E7=BD=B2=E6=8E=A7=E5=88=B6=E6=9C=8D=E5=8A=A1=E8=B0=83?= =?UTF-8?q?=E7=94=A8=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 导入callDeployControl和normalizeTokenResponse函数用于处理部署配置 - 新增hasTargetFrontendUrl函数检查响应中是否存在目标前端URL - 在注册流程中添加部署路由解析逻辑,当缺少前端URL时调用部署控制服务获取配置 - 更新normalizeTokenResponse函数以支持从实例对象中提取URL配置 refactor(runtime-control): 增强令牌响应标准化功能 - 扩展normalizeTokenResponse函数支持从instance对象中获取URL配置 - 添加对instance字段的支持,优先级为routing > instance配置 - 支持从instance中提取frontend_base_url、api_base_url和public_url build(tsconfig): 排除测试文件构建 - 在tsconfig.json中添加排除规则,排除**/*.test.ts和**/*.test.tsx文件 - 避免测试文件参与生产构建 refactor(authz-service): 优化Python后端令牌响应处理 - 更新_normalize_portal_token_response函数支持从实例对象中提取URL配置 - 重构URL优先级逻辑,支持routing和instance双重数据源 - 改进代码可读性,将复杂的URL赋值逻辑拆分为多行 --- .../src/app/api/runtime/register/route.ts | 30 +++++++++++++++++-- auth-portal/src/lib/runtime-control.test.ts | 25 ++++++++++++++++ auth-portal/src/lib/runtime-control.ts | 15 ++++++++-- auth-portal/src/tsconfig.json | 5 ++-- authz-service/src/app/main.py | 27 ++++++++++++++--- 5 files changed, 91 insertions(+), 11 deletions(-) create mode 100644 auth-portal/src/lib/runtime-control.test.ts diff --git a/auth-portal/src/app/api/runtime/register/route.ts b/auth-portal/src/app/api/runtime/register/route.ts index fcb1142..67eceb1 100644 --- a/auth-portal/src/app/api/runtime/register/route.ts +++ b/auth-portal/src/app/api/runtime/register/route.ts @@ -2,7 +2,13 @@ import { NextRequest, NextResponse } from 'next/server'; import type { TokenResponse } from '@/types/auth'; import { normalizePortalLocale, pickPortalText } from '@/lib/i18n/core'; -import { HttpError, REGISTER_REQUEST_TIMEOUT_MS, callAuthzService } from '@/lib/runtime-control'; +import { + HttpError, + REGISTER_REQUEST_TIMEOUT_MS, + callAuthzService, + callDeployControl, + normalizeTokenResponse, +} from '@/lib/runtime-control'; function errorStatus(error: unknown): number { if (error instanceof HttpError) { @@ -18,6 +24,15 @@ function errorDetail(error: unknown): string { return error instanceof Error ? error.message : 'registration failed'; } +function hasTargetFrontendUrl(response: TokenResponse): boolean { + return Boolean( + response.backend_connection?.frontend_base_url || + response.backend_connection?.public_base_url || + response.backend_connection?.api_base_url || + response.local_backend?.public_base_url + ); +} + export async function POST(request: NextRequest) { const locale = normalizePortalLocale( request.cookies.get('beaver_locale')?.value || @@ -46,7 +61,18 @@ export async function POST(request: NextRequest) { password, }, REGISTER_REQUEST_TIMEOUT_MS); - return NextResponse.json(response); + if (hasTargetFrontendUrl(response)) { + return NextResponse.json(response); + } + + const routing = await callDeployControl<{ + api_base_url?: string; + frontend_base_url?: string; + public_url?: string; + instance?: unknown; + }>('/api/instances/resolve', { username }); + + return NextResponse.json(normalizeTokenResponse(response, routing)); } catch (error) { return NextResponse.json({ detail: errorDetail(error) }, { status: errorStatus(error) }); } diff --git a/auth-portal/src/lib/runtime-control.test.ts b/auth-portal/src/lib/runtime-control.test.ts new file mode 100644 index 0000000..c6b4db5 --- /dev/null +++ b/auth-portal/src/lib/runtime-control.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from 'vitest'; + +import { normalizeTokenResponse } from './runtime-control'; + +describe('normalizeTokenResponse', () => { + it('uses nested instance routing when top-level route URLs are missing', () => { + const response = normalizeTokenResponse({ + access_token: 'token', + refresh_token: '', + token_type: 'bearer', + user_id: 'alice', + username: 'alice', + role: 'owner', + handoff_code: 'handoff-1', + }, { + instance: { + public_url: 'workspace.example.com:8088', + frontend_base_url: 'workspace.example.com:8088', + }, + }); + + expect(response.backend_connection?.frontend_base_url).toBe('workspace.example.com:8088'); + expect(response.backend_connection?.public_base_url).toBe('workspace.example.com:8088'); + }); +}); diff --git a/auth-portal/src/lib/runtime-control.ts b/auth-portal/src/lib/runtime-control.ts index f3f494a..abd72e8 100644 --- a/auth-portal/src/lib/runtime-control.ts +++ b/auth-portal/src/lib/runtime-control.ts @@ -107,11 +107,20 @@ export function normalizeTokenResponse( frontend_base_url?: unknown; api_base_url?: unknown; public_url?: unknown; + instance?: unknown; } ): TokenResponse { - const frontendBaseUrl = asString(routing.frontend_base_url); - const apiBaseUrl = asString(routing.api_base_url) || asString(routing.public_url); - const publicUrl = asString(routing.public_url) || apiBaseUrl; + const instance = asObject(routing.instance); + const frontendBaseUrl = + asString(routing.frontend_base_url) || + asString(instance.frontend_base_url) || + asString(instance.public_url); + const apiBaseUrl = + asString(routing.api_base_url) || + asString(instance.api_base_url) || + asString(routing.public_url) || + asString(instance.public_url); + const publicUrl = asString(routing.public_url) || asString(instance.public_url) || apiBaseUrl; const backendConnection = asObject(response.backend_connection); const mergedBackendConnection = { diff --git a/auth-portal/src/tsconfig.json b/auth-portal/src/tsconfig.json index e5439d6..11fd9f5 100644 --- a/auth-portal/src/tsconfig.json +++ b/auth-portal/src/tsconfig.json @@ -36,7 +36,8 @@ ".next/types/**/*.ts" ], "exclude": [ - "node_modules" + "node_modules", + "**/*.test.ts", + "**/*.test.tsx" ] } - diff --git a/authz-service/src/app/main.py b/authz-service/src/app/main.py index ec91bc2..9e8d460 100644 --- a/authz-service/src/app/main.py +++ b/authz-service/src/app/main.py @@ -187,14 +187,33 @@ def _normalize_portal_token_response( response: dict[str, Any], routing: dict[str, Any], ) -> dict[str, Any]: - frontend_base_url = _as_string(routing.get("frontend_base_url")) - api_base_url = _as_string(routing.get("api_base_url")) or _as_string(routing.get("public_url")) - public_url = _as_string(routing.get("public_url")) or api_base_url + instance = _as_object(routing.get("instance")) + frontend_base_url = ( + _as_string(routing.get("frontend_base_url")) + or _as_string(instance.get("frontend_base_url")) + or _as_string(instance.get("public_url")) + ) + api_base_url = ( + _as_string(routing.get("api_base_url")) + or _as_string(instance.get("api_base_url")) + or _as_string(routing.get("public_url")) + or _as_string(instance.get("public_url")) + ) + public_url = ( + _as_string(routing.get("public_url")) + or _as_string(instance.get("public_url")) + or api_base_url + ) backend_connection = _as_object(response.get("backend_connection")) merged_backend_connection = { **backend_connection, - "frontend_base_url": _as_string(backend_connection.get("frontend_base_url")) or frontend_base_url or public_url or None, + "frontend_base_url": ( + _as_string(backend_connection.get("frontend_base_url")) + or frontend_base_url + or public_url + or None + ), "api_base_url": _as_string(backend_connection.get("api_base_url")) or api_base_url or public_url or None, "public_base_url": _as_string(backend_connection.get("public_base_url")) or public_url or api_base_url or None, }