1
0
mirror of https://github.com/volatiletech/authboss.git synced 2025-01-06 03:54:17 +02:00

Fix confirm module.

This commit is contained in:
Aaron 2015-02-22 00:09:52 -08:00
parent 1ca6eb1cc0
commit 94f441f3d7
2 changed files with 78 additions and 93 deletions

View File

@ -2,7 +2,6 @@
package confirm
import (
"bytes"
"crypto/md5"
"crypto/rand"
"encoding/base64"
@ -12,7 +11,7 @@ import (
"net/url"
"gopkg.in/authboss.v0"
"gopkg.in/authboss.v0/internal/views"
"gopkg.in/authboss.v0/internal/render"
)
const (
@ -26,9 +25,7 @@ const (
)
var (
// ErrNotConfirmed happens when the account is there, but
// not yet confirmed.
ErrNotConfirmed = errors.New("Account is not confirmed.")
errUserMissing = errors.New("confirm: After registration user must be loaded")
)
// C is the singleton instance of the confirm module which will have been
@ -41,7 +38,7 @@ func init() {
}
type Confirm struct {
emailTemplates views.Templates
emailTemplates render.Templates
}
func (c *Confirm) Initialize() (err error) {
@ -51,7 +48,7 @@ func (c *Confirm) Initialize() (err error) {
return errors.New("confirm: Need a ConfirmStorer.")
}
c.emailTemplates, err = views.Get(authboss.Cfg.LayoutEmail, authboss.Cfg.ViewsPath, tplConfirmHTML, tplConfirmText)
c.emailTemplates, err = render.LoadTemplates(authboss.Cfg.LayoutEmail, authboss.Cfg.ViewsPath, tplConfirmHTML, tplConfirmText)
if err != nil {
return err
}
@ -75,41 +72,42 @@ func (c *Confirm) Storage() authboss.StorageOptions {
}
}
func (c *Confirm) BeforeGet(ctx *authboss.Context) error {
if intf, ok := ctx.User[StoreConfirmed]; ok {
if confirmed, ok := intf.(bool); ok && confirmed {
return nil
}
func (c *Confirm) BeforeGet(ctx *authboss.Context) (authboss.Interrupt, error) {
if confirmed, err := ctx.User.BoolErr(StoreConfirmed); err != nil {
return authboss.InterruptNone, err
} else if !confirmed {
return authboss.InterruptAccountNotConfirmed, nil
}
return ErrNotConfirmed
return authboss.InterruptNone, nil
}
// AfterRegister ensures the account is not activated.
func (c *Confirm) AfterRegister(ctx *authboss.Context) {
func (c *Confirm) AfterRegister(ctx *authboss.Context) error {
if ctx.User == nil {
fmt.Fprintln(authboss.Cfg.LogWriter, "confirm: user not loaded in AfterRegister callback")
return
return errUserMissing
}
token := make([]byte, 32)
if _, err := rand.Read(token); err != nil {
fmt.Fprintln(authboss.Cfg.LogWriter, "confirm: failed to produce random token:", err)
return err
}
sum := md5.Sum(token)
ctx.User[StoreConfirmToken] = base64.StdEncoding.EncodeToString(sum[:])
if err := ctx.SaveUser(); err != nil {
fmt.Fprintln(authboss.Cfg.LogWriter, "confirm: failed to save user's token:", err)
return
return err
}
if email, ok := ctx.User.String(authboss.StoreEmail); !ok {
fmt.Fprintln(authboss.Cfg.LogWriter, "confirm: user has no e-mail address to send to, could not send confirm e-mail")
} else {
goConfirmEmail(c, email, base64.URLEncoding.EncodeToString(sum[:]))
email, err := ctx.User.StringErr(authboss.StoreEmail)
if err != nil {
return err
}
goConfirmEmail(c, email, base64.URLEncoding.EncodeToString(sum[:]))
return nil
}
var goConfirmEmail = func(c *Confirm, to, token string) {
@ -120,42 +118,29 @@ var goConfirmEmail = func(c *Confirm, to, token string) {
func (c *Confirm) confirmEmail(to, token string) {
url := fmt.Sprintf("%s/confirm?%s=%s", authboss.Cfg.HostName, url.QueryEscape(FormValueConfirm), url.QueryEscape(token))
var htmlEmailBody, textEmailBody *bytes.Buffer
var err error
if htmlEmailBody, err = c.emailTemplates.ExecuteTemplate(tplConfirmHTML, url); err != nil {
fmt.Fprintln(authboss.Cfg.LogWriter, "confirm: failed to build html template:", err)
return
email := authboss.Email{
To: []string{to},
From: authboss.Cfg.EmailFrom,
Subject: authboss.Cfg.EmailSubjectPrefix + "Confirm New Account",
}
if textEmailBody, err = c.emailTemplates.ExecuteTemplate(tplConfirmText, url); err != nil {
fmt.Fprintln(authboss.Cfg.LogWriter, "confirm: failed to build plaintext template:", err)
return
}
if err := authboss.Cfg.Mailer.Send(authboss.Email{
To: []string{to},
From: authboss.Cfg.EmailFrom,
Subject: authboss.Cfg.EmailSubjectPrefix + "Confirm New Account",
TextBody: textEmailBody.String(),
HTMLBody: htmlEmailBody.String(),
}); err != nil {
fmt.Fprintln(authboss.Cfg.LogWriter, "confirm: failed to build plaintext template:", err)
err := c.emailTemplates.RenderEmail(email, tplConfirmHTML, tplConfirmText, url)
if err != nil {
fmt.Fprintf(authboss.Cfg.LogWriter, "confirm: Failed to send e-mail: %v", err)
}
}
func (c *Confirm) confirmHandler(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) {
token, ok := ctx.FirstFormValue(FormValueConfirm)
if len(token) == 0 || !ok {
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
fmt.Fprintln(authboss.Cfg.LogWriter, "confirm: no confirm token found in request")
return
func (c *Confirm) confirmHandler(ctx *authboss.Context, w http.ResponseWriter, r *http.Request) error {
token, err := ctx.FirstFormValueErr(FormValueConfirm)
if err != nil {
return err
}
toHash, err := base64.URLEncoding.DecodeString(token)
if err != nil {
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
fmt.Fprintf(authboss.Cfg.LogWriter, "confirm: confirm token failed to decode %q => %v\n", token, err)
return
return authboss.ErrAndRedirect{
Endpoint: "/", Err: fmt.Errorf("confirm: token failed to decode %q => %v\n", token, err),
}
}
sum := md5.Sum(toHash)
@ -163,13 +148,9 @@ func (c *Confirm) confirmHandler(ctx *authboss.Context, w http.ResponseWriter, r
dbTok := base64.StdEncoding.EncodeToString(sum[:])
user, err := authboss.Cfg.Storer.(authboss.ConfirmStorer).ConfirmUser(dbTok)
if err == authboss.ErrUserNotFound {
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
fmt.Fprintln(authboss.Cfg.LogWriter, "confirm: token not found:", err)
return
return authboss.ErrAndRedirect{Endpoint: "/", Err: errors.New("confirm: token not found")}
} else if err != nil {
w.WriteHeader(500)
fmt.Fprintln(authboss.Cfg.LogWriter, "confirm: error retrieving user token:", err)
return
return err
}
ctx.User = authboss.Unbind(user)
@ -177,14 +158,17 @@ func (c *Confirm) confirmHandler(ctx *authboss.Context, w http.ResponseWriter, r
ctx.User[StoreConfirmToken] = ""
ctx.User[StoreConfirmed] = true
key, _ := ctx.User.String(authboss.StoreUsername)
ctx.SessionStorer.Put(authboss.SessionKey, key)
ctx.SessionStorer.Put(authboss.FlashSuccessKey, "Successfully confirmed your account.")
if err := ctx.SaveUser(); err != nil {
fmt.Fprintln(authboss.Cfg.LogWriter, "confirm: failed to clear the user's token:", err)
return
key, err := ctx.User.StringErr(authboss.StoreUsername)
if err != nil {
return err
}
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
if err := ctx.SaveUser(); err != nil {
return err
}
ctx.SessionStorer.Put(authboss.SessionKey, key)
render.Redirect(ctx, w, r, "/", "You have successfully confirmed your account.", "")
return nil
}

View File

@ -4,9 +4,11 @@ import (
"bytes"
"crypto/md5"
"encoding/base64"
"errors"
"html/template"
"net/http"
"net/http/httptest"
"reflect"
"strings"
"testing"
@ -67,24 +69,26 @@ func TestConfirm_BeforeGet(t *testing.T) {
c := setup()
ctx := authboss.NewContext()
if err := c.BeforeGet(ctx); err == nil {
t.Error("Should stop the get due to non-confirm.")
if _, err := c.BeforeGet(ctx); err == nil {
t.Error("Should stop the get due to attribute missing:", err)
}
ctx.User = authboss.Attributes{
StoreConfirmed: false,
}
if err := c.BeforeGet(ctx); err == nil {
t.Error("Should stop the get due to non-confirm.")
if interrupt, err := c.BeforeGet(ctx); interrupt != authboss.InterruptAccountNotConfirmed {
t.Error("Should stop the get due to non-confirm:", interrupt)
} else if err != nil {
t.Error(err)
}
ctx.User = authboss.Attributes{
StoreConfirmed: true,
}
if err := c.BeforeGet(ctx); err != nil {
t.Error(err)
if interrupt, err := c.BeforeGet(ctx); interrupt != authboss.InterruptNone || err != nil {
t.Error(interrupt, err)
}
}
@ -102,16 +106,13 @@ func TestConfirm_AfterRegister(t *testing.T) {
sentEmail = true
}
c.AfterRegister(ctx)
if str := log.String(); !strings.Contains(str, "user not loaded") {
t.Error("Expected it to die with loading error:", str)
if err := c.AfterRegister(ctx); err != errUserMissing {
t.Error("Expected it to die with user error:", err)
}
ctx.User = authboss.Attributes{authboss.StoreUsername: "uname"}
log.Reset()
c.AfterRegister(ctx)
if str := log.String(); !strings.Contains(str, "no e-mail address to send to") {
t.Error("Expected it to die with e-mail address error:", str)
if err := c.AfterRegister(ctx); err == nil || err.(authboss.AttributeErr).Name != "email" {
t.Error("Expected it to die with e-mail address error:", err)
}
ctx.User[authboss.StoreEmail] = "a@a.com"
@ -135,12 +136,15 @@ func TestConfirm_ConfirmHandlerErrors(t *testing.T) {
tests := []struct {
URL string
Confirmed bool
Redirect bool
Error string
Error error
}{
{"http://localhost", false, true, "no confirm token found in request"},
{"http://localhost?cnf=c$ats", false, true, "confirm token failed to decode"},
{"http://localhost?cnf=SGVsbG8sIHBsYXlncm91bmQ=", false, true, "token not found"},
{"http://localhost", false, authboss.ClientDataErr{FormValueConfirm}},
{"http://localhost?cnf=c$ats", false,
authboss.ErrAndRedirect{Endpoint: "/", Err: errors.New("confirm: token failed to decode \"c$ats\" => illegal base64 data at input byte 1\n")},
},
{"http://localhost?cnf=SGVsbG8sIHBsYXlncm91bmQ=", false,
authboss.ErrAndRedirect{Endpoint: "/", Err: errors.New(`confirm: token not found`)},
},
}
for i, test := range tests {
@ -148,23 +152,19 @@ func TestConfirm_ConfirmHandlerErrors(t *testing.T) {
w := httptest.NewRecorder()
ctx, _ := authboss.ContextFromRequest(r)
log.Reset()
c.confirmHandler(ctx, w, r)
err := c.confirmHandler(ctx, w, r)
if err == nil {
t.Fatal("%d) Expected an error", i)
}
if len(test.Error) != 0 {
if str := log.String(); !strings.Contains(str, test.Error) {
t.Errorf("%d) Expected: %q, got: %q", i, test.Error, str)
}
if !reflect.DeepEqual(err, test.Error) {
t.Errorf("Expected: %v, got: %v", test.Error, err)
}
is, ok := ctx.User.Bool(StoreConfirmed)
if ok && is {
t.Error("The user should not be confirmed.")
}
if test.Redirect && w.Code != http.StatusTemporaryRedirect {
t.Error("Expected a redirect, got:", w.Header)
}
}
}
@ -192,6 +192,7 @@ func TestConfirm_Confirm(t *testing.T) {
r, _ := http.NewRequest("GET", "http://localhost?cnf="+base64.URLEncoding.EncodeToString(token), nil)
w := httptest.NewRecorder()
ctx, _ = authboss.ContextFromRequest(r)
ctx.CookieStorer = mocks.NewMockClientStorer()
session := mocks.NewMockClientStorer()
ctx.User = user
ctx.SessionStorer = session
@ -218,7 +219,7 @@ func TestConfirm_Confirm(t *testing.T) {
if key, ok := ctx.SessionStorer.Get(authboss.SessionKey); !ok || len(key) == 0 {
t.Error("Should have logged the user in.")
}
if success, ok := ctx.SessionStorer.Get(authboss.FlashSuccessKey); !ok || len(success) == 0 {
if success, ok := ctx.CookieStorer.Get(authboss.FlashSuccessKey); !ok || len(success) == 0 {
t.Error("Should have left a nice message.")
}
}