Files
pagodera/app/api/auth/login/route.ts
T
Evandro Fonseca Junior 55eebde0ba feat: add user authentication UI to header component
Add login and register dialogs, user dropdown menu with profile link and logout option, and integrate with session management hook. Prevent hydration mismatches by conditionally rendering auth elements after mount. Update package-lock.json with new @esbuild dependencies for build tooling.
2025-11-04 02:33:29 -03:00

200 lines
5.1 KiB
TypeScript

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 }
);
}
}