Files
kjol/go/auth/password.go
2026-07-14 16:49:56 -04:00

68 lines
2.1 KiB
Go

package auth
import (
"crypto/rand"
"encoding/hex"
"errors"
"strconv"
"strings"
"unicode"
)
// CheckPassword reports whether a password satisfies the configured policy,
// returning an error phrased for the end user (the caller can surface it
// verbatim on a signup or change-password form).
func (a *Authenticator) CheckPassword(password string) error {
return CheckPasswordPolicy(a.cfg.Password, password)
}
// CheckPasswordPolicy is CheckPassword against an explicit policy, for callers
// that validate a password without an Authenticator in hand.
func CheckPasswordPolicy(policy PasswordPolicy, password string) error {
if strings.TrimSpace(password) == "" {
return errors.New("Password cannot be blank.")
}
if len(password) < policy.MinLength {
return errors.New("Password must be at least " + strconv.Itoa(policy.MinLength) + " characters long.")
}
var uppercase, lowercase, numbers, symbols int
for _, r := range password {
switch {
case unicode.IsUpper(r):
uppercase++
case unicode.IsLower(r):
lowercase++
case unicode.IsNumber(r):
numbers++
default:
symbols++
}
}
switch {
case uppercase < policy.RequiredUppercase:
return errors.New("Password must contain at least " + strconv.Itoa(policy.RequiredUppercase) + " uppercase character(s).")
case lowercase < policy.RequiredLowercase:
return errors.New("Password must contain at least " + strconv.Itoa(policy.RequiredLowercase) + " lowercase character(s).")
case numbers < policy.RequiredNumbers:
return errors.New("Password must contain at least " + strconv.Itoa(policy.RequiredNumbers) + " number(s).")
case symbols < policy.RequiredSymbols:
return errors.New("Password must contain at least " + strconv.Itoa(policy.RequiredSymbols) + " symbol(s).")
}
return nil
}
// GeneratePasswordResetToken returns a 32-byte crypto-random token, hex-encoded
// (64 characters). The app stores it against the user with an expiry and mails
// the same string out.
func GeneratePasswordResetToken() (string, error) {
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
return "", err
}
return hex.EncodeToString(b), nil
}