From b1847b4cfc6211c551c806e4cf19246313490cd5 Mon Sep 17 00:00:00 2001 From: Stephen Adamson Date: Sun, 31 May 2026 18:49:16 +0100 Subject: [PATCH] fix(payments,portfolio,scheduling): add ID validation hardening Add validators.IsValidID() checks on URL param IDs to return 404 instead of 400 for invalid IDs. Add offset cap and query length limit in portfolio images handler. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- backend/handlers/payments/handlers.go | 25 ++++++++++---------- backend/handlers/portfolio/images.go | 10 +++++++- backend/handlers/scheduling/time-blockers.go | 4 ++-- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/backend/handlers/payments/handlers.go b/backend/handlers/payments/handlers.go index a51d6b7..349c945 100644 --- a/backend/handlers/payments/handlers.go +++ b/backend/handlers/payments/handlers.go @@ -2,6 +2,7 @@ package payments import ( "crussell/internal/square" + "crussell/internal/validators" "crussell/mw" "encoding/json" "errors" @@ -87,8 +88,8 @@ type PaymentSummaryResponse struct { func CreateTerminalPayment(w http.ResponseWriter, r *http.Request) { bookingID := chi.URLParam(r, "id") - if bookingID == "" { - http.Error(w, "Booking ID is required", http.StatusBadRequest) + if bookingID == "" || !validators.IsValidID(bookingID) { + http.Error(w, "Booking not found", http.StatusNotFound) return } @@ -260,8 +261,8 @@ func GetCheckoutStatus(w http.ResponseWriter, r *http.Request) { func CreateBookingPayment(w http.ResponseWriter, r *http.Request) { bookingID := chi.URLParam(r, "id") - if bookingID == "" { - http.Error(w, "Booking ID is required", http.StatusBadRequest) + if bookingID == "" || !validators.IsValidID(bookingID) { + http.Error(w, "Booking not found", http.StatusNotFound) return } @@ -459,8 +460,8 @@ func GetUserPaymentMethods(w http.ResponseWriter, r *http.Request) { func DeletePaymentMethod(w http.ResponseWriter, r *http.Request) { cardID := chi.URLParam(r, "id") - if cardID == "" { - http.Error(w, "Card ID is required", http.StatusBadRequest) + if cardID == "" || !validators.IsValidID(cardID) { + http.Error(w, "Payment method not found", http.StatusNotFound) return } @@ -524,8 +525,8 @@ func CreatePaymentMethod(w http.ResponseWriter, r *http.Request) { func RefundPayment(w http.ResponseWriter, r *http.Request) { paymentID := chi.URLParam(r, "payment_id") - if paymentID == "" { - http.Error(w, "Payment ID is required", http.StatusBadRequest) + if paymentID == "" || !validators.IsValidID(paymentID) { + http.Error(w, "Payment not found", http.StatusNotFound) return } @@ -640,8 +641,8 @@ func RefundPayment(w http.ResponseWriter, r *http.Request) { func CreateTipPayment(w http.ResponseWriter, r *http.Request) { bookingID := chi.URLParam(r, "id") - if bookingID == "" { - http.Error(w, "Booking ID is required", http.StatusBadRequest) + if bookingID == "" || !validators.IsValidID(bookingID) { + http.Error(w, "Booking not found", http.StatusNotFound) return } @@ -753,8 +754,8 @@ func CreateTipPayment(w http.ResponseWriter, r *http.Request) { func GetBookingPaymentSummary(w http.ResponseWriter, r *http.Request) { bookingID := chi.URLParam(r, "id") - if bookingID == "" { - http.Error(w, "Booking ID is required", http.StatusBadRequest) + if bookingID == "" || !validators.IsValidID(bookingID) { + http.Error(w, "Booking not found", http.StatusNotFound) return } diff --git a/backend/handlers/portfolio/images.go b/backend/handlers/portfolio/images.go index e806887..bf4eb1f 100644 --- a/backend/handlers/portfolio/images.go +++ b/backend/handlers/portfolio/images.go @@ -6,6 +6,7 @@ import ( "crussell/db" "crussell/internal/images" "crussell/internal/s3" + "crussell/internal/validators" "crussell/mw" "encoding/json" "fmt" @@ -114,7 +115,7 @@ func ListImages(w http.ResponseWriter, r *http.Request) { } } if o := r.URL.Query().Get("offset"); o != "" { - if parsed, err := strconv.Atoi(o); err == nil && parsed >= 0 { + if parsed, err := strconv.Atoi(o); err == nil && parsed >= 0 && parsed <= 10000 { offset = parsed } } @@ -264,6 +265,9 @@ func ListImages(w http.ResponseWriter, r *http.Request) { func ListTags(w http.ResponseWriter, r *http.Request) { q := r.URL.Query().Get("q") + if len(q) > 100 { + q = q[:100] + } var query string var args []interface{} @@ -694,6 +698,10 @@ func DeleteImage(w http.ResponseWriter, r *http.Request) { } imageID := chi.URLParam(r, "id") + if imageID == "" || !validators.IsValidID(imageID) { + http.Error(w, "Image not found", http.StatusNotFound) + return + } var url, thumbURL string err := db.DB.QueryRow(r.Context(), ` diff --git a/backend/handlers/scheduling/time-blockers.go b/backend/handlers/scheduling/time-blockers.go index e10b2f8..1ec1c9a 100644 --- a/backend/handlers/scheduling/time-blockers.go +++ b/backend/handlers/scheduling/time-blockers.go @@ -169,8 +169,8 @@ func CreateTimeBlocker(w http.ResponseWriter, r *http.Request) { // DELETE /api/admin/time-blockers/{id} func DeleteTimeBlocker(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") - if id == "" { - http.Error(w, "missing id parameter", http.StatusBadRequest) + if id == "" || !validators.IsValidID(id) { + http.Error(w, "time blocker not found", http.StatusNotFound) return }