Security: add rate limiting, input validation, and filter category
validation Backend: - Add rate limiting middleware (mw/ratelimit.go) - in-memory per-IP limiter - Apply rate limits per endpoint group: - Public read-only: 120/min - Registration: 10/min - Portfolio filters: 60/min - Authenticated users: 120/min - Admin: none (trusted) - Add 256 char input length validation on portfolio endpoints - Validate filter categories exist in DB before querying - Secure GetImage endpoint: only allow UUID or numeric timestamp (15-20 digits) - Remove pattern-based image lookup to prevent enumeration - Add services validation: name (100), duration (1-480), patch test (0-168) Frontend: - Add maxlength=256 to portfolio tag/search inputs - Add maxlength to registration: name (50), email (255), phone (20), password (72) - Add maxlength=100 to service name input
This commit is contained in:
+23
-19
@@ -81,38 +81,46 @@ func main() {
|
||||
// All API routes grouped under /api for clarity
|
||||
r.Route("/api", func(r chi.Router) {
|
||||
|
||||
// --- Public Routes ---
|
||||
r.Get("/services", services.ServicesHandler)
|
||||
r.Post("/register", authHandlers.RegisterHandler)
|
||||
// Public read-only
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(mw.RateLimit(120, time.Minute))
|
||||
r.Get("/services", services.ServicesHandler)
|
||||
})
|
||||
|
||||
// Registration: 10/min to prevent spam
|
||||
r.With(mw.RateLimit(10, time.Minute)).Post("/register", authHandlers.RegisterHandler)
|
||||
|
||||
// Login: Has its own internal rate limiting
|
||||
r.Post("/login", authHandlers.LoginHandler)
|
||||
|
||||
// --- Portfolio Routes (public read, admin write) ---
|
||||
// Portfolio
|
||||
r.Route("/portfolio", func(r chi.Router) {
|
||||
r.Get("/images", portfolio.ListImages)
|
||||
r.Get("/tags", portfolio.ListTags)
|
||||
r.Get("/filters", portfolio.ListFilters)
|
||||
r.Get("/images/{id}", portfolio.GetImage)
|
||||
r.With(mw.RateLimit(60, time.Minute)).Get("/filters", portfolio.ListFilters)
|
||||
r.With(mw.RateLimit(120, time.Minute)).Get("/images/{id}", portfolio.GetImage)
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(mw.RequireAuth)
|
||||
r.Use(mw.RequireAdmin)
|
||||
r.Use(mw.RateLimit(60, time.Minute))
|
||||
r.Post("/images", portfolio.UploadImage)
|
||||
r.Delete("/images/{id}", portfolio.DeleteImage)
|
||||
})
|
||||
})
|
||||
|
||||
// --- Scheduling Routes ---
|
||||
// Scheduling
|
||||
r.Route("/scheduling", func(r chi.Router) {
|
||||
// Public GET routes
|
||||
r.Use(mw.RateLimit(120, time.Minute))
|
||||
r.Get("/default-hours", scheduling.GetDefaultHours)
|
||||
r.Get("/exceptional-groups", scheduling.ListExceptionalGroups)
|
||||
r.Get("/working-hours", scheduling.GetWorkingHours)
|
||||
r.Get("/available-hours", scheduling.GetAvailableHours)
|
||||
|
||||
// Admin-only scheduling modifications
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(mw.RequireAuth)
|
||||
r.Use(mw.RequireAdmin)
|
||||
r.Use(mw.RateLimit(60, time.Minute))
|
||||
|
||||
r.Put("/default-hours", scheduling.UpdateDefaultHours)
|
||||
r.Post("/exceptional-groups", scheduling.CreateExceptionalGroup)
|
||||
@@ -121,9 +129,10 @@ func main() {
|
||||
})
|
||||
})
|
||||
|
||||
// --- Protected routes (any authenticated user) ---
|
||||
// Authenticated users
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(mw.RequireAuth)
|
||||
r.Use(mw.RateLimit(120, time.Minute))
|
||||
|
||||
r.Post("/refresh-token", authHandlers.RefreshTokenHandler)
|
||||
|
||||
@@ -132,17 +141,16 @@ func main() {
|
||||
r.Delete("/user/account", user.DeleteAccountHandler)
|
||||
r.Get("/user/loyalty", user.GetLoyaltyHandler)
|
||||
|
||||
// Booking routes for authenticated users
|
||||
r.Route("/bookings", func(r chi.Router) {
|
||||
r.Get("/", bookings.GetAllUserBookingsHandler)
|
||||
r.Post("/", bookings.CreateBookingHandler)
|
||||
r.Get("/", bookings.GetAllUserBookingsHandler)
|
||||
r.Get("/{id}", bookings.GetBookingHandler)
|
||||
r.Put("/{id}", bookings.EditBookingHandler)
|
||||
r.Delete("/{id}", bookings.DeleteBookingHandler)
|
||||
})
|
||||
})
|
||||
|
||||
// --- Admin-only routes ---
|
||||
// Admin-only (no rate limit - trusted users with authenticated sessions)
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(mw.RequireAuth)
|
||||
r.Use(mw.RequireAdmin)
|
||||
@@ -157,11 +165,11 @@ func main() {
|
||||
r.Route("/admin/bookings", func(r chi.Router) {
|
||||
r.Get("/", bookings.GetAllAdminBookingsHandler)
|
||||
r.Post("/", bookings.AdminCreateBookingForUserHandler)
|
||||
r.Get("/search", bookings.SearchAdminBookingsHandler)
|
||||
r.With(mw.RateLimit(60, time.Minute)).Get("/search", bookings.SearchAdminBookingsHandler)
|
||||
r.Get("/user/{user_id}", bookings.GetAllBookingsByUserHandler)
|
||||
r.Get("/{id}", bookings.GetAdminBookingHandler)
|
||||
r.Put("/{id}/progress", bookings.ProgressBookingHandler)
|
||||
r.Post("/{id}/confirm", bookings.ConfirmBookingHandler) // HERE
|
||||
r.Post("/{id}/confirm", bookings.ConfirmBookingHandler)
|
||||
r.Post("/{id}/cancel", bookings.ConfirmBookingHandler)
|
||||
})
|
||||
|
||||
@@ -176,12 +184,8 @@ func main() {
|
||||
r.Get("/pending-approvals", today.GetPendingApprovalsHandler)
|
||||
})
|
||||
|
||||
// --- Admin Notifications ---
|
||||
r.Route("/admin/notifications", func(r chi.Router) {
|
||||
// List all unacknowledged notifications
|
||||
r.Get("/", notifications.GetNotifications)
|
||||
|
||||
// Acknowledge a single notification
|
||||
r.Post("/{id}/acknowledge", notifications.AcknowledgeNotification)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user