import { NextRequest, NextResponse } from "next/server"; import { ADMIN_ROLE } from "@/lib/constants/auth"; import { verifyToken } from "@/modules/auth/jwt"; import { AuthError } from "@/modules/auth/errors"; import { parseServiceToken, verifyServiceToken, } from "@/modules/auth/service-token"; const PUBLIC_API_PATHS = ["/api/auth/login"]; function isPublicApi(pathname: string): boolean { 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 === "/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/markup-configs") || pathname.startsWith("/api/addresses") ); } 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(); } const token = extractBearerToken(request) ?? extractBullBoardCookieToken(request); if (!token) { 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"); if (customerId) { const verified = verifyServiceToken(token, customerId); return passWithHeaders(request, { "x-auth-type": "service", "x-customer-id": verified.customerId, "x-permissions": verified.permissions.join(","), }); } const parsed = parseServiceToken(token); if (!parsed) { return jsonError("UNAUTHORIZED", "Service Token 无效", 401); } return passWithHeaders(request, { "x-auth-type": "service", "x-customer-id": parsed.customerId, "x-permissions": parsed.permissions.join(","), }); } // 未分类 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*"], };