Fix IP in ProvaService.ts and Add PUBLIC_URL

Signed-off-by: Rodrigo Martins <rodrigo.sousa.577@gmail.com>
This commit is contained in:
rodrigo committed 2025-04-11 12:40:44 -03:00
1 parent 8dfe0d6f94
commit 11688ececb
5 files changed
+291 -25

No files matched your search

+20 -15
View File
@@ -27,29 +27,34 @@ export function VerificarProva() {
const [loading, setLoading] = useState<boolean>(true);
useEffect(() => {
const fetchProva = async () => {
const fetchProvaDetails = async () => {
try {
setLoading(true);
const response = await fetch(`/api/provas/verificar/${hash}`);
// Obter informações de conexão do cliente para incluir nos logs
// Estas informações serão enviadas junto com a solicitação de verificação
const connectionInfo = {
userAgent: navigator.userAgent,
language: navigator.language,
platform: navigator.platform,
screenWidth: window.screen.width,
screenHeight: window.screen.height,
timestamp: new Date().toISOString()
};
if (!response.ok) {
throw new Error(response.statusText || 'Falha na verificação');
}
// Fazer a requisição para verificar a prova, incluindo informações do cliente
const response = await api.post(`/provas/verificar/${hash}`, {
verificationContext: connectionInfo
});
const data = await response.json();
console.log('Dados recebidos:', data);
setResult(data);
setResult(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();
}
fetchProvaDetails();
}, [hash]);
// Função segura para formatar datas
+5
View File
@@ -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 }));
+52
View File
@@ -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;
}
}
}
+154 -5
View File
@@ -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,7 +104,7 @@ 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({
@@ -147,6 +152,94 @@ 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 se temos o IP encaminhado nas informações de conexão
if (metadata.connectionInfo && metadata.connectionInfo.forwardedIp) {
const forwardedIp = metadata.connectionInfo.forwardedIp;
if (!localIPs.includes(forwardedIp)) {
console.log('Usando forwardedIp dos dados de conexão:', forwardedIp);
return forwardedIp;
}
}
// Verificar se temos o IP do cliente nos headers HTTP
if (metadata.headers) {
// Headers comuns que podem conter o IP real
const headerCandidates = [
'x-forwarded-for',
'X-Forwarded-For',
'x-real-ip',
'X-Real-IP',
'cf-connecting-ip',
'CF-Connecting-IP',
'true-client-ip',
'True-Client-IP'
];
// Tentar cada header candidato
for (const header of headerCandidates) {
if (metadata.headers[header]) {
const headerValue = metadata.headers[header];
// X-Forwarded-For pode conter múltiplos IPs separados por vírgula
if (header.toLowerCase() === 'x-forwarded-for') {
const ips = headerValue.split(',').map(ip => ip.trim());
if (ips.length > 0 && !localIPs.includes(ips[0])) {
console.log(`Usando IP do header ${header}:`, ips[0]);
return ips[0];
}
} else if (!localIPs.includes(headerValue)) {
console.log(`Usando IP do header ${header}:`, headerValue);
return headerValue;
}
}
}
}
// Tentar extrair o IP das informações de conexão direta
if (metadata.remoteAddress && !localIPs.includes(metadata.remoteAddress)) {
console.log('Usando remoteAddress:', metadata.remoteAddress);
return metadata.remoteAddress;
}
// Se temos um valor em clientPort, usar para identificação do cliente
if (metadata.clientPort) {
const clientPort = metadata.clientPort;
console.log('Usando porta do cliente para identificação:', clientPort);
return `${ipAddress}:${clientPort}`;
}
// Se o IP é local, tentar criar um identificador único
if (localIPs.includes(ipAddress)) {
if (metadata.userAgent) {
// Adicionar um hash identificador baseado no userAgent
const userAgentHash = createHash('md5').update(metadata.userAgent).digest('hex').substring(0, 8);
const ipUserAgent = `${ipAddress}#${userAgentHash}`;
console.log('Usando combinação IP#userAgentHash:', ipUserAgent);
return ipUserAgent;
}
}
// Se chegamos aqui, não encontramos nenhuma alternativa melhor
console.log('Nenhum IP alternativo encontrado, usando IP original:', ipAddress || '0.0.0.0');
return ipAddress || '0.0.0.0';
}
private generateChecksums(buffer: Buffer) {
return {
sha256: createHash('sha256').update(buffer).digest('hex'),
@@ -166,8 +259,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 +290,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;
}
}
@@ -295,3 +404,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 });
}
};
+60 -5
View File
@@ -458,11 +458,12 @@ 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";
// IP - Usar o X-Forwarded-For ou outras fontes confiáveis, evitando IPs locais
const ip = this.extractRealClientIP(
data.metadata?.ipAddress,
data.metadata?.connectionInfo?.forwardedIp,
data.metadata?.headers?.['x-forwarded-for']
);
this.drawLabelValue(page, "IP:", ip, y, helvetica, helveticaBold);
y -= 15;
@@ -991,4 +992,58 @@ export class ReportGenerator {
return yPosition;
}
/**
* Extrai o IP real do cliente a partir de várias fontes
*/
private extractRealClientIP(ipAddress?: string, forwardedIp?: string, xForwardedFor?: string): string {
const ipSources = [xForwardedFor, forwardedIp, ipAddress];
for (const ip of ipSources) {
if (ip && !ip.startsWith('127.') && !ip.startsWith('::1')) {
return ip.split(',')[0].trim(); // Retorna o primeiro IP válido
}
}
return 'IP não disponível';
}
/**
* 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';
}
}