1
0
mirror of https://github.com/volatiletech/authboss.git synced 2025-10-30 23:47:59 +02:00

First part of recover module reworking

This commit is contained in:
Kris Runzer
2015-02-08 23:08:33 -08:00
parent d86fac49ac
commit 57c9282cbd
12 changed files with 711 additions and 142 deletions

View File

@@ -8,6 +8,12 @@ import (
"time"
"golang.org/x/crypto/bcrypt"
"gopkg.in/authboss.v0/internal/views"
)
const (
layoutTpl = "layout.tpl"
layoutEmailTpl = "layoutEmail.tpl"
)
// Config holds all the configuration for both authboss and it's modules.
@@ -60,12 +66,25 @@ type Config struct {
// NewConfig creates a new config full of default values ready to override.
func NewConfig() *Config {
layout, err := views.AssetToTemplate(layoutTpl)
if err != nil {
panic(err)
}
layoutEmail, err := views.AssetToTemplate(layoutEmailTpl)
if err != nil {
panic(err)
}
return &Config{
MountPath: "/",
ViewsPath: "/",
HostName: "localhost:8080",
BCryptCost: bcrypt.DefaultCost,
Layout: layout,
LayoutEmail: layoutEmail,
AuthLogoutRoute: "/",
AuthLoginSuccessRoute: "/",

View File

@@ -9,6 +9,7 @@ import (
)
type MockUser struct {
Username string
Email string
Password string
}
@@ -42,9 +43,25 @@ func (m *MockStorer) Put(key string, attr authboss.Attributes) error {
}
func (m *MockStorer) Get(key string, attrMeta authboss.AttributeMeta) (result interface{}, err error) {
return &MockUser{
m.Users[key]["username"].(string), m.Users[key]["password"].(string),
}, nil
if _, ok := m.Users[key]; !ok {
return nil, authboss.ErrUserNotFound
}
u := &MockUser{}
if val, ok := m.Users[key]["username"]; ok {
u.Username = val.(string)
}
if val, ok := m.Users[key]["email"]; ok {
u.Email = val.(string)
}
if val, ok := m.Users[key]["password"]; ok {
u.Password = val.(string)
}
return u, nil
}
func (m *MockStorer) AddToken(key, token string) error {
@@ -105,3 +122,12 @@ func MockRequestContext(postKeyValues ...string) *authboss.Context {
return ctx
}
type MockMailer struct {
Last authboss.Email
}
func (m *MockMailer) Send(email authboss.Email) error {
m.Last = email
return nil
}

View File

@@ -61,7 +61,47 @@ func (fi bindata_file_info) Sys() interface{} {
return nil
}
var _login_tpl = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x8c\x51\x4d\x4f\xc4\x20\x10\xbd\x9b\xf8\x1f\xc8\x78\xde\x34\xde\x69\x8f\x9e\xf6\x60\x4c\xfc\x01\xb4\xcc\x16\x12\x5a\x70\x80\xd5\xfd\xf7\x4e\x2b\xa0\x8d\x31\x91\xcb\xbc\xf9\xe0\xf1\x78\x23\x4d\x5a\xdc\x70\x7f\x27\x47\xaf\x6f\x5b\xd4\xf6\x2a\x26\xa7\x62\xec\xc1\xf9\xd9\xae\xa7\x49\x91\x06\xee\x08\x3e\xd2\x3c\x0e\x67\x3f\x9f\xec\x2a\x3b\x86\x72\xa4\xda\xb8\x78\x5a\x0a\xde\x73\xbb\x86\x9c\x44\xba\x05\xec\x21\xe1\x47\x02\xb1\xaa\x85\x71\x8e\x48\x1b\x02\x11\x9c\x9a\xd0\x78\xa7\x91\x7a\x78\x6d\x65\xc2\xb7\x6c\x09\x35\x5f\xa3\x8c\xf0\x17\x67\x60\x85\xef\x9e\x95\x15\xde\xef\xfc\xc0\xfb\xdc\xca\xff\xe4\x8d\x79\x5c\x6c\x53\xbb\x3b\x00\x07\x3f\xc4\x97\x2b\x75\xee\xaa\x5c\xe6\xc1\xf3\x3e\x58\xcd\xe8\x8a\x1b\x25\xfd\x65\xa9\x41\x17\x0e\x0a\x94\x30\x84\x97\x1e\x1e\x60\x78\xc1\xd9\xc6\x84\x24\x3b\x35\xfc\xac\x3f\x79\x9a\x7d\x12\xf5\x43\x5b\xbb\xbe\xc6\xfc\xdb\xe6\x5a\xac\xab\xec\xca\x6e\x3f\x03\x00\x00\xff\xff\x05\x40\xcd\x07\xe4\x01\x00\x00")
var _layout_tpl = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xd4\x95\xc1\x72\xd3\x30\x10\x86\xef\x79\x0a\x8d\x0e\xdc\x2c\xd1\x26\xc0\x4c\xeb\xe4\x06\x2f\xc0\x13\xc8\xd6\x3a\x56\x90\x25\xa3\x5d\xb7\xc9\x78\xfa\xee\xc8\x96\x1b\x5c\xd7\x40\x87\x03\x33\xe4\x60\x67\x37\xda\xff\xdf\xfd\x34\x52\xf2\x9a\x1a\x7b\xd8\xe4\x35\x28\x7d\xd8\xb0\xf8\xc9\xad\x71\xdf\x58\x1d\xa0\xda\x73\x29\x1d\x90\x76\x4a\x14\xde\x13\x52\x50\x6d\xa9\x9d\x28\x7d\x23\x2b\xef\x28\x53\x8f\x80\xbe\x01\xb9\x13\xef\xc5\x56\x96\x88\x2f\xd2\x22\x26\x38\x0b\x60\xf7\x1c\xe9\x62\x01\x6b\x00\xe2\x4c\xae\xd9\x34\xea\x3c\x28\xbf\xb2\xb9\x26\xe4\x56\x6c\xc5\xcd\xe8\x71\xcd\x89\xc6\xb8\x3f\x98\x60\x19\x4c\x4b\x0c\x43\xb9\xe7\x35\x51\x8b\x77\x52\xaa\x93\x3a\x8b\xa3\xf7\x47\x0b\xaa\x35\x38\xfa\x0c\x39\x69\x4d\x81\xf2\xf4\xbd\x83\x70\x91\xb7\xe2\x26\x8e\x94\x82\xd1\xe7\x84\xfc\x90\xcb\xa4\xb7\x22\xfe\xd6\x11\x6e\xe5\x69\x39\x41\x54\x66\x74\x69\x61\xcf\x09\xce\x24\x4f\xea\x41\x25\xe5\xb9\x61\x2e\xd3\x0e\xe5\x85\xd7\x97\xf8\xd2\xe6\x81\x95\x56\x21\xee\x79\x19\x99\x2b\xe3\x20\x64\x95\xed\x8c\xe6\xa9\xbb\xbe\x37\x15\x13\x5f\xe2\x92\xfa\x6b\x57\x96\x80\xf8\xf4\x94\xda\x9e\x95\x06\xff\x38\x2d\x5f\xfe\x52\x7a\x9b\x9d\x31\xf3\x55\x85\x40\xd9\x96\x0d\x71\xa3\xb3\x8f\xb3\xe5\xcb\x12\x65\x21\x10\x1b\x9f\x19\x26\xcb\x29\xd2\x06\x1b\x83\xa8\x0a\x0b\x9c\x8d\xdb\xb4\xe7\x8d\x0a\x47\xe3\x32\xf2\xed\x1d\xfb\xf4\xa1\x3d\xdf\x2f\x94\x47\xf5\xa2\x23\xf2\x6e\xa2\x93\x02\x7e\xed\xd0\x7a\x8c\x72\x5a\x91\x7a\x36\x98\x7a\x88\xdc\xb0\x55\xee\xf0\x8e\x4c\x03\x78\x1f\x21\x0e\x51\x2e\x93\xc0\x6b\x9b\xbe\x6f\x83\x71\xb4\x4a\xeb\xda\x8a\x8c\x93\xce\x50\xfd\x0c\x67\x5f\xfb\x1e\x9c\x9e\x0a\x67\xfc\x3f\x87\xe0\xc3\xbf\xa4\xaf\x95\x3b\x42\xf8\x1f\xe1\xcf\x51\xfd\x35\xfa\x25\xe3\xb5\xa9\x6f\x17\x53\x2f\xe9\x47\xda\x13\xfd\xdd\x33\xfd\xdd\x6f\xe8\xc7\x19\xc1\xb2\xf1\x99\x69\xa8\x54\x67\x69\x0d\xe9\xb2\x22\x1b\x0e\xb5\x71\xc7\xe1\xa4\xbf\x18\xf2\xd7\x15\xc3\xf9\x5f\x91\x4e\x0c\x08\x9a\xd6\x2a\x02\xc6\x55\x47\x75\xe1\x87\xbb\x51\x2c\x70\xae\x20\x7d\x13\xe5\xeb\x6b\xba\x81\x64\xfa\xe7\xd8\x6c\x7e\x04\x00\x00\xff\xff\x06\xe2\x8e\xc9\x43\x06\x00\x00")
func layout_tpl_bytes() ([]byte, error) {
return bindata_read(
_layout_tpl,
"layout.tpl",
)
}
func layout_tpl() (*asset, error) {
bytes, err := layout_tpl_bytes()
if err != nil {
return nil, err
}
info := bindata_file_info{name: "layout.tpl", size: 1603, mode: os.FileMode(438), modTime: time.Unix(1423332529, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _layoutemail_tpl = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xb2\x49\xcd\xb5\xab\xae\x2e\x49\xcd\x2d\xc8\x49\x2c\x49\x55\x50\x4a\x2c\x2d\xc9\x48\xca\x2f\x2e\x56\x52\xd0\xab\xad\xb5\xd1\x07\xca\x02\x02\x00\x00\xff\xff\x3a\xdb\x96\xd1\x22\x00\x00\x00")
func layoutemail_tpl_bytes() ([]byte, error) {
return bindata_read(
_layoutemail_tpl,
"layoutEmail.tpl",
)
}
func layoutemail_tpl() (*asset, error) {
bytes, err := layoutemail_tpl_bytes()
if err != nil {
return nil, err
}
info := bindata_file_info{name: "layoutEmail.tpl", size: 34, mode: os.FileMode(438), modTime: time.Unix(1423334931, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _login_tpl = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xcc\x94\x4b\x8e\xa3\x30\x10\x86\xf7\x23\xcd\x1d\x2c\xef\x09\x17\x00\xa4\x59\xcc\x6e\x46\x13\x4d\xba\x0f\x60\x4c\x11\xac\xd8\x2e\xcb\xd8\x79\x08\x71\xf7\x36\xcf\x06\xf2\xe8\x6d\x47\xb2\x54\xaa\xd4\xff\xa7\xfe\x2f\x86\xa4\x44\xab\x08\xe3\x4e\xa0\x4e\x69\x2c\xf1\x28\x34\x25\x0a\x5c\x85\x45\x4a\xf7\xff\x0e\x6f\x34\xfb\xf9\x83\x84\x4f\x52\x88\x33\xe1\x92\xd5\x75\x4a\x3b\x51\x74\xb4\xe8\x4d\xd3\x88\x92\xec\x7e\x5b\x8b\xb6\x6d\x49\xc5\xea\x08\xba\xba\x69\x40\x17\x6d\x3b\x69\xb7\x7a\xa1\x8d\x77\x83\xc1\x72\xa4\x1f\xab\x0d\xd3\x0f\xe6\x22\x56\x14\xa8\x69\x96\x88\x79\x09\x46\x4a\x16\xf9\x1a\x6c\xe8\xc6\x22\x9c\x4e\xba\xb5\xeb\x2d\x88\xbb\x19\x48\xa9\x83\xab\xa3\xab\x0c\x1c\xb5\xb3\x28\x29\xd1\x4c\x85\x81\xce\xac\xab\x28\x31\x92\x71\xa8\x50\x16\x60\x53\xfa\x3e\xb7\xcf\x4c\xfa\x30\xd7\x34\xbb\xa9\xb7\x09\x19\x87\x94\x13\xb0\x65\xfd\x3d\xe1\x49\xe4\xa7\xaf\xe1\x8d\xf4\x4c\x10\x5e\xd0\x16\x2f\x09\x7e\x0e\xad\x08\xee\xa7\xf6\x13\x56\x77\xcb\x57\x20\x4d\x94\x0f\xfb\x05\xda\x23\xa3\xd5\x96\x4b\xfd\x40\xf2\x50\xe1\xe5\x3f\x28\x50\x39\x84\xe1\x7b\xf2\xbc\x02\x7e\xca\xf1\xba\xda\x42\xb2\x1c\xe4\xab\x5b\x33\xab\xc6\x88\x56\xcd\xf7\xc0\x59\x0f\x34\x23\xd3\x6f\x92\xbf\xb0\x8c\xb7\x74\x5e\x2f\xdb\xff\xbf\xe3\x17\xb9\x77\x0e\xe7\xdc\xb9\xd3\x24\x9c\xc8\x58\xa1\x98\xbd\xf5\xf5\x80\x61\xdc\xa6\xf6\xb9\x12\x8e\x66\x7f\xba\x27\x35\x89\x07\xf5\x03\x0a\x1c\xcf\x0b\x08\x6c\xeb\x2f\x85\x3e\x3d\x35\x27\x95\x85\x32\xbc\x0d\xec\xe0\x42\xb3\xd1\x8e\xfc\xe2\x1c\xbd\x76\x49\xcc\xb6\x51\x92\xb8\xbb\x0d\xd9\x47\x00\x00\x00\xff\xff\x94\xfa\xa3\xd7\x4f\x04\x00\x00")
func login_tpl_bytes() ([]byte, error) {
return bindata_read(
@@ -76,12 +116,12 @@ func login_tpl() (*asset, error) {
return nil, err
}
info := bindata_file_info{name: "login.tpl", size: 484, mode: os.FileMode(438), modTime: time.Unix(1421625609, 0)}
info := bindata_file_info{name: "login.tpl", size: 1103, mode: os.FileMode(438), modTime: time.Unix(1423332525, 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\x01\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00")
var _recover_complete_tpl = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xd4\x51\x41\xcf\x9b\x30\x0c\xbd\xf7\x57\x58\x51\xaf\x94\xfb\x04\x5c\xa6\x1d\xa7\x55\x6a\xff\x40\x20\xa6\x44\x0d\x49\x66\x42\xb7\x2a\xca\x7f\x5f\x20\xd0\x75\xa8\x95\x26\x4d\x3b\x7c\x48\x11\xb1\x63\x3f\xfb\xbd\x57\xb4\x86\x7a\xe0\x8d\x93\x46\x97\x2c\x27\x6c\xcc\x0d\x29\x6f\x4c\x6f\x15\x3a\x64\xd0\xa3\xeb\x8c\x28\xd9\xf1\xdb\xe9\xcc\xaa\x1d\xc4\xaf\x90\xda\x8e\x0e\xdc\xdd\x62\xc9\x3a\x29\x04\x6a\x06\x9a\xf7\x31\x72\xe6\x3a\x05\x37\xae\xc6\x18\x79\x7f\x38\x4f\x89\x10\x18\xe4\xa9\xd7\xfb\xbd\xe5\xc3\xf0\xc3\x90\xf8\x42\x34\xc0\xa7\x12\x0e\xf1\xf2\x95\xdb\xc3\x9a\x0f\x21\x4d\x11\xf2\x06\x8d\x8a\xc9\x92\x4d\x4b\x66\x17\x32\xa3\xf5\x5e\xb6\xf0\x07\x44\x08\xd0\xf1\x21\x43\x22\x43\xde\xa3\x8e\xfd\xcb\x9e\x5b\x94\x79\xed\x04\xf3\x54\x31\x57\x0d\x96\xeb\x17\x65\x19\x17\xc2\x68\x56\x15\xf2\xb1\x09\x87\x96\x67\xca\x34\xd7\x98\xcd\x65\x3c\x53\xeb\x06\x2d\xe9\xf3\xbc\x7b\x63\xb4\x23\xa3\xd8\x22\x9a\xc3\x9f\x6e\x95\x6c\xe5\xc2\xc0\x2a\xde\x60\x67\x94\x40\x8a\x7a\x3f\xd2\x84\xdf\x47\x49\x28\x56\x09\xe7\x11\x79\xe4\xf5\x3b\xf4\x9e\xb8\xbe\x20\xec\xa3\x0a\x93\xa4\x1b\x81\xde\x73\xed\x50\xd9\xac\x4e\x74\xbc\xb7\x24\xb5\x9b\x41\x42\xd8\x12\x5b\xa4\xdd\x3d\x4d\x5f\x0d\x8d\xe4\x5a\x49\xfd\xf1\x8d\xaf\x9b\xe7\xbf\xb0\xf7\x05\xe0\x47\x77\x79\x43\x69\x63\xf6\xe7\xf4\x0a\xff\x66\xfa\x4b\xd9\xfe\x93\xf7\xf3\xb5\x1e\x9d\x33\x0f\xbc\xda\x69\x88\x27\x8b\x48\x3d\xa7\xfb\x7c\x4f\xf0\x8b\x1e\xc3\x58\xf7\xd2\xb1\xea\x34\xff\x8b\x3c\xb5\x57\xbb\x22\x9f\xd4\xab\x7e\x05\x00\x00\xff\xff\x36\xd8\xe2\xc3\x8b\x04\x00\x00")
func recover_complete_tpl_bytes() ([]byte, error) {
return bindata_read(
@@ -96,7 +136,7 @@ func recover_complete_tpl() (*asset, error) {
return nil, err
}
info := bindata_file_info{name: "recover-complete.tpl", size: 0, mode: os.FileMode(438), modTime: time.Unix(1421625609, 0)}
info := bindata_file_info{name: "recover-complete.tpl", size: 1163, mode: os.FileMode(438), modTime: time.Unix(1423332541, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -116,7 +156,7 @@ 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(1422337208, 0)}
info := bindata_file_info{name: "recover-html.email", size: 26, mode: os.FileMode(438), modTime: time.Unix(1422773459, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -136,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(1421625609, 0)}
info := bindata_file_info{name: "recover-text.email", size: 9, mode: os.FileMode(438), modTime: time.Unix(1422773459, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _recover_tpl = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xca\xcf\x4d\xaf\x52\xc8\x54\x48\xcc\x55\x28\x4a\x4d\xce\x2f\x4b\x2d\xaa\x02\x04\x00\x00\xff\xff\x36\xaf\xf6\xd6\x12\x00\x00\x00")
var _recover_tpl = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xd4\x94\xc1\x6e\xa3\x30\x10\x86\xef\x79\x8a\x91\x95\x2b\xe1\xbe\x02\x2e\xd1\x1e\x57\xbb\xda\x36\x0f\x30\x80\x09\x56\x8c\x6d\x0d\x26\x6a\x64\xf9\xdd\x6b\x20\xa1\x89\x45\xa4\xf6\xd0\x43\x91\x90\x8c\x19\x7f\xe3\xff\xff\x2d\x67\x8d\xa6\x0e\xb0\xb2\x42\xab\x9c\xa5\xc4\x2b\x7d\xe6\xc4\xa0\xe3\xb6\xd5\x75\xce\xfe\xfd\x7d\x79\x65\xc5\x06\xc2\xe3\xdc\x76\xe8\x39\x29\xec\xf8\x6f\xa2\x1e\x7e\xe5\xb0\x0b\x83\x3f\x68\x76\xb7\x79\xef\xa7\xca\xac\x16\x67\xa8\x24\xf6\x7d\xce\x46\x7e\x72\x24\x3d\x18\xe7\x44\x03\x0f\x08\xef\xa1\xc5\x3e\xe1\x44\x9a\x9c\xe3\xaa\xf6\xfe\xda\x2b\xa6\x08\x65\x06\x3b\x63\xee\x2a\xa6\xaa\xde\xa0\x5a\x29\x4b\xb0\xae\xb5\x62\x45\x26\x96\x9d\x20\x34\x98\x8c\xfd\xc3\x6c\x2a\xc2\x3b\x2e\x8d\x68\x13\xe1\x61\xef\x95\x56\x96\xb4\x64\x60\x2f\x86\xe7\xcc\xf2\x37\xcb\x60\x54\x90\xb3\x9b\x16\x06\x46\x62\xc5\x5b\x2d\x6b\x4e\x39\x3b\x2c\xd3\x67\x94\x43\xa8\x73\x6e\x77\x58\x1c\x62\x90\xde\x69\x4c\x83\xc8\x8f\x4f\xe7\x08\xd5\x91\xc3\x36\x58\x32\xfa\x1b\xb9\xf5\x5c\x78\xcb\xa5\x49\x4a\xa9\xab\x13\x2b\x9c\x33\x24\x94\x9d\x20\xde\xc7\x2a\xaf\x3e\x6f\xee\xba\xdf\xd2\x0d\x4a\x1b\x41\xdd\xe1\x49\xc8\xd1\xef\x4f\x64\xbd\x02\xfc\xe9\x91\x47\x92\xa2\xe4\xf7\xf3\x5f\x58\x3b\x01\xfb\xd8\xbe\x2f\x1d\x84\x55\x2b\xbf\xe9\x3c\x8c\xc3\xf9\xbb\x1c\xac\xd5\x0b\xb4\xb4\x0a\xc2\x9b\x04\x5c\x87\x74\x99\xc6\x73\x8f\xab\x51\xfd\x50\x76\xc2\xb2\xe2\xff\x7c\x8b\x64\xe9\xbc\x7e\x26\x66\x18\x73\xa4\x50\xa7\xa7\x10\x68\x89\x37\xe1\x46\x92\xfa\x28\x42\xa6\x7b\x54\x15\x97\x59\x8a\xc5\x26\x4b\xc7\x90\x8a\xf7\x00\x00\x00\xff\xff\xc2\xb1\x79\x5b\xba\x04\x00\x00")
func recover_tpl_bytes() ([]byte, error) {
return bindata_read(
@@ -156,7 +196,7 @@ func recover_tpl() (*asset, error) {
return nil, err
}
info := bindata_file_info{name: "recover.tpl", size: 18, mode: os.FileMode(438), modTime: time.Unix(1421625609, 0)}
info := bindata_file_info{name: "recover.tpl", size: 1210, mode: os.FileMode(438), modTime: time.Unix(1423332554, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -202,6 +242,8 @@ func AssetNames() []string {
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){
"layout.tpl": layout_tpl,
"layoutEmail.tpl": layoutemail_tpl,
"login.tpl": login_tpl,
"recover-complete.tpl": recover_complete_tpl,
"recover-html.email": recover_html_email,
@@ -249,6 +291,10 @@ type _bintree_t struct {
Children map[string]*_bintree_t
}
var _bintree = &_bintree_t{nil, map[string]*_bintree_t{
"layout.tpl": &_bintree_t{layout_tpl, map[string]*_bintree_t{
}},
"layoutEmail.tpl": &_bintree_t{layoutemail_tpl, map[string]*_bintree_t{
}},
"login.tpl": &_bintree_t{login_tpl, map[string]*_bintree_t{
}},
"recover-complete.tpl": &_bintree_t{recover_complete_tpl, map[string]*_bintree_t{

View File

@@ -0,0 +1,44 @@
<html>
<head>
<link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet" />
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js" type="text/javascript"></script>
</head>
<body>
<div class="container-fluid">
{{if .FlashSuccess}}
<div class="row">
<div class="col-xs-offset-3 col-md-6">
<div class="alert alert-success alert-dismissable" style="margin-top: 75px;">
<button type="button" class="close" data-dismiss="alert"><span>&times;</span></button>
{{print .FlashSuccess}}
</div>
</div>
</div>
{{end}}
{{if .FlashError}}
<div class="row">
<div class="col-xs-offset-3 col-md-6">
<div class="alert alert-danger alert-dismissable" style="margin-top: 75px;">
<button type="button" class="close" data-dismiss="alert"><span>&times;</span></button>
{{print .FlashError}}
</div>
</div>
</div>
{{end}}
<div class="row" style="margin-top: 25px;">
<div class="col-md-offset-4 col-md-4">
<div class="panel panel-default">
<div class="panel-heading"></div>
<div class="panel-body">
{{template "authboss" .}}
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1 @@
<em>{{template "authboss" .}}</em>

View File

@@ -1,16 +1,26 @@
<html>
<body>
<div class="login-card">
<h1>Log-in</h1><br>
<form>
<input type="text" name="username" placeholder="Username" required="true">
<input type="password" name="password" placeholder="Password" required="true">
<input type="submit" name="login" class="login login-submit" value="Login">
</form>
<div class="login-help">
<a href="#">Register</a><a href="#">Forgot Password</a>
<form action="/login" method="POST">
<div class="form-group{{if .Error}} has-error{{end}}">
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-user"></i></span>
<input type="text" class="form-control" name="username" placeholder="Username" value="{{.Username}}">
</div>
</div>
</div>
</body>
</html>
<div class="form-group{{if .Error}} has-error{{end}}">
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-lock"></i></span>
<input type="password" class="form-control" name="password" placeholder="Password">
</div>
<span class="help-block">{{.Error}}</span>
</div>
{{if .ShowRemember}}
<div class="checkbox">
<label>
<input type="checkbox" name="rm" value="true"> Remember Me
</label>
</div>
{{end}}
<button class="btn btn-primary btn-block" type="submit">Login</button>
{{if .ShowRecover}}
<a class="btn btn-link btn-block" type="submit" href="/recover">Recover Account</a>
{{end}}
</form>

View File

@@ -0,0 +1,25 @@
<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>
</form>

View File

@@ -1 +1,26 @@
omgz i am recoverz
<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>
</form>

View File

@@ -6,9 +6,9 @@ package views
//go:generate go-bindata -pkg=views -prefix=templates templates
import (
"bytes"
"errors"
"html/template"
"io"
"io/ioutil"
"os"
"path/filepath"
@@ -24,13 +24,16 @@ type Templates map[string]*template.Template
// ExecuteTemplate is a convenience wrapper for executing a template from the layout. Returns
// ErrTemplateNotFound when the template is missing, othwerise error.
func (t Templates) ExecuteTemplate(w io.Writer, name string, data interface{}) error {
func (t Templates) ExecuteTemplate(name string, data interface{}) (buffer *bytes.Buffer, err error) {
tpl, ok := t[name]
if !ok {
return ErrTemplateNotFound
return nil, ErrTemplateNotFound
}
return tpl.ExecuteTemplate(w, tpl.Name(), data)
buffer = &bytes.Buffer{}
err = tpl.ExecuteTemplate(buffer, tpl.Name(), data)
return buffer, err
}
// Get parses all speicified files located in path. Each template is wrapped
@@ -65,3 +68,13 @@ func Get(layout *template.Template, path string, files ...string) (Templates, er
return m, nil
}
// Asset parses a specified file from the internal bindata.
func AssetToTemplate(file string) (*template.Template, error) {
b, err := Asset(file)
if err != nil {
return nil, err
}
return template.New("").Parse(string(b))
}

View File

@@ -1,7 +1,6 @@
package views
import (
"bytes"
"html/template"
"io/ioutil"
"os"
@@ -15,8 +14,9 @@ func TestTemplates_ExecuteTemplate_ReturnsTemplateWhenFound(t *testing.T) {
tpl, _ := template.New("").Parse("<strong>{{.Val}}</strong>")
tpls := Templates{"a": tpl}
b := &bytes.Buffer{}
if err := tpls.ExecuteTemplate(b, "a", struct{ Val string }{"hi"}); err != nil {
//b := &bytes.Buffer{}
b, err := tpls.ExecuteTemplate("a", struct{ Val string }{"hi"})
if err != nil {
t.Error("Unexpected error:", err)
}
@@ -31,7 +31,7 @@ func TestTemplates_ExecuteTemplate_ReturnsErrTempalteNotFound(t *testing.T) {
t.Parallel()
tpls := Templates{}
err := tpls.ExecuteTemplate(ioutil.Discard, "shouldnotbefound", nil)
_, err := tpls.ExecuteTemplate("shouldnotbefound", nil)
if err == nil {
t.Error("Expected error")
}
@@ -65,8 +65,8 @@ func TestGet(t *testing.T) {
t.Error("Unexpected error:", err)
}
b := &bytes.Buffer{}
if err := tpls.ExecuteTemplate(b, filename, struct{ Val string }{"hi"}); err != nil {
b, err := tpls.ExecuteTemplate(filename, struct{ Val string }{"hi"})
if err != nil {
t.Error("Unexpected error:", err)
}

View File

@@ -1,17 +1,15 @@
package recover
import (
"bytes"
"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/flashutil"
"gopkg.in/authboss.v0/internal/views"
@@ -78,7 +76,7 @@ func (m *RecoverModule) Initialize(config *authboss.Config) (err error) {
func (m *RecoverModule) Routes() authboss.RouteTable {
return authboss.RouteTable{
"recover": m.recoverHandlerFunc,
"recover/complete": m.recoverCompleteHandlerFunc,
"recover/complete": nil, // TODO : Fix
}
}
func (m *RecoverModule) Storage() authboss.StorageOptions {
@@ -91,6 +89,20 @@ func (m *RecoverModule) Storage() authboss.StorageOptions {
}
}
func (m *RecoverModule) execTpl(tpl string, w http.ResponseWriter, page pageRecover) {
buffer, err := m.templates.ExecuteTemplate(tpl, page)
if err != nil {
fmt.Fprintf(m.config.LogWriter, errFormat, "unable to execute template", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
if _, err := io.Copy(w, buffer); err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
}
type pageRecover struct {
Username, ConfirmUsername string
ErrMap map[string][]string
@@ -101,12 +113,18 @@ type pageRecover struct {
func (m *RecoverModule) recoverHandlerFunc(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) {
switch r.Method {
case methodGET:
m.execTpl(w, pageRecover{FlashError: flashutil.Pull(ctx.SessionStorer, authboss.FlashErrorKey)})
page := pageRecover{}
page.FlashError = flashutil.Pull(ctx.SessionStorer, authboss.FlashErrorKey)
m.execTpl(tplRecover, w, page)
case methodPOST:
if page := m.recover(ctx); page != nil {
m.execTpl(w, page)
page, _ := m.recover(ctx)
if page != nil {
m.execTpl(tplRecover, w, *page)
return
}
ctx.SessionStorer.Put(authboss.FlashSuccessKey, m.config.RecoverInitiateSuccessFlash)
http.Redirect(w, r, m.config.RecoverRedirect, http.StatusFound)
default:
@@ -114,78 +132,91 @@ func (m *RecoverModule) recoverHandlerFunc(ctx *authboss.Context, w http.Respons
}
}
func (m *RecoverModule) execTpl(w http.ResponseWriter, data interface{}) {
if err := m.templates.ExecuteTemplate(w, tplRecover, data); err != nil {
fmt.Fprintf(m.config.LogWriter, errFormat, "unable to execute template", err)
}
}
func (m *RecoverModule) recover(ctx *authboss.Context) *pageRecover {
func (m *RecoverModule) recover(ctx *authboss.Context) (errPage *pageRecover, emailSent <-chan struct{}) {
username, _ := ctx.FirstPostFormValue("username")
confirmUsername, _ := ctx.FirstPostFormValue("confirmUsername")
policies := authboss.FilterValidators(m.config.Policies, "username")
if validationErrs := ctx.Validate(policies, m.config.ConfirmFields...); len(validationErrs) > 0 {
return m.prepareRecoverPage(username, confirmUsername, "", "validation failed", validationErrs.Map())
fmt.Fprintf(m.config.LogWriter, errFormat, "validation failed", validationErrs)
return &pageRecover{username, confirmUsername, validationErrs.Map(), "", ""}, nil
}
if err := ctx.LoadUser(username, m.config.Storer); err != nil {
return m.prepareRecoverPage(username, confirmUsername, m.config.RecoverFailedErrorFlash, "failed to recover", nil)
err, emailSent := m.makeAndSendToken(ctx, username)
if err != nil {
fmt.Fprintf(m.config.LogWriter, errFormat, "failed to recover", err)
return &pageRecover{username, confirmUsername, nil, "", m.config.RecoverFailedErrorFlash}, nil
}
return nil, emailSent
}
func (m *RecoverModule) makeAndSendToken(ctx *authboss.Context, username string) (err error, emailSent <-chan struct{}) {
if err = ctx.LoadUser(username, m.config.Storer); 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 m.prepareRecoverPage(username, confirmUsername, m.config.RecoverFailedErrorFlash, "failed to recover", nil)
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(m.config.RecoverTokenDuration)
if err := ctx.SaveUser(username, m.config.Storer); err != nil {
return m.prepareRecoverPage(username, confirmUsername, m.config.RecoverFailedErrorFlash, "failed to recover", nil)
if err = ctx.SaveUser(username, m.config.Storer); err != nil {
return err, nil
}
/*if email, ok := ctx.User.String(attrEmail); !ok {
return m.prepareRecoverPage(username, confirmUsername, m.config.RecoverFailedErrorFlash, "failed to recover", nil)
} else {
go m.sendRecoverEmail(email, token)
}*/
return nil
return nil, m.sendRecoverEmail(email, token)
}
func (m *RecoverModule) prepareRecoverPage(username, confirmUsername, flashError, message string, validationErrs map[string][]string) *pageRecover {
fmt.Fprintf(m.config.LogWriter, errFormat, message, validationErrs)
return &pageRecover{username, confirmUsername, validationErrs, "", flashError}
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", m.config.HostName, base64.URLEncoding.EncodeToString(token))}
htmlEmailBody, err := m.emailTemplates.ExecuteTemplate(tplInitHTMLEmail, data)
if err != nil {
fmt.Fprintf(m.config.LogWriter, errFormat, "failed to build html email", err)
close(emailSent)
return
}
textEmaiLBody, err := m.emailTemplates.ExecuteTemplate(tplInitTextEmail, data)
if err != nil {
fmt.Fprintf(m.config.LogWriter, errFormat, "failed to build plaintext email", err)
close(emailSent)
return
}
if err := m.config.Mailer.Send(authboss.Email{
To: []string{to},
ToNames: []string{""},
From: m.config.EmailFrom,
Subject: m.config.EmailSubjectPrefix + "Password Reset",
TextBody: textEmaiLBody.String(),
HTMLBody: htmlEmailBody.String(),
}); err != nil {
fmt.Fprintf(m.config.LogWriter, errFormat, "failed to send email", err)
close(emailSent)
return
}
emailSent <- struct{}{}
}()
return emailSent
}
func (m *RecoverModule) sendRecoverEmail(to string, token []byte) {
data := struct{ Link string }{fmt.Sprintf("%s/recover/complete?token=%s", m.config.HostName, base64.URLEncoding.EncodeToString(token))}
htmlEmailBody := &bytes.Buffer{}
if err := m.emailTemplates.ExecuteTemplate(htmlEmailBody, tplInitHTMLEmail, data); err != nil {
fmt.Fprintf(m.config.LogWriter, errFormat, "failed to build html tpl", err)
}
textEmailBody := &bytes.Buffer{}
if err := m.emailTemplates.ExecuteTemplate(textEmaiLBody, tplInitTextEmail, data); err != nil {
fmt.Fprintf(m.config.LogWriter, errFormat, "failed to build plaintext tpl", err)
}
if err := m.config.Mailer.Send(authboss.Email{
To: []string{to},
ToNames: []string{""},
From: m.config.EmailFrom,
Subject: m.config.EmailSubjectPrefix + "Password Reset",
TextBody: textEmailBody.String(),
HTMLBody: htmlEmailBody.String(),
}); err != nil {
fmt.Fprintf(m.config.LogWriter, errFormat, "failed to send email", err)
}
}
type pageRecoverComplete struct {
/*type pageRecoverComplete struct {
Token string
ErrMap map[string][]string
FlashSuccess string
@@ -295,3 +326,4 @@ func (m *RecoverModule) verifyToken(token string) (attrs authboss.Attributes, er
return authboss.Unbind(userInter), nil
}
*/

View File

@@ -2,6 +2,8 @@ package recover
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"html/template"
"io"
@@ -16,37 +18,9 @@ import (
"gopkg.in/authboss.v0"
"gopkg.in/authboss.v0/internal/mocks"
"gopkg.in/authboss.v0/internal/views"
)
var filenames = []string{
tplLogin,
tplRecover,
tplRecoverComplete,
tplInitHTMLEmail,
tplInitTextEmail,
}
func TestMain(main *testing.M) {
for _, filename := range filenames {
file, err := os.Create(fmt.Sprintf("%s/%s", os.TempDir(), filename))
if err != nil {
panic(err)
}
if _, err := file.WriteString(filename); err != nil {
}
}
code := main.Run()
for _, filename := range filenames {
os.Remove(filename)
}
os.Exit(code)
}
type failStorer int
func (_ failStorer) Create(_ string, _ authboss.Attributes) error { return nil }
@@ -105,16 +79,21 @@ func testValidTestConfig() *authboss.Config {
config := &authboss.Config{}
config.Storer = mocks.NewMockStorer()
config.ViewsPath = os.TempDir()
config.EmailFrom = "auth@boss.com"
var err error
if config.Layout, err = template.New("").Parse(`<i>{{template "authboss" .}}</i>`); err != nil {
if config.Layout, err = views.AssetToTemplate("layout.tpl"); err != nil {
panic(err)
}
if config.LayoutEmail, _ = template.New("").Parse(`<b>{{template "authboss" .}}</b>`); err != nil {
if config.LayoutEmail, err = views.AssetToTemplate("layoutEmail.tpl"); err != nil {
panic(err)
}
config.RecoverRedirect = "/login"
config.RecoverInitiateSuccessFlash = "sf"
config.RecoverTokenExpiredFlash = "exf"
config.RecoverFailedErrorFlash = "errf"
config.Policies = []authboss.Validator{
authboss.Rules{
FieldName: "username",
@@ -122,21 +101,23 @@ func testValidTestConfig() *authboss.Config {
},
}
config.ConfirmFields = []string{"username", "confirmUsername"}
config.LogWriter = &bytes.Buffer{}
config.Mailer = &mocks.MockMailer{}
config.EmailFrom = "auth@boss.com"
config.HostName = "localhost"
return config
}
func testValidRecoverModule() (*RecoverModule, *bytes.Buffer) {
c := testValidTestConfig()
logger := &bytes.Buffer{}
c.LogWriter = logger
m := &RecoverModule{}
if err := m.Initialize(c); err != nil {
panic(err)
}
return m, logger
return m, c.LogWriter.(*bytes.Buffer)
}
func Test_Routes(t *testing.T) {
@@ -212,39 +193,159 @@ func Test_recoverHandlerFunc_GET(t *testing.T) {
m.recoverHandlerFunc(ctx, w, r)
expectedBody := &bytes.Buffer{}
if err := m.templates[tplRecover].Execute(expectedBody, pageRecover{}); err != nil {
panic(err)
}
if w.Code != http.StatusOK {
t.Error("Unexpected code:", w.Code)
}
if w.Body.String() != "<i>recover.tpl</i>" {
if !bytes.Equal(expectedBody.Bytes(), w.Body.Bytes()) {
t.Error("Unexpected body:", w.Body.String())
}
}
/*func TestRecoverModule_recoverHandlerFunc_POST(t *testing.T) {
func Test_recoverHandlerFunc_POST_RecoveryFailed(t *testing.T) {
t.Parallel()
}*/
func Test_recover(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: m.config.RecoverFailedErrorFlash,
}); 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) {
t.Parallel()
m, _ := testValidRecoverModule()
w, r, ctx := testHttpRequest("POST", "/login", url.Values{"username": []string{"a"}, "confirmUsername": []string{"a"}})
storer, ok := m.config.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)[authboss.FlashSuccessKey]
if successFlash != m.config.RecoverInitiateSuccessFlash {
t.Error("Unexpected success flash message:", successFlash)
}
}
func Test_execTpl_TemplateExectionFail(t *testing.T) {
t.Parallel()
m, logger := testValidRecoverModule()
w := httptest.NewRecorder()
page := m.recover(mocks.MockRequestContext())
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) {
t.Parallel()
m, _ := testValidRecoverModule()
w := httptest.NewRecorder()
page := pageRecover{"bobby", "bob", nil, "", m.config.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())
}
}
func Test_recover_UsernameValidationFail(t *testing.T) {
t.Parallel()
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]: map[username:[Cannot be blank]]\n")
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.Error("Unexpected logs:", string(expectedLog))
t.Errorf("Unexpected logs: %q", string(actualLog))
}
if emailSent != nil {
t.Error("Unexpected sent email")
}
}
page = m.recover(mocks.MockRequestContext("username", "a", "confirmUsername", "b"))
func Test_recover_ConfirmUsernameCheckFail(t *testing.T) {
t.Parallel()
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")
}
@@ -254,22 +355,128 @@ func Test_recover(t *testing.T) {
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]: map[confirmUsername:[Does not match username]]\n")
actualLog, err = ioutil.ReadAll(logger)
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(expectedLog))
t.Error("Unexpected logs:", string(actualLog))
}
if emailSent != nil {
t.Error("Unexpected sent email")
}
}
func Test_recover_InvalidUser(t *testing.T) {
t.Parallel()
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 != m.config.RecoverFailedErrorFlash {
t.Error("Expected flash error:", m.config.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) {
t.Parallel()
m, _ := testValidRecoverModule()
storer, ok := m.config.Storer.(*mocks.MockStorer)
if !ok {
panic("Failed to get storer")
}
storer.Users["a"] = authboss.Attributes{"username": "", "password": "", "email", "a@b.c"}
storer.Users["a"] = authboss.Attributes{"username": "", "password": "", "email": "a@b.c"}
page = m.recover(mocks.MockRequestContext("username", "a", "confirmUsername", "a"))
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) {
t.Parallel()
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) {
t.Parallel()
m, _ := testValidRecoverModule()
ctx := mocks.MockRequestContext()
storer, ok := m.config.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) {
t.Parallel()
m, _ := testValidRecoverModule()
ctx := mocks.MockRequestContext()
storer, ok := m.config.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 {
@@ -281,6 +488,127 @@ func Test_recover(t *testing.T) {
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) {
t.Parallel()
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]:")
}
}
type failMailer struct{}
func (_ failMailer) Send(_ authboss.Email) error {
return errors.New("")
}
func Test_sendRecoverEmail_FailToSend(t *testing.T) {
t.Parallel()
m, logger := testValidRecoverModule()
m.config.Mailer = failMailer{}
<-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) {
t.Parallel()
m, _ := testValidRecoverModule()
<-m.sendRecoverEmail("a@b.c", []byte("abc123"))
mailer, ok := m.config.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 != m.config.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",
m.config.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) {