user: introduce "invite" emails

Invite emails are essentially just reset password emails with a
different template (though this can and probably will change (slightly)
in the near future)
This commit is contained in:
Bobby Rullo 2015-10-30 14:41:00 -07:00
parent 9172f54fc2
commit 2ef1b4beff
7 changed files with 63 additions and 4 deletions

View file

@ -519,6 +519,7 @@ func TestCreateUser(t *testing.T) {
cantEmail: tt.cantEmail,
lastEmail: tt.req.User.Email,
lastClientID: "XXX",
lastWasInvite: true,
lastRedirectURL: *urlParsed,
}
if diff := pretty.Compare(wantEmalier, f.emailer); diff != "" {
@ -578,6 +579,7 @@ type testEmailer struct {
lastEmail string
lastClientID string
lastRedirectURL url.URL
lastWasInvite bool
}
// SendResetPasswordEmail returns resetPasswordURL when it can't email, mimicking the behavior of the real UserEmailer.
@ -585,6 +587,20 @@ func (t *testEmailer) SendResetPasswordEmail(email string, redirectURL url.URL,
t.lastEmail = email
t.lastRedirectURL = redirectURL
t.lastClientID = clientID
t.lastWasInvite = false
var retURL *url.URL
if t.cantEmail {
retURL = &testResetPasswordURL
}
return retURL, nil
}
func (t *testEmailer) SendInviteEmail(email string, redirectURL url.URL, clientID string) (*url.URL, error) {
t.lastEmail = email
t.lastRedirectURL = redirectURL
t.lastClientID = clientID
t.lastWasInvite = true
var retURL *url.URL
if t.cantEmail {

View file

@ -102,7 +102,7 @@ func TestSendResetPasswordEmailHandler(t *testing.T) {
wantEmailer: &testEmailer{
to: str("Email-1@example.com"),
from: "noreply@example.com",
subject: "Reset your password.",
subject: "Reset Your Password",
},
wantPRUserID: "ID-1",
wantPRRedirect: &testRedirectURL,
@ -138,7 +138,7 @@ func TestSendResetPasswordEmailHandler(t *testing.T) {
wantEmailer: &testEmailer{
to: str("Email-1@example.com"),
from: "noreply@example.com",
subject: "Reset your password.",
subject: "Reset Your Password",
},
wantPRPassword: "password",
wantPRUserID: "ID-1",

7
static/email/invite.html Normal file
View file

@ -0,0 +1,7 @@
<html>
<body>
Welcome to Dex! Click below to set your password:
<a href="{{ .link }}">Set Password</a>
</body>
</html>

4
static/email/invite.txt Normal file
View file

@ -0,0 +1,4 @@
Welcome to Dex! Click below to set your password:
Link:
{{ .link }}

View file

@ -89,6 +89,7 @@ type UsersAPI struct {
type Emailer interface {
SendResetPasswordEmail(string, url.URL, string) (*url.URL, error)
SendInviteEmail(string, url.URL, string) (*url.URL, error)
}
type Creds struct {
@ -169,7 +170,7 @@ func (u *UsersAPI) CreateUser(creds Creds, usr schema.User, redirURL url.URL) (s
usr = userToSchemaUser(userUser)
url, err := u.emailer.SendResetPasswordEmail(usr.Email, validRedirURL, creds.ClientID)
url, err := u.emailer.SendInviteEmail(usr.Email, validRedirURL, creds.ClientID)
// An email is sent only if we don't get a link and there's no error.
emailSent := err == nil && url == nil

View file

@ -20,13 +20,23 @@ type testEmailer struct {
lastEmail string
lastClientID string
lastRedirectURL url.URL
lastWasInvite bool
}
// SendResetPasswordEmail returns resetPasswordURL when it can't email, mimicking the behavior of the real UserEmailer.
func (t *testEmailer) SendResetPasswordEmail(email string, redirectURL url.URL, clientID string) (*url.URL, error) {
return t.sendEmail(email, redirectURL, clientID, false)
}
func (t *testEmailer) SendInviteEmail(email string, redirectURL url.URL, clientID string) (*url.URL, error) {
return t.sendEmail(email, redirectURL, clientID, true)
}
func (t *testEmailer) sendEmail(email string, redirectURL url.URL, clientID string, invite bool) (*url.URL, error) {
t.lastEmail = email
t.lastRedirectURL = redirectURL
t.lastClientID = clientID
t.lastWasInvite = invite
var retURL *url.URL
if t.cantEmail {
@ -369,6 +379,7 @@ func TestCreateUser(t *testing.T) {
lastEmail: tt.usr.Email,
lastClientID: tt.creds.ClientID,
lastRedirectURL: tt.redirURL,
lastWasInvite: true,
}
if diff := pretty.Compare(wantEmalier, emailer); diff != "" {
t.Errorf("case %d: Compare(want, got) = %v", i,

View file

@ -53,6 +53,16 @@ func NewUserEmailer(ur user.UserRepo,
// This method DOES NOT check for client ID, redirect URL validity - it is expected that upstream users have already done so.
// If there is no emailer is configured, the URL of the aforementioned link is returned, otherwise nil is returned.
func (u *UserEmailer) SendResetPasswordEmail(email string, redirectURL url.URL, clientID string) (*url.URL, error) {
return u.sendResetPasswordOrInviteEmail(email, redirectURL, clientID, false)
}
// SendInviteEmail is exactly the same as SendResetPasswordEmail, except that it uses the invite template and subject name.
// In the near future, invite emails might diverge further.
func (u *UserEmailer) SendInviteEmail(email string, redirectURL url.URL, clientID string) (*url.URL, error) {
return u.sendResetPasswordOrInviteEmail(email, redirectURL, clientID, true)
}
func (u *UserEmailer) sendResetPasswordOrInviteEmail(email string, redirectURL url.URL, clientID string, invite bool) (*url.URL, error) {
usr, err := u.ur.GetByEmail(nil, email)
if err == user.ErrorNotFound {
log.Errorf("No Such user for email: %q", email)
@ -95,8 +105,17 @@ func (u *UserEmailer) SendResetPasswordEmail(email string, redirectURL url.URL,
q.Set("token", token)
resetURL.RawQuery = q.Encode()
var tmplName, subj string
if invite {
tmplName = "invite"
subj = "Activate Your Account"
} else {
tmplName = "password-reset"
subj = "Reset Your Password"
}
if u.emailer != nil {
err = u.emailer.SendMail(u.fromAddress, "Reset your password.", "password-reset",
err = u.emailer.SendMail(u.fromAddress, subj, tmplName,
map[string]interface{}{
"email": usr.Email,
"link": resetURL.String(),
@ -107,6 +126,7 @@ func (u *UserEmailer) SendResetPasswordEmail(email string, redirectURL url.URL,
return nil, err
}
return &resetURL, nil
}
// SendEmailVerification sends an email to the user with the given userID containing a link which when visited marks the user as having had their email verified.