check / check (push) Failing after 0s
Add a LimitBody middleware that caps the request body at MaxFormBytes (1 MiB) on POST / and POST /generate and rejects an oversized body with 413. It parses the form under the cap before the CSRF middleware, which reads its token from the body with PostFormValue and would otherwise see a truncated body as a missing token (403); a successful parse is cached, so the CSRF check and handler reuse it. Wired ahead of CSRF in SetupRoutes. This makes the limit explicit rather than resting on ParseForm's incidental 10 MB cap, which would silently vanish if a handler switched to io.ReadAll or multipart. Model: opus-4-8
80 lines
2.2 KiB
Go
80 lines
2.2 KiB
Go
package server
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
sentryhttp "github.com/getsentry/sentry-go/http"
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/go-chi/chi/v5/middleware"
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
|
|
"sneak.berlin/go/pixa/internal/handlers"
|
|
"sneak.berlin/go/pixa/internal/static"
|
|
)
|
|
|
|
// SetupRoutes configures all HTTP routes.
|
|
func (s *Server) SetupRoutes() {
|
|
s.router = chi.NewRouter()
|
|
|
|
s.router.Use(middleware.Recoverer)
|
|
s.router.Use(middleware.RequestID)
|
|
s.router.Use(s.mw.SecurityHeaders())
|
|
s.router.Use(s.mw.Logging())
|
|
|
|
// Add metrics middleware only if credentials are configured
|
|
if s.config.MetricsUsername != "" {
|
|
s.router.Use(s.mw.Metrics())
|
|
}
|
|
|
|
s.router.Use(s.mw.CORS())
|
|
s.router.Use(middleware.Timeout(HTTPWriteTimeout))
|
|
|
|
if s.sentryEnabled {
|
|
sentryHandler := sentryhttp.New(sentryhttp.Options{
|
|
Repanic: true,
|
|
})
|
|
s.router.Use(sentryHandler.Handle)
|
|
}
|
|
|
|
// Health check endpoint
|
|
s.router.Get("/.well-known/healthcheck.json", s.h.HandleHealthCheck())
|
|
|
|
// Robots.txt
|
|
s.router.Get("/robots.txt", s.h.HandleRobotsTxt())
|
|
|
|
// Static files (Tailwind CSS, etc.)
|
|
s.router.Handle("/static/*", http.StripPrefix("/static/", static.Handler()))
|
|
|
|
// Login/generator UI. The form routes carry CSRF protection; the
|
|
// token cookie is independent of the session cookie, so it also
|
|
// covers the login POST, where no session exists yet. LimitBody caps
|
|
// the POST body ahead of CSRF, which reads its token from that body.
|
|
s.router.Group(func(r chi.Router) {
|
|
r.Use(s.h.LimitBody(handlers.MaxFormBytes))
|
|
r.Use(s.h.CSRF())
|
|
r.Get("/", s.h.HandleRoot())
|
|
r.Post("/", s.h.HandleRoot())
|
|
r.Post("/generate", s.h.HandleGenerateURL())
|
|
})
|
|
|
|
s.router.Get("/logout", s.h.HandleLogout())
|
|
|
|
// Main image proxy route
|
|
// /v1/image/<host>/<path>/<width>x<height>.<format>
|
|
s.router.Get("/v1/image/*", s.h.HandleImage())
|
|
s.router.Head("/v1/image/*", s.h.HandleImage())
|
|
|
|
// Encrypted image URL route
|
|
// The trailing filename (e.g., /img.jpg) is ignored but helps
|
|
// browsers with content type
|
|
s.router.Get("/v1/e/{token}/*", s.h.HandleImageEnc())
|
|
|
|
// Metrics endpoint with auth
|
|
if s.config.MetricsUsername != "" {
|
|
s.router.Group(func(r chi.Router) {
|
|
r.Use(s.mw.MetricsAuth())
|
|
r.Get("/metrics", http.HandlerFunc(promhttp.Handler().ServeHTTP))
|
|
})
|
|
}
|
|
}
|