72 lines
2.0 KiB
Go
72 lines
2.0 KiB
Go
package auth
|
|
|
|
import (
|
|
"errors"
|
|
"unicode"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
const (
|
|
// BcryptCost is the bcrypt hashing cost. 12 balances security and performance.
|
|
BcryptCost = 12
|
|
|
|
// MinPasswordLength is the minimum allowed password length.
|
|
MinPasswordLength = 8
|
|
// MaxPasswordLength is the maximum allowed password length (bcrypt limit).
|
|
MaxPasswordLength = 72
|
|
)
|
|
|
|
var (
|
|
// ErrPasswordTooShort is returned when a password is below the minimum length.
|
|
ErrPasswordTooShort = errors.New("password must be at least 8 characters")
|
|
// ErrPasswordTooLong is returned when a password exceeds the bcrypt limit.
|
|
ErrPasswordTooLong = errors.New("password must be at most 72 characters")
|
|
// ErrPasswordWeak is returned when a password lacks required character types.
|
|
ErrPasswordWeak = errors.New("password must contain at least one uppercase letter, one lowercase letter, and one number")
|
|
)
|
|
|
|
// HashPassword hashes a plaintext password using bcrypt.
|
|
func HashPassword(password string) (string, error) {
|
|
hash, err := bcrypt.GenerateFromPassword([]byte(password), BcryptCost)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(hash), nil
|
|
}
|
|
|
|
// CheckPassword compares a plaintext password against a bcrypt hash.
|
|
// Returns true if the password matches.
|
|
func CheckPassword(password, hash string) bool {
|
|
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil
|
|
}
|
|
|
|
// ValidatePasswordStrength checks that a password meets minimum complexity requirements.
|
|
// Returns nil if the password is acceptable.
|
|
func ValidatePasswordStrength(password string) error {
|
|
if len(password) < MinPasswordLength {
|
|
return ErrPasswordTooShort
|
|
}
|
|
if len(password) > MaxPasswordLength {
|
|
return ErrPasswordTooLong
|
|
}
|
|
|
|
var hasUpper, hasLower, hasDigit bool
|
|
for _, r := range password {
|
|
switch {
|
|
case unicode.IsUpper(r):
|
|
hasUpper = true
|
|
case unicode.IsLower(r):
|
|
hasLower = true
|
|
case unicode.IsDigit(r):
|
|
hasDigit = true
|
|
}
|
|
}
|
|
|
|
if !hasUpper || !hasLower || !hasDigit {
|
|
return ErrPasswordWeak
|
|
}
|
|
|
|
return nil
|
|
}
|