Files
advx/src/server/routes/provas.ts
T
2025-04-23 00:12:44 -03:00

335 lines
11 KiB
TypeScript

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;