2015-08-18 05:57:27 +05:30
package email
import (
"net/url"
"time"
"github.com/coreos/go-oidc/jose"
"github.com/coreos/dex/email"
"github.com/coreos/dex/pkg/log"
"github.com/coreos/dex/user"
)
// UserEmailer provides functions for sending emails to Users.
type UserEmailer struct {
ur user . UserRepo
pwi user . PasswordInfoRepo
signerFn signerFunc
tokenValidityWindow time . Duration
issuerURL url . URL
emailer * email . TemplatizedEmailer
fromAddress string
passwordResetURL url . URL
verifyEmailURL url . URL
2015-11-10 04:05:11 +05:30
invitationURL url . URL
2015-08-18 05:57:27 +05:30
}
// NewUserEmailer creates a new UserEmailer.
func NewUserEmailer ( ur user . UserRepo ,
pwi user . PasswordInfoRepo ,
signerFn signerFunc ,
tokenValidityWindow time . Duration ,
issuerURL url . URL ,
emailer * email . TemplatizedEmailer ,
fromAddress string ,
passwordResetURL url . URL ,
verifyEmailURL url . URL ,
2015-11-10 04:05:11 +05:30
invitationURL url . URL ,
2015-08-18 05:57:27 +05:30
) * UserEmailer {
return & UserEmailer {
ur : ur ,
pwi : pwi ,
signerFn : signerFn ,
tokenValidityWindow : tokenValidityWindow ,
issuerURL : issuerURL ,
emailer : emailer ,
fromAddress : fromAddress ,
passwordResetURL : passwordResetURL ,
verifyEmailURL : verifyEmailURL ,
2015-11-10 04:05:11 +05:30
invitationURL : invitationURL ,
2015-08-18 05:57:27 +05:30
}
}
2015-11-10 04:05:11 +05:30
func ( u * UserEmailer ) userPasswordInfo ( email string ) ( user . User , user . PasswordInfo , error ) {
2015-08-18 05:57:27 +05:30
usr , err := u . ur . GetByEmail ( nil , email )
if err != nil {
log . Errorf ( "Error getting user: %q" , err )
2015-11-10 04:05:11 +05:30
return user . User { } , user . PasswordInfo { } , err
2015-08-18 05:57:27 +05:30
}
pwi , err := u . pwi . Get ( nil , usr . ID )
if err != nil {
log . Errorf ( "Error getting password: %q" , err )
2015-11-10 04:05:11 +05:30
return user . User { } , user . PasswordInfo { } , err
2015-08-18 05:57:27 +05:30
}
2015-11-10 04:05:11 +05:30
return usr , pwi , nil
}
func ( u * UserEmailer ) signedClaimsToken ( claims jose . Claims ) ( string , error ) {
2015-08-18 05:57:27 +05:30
signer , err := u . signerFn ( )
2015-10-17 03:17:58 +05:30
if err != nil || signer == nil {
log . Errorf ( "error getting signer: %v (%v)" , err , signer )
2015-11-10 04:05:11 +05:30
return "" , err
}
jwt , err := jose . NewSignedJWT ( claims , signer )
if err != nil {
log . Errorf ( "error constructing or signing a JWT: %v" , err )
return "" , err
}
return jwt . Encode ( ) , nil
}
// SendResetPasswordEmail sends a password reset email to the user specified by the email addresss, containing a link with a signed token which can be visitied to initiate the password change/reset process.
// This method DOES NOT check for client ID, redirect URL validity - it is expected that upstream users have already done so.
// A link that can be used to reset the given user's password is returned.
func ( u * UserEmailer ) SendResetPasswordEmail ( email string , redirectURL url . URL , clientID string ) ( * url . URL , error ) {
usr , pwi , err := u . userPasswordInfo ( email )
if err != nil {
2015-08-18 05:57:27 +05:30
return nil , err
}
2015-11-10 04:05:11 +05:30
passwordReset := user . NewPasswordReset ( usr . ID , pwi . Password , u . issuerURL ,
2015-08-18 05:57:27 +05:30
clientID , redirectURL , u . tokenValidityWindow )
2015-11-10 04:05:11 +05:30
token , err := u . signedClaimsToken ( passwordReset . Claims )
2015-08-18 05:57:27 +05:30
if err != nil {
return nil , err
}
resetURL := u . passwordResetURL
q := resetURL . Query ( )
q . Set ( "token" , token )
resetURL . RawQuery = q . Encode ( )
2015-11-10 04:05:11 +05:30
if u . emailer != nil {
err = u . emailer . SendMail ( u . fromAddress , "Reset Your Password" , "password-reset" ,
map [ string ] interface { } {
"email" : usr . Email ,
"link" : resetURL . String ( ) ,
} , usr . Email )
if err != nil {
log . Errorf ( "error sending password reset email %v: " , err )
}
return nil , err
}
return & resetURL , nil
}
// SendInviteEmail is sends an email that allows the user to both
// reset their password *and* verify their email address. Similar to
// SendResetPasswordEmail, the given url and client id are assumed
// valid. A link that can be used to validate the given email address
// and reset the password is returned.
func ( u * UserEmailer ) SendInviteEmail ( email string , redirectURL url . URL , clientID string ) ( * url . URL , error ) {
usr , pwi , err := u . userPasswordInfo ( email )
if err != nil {
return nil , err
}
invitation := user . NewInvitation ( usr , pwi . Password , u . issuerURL ,
clientID , redirectURL , u . tokenValidityWindow )
token , err := u . signedClaimsToken ( invitation . Claims )
if err != nil {
return nil , err
2015-10-31 03:11:00 +05:30
}
2015-11-10 04:05:11 +05:30
resetURL := u . invitationURL
q := resetURL . Query ( )
q . Set ( "token" , token )
resetURL . RawQuery = q . Encode ( )
2015-08-18 05:57:27 +05:30
if u . emailer != nil {
2015-11-10 04:05:11 +05:30
err = u . emailer . SendMail ( u . fromAddress , "Activate Your Account" , "invite" ,
2015-08-18 05:57:27 +05:30
map [ string ] interface { } {
"email" : usr . Email ,
"link" : resetURL . String ( ) ,
} , usr . Email )
if err != nil {
log . Errorf ( "error sending password reset email %v: " , err )
}
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.
// If there is no emailer is configured, the URL of the aforementioned link is returned, otherwise nil is returned.
func ( u * UserEmailer ) SendEmailVerification ( userID , clientID string , redirectURL url . URL ) ( * url . URL , error ) {
usr , err := u . ur . Get ( nil , userID )
if err == user . ErrorNotFound {
log . Errorf ( "No Such user for ID: %q" , userID )
return nil , err
}
if err != nil {
log . Errorf ( "Error getting user: %q" , err )
return nil , err
}
ev := user . NewEmailVerification ( usr , clientID , u . issuerURL , redirectURL , u . tokenValidityWindow )
signer , err := u . signerFn ( )
2015-10-17 03:17:58 +05:30
if err != nil || signer == nil {
log . Errorf ( "error getting signer: %v (signer: %v)" , err , signer )
2015-08-18 05:57:27 +05:30
return nil , err
}
2015-10-17 03:17:58 +05:30
jwt , err := jose . NewSignedJWT ( ev . Claims , signer )
2015-08-18 05:57:27 +05:30
if err != nil {
2015-10-17 03:17:58 +05:30
log . Errorf ( "error constructing or signing EmailVerification JWT: %v" , err )
2015-08-18 05:57:27 +05:30
return nil , err
}
2015-10-17 03:17:58 +05:30
token := jwt . Encode ( )
2015-08-18 05:57:27 +05:30
verifyURL := u . verifyEmailURL
q := verifyURL . Query ( )
q . Set ( "token" , token )
verifyURL . RawQuery = q . Encode ( )
if u . emailer != nil {
err = u . emailer . SendMail ( u . fromAddress , "Please verify your email address." , "verify-email" ,
map [ string ] interface { } {
"email" : usr . Email ,
"link" : verifyURL . String ( ) ,
} , usr . Email )
if err != nil {
log . Errorf ( "error sending email verification email %v: " , err )
}
return nil , err
}
return & verifyURL , nil
}
func ( u * UserEmailer ) SetEmailer ( emailer * email . TemplatizedEmailer ) {
u . emailer = emailer
}
type signerFunc func ( ) ( jose . Signer , error )