Skip to content

Commit

Permalink
feat(notification): important events notifications (#4644)
Browse files Browse the repository at this point in the history
This adds important event notifications.
  • Loading branch information
james-d-elliott committed Dec 27, 2022
1 parent 1c3f650 commit f685f24
Show file tree
Hide file tree
Showing 13 changed files with 340 additions and 48 deletions.
8 changes: 7 additions & 1 deletion docs/content/en/reference/guides/templating.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,15 @@ The following functions which mimic the behaviour of helm exist in most templati
- sha512sum
- squote
- now
- keys
- sortAlpha
- b64enc
- b64dec
- b32enc
- b32dec

See the [Helm Documentation](https://helm.sh/docs/chart_template_guide/function_list/) for more information. Please
note that only the functions listed above are supported.
note that only the functions listed above are supported and the functions don't necessarily behave exactly the same.

__*Special Note:* The `env` and `expandenv` function automatically excludes environment variables that start with
`AUTHELIA_` or `X_AUTHELIA_` and end with one of `KEY`, `SECRET`, `PASSWORD`, `TOKEN`, or `CERTIFICATE_CHAIN`.__
Expand Down
8 changes: 4 additions & 4 deletions internal/handlers/handler_register_totp.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ func totpIdentityFinish(ctx *middlewares.AutheliaCtx, username string) {
ctx.Error(fmt.Errorf("unable to generate TOTP key: %s", err), messageUnableToRegisterOneTimePassword)
}

err = ctx.Providers.StorageProvider.SaveTOTPConfiguration(ctx, *config)
if err != nil {
if err = ctx.Providers.StorageProvider.SaveTOTPConfiguration(ctx, *config); err != nil {
ctx.Error(fmt.Errorf("unable to save TOTP secret in DB: %s", err), messageUnableToRegisterOneTimePassword)
return
}
Expand All @@ -57,10 +56,11 @@ func totpIdentityFinish(ctx *middlewares.AutheliaCtx, username string) {
Base32Secret: string(config.Secret),
}

err = ctx.SetJSONBody(response)
if err != nil {
if err = ctx.SetJSONBody(response); err != nil {
ctx.Logger.Errorf("Unable to set TOTP key response in body: %s", err)
}

ctxLogEvent(ctx, username, "Second Factor Method Added", map[string]any{"Action": "Second Factor Method Added", "Category": "Time-based One Time Password"})
}

// TOTPIdentityFinish the handler for finishing the identity validation.
Expand Down
6 changes: 2 additions & 4 deletions internal/handlers/handler_register_webauthn.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,10 @@ func WebauthnAttestationPOST(ctx *middlewares.AutheliaCtx) {
userSession.Webauthn = nil
if err = ctx.SaveSession(userSession); err != nil {
ctx.Logger.Errorf(logFmtErrSessionSave, "removal of the attestation challenge", regulation.AuthTypeWebauthn, userSession.Username, err)

respondUnauthorized(ctx, messageMFAValidationFailed)

return
}

ctx.ReplyOK()
ctx.SetStatusCode(fasthttp.StatusCreated)

ctxLogEvent(ctx, userSession.Username, "Second Factor Method Added", map[string]any{"Action": "Second Factor Method Added", "Category": "Webauthn Credential", "Device Name": "Primary"})
}
7 changes: 5 additions & 2 deletions internal/handlers/handler_reset_password_step2.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,18 +73,21 @@ func ResetPasswordPOST(ctx *middlewares.AutheliaCtx) {
return
}

data := templates.EmailPasswordResetValues{
data := templates.EmailEventValues{
Title: "Password changed successfully",
DisplayName: userInfo.DisplayName,
RemoteIP: ctx.RemoteIP().String(),
Details: map[string]any{
"Action": "Password Reset",
},
}

addresses := userInfo.Addresses()

ctx.Logger.Debugf("Sending an email to user %s (%s) to inform that the password has changed.",
username, addresses[0])

if err = ctx.Providers.Notifier.Send(ctx, addresses[0], "Password changed successfully", ctx.Providers.Templates.GetPasswordResetEmailTemplate(), data); err != nil {
if err = ctx.Providers.Notifier.Send(ctx, addresses[0], "Password changed successfully", ctx.Providers.Templates.GetEventEmailTemplate(), data); err != nil {
ctx.Logger.Error(err)
ctx.ReplyOK()

Expand Down
41 changes: 41 additions & 0 deletions internal/handlers/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ package handlers

import (
"bytes"
"fmt"
"net/url"

"github.com/authelia/authelia/v4/internal/authentication"
"github.com/authelia/authelia/v4/internal/middlewares"
"github.com/authelia/authelia/v4/internal/templates"
)

var bytesEmpty = []byte("")
Expand All @@ -24,3 +27,41 @@ func ctxGetPortalURL(ctx *middlewares.AutheliaCtx) (portalURL *url.URL) {

return nil
}

func ctxLogEvent(ctx *middlewares.AutheliaCtx, username, description string, eventDetails map[string]any) {
var (
details *authentication.UserDetails
err error
)

ctx.Logger.Debugf("Getting user details for notification")

// Send Notification.
if details, err = ctx.Providers.UserProvider.GetDetails(username); err != nil {
ctx.Logger.Error(err)
return
}

if len(details.Emails) == 0 {
ctx.Logger.Error(fmt.Errorf("user %s has no email address configured", username))
return
}

data := templates.EmailEventValues{
Title: description,
DisplayName: details.DisplayName,
RemoteIP: ctx.RemoteIP().String(),
Details: eventDetails,
}

ctx.Logger.Debugf("Getting user addresses for notification")

addresses := details.Addresses()

ctx.Logger.Debugf("Sending an email to user %s (%s) to inform them of an important event.", username, addresses[0])

if err = ctx.Providers.Notifier.Send(ctx, addresses[0], description, ctx.Providers.Templates.GetEventEmailTemplate(), data); err != nil {
ctx.Logger.Error(err)
return
}
}
18 changes: 9 additions & 9 deletions internal/notification/smtp_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"net/smtp"
"strings"

gomail "github.com/wneessen/go-mail"
"github.com/wneessen/go-mail"
"github.com/wneessen/go-mail/auth"

"github.com/authelia/authelia/v4/internal/configuration/schema"
Expand All @@ -29,7 +29,7 @@ func NewOpportunisticSMTPAuth(config *schema.SMTPNotifierConfiguration) *Opportu
type OpportunisticSMTPAuth struct {
username, password, host string

satPreference []gomail.SMTPAuthType
satPreference []mail.SMTPAuthType
sa smtp.Auth
}

Expand All @@ -43,11 +43,11 @@ func (a *OpportunisticSMTPAuth) Start(server *smtp.ServerInfo) (proto string, to
for _, pref := range a.satPreference {
if utils.IsStringInSlice(string(pref), server.Auth) {
switch pref {
case gomail.SMTPAuthPlain:
case mail.SMTPAuthPlain:
a.sa = smtp.PlainAuth("", a.username, a.password, a.host)
case gomail.SMTPAuthLogin:
case mail.SMTPAuthLogin:
a.sa = auth.LoginAuth(a.username, a.password, a.host)
case gomail.SMTPAuthCramMD5:
case mail.SMTPAuthCramMD5:
a.sa = smtp.CRAMMD5Auth(a.username, a.password)
}

Expand All @@ -57,12 +57,12 @@ func (a *OpportunisticSMTPAuth) Start(server *smtp.ServerInfo) (proto string, to

if a.sa == nil {
for _, sa := range server.Auth {
switch gomail.SMTPAuthType(sa) {
case gomail.SMTPAuthPlain:
switch mail.SMTPAuthType(sa) {
case mail.SMTPAuthPlain:
a.sa = smtp.PlainAuth("", a.username, a.password, a.host)
case gomail.SMTPAuthLogin:
case mail.SMTPAuthLogin:
a.sa = auth.LoginAuth(a.username, a.password, a.host)
case gomail.SMTPAuthCramMD5:
case mail.SMTPAuthCramMD5:
a.sa = smtp.CRAMMD5Auth(a.username, a.password)
}
}
Expand Down
1 change: 1 addition & 0 deletions internal/templates/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const (
const (
TemplateNameEmailIdentityVerification = "IdentityVerification"
TemplateNameEmailPasswordReset = "PasswordReset"
TemplateNameEmailEvent = "Event"
)

// Template Category Names.
Expand Down
70 changes: 69 additions & 1 deletion internal/templates/funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import (
"crypto/sha1" //nolint:gosec
"crypto/sha256"
"crypto/sha512"
"encoding/base32"
"encoding/base64"
"encoding/hex"
"fmt"
"hash"
"os"
"reflect"
"sort"
"strconv"
"strings"
"time"
Expand All @@ -27,6 +30,8 @@ func FuncMap() map[string]any {
"hasPrefix": FuncStringHasPrefix,
"hasSuffix": FuncStringHasSuffix,
"lower": strings.ToLower,
"keys": FuncKeys,
"sortAlpha": FuncSortAlpha,
"upper": strings.ToUpper,
"title": strings.ToTitle,
"trim": strings.TrimSpace,
Expand All @@ -40,9 +45,43 @@ func FuncMap() map[string]any {
"sha512sum": FuncHashSum(sha512.New),
"squote": FuncStringSQuote,
"now": time.Now,
"b64enc": FuncB64Enc,
"b64dec": FuncB64Dec,
"b32enc": FuncB32Enc,
"b32dec": FuncB32Dec,
}
}

// FuncB64Enc is a helper function that provides similar functionality to the helm b64enc func.
func FuncB64Enc(input string) string {
return base64.StdEncoding.EncodeToString([]byte(input))
}

// FuncB64Dec is a helper function that provides similar functionality to the helm b64dec func.
func FuncB64Dec(input string) (string, error) {
data, err := base64.StdEncoding.DecodeString(input)
if err != nil {
return "", err
}

return string(data), nil
}

// FuncB32Enc is a helper function that provides similar functionality to the helm b32enc func.
func FuncB32Enc(input string) string {
return base32.StdEncoding.EncodeToString([]byte(input))
}

// FuncB32Dec is a helper function that provides similar functionality to the helm b32dec func.
func FuncB32Dec(input string) (string, error) {
data, err := base32.StdEncoding.DecodeString(input)
if err != nil {
return "", err
}

return string(data), nil
}

// FuncExpandEnv is a special version of os.ExpandEnv that excludes secret keys.
func FuncExpandEnv(s string) string {
return os.Expand(s, FuncGetEnv)
Expand All @@ -68,6 +107,35 @@ func FuncHashSum(new func() hash.Hash) func(data string) string {
}
}

// FuncKeys is a helper function that provides similar functionality to the helm keys func.
func FuncKeys(maps ...map[string]any) []string {
var keys []string

for _, m := range maps {
for k := range m {
keys = append(keys, k)
}
}

return keys
}

// FuncSortAlpha is a helper function that provides similar functionality to the helm sortAlpha func.
func FuncSortAlpha(slice any) []string {
kind := reflect.Indirect(reflect.ValueOf(slice)).Kind()

switch kind {
case reflect.Slice, reflect.Array:
unsorted := strslice(slice)
sorted := sort.StringSlice(unsorted)
sorted.Sort()

return sorted
}

return []string{strval(slice)}
}

// FuncStringReplace is a helper function that provides similar functionality to the helm replace func.
func FuncStringReplace(old, new, s string) string {
return strings.ReplaceAll(s, old, new)
Expand Down Expand Up @@ -114,7 +182,7 @@ func FuncStringSQuote(in ...any) string {

for _, s := range in {
if s != nil {
out = append(out, fmt.Sprintf("%q", strval(s)))
out = append(out, fmt.Sprintf("'%s'", strval(s)))
}
}

Expand Down

0 comments on commit f685f24

Please sign in to comment.