69 lines
2.6 KiB
Go
69 lines
2.6 KiB
Go
|
|
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
|
|||
|
|
}
|