mirror of
https://github.com/volatiletech/authboss.git
synced 2025-03-05 15:15:45 +02:00
Merge pull request #353 from viovanov/vlad/secondary-emails
support for secondary recovery emails emails
This commit is contained in:
commit
ccfe4d1c31
43
mocks/secondary_emails_mocks.go
Normal file
43
mocks/secondary_emails_mocks.go
Normal file
@ -0,0 +1,43 @@
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/volatiletech/authboss/v3"
|
||||
)
|
||||
|
||||
type UserWithSecondaryEmails struct {
|
||||
User
|
||||
SecondaryEmails []string
|
||||
}
|
||||
|
||||
// GetSecondaryEmails for the user
|
||||
func (u *UserWithSecondaryEmails) GetSecondaryEmails() []string {
|
||||
return u.SecondaryEmails
|
||||
}
|
||||
|
||||
type ServerStorerWithSecondaryEmails struct {
|
||||
BasicStorer *ServerStorer
|
||||
}
|
||||
|
||||
func (s ServerStorerWithSecondaryEmails) Load(ctx context.Context, key string) (authboss.User, error) {
|
||||
user, err := s.BasicStorer.Load(ctx, key)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
|
||||
mockedUser := user.(*User)
|
||||
|
||||
return &UserWithSecondaryEmails{
|
||||
User: *mockedUser,
|
||||
SecondaryEmails: []string{"personal@one.com", "personal@two.com"},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s ServerStorerWithSecondaryEmails) Save(ctx context.Context, user authboss.User) error {
|
||||
if u, ok := user.(*UserWithSecondaryEmails); ok {
|
||||
user = &u.User
|
||||
}
|
||||
|
||||
return s.BasicStorer.Save(ctx, user)
|
||||
}
|
@ -118,6 +118,8 @@ func (r *Recover) StartPost(w http.ResponseWriter, req *http.Request) error {
|
||||
return err
|
||||
}
|
||||
|
||||
ruWithSecondaries, hasSecondaryEmails := authboss.CanBeRecoverableUserWithSecondaryEmails(user)
|
||||
|
||||
ru.PutRecoverSelector(selector)
|
||||
ru.PutRecoverVerifier(verifier)
|
||||
ru.PutRecoverExpiry(time.Now().UTC().Add(r.Config.Modules.RecoverTokenDuration))
|
||||
@ -126,10 +128,16 @@ func (r *Recover) StartPost(w http.ResponseWriter, req *http.Request) error {
|
||||
return err
|
||||
}
|
||||
|
||||
recoveryEmailRecipients := []string{ru.GetEmail()}
|
||||
|
||||
if hasSecondaryEmails {
|
||||
recoveryEmailRecipients = append(recoveryEmailRecipients, ruWithSecondaries.GetSecondaryEmails()...)
|
||||
}
|
||||
|
||||
if r.Authboss.Modules.MailNoGoroutine {
|
||||
r.SendRecoverEmail(req.Context(), ru.GetEmail(), token)
|
||||
r.SendRecoverEmail(req.Context(), recoveryEmailRecipients, token)
|
||||
} else {
|
||||
go r.SendRecoverEmail(req.Context(), ru.GetEmail(), token)
|
||||
go r.SendRecoverEmail(req.Context(), recoveryEmailRecipients, token)
|
||||
}
|
||||
|
||||
_, err = r.Authboss.Events.FireAfter(authboss.EventRecoverStart, w, req)
|
||||
@ -148,13 +156,13 @@ func (r *Recover) StartPost(w http.ResponseWriter, req *http.Request) error {
|
||||
|
||||
// SendRecoverEmail to a specific e-mail address passing along the encodedToken
|
||||
// in an escaped URL to the templates.
|
||||
func (r *Recover) SendRecoverEmail(ctx context.Context, to, encodedToken string) {
|
||||
func (r *Recover) SendRecoverEmail(ctx context.Context, to []string, encodedToken string) {
|
||||
logger := r.Authboss.Logger(ctx)
|
||||
|
||||
mailURL := r.mailURL(encodedToken)
|
||||
|
||||
email := authboss.Email{
|
||||
To: []string{to},
|
||||
To: to,
|
||||
From: r.Authboss.Config.Mail.From,
|
||||
FromName: r.Authboss.Config.Mail.FromName,
|
||||
Subject: r.Authboss.Config.Mail.SubjectPrefix + "Password Reset",
|
||||
|
87
recover/recover_secondary_emails_test.go
Normal file
87
recover/recover_secondary_emails_test.go
Normal file
@ -0,0 +1,87 @@
|
||||
package recover
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/volatiletech/authboss/v3"
|
||||
"github.com/volatiletech/authboss/v3/mocks"
|
||||
)
|
||||
|
||||
func testSetupWithSecondaryEmails() *testHarness {
|
||||
harness := &testHarness{}
|
||||
|
||||
harness.ab = authboss.New()
|
||||
harness.bodyReader = &mocks.BodyReader{}
|
||||
harness.mailer = &mocks.Emailer{}
|
||||
harness.redirector = &mocks.Redirector{}
|
||||
harness.renderer = &mocks.Renderer{}
|
||||
harness.responder = &mocks.Responder{}
|
||||
harness.session = mocks.NewClientRW()
|
||||
harness.storer = mocks.NewServerStorer()
|
||||
|
||||
harness.ab.Paths.RecoverOK = "/recover/ok"
|
||||
harness.ab.Modules.MailNoGoroutine = true
|
||||
|
||||
harness.ab.Config.Core.BodyReader = harness.bodyReader
|
||||
harness.ab.Config.Core.Logger = mocks.Logger{}
|
||||
harness.ab.Config.Core.Mailer = harness.mailer
|
||||
harness.ab.Config.Core.Redirector = harness.redirector
|
||||
harness.ab.Config.Core.MailRenderer = harness.renderer
|
||||
harness.ab.Config.Core.Responder = harness.responder
|
||||
harness.ab.Config.Storage.SessionState = harness.session
|
||||
harness.ab.Config.Storage.Server = mocks.ServerStorerWithSecondaryEmails{
|
||||
BasicStorer: harness.storer,
|
||||
}
|
||||
|
||||
harness.recover = &Recover{harness.ab}
|
||||
|
||||
return harness
|
||||
}
|
||||
|
||||
func TestSecondaryEmails(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
h := testSetupWithSecondaryEmails()
|
||||
|
||||
h.bodyReader.Return = &mocks.Values{
|
||||
PID: "test@test.com",
|
||||
}
|
||||
h.storer.Users["test@test.com"] = &mocks.User{
|
||||
Email: "test@test.com",
|
||||
Password: "i can't recall, doesn't seem like something bcrypted though",
|
||||
}
|
||||
|
||||
r := mocks.Request("GET")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
if err := h.recover.StartPost(w, r); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if w.Code != http.StatusTemporaryRedirect {
|
||||
t.Error("code was wrong:", w.Code)
|
||||
}
|
||||
if h.redirector.Options.RedirectPath != h.ab.Config.Paths.RecoverOK {
|
||||
t.Error("page was wrong:", h.responder.Page)
|
||||
}
|
||||
if len(h.redirector.Options.Success) == 0 {
|
||||
t.Error("expected a nice success message")
|
||||
}
|
||||
|
||||
if h.mailer.Email.To[0] != "test@test.com" {
|
||||
t.Error("e-mail to address is wrong:", h.mailer.Email.To)
|
||||
}
|
||||
if !strings.HasSuffix(h.mailer.Email.Subject, "Password Reset") {
|
||||
t.Error("e-mail subject line is wrong:", h.mailer.Email.Subject)
|
||||
}
|
||||
if len(h.renderer.Data[DataRecoverURL].(string)) == 0 {
|
||||
t.Errorf("the renderer's url in data was missing: %#v", h.renderer.Data)
|
||||
}
|
||||
|
||||
if len(h.mailer.Email.To) != 3 {
|
||||
t.Errorf("should have sent 3 e-mails out, but sent %d", len(h.mailer.Email.To))
|
||||
}
|
||||
}
|
13
user.go
13
user.go
@ -73,6 +73,12 @@ type RecoverableUser interface {
|
||||
PutRecoverExpiry(expiry time.Time)
|
||||
}
|
||||
|
||||
type RecoverableUserWithSecondaryEmails interface {
|
||||
RecoverableUser
|
||||
|
||||
GetSecondaryEmails() (secondaryEmails []string)
|
||||
}
|
||||
|
||||
// ArbitraryUser allows arbitrary data from the web form through. You should
|
||||
// definitely only pull the keys you want from the map, since this is unfiltered
|
||||
// input from a web request and is an attack vector.
|
||||
@ -142,6 +148,13 @@ func MustBeRecoverable(u User) RecoverableUser {
|
||||
panic(fmt.Sprintf("could not upgrade user to a recoverable user, given type: %T", u))
|
||||
}
|
||||
|
||||
func CanBeRecoverableUserWithSecondaryEmails(u User) (RecoverableUserWithSecondaryEmails, bool) {
|
||||
if lu, ok := u.(RecoverableUserWithSecondaryEmails); ok {
|
||||
return lu, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// MustBeOAuthable forces an upgrade to an OAuth2User or panic.
|
||||
func MustBeOAuthable(u User) OAuth2User {
|
||||
if ou, ok := u.(OAuth2User); ok {
|
||||
|
Loading…
x
Reference in New Issue
Block a user