diff --git a/.env.example b/.env.example index 5f916f8..74168f0 100644 --- a/.env.example +++ b/.env.example @@ -9,4 +9,6 @@ DB_ANTRIAN_PASSWORD=supersecret DB_ANTRIAN_HOST=localhost DB_ANTRIAN_DATABASE=antrian_operasi DB_ANTRIAN_PORT=5432 -DB_ANTRIAN_SSLMODE=disable \ No newline at end of file +DB_ANTRIAN_SSLMODE=disable + +SECURITY_TRUSTED_ORIGINS=http://localhost:3000 \ No newline at end of file diff --git a/go.mod b/go.mod index 0cdd7c0..366cf1e 100644 --- a/go.mod +++ b/go.mod @@ -7,13 +7,14 @@ require ( github.com/bytedance/sonic v1.14.0 // indirect github.com/bytedance/sonic/loader v0.3.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect - github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/gabriel-vasile/mimetype v1.4.9 // indirect + github.com/gin-contrib/cors v1.7.6 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/gin-gonic/gin v1.11.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.27.0 // indirect - github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.18.0 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 // indirect @@ -31,7 +32,7 @@ require ( github.com/leodido/go-urn v1.4.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/montanaflynn/stats v0.7.1 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect diff --git a/go.sum b/go.sum index 8af9aa7..4ba659f 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,10 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= +github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= +github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY= +github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= @@ -24,6 +28,8 @@ github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAu github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= @@ -62,6 +68,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= diff --git a/internal/config/config.go b/internal/config/config.go index 218c9ca..bcd43dd 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -20,6 +20,9 @@ func LoadConfig() *Config { Mode: getEnv("GIN_MODE", "debug"), }, Databases: make(map[string]DatabaseConfig), + Security: SecurityConfig{ + TrustedOrigins: parseOrigins(getEnv("SECURITY_TRUSTED_ORIGINS", "http://localhost:3000,http://localhost:8080")), + }, } config.loadCustomDatabaseConfigs() diff --git a/internal/config/helper.go b/internal/config/helper.go index b676e82..58e72f7 100644 --- a/internal/config/helper.go +++ b/internal/config/helper.go @@ -3,6 +3,7 @@ package config import ( "os" "strconv" + "strings" "time" ) @@ -155,3 +156,14 @@ func parseDuration(durationStr string) time.Duration { } return 5 * time.Minute } + +func parseOrigins(originsStr string) []string { + if originsStr == "" { + return []string{"http://localhost:8080"} // Default untuk pengembangan + } + origins := strings.Split(originsStr, ",") + for i, origin := range origins { + origins[i] = strings.TrimSpace(origin) + } + return origins +} diff --git a/internal/config/struct.go b/internal/config/struct.go index 6d68ea7..35df448 100644 --- a/internal/config/struct.go +++ b/internal/config/struct.go @@ -6,6 +6,7 @@ type Config struct { Server ServerConfig Databases map[string]DatabaseConfig ReadReplicas map[string][]DatabaseConfig + Security SecurityConfig } type ServerConfig struct { @@ -43,3 +44,7 @@ type DatabaseConfig struct { MaxIdleTime time.Duration // Maximum amount of time a connection may be idle HealthCheckPeriod time.Duration // Health check period } + +type SecurityConfig struct { + TrustedOrigins []string `mapstructure:"trusted_origins"` +} diff --git a/internal/middleware/security.go b/internal/middleware/security.go new file mode 100644 index 0000000..939f429 --- /dev/null +++ b/internal/middleware/security.go @@ -0,0 +1,21 @@ +package middleware + +import ( + "antrian-operasi/internal/config" + "time" + + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" +) + +// SecureCORSConfig menyediakan konfigurasi CORS yang aman dan fleksibel +func SecureCORSConfig(cfg config.SecurityConfig) gin.HandlerFunc { + return cors.New(cors.Config{ + AllowOrigins: cfg.TrustedOrigins, + AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"}, + AllowHeaders: []string{"Origin", "Content-Length", "Content-Type", "Authorization"}, + ExposeHeaders: []string{"Content-Length"}, + AllowCredentials: true, // Hanya gunakan 'true' jika Anda benar-benar membutuhkannya (cookie, auth) + MaxAge: 12 * time.Hour, + }) +} diff --git a/internal/routes/routes.go b/internal/routes/routes.go index ca016a2..792deae 100644 --- a/internal/routes/routes.go +++ b/internal/routes/routes.go @@ -7,6 +7,7 @@ import ( "antrian-operasi/internal/domain/reference/dokter" "antrian-operasi/internal/domain/reference/kategori" "antrian-operasi/internal/domain/reference/spesialis" + "antrian-operasi/internal/middleware" "net/http" "github.com/gin-gonic/gin" @@ -22,13 +23,8 @@ func RegisterRoutes(cfg *config.Config, dbService database.Service) *gin.Engine }) }) - // router.GET("databases", func(c *gin.Context) { - // c.JSON(200, gin.H{ - // "databases": dbService.ListDBs(), - // "health": dbService.Health(), - // "timestamp": time.Now().Unix(), - // }) - // }) + // init middleware + router.Use(middleware.SecureCORSConfig(cfg.Security)) api := router.Group("/api") {