104 lines
3.4 KiB
Go
104 lines
3.4 KiB
Go
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
|
|
}
|