feat: add marked library for markdown parsing and enhance documentation handling with new cron job for scheduled checks

This commit is contained in:
2025-12-06 20:43:41 -03:00
parent f3b4721119
commit 0ec12721ba
17 changed files with 3033 additions and 4 deletions

View File

@@ -1804,7 +1804,7 @@ export const enviarMensagemChatSistema = internalMutation({
conteudo: args.mensagem,
conteudoBusca: args.mensagem.toLowerCase(),
tipo: 'texto',
criadaEm: Date.now()
enviadaEm: Date.now()
});
// Atualizar última mensagem da conversa
@@ -1831,6 +1831,99 @@ export const enviarMensagemChatSistema = internalMutation({
}
});
/**
* Notificar quando rate limit é excedido
*/
export const notificarRateLimitExcedido = internalMutation({
args: {
configId: v.id('rateLimitConfig'),
tipo: v.union(v.literal('ip'), v.literal('usuario'), v.literal('endpoint'), v.literal('global')),
identificador: v.string(),
endpoint: v.string(),
acaoExcedido: v.union(v.literal('bloquear'), v.literal('throttle'), v.literal('alertar')),
limite: v.number(),
janelaSegundos: v.number()
},
returns: v.null(),
handler: async (ctx, args) => {
const config = await ctx.db.get(args.configId);
if (!config) return null;
// Buscar usuários TI para notificar
const rolesTi = await ctx.db
.query('roles')
.withIndex('by_nivel', (q) => q.lte('nivel', 1))
.collect();
const usuariosNotificados: Id<'usuarios'>[] = [];
for (const role of rolesTi) {
const membros = await ctx.db
.query('usuarios')
.withIndex('by_role', (q) => q.eq('roleId', role._id))
.collect();
for (const usuario of membros) {
usuariosNotificados.push(usuario._id);
}
}
// Criar notificações para usuários TI
const tipoAcao = args.acaoExcedido === 'bloquear' ? 'bloqueado' : args.acaoExcedido === 'alertar' ? 'alertado' : 'throttled';
const emoji = args.acaoExcedido === 'bloquear' ? '🚫' : '⚠️';
const titulo = `${emoji} Rate Limit ${tipoAcao === 'bloqueado' ? 'Bloqueado' : tipoAcao === 'alertado' ? 'Alertado' : 'Throttled'}`;
const descricao = `${args.tipo.toUpperCase()}: ${args.identificador} excedeu o limite de ${args.limite} requisições em ${args.janelaSegundos}s no endpoint ${args.endpoint}`;
for (const usuarioId of usuariosNotificados) {
await ctx.db.insert('notificacoes', {
usuarioId,
tipo: 'alerta_seguranca',
conversaId: undefined,
mensagemId: undefined,
remetenteId: undefined,
titulo,
descricao,
lida: false,
criadaEm: Date.now()
});
}
// Criar evento de segurança se foi bloqueado
if (args.acaoExcedido === 'bloquear') {
// Determinar tipo de ataque baseado no contexto
let tipoAtaque: AtaqueCiberneticoTipo = 'brute_force';
if (args.tipo === 'ip') {
tipoAtaque = 'ddos';
} else if (args.tipo === 'usuario') {
tipoAtaque = 'brute_force';
}
// Criar evento de segurança
const eventoId = await ctx.db.insert('securityEvents', {
referencia: `rate_limit_${args.tipo}_${args.identificador}_${Date.now()}`,
timestamp: Date.now(),
tipoAtaque,
severidade: 'alto',
status: 'detectado',
descricao: `Rate limit bloqueado: ${args.identificador} excedeu ${args.limite} requisições em ${args.janelaSegundos}s`,
origemIp: args.tipo === 'ip' ? args.identificador : undefined,
tags: ['rate_limit', 'bloqueio_automatico'],
atualizadoEm: Date.now()
});
// Disparar alertas se configurado
ctx.scheduler
.runAfter(0, internal.security.dispararAlertasInternos, {
eventoId
})
.catch((error) => {
console.error('Erro ao agendar alertas de rate limit:', error);
});
}
return null;
}
});
export const expirarBloqueiosIpAutomaticos = internalMutation({
args: {},
returns: v.null(),
@@ -1961,6 +2054,24 @@ async function aplicarRateLimit(
if (!result.ok) {
const retryAfter = result.retryAfter ?? periodo;
// Criar notificações e eventos quando rate limit é excedido
// Usar scheduler para não bloquear a requisição
if ('runMutation' in ctx) {
ctx.scheduler
.runAfter(0, internal.security.notificarRateLimitExcedido, {
configId: config._id,
tipo,
identificador,
endpoint: endpoint ?? 'default',
acaoExcedido: config.acaoExcedido,
limite: config.limite,
janelaSegundos: config.janelaSegundos
})
.catch((error) => {
console.error('Erro ao agendar notificação de rate limit:', error);
});
}
if (config.acaoExcedido === 'bloquear') {
return {
permitido: false,