fix(api): validate quantity on POST /api/user-cards #46

Merged
varutasu merged 1 commit from fix/scanner-user-cards-quantity-guard into main 2026-05-27 15:25:21 -04:00
3 changed files with 34 additions and 4 deletions
Showing only changes of commit 8827d7b20c - Show all commits

View file

@ -24,8 +24,8 @@ Reports posted to [PR #44](https://github.com/varutasu/tcg-vault/pull/44#issueco
| Slug | Priority | Source | | Slug | Priority | Source |
| --- | --- | --- | | --- | --- | --- |
| `scanner-redesign-a11y-fixes` | P1 | **In progress** — PR pending | | `scanner-redesign-a11y-fixes` | P1 | **RESOLVED** — PR #45 |
| `scanner-user-cards-quantity-guard` | P2 | Reviewer — parseInt/NaN guard parity with decks handler | | `scanner-user-cards-quantity-guard` | P2 | **In progress** — PR pending |
| `test-scanner-redesign-surfaces` | P2 | Reviewer — unit tests for new components + upload route | | `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 | | `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 | | `camera-scanner-token-cleanup` | P3 | Design system — replace hardcoded hex overlay colors in CameraScanner |

View file

@ -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

View file

@ -21,6 +21,11 @@ export default async function handler(req, res) {
return res.status(400).json({ error: 'Card ID is required' }); 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 = const scanImageUrl =
typeof scanImageUrlRaw === 'string' && scanImageUrlRaw.trim().length > 0 typeof scanImageUrlRaw === 'string' && scanImageUrlRaw.trim().length > 0
? scanImageUrlRaw.trim() ? scanImageUrlRaw.trim()
@ -34,7 +39,7 @@ export default async function handler(req, res) {
if (existingResult.rows.length > 0) { if (existingResult.rows.length > 0) {
// Update quantity; preserve existing scan image unless a new URL is supplied // 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` await sql`
UPDATE user_cards UPDATE user_cards
SET quantity = ${newQuantity}, SET quantity = ${newQuantity},
@ -46,7 +51,7 @@ export default async function handler(req, res) {
// Insert new record // Insert new record
await sql` await sql`
INSERT INTO user_cards (user_id, card_id, quantity, condition, is_foil, scan_image_url) 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})
`; `;
} }