281 lines
8.9 KiB
JavaScript
281 lines
8.9 KiB
JavaScript
/**
|
||
* Test complet du système de signature et vérification
|
||
*
|
||
* Ce script :
|
||
* 1. Lit le fichier test-contrat.pdf
|
||
* 2. Appelle la Lambda Odentas Sign pour signer
|
||
* 3. Extrait le hash de signature du PDF signé
|
||
* 4. Upload le PDF signé sur S3
|
||
* 5. Crée l'entrée de vérification via l'API
|
||
* 6. Génère le PDF de preuve avec QR code
|
||
* 7. Sauvegarde les fichiers résultants
|
||
*/
|
||
|
||
import fs from 'fs';
|
||
import path from 'path';
|
||
import { fileURLToPath } from 'url';
|
||
import crypto from 'crypto';
|
||
import AWS from 'aws-sdk';
|
||
|
||
const __filename = fileURLToPath(import.meta.url);
|
||
const __dirname = path.dirname(__filename);
|
||
|
||
// Configuration AWS
|
||
AWS.config.update({
|
||
region: 'eu-west-3',
|
||
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
||
});
|
||
|
||
const lambda = new AWS.Lambda();
|
||
const s3 = new AWS.S3();
|
||
|
||
// Configuration
|
||
const TEST_PDF_PATH = path.join(__dirname, 'test-contrat.pdf');
|
||
const OUTPUT_DIR = path.join(__dirname, 'test-signature-output');
|
||
const S3_BUCKET = 'odentas-espace-paie-documents';
|
||
|
||
async function main() {
|
||
console.log('🚀 Test complet du système de signature électronique\n');
|
||
|
||
// Créer le dossier de sortie
|
||
if (!fs.existsSync(OUTPUT_DIR)) {
|
||
fs.mkdirSync(OUTPUT_DIR);
|
||
}
|
||
|
||
// Étape 1 : Lire le PDF à signer
|
||
console.log('📄 Étape 1 : Lecture du PDF à signer...');
|
||
const pdfBuffer = fs.readFileSync(TEST_PDF_PATH);
|
||
console.log(` ✅ PDF lu (${pdfBuffer.length} bytes)\n`);
|
||
|
||
// Étape 2 : Upload du PDF source sur S3
|
||
console.log('☁️ Étape 2 : Upload du PDF source sur S3...');
|
||
const sourceKey = await uploadToS3ForSigning(pdfBuffer);
|
||
console.log(` ✅ S3 Key : ${sourceKey}\n`);
|
||
|
||
// Étape 3 : Signer le PDF avec Odentas Sign
|
||
console.log('✍️ Étape 3 : Signature du PDF avec Odentas Sign...');
|
||
const signedS3Key = await signPdfWithLambda(sourceKey);
|
||
console.log(` ✅ PDF signé : ${signedS3Key}\n`);
|
||
|
||
// Étape 4 : Télécharger le PDF signé
|
||
console.log('📥 Étape 4 : Téléchargement du PDF signé...');
|
||
const signedPdfBuffer = await downloadFromS3(signedS3Key);
|
||
|
||
const signedPdfPath = path.join(OUTPUT_DIR, 'test-contrat-signe.pdf');
|
||
fs.writeFileSync(signedPdfPath, signedPdfBuffer);
|
||
console.log(` ✅ PDF signé sauvegardé : ${signedPdfPath}`);
|
||
console.log(` 📊 Taille : ${signedPdfBuffer.length} bytes\n`);
|
||
|
||
// Étape 5 : Extraire le hash de signature
|
||
console.log('🔍 Étape 5 : Extraction du hash de signature...');
|
||
const signatureHash = extractSignatureHash(signedPdfBuffer);
|
||
console.log(` ✅ Hash SHA-256 : ${signatureHash}\n`);
|
||
|
||
// Étape 6 : Générer URL présignée
|
||
console.log('🔗 Étape 6 : Génération URL présignée...');
|
||
const s3Url = await getPresignedUrl(signedS3Key);
|
||
console.log(` ✅ URL S3 : ${s3Url}\n`);
|
||
|
||
// Étape 7 : Créer l'entrée de vérification
|
||
console.log('🔐 Étape 7 : Création de la preuve de signature...');
|
||
const verificationData = {
|
||
document_name: 'Test Contrat CDDU - Jean Dupont',
|
||
pdf_url: s3Url,
|
||
signer_name: 'Jean Dupont',
|
||
signer_email: 'jean.dupont@example.com',
|
||
signature_hash: signatureHash,
|
||
signature_hex: signedPdfBuffer.toString('hex').substring(0, 200), // Premiers 200 chars
|
||
certificate_info: {
|
||
issuer: 'CN=Odentas Media SAS, O=Odentas Media, C=FR',
|
||
subject: 'CN=Jean Dupont, O=Odentas Media SAS',
|
||
valid_from: new Date().toISOString(),
|
||
valid_until: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString(),
|
||
serial_number: crypto.randomBytes(8).toString('hex'),
|
||
},
|
||
timestamp: {
|
||
tsa_url: 'freetsa.org/tsr',
|
||
timestamp: new Date().toISOString(),
|
||
hash: signatureHash,
|
||
},
|
||
organization_id: '550e8400-e29b-41d4-a716-446655440000', // UUID test
|
||
};
|
||
|
||
console.log(' 📝 Données de vérification préparées\n');
|
||
|
||
// Étape 8 : Générer le PDF de preuve (simulation)
|
||
console.log('📄 Étape 8 : Génération du PDF de preuve...');
|
||
console.log(' ℹ️ Le PDF de preuve sera généré par l\'API Next.js avec le QR code');
|
||
console.log(' 📍 Emplacement S3 : s3://odentas-sign/evidence/proofs/');
|
||
console.log(` 📝 Nom du fichier : TEST-${Date.now()}.pdf`);
|
||
console.log(' ✅ Structure prête\n');
|
||
|
||
// Étape 9 : Afficher les résultats
|
||
console.log('✅ TEST COMPLET TERMINÉ !\n');
|
||
console.log('📦 Fichiers générés :');
|
||
console.log(` • PDF signé : ${signedPdfPath}`);
|
||
console.log(` • URL S3 : ${s3Url}`);
|
||
console.log(`\n🔗 Prochaines étapes :`);
|
||
console.log(' 1. Appliquer la migration Supabase');
|
||
console.log(' 2. Lancer le serveur dev : npm run dev');
|
||
console.log(' 3. Visiter : http://localhost:3000/test-signature-verification');
|
||
console.log(' 4. Coller ces données dans le formulaire pour créer la preuve\n');
|
||
|
||
// Sauvegarder les données de test
|
||
const testDataPath = path.join(OUTPUT_DIR, 'verification-data.json');
|
||
fs.writeFileSync(testDataPath, JSON.stringify(verificationData, null, 2));
|
||
console.log(` 📄 Données sauvegardées : ${testDataPath}\n`);
|
||
}
|
||
|
||
async function signPdfWithLambda(sourceKey) {
|
||
const params = {
|
||
FunctionName: 'odentas-pades-sign',
|
||
InvocationType: 'RequestResponse',
|
||
Payload: JSON.stringify({
|
||
sourceKey: sourceKey,
|
||
requestRef: `TEST-${Date.now()}`,
|
||
}),
|
||
};
|
||
|
||
console.log(' ⏳ Appel de la Lambda odentas-pades-sign...');
|
||
|
||
try {
|
||
const result = await lambda.invoke(params).promise();
|
||
|
||
if (result.FunctionError) {
|
||
const errorPayload = JSON.parse(result.Payload);
|
||
throw new Error(`Lambda error: ${errorPayload.errorMessage}`);
|
||
}
|
||
|
||
const response = JSON.parse(result.Payload);
|
||
|
||
if (response.statusCode !== 200) {
|
||
const body = JSON.parse(response.body);
|
||
throw new Error(`Lambda returned status ${response.statusCode}: ${JSON.stringify(body)}`);
|
||
}
|
||
|
||
const body = JSON.parse(response.body);
|
||
|
||
if (!body.signed_pdf_s3_key) {
|
||
throw new Error(`Missing signed_pdf_s3_key in response. Body: ${JSON.stringify(body)}`);
|
||
}
|
||
|
||
console.log(` 📊 SHA-256: ${body.sha256}`);
|
||
return body.signed_pdf_s3_key;
|
||
} catch (error) {
|
||
console.error(' ❌ Erreur lors de la signature:', error.message);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
function extractSignatureHash(pdfBuffer) {
|
||
// Extraire le contenu signé via ByteRange
|
||
const pdfString = pdfBuffer.toString('latin1');
|
||
|
||
// Trouver le ByteRange
|
||
const byteRangeMatch = pdfString.match(/\/ByteRange\s*\[\s*(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s*\]/);
|
||
|
||
if (!byteRangeMatch) {
|
||
console.warn(' ⚠️ ByteRange non trouvé, calcul du hash sur tout le PDF');
|
||
return crypto.createHash('sha256').update(pdfBuffer).digest('hex');
|
||
}
|
||
|
||
const [_, o1, l1, o2, l2] = byteRangeMatch.map(Number);
|
||
|
||
console.log(` 📍 ByteRange trouvé : [${o1}, ${l1}, ${o2}, ${l2}]`);
|
||
|
||
// Extraire les deux segments
|
||
const segment1 = pdfBuffer.slice(o1, o1 + l1);
|
||
const segment2 = pdfBuffer.slice(o2, o2 + l2);
|
||
|
||
// Calculer le hash
|
||
const hash = crypto.createHash('sha256');
|
||
hash.update(segment1);
|
||
hash.update(segment2);
|
||
|
||
return hash.digest('hex');
|
||
}
|
||
|
||
async function uploadToS3ForSigning(buffer) {
|
||
const filename = `source/test-contrat-${Date.now()}.pdf`;
|
||
|
||
const params = {
|
||
Bucket: 'odentas-sign', // Bucket de la Lambda
|
||
Key: filename,
|
||
Body: buffer,
|
||
ContentType: 'application/pdf',
|
||
};
|
||
|
||
console.log(` ⏳ Upload vers s3://odentas-sign/${filename}...`);
|
||
|
||
try {
|
||
await s3.putObject(params).promise();
|
||
return filename;
|
||
} catch (error) {
|
||
console.error(' ❌ Erreur lors de l\'upload S3:', error.message);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
async function downloadFromS3(key) {
|
||
const params = {
|
||
Bucket: 'odentas-sign',
|
||
Key: key,
|
||
};
|
||
|
||
console.log(` ⏳ Téléchargement depuis s3://odentas-sign/${key}...`);
|
||
|
||
try {
|
||
const data = await s3.getObject(params).promise();
|
||
return data.Body;
|
||
} catch (error) {
|
||
console.error(' ❌ Erreur lors du téléchargement S3:', error.message);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
async function getPresignedUrl(key) {
|
||
const params = {
|
||
Bucket: 'odentas-sign',
|
||
Key: key,
|
||
Expires: 7 * 24 * 60 * 60, // 7 jours
|
||
};
|
||
|
||
return s3.getSignedUrl('getObject', params);
|
||
}
|
||
|
||
async function uploadToS3(buffer, prefix) {
|
||
const filename = `${prefix}/test-contrat-${Date.now()}.pdf`;
|
||
|
||
const params = {
|
||
Bucket: S3_BUCKET,
|
||
Key: filename,
|
||
Body: buffer,
|
||
ContentType: 'application/pdf',
|
||
ACL: 'private',
|
||
};
|
||
|
||
console.log(` ⏳ Upload vers s3://${S3_BUCKET}/${filename}...`);
|
||
|
||
try {
|
||
await s3.putObject(params).promise();
|
||
|
||
// Générer une URL présignée (valide 7 jours)
|
||
const presignedUrl = s3.getSignedUrl('getObject', {
|
||
Bucket: S3_BUCKET,
|
||
Key: filename,
|
||
Expires: 7 * 24 * 60 * 60, // 7 jours
|
||
});
|
||
|
||
return presignedUrl;
|
||
} catch (error) {
|
||
console.error(' ❌ Erreur lors de l\'upload S3:', error.message);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// Exécuter
|
||
main().catch(error => {
|
||
console.error('\n❌ ERREUR FATALE:', error);
|
||
process.exit(1);
|
||
});
|