From 1db200435ec7cb57ac9ddc72eb98253f36db51d4 Mon Sep 17 00:00:00 2001 From: ahmadafriadi Date: Fri, 27 Jun 2025 11:17:20 +0700 Subject: [PATCH] Update JWT --- .gitignore | 2 ++ Dockerfile | 16 ++++++++++++++ api/routes.go | 5 +++++ cmd/main.go | 3 +++ config/config.go | 44 +++++++++++++++++++++++++++++++------ database.txt | 2 +- database/database.go | 16 +++++++++----- docker-compose.yml | 10 +++++++++ go.mod | 1 + go.sum | 2 ++ middlewares/jwt.go | 17 +++++++++++--- modules/auth/service/jwt.go | 24 ++++++++------------ 12 files changed, 110 insertions(+), 32 deletions(-) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f8b0b28 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Ignore environment config +.env \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..15d2af2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +# Dockerfile +FROM golang:1.24.4 + +WORKDIR /app + +COPY go.mod ./ +COPY go.sum ./ +RUN go mod download + +COPY . . + +RUN go build -o main ./cmd/main.go + +EXPOSE 3000 + +CMD ["./main"] \ No newline at end of file diff --git a/api/routes.go b/api/routes.go index ba5ec5a..3bc915f 100644 --- a/api/routes.go +++ b/api/routes.go @@ -1,6 +1,7 @@ package api import ( + "BE-MiniERP/middlewares" "BE-MiniERP/modules/auth" "BE-MiniERP/modules/inventory" "BE-MiniERP/modules/sales" @@ -10,6 +11,10 @@ import ( func SetupRoutes(app *fiber.App) { auth.RegisterRoutes(app.Group("/auth")) + + app.Use("/inventory", middlewares.JWTProtected()) + app.Use("/sales", middlewares.JWTProtected()) + inventory.RegisterRoutes(app.Group("/inventory")) sales.RegisterRoutes(app.Group("/sales")) } diff --git a/cmd/main.go b/cmd/main.go index b66cde3..0d32aec 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -4,12 +4,15 @@ import ( "log" "BE-MiniERP/api" + "BE-MiniERP/config" "BE-MiniERP/database" // pastikan ini sesuai dengan module di go.mod "github.com/gofiber/fiber/v2" ) func main() { + // Inisialisasi konfigurasi + config.InitConfig() // Koneksi database database.Connect() diff --git a/config/config.go b/config/config.go index a2d6483..99061c7 100644 --- a/config/config.go +++ b/config/config.go @@ -1,15 +1,45 @@ package config -import "os" +import ( + "log" + "os" + + "github.com/joho/godotenv" +) type Config struct { - DBUrl string - JWTSecret string + JWTSecret string + DBHost string + DBPort string + DBUser string + DBPassword string + DBName string + DBSSLMode string } -func GetConfig() Config { - return Config{ - DBUrl: os.Getenv("DB_URL"), - JWTSecret: os.Getenv("JWT_SECRET"), +var appConfig = &Config{} + +func InitConfig() { + // Load .env file + err := godotenv.Load() + if err != nil { + log.Println("Warning: .env file not found, using system environment variables.") + } + + // Load config from env + appConfig.JWTSecret = os.Getenv("JWT_SECRET") + appConfig.DBHost = os.Getenv("DB_HOST") + appConfig.DBPort = os.Getenv("DB_PORT") + appConfig.DBUser = os.Getenv("DB_USER") + appConfig.DBPassword = os.Getenv("DB_PASSWORD") + appConfig.DBName = os.Getenv("DB_NAME") + appConfig.DBSSLMode = os.Getenv("DB_SSLMODE") + + if appConfig.JWTSecret == "" || appConfig.DBHost == "" { + log.Fatal("Required environment variables are missing") } } + +func GetConfig() *Config { + return appConfig +} diff --git a/database.txt b/database.txt index 3a524c7..b0fb187 100644 --- a/database.txt +++ b/database.txt @@ -7,7 +7,7 @@ CREATE TABLE users ( updated_at TIMESTAMP DEFAULT NOW() ); -CREATE TABLE master_product_category ( +CREATE TABLE product_categories ( id SERIAL PRIMARY KEY, code VARCHAR(50), name VARCHAR(100), diff --git a/database/database.go b/database/database.go index 4574ea1..48b4a8e 100644 --- a/database/database.go +++ b/database/database.go @@ -1,9 +1,11 @@ package database import ( + "BE-MiniERP/config" auth_models "BE-MiniERP/modules/auth/models" inventory_models "BE-MiniERP/modules/inventory/models" sales_models "BE-MiniERP/modules/sales/models" + "fmt" "log" "gorm.io/driver/postgres" @@ -13,18 +15,22 @@ import ( var DB *gorm.DB func Connect() { - dsn := "host=10.5.50.9 user=probindo password=Pr08ind0 dbname=erp port=5432 sslmode=disable" + cfg := config.GetConfig() + + dsn := fmt.Sprintf( + "host=%s user=%s password=%s dbname=%s port=%s sslmode=%s", + cfg.DBHost, cfg.DBUser, cfg.DBPassword, cfg.DBName, cfg.DBPort, cfg.DBSSLMode, + ) + var err error DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{}) if err != nil { log.Fatal("Failed to connect to DB:", err) } + // Auto migrate models err = DB.AutoMigrate( - // Auth &auth_models.User{}, - - // Inventory &inventory_models.Product{}, &inventory_models.ProductCategory{}, &inventory_models.Collection{}, @@ -34,8 +40,6 @@ func Connect() { &inventory_models.ProductionOrder{}, &inventory_models.Warehouse{}, &inventory_models.StockMovement{}, - - // Sales &sales_models.Customer{}, &sales_models.SalesOrder{}, &sales_models.SalesOrderItem{}, diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..98f96b8 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,10 @@ +version: "3.8" + +services: + app: + build: . + container_name: be-minierp-container + ports: + - "8080:3000" + environment: + - DATABASE_URL=host=10.5.50.9 user=probindo password=Pr08ind0 dbname=erp port=5432 sslmode=disable \ No newline at end of file diff --git a/go.mod b/go.mod index 5b1a5e4..f1a3aee 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect + github.com/joho/godotenv v1.5.1 github.com/klauspost/compress v1.17.9 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect diff --git a/go.sum b/go.sum index 12de186..0f66c7f 100644 --- a/go.sum +++ b/go.sum @@ -19,6 +19,8 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= diff --git a/middlewares/jwt.go b/middlewares/jwt.go index 3dcb893..4bf2d1b 100644 --- a/middlewares/jwt.go +++ b/middlewares/jwt.go @@ -12,7 +12,9 @@ func JWTProtected() fiber.Handler { return func(c *fiber.Ctx) error { authHeader := c.Get("Authorization") if authHeader == "" { - return c.SendStatus(fiber.StatusUnauthorized) + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "message": "Missing Authorization header", + }) } tokenString := strings.TrimPrefix(authHeader, "Bearer ") @@ -21,10 +23,19 @@ func JWTProtected() fiber.Handler { }) if err != nil || !token.Valid { - return c.SendStatus(fiber.StatusUnauthorized) + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "message": "Invalid or expired token", + }) } - c.Locals("user", token.Claims) + claims, ok := token.Claims.(jwt.MapClaims) + if !ok { + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "message": "Invalid token claims", + }) + } + + c.Locals("user", claims) return c.Next() } } diff --git a/modules/auth/service/jwt.go b/modules/auth/service/jwt.go index 3565292..3c75278 100644 --- a/modules/auth/service/jwt.go +++ b/modules/auth/service/jwt.go @@ -1,27 +1,21 @@ package service import ( + "BE-MiniERP/config" "time" "github.com/golang-jwt/jwt/v4" ) -var jwtKey = []byte("supersecret") // sebaiknya dari env - -type Claims struct { - UserID uint `json:"user_id"` - Role string `json:"role"` - jwt.RegisteredClaims -} - func GenerateJWT(userID uint, role string) (string, error) { - claims := &Claims{ - UserID: userID, - Role: role, - RegisteredClaims: jwt.RegisteredClaims{ - ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), - }, + claims := jwt.MapClaims{ + "user_id": userID, + "role": role, + "exp": time.Now().Add(time.Hour * 1).Unix(), // expired dalam 1 jam } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - return token.SignedString(jwtKey) + + // INI PALING PENTING ⬇️ + return token.SignedString([]byte(config.GetConfig().JWTSecret)) }