diff --git a/src/server/app.ts b/src/server/app.ts index b90e602..d274b7b 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 })); 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/services/ProvaService.ts b/src/server/services/ProvaService.ts index ddad2a4..c713a6e 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,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,87 @@ 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); + console.log('Metadados disponíveis:', JSON.stringify(metadata, null, 2)); + + // Lista de IPs locais/internos que devem ser evitados + const localIPs = ['::1', '127.0.0.1', 'localhost', '::ffff:127.0.0.1']; + + // Tentar extrair o IP das informações de conexão direta primeiro + if (metadata.remoteAddress && !localIPs.includes(metadata.remoteAddress)) { + console.log('Usando remoteAddress:', metadata.remoteAddress); + return metadata.remoteAddress; + } + + // 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; + } + } + } + } + + // Se o IP é local, tentar identificar o cliente de forma única + if (localIPs.includes(ipAddress)) { + // Tentar usar a porta de conexão para identificar o cliente + if (metadata.porta && metadata.porta !== "0") { + const ipPorta = `${ipAddress}:${metadata.porta}`; + console.log('Usando combinação IP:porta:', ipPorta); + return ipPorta; + } + + // Se tiver informações de dispositivo/navegador, adicionar ao IP para melhor identificação + 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'), @@ -295,3 +381,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 }); + } +};