diff --git a/config/config.go b/config/config.go index 14b022d..b20863e 100644 --- a/config/config.go +++ b/config/config.go @@ -4,6 +4,7 @@ import ( "errors" "os" "strconv" + "time" "github.com/joho/godotenv" ) @@ -15,6 +16,12 @@ type DbConfig struct { DBPort string DBName string } + +type AuthConfig struct { + UserAuthEnabled bool + JWTSecret string + TokenExpiry time.Duration +} type LoginConfig struct { LoginAPI string AuthMeAPI string @@ -41,6 +48,7 @@ type Config struct { TokenApi ApiConfig LoginConfig + AuthConfig } func (c *Config) readConfig() error { @@ -83,6 +91,29 @@ func (c *Config) readConfig() error { SkipSSLVerification: skipSSL, } + userAuthEnabled := true // Default to true for security + if authEnabledStr := os.Getenv("USER_AUTH_ENABLED"); authEnabledStr != "" { + var err error + userAuthEnabled, err = strconv.ParseBool(authEnabledStr) + if err != nil { + return errors.New("invalid USER_AUTH_ENABLED value, must be true or false") + } + } + + // Parse token expiry (default to 24 hours) + tokenExpiry := 24 * time.Hour + if expiryStr := os.Getenv("JWT_TOKEN_EXPIRY_HOURS"); expiryStr != "" { + if hours, err := strconv.Atoi(expiryStr); err == nil { + tokenExpiry = time.Duration(hours) * time.Hour + } + } + + c.AuthConfig = AuthConfig{ + UserAuthEnabled: userAuthEnabled, + JWTSecret: os.Getenv("JWT_SECRET"), + TokenExpiry: tokenExpiry, + } + if c.ApiConfig.ApiPort == "" { return errors.New("failed to read environment variables") } diff --git a/delivery/controller/activity_logs_controller.go b/delivery/controller/activity_logs_controller.go index e99df34..585cbcb 100644 --- a/delivery/controller/activity_logs_controller.go +++ b/delivery/controller/activity_logs_controller.go @@ -3,6 +3,7 @@ package controller import ( "net/http" "strconv" + "users_management/m/config" "users_management/m/middleware" "users_management/m/usecase" "users_management/m/utils/common" @@ -15,26 +16,26 @@ import ( type ActivityLogController struct { activityLogUC usecase.ActivityLogUseCase rg *gin.RouterGroup + cfg *config.Config // Add config field for middleware } - -func NewActivityLogController(activityLogUC usecase.ActivityLogUseCase, rg *gin.RouterGroup) *ActivityLogController { +func NewActivityLogController(activityLogUC usecase.ActivityLogUseCase, rg *gin.RouterGroup, cfg *config.Config) *ActivityLogController { return &ActivityLogController{ activityLogUC: activityLogUC, rg: rg, + cfg: cfg, // Initialize config } } + func (c *ActivityLogController) Route() { logs := c.rg.Group("/logs") { // Users can see their own logs (simplified response) logs.GET("/my-logs", c.getMyLogs) - // Only admins and superadmins can see all logs (detailed response) - logs.GET("/all", middleware.RequireAdminRole(), c.getAllLogs) - + logs.GET("/all", middleware.ConditionalRequireAnyRole(c.cfg, "Admin", "SuperAdmin"), c.getAllLogs) // Admins and superadmins can see teknisi logs specifically (detailed response) - logs.GET("/teknisi", middleware.RequireAdminRole(), c.getTeknisinLogs) + logs.GET("/teknisi", middleware.ConditionalRequireAnyRole(c.cfg, "Admin", "SuperAdmin"), c.getTeknisinLogs) } } diff --git a/delivery/controller/backbone_controller.go b/delivery/controller/backbone_controller.go index 833ee1e..4920076 100644 --- a/delivery/controller/backbone_controller.go +++ b/delivery/controller/backbone_controller.go @@ -3,6 +3,7 @@ package controller import ( "log" "net/http" + "users_management/m/config" "users_management/m/middleware" "users_management/m/model/dto/req" "users_management/m/usecase" @@ -11,10 +12,10 @@ import ( "github.com/gin-gonic/gin" "github.com/google/uuid" ) - type BackboneController struct { - bu usecase.BackboneUseCase - rg *gin.RouterGroup + bu usecase.BackboneUseCase + rg *gin.RouterGroup + cfg *config.Config } func (bc *BackboneController) Route() { @@ -23,19 +24,21 @@ func (bc *BackboneController) Route() { rg.GET("", bc.GetBackbone()) rg.GET("/:uuid", bc.GetBackboneByID()) - rg.POST("", middleware.RequireAnyRole("Teknisi", "Admin", "Superadmin"), bc.CreateBackbone()) - rg.PUT("/:uuid", middleware.RequireAnyRole("Teknisi", "Admin", "Superadmin"), bc.UpdateBackbone()) + rg.POST("", middleware.ConditionalRequireAnyRole(bc.cfg, "Teknisi", "Admin", "Superadmin"), bc.CreateBackbone()) + rg.PUT("/:uuid", middleware.ConditionalRequireAnyRole(bc.cfg, "Teknisi", "Admin", "Superadmin"), bc.UpdateBackbone()) } } - -func NewBackboneController(bu usecase.BackboneUseCase, rg *gin.RouterGroup) *BackboneController { +func NewBackboneController(bu usecase.BackboneUseCase, rg *gin.RouterGroup, cfg *config.Config) *BackboneController { return &BackboneController{ - bu: bu, - rg: rg, + bu: bu, + rg: rg, + cfg: cfg, } } + + func (bc *BackboneController) GetBackbone() gin.HandlerFunc { return func(c *gin.Context) { backbones, err := bc.bu.GetAllBackbone() diff --git a/delivery/controller/device_details.go b/delivery/controller/device_details.go index d462313..9bef0fe 100644 --- a/delivery/controller/device_details.go +++ b/delivery/controller/device_details.go @@ -9,6 +9,7 @@ import ( "net/http" "strconv" "strings" + "users_management/m/config" "users_management/m/middleware" "users_management/m/model/dto/req" "users_management/m/usecase" @@ -21,18 +22,20 @@ import ( type DeviceDetailsController struct { deviceDetailsUC usecase.DeviceDetailsUseCase rg *gin.RouterGroup + cfg *config.Config // Add config field for middleware } -func NewDeviceDetailsController(deviceDetailsUC usecase.DeviceDetailsUseCase, rg *gin.RouterGroup) *DeviceDetailsController { +func NewDeviceDetailsController(deviceDetailsUC usecase.DeviceDetailsUseCase, rg *gin.RouterGroup, cfg *config.Config) *DeviceDetailsController { return &DeviceDetailsController{ deviceDetailsUC: deviceDetailsUC, rg: rg, + cfg: cfg, } } func (c *DeviceDetailsController) Route() { deviceDetails := c.rg.Group("/device-details") - deviceDetails.Use(middleware.RequireAnyRole("Teknisi", "Admin", "Superadmin")) + deviceDetails.Use(middleware.ConditionalRequireAnyRole(c.cfg, "Teknisi", "Admin", "Superadmin")) { deviceDetails.GET("", c.getAllDeviceDetails) deviceDetails.POST("", c.createDeviceDetails) diff --git a/delivery/controller/devices_controller.go b/delivery/controller/devices_controller.go index 0065e74..5081efb 100644 --- a/delivery/controller/devices_controller.go +++ b/delivery/controller/devices_controller.go @@ -7,6 +7,7 @@ import ( "net/http" "strconv" "strings" + "users_management/m/config" "users_management/m/middleware" "users_management/m/model/dto/req" "users_management/m/usecase" @@ -19,11 +20,20 @@ import ( type DeviceController struct { du usecase.DeviceUseCase rg *gin.RouterGroup + cfg *config.Config +} + +func NewDeviceController(du usecase.DeviceUseCase, rg *gin.RouterGroup, cfg *config.Config) *DeviceController { + return &DeviceController{ + du: du, + rg: rg, + cfg: cfg, + } } func (dc *DeviceController) Route() { rg := dc.rg.Group("/devices") - rg.Use(middleware.RequireAnyRole("Teknisi", "Admin", "Superadmin")) + rg.Use(middleware.ConditionalRequireAnyRole(dc.cfg,"Teknisi", "Admin", "Superadmin")) { rg.POST("", dc.CreateDevice()) rg.GET("", dc.GetAllDevices()) @@ -34,12 +44,7 @@ func (dc *DeviceController) Route() { } } -func NewDeviceController(du usecase.DeviceUseCase, rg *gin.RouterGroup) *DeviceController { - return &DeviceController{ - du: du, - rg: rg, - } -} + func (dc *DeviceController) BulkUploadImages() gin.HandlerFunc { return func(c *gin.Context) { diff --git a/delivery/controller/fishbone_controller.go b/delivery/controller/fishbone_controller.go index 5d6f732..d1c5fa6 100644 --- a/delivery/controller/fishbone_controller.go +++ b/delivery/controller/fishbone_controller.go @@ -3,6 +3,7 @@ package controller import ( "log" "net/http" + "users_management/m/config" "users_management/m/middleware" "users_management/m/model/dto/req" "users_management/m/usecase" @@ -15,11 +16,20 @@ import ( type FishboneController struct { fu usecase.FishboneUseCase rg *gin.RouterGroup + cfg *config.Config +} + +func NewFishboneController(fu usecase.FishboneUseCase, rg *gin.RouterGroup, cfg *config.Config) *FishboneController { + return &FishboneController{ + fu: fu, + rg: rg, + cfg: cfg, + } } func (fc *FishboneController) Route() { rg := fc.rg.Group("/fishbone") - rg.Use(middleware.RequireAnyRole("Teknisi", "Admin", "Superadmin")) + rg.Use(middleware.ConditionalRequireAnyRole(fc.cfg,"Teknisi", "Admin", "Superadmin")) // Apply middleware to all routes { rg.GET("", fc.GetFishbone()) @@ -30,12 +40,6 @@ func (fc *FishboneController) Route() { } } -func NewFishboneController(fu usecase.FishboneUseCase, rg *gin.RouterGroup) *FishboneController { - return &FishboneController{ - fu: fu, - rg: rg, - } -} func (fc *FishboneController) GetFishboneByBackboneID() gin.HandlerFunc { return func(c *gin.Context) { diff --git a/delivery/controller/nearest_device_controller.go b/delivery/controller/nearest_device_controller.go index a5e2024..40ee5db 100644 --- a/delivery/controller/nearest_device_controller.go +++ b/delivery/controller/nearest_device_controller.go @@ -1,32 +1,35 @@ package controller import ( - "net/http" - "strconv" - "users_management/m/middleware" - "users_management/m/model/dto/req" - "users_management/m/usecase" - "users_management/m/utils/common" + "net/http" + "strconv" + "users_management/m/config" + "users_management/m/middleware" + "users_management/m/model/dto/req" + "users_management/m/usecase" + "users_management/m/utils/common" - "github.com/gin-gonic/gin" - "github.com/google/uuid" + "github.com/gin-gonic/gin" + "github.com/google/uuid" ) type NearestDeviceController struct { nearestDeviceUC usecase.NearestDeviceUseCase rg *gin.RouterGroup + cfg *config.Config } -func NewNearestDeviceController(nearestDeviceUC usecase.NearestDeviceUseCase, rg *gin.RouterGroup) *NearestDeviceController { +func NewNearestDeviceController(nearestDeviceUC usecase.NearestDeviceUseCase, rg *gin.RouterGroup, cfg *config.Config) *NearestDeviceController { return &NearestDeviceController{ nearestDeviceUC: nearestDeviceUC, rg: rg, + cfg: cfg, } } func (c *NearestDeviceController) Route() { nearestDevices := c.rg.Group("/nearest-devices") - nearestDevices.Use(middleware.RequireAnyRole("Teknisi", "Admin", "Superadmin")) + nearestDevices.Use(middleware.ConditionalRequireAnyRole(c.cfg,"Teknisi", "Admin", "Superadmin")) { nearestDevices.POST("/search", c.getNearestDevices) nearestDevices.GET("/:id", c.getNearestDeviceByID) diff --git a/delivery/controller/tower_controller.go b/delivery/controller/tower_controller.go index bbe5d88..cd597bc 100644 --- a/delivery/controller/tower_controller.go +++ b/delivery/controller/tower_controller.go @@ -7,6 +7,7 @@ import ( "net/http" "strconv" "strings" + "users_management/m/config" "users_management/m/middleware" "users_management/m/model/dto/req" "users_management/m/usecase" @@ -19,11 +20,12 @@ import ( type TowerController struct { tu usecase.TowerUseCase rg *gin.RouterGroup + cfg *config.Config } func (tc *TowerController) Route() { rg := tc.rg.Group("/tower") - rg.Use(middleware.RequireAnyRole("Teknisi", "Admin", "Superadmin")) // Apply middleware to all routes + rg.Use(middleware.ConditionalRequireAnyRole(tc.cfg,"Teknisi", "Admin", "Superadmin")) // Apply middleware to all routes { rg.GET("", tc.GetTower()) rg.POST("", tc.CreateTower()) @@ -35,10 +37,11 @@ func (tc *TowerController) Route() { } } -func NewTowerController(tu usecase.TowerUseCase, rg *gin.RouterGroup) *TowerController { +func NewTowerController(tu usecase.TowerUseCase, rg *gin.RouterGroup, cfg *config.Config) *TowerController { return &TowerController{ tu: tu, rg: rg, + cfg: cfg, } } diff --git a/delivery/controller/users_controller.go b/delivery/controller/users_controller.go index 99b9320..5236ff4 100644 --- a/delivery/controller/users_controller.go +++ b/delivery/controller/users_controller.go @@ -19,7 +19,7 @@ type UsersController struct { func (uc *UsersController) Route() { rg:= uc.rg.Group("/users") rg.Use(middleware.CORSMiddleware()) - rg.Use(middleware.RateLoginMiddleware()) + // rg.Use(middleware.RateLoginMiddleware()) rg.OPTIONS("/login", func(c *gin.Context) { c.Status(http.StatusNoContent) }) diff --git a/delivery/server.go b/delivery/server.go index 8e1e496..95dea2d 100644 --- a/delivery/server.go +++ b/delivery/server.go @@ -15,6 +15,7 @@ type Server struct { ucManager manager.UsecaseManager engine *gin.Engine host string + cfg *config.Config } func NewServer() *Server { @@ -31,6 +32,11 @@ func NewServer() *Server { repoManager := manager.NewRepositoryManager(infraManager) ucManager := manager.NewUsecaseManager(repoManager, cfg) engine := gin.Default() + + engine.Use(middleware.CORSMiddleware()) + engine.Use(middleware.ConditionalAuthMiddleware(ucManager.NewUserUsecase(), cfg)) + engine.Use(middleware.ConditionalActivityLoggingMiddleware(ucManager.NewActivityLogUsecase(), cfg)) + engine.Use(common.LoggingToFile()) host := fmt.Sprintf(":%s", cfg.ApiPort) @@ -38,31 +44,26 @@ func NewServer() *Server { ucManager: ucManager, engine: engine, host: host, + cfg: cfg, } } func (s *Server) setupController() { s.engine.Static("/uploads", "./uploads") - s.engine.Use(middleware.CORSMiddleware()) - rg := s.engine.Group("/api/v1") - controller.NewUsersController(s.ucManager.NewUserUsecase(), s.ucManager.NewAuthUsecase(),rg).Route() - rg.Use(middleware.AuthMiddleware(s.ucManager.NewUserUsecase())) + controller.NewUsersController(s.ucManager.NewUserUsecase(), s.ucManager.NewAuthUsecase(),rg).Route() controller.NewCountAssetsController(s.ucManager.NewCountAssetsUsecase(), rg).Route() - rg.Use(middleware.ActivityLoggingMiddleware(s.ucManager.NewActivityLogUsecase())) - { - controller.NewDeviceController(s.ucManager.NewDeviceUsecase(), rg).Route() - controller.NewBackboneController(s.ucManager.NewBackboneUsecase(), rg).Route() - controller.NewFishboneController(s.ucManager.NewFishboneUsecase(), rg).Route() - controller.NewTowerController(s.ucManager.NewTowerUsecase(), rg).Route() + controller.NewDeviceController(s.ucManager.NewDeviceUsecase(), rg,s.cfg).Route() + controller.NewBackboneController(s.ucManager.NewBackboneUsecase(), rg,s.cfg).Route() + controller.NewFishboneController(s.ucManager.NewFishboneUsecase(), rg,s.cfg).Route() + controller.NewTowerController(s.ucManager.NewTowerUsecase(), rg,s.cfg).Route() controller.NewDevicePortController(s.ucManager.NewDevicePortUsecase(), rg).Route() - controller.NewActivityLogController(s.ucManager.NewActivityLogUsecase(), rg).Route() + controller.NewActivityLogController(s.ucManager.NewActivityLogUsecase(), rg,s.cfg).Route() controller.NewDeviceInspectionController(s.ucManager.NewDeviceInspectionUsecase(), rg).Route() - controller.NewNearestDeviceController(s.ucManager.NewNearestDeviceUsecase(), rg).Route() - controller.NewDeviceDetailsController(s.ucManager.NewDeviceDetailsUsecase(), rg).Route() - } + controller.NewNearestDeviceController(s.ucManager.NewNearestDeviceUsecase(), rg,s.cfg).Route() + controller.NewDeviceDetailsController(s.ucManager.NewDeviceDetailsUsecase(), rg,s.cfg).Route() } diff --git a/middleware/activity_logs_middleware.go b/middleware/activity_logs_middleware.go index 2531ed9..e28f4b3 100644 --- a/middleware/activity_logs_middleware.go +++ b/middleware/activity_logs_middleware.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "strings" + "users_management/m/config" "users_management/m/usecase" "github.com/gin-gonic/gin" @@ -22,6 +23,19 @@ func (r responseWriter) Write(b []byte) (int, error) { return r.ResponseWriter.Write(b) } +// ConditionalActivityLoggingMiddleware that respects auth config +func ConditionalActivityLoggingMiddleware(activityLogUC usecase.ActivityLogUseCase, cfg *config.Config) gin.HandlerFunc { + return func(c *gin.Context) { + // If auth is disabled, skip activity logging + if !cfg.AuthConfig.UserAuthEnabled { + c.Next() + return + } + + // If auth is enabled, run normal activity logging + ActivityLoggingMiddleware(activityLogUC)(c) + } +} func ActivityLoggingMiddleware(activityLogUC usecase.ActivityLogUseCase) gin.HandlerFunc { return func(c *gin.Context) { // Skip logging for certain endpoints diff --git a/middleware/auth_middleware.go b/middleware/auth_middleware.go index f22032c..3a9c66d 100644 --- a/middleware/auth_middleware.go +++ b/middleware/auth_middleware.go @@ -1,21 +1,46 @@ package middleware import ( - "crypto/tls" - "encoding/json" - "log" - "net/http" - "strings" - "time" + "crypto/tls" + "encoding/json" + "log" + "net/http" + "strings" + "time" - "users_management/m/model/dto/res" - "users_management/m/model/entity" - "users_management/m/usecase" - "users_management/m/utils/common" + "users_management/m/config" + "users_management/m/model/dto/res" + "users_management/m/model/entity" + "users_management/m/usecase" + "users_management/m/utils/common" - "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin" + "github.com/google/uuid" ) +// ConditionalAuthMiddleware checks config to decide whether to apply auth +func ConditionalAuthMiddleware(userUC usecase.UsersUsecase, cfg *config.Config) gin.HandlerFunc { + return func(c *gin.Context) { + // If auth is disabled, skip authentication but set default values + if !cfg.AuthConfig.UserAuthEnabled { + log.Println("Authentication disabled - skipping auth middleware") + + // Set default values for when auth is disabled + c.Set("userID", uuid.New()) // Generate a dummy UUID + c.Set("userName", "system") + c.Set("userRole", "Admin") // Default role when auth is disabled + c.Set("token", "no-auth-mode") + + c.Next() + return + } + + // If auth is enabled, run the normal auth middleware + AuthMiddleware(userUC)(c) + } +} + + func AuthMiddleware(userUC usecase.UsersUsecase) gin.HandlerFunc { return func(c *gin.Context) { token := c.GetHeader("Authorization") diff --git a/middleware/cors_middleware.go b/middleware/cors_middleware.go index fac1018..fe387bc 100644 --- a/middleware/cors_middleware.go +++ b/middleware/cors_middleware.go @@ -16,8 +16,10 @@ func CORSMiddleware() gin.HandlerFunc { "http://localhost:3001", "http://127.0.0.1:3000", "http://127.0.0.1:5173", - "http://103.110.8.103:80",// Add production URL - "http://103.110.8.103", + "http://103.110.8.103:80",// Add production URL + "http://103.110.8.103", + + "http://nam.winteraccess.id", } diff --git a/middleware/rbac.go b/middleware/rbac.go index 48cfc46..aa94e68 100644 --- a/middleware/rbac.go +++ b/middleware/rbac.go @@ -1,12 +1,40 @@ package middleware import ( - "net/http" - "users_management/m/utils/common" - - "github.com/gin-gonic/gin" + "net/http" + "users_management/m/config" + "users_management/m/utils/common" + + "github.com/gin-gonic/gin" ) +// ConditionalRequireRole middleware that respects auth config +func ConditionalRequireRole(cfg *config.Config, allowedRoles ...string) gin.HandlerFunc { + return func(c *gin.Context) { + // If auth is disabled, allow all requests + if !cfg.AuthConfig.UserAuthEnabled { + c.Next() + return + } + + // If auth is enabled, check roles normally + RequireRole(allowedRoles...)(c) + } +} + +// ConditionalRequireAnyRole middleware that respects auth config +func ConditionalRequireAnyRole(cfg *config.Config, roles ...string) gin.HandlerFunc { + return func(c *gin.Context) { + // If auth is disabled, allow all requests + if !cfg.AuthConfig.UserAuthEnabled { + c.Next() + return + } + + // If auth is enabled, check roles normally + RequireAnyRole(roles...)(c) + } +} // RequireRole middleware to check if user has required role func RequireRole(allowedRoles ...string) gin.HandlerFunc { return func(c *gin.Context) {