[FEATURE] Customizable Email Subject (#830)

* [FEATURE] Customizable Email Subject

* allow users to optionally change email subject
* this is so they can more easily communicate the source of the email

* Update docs/configuration/notifier/smtp.md

Co-Authored-By: Amir Zarrinkafsh <nightah@me.com>

Co-authored-by: Clément Michaud <clement.michaud34@gmail.com>
Co-authored-by: Amir Zarrinkafsh <nightah@me.com>
This commit is contained in:
James Elliott 2020-04-09 10:21:28 +10:00 committed by GitHub
parent aae665eff2
commit 2fed503e5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 32 additions and 10 deletions

View File

@ -361,6 +361,9 @@ notifier:
host: 127.0.0.1 host: 127.0.0.1
port: 1025 port: 1025
sender: admin@example.com sender: admin@example.com
# Subject configuration of the emails sent.
# {title} is replaced by the text from the notifier
subject: "[Authelia] {title}"
## disable_require_tls: false ## disable_require_tls: false
## disable_verify_cert: false ## disable_verify_cert: false
## trusted_cert: "" ## trusted_cert: ""

View File

@ -12,7 +12,16 @@ nav_order: 2
It can be configured as described below. It can be configured as described below.
```yaml ```yaml
# Configuration of the notification system.
#
# Notifications are sent to users when they require a password reset, a u2f
# registration or a TOTP registration.
# Use only an available configuration: filesystem, smtp
notifier: notifier:
# For testing purpose, notifications can be sent in a file
## filesystem:
## filename: /tmp/authelia/notification.txt
# Use a SMTP server for sending notifications. Authelia uses PLAIN or LOGIN method to authenticate. # Use a SMTP server for sending notifications. Authelia uses PLAIN or LOGIN method to authenticate.
# [Security] By default Authelia will: # [Security] By default Authelia will:
# - force all SMTP connections over TLS including unauthenticated connections # - force all SMTP connections over TLS including unauthenticated connections
@ -31,6 +40,9 @@ notifier:
host: 127.0.0.1 host: 127.0.0.1
port: 1025 port: 1025
sender: admin@example.com sender: admin@example.com
# Subject configuration of the emails sent.
# {title} is replaced by the text from the notifier
subject: "[Authelia] {title}"
## disable_require_tls: false ## disable_require_tls: false
## disable_verify_cert: false ## disable_verify_cert: false
## trusted_cert: "" ## trusted_cert: ""

View File

@ -10,6 +10,7 @@ type SMTPNotifierConfiguration struct {
Username string `mapstructure:"username"` Username string `mapstructure:"username"`
Password string `mapstructure:"password"` Password string `mapstructure:"password"`
Sender string `mapstructure:"sender"` Sender string `mapstructure:"sender"`
Subject string `mapstructure:"subject"`
Host string `mapstructure:"host"` Host string `mapstructure:"host"`
Port int `mapstructure:"port"` Port int `mapstructure:"port"`
TrustedCert string `mapstructure:"trusted_cert"` TrustedCert string `mapstructure:"trusted_cert"`
@ -22,3 +23,7 @@ type NotifierConfiguration struct {
FileSystem *FileSystemNotifierConfiguration `mapstructure:"filesystem"` FileSystem *FileSystemNotifierConfiguration `mapstructure:"filesystem"`
SMTP *SMTPNotifierConfiguration `mapstructure:"smtp"` SMTP *SMTPNotifierConfiguration `mapstructure:"smtp"`
} }
var DefaultSMTPNotifierConfiguration = SMTPNotifierConfiguration{
Subject: "[Authelia] {title}",
}

View File

@ -37,6 +37,10 @@ func ValidateNotifier(configuration *schema.NotifierConfiguration, validator *sc
if configuration.SMTP.Sender == "" { if configuration.SMTP.Sender == "" {
validator.Push(fmt.Errorf("Sender of SMTP notifier must be provided")) validator.Push(fmt.Errorf("Sender of SMTP notifier must be provided"))
} }
if configuration.SMTP.Subject == "" {
configuration.SMTP.Subject = schema.DefaultSMTPNotifierConfiguration.Subject
}
return return
} }
} }

View File

@ -29,7 +29,6 @@ func isTokenUserValidFor2FARegistration(ctx *middlewares.AutheliaCtx, username s
// SecondFactorTOTPIdentityStart the handler for initiating the identity validation. // SecondFactorTOTPIdentityStart the handler for initiating the identity validation.
var SecondFactorTOTPIdentityStart = middlewares.IdentityVerificationStart(middlewares.IdentityVerificationStartArgs{ var SecondFactorTOTPIdentityStart = middlewares.IdentityVerificationStart(middlewares.IdentityVerificationStartArgs{
MailSubject: "[Authelia] Register your mobile",
MailTitle: "Register your mobile", MailTitle: "Register your mobile",
MailButtonContent: "Register", MailButtonContent: "Register",
TargetEndpoint: "/one-time-password/register", TargetEndpoint: "/one-time-password/register",

View File

@ -16,7 +16,6 @@ var u2fConfig = &u2f.Config{
// SecondFactorU2FIdentityStart the handler for initiating the identity validation. // SecondFactorU2FIdentityStart the handler for initiating the identity validation.
var SecondFactorU2FIdentityStart = middlewares.IdentityVerificationStart(middlewares.IdentityVerificationStartArgs{ var SecondFactorU2FIdentityStart = middlewares.IdentityVerificationStart(middlewares.IdentityVerificationStartArgs{
MailSubject: "[Authelia] Register your key",
MailTitle: "Register your key", MailTitle: "Register your key",
MailButtonContent: "Register", MailButtonContent: "Register",
TargetEndpoint: "/security-key/register", TargetEndpoint: "/security-key/register",

View File

@ -35,7 +35,6 @@ func identityRetrieverFromStorage(ctx *middlewares.AutheliaCtx) (*session.Identi
// ResetPasswordIdentityStart the handler for initiating the identity validation for resetting a password. // ResetPasswordIdentityStart the handler for initiating the identity validation for resetting a password.
// We need to ensure the attacker cannot perform user enumeration by always replying with 200 whatever what happens in backend. // We need to ensure the attacker cannot perform user enumeration by always replying with 200 whatever what happens in backend.
var ResetPasswordIdentityStart = middlewares.IdentityVerificationStart(middlewares.IdentityVerificationStartArgs{ var ResetPasswordIdentityStart = middlewares.IdentityVerificationStart(middlewares.IdentityVerificationStartArgs{
MailSubject: "[Authelia] Reset your password",
MailTitle: "Reset your password", MailTitle: "Reset your password",
MailButtonContent: "Reset", MailButtonContent: "Reset",
TargetEndpoint: "/reset-password/step2", TargetEndpoint: "/reset-password/step2",

View File

@ -78,7 +78,7 @@ func IdentityVerificationStart(args IdentityVerificationStartArgs) RequestHandle
ctx.Logger.Debugf("Sending an email to user %s (%s) to confirm identity for registering a device.", ctx.Logger.Debugf("Sending an email to user %s (%s) to confirm identity for registering a device.",
identity.Username, identity.Email) identity.Username, identity.Email)
err = ctx.Providers.Notifier.Send(identity.Email, args.MailSubject, buf.String()) err = ctx.Providers.Notifier.Send(identity.Email, args.MailTitle, buf.String())
if err != nil { if err != nil {
ctx.Error(err, operationFailedMessage) ctx.Error(err, operationFailedMessage)

View File

@ -19,7 +19,6 @@ func newArgs(retriever func(ctx *middlewares.AutheliaCtx) (*session.Identity, er
return middlewares.IdentityVerificationStartArgs{ return middlewares.IdentityVerificationStartArgs{
ActionClaim: "Claim", ActionClaim: "Claim",
MailButtonContent: "Register", MailButtonContent: "Register",
MailSubject: "Subject",
MailTitle: "Title", MailTitle: "Title",
TargetEndpoint: "/target", TargetEndpoint: "/target",
IdentityRetrieverFunc: retriever, IdentityRetrieverFunc: retriever,
@ -77,7 +76,7 @@ func TestShouldFailSendingAnEmail(t *testing.T) {
Return(nil) Return(nil)
mock.NotifierMock.EXPECT(). mock.NotifierMock.EXPECT().
Send(gomock.Eq("john@example.com"), gomock.Eq("Subject"), gomock.Any()). Send(gomock.Eq("john@example.com"), gomock.Eq("Title"), gomock.Any()).
Return(fmt.Errorf("no notif")) Return(fmt.Errorf("no notif"))
args := newArgs(defaultRetriever) args := newArgs(defaultRetriever)
@ -136,7 +135,7 @@ func TestShouldSucceedIdentityVerificationStartProcess(t *testing.T) {
Return(nil) Return(nil)
mock.NotifierMock.EXPECT(). mock.NotifierMock.EXPECT().
Send(gomock.Eq("john@example.com"), gomock.Eq("Subject"), gomock.Any()). Send(gomock.Eq("john@example.com"), gomock.Eq("Title"), gomock.Any()).
Return(nil) Return(nil)
args := newArgs(defaultRetriever) args := newArgs(defaultRetriever)

View File

@ -47,7 +47,6 @@ type Middleware = func(RequestHandler) RequestHandler
// of the identity verification process. // of the identity verification process.
type IdentityVerificationStartArgs struct { type IdentityVerificationStartArgs struct {
// Email template needs a subject, a title and the content of the button. // Email template needs a subject, a title and the content of the button.
MailSubject string
MailTitle string MailTitle string
MailButtonContent string MailButtonContent string

View File

@ -26,6 +26,7 @@ type SMTPNotifier struct {
disableVerifyCert bool disableVerifyCert bool
disableRequireTLS bool disableRequireTLS bool
address string address string
subject string
client *smtp.Client client *smtp.Client
tlsConfig *tls.Config tlsConfig *tls.Config
} }
@ -42,6 +43,7 @@ func NewSMTPNotifier(configuration schema.SMTPNotifierConfiguration) *SMTPNotifi
disableVerifyCert: configuration.DisableVerifyCert, disableVerifyCert: configuration.DisableVerifyCert,
disableRequireTLS: configuration.DisableRequireTLS, disableRequireTLS: configuration.DisableRequireTLS,
address: fmt.Sprintf("%s:%d", configuration.Host, configuration.Port), address: fmt.Sprintf("%s:%d", configuration.Host, configuration.Port),
subject: configuration.Subject,
} }
notifier.initializeTLSConfig() notifier.initializeTLSConfig()
return notifier return notifier
@ -231,7 +233,8 @@ func (n *SMTPNotifier) cleanup() {
} }
// Send an email // Send an email
func (n *SMTPNotifier) Send(recipient, subject, body string) error { func (n *SMTPNotifier) Send(recipient, title, body string) error {
subject := strings.ReplaceAll(n.subject, "{title}", title)
if err := n.dial(); err != nil { if err := n.dial(); err != nil {
return err return err
} }