Skip to content

Commit

Permalink
Role Based Auth
Browse files Browse the repository at this point in the history
  • Loading branch information
harshsinghvi committed Nov 19, 2023
1 parent 4eb0b96 commit 015f63a
Show file tree
Hide file tree
Showing 11 changed files with 129 additions and 31 deletions.
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
docker-compose/postgres
.env
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
- scale down to zero (Coldstart)
- api rate limiting and security
- ~~ API Auth and Access Logs ~~
- access token roles
- ~~access token roles~~
- API Billing
- API analytics

Expand Down
65 changes: 53 additions & 12 deletions controllers/controllers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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,
})
}

Expand All @@ -70,20 +79,22 @@ 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)
return
}

c.JSON(http.StatusCreated, gin.H{
"data": newTodo,
"status": http.StatusCreated,
"message": "Todo created Successfully",
})
Expand All @@ -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",
Expand Down Expand Up @@ -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
}

Expand All @@ -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
Expand Down Expand Up @@ -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",
})
}
35 changes: 34 additions & 1 deletion database/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
}
2 changes: 1 addition & 1 deletion k8s-deployments/secrets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ metadata:
labels:
app: app-name
data:
.dockerconfigjson: eyAiYXV0aHMiOiB7ICJnaGNyLmlvIjogeyAiYXV0aCI6ImFHRnljMmh6YVc1bmFIWnBPbWRvY0Y5UU1FOHdZbXRXU0VwMVYzbHJObWRPVnpaQ1lWYzRRMFY2Y25sTFVXZ3hkRkp1UlVrPSIgfSB9IH0=
.dockerconfigjson: ewogICAgImF1dGhzIjoKICAgIHsKICAgICAgICAiZ2hjci5pbyI6CiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJhdXRoIjoiYUdGeWMyaHphVzVuYUhacE9tZG9jRjlSVDNvNGNsWnJaRlp5Y0ZweGRIQkVZVUU0UVhCck5FWXdOMWhvU0dJd1VsUk1SV2c9IgogICAgICAgICAgICB9CiAgICB9Cn0=

# App Secrets
---
Expand Down
2 changes: 1 addition & 1 deletion k8s-eks-system/.dockerconfigjson
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{
"ghcr.io":
{
"auth":"aGFyc2hzaW5naHZpOmdocF9QME8wYmtWSEp1V3lrNmdOVzZCYVc4Q0V6cnlLUWgxdFJuRUk="
"auth":"aGFyc2hzaW5naHZpOmdocF9RT3o4clZrZFZycFpxdHBEYUE4QXBrNEYwN1hoSGIwUlRMRWg="
}
}
}
21 changes: 9 additions & 12 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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)
}
}

Expand Down
7 changes: 5 additions & 2 deletions middlewares/middlewares.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand All @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions models/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}

Expand Down
21 changes: 21 additions & 0 deletions models/roles/roles.go
Original file line number Diff line number Diff line change
@@ -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
}
2 changes: 1 addition & 1 deletion utils/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}

0 comments on commit 015f63a

Please sign in to comment.