diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index cb425b7..728cf33 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -2798,7 +2798,8 @@ "@chakra-ui/css-reset": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@chakra-ui/css-reset/-/css-reset-1.1.1.tgz", - "integrity": "sha512-+KNNHL4OWqeKia5SL858K3Qbd8WxMij9mWIilBzLD4j2KFrl/+aWFw8syMKth3NmgIibrjsljo+PU3fy2o50dg==" + "integrity": "sha512-+KNNHL4OWqeKia5SL858K3Qbd8WxMij9mWIilBzLD4j2KFrl/+aWFw8syMKth3NmgIibrjsljo+PU3fy2o50dg==", + "requires": {} }, "@chakra-ui/descendant": { "version": "2.1.1", @@ -3402,7 +3403,8 @@ "@graphql-typed-document-node/core": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.1.1.tgz", - "integrity": "sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg==" + "integrity": "sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg==", + "requires": {} }, "@popperjs/core": { "version": "2.11.0", @@ -3739,7 +3741,8 @@ "draft-js-utils": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/draft-js-utils/-/draft-js-utils-1.4.1.tgz", - "integrity": "sha512-xE81Y+z/muC5D5z9qWmKfxEW1XyXfsBzSbSBk2JRsoD0yzMGGHQm/0MtuqHl/EUDkaBJJLjJ2EACycoDMY/OOg==" + "integrity": "sha512-xE81Y+z/muC5D5z9qWmKfxEW1XyXfsBzSbSBk2JRsoD0yzMGGHQm/0MtuqHl/EUDkaBJJLjJ2EACycoDMY/OOg==", + "requires": {} }, "draftjs-to-html": { "version": "0.9.1", @@ -3749,7 +3752,8 @@ "draftjs-utils": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/draftjs-utils/-/draftjs-utils-0.10.2.tgz", - "integrity": "sha512-EstHqr3R3JVcilJrBaO/A+01GvwwKmC7e4TCjC7S94ZeMh4IVmf60OuQXtHHpwItK8C2JCi3iljgN5KHkJboUg==" + "integrity": "sha512-EstHqr3R3JVcilJrBaO/A+01GvwwKmC7e4TCjC7S94ZeMh4IVmf60OuQXtHHpwItK8C2JCi3iljgN5KHkJboUg==", + "requires": {} }, "error-ex": { "version": "1.3.2", @@ -4043,7 +4047,8 @@ "html-to-draftjs": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/html-to-draftjs/-/html-to-draftjs-1.5.0.tgz", - "integrity": "sha512-kggLXBNciKDwKf+KYsuE+V5gw4dZ7nHyGMX9m0wy7urzWjKGWyNFetmArRLvRV0VrxKN70WylFsJvMTJx02OBQ==" + "integrity": "sha512-kggLXBNciKDwKf+KYsuE+V5gw4dZ7nHyGMX9m0wy7urzWjKGWyNFetmArRLvRV0VrxKN70WylFsJvMTJx02OBQ==", + "requires": {} }, "immutable": { "version": "3.7.6", @@ -4272,7 +4277,8 @@ "react-icons": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.3.1.tgz", - "integrity": "sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ==" + "integrity": "sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ==", + "requires": {} }, "react-is": { "version": "16.13.1", @@ -4483,7 +4489,8 @@ "use-callback-ref": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.2.5.tgz", - "integrity": "sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg==" + "integrity": "sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg==", + "requires": {} }, "use-sidecar": { "version": "1.0.5", diff --git a/dashboard/src/components/UpdateEmailTemplateModal.tsx b/dashboard/src/components/UpdateEmailTemplateModal.tsx index 5deee15..1157c09 100644 --- a/dashboard/src/components/UpdateEmailTemplateModal.tsx +++ b/dashboard/src/components/UpdateEmailTemplateModal.tsx @@ -62,8 +62,7 @@ interface validatorDataType { } const initTemplateData: emailTemplateDataType = { - [EmailTemplateInputDataFields.EVENT_NAME]: - emailTemplateEventNames.BASIC_AUTH_SIGNUP, + [EmailTemplateInputDataFields.EVENT_NAME]: emailTemplateEventNames.Signup, [EmailTemplateInputDataFields.SUBJECT]: '', }; @@ -206,10 +205,10 @@ const UpdateEmailTemplate = ({ ).reduce((acc, varData): any => { if ( (templateData[EmailTemplateInputDataFields.EVENT_NAME] !== - emailTemplateEventNames.VERIFY_OTP && + emailTemplateEventNames['Verify Otp'] && varData[1] === emailTemplateVariables.otp) || (templateData[EmailTemplateInputDataFields.EVENT_NAME] === - emailTemplateEventNames.VERIFY_OTP && + emailTemplateEventNames['Verify Otp'] && varData[1] === emailTemplateVariables.verification_url) ) { return acc; diff --git a/dashboard/src/components/UpdateWebhookModal.tsx b/dashboard/src/components/UpdateWebhookModal.tsx index 9749ef5..a9cf128 100644 --- a/dashboard/src/components/UpdateWebhookModal.tsx +++ b/dashboard/src/components/UpdateWebhookModal.tsx @@ -94,7 +94,7 @@ interface validatorDataType { } const initWebhookData: webhookDataType = { - [WebhookInputDataFields.EVENT_NAME]: webhookEventNames.USER_LOGIN, + [WebhookInputDataFields.EVENT_NAME]: webhookEventNames['User login'], [WebhookInputDataFields.ENDPOINT]: '', [WebhookInputDataFields.ENABLED]: true, [WebhookInputDataFields.HEADERS]: [{ ...initHeadersData }], diff --git a/dashboard/src/constants.ts b/dashboard/src/constants.ts index 79b6c26..1998785 100644 --- a/dashboard/src/constants.ts +++ b/dashboard/src/constants.ts @@ -183,20 +183,21 @@ export enum UpdateModalViews { export const pageLimits: number[] = [5, 10, 15]; export const webhookEventNames = { - USER_SIGNUP: 'user.signup', - USER_CREATED: 'user.created', - USER_LOGIN: 'user.login', - USER_DELETED: 'user.deleted', - USER_ACCESS_ENABLED: 'user.access_enabled', - USER_ACCESS_REVOKED: 'user.access_revoked', + 'User signup': 'user.signup', + 'User created': 'user.created', + 'User login': 'user.login', + 'User deleted': 'user.deleted', + 'User access enabled': 'user.access_enabled', + 'User access revoked': 'user.access_revoked', }; export const emailTemplateEventNames = { - BASIC_AUTH_SIGNUP: 'basic_auth_signup', - MAGIC_LINK_LOGIN: 'magic_link_login', - UPDATE_EMAIL: 'update_email', - FORGOT_PASSWORD: 'forgot_password', - VERIFY_OTP: 'verify_otp', + Signup: 'basic_auth_signup', + 'Magic Link Login': 'magic_link_login', + 'Update Email': 'update_email', + 'Forgot Password': 'forgot_password', + 'Verify Otp': 'verify_otp', + 'Invite member': 'invite_member', }; export enum webhookVerifiedStatus { @@ -206,27 +207,27 @@ export enum webhookVerifiedStatus { } export const emailTemplateVariables = { - 'user.id': '{user.id}}', - 'user.email': '{user.email}}', - 'user.given_name': '{user.given_name}}', - 'user.family_name': '{user.family_name}}', - 'user.signup_methods': '{user.signup_methods}}', - 'user.email_verified': '{user.email_verified}}', - 'user.picture': '{user.picture}}', - 'user.roles': '{user.roles}}', - 'user.middle_name': '{user.middle_name}}', - 'user.nickname': '{user.nickname}}', - 'user.preferred_username': '{user.preferred_username}}', - 'user.gender': '{user.gender}}', - 'user.birthdate': '{user.birthdate}}', - 'user.phone_number': '{user.phone_number}}', - 'user.phone_number_verified': '{user.phone_number_verified}}', - 'user.created_at': '{user.created_at}}', - 'user.updated_at': '{user.updated_at}}', - 'organization.name': '{organization.name}}', - 'organization.logo': '{organization.logo}}', - verification_url: '{verification_url}}', - otp: '{otp}}', + 'user.id': '{.user.id}}', + 'user.email': '{.user.email}}', + 'user.given_name': '{.user.given_name}}', + 'user.family_name': '{.user.family_name}}', + 'user.signup_methods': '{.user.signup_methods}}', + 'user.email_verified': '{.user.email_verified}}', + 'user.picture': '{.user.picture}}', + 'user.roles': '{.user.roles}}', + 'user.middle_name': '{.user.middle_name}}', + 'user.nickname': '{.user.nickname}}', + 'user.preferred_username': '{.user.preferred_username}}', + 'user.gender': '{.user.gender}}', + 'user.birthdate': '{.user.birthdate}}', + 'user.phone_number': '{.user.phone_number}}', + 'user.phone_number_verified': '{.user.phone_number_verified}}', + 'user.created_at': '{.user.created_at}}', + 'user.updated_at': '{.user.updated_at}}', + 'organization.name': '{.organization.name}}', + 'organization.logo': '{.organization.logo}}', + verification_url: '{.verification_url}}', + otp: '{.otp}}', }; export const webhookPayloadExample: string = `{ diff --git a/dashboard/src/graphql/queries/index.ts b/dashboard/src/graphql/queries/index.ts index 06fe1fd..146748b 100644 --- a/dashboard/src/graphql/queries/index.ts +++ b/dashboard/src/graphql/queries/index.ts @@ -126,7 +126,7 @@ export const WebhooksDataQuery = ` export const EmailTemplatesQuery = ` query getEmailTemplates($params: PaginatedInput!) { _email_templates(params: $params) { - EmailTemplates { + email_templates { id event_name subject diff --git a/dashboard/src/pages/EmailTemplates.tsx b/dashboard/src/pages/EmailTemplates.tsx index 9058003..83bea54 100644 --- a/dashboard/src/pages/EmailTemplates.tsx +++ b/dashboard/src/pages/EmailTemplates.tsx @@ -40,7 +40,7 @@ import { UpdateModalViews, EmailTemplateInputDataFields, } from '../constants'; -import { EmailTemplatesQuery, WebhooksDataQuery } from '../graphql/queries'; +import { EmailTemplatesQuery } from '../graphql/queries'; import dayjs from 'dayjs'; import DeleteEmailTemplateModal from '../components/DeleteEmailTemplateModal'; @@ -94,7 +94,7 @@ const EmailTemplates = () => { }) .toPromise(); if (res.data?._email_templates) { - const { pagination, EmailTemplates: emailTemplates } = + const { pagination, email_templates: emailTemplates } = res.data?._email_templates; const maxPages = getMaxPages(pagination); if (emailTemplates?.length) { diff --git a/server/constants/verification_types.go b/server/constants/verification_types.go index de64dbb..0a83df8 100644 --- a/server/constants/verification_types.go +++ b/server/constants/verification_types.go @@ -9,4 +9,8 @@ const ( VerificationTypeUpdateEmail = "update_email" // VerificationTypeForgotPassword is the forgot_password verification type VerificationTypeForgotPassword = "forgot_password" + // VerificationTypeInviteMember is the invite_member verification type + VerificationTypeInviteMember = "invite_member" + // VerificationTypeOTP is the otp verification type + VerificationTypeOTP = "otp" ) diff --git a/server/db/models/user.go b/server/db/models/user.go index bc5bc04..f8a054d 100644 --- a/server/db/models/user.go +++ b/server/db/models/user.go @@ -1,6 +1,7 @@ package models import ( + "encoding/json" "strings" "github.com/authorizerdev/authorizer/server/graph/model" @@ -64,3 +65,10 @@ func (user *User) AsAPIUser() *model.User { 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 +} diff --git a/server/email/email.go b/server/email/email.go index 99b2c03..730ae56 100644 --- a/server/email/email.go +++ b/server/email/email.go @@ -2,8 +2,8 @@ package email import ( "bytes" + "context" "crypto/tls" - "encoding/json" "strconv" "text/template" @@ -11,27 +11,75 @@ import ( gomail "gopkg.in/mail.v2" "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" ) -// addEmailTemplate is used to add html template in email body -func addEmailTemplate(a string, b map[string]interface{}, templateName string) string { - tmpl, err := template.New(templateName).Parse(a) - if err != nil { - output, _ := json.Marshal(b) - return string(output) +func getDefaultTemplate(event string) *model.EmailTemplate { + switch event { + case constants.VerificationTypeBasicAuthSignup, constants.VerificationTypeMagicLinkLogin: + return &model.EmailTemplate{ + Subject: emailVerificationSubject, + 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 SendMail(to []string, Subject, bodyMessage string) error { +func getEmailTemplate(event string, data map[string]interface{}) (*model.EmailTemplate, 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 envKey, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyEnv) if err != nil { @@ -40,6 +88,13 @@ func SendMail(to []string, Subject, bodyMessage string) error { if envKey == constants.TestEnv { return nil } + + tmp, err := getEmailTemplate(event, data) + if err != nil { + log.Errorf("Failed to get event template: ", err) + return err + } + m := gomail.NewMessage() senderEmail, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeySenderEmail) if err != nil { @@ -79,8 +134,8 @@ func SendMail(to []string, Subject, bodyMessage string) error { m.SetHeader("From", senderEmail) m.SetHeader("To", to...) - m.SetHeader("Subject", Subject) - m.SetBody("text/html", bodyMessage) + m.SetHeader("Subject", tmp.Subject) + m.SetBody("text/html", tmp.Template) port, _ := strconv.Atoi(smtpPort) d := gomail.NewDialer(smtpHost, port, smtpUsername, smtpPassword) if !isProd { diff --git a/server/email/verification_email.go b/server/email/email_verification.go similarity index 85% rename from server/email/verification_email.go rename to server/email/email_verification.go index dded5ef..51a99bc 100644 --- a/server/email/verification_email.go +++ b/server/email/email_verification.go @@ -1,19 +1,8 @@ package email -import ( - log "github.com/sirupsen/logrus" - - "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 := ` +const ( + emailVerificationSubject = "Please verify your email" + emailVerificationTemplate = `
@@ -98,23 +87,4 @@ func SendVerificationMail(toEmail, token, hostname string) error { ` - 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 -} +) diff --git a/server/email/forgot_password_email.go b/server/email/forgot_password_email.go index aabd6a9..678f83c 100644 --- a/server/email/forgot_password_email.go +++ b/server/email/forgot_password_email.go @@ -1,28 +1,8 @@ package email -import ( - "github.com/authorizerdev/authorizer/server/constants" - "github.com/authorizerdev/authorizer/server/memorystore" -) - -// 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 := ` +const ( + forgotPasswordSubject = "Reset Password" + forgotPasswordTemplate = ` @@ -73,13 +53,13 @@ func SendForgotPasswordMail(toEmail, token, hostname string) error {
Hey there 👋 -We have received a request to reset password for email: {{.org_name}}. If this is correct, please reset the password clicking the button below. + We have received a request to reset password for email: {{.organization.name}}. If this is correct, please reset the password clicking the button below. Reset Password |
Hi there 👋 -Join us! You are invited to sign-up for {{.org_name}}. Please accept the invitation by clicking the button below. + Join us! You are invited to sign-up for {{.organization.name}}. Please accept the invitation by clicking the button below. Get Started |
@@ -98,23 +87,4 @@ func InviteEmail(toEmail, token, verificationURL, redirectURI string) error {
`
- 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
-}
+)
diff --git a/server/email/otp.go b/server/email/otp.go
index 181a1e0..d3bf7c0 100644
--- a/server/email/otp.go
+++ b/server/email/otp.go
@@ -1,19 +1,8 @@
package email
-import (
- log "github.com/sirupsen/logrus"
-
- "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 := `
+const (
+ otpEmailSubject = "OTP for your multi factor authentication"
+ otpEmailTemplate = `
@@ -64,13 +53,13 @@ func SendOtpMail(toEmail, otp string) error {