Improve web signing flow and locale handling

This commit is contained in:
2026-04-23 21:45:24 -05:00
parent b24d17b98e
commit 24fe8d0a0b
13 changed files with 312 additions and 42 deletions
@@ -8,18 +8,21 @@ const getCtx = (req) => ({
userName: req.user?.name || req.user?.email,
userEmail: req.user?.email || null,
frontendUrl: req.headers.origin || null,
clientLocale: req.body?.clientLocale || null,
clientTimeZone: req.body?.clientTimeZone || null,
clientCountry: req.body?.clientCountry || null,
});
const sign = async (req, res, next) => {
try {
const { documentId, certificateId, certPassword, reason, location, page, rect, signatureStyle, qrPosition, qrBackground, qrBorder, signatureImageBase64 } = req.body;
const { documentId, certificateId, certPassword, reason, location, page, rect, signatureStyle, qrPosition, qrBackground, qrBorder, signatureImageBase64, clientLocale, clientTimeZone, clientCountry } = req.body;
const docId = Number(documentId);
const certId = Number(certificateId);
if (!docId || !certId || isNaN(docId) || isNaN(certId)) {
const e = new Error('documentId and certificateId are required.'); e.statusCode = 400; throw e;
}
const result = await signaturesService.sign(
{ documentId: docId, certificateId: certId, certPassword, reason, location, page, rect, signatureStyle, qrPosition, qrBackground, qrBorder, signatureImageBase64 },
{ documentId: docId, certificateId: certId, certPassword, reason, location, page, rect, signatureStyle, qrPosition, qrBackground, qrBorder, signatureImageBase64, clientLocale, clientTimeZone, clientCountry },
req.user.id,
getCtx(req)
);
@@ -9,7 +9,7 @@ const { signPdf } = require('../signing/signing.service');
const auditService = require('../audit/audit.service');
const env = require('../../config/env');
const sign = async ({ documentId, certificateId, certPassword, reason, location, page, rect, signatureStyle, qrPosition, qrBackground, qrBorder, signatureImageBase64 }, userId, context = {}) => {
const sign = async ({ documentId, certificateId, certPassword, reason, location, page, rect, signatureStyle, qrPosition, qrBackground, qrBorder, signatureImageBase64, clientLocale, clientTimeZone, clientCountry }, userId, context = {}) => {
const doc = await documentsRepository.findById(documentId);
if (!doc) { const e = new Error('Document not found.'); e.statusCode = 404; throw e; }
if (doc.user_id !== userId) { const e = new Error('Access denied.'); e.statusCode = 403; throw e; }
@@ -50,6 +50,9 @@ const sign = async ({ documentId, certificateId, certPassword, reason, location,
qrBorder: qrBorder || 'none',
signatureImageBase64: signatureImageBase64 || null,
verificationUrl,
clientLocale: clientLocale || context.clientLocale || null,
clientTimeZone: clientTimeZone || context.clientTimeZone || null,
clientCountry: clientCountry || context.clientCountry || null,
}));
} catch (signErr) {
const msg = signErr.message || '';
+34 -3
View File
@@ -39,6 +39,34 @@ function getSignatureWidth(style, signerName = '') {
return Math.max(170, Math.min(270, Math.round(calculatedWidth)));
}
function buildLocalizedSignatureTimestamp({ locale, timeZone } = {}) {
const now = new Date();
const normalizedLocale = locale || 'es-EC';
const options = timeZone ? { timeZone } : {};
try {
return {
dateStr: now.toLocaleDateString(normalizedLocale, {
day: '2-digit',
month: '2-digit',
year: 'numeric',
...options,
}),
timeStr: now.toLocaleTimeString(normalizedLocale, {
hour: '2-digit',
minute: '2-digit',
hour12: true,
...options,
}),
};
} catch {
return {
dateStr: now.toLocaleDateString('es-EC', { day: '2-digit', month: '2-digit', year: 'numeric' }),
timeStr: now.toLocaleTimeString('es-EC', { hour: '2-digit', minute: '2-digit', hour12: true }),
};
}
}
// ── Style: CLASSIC (blue bar + DS badge) ──────────────────────────────────────
async function drawStyleClassic(pdfDoc, page, sx, sy, sw, sh, { signerName, reason, dateStr, timeStr, location }) {
const f1 = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
@@ -222,6 +250,8 @@ const signPdf = async ({
qrBackground = 'transparent',
qrBorder = 'none',
verificationUrl = null,
clientLocale = null,
clientTimeZone = null,
outputDir = null,
outputBaseName = null,
}) => {
@@ -258,9 +288,10 @@ const signPdf = async ({
};
const { x: sx, y: sy, width: sw, height: sh } = sigRect;
const now = new Date();
const dateStr = now.toLocaleDateString('es-EC', { day: '2-digit', month: '2-digit', year: 'numeric' });
const timeStr = now.toLocaleTimeString('es-EC', { hour: '2-digit', minute: '2-digit', hour12: true });
const { dateStr, timeStr } = buildLocalizedSignatureTimestamp({
locale: clientLocale,
timeZone: clientTimeZone,
});
const info = { signerName, reason, dateStr, timeStr, location, signatureImageBase64, verificationUrl, qrPosition, qrBackground, qrBorder };