diff --git a/apps/web/src/hooks.server.ts b/apps/web/src/hooks.server.ts index a04f98e..d9ee317 100644 --- a/apps/web/src/hooks.server.ts +++ b/apps/web/src/hooks.server.ts @@ -6,6 +6,92 @@ import { api } from '@sgse-app/backend/convex/_generated/api'; export const handle: Handle = async ({ event, resolve }) => { event.locals.token = await getToken(createAuth, event.cookies); + // Enforcement para endpoints sensíveis (antes de chegar nas rotas) + // - Foco: /api/auth/* (login, logout, etc.) + // - Aplica blacklist + rate limit configuráveis via Convex + const pathname = event.url.pathname; + if (pathname.startsWith('/api/auth/')) { + const token = event.locals.token; + const client = createConvexHttpClient({ token: token || undefined }); + + // Preferir X-Forwarded-For quando existir (proxy), senão fallback do adapter + const forwardedFor = event.request.headers.get('x-forwarded-for'); + const ip = + forwardedFor?.split(',')[0]?.trim() || + event.request.headers.get('x-real-ip') || + event.getClientAddress(); + + try { + // 1) Enforcement básico (blacklist + rate limit) + const enforcement = await client.mutation(api.security.enforceRequest, { + ip, + path: pathname, + method: event.request.method + }); + + if (!enforcement.allowed) { + const headers = new Headers({ 'Content-Type': 'application/json' }); + if (enforcement.retryAfterMs) { + headers.set('Retry-After', String(Math.ceil(enforcement.retryAfterMs / 1000))); + } + return new Response(JSON.stringify(enforcement), { status: enforcement.status, headers }); + } + + // 2) Análise de ataques e bloqueio automático + // Extrair dados da requisição para análise + const headers: Record = {}; + event.request.headers.forEach((value, key) => { + headers[key] = value; + }); + + const queryParams: Record = {}; + event.url.searchParams.forEach((value, key) => { + queryParams[key] = value; + }); + + let body: string | undefined; + try { + // Tentar ler body apenas se for POST/PUT/PATCH + if (['POST', 'PUT', 'PATCH'].includes(event.request.method)) { + const clonedRequest = event.request.clone(); + body = await clonedRequest.text(); + } + } catch { + // Ignorar erros ao ler body + } + + const analise = await client.mutation(api.security.analisarRequisicaoHTTP, { + url: pathname + event.url.search, + method: event.request.method, + headers, + body, + queryParams, + ipOrigem: ip, + userAgent: event.request.headers.get('user-agent') ?? undefined + }); + + // Se ataque detectado e bloqueio automático aplicado, retornar 403 + if (analise.ataqueDetectado && analise.bloqueadoAutomatico) { + return new Response( + JSON.stringify({ + error: 'Acesso negado', + reason: 'ataque_detectado', + tipoAtaque: analise.tipoAtaque, + severidade: analise.severidade + }), + { + status: 403, + headers: { 'Content-Type': 'application/json' } + } + ); + } + } catch (err) { + // Se o enforcement falhar, não bloquear login (fail-open), + // mas registrar erro para observabilidade via handleError (se ocorrer) + console.error('❌ Falha no enforcement de segurança:', err); + } + } + return resolve(event); }; diff --git a/apps/web/src/lib/components/call/CallWindow.svelte b/apps/web/src/lib/components/call/CallWindow.svelte index b68873a..2e8f744 100644 --- a/apps/web/src/lib/components/call/CallWindow.svelte +++ b/apps/web/src/lib/components/call/CallWindow.svelte @@ -363,10 +363,51 @@ } try { - const config = configJitsi(); + const config = configJitsi; + if (!config) { + handleError( + 'Configuração Jitsi não encontrada', + 'Não foi possível obter a configuração do Jitsi. Verifique as configurações no painel de administração.' + ); + return; + } const { host, porta } = obterHostEPorta(config.domain); const protocol = config.useHttps ? 'https' : 'http'; + // Buscar token JWT se configurado + let jwtToken: string | null = null; + try { + const tokenResult = await client.action(api.configuracaoJitsi.gerarTokenJitsi, { + roomName, + conversaId, + chamadaId, + ambiente: config.ambiente || undefined + }); + + if (tokenResult.sucesso) { + jwtToken = tokenResult.token; + console.log('✅ Token JWT gerado com sucesso'); + } else { + console.warn('⚠️ Não foi possível gerar token JWT:', tokenResult.erro); + // Se JWT está configurado mas falhou, mostrar erro + if (tokenResult.erro?.includes('JWT Secret não configurado')) { + // JWT não está configurado, continuar sem token + console.log('ℹ️ JWT não configurado, continuando sem autenticação'); + } else { + // Outro erro (permissão, etc.) + handleError( + 'Erro ao gerar token de autenticação', + `Não foi possível gerar o token de autenticação: ${tokenResult.erro}. Verifique as configurações do Jitsi.` + ); + return; + } + } + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : String(error); + console.warn('⚠️ Erro ao buscar token JWT (continuando sem token):', errorMessage); + // Continuar sem token se não for crítico + } + // Configuração conforme documentação oficial do Jitsi Meet // https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-ljm-api/ const baseUrl = `${protocol}://${host}${porta && porta !== (config.useHttps ? 443 : 80) ? `:${porta}` : ''}`; @@ -415,10 +456,17 @@ baseUrl, serviceUrl: options.serviceUrl, muc: options.hosts?.muc, - focus: options.hosts?.focus + focus: options.hosts?.focus, + usandoJWT: jwtToken !== null }); - const connection = new JitsiMeetJS.JitsiConnection(null, null, options); + // Criar conexão com token JWT (se disponível) + // Segundo parâmetro é o token JWT, primeiro é appId (pode ser null se usar token) + const connection = new JitsiMeetJS.JitsiConnection( + jwtToken ? null : config.appId, // App ID (null se usar token) + jwtToken, // Token JWT + options + ); jitsiConnection = connection; setJitsiApi(connection); @@ -477,6 +525,54 @@ atualizarStatusConexao(false); const errorMsg = error instanceof Error ? error.message : String(error); + const errorStr = String(error).toLowerCase(); + + // Verificar se é erro relacionado a JWT + const isJWTError = + errorStr.includes('jwt') || + errorStr.includes('token') || + errorStr.includes('authentication') || + errorStr.includes('unauthorized') || + errorStr.includes('forbidden') || + errorMsg.includes('401') || + errorMsg.includes('403'); + + if (isJWTError) { + reconectando = false; + handleError( + 'Erro de autenticação JWT', + `Falha na autenticação com o servidor Jitsi. Isso pode ocorrer se:\n\n` + + `• O token JWT está expirado ou inválido\n` + + `• O JWT Secret está incorreto nas configurações\n` + + `• Você não tem permissão para acessar esta sala\n\n` + + `Verifique as configurações do Jitsi no painel de administração.\n\nErro: ${errorMsg}`, + false + ); + return; + } + + // Verificar se é erro de servidor inacessível + const isServerError = + errorStr.includes('network') || + errorStr.includes('timeout') || + errorStr.includes('connection refused') || + errorStr.includes('dns') || + errorMsg.includes('ECONNREFUSED') || + errorMsg.includes('ENOTFOUND'); + + if (isServerError) { + reconectando = false; + handleError( + 'Servidor Jitsi inacessível', + `Não foi possível conectar ao servidor Jitsi. Verifique:\n\n` + + `• Se o domínio está correto nas configurações\n` + + `• Se o servidor está online e acessível\n` + + `• Se há problemas de firewall ou rede\n\n` + + `Erro: ${errorMsg}`, + false + ); + return; + } // Tentar reconectar se ainda houver tentativas if (tentativasReconexao < MAX_TENTATIVAS_RECONEXAO) { diff --git a/apps/web/src/lib/components/chat/ChatWindow.svelte b/apps/web/src/lib/components/chat/ChatWindow.svelte index df1108f..1df41c9 100644 --- a/apps/web/src/lib/components/chat/ChatWindow.svelte +++ b/apps/web/src/lib/components/chat/ChatWindow.svelte @@ -171,6 +171,24 @@ return; } + // Verificar se Jitsi está configurado + try { + const configJitsi = await client.query(api.configuracaoJitsi.obterConfigJitsi, {}); + if (!configJitsi || !configJitsi.ativo) { + errorTitle = 'Jitsi não configurado'; + errorMessage = + 'O sistema de videochamadas não está configurado. Entre em contato com o administrador do sistema para configurar o Jitsi.'; + errorInstructions = + 'Um administrador precisa configurar o servidor Jitsi no painel de administração antes que as chamadas possam ser iniciadas.'; + errorDetails = undefined; + showErrorModal = true; + return; + } + } catch (error: unknown) { + console.error('Erro ao verificar configuração Jitsi:', error); + // Continuar mesmo se houver erro na verificação (pode ser problema temporário) + } + try { iniciandoChamada = true; const chamadaId = await client.mutation(api.chamadas.criarChamada, { diff --git a/apps/web/src/lib/components/ti/AttackCapabilitiesSection.svelte b/apps/web/src/lib/components/ti/AttackCapabilitiesSection.svelte new file mode 100644 index 0000000..c64f4d3 --- /dev/null +++ b/apps/web/src/lib/components/ti/AttackCapabilitiesSection.svelte @@ -0,0 +1,152 @@ + + +
+ +
+
+
+ +
+
Total de Tipos
+
{stats.total}
+
Tipos de ataques monitorados
+
+ +
+
+ +
+
Com Bloqueio Automático
+
{stats.withAutoBlock}
+
Tipos com mitigação ativa
+
+ +
+
+ +
+
Taxa de Cobertura
+
+ {Math.round((stats.withAutoBlock / stats.total) * 100)}% +
+
Cobertura de mitigação ativa
+
+
+ + +
+

Capacidades por Grupo de Ataque

+ + {#each grupos as grupo} + {@const capabilities = getCapabilitiesByGroup(grupo.id as any)} +
+
+

{grupo.nome}

+

+ {capabilities.length} tipo{capabilities.length !== 1 ? 's' : ''} de ataque +

+ +
+ {#each capabilities as cap} +
abrirModal(cap.tipo)} + > +
+
+
{cap.nome}
+ +
+ +
+ + {severidadeLabels[cap.severidadePadrao]} + + {#if cap.deteccao.tempoReal} + Tempo Real + {/if} +
+ +
+ {#if cap.acoes.bloqueioAutomatico} + 🔒 + {/if} + {#if cap.acoes.blacklist} + 📋 + {/if} + {#if cap.acoes.rateLimit} + + {/if} + {#if cap.acoes.alerta} + 📢 + {/if} +
+ +

+ {cap.descricao} +

+
+
+ {/each} +
+
+
+ {/each} +
+
+ + diff --git a/apps/web/src/lib/components/ti/AttackDetailsModal.svelte b/apps/web/src/lib/components/ti/AttackDetailsModal.svelte new file mode 100644 index 0000000..bd315a3 --- /dev/null +++ b/apps/web/src/lib/components/ti/AttackDetailsModal.svelte @@ -0,0 +1,187 @@ + + +{#if aberto && capability} + +{/if} diff --git a/apps/web/src/lib/components/ti/AttackTypeTooltip.svelte b/apps/web/src/lib/components/ti/AttackTypeTooltip.svelte new file mode 100644 index 0000000..e32462b --- /dev/null +++ b/apps/web/src/lib/components/ti/AttackTypeTooltip.svelte @@ -0,0 +1,80 @@ + + +
+ +
+ + diff --git a/apps/web/src/lib/components/ti/CybersecurityWizcard.svelte b/apps/web/src/lib/components/ti/CybersecurityWizcard.svelte index 96b8871..beec942 100644 --- a/apps/web/src/lib/components/ti/CybersecurityWizcard.svelte +++ b/apps/web/src/lib/components/ti/CybersecurityWizcard.svelte @@ -10,6 +10,10 @@ import autoTable from 'jspdf-autotable'; import { browser } from '$app/environment'; import { authStore } from '$lib/stores/auth.svelte'; + import AttackDetailsModal from './AttackDetailsModal.svelte'; + import AttackCapabilitiesSection from './AttackCapabilitiesSection.svelte'; + import { attackCapabilities } from '$lib/data/attackCapabilities'; + import { Info } from 'lucide-svelte'; const client = useConvexClient(); const visaoCamadas = useQuery(api.security.obterVisaoCamadas, { @@ -136,8 +140,7 @@ severidadeMin: alertSeveridadeMin, tiposAtaque: alertTiposAtaque as AtaqueCiberneticoTipo[], reenvioMin: alertReenvioMin, - templateCodigo: alertTemplate, // Incluir template selecionado - criadoPor: obterUsuarioId() + templateCodigo: alertTemplate // Incluir template selecionado }); feedback = { tipo: 'success', @@ -226,7 +229,6 @@ xxe: 'XXE', man_in_the_middle: 'MITM', ddos: 'DDoS', - engenharia_social: 'Engenharia Social', cve_exploit: 'Exploração de CVE', apt: 'APT', zero_day: 'Zero-Day', @@ -276,6 +278,13 @@ let feedback = $state<{ tipo: 'success' | 'error'; mensagem: string } | null>(null); let ultimaReferenciaCritica: Id<'securityEvents'> | null = null; let audioCtx: AudioContext | null = null; + let confirmDialog: HTMLDialogElement | null = null; + let confirmTitulo = $state('Confirmar ação'); + let confirmMensagem = $state(''); + let confirmConfirmarLabel = $state('Confirmar'); + let confirmCancelLabel = $state('Cancelar'); + let confirmBusy = $state(false); + let confirmAcao = $state Promise)>(null); // Rate Limiting let mostrarRateLimitConfig = $state(false); @@ -293,6 +302,113 @@ let rateLimitNotas = $state(''); let rateLimitEditando = $state | null>(null); + // Bloqueio Automático + let mostrarAutoBlockConfig = $state(false); + let autoBlockTipoAtaque = $state('sql_injection'); + let autoBlockBloquearAutomatico = $state(true); + let autoBlockSeveridadeMinima = $state('alto'); + let autoBlockDuracaoSegundos = $state(undefined); + let autoBlockAtivo = $state(true); + let autoBlockEditando = $state | null>(null); + + async function salvarAutoBlockConfig() { + try { + if (autoBlockEditando) { + await client.mutation(api.security.atualizarAutoBlockConfig, { + configId: autoBlockEditando, + bloquearAutomatico: autoBlockBloquearAutomatico, + severidadeMinima: autoBlockSeveridadeMinima, + duracaoBloqueioSegundos: autoBlockDuracaoSegundos, + ativo: autoBlockAtivo + }); + feedback = { tipo: 'success', mensagem: 'Configuração atualizada com sucesso.' }; + } else { + await client.mutation(api.security.criarAutoBlockConfig, { + tipoAtaque: autoBlockTipoAtaque, + bloquearAutomatico: autoBlockBloquearAutomatico, + severidadeMinima: autoBlockSeveridadeMinima, + duracaoBloqueioSegundos: autoBlockDuracaoSegundos, + ativo: autoBlockAtivo + }); + feedback = { tipo: 'success', mensagem: 'Configuração criada com sucesso.' }; + } + resetarFormAutoBlock(); + mostrarAutoBlockConfig = false; + } catch (erro: unknown) { + feedback = { tipo: 'error', mensagem: mensagemErro(erro) }; + } + } + + function resetarFormAutoBlock() { + autoBlockTipoAtaque = 'sql_injection'; + autoBlockBloquearAutomatico = true; + autoBlockSeveridadeMinima = 'alto'; + autoBlockDuracaoSegundos = undefined; + autoBlockAtivo = true; + autoBlockEditando = null; + } + + async function deletarAutoBlockConfig(configId: Id<'autoBlockConfig'>) { + confirmTitulo = 'Confirmar exclusão'; + confirmMensagem = 'Tem certeza que deseja excluir esta configuração de bloqueio automático?'; + confirmConfirmarLabel = 'Excluir'; + confirmCancelLabel = 'Cancelar'; + confirmAcao = async () => { + try { + await client.mutation(api.security.deletarAutoBlockConfig, { configId }); + feedback = { tipo: 'success', mensagem: 'Configuração excluída com sucesso.' }; + } catch (erro: unknown) { + feedback = { tipo: 'error', mensagem: mensagemErro(erro) }; + } + }; + confirmDialog?.showModal(); + } + + function editarAutoBlockConfig(config: { + _id: Id<'autoBlockConfig'>; + tipoAtaque: AtaqueCiberneticoTipo; + bloquearAutomatico: boolean; + severidadeMinima: SeveridadeSeguranca; + duracaoBloqueioSegundos?: number; + ativo: boolean; + }) { + autoBlockTipoAtaque = config.tipoAtaque; + autoBlockBloquearAutomatico = config.bloquearAutomatico; + autoBlockSeveridadeMinima = config.severidadeMinima; + autoBlockDuracaoSegundos = config.duracaoBloqueioSegundos; + autoBlockAtivo = config.ativo; + autoBlockEditando = config._id; + mostrarAutoBlockConfig = true; + } + + // Capacidades e detalhes de ataques + let mostrarCapacidades = $state(false); + let modalAtaqueAberto = $state(false); + let tipoAtaqueSelecionado = $state(null); + const autoBlockConfigs = useQuery(api.security.listarAutoBlockConfigs, { + ativo: true, + limit: 100 + }); + + function abrirModalAtaque(tipo: AtaqueCiberneticoTipo) { + tipoAtaqueSelecionado = tipo; + modalAtaqueAberto = true; + } + + function fecharModalAtaque() { + modalAtaqueAberto = false; + tipoAtaqueSelecionado = null; + } + + // Verificar se um tipo de ataque tem bloqueio automático ativo + function temBloqueioAutomatico(tipo: AtaqueCiberneticoTipo): boolean { + return ( + autoBlockConfigs?.data?.some( + (config) => config.tipoAtaque === tipo && config.bloquearAutomatico && config.ativo + ) ?? false + ); + } + let series = $derived.by(() => visaoCamadas?.data?.series ?? []); let totais = $derived.by(() => visaoCamadas?.data?.totais ?? null); let eventosFiltrados = $derived.by(() => { @@ -453,9 +569,33 @@ if (!browser) { return; } + tocarAlertaSonoro().catch(() => { + // Ignorar falhas de autoplay policy + }); + }); + + async function prepararAudioContext() { + if (!browser) return; + try { + if (!audioCtx) { + audioCtx = new AudioContext(); + } + if (audioCtx.state === 'suspended') { + await audioCtx.resume(); + } + } catch (erro) { + console.warn('Falha ao preparar áudio:', erro); + } + } + + async function tocarAlertaSonoro() { + if (!browser) return; if (!audioCtx) { audioCtx = new AudioContext(); } + if (audioCtx.state === 'suspended') { + await audioCtx.resume(); + } const oscillator = audioCtx.createOscillator(); const gain = audioCtx.createGain(); oscillator.type = 'triangle'; @@ -463,10 +603,42 @@ gain.gain.value = 0.08; oscillator.connect(gain).connect(audioCtx.destination); oscillator.start(); - setTimeout(() => { - oscillator.stop(); - }, 300); - }); + setTimeout(() => oscillator.stop(), 300); + } + + function abrirConfirmacao(params: { + titulo: string; + mensagem: string; + confirmarLabel?: string; + cancelarLabel?: string; + acao: () => Promise; + }) { + confirmTitulo = params.titulo; + confirmMensagem = params.mensagem; + confirmConfirmarLabel = params.confirmarLabel ?? 'Confirmar'; + confirmCancelLabel = params.cancelarLabel ?? 'Cancelar'; + confirmAcao = params.acao; + confirmBusy = false; + confirmDialog?.showModal(); + } + + async function confirmarAcao() { + if (!confirmAcao) return; + try { + confirmBusy = true; + await confirmAcao(); + confirmDialog?.close(); + } finally { + confirmBusy = false; + confirmAcao = null; + } + } + + function cancelarConfirmacao() { + if (confirmBusy) return; + confirmDialog?.close(); + confirmAcao = null; + } function mensagemErro(erro: unknown): string { if (erro instanceof Error) return erro.message; @@ -498,7 +670,6 @@ } try { await client.mutation(api.security.atualizarReputacaoIndicador, { - usuarioId: obterUsuarioId(), indicador: ipAlvo, categoria: 'ip', acao: acaoSelecionada, @@ -512,7 +683,6 @@ eventoId, tipo: acaoSelecionada === 'forcar_blacklist' ? 'block_ip' : 'unblock_ip', origem: 'manual', - executadoPor: obterUsuarioId(), detalhes: comentarioManual || 'Ação executada via Wizcard', resultado: 'registrado', relacionadoA: undefined @@ -537,7 +707,6 @@ e.preventDefault(); try { await client.mutation(api.security.configurarRegraPorta, { - usuarioId: obterUsuarioId(), porta, protocolo, acao, @@ -944,33 +1113,38 @@ doc.save(nomeArquivo); } catch (error) { console.error('Erro ao gerar PDF:', error); - alert('Erro ao gerar PDF do relatório. Tente novamente.'); + feedback = { tipo: 'error', mensagem: 'Erro ao gerar PDF do relatório. Tente novamente.' }; } } async function excluirRelatorio(relatorioId: Id<'reportRequests'>) { - if (!confirm('Excluir este relatório? Esta ação não pode ser desfeita.')) return; - try { - const resp = await client.mutation(api.security.deletarRelatorio, { - relatorioId - }); - if (resp?.success) { - feedback = { tipo: 'success', mensagem: 'Relatório excluído.' }; - } else { - feedback = { - tipo: 'error', - mensagem: 'Não foi possível excluir o relatório.' - }; + abrirConfirmacao({ + titulo: 'Excluir relatório?', + mensagem: 'Esta ação não pode ser desfeita.', + confirmarLabel: 'Excluir', + acao: async () => { + try { + const resp = await client.mutation(api.security.deletarRelatorio, { + relatorioId + }); + if (resp?.success) { + feedback = { tipo: 'success', mensagem: 'Relatório excluído.' }; + } else { + feedback = { + tipo: 'error', + mensagem: 'Não foi possível excluir o relatório.' + }; + } + } catch (erro: unknown) { + feedback = { tipo: 'error', mensagem: mensagemErro(erro) }; + } } - } catch (erro: unknown) { - feedback = { tipo: 'error', mensagem: mensagemErro(erro) }; - } + }); } async function gerarRelatorioAvancado(e: SubmitEvent) { e.preventDefault(); try { await client.mutation(api.security.solicitarRelatorioSeguranca, { - solicitanteId: obterUsuarioId(), filtros: { dataInicio: parseDatetime(relatorioInicio), dataFim: parseDatetime(relatorioFim), @@ -1028,7 +1202,6 @@ if (rateLimitEditando) { await client.mutation(api.security.atualizarConfigRateLimit, { configId: rateLimitEditando, - usuarioId: obterUsuarioId(), nome: rateLimitNome, limite: rateLimitLimite, janelaSegundos: rateLimitJanelaSegundos, @@ -1044,7 +1217,6 @@ }; } else { await client.mutation(api.security.criarConfigRateLimit, { - usuarioId: obterUsuarioId(), nome: rateLimitNome, tipo: rateLimitTipo, identificador: @@ -1070,26 +1242,30 @@ } async function deletarConfigRateLimit(configId: Id<'rateLimitConfig'>) { - if (!confirm('Tem certeza que deseja deletar esta configuração de rate limit?')) return; - try { - await client.mutation(api.security.deletarConfigRateLimit, { - configId, - usuarioId: obterUsuarioId() - }); - feedback = { - tipo: 'success', - mensagem: 'Configuração de rate limit deletada com sucesso.' - }; - } catch (erro: unknown) { - feedback = { tipo: 'error', mensagem: mensagemErro(erro) }; - } + abrirConfirmacao({ + titulo: 'Deletar configuração de rate limit?', + mensagem: 'Tem certeza que deseja deletar esta configuração?', + confirmarLabel: 'Deletar', + acao: async () => { + try { + await client.mutation(api.security.deletarConfigRateLimit, { + configId + }); + feedback = { + tipo: 'success', + mensagem: 'Configuração de rate limit deletada com sucesso.' + }; + } catch (erro: unknown) { + feedback = { tipo: 'error', mensagem: mensagemErro(erro) }; + } + } + }); } async function toggleAtivoRateLimit(configId: Id<'rateLimitConfig'>, ativo: boolean) { try { await client.mutation(api.security.atualizarConfigRateLimit, { configId, - usuarioId: obterUsuarioId(), ativo: !ativo }); feedback = { @@ -1115,6 +1291,29 @@
+ + + + + {#if feedback}
@@ -1169,7 +1368,14 @@
@@ -1506,10 +1723,33 @@ {severityLabels[evento.severidade]} - {attackLabels[evento.tipoAtaque]} +
+ {attackLabels[evento.tipoAtaque]} + + {#if temBloqueioAutomatico(evento.tipoAtaque)} + + 🔒 + + {/if} +
{evento.status} + {#if evento.tags?.includes('bloqueio_automatico')} + + 🔒 Auto-Bloqueado + + {/if}
{formatarData(evento.timestamp)}
@@ -1553,7 +1793,6 @@ onclick={async () => { try { await client.mutation(api.security.solicitarRelatorioSeguranca, { - solicitanteId: obterUsuarioId(), filtros: { dataInicio: evento.timestamp - 15 * 60 * 1000, dataFim: evento.timestamp + 15 * 60 * 1000, @@ -1647,7 +1886,6 @@ onclick={async () => { try { await client.mutation(api.security.configurarRegraPorta, { - usuarioId: obterUsuarioId(), regraId: regra._id, porta: regra.porta, protocolo: regra.protocolo, @@ -1666,18 +1904,22 @@ @@ -2052,6 +2294,200 @@
+ +
+
+
+

Bloqueio Automático de Ataques

+

+ Configure bloqueio automático por tipo de ataque. Quando um ataque é detectado e atende aos + critérios, o IP será bloqueado automaticamente. +

+
+ +
+ + {#if mostrarAutoBlockConfig} +
{ e.preventDefault(); salvarAutoBlockConfig(); }} + > +
+ + +
+ + + + {#if autoBlockBloquearAutomatico} + + {/if} + + + +
+ + {#if autoBlockEditando} + + {/if} +
+
+ {/if} + +
+

Configurações Ativas

+ {#if autoBlockConfigs?.data && autoBlockConfigs.data.length > 0} +
+ + + + + + + + + + + + + {#each autoBlockConfigs.data as config (config._id)} + + + + + + + + + {/each} + +
Tipo de AtaqueSeveridade MínimaBloqueio AutoDuraçãoStatusAções
+
+ {attackLabels[config.tipoAtaque]} + +
+
+ {severityLabels[config.severidadeMinima]} + + {#if config.bloquearAutomatico} + Sim + {:else} + Não + {/if} + + {#if config.duracaoBloqueioSegundos} + {Math.floor(config.duracaoBloqueioSegundos / 60)} min + {:else} + Permanente + {/if} + + {#if config.ativo} + Ativo + {:else} + Inativo + {/if} + +
+ + +
+
+
+ {:else} +
+

Nenhuma configuração de bloqueio automático.

+

+ Configure bloqueio automático para tipos de ataques acima +

+
+ {/if} +
+
+
{/if}
+ + + {#if mostrarCapacidades} +
+
+

Capacidades de Detecção e Mitigação

+ +
+ +
+ {/if} + + +