espace-paie-odentas/create-real-signature.js
odentas b790faf12c feat: Implémentation complète du système Odentas Sign
- Remplacement de DocuSeal par solution souveraine Odentas Sign
- Système d'authentification OTP pour signataires (bcryptjs + JWT)
- 8 routes API: send-otp, verify-otp, sign, pdf-url, positions, status, webhook, signers
- Interface moderne avec canvas de signature et animations (framer-motion, confetti)
- Système de templates pour auto-détection des positions de signature (CDDU, RG, avenants)
- PDF viewer avec @react-pdf-viewer (compatible Next.js)
- Stockage S3: source/, signatures/, evidence/, signed/, certs/
- Tables Supabase: sign_requests, signers, sign_positions, sign_events, sign_assets
- Evidence bundle automatique (JSON metadata + timestamps)
- Templates emails: OTP et completion
- Scripts Lambda prêts: pades-sign (KMS seal) et tsaStamp (RFC3161)
- Mode test détecté automatiquement (emails whitelist)
- Tests complets avec PDF CDDU réel (2 signataires)
2025-10-27 19:03:07 +01:00

218 lines
6.5 KiB
JavaScript
Executable file
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* Script simple pour créer une demande depuis un PDF local
* Utilise AWS SDK comme test-odentas-sign.js
*/
const fs = require('fs');
const path = require('path');
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const API_BASE = process.env.API_BASE || 'http://localhost:3000/api/odentas-sign';
// S3 Client (utilise les credentials du .env)
const s3Client = new S3Client({
region: process.env.AWS_REGION || 'eu-west-3',
});
const BUCKET = 'odentas-sign';
/**
* Charge les templates de signature disponibles
*/
function loadTemplates() {
const templatesDir = path.join(__dirname, 'signature-templates');
const templates = [];
if (!fs.existsSync(templatesDir)) {
return templates;
}
const files = fs.readdirSync(templatesDir);
for (const file of files) {
if (file.endsWith('.json')) {
try {
const content = fs.readFileSync(path.join(templatesDir, file), 'utf-8');
templates.push(JSON.parse(content));
} catch (error) {
console.warn(`⚠️ Impossible de charger le template ${file}`);
}
}
}
return templates;
}
/**
* Détecte le template approprié pour un PDF
*/
function detectTemplate(filename, templates) {
for (const template of templates) {
const pattern = new RegExp(template.pdfPattern, 'i');
if (pattern.test(filename)) {
return template;
}
}
return null;
}
/**
* Convertit les positions du template en format API
*/
function templateToPositions(template) {
const positions = {};
template.positions.forEach(p => {
positions[p.role] = {
page: p.page,
x: p.x,
y: p.y,
width: p.width,
height: p.height,
};
});
return positions;
}
async function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.error('Usage: node create-real-signature.js <chemin-pdf>');
console.error('Exemple: node create-real-signature.js contrat_cddu_LYXHX3GI_240V001.pdf');
process.exit(1);
}
const pdfPath = args[0];
if (!fs.existsSync(pdfPath)) {
console.error(`❌ Fichier introuvable: ${pdfPath}`);
process.exit(1);
}
console.log('═══════════════════════════════════════════════════════');
console.log(' 📄 Création de signature depuis PDF réel');
console.log('═══════════════════════════════════════════════════════\n');
// Lire le PDF
const dataBuffer = fs.readFileSync(pdfPath);
const pdfSize = Math.round(dataBuffer.length / 1024);
const filename = path.basename(pdfPath);
console.log(`📖 PDF: ${filename}`);
console.log(` Taille: ${pdfSize} KB\n`);
// Charger les templates
console.log('🔍 Détection du template...');
const templates = loadTemplates();
console.log(` ${templates.length} template(s) disponible(s)`);
const template = detectTemplate(filename, templates);
let positions;
if (template) {
console.log(` ✅ Template détecté: ${template.templateName}`);
console.log(` 📝 ${template.description}`);
positions = templateToPositions(template);
} else {
console.log(' ⚠️ Aucun template trouvé, utilisation des positions par défaut');
// Positions par défaut (bas de page, centrées)
positions = {
'Employeur': { page: 1, x: 70, y: 120, width: 180, height: 70 },
'Salarié': { page: 1, x: 350, y: 120, width: 180, height: 70 },
};
}
console.log('\n📍 Positions de signature:');
Object.entries(positions).forEach(([role, pos]) => {
console.log(` ${role}: page ${pos.page}, (${pos.x}, ${pos.y}), ${pos.width}x${pos.height}px`);
});
// Upload vers S3
console.log('\n☁ Upload du PDF vers S3...');
const ref = `REAL-${Date.now()}`;
const s3Key = `source/real/${ref}.pdf`;
await s3Client.send(new PutObjectCommand({
Bucket: BUCKET,
Key: s3Key,
Body: dataBuffer,
ContentType: 'application/pdf',
Metadata: {
original_filename: filename,
ref: ref,
},
}));
console.log(` ✅ Uploadé: s3://${BUCKET}/${s3Key}`);
// Créer la demande
console.log('\n✍ Création de la demande...');
const signatureRequest = {
contractId: `CDDU-${Date.now()}`,
contractRef: ref,
pdfS3Key: s3Key,
title: `Contrat CDDU - ${filename.replace('.pdf', '')}`,
signers: [
{
name: 'Odentas Paie',
email: 'paie@odentas.fr',
role: 'Employeur',
positions: [positions['Employeur']],
},
{
name: 'Renaud Breviere',
email: 'renaud.breviere@gmail.com',
role: 'Salarié',
positions: [positions['Salarié']],
},
],
};
const response = await fetch(`${API_BASE}/requests/create`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(signatureRequest),
});
if (!response.ok) {
const error = await response.json();
console.error('\n❌ Erreur:', error);
process.exit(1);
}
const result = await response.json();
// Sauvegarder les infos
const infoFile = 'signature-real-info.json';
fs.writeFileSync(infoFile, JSON.stringify(result, null, 2));
console.log('\n═══════════════════════════════════════════════════════');
console.log(' ✅ Demande créée avec succès !');
console.log('═══════════════════════════════════════════════════════\n');
console.log(`📋 Référence: ${result.request.ref}`);
console.log(`📝 ID: ${result.request.id}\n`);
console.log('🔗 URLs de signature:\n');
result.signers.forEach(signer => {
const localUrl = signer.signatureUrl.replace(
'https://espace-paie.odentas.fr',
'http://localhost:3000'
);
console.log(`${signer.role} (${signer.email}):`);
console.log(` ${localUrl}\n`);
});
console.log(`💾 Informations sauvegardées dans: ${infoFile}`);
console.log('\n🚀 Pour tester:');
console.log(' 1. Ouvrir une des URLs ci-dessus');
console.log(' 2. Recevoir et valider l\'OTP (affiché dans les logs)');
console.log(' 3. Dessiner et valider la signature');
console.log(' 4. Répéter pour le 2ème signataire\n');
}
main().catch(console.error);