mirror of
https://github.com/volatiletech/authboss.git
synced 2025-03-29 22:06:58 +02:00
Rework recover
This commit is contained in:
parent
ec5da7694e
commit
199d0ec0b3
31
auth/auth.go
31
auth/auth.go
@ -1,6 +1,7 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
@ -17,18 +18,22 @@ const (
|
||||
)
|
||||
|
||||
func init() {
|
||||
a := &AuthModule{}
|
||||
a := &Auth{}
|
||||
authboss.RegisterModule("auth", a)
|
||||
}
|
||||
|
||||
type AuthModule struct {
|
||||
type Auth struct {
|
||||
templates render.Templates
|
||||
policies []authboss.Validator
|
||||
isRememberLoaded bool
|
||||
isRecoverLoaded bool
|
||||
}
|
||||
|
||||
func (a *AuthModule) Initialize() (err error) {
|
||||
func (a *Auth) Initialize() (err error) {
|
||||
if authboss.Cfg.Storer == nil {
|
||||
return errors.New("auth: Need a Storer.")
|
||||
}
|
||||
|
||||
a.templates, err = render.LoadTemplates(authboss.Cfg.Layout, authboss.Cfg.ViewsPath, tplLogin)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -42,21 +47,21 @@ func (a *AuthModule) Initialize() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AuthModule) Routes() authboss.RouteTable {
|
||||
func (a *Auth) Routes() authboss.RouteTable {
|
||||
return authboss.RouteTable{
|
||||
"login": a.loginHandlerFunc,
|
||||
"logout": a.logoutHandlerFunc,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AuthModule) Storage() authboss.StorageOptions {
|
||||
func (a *Auth) Storage() authboss.StorageOptions {
|
||||
return authboss.StorageOptions{
|
||||
authboss.Cfg.PrimaryID: authboss.String,
|
||||
authboss.StorePassword: authboss.String,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AuthModule) loginHandlerFunc(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) error {
|
||||
func (a *Auth) loginHandlerFunc(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) error {
|
||||
switch r.Method {
|
||||
case methodGET:
|
||||
if _, ok := ctx.SessionStorer.Get(authboss.SessionKey); ok {
|
||||
@ -65,7 +70,12 @@ func (a *AuthModule) loginHandlerFunc(ctx *authboss.Context, w http.ResponseWrit
|
||||
}
|
||||
}
|
||||
|
||||
data := authboss.NewHTMLData("showRemember", a.isRememberLoaded, "showRecover", a.isRecoverLoaded)
|
||||
data := authboss.NewHTMLData(
|
||||
"showRemember", a.isRememberLoaded,
|
||||
"showRecover", a.isRecoverLoaded,
|
||||
"primaryID", authboss.Cfg.PrimaryID,
|
||||
"primaryIDValue", "",
|
||||
)
|
||||
return a.templates.Render(ctx, w, r, tplLogin, data)
|
||||
case methodPOST:
|
||||
interrupted, err := authboss.Cfg.Callbacks.FireBefore(authboss.EventAuth, ctx)
|
||||
@ -87,8 +97,9 @@ func (a *AuthModule) loginHandlerFunc(ctx *authboss.Context, w http.ResponseWrit
|
||||
password, _ := ctx.FirstPostFormValue("password")
|
||||
|
||||
errData := authboss.NewHTMLData(
|
||||
"error", "invalid username and/or password",
|
||||
authboss.Cfg.PrimaryID, key,
|
||||
"error", fmt.Sprintf("invalid %s and/or password", authboss.Cfg.PrimaryID),
|
||||
"primaryID", authboss.Cfg.PrimaryID,
|
||||
"primaryIDValue", key,
|
||||
"showRemember", a.isRememberLoaded,
|
||||
"showRecover", a.isRecoverLoaded,
|
||||
)
|
||||
@ -130,7 +141,7 @@ func validateCredentials(ctx *authboss.Context, key, password string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AuthModule) logoutHandlerFunc(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) error {
|
||||
func (a *Auth) logoutHandlerFunc(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) error {
|
||||
switch r.Method {
|
||||
case methodGET:
|
||||
ctx.SessionStorer.Del(authboss.SessionKey)
|
||||
|
@ -10,11 +10,6 @@ import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
const (
|
||||
layoutTpl = "layout.tpl"
|
||||
layoutEmailTpl = "layoutEmail.tpl"
|
||||
)
|
||||
|
||||
// Cfg is the singleton instance of Config
|
||||
var Cfg *Config = NewConfig()
|
||||
|
||||
@ -105,6 +100,7 @@ func NewConfig() *Config {
|
||||
},
|
||||
ConfirmFields: []string{
|
||||
StoreEmail, ConfirmPrefix + StoreEmail,
|
||||
StoreUsername, ConfirmPrefix + StoreUsername,
|
||||
StorePassword, ConfirmPrefix + StorePassword,
|
||||
},
|
||||
|
||||
|
@ -76,7 +76,7 @@ func confirm_email_html_tpl() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindata_file_info{name: "confirm_email.html.tpl", size: 26, mode: os.FileMode(438), modTime: time.Unix(1424498554, 0)}
|
||||
info := bindata_file_info{name: "confirm_email.html.tpl", size: 26, mode: os.FileMode(438), modTime: time.Unix(1424471280, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
@ -96,12 +96,12 @@ func confirm_email_txt_tpl() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindata_file_info{name: "confirm_email.txt.tpl", size: 9, mode: os.FileMode(438), modTime: time.Unix(1424498554, 0)}
|
||||
info := bindata_file_info{name: "confirm_email.txt.tpl", size: 9, mode: os.FileMode(438), modTime: time.Unix(1424471280, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _login_tpl = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x7c\x92\x4d\x6e\x83\x30\x10\x85\xf7\x95\x7a\x87\xd1\xec\x5b\x2e\x00\x96\xba\xef\x4f\xd4\xa6\x07\x30\x66\x28\x28\xd8\x83\x06\x3b\x49\x85\xb8\x7b\xed\x12\x12\x90\xaa\xb2\xb2\x9e\x1f\xef\x7b\xcc\x90\xd7\x2c\x16\xb4\xf1\x2d\xbb\x02\xb3\x8e\xbf\x5a\x87\x60\xc9\x37\x5c\x15\xb8\x7b\xfb\xd8\xa3\xba\xbf\x83\xf8\x8c\x63\x5b\xc3\x23\x89\xb0\x4c\xd3\x38\x2e\xa7\xbc\x14\xc8\xd4\x38\x92\xab\xa6\x69\x76\xe6\xad\xeb\x83\x07\xff\xdd\x53\x81\x9e\xce\x1e\xc1\x74\x7a\x18\x0a\x4c\xb0\x07\xc3\xce\x0b\x77\x08\x4e\xdb\x68\x08\x03\x49\x3a\x21\xf4\x9d\x36\xd4\x70\x57\x91\x14\xf8\x79\x95\x8f\xba\x0b\xd1\x17\x91\x8b\x75\x9a\x50\xcd\xdc\x0d\xf0\x42\xec\x23\xea\xc4\x52\xfd\x4b\xbd\x99\x36\xd4\xdd\x22\xff\x95\x3f\xc7\x37\x6d\x55\x91\x5b\x72\x62\xab\xf3\x20\xf5\xeb\xdc\x6a\xd5\x35\xa9\x7b\x3e\x90\x4b\x72\xb6\x99\xe1\xd0\xf0\xe9\x9d\x2c\xd9\x92\xd2\x00\xd7\xe1\xa6\x21\x73\x28\xf9\xbc\xc4\x8b\xbd\x66\x7a\x09\x84\x0a\x96\x17\xe1\x85\xb6\x43\x5f\xf7\x2d\x83\xf7\xec\x2e\x99\x43\x28\x6d\xeb\x51\x3d\xa7\xdd\xe6\xd9\x7c\xb7\xf9\xbe\x75\x2d\xc3\xc7\xdf\x56\x1a\x1a\xa1\x3a\xfe\x12\x32\x4b\xa8\x2e\x77\xf0\x64\x0c\x07\xe7\xf3\x4c\xdf\xd6\x9e\x67\x69\xc8\xea\x27\x00\x00\xff\xff\xe8\x9d\xff\x88\x4e\x02\x00\x00")
|
||||
var _login_tpl = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x7c\x92\x4d\x6b\xf3\x30\x0c\x80\xef\x85\xfe\x07\xe3\xfb\xdb\xfc\x81\x24\xf0\xc2\x2e\x83\x7d\x94\xad\xf7\xe1\x38\xca\x62\x1a\x5b\x41\x96\xfb\x41\xc8\x7f\x9f\xbd\xa4\x6b\x02\x63\x39\x05\x49\x7e\xf4\x58\xf2\x30\xec\x9a\x4e\xf9\xf6\xc3\x07\xad\xc1\xfb\x71\xdc\x6e\xf2\x06\xc9\x0a\xa5\xd9\xa0\x2b\x64\xd6\xe1\xa7\x71\x52\x58\xe0\x16\xeb\x42\xee\x5f\xdf\x0f\xb2\xdc\x6e\x44\xfc\x86\xc1\x34\x62\x07\x44\x48\xe3\x18\x51\xf3\x5f\x5e\x91\xc8\xca\x61\x00\x57\x27\x5e\xaa\xcc\x8d\xeb\x03\x0b\xbe\xf6\x50\x48\x86\x0b\x4b\xa1\x63\x5f\x5f\xc8\xd4\xec\x9f\x46\xc7\x84\x9d\x14\x4e\xd9\x58\x10\x51\x3d\x19\xab\xe8\xfa\xf8\x30\x8e\x52\xf4\x9d\xd2\xd0\x62\x57\x03\xa5\x24\x1b\xee\x40\xac\x4b\x4e\xaa\x0b\xd3\xc9\xe0\x81\x12\x26\x46\xcb\xc9\x64\xa5\x30\x3b\xf4\xb1\xf9\x19\xa9\xfe\xd3\xe3\x5e\xb4\x32\xd8\xdf\xc2\xbf\xf1\x27\x7c\x6b\xea\x1a\xdc\xe2\x3e\x17\x4f\xcd\xcb\x64\xb5\x70\x4d\xd1\x03\x1e\xc1\xa5\x70\xb6\x9a\xaa\x6f\xf1\xfc\x06\x16\x6c\x05\x69\xa4\x4b\xb8\x6e\x41\x1f\x2b\xbc\xdc\xf0\x64\x7f\x98\x4c\x01\x64\x29\x6e\x07\xc5\x33\xac\xd7\x50\x05\x66\x74\x33\xc7\x87\xca\x1a\x96\xe5\x53\xda\x70\x9e\x4d\xb9\xd5\x9d\x96\x2a\x1a\x4f\xdf\x26\x4a\xb4\x04\x4d\x7c\x18\x34\x85\x64\x39\xe7\xc4\x7f\xad\x31\x38\xce\x33\x75\x5f\x7e\x9e\xa5\xc1\x96\x5f\x01\x00\x00\xff\xff\xe7\xed\x2e\xa4\x68\x02\x00\x00")
|
||||
|
||||
func login_tpl_bytes() ([]byte, error) {
|
||||
return bindata_read(
|
||||
@ -116,12 +116,12 @@ func login_tpl() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindata_file_info{name: "login.tpl", size: 590, mode: os.FileMode(438), modTime: time.Unix(1424567032, 0)}
|
||||
info := bindata_file_info{name: "login.tpl", size: 616, mode: os.FileMode(438), modTime: time.Unix(1424735294, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _recover_complete_tpl = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xd4\x54\xc1\x8e\xd3\x30\x10\xbd\xef\x57\x8c\xac\xbd\xa6\xb9\xa3\xa4\x17\x04\x37\x60\x45\xf7\xc0\xd5\x8d\xa7\x1b\x6b\x1d\xdb\x4c\x9c\xc2\xca\xf2\xbf\x33\x8e\x9b\x6e\x89\x5a\x81\x84\x38\x6c\x24\x2b\x9e\xb1\xe7\x79\xde\x7b\xd2\x34\x07\x47\x03\xc8\x2e\x68\x67\x5b\x51\x13\x76\xee\x88\x54\x77\x6e\xf0\x06\x03\x0a\x18\x30\xf4\x4e\xb5\xe2\xe1\xcb\xee\x51\x6c\xef\x80\xbf\x46\x5b\x3f\x05\x08\x2f\x1e\x5b\xd1\x6b\xa5\xd0\x0a\xb0\x72\xe0\x28\xb8\xe7\x1c\x1c\xa5\x99\x38\x8a\x71\xf3\x98\x13\x29\x09\xa8\x4b\x6d\x8c\xf7\x5e\x8e\xe3\x0f\x47\xea\x03\xd1\x08\xef\x5a\xd8\xf0\xe6\x93\xf4\x9b\x25\x9f\x52\x79\x45\xe9\x23\x74\x86\x93\xad\xc8\x4d\x56\x4f\xe4\x26\x1f\xa3\x3e\xc0\x6f\x10\x29\x41\x2f\xc7\x0a\x89\x1c\xc5\x88\x96\xeb\x4f\x7d\xae\x51\xe6\xb6\x0b\xcc\xc5\x8d\xf9\xd6\xe8\xa5\xbd\x72\xad\x92\x4a\x39\x2b\xb6\x8d\x3e\x77\x22\xe1\x20\x2b\xe3\xba\x67\xce\xd6\x9a\x57\x2e\x5d\xa1\x15\x7d\x2e\x7b\xef\x9c\x0d\xe4\x8c\x38\x89\x16\xf0\x67\x58\x24\x5b\xb8\x08\xf0\x46\x76\xd8\x3b\xa3\x90\x58\xef\x73\x9a\xf0\xfb\xa4\x09\xd5\x22\xe1\xfc\x44\xcd\xbc\x5e\xc3\x18\x49\xda\x27\x84\x7b\x56\x21\x4b\xba\x12\xe8\x36\xd7\x1e\x8d\xaf\xf6\x85\x4e\x8c\x9e\xb4\x0d\x33\x48\x4a\x6b\x62\x27\x69\xef\x2e\x5e\x5f\x0c\x65\x72\x07\x4d\xc3\xc3\x0d\x5f\x57\xc7\x7f\x61\xef\x15\xc0\xb7\xee\xf2\x8a\xd2\xca\xec\xf7\xe5\x14\xfe\xcd\xf4\xab\xb2\xfd\x27\xef\xe7\xed\x7e\x0a\xc1\x9d\xf1\xf6\xc1\x02\xaf\x8a\x91\x06\x49\x2f\xf3\xbe\xc0\x9f\xf4\x18\xa7\xfd\xa0\x83\xd8\xee\xe6\x7f\x53\x97\xf2\x3f\xce\x14\x9e\x22\xdf\x76\x5f\x3f\x7e\xe6\x20\x0f\x92\xd7\xd9\x92\xb3\x97\xf3\xa5\xa9\xb3\x0b\xdb\x5f\x01\x00\x00\xff\xff\x30\xbb\xc0\xa5\xd3\x04\x00\x00")
|
||||
var _recover_complete_tpl = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xb4\x52\xc1\x6e\xea\x30\x10\xbc\xf3\x15\x2b\xeb\x9d\xf1\xfd\xc9\xc9\x85\x7b\x8b\x5a\xee\x95\x89\x37\xc4\xaa\xb3\x8e\xd6\x0e\x50\x45\xf9\xf7\xda\x04\x08\x6d\xa9\x7a\xa8\x9a\x4b\x3c\xeb\x9d\x19\x8f\x34\xaa\xf6\xdc\x82\xae\xa2\xf5\x54\x08\xc9\x58\xf9\x3d\xb2\xac\x7c\xdb\x39\x8c\x28\xa0\xc5\xd8\x78\x53\x88\xf5\xe3\xf3\x46\x94\x0b\x48\x9f\xb2\xd4\xf5\x11\xe2\x5b\x87\x85\x68\xac\x31\x48\x02\x48\xb7\x09\x45\xff\x9a\xc1\x5e\xbb\x3e\xa1\x61\x58\x9e\x06\xe3\x28\x40\xde\xe1\x76\x3a\x84\x83\x67\x73\x61\xcf\xb8\x73\xba\xc2\xc6\x3b\x83\x9c\xac\xaf\xe3\x59\xf7\xb2\x3a\x49\xab\x2d\x5f\x0c\x86\xe1\x60\x63\x03\x4b\x64\x0e\xe3\x78\x46\xff\x12\x72\x36\x44\xf8\x5f\x80\x25\x83\x47\x58\xc2\xec\x96\xd7\x58\xd3\x0e\xaf\x7b\xe3\xa8\x42\xa7\xa9\x4c\x46\xe9\x28\x4f\xe7\xc9\x63\x18\x90\x4c\x26\xdc\xfe\x7e\x4e\x56\x79\xaa\x2d\xb7\x2f\xdf\x24\x5c\x4d\xd7\x70\x2f\xe9\x99\xba\xfe\x7d\xe0\x2f\x8f\xf8\x83\xe0\x1f\xeb\x90\x64\x8e\x81\xeb\x87\x04\xf2\xbb\xe7\x50\x79\xba\xf9\x5c\x8d\x6d\x1f\xa3\xa7\xb3\x50\xe8\xb7\xad\x8d\xa2\x7c\x9a\x1a\xa9\xe4\x74\x7b\x9b\x5c\x69\x68\x18\xeb\x54\x5b\xe7\x77\x96\x44\xb9\xd2\x54\xa1\x53\x52\x97\x0b\x25\x73\xb1\xcb\xf7\x00\x00\x00\xff\xff\x67\x02\x74\x02\xdf\x02\x00\x00")
|
||||
|
||||
func recover_complete_tpl_bytes() ([]byte, error) {
|
||||
return bindata_read(
|
||||
@ -136,12 +136,12 @@ func recover_complete_tpl() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindata_file_info{name: "recover-complete.tpl", size: 1235, mode: os.FileMode(438), modTime: time.Unix(1424498554, 0)}
|
||||
info := bindata_file_info{name: "recover-complete.tpl", size: 735, mode: os.FileMode(438), modTime: time.Unix(1424735204, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _recover_html_email = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xb2\x29\x2e\x29\xca\xcf\x4b\xb7\xab\xae\xd6\xf3\xc9\xcc\xcb\xae\xad\xb5\xd1\x87\x8a\x00\x02\x00\x00\xff\xff\xe1\x46\x1b\xff\x1a\x00\x00\x00")
|
||||
var _recover_html_email = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xb2\x29\x2e\x29\xca\xcf\x4b\xb7\xab\xae\xd6\xab\xad\xb5\xd1\x87\xf2\x00\x01\x00\x00\xff\xff\xe7\xfa\xf4\xa7\x16\x00\x00\x00")
|
||||
|
||||
func recover_html_email_bytes() ([]byte, error) {
|
||||
return bindata_read(
|
||||
@ -156,12 +156,12 @@ func recover_html_email() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindata_file_info{name: "recover-html.email", size: 26, mode: os.FileMode(438), modTime: time.Unix(1424498554, 0)}
|
||||
info := bindata_file_info{name: "recover-html.email", size: 22, mode: os.FileMode(438), modTime: time.Unix(1424728125, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _recover_text_email = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xaa\xae\xd6\xf3\xc9\xcc\xcb\xae\xad\x05\x04\x00\x00\xff\xff\x41\xf7\xa1\x3d\x09\x00\x00\x00")
|
||||
var _recover_text_email = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xaa\xae\xd6\xab\xad\x05\x04\x00\x00\xff\xff\x8e\x60\xe8\x72\x05\x00\x00\x00")
|
||||
|
||||
func recover_text_email_bytes() ([]byte, error) {
|
||||
return bindata_read(
|
||||
@ -176,12 +176,12 @@ func recover_text_email() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindata_file_info{name: "recover-text.email", size: 9, mode: os.FileMode(438), modTime: time.Unix(1424498554, 0)}
|
||||
info := bindata_file_info{name: "recover-text.email", size: 5, mode: os.FileMode(438), modTime: time.Unix(1424728129, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var _recover_tpl = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xd4\x54\x4d\x8f\x9b\x30\x10\xbd\xf3\x2b\xa6\xd6\x5e\xbd\xdc\x2b\xe0\x12\xb5\xb7\x7e\x68\x3f\xa4\x5e\x0d\x98\x60\xc5\xd8\x68\x30\x51\x57\x16\xff\xbd\x43\x0c\x1b\xc2\x92\x36\x6d\xd5\x43\x23\x11\x0d\x66\xfc\x9e\xe7\xbd\xf1\x24\x95\xc5\x06\x44\xe1\x94\x35\x29\x8b\x51\x16\xf6\x28\x91\x41\x23\x5d\x6d\xcb\x94\x7d\xfd\xf2\xf8\xc4\xb2\x08\xe8\x97\x28\xd3\xf6\x0e\xdc\x4b\x2b\x53\xe6\xe4\x77\xc7\xc0\x88\x86\xe2\xbe\x93\x38\x46\x0c\x5a\x2d\x0a\x59\x5b\x5d\x4a\x4c\xd9\xf3\xeb\xf2\x51\xe8\x9e\xf2\xbc\xbf\x9f\x53\x87\x81\x41\x9c\x25\x39\xd2\xff\xcf\xc1\x0b\x6b\x2a\x85\xcd\xf3\x36\xc7\x2e\x7c\x85\x2d\xae\xd5\xce\xb7\x94\x79\xef\x9c\x35\x13\x67\xd7\xe7\x8d\x72\x2c\x7b\x08\x0a\x24\x71\xf8\x3a\xa5\x0a\xa8\x51\x56\x24\x90\xb6\x7b\x65\x58\xb6\x13\xa6\x90\x3a\x89\x45\x16\x25\xf1\x28\x61\x16\x45\x51\xf2\x8e\x73\xf8\x0d\x41\xbd\xbf\x9b\xf5\xf8\x80\xd8\xc1\xfb\x14\xee\x29\xf8\x24\xda\x85\x4e\x81\xbf\x54\x47\x28\xb4\xe8\xba\x94\x8d\xf8\x7c\x8f\xb6\x6f\xbd\x57\x15\x5c\x40\x0c\x03\xd4\xa2\xe3\x12\xd1\xa2\xf7\xd2\x94\x54\x74\xe0\x5a\xa3\x9c\xe4\x0e\x30\x8b\x8c\x53\x56\xd7\x0a\xb3\x91\xc6\x45\x59\x5a\x2a\x3d\x51\xaf\x27\x11\x50\x09\x3e\xf2\xd3\x6a\xac\xe8\x19\xb7\xae\xd0\x82\xaf\xcb\xb3\x93\x2f\x0e\xad\x66\x7f\xd7\x49\x97\xb6\x9e\x6b\x8c\xa9\xc8\xf3\xab\xf7\x28\xcc\x5e\xc2\x1d\x49\x32\xea\xbb\x52\xeb\x7a\xe1\xb5\xd4\x2d\xcf\xb5\x2d\x0e\x2c\xf3\xbe\x45\x65\xdc\x09\x64\x18\xd6\x55\x4e\x3a\x47\x0b\xf6\xd9\xdd\x55\x07\xae\x4d\x7e\xd3\xa0\xbf\xf4\x7a\x03\xf0\x7f\xb7\xfc\xcf\xef\xf7\x6e\xeb\x7e\xdf\xdc\x08\x9b\x52\xfe\xa3\x7e\x18\xc3\x8b\x91\x33\x81\xe6\xce\x00\x3d\x9c\xe0\x1a\x81\x2f\xa7\x38\x70\xdc\x38\x94\x56\x38\x5a\x99\xc3\x55\x90\xeb\x13\x6c\x61\x5a\xd8\x51\xab\xb2\x94\x66\x76\x88\xa4\xfe\xf6\xf8\xf0\xf1\xf3\xa4\xf1\xd9\x80\x71\xf5\xc9\x1e\xa4\x99\xa4\x9f\x26\x21\x70\x9e\xfd\x08\x00\x00\xff\xff\x94\x24\x92\xf9\x57\x06\x00\x00")
|
||||
var _recover_tpl = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xb4\x53\x4d\x6f\xe2\x30\x10\xbd\xf3\x2b\x46\x16\x7b\x25\xf7\x55\xe2\x0b\xbb\x87\x5e\x5a\xd4\xa2\x5e\x2b\x93\x4c\x88\x55\xc7\x8e\x26\x13\x0a\x4a\xfd\xdf\x6b\x63\x8a\x48\x2b\x81\x7a\x68\x2e\x99\x8f\x37\x2f\xf3\x9e\x26\x79\xed\xa8\x05\x55\xb2\x76\xb6\x10\x19\x61\xe9\x76\x48\x02\x5a\xe4\xc6\x55\x85\x58\x3d\x3c\xad\x85\x9c\x41\x78\xc6\xf1\x4d\x73\x03\x8b\xda\xa8\xbe\xf9\x4f\xe4\xc8\xfb\x71\x5c\x78\x9f\x6f\x08\x32\x39\x8e\x68\x2b\xef\x8f\xd0\x5c\xdb\x6e\x60\xe0\x43\x87\x85\x60\xdc\xb3\x00\xab\xda\x10\x07\x7c\x47\xba\x55\x74\xb8\xfb\xe7\xbd\x80\xce\xa8\x12\x1b\x67\x2a\xa4\xd8\x64\xcd\x06\x61\x0a\xd9\x29\x33\x7c\x99\x7c\x8e\xa5\xd8\xcb\x64\xfa\xf6\x69\xbf\x79\xa7\x2b\xf8\x5b\x4c\x08\x3e\xb7\x46\xa2\xfe\x9c\xcd\x43\x66\x74\xcf\x11\xac\x6d\x85\x7b\x58\x40\x1c\x8e\x00\x52\x76\x8b\x67\x44\x50\xd7\x77\xca\xca\x24\x34\x3b\xc6\x13\xbd\xd3\xd7\x75\xf5\xa5\xb3\xb5\xa6\xf6\xe5\xaa\x0b\xcb\x04\x82\x5b\x6e\x9c\xc8\x56\x37\x4d\x29\xbf\xb9\x02\xef\x10\x62\xcb\x35\x9c\x57\xfa\xd3\x8b\x9f\x78\x55\xfe\x8e\x59\x8d\xae\x2a\xb4\x17\xc7\xb2\xef\xa9\xbe\x0f\xc9\x54\x7a\xac\xae\xdd\x2b\xda\x24\x37\xf1\x6c\x06\x66\x67\x4f\x44\xfd\xb0\x69\x35\x0b\xf9\x98\xee\x39\xcf\x52\xf7\xd2\x99\x5c\x41\x43\x58\x87\xa3\x37\x6e\xab\xad\x90\x4b\x65\x4b\x34\x79\xa6\xe4\x2c\xcf\xe2\x6f\x21\x3f\x02\x00\x00\xff\xff\x93\x4d\xba\xa6\x1d\x03\x00\x00")
|
||||
|
||||
func recover_tpl_bytes() ([]byte, error) {
|
||||
return bindata_read(
|
||||
@ -196,7 +196,7 @@ func recover_tpl() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindata_file_info{name: "recover.tpl", size: 1623, mode: os.FileMode(438), modTime: time.Unix(1424567032, 0)}
|
||||
info := bindata_file_info{name: "recover.tpl", size: 797, mode: os.FileMode(438), modTime: time.Unix(1424735212, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
@ -216,7 +216,7 @@ func register_html_tpl() (*asset, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindata_file_info{name: "register.html.tpl", size: 907, mode: os.FileMode(438), modTime: time.Unix(1424683974, 0)}
|
||||
info := bindata_file_info{name: "register.html.tpl", size: 907, mode: os.FileMode(438), modTime: time.Unix(1424722297, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
{{.flash_success}}
|
||||
<form action="/login" method="POST">
|
||||
{{if .error}}{{.error}}<br />{{end}}
|
||||
<input type="text" class="form-control" name="username" placeholder="Username" value="{{.username}}"><br />
|
||||
<input type="text" class="form-control" name="{{.primaryID}}" placeholder="{{title .primaryID}}" value="{{.username}}"><br />
|
||||
<input type="password" class="form-control" name="password" placeholder="Password"><br />
|
||||
<input type="hidden" name="{{.xsrfName}}" value="{{.xsrfToken}}" />
|
||||
{{if .showRemember}}<input type="checkbox" name="rm" value="true"> Remember Me{{end}}
|
||||
<br />
|
||||
<button type="submit">Login</button><br />
|
||||
{{if .showRecover}}<a href="/recover">Recover Account</a>{{end}}
|
||||
</form>
|
@ -1,26 +1,10 @@
|
||||
<form action="/recover/complete" method="POST">
|
||||
<input type="hidden" name="token" value="{{.Token}}" />
|
||||
{{$passwordErrs := .ErrMap.password}}
|
||||
<div class="form-group{{if $passwordErrs}} has-error{{end}}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-lock"></i></span>
|
||||
<input class="form-control" type="text" name="password" placeholder="Password" required />
|
||||
</div>
|
||||
{{range $err := $passwordErrs}}
|
||||
<span class="help-block">{{print $err}}</span>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
{{$confirmPasswordErrs := .ErrMap.confirmPassword}}
|
||||
<div class="form-group{{if $confirmPasswordErrs}} has-error{{end}}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-lock"></i></span>
|
||||
<input class="form-control" type="text" name="confirmPassword" placeholder="Confirm Password" required />
|
||||
</div>
|
||||
{{range $err := $confirmPasswordErrs}}
|
||||
<span class="help-block">{{print $err}}</span>
|
||||
{{end}}
|
||||
</div>
|
||||
<button class="btn btn-primary btn-block" type="submit">Submit</button>
|
||||
<input type="hidden" name="{{.XSRFName}}" value="{{.XSRFToken}}" />
|
||||
<input type="hidden" name="token" value="{{.token}}" />
|
||||
<input type="password" name="password" placeholder="Password" value="{{.password}}" /><br />
|
||||
{{with .errs}}{{with $errlist := index . "password"}}{{range $errlist}}<span>{{.}}</span><br />{{end}}{{end}}{{end}}
|
||||
<input type="password" name="confirm_password" placeholder="Confirm Password" value="{{.confirmPassword}}" /><br />
|
||||
{{with .errs}}{{with $errlist := index . "confirm_password"}}{{range $errlist}}<span>{{.}}</span><br />{{end}}{{end}}{{end}}
|
||||
<input type="hidden" name="{{.xsrfName}}" value="{{.xsrfToken}}" />
|
||||
<button type="submit">Recover</button><br />
|
||||
<a href="/login">Cancel</a>
|
||||
</form>
|
@ -1 +1 @@
|
||||
<strong>{{.Link}}</strong>
|
||||
<strong>{{.}}</strong>
|
@ -1 +1 @@
|
||||
{{.Link}}
|
||||
{{.}}
|
@ -1,35 +1,10 @@
|
||||
<form action="/recover" method="POST">
|
||||
<input type="text" name="username" placeholder="Username" value="{{.username}}" /><br />
|
||||
<input type="text" name="confirmUsername" placeholder="Confirm Username" value="{{.confirmUsername}}" /><br />
|
||||
<button type="submit">Recover</button>
|
||||
{{with .flashError}}{{.}}<br />{{end}}
|
||||
<input type="text" name="{{.primaryID}}" placeholder="{{title .primaryID}}" value="{{.primaryIDValue}}" /><br />
|
||||
{{$pid := .primaryID}}{{with .errs}}{{with $errlist := index . $pid}}{{range $errlist}}<span>{{.}}</span><br />{{end}}{{end}}{{end}}
|
||||
<input type="text" name="confirm_{{.primaryID}}" placeholder="Confirm {{title .primaryID}}" value="{{.confirmPrimaryIDValue}}" /><br />
|
||||
{{$cpid := .primaryID | printf "confirm_%s"}}{{with .errs}}{{with $errlist := index . $cpid}}{{range $errlist}}<span>{{.}}</span><br />{{end}}{{end}}{{end}}
|
||||
<input type="hidden" name="{{.xsrfName}}" value="{{.xsrfToken}}" />
|
||||
<button type="submit">Recover</button><br />
|
||||
<a href="/login">Cancel</a>
|
||||
</form>
|
||||
|
||||
|
||||
<!-- <form action="/recover" method="POST">
|
||||
{{$usernameErrs := .ErrMap.username}}
|
||||
<div class="form-group{{if $usernameErrs}} has-error{{end}}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-user"></i></span>
|
||||
<input class="form-control" type="text" name="username" placeholder="Username" value="{{.Username}}" />
|
||||
</div>
|
||||
{{range $err := $usernameErrs}}
|
||||
<span class="help-block">{{print $err}}</span>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
{{$confirmUsernameErrs := .ErrMap.confirmUsername}}
|
||||
<div class="form-group{{if $confirmUsernameErrs}} has-error{{end}}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-user"></i></span>
|
||||
<input class="form-control" type="text" name="confirmUsername" placeholder="Confirm Username" value="{{.ConfirmUsername}}" />
|
||||
</div>
|
||||
{{range $err := $confirmUsernameErrs}}
|
||||
<span class="help-block">{{print $err}}</span>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary btn-block" type="submit">Recover</button>
|
||||
<a class="btn btn-link btn-block" type="submit" href="/login">Cancel</a>
|
||||
<input type="hidden" name="{{.XSRFName}}" value="{{.XSRFToken}}" />
|
||||
</form> -->
|
||||
</form>
|
@ -1,143 +0,0 @@
|
||||
package recover
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gopkg.in/authboss.v0"
|
||||
"gopkg.in/authboss.v0/internal/flashutil"
|
||||
)
|
||||
|
||||
var errRecoveryTokenExpired = errors.New("recovery token expired")
|
||||
|
||||
type pageRecoverComplete struct {
|
||||
Token, Password, ConfirmPassword string
|
||||
ErrMap map[string][]string
|
||||
FlashSuccess, FlashError string
|
||||
XSRFName, XSRFToken string
|
||||
}
|
||||
|
||||
func (m *RecoverModule) recoverCompleteHandlerFunc(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case methodGET:
|
||||
_, err := verifyToken(ctx, authboss.Cfg.Storer.(authboss.RecoverStorer))
|
||||
if err != nil {
|
||||
if err.Error() == errRecoveryTokenExpired.Error() {
|
||||
fmt.Fprintln(authboss.Cfg.LogWriter, "recover [token expired]:", err)
|
||||
ctx.SessionStorer.Put(authboss.FlashErrorKey, authboss.Cfg.RecoverTokenExpiredFlash)
|
||||
http.Redirect(w, r, "/recover", http.StatusFound)
|
||||
return
|
||||
} else {
|
||||
fmt.Fprintln(authboss.Cfg.LogWriter, "recover:", err)
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
token, _ := ctx.FirstFormValue("token")
|
||||
|
||||
page := pageRecoverComplete{
|
||||
Token: token,
|
||||
FlashError: flashutil.Pull(ctx.SessionStorer, authboss.FlashErrorKey),
|
||||
XSRFName: authboss.Cfg.XSRFName,
|
||||
XSRFToken: authboss.Cfg.XSRFMaker(w, r),
|
||||
}
|
||||
m.execTpl(tplRecoverComplete, w, page)
|
||||
case methodPOST:
|
||||
errPage := m.recoverComplete(ctx, authboss.Cfg.XSRFName, authboss.Cfg.XSRFMaker(w, r))
|
||||
if errPage != nil {
|
||||
m.execTpl(tplRecoverComplete, w, *errPage)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, authboss.Cfg.AuthLoginSuccessRoute, http.StatusFound)
|
||||
default:
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
// verifyToken expects a base64.URLEncoded token.
|
||||
func verifyToken(ctx *authboss.Context, storer authboss.RecoverStorer) (attrs authboss.Attributes, err error) {
|
||||
token, ok := ctx.FirstFormValue("token")
|
||||
if !ok {
|
||||
return nil, errors.New("missing form value: token")
|
||||
}
|
||||
|
||||
decoded, err := base64.URLEncoding.DecodeString(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sum := md5.Sum(decoded)
|
||||
userInter, err := storer.RecoverUser(base64.StdEncoding.EncodeToString(sum[:]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
attrs = authboss.Unbind(userInter)
|
||||
|
||||
expiry, ok := attrs.DateTime(attrRecoverTokenExpiry)
|
||||
if !ok || time.Now().After(expiry) {
|
||||
return nil, errRecoveryTokenExpired
|
||||
}
|
||||
|
||||
return attrs, nil
|
||||
}
|
||||
|
||||
func (m *RecoverModule) recoverComplete(ctx *authboss.Context, xsrfName, xsrfToken string) (errPage *pageRecoverComplete) {
|
||||
token, _ := ctx.FirstFormValue("token")
|
||||
password, _ := ctx.FirstPostFormValue("password")
|
||||
confirmPassword, _ := ctx.FirstPostFormValue("confirmPassword")
|
||||
defaultErrPage := &pageRecoverComplete{
|
||||
Token: token,
|
||||
Password: password,
|
||||
ConfirmPassword: confirmPassword,
|
||||
FlashError: authboss.Cfg.RecoverFailedErrorFlash,
|
||||
XSRFName: xsrfName,
|
||||
XSRFToken: xsrfToken,
|
||||
}
|
||||
|
||||
var err error
|
||||
ctx.User, err = verifyToken(ctx, authboss.Cfg.Storer.(authboss.RecoverStorer))
|
||||
if err != nil {
|
||||
fmt.Fprintf(authboss.Cfg.LogWriter, errFormat, "failed to verify token", err)
|
||||
return defaultErrPage
|
||||
}
|
||||
|
||||
policies := authboss.FilterValidators(authboss.Cfg.Policies, "password")
|
||||
if validationErrs := ctx.Validate(policies, authboss.Cfg.ConfirmFields...); len(validationErrs) > 0 {
|
||||
fmt.Fprintf(authboss.Cfg.LogWriter, errFormat, "validation failed", validationErrs)
|
||||
return &pageRecoverComplete{
|
||||
Token: token,
|
||||
Password: password,
|
||||
ConfirmPassword: confirmPassword,
|
||||
ErrMap: validationErrs.Map(),
|
||||
XSRFName: xsrfName,
|
||||
XSRFToken: xsrfToken,
|
||||
}
|
||||
}
|
||||
|
||||
encryptedPassword, err := bcrypt.GenerateFromPassword([]byte(password), authboss.Cfg.BCryptCost)
|
||||
if err != nil {
|
||||
fmt.Fprintf(authboss.Cfg.LogWriter, errFormat, "failed to encrypt password", err)
|
||||
return defaultErrPage
|
||||
}
|
||||
ctx.User[attrPassword] = string(encryptedPassword)
|
||||
ctx.User[attrRecoverToken] = ""
|
||||
var nullTime time.Time
|
||||
ctx.User[attrRecoverTokenExpiry] = nullTime
|
||||
|
||||
username, _ := ctx.User.String(attrUsername)
|
||||
if err := ctx.SaveUser(); err != nil {
|
||||
fmt.Fprintf(authboss.Cfg.LogWriter, errFormat, "failed to save user", err)
|
||||
return defaultErrPage
|
||||
}
|
||||
|
||||
ctx.SessionStorer.Put(authboss.SessionKey, username)
|
||||
return nil
|
||||
}
|
@ -1,370 +0,0 @@
|
||||
package recover
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gopkg.in/authboss.v0"
|
||||
"gopkg.in/authboss.v0/internal/mocks"
|
||||
)
|
||||
|
||||
const (
|
||||
testUrlBase64Token = "MTIzNA=="
|
||||
testStdBase64Token = "gdyb21LQTcIANtvYMT7QVQ=="
|
||||
)
|
||||
|
||||
func Test_recoverCompleteHandlerFunc_GET_TokenExpired(t *testing.T) {
|
||||
m, logger := testValidRecoverModule()
|
||||
|
||||
storer, ok := authboss.Cfg.Storer.(*mocks.MockStorer)
|
||||
if !ok {
|
||||
panic("Failed to get storer")
|
||||
}
|
||||
storer.Users["b"] = authboss.Attributes{
|
||||
attrUsername: "b",
|
||||
attrRecoverToken: testStdBase64Token,
|
||||
attrRecoverTokenExpiry: time.Now().Add(time.Duration(-24) * time.Hour),
|
||||
}
|
||||
|
||||
w, r, ctx := testHttpRequest("GET", fmt.Sprintf("/recover/complete?token=%s", testUrlBase64Token), nil)
|
||||
clientStorer := mocks.NewMockClientStorer()
|
||||
ctx.SessionStorer = clientStorer
|
||||
|
||||
m.recoverCompleteHandlerFunc(ctx, w, r)
|
||||
|
||||
actualLog, err := ioutil.ReadAll(logger)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if !bytes.Contains(actualLog, []byte("recover [token expired]:")) {
|
||||
t.Error("Expected logs to start with:", "recover [token expired]:")
|
||||
}
|
||||
|
||||
if flash := clientStorer.Values[authboss.FlashErrorKey]; flash != authboss.Cfg.RecoverTokenExpiredFlash {
|
||||
t.Error("Unexpected error flash:", flash)
|
||||
}
|
||||
|
||||
if location := w.Header().Get("Location"); location != "/recover" {
|
||||
t.Error("Unexpected location:", location)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_recoverCompleteHandlerFunc_GET_OtherErrors(t *testing.T) {
|
||||
m, logger := testValidRecoverModule()
|
||||
w, r, ctx := testHttpRequest("GET", "/recover/complete?token=asdf", nil)
|
||||
|
||||
m.recoverCompleteHandlerFunc(ctx, w, r)
|
||||
|
||||
actualLog, err := ioutil.ReadAll(logger)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if !bytes.Contains(actualLog, []byte("recover:")) {
|
||||
t.Error("Expected logs to start with:", "recover:")
|
||||
}
|
||||
|
||||
if location := w.Header().Get("Location"); location != "/" {
|
||||
t.Error("Unexpected location:", location)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_recoverCompleteHandlerFunc_GET(t *testing.T) {
|
||||
m, _ := testValidRecoverModule()
|
||||
|
||||
storer, ok := authboss.Cfg.Storer.(*mocks.MockStorer)
|
||||
if !ok {
|
||||
panic("Failed to get storer")
|
||||
}
|
||||
storer.Users["b"] = authboss.Attributes{
|
||||
attrUsername: "b",
|
||||
attrRecoverToken: testStdBase64Token,
|
||||
attrRecoverTokenExpiry: time.Now().Add(time.Duration(24) * time.Hour),
|
||||
}
|
||||
|
||||
w, r, ctx := testHttpRequest("GET", fmt.Sprintf("/recover/complete?token=%s", testUrlBase64Token), nil)
|
||||
sessionStorer := mocks.NewMockClientStorer()
|
||||
sessionStorer.Values = map[string]string{authboss.FlashErrorKey: "asdf"}
|
||||
ctx.SessionStorer = sessionStorer
|
||||
|
||||
m.recoverCompleteHandlerFunc(ctx, w, r)
|
||||
|
||||
page := pageRecoverComplete{
|
||||
Token: testUrlBase64Token,
|
||||
FlashError: "asdf",
|
||||
XSRFName: authboss.Cfg.XSRFName,
|
||||
XSRFToken: authboss.Cfg.XSRFMaker(nil, nil),
|
||||
}
|
||||
expectedBody := &bytes.Buffer{}
|
||||
if err := m.templates[tplRecoverComplete].Execute(expectedBody, page); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Error("Unexpected code:", w.Code)
|
||||
}
|
||||
if !bytes.Equal(expectedBody.Bytes(), w.Body.Bytes()) {
|
||||
t.Error("Unexpected body:", w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func Test_recoverCompleteHandlerFunc_POST_RecoveryCompleteFailed(t *testing.T) {
|
||||
m, _ := testValidRecoverModule()
|
||||
w, r, ctx := testHttpRequest(
|
||||
"POST",
|
||||
fmt.Sprintf("/recover/complete?token=%s", testUrlBase64Token),
|
||||
url.Values{"password": []string{"a"}, "confirmPassword": []string{"a"}},
|
||||
)
|
||||
|
||||
tpl := m.templates[tplRecoverComplete]
|
||||
expectedBody := &bytes.Buffer{}
|
||||
if err := tpl.Execute(expectedBody, pageRecoverComplete{
|
||||
Token: testUrlBase64Token,
|
||||
Password: "a",
|
||||
ConfirmPassword: "a",
|
||||
FlashError: authboss.Cfg.RecoverFailedErrorFlash,
|
||||
XSRFName: authboss.Cfg.XSRFName,
|
||||
XSRFToken: authboss.Cfg.XSRFMaker(nil, nil),
|
||||
}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// missing storer will cause this to fail
|
||||
m.recoverCompleteHandlerFunc(ctx, w, r)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Error("Unexpected code:", w.Code)
|
||||
}
|
||||
|
||||
if !bytes.Equal(expectedBody.Bytes(), w.Body.Bytes()) {
|
||||
t.Error("Unexpected body:", w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func Test_recoverCompleteHandlerFunc_POST(t *testing.T) {
|
||||
m, _ := testValidRecoverModule()
|
||||
w, r, ctx := testHttpRequest(
|
||||
"POST",
|
||||
fmt.Sprintf("/recover/complete?token=%s", testUrlBase64Token),
|
||||
url.Values{"password": []string{"a"}, "confirmPassword": []string{"a"}},
|
||||
)
|
||||
|
||||
storer, ok := authboss.Cfg.Storer.(*mocks.MockStorer)
|
||||
if !ok {
|
||||
panic("Failed to get storer")
|
||||
}
|
||||
storer.Users["a"] = authboss.Attributes{
|
||||
attrUsername: "a",
|
||||
attrRecoverToken: testStdBase64Token,
|
||||
attrRecoverTokenExpiry: time.Now().Add(time.Duration(24) * time.Hour),
|
||||
}
|
||||
|
||||
m.recoverCompleteHandlerFunc(ctx, w, r)
|
||||
|
||||
if w.Code != http.StatusFound {
|
||||
t.Error("Unexpected code:", w.Code)
|
||||
}
|
||||
|
||||
location := w.Header().Get("Location")
|
||||
if location != "/login" {
|
||||
t.Error("Unexpected redirect:", location)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_verifyToken_MissingToken(t *testing.T) {
|
||||
authboss.NewConfig()
|
||||
ctx := mocks.MockRequestContext()
|
||||
_, err := verifyToken(ctx, nil)
|
||||
|
||||
if err.Error() != "missing form value: token" {
|
||||
t.Error("Unexpected error:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_verifyToken_InvalidToken(t *testing.T) {
|
||||
authboss.NewConfig()
|
||||
testValidTestConfig()
|
||||
storer, ok := authboss.Cfg.Storer.(*mocks.MockStorer)
|
||||
if !ok {
|
||||
panic("Failed to get storer")
|
||||
}
|
||||
storer.Users["a"] = authboss.Attributes{
|
||||
attrRecoverToken: testStdBase64Token,
|
||||
}
|
||||
|
||||
ctx := mocks.MockRequestContext("token", "asdf")
|
||||
_, err := verifyToken(ctx, storer)
|
||||
|
||||
if err.Error() != authboss.ErrUserNotFound.Error() {
|
||||
t.Error("Unexpected error:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_verifyToken_ExpiredToken(t *testing.T) {
|
||||
testValidTestConfig()
|
||||
storer, ok := authboss.Cfg.Storer.(*mocks.MockStorer)
|
||||
if !ok {
|
||||
panic("Failed to get storer")
|
||||
}
|
||||
storer.Users["a"] = authboss.Attributes{
|
||||
attrRecoverToken: testStdBase64Token,
|
||||
attrRecoverTokenExpiry: time.Now().Add(time.Duration(-24) * time.Hour),
|
||||
}
|
||||
|
||||
ctx := mocks.MockRequestContext("token", testUrlBase64Token)
|
||||
_, err := verifyToken(ctx, storer)
|
||||
|
||||
if err.Error() != "recovery token expired" {
|
||||
t.Error("Unexpected error:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_verifyToken(t *testing.T) {
|
||||
testValidTestConfig()
|
||||
|
||||
storer, ok := authboss.Cfg.Storer.(*mocks.MockStorer)
|
||||
if !ok {
|
||||
panic("Failed to get storer")
|
||||
}
|
||||
storer.Users["a"] = authboss.Attributes{
|
||||
attrRecoverToken: testStdBase64Token,
|
||||
attrRecoverTokenExpiry: time.Now().Add(time.Duration(24) * time.Hour),
|
||||
}
|
||||
|
||||
ctx := mocks.MockRequestContext("token", testUrlBase64Token)
|
||||
attrs, err := verifyToken(ctx, storer)
|
||||
|
||||
if err != nil {
|
||||
t.Error("Unexpected error:", err)
|
||||
}
|
||||
if attrs == nil {
|
||||
t.Error("Unexpected nil attrs")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_recoverComplete_TokenVerificationFails(t *testing.T) {
|
||||
m, logger := testValidRecoverModule()
|
||||
ctx := mocks.MockRequestContext()
|
||||
|
||||
errPage := m.recoverComplete(ctx, "", "")
|
||||
if errPage == nil {
|
||||
t.Error("Expected err page")
|
||||
}
|
||||
if !reflect.DeepEqual(*errPage, pageRecoverComplete{FlashError: authboss.Cfg.RecoverFailedErrorFlash}) {
|
||||
t.Error("Unexpected err page:", errPage)
|
||||
}
|
||||
|
||||
actualLog, err := ioutil.ReadAll(logger)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if !bytes.Contains(actualLog, []byte("recover [failed to verify token]:")) {
|
||||
t.Error("Expected logs to start with:", "recover [failed to verify token]:")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_recoverComplete_ValidationFails(t *testing.T) {
|
||||
m, logger := testValidRecoverModule()
|
||||
ctx := mocks.MockRequestContext("token", testUrlBase64Token, "password", "a", "confirmPassword", "b")
|
||||
|
||||
storer, ok := authboss.Cfg.Storer.(*mocks.MockStorer)
|
||||
if !ok {
|
||||
panic("Failed to get storer")
|
||||
}
|
||||
storer.Users["b"] = authboss.Attributes{
|
||||
attrRecoverToken: testStdBase64Token,
|
||||
attrRecoverTokenExpiry: time.Now().Add(time.Duration(24) * time.Hour),
|
||||
}
|
||||
|
||||
errPage := m.recoverComplete(ctx, "", "")
|
||||
if errPage == nil {
|
||||
t.Error("Expected err page")
|
||||
}
|
||||
expectedErrPage := pageRecoverComplete{
|
||||
Token: testUrlBase64Token,
|
||||
Password: "a",
|
||||
ConfirmPassword: "b",
|
||||
ErrMap: map[string][]string{"confirmPassword": []string{"Does not match password"}},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(*errPage, expectedErrPage) {
|
||||
t.Error("Unexpected err page:", errPage)
|
||||
}
|
||||
|
||||
actualLog, err := ioutil.ReadAll(logger)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
expetedLog := []byte("recover [validation failed]: confirmPassword: Does not match password\n")
|
||||
if !bytes.Equal(actualLog, expetedLog) {
|
||||
t.Error("Expected logs:", string(expetedLog))
|
||||
}
|
||||
}
|
||||
|
||||
func Test_recoverComplete(t *testing.T) {
|
||||
m, _ := testValidRecoverModule()
|
||||
ctx := mocks.MockRequestContext("token", testUrlBase64Token, "password", "a", "confirmPassword", "a")
|
||||
|
||||
clientStorer := mocks.NewMockClientStorer()
|
||||
ctx.SessionStorer = clientStorer
|
||||
|
||||
storer, ok := authboss.Cfg.Storer.(*mocks.MockStorer)
|
||||
if !ok {
|
||||
panic("Failed to get storer")
|
||||
}
|
||||
storer.Users["b"] = authboss.Attributes{
|
||||
attrUsername: "b",
|
||||
attrRecoverToken: testStdBase64Token,
|
||||
attrRecoverTokenExpiry: time.Now().Add(time.Duration(24) * time.Hour),
|
||||
}
|
||||
|
||||
if errPage := m.recoverComplete(ctx, "", ""); errPage != nil {
|
||||
t.Error("Expected nil err page")
|
||||
}
|
||||
|
||||
password, ok := storer.Users["b"].String(attrPassword)
|
||||
if !ok {
|
||||
panic("cannot find password")
|
||||
}
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(password), []byte("a")); err != nil {
|
||||
t.Error("Unexpected encrypted password")
|
||||
}
|
||||
|
||||
recoverToken, ok := storer.Users["b"].String(attrRecoverToken)
|
||||
if !ok {
|
||||
panic("cannot find token")
|
||||
}
|
||||
if recoverToken != "" {
|
||||
t.Error("Unexpected recover token:", recoverToken)
|
||||
}
|
||||
|
||||
recoverTokenExpiry, ok := storer.Users["b"].DateTime(attrRecoverTokenExpiry)
|
||||
if !ok {
|
||||
panic("cannot find token")
|
||||
}
|
||||
var nullTime time.Time
|
||||
if recoverTokenExpiry != nullTime {
|
||||
t.Error("Unexpected recover token expiry:", recoverTokenExpiry)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_recoverCompleteHandlerFunc_OtherMethods(t *testing.T) {
|
||||
m, _ := testValidRecoverModule()
|
||||
|
||||
for i, method := range []string{"HEAD", "PUT", "DELETE", "TRACE", "CONNECT"} {
|
||||
w, r, _ := testHttpRequest(method, "/recover/complete", nil)
|
||||
|
||||
m.recoverHandlerFunc(nil, w, r)
|
||||
|
||||
if http.StatusMethodNotAllowed != w.Code {
|
||||
t.Errorf("%d> Expected status code %d, got %d", i, http.StatusMethodNotAllowed, w.Code)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
141
recover/init.go
141
recover/init.go
@ -1,141 +0,0 @@
|
||||
package recover
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"gopkg.in/authboss.v0"
|
||||
"gopkg.in/authboss.v0/internal/flashutil"
|
||||
)
|
||||
|
||||
type pageRecover struct {
|
||||
Username, ConfirmUsername string
|
||||
ErrMap map[string][]string
|
||||
FlashSuccess, FlashError string
|
||||
XSRFName, XSRFToken string
|
||||
}
|
||||
|
||||
func (m *RecoverModule) recoverHandlerFunc(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case methodGET:
|
||||
page := pageRecover{
|
||||
FlashError: flashutil.Pull(ctx.SessionStorer, authboss.FlashErrorKey),
|
||||
XSRFName: authboss.Cfg.XSRFName,
|
||||
XSRFToken: authboss.Cfg.XSRFMaker(w, r),
|
||||
}
|
||||
|
||||
m.execTpl(tplRecover, w, page)
|
||||
case methodPOST:
|
||||
errPage, _ := m.recover(ctx, authboss.Cfg.XSRFName, authboss.Cfg.XSRFMaker(w, r))
|
||||
|
||||
if errPage != nil {
|
||||
m.execTpl(tplRecover, w, *errPage)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SessionStorer.Put(authboss.FlashSuccessKey, authboss.Cfg.RecoverInitiateSuccessFlash)
|
||||
http.Redirect(w, r, authboss.Cfg.RecoverRedirect, http.StatusFound)
|
||||
default:
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *RecoverModule) recover(ctx *authboss.Context, xsrfName, xsrfToken string) (errPage *pageRecover, emailSent <-chan struct{}) {
|
||||
username, _ := ctx.FirstPostFormValue("username")
|
||||
confirmUsername, _ := ctx.FirstPostFormValue("confirmUsername")
|
||||
|
||||
policies := authboss.FilterValidators(authboss.Cfg.Policies, "username")
|
||||
if validationErrs := ctx.Validate(policies, authboss.Cfg.ConfirmFields...); len(validationErrs) > 0 {
|
||||
fmt.Fprintf(authboss.Cfg.LogWriter, errFormat, "validation failed", validationErrs)
|
||||
return &pageRecover{
|
||||
Username: username,
|
||||
ConfirmUsername: confirmUsername,
|
||||
ErrMap: validationErrs.Map(),
|
||||
XSRFName: xsrfName,
|
||||
XSRFToken: xsrfToken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
err, emailSent := m.makeAndSendToken(ctx, username)
|
||||
if err != nil {
|
||||
fmt.Fprintf(authboss.Cfg.LogWriter, errFormat, "failed to recover", err)
|
||||
return &pageRecover{
|
||||
Username: username,
|
||||
ConfirmUsername: confirmUsername,
|
||||
FlashError: authboss.Cfg.RecoverFailedErrorFlash,
|
||||
XSRFName: xsrfName,
|
||||
XSRFToken: xsrfToken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, emailSent
|
||||
}
|
||||
|
||||
func (m *RecoverModule) makeAndSendToken(ctx *authboss.Context, username string) (err error, emailSent <-chan struct{}) {
|
||||
if err = ctx.LoadUser(username); err != nil {
|
||||
return err, nil
|
||||
}
|
||||
|
||||
email, ok := ctx.User.String(attrEmail)
|
||||
if !ok || email == "" {
|
||||
return fmt.Errorf("email required: %v", attrEmail), nil
|
||||
}
|
||||
|
||||
token := make([]byte, 32)
|
||||
if _, err = rand.Read(token); err != nil {
|
||||
return err, nil
|
||||
}
|
||||
sum := md5.Sum(token)
|
||||
|
||||
ctx.User[attrRecoverToken] = base64.StdEncoding.EncodeToString(sum[:])
|
||||
ctx.User[attrRecoverTokenExpiry] = time.Now().Add(authboss.Cfg.RecoverTokenDuration)
|
||||
|
||||
if err = ctx.SaveUser(); err != nil {
|
||||
return err, nil
|
||||
}
|
||||
|
||||
return nil, m.sendRecoverEmail(email, token)
|
||||
}
|
||||
|
||||
func (m *RecoverModule) sendRecoverEmail(to string, token []byte) <-chan struct{} {
|
||||
emailSent := make(chan struct{}, 1)
|
||||
|
||||
go func() {
|
||||
data := struct{ Link string }{fmt.Sprintf("%s/recover/complete?token=%s", authboss.Cfg.HostName, base64.URLEncoding.EncodeToString(token))}
|
||||
|
||||
htmlEmailBody, err := m.emailTemplates.ExecuteTemplate(tplInitHTMLEmail, data)
|
||||
if err != nil {
|
||||
fmt.Fprintf(authboss.Cfg.LogWriter, errFormat, "failed to build html email", err)
|
||||
close(emailSent)
|
||||
return
|
||||
}
|
||||
|
||||
textEmaiLBody, err := m.emailTemplates.ExecuteTemplate(tplInitTextEmail, data)
|
||||
if err != nil {
|
||||
fmt.Fprintf(authboss.Cfg.LogWriter, errFormat, "failed to build plaintext email", err)
|
||||
close(emailSent)
|
||||
return
|
||||
}
|
||||
|
||||
if err := authboss.Cfg.Mailer.Send(authboss.Email{
|
||||
To: []string{to},
|
||||
ToNames: []string{""},
|
||||
From: authboss.Cfg.EmailFrom,
|
||||
Subject: authboss.Cfg.EmailSubjectPrefix + "Password Reset",
|
||||
TextBody: textEmaiLBody.String(),
|
||||
HTMLBody: htmlEmailBody.String(),
|
||||
}); err != nil {
|
||||
fmt.Fprintf(authboss.Cfg.LogWriter, errFormat, "failed to send email", err)
|
||||
close(emailSent)
|
||||
return
|
||||
}
|
||||
|
||||
emailSent <- struct{}{}
|
||||
}()
|
||||
|
||||
return emailSent
|
||||
}
|
@ -1,385 +0,0 @@
|
||||
package recover
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"gopkg.in/authboss.v0"
|
||||
"gopkg.in/authboss.v0/internal/mocks"
|
||||
)
|
||||
|
||||
func Test_recoverHandlerFunc_GET(t *testing.T) {
|
||||
m, _ := testValidRecoverModule()
|
||||
w, r, ctx := testHttpRequest("GET", "/recover", nil)
|
||||
|
||||
m.recoverHandlerFunc(ctx, w, r)
|
||||
|
||||
expectedBody := &bytes.Buffer{}
|
||||
err := m.templates[tplRecover].Execute(expectedBody, pageRecover{
|
||||
XSRFName: authboss.Cfg.XSRFName,
|
||||
XSRFToken: authboss.Cfg.XSRFMaker(nil, nil),
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Error("Unexpected code:", w.Code)
|
||||
}
|
||||
if !bytes.Equal(expectedBody.Bytes(), w.Body.Bytes()) {
|
||||
t.Error("Unexpected body:", w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func Test_recoverHandlerFunc_POST_RecoveryFailed(t *testing.T) {
|
||||
m, _ := testValidRecoverModule()
|
||||
w, r, ctx := testHttpRequest("POST", "/login", url.Values{"username": []string{"a"}, "confirmUsername": []string{"a"}})
|
||||
|
||||
tpl := m.templates[tplRecover]
|
||||
expectedBody := &bytes.Buffer{}
|
||||
if err := tpl.Execute(expectedBody, pageRecover{
|
||||
Username: "a",
|
||||
ConfirmUsername: "a",
|
||||
FlashError: authboss.Cfg.RecoverFailedErrorFlash,
|
||||
XSRFName: authboss.Cfg.XSRFName,
|
||||
XSRFToken: authboss.Cfg.XSRFMaker(nil, nil),
|
||||
}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// missing storer will cause this to fail
|
||||
m.recoverHandlerFunc(ctx, w, r)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Error("Unexpected code:", w.Code)
|
||||
}
|
||||
|
||||
if !bytes.Equal(expectedBody.Bytes(), w.Body.Bytes()) {
|
||||
t.Error("Unexpected body:", w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func Test_recoverHandlerFunc_POST(t *testing.T) {
|
||||
m, _ := testValidRecoverModule()
|
||||
w, r, ctx := testHttpRequest("POST", "/login", url.Values{"username": []string{"a"}, "confirmUsername": []string{"a"}})
|
||||
|
||||
storer, ok := authboss.Cfg.Storer.(*mocks.MockStorer)
|
||||
if !ok {
|
||||
panic("Failed to get storer")
|
||||
}
|
||||
storer.Users["a"] = authboss.Attributes{"username": "", "password": "", "email": "a@b.c"}
|
||||
|
||||
m.recoverHandlerFunc(ctx, w, r)
|
||||
|
||||
if w.Code != http.StatusFound {
|
||||
t.Error("Unexpected code:", w.Code)
|
||||
}
|
||||
|
||||
location := w.Header().Get("Location")
|
||||
if location != "/login" {
|
||||
t.Error("Unexpected redirect:", location)
|
||||
}
|
||||
|
||||
successFlash := ctx.SessionStorer.(*mocks.MockClientStorer).Values[authboss.FlashSuccessKey]
|
||||
if successFlash != authboss.Cfg.RecoverInitiateSuccessFlash {
|
||||
t.Error("Unexpected success flash message:", successFlash)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_recover_UsernameValidationFail(t *testing.T) {
|
||||
m, logger := testValidRecoverModule()
|
||||
ctx := mocks.MockRequestContext()
|
||||
|
||||
page, emailSent := m.recover(ctx, "", "")
|
||||
if len(page.ErrMap["username"]) != 1 {
|
||||
t.Error("Exepted single validation error for username")
|
||||
}
|
||||
if page.ErrMap["username"][0] != "Cannot be blank" {
|
||||
t.Error("Unexpected validation error for username:", page.ErrMap["username"][0])
|
||||
}
|
||||
expectedLog := []byte("recover [validation failed]: username: Cannot be blank\n")
|
||||
actualLog, err := ioutil.ReadAll(logger)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if !bytes.Equal(expectedLog, actualLog) {
|
||||
t.Errorf("Unexpected logs: %q", string(actualLog))
|
||||
}
|
||||
if emailSent != nil {
|
||||
t.Error("Unexpected sent email")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_recover_ConfirmUsernameCheckFail(t *testing.T) {
|
||||
m, logger := testValidRecoverModule()
|
||||
ctx := mocks.MockRequestContext("username", "a", "confirmUsername", "b")
|
||||
|
||||
page, emailSent := m.recover(ctx, "", "")
|
||||
if len(page.ErrMap["username"]) != 0 {
|
||||
t.Error("Exepted no validation errors for username")
|
||||
}
|
||||
if len(page.ErrMap["confirmUsername"]) != 1 {
|
||||
t.Error("Exepted single validation error for confirmUsername")
|
||||
}
|
||||
if page.ErrMap["confirmUsername"][0] != "Does not match username" {
|
||||
t.Error("Unexpected validation error for confirmUsername:", page.ErrMap["confirmUsername"][0])
|
||||
}
|
||||
expectedLog := []byte("recover [validation failed]: confirmUsername: Does not match username\n")
|
||||
actualLog, err := ioutil.ReadAll(logger)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if !bytes.Equal(expectedLog, actualLog) {
|
||||
t.Error("Unexpected logs:", string(actualLog))
|
||||
}
|
||||
if emailSent != nil {
|
||||
t.Error("Unexpected sent email")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_recover_InvalidUser(t *testing.T) {
|
||||
m, logger := testValidRecoverModule()
|
||||
ctx := mocks.MockRequestContext("username", "a", "confirmUsername", "a")
|
||||
|
||||
page, emailSent := m.recover(ctx, "", "")
|
||||
if page.ErrMap != nil {
|
||||
t.Error("Exepted no validation errors")
|
||||
}
|
||||
if page.FlashError != authboss.Cfg.RecoverFailedErrorFlash {
|
||||
t.Error("Expected flash error:", authboss.Cfg.RecoverFailedErrorFlash)
|
||||
}
|
||||
|
||||
actualLog, err := ioutil.ReadAll(logger)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if !bytes.Contains(actualLog, []byte("recover [failed to recover]:")) {
|
||||
t.Error("Expected log message starting with:", "recover [failed to recover]:")
|
||||
}
|
||||
if emailSent != nil {
|
||||
t.Error("Unexpected sent email")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_recover(t *testing.T) {
|
||||
m, _ := testValidRecoverModule()
|
||||
|
||||
storer, ok := authboss.Cfg.Storer.(*mocks.MockStorer)
|
||||
if !ok {
|
||||
panic("Failed to get storer")
|
||||
}
|
||||
storer.Users["a"] = authboss.Attributes{"username": "", "password": "", "email": "a@b.c"}
|
||||
|
||||
ctx := mocks.MockRequestContext("username", "a", "confirmUsername", "a")
|
||||
|
||||
page, emailSent := m.recover(ctx, "", "")
|
||||
if page != nil {
|
||||
t.Error("Expected nil page")
|
||||
}
|
||||
if emailSent == nil {
|
||||
t.Error("Expected sent email")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_makeAndSendToken_MissingStorer(t *testing.T) {
|
||||
m, _ := testValidRecoverModule()
|
||||
ctx := mocks.MockRequestContext()
|
||||
|
||||
err, ch := m.makeAndSendToken(ctx, "a")
|
||||
if err == nil || err.Error() != authboss.ErrUserNotFound.Error() {
|
||||
t.Error("Expected error:", authboss.ErrUserNotFound)
|
||||
}
|
||||
if ch != nil {
|
||||
t.Error("Expected nil channel")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_makeAndSendToken_CheckEmail(t *testing.T) {
|
||||
m, _ := testValidRecoverModule()
|
||||
ctx := mocks.MockRequestContext()
|
||||
|
||||
storer, ok := authboss.Cfg.Storer.(*mocks.MockStorer)
|
||||
if !ok {
|
||||
panic("Failed to get storer")
|
||||
}
|
||||
storer.Users["a"] = authboss.Attributes{"username": "", "password": ""}
|
||||
|
||||
// missing
|
||||
err, ch := m.makeAndSendToken(ctx, "a")
|
||||
expectedErr := fmt.Sprintf("email required: %v", attrEmail)
|
||||
if err == nil || err.Error() != expectedErr {
|
||||
t.Error("Expected error:", expectedErr)
|
||||
}
|
||||
if ch != nil {
|
||||
t.Error("Expected nil channel")
|
||||
}
|
||||
|
||||
// empty
|
||||
storer.Users["a"] = authboss.Attributes{"username": "", "password": "", "email": ""}
|
||||
err, ch = m.makeAndSendToken(ctx, "a")
|
||||
if err == nil || err.Error() != expectedErr {
|
||||
t.Error("Expected error:", expectedErr)
|
||||
}
|
||||
if ch != nil {
|
||||
t.Error("Expected nil channel")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_makeAndSendToken(t *testing.T) {
|
||||
m, _ := testValidRecoverModule()
|
||||
ctx := mocks.MockRequestContext()
|
||||
|
||||
storer, ok := authboss.Cfg.Storer.(*mocks.MockStorer)
|
||||
if !ok {
|
||||
panic("Failed to get storer")
|
||||
}
|
||||
storer.Users["a"] = authboss.Attributes{"username": "a", "password": "b", "email": "a@b.c"}
|
||||
|
||||
err, ch := m.makeAndSendToken(ctx, "a")
|
||||
|
||||
_, ok = storer.Users["a"][attrRecoverToken]
|
||||
if !ok {
|
||||
t.Error("Expected recover token")
|
||||
}
|
||||
|
||||
_, ok = storer.Users["a"][attrRecoverTokenExpiry]
|
||||
if !ok {
|
||||
t.Error("Expected recover token expiry")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Error("Unexpected error:", err)
|
||||
}
|
||||
if ch == nil {
|
||||
t.Error("Unexpected nil channel")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_sendRecoverEmail_InvalidTemplates(t *testing.T) {
|
||||
m, logger := testValidRecoverModule()
|
||||
|
||||
failTpl, err := template.New("").Parse("{{.Fail}}")
|
||||
if err != nil {
|
||||
panic("Failed to build tpl")
|
||||
}
|
||||
|
||||
// broken html template
|
||||
originalHtmlEmail := m.emailTemplates[tplInitHTMLEmail]
|
||||
m.emailTemplates[tplInitHTMLEmail] = failTpl
|
||||
|
||||
<-m.sendRecoverEmail("a@b.c", []byte("abc123"))
|
||||
|
||||
actualLog, err := ioutil.ReadAll(logger)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !bytes.Contains(actualLog, []byte("recover [failed to build html email]:")) {
|
||||
t.Error("Expected log message starting with:", "recover [failed to build html email]:")
|
||||
}
|
||||
|
||||
// broken plain text template
|
||||
m.emailTemplates[tplInitHTMLEmail] = originalHtmlEmail
|
||||
m.emailTemplates[tplInitTextEmail] = failTpl
|
||||
|
||||
<-m.sendRecoverEmail("a@b.c", []byte("abc123"))
|
||||
|
||||
actualLog, err = ioutil.ReadAll(logger)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !bytes.Contains(actualLog, []byte("recover [failed to build plaintext email]:")) {
|
||||
t.Error("Expected log message starting with:", "recover [failed to build plaintext email]:")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_sendRecoverEmail_FailToSend(t *testing.T) {
|
||||
m, logger := testValidRecoverModule()
|
||||
|
||||
mailer := mocks.NewMockMailer()
|
||||
mailer.SendErr = "explode"
|
||||
authboss.Cfg.Mailer = mailer
|
||||
<-m.sendRecoverEmail("a@b.c", []byte("abc123"))
|
||||
|
||||
actualLog, err := ioutil.ReadAll(logger)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !bytes.Contains(actualLog, []byte("recover [failed to send email]:")) {
|
||||
t.Error("Expecte log message starting with:", "recover [failed to send email]:")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_sendRecoverEmail(t *testing.T) {
|
||||
m, _ := testValidRecoverModule()
|
||||
|
||||
<-m.sendRecoverEmail("a@b.c", []byte("abc123"))
|
||||
|
||||
mailer, ok := authboss.Cfg.Mailer.(*mocks.MockMailer)
|
||||
if !ok {
|
||||
panic("Failed to assert mock mailer")
|
||||
}
|
||||
|
||||
sent := mailer.Last
|
||||
if len(sent.To) != 1 {
|
||||
t.Error("Expected one to email")
|
||||
}
|
||||
if sent.To[0] != "a@b.c" {
|
||||
t.Error("Unexpected to email:", sent.To[0])
|
||||
}
|
||||
|
||||
if sent.From != authboss.Cfg.EmailFrom {
|
||||
t.Error("Unexpected from email:", sent.From)
|
||||
}
|
||||
|
||||
if sent.Subject != "Password Reset" {
|
||||
t.Error("Unexpected subject:", sent.Subject)
|
||||
}
|
||||
|
||||
data := struct {
|
||||
Link string
|
||||
}{
|
||||
fmt.Sprintf("%s/recover/complete?token=%s",
|
||||
authboss.Cfg.HostName,
|
||||
base64.URLEncoding.EncodeToString([]byte("abc123")),
|
||||
),
|
||||
}
|
||||
html, err := m.emailTemplates.ExecuteTemplate(tplInitHTMLEmail, data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
test, err := m.emailTemplates.ExecuteTemplate(tplInitTextEmail, data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(html.Bytes(), []byte(sent.HTMLBody)) {
|
||||
t.Error("Unexpected html body:", sent.HTMLBody)
|
||||
}
|
||||
if !bytes.Equal(test.Bytes(), []byte(sent.TextBody)) {
|
||||
t.Error("Unexpected text body:", sent.TextBody)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_recoverHandlerFunc_OtherMethods(t *testing.T) {
|
||||
m, _ := testValidRecoverModule()
|
||||
|
||||
for i, method := range []string{"HEAD", "PUT", "DELETE", "TRACE", "CONNECT"} {
|
||||
w, r, _ := testHttpRequest(method, "/recover", nil)
|
||||
|
||||
m.recoverHandlerFunc(nil, w, r)
|
||||
|
||||
if http.StatusMethodNotAllowed != w.Code {
|
||||
t.Errorf("%d> Expected status code %d, got %d", i, http.StatusMethodNotAllowed, w.Code)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +1,17 @@
|
||||
package recover
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gopkg.in/authboss.v0"
|
||||
"gopkg.in/authboss.v0/internal/views"
|
||||
"gopkg.in/authboss.v0/internal/render"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -20,26 +24,26 @@ const (
|
||||
tplInitHTMLEmail = "recover-html.email"
|
||||
tplInitTextEmail = "recover-text.email"
|
||||
|
||||
attrUsername = "username"
|
||||
attrRecoverToken = "recover_token"
|
||||
attrRecoverTokenExpiry = "recover_token_expiry"
|
||||
attrEmail = "email"
|
||||
attrPassword = "password"
|
||||
|
||||
errFormat = "recover [%s]: %s\n"
|
||||
storeUsername = "username"
|
||||
storeRecoverToken = "recover_token"
|
||||
storeRecoverTokenExpiry = "recover_token_expiry"
|
||||
storeEmail = "email"
|
||||
storePassword = "password"
|
||||
)
|
||||
|
||||
var errRecoveryTokenExpired = errors.New("recovery token expired")
|
||||
|
||||
func init() {
|
||||
m := &RecoverModule{}
|
||||
m := &Recover{}
|
||||
authboss.RegisterModule("recover", m)
|
||||
}
|
||||
|
||||
type RecoverModule struct {
|
||||
templates views.Templates
|
||||
emailTemplates views.Templates
|
||||
type Recover struct {
|
||||
templates render.Templates
|
||||
emailTemplates render.Templates
|
||||
}
|
||||
|
||||
func (m *RecoverModule) Initialize() (err error) {
|
||||
func (r *Recover) Initialize() (err error) {
|
||||
if authboss.Cfg.Storer == nil {
|
||||
return errors.New("recover: Need a RecoverStorer.")
|
||||
}
|
||||
@ -48,49 +52,217 @@ func (m *RecoverModule) Initialize() (err error) {
|
||||
return errors.New("recover: RecoverStorer required for recover functionality.")
|
||||
}
|
||||
|
||||
if authboss.Cfg.Layout == nil {
|
||||
return errors.New("recover: Layout required for Recover functionallity.")
|
||||
}
|
||||
if m.templates, err = views.Get(authboss.Cfg.Layout, authboss.Cfg.ViewsPath, tplRecover, tplRecoverComplete); err != nil {
|
||||
r.templates, err = render.LoadTemplates(authboss.Cfg.Layout, authboss.Cfg.ViewsPath, tplRecover, tplRecoverComplete)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if authboss.Cfg.LayoutEmail == nil {
|
||||
return errors.New("recover: LayoutEmail required for Recover functionallity.")
|
||||
}
|
||||
if m.emailTemplates, err = views.Get(authboss.Cfg.LayoutEmail, authboss.Cfg.ViewsPath, tplInitHTMLEmail, tplInitTextEmail); err != nil {
|
||||
r.emailTemplates, err = render.LoadTemplates(authboss.Cfg.LayoutEmail, authboss.Cfg.ViewsPath, tplInitHTMLEmail, tplInitTextEmail)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *RecoverModule) Routes() authboss.RouteTable {
|
||||
func (r *Recover) Routes() authboss.RouteTable {
|
||||
return authboss.RouteTable{
|
||||
"recover": m.recoverHandlerFunc,
|
||||
"recover/complete": m.recoverCompleteHandlerFunc,
|
||||
"recover": r.startHandlerFunc,
|
||||
"recover/complete": r.completeHandlerFunc,
|
||||
}
|
||||
}
|
||||
func (m *RecoverModule) Storage() authboss.StorageOptions {
|
||||
func (r *Recover) Storage() authboss.StorageOptions {
|
||||
return authboss.StorageOptions{
|
||||
attrUsername: authboss.String,
|
||||
attrRecoverToken: authboss.String,
|
||||
attrEmail: authboss.String,
|
||||
attrRecoverTokenExpiry: authboss.String,
|
||||
attrPassword: authboss.String,
|
||||
storeUsername: authboss.String,
|
||||
storeRecoverToken: authboss.String,
|
||||
storeEmail: authboss.String,
|
||||
storeRecoverTokenExpiry: authboss.String,
|
||||
storePassword: authboss.String,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *RecoverModule) execTpl(tpl string, w http.ResponseWriter, data interface{}) {
|
||||
buffer, err := m.templates.ExecuteTemplate(tpl, data)
|
||||
func (r *Recover) startHandlerFunc(ctx *authboss.Context, w http.ResponseWriter, req *http.Request) error {
|
||||
switch req.Method {
|
||||
case methodGET:
|
||||
data := authboss.NewHTMLData(
|
||||
"primaryID", authboss.Cfg.PrimaryID,
|
||||
"primaryIDValue", "",
|
||||
"confirmPrimaryIDValue", "",
|
||||
)
|
||||
|
||||
return r.templates.Render(ctx, w, req, tplRecover, data)
|
||||
case methodPOST:
|
||||
primaryID, _ := ctx.FirstPostFormValue(authboss.Cfg.PrimaryID)
|
||||
confirmPrimaryID, _ := ctx.FirstPostFormValue(fmt.Sprintf("confirm_%s", authboss.Cfg.PrimaryID))
|
||||
|
||||
errData := authboss.NewHTMLData(
|
||||
"primaryID", authboss.Cfg.PrimaryID,
|
||||
"primaryIDValue", primaryID,
|
||||
"confirmPrimaryIDValue", confirmPrimaryID,
|
||||
)
|
||||
|
||||
policies := authboss.FilterValidators(authboss.Cfg.Policies, authboss.Cfg.PrimaryID)
|
||||
if validationErrs := ctx.Validate(policies, authboss.Cfg.ConfirmFields...).Map(); len(validationErrs) > 0 {
|
||||
fmt.Fprintln(authboss.Cfg.LogWriter, "recover: form validation failed:", validationErrs)
|
||||
errData.MergeKV("errs", validationErrs)
|
||||
return r.templates.Render(ctx, w, req, tplRecover, errData)
|
||||
}
|
||||
|
||||
if err := ctx.LoadUser(primaryID); err == authboss.ErrUserNotFound {
|
||||
errData.MergeKV("flashError", authboss.Cfg.RecoverFailedErrorFlash)
|
||||
return r.templates.Render(ctx, w, req, tplRecover, errData)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
email, err := ctx.User.StringErr(storeEmail)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encodedToken, encodedChecksum, err := newToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.User[storeRecoverToken] = encodedChecksum
|
||||
ctx.User[storeRecoverTokenExpiry] = time.Now().Add(authboss.Cfg.RecoverTokenDuration)
|
||||
|
||||
if err := ctx.SaveUser(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go goRecoverEmail(r, email, encodedToken)
|
||||
|
||||
ctx.SessionStorer.Put(authboss.FlashSuccessKey, authboss.Cfg.RecoverInitiateSuccessFlash)
|
||||
http.Redirect(w, req, authboss.Cfg.RecoverRedirect, http.StatusFound)
|
||||
default:
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newToken() (encodedToken, encodedChecksum string, err error) {
|
||||
token := make([]byte, 32)
|
||||
if _, err = rand.Read(token); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
sum := md5.Sum(token)
|
||||
|
||||
return base64.URLEncoding.EncodeToString(token), base64.StdEncoding.EncodeToString(sum[:]), nil
|
||||
}
|
||||
|
||||
var goRecoverEmail = func(r *Recover, to, encodedToken string) {
|
||||
go r.sendRecoverEmail(to, encodedToken)
|
||||
}
|
||||
|
||||
func (r *Recover) sendRecoverEmail(to, encodedToken string) {
|
||||
url := fmt.Sprintf("%s/recover/complete?token=%s", authboss.Cfg.HostName, encodedToken)
|
||||
|
||||
email := authboss.Email{
|
||||
To: []string{to},
|
||||
From: authboss.Cfg.EmailFrom,
|
||||
Subject: authboss.Cfg.EmailSubjectPrefix + "Password Reset",
|
||||
}
|
||||
|
||||
if err := r.emailTemplates.RenderEmail(email, tplInitHTMLEmail, tplInitTextEmail, url); err != nil {
|
||||
fmt.Fprintln(authboss.Cfg.LogWriter, "recover: failed to send recover email:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Recover) completeHandlerFunc(ctx *authboss.Context, w http.ResponseWriter, req *http.Request) (err error) {
|
||||
switch req.Method {
|
||||
case methodGET:
|
||||
_, err = verifyToken(ctx)
|
||||
if err == errRecoveryTokenExpired {
|
||||
return authboss.ErrAndRedirect{err, "/recover", "", authboss.Cfg.RecoverTokenExpiredFlash}
|
||||
} else if err != nil {
|
||||
return authboss.ErrAndRedirect{err, "/", "", ""}
|
||||
}
|
||||
|
||||
token, _ := ctx.FirstFormValue("token")
|
||||
data := authboss.NewHTMLData("token", token)
|
||||
return r.templates.Render(ctx, w, req, tplRecoverComplete, data)
|
||||
case methodPOST:
|
||||
token, err := ctx.FirstFormValueErr("token")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
password, _ := ctx.FirstPostFormValue("password")
|
||||
confirmPassword, _ := ctx.FirstPostFormValue("confirmPassword")
|
||||
|
||||
policies := authboss.FilterValidators(authboss.Cfg.Policies, "password")
|
||||
if validationErrs := ctx.Validate(policies, authboss.Cfg.ConfirmFields...).Map(); len(validationErrs) > 0 {
|
||||
fmt.Fprintln(authboss.Cfg.LogWriter, "recover: form validation failed:", validationErrs)
|
||||
data := authboss.NewHTMLData(
|
||||
"token", token,
|
||||
"password", password,
|
||||
"confirmPassword", confirmPassword,
|
||||
"errs", validationErrs,
|
||||
)
|
||||
return r.templates.Render(ctx, w, req, tplRecoverComplete, data)
|
||||
}
|
||||
|
||||
if ctx.User, err = verifyToken(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encryptedPassword, err := bcrypt.GenerateFromPassword([]byte(password), authboss.Cfg.BCryptCost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.User[storePassword] = string(encryptedPassword)
|
||||
ctx.User[storeRecoverToken] = ""
|
||||
var nullTime time.Time
|
||||
ctx.User[storeRecoverTokenExpiry] = nullTime
|
||||
|
||||
primaryID, err := ctx.User.StringErr(authboss.Cfg.PrimaryID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ctx.SaveUser(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.SessionStorer.Put(authboss.SessionKey, primaryID)
|
||||
http.Redirect(w, req, authboss.Cfg.AuthLoginSuccessRoute, http.StatusFound)
|
||||
default:
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// verifyToken expects a base64.URLEncoded token.
|
||||
func verifyToken(ctx *authboss.Context) (attrs authboss.Attributes, err error) {
|
||||
token, err := ctx.FirstFormValueErr("token")
|
||||
if err != nil {
|
||||
fmt.Fprintf(authboss.Cfg.LogWriter, errFormat, "unable to execute template", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(w, buffer); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
decoded, err := base64.URLEncoding.DecodeString(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sum := md5.Sum(decoded)
|
||||
storer := authboss.Cfg.Storer.(authboss.RecoverStorer)
|
||||
|
||||
userInter, err := storer.RecoverUser(base64.StdEncoding.EncodeToString(sum[:]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
attrs = authboss.Unbind(userInter)
|
||||
|
||||
expiry, ok := attrs.DateTime(storeRecoverTokenExpiry)
|
||||
if !ok || time.Now().After(expiry) {
|
||||
return nil, errRecoveryTokenExpired
|
||||
}
|
||||
|
||||
return attrs, nil
|
||||
}
|
||||
|
@ -1,239 +0,0 @@
|
||||
package recover
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"html/template"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gopkg.in/authboss.v0"
|
||||
"gopkg.in/authboss.v0/internal/mocks"
|
||||
"gopkg.in/authboss.v0/internal/views"
|
||||
)
|
||||
|
||||
func Test_Initialize(t *testing.T) {
|
||||
authboss.NewConfig()
|
||||
m := &RecoverModule{}
|
||||
authboss.Cfg.Storer = nil
|
||||
|
||||
if err := m.Initialize(); err == nil {
|
||||
t.Error("Expected error")
|
||||
} else if err.Error() != "recover: Need a RecoverStorer." {
|
||||
t.Error("Got error but wrong reason:", err)
|
||||
}
|
||||
authboss.Cfg.Storer = mocks.MockFailStorer{}
|
||||
|
||||
if err := m.Initialize(); err == nil {
|
||||
t.Error("Expected error")
|
||||
} else if err.Error() != "recover: RecoverStorer required for recover functionality." {
|
||||
t.Error("Got error but wrong reason:", err)
|
||||
}
|
||||
authboss.Cfg.Storer = mocks.NewMockStorer()
|
||||
authboss.Cfg.Layout = nil
|
||||
|
||||
if err := m.Initialize(); err == nil {
|
||||
t.Error("Expected error")
|
||||
} else if err.Error() != "recover: Layout required for Recover functionallity." {
|
||||
t.Error("Got error but wrong reason:", err)
|
||||
}
|
||||
|
||||
var err error
|
||||
authboss.Cfg.Layout, err = template.New("").Parse(`{{template "authboss" .}}`)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error:", err)
|
||||
}
|
||||
authboss.Cfg.LayoutEmail = nil
|
||||
|
||||
if err := m.Initialize(); err == nil {
|
||||
t.Error("Expected error:", err)
|
||||
} else if err.Error() != "recover: LayoutEmail required for Recover functionallity." {
|
||||
t.Error("Got error but wrong reason:", err)
|
||||
}
|
||||
authboss.Cfg.LayoutEmail, err = template.New("").Parse(`{{template "authboss" .}}`)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error:", err)
|
||||
}
|
||||
|
||||
if err := m.Initialize(); err != nil {
|
||||
t.Error("Unexpected error:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func testValidTestConfig() {
|
||||
cfg := authboss.Config{
|
||||
Storer: mocks.NewMockStorer(),
|
||||
RecoverRedirect: "/login",
|
||||
RecoverInitiateSuccessFlash: "sf",
|
||||
RecoverTokenExpiredFlash: "exf",
|
||||
RecoverFailedErrorFlash: "errf",
|
||||
|
||||
Policies: []authboss.Validator{
|
||||
authboss.Rules{
|
||||
FieldName: "username",
|
||||
Required: true,
|
||||
},
|
||||
authboss.Rules{
|
||||
FieldName: "password",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
|
||||
ConfirmFields: []string{"username", "confirmUsername", "password", "confirmPassword"},
|
||||
LogWriter: &bytes.Buffer{},
|
||||
Mailer: &mocks.MockMailer{},
|
||||
EmailFrom: "auth@boss.com",
|
||||
HostName: "localhost",
|
||||
RecoverTokenDuration: time.Duration(24) * time.Hour,
|
||||
BCryptCost: 4,
|
||||
AuthLoginSuccessRoute: "/login",
|
||||
XSRFName: "rofl",
|
||||
XSRFMaker: func(_ http.ResponseWriter, _ *http.Request) string {
|
||||
return "lawl"
|
||||
},
|
||||
}
|
||||
|
||||
var err error
|
||||
if cfg.Layout, err = views.AssetToTemplate("layout.tpl"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if cfg.LayoutEmail, err = views.AssetToTemplate("layoutEmail.tpl"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
authboss.Cfg = &cfg
|
||||
}
|
||||
|
||||
func testValidRecoverModule() (*RecoverModule, *bytes.Buffer) {
|
||||
testValidTestConfig()
|
||||
|
||||
m := &RecoverModule{}
|
||||
if err := m.Initialize(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return m, authboss.Cfg.LogWriter.(*bytes.Buffer)
|
||||
}
|
||||
|
||||
func Test_Routes(t *testing.T) {
|
||||
testValidTestConfig()
|
||||
|
||||
m, _ := testValidRecoverModule()
|
||||
|
||||
routes := m.Routes()
|
||||
|
||||
if _, ok := routes["recover"]; !ok {
|
||||
t.Error("Expected route: recover")
|
||||
}
|
||||
|
||||
if _, ok := routes["recover/complete"]; !ok {
|
||||
t.Error("Expected route: recover/complete")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Storage(t *testing.T) {
|
||||
testValidTestConfig()
|
||||
|
||||
m, _ := testValidRecoverModule()
|
||||
|
||||
tests := []struct {
|
||||
Attr string
|
||||
Kind authboss.DataType
|
||||
}{
|
||||
{attrUsername, authboss.String},
|
||||
{attrRecoverToken, authboss.String},
|
||||
{attrEmail, authboss.String},
|
||||
{attrRecoverTokenExpiry, authboss.String},
|
||||
{attrPassword, authboss.String},
|
||||
}
|
||||
|
||||
options := m.Storage()
|
||||
|
||||
for i, test := range tests {
|
||||
if kind, ok := options[test.Attr]; !ok {
|
||||
t.Errorf("%s> Expected attr: %s", i, test.Attr)
|
||||
} else if kind != test.Kind {
|
||||
t.Errorf("%s> Expected DataType: %s", i, test.Kind)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testHttpRequest(method, url string, data url.Values) (*httptest.ResponseRecorder, *http.Request, *authboss.Context) {
|
||||
var body io.Reader
|
||||
if method != "GET" {
|
||||
body = strings.NewReader(data.Encode())
|
||||
}
|
||||
|
||||
r, err := http.NewRequest(method, url, body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
ctx, err := authboss.ContextFromRequest(r)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sessionStorer := mocks.NewMockClientStorer()
|
||||
ctx.SessionStorer = sessionStorer
|
||||
|
||||
return w, r, ctx
|
||||
}
|
||||
|
||||
func Test_execTpl_TemplateExectionFail(t *testing.T) {
|
||||
testValidTestConfig()
|
||||
|
||||
m, logger := testValidRecoverModule()
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
failTpl, err := template.New("").Parse("{{.Fail}}")
|
||||
if err != nil {
|
||||
panic("Failed to build tpl")
|
||||
}
|
||||
m.templates["fail.tpl"] = failTpl
|
||||
|
||||
m.execTpl("fail.tpl", w, pageRecover{})
|
||||
|
||||
if w.Code != http.StatusInternalServerError {
|
||||
t.Error("Unexpected code:", w.Code)
|
||||
}
|
||||
|
||||
actualLog, err := ioutil.ReadAll(logger)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if !bytes.Contains(actualLog, []byte("recover [unable to execute template]:")) {
|
||||
t.Error("Expected log message starting with:", "recover [unable to execute template]:")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_execTpl(t *testing.T) {
|
||||
testValidTestConfig()
|
||||
|
||||
m, _ := testValidRecoverModule()
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
page := pageRecover{"bobby", "bob", nil, "", authboss.Cfg.RecoverFailedErrorFlash, "", ""}
|
||||
m.execTpl(tplRecover, w, page)
|
||||
|
||||
tpl := m.templates[tplRecover]
|
||||
expectedBody := &bytes.Buffer{}
|
||||
if err := tpl.Execute(expectedBody, page); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Error("Unexpected code:", w.Code)
|
||||
}
|
||||
if !bytes.Equal(expectedBody.Bytes(), w.Body.Bytes()) {
|
||||
t.Error("Unexpected body:", w.Body.String())
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user