- 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
This commit is contained in:
		| @@ -9,7 +9,6 @@ import ( | |||||||
| 	"gitea.nehmer.net/torben/dyndns/service" | 	"gitea.nehmer.net/torben/dyndns/service" | ||||||
| 	"github.com/spf13/cobra" | 	"github.com/spf13/cobra" | ||||||
| 	"github.com/spf13/viper" | 	"github.com/spf13/viper" | ||||||
| 	"golang.org/x/crypto/bcrypt" |  | ||||||
| 	"golang.org/x/crypto/ssh/terminal" | 	"golang.org/x/crypto/ssh/terminal" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -36,7 +35,7 @@ have to store a plaintext password.`, | |||||||
| 			log.Fatalln("the passwords do not match.") | 			log.Fatalln("the passwords do not match.") | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) | 		hash, err := service.HashPassword(password) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			log.Fatalf("failed to create password hash: %v", err) | 			log.Fatalf("failed to create password hash: %v", err) | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -1,21 +1,57 @@ | |||||||
| package service | package service | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"log" | 	"log" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path" | 	"path" | ||||||
|  |  | ||||||
| 	"github.com/spf13/viper" | 	"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 := fmt.Sprintf("%s/%s.yml", C.UsersConfigDir, username) | ||||||
| 	configFile = path.Clean(configFile) | 	configFile = path.Clean(configFile) | ||||||
| 	log.Printf("Trying to load config file %s for user %s", configFile, username) | 	log.Printf("Trying to load config file %s for user %s", configFile, username) | ||||||
|  |  | ||||||
| 	if _, err := os.Stat(configFile); err != nil { | 	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() | 	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 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) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| username: test | username: test | ||||||
| password: $2a$10$7eYMA3zoyDb.2dM6bbeiqexxS9LhLz7XM.Q0EL0VHVbcxyfRkfp7. | password: $2a$10$ymHFfGjKINWsUKwEo5xp.efN6DR6NkiRCXsbH4VWo0Nnzma8Yc.82 | ||||||
| router:  | router:  | ||||||
|   DNS: brandfeld.dyn.local |   DNS: brandfeld.dyn.local | ||||||
|   NFT: |   NFT: | ||||||
| @@ -7,10 +7,16 @@ router: | |||||||
|     set4: blackhole |     set4: blackhole | ||||||
|     set6: blackhole6 |     set6: blackhole6 | ||||||
| others: | others: | ||||||
|   - registerv4: true |   - DNS: atlantis.dyn.local | ||||||
|     v6iid: ::dead:beef:dead:beef |     v6iid: ::dead:beef:dead:beef | ||||||
|     DNS: atlantis.dynlocal |     registerv4: true | ||||||
|     NFT:  |     NFT:  | ||||||
|       table: sshguard |       table: sshguard | ||||||
|       set4: blackhole |       set4: blackhole | ||||||
|       set6: blackhole6 |       set6: blackhole6 | ||||||
|  |   - DNS: troya.dyn.local | ||||||
|  |     v6iid: ::cafe:babe:deca:fbad | ||||||
|  |     registerv4: false | ||||||
|  |     NFT: | ||||||
|  |       table: sshguard | ||||||
|  |       set6: blackhole6 | ||||||
| @@ -7,7 +7,6 @@ import ( | |||||||
|  |  | ||||||
| 	"gitea.nehmer.net/torben/dyndns/service" | 	"gitea.nehmer.net/torben/dyndns/service" | ||||||
| 	"github.com/gorilla/mux" | 	"github.com/gorilla/mux" | ||||||
| 	"golang.org/x/crypto/bcrypt" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func Server() { | func Server() { | ||||||
| @@ -15,7 +14,7 @@ func Server() { | |||||||
| 	r.StrictSlash(true) | 	r.StrictSlash(true) | ||||||
|  |  | ||||||
| 	r.HandleFunc("/hello", handleHello) | 	r.HandleFunc("/hello", handleHello) | ||||||
| 	r.HandleFunc("/update", handleDNSUpdate) | 	r.HandleFunc("/update", handleUpdate) | ||||||
|  |  | ||||||
| 	log.Printf("Listening to: %s", C.ListenAddress) | 	log.Printf("Listening to: %s", C.ListenAddress) | ||||||
| 	go log.Fatal(http.ListenAndServe(C.ListenAddress, r)) | 	go log.Fatal(http.ListenAndServe(C.ListenAddress, r)) | ||||||
| @@ -30,7 +29,7 @@ func handleHello(w http.ResponseWriter, r *http.Request) { | |||||||
| 	fmt.Fprint(w, "<html><body><p>Hello World</p></body></html>") | 	fmt.Fprint(w, "<html><body><p>Hello World</p></body></html>") | ||||||
| } | } | ||||||
|  |  | ||||||
| func handleDNSUpdate(w http.ResponseWriter, r *http.Request) { | func handleUpdate(w http.ResponseWriter, r *http.Request) { | ||||||
| 	err := r.ParseForm() | 	err := r.ParseForm() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		http.Error(w, err.Error(), http.StatusBadRequest) | 		http.Error(w, err.Error(), http.StatusBadRequest) | ||||||
| @@ -43,11 +42,16 @@ func handleDNSUpdate(w http.ResponseWriter, r *http.Request) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	v, err := service.LoadConfigForUser(ur.UserName) | 	uc, err := service.LoadConfigForUser(ur.UserName, ur.Password) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | 		if _, ok := err.(*service.UnauthorizedError); ok { | ||||||
|  | 			http.Error(w, err.Error(), http.StatusUnauthorized) | ||||||
|  | 		} else { | ||||||
| 			http.Error(w, err.Error(), http.StatusInternalServerError) | 			http.Error(w, err.Error(), http.StatusInternalServerError) | ||||||
|  | 		} | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | 	v := uc.DB | ||||||
|  |  | ||||||
| 	w.Header().Set("Content-Type", "text/plain") | 	w.Header().Set("Content-Type", "text/plain") | ||||||
| 	fmt.Fprintln(w, "OK") | 	fmt.Fprintln(w, "OK") | ||||||
| @@ -57,7 +61,6 @@ func handleDNSUpdate(w http.ResponseWriter, r *http.Request) { | |||||||
| 	log.Println(ur.IPv6Net.Mask) | 	log.Println(ur.IPv6Net.Mask) | ||||||
| 	log.Println(v.AllSettings()) | 	log.Println(v.AllSettings()) | ||||||
| 	log.Printf("Request PW: %s, Config PW: %s", ur.Password, v.GetString("password")) | 	log.Printf("Request PW: %s, Config PW: %s", ur.Password, v.GetString("password")) | ||||||
|  | 	fmt.Fprintln(w, "Unmarshalled userconfig:") | ||||||
| 	err = bcrypt.CompareHashAndPassword([]byte(v.GetString("password")), []byte(ur.Password)) | 	fmt.Fprintln(w, uc.PrettyPrint()) | ||||||
| 	log.Printf("PW Compare Result: %v", err) |  | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user