117 lines
3.8 KiB
TypeScript
117 lines
3.8 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server"
|
|
import { getCookieCache } from "better-auth/cookies"
|
|
|
|
// 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) {
|
|
const { pathname, search } = request.nextUrl
|
|
|
|
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
|
|
|
|
// 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
|
|
if (pathname === "/login") {
|
|
const target = machinePersona === "manager" ? "/dashboard" : "/portal/tickets"
|
|
const url = new URL(target, request.url)
|
|
return NextResponse.redirect(url)
|
|
}
|
|
}
|
|
|
|
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: ["/((?!api|_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
|
|
}
|
|
}
|