mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 20:17:23 +00:00
feat: Custom message files (#1992)
* feat: add get custom message text to admin api * feat: read custom message texts from files * feat: get languages in apis * feat: get languages in apis * feat: get languages in apis * feat: pr feedback * feat: docs * feat: merge main
This commit is contained in:
parent
7ebf0333c3
commit
03a38fbf1c
@ -20,6 +20,18 @@ It respondes as soon as ZITADEL started
|
||||
GET: /healthz
|
||||
|
||||
|
||||
### GetSupportedLanguages
|
||||
|
||||
> **rpc** GetSupportedLanguages([GetSupportedLanguagesRequest](#getsupportedlanguagesrequest))
|
||||
[GetSupportedLanguagesResponse](#getsupportedlanguagesresponse)
|
||||
|
||||
Returns the default languages
|
||||
|
||||
|
||||
|
||||
GET: /languages
|
||||
|
||||
|
||||
### IsOrgUnique
|
||||
|
||||
> **rpc** IsOrgUnique([IsOrgUniqueRequest](#isorguniquerequest))
|
||||
@ -658,7 +670,19 @@ it impacts all organisations without a customised policy
|
||||
> **rpc** GetDefaultInitMessageText([GetDefaultInitMessageTextRequest](#getdefaultinitmessagetextrequest))
|
||||
[GetDefaultInitMessageTextResponse](#getdefaultinitmessagetextresponse)
|
||||
|
||||
Returns the custom text for initial message
|
||||
Returns the default text for initial message (translation file)
|
||||
|
||||
|
||||
|
||||
GET: /text/default/message/init/{language}
|
||||
|
||||
|
||||
### GetCustomInitMessageText
|
||||
|
||||
> **rpc** GetCustomInitMessageText([GetCustomInitMessageTextRequest](#getcustominitmessagetextrequest))
|
||||
[GetCustomInitMessageTextResponse](#getcustominitmessagetextresponse)
|
||||
|
||||
Returns the custom text for initial message (overwritten in eventstore)
|
||||
|
||||
|
||||
|
||||
@ -685,7 +709,19 @@ The Following Variables can be used:
|
||||
> **rpc** GetDefaultPasswordResetMessageText([GetDefaultPasswordResetMessageTextRequest](#getdefaultpasswordresetmessagetextrequest))
|
||||
[GetDefaultPasswordResetMessageTextResponse](#getdefaultpasswordresetmessagetextresponse)
|
||||
|
||||
Returns the custom text for password reset message
|
||||
Returns the default text for password reset message (translation file)
|
||||
|
||||
|
||||
|
||||
GET: /text/deafult/message/passwordreset/{language}
|
||||
|
||||
|
||||
### GetCustomPasswordResetMessageText
|
||||
|
||||
> **rpc** GetCustomPasswordResetMessageText([GetCustomPasswordResetMessageTextRequest](#getcustompasswordresetmessagetextrequest))
|
||||
[GetCustomPasswordResetMessageTextResponse](#getcustompasswordresetmessagetextresponse)
|
||||
|
||||
Returns the custom text for password reset message (overwritten in eventstore)
|
||||
|
||||
|
||||
|
||||
@ -712,7 +748,19 @@ The Following Variables can be used:
|
||||
> **rpc** GetDefaultVerifyEmailMessageText([GetDefaultVerifyEmailMessageTextRequest](#getdefaultverifyemailmessagetextrequest))
|
||||
[GetDefaultVerifyEmailMessageTextResponse](#getdefaultverifyemailmessagetextresponse)
|
||||
|
||||
Returns the custom text for verify email message
|
||||
Returns the default text for verify email message (translation files)
|
||||
|
||||
|
||||
|
||||
GET: /text/default/message/verifyemail/{language}
|
||||
|
||||
|
||||
### GetCustomVerifyEmailMessageText
|
||||
|
||||
> **rpc** GetCustomVerifyEmailMessageText([GetCustomVerifyEmailMessageTextRequest](#getcustomverifyemailmessagetextrequest))
|
||||
[GetCustomVerifyEmailMessageTextResponse](#getcustomverifyemailmessagetextresponse)
|
||||
|
||||
Returns the custom text for verify email message (overwritten in eventstore)
|
||||
|
||||
|
||||
|
||||
@ -739,6 +787,18 @@ The Following Variables can be used:
|
||||
> **rpc** GetDefaultVerifyPhoneMessageText([GetDefaultVerifyPhoneMessageTextRequest](#getdefaultverifyphonemessagetextrequest))
|
||||
[GetDefaultVerifyPhoneMessageTextResponse](#getdefaultverifyphonemessagetextresponse)
|
||||
|
||||
Returns the default text for verify phone message (translation file)
|
||||
|
||||
|
||||
|
||||
GET: /text/default/message/verifyphone/{language}
|
||||
|
||||
|
||||
### GetCustomVerifyPhoneMessageText
|
||||
|
||||
> **rpc** GetCustomVerifyPhoneMessageText([GetCustomVerifyPhoneMessageTextRequest](#getcustomverifyphonemessagetextrequest))
|
||||
[GetCustomVerifyPhoneMessageTextResponse](#getcustomverifyphonemessagetextresponse)
|
||||
|
||||
Returns the custom text for verify phone message
|
||||
|
||||
|
||||
@ -766,7 +826,19 @@ The Following Variables can be used:
|
||||
> **rpc** GetDefaultDomainClaimedMessageText([GetDefaultDomainClaimedMessageTextRequest](#getdefaultdomainclaimedmessagetextrequest))
|
||||
[GetDefaultDomainClaimedMessageTextResponse](#getdefaultdomainclaimedmessagetextresponse)
|
||||
|
||||
Returns the custom text for domain claimed message
|
||||
Returns the default text for domain claimed message (translation file)
|
||||
|
||||
|
||||
|
||||
GET: /text/default/message/domainclaimed/{language}
|
||||
|
||||
|
||||
### GetCustomDomainClaimedMessageText
|
||||
|
||||
> **rpc** GetCustomDomainClaimedMessageText([GetCustomDomainClaimedMessageTextRequest](#getcustomdomainclaimedmessagetextrequest))
|
||||
[GetCustomDomainClaimedMessageTextResponse](#getcustomdomainclaimedmessagetextresponse)
|
||||
|
||||
Returns the custom text for domain claimed message (overwritten in eventstore)
|
||||
|
||||
|
||||
|
||||
@ -1171,6 +1243,50 @@ This is an empty response
|
||||
|
||||
|
||||
|
||||
### GetCustomDomainClaimedMessageTextRequest
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| language | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### GetCustomDomainClaimedMessageTextResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| custom_text | zitadel.text.v1.MessageCustomText | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### GetCustomInitMessageTextRequest
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| language | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### GetCustomInitMessageTextResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| custom_text | zitadel.text.v1.MessageCustomText | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### GetCustomLoginTextsRequest
|
||||
|
||||
|
||||
@ -1216,6 +1332,72 @@ This is an empty response
|
||||
|
||||
|
||||
|
||||
### GetCustomPasswordResetMessageTextRequest
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| language | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### GetCustomPasswordResetMessageTextResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| custom_text | zitadel.text.v1.MessageCustomText | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### GetCustomVerifyEmailMessageTextRequest
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| language | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### GetCustomVerifyEmailMessageTextResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| custom_text | zitadel.text.v1.MessageCustomText | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### GetCustomVerifyPhoneMessageTextRequest
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| language | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### GetCustomVerifyPhoneMessageTextResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| custom_text | zitadel.text.v1.MessageCustomText | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### GetDefaultDomainClaimedMessageTextRequest
|
||||
|
||||
|
||||
@ -1567,6 +1749,23 @@ This is an empty request
|
||||
|
||||
|
||||
|
||||
### GetSupportedLanguagesRequest
|
||||
This is an empty request
|
||||
|
||||
|
||||
|
||||
|
||||
### GetSupportedLanguagesResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| languages | repeated string | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### HealthzRequest
|
||||
This is an empty request
|
||||
|
||||
|
@ -19,6 +19,18 @@ title: zitadel/auth.proto
|
||||
GET: /healthz
|
||||
|
||||
|
||||
### GetSupportedLanguages
|
||||
|
||||
> **rpc** GetSupportedLanguages([GetSupportedLanguagesRequest](#getsupportedlanguagesrequest))
|
||||
[GetSupportedLanguagesResponse](#getsupportedlanguagesresponse)
|
||||
|
||||
Returns the default languages
|
||||
|
||||
|
||||
|
||||
GET: /languages
|
||||
|
||||
|
||||
### GetMyUser
|
||||
|
||||
> **rpc** GetMyUser([GetMyUserRequest](#getmyuserrequest))
|
||||
@ -646,6 +658,23 @@ the request parameters are read from the token-header
|
||||
|
||||
|
||||
|
||||
### GetSupportedLanguagesRequest
|
||||
This is an empty request
|
||||
|
||||
|
||||
|
||||
|
||||
### GetSupportedLanguagesResponse
|
||||
This is an empty response
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| languages | repeated string | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### HealthzRequest
|
||||
This is an empty request
|
||||
|
||||
|
@ -43,6 +43,18 @@ Returns some needed settings of the IAM (Global Organisation ID, Zitadel Project
|
||||
GET: /iam
|
||||
|
||||
|
||||
### GetSupportedLanguages
|
||||
|
||||
> **rpc** GetSupportedLanguages([GetSupportedLanguagesRequest](#getsupportedlanguagesrequest))
|
||||
[GetSupportedLanguagesResponse](#getsupportedlanguagesresponse)
|
||||
|
||||
Returns the default languages
|
||||
|
||||
|
||||
|
||||
GET: /languages
|
||||
|
||||
|
||||
### GetUserByID
|
||||
|
||||
> **rpc** GetUserByID([GetUserByIDRequest](#getuserbyidrequest))
|
||||
@ -4282,6 +4294,23 @@ This is an empty request
|
||||
|
||||
|
||||
|
||||
### GetSupportedLanguagesRequest
|
||||
This is an empty request
|
||||
|
||||
|
||||
|
||||
|
||||
### GetSupportedLanguagesResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| languages | repeated string | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### GetUserByIDRequest
|
||||
|
||||
|
||||
|
@ -10,10 +10,12 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
v1 "github.com/caos/zitadel/internal/eventstore/v1"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/caos/zitadel/internal/i18n"
|
||||
iam_view "github.com/caos/zitadel/internal/iam/repository/view"
|
||||
"github.com/caos/zitadel/internal/user/repository/view/model"
|
||||
|
||||
@ -30,15 +32,30 @@ import (
|
||||
)
|
||||
|
||||
type IAMRepository struct {
|
||||
Eventstore v1.Eventstore
|
||||
SearchLimit uint64
|
||||
View *admin_view.View
|
||||
SystemDefaults systemdefaults.SystemDefaults
|
||||
Roles []string
|
||||
PrefixAvatarURL string
|
||||
LoginDir http.FileSystem
|
||||
TranslationFileContents map[string][]byte
|
||||
mutex sync.Mutex
|
||||
Eventstore v1.Eventstore
|
||||
SearchLimit uint64
|
||||
View *admin_view.View
|
||||
SystemDefaults systemdefaults.SystemDefaults
|
||||
Roles []string
|
||||
PrefixAvatarURL string
|
||||
LoginDir http.FileSystem
|
||||
NotificationDir http.FileSystem
|
||||
LoginTranslationFileContents map[string][]byte
|
||||
NotificationTranslationFileContents map[string][]byte
|
||||
mutex sync.Mutex
|
||||
supportedLangs []language.Tag
|
||||
}
|
||||
|
||||
func (repo *IAMRepository) Languages(ctx context.Context) ([]language.Tag, error) {
|
||||
if len(repo.supportedLangs) == 0 {
|
||||
langs, err := i18n.SupportedLanguages(repo.LoginDir)
|
||||
if err != nil {
|
||||
logging.Log("ADMIN-tiMWs").WithError(err).Debug("unable to parse language")
|
||||
return nil, err
|
||||
}
|
||||
repo.supportedLangs = langs
|
||||
}
|
||||
return repo.supportedLangs, nil
|
||||
}
|
||||
|
||||
func (repo *IAMRepository) IAMMemberByID(ctx context.Context, iamID, userID string) (*iam_model.IAMMemberView, error) {
|
||||
@ -365,21 +382,36 @@ func (repo *IAMRepository) SearchIAMMembersx(ctx context.Context, request *iam_m
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (repo *IAMRepository) GetDefaultMessageTexts(ctx context.Context) (*iam_model.MessageTextsView, error) {
|
||||
text, err := repo.View.MessageTexts(repo.SystemDefaults.IamID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (repo *IAMRepository) GetDefaultMessageText(ctx context.Context, textType, lang string) (*domain.CustomMessageText, error) {
|
||||
repo.mutex.Lock()
|
||||
defer repo.mutex.Unlock()
|
||||
var err error
|
||||
contents, ok := repo.NotificationTranslationFileContents[lang]
|
||||
if !ok {
|
||||
contents, err = repo.readTranslationFile(repo.NotificationDir, fmt.Sprintf("/i18n/%s.yaml", lang))
|
||||
if caos_errs.IsNotFound(err) {
|
||||
contents, err = repo.readTranslationFile(repo.NotificationDir, fmt.Sprintf("/i18n/%s.yaml", repo.SystemDefaults.DefaultLanguage.String()))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repo.NotificationTranslationFileContents[lang] = contents
|
||||
}
|
||||
return iam_es_model.MessageTextsViewToModel(text, true), err
|
||||
messageTexts := new(domain.MessageTexts)
|
||||
if err := yaml.Unmarshal(contents, messageTexts); err != nil {
|
||||
return nil, caos_errs.ThrowInternal(err, "TEXT-3N9fs", "Errors.TranslationFile.ReadError")
|
||||
}
|
||||
return messageTexts.GetMessageTextByType(textType), nil
|
||||
}
|
||||
|
||||
func (repo *IAMRepository) GetDefaultMessageText(ctx context.Context, textType, lang string) (*iam_model.MessageTextView, error) {
|
||||
text, err := repo.View.MessageTextByIDs(repo.SystemDefaults.IamID, textType, lang)
|
||||
func (repo *IAMRepository) GetCustomMessageText(ctx context.Context, textType, lang string) (*domain.CustomMessageText, error) {
|
||||
texts, err := repo.View.CustomTextsByAggregateIDAndTemplateAndLand(repo.SystemDefaults.IamID, textType, lang)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
text.Default = true
|
||||
return iam_es_model.MessageTextViewToModel(text), err
|
||||
result := iam_es_model.CustomTextViewsToMessageDomain(repo.SystemDefaults.IamID, lang, texts)
|
||||
result.Default = true
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (repo *IAMRepository) GetDefaultPrivacyPolicy(ctx context.Context) (*iam_model.PrivacyPolicyView, error) {
|
||||
@ -412,17 +444,17 @@ func (repo *IAMRepository) GetDefaultPrivacyPolicy(ctx context.Context) (*iam_mo
|
||||
func (repo *IAMRepository) GetDefaultLoginTexts(ctx context.Context, lang string) (*domain.CustomLoginText, error) {
|
||||
repo.mutex.Lock()
|
||||
defer repo.mutex.Unlock()
|
||||
contents, ok := repo.TranslationFileContents[lang]
|
||||
contents, ok := repo.LoginTranslationFileContents[lang]
|
||||
var err error
|
||||
if !ok {
|
||||
contents, err = repo.readTranslationFile(fmt.Sprintf("/i18n/%s.yaml", lang))
|
||||
contents, err = repo.readTranslationFile(repo.LoginDir, fmt.Sprintf("/i18n/%s.yaml", lang))
|
||||
if caos_errs.IsNotFound(err) {
|
||||
contents, err = repo.readTranslationFile(fmt.Sprintf("/i18n/%s.yaml", repo.SystemDefaults.DefaultLanguage.String()))
|
||||
contents, err = repo.readTranslationFile(repo.LoginDir, fmt.Sprintf("/i18n/%s.yaml", repo.SystemDefaults.DefaultLanguage.String()))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repo.TranslationFileContents[lang] = contents
|
||||
repo.LoginTranslationFileContents[lang] = contents
|
||||
}
|
||||
loginText := new(domain.CustomLoginText)
|
||||
if err := yaml.Unmarshal(contents, loginText); err != nil {
|
||||
@ -447,8 +479,8 @@ func (repo *IAMRepository) getIAMEvents(ctx context.Context, sequence uint64) ([
|
||||
return repo.Eventstore.FilterEvents(ctx, query)
|
||||
}
|
||||
|
||||
func (repo *IAMRepository) readTranslationFile(filename string) ([]byte, error) {
|
||||
r, err := repo.LoginDir.Open(filename)
|
||||
func (repo *IAMRepository) readTranslationFile(dir http.FileSystem, filename string) ([]byte, error) {
|
||||
r, err := dir.Open(filename)
|
||||
if os.IsNotExist(err) {
|
||||
return nil, caos_errs.ThrowNotFound(err, "TEXT-3n9fs", "Errors.TranslationFile.NotFound")
|
||||
}
|
||||
|
@ -54,6 +54,9 @@ func Start(ctx context.Context, conf Config, systemDefaults sd.SystemDefaults, s
|
||||
statikLoginFS, err := fs.NewWithNamespace("login")
|
||||
logging.Log("CONFI-7usEW").OnError(err).Panic("unable to start login statik dir")
|
||||
|
||||
statikNotificationFS, err := fs.NewWithNamespace("notification")
|
||||
logging.Log("CONFI-7usEW").OnError(err).Panic("unable to start notification statik dir")
|
||||
|
||||
return &EsRepository{
|
||||
spooler: spool,
|
||||
OrgRepo: eventstore.OrgRepo{
|
||||
@ -63,14 +66,16 @@ func Start(ctx context.Context, conf Config, systemDefaults sd.SystemDefaults, s
|
||||
SystemDefaults: systemDefaults,
|
||||
},
|
||||
IAMRepository: eventstore.IAMRepository{
|
||||
Eventstore: es,
|
||||
View: view,
|
||||
SystemDefaults: systemDefaults,
|
||||
SearchLimit: conf.SearchLimit,
|
||||
Roles: roles,
|
||||
PrefixAvatarURL: assetsAPI,
|
||||
LoginDir: statikLoginFS,
|
||||
TranslationFileContents: make(map[string][]byte),
|
||||
Eventstore: es,
|
||||
View: view,
|
||||
SystemDefaults: systemDefaults,
|
||||
SearchLimit: conf.SearchLimit,
|
||||
Roles: roles,
|
||||
PrefixAvatarURL: assetsAPI,
|
||||
LoginDir: statikLoginFS,
|
||||
NotificationDir: statikNotificationFS,
|
||||
LoginTranslationFileContents: make(map[string][]byte),
|
||||
NotificationTranslationFileContents: make(map[string][]byte),
|
||||
},
|
||||
AdministratorRepo: eventstore.AdministratorRepo{
|
||||
View: view,
|
||||
|
@ -2,6 +2,9 @@ package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
usr_model "github.com/caos/zitadel/internal/user/model"
|
||||
|
||||
@ -9,6 +12,8 @@ import (
|
||||
)
|
||||
|
||||
type IAMRepository interface {
|
||||
Languages(ctx context.Context) ([]language.Tag, error)
|
||||
|
||||
SearchIAMMembers(ctx context.Context, request *iam_model.IAMMemberSearchRequest) (*iam_model.IAMMemberSearchResponse, error)
|
||||
|
||||
GetIAMMemberRoles() []string
|
||||
@ -29,8 +34,8 @@ type IAMRepository interface {
|
||||
|
||||
GetDefaultMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error)
|
||||
|
||||
GetDefaultMessageTexts(ctx context.Context) (*iam_model.MessageTextsView, error)
|
||||
GetDefaultMessageText(ctx context.Context, textType string, language string) (*iam_model.MessageTextView, error)
|
||||
GetDefaultMessageText(ctx context.Context, textType, language string) (*domain.CustomMessageText, error)
|
||||
GetCustomMessageText(ctx context.Context, textType string, language string) (*domain.CustomMessageText, error)
|
||||
GetDefaultLoginTexts(ctx context.Context, lang string) (*domain.CustomLoginText, error)
|
||||
GetCustomLoginTexts(ctx context.Context, lang string) (*domain.CustomLoginText, error)
|
||||
|
||||
|
@ -15,7 +15,17 @@ func (s *Server) GetDefaultInitMessageText(ctx context.Context, req *admin_pb.Ge
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.GetDefaultInitMessageTextResponse{
|
||||
CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
|
||||
CustomText: text_grpc.DomainCustomMsgTextToPb(msg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetCustomInitMessageText(ctx context.Context, req *admin_pb.GetCustomInitMessageTextRequest) (*admin_pb.GetCustomInitMessageTextResponse, error) {
|
||||
msg, err := s.iam.GetCustomMessageText(ctx, domain.InitCodeMessageType, req.Language)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.GetCustomInitMessageTextResponse{
|
||||
CustomText: text_grpc.DomainCustomMsgTextToPb(msg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -39,7 +49,17 @@ func (s *Server) GetDefaultPasswordResetMessageText(ctx context.Context, req *ad
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.GetDefaultPasswordResetMessageTextResponse{
|
||||
CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
|
||||
CustomText: text_grpc.DomainCustomMsgTextToPb(msg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetCustomPasswordResetMessageText(ctx context.Context, req *admin_pb.GetCustomPasswordResetMessageTextRequest) (*admin_pb.GetCustomPasswordResetMessageTextResponse, error) {
|
||||
msg, err := s.iam.GetCustomMessageText(ctx, domain.PasswordResetMessageType, req.Language)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.GetCustomPasswordResetMessageTextResponse{
|
||||
CustomText: text_grpc.DomainCustomMsgTextToPb(msg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -63,7 +83,17 @@ func (s *Server) GetDefaultVerifyEmailMessageText(ctx context.Context, req *admi
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.GetDefaultVerifyEmailMessageTextResponse{
|
||||
CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
|
||||
CustomText: text_grpc.DomainCustomMsgTextToPb(msg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetCustomVerifyEmailMessageText(ctx context.Context, req *admin_pb.GetCustomVerifyEmailMessageTextRequest) (*admin_pb.GetCustomVerifyEmailMessageTextResponse, error) {
|
||||
msg, err := s.iam.GetCustomMessageText(ctx, domain.VerifyEmailMessageType, req.Language)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.GetCustomVerifyEmailMessageTextResponse{
|
||||
CustomText: text_grpc.DomainCustomMsgTextToPb(msg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -87,7 +117,17 @@ func (s *Server) GetDefaultVerifyPhoneMessageText(ctx context.Context, req *admi
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.GetDefaultVerifyPhoneMessageTextResponse{
|
||||
CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
|
||||
CustomText: text_grpc.DomainCustomMsgTextToPb(msg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetCustomVerifyPhoneMessageText(ctx context.Context, req *admin_pb.GetCustomVerifyPhoneMessageTextRequest) (*admin_pb.GetCustomVerifyPhoneMessageTextResponse, error) {
|
||||
msg, err := s.iam.GetCustomMessageText(ctx, domain.VerifyPhoneMessageType, req.Language)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.GetCustomVerifyPhoneMessageTextResponse{
|
||||
CustomText: text_grpc.DomainCustomMsgTextToPb(msg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -111,7 +151,17 @@ func (s *Server) GetDefaultDomainClaimedMessageText(ctx context.Context, req *ad
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.GetDefaultDomainClaimedMessageTextResponse{
|
||||
CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
|
||||
CustomText: text_grpc.DomainCustomMsgTextToPb(msg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetCustomDomainClaimedMessageText(ctx context.Context, req *admin_pb.GetCustomDomainClaimedMessageTextRequest) (*admin_pb.GetCustomDomainClaimedMessageTextResponse, error) {
|
||||
msg, err := s.iam.GetCustomMessageText(ctx, domain.DomainClaimedMessageType, req.Language)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.GetCustomDomainClaimedMessageTextResponse{
|
||||
CustomText: text_grpc.DomainCustomMsgTextToPb(msg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
16
internal/api/grpc/admin/language.go
Normal file
16
internal/api/grpc/admin/language.go
Normal file
@ -0,0 +1,16 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/grpc/text"
|
||||
admin_pb "github.com/caos/zitadel/pkg/grpc/admin"
|
||||
)
|
||||
|
||||
func (s *Server) GetSupportedLanguages(ctx context.Context, req *admin_pb.GetSupportedLanguagesRequest) (*admin_pb.GetSupportedLanguagesResponse, error) {
|
||||
langs, err := s.iam.Languages(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.GetSupportedLanguagesResponse{Languages: text.LanguageTagsToStrings(langs)}, nil
|
||||
}
|
16
internal/api/grpc/auth/language.go
Normal file
16
internal/api/grpc/auth/language.go
Normal file
@ -0,0 +1,16 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/grpc/text"
|
||||
auth_pb "github.com/caos/zitadel/pkg/grpc/auth"
|
||||
)
|
||||
|
||||
func (s *Server) GetSupportedLanguages(ctx context.Context, req *auth_pb.GetSupportedLanguagesRequest) (*auth_pb.GetSupportedLanguagesResponse, error) {
|
||||
langs, err := s.repo.Languages(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &auth_pb.GetSupportedLanguagesResponse{Languages: text.LanguageTagsToStrings(langs)}, nil
|
||||
}
|
@ -18,7 +18,7 @@ func (s *Server) GetCustomInitMessageText(ctx context.Context, req *mgmt_pb.GetC
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.GetCustomInitMessageTextResponse{
|
||||
CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
|
||||
CustomText: text_grpc.DomainCustomMsgTextToPb(msg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -28,7 +28,7 @@ func (s *Server) GetDefaultInitMessageText(ctx context.Context, req *mgmt_pb.Get
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.GetDefaultInitMessageTextResponse{
|
||||
CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
|
||||
CustomText: text_grpc.DomainCustomMsgTextToPb(msg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -66,7 +66,7 @@ func (s *Server) GetCustomPasswordResetMessageText(ctx context.Context, req *mgm
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.GetCustomPasswordResetMessageTextResponse{
|
||||
CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
|
||||
CustomText: text_grpc.DomainCustomMsgTextToPb(msg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -76,7 +76,7 @@ func (s *Server) GetDefaultPasswordResetMessageText(ctx context.Context, req *mg
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.GetDefaultPasswordResetMessageTextResponse{
|
||||
CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
|
||||
CustomText: text_grpc.DomainCustomMsgTextToPb(msg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -114,7 +114,7 @@ func (s *Server) GetCustomVerifyEmailMessageText(ctx context.Context, req *mgmt_
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.GetCustomVerifyEmailMessageTextResponse{
|
||||
CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
|
||||
CustomText: text_grpc.DomainCustomMsgTextToPb(msg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -124,7 +124,7 @@ func (s *Server) GetDefaultVerifyEmailMessageText(ctx context.Context, req *mgmt
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.GetDefaultVerifyEmailMessageTextResponse{
|
||||
CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
|
||||
CustomText: text_grpc.DomainCustomMsgTextToPb(msg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -162,7 +162,7 @@ func (s *Server) GetCustomVerifyPhoneMessageText(ctx context.Context, req *mgmt_
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.GetCustomVerifyPhoneMessageTextResponse{
|
||||
CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
|
||||
CustomText: text_grpc.DomainCustomMsgTextToPb(msg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -172,7 +172,7 @@ func (s *Server) GetDefaultVerifyPhoneMessageText(ctx context.Context, req *mgmt
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.GetDefaultVerifyPhoneMessageTextResponse{
|
||||
CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
|
||||
CustomText: text_grpc.DomainCustomMsgTextToPb(msg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -210,7 +210,7 @@ func (s *Server) GetCustomDomainClaimedMessageText(ctx context.Context, req *mgm
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.GetCustomDomainClaimedMessageTextResponse{
|
||||
CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
|
||||
CustomText: text_grpc.DomainCustomMsgTextToPb(msg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -220,7 +220,7 @@ func (s *Server) GetDefaultDomainClaimedMessageText(ctx context.Context, req *mg
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.GetDefaultDomainClaimedMessageTextResponse{
|
||||
CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
|
||||
CustomText: text_grpc.DomainCustomMsgTextToPb(msg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
16
internal/api/grpc/management/language.go
Normal file
16
internal/api/grpc/management/language.go
Normal file
@ -0,0 +1,16 @@
|
||||
package management
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/grpc/text"
|
||||
mgmt_pb "github.com/caos/zitadel/pkg/grpc/management"
|
||||
)
|
||||
|
||||
func (s *Server) GetSupportedLanguages(ctx context.Context, req *mgmt_pb.GetSupportedLanguagesRequest) (*mgmt_pb.GetSupportedLanguagesResponse, error) {
|
||||
langs, err := s.org.Languages(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.GetSupportedLanguagesResponse{Languages: text.LanguageTagsToStrings(langs)}, nil
|
||||
}
|
@ -43,7 +43,7 @@ func AddHumanUserRequestToDomain(req *mgmt_pb.AddHumanUserRequest) *domain.Human
|
||||
Username: req.UserName,
|
||||
}
|
||||
preferredLanguage, err := language.Parse(req.Profile.PreferredLanguage)
|
||||
logging.Log("MANAG-3GUFJ").OnError(err).Debug("language malformed")
|
||||
logging.Log("MANAG-M029f").OnError(err).Debug("language malformed")
|
||||
h.Profile = &domain.Profile{
|
||||
FirstName: req.Profile.FirstName,
|
||||
LastName: req.Profile.LastName,
|
||||
|
@ -3,11 +3,10 @@ package text
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/api/grpc/object"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/iam/model"
|
||||
text_pb "github.com/caos/zitadel/pkg/grpc/text"
|
||||
)
|
||||
|
||||
func ModelCustomMsgTextToPb(msg *model.MessageTextView) *text_pb.MessageCustomText {
|
||||
func DomainCustomMsgTextToPb(msg *domain.CustomMessageText) *text_pb.MessageCustomText {
|
||||
return &text_pb.MessageCustomText{
|
||||
Title: msg.Title,
|
||||
PreHeader: msg.PreHeader,
|
||||
|
13
internal/api/grpc/text/language.go
Normal file
13
internal/api/grpc/text/language.go
Normal file
@ -0,0 +1,13 @@
|
||||
package text
|
||||
|
||||
import (
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
func LanguageTagsToStrings(langs []language.Tag) []string {
|
||||
result := make([]string, len(langs))
|
||||
for i, lang := range langs {
|
||||
result[i] = lang.String()
|
||||
}
|
||||
return result
|
||||
}
|
@ -2,15 +2,35 @@ package eventstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/caos/logging"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/caos/zitadel/internal/i18n"
|
||||
"github.com/caos/zitadel/internal/query"
|
||||
|
||||
"github.com/caos/zitadel/internal/iam/model"
|
||||
)
|
||||
|
||||
type IAMRepository struct {
|
||||
IAMID string
|
||||
IAMID string
|
||||
LoginDir http.FileSystem
|
||||
|
||||
IAMV2QuerySide *query.Queries
|
||||
supportedLangs []language.Tag
|
||||
}
|
||||
|
||||
func (repo *IAMRepository) Languages(ctx context.Context) ([]language.Tag, error) {
|
||||
if len(repo.supportedLangs) == 0 {
|
||||
langs, err := i18n.SupportedLanguages(repo.LoginDir)
|
||||
if err != nil {
|
||||
logging.Log("ADMIN-tiMWs").WithError(err).Debug("unable to parse language")
|
||||
return nil, err
|
||||
}
|
||||
repo.supportedLangs = langs
|
||||
}
|
||||
return repo.supportedLangs, nil
|
||||
}
|
||||
|
||||
func (repo *IAMRepository) GetIAM(ctx context.Context) (*model.IAM, error) {
|
||||
|
@ -3,6 +3,9 @@ package eventsourcing
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/logging"
|
||||
"github.com/rakyll/statik/fs"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/eventstore"
|
||||
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/spooler"
|
||||
@ -76,6 +79,9 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, co
|
||||
return nil, err
|
||||
}
|
||||
|
||||
statikLoginFS, err := fs.NewWithNamespace("login")
|
||||
logging.Log("CONFI-20opp").OnError(err).Panic("unable to start login statik dir")
|
||||
|
||||
keyChan := make(chan *key_model.KeyView)
|
||||
spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, systemDefaults, keyChan)
|
||||
locker := spooler.NewLocker(sqlClient)
|
||||
@ -153,6 +159,7 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, co
|
||||
},
|
||||
eventstore.IAMRepository{
|
||||
IAMID: systemDefaults.IamID,
|
||||
LoginDir: statikLoginFS,
|
||||
IAMV2QuerySide: queries,
|
||||
},
|
||||
eventstore.FeaturesRepo{
|
||||
|
@ -3,9 +3,12 @@ package repository
|
||||
import (
|
||||
"context"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/caos/zitadel/internal/iam/model"
|
||||
)
|
||||
|
||||
type IAMRepository interface {
|
||||
Languages(ctx context.Context) ([]language.Tag, error)
|
||||
GetIAM(ctx context.Context) (*model.IAM, error)
|
||||
}
|
||||
|
@ -21,6 +21,14 @@ const (
|
||||
MessageFooterText = "Footer"
|
||||
)
|
||||
|
||||
type MessageTexts struct {
|
||||
InitCode CustomMessageText
|
||||
PasswordReset CustomMessageText
|
||||
VerifyEmail CustomMessageText
|
||||
VerifyPhone CustomMessageText
|
||||
DomainClaimed CustomMessageText
|
||||
}
|
||||
|
||||
type CustomMessageText struct {
|
||||
models.ObjectRoot
|
||||
|
||||
@ -40,3 +48,19 @@ type CustomMessageText struct {
|
||||
func (m *CustomMessageText) IsValid() bool {
|
||||
return m.MessageTextType != "" && m.Language != language.Und
|
||||
}
|
||||
|
||||
func (m *MessageTexts) GetMessageTextByType(msgType string) *CustomMessageText {
|
||||
switch msgType {
|
||||
case InitCodeMessageType:
|
||||
return &m.InitCode
|
||||
case PasswordResetMessageType:
|
||||
return &m.PasswordReset
|
||||
case VerifyEmailMessageType:
|
||||
return &m.VerifyEmail
|
||||
case VerifyPhoneMessageType:
|
||||
return &m.VerifyPhone
|
||||
case DomainClaimedMessageType:
|
||||
return &m.DomainClaimed
|
||||
}
|
||||
return nil
|
||||
}
|
@ -8,10 +8,6 @@ import (
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
)
|
||||
|
||||
type MessageTextsView struct {
|
||||
Texts []*MessageTextView
|
||||
Default bool
|
||||
}
|
||||
type MessageTextView struct {
|
||||
AggregateID string
|
||||
MessageTextType string
|
||||
|
@ -110,6 +110,46 @@ func (r *CustomTextView) IsMessageTemplate() bool {
|
||||
r.Template == domain.DomainClaimedMessageType
|
||||
}
|
||||
|
||||
func CustomTextViewsToMessageDomain(aggregateID, lang string, texts []*CustomTextView) *domain.CustomMessageText {
|
||||
langTag := language.Make(lang)
|
||||
result := &domain.CustomMessageText{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: aggregateID,
|
||||
},
|
||||
Language: langTag,
|
||||
}
|
||||
for _, text := range texts {
|
||||
if text.CreationDate.Before(result.CreationDate) {
|
||||
result.CreationDate = text.CreationDate
|
||||
}
|
||||
if text.ChangeDate.After(result.ChangeDate) {
|
||||
result.ChangeDate = text.ChangeDate
|
||||
}
|
||||
if text.Key == domain.MessageTitle {
|
||||
result.Title = text.Text
|
||||
}
|
||||
if text.Key == domain.MessagePreHeader {
|
||||
result.PreHeader = text.Text
|
||||
}
|
||||
if text.Key == domain.MessageSubject {
|
||||
result.Subject = text.Text
|
||||
}
|
||||
if text.Key == domain.MessageGreeting {
|
||||
result.Greeting = text.Text
|
||||
}
|
||||
if text.Key == domain.MessageText {
|
||||
result.Text = text.Text
|
||||
}
|
||||
if text.Key == domain.MessageButtonText {
|
||||
result.ButtonText = text.Text
|
||||
}
|
||||
if text.Key == domain.MessageFooterText {
|
||||
result.FooterText = text.Text
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func CustomTextViewsToLoginDomain(aggregateID, lang string, texts []*CustomTextView) *domain.CustomLoginText {
|
||||
langTag := language.Make(lang)
|
||||
result := &domain.CustomLoginText{
|
||||
|
@ -59,12 +59,6 @@ func MessageTextViewFromModel(template *model.MessageTextView) *MessageTextView
|
||||
}
|
||||
}
|
||||
|
||||
func MessageTextsViewToModel(textsIn []*MessageTextView, defaultIn bool) *model.MessageTextsView {
|
||||
return &model.MessageTextsView{
|
||||
Texts: messageTextsViewToModelArr(textsIn, defaultIn),
|
||||
}
|
||||
}
|
||||
|
||||
func messageTextsViewToModelArr(texts []*MessageTextView, defaultIn bool) []*model.MessageTextView {
|
||||
result := make([]*model.MessageTextView, len(texts))
|
||||
for i, r := range texts {
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"github.com/caos/logging"
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/config/systemdefaults"
|
||||
@ -21,6 +22,7 @@ import (
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/caos/zitadel/internal/i18n"
|
||||
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||
iam_view "github.com/caos/zitadel/internal/iam/repository/view"
|
||||
iam_es_model "github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||
@ -37,15 +39,30 @@ import (
|
||||
)
|
||||
|
||||
type OrgRepository struct {
|
||||
SearchLimit uint64
|
||||
Eventstore v1.Eventstore
|
||||
View *mgmt_view.View
|
||||
Roles []string
|
||||
SystemDefaults systemdefaults.SystemDefaults
|
||||
PrefixAvatarURL string
|
||||
LoginDir http.FileSystem
|
||||
TranslationFileContents map[string][]byte
|
||||
mutex sync.Mutex
|
||||
SearchLimit uint64
|
||||
Eventstore v1.Eventstore
|
||||
View *mgmt_view.View
|
||||
Roles []string
|
||||
SystemDefaults systemdefaults.SystemDefaults
|
||||
PrefixAvatarURL string
|
||||
LoginDir http.FileSystem
|
||||
NotificationDir http.FileSystem
|
||||
LoginTranslationFileContents map[string][]byte
|
||||
NotificationTranslationFileContents map[string][]byte
|
||||
mutex sync.Mutex
|
||||
supportedLangs []language.Tag
|
||||
}
|
||||
|
||||
func (repo *OrgRepository) Languages(ctx context.Context) ([]language.Tag, error) {
|
||||
if len(repo.supportedLangs) == 0 {
|
||||
langs, err := i18n.SupportedLanguages(repo.LoginDir)
|
||||
if err != nil {
|
||||
logging.Log("ADMIN-tiMWs").WithError(err).Debug("unable to parse language")
|
||||
return nil, err
|
||||
}
|
||||
repo.supportedLangs = langs
|
||||
}
|
||||
return repo.supportedLangs, nil
|
||||
}
|
||||
|
||||
func (repo *OrgRepository) OrgByID(ctx context.Context, id string) (*org_model.OrgView, error) {
|
||||
@ -576,68 +593,74 @@ func (repo *OrgRepository) GetMailTemplate(ctx context.Context) (*iam_model.Mail
|
||||
return iam_es_model.MailTemplateViewToModel(template), err
|
||||
}
|
||||
|
||||
func (repo *OrgRepository) GetDefaultMessageTexts(ctx context.Context) (*iam_model.MessageTextsView, error) {
|
||||
texts, err := repo.View.MessageTextsByAggregateID(repo.SystemDefaults.IamID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return iam_es_model.MessageTextsViewToModel(texts, true), err
|
||||
}
|
||||
|
||||
func (repo *OrgRepository) GetMessageTexts(ctx context.Context) (*iam_model.MessageTextsView, error) {
|
||||
defaultIn := false
|
||||
texts, err := repo.View.MessageTextsByAggregateID(authz.GetCtxData(ctx).OrgID)
|
||||
if errors.IsNotFound(err) || len(texts) == 0 {
|
||||
texts, err = repo.View.MessageTextsByAggregateID(repo.SystemDefaults.IamID)
|
||||
func (repo *OrgRepository) GetDefaultMessageText(ctx context.Context, textType, lang string) (*domain.CustomMessageText, error) {
|
||||
repo.mutex.Lock()
|
||||
defer repo.mutex.Unlock()
|
||||
var err error
|
||||
contents, ok := repo.NotificationTranslationFileContents[lang]
|
||||
if !ok {
|
||||
contents, err = repo.readTranslationFile(repo.NotificationDir, fmt.Sprintf("/i18n/%s.yaml", lang))
|
||||
if errors.IsNotFound(err) {
|
||||
contents, err = repo.readTranslationFile(repo.NotificationDir, fmt.Sprintf("/i18n/%s.yaml", repo.SystemDefaults.DefaultLanguage.String()))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defaultIn = true
|
||||
repo.NotificationTranslationFileContents[lang] = contents
|
||||
}
|
||||
notificationTextMap := make(map[string]interface{})
|
||||
if err := yaml.Unmarshal(contents, ¬ificationTextMap); err != nil {
|
||||
return nil, errors.ThrowInternal(err, "TEXT-093sd", "Errors.TranslationFile.ReadError")
|
||||
}
|
||||
texts, err := repo.View.CustomTextsByAggregateIDAndTemplateAndLand(repo.SystemDefaults.IamID, textType, lang)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return iam_es_model.MessageTextsViewToModel(texts, defaultIn), err
|
||||
}
|
||||
|
||||
func (repo *OrgRepository) GetDefaultMessageText(ctx context.Context, textType, lang string) (*iam_model.MessageTextView, error) {
|
||||
text, err := repo.View.MessageTextByIDs(repo.SystemDefaults.IamID, textType, lang)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
text.Default = true
|
||||
return iam_es_model.MessageTextViewToModel(text), err
|
||||
}
|
||||
|
||||
func (repo *OrgRepository) GetMessageText(ctx context.Context, orgID, textType, lang string) (*iam_model.MessageTextView, error) {
|
||||
text, err := repo.View.MessageTextByIDs(orgID, textType, lang)
|
||||
if errors.IsNotFound(err) {
|
||||
result, err := repo.GetDefaultMessageText(ctx, textType, lang)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
for _, text := range texts {
|
||||
messageTextMap, ok := notificationTextMap[textType].(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
return result, nil
|
||||
messageTextMap[text.Key] = text.Text
|
||||
}
|
||||
jsonbody, err := json.Marshal(notificationTextMap)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "TEXT-02m8f", "Errors.TranslationFile.MergeError")
|
||||
}
|
||||
notificationText := new(domain.MessageTexts)
|
||||
if err := json.Unmarshal(jsonbody, ¬ificationText); err != nil {
|
||||
return nil, errors.ThrowInternal(err, "TEXT-20ops", "Errors.TranslationFile.MergeError")
|
||||
}
|
||||
result := notificationText.GetMessageTextByType(textType)
|
||||
result.Default = true
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (repo *OrgRepository) GetMessageText(ctx context.Context, orgID, textType, lang string) (*domain.CustomMessageText, error) {
|
||||
texts, err := repo.View.CustomTextsByAggregateIDAndTemplateAndLand(orgID, textType, lang)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return iam_es_model.MessageTextViewToModel(text), err
|
||||
if len(texts) == 0 {
|
||||
return repo.GetDefaultMessageText(ctx, textType, lang)
|
||||
}
|
||||
return iam_es_model.CustomTextViewsToMessageDomain(repo.SystemDefaults.IamID, lang, texts), err
|
||||
}
|
||||
|
||||
func (repo *OrgRepository) GetDefaultLoginTexts(ctx context.Context, lang string) (*domain.CustomLoginText, error) {
|
||||
repo.mutex.Lock()
|
||||
defer repo.mutex.Unlock()
|
||||
contents, ok := repo.TranslationFileContents[lang]
|
||||
contents, ok := repo.LoginTranslationFileContents[lang]
|
||||
var err error
|
||||
if !ok {
|
||||
contents, err = repo.readTranslationFile(fmt.Sprintf("/i18n/%s.yaml", lang))
|
||||
contents, err = repo.readTranslationFile(repo.LoginDir, fmt.Sprintf("/i18n/%s.yaml", lang))
|
||||
if errors.IsNotFound(err) {
|
||||
contents, err = repo.readTranslationFile(fmt.Sprintf("/i18n/%s.yaml", repo.SystemDefaults.DefaultLanguage.String()))
|
||||
contents, err = repo.readTranslationFile(repo.LoginDir, fmt.Sprintf("/i18n/%s.yaml", repo.SystemDefaults.DefaultLanguage.String()))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repo.TranslationFileContents[lang] = contents
|
||||
repo.LoginTranslationFileContents[lang] = contents
|
||||
}
|
||||
loginTextMap := make(map[string]interface{})
|
||||
if err := yaml.Unmarshal(contents, &loginTextMap); err != nil {
|
||||
@ -770,8 +793,8 @@ func (repo *OrgRepository) getIAMEvents(ctx context.Context, sequence uint64) ([
|
||||
return repo.Eventstore.FilterEvents(ctx, query)
|
||||
}
|
||||
|
||||
func (repo *OrgRepository) readTranslationFile(filename string) ([]byte, error) {
|
||||
r, err := repo.LoginDir.Open(filename)
|
||||
func (repo *OrgRepository) readTranslationFile(dir http.FileSystem, filename string) ([]byte, error) {
|
||||
r, err := dir.Open(filename)
|
||||
if os.IsNotExist(err) {
|
||||
return nil, errors.ThrowNotFound(err, "TEXT-93nfl", "Errors.TranslationFile.NotFound")
|
||||
}
|
||||
|
@ -58,17 +58,22 @@ func Start(conf Config, systemDefaults sd.SystemDefaults, roles []string, querie
|
||||
statikLoginFS, err := fs.NewWithNamespace("login")
|
||||
logging.Log("CONFI-7usEW").OnError(err).Panic("unable to start login statik dir")
|
||||
|
||||
statikNotificationFS, err := fs.NewWithNamespace("notification")
|
||||
logging.Log("CONFI-7usEW").OnError(err).Panic("unable to start notification statik dir")
|
||||
|
||||
return &EsRepository{
|
||||
spooler: spool,
|
||||
OrgRepository: eventstore.OrgRepository{
|
||||
SearchLimit: conf.SearchLimit,
|
||||
Eventstore: es,
|
||||
View: view,
|
||||
Roles: roles,
|
||||
SystemDefaults: systemDefaults,
|
||||
PrefixAvatarURL: assetsAPI,
|
||||
LoginDir: statikLoginFS,
|
||||
TranslationFileContents: make(map[string][]byte),
|
||||
SearchLimit: conf.SearchLimit,
|
||||
Eventstore: es,
|
||||
View: view,
|
||||
Roles: roles,
|
||||
SystemDefaults: systemDefaults,
|
||||
PrefixAvatarURL: assetsAPI,
|
||||
LoginDir: statikLoginFS,
|
||||
NotificationDir: statikNotificationFS,
|
||||
LoginTranslationFileContents: make(map[string][]byte),
|
||||
NotificationTranslationFileContents: make(map[string][]byte),
|
||||
},
|
||||
ProjectRepo: eventstore.ProjectRepo{es, conf.SearchLimit, view, roles, systemDefaults.IamID, assetsAPI},
|
||||
UserRepo: eventstore.UserRepo{es, conf.SearchLimit, view, systemDefaults, assetsAPI},
|
||||
|
@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||
|
||||
@ -11,6 +13,7 @@ import (
|
||||
)
|
||||
|
||||
type OrgRepository interface {
|
||||
Languages(ctx context.Context) ([]language.Tag, error)
|
||||
OrgByID(ctx context.Context, id string) (*org_model.OrgView, error)
|
||||
OrgByDomainGlobal(ctx context.Context, domain string) (*org_model.OrgView, error)
|
||||
OrgChanges(ctx context.Context, id string, lastSequence uint64, limit uint64, sortAscending bool, auditLogRetention time.Duration) (*org_model.OrgChanges, error)
|
||||
@ -48,10 +51,8 @@ type OrgRepository interface {
|
||||
GetDefaultMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error)
|
||||
GetMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error)
|
||||
|
||||
GetDefaultMessageTexts(ctx context.Context) (*iam_model.MessageTextsView, error)
|
||||
GetMessageTexts(ctx context.Context) (*iam_model.MessageTextsView, error)
|
||||
GetDefaultMessageText(ctx context.Context, textType string, language string) (*iam_model.MessageTextView, error)
|
||||
GetMessageText(ctx context.Context, orgID, textType, language string) (*iam_model.MessageTextView, error)
|
||||
GetDefaultMessageText(ctx context.Context, textType string, language string) (*domain.CustomMessageText, error)
|
||||
GetMessageText(ctx context.Context, orgID, textType, lang string) (*domain.CustomMessageText, error)
|
||||
|
||||
GetDefaultLoginTexts(ctx context.Context, lang string) (*domain.CustomLoginText, error)
|
||||
GetLoginTexts(ctx context.Context, orgID, lang string) (*domain.CustomLoginText, error)
|
||||
|
@ -0,0 +1,125 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/caos/logging"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/query"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/spooler"
|
||||
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
|
||||
iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||
"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
|
||||
)
|
||||
|
||||
type CustomText struct {
|
||||
handler
|
||||
subscription *v1.Subscription
|
||||
}
|
||||
|
||||
func newCustomText(handler handler) *CustomText {
|
||||
h := &CustomText{
|
||||
handler: handler,
|
||||
}
|
||||
|
||||
h.subscribe()
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func (m *CustomText) subscribe() {
|
||||
m.subscription = m.es.Subscribe(m.AggregateTypes()...)
|
||||
go func() {
|
||||
for event := range m.subscription.Events {
|
||||
query.ReduceEvent(m, event)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
const (
|
||||
customTextTable = "notification.custom_texts"
|
||||
)
|
||||
|
||||
func (m *CustomText) ViewModel() string {
|
||||
return customTextTable
|
||||
}
|
||||
|
||||
func (m *CustomText) Subscription() *v1.Subscription {
|
||||
return m.subscription
|
||||
}
|
||||
|
||||
func (_ *CustomText) AggregateTypes() []es_models.AggregateType {
|
||||
return []es_models.AggregateType{model.OrgAggregate, iam_es_model.IAMAggregate}
|
||||
}
|
||||
|
||||
func (p *CustomText) CurrentSequence() (uint64, error) {
|
||||
sequence, err := p.view.GetLatestCustomTextSequence()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return sequence.CurrentSequence, nil
|
||||
}
|
||||
|
||||
func (m *CustomText) EventQuery() (*es_models.SearchQuery, error) {
|
||||
sequence, err := m.view.GetLatestCustomTextSequence()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return es_models.NewSearchQuery().
|
||||
AggregateTypeFilter(m.AggregateTypes()...).
|
||||
LatestSequenceFilter(sequence.CurrentSequence), nil
|
||||
}
|
||||
|
||||
func (m *CustomText) Reduce(event *es_models.Event) (err error) {
|
||||
switch event.AggregateType {
|
||||
case model.OrgAggregate, iam_es_model.IAMAggregate:
|
||||
err = m.processCustomText(event)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *CustomText) processCustomText(event *es_models.Event) (err error) {
|
||||
customText := new(iam_model.CustomTextView)
|
||||
switch event.Type {
|
||||
case iam_es_model.CustomTextSet, model.CustomTextSet:
|
||||
text := new(iam_model.CustomTextView)
|
||||
err = text.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
customText, err = m.view.CustomTextByIDs(event.AggregateID, text.Template, text.Key, text.Language)
|
||||
if err != nil && !caos_errs.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
if caos_errs.IsNotFound(err) {
|
||||
err = nil
|
||||
customText = new(iam_model.CustomTextView)
|
||||
customText.Language = text.Language
|
||||
customText.Template = text.Template
|
||||
customText.CreationDate = event.CreationDate
|
||||
}
|
||||
err = customText.AppendEvent(event)
|
||||
case iam_es_model.CustomTextRemoved, model.CustomTextRemoved:
|
||||
text := new(iam_model.CustomTextView)
|
||||
err = text.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return m.view.DeleteCustomText(event.AggregateID, text.Template, text.Language, event)
|
||||
default:
|
||||
return m.view.ProcessedCustomTextSequence(event)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return m.view.PutCustomText(customText, event)
|
||||
}
|
||||
|
||||
func (m *CustomText) OnError(event *es_models.Event, err error) error {
|
||||
logging.LogWithFields("SPOOL-3m912", "id", event.AggregateID).WithError(err).Warn("something went wrong in custom text handler")
|
||||
return spooler.HandleError(event, err, m.view.GetLatestCustomTextFailedEvent, m.view.ProcessedCustomTextFailedEvent, m.view.ProcessedCustomTextSequence, m.errorCountUntilSkip)
|
||||
}
|
||||
|
||||
func (o *CustomText) OnSuccess() error {
|
||||
return spooler.HandleSuccess(o.view.UpdateCustomTextSpoolerRunTimestamp)
|
||||
}
|
@ -1,17 +1,18 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/command"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/command"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1"
|
||||
|
||||
"github.com/caos/logging"
|
||||
|
||||
sd "github.com/caos/zitadel/internal/config/systemdefaults"
|
||||
"github.com/caos/zitadel/internal/config/types"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/query"
|
||||
"github.com/caos/zitadel/internal/i18n"
|
||||
"github.com/caos/zitadel/internal/notification/repository/eventsourcing/view"
|
||||
)
|
||||
|
||||
@ -34,7 +35,7 @@ func (h *handler) Eventstore() v1.Eventstore {
|
||||
return h.es
|
||||
}
|
||||
|
||||
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es v1.Eventstore, command *command.Commands, systemDefaults sd.SystemDefaults, i18n *i18n.Translator, dir http.FileSystem, apiDomain string) []query.Handler {
|
||||
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es v1.Eventstore, command *command.Commands, systemDefaults sd.SystemDefaults, dir http.FileSystem, apiDomain string) []query.Handler {
|
||||
aesCrypto, err := crypto.NewAESCrypto(systemDefaults.UserVerificationKey)
|
||||
if err != nil {
|
||||
logging.Log("HANDL-s90ew").WithError(err).Debug("error create new aes crypto")
|
||||
@ -44,12 +45,12 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
|
||||
handler{view, bulkLimit, configs.cycleDuration("User"), errorCount, es},
|
||||
systemDefaults.IamID,
|
||||
),
|
||||
newCustomText(handler{view, bulkLimit, configs.cycleDuration("CustomText"), errorCount, es}),
|
||||
newNotification(
|
||||
handler{view, bulkLimit, configs.cycleDuration("Notification"), errorCount, es},
|
||||
command,
|
||||
systemDefaults,
|
||||
aesCrypto,
|
||||
i18n,
|
||||
dir,
|
||||
apiDomain,
|
||||
),
|
||||
|
@ -29,19 +29,14 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
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"
|
||||
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"
|
||||
)
|
||||
|
||||
type Notification struct {
|
||||
@ -49,7 +44,6 @@ type Notification struct {
|
||||
command *command.Commands
|
||||
systemDefaults sd.SystemDefaults
|
||||
AesCrypto crypto.EncryptionAlgorithm
|
||||
i18n *i18n.Translator
|
||||
statikDir http.FileSystem
|
||||
subscription *v1.Subscription
|
||||
apiDomain string
|
||||
@ -60,7 +54,6 @@ func newNotification(
|
||||
command *command.Commands,
|
||||
defaults sd.SystemDefaults,
|
||||
aesCrypto crypto.EncryptionAlgorithm,
|
||||
translator *i18n.Translator,
|
||||
statikDir http.FileSystem,
|
||||
apiDomain string,
|
||||
) *Notification {
|
||||
@ -68,7 +61,6 @@ func newNotification(
|
||||
handler: handler,
|
||||
command: command,
|
||||
systemDefaults: defaults,
|
||||
i18n: translator,
|
||||
statikDir: statikDir,
|
||||
AesCrypto: aesCrypto,
|
||||
apiDomain: apiDomain,
|
||||
@ -166,12 +158,12 @@ func (n *Notification) handleInitUserCode(event *models.Event) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
text, err := n.getMessageText(user, messageTextTypeInitCode, user.PreferredLanguage)
|
||||
translator, err := n.getTranslatorWithOrgTexts(user.ResourceOwner, domain.InitCodeMessageType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = types.SendUserInitCode(string(template.Template), text, user, initCode, n.systemDefaults, n.AesCrypto, colors, n.apiDomain)
|
||||
err = types.SendUserInitCode(string(template.Template), translator, user, initCode, n.systemDefaults, n.AesCrypto, colors, n.apiDomain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -205,11 +197,11 @@ func (n *Notification) handlePasswordCode(event *models.Event) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
text, err := n.getMessageText(user, messageTextTypePasswordReset, user.PreferredLanguage)
|
||||
translator, err := n.getTranslatorWithOrgTexts(user.ResourceOwner, domain.PasswordResetMessageType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = types.SendPasswordCode(string(template.Template), text, user, pwCode, n.systemDefaults, n.AesCrypto, colors, n.apiDomain)
|
||||
err = types.SendPasswordCode(string(template.Template), translator, user, pwCode, n.systemDefaults, n.AesCrypto, colors, n.apiDomain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -243,12 +235,12 @@ func (n *Notification) handleEmailVerificationCode(event *models.Event) (err err
|
||||
return err
|
||||
}
|
||||
|
||||
text, err := n.getMessageText(user, messageTextTypeVerifyEmail, user.PreferredLanguage)
|
||||
translator, err := n.getTranslatorWithOrgTexts(user.ResourceOwner, domain.VerifyEmailMessageType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = types.SendEmailVerificationCode(string(template.Template), text, user, emailCode, n.systemDefaults, n.AesCrypto, colors, n.apiDomain)
|
||||
err = types.SendEmailVerificationCode(string(template.Template), translator, user, emailCode, n.systemDefaults, n.AesCrypto, colors, n.apiDomain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -270,11 +262,11 @@ func (n *Notification) handlePhoneVerificationCode(event *models.Event) (err err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
text, err := n.getMessageText(user, messageTextTypeVerifyPhone, user.PreferredLanguage)
|
||||
translator, err := n.getTranslatorWithOrgTexts(user.ResourceOwner, domain.VerifyPhoneMessageType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = types.SendPhoneVerificationCode(text, user, phoneCode, n.systemDefaults, n.AesCrypto)
|
||||
err = types.SendPhoneVerificationCode(translator, user, phoneCode, n.systemDefaults, n.AesCrypto)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -309,11 +301,11 @@ func (n *Notification) handleDomainClaimed(event *models.Event) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
text, err := n.getMessageText(user, messageTextTypeDomainClaimed, user.PreferredLanguage)
|
||||
translator, err := n.getTranslatorWithOrgTexts(user.ResourceOwner, domain.DomainClaimedMessageType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = types.SendDomainClaimed(string(template.Template), text, user, data["userName"], n.systemDefaults, colors, n.apiDomain)
|
||||
err = types.SendDomainClaimed(string(template.Template), translator, user, data["userName"], n.systemDefaults, colors, n.apiDomain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -400,6 +392,31 @@ func (n *Notification) getMailTemplate(ctx context.Context) (*iam_model.MailTemp
|
||||
return iam_es_model.MailTemplateViewToModel(template), err
|
||||
}
|
||||
|
||||
func (n *Notification) getTranslatorWithOrgTexts(orgID, textType string) (*i18n.Translator, error) {
|
||||
translator, err := i18n.NewTranslator(n.statikDir, i18n.TranslatorConfig{DefaultLanguage: n.systemDefaults.DefaultLanguage})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allCustomTexts, err := n.view.CustomTextsByAggregateIDAndTemplate(domain.IAMID, textType)
|
||||
if err == nil {
|
||||
return translator, nil
|
||||
}
|
||||
customTexts, err := n.view.CustomTextsByAggregateIDAndTemplate(orgID, textType)
|
||||
if err == nil {
|
||||
return translator, nil
|
||||
}
|
||||
allCustomTexts = append(allCustomTexts, customTexts...)
|
||||
|
||||
for _, text := range allCustomTexts {
|
||||
msg := i18n.Message{
|
||||
ID: text.Key,
|
||||
Text: text.Text,
|
||||
}
|
||||
translator.AddMessages(language.Make(text.Language), msg)
|
||||
}
|
||||
return translator, nil
|
||||
}
|
||||
|
||||
// Read organization specific texts
|
||||
func (n *Notification) getMessageText(user *model.NotifyUser, textType, lang string) (*iam_model.MessageTextView, error) {
|
||||
langTag := language.Make(lang)
|
||||
|
@ -1,17 +1,18 @@
|
||||
package eventsourcing
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/caos/zitadel/internal/command"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
|
||||
sd "github.com/caos/zitadel/internal/config/systemdefaults"
|
||||
"github.com/caos/zitadel/internal/config/types"
|
||||
es_spol "github.com/caos/zitadel/internal/eventstore/v1/spooler"
|
||||
"github.com/caos/zitadel/internal/i18n"
|
||||
"github.com/caos/zitadel/internal/notification/repository/eventsourcing/spooler"
|
||||
noti_view "github.com/caos/zitadel/internal/notification/repository/eventsourcing/view"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
@ -41,11 +42,7 @@ func Start(conf Config, dir http.FileSystem, systemDefaults sd.SystemDefaults, c
|
||||
return nil, err
|
||||
}
|
||||
|
||||
translator, err := i18n.NewTranslator(dir, i18n.TranslatorConfig{DefaultLanguage: conf.DefaultLanguage})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, command, systemDefaults, translator, dir, apiDomain)
|
||||
spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, command, systemDefaults, dir, apiDomain)
|
||||
|
||||
return &EsRepository{
|
||||
spool,
|
||||
|
@ -2,13 +2,13 @@ package spooler
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"net/http"
|
||||
|
||||
"github.com/caos/zitadel/internal/command"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1"
|
||||
"net/http"
|
||||
|
||||
sd "github.com/caos/zitadel/internal/config/systemdefaults"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/spooler"
|
||||
"github.com/caos/zitadel/internal/i18n"
|
||||
"github.com/caos/zitadel/internal/notification/repository/eventsourcing/handler"
|
||||
"github.com/caos/zitadel/internal/notification/repository/eventsourcing/view"
|
||||
)
|
||||
@ -20,12 +20,12 @@ type SpoolerConfig struct {
|
||||
Handlers handler.Configs
|
||||
}
|
||||
|
||||
func StartSpooler(c SpoolerConfig, es v1.Eventstore, view *view.View, sql *sql.DB, command *command.Commands, systemDefaults sd.SystemDefaults, i18n *i18n.Translator, dir http.FileSystem, apiDomain string) *spooler.Spooler {
|
||||
func StartSpooler(c SpoolerConfig, es v1.Eventstore, view *view.View, sql *sql.DB, command *command.Commands, systemDefaults sd.SystemDefaults, dir http.FileSystem, apiDomain string) *spooler.Spooler {
|
||||
spoolerConfig := spooler.Config{
|
||||
Eventstore: es,
|
||||
Locker: &locker{dbClient: sql},
|
||||
ConcurrentWorkers: c.ConcurrentWorkers,
|
||||
ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, command, systemDefaults, i18n, dir, apiDomain),
|
||||
ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, command, systemDefaults, dir, apiDomain),
|
||||
}
|
||||
spool := spoolerConfig.New()
|
||||
spool.Start()
|
||||
|
@ -0,0 +1,61 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/caos/zitadel/internal/iam/repository/view"
|
||||
"github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||
global_view "github.com/caos/zitadel/internal/view/repository"
|
||||
)
|
||||
|
||||
const (
|
||||
customTextTable = "notification.custom_texts"
|
||||
)
|
||||
|
||||
func (v *View) CustomTextByIDs(aggregateID, template, lang, key string) (*model.CustomTextView, error) {
|
||||
return view.CustomTextByIDs(v.Db, customTextTable, aggregateID, template, lang, key)
|
||||
}
|
||||
|
||||
func (v *View) CustomTextsByAggregateIDAndTemplate(aggregateID, template string) ([]*model.CustomTextView, error) {
|
||||
return view.GetCustomTextsByAggregateIDAndTemplate(v.Db, customTextTable, aggregateID, template)
|
||||
}
|
||||
|
||||
func (v *View) CustomTextsByAggregateIDAndTemplateAndLang(aggregateID, template, lang string) ([]*model.CustomTextView, error) {
|
||||
return view.GetCustomTexts(v.Db, customTextTable, aggregateID, template, lang)
|
||||
}
|
||||
|
||||
func (v *View) PutCustomText(template *model.CustomTextView, event *models.Event) error {
|
||||
err := view.PutCustomText(v.Db, customTextTable, template)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return v.ProcessedCustomTextSequence(event)
|
||||
}
|
||||
|
||||
func (v *View) DeleteCustomText(aggregateID, textType, lang string, event *models.Event) error {
|
||||
err := view.DeleteCustomText(v.Db, customTextTable, aggregateID, textType, lang)
|
||||
if err != nil && !errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
return v.ProcessedCustomTextSequence(event)
|
||||
}
|
||||
|
||||
func (v *View) GetLatestCustomTextSequence() (*global_view.CurrentSequence, error) {
|
||||
return v.latestSequence(customTextTable)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedCustomTextSequence(event *models.Event) error {
|
||||
return v.saveCurrentSequence(customTextTable, event)
|
||||
}
|
||||
|
||||
func (v *View) UpdateCustomTextSpoolerRunTimestamp() error {
|
||||
return v.updateSpoolerRunSequence(customTextTable)
|
||||
}
|
||||
|
||||
func (v *View) GetLatestCustomTextFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
|
||||
return v.latestFailedEvent(customTextTable, sequence)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedCustomTextFailedEvent(failedEvent *global_view.FailedEvent) error {
|
||||
return v.saveFailedEvent(failedEvent)
|
||||
}
|
@ -5,6 +5,7 @@ import (
|
||||
"html"
|
||||
"strings"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/i18n"
|
||||
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||
)
|
||||
@ -16,10 +17,6 @@ const (
|
||||
defaultFontColor = "#22292f"
|
||||
defaultBackgroundColor = "#fafafa"
|
||||
defaultPrimaryColor = "#5282C1"
|
||||
//defaultOrgName = "CAOS AG"
|
||||
//defaultOrgURL = "http://www.caos.ch"
|
||||
//defaultFooter1 = "Teufener Strasse 19"
|
||||
//defaultFooter2 = "CH-9000 St. Gallen"
|
||||
)
|
||||
|
||||
type TemplateData struct {
|
||||
@ -41,28 +38,19 @@ type TemplateData struct {
|
||||
FooterText string
|
||||
}
|
||||
|
||||
func (data *TemplateData) Translate(i18n *i18n.Translator, args map[string]interface{}, langs ...string) {
|
||||
data.Title = i18n.Localize(data.Title, nil, langs...)
|
||||
data.PreHeader = i18n.Localize(data.PreHeader, nil, langs...)
|
||||
data.Subject = i18n.Localize(data.Subject, nil, langs...)
|
||||
data.Greeting = i18n.Localize(data.Greeting, args, langs...)
|
||||
data.Text = html.UnescapeString(i18n.Localize(data.Text, args, langs...))
|
||||
if data.Href != "" {
|
||||
data.Href = i18n.Localize(data.Href, nil, langs...)
|
||||
}
|
||||
data.ButtonText = i18n.Localize(data.ButtonText, nil, langs...)
|
||||
func (data *TemplateData) Translate(translator *i18n.Translator, msgType string, args map[string]interface{}, langs ...string) {
|
||||
data.Title = translator.Localize(fmt.Sprintf("%s.%s", msgType, domain.MessageTitle), args, langs...)
|
||||
data.PreHeader = translator.Localize(fmt.Sprintf("%s.%s", msgType, domain.MessagePreHeader), args, langs...)
|
||||
data.Subject = translator.Localize(fmt.Sprintf("%s.%s", msgType, domain.MessageSubject), args, langs...)
|
||||
data.Greeting = translator.Localize(fmt.Sprintf("%s.%s", msgType, domain.MessageGreeting), args, langs...)
|
||||
data.Text = html.UnescapeString(translator.Localize(fmt.Sprintf("%s.%s", msgType, domain.MessageText), args, langs...))
|
||||
data.ButtonText = translator.Localize(fmt.Sprintf("%s.%s", msgType, domain.MessageButtonText), args, langs...)
|
||||
data.FooterText = translator.Localize(fmt.Sprintf("%s.%s", msgType, domain.MessageFooterText), args, langs...)
|
||||
}
|
||||
|
||||
func GetTemplateData(apiDomain, href string, text *iam_model.MessageTextView, policy *iam_model.LabelPolicyView) TemplateData {
|
||||
func GetTemplateData(translator *i18n.Translator, translateArgs map[string]interface{}, apiDomain, href, msgType, lang string, policy *iam_model.LabelPolicyView) TemplateData {
|
||||
templateData := TemplateData{
|
||||
Title: text.Title,
|
||||
PreHeader: text.PreHeader,
|
||||
Subject: text.Subject,
|
||||
Greeting: text.Greeting,
|
||||
Text: html.UnescapeString(text.Text),
|
||||
Href: href,
|
||||
ButtonText: text.ButtonText,
|
||||
FooterText: text.FooterText,
|
||||
PrimaryColor: defaultPrimaryColor,
|
||||
BackgroundColor: defaultBackgroundColor,
|
||||
FontColor: defaultFontColor,
|
||||
@ -71,6 +59,7 @@ func GetTemplateData(apiDomain, href string, text *iam_model.MessageTextView, po
|
||||
FontFamily: defaultFontFamily,
|
||||
IncludeFooter: false,
|
||||
}
|
||||
templateData.Translate(translator, msgType, translateArgs, lang)
|
||||
if policy.PrimaryColor != "" {
|
||||
templateData.PrimaryColor = policy.PrimaryColor
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"html"
|
||||
"strings"
|
||||
|
||||
"github.com/caos/zitadel/internal/config/systemdefaults"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/i18n"
|
||||
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||
"github.com/caos/zitadel/internal/notification/templates"
|
||||
view_model "github.com/caos/zitadel/internal/user/repository/view/model"
|
||||
@ -15,7 +16,7 @@ type DomainClaimedData struct {
|
||||
URL string
|
||||
}
|
||||
|
||||
func SendDomainClaimed(mailhtml string, text *iam_model.MessageTextView, user *view_model.NotifyUser, username string, systemDefaults systemdefaults.SystemDefaults, colors *iam_model.LabelPolicyView, apiDomain string) error {
|
||||
func SendDomainClaimed(mailhtml string, translator *i18n.Translator, 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
|
||||
@ -24,17 +25,13 @@ func SendDomainClaimed(mailhtml string, text *iam_model.MessageTextView, user *v
|
||||
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)
|
||||
text.Text = html.UnescapeString(text.Text)
|
||||
|
||||
emailCodeData := &DomainClaimedData{
|
||||
TemplateData: templates.GetTemplateData(apiDomain, url, text, colors),
|
||||
domainClaimedData := &DomainClaimedData{
|
||||
TemplateData: templates.GetTemplateData(translator, args, apiDomain, url, domain.DomainClaimedMessageType, user.PreferredLanguage, colors),
|
||||
URL: url,
|
||||
}
|
||||
template, err := templates.GetParsedTemplate(mailhtml, emailCodeData)
|
||||
template, err := templates.GetParsedTemplate(mailhtml, domainClaimedData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return generateEmail(user, text.Subject, template, systemDefaults.Notifications, true)
|
||||
return generateEmail(user, domainClaimedData.Subject, template, systemDefaults.Notifications, true)
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"html"
|
||||
|
||||
"github.com/caos/zitadel/internal/config/systemdefaults"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"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"
|
||||
@ -16,7 +16,7 @@ type EmailVerificationCodeData struct {
|
||||
URL string
|
||||
}
|
||||
|
||||
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 {
|
||||
func SendEmailVerificationCode(mailhtml string, translator *i18n.Translator, 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
|
||||
@ -29,12 +29,8 @@ func SendEmailVerificationCode(mailhtml string, text *iam_model.MessageTextView,
|
||||
var args = mapNotifyUserToArgs(user)
|
||||
args["Code"] = codeString
|
||||
|
||||
text.Greeting, err = templates.ParseTemplateText(text.Greeting, args)
|
||||
text.Text, err = templates.ParseTemplateText(text.Text, args)
|
||||
text.Text = html.UnescapeString(text.Text)
|
||||
|
||||
emailCodeData := &EmailVerificationCodeData{
|
||||
TemplateData: templates.GetTemplateData(apiDomain, url, text, colors),
|
||||
TemplateData: templates.GetTemplateData(translator, args, apiDomain, url, domain.VerifyEmailMessageType, user.PreferredLanguage, colors),
|
||||
URL: url,
|
||||
}
|
||||
|
||||
@ -42,5 +38,5 @@ func SendEmailVerificationCode(mailhtml string, text *iam_model.MessageTextView,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return generateEmail(user, text.Subject, template, systemDefaults.Notifications, true)
|
||||
return generateEmail(user, emailCodeData.Subject, template, systemDefaults.Notifications, true)
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"html"
|
||||
|
||||
"github.com/caos/zitadel/internal/config/systemdefaults"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"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"
|
||||
@ -22,7 +22,7 @@ type UrlData struct {
|
||||
PasswordSet bool
|
||||
}
|
||||
|
||||
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 {
|
||||
func SendUserInitCode(mailhtml string, translator *i18n.Translator, 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
|
||||
@ -34,17 +34,13 @@ func SendUserInitCode(mailhtml string, text *iam_model.MessageTextView, user *vi
|
||||
var args = mapNotifyUserToArgs(user)
|
||||
args["Code"] = codeString
|
||||
|
||||
text.Greeting, err = templates.ParseTemplateText(text.Greeting, args)
|
||||
text.Text, err = templates.ParseTemplateText(text.Text, args)
|
||||
text.Text = html.UnescapeString(text.Text)
|
||||
|
||||
emailCodeData := &InitCodeEmailData{
|
||||
TemplateData: templates.GetTemplateData(apiDomain, url, text, colors),
|
||||
initCodeData := &InitCodeEmailData{
|
||||
TemplateData: templates.GetTemplateData(translator, args, apiDomain, url, domain.InitCodeMessageType, user.PreferredLanguage, colors),
|
||||
URL: url,
|
||||
}
|
||||
template, err := templates.GetParsedTemplate(mailhtml, emailCodeData)
|
||||
template, err := templates.GetParsedTemplate(mailhtml, initCodeData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return generateEmail(user, text.Subject, template, systemDefaults.Notifications, true)
|
||||
return generateEmail(user, initCodeData.Subject, template, systemDefaults.Notifications, true)
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"html"
|
||||
|
||||
"github.com/caos/zitadel/internal/config/systemdefaults"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"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"
|
||||
@ -18,7 +18,7 @@ type PasswordCodeData struct {
|
||||
URL string
|
||||
}
|
||||
|
||||
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 {
|
||||
func SendPasswordCode(mailhtml string, translator *i18n.Translator, 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
|
||||
@ -30,19 +30,15 @@ func SendPasswordCode(mailhtml string, text *iam_model.MessageTextView, user *vi
|
||||
var args = mapNotifyUserToArgs(user)
|
||||
args["Code"] = codeString
|
||||
|
||||
text.Greeting, err = templates.ParseTemplateText(text.Greeting, args)
|
||||
text.Text, err = templates.ParseTemplateText(text.Text, args)
|
||||
text.Text = html.UnescapeString(text.Text)
|
||||
|
||||
emailCodeData := &PasswordCodeData{
|
||||
TemplateData: templates.GetTemplateData(apiDomain, url, text, colors),
|
||||
passwordResetData := &PasswordCodeData{
|
||||
TemplateData: templates.GetTemplateData(translator, args, apiDomain, url, domain.PasswordResetMessageType, user.PreferredLanguage, colors),
|
||||
FirstName: user.FirstName,
|
||||
LastName: user.LastName,
|
||||
URL: url,
|
||||
}
|
||||
template, err := templates.GetParsedTemplate(mailhtml, emailCodeData)
|
||||
template, err := templates.GetParsedTemplate(mailhtml, passwordResetData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return generateEmail(user, text.Subject, template, systemDefaults.Notifications, true)
|
||||
return generateEmail(user, passwordResetData.Subject, template, systemDefaults.Notifications, true)
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/caos/zitadel/internal/config/systemdefaults"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/i18n"
|
||||
"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,7 +16,7 @@ type PhoneVerificationCodeData struct {
|
||||
UserID string
|
||||
}
|
||||
|
||||
func SendPhoneVerificationCode(text *iam_model.MessageTextView, user *view_model.NotifyUser, code *es_model.PhoneCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm) error {
|
||||
func SendPhoneVerificationCode(translator *i18n.Translator, 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
|
||||
@ -21,10 +24,10 @@ func SendPhoneVerificationCode(text *iam_model.MessageTextView, user *view_model
|
||||
var args = mapNotifyUserToArgs(user)
|
||||
args["Code"] = codeString
|
||||
|
||||
text.Text, err = templates.ParseTemplateText(text.Text, args)
|
||||
text := translator.Localize(fmt.Sprintf("%s.%s", domain.VerifyPhoneMessageType, domain.MessageTitle), args, user.PreferredLanguage)
|
||||
|
||||
codeData := &PhoneVerificationCodeData{UserID: user.ID}
|
||||
template, err := templates.ParseTemplateText(text.Text, codeData)
|
||||
template, err := templates.ParseTemplateText(text, codeData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
14
migrations/cockroach/V1.55__custom_text.sql
Normal file
14
migrations/cockroach/V1.55__custom_text.sql
Normal file
@ -0,0 +1,14 @@
|
||||
CREATE TABLE notification.custom_texts (
|
||||
aggregate_id TEXT,
|
||||
|
||||
creation_date TIMESTAMPTZ,
|
||||
change_date TIMESTAMPTZ,
|
||||
sequence BIGINT,
|
||||
|
||||
template TEXT,
|
||||
language TEXT,
|
||||
key TEXT,
|
||||
text TEXT,
|
||||
|
||||
PRIMARY KEY (aggregate_id, template, key, language)
|
||||
);
|
@ -141,7 +141,18 @@ service AdminService {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Returns the default languages
|
||||
rpc GetSupportedLanguages(GetSupportedLanguagesRequest) returns (GetSupportedLanguagesResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/languages";
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "iam.read";
|
||||
};
|
||||
}
|
||||
|
||||
//Checks whether an organisation exists by the given parameters
|
||||
rpc IsOrgUnique(IsOrgUniqueRequest) returns (IsOrgUniqueResponse) {
|
||||
option (google.api.http) = {
|
||||
@ -1509,8 +1520,19 @@ service AdminService {
|
||||
};
|
||||
}
|
||||
|
||||
//Returns the custom text for initial message
|
||||
//Returns the default text for initial message (translation file)
|
||||
rpc GetDefaultInitMessageText(GetDefaultInitMessageTextRequest) returns (GetDefaultInitMessageTextResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/text/default/message/init/{language}";
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "iam.policy.read";
|
||||
};
|
||||
}
|
||||
|
||||
//Returns the custom text for initial message (overwritten in eventstore)
|
||||
rpc GetCustomInitMessageText(GetCustomInitMessageTextRequest) returns (GetCustomInitMessageTextResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/text/message/init/{language}";
|
||||
};
|
||||
@ -1535,8 +1557,19 @@ service AdminService {
|
||||
};
|
||||
}
|
||||
|
||||
//Returns the custom text for password reset message
|
||||
//Returns the default text for password reset message (translation file)
|
||||
rpc GetDefaultPasswordResetMessageText(GetDefaultPasswordResetMessageTextRequest) returns (GetDefaultPasswordResetMessageTextResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/text/deafult/message/passwordreset/{language}";
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "iam.policy.read";
|
||||
};
|
||||
}
|
||||
|
||||
//Returns the custom text for password reset message (overwritten in eventstore)
|
||||
rpc GetCustomPasswordResetMessageText(GetCustomPasswordResetMessageTextRequest) returns (GetCustomPasswordResetMessageTextResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/text/message/passwordreset/{language}";
|
||||
};
|
||||
@ -1562,8 +1595,19 @@ service AdminService {
|
||||
|
||||
}
|
||||
|
||||
//Returns the custom text for verify email message
|
||||
//Returns the default text for verify email message (translation files)
|
||||
rpc GetDefaultVerifyEmailMessageText(GetDefaultVerifyEmailMessageTextRequest) returns (GetDefaultVerifyEmailMessageTextResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/text/default/message/verifyemail/{language}";
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "iam.policy.read";
|
||||
};
|
||||
}
|
||||
|
||||
//Returns the custom text for verify email message (overwritten in eventstore)
|
||||
rpc GetCustomVerifyEmailMessageText(GetCustomVerifyEmailMessageTextRequest) returns (GetCustomVerifyEmailMessageTextResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/text/message/verifyemail/{language}";
|
||||
};
|
||||
@ -1588,8 +1632,19 @@ service AdminService {
|
||||
};
|
||||
}
|
||||
|
||||
//Returns the custom text for verify phone message
|
||||
//Returns the default text for verify phone message (translation file)
|
||||
rpc GetDefaultVerifyPhoneMessageText(GetDefaultVerifyPhoneMessageTextRequest) returns (GetDefaultVerifyPhoneMessageTextResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/text/default/message/verifyphone/{language}";
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "iam.policy.read";
|
||||
};
|
||||
}
|
||||
|
||||
//Returns the custom text for verify phone message
|
||||
rpc GetCustomVerifyPhoneMessageText(GetCustomVerifyPhoneMessageTextRequest) returns (GetCustomVerifyPhoneMessageTextResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/text/message/verifyphone/{language}";
|
||||
};
|
||||
@ -1597,7 +1652,6 @@ service AdminService {
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "iam.policy.read";
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
//Sets the default custom text for verify phone message
|
||||
@ -1615,8 +1669,19 @@ service AdminService {
|
||||
};
|
||||
}
|
||||
|
||||
//Returns the custom text for domain claimed message
|
||||
//Returns the default text for domain claimed message (translation file)
|
||||
rpc GetDefaultDomainClaimedMessageText(GetDefaultDomainClaimedMessageTextRequest) returns (GetDefaultDomainClaimedMessageTextResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/text/default/message/domainclaimed/{language}";
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "iam.policy.read";
|
||||
};
|
||||
}
|
||||
|
||||
//Returns the custom text for domain claimed message (overwritten in eventstore)
|
||||
rpc GetCustomDomainClaimedMessageText(GetCustomDomainClaimedMessageTextRequest) returns (GetCustomDomainClaimedMessageTextResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/text/message/domainclaimed/{language}";
|
||||
};
|
||||
@ -1980,6 +2045,13 @@ message HealthzRequest {}
|
||||
//This is an empty response
|
||||
message HealthzResponse {}
|
||||
|
||||
//This is an empty request
|
||||
message GetSupportedLanguagesRequest {}
|
||||
|
||||
message GetSupportedLanguagesResponse {
|
||||
repeated string languages = 1;
|
||||
}
|
||||
|
||||
// if name or domain is already in use, org is not unique
|
||||
message IsOrgUniqueRequest {
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
|
||||
@ -3050,6 +3122,14 @@ message GetDefaultInitMessageTextResponse {
|
||||
zitadel.text.v1.MessageCustomText custom_text = 1;
|
||||
}
|
||||
|
||||
message GetCustomInitMessageTextRequest {
|
||||
string language = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
}
|
||||
|
||||
message GetCustomInitMessageTextResponse {
|
||||
zitadel.text.v1.MessageCustomText custom_text = 1;
|
||||
}
|
||||
|
||||
message SetDefaultInitMessageTextRequest {
|
||||
string language = 1 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
@ -3078,6 +3158,14 @@ message GetDefaultPasswordResetMessageTextResponse {
|
||||
zitadel.text.v1.MessageCustomText custom_text = 1;
|
||||
}
|
||||
|
||||
message GetCustomPasswordResetMessageTextRequest {
|
||||
string language = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
}
|
||||
|
||||
message GetCustomPasswordResetMessageTextResponse {
|
||||
zitadel.text.v1.MessageCustomText custom_text = 1;
|
||||
}
|
||||
|
||||
message SetDefaultPasswordResetMessageTextRequest {
|
||||
string language = 1 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
@ -3106,6 +3194,14 @@ message GetDefaultVerifyEmailMessageTextResponse {
|
||||
zitadel.text.v1.MessageCustomText custom_text = 1;
|
||||
}
|
||||
|
||||
message GetCustomVerifyEmailMessageTextRequest {
|
||||
string language = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
}
|
||||
|
||||
message GetCustomVerifyEmailMessageTextResponse {
|
||||
zitadel.text.v1.MessageCustomText custom_text = 1;
|
||||
}
|
||||
|
||||
message SetDefaultVerifyEmailMessageTextRequest {
|
||||
string language = 1 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
@ -3134,6 +3230,14 @@ message GetDefaultVerifyPhoneMessageTextResponse {
|
||||
zitadel.text.v1.MessageCustomText custom_text = 1;
|
||||
}
|
||||
|
||||
message GetCustomVerifyPhoneMessageTextRequest {
|
||||
string language = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
}
|
||||
|
||||
message GetCustomVerifyPhoneMessageTextResponse {
|
||||
zitadel.text.v1.MessageCustomText custom_text = 1;
|
||||
}
|
||||
|
||||
message SetDefaultVerifyPhoneMessageTextRequest {
|
||||
string language = 1 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
@ -3162,6 +3266,14 @@ message GetDefaultDomainClaimedMessageTextResponse {
|
||||
zitadel.text.v1.MessageCustomText custom_text = 1;
|
||||
}
|
||||
|
||||
message GetCustomDomainClaimedMessageTextRequest {
|
||||
string language = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
}
|
||||
|
||||
message GetCustomDomainClaimedMessageTextResponse {
|
||||
zitadel.text.v1.MessageCustomText custom_text = 1;
|
||||
}
|
||||
|
||||
message SetDefaultDomainClaimedMessageTextRequest {
|
||||
string language = 1 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
|
@ -49,6 +49,17 @@ service AuthService {
|
||||
};
|
||||
}
|
||||
|
||||
// Returns the default languages
|
||||
rpc GetSupportedLanguages(GetSupportedLanguagesRequest) returns (GetSupportedLanguagesResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/languages";
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "authenticated";
|
||||
};
|
||||
}
|
||||
|
||||
// Returns my full blown user
|
||||
rpc GetMyUser(GetMyUserRequest) returns (GetMyUserResponse) {
|
||||
option (google.api.http) = {
|
||||
@ -504,6 +515,14 @@ message HealthzRequest {}
|
||||
//This is an empty response
|
||||
message HealthzResponse {}
|
||||
|
||||
//This is an empty request
|
||||
message GetSupportedLanguagesRequest {}
|
||||
|
||||
//This is an empty response
|
||||
message GetSupportedLanguagesResponse {
|
||||
repeated string languages = 1;
|
||||
}
|
||||
|
||||
//This is an empty request
|
||||
// the request parameters are read from the token-header
|
||||
message GetMyUserRequest {}
|
||||
|
@ -84,6 +84,17 @@ service ManagementService {
|
||||
};
|
||||
}
|
||||
|
||||
// Returns the default languages
|
||||
rpc GetSupportedLanguages(GetSupportedLanguagesRequest) returns (GetSupportedLanguagesResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/languages";
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "authenticated";
|
||||
};
|
||||
}
|
||||
|
||||
// Returns the requested full blown user (human or machine)
|
||||
rpc GetUserByID(GetUserByIDRequest) returns (GetUserByIDResponse) {
|
||||
option (google.api.http) = {
|
||||
@ -2568,6 +2579,13 @@ message GetIAMResponse {
|
||||
string iam_project_id = 2;
|
||||
}
|
||||
|
||||
//This is an empty request
|
||||
message GetSupportedLanguagesRequest {}
|
||||
|
||||
message GetSupportedLanguagesResponse {
|
||||
repeated string languages = 1;
|
||||
}
|
||||
|
||||
message GetUserByIDRequest {
|
||||
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user