Merge pull request #1292 from nxglabs/sync-to-public_repo-17730712175

Merge pull request #1291 from nxglabs/staging
This commit is contained in:
prafull-opensignlabs
2025-09-15 14:51:16 +00:00
parent 6cb2d69566
commit ca4ccfd889
45 changed files with 2541 additions and 2240 deletions
@@ -2,9 +2,12 @@ import express from 'express';
import cors from 'cors';
import dotenv from 'dotenv';
import uploadFile from './uploadFile.js';
import docxtopdf, { upload as docxUpload } from './docxtopdf.js';
import decryptpdf, { upload as decryptUpload } from './decryptpdf.js';
import { deleteUserByAdmin, deleteUserGet, deleteUserPost } from './deleteUser.js';
import { deleteUserByAdmin, deleteUserPost } from './deleteAccount/deleteUser.js';
import { deleteUserGet } from './deleteAccount/deleteUserGet.js';
import { deleteUserOtp } from './deleteAccount/deleteUserOtp.js';
export const app = express();
@@ -17,5 +20,6 @@ app.post('/file_upload', uploadFile);
app.post('/docxtopdf', docxUpload.single('file'), docxtopdf);
app.post('/decryptpdf', decryptUpload.single('file'), decryptpdf);
app.get('/delete-account/:userId', deleteUserGet);
app.post('/delete-account/:userId/otp', deleteUserOtp);
app.post('/delete-account/:userId', deleteUserPost);
app.post('/deleteuser/:userId', deleteUserByAdmin);
@@ -1,7 +1,7 @@
import { S3Client, DeleteObjectCommand } from '@aws-sdk/client-s3';
import fs from 'node:fs/promises';
import pLimit from 'p-limit';
import { serverAppId } from '../../Utils.js';
import { serverAppId } from '../../../Utils.js';
// === Configuration ===
const serverHost = new URL(process.env.SERVER_URL).hostname;
@@ -1,7 +1,8 @@
import axios from 'axios';
import { cloudServerUrl, generateId, serverAppId } from '../../Utils.js';
import sendmailtoSupport from './sendMailToSupport.js';
import { cloudServerUrl, generateId, serverAppId } from '../../../Utils.js';
import sendmailtoSupport from '../sendMailToSupport.js';
import { deleteContactsInBatch, deleteDataFiles, deleteInBatches } from './deleteFileUrl.js';
import { MAX_ATTEMPTS } from './deleteUtils.js';
const serverUrl = cloudServerUrl;
const appId = serverAppId;
@@ -250,108 +251,10 @@ export async function deleteUser(userId, adminId) {
}
}
// 1. HTML Password Prompt Page
export const deleteUserGet = async (req, res) => {
const { userId } = req.params;
const extUserQuery = new Parse.Query('contracts_Users');
extUserQuery.equalTo('UserId', { __type: 'Pointer', className: '_User', objectId: userId });
const extUser = await extUserQuery.first({ useMasterKey: true });
if (!extUser) {
const errorMessage = 'User not found.';
return res.send(errorMessage);
}
const routePath = process?.env?.SERVER_URL?.includes?.('api') ? '/api' : '';
const htmlForm = `
<html>
<head>
<title>Delete Account</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f8f9fa;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.container {
background-color: #ffffff;
padding: 40px;
border-radius: 8px;
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 400px;
text-align: center;
}
h2 {
color: #dc3545;
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 10px;
font-weight: 600;
}
input[type="password"] {
width: 100%;
padding: 12px;
margin-bottom: 20px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 16px;
}
button {
background-color: #d9534f;
color: #ffffff;
border: none;
padding: 12px 20px;
font-size: 16px;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s ease;
}
button:hover {
background-color: #c9302c;
}
.warning {
color: #6c757d;
font-size: 14px;
margin-top: -10px;
margin-bottom: 20px;
}
</style>
</head>
<body>
<div class="container">
<h2>Confirm Account Deletion</h2>
<p class="warning">This action is irreversible. Please confirm by entering your password.</p>
<form method="POST" action="${routePath}/delete-account/${userId}">
<label for="password">Password</label>
<input type="password" name="password" id="password" placeholder="Please provide your password" required />
<button type="submit">Delete My Account</button>
</form>
</div>
</body>
</html>
`;
res.send(htmlForm);
};
// 2. Handle Password Verification and Deletion
export const deleteUserPost = async (req, res) => {
const { userId } = req.params;
const routePath = process?.env?.SERVER_URL?.includes?.('api') ? '/api' : '';
const { password } = req.body;
const { otp } = req.body;
let userDetails = {
UserRole: 'not found',
Name: 'not found',
@@ -375,27 +278,51 @@ export const deleteUserPost = async (req, res) => {
}
const extUserQuery = new Parse.Query('contracts_Users');
extUserQuery.equalTo('UserId', { __type: 'Pointer', className: '_User', objectId: userId });
const extUser = await extUserQuery.first({ useMasterKey: true });
if (!extUser) {
const errorMessage = 'User not found.';
return res.send(errorMessage);
}
// 2. Attempt login to verify password
const username = user.get('username'); // assuming 'username' is used for login
// Get stored OTP info
const savedOtp = extUser.get('DeleteOTP') || '';
const expiry = extUser.get('DeleteOTPExpiry');
const tries = Number(extUser.get('DeleteOTPTries') || 0);
if (tries >= MAX_ATTEMPTS) {
return res.status(429).send('Too many invalid attempts. Please resend OTP and try again.');
}
if (!otp || typeof otp !== 'string') {
// Count attempt
extUser.set('DeleteOTPTries', tries + 1);
await extUser.save(null, { useMasterKey: true });
return res.status(400).send('OTP is required.');
}
if (!savedOtp) {
return res.status(400).send('No OTP found. Please request a new OTP.');
}
if (expiry && Date.now() > expiry.getTime()) {
return res.status(400).send('OTP has expired. Please request a new OTP.');
}
if (otp !== savedOtp) {
// Increment tries on mismatch
extUser.set('DeleteOTPTries', tries + 1);
await extUser.save(null, { useMasterKey: true });
return res.status(400).send('Invalid OTP.');
}
// 2. Remove OTP related data
try {
// await Parse.User.logIn(username, password); // Will throw if password invalid
// Use REST login to avoid mutating the global Parse current user
// Will throw if password invalid
const res = await axios.get(serverUrl + '/login', {
params: { username, password },
headers: { 'X-Parse-Application-Id': appId },
});
console.log('Res ', res?.data);
extUser.unset('DeleteOTP');
extUser.unset('DeleteOTPExpiry');
extUser.unset('DeleteOTPSentAt');
extUser.unset('DeleteOTPTries');
await extUser.save(null, { useMasterKey: true });
} catch (err) {
console.log('err while validating password: ', err?.response?.data || err);
const errorMessage = `Invalid password. <a href="${routePath}/delete-account/${userId}">Try again</a>`;
sendmailtoSupport(userDetails, errorMessage);
return res.status(401).send(errorMessage);
// sendmailtoSupport(userDetails, errorMessage);
// return res.status(401).send(errorMessage);
}
const response = await deleteUser(userId);
@@ -0,0 +1,143 @@
import { OTP_LENGTH, RESEND_COOLDOWN_SEC } from './deleteUtils.js';
// 1. HTML Password Prompt Page
export const deleteUserGet = async (req, res) => {
const { userId } = req.params;
const extUserQuery = new Parse.Query('contracts_Users');
extUserQuery.equalTo('UserId', { __type: 'Pointer', className: '_User', objectId: userId });
const extUser = await extUserQuery.first({ useMasterKey: true });
if (!extUser) return res.status(404).send('User not found.');
const routePath = process?.env?.SERVER_URL?.includes?.('api') ? '/api' : '';
const htmlForm = `
<html>
<head>
<title>Delete Account</title>
<style>
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: #f8f9fa; margin: 0; padding: 0; display: flex; justify-content: center; align-items: center; height: 100vh; }
.container { background-color: #ffffff; padding: 40px; border-radius: 8px; box-shadow: 0 8px 16px rgba(0,0,0,0.1); width: 100%; max-width: 420px; text-align: center; }
h2 { color: #dc3545; margin-bottom: 14px; }
p.warning { color: #6c757d; font-size: 14px; margin-top: 0; margin-bottom: 18px; }
label { display: block; margin-bottom: 10px; font-weight: 600; text-align: left; }
input[type="text"] { width: 100%; padding: 12px; margin-bottom: 16px; border: 1px solid #ccc; border-radius: 4px; font-size: 16px; letter-spacing: 0.2em; }
button { background-color: #d9534f; color: #ffffff; border: none; padding: 12px 16px; font-size: 16px; border-radius: 4px; cursor: pointer; transition: background-color 0.3s ease; }
button:hover { background-color: #c9302c; }
.secondary { background-color: #6c757d; }
.secondary:hover { background-color: #5a6268; }
.btn-block { width: 100%; }
.link-btn { background: transparent; border: none; color: #0d6efd; text-decoration: underline; padding: 0; font-size: 14px; cursor: pointer; }
.link-btn[disabled] { color: #6c757d; text-decoration: none; cursor: not-allowed; }
.muted { color: #6c757d; font-size: 13px; margin-top: 8px; min-height: 18px; }
.error { color: #c9302c; font-size: 13px; margin-top: 8px; min-height: 18px; }
.hidden { display: none; }
</style>
</head>
<body>
<div class="container">
<h2>Confirm Account Deletion</h2>
<!-- Initial view: only Send OTP -->
<div id="preOtp">
<p class="warning">This action is irreversible. A verification mail will be sent to your account to your registered email.</p>
<button type="button" id="sendOtpBtn" class="secondary btn-block">Send OTP</button>
<div id="preMsg" class="muted"></div>
<div id="preErr" class="error"></div>
</div>
<!-- Shown after OTP is sent -->
<form id="otpForm" class="hidden" method="POST" action="${routePath}/delete-account/${userId}">
<p class="warning">This action is irreversible. Verify with an OTP sent to your registered email.</p>
<label for="otp">One-Time Password (OTP)</label>
<input type="text" name="otp" id="otp" placeholder="Enter ${OTP_LENGTH}-digit OTP" required maxlength="${OTP_LENGTH}" inputmode="numeric" />
<div style="text-align:center; margin-top: 10px;">
<button type="submit" id="deleteBtn">Delete My Account</button>
</div>
<div style="text-align:center; margin-top: 10px;">
<button type="button" id="resendOtpLink" class="link-btn">Resend OTP</button>
</div>
<div id="timer" class="muted"></div>
<div id="msg" class="muted"></div>
<div id="err" class="error"></div>
</form>
</div>
<script>
(function(){
const preOtp = document.getElementById('preOtp');
const sendBtn = document.getElementById('sendOtpBtn');
const preMsg = document.getElementById('preMsg');
const preErr = document.getElementById('preErr');
const form = document.getElementById('otpForm');
const otpInput = document.getElementById('otp');
const resendLink = document.getElementById('resendOtpLink');
const timerEl = document.getElementById('timer');
const msgEl = document.getElementById('msg');
const errEl = document.getElementById('err');
const RESEND_WAIT = ${RESEND_COOLDOWN_SEC};
let countdown = 0;
let iv = null;
function setText(el, t){ el.textContent = t || ''; }
function updateResendState(){
resendLink.disabled = countdown>0;
if(countdown>0){
resendLink.setAttribute('disabled','true');
resendLink.style.pointerEvents='none';
}else{
resendLink.removeAttribute('disabled');
resendLink.style.pointerEvents='auto';
}
}
function tick(){
if(countdown<=0){ clearInterval(iv); iv=null; setText(timerEl,'You can resend the OTP now.'); updateResendState(); return; }
setText(timerEl,'Resend available in '+countdown+'s'); countdown--; updateResendState();
}
function startTimer(sec){ countdown=sec||RESEND_WAIT; if(iv) clearInterval(iv); tick(); iv=setInterval(tick,1000); }
async function sendOtp(showForm){
setText(preErr,''); setText(preMsg,'Sending OTP...');
try {
const resp = await fetch('${routePath}/delete-account/${userId}/otp',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({})});
const data = await resp.json().catch(()=>({}));
if(!resp.ok){
if(resp.status===429 && data?.retryAfterSec){
startTimer(data.retryAfterSec); setText(preErr,'Please wait '+data.retryAfterSec+'s before resending.'); setText(preMsg,'');
return;
}
throw new Error(data?.error || 'Failed to send OTP.');
}
startTimer(data?.cooldownSec || RESEND_WAIT);
// Success: hide pre, show form
preOtp.classList.add('hidden');
form.classList.remove('hidden');
setText(msgEl,'OTP sent to your registered email.');
otpInput.focus();
} catch(e){
setText(preErr,e.message||'Error sending OTP.');
setText(preMsg,'');
}
}
sendBtn.addEventListener('click',()=>sendOtp(true));
resendLink.addEventListener('click',()=>{ if(countdown<=0) sendOtp(false); });
})();
</script>
</body>
</html>
`;
res.send(htmlForm);
};
@@ -0,0 +1,43 @@
import {
generateOtp,
msUntil,
sendDeleteOtpEmail,
OTP_EXPIRES_MIN,
RESEND_COOLDOWN_SEC,
} from './deleteUtils.js';
export const deleteUserOtp = async (req, res) => {
const { userId } = req.params;
const extUserQuery = new Parse.Query('contracts_Users');
extUserQuery.equalTo('UserId', { __type: 'Pointer', className: '_User', objectId: userId });
const extUser = await extUserQuery.first({ useMasterKey: true });
if (!extUser) return res.status(404).json({ error: 'User not found' });
const now = Date.now();
const lastSentAt = extUser.get('DeleteOTPSentAt')?.getTime?.() || 0;
const cooldownEndsAt = lastSentAt + RESEND_COOLDOWN_SEC * 1000;
const remainingMs = msUntil(now, cooldownEndsAt);
if (remainingMs > 0) {
return res
.status(429)
.json({ error: 'Cooldown not finished', retryAfterSec: Math.ceil(remainingMs / 1000) });
}
const otp = generateOtp();
const expiresAt = new Date(now + OTP_EXPIRES_MIN * 60 * 1000);
try {
const resp = await sendDeleteOtpEmail(extUser, otp);
extUser.set('DeleteOTP', otp);
extUser.set('DeleteOTPExpiry', expiresAt);
extUser.set('DeleteOTPSentAt', new Date(now));
extUser.set('DeleteOTPTries', 0); // reset tries on resend
await extUser.save(null, { useMasterKey: true });
return res.json({ ok: true, cooldownSec: RESEND_COOLDOWN_SEC, expiresInMin: OTP_EXPIRES_MIN });
} catch (err) {
console.log('Error sending delete OTP (POST /otp):', err?.response?.data || err);
return res.status(500).json({ error: 'Failed to send OTP' });
}
};
@@ -0,0 +1,72 @@
import axios from 'axios';
import { appName, cloudServerUrl, serverAppId } from '../../../Utils.js';
const serverUrl = cloudServerUrl;
const appId = serverAppId;
const masterKey = process.env.MASTER_KEY;
// Constants (adjust to your preference)
export const OTP_LENGTH = 6;
export const OTP_EXPIRES_MIN = 10; // OTP validity in minutes
export const RESEND_COOLDOWN_SEC = 30; // Cooldown between OTP sends
export const MAX_ATTEMPTS = 5; // Max allowed wrong attempts
export function generateOtp(len = OTP_LENGTH) {
// 6-digit numeric OTP (000000999999, padded)
const n = Math.floor(Math.random() * Math.pow(10, len));
return String(n).padStart(len, '0');
}
export async function sendDeleteOtpEmail(extUser, otp) {
const params = {
extUserId: extUser.id,
from: appName,
recipient: extUser?.get('Email'),
subject: 'OTP for Deletion account request',
html: `
<html lang="en">
<body style="margin:0;padding:0;background:#f6f7fb;font-family:Arial,Helvetica,sans-serif;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#f6f7fb;">
<tr>
<td align="center" style="padding:24px;">
<table role="presentation" width="480" cellpadding="0" cellspacing="0" style="background:#ffffff;border:1px solid #e9ecf1;border-radius:8px;padding:20px;">
<tr>
<td align="left" style="font-size:16px;color:#0f172a;">
<div style="font-weight:bold;margin-bottom:8px;">${appName}</div>
<div style="font-size:18px;margin:0 0 12px 0;">Your verification code</div>
<div style="display:inline-block;border:1px solid #e9ecf1;border-radius:6px;background:#f8fafc;padding:10px 14px;margin-bottom:10px;">
<span style="font-family:Consolas,'Courier New',monospace;font-size:24px;letter-spacing:6px;color:#0f172a;">${otp}</span>
</div>
<p style="margin:8px 0 0 0;font-size:13px;color:#475569;">
This code expires in <strong>${OTP_EXPIRES_MIN}</strong> minutes.
</p>
<hr style="border:none;border-top:1px solid #e9ecf1;margin:18px 0;">
<p style="margin:0;font-size:12px;color:#64748b;">
If you didnt request this code, you can ignore this email.
</p>
</td>
</tr>
</table>
<div style="font-size:11px;color:#94a3b8;margin-top:12px;">
&copy; ${new Date().getFullYear()} ${appName}. All rights reserved.
</div>
</td>
</tr>
</table>
</body>
</html>
`,
};
const headers = {
'Content-Type': 'application/json',
'X-Parse-Application-Id': appId,
'X-Parse-Master-Key': masterKey,
};
return axios.post(serverUrl + '/functions/sendmailv3', params, { headers });
}
export function msUntil(nowMs, futureMs) {
return Math.max(0, (futureMs || 0) - nowMs);
}
@@ -46,7 +46,7 @@ export default async function getDrive(request) {
return { error: 'Please provide required parameter!' };
}
} catch (err) {
console.log('err', err);
console.log('err', err?.response?.data || err);
if (err.code == 209) {
return { error: 'Invalid session token' };
} else {
@@ -21,6 +21,12 @@ const APPID = serverAppId;
const masterKEY = process.env.MASTER_KEY;
const eSignName = 'OpenSign';
const eSigncontact = 'hello@opensignlabs.com';
const docUrl = `${serverUrl}/classes/contracts_Document`;
const headers = {
'Content-Type': 'application/json',
'X-Parse-Application-Id': APPID,
'X-Parse-Master-Key': masterKEY,
};
async function unlinkFile(path) {
if (fs.existsSync(path)) {
@@ -90,13 +96,7 @@ async function updateDoc(docId, url, userId, ipAddress, data, className, sign) {
isCompleted = true;
}
const body = { SignedUrl: url, AuditTrail: updateAuditTrail, IsCompleted: isCompleted };
const signedRes = await axios.put(serverUrl + '/classes/contracts_Document/' + docId, body, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': APPID,
'X-Parse-Master-Key': masterKEY,
},
});
const signedRes = await axios.put(`${docUrl}/${docId}`, body, { headers });
return { isCompleted: isCompleted, message: 'success', AuditTrail: updateAuditTrail };
} catch (err) {
console.log('update doc err ', err);
@@ -141,13 +141,7 @@ async function sendNotifyMail(doc, signUser, mailProvider, publicUrl) {
html: body,
mailProvider: mailProvider,
};
await axios.post(serverUrl + '/functions/sendmailv3', params, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': APPID,
'X-Parse-Master-Key': masterKEY,
},
});
await axios.post(serverUrl + '/functions/sendmailv3', params, { headers });
}
} catch (err) {
console.log('err in sendnotifymail', err);
@@ -255,13 +249,7 @@ async function sendCompletedMail(obj) {
filename: docName,
};
try {
const res = await axios.post(serverUrl + '/functions/sendmailv3', params, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': APPID,
'X-Parse-Master-Key': masterKEY,
},
});
const res = await axios.post(serverUrl + '/functions/sendmailv3', params, { headers });
// console.log('res', res.data.result);
if (res.data?.result?.status !== 'success') {
unlinkFile(`./exports/signed_certificate_${doc.objectId}.pdf`);
@@ -298,13 +286,7 @@ async function sendMailsaveCertifcate(doc, pfx, isCustomMail, mailProvider, file
fs.writeFileSync(certificatePath, signedCertificate);
const file = await uploadFile('certificate.pdf', certificatePath);
const body = { CertificateUrl: file.imageUrl };
await axios.put(serverUrl + '/classes/contracts_Document/' + doc.objectId, body, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': APPID,
'X-Parse-Master-Key': masterKEY,
},
});
await axios.put(`${docUrl}/${doc.objectId}`, body, { headers });
// used in API only
if (doc.IsSendMail === false) {
console.log("don't send mail");
@@ -313,6 +295,41 @@ async function sendMailsaveCertifcate(doc, pfx, isCustomMail, mailProvider, file
}
saveFileUsage(CertificateBuffer.length, file.imageUrl, doc?.CreatedBy?.objectId);
unlinkFile(pfx.name);
return file.imageUrl;
}
/**
* Process a PDF for signing:
* - updates audit trail, generates certificate.
* - Optionally inserts a signature placeholder (Placeholder()).
* - Otherwise (no merge + no placeholder), it flattens forms for finalization.
*
* @param {Object} _resDoc - Document details (expects AuditTrail, etc.)
* @param {Buffer|Uint8Array} pdfBytes - Original PDF bytes
* @param {string} [options.reason] - Reason text used in placeholder
* @param {string} [options.UserPtr] - user pointer (for audit trail)
* @param {string} [options.ipAddress] - IP (for audit trail)
* @param {string} [options.Signature] - Signature (for audit trail)
* @returns {Promise<Buffer>} merged PDF Buffer
*/
async function processPdf(_resDoc, PdfBuffer, reason) {
// No CC merge; operate directly on the original PDF
const pdfDoc = await PDFDocument.load(PdfBuffer);
const form = pdfDoc.getForm();
// Updates the field appearances to ensure visual changes are reflected.
form.updateFieldAppearances();
// Flattens the form, converting all form fields into non-editable, static content
form.flatten();
Placeholder({
pdfDoc: pdfDoc,
reason: `Digitally signed by ${eSignName} for ${reason}`,
location: 'n/a',
name: eSignName,
contactInfo: eSigncontact,
signatureLength: 16000,
});
const pdfWithPlaceholderBytes = await pdfDoc.save();
return Buffer.from(pdfWithPlaceholderBytes);
}
/**
*
@@ -389,7 +406,11 @@ async function PDF(req) {
const auditTrail = updateAuditTrail.filter(x => x.Activity === 'Signed');
let isCompleted = false;
if (_resDoc.Signers && _resDoc.Signers.length > 0) {
if (auditTrail.length === _resDoc.Signers.length) {
const removePrefill =
_resDoc?.Placeholders?.length > 0 &&
_resDoc?.Placeholders?.filter(x => x?.Role !== 'prefill');
if (auditTrail.length === removePrefill?.length) {
// if (auditTrail.length === _resDoc.Signers.length) {
isCompleted = true;
}
} else {
@@ -408,24 +429,9 @@ async function PDF(req) {
signersName && signersName.length > 0
? signersName?.join(', ')
: username + ' <' + userEmail + '>';
const pdfDoc = await PDFDocument.load(PdfBuffer);
const form = pdfDoc.getForm();
// Updates the field appearances to ensure visual changes are reflected.
form.updateFieldAppearances();
// Flattens the form, converting all form fields into non-editable, static content
form.flatten();
const p12Cert = new P12Signer(P12Buffer, { passphrase: passphrase || null });
signedFilePath = `./exports/signed_${name}`;
Placeholder({
pdfDoc: pdfDoc,
reason: `Digitally signed by ${eSignName} for ${reason}`,
location: 'n/a',
name: eSignName,
contactInfo: eSigncontact,
signatureLength: 16000,
});
const pdfWithPlaceholderBytes = await pdfDoc.save();
PdfBuffer = Buffer.from(pdfWithPlaceholderBytes);
PdfBuffer = await processPdf(_resDoc, PdfBuffer, reason, UserPtr, userIP, sign);
//`new signPDF` create new instance of pdfBuffer and p12Buffer
const OBJ = new SignPdf();
// `signedDocs` is used to signpdf digitally
@@ -484,9 +490,7 @@ async function PDF(req) {
console.log('Err in signpdf', err);
const body = { DebugginLog: err?.message };
try {
await axios.put(serverUrl + '/classes/contracts_Document/' + docId, body, {
headers: { 'X-Parse-Application-Id': APPID, 'X-Parse-Master-Key': masterKEY },
});
await axios.put(`${docUrl}/${docId}`, body, { headers });
} catch (err) {
console.log('err in saving debugginglog', err);
}
@@ -25,6 +25,7 @@ export default function reportJson(id, currentUserId) {
'RequestSubject',
'ExtUserPtr.TenantId.RequestBody',
'ExtUserPtr.TenantId.RequestSubject',
'DocSentAt',
];
const filterKeys = [
'TimeToCompleteDays',