package api import ( "context" "fmt" "strings" "github.com/sirupsen/logrus" ) // BCrypt hashed passwords have a 72 character limit const MaxPasswordLength = 72 // WeakPasswordError encodes an error that a password does not meet strength // requirements. It is handled specially in errors.go as it gets transformed to // a HTTPError with a special weak_password field that encodes the Reasons // slice. type WeakPasswordError struct { Message string `json:"message,omitempty"` Reasons []string `json:"reasons,omitempty"` } func (e *WeakPasswordError) Error() string { return e.Message } func (a *API) checkPasswordStrength(ctx context.Context, password string) error { config := a.config if len(password) > MaxPasswordLength { return badRequestError(ErrorCodeValidationFailed, fmt.Sprintf("Password cannot be longer than %v characters", MaxPasswordLength)) } var messages, reasons []string if len(password) < config.Password.MinLength { reasons = append(reasons, "length") messages = append(messages, fmt.Sprintf("Password should be at least %d characters.", config.Password.MinLength)) } for _, characterSet := range config.Password.RequiredCharacters { if characterSet != "" && !strings.ContainsAny(password, characterSet) { reasons = append(reasons, "characters") messages = append(messages, fmt.Sprintf("Password should contain at least one character of each: %s.", strings.Join(config.Password.RequiredCharacters, ", "))) break } } if config.Password.HIBP.Enabled { pwned, err := a.hibpClient.Check(ctx, password) if err != nil { if config.Password.HIBP.FailClosed { return internalServerError("Unable to perform password strength check with HaveIBeenPwned.org.").WithInternalError(err) } else { logrus.WithError(err).Warn("Unable to perform password strength check with HaveIBeenPwned.org, pwned passwords are being allowed") } } else if pwned { reasons = append(reasons, "pwned") messages = append(messages, "Password is known to be weak and easy to guess, please choose a different one.") } } if len(reasons) > 0 { return &WeakPasswordError{ Message: strings.Join(messages, " "), Reasons: reasons, } } return nil }