mirror of
https://github.com/volatiletech/authboss.git
synced 2025-07-03 00:47:13 +02:00
Initial validate module.
This commit is contained in:
@ -13,6 +13,10 @@ type Config struct {
|
|||||||
AuthLogoutRoute string `json:"authLogoutRoute" xml:"authLogoutRoute"`
|
AuthLogoutRoute string `json:"authLogoutRoute" xml:"authLogoutRoute"`
|
||||||
AuthLoginSuccessRoute string `json:"authLoginSuccessRoute" xml:"authLoginSuccessRoute"`
|
AuthLoginSuccessRoute string `json:"authLoginSuccessRoute" xml:"authLoginSuccessRoute"`
|
||||||
|
|
||||||
|
ValidateEmail Validator
|
||||||
|
ValidateUsername Validator
|
||||||
|
ValidatePassword Validator
|
||||||
|
|
||||||
Storer Storer `json:"-" xml:"-"`
|
Storer Storer `json:"-" xml:"-"`
|
||||||
CookieStoreMaker CookieStoreMaker `json:"-" xml:"-"`
|
CookieStoreMaker CookieStoreMaker `json:"-" xml:"-"`
|
||||||
SessionStoreMaker SessionStoreMaker `json:"-" xml:"-"`
|
SessionStoreMaker SessionStoreMaker `json:"-" xml:"-"`
|
||||||
|
@ -38,19 +38,19 @@ type Remember struct {
|
|||||||
logger io.Writer
|
logger io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Remember) Initialize(c *authboss.Config) error {
|
func (r *Remember) Initialize(config *authboss.Config) error {
|
||||||
if c.Storer == nil {
|
if config.Storer == nil {
|
||||||
return errors.New("remember: Need a TokenStorer.")
|
return errors.New("remember: Need a TokenStorer.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if storer, ok := c.Storer.(authboss.TokenStorer); !ok {
|
if storer, ok := config.Storer.(authboss.TokenStorer); !ok {
|
||||||
return errors.New("remember: TokenStorer required for remember me functionality.")
|
return errors.New("remember: TokenStorer required for remember me functionality.")
|
||||||
} else {
|
} else {
|
||||||
r.storer = storer
|
r.storer = storer
|
||||||
}
|
}
|
||||||
|
|
||||||
r.logger = c.LogWriter
|
r.logger = c.LogWriter
|
||||||
c.Callbacks.After(authboss.EventAuth, r.AfterAuth)
|
config.Callbacks.After(authboss.EventAuth, r.AfterAuth)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
144
validate/rules.go
Normal file
144
validate/rules.go
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
package validate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"gopkg.in/authboss.v0"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Rules defines a ruleset by which a string can be validated.
|
||||||
|
type Rules struct {
|
||||||
|
// Field is the name of the field this is intended to validate.
|
||||||
|
Field string
|
||||||
|
// MatchError describes the MustMatch regexp to a user.
|
||||||
|
MatchError string
|
||||||
|
MustMatch *regexp.Regexp
|
||||||
|
MinLength, MaxLength int
|
||||||
|
MinLetters int
|
||||||
|
MinNumeric int
|
||||||
|
MinSymbols int
|
||||||
|
AllowWhitespace bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) authboss.ErrorList {
|
||||||
|
errs := make(authboss.ErrorList, 0)
|
||||||
|
|
||||||
|
ln := len(toValidate)
|
||||||
|
if ln == 0 {
|
||||||
|
errs = append(errs, authboss.FieldError{r.Field, errors.New("Cannot be blank")})
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.MustMatch != nil {
|
||||||
|
if !r.MustMatch.MatchString(toValidate) {
|
||||||
|
errs = append(errs, authboss.FieldError{r.Field, errors.New(r.MatchError)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r.MinLength > 0 && ln < r.MinLength) || (r.MaxLength > 0 && ln > r.MaxLength) {
|
||||||
|
errs = append(errs, authboss.FieldError{r.Field, errors.New(r.lengthErr())})
|
||||||
|
}
|
||||||
|
|
||||||
|
chars, numeric, symbols, whitespace := tallyCharacters(toValidate)
|
||||||
|
if chars < r.MinLetters {
|
||||||
|
errs = append(errs, authboss.FieldError{r.Field, errors.New(r.charErr())})
|
||||||
|
}
|
||||||
|
if numeric < r.MinNumeric {
|
||||||
|
errs = append(errs, authboss.FieldError{r.Field, errors.New(r.numericErr())})
|
||||||
|
}
|
||||||
|
if symbols < r.MinSymbols {
|
||||||
|
errs = append(errs, authboss.FieldError{r.Field, errors.New(r.symbolErr())})
|
||||||
|
}
|
||||||
|
if !r.AllowWhitespace && whitespace > 0 {
|
||||||
|
errs = append(errs, authboss.FieldError{r.Field, 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 {
|
||||||
|
rules := make([]string, 0)
|
||||||
|
|
||||||
|
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.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) 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) (chars, numeric, symbols, whitespace int) {
|
||||||
|
for _, c := range s {
|
||||||
|
switch {
|
||||||
|
case unicode.IsLetter(c):
|
||||||
|
chars++
|
||||||
|
case unicode.IsDigit(c):
|
||||||
|
numeric++
|
||||||
|
case unicode.IsSpace(c):
|
||||||
|
whitespace++
|
||||||
|
default:
|
||||||
|
symbols++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return chars, numeric, symbols, whitespace
|
||||||
|
}
|
131
validate/rules_test.go
Normal file
131
validate/rules_test.go
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
package validate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRules_Errors(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Rules Rules
|
||||||
|
In string
|
||||||
|
Error string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Rules{Field: "email"},
|
||||||
|
"",
|
||||||
|
"email: Cannot be blank",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rules{Field: "email", MatchError: "Regexp must match!", MustMatch: regexp.MustCompile("abc")},
|
||||||
|
"hello",
|
||||||
|
"email: Regexp must match!",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rules{Field: "email", MinLength: 5},
|
||||||
|
"hi",
|
||||||
|
"email: Must be at least 5 characters",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rules{Field: "email", MaxLength: 3},
|
||||||
|
"hello",
|
||||||
|
"email: Must be at most 3 characters",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rules{Field: "email", MinLength: 3, MaxLength: 5},
|
||||||
|
"hi",
|
||||||
|
"email: Must be between 3 and 5 characters",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rules{Field: "email", MinLetters: 5},
|
||||||
|
"13345",
|
||||||
|
"email: Must contain at least 5 letters",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rules{Field: "email", MinSymbols: 5},
|
||||||
|
"hi",
|
||||||
|
"email: Must contain at least 5 symbols",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rules{Field: "email", MinNumeric: 5},
|
||||||
|
"hi",
|
||||||
|
"email: Must contain at least 5 numbers",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Rules{Field: "email"},
|
||||||
|
"hi whitespace",
|
||||||
|
"email: No whitespace permitted",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
i = i + 1
|
||||||
|
|
||||||
|
err := test.Rules.Errors(test.In)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("(%d) Wanted: %q", i, test.Error)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if e := err.Error(); e != test.Error {
|
||||||
|
t.Errorf("(%d) The error was wrong: %q", i, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRules_Rules(t *testing.T) {
|
||||||
|
r := Rules{
|
||||||
|
Field: "email",
|
||||||
|
MatchError: "Must adhere to this regexp",
|
||||||
|
MustMatch: regexp.MustCompile(""),
|
||||||
|
MinLength: 1,
|
||||||
|
MaxLength: 2,
|
||||||
|
MinLetters: 3,
|
||||||
|
MinNumeric: 4,
|
||||||
|
MinSymbols: 5,
|
||||||
|
AllowWhitespace: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
rules := r.Rules()
|
||||||
|
|
||||||
|
mustFind := []string{
|
||||||
|
"Must adhere to this regexp",
|
||||||
|
"Must be between 1 and 2 characters",
|
||||||
|
"Must contain at least 3 letters",
|
||||||
|
"Must contain at least 4 numbers",
|
||||||
|
"Must contain at least 5 symbols",
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, toFind := range mustFind {
|
||||||
|
if rules[i] != toFind {
|
||||||
|
t.Error("Expected:", toFind, "got:", rules[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRules_IsValid(t *testing.T) {
|
||||||
|
r := Rules{Field: "email"}
|
||||||
|
if r.IsValid("") {
|
||||||
|
t.Error("It should not be valid.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !r.IsValid("joe@joe.com") {
|
||||||
|
t.Error("It should be valid.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTallyCharacters(t *testing.T) {
|
||||||
|
c, n, s, w := tallyCharacters("123abcDEF@#$%^* ")
|
||||||
|
if c != 6 {
|
||||||
|
t.Error("Number of chars:", c)
|
||||||
|
}
|
||||||
|
if n != 3 {
|
||||||
|
t.Error("Number of numerics:", n)
|
||||||
|
}
|
||||||
|
if s != 6 {
|
||||||
|
t.Error("Number of symbols:", s)
|
||||||
|
}
|
||||||
|
if w != 3 {
|
||||||
|
t.Error("Number of whitespace:", w)
|
||||||
|
}
|
||||||
|
}
|
60
validate/validate.go
Normal file
60
validate/validate.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
// Package validate supports validation of usernames, email addresses, and passwords.
|
||||||
|
package validate
|
||||||
|
|
||||||
|
import "gopkg.in/authboss.v0"
|
||||||
|
|
||||||
|
var V *Validate
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
V = &Validate{}
|
||||||
|
authboss.RegisterModule("validate", V)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Validate struct {
|
||||||
|
Username authboss.Validator
|
||||||
|
Password authboss.Validator
|
||||||
|
Email authboss.Validator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Validate) Initialize(config *authboss.Config) error {
|
||||||
|
v.Email = config.ValidateEmail
|
||||||
|
v.Username = config.ValidateUsername
|
||||||
|
v.Password = config.ValidatePassword
|
||||||
|
|
||||||
|
config.Callbacks.Before(authboss.EventRegister, v.BeforeRegister)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Validate) Routes() authboss.RouteTable { return nil }
|
||||||
|
func (v *Validate) Storage() authboss.StorageOptions { return nil }
|
||||||
|
|
||||||
|
func (v *Validate) BeforeRegister(ctx *authboss.Context) error {
|
||||||
|
errList := make(authboss.ErrorList, 0)
|
||||||
|
|
||||||
|
if v.Email != nil {
|
||||||
|
email, ok := ctx.FirstPostFormValue("email")
|
||||||
|
if ok {
|
||||||
|
errs := v.Email.Errors(email)
|
||||||
|
errList = append(errList, errs...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Username != nil {
|
||||||
|
username, ok := ctx.FirstPostFormValue("username")
|
||||||
|
if ok {
|
||||||
|
errs := v.Username.Errors(username)
|
||||||
|
errList = append(errList, errs...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Password != nil {
|
||||||
|
password, ok := ctx.FirstPostFormValue("password")
|
||||||
|
if ok {
|
||||||
|
errs := v.Password.Errors(password)
|
||||||
|
errList = append(errList, errs...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errList
|
||||||
|
}
|
65
validate/validate_test.go
Normal file
65
validate/validate_test.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package validate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gopkg.in/authboss.v0"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValidate_Initialiaze(t *testing.T) {
|
||||||
|
cfg := authboss.NewConfig()
|
||||||
|
cfg.ValidateEmail = Rules{}
|
||||||
|
cfg.ValidateUsername = Rules{}
|
||||||
|
cfg.ValidatePassword = Rules{}
|
||||||
|
|
||||||
|
err := V.Initialize(cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Unexpected error:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if V.Email == nil {
|
||||||
|
t.Error("Should have set Email validator.")
|
||||||
|
}
|
||||||
|
if V.Username == nil {
|
||||||
|
t.Error("Should have set Username validator.")
|
||||||
|
}
|
||||||
|
if V.Password == nil {
|
||||||
|
t.Error("Should have set Password validator.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidate_BeforeRegister(t *testing.T) {
|
||||||
|
cfg := authboss.NewConfig()
|
||||||
|
cfg.ValidateEmail = Rules{Field: "email", MinLength: 15}
|
||||||
|
cfg.ValidateUsername = Rules{Field: "username", MaxLength: 1}
|
||||||
|
cfg.ValidatePassword = Rules{Field: "password", MinLength: 8}
|
||||||
|
|
||||||
|
err := V.Initialize(cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Unexpected error:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
body := `email=joe@joe.ca&password=hi&username=hello`
|
||||||
|
req, err := http.NewRequest("POST", "http://localhost", bytes.NewBufferString(body))
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Unexpected Error:", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
ctx, err := authboss.ContextFromRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Unexpected error:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = V.BeforeRegister(ctx)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected three validation errors.")
|
||||||
|
}
|
||||||
|
|
||||||
|
list := err.(authboss.ErrorList)
|
||||||
|
if len(list) != 3 {
|
||||||
|
t.Error("Expected three validation errors.")
|
||||||
|
}
|
||||||
|
}
|
41
validation.go
Normal file
41
validation.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package authboss
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ErrorList []error
|
||||||
|
|
||||||
|
// FieldError represents an error that occurs during validation and is always
|
||||||
|
// attached to field on a form.
|
||||||
|
type FieldError struct {
|
||||||
|
Name string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FieldError) Error() string {
|
||||||
|
return fmt.Sprintf("%s: %v", f.Name, f.Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error satisfies the error interface.
|
||||||
|
func (e ErrorList) Error() string {
|
||||||
|
b := &bytes.Buffer{}
|
||||||
|
first := true
|
||||||
|
for _, err := range e {
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
} else {
|
||||||
|
b.WriteString(", ")
|
||||||
|
}
|
||||||
|
b.WriteString(err.Error())
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validator is anything that can validate a string and provide a list of errors
|
||||||
|
// and describe its set of rules.
|
||||||
|
type Validator interface {
|
||||||
|
Errors(in string) ErrorList
|
||||||
|
Rules() []string
|
||||||
|
}
|
13
validation_test.go
Normal file
13
validation_test.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package authboss
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestErrorList_Error(t *testing.T) {
|
||||||
|
errList := ErrorList{errors.New("one"), errors.New("two")}
|
||||||
|
if e := errList.Error(); e != "one, two" {
|
||||||
|
t.Error("Wrong value for error:", e)
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user