import { getCookieCache } from "better-auth/cookies" import { NextRequest, NextResponse } from "next/server" import { isAllowedHost } from "@/config/allowed-hosts" // Rotas públicas explícitas (não autenticadas) // Permite handshake de máquina sem sessão prévia para criar a sessão de máquina. const PUBLIC_PATHS = [/^\/login$/, /^\/machines\/handshake$/] // Rotas somente admin const ADMIN_ONLY_PATHS = [/^\/admin(?:$|\/)/] const APP_HOME = "/dashboard" export async function middleware(request: NextRequest) { if (process.env.NODE_ENV === "production" && !isAllowedHost(request.headers.get("host"))) { return new NextResponse("Invalid Host header", { status: 403 }) } const { pathname, searchParams, search } = request.nextUrl if (pathname.startsWith("/api")) { return NextResponse.next() } if (PUBLIC_PATHS.some((pattern) => pattern.test(pathname))) return NextResponse.next() const session = await getCookieCache(request) if (!session?.user) { const hasSessionCookie = Boolean(request.cookies.get("better-auth.session-token")) const hasRefreshCookie = Boolean(request.cookies.get("better-auth.refresh-token")) || Boolean(request.cookies.get("better-auth.refresh-token-v2")) if (hasSessionCookie || hasRefreshCookie) { const refreshed = await attemptSessionRefresh(request) if (refreshed) { return refreshed } } const redirectUrl = new URL("/login", request.url) redirectUrl.searchParams.set("callbackUrl", pathname + search) return NextResponse.redirect(redirectUrl) } const role = (session.user as { role?: string })?.role?.toLowerCase() ?? "agent" const machinePersona = role === "machine" ? ((session.user as unknown as { machinePersona?: string }).machinePersona ?? "").toLowerCase() : null if (pathname === "/login") { const callback = searchParams.get("callbackUrl") ?? undefined const defaultDestination = role === "machine" ? machinePersona === "manager" ? "/dashboard" : "/portal/tickets" : APP_HOME const target = callback && !callback.startsWith("/login") ? callback : defaultDestination return NextResponse.redirect(new URL(target, request.url)) } // Ajusta destinos conforme persona da máquina para evitar loops login<->dashboard if (role === "machine") { // Evita enviar colaborador ao dashboard; redireciona para o Portal if (pathname.startsWith("/dashboard") && machinePersona !== "manager") { return NextResponse.redirect(new URL("/portal/tickets", request.url)) } // Evita mostrar login quando já há sessão de máquina } const isAdmin = role === "admin" // Em desenvolvimento, evitamos bloquear rotas admin por possíveis diferenças // de cache de cookie/sessão entre dev server e middleware. Em produção, aplica o gate. if ( process.env.NODE_ENV === "production" && !isAdmin && ADMIN_ONLY_PATHS.some((pattern) => pattern.test(pathname)) ) { return NextResponse.redirect(new URL(APP_HOME, request.url)) } return NextResponse.next() } export const config = { runtime: "nodejs", // Evita executar para assets e imagens estáticas matcher: ["/((?!_next/static|_next/image|favicon.ico|icon.png).*)"], } async function attemptSessionRefresh(request: NextRequest): Promise { try { const refreshUrl = new URL("/api/auth/get-session", request.url) const response = await fetch(refreshUrl, { method: "GET", headers: { cookie: request.headers.get("cookie") ?? "", }, }) if (!response.ok) { return null } const data = await response.json().catch(() => null) if (!data?.user) { return null } const redirect = NextResponse.redirect(request.nextUrl) const headersWithGetSetCookie = response.headers as Headers & { getSetCookie?: () => string[] | undefined } let setCookieHeaders = typeof headersWithGetSetCookie.getSetCookie === "function" ? headersWithGetSetCookie.getSetCookie() ?? [] : [] if (setCookieHeaders.length === 0) { const single = response.headers.get("set-cookie") if (single) { setCookieHeaders = [single] } } for (const cookie of setCookieHeaders) { redirect.headers.append("set-cookie", cookie) } return redirect } catch { return null } }