From 6d565c2cd2128ef52646eee2d4f9afc812cf7ccb Mon Sep 17 00:00:00 2001 From: Ramanan Ravikumar <38394463+ramanan-ravi@users.noreply.github.com> Date: Thu, 26 Oct 2023 14:09:10 +0530 Subject: [PATCH] HTML email using templates #1258 (#1690) --- .gitignore | 2 +- deepfence_server/handler/user.go | 42 +++++++++- deepfence_server/pkg/sendemail/templates.go | 49 ++++++++++- .../pkg/sendemail/templates/invite-user.html | 84 +++++++++++++++++++ .../sendemail/templates/reset-password.html | 82 ++++++++++++++++++ deepfence_utils/utils/constants.go | 4 + 6 files changed, 256 insertions(+), 7 deletions(-) create mode 100644 deepfence_server/pkg/sendemail/templates/invite-user.html create mode 100644 deepfence_server/pkg/sendemail/templates/reset-password.html diff --git a/.gitignore b/.gitignore index 589e089787..6fa433dc48 100644 --- a/.gitignore +++ b/.gitignore @@ -54,7 +54,7 @@ deepfence_ctl/vendor go.work* -pkg/ +./pkg/ deepfence_agent/tools/apache/scope/docker/deepfenceutil deepfence_agent/tools/apache/scope/docker/deepfence_exe deepfence_agent/tools/apache/scope/docker/deepfence diff --git a/deepfence_server/handler/user.go b/deepfence_server/handler/user.go index 60460e7ad4..dbbfb7f575 100644 --- a/deepfence_server/handler/user.go +++ b/deepfence_server/handler/user.go @@ -349,14 +349,31 @@ func (h *Handler) InviteUser(w http.ResponseWriter, r *http.Request) { h.respondError(err, w) return } + + htmlEmail, err := sendemail.RenderEmailTemplate( + sendemail.UserInviteTemplate, + sendemail.UserInvite{ + Project: utils.Project, + Username: "", + RequestedBy: fmt.Sprintf("%s %s (%s)", user.FirstName, user.LastName, user.Email), + InviteLink: inviteURL, + }, + ) + if err != nil { + log.Error().Err(err).Msg("error rendering UserInviteTemplate") + h.respondError(err, w) + return + } + err = emailSender.Send( []string{inviteUserRequest.Email}, - "Deepfence - Invitation to join ThreatMapper", - fmt.Sprintf(sendemail.UserInviteEmail, user.FirstName, user.LastName, user.Email, inviteURL), + fmt.Sprintf(sendemail.UserInviteEmailSubject, utils.Project), "", + htmlEmail, nil, ) if err != nil { + log.Error().Err(err).Msg("error sending user invite email") h.respondError(err, w) return } @@ -665,15 +682,32 @@ func (h *Handler) ResetPasswordRequest(w http.ResponseWriter, r *http.Request) { return } resetPasswordURL := fmt.Sprintf("%s/auth/reset-password?code=%s&namespace=%s", consoleUrl, resetCode, user.CompanyNamespace) + + htmlEmail, err := sendemail.RenderEmailTemplate( + sendemail.PasswordResetTemplate, + sendemail.PasswordReset{ + Project: utils.Project, + Username: user.FirstName + " " + user.LastName, + InviteLink: resetPasswordURL, + }, + ) + if err != nil { + pgClient.DeletePasswordResetByUserEmail(ctx, user.Email) + log.Error().Err(err).Msg("error rendering PasswordResetTemplate") + h.respondError(err, w) + return + } + err = emailSender.Send( []string{resetPasswordRequest.Email}, - "Deepfence - Password Reset", - fmt.Sprintf(sendemail.PasswordResetEmail, user.FirstName, user.LastName, resetPasswordURL, 10), + sendemail.PasswordResetEmailSubject, "", + htmlEmail, nil, ) if err != nil { pgClient.DeletePasswordResetByUserEmail(ctx, user.Email) + log.Error().Err(err).Msg("error sending password reset email") h.respondError(err, w) return } diff --git a/deepfence_server/pkg/sendemail/templates.go b/deepfence_server/pkg/sendemail/templates.go index 7059fdd285..ce658185b3 100644 --- a/deepfence_server/pkg/sendemail/templates.go +++ b/deepfence_server/pkg/sendemail/templates.go @@ -1,7 +1,52 @@ package sendemail +import ( + "bytes" + "embed" + _ "embed" + "html/template" + + "github.com/Masterminds/sprig/v3" +) + +type EmailTemplateType string + +const ( + PasswordResetTemplate EmailTemplateType = "reset-password.html" + UserInviteTemplate EmailTemplateType = "invite-user.html" +) + +const ( + PasswordResetEmailSubject = "Deepfence - Reset Password" + UserInviteEmailSubject = "Deepfence - Invitation to join %s" +) + var ( - PasswordResetEmail = "Hello %s %s, \n\nPlease click here to reset your Deepfence ThreatMapper password: %s (Link valid for %d minutes)\n" + //go:embed templates/*.html + emailTemplatesContent embed.FS - UserInviteEmail = "Hello, \n\nYou have been invited to join Deepfence ThreatMapper by %s %s (%s). \nPlease click here to register: %s\n" + EmailTemplates = template.Must( + template.New("").Funcs(sprig.FuncMap()).ParseFS(emailTemplatesContent, []string{"templates/*.html"}...)) ) + +type PasswordReset struct { + Project string + Username string + InviteLink string +} + +type UserInvite struct { + Project string + Username string + RequestedBy string + InviteLink string +} + +func RenderEmailTemplate(emailTemplateType EmailTemplateType, data interface{}) (string, error) { + var rendered bytes.Buffer + err := EmailTemplates.ExecuteTemplate(&rendered, string(emailTemplateType), data) + if err != nil { + return "", err + } + return rendered.String(), nil +} diff --git a/deepfence_server/pkg/sendemail/templates/invite-user.html b/deepfence_server/pkg/sendemail/templates/invite-user.html new file mode 100644 index 0000000000..0b469a94a8 --- /dev/null +++ b/deepfence_server/pkg/sendemail/templates/invite-user.html @@ -0,0 +1,84 @@ + + +
+ + + + + +
+
Hello + {{.Username}}, +You have been + invited to join Deepfence {{.Project}} by {{.RequestedBy}} ({{.RequestedByEmail}}). + +
or copy and paste + this URL into your browser: {{.InviteLink}} + |
+
+
Hello + {{.Username}}, +If you wish to + reset your Deepfence {{.Project}} password, click the button below: +
or copy and paste + this URL into your browser: {{.InviteLink}} + |
+