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:
2026-08-22 00:34:51 +01:00
co-authored by Sisyphus
parent 348a2e5bfc
commit 37f6723d9e
3 changed files with 15 additions and 12 deletions
+12 -9
View File
@@ -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)