335 lines
11 KiB
TypeScript
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;
|