package auth import ( "errors" "unicode" "golang.org/x/crypto/bcrypt" ) // Политика установки пароля. Применяется только при создании/смене // пароля: уже существующие аккаунты входят со своими старыми паролями. const ( // MinPasswordLen — минимум символов (считаем руны, не байты: // кириллический символ — уже два байта). MinPasswordLen = 10 // MaxPasswordLen — потолок bcrypt: он режет вход длиннее 72 байт, // пароль «длиннее максимума» молча стал бы короче. MaxPasswordLen = 72 ) // Ошибки политики пароля. Тексты — короткие и англоязычные, // как и все ошибки API. var ( ErrPasswordShort = errors.New("password must be at least 10 characters") ErrPasswordLong = errors.New("password must be at most 72 characters") ErrPasswordWeak = errors.New("password must contain letters and digits") ) // ValidatePassword проверяет пароль по политике: длина 10..72 и // обязательные буквы И цифры. Спецсимволы не требуются — их принуждение // чаще порождает «P@ssw0rd!», чем стойкость; зато цифры и буквы отсекают // «password» и «1234567890». func ValidatePassword(password string) error { n := 0 var hasLetter, hasDigit bool for _, r := range password { n++ switch { case unicode.IsLetter(r): hasLetter = true case unicode.IsDigit(r): hasDigit = true } } if n < MinPasswordLen { return ErrPasswordShort } if len(password) > MaxPasswordLen { return ErrPasswordLong } if !hasLetter || !hasDigit { return ErrPasswordWeak } return nil } // HashPassword считает bcrypt-хеш с cost 12. Ошибка возможна только // на очень длинных паролях (bcrypt режет вход длиннее 72 байт). func HashPassword(password string) (string, error) { bytes, err := bcrypt.GenerateFromPassword([]byte(password), 12) return string(bytes), err } // CheckPassword сверяет пароль с хешем. Сравнение за постоянное время — // перебор по времени ответа не даёт выигрыша. func CheckPassword(hash, password string) bool { return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil }