Files
pagodera/lib/auth/middleware.ts
T
2025-11-11 03:08:36 -03:00

187 lines
4.1 KiB
TypeScript

/**
* 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<AuthenticatedUser | null> {
// 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;
}