axe/internal/auth/password.go

69 lines
2.6 KiB
Go
Raw Normal View History

2026-10-18 19:07:25 +00:00
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
}