From f1429e07ea628a126a1b715f648d630115e52790 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd-Ren=C3=A9=20Predota?= Date: Tue, 18 May 2021 11:40:34 +0200 Subject: [PATCH] authentication middleware added --- README.md | 2 +- api/v1/users/route.go | 9 +++-- api/v1/users/users.go | 32 ++++++++++++++---- cmd/server/server.go | 37 ++++++++++++++------- lib/authentication/authentication.go | 21 ++++++++++++ lib/cache/cache.go | 7 ++-- lib/middlewares/authentication.go | 49 ++++++++++++++++++++++++++++ 7 files changed, 133 insertions(+), 24 deletions(-) create mode 100644 lib/authentication/authentication.go create mode 100644 lib/middlewares/authentication.go diff --git a/README.md b/README.md index 8b863da..9e55ca6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # golang API Skeleton -[![Build Status](https://drone.devices.local/api/badges/mawas/golang-api-skeleton/status.svg)](https://drone.devices.local/mawas/golang-api-skeleton)![gopherbadger-tag-do-not-edit](https://img.shields.io/badge/Go%20Coverage-69%25-brightgreen.svg?longCache=true&style=flat) +[![Build Status](https://drone.devices.local/api/badges/mawas/golang-api-skeleton/status.svg)](https://drone.devices.local/mawas/golang-api-skeleton)![gopherbadger-tag-do-not-edit](https://img.shields.io/badge/Go%20Coverage-77%25-brightgreen.svg?longCache=true&style=flat) refined skeleton future apis should be based on diff --git a/api/v1/users/route.go b/api/v1/users/route.go index a902e41..af5932a 100644 --- a/api/v1/users/route.go +++ b/api/v1/users/route.go @@ -1,14 +1,17 @@ package users -import "github.com/gin-gonic/gin" +import ( + "git.devices.local/mawas/golang-api-skeleton/lib/authentication" + "github.com/gin-gonic/gin" +) // ApplyRoutes applies router to the gin Engine func ApplyRoutes(r *gin.RouterGroup) *gin.RouterGroup { users := r.Group("/users") { // actions.POST("", middlewares.Authorized, CreateActions) - // actions.GET("/:id", middlewares.Authorized, ReadAction) - users.GET("/:username", Read) + users.GET("/:username", authentication.Authorized, ReadByID) + users.GET("", authentication.Authorized, ReadAll) // actions.GET("/:id/logs", middlewares.Authorized, actionlogs.ReadActionLogs) // actions.PATCH("/:id", middlewares.Authorized, UpdateAction) // actions.PATCH("", middlewares.Authorized, UpdateActions) diff --git a/api/v1/users/users.go b/api/v1/users/users.go index e5337dd..57f05c5 100644 --- a/api/v1/users/users.go +++ b/api/v1/users/users.go @@ -9,19 +9,37 @@ import ( "gorm.io/gorm" ) -func Read(c *gin.Context) { - db := c.MustGet("db").(*gorm.DB) - cc := c.MustGet("cache").(cache.Cache) - response := response.Envelope{ +func prepare(c *gin.Context) (response.Envelope, *services.UserService) { + resp := response.Envelope{ RequestID: c.MustGet("requestID").(string), } - userRepo := repositories.NewUserRepository(db, "01F5FSJXDHWT4HK93B9NB8V5G4", "test", cc) + userRepo := repositories.NewUserRepository( + c.MustGet("db").(*gorm.DB), + c.MustGet("userID").(string), + c.MustGet("username").(string), + c.MustGet("cache").(cache.Cache), + ) userService := services.NewUserService(userRepo) + return resp, userService +} + +func ReadByID(c *gin.Context) { + resp, userService := prepare(c) username := c.Param("username") user, err := userService.ReadByID(username) if err != nil { - c.AbortWithStatusJSON(500, response.AppendError(err)) + c.AbortWithStatusJSON(500, resp.AppendError(err)) + return + } + c.JSON(200, resp.SetSuccess(user)) +} + +func ReadAll(c *gin.Context) { + resp, userService := prepare(c) + users, err := userService.ReadAll() + if err != nil { + c.AbortWithStatusJSON(500, resp.AppendError(err)) return } - c.JSON(200, response.SetSuccess(user)) + c.JSON(200, resp.SetSuccess(users)) } diff --git a/cmd/server/server.go b/cmd/server/server.go index 6bd0e11..18110f0 100644 --- a/cmd/server/server.go +++ b/cmd/server/server.go @@ -71,23 +71,38 @@ func startServer(version string, appName string, cfgFile string, flagCfg map[str } // Prefill cache // TODO also add deleted users to cache for old references + // users userRepo := repositories.NewUserRepository(db, common.CLIUserID, common.CLIUsername, nil) userService := services.NewUserService(userRepo) - if userData, err := userService.ReadAll(); err != nil { - } else { - for i := range userData { - if err := c.Set("user:"+userData[i].ID.String(), userData[i].Username); err != nil { // userID -> username mapping - return fmt.Errorf("cache:%v", err) - } - if err := c.Set("user:"+userData[i].Username, userData[i].ID.String()); err != nil { // username -> userID mapping - return fmt.Errorf("cache:%v", err) - } + userData, err := userService.ReadAll() + if err != nil { + return fmt.Errorf("cache:%v", err) + } + for i := range userData { + if err := c.Set("user:"+userData[i].ID.String(), userData[i].Username); err != nil { // userID -> username mapping + return fmt.Errorf("cache:%v", err) + } + if err := c.Set("user:"+userData[i].Username, userData[i].ID.String()); err != nil { // username -> userID mapping + return fmt.Errorf("cache:%v", err) + } + } + // tokens + tokenRepo := repositories.NewTokenRepository(db, common.CLIUserID, common.CLIUsername, nil) + tokenService := services.NewTokenService(tokenRepo) + tokenData, err := tokenService.ReadAll() + if err != nil { + return fmt.Errorf("cache:%v", err) + } + for i := range tokenData { + if err := c.Set("token:"+tokenData[i].Token.String(), tokenData[i].UserID.String()); err != nil { // token -> userID mapping + return fmt.Errorf("cache:%v", err) } } - app.Use(middlewares.RequestID()) - app.Use(middlewares.VersionHeader(version)) app.Use(inject("cache", c)) app.Use(inject("db", db)) + app.Use(middlewares.RequestID()) + app.Use(middlewares.Authentication()) + app.Use(middlewares.VersionHeader(version)) // FIXME find out where todo database migration if err := db.AutoMigrate(&models.Token{}); err != nil { return fmt.Errorf("database:%v", err) diff --git a/lib/authentication/authentication.go b/lib/authentication/authentication.go new file mode 100644 index 0000000..24eef4a --- /dev/null +++ b/lib/authentication/authentication.go @@ -0,0 +1,21 @@ +package authentication + +import ( + "errors" + "fmt" + + "git.devices.local/mawas/golang-api-skeleton/lib/response" + "github.com/gin-gonic/gin" +) + +// Authorized blocks unauthorized requestors +func Authorized(c *gin.Context) { + var resp response.Envelope + userID, exists := c.Get("userID") + if !exists { + c.AbortWithStatusJSON(403, resp.AppendError(errors.New("unauthorized"))) + return + } + fmt.Println("permission check", userID) + // TODO add cache perm check here +} diff --git a/lib/cache/cache.go b/lib/cache/cache.go index 7889ac7..c5932bd 100644 --- a/lib/cache/cache.go +++ b/lib/cache/cache.go @@ -63,8 +63,11 @@ func (cache bCache) Get(key string) (*string, error) { }); err != nil { return nil, err } - r := string(result) - return &r, nil + if len(result) > 0 { + r := string(result) + return &r, nil + } + return nil, nil } // SetWithTTL sets a cache entry diff --git a/lib/middlewares/authentication.go b/lib/middlewares/authentication.go new file mode 100644 index 0000000..408139c --- /dev/null +++ b/lib/middlewares/authentication.go @@ -0,0 +1,49 @@ +package middlewares + +import ( + "strings" + + "git.devices.local/mawas/golang-api-skeleton/lib/cache" + "github.com/gin-gonic/gin" +) + +func Authentication() gin.HandlerFunc { + return func(c *gin.Context) { + tokenString, err := c.Cookie("token") + appCache := c.MustGet("cache").(cache.Cache) + // failed to read cookie + if err != nil { + // try reading HTTP Header + authorization := c.Request.Header.Get("Authorization") + if authorization == "" { + c.Next() + return + } + sp := strings.Split(authorization, "Bearer ") + // invalid token + if len(sp) < 2 { + c.Next() + return + } + tokenString = sp[1] + } + // https://datatracker.ietf.org/doc/rfc8959/?include_text=1 + userID, err := appCache.Get("token:" + tokenString) + if err != nil { + c.Next() + return + } + if userID != nil { + username, err := appCache.Get("user:" + *userID) + if err != nil { + c.Next() + return + } + if username != nil { + c.Set("username", *username) + } + c.Set("userID", *userID) + } + c.Next() + } +}