From 87b6d724775bd350da0e59d0516c76f906bcaa1a Mon Sep 17 00:00:00 2001 From: Michael Mayer Date: Mon, 19 Jun 2023 17:24:02 +0200 Subject: [PATCH] CLI: Improve length check in "photoprism passwd" command #3482 Signed-off-by: Michael Mayer --- internal/commands/passwd.go | 9 ++++++--- internal/commands/users_add.go | 7 +++++-- internal/entity/auth_user.go | 6 ++++-- internal/entity/auth_user_add.go | 5 ++++- internal/entity/password.go | 8 +++++--- pkg/clean/auth.go | 10 ++-------- 6 files changed, 26 insertions(+), 19 deletions(-) diff --git a/internal/commands/passwd.go b/internal/commands/passwd.go index 11b5cd046..41d896b6d 100644 --- a/internal/commands/passwd.go +++ b/internal/commands/passwd.go @@ -15,6 +15,7 @@ import ( "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/rnd" + "github.com/photoprism/photoprism/pkg/txt" ) // PasswdCommand configures the command name, flags, and action. @@ -67,12 +68,14 @@ func passwdAction(ctx *cli.Context) error { return fmt.Errorf("user %s has been deleted", clean.LogQuote(id)) } - log.Infof("please enter a new password for %s (minimum %d characters)\n", clean.Log(m.Username()), entity.PasswordLength) + log.Infof("please enter a new password for %s (%d-%d characters)\n", clean.Log(m.Username()), entity.PasswordLength, txt.ClipPassword) newPassword := getPassword("New Password: ") - if len(newPassword) < 6 { - return errors.New("new password is too short, please try again") + if len([]rune(newPassword)) < entity.PasswordLength { + return fmt.Errorf("password must have at least %d characters", entity.PasswordLength) + } else if len(newPassword) > txt.ClipPassword { + return fmt.Errorf("password must have less than %d characters", txt.ClipPassword) } retypePassword := getPassword("Retype Password: ") diff --git a/internal/commands/users_add.go b/internal/commands/users_add.go index 4df2db87b..9e753941c 100644 --- a/internal/commands/users_add.go +++ b/internal/commands/users_add.go @@ -11,6 +11,7 @@ import ( "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/pkg/clean" + "github.com/photoprism/photoprism/pkg/txt" ) // UsersAddCommand configures the command name, flags, and action. @@ -90,10 +91,12 @@ func usersAddAction(ctx *cli.Context) error { frm.UserEmail = clean.Email(res) } - if interactive && len(ctx.String("password")) < entity.PasswordLength { + if interactive && len([]rune(ctx.String("password"))) < entity.PasswordLength { validate := func(input string) error { - if len(input) < entity.PasswordLength { + if len([]rune(input)) < entity.PasswordLength { return fmt.Errorf("password must have at least %d characters", entity.PasswordLength) + } else if len(input) > txt.ClipPassword { + return fmt.Errorf("password must have less than %d characters", txt.ClipPassword) } return nil } diff --git a/internal/entity/auth_user.go b/internal/entity/auth_user.go index a71a826a2..7dc18ff91 100644 --- a/internal/entity/auth_user.go +++ b/internal/entity/auth_user.go @@ -31,7 +31,7 @@ const ( // UsernameLength specifies the minimum length of the username in characters. var UsernameLength = 1 -// PasswordLength specifies the minimum length of the password in characters. +// PasswordLength specifies the minimum length of a password in characters (runes, not bytes). var PasswordLength = 4 // UsersPath is the relative path for user assets. @@ -768,8 +768,10 @@ func (m *User) SetPassword(password string) error { return fmt.Errorf("only registered users can change their password") } - if len(password) < PasswordLength { + if len([]rune(password)) < PasswordLength { return fmt.Errorf("password must have at least %d characters", PasswordLength) + } else if len(password) > txt.ClipPassword { + return fmt.Errorf("password must have less than %d characters", txt.ClipPassword) } pw := NewPassword(m.UserUID, password, false) diff --git a/internal/entity/auth_user_add.go b/internal/entity/auth_user_add.go index de6630dad..db1e7e59d 100644 --- a/internal/entity/auth_user_add.go +++ b/internal/entity/auth_user_add.go @@ -7,14 +7,17 @@ import ( "github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/pkg/clean" + "github.com/photoprism/photoprism/pkg/txt" ) // AddUser creates a new user record and sets the password in a single transaction. func AddUser(frm form.User) error { user := NewUser().SetFormValues(frm) - if len(frm.Password) < PasswordLength { + if len([]rune(frm.Password)) < PasswordLength { return fmt.Errorf("password must have at least %d characters", PasswordLength) + } else if len(frm.Password) > txt.ClipPassword { + return fmt.Errorf("password must have less than %d characters", txt.ClipPassword) } if err := user.Validate(); err != nil { diff --git a/internal/entity/password.go b/internal/entity/password.go index 5b0c2fb8e..f906c9049 100644 --- a/internal/entity/password.go +++ b/internal/entity/password.go @@ -46,12 +46,14 @@ func NewPassword(uid, pw string, allowHash bool) Password { // SetPassword sets a new password stored as hash. func (m *Password) SetPassword(pw string, allowHash bool) error { + // Remove leading and trailing white space. pw = clean.Password(pw) - if l := len(pw); l > txt.ClipPassword { - return fmt.Errorf("password is too long") - } else if l < 1 { + // Check if password is too short or too long. + if len([]rune(pw)) < 1 { return fmt.Errorf("password is too short") + } else if len(pw) > txt.ClipPassword { + return fmt.Errorf("password must have less than %d characters", txt.ClipPassword) } // Check if string already is a bcrypt hash. diff --git a/pkg/clean/auth.go b/pkg/clean/auth.go index 2842d435f..61f656cb7 100644 --- a/pkg/clean/auth.go +++ b/pkg/clean/auth.go @@ -109,13 +109,7 @@ func Attr(s string) string { return list.ParseAttr(s).String() } -// Password returns the sanitized password string with trimmed whitespace. +// Password returns the password string with all leading and trailing white space removed. func Password(s string) string { - s = strings.TrimSpace(s) - - if s == "" || reject(s, txt.ClipPassword) { - return "" - } - - return s + return strings.TrimSpace(s) }