346 lines
11 KiB
JavaScript
Executable file
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);
|
|
});
|