feat: enhance login process with IP capture and improved error handling

- Implemented an internal mutation for login that captures the user's IP address and user agent for better security and tracking.
- Enhanced the HTTP login endpoint to extract and log client IP, improving the overall authentication process.
- Added validation for IP addresses to ensure only valid formats are recorded, enhancing data integrity.
- Updated the login mutation to handle rate limiting and user status checks more effectively, providing clearer feedback on login attempts.
This commit is contained in:
2025-11-04 03:26:34 -03:00
parent f278ad4d17
commit c6c88f85a7
11 changed files with 3531 additions and 70 deletions

View File

@@ -1,5 +1,150 @@
import { httpRouter } from "convex/server";
const http = httpRouter();
export default http;
import { httpRouter } from "convex/server";
import { httpAction } from "./_generated/server";
import { internal } from "./_generated/api";
import { getClientIP } from "./utils/getClientIP";
import { v } from "convex/values";
const http = httpRouter();
/**
* Endpoint de teste para debug - retorna todos os headers disponíveis
* GET /api/debug/headers
*/
http.route({
path: "/api/debug/headers",
method: "GET",
handler: httpAction(async (ctx, request) => {
const headers: Record<string, string> = {};
request.headers.forEach((value, key) => {
headers[key] = value;
});
const ip = getClientIP(request);
return new Response(
JSON.stringify({
headers,
extractedIP: ip,
url: request.url,
}),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
);
}),
});
/**
* Endpoint HTTP para login que captura automaticamente o IP do cliente
* POST /api/login
* Body: { matriculaOuEmail: string, senha: string }
*/
http.route({
path: "/api/login",
method: "POST",
handler: httpAction(async (ctx, request) => {
try {
// Debug: Log todos os headers disponíveis
console.log("=== DEBUG: Headers HTTP ===");
const headersEntries: string[] = [];
request.headers.forEach((value, key) => {
headersEntries.push(`${key}: ${value}`);
});
console.log("Headers:", headersEntries.join(", "));
console.log("Request URL:", request.url);
// Extrair IP do cliente do request
let clientIP = getClientIP(request);
console.log("IP extraído:", clientIP);
// Se não encontrou IP, tentar obter do URL ou usar valor padrão
if (!clientIP) {
try {
const url = new URL(request.url);
// Tentar pegar do query param se disponível
const ipParam = url.searchParams.get("client_ip");
if (ipParam && /^(\d{1,3}\.){3}\d{1,3}$/.test(ipParam)) {
clientIP = ipParam;
console.log("IP obtido do query param:", clientIP);
} else {
// Se ainda não tiver IP, usar um identificador baseado no timestamp
// Isso pelo menos diferencia requisições
console.warn("IP não encontrado nos headers. Usando fallback.");
clientIP = undefined; // Deixar como undefined para registrar como não disponível
}
} catch {
console.warn("Erro ao processar URL para IP");
}
}
// Extrair User-Agent
const userAgent = request.headers.get("user-agent") || undefined;
// Ler body da requisição
const body = await request.json();
if (!body.matriculaOuEmail || !body.senha) {
return new Response(
JSON.stringify({
sucesso: false,
erro: "Matrícula/Email e senha são obrigatórios",
}),
{
status: 400,
headers: { "Content-Type": "application/json" },
}
);
}
// Chamar a mutation de login interna com IP e userAgent
const resultado = await ctx.runMutation(internal.autenticacao.loginComIP, {
matriculaOuEmail: body.matriculaOuEmail,
senha: body.senha,
ipAddress: clientIP,
userAgent: userAgent,
});
return new Response(JSON.stringify(resultado), {
status: 200,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
},
});
} catch (error) {
return new Response(
JSON.stringify({
sucesso: false,
erro: error instanceof Error ? error.message : "Erro ao processar login",
}),
{
status: 500,
headers: { "Content-Type": "application/json" },
}
);
}
}),
});
/**
* Endpoint OPTIONS para CORS preflight
*/
http.route({
path: "/api/login",
method: "OPTIONS",
handler: httpAction(async () => {
return new Response(null, {
status: 200,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
},
});
}),
});
export default http;