mirror of
https://github.com/volatiletech/authboss.git
synced 2025-01-22 05:09:42 +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
|
ShowRemember bool
|
||||||
ShowRecover bool
|
ShowRecover bool
|
||||||
|
|
||||||
|
FlashSuccess string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Auth struct {
|
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:
|
case methodPOST:
|
||||||
u, ok := ctx.FirstPostFormValue("username")
|
u, ok := ctx.FirstPostFormValue("username")
|
||||||
if !ok {
|
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 {
|
if err := a.callbacks.FireBefore(authboss.EventAuth, ctx); err != nil {
|
||||||
w.WriteHeader(http.StatusForbidden)
|
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")
|
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 {
|
if err := a.authenticate(ctx, u, p); err != nil {
|
||||||
fmt.Fprintln(a.logger, err)
|
fmt.Fprintln(a.logger, err)
|
||||||
w.WriteHeader(http.StatusForbidden)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,10 @@ const (
|
|||||||
// the remember module. This serves as a way to force full authentication
|
// the remember module. This serves as a way to force full authentication
|
||||||
// by denying half-authed users acccess to sensitive areas.
|
// by denying half-authed users acccess to sensitive areas.
|
||||||
HalfAuthKey = "halfauth"
|
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
|
// 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.
|
// Config holds all the configuration for both authboss and it's modules.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// MountPath is the path to mount the router at.
|
// 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 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"`
|
AuthLogoutRoute string
|
||||||
AuthLoginSuccessRoute string `json:"auth_login_success_route" xml:"authLoginSuccessRoute"`
|
AuthLoginSuccessRoute string
|
||||||
|
|
||||||
ValidateEmail Validator `json:"-" xml:"-"`
|
RecoverInitiateRedirect string
|
||||||
ValidateUsername Validator `json:"-" xml:"-"`
|
RecoverInitiateSuccessFlash string
|
||||||
ValidatePassword Validator `json:"-" xml:"-"`
|
|
||||||
|
|
||||||
ExpireAfter time.Duration `json:"expire_after" xml:"expireAfter"`
|
Policies []Validator
|
||||||
|
ConfirmFields []string
|
||||||
|
|
||||||
LockAfter int `json:"lock_after" xml:"lockAfter"`
|
ExpireAfter time.Duration
|
||||||
LockWindow time.Duration `json:"lock_window" xml:"lockWindow"`
|
LockAfter int
|
||||||
LockDuration time.Duration `json:"lock_duration" xml:"lockDuration"`
|
LockWindow time.Duration
|
||||||
|
LockDuration time.Duration
|
||||||
|
|
||||||
EmailFrom string `json:"email_from" xml:"emailFrom"`
|
EmailFrom string
|
||||||
EmailSubjectPrefix string `json:"email_subject_prefix" xml:"emailSubjectPrefix"`
|
EmailSubjectPrefix string
|
||||||
|
|
||||||
SMTPAddress string `json:"smtp_address" xml:"smtpAddress"`
|
SMTPAddress string
|
||||||
SMTPAuth smtp.Auth `json:"-" xml:"-"`
|
SMTPAuth smtp.Auth
|
||||||
|
|
||||||
Storer Storer `json:"-" xml:"-"`
|
Storer Storer
|
||||||
CookieStoreMaker CookieStoreMaker `json:"-" xml:"-"`
|
CookieStoreMaker CookieStoreMaker
|
||||||
SessionStoreMaker SessionStoreMaker `json:"-" xml:"-"`
|
SessionStoreMaker SessionStoreMaker
|
||||||
LogWriter io.Writer `json:"-" xml:"-"`
|
LogWriter io.Writer
|
||||||
Callbacks *Callbacks `json:"-" xml:"-"`
|
Callbacks *Callbacks
|
||||||
Mailer Mailer `json:"-" xml:"-"`
|
Mailer Mailer
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfig creates a new config full of default values ready to override.
|
// NewConfig creates a new config full of default values ready to override.
|
||||||
@ -50,6 +53,9 @@ func NewConfig() *Config {
|
|||||||
AuthLogoutRoute: "/",
|
AuthLogoutRoute: "/",
|
||||||
AuthLoginSuccessRoute: "/",
|
AuthLoginSuccessRoute: "/",
|
||||||
|
|
||||||
|
RecoverInitiateRedirect: "/login",
|
||||||
|
RecoverInitiateSuccessFlash: "An email has been sent with further insructions on how to reset your password",
|
||||||
|
|
||||||
LogWriter: ioutil.Discard,
|
LogWriter: ioutil.Discard,
|
||||||
Callbacks: NewCallbacks(),
|
Callbacks: NewCallbacks(),
|
||||||
Mailer: LogMailer(ioutil.Discard),
|
Mailer: LogMailer(ioutil.Discard),
|
||||||
|
@ -1,20 +1,17 @@
|
|||||||
package recover
|
package recover
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"bytes"
|
|
||||||
"crypto/md5"
|
|
||||||
"encoding/base64"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"gopkg.in/authboss.v0"
|
"gopkg.in/authboss.v0"
|
||||||
"gopkg.in/authboss.v0/internal/views"
|
"gopkg.in/authboss.v0/internal/views"
|
||||||
)
|
)
|
||||||
@ -23,13 +20,14 @@ const (
|
|||||||
methodGET = "GET"
|
methodGET = "GET"
|
||||||
methodPOST = "POST"
|
methodPOST = "POST"
|
||||||
|
|
||||||
|
tplLogin = "login.tpl"
|
||||||
tplRecover = "recover.tpl"
|
tplRecover = "recover.tpl"
|
||||||
tplRecoverComplete = "recover-complete.tpl"
|
tplRecoverComplete = "recover-complete.tpl"
|
||||||
tplInitEmail = "recover-init.email"
|
tplInitEmail = "recover-init.email"
|
||||||
|
|
||||||
attrUsername = "username"
|
attrUsername = "username"
|
||||||
attrResetToken = "resettoken"
|
attrRecoverToken = "recover_token"
|
||||||
attrEmail = "email"
|
attrEmail = "email"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -38,77 +36,98 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RecoverPage struct {
|
type RecoverPage struct {
|
||||||
Username, ConfirmUsername, Error string
|
Username, ConfirmUsername string
|
||||||
|
ErrMap map[string][]string
|
||||||
}
|
}
|
||||||
|
|
||||||
type RecoverModule struct {
|
type RecoverModule struct {
|
||||||
templates *template.Template
|
templates *template.Template
|
||||||
routes authboss.RouteTable
|
routes authboss.RouteTable
|
||||||
storageOptions authboss.StorageOptions
|
storageOptions authboss.StorageOptions
|
||||||
storer authboss.Storer
|
storer authboss.RecoverStorer
|
||||||
logger io.Writer
|
logger io.Writer
|
||||||
|
policies []authboss.Validator
|
||||||
fromEmail string
|
confirmFields []string
|
||||||
|
hostName string
|
||||||
|
recoverInitiateRedirect string
|
||||||
|
recoverInitiateSuccessFlash string
|
||||||
|
fromEmail string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *RecoverModule) Initialize(config *authboss.Config) (err error) {
|
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 {
|
if m.templates, err = views.Get(config.ViewsPath, tplRecover, tplRecoverComplete, tplInitEmail); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
m.routes = authboss.RouteTable{
|
m.routes = authboss.RouteTable{
|
||||||
"recover": m.recoverHandlerFunc,
|
"recover": m.recoverHandlerFunc,
|
||||||
"recover/complete": m.recoverCompleteHandlerFunc,
|
//"recover/complete": m.recoverCompleteHandlerFunc,
|
||||||
}
|
}
|
||||||
m.storageOptions = authboss.StorageOptions{
|
m.storageOptions = authboss.StorageOptions{
|
||||||
attrUsername: authboss.String,
|
attrUsername: authboss.String,
|
||||||
attrResetToken: authboss.String,
|
attrRecoverToken: authboss.String,
|
||||||
attrEmail: authboss.String,
|
attrEmail: authboss.String,
|
||||||
}
|
}
|
||||||
m.storer = config.Storer
|
|
||||||
m.logger = config.LogWriter
|
m.logger = config.LogWriter
|
||||||
m.fromEmail = config.RecoverFromEmail
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *RecoverModule) Routes() authboss.RouteTable {
|
func (m *RecoverModule) Routes() authboss.RouteTable { return m.routes }
|
||||||
return m.routes
|
func (m *RecoverModule) Storage() authboss.StorageOptions { return m.storageOptions }
|
||||||
}
|
|
||||||
|
|
||||||
func (m *RecoverModule) Storage() authboss.StorageOptions {
|
|
||||||
return m.storageOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *RecoverModule) recoverHandlerFunc(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) {
|
func (m *RecoverModule) recoverHandlerFunc(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) {
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case methodGET:
|
case methodGET:
|
||||||
m.templates.ExecuteTemplate(w, tplRecover, nil)
|
m.templates.ExecuteTemplate(w, tplRecover, nil)
|
||||||
case methodPOST:
|
case methodPOST:
|
||||||
username, ok := ctx.FirstPostFormValue("username")
|
username, _ := ctx.FirstPostFormValue("username")
|
||||||
if !ok {
|
confirmUsername, _ := ctx.FirstPostFormValue("confirmUsername")
|
||||||
fmt.Fprintln(m.logger, errors.New("recover: Expected postFormValue 'username' to be in the context"))
|
|
||||||
}
|
|
||||||
|
|
||||||
confirmUsername, ok := ctx.FirstPostFormValue("confirmUsername")
|
policies := authboss.FilterValidators(m.policies, "username")
|
||||||
if !ok {
|
if validationErrs := ctx.Validate(policies, m.confirmFields...); len(validationErrs) > 0 {
|
||||||
fmt.Fprintln(m.logger, errors.New("recover: Expected postFormValue 'confirmUsername' to be in the context"))
|
err := m.templates.ExecuteTemplate(w, tplRecover, RecoverPage{username, confirmUsername, validationErrs.Map()})
|
||||||
}
|
if err != nil {
|
||||||
|
fmt.Fprintln(m.logger, "recover:", err)
|
||||||
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()})
|
|
||||||
return
|
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:
|
default:
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *RecoverModule) initiateRecover(ctx *authboss.Context, username, confirmUsername, host string) error {
|
func (m *RecoverModule) initiateRecover(ctx *authboss.Context, username, confirmUsername string) (err error) {
|
||||||
if !strings.EqualFold(username, confirmUsername) {
|
if err := ctx.LoadUser(username, m.storer); err != nil {
|
||||||
return errors.New("Confirm username does not match")
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
email, ok := ctx.User.String(attrEmail)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("missing attr: %s", email)
|
||||||
}
|
}
|
||||||
|
|
||||||
token := make([]byte, 32)
|
token := make([]byte, 32)
|
||||||
@ -116,25 +135,8 @@ func (m *RecoverModule) initiateRecover(ctx *authboss.Context, username, confirm
|
|||||||
return err
|
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)
|
sum := md5.Sum(token)
|
||||||
ctx.User[attrResetToken] = base64.StdEncoding.EncodeToString(sum[:])
|
ctx.User[attrRecoverToken] = base64.StdEncoding.EncodeToString(sum[:])
|
||||||
log.Printf("%#v", ctx.User)
|
|
||||||
|
|
||||||
if err := ctx.SaveUser(username, m.storer); err != nil {
|
if err := ctx.SaveUser(username, m.storer); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -142,25 +144,63 @@ func (m *RecoverModule) initiateRecover(ctx *authboss.Context, username, confirm
|
|||||||
|
|
||||||
emailBody := &bytes.Buffer{}
|
emailBody := &bytes.Buffer{}
|
||||||
if err := m.templates.ExecuteTemplate(emailBody, tplInitEmail, struct{ Link string }{
|
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 {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := authboss.SendEmail(email, m.fromEmail, emailBody.Bytes()); err != nil {
|
return authboss.SendEmail(email, m.fromEmail, emailBody.Bytes())
|
||||||
fmt.Fprintln(m.logger, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
switch r.Method {
|
||||||
case methodGET:
|
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)
|
m.templates.ExecuteTemplate(w, tplRecoverComplete, nil)
|
||||||
case methodPOST:
|
case methodPOST:
|
||||||
|
//if err := completeRecover(ctx); err :=
|
||||||
default:
|
default:
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
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"
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var blankRegex = regexp.MustCompile(`^\s*$`)
|
||||||
|
|
||||||
// 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 {
|
||||||
// FieldName is the name of the field this is intended to validate.
|
// FieldName is the name of the field this is intended to validate.
|
||||||
FieldName string
|
FieldName string
|
||||||
// MatchError describes the MustMatch regexp to a user.
|
// MatchError describes the MustMatch regexp to a user.
|
||||||
|
Required bool
|
||||||
MatchError string
|
MatchError string
|
||||||
MustMatch *regexp.Regexp
|
MustMatch *regexp.Regexp
|
||||||
MinLength, MaxLength int
|
MinLength, MaxLength int
|
||||||
@ -32,9 +35,8 @@ func (r Rules) Errors(toValidate string) ErrorList {
|
|||||||
errs := make(ErrorList, 0)
|
errs := make(ErrorList, 0)
|
||||||
|
|
||||||
ln := len(toValidate)
|
ln := len(toValidate)
|
||||||
if ln == 0 {
|
if r.Required && (ln == 0 || blankRegex.MatchString(toValidate)) {
|
||||||
errs = append(errs, FieldError{r.FieldName, errors.New("Cannot be blank")})
|
return append(errs, FieldError{r.FieldName, errors.New("Cannot be blank")})
|
||||||
return errs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.MustMatch != nil {
|
if r.MustMatch != nil {
|
||||||
@ -64,6 +66,7 @@ func (r Rules) Errors(toValidate string) ErrorList {
|
|||||||
if len(errs) == 0 {
|
if len(errs) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,10 +14,15 @@ func TestRules_Errors(t *testing.T) {
|
|||||||
Error string
|
Error string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Rules{FieldName: "email"},
|
Rules{FieldName: "email", Required: true},
|
||||||
"",
|
"",
|
||||||
"email: Cannot be blank",
|
"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")},
|
Rules{FieldName: "email", MatchError: "Regexp must match!", MustMatch: regexp.MustCompile("abc")},
|
||||||
"hello",
|
"hello",
|
||||||
@ -110,7 +115,7 @@ func TestRules_Rules(t *testing.T) {
|
|||||||
func TestRules_IsValid(t *testing.T) {
|
func TestRules_IsValid(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
r := Rules{FieldName: "email"}
|
r := Rules{FieldName: "email", Required: true}
|
||||||
if r.IsValid("") {
|
if r.IsValid("") {
|
||||||
t.Error("It should not be valid.")
|
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)
|
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.
|
// DataType represents the various types that clients must be able to store.
|
||||||
type DataType int
|
type DataType int
|
||||||
|
|
||||||
@ -100,6 +105,37 @@ func (a Attributes) Names() []string {
|
|||||||
return names
|
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
|
// Bind the data in the attributes to the given struct. This means the
|
||||||
// struct creator must have read the documentation and decided what fields
|
// struct creator must have read the documentation and decided what fields
|
||||||
// will be needed ahead of time.
|
// will be needed ahead of time.
|
||||||
|
@ -58,7 +58,7 @@ func (f FieldError) Error() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates a request using the given ruleset.
|
// 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)
|
errList := make(ErrorList, 0)
|
||||||
|
|
||||||
for _, validator := range ruleset {
|
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
|
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.")
|
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…
x
Reference in New Issue
Block a user