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:
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user