diff --git a/.gitignore b/.gitignore index e23f3a1..95e2b2b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -database.db +appdata .idea *.exe \ No newline at end of file diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..93cbd19 --- /dev/null +++ b/config/config.go @@ -0,0 +1,56 @@ +package config + +import ( + "log" + "os" + "path/filepath" + + "gopkg.in/yaml.v3" +) + +type Config struct { + Port string `yaml:"port"` + DatabasePath string `yaml:"database_path"` +} + +const configPath = "./appdata/config.yaml" + +func CheckIfExists() error { + _, err := os.Stat(configPath) + if err == nil { + return nil + } + + if !os.IsNotExist(err) { + return err + } + + log.Printf("Config file %s doesn't exist, creating...", configPath) + err = os.MkdirAll(filepath.Dir(configPath), 0755) + if err != nil { + return err + } + + defaultConfig := Config{ + Port: "8080", + DatabasePath: "./appdata/database.db", + } + + data, err := yaml.Marshal(defaultConfig) + if err != nil { + return err + } + + return os.WriteFile(configPath, data, 0644) +} +func LoadConfig() (Config, error) { + var cfg Config + + file, err := os.ReadFile(configPath) + if err != nil { + return cfg, err + } + + err = yaml.Unmarshal(file, &cfg) + return cfg, err +} diff --git a/go.mod b/go.mod index 1d83644..0df70d5 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,10 @@ go 1.26.0 require ( github.com/glebarez/go-sqlite v1.22.0 + github.com/golang-jwt/jwt/v5 v5.3.1 github.com/google/uuid v1.6.0 golang.org/x/crypto v0.48.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( diff --git a/go.sum b/go.sum index a7617e8..5ec7347 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -15,6 +17,10 @@ golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVo golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= modernc.org/libc v1.37.6 h1:orZH3c5wmhIQFTXF+Nt+eeauyd+ZIt2BX6ARe+kD+aw= modernc.org/libc v1.37.6/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= diff --git a/main.go b/main.go index c61a763..9effc4e 100644 --- a/main.go +++ b/main.go @@ -2,20 +2,21 @@ package main import ( "log" - "net/http" - "shap-planner-backend/handlers" + "shap-planner-backend/server" "shap-planner-backend/storage" ) func main() { - err := storage.InitDB("database.db") + var SERVER = server.InitServer() + + err := storage.InitDB(SERVER.DatabasePath) if err != nil { log.Fatal(err) } - http.HandleFunc("/register", handlers.Register) - http.HandleFunc("/login", handlers.Login) - - log.Println("Server läuft auf :8080") - log.Fatal(http.ListenAndServe(":8080", nil)) + SERVER.Run() +} + +func Setup() { + //TODO: first configuration } diff --git a/server/server.go b/server/server.go new file mode 100644 index 0000000..5a25b0b --- /dev/null +++ b/server/server.go @@ -0,0 +1,50 @@ +package server + +import ( + "log" + "net/http" + "os" + "shap-planner-backend/config" + "shap-planner-backend/handlers" +) + +type Server struct { + Port string + JWTSecret []byte + DatabasePath string +} + +func InitServer() *Server { + + err := config.CheckIfExists() + if err != nil { + log.Fatal(err) + } + + cfg, err := config.LoadConfig() + if err != nil { + log.Fatal(err) + } + + jwtSecret := os.Getenv("SHAP_JWT_SECRET") + if jwtSecret == "" { + log.Fatal("SHAP_JWT_SECRET environment variable not set.") + } + if len(jwtSecret) < 32 { + log.Fatal("SHAP_JWT_SECRET must be at least 32 characters long.") + } + + return &Server{ + Port: cfg.Port, + JWTSecret: []byte(jwtSecret), + DatabasePath: cfg.DatabasePath, + } +} + +func (server *Server) Run() { + http.HandleFunc("/register", handlers.Register) + http.HandleFunc("/login", handlers.Login) + + log.Printf("Listening on port %s", server.Port) + log.Fatal(http.ListenAndServe(":"+server.Port, nil)) +} diff --git a/utils/util.go b/utils/util.go index 2b8083f..1126979 100644 --- a/utils/util.go +++ b/utils/util.go @@ -1,20 +1,53 @@ package utils import ( + "os" + "time" + + "crypto/rand" + "encoding/base64" + + "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" "golang.org/x/crypto/bcrypt" ) +var secret = []byte(os.Getenv("SHAP_JWT_SECRET")) + +// Password Validation func HashPassword(password string) (string, error) { bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14) return string(bytes), err } - func CheckPasswordHash(password, hash string) bool { err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) return err == nil } +// JWT Validation +func GenerateJWT(userID string) (string, error) { + claims := jwt.MapClaims{ + "userId": userID, + "exp": time.Now().Add(time.Hour * 1).Unix(), + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + return token.SignedString(secret) +} +func ValidateJWT(tokenString string) (*jwt.Token, error) { + return jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + return secret, nil + }) +} + func GenerateUUID() string { return uuid.New().String() } +func GenerateSecret() string { + b := make([]byte, 64) + _, err := rand.Read(b) + if err != nil { + return err.Error() + } + return base64.StdEncoding.EncodeToString(b) +}