Handle malformed wallet withdrawal bodies#185
Conversation
Greptile SummaryThis PR hardens the wallet withdrawal endpoint against malformed request bodies by extracting a
Confidence Score: 5/5Safe to merge — all new validation exits before any database or wallet calls and the existing happy-path is untouched. The changes are narrowly scoped: a new parsing helper and three extra type guards added ahead of all side-effectful operations. Each guard is independently tested and the existing tests continue to pass unchanged. No logic downstream of the guards is modified. No files require special attention. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[POST /api/wallet/withdraw] --> B[getAuthContext]
B -- null --> C[401 Unauthorized]
B -- auth --> D[parseWithdrawRequestBody]
D -- parse error / non-object --> E[400 Invalid request body]
D -- body object --> F{!amount_sats OR !destination}
F -- true --> G[400 required fields]
F -- false --> H{typeof amount_sats !== number OR !isInteger}
H -- true --> I[400 Amount must be whole number]
H -- false --> J{typeof destination !== string}
J -- true --> K[400 Destination must be a string]
J -- false --> L{amount_sats range check}
L -- out of range --> M[400 Amount out of range]
L -- ok --> N[Supabase rate-limit check]
N --> O[Daily limit check]
O --> P[getUserLnWallet]
P -- null --> Q[400 No Lightning wallet]
P -- wallet --> R[getLnBalance]
R -- insufficient --> S[400 Insufficient balance]
R -- ok --> T[payInvoice / LNURL resolve]
T -- error --> U[502 Payment failed]
T -- ok --> V[syncBalanceCache + insert tx]
V --> W[200 ok]
Reviews (3): Last reviewed commit: "Handle malformed wallet withdrawal bodie..." | Re-trigger Greptile |
| if (!amount_sats || !destination) { | ||
| return NextResponse.json({ error: "amount_sats and destination are required" }, { status: 400 }); | ||
| } | ||
| if (!Number.isInteger(amount_sats)) { | ||
| if (typeof amount_sats !== "number" || !Number.isInteger(amount_sats)) { | ||
| return NextResponse.json({ error: "Amount must be a whole number" }, { status: 400 }); | ||
| } | ||
| if (typeof destination !== "string") { | ||
| return NextResponse.json({ error: "Destination must be a string" }, { status: 400 }); | ||
| } |
There was a problem hiding this comment.
Falsy destination short-circuits new type check
The !destination guard on line 43 is falsy, so a destination: 0 payload returns "amount_sats and destination are required" before the new typeof destination !== "string" check on line 49 is ever reached. Concretely, { amount_sats: 100, destination: 0 } produces the generic "required" message rather than the more informative "Destination must be a string". Using an explicit null/undefined check (destination == null) instead of the falsy !destination would let all non-null, non-string destinations reach the type-specific error path the PR introduces.
Summary
Fixes #184
uGig task
Submitted for the active uGig repo testing task: https://ugig.net/gigs/4741218f-a723-46bb-82cb-6516120331ae
No payout details included; those can be provided after acceptance.
Tests