sistema-de-chamados/proxy.ts

134 lines
4.3 KiB
TypeScript

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 proxy(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<NextResponse | null> {
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
}
}