From 73bc1972c7249391434ddc4ffe0d362b63b85ac0 Mon Sep 17 00:00:00 2001 From: Pau Costa Date: Fri, 12 Jul 2024 20:37:31 +0200 Subject: [PATCH] feat: add auth middleware, and update event post to make use of it --- functional-test/auth.http | 4 +- functional-test/events.http | 1 + functional-test/http-client.private.env.json | 6 +++ middleware/middleware.go | 49 ++++++++++++++++++++ models/user.go | 29 ++++++++++++ routes/events.routes.go | 3 +- routes/routes.go | 9 ++-- 7 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 functional-test/http-client.private.env.json create mode 100644 middleware/middleware.go diff --git a/functional-test/auth.http b/functional-test/auth.http index 7858faf..241ff00 100644 --- a/functional-test/auth.http +++ b/functional-test/auth.http @@ -16,4 +16,6 @@ content-type: application/json { "email": "test@example.com", "password": "examplePassword" -} \ No newline at end of file +} + +> {% client.global.set("auth_token", response.body.jwt); %} \ No newline at end of file diff --git a/functional-test/events.http b/functional-test/events.http index 9bf2bc3..82d2805 100644 --- a/functional-test/events.http +++ b/functional-test/events.http @@ -5,6 +5,7 @@ GET http://localhost:8080/events ### Post a new event POST http://localhost:8080/events/ content-type: application/json +Authorization: Bearer {{auth_token}} { "name": "My test event", diff --git a/functional-test/http-client.private.env.json b/functional-test/http-client.private.env.json new file mode 100644 index 0000000..8160c34 --- /dev/null +++ b/functional-test/http-client.private.env.json @@ -0,0 +1,6 @@ +{ + "dev": { + "name": "value", + "auth_token": "" + } +} \ No newline at end of file diff --git a/middleware/middleware.go b/middleware/middleware.go new file mode 100644 index 0000000..3040586 --- /dev/null +++ b/middleware/middleware.go @@ -0,0 +1,49 @@ +package middleware + +import ( + "fmt" + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" + "net/http" + "os" + "time" + "udemy_httpserver/models" +) + +func RequireAuth(c *gin.Context) { + // Get JWT from cookie + tokenString := c.GetHeader("Authorization") + if tokenString == "" { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) + c.Abort() + return + } + tokenString = tokenString[7:] // Remove "Bearer " from the token + // Decode/validate it + token, _ := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + // Don't forget to validate the alg is what you expect: + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } + + // hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key") + return []byte(os.Getenv("JWT_SECRET")), nil + }) + + if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { + // Check the expiry date + if float64(time.Now().Unix()) > claims["exp"].(float64) { + c.AbortWithStatus(http.StatusUnauthorized) + } + // Validate that the user exists in the database + user, err := models.FindOneByID(int(claims["sub"].(float64))) + if err != nil || user == nil { + c.AbortWithStatus(http.StatusUnauthorized) + } + c.Set("user", user) + c.Next() + } else { + c.AbortWithStatus(http.StatusUnauthorized) + } + +} diff --git a/models/user.go b/models/user.go index b76bcfe..299dc25 100644 --- a/models/user.go +++ b/models/user.go @@ -1,6 +1,7 @@ package models import ( + "database/sql" "fmt" "github.com/golang-jwt/jwt/v5" "golang.org/x/crypto/bcrypt" @@ -35,6 +36,28 @@ func FindOneByEmail(email string) (*User, error) { selectQuery := `SELECT * FROM users WHERE email = ?` dbResponse, err := db.Conn.Query(selectQuery, email) + defer closeDbConnection(dbResponse) + if err != nil { + fmt.Println("Something went wrong trying to find a user") + return nil, err + } + if dbResponse.Next() { + var user User + err = dbResponse.Scan(&user.ID, &user.Name, &user.Email, &user.Password) + if err != nil { + fmt.Println("Something went wrong parsing the database row") + return nil, err + } + return &user, nil + } + return nil, nil +} + +func FindOneByID(id int) (*User, error) { + selectQuery := `SELECT * FROM users WHERE id =?` + + dbResponse, err := db.Conn.Query(selectQuery, id) + defer closeDbConnection(dbResponse) if err != nil { fmt.Println("Something went wrong trying to find a user") return nil, err @@ -68,3 +91,9 @@ func (u *User) GetJWToken() (string, error) { } return tokenString, nil } +func closeDbConnection(dbResponse *sql.Rows) { + err := dbResponse.Close() + if err != nil { + fmt.Println("Something went wrong closing the database row") + } +} diff --git a/routes/events.routes.go b/routes/events.routes.go index 8912ee6..7e1b21f 100644 --- a/routes/events.routes.go +++ b/routes/events.routes.go @@ -15,7 +15,8 @@ func postEvent(c *gin.Context) { return } // Todo: implement user auth - event.UserID = 0 + user, _ := c.Get("user") + event.UserID = user.(*models.User).ID err = event.Save() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) diff --git a/routes/routes.go b/routes/routes.go index 9d42023..d8fd4b2 100644 --- a/routes/routes.go +++ b/routes/routes.go @@ -1,12 +1,15 @@ package routes -import "github.com/gin-gonic/gin" +import ( + "github.com/gin-gonic/gin" + "udemy_httpserver/middleware" +) func RegisterRoutes(router *gin.Engine) { - router.POST("/events", postEvent) + router.POST("/events", middleware.RequireAuth, postEvent) router.GET("/events", getEvents) router.GET("/events/:id", getSingleEvent) - router.PUT("/events/:id", updateEvent) + router.PUT("/events/:id", middleware.RequireAuth, updateEvent) router.POST("/auth/register", registerUser) router.POST("/auth/login", Login)