import { randomUUID } from "node:crypto"; import { type NextRequest, NextResponse } from "next/server"; import { generateAccessToken, generateRefreshToken } from "@/lib/auth/jwt"; import { comparePassword } from "@/lib/auth/password"; import { getDefaultSubscriptionTier, getSubscriptionTierById, } from "@/lib/prisma/queries/subscription-tiers"; import { getUserByEmail, getUserByUsername, updateLastLogin, } from "@/lib/prisma/queries/users"; import { checkRateLimit, createRateLimitResponse, getClientIP, } from "@/lib/rate-limiter"; import { setSession } from "@/lib/redis/session"; import { secureLogger } from "@/lib/secure-logger"; import { loginSchema } from "@/lib/validation-schemas"; 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 tierInfo = user.subscription_tier ?? (user.subscription_tier_id ? await getSubscriptionTierById(user.subscription_tier_id) : await getDefaultSubscriptionTier()); const accessToken = generateAccessToken(user.user_id, user.username, { tierSlug: tierInfo?.slug ?? undefined, tierLevel: tierInfo?.level ?? undefined, }); const refreshToken = generateRefreshToken(user.user_id, user.username, { tierSlug: tierInfo?.slug ?? undefined, tierLevel: tierInfo?.level ?? undefined, }); // 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(), subscriptionTierId: tierInfo?.subscription_tier_id ?? null, subscriptionTierSlug: tierInfo?.slug ?? null, subscriptionTierName: tierInfo?.name ?? null, subscriptionTierLevel: tierInfo?.level ?? null, }); // 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, subscriptionTier: tierInfo ? { id: tierInfo.subscription_tier_id, name: tierInfo.name, slug: tierInfo.slug, level: tierInfo.level, } : null, }, 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 }, ); } }