package middleware import ( "context" "encoding/json" "errors" "github.com/Pena-Co-Ltd/amocrm_templategen_back/amo" "github.com/Pena-Co-Ltd/amocrm_templategen_back/dal" "github.com/Pena-Co-Ltd/amocrm_templategen_back/dal/model" "github.com/dgrijalva/jwt-go" "go.uber.org/zap" "net/http" "strconv" "strings" ) var ( JwtSecret = []byte("my-secret-123") ) type Middleware struct { dal *dal.MongoDAL amo *amo.ClientApp logger *zap.Logger AmoAccessMap map[string]string // key - endpoint; value - access (visibility, creation, delete) } type UserClaims struct { UserID string FullName string Email string IsActivated bool jwt.StandardClaims } func InitMiddleware(dal *dal.MongoDAL, amo *amo.ClientApp, logger *zap.Logger, amoAccessMap map[string]string) *Middleware { return &Middleware{dal: dal, amo: amo, logger: logger, AmoAccessMap: amoAccessMap} } func (mw *Middleware) MiddlewareCors(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == "OPTIONS" { w.WriteHeader(http.StatusOK) } else { next.ServeHTTP(w, r) } }) } func (mw *Middleware) Logger(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if strings.Contains(r.URL.String(), "/assets/") { next.ServeHTTP(w, r) return } userId := "" user := getJwtUser(r) if user != nil { userId = user.UserID } amoData := getAmoByJwt(r) if amoData != nil { userId = amoData.UserID } mw.logger.Info("HttpRequest", zap.String("URL", r.URL.String()), zap.String("UserID", userId), ) next.ServeHTTP(w, r) }) } func (mw *Middleware) MiddlewareJwt(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if strings.Contains(r.URL.String(), "/assets/") { next.ServeHTTP(w, r) return } authMap := map[string]interface{}{ //"/amo": nil, } required := false _, ok := authMap[r.URL.String()] if ok { required = true } // Check token in headers tokenHeader := r.Header.Get("Authorization") if tokenHeader == "" && required { w.WriteHeader(http.StatusUnauthorized) return } tokenHeader = strings.Replace(tokenHeader, "Bearer ", "", -1) if tokenHeader != "" { token, err := jwt.ParseWithClaims(tokenHeader, &UserClaims{}, func(tk *jwt.Token) (interface{}, error) { return JwtSecret, nil }) if err != nil { mw.logger.Error("ErrJwtAccess", zap.Error(err)) } if claims, ok := token.Claims.(*UserClaims); ok && token.Valid { ctx := context.WithValue(r.Context(), "JWT", claims) next.ServeHTTP(w, r.WithContext(ctx)) return } } // Check token in cookies c, err := r.Cookie("Authorization") if err != nil { if !required { next.ServeHTTP(w, r) } else { mw.logger.Error("ErrJwtAccess", zap.Error(err)) w.WriteHeader(http.StatusUnauthorized) } return } tokenCookie := strings.Replace(c.Value, "Bearer ", "", -1) token, err := jwt.ParseWithClaims(tokenCookie, &UserClaims{}, func(tk *jwt.Token) (interface{}, error) { return JwtSecret, nil }) if err != nil { mw.logger.Error("ErrJwtAccess", zap.Error(err)) if !required { next.ServeHTTP(w, r) } else { w.WriteHeader(http.StatusUnauthorized) } return } claims, ok := token.Claims.(*UserClaims) if !ok || !token.Valid { mw.logger.Error("ErrJwtAccess", zap.Error(err)) if !required { next.ServeHTTP(w, r) } else { w.WriteHeader(http.StatusUnauthorized) } return } ctx := context.WithValue(r.Context(), "JWT", claims) next.ServeHTTP(w, r.WithContext(ctx)) }) } func (mw *Middleware) MiddlewareHeaders(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With, X-Auth-Token") w.Header().Set("Access-Control-Allow-Credentials", "true") w.Header().Set("Access-Control-Expose-Headers", "*") w.Header().Set("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE") //w.Header().Add() next.ServeHTTP(w, r) }) } func (mw *Middleware) MiddlewareAmoJwt(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if strings.Contains(r.URL.String(), "/assets/") { next.ServeHTTP(w, r) return } authMap := map[string]interface{}{ //"/amo": nil, } required := false _, ok := authMap[r.URL.String()] if ok { required = true } token, err := mw.amo.DecodeJwt(r) if err != nil { if required { mw.reportError(w, err, http.StatusUnauthorized) } else { mw.logger.Error("ErrAmoJwtAccess", zap.Error(err)) next.ServeHTTP(w, r) } return } data, err := mw.dal.Amo.GetByAccountID(r.Context(), strconv.FormatInt(token.AccountId, 10)) if err != nil { mw.reportError(w, err, http.StatusInternalServerError) return } if data == nil { mw.reportError(w, errors.New("amo account not found"), http.StatusUnauthorized) return } // check access rules isAcessGranted := token.IsAdmin // Если права не указаны для эндпоинта, то доступно любому юзеру rule, ok := mw.AmoAccessMap[r.URL.String()] if ok { // Если не админ, то проверяем права пользователя if rule == "visibility" && !isAcessGranted { for _, userId := range data.AccessRules.Visibility { if userId == token.UserId { isAcessGranted = true break } } } if rule == "creation" && !isAcessGranted { for _, userId := range data.AccessRules.Creation { if userId == token.UserId { isAcessGranted = true break } } } if rule == "delete" && !isAcessGranted { for _, userId := range data.AccessRules.Delete { if userId == token.UserId { isAcessGranted = true break } } } } else { isAcessGranted = true } // Если прав нет, прерываемся if !isAcessGranted { mw.reportError(w, errors.New("amoUser forbidden"), http.StatusUnauthorized) // mb: http.StatusForbidden return } ctx := context.WithValue(r.Context(), "amoData", data) ctx = context.WithValue(ctx, "amoIsAdmin", token.IsAdmin) ctx = context.WithValue(ctx, "amoUserID", token.UserId) next.ServeHTTP(w, r.WithContext(ctx)) }) } func (mw *Middleware) MiddlewareAmoPlug(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if strings.Contains(r.URL.String(), "/assets/") { next.ServeHTTP(w, r) return } ctx := r.Context() if r.Header.Get("TestAuth") == "75bc853ae763ffc36cad97069682fed1" { data, err := mw.dal.Amo.GetByID(r.Context(), "63226edec2259da3375fe2a4") if err != nil { mw.reportError(w, err, http.StatusInternalServerError) return } if data == nil { mw.reportError(w, errors.New("amo account not found"), http.StatusUnauthorized) return } ctx = context.WithValue(ctx, "amoData", data) } next.ServeHTTP(w, r.WithContext(ctx)) }) } func getJwtUser(r *http.Request) *UserClaims { user, ok := r.Context().Value("JWT").(*UserClaims) if ok { return user } return nil } func getAmoByJwt(r *http.Request) *model.Amo { amoData, ok := r.Context().Value("amoData").(*model.Amo) if ok { return amoData } return nil } func (mw *Middleware) reportError(w http.ResponseWriter, err error, status int) error { mw.logger.Error("ErrorMiddleware", zap.Error(err)) w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(status) return json.NewEncoder(w).Encode(err) }