mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 19:07:30 +00:00
feat: custom message text (#1801)
* feat: default custom message text * feat: org custom message text * feat: org custom message text * feat: custom messages query side * feat: default messages * feat: message text user fields * feat: check for inactive user * feat: fix send password reset * feat: fix custom org text * feat: add variables to docs * feat: custom text tests * feat: fix notifications * feat: add custom text feature * feat: add custom text feature * feat: feature in custom message texts * feat: add custom text feature in frontend * feat: merge main * feat: feature tests * feat: change phone message in setup * fix: remove unused code, add event translation * fix: merge main and fix problems * fix: english translation file * fix: migration versions * fix: setup * feat: fix pr requests * feat: fix phone code message * feat: migration * feat: setup * fix: remove unused tests Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
@@ -29,19 +29,19 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
notificationTable = "notification.notifications"
|
||||
NotifyUserID = "NOTIFICATION"
|
||||
labelPolicyTableOrg = "management.label_policies"
|
||||
labelPolicyTableDef = "adminapi.label_policies"
|
||||
mailTemplateTableOrg = "management.mail_templates"
|
||||
mailTemplateTableDef = "adminapi.mail_templates"
|
||||
mailTextTableOrg = "management.mail_texts"
|
||||
mailTextTableDef = "adminapi.mail_texts"
|
||||
mailTextTypeDomainClaimed = "DomainClaimed"
|
||||
mailTextTypeInitCode = "InitCode"
|
||||
mailTextTypePasswordReset = "PasswordReset"
|
||||
mailTextTypeVerifyEmail = "VerifyEmail"
|
||||
mailTextTypeVerifyPhone = "VerifyPhone"
|
||||
notificationTable = "notification.notifications"
|
||||
NotifyUserID = "NOTIFICATION"
|
||||
labelPolicyTableOrg = "management.label_policies"
|
||||
labelPolicyTableDef = "adminapi.label_policies"
|
||||
mailTemplateTableOrg = "management.mail_templates"
|
||||
mailTemplateTableDef = "adminapi.mail_templates"
|
||||
messageTextTableOrg = "management.message_texts"
|
||||
messageTextTableDef = "adminapi.message_texts"
|
||||
messageTextTypeDomainClaimed = "DomainClaimed"
|
||||
messageTextTypeInitCode = "InitCode"
|
||||
messageTextTypePasswordReset = "PasswordReset"
|
||||
messageTextTypeVerifyEmail = "VerifyEmail"
|
||||
messageTextTypeVerifyPhone = "VerifyPhone"
|
||||
)
|
||||
|
||||
type Notification struct {
|
||||
@@ -146,7 +146,6 @@ func (n *Notification) handleInitUserCode(event *models.Event) (err error) {
|
||||
if err != nil || alreadyHandled {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := getSetNotifyContextData(event.ResourceOwner)
|
||||
colors, err := n.getLabelPolicy(ctx)
|
||||
if err != nil {
|
||||
@@ -163,7 +162,7 @@ func (n *Notification) handleInitUserCode(event *models.Event) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
text, err := n.getMailText(ctx, mailTextTypeInitCode, user.PreferredLanguage)
|
||||
text, err := n.getMessageText(user, messageTextTypeInitCode, user.PreferredLanguage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -202,7 +201,7 @@ func (n *Notification) handlePasswordCode(event *models.Event) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
text, err := n.getMailText(ctx, mailTextTypePasswordReset, user.PreferredLanguage)
|
||||
text, err := n.getMessageText(user, messageTextTypePasswordReset, user.PreferredLanguage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -224,7 +223,6 @@ func (n *Notification) handleEmailVerificationCode(event *models.Event) (err err
|
||||
if err != nil || alreadyHandled {
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx := getSetNotifyContextData(event.ResourceOwner)
|
||||
colors, err := n.getLabelPolicy(ctx)
|
||||
if err != nil {
|
||||
@@ -241,7 +239,7 @@ func (n *Notification) handleEmailVerificationCode(event *models.Event) (err err
|
||||
return err
|
||||
}
|
||||
|
||||
text, err := n.getMailText(ctx, mailTextTypeVerifyEmail, user.PreferredLanguage)
|
||||
text, err := n.getMessageText(user, messageTextTypeVerifyEmail, user.PreferredLanguage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -268,7 +266,11 @@ func (n *Notification) handlePhoneVerificationCode(event *models.Event) (err err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = types.SendPhoneVerificationCode(n.i18n, user, phoneCode, n.systemDefaults, n.AesCrypto)
|
||||
text, err := n.getMessageText(user, messageTextTypeVerifyPhone, user.PreferredLanguage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = types.SendPhoneVerificationCode(text, user, phoneCode, n.systemDefaults, n.AesCrypto)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -303,7 +305,7 @@ func (n *Notification) handleDomainClaimed(event *models.Event) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
text, err := n.getMailText(ctx, mailTextTypeDomainClaimed, user.PreferredLanguage)
|
||||
text, err := n.getMessageText(user, messageTextTypeDomainClaimed, user.PreferredLanguage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -395,26 +397,29 @@ func (n *Notification) getMailTemplate(ctx context.Context) (*iam_model.MailTemp
|
||||
}
|
||||
|
||||
// Read organization specific texts
|
||||
func (n *Notification) getMailText(ctx context.Context, textType string, lang string) (*iam_model.MailTextView, error) {
|
||||
func (n *Notification) getMessageText(user *model.NotifyUser, textType, lang string) (*iam_model.MessageTextView, error) {
|
||||
langTag := language.Make(lang)
|
||||
if langTag == language.Und {
|
||||
langTag = n.systemDefaults.DefaultLanguage
|
||||
langTag = language.English
|
||||
}
|
||||
base, _ := langTag.Base()
|
||||
langBase, _ := langTag.Base()
|
||||
|
||||
defaultMessageText, err := n.view.MessageTextByIDs(n.systemDefaults.IamID, textType, langBase.String(), messageTextTableDef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defaultMessageText.Default = true
|
||||
|
||||
// read from Org
|
||||
mailText, err := n.view.MailTextByIDs(authz.GetCtxData(ctx).OrgID, textType, base.String(), mailTextTableOrg)
|
||||
orgMessageText, err := n.view.MessageTextByIDs(user.ResourceOwner, textType, langBase.String(), messageTextTableOrg)
|
||||
if errors.IsNotFound(err) {
|
||||
// read from default
|
||||
mailText, err = n.view.MailTextByIDs(n.systemDefaults.IamID, textType, base.String(), mailTextTableDef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mailText.Default = true
|
||||
return iam_es_model.MessageTextViewToModel(defaultMessageText), nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return iam_es_model.MailTextViewToModel(mailText), err
|
||||
mergedText := mergeMessageTexts(defaultMessageText, orgMessageText)
|
||||
return iam_es_model.MessageTextViewToModel(mergedText), err
|
||||
}
|
||||
|
||||
func (n *Notification) getUserByID(userID string) (*model.NotifyUser, error) {
|
||||
@@ -440,3 +445,28 @@ func (n *Notification) getUserByID(userID string) (*model.NotifyUser, error) {
|
||||
}
|
||||
return &userCopy, nil
|
||||
}
|
||||
|
||||
func mergeMessageTexts(defaultText *iam_es_model.MessageTextView, orgText *iam_es_model.MessageTextView) *iam_es_model.MessageTextView {
|
||||
if orgText.Subject == "" {
|
||||
orgText.Subject = defaultText.Subject
|
||||
}
|
||||
if orgText.Title == "" {
|
||||
orgText.Title = defaultText.Title
|
||||
}
|
||||
if orgText.PreHeader == "" {
|
||||
orgText.PreHeader = defaultText.PreHeader
|
||||
}
|
||||
if orgText.Text == "" {
|
||||
orgText.Text = defaultText.Text
|
||||
}
|
||||
if orgText.Greeting == "" {
|
||||
orgText.Greeting = defaultText.Greeting
|
||||
}
|
||||
if orgText.ButtonText == "" {
|
||||
orgText.ButtonText = defaultText.ButtonText
|
||||
}
|
||||
if orgText.FooterText == "" {
|
||||
orgText.FooterText = defaultText.FooterText
|
||||
}
|
||||
return orgText
|
||||
}
|
||||
|
@@ -1,10 +0,0 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/iam/repository/view"
|
||||
"github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||
)
|
||||
|
||||
func (v *View) MailTextByIDs(aggregateID string, textType string, language string, mailTextTableVar string) (*model.MailTextView, error) {
|
||||
return view.GetMailTextByIDs(v.Db, mailTextTableVar, aggregateID, textType, language)
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/iam/repository/view"
|
||||
"github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||
)
|
||||
|
||||
func (v *View) MessageTextByIDs(aggregateID, textType, lang, messageTextTableVar string) (*model.MessageTextView, error) {
|
||||
return view.GetMessageTextByIDs(v.Db, messageTextTableVar, aggregateID, textType, lang)
|
||||
}
|
@@ -53,7 +53,7 @@ func (data *TemplateData) Translate(i18n *i18n.Translator, args map[string]inter
|
||||
data.ButtonText = i18n.Localize(data.ButtonText, nil, langs...)
|
||||
}
|
||||
|
||||
func GetTemplateData(apiDomain, href string, text *iam_model.MailTextView, policy *iam_model.LabelPolicyView) TemplateData {
|
||||
func GetTemplateData(apiDomain, href string, text *iam_model.MessageTextView, policy *iam_model.LabelPolicyView) TemplateData {
|
||||
templateData := TemplateData{
|
||||
Title: text.Title,
|
||||
PreHeader: text.PreHeader,
|
||||
@@ -62,6 +62,7 @@ func GetTemplateData(apiDomain, href string, text *iam_model.MailTextView, polic
|
||||
Text: html.UnescapeString(text.Text),
|
||||
Href: href,
|
||||
ButtonText: text.ButtonText,
|
||||
FooterText: text.FooterText,
|
||||
PrimaryColor: defaultPrimaryColor,
|
||||
BackgroundColor: defaultBackgroundColor,
|
||||
FontColor: defaultFontColor,
|
||||
|
@@ -15,18 +15,14 @@ type DomainClaimedData struct {
|
||||
URL string
|
||||
}
|
||||
|
||||
func SendDomainClaimed(mailhtml string, text *iam_model.MailTextView, user *view_model.NotifyUser, username string, systemDefaults systemdefaults.SystemDefaults, colors *iam_model.LabelPolicyView, apiDomain string) error {
|
||||
func SendDomainClaimed(mailhtml string, text *iam_model.MessageTextView, user *view_model.NotifyUser, username string, systemDefaults systemdefaults.SystemDefaults, colors *iam_model.LabelPolicyView, apiDomain string) error {
|
||||
url, err := templates.ParseTemplateText(systemDefaults.Notifications.Endpoints.DomainClaimed, &UrlData{UserID: user.ID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var args = map[string]interface{}{
|
||||
"FirstName": user.FirstName,
|
||||
"LastName": user.LastName,
|
||||
"Username": user.LastEmail,
|
||||
"TempUsername": username,
|
||||
"Domain": strings.Split(user.LastEmail, "@")[1],
|
||||
}
|
||||
var args = mapNotifyUserToArgs(user)
|
||||
args["TempUsername"] = username
|
||||
args["Domain"] = strings.Split(user.LastEmail, "@")[1]
|
||||
|
||||
text.Greeting, err = templates.ParseTemplateText(text.Greeting, args)
|
||||
text.Text, err = templates.ParseTemplateText(text.Text, args)
|
||||
|
@@ -16,7 +16,7 @@ type EmailVerificationCodeData struct {
|
||||
URL string
|
||||
}
|
||||
|
||||
func SendEmailVerificationCode(mailhtml string, text *iam_model.MailTextView, user *view_model.NotifyUser, code *es_model.EmailCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView, apiDomain string) error {
|
||||
func SendEmailVerificationCode(mailhtml string, text *iam_model.MessageTextView, user *view_model.NotifyUser, code *es_model.EmailCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView, apiDomain string) error {
|
||||
codeString, err := crypto.DecryptString(code.Code, alg)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -25,11 +25,9 @@ func SendEmailVerificationCode(mailhtml string, text *iam_model.MailTextView, us
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var args = map[string]interface{}{
|
||||
"FirstName": user.FirstName,
|
||||
"LastName": user.LastName,
|
||||
"Code": codeString,
|
||||
}
|
||||
|
||||
var args = mapNotifyUserToArgs(user)
|
||||
args["Code"] = codeString
|
||||
|
||||
text.Greeting, err = templates.ParseTemplateText(text.Greeting, args)
|
||||
text.Text, err = templates.ParseTemplateText(text.Text, args)
|
||||
|
@@ -22,7 +22,7 @@ type UrlData struct {
|
||||
PasswordSet bool
|
||||
}
|
||||
|
||||
func SendUserInitCode(mailhtml string, text *iam_model.MailTextView, user *view_model.NotifyUser, code *es_model.InitUserCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView, apiDomain string) error {
|
||||
func SendUserInitCode(mailhtml string, text *iam_model.MessageTextView, user *view_model.NotifyUser, code *es_model.InitUserCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView, apiDomain string) error {
|
||||
codeString, err := crypto.DecryptString(code.Code, alg)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -31,12 +31,8 @@ func SendUserInitCode(mailhtml string, text *iam_model.MailTextView, user *view_
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var args = map[string]interface{}{
|
||||
"FirstName": user.FirstName,
|
||||
"LastName": user.LastName,
|
||||
"Code": codeString,
|
||||
"PreferredLoginName": user.PreferredLoginName,
|
||||
}
|
||||
var args = mapNotifyUserToArgs(user)
|
||||
args["Code"] = codeString
|
||||
|
||||
text.Greeting, err = templates.ParseTemplateText(text.Greeting, args)
|
||||
text.Text, err = templates.ParseTemplateText(text.Text, args)
|
||||
|
@@ -18,7 +18,7 @@ type PasswordCodeData struct {
|
||||
URL string
|
||||
}
|
||||
|
||||
func SendPasswordCode(mailhtml string, text *iam_model.MailTextView, user *view_model.NotifyUser, code *es_model.PasswordCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView, apiDomain string) error {
|
||||
func SendPasswordCode(mailhtml string, text *iam_model.MessageTextView, user *view_model.NotifyUser, code *es_model.PasswordCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView, apiDomain string) error {
|
||||
codeString, err := crypto.DecryptString(code.Code, alg)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -27,11 +27,8 @@ func SendPasswordCode(mailhtml string, text *iam_model.MailTextView, user *view_
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var args = map[string]interface{}{
|
||||
"FirstName": user.FirstName,
|
||||
"LastName": user.LastName,
|
||||
"Code": codeString,
|
||||
}
|
||||
var args = mapNotifyUserToArgs(user)
|
||||
args["Code"] = codeString
|
||||
|
||||
text.Greeting, err = templates.ParseTemplateText(text.Greeting, args)
|
||||
text.Text, err = templates.ParseTemplateText(text.Text, args)
|
||||
|
@@ -3,7 +3,7 @@ package types
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/config/systemdefaults"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/i18n"
|
||||
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||
"github.com/caos/zitadel/internal/notification/templates"
|
||||
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||
view_model "github.com/caos/zitadel/internal/user/repository/view/model"
|
||||
@@ -13,19 +13,18 @@ type PhoneVerificationCodeData struct {
|
||||
UserID string
|
||||
}
|
||||
|
||||
func SendPhoneVerificationCode(i18n *i18n.Translator, user *view_model.NotifyUser, code *es_model.PhoneCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm) error {
|
||||
func SendPhoneVerificationCode(text *iam_model.MessageTextView, user *view_model.NotifyUser, code *es_model.PhoneCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm) error {
|
||||
codeString, err := crypto.DecryptString(code.Code, alg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var args = map[string]interface{}{
|
||||
"FirstName": user.FirstName,
|
||||
"LastName": user.LastName,
|
||||
"Code": codeString,
|
||||
}
|
||||
systemDefaults.Notifications.TemplateData.VerifyPhone.Translate(i18n, args, user.PreferredLanguage)
|
||||
var args = mapNotifyUserToArgs(user)
|
||||
args["Code"] = codeString
|
||||
|
||||
text.Text, err = templates.ParseTemplateText(text.Text, args)
|
||||
|
||||
codeData := &PhoneVerificationCodeData{UserID: user.ID}
|
||||
template, err := templates.ParseTemplateText(systemDefaults.Notifications.TemplateData.VerifyPhone.Text, codeData)
|
||||
template, err := templates.ParseTemplateText(text.Text, codeData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@@ -42,3 +42,21 @@ func sendDebugEmail(message providers.Message, config systemdefaults.Notificatio
|
||||
}
|
||||
return provider.HandleMessage(message)
|
||||
}
|
||||
|
||||
func mapNotifyUserToArgs(user *view_model.NotifyUser) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"UserName": user.UserName,
|
||||
"FirstName": user.FirstName,
|
||||
"LastName": user.LastName,
|
||||
"NickName": user.NickName,
|
||||
"DisplayName": user.DisplayName,
|
||||
"LastEmail": user.LastEmail,
|
||||
"VerifiedEmail": user.VerifiedEmail,
|
||||
"LastPhone": user.LastPhone,
|
||||
"VerifiedPhone": user.VerifiedPhone,
|
||||
"PreferredLoginName": user.PreferredLoginName,
|
||||
"LoginNames": user.LoginNames,
|
||||
"ChangeDate": user.ChangeDate,
|
||||
"CreationDate": user.CreationDate,
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user