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 13:29:26 -03:00
1 parent 8dfe0d6f94
commit b764c93106
7 files changed
+693 -287

No files matched your search

+191 -7
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,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 });
}
};
+121 -13
View File
@@ -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';
}
}