feat: integrate rate limiting and enhance security features
- Added @convex-dev/rate-limiter dependency to manage request limits effectively. - Implemented rate limiting configurations for IPs, users, and endpoints to prevent abuse and enhance security. - Introduced new security analysis endpoint to detect potential attacks based on incoming requests. - Updated backend schema to include rate limit configurations and various cyber attack types for improved incident tracking. - Enhanced existing security functions to incorporate rate limiting checks, ensuring robust protection against brute force and other attacks.
This commit is contained in:
134
packages/backend/convex/_generated/api.d.ts
vendored
134
packages/backend/convex/_generated/api.d.ts
vendored
@@ -2226,4 +2226,138 @@ export declare const components: {
|
||||
updateMany: FunctionReference<"mutation", "internal", any, any>;
|
||||
};
|
||||
};
|
||||
rateLimiter: {
|
||||
lib: {
|
||||
checkRateLimit: FunctionReference<
|
||||
"query",
|
||||
"internal",
|
||||
{
|
||||
config:
|
||||
| {
|
||||
capacity?: number;
|
||||
kind: "token bucket";
|
||||
maxReserved?: number;
|
||||
period: number;
|
||||
rate: number;
|
||||
shards?: number;
|
||||
start?: null;
|
||||
}
|
||||
| {
|
||||
capacity?: number;
|
||||
kind: "fixed window";
|
||||
maxReserved?: number;
|
||||
period: number;
|
||||
rate: number;
|
||||
shards?: number;
|
||||
start?: number;
|
||||
};
|
||||
count?: number;
|
||||
key?: string;
|
||||
name: string;
|
||||
reserve?: boolean;
|
||||
throws?: boolean;
|
||||
},
|
||||
{ ok: true; retryAfter?: number } | { ok: false; retryAfter: number }
|
||||
>;
|
||||
clearAll: FunctionReference<
|
||||
"mutation",
|
||||
"internal",
|
||||
{ before?: number },
|
||||
null
|
||||
>;
|
||||
getServerTime: FunctionReference<"mutation", "internal", {}, number>;
|
||||
getValue: FunctionReference<
|
||||
"query",
|
||||
"internal",
|
||||
{
|
||||
config:
|
||||
| {
|
||||
capacity?: number;
|
||||
kind: "token bucket";
|
||||
maxReserved?: number;
|
||||
period: number;
|
||||
rate: number;
|
||||
shards?: number;
|
||||
start?: null;
|
||||
}
|
||||
| {
|
||||
capacity?: number;
|
||||
kind: "fixed window";
|
||||
maxReserved?: number;
|
||||
period: number;
|
||||
rate: number;
|
||||
shards?: number;
|
||||
start?: number;
|
||||
};
|
||||
key?: string;
|
||||
name: string;
|
||||
sampleShards?: number;
|
||||
},
|
||||
{
|
||||
config:
|
||||
| {
|
||||
capacity?: number;
|
||||
kind: "token bucket";
|
||||
maxReserved?: number;
|
||||
period: number;
|
||||
rate: number;
|
||||
shards?: number;
|
||||
start?: null;
|
||||
}
|
||||
| {
|
||||
capacity?: number;
|
||||
kind: "fixed window";
|
||||
maxReserved?: number;
|
||||
period: number;
|
||||
rate: number;
|
||||
shards?: number;
|
||||
start?: number;
|
||||
};
|
||||
shard: number;
|
||||
ts: number;
|
||||
value: number;
|
||||
}
|
||||
>;
|
||||
rateLimit: FunctionReference<
|
||||
"mutation",
|
||||
"internal",
|
||||
{
|
||||
config:
|
||||
| {
|
||||
capacity?: number;
|
||||
kind: "token bucket";
|
||||
maxReserved?: number;
|
||||
period: number;
|
||||
rate: number;
|
||||
shards?: number;
|
||||
start?: null;
|
||||
}
|
||||
| {
|
||||
capacity?: number;
|
||||
kind: "fixed window";
|
||||
maxReserved?: number;
|
||||
period: number;
|
||||
rate: number;
|
||||
shards?: number;
|
||||
start?: number;
|
||||
};
|
||||
count?: number;
|
||||
key?: string;
|
||||
name: string;
|
||||
reserve?: boolean;
|
||||
throws?: boolean;
|
||||
},
|
||||
{ ok: true; retryAfter?: number } | { ok: false; retryAfter: number }
|
||||
>;
|
||||
resetRateLimit: FunctionReference<
|
||||
"mutation",
|
||||
"internal",
|
||||
{ key?: string; name: string },
|
||||
null
|
||||
>;
|
||||
};
|
||||
time: {
|
||||
getServerTime: FunctionReference<"mutation", "internal", {}, number>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { defineApp } from "convex/server";
|
||||
import betterAuth from "@convex-dev/better-auth/convex.config";
|
||||
import rateLimiter from "@convex-dev/rate-limiter/convex.config";
|
||||
|
||||
const app = defineApp();
|
||||
app.use(betterAuth);
|
||||
app.use(rateLimiter);
|
||||
|
||||
export default app;
|
||||
|
||||
@@ -46,5 +46,13 @@ crons.interval(
|
||||
{}
|
||||
);
|
||||
|
||||
// Monitorar logs de login e detectar brute force a cada 5 minutos
|
||||
crons.interval(
|
||||
"monitorar-logs-login-brute-force",
|
||||
{ minutes: 5 },
|
||||
internal.security.monitorarLogsLogin,
|
||||
{}
|
||||
);
|
||||
|
||||
export default crons;
|
||||
|
||||
|
||||
@@ -1,8 +1,74 @@
|
||||
import { httpRouter } from "convex/server";
|
||||
import { authComponent, createAuth } from "./auth";
|
||||
import { httpAction } from "./_generated/server";
|
||||
import { api } from "./_generated/api";
|
||||
import { getClientIP } from "./utils/getClientIP";
|
||||
|
||||
const http = httpRouter();
|
||||
|
||||
// Action HTTP para análise de segurança de requisições
|
||||
// Pode ser chamada do frontend ou de outros sistemas
|
||||
http.route({
|
||||
path: "/security/analyze",
|
||||
method: "POST",
|
||||
handler: httpAction(async (ctx, request) => {
|
||||
const url = new URL(request.url);
|
||||
const method = request.method;
|
||||
|
||||
// Extrair IP do cliente
|
||||
const ipOrigem = getClientIP(request);
|
||||
|
||||
// Extrair headers
|
||||
const headers: Record<string, string> = {};
|
||||
request.headers.forEach((value, key) => {
|
||||
headers[key] = value;
|
||||
});
|
||||
|
||||
// Extrair query params
|
||||
const queryParams: Record<string, string> = {};
|
||||
url.searchParams.forEach((value, key) => {
|
||||
queryParams[key] = value;
|
||||
});
|
||||
|
||||
// Extrair body se disponível
|
||||
let body: string | undefined;
|
||||
try {
|
||||
body = await request.text();
|
||||
} catch {
|
||||
// Ignorar erros ao ler body
|
||||
}
|
||||
|
||||
// Analisar requisição para detectar ataques
|
||||
const resultado = await ctx.runMutation(api.security.analisarRequisicaoHTTP, {
|
||||
url: url.pathname + url.search,
|
||||
method,
|
||||
headers,
|
||||
body,
|
||||
queryParams,
|
||||
ipOrigem,
|
||||
userAgent: request.headers.get('user-agent') ?? undefined
|
||||
});
|
||||
|
||||
return new Response(JSON.stringify(resultado), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
// Seed de rate limit para ambiente de desenvolvimento
|
||||
http.route({
|
||||
path: "/security/rate-limit/seed-dev",
|
||||
method: "POST",
|
||||
handler: httpAction(async (ctx) => {
|
||||
const resultado = await ctx.runMutation(api.security.seedRateLimitDev, {});
|
||||
return new Response(JSON.stringify(resultado), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
authComponent.registerRoutes(http, createAuth);
|
||||
|
||||
export default http;
|
||||
|
||||
@@ -65,6 +65,38 @@ export async function registrarLogin(
|
||||
sistema,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
// Detecção automática de brute force após login falho
|
||||
// Verificar se há múltiplas tentativas falhas do mesmo IP
|
||||
if (!dados.sucesso && ipAddressValidado) {
|
||||
const minutosAtras = 15;
|
||||
const dataLimite = Date.now() - minutosAtras * 60 * 1000;
|
||||
|
||||
// Contar tentativas falhas recentes do mesmo IP
|
||||
const tentativasFalhas = await ctx.db
|
||||
.query("logsLogin")
|
||||
.withIndex("by_ip", (q) => q.eq("ipAddress", ipAddressValidado))
|
||||
.filter((q) =>
|
||||
q.gte(q.field("timestamp"), dataLimite) &&
|
||||
q.eq(q.field("sucesso"), false)
|
||||
)
|
||||
.collect();
|
||||
|
||||
// Se houver 5 ou mais tentativas falhas, registrar evento de segurança
|
||||
if (tentativasFalhas.length >= 5) {
|
||||
// Importar função de segurança dinamicamente para evitar dependência circular
|
||||
const { internal } = await import("./_generated/api");
|
||||
try {
|
||||
await ctx.scheduler.runAfter(0, internal.security.detectarBruteForce, {
|
||||
ipAddress: ipAddressValidado,
|
||||
janelaMinutos: minutosAtras
|
||||
});
|
||||
} catch (error) {
|
||||
// Log erro mas não bloqueia o registro de login
|
||||
console.error("Erro ao agendar detecção de brute force:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers para extrair informações do userAgent
|
||||
|
||||
@@ -15,6 +15,10 @@ export const ataqueCiberneticoTipo = v.union(
|
||||
v.literal("credential_stuffing"),
|
||||
v.literal("sql_injection"),
|
||||
v.literal("xss"),
|
||||
v.literal("path_traversal"),
|
||||
v.literal("command_injection"),
|
||||
v.literal("nosql_injection"),
|
||||
v.literal("xxe"),
|
||||
v.literal("man_in_the_middle"),
|
||||
v.literal("ddos"),
|
||||
v.literal("engenharia_social"),
|
||||
@@ -1261,4 +1265,39 @@ export default defineSchema({
|
||||
.index("by_status", ["status"])
|
||||
.index("by_solicitante", ["solicitanteId", "status"])
|
||||
.index("by_criado_em", ["criadoEm"]),
|
||||
|
||||
rateLimitConfig: defineTable({
|
||||
nome: v.string(),
|
||||
tipo: v.union(
|
||||
v.literal("ip"),
|
||||
v.literal("usuario"),
|
||||
v.literal("endpoint"),
|
||||
v.literal("global")
|
||||
),
|
||||
identificador: v.optional(v.string()),
|
||||
limite: v.number(),
|
||||
janelaSegundos: v.number(),
|
||||
estrategia: v.union(
|
||||
v.literal("fixed_window"),
|
||||
v.literal("sliding_window"),
|
||||
v.literal("token_bucket")
|
||||
),
|
||||
acaoExcedido: v.union(
|
||||
v.literal("bloquear"),
|
||||
v.literal("throttle"),
|
||||
v.literal("alertar")
|
||||
),
|
||||
bloqueioTemporarioSegundos: v.optional(v.number()),
|
||||
ativo: v.boolean(),
|
||||
prioridade: v.number(),
|
||||
criadoPor: v.id("usuarios"),
|
||||
atualizadoPor: v.optional(v.id("usuarios")),
|
||||
criadoEm: v.number(),
|
||||
atualizadoEm: v.number(),
|
||||
notas: v.optional(v.string()),
|
||||
tags: v.optional(v.array(v.string()))
|
||||
})
|
||||
.index("by_tipo_identificador", ["tipo", "identificador"])
|
||||
.index("by_ativo", ["ativo"])
|
||||
.index("by_prioridade", ["prioridade"])
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,6 +23,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@convex-dev/better-auth": "^0.9.7",
|
||||
"@convex-dev/rate-limiter": "^0.3.0",
|
||||
"@dicebear/avataaars": "^9.2.4",
|
||||
"better-auth": "catalog:",
|
||||
"convex": "catalog:",
|
||||
|
||||
Reference in New Issue
Block a user