From 9a31bbc91248916f4e6858db9c3c0496d9fc7470 Mon Sep 17 00:00:00 2001 From: Torben Nehmer Date: Sun, 22 Aug 2021 14:36:55 +0200 Subject: [PATCH] - Clarified web handler name - Implemented Configuration Loader with unauthorized error handling - More elaborate test user config - Use Viper Unmarshalling for User Config loading - Centralized password hasing code --- cmd/hash-password.go | 3 +- service/userconfig.go | 79 +++++++++++++++++++++++++++++++++++++++++-- users/test.yml | 14 +++++--- webapi/server.go | 19 ++++++----- 4 files changed, 98 insertions(+), 17 deletions(-) diff --git a/cmd/hash-password.go b/cmd/hash-password.go index df200fc..be71751 100644 --- a/cmd/hash-password.go +++ b/cmd/hash-password.go @@ -9,7 +9,6 @@ import ( "gitea.nehmer.net/torben/dyndns/service" "github.com/spf13/cobra" "github.com/spf13/viper" - "golang.org/x/crypto/bcrypt" "golang.org/x/crypto/ssh/terminal" ) @@ -36,7 +35,7 @@ have to store a plaintext password.`, log.Fatalln("the passwords do not match.") } - hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + hash, err := service.HashPassword(password) if err != nil { log.Fatalf("failed to create password hash: %v", err) } diff --git a/service/userconfig.go b/service/userconfig.go index 02d844e..55f06cf 100644 --- a/service/userconfig.go +++ b/service/userconfig.go @@ -1,21 +1,57 @@ package service import ( + "encoding/json" "fmt" "log" "os" "path" "github.com/spf13/viper" + "golang.org/x/crypto/bcrypt" ) -func LoadConfigForUser(username string) (*viper.Viper, error) { +type UnauthorizedError string + +func (uae UnauthorizedError) Error() string { + return "Invalid user or password" +} + +type UserConfig struct { + DB *viper.Viper + + UserName string + PassWord string + Router UserConfigRouter + Others []UserConfigOther +} + +type UserConfigRouter struct { + DNS string + NFT UserConfigNFT +} + +type UserConfigOther struct { + DNS string + V6IID string + RegisterV4 bool + NFT UserConfigNFT +} + +type UserConfigNFT struct { + Table string + Set4 string + Set6 string +} + +func LoadConfigForUser(username string, password string) (*UserConfig, error) { configFile := fmt.Sprintf("%s/%s.yml", C.UsersConfigDir, username) configFile = path.Clean(configFile) log.Printf("Trying to load config file %s for user %s", configFile, username) if _, err := os.Stat(configFile); err != nil { - return nil, fmt.Errorf("cannot stat the file %s: %v", configFile, err) + log.Printf("cannot stat the file %s: %v", configFile, err) + return nil, UnauthorizedError("cannot stat") } v := viper.New() @@ -26,5 +62,42 @@ func LoadConfigForUser(username string) (*viper.Viper, error) { return nil, fmt.Errorf("failed to parse config file %s: %v", configFile, err) } - return v, nil + result := &UserConfig{DB: v} + + err = result.PasswordCheck(password) + if err != nil { + return nil, UnauthorizedError("pwcheck failed") + } + + err = result.DB.Unmarshal(result) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal config file %s: %v", configFile, err) + } + + return result, nil +} + +func HashPassword(pw []byte) (string, error) { + hash, err := bcrypt.GenerateFromPassword(pw, bcrypt.DefaultCost) + if err != nil { + return "", fmt.Errorf("failed to create password hash: %v", err) + } + return string(hash), nil +} + +func (ur *UserConfig) PasswordCheck(pwToCheck string) error { + hashedPassword := []byte(ur.DB.GetString("password")) + bytePwToCheck := []byte(pwToCheck) + + err := bcrypt.CompareHashAndPassword(hashedPassword, bytePwToCheck) + + return err +} + +func (uc *UserConfig) PrettyPrint() string { + s, err := json.MarshalIndent(uc, "", " ") + if err != nil { + log.Fatalf("Failed to pretty print UpdateConfig via JSON: %v", err) + } + return string(s) } diff --git a/users/test.yml b/users/test.yml index 983fa64..c04df9e 100644 --- a/users/test.yml +++ b/users/test.yml @@ -1,16 +1,22 @@ username: test -password: $2a$10$7eYMA3zoyDb.2dM6bbeiqexxS9LhLz7XM.Q0EL0VHVbcxyfRkfp7. -router: +password: $2a$10$ymHFfGjKINWsUKwEo5xp.efN6DR6NkiRCXsbH4VWo0Nnzma8Yc.82 +router: DNS: brandfeld.dyn.local NFT: table: sshguard set4: blackhole set6: blackhole6 others: - - registerv4: true + - DNS: atlantis.dyn.local v6iid: ::dead:beef:dead:beef - DNS: atlantis.dynlocal + registerv4: true NFT: table: sshguard set4: blackhole + set6: blackhole6 + - DNS: troya.dyn.local + v6iid: ::cafe:babe:deca:fbad + registerv4: false + NFT: + table: sshguard set6: blackhole6 \ No newline at end of file diff --git a/webapi/server.go b/webapi/server.go index 276ed03..a5b7499 100644 --- a/webapi/server.go +++ b/webapi/server.go @@ -7,7 +7,6 @@ import ( "gitea.nehmer.net/torben/dyndns/service" "github.com/gorilla/mux" - "golang.org/x/crypto/bcrypt" ) func Server() { @@ -15,7 +14,7 @@ func Server() { r.StrictSlash(true) r.HandleFunc("/hello", handleHello) - r.HandleFunc("/update", handleDNSUpdate) + r.HandleFunc("/update", handleUpdate) log.Printf("Listening to: %s", C.ListenAddress) go log.Fatal(http.ListenAndServe(C.ListenAddress, r)) @@ -30,7 +29,7 @@ func handleHello(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "

Hello World

") } -func handleDNSUpdate(w http.ResponseWriter, r *http.Request) { +func handleUpdate(w http.ResponseWriter, r *http.Request) { err := r.ParseForm() if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) @@ -43,11 +42,16 @@ func handleDNSUpdate(w http.ResponseWriter, r *http.Request) { return } - v, err := service.LoadConfigForUser(ur.UserName) + uc, err := service.LoadConfigForUser(ur.UserName, ur.Password) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + if _, ok := err.(*service.UnauthorizedError); ok { + http.Error(w, err.Error(), http.StatusUnauthorized) + } else { + http.Error(w, err.Error(), http.StatusInternalServerError) + } return } + v := uc.DB w.Header().Set("Content-Type", "text/plain") fmt.Fprintln(w, "OK") @@ -57,7 +61,6 @@ func handleDNSUpdate(w http.ResponseWriter, r *http.Request) { log.Println(ur.IPv6Net.Mask) log.Println(v.AllSettings()) log.Printf("Request PW: %s, Config PW: %s", ur.Password, v.GetString("password")) - - err = bcrypt.CompareHashAndPassword([]byte(v.GetString("password")), []byte(ur.Password)) - log.Printf("PW Compare Result: %v", err) + fmt.Fprintln(w, "Unmarshalled userconfig:") + fmt.Fprintln(w, uc.PrettyPrint()) }