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 :
|
||||
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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
Reference in new issue
Block a user