- Page publique /verify/[id] affichant Odentas Seal, TSA, certificat - API /api/signatures/create-verification pour créer preuves - Générateur PDF de preuve avec QR code (jsPDF) - Hook useSignatureProof() pour intégration facile - Table Supabase signature_verifications avec RLS public - Page de test /test-signature-verification - Documentation complète du système Les signataires peuvent scanner le QR code ou visiter l'URL pour vérifier l'authenticité et l'intégrité de leur document signé.
204 lines
6 KiB
TypeScript
204 lines
6 KiB
TypeScript
import { jsPDF } from "jspdf";
|
|
|
|
interface SignatureProofData {
|
|
document_name: string;
|
|
signer_name: string;
|
|
signer_email: string;
|
|
signed_at: string;
|
|
signature_hash: string;
|
|
verification_url: string;
|
|
qr_code_data_url: string;
|
|
certificate_info: {
|
|
issuer: string;
|
|
subject: string;
|
|
valid_from: string;
|
|
valid_until: string;
|
|
serial_number: string;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Génère un PDF "Preuve de Signature" avec QR code
|
|
*/
|
|
export async function generateSignatureProofPDF(data: SignatureProofData): Promise<Blob> {
|
|
const doc = new jsPDF({
|
|
orientation: "portrait",
|
|
unit: "mm",
|
|
format: "a4",
|
|
});
|
|
|
|
const pageWidth = doc.internal.pageSize.getWidth();
|
|
const pageHeight = doc.internal.pageSize.getHeight();
|
|
const margin = 20;
|
|
|
|
// Couleurs Odentas
|
|
const primaryColor = [99, 102, 241]; // Indigo
|
|
const textColor = [30, 41, 59]; // Slate-800
|
|
const lightGray = [241, 245, 249]; // Slate-100
|
|
|
|
// En-tête avec logo
|
|
doc.setFillColor(...primaryColor);
|
|
doc.rect(0, 0, pageWidth, 40, "F");
|
|
|
|
doc.setTextColor(255, 255, 255);
|
|
doc.setFontSize(24);
|
|
doc.setFont("helvetica", "bold");
|
|
doc.text("Odentas Sign", pageWidth / 2, 20, { align: "center" });
|
|
|
|
doc.setFontSize(12);
|
|
doc.setFont("helvetica", "normal");
|
|
doc.text("Preuve de Signature Électronique", pageWidth / 2, 30, { align: "center" });
|
|
|
|
// Titre principal
|
|
doc.setTextColor(...textColor);
|
|
doc.setFontSize(18);
|
|
doc.setFont("helvetica", "bold");
|
|
doc.text("Certificat de Signature Électronique", margin, 60);
|
|
|
|
// Zone d'information document
|
|
let y = 75;
|
|
doc.setFillColor(...lightGray);
|
|
doc.roundedRect(margin, y, pageWidth - 2 * margin, 45, 3, 3, "F");
|
|
|
|
doc.setFontSize(10);
|
|
doc.setFont("helvetica", "bold");
|
|
doc.setTextColor(...primaryColor);
|
|
doc.text("DOCUMENT SIGNÉ", margin + 5, y + 8);
|
|
|
|
doc.setFont("helvetica", "normal");
|
|
doc.setTextColor(...textColor);
|
|
doc.setFontSize(9);
|
|
|
|
doc.text(`Nom du document :`, margin + 5, y + 16);
|
|
doc.setFont("helvetica", "bold");
|
|
doc.text(data.document_name, margin + 40, y + 16);
|
|
|
|
doc.setFont("helvetica", "normal");
|
|
doc.text(`Date de signature :`, margin + 5, y + 23);
|
|
doc.setFont("helvetica", "bold");
|
|
doc.text(
|
|
new Date(data.signed_at).toLocaleString("fr-FR", {
|
|
day: "2-digit",
|
|
month: "long",
|
|
year: "numeric",
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
}),
|
|
margin + 40,
|
|
y + 23
|
|
);
|
|
|
|
doc.setFont("helvetica", "normal");
|
|
doc.text(`Signataire :`, margin + 5, y + 30);
|
|
doc.setFont("helvetica", "bold");
|
|
doc.text(data.signer_name, margin + 40, y + 30);
|
|
|
|
doc.setFont("helvetica", "normal");
|
|
doc.text(`Email :`, margin + 5, y + 37);
|
|
doc.setFont("helvetica", "bold");
|
|
doc.text(data.signer_email, margin + 40, y + 37);
|
|
|
|
// QR Code (plus grand et centré)
|
|
y = 130;
|
|
const qrSize = 70;
|
|
const qrX = (pageWidth - qrSize) / 2;
|
|
|
|
doc.addImage(data.qr_code_data_url, "PNG", qrX, y, qrSize, qrSize);
|
|
|
|
doc.setFontSize(10);
|
|
doc.setFont("helvetica", "bold");
|
|
doc.setTextColor(...primaryColor);
|
|
doc.text("Scannez pour vérifier", pageWidth / 2, y + qrSize + 8, { align: "center" });
|
|
|
|
doc.setFontSize(8);
|
|
doc.setFont("helvetica", "normal");
|
|
doc.setTextColor(...textColor);
|
|
doc.text("Ou visitez :", pageWidth / 2, y + qrSize + 14, { align: "center" });
|
|
|
|
doc.setTextColor(...primaryColor);
|
|
doc.setFont("helvetica", "bold");
|
|
// Tronquer l'URL si trop longue
|
|
const urlDisplay = data.verification_url.length > 60
|
|
? data.verification_url.substring(0, 57) + "..."
|
|
: data.verification_url;
|
|
doc.text(urlDisplay, pageWidth / 2, y + qrSize + 19, { align: "center" });
|
|
|
|
// Détails techniques
|
|
y = 220;
|
|
doc.setFontSize(12);
|
|
doc.setFont("helvetica", "bold");
|
|
doc.setTextColor(...textColor);
|
|
doc.text("Détails Techniques", margin, y);
|
|
|
|
y += 7;
|
|
doc.setFontSize(8);
|
|
doc.setFont("helvetica", "normal");
|
|
|
|
doc.text(`Format : PAdES-BASELINE-B (ETSI TS 102 778)`, margin, y);
|
|
y += 5;
|
|
doc.text(`Algorithme : RSASSA-PSS avec SHA-256`, margin, y);
|
|
y += 5;
|
|
doc.text(`Empreinte SHA-256 :`, margin, y);
|
|
y += 4;
|
|
doc.setFont("courier", "normal");
|
|
doc.setFontSize(7);
|
|
doc.text(data.signature_hash, margin, y);
|
|
|
|
y += 8;
|
|
doc.setFont("helvetica", "bold");
|
|
doc.setFontSize(8);
|
|
doc.text("Certificat de signature :", margin, y);
|
|
y += 5;
|
|
doc.setFont("helvetica", "normal");
|
|
doc.text(`Émetteur : ${data.certificate_info.issuer}`, margin + 3, y);
|
|
y += 4;
|
|
doc.text(`Sujet : ${data.certificate_info.subject}`, margin + 3, y);
|
|
y += 4;
|
|
doc.text(
|
|
`Validité : du ${new Date(data.certificate_info.valid_from).toLocaleDateString("fr-FR")} au ${new Date(
|
|
data.certificate_info.valid_until
|
|
).toLocaleDateString("fr-FR")}`,
|
|
margin + 3,
|
|
y
|
|
);
|
|
y += 4;
|
|
doc.text(`N° de série : ${data.certificate_info.serial_number}`, margin + 3, y);
|
|
|
|
// Note importante
|
|
y = pageHeight - 40;
|
|
doc.setFillColor(255, 243, 224); // Orange-50
|
|
doc.roundedRect(margin, y, pageWidth - 2 * margin, 20, 2, 2, "F");
|
|
|
|
doc.setFontSize(7);
|
|
doc.setFont("helvetica", "bold");
|
|
doc.setTextColor(194, 65, 12); // Orange-700
|
|
doc.text("NOTE IMPORTANTE", margin + 3, y + 5);
|
|
|
|
doc.setFont("helvetica", "normal");
|
|
doc.setTextColor(124, 45, 18); // Orange-900
|
|
const noteText = `Cette signature est techniquement conforme au standard PAdES-BASELINE-B. Le certificat utilisé est auto-signé et n'est pas reconnu par les autorités de certification européennes. Pour une validation complète avec reconnaissance légale, un certificat qualifié serait nécessaire.`;
|
|
|
|
const splitNote = doc.splitTextToSize(noteText, pageWidth - 2 * margin - 6);
|
|
doc.text(splitNote, margin + 3, y + 10);
|
|
|
|
// Footer
|
|
doc.setFontSize(8);
|
|
doc.setTextColor(100, 116, 139); // Slate-500
|
|
doc.setFont("helvetica", "normal");
|
|
doc.text(
|
|
`Odentas Media SAS - Signature électronique sécurisée`,
|
|
pageWidth / 2,
|
|
pageHeight - 10,
|
|
{ align: "center" }
|
|
);
|
|
|
|
doc.setFontSize(7);
|
|
doc.text(
|
|
`Document généré le ${new Date().toLocaleString("fr-FR")}`,
|
|
pageWidth / 2,
|
|
pageHeight - 5,
|
|
{ align: "center" }
|
|
);
|
|
|
|
return doc.output("blob");
|
|
}
|