mirror of
https://github.com/volatiletech/authboss.git
synced 2025-01-04 03:49:32 +02:00
Safety commit
- Add new validation methods - Cleaned up interactions with validation - Add required validation - Add confirm fields to validation
This commit is contained in:
parent
042bdba669
commit
e660edd428
15
auth/auth.go
15
auth/auth.go
@ -36,6 +36,8 @@ type AuthPage struct {
|
||||
|
||||
ShowRemember bool
|
||||
ShowRecover bool
|
||||
|
||||
FlashSuccess string
|
||||
}
|
||||
|
||||
type Auth struct {
|
||||
@ -94,7 +96,14 @@ func (a *Auth) loginHandlerFunc(ctx *authboss.Context, w http.ResponseWriter, r
|
||||
}
|
||||
}
|
||||
|
||||
a.templates.ExecuteTemplate(w, pageLogin, AuthPage{ShowRemember: a.isRememberLoaded, ShowRecover: a.isRecoverLoaded})
|
||||
page := AuthPage{ShowRemember: a.isRememberLoaded, ShowRecover: a.isRecoverLoaded}
|
||||
|
||||
if msg, ok := ctx.SessionStorer.Get(authboss.FlashSuccessKey); ok {
|
||||
page.FlashSuccess = msg
|
||||
ctx.SessionStorer.Del(authboss.FlashSuccessKey)
|
||||
}
|
||||
|
||||
a.templates.ExecuteTemplate(w, pageLogin, page)
|
||||
case methodPOST:
|
||||
u, ok := ctx.FirstPostFormValue("username")
|
||||
if !ok {
|
||||
@ -103,7 +112,7 @@ func (a *Auth) loginHandlerFunc(ctx *authboss.Context, w http.ResponseWriter, r
|
||||
|
||||
if err := a.callbacks.FireBefore(authboss.EventAuth, ctx); err != nil {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
a.templates.ExecuteTemplate(w, pageLogin, AuthPage{err.Error(), u, a.isRememberLoaded, a.isRecoverLoaded})
|
||||
a.templates.ExecuteTemplate(w, pageLogin, AuthPage{err.Error(), u, a.isRememberLoaded, a.isRecoverLoaded, ""})
|
||||
}
|
||||
|
||||
p, ok := ctx.FirstPostFormValue("password")
|
||||
@ -114,7 +123,7 @@ func (a *Auth) loginHandlerFunc(ctx *authboss.Context, w http.ResponseWriter, r
|
||||
if err := a.authenticate(ctx, u, p); err != nil {
|
||||
fmt.Fprintln(a.logger, err)
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
a.templates.ExecuteTemplate(w, pageLogin, AuthPage{"invalid username and/or password", u, a.isRememberLoaded, a.isRecoverLoaded})
|
||||
a.templates.ExecuteTemplate(w, pageLogin, AuthPage{"invalid username and/or password", u, a.isRememberLoaded, a.isRecoverLoaded, ""})
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,10 @@ const (
|
||||
// the remember module. This serves as a way to force full authentication
|
||||
// by denying half-authed users acccess to sensitive areas.
|
||||
HalfAuthKey = "halfauth"
|
||||
// FlashSuccessKey is used for storing sucess flash messages on the session
|
||||
FlashSuccessKey = "flash_success"
|
||||
// FlashErrorKey is used for storing sucess flash messages on the session
|
||||
FlashErrorKey = "flash_error"
|
||||
)
|
||||
|
||||
// ClientStorer should be able to store values on the clients machine. Cookie and
|
||||
|
48
config.go
48
config.go
@ -10,35 +10,38 @@ import (
|
||||
// Config holds all the configuration for both authboss and it's modules.
|
||||
type Config struct {
|
||||
// MountPath is the path to mount the router at.
|
||||
MountPath string `json:"mount_path" xml:"mountPath"`
|
||||
MountPath string
|
||||
// ViewsPath is the path to overiding view template files.
|
||||
ViewsPath string `json:"views_path" xml:"viewsPath"`
|
||||
ViewsPath string
|
||||
// HostName is self explanitory
|
||||
HostName string
|
||||
|
||||
AuthLogoutRoute string `json:"auth_logout_route" xml:"authLogoutRoute"`
|
||||
AuthLoginSuccessRoute string `json:"auth_login_success_route" xml:"authLoginSuccessRoute"`
|
||||
AuthLogoutRoute string
|
||||
AuthLoginSuccessRoute string
|
||||
|
||||
ValidateEmail Validator `json:"-" xml:"-"`
|
||||
ValidateUsername Validator `json:"-" xml:"-"`
|
||||
ValidatePassword Validator `json:"-" xml:"-"`
|
||||
RecoverInitiateRedirect string
|
||||
RecoverInitiateSuccessFlash string
|
||||
|
||||
ExpireAfter time.Duration `json:"expire_after" xml:"expireAfter"`
|
||||
Policies []Validator
|
||||
ConfirmFields []string
|
||||
|
||||
LockAfter int `json:"lock_after" xml:"lockAfter"`
|
||||
LockWindow time.Duration `json:"lock_window" xml:"lockWindow"`
|
||||
LockDuration time.Duration `json:"lock_duration" xml:"lockDuration"`
|
||||
ExpireAfter time.Duration
|
||||
LockAfter int
|
||||
LockWindow time.Duration
|
||||
LockDuration time.Duration
|
||||
|
||||
EmailFrom string `json:"email_from" xml:"emailFrom"`
|
||||
EmailSubjectPrefix string `json:"email_subject_prefix" xml:"emailSubjectPrefix"`
|
||||
EmailFrom string
|
||||
EmailSubjectPrefix string
|
||||
|
||||
SMTPAddress string `json:"smtp_address" xml:"smtpAddress"`
|
||||
SMTPAuth smtp.Auth `json:"-" xml:"-"`
|
||||
SMTPAddress string
|
||||
SMTPAuth smtp.Auth
|
||||
|
||||
Storer Storer `json:"-" xml:"-"`
|
||||
CookieStoreMaker CookieStoreMaker `json:"-" xml:"-"`
|
||||
SessionStoreMaker SessionStoreMaker `json:"-" xml:"-"`
|
||||
LogWriter io.Writer `json:"-" xml:"-"`
|
||||
Callbacks *Callbacks `json:"-" xml:"-"`
|
||||
Mailer Mailer `json:"-" xml:"-"`
|
||||
Storer Storer
|
||||
CookieStoreMaker CookieStoreMaker
|
||||
SessionStoreMaker SessionStoreMaker
|
||||
LogWriter io.Writer
|
||||
Callbacks *Callbacks
|
||||
Mailer Mailer
|
||||
}
|
||||
|
||||
// NewConfig creates a new config full of default values ready to override.
|
||||
@ -50,6 +53,9 @@ func NewConfig() *Config {
|
||||
AuthLogoutRoute: "/",
|
||||
AuthLoginSuccessRoute: "/",
|
||||
|
||||
RecoverInitiateRedirect: "/login",
|
||||
RecoverInitiateSuccessFlash: "An email has been sent with further insructions on how to reset your password",
|
||||
|
||||
LogWriter: ioutil.Discard,
|
||||
Callbacks: NewCallbacks(),
|
||||
Mailer: LogMailer(ioutil.Discard),
|
||||
|
@ -1,20 +1,17 @@
|
||||
package recover
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"io"
|
||||
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"log"
|
||||
|
||||
"gopkg.in/authboss.v0"
|
||||
"gopkg.in/authboss.v0/internal/views"
|
||||
)
|
||||
@ -23,13 +20,14 @@ const (
|
||||
methodGET = "GET"
|
||||
methodPOST = "POST"
|
||||
|
||||
tplLogin = "login.tpl"
|
||||
tplRecover = "recover.tpl"
|
||||
tplRecoverComplete = "recover-complete.tpl"
|
||||
tplInitEmail = "recover-init.email"
|
||||
|
||||
attrUsername = "username"
|
||||
attrResetToken = "resettoken"
|
||||
attrEmail = "email"
|
||||
attrUsername = "username"
|
||||
attrRecoverToken = "recover_token"
|
||||
attrEmail = "email"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -38,77 +36,98 @@ func init() {
|
||||
}
|
||||
|
||||
type RecoverPage struct {
|
||||
Username, ConfirmUsername, Error string
|
||||
Username, ConfirmUsername string
|
||||
ErrMap map[string][]string
|
||||
}
|
||||
|
||||
type RecoverModule struct {
|
||||
templates *template.Template
|
||||
routes authboss.RouteTable
|
||||
storageOptions authboss.StorageOptions
|
||||
storer authboss.Storer
|
||||
logger io.Writer
|
||||
|
||||
fromEmail string
|
||||
templates *template.Template
|
||||
routes authboss.RouteTable
|
||||
storageOptions authboss.StorageOptions
|
||||
storer authboss.RecoverStorer
|
||||
logger io.Writer
|
||||
policies []authboss.Validator
|
||||
confirmFields []string
|
||||
hostName string
|
||||
recoverInitiateRedirect string
|
||||
recoverInitiateSuccessFlash string
|
||||
fromEmail string
|
||||
}
|
||||
|
||||
func (m *RecoverModule) Initialize(config *authboss.Config) (err error) {
|
||||
if config.Storer == nil {
|
||||
return errors.New("recover: Need a RecoverStorer.")
|
||||
}
|
||||
|
||||
if storer, ok := config.Storer.(authboss.RecoverStorer); !ok {
|
||||
return errors.New("recover: RecoverStorer required for recover functionality.")
|
||||
} else {
|
||||
m.storer = storer
|
||||
}
|
||||
|
||||
if m.templates, err = views.Get(config.ViewsPath, tplRecover, tplRecoverComplete, tplInitEmail); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.routes = authboss.RouteTable{
|
||||
"recover": m.recoverHandlerFunc,
|
||||
"recover/complete": m.recoverCompleteHandlerFunc,
|
||||
"recover": m.recoverHandlerFunc,
|
||||
//"recover/complete": m.recoverCompleteHandlerFunc,
|
||||
}
|
||||
m.storageOptions = authboss.StorageOptions{
|
||||
attrUsername: authboss.String,
|
||||
attrResetToken: authboss.String,
|
||||
attrEmail: authboss.String,
|
||||
attrUsername: authboss.String,
|
||||
attrRecoverToken: authboss.String,
|
||||
attrEmail: authboss.String,
|
||||
}
|
||||
m.storer = config.Storer
|
||||
m.logger = config.LogWriter
|
||||
m.fromEmail = config.RecoverFromEmail
|
||||
m.hostName = config.HostName
|
||||
m.recoverInitiateRedirect = config.RecoverInitiateRedirect
|
||||
m.recoverInitiateSuccessFlash = config.RecoverInitiateSuccessFlash
|
||||
m.policies = config.Policies
|
||||
m.confirmFields = config.ConfirmFields
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *RecoverModule) Routes() authboss.RouteTable {
|
||||
return m.routes
|
||||
}
|
||||
|
||||
func (m *RecoverModule) Storage() authboss.StorageOptions {
|
||||
return m.storageOptions
|
||||
}
|
||||
func (m *RecoverModule) Routes() authboss.RouteTable { return m.routes }
|
||||
func (m *RecoverModule) Storage() authboss.StorageOptions { return m.storageOptions }
|
||||
|
||||
func (m *RecoverModule) recoverHandlerFunc(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case methodGET:
|
||||
m.templates.ExecuteTemplate(w, tplRecover, nil)
|
||||
case methodPOST:
|
||||
username, ok := ctx.FirstPostFormValue("username")
|
||||
if !ok {
|
||||
fmt.Fprintln(m.logger, errors.New("recover: Expected postFormValue 'username' to be in the context"))
|
||||
}
|
||||
username, _ := ctx.FirstPostFormValue("username")
|
||||
confirmUsername, _ := ctx.FirstPostFormValue("confirmUsername")
|
||||
|
||||
confirmUsername, ok := ctx.FirstPostFormValue("confirmUsername")
|
||||
if !ok {
|
||||
fmt.Fprintln(m.logger, errors.New("recover: Expected postFormValue 'confirmUsername' to be in the context"))
|
||||
}
|
||||
|
||||
if err := m.initiateRecover(ctx, username, confirmUsername, r.Host); err != nil {
|
||||
fmt.Fprintln(m.logger, fmt.Sprintf("recover: %s"), err.Error())
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
m.templates.ExecuteTemplate(w, tplRecover, RecoverPage{username, confirmUsername, err.Error()})
|
||||
policies := authboss.FilterValidators(m.policies, "username")
|
||||
if validationErrs := ctx.Validate(policies, m.confirmFields...); len(validationErrs) > 0 {
|
||||
err := m.templates.ExecuteTemplate(w, tplRecover, RecoverPage{username, confirmUsername, validationErrs.Map()})
|
||||
if err != nil {
|
||||
fmt.Fprintln(m.logger, "recover:", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := m.initiateRecover(ctx, username, confirmUsername); err != nil {
|
||||
fmt.Fprintln(m.logger, fmt.Sprintf("recover: %s", err.Error()))
|
||||
}
|
||||
|
||||
ctx.SessionStorer.Put(authboss.FlashSuccessKey, m.recoverInitiateSuccessFlash)
|
||||
http.Redirect(w, r, m.recoverInitiateRedirect, http.StatusFound)
|
||||
default:
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *RecoverModule) initiateRecover(ctx *authboss.Context, username, confirmUsername, host string) error {
|
||||
if !strings.EqualFold(username, confirmUsername) {
|
||||
return errors.New("Confirm username does not match")
|
||||
func (m *RecoverModule) initiateRecover(ctx *authboss.Context, username, confirmUsername string) (err error) {
|
||||
if err := ctx.LoadUser(username, m.storer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
email, ok := ctx.User.String(attrEmail)
|
||||
if !ok {
|
||||
return fmt.Errorf("missing attr: %s", email)
|
||||
}
|
||||
|
||||
token := make([]byte, 32)
|
||||
@ -116,25 +135,8 @@ func (m *RecoverModule) initiateRecover(ctx *authboss.Context, username, confirm
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ctx.LoadUser(username, m.storer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
emailInter, ok := ctx.User[attrEmail]
|
||||
if !ok {
|
||||
return errors.New("user does not have mapped email")
|
||||
}
|
||||
|
||||
email, ok := emailInter.(string)
|
||||
if !ok {
|
||||
return errors.New("user does not have a valid email")
|
||||
}
|
||||
|
||||
// TODO : email regex check on to and from
|
||||
|
||||
sum := md5.Sum(token)
|
||||
ctx.User[attrResetToken] = base64.StdEncoding.EncodeToString(sum[:])
|
||||
log.Printf("%#v", ctx.User)
|
||||
ctx.User[attrRecoverToken] = base64.StdEncoding.EncodeToString(sum[:])
|
||||
|
||||
if err := ctx.SaveUser(username, m.storer); err != nil {
|
||||
return err
|
||||
@ -142,25 +144,63 @@ func (m *RecoverModule) initiateRecover(ctx *authboss.Context, username, confirm
|
||||
|
||||
emailBody := &bytes.Buffer{}
|
||||
if err := m.templates.ExecuteTemplate(emailBody, tplInitEmail, struct{ Link string }{
|
||||
fmt.Sprintf("%s/recover/complete?token=%s", host, base64.URLEncoding.EncodeToString(token)),
|
||||
fmt.Sprintf("%s/recover/complete?token=%s", m.hostName, base64.URLEncoding.EncodeToString(sum[:])),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := authboss.SendEmail(email, m.fromEmail, emailBody.Bytes()); err != nil {
|
||||
fmt.Fprintln(m.logger, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return authboss.SendEmail(email, m.fromEmail, emailBody.Bytes())
|
||||
}
|
||||
|
||||
func (m *RecoverModule) recoverCompleteHandlerFunc(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) {
|
||||
/*func (m *RecoverModule) recoverCompleteHandlerFunc(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case methodGET:
|
||||
|
||||
token, ok := ctx.FirstFormValue("token")
|
||||
if !ok {
|
||||
fmt.Fprintln(m.logger, "recover: expected value token")
|
||||
//http.Redirect(w, r, "/", http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
userAttrs, err := m.verifyToken(token);
|
||||
if err != nil {
|
||||
fmt.Fprintf(m.logger, "recover: %s", err)
|
||||
//http.Redirect(w, r, urlStr, code)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
m.templates.ExecuteTemplate(w, tplRecoverComplete, nil)
|
||||
case methodPOST:
|
||||
|
||||
//if err := completeRecover(ctx); err :=
|
||||
default:
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *RecoverModule) verifyToken(token) (attrs authboss.Attributes, err) {
|
||||
decodedToken, err := base64.URLEncoding.DecodeString(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sum := md5.Sum(decodedToken)
|
||||
|
||||
userInter, err := m.storer.RecoverUser(base64.StdEncoding.EncodeToString(sum[:]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return authboss.Unbind(userInter), nil
|
||||
}
|
||||
|
||||
func (m *RecoverModule) completeRecover(ctx *authboss.Context, password, confirmPassword string) error {
|
||||
if password == confirmPassword {
|
||||
return errors.New("Passwords do not match")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
*/
|
||||
|
9
rules.go
9
rules.go
@ -7,11 +7,14 @@ import (
|
||||
"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
|
||||
@ -32,9 +35,8 @@ func (r Rules) Errors(toValidate string) ErrorList {
|
||||
errs := make(ErrorList, 0)
|
||||
|
||||
ln := len(toValidate)
|
||||
if ln == 0 {
|
||||
errs = append(errs, FieldError{r.FieldName, errors.New("Cannot be blank")})
|
||||
return errs
|
||||
if r.Required && (ln == 0 || blankRegex.MatchString(toValidate)) {
|
||||
return append(errs, FieldError{r.FieldName, errors.New("Cannot be blank")})
|
||||
}
|
||||
|
||||
if r.MustMatch != nil {
|
||||
@ -64,6 +66,7 @@ func (r Rules) Errors(toValidate string) ErrorList {
|
||||
if len(errs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
|
@ -14,10 +14,15 @@ func TestRules_Errors(t *testing.T) {
|
||||
Error string
|
||||
}{
|
||||
{
|
||||
Rules{FieldName: "email"},
|
||||
Rules{FieldName: "email", Required: true},
|
||||
"",
|
||||
"email: Cannot be blank",
|
||||
},
|
||||
{
|
||||
Rules{FieldName: "email", Required: true},
|
||||
" \t\t\n ",
|
||||
"email: Cannot be blank",
|
||||
},
|
||||
{
|
||||
Rules{FieldName: "email", MatchError: "Regexp must match!", MustMatch: regexp.MustCompile("abc")},
|
||||
"hello",
|
||||
@ -110,7 +115,7 @@ func TestRules_Rules(t *testing.T) {
|
||||
func TestRules_IsValid(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
r := Rules{FieldName: "email"}
|
||||
r := Rules{FieldName: "email", Required: true}
|
||||
if r.IsValid("") {
|
||||
t.Error("It should not be valid.")
|
||||
}
|
||||
|
36
storer.go
36
storer.go
@ -49,6 +49,11 @@ type TokenStorer interface {
|
||||
UseToken(givenKey, token string) (key string, err error)
|
||||
}
|
||||
|
||||
type RecoverStorer interface {
|
||||
Storer
|
||||
RecoverUser(recover string) (interface{}, error)
|
||||
}
|
||||
|
||||
// DataType represents the various types that clients must be able to store.
|
||||
type DataType int
|
||||
|
||||
@ -100,6 +105,37 @@ func (a Attributes) Names() []string {
|
||||
return names
|
||||
}
|
||||
|
||||
// String returns a single value as a string
|
||||
func (a Attributes) String(key string) (string, bool) {
|
||||
inter, ok := a[key]
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
val, ok := inter.(string)
|
||||
return val, ok
|
||||
}
|
||||
|
||||
// Int returns a single value as a int
|
||||
func (a Attributes) Int(key string) (int, bool) {
|
||||
inter, ok := a[key]
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
val, ok := inter.(int)
|
||||
return val, ok
|
||||
}
|
||||
|
||||
// DateTime returns a single value as a time.Time
|
||||
func (a Attributes) DateTime(key string) (time.Time, bool) {
|
||||
inter, ok := a[key]
|
||||
if !ok {
|
||||
var time time.Time
|
||||
return time, false
|
||||
}
|
||||
val, ok := inter.(time.Time)
|
||||
return val, ok
|
||||
}
|
||||
|
||||
// Bind the data in the attributes to the given struct. This means the
|
||||
// struct creator must have read the documentation and decided what fields
|
||||
// will be needed ahead of time.
|
||||
|
@ -58,7 +58,7 @@ func (f FieldError) Error() string {
|
||||
}
|
||||
|
||||
// Validate validates a request using the given ruleset.
|
||||
func (ctx *Context) Validate(ruleset []Validator) ErrorList {
|
||||
func (ctx *Context) Validate(ruleset []Validator, confirmFields ...string) ErrorList {
|
||||
errList := make(ErrorList, 0)
|
||||
|
||||
for _, validator := range ruleset {
|
||||
@ -70,5 +70,32 @@ func (ctx *Context) Validate(ruleset []Validator) ErrorList {
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < len(confirmFields)-1; i += 2 {
|
||||
main, ok := ctx.FirstPostFormValue(confirmFields[i])
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
confirm, ok := ctx.FirstPostFormValue(confirmFields[i+1])
|
||||
if !ok || main != confirm {
|
||||
errList = append(errList, FieldError{confirmFields[i+1], fmt.Errorf("Does not match %s", confirmFields[i])})
|
||||
}
|
||||
}
|
||||
|
||||
return errList
|
||||
}
|
||||
|
||||
func FilterValidators(validators []Validator, fields ...string) []Validator {
|
||||
var arr []Validator
|
||||
|
||||
for _, validator := range validators {
|
||||
fieldName := validator.Field()
|
||||
for _, field := range fields {
|
||||
if fieldName == field {
|
||||
arr = append(arr, validator)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return arr
|
||||
}
|
||||
|
@ -91,3 +91,47 @@ func TestValidate(t *testing.T) {
|
||||
t.Error("Expected no errors for email.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidate_Confirm(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := mockRequestContext("username", "john", "confirmUsername", "johnny")
|
||||
errs := ctx.Validate(nil, "username", "confirmUsername").Map()
|
||||
if errs["confirmUsername"][0] != "Does not match username" {
|
||||
t.Error("Expected a different error for confirmUsername:", errs["confirmUsername"][0])
|
||||
}
|
||||
|
||||
ctx = mockRequestContext("username", "john", "confirmUsername", "john")
|
||||
errs = ctx.Validate(nil, "username", "confirmUsername").Map()
|
||||
if len(errs) != 0 {
|
||||
t.Error("Expected no errors:", errs)
|
||||
}
|
||||
|
||||
ctx = mockRequestContext("username", "john", "confirmUsername", "john")
|
||||
errs = ctx.Validate(nil, "username").Map()
|
||||
if len(errs) != 0 {
|
||||
t.Error("Expected no errors:", errs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterValidators(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
validators := []Validator{
|
||||
mockValidator{
|
||||
FieldName: "username", Errs: ErrorList{FieldError{"username", errors.New("must be longer than 4")}},
|
||||
},
|
||||
mockValidator{
|
||||
FieldName: "password", Errs: ErrorList{FieldError{"password", errors.New("must be longer than 4")}},
|
||||
},
|
||||
}
|
||||
|
||||
validators = FilterValidators(validators, "username")
|
||||
|
||||
if len(validators) != 1 {
|
||||
t.Error("Expected length to be 1")
|
||||
}
|
||||
if validators[0].Field() != "username" {
|
||||
t.Error("Expcted validator for field username", validators[0].Field())
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user