mirror of
https://github.com/volatiletech/authboss.git
synced 2025-04-23 12:18:58 +02:00
Second shot at proper validation.
This commit is contained in:
parent
fe7b990095
commit
65f7fad5fc
@ -1,5 +1,11 @@
|
|||||||
package authboss
|
package authboss
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
type mockUser struct {
|
type mockUser struct {
|
||||||
Email string
|
Email string
|
||||||
Password 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) Put(key, val string) { m[key] = val }
|
||||||
func (m mockClientStore) Del(key string) { delete(m, key) }
|
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 (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"gopkg.in/authboss.v0"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Rules defines a ruleset by which a string can be validated.
|
// Rules defines a ruleset by which a string can be validated.
|
||||||
type Rules struct {
|
type Rules struct {
|
||||||
// Field is the name of the field this is intended to validate.
|
// FieldName is the name of the field this is intended to validate.
|
||||||
Field string
|
FieldName string
|
||||||
// MatchError describes the MustMatch regexp to a user.
|
// MatchError describes the MustMatch regexp to a user.
|
||||||
MatchError string
|
MatchError string
|
||||||
MustMatch *regexp.Regexp
|
MustMatch *regexp.Regexp
|
||||||
@ -23,39 +21,44 @@ type Rules struct {
|
|||||||
AllowWhitespace bool
|
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
|
// Errors returns an array of errors for each validation error that
|
||||||
// is present in the given string. Returns nil if there are no errors.
|
// is present in the given string. Returns nil if there are no errors.
|
||||||
func (r Rules) Errors(toValidate string) authboss.ErrorList {
|
func (r Rules) Errors(toValidate string) ErrorList {
|
||||||
errs := make(authboss.ErrorList, 0)
|
errs := make(ErrorList, 0)
|
||||||
|
|
||||||
ln := len(toValidate)
|
ln := len(toValidate)
|
||||||
if ln == 0 {
|
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
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.MustMatch != nil {
|
if r.MustMatch != nil {
|
||||||
if !r.MustMatch.MatchString(toValidate) {
|
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) {
|
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)
|
chars, numeric, symbols, whitespace := tallyCharacters(toValidate)
|
||||||
if chars < r.MinLetters {
|
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 {
|
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 {
|
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 {
|
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 {
|
if len(errs) == 0 {
|
@ -1,4 +1,4 @@
|
|||||||
package validate
|
package authboss
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
@ -6,53 +6,55 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestRules_Errors(t *testing.T) {
|
func TestRules_Errors(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
Rules Rules
|
Rules Rules
|
||||||
In string
|
In string
|
||||||
Error string
|
Error string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Rules{Field: "email"},
|
Rules{FieldName: "email"},
|
||||||
"",
|
"",
|
||||||
"email: Cannot be blank",
|
"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",
|
"hello",
|
||||||
"email: Regexp must match!",
|
"email: Regexp must match!",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Rules{Field: "email", MinLength: 5},
|
Rules{FieldName: "email", MinLength: 5},
|
||||||
"hi",
|
"hi",
|
||||||
"email: Must be at least 5 characters",
|
"email: Must be at least 5 characters",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Rules{Field: "email", MaxLength: 3},
|
Rules{FieldName: "email", MaxLength: 3},
|
||||||
"hello",
|
"hello",
|
||||||
"email: Must be at most 3 characters",
|
"email: Must be at most 3 characters",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Rules{Field: "email", MinLength: 3, MaxLength: 5},
|
Rules{FieldName: "email", MinLength: 3, MaxLength: 5},
|
||||||
"hi",
|
"hi",
|
||||||
"email: Must be between 3 and 5 characters",
|
"email: Must be between 3 and 5 characters",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Rules{Field: "email", MinLetters: 5},
|
Rules{FieldName: "email", MinLetters: 5},
|
||||||
"13345",
|
"13345",
|
||||||
"email: Must contain at least 5 letters",
|
"email: Must contain at least 5 letters",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Rules{Field: "email", MinSymbols: 5},
|
Rules{FieldName: "email", MinSymbols: 5},
|
||||||
"hi",
|
"hi",
|
||||||
"email: Must contain at least 5 symbols",
|
"email: Must contain at least 5 symbols",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Rules{Field: "email", MinNumeric: 5},
|
Rules{FieldName: "email", MinNumeric: 5},
|
||||||
"hi",
|
"hi",
|
||||||
"email: Must contain at least 5 numbers",
|
"email: Must contain at least 5 numbers",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Rules{Field: "email"},
|
Rules{FieldName: "email"},
|
||||||
"hi whitespace",
|
"hi whitespace",
|
||||||
"email: No whitespace permitted",
|
"email: No whitespace permitted",
|
||||||
},
|
},
|
||||||
@ -74,8 +76,10 @@ func TestRules_Errors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRules_Rules(t *testing.T) {
|
func TestRules_Rules(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
r := Rules{
|
r := Rules{
|
||||||
Field: "email",
|
FieldName: "email",
|
||||||
MatchError: "Must adhere to this regexp",
|
MatchError: "Must adhere to this regexp",
|
||||||
MustMatch: regexp.MustCompile(""),
|
MustMatch: regexp.MustCompile(""),
|
||||||
MinLength: 1,
|
MinLength: 1,
|
||||||
@ -104,7 +108,9 @@ func TestRules_Rules(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRules_IsValid(t *testing.T) {
|
func TestRules_IsValid(t *testing.T) {
|
||||||
r := Rules{Field: "email"}
|
t.Parallel()
|
||||||
|
|
||||||
|
r := Rules{FieldName: "email"}
|
||||||
if r.IsValid("") {
|
if r.IsValid("") {
|
||||||
t.Error("It should not be valid.")
|
t.Error("It should not be valid.")
|
||||||
}
|
}
|
||||||
@ -115,6 +121,8 @@ func TestRules_IsValid(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTallyCharacters(t *testing.T) {
|
func TestTallyCharacters(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
c, n, s, w := tallyCharacters("123abcDEF@#$%^* ")
|
c, n, s, w := tallyCharacters("123abcDEF@#$%^* ")
|
||||||
if c != 6 {
|
if c != 6 {
|
||||||
t.Error("Number of chars:", c)
|
t.Error("Number of chars:", c)
|
@ -5,6 +5,14 @@ import (
|
|||||||
"fmt"
|
"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
|
type ErrorList []error
|
||||||
|
|
||||||
// Error satisfies the error interface.
|
// Error satisfies the error interface.
|
||||||
@ -49,9 +57,18 @@ func (f FieldError) Error() string {
|
|||||||
return fmt.Sprintf("%s: %v", f.Name, f.Err)
|
return fmt.Sprintf("%s: %v", f.Name, f.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validator is anything that can validate a string and provide a list of errors
|
// Validate validates a request using the given ruleset.
|
||||||
// and describe its set of rules.
|
func (ctx *Context) Validate(ruleset []Validator) ErrorList {
|
||||||
type Validator interface {
|
errList := make(ErrorList, 0)
|
||||||
Errors(in string) ErrorList
|
|
||||||
Rules() []string
|
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])
|
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