Skip to content
Merged

v2.23.1 #1784

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
606 changes: 603 additions & 3 deletions apps/OpenSign/package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions apps/OpenSign/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
"private": true,
"dependencies": {
"@formkit/auto-animate": "^0.8.2",
"@imgly/background-removal": "^1.6.0",
"@lottiefiles/dotlottie-react": "^0.13.5",
"@pdf-lib/fontkit": "^1.1.1",
"@radix-ui/themes": "^3.2.1",
"@reduxjs/toolkit": "^2.8.2",
"@imgly/background-removal": "^1.6.0",
"axios": "^1.9.0",
"date-fns-tz": "^3.2.0",
"file-saver": "^2.0.5",
Expand All @@ -19,8 +19,8 @@
"jwt-decode": "^4.0.0",
"moment": "^2.30.1",
"parse": "^6.1.1",
"pkijs": "^3.0.8",
"pdf-lib": "^1.17.1",
"pkijs": "^3.0.8",
"print-js": "^1.6.0",
"prismjs": "^1.30.0",
"radix-ui": "^1.4.2",
Expand Down
39 changes: 26 additions & 13 deletions apps/OpenSign/src/components/pdf/AgreementSign.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,32 @@ function AgreementSign(props) {
<div className="op-modal op-modal-open absolute z-[448]">
<div className="w-[95%] md:w-[60%] lg:w-[40%] op-modal-box overflow-y-auto hide-scrollbar text-sm p-4">
<div className="flex flex-row items-center">
<input
data-tut="IsAgree"
className="mr-3 op-checkbox op-checkbox-m"
type="checkbox"
value={isChecked}
onChange={(e) => {
setIsChecked(e.target.checked);
if (e.target.checked) {
props.setIsAgreeTour(false);
}
props.showFirstWidget();
}}
/>
<label className="inline-flex justify-center items-center cursor-pointer mb-0">
{/* 1) This div becomes the “fake” checkbox */}
<div
data-tut="IsAgree"
className={`w-6 h-6 border-2 mr-3 rounded-full flex text-center items-center justify-center ${isChecked ? "op-border-primary" : "border-red-500"}`}
>
{isChecked ? (
<span className="op-text-primary text-sm font-bold">✓</span>
) : (
<span className="text-red-500 text-sm font-bold">X</span>
)}
</div>
{/* 2) Visually hide the native checkbox but keep it in the DOM */}
<input
className="sr-only"
type="checkbox"
checked={isChecked}
onChange={(e) => {
setIsChecked(e.target.checked);
if (e.target.checked) {
props.setIsAgreeTour(false);
}
props.showFirstWidget();
}}
/>
</label>
<div className="text-[11px] md:text-base">
<span>{t("agree-p1")}</span>
<span
Expand Down
1 change: 1 addition & 0 deletions apps/OpenSign/src/components/pdf/WidgetComponent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ function WidgetComponent(props) {
handleDivClick={props.handleDivClick}
handleMouseLeave={props.handleMouseLeave}
signRef={signRef}
addPositionOfSignature={props.addPositionOfSignature}
/>
</div>
</div>
Expand Down
97 changes: 55 additions & 42 deletions apps/OpenSign/src/components/pdf/WidgetsValueModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
onSaveSign,
radioButtonWidget,
selectCheckbox,
signatureTypes,
textInputWidget,
textWidget,
years
Expand Down Expand Up @@ -85,7 +84,8 @@ function WidgetsValueModal(props) {
setXyPosition,
isSave,
setUniqueId,
tempSignerId
tempSignerId,
signatureTypes
} = props;
const [penColor, setPenColor] = useState("blue");
const [isOptional, setIsOptional] = useState(true);
Expand Down Expand Up @@ -400,7 +400,7 @@ function WidgetsValueModal(props) {

if (getIndex !== -1) {
setIsSignTypes(true);
const tab = signatureTypes[getIndex].name;
const tab = signatureTypes?.[getIndex].name;
if (tab === "draw") {
setIsTab("draw");
setSignatureType("draw");
Expand All @@ -427,7 +427,7 @@ function WidgetsValueModal(props) {
}
}
function isTabEnabled(tabName) {
const isEnabled = signatureTypes.find((x) => x.name === tabName)?.enabled;
const isEnabled = signatureTypes?.find((x) => x.name === tabName)?.enabled;
return isEnabled;
}

Expand Down Expand Up @@ -683,51 +683,64 @@ function WidgetsValueModal(props) {
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isTab]);
//function for convert input text value in image
// function for convert input text value in image
const convertToImg = async (fontStyle, text, color) => {
//get text content to convert in image
const textContent = text;
const fontfamily = fontStyle
? fontStyle
: fontSelect
? fontSelect
: "Fasthand";
const fontSizeValue = "40px";
//creating span for getting text content width
// 1) Read the max widget dimensions:
const maxWidth = currWidgetsDetails.Width; // e.g. 150 px
const maxHeight = currWidgetsDetails.Height; // e.g. 40 px

// 2) Pick a “baseline” font size for measurement:
const baselineFontSizePx = 40;
const chosenFontFamily = fontStyle || fontSelect || "Fasthand";
const fillColor = color || penColor;

// 3) Create a temporary <span> (hidden) to measure the text at 40px:
const span = document.createElement("span");
span.textContent = textContent;
span.style.font = `${fontSizeValue} ${fontfamily}`; // here put your text size and font family
span.style.color = color ? color : penColor;
span.style.display = "hidden";
document.body.appendChild(span); // Replace 'container' with the ID of the container element
span.textContent = text;
span.style.font = `${baselineFontSizePx}px ${chosenFontFamily}`;
span.style.visibility = "hidden"; // keep it in the DOM so offsetWidth/Height works
span.style.whiteSpace = "nowrap"; // so we measure a single line
document.body.appendChild(span);

// Measured size at 40px:
const measuredWidth = span.offsetWidth;
const measuredHeight = span.offsetHeight;
document.body.removeChild(span);

//create canvas to render text in canvas and convert in image
const canvasElement = document.createElement("canvas");
// Draw the text content on the canvas
const ctx = canvasElement.getContext("2d");
// 4) Compute uniform scale so that 40px‐sized text fits inside (maxWidth × maxHeight):
const scaleX = maxWidth / measuredWidth;
const scaleY = maxHeight / measuredHeight;
const scale = Math.min(scaleX, scaleY, 1); // never scale up beyond 1

// 5) Final text size in **CSS px**:
const finalFontSizePx = baselineFontSizePx * scale;

// 6) Create a <canvas> that is ALWAYS maxWidth × maxHeight in **CSS px**,
// but use devicePixelRatio for sharpness.
const pixelRatio = window.devicePixelRatio || 1;
const addExtraWidth = currWidgetsDetails?.type === "initials" ? 10 : 50;
const width = span.offsetWidth + addExtraWidth;
const height = span.offsetHeight;
setTextWidth(width);
setTextHeight(height);
const font = span.style["font"];
// Set the canvas dimensions to match the span
canvasElement.width = width * pixelRatio;
canvasElement.height = height * pixelRatio;
const canvas = document.createElement("canvas");

// ★ Instead of using `finalTextWidth/Height`, force it to be the max box:
canvas.width = Math.ceil(maxWidth * pixelRatio);
canvas.height = Math.ceil(maxHeight * pixelRatio);

// You can customize text styles if needed
ctx.font = font;
ctx.fillStyle = color ? color : penColor; // Set the text color
const ctx = canvas.getContext("2d");
ctx.scale(pixelRatio, pixelRatio);

// 7) Draw the text **centered** inside the full maxWidth×maxHeight box:
ctx.font = `${finalFontSizePx}px ${chosenFontFamily}`;
ctx.fillStyle = fillColor;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.scale(pixelRatio, pixelRatio);
// Draw the content of the span onto the canvas
ctx.fillText(span.textContent, width / 2, height / 2); // Adjust the x,y-coordinate as needed
//remove span tag
document.body.removeChild(span);
// Convert the canvas to image data
const dataUrl = canvasElement.toDataURL("image/png");

// ★ Center = (maxWidth/2, maxHeight/2):
const centerX = maxWidth / 2;
const centerY = maxHeight / 2;

ctx.fillText(text, centerX, centerY);

// 8) Export to a PNG data-URL:
const dataUrl = canvas.toDataURL("image/png");
setSignature(dataUrl);
};
const PenColorComponent = (props) => {
Expand Down
14 changes: 9 additions & 5 deletions apps/OpenSign/src/constant/Utils.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -683,10 +683,14 @@ export const signPdfFun = async (
};

export const randomId = () => {
const randomBytes = crypto.getRandomValues(new Uint16Array(1));
const randomValue = randomBytes[0];
const randomDigit = 1000 + (randomValue % 9000);
return randomDigit;
// 1. Grab a cryptographically-secure 32-bit random value
const randomBytes = crypto.getRandomValues(new Uint32Array(1));
const raw = randomBytes[0]; // 0 … 4 294 967 295

// 2. Collapse into a 90 000 000-wide band (0…89 999 999), then shift to 10 000 000…99 999 999
const eightDigit = 10_000_000 + (raw % 90_000_000);

Check failure

Code scanning / CodeQL

Creating biased random numbers from a cryptographically secure source

Using modulo on a [cryptographically secure random number](1) produces biased results.

Copilot Autofix

AI about 1 year ago

To fix the issue, we need to ensure that the random ID generation is unbiased. This can be achieved by discarding values that would introduce bias, as demonstrated in the background example. Specifically, we will discard any random value greater than or equal to the largest multiple of the divisor (90,000,000) that fits within the range of raw (4,294,967,295). This ensures that the modulo operation is applied to a uniformly distributed range.

The fix involves modifying the randomId function to include a loop that discards biased values and only uses unbiased random values for the modulo operation. No new dependencies are required, as the existing crypto.getRandomValues API is sufficient.


Suggested changeset 1
apps/OpenSign/src/constant/Utils.jsx

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/apps/OpenSign/src/constant/Utils.jsx b/apps/OpenSign/src/constant/Utils.jsx
--- a/apps/OpenSign/src/constant/Utils.jsx
+++ b/apps/OpenSign/src/constant/Utils.jsx
@@ -685,8 +685,14 @@
 export const randomId = () => {
-  // 1. Grab a cryptographically-secure 32-bit random value
-  const randomBytes = crypto.getRandomValues(new Uint32Array(1));
-  const raw = randomBytes[0];          // 0 … 4 294 967 295
+  const divisor = 90_000_000;
+  const rangeLimit = Math.floor(4_294_967_295 / divisor) * divisor; // Largest multiple of divisor within range
+  let raw;
 
-  // 2. Collapse into a 90 000 000-wide band (0…89 999 999), then shift to 10 000 000…99 999 999
-  const eightDigit = 10_000_000 + (raw % 90_000_000);
+  // Loop until we get an unbiased random value
+  do {
+    const randomBytes = crypto.getRandomValues(new Uint32Array(1));
+    raw = randomBytes[0];
+  } while (raw >= rangeLimit);
+
+  // Collapse into a 90 000 000-wide band (0…89 999 999), then shift to 10 000 000…99 999 999
+  const eightDigit = 10_000_000 + (raw % divisor);
 
EOF
@@ -685,8 +685,14 @@
export const randomId = () => {
// 1. Grab a cryptographically-secure 32-bit random value
const randomBytes = crypto.getRandomValues(new Uint32Array(1));
const raw = randomBytes[0]; // 0 … 4 294 967 295
const divisor = 90_000_000;
const rangeLimit = Math.floor(4_294_967_295 / divisor) * divisor; // Largest multiple of divisor within range
let raw;

// 2. Collapse into a 90 000 000-wide band (0…89 999 999), then shift to 10 000 000…99 999 999
const eightDigit = 10_000_000 + (raw % 90_000_000);
// Loop until we get an unbiased random value
do {
const randomBytes = crypto.getRandomValues(new Uint32Array(1));
raw = randomBytes[0];
} while (raw >= rangeLimit);

// Collapse into a 90 000 000-wide band (0…89 999 999), then shift to 10 000 000…99 999 999
const eightDigit = 10_000_000 + (raw % divisor);

Copilot is powered by AI and may make mistakes. Always verify output.

return eightDigit;
};

export const createDocument = async (
Expand Down Expand Up @@ -1552,7 +1556,7 @@ export const multiSignEmbed = async (
let hasError = false;
for (let item of widgets) {
//pdfOriginalWH contained all pdf's pages width and height
//'getSize' is used to get particular pdf's page width and height
//'getSize' is used to get particular pdf's page width and height
const getSize = pdfOriginalWH.find(
(page) => page?.pageNumber === item?.pageNumber
);
Expand Down
4 changes: 2 additions & 2 deletions apps/OpenSign/src/pages/Form.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ const Forms = (props) => {
setBcc([]);
setFolder({ ObjectId: "", Name: "" });
const notifySign =
extUserData?.NotifyOnSignatures
extUserData?.NotifyOnSignatures !== undefined
? extUserData?.NotifyOnSignatures
: true;
setFormData({
Expand Down Expand Up @@ -534,7 +534,7 @@ const Forms = (props) => {
setBcc([]);
setFolder({ ObjectId: "", Name: "" });
const notifySign =
extUserData?.NotifyOnSignatures
extUserData?.NotifyOnSignatures !== undefined
? extUserData?.NotifyOnSignatures
: true;
let obj = {
Expand Down
11 changes: 8 additions & 3 deletions apps/OpenSign/src/pages/PdfRequestFiles.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -821,7 +821,7 @@ function PdfRequestFiles(
await embedDocId(pdfOriginalWH, pdfDoc, docId);
}
}
//embed all widgets in document
//embed all widgets in document
const pdfBytes = await multiSignEmbed(
pdfOriginalWH,
widgets,
Expand Down Expand Up @@ -1564,11 +1564,15 @@ function PdfRequestFiles(
const widgetValue = widgetDataValue(dragTypeValue, parseUser);
//adding and updating drop position in array when user drop signature button in div
if (item === "onclick") {
// `getBoundingClientRect()` is used to get accurate measurement width, height of the Pdf div
const divWidth = divRef.current.getBoundingClientRect().width;
const divHeight = divRef.current.getBoundingClientRect().height;
// `getBoundingClientRect()` is used to get accurate measurement height of the div
// Compute the pixel‐space center within the PDF viewport:
const centerX_Pixels = divWidth / 2 - widgetWidth / 2;
const xPosition_Final = centerX_Pixels / (containerScale * scale);
dropObj = {
//onclick put placeholder center on pdf
xPosition: widgetWidth / 4 + containerWH.width / 2,
xPosition: xPosition_Final,
yPosition: widgetHeight + divHeight / 2,
isStamp:
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
Expand Down Expand Up @@ -2259,6 +2263,7 @@ function PdfRequestFiles(
index={pageNumber}
setUniqueId={setUniqueId}
tempSignerId={tempSignerId}
signatureTypes={signatureType}
/>
)}
<DownloadPdfZip
Expand Down
9 changes: 7 additions & 2 deletions apps/OpenSign/src/pages/PlaceHolderSign.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -524,11 +524,15 @@ function PlaceHolderSign() {
defaultWidthHeight(dragTypeValue).height * containerScale;
//adding and updating drop position in array when user drop signature button in div
if (item === "onclick") {
// `getBoundingClientRect()` is used to get accurate measurement width, height of the Pdf div
const divWidth = divRef.current.getBoundingClientRect().width;
const divHeight = divRef.current.getBoundingClientRect().height;
// `getBoundingClientRect()` is used to get accurate measurement height of the div
// Compute the pixel‐space center within the PDF viewport:
const centerX_Pixels = divWidth / 2 - widgetWidth / 2;
const xPosition_Final = centerX_Pixels / (containerScale * scale);
dropObj = {
//onclick put placeholder center on pdf
xPosition: widgetWidth / 4 + containerWH.width / 2,
xPosition: xPosition_Final,
yPosition: widgetHeight + divHeight / 2,
isStamp:
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
Expand Down Expand Up @@ -2589,6 +2593,7 @@ function PlaceHolderSign() {
isSave={true}
tempSignerId={tempSignerId}
setUniqueId={setUniqueId}
signatureTypes={signatureType}
/>
)}
<ModalUi
Expand Down
12 changes: 9 additions & 3 deletions apps/OpenSign/src/pages/SignyourselfPdf.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ import {
generatePdfName,
handleRemoveWidgets,
addWidgetSelfsignOptions,
getOriginalWH
getOriginalWH,
signatureTypes
} from "../constant/Utils";
import { useParams } from "react-router";
import Tour from "../primitives/Tour";
Expand Down Expand Up @@ -365,15 +366,19 @@ function SignYourSelf() {
);
//adding and updating drop position in array when user drop signature button in div
if (item === "onclick") {
// `getBoundingClientRect()` is used to get accurate measurement height of the div
// `getBoundingClientRect()` is used to get accurate measurement width, height of the Pdf div
const divHeight = divRef.current.getBoundingClientRect().height;
const divWidth = divRef.current.getBoundingClientRect().width;
const getWidth = widgetTypeExist
? calculateInitialWidthHeight(widgetValue).getWidth
: defaultWidthHeight(dragTypeValue).width;
const getHeight = defaultWidthHeight(dragTypeValue).height;

// Compute the pixel‐space center within the PDF viewport:
const centerX_Pixels = divWidth / 2 - getWidth / 2;
const xPosition_Final = centerX_Pixels / (containerScale * scale);
dropObj = {
xPosition: getWidth / 2 + containerWH.width / 2,
xPosition: xPosition_Final,
yPosition: getHeight + divHeight / 2,
isStamp:
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
Expand Down Expand Up @@ -1330,6 +1335,7 @@ function SignYourSelf() {
currWidgetsDetails={currWidgetsDetails}
index={index}
isSave={true}
signatureTypes={signatureTypes}
/>
)}
<RotateAlert
Expand Down
8 changes: 6 additions & 2 deletions apps/OpenSign/src/pages/TemplatePlaceholder.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -376,11 +376,15 @@ const TemplatePlaceholder = () => {
filterSignerPos;
let placeHolder;
if (item === "onclick") {
// `getBoundingClientRect()` is used to get accurate measurement height of the div
// `getBoundingClientRect()` is used to get accurate measurement width, height of the Pdf div
const divWidth = divRef.current.getBoundingClientRect().width;
const divHeight = divRef.current.getBoundingClientRect().height;
// Compute the pixel‐space center within the PDF viewport:
const centerX_Pixels = divWidth / 2 - widgetWidth / 2;
const xPosition_Final = centerX_Pixels / (containerScale * scale);
dropObj = {
//onclick put placeholder center on pdf
xPosition: widgetWidth / 4 + containerWH.width / 2,
xPosition: xPosition_Final,
yPosition: widgetHeight + divHeight / 2,
isStamp:
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
Expand Down