diff --git a/backend/cmd/http/main.go b/backend/cmd/http/main.go index 62ccf4a37..f0e59b8a6 100644 --- a/backend/cmd/http/main.go +++ b/backend/cmd/http/main.go @@ -45,7 +45,7 @@ func main() { log.Fatal(ctx, "failed while creating services: %s", err) } - router, err := api.NewRouter(&cfg.HTTP, log) + router, err := api.NewRouter(&cfg.HTTP, log, nil, nil, nil, nil) if err != nil { log.Fatal(ctx, "failed while creating router: %s", err) } diff --git a/backend/cmd/integrations/main.go b/backend/cmd/integrations/main.go index 8c375fbd5..f0a6b8d32 100644 --- a/backend/cmd/integrations/main.go +++ b/backend/cmd/integrations/main.go @@ -34,12 +34,11 @@ func main() { log.Fatal(ctx, "can't init services: %s", err) } - router, err := api.NewRouter(&cfg.HTTP, log) + router, err := api.NewRouter(&cfg.HTTP, log, builder.RateLimiter, builder.Auth, nil, builder.AuditTrail) if err != nil { log.Fatal(ctx, "failed while creating router: %s", err) } router.AddHandlers(api.NoPrefix, builder.IntegrationsAPI) - router.AddMiddlewares(builder.Auth.Middleware, builder.RateLimiter.Middleware, builder.AuditTrail.Middleware) server.Run(ctx, log, &cfg.HTTP, router) } diff --git a/backend/cmd/spot/main.go b/backend/cmd/spot/main.go index 822b60d34..ec5e1f121 100644 --- a/backend/cmd/spot/main.go +++ b/backend/cmd/spot/main.go @@ -37,12 +37,11 @@ func main() { log.Fatal(ctx, "can't init services: %s", err) } - router, err := api.NewRouter(&cfg.HTTP, log) + router, err := api.NewRouter(&cfg.HTTP, log, builder.RateLimiter, builder.Authenticator, builder.Permissions, builder.AuditTrail) if err != nil { log.Fatal(ctx, "failed while creating router: %s", err) } router.AddHandlers(prefix, builder.SpotsAPI) - router.AddMiddlewares(builder.Auth.Middleware, builder.RateLimiter.Middleware, builder.AuditTrail.Middleware) server.Run(ctx, log, &cfg.HTTP, router) } diff --git a/backend/pkg/analytics/builder.go b/backend/pkg/analytics/builder.go index 68098dc01..ef1f180c7 100644 --- a/backend/pkg/analytics/builder.go +++ b/backend/pkg/analytics/builder.go @@ -1,27 +1,28 @@ package analytics import ( - "github.com/go-playground/validator/v10" - "openreplay/backend/pkg/analytics/charts" - "openreplay/backend/pkg/metrics/database" "time" + "github.com/go-playground/validator/v10" + "openreplay/backend/internal/config/analytics" "openreplay/backend/pkg/analytics/cards" + "openreplay/backend/pkg/analytics/charts" "openreplay/backend/pkg/analytics/dashboards" "openreplay/backend/pkg/db/postgres/pool" "openreplay/backend/pkg/logger" + "openreplay/backend/pkg/metrics/database" "openreplay/backend/pkg/metrics/web" "openreplay/backend/pkg/server/api" - "openreplay/backend/pkg/server/auth" + "openreplay/backend/pkg/server/auth/jwt" "openreplay/backend/pkg/server/limiter" "openreplay/backend/pkg/server/tracer" ) type ServicesBuilder struct { - Auth auth.Auth - RateLimiter *limiter.UserRateLimiter - AuditTrail tracer.Tracer + Auth api.RouterMiddleware + RateLimiter api.RouterMiddleware + AuditTrail api.RouterMiddleware CardsAPI api.Handlers DashboardsAPI api.Handlers ChartsAPI api.Handlers @@ -59,7 +60,7 @@ func NewServiceBuilder(log logger.Logger, cfg *analytics.Config, webMetrics web. return nil, err } return &ServicesBuilder{ - Auth: auth.NewAuth(log, cfg.JWTSecret, cfg.JWTSpotSecret, pgconn, nil, api.NoPrefix), + Auth: jwt.NewAuth(log, cfg.JWTSecret, pgconn), RateLimiter: limiter.NewUserRateLimiter(10, 30, 1*time.Minute, 5*time.Minute), AuditTrail: audiTrail, CardsAPI: cardsHandlers, diff --git a/backend/pkg/analytics/cards/handlers.go b/backend/pkg/analytics/cards/handlers.go index f0cf16d02..4217bacd3 100644 --- a/backend/pkg/analytics/cards/handlers.go +++ b/backend/pkg/analytics/cards/handlers.go @@ -41,11 +41,11 @@ type handlersImpl struct { func (e *handlersImpl) GetAll() []*api.Description { return []*api.Description{ - {"/v1/analytics/{projectId}/cards", e.createCard, "POST"}, - {"/v1/analytics/{projectId}/cards", e.getCardsPaginated, "GET"}, - {"/v1/analytics/{projectId}/cards/{id}", e.getCard, "GET"}, - {"/v1/analytics/{projectId}/cards/{id}", e.updateCard, "PUT"}, - {"/v1/analytics/{projectId}/cards/{id}", e.deleteCard, "DELETE"}, + {"/v1/analytics/{projectId}/cards", "POST", e.createCard, api.NoPermissions, api.DoNotTrack}, + {"/v1/analytics/{projectId}/cards", "GET", e.getCardsPaginated, api.NoPermissions, api.DoNotTrack}, + {"/v1/analytics/{projectId}/cards/{id}", "GET", e.getCard, api.NoPermissions, api.DoNotTrack}, + {"/v1/analytics/{projectId}/cards/{id}", "PUT", e.updateCard, api.NoPermissions, api.DoNotTrack}, + {"/v1/analytics/{projectId}/cards/{id}", "DELETE", e.deleteCard, api.NoPermissions, api.DoNotTrack}, } } diff --git a/backend/pkg/analytics/charts/handlers.go b/backend/pkg/analytics/charts/handlers.go index 771732b43..f62374cb0 100644 --- a/backend/pkg/analytics/charts/handlers.go +++ b/backend/pkg/analytics/charts/handlers.go @@ -41,8 +41,8 @@ type handlersImpl struct { func (e *handlersImpl) GetAll() []*api.Description { return []*api.Description{ - {"/v1/analytics/{projectId}/cards/{id}/chart", e.getCardChartData, "POST"}, - {"/v1/analytics/{projectId}/cards/{id}/try", e.getCardChartData, "POST"}, + {"/v1/analytics/{projectId}/cards/{id}/chart", "POST", e.getCardChartData, api.NoPermissions, api.DoNotTrack}, + {"/v1/analytics/{projectId}/cards/{id}/try", "POST", e.getCardChartData, api.NoPermissions, api.DoNotTrack}, } } diff --git a/backend/pkg/analytics/dashboards/handlers.go b/backend/pkg/analytics/dashboards/handlers.go index d81c75159..4cb86b6dc 100644 --- a/backend/pkg/analytics/dashboards/handlers.go +++ b/backend/pkg/analytics/dashboards/handlers.go @@ -41,13 +41,13 @@ type handlersImpl struct { func (e *handlersImpl) GetAll() []*api.Description { return []*api.Description{ - {"/v1/analytics/{projectId}/dashboards", e.createDashboard, "POST"}, - {"/v1/analytics/{projectId}/dashboards", e.getDashboards, "GET"}, - {"/v1/analytics/{projectId}/dashboards/{id}", e.getDashboard, "GET"}, - {"/v1/analytics/{projectId}/dashboards/{id}", e.updateDashboard, "PUT"}, - {"/v1/analytics/{projectId}/dashboards/{id}", e.deleteDashboard, "DELETE"}, - {"/v1/analytics/{projectId}/dashboards/{id}/cards", e.addCardToDashboard, "POST"}, - {"/v1/analytics/{projectId}/dashboards/{id}/cards/{cardId}", e.removeCardFromDashboard, "DELETE"}, + {"/v1/analytics/{projectId}/dashboards", "POST", e.createDashboard, api.NoPermissions, api.DoNotTrack}, + {"/v1/analytics/{projectId}/dashboards", "GET", e.getDashboards, api.NoPermissions, api.DoNotTrack}, + {"/v1/analytics/{projectId}/dashboards/{id}", "GET", e.getDashboard, api.NoPermissions, api.DoNotTrack}, + {"/v1/analytics/{projectId}/dashboards/{id}", "PUT", e.updateDashboard, api.NoPermissions, api.DoNotTrack}, + {"/v1/analytics/{projectId}/dashboards/{id}", "DELETE", e.deleteDashboard, api.NoPermissions, api.DoNotTrack}, + {"/v1/analytics/{projectId}/dashboards/{id}/cards", "POST", e.addCardToDashboard, api.NoPermissions, api.DoNotTrack}, + {"/v1/analytics/{projectId}/dashboards/{id}/cards/{cardId}", "DELETE", e.removeCardFromDashboard, api.NoPermissions, api.DoNotTrack}, } } diff --git a/backend/pkg/conditions/api/handlers.go b/backend/pkg/conditions/api/handlers.go index 13179eb57..a166e9ce7 100644 --- a/backend/pkg/conditions/api/handlers.go +++ b/backend/pkg/conditions/api/handlers.go @@ -24,8 +24,8 @@ func NewHandlers(log logger.Logger, responser *api.Responser, tokenizer *token.T func (e *handlersImpl) GetAll() []*api.Description { return []*api.Description{ - {"/v1/web/conditions/{project}", e.getConditions, "GET"}, - {"/v1/mobile/conditions/{project}", e.getConditions, "GET"}, + {"GET", "/v1/web/conditions/{project}", e.getConditions, api.NoPermissions, api.DoNotTrack}, + {"GET", "/v1/mobile/conditions/{project}", e.getConditions, api.NoPermissions, api.DoNotTrack}, } } diff --git a/backend/pkg/featureflags/api/handlers.go b/backend/pkg/featureflags/api/handlers.go index ded3a180a..5330fc435 100644 --- a/backend/pkg/featureflags/api/handlers.go +++ b/backend/pkg/featureflags/api/handlers.go @@ -38,7 +38,7 @@ func NewHandlers(log logger.Logger, responser *api.Responser, jsonSizeLimit int6 func (e *handlersImpl) GetAll() []*api.Description { return []*api.Description{ - {"/v1/web/feature-flags", e.featureFlagsHandlerWeb, "POST"}, + {"POST", "/v1/web/feature-flags", e.featureFlagsHandlerWeb, api.NoPermissions, api.DoNotTrack}, } } diff --git a/backend/pkg/integrations/api/handlers.go b/backend/pkg/integrations/api/handlers.go index 24e3312a4..7a62144cc 100644 --- a/backend/pkg/integrations/api/handlers.go +++ b/backend/pkg/integrations/api/handlers.go @@ -34,11 +34,11 @@ func NewHandlers(log logger.Logger, cfg *integrationsCfg.Config, responser *api. func (e *handlersImpl) GetAll() []*api.Description { return []*api.Description{ - {"/v1/integrations/{name}/{project}", e.createIntegration, "POST"}, - {"/v1/integrations/{name}/{project}", e.getIntegration, "GET"}, - {"/v1/integrations/{name}/{project}", e.updateIntegration, "PATCH"}, - {"/v1/integrations/{name}/{project}", e.deleteIntegration, "DELETE"}, - {"/v1/integrations/{name}/{project}/data/{session}", e.getIntegrationData, "GET"}, + {"POST", "/v1/integrations/{name}/{project}", e.createIntegration, api.NoPermissions, api.DoNotTrack}, + {"GET", "/v1/integrations/{name}/{project}", e.getIntegration, api.NoPermissions, api.DoNotTrack}, + {"PATCH", "/v1/integrations/{name}/{project}", e.updateIntegration, api.NoPermissions, api.DoNotTrack}, + {"DELETE", "/v1/integrations/{name}/{project}", e.deleteIntegration, api.NoPermissions, api.DoNotTrack}, + {"GET", "/v1/integrations/{name}/{project}/data/{session}", e.getIntegrationData, api.NoPermissions, api.DoNotTrack}, } } diff --git a/backend/pkg/integrations/builder.go b/backend/pkg/integrations/builder.go index c4718ae9e..a47b22698 100644 --- a/backend/pkg/integrations/builder.go +++ b/backend/pkg/integrations/builder.go @@ -1,26 +1,26 @@ package integrations import ( - "openreplay/backend/pkg/integrations/service" - "openreplay/backend/pkg/metrics/database" - "openreplay/backend/pkg/metrics/web" - "openreplay/backend/pkg/server/tracer" "time" "openreplay/backend/internal/config/integrations" "openreplay/backend/pkg/db/postgres/pool" integrationsAPI "openreplay/backend/pkg/integrations/api" + "openreplay/backend/pkg/integrations/service" "openreplay/backend/pkg/logger" + "openreplay/backend/pkg/metrics/database" + "openreplay/backend/pkg/metrics/web" "openreplay/backend/pkg/objectstorage/store" "openreplay/backend/pkg/server/api" - "openreplay/backend/pkg/server/auth" + "openreplay/backend/pkg/server/auth/jwt" "openreplay/backend/pkg/server/limiter" + "openreplay/backend/pkg/server/tracer" ) type ServiceBuilder struct { - Auth auth.Auth - RateLimiter *limiter.UserRateLimiter - AuditTrail tracer.Tracer + Auth api.RouterMiddleware + RateLimiter api.RouterMiddleware + AuditTrail api.RouterMiddleware IntegrationsAPI api.Handlers } @@ -43,7 +43,7 @@ func NewServiceBuilder(log logger.Logger, cfg *integrations.Config, webMetrics w return nil, err } builder := &ServiceBuilder{ - Auth: auth.NewAuth(log, cfg.JWTSecret, "", pgconn, nil, api.NoPrefix), + Auth: jwt.NewAuth(log, cfg.JWTSecret, pgconn), RateLimiter: limiter.NewUserRateLimiter(10, 30, 1*time.Minute, 5*time.Minute), AuditTrail: auditrail, IntegrationsAPI: handlers, diff --git a/backend/pkg/server/api/body-reader.go b/backend/pkg/server/api/body-reader.go index 1cfcc92d4..46919dd11 100644 --- a/backend/pkg/server/api/body-reader.go +++ b/backend/pkg/server/api/body-reader.go @@ -13,8 +13,6 @@ import ( func ReadBody(log logger.Logger, w http.ResponseWriter, r *http.Request, limit int64) ([]byte, error) { body := http.MaxBytesReader(w, r.Body, limit) bodyBytes, err := io.ReadAll(body) - - // Close body if closeErr := body.Close(); closeErr != nil { log.Warn(r.Context(), "error while closing request body: %s", closeErr) } @@ -47,8 +45,6 @@ func ReadCompressedBody(log logger.Logger, w http.ResponseWriter, r *http.Reques } else { bodyBytes, err = io.ReadAll(body) } - - // Close body if closeErr := body.Close(); closeErr != nil { log.Warn(r.Context(), "error while closing request body: %s", closeErr) } diff --git a/backend/pkg/server/api/handlers.go b/backend/pkg/server/api/handlers.go index c7e6f0811..5ca8eea26 100644 --- a/backend/pkg/server/api/handlers.go +++ b/backend/pkg/server/api/handlers.go @@ -3,11 +3,16 @@ package api import "net/http" type Description struct { - Path string - Handler http.HandlerFunc - Method string + Path string + Method string + Handler http.HandlerFunc + Permissions []string + TrackName string } type Handlers interface { GetAll() []*Description } + +var NoPermissions []string +var DoNotTrack string diff --git a/backend/pkg/server/api/middleware.go b/backend/pkg/server/api/middleware.go index 423e7e0d9..0b8e433d2 100644 --- a/backend/pkg/server/api/middleware.go +++ b/backend/pkg/server/api/middleware.go @@ -7,6 +7,22 @@ import ( "openreplay/backend/internal/http/util" ) +type RouterMiddleware interface { + Middleware(next http.Handler) http.Handler +} + +type defaultMiddleware struct{} + +func NewDefaultMiddleware() RouterMiddleware { + return &defaultMiddleware{} +} + +func (d *defaultMiddleware) Middleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + next.ServeHTTP(w, r) + }) +} + func (e *routerImpl) health(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } diff --git a/backend/pkg/server/api/router.go b/backend/pkg/server/api/router.go index 13f615e5e..baa7846c1 100644 --- a/backend/pkg/server/api/router.go +++ b/backend/pkg/server/api/router.go @@ -12,37 +12,51 @@ import ( type Router interface { AddHandlers(prefix string, handlers ...Handlers) - AddMiddlewares(middlewares ...func(http.Handler) http.Handler) Get() http.Handler } -type routerImpl struct { - log logger.Logger - cfg *common.HTTP - router *mux.Router +type endpoint struct { + Permissions []string + TrackName string } -func NewRouter(cfg *common.HTTP, log logger.Logger) (Router, error) { +type routerImpl struct { + log logger.Logger + cfg *common.HTTP + router *mux.Router + endpoints map[string]*endpoint // map[method+path]endpoint +} + +func NewRouter(cfg *common.HTTP, log logger.Logger, rateLimiter, authenticator, permissions, tracer RouterMiddleware) (Router, error) { switch { case cfg == nil: return nil, fmt.Errorf("config is empty") case log == nil: return nil, fmt.Errorf("logger is empty") + case rateLimiter == nil: + rateLimiter = NewDefaultMiddleware() + case authenticator == nil: + authenticator = NewDefaultMiddleware() + case permissions == nil: + permissions = NewDefaultMiddleware() + case tracer == nil: + tracer = NewDefaultMiddleware() } e := &routerImpl{ - log: log, - cfg: cfg, - router: mux.NewRouter(), + log: log, + cfg: cfg, + router: mux.NewRouter(), + endpoints: make(map[string]*endpoint), } - e.initRouter() - return e, nil -} - -func (e *routerImpl) initRouter() { e.router.HandleFunc("/", e.health) - // Default middlewares + // Add all middlewares e.router.Use(e.healthMiddleware) e.router.Use(e.corsMiddleware) + e.router.Use(rateLimiter.Middleware) + e.router.Use(authenticator.Middleware) + e.router.Use(permissions.Middleware) + e.router.Use(tracer.Middleware) + return e, nil } const NoPrefix = "" @@ -54,16 +68,14 @@ func (e *routerImpl) AddHandlers(prefix string, handlers ...Handlers) { if prefix != NoPrefix { e.router.HandleFunc(prefix+handler.Path, handler.Handler).Methods(handler.Method, "OPTIONS") } + e.endpoints[handler.Method+handler.Path] = &endpoint{ + Permissions: handler.Permissions, + TrackName: handler.TrackName, + } } } } -func (e *routerImpl) AddMiddlewares(middlewares ...func(http.Handler) http.Handler) { - for _, middleware := range middlewares { - e.router.Use(middleware) - } -} - func (e *routerImpl) Get() http.Handler { return e.router } diff --git a/backend/pkg/server/auth/auth.go b/backend/pkg/server/auth/auth.go index 130943832..9064b5b25 100644 --- a/backend/pkg/server/auth/auth.go +++ b/backend/pkg/server/auth/auth.go @@ -14,7 +14,7 @@ import ( ) type Auth interface { - IsAuthorized(authHeader string, permissions []string, isExtension bool) (*user.User, error) + IsAuthorized(authHeader string, isExtension bool) (*user.User, error) Middleware(next http.Handler) http.Handler } diff --git a/backend/pkg/server/auth/authorizer.go b/backend/pkg/server/auth/authorizer.go index 2f2a2571e..ba022f5eb 100644 --- a/backend/pkg/server/auth/authorizer.go +++ b/backend/pkg/server/auth/authorizer.go @@ -2,7 +2,7 @@ package auth import "openreplay/backend/pkg/server/user" -func (a *authImpl) IsAuthorized(authHeader string, permissions []string, isExtension bool) (*user.User, error) { +func (a *authImpl) IsAuthorized(authHeader string, isExtension bool) (*user.User, error) { secret := a.secret if isExtension { secret = a.spotSecret diff --git a/backend/pkg/server/auth/jwt/auth.go b/backend/pkg/server/auth/jwt/auth.go new file mode 100644 index 000000000..a63f3445b --- /dev/null +++ b/backend/pkg/server/auth/jwt/auth.go @@ -0,0 +1,98 @@ +package jwt + +import ( + "fmt" + "net/http" + "strings" + + ctxStore "github.com/docker/distribution/context" + "github.com/golang-jwt/jwt/v5" + + "openreplay/backend/pkg/db/postgres/pool" + "openreplay/backend/pkg/logger" + "openreplay/backend/pkg/server/api" + "openreplay/backend/pkg/server/user" +) + +type authImpl struct { + log logger.Logger + jwtSecret string + pgconn pool.Pool +} + +func NewAuth(log logger.Logger, jwtSecret string, pgConn pool.Pool) api.RouterMiddleware { + return &authImpl{ + log: log, + jwtSecret: jwtSecret, + pgconn: pgConn, + } +} + +func (a *authImpl) Middleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + authHeader := r.Header.Get("Authorization") + if authHeader == "" { + w.WriteHeader(http.StatusUnauthorized) + return + } + jwtInfo, err := parseJWT(authHeader, a.jwtSecret) + if err != nil { + a.log.Warn(r.Context(), "Unauthorized request, wrong jwt token: %s", err) + w.WriteHeader(http.StatusUnauthorized) + return + } + userInfo, err := authUser(a.pgconn, jwtInfo.UserId, jwtInfo.TenantID, int(jwtInfo.IssuedAt.Unix())) + if err != nil { + a.log.Warn(r.Context(), "Unauthorized request, user not found: %s", err) + w.WriteHeader(http.StatusUnauthorized) + return + } + r = r.WithContext(ctxStore.WithValues(r.Context(), map[string]interface{}{"userData": userInfo})) + next.ServeHTTP(w, r) + }) +} + +func parseJWT(authHeader, secret string) (*user.JWTClaims, error) { + if authHeader == "" { + return nil, fmt.Errorf("authorization header missing") + } + tokenParts := strings.Split(authHeader, "Bearer ") + if len(tokenParts) != 2 { + return nil, fmt.Errorf("invalid authorization header") + } + tokenString := tokenParts[1] + + claims := &user.JWTClaims{} + token, err := jwt.ParseWithClaims(tokenString, claims, + func(token *jwt.Token) (interface{}, error) { + return []byte(secret), nil + }) + if err != nil || !token.Valid { + fmt.Printf("token err: %v\n", err) + return nil, fmt.Errorf("invalid token") + } + return claims, nil +} + +func authUser(conn pool.Pool, userID, tenantID, jwtIAT int) (*user.User, error) { + sql := ` + SELECT user_id, name, email, EXTRACT(epoch FROM spot_jwt_iat)::BIGINT AS spot_jwt_iat + FROM public.users + WHERE user_id = $1 AND deleted_at IS NULL + LIMIT 1;` + newUser := &user.User{TenantID: 1, AuthMethod: "jwt"} + if err := conn.QueryRow(sql, userID).Scan(&newUser.ID, &newUser.Name, &newUser.Email, &newUser.JwtIat); err != nil { + return nil, fmt.Errorf("user not found") + } + if newUser.JwtIat == 0 || abs(jwtIAT-newUser.JwtIat) > 1 { + return nil, fmt.Errorf("token has been updated") + } + return newUser, nil +} + +func abs(x int) int { + if x < 0 { + return -x + } + return x +} diff --git a/backend/pkg/server/auth/middleware.go b/backend/pkg/server/auth/middleware.go index fc5f54121..f79ea376f 100644 --- a/backend/pkg/server/auth/middleware.go +++ b/backend/pkg/server/auth/middleware.go @@ -10,7 +10,7 @@ import ( func (e *authImpl) Middleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - user, err := e.IsAuthorized(r.Header.Get("Authorization"), getPermissions(r.URL.Path), e.isExtensionRequest(r)) + user, err := e.IsAuthorized(r.Header.Get("Authorization"), e.isExtensionRequest(r)) if err != nil { if !e.isSpotWithKeyRequest(r) { e.log.Warn(r.Context(), "Unauthorized request, wrong jwt token: %s", err) diff --git a/backend/pkg/server/auth/permissions.go b/backend/pkg/server/auth/permissions.go deleted file mode 100644 index 6edc34199..000000000 --- a/backend/pkg/server/auth/permissions.go +++ /dev/null @@ -1,5 +0,0 @@ -package auth - -func getPermissions(urlPath string) []string { - return nil -} diff --git a/backend/pkg/server/limiter/limiter.go b/backend/pkg/server/limiter/limiter.go index b72a4d7b3..491cbf8ba 100644 --- a/backend/pkg/server/limiter/limiter.go +++ b/backend/pkg/server/limiter/limiter.go @@ -1,6 +1,7 @@ package limiter import ( + "net/http" "sync" "time" ) @@ -46,7 +47,7 @@ func (rl *RateLimiter) Allow() bool { return false } -type UserRateLimiter struct { +type userRateLimiter struct { rateLimiters sync.Map rate int burst int @@ -54,8 +55,12 @@ type UserRateLimiter struct { maxIdleTime time.Duration } -func NewUserRateLimiter(rate int, burst int, cleanupInterval time.Duration, maxIdleTime time.Duration) *UserRateLimiter { - url := &UserRateLimiter{ +type UserRateLimiter interface { + Middleware(next http.Handler) http.Handler +} + +func NewUserRateLimiter(rate int, burst int, cleanupInterval time.Duration, maxIdleTime time.Duration) UserRateLimiter { + url := &userRateLimiter{ rate: rate, burst: burst, cleanupInterval: cleanupInterval, @@ -65,12 +70,12 @@ func NewUserRateLimiter(rate int, burst int, cleanupInterval time.Duration, maxI return url } -func (url *UserRateLimiter) GetRateLimiter(user uint64) *RateLimiter { +func (url *userRateLimiter) getRateLimiter(user uint64) *RateLimiter { value, _ := url.rateLimiters.LoadOrStore(user, NewRateLimiter(url.rate, url.burst)) return value.(*RateLimiter) } -func (url *UserRateLimiter) cleanup() { +func (url *userRateLimiter) cleanup() { for { time.Sleep(url.cleanupInterval) now := time.Now() diff --git a/backend/pkg/server/limiter/middleware.go b/backend/pkg/server/limiter/middleware.go index 4d50161ce..9b41e21b2 100644 --- a/backend/pkg/server/limiter/middleware.go +++ b/backend/pkg/server/limiter/middleware.go @@ -5,7 +5,7 @@ import ( "openreplay/backend/pkg/server/user" ) -func (rl *UserRateLimiter) Middleware(next http.Handler) http.Handler { +func (url *userRateLimiter) Middleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { userContext := r.Context().Value("userData") if userContext == nil { @@ -13,7 +13,7 @@ func (rl *UserRateLimiter) Middleware(next http.Handler) http.Handler { return } authUser := userContext.(*user.User) - rl := rl.GetRateLimiter(authUser.ID) + rl := url.getRateLimiter(authUser.ID) if !rl.Allow() { http.Error(w, "Too Many Requests", http.StatusTooManyRequests) diff --git a/backend/pkg/server/permissions/permissions.go b/backend/pkg/server/permissions/permissions.go new file mode 100644 index 000000000..39a86d5ff --- /dev/null +++ b/backend/pkg/server/permissions/permissions.go @@ -0,0 +1,10 @@ +package permissions + +import ( + "openreplay/backend/pkg/logger" + "openreplay/backend/pkg/server/api" +) + +func New(log logger.Logger) api.RouterMiddleware { + return api.NewDefaultMiddleware() +} diff --git a/backend/pkg/sessions/api/mobile/handlers.go b/backend/pkg/sessions/api/mobile/handlers.go index 6fd46801b..35ab7efc7 100644 --- a/backend/pkg/sessions/api/mobile/handlers.go +++ b/backend/pkg/sessions/api/mobile/handlers.go @@ -88,10 +88,10 @@ func NewHandlers(cfg *httpCfg.Config, log logger.Logger, responser *api.Response func (e *handlersImpl) GetAll() []*api.Description { return []*api.Description{ - {"/v1/mobile/start", e.startMobileSessionHandler, "POST"}, - {"/v1/mobile/i", e.pushMobileMessagesHandler, "POST"}, - {"/v1/mobile/late", e.pushMobileLateMessagesHandler, "POST"}, - {"/v1/mobile/images", e.mobileImagesUploadHandler, "POST"}, + {"POST", "/v1/mobile/start", e.startMobileSessionHandler, api.NoPermissions, api.DoNotTrack}, + {"POST", "/v1/mobile/i", e.pushMobileMessagesHandler, api.NoPermissions, api.DoNotTrack}, + {"POST", "/v1/mobile/late", e.pushMobileLateMessagesHandler, api.NoPermissions, api.DoNotTrack}, + {"POST", "/v1/mobile/images", e.mobileImagesUploadHandler, api.NoPermissions, api.DoNotTrack}, } } diff --git a/backend/pkg/sessions/api/web/handlers.go b/backend/pkg/sessions/api/web/handlers.go index f3530015a..6b06e846d 100644 --- a/backend/pkg/sessions/api/web/handlers.go +++ b/backend/pkg/sessions/api/web/handlers.go @@ -73,9 +73,9 @@ func NewHandlers(cfg *httpCfg.Config, log logger.Logger, responser *api.Response func (e *handlersImpl) GetAll() []*api.Description { return []*api.Description{ - {"/v1/web/start", e.startSessionHandlerWeb, "POST"}, - {"/v1/web/i", e.pushMessagesHandlerWeb, "POST"}, - {"/v1/web/images", e.imagesUploaderHandlerWeb, "POST"}, + {"POST", "/v1/web/start", e.startSessionHandlerWeb, api.NoPermissions, api.DoNotTrack}, + {"POST", "/v1/web/i", e.pushMessagesHandlerWeb, api.NoPermissions, api.DoNotTrack}, + {"POST", "/v1/web/images", e.imagesUploaderHandlerWeb, api.NoPermissions, api.DoNotTrack}, } } diff --git a/backend/pkg/spot/api/handlers.go b/backend/pkg/spot/api/handlers.go index 87fa5b6e5..86b235c9f 100644 --- a/backend/pkg/spot/api/handlers.go +++ b/backend/pkg/spot/api/handlers.go @@ -47,18 +47,18 @@ func NewHandlers(log logger.Logger, cfg *spotConfig.Config, responser *api.Respo func (e *handlersImpl) GetAll() []*api.Description { return []*api.Description{ - {"/v1/spots", e.createSpot, "POST"}, - {"/v1/spots/{id}", e.getSpot, "GET"}, - {"/v1/spots/{id}", e.updateSpot, "PATCH"}, - {"/v1/spots", e.getSpots, "GET"}, - {"/v1/spots", e.deleteSpots, "DELETE"}, - {"/v1/spots/{id}/comment", e.addComment, "POST"}, - {"/v1/spots/{id}/uploaded", e.uploadedSpot, "POST"}, - {"/v1/spots/{id}/video", e.getSpotVideo, "GET"}, - {"/v1/spots/{id}/public-key", e.getPublicKey, "GET"}, - {"/v1/spots/{id}/public-key", e.updatePublicKey, "PATCH"}, - {"/v1/spots/{id}/status", e.spotStatus, "GET"}, - {"/v1/ping", e.ping, "GET"}, + {"POST", "/v1/spots", e.createSpot, api.NoPermissions, "createSpot"}, + {"GET", "/v1/spots/{id}", e.getSpot, api.NoPermissions, "getSpot"}, + {"PATCH", "/v1/spots/{id}", e.updateSpot, api.NoPermissions, "updateSpot"}, + {"GET", "/v1/spots", e.getSpots, api.NoPermissions, api.DoNotTrack}, + {"DELETE", "/v1/spots", e.deleteSpots, api.NoPermissions, "deleteSpots"}, + {"POST", "/v1/spots/{id}/comment", e.addComment, api.NoPermissions, "addComment"}, + {"POST", "/v1/spots/{id}/uploaded", e.uploadedSpot, api.NoPermissions, api.DoNotTrack}, + {"GET", "/v1/spots/{id}/video", e.getSpotVideo, api.NoPermissions, "getSpotVideo"}, + {"GET", "/v1/spots/{id}/public-key", e.getPublicKey, api.NoPermissions, api.DoNotTrack}, + {"PATCH", "/v1/spots/{id}/public-key", e.updatePublicKey, api.NoPermissions, "updatePublicKey"}, + {"GET", "/v1/spots/{id}/status", e.spotStatus, api.NoPermissions, api.DoNotTrack}, + {"GET", "/v1/ping", e.ping, api.NoPermissions, api.DoNotTrack}, } } diff --git a/backend/pkg/spot/builder.go b/backend/pkg/spot/builder.go index 5781df448..91aaff741 100644 --- a/backend/pkg/spot/builder.go +++ b/backend/pkg/spot/builder.go @@ -1,13 +1,13 @@ package spot import ( - "openreplay/backend/pkg/metrics/database" "time" "openreplay/backend/internal/config/spot" "openreplay/backend/pkg/db/postgres/pool" "openreplay/backend/pkg/flakeid" "openreplay/backend/pkg/logger" + "openreplay/backend/pkg/metrics/database" spotMetrics "openreplay/backend/pkg/metrics/spot" "openreplay/backend/pkg/metrics/web" "openreplay/backend/pkg/objectstorage/store" @@ -15,6 +15,7 @@ import ( "openreplay/backend/pkg/server/auth" "openreplay/backend/pkg/server/keys" "openreplay/backend/pkg/server/limiter" + "openreplay/backend/pkg/server/permissions" "openreplay/backend/pkg/server/tracer" spotAPI "openreplay/backend/pkg/spot/api" "openreplay/backend/pkg/spot/service" @@ -22,10 +23,11 @@ import ( ) type ServicesBuilder struct { - Auth auth.Auth - RateLimiter *limiter.UserRateLimiter - AuditTrail tracer.Tracer - SpotsAPI api.Handlers + RateLimiter api.RouterMiddleware + Authenticator api.RouterMiddleware + Permissions api.RouterMiddleware + AuditTrail api.RouterMiddleware + SpotsAPI api.Handlers } func NewServiceBuilder(log logger.Logger, cfg *spot.Config, webMetrics web.Web, spotMetrics spotMetrics.Spot, dbMetrics database.Database, pgconn pool.Pool, prefix string) (*ServicesBuilder, error) { @@ -47,9 +49,10 @@ func NewServiceBuilder(log logger.Logger, cfg *spot.Config, webMetrics web.Web, return nil, err } return &ServicesBuilder{ - Auth: auth.NewAuth(log, cfg.JWTSecret, cfg.JWTSpotSecret, pgconn, keys, prefix), - RateLimiter: limiter.NewUserRateLimiter(10, 30, 1*time.Minute, 5*time.Minute), - AuditTrail: auditrail, - SpotsAPI: handlers, + RateLimiter: limiter.NewUserRateLimiter(10, 30, 1*time.Minute, 5*time.Minute), + Authenticator: auth.NewAuth(log, cfg.JWTSecret, cfg.JWTSpotSecret, pgconn, keys, prefix), + Permissions: permissions.New(log), + AuditTrail: auditrail, + SpotsAPI: handlers, }, nil } diff --git a/backend/pkg/tags/api/handlers.go b/backend/pkg/tags/api/handlers.go index 6aa6e00b2..4683c052b 100644 --- a/backend/pkg/tags/api/handlers.go +++ b/backend/pkg/tags/api/handlers.go @@ -33,7 +33,7 @@ func NewHandlers(log logger.Logger, responser *api.Responser, tokenizer *token.T func (e *handlersImpl) GetAll() []*api.Description { return []*api.Description{ - {"/v1/web/tags", e.getTags, "GET"}, + {"GET", "/v1/web/tags", e.getTags, api.NoPermissions, api.DoNotTrack}, } } diff --git a/backend/pkg/uxtesting/api/handlers.go b/backend/pkg/uxtesting/api/handlers.go index 9188d6c88..396229bd0 100644 --- a/backend/pkg/uxtesting/api/handlers.go +++ b/backend/pkg/uxtesting/api/handlers.go @@ -43,10 +43,10 @@ func NewHandlers(log logger.Logger, responser *api.Responser, jsonSizeLimit int6 func (e *handlersImpl) GetAll() []*api.Description { return []*api.Description{ - {"/v1/web/uxt/signals/test", e.sendUXTestSignal, "POST"}, - {"/v1/web/uxt/signals/task", e.sendUXTaskSignal, "POST"}, - {"/v1/web/uxt/test/{id}", e.getUXTestInfo, "GET"}, - {"/v1/web/uxt/upload-url", e.getUXUploadUrl, "GET"}, + {"POST", "/v1/web/uxt/signals/test", e.sendUXTestSignal, api.NoPermissions, api.DoNotTrack}, + {"POST", "/v1/web/uxt/signals/task", e.sendUXTaskSignal, api.NoPermissions, api.DoNotTrack}, + {"GET", "/v1/web/uxt/test/{id}", e.getUXTestInfo, api.NoPermissions, api.DoNotTrack}, + {"GET", "/v1/web/uxt/upload-url", e.getUXUploadUrl, api.NoPermissions, api.DoNotTrack}, } }