diff --git a/backend/handlers/webhooks/square.go b/backend/handlers/webhooks/square.go index df67483..b7cbb43 100644 --- a/backend/handlers/webhooks/square.go +++ b/backend/handlers/webhooks/square.go @@ -20,10 +20,11 @@ type SquareWebhookEvent struct { } func HandleSquareWebhook(w http.ResponseWriter, r *http.Request) { + r.Body = http.MaxBytesReader(w, r.Body, 512*1024) body, err := io.ReadAll(r.Body) if err != nil { log.Printf("Failed to read webhook body: %v", err) - http.Error(w, "Internal server error", http.StatusInternalServerError) + http.Error(w, "request body too large or unreadable", http.StatusRequestEntityTooLarge) return } defer r.Body.Close() diff --git a/backend/main.go b/backend/main.go index e09c8d6..967daad 100644 --- a/backend/main.go +++ b/backend/main.go @@ -42,6 +42,22 @@ func init() { auth.InitJWT(jwtSecret) } +func limitBody(limit int64) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Body != nil { + r.Body = http.MaxBytesReader(w, r.Body, limit) + } + next.ServeHTTP(w, r) + }) + } +} + +const ( + defaultBodyLimit int64 = 1 * 1024 * 1024 // 1MB + uploadBodyLimit int64 = 15 * 1024 * 1024 // 15MB +) + func initDB() { if err := db.Connect(); err != nil { log.Fatal("Failed to connect to DB:", err) @@ -146,11 +162,11 @@ func main() { r.With(mw.RateLimit(10, time.Minute)).Post("/register", authHandlers.RegisterHandler) // Login: Has its own internal rate limiting - r.Post("/login", authHandlers.LoginHandler) + r.With(mw.RateLimit(10, time.Minute)).Post("/login", authHandlers.LoginHandler) // Email verification - r.Post("/verify/generate", authHandlers.GenerateVerificationCodeHandler) - r.Post("/verify/check", authHandlers.VerifyCodeHandler) + r.With(mw.RateLimit(10, time.Minute)).Post("/verify/generate", authHandlers.GenerateVerificationCodeHandler) + r.With(mw.RateLimit(20, time.Minute)).Post("/verify/check", authHandlers.VerifyCodeHandler) // Health check r.Get("/health", healthCheckHandler) @@ -169,7 +185,7 @@ func main() { r.Use(mw.RequireAuth) r.Use(mw.RequireAdmin) r.Use(mw.RateLimit(60, time.Minute)) - r.Post("/images", portfolio.UploadImage) + r.With(limitBody(uploadBodyLimit)).Post("/images", portfolio.UploadImage) r.Delete("/images/{id}", portfolio.DeleteImage) }) }) @@ -208,12 +224,12 @@ func main() { r.Group(func(r chi.Router) { r.Use(mw.RequireAuth) r.Use(mw.RateLimit(120, time.Minute)) + r.Use(limitBody(defaultBodyLimit)) r.Post("/refresh-token", authHandlers.RefreshTokenHandler) r.Get("/user/profile", user.GetProfileHandler) r.Put("/user/profile", user.UpdateProfileHandler) - r.Post("/user/profile-picture", user.UploadProfilePictureHandler) r.Put("/user/change-password", user.ChangePasswordHandler) r.Get("/user/notification-preferences", user.GetNotificationPreferencesHandler) r.Put("/user/notification-preferences", user.UpdateNotificationPreferencesHandler) @@ -239,6 +255,8 @@ func main() { r.Get("/bookings/{id}/payment-summary", payments.GetBookingPaymentSummary) }) + r.With(mw.RequireAuth, mw.RequireVerified, limitBody(uploadBodyLimit)).Post("/user/profile-picture", user.UploadProfilePictureHandler) + // Admin-only (no rate limit - trusted users with authenticated sessions) r.Group(func(r chi.Router) { r.Use(mw.RequireAuth)