diff --git a/.env.local_dev b/.env.local_dev index cf19e847c..807d61ec0 100644 --- a/.env.local_dev +++ b/.env.local_dev @@ -44,10 +44,16 @@ DO_REGION=us-west # local storage USE_LOCAL=FALSE -# Email mailgun config (The app will not initialize if any of these 3 variables are not set) ********************************************************************************************************************* -MAILGUN_API_KEY=XXXXX +# Email using Mailgun or SMTP if set enable config (The app will not initialize if any of these variables are not set) ********************************************************************************************************************* +MAILGUN_API_KEY= MAILGUN_DOMAIN=mail.yourdomain.com MAILGUN_SENDER=postmaster@mail.yourdomain.com +SMTP_ENABLE= +SMTP_HOST= +SMTP_PORT= +SMTP_USER_EMAIL= +SMTP_PASS= + # Base64 encoded PFX or p12 document signing certificate file ********************************************************************************************************************* PFX_BASE64='MIIKLwIBAzCCCeUGCSqGSIb3DQEHAaCCCdYEggnSMIIJzjCCBEIGCSqGSIb3DQEH diff --git a/.gitignore b/.gitignore index 8e62b9ce7..b6dcebdf3 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,5 @@ npm-debug.log* yarn-debug.log* yarn-error.log* apps/OpenSign/public/mfbuild/* -microfrontends/SignDocuments/build/* \ No newline at end of file +microfrontends/SignDocuments/build/* +apps/OpenSignServer/files/files/* \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit index 1999e3518..21170d497 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,5 +1,4 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" -npx eslint '**/*.{js,jsx}' -npx pretty-quick --staged '**/*.{js,jsx}' \ No newline at end of file +npm run lint-staged-changes diff --git a/.lintstagedrc.json b/.lintstagedrc.json new file mode 100644 index 000000000..5478c3ce3 --- /dev/null +++ b/.lintstagedrc.json @@ -0,0 +1,3 @@ +{ + "*.js": "prettier --write" +} diff --git a/INSTALLATION.md b/INSTALLATION.md index a489eb8fe..86a9fb8e6 100644 --- a/INSTALLATION.md +++ b/INSTALLATION.md @@ -127,7 +127,24 @@ openssl pkcs12 -inkey ./cert/local_dev.key -in ./cert/local_dev.crt -export -out openssl base64 -in ./cert/local_dev.pfx -out ./cert/base64_pfx ``` -#Build Local Environment +# CORS Configuration -Below are the steps to follow - +As document storage is delegated to S3-compatible services that reside in a different host than the OpenSign one, document operations (loading, storing, deleting) are subject to [Cross-Origin Resource Sharing](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) restriction policies; as a consequence, OpenSign app may fail with (browser console) errors like the following: +``` +Access to fetch at 'https://foo.nyc3.digitaloceanspaces.com/exported_file_4627_0000-00-00T00%3A45%3A43.344Z.pdf' +from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header +is present on the requested resource. If an opaque response serves your needs, set the request's mode to +'no-cors' to fetch the resource with CORS disabled. +``` + +In order to address this, your document storage system must be instructed to accept requests from other hosts; below the relevant documentation links: +- [How to Configure CORS on DigitalOcean Spaces](https://docs.digitalocean.com/products/spaces/how-to/configure-cors/) +- [Configuring cross-origin resource sharing on AWS S3](https://docs.aws.amazon.com/AmazonS3/latest/userguide/enabling-cors-examples.html) + +# Build Local Environment + +Command to build project - - Execute `make build` + +Command to run project - +- Execute `make run` \ No newline at end of file diff --git a/Makefile b/Makefile index 35fa55901..d44c064b7 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,9 @@ build: + cp .env.local_dev .env + rm -rf apps/OpenSign/public/mfbuild + cd microfrontends/SignDocuments && npm install && npm run build + docker compose up --build --force-recreate + +run: cp .env.local_dev .env docker compose up -d \ No newline at end of file diff --git a/README.md b/README.md index 200487ed5..7a4d986c7 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ -

OpenSign™

- -
+

+

-The free and open source alternative to DocuSign +[The free and open source alternative to DocuSign](https://www.opensignlabs.com) [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/opensignlabs/opensign.svg)](http://isitmaintained.com/project/opensignlabs/opensign "Average time to resolve an issue") [![All Contributors](https://img.shields.io/github/all-contributors/opensignlabs/opensign?color=ee8449&style=flat-square)](#contributors) @@ -23,7 +22,7 @@ The free and open source alternative to DocuSign LinkedIn -## An open-source document e-signing solution +## The open-source document e-signing solution ---
@@ -48,14 +47,18 @@ Welcome to OpenSign, an open-source document e-signing solution designed to prov ### Features -- **Secure Signing**: Utilizes state-of-the-art cryptographic algorithms to ensure the security & integrity of your documents. -- **User-Friendly Interface**: Designed with usability in mind, making it easy for both technical and non-technical users. -- **Multi-Platform Support**: Compatible with various browsers and devices. -- **Invite & collaborate users**: Bring multiple people from your team into the signing process, all within your own infrastructure. -- **Secure**: Allows for the easy, secure and seamless organization of your documents using 'OpenSigDrive'. -- **Audit Trails**: Keeps a detailed log of all activities related to the document signing process including IP addresses and access timings. -- **Completion Certificate**: Generate secure completion certificate as soon as a document is signed by all participants. -- **API Support**: Provides a robust API for integration into other software and services. +- **Secure PDF E-Signing:** With the help of Robust encryption algorithms, OpenSign™ ensures maximum security, privacy & compatibility. +- **Annotate Documents:** OpenSign™ allows you to annotate PDF documents with an advanced signing pad that comes with hand drawn signatures support as well as uploaded images & saved signatures for the simplest signing experience. +- **User-Friendly Interface:** OpenSign™ was built while keeping Intuitive design in mind for ease of use. Features like "Sign yourself", "One click signatures" and "OpenSign Drive" makes it stand out of the crowd and even makes it better than a lot of so-called industry leaders. +- **Multi-signer Support:** OpenSign's ability to invite multiple signers for signing along with the ability to invite witnesses & being able to enforce signing in a sequence makes it the only open source solution that is fully loaded and allows it to compete head-to-head with established players. +- **Email Unique Code(OTP) verification support for guest signers:** With OpenSign™, your documents are fully secure even when being signed by guest users. Guest signers can only sign the document after entering a unique code sent to their email address.  +- **"Expiring Docs" & "Rejection":** You can set documents to expire after certain number of days after which nobody will be able to sign it. Not just this, OpenSign also allows signers to reject signing a document. +- **Beautiful email templates:** All document signing invitations, completion notifications & reminders are formatted using great looking email templates. +- **PDF Template Creation(coming soon):** OpenSign™ allows you to create and store PDF document templates for repeated use thereby saving you a lot of time. +- **OpenSign™ Drive:** It is a centralised secure vault for your signed documents that makes storing, signing, organizing, sharing & achieving your docs a breeze. +- **Audit Trails & completion certificate:** Being a security focused solution, OpenSign™ makes it a top priority to save detailed logs for tracking document activities along with time-stamps, IP addresses, email IDs & phone numbers. A completion certificate is generated as soon as document is completed which contains all the document related logs for added safety. +- **API Support(coming soon):** OpenSign™ API allows seamless integration into existing systems and software. APIs will soon be available as a cloud hosted solution. +- **Integrations:** Seamless integrations with various Cloud storage systems, CRMs & enterprise platforms is coming soon. diff --git a/apps/OpenSign/Dockerfile b/apps/OpenSign/Dockerfile index 7d8542bf6..57c815846 100644 --- a/apps/OpenSign/Dockerfile +++ b/apps/OpenSign/Dockerfile @@ -21,5 +21,5 @@ EXPOSE 3000 # ENV NODE_ENV production # Run the application -CMD ["npm", "start"] +ENTRYPOINT npm run start-dev diff --git a/apps/OpenSign/public/mfbuild/.gitkeep b/apps/OpenSign/public/mfbuild/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/OpenSign/src/routes/Login.js b/apps/OpenSign/src/routes/Login.js index 509dc0efd..25ea70907 100644 --- a/apps/OpenSign/src/routes/Login.js +++ b/apps/OpenSign/src/routes/Login.js @@ -1154,7 +1154,7 @@ function Login(props) {
-
Login form
+
Additional Info
@@ -1210,18 +1210,19 @@ function Login(props) {
diff --git a/apps/OpenSign/src/routes/PlanSubscriptions.js b/apps/OpenSign/src/routes/PlanSubscriptions.js index 740277932..88d9d3ed2 100644 --- a/apps/OpenSign/src/routes/PlanSubscriptions.js +++ b/apps/OpenSign/src/routes/PlanSubscriptions.js @@ -28,7 +28,7 @@ const PlanSubscriptions = () => { : ""; const phone = userDetails && userDetails.phone ? "&mobile=" + userDetails.phone : ""; - const details = "?" + name + email + company + phone; + const details = "?shipping_country_code=US&" + name + email + company + phone; useEffect(() => { if (localStorage.getItem("accesstoken")) { setIsLoader(false); diff --git a/apps/OpenSign/src/routes/Signup.js b/apps/OpenSign/src/routes/Signup.js index 31c26f566..137c76b21 100644 --- a/apps/OpenSign/src/routes/Signup.js +++ b/apps/OpenSign/src/routes/Signup.js @@ -117,7 +117,7 @@ const Signup = (props) => { await Parse.User.requestPasswordReset(email).then( async function (res1) { if (res1.data === undefined) { - alert("Email has been sent to your mail!"); + alert("Verification mail has been sent to your E-mail!"); } } ); diff --git a/apps/OpenSignServer/cloud/customRoute/uploadFile.js b/apps/OpenSignServer/cloud/customRoute/uploadFile.js index 34abc14d0..b0152e1bd 100644 --- a/apps/OpenSignServer/cloud/customRoute/uploadFile.js +++ b/apps/OpenSignServer/cloud/customRoute/uploadFile.js @@ -4,6 +4,12 @@ import multerS3 from 'multer-s3'; import aws from 'aws-sdk'; import dotenv from 'dotenv'; dotenv.config(); + +function sanitizeFileName(fileName) { + // Remove spaces and invalid characters + return fileName.replace(/[^a-zA-Z0-9._-]/g, ''); +} + async function uploadFile(req, res) { try { //--size extended to 100 mb @@ -50,16 +56,28 @@ async function uploadFile(req, res) { region: process.env.DO_REGION, }); - // const s3 = new aws.S3(); - const upload = multer({ - fileFilter: function (req, file, cb) { - if (accepted_extensions.some(ext => file.originalname.toLowerCase().endsWith('.' + ext))) { - return cb(null, true); + const parseBaseUrl = process.env.SERVER_URL; + const parseAppId = process.env.APP_ID; + + if (process.env.USE_LOCAL == "TRUE") { + var fileStorage = multer.diskStorage({ + destination: function(req, file, cb) { + cb(null, "files/files"); + }, + metadata: function (req, file, cb) { + cb(null, { fieldName: 'OPENSIGN_METADATA' }); + }, + filename: function(req, file, cb) { + let filename = file.originalname; + let newFileName = filename.split('.')[0]; + let extension = filename.split('.')[1]; + newFileName = sanitizeFileName(newFileName + '_' + new Date().toISOString() + '.' + extension) + console.log(newFileName); + cb(null, newFileName); } - // otherwise, return error - return cb('Only ' + accepted_extensions.join(', ') + ' files are allowed!'); - }, - storage: multerS3({ + }); + } else { + var fileStorage = multerS3({ acl: 'public-read', s3, bucket: DO_SPACE, @@ -69,14 +87,25 @@ async function uploadFile(req, res) { key: function (req, file, cb) { //console.log(file); let filename = file.originalname; - let filenam = filename.split('.')[0]; + let newFileName = filename.split('.')[0]; let extension = filename.split('.')[1]; - filenam = filenam + '_' + new Date().toISOString() + '.' + extension; - console.log(filenam); - cb(null, filenam); - }, - }), + newFileName = sanitizeFileName(newFileName + '_' + new Date().toISOString() + '.' + extension) + console.log(newFileName); + cb(null, newFileName); + } + }); + } + // const s3 = new aws.S3(); + const upload = multer({ + fileFilter: function (req, file, cb) { + if (accepted_extensions.some(ext => file.originalname.toLowerCase().endsWith('.' + ext))) { + return cb(null, true); + } + // otherwise, return error + return cb('Only ' + accepted_extensions.join(', ') + ' files are allowed!'); + }, + storage: fileStorage, limits: { fileSize: size }, }).single('file'); @@ -93,7 +122,14 @@ async function uploadFile(req, res) { const status = 'Success'; //res.header("Access-Control-Allow-Headers", "Content-Type"); //res.setHeader("Access-Control-Allow-Origin", "*"); - return res.json({ status, imageUrl: req.file.location }); + if (process.env.USE_LOCAL == "TRUE") { + console.log(req.file); + var fileUrl = `${parseBaseUrl}/files/${parseAppId}/${req.file.filename}`; + } else { + var fileUrl = req.file.location; + } + + return res.json({ status, imageUrl: fileUrl }); }); } catch (err) { console.log('Exeption in query ' + err.stack); diff --git a/apps/OpenSignServer/cloud/parsefunction/SendMailv1.js b/apps/OpenSignServer/cloud/parsefunction/SendMailv1.js index f7cb953ae..741ac4ec0 100644 --- a/apps/OpenSignServer/cloud/parsefunction/SendMailv1.js +++ b/apps/OpenSignServer/cloud/parsefunction/SendMailv1.js @@ -5,7 +5,7 @@ async function SendMailv1(request) { const recipient = request.params.email; const otp = request.params.otp; const res = await Parse.Cloud.sendEmail({ - from: 'Test user' + ' <' + process.env.MAILGUN_SENDER + '>', + from: 'Test user' + ' <' + process.env.SMTP_ENABLE ? process.env.SMTP_USER_EMAIL : process.env.MAILGUN_SENDER + '>', recipient: recipient, subject: 'Your OpenSign™ OTP', text: 'This email is a test.', diff --git a/apps/OpenSignServer/cloud/parsefunction/sendMail.js b/apps/OpenSignServer/cloud/parsefunction/sendMail.js index 702a3c93a..3465332c1 100644 --- a/apps/OpenSignServer/cloud/parsefunction/sendMail.js +++ b/apps/OpenSignServer/cloud/parsefunction/sendMail.js @@ -1,14 +1,29 @@ import fs from 'node:fs'; +import https from 'https'; import formData from 'form-data'; import Mailgun from 'mailgun.js'; -import https from 'https'; +import { createTransport } from 'nodemailer'; -const mailgun = new Mailgun(formData); -const mailgunClient = mailgun.client({ - username: 'api', - key: process.env.MAILGUN_API_KEY, -}); -const mailgunDomain = process.env.MAILGUN_DOMAIN; +let transporterSMTP; +let mailgunClient; + +if (process.env.SMTP_ENABLE) { + transporterSMTP = createTransport({ + host: process.env.SMTP_HOST, + port: process.env.SMTP_PORT || 465, + secure: process.env.SMTP_SECURE || true, + auth: { + user: process.env.SMTP_USER_EMAIL, + pass: process.env.SMTP_PASS, + }, + }); +} else { + const mailgun = new Mailgun(formData); + mailgunClient = mailgun.client({ + username: 'api', + key: process.env.MAILGUN_API_KEY, + }); +} async function sendmail(req) { try { @@ -38,7 +53,8 @@ async function sendmail(req) { const pdfName = req.params.pdfName && `${req.params.pdfName}.pdf`; const file = { filename: pdfName || 'exported.pdf', - data: PdfBuffer, //fs.readFileSync('./exports/exported_file_1223.pdf'), + content: process.env.SMTP_ENABLE ? PdfBuffer : undefined, //fs.readFileSync('./exports/exported_file_1223.pdf'), + data: process.env.SMTP_ENABLE ? undefined : PdfBuffer, }; // const html = "

Hello!

This is a html checking mail

" @@ -46,14 +62,60 @@ async function sendmail(req) { const from = req.params.from || ''; const messageParams = { - from: from + ' <' + process.env.MAILGUN_SENDER + '>', + from: + from + ' <' + process.env.SMTP_ENABLE + ? process.env.SMTP_USER_EMAIL + : process.env.MAILGUN_SENDER + '>', to: req.params.recipient, subject: req.params.subject, text: req.params.text || 'mail', html: req.params.html || '', - attachment: file, + attachments: process.env.SMTP_ENABLE ? [file] : undefined, + attachment: process.env.SMTP_ENABLE ? undefined : file, }; + if (transporterSMTP) { + const res = await transporterSMTP.sendMail(messageParams); + + console.log('Res ', res); + if (!res.err) { + return { + status: 'success', + }; + } + } else { + const res = await mailgunClient.messages.create(mailgunDomain, messageParams); + console.log('Res ', res); + if (res.status === 200) { + return { + status: 'success', + }; + } + } + } + } else { + const from = req.params.from || ''; + const messageParams = { + from: + from + ' <' + process.env.SMTP_ENABLE + ? process.env.SMTP_USER_EMAIL + : process.env.MAILGUN_SENDER + '>', + to: req.params.recipient, + subject: req.params.subject, + text: req.params.text || 'mail', + html: req.params.html || '', + }; + + if (transporterSMTP) { + const res = await transporterSMTP.sendMail(messageParams); + + console.log('Res ', res); + if (!res.err) { + return { + status: 'success', + }; + } + } else { const res = await mailgunClient.messages.create(mailgunDomain, messageParams); console.log('Res ', res); if (res.status === 200) { @@ -62,23 +124,6 @@ async function sendmail(req) { }; } } - } else { - const from = req.params.from || ''; - const messageParams = { - from: from + ' <' + process.env.MAILGUN_SENDER + '>', - to: req.params.recipient, - subject: req.params.subject, - text: req.params.text || 'mail', - html: req.params.html || '', - }; - - const res = await mailgunClient.messages.create(mailgunDomain, messageParams); - console.log('Res ', res); - if (res.status === 200) { - return { - status: 'success', - }; - } } } catch (err) { console.log('err ', err); diff --git a/apps/OpenSignServer/index.js b/apps/OpenSignServer/index.js index b60238cf7..14ca95558 100644 --- a/apps/OpenSignServer/index.js +++ b/apps/OpenSignServer/index.js @@ -21,7 +21,7 @@ import { exec } from 'child_process'; const spacesEndpoint = new AWS.Endpoint(process.env.DO_ENDPOINT); // console.log("configuration ", configuration); -if (process.env.USE_LOCAL !== 'TRUE') { +if (process.env.USE_LOCAL !== "TRUE") { const s3Options = { bucket: process.env.DO_SPACE, // globalConfig.S3FilesAdapter.bucket, baseUrl: process.env.DO_BASEURL, @@ -41,14 +41,27 @@ if (process.env.USE_LOCAL !== 'TRUE') { }); } +let transporterMail; let mailgunClient; let mailgunDomain; -if (process.env.MAILGUN_API_KEY) { + +if (process.env.SMTP_ENABLE) { + transporterMail = createTransport({ + host: process.env.SMTP_HOST, + port: process.env.SMTP_PORT || 465, + secure: process.env.SMTP_SECURE || true, + auth: { + user: process.env.SMTP_USER_EMAIL, + pass: process.env.SMTP_PASS, + }, + }); +} else if (process.env.MAILGUN_API_KEY) { const mailgun = new Mailgun(formData); mailgunClient = mailgun.client({ username: 'api', key: process.env.MAILGUN_API_KEY, }); + mailgunDomain = process.env.MAILGUN_DOMAIN; } @@ -65,36 +78,39 @@ export const config = { // Your apps name. This will appear in the subject and body of the emails that are sent. appName: 'Open Sign', allowClientClassCreation: false, - emailAdapter: process.env.MAILGUN_API_KEY - ? { - module: 'parse-server-api-mail-adapter', - options: { - // The email address from which emails are sent. - sender: process.env.MAILGUN_SENDER, - // The email templates. - templates: { - // The template used by Parse Server to send an email for password - // reset; this is a reserved template name. - passwordResetEmail: { - subjectPath: './files/password_reset_email_subject.txt', - textPath: './files/password_reset_email.txt', - htmlPath: './files/password_reset_email.html', + emailAdapter: + process.env.SMTP_ENABLE || process.env.MAILGUN_API_KEY + ? { + module: 'parse-server-api-mail-adapter', + options: { + // The email address from which emails are sent. + sender: process.env.SMTP_ENABLE ? process.env.SMTP_USER_EMAIL : process.env.MAILGUN_SENDER, + // The email templates. + templates: { + // The template used by Parse Server to send an email for password + // reset; this is a reserved template name. + passwordResetEmail: { + subjectPath: './files/password_reset_email_subject.txt', + textPath: './files/password_reset_email.txt', + htmlPath: './files/password_reset_email.html', + }, + // The template used by Parse Server to send an email for email + // address verification; this is a reserved template name. + verificationEmail: { + subjectPath: './files/verification_email_subject.txt', + textPath: './files/verification_email.txt', + htmlPath: './files/verification_email.html', + }, }, - // The template used by Parse Server to send an email for email - // address verification; this is a reserved template name. - verificationEmail: { - subjectPath: './files/verification_email_subject.txt', - textPath: './files/verification_email.txt', - htmlPath: './files/verification_email.html', + apiCallback: async ({ payload, locale }) => { + if (mailgunClient) { + const mailgunPayload = ApiPayloadConverter.mailgun(payload); + await mailgunClient.messages.create(mailgunDomain, mailgunPayload); + } else if (transporterMail) await transporterMail.sendMail(payload); }, }, - apiCallback: async ({ payload, locale }) => { - const mailgunPayload = ApiPayloadConverter.mailgun(payload); - await mailgunClient.messages.create(mailgunDomain, mailgunPayload); - }, - }, - } - : null, + } + : null, filesAdapter: fsAdapter, auth: { google: { diff --git a/apps/OpenSignServer/package-lock.json b/apps/OpenSignServer/package-lock.json index aadbb80b1..f7626012e 100644 --- a/apps/OpenSignServer/package-lock.json +++ b/apps/OpenSignServer/package-lock.json @@ -18,12 +18,13 @@ "express-sse": "^0.5.3", "form-data": "^4.0.0", "jsonschema": "^1.4.1", - "mailgun.js": "^9.0.1", + "mailgun.js": "^9.3.0", "mongoose": "^7.2.1", "multer": "^1.4.5-lts.1", "multer-s3": "^2.10.0", "node-forge": "^1.3.1", "node-signpdf": "^1.5.1", + "nodemailer": "^6.9.7", "openai": "^4.8.0", "parse": "4.1.0", "parse-server": "6.3.1", @@ -6017,9 +6018,9 @@ } }, "node_modules/mailgun.js": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/mailgun.js/-/mailgun.js-9.0.1.tgz", - "integrity": "sha512-bVZ1zGh5UfQcChqIwIAil99hzoTeB3aHqAUbqmIBLMLcKfx+FF2qvhbc00WtwrFhe5dBQqQw6zPj0QBlqV4nXg==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/mailgun.js/-/mailgun.js-9.3.0.tgz", + "integrity": "sha512-iRqCglCdi+Q5anFpeRKHiytT/i34E14p/WtTE57VSeq5bATK+zQ8UnpFgPRaqGGTJqyGjqcO9m5YDRRB4/qirw==", "dependencies": { "axios": "^1.3.3", "base-64": "^1.0.0", @@ -6800,6 +6801,14 @@ "node-forge": "^1.2.1" } }, + "node_modules/nodemailer": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.7.tgz", + "integrity": "sha512-rUtR77ksqex/eZRLmQ21LKVH5nAAsVicAtAYudK7JgwenEDZ0UIQ1adUGqErz7sMkWYxWTTU1aeP2Jga6WQyJw==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/nodemon": { "version": "2.0.22", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", diff --git a/apps/OpenSignServer/package.json b/apps/OpenSignServer/package.json index 50292ad43..5049e7188 100644 --- a/apps/OpenSignServer/package.json +++ b/apps/OpenSignServer/package.json @@ -27,18 +27,19 @@ "express-sse": "^0.5.3", "form-data": "^4.0.0", "jsonschema": "^1.4.1", - "mailgun.js": "^9.0.1", + "mailgun.js": "^9.3.0", "mongoose": "^7.2.1", "multer": "^1.4.5-lts.1", "multer-s3": "^2.10.0", "node-forge": "^1.3.1", "node-signpdf": "^1.5.1", + "nodemailer": "^6.9.7", "openai": "^4.8.0", "parse": "4.1.0", "parse-server": "6.3.1", "parse-server-api-mail-adapter": "^3.0.0", - "parse-server-s3-adapter": "^1.2.0", "parse-server-fs-adapter": "1.0.1", + "parse-server-s3-adapter": "^1.2.0", "pdf-lib": "^1.16.0", "pdfkit": "^0.13.0", "razorpay": "^2.8.6", diff --git a/microfrontends/SignDocuments/src/Component/Certificate/Certificate.js b/microfrontends/SignDocuments/src/Component/Certificate/Certificate.js index 5df07d5f5..37eee4fc3 100644 --- a/microfrontends/SignDocuments/src/Component/Certificate/Certificate.js +++ b/microfrontends/SignDocuments/src/Component/Certificate/Certificate.js @@ -1,7 +1,15 @@ import React, { useEffect, useState } from "react"; import "./certificate.css"; +import opensignLogo from "../../assests/open-sign-logo.png"; -import { Page, Text, View, Document, StyleSheet } from "@react-pdf/renderer"; +import { + Page, + Text, + View, + Document, + StyleSheet, + Image +} from "@react-pdf/renderer"; function Certificate({ pdfData }) { const [isMultiSigners, setIsMultiSigners] = useState(); @@ -49,70 +57,34 @@ function Certificate({ pdfData }) { fontSize: "11px", marginBottom: "10px", color: "gray" + }, + image: { + width: "71px", + height: "17px" } }); const generatedDate = () => { const newDate = new Date(); - const localExpireDate = newDate.toLocaleDateString("en-US", { - day: "numeric", - month: "long", - year: "numeric" - }); - - var currentOffset = newDate.getTimezoneOffset(); - - var ISTOffset = 330; // IST offset UTC +5:30 - - var ISTTime = new Date( - newDate.getTime() + (ISTOffset + currentOffset) * 60000 - ); - - // ISTTime now represents the time in IST coordinates - - var hoursIST = ISTTime.getHours(); - var minutesIST = ISTTime.getMinutes(); + const utcTime = newDate.toUTCString(); return ( - Generated On {localExpireDate} {hoursIST}:{minutesIST} IST + Generated On {utcTime} ); }; const changeCompletedDate = () => { const completedOn = pdfData[0].updatedAt; const newDate = new Date(completedOn); - const localExpireDate = newDate.toLocaleDateString("en-US", { - day: "numeric", - month: "long", - year: "numeric" - }); + const utcTime = newDate.toUTCString(); - var currentOffset = newDate.getTimezoneOffset(); - - var ISTOffset = 330; // IST offset UTC +5:30 - - var ISTTime = new Date( - newDate.getTime() + (ISTOffset + currentOffset) * 60000 - ); - - // ISTTime now represents the time in IST coordinates - - var hoursIST = ISTTime.getHours(); - var minutesIST = ISTTime.getMinutes(); - - return ( - - {localExpireDate} {hoursIST}:{minutesIST} IST - - ); + return {utcTime}; }; const signerName = (data) => { @@ -143,7 +115,19 @@ function Certificate({ pdfData }) { {/** Page defines a single page of content. */} - {generatedDate()} + + + {generatedDate()} + + Organization :   - __ + + {pdfData[0].ExtUserPtr.Company} + Completed on :  {changeCompletedDate()} diff --git a/microfrontends/SignDocuments/src/Component/LegaDrive/FolderDrive/legaDriveComponent.js b/microfrontends/SignDocuments/src/Component/LegaDrive/FolderDrive/legaDriveComponent.js index ab7d1bd4c..df62c822d 100644 --- a/microfrontends/SignDocuments/src/Component/LegaDrive/FolderDrive/legaDriveComponent.js +++ b/microfrontends/SignDocuments/src/Component/LegaDrive/FolderDrive/legaDriveComponent.js @@ -99,7 +99,7 @@ function PdfFileComponent({ //function for navigate user to microapp-signature component const checkPdfStatus = async (data) => { - + const hostUrl = getHostUrl(); const expireDate = data.ExpiryDate.iso; const expireUpdateDate = new Date(expireDate).getTime(); @@ -221,13 +221,19 @@ function PdfFileComponent({ // console.log("download") const pdfName = data && data.Name; const pdfUrl = data && data.SignedUrl ? data.SignedUrl : data.URL; - saveAs(pdfUrl, `${pdfName}_signed_by_OpenSign™.pdf`); + saveAs(pdfUrl, `${sanitizeFileName(pdfName)}_signed_by_OpenSign™.pdf`); } else if (selectType === "Rename") { // console.log("rename") setRenameValue(data.Name); setRename(data.objectId); } }; + + const sanitizeFileName = (pdfName) => { + // Replace spaces with underscore + return pdfName.replace(/ /g, '_'); + } + const handleEnterPress = (e, data) => { if (e.key === "Enter") { handledRenameDoc(data); diff --git a/microfrontends/SignDocuments/src/Component/PdfRequestFiles.js b/microfrontends/SignDocuments/src/Component/PdfRequestFiles.js index 6d2d33061..d741162d5 100644 --- a/microfrontends/SignDocuments/src/Component/PdfRequestFiles.js +++ b/microfrontends/SignDocuments/src/Component/PdfRequestFiles.js @@ -192,7 +192,6 @@ function PdfRequestFiles() { //check document is signed or not if (checkDocIdExist && checkDocIdExist.length > 0) { - setAlreadySign(true); setIsDocId(true); const signerRes = res[0].Signers; //comparison auditTrail user details with signers user details diff --git a/microfrontends/SignDocuments/src/Component/SignYourselfPdf.js b/microfrontends/SignDocuments/src/Component/SignYourselfPdf.js index 92eec4b8f..6591667bc 100644 --- a/microfrontends/SignDocuments/src/Component/SignYourselfPdf.js +++ b/microfrontends/SignDocuments/src/Component/SignYourselfPdf.js @@ -500,7 +500,6 @@ function SignYourSelf() { removeBase64Fromjpeg, "" ); - //function for call to embed signature in pdf and get digital signature pdf signPdfFun(newImgUrl, documentId, data, pdfBase64, pageNo); }) @@ -575,14 +574,18 @@ function SignYourSelf() { const scale = isMobile ? pdfOriginalWidth / newWidth : 1; const posY = () => { - if (id === 0) { - return ( - page.getHeight() - - imgUrlList[id].yPosition * scale - - imgHeight - ); - } else if (id > 0) { - return page.getHeight() - imgUrlList[id].yPosition * scale; + if (isMobile) { + if (id === 0) { + return ( + page.getHeight() - + imgUrlList[id].yPosition * scale - + imgHeight + ); + } else if (id > 0) { + return page.getHeight() - imgUrlList[id].yPosition * scale; + } + } else { + return page.getHeight() - imgUrlList[id].yPosition - imgHeight; } }; page.drawImage(img, { @@ -596,7 +599,6 @@ function SignYourSelf() { }); } const pdfBytes = await pdfDoc.saveAsBase64({ useObjectStreams: false }); - signPdfFun(pdfBytes, documentId); } setIsSignPad(false); @@ -615,17 +617,18 @@ function SignYourSelf() { pageNo ) => { let singleSign; + const isMobile = window.innerWidth < 767; const newWidth = window.innerWidth; const scale = isMobile ? pdfOriginalWidth / newWidth : 1; - const imgWidth = xyPosData.Width.Width ? xyPosData.Width.Width : 150; + const imgWidth = xyPosData ? xyPosData.Width : 150; if (xyPostion.length === 1 && xyPostion[0].pos.length === 1) { const height = xyPosData.Height ? xyPosData.Height : 60; const bottomY = xyPosData.isDrag ? xyPosData.yBottom * scale - height : xyPosData.firstYPos - ? xyPosData.yBottom * scale - height + xyPosData.firstYPos - : xyPosData.yBottom * scale - height; + ? xyPosData.yBottom * scale - height + xyPosData.firstYPos + : xyPosData.yBottom * scale - height; singleSign = { pdfFile: pdfBase64Url, @@ -661,15 +664,7 @@ function SignYourSelf() { // console.log("json ", json); setPdfUrl(json.result.data); if (json.result.data) { - const docStatus = { - isCompleted: true - }; - - setDocumentStatus(docStatus); - const loadObj = { - isLoad: false - }; - setIsLoading(loadObj); + getDocumentDetails(); } }) .catch((err) => { @@ -1115,6 +1110,8 @@ function SignYourSelf() { pdfDetails={pdfDetails} isShowHeader={true} currentSigner={true} + alreadySign={pdfUrl ? true : false} + isSignYourself={true} /> {/* className="hidePdf" */} diff --git a/microfrontends/SignDocuments/src/Component/component/emailComponent.js b/microfrontends/SignDocuments/src/Component/component/emailComponent.js index 5892c8ef5..532b3be4e 100644 --- a/microfrontends/SignDocuments/src/Component/component/emailComponent.js +++ b/microfrontends/SignDocuments/src/Component/component/emailComponent.js @@ -28,9 +28,9 @@ function EmailComponent({ setIsLoading(true); let sendMail; for (let i = 0; i < emailCount.length; i++) { - + try { - + const imgPng = "https://qikinnovation.ams3.digitaloceanspaces.com/logo.png"; // "https://qikinnovation.ams3.digitaloceanspaces.com/mailLogo_2023-08-18T12%3A51%3A31.573Z.png"; @@ -41,7 +41,7 @@ function EmailComponent({ "X-Parse-Application-Id": localStorage.getItem("parseAppId"), sessionToken: localStorage.getItem("accesstoken") }; - + const themeBGcolor = themeColor(); let params = { pdfName: pdfName, @@ -125,12 +125,17 @@ function EmailComponent({ //handle download signed pdf const handleDownloadPdf = () => { - saveAs(pdfUrl, `${pdfName}_signed_by_OpenSign™.pdf`); + saveAs(pdfUrl, `${sanitizeFileName(pdfName)}_signed_by_OpenSign™.pdf`); }; + const sanitizeFileName = (pdfName) => { + // Replace spaces with underscore + return pdfName.replace(/ /g, '_'); + } + const isAndroid = /Android/i.test(navigator.userAgent); - + return (
{/* isEmail */} @@ -248,13 +253,13 @@ function EmailComponent({ style={{ display: "flex", flexDirection: "row", - + flexWrap: "wrap" }} > {emailCount.map((data, ind) => { return ( -
1)) && !window.MSStream + const isAppleDevice = + (/iPad|iPhone|iPod/.test(navigator.platform) || + (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1)) && + !window.MSStream; if (isAndroidDevice || isAppleDevice) { - const byteArray = Uint8Array.from(atob(pdf).split('').map(char => char.charCodeAt(0))); - const blob = new Blob([byteArray], { type: 'application/pdf' }); + const byteArray = Uint8Array.from( + atob(pdf) + .split("") + .map((char) => char.charCodeAt(0)) + ); + const blob = new Blob([byteArray], { type: "application/pdf" }); const blobUrl = URL.createObjectURL(blob); - window.open(blobUrl, '_blank'); + window.open(blobUrl, "_blank"); } else { printModule({ printable: pdf, type: "pdf", base64: true }); } @@ -75,20 +82,159 @@ function Header({ //handle download signed pdf const handleDownloadPdf = () => { const pdfName = pdfDetails[0] && pdfDetails[0].Name; - - saveAs(pdfUrl, `${pdfName}_signed_by_OpenSign™.pdf`); + saveAs(pdfUrl, `${sanitizeFileName(pdfName)}_signed_by_OpenSign™.pdf`); }; + const sanitizeFileName = (pdfName) => { + // Replace spaces with underscore + return pdfName.replace(/ /g, "_"); + }; + //certificate generate and download component in mobile view + const CertificateDropDown = () => { + //after generate download certifcate pdf + const handleDownload = (pdfBlob, fileName) => { + if (pdfBlob) { + const url = window.URL.createObjectURL(pdfBlob); + // Create a temporary anchor element + const link = document.createElement("a"); + link.href = url; + link.download = fileName; + // Append the anchor to the body + document.body.appendChild(link); + // Programmatically click the anchor to trigger the download + link.click(); + // Remove the anchor from the body + document.body.removeChild(link); + // Release the object URL to free up resources + window.URL.revokeObjectURL(url); + } + }; + + return ( + e.preventDefault()} + style={{ textDecoration: "none", zIndex: "35" }} + document={} + > + {({ blob, url, loading, error }) => ( + <> + {loading ? ( +
+ + Certificate +
+ ) : ( +
+ handleDownload( + blob, + `completion certificate-${ + pdfDetails[0] && pdfDetails[0].Name + }.pdf` + ) + } + > + + Certificate +
+ )} + + )} +
+ ); + }; + const CertificateComponent = () => { + return ( + } + fileName={`completion certificate-${ + pdfDetails[0] && pdfDetails[0].Name + }.pdf`} + > + {({ blob, url, loading, error }) => + loading ? ( + + ) : ( + + ) + } + + ); + }; return (
{isMobile && isShowHeader ? (