feat(notification): password reset notification custom templates (#2828)
Implemented a system to allow overriding email templates, including the remote IP, and sending email notifications when the password was reset successfully. Closes #2755, Closes #2756 Co-authored-by: Manuel Nuñez <@mind-ar> Co-authored-by: James Elliott <james-d-elliott@users.noreply.github.com>pull/3097/head^2
parent
9e05066097
commit
bfd5d66ed8
|
@ -16,6 +16,7 @@ verify their identity.
|
||||||
```yaml
|
```yaml
|
||||||
notifier:
|
notifier:
|
||||||
disable_startup_check: false
|
disable_startup_check: false
|
||||||
|
template_path: /path/to/templates/folder
|
||||||
filesystem: {}
|
filesystem: {}
|
||||||
smtp: {}
|
smtp: {}
|
||||||
```
|
```
|
||||||
|
@ -36,6 +37,52 @@ The notifier has a startup check which validates the specified provider
|
||||||
configuration is correct and will be able to send emails. This can be
|
configuration is correct and will be able to send emails. This can be
|
||||||
disabled with the `disable_startup_check` option:
|
disabled with the `disable_startup_check` option:
|
||||||
|
|
||||||
|
### template_path
|
||||||
|
<div markdown="1">
|
||||||
|
type: string
|
||||||
|
{: .label .label-config .label-purple }
|
||||||
|
default: ""
|
||||||
|
{: .label .label-config .label-blue }
|
||||||
|
required: no
|
||||||
|
{: .label .label-config .label-green }
|
||||||
|
</div>
|
||||||
|
|
||||||
|
This option allows the administrator to set custom templates for notifications
|
||||||
|
the templates folder should contain the following files
|
||||||
|
|
||||||
|
|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 |
|
||||||
|
|
||||||
|
Note:
|
||||||
|
* if you don't define some of these files, a default template is used for that notification
|
||||||
|
|
||||||
|
|
||||||
|
In template files, you can use the following variables:
|
||||||
|
|
||||||
|
|File |Description |
|
||||||
|
|------------------------|---------------------------------------------------|
|
||||||
|
|`{{.title}}`| A predefined title for the email. <br> 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 |
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
```html
|
||||||
|
<body>
|
||||||
|
<h1>{{.title}}</h1>
|
||||||
|
Hi {{.displayName}} <br/>
|
||||||
|
This email has been sent to you in order to validate your identity
|
||||||
|
Click <a href="{{.url}}" >here</a> to change your password
|
||||||
|
</body>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### filesystem
|
### filesystem
|
||||||
|
|
||||||
The [filesystem](filesystem.md) provider.
|
The [filesystem](filesystem.md) provider.
|
||||||
|
|
|
@ -31,6 +31,7 @@ type NotifierConfiguration struct {
|
||||||
DisableStartupCheck bool `koanf:"disable_startup_check"`
|
DisableStartupCheck bool `koanf:"disable_startup_check"`
|
||||||
FileSystem *FileSystemNotifierConfiguration `koanf:"filesystem"`
|
FileSystem *FileSystemNotifierConfiguration `koanf:"filesystem"`
|
||||||
SMTP *SMTPNotifierConfiguration `koanf:"smtp"`
|
SMTP *SMTPNotifierConfiguration `koanf:"smtp"`
|
||||||
|
TemplatePath string `koanf:"template_path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultSMTPNotifierConfiguration represents default configuration parameters for the SMTP notifier.
|
// DefaultSMTPNotifierConfiguration represents default configuration parameters for the SMTP notifier.
|
||||||
|
|
|
@ -54,6 +54,9 @@ const (
|
||||||
errFmtNotifierMultipleConfigured = "notifier: please ensure only one of the 'smtp' or 'filesystem' notifier is configured"
|
errFmtNotifierMultipleConfigured = "notifier: please ensure only one of the 'smtp' or 'filesystem' notifier is configured"
|
||||||
errFmtNotifierNotConfigured = "notifier: you must ensure either the 'smtp' or 'filesystem' notifier " +
|
errFmtNotifierNotConfigured = "notifier: you must ensure either the 'smtp' or 'filesystem' notifier " +
|
||||||
"is configured"
|
"is configured"
|
||||||
|
errFmtNotifierTemplatePathNotExist = "notifier: option 'template_path' refers to location '%s' which does not exist"
|
||||||
|
errFmtNotifierTemplatePathUnknownError = "notifier: option 'template_path' refers to location '%s' which couldn't be opened: %w"
|
||||||
|
errFmtNotifierTemplateLoad = "notifier: error loading template '%s': %w"
|
||||||
errFmtNotifierFileSystemFileNameNotConfigured = "notifier: filesystem: option 'filename' is required "
|
errFmtNotifierFileSystemFileNameNotConfigured = "notifier: filesystem: option 'filename' is required "
|
||||||
errFmtNotifierSMTPNotConfigured = "notifier: smtp: option '%s' is required"
|
errFmtNotifierSMTPNotConfigured = "notifier: smtp: option '%s' is required"
|
||||||
)
|
)
|
||||||
|
@ -412,6 +415,7 @@ var ValidKeys = []string{
|
||||||
"notifier.smtp.tls.minimum_version",
|
"notifier.smtp.tls.minimum_version",
|
||||||
"notifier.smtp.tls.skip_verify",
|
"notifier.smtp.tls.skip_verify",
|
||||||
"notifier.smtp.tls.server_name",
|
"notifier.smtp.tls.server_name",
|
||||||
|
"notifier.template_path",
|
||||||
|
|
||||||
// Regulation Keys.
|
// Regulation Keys.
|
||||||
"regulation.max_retries",
|
"regulation.max_retries",
|
||||||
|
|
|
@ -2,8 +2,12 @@ package validator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
|
"github.com/authelia/authelia/v4/internal/templates"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidateNotifier validates and update notifier configuration.
|
// ValidateNotifier validates and update notifier configuration.
|
||||||
|
@ -27,6 +31,54 @@ func ValidateNotifier(config *schema.NotifierConfiguration, validator *schema.St
|
||||||
}
|
}
|
||||||
|
|
||||||
validateSMTPNotifier(config.SMTP, validator)
|
validateSMTPNotifier(config.SMTP, validator)
|
||||||
|
|
||||||
|
validateNotifierTemplates(config, validator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateNotifierTemplates(config *schema.NotifierConfiguration, validator *schema.StructValidator) {
|
||||||
|
if config.TemplatePath == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
t *template.Template
|
||||||
|
)
|
||||||
|
|
||||||
|
_, err = os.Stat(config.TemplatePath)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case os.IsNotExist(err):
|
||||||
|
validator.Push(fmt.Errorf(errFmtNotifierTemplatePathNotExist, config.TemplatePath))
|
||||||
|
return
|
||||||
|
case err != nil:
|
||||||
|
validator.Push(fmt.Errorf(errFmtNotifierTemplatePathUnknownError, config.TemplatePath, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if t, err = template.ParseFiles(filepath.Join(config.TemplatePath, templates.TemplateNameStep1+".html")); err == nil {
|
||||||
|
templates.HTMLEmailTemplateStep1 = t
|
||||||
|
} else {
|
||||||
|
validator.PushWarning(fmt.Errorf(errFmtNotifierTemplateLoad, templates.TemplateNameStep1+".html", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if t, err = template.ParseFiles(filepath.Join(config.TemplatePath, templates.TemplateNameStep1+".txt")); err == nil {
|
||||||
|
templates.PlainTextEmailTemplateStep1 = t
|
||||||
|
} else {
|
||||||
|
validator.PushWarning(fmt.Errorf(errFmtNotifierTemplateLoad, templates.TemplateNameStep1+".txt", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if t, err = template.ParseFiles(filepath.Join(config.TemplatePath, templates.TemplateNameStep2+".html")); err == nil {
|
||||||
|
templates.HTMLEmailTemplateStep2 = t
|
||||||
|
} else {
|
||||||
|
validator.PushWarning(fmt.Errorf(errFmtNotifierTemplateLoad, templates.TemplateNameStep2+".html", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if t, err = template.ParseFiles(filepath.Join(config.TemplatePath, templates.TemplateNameStep2+".txt")); err == nil {
|
||||||
|
templates.PlainTextEmailTemplateStep2 = t
|
||||||
|
} else {
|
||||||
|
validator.PushWarning(fmt.Errorf(errFmtNotifierTemplateLoad, templates.TemplateNameStep2+".txt", err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateSMTPNotifier(config *schema.SMTPNotifierConfiguration, validator *schema.StructValidator) {
|
func validateSMTPNotifier(config *schema.SMTPNotifierConfiguration, validator *schema.StructValidator) {
|
||||||
|
|
|
@ -28,8 +28,9 @@ func identityRetrieverFromStorage(ctx *middlewares.AutheliaCtx) (*session.Identi
|
||||||
}
|
}
|
||||||
|
|
||||||
return &session.Identity{
|
return &session.Identity{
|
||||||
Username: requestBody.Username,
|
Username: requestBody.Username,
|
||||||
Email: details.Emails[0],
|
Email: details.Emails[0],
|
||||||
|
DisplayName: details.DisplayName,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/middlewares"
|
"github.com/authelia/authelia/v4/internal/middlewares"
|
||||||
|
"github.com/authelia/authelia/v4/internal/templates"
|
||||||
"github.com/authelia/authelia/v4/internal/utils"
|
"github.com/authelia/authelia/v4/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,6 +21,8 @@ func ResetPasswordPost(ctx *middlewares.AutheliaCtx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
username := *userSession.PasswordResetUsername
|
||||||
|
|
||||||
var requestBody resetPasswordStep2RequestBody
|
var requestBody resetPasswordStep2RequestBody
|
||||||
err := ctx.ParseBody(&requestBody)
|
err := ctx.ParseBody(&requestBody)
|
||||||
|
|
||||||
|
@ -32,7 +36,7 @@ func ResetPasswordPost(ctx *middlewares.AutheliaCtx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ctx.Providers.UserProvider.UpdatePassword(*userSession.PasswordResetUsername, requestBody.Password)
|
err = ctx.Providers.UserProvider.UpdatePassword(username, requestBody.Password)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch {
|
switch {
|
||||||
|
@ -46,7 +50,7 @@ func ResetPasswordPost(ctx *middlewares.AutheliaCtx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Logger.Debugf("Password of user %s has been reset", *userSession.PasswordResetUsername)
|
ctx.Logger.Debugf("Password of user %s has been reset", username)
|
||||||
|
|
||||||
// Reset the request.
|
// Reset the request.
|
||||||
userSession.PasswordResetUsername = nil
|
userSession.PasswordResetUsername = nil
|
||||||
|
@ -57,5 +61,69 @@ func ResetPasswordPost(ctx *middlewares.AutheliaCtx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.ReplyOK()
|
// Send Notification.
|
||||||
|
userInfo, err := ctx.Providers.UserProvider.GetDetails(username)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Logger.Error(err)
|
||||||
|
ctx.ReplyOK()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(userInfo.Emails) == 0 {
|
||||||
|
ctx.Logger.Error(fmt.Errorf("user %s has no email address configured", username))
|
||||||
|
ctx.ReplyOK()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bufHTML := new(bytes.Buffer)
|
||||||
|
|
||||||
|
disableHTML := false
|
||||||
|
if ctx.Configuration.Notifier != nil && ctx.Configuration.Notifier.SMTP != nil {
|
||||||
|
disableHTML = ctx.Configuration.Notifier.SMTP.DisableHTMLEmails
|
||||||
|
}
|
||||||
|
|
||||||
|
if !disableHTML {
|
||||||
|
htmlParams := map[string]interface{}{
|
||||||
|
"title": "Password changed successfully",
|
||||||
|
"displayName": userInfo.DisplayName,
|
||||||
|
"remoteIP": ctx.RemoteIP().String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
err = templates.HTMLEmailTemplateStep2.Execute(bufHTML, htmlParams)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ctx.Logger.Error(err)
|
||||||
|
ctx.ReplyOK()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bufText := new(bytes.Buffer)
|
||||||
|
textParams := map[string]interface{}{
|
||||||
|
"displayName": userInfo.DisplayName,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = templates.PlainTextEmailTemplateStep2.Execute(bufText, textParams)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ctx.Logger.Error(err)
|
||||||
|
ctx.ReplyOK()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Logger.Debugf("Sending an email to user %s (%s) to inform that the password has changed.",
|
||||||
|
username, userInfo.Emails[0])
|
||||||
|
|
||||||
|
err = ctx.Providers.Notifier.Send(userInfo.Emails[0], "Password changed successfully", bufText.String(), bufHTML.String())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ctx.Logger.Error(err)
|
||||||
|
ctx.ReplyOK()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,12 +79,14 @@ func IdentityVerificationStart(args IdentityVerificationStartArgs, delayFunc Tim
|
||||||
|
|
||||||
if !disableHTML {
|
if !disableHTML {
|
||||||
htmlParams := map[string]interface{}{
|
htmlParams := map[string]interface{}{
|
||||||
"title": args.MailTitle,
|
"title": args.MailTitle,
|
||||||
"url": link,
|
"url": link,
|
||||||
"button": args.MailButtonContent,
|
"button": args.MailButtonContent,
|
||||||
|
"displayName": identity.DisplayName,
|
||||||
|
"remoteIP": ctx.RemoteIP().String(),
|
||||||
}
|
}
|
||||||
|
|
||||||
err = templates.HTMLEmailTemplate.Execute(bufHTML, htmlParams)
|
err = templates.HTMLEmailTemplateStep1.Execute(bufHTML, htmlParams)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(err, messageOperationFailed)
|
ctx.Error(err, messageOperationFailed)
|
||||||
|
@ -94,10 +96,11 @@ func IdentityVerificationStart(args IdentityVerificationStartArgs, delayFunc Tim
|
||||||
|
|
||||||
bufText := new(bytes.Buffer)
|
bufText := new(bytes.Buffer)
|
||||||
textParams := map[string]interface{}{
|
textParams := map[string]interface{}{
|
||||||
"url": link,
|
"url": link,
|
||||||
|
"displayName": identity.DisplayName,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = templates.PlainTextEmailTemplate.Execute(bufText, textParams)
|
err = templates.PlainTextEmailTemplateStep1.Execute(bufText, textParams)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(err, messageOperationFailed)
|
ctx.Error(err, messageOperationFailed)
|
||||||
|
|
|
@ -55,8 +55,9 @@ type UserSession struct {
|
||||||
|
|
||||||
// Identity identity of the user who is being verified.
|
// Identity identity of the user who is being verified.
|
||||||
type Identity struct {
|
type Identity struct {
|
||||||
Username string
|
Username string
|
||||||
Email string
|
Email string
|
||||||
|
DisplayName string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRedisLogger() *redisLogger {
|
func newRedisLogger() *redisLogger {
|
||||||
|
|
|
@ -6,6 +6,8 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- ./example/compose/nginx/portal/nginx.conf:/etc/nginx/nginx.conf
|
- ./example/compose/nginx/portal/nginx.conf:/etc/nginx/nginx.conf
|
||||||
- ./example/compose/nginx/portal/ssl:/etc/ssl
|
- ./example/compose/nginx/portal/ssl:/etc/ssl
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
networks:
|
networks:
|
||||||
authelianet:
|
authelianet:
|
||||||
aliases:
|
aliases:
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package templates
|
||||||
|
|
||||||
|
// Template File Names.
|
||||||
|
const (
|
||||||
|
TemplateNameStep1 = "PasswordResetStep1"
|
||||||
|
TemplateNameStep2 = "PasswordResetStep2"
|
||||||
|
)
|
|
@ -4,19 +4,19 @@ import (
|
||||||
"text/template"
|
"text/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HTMLEmailTemplate the template of email that the user will receive for identity verification.
|
// HTMLEmailTemplateStep1 the template of email that the user will receive for identity verification.
|
||||||
var HTMLEmailTemplate *template.Template
|
var HTMLEmailTemplateStep1 *template.Template
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
t, err := template.New("html_email_template").Parse(emailHTMLContent)
|
t, err := template.New("html_email_template").Parse(emailHTMLContentStep1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
HTMLEmailTemplate = t
|
HTMLEmailTemplateStep1 = t
|
||||||
}
|
}
|
||||||
|
|
||||||
const emailHTMLContent = `
|
const emailHTMLContentStep1 = `
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
|
||||||
|
@ -93,24 +93,27 @@ const emailHTMLContent = `
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: #ffffff;
|
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
text-decoration: none !important;
|
text-decoration: none !important;
|
||||||
}
|
}
|
||||||
.link {
|
|
||||||
color: #0645AD;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
padding: 15px 30px;
|
color: #ffffff;
|
||||||
border-radius: 10px;
|
padding: 15px 30px;
|
||||||
background: rgb(25, 118, 210);
|
border-radius: 10px;
|
||||||
text-decoration: none;
|
background: rgb(25, 118, 210);
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
color: rgb(25, 118, 210);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*STYLES*/
|
/*STYLES*/
|
||||||
table[class=full] {
|
table[class=full] {
|
||||||
|
@ -311,6 +314,12 @@ const emailHTMLContent = `
|
||||||
class="devicewidthinner">
|
class="devicewidthinner">
|
||||||
<tbody>
|
<tbody>
|
||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
|
<tr>
|
||||||
|
<td style="font-family: Helvetica, arial, sans-serif; font-size: 16px; color: #333333; text-align:center; line-height: 30px;"
|
||||||
|
st-title="fulltext-content">
|
||||||
|
Hi {{.displayName}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="font-family: Helvetica, arial, sans-serif; font-size: 16px; color: #333333; text-align:center; line-height: 30px;"
|
<td style="font-family: Helvetica, arial, sans-serif; font-size: 16px; color: #333333; text-align:center; line-height: 30px;"
|
||||||
st-title="fulltext-content">
|
st-title="fulltext-content">
|
||||||
|
@ -333,17 +342,13 @@ const emailHTMLContent = `
|
||||||
<a href="{{.url}}" class="button">{{.button}}</a>
|
<a href="{{.url}}" class="button">{{.button}}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<!-- spacing -->
|
||||||
<tr>
|
<tr>
|
||||||
<td width="100%" height="20"
|
<td width="100%" height="20"
|
||||||
style="font-size:1px; line-height:1px; mso-line-height-rule: exactly;">
|
style="font-size:1px; line-height:1px; mso-line-height-rule: exactly;">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<!-- End of spacing -->
|
||||||
<td style="font-family: Helvetica, arial, sans-serif; font-size: 16px; color: #333333; text-align:center; line-height: 30px;"
|
|
||||||
st-title="fulltext-content">
|
|
||||||
Or
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td style="word-break: break-word; overflow-wrap: break-word; text-align:center; line-height: 30px;">
|
<td style="word-break: break-word; overflow-wrap: break-word; text-align:center; line-height: 30px;">
|
||||||
<a href="{{.url}}" class="link">{{.url}}</a>
|
<a href="{{.url}}" class="link">{{.url}}</a>
|
||||||
|
@ -354,13 +359,6 @@ const emailHTMLContent = `
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<!-- Spacing -->
|
|
||||||
<tr>
|
|
||||||
<td height="20"
|
|
||||||
style="font-size:1px; line-height:1px; mso-line-height-rule: exactly;">
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<!-- Spacing -->
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
|
@ -417,6 +415,19 @@ const emailHTMLContent = `
|
||||||
Please contact an administrator if you did not initiate the process.
|
Please contact an administrator if you did not initiate the process.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<!-- spacing -->
|
||||||
|
<tr>
|
||||||
|
<td width="100%" height="20"
|
||||||
|
style="font-size:1px; line-height:1px; mso-line-height-rule: exactly;">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- End of spacing -->
|
||||||
|
<tr>
|
||||||
|
<td style="font-family: Helvetica, arial, sans-serif; font-style: italic; font-size: 12px; color: #333333; text-align:center; line-height: 30px;"
|
||||||
|
st-title="fulltext-content">
|
||||||
|
This email was generated by some with the IP address {{.remoteIP}}.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<!-- Spacing -->
|
<!-- Spacing -->
|
||||||
<tr>
|
<tr>
|
||||||
<td width="100%" height="20"></td>
|
<td width="100%" height="20"></td>
|
|
@ -0,0 +1,423 @@
|
||||||
|
package templates
|
||||||
|
|
||||||
|
import (
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTMLEmailTemplateStep2 the template of email that the user will receive for identity verification.
|
||||||
|
var HTMLEmailTemplateStep2 *template.Template
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
t, err := template.New("html_email_template").Parse(emailHTMLContentStep2)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
HTMLEmailTemplateStep2 = t
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailHTMLContentStep2 = `
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Authelia</title>
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
/* client-specific Styles */
|
||||||
|
#outlook a {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Force Outlook to provide a "view in browser" menu link. */
|
||||||
|
body {
|
||||||
|
width: 100% !important;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-ms-text-size-adjust: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prevent Webkit and Windows Mobile platforms from changing default font sizes, while not breaking desktop design. */
|
||||||
|
.ExternalClass {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Force Hotmail to display emails at full width */
|
||||||
|
.ExternalClass,
|
||||||
|
.ExternalClass p,
|
||||||
|
.ExternalClass span,
|
||||||
|
.ExternalClass font,
|
||||||
|
.ExternalClass td,
|
||||||
|
.ExternalClass div {
|
||||||
|
line-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Force Hotmail to display normal line spacing.*/
|
||||||
|
#backgroundTable {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100% !important;
|
||||||
|
line-height: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
outline: none;
|
||||||
|
text-decoration: none;
|
||||||
|
border: none;
|
||||||
|
-ms-interpolation-mode: bicubic;
|
||||||
|
}
|
||||||
|
|
||||||
|
a img {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image_fix {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0px 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td {
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
mso-table-lspace: 0pt;
|
||||||
|
mso-table-rspace: 0pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #ffffff;
|
||||||
|
text-decoration: none;
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
color: #0645AD;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
padding: 15px 30px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgb(25, 118, 210);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*STYLES*/
|
||||||
|
table[class=full] {
|
||||||
|
width: 100%;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*IPAD STYLES*/
|
||||||
|
@media only screen and (max-width: 640px) {
|
||||||
|
|
||||||
|
a[href^="tel"],
|
||||||
|
a[href^="sms"] {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #0a8cce;
|
||||||
|
/* or whatever your want */
|
||||||
|
pointer-events: none;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile_link a[href^="tel"],
|
||||||
|
.mobile_link a[href^="sms"] {
|
||||||
|
text-decoration: default;
|
||||||
|
color: #0a8cce !important;
|
||||||
|
pointer-events: auto;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
table[class=devicewidth] {
|
||||||
|
width: 440px !important;
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
table[class=devicewidthinner] {
|
||||||
|
width: 420px !important;
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
img[class=banner] {
|
||||||
|
width: 440px !important;
|
||||||
|
height: 220px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
img[class=colimg2] {
|
||||||
|
width: 440px !important;
|
||||||
|
height: 220px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*IPHONE STYLES*/
|
||||||
|
@media only screen and (max-width: 480px) {
|
||||||
|
|
||||||
|
a[href^="tel"],
|
||||||
|
a[href^="sms"] {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #0a8cce;
|
||||||
|
/* or whatever your want */
|
||||||
|
pointer-events: none;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile_link a[href^="tel"],
|
||||||
|
.mobile_link a[href^="sms"] {
|
||||||
|
text-decoration: default;
|
||||||
|
color: #0a8cce !important;
|
||||||
|
pointer-events: auto;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
table[class=devicewidth] {
|
||||||
|
width: 280px !important;
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
table[class=devicewidthinner] {
|
||||||
|
width: 260px !important;
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
img[class=banner] {
|
||||||
|
width: 280px !important;
|
||||||
|
height: 140px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
img[class=colimg2] {
|
||||||
|
width: 280px !important;
|
||||||
|
height: 140px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
td[class=mobile-hide] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
td[class="padding-bottom25"] {
|
||||||
|
padding-bottom: 25px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!-- Start of header -->
|
||||||
|
<table width="100%" bgcolor="#ffffff" cellpadding="0" cellspacing="0" border="0" id="backgroundTable"
|
||||||
|
st-sortable="header">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<table width="600" cellpadding="0" cellspacing="0" border="0" align="center" class="devicewidth">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td width="100%">
|
||||||
|
<table width="600" cellpadding="0" cellspacing="0" border="0" align="center"
|
||||||
|
class="devicewidth">
|
||||||
|
<tbody>
|
||||||
|
<!-- Spacing -->
|
||||||
|
<tr>
|
||||||
|
<td height="20"
|
||||||
|
style="font-size:1px; line-height:1px; mso-line-height-rule: exactly;">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Spacing -->
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<!-- logo -->
|
||||||
|
<table width="140" align="center" border="0" cellpadding="0" cellspacing="0"
|
||||||
|
class="devicewidth">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td width="300" height="50" align="center">
|
||||||
|
<h1>{{.title}}</h1>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<!-- end of logo -->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Spacing -->
|
||||||
|
<tr>
|
||||||
|
<td height="20"
|
||||||
|
style="font-size:1px; line-height:1px; mso-line-height-rule: exactly;">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Spacing -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<!-- End of Header -->
|
||||||
|
<!-- Start of separator -->
|
||||||
|
<table width="100%" bgcolor="#ffffff" cellpadding="0" cellspacing="0" border="0" id="backgroundTable"
|
||||||
|
st-sortable="separator">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<table width="600" align="center" cellspacing="0" cellpadding="0" border="0" class="devicewidth">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td align="center" height="20" style="font-size:1px; line-height:1px;"> </td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<!-- End of separator -->
|
||||||
|
<!-- Start Full Text -->
|
||||||
|
<table width="100%" bgcolor="#ffffff" cellpadding="0" cellspacing="0" border="0" id="backgroundTable"
|
||||||
|
st-sortable="full-text">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<table width="600" cellpadding="0" cellspacing="0" border="0" align="center" class="devicewidth">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td width="100%">
|
||||||
|
<table width="600" cellpadding="0" cellspacing="0" border="0" align="center"
|
||||||
|
class="devicewidth">
|
||||||
|
<tbody>
|
||||||
|
<!-- Spacing -->
|
||||||
|
<tr>
|
||||||
|
<td height="20"
|
||||||
|
style="font-size:1px; line-height:1px; mso-line-height-rule: exactly;">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Spacing -->
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<table width="560" align="center" cellpadding="0" cellspacing="0" border="0"
|
||||||
|
class="devicewidthinner">
|
||||||
|
<tbody>
|
||||||
|
<!-- Title -->
|
||||||
|
<tr>
|
||||||
|
<td style="font-family: Helvetica, arial, sans-serif; font-size: 16px; color: #333333; text-align:center; line-height: 30px;"
|
||||||
|
st-title="fulltext-content">
|
||||||
|
Hi {{.displayName}} <br/>
|
||||||
|
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.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- End of Title -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Spacing -->
|
||||||
|
<tr>
|
||||||
|
<td height="20"
|
||||||
|
style="font-size:1px; line-height:1px; mso-line-height-rule: exactly;">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Spacing -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<!-- end of full text -->
|
||||||
|
<!-- Start of separator -->
|
||||||
|
<table width="100%" bgcolor="#ffffff" cellpadding="0" cellspacing="0" border="0" id="backgroundTable"
|
||||||
|
st-sortable="separator">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<table width="600" align="center" cellspacing="0" cellpadding="0" border="0" class="devicewidth">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td align="center" height="30" style="font-size:1px; line-height:1px;"> </td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="550" align="center" height="1" bgcolor="#d1d1d1"
|
||||||
|
style="font-size:1px; line-height:1px;"> </td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center" height="30" style="font-size:1px; line-height:1px;"> </td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<!-- End of separator -->
|
||||||
|
<!-- Start of Postfooter -->
|
||||||
|
<table width="100%" bgcolor="#ffffff" cellpadding="0" cellspacing="0" border="0" id="backgroundTable"
|
||||||
|
st-sortable="postfooter">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<table width="600" cellpadding="0" cellspacing="0" border="0" align="center" class="devicewidth">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td width="100%">
|
||||||
|
<table width="600" cellpadding="0" cellspacing="0" border="0" align="center"
|
||||||
|
class="devicewidth">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td align="center" valign="middle"
|
||||||
|
style="font-family: Helvetica, arial, sans-serif; font-size: 14px;color: #666666"
|
||||||
|
st-content="postfooter">
|
||||||
|
Please contact an administrator if you did not initiate the process.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- spacing -->
|
||||||
|
<tr>
|
||||||
|
<td width="100%" height="20"
|
||||||
|
style="font-size:1px; line-height:1px; mso-line-height-rule: exactly;">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- End of spacing -->
|
||||||
|
<tr>
|
||||||
|
<td style="font-family: Helvetica, arial, sans-serif; font-style: italic; font-size: 12px; color: #333333; text-align:center; line-height: 30px;"
|
||||||
|
st-title="fulltext-content">
|
||||||
|
This email was generated by some with the IP address {{.remoteIP}}.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Spacing -->
|
||||||
|
<tr>
|
||||||
|
<td width="100%" height="20"></td>
|
||||||
|
</tr>
|
||||||
|
<!-- Spacing -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<!-- End of postfooter -->
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
`
|
|
@ -4,23 +4,25 @@ import (
|
||||||
"text/template"
|
"text/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PlainTextEmailTemplate the template of email that the user will receive for identity verification.
|
// PlainTextEmailTemplateStep1 the template of email that the user will receive for identity verification.
|
||||||
var PlainTextEmailTemplate *template.Template
|
var PlainTextEmailTemplateStep1 *template.Template
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
t, err := template.New("text_email_template").Parse(emailPlainTextContent)
|
t, err := template.New("text_email_template").Parse(emailPlainTextContentStep1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
PlainTextEmailTemplate = t
|
PlainTextEmailTemplateStep1 = t
|
||||||
}
|
}
|
||||||
|
|
||||||
const emailPlainTextContent = `
|
const emailPlainTextContentStep1 = `
|
||||||
This email has been sent to you in order to validate your identity.
|
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.
|
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}}
|
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.
|
Please contact an administrator if you did not initiate the process.
|
||||||
`
|
`
|
|
@ -0,0 +1,26 @@
|
||||||
|
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.
|
||||||
|
`
|
Loading…
Reference in New Issue