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 <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-05-31 18:49:16 +01:00
co-authored by Sisyphus
parent 1d8247e655
commit b1847b4cfc
3 changed files with 24 additions and 15 deletions
+13 -12
View File
@@ -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
}
+9 -1
View File
@@ -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(), `
+2 -2
View File
@@ -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
}