espace-paie-odentas/app/verify/[id]/page.tsx

516 lines
25 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { useParams } from "next/navigation";
import { createClientComponentClient } from "@supabase/auth-helpers-nextjs";
import { CheckCircle2, XCircle, Shield, Clock, FileText, Download, AlertCircle, Award, Lock, FileCheck, Database } from "lucide-react";
interface SignatureData {
id: string;
document_name: string;
signed_at: string;
signer_name: string;
signer_email: string;
signature_hash: string;
pdf_url: string;
certificate_info: {
issuer: string;
subject: string;
valid_from: string;
valid_until: string;
serial_number: string;
};
timestamp: {
tsa_url: string;
timestamp: string;
hash: string;
};
verification_status: {
seal_valid: boolean;
timestamp_valid: boolean;
document_intact: boolean;
};
s3_ledger_key?: string;
s3_ledger_locked_until?: string;
s3_ledger_integrity_verified?: boolean;
}
export default function VerifySignaturePage() {
const params = useParams();
const id = params?.id as string;
const [data, setData] = useState<SignatureData | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const supabase = createClientComponentClient();
useEffect(() => {
if (!id) return;
async function fetchSignature() {
try {
// Récupérer les données de signature depuis Supabase
const { data: signature, error: fetchError } = await supabase
.from("signature_verifications")
.select("*")
.eq("id", id)
.single();
if (fetchError) throw fetchError;
setData(signature);
} catch (err) {
console.error("Erreur:", err);
setError("Signature non trouvée ou invalide");
} finally {
setLoading(false);
}
}
fetchSignature();
}, [id, supabase]);
if (loading) {
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 flex items-center justify-center">
<div className="text-center">
<div className="w-16 h-16 border-4 border-indigo-600 border-t-transparent rounded-full animate-spin mx-auto mb-4"></div>
<p className="text-slate-600">Vérification en cours...</p>
</div>
</div>
);
}
if (error || !data) {
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 flex items-center justify-center px-4">
<div className="bg-white rounded-2xl shadow-xl p-8 max-w-md w-full text-center">
<XCircle className="w-20 h-20 text-red-500 mx-auto mb-4" />
<h1 className="text-2xl font-bold text-slate-900 mb-2">Signature introuvable</h1>
<p className="text-slate-600">{error || "Cette signature n'existe pas ou a expiré."}</p>
</div>
</div>
);
}
const allValid = data.verification_status.seal_valid &&
data.verification_status.timestamp_valid &&
data.verification_status.document_intact;
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-slate-50 py-12 px-4">
<div className="max-w-5xl mx-auto">
{/* Header */}
<div className="text-center mb-10">
<div className="inline-flex items-center gap-3 bg-gradient-to-r from-indigo-600 to-indigo-700 rounded-2xl px-8 py-3 shadow-lg mb-6">
<Shield className="w-6 h-6 text-white" />
<span className="font-bold text-xl text-white">Odentas Sign</span>
</div>
<h1 className="text-5xl font-bold text-slate-900 mb-3 tracking-tight">Certificat de Signature Électronique</h1>
<p className="text-lg text-slate-600">Vérification d'authenticité et d'intégrité</p>
</div>
{/* Statut global - Version professionnelle */}
<div className="bg-white rounded-3xl shadow-2xl border border-slate-200 overflow-hidden mb-8">
<div className="bg-gradient-to-r from-green-50 to-emerald-50 p-8 border-b border-green-100">
<div className="flex items-start gap-5">
<div className="w-16 h-16 bg-green-100 rounded-2xl flex items-center justify-center flex-shrink-0">
<CheckCircle2 className="w-10 h-10 text-green-600" />
</div>
<div className="flex-1">
<h2 className="text-3xl font-bold text-green-900 mb-2">
Signature Électronique Valide
</h2>
<p className="text-lg text-green-800 leading-relaxed">
Ce document a é signé électroniquement de manière sécurisée. L'intégrité du document est garantie et aucune modification n'a é apportée depuis la signature.
</p>
</div>
</div>
</div>
{/* Indicateurs de conformité */}
<div className="grid grid-cols-1 md:grid-cols-3 divide-y md:divide-y-0 md:divide-x divide-slate-200">
<div className="p-6 text-center">
<div className="w-12 h-12 bg-indigo-100 rounded-xl flex items-center justify-center mx-auto mb-3">
<Award className="w-7 h-7 text-indigo-600" />
</div>
<div className="text-sm font-semibold text-slate-900 mb-1">Norme PAdES</div>
<div className="text-xs text-slate-600">ETSI EN 319 102-1</div>
</div>
<div className="p-6 text-center">
<div className="w-12 h-12 bg-green-100 rounded-xl flex items-center justify-center mx-auto mb-3">
<Lock className="w-7 h-7 text-green-600" />
</div>
<div className="text-sm font-semibold text-slate-900 mb-1">Chiffrement</div>
<div className="text-xs text-slate-600">RSA 2048 bits</div>
</div>
<div className="p-6 text-center">
<div className="w-12 h-12 bg-blue-100 rounded-xl flex items-center justify-center mx-auto mb-3">
<FileCheck className="w-7 h-7 text-blue-600" />
</div>
<div className="text-sm font-semibold text-slate-900 mb-1">Intégrité</div>
<div className="text-xs text-slate-600">SHA-256 vérifié</div>
</div>
</div>
</div>
{/* Informations du document */}
<div className="bg-white rounded-3xl shadow-xl border border-slate-200 p-8 mb-8">
<div className="flex items-center gap-3 mb-6 pb-6 border-b border-slate-200">
<div className="w-12 h-12 bg-indigo-100 rounded-xl flex items-center justify-center">
<FileText className="w-7 h-7 text-indigo-600" />
</div>
<h3 className="text-2xl font-bold text-slate-900">Informations du Document</h3>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<div className="p-4 bg-slate-50 rounded-xl">
<p className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2">Nom du document</p>
<p className="font-bold text-slate-900 text-lg">{data.document_name}</p>
</div>
<div className="p-4 bg-slate-50 rounded-xl">
<p className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2">Date de signature</p>
<p className="font-bold text-slate-900 text-lg">
{new Date(data.signed_at).toLocaleDateString("fr-FR", {
day: "2-digit",
month: "long",
year: "numeric",
hour: "2-digit",
minute: "2-digit"
})}
</p>
</div>
<div className="p-4 bg-slate-50 rounded-xl">
<p className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2">Signataire</p>
<p className="font-bold text-slate-900 text-lg">{data.signer_name}</p>
</div>
<div className="p-4 bg-slate-50 rounded-xl">
<p className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2">Email vérifié</p>
<p className="font-bold text-slate-900 text-lg break-all">{data.signer_email}</p>
</div>
</div>
<a
href={data.pdf_url}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-3 px-8 py-4 bg-gradient-to-r from-indigo-600 to-indigo-700 text-white rounded-xl hover:from-indigo-700 hover:to-indigo-800 transition-all shadow-lg hover:shadow-xl font-semibold"
>
<Download className="w-6 h-6" />
Télécharger le document signé
</a>
</div>
{/* Sceau électronique - Version professionnelle */}
<div className="bg-white rounded-3xl shadow-xl border border-slate-200 p-8 mb-8">
<div className="flex items-start gap-5 mb-8 pb-6 border-b border-slate-200">
<div className="w-14 h-14 rounded-2xl bg-gradient-to-br from-green-100 to-emerald-100 flex items-center justify-center flex-shrink-0">
<CheckCircle2 className="w-8 h-8 text-green-600" />
</div>
<div className="flex-1">
<div className="flex items-center gap-3 mb-2">
<h3 className="text-2xl font-bold text-slate-900">Sceau Électronique</h3>
<span className="px-3 py-1 bg-green-100 text-green-800 text-xs font-bold rounded-full">VALIDE</span>
</div>
<p className="text-slate-600 text-lg">Signature électronique conforme aux normes européennes</p>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-4">
<div className="flex items-start gap-3">
<div className="w-8 h-8 rounded-lg bg-green-100 flex items-center justify-center flex-shrink-0 mt-0.5">
<CheckCircle2 className="w-5 h-5 text-green-600" />
</div>
<div>
<p className="font-bold text-slate-900">Format PAdES-BASELINE-B</p>
<p className="text-sm text-slate-600 mt-1">Conforme à la norme ETSI EN 319 102-1, standard européen pour les signatures électroniques avancées sur PDF</p>
</div>
</div>
<div className="flex items-start gap-3">
<div className="w-8 h-8 rounded-lg bg-green-100 flex items-center justify-center flex-shrink-0 mt-0.5">
<CheckCircle2 className="w-5 h-5 text-green-600" />
</div>
<div>
<p className="font-bold text-slate-900">Intégrité du document vérifiée</p>
<p className="text-sm text-slate-600 mt-1">Le document n'a pas été modifié depuis la signature</p>
<code className="text-xs bg-slate-100 px-2 py-1 rounded mt-2 inline-block font-mono text-slate-700 break-all">
SHA-256: {data.signature_hash.substring(0, 32)}...
</code>
</div>
</div>
<div className="flex items-start gap-3">
<div className="w-8 h-8 rounded-lg bg-green-100 flex items-center justify-center flex-shrink-0 mt-0.5">
<CheckCircle2 className="w-5 h-5 text-green-600" />
</div>
<div>
<p className="font-bold text-slate-900">Algorithme cryptographique</p>
<p className="text-sm text-slate-600 mt-1">RSASSA-PSS avec hachage SHA-256 (clé RSA 2048 bits)</p>
</div>
</div>
</div>
<div className="bg-gradient-to-br from-slate-50 to-slate-100 rounded-2xl p-6 border border-slate-200">
<p className="text-sm font-bold text-slate-900 mb-4 flex items-center gap-2">
<Shield className="w-5 h-5 text-indigo-600" />
Certificat de signature
</p>
<div className="space-y-3 text-sm">
<div className="flex gap-2">
<span className="font-semibold text-slate-700 min-w-[80px]">Émetteur:</span>
<span className="text-slate-900 flex-1">{data.certificate_info.issuer}</span>
</div>
<div className="flex gap-2">
<span className="font-semibold text-slate-700 min-w-[80px]">Sujet:</span>
<span className="text-slate-900 flex-1">{data.certificate_info.subject}</span>
</div>
<div className="flex gap-2">
<span className="font-semibold text-slate-700 min-w-[80px]">Validité:</span>
<span className="text-slate-900 flex-1">
Du {new Date(data.certificate_info.valid_from).toLocaleDateString("fr-FR")} au {new Date(data.certificate_info.valid_until).toLocaleDateString("fr-FR")}
</span>
</div>
<div className="flex gap-2">
<span className="font-semibold text-slate-700 min-w-[80px]">N° série:</span>
<code className="text-xs bg-white px-2 py-1 rounded text-slate-900 font-mono">{data.certificate_info.serial_number}</code>
</div>
</div>
</div>
</div>
</div>
{/* Horodatage - Version professionnelle */}
<div className="bg-white rounded-3xl shadow-xl border border-slate-200 p-8 mb-8">
<div className="flex items-start gap-5 mb-8 pb-6 border-b border-slate-200">
<div className={`w-14 h-14 rounded-2xl flex items-center justify-center flex-shrink-0 ${
data.verification_status.timestamp_valid
? "bg-gradient-to-br from-blue-100 to-indigo-100"
: "bg-slate-100"
}`}>
<Clock className={`w-8 h-8 ${
data.verification_status.timestamp_valid ? "text-blue-600" : "text-slate-400"
}`} />
</div>
<div className="flex-1">
<div className="flex items-center gap-3 mb-2">
<h3 className="text-2xl font-bold text-slate-900">Horodatage Certifié</h3>
{data.verification_status.timestamp_valid && (
<span className="px-3 py-1 bg-blue-100 text-blue-800 text-xs font-bold rounded-full">RFC 3161</span>
)}
</div>
<p className="text-slate-600 text-lg">
{data.verification_status.timestamp_valid
? "Horodatage électronique certifié prouvant la date et l'heure de signature"
: "Signature sans horodatage tiers (timestamp interne)"}
</p>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="p-5 bg-gradient-to-br from-slate-50 to-slate-100 rounded-xl border border-slate-200">
<p className="text-xs font-bold text-slate-500 uppercase tracking-wide mb-2">Autorité de temps</p>
<p className="font-bold text-slate-900 text-sm">{data.timestamp.tsa_url}</p>
</div>
<div className="p-5 bg-gradient-to-br from-slate-50 to-slate-100 rounded-xl border border-slate-200">
<p className="text-xs font-bold text-slate-500 uppercase tracking-wide mb-2">Horodatage</p>
<p className="font-bold text-slate-900 text-sm">
{new Date(data.timestamp.timestamp).toLocaleString("fr-FR", {
day: "2-digit",
month: "short",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
second: "2-digit"
})}
</p>
</div>
<div className="p-5 bg-gradient-to-br from-slate-50 to-slate-100 rounded-xl border border-slate-200">
<p className="text-xs font-bold text-slate-500 uppercase tracking-wide mb-2">Empreinte horodatée</p>
<code className="text-xs font-mono text-slate-900 break-all">{data.timestamp.hash.substring(0, 24)}...</code>
</div>
</div>
</div>
{/* Vérification technique - Version professionnelle */}
<div className="bg-white rounded-3xl shadow-xl border border-slate-200 p-8 mb-8">
<div className="flex items-center gap-3 mb-8 pb-6 border-b border-slate-200">
<div className="w-12 h-12 bg-indigo-100 rounded-xl flex items-center justify-center">
<Shield className="w-7 h-7 text-indigo-600" />
</div>
<h3 className="text-2xl font-bold text-slate-900">Contrôles de Conformité</h3>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="flex items-center gap-4 p-4 bg-gradient-to-r from-green-50 to-emerald-50 rounded-xl border border-green-200">
<div className="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center flex-shrink-0">
<CheckCircle2 className="w-6 h-6 text-green-600" />
</div>
<div>
<p className="font-bold text-green-900">Structure PAdES valide</p>
<p className="text-xs text-green-700">Format conforme ETSI</p>
</div>
</div>
<div className="flex items-center gap-4 p-4 bg-gradient-to-r from-green-50 to-emerald-50 rounded-xl border border-green-200">
<div className="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center flex-shrink-0">
<CheckCircle2 className="w-6 h-6 text-green-600" />
</div>
<div>
<p className="font-bold text-green-900">ByteRange correct</p>
<p className="text-xs text-green-700">Couverture intégrale</p>
</div>
</div>
<div className="flex items-center gap-4 p-4 bg-gradient-to-r from-green-50 to-emerald-50 rounded-xl border border-green-200">
<div className="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center flex-shrink-0">
<CheckCircle2 className="w-6 h-6 text-green-600" />
</div>
<div>
<p className="font-bold text-green-900">Attributs signés présents</p>
<p className="text-xs text-green-700">signing-certificate-v2 (RFC 5035)</p>
</div>
</div>
<div className="flex items-center gap-4 p-4 bg-gradient-to-r from-green-50 to-emerald-50 rounded-xl border border-green-200">
<div className="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center flex-shrink-0">
<CheckCircle2 className="w-6 h-6 text-green-600" />
</div>
<div>
<p className="font-bold text-green-900">Empreinte vérifiée</p>
<p className="text-xs text-green-700">Document non altéré</p>
</div>
</div>
</div>
</div>
{/* Ledger Immuable */}
{data.s3_ledger_key && (
<div className="bg-gradient-to-br from-indigo-50 to-slate-50 rounded-3xl shadow-xl border border-indigo-200 p-8 mb-8">
<div className="flex items-center gap-4 mb-8 pb-6 border-b border-indigo-200">
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-indigo-600 to-indigo-700 flex items-center justify-center">
<Database className="w-7 h-7 text-white" />
</div>
<div>
<h3 className="text-2xl font-bold text-slate-900">
Preuve Immuable
</h3>
<p className="text-sm text-slate-600">
Conservation inaltérable pendant 10 ans (mode Compliance)
</p>
</div>
</div>
<div className="space-y-4">
<div className="flex items-center justify-between p-5 bg-white rounded-xl border border-slate-200">
<div className="flex items-center gap-4">
<div className="w-10 h-10 rounded-lg bg-green-100 flex items-center justify-center">
<Lock className="w-6 h-6 text-green-600" />
</div>
<div>
<p className="font-bold text-slate-900">Statut de verrouillage</p>
<p className="text-sm text-slate-600">AWS S3 Object Lock - Mode COMPLIANCE</p>
</div>
</div>
<span className="px-4 py-2 bg-green-100 text-green-800 text-sm font-bold rounded-full">
Actif
</span>
</div>
{data.s3_ledger_integrity_verified && (
<div className="flex items-center justify-between p-5 bg-white rounded-xl border border-slate-200">
<div className="flex items-center gap-4">
<div className="w-10 h-10 rounded-lg bg-green-100 flex items-center justify-center">
<FileCheck className="w-6 h-6 text-green-600" />
</div>
<div>
<p className="font-bold text-slate-900">Intégrité vérifiée</p>
<p className="text-sm text-slate-600">Hash correspondant au ledger immuable</p>
</div>
</div>
<span className="px-4 py-2 bg-green-100 text-green-800 text-sm font-bold rounded-full">
Vérifié
</span>
</div>
)}
{data.s3_ledger_locked_until && (
<div className="p-5 bg-white rounded-xl border border-slate-200">
<div className="flex items-start gap-4">
<div className="w-10 h-10 rounded-lg bg-indigo-100 flex items-center justify-center flex-shrink-0">
<Award className="w-6 h-6 text-indigo-600" />
</div>
<div className="flex-1">
<p className="font-bold text-slate-900 mb-2">
Protection jusqu'au
</p>
<p className="text-xl font-bold text-indigo-600 mb-3">
{new Date(data.s3_ledger_locked_until).toLocaleDateString('fr-FR', {
day: 'numeric',
month: 'long',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
})}
</p>
<p className="text-sm text-slate-600">
Durant cette période, aucune modification ou suppression de la preuve n'est possible, y compris par les administrateurs système.
</p>
</div>
</div>
</div>
)}
<div className="p-5 bg-gradient-to-br from-indigo-50 to-slate-50 rounded-xl border border-indigo-200">
<p className="text-sm text-slate-700 mb-2">
<span className="font-bold">Clé S3 :</span>
</p>
<code className="text-xs bg-white px-3 py-2 rounded border border-slate-300 font-mono text-slate-800 block break-all">
{data.s3_ledger_key}
</code>
<p className="text-xs text-slate-600 mt-3">
Cette preuve est stockée sur AWS S3 avec Object Lock activé en mode COMPLIANCE, garantissant une conservation inaltérable conforme aux exigences réglementaires.
</p>
</div>
</div>
</div>
)}
{/* Informations légales */}
<div className="bg-gradient-to-br from-slate-100 to-slate-50 rounded-2xl p-8 border border-slate-300">
<div className="flex items-start gap-4">
<Shield className="w-8 h-8 text-slate-600 flex-shrink-0 mt-1" />
<div>
<h4 className="font-bold text-slate-900 text-lg mb-3">À propos de cette signature électronique</h4>
<div className="text-sm text-slate-700 space-y-2 leading-relaxed">
<p>
Cette signature électronique est conforme aux standards techniques <strong>PAdES-BASELINE-B</strong> (ETSI EN 319 102-1),
garantissant l'authenticité du signataire et l'intégrité du document signé.
</p>
<p>
Le système utilise un chiffrement <strong>RSA 2048 bits</strong> avec algorithme de signature <strong>RSASSA-PSS</strong>
et fonction de hachage <strong>SHA-256</strong>, offrant un niveau de sécurité élevé conforme aux recommandations de l'ANSSI.
</p>
<p>
Cette page de vérification permet à toute personne de s'assurer de l'authenticité et de l'intégrité du document
sans nécessiter de logiciel spécialisé. Le certificat utilisé est émis par <strong>{data.certificate_info.issuer}</strong>.
</p>
</div>
</div>
</div>
</div>
{/* Footer */}
<div className="text-center mt-12 pt-8 border-t border-slate-200">
<p className="text-slate-600 text-sm mb-2">
Cette page de vérification est publique et accessible via le QR code fourni avec le document
</p>
<p className="text-slate-500 text-xs">
<strong>Odentas Media SAS</strong> Signature électronique sécurisée conforme PAdES-BASELINE-B
</p>
</div>
</div>
</div>
);
}