Fix IP in ProvaService.ts and Add PUBLIC_URL

Signed-off-by: Rodrigo Martins <rodrigo.sousa.577@gmail.com>
This commit is contained in:
rodrigo committed 2025-04-11 13:29:26 -03:00
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 :
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>
+219 -263
View File
@@ -1,291 +1,247 @@
import { useEffect, useState } from 'react';
import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { Card } from '../../components/ui/Card';
import { format, isValid } from 'date-fns';
import ptBR from 'date-fns/locale/pt-BR';
import axios from 'axios'; // Importar axios para fazer a requisição
import { format } from 'date-fns';
import { ptBR } from 'date-fns/locale';
import {
Container,
Box,
Typography,
CircularProgress,
Alert,
Paper,
Grid,
Divider,
Button
} from '@mui/material';
interface ProvaVerificacao {
verified: boolean;
message: string;
prova?: {
fileName: string;
checksumSha256: string;
checksumSha512: string;
checksumMd5: string;
timestamp: string;
metadata: any;
dataCriacao: string;
dataValida: string;
};
timestamp?: any;
}
export function VerificarProva() {
// Componente para verificação de provas digitais
export const VerificarProva: React.FC = () => {
const { hash } = useParams<{ hash: string }>();
const [result, setResult] = useState<ProvaVerificacao | null>(null);
const [error, setError] = useState<string | null>(null);
const [prova, setProva] = useState<any>(null);
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(() => {
const fetchProva = async () => {
const fetchProvaDetails = async () => {
try {
setLoading(true);
const response = await fetch(`/api/provas/verificar/${hash}`);
if (!response.ok) {
throw new Error(response.statusText || 'Falha na verificação');
// Obter informações de conexão do cliente para incluir nos logs
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();
console.log('Dados recebidos:', data);
setResult(data);
setProva(response.data);
setLoading(false);
} catch (error) {
console.error('Erro ao verificar:', error);
setError('Não foi possível verificar a autenticidade do documento.');
} finally {
console.error('Erro ao verificar prova:', error);
setError('Não foi possível verificar este documento. O hash pode ser inválido ou o documento não existe.');
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 {
const date = new Date(dateString);
if (!isValid(date)) {
console.warn('Data inválida:', dateString);
return 'Data inválida';
}
return format(date, 'dd/MM/yyyy HH:mm:ss', { locale: ptBR });
const data = new Date(dataString);
return format(data, 'dd/MM/yyyy HH:mm:ss', { locale: ptBR });
} catch (error) {
console.error('Erro ao formatar data:', error);
return 'Erro na formatação da data';
return 'Data inválida';
}
};
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 (
<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">
<div className="text-center mb-8">
{result.verified ? (
<>
<h2 className="text-2xl font-bold text-green-600 mb-2">Documento Verificado</h2>
<p className="text-gray-600">
Este documento foi validado digitalmente em nossa plataforma.
</p>
</>
) : (
<>
<h2 className="text-2xl font-bold text-red-600 mb-2">Verificação com Problemas</h2>
<p className="text-gray-600">
{result.message}
</p>
</>
)}
</div>
<Container maxWidth="md">
<Box mt={4} mb={4}>
{prova?.verified ? (
<Alert severity="success" variant="filled">
<Typography variant="h6">Documento Verificado com Sucesso</Typography>
<Typography>
Este documento foi validado e sua integridade foi confirmada.
</Typography>
</Alert>
) : (
<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.'}
</Typography>
</Alert>
)}
</Box>
{result.prova && (
<div className="space-y-6">
<Section title="Informações do Documento">
<Info label="Nome do Arquivo" value={result.prova.fileName} />
<Info
label="Data de Validação"
value={formatDate(result.prova.dataCriacao)}
/>
<Info
label="Última Verificação"
value={formatDate(result.prova.dataValida)}
/>
</Section>
{prova && (
<Paper elevation={3}>
<Box p={3}>
<Typography variant="h5" gutterBottom>
Detalhes do Documento
</Typography>
<Divider sx={{ mb: 2 }} />
<Grid container spacing={2}>
<Grid item xs={12} sm={6}>
<Typography variant="subtitle1" fontWeight="bold">
Nome do arquivo:
</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">
<Info label="SHA-256" value={result.prova.checksumSha256} monospace />
<Info label="SHA-512" value={result.prova.checksumSha512} monospace />
<Info label="MD5" value={result.prova.checksumMd5} monospace />
</Section>
<Grid item xs={12} sm={6}>
<Typography variant="subtitle1" fontWeight="bold">
IP:
</Typography>
<Typography gutterBottom>
{prova.verificacao?.ip || prova.prova?.metadata?.ipAddress || 'Não disponível'}
</Typography>
</Grid>
<Section title="Timestamp">
{result.timestamp ? (
<Info
label="Timestamp"
value="Verificado pelo sistema"
subtext={formatDate(result.prova.timestamp ?
JSON.parse(result.prova.timestamp).timestamp :
result.prova.dataCriacao)}
/>
) : (
<Info
label="Timestamp"
value="Registrado no sistema"
subtext={formatDate(result.prova.dataCriacao)}
/>
)}
</Section>
{result.prova.metadata && (
<Section title="Metadados do Upload">
<Info
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>
<Grid item xs={12} sm={6}>
<Typography variant="subtitle1" fontWeight="bold">
Porta:
</Typography>
<Typography gutterBottom>
{prova.verificacao?.porta || prova.prova?.metadata?.clientPort || 'Não disponível'}
</Typography>
</Grid>
</Grid>
{prova.prova?.reportPath && (
<Box mt={3} display="flex" justifyContent="center">
<Button
variant="contained"
color="primary"
href={`${API_BASE_URL}/provas/download-report/${prova.prova.checksumSha256}`}
target="_blank"
>
Baixar Relatório Completo
</Button>
</Box>
)}
</div>
</Card>
</div>
</div>
</Box>
</Paper>
)}
</Container>
);
}
};
function Section({ title, children }: { title: string; children: React.ReactNode }) {
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>
);
}
export default VerificarProva;