fix: security — CORS preflight origin gating, IDOR payment-existence check scoped to user, per-IP anonymous reservation cap
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -2955,7 +2955,7 @@ func DeleteBookingHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var paymentExists bool
|
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)
|
log.Printf("Failed to check booking %s for user %s: %v", bookingID, userID, err)
|
||||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -267,8 +267,8 @@ func ReserveSlotHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
var anonCount int
|
var anonCount int
|
||||||
if err := tx.QueryRow(r.Context(), `
|
if err := tx.QueryRow(r.Context(), `
|
||||||
SELECT COUNT(*) FROM time_blockers
|
SELECT COUNT(*) FROM time_blockers
|
||||||
WHERE description LIKE 'RESERVATION:anon:%' AND created_at > $1
|
WHERE description LIKE 'RESERVATION:anon:' || $1 || ':%' AND created_at > $2
|
||||||
`, tenMinutesAgo).Scan(&anonCount); err != nil {
|
`, ipHash, tenMinutesAgo).Scan(&anonCount); err != nil {
|
||||||
log.Printf("Failed to check anon cap: %v", err)
|
log.Printf("Failed to check anon cap: %v", err)
|
||||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
|||||||
+12
-9
@@ -472,11 +472,14 @@ func corsMiddleware(next http.Handler) http.Handler {
|
|||||||
if origin != "" && originAllowed(origin, allowedOrigins) {
|
if origin != "" && originAllowed(origin, allowedOrigins) {
|
||||||
w.Header().Set("Access-Control-Allow-Origin", origin)
|
w.Header().Set("Access-Control-Allow-Origin", origin)
|
||||||
w.Header().Set("Vary", "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 r.Method == http.MethodOptions {
|
||||||
|
if origin == "" || !originAllowed(origin, allowedOrigins) {
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
w.WriteHeader(http.StatusNoContent)
|
w.WriteHeader(http.StatusNoContent)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -857,7 +860,7 @@ func main() {
|
|||||||
r.Post("/{id}/patch-tests", user.AddPatchTestHandler)
|
r.Post("/{id}/patch-tests", user.AddPatchTestHandler)
|
||||||
r.Get("/{id}/giftcard-balance", payments.GetUserGiftCardBalanceAdmin)
|
r.Get("/{id}/giftcard-balance", payments.GetUserGiftCardBalanceAdmin)
|
||||||
r.Get("/{id}/payment-methods", payments.AdminGetUserPaymentMethods)
|
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
|
// Admin-scoped 2FA mint: the operator requests a code FOR the
|
||||||
// customer whose saved card is being charged at the till/admin
|
// customer whose saved card is being charged at the till/admin
|
||||||
// payment modal. Mints keyed to the CUSTOMER so the code is
|
// payment modal. Mints keyed to the CUSTOMER so the code is
|
||||||
@@ -893,15 +896,15 @@ func main() {
|
|||||||
|
|
||||||
// Admin payment routes
|
// Admin payment routes
|
||||||
r.Post("/admin/bookings/{id}/payment", payments.CreateTerminalPayment)
|
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.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
|
// Admin gift card routes
|
||||||
r.Get("/admin/gift-cards", payments.GetGiftCards)
|
r.Get("/admin/gift-cards", payments.GetGiftCards)
|
||||||
r.Post("/admin/gift-cards", payments.CreateGiftCard)
|
r.With(mw.RateLimitByUser(10, time.Minute)).Post("/admin/gift-cards", payments.CreateGiftCard)
|
||||||
r.Put("/admin/gift-cards/{id}/topup", payments.TopUpGiftCard)
|
r.With(mw.RateLimitByUser(10, time.Minute)).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/{from}/transfer", payments.TransferGiftCard)
|
||||||
r.Post("/admin/gift-cards/cancel", payments.AdminCancelGiftCard)
|
r.Post("/admin/gift-cards/cancel", payments.AdminCancelGiftCard)
|
||||||
r.Get("/admin/gift-cards/expired-balances", payments.GetExpiredBalances)
|
r.Get("/admin/gift-cards/expired-balances", payments.GetExpiredBalances)
|
||||||
r.Post("/admin/gift-cards/expired-balances/claim", payments.ClaimExpiredBalance)
|
r.Post("/admin/gift-cards/expired-balances/claim", payments.ClaimExpiredBalance)
|
||||||
|
|||||||
Reference in New Issue
Block a user