feat: implement alert configuration and recent report features

- Added alert configuration management for email and chat notifications, allowing users to set preferences for severity levels, attack types, and notification channels.
- Introduced functionality to save, edit, and delete alert configurations, enhancing user control over security notifications.
- Implemented a new query to list recent security reports, providing users with quick access to the latest security incidents.
- Enhanced the backend schema to support alert configurations and recent report tracking, improving overall security management capabilities.
This commit is contained in:
2025-11-16 07:37:36 -03:00
parent 88983ea297
commit 70d405d98d
5 changed files with 650 additions and 37 deletions

View File

@@ -1244,6 +1244,63 @@ export const solicitarRelatorioSeguranca = mutation({
}
});
// Lista relatórios recentes para exibição no dashboard
export const listarRelatoriosRecentes = query({
args: {
limit: v.optional(v.number())
},
returns: v.array(
v.object({
_id: v.id('reportRequests'),
status: v.union(
v.literal('pendente'),
v.literal('processando'),
v.literal('concluido'),
v.literal('falhou')
),
criadoEm: v.number(),
concluidoEm: v.optional(v.number()),
observacoes: v.optional(v.string())
})
),
handler: async (ctx, args) => {
const max = Math.min(args.limit ?? 10, 50);
const rows = await ctx.db
.query('reportRequests')
.withIndex('by_criado_em', (q) => q.gte('criadoEm', 0))
.order('desc')
.take(max);
return rows.map((r) => ({
_id: r._id,
status: r.status,
criadoEm: r.criadoEm,
concluidoEm: r.concluidoEm,
observacoes: r.observacoes
}));
}
});
// Health check leve para o dashboard
export const healthStatus = query({
args: {},
returns: v.object({
ok: v.boolean(),
now: v.number(),
pendingReports: v.number()
}),
handler: async (ctx) => {
// Contar rapidamente quantos relatórios pendentes existem (limitado)
const pending = await ctx.db
.query('reportRequests')
.withIndex('by_status', (q) => q.eq('status', 'pendente'))
.take(1);
return {
ok: true,
now: Date.now(),
pendingReports: pending.length
};
}
});
export const processarRelatorioSegurancaInternal = internalMutation({
args: {
relatorioId: v.id('reportRequests')
@@ -1319,6 +1376,97 @@ export const processarRelatorioSegurancaInternal = internalMutation({
}
});
// ---------- Alertas (email/chat) ----------
export const listarAlertConfigs = query({
args: { limit: v.optional(v.number()) },
returns: v.array(
v.object({
_id: v.id('alertConfigs'),
nome: v.string(),
canais: v.object({ email: v.boolean(), chat: v.boolean() }),
emails: v.array(v.string()),
chatUsers: v.array(v.string()),
severidadeMin: severidadeValidator,
tiposAtaque: v.optional(v.array(ataqueValidator)),
reenvioMin: v.number(),
criadoEm: v.number(),
atualizadoEm: v.number()
})
),
handler: async (ctx, args) => {
const max = Math.min(args.limit ?? 100, 200);
const rows = await ctx.db
.query('alertConfigs')
.withIndex('by_criadoEm', (q) => q.gte('criadoEm', 0))
.order('desc')
.take(max);
return rows.map((r) => ({
_id: r._id,
nome: r.nome,
canais: r.canais,
emails: r.emails,
chatUsers: r.chatUsers,
severidadeMin: r.severidadeMin,
tiposAtaque: r.tiposAtaque,
reenvioMin: r.reenvioMin,
criadoEm: r.criadoEm,
atualizadoEm: r.atualizadoEm
}));
}
});
export const salvarAlertConfig = mutation({
args: {
configId: v.optional(v.id('alertConfigs')),
nome: v.string(),
canais: v.object({ email: v.boolean(), chat: v.boolean() }),
emails: v.array(v.string()),
chatUsers: v.array(v.string()),
severidadeMin: severidadeValidator,
tiposAtaque: v.optional(v.array(ataqueValidator)),
reenvioMin: v.number(),
criadoPor: v.id('usuarios')
},
returns: v.object({ _id: v.id('alertConfigs') }),
handler: async (ctx, args) => {
const agora = Date.now();
if (args.configId) {
await ctx.db.patch(args.configId, {
nome: args.nome,
canais: args.canais,
emails: args.emails,
chatUsers: args.chatUsers,
severidadeMin: args.severidadeMin,
tiposAtaque: args.tiposAtaque,
reenvioMin: args.reenvioMin,
atualizadoEm: agora
});
return { _id: args.configId };
}
const id = await ctx.db.insert('alertConfigs', {
nome: args.nome,
canais: args.canais,
emails: args.emails,
chatUsers: args.chatUsers,
severidadeMin: args.severidadeMin,
tiposAtaque: args.tiposAtaque,
reenvioMin: args.reenvioMin,
criadoPor: args.criadoPor,
criadoEm: agora,
atualizadoEm: agora
});
return { _id: id };
}
});
export const deletarAlertConfig = mutation({
args: { configId: v.id('alertConfigs') },
returns: v.null(),
handler: async (ctx, args) => {
await ctx.db.delete(args.configId);
return null;
}
});
export const dispararAlertasInternos = internalMutation({
args: {
eventoId: v.id('securityEvents')
@@ -1729,6 +1877,12 @@ export const analisarRequisicaoHTTP = mutation({
// Calcular severidade
const severidade = calcularSeveridade(tipoAtaque, undefined, undefined);
// Permitir que o chamador informe o destino/protocolo via query string em cenários de dev/teste
const destinoIp =
(args.queryParams && (args.queryParams['dst'] || args.queryParams['dest'] || args.queryParams['destino'])) ||
undefined;
const protocolo = (args.queryParams && (args.queryParams['proto'] as string)) || 'http';
// Registrar evento de segurança
const referencia = `http_${Date.now()}_${Math.random().toString(36).substring(7)}`;
const agora = Date.now();
@@ -1740,12 +1894,13 @@ export const analisarRequisicaoHTTP = mutation({
status: statusInicial(severidade),
descricao: `Ataque ${tipoAtaque} detectado na requisição HTTP ${args.method} ${args.url}`,
origemIp: args.ipOrigem,
protocolo: 'http',
protocolo,
transporte: 'tcp',
detectadoPor: 'analisador_http_automatico',
fingerprint: args.userAgent ? {
userAgent: args.userAgent
} : undefined,
destinoIp: destinoIp ?? undefined,
tags: ['detecção_automática', 'http', tipoAtaque],
atualizadoEm: agora
});
@@ -2166,7 +2321,7 @@ export const seedRateLimitDev = mutation({
nome: 'Bloqueio Login Dev',
tipo: 'endpoint',
identificador: 'api/auth/sign-in/email',
limite: 10,
limite: 5,
janelaSegundos: 20,
estrategia: 'token_bucket',
acaoExcedido: 'bloquear',