Fix IP in ProvaService.ts and Add PUBLIC_URL
Signed-off-by: Rodrigo Martins <rodrigo.sousa.577@gmail.com>
This commit is contained in:
1 parent
8dfe0d6f94
commit
b764c93106
7 files changed
+693
-287
No files matched your search
@@ -160,6 +160,28 @@ export function PublicVerificarProva() {
|
|||||||
JSON.parse(result.prova.timestamp).timestamp :
|
JSON.parse(result.prova.timestamp).timestamp :
|
||||||
result.prova.dataCriacao)}
|
result.prova.dataCriacao)}
|
||||||
/>
|
/>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<dt className="text-sm font-medium text-gray-500">IP:</dt>
|
||||||
|
<dd className="mt-1 text-base text-gray-900 break-words">
|
||||||
|
{result.verificacao?.ip ||
|
||||||
|
result.prova.metadata?.connectionInfo?.forwardedIp ||
|
||||||
|
(result.prova.metadata?.headers?.['x-forwarded-for'] ?
|
||||||
|
result.prova.metadata.headers['x-forwarded-for'].split(',')[0].trim() :
|
||||||
|
result.prova.metadata?.ipAddress) ||
|
||||||
|
'Não disponível'}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dt className="text-sm font-medium text-gray-500">Porta:</dt>
|
||||||
|
<dd className="mt-1 text-base text-gray-900 break-words">
|
||||||
|
{result.verificacao?.porta ||
|
||||||
|
result.prova.metadata?.clientPort ||
|
||||||
|
result.prova.metadata?.connectionInfo?.socketPort ||
|
||||||
|
'Não disponível'}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,291 +1,247 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { Card } from '../../components/ui/Card';
|
import axios from 'axios'; // Importar axios para fazer a requisição
|
||||||
import { format, isValid } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import ptBR from 'date-fns/locale/pt-BR';
|
import { ptBR } from 'date-fns/locale';
|
||||||
|
import {
|
||||||
|
Container,
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
CircularProgress,
|
||||||
|
Alert,
|
||||||
|
Paper,
|
||||||
|
Grid,
|
||||||
|
Divider,
|
||||||
|
Button
|
||||||
|
} from '@mui/material';
|
||||||
|
|
||||||
interface ProvaVerificacao {
|
// Componente para verificação de provas digitais
|
||||||
verified: boolean;
|
export const VerificarProva: React.FC = () => {
|
||||||
message: string;
|
|
||||||
prova?: {
|
|
||||||
fileName: string;
|
|
||||||
checksumSha256: string;
|
|
||||||
checksumSha512: string;
|
|
||||||
checksumMd5: string;
|
|
||||||
timestamp: string;
|
|
||||||
metadata: any;
|
|
||||||
dataCriacao: string;
|
|
||||||
dataValida: string;
|
|
||||||
};
|
|
||||||
timestamp?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function VerificarProva() {
|
|
||||||
const { hash } = useParams<{ hash: string }>();
|
const { hash } = useParams<{ hash: string }>();
|
||||||
const [result, setResult] = useState<ProvaVerificacao | null>(null);
|
const [prova, setProva] = useState<any>(null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
|
||||||
const [loading, setLoading] = useState<boolean>(true);
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// URL base da API - usar a mesma do ambiente atual
|
||||||
|
const API_BASE_URL = process.env.REACT_APP_API_URL || '/api';
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchProva = async () => {
|
const fetchProvaDetails = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await fetch(`/api/provas/verificar/${hash}`);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
// Obter informações de conexão do cliente para incluir nos logs
|
||||||
throw new Error(response.statusText || 'Falha na verificação');
|
const connectionInfo = {
|
||||||
|
userAgent: navigator.userAgent,
|
||||||
|
language: navigator.language,
|
||||||
|
platform: navigator.platform,
|
||||||
|
screenWidth: window.screen.width,
|
||||||
|
screenHeight: window.screen.height,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fazer a requisição para verificar a prova
|
||||||
|
const response = await axios.get(`${API_BASE_URL}/provas/verificar/${hash}`, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Também registrar a verificação separadamente
|
||||||
|
try {
|
||||||
|
await axios.post(`${API_BASE_URL}/provas/registrar-verificacao`, {
|
||||||
|
hash,
|
||||||
|
verificationContext: connectionInfo
|
||||||
|
});
|
||||||
|
} catch (logError) {
|
||||||
|
console.warn('Erro ao registrar verificação:', logError);
|
||||||
|
// Não interromper o fluxo principal por erro no log
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
setProva(response.data);
|
||||||
console.log('Dados recebidos:', data);
|
setLoading(false);
|
||||||
setResult(data);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erro ao verificar:', error);
|
console.error('Erro ao verificar prova:', error);
|
||||||
setError('Não foi possível verificar a autenticidade do documento.');
|
setError('Não foi possível verificar este documento. O hash pode ser inválido ou o documento não existe.');
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (hash) {
|
|
||||||
fetchProva();
|
|
||||||
}
|
|
||||||
}, [hash]);
|
|
||||||
|
|
||||||
// Função segura para formatar datas
|
|
||||||
const formatDate = (dateString: string | undefined | null) => {
|
|
||||||
if (!dateString) return 'Data não disponível';
|
|
||||||
|
|
||||||
|
if (hash) {
|
||||||
|
fetchProvaDetails();
|
||||||
|
} else {
|
||||||
|
setError('Hash de verificação não fornecido');
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [hash, API_BASE_URL]);
|
||||||
|
|
||||||
|
// Renderização condicional com base no estado
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<Container maxWidth="md">
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
flexDirection="column"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
minHeight="70vh"
|
||||||
|
>
|
||||||
|
<CircularProgress size={60} />
|
||||||
|
<Typography variant="h6" mt={2}>
|
||||||
|
Verificando autenticidade do documento...
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<Container maxWidth="md">
|
||||||
|
<Box mt={4}>
|
||||||
|
<Alert severity="error" variant="filled">
|
||||||
|
<Typography variant="h6">Verificação Falhou</Typography>
|
||||||
|
<Typography>{error}</Typography>
|
||||||
|
</Alert>
|
||||||
|
<Box mt={2}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
onClick={() => window.location.href = '/'}
|
||||||
|
>
|
||||||
|
Voltar à página inicial
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formatação da data para exibição
|
||||||
|
const formatarData = (dataString: string) => {
|
||||||
try {
|
try {
|
||||||
const date = new Date(dateString);
|
const data = new Date(dataString);
|
||||||
|
return format(data, 'dd/MM/yyyy HH:mm:ss', { locale: ptBR });
|
||||||
if (!isValid(date)) {
|
|
||||||
console.warn('Data inválida:', dateString);
|
|
||||||
return 'Data inválida';
|
|
||||||
}
|
|
||||||
|
|
||||||
return format(date, 'dd/MM/yyyy HH:mm:ss', { locale: ptBR });
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erro ao formatar data:', error);
|
return 'Data inválida';
|
||||||
return 'Erro na formatação da data';
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen bg-gray-100 py-12 px-4 sm:px-6 lg:px-8">
|
|
||||||
<div className="max-w-3xl mx-auto">
|
|
||||||
<Card>
|
|
||||||
<div className="p-6 text-center">
|
|
||||||
<div className="animate-pulse">
|
|
||||||
<div className="h-4 bg-gray-300 rounded w-3/4 mx-auto mb-4"></div>
|
|
||||||
<div className="h-4 bg-gray-300 rounded w-1/2 mx-auto"></div>
|
|
||||||
</div>
|
|
||||||
<p className="mt-4 text-gray-500">Carregando informações...</p>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error || !result) {
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen bg-gray-100 py-12 px-4 sm:px-6 lg:px-8">
|
|
||||||
<div className="max-w-3xl mx-auto">
|
|
||||||
<Card>
|
|
||||||
<div className="p-6 text-center">
|
|
||||||
<h2 className="text-2xl font-bold text-red-600 mb-4">Verificação Falhou</h2>
|
|
||||||
<p className="text-gray-600">{error || 'Não foi possível verificar este documento.'}</p>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-100 py-12 px-4 sm:px-6 lg:px-8">
|
<Container maxWidth="md">
|
||||||
<div className="max-w-3xl mx-auto">
|
<Box mt={4} mb={4}>
|
||||||
<Card>
|
{prova?.verified ? (
|
||||||
<div className="p-6">
|
<Alert severity="success" variant="filled">
|
||||||
<div className="text-center mb-8">
|
<Typography variant="h6">Documento Verificado com Sucesso</Typography>
|
||||||
{result.verified ? (
|
<Typography>
|
||||||
<>
|
Este documento foi validado e sua integridade foi confirmada.
|
||||||
<h2 className="text-2xl font-bold text-green-600 mb-2">Documento Verificado</h2>
|
</Typography>
|
||||||
<p className="text-gray-600">
|
</Alert>
|
||||||
Este documento foi validado digitalmente em nossa plataforma.
|
) : (
|
||||||
</p>
|
<Alert severity="warning" variant="filled">
|
||||||
</>
|
<Typography variant="h6">Verificação Inconclusiva</Typography>
|
||||||
) : (
|
<Typography>
|
||||||
<>
|
{prova?.message || 'Não foi possível confirmar a integridade deste documento.'}
|
||||||
<h2 className="text-2xl font-bold text-red-600 mb-2">Verificação com Problemas</h2>
|
</Typography>
|
||||||
<p className="text-gray-600">
|
</Alert>
|
||||||
{result.message}
|
)}
|
||||||
</p>
|
</Box>
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{result.prova && (
|
{prova && (
|
||||||
<div className="space-y-6">
|
<Paper elevation={3}>
|
||||||
<Section title="Informações do Documento">
|
<Box p={3}>
|
||||||
<Info label="Nome do Arquivo" value={result.prova.fileName} />
|
<Typography variant="h5" gutterBottom>
|
||||||
<Info
|
Detalhes do Documento
|
||||||
label="Data de Validação"
|
</Typography>
|
||||||
value={formatDate(result.prova.dataCriacao)}
|
<Divider sx={{ mb: 2 }} />
|
||||||
/>
|
|
||||||
<Info
|
<Grid container spacing={2}>
|
||||||
label="Última Verificação"
|
<Grid item xs={12} sm={6}>
|
||||||
value={formatDate(result.prova.dataValida)}
|
<Typography variant="subtitle1" fontWeight="bold">
|
||||||
/>
|
Nome do arquivo:
|
||||||
</Section>
|
</Typography>
|
||||||
|
<Typography gutterBottom>
|
||||||
|
{prova.prova?.fileName || 'Não disponível'}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={12} sm={6}>
|
||||||
|
<Typography variant="subtitle1" fontWeight="bold">
|
||||||
|
Tipo:
|
||||||
|
</Typography>
|
||||||
|
<Typography gutterBottom>
|
||||||
|
{prova.prova?.mimeType || 'Não disponível'}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Typography variant="subtitle1" fontWeight="bold">
|
||||||
|
Hash SHA256:
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
gutterBottom
|
||||||
|
sx={{ wordBreak: 'break-all', fontSize: '0.9rem' }}
|
||||||
|
>
|
||||||
|
{prova.prova?.checksumSha256 || 'Não disponível'}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={12} sm={6}>
|
||||||
|
<Typography variant="subtitle1" fontWeight="bold">
|
||||||
|
Data de registro:
|
||||||
|
</Typography>
|
||||||
|
<Typography gutterBottom>
|
||||||
|
{prova.timestamp?.timestamp ?
|
||||||
|
formatarData(prova.timestamp.timestamp) :
|
||||||
|
'Não disponível'}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={12} sm={6}>
|
||||||
|
<Typography variant="subtitle1" fontWeight="bold">
|
||||||
|
Data de verificação:
|
||||||
|
</Typography>
|
||||||
|
<Typography gutterBottom>
|
||||||
|
{formatarData(new Date().toISOString())}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<Section title="Checksums">
|
<Grid item xs={12} sm={6}>
|
||||||
<Info label="SHA-256" value={result.prova.checksumSha256} monospace />
|
<Typography variant="subtitle1" fontWeight="bold">
|
||||||
<Info label="SHA-512" value={result.prova.checksumSha512} monospace />
|
IP:
|
||||||
<Info label="MD5" value={result.prova.checksumMd5} monospace />
|
</Typography>
|
||||||
</Section>
|
<Typography gutterBottom>
|
||||||
|
{prova.verificacao?.ip || prova.prova?.metadata?.ipAddress || 'Não disponível'}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<Section title="Timestamp">
|
<Grid item xs={12} sm={6}>
|
||||||
{result.timestamp ? (
|
<Typography variant="subtitle1" fontWeight="bold">
|
||||||
<Info
|
Porta:
|
||||||
label="Timestamp"
|
</Typography>
|
||||||
value="Verificado pelo sistema"
|
<Typography gutterBottom>
|
||||||
subtext={formatDate(result.prova.timestamp ?
|
{prova.verificacao?.porta || prova.prova?.metadata?.clientPort || 'Não disponível'}
|
||||||
JSON.parse(result.prova.timestamp).timestamp :
|
</Typography>
|
||||||
result.prova.dataCriacao)}
|
</Grid>
|
||||||
/>
|
</Grid>
|
||||||
) : (
|
|
||||||
<Info
|
{prova.prova?.reportPath && (
|
||||||
label="Timestamp"
|
<Box mt={3} display="flex" justifyContent="center">
|
||||||
value="Registrado no sistema"
|
<Button
|
||||||
subtext={formatDate(result.prova.dataCriacao)}
|
variant="contained"
|
||||||
/>
|
color="primary"
|
||||||
)}
|
href={`${API_BASE_URL}/provas/download-report/${prova.prova.checksumSha256}`}
|
||||||
</Section>
|
target="_blank"
|
||||||
|
>
|
||||||
{result.prova.metadata && (
|
Baixar Relatório Completo
|
||||||
<Section title="Metadados do Upload">
|
</Button>
|
||||||
<Info
|
</Box>
|
||||||
label="IP"
|
|
||||||
value={
|
|
||||||
result.prova.metadata.ipAddress ||
|
|
||||||
result.prova.metadata.connectionInfo?.ip ||
|
|
||||||
result.prova.metadata.connectionInfo?.socketAddress ||
|
|
||||||
'::1'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Info
|
|
||||||
label="Porta de Conexão"
|
|
||||||
value={
|
|
||||||
result.prova.metadata.clientPort ||
|
|
||||||
result.prova.metadata.connectionInfo?.socketPort ||
|
|
||||||
'61465'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Info
|
|
||||||
label="Família IP"
|
|
||||||
value={
|
|
||||||
result.prova.metadata.connectionInfo?.remoteFamily ||
|
|
||||||
((result.prova.metadata.ipAddress || result.prova.metadata.connectionInfo?.socketAddress || '::1').includes(':') ?
|
|
||||||
'IPv6' : 'IPv4')
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Info
|
|
||||||
label="Navegador"
|
|
||||||
value={
|
|
||||||
result.prova.metadata.userInfo?.browser?.name ?
|
|
||||||
`${result.prova.metadata.userInfo.browser.name} ${result.prova.metadata.userInfo.browser.version || ''}` :
|
|
||||||
result.prova.metadata.userAgent?.includes('Chrome') ?
|
|
||||||
`Chrome ${result.prova.metadata.userAgent?.match(/Chrome\/([0-9.]+)/)?.[1] || ''}` :
|
|
||||||
result.prova.metadata.userAgent || 'Chrome'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Info
|
|
||||||
label="Sistema"
|
|
||||||
value={
|
|
||||||
result.prova.metadata.userInfo?.os?.name ?
|
|
||||||
`${result.prova.metadata.userInfo.os.name} ${result.prova.metadata.userInfo.os.version || ''}` :
|
|
||||||
result.prova.metadata.platform || 'Windows 10'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Info
|
|
||||||
label="Idioma"
|
|
||||||
value={
|
|
||||||
result.prova.metadata.language ||
|
|
||||||
result.prova.metadata.headers?.['accept-language'] ||
|
|
||||||
'pt-BR'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{result.prova.metadata.timezone && (
|
|
||||||
<Info
|
|
||||||
label="Timezone"
|
|
||||||
value={result.prova.metadata.timezone}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{(result.prova.metadata.latitude !== undefined || result.prova.metadata.longitude !== undefined) && (
|
|
||||||
<Info
|
|
||||||
label="Coordenadas"
|
|
||||||
value={`${result.prova.metadata.latitude || 'N/A'}, ${result.prova.metadata.longitude || 'N/A'}`}
|
|
||||||
subtext={`Permissão: ${result.prova.metadata.location_permission || 'N/A'}`}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{(result.prova.metadata.file_specific?.modified || result.prova.metadata.modification_times) && (
|
|
||||||
<Info
|
|
||||||
label="Data de Modificação do Arquivo"
|
|
||||||
value={formatDate(result.prova.metadata.file_specific?.modified ||
|
|
||||||
(result.prova.metadata.modification_times ?
|
|
||||||
new Date(parseInt(result.prova.metadata.modification_times)) : null))}
|
|
||||||
subtext="Esta é a data e hora real da última modificação do arquivo, preservada dos metadados originais"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{(result.prova.metadata.file_specific?.modifiedTimestamp || result.prova.metadata.modification_times) && (
|
|
||||||
<Info
|
|
||||||
label="Timestamp de Modificação"
|
|
||||||
value={result.prova.metadata.file_specific?.modifiedTimestamp || result.prova.metadata.modification_times}
|
|
||||||
subtext="Valor original do timestamp (Unix) da última modificação"
|
|
||||||
monospace={true}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Section>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</Box>
|
||||||
</Card>
|
</Paper>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
function Section({ title, children }: { title: string; children: React.ReactNode }) {
|
export default VerificarProva;
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h3 className="font-semibold text-gray-900 mb-3">{title}</h3>
|
|
||||||
<div className="bg-gray-50 rounded-lg p-4 space-y-2">
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Info({
|
|
||||||
label,
|
|
||||||
value,
|
|
||||||
monospace = false,
|
|
||||||
subtext
|
|
||||||
}: {
|
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
monospace?: boolean;
|
|
||||||
subtext?: string;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<dt className="text-sm font-medium text-gray-500">{label}</dt>
|
|
||||||
<dd className={`mt-1 text-sm text-gray-900 ${monospace ? 'font-mono' : ''}`}>{value}</dd>
|
|
||||||
{subtext && <dd className="mt-1 text-xs text-gray-500">{subtext}</dd>}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -14,6 +14,11 @@ dotenv.config();
|
|||||||
const app = express();
|
const app = express();
|
||||||
const prisma = new PrismaClient();
|
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(cors());
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
app.use(bodyParser.urlencoded({ extended: true }));
|
app.use(bodyParser.urlencoded({ extended: true }));
|
||||||
@@ -24,6 +29,7 @@ app.use('/api/indices', indicesMonetariosRoutes);
|
|||||||
app.use('/api/pensao-alimenticia', pensaoAlimenticiaRoutes);
|
app.use('/api/pensao-alimenticia', pensaoAlimenticiaRoutes);
|
||||||
|
|
||||||
app.use(express.static(path.join(__dirname, 'public')));
|
app.use(express.static(path.join(__dirname, 'public')));
|
||||||
|
app.set('trust proxy', true); // Importante para obter o IP real em ambientes com proxy
|
||||||
|
|
||||||
const PORT = process.env.PORT || 5000;
|
const PORT = process.env.PORT || 5000;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -175,15 +175,93 @@ router.get('/:id/status', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Verificar documento
|
// Modificar a rota de verificação
|
||||||
router.get('/verificar/:hash', async (req, res) => {
|
router.get('/verificar/:hash', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { hash } = req.params;
|
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);
|
const result = await provaService.verifyFile(hash);
|
||||||
res.json(result);
|
|
||||||
|
// 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) {
|
} catch (error) {
|
||||||
console.error('Erro ao verificar documento:', error);
|
console.error('Erro ao verificar arquivo:', error);
|
||||||
res.status(500).json({ error: 'Erro ao verificar documento' });
|
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}`
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ import * as fs from 'fs/promises';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
import { BlockchainService } from './BlockchainService';
|
import { BlockchainService } from './BlockchainService';
|
||||||
|
import { ipDetectionMiddleware } from '../middleware/ipDetection';
|
||||||
import { FileValidator } from './FileValidator';
|
import { FileValidator } from './FileValidator';
|
||||||
import { ReportGenerator } from './ReportGenerator'; // Adicionar import
|
import { ReportGenerator } from './ReportGenerator'; // Adicionar import
|
||||||
|
import { Request, Response } from 'express'; // Importar Request e Response
|
||||||
|
|
||||||
// Tentativa de importar o MetadataCollector com fallback
|
// Tentativa de importar o MetadataCollector com fallback
|
||||||
let MetadataCollectorClass;
|
let MetadataCollectorClass;
|
||||||
@@ -66,9 +68,12 @@ export class ProvaService {
|
|||||||
// 2. Coletar metadados avançados (com tratamento de erro)
|
// 2. Coletar metadados avançados (com tratamento de erro)
|
||||||
let enhancedMetadata;
|
let enhancedMetadata;
|
||||||
try {
|
try {
|
||||||
|
// Garantir que estamos usando o IP real do cliente
|
||||||
|
const clientIP = this.extractRealClientIP(metadata.ipAddress, metadata);
|
||||||
|
|
||||||
enhancedMetadata = await this.metadataCollector.collectMetadata({
|
enhancedMetadata = await this.metadataCollector.collectMetadata({
|
||||||
userAgent: metadata.userAgent,
|
userAgent: metadata.userAgent,
|
||||||
ipAddress: metadata.ipAddress || '0.0.0.0',
|
ipAddress: clientIP,
|
||||||
timestamp: new Date(metadata.timestamp),
|
timestamp: new Date(metadata.timestamp),
|
||||||
fileName: file.originalname,
|
fileName: file.originalname,
|
||||||
fileModified: metadata.modification_times ? new Date(parseInt(metadata.modification_times)) : undefined,
|
fileModified: metadata.modification_times ? new Date(parseInt(metadata.modification_times)) : undefined,
|
||||||
@@ -99,14 +104,21 @@ export class ProvaService {
|
|||||||
const processedFile = await this.processFileByType(file);
|
const processedFile = await this.processFileByType(file);
|
||||||
|
|
||||||
// 6. Gerar URL de verificação
|
// 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
|
// 7. Gerar relatório
|
||||||
const reportResult = await this.reportGenerator.generateReport({
|
const reportResult = await this.reportGenerator.generateReport({
|
||||||
file,
|
file,
|
||||||
checksums,
|
checksums,
|
||||||
timestamp,
|
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,
|
processedFile,
|
||||||
verificationUrl
|
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) {
|
private generateChecksums(buffer: Buffer) {
|
||||||
return {
|
return {
|
||||||
sha256: createHash('sha256').update(buffer).digest('hex'),
|
sha256: createHash('sha256').update(buffer).digest('hex'),
|
||||||
@@ -166,8 +247,15 @@ export class ProvaService {
|
|||||||
// 2. Obter tempo NTP do servidor oficial para maior confiabilidade
|
// 2. Obter tempo NTP do servidor oficial para maior confiabilidade
|
||||||
let ntpTime = '';
|
let ntpTime = '';
|
||||||
try {
|
try {
|
||||||
// Usar servidores NTP confiáveis para obter tempo preciso
|
// Detectar plataforma e usar o comando apropriado
|
||||||
ntpTime = execSync('w32tm /stripchart /computer:time.nist.gov /samples:1 /dataonly').toString();
|
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) {
|
} catch (e) {
|
||||||
console.warn('Não foi possível obter tempo NTP, usando tempo local:', e);
|
console.warn('Não foi possível obter tempo NTP, usando tempo local:', e);
|
||||||
}
|
}
|
||||||
@@ -190,7 +278,16 @@ export class ProvaService {
|
|||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erro ao criar timestamp local:', 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;
|
return file.buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
async verifyFile(hash: string) {
|
async verifyFile(hash: string, ipInfo?: any) {
|
||||||
try {
|
try {
|
||||||
// 1. Buscar prova no banco de dados
|
// 1. Buscar prova no banco de dados
|
||||||
const prova = await prisma.prova.findFirst({
|
const prova = await prisma.prova.findFirst({
|
||||||
@@ -249,6 +346,16 @@ export class ProvaService {
|
|||||||
// 4. Ler o timestamp
|
// 4. Ler o timestamp
|
||||||
const timestampData = JSON.parse(await fs.readFile(timestampPath, 'utf-8'));
|
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 {
|
return {
|
||||||
verified: true,
|
verified: true,
|
||||||
message: 'Arquivo verificado com sucesso',
|
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
|
// Novo método para registrar informações forenses
|
||||||
private async logForensicInfo(file: Express.Multer.File, metadata: any, checksums: any) {
|
private async logForensicInfo(file: Express.Multer.File, metadata: any, checksums: any) {
|
||||||
try {
|
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 });
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -458,23 +458,90 @@ export class ReportGenerator {
|
|||||||
this.drawLabelValue(page, "Idioma:", idioma, y, helvetica, helveticaBold);
|
this.drawLabelValue(page, "Idioma:", idioma, y, helvetica, helveticaBold);
|
||||||
y -= 15;
|
y -= 15;
|
||||||
|
|
||||||
// IP - CORRIGIDO PARA EVITAR N/A
|
// IP - Usar o X-Forwarded-For ou outras fontes confiáveis, evitando IPs locais
|
||||||
const ip = data.metadata?.ipAddress ||
|
let displayIp = '127.0.0.1';
|
||||||
data.metadata?.connectionInfo?.ip ||
|
let porta = '';
|
||||||
data.metadata?.connectionInfo?.socketAddress ||
|
|
||||||
"::1";
|
try {
|
||||||
this.drawLabelValue(page, "IP:", ip, y, helvetica, helveticaBold);
|
// Lista de IPs locais que devemos evitar
|
||||||
y -= 15;
|
const localIPs = ['::1', '127.0.0.1', 'localhost', '::ffff:127.0.0.1', '::ffff:10.15.0.1'];
|
||||||
|
|
||||||
// Porta - CORRIGIDA PARA EVITAR N/A
|
// VERIFICAÇÃO DE DEBUG - mostrar os dados disponíveis no console
|
||||||
const porta = data.metadata?.clientPort ||
|
console.log('[PDF] Dados disponíveis para IP:', {
|
||||||
data.metadata?.connectionInfo?.socketPort;
|
connectionInfo: data.metadata?.connectionInfo,
|
||||||
this.drawLabelValue(page, "Porta:", String(porta || 61465), y, helvetica, helveticaBold);
|
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;
|
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
|
// Família IP - CORRIGIDA PARA EVITAR N/A
|
||||||
const familia = data.metadata?.connectionInfo?.remoteFamily ||
|
const familia = data.metadata?.connectionInfo?.remoteFamily ||
|
||||||
(ip.includes(':') ? 'IPv6' : 'IPv4');
|
(displayIp.includes(':') ? 'IPv6' : 'IPv4');
|
||||||
this.drawLabelValue(page, "Família IP:", familia, y, helvetica, helveticaBold);
|
this.drawLabelValue(page, "Família IP:", familia, y, helvetica, helveticaBold);
|
||||||
y -= 15;
|
y -= 15;
|
||||||
|
|
||||||
@@ -991,4 +1058,45 @@ export class ReportGenerator {
|
|||||||
|
|
||||||
return yPosition;
|
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';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user