- 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)
81 lines
1.9 KiB
TypeScript
81 lines
1.9 KiB
TypeScript
import crypto from 'crypto';
|
|
import bcrypt from 'bcryptjs';
|
|
|
|
/**
|
|
* Génère un code OTP à 6 chiffres
|
|
*/
|
|
export function generateOTP(): string {
|
|
return crypto.randomInt(100000, 999999).toString();
|
|
}
|
|
|
|
/**
|
|
* Hash un code OTP avec bcrypt
|
|
*/
|
|
export async function hashOTP(otp: string): Promise<string> {
|
|
const salt = await bcrypt.genSalt(10);
|
|
return bcrypt.hash(otp, salt);
|
|
}
|
|
|
|
/**
|
|
* Vérifie un code OTP contre son hash
|
|
*/
|
|
export async function verifyOTP(otp: string, hash: string): Promise<boolean> {
|
|
return bcrypt.compare(otp, hash);
|
|
}
|
|
|
|
/**
|
|
* Calcule la date d'expiration d'un OTP (15 minutes)
|
|
*/
|
|
export function getOTPExpiration(): Date {
|
|
const now = new Date();
|
|
now.setMinutes(now.getMinutes() + 15);
|
|
return now;
|
|
}
|
|
|
|
/**
|
|
* Vérifie si un OTP est expiré
|
|
*/
|
|
export function isOTPExpired(expiresAt: string | null): boolean {
|
|
if (!expiresAt) return true;
|
|
return new Date(expiresAt) < new Date();
|
|
}
|
|
|
|
/**
|
|
* Génère une référence unique pour une demande de signature
|
|
* Format: REQ-YYYYMMDD-XXXXXX (6 caractères aléatoires)
|
|
*/
|
|
export function generateRequestRef(contractRef?: string): string {
|
|
if (contractRef) {
|
|
// Si on a une référence de contrat, l'utiliser comme base
|
|
return contractRef;
|
|
}
|
|
|
|
const date = new Date();
|
|
const dateStr = date.toISOString().slice(0, 10).replace(/-/g, '');
|
|
const random = crypto.randomBytes(3).toString('hex').toUpperCase();
|
|
return `REQ-${dateStr}-${random}`;
|
|
}
|
|
|
|
/**
|
|
* Génère un nom de fichier sécurisé pour S3
|
|
*/
|
|
export function sanitizeFilename(filename: string): string {
|
|
return filename
|
|
.replace(/[^a-zA-Z0-9._-]/g, '_')
|
|
.replace(/_{2,}/g, '_')
|
|
.substring(0, 255);
|
|
}
|
|
|
|
/**
|
|
* Calcule le SHA-256 d'un buffer
|
|
*/
|
|
export function calculateSHA256(buffer: Buffer): string {
|
|
return crypto.createHash('sha256').update(buffer).digest('hex');
|
|
}
|
|
|
|
/**
|
|
* Génère un ID de session unique
|
|
*/
|
|
export function generateSessionId(): string {
|
|
return crypto.randomBytes(32).toString('hex');
|
|
}
|