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:
parent
1ca6eb1cc0
commit
94f441f3d7
@ -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
|
||||
}
|
||||
|
@ -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.")
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user