espace-paie-odentas/app/api/signatures/create-verification/route.ts

283 lines
8.2 KiB
TypeScript

import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
import QRCode from "qrcode";
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import crypto from "crypto";
const s3Client = new S3Client({
region: process.env.AWS_REGION || "eu-west-3",
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
}
});
const s3Ledger = new S3Client({
region: process.env.AWS_REGION || "eu-west-3",
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
}
});
export const runtime = "nodejs";
/**
* API pour créer une preuve de signature vérifiable
* POST /api/signatures/create-verification
*
* Body:
* {
* document_name: string,
* pdf_url: string,
* signer_name: string,
* signer_email: string,
* signature_hash: string,
* signature_hex: string,
* certificate_info: object,
* timestamp: object,
* contract_id?: string,
* organization_id: string,
* request_ref?: string // Pour nommer le fichier evidence
* }
*
* Returns:
* {
* verification_id: string,
* verification_url: string,
* qr_code_data_url: string,
* proof_pdf_url: string // URL S3 du PDF de preuve
* }
*/
export async function POST(request: Request) {
try {
const supabase = createRouteHandlerClient({ cookies });
// Vérifier l'authentification
const {
data: { session },
error: authError,
} = await supabase.auth.getSession();
if (authError || !session) {
return NextResponse.json(
{ error: "Non authentifié" },
{ status: 401 }
);
}
// Parser le body
const body = await request.json();
const {
document_name,
pdf_url,
signer_name,
signer_email,
signature_hash,
signature_hex,
certificate_info,
timestamp,
contract_id,
organization_id,
request_ref,
} = body;
// Validation
if (!document_name || !pdf_url || !signer_name || !signer_email || !signature_hash || !organization_id) {
return NextResponse.json(
{ error: "Paramètres manquants" },
{ status: 400 }
);
}
// Générer l'ID unique pour la vérification
const verificationId = crypto.randomUUID();
const lockUntilDate = new Date();
lockUntilDate.setFullYear(lockUntilDate.getFullYear() + 10); // 10 ans
// Créer le document ledger immuable
const ledgerDocument = {
verification_id: verificationId,
document: {
name: document_name,
pdf_url: pdf_url,
pdf_sha256: signature_hash,
signed_at: new Date().toISOString()
},
signer: {
name: signer_name,
email: signer_email,
ip_address: request.headers.get("x-forwarded-for") || "unknown"
},
signature: {
hash: signature_hash,
hex: signature_hex ? signature_hex.substring(0, 200) : "",
algorithm: "RSASSA-PSS-SHA256",
certificate: certificate_info || {
issuer: "Odentas Media SAS",
subject: `CN=${signer_name}`,
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: timestamp || {
tsa_url: "freetsa.org/tsr",
timestamp: new Date().toISOString(),
hash: signature_hash,
},
metadata: {
contract_id: contract_id,
organization_id: organization_id,
created_at: new Date().toISOString(),
created_by_ip: request.headers.get("x-forwarded-for") || "unknown",
user_agent: request.headers.get("user-agent") || "unknown",
request_ref: request_ref
},
ledger: {
version: "1.0",
schema: "signature_verification",
locked_until: lockUntilDate.toISOString(),
compliance_mode: true,
bucket: "odentas-signatures-ledger"
}
};
// ⭐ Upload sur S3 avec Object Lock COMPLIANCE (IMMUABLE)
const s3Key = `verifications/${verificationId}.json`;
try {
const s3Response = await s3Ledger.send(new PutObjectCommand({
Bucket: "odentas-signatures-ledger",
Key: s3Key,
Body: JSON.stringify(ledgerDocument, null, 2),
ContentType: "application/json",
// 🔒 COMPLIANCE LOCK - Document immuable pendant 10 ans
ObjectLockMode: "COMPLIANCE",
ObjectLockRetainUntilDate: lockUntilDate,
// Métadonnées S3
Metadata: {
verification_id: verificationId,
signer_email,
document_hash: signature_hash,
locked_until: lockUntilDate.toISOString()
}
}));
console.log(`[LEDGER] Document immuable créé: ${s3Key}, VersionId: ${s3Response.VersionId}`);
} catch (ledgerError) {
console.error("[LEDGER ERROR]", ledgerError);
return NextResponse.json(
{ error: "Erreur lors de la création du ledger immuable" },
{ status: 500 }
);
}
// Créer l'entrée de vérification
const { data: verification, error: insertError } = await supabase
.from("signature_verifications")
.insert({
id: verificationId,
document_name,
pdf_url,
signer_name,
signer_email,
signature_hash,
signature_hex: signature_hex || "",
certificate_info: ledgerDocument.signature.certificate,
timestamp: ledgerDocument.timestamp,
verification_status: {
seal_valid: true,
timestamp_valid: !!timestamp,
document_intact: true,
},
// ⭐ Référence vers le ledger S3 immuable
s3_ledger_key: s3Key,
s3_ledger_version_id: null, // Sera mis à jour après
s3_ledger_locked_until: lockUntilDate.toISOString(),
s3_ledger_integrity_verified: false,
contract_id,
organization_id,
})
.select()
.single();
if (insertError) {
console.error("Erreur insertion:", insertError);
return NextResponse.json(
{ error: "Erreur lors de la création de la vérification" },
{ status: 500 }
);
}
// Générer l'URL de vérification
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || "https://espace-paie.odentas.com";
const verificationUrl = `${baseUrl}/verify/${verification.id}`;
// Générer le QR code
const qrCodeDataUrl = await QRCode.toDataURL(verificationUrl, {
errorCorrectionLevel: "M",
type: "image/png",
width: 512,
margin: 2,
color: {
dark: "#1e293b",
light: "#ffffff",
},
});
// Générer le PDF de preuve et l'uploader sur S3
const { generateSignatureProofPDF } = await import("@/lib/signature-proof-pdf");
const proofPdfBlob = await generateSignatureProofPDF({
document_name,
signer_name,
signer_email,
signed_at: verification.signed_at,
signature_hash,
verification_url: verificationUrl,
qr_code_data_url: qrCodeDataUrl,
certificate_info: verification.certificate_info as any,
});
// Upload du PDF de preuve sur S3
const proofFileName = `evidence/proofs/${request_ref || verification.id}.pdf`;
const proofBuffer = Buffer.from(await proofPdfBlob.arrayBuffer());
await s3Client.send(new PutObjectCommand({
Bucket: "odentas-sign",
Key: proofFileName,
Body: proofBuffer,
ContentType: "application/pdf",
Metadata: {
verification_id: verification.id,
signer_email,
},
}));
const proofPdfUrl = `https://odentas-sign.s3.eu-west-3.amazonaws.com/${proofFileName}`;
return NextResponse.json({
verification_id: verification.id,
verification_url: verificationUrl,
qr_code_data_url: qrCodeDataUrl,
proof_pdf_url: proofPdfUrl,
ledger: {
s3_key: s3Key,
locked_until: lockUntilDate.toISOString(),
compliance_mode: true
}
});
} catch (error) {
console.error("Erreur API create-verification:", error);
return NextResponse.json(
{ error: "Erreur serveur" },
{ status: 500 }
);
}
}