Skip to content

Commit

Permalink
feat(config): support defining template content in toml
Browse files Browse the repository at this point in the history
  • Loading branch information
sweatybridge committed Nov 12, 2024
1 parent 1e5e47b commit b81dd69
Show file tree
Hide file tree
Showing 3 changed files with 246 additions and 29 deletions.
104 changes: 104 additions & 0 deletions pkg/config/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ type (
emailTemplate struct {
Subject string `toml:"subject"`
ContentPath string `toml:"content_path"`
Content string `toml:"content"`
}

sms struct {
Expand Down Expand Up @@ -326,6 +327,52 @@ func (e email) toAuthConfigBody(body *v1API.UpdateAuthConfigBody) {
// Setting a single empty string disables SMTP
body.SmtpHost = cast.Ptr("")
}
if len(e.Template) == 0 {
return
}
var tmpl *emailTemplate
tmpl = cast.Ptr(e.Template["invite"])
if len(tmpl.Subject) > 0 {
body.MailerSubjectsInvite = &tmpl.Subject
}
if len(tmpl.Content) > 0 {
body.MailerTemplatesInviteContent = &tmpl.Content
}
tmpl = cast.Ptr(e.Template["confirmation"])
if len(tmpl.Subject) > 0 {
body.MailerSubjectsConfirmation = &tmpl.Subject
}
if len(tmpl.Content) > 0 {
body.MailerTemplatesConfirmationContent = &tmpl.Content
}
tmpl = cast.Ptr(e.Template["recovery"])
if len(tmpl.Subject) > 0 {
body.MailerSubjectsRecovery = &tmpl.Subject
}
if len(tmpl.Content) > 0 {
body.MailerTemplatesRecoveryContent = &tmpl.Content
}
tmpl = cast.Ptr(e.Template["magic_link"])
if len(tmpl.Subject) > 0 {
body.MailerSubjectsMagicLink = &tmpl.Subject
}
if len(tmpl.Content) > 0 {
body.MailerTemplatesMagicLinkContent = &tmpl.Content
}
tmpl = cast.Ptr(e.Template["email_change"])
if len(tmpl.Subject) > 0 {
body.MailerSubjectsEmailChange = &tmpl.Subject
}
if len(tmpl.Content) > 0 {
body.MailerTemplatesEmailChangeContent = &tmpl.Content
}
tmpl = cast.Ptr(e.Template["reauthentication"])
if len(tmpl.Subject) > 0 {
body.MailerSubjectsReauthentication = &tmpl.Subject
}
if len(tmpl.Content) > 0 {
body.MailerTemplatesReauthenticationContent = &tmpl.Content
}
}

func (e *email) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) {
Expand All @@ -352,6 +399,63 @@ func (e *email) fromAuthConfig(remoteConfig v1API.AuthConfigResponse) {
} else {
e.Smtp = nil
}
if len(e.Template) == 0 {
return
}
var tmpl emailTemplate
tmpl = e.Template["invite"]
if len(tmpl.Subject) > 0 {
tmpl.Subject = cast.Val(remoteConfig.MailerSubjectsInvite, "")
}
if len(tmpl.Content) > 0 {
tmpl.Content = cast.Val(remoteConfig.MailerTemplatesInviteContent, "")
}
e.Template["invite"] = tmpl

tmpl = e.Template["confirmation"]
if len(tmpl.Subject) > 0 {
tmpl.Subject = cast.Val(remoteConfig.MailerSubjectsConfirmation, "")
}
if len(tmpl.Content) > 0 {
tmpl.Content = cast.Val(remoteConfig.MailerTemplatesConfirmationContent, "")
}
e.Template["confirmation"] = tmpl

tmpl = e.Template["recovery"]
if len(tmpl.Subject) > 0 {
tmpl.Subject = cast.Val(remoteConfig.MailerSubjectsRecovery, "")
}
if len(tmpl.Content) > 0 {
tmpl.Content = cast.Val(remoteConfig.MailerTemplatesRecoveryContent, "")
}
e.Template["recovery"] = tmpl

tmpl = e.Template["magic_link"]
if len(tmpl.Subject) > 0 {
tmpl.Subject = cast.Val(remoteConfig.MailerSubjectsMagicLink, "")
}
if len(tmpl.Content) > 0 {
tmpl.Content = cast.Val(remoteConfig.MailerTemplatesMagicLinkContent, "")
}
e.Template["magic_link"] = tmpl

tmpl = e.Template["email_change"]
if len(tmpl.Subject) > 0 {
tmpl.Subject = cast.Val(remoteConfig.MailerSubjectsEmailChange, "")
}
if len(tmpl.Content) > 0 {
tmpl.Content = cast.Val(remoteConfig.MailerTemplatesEmailChangeContent, "")
}
e.Template["email_change"] = tmpl

tmpl = e.Template["reauthentication"]
if len(tmpl.Subject) > 0 {
tmpl.Subject = cast.Val(remoteConfig.MailerSubjectsReauthentication, "")
}
if len(tmpl.Content) > 0 {
tmpl.Content = cast.Val(remoteConfig.MailerTemplatesReauthenticationContent, "")
}
e.Template["reauthentication"] = tmpl
}

func (s sms) toAuthConfigBody(body *v1API.UpdateAuthConfigBody) {
Expand Down
147 changes: 127 additions & 20 deletions pkg/config/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,11 +228,30 @@ func TestEmailDiff(t *testing.T) {
EnableConfirmations: true,
SecurePasswordChange: true,
Template: map[string]emailTemplate{
"invite": {},
"confirmation": {},
"recovery": {},
"magic_link": {},
"email_change": {},
"invite": {
Subject: "invite-subject",
Content: "invite-content",
},
"confirmation": {
Subject: "confirmation-subject",
Content: "confirmation-content",
},
"recovery": {
Subject: "recovery-subject",
Content: "recovery-content",
},
"magic_link": {
Subject: "magic-link-subject",
Content: "magic-link-content",
},
"email_change": {
Subject: "email-change-subject",
Content: "email-change-content",
},
"reauthentication": {
Subject: "reauthentication-subject",
Content: "reauthentication-content",
},
},
Smtp: &smtp{
Host: "smtp.sendgrid.net",
Expand Down Expand Up @@ -261,6 +280,19 @@ func TestEmailDiff(t *testing.T) {
SmtpAdminEmail: cast.Ptr("[email protected]"),
SmtpSenderName: cast.Ptr("Admin"),
SmtpMaxFrequency: cast.Ptr(1),
// Custom templates
MailerSubjectsInvite: cast.Ptr("invite-subject"),
MailerTemplatesInviteContent: cast.Ptr("invite-content"),
MailerSubjectsConfirmation: cast.Ptr("confirmation-subject"),
MailerTemplatesConfirmationContent: cast.Ptr("confirmation-content"),
MailerSubjectsRecovery: cast.Ptr("recovery-subject"),
MailerTemplatesRecoveryContent: cast.Ptr("recovery-content"),
MailerSubjectsMagicLink: cast.Ptr("magic-link-subject"),
MailerTemplatesMagicLinkContent: cast.Ptr("magic-link-content"),
MailerSubjectsEmailChange: cast.Ptr("email-change-subject"),
MailerTemplatesEmailChangeContent: cast.Ptr("email-change-content"),
MailerSubjectsReauthentication: cast.Ptr("reauthentication-subject"),
MailerTemplatesReauthenticationContent: cast.Ptr("reauthentication-content"),
})
// Check error
assert.NoError(t, err)
Expand All @@ -275,11 +307,28 @@ func TestEmailDiff(t *testing.T) {
EnableConfirmations: true,
SecurePasswordChange: true,
Template: map[string]emailTemplate{
"invite": {},
"confirmation": {},
"recovery": {},
"magic_link": {},
"email_change": {},
"invite": {
Subject: "invite-subject",
Content: "invite-content",
},
"confirmation": {
Subject: "confirmation-subject",
},
"recovery": {
Content: "recovery-content",
},
"magic_link": {
Subject: "magic-link-subject",
Content: "magic-link-content",
},
"email_change": {
Subject: "email-change-subject",
Content: "email-change-content",
},
"reauthentication": {
Subject: "reauthentication-subject",
Content: "reauthentication-content",
},
},
Smtp: &smtp{
Host: "smtp.sendgrid.net",
Expand All @@ -302,9 +351,15 @@ func TestEmailDiff(t *testing.T) {
MailerOtpExp: 3600,
SecurityUpdatePasswordRequireReauthentication: cast.Ptr(false),
SmtpMaxFrequency: cast.Ptr(60),
// Custom templates
MailerTemplatesConfirmationContent: cast.Ptr("confirmation-content"),
MailerSubjectsRecovery: cast.Ptr("recovery-subject"),
MailerSubjectsMagicLink: cast.Ptr("magic-link-subject"),
MailerTemplatesEmailChangeContent: cast.Ptr("email-change-content"),
})
// Check error
assert.NoError(t, err)

assert.Contains(t, string(diff), ` [email]`)
assert.Contains(t, string(diff), `-enable_signup = false`)
assert.Contains(t, string(diff), `-double_confirm_changes = false`)
Expand All @@ -320,18 +375,56 @@ func TestEmailDiff(t *testing.T) {
assert.Contains(t, string(diff), `+max_frequency = "1s"`)
assert.Contains(t, string(diff), `+otp_length = 8`)
assert.Contains(t, string(diff), `+otp_expiry = 86400`)

assert.Contains(t, string(diff), `+[email.smtp]`)
assert.Contains(t, string(diff), `+host = "smtp.sendgrid.net"`)
assert.Contains(t, string(diff), `+port = 587`)
assert.Contains(t, string(diff), `+user = "apikey"`)
assert.Contains(t, string(diff), `+pass = "hash:ed64b7695a606bc6ab4fcb41fe815b5ddf1063ccbc87afe1fa89756635db520e"`)
assert.Contains(t, string(diff), `+admin_email = "[email protected]"`)
assert.Contains(t, string(diff), `+sender_name = "Admin"`)

assert.Contains(t, string(diff), ` [email.template]`)
assert.Contains(t, string(diff), ` [email.template.confirmation]`)
assert.Contains(t, string(diff), `-subject = ""`)
assert.Contains(t, string(diff), `+subject = "confirmation-subject"`)
assert.Contains(t, string(diff), ` content_path = ""`)
assert.Contains(t, string(diff), ` content = ""`)
assert.Contains(t, string(diff), ` [email.template.email_change]`)
assert.Contains(t, string(diff), `-subject = ""`)
assert.Contains(t, string(diff), `+subject = "email-change-subject"`)
assert.Contains(t, string(diff), ` content_path = ""`)
assert.Contains(t, string(diff), ` content = "email-change-content"`)
assert.Contains(t, string(diff), ` [email.template.invite]`)
assert.Contains(t, string(diff), `-subject = ""`)
assert.Contains(t, string(diff), `-content_path = ""`)
assert.Contains(t, string(diff), `-content = ""`)
assert.Contains(t, string(diff), `+subject = "invite-subject"`)
assert.Contains(t, string(diff), `+content_path = ""`)
assert.Contains(t, string(diff), `+content = "invite-content"`)
assert.Contains(t, string(diff), ` [email.template.magic_link]`)
assert.Contains(t, string(diff), ` subject = "magic-link-subject"`)
assert.Contains(t, string(diff), ` content_path = ""`)
assert.Contains(t, string(diff), `-content = ""`)
assert.Contains(t, string(diff), `+content = "magic-link-content"`)
assert.Contains(t, string(diff), ` [email.template.recovery]`)
assert.Contains(t, string(diff), ` subject = ""`)
assert.Contains(t, string(diff), ` content_path = ""`)
assert.Contains(t, string(diff), `-content = ""`)
assert.Contains(t, string(diff), `+content = "recovery-content"`)
})

t.Run("local disabled remote enabled", func(t *testing.T) {
c := newWithDefaults()
c.Email = email{
EnableConfirmations: false,
Template: map[string]emailTemplate{
"invite": {},
"confirmation": {},
"recovery": {},
"magic_link": {},
"email_change": {},
"invite": {},
"confirmation": {},
"recovery": {},
"magic_link": {},
"email_change": {},
"reauthentication": {},
},
MaxFrequency: time.Minute,
OtpLength: 8,
Expand All @@ -352,6 +445,19 @@ func TestEmailDiff(t *testing.T) {
SmtpAdminEmail: cast.Ptr("[email protected]"),
SmtpSenderName: cast.Ptr("Admin"),
SmtpMaxFrequency: cast.Ptr(1),
// Custom templates
MailerSubjectsInvite: cast.Ptr("invite-subject"),
MailerTemplatesInviteContent: cast.Ptr("invite-content"),
MailerSubjectsConfirmation: cast.Ptr("confirmation-subject"),
MailerTemplatesConfirmationContent: cast.Ptr("confirmation-content"),
MailerSubjectsRecovery: cast.Ptr("recovery-subject"),
MailerTemplatesRecoveryContent: cast.Ptr("recovery-content"),
MailerSubjectsMagicLink: cast.Ptr("magic-link-subject"),
MailerTemplatesMagicLinkContent: cast.Ptr("magic-link-content"),
MailerSubjectsEmailChange: cast.Ptr("email-change-subject"),
MailerTemplatesEmailChangeContent: cast.Ptr("email-change-content"),
MailerSubjectsReauthentication: cast.Ptr("reauthentication-subject"),
MailerTemplatesReauthenticationContent: cast.Ptr("reauthentication-content"),
})
// Check error
assert.NoError(t, err)
Expand Down Expand Up @@ -386,11 +492,12 @@ func TestEmailDiff(t *testing.T) {
c.Email = email{
EnableConfirmations: false,
Template: map[string]emailTemplate{
"invite": {},
"confirmation": {},
"recovery": {},
"magic_link": {},
"email_change": {},
"invite": {},
"confirmation": {},
"recovery": {},
"magic_link": {},
"email_change": {},
"reauthentication": {},
},
MaxFrequency: time.Minute,
OtpLength: 6,
Expand Down
24 changes: 15 additions & 9 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,11 +292,12 @@ func NewConfig(editors ...ConfigEditor) config {
Image: gotrueImage,
Email: email{
Template: map[string]emailTemplate{
"invite": {},
"confirmation": {},
"recovery": {},
"magic_link": {},
"email_change": {},
"invite": {},
"confirmation": {},
"recovery": {},
"magic_link": {},
"email_change": {},
"reauthentication": {},
},
},
External: map[string]provider{
Expand Down Expand Up @@ -817,11 +818,16 @@ func (c *seed) loadSeedPaths(basePath string, fsys fs.FS) error {

func (e *email) validate(fsys fs.FS) (err error) {
for name, tmpl := range e.Template {
if len(tmpl.ContentPath) > 0 {
if _, err = fs.Stat(fsys, filepath.Clean(tmpl.ContentPath)); err != nil {
return errors.Errorf("Invalid config for auth.email.%s.content_path: %s", name, tmpl.ContentPath)
}
if len(tmpl.Content) > 0 || len(tmpl.ContentPath) == 0 {
continue
}
// Load the file content of the template within the config
if content, err := fs.ReadFile(fsys, filepath.Clean(tmpl.ContentPath)); err != nil {
return errors.Errorf("Invalid config for auth.email.%s.content_path: %w", name, err)
} else {
tmpl.Content = string(content)
}
e.Template[name] = tmpl
}
if e.Smtp != nil {
if len(e.Smtp.Host) == 0 {
Expand Down

0 comments on commit b81dd69

Please sign in to comment.