feat(router): small refactoring
This commit is contained in:
parent
24a220bc51
commit
088a227dec
29 changed files with 259 additions and 120 deletions
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
98
backend/pkg/server/auth/jwt/auth.go
Normal file
98
backend/pkg/server/auth/jwt/auth.go
Normal file
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
package auth
|
||||
|
||||
func getPermissions(urlPath string) []string {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
10
backend/pkg/server/permissions/permissions.go
Normal file
10
backend/pkg/server/permissions/permissions.go
Normal file
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue