From b764c9310687552ed91cd948ccdfc8382c5d0b1a Mon Sep 17 00:00:00 2001 From: Rodrigo Martins Date: Fri, 11 Apr 2025 11:57:22 -0300 Subject: [PATCH] Fix IP in ProvaService.ts and Add PUBLIC_URL Signed-off-by: Rodrigo Martins --- .../provas/PublicVerificarProva.tsx | 22 + src/components/provas/VerificarProva.tsx | 482 ++++++++---------- src/server/app.ts | 6 + src/server/middleware/ipDetection.ts | 52 ++ src/server/routes/provas.ts | 86 +++- src/server/services/ProvaService.ts | 198 ++++++- src/server/services/ReportGenerator.ts | 134 ++++- 7 files changed, 693 insertions(+), 287 deletions(-) create mode 100644 src/server/middleware/ipDetection.ts diff --git a/src/components/provas/PublicVerificarProva.tsx b/src/components/provas/PublicVerificarProva.tsx index 1c50ff7..ff1df55 100644 --- a/src/components/provas/PublicVerificarProva.tsx +++ b/src/components/provas/PublicVerificarProva.tsx @@ -160,6 +160,28 @@ export function PublicVerificarProva() { JSON.parse(result.prova.timestamp).timestamp : result.prova.dataCriacao)} /> +
+
+
IP:
+
+ {result.verificacao?.ip || + result.prova.metadata?.connectionInfo?.forwardedIp || + (result.prova.metadata?.headers?.['x-forwarded-for'] ? + result.prova.metadata.headers['x-forwarded-for'].split(',')[0].trim() : + result.prova.metadata?.ipAddress) || + 'Não disponível'} +
+
+
+
Porta:
+
+ {result.verificacao?.porta || + result.prova.metadata?.clientPort || + result.prova.metadata?.connectionInfo?.socketPort || + 'Não disponível'} +
+
+
diff --git a/src/components/provas/VerificarProva.tsx b/src/components/provas/VerificarProva.tsx index fad8b02..735d69d 100644 --- a/src/components/provas/VerificarProva.tsx +++ b/src/components/provas/VerificarProva.tsx @@ -1,291 +1,247 @@ -import { useEffect, useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { useParams } from 'react-router-dom'; -import { Card } from '../../components/ui/Card'; -import { format, isValid } from 'date-fns'; -import ptBR from 'date-fns/locale/pt-BR'; +import axios from 'axios'; // Importar axios para fazer a requisição +import { format } from 'date-fns'; +import { ptBR } from 'date-fns/locale'; +import { + Container, + Box, + Typography, + CircularProgress, + Alert, + Paper, + Grid, + Divider, + Button +} from '@mui/material'; -interface ProvaVerificacao { - verified: boolean; - message: string; - prova?: { - fileName: string; - checksumSha256: string; - checksumSha512: string; - checksumMd5: string; - timestamp: string; - metadata: any; - dataCriacao: string; - dataValida: string; - }; - timestamp?: any; -} - -export function VerificarProva() { +// Componente para verificação de provas digitais +export const VerificarProva: React.FC = () => { const { hash } = useParams<{ hash: string }>(); - const [result, setResult] = useState(null); - const [error, setError] = useState(null); + const [prova, setProva] = useState(null); const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + // URL base da API - usar a mesma do ambiente atual + const API_BASE_URL = process.env.REACT_APP_API_URL || '/api'; useEffect(() => { - const fetchProva = async () => { + const fetchProvaDetails = async () => { try { setLoading(true); - const response = await fetch(`/api/provas/verificar/${hash}`); - if (!response.ok) { - throw new Error(response.statusText || 'Falha na verificação'); + // Obter informações de conexão do cliente para incluir nos logs + const connectionInfo = { + userAgent: navigator.userAgent, + language: navigator.language, + platform: navigator.platform, + screenWidth: window.screen.width, + screenHeight: window.screen.height, + timestamp: new Date().toISOString() + }; + + // Fazer a requisição para verificar a prova + const response = await axios.get(`${API_BASE_URL}/provas/verificar/${hash}`, { + headers: { + 'Content-Type': 'application/json' + } + }); + + // Também registrar a verificação separadamente + try { + await axios.post(`${API_BASE_URL}/provas/registrar-verificacao`, { + hash, + verificationContext: connectionInfo + }); + } catch (logError) { + console.warn('Erro ao registrar verificação:', logError); + // Não interromper o fluxo principal por erro no log } - const data = await response.json(); - console.log('Dados recebidos:', data); - setResult(data); + setProva(response.data); + setLoading(false); } catch (error) { - console.error('Erro ao verificar:', error); - setError('Não foi possível verificar a autenticidade do documento.'); - } finally { + console.error('Erro ao verificar prova:', error); + setError('Não foi possível verificar este documento. O hash pode ser inválido ou o documento não existe.'); setLoading(false); } }; - - if (hash) { - fetchProva(); - } - }, [hash]); - - // Função segura para formatar datas - const formatDate = (dateString: string | undefined | null) => { - if (!dateString) return 'Data não disponível'; + if (hash) { + fetchProvaDetails(); + } else { + setError('Hash de verificação não fornecido'); + setLoading(false); + } + }, [hash, API_BASE_URL]); + + // Renderização condicional com base no estado + if (loading) { + return ( + + + + + Verificando autenticidade do documento... + + + + ); + } + + if (error) { + return ( + + + + Verificação Falhou + {error} + + + + + + + ); + } + + // Formatação da data para exibição + const formatarData = (dataString: string) => { try { - const date = new Date(dateString); - - if (!isValid(date)) { - console.warn('Data inválida:', dateString); - return 'Data inválida'; - } - - return format(date, 'dd/MM/yyyy HH:mm:ss', { locale: ptBR }); + const data = new Date(dataString); + return format(data, 'dd/MM/yyyy HH:mm:ss', { locale: ptBR }); } catch (error) { - console.error('Erro ao formatar data:', error); - return 'Erro na formatação da data'; + return 'Data inválida'; } }; - if (loading) { - return ( -
-
- -
-
-
-
-
-

Carregando informações...

-
-
-
-
- ); - } - - if (error || !result) { - return ( -
-
- -
-

Verificação Falhou

-

{error || 'Não foi possível verificar este documento.'}

-
-
-
-
- ); - } - return ( -
-
- -
-
- {result.verified ? ( - <> -

Documento Verificado

-

- Este documento foi validado digitalmente em nossa plataforma. -

- - ) : ( - <> -

Verificação com Problemas

-

- {result.message} -

- - )} -
+ + + {prova?.verified ? ( + + Documento Verificado com Sucesso + + Este documento foi validado e sua integridade foi confirmada. + + + ) : ( + + Verificação Inconclusiva + + {prova?.message || 'Não foi possível confirmar a integridade deste documento.'} + + + )} + - {result.prova && ( -
-
- - - -
+ {prova && ( + + + + Detalhes do Documento + + + + + + + Nome do arquivo: + + + {prova.prova?.fileName || 'Não disponível'} + + + + + + Tipo: + + + {prova.prova?.mimeType || 'Não disponível'} + + + + + + Hash SHA256: + + + {prova.prova?.checksumSha256 || 'Não disponível'} + + + + + + Data de registro: + + + {prova.timestamp?.timestamp ? + formatarData(prova.timestamp.timestamp) : + 'Não disponível'} + + + + + + Data de verificação: + + + {formatarData(new Date().toISOString())} + + -
- - - -
+ + + IP: + + + {prova.verificacao?.ip || prova.prova?.metadata?.ipAddress || 'Não disponível'} + + -
- {result.timestamp ? ( - - ) : ( - - )} -
- - {result.prova.metadata && ( -
- - - - - - - {result.prova.metadata.timezone && ( - - )} - {(result.prova.metadata.latitude !== undefined || result.prova.metadata.longitude !== undefined) && ( - - )} - {(result.prova.metadata.file_specific?.modified || result.prova.metadata.modification_times) && ( - - )} - {(result.prova.metadata.file_specific?.modifiedTimestamp || result.prova.metadata.modification_times) && ( - - )} -
- )} -
+ + + Porta: + + + {prova.verificacao?.porta || prova.prova?.metadata?.clientPort || 'Não disponível'} + + + + + {prova.prova?.reportPath && ( + + + )} -
-
-
-
+ + + )} + ); -} +}; -function Section({ title, children }: { title: string; children: React.ReactNode }) { - return ( -
-

{title}

-
- {children} -
-
- ); -} - -function Info({ - label, - value, - monospace = false, - subtext -}: { - label: string; - value: string; - monospace?: boolean; - subtext?: string; -}) { - return ( -
-
{label}
-
{value}
- {subtext &&
{subtext}
} -
- ); -} +export default VerificarProva; diff --git a/src/server/app.ts b/src/server/app.ts index b90e602..bd1c7bb 100644 --- a/src/server/app.ts +++ b/src/server/app.ts @@ -14,6 +14,11 @@ dotenv.config(); const app = express(); const prisma = new PrismaClient(); +// Configurar para confiar em proxies, permitindo a leitura de X-Forwarded-For +// Isso é necessário se sua aplicação estiver atrás de um balanceador de carga, +// proxy reverso ou Cloudflare +app.set('trust proxy', true); + app.use(cors()); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); @@ -24,6 +29,7 @@ app.use('/api/indices', indicesMonetariosRoutes); app.use('/api/pensao-alimenticia', pensaoAlimenticiaRoutes); app.use(express.static(path.join(__dirname, 'public'))); +app.set('trust proxy', true); // Importante para obter o IP real em ambientes com proxy const PORT = process.env.PORT || 5000; diff --git a/src/server/middleware/ipDetection.ts b/src/server/middleware/ipDetection.ts new file mode 100644 index 0000000..01557ca --- /dev/null +++ b/src/server/middleware/ipDetection.ts @@ -0,0 +1,52 @@ +import { Request, Response, NextFunction } from 'express'; + +export const ipDetectionMiddleware = (req: Request, res: Response, next: NextFunction) => { + // Extrair o endereço IP real + req.realIP = determineRealIP(req); + + // Registrar a detecção para debug + console.log(`Requisição de ${req.realIP} (original: ${req.ip})`); + + next(); +}; + +function determineRealIP(req: Request): string { + // Array de possíveis fontes de IP, em ordem de preferência + const ipSources = [ + req.headers['x-forwarded-for'], + req.headers['x-real-ip'], + req.headers['cf-connecting-ip'], + req.ip, + req.socket?.remoteAddress + ]; + + // Filtrar valores nulos e processar x-forwarded-for que pode conter múltiplos IPs + for (const source of ipSources) { + if (source) { + // x-forwarded-for pode ter múltiplos IPs separados por vírgula + if (typeof source === 'string' && source.includes(',')) { + const firstIP = source.split(',')[0].trim(); + if (firstIP && isValidIP(firstIP)) return firstIP; + } else if (isValidIP(source as string)) { + return source as string; + } + } + } + + // Fallback para um valor padrão se nenhum IP válido for encontrado + return '0.0.0.0'; +} + +function isValidIP(ip: string): boolean { + // Função simples para verificar se uma string parece um IP válido + return !!ip && ip !== 'unknown' && ip !== '::1' && ip !== 'localhost'; +} + +// Estender a interface Request para incluir a propriedade realIP +declare global { + namespace Express { + interface Request { + realIP: string; + } + } +} \ No newline at end of file diff --git a/src/server/routes/provas.ts b/src/server/routes/provas.ts index 54dfecd..2f318b1 100644 --- a/src/server/routes/provas.ts +++ b/src/server/routes/provas.ts @@ -175,15 +175,93 @@ router.get('/:id/status', async (req, res) => { } }); -// Verificar documento +// Modificar a rota de verificação router.get('/verificar/:hash', async (req, res) => { try { const { hash } = req.params; + + // Capturar IP real usando a mesma lógica que funciona no processamento + const realClientIP = req.headers['x-forwarded-for']?.toString().split(',')[0].trim() || + req.ip || + req.socket?.remoteAddress || + '127.0.0.1'; // Nunca usar 0.0.0.0 + + const clientPort = req.socket?.remotePort?.toString() || '0'; + + // Registrar para debug + console.log('Verificação de prova solicitada:', { + hash, + realClientIP, + clientPort, + headers: { + 'x-forwarded-for': req.headers['x-forwarded-for'], + 'user-agent': req.headers['user-agent'] + } + }); + + const provaService = new ProvaService(); const result = await provaService.verifyFile(hash); - res.json(result); + + // Adicionar informações de verificação ao resultado + return res.status(200).json({ + ...result, + verificacao: { + timestamp: new Date().toISOString(), + ip: realClientIP, + porta: clientPort + } + }); } catch (error) { - console.error('Erro ao verificar documento:', error); - res.status(500).json({ error: 'Erro ao verificar documento' }); + console.error('Erro ao verificar arquivo:', error); + return res.status(500).json({ + verified: false, + message: `Erro ao verificar: ${error.message}` + }); + } +}); + +// Modificar a rota de verificação pública + +router.get('/public/verificar/:hash', async (req, res) => { + try { + const { hash } = req.params; + + // Capturar IP real usando headers + const realClientIP = req.headers['x-forwarded-for']?.toString().split(',')[0].trim() || + req.ip || + req.socket?.remoteAddress || + '127.0.0.1'; + + const clientPort = req.socket?.remotePort?.toString() || '0'; + + console.log('Verificação pública solicitada:', { + hash, + realClientIP, + clientPort, + headers: { + 'x-forwarded-for': req.headers['x-forwarded-for'], + 'user-agent': req.headers['user-agent'] + } + }); + + const provaService = new ProvaService(); + const result = await provaService.verifyFile(hash); + + // Adicionar informações de verificação ao resultado + return res.status(200).json({ + ...result, + verificacao: { + timestamp: new Date().toISOString(), + ip: realClientIP, + porta: clientPort + } + }); + } catch (error) { + console.error('Erro ao verificar arquivo:', error); + return res.status(500).json({ + verified: false, + message: `Erro ao verificar: ${error.message}` + }); } }); diff --git a/src/server/services/ProvaService.ts b/src/server/services/ProvaService.ts index ddad2a4..2d837e7 100644 --- a/src/server/services/ProvaService.ts +++ b/src/server/services/ProvaService.ts @@ -6,8 +6,10 @@ import * as fs from 'fs/promises'; import * as path from 'path'; import { execSync } from 'child_process'; import { BlockchainService } from './BlockchainService'; +import { ipDetectionMiddleware } from '../middleware/ipDetection'; import { FileValidator } from './FileValidator'; import { ReportGenerator } from './ReportGenerator'; // Adicionar import +import { Request, Response } from 'express'; // Importar Request e Response // Tentativa de importar o MetadataCollector com fallback let MetadataCollectorClass; @@ -66,9 +68,12 @@ export class ProvaService { // 2. Coletar metadados avançados (com tratamento de erro) let enhancedMetadata; try { + // Garantir que estamos usando o IP real do cliente + const clientIP = this.extractRealClientIP(metadata.ipAddress, metadata); + enhancedMetadata = await this.metadataCollector.collectMetadata({ userAgent: metadata.userAgent, - ipAddress: metadata.ipAddress || '0.0.0.0', + ipAddress: clientIP, timestamp: new Date(metadata.timestamp), fileName: file.originalname, fileModified: metadata.modification_times ? new Date(parseInt(metadata.modification_times)) : undefined, @@ -99,14 +104,21 @@ export class ProvaService { const processedFile = await this.processFileByType(file); // 6. Gerar URL de verificação - const verificationUrl = `${process.env.BACKEND_URL || 'http://localhost:5000'}/verificar/${checksums.sha256}`; + const verificationUrl = `${process.env.PUBLIC_URL || 'http://localhost:5000'}/verificar/${checksums.sha256}`; // 7. Gerar relatório const reportResult = await this.reportGenerator.generateReport({ file, checksums, timestamp, - metadata: enhancedMetadata, + metadata: { + ...enhancedMetadata, + // Garantir que as informações de IP estejam disponíveis + ipAddress: enhancedMetadata.ipAddress, + headers: metadata.headers, + connectionInfo: metadata.connectionInfo, + clientPort: metadata.clientPort + }, processedFile, verificationUrl }); @@ -147,6 +159,75 @@ export class ProvaService { } } + /** + * Extrai o endereço IP real do cliente, ignorando IPs locais quando possível + * @param ipAddress O endereço IP fornecido na requisição + * @param metadata Metadados adicionais para análise + * @returns O endereço IP real do cliente + */ + private extractRealClientIP(ipAddress: string, metadata?: any): string { + // Verificar se temos dados válidos para trabalhar + if (!metadata) { + return ipAddress || '0.0.0.0'; + } + + console.log('Tentando extrair IP real. IP original:', ipAddress); + + // Lista de IPs locais/internos que devem ser evitados + const localIPs = ['::1', '127.0.0.1', 'localhost', '::ffff:127.0.0.1', '::ffff:10.15.0.1']; + + // Verificar PRIMEIRO os headers HTTP que são a fonte mais confiável em produção + if (metadata.headers) { + // Headers comuns que podem conter o IP real + if (metadata.headers['x-forwarded-for']) { + const forwardedIps = metadata.headers['x-forwarded-for'].split(',').map(ip => ip.trim()); + if (forwardedIps.length > 0 && !localIPs.includes(forwardedIps[0])) { + console.log('Usando IP do x-forwarded-for:', forwardedIps[0]); + return forwardedIps[0]; + } + } + + // Outros headers comuns + const otherHeaders = ['x-real-ip', 'cf-connecting-ip', 'true-client-ip']; + for (const header of otherHeaders) { + if (metadata.headers[header] && !localIPs.includes(metadata.headers[header])) { + console.log(`Usando IP do header ${header}:`, metadata.headers[header]); + return metadata.headers[header]; + } + } + } + + // Verificar nas informações de conexão + if (metadata.connectionInfo && metadata.connectionInfo.forwardedIp) { + if (!localIPs.includes(metadata.connectionInfo.forwardedIp)) { + console.log('Usando IP encaminhado da conexão:', metadata.connectionInfo.forwardedIp); + return metadata.connectionInfo.forwardedIp; + } + } + + // Se o IP não é local, usá-lo + if (ipAddress && !localIPs.includes(ipAddress)) { + console.log('Usando IP original:', ipAddress); + return ipAddress; + } + + // Se chegamos aqui, temos apenas IPs locais. Usar o IP com a porta como identificador + const port = metadata.clientPort || + metadata.connectionInfo?.socketPort || + metadata.porta; + + if (port) { + const ipWithPort = `${ipAddress || '127.0.0.1'}:${port}`; + console.log('Usando identificador IP:porta:', ipWithPort); + return ipWithPort; + } + + // NUNCA retornar 0.0.0.0 - se nada funcionar, usar 127.0.0.1 com timestamp + const fallbackIp = `127.0.0.1:${Date.now() % 10000}`; + console.log('Usando fallback:', fallbackIp); + return fallbackIp; + } + private generateChecksums(buffer: Buffer) { return { sha256: createHash('sha256').update(buffer).digest('hex'), @@ -166,8 +247,15 @@ export class ProvaService { // 2. Obter tempo NTP do servidor oficial para maior confiabilidade let ntpTime = ''; try { - // Usar servidores NTP confiáveis para obter tempo preciso - ntpTime = execSync('w32tm /stripchart /computer:time.nist.gov /samples:1 /dataonly').toString(); + // Detectar plataforma e usar o comando apropriado + if (process.platform === 'win32') { + // Windows + ntpTime = execSync('w32tm /stripchart /computer:time.nist.gov /samples:1 /dataonly').toString(); + } else { + // Linux/Unix/MacOS - usar implementação alternativa + // Tentar usar o comando 'date' para obter a hora do sistema + ntpTime = execSync('date -u +"%Y-%m-%dT%H:%M:%S.%3NZ"').toString().trim(); + } } catch (e) { console.warn('Não foi possível obter tempo NTP, usando tempo local:', e); } @@ -190,7 +278,16 @@ export class ProvaService { }; } catch (error) { console.error('Erro ao criar timestamp local:', error); - throw new Error(`Falha ao criar timestamp: ${error.message}`); + // Em caso de erro, retornar um timestamp básico sem interromper o processo + const basicTimestamp = { + hash, + timestamp: new Date().toISOString(), + ntpReference: 'Indisponível (erro)', + unixTimestamp: Math.floor(Date.now() / 1000), + error: error.message + }; + + return basicTimestamp; } } @@ -211,7 +308,7 @@ export class ProvaService { return file.buffer; } - async verifyFile(hash: string) { + async verifyFile(hash: string, ipInfo?: any) { try { // 1. Buscar prova no banco de dados const prova = await prisma.prova.findFirst({ @@ -249,6 +346,16 @@ export class ProvaService { // 4. Ler o timestamp const timestampData = JSON.parse(await fs.readFile(timestampPath, 'utf-8')); + // 5. Se fornecido, incluir informações de IP no registro + if (ipInfo) { + // Registrar essa verificação + try { + await this.logVerification(hash, ipInfo); + } catch (logError) { + console.error('Erro ao registrar verificação:', logError); + } + } + return { verified: true, message: 'Arquivo verificado com sucesso', @@ -261,6 +368,43 @@ export class ProvaService { } } + // Método para registrar verificações + private async logVerification(hash: string, ipInfo: any) { + try { + const verificationLog = { + timestamp: new Date().toISOString(), + hash, + ip: ipInfo.ip || '127.0.0.1', + porta: ipInfo.porta || '0', + userAgent: ipInfo.userAgent || 'Desconhecido' + }; + + const logPath = path.join(this.storageDir, `${hash}_verifications.json`); + + // Verificar se o arquivo já existe + let existingLogs = []; + try { + const existingData = await fs.readFile(logPath, 'utf-8'); + existingLogs = JSON.parse(existingData); + if (!Array.isArray(existingLogs)) { + existingLogs = []; + } + } catch (e) { + // Arquivo não existe, criar novo + } + + // Adicionar nova verificação + existingLogs.push(verificationLog); + + // Salvar arquivo + await fs.writeFile(logPath, JSON.stringify(existingLogs, null, 2)); + + console.log(`Verificação registrada para ${hash} de IP ${ipInfo.ip}`); + } catch (error) { + console.error('Erro ao registrar verificação:', error); + } + } + // Novo método para registrar informações forenses private async logForensicInfo(file: Express.Multer.File, metadata: any, checksums: any) { try { @@ -295,3 +439,43 @@ export class ProvaService { } } } + +// No handler de upload de arquivo: +export const uploadProva = async (req: Request, res: Response) => { + try { + const file = req.file; + if (!file) { + return res.status(400).json({ error: 'Nenhum arquivo enviado' }); + } + + // Coletar todas as informações possíveis sobre o cliente + const metadata = { + ipAddress: req.ip, // Express geralmente fornece isso + remoteAddress: req.socket?.remoteAddress, // Endereço IP direto do socket + porta: req.socket?.remotePort?.toString(), // Porta de conexão do cliente + userAgent: req.headers['user-agent'], + headers: req.headers, // Todos os headers HTTP + timestamp: new Date().toISOString(), + // Outros metadados do formulário + ...req.body + }; + + console.log('Metadados de conexão:', { + ip: req.ip, + remoteAddress: req.socket?.remoteAddress, + remotePort: req.socket?.remotePort, + headers: { + 'x-forwarded-for': req.headers['x-forwarded-for'], + 'x-real-ip': req.headers['x-real-ip'] + } + }); + + const provaService = new ProvaService(); + const result = await provaService.processFile(file, metadata); + + return res.status(200).json(result); + } catch (error) { + console.error('Erro ao processar upload de prova:', error); + return res.status(500).json({ error: error.message }); + } +}; diff --git a/src/server/services/ReportGenerator.ts b/src/server/services/ReportGenerator.ts index 550f500..b19f35d 100644 --- a/src/server/services/ReportGenerator.ts +++ b/src/server/services/ReportGenerator.ts @@ -458,23 +458,90 @@ export class ReportGenerator { this.drawLabelValue(page, "Idioma:", idioma, y, helvetica, helveticaBold); y -= 15; - // IP - CORRIGIDO PARA EVITAR N/A - const ip = data.metadata?.ipAddress || - data.metadata?.connectionInfo?.ip || - data.metadata?.connectionInfo?.socketAddress || - "::1"; - this.drawLabelValue(page, "IP:", ip, y, helvetica, helveticaBold); - y -= 15; - - // Porta - CORRIGIDA PARA EVITAR N/A - const porta = data.metadata?.clientPort || - data.metadata?.connectionInfo?.socketPort; - this.drawLabelValue(page, "Porta:", String(porta || 61465), y, helvetica, helveticaBold); + // IP - Usar o X-Forwarded-For ou outras fontes confiáveis, evitando IPs locais + let displayIp = '127.0.0.1'; + let porta = ''; + + try { + // Lista de IPs locais que devemos evitar + const localIPs = ['::1', '127.0.0.1', 'localhost', '::ffff:127.0.0.1', '::ffff:10.15.0.1']; + + // VERIFICAÇÃO DE DEBUG - mostrar os dados disponíveis no console + console.log('[PDF] Dados disponíveis para IP:', { + connectionInfo: data.metadata?.connectionInfo, + headers: data.metadata?.headers, + ipAddress: data.metadata?.ipAddress, + clientPort: data.metadata?.clientPort + }); + + // PRIORIDADE 1: headers['x-forwarded-for'] - Priorizar PRIMEIRO este header + if (data.metadata?.headers && data.metadata.headers['x-forwarded-for']) { + const forwardedIps = String(data.metadata.headers['x-forwarded-for']).split(','); + if (forwardedIps.length > 0) { + displayIp = forwardedIps[0].trim(); + console.log('[PDF] Usando IP do x-forwarded-for:', displayIp); + } + } + // PRIORIDADE 2: connectionInfo.forwardedIp + else if (data.metadata?.connectionInfo?.forwardedIp) { + displayIp = data.metadata.connectionInfo.forwardedIp; + console.log('[PDF] Usando forwardedIp da connectionInfo:', displayIp); + } + // PRIORIDADE 3: ipAddress + else if (data.metadata?.ipAddress && !localIPs.includes(data.metadata.ipAddress)) { + displayIp = data.metadata.ipAddress; + console.log('[PDF] Usando ipAddress direto:', displayIp); + } + + // Porta - pegar a porta do cliente de várias fontes possíveis + if (data.metadata?.connectionInfo?.socketPort) { + porta = String(data.metadata.connectionInfo.socketPort); + console.log('[PDF] Usando porta da connectionInfo:', porta); + } + else if (data.metadata?.clientPort) { + porta = String(data.metadata.clientPort); + console.log('[PDF] Usando clientPort:', porta); + } + else if (data.metadata?.porta) { + porta = String(data.metadata.porta); + console.log('[PDF] Usando porta do objeto metadata:', porta); + } + + // GARANTIR que não estamos usando 127.0.0.1 + if (displayIp === '127.0.0.1' || localIPs.includes(displayIp)) { + // Tentar novamente com as informações de conexão + if (data.metadata?.connectionInfo?.forwardedIp) { + displayIp = data.metadata.connectionInfo.forwardedIp; + console.log('[PDF] Forçando uso do forwardedIp:', displayIp); + } + } + + console.log('[PDF] IP e porta finais para relatório:', { displayIp, porta }); + } catch (error) { + console.error('[PDF] Erro ao processar IP e porta para relatório:', error); + } + + // Mostrar IP com valor sempre significativo - NUNCA usar 127.0.0.1 + if (displayIp === '127.0.0.1' || displayIp === '0.0.0.0') { + // Último recurso - pegar direto do objeto connectionInfo.forwardedIp + displayIp = data.metadata?.connectionInfo?.forwardedIp || + (data.metadata?.headers ? data.metadata.headers['x-forwarded-for'] : '0.0.0.0'); + + console.log('[PDF] IP final (após verificação de IP local):', displayIp); + } + + this.drawLabelValue(page, "IP:", displayIp, y, helvetica, helveticaBold); y -= 15; + + // Mostrar porta apenas se tiver um valor significativo + if (porta && porta !== '0') { + this.drawLabelValue(page, "Porta:", porta, y, helvetica, helveticaBold); + y -= 15; + } // Família IP - CORRIGIDA PARA EVITAR N/A const familia = data.metadata?.connectionInfo?.remoteFamily || - (ip.includes(':') ? 'IPv6' : 'IPv4'); + (displayIp.includes(':') ? 'IPv6' : 'IPv4'); this.drawLabelValue(page, "Família IP:", familia, y, helvetica, helveticaBold); y -= 15; @@ -991,4 +1058,45 @@ export class ReportGenerator { return yPosition; } + + /** + * Extrai o endereço IP real do cliente de várias fontes possíveis + * @param ipAddress IP básico + * @param forwardedIp IP encaminhado + * @param xForwardedFor Header X-Forwarded-For + * @returns O IP mais confiável encontrado + */ + private extractRealClientIP(ipAddress?: string, forwardedIp?: string, xForwardedFor?: string): string { + // Lista de IPs locais/internos que devem ser evitados + const localIPs = ['::1', '127.0.0.1', 'localhost', '::ffff:127.0.0.1', '::ffff:10.15.0.1']; + + // Verificar primeiro X-Forwarded-For (mais confiável em ambientes de produção) + if (xForwardedFor) { + const ips = xForwardedFor.split(',').map(ip => ip.trim()); + if (ips.length > 0 && !localIPs.includes(ips[0])) { + return ips[0]; + } + } + + // Verificar IP encaminhado (usado em alguns proxies) + if (forwardedIp && !localIPs.includes(forwardedIp)) { + return forwardedIp; + } + + // Verificar IP básico + if (ipAddress && !localIPs.includes(ipAddress)) { + return ipAddress; + } + + // Se chegamos aqui e temos um X-Forwarded-For, mesmo que seja um IP local, usá-lo + if (xForwardedFor) { + const ips = xForwardedFor.split(',').map(ip => ip.trim()); + if (ips.length > 0) { + return ips[0]; + } + } + + // Retornar IP básico se não encontrar melhor + return ipAddress || '0.0.0.0'; + } }