From 6f074543f4cde3e38ef84f88019ec67724cb247d Mon Sep 17 00:00:00 2001 From: Aaron Date: Mon, 23 Feb 2015 02:03:29 -0800 Subject: [PATCH] Start the register module. --- config.go | 5 +- internal/render/bindata.go | 39 +++++-- internal/render/render.go | 15 ++- internal/render/templates/register.html.tpl | 15 +++ register/register.go | 107 ++++++++++++++++++++ register/register_test.go | 69 +++++++++++++ validation.go | 5 + 7 files changed, 239 insertions(+), 16 deletions(-) create mode 100644 internal/render/templates/register.html.tpl create mode 100644 register/register.go create mode 100644 register/register_test.go diff --git a/config.go b/config.go index 118b537..82d934d 100644 --- a/config.go +++ b/config.go @@ -5,7 +5,6 @@ import ( "io" "io/ioutil" "net/smtp" - "strings" "time" "golang.org/x/crypto/bcrypt" @@ -105,8 +104,8 @@ func NewConfig() *Config { }, }, ConfirmFields: []string{ - StoreEmail, "confirm" + strings.Title(StoreEmail), - StorePassword, "confirm" + strings.Title(StorePassword), + StoreEmail, ConfirmPrefix + StoreEmail, + StorePassword, ConfirmPrefix + StorePassword, }, RecoverRedirect: "/login", diff --git a/internal/render/bindata.go b/internal/render/bindata.go index a90d74c..369c67f 100644 --- a/internal/render/bindata.go +++ b/internal/render/bindata.go @@ -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(1424471280, 0)} + info := bindata_file_info{name: "confirm_email.html.tpl", size: 26, mode: os.FileMode(438), modTime: time.Unix(1424498554, 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(1424471280, 0)} + info := bindata_file_info{name: "confirm_email.txt.tpl", size: 9, mode: os.FileMode(438), modTime: time.Unix(1424498554, 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\x52\x5b\x6e\x83\x30\x10\xfc\xaf\xd4\x3b\xac\xf6\xbf\xe5\x02\x80\xd4\xff\x3e\xa2\x36\x3d\x80\x31\x4b\x41\xc1\x5e\xb4\xd8\x49\x2a\xc4\xdd\x6b\x07\x9c\x87\x14\x95\xaf\xd5\xec\x30\x33\xcc\x92\x37\x2c\x06\x94\x76\x1d\xdb\x02\xb3\x9e\x7f\x3a\x8b\x60\xc8\xb5\x5c\x17\xb8\xf9\xf8\xda\x62\xf9\xf8\x00\xe1\x99\xa6\xae\x81\x67\x12\x61\x99\xe7\x69\x4a\x53\x5e\x09\x64\xe5\x34\x91\xad\xe7\x79\x61\xe6\x9d\x1d\xbc\x03\xf7\x3b\x50\x81\x8e\x8e\x0e\x41\xf7\x6a\x1c\x0b\x8c\x66\x4f\x9a\xad\x13\xee\x11\xac\x32\x81\xe0\x47\x92\x38\x21\x0c\xbd\xd2\xd4\x72\x5f\x93\x14\xf8\x7d\x86\xf7\xaa\xf7\x81\x17\x2c\x13\x75\x9e\x53\xa8\xd5\x6a\xf5\x1a\x82\xc9\x81\xa5\xfe\xd7\xef\x42\xba\xf1\xdb\x24\xb8\xbc\xf3\x11\x6d\x57\xd7\x64\x93\x42\x48\x72\x1c\xa5\x79\x5f\x92\x5c\xe5\x8b\xe8\x96\x77\x64\x23\x9c\xdd\xf4\x36\xb6\x7c\xf8\x24\x43\xa6\xa2\x58\xda\xb5\xb8\x6e\x49\xef\x2a\x3e\x26\x79\x31\x67\x4d\x27\x9e\xb0\x84\xf4\x22\xbc\xd1\x6d\xd1\xa7\xee\xd3\xec\x9d\x63\xbb\x6a\x8e\xbe\x32\x9d\xc3\xf2\x35\xde\x33\xcf\x96\xdd\x9d\x40\x9a\xf7\xa7\x3c\x0a\x5a\xa1\x26\xfc\x00\xb2\x40\x58\xae\x3b\x78\xd1\x9a\xbd\x75\x79\xa6\x2e\x47\xce\xb3\x58\x6c\xf9\x17\x00\x00\xff\xff\x1e\x0e\xca\x3a\x3c\x02\x00\x00") +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") func login_tpl_bytes() ([]byte, error) { return bindata_read( @@ -116,7 +116,7 @@ func login_tpl() (*asset, error) { return nil, err } - info := bindata_file_info{name: "login.tpl", size: 572, mode: os.FileMode(438), modTime: time.Unix(1424503580, 0)} + info := bindata_file_info{name: "login.tpl", size: 590, mode: os.FileMode(438), modTime: time.Unix(1424567032, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -136,7 +136,7 @@ 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(1424471280, 0)} + info := bindata_file_info{name: "recover-complete.tpl", size: 1235, mode: os.FileMode(438), modTime: time.Unix(1424498554, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -156,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(1424471280, 0)} + info := bindata_file_info{name: "recover-html.email", size: 26, mode: os.FileMode(438), modTime: time.Unix(1424498554, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -176,7 +176,7 @@ 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(1424471280, 0)} + info := bindata_file_info{name: "recover-text.email", size: 9, mode: os.FileMode(438), modTime: time.Unix(1424498554, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -196,7 +196,27 @@ func recover_tpl() (*asset, error) { return nil, err } - info := bindata_file_info{name: "recover.tpl", size: 1623, mode: os.FileMode(438), modTime: time.Unix(1424541421, 0)} + info := bindata_file_info{name: "recover.tpl", size: 1623, mode: os.FileMode(438), modTime: time.Unix(1424567032, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _register_html_tpl = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x94\x93\x41\x6f\xea\x30\x0c\xc7\xcf\xf4\x53\x58\x11\xe7\xf6\x8e\xd2\x5e\xde\xbb\x3c\xe9\x69\xe2\x30\xed\x3a\x85\xc6\xa5\xd1\xd2\x24\x4a\xc2\x00\x55\xfd\xee\x4b\x68\xd7\x12\xd0\x36\x76\x81\xd8\xfa\xdb\xfe\xff\x2c\x97\x36\xda\x76\xc0\x6a\x2f\xb4\x2a\x89\xc5\xbd\x70\x1e\x2d\x81\x0e\x7d\xab\x79\x49\x8c\x76\x9e\x54\xd9\x8a\x4a\xb6\x43\x09\x41\x5d\x92\xbe\xcf\x8d\x15\x1d\xb3\xe7\x7f\x7f\x87\x81\x54\x69\xbc\xa1\xc5\x45\x1b\x8b\x84\x32\x07\x0f\x8a\x75\x58\xa6\x22\xf0\x67\x83\x25\xf1\x78\xf2\x04\xde\x99\x3c\x60\x6c\x2b\x1a\x58\x44\x2f\x31\x3b\x0c\xd7\x75\x73\x0a\x15\x0f\x83\xc1\x48\x56\x63\xab\x25\xc7\x7b\x57\x50\x54\x74\x67\xc3\x6f\xb6\xea\xfb\xb5\x11\x1c\x36\x25\x5c\x2b\xfa\xfe\x28\x7c\x0b\x39\x5a\xeb\xe6\x68\x1d\x22\x19\x56\x10\xc5\x42\x71\x3c\x41\x0e\xb1\x38\x0a\x2c\x53\x7b\x9c\x15\xc3\x40\x9d\x61\x2a\xc2\x87\x67\x71\x79\x8f\x03\x27\x7b\xe9\x5f\xba\x41\xc3\x9c\x3b\x6a\xcb\x49\xb5\x9d\x5e\x5f\x6c\x6d\x51\x4e\x1b\x5b\xe2\x04\x7e\x3b\xa7\xaf\xb1\x53\xc0\xd1\x7f\xfe\xd9\xe1\x51\x80\xd4\x79\xad\x55\x23\x6c\xf7\xba\x10\xfc\x19\x33\xf0\x13\xc9\x5d\xe5\xf7\x44\xb7\x6d\x1f\x20\xbb\x9d\xf0\x0b\xc2\xd1\xe8\x68\xc8\x1d\x76\x9d\x58\xce\xf2\xbf\xde\x0b\x45\xe6\xd9\x94\x41\x6b\xb1\x29\x49\x11\xd0\x99\xaa\x51\xd2\x82\x55\xd9\x4d\x8f\x56\x70\x8e\x8a\x4c\xe8\xc1\xc0\xc9\xd9\xe6\x29\x04\xf1\x32\xe7\x7b\xbf\x64\x9f\xf5\x1b\xaa\xf1\x60\x33\x5a\xc4\xcf\xb1\xfa\x08\x00\x00\xff\xff\x3c\x36\x7b\x13\x95\x03\x00\x00") + +func register_html_tpl_bytes() ([]byte, error) { + return bindata_read( + _register_html_tpl, + "register.html.tpl", + ) +} + +func register_html_tpl() (*asset, error) { + bytes, err := register_html_tpl_bytes() + if err != nil { + return nil, err + } + + info := bindata_file_info{name: "register.html.tpl", size: 917, mode: os.FileMode(438), modTime: time.Unix(1424682732, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -249,6 +269,7 @@ var _bindata = map[string]func() (*asset, error){ "recover-html.email": recover_html_email, "recover-text.email": recover_text_email, "recover.tpl": recover_tpl, + "register.html.tpl": register_html_tpl, } // AssetDir returns the file names below a certain @@ -305,6 +326,8 @@ var _bintree = &_bintree_t{nil, map[string]*_bintree_t{ }}, "recover.tpl": &_bintree_t{recover_tpl, map[string]*_bintree_t{ }}, + "register.html.tpl": &_bintree_t{register_html_tpl, map[string]*_bintree_t{ + }}, }} // Restore an asset under the given directory diff --git a/internal/render/render.go b/internal/render/render.go index e55934f..84542a5 100644 --- a/internal/render/render.go +++ b/internal/render/render.go @@ -12,6 +12,7 @@ import ( "net/http" "os" "path/filepath" + "strings" "gopkg.in/authboss.v0" ) @@ -19,6 +20,10 @@ import ( var ( // ErrTemplateNotFound should be returned from Get when the view is not found ErrTemplateNotFound = errors.New("Template not found") + + funcMap = template.FuncMap{ + "title": strings.Title, + } ) // Templates is a map depicting the forms a template needs wrapped within the specified layout @@ -47,7 +52,7 @@ func LoadTemplates(layout *template.Template, path string, files ...string) (Tem return nil, err } - _, err = clone.New("authboss").Parse(string(b)) + _, err = clone.New("authboss").Funcs(funcMap).Parse(string(b)) if err != nil { return nil, err } @@ -71,12 +76,12 @@ func (t Templates) Render(ctx *authboss.Context, w http.ResponseWriter, r *http. data.Merge(authboss.Cfg.LayoutDataMaker(w, r)) } - if flash, ok := ctx.CookieStorer.Get(authboss.FlashSuccessKey); ok { - ctx.CookieStorer.Del(authboss.FlashSuccessKey) + if flash, ok := ctx.SessionStorer.Get(authboss.FlashSuccessKey); ok { + ctx.SessionStorer.Del(authboss.FlashSuccessKey) data.MergeKV(authboss.FlashSuccessKey, flash) } - if flash, ok := ctx.CookieStorer.Get(authboss.FlashErrorKey); ok { - ctx.CookieStorer.Del(authboss.FlashErrorKey) + if flash, ok := ctx.SessionStorer.Get(authboss.FlashErrorKey); ok { + ctx.SessionStorer.Del(authboss.FlashErrorKey) data.MergeKV(authboss.FlashErrorKey, flash) } diff --git a/internal/render/templates/register.html.tpl b/internal/render/templates/register.html.tpl new file mode 100644 index 0000000..4de3b5c --- /dev/null +++ b/internal/render/templates/register.html.tpl @@ -0,0 +1,15 @@ +
+ +
+ {{$pid := .primaryID}}{{with .errs}}{{with $errlist := index . $pid}}{{range $errlist}}{{.}}
{{end}}{{end}}{{end}} + +
+ {{with .errs}}{{range .password}}{{.}}
{{end}}{{end}} + +
+ {{with .errs}}{{range .confirm_password}}{{.}}
{{end}}{{end}} +
+ Cancel + + +
\ No newline at end of file diff --git a/register/register.go b/register/register.go new file mode 100644 index 0000000..ecb5da5 --- /dev/null +++ b/register/register.go @@ -0,0 +1,107 @@ +// Package register allows for user registration. +package register + +import ( + "net/http" + + "gopkg.in/authboss.v0" + "gopkg.in/authboss.v0/internal/render" +) + +const ( + tplRegister = "register.html.tpl" +) + +// R is the singleton instance of the register module which will have been +// configured and ready to use after authboss.Init() +var R *Register + +func init() { + R = &Register{} + authboss.RegisterModule("register", R) +} + +// Register module. +type Register struct { + templates render.Templates +} + +// Initialize the module. +func (r *Register) Initialize() (err error) { + if r.templates, err = render.LoadTemplates(authboss.Cfg.Layout, authboss.Cfg.ViewsPath, tplRegister); err != nil { + return err + } + + return nil +} + +// Routes creates the routing table. +func (r *Register) Routes() authboss.RouteTable { + return authboss.RouteTable{ + "/register": r.registerHandler, + } +} + +// Storage returns storage requirements. +func (r *Register) Storage() authboss.StorageOptions { + return authboss.StorageOptions{ + authboss.Cfg.PrimaryID: authboss.String, + authboss.StorePassword: authboss.String, + } +} + +func (reg *Register) registerHandler(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) error { + switch r.Method { + case "GET": + data := authboss.HTMLData{ + "primaryID": authboss.Cfg.PrimaryID, + "primaryIDValue": "", + } + return reg.templates.Render(ctx, w, r, tplRegister, data) + case "POST": + return reg.registerPostHandler(ctx, w, r) + } + return nil +} + +func (reg *Register) registerPostHandler(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) error { + key, _ := ctx.FirstPostFormValue(authboss.Cfg.PrimaryID) + password, _ := ctx.FirstPostFormValue(authboss.StorePassword) + + policies := authboss.FilterValidators(authboss.Cfg.Policies, authboss.Cfg.PrimaryID, authboss.StorePassword) + validationErrs := ctx.Validate(policies, authboss.Cfg.ConfirmFields...) + + if len(validationErrs) != 0 { + data := authboss.HTMLData{ + "primaryID": authboss.Cfg.PrimaryID, + "primaryIDValue": key, + "errs": validationErrs.Map(), + } + + return reg.templates.Render(ctx, w, r, tplRegister, data) + } + + attr, err := ctx.Attributes() // Attributes from overriden forms + if err != nil { + return err + } + attr[authboss.Cfg.PrimaryID] = key + attr[authboss.StorePassword] = password + ctx.User = attr + + if err := authboss.Cfg.Storer.Create(key, attr); err != nil { + return err + } + + authboss.Cfg.Callbacks.FireAfter(authboss.EventRegister, ctx) + + if authboss.IsLoaded("confirm") { + render.Redirect(ctx, w, r, "/", "Account successfully created, please verify your e-mail address.", "") + return nil + } + + ctx.SessionStorer.Put(authboss.SessionKey, key) + render.Redirect(ctx, w, r, "/", "Account successfully created, you are now logged in.", "") + + return nil +} diff --git a/register/register_test.go b/register/register_test.go new file mode 100644 index 0000000..da600c9 --- /dev/null +++ b/register/register_test.go @@ -0,0 +1,69 @@ +package register + +import ( + "html/template" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "gopkg.in/authboss.v0" + "gopkg.in/authboss.v0/internal/mocks" +) + +func TestRegister(t *testing.T) { + authboss.Cfg = authboss.NewConfig() + r := Register{} + + if err := r.Initialize(); err != nil { + t.Error(err) + } + + if r.Routes()["/register"] == nil { + t.Error("Expected a register handler at /register.") + } + + sto := r.Storage() + if sto[authboss.Cfg.PrimaryID] != authboss.String { + t.Error("Wanted primary ID to be a string.") + } + if sto[authboss.StorePassword] != authboss.String { + t.Error("Wanted password to be a string.") + } +} + +func TestRegisterGet(t *testing.T) { + authboss.Cfg = &authboss.Config{ + Layout: template.Must(template.New("").Parse(`{{template "authboss"}}`)), + XSRFName: "xsrf", + XSRFMaker: func(_ http.ResponseWriter, _ *http.Request) string { + return "xsrfvalue" + }, + } + reg := Register{} + + if err := reg.Initialize(); err != nil { + t.Error(err) + } + + w := httptest.NewRecorder() + r, _ := http.NewRequest("GET", "/register", nil) + ctx, _ := authboss.ContextFromRequest(r) + ctx.SessionStorer = mocks.NewMockClientStorer() + + if err := reg.registerHandler(ctx, w, r); err != nil { + t.Error(err) + } + + if w.Code != http.StatusOK { + t.Error("It should have written a 200:", w.Code) + } + + if w.Body.Len() == 0 { + t.Error("It should have wrote a response.") + } + + if str := w.Body.String(); !strings.Contains(str, "