package middleware import ( "context" "encoding/json" "errors" "net/http" "strconv" "strings" "go.uber.org/zap" "penahub.gitlab.yandexcloud.net/backend/penahub_common/jwt_adapter" "penahub.gitlab.yandexcloud.net/backend/templategen/amo" "penahub.gitlab.yandexcloud.net/backend/templategen/dal" "penahub.gitlab.yandexcloud.net/backend/templategen/dal/model" ) type Middleware struct { dal *dal.MongoDAL amo *amo.ClientApp logger *zap.Logger AmoAccessMap map[string]string // key - endpoint; value - access (visibility, creation, delete) } type contextKey uint const ( PenaUserIDContextKey contextKey = 1 // UserID from Pena JWT AmoDataContextKey contextKey = 2 // AmoData AmoIsAdminContextKey contextKey = 3 // Is Admin? AmoUserIDContextKey contextKey = 4 // ID of user making the request from AMO CRM ) 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 == http.MethodOptions { 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 } var penaID string amoData := GetAmoData(r) if amoData != nil { penaID = amoData.PenaID } mw.logger.Info("HttpRequest", zap.String("URL", r.URL.String()), zap.String("PenaID", penaID), ) 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 } ctx := r.Context() // Если уже прошла авторизация через сервис AMO, то пропускаем дальнейшие действия amoData := GetAmoData(r) if amoData != nil { ctx = context.WithValue(ctx, PenaUserIDContextKey, amoData.PenaID) next.ServeHTTP(w, r.WithContext(ctx)) return } // Check token in headers headerValue := r.Header.Get(jwt_adapter.DefaultHeaderKey) jwt, err := jwt_adapter.Decode(headerValue) if err != nil { mw.reportError(w, http.StatusUnauthorized, err) } ctx = context.WithValue(ctx, PenaUserIDContextKey, jwt.GetUserID()) // Add AMO data to context amoData, err = mw.dal.Amo.GetByPenaID(ctx, jwt.GetUserID()) if err != nil { mw.reportError(w, http.StatusInternalServerError, err) return } ctx = context.WithValue(ctx, AmoDataContextKey, amoData) 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") 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, http.StatusUnauthorized, err) } else { next.ServeHTTP(w, r) } return } data, err := mw.dal.Amo.GetByAccountID(r.Context(), strconv.FormatInt(token.AccountID, 10)) if err != nil { mw.reportError(w, http.StatusInternalServerError, err) return } if data == nil && !strings.Contains(r.URL.String(), "/amo/state") { mw.reportError(w, http.StatusUnauthorized, errors.New("amo account not found")) 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, http.StatusForbidden, errors.New("amoUser forbidden")) // mb: http.StatusForbidden return } ctx := context.WithValue(r.Context(), AmoDataContextKey, data) ctx = context.WithValue(ctx, AmoIsAdminContextKey, token.IsAdmin) ctx = context.WithValue(ctx, AmoUserIDContextKey, 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.GetByAccountID(r.Context(), "30228997") if err != nil { mw.reportError(w, http.StatusInternalServerError, err) return } if data == nil { mw.reportError(w, http.StatusUnauthorized, errors.New("amo account not found")) return } ctx = context.WithValue(ctx, AmoDataContextKey, data) } next.ServeHTTP(w, r.WithContext(ctx)) }) } func GetPenaUserID(r *http.Request) string { user, ok := r.Context().Value(PenaUserIDContextKey).(string) if ok { return user } return "" } func GetAmoData(r *http.Request) *model.Amo { amoData, ok := r.Context().Value(AmoDataContextKey).(*model.Amo) if ok { return amoData } return nil } func GetAmoUserID(r *http.Request) int64 { result, ok := r.Context().Value(AmoUserIDContextKey).(int64) if ok { return result } return 0 } func (mw *Middleware) reportError(w http.ResponseWriter, status int, err error) { mw.logger.Error("ErrorMiddleware", zap.Error(err)) w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(status) e := json.NewEncoder(w) e.SetEscapeHTML(false) errJSON := e.Encode(err) if errJSON != nil { mw.logger.Error("ErrorMiddleware - JSON ENCODE", zap.Error(errJSON)) } }