import { NextRequest, NextResponse } from "next/server"; import { ADMIN_ROLE } from "@/lib/constants/auth"; import { hostPublicDefaultCustomerId, isHostPublicApiEnabled, } from "@/lib/constants/host-public-api"; import { DEFAULT_HOST_CUSTOMER_ID, isHostServiceAuthDisabled, } from "@/lib/constants/host-service"; import { verifyToken } from "@/modules/auth/jwt"; import { AuthError } from "@/modules/auth/errors"; import { parseServiceTokenFromEnv, resolveEnvServiceToken, } from "@/modules/auth/service-token-env"; import { EMBED_DEMO_COOKIE, verifyEmbedDemoToken, } from "@/modules/embed-demo/auth"; const PUBLIC_API_PATHS = [ "/api/auth/login", "/api/embed-demo/login", ]; function isHostPublicRoute(pathname: string): boolean { return pathname.startsWith("/api/host/"); } function isPublicApi(pathname: string): boolean { if (pathname.startsWith("/api/embed-demo/")) { return true; } return PUBLIC_API_PATHS.some( (path) => pathname === path || pathname.startsWith(`${path}/`), ); } function isAdminRoute(pathname: string): boolean { return ( pathname.startsWith("/api/alerts") || pathname.startsWith("/api/metrics") || pathname.startsWith("/api/rpa") || pathname.startsWith("/api/admin/queues") || pathname.startsWith("/api/admin/markup-configs") || pathname.startsWith("/api/admin/customers") || pathname === "/api/quotes/manual-fallback" ); } function isHostRoute(pathname: string): boolean { if (pathname === "/api/quotes/manual-fallback") { return false; } return ( pathname.startsWith("/api/quotes") || pathname.startsWith("/api/priority1") || pathname.startsWith("/api/flock") || pathname.startsWith("/api/markup-configs") || pathname.startsWith("/api/addresses") || pathname.startsWith("/api/host") ); } function extractBearerToken(request: NextRequest): string | null { const header = request.headers.get("authorization"); if (!header?.startsWith("Bearer ")) { return null; } const token = header.slice(7).trim(); return token || null; } function extractBullBoardCookieToken(request: NextRequest): string | null { if (!request.nextUrl.pathname.startsWith("/api/admin/queues")) { return null; } const raw = request.cookies.get("bull_board_auth")?.value; if (!raw) { return null; } try { return decodeURIComponent(raw); } catch { return raw; } } function jsonError(code: string, message: string, status: number): NextResponse { return NextResponse.json( { code, message, data: null }, { status, headers: { "Cache-Control": "no-store" } }, ); } function passWithHeaders( request: NextRequest, headers: Record, ): NextResponse { const requestHeaders = new Headers(request.headers); for (const [key, value] of Object.entries(headers)) { requestHeaders.set(key, value); } return NextResponse.next({ request: { headers: requestHeaders } }); } export async function middleware(request: NextRequest) { const { pathname } = request.nextUrl; if (!pathname.startsWith("/api/")) { return NextResponse.next(); } if (isPublicApi(pathname)) { return NextResponse.next(); } if (isHostPublicApiEnabled() && isHostPublicRoute(pathname)) { return passWithHeaders(request, { "x-auth-type": "service", "x-customer-id": hostPublicDefaultCustomerId(), "x-permissions": "", }); } if (isHostServiceAuthDisabled() && isHostRoute(pathname)) { const customerId = request.headers.get("x-customer-id")?.trim() || hostPublicDefaultCustomerId() || DEFAULT_HOST_CUSTOMER_ID; return passWithHeaders(request, { "x-auth-type": "service", "x-customer-id": customerId, "x-permissions": "pricing:markup:write", }); } const token = extractBearerToken(request) ?? extractBullBoardCookieToken(request); if (!token) { const embedRaw = request.cookies.get(EMBED_DEMO_COOKIE)?.value; if (embedRaw && isHostRoute(pathname)) { try { const embed = await verifyEmbedDemoToken(embedRaw); return passWithHeaders(request, { "x-auth-type": "service", "x-customer-id": embed.sub, "x-permissions": "pricing:markup:write", "x-service-verified": "true", }); } catch { return jsonError("UNAUTHORIZED", "演示会话无效或已过期", 401); } } return jsonError("UNAUTHORIZED", "缺少 Authorization 头", 401); } try { if (isAdminRoute(pathname)) { const payload = await verifyToken(token); if (payload.role !== ADMIN_ROLE) { return jsonError("FORBIDDEN", "需要管理员权限", 403); } return passWithHeaders(request, { "x-auth-type": "admin", "x-user-id": payload.sub, "x-user-role": payload.role, }); } if (isHostRoute(pathname)) { const customerId = request.headers.get("x-customer-id")?.trim() || null; try { if (customerId) { const verified = resolveEnvServiceToken(token, customerId); if (verified) { return passWithHeaders(request, { "x-auth-type": "service", "x-customer-id": verified.customerId, "x-permissions": verified.permissions.join(","), "x-service-verified": "true", }); } } else { const parsed = parseServiceTokenFromEnv(token); if (parsed) { return passWithHeaders(request, { "x-auth-type": "service", "x-customer-id": parsed.customerId, "x-permissions": parsed.permissions.join(","), "x-service-verified": "true", }); } } } catch (error) { if (error instanceof AuthError) { return jsonError(error.code, error.message, error.httpStatus); } throw error; } return passWithHeaders(request, { "x-auth-type": "service", "x-customer-id": customerId ?? "", "x-bearer-token": token, "x-permissions": "", "x-service-verified": "false", }); } // 未分类 API 默认要求管理员 JWT const payload = await verifyToken(token); return passWithHeaders(request, { "x-auth-type": "admin", "x-user-id": payload.sub, "x-user-role": payload.role, }); } catch (error) { if (error instanceof AuthError) { return jsonError(error.code, error.message, error.httpStatus); } console.error("[middleware] 鉴权异常:", error); return jsonError("INTERNAL_ERROR", "鉴权处理失败", 500); } } export const config = { matcher: ["/api/:path*"], };