From 06fd7105eae41b096fed0bc569d8941986f71c85 Mon Sep 17 00:00:00 2001 From: James Elliott Date: Thu, 7 Apr 2022 13:05:20 +1000 Subject: [PATCH] refactor(templates): utilize more accurate naming (#3125) --- docs/configuration/notifier/index.md | 49 +- examples/templates/notifications/README.md | 4 + .../html_email_with_button_and_link.html | 430 ++++++++++++++++++ internal/configuration/validator/notifier.go | 24 +- .../handlers/handler_reset_password_step2.go | 12 +- internal/middlewares/identity_verification.go | 18 +- internal/templates/const.go | 4 +- ...go => email_identity_verification_html.go} | 32 +- .../email_identity_verification_plaintext.go | 28 ++ ...step_2.go => email_password_reset_html.go} | 19 +- .../email_password_reset_plaintext.go | 26 ++ internal/templates/plaintext_email_step_1.go | 28 -- internal/templates/plaintext_email_step_2.go | 26 -- 13 files changed, 564 insertions(+), 136 deletions(-) create mode 100644 examples/templates/notifications/README.md create mode 100644 examples/templates/notifications/html_email_with_button_and_link.html rename internal/templates/{html_email_step_1.go => email_identity_verification_html.go} (90%) create mode 100644 internal/templates/email_identity_verification_plaintext.go rename internal/templates/{html_email_step_2.go => email_password_reset_html.go} (95%) create mode 100644 internal/templates/email_password_reset_plaintext.go delete mode 100644 internal/templates/plaintext_email_step_1.go delete mode 100644 internal/templates/plaintext_email_step_2.go diff --git a/docs/configuration/notifier/index.md b/docs/configuration/notifier/index.md index f48a2d702..df6a37488 100644 --- a/docs/configuration/notifier/index.md +++ b/docs/configuration/notifier/index.md @@ -47,41 +47,46 @@ required: no {: .label .label-config .label-green } -This option allows the administrator to set custom templates for notifications -the templates folder should contain the following files +This option allows the administrator to set a path where custom templates for notifications can be found. Each template +has two extensions; `.html` for HTML templates, and `.txt` for plaintext templates. -|File |Description | -|------------------------|---------------------------------------------------| -|PasswordResetStep1.html |HTML Template for Step 1 of password reset process | -|PasswordResetStep1.txt |Text Template for Step 1 of password reset process | -|PasswordResetStep2.html |HTML Template for Step 2 of password reset process | -|PasswordResetStep2.txt |Text Template for Step 2 of password reset process | +| Template | Description | +|:--------------------:|:-----------------------------------------------------------------------------------------------:| +| IdentityVerification | Template used when registering devices or resetting passwords | +| PasswordReset | Template used to send the notification to users when their password has successfully been reset | -Note: -* if you don't define some of these files, a default template is used for that notification +For example, to modify the `IdentityVerification` HTML template, if your `template_path` was `/config/email_templates`, +you would create the `/config/email_templates/IdentityVerification.html` file. + +_**Note:** you may configure this directory and add only add the templates you wish to override, any templates not +supplied in this folder will utilize the default templates._ In template files, you can use the following variables: -|File |Description | -|------------------------|---------------------------------------------------| -|`{{.title}}`| A predefined title for the email.
It will be `"Reset your password"` or `"Password changed successfully"`, depending on the current step | -|`{{.url}}` | The url that allows to reset the user password | -|`{{.displayName}}` |The name of the user, i.e. `John Doe` | -|`{{.button}}` |The content for the password reset button, it's hardcoded to `Reset` | -|`{{.remoteIP}}` |The remote IP address that initiated the request or event | +| Placeholder | Templates | Description | +|:--------------------:|:--------------------:|:---------------------------------------------------------------------------------------------------------------------------------------------:| +| `{{ .LinkURL }}` | IdentityVerification | The URL of the used with the IdentityVerification template. | +| `{{ .LinkText }}` | IdentityVerification | The display value for the IdentityVerification button intended for the link. | +| `{{ .Title }}` | All | A predefined title for the email.
It will be `"Reset your password"` or `"Password changed successfully"`, depending on the current step | +| `{{ .DisplayName }}` | All | The name of the user, i.e. `John Doe` | +| `{{ .RemoteIP }}` | All | The remote IP address that initiated the request or event | -#### Example +#### Examples + +This is a basic example: ```html -

{{.title}}

- Hi {{.displayName}}
- This email has been sent to you in order to validate your identity - Click here to change your password +

{{ .Title }}

+ Hi {{ .DisplayName }}
+ This email has been sent to you in order to validate your identity. + Click here to change your password. ``` +Some Additional examples for specific purposes can be found in the +[examples directory on GitHub](https://github.com/authelia/authelia/tree/master/examples/templates/notifications). ### filesystem diff --git a/examples/templates/notifications/README.md b/examples/templates/notifications/README.md new file mode 100644 index 000000000..115332a51 --- /dev/null +++ b/examples/templates/notifications/README.md @@ -0,0 +1,4 @@ +# Notification Templates + +The following folder contains email templates as examples of what can be done with the email template system. See the +[docs](https://www.authelia.com/docs/configuration/notifier/#template_path) for more information. \ No newline at end of file diff --git a/examples/templates/notifications/html_email_with_button_and_link.html b/examples/templates/notifications/html_email_with_button_and_link.html new file mode 100644 index 000000000..e5f764486 --- /dev/null +++ b/examples/templates/notifications/html_email_with_button_and_link.html @@ -0,0 +1,430 @@ + + + + + + + Authelia + + + + + + + + + + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + +
  +
+ + + + + + + +
+

{{ .Title }}

+
+ +
  +
+
+
+ + + + + + + + +
+ + + + + + +
 
+
+ + + + + + + + +
+ + + + + + +
+ + + + + + + + + + + +
  +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Hi {{ .DisplayName }} +
+ This email has been sent to you in order to validate your identity. + If you did not initiate the process your credentials might have been compromised. You should reset your password and contact an administrator. +
+  
+ {{ .LinkText }} +
+  
+ {{ .LinkURL }} +
+
+
+
+ + + + + + + + +
+ + + + + + + + + + + + +
 
 
 
+
+ + + + + + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ Please contact an administrator if you did not initiate this process. +
+  
+ This email was generated by a request from the IP address {{ .RemoteIP }}. +
+
+
+ + + + \ No newline at end of file diff --git a/internal/configuration/validator/notifier.go b/internal/configuration/validator/notifier.go index 45216fa92..56810a60d 100644 --- a/internal/configuration/validator/notifier.go +++ b/internal/configuration/validator/notifier.go @@ -56,28 +56,28 @@ func validateNotifierTemplates(config *schema.NotifierConfiguration, validator * return } - if t, err = template.ParseFiles(filepath.Join(config.TemplatePath, templates.TemplateNameStep1+".html")); err == nil { - templates.HTMLEmailTemplateStep1 = t + if t, err = template.ParseFiles(filepath.Join(config.TemplatePath, templates.TemplateNameIdentityVerification+".html")); err == nil { + templates.EmailIdentityVerificationHTML = t } else { - validator.PushWarning(fmt.Errorf(errFmtNotifierTemplateLoad, templates.TemplateNameStep1+".html", err)) + validator.PushWarning(fmt.Errorf(errFmtNotifierTemplateLoad, templates.TemplateNameIdentityVerification+".html", err)) } - if t, err = template.ParseFiles(filepath.Join(config.TemplatePath, templates.TemplateNameStep1+".txt")); err == nil { - templates.PlainTextEmailTemplateStep1 = t + if t, err = template.ParseFiles(filepath.Join(config.TemplatePath, templates.TemplateNameIdentityVerification+".txt")); err == nil { + templates.EmailIdentityVerificationPlainText = t } else { - validator.PushWarning(fmt.Errorf(errFmtNotifierTemplateLoad, templates.TemplateNameStep1+".txt", err)) + validator.PushWarning(fmt.Errorf(errFmtNotifierTemplateLoad, templates.TemplateNameIdentityVerification+".txt", err)) } - if t, err = template.ParseFiles(filepath.Join(config.TemplatePath, templates.TemplateNameStep2+".html")); err == nil { - templates.HTMLEmailTemplateStep2 = t + if t, err = template.ParseFiles(filepath.Join(config.TemplatePath, templates.TemplateNameBasic+".html")); err == nil { + templates.EmailPasswordResetHTML = t } else { - validator.PushWarning(fmt.Errorf(errFmtNotifierTemplateLoad, templates.TemplateNameStep2+".html", err)) + validator.PushWarning(fmt.Errorf(errFmtNotifierTemplateLoad, templates.TemplateNameBasic+".html", err)) } - if t, err = template.ParseFiles(filepath.Join(config.TemplatePath, templates.TemplateNameStep2+".txt")); err == nil { - templates.PlainTextEmailTemplateStep2 = t + if t, err = template.ParseFiles(filepath.Join(config.TemplatePath, templates.TemplateNameBasic+".txt")); err == nil { + templates.EmailPasswordResetPlainText = t } else { - validator.PushWarning(fmt.Errorf(errFmtNotifierTemplateLoad, templates.TemplateNameStep2+".txt", err)) + validator.PushWarning(fmt.Errorf(errFmtNotifierTemplateLoad, templates.TemplateNameBasic+".txt", err)) } } diff --git a/internal/handlers/handler_reset_password_step2.go b/internal/handlers/handler_reset_password_step2.go index 19e3d5c89..7950f7b07 100644 --- a/internal/handlers/handler_reset_password_step2.go +++ b/internal/handlers/handler_reset_password_step2.go @@ -86,12 +86,12 @@ func ResetPasswordPost(ctx *middlewares.AutheliaCtx) { if !disableHTML { htmlParams := map[string]interface{}{ - "title": "Password changed successfully", - "displayName": userInfo.DisplayName, - "remoteIP": ctx.RemoteIP().String(), + "Title": "Password changed successfully", + "DisplayName": userInfo.DisplayName, + "RemoteIP": ctx.RemoteIP().String(), } - err = templates.HTMLEmailTemplateStep2.Execute(bufHTML, htmlParams) + err = templates.EmailPasswordResetHTML.Execute(bufHTML, htmlParams) if err != nil { ctx.Logger.Error(err) @@ -103,10 +103,10 @@ func ResetPasswordPost(ctx *middlewares.AutheliaCtx) { bufText := new(bytes.Buffer) textParams := map[string]interface{}{ - "displayName": userInfo.DisplayName, + "DisplayName": userInfo.DisplayName, } - err = templates.PlainTextEmailTemplateStep2.Execute(bufText, textParams) + err = templates.EmailPasswordResetPlainText.Execute(bufText, textParams) if err != nil { ctx.Logger.Error(err) diff --git a/internal/middlewares/identity_verification.go b/internal/middlewares/identity_verification.go index 13d8f3498..db6c7076d 100644 --- a/internal/middlewares/identity_verification.go +++ b/internal/middlewares/identity_verification.go @@ -79,14 +79,14 @@ func IdentityVerificationStart(args IdentityVerificationStartArgs, delayFunc Tim if !disableHTML { htmlParams := map[string]interface{}{ - "title": args.MailTitle, - "url": link, - "button": args.MailButtonContent, - "displayName": identity.DisplayName, - "remoteIP": ctx.RemoteIP().String(), + "Title": args.MailTitle, + "LinkURL": link, + "LinkText": args.MailButtonContent, + "DisplayName": identity.DisplayName, + "RemoteIP": ctx.RemoteIP().String(), } - err = templates.HTMLEmailTemplateStep1.Execute(bufHTML, htmlParams) + err = templates.EmailIdentityVerificationHTML.Execute(bufHTML, htmlParams) if err != nil { ctx.Error(err, messageOperationFailed) @@ -96,11 +96,11 @@ func IdentityVerificationStart(args IdentityVerificationStartArgs, delayFunc Tim bufText := new(bytes.Buffer) textParams := map[string]interface{}{ - "url": link, - "displayName": identity.DisplayName, + "LinkURL": link, + "DisplayName": identity.DisplayName, } - err = templates.PlainTextEmailTemplateStep1.Execute(bufText, textParams) + err = templates.EmailIdentityVerificationPlainText.Execute(bufText, textParams) if err != nil { ctx.Error(err, messageOperationFailed) diff --git a/internal/templates/const.go b/internal/templates/const.go index edaea45b2..4c15720ce 100644 --- a/internal/templates/const.go +++ b/internal/templates/const.go @@ -2,6 +2,6 @@ package templates // Template File Names. const ( - TemplateNameStep1 = "PasswordResetStep1" - TemplateNameStep2 = "PasswordResetStep2" + TemplateNameBasic = "Basic" + TemplateNameIdentityVerification = "IdentityVerification" ) diff --git a/internal/templates/html_email_step_1.go b/internal/templates/email_identity_verification_html.go similarity index 90% rename from internal/templates/html_email_step_1.go rename to internal/templates/email_identity_verification_html.go index ea18fe180..01aac0f33 100644 --- a/internal/templates/html_email_step_1.go +++ b/internal/templates/email_identity_verification_html.go @@ -4,19 +4,19 @@ import ( "text/template" ) -// HTMLEmailTemplateStep1 the template of email that the user will receive for identity verification. -var HTMLEmailTemplateStep1 *template.Template +// EmailIdentityVerificationHTML the template of email that the user will receive for identity verification. +var EmailIdentityVerificationHTML *template.Template func init() { - t, err := template.New("html_email_template").Parse(emailHTMLContentStep1) + t, err := template.New("email_identity_verification_html").Parse(emailContentIdentityVerificationHTML) if err != nil { panic(err) } - HTMLEmailTemplateStep1 = t + EmailIdentityVerificationHTML = t } -const emailHTMLContentStep1 = ` +const emailContentIdentityVerificationHTML = ` @@ -244,7 +244,7 @@ const emailHTMLContentStep1 = ` -

{{.title}}

+

{{ .Title }}

@@ -317,7 +317,7 @@ const emailHTMLContentStep1 = ` - Hi {{.displayName}} + Hi {{ .DisplayName }} @@ -339,19 +339,7 @@ const emailHTMLContentStep1 = ` - {{.button}} - - - - - -   - - - - - {{.url}} + {{ .LinkText }} @@ -412,7 +400,7 @@ const emailHTMLContentStep1 = ` - Please contact an administrator if you did not initiate the process. + Please contact an administrator if you did not initiate this process. @@ -425,7 +413,7 @@ const emailHTMLContentStep1 = ` - This email was generated by some with the IP address {{.remoteIP}}. + This email was generated by a request from the IP address {{ .RemoteIP }}. diff --git a/internal/templates/email_identity_verification_plaintext.go b/internal/templates/email_identity_verification_plaintext.go new file mode 100644 index 000000000..5af344a41 --- /dev/null +++ b/internal/templates/email_identity_verification_plaintext.go @@ -0,0 +1,28 @@ +package templates + +import ( + "text/template" +) + +// EmailIdentityVerificationPlainText the template of email that the user will receive for identity verification. +var EmailIdentityVerificationPlainText *template.Template + +func init() { + t, err := template.New("email_identity_verification_plain_text").Parse(emailContentIdentityVerificationPlainText) + if err != nil { + panic(err) + } + + EmailIdentityVerificationPlainText = t +} + +const emailContentIdentityVerificationPlainText = ` +This email has been sent to you in order to validate your identity. +If you did not initiate the process your credentials might have been compromised. You should reset your password and contact an administrator. + +To setup your 2FA please visit the following URL: {{ .LinkURL }} + +This email was generated by a user with the IP {{ .RemoteIP }}. + +Please contact an administrator if you did not initiate this process. +` diff --git a/internal/templates/html_email_step_2.go b/internal/templates/email_password_reset_html.go similarity index 95% rename from internal/templates/html_email_step_2.go rename to internal/templates/email_password_reset_html.go index ba3e4b5bd..f6f2d5c81 100644 --- a/internal/templates/html_email_step_2.go +++ b/internal/templates/email_password_reset_html.go @@ -4,19 +4,20 @@ import ( "text/template" ) -// HTMLEmailTemplateStep2 the template of email that the user will receive for identity verification. -var HTMLEmailTemplateStep2 *template.Template +// EmailPasswordResetHTML the template of email that the user will receive for identity verification. +var EmailPasswordResetHTML *template.Template func init() { - t, err := template.New("html_email_template").Parse(emailHTMLContentStep2) + t, err := template.New("email_password_reset_html").Parse(emailContentPasswordResetHTML) if err != nil { panic(err) } - HTMLEmailTemplateStep2 = t + EmailPasswordResetHTML = t } -const emailHTMLContentStep2 = ` +//nolint:gosec // This is a template not hardcoded credentials. +const emailContentPasswordResetHTML = ` @@ -242,7 +243,7 @@ const emailHTMLContentStep2 = ` -

{{.title}}

+

{{ .Title }}

@@ -315,7 +316,7 @@ const emailHTMLContentStep2 = ` - Hi {{.displayName}}
+ Hi {{ .DisplayName }}
Your password has been successfully reset. If you did not initiate the process your credentials might have been compromised. You should reset your password and contact an administrator. @@ -385,7 +386,7 @@ const emailHTMLContentStep2 = ` - Please contact an administrator if you did not initiate the process. + Please contact an administrator if you did not initiate this process. @@ -398,7 +399,7 @@ const emailHTMLContentStep2 = ` - This email was generated by some with the IP address {{.remoteIP}}. + This email was generated by a request from the IP address {{ .RemoteIP }}. diff --git a/internal/templates/email_password_reset_plaintext.go b/internal/templates/email_password_reset_plaintext.go new file mode 100644 index 000000000..0f4b0078a --- /dev/null +++ b/internal/templates/email_password_reset_plaintext.go @@ -0,0 +1,26 @@ +package templates + +import ( + "text/template" +) + +// EmailPasswordResetPlainText the template of email that the user will receive for identity verification. +var EmailPasswordResetPlainText *template.Template + +func init() { + t, err := template.New("email_password_reset_plain_text").Parse(emailContentBasicPlainText) + if err != nil { + panic(err) + } + + EmailPasswordResetPlainText = t +} + +const emailContentBasicPlainText = ` +Your password has been successfully reset. +If you did not initiate the process your credentials might have been compromised. You should reset your password and contact an administrator. + +This email was generated by a user with the IP {{ .RemoteIP }}. + +Please contact an administrator if you did not initiate this process. +` diff --git a/internal/templates/plaintext_email_step_1.go b/internal/templates/plaintext_email_step_1.go deleted file mode 100644 index 22ffbd6bc..000000000 --- a/internal/templates/plaintext_email_step_1.go +++ /dev/null @@ -1,28 +0,0 @@ -package templates - -import ( - "text/template" -) - -// PlainTextEmailTemplateStep1 the template of email that the user will receive for identity verification. -var PlainTextEmailTemplateStep1 *template.Template - -func init() { - t, err := template.New("text_email_template").Parse(emailPlainTextContentStep1) - if err != nil { - panic(err) - } - - PlainTextEmailTemplateStep1 = t -} - -const emailPlainTextContentStep1 = ` -This email has been sent to you in order to validate your identity. -If you did not initiate the process your credentials might have been compromised. You should reset your password and contact an administrator. - -To setup your 2FA please visit the following URL: {{.url}} - -This email was generated by a user with the IP {{.remoteIP}}. - -Please contact an administrator if you did not initiate the process. -` diff --git a/internal/templates/plaintext_email_step_2.go b/internal/templates/plaintext_email_step_2.go deleted file mode 100644 index c378d81cf..000000000 --- a/internal/templates/plaintext_email_step_2.go +++ /dev/null @@ -1,26 +0,0 @@ -package templates - -import ( - "text/template" -) - -// PlainTextEmailTemplateStep2 the template of email that the user will receive for identity verification. -var PlainTextEmailTemplateStep2 *template.Template - -func init() { - t, err := template.New("text_email_template").Parse(emailPlainTextContentStep2) - if err != nil { - panic(err) - } - - PlainTextEmailTemplateStep2 = t -} - -const emailPlainTextContentStep2 = ` -Your password has been successfully reset. -If you did not initiate the process your credentials might have been compromised. You should reset your password and contact an administrator. - -This email was generated by a user with the IP {{.remoteIP}}. - -Please contact an administrator if you did not initiate the process. -`