import express from 'express'; import multer from 'multer'; import { ProvaService } from '../services/ProvaService'; import { prisma } from '../prismaClient'; import * as fsPromises from 'fs/promises'; import * as fs from 'fs'; // Importando o módulo fs padrão import * as path from 'path'; const router = express.Router(); const storage = multer.memoryStorage(); const upload = multer({ storage }); const provaService = new ProvaService(); // Upload de arquivo para validação router.post('/upload', upload.array('files'), async (req, res) => { try { console.log('Iniciando upload de arquivos...'); if (!req.files || !Array.isArray(req.files) || req.files.length === 0) { return res.status(400).json({ error: 'Nenhum arquivo recebido' }); } // Extrair informações sobre o endereço IP e porta do cliente const forwarded = req.headers['x-forwarded-for'] as string; const socketInfo = req.socket.remoteAddress ? { address: req.socket.remoteAddress, port: req.socket.remotePort || 'desconhecida' } : null; // Metadata avançada seguindo RFC 3227 // Corrigir: priorizar latitude/longitude do frontend se vierem preenchidas let latitude: number | undefined = undefined; let longitude: number | undefined = undefined; if ( typeof req.body.latitude !== 'undefined' && req.body.latitude !== null && req.body.latitude !== '' && typeof req.body.longitude !== 'undefined' && req.body.longitude !== null && req.body.longitude !== '' ) { latitude = Number(req.body.latitude); longitude = Number(req.body.longitude); } const metadata = { userAgent: req.body.user_agent, language: req.body.language, viewportWidth: req.body.viewport_width, viewportHeight: req.body.viewport_height, ipAddress: req.ip || req.socket.remoteAddress, clientPort: req.socket.remotePort || 'desconhecida', connectionInfo: { ip: req.ip || req.socket.remoteAddress, forwardedIp: forwarded, socketAddress: socketInfo?.address, socketPort: socketInfo?.port, // Corrigir: determinar família do IP real remoteFamily: (() => { const ip = (req.ip || req.socket.remoteAddress || '').toString(); return ip.includes(':') ? 'IPv6' : 'IPv4'; })() }, timestamp: new Date(), modification_times: req.body.modification_times, referrer: req.body.referrer, timezone: req.body.timezone, timezone_offset: req.body.timezone_offset, platform: req.body.platform, cookies_enabled: req.body.cookies_enabled, browser_plugins: req.body.browser_plugins, latitude, // sempre priorizar o valor do frontend longitude, location_permission: req.body.location_permission, client_timestamp: req.body.client_timestamp, headers: { 'x-forwarded-for': req.headers['x-forwarded-for'], 'user-agent': req.headers['user-agent'], 'accept-language': req.headers['accept-language'], 'referer': req.headers['referer'], 'host': req.headers['host'], 'connection': req.headers['connection'], }, server: { timestamp: new Date().toISOString(), hostname: require('os').hostname(), process: process.pid, nodeVersion: process.version, } }; console.log('Metadata recebida:', metadata); console.log('Informações de conexão do cliente:', metadata.connectionInfo); // Adicionar informações de segurança const securityInfo = { requestMethod: req.method, protocol: req.protocol, isSecure: req.secure, originalUrl: req.originalUrl, contentType: req.headers['content-type'], }; console.log('Informações de segurança:', securityInfo); // Processar cada arquivo const results = []; for (const file of req.files as Express.Multer.File[]) { try { // Adicionar metadados específicos do arquivo atual const fileIndex = (req.files as Express.Multer.File[]).indexOf(file); // Extrair e formatar a data de modificação do arquivo let fileModifiedDate = null; const modificationTime = req.body.modification_times ? (Array.isArray(req.body.modification_times) ? req.body.modification_times[fileIndex] : req.body.modification_times) : null; if (modificationTime) { try { // Converter timestamp para objeto Date const timestamp = parseInt(modificationTime); if (!isNaN(timestamp)) { fileModifiedDate = new Date(timestamp); } } catch (dateError) { console.warn(`Erro ao processar data de modificação para ${file.originalname}:`, dateError); } } const fileMetadata = { ...metadata, file_specific: { name: req.body.file_names ? (Array.isArray(req.body.file_names) ? req.body.file_names[fileIndex] : req.body.file_names) : file.originalname, size: req.body.file_sizes ? (Array.isArray(req.body.file_sizes) ? req.body.file_sizes[fileIndex] : req.body.file_sizes) : file.size, type: req.body.file_types ? (Array.isArray(req.body.file_types) ? req.body.file_types[fileIndex] : req.body.file_types) : file.mimetype, modified: fileModifiedDate ? fileModifiedDate.toISOString() : null, modifiedTimestamp: modificationTime }, security: securityInfo }; // Adicionar log para debug de data de modificação console.log(`Informações de modificação do arquivo ${file.originalname}:`, { rawTimestamp: modificationTime, processedDate: fileModifiedDate, isoString: fileModifiedDate ? fileModifiedDate.toISOString() : null }); const result = await provaService.processFile(file, fileMetadata); results.push(result); } catch (error) { console.error(`Erro ao processar arquivo ${file.originalname}:`, error); return res.status(500).json({ error: `Erro ao processar arquivo ${file.originalname}`, details: error.message }); } } res.json({ success: true, results, id: results[0].id }); } catch (error) { console.error('Erro no upload:', error); res.status(500).json({ error: 'Erro ao processar arquivos' }); } }); // Status da prova router.get('/:id/status', async (req, res) => { try { const { id } = req.params; const prova = await prisma.prova.findUnique({ where: { id } }); if (!prova) { return res.status(404).json({ error: 'Prova não encontrada' }); } res.json(prova); } catch (error) { console.error('Erro ao buscar status:', error); res.status(500).json({ error: 'Erro ao buscar status da prova' }); } }); // 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); // 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}` }); } }); // 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}` }); } }); // Modificar/adicionar esta rota para servir o relatório a partir do hash router.get('/relatorio/:hash', async (req, res) => { try { const { hash } = req.params; // Buscar a prova pelo hash const prova = await prisma.prova.findFirst({ where: { checksumSha256: hash } }); if (!prova || prova.status !== 'CONCLUIDO') { return res.status(404).json({ error: 'Relatório não encontrado' }); } // Construir o caminho para o arquivo do relatório const reportPath = path.join(process.cwd(), 'storage', 'provas', `${hash}_report.pdf`); // Verificar se o arquivo existe try { await fsPromises.access(reportPath); // Usando fsPromises para operação assíncrona } catch (error) { console.error('Erro ao acessar relatório:', error); return res.status(404).json({ error: 'Arquivo do relatório não encontrado' }); } // Enviar o arquivo como resposta res.setHeader('Content-Type', 'application/pdf'); res.setHeader('Content-Disposition', `inline; filename="${prova.fileName.replace(/\.[^/.]+$/, '')}_relatorio.pdf"`); // Criar stream de leitura e enviar para o cliente const fileStream = fs.createReadStream(reportPath); // Usando fs padrão para createReadStream fileStream.pipe(res); } catch (error) { console.error('Erro ao servir relatório:', error); res.status(500).json({ error: 'Erro ao gerar relatório' }); } }); // Listar provas (admin) router.get('/admin', async (req, res) => { try { const provas = await prisma.prova.findMany({ orderBy: { dataCriacao: 'desc' } }); res.json(provas); } catch (error) { console.error('Erro ao listar provas:', error); res.status(500).json({ error: 'Erro ao listar provas' }); } }); export default router;