[#275] added support to customize the default user email templates from the Admin UI
This commit is contained in:
+134
-78
@@ -10,48 +10,46 @@ import (
|
||||
validation "github.com/go-ozzo/ozzo-validation/v4"
|
||||
"github.com/go-ozzo/ozzo-validation/v4/is"
|
||||
"github.com/pocketbase/pocketbase/tools/auth"
|
||||
"github.com/pocketbase/pocketbase/tools/rest"
|
||||
"github.com/pocketbase/pocketbase/tools/security"
|
||||
)
|
||||
|
||||
// Common settings placeholder tokens
|
||||
const (
|
||||
EmailPlaceholderAppUrl string = "%APP_URL%"
|
||||
EmailPlaceholderToken string = "%TOKEN%"
|
||||
)
|
||||
|
||||
// Settings defines common app configuration options.
|
||||
type Settings struct {
|
||||
mux sync.RWMutex
|
||||
|
||||
Meta MetaConfig `form:"meta" json:"meta"`
|
||||
Logs LogsConfig `form:"logs" json:"logs"`
|
||||
Smtp SmtpConfig `form:"smtp" json:"smtp"`
|
||||
S3 S3Config `form:"s3" json:"s3"`
|
||||
AdminAuthToken TokenConfig `form:"adminAuthToken" json:"adminAuthToken"`
|
||||
AdminPasswordResetToken TokenConfig `form:"adminPasswordResetToken" json:"adminPasswordResetToken"`
|
||||
UserAuthToken TokenConfig `form:"userAuthToken" json:"userAuthToken"`
|
||||
UserPasswordResetToken TokenConfig `form:"userPasswordResetToken" json:"userPasswordResetToken"`
|
||||
UserEmailChangeToken TokenConfig `form:"userEmailChangeToken" json:"userEmailChangeToken"`
|
||||
UserVerificationToken TokenConfig `form:"userVerificationToken" json:"userVerificationToken"`
|
||||
EmailAuth EmailAuthConfig `form:"emailAuth" json:"emailAuth"`
|
||||
GoogleAuth AuthProviderConfig `form:"googleAuth" json:"googleAuth"`
|
||||
FacebookAuth AuthProviderConfig `form:"facebookAuth" json:"facebookAuth"`
|
||||
GithubAuth AuthProviderConfig `form:"githubAuth" json:"githubAuth"`
|
||||
GitlabAuth AuthProviderConfig `form:"gitlabAuth" json:"gitlabAuth"`
|
||||
Meta MetaConfig `form:"meta" json:"meta"`
|
||||
Logs LogsConfig `form:"logs" json:"logs"`
|
||||
Smtp SmtpConfig `form:"smtp" json:"smtp"`
|
||||
S3 S3Config `form:"s3" json:"s3"`
|
||||
|
||||
AdminAuthToken TokenConfig `form:"adminAuthToken" json:"adminAuthToken"`
|
||||
AdminPasswordResetToken TokenConfig `form:"adminPasswordResetToken" json:"adminPasswordResetToken"`
|
||||
UserAuthToken TokenConfig `form:"userAuthToken" json:"userAuthToken"`
|
||||
UserPasswordResetToken TokenConfig `form:"userPasswordResetToken" json:"userPasswordResetToken"`
|
||||
UserEmailChangeToken TokenConfig `form:"userEmailChangeToken" json:"userEmailChangeToken"`
|
||||
UserVerificationToken TokenConfig `form:"userVerificationToken" json:"userVerificationToken"`
|
||||
|
||||
EmailAuth EmailAuthConfig `form:"emailAuth" json:"emailAuth"`
|
||||
GoogleAuth AuthProviderConfig `form:"googleAuth" json:"googleAuth"`
|
||||
FacebookAuth AuthProviderConfig `form:"facebookAuth" json:"facebookAuth"`
|
||||
GithubAuth AuthProviderConfig `form:"githubAuth" json:"githubAuth"`
|
||||
GitlabAuth AuthProviderConfig `form:"gitlabAuth" json:"gitlabAuth"`
|
||||
}
|
||||
|
||||
// NewSettings creates and returns a new default Settings instance.
|
||||
func NewSettings() *Settings {
|
||||
return &Settings{
|
||||
Meta: MetaConfig{
|
||||
AppName: "Acme",
|
||||
AppUrl: "http://localhost:8090",
|
||||
SenderName: "Support",
|
||||
SenderAddress: "support@example.com",
|
||||
UserVerificationUrl: EmailPlaceholderAppUrl + "/_/#/users/confirm-verification/" + EmailPlaceholderToken,
|
||||
UserResetPasswordUrl: EmailPlaceholderAppUrl + "/_/#/users/confirm-password-reset/" + EmailPlaceholderToken,
|
||||
UserConfirmEmailChangeUrl: EmailPlaceholderAppUrl + "/_/#/users/confirm-email-change/" + EmailPlaceholderToken,
|
||||
AppName: "Acme",
|
||||
AppUrl: "http://localhost:8090",
|
||||
SenderName: "Support",
|
||||
SenderAddress: "support@example.com",
|
||||
VerificationTemplate: defaultVerificationTemplate,
|
||||
ResetPasswordTemplate: defaultResetPasswordTemplate,
|
||||
ConfirmEmailChangeTemplate: defaultConfirmEmailChangeTemplate,
|
||||
},
|
||||
|
||||
Logs: LogsConfig{
|
||||
MaxDays: 7,
|
||||
},
|
||||
@@ -194,6 +192,9 @@ func (s *Settings) RedactClone() (*Settings, error) {
|
||||
// NamedAuthProviderConfigs returns a map with all registered OAuth2
|
||||
// provider configurations (indexed by their name identifier).
|
||||
func (s *Settings) NamedAuthProviderConfigs() map[string]AuthProviderConfig {
|
||||
s.mux.RLock()
|
||||
defer s.mux.RUnlock()
|
||||
|
||||
return map[string]AuthProviderConfig{
|
||||
auth.NameGoogle: s.GoogleAuth,
|
||||
auth.NameFacebook: s.FacebookAuth,
|
||||
@@ -267,13 +268,13 @@ func (c S3Config) Validate() error {
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type MetaConfig struct {
|
||||
AppName string `form:"appName" json:"appName"`
|
||||
AppUrl string `form:"appUrl" json:"appUrl"`
|
||||
SenderName string `form:"senderName" json:"senderName"`
|
||||
SenderAddress string `form:"senderAddress" json:"senderAddress"`
|
||||
UserVerificationUrl string `form:"userVerificationUrl" json:"userVerificationUrl"`
|
||||
UserResetPasswordUrl string `form:"userResetPasswordUrl" json:"userResetPasswordUrl"`
|
||||
UserConfirmEmailChangeUrl string `form:"userConfirmEmailChangeUrl" json:"userConfirmEmailChangeUrl"`
|
||||
AppName string `form:"appName" json:"appName"`
|
||||
AppUrl string `form:"appUrl" json:"appUrl"`
|
||||
SenderName string `form:"senderName" json:"senderName"`
|
||||
SenderAddress string `form:"senderAddress" json:"senderAddress"`
|
||||
VerificationTemplate EmailTemplate `form:"verificationTemplate" json:"verificationTemplate"`
|
||||
ResetPasswordTemplate EmailTemplate `form:"resetPasswordTemplate" json:"resetPasswordTemplate"`
|
||||
ConfirmEmailChangeTemplate EmailTemplate `form:"confirmEmailChangeTemplate" json:"confirmEmailChangeTemplate"`
|
||||
}
|
||||
|
||||
// Validate makes MetaConfig validatable by implementing [validation.Validatable] interface.
|
||||
@@ -283,34 +284,45 @@ func (c MetaConfig) Validate() error {
|
||||
validation.Field(&c.AppUrl, validation.Required, is.URL),
|
||||
validation.Field(&c.SenderName, validation.Required, validation.Length(1, 255)),
|
||||
validation.Field(&c.SenderAddress, is.Email, validation.Required),
|
||||
validation.Field(&c.VerificationTemplate, validation.Required),
|
||||
validation.Field(&c.ResetPasswordTemplate, validation.Required),
|
||||
validation.Field(&c.ConfirmEmailChangeTemplate, validation.Required),
|
||||
)
|
||||
}
|
||||
|
||||
type EmailTemplate struct {
|
||||
Body string `form:"body" json:"body"`
|
||||
Subject string `form:"subject" json:"subject"`
|
||||
ActionUrl string `form:"actionUrl" json:"actionUrl"`
|
||||
}
|
||||
|
||||
// Validate makes EmailTemplate validatable by implementing [validation.Validatable] interface.
|
||||
func (t EmailTemplate) Validate() error {
|
||||
return validation.ValidateStruct(&t,
|
||||
validation.Field(&t.Subject, validation.Required),
|
||||
validation.Field(
|
||||
&c.UserVerificationUrl,
|
||||
&t.Body,
|
||||
validation.Required,
|
||||
validation.By(c.checkPlaceholders(EmailPlaceholderToken)),
|
||||
validation.By(checkPlaceholderParams(EmailPlaceholderActionUrl)),
|
||||
),
|
||||
validation.Field(
|
||||
&c.UserResetPasswordUrl,
|
||||
&t.ActionUrl,
|
||||
validation.Required,
|
||||
validation.By(c.checkPlaceholders(EmailPlaceholderToken)),
|
||||
),
|
||||
validation.Field(
|
||||
&c.UserConfirmEmailChangeUrl,
|
||||
validation.Required,
|
||||
validation.By(c.checkPlaceholders(EmailPlaceholderToken)),
|
||||
validation.By(checkPlaceholderParams(EmailPlaceholderToken)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *MetaConfig) checkPlaceholders(params ...string) validation.RuleFunc {
|
||||
func checkPlaceholderParams(params ...string) validation.RuleFunc {
|
||||
return func(value any) error {
|
||||
v, _ := value.(string)
|
||||
if v == "" {
|
||||
return nil // nothing to check
|
||||
}
|
||||
|
||||
for _, param := range params {
|
||||
if !strings.Contains(v, param) {
|
||||
return validation.NewError("validation_missing_required_param", fmt.Sprintf("Missing required parameter %q", param))
|
||||
return validation.NewError(
|
||||
"validation_missing_required_param",
|
||||
fmt.Sprintf("Missing required parameter %q", param),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,6 +330,50 @@ func (c *MetaConfig) checkPlaceholders(params ...string) validation.RuleFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve replaces the placeholder parameters in the current email
|
||||
// template and returns its components as ready-to-use strings.
|
||||
func (t EmailTemplate) Resolve(
|
||||
appName string,
|
||||
appUrl,
|
||||
token string,
|
||||
) (subject, body, actionUrl string) {
|
||||
// replace action url placeholder params (if any)
|
||||
actionUrlParams := map[string]string{
|
||||
EmailPlaceholderAppName: appName,
|
||||
EmailPlaceholderAppUrl: appUrl,
|
||||
EmailPlaceholderToken: token,
|
||||
}
|
||||
actionUrl = t.ActionUrl
|
||||
for k, v := range actionUrlParams {
|
||||
actionUrl = strings.ReplaceAll(actionUrl, k, v)
|
||||
}
|
||||
actionUrl, _ = rest.NormalizeUrl(actionUrl)
|
||||
|
||||
// replace body placeholder params (if any)
|
||||
bodyParams := map[string]string{
|
||||
EmailPlaceholderAppName: appName,
|
||||
EmailPlaceholderAppUrl: appUrl,
|
||||
EmailPlaceholderToken: token,
|
||||
EmailPlaceholderActionUrl: actionUrl,
|
||||
}
|
||||
body = t.Body
|
||||
for k, v := range bodyParams {
|
||||
body = strings.ReplaceAll(body, k, v)
|
||||
}
|
||||
|
||||
// replace subject placeholder params (if any)
|
||||
subjectParams := map[string]string{
|
||||
EmailPlaceholderAppName: appName,
|
||||
EmailPlaceholderAppUrl: appUrl,
|
||||
}
|
||||
subject = t.Subject
|
||||
for k, v := range subjectParams {
|
||||
subject = strings.ReplaceAll(subject, k, v)
|
||||
}
|
||||
|
||||
return subject, body, actionUrl
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type LogsConfig struct {
|
||||
@@ -333,6 +389,35 @@ func (c LogsConfig) Validate() error {
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type EmailAuthConfig struct {
|
||||
Enabled bool `form:"enabled" json:"enabled"`
|
||||
ExceptDomains []string `form:"exceptDomains" json:"exceptDomains"`
|
||||
OnlyDomains []string `form:"onlyDomains" json:"onlyDomains"`
|
||||
MinPasswordLength int `form:"minPasswordLength" json:"minPasswordLength"`
|
||||
}
|
||||
|
||||
// Validate makes `EmailAuthConfig` validatable by implementing [validation.Validatable] interface.
|
||||
func (c EmailAuthConfig) Validate() error {
|
||||
return validation.ValidateStruct(&c,
|
||||
validation.Field(
|
||||
&c.ExceptDomains,
|
||||
validation.When(len(c.OnlyDomains) > 0, validation.Empty).Else(validation.Each(is.Domain)),
|
||||
),
|
||||
validation.Field(
|
||||
&c.OnlyDomains,
|
||||
validation.When(len(c.ExceptDomains) > 0, validation.Empty).Else(validation.Each(is.Domain)),
|
||||
),
|
||||
validation.Field(
|
||||
&c.MinPasswordLength,
|
||||
validation.When(c.Enabled, validation.Required),
|
||||
validation.Min(5),
|
||||
validation.Max(100),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type AuthProviderConfig struct {
|
||||
Enabled bool `form:"enabled" json:"enabled"`
|
||||
AllowRegistrations bool `form:"allowRegistrations" json:"allowRegistrations"`
|
||||
@@ -382,32 +467,3 @@ func (c AuthProviderConfig) SetupProvider(provider auth.Provider) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type EmailAuthConfig struct {
|
||||
Enabled bool `form:"enabled" json:"enabled"`
|
||||
ExceptDomains []string `form:"exceptDomains" json:"exceptDomains"`
|
||||
OnlyDomains []string `form:"onlyDomains" json:"onlyDomains"`
|
||||
MinPasswordLength int `form:"minPasswordLength" json:"minPasswordLength"`
|
||||
}
|
||||
|
||||
// Validate makes `EmailAuthConfig` validatable by implementing [validation.Validatable] interface.
|
||||
func (c EmailAuthConfig) Validate() error {
|
||||
return validation.ValidateStruct(&c,
|
||||
validation.Field(
|
||||
&c.ExceptDomains,
|
||||
validation.When(len(c.OnlyDomains) > 0, validation.Empty).Else(validation.Each(is.Domain)),
|
||||
),
|
||||
validation.Field(
|
||||
&c.OnlyDomains,
|
||||
validation.When(len(c.ExceptDomains) > 0, validation.Empty).Else(validation.Each(is.Domain)),
|
||||
),
|
||||
validation.Field(
|
||||
&c.MinPasswordLength,
|
||||
validation.When(c.Enabled, validation.Required),
|
||||
validation.Min(5),
|
||||
validation.Max(100),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package core
|
||||
|
||||
// Common settings placeholder tokens
|
||||
const (
|
||||
EmailPlaceholderAppName string = "{APP_NAME}"
|
||||
EmailPlaceholderAppUrl string = "{APP_URL}"
|
||||
EmailPlaceholderToken string = "{TOKEN}"
|
||||
EmailPlaceholderActionUrl string = "{ACTION_URL}"
|
||||
)
|
||||
|
||||
var defaultVerificationTemplate = EmailTemplate{
|
||||
Subject: "Verify your " + EmailPlaceholderAppName + " email",
|
||||
Body: `<p>Hello,</p>
|
||||
<p>Thank you for joining us at ` + EmailPlaceholderAppName + `.</p>
|
||||
<p>Click on the button below to verify your email address.</p>
|
||||
<p>
|
||||
<a class="btn" href="` + EmailPlaceholderActionUrl + `" target="_blank" rel="noopener">Verify</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Thanks,<br/>
|
||||
` + EmailPlaceholderAppName + ` team
|
||||
</p>`,
|
||||
ActionUrl: EmailPlaceholderAppUrl + "/_/#/users/confirm-verification/" + EmailPlaceholderToken,
|
||||
}
|
||||
|
||||
var defaultResetPasswordTemplate = EmailTemplate{
|
||||
Subject: "Reset your " + EmailPlaceholderAppName + " password",
|
||||
Body: `<p>Hello,</p>
|
||||
<p>Click on the button below to reset your password.</p>
|
||||
<p>
|
||||
<a class="btn" href="` + EmailPlaceholderActionUrl + `" target="_blank" rel="noopener">Reset password</a>
|
||||
</p>
|
||||
<p><i>If you didn't ask to reset your password, you can ignore this email.</i></p>
|
||||
<p>
|
||||
Thanks,<br/>
|
||||
` + EmailPlaceholderAppName + ` team
|
||||
</p>`,
|
||||
ActionUrl: EmailPlaceholderAppUrl + "/_/#/users/confirm-password-reset/" + EmailPlaceholderToken,
|
||||
}
|
||||
|
||||
var defaultConfirmEmailChangeTemplate = EmailTemplate{
|
||||
Subject: "Confirm your " + EmailPlaceholderAppName + " new email address",
|
||||
Body: `<p>Hello,</p>
|
||||
<p>Click on the button below to confirm your new email address.</p>
|
||||
<p>
|
||||
<a class="btn" href="` + EmailPlaceholderActionUrl + `" target="_blank" rel="noopener">Confirm new email</a>
|
||||
</p>
|
||||
<p><i>If you didn't ask to change your email address, you can ignore this email.</i></p>
|
||||
<p>
|
||||
Thanks,<br/>
|
||||
` + EmailPlaceholderAppName + ` team
|
||||
</p>`,
|
||||
ActionUrl: EmailPlaceholderAppUrl + "/_/#/users/confirm-email-change/" + EmailPlaceholderToken,
|
||||
}
|
||||
+166
-22
@@ -2,9 +2,11 @@ package core_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
validation "github.com/go-ozzo/ozzo-validation/v4"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/tools/auth"
|
||||
)
|
||||
@@ -172,7 +174,7 @@ func TestSettingsRedactClone(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := `{"meta":{"appName":"test123","appUrl":"http://localhost:8090","senderName":"Support","senderAddress":"support@example.com","userVerificationUrl":"%APP_URL%/_/#/users/confirm-verification/%TOKEN%","userResetPasswordUrl":"%APP_URL%/_/#/users/confirm-password-reset/%TOKEN%","userConfirmEmailChangeUrl":"%APP_URL%/_/#/users/confirm-email-change/%TOKEN%"},"logs":{"maxDays":7},"smtp":{"enabled":false,"host":"smtp.example.com","port":587,"username":"","password":"******","tls":true},"s3":{"enabled":false,"bucket":"","region":"","endpoint":"","accessKey":"","secret":"******","forcePathStyle":false},"adminAuthToken":{"secret":"******","duration":1209600},"adminPasswordResetToken":{"secret":"******","duration":1800},"userAuthToken":{"secret":"******","duration":1209600},"userPasswordResetToken":{"secret":"******","duration":1800},"userEmailChangeToken":{"secret":"******","duration":1800},"userVerificationToken":{"secret":"******","duration":604800},"emailAuth":{"enabled":true,"exceptDomains":null,"onlyDomains":null,"minPasswordLength":8},"googleAuth":{"enabled":false,"allowRegistrations":true,"clientSecret":"******"},"facebookAuth":{"enabled":false,"allowRegistrations":true,"clientSecret":"******"},"githubAuth":{"enabled":false,"allowRegistrations":true,"clientSecret":"******"},"gitlabAuth":{"enabled":false,"allowRegistrations":true,"clientSecret":"******"}}`
|
||||
expected := `{"meta":{"appName":"test123","appUrl":"http://localhost:8090","senderName":"Support","senderAddress":"support@example.com","verificationTemplate":{"body":"\u003cp\u003eHello,\u003c/p\u003e\n\u003cp\u003eThank you for joining us at {APP_NAME}.\u003c/p\u003e\n\u003cp\u003eClick on the button below to verify your email address.\u003c/p\u003e\n\u003cp\u003e\n \u003ca class=\"btn\" href=\"{ACTION_URL}\" target=\"_blank\" rel=\"noopener\"\u003eVerify\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp\u003e\n Thanks,\u003cbr/\u003e\n {APP_NAME} team\n\u003c/p\u003e","subject":"Verify your {APP_NAME} email","actionUrl":"{APP_URL}/_/#/users/confirm-verification/{TOKEN}"},"resetPasswordTemplate":{"body":"\u003cp\u003eHello,\u003c/p\u003e\n\u003cp\u003eClick on the button below to reset your password.\u003c/p\u003e\n\u003cp\u003e\n \u003ca class=\"btn\" href=\"{ACTION_URL}\" target=\"_blank\" rel=\"noopener\"\u003eReset password\u003c/a\u003e\n\u003c/p\u003e\n\u003cp\u003e\u003ci\u003eIf you didn't ask to reset your password, you can ignore this email.\u003c/i\u003e\u003c/p\u003e\n\u003cp\u003e\n Thanks,\u003cbr/\u003e\n {APP_NAME} team\n\u003c/p\u003e","subject":"Reset your {APP_NAME} password","actionUrl":"{APP_URL}/_/#/users/confirm-password-reset/{TOKEN}"},"confirmEmailChangeTemplate":{"body":"\u003cp\u003eHello,\u003c/p\u003e\n\u003cp\u003eClick on the button below to confirm your new email address.\u003c/p\u003e\n\u003cp\u003e\n \u003ca class=\"btn\" href=\"{ACTION_URL}\" target=\"_blank\" rel=\"noopener\"\u003eConfirm new email\u003c/a\u003e\n\u003c/p\u003e\n\u003cp\u003e\u003ci\u003eIf you didn't ask to change your email address, you can ignore this email.\u003c/i\u003e\u003c/p\u003e\n\u003cp\u003e\n Thanks,\u003cbr/\u003e\n {APP_NAME} team\n\u003c/p\u003e","subject":"Confirm your {APP_NAME} new email address","actionUrl":"{APP_URL}/_/#/users/confirm-email-change/{TOKEN}"}},"logs":{"maxDays":7},"smtp":{"enabled":false,"host":"smtp.example.com","port":587,"username":"","password":"******","tls":true},"s3":{"enabled":false,"bucket":"","region":"","endpoint":"","accessKey":"","secret":"******","forcePathStyle":false},"adminAuthToken":{"secret":"******","duration":1209600},"adminPasswordResetToken":{"secret":"******","duration":1800},"userAuthToken":{"secret":"******","duration":1209600},"userPasswordResetToken":{"secret":"******","duration":1800},"userEmailChangeToken":{"secret":"******","duration":1800},"userVerificationToken":{"secret":"******","duration":604800},"emailAuth":{"enabled":true,"exceptDomains":null,"onlyDomains":null,"minPasswordLength":8},"googleAuth":{"enabled":false,"allowRegistrations":true,"clientSecret":"******"},"facebookAuth":{"enabled":false,"allowRegistrations":true,"clientSecret":"******"},"githubAuth":{"enabled":false,"allowRegistrations":true,"clientSecret":"******"},"gitlabAuth":{"enabled":false,"allowRegistrations":true,"clientSecret":"******"}}`
|
||||
|
||||
if encodedStr := string(encoded); encodedStr != expected {
|
||||
t.Fatalf("Expected %v, got \n%v", expected, encodedStr)
|
||||
@@ -355,6 +357,24 @@ func TestS3ConfigValidate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMetaConfigValidate(t *testing.T) {
|
||||
invalidTemplate := core.EmailTemplate{
|
||||
Subject: "test",
|
||||
ActionUrl: "test",
|
||||
Body: "test",
|
||||
}
|
||||
|
||||
noPlaceholdersTemplate := core.EmailTemplate{
|
||||
Subject: "test",
|
||||
ActionUrl: "http://example.com",
|
||||
Body: "test",
|
||||
}
|
||||
|
||||
withPlaceholdersTemplate := core.EmailTemplate{
|
||||
Subject: "test",
|
||||
ActionUrl: "http://example.com" + core.EmailPlaceholderToken,
|
||||
Body: "test" + core.EmailPlaceholderActionUrl,
|
||||
}
|
||||
|
||||
scenarios := []struct {
|
||||
config core.MetaConfig
|
||||
expectError bool
|
||||
@@ -367,39 +387,39 @@ func TestMetaConfigValidate(t *testing.T) {
|
||||
// invalid data
|
||||
{
|
||||
core.MetaConfig{
|
||||
AppName: strings.Repeat("a", 300),
|
||||
AppUrl: "test",
|
||||
SenderName: strings.Repeat("a", 300),
|
||||
SenderAddress: "invalid_email",
|
||||
UserVerificationUrl: "test",
|
||||
UserResetPasswordUrl: "test",
|
||||
UserConfirmEmailChangeUrl: "test",
|
||||
AppName: strings.Repeat("a", 300),
|
||||
AppUrl: "test",
|
||||
SenderName: strings.Repeat("a", 300),
|
||||
SenderAddress: "invalid_email",
|
||||
VerificationTemplate: invalidTemplate,
|
||||
ResetPasswordTemplate: invalidTemplate,
|
||||
ConfirmEmailChangeTemplate: invalidTemplate,
|
||||
},
|
||||
true,
|
||||
},
|
||||
// invalid data (missing required placeholders)
|
||||
{
|
||||
core.MetaConfig{
|
||||
AppName: "test",
|
||||
AppUrl: "https://example.com",
|
||||
SenderName: "test",
|
||||
SenderAddress: "test@example.com",
|
||||
UserVerificationUrl: "https://example.com",
|
||||
UserResetPasswordUrl: "https://example.com",
|
||||
UserConfirmEmailChangeUrl: "https://example.com",
|
||||
AppName: "test",
|
||||
AppUrl: "https://example.com",
|
||||
SenderName: "test",
|
||||
SenderAddress: "test@example.com",
|
||||
VerificationTemplate: noPlaceholdersTemplate,
|
||||
ResetPasswordTemplate: noPlaceholdersTemplate,
|
||||
ConfirmEmailChangeTemplate: noPlaceholdersTemplate,
|
||||
},
|
||||
true,
|
||||
},
|
||||
// valid data
|
||||
{
|
||||
core.MetaConfig{
|
||||
AppName: "test",
|
||||
AppUrl: "https://example.com",
|
||||
SenderName: "test",
|
||||
SenderAddress: "test@example.com",
|
||||
UserVerificationUrl: "https://example.com/" + core.EmailPlaceholderToken,
|
||||
UserResetPasswordUrl: "https://example.com/" + core.EmailPlaceholderToken,
|
||||
UserConfirmEmailChangeUrl: "https://example.com/" + core.EmailPlaceholderToken,
|
||||
AppName: "test",
|
||||
AppUrl: "https://example.com",
|
||||
SenderName: "test",
|
||||
SenderAddress: "test@example.com",
|
||||
VerificationTemplate: withPlaceholdersTemplate,
|
||||
ResetPasswordTemplate: withPlaceholdersTemplate,
|
||||
ConfirmEmailChangeTemplate: withPlaceholdersTemplate,
|
||||
},
|
||||
false,
|
||||
},
|
||||
@@ -418,6 +438,130 @@ func TestMetaConfigValidate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmailTemplateValidate(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
emailTemplate core.EmailTemplate
|
||||
expectedErrors []string
|
||||
}{
|
||||
// require values
|
||||
{
|
||||
core.EmailTemplate{},
|
||||
[]string{"subject", "actionUrl", "body"},
|
||||
},
|
||||
// missing placeholders
|
||||
{
|
||||
core.EmailTemplate{
|
||||
Subject: "test",
|
||||
ActionUrl: "test",
|
||||
Body: "test",
|
||||
},
|
||||
[]string{"actionUrl", "body"},
|
||||
},
|
||||
// valid data
|
||||
{
|
||||
core.EmailTemplate{
|
||||
Subject: "test",
|
||||
ActionUrl: "test" + core.EmailPlaceholderToken,
|
||||
Body: "test" + core.EmailPlaceholderActionUrl,
|
||||
},
|
||||
[]string{},
|
||||
},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
result := s.emailTemplate.Validate()
|
||||
|
||||
// parse errors
|
||||
errs, ok := result.(validation.Errors)
|
||||
if !ok && result != nil {
|
||||
t.Errorf("(%d) Failed to parse errors %v", i, result)
|
||||
continue
|
||||
}
|
||||
|
||||
// check errors
|
||||
if len(errs) > len(s.expectedErrors) {
|
||||
t.Errorf("(%d) Expected error keys %v, got %v", i, s.expectedErrors, errs)
|
||||
}
|
||||
for _, k := range s.expectedErrors {
|
||||
if _, ok := errs[k]; !ok {
|
||||
t.Errorf("(%d) Missing expected error key %q in %v", i, k, errs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmailTemplateResolve(t *testing.T) {
|
||||
allPlaceholders := core.EmailPlaceholderActionUrl + core.EmailPlaceholderToken + core.EmailPlaceholderAppName + core.EmailPlaceholderAppUrl
|
||||
|
||||
scenarios := []struct {
|
||||
emailTemplate core.EmailTemplate
|
||||
expectedSubject string
|
||||
expectedBody string
|
||||
expectedActionUrl string
|
||||
}{
|
||||
// no placeholders
|
||||
{
|
||||
emailTemplate: core.EmailTemplate{
|
||||
Subject: "subject:",
|
||||
Body: "body:",
|
||||
ActionUrl: "/actionUrl////",
|
||||
},
|
||||
expectedSubject: "subject:",
|
||||
expectedActionUrl: "/actionUrl/",
|
||||
expectedBody: "body:",
|
||||
},
|
||||
// with placeholders
|
||||
{
|
||||
emailTemplate: core.EmailTemplate{
|
||||
ActionUrl: "/actionUrl////" + allPlaceholders,
|
||||
Subject: "subject:" + allPlaceholders,
|
||||
Body: "body:" + allPlaceholders,
|
||||
},
|
||||
expectedActionUrl: fmt.Sprintf(
|
||||
"/actionUrl/%%7BACTION_URL%%7D%s%s%s",
|
||||
"token_test",
|
||||
"name_test",
|
||||
"url_test",
|
||||
),
|
||||
expectedSubject: fmt.Sprintf(
|
||||
"subject:%s%s%s%s",
|
||||
core.EmailPlaceholderActionUrl,
|
||||
core.EmailPlaceholderToken,
|
||||
"name_test",
|
||||
"url_test",
|
||||
),
|
||||
expectedBody: fmt.Sprintf(
|
||||
"body:%s%s%s%s",
|
||||
fmt.Sprintf(
|
||||
"/actionUrl/%%7BACTION_URL%%7D%s%s%s",
|
||||
"token_test",
|
||||
"name_test",
|
||||
"url_test",
|
||||
),
|
||||
"token_test",
|
||||
"name_test",
|
||||
"url_test",
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
subject, body, actionUrl := s.emailTemplate.Resolve("name_test", "url_test", "token_test")
|
||||
|
||||
if s.expectedSubject != subject {
|
||||
t.Errorf("(%d) Expected subject %q got %q", i, s.expectedSubject, subject)
|
||||
}
|
||||
|
||||
if s.expectedBody != body {
|
||||
t.Errorf("(%d) Expected body \n%v got \n%v", i, s.expectedBody, body)
|
||||
}
|
||||
|
||||
if s.expectedActionUrl != actionUrl {
|
||||
t.Errorf("(%d) Expected actionUrl \n%v got \n%v", i, s.expectedActionUrl, actionUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogsConfigValidate(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
config core.LogsConfig
|
||||
|
||||
Reference in New Issue
Block a user