134 lines
4.3 KiB
TypeScript
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
|
|
}
|
|
}
|