diff --git a/cmd_tools.go b/cmd_tools.go new file mode 100644 index 0000000..1235f5e --- /dev/null +++ b/cmd_tools.go @@ -0,0 +1,66 @@ +package main + +import ( + "bytes" + "fmt" + "math/rand" + + pwgen "github.com/sethvargo/go-password/password" + "golang.org/x/crypto/bcrypt" + "golang.org/x/crypto/ssh/terminal" +) + +type PasswordParam struct { + Password string `name:"password" help:"The password to be set. Empty to prompt."` + GeneratePassword bool `name:"generate-password" help:"If set, the password is auto generated."` + GeneratePasswordLength int `name:"password-length" help:"If generate-password is set, this specified the length of the generated password." default:"32"` +} + +func (p *PasswordParam) acquirePassword() (string, error) { + if p.GeneratePassword { + // math.rand is not secure. For determining the number of digits in a password + // it should suffice, though. Beware that we expect here that we at least initialized + // the seed somewhere. + pw, err := pwgen.Generate(p.GeneratePasswordLength, rand.Intn(p.GeneratePasswordLength/2), 0, false, true) + if err != nil { + return "", fmt.Errorf("cannot generate password: %w", err) + } + + p.Password = pw + + fmt.Printf("Password generated: %s\n", p.Password) + } + + if p.Password == "" { + fmt.Printf("Enter password: ") + bytePassword, err := terminal.ReadPassword(0) + if err != nil { + fmt.Println() + return "", fmt.Errorf("error reading password: %w", err) + } + fmt.Printf("\nRepeat password: ") + bytePasswordRepeat, err := terminal.ReadPassword(0) + if err != nil { + fmt.Println() + return "", fmt.Errorf("error reading password: %w", err) + } + fmt.Println() + + if !bytes.Equal(bytePassword, bytePasswordRepeat) { + return "", fmt.Errorf("passwords do not match") + } + + p.Password = string(bytePassword) + } + + if p.Password == "" { + return "", fmt.Errorf("empty password supplied") + } + + hash, err := bcrypt.GenerateFromPassword([]byte(p.Password), 0) + if err != nil { + return "", fmt.Errorf("cannot hash password: %w", err) + } + + return string(hash), nil +} diff --git a/cmd_user.go b/cmd_user.go index a23ad75..9c0ba55 100644 --- a/cmd_user.go +++ b/cmd_user.go @@ -53,9 +53,9 @@ func (cmd CmdUserList) Run(app *app) error { } type CmdUserAdd struct { - Username string `arg:"" name:"username" help:"The username to be added."` - Password string `name:"password" help:"The password of the new user."` - Role GlobalRole `name:"role" default:"user" help:"Role of the user. 'admin' or ' user'"` + Username string `arg:"" name:"username" help:"The username to be added."` + PasswordParam + Role GlobalRole `name:"role" default:"user" help:"Role of the user. 'admin' or ' user'"` } func (cmd CmdUserAdd) Run(app *app) error { @@ -66,14 +66,14 @@ func (cmd CmdUserAdd) Run(app *app) error { return fmt.Errorf("invalid user role") } - hash, err := bcrypt.GenerateFromPassword([]byte(cmd.Password), 0) + password, err := cmd.acquirePassword() if err != nil { - return fmt.Errorf("cannot hash password: %w", err) + return fmt.Errorf("cannot acquire password for user: %w", err) } user := User{ Username: cmd.Username, - Password: string(hash), + Password: password, Role: cmd.Role, } return app.userStore.AddUser(user) diff --git a/main.go b/main.go index 5639d40..f81471d 100644 --- a/main.go +++ b/main.go @@ -26,6 +26,11 @@ package main import ( + crand "crypto/rand" + "math" + "math/big" + "math/rand" + "github.com/alecthomas/kong" ) @@ -38,6 +43,14 @@ type app struct { } func main() { + // When we need simple randomization (not cryptographically secure one), we should at least start + // with some non-default-seed. + seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64)) + if err != nil { + panic("cannot initialize rand: " + err.Error()) + } + rand.Seed(seed.Int64()) + var app app ctx := kong.Parse(&app) @@ -50,32 +63,6 @@ func main() { ctx.FatalIfErrorf(ctx.Run(&app)) - //if *genPassword { - // // math.rand is not secure. For determining the number of digits in a password - // // it should suffice, though. - // rand.Seed(time.Now().UnixNano()) - // pwLen := 32 - // pw, err := pwgen.Generate(pwLen, rand.Intn(pwLen/2), 0, false, true) - // if err != nil { - // panic(err) - // } - // hash, err := bcrypt.GenerateFromPassword([]byte(pw), 0) - // if err != nil { - // panic(err) - // } - // - // fmt.Printf("Password: %s\n", pw) - // fmt.Printf(" Hash: %s\n", hash) - // return - //} else if *hashPassword != "" { - // hash, err := bcrypt.GenerateFromPassword([]byte(*hashPassword), 0) - // if err != nil { - // panic(err) - // } - // fmt.Println(string(hash)) - // return - //} - //c := LoadConfig(*configFile) // //h := &webdav.Handler{}