diff --git a/backend/handlers/bookings/bookings.go b/backend/handlers/bookings/bookings.go index e06511d..921a31f 100644 --- a/backend/handlers/bookings/bookings.go +++ b/backend/handlers/bookings/bookings.go @@ -2955,7 +2955,7 @@ func DeleteBookingHandler(w http.ResponseWriter, r *http.Request) { } var paymentExists bool - if err := db.Conn.QueryRow(r.Context(), "SELECT EXISTS(SELECT 1 FROM payments WHERE booking_id = $1)", bookingID).Scan(&paymentExists); err != nil { + if err := db.Conn.QueryRow(r.Context(), "SELECT EXISTS(SELECT 1 FROM payments WHERE booking_id = $1 AND EXISTS (SELECT 1 FROM bookings WHERE id = $2 AND user_id = $3))", bookingID, bookingID, userID).Scan(&paymentExists); err != nil { log.Printf("Failed to check booking %s for user %s: %v", bookingID, userID, err) http.Error(w, "Internal server error", http.StatusInternalServerError) return diff --git a/backend/handlers/bookings/reserve.go b/backend/handlers/bookings/reserve.go index 841e478..5853eb5 100644 --- a/backend/handlers/bookings/reserve.go +++ b/backend/handlers/bookings/reserve.go @@ -267,8 +267,8 @@ func ReserveSlotHandler(w http.ResponseWriter, r *http.Request) { var anonCount int if err := tx.QueryRow(r.Context(), ` SELECT COUNT(*) FROM time_blockers - WHERE description LIKE 'RESERVATION:anon:%' AND created_at > $1 - `, tenMinutesAgo).Scan(&anonCount); err != nil { + WHERE description LIKE 'RESERVATION:anon:' || $1 || ':%' AND created_at > $2 + `, ipHash, tenMinutesAgo).Scan(&anonCount); err != nil { log.Printf("Failed to check anon cap: %v", err) http.Error(w, "Internal server error", http.StatusInternalServerError) return diff --git a/backend/main.go b/backend/main.go index da3a97d..9f8ecaa 100644 --- a/backend/main.go +++ b/backend/main.go @@ -472,11 +472,14 @@ func corsMiddleware(next http.Handler) http.Handler { if origin != "" && originAllowed(origin, allowedOrigins) { w.Header().Set("Access-Control-Allow-Origin", origin) w.Header().Set("Vary", "Origin") + w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, Idempotency-Key") } - w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS") - w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, Idempotency-Key") - if r.Method == http.MethodOptions { + if origin == "" || !originAllowed(origin, allowedOrigins) { + w.WriteHeader(http.StatusForbidden) + return + } w.WriteHeader(http.StatusNoContent) return } @@ -857,7 +860,7 @@ func main() { r.Post("/{id}/patch-tests", user.AddPatchTestHandler) r.Get("/{id}/giftcard-balance", payments.GetUserGiftCardBalanceAdmin) r.Get("/{id}/payment-methods", payments.AdminGetUserPaymentMethods) - r.Post("/{id}/2fa/remove", user.AdminRemoveUser2FAHandler) + r.With(mw.RateLimitByUser(10, time.Minute)).Post("/{id}/2fa/remove", user.AdminRemoveUser2FAHandler) // Admin-scoped 2FA mint: the operator requests a code FOR the // customer whose saved card is being charged at the till/admin // payment modal. Mints keyed to the CUSTOMER so the code is @@ -893,15 +896,15 @@ func main() { // Admin payment routes r.Post("/admin/bookings/{id}/payment", payments.CreateTerminalPayment) - r.Post("/admin/bookings/{id}/refund", payments.AdminRefundBooking) + r.With(mw.RateLimitByUser(10, time.Minute)).Post("/admin/bookings/{id}/refund", payments.AdminRefundBooking) r.Get("/admin/payments/{checkout_id}/status", payments.GetCheckoutStatus) - r.Post("/admin/payments/{payment_id}/refund", payments.RefundPayment) + r.With(mw.RateLimitByUser(10, time.Minute)).Post("/admin/payments/{payment_id}/refund", payments.RefundPayment) // Admin gift card routes r.Get("/admin/gift-cards", payments.GetGiftCards) - r.Post("/admin/gift-cards", payments.CreateGiftCard) - r.Put("/admin/gift-cards/{id}/topup", payments.TopUpGiftCard) - r.Post("/admin/gift-cards/{from}/transfer", payments.TransferGiftCard) + r.With(mw.RateLimitByUser(10, time.Minute)).Post("/admin/gift-cards", payments.CreateGiftCard) + r.With(mw.RateLimitByUser(10, time.Minute)).Put("/admin/gift-cards/{id}/topup", payments.TopUpGiftCard) + r.With(mw.RateLimitByUser(10, time.Minute)).Post("/admin/gift-cards/{from}/transfer", payments.TransferGiftCard) r.Post("/admin/gift-cards/cancel", payments.AdminCancelGiftCard) r.Get("/admin/gift-cards/expired-balances", payments.GetExpiredBalances) r.Post("/admin/gift-cards/expired-balances/claim", payments.ClaimExpiredBalance)