283 lines
8.2 KiB
TypeScript
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 }
|
|
);
|
|
}
|
|
}
|