mirror of
https://github.com/volatiletech/authboss.git
synced 2025-04-15 11:56:31 +02:00
Second shot at proper validation.
This commit is contained in:
parent
fe7b990095
commit
65f7fad5fc
@ -1,5 +1,11 @@
|
||||
package authboss
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type mockUser struct {
|
||||
Email string
|
||||
Password string
|
||||
@ -31,3 +37,44 @@ func (m mockClientStore) Get(key string) (string, bool) {
|
||||
}
|
||||
func (m mockClientStore) Put(key, val string) { m[key] = val }
|
||||
func (m mockClientStore) Del(key string) { delete(m, key) }
|
||||
|
||||
func mockRequestContext(postKeyValues ...string) *Context {
|
||||
keyValues := &bytes.Buffer{}
|
||||
for i := 0; i < len(postKeyValues); i += 2 {
|
||||
if i != 0 {
|
||||
keyValues.WriteByte('&')
|
||||
}
|
||||
fmt.Fprintf(keyValues, "%s=%s", postKeyValues[i], postKeyValues[i+1])
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", "http://localhost", keyValues)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
ctx, err := ContextFromRequest(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
type mockValidator struct {
|
||||
FieldName string
|
||||
Errs ErrorList
|
||||
Ruleset []string
|
||||
}
|
||||
|
||||
func (m mockValidator) Field() string {
|
||||
return m.FieldName
|
||||
}
|
||||
|
||||
func (m mockValidator) Errors(in string) ErrorList {
|
||||
return m.Errs
|
||||
}
|
||||
|
||||
func (m mockValidator) Rules() []string {
|
||||
return m.Ruleset
|
||||
}
|
||||
|
@ -1,18 +1,16 @@
|
||||
package validate
|
||||
package authboss
|
||||
|
||||
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
|
||||
// FieldName is the name of the field this is intended to validate.
|
||||
FieldName string
|
||||
// MatchError describes the MustMatch regexp to a user.
|
||||
MatchError string
|
||||
MustMatch *regexp.Regexp
|
||||
@ -23,39 +21,44 @@ type Rules struct {
|
||||
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) authboss.ErrorList {
|
||||
errs := make(authboss.ErrorList, 0)
|
||||
func (r Rules) Errors(toValidate string) ErrorList {
|
||||
errs := make(ErrorList, 0)
|
||||
|
||||
ln := len(toValidate)
|
||||
if ln == 0 {
|
||||
errs = append(errs, authboss.FieldError{r.Field, errors.New("Cannot be blank")})
|
||||
errs = append(errs, FieldError{r.FieldName, errors.New("Cannot be blank")})
|
||||
return errs
|
||||
}
|
||||
|
||||
if r.MustMatch != nil {
|
||||
if !r.MustMatch.MatchString(toValidate) {
|
||||
errs = append(errs, authboss.FieldError{r.Field, errors.New(r.MatchError)})
|
||||
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, authboss.FieldError{r.Field, errors.New(r.lengthErr())})
|
||||
errs = append(errs, FieldError{r.FieldName, 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())})
|
||||
errs = append(errs, FieldError{r.FieldName, errors.New(r.charErr())})
|
||||
}
|
||||
if numeric < r.MinNumeric {
|
||||
errs = append(errs, authboss.FieldError{r.Field, errors.New(r.numericErr())})
|
||||
errs = append(errs, FieldError{r.FieldName, errors.New(r.numericErr())})
|
||||
}
|
||||
if symbols < r.MinSymbols {
|
||||
errs = append(errs, authboss.FieldError{r.Field, errors.New(r.symbolErr())})
|
||||
errs = append(errs, FieldError{r.FieldName, errors.New(r.symbolErr())})
|
||||
}
|
||||
if !r.AllowWhitespace && whitespace > 0 {
|
||||
errs = append(errs, authboss.FieldError{r.Field, errors.New("No whitespace permitted")})
|
||||
errs = append(errs, FieldError{r.FieldName, errors.New("No whitespace permitted")})
|
||||
}
|
||||
|
||||
if len(errs) == 0 {
|
@ -1,4 +1,4 @@
|
||||
package validate
|
||||
package authboss
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
@ -6,53 +6,55 @@ import (
|
||||
)
|
||||
|
||||
func TestRules_Errors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
Rules Rules
|
||||
In string
|
||||
Error string
|
||||
}{
|
||||
{
|
||||
Rules{Field: "email"},
|
||||
Rules{FieldName: "email"},
|
||||
"",
|
||||
"email: Cannot be blank",
|
||||
},
|
||||
{
|
||||
Rules{Field: "email", MatchError: "Regexp must match!", MustMatch: regexp.MustCompile("abc")},
|
||||
Rules{FieldName: "email", MatchError: "Regexp must match!", MustMatch: regexp.MustCompile("abc")},
|
||||
"hello",
|
||||
"email: Regexp must match!",
|
||||
},
|
||||
{
|
||||
Rules{Field: "email", MinLength: 5},
|
||||
Rules{FieldName: "email", MinLength: 5},
|
||||
"hi",
|
||||
"email: Must be at least 5 characters",
|
||||
},
|
||||
{
|
||||
Rules{Field: "email", MaxLength: 3},
|
||||
Rules{FieldName: "email", MaxLength: 3},
|
||||
"hello",
|
||||
"email: Must be at most 3 characters",
|
||||
},
|
||||
{
|
||||
Rules{Field: "email", MinLength: 3, MaxLength: 5},
|
||||
Rules{FieldName: "email", MinLength: 3, MaxLength: 5},
|
||||
"hi",
|
||||
"email: Must be between 3 and 5 characters",
|
||||
},
|
||||
{
|
||||
Rules{Field: "email", MinLetters: 5},
|
||||
Rules{FieldName: "email", MinLetters: 5},
|
||||
"13345",
|
||||
"email: Must contain at least 5 letters",
|
||||
},
|
||||
{
|
||||
Rules{Field: "email", MinSymbols: 5},
|
||||
Rules{FieldName: "email", MinSymbols: 5},
|
||||
"hi",
|
||||
"email: Must contain at least 5 symbols",
|
||||
},
|
||||
{
|
||||
Rules{Field: "email", MinNumeric: 5},
|
||||
Rules{FieldName: "email", MinNumeric: 5},
|
||||
"hi",
|
||||
"email: Must contain at least 5 numbers",
|
||||
},
|
||||
{
|
||||
Rules{Field: "email"},
|
||||
Rules{FieldName: "email"},
|
||||
"hi whitespace",
|
||||
"email: No whitespace permitted",
|
||||
},
|
||||
@ -74,8 +76,10 @@ func TestRules_Errors(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRules_Rules(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r := Rules{
|
||||
Field: "email",
|
||||
FieldName: "email",
|
||||
MatchError: "Must adhere to this regexp",
|
||||
MustMatch: regexp.MustCompile(""),
|
||||
MinLength: 1,
|
||||
@ -104,7 +108,9 @@ func TestRules_Rules(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRules_IsValid(t *testing.T) {
|
||||
r := Rules{Field: "email"}
|
||||
t.Parallel()
|
||||
|
||||
r := Rules{FieldName: "email"}
|
||||
if r.IsValid("") {
|
||||
t.Error("It should not be valid.")
|
||||
}
|
||||
@ -115,6 +121,8 @@ func TestRules_IsValid(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTallyCharacters(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
c, n, s, w := tallyCharacters("123abcDEF@#$%^* ")
|
||||
if c != 6 {
|
||||
t.Error("Number of chars:", c)
|
@ -5,6 +5,14 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Validator is anything that can validate a string and provide a list of errors
|
||||
// and describe its set of rules.
|
||||
type Validator interface {
|
||||
Field() string
|
||||
Errors(in string) ErrorList
|
||||
Rules() []string
|
||||
}
|
||||
|
||||
type ErrorList []error
|
||||
|
||||
// Error satisfies the error interface.
|
||||
@ -49,9 +57,18 @@ func (f FieldError) Error() string {
|
||||
return fmt.Sprintf("%s: %v", f.Name, f.Err)
|
||||
}
|
||||
|
||||
// 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
|
||||
// Validate validates a request using the given ruleset.
|
||||
func (ctx *Context) Validate(ruleset []Validator) ErrorList {
|
||||
errList := make(ErrorList, 0)
|
||||
|
||||
for _, validator := range ruleset {
|
||||
field := validator.Field()
|
||||
|
||||
val, _ := ctx.FirstFormValue(field)
|
||||
if errs := validator.Errors(val); len(errs) > 0 {
|
||||
errList = append(errList, errs...)
|
||||
}
|
||||
}
|
||||
|
||||
return errList
|
||||
}
|
||||
|
@ -60,3 +60,32 @@ func TestErrorList_Map(t *testing.T) {
|
||||
t.Error("Wrong unkown error at 0:", unknownErrs[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := mockRequestContext("username", "john", "email", "john@john.com")
|
||||
|
||||
errList := ctx.Validate([]Validator{
|
||||
mockValidator{
|
||||
FieldName: "username", Errs: ErrorList{FieldError{"username", errors.New("must be longer than 4")}},
|
||||
},
|
||||
mockValidator{
|
||||
FieldName: "missing_field", Errs: ErrorList{FieldError{"missing_field", errors.New("Expected field to exist.")}},
|
||||
},
|
||||
mockValidator{
|
||||
FieldName: "email", Errs: nil,
|
||||
},
|
||||
})
|
||||
|
||||
errs := errList.Map()
|
||||
if errs["username"][0] != "must be longer than 4" {
|
||||
t.Error("Expected a different error for username:", errs["username"][0])
|
||||
}
|
||||
if errs["missing_field"][0] != "Expected field to exist." {
|
||||
t.Error("Expected a different error for missing_field:", errs["missing_field"][0])
|
||||
}
|
||||
if _, ok := errs["email"]; ok {
|
||||
t.Error("Expected no errors for email.")
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user