Bootstrap copied BFF and local accounts with public Sendico contracts (#34)
This commit is contained in:
1 parent
67dc5b5b1e
commit
7062672a87
556 files changed
+42291
-777
No files matched your search
@@ -0,0 +1,172 @@
|
||||
package httptransport
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"git.sendico.io/tech/sxchnge/bff/edge/shared/auth/browser"
|
||||
authstorage "git.sendico.io/tech/sxchnge/bff/edge/shared/auth/storage"
|
||||
edgeapi "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api"
|
||||
"git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api/sresponse"
|
||||
wsh "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api/ws"
|
||||
"git.sendico.io/tech/sxchnge/bff/edge/shared/interface/middleware"
|
||||
httprouter "git.sendico.io/tech/sxchnge/bff/edge/shared/internal/transport/http/router"
|
||||
mr "git.sendico.io/tech/sxchnge/bff/edge/shared/internal/transport/http/router/metrics"
|
||||
ws "git.sendico.io/tech/sxchnge/bff/edge/shared/internal/transport/http/websocket"
|
||||
api "git.sendico.io/tech/sxchnge/bff/pkg/api/http"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/api/http/response"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/auth"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/db"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/merrors"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/mlogger"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/mservice"
|
||||
"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
|
||||
wshandler ws.Router
|
||||
epdispatcher *httprouter.Dispatcher
|
||||
}
|
||||
|
||||
func (mw *Middleware) Handler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.HandlerFunc) {
|
||||
mw.epdispatcher.Handler(service, endpoint, method, handler)
|
||||
}
|
||||
|
||||
func (mw *Middleware) AccountHandler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.AccountHandlerFunc) {
|
||||
mw.epdispatcher.AccountHandler(service, endpoint, method, handler)
|
||||
}
|
||||
|
||||
func (mw *Middleware) PendingAccountHandler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.PendingAccountHandlerFunc) {
|
||||
mw.epdispatcher.PendingAccountHandler(service, endpoint, method, handler)
|
||||
}
|
||||
|
||||
func (mw *Middleware) WSHandler(messageType string, handler wsh.HandlerFunc) {
|
||||
if mw.wshandler == nil {
|
||||
mw.logger.Info("WebSocket handler registration skipped because websocket router is disabled", zap.String("message_type", messageType))
|
||||
return
|
||||
}
|
||||
mw.wshandler.InstallHandler(messageType, handler)
|
||||
}
|
||||
|
||||
func (mw *Middleware) Finish() {
|
||||
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,
|
||||
db db.Factory,
|
||||
enforcer auth.Enforcer,
|
||||
apiRouter *chi.Mux,
|
||||
config *middleware.Config,
|
||||
authStorage *edgeapi.AuthStorageConfig,
|
||||
installWebSocket bool,
|
||||
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
|
||||
}
|
||||
loginRateLimits, err := config.ResolveLoginRateLimits()
|
||||
if err != nil {
|
||||
logger.Error("Invalid login rate limit configuration", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
p := &Middleware{
|
||||
logger: logger.Named("middleware"),
|
||||
router: apiRouter,
|
||||
apiEndpoint: os.Getenv(config.EndPointEnv),
|
||||
}
|
||||
p.logger.Info("Set endpoint", zap.String("endpoint", p.apiEndpoint))
|
||||
p.installMiddleware(config, debug)
|
||||
if p.metrics, err = mr.NewMetricsRouter(p.logger, p.router, p.apiEndpoint); err != nil {
|
||||
p.logger.Error("Failed to create metrics router", zap.Error(err), zap.String("api_endpoint", p.apiEndpoint))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stores, err := authstorage.OpenStores(p.logger, db, authStorage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.epdispatcher = httprouter.NewDispatcher(
|
||||
p.logger,
|
||||
p.router,
|
||||
stores.Accounts,
|
||||
stores.Verification,
|
||||
stores.RefreshTokens,
|
||||
stores.LoginAttempts,
|
||||
enforcer,
|
||||
config,
|
||||
loginRateLimits,
|
||||
)
|
||||
if installWebSocket {
|
||||
p.wshandler = ws.NewRouter(p.logger, p.router, &config.WebSocket, p.apiEndpoint)
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
Reference in new issue
Block a user