package httptransport import ( "net/http" "path" mr "git.sendico.io/tech/sxchnge/product/app/metrics" "git.sendico.io/tech/sxchnge/product/auth/config" "git.sendico.io/tech/sxchnge/product/sessions/browser" "git.sendico.io/tech/sxchnge/shared/errors" "git.sendico.io/tech/sxchnge/shared/http/response" "git.sendico.io/tech/sxchnge/shared/logging" "git.sendico.io/tech/sxchnge/shared/service" "github.com/go-chi/chi/v5" cm "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/cors" "github.com/go-chi/metrics" "go.uber.org/zap" "moul.io/chizap" ) type Middleware struct { logger mlogger.Logger router *chi.Mux apiEndpoint string metrics mr.Metrics } func (mw *Middleware) Finish() { if mw.metrics != nil { mw.metrics.Finish() } } func (mw *Middleware) installMiddleware(config *middleware.Config, debug bool) { mw.logger.Debug("Installing middleware stack...") // Collect metrics for application traffic only. Scrapes and health probes are // operational traffic and would otherwise distort request and in-flight SLIs. mw.router.Use(metrics.Collector(metrics.CollectorOpts{ Host: false, // avoid high-cardinality "host" label Proto: true, // include HTTP protocol label Skip: func(r *http.Request) bool { return isObservabilityRequest(mw.apiEndpoint, r.URL.Path) }, })) mw.router.Use(cm.RequestID) if debug { mw.router.Use(chizap.New(mw.logger.Named("http_trace"), &chizap.Opts{ WithReferer: true, WithUserAgent: true, })) } mw.router.Use(cors.Handler(cors.Options{ AllowedOrigins: config.CORS.AllowedOrigins, AllowedMethods: config.CORS.AllowedMethods, AllowedHeaders: config.CORS.AllowedHeaders, ExposedHeaders: config.CORS.ExposedHeaders, AllowCredentials: config.CORS.AllowCredentials, MaxAge: config.CORS.MaxAge, OptionsPassthrough: false, Debug: debug, })) mw.router.Use(func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if err := browser.ValidateCSRF(r, config.CORS.AllowedOrigins); err != nil { mw.logger.Info("Rejected browser request with invalid CSRF context", zap.String("method", r.Method), zap.String("path", r.URL.Path)) response.Forbidden(mw.logger, mservice.Accounts, "csrf_rejected", "browser request failed CSRF validation").ServeHTTP(w, r) return } next.ServeHTTP(w, r) }) }) mw.router.Use(cm.Recoverer) mw.router.Handle("/metrics", metrics.Handler()) mw.logger.Info("Middleware stack installation complete") } func isObservabilityRequest(apiEndpoint, requestPath string) bool { healthPath := path.Join("/", apiEndpoint, "health") metricsPath := path.Join("/", apiEndpoint, "metrics") return requestPath == healthPath || requestPath == metricsPath || requestPath == "/metrics" } func CreateMiddleware(logger mlogger.Logger, apiRouter *chi.Mux, config *middleware.Config, endpoint string, debug bool) (*Middleware, error) { if config == nil { return nil, merrors.InvalidArgument("middleware configuration is required", "middleware") } if err := config.Token.Validate(); err != nil { return nil, err } if err := config.CORS.Validate(); err != nil { return nil, err } p := &Middleware{logger: logger.Named("middleware"), router: apiRouter, apiEndpoint: endpoint} p.installMiddleware(config, debug) var err error p.metrics, err = mr.NewMetricsRouter(p.logger, p.router, p.apiEndpoint) if err != nil { return nil, err } return p, nil }