Skip to content

Commit

Permalink
added jwt middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
shayja committed Dec 4, 2024
1 parent 084a7b2 commit f9f53de
Show file tree
Hide file tree
Showing 21 changed files with 430 additions and 18 deletions.
13 changes: 13 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Database credentials
DB_HOST="<<DB_HOST>>"
DB_PORT="<<DB_PORT>>"
DB_USER="<<DB_USER>>"
DB_PASSWORD="<<DB_PASSWORD>>"
DB_NAME=shop
SSL_MODE=disable
PGADMIN_DEFAULT_EMAIL="<<PGADMIN_ADMIN_USER_EMAIL>>"
PGADMIN_DEFAULT_PASSWORD="<<PGADMIN_ADMIN_PASSWORD>>"

# Authentication credentials
TOKEN_TTL="3000"
JWT_PRIVATE_KEY="<<VERY_STRONG_KEY>>"
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Database:

1. Create a new Postgres DB, choose "shop" as database name, create a new user "appuser" and set a login password.
2. Exceute the sql script from project path /scripts on "shop" database.
3. Set your DB credentials in .env file.
3. Set your DB credentials in .env.local file and rename the file to '.env'.
4. Add new .env file in project root with this content, change the password configuration value:

# Database settings:
Expand All @@ -30,6 +30,10 @@ DB_PASSWORD="<YOUR_PASSWORD>"
DB_NAME="shop"
DB_PORT=5432

configure the Postgres admin user credentials:
PGADMIN_DEFAULT_EMAIL="your@admmin.email.here"
PGADMIN_DEFAULT_PASSWORD="<<PGADMIN_ADMIN_PASSWORD>>"

App endpoints:

GET
Expand Down
43 changes: 29 additions & 14 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/shayja/go-template-api/controller"
"github.com/shayja/go-template-api/db"
"github.com/shayja/go-template-api/middleware"
)

type App struct {
Expand All @@ -25,21 +26,35 @@ const (
)

func (app *App) Routes() {
r := gin.Default()
r.SetTrustedProxies([]string{"127.0.0.1"})
router := gin.Default()
fmt.Println(gin.Version)
controller := controller.CreateProductController(app.DB)
baseUrl := fmt.Sprintf("%s/v%d/product", prefix, api_ver)
r.POST(baseUrl, controller.Create)
r.GET(baseUrl, controller.GetAll)
r.GET(fmt.Sprintf("%s/:id", baseUrl), controller.GetSingle)
r.PUT(fmt.Sprintf("%s/:id", baseUrl), controller.Update)
r.PATCH(fmt.Sprintf("%s/:id", baseUrl), controller.UpdatePrice)
r.POST(fmt.Sprintf("%s/image/:id", baseUrl), controller.UpdateImage)
r.DELETE(fmt.Sprintf("%s/:id", baseUrl), controller.Delete)
app.Router = r
router.SetTrustedProxies([]string{"127.0.0.1"})

baseUrl := fmt.Sprintf("%s/v%d/", prefix, api_ver)
productController := controller.CreateProductController(app.DB)
userController := controller.CreateUserController(app.DB)


publicRoutes := router.Group(baseUrl+"/auth")
publicRoutes.POST("/register", userController.Register)
publicRoutes.POST("/login", userController.Login)

protectedRoutes := router.Group(baseUrl+"/product")
protectedRoutes.Use(middleware.JWTAuthMiddleware())

protectedRoutes.POST("", productController.Create)
protectedRoutes.GET("", productController.GetAll)
protectedRoutes.GET(":id", productController.GetSingle)
protectedRoutes.PUT(":id", productController.Update)
protectedRoutes.PATCH(":id", productController.UpdatePrice)
protectedRoutes.POST("/image/:id", productController.UpdateImage)
protectedRoutes.DELETE(":id", productController.Delete)

// Register Routes
app.Router = router
}

func (a *App) Run() {
a.Router.Run(":8080")

func (app *App) Run() {
app.Router.Run(":8080")
}
84 changes: 84 additions & 0 deletions controller/authentication.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package controller

import (
"database/sql"
"net/http"

"github.com/gin-gonic/gin"
"github.com/shayja/go-template-api/helper"
"github.com/shayja/go-template-api/model"
repository "github.com/shayja/go-template-api/repository/user"
"github.com/shayja/go-template-api/utils"
)

type AuthenticationController struct {
Db *sql.DB
}

func CreateUserController(db *sql.DB) AuthenticationInterface {
return &AuthenticationController{Db: db}
}

func (m *AuthenticationController) Register(c *gin.Context) {

AddRequestHeader(c)
DB := m.Db

var userReq model.User
if err := c.ShouldBindJSON(&userReq); err != nil {

c.JSON(http.StatusBadRequest, gin.H{"status": "failed", "msg": err})
return
}

repository := repository.NewUserRepository(DB)

insertedId, err := repository.Create(userReq)

if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"status": "failed", "msg": err})
return
}

if utils.IsValidUUID(insertedId) {
c.JSON(http.StatusCreated, gin.H{"status": "success", "msg": nil, "id": insertedId})
} else {
c.JSON(http.StatusInternalServerError, gin.H{"status": "failed", "msg": "insert product failed"})
}
}

func (m *AuthenticationController) Login(c *gin.Context) {
var input model.AuthenticateRequest

if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"status": "failed", "msg": err})
return
}

AddRequestHeader(c)
DB := m.Db

repository := repository.NewUserRepository(DB)

user, err := repository.GetByUsername(input.Username)

if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"status": "failed", "msg": err})
return
}

err = repository.ValidatePassword(user, input.Password)

if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"status": "failed", "msg": err})
return
}

jwt, err := helper.GenerateJWT(user)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"status": "failed", "msg": err})
return
}

c.JSON(http.StatusOK, gin.H{"jwt": jwt})
}
9 changes: 9 additions & 0 deletions controller/authentication_interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package controller

import "github.com/gin-gonic/gin"

type AuthenticationInterface interface {
Login(*gin.Context)
Register(*gin.Context)

}
2 changes: 1 addition & 1 deletion controller/product.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

"github.com/gin-gonic/gin"
"github.com/shayja/go-template-api/model"
"github.com/shayja/go-template-api/repository"
repository "github.com/shayja/go-template-api/repository/product"
"github.com/shayja/go-template-api/utils"
)

Expand Down
2 changes: 1 addition & 1 deletion db/connectdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,6 @@ func OpenDBConnection() (*sql.DB) {
panic(err)
}

fmt.Printf("The database `%s` is successfully connected!", v.DBName)
fmt.Printf("Successfully connected to the database `%s`", v.DBName)
return db
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ require (
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.23.0 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/golang-jwt/jwt/v4 v4.5.1
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL
github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
Expand Down
91 changes: 91 additions & 0 deletions helper/jwt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package helper

import (
"database/sql"
"errors"
"fmt"
"log"
"os"
"strconv"
"strings"
"time"

"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v4"
"github.com/shayja/go-template-api/model"
"github.com/shayja/go-template-api/service"
)

var privateKey = []byte(os.Getenv("JWT_PRIVATE_KEY"))

type JwtHelper struct {
Db *sql.DB
}

func GenerateJWT(user model.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 ValidateJWT(context *gin.Context) error {
token, err := getToken(context)

if err != nil {
return err
}

_, ok := token.Claims.(jwt.MapClaims)

if ok && token.Valid {
return nil
}

return errors.New("invalid client token provided")
}


func(m *JwtHelper) CurrentUser(context *gin.Context) (model.User, error) {
err := ValidateJWT(context)
if err != nil {
return model.User{}, err
}

token, _ := getToken(context)
claims, _ := token.Claims.(jwt.MapClaims)
userId := string(claims["id"].(string))


service := service.CreateUserService(m.Db)

user, err := service.GetById(userId)
if err != nil {
return model.User{}, err
}
return user, nil
}

func getToken(context *gin.Context) (*jwt.Token, error) {
tokenString := getTokenFromRequest(context)
token, err := jwt.Parse(tokenString, 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 getTokenFromRequest(context *gin.Context) string {
bearerToken := context.Request.Header.Get("Authorization")
splitToken := strings.Split(bearerToken, " ")
if len(splitToken) == 2 {
return splitToken[1]
}
return ""
}
Binary file added images/d6a1798b964a4a5aa710b34bccb16191.JPG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions middleware/jwtAuth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package middleware

import (
"fmt"
"net/http"

"github.com/gin-gonic/gin"
"github.com/shayja/go-template-api/helper"
)

func JWTAuthMiddleware() gin.HandlerFunc {
return func(context *gin.Context) {
err := helper.ValidateJWT(context)
if err != nil {
context.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication required"})
fmt.Println(err)
context.Abort()
return
}
context.Next()
}
}
6 changes: 6 additions & 0 deletions model/authetication.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package model

type AuthenticateRequest struct {
Username string `json:"username" validate:"required"`
Password string `json:"password" validate:"required"`
}
14 changes: 14 additions & 0 deletions model/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package model

import "time"

type User struct {
Id string `json:"id"`
Username string `json:"username" validate:"required"`
Password string `json:"passhash" validate:"required"`
Mobile string `json:"mobile"`
Name string `json:"name"`
Email string `json:"email"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (

type ProductRepositoryInterface interface {
GetAll(page int)([]model.Product, error)
GetSingle(string) (model.Product, error)
GetSingle(id string) (model.Product, error)
Create(model.ProductRequest) (string, error)
Update(string, model.ProductRequest) (model.Product, error)
UpdatePrice(id string, post model.ProductRequestPrice) (model.Product, error)
Expand Down
Loading

0 comments on commit f9f53de

Please sign in to comment.