espace-paie-odentas/test-odentas-sign-complete.js

346 lines
11 KiB
JavaScript
Executable file

#!/usr/bin/env node
/**
* Script de test COMPLET Odentas Sign + Vérification + Ledger
*
* Workflow complet:
* 1. Upload PDF → S3
* 2. Crée demande de signature
* 3. Affiche les liens pour signer manuellement (Employeur puis Salarié)
* 4. Attend que les 2 signatures soient faites
* 5. Lance le scellement PAdES
* 6. Crée la preuve de vérification avec ledger S3 Compliance Lock
* 7. Affiche le lien de vérification publique
*/
const fs = require('fs');
const path = require('path');
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
// Configuration
const PDF_PATH = path.join(__dirname, 'test-contrat.pdf');
const BUCKET = process.env.ODENTAS_SIGN_BUCKET || 'odentas-sign';
const REGION = process.env.AWS_REGION || 'eu-west-3';
const API_URL = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000';
// Emails pour le test
const EMPLOYEUR_EMAIL = 'paie@odentas.fr';
const SALARIE_EMAIL = 'renaud.breviere@gmail.com';
// Couleurs console
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
green: '\x1b[32m',
blue: '\x1b[34m',
yellow: '\x1b[33m',
cyan: '\x1b[36m',
red: '\x1b[31m',
};
function log(emoji, text, color = colors.reset) {
console.log(`${color}${emoji} ${text}${colors.reset}`);
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function checkSignaturesStatus(requestId) {
const response = await fetch(`${API_URL}/api/odentas-sign/requests/${requestId}/status`);
if (!response.ok) return null;
const data = await response.json();
return data;
}
async function waitForSignatures(requestId, signers) {
log('⏳', 'En attente des signatures...', colors.yellow);
console.log('');
let attempts = 0;
const maxAttempts = 120; // 10 minutes max
while (attempts < maxAttempts) {
const status = await checkSignaturesStatus(requestId);
if (status && status.signers) {
const allSigned = status.signers.every(s => s.has_signed);
const signedCount = status.signers.filter(s => s.has_signed).length;
process.stdout.write(`\r⏳ Signatures: ${signedCount}/${status.signers.length} `);
if (allSigned) {
console.log('');
log('✅', 'Toutes les signatures sont complètes !', colors.green);
return true;
}
}
await sleep(5000); // Vérifier toutes les 5 secondes
attempts++;
}
log('❌', 'Timeout: signatures non complètes après 10 minutes', colors.red);
return false;
}
async function sealDocument(requestId) {
log('🔒', 'Lancement du scellement PAdES...', colors.cyan);
const response = await fetch(`${API_URL}/api/odentas-sign/seal-document`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ requestId }),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'Erreur scellement');
}
const result = await response.json();
log('✅', `Document scellé avec ${result.signatures_count} signatures PAdES`, colors.green);
return result;
}
async function createVerificationProof(sealedPdfUrl, signatureData) {
log('📜', 'Création de la preuve de vérification avec ledger immuable...', colors.cyan);
// Simuler les données de signature (à adapter selon votre API)
const verificationData = {
document_name: signatureData.title || 'Document Test',
pdf_url: sealedPdfUrl,
signer_name: 'Odentas Media SAS',
signer_email: 'paie@odentas.fr',
signature_hash: signatureData.pdf_sha256 || 'test-hash',
signature_hex: 'test-hex-signature',
certificate_info: {
issuer: 'Odentas CA',
subject: 'CN=Odentas Media SAS',
valid_from: new Date().toISOString(),
valid_until: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString(),
serial_number: '1234567890',
},
timestamp: {
tsa_url: 'https://freetsa.org/tsr',
timestamp: new Date().toISOString(),
hash: signatureData.tsa_token_sha256 || 'test-tsa-hash',
},
};
const response = await fetch(`${API_URL}/api/signatures/create-verification`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(verificationData),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'Erreur création preuve');
}
const result = await response.json();
log('✅', 'Preuve de vérification créée avec succès', colors.green);
return result;
}
async function main() {
console.log('');
log('🚀', '═══════════════════════════════════════════════════════', colors.bright);
log('🚀', ' ODENTAS SIGN - TEST COMPLET (Signature + Ledger) ', colors.bright);
log('🚀', '═══════════════════════════════════════════════════════', colors.bright);
console.log('');
// 1. Vérifier que le PDF existe
if (!fs.existsSync(PDF_PATH)) {
log('❌', `PDF introuvable: ${PDF_PATH}`, colors.red);
process.exit(1);
}
const pdfBuffer = fs.readFileSync(PDF_PATH);
log('✅', `PDF chargé: ${(pdfBuffer.length / 1024).toFixed(1)} KB`, colors.green);
console.log('');
// 2. Upload vers S3
log('📤', 'Upload du PDF vers S3...', colors.blue);
const testRef = `TEST-${Date.now()}`;
const s3Key = `source/test/${testRef}.pdf`;
const s3Client = new S3Client({ region: REGION });
try {
await s3Client.send(new PutObjectCommand({
Bucket: BUCKET,
Key: s3Key,
Body: pdfBuffer,
ContentType: 'application/pdf',
Metadata: {
test: 'true',
uploaded_by: 'test-complete-script',
original_name: 'test-contrat.pdf',
},
}));
log('✅', `PDF uploadé: s3://${BUCKET}/${s3Key}`, colors.green);
} catch (error) {
log('❌', `Erreur upload S3: ${error.message}`, colors.red);
process.exit(1);
}
console.log('');
// 3. Créer la demande de signature
log('📝', 'Création de la demande de signature...', colors.blue);
const requestBody = {
contractId: `test-complete-${Date.now()}`,
contractRef: testRef,
pdfS3Key: s3Key,
title: 'Contrat CDDU - Test Complet',
signers: [
{
role: 'Employeur',
name: 'Odentas Paie',
email: EMPLOYEUR_EMAIL,
},
{
role: 'Salarié',
name: 'Renaud Breviere',
email: SALARIE_EMAIL,
},
],
positions: [
{
role: 'Employeur',
page: 3,
x: 20,
y: 260,
w: 150,
h: 60,
kind: 'signature',
},
{
role: 'Salarié',
page: 3,
x: 180,
y: 260,
w: 150,
h: 60,
kind: 'signature',
},
],
};
let result;
try {
const response = await fetch(`${API_URL}/api/odentas-sign/requests/create`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(requestBody),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || `HTTP ${response.status}`);
}
result = await response.json();
log('✅', 'Demande créée avec succès !', colors.green);
} catch (error) {
log('❌', `Erreur création demande: ${error.message}`, colors.red);
process.exit(1);
}
console.log('');
log('📋', '═══════════════════════════════════════════════════════', colors.bright);
log('📋', ' LIENS DE SIGNATURE (ouvrir dans le navigateur) ', colors.bright);
log('📋', '═══════════════════════════════════════════════════════', colors.bright);
console.log('');
result.signers.forEach((signer, index) => {
console.log(`${colors.cyan}${index + 1}. ${signer.role} - ${signer.name}${colors.reset}`);
console.log(` ${colors.yellow}${signer.signatureUrl}${colors.reset}`);
console.log('');
});
log('💡', 'Ouvrez ces liens et signez dans l\'ordre:', colors.yellow);
log('💡', ' 1. D\'abord l\'Employeur', colors.yellow);
log('💡', ' 2. Puis le Salarié', colors.yellow);
log('💡', 'Les codes OTP sont dans les logs du serveur (mode TEST)', colors.yellow);
console.log('');
// Sauvegarder les infos
const testInfoPath = path.join(__dirname, 'test-complete-info.json');
fs.writeFileSync(testInfoPath, JSON.stringify(result, null, 2));
log('💾', `Infos sauvegardées: ${testInfoPath}`, colors.green);
console.log('');
// 4. Attendre que les 2 signatures soient complètes
const allSigned = await waitForSignatures(result.request.id, result.signers);
if (!allSigned) {
log('❌', 'Test interrompu: signatures non complètes', colors.red);
process.exit(1);
}
console.log('');
// 5. Lancer le scellement PAdES
let sealResult;
try {
sealResult = await sealDocument(result.request.id);
} catch (error) {
log('❌', `Erreur scellement: ${error.message}`, colors.red);
process.exit(1);
}
console.log('');
// 6. Créer la preuve de vérification avec ledger
let verificationResult;
try {
verificationResult = await createVerificationProof(
sealResult.signed_s3_key,
sealResult
);
} catch (error) {
log('❌', `Erreur création preuve: ${error.message}`, colors.red);
log('⚠️', 'Le document est scellé mais la preuve n\'a pas pu être créée', colors.yellow);
process.exit(1);
}
console.log('');
log('🎉', '═══════════════════════════════════════════════════════', colors.green);
log('🎉', ' TEST COMPLET RÉUSSI ! ', colors.green);
log('🎉', '═══════════════════════════════════════════════════════', colors.green);
console.log('');
if (verificationResult) {
log('🔗', 'LIEN DE VÉRIFICATION PUBLIQUE:', colors.cyan);
console.log(` ${colors.bright}${verificationResult.verification_url}${colors.reset}`);
console.log('');
if (verificationResult.ledger) {
log('🔒', 'LEDGER IMMUABLE (S3 Compliance Lock):', colors.cyan);
console.log(` Clé S3: ${verificationResult.ledger.s3_key}`);
console.log(` Verrouillé jusqu'au: ${verificationResult.ledger.locked_until}`);
console.log(` Mode: COMPLIANCE (aucune suppression possible)`);
console.log('');
}
if (verificationResult.proof_pdf_url) {
log('📄', 'PDF DE PREUVE:', colors.cyan);
console.log(` ${verificationResult.proof_pdf_url}`);
console.log('');
}
}
log('✅', 'Document signé: ' + sealResult.signed_s3_key, colors.green);
console.log('');
}
main().catch(error => {
console.error('');
log('❌', `ERREUR FATALE: ${error.message}`, colors.red);
console.error(error.stack);
process.exit(1);
});