From 30b21b42c56f320875b7812ef8498f6227956c63 Mon Sep 17 00:00:00 2001 From: varutasu <104105839+varutasu@users.noreply.github.com> Date: Wed, 27 May 2026 14:25:20 -0500 Subject: [PATCH] fix(api): validate quantity on POST /api/user-cards (#46) Match decks handler parseInt/NaN guard so non-numeric quantities cannot corrupt user_cards row counts from scanner add paths. Co-authored-by: Cursor --- .../audit-redesign-scanner-flow-44.md | 4 +-- .convoys/scanner-user-cards-quantity-guard.md | 25 +++++++++++++++++++ pages/api/user-cards.js | 9 +++++-- 3 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 .convoys/scanner-user-cards-quantity-guard.md diff --git a/.convoys/redesign-scanner-flow/audit-redesign-scanner-flow-44.md b/.convoys/redesign-scanner-flow/audit-redesign-scanner-flow-44.md index c43e1fc..51a6142 100644 --- a/.convoys/redesign-scanner-flow/audit-redesign-scanner-flow-44.md +++ b/.convoys/redesign-scanner-flow/audit-redesign-scanner-flow-44.md @@ -24,8 +24,8 @@ Reports posted to [PR #44](https://github.com/varutasu/tcg-vault/pull/44#issueco | Slug | Priority | Source | | --- | --- | --- | -| `scanner-redesign-a11y-fixes` | P1 | **In progress** — PR pending | -| `scanner-user-cards-quantity-guard` | P2 | Reviewer — parseInt/NaN guard parity with decks handler | +| `scanner-redesign-a11y-fixes` | P1 | **RESOLVED** — PR #45 | +| `scanner-user-cards-quantity-guard` | P2 | **In progress** — PR pending | | `test-scanner-redesign-surfaces` | P2 | Reviewer — unit tests for new components + upload route | | `document-condition-foil-destination-semantics` | P3 | Reviewer — clarify or migrate condition/foil for collection/deck rows | | `camera-scanner-token-cleanup` | P3 | Design system — replace hardcoded hex overlay colors in CameraScanner | diff --git a/.convoys/scanner-user-cards-quantity-guard.md b/.convoys/scanner-user-cards-quantity-guard.md new file mode 100644 index 0000000..1407c57 --- /dev/null +++ b/.convoys/scanner-user-cards-quantity-guard.md @@ -0,0 +1,25 @@ +--- +name: scanner-user-cards-quantity-guard +classification: fix +success_metric: | + POST /api/user-cards rejects non-numeric and sub-1 quantity with 400, matching + the decks/[id]/cards handler contract. +depends_on: + - redesign-scanner-flow +status: open +created: 2026-05-27 +--- + +# Convoy: scanner-user-cards-quantity-guard + +P2 follow-up from `audit-redesign-scanner-flow-44` reviewer report. + +## Scope + +- `pages/api/user-cards.js` — `parseInt(quantity, 10)` + NaN / `< 1` guard on POST + +## Acceptance criteria + +1. `quantity: "abc"` → 400 `Quantity must be at least 1` +2. `quantity: 0` → 400 +3. Valid integer ≥ 1 uses parsed value for INSERT and UPDATE increment diff --git a/pages/api/user-cards.js b/pages/api/user-cards.js index 6739290..eb579d6 100644 --- a/pages/api/user-cards.js +++ b/pages/api/user-cards.js @@ -21,6 +21,11 @@ export default async function handler(req, res) { return res.status(400).json({ error: 'Card ID is required' }); } + const parsedQuantity = parseInt(quantity, 10); + if (Number.isNaN(parsedQuantity) || parsedQuantity < 1) { + return res.status(400).json({ error: 'Quantity must be at least 1' }); + } + const scanImageUrl = typeof scanImageUrlRaw === 'string' && scanImageUrlRaw.trim().length > 0 ? scanImageUrlRaw.trim() @@ -34,7 +39,7 @@ export default async function handler(req, res) { if (existingResult.rows.length > 0) { // Update quantity; preserve existing scan image unless a new URL is supplied - const newQuantity = existingResult.rows[0].quantity + quantity; + const newQuantity = existingResult.rows[0].quantity + parsedQuantity; await sql` UPDATE user_cards SET quantity = ${newQuantity}, @@ -46,7 +51,7 @@ export default async function handler(req, res) { // Insert new record await sql` INSERT INTO user_cards (user_id, card_id, quantity, condition, is_foil, scan_image_url) - VALUES (${user.userId}, ${cardId}, ${quantity}, ${condition}, ${is_foil}, ${scanImageUrl}) + VALUES (${user.userId}, ${cardId}, ${parsedQuantity}, ${condition}, ${is_foil}, ${scanImageUrl}) `; }