From f891d96e831f6d0e3fe8fcac2fec12e681454ef9 Mon Sep 17 00:00:00 2001 From: QUDUSKUNLE Date: Sun, 6 Aug 2023 21:07:48 +0100 Subject: [PATCH] Abstract Login to services --- internal/adapters/handlers/address.handler.go | 13 +- internal/adapters/handlers/handlers.go | 112 +----------------- internal/adapters/handlers/helpers.go | 37 ++++++ internal/adapters/handlers/middlewares.go | 75 ++++++++++++ internal/adapters/handlers/profile.handler.go | 14 +-- internal/adapters/handlers/users.handler.go | 24 ++-- internal/core/services/users.services.go | 27 +++++ main.go | 12 +- 8 files changed, 168 insertions(+), 146 deletions(-) create mode 100644 internal/adapters/handlers/helpers.go create mode 100644 internal/adapters/handlers/middlewares.go diff --git a/internal/adapters/handlers/address.handler.go b/internal/adapters/handlers/address.handler.go index 27c3f2e..f467f4b 100644 --- a/internal/adapters/handlers/address.handler.go +++ b/internal/adapters/handlers/address.handler.go @@ -13,19 +13,18 @@ func (service *HTTPHandler) SaveAddress(ctx *gin.Context) { ctx.JSON(http.StatusBadRequest, gin.H{"error": service.CompileErrors(err) }) return } - user, err := service.CurrentUser(ctx) - if err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error() }) + user, fal := ctx.Get("user") + if !fal { + ctx.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized", "status": false }) return } - - profile, err := service.ExternalServicesAdapter.ReadProfile((user.ID).String()) + profile, err := service.ServicesAdapter.ReadProfile(((user.(*domain.User)).ID).String()) if err != nil { ctx.JSON(http.StatusNotFound, gin.H{"error": "Profile record not found", "status": false}) return } - if err := service.ExternalServicesAdapter.SaveAddress( + if err := service.ServicesAdapter.SaveAddress( domain.Address{ StreetNo: address.StreetNo, StreetName: address.StreetName, @@ -44,7 +43,7 @@ func (service *HTTPHandler) ReadAddress(ctx *gin.Context) { } func (service *HTTPHandler) ReadAddresses(ctx *gin.Context) { - addresses, err := service.ExternalServicesAdapter.ReadAddresses() + addresses, err := service.ServicesAdapter.ReadAddresses() if err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{ "error": err.Error(), "status": false}) return diff --git a/internal/adapters/handlers/handlers.go b/internal/adapters/handlers/handlers.go index 47f8433..6e410a8 100644 --- a/internal/adapters/handlers/handlers.go +++ b/internal/adapters/handlers/handlers.go @@ -1,123 +1,15 @@ package handlers import ( - "fmt" - "os" - "strconv" - "time" services "server/internal/core/services" - "net/http" - "errors" - "strings" - "github.com/go-playground/validator/v10" - "server/internal/core/domain" - "github.com/google/uuid" - "github.com/golang-jwt/jwt/v4" - "github.com/gin-gonic/gin" ) -type ErrorMessage struct { - Field string `json:"field"` - Message string `json:"message"` -} - -var privateKey = []byte(os.Getenv("JWT_PRIVATE_KEY")) - type HTTPHandler struct { - ExternalServicesAdapter services.ServicesHandler - InternalServicesAdapter services.ServicesHandler + ServicesAdapter services.ServicesHandler } func HTTPAdapter(services services.ServicesHandler) *HTTPHandler { return &HTTPHandler{ - ExternalServicesAdapter: services, - InternalServicesAdapter: services, - } -} - -func (service *HTTPHandler) JWTAuthMiddleware() gin.HandlerFunc { - return func(ctx *gin.Context) { - token, err := service.ExtractToken(ctx) - if err != nil { - ctx.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()}) - ctx.Abort() - return - } - if _, err := service.ValidateJWToken(token); err != nil { - ctx.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()}) - ctx.Abort() - return - } - ctx.Next() - } -} - -func (service *HTTPHandler) UUidMiddleware() gin.HandlerFunc { - return func(context *gin.Context) { - if !service.ValidateUUID(context.Param("id")) { - context.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "invalid id credential"}) - return - } - context.Next() - } -} - -func (service *HTTPHandler) ExtractToken(context *gin.Context) (*jwt.Token, error) { - bearerToken := context.Request.Header.Get("Authorization") - splitToken := strings.Split(bearerToken, " ") - token, err := jwt.Parse(splitToken[1], func(token *jwt.Token) (interface{}, error) { - if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) - } - return privateKey, nil - }) - return token, err -} - -func (service *HTTPHandler) ValidateJWToken(token *jwt.Token) (jwt.MapClaims, error) { - claim, ok := token.Claims.(jwt.MapClaims) - if ok && token.Valid { - return claim, nil - } - return nil, errors.New("invalid token") -} - -func (service *HTTPHandler) ValidateUUID(uu string) bool { - if _, err := uuid.Parse(uu); err != nil { - return false - } - return true -} - -func (service *HTTPHandler) GenerateJWToken(user domain.User) (string, error) { - tokenTTL, _ := strconv.Atoi(os.Getenv("TOKEN_TTL")) - token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - "id": user.ID, - "iat": time.Now().Unix(), - "eat": time.Now().Add(time.Second * time.Duration(tokenTTL)).Unix(), - }) - return token.SignedString(privateKey) -} - -func (service *HTTPHandler) SetErrorMessage(message validator.FieldError) string { - switch message.Tag() { - case "required": - return "This field is required" - case "lte": - return "Should be less than " + message.Param() - case "gte": - return "Should be greater than " + message.Param() - } - return "unknown" -} - -func (service *HTTPHandler) CompileErrors(err error) []ErrorMessage { - var ve validator.ValidationErrors - var result []ErrorMessage - if errors.As(err, &ve) { - for _, fe := range ve { - result = append(result, ErrorMessage{fe.Field(), service.SetErrorMessage(fe)}) - } + ServicesAdapter: services, } - return result } diff --git a/internal/adapters/handlers/helpers.go b/internal/adapters/handlers/helpers.go new file mode 100644 index 0000000..98e22f3 --- /dev/null +++ b/internal/adapters/handlers/helpers.go @@ -0,0 +1,37 @@ +package handlers + +import ( + "os" + "errors" + "github.com/go-playground/validator/v10" +) + +type ErrorMessage struct { + Field string `json:"field"` + Message string `json:"message"` +} + +var privateKey = []byte(os.Getenv("JWT_PRIVATE_KEY")) + +func (service *HTTPHandler) SetErrorMessage(message validator.FieldError) string { + switch message.Tag() { + case "required": + return "This field is required" + case "lte": + return "Should be less than " + message.Param() + case "gte": + return "Should be greater than " + message.Param() + } + return "unknown" +} + +func (service *HTTPHandler) CompileErrors(err error) []ErrorMessage { + var ve validator.ValidationErrors + var result []ErrorMessage + if errors.As(err, &ve) { + for _, fe := range ve { + result = append(result, ErrorMessage{fe.Field(), service.SetErrorMessage(fe)}) + } + } + return result +} diff --git a/internal/adapters/handlers/middlewares.go b/internal/adapters/handlers/middlewares.go new file mode 100644 index 0000000..f5d563f --- /dev/null +++ b/internal/adapters/handlers/middlewares.go @@ -0,0 +1,75 @@ +package handlers + +import ( + "fmt" + "net/http" + "errors" + "strings" + "github.com/google/uuid" + "github.com/golang-jwt/jwt/v4" + "github.com/gin-gonic/gin" +) + +func (service *HTTPHandler) JWTAuthentication() gin.HandlerFunc { + return func(ctx *gin.Context) { + token, err := extractToken(ctx) + if err != nil { + ctx.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()}) + ctx.Abort() + return + } + cla, err := validateJWToken(token) + if err != nil { + ctx.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()}) + ctx.Abort() + return + } + UserID := cla["id"].(string) + user, err := service.ServicesAdapter.ReadUser(UserID) + if err != nil { + ctx.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()}) + ctx.Abort() + return + } + ctx.Set("user", user) + ctx.Next() + } +} + +func (service *HTTPHandler) UUIDMiddleware() gin.HandlerFunc { + return func (ctx *gin.Context) { + if !validateUUID(ctx.Param("id")) { + ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "invalid id credential"}) + return + } + ctx.Next() + } +} + +func extractToken(ctx *gin.Context) (*jwt.Token, error) { + bearerToken := ctx.Request.Header.Get("Authorization") + splitToken := strings.Split(bearerToken, " ") + token, err := jwt.Parse(splitToken[1], func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } + return privateKey, nil + }) + return token, err +} + +func validateJWToken(token *jwt.Token) (jwt.MapClaims, error) { + claim, ok := token.Claims.(jwt.MapClaims) + if ok && token.Valid { + return claim, nil + } + return nil, errors.New("invalid token") +} + +func validateUUID(str string) bool { + if _, err := uuid.Parse(str); err != nil { + return false + } + return true +} + diff --git a/internal/adapters/handlers/profile.handler.go b/internal/adapters/handlers/profile.handler.go index b6e2610..e123935 100644 --- a/internal/adapters/handlers/profile.handler.go +++ b/internal/adapters/handlers/profile.handler.go @@ -14,16 +14,16 @@ func (service *HTTPHandler) SaveProfile(ctx *gin.Context) { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error() }) return } - user, err := service.CurrentUser(ctx) - if err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error() }) + user, fal := ctx.Get("user") + if !fal { + ctx.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized", "status": false }) return } - if err := service.ExternalServicesAdapter.SaveProfile( + if err := service.ServicesAdapter.SaveProfile( domain.Profile{ FirstName: strings.TrimSpace(profileDto.FirstName), LastName: strings.TrimSpace(profileDto.LastName), - User: &user, + User: user.(*domain.User), }); err != nil { ctx.JSON(http.StatusConflict, gin.H{"error": err.Error()}) return @@ -32,7 +32,7 @@ func (service *HTTPHandler) SaveProfile(ctx *gin.Context) { } func (service *HTTPHandler) ReadProfile(ctx *gin.Context) { - profile, err := service.ExternalServicesAdapter.ReadProfile(ctx.Param("id")) + profile, err := service.ServicesAdapter.ReadProfile(ctx.Param("id")) if err != nil { ctx.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return @@ -46,7 +46,7 @@ func (service *HTTPHandler) ReadProfiles(ctx *gin.Context) { ctx.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) return } - profiles, err := service.ExternalServicesAdapter.ReadProfiles() + profiles, err := service.ServicesAdapter.ReadProfiles() if err != nil { ctx.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return diff --git a/internal/adapters/handlers/users.handler.go b/internal/adapters/handlers/users.handler.go index c516ccb..a56381c 100644 --- a/internal/adapters/handlers/users.handler.go +++ b/internal/adapters/handlers/users.handler.go @@ -12,7 +12,7 @@ func (service *HTTPHandler) SaveUser(ctx *gin.Context) { if err := ctx.ShouldBindJSON(&user); err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": service.CompileErrors(err) }) } - if err := service.ExternalServicesAdapter.SaveUser( + if err := service.ServicesAdapter.SaveUser( domain.User{ Email: user.Email, Password: user.Password }, ); err != nil { ctx.JSON(http.StatusConflict, gin.H{"error": err.Error(), "status": false}) @@ -22,7 +22,7 @@ func (service *HTTPHandler) SaveUser(ctx *gin.Context) { } func (service *HTTPHandler) ReadUsers(ctx *gin.Context) { - result, err := service.ExternalServicesAdapter.ReadUsers() + result, err := service.ServicesAdapter.ReadUsers() if err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{ "error": err.Error(), "status": false}) return @@ -36,16 +36,8 @@ func (service *HTTPHandler) Login(ctx *gin.Context) { ctx.JSON(http.StatusBadRequest, gin.H{"error": service.CompileErrors(err), "status": false }) return } - user, err := service.InternalServicesAdapter.ReadUserByEmail(login.Email) - if err != nil { - ctx.JSON(http.StatusNotFound, gin.H{"error": err.Error(), "status": false}) - return - } - if err := user.ValidatePassword(login.Password); err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error(), "status": false }) - return - } - jwt, err := service.GenerateJWToken(*user) + + jwt, err := service.ServicesAdapter.Login(login) if err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": service.CompileErrors(err), "status": false }) return @@ -53,17 +45,17 @@ func (service *HTTPHandler) Login(ctx *gin.Context) { ctx.JSON(http.StatusOK, gin.H{"token": jwt, "status": true }) } -func (service *HTTPHandler) CurrentUser(ctx *gin.Context) (domain.User, error) { - token, err := service.ExtractToken(ctx) +func (service *HTTPHandler) currentUser(ctx *gin.Context) (domain.User, error) { + token, err := extractToken(ctx) if err != nil { return domain.User{}, err } - claim, err := service.ValidateJWToken(token); + claim, err := validateJWToken(token); if err != nil { return domain.User{}, err } UserID := claim["id"].(string) - user, err := service.ExternalServicesAdapter.ReadUser(UserID) + user, err := service.ServicesAdapter.ReadUser(UserID) if err != nil { return domain.User{}, err } diff --git a/internal/core/services/users.services.go b/internal/core/services/users.services.go index 3afc2d1..917d726 100644 --- a/internal/core/services/users.services.go +++ b/internal/core/services/users.services.go @@ -1,13 +1,19 @@ package services import ( + "os" + "strconv" + "time" "html" "strings" "github.com/satori/go.uuid" + "github.com/golang-jwt/jwt/v4" "golang.org/x/crypto/bcrypt" domain "server/internal/core/domain" ) +var privateKey = []byte(os.Getenv("JWT_PRIVATE_KEY")) + func (externalServiceHandler *ServicesHandler) SaveUser(user domain.User) error { user.ID = uuid.NewV4() hashedPassword, err := bcrypt.GenerateFromPassword( @@ -33,3 +39,24 @@ func (externalServiceHandler *ServicesHandler) ReadUsers() ([]*domain.User, erro func (externalServiceHandler *ServicesHandler) ReadUserByEmail(Email string) (*domain.User, error) { return externalServiceHandler.External.ReadUserByEmail(Email) } + +func (externalServiceHandler *ServicesHandler) Login(user domain.UserDto) (string, error) { + userByEmail, err := externalServiceHandler.External.ReadUserByEmail(user.Email) + if err != nil { + return "", err + } + if err := userByEmail.ValidatePassword(user.Password); err != nil { + return "", err + } + return externalServiceHandler.generateJWToken(userByEmail) +} + +func (externalServiceHandler *ServicesHandler) generateJWToken(user *domain.User) (string, error) { + tokenTTL, _ := strconv.Atoi(os.Getenv("TOKEN_TTL")) + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "id": user.ID, + "iat": time.Now().Unix(), + "eat": time.Now().Add(time.Second * time.Duration(tokenTTL)).Unix(), + }) + return token.SignedString(privateKey) +} diff --git a/main.go b/main.go index 98779d1..f8524d0 100644 --- a/main.go +++ b/main.go @@ -58,20 +58,20 @@ func InitializeRoutes() { // ProtectedRoutes Endpoints protectedRoutes := router.Group("/v1") - protectedRoutes.Use(httpHandler.JWTAuthMiddleware()) + protectedRoutes.Use(httpHandler.JWTAuthentication()) // Address Endpoints protectedRoutes.POST("/addresses", httpHandler.SaveAddress) protectedRoutes.GET("/addresses", httpHandler.ReadAddresses) - protectedRoutes.GET("/addresses/:id", httpHandler.UUidMiddleware(), httpHandler.ReadAddress) - protectedRoutes.PATCH("/addresses/:id", httpHandler.UUidMiddleware(), httpHandler.PatchAddress) - protectedRoutes.DELETE("/addresses/:id", httpHandler.UUidMiddleware(), httpHandler.DeleteAddress) + protectedRoutes.GET("/addresses/:id", httpHandler.UUIDMiddleware(), httpHandler.ReadAddress) + protectedRoutes.PATCH("/addresses/:id", httpHandler.UUIDMiddleware(), httpHandler.PatchAddress) + protectedRoutes.DELETE("/addresses/:id", httpHandler.UUIDMiddleware(), httpHandler.DeleteAddress) // Profile Endpoints protectedRoutes.POST("/profiles", httpHandler.SaveProfile) protectedRoutes.GET("/profiles", httpHandler.ReadProfiles) - protectedRoutes.GET("/profiles/:id", httpHandler.UUidMiddleware(), httpHandler.ReadProfile) - protectedRoutes.PATCH("/profiles/:id", httpHandler.UUidMiddleware(), httpHandler.PatchProfile) + protectedRoutes.GET("/profiles/:id", httpHandler.UUIDMiddleware(), httpHandler.ReadProfile) + protectedRoutes.PATCH("/profiles/:id", httpHandler.UUIDMiddleware(), httpHandler.PatchProfile) if err := router.Run("localhost:" + port); err != nil { return