mirror of
https://github.com/volatiletech/authboss.git
synced 2024-11-28 08:58:38 +02:00
183 lines
4.4 KiB
Go
183 lines
4.4 KiB
Go
package authboss
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"regexp"
|
|
"unicode"
|
|
)
|
|
|
|
var blankRegex = regexp.MustCompile(`^\s*$`)
|
|
|
|
// Rules defines a ruleset by which a string can be validated.
|
|
type Rules struct {
|
|
// FieldName is the name of the field this is intended to validate.
|
|
FieldName string
|
|
// MatchError describes the MustMatch regexp to a user.
|
|
Required bool
|
|
MatchError string
|
|
MustMatch *regexp.Regexp
|
|
MinLength, MaxLength int
|
|
MinLetters int
|
|
MinLower, MinUpper int
|
|
MinNumeric int
|
|
MinSymbols int
|
|
AllowWhitespace bool
|
|
}
|
|
|
|
// Field names the field this ruleset applies to.
|
|
func (r Rules) Field() string {
|
|
return r.FieldName
|
|
}
|
|
|
|
// Errors returns an array of errors for each validation error that
|
|
// is present in the given string. Returns nil if there are no errors.
|
|
func (r Rules) Errors(toValidate string) ErrorList {
|
|
errs := make(ErrorList, 0)
|
|
|
|
ln := len(toValidate)
|
|
if r.Required && (ln == 0 || blankRegex.MatchString(toValidate)) {
|
|
return append(errs, FieldError{r.FieldName, errors.New("Cannot be blank")})
|
|
}
|
|
|
|
if r.MustMatch != nil {
|
|
if !r.MustMatch.MatchString(toValidate) {
|
|
errs = append(errs, FieldError{r.FieldName, errors.New(r.MatchError)})
|
|
}
|
|
}
|
|
|
|
if (r.MinLength > 0 && ln < r.MinLength) || (r.MaxLength > 0 && ln > r.MaxLength) {
|
|
errs = append(errs, FieldError{r.FieldName, errors.New(r.lengthErr())})
|
|
}
|
|
|
|
upper, lower, numeric, symbols, whitespace := tallyCharacters(toValidate)
|
|
if upper+lower < r.MinLetters {
|
|
errs = append(errs, FieldError{r.FieldName, errors.New(r.charErr())})
|
|
}
|
|
if upper < r.MinUpper {
|
|
errs = append(errs, FieldError{r.FieldName, errors.New(r.upperErr())})
|
|
}
|
|
if upper < r.MinLower {
|
|
errs = append(errs, FieldError{r.FieldName, errors.New(r.lowerErr())})
|
|
}
|
|
if numeric < r.MinNumeric {
|
|
errs = append(errs, FieldError{r.FieldName, errors.New(r.numericErr())})
|
|
}
|
|
if symbols < r.MinSymbols {
|
|
errs = append(errs, FieldError{r.FieldName, errors.New(r.symbolErr())})
|
|
}
|
|
if !r.AllowWhitespace && whitespace > 0 {
|
|
errs = append(errs, FieldError{r.FieldName, errors.New("No whitespace permitted")})
|
|
}
|
|
|
|
if len(errs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
return errs
|
|
}
|
|
|
|
// IsValid checks toValidate to make sure it's valid according to the rules.
|
|
func (r Rules) IsValid(toValidate string) bool {
|
|
return nil == r.Errors(toValidate)
|
|
}
|
|
|
|
// Rules returns an array of strings describing the rules.
|
|
func (r Rules) Rules() []string {
|
|
var rules []string
|
|
|
|
if r.MustMatch != nil {
|
|
rules = append(rules, r.MatchError)
|
|
}
|
|
|
|
if e := r.lengthErr(); len(e) > 0 {
|
|
rules = append(rules, e)
|
|
}
|
|
if e := r.charErr(); len(e) > 0 {
|
|
rules = append(rules, e)
|
|
}
|
|
if e := r.upperErr(); len(e) > 0 {
|
|
rules = append(rules, e)
|
|
}
|
|
if e := r.lowerErr(); len(e) > 0 {
|
|
rules = append(rules, e)
|
|
}
|
|
if e := r.numericErr(); len(e) > 0 {
|
|
rules = append(rules, e)
|
|
}
|
|
if e := r.symbolErr(); len(e) > 0 {
|
|
rules = append(rules, e)
|
|
}
|
|
|
|
return rules
|
|
}
|
|
|
|
func (r Rules) lengthErr() (err string) {
|
|
switch {
|
|
case r.MinLength > 0 && r.MaxLength > 0:
|
|
err = fmt.Sprintf("Must be between %d and %d characters", r.MinLength, r.MaxLength)
|
|
case r.MinLength > 0:
|
|
err = fmt.Sprintf("Must be at least %d characters", r.MinLength)
|
|
case r.MaxLength > 0:
|
|
err = fmt.Sprintf("Must be at most %d characters", r.MaxLength)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (r Rules) charErr() (err string) {
|
|
if r.MinLetters > 0 {
|
|
err = fmt.Sprintf("Must contain at least %d letters", r.MinLetters)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (r Rules) upperErr() (err string) {
|
|
if r.MinUpper > 0 {
|
|
err = fmt.Sprintf("Must contain at least %d uppercase letters", r.MinUpper)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (r Rules) lowerErr() (err string) {
|
|
if r.MinLower > 0 {
|
|
err = fmt.Sprintf("Must contain at least %d lowercase letters", r.MinLower)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (r Rules) numericErr() (err string) {
|
|
if r.MinNumeric > 0 {
|
|
err = fmt.Sprintf("Must contain at least %d numbers", r.MinNumeric)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (r Rules) symbolErr() (err string) {
|
|
if r.MinSymbols > 0 {
|
|
err = fmt.Sprintf("Must contain at least %d symbols", r.MinSymbols)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func tallyCharacters(s string) (upper, lower, numeric, symbols, whitespace int) {
|
|
for _, c := range s {
|
|
switch {
|
|
case unicode.IsLetter(c):
|
|
if unicode.IsUpper(c) {
|
|
upper++
|
|
} else {
|
|
lower++
|
|
}
|
|
case unicode.IsDigit(c):
|
|
numeric++
|
|
case unicode.IsSpace(c):
|
|
whitespace++
|
|
default:
|
|
symbols++
|
|
}
|
|
}
|
|
|
|
return upper, lower, numeric, symbols, whitespace
|
|
}
|