feat: send email based on template

This commit is contained in:
Lakhan Samani 2022-08-09 01:43:37 +05:30
parent 0714b4360b
commit 81fce1a471
20 changed files with 239 additions and 204 deletions

View File

@ -206,27 +206,27 @@ export enum webhookVerifiedStatus {
} }
export const emailTemplateVariables = { export const emailTemplateVariables = {
'user.id': '{user.id}}', 'user.id': '{.user.id}}',
'user.email': '{user.email}}', 'user.email': '{.user.email}}',
'user.given_name': '{user.given_name}}', 'user.given_name': '{.user.given_name}}',
'user.family_name': '{user.family_name}}', 'user.family_name': '{.user.family_name}}',
'user.signup_methods': '{user.signup_methods}}', 'user.signup_methods': '{.user.signup_methods}}',
'user.email_verified': '{user.email_verified}}', 'user.email_verified': '{.user.email_verified}}',
'user.picture': '{user.picture}}', 'user.picture': '{.user.picture}}',
'user.roles': '{user.roles}}', 'user.roles': '{.user.roles}}',
'user.middle_name': '{user.middle_name}}', 'user.middle_name': '{.user.middle_name}}',
'user.nickname': '{user.nickname}}', 'user.nickname': '{.user.nickname}}',
'user.preferred_username': '{user.preferred_username}}', 'user.preferred_username': '{.user.preferred_username}}',
'user.gender': '{user.gender}}', 'user.gender': '{.user.gender}}',
'user.birthdate': '{user.birthdate}}', 'user.birthdate': '{.user.birthdate}}',
'user.phone_number': '{user.phone_number}}', 'user.phone_number': '{.user.phone_number}}',
'user.phone_number_verified': '{user.phone_number_verified}}', 'user.phone_number_verified': '{.user.phone_number_verified}}',
'user.created_at': '{user.created_at}}', 'user.created_at': '{.user.created_at}}',
'user.updated_at': '{user.updated_at}}', 'user.updated_at': '{.user.updated_at}}',
'organization.name': '{organization.name}}', 'organization.name': '{.organization.name}}',
'organization.logo': '{organization.logo}}', 'organization.logo': '{.organization.logo}}',
verification_url: '{verification_url}}', verification_url: '{.verification_url}}',
otp: '{otp}}', otp: '{.otp}}',
}; };
export const webhookPayloadExample: string = `{ export const webhookPayloadExample: string = `{

View File

@ -9,4 +9,8 @@ const (
VerificationTypeUpdateEmail = "update_email" VerificationTypeUpdateEmail = "update_email"
// VerificationTypeForgotPassword is the forgot_password verification type // VerificationTypeForgotPassword is the forgot_password verification type
VerificationTypeForgotPassword = "forgot_password" VerificationTypeForgotPassword = "forgot_password"
// VerificationTypeInviteMember is the invite_member verification type
VerificationTypeInviteMember = "invite_member"
// VerificationTypeOTP is the otp verification type
VerificationTypeOTP = "otp"
) )

View File

@ -1,6 +1,7 @@
package models package models
import ( import (
"encoding/json"
"strings" "strings"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
@ -64,3 +65,10 @@ func (user *User) AsAPIUser() *model.User {
UpdatedAt: refs.NewInt64Ref(user.UpdatedAt), UpdatedAt: refs.NewInt64Ref(user.UpdatedAt),
} }
} }
func (user *User) ToMap() map[string]interface{} {
res := map[string]interface{}{}
data, _ := json.Marshal(user) // Convert to a json string
json.Unmarshal(data, &res) // Convert to a map
return res
}

View File

@ -2,8 +2,8 @@ package email
import ( import (
"bytes" "bytes"
"context"
"crypto/tls" "crypto/tls"
"encoding/json"
"strconv" "strconv"
"text/template" "text/template"
@ -11,27 +11,75 @@ import (
gomail "gopkg.in/mail.v2" gomail "gopkg.in/mail.v2"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
) )
// addEmailTemplate is used to add html template in email body func getDefaultTemplate(event string) *model.EmailTemplate {
func addEmailTemplate(a string, b map[string]interface{}, templateName string) string { switch event {
tmpl, err := template.New(templateName).Parse(a) case constants.VerificationTypeBasicAuthSignup, constants.VerificationTypeMagicLinkLogin:
if err != nil { return &model.EmailTemplate{
output, _ := json.Marshal(b) Subject: emailVerificationSubject,
return string(output) Template: emailVerificationTemplate,
}
case constants.VerificationTypeForgotPassword:
return &model.EmailTemplate{
Subject: forgotPasswordSubject,
Template: forgotPasswordTemplate,
}
case constants.VerificationTypeInviteMember:
return &model.EmailTemplate{
Subject: inviteEmailSubject,
Template: inviteEmailTemplate,
}
case constants.VerificationTypeOTP:
return &model.EmailTemplate{
Subject: otpEmailSubject,
Template: otpEmailTemplate,
}
default:
return nil
} }
buf := &bytes.Buffer{}
err = tmpl.Execute(buf, b)
if err != nil {
panic(err)
}
s := buf.String()
return s
} }
// SendMail function to send mail func getEmailTemplate(event string, data map[string]interface{}) (*model.EmailTemplate, error) {
func SendMail(to []string, Subject, bodyMessage string) error { ctx := context.Background()
tmp, err := db.Provider.GetEmailTemplateByEventName(ctx, event)
if err != nil || tmp == nil {
tmp = getDefaultTemplate(event)
}
templ, err := template.New(event + "_template.tmpl").Parse(tmp.Template)
if err != nil {
return nil, err
}
buf := &bytes.Buffer{}
err = templ.Execute(buf, data)
if err != nil {
return nil, err
}
templateString := buf.String()
subject, err := template.New(event + "_subject.tmpl").Parse(tmp.Subject)
if err != nil {
return nil, err
}
buf = &bytes.Buffer{}
err = subject.Execute(buf, data)
if err != nil {
return nil, err
}
subjectString := buf.String()
return &model.EmailTemplate{
Template: templateString,
Subject: subjectString,
}, nil
}
// SendEmail function to send mail
func SendEmail(to []string, event string, data map[string]interface{}) error {
// dont trigger email sending in case of test // dont trigger email sending in case of test
envKey, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyEnv) envKey, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyEnv)
if err != nil { if err != nil {
@ -40,6 +88,13 @@ func SendMail(to []string, Subject, bodyMessage string) error {
if envKey == constants.TestEnv { if envKey == constants.TestEnv {
return nil return nil
} }
tmp, err := getEmailTemplate(event, data)
if err != nil {
log.Errorf("Failed to get event template: ", err)
return err
}
m := gomail.NewMessage() m := gomail.NewMessage()
senderEmail, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeySenderEmail) senderEmail, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeySenderEmail)
if err != nil { if err != nil {
@ -79,8 +134,8 @@ func SendMail(to []string, Subject, bodyMessage string) error {
m.SetHeader("From", senderEmail) m.SetHeader("From", senderEmail)
m.SetHeader("To", to...) m.SetHeader("To", to...)
m.SetHeader("Subject", Subject) m.SetHeader("Subject", tmp.Subject)
m.SetBody("text/html", bodyMessage) m.SetBody("text/html", tmp.Template)
port, _ := strconv.Atoi(smtpPort) port, _ := strconv.Atoi(smtpPort)
d := gomail.NewDialer(smtpHost, port, smtpUsername, smtpPassword) d := gomail.NewDialer(smtpHost, port, smtpUsername, smtpPassword)
if !isProd { if !isProd {

View File

@ -1,19 +1,8 @@
package email package email
import ( const (
log "github.com/sirupsen/logrus" emailVerificationSubject = "Please verify your email"
emailVerificationTemplate = `
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore"
)
// SendVerificationMail to send verification email
func SendVerificationMail(toEmail, token, hostname string) error {
// The receiver needs to be in slice as the receive supports multiple receiver
Receiver := []string{toEmail}
Subject := "Please verify your email"
message := `
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head> <head>
@ -98,23 +87,4 @@ func SendVerificationMail(toEmail, token, hostname string) error {
</body> </body>
</html> </html>
` `
data := make(map[string]interface{}, 3) )
var err error
data["org_logo"], err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
if err != nil {
return err
}
data["org_name"], err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
if err != nil {
return err
}
data["verification_url"] = hostname + "/verify_email?token=" + token
message = addEmailTemplate(message, data, "verify_email.tmpl")
// bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)
err = SendMail(Receiver, Subject, message)
if err != nil {
log.Warn("error sending email: ", err)
}
return err
}

View File

@ -1,28 +1,8 @@
package email package email
import ( const (
"github.com/authorizerdev/authorizer/server/constants" forgotPasswordSubject = "Reset Password"
"github.com/authorizerdev/authorizer/server/memorystore" forgotPasswordTemplate = `
)
// SendForgotPasswordMail to send forgot password email
func SendForgotPasswordMail(toEmail, token, hostname string) error {
resetPasswordUrl, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyResetPasswordURL)
if err != nil {
return err
}
if resetPasswordUrl == "" {
if err := memorystore.Provider.UpdateEnvVariable(constants.EnvKeyResetPasswordURL, hostname+"/app/reset-password"); err != nil {
return err
}
}
// The receiver needs to be in slice as the receive supports multiple receiver
Receiver := []string{toEmail}
Subject := "Reset Password"
message := `
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head> <head>
@ -73,13 +53,13 @@ func SendForgotPasswordMail(toEmail, token, hostname string) error {
<table width="100%%" cellspacing="0" cellpadding="0"> <table width="100%%" cellspacing="0" cellpadding="0">
<tbody> <tbody>
<tr> <tr>
<td class="esd-block-image es-m-txt-c es-p5b" style="font-size:0;padding:10px" align="center"><a target="_blank" clicktracking="off"><img src="{{.org_logo}}" alt="icon" style="display: block;" title="icon" width="30"></a></td> <td class="esd-block-image es-m-txt-c es-p5b" style="font-size:0;padding:10px" align="center"><a target="_blank" clicktracking="off"><img src="{{.organization.logo}}" alt="icon" style="display: block;" title="icon" width="30"></a></td>
</tr> </tr>
<tr style="background: rgb(249,250,251);padding: 10px;margin-bottom:10px;border-radius:5px;"> <tr style="background: rgb(249,250,251);padding: 10px;margin-bottom:10px;border-radius:5px;">
<td class="esd-block-text es-m-txt-c es-p15t" align="center" style="padding:10px;padding-bottom:30px;"> <td class="esd-block-text es-m-txt-c es-p15t" align="center" style="padding:10px;padding-bottom:30px;">
<p>Hey there 👋</p> <p>Hey there 👋</p>
<p>We have received a request to reset password for email: <b>{{.org_name}}</b>. If this is correct, please reset the password clicking the button below.</p> <br/> <p>We have received a request to reset password for email: <b>{{.organization.name}}</b>. If this is correct, please reset the password clicking the button below.</p> <br/>
<a clicktracking="off" href="{{.verification_url}}" class="es-button" target="_blank" style="text-decoration: none;padding:10px 15px;background-color: rgba(59,130,246,1);color: #fff;font-size: 1em;border-radius:5px;">Reset Password</a> <a clicktracking="off" href="{{.verification_url}}" class="es-button" target="_blank" style="text-decoration: none;padding:10px 15px;background-color: rgba(59,130,246,1);color: #fff;font-size: 1em;border-radius:5px;">Reset Password</a>
</td> </td>
</tr> </tr>
@ -106,18 +86,4 @@ func SendForgotPasswordMail(toEmail, token, hostname string) error {
</body> </body>
</html> </html>
` `
)
data := make(map[string]interface{}, 3)
data["org_logo"], err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
if err != nil {
return err
}
data["org_name"], err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
if err != nil {
return err
}
data["verification_url"] = resetPasswordUrl + "?token=" + token
message = addEmailTemplate(message, data, "reset_password_email.tmpl")
return SendMail(Receiver, Subject, message)
}

View File

@ -1,19 +1,8 @@
package email package email
import ( const (
log "github.com/sirupsen/logrus" inviteEmailSubject = "Please accept the invitation"
inviteEmailTemplate = `
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore"
)
// InviteEmail to send invite email
func InviteEmail(toEmail, token, verificationURL, redirectURI string) error {
// The receiver needs to be in slice as the receive supports multiple receiver
Receiver := []string{toEmail}
Subject := "Please accept the invitation"
message := `
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head> <head>
@ -64,13 +53,13 @@ func InviteEmail(toEmail, token, verificationURL, redirectURI string) error {
<table width="100%%" cellspacing="0" cellpadding="0"> <table width="100%%" cellspacing="0" cellpadding="0">
<tbody> <tbody>
<tr> <tr>
<td class="esd-block-image es-m-txt-c es-p5b" style="font-size:0;padding:10px" align="center"><a target="_blank" clicktracking="off"><img src="{{.org_logo}}" alt="icon" style="display: block;" title="icon" width="30"></a></td> <td class="esd-block-image es-m-txt-c es-p5b" style="font-size:0;padding:10px" align="center"><a target="_blank" clicktracking="off"><img src="{{.organization.logo}}" alt="icon" style="display: block;" title="icon" width="30"></a></td>
</tr> </tr>
<tr style="background: rgb(249,250,251);padding: 10px;margin-bottom:10px;border-radius:5px;"> <tr style="background: rgb(249,250,251);padding: 10px;margin-bottom:10px;border-radius:5px;">
<td class="esd-block-text es-m-txt-c es-p15t" align="center" style="padding:10px;padding-bottom:30px;"> <td class="esd-block-text es-m-txt-c es-p15t" align="center" style="padding:10px;padding-bottom:30px;">
<p>Hi there 👋</p> <p>Hi there 👋</p>
<p>Join us! You are invited to sign-up for <b>{{.org_name}}</b>. Please accept the invitation by clicking the button below.</p> <br/> <p>Join us! You are invited to sign-up for <b>{{.organization.name}}</b>. Please accept the invitation by clicking the button below.</p> <br/>
<a <a
clicktracking="off" href="{{.verification_url}}" class="es-button" target="_blank" style="text-decoration: none;padding:10px 15px;background-color: rgba(59,130,246,1);color: #fff;font-size: 1em;border-radius:5px;">Get Started</a> clicktracking="off" href="{{.verification_url}}" class="es-button" target="_blank" style="text-decoration: none;padding:10px 15px;background-color: rgba(59,130,246,1);color: #fff;font-size: 1em;border-radius:5px;">Get Started</a>
</td> </td>
@ -98,23 +87,4 @@ func InviteEmail(toEmail, token, verificationURL, redirectURI string) error {
</body> </body>
</html> </html>
` `
data := make(map[string]interface{}, 3) )
var err error
data["org_logo"], err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
if err != nil {
return err
}
data["org_name"], err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
if err != nil {
return err
}
data["verification_url"] = verificationURL + "?token=" + token + "&redirect_uri=" + redirectURI
message = addEmailTemplate(message, data, "invite_email.tmpl")
// bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)
err = SendMail(Receiver, Subject, message)
if err != nil {
log.Warn("error sending email: ", err)
}
return err
}

View File

@ -1,19 +1,8 @@
package email package email
import ( const (
log "github.com/sirupsen/logrus" otpEmailSubject = "OTP for your multi factor authentication"
otpEmailTemplate = `
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore"
)
// SendOtpMail to send otp email
func SendOtpMail(toEmail, otp string) error {
// The receiver needs to be in slice as the receive supports multiple receiver
Receiver := []string{toEmail}
Subject := "OTP for your multi factor authentication"
message := `
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head> <head>
@ -64,13 +53,13 @@ func SendOtpMail(toEmail, otp string) error {
<table width="100%%" cellspacing="0" cellpadding="0"> <table width="100%%" cellspacing="0" cellpadding="0">
<tbody> <tbody>
<tr> <tr>
<td class="esd-block-image es-m-txt-c es-p5b" style="font-size:0;padding:10px" align="center"><a target="_blank" clicktracking="off"><img src="{{.org_logo}}" alt="icon" style="display: block;" title="icon" width="30"></a></td> <td class="esd-block-image es-m-txt-c es-p5b" style="font-size:0;padding:10px" align="center"><a target="_blank" clicktracking="off"><img src="{{.organization.logo}}" alt="icon" style="display: block;" title="icon" width="30"></a></td>
</tr> </tr>
<tr style="background: rgb(249,250,251);padding: 10px;margin-bottom:10px;border-radius:5px;"> <tr style="background: rgb(249,250,251);padding: 10px;margin-bottom:10px;border-radius:5px;">
<td class="esd-block-text es-m-txt-c es-p15t" align="center" style="padding:10px;padding-bottom:30px;"> <td class="esd-block-text es-m-txt-c es-p15t" align="center" style="padding:10px;padding-bottom:30px;">
<p>Hey there 👋</p> <p>Hey there 👋</p>
<b>{{.otp}}</b> is your one time password (OTP) for accessing {{.org_name}}. Please keep your OTP confidential and it will expire in 1 minute. <b>{{.otp}}</b> is your one time password (OTP) for accessing {{.organization.name}}. Please keep your OTP confidential and it will expire in 1 minute.
</td> </td>
</tr> </tr>
</tbody> </tbody>
@ -96,23 +85,4 @@ func SendOtpMail(toEmail, otp string) error {
</body> </body>
</html> </html>
` `
data := make(map[string]interface{}, 3) )
var err error
data["org_logo"], err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
if err != nil {
return err
}
data["org_name"], err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
if err != nil {
return err
}
data["otp"] = otp
message = addEmailTemplate(message, data, "otp.tmpl")
// bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)
err = SendMail(Receiver, Subject, message)
if err != nil {
log.Warn("error sending email: ", err)
}
return err
}

1
server/email/utils.go Normal file
View File

@ -0,0 +1 @@
package email

View File

@ -49,7 +49,7 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
log := log.WithFields(log.Fields{ log := log.WithFields(log.Fields{
"email": params.Email, "email": params.Email,
}) })
_, err = db.Provider.GetUserByEmail(ctx, params.Email) user, err := db.Provider.GetUserByEmail(ctx, params.Email)
if err != nil { if err != nil {
log.Debug("User not found: ", err) log.Debug("User not found: ", err)
return res, fmt.Errorf(`user with this email not found`) return res, fmt.Errorf(`user with this email not found`)
@ -84,8 +84,12 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
return res, err return res, err
} }
// exec it as go routin so that we can reduce the api latency // exec it as go routine so that we can reduce the api latency
go email.SendForgotPasswordMail(params.Email, verificationToken, hostname) go email.SendEmail([]string{params.Email}, constants.VerificationTypeForgotPassword, map[string]interface{}{
"user": user.ToMap(),
"organization": utils.GetOrganization(),
"verification_url": utils.GetForgotPasswordURL(verificationToken, hostname),
})
res = &model.Response{ res = &model.Response{
Message: `Please check your inbox! We have sent a password reset link.`, Message: `Please check your inbox! We have sent a password reset link.`,

View File

@ -115,7 +115,7 @@ func InviteMembersResolver(ctx context.Context, params model.InviteMemberInput)
return nil, err return nil, err
} }
verificationToken, err := token.CreateVerificationToken(email, constants.VerificationTypeForgotPassword, hostname, nonceHash, redirectURL) verificationToken, err := token.CreateVerificationToken(email, constants.VerificationTypeInviteMember, hostname, nonceHash, redirectURL)
if err != nil { if err != nil {
log.Debug("Failed to create verification token: ", err) log.Debug("Failed to create verification token: ", err)
} }
@ -135,7 +135,7 @@ func InviteMembersResolver(ctx context.Context, params model.InviteMemberInput)
} else { } else {
// use basic authentication if that option is on // use basic authentication if that option is on
user.SignupMethods = constants.AuthRecipeMethodBasicAuth user.SignupMethods = constants.AuthRecipeMethodBasicAuth
verificationRequest.Identifier = constants.VerificationTypeForgotPassword verificationRequest.Identifier = constants.VerificationTypeInviteMember
isMFAEnforced, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyEnforceMultiFactorAuthentication) isMFAEnforced, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyEnforceMultiFactorAuthentication)
if err != nil { if err != nil {
@ -162,7 +162,12 @@ func InviteMembersResolver(ctx context.Context, params model.InviteMemberInput)
return nil, err return nil, err
} }
go emailservice.InviteEmail(email, verificationToken, verifyEmailURL, redirectURL) // exec it as go routine so that we can reduce the api latency
go emailservice.SendEmail([]string{user.Email}, constants.VerificationTypeInviteMember, map[string]interface{}{
"user": user.ToMap(),
"organization": utils.GetOrganization(),
"verification_url": utils.GetInviteVerificationURL(verifyEmailURL, verificationToken, hostname),
})
} }
return &model.Response{ return &model.Response{

View File

@ -123,7 +123,12 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
} }
go func() { go func() {
err := email.SendOtpMail(user.Email, otpData.Otp) // exec it as go routine so that we can reduce the api latency
go email.SendEmail([]string{params.Email}, constants.VerificationTypeOTP, map[string]interface{}{
"user": user.ToMap(),
"organization": utils.GetOrganization(),
"otp": otpData.Otp,
})
if err != nil { if err != nil {
log.Debug("Failed to send otp email: ", err) log.Debug("Failed to send otp email: ", err)
} }

View File

@ -219,8 +219,12 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
return res, err return res, err
} }
// exec it as go routing so that we can reduce the api latency // exec it as go routine so that we can reduce the api latency
go email.SendVerificationMail(params.Email, verificationToken, hostname) go email.SendEmail([]string{params.Email}, constants.VerificationTypeMagicLinkLogin, map[string]interface{}{
"user": user.ToMap(),
"organization": utils.GetOrganization(),
"verification_url": utils.GetEmailVerificationURL(verificationToken, hostname),
})
} }
res = &model.Response{ res = &model.Response{

View File

@ -84,7 +84,12 @@ func ResendOTPResolver(ctx context.Context, params model.ResendOTPRequest) (*mod
} }
go func() { go func() {
err := email.SendOtpMail(params.Email, otp) // exec it as go routine so that we can reduce the api latency
go email.SendEmail([]string{params.Email}, constants.VerificationTypeOTP, map[string]interface{}{
"user": user.ToMap(),
"organization": utils.GetOrganization(),
"otp": otp,
})
if err != nil { if err != nil {
log.Debug("Error sending otp email: ", otp) log.Debug("Error sending otp email: ", otp)
} }

View File

@ -39,6 +39,11 @@ func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEma
return res, fmt.Errorf("invalid identifier") return res, fmt.Errorf("invalid identifier")
} }
user, err := db.Provider.GetUserByEmail(ctx, params.Email)
if err != nil {
return res, fmt.Errorf("invalid user")
}
verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, params.Email, params.Identifier) verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, params.Email, params.Identifier)
if err != nil { if err != nil {
log.Debug("Failed to get verification request: ", err) log.Debug("Failed to get verification request: ", err)
@ -74,8 +79,12 @@ func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEma
log.Debug("Failed to add verification request: ", err) log.Debug("Failed to add verification request: ", err)
} }
// exec it as go routin so that we can reduce the api latency // exec it as go routine so that we can reduce the api latency
go email.SendVerificationMail(params.Email, verificationToken, hostname) go email.SendEmail([]string{params.Email}, params.Identifier, map[string]interface{}{
"user": user.ToMap(),
"organization": utils.GetOrganization(),
"verification_url": utils.GetEmailVerificationURL(verificationToken, hostname),
})
res = &model.Response{ res = &model.Response{
Message: `Verification email has been sent. Please check your inbox`, Message: `Verification email has been sent. Please check your inbox`,

View File

@ -221,9 +221,14 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
return res, err return res, err
} }
// exec it as go routin so that we can reduce the api latency // exec it as go routine so that we can reduce the api latency
go func() { go func() {
email.SendVerificationMail(params.Email, verificationToken, hostname) // exec it as go routine so that we can reduce the api latency
email.SendEmail([]string{params.Email}, constants.VerificationTypeBasicAuthSignup, map[string]interface{}{
"user": user.ToMap(),
"organization": utils.GetOrganization(),
"verification_url": utils.GetEmailVerificationURL(verificationToken, hostname),
})
utils.RegisterEvent(ctx, constants.UserCreatedWebhookEvent, constants.AuthRecipeMethodBasicAuth, user) utils.RegisterEvent(ctx, constants.UserCreatedWebhookEvent, constants.AuthRecipeMethodBasicAuth, user)
}() }()

View File

@ -244,8 +244,12 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
return res, err return res, err
} }
// exec it as go routin so that we can reduce the api latency // exec it as go routine so that we can reduce the api latency
go email.SendVerificationMail(newEmail, verificationToken, hostname) go email.SendEmail([]string{user.Email}, constants.VerificationTypeBasicAuthSignup, map[string]interface{}{
"user": user.ToMap(),
"organization": utils.GetOrganization(),
"verification_url": utils.GetEmailVerificationURL(verificationToken, hostname),
})
} }
} }

View File

@ -156,8 +156,12 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
return res, err return res, err
} }
// exec it as go routin so that we can reduce the api latency // exec it as go routine so that we can reduce the api latency
go email.SendVerificationMail(newEmail, verificationToken, hostname) go email.SendEmail([]string{user.Email}, constants.VerificationTypeBasicAuthSignup, map[string]interface{}{
"user": user.ToMap(),
"organization": utils.GetOrganization(),
"verification_url": utils.GetEmailVerificationURL(verificationToken, hostname),
})
} }

View File

@ -2,6 +2,9 @@ package utils
import ( import (
"reflect" "reflect"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore"
) )
// StringSliceContains checks if a string slice contains a particular string // StringSliceContains checks if a string slice contains a particular string
@ -58,3 +61,46 @@ func ConvertInterfaceToStringSlice(slice interface{}) []string {
} }
return resSlice return resSlice
} }
// GetOrganization to get organization object
func GetOrganization() map[string]interface{} {
orgLogo, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
if err != nil {
return nil
}
orgName, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
if err != nil {
return nil
}
organization := map[string]interface{}{
"name": orgName,
"logo": orgLogo,
}
return organization
}
// GetForgotPasswordURL to get url for given token and hostname
func GetForgotPasswordURL(token, hostname string) string {
resetPasswordUrl, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyResetPasswordURL)
if err != nil {
return ""
}
if resetPasswordUrl == "" {
if err := memorystore.Provider.UpdateEnvVariable(constants.EnvKeyResetPasswordURL, hostname+"/app/reset-password"); err != nil {
return ""
}
}
verificationURL := resetPasswordUrl + "?token=" + token
return verificationURL
}
// GetInviteVerificationURL to get url for invite email verification
func GetInviteVerificationURL(verificationURL, token, redirectURI string) string {
return verificationURL + "?token=" + token + "&redirect_uri=" + redirectURI
}
// GetEmailVerificationURL to get url for invite email verification
func GetEmailVerificationURL(token, hostname string) string {
return hostname + "/verify_email?token=" + token
}

View File

@ -4,7 +4,7 @@ import "github.com/authorizerdev/authorizer/server/constants"
// IsValidEmailTemplateEventName function to validate email template events // IsValidEmailTemplateEventName function to validate email template events
func IsValidEmailTemplateEventName(eventName string) bool { func IsValidEmailTemplateEventName(eventName string) bool {
if eventName != constants.VerificationTypeBasicAuthSignup && eventName != constants.VerificationTypeForgotPassword && eventName != constants.VerificationTypeMagicLinkLogin && eventName != constants.VerificationTypeUpdateEmail { if eventName != constants.VerificationTypeBasicAuthSignup && eventName != constants.VerificationTypeForgotPassword && eventName != constants.VerificationTypeMagicLinkLogin && eventName != constants.VerificationTypeUpdateEmail && eventName != constants.VerificationTypeOTP {
return false return false
} }