import { NextRequest, NextResponse } from "next/server"; import { checkRateLimit, getClientIP, createRateLimitResponse, } from "@/lib/rate-limiter"; import { secureLogger } from "@/lib/secure-logger"; import { loginSchema } from "@/lib/validation-schemas"; import { comparePassword } from "@/lib/auth/password"; import { generateAccessToken, generateRefreshToken, } from "@/lib/auth/jwt"; import { getUserByEmail, getUserByUsername, updateLastLogin, } from "@/lib/db/queries/users"; import { setSession } from "@/lib/redis/session"; import { randomUUID } from "crypto"; export async function POST(request: NextRequest) { try { // Rate limiting const ip = getClientIP(request); const rateLimit = checkRateLimit(ip, "/api/auth/login"); if (!rateLimit.allowed) { return createRateLimitResponse(rateLimit.resetAt); } const body = await request.json(); secureLogger.info("📥 POST /api/auth/login - Recebido", { ip, timestamp: new Date().toISOString(), }); // Validação com Zod const validationResult = loginSchema.safeParse(body); if (!validationResult.success) { const errors = validationResult.error.issues.map((err) => ({ field: err.path.join("."), message: err.message, })); return NextResponse.json( { success: false, error: "Dados de entrada inválidos", details: errors, }, { status: 400 } ); } const { emailOrUsername, password } = validationResult.data; // Buscar usuário por email ou username const user = (await getUserByEmail(emailOrUsername)) || (await getUserByUsername(emailOrUsername)); if (!user) { secureLogger.warn("⚠️ Tentativa de login com credenciais inválidas", { emailOrUsername: secureLogger.mask({ emailOrUsername }), timestamp: new Date().toISOString(), }); return NextResponse.json( { success: false, error: "Email/username ou senha incorretos", }, { status: 401 } ); } // Verificar se usuário está ativo if (!user.is_active) { return NextResponse.json( { success: false, error: "Conta desativada. Entre em contato com o suporte.", }, { status: 403 } ); } // Verificar senha if (!user.password_hash) { secureLogger.error("❌ Usuário sem password_hash", { userId: user.user_id, timestamp: new Date().toISOString(), }); return NextResponse.json( { success: false, error: "Erro interno. Tente novamente mais tarde.", }, { status: 500 } ); } const passwordMatch = await comparePassword(password, user.password_hash); if (!passwordMatch) { secureLogger.warn("⚠️ Senha incorreta", { userId: user.user_id, timestamp: new Date().toISOString(), }); return NextResponse.json( { success: false, error: "Email/username ou senha incorretos", }, { status: 401 } ); } // Atualizar último login await updateLastLogin(user.user_id); // Gerar tokens const accessToken = generateAccessToken(user.user_id, user.username); const refreshToken = generateRefreshToken(user.user_id, user.username); // Criar sessão no Redis const sessionId = randomUUID(); await setSession(sessionId, { userId: user.user_id, username: user.username, email: user.email, accessToken, createdAt: Date.now(), }); // Criar resposta const response = NextResponse.json( { success: true, user: { userId: user.user_id, username: user.username, email: user.email, firstName: user.first_name, lastName: user.last_name, accountLevel: user.account_level, hasDonated: user.has_donated, }, accessToken, }, { status: 200 } ); // Adicionar refresh token e sessionId como cookies httpOnly response.cookies.set("refreshToken", refreshToken, { httpOnly: true, secure: process.env.NODE_ENV === "production", sameSite: "lax", maxAge: 60 * 60 * 24 * 7, // 7 dias path: "/", }); response.cookies.set("sessionId", sessionId, { httpOnly: true, secure: process.env.NODE_ENV === "production", sameSite: "lax", maxAge: 60 * 60 * 24 * 7, // 7 dias path: "/", }); secureLogger.info("✅ Login realizado com sucesso", { userId: user.user_id, username: user.username, timestamp: new Date().toISOString(), }); return response; } catch (error) { secureLogger.error("❌ Erro ao fazer login", { error: error instanceof Error ? error.message : "Erro desconhecido", stack: error instanceof Error ? error.stack : undefined, timestamp: new Date().toISOString(), }); return NextResponse.json( { success: false, error: error instanceof Error ? error.message : "Erro interno", }, { status: 500 } ); } }