From 0f8ee62412b6fa8f5fa40102ac82fc40123b19c8 Mon Sep 17 00:00:00 2001 From: Harsh Singhvi Date: Sun, 19 Nov 2023 05:35:36 +0530 Subject: [PATCH] Role Based Auth --- controllers/controllers.go | 65 +++++++++++++++++++++++++++++++------- database/database.go | 35 +++++++++++++++++++- main.go | 21 ++++++------ middlewares/middlewares.go | 7 ++-- models/auth.go | 2 ++ models/roles/roles.go | 21 ++++++++++++ utils/error.go | 2 +- 7 files changed, 125 insertions(+), 28 deletions(-) create mode 100644 models/roles/roles.go diff --git a/controllers/controllers.go b/controllers/controllers.go index 511dec8..15d1efd 100644 --- a/controllers/controllers.go +++ b/controllers/controllers.go @@ -6,8 +6,8 @@ import ( guuid "github.com/google/uuid" "harshsinghvi/golang-postgres-kubernetes/database" "harshsinghvi/golang-postgres-kubernetes/models" + "harshsinghvi/golang-postgres-kubernetes/models/roles" "harshsinghvi/golang-postgres-kubernetes/utils" - "log" "net/http" "time" ) @@ -51,15 +51,24 @@ func GetAllTodos(c *gin.Context) { func GetSingleTodo(c *gin.Context) { todoId := c.Param("id") - todo := &models.Todo{ID: todoId} - if err := database.Connection.Select(todo); err != nil { - utils.InternalServerError(c, "Error while getting a single todo, Reason:", err) + var todos []models.Todo + querry := database.Connection.Model(&todos).Where("id = ?", todoId) + if count, _ := querry.Count(); count == 1 { + if err := querry.Select(); err != nil { + utils.InternalServerError(c, "Error while getting a single todo, Reason:", err) + return + } + c.JSON(http.StatusOK, gin.H{ + "status": http.StatusOK, + "message": "Single Todo", + "data": todos, + }) return } c.JSON(http.StatusOK, gin.H{ "status": http.StatusOK, "message": "Single Todo", - "data": todo, + "data": todos, }) } @@ -70,13 +79,14 @@ func CreateTodo(c *gin.Context) { text := todo.Text id := guuid.New().String() - insertError := database.Connection.Insert(&models.Todo{ + newTodo := models.Todo{ ID: id, Text: text, Completed: false, CreatedAt: time.Now(), UpdatedAt: time.Now(), - }) + } + insertError := database.Connection.Insert(&newTodo) if insertError != nil { utils.InternalServerError(c, "Error while inserting new todo into db, Reason:", insertError) @@ -84,6 +94,7 @@ func CreateTodo(c *gin.Context) { } c.JSON(http.StatusCreated, gin.H{ + "data": newTodo, "status": http.StatusCreated, "message": "Todo created Successfully", }) @@ -106,7 +117,6 @@ func EditTodo(c *gin.Context) { } if res.RowsAffected() == 0 { - log.Printf("Error while update todo, Reason: \n") c.JSON(http.StatusNotFound, gin.H{ "status": http.StatusNotFound, "message": "Todo not found", @@ -142,13 +152,14 @@ func CreateNewToken(c *gin.Context) { insertError := database.Connection.Insert(&models.AccessToken{ ID: id, Token: token, - Email: accessToken.Email, // TODO: validate Email + Email: accessToken.Email, + Roles: []string{roles.Read, roles.Write}, Expiry: time.Now().AddDate(0, 0, 10), CreatedAt: time.Now(), }) if insertError != nil { - utils.InternalServerError(c, "Error while inserting new todo into db, Reason:", insertError) + utils.InternalServerError(c, "Error while inserting new token into db, Reason:", insertError) return } @@ -159,8 +170,6 @@ func CreateNewToken(c *gin.Context) { }) } -// Get tokens by email - func GetTokens(c *gin.Context) { email := c.Param("email") var pag models.Pagination @@ -196,3 +205,35 @@ func GetTokens(c *gin.Context) { "pagination": pag.Validate(), }) } + +func UpdateToken(c *gin.Context) { + id := c.Param("id") + var accessToken models.AccessToken + c.Bind(&accessToken) + + if accessToken.Roles == nil { + c.JSON(http.StatusBadRequest, gin.H{ + "status": http.StatusBadRequest, + "message": "Token not Udpated include data in req body", + }) + } + + querry := database.Connection.Model(&models.AccessToken{}).Set("roles = ?", accessToken.Roles).Set("updated_at = ?", time.Now()) + + res, err := querry.Where("id = ?", id).Update() + if err != nil { + utils.InternalServerError(c, "Error while editing token, Reason:", err) + } + if res.RowsAffected() == 0 { + c.JSON(http.StatusNotFound, gin.H{ + "status": http.StatusNotFound, + "message": "Token not found", + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "status": 200, + "message": "Token Edited Successfully", + }) +} diff --git a/database/database.go b/database/database.go index 47443ce..7af4c37 100644 --- a/database/database.go +++ b/database/database.go @@ -4,8 +4,10 @@ import ( "github.com/go-pg/pg/v9" orm "github.com/go-pg/pg/v9/orm" "harshsinghvi/golang-postgres-kubernetes/models" + "harshsinghvi/golang-postgres-kubernetes/models/roles" "harshsinghvi/golang-postgres-kubernetes/utils" "log" + "time" ) var Connection *pg.DB @@ -83,11 +85,42 @@ func CreateTables() error { log.Printf("Error while creating access_logs table, Reason: %v\n", createError) return createError } - // TODO if _, err := Connection.Exec(`CREATE UNIQUE INDEX IF NOT EXISTS go_index_access_logs ON access_logs(token, path, method, response_time, status_code, server_hostname, created_at);`); err != nil { log.Println(err.Error()) return err } log.Printf("Todo table and indexes created") + + checkAndCreateAdminToken() return nil } + +func checkAndCreateAdminToken() { + var accessToken models.AccessToken + querry := Connection.Model(&accessToken).Where("id = ?", "admin") + count, err := querry.Count() + if err != nil { + log.Println("Error in getting access_token count") + } + if count != 0 { + return + } + + id := "admin" + token := utils.GenerateToken(id) + + insertError := Connection.Insert(&models.AccessToken{ + ID: id, + Token: token, + Email: id, + Expiry: time.Now().AddDate(99, 0, 00), + CreatedAt: time.Now(), + Roles: []string{roles.Admin}, + }) + + if insertError != nil { + log.Printf("Error while inserting new token into db, Reason: %v\n", insertError) + } + + log.Printf("Admin Token created") +} diff --git a/main.go b/main.go index 872ec26..cbad8db 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( "harshsinghvi/golang-postgres-kubernetes/controllers_old" "harshsinghvi/golang-postgres-kubernetes/database" "harshsinghvi/golang-postgres-kubernetes/middlewares" + "harshsinghvi/golang-postgres-kubernetes/models/roles" "log" "net/http" ) @@ -51,19 +52,15 @@ func main() { v2 := api.Group("/v2") { - v2.POST("/token", controllers.CreateNewToken) - v2.GET("/token/:email", controllers.GetTokens) - - todo := v2.Group("/todo") - { - todo.Use(middlewares.AuthMiddleware()) - todo.GET("/", controllers.GetAllTodos) - todo.GET("/:id", controllers.GetSingleTodo) - todo.POST("/", controllers.CreateTodo) - todo.PUT("/:id", controllers.EditTodo) - todo.DELETE("/:id", controllers.DeleteTodo) - } + v2.POST("/token", middlewares.AuthMiddleware([]string{roles.Admin}), controllers.CreateNewToken) + v2.GET("/token/:email", middlewares.AuthMiddleware([]string{roles.Admin}), controllers.GetTokens) + v2.PUT("/token/:id", middlewares.AuthMiddleware([]string{roles.Admin}), controllers.UpdateToken) + v2.GET("/todo/", middlewares.AuthMiddleware([]string{roles.Admin, roles.Read}), controllers.GetAllTodos) + v2.GET("/todo/:id", middlewares.AuthMiddleware([]string{roles.Admin, roles.Read, roles.ReadOne}), controllers.GetSingleTodo) + v2.POST("/todo/", middlewares.AuthMiddleware([]string{roles.Admin, roles.Write, roles.WriteNewOnly}), controllers.CreateTodo) + v2.PUT("/todo/:id", middlewares.AuthMiddleware([]string{roles.Admin, roles.Write, roles.WriteUpdateOnly}), controllers.EditTodo) + v2.DELETE("/todo/:id", middlewares.AuthMiddleware([]string{roles.Admin, roles.Write}), controllers.DeleteTodo) } } diff --git a/middlewares/middlewares.go b/middlewares/middlewares.go index d4cde53..3f54c2f 100644 --- a/middlewares/middlewares.go +++ b/middlewares/middlewares.go @@ -5,13 +5,14 @@ import ( guuid "github.com/google/uuid" "harshsinghvi/golang-postgres-kubernetes/database" "harshsinghvi/golang-postgres-kubernetes/models" + "harshsinghvi/golang-postgres-kubernetes/models/roles" "harshsinghvi/golang-postgres-kubernetes/utils" "log" "os" "time" ) -func AuthMiddleware() gin.HandlerFunc { +func AuthMiddleware(requiredRoles []string) gin.HandlerFunc { return func(c *gin.Context) { reqStart := time.Now() var accessToken models.AccessToken @@ -46,7 +47,8 @@ func AuthMiddleware() gin.HandlerFunc { return } - if time.Until(accessToken.Expiry).Seconds() <= 0 { + if time.Until(accessToken.Expiry).Seconds() <= 0 || + !roles.CheckRoles(requiredRoles, accessToken.Roles) { utils.UnauthorizedResponse(c) return } @@ -57,6 +59,7 @@ func AuthMiddleware() gin.HandlerFunc { if hostname, err = os.Hostname(); err != nil { log.Printf("Error loading system hostname %v\n", err) } + insertError := database.Connection.Insert(&models.AccessLog{ ID: reqId, Token: accessToken.Token, diff --git a/models/auth.go b/models/auth.go index 9476661..f2a602b 100644 --- a/models/auth.go +++ b/models/auth.go @@ -6,7 +6,9 @@ type AccessToken struct { ID string `json:"id"` Email string `json:"email"` Token string `json:"token"` + Roles []string `json:"roles"` // read, read-one, write, write-new-only, write-update-only Expiry time.Time `json:"expiry"` + UpdatedAt time.Time `json:"updated_at"` CreatedAt time.Time `json:"created_at"` } diff --git a/models/roles/roles.go b/models/roles/roles.go new file mode 100644 index 0000000..3f18d5b --- /dev/null +++ b/models/roles/roles.go @@ -0,0 +1,21 @@ +package roles + +const ( + Admin = "admin" + Read = "read" + ReadOne = "read_one" + Write = "write" + WriteNewOnly = "write_new_only" + WriteUpdateOnly = "write_update_only" +) + +func CheckRoles(requiredRoles []string, grantedRoles []string) bool { + for _, requiredRole := range requiredRoles { + for _, grantedRole := range grantedRoles { + if grantedRole == requiredRole { + return true + } + } + } + return false +} diff --git a/utils/error.go b/utils/error.go index 52319ab..a040a44 100644 --- a/utils/error.go +++ b/utils/error.go @@ -17,7 +17,7 @@ func InternalServerError(c *gin.Context, msg string, err error) { func UnauthorizedResponse(c *gin.Context) { c.JSON(http.StatusUnauthorized, gin.H{ "status": http.StatusUnauthorized, - "message": "Invalid or Expired Token, please check token or include tokens in the headers", + "message": "Invalid or Expired Token or no enough permission, please check token or include tokens in the headers", }) c.Abort() }