From f4a9166bee161839f56019dda21bf78a9ff3c634 Mon Sep 17 00:00:00 2001 From: "$(pass /github/name)" <$(pass /github/email)> Date: Wed, 17 Jul 2024 16:46:29 +0200 Subject: [PATCH] add: Environment Var support --- cmd/membership/main.go | 4 +- go.mod | 7 +- go.sum | 2 + internal/config/config.go | 88 ++++++++++++-------- internal/controllers/membershipController.go | 4 +- internal/controllers/user_controller_test.go | 4 +- internal/middlewares/csrf_middleware.go | 12 +-- internal/server/server.go | 9 +- internal/services/email_service.go | 24 +++--- 9 files changed, 86 insertions(+), 68 deletions(-) diff --git a/cmd/membership/main.go b/cmd/membership/main.go index fdefd35..8fecd5c 100644 --- a/cmd/membership/main.go +++ b/cmd/membership/main.go @@ -2,11 +2,11 @@ package main import ( "GoMembership/internal/server" - "log" + "GoMembership/pkg/logger" ) func main() { - log.Println("startup...") + logger.Info.Println("startup...") server.Run() } diff --git a/go.mod b/go.mod index c617595..de23eaa 100644 --- a/go.mod +++ b/go.mod @@ -6,19 +6,19 @@ require ( github.com/gin-gonic/gin v1.10.0 github.com/go-playground/validator/v10 v10.22.0 github.com/jbub/banking v0.8.0 - github.com/stretchr/testify v1.9.0 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gorm.io/driver/sqlite v1.5.6 gorm.io/gorm v1.25.10 ) +require github.com/kelseyhightower/envconfig v1.4.0 + require ( github.com/bytedance/sonic v1.11.9 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/gabriel-vasile/mimetype v1.4.4 // in + github.com/gabriel-vasile/mimetype v1.4.4 // indirect; in github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect @@ -33,7 +33,6 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect golang.org/x/arch v0.8.0 // indirect diff --git a/go.sum b/go.sum index 3ce9ba2..987de58 100644 --- a/go.sum +++ b/go.sum @@ -36,6 +36,8 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= diff --git a/internal/config/config.go b/internal/config/config.go index 9295677..f12a9d8 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,36 +1,39 @@ package config import ( - "GoMembership/internal/utils" - "GoMembership/pkg/logger" "encoding/json" "os" "path/filepath" - "sync" + + "github.com/kelseyhightower/envconfig" + + "GoMembership/internal/utils" + "GoMembership/pkg/logger" ) type DatabaseConfig struct { - Path string `json:"Path"` + Path string `json:"Path" envconfig:"DB_PATH"` } type AuthenticationConfig struct { JWTSecret string CSRFSecret string - APIKEY string `json:"APIKey"` + APIKEY string `json:"APIKey" envconfig:"API_KEY"` } type SMTPConfig struct { - Host string `json:"Host"` - User string `json:"User"` - Password string `json:"Password"` - Mailtype string `json:"Mailtype"` - AdminEmail string `json:"AdminEmail"` - Port int `json:"Port"` + Host string `json:"Host" envconfig:"SMTP_HOST"` + User string `json:"User" envconfig:"SMTP_USER"` + Password string `json:"Password" envconfig:"SMTP_PASS"` + Mailtype string `json:"Mailtype" envconfig:"MAIL_TYPE"` + AdminEmail string `json:"AdminEmail" envconfig:"ADMIN_MAIL"` + Port int `json:"Port" envconfig:"SMTP_PORT"` } type TemplateConfig struct { - MailDir string `json:"MailDir"` + MailDir string `json:"MailDir" envconfig:"TEMPLATE_DIR"` } + type Config struct { Auth AuthenticationConfig `json:"auth"` DB DatabaseConfig `json:"db"` @@ -39,46 +42,57 @@ type Config struct { } var ( - pConfig Config - once sync.Once - loaded bool + CFG Config + Auth AuthenticationConfig + DB DatabaseConfig + Templates TemplateConfig + SMTP SMTPConfig ) -func LoadConfig() *Config { +func LoadConfig() { + readFile(&CFG) + readEnv(&CFG) + csrfSecret, err := utils.GenerateRandomString(32) + if err != nil { + logger.Error.Fatalf("could not generate CSRF secret: %v", err) + } + + jwtSecret, err := utils.GenerateRandomString(32) + if err != nil { + logger.Error.Fatalf("could not generate JWT secret: %v", err) + } + CFG.Auth.JWTSecret = jwtSecret + CFG.Auth.CSRFSecret = csrfSecret + + Auth = CFG.Auth + DB = CFG.DB + Templates = CFG.Templates + SMTP = CFG.SMTP +} + +func readFile(cfg *Config) { path, err := os.Getwd() if err != nil { logger.Error.Fatalf("could not get working directory: %v", err) } configFile, err := os.Open(filepath.Join(path, "configs", "config.json")) + // configFile, err := os.Open("config.json") if err != nil { logger.Error.Fatalf("could not open config file: %v", err) } defer configFile.Close() decoder := json.NewDecoder(configFile) - // pConfig = &Config{} - err = decoder.Decode(&pConfig) + err = decoder.Decode(cfg) if err != nil { logger.Error.Fatalf("could not decode config file: %v", err) } - if !loaded { - once.Do( - func() { - csrfSecret, err := utils.GenerateRandomString(32) - if err != nil { - logger.Error.Fatalf("could not generate CSRF secret: %v", err) - } - - jwtSecret, err := utils.GenerateRandomString(32) - if err != nil { - logger.Error.Fatalf("could not generate JWT secret: %v", err) - } - pConfig.Auth.JWTSecret = jwtSecret - pConfig.Auth.CSRFSecret = csrfSecret - loaded = true - }) - } - - return &pConfig +} + +func readEnv(cfg *Config) { + err := envconfig.Process("", cfg) + if err != nil { + logger.Error.Fatalf("could not decode env variables: %#v", err) + } } diff --git a/internal/controllers/membershipController.go b/internal/controllers/membershipController.go index d1a24e5..30c461d 100644 --- a/internal/controllers/membershipController.go +++ b/internal/controllers/membershipController.go @@ -28,14 +28,14 @@ func (mc *MembershipController) RegisterSubscription(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": "Couldn't decode subscription data"}) } - logger.Info.Printf("Using API key: %v", config.LoadConfig().Auth.APIKEY) + logger.Info.Printf("Using API key: %v", config.Auth.APIKEY) if regData.APIKey == "" { logger.Error.Println("API Key is missing") c.JSON(http.StatusBadRequest, "API Key is missing") return } - if regData.APIKey != config.LoadConfig().Auth.APIKEY { + if regData.APIKey != config.Auth.APIKEY { logger.Error.Printf("API Key not valid: %v", regData.APIKey) c.JSON(http.StatusExpectationFailed, "API Key is missing") return diff --git a/internal/controllers/user_controller_test.go b/internal/controllers/user_controller_test.go index 1e732cc..5678e1f 100644 --- a/internal/controllers/user_controller_test.go +++ b/internal/controllers/user_controller_test.go @@ -42,8 +42,8 @@ func TestUserController(t *testing.T) { t.Errorf("Failed to create DB: %#v", err) } - cfg := config.LoadConfig() - emailService := services.NewEmailService(cfg.SMTP.Host, cfg.SMTP.Port, cfg.SMTP.User, cfg.SMTP.Password, cfg.SMTP.AdminEmail) + config.LoadConfig() + emailService := services.NewEmailService(config.SMTP.Host, config.SMTP.Port, config.SMTP.User, config.SMTP.Password, config.SMTP.AdminEmail) var consentRepo repositories.ConsentRepositoryInterface = &repositories.ConsentRepository{} consentService := &services.ConsentService{Repo: consentRepo} diff --git a/internal/middlewares/csrf_middleware.go b/internal/middlewares/csrf_middleware.go index 217ca19..847fe71 100644 --- a/internal/middlewares/csrf_middleware.go +++ b/internal/middlewares/csrf_middleware.go @@ -1,14 +1,16 @@ package middlewares import ( - "GoMembership/internal/config" - "GoMembership/internal/utils" - "GoMembership/pkg/logger" "crypto/hmac" "crypto/sha256" "encoding/base64" "net/http" "strings" + + "GoMembership/internal/config" + "GoMembership/internal/server" + "GoMembership/internal/utils" + "GoMembership/pkg/logger" ) // GenerateCSRFToken generates HMAC-signed CSRF token @@ -46,7 +48,7 @@ func CSRFMiddleware(next http.Handler) http.Handler { next.ServeHTTP(w, r) return } - csrfSecret := config.LoadConfig().Auth.CSRFSecret + csrfSecret := config.Auth.CSRFSecret // Retrieve CSRF token from request (e.g., from cookie, header, or form data) csrfToken := r.Header.Get("X-CSRF-Token") @@ -78,7 +80,7 @@ func GenerateCSRFTokenHandler(w http.ResponseWriter, r *http.Request) { sessionID := "exampleSessionID123" // Generate HMAC-signed CSRF token - csrfToken := GenerateCSRFToken(sessionID, config.LoadConfig().Auth.CSRFSecret) + csrfToken := GenerateCSRFToken(sessionID, config.Auth.CSRFSecret) // Set CSRF token in a cookie (example) http.SetCookie(w, &http.Cookie{ diff --git a/internal/server/server.go b/internal/server/server.go index dfe7947..226d103 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -17,14 +17,15 @@ import ( ) func Run() { - cfg := config.LoadConfig() - logger.Info.Printf("Config: %+v", cfg) - err := database.InitDB(cfg.DB.Path) + config.LoadConfig() + logger.Info.Printf("Config loaded: %#v", config.CFG) + + err := database.InitDB(config.DB.Path) if err != nil { logger.Error.Fatalf("Couldn't init database: %v", err) } - emailService := services.NewEmailService(cfg.SMTP.Host, cfg.SMTP.Port, cfg.SMTP.User, cfg.SMTP.Password, cfg.SMTP.AdminEmail) + emailService := services.NewEmailService(config.SMTP.Host, config.SMTP.Port, config.SMTP.User, config.SMTP.Password, config.SMTP.AdminEmail) var consentRepo repositories.ConsentRepositoryInterface = &repositories.ConsentRepository{} consentService := &services.ConsentService{Repo: consentRepo} diff --git a/internal/services/email_service.go b/internal/services/email_service.go index 482dfcb..e74d2b8 100644 --- a/internal/services/email_service.go +++ b/internal/services/email_service.go @@ -37,7 +37,7 @@ func (s *EmailService) SendEmail(to string, subject string, body string) error { func ParseTemplate(filename string, data interface{}) (string, error) { // Read the email template file - templateDir := config.LoadConfig().Templates.MailDir + templateDir := config.Templates.MailDir tpl, err := template.ParseFiles(templateDir + "/" + filename) if err != nil { logger.Error.Printf("Failed to parse email template: %v", err) @@ -106,20 +106,20 @@ func (s *EmailService) SendWelcomeEmail(user *models.User) error { func (s *EmailService) NotifyAdminOfNewUser(user *models.User) error { // Prepare data to be injected into the template data := struct { - Company string - FirstName string + City string + Email string LastName string MembershipModel string - MembershipID int64 - MembershipFee float32 - RentalFee float32 Address string - ZipCode string - City string - DateOfBirth string - Email string - Phone string IBAN string + FirstName string + Phone string + DateOfBirth string + Company string + ZipCode string + MembershipID int64 + RentalFee float32 + MembershipFee float32 }{ Company: *user.Company, FirstName: user.FirstName, @@ -143,5 +143,5 @@ func (s *EmailService) NotifyAdminOfNewUser(user *models.User) error { logger.Error.Print("Couldn't send admin notification mail") return err } - return s.SendEmail(config.LoadConfig().SMTP.AdminEmail, subject, body) + return s.SendEmail(config.SMTP.AdminEmail, subject, body) }