From 80aeb74078ca618e20711296ec1c9133ea3b22e7 Mon Sep 17 00:00:00 2001 From: zqxuii <96630940+zqxuii@users.noreply.github.com> Date: Tue, 21 Jul 2026 19:02:00 +0000 Subject: [PATCH] fix(money): reject partial dollar amounts passed as numbers The number branch of dollarsToCents rounded sub-cent values (e.g. 1.234 -> 123) while the string path rejects the equivalent "$1.234" via MONEY_RE. Because the auction/bid routes pass req.json() amounts straight through, a numeric JSON body like {"amount": 5.005} bypassed the partial-amount guard added in #40. Reject sub-cent precision in the number path too; add regression tests. --- lib/money.ts | 8 +++++++- tests/money.test.mjs | 12 ++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/money.ts b/lib/money.ts index 01e4511..9c68729 100644 --- a/lib/money.ts +++ b/lib/money.ts @@ -4,7 +4,13 @@ export function dollarsToCents(value: unknown): number | null { if (value === "" || value == null) return null; if (typeof value === "number") { if (!Number.isFinite(value) || value <= 0) return null; - return Math.round(value * 100); + // Reject sub-cent precision, exactly as the string path does via MONEY_RE's + // `\.\d{1,2}`. Without this a bare number like 1.234 silently rounds to 123, + // letting a partial dollar amount slip through the number path while the + // equivalent "$1.234" string is rejected. + const cents = value * 100; + if (Math.abs(cents - Math.round(cents)) > 1e-6) return null; + return Math.round(cents); } const text = String(value).trim().replace(/\s+/g, ""); diff --git a/tests/money.test.mjs b/tests/money.test.mjs index e12f083..3ba0895 100644 --- a/tests/money.test.mjs +++ b/tests/money.test.mjs @@ -23,3 +23,15 @@ test("dollarsToCents handles numeric values without accepting non-finite numbers assert.equal(dollarsToCents(Number.NaN), null); assert.equal(dollarsToCents(Number.POSITIVE_INFINITY), null); }); + +test("dollarsToCents rejects partial dollar amounts passed as numbers", () => { + // The number path must reject sub-cent precision just like the "$1.234" + // string case above — otherwise numeric JSON bodies bypass the guard. + assert.equal(dollarsToCents(1.234), null); + assert.equal(dollarsToCents(5.005), null); + assert.equal(dollarsToCents(0.999), null); + // Whole-cent numbers still parse, including exact two-decimal values. + assert.equal(dollarsToCents(1), 100); + assert.equal(dollarsToCents(1.99), 199); + assert.equal(dollarsToCents(1234.56), 123456); +});