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';
+ }
}