/** * Middleware de autenticação * Extrai e valida tokens JWT das requisições */ import { cookies } from "next/headers"; import { type NextRequest, NextResponse } from "next/server"; import { getSession } from "@/lib/redis/session"; import { verifyAccessToken } from "./jwt"; export interface AuthenticatedUser { userId: number; username: string; tierSlug?: string; tierLevel?: number; } /** * Extrai token do header Authorization * @param request - Request do Next.js * @returns Token ou null */ function extractToken(request: NextRequest): string | null { const authHeader = request.headers.get("authorization"); if (!authHeader || !authHeader.startsWith("Bearer ")) { return null; } return authHeader.substring(7); // Remove "Bearer " } /** * Converte payload de token em objeto AuthenticatedUser. */ function toAuthenticatedUser(payload: { userId: number; username: string; tierSlug?: string; tierLevel?: number; }): AuthenticatedUser { return { userId: payload.userId, username: payload.username, tierSlug: payload.tierSlug, tierLevel: payload.tierLevel, }; } export async function requireAuth( request: NextRequest, ): Promise< AuthenticatedUser | NextResponse<{ success: false; error: string }> > { // Tentar primeiro com Bearer token const token = extractToken(request); if (token) { const payload = verifyAccessToken(token); if (payload) { return toAuthenticatedUser(payload); } } // Se não tiver token no header, tentar com cookie try { const cookieStore = await cookies(); const sessionId = cookieStore.get("sessionId")?.value; if (sessionId) { const session = await getSession(sessionId); if (session) { const tokenPayload = verifyAccessToken(session.accessToken); if (tokenPayload) { return toAuthenticatedUser(tokenPayload); } } } } catch (_error) { // Se der erro ao acessar cookies, continuar com o erro de token } return NextResponse.json( { success: false, error: "Token de autenticação não fornecido ou inválido", }, { status: 401 }, ); } /** * Middleware opcional de autenticação * Retorna dados do usuário se autenticado, null caso contrário * Não lança erro se não autenticado * @param request - Request do Next.js * @returns Dados do usuário ou null */ export async function optionalAuth( request: NextRequest, ): Promise { // Tentar primeiro com Bearer token const token = extractToken(request); if (token) { const payload = verifyAccessToken(token); if (payload) { return toAuthenticatedUser(payload); } } // Tentar com cookie try { const cookieStore = await cookies(); const sessionId = cookieStore.get("sessionId")?.value; if (sessionId) { const session = await getSession(sessionId); if (session) { const tokenPayload = verifyAccessToken(session.accessToken); if (tokenPayload) { return toAuthenticatedUser(tokenPayload); } } } } catch (_error) { // Ignorar erros de cookie } return null; } export async function requireAdminUser( request: NextRequest, ): Promise< AuthenticatedUser | NextResponse<{ success: false; error: string }> > { const auth = await requireAuth(request); if (auth instanceof NextResponse) { return auth; } if (auth.userId !== 1) { return NextResponse.json( { success: false, error: "Acesso restrito ao administrador.", }, { status: 403 }, ); } return auth; } export async function requireTierLevel( request: NextRequest, minimumLevel: number, ): Promise< AuthenticatedUser | NextResponse<{ success: false; error: string }> > { const auth = await requireAuth(request); if (auth instanceof NextResponse) { return auth; } const tierLevel = auth.tierLevel ?? 0; if (tierLevel < minimumLevel) { return NextResponse.json( { success: false, error: "Plano atual não possui acesso a este recurso.", }, { status: 403 }, ); } return auth; }