feat: custom message text (#1801)

* feat: default custom message text

* feat: org custom message text

* feat: org custom message text

* feat: custom messages query side

* feat: default messages

* feat: message text user fields

* feat: check for inactive user

* feat: fix send password reset

* feat: fix custom org text

* feat: add variables to docs

* feat: custom text tests

* feat: fix notifications

* feat: add custom text feature

* feat: add custom text feature

* feat: feature in custom message texts

* feat: add custom text feature in frontend

* feat: merge main

* feat: feature tests

* feat: change phone message in setup

* fix: remove unused code, add event translation

* fix: merge main and fix problems

* fix: english translation file

* fix: migration versions

* fix: setup

* feat: fix pr requests

* feat: fix phone code message

* feat: migration

* feat: setup

* fix: remove unused tests

Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
Fabi 2021-06-10 13:49:10 +02:00 committed by GitHub
parent 67462eefe0
commit bdf3887f9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
107 changed files with 4930 additions and 2715 deletions

File diff suppressed because one or more lines are too long

View File

@ -141,7 +141,15 @@
<mat-slide-toggle color="primary" name="hasNumber" ngDefaultControl [(ngModel)]="features.customDomain"
[disabled]="(['iam.features.write'] | hasRole | async) == false">
</mat-slide-toggle>
</div>
</div>
<div class="row">
<span class="left-desc">{{'FEATURES.DATA.CUSTOMTEXT' | translate}}</span>
<span class="fill-space"></span>
<mat-slide-toggle color="primary" name="hasNumber" ngDefaultControl [(ngModel)]="features.customText"
[disabled]="(['iam.features.write'] | hasRole | async) == false">
</mat-slide-toggle>
</div>
</div>
<div class="btn-container">

View File

@ -161,6 +161,7 @@ export class FeaturesComponent implements OnDestroy {
req.setLabelPolicyPrivateLabel(this.features.labelPolicyPrivateLabel);
req.setLabelPolicyWatermark(this.features.labelPolicyWatermark);
req.setCustomDomain(this.features.customDomain);
req.setCustomText(this.features.customText);
this.adminService.setOrgFeatures(req).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
@ -181,6 +182,7 @@ export class FeaturesComponent implements OnDestroy {
dreq.setLabelPolicyPrivateLabel(this.features.labelPolicyPrivateLabel);
dreq.setLabelPolicyWatermark(this.features.labelPolicyWatermark);
dreq.setCustomDomain(this.features.customDomain);
dreq.setCustomText(this.features.customText);
this.adminService.setDefaultFeatures(dreq).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);

View File

@ -611,12 +611,13 @@
"LOGINPOLICYPASSWORDRESET": "Login Richtlinie: Passwort vergessen Link nicht anzeigen - benutzerdefiniert",
"LOGINPOLICYREGISTRATION": "Login Richtlinie: Registration erlauben - benutzerdefiniert",
"LOGINPOLICYIDP": "Login Richtlinie: Identity Providers - benutzerdefiniert",
"LOGINPOLICYFACTORS": "Login Richtlinie: Mltifaktoren - benutzerdefiniert",
"LOGINPOLICYFACTORS": "Login Richtlinie: Multifaktoren - benutzerdefiniert",
"LOGINPOLICYPASSWORDLESS": "Login Richtlinie: Passwortlose Authentifizierung - benutzerdefiniert",
"LOGINPOLICYCOMPLEXITYPOLICY": "Passwortkomplexitäts Richtlinie - benutzerdefiniert",
"LABELPOLICYPRIVATELABEL": "Label Richtlinie - benutzerdefiniert",
"LABELPOLICYWATERMARK": "Label Richtlinie - Wasserzeichen",
"CUSTOMDOMAIN": "Domänen Verifikation - verfügbar"
"CUSTOMDOMAIN": "Domänen Verifikation - verfügbar",
"CUSTOMTEXT": "Benutzerdefinierte Texte"
},
"TIERSTATES": {
"0": "Aktiv",

View File

@ -616,7 +616,8 @@
"LOGINPOLICYCOMPLEXITYPOLICY": "Password Complexity Policy - custom",
"LABELPOLICYPRIVATELABEL": "Label Richtlinie - benutzerdefiniert",
"LABELPOLICYWATERMARK": "Label Richtlinie - Wasserzeichen",
"CUSTOMDOMAIN": "Domain Verification - available"
"CUSTOMDOMAIN": "Domain Verification - available",
"CUSTOMTEXT": "Custom texts"
},
"TIERSTATES": {
"0": "Active",

View File

@ -528,6 +528,121 @@ it impacts all organisations without a customised policy
### GetDefaultInitMessageText
> **rpc** GetDefaultInitMessageText([GetDefaultInitMessageTextRequest](#getdefaultinitmessagetextrequest))
[GetDefaultInitMessageTextResponse](#getdefaultinitmessagetextresponse)
Returns the custom text for initial message
### SetDefaultInitMessageText
> **rpc** SetDefaultInitMessageText([SetDefaultInitMessageTextRequest](#setdefaultinitmessagetextrequest))
[SetDefaultInitMessageTextResponse](#setdefaultinitmessagetextresponse)
Sets the default custom text for initial message
it impacts all organisations without customized initial message text
The Following Variables can be used:
{{.Code}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
### GetDefaultPasswordResetMessageText
> **rpc** GetDefaultPasswordResetMessageText([GetDefaultPasswordResetMessageTextRequest](#getdefaultpasswordresetmessagetextrequest))
[GetDefaultPasswordResetMessageTextResponse](#getdefaultpasswordresetmessagetextresponse)
Returns the custom text for password reset message
### SetDefaultPasswordResetMessageText
> **rpc** SetDefaultPasswordResetMessageText([SetDefaultPasswordResetMessageTextRequest](#setdefaultpasswordresetmessagetextrequest))
[SetDefaultPasswordResetMessageTextResponse](#setdefaultpasswordresetmessagetextresponse)
Sets the default custom text for password reset message
it impacts all organisations without customized password reset message text
The Following Variables can be used:
{{.Code}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
### GetDefaultVerifyEmailMessageText
> **rpc** GetDefaultVerifyEmailMessageText([GetDefaultVerifyEmailMessageTextRequest](#getdefaultverifyemailmessagetextrequest))
[GetDefaultVerifyEmailMessageTextResponse](#getdefaultverifyemailmessagetextresponse)
Returns the custom text for verify email message
### SetDefaultVerifyEmailMessageText
> **rpc** SetDefaultVerifyEmailMessageText([SetDefaultVerifyEmailMessageTextRequest](#setdefaultverifyemailmessagetextrequest))
[SetDefaultVerifyEmailMessageTextResponse](#setdefaultverifyemailmessagetextresponse)
Sets the default custom text for verify email message
it impacts all organisations without customized verify email message text
The Following Variables can be used:
{{.Code}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
### GetDefaultVerifyPhoneMessageText
> **rpc** GetDefaultVerifyPhoneMessageText([GetDefaultVerifyPhoneMessageTextRequest](#getdefaultverifyphonemessagetextrequest))
[GetDefaultVerifyPhoneMessageTextResponse](#getdefaultverifyphonemessagetextresponse)
Returns the custom text for verify phone message
### SetDefaultVerifyPhoneMessageText
> **rpc** SetDefaultVerifyPhoneMessageText([SetDefaultVerifyPhoneMessageTextRequest](#setdefaultverifyphonemessagetextrequest))
[SetDefaultVerifyPhoneMessageTextResponse](#setdefaultverifyphonemessagetextresponse)
Sets the default custom text for verify phone message
it impacts all organisations without customized verify phone message text
The Following Variables can be used:
{{.Code}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
### GetDefaultDomainClaimedMessageText
> **rpc** GetDefaultDomainClaimedMessageText([GetDefaultDomainClaimedMessageTextRequest](#getdefaultdomainclaimedmessagetextrequest))
[GetDefaultDomainClaimedMessageTextResponse](#getdefaultdomainclaimedmessagetextresponse)
Returns the custom text for domain claimed message
### SetDefaultDomainClaimedMessageText
> **rpc** SetDefaultDomainClaimedMessageText([SetDefaultDomainClaimedMessageTextRequest](#setdefaultdomainclaimedmessagetextrequest))
[SetDefaultDomainClaimedMessageTextResponse](#setdefaultdomainclaimedmessagetextresponse)
Sets the default custom text for domain claimed phone message
it impacts all organisations without customized verify phone message text
The Following Variables can be used:
{{.Domain}} {{.TempUsername}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
### ListIAMMemberRoles
> **rpc** ListIAMMemberRoles([ListIAMMemberRolesRequest](#listiammemberrolesrequest))
@ -877,6 +992,28 @@ This is an empty response
### GetDefaultDomainClaimedMessageTextRequest
This is an empty request
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| language | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
### GetDefaultDomainClaimedMessageTextResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| custom_text | zitadel.text.v1.MessageCustomText | - | |
### GetDefaultFeaturesRequest
@ -894,6 +1031,94 @@ This is an empty response
### GetDefaultInitMessageTextRequest
This is an empty request
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| language | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
### GetDefaultInitMessageTextResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| custom_text | zitadel.text.v1.MessageCustomText | - | |
### GetDefaultPasswordResetMessageTextRequest
This is an empty request
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| language | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
### GetDefaultPasswordResetMessageTextResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| custom_text | zitadel.text.v1.MessageCustomText | - | |
### GetDefaultVerifyEmailMessageTextRequest
This is an empty request
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| language | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
### GetDefaultVerifyEmailMessageTextResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| custom_text | zitadel.text.v1.MessageCustomText | - | |
### GetDefaultVerifyPhoneMessageTextRequest
This is an empty request
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| language | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
### GetDefaultVerifyPhoneMessageTextResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| custom_text | zitadel.text.v1.MessageCustomText | - | |
### GetIDPByIDRequest
@ -1593,6 +1818,35 @@ This is an empty request
### SetDefaultDomainClaimedMessageTextRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| language | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| title | string | - | string.max_len: 200<br /> |
| pre_header | string | - | string.max_len: 200<br /> |
| subject | string | - | string.max_len: 200<br /> |
| greeting | string | - | string.max_len: 200<br /> |
| text | string | - | string.max_len: 800<br /> |
| button_text | string | - | string.max_len: 200<br /> |
| footer_text | string | - | string.max_len: 200<br /> |
### SetDefaultDomainClaimedMessageTextResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### SetDefaultFeaturesRequest
@ -1613,6 +1867,7 @@ This is an empty request
| login_policy_password_reset | bool | - | |
| label_policy_private_label | bool | - | |
| label_policy_watermark | bool | - | |
| custom_text | bool | - | |
@ -1628,6 +1883,122 @@ This is an empty request
### SetDefaultInitMessageTextRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| language | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| title | string | - | string.max_len: 200<br /> |
| pre_header | string | - | string.max_len: 200<br /> |
| subject | string | - | string.max_len: 200<br /> |
| greeting | string | - | string.max_len: 200<br /> |
| text | string | - | string.max_len: 1000<br /> |
| button_text | string | - | string.max_len: 200<br /> |
| footer_text | string | - | string.max_len: 200<br /> |
### SetDefaultInitMessageTextResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### SetDefaultPasswordResetMessageTextRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| language | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| title | string | - | string.max_len: 200<br /> |
| pre_header | string | - | string.max_len: 200<br /> |
| subject | string | - | string.max_len: 200<br /> |
| greeting | string | - | string.max_len: 200<br /> |
| text | string | - | string.max_len: 800<br /> |
| button_text | string | - | string.max_len: 200<br /> |
| footer_text | string | - | string.max_len: 200<br /> |
### SetDefaultPasswordResetMessageTextResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### SetDefaultVerifyEmailMessageTextRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| language | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| title | string | - | string.max_len: 200<br /> |
| pre_header | string | - | string.max_len: 200<br /> |
| subject | string | - | string.max_len: 200<br /> |
| greeting | string | - | string.max_len: 200<br /> |
| text | string | - | string.max_len: 800<br /> |
| button_text | string | - | string.max_len: 200<br /> |
| footer_text | string | - | string.max_len: 200<br /> |
### SetDefaultVerifyEmailMessageTextResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### SetDefaultVerifyPhoneMessageTextRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| language | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| title | string | - | string.max_len: 200<br /> |
| pre_header | string | - | string.max_len: 200<br /> |
| subject | string | - | string.max_len: 200<br /> |
| greeting | string | - | string.max_len: 200<br /> |
| text | string | - | string.max_len: 800<br /> |
| button_text | string | - | string.max_len: 200<br /> |
| footer_text | string | - | string.max_len: 200<br /> |
### SetDefaultVerifyPhoneMessageTextResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### SetOrgFeaturesRequest
@ -1651,6 +2022,7 @@ This is an empty request
| login_policy_password_reset | bool | - | |
| label_policy_private_label | bool | - | |
| label_policy_watermark | bool | - | |
| custom_text | bool | - | |

View File

@ -1712,6 +1712,176 @@ The default policy of the IAM will trigger after
### GetCustomInitMessageText
> **rpc** GetCustomInitMessageText([GetCustomInitMessageTextRequest](#getcustominitmessagetextrequest))
[GetCustomInitMessageTextResponse](#getcustominitmessagetextresponse)
Returns the custom text for initial message
### SetCustomInitMessageText
> **rpc** SetCustomInitMessageText([SetCustomInitMessageTextRequest](#setcustominitmessagetextrequest))
[SetCustomInitMessageTextResponse](#setcustominitmessagetextresponse)
Sets the default custom text for initial message
it impacts all organisations without customized initial message text
The Following Variables can be used:
{{.Code}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
### ResetCustomInitMessageTextToDefault
> **rpc** ResetCustomInitMessageTextToDefault([ResetCustomInitMessageTextToDefaultRequest](#resetcustominitmessagetexttodefaultrequest))
[ResetCustomInitMessageTextToDefaultResponse](#resetcustominitmessagetexttodefaultresponse)
Removes the custom init message text of the organisation
The default text of the IAM will trigger after
### GetCustomPasswordResetMessageText
> **rpc** GetCustomPasswordResetMessageText([GetCustomPasswordResetMessageTextRequest](#getcustompasswordresetmessagetextrequest))
[GetCustomPasswordResetMessageTextResponse](#getcustompasswordresetmessagetextresponse)
Returns the custom text for password reset message
### SetCustomPasswordResetMessageText
> **rpc** SetCustomPasswordResetMessageText([SetCustomPasswordResetMessageTextRequest](#setcustompasswordresetmessagetextrequest))
[SetCustomPasswordResetMessageTextResponse](#setcustompasswordresetmessagetextresponse)
Sets the default custom text for password reset message
it impacts all organisations without customized password reset message text
The Following Variables can be used:
{{.Code}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
### ResetCustomPasswordResetMessageTextToDefault
> **rpc** ResetCustomPasswordResetMessageTextToDefault([ResetCustomPasswordResetMessageTextToDefaultRequest](#resetcustompasswordresetmessagetexttodefaultrequest))
[ResetCustomPasswordResetMessageTextToDefaultResponse](#resetcustompasswordresetmessagetexttodefaultresponse)
Removes the custom init message text of the organisation
The default text of the IAM will trigger after
### GetCustomVerifyEmailMessageText
> **rpc** GetCustomVerifyEmailMessageText([GetCustomVerifyEmailMessageTextRequest](#getcustomverifyemailmessagetextrequest))
[GetCustomVerifyEmailMessageTextResponse](#getcustomverifyemailmessagetextresponse)
Returns the custom text for verify email message
### SetCustomVerifyEmailMessageText
> **rpc** SetCustomVerifyEmailMessageText([SetCustomVerifyEmailMessageTextRequest](#setcustomverifyemailmessagetextrequest))
[SetCustomVerifyEmailMessageTextResponse](#setcustomverifyemailmessagetextresponse)
Sets the default custom text for verify email message
it impacts all organisations without customized verify email message text
The Following Variables can be used:
{{.Code}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
### ResetCustomVerifyEmailMessageTextToDefault
> **rpc** ResetCustomVerifyEmailMessageTextToDefault([ResetCustomVerifyEmailMessageTextToDefaultRequest](#resetcustomverifyemailmessagetexttodefaultrequest))
[ResetCustomVerifyEmailMessageTextToDefaultResponse](#resetcustomverifyemailmessagetexttodefaultresponse)
Removes the custom init message text of the organisation
The default text of the IAM will trigger after
### GetCustomVerifyPhoneMessageText
> **rpc** GetCustomVerifyPhoneMessageText([GetCustomVerifyPhoneMessageTextRequest](#getcustomverifyphonemessagetextrequest))
[GetCustomVerifyPhoneMessageTextResponse](#getcustomverifyphonemessagetextresponse)
Returns the custom text for verify email message
### SetCustomVerifyPhoneMessageText
> **rpc** SetCustomVerifyPhoneMessageText([SetCustomVerifyPhoneMessageTextRequest](#setcustomverifyphonemessagetextrequest))
[SetCustomVerifyPhoneMessageTextResponse](#setcustomverifyphonemessagetextresponse)
Sets the default custom text for verify email message
it impacts all organisations without customized verify email message text
The Following Variables can be used:
{{.Code}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
### ResetCustomVerifyPhoneMessageTextToDefault
> **rpc** ResetCustomVerifyPhoneMessageTextToDefault([ResetCustomVerifyPhoneMessageTextToDefaultRequest](#resetcustomverifyphonemessagetexttodefaultrequest))
[ResetCustomVerifyPhoneMessageTextToDefaultResponse](#resetcustomverifyphonemessagetexttodefaultresponse)
Removes the custom init message text of the organisation
The default text of the IAM will trigger after
### GetCustomDomainClaimedMessageText
> **rpc** GetCustomDomainClaimedMessageText([GetCustomDomainClaimedMessageTextRequest](#getcustomdomainclaimedmessagetextrequest))
[GetCustomDomainClaimedMessageTextResponse](#getcustomdomainclaimedmessagetextresponse)
Returns the custom text for domain claimed message
### SetCustomDomainClaimedMessageCustomText
> **rpc** SetCustomDomainClaimedMessageCustomText([SetCustomDomainClaimedMessageTextRequest](#setcustomdomainclaimedmessagetextrequest))
[SetCustomDomainClaimedMessageTextResponse](#setcustomdomainclaimedmessagetextresponse)
Sets the default custom text for domain claimed message
it impacts all organisations without customized domain claimed message text
The Following Variables can be used:
{{.Domain}} {{.TempUsername}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
### ResetCustomDomainClaimedMessageTextToDefault
> **rpc** ResetCustomDomainClaimedMessageTextToDefault([ResetCustomDomainClaimedMessageTextToDefaultRequest](#resetcustomdomainclaimedmessagetexttodefaultrequest))
[ResetCustomDomainClaimedMessageTextToDefaultResponse](#resetcustomdomainclaimedmessagetexttodefaultresponse)
Removes the custom init message text of the organisation
The default text of the IAM will trigger after
### GetOrgIDPByID
> **rpc** GetOrgIDPByID([GetOrgIDPByIDRequest](#getorgidpbyidrequest))
@ -2758,6 +2928,116 @@ This is an empty request
### GetCustomDomainClaimedMessageTextRequest
This is an empty request
| 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
This is an empty request
| 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 | - | |
### GetCustomPasswordResetMessageTextRequest
This is an empty request
| 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
This is an empty request
| 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
This is an empty request
| 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 | - | |
### GetDefaultLabelPolicyRequest
This is an empty request
@ -4972,6 +5252,116 @@ This is an empty response
### ResetCustomDomainClaimedMessageTextToDefaultRequest
This is an empty request
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| language | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
### ResetCustomDomainClaimedMessageTextToDefaultResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### ResetCustomInitMessageTextToDefaultRequest
This is an empty request
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| language | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
### ResetCustomInitMessageTextToDefaultResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### ResetCustomPasswordResetMessageTextToDefaultRequest
This is an empty request
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| language | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
### ResetCustomPasswordResetMessageTextToDefaultResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### ResetCustomVerifyEmailMessageTextToDefaultRequest
This is an empty request
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| language | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
### ResetCustomVerifyEmailMessageTextToDefaultResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### ResetCustomVerifyPhoneMessageTextToDefaultRequest
This is an empty request
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| language | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
### ResetCustomVerifyPhoneMessageTextToDefaultResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### ResetLabelPolicyToDefaultRequest
This is an empty request
@ -5080,6 +5470,151 @@ This is an empty request
### SetCustomDomainClaimedMessageTextRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| language | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| title | string | - | string.max_len: 200<br /> |
| pre_header | string | - | string.max_len: 200<br /> |
| subject | string | - | string.max_len: 200<br /> |
| greeting | string | - | string.max_len: 200<br /> |
| text | string | - | string.max_len: 800<br /> |
| button_text | string | - | string.max_len: 200<br /> |
| footer_text | string | - | string.max_len: 200<br /> |
### SetCustomDomainClaimedMessageTextResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### SetCustomInitMessageTextRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| language | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| title | string | - | string.max_len: 200<br /> |
| pre_header | string | - | string.max_len: 200<br /> |
| subject | string | - | string.max_len: 200<br /> |
| greeting | string | - | string.max_len: 200<br /> |
| text | string | - | string.max_len: 800<br /> |
| button_text | string | - | string.max_len: 200<br /> |
| footer_text | string | - | string.max_len: 200<br /> |
### SetCustomInitMessageTextResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### SetCustomPasswordResetMessageTextRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| language | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| title | string | - | string.max_len: 200<br /> |
| pre_header | string | - | string.max_len: 200<br /> |
| subject | string | - | string.max_len: 200<br /> |
| greeting | string | - | string.max_len: 200<br /> |
| text | string | - | string.max_len: 800<br /> |
| button_text | string | - | string.max_len: 200<br /> |
| footer_text | string | - | string.max_len: 200<br /> |
### SetCustomPasswordResetMessageTextResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### SetCustomVerifyEmailMessageTextRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| language | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| title | string | - | string.max_len: 200<br /> |
| pre_header | string | - | string.max_len: 200<br /> |
| subject | string | - | string.max_len: 200<br /> |
| greeting | string | - | string.max_len: 200<br /> |
| text | string | - | string.max_len: 800<br /> |
| button_text | string | - | string.max_len: 200<br /> |
| footer_text | string | - | string.max_len: 200<br /> |
### SetCustomVerifyEmailMessageTextResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### SetCustomVerifyPhoneMessageTextRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| language | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
| title | string | - | string.max_len: 200<br /> |
| pre_header | string | - | string.max_len: 200<br /> |
| subject | string | - | string.max_len: 200<br /> |
| greeting | string | - | string.max_len: 200<br /> |
| text | string | - | string.max_len: 800<br /> |
| button_text | string | - | string.max_len: 200<br /> |
| footer_text | string | - | string.max_len: 200<br /> |
### SetCustomVerifyPhoneMessageTextResponse
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| details | zitadel.v1.ObjectDetails | - | |
### SetHumanInitialPasswordRequest

View File

@ -2,16 +2,18 @@ package eventstore
import (
"context"
"strings"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore/v1"
"github.com/caos/zitadel/internal/eventstore/v1/models"
iam_view "github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/user/repository/view/model"
"strings"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/logging"
admin_view "github.com/caos/zitadel/internal/admin/repository/eventsourcing/view"
"github.com/caos/zitadel/internal/config/systemdefaults"
iam_model "github.com/caos/zitadel/internal/iam/model"
@ -347,21 +349,21 @@ func (repo *IAMRepository) SearchIAMMembersx(ctx context.Context, request *iam_m
return result, nil
}
func (repo *IAMRepository) GetDefaultMailTexts(ctx context.Context) (*iam_model.MailTextsView, error) {
text, err := repo.View.MailTexts(repo.SystemDefaults.IamID)
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
}
return iam_es_model.MailTextsViewToModel(text, true), err
return iam_es_model.MessageTextsViewToModel(text, true), err
}
func (repo *IAMRepository) GetDefaultMailText(ctx context.Context, textType string, language string) (*iam_model.MailTextView, error) {
text, err := repo.View.MailTextByIDs(repo.SystemDefaults.IamID, textType, language)
func (repo *IAMRepository) 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.MailTextViewToModel(text), err
return iam_es_model.MessageTextViewToModel(text), err
}
func (repo *IAMRepository) getIAMEvents(ctx context.Context, sequence uint64) ([]*models.Event, error) {

View File

@ -62,8 +62,8 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
defaults),
newMailTemplate(
handler{view, bulkLimit, configs.cycleDuration("MailTemplate"), errorCount, es}),
newMailText(
handler{view, bulkLimit, configs.cycleDuration("MailText"), errorCount, es}),
newMessageText(
handler{view, bulkLimit, configs.cycleDuration("MessageText"), errorCount, es}),
newFeatures(
handler{view, bulkLimit, configs.cycleDuration("Features"), errorCount, es}),
}

View File

@ -1,108 +0,0 @@
package handler
import (
"github.com/caos/logging"
"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"
"github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
)
type MailText struct {
handler
subscription *v1.Subscription
}
func newMailText(handler handler) *MailText {
h := &MailText{
handler: handler,
}
h.subscribe()
return h
}
func (m *MailText) subscribe() {
m.subscription = m.es.Subscribe(m.AggregateTypes()...)
go func() {
for event := range m.subscription.Events {
query.ReduceEvent(m, event)
}
}()
}
const (
mailTextTable = "adminapi.mail_texts"
)
func (m *MailText) ViewModel() string {
return mailTextTable
}
func (_ *MailText) AggregateTypes() []es_models.AggregateType {
return []es_models.AggregateType{iam_es_model.IAMAggregate}
}
func (p *MailText) CurrentSequence() (uint64, error) {
sequence, err := p.view.GetLatestMailTextSequence()
if err != nil {
return 0, err
}
return sequence.CurrentSequence, nil
}
func (m *MailText) EventQuery() (*es_models.SearchQuery, error) {
sequence, err := m.view.GetLatestMailTextSequence()
if err != nil {
return nil, err
}
return es_models.NewSearchQuery().
AggregateTypeFilter(m.AggregateTypes()...).
LatestSequenceFilter(sequence.CurrentSequence), nil
}
func (m *MailText) Reduce(event *es_models.Event) (err error) {
switch event.AggregateType {
case model.IAMAggregate:
err = m.processMailText(event)
}
return err
}
func (m *MailText) processMailText(event *es_models.Event) (err error) {
mailText := new(iam_model.MailTextView)
switch event.Type {
case model.MailTextAdded:
err = mailText.AppendEvent(event)
case model.MailTextChanged:
err = mailText.SetData(event)
if err != nil {
return err
}
mailText, err = m.view.MailTextByIDs(event.AggregateID, mailText.MailTextType, mailText.Language)
if err != nil {
return err
}
err = mailText.AppendEvent(event)
default:
return m.view.ProcessedMailTextSequence(event)
}
if err != nil {
return err
}
return m.view.PutMailText(mailText, event)
}
func (m *MailText) OnError(event *es_models.Event, err error) error {
logging.LogWithFields("HANDL-5jk84", "id", event.AggregateID).WithError(err).Warn("something went wrong in label mailText handler")
return spooler.HandleError(event, err, m.view.GetLatestMailTextFailedEvent, m.view.ProcessedMailTextFailedEvent, m.view.ProcessedMailTextSequence, m.errorCountUntilSkip)
}
func (o *MailText) OnSuccess() error {
return spooler.HandleSuccess(o.view.UpdateMailTextSpoolerRunTimestamp)
}

View File

@ -0,0 +1,116 @@
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"
"github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
)
type MessageText struct {
handler
subscription *v1.Subscription
}
func newMessageText(handler handler) *MessageText {
h := &MessageText{
handler: handler,
}
h.subscribe()
return h
}
func (m *MessageText) subscribe() {
m.subscription = m.es.Subscribe(m.AggregateTypes()...)
go func() {
for event := range m.subscription.Events {
query.ReduceEvent(m, event)
}
}()
}
const (
mailTextTable = "adminapi.message_texts"
)
func (m *MessageText) ViewModel() string {
return mailTextTable
}
func (_ *MessageText) AggregateTypes() []es_models.AggregateType {
return []es_models.AggregateType{iam_es_model.IAMAggregate}
}
func (p *MessageText) CurrentSequence() (uint64, error) {
sequence, err := p.view.GetLatestMessageTextSequence()
if err != nil {
return 0, err
}
return sequence.CurrentSequence, nil
}
func (m *MessageText) EventQuery() (*es_models.SearchQuery, error) {
sequence, err := m.view.GetLatestMessageTextSequence()
if err != nil {
return nil, err
}
return es_models.NewSearchQuery().
AggregateTypeFilter(m.AggregateTypes()...).
LatestSequenceFilter(sequence.CurrentSequence), nil
}
func (m *MessageText) Reduce(event *es_models.Event) (err error) {
switch event.AggregateType {
case model.IAMAggregate:
err = m.processMessageText(event)
}
return err
}
func (m *MessageText) processMessageText(event *es_models.Event) (err error) {
message := new(iam_model.MessageTextView)
switch event.Type {
case model.CustomTextSet,
model.CustomTextRemoved:
text := new(iam_model.CustomText)
err = text.SetData(event)
if err != nil {
return err
}
message, err = m.view.MessageTextByIDs(event.AggregateID, text.Template, text.Language.String())
if err != nil && !caos_errs.IsNotFound(err) {
return err
}
if caos_errs.IsNotFound(err) {
err = nil
message = new(iam_model.MessageTextView)
message.Language = text.Language.String()
message.MessageTextType = text.Template
message.CreationDate = event.CreationDate
}
err = message.AppendEvent(event)
default:
return m.view.ProcessedMessageTextSequence(event)
}
if err != nil {
return err
}
return m.view.PutMessageText(message, event)
}
func (m *MessageText) OnError(event *es_models.Event, err error) error {
logging.LogWithFields("HANDL-5jk84", "id", event.AggregateID).WithError(err).Warn("something went wrong in label mailText handler")
return spooler.HandleError(event, err, m.view.GetLatestMessageTextFailedEvent, m.view.ProcessedMessageTextFailedEvent, m.view.ProcessedMessageTextSequence, m.errorCountUntilSkip)
}
func (o *MessageText) OnSuccess() error {
return spooler.HandleSuccess(o.view.UpdateMessageTextSpoolerRunTimestamp)
}

View File

@ -1,48 +0,0 @@
package view
import (
"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 (
mailTextTable = "adminapi.mail_texts"
)
func (v *View) MailTexts(aggregateID string) ([]*model.MailTextView, error) {
return view.GetMailTexts(v.Db, mailTextTable, aggregateID)
}
func (v *View) MailTextByIDs(aggregateID string, textType string, language string) (*model.MailTextView, error) {
return view.GetMailTextByIDs(v.Db, mailTextTable, aggregateID, textType, language)
}
func (v *View) PutMailText(template *model.MailTextView, event *models.Event) error {
err := view.PutMailText(v.Db, mailTextTable, template)
if err != nil {
return err
}
return v.ProcessedMailTextSequence(event)
}
func (v *View) GetLatestMailTextSequence() (*global_view.CurrentSequence, error) {
return v.latestSequence(mailTextTable)
}
func (v *View) ProcessedMailTextSequence(event *models.Event) error {
return v.saveCurrentSequence(mailTextTable, event)
}
func (v *View) UpdateMailTextSpoolerRunTimestamp() error {
return v.updateSpoolerRunSequence(mailTextTable)
}
func (v *View) GetLatestMailTextFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
return v.latestFailedEvent(mailTextTable, sequence)
}
func (v *View) ProcessedMailTextFailedEvent(failedEvent *global_view.FailedEvent) error {
return v.saveFailedEvent(failedEvent)
}

View File

@ -0,0 +1,48 @@
package view
import (
"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 (
messageTextTable = "adminapi.message_texts"
)
func (v *View) MessageTexts(aggregateID string) ([]*model.MessageTextView, error) {
return view.GetMessageTexts(v.Db, messageTextTable, aggregateID)
}
func (v *View) MessageTextByIDs(aggregateID, textType, lang string) (*model.MessageTextView, error) {
return view.GetMessageTextByIDs(v.Db, messageTextTable, aggregateID, textType, lang)
}
func (v *View) PutMessageText(template *model.MessageTextView, event *models.Event) error {
err := view.PutMessageText(v.Db, messageTextTable, template)
if err != nil {
return err
}
return v.ProcessedMessageTextSequence(event)
}
func (v *View) GetLatestMessageTextSequence() (*global_view.CurrentSequence, error) {
return v.latestSequence(messageTextTable)
}
func (v *View) ProcessedMessageTextSequence(event *models.Event) error {
return v.saveCurrentSequence(messageTextTable, event)
}
func (v *View) UpdateMessageTextSpoolerRunTimestamp() error {
return v.updateSpoolerRunSequence(messageTextTable)
}
func (v *View) GetLatestMessageTextFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
return v.latestFailedEvent(messageTextTable, sequence)
}
func (v *View) ProcessedMessageTextFailedEvent(failedEvent *global_view.FailedEvent) error {
return v.saveFailedEvent(failedEvent)
}

View File

@ -28,8 +28,8 @@ type IAMRepository interface {
GetDefaultMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error)
GetDefaultMailTexts(ctx context.Context) (*iam_model.MailTextsView, error)
GetDefaultMailText(ctx context.Context, textType string, language string) (*iam_model.MailTextView, error)
GetDefaultMessageTexts(ctx context.Context) (*iam_model.MessageTextsView, error)
GetDefaultMessageText(ctx context.Context, textType string, language string) (*iam_model.MessageTextView, error)
GetDefaultPasswordComplexityPolicy(ctx context.Context) (*iam_model.PasswordComplexityPolicyView, error)

View File

@ -0,0 +1,130 @@
package admin
import (
"context"
"github.com/caos/zitadel/internal/api/grpc/object"
text_grpc "github.com/caos/zitadel/internal/api/grpc/text"
"github.com/caos/zitadel/internal/domain"
admin_pb "github.com/caos/zitadel/pkg/grpc/admin"
)
func (s *Server) GetDefaultInitMessageText(ctx context.Context, req *admin_pb.GetDefaultInitMessageTextRequest) (*admin_pb.GetDefaultInitMessageTextResponse, error) {
msg, err := s.iam.GetDefaultMessageText(ctx, domain.InitCodeMessageType, req.Language)
if err != nil {
return nil, err
}
return &admin_pb.GetDefaultInitMessageTextResponse{
CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
}, nil
}
func (s *Server) SetDefaultInitMessageText(ctx context.Context, req *admin_pb.SetDefaultInitMessageTextRequest) (*admin_pb.SetDefaultInitMessageTextResponse, error) {
result, err := s.command.SetDefaultMessageText(ctx, SetInitCustomTextToDomain(req))
if err != nil {
return nil, err
}
return &admin_pb.SetDefaultInitMessageTextResponse{
Details: object.ChangeToDetailsPb(
result.Sequence,
result.EventDate,
result.ResourceOwner,
),
}, nil
}
func (s *Server) GetDefaultPasswordResetMessageText(ctx context.Context, req *admin_pb.GetDefaultPasswordResetMessageTextRequest) (*admin_pb.GetDefaultPasswordResetMessageTextResponse, error) {
msg, err := s.iam.GetDefaultMessageText(ctx, domain.PasswordResetMessageType, req.Language)
if err != nil {
return nil, err
}
return &admin_pb.GetDefaultPasswordResetMessageTextResponse{
CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
}, nil
}
func (s *Server) SetDefaultPasswordResetMessageText(ctx context.Context, req *admin_pb.SetDefaultPasswordResetMessageTextRequest) (*admin_pb.SetDefaultPasswordResetMessageTextResponse, error) {
result, err := s.command.SetDefaultMessageText(ctx, SetPasswordResetCustomTextToDomain(req))
if err != nil {
return nil, err
}
return &admin_pb.SetDefaultPasswordResetMessageTextResponse{
Details: object.ChangeToDetailsPb(
result.Sequence,
result.EventDate,
result.ResourceOwner,
),
}, nil
}
func (s *Server) GetDefaultVerifyEmailMessageText(ctx context.Context, req *admin_pb.GetDefaultVerifyEmailMessageTextRequest) (*admin_pb.GetDefaultVerifyEmailMessageTextResponse, error) {
msg, err := s.iam.GetDefaultMessageText(ctx, domain.VerifyEmailMessageType, req.Language)
if err != nil {
return nil, err
}
return &admin_pb.GetDefaultVerifyEmailMessageTextResponse{
CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
}, nil
}
func (s *Server) SetVerifyEmailMessageCustomText(ctx context.Context, req *admin_pb.SetDefaultVerifyEmailMessageTextRequest) (*admin_pb.SetDefaultVerifyEmailMessageTextResponse, error) {
result, err := s.command.SetDefaultMessageText(ctx, SetVerifyEmailCustomTextToDomain(req))
if err != nil {
return nil, err
}
return &admin_pb.SetDefaultVerifyEmailMessageTextResponse{
Details: object.ChangeToDetailsPb(
result.Sequence,
result.EventDate,
result.ResourceOwner,
),
}, nil
}
func (s *Server) GetDefaultVerifyPhoneMessageText(ctx context.Context, req *admin_pb.GetDefaultVerifyPhoneMessageTextRequest) (*admin_pb.GetDefaultVerifyPhoneMessageTextResponse, error) {
msg, err := s.iam.GetDefaultMessageText(ctx, domain.VerifyPhoneMessageType, req.Language)
if err != nil {
return nil, err
}
return &admin_pb.GetDefaultVerifyPhoneMessageTextResponse{
CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
}, nil
}
func (s *Server) SetDefaultVerifyPhoneMessageText(ctx context.Context, req *admin_pb.SetDefaultVerifyPhoneMessageTextRequest) (*admin_pb.SetDefaultVerifyPhoneMessageTextResponse, error) {
result, err := s.command.SetDefaultMessageText(ctx, SetVerifyPhoneCustomTextToDomain(req))
if err != nil {
return nil, err
}
return &admin_pb.SetDefaultVerifyPhoneMessageTextResponse{
Details: object.ChangeToDetailsPb(
result.Sequence,
result.EventDate,
result.ResourceOwner,
),
}, nil
}
func (s *Server) GetDefaultDomainClaimedMessageText(ctx context.Context, req *admin_pb.GetDefaultDomainClaimedMessageTextRequest) (*admin_pb.GetDefaultDomainClaimedMessageTextResponse, error) {
msg, err := s.iam.GetDefaultMessageText(ctx, domain.DomainClaimedMessageType, req.Language)
if err != nil {
return nil, err
}
return &admin_pb.GetDefaultDomainClaimedMessageTextResponse{
CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
}, nil
}
func (s *Server) SetDefaultDomainClaimedMessageText(ctx context.Context, req *admin_pb.SetDefaultDomainClaimedMessageTextRequest) (*admin_pb.SetDefaultDomainClaimedMessageTextResponse, error) {
result, err := s.command.SetDefaultMessageText(ctx, SetDomainClaimedCustomTextToDomain(req))
if err != nil {
return nil, err
}
return &admin_pb.SetDefaultDomainClaimedMessageTextResponse{
Details: object.ChangeToDetailsPb(
result.Sequence,
result.EventDate,
result.ResourceOwner,
),
}, nil
}

View File

@ -0,0 +1,83 @@
package admin
import (
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/domain"
admin_pb "github.com/caos/zitadel/pkg/grpc/admin"
)
func SetInitCustomTextToDomain(msg *admin_pb.SetDefaultInitMessageTextRequest) *domain.CustomMessageText {
langTag := language.Make(msg.Language)
return &domain.CustomMessageText{
MessageTextType: domain.InitCodeMessageType,
Language: langTag,
Title: msg.Title,
PreHeader: msg.PreHeader,
Subject: msg.Subject,
Greeting: msg.Greeting,
Text: msg.Text,
ButtonText: msg.ButtonText,
FooterText: msg.FooterText,
}
}
func SetPasswordResetCustomTextToDomain(msg *admin_pb.SetDefaultPasswordResetMessageTextRequest) *domain.CustomMessageText {
langTag := language.Make(msg.Language)
return &domain.CustomMessageText{
MessageTextType: domain.PasswordResetMessageType,
Language: langTag,
Title: msg.Title,
PreHeader: msg.PreHeader,
Subject: msg.Subject,
Greeting: msg.Greeting,
Text: msg.Text,
ButtonText: msg.ButtonText,
FooterText: msg.FooterText,
}
}
func SetVerifyEmailCustomTextToDomain(msg *admin_pb.SetDefaultVerifyEmailMessageTextRequest) *domain.CustomMessageText {
langTag := language.Make(msg.Language)
return &domain.CustomMessageText{
MessageTextType: domain.VerifyEmailMessageType,
Language: langTag,
Title: msg.Title,
PreHeader: msg.PreHeader,
Subject: msg.Subject,
Greeting: msg.Greeting,
Text: msg.Text,
ButtonText: msg.ButtonText,
FooterText: msg.FooterText,
}
}
func SetVerifyPhoneCustomTextToDomain(msg *admin_pb.SetDefaultVerifyPhoneMessageTextRequest) *domain.CustomMessageText {
langTag := language.Make(msg.Language)
return &domain.CustomMessageText{
MessageTextType: domain.VerifyPhoneMessageType,
Language: langTag,
Title: msg.Title,
PreHeader: msg.PreHeader,
Subject: msg.Subject,
Greeting: msg.Greeting,
Text: msg.Text,
ButtonText: msg.ButtonText,
FooterText: msg.FooterText,
}
}
func SetDomainClaimedCustomTextToDomain(msg *admin_pb.SetDefaultDomainClaimedMessageTextRequest) *domain.CustomMessageText {
langTag := language.Make(msg.Language)
return &domain.CustomMessageText{
MessageTextType: domain.DomainClaimedMessageType,
Language: langTag,
Title: msg.Title,
PreHeader: msg.PreHeader,
Subject: msg.Subject,
Greeting: msg.Greeting,
Text: msg.Text,
ButtonText: msg.ButtonText,
FooterText: msg.FooterText,
}
}

View File

@ -74,6 +74,7 @@ func setDefaultFeaturesRequestToDomain(req *admin_pb.SetDefaultFeaturesRequest)
LabelPolicyPrivateLabel: req.LabelPolicy || req.LabelPolicyPrivateLabel,
LabelPolicyWatermark: req.LabelPolicyWatermark,
CustomDomain: req.CustomDomain,
CustomText: req.CustomText,
}
}
@ -94,5 +95,6 @@ func setOrgFeaturesRequestToDomain(req *admin_pb.SetOrgFeaturesRequest) *domain.
LabelPolicyPrivateLabel: req.LabelPolicy || req.LabelPolicyPrivateLabel,
LabelPolicyWatermark: req.LabelPolicyWatermark,
CustomDomain: req.CustomDomain,
CustomText: req.CustomText,
}
}

View File

@ -27,6 +27,7 @@ func FeaturesFromModel(features *features_model.FeaturesView) *features_pb.Featu
CustomDomain: features.CustomDomain,
LabelPolicyPrivateLabel: features.LabelPolicyPrivateLabel,
LabelPolicyWatermark: features.LabelPolicyWatermark,
CustomText: features.CustomText,
}
}

View File

@ -0,0 +1,131 @@
package management
import (
"context"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/grpc/object"
text_grpc "github.com/caos/zitadel/internal/api/grpc/text"
"github.com/caos/zitadel/internal/domain"
mgmt_pb "github.com/caos/zitadel/pkg/grpc/management"
)
func (s *Server) GetCustomInitMessageText(ctx context.Context, req *mgmt_pb.GetCustomInitMessageTextRequest) (*mgmt_pb.GetCustomInitMessageTextResponse, error) {
msg, err := s.org.GetMessageText(ctx, authz.GetCtxData(ctx).OrgID, domain.InitCodeMessageType, req.Language)
if err != nil {
return nil, err
}
return &mgmt_pb.GetCustomInitMessageTextResponse{
CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
}, nil
}
func (s *Server) SetCustomInitMessageText(ctx context.Context, req *mgmt_pb.SetCustomInitMessageTextRequest) (*mgmt_pb.SetCustomInitMessageTextResponse, error) {
result, err := s.command.SetOrgMessageText(ctx, authz.GetCtxData(ctx).OrgID, SetInitCustomTextToDomain(req))
if err != nil {
return nil, err
}
return &mgmt_pb.SetCustomInitMessageTextResponse{
Details: object.ChangeToDetailsPb(
result.Sequence,
result.EventDate,
result.ResourceOwner,
),
}, nil
}
func (s *Server) GetCustomPasswordResetMessageText(ctx context.Context, req *mgmt_pb.GetCustomPasswordResetMessageTextRequest) (*mgmt_pb.GetCustomPasswordResetMessageTextResponse, error) {
msg, err := s.org.GetMessageText(ctx, authz.GetCtxData(ctx).OrgID, domain.PasswordResetMessageType, req.Language)
if err != nil {
return nil, err
}
return &mgmt_pb.GetCustomPasswordResetMessageTextResponse{
CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
}, nil
}
func (s *Server) SetCustomPasswordResetMessageText(ctx context.Context, req *mgmt_pb.SetCustomPasswordResetMessageTextRequest) (*mgmt_pb.SetCustomPasswordResetMessageTextResponse, error) {
result, err := s.command.SetOrgMessageText(ctx, authz.GetCtxData(ctx).OrgID, SetPasswordResetCustomTextToDomain(req))
if err != nil {
return nil, err
}
return &mgmt_pb.SetCustomPasswordResetMessageTextResponse{
Details: object.ChangeToDetailsPb(
result.Sequence,
result.EventDate,
result.ResourceOwner,
),
}, nil
}
func (s *Server) GetCustomVerifyEmailMessageText(ctx context.Context, req *mgmt_pb.GetCustomVerifyEmailMessageTextRequest) (*mgmt_pb.GetCustomVerifyEmailMessageTextResponse, error) {
msg, err := s.org.GetMessageText(ctx, authz.GetCtxData(ctx).OrgID, domain.VerifyEmailMessageType, req.Language)
if err != nil {
return nil, err
}
return &mgmt_pb.GetCustomVerifyEmailMessageTextResponse{
CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
}, nil
}
func (s *Server) SetCustomVerifyEmailMessageText(ctx context.Context, req *mgmt_pb.SetCustomVerifyEmailMessageTextRequest) (*mgmt_pb.SetCustomVerifyEmailMessageTextResponse, error) {
result, err := s.command.SetOrgMessageText(ctx, authz.GetCtxData(ctx).OrgID, SetVerifyEmailCustomTextToDomain(req))
if err != nil {
return nil, err
}
return &mgmt_pb.SetCustomVerifyEmailMessageTextResponse{
Details: object.ChangeToDetailsPb(
result.Sequence,
result.EventDate,
result.ResourceOwner,
),
}, nil
}
func (s *Server) GetCustomVerifyPhoneMessageText(ctx context.Context, req *mgmt_pb.GetCustomVerifyPhoneMessageTextRequest) (*mgmt_pb.GetCustomVerifyPhoneMessageTextResponse, error) {
msg, err := s.org.GetMessageText(ctx, authz.GetCtxData(ctx).OrgID, domain.VerifyPhoneMessageType, req.Language)
if err != nil {
return nil, err
}
return &mgmt_pb.GetCustomVerifyPhoneMessageTextResponse{
CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
}, nil
}
func (s *Server) SetCustomVerifyPhoneMessageText(ctx context.Context, req *mgmt_pb.SetCustomVerifyPhoneMessageTextRequest) (*mgmt_pb.SetCustomVerifyPhoneMessageTextResponse, error) {
result, err := s.command.SetOrgMessageText(ctx, authz.GetCtxData(ctx).OrgID, SetVerifyPhoneCustomTextToDomain(req))
if err != nil {
return nil, err
}
return &mgmt_pb.SetCustomVerifyPhoneMessageTextResponse{
Details: object.ChangeToDetailsPb(
result.Sequence,
result.EventDate,
result.ResourceOwner,
),
}, nil
}
func (s *Server) GetCustomDomainClaimedMessageText(ctx context.Context, req *mgmt_pb.GetCustomDomainClaimedMessageTextRequest) (*mgmt_pb.GetCustomDomainClaimedMessageTextResponse, error) {
msg, err := s.org.GetMessageText(ctx, authz.GetCtxData(ctx).OrgID, domain.DomainClaimedMessageType, req.Language)
if err != nil {
return nil, err
}
return &mgmt_pb.GetCustomDomainClaimedMessageTextResponse{
CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
}, nil
}
func (s *Server) SetCustomDomainClaimedMessageText(ctx context.Context, req *mgmt_pb.SetCustomDomainClaimedMessageTextRequest) (*mgmt_pb.SetCustomDomainClaimedMessageTextResponse, error) {
result, err := s.command.SetOrgMessageText(ctx, authz.GetCtxData(ctx).OrgID, SetDomainClaimedCustomTextToDomain(req))
if err != nil {
return nil, err
}
return &mgmt_pb.SetCustomDomainClaimedMessageTextResponse{
Details: object.ChangeToDetailsPb(
result.Sequence,
result.EventDate,
result.ResourceOwner,
),
}, nil
}

View File

@ -0,0 +1,83 @@
package management
import (
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/domain"
mgmt_pb "github.com/caos/zitadel/pkg/grpc/management"
)
func SetInitCustomTextToDomain(msg *mgmt_pb.SetCustomInitMessageTextRequest) *domain.CustomMessageText {
langTag := language.Make(msg.Language)
return &domain.CustomMessageText{
MessageTextType: domain.InitCodeMessageType,
Language: langTag,
Title: msg.Title,
PreHeader: msg.PreHeader,
Subject: msg.Subject,
Greeting: msg.Greeting,
Text: msg.Text,
ButtonText: msg.ButtonText,
FooterText: msg.FooterText,
}
}
func SetPasswordResetCustomTextToDomain(msg *mgmt_pb.SetCustomPasswordResetMessageTextRequest) *domain.CustomMessageText {
langTag := language.Make(msg.Language)
return &domain.CustomMessageText{
MessageTextType: domain.PasswordResetMessageType,
Language: langTag,
Title: msg.Title,
PreHeader: msg.PreHeader,
Subject: msg.Subject,
Greeting: msg.Greeting,
Text: msg.Text,
ButtonText: msg.ButtonText,
FooterText: msg.FooterText,
}
}
func SetVerifyEmailCustomTextToDomain(msg *mgmt_pb.SetCustomVerifyEmailMessageTextRequest) *domain.CustomMessageText {
langTag := language.Make(msg.Language)
return &domain.CustomMessageText{
MessageTextType: domain.VerifyEmailMessageType,
Language: langTag,
Title: msg.Title,
PreHeader: msg.PreHeader,
Subject: msg.Subject,
Greeting: msg.Greeting,
Text: msg.Text,
ButtonText: msg.ButtonText,
FooterText: msg.FooterText,
}
}
func SetVerifyPhoneCustomTextToDomain(msg *mgmt_pb.SetCustomVerifyPhoneMessageTextRequest) *domain.CustomMessageText {
langTag := language.Make(msg.Language)
return &domain.CustomMessageText{
MessageTextType: domain.VerifyPhoneMessageType,
Language: langTag,
Title: msg.Title,
PreHeader: msg.PreHeader,
Subject: msg.Subject,
Greeting: msg.Greeting,
Text: msg.Text,
ButtonText: msg.ButtonText,
FooterText: msg.FooterText,
}
}
func SetDomainClaimedCustomTextToDomain(msg *mgmt_pb.SetCustomDomainClaimedMessageTextRequest) *domain.CustomMessageText {
langTag := language.Make(msg.Language)
return &domain.CustomMessageText{
MessageTextType: domain.DomainClaimedMessageType,
Language: langTag,
Title: msg.Title,
PreHeader: msg.PreHeader,
Subject: msg.Subject,
Greeting: msg.Greeting,
Text: msg.Text,
ButtonText: msg.ButtonText,
FooterText: msg.FooterText,
}
}

View File

@ -0,0 +1,25 @@
package text
import (
"github.com/caos/zitadel/internal/api/grpc/object"
"github.com/caos/zitadel/internal/iam/model"
text_pb "github.com/caos/zitadel/pkg/grpc/text"
)
func ModelCustomMsgTextToPb(msg *model.MessageTextView) *text_pb.MessageCustomText {
return &text_pb.MessageCustomText{
Title: msg.Title,
PreHeader: msg.PreHeader,
Subject: msg.Subject,
Greeting: msg.Greeting,
Text: msg.Text,
ButtonText: msg.ButtonText,
FooterText: msg.FooterText,
Details: object.ToViewDetailsPb(
msg.Sequence,
msg.CreationDate,
msg.ChangeDate,
"", //TODO: resourceowner
),
}
}

View File

@ -465,7 +465,9 @@ func (repo *AuthRequestRepo) checkLoginName(ctx context.Context, request *domain
if err != nil {
return err
}
if user.State == int32(domain.UserStateInactive) {
return errors.ThrowPreconditionFailed(nil, "AUTH-2n8fs", "Errors.User.Inactive")
}
request.SetUserInfo(user.ID, loginName, user.PreferredLoginName, "", "", user.ResourceOwner)
return nil
}

View File

@ -145,6 +145,18 @@ func checkFeatures(features *features_view_model.FeaturesView, requiredFeatures
}
continue
}
if requiredFeature == domain.FeatureCustomDomain {
if !features.CustomDomain {
return MissingFeatureErr(requiredFeature)
}
continue
}
if requiredFeature == domain.FeatureCustomText {
if !features.CustomText {
return MissingFeatureErr(requiredFeature)
}
continue
}
return MissingFeatureErr(requiredFeature)
}
return nil

View File

@ -0,0 +1,167 @@
package command
import (
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/policy"
)
type CustomMessageTextReadModel struct {
eventstore.WriteModel
MessageTextType string
Language language.Tag
Title string
PreHeader string
Subject string
Greeting string
Text string
ButtonText string
FooterText string
State domain.PolicyState
}
func (wm *CustomMessageTextReadModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *policy.CustomTextSetEvent:
if e.Template != wm.MessageTextType || wm.Language != e.Language {
continue
}
if e.Key == domain.MessageSubject {
wm.Subject = e.Text
}
if e.Key == domain.MessageTitle {
wm.Title = e.Text
}
if e.Key == domain.MessagePreHeader {
wm.PreHeader = e.Text
}
if e.Key == domain.MessageText {
wm.Text = e.Text
}
if e.Key == domain.MessageGreeting {
wm.Greeting = e.Text
}
if e.Key == domain.MessageButtonText {
wm.ButtonText = e.Text
}
if e.Key == domain.MessageFooterText {
wm.FooterText = e.Text
}
wm.State = domain.PolicyStateActive
case *policy.CustomTextRemovedEvent:
if e.Key != wm.MessageTextType || wm.Language != e.Language {
continue
}
if e.Key == domain.MessageSubject {
wm.Subject = ""
}
if e.Key == domain.MessageTitle {
wm.Title = ""
}
if e.Key == domain.MessagePreHeader {
wm.PreHeader = ""
}
if e.Key == domain.MessageText {
wm.Text = ""
}
if e.Key == domain.MessageGreeting {
wm.Greeting = ""
}
if e.Key == domain.MessageButtonText {
wm.ButtonText = ""
}
if e.Key == domain.MessageFooterText {
wm.FooterText = ""
}
case *policy.CustomTextTemplateRemovedEvent:
wm.State = domain.PolicyStateRemoved
}
}
return wm.WriteModel.Reduce()
}
type CustomMessageTemplatesReadModel struct {
eventstore.WriteModel
CustomMessageTemplate map[string]*CustomText
}
type CustomText struct {
Template string
Language language.Tag
Title string
PreHeader string
Subject string
Greeting string
Text string
ButtonText string
FooterText string
State domain.PolicyState
}
func (wm *CustomMessageTemplatesReadModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *policy.CustomTextSetEvent:
if _, ok := wm.CustomMessageTemplate[e.Template+e.Language.String()]; !ok {
wm.CustomMessageTemplate[e.Template+e.Language.String()] = &CustomText{Language: e.Language, Template: e.Template}
}
if e.Key == domain.MessageSubject {
wm.CustomMessageTemplate[e.Template+e.Language.String()].Subject = e.Text
}
if e.Key == domain.MessageTitle {
wm.CustomMessageTemplate[e.Template+e.Language.String()].Title = e.Text
}
if e.Key == domain.MessagePreHeader {
wm.CustomMessageTemplate[e.Template+e.Language.String()].PreHeader = e.Text
}
if e.Key == domain.MessageText {
wm.CustomMessageTemplate[e.Template+e.Language.String()].Text = e.Text
}
if e.Key == domain.MessageGreeting {
wm.CustomMessageTemplate[e.Template+e.Language.String()].Greeting = e.Text
}
if e.Key == domain.MessageButtonText {
wm.CustomMessageTemplate[e.Template+e.Language.String()].ButtonText = e.Text
}
if e.Key == domain.MessageFooterText {
wm.CustomMessageTemplate[e.Template+e.Language.String()].FooterText = e.Text
}
wm.CustomMessageTemplate[e.Template+e.Language.String()].State = domain.PolicyStateActive
case *policy.CustomTextRemovedEvent:
if _, ok := wm.CustomMessageTemplate[e.Template+e.Language.String()]; !ok {
wm.CustomMessageTemplate[e.Template+e.Language.String()] = new(CustomText)
}
if e.Key == domain.MessageSubject {
wm.CustomMessageTemplate[e.Template+e.Language.String()].Subject = ""
}
if e.Key == domain.MessageTitle {
wm.CustomMessageTemplate[e.Template+e.Language.String()].Title = ""
}
if e.Key == domain.MessagePreHeader {
wm.CustomMessageTemplate[e.Template+e.Language.String()].PreHeader = ""
}
if e.Key == domain.MessageText {
wm.CustomMessageTemplate[e.Template+e.Language.String()].Text = ""
}
if e.Key == domain.MessageGreeting {
wm.CustomMessageTemplate[e.Template+e.Language.String()].Greeting = ""
}
if e.Key == domain.MessageButtonText {
wm.CustomMessageTemplate[e.Template+e.Language.String()].ButtonText = ""
}
if e.Key == domain.MessageFooterText {
wm.CustomMessageTemplate[e.Template+e.Language.String()].FooterText = ""
}
case *policy.CustomTextTemplateRemovedEvent:
if _, ok := wm.CustomMessageTemplate[e.Template+e.Language.String()]; ok {
delete(wm.CustomMessageTemplate, e.Template)
}
}
}
return wm.WriteModel.Reduce()
}

View File

@ -0,0 +1,34 @@
package command
import (
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/policy"
)
type CustomTextWriteModel struct {
eventstore.WriteModel
Key string
Language language.Tag
Text string
State domain.CustomTextState
}
func (wm *CustomTextWriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *policy.CustomTextSetEvent:
if wm.Key != e.Key || wm.Language != e.Language {
continue
}
wm.Text = e.Text
wm.State = domain.CustomTextStateActive
case *policy.CustomTextRemovedEvent:
wm.State = domain.CustomTextStateRemoved
}
}
return wm.WriteModel.Reduce()
}

View File

@ -26,6 +26,7 @@ type FeaturesWriteModel struct {
LabelPolicyPrivateLabel bool
LabelPolicyWatermark bool
CustomDomain bool
CustomText bool
}
func (wm *FeaturesWriteModel) Reduce() error {
@ -81,6 +82,9 @@ func (wm *FeaturesWriteModel) Reduce() error {
if e.CustomDomain != nil {
wm.CustomDomain = *e.CustomDomain
}
if e.CustomText != nil {
wm.CustomText = *e.CustomText
}
case *features.FeaturesRemovedEvent:
wm.State = domain.FeaturesStateRemoved
}

View File

@ -69,21 +69,6 @@ func writeModelToMailTemplate(wm *MailTemplateWriteModel) *domain.MailTemplate {
}
}
func writeModelToMailText(wm *MailTextWriteModel) *domain.MailText {
return &domain.MailText{
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
MailTextType: wm.MailTextType,
Language: wm.Language,
Title: wm.Title,
PreHeader: wm.PreHeader,
Subject: wm.Subject,
Greeting: wm.Greeting,
Text: wm.Text,
ButtonText: wm.ButtonText,
State: wm.State,
}
}
func writeModelToOrgIAMPolicy(wm *IAMOrgIAMPolicyWriteModel) *domain.OrgIAMPolicy {
return &domain.OrgIAMPolicy{
ObjectRoot: writeModelToObjectRoot(wm.PolicyOrgIAMWriteModel.WriteModel),
@ -98,18 +83,13 @@ func writeModelToMailTemplatePolicy(wm *MailTemplateWriteModel) *domain.MailTemp
}
}
func writeModelToMailTextPolicy(wm *MailTextWriteModel) *domain.MailText {
return &domain.MailText{
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
State: wm.State,
MailTextType: wm.MailTextType,
Language: wm.Language,
Title: wm.Title,
PreHeader: wm.PreHeader,
Subject: wm.Subject,
Greeting: wm.Greeting,
Text: wm.Text,
ButtonText: wm.ButtonText,
func writeModelToCustomText(wm *CustomTextWriteModel) *domain.CustomText {
return &domain.CustomText{
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
State: wm.State,
Key: wm.Key,
Language: wm.Language,
Text: wm.Text,
}
}

View File

@ -0,0 +1,72 @@
package command
import (
"context"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/iam"
)
func (c *Commands) SetDefaultMessageText(ctx context.Context, messageText *domain.CustomMessageText) (*domain.ObjectDetails, error) {
iamAgg := iam.NewAggregate()
events, existingMessageText, err := c.setDefaultMessageText(ctx, &iamAgg.Aggregate, messageText)
if err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.PushEvents(ctx, events...)
if err != nil {
return nil, err
}
err = AppendAndReduce(existingMessageText, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingMessageText.WriteModel), nil
}
func (c *Commands) setDefaultMessageText(ctx context.Context, iamAgg *eventstore.Aggregate, msg *domain.CustomMessageText) ([]eventstore.EventPusher, *IAMCustomMessageTextReadModel, error) {
if !msg.IsValid() {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "IAM-kd9fs", "Errors.CustomMessageText.Invalid")
}
existingMessageText, err := c.defaultCustomMessageTextWriteModelByID(ctx, msg.MessageTextType, msg.Language)
if err != nil {
return nil, nil, err
}
events := make([]eventstore.EventPusher, 0)
if existingMessageText.Greeting != msg.Greeting {
events = append(events, iam.NewCustomTextSetEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageGreeting, msg.Greeting, msg.Language))
}
if existingMessageText.Subject != msg.Subject {
events = append(events, iam.NewCustomTextSetEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageSubject, msg.Subject, msg.Language))
}
if existingMessageText.Title != msg.Title {
events = append(events, iam.NewCustomTextSetEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageTitle, msg.Title, msg.Language))
}
if existingMessageText.PreHeader != msg.PreHeader {
events = append(events, iam.NewCustomTextSetEvent(ctx, iamAgg, msg.MessageTextType, domain.MessagePreHeader, msg.PreHeader, msg.Language))
}
if existingMessageText.Text != msg.Text {
events = append(events, iam.NewCustomTextSetEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageText, msg.Text, msg.Language))
}
if existingMessageText.ButtonText != msg.ButtonText {
events = append(events, iam.NewCustomTextSetEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageButtonText, msg.ButtonText, msg.Language))
}
if existingMessageText.FooterText != msg.FooterText {
events = append(events, iam.NewCustomTextSetEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageFooterText, msg.FooterText, msg.Language))
}
return events, existingMessageText, nil
}
func (c *Commands) defaultCustomMessageTextWriteModelByID(ctx context.Context, messageType string, lang language.Tag) (*IAMCustomMessageTextReadModel, error) {
writeModel := NewIAMCustomMessageTextWriteModel(messageType, lang)
err := c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {
return nil, err
}
return writeModel, nil
}

View File

@ -0,0 +1,47 @@
package command
import (
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/iam"
)
type IAMCustomMessageTextReadModel struct {
CustomMessageTextReadModel
}
func NewIAMCustomMessageTextWriteModel(messageTextType string, lang language.Tag) *IAMCustomMessageTextReadModel {
return &IAMCustomMessageTextReadModel{
CustomMessageTextReadModel{
WriteModel: eventstore.WriteModel{
AggregateID: domain.IAMID,
ResourceOwner: domain.IAMID,
},
MessageTextType: messageTextType,
Language: lang,
},
}
}
func (wm *IAMCustomMessageTextReadModel) AppendEvents(events ...eventstore.EventReader) {
for _, event := range events {
switch e := event.(type) {
case *iam.CustomTextSetEvent:
wm.CustomMessageTextReadModel.AppendEvents(&e.CustomTextSetEvent)
}
}
}
func (wm *IAMCustomMessageTextReadModel) Reduce() error {
return wm.CustomMessageTextReadModel.Reduce()
}
func (wm *IAMCustomMessageTextReadModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, iam.AggregateType).
AggregateIDs(wm.CustomMessageTextReadModel.AggregateID).
ResourceOwner(wm.ResourceOwner).
EventTypes(
iam.CustomTextSetEventType)
}

View File

@ -0,0 +1,163 @@
package command
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/repository/iam"
)
func TestCommandSide_SetDefaultMessageText(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
config *domain.CustomMessageText
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "invalid custom text, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
config: &domain.CustomMessageText{},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "custom text set all fields, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
iam.NewCustomTextSetEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"Template",
domain.MessageGreeting,
"Greeting",
language.English,
),
),
eventFromEventPusher(
iam.NewCustomTextSetEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"Template",
domain.MessageSubject,
"Subject",
language.English,
),
),
eventFromEventPusher(
iam.NewCustomTextSetEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"Template",
domain.MessageTitle,
"Title",
language.English,
),
),
eventFromEventPusher(
iam.NewCustomTextSetEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"Template",
domain.MessagePreHeader,
"PreHeader",
language.English,
),
),
eventFromEventPusher(
iam.NewCustomTextSetEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"Template",
domain.MessageText,
"Text",
language.English,
),
),
eventFromEventPusher(
iam.NewCustomTextSetEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"Template",
domain.MessageButtonText,
"ButtonText",
language.English,
),
),
eventFromEventPusher(
iam.NewCustomTextSetEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"Template",
domain.MessageFooterText,
"FooterText",
language.English,
),
),
},
),
),
},
args: args{
ctx: context.Background(),
config: &domain.CustomMessageText{
MessageTextType: "Template",
Language: language.English,
Greeting: "Greeting",
Subject: "Subject",
Title: "Title",
PreHeader: "PreHeader",
Text: "Text",
ButtonText: "ButtonText",
FooterText: "FooterText",
},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "IAM",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.SetDefaultMessageText(tt.args.ctx, tt.args.config)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}

View File

@ -49,6 +49,7 @@ func (c *Commands) setDefaultFeatures(ctx context.Context, existingFeatures *IAM
features.LabelPolicyPrivateLabel,
features.LabelPolicyWatermark,
features.CustomDomain,
features.CustomText,
)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "Features-GE4h2", "Errors.Features.NotChanged")

View File

@ -64,7 +64,8 @@ func (wm *IAMFeaturesWriteModel) NewSetEvent(
passwordComplexityPolicy,
labelPolicyPrivateLabel,
labelPolicyWatermark,
customDomain bool,
customDomain,
customText bool,
) (*iam.FeaturesSetEvent, bool) {
changes := make([]features.FeaturesChanges, 0)
@ -111,6 +112,9 @@ func (wm *IAMFeaturesWriteModel) NewSetEvent(
if wm.CustomDomain != customDomain {
changes = append(changes, features.ChangeCustomDomain(customDomain))
}
if wm.CustomText != customText {
changes = append(changes, features.ChangeCustomText(customText))
}
if len(changes) == 0 {
return nil, false

View File

@ -1,106 +0,0 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
iam_repo "github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/telemetry/tracing"
)
func (c *Commands) AddDefaultMailText(ctx context.Context, policy *domain.MailText) (*domain.MailText, error) {
addedPolicy := NewIAMMailTextWriteModel(policy.MailTextType, policy.Language)
iamAgg := IAMAggregateFromWriteModel(&addedPolicy.MailTextWriteModel.WriteModel)
event, err := c.addDefaultMailText(ctx, iamAgg, addedPolicy, policy)
if err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.PushEvents(ctx, event)
if err != nil {
return nil, err
}
err = AppendAndReduce(addedPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToMailTextPolicy(&addedPolicy.MailTextWriteModel), nil
}
func (c *Commands) addDefaultMailText(ctx context.Context, iamAgg *eventstore.Aggregate, addedPolicy *IAMMailTextWriteModel, mailText *domain.MailText) (eventstore.EventPusher, error) {
if !mailText.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-3n8fs", "Errors.IAM.MailText.Invalid")
}
err := c.eventstore.FilterToQueryReducer(ctx, addedPolicy)
if err != nil {
return nil, err
}
if addedPolicy.State == domain.PolicyStateActive {
return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-9o0pM", "Errors.IAM.MailText.AlreadyExists")
}
return iam_repo.NewMailTextAddedEvent(
ctx,
iamAgg,
mailText.MailTextType,
mailText.Language,
mailText.Title,
mailText.PreHeader,
mailText.Subject,
mailText.Greeting,
mailText.Text,
mailText.ButtonText), nil
}
func (c *Commands) ChangeDefaultMailText(ctx context.Context, mailText *domain.MailText) (*domain.MailText, error) {
if !mailText.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-kd9fs", "Errors.IAM.MailText.Invalid")
}
existingPolicy, err := c.defaultMailTextWriteModelByID(ctx, mailText.MailTextType, mailText.Language)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "IAM-2N8fs", "Errors.IAM.MailText.NotFound")
}
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.MailTextWriteModel.WriteModel)
changedEvent, hasChanged := existingPolicy.NewChangedEvent(
ctx,
iamAgg,
mailText.MailTextType,
mailText.Language,
mailText.Title,
mailText.PreHeader,
mailText.Subject,
mailText.Greeting,
mailText.Text,
mailText.ButtonText)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-m9L0s", "Errors.IAM.MailText.NotChanged")
}
pushedEvents, err := c.eventstore.PushEvents(ctx, changedEvent)
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToMailTextPolicy(&existingPolicy.MailTextWriteModel), nil
}
func (c *Commands) defaultMailTextWriteModelByID(ctx context.Context, mailTextType, language string) (policy *IAMMailTextWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
writeModel := NewIAMMailTextWriteModel(mailTextType, language)
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {
return nil, err
}
return writeModel, nil
}

View File

@ -1,91 +0,0 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/repository/policy"
)
type IAMMailTextWriteModel struct {
MailTextWriteModel
}
func NewIAMMailTextWriteModel(mailTextType, language string) *IAMMailTextWriteModel {
return &IAMMailTextWriteModel{
MailTextWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: domain.IAMID,
ResourceOwner: domain.IAMID,
},
MailTextType: mailTextType,
Language: language,
},
}
}
func (wm *IAMMailTextWriteModel) AppendEvents(events ...eventstore.EventReader) {
for _, event := range events {
switch e := event.(type) {
case *iam.MailTextAddedEvent:
wm.MailTextWriteModel.AppendEvents(&e.MailTextAddedEvent)
case *iam.MailTextChangedEvent:
wm.MailTextWriteModel.AppendEvents(&e.MailTextChangedEvent)
}
}
}
func (wm *IAMMailTextWriteModel) Reduce() error {
return wm.MailTextWriteModel.Reduce()
}
func (wm *IAMMailTextWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, iam.AggregateType).
AggregateIDs(wm.MailTextWriteModel.AggregateID).
ResourceOwner(wm.ResourceOwner).
EventTypes(
iam.MailTextAddedEventType,
iam.MailTextChangedEventType)
}
func (wm *IAMMailTextWriteModel) NewChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
mailTextType,
language,
title,
preHeader,
subject,
greeting,
text,
buttonText string,
) (*iam.MailTextChangedEvent, bool) {
changes := make([]policy.MailTextChanges, 0)
if wm.Title != title {
changes = append(changes, policy.ChangeTitle(title))
}
if wm.PreHeader != preHeader {
changes = append(changes, policy.ChangePreHeader(preHeader))
}
if wm.Subject != subject {
changes = append(changes, policy.ChangeSubject(subject))
}
if wm.Greeting != greeting {
changes = append(changes, policy.ChangeGreeting(greeting))
}
if wm.Text != text {
changes = append(changes, policy.ChangeText(text))
}
if wm.ButtonText != buttonText {
changes = append(changes, policy.ChangeButtonText(buttonText))
}
if len(changes) == 0 {
return nil, false
}
changedEvent, err := iam.NewMailTextChangedEvent(ctx, aggregate, mailTextType, language, changes)
if err != nil {
return nil, false
}
return changedEvent, true
}

View File

@ -1,366 +0,0 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/repository/policy"
"github.com/stretchr/testify/assert"
"testing"
)
func TestCommandSide_AddDefaultMailTextPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
policy *domain.MailText
}
type res struct {
want *domain.MailText
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "mail text invalid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.MailText{},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "mail text already existing, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewMailTextAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"mail-text-type",
"de",
"title",
"pre-header",
"subject",
"greeting",
"text",
"button-text",
),
),
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.MailText{
MailTextType: "mail-text-type",
Language: "de",
Title: "title",
PreHeader: "pre-header",
Subject: "subject",
Greeting: "greeting",
Text: "text",
ButtonText: "button-text",
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "add mail template,ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
iam.NewMailTextAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"mail-text-type",
"de",
"title",
"pre-header",
"subject",
"greeting",
"text",
"button-text",
),
),
},
uniqueConstraintsFromEventConstraint(policy.NewAddMailTextUniqueConstraint("IAM", "mail-text-type", "de")),
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.MailText{
MailTextType: "mail-text-type",
Language: "de",
Title: "title",
PreHeader: "pre-header",
Subject: "subject",
Greeting: "greeting",
Text: "text",
ButtonText: "button-text",
},
},
res: res{
want: &domain.MailText{
ObjectRoot: models.ObjectRoot{
AggregateID: "IAM",
ResourceOwner: "IAM",
},
MailTextType: "mail-text-type",
Language: "de",
Title: "title",
PreHeader: "pre-header",
Subject: "subject",
Greeting: "greeting",
Text: "text",
ButtonText: "button-text",
State: domain.PolicyStateActive,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddDefaultMailText(tt.args.ctx, tt.args.policy)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_ChangeDefaultMailTextPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
policy *domain.MailText
}
type res struct {
want *domain.MailText
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "mailtext invalid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.MailText{},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "mail text not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
policy: &domain.MailText{
MailTextType: "mail-text-type",
Language: "de",
Title: "title",
PreHeader: "pre-header",
Subject: "subject",
Greeting: "greeting",
Text: "text",
ButtonText: "button-text",
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "no changes, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewMailTextAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"mail-text-type",
"de",
"title",
"pre-header",
"subject",
"greeting",
"text",
"button-text",
),
),
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.MailText{
MailTextType: "mail-text-type",
Language: "de",
Title: "title",
PreHeader: "pre-header",
Subject: "subject",
Greeting: "greeting",
Text: "text",
ButtonText: "button-text",
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "change, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewMailTextAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"mail-text-type",
"de",
"title",
"pre-header",
"subject",
"greeting",
"text",
"button-text",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newDefaultMailTextPolicyChangedEvent(
context.Background(),
"mail-text-type",
"de",
"title-change",
"pre-header-change",
"subject-change",
"greeting-change",
"text-change",
"button-text-change"),
),
},
),
),
},
args: args{
ctx: context.Background(),
policy: &domain.MailText{
MailTextType: "mail-text-type",
Language: "de",
Title: "title-change",
PreHeader: "pre-header-change",
Subject: "subject-change",
Greeting: "greeting-change",
Text: "text-change",
ButtonText: "button-text-change",
},
},
res: res{
want: &domain.MailText{
ObjectRoot: models.ObjectRoot{
AggregateID: "IAM",
ResourceOwner: "IAM",
},
MailTextType: "mail-text-type",
Language: "de",
Title: "title-change",
PreHeader: "pre-header-change",
Subject: "subject-change",
Greeting: "greeting-change",
Text: "text-change",
ButtonText: "button-text-change",
State: domain.PolicyStateActive,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangeDefaultMailText(tt.args.ctx, tt.args.policy)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func newDefaultMailTextPolicyChangedEvent(ctx context.Context, mailTextType, language, title, preHeader, subject, greeting, text, buttonText string) *iam.MailTextChangedEvent {
event, _ := iam.NewMailTextChangedEvent(ctx,
&iam.NewAggregate().Aggregate,
mailTextType,
language,
[]policy.MailTextChanges{
policy.ChangeTitle(title),
policy.ChangePreHeader(preHeader),
policy.ChangeSubject(subject),
policy.ChangeGreeting(greeting),
policy.ChangeText(text),
policy.ChangeButtonText(buttonText),
},
)
return event
}

View File

@ -0,0 +1,95 @@
package command
import (
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/org"
)
type OrgCustomMessageTextReadModel struct {
CustomMessageTextReadModel
}
func NewOrgCustomMessageTextWriteModel(orgID, messageTextType string, lang language.Tag) *OrgCustomMessageTextReadModel {
return &OrgCustomMessageTextReadModel{
CustomMessageTextReadModel{
WriteModel: eventstore.WriteModel{
AggregateID: orgID,
ResourceOwner: orgID,
},
MessageTextType: messageTextType,
Language: lang,
},
}
}
func (wm *OrgCustomMessageTextReadModel) AppendEvents(events ...eventstore.EventReader) {
for _, event := range events {
switch e := event.(type) {
case *org.CustomTextSetEvent:
wm.CustomMessageTextReadModel.AppendEvents(&e.CustomTextSetEvent)
case *org.CustomTextRemovedEvent:
wm.CustomMessageTextReadModel.AppendEvents(&e.CustomTextRemovedEvent)
case *org.CustomTextTemplateRemovedEvent:
wm.CustomMessageTextReadModel.AppendEvents(&e.CustomTextTemplateRemovedEvent)
}
}
}
func (wm *OrgCustomMessageTextReadModel) Reduce() error {
return wm.CustomMessageTextReadModel.Reduce()
}
func (wm *OrgCustomMessageTextReadModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, org.AggregateType).
AggregateIDs(wm.CustomMessageTextReadModel.AggregateID).
ResourceOwner(wm.ResourceOwner).
EventTypes(
org.CustomTextSetEventType,
org.CustomTextRemovedEventType,
org.CustomTextTemplateRemovedEventType)
}
type OrgCustomMessageTemplatesReadModel struct {
CustomMessageTemplatesReadModel
}
func NewOrgCustomMessageTextsWriteModel(orgID string) *OrgCustomMessageTemplatesReadModel {
return &OrgCustomMessageTemplatesReadModel{
CustomMessageTemplatesReadModel{
WriteModel: eventstore.WriteModel{
AggregateID: orgID,
ResourceOwner: orgID,
},
CustomMessageTemplate: make(map[string]*CustomText),
},
}
}
func (wm *OrgCustomMessageTemplatesReadModel) AppendEvents(events ...eventstore.EventReader) {
for _, event := range events {
switch e := event.(type) {
case *org.CustomTextSetEvent:
wm.CustomMessageTemplatesReadModel.AppendEvents(&e.CustomTextSetEvent)
case *org.CustomTextRemovedEvent:
wm.CustomMessageTemplatesReadModel.AppendEvents(&e.CustomTextRemovedEvent)
case *org.CustomTextTemplateRemovedEvent:
wm.CustomMessageTemplatesReadModel.AppendEvents(&e.CustomTextTemplateRemovedEvent)
}
}
}
func (wm *OrgCustomMessageTemplatesReadModel) Reduce() error {
return wm.CustomMessageTemplatesReadModel.Reduce()
}
func (wm *OrgCustomMessageTemplatesReadModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, org.AggregateType).
AggregateIDs(wm.CustomMessageTemplatesReadModel.AggregateID).
ResourceOwner(wm.ResourceOwner).
EventTypes(
org.CustomTextSetEventType,
org.CustomTextRemovedEventType,
org.CustomTextTemplateRemovedEventType)
}

View File

@ -0,0 +1,137 @@
package command
import (
"context"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/org"
)
func (c *Commands) SetOrgMessageText(ctx context.Context, resourceOwner string, messageText *domain.CustomMessageText) (*domain.ObjectDetails, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-2biiR", "Errors.ResourceOwnerMissing")
}
orgAgg := org.NewAggregate(resourceOwner, resourceOwner)
events, existingMessageText, err := c.setOrgMessageText(ctx, &orgAgg.Aggregate, messageText)
if err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.PushEvents(ctx, events...)
if err != nil {
return nil, err
}
err = AppendAndReduce(existingMessageText, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingMessageText.WriteModel), nil
}
func (c *Commands) setOrgMessageText(ctx context.Context, orgAgg *eventstore.Aggregate, message *domain.CustomMessageText) ([]eventstore.EventPusher, *OrgCustomMessageTextReadModel, error) {
if !message.IsValid() {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "ORG-2jfsf", "Errors.CustomText.Invalid")
}
existingMessageText, err := c.orgCustomMessageTextWriteModelByID(ctx, orgAgg.ID, message.MessageTextType, message.Language)
if err != nil {
return nil, nil, err
}
events := make([]eventstore.EventPusher, 0)
if existingMessageText.Greeting != message.Greeting {
if message.Greeting != "" {
events = append(events, org.NewCustomTextSetEvent(ctx, orgAgg, message.MessageTextType, domain.MessageGreeting, message.Greeting, message.Language))
} else {
events = append(events, org.NewCustomTextRemovedEvent(ctx, orgAgg, message.MessageTextType, domain.MessageGreeting, message.Language))
}
}
if existingMessageText.Subject != message.Subject {
if message.Subject != "" {
events = append(events, org.NewCustomTextSetEvent(ctx, orgAgg, message.MessageTextType, domain.MessageSubject, message.Subject, message.Language))
} else {
events = append(events, org.NewCustomTextRemovedEvent(ctx, orgAgg, message.MessageTextType, domain.MessageSubject, message.Language))
}
}
if existingMessageText.Title != message.Title {
if message.Title != "" {
events = append(events, org.NewCustomTextSetEvent(ctx, orgAgg, message.MessageTextType, domain.MessageTitle, message.Title, message.Language))
} else {
events = append(events, org.NewCustomTextRemovedEvent(ctx, orgAgg, message.MessageTextType, domain.MessageTitle, message.Language))
}
}
if existingMessageText.PreHeader != message.PreHeader {
if message.PreHeader != "" {
events = append(events, org.NewCustomTextSetEvent(ctx, orgAgg, message.MessageTextType, domain.MessagePreHeader, message.PreHeader, message.Language))
} else {
events = append(events, org.NewCustomTextRemovedEvent(ctx, orgAgg, message.MessageTextType, domain.MessagePreHeader, message.Language))
}
}
if existingMessageText.Text != message.Text {
if message.Text != "" {
events = append(events, org.NewCustomTextSetEvent(ctx, orgAgg, message.MessageTextType, domain.MessageText, message.Text, message.Language))
} else {
events = append(events, org.NewCustomTextRemovedEvent(ctx, orgAgg, message.MessageTextType, domain.MessageText, message.Language))
}
}
if existingMessageText.ButtonText != message.ButtonText {
if message.ButtonText != "" {
events = append(events, org.NewCustomTextSetEvent(ctx, orgAgg, message.MessageTextType, domain.MessageButtonText, message.ButtonText, message.Language))
} else {
events = append(events, org.NewCustomTextRemovedEvent(ctx, orgAgg, message.MessageTextType, domain.MessageButtonText, message.Language))
}
}
if existingMessageText.FooterText != message.FooterText {
if message.FooterText != "" {
events = append(events, org.NewCustomTextSetEvent(ctx, orgAgg, message.MessageTextType, domain.MessageFooterText, message.FooterText, message.Language))
} else {
events = append(events, org.NewCustomTextRemovedEvent(ctx, orgAgg, message.MessageTextType, domain.MessageFooterText, message.Language))
}
}
return events, existingMessageText, nil
}
func (c *Commands) RemoveOrgMessageTexts(ctx context.Context, resourceOwner, messageTextType string, lang language.Tag) error {
if resourceOwner == "" {
return caos_errs.ThrowInvalidArgument(nil, "Org-3mfsf", "Errors.ResourceOwnerMissing")
}
if messageTextType == "" || lang == language.Und {
return caos_errs.ThrowInvalidArgument(nil, "Org-j59f", "Errors.CustomMessageText.Invalid")
}
customText, err := c.orgCustomMessageTextWriteModelByID(ctx, resourceOwner, messageTextType, lang)
if err != nil {
return err
}
if customText.State == domain.PolicyStateUnspecified || customText.State == domain.PolicyStateRemoved {
return caos_errs.ThrowNotFound(nil, "Org-3b8Jf", "Errors.CustomMessageText.NotFound")
}
orgAgg := OrgAggregateFromWriteModel(&customText.WriteModel)
_, err = c.eventstore.PushEvents(ctx, org.NewCustomTextTemplateRemovedEvent(ctx, orgAgg, messageTextType, lang))
return err
}
func (c *Commands) removeOrgMessageTextsIfExists(ctx context.Context, orgID string) ([]eventstore.EventPusher, error) {
msgTemplates := NewOrgCustomMessageTextsWriteModel(orgID)
err := c.eventstore.FilterToQueryReducer(ctx, msgTemplates)
if err != nil {
return nil, err
}
orgAgg := OrgAggregateFromWriteModel(&msgTemplates.WriteModel)
events := make([]eventstore.EventPusher, 0, len(msgTemplates.CustomMessageTemplate))
for _, tmpl := range msgTemplates.CustomMessageTemplate {
events = append(events, org.NewCustomTextTemplateRemovedEvent(ctx, orgAgg, tmpl.Template, tmpl.Language))
}
return events, nil
}
func (c *Commands) orgCustomMessageTextWriteModelByID(ctx context.Context, orgID, messageType string, lang language.Tag) (*OrgCustomMessageTextReadModel, error) {
writeModel := NewOrgCustomMessageTextWriteModel(orgID, messageType, lang)
err := c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {
return nil, err
}
return writeModel, nil
}

View File

@ -0,0 +1,514 @@
package command
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/repository/org"
)
func TestCommandSide_SetCustomMessageText(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
resourceOwner string
config *domain.CustomMessageText
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "no resource owner, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
config: &domain.CustomMessageText{},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "invalid custom text, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
config: &domain.CustomMessageText{},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "custom text set all fields, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"Template",
domain.MessageGreeting,
"Greeting",
language.English,
),
),
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"Template",
domain.MessageSubject,
"Subject",
language.English,
),
),
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"Template",
domain.MessageTitle,
"Title",
language.English,
),
),
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"Template",
domain.MessagePreHeader,
"PreHeader",
language.English,
),
),
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"Template",
domain.MessageText,
"Text",
language.English,
),
),
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"Template",
domain.MessageButtonText,
"ButtonText",
language.English,
),
),
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"Template",
domain.MessageFooterText,
"FooterText",
language.English,
),
),
},
),
),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
config: &domain.CustomMessageText{
MessageTextType: "Template",
Language: language.English,
Greeting: "Greeting",
Subject: "Subject",
Title: "Title",
PreHeader: "PreHeader",
Text: "Text",
ButtonText: "ButtonText",
FooterText: "FooterText",
},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
{
name: "custom text remove all fields, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"Template",
domain.MessageGreeting,
"Greeting",
language.English,
),
),
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"Template",
domain.MessageSubject,
"Subject",
language.English,
),
),
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"Template",
domain.MessageTitle,
"Title",
language.English,
),
),
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"Template",
domain.MessagePreHeader,
"PreHeader",
language.English,
),
),
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"Template",
domain.MessageText,
"Text",
language.English,
),
),
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"Template",
domain.MessageButtonText,
"ButtonText",
language.English,
),
),
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"Template",
domain.MessageFooterText,
"FooterText",
language.English,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
org.NewCustomTextRemovedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"Template",
domain.MessageGreeting,
language.English,
),
),
eventFromEventPusher(
org.NewCustomTextRemovedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"Template",
domain.MessageSubject,
language.English,
),
),
eventFromEventPusher(
org.NewCustomTextRemovedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"Template",
domain.MessageTitle,
language.English,
),
),
eventFromEventPusher(
org.NewCustomTextRemovedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"Template",
domain.MessagePreHeader,
language.English,
),
),
eventFromEventPusher(
org.NewCustomTextRemovedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"Template",
domain.MessageText,
language.English,
),
),
eventFromEventPusher(
org.NewCustomTextRemovedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"Template",
domain.MessageButtonText,
language.English,
),
),
eventFromEventPusher(
org.NewCustomTextRemovedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"Template",
domain.MessageFooterText,
language.English,
),
),
},
),
),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
config: &domain.CustomMessageText{
MessageTextType: "Template",
Language: language.English,
Greeting: "",
Subject: "",
Title: "",
PreHeader: "",
Text: "",
ButtonText: "",
FooterText: "",
},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.SetOrgMessageText(tt.args.ctx, tt.args.resourceOwner, tt.args.config)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_RemoveCustomMessageText(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
resourceOwner string
mailTextType string
lang language.Tag
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "no resource owner, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
mailTextType: "Template",
lang: language.English,
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "no mail text type owner, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
lang: language.English,
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "no mail text type owner, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
mailTextType: "Template",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "custom text remove all fields, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"Template",
domain.MessageGreeting,
"Greeting",
language.English,
),
),
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"Template",
domain.MessageSubject,
"Subject",
language.English,
),
),
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"Template",
domain.MessageTitle,
"Title",
language.English,
),
),
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"Template",
domain.MessagePreHeader,
"PreHeader",
language.English,
),
),
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"Template",
domain.MessageText,
"Text",
language.English,
),
),
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"Template",
domain.MessageButtonText,
"ButtonText",
language.English,
),
),
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"Template",
domain.MessageFooterText,
"FooterText",
language.English,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
org.NewCustomTextTemplateRemovedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"Template",
language.English,
),
),
},
),
),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
mailTextType: "Template",
lang: language.English,
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
err := r.RemoveOrgMessageTexts(tt.args.ctx, tt.args.resourceOwner, tt.args.mailTextType, tt.args.lang)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}

View File

@ -40,6 +40,7 @@ func (c *Commands) SetOrgFeatures(ctx context.Context, resourceOwner string, fea
features.LabelPolicyPrivateLabel,
features.LabelPolicyWatermark,
features.CustomDomain,
features.CustomText,
)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "Features-GE4h2", "Errors.Features.NotChanged")
@ -126,6 +127,15 @@ func (c *Commands) ensureOrgSettingsToFeatures(ctx context.Context, orgID string
events = append(events, removeCustomDomainsEvents...)
}
}
if !features.CustomText {
removeCustomTextEvents, err := c.removeOrgMessageTextsIfExists(ctx, orgID)
if err != nil {
return nil, err
}
if removeCustomTextEvents != nil {
events = append(events, removeCustomTextEvents...)
}
}
return events, nil
}

View File

@ -71,7 +71,8 @@ func (wm *OrgFeaturesWriteModel) NewSetEvent(
passwordComplexityPolicy,
labelPolicyPrivateLabel,
labelPolicyWatermark,
customDomain bool,
customDomain,
customText bool,
) (*org.FeaturesSetEvent, bool) {
changes := make([]features.FeaturesChanges, 0)
@ -121,6 +122,9 @@ func (wm *OrgFeaturesWriteModel) NewSetEvent(
if wm.CustomDomain != customDomain {
changes = append(changes, features.ChangeCustomDomain(customDomain))
}
if wm.CustomText != customText {
changes = append(changes, features.ChangeCustomText(customText))
}
if len(changes) == 0 {
return nil, false

View File

@ -7,6 +7,7 @@ import (
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
@ -226,6 +227,18 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
),
),
),
expectFilter(
eventFromEventPusher(
iam.NewCustomTextSetEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
domain.InitCodeMessageType,
domain.MessageSubject,
"text",
language.English,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
@ -253,6 +266,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
LabelPolicyPrivateLabel: false,
LabelPolicyWatermark: false,
CustomDomain: false,
CustomText: false,
},
},
res: res{
@ -373,6 +387,18 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
),
),
),
expectFilter(
eventFromEventPusher(
iam.NewCustomTextSetEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
domain.InitCodeMessageType,
domain.MessageSubject,
"text",
language.English,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
@ -534,6 +560,18 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
),
),
),
expectFilter(
eventFromEventPusher(
iam.NewCustomTextSetEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
domain.InitCodeMessageType,
domain.MessageSubject,
"text",
language.English,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
@ -705,6 +743,18 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
),
),
),
expectFilter(
eventFromEventPusher(
iam.NewCustomTextSetEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
domain.InitCodeMessageType,
domain.MessageSubject,
"text",
language.English,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
@ -931,6 +981,18 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
),
),
),
expectFilter(
eventFromEventPusher(
org.NewCustomTextSetEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
domain.InitCodeMessageType,
domain.MessageSubject,
"text",
language.English,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
@ -951,6 +1013,9 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
eventFromEventPusher(
org.NewLabelPolicyRemovedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate),
),
eventFromEventPusher(
org.NewCustomTextTemplateRemovedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate, domain.InitCodeMessageType, language.English),
),
eventFromEventPusher(
newFeaturesSetEvent(context.Background(), "org1", "Test", domain.FeaturesStateActive, time.Hour),
),
@ -1144,6 +1209,18 @@ func TestCommandSide_RemoveOrgFeatures(t *testing.T) {
),
),
),
expectFilter(
eventFromEventPusher(
iam.NewCustomTextSetEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
domain.InitCodeMessageType,
domain.MessageSubject,
"text",
language.English,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(

View File

@ -1,114 +0,0 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/repository/org"
)
func (c *Commands) AddMailText(ctx context.Context, resourceOwner string, mailText *domain.MailText) (*domain.MailText, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-MFiig", "Errors.ResourceOwnerMissing")
}
if !mailText.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-4778u", "Errors.Org.MailText.Invalid")
}
addedPolicy := NewOrgMailTextWriteModel(resourceOwner, mailText.MailTextType, mailText.Language)
err := c.eventstore.FilterToQueryReducer(ctx, addedPolicy)
if err != nil {
return nil, err
}
if addedPolicy.State == domain.PolicyStateActive {
return nil, caos_errs.ThrowAlreadyExists(nil, "Org-9kufs", "Errors.Org.MailText.AlreadyExists")
}
orgAgg := OrgAggregateFromWriteModel(&addedPolicy.MailTextWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(
ctx,
org.NewMailTextAddedEvent(
ctx,
orgAgg,
mailText.MailTextType,
mailText.Language,
mailText.Title,
mailText.PreHeader,
mailText.Subject,
mailText.Greeting,
mailText.Text,
mailText.ButtonText))
if err != nil {
return nil, err
}
err = AppendAndReduce(addedPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToMailText(&addedPolicy.MailTextWriteModel), nil
}
func (c *Commands) ChangeMailText(ctx context.Context, resourceOwner string, mailText *domain.MailText) (*domain.MailText, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-NFus3", "Errors.ResourceOwnerMissing")
}
if !mailText.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-3m9fs", "Errors.Org.MailText.Invalid")
}
existingPolicy := NewOrgMailTextWriteModel(resourceOwner, mailText.MailTextType, mailText.Language)
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "Org-3n8fM", "Errors.Org.MailText.NotFound")
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.MailTextWriteModel.WriteModel)
changedEvent, hasChanged := existingPolicy.NewChangedEvent(
ctx,
orgAgg,
mailText.MailTextType,
mailText.Language,
mailText.Title,
mailText.PreHeader,
mailText.Subject,
mailText.Greeting,
mailText.Text,
mailText.ButtonText)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-2n9fs", "Errors.Org.MailText.NotChanged")
}
pushedEvents, err := c.eventstore.PushEvents(ctx, changedEvent)
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToMailText(&existingPolicy.MailTextWriteModel), nil
}
func (c *Commands) RemoveMailText(ctx context.Context, resourceOwner, mailTextType, language string) error {
if resourceOwner == "" {
return caos_errs.ThrowInvalidArgument(nil, "Org-2N7fd", "Errors.ResourceOwnerMissing")
}
if mailTextType == "" || language == "" {
return caos_errs.ThrowInvalidArgument(nil, "Org-N8fsf", "Errors.Org.MailText.Invalid")
}
existingPolicy := NewOrgMailTextWriteModel(resourceOwner, mailTextType, language)
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
if err != nil {
return err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return caos_errs.ThrowNotFound(nil, "Org-3b8Jf", "Errors.Org.MailText.NotFound")
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.WriteModel)
_, err = c.eventstore.PushEvents(ctx, org.NewMailTextRemovedEvent(ctx, orgAgg, mailTextType, language))
return err
}

View File

@ -1,95 +0,0 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/org"
"github.com/caos/zitadel/internal/repository/policy"
)
type OrgMailTextWriteModel struct {
MailTextWriteModel
}
func NewOrgMailTextWriteModel(orgID, mailTextType, language string) *OrgMailTextWriteModel {
return &OrgMailTextWriteModel{
MailTextWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: orgID,
ResourceOwner: orgID,
},
MailTextType: mailTextType,
Language: language,
},
}
}
func (wm *OrgMailTextWriteModel) AppendEvents(events ...eventstore.EventReader) {
for _, event := range events {
switch e := event.(type) {
case *org.MailTextAddedEvent:
wm.MailTextWriteModel.AppendEvents(&e.MailTextAddedEvent)
case *org.MailTextChangedEvent:
wm.MailTextWriteModel.AppendEvents(&e.MailTextChangedEvent)
case *org.MailTextRemovedEvent:
wm.MailTextWriteModel.AppendEvents(&e.MailTextRemovedEvent)
}
}
}
func (wm *OrgMailTextWriteModel) Reduce() error {
return wm.MailTextWriteModel.Reduce()
}
func (wm *OrgMailTextWriteModel) Query() *eventstore.SearchQueryBuilder {
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, org.AggregateType).
AggregateIDs(wm.MailTextWriteModel.AggregateID).
EventTypes(org.MailTextAddedEventType,
org.MailTextChangedEventType,
org.MailTextRemovedEventType)
if wm.ResourceOwner != "" {
query.ResourceOwner(wm.ResourceOwner)
}
return query
}
func (wm *OrgMailTextWriteModel) NewChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
mailTextType,
language,
title,
preHeader,
subject,
greeting,
text,
buttonText string,
) (*org.MailTextChangedEvent, bool) {
changes := make([]policy.MailTextChanges, 0)
if wm.Title != title {
changes = append(changes, policy.ChangeTitle(title))
}
if wm.PreHeader != preHeader {
changes = append(changes, policy.ChangePreHeader(preHeader))
}
if wm.Subject != subject {
changes = append(changes, policy.ChangeSubject(subject))
}
if wm.Greeting != greeting {
changes = append(changes, policy.ChangeGreeting(greeting))
}
if wm.Text != text {
changes = append(changes, policy.ChangeText(text))
}
if wm.ButtonText != buttonText {
changes = append(changes, policy.ChangeButtonText(buttonText))
}
if len(changes) == 0 {
return nil, false
}
changedEvent, err := org.NewMailTextChangedEvent(ctx, aggregate, mailTextType, language, changes)
if err != nil {
return nil, false
}
return changedEvent, true
}

View File

@ -1,563 +0,0 @@
package command
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/org"
"github.com/caos/zitadel/internal/repository/policy"
)
func TestCommandSide_AddMailText(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
policy *domain.MailText
}
type res struct {
want *domain.MailText
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "org id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.MailText{
MailTextType: "mail-text-type",
Language: "de",
Title: "title",
PreHeader: "pre-header",
Subject: "subject",
Greeting: "greeting",
Text: "text",
ButtonText: "button-text",
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "mail text already existing, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewMailTextAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"mail-text-type",
"de",
"title",
"pre-header",
"subject",
"greeting",
"text",
"button-text",
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.MailText{
MailTextType: "mail-text-type",
Language: "de",
Title: "title",
PreHeader: "pre-header",
Subject: "subject",
Greeting: "greeting",
Text: "text",
ButtonText: "button-text",
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "mail text already existing, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewMailTextAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"mail-text-type",
"de",
"title",
"pre-header",
"subject",
"greeting",
"text",
"button-text",
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.MailText{
MailTextType: "mail-text-type",
Language: "de",
Title: "title",
PreHeader: "pre-header",
Subject: "subject",
Greeting: "greeting",
Text: "text",
ButtonText: "button-text",
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "add policy,ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
org.NewMailTextAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"mail-text-type",
"de",
"title",
"pre-header",
"subject",
"greeting",
"text",
"button-text",
),
),
},
uniqueConstraintsFromEventConstraint(policy.NewAddMailTextUniqueConstraint("org1", "mail-text-type", "de")),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.MailText{
MailTextType: "mail-text-type",
Language: "de",
Title: "title",
PreHeader: "pre-header",
Subject: "subject",
Greeting: "greeting",
Text: "text",
ButtonText: "button-text",
},
},
res: res{
want: &domain.MailText{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
ResourceOwner: "org1",
},
MailTextType: "mail-text-type",
Language: "de",
Title: "title",
PreHeader: "pre-header",
Subject: "subject",
Greeting: "greeting",
Text: "text",
ButtonText: "button-text",
State: domain.PolicyStateActive,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddMailText(tt.args.ctx, tt.args.orgID, tt.args.policy)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_ChangeMailText(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
policy *domain.MailText
}
type res struct {
want *domain.MailText
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "org id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.MailText{
MailTextType: "mail-text-type",
Language: "de",
Title: "title",
PreHeader: "pre-header",
Subject: "subject",
Greeting: "greeting",
Text: "text",
ButtonText: "button-text",
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "mailtext invalid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.MailText{},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "mail template not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.MailText{
MailTextType: "mail-text-type",
Language: "de",
Title: "title",
PreHeader: "pre-header",
Subject: "subject",
Greeting: "greeting",
Text: "text",
ButtonText: "button-text",
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "no changes, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewMailTextAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"mail-text-type",
"de",
"title",
"pre-header",
"subject",
"greeting",
"text",
"button-text",
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.MailText{
MailTextType: "mail-text-type",
Language: "de",
Title: "title",
PreHeader: "pre-header",
Subject: "subject",
Greeting: "greeting",
Text: "text",
ButtonText: "button-text",
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "change, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewMailTextAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"mail-text-type",
"de",
"title",
"pre-header",
"subject",
"greeting",
"text",
"button-text",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newMailTextChangedEvent(
context.Background(),
"org1",
"mail-text-type",
"de",
"title-change",
"pre-header-change",
"subject-change",
"greeting-change",
"text-change",
"button-text-change"),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.MailText{
MailTextType: "mail-text-type",
Language: "de",
Title: "title-change",
PreHeader: "pre-header-change",
Subject: "subject-change",
Greeting: "greeting-change",
Text: "text-change",
ButtonText: "button-text-change",
},
},
res: res{
want: &domain.MailText{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
ResourceOwner: "org1",
},
MailTextType: "mail-text-type",
Language: "de",
Title: "title-change",
PreHeader: "pre-header-change",
Subject: "subject-change",
Greeting: "greeting-change",
Text: "text-change",
ButtonText: "button-text-change",
State: domain.PolicyStateActive,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangeMailText(tt.args.ctx, tt.args.orgID, tt.args.policy)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_RemoveMailText(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
mailTextType string
language string
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "org id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "policy not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
mailTextType: "mail-text-type",
language: "de",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "remove, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewMailTextAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"mail-text-type",
"de",
"title",
"pre-header",
"subject",
"greeting",
"text",
"button-text",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
org.NewMailTextRemovedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"mail-text-type",
"de"),
),
},
uniqueConstraintsFromEventConstraint(policy.NewRemoveMailTextUniqueConstraint("org1", "mail-text-type", "de")),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
mailTextType: "mail-text-type",
language: "de",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
err := r.RemoveMailText(tt.args.ctx, tt.args.orgID, tt.args.mailTextType, tt.args.language)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func newMailTextChangedEvent(ctx context.Context, orgID, mailTextType, language, title, preHeader, subject, greeting, text, buttonText string) *org.MailTextChangedEvent {
event, _ := org.NewMailTextChangedEvent(ctx,
&org.NewAggregate(orgID, orgID).Aggregate,
mailTextType,
language,
[]policy.MailTextChanges{
policy.ChangeTitle(title),
policy.ChangePreHeader(preHeader),
policy.ChangeSubject(subject),
policy.ChangeGreeting(greeting),
policy.ChangeText(text),
policy.ChangeButtonText(buttonText),
},
)
return event
}

View File

@ -65,7 +65,7 @@ func (c *Commands) ChangePasswordAgePolicy(ctx context.Context, resourceOwner st
func (c *Commands) RemovePasswordAgePolicy(ctx context.Context, orgID string) (*domain.ObjectDetails, error) {
if orgID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-2N8fs", "Errors.ResourceOwnerMissing")
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-M58wd", "Errors.ResourceOwnerMissing")
}
existingPolicy := NewOrgPasswordAgePolicyWriteModel(orgID)
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)

View File

@ -1,65 +0,0 @@
package command
import (
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/policy"
)
type MailTextWriteModel struct {
eventstore.WriteModel
MailTextType string
Language string
Title string
PreHeader string
Subject string
Greeting string
Text string
ButtonText string
State domain.PolicyState
}
func (wm *MailTextWriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *policy.MailTextAddedEvent:
if wm.MailTextType != e.MailTextType || wm.Language != e.Language {
continue
}
wm.Title = e.Title
wm.PreHeader = e.PreHeader
wm.Subject = e.Subject
wm.Greeting = e.Greeting
wm.Text = e.Text
wm.ButtonText = e.ButtonText
wm.State = domain.PolicyStateActive
case *policy.MailTextChangedEvent:
if wm.MailTextType != e.MailTextType || wm.Language != e.Language {
continue
}
if e.Title != nil {
wm.Title = *e.Title
}
if e.PreHeader != nil {
wm.PreHeader = *e.PreHeader
}
if e.Subject != nil {
wm.Subject = *e.Subject
}
if e.Greeting != nil {
wm.Greeting = *e.Greeting
}
if e.Text != nil {
wm.Text = *e.Text
}
if e.ButtonText != nil {
wm.ButtonText = *e.ButtonText
}
case *policy.MailTextRemovedEvent:
wm.State = domain.PolicyStateRemoved
}
}
return wm.WriteModel.Reduce()
}

View File

@ -2,14 +2,15 @@ package command
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
)
type Step10 struct {
DefaultMailTemplate domain.MailTemplate
DefaultMailTexts []domain.MailText
}
func (s *Step10) Step() domain.Step {
@ -30,13 +31,6 @@ func (c *Commands) SetupStep10(ctx context.Context, step *Step10) error {
events := []eventstore.EventPusher{
mailTemplateEvent,
}
for _, text := range step.DefaultMailTexts {
defaultTextEvent, err := c.addDefaultMailText(ctx, iamAgg, NewIAMMailTextWriteModel(text.MailTextType, text.Language), &text)
if err != nil {
return nil, err
}
events = append(events, defaultTextEvent)
}
logging.Log("SETUP-3N9fs").Info("default mail template/text set up")
return events, nil
}

View File

@ -0,0 +1,39 @@
package command
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
)
type Step16 struct {
DefaultMessageTexts []domain.CustomMessageText
}
func (s *Step16) Step() domain.Step {
return domain.Step16
}
func (s *Step16) execute(ctx context.Context, commandSide *Commands) error {
return commandSide.SetupStep16(ctx, s)
}
func (c *Commands) SetupStep16(ctx context.Context, step *Step16) error {
fn := func(iam *IAMWriteModel) ([]eventstore.EventPusher, error) {
iamAgg := IAMAggregateFromWriteModel(&iam.WriteModel)
events := make([]eventstore.EventPusher, 0)
for _, text := range step.DefaultMessageTexts {
mailEvents, _, err := c.setDefaultMessageText(ctx, iamAgg, &text)
if err != nil {
return nil, err
}
events = append(events, mailEvents...)
}
logging.Log("SETUP-4k0LL").Info("default message text set up")
return events, nil
}
return c.setup(ctx, step, fn)
}

View File

@ -0,0 +1,42 @@
package domain
import (
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/eventstore/v1/models"
)
const (
InitCodeMessageType = "InitCode"
PasswordResetMessageType = "PasswordReset"
VerifyEmailMessageType = "VerifyEmail"
VerifyPhoneMessageType = "VerifyPhone"
DomainClaimedMessageType = "DomainClaimed"
MessageTitle = "Title"
MessagePreHeader = "PreHeader"
MessageSubject = "Subject"
MessageGreeting = "Greeting"
MessageText = "Text"
MessageButtonText = "ButtonText"
MessageFooterText = "FooterText"
)
type CustomMessageText struct {
models.ObjectRoot
State PolicyState
Default bool
MessageTextType string
Language language.Tag
Title string
PreHeader string
Subject string
Greeting string
Text string
ButtonText string
FooterText string
}
func (m *CustomMessageText) IsValid() bool {
return m.MessageTextType != "" && m.Language != language.Und
}

View File

@ -0,0 +1,32 @@
package domain
import (
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/eventstore/v1/models"
)
type CustomText struct {
models.ObjectRoot
State CustomTextState
Default bool
Template string
Key string
Language language.Tag
Text string
}
type CustomTextState int32
const (
CustomTextStateUnspecified CustomTextState = iota
CustomTextStateActive
CustomTextStateRemoved
customTextStateCount
)
func (m *CustomText) IsValid() bool {
return m.Key != "" && m.Language != language.Und && m.Text != ""
}

View File

@ -18,6 +18,7 @@ const (
FeatureLabelPolicy = "label_policy"
FeatureLabelPolicyPrivateLabel = FeatureLabelPolicy + ".private_label"
FeatureLabelPolicyWatermark = FeatureLabelPolicy + ".watermark"
FeatureCustomText = "custom_text"
FeatureCustomDomain = "custom_domain"
)
@ -41,6 +42,7 @@ type Features struct {
LabelPolicyPrivateLabel bool
LabelPolicyWatermark bool
CustomDomain bool
CustomText bool
}
type FeaturesState int32

View File

@ -1,22 +0,0 @@
package domain
import "github.com/caos/zitadel/internal/eventstore/v1/models"
type MailText struct {
models.ObjectRoot
State PolicyState
Default bool
MailTextType string
Language string
Title string
PreHeader string
Subject string
Greeting string
Text string
ButtonText string
}
func (m *MailText) IsValid() bool {
return m.MailTextType != "" && m.Language != "" && m.Title != "" && m.PreHeader != "" && m.Subject != "" && m.Greeting != "" && m.Text != "" && m.ButtonText != ""
}

View File

@ -18,6 +18,7 @@ const (
Step13
Step14
Step15
Step16
//StepCount marks the the length of possible steps (StepCount-1 == last possible step)
StepCount
)

View File

@ -28,6 +28,7 @@ type FeaturesView struct {
LabelPolicyPrivateLabel bool
LabelPolicyWatermark bool
CustomDomain bool
CustomText bool
}
func (f *FeaturesView) FeatureList() []string {
@ -62,6 +63,9 @@ func (f *FeaturesView) FeatureList() []string {
if f.CustomDomain {
list = append(list, domain.FeatureCustomDomain)
}
if f.CustomText {
list = append(list, domain.FeatureCustomText)
}
return list
}

View File

@ -42,6 +42,7 @@ type FeaturesView struct {
LabelPolicyPrivateLabel bool `json:"labelPolicyPrivateLabel" gorm:"column:label_policy_private_label"`
LabelPolicyWatermark bool `json:"labelPolicyWatermark" gorm:"column:label_policy_watermark"`
CustomDomain bool `json:"customDomain" gorm:"column:custom_domain"`
CustomText bool `json:"customText" gorm:"column:custom_text"`
}
func FeaturesToModel(features *FeaturesView) *features_model.FeaturesView {
@ -66,6 +67,7 @@ func FeaturesToModel(features *FeaturesView) *features_model.FeaturesView {
LabelPolicyPrivateLabel: features.LabelPolicyPrivateLabel,
LabelPolicyWatermark: features.LabelPolicyWatermark,
CustomDomain: features.CustomDomain,
CustomText: features.CustomText,
}
}

View File

@ -21,6 +21,7 @@ type MailText struct {
Greeting string
Text string
ButtonText string
FooterText string
}
func (p *MailText) IsValid() bool {

View File

@ -1,59 +0,0 @@
package model
import (
"github.com/caos/zitadel/internal/domain"
"time"
)
type MailTextsView struct {
Texts []*MailTextView
Default bool
}
type MailTextView struct {
AggregateID string
MailTextType string
Language string
Title string
PreHeader string
Subject string
Greeting string
Text string
ButtonText string
Default bool
CreationDate time.Time
ChangeDate time.Time
Sequence uint64
}
type MailTextSearchRequest struct {
Offset uint64
Limit uint64
SortingColumn MailTextSearchKey
Asc bool
Queries []*MailTextSearchQuery
}
type MailTextSearchKey int32
const (
MailTextSearchKeyUnspecified MailTextSearchKey = iota
MailTextSearchKeyAggregateID
MailTextSearchKeyMailTextType
MailTextSearchKeyLanguage
)
type MailTextSearchQuery struct {
Key MailTextSearchKey
Method domain.SearchMethod
Value interface{}
}
type MailTextSearchResponse struct {
Offset uint64
Limit uint64
TotalResult uint64
Result []*MailTextView
Sequence uint64
Timestamp time.Time
}

View File

@ -0,0 +1,63 @@
package model
import (
"time"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/domain"
)
type MessageTextsView struct {
Texts []*MessageTextView
Default bool
}
type MessageTextView struct {
AggregateID string
MessageTextType string
Language language.Tag
Title string
PreHeader string
Subject string
Greeting string
Text string
ButtonText string
FooterText string
Default bool
CreationDate time.Time
ChangeDate time.Time
Sequence uint64
}
type MessageTextSearchRequest struct {
Offset uint64
Limit uint64
SortingColumn MessageTextSearchKey
Asc bool
Queries []*MessageTextSearchQuery
}
type MessageTextSearchKey int32
const (
MessageTextSearchKeyUnspecified MessageTextSearchKey = iota
MessageTextSearchKeyAggregateID
MessageTextSearchKeyMessageTextType
MessageTextSearchKeyLanguage
)
type MessageTextSearchQuery struct {
Key MessageTextSearchKey
Method domain.SearchMethod
Value interface{}
}
type MessageTextSearchResponse struct {
Offset uint64
Limit uint64
TotalResult uint64
Result []*MessageTextView
Sequence uint64
Timestamp time.Time
}

View File

@ -33,64 +33,23 @@ type IAM struct {
DefaultLoginPolicy *LoginPolicy `json:"-"`
DefaultLabelPolicy *LabelPolicy `json:"-"`
DefaultMailTemplate *MailTemplate `json:"-"`
DefaultMailTexts []*MailText `json:"-"`
DefaultOrgIAMPolicy *OrgIAMPolicy `json:"-"`
DefaultPasswordComplexityPolicy *PasswordComplexityPolicy `json:"-"`
DefaultPasswordAgePolicy *PasswordAgePolicy `json:"-"`
DefaultPasswordLockoutPolicy *PasswordLockoutPolicy `json:"-"`
}
func IAMFromModel(iam *model.IAM) *IAM {
members := IAMMembersFromModel(iam.Members)
idps := IDPConfigsFromModel(iam.IDPs)
mailTexts := MailTextsFromModel(iam.DefaultMailTexts)
converted := &IAM{
ObjectRoot: iam.ObjectRoot,
SetUpStarted: Step(iam.SetUpStarted),
SetUpDone: Step(iam.SetUpDone),
GlobalOrgID: iam.GlobalOrgID,
IAMProjectID: iam.IAMProjectID,
Members: members,
IDPs: idps,
DefaultMailTexts: mailTexts,
}
if iam.DefaultLoginPolicy != nil {
converted.DefaultLoginPolicy = LoginPolicyFromModel(iam.DefaultLoginPolicy)
}
if iam.DefaultLabelPolicy != nil {
converted.DefaultLabelPolicy = LabelPolicyFromModel(iam.DefaultLabelPolicy)
}
if iam.DefaultMailTemplate != nil {
converted.DefaultMailTemplate = MailTemplateFromModel(iam.DefaultMailTemplate)
}
if iam.DefaultPasswordComplexityPolicy != nil {
converted.DefaultPasswordComplexityPolicy = PasswordComplexityPolicyFromModel(iam.DefaultPasswordComplexityPolicy)
}
if iam.DefaultPasswordAgePolicy != nil {
converted.DefaultPasswordAgePolicy = PasswordAgePolicyFromModel(iam.DefaultPasswordAgePolicy)
}
if iam.DefaultPasswordLockoutPolicy != nil {
converted.DefaultPasswordLockoutPolicy = PasswordLockoutPolicyFromModel(iam.DefaultPasswordLockoutPolicy)
}
if iam.DefaultOrgIAMPolicy != nil {
converted.DefaultOrgIAMPolicy = OrgIAMPolicyFromModel(iam.DefaultOrgIAMPolicy)
}
return converted
}
func IAMToModel(iam *IAM) *model.IAM {
members := IAMMembersToModel(iam.Members)
idps := IDPConfigsToModel(iam.IDPs)
mailTexts := MailTextsToModel(iam.DefaultMailTexts)
converted := &model.IAM{
ObjectRoot: iam.ObjectRoot,
SetUpStarted: domain.Step(iam.SetUpStarted),
SetUpDone: domain.Step(iam.SetUpDone),
GlobalOrgID: iam.GlobalOrgID,
IAMProjectID: iam.IAMProjectID,
Members: members,
IDPs: idps,
DefaultMailTexts: mailTexts,
ObjectRoot: iam.ObjectRoot,
SetUpStarted: domain.Step(iam.SetUpStarted),
SetUpDone: domain.Step(iam.SetUpDone),
GlobalOrgID: iam.GlobalOrgID,
IAMProjectID: iam.IAMProjectID,
Members: members,
IDPs: idps,
}
if iam.DefaultLoginPolicy != nil {
converted.DefaultLoginPolicy = LoginPolicyToModel(iam.DefaultLoginPolicy)
@ -199,10 +158,6 @@ func (i *IAM) AppendEvent(event *es_models.Event) (err error) {
return i.appendAddMailTemplateEvent(event)
case MailTemplateChanged:
return i.appendChangeMailTemplateEvent(event)
case MailTextAdded:
return i.appendAddMailTextEvent(event)
case MailTextChanged:
return i.appendChangeMailTextEvent(event)
case PasswordComplexityPolicyAdded:
return i.appendAddPasswordComplexityPolicyEvent(event)
case PasswordComplexityPolicyChanged:

View File

@ -110,43 +110,6 @@ func (p *MailText) Changes(changed *MailText) map[string]interface{} {
return changes
}
func (i *IAM) appendAddMailTextEvent(event *es_models.Event) error {
mailText := &MailText{}
err := mailText.SetDataLabel(event)
if err != nil {
return err
}
mailText.ObjectRoot.CreationDate = event.CreationDate
i.DefaultMailTexts = append(i.DefaultMailTexts, mailText)
return nil
}
func (i *IAM) appendChangeMailTextEvent(event *es_models.Event) error {
mailText := &MailText{}
err := mailText.SetDataLabel(event)
if err != nil {
return err
}
if n, m := GetMailText(i.DefaultMailTexts, mailText.MailTextType, mailText.Language); m != nil {
i.DefaultMailTexts[n] = mailText
}
return nil
}
func (i *IAM) appendRemoveMailTextEvent(event *es_models.Event) error {
mailText := &MailText{}
err := mailText.SetDataLabel(event)
if err != nil {
return err
}
if n, m := GetMailText(i.DefaultMailTexts, mailText.MailTextType, mailText.Language); m != nil {
i.DefaultMailTexts[n] = i.DefaultMailTexts[len(i.DefaultMailTexts)-1]
i.DefaultMailTexts[len(i.DefaultMailTexts)-1] = nil
i.DefaultMailTexts = i.DefaultMailTexts[:len(i.DefaultMailTexts)-1]
}
return nil
}
func (p *MailText) SetDataLabel(event *es_models.Event) error {
err := json.Unmarshal(event.Data, p)
if err != nil {

View File

@ -1,134 +0,0 @@
package model
import (
"encoding/json"
"testing"
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
)
func TestAppendAddMailTextEvent(t *testing.T) {
type args struct {
iam *IAM
mailText *MailText
event *es_models.Event
}
tests := []struct {
name string
args args
result *IAM
}{
{
name: "append add mailText event",
args: args{
iam: &IAM{},
mailText: &MailText{
MailTextType: "PasswordReset",
Language: "DE"},
event: &es_models.Event{},
},
result: &IAM{DefaultMailTexts: []*MailText{&MailText{
MailTextType: "PasswordReset",
Language: "DE"}}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.mailText != nil {
data, _ := json.Marshal(tt.args.mailText)
tt.args.event.Data = data
}
tt.args.iam.appendAddMailTextEvent(tt.args.event)
if len(tt.args.iam.DefaultMailTexts) != 1 {
t.Errorf("got wrong result should have one mailText actual: %v ", len(tt.args.iam.DefaultMailTexts))
}
if tt.args.iam.DefaultMailTexts[0] == tt.result.DefaultMailTexts[0] {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.DefaultMailTexts[0], tt.args.iam.DefaultMailTexts[0])
}
})
}
}
func TestAppendChangeMailTextEvent(t *testing.T) {
type args struct {
iam *IAM
mailText *MailText
event *es_models.Event
}
tests := []struct {
name string
args args
result *IAM
}{
{
name: "append change mailText event",
args: args{
iam: &IAM{DefaultMailTexts: []*MailText{&MailText{
MailTextType: "PasswordReset",
Language: "DE"}}},
mailText: &MailText{
MailTextType: "ChangedPasswordReset",
Language: "DE"},
event: &es_models.Event{},
},
result: &IAM{DefaultMailTexts: []*MailText{&MailText{
MailTextType: "PasswordReset",
Language: "ChangedDE"}}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.mailText != nil {
data, _ := json.Marshal(tt.args.mailText)
tt.args.event.Data = data
}
tt.args.iam.appendChangeMailTextEvent(tt.args.event)
if len(tt.args.iam.DefaultMailTexts) != 1 {
t.Errorf("got wrong result should have one mailText actual: %v ", len(tt.args.iam.DefaultMailTexts))
}
if tt.args.iam.DefaultMailTexts[0] == tt.result.DefaultMailTexts[0] {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.DefaultMailTexts[0], tt.args.iam.DefaultMailTexts[0])
}
})
}
}
func TestAppendRemoveMailTextEvent(t *testing.T) {
type args struct {
iam *IAM
mailText *MailText
event *es_models.Event
}
tests := []struct {
name string
args args
result *IAM
}{
{
name: "append remove mailText event",
args: args{
iam: &IAM{DefaultMailTexts: []*MailText{&MailText{
MailTextType: "PasswordReset",
Language: "DE",
Subject: "Subject"}}},
mailText: &MailText{
MailTextType: "PasswordReset",
Language: "DE"},
event: &es_models.Event{},
},
result: &IAM{DefaultMailTexts: []*MailText{}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.mailText != nil {
data, _ := json.Marshal(tt.args.mailText)
tt.args.event.Data = data
}
tt.args.iam.appendRemoveMailTextEvent(tt.args.event)
if len(tt.args.iam.DefaultMailTexts) != 0 {
t.Errorf("got wrong result should have no mailText actual: %v ", len(tt.args.iam.DefaultMailTexts))
}
})
}
}

View File

@ -54,8 +54,9 @@ const (
MailTemplateAdded models.EventType = "iam.mail.template.added"
MailTemplateChanged models.EventType = "iam.mail.template.changed"
MailTextAdded models.EventType = "iam.mail.text.added"
MailTextChanged models.EventType = "iam.mail.text.changed"
CustomTextSet models.EventType = "iam.customtext.set"
CustomTextRemoved models.EventType = "iam.customtext.removed"
PasswordComplexityPolicyAdded models.EventType = "iam.policy.password.complexity.added"
PasswordComplexityPolicyChanged models.EventType = "iam.policy.password.complexity.changed"

View File

@ -1,54 +0,0 @@
package view
import (
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
iam_model "github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/internal/iam/repository/view/model"
"github.com/caos/zitadel/internal/view/repository"
"github.com/jinzhu/gorm"
"strings"
)
func GetMailTexts(db *gorm.DB, table string, aggregateID string) ([]*model.MailTextView, error) {
texts := make([]*model.MailTextView, 0)
queries := []*iam_model.MailTextSearchQuery{
{
Key: iam_model.MailTextSearchKeyAggregateID,
Value: aggregateID,
Method: domain.SearchMethodEquals,
},
}
query := repository.PrepareSearchQuery(table, model.MailTextSearchRequest{Queries: queries})
_, err := query(db, &texts)
if err != nil {
return nil, err
}
return texts, nil
}
func GetMailTextByIDs(db *gorm.DB, table, aggregateID string, textType string, language string) (*model.MailTextView, error) {
mailText := new(model.MailTextView)
aggregateIDQuery := &model.MailTextSearchQuery{Key: iam_model.MailTextSearchKeyAggregateID, Value: aggregateID, Method: domain.SearchMethodEquals}
textTypeQuery := &model.MailTextSearchQuery{Key: iam_model.MailTextSearchKeyMailTextType, Value: textType, Method: domain.SearchMethodEquals}
languageQuery := &model.MailTextSearchQuery{Key: iam_model.MailTextSearchKeyLanguage, Value: strings.ToUpper(language), Method: domain.SearchMethodEquals}
query := repository.PrepareGetByQuery(table, aggregateIDQuery, textTypeQuery, languageQuery)
err := query(db, mailText)
if caos_errs.IsNotFound(err) {
return nil, caos_errs.ThrowNotFound(nil, "VIEW-IiJjm", "Errors.IAM.MailText.NotExisting")
}
return mailText, err
}
func PutMailText(db *gorm.DB, table string, mailText *model.MailTextView) error {
save := repository.PrepareSave(table)
return save(db, mailText)
}
func DeleteMailText(db *gorm.DB, table, aggregateID string, textType string, language string) error {
aggregateIDSearch := repository.Key{Key: model.MailTextSearchKey(iam_model.MailTextSearchKeyAggregateID), Value: aggregateID}
textTypeSearch := repository.Key{Key: model.MailTextSearchKey(iam_model.MailTextSearchKeyMailTextType), Value: textType}
languageSearch := repository.Key{Key: model.MailTextSearchKey(iam_model.MailTextSearchKeyLanguage), Value: language}
delete := repository.PrepareDeleteByKeys(table, aggregateIDSearch, textTypeSearch, languageSearch)
return delete(db)
}

View File

@ -0,0 +1,54 @@
package view
import (
"github.com/jinzhu/gorm"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
iam_model "github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/internal/iam/repository/view/model"
"github.com/caos/zitadel/internal/view/repository"
)
func GetMessageTexts(db *gorm.DB, table string, aggregateID string) ([]*model.MessageTextView, error) {
texts := make([]*model.MessageTextView, 0)
queries := []*iam_model.MessageTextSearchQuery{
{
Key: iam_model.MessageTextSearchKeyAggregateID,
Value: aggregateID,
Method: domain.SearchMethodEquals,
},
}
query := repository.PrepareSearchQuery(table, model.MessageTextSearchRequest{Queries: queries})
_, err := query(db, &texts)
if err != nil {
return nil, err
}
return texts, nil
}
func GetMessageTextByIDs(db *gorm.DB, table, aggregateID, textType, lang string) (*model.MessageTextView, error) {
mailText := new(model.MessageTextView)
aggregateIDQuery := &model.MessageTextSearchQuery{Key: iam_model.MessageTextSearchKeyAggregateID, Value: aggregateID, Method: domain.SearchMethodEquals}
textTypeQuery := &model.MessageTextSearchQuery{Key: iam_model.MessageTextSearchKeyMessageTextType, Value: textType, Method: domain.SearchMethodEquals}
languageQuery := &model.MessageTextSearchQuery{Key: iam_model.MessageTextSearchKeyLanguage, Value: lang, Method: domain.SearchMethodEquals}
query := repository.PrepareGetByQuery(table, aggregateIDQuery, textTypeQuery, languageQuery)
err := query(db, mailText)
if caos_errs.IsNotFound(err) {
return nil, caos_errs.ThrowNotFound(nil, "VIEW-IiJjm", "Errors.IAM.CustomMessageText.NotExisting")
}
return mailText, err
}
func PutMessageText(db *gorm.DB, table string, mailText *model.MessageTextView) error {
save := repository.PrepareSave(table)
return save(db, mailText)
}
func DeleteMessageText(db *gorm.DB, table, aggregateID, textType, lang string) error {
aggregateIDSearch := repository.Key{Key: model.MessageTextSearchKey(iam_model.MessageTextSearchKeyAggregateID), Value: aggregateID}
textTypeSearch := repository.Key{Key: model.MessageTextSearchKey(iam_model.MessageTextSearchKeyMessageTextType), Value: textType}
languageSearch := repository.Key{Key: model.MessageTextSearchKey(iam_model.MessageTextSearchKeyLanguage), Value: lang}
delete := repository.PrepareDeleteByKeys(table, aggregateIDSearch, textTypeSearch, languageSearch)
return delete(db)
}

View File

@ -1,117 +0,0 @@
package model
import (
"encoding/json"
"time"
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
"github.com/caos/logging"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/iam/model"
)
const (
MailTextKeyAggregateID = "aggregate_id"
MailTextKeyMailTextType = "mail_text_type"
MailTextKeyLanguage = "language"
)
type MailTextView struct {
AggregateID string `json:"-" gorm:"column:aggregate_id;primary_key"`
CreationDate time.Time `json:"-" gorm:"column:creation_date"`
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
State int32 `json:"-" gorm:"column:mail_text_state"`
MailTextType string `json:"mailTextType" gorm:"column:mail_text_type;primary_key"`
Language string `json:"language" gorm:"column:language;primary_key"`
Title string `json:"title" gorm:"column:title"`
PreHeader string `json:"preHeader" gorm:"column:pre_header"`
Subject string `json:"subject" gorm:"column:subject"`
Greeting string `json:"greeting" gorm:"column:greeting"`
Text string `json:"text" gorm:"column:text"`
ButtonText string `json:"buttonText" gorm:"column:button_text"`
Default bool `json:"-" gorm:"-"`
Sequence uint64 `json:"-" gorm:"column:sequence"`
}
func MailTextViewFromModel(template *model.MailTextView) *MailTextView {
return &MailTextView{
AggregateID: template.AggregateID,
Sequence: template.Sequence,
CreationDate: template.CreationDate,
ChangeDate: template.ChangeDate,
MailTextType: template.MailTextType,
Language: template.Language,
Title: template.Title,
PreHeader: template.PreHeader,
Subject: template.Subject,
Greeting: template.Greeting,
Text: template.Text,
ButtonText: template.ButtonText,
Default: template.Default,
}
}
func MailTextsViewToModel(textsIn []*MailTextView, defaultIn bool) *model.MailTextsView {
return &model.MailTextsView{
Texts: mailTextsViewToModelArr(textsIn, defaultIn),
}
}
func mailTextsViewToModelArr(texts []*MailTextView, defaultIn bool) []*model.MailTextView {
result := make([]*model.MailTextView, len(texts))
for i, r := range texts {
r.Default = defaultIn
result[i] = MailTextViewToModel(r)
}
return result
}
func MailTextViewToModel(template *MailTextView) *model.MailTextView {
return &model.MailTextView{
AggregateID: template.AggregateID,
Sequence: template.Sequence,
CreationDate: template.CreationDate,
ChangeDate: template.ChangeDate,
MailTextType: template.MailTextType,
Language: template.Language,
Title: template.Title,
PreHeader: template.PreHeader,
Subject: template.Subject,
Greeting: template.Greeting,
Text: template.Text,
ButtonText: template.ButtonText,
Default: template.Default,
}
}
func (i *MailTextView) AppendEvent(event *models.Event) (err error) {
i.Sequence = event.Sequence
switch event.Type {
case es_model.MailTextAdded, org_es_model.MailTextAdded:
i.setRootData(event)
i.CreationDate = event.CreationDate
err = i.SetData(event)
case es_model.MailTextChanged, org_es_model.MailTextChanged:
i.ChangeDate = event.CreationDate
err = i.SetData(event)
}
return err
}
func (r *MailTextView) setRootData(event *models.Event) {
r.AggregateID = event.AggregateID
}
func (r *MailTextView) SetData(event *models.Event) error {
if err := json.Unmarshal(event.Data, r); err != nil {
logging.Log("MODEL-UFqAG").WithError(err).Error("could not unmarshal event data")
return caos_errs.ThrowInternal(err, "MODEL-5CVaR", "Could not unmarshal data")
}
return nil
}

View File

@ -1,63 +0,0 @@
package model
import (
"github.com/caos/zitadel/internal/domain"
iam_model "github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/internal/view/repository"
)
type MailTextSearchRequest iam_model.MailTextSearchRequest
type MailTextSearchQuery iam_model.MailTextSearchQuery
type MailTextSearchKey iam_model.MailTextSearchKey
func (req MailTextSearchRequest) GetLimit() uint64 {
return req.Limit
}
func (req MailTextSearchRequest) GetOffset() uint64 {
return req.Offset
}
func (req MailTextSearchRequest) GetSortingColumn() repository.ColumnKey {
if req.SortingColumn == iam_model.MailTextSearchKeyUnspecified {
return nil
}
return MailTextSearchKey(req.SortingColumn)
}
func (req MailTextSearchRequest) GetAsc() bool {
return req.Asc
}
func (req MailTextSearchRequest) GetQueries() []repository.SearchQuery {
result := make([]repository.SearchQuery, len(req.Queries))
for i, q := range req.Queries {
result[i] = MailTextSearchQuery{Key: q.Key, Value: q.Value, Method: q.Method}
}
return result
}
func (req MailTextSearchQuery) GetKey() repository.ColumnKey {
return MailTextSearchKey(req.Key)
}
func (req MailTextSearchQuery) GetMethod() domain.SearchMethod {
return req.Method
}
func (req MailTextSearchQuery) GetValue() interface{} {
return req.Value
}
func (key MailTextSearchKey) ToColumnName() string {
switch iam_model.MailTextSearchKey(key) {
case iam_model.MailTextSearchKeyAggregateID:
return MailTextKeyAggregateID
case iam_model.MailTextSearchKeyMailTextType:
return MailTextKeyMailTextType
case iam_model.MailTextSearchKeyLanguage:
return MailTextKeyLanguage
default:
return ""
}
}

View File

@ -0,0 +1,184 @@
package model
import (
"encoding/json"
"time"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/domain"
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
"github.com/caos/logging"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/iam/model"
)
const (
MessageTextKeyAggregateID = "aggregate_id"
MessageTextKeyMessageTextType = "message_text_type"
MessageTextKeyLanguage = "language"
)
type MessageTextView struct {
AggregateID string `json:"-" gorm:"column:aggregate_id;primary_key"`
CreationDate time.Time `json:"-" gorm:"column:creation_date"`
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
State int32 `json:"-" gorm:"column:message_text_state"`
MessageTextType string `json:"-" gorm:"column:message_text_type;primary_key"`
Language string `json:"-" gorm:"column:language;primary_key"`
Title string `json:"-" gorm:"column:title"`
PreHeader string `json:"-" gorm:"column:pre_header"`
Subject string `json:"-" gorm:"column:subject"`
Greeting string `json:"-" gorm:"column:greeting"`
Text string `json:"-" gorm:"column:text"`
ButtonText string `json:"-" gorm:"column:button_text"`
FooterText string `json:"-" gorm:"column:footer_text"`
Default bool `json:"-" gorm:"-"`
Sequence uint64 `json:"-" gorm:"column:sequence"`
}
func MessageTextViewFromModel(template *model.MessageTextView) *MessageTextView {
return &MessageTextView{
AggregateID: template.AggregateID,
Sequence: template.Sequence,
CreationDate: template.CreationDate,
ChangeDate: template.ChangeDate,
MessageTextType: template.MessageTextType,
Language: template.Language.String(),
Title: template.Title,
PreHeader: template.PreHeader,
Subject: template.Subject,
Greeting: template.Greeting,
Text: template.Text,
ButtonText: template.ButtonText,
FooterText: template.FooterText,
Default: template.Default,
}
}
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 {
r.Default = defaultIn
result[i] = MessageTextViewToModel(r)
}
return result
}
func MessageTextViewToModel(template *MessageTextView) *model.MessageTextView {
lang := language.Make(template.Language)
return &model.MessageTextView{
AggregateID: template.AggregateID,
Sequence: template.Sequence,
CreationDate: template.CreationDate,
ChangeDate: template.ChangeDate,
MessageTextType: template.MessageTextType,
Language: lang,
Title: template.Title,
PreHeader: template.PreHeader,
Subject: template.Subject,
Greeting: template.Greeting,
Text: template.Text,
ButtonText: template.ButtonText,
FooterText: template.FooterText,
Default: template.Default,
}
}
func (i *MessageTextView) AppendEvent(event *models.Event) (err error) {
i.Sequence = event.Sequence
switch event.Type {
case es_model.CustomTextSet, org_es_model.CustomTextSet:
i.setRootData(event)
customText := new(CustomText)
err = customText.SetData(event)
if err != nil {
return err
}
if customText.Key == domain.MessageTitle {
i.Title = customText.Text
}
if customText.Key == domain.MessagePreHeader {
i.PreHeader = customText.Text
}
if customText.Key == domain.MessageSubject {
i.Subject = customText.Text
}
if customText.Key == domain.MessageGreeting {
i.Greeting = customText.Text
}
if customText.Key == domain.MessageText {
i.Text = customText.Text
}
if customText.Key == domain.MessageButtonText {
i.ButtonText = customText.Text
}
if customText.Key == domain.MessageFooterText {
i.FooterText = customText.Text
}
i.ChangeDate = event.CreationDate
case es_model.CustomTextRemoved, org_es_model.CustomTextRemoved:
customText := new(CustomText)
err = customText.SetData(event)
if err != nil {
return err
}
if customText.Key == domain.MessageTitle {
i.Title = ""
}
if customText.Key == domain.MessagePreHeader {
i.PreHeader = ""
}
if customText.Key == domain.MessageSubject {
i.Subject = ""
}
if customText.Key == domain.MessageGreeting {
i.Greeting = ""
}
if customText.Key == domain.MessageText {
i.Text = ""
}
if customText.Key == domain.MessageButtonText {
i.ButtonText = ""
}
if customText.Key == domain.MessageFooterText {
i.FooterText = ""
}
i.ChangeDate = event.CreationDate
case org_es_model.CustomTextMessageRemoved:
i.State = int32(model.PolicyStateRemoved)
}
return err
}
func (r *MessageTextView) setRootData(event *models.Event) {
r.AggregateID = event.AggregateID
}
type CustomText struct {
Template string `json:"template"`
Key string `json:"key"`
Language language.Tag `json:"language"`
Text string `json:"text"`
}
func (r *CustomText) SetData(event *models.Event) error {
if err := json.Unmarshal(event.Data, r); err != nil {
logging.Log("MODEL-3n9fs").WithError(err).Error("could not unmarshal event data")
return caos_errs.ThrowInternal(err, "MODEL-5CVaR", "Could not unmarshal data")
}
return nil
}

View File

@ -0,0 +1,63 @@
package model
import (
"github.com/caos/zitadel/internal/domain"
iam_model "github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/internal/view/repository"
)
type MessageTextSearchRequest iam_model.MessageTextSearchRequest
type MessageTextSearchQuery iam_model.MessageTextSearchQuery
type MessageTextSearchKey iam_model.MessageTextSearchKey
func (req MessageTextSearchRequest) GetLimit() uint64 {
return req.Limit
}
func (req MessageTextSearchRequest) GetOffset() uint64 {
return req.Offset
}
func (req MessageTextSearchRequest) GetSortingColumn() repository.ColumnKey {
if req.SortingColumn == iam_model.MessageTextSearchKeyUnspecified {
return nil
}
return MessageTextSearchKey(req.SortingColumn)
}
func (req MessageTextSearchRequest) GetAsc() bool {
return req.Asc
}
func (req MessageTextSearchRequest) GetQueries() []repository.SearchQuery {
result := make([]repository.SearchQuery, len(req.Queries))
for i, q := range req.Queries {
result[i] = MessageTextSearchQuery{Key: q.Key, Value: q.Value, Method: q.Method}
}
return result
}
func (req MessageTextSearchQuery) GetKey() repository.ColumnKey {
return MessageTextSearchKey(req.Key)
}
func (req MessageTextSearchQuery) GetMethod() domain.SearchMethod {
return req.Method
}
func (req MessageTextSearchQuery) GetValue() interface{} {
return req.Value
}
func (key MessageTextSearchKey) ToColumnName() string {
switch iam_model.MessageTextSearchKey(key) {
case iam_model.MessageTextSearchKeyAggregateID:
return MessageTextKeyAggregateID
case iam_model.MessageTextSearchKeyMessageTextType:
return MessageTextKeyMessageTextType
case iam_model.MessageTextSearchKeyLanguage:
return MessageTextKeyLanguage
default:
return ""
}
}

View File

@ -548,19 +548,19 @@ func (repo *OrgRepository) GetMailTemplate(ctx context.Context) (*iam_model.Mail
return iam_es_model.MailTemplateViewToModel(template), err
}
func (repo *OrgRepository) GetDefaultMailTexts(ctx context.Context) (*iam_model.MailTextsView, error) {
texts, err := repo.View.MailTextsByAggregateID(repo.SystemDefaults.IamID)
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.MailTextsViewToModel(texts, true), err
return iam_es_model.MessageTextsViewToModel(texts, true), err
}
func (repo *OrgRepository) GetMailTexts(ctx context.Context) (*iam_model.MailTextsView, error) {
func (repo *OrgRepository) GetMessageTexts(ctx context.Context) (*iam_model.MessageTextsView, error) {
defaultIn := false
texts, err := repo.View.MailTextsByAggregateID(authz.GetCtxData(ctx).OrgID)
texts, err := repo.View.MessageTextsByAggregateID(authz.GetCtxData(ctx).OrgID)
if errors.IsNotFound(err) || len(texts) == 0 {
texts, err = repo.View.MailTextsByAggregateID(repo.SystemDefaults.IamID)
texts, err = repo.View.MessageTextsByAggregateID(repo.SystemDefaults.IamID)
if err != nil {
return nil, err
}
@ -569,7 +569,31 @@ func (repo *OrgRepository) GetMailTexts(ctx context.Context) (*iam_model.MailTex
if err != nil {
return nil, err
}
return iam_es_model.MailTextsViewToModel(texts, defaultIn), 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
}
return result, nil
}
if err != nil {
return nil, err
}
return iam_es_model.MessageTextViewToModel(text), err
}
func (repo *OrgRepository) getOrgChanges(ctx context.Context, orgID string, lastSequence uint64, limit uint64, sortAscending bool, auditLogRetention time.Duration) (*org_model.OrgChanges, error) {

View File

@ -77,8 +77,8 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
handler{view, bulkLimit, configs.cycleDuration("OrgIAMPolicy"), errorCount, es}),
newMailTemplate(
handler{view, bulkLimit, configs.cycleDuration("MailTemplate"), errorCount, es}),
newMailText(
handler{view, bulkLimit, configs.cycleDuration("MailText"), errorCount, es}),
newMessageText(
handler{view, bulkLimit, configs.cycleDuration("MessageText"), errorCount, es}),
newFeatures(
handler{view, bulkLimit, configs.cycleDuration("Features"), errorCount, es}),
}

View File

@ -1,111 +0,0 @@
package handler
import (
"github.com/caos/logging"
"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 MailText struct {
handler
subscription *v1.Subscription
}
func newMailText(handler handler) *MailText {
h := &MailText{
handler: handler,
}
h.subscribe()
return h
}
func (m *MailText) subscribe() {
m.subscription = m.es.Subscribe(m.AggregateTypes()...)
go func() {
for event := range m.subscription.Events {
query.ReduceEvent(m, event)
}
}()
}
const (
mailTextTable = "management.mail_texts"
)
func (m *MailText) ViewModel() string {
return mailTextTable
}
func (_ *MailText) AggregateTypes() []es_models.AggregateType {
return []es_models.AggregateType{model.OrgAggregate, iam_es_model.IAMAggregate}
}
func (p *MailText) CurrentSequence() (uint64, error) {
sequence, err := p.view.GetLatestMailTextSequence()
if err != nil {
return 0, err
}
return sequence.CurrentSequence, nil
}
func (m *MailText) EventQuery() (*es_models.SearchQuery, error) {
sequence, err := m.view.GetLatestMailTextSequence()
if err != nil {
return nil, err
}
return es_models.NewSearchQuery().
AggregateTypeFilter(m.AggregateTypes()...).
LatestSequenceFilter(sequence.CurrentSequence), nil
}
func (m *MailText) Reduce(event *es_models.Event) (err error) {
switch event.AggregateType {
case model.OrgAggregate, iam_es_model.IAMAggregate:
err = m.processMailText(event)
}
return err
}
func (m *MailText) processMailText(event *es_models.Event) (err error) {
text := new(iam_model.MailTextView)
switch event.Type {
case iam_es_model.MailTextAdded, model.MailTextAdded:
err = text.AppendEvent(event)
case iam_es_model.MailTextChanged, model.MailTextChanged:
err = text.SetData(event)
if err != nil {
return err
}
text, err = m.view.MailTextByIDs(event.AggregateID, text.MailTextType, text.Language)
if err != nil {
return err
}
text.ChangeDate = event.CreationDate
err = text.AppendEvent(event)
case model.MailTextRemoved:
err = text.SetData(event)
return m.view.DeleteMailText(event.AggregateID, text.MailTextType, text.Language, event)
default:
return m.view.ProcessedMailTextSequence(event)
}
if err != nil {
return err
}
return m.view.PutMailText(text, event)
}
func (m *MailText) OnError(event *es_models.Event, err error) error {
logging.LogWithFields("SPOOL-4Djo9", "id", event.AggregateID).WithError(err).Warn("something went wrong in label text handler")
return spooler.HandleError(event, err, m.view.GetLatestMailTextFailedEvent, m.view.ProcessedMailTextFailedEvent, m.view.ProcessedMailTextSequence, m.errorCountUntilSkip)
}
func (o *MailText) OnSuccess() error {
return spooler.HandleSuccess(o.view.UpdateMailTextSpoolerRunTimestamp)
}

View File

@ -0,0 +1,122 @@
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 MessageText struct {
handler
subscription *v1.Subscription
}
func newMessageText(handler handler) *MessageText {
h := &MessageText{
handler: handler,
}
h.subscribe()
return h
}
func (m *MessageText) subscribe() {
m.subscription = m.es.Subscribe(m.AggregateTypes()...)
go func() {
for event := range m.subscription.Events {
query.ReduceEvent(m, event)
}
}()
}
const (
messageTextTable = "management.message_texts"
)
func (m *MessageText) ViewModel() string {
return messageTextTable
}
func (_ *MessageText) AggregateTypes() []es_models.AggregateType {
return []es_models.AggregateType{model.OrgAggregate, iam_es_model.IAMAggregate}
}
func (p *MessageText) CurrentSequence() (uint64, error) {
sequence, err := p.view.GetLatestMessageTextSequence()
if err != nil {
return 0, err
}
return sequence.CurrentSequence, nil
}
func (m *MessageText) EventQuery() (*es_models.SearchQuery, error) {
sequence, err := m.view.GetLatestMessageTextSequence()
if err != nil {
return nil, err
}
return es_models.NewSearchQuery().
AggregateTypeFilter(m.AggregateTypes()...).
LatestSequenceFilter(sequence.CurrentSequence), nil
}
func (m *MessageText) Reduce(event *es_models.Event) (err error) {
switch event.AggregateType {
case model.OrgAggregate, iam_es_model.IAMAggregate:
err = m.processMessageText(event)
}
return err
}
func (m *MessageText) processMessageText(event *es_models.Event) (err error) {
message := new(iam_model.MessageTextView)
switch event.Type {
case iam_es_model.CustomTextSet, model.CustomTextSet,
iam_es_model.CustomTextRemoved, model.CustomTextRemoved:
text := new(iam_model.CustomText)
err = text.SetData(event)
if err != nil {
return err
}
message, err = m.view.MessageTextByIDs(event.AggregateID, text.Template, text.Language.String())
if err != nil && !caos_errs.IsNotFound(err) {
return err
}
if caos_errs.IsNotFound(err) {
err = nil
message = new(iam_model.MessageTextView)
message.Language = text.Language.String()
message.MessageTextType = text.Template
message.CreationDate = event.CreationDate
}
err = message.AppendEvent(event)
case model.CustomTextMessageRemoved:
text := new(iam_model.CustomText)
err = text.SetData(event)
if err != nil {
return err
}
return m.view.DeleteMessageText(event.AggregateID, text.Template, text.Language.String(), event)
default:
return m.view.ProcessedMessageTextSequence(event)
}
if err != nil {
return err
}
return m.view.PutMessageText(message, event)
}
func (m *MessageText) OnError(event *es_models.Event, err error) error {
logging.LogWithFields("SPOOL-4Djo9", "id", event.AggregateID).WithError(err).Warn("something went wrong in label text handler")
return spooler.HandleError(event, err, m.view.GetLatestMessageTextFailedEvent, m.view.ProcessedMessageTextFailedEvent, m.view.ProcessedMessageTextSequence, m.errorCountUntilSkip)
}
func (o *MessageText) OnSuccess() error {
return spooler.HandleSuccess(o.view.UpdateMessageTextSpoolerRunTimestamp)
}

View File

@ -1,57 +0,0 @@
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 (
mailTextTable = "management.mail_texts"
)
func (v *View) MailTextsByAggregateID(aggregateID string) ([]*model.MailTextView, error) {
return view.GetMailTexts(v.Db, mailTextTable, aggregateID)
}
func (v *View) MailTextByIDs(aggregateID string, textType string, language string) (*model.MailTextView, error) {
return view.GetMailTextByIDs(v.Db, mailTextTable, aggregateID, textType, language)
}
func (v *View) PutMailText(template *model.MailTextView, event *models.Event) error {
err := view.PutMailText(v.Db, mailTextTable, template)
if err != nil {
return err
}
return v.ProcessedMailTextSequence(event)
}
func (v *View) DeleteMailText(aggregateID string, textType string, language string, event *models.Event) error {
err := view.DeleteMailText(v.Db, mailTextTable, aggregateID, textType, language)
if err != nil && !errors.IsNotFound(err) {
return err
}
return v.ProcessedMailTextSequence(event)
}
func (v *View) GetLatestMailTextSequence() (*global_view.CurrentSequence, error) {
return v.latestSequence(mailTextTable)
}
func (v *View) ProcessedMailTextSequence(event *models.Event) error {
return v.saveCurrentSequence(mailTextTable, event)
}
func (v *View) UpdateMailTextSpoolerRunTimestamp() error {
return v.updateSpoolerRunSequence(mailTextTable)
}
func (v *View) GetLatestMailTextFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
return v.latestFailedEvent(mailTextTable, sequence)
}
func (v *View) ProcessedMailTextFailedEvent(failedEvent *global_view.FailedEvent) error {
return v.saveFailedEvent(failedEvent)
}

View File

@ -0,0 +1,57 @@
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 (
messageTextTable = "management.message_texts"
)
func (v *View) MessageTextsByAggregateID(aggregateID string) ([]*model.MessageTextView, error) {
return view.GetMessageTexts(v.Db, messageTextTable, aggregateID)
}
func (v *View) MessageTextByIDs(aggregateID, textType, lang string) (*model.MessageTextView, error) {
return view.GetMessageTextByIDs(v.Db, messageTextTable, aggregateID, textType, lang)
}
func (v *View) PutMessageText(template *model.MessageTextView, event *models.Event) error {
err := view.PutMessageText(v.Db, messageTextTable, template)
if err != nil {
return err
}
return v.ProcessedMessageTextSequence(event)
}
func (v *View) DeleteMessageText(aggregateID, textType, lang string, event *models.Event) error {
err := view.DeleteMessageText(v.Db, messageTextTable, aggregateID, textType, lang)
if err != nil && !errors.IsNotFound(err) {
return err
}
return v.ProcessedMessageTextSequence(event)
}
func (v *View) GetLatestMessageTextSequence() (*global_view.CurrentSequence, error) {
return v.latestSequence(messageTextTable)
}
func (v *View) ProcessedMessageTextSequence(event *models.Event) error {
return v.saveCurrentSequence(messageTextTable, event)
}
func (v *View) UpdateMessageTextSpoolerRunTimestamp() error {
return v.updateSpoolerRunSequence(messageTextTable)
}
func (v *View) GetLatestMessageTextFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
return v.latestFailedEvent(messageTextTable, sequence)
}
func (v *View) ProcessedMessageTextFailedEvent(failedEvent *global_view.FailedEvent) error {
return v.saveFailedEvent(failedEvent)
}

View File

@ -44,8 +44,10 @@ type OrgRepository interface {
GetDefaultMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error)
GetMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error)
GetDefaultMailTexts(ctx context.Context) (*iam_model.MailTextsView, error)
GetMailTexts(ctx context.Context) (*iam_model.MailTextsView, 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)
GetLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error)
GetPreviewLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error)

View File

@ -29,19 +29,19 @@ import (
)
const (
notificationTable = "notification.notifications"
NotifyUserID = "NOTIFICATION"
labelPolicyTableOrg = "management.label_policies"
labelPolicyTableDef = "adminapi.label_policies"
mailTemplateTableOrg = "management.mail_templates"
mailTemplateTableDef = "adminapi.mail_templates"
mailTextTableOrg = "management.mail_texts"
mailTextTableDef = "adminapi.mail_texts"
mailTextTypeDomainClaimed = "DomainClaimed"
mailTextTypeInitCode = "InitCode"
mailTextTypePasswordReset = "PasswordReset"
mailTextTypeVerifyEmail = "VerifyEmail"
mailTextTypeVerifyPhone = "VerifyPhone"
notificationTable = "notification.notifications"
NotifyUserID = "NOTIFICATION"
labelPolicyTableOrg = "management.label_policies"
labelPolicyTableDef = "adminapi.label_policies"
mailTemplateTableOrg = "management.mail_templates"
mailTemplateTableDef = "adminapi.mail_templates"
messageTextTableOrg = "management.message_texts"
messageTextTableDef = "adminapi.message_texts"
messageTextTypeDomainClaimed = "DomainClaimed"
messageTextTypeInitCode = "InitCode"
messageTextTypePasswordReset = "PasswordReset"
messageTextTypeVerifyEmail = "VerifyEmail"
messageTextTypeVerifyPhone = "VerifyPhone"
)
type Notification struct {
@ -146,7 +146,6 @@ func (n *Notification) handleInitUserCode(event *models.Event) (err error) {
if err != nil || alreadyHandled {
return err
}
ctx := getSetNotifyContextData(event.ResourceOwner)
colors, err := n.getLabelPolicy(ctx)
if err != nil {
@ -163,7 +162,7 @@ func (n *Notification) handleInitUserCode(event *models.Event) (err error) {
return err
}
text, err := n.getMailText(ctx, mailTextTypeInitCode, user.PreferredLanguage)
text, err := n.getMessageText(user, messageTextTypeInitCode, user.PreferredLanguage)
if err != nil {
return err
}
@ -202,7 +201,7 @@ func (n *Notification) handlePasswordCode(event *models.Event) (err error) {
return err
}
text, err := n.getMailText(ctx, mailTextTypePasswordReset, user.PreferredLanguage)
text, err := n.getMessageText(user, messageTextTypePasswordReset, user.PreferredLanguage)
if err != nil {
return err
}
@ -224,7 +223,6 @@ func (n *Notification) handleEmailVerificationCode(event *models.Event) (err err
if err != nil || alreadyHandled {
return nil
}
ctx := getSetNotifyContextData(event.ResourceOwner)
colors, err := n.getLabelPolicy(ctx)
if err != nil {
@ -241,7 +239,7 @@ func (n *Notification) handleEmailVerificationCode(event *models.Event) (err err
return err
}
text, err := n.getMailText(ctx, mailTextTypeVerifyEmail, user.PreferredLanguage)
text, err := n.getMessageText(user, messageTextTypeVerifyEmail, user.PreferredLanguage)
if err != nil {
return err
}
@ -268,7 +266,11 @@ func (n *Notification) handlePhoneVerificationCode(event *models.Event) (err err
if err != nil {
return err
}
err = types.SendPhoneVerificationCode(n.i18n, user, phoneCode, n.systemDefaults, n.AesCrypto)
text, err := n.getMessageText(user, messageTextTypeVerifyPhone, user.PreferredLanguage)
if err != nil {
return err
}
err = types.SendPhoneVerificationCode(text, user, phoneCode, n.systemDefaults, n.AesCrypto)
if err != nil {
return err
}
@ -303,7 +305,7 @@ func (n *Notification) handleDomainClaimed(event *models.Event) (err error) {
return err
}
text, err := n.getMailText(ctx, mailTextTypeDomainClaimed, user.PreferredLanguage)
text, err := n.getMessageText(user, messageTextTypeDomainClaimed, user.PreferredLanguage)
if err != nil {
return err
}
@ -395,26 +397,29 @@ func (n *Notification) getMailTemplate(ctx context.Context) (*iam_model.MailTemp
}
// Read organization specific texts
func (n *Notification) getMailText(ctx context.Context, textType string, lang string) (*iam_model.MailTextView, error) {
func (n *Notification) getMessageText(user *model.NotifyUser, textType, lang string) (*iam_model.MessageTextView, error) {
langTag := language.Make(lang)
if langTag == language.Und {
langTag = n.systemDefaults.DefaultLanguage
langTag = language.English
}
base, _ := langTag.Base()
langBase, _ := langTag.Base()
defaultMessageText, err := n.view.MessageTextByIDs(n.systemDefaults.IamID, textType, langBase.String(), messageTextTableDef)
if err != nil {
return nil, err
}
defaultMessageText.Default = true
// read from Org
mailText, err := n.view.MailTextByIDs(authz.GetCtxData(ctx).OrgID, textType, base.String(), mailTextTableOrg)
orgMessageText, err := n.view.MessageTextByIDs(user.ResourceOwner, textType, langBase.String(), messageTextTableOrg)
if errors.IsNotFound(err) {
// read from default
mailText, err = n.view.MailTextByIDs(n.systemDefaults.IamID, textType, base.String(), mailTextTableDef)
if err != nil {
return nil, err
}
mailText.Default = true
return iam_es_model.MessageTextViewToModel(defaultMessageText), nil
}
if err != nil {
return nil, err
}
return iam_es_model.MailTextViewToModel(mailText), err
mergedText := mergeMessageTexts(defaultMessageText, orgMessageText)
return iam_es_model.MessageTextViewToModel(mergedText), err
}
func (n *Notification) getUserByID(userID string) (*model.NotifyUser, error) {
@ -440,3 +445,28 @@ func (n *Notification) getUserByID(userID string) (*model.NotifyUser, error) {
}
return &userCopy, nil
}
func mergeMessageTexts(defaultText *iam_es_model.MessageTextView, orgText *iam_es_model.MessageTextView) *iam_es_model.MessageTextView {
if orgText.Subject == "" {
orgText.Subject = defaultText.Subject
}
if orgText.Title == "" {
orgText.Title = defaultText.Title
}
if orgText.PreHeader == "" {
orgText.PreHeader = defaultText.PreHeader
}
if orgText.Text == "" {
orgText.Text = defaultText.Text
}
if orgText.Greeting == "" {
orgText.Greeting = defaultText.Greeting
}
if orgText.ButtonText == "" {
orgText.ButtonText = defaultText.ButtonText
}
if orgText.FooterText == "" {
orgText.FooterText = defaultText.FooterText
}
return orgText
}

View File

@ -1,10 +0,0 @@
package view
import (
"github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/iam/repository/view/model"
)
func (v *View) MailTextByIDs(aggregateID string, textType string, language string, mailTextTableVar string) (*model.MailTextView, error) {
return view.GetMailTextByIDs(v.Db, mailTextTableVar, aggregateID, textType, language)
}

View File

@ -0,0 +1,10 @@
package view
import (
"github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/iam/repository/view/model"
)
func (v *View) MessageTextByIDs(aggregateID, textType, lang, messageTextTableVar string) (*model.MessageTextView, error) {
return view.GetMessageTextByIDs(v.Db, messageTextTableVar, aggregateID, textType, lang)
}

View File

@ -53,7 +53,7 @@ func (data *TemplateData) Translate(i18n *i18n.Translator, args map[string]inter
data.ButtonText = i18n.Localize(data.ButtonText, nil, langs...)
}
func GetTemplateData(apiDomain, href string, text *iam_model.MailTextView, policy *iam_model.LabelPolicyView) TemplateData {
func GetTemplateData(apiDomain, href string, text *iam_model.MessageTextView, policy *iam_model.LabelPolicyView) TemplateData {
templateData := TemplateData{
Title: text.Title,
PreHeader: text.PreHeader,
@ -62,6 +62,7 @@ func GetTemplateData(apiDomain, href string, text *iam_model.MailTextView, polic
Text: html.UnescapeString(text.Text),
Href: href,
ButtonText: text.ButtonText,
FooterText: text.FooterText,
PrimaryColor: defaultPrimaryColor,
BackgroundColor: defaultBackgroundColor,
FontColor: defaultFontColor,

View File

@ -15,18 +15,14 @@ type DomainClaimedData struct {
URL string
}
func SendDomainClaimed(mailhtml string, text *iam_model.MailTextView, user *view_model.NotifyUser, username string, systemDefaults systemdefaults.SystemDefaults, colors *iam_model.LabelPolicyView, apiDomain string) error {
func SendDomainClaimed(mailhtml string, text *iam_model.MessageTextView, user *view_model.NotifyUser, username string, systemDefaults systemdefaults.SystemDefaults, colors *iam_model.LabelPolicyView, apiDomain string) error {
url, err := templates.ParseTemplateText(systemDefaults.Notifications.Endpoints.DomainClaimed, &UrlData{UserID: user.ID})
if err != nil {
return err
}
var args = map[string]interface{}{
"FirstName": user.FirstName,
"LastName": user.LastName,
"Username": user.LastEmail,
"TempUsername": username,
"Domain": strings.Split(user.LastEmail, "@")[1],
}
var args = mapNotifyUserToArgs(user)
args["TempUsername"] = username
args["Domain"] = strings.Split(user.LastEmail, "@")[1]
text.Greeting, err = templates.ParseTemplateText(text.Greeting, args)
text.Text, err = templates.ParseTemplateText(text.Text, args)

View File

@ -16,7 +16,7 @@ type EmailVerificationCodeData struct {
URL string
}
func SendEmailVerificationCode(mailhtml string, text *iam_model.MailTextView, user *view_model.NotifyUser, code *es_model.EmailCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView, apiDomain string) error {
func SendEmailVerificationCode(mailhtml string, text *iam_model.MessageTextView, user *view_model.NotifyUser, code *es_model.EmailCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView, apiDomain string) error {
codeString, err := crypto.DecryptString(code.Code, alg)
if err != nil {
return err
@ -25,11 +25,9 @@ func SendEmailVerificationCode(mailhtml string, text *iam_model.MailTextView, us
if err != nil {
return err
}
var args = map[string]interface{}{
"FirstName": user.FirstName,
"LastName": user.LastName,
"Code": codeString,
}
var args = mapNotifyUserToArgs(user)
args["Code"] = codeString
text.Greeting, err = templates.ParseTemplateText(text.Greeting, args)
text.Text, err = templates.ParseTemplateText(text.Text, args)

View File

@ -22,7 +22,7 @@ type UrlData struct {
PasswordSet bool
}
func SendUserInitCode(mailhtml string, text *iam_model.MailTextView, user *view_model.NotifyUser, code *es_model.InitUserCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView, apiDomain string) error {
func SendUserInitCode(mailhtml string, text *iam_model.MessageTextView, user *view_model.NotifyUser, code *es_model.InitUserCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView, apiDomain string) error {
codeString, err := crypto.DecryptString(code.Code, alg)
if err != nil {
return err
@ -31,12 +31,8 @@ func SendUserInitCode(mailhtml string, text *iam_model.MailTextView, user *view_
if err != nil {
return err
}
var args = map[string]interface{}{
"FirstName": user.FirstName,
"LastName": user.LastName,
"Code": codeString,
"PreferredLoginName": user.PreferredLoginName,
}
var args = mapNotifyUserToArgs(user)
args["Code"] = codeString
text.Greeting, err = templates.ParseTemplateText(text.Greeting, args)
text.Text, err = templates.ParseTemplateText(text.Text, args)

View File

@ -18,7 +18,7 @@ type PasswordCodeData struct {
URL string
}
func SendPasswordCode(mailhtml string, text *iam_model.MailTextView, user *view_model.NotifyUser, code *es_model.PasswordCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView, apiDomain string) error {
func SendPasswordCode(mailhtml string, text *iam_model.MessageTextView, user *view_model.NotifyUser, code *es_model.PasswordCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView, apiDomain string) error {
codeString, err := crypto.DecryptString(code.Code, alg)
if err != nil {
return err
@ -27,11 +27,8 @@ func SendPasswordCode(mailhtml string, text *iam_model.MailTextView, user *view_
if err != nil {
return err
}
var args = map[string]interface{}{
"FirstName": user.FirstName,
"LastName": user.LastName,
"Code": codeString,
}
var args = mapNotifyUserToArgs(user)
args["Code"] = codeString
text.Greeting, err = templates.ParseTemplateText(text.Greeting, args)
text.Text, err = templates.ParseTemplateText(text.Text, args)

View File

@ -3,7 +3,7 @@ package types
import (
"github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/i18n"
iam_model "github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/internal/notification/templates"
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
view_model "github.com/caos/zitadel/internal/user/repository/view/model"
@ -13,19 +13,18 @@ type PhoneVerificationCodeData struct {
UserID string
}
func SendPhoneVerificationCode(i18n *i18n.Translator, user *view_model.NotifyUser, code *es_model.PhoneCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm) error {
func SendPhoneVerificationCode(text *iam_model.MessageTextView, user *view_model.NotifyUser, code *es_model.PhoneCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm) error {
codeString, err := crypto.DecryptString(code.Code, alg)
if err != nil {
return err
}
var args = map[string]interface{}{
"FirstName": user.FirstName,
"LastName": user.LastName,
"Code": codeString,
}
systemDefaults.Notifications.TemplateData.VerifyPhone.Translate(i18n, args, user.PreferredLanguage)
var args = mapNotifyUserToArgs(user)
args["Code"] = codeString
text.Text, err = templates.ParseTemplateText(text.Text, args)
codeData := &PhoneVerificationCodeData{UserID: user.ID}
template, err := templates.ParseTemplateText(systemDefaults.Notifications.TemplateData.VerifyPhone.Text, codeData)
template, err := templates.ParseTemplateText(text.Text, codeData)
if err != nil {
return err
}

View File

@ -42,3 +42,21 @@ func sendDebugEmail(message providers.Message, config systemdefaults.Notificatio
}
return provider.HandleMessage(message)
}
func mapNotifyUserToArgs(user *view_model.NotifyUser) map[string]interface{} {
return map[string]interface{}{
"UserName": user.UserName,
"FirstName": user.FirstName,
"LastName": user.LastName,
"NickName": user.NickName,
"DisplayName": user.DisplayName,
"LastEmail": user.LastEmail,
"VerifiedEmail": user.VerifiedEmail,
"LastPhone": user.LastPhone,
"VerifiedPhone": user.VerifiedPhone,
"PreferredLoginName": user.PreferredLoginName,
"LoginNames": user.LoginNames,
"ChangeDate": user.ChangeDate,
"CreationDate": user.CreationDate,
}
}

View File

@ -1,44 +0,0 @@
package model
import (
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
)
func (o *Org) appendAddMailTextEvent(event *es_models.Event) error {
mailText := &iam_es_model.MailText{}
err := mailText.SetDataLabel(event)
if err != nil {
return err
}
mailText.ObjectRoot.CreationDate = event.CreationDate
o.MailTexts = append(o.MailTexts, mailText)
return nil
}
func (o *Org) appendChangeMailTextEvent(event *es_models.Event) error {
mailText := &iam_es_model.MailText{}
err := mailText.SetDataLabel(event)
if err != nil {
return err
}
mailText.ObjectRoot.ChangeDate = event.CreationDate
if n, m := iam_es_model.GetMailText(o.MailTexts, mailText.MailTextType, mailText.Language); m != nil {
o.MailTexts[n] = mailText
}
return nil
}
func (o *Org) appendRemoveMailTextEvent(event *es_models.Event) error {
mailText := &iam_es_model.MailText{}
err := mailText.SetDataLabel(event)
if err != nil {
return err
}
if n, m := iam_es_model.GetMailText(o.MailTexts, mailText.MailTextType, mailText.Language); m != nil {
o.MailTexts[n] = o.MailTexts[len(o.MailTexts)-1]
o.MailTexts[len(o.MailTexts)-1] = nil
o.MailTexts = o.MailTexts[:len(o.MailTexts)-1]
}
return nil
}

View File

@ -1,91 +0,0 @@
package model
import (
"encoding/json"
"testing"
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
)
func TestAppendAddMailTextEvent(t *testing.T) {
type args struct {
org *Org
mailText *iam_es_model.MailText
event *es_models.Event
}
tests := []struct {
name string
args args
result *Org
}{
{
name: "append add mail text event",
args: args{
org: &Org{},
mailText: &iam_es_model.MailText{MailTextType: "Type", Language: "DE"},
event: &es_models.Event{},
},
result: &Org{MailTexts: []*iam_es_model.MailText{&iam_es_model.MailText{MailTextType: "Type", Language: "DE"}}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.mailText != nil {
data, _ := json.Marshal(tt.args.mailText)
tt.args.event.Data = data
}
tt.args.org.appendAddMailTextEvent(tt.args.event)
if len(tt.args.org.MailTexts) != 1 {
t.Errorf("got wrong result should have one mailtext actual: %v ", len(tt.args.org.MailTexts))
}
if tt.result.MailTexts[0].Language != tt.args.org.MailTexts[0].Language {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.MailTexts[0].Language, tt.args.org.MailTexts[0].Language)
}
})
}
}
func TestAppendChangeMailTextEvent(t *testing.T) {
type args struct {
org *Org
mailText *iam_es_model.MailText
event *es_models.Event
}
tests := []struct {
name string
args args
result *Org
}{
{
name: "append change mail text event",
args: args{
org: &Org{MailTexts: []*iam_es_model.MailText{&iam_es_model.MailText{
Language: "DE",
MailTextType: "TypeX",
}}},
mailText: &iam_es_model.MailText{MailTextType: "Type", Language: "DE"},
event: &es_models.Event{},
},
result: &Org{MailTexts: []*iam_es_model.MailText{&iam_es_model.MailText{
Language: "DE",
MailTextType: "Type",
}}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.mailText != nil {
data, _ := json.Marshal(tt.args.mailText)
tt.args.event.Data = data
}
tt.args.org.appendChangeMailTextEvent(tt.args.event)
if len(tt.args.org.MailTexts) != 1 {
t.Errorf("got wrong result should have one mailtext actual: %v ", len(tt.args.org.MailTexts))
}
if tt.result.MailTexts[0].Language != tt.args.org.MailTexts[0].Language {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.MailTexts[0].Language, tt.args.org.MailTexts[0].Language)
}
})
}
}

View File

@ -26,7 +26,6 @@ type Org struct {
OrgIAMPolicy *iam_es_model.OrgIAMPolicy `json:"-"`
LabelPolicy *iam_es_model.LabelPolicy `json:"-"`
MailTemplate *iam_es_model.MailTemplate `json:"-"`
MailTexts []*iam_es_model.MailText `json:"-"`
IDPs []*iam_es_model.IDPConfig `json:"-"`
LoginPolicy *iam_es_model.LoginPolicy `json:"-"`
PasswordComplexityPolicy *iam_es_model.PasswordComplexityPolicy `json:"-"`
@ -34,44 +33,6 @@ type Org struct {
PasswordLockoutPolicy *iam_es_model.PasswordLockoutPolicy `json:"-"`
}
func OrgFromModel(org *org_model.Org) *Org {
members := OrgMembersFromModel(org.Members)
domains := OrgDomainsFromModel(org.Domains)
idps := iam_es_model.IDPConfigsFromModel(org.IDPs)
mailTexts := iam_es_model.MailTextsFromModel(org.MailTexts)
converted := &Org{
ObjectRoot: org.ObjectRoot,
Name: org.Name,
State: int32(org.State),
Domains: domains,
MailTexts: mailTexts,
Members: members,
IDPs: idps,
}
if org.OrgIamPolicy != nil {
converted.OrgIAMPolicy = iam_es_model.OrgIAMPolicyFromModel(org.OrgIamPolicy)
}
if org.LoginPolicy != nil {
converted.LoginPolicy = iam_es_model.LoginPolicyFromModel(org.LoginPolicy)
}
if org.LabelPolicy != nil {
converted.LabelPolicy = iam_es_model.LabelPolicyFromModel(org.LabelPolicy)
}
if org.MailTemplate != nil {
converted.MailTemplate = iam_es_model.MailTemplateFromModel(org.MailTemplate)
}
if org.PasswordComplexityPolicy != nil {
converted.PasswordComplexityPolicy = iam_es_model.PasswordComplexityPolicyFromModel(org.PasswordComplexityPolicy)
}
if org.PasswordAgePolicy != nil {
converted.PasswordAgePolicy = iam_es_model.PasswordAgePolicyFromModel(org.PasswordAgePolicy)
}
if org.PasswordLockoutPolicy != nil {
converted.PasswordLockoutPolicy = iam_es_model.PasswordLockoutPolicyFromModel(org.PasswordLockoutPolicy)
}
return converted
}
func OrgToModel(org *Org) *org_model.Org {
converted := &org_model.Org{
ObjectRoot: org.ObjectRoot,
@ -79,7 +40,6 @@ func OrgToModel(org *Org) *org_model.Org {
State: org_model.OrgState(org.State),
Domains: OrgDomainsToModel(org.Domains),
Members: OrgMembersToModel(org.Members),
MailTexts: iam_es_model.MailTextsToModel(org.MailTexts),
IDPs: iam_es_model.IDPConfigsToModel(org.IDPs),
}
if org.OrgIAMPolicy != nil {
@ -216,12 +176,6 @@ func (o *Org) AppendEvent(event *es_models.Event) (err error) {
err = o.appendChangeMailTemplateEvent(event)
case MailTemplateRemoved:
o.appendRemoveMailTemplateEvent(event)
case MailTextAdded:
err = o.appendAddMailTextEvent(event)
case MailTextChanged:
err = o.appendChangeMailTextEvent(event)
case MailTextRemoved:
o.appendRemoveMailTextEvent(event)
case LoginPolicySecondFactorAdded:
err = o.appendAddSecondFactorToLoginPolicyEvent(event)
case LoginPolicySecondFactorRemoved:

View File

@ -76,9 +76,10 @@ const (
MailTemplateAdded models.EventType = "org.mail.template.added"
MailTemplateChanged models.EventType = "org.mail.template.changed"
MailTemplateRemoved models.EventType = "org.mail.template.removed"
MailTextAdded models.EventType = "org.mail.text.added"
MailTextChanged models.EventType = "org.mail.text.changed"
MailTextRemoved models.EventType = "org.mail.text.removed"
CustomTextSet models.EventType = "org.customtext.set"
CustomTextRemoved models.EventType = "org.customtext.removed"
CustomTextMessageRemoved models.EventType = "org.customtext.template.removed"
PasswordComplexityPolicyAdded models.EventType = "org.policy.password.complexity.added"
PasswordComplexityPolicyChanged models.EventType = "org.policy.password.complexity.changed"

View File

@ -81,7 +81,7 @@ func (r *ProjectGrantViewSearchRequest) AppendMyResourceOwnerQuery(orgID string)
func (r *ProjectGrantViewSearchRequest) EnsureLimit(limit uint64) error {
if r.Limit > limit {
return caos_errors.ThrowInvalidArgument(nil, "SEARCH-2n8fS", "Errors.Limit.ExceedsDefault")
return caos_errors.ThrowInvalidArgument(nil, "SEARCH-0fj3s", "Errors.Limit.ExceedsDefault")
}
if r.Limit == 0 {
r.Limit = limit

View File

@ -35,6 +35,7 @@ type FeaturesSetEvent struct {
LabelPolicyPrivateLabel *bool `json:"labelPolicyPrivateLabel,omitempty"`
LabelPolicyWatermark *bool `json:"labelPolicyWatermark,omitempty"`
CustomDomain *bool `json:"customDomain,omitempty"`
CustomText *bool `json:"customText,omitempty"`
}
func (e *FeaturesSetEvent) Data() interface{} {
@ -153,6 +154,11 @@ func ChangeCustomDomain(customDomain bool) func(event *FeaturesSetEvent) {
}
}
func ChangeCustomText(customText bool) func(event *FeaturesSetEvent) {
return func(e *FeaturesSetEvent) {
e.CustomText = &customText
}
}
func FeaturesSetEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e := &FeaturesSetEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),

View File

@ -0,0 +1,46 @@
package iam
import (
"context"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/repository/policy"
)
var (
CustomTextSetEventType = iamEventTypePrefix + policy.CustomTextSetEventType
)
type CustomTextSetEvent struct {
policy.CustomTextSetEvent
}
func NewCustomTextSetEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
template,
key,
text string,
language language.Tag,
) *CustomTextSetEvent {
return &CustomTextSetEvent{
CustomTextSetEvent: *policy.NewCustomTextSetEvent(
eventstore.NewBaseEventForPush(ctx, aggregate, CustomTextSetEventType),
template,
key,
text,
language),
}
}
func CustomTextSetEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e, err := policy.CustomTextSetEventMapper(event)
if err != nil {
return nil, err
}
return &CustomTextSetEvent{CustomTextSetEvent: *e.(*policy.CustomTextSetEvent)}, nil
}

View File

@ -56,5 +56,6 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
RegisterFilterEventMapper(MailTemplateChangedEventType, MailTemplateChangedEventMapper).
RegisterFilterEventMapper(MailTextAddedEventType, MailTextAddedEventMapper).
RegisterFilterEventMapper(MailTextChangedEventType, MailTextChangedEventMapper).
RegisterFilterEventMapper(CustomTextSetEventType, CustomTextSetEventMapper).
RegisterFilterEventMapper(FeaturesSetEventType, FeaturesSetEventMapper)
}

View File

@ -0,0 +1,107 @@
package org
import (
"context"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/repository/policy"
)
var (
CustomTextSetEventType = orgEventTypePrefix + policy.CustomTextSetEventType
CustomTextRemovedEventType = orgEventTypePrefix + policy.CustomTextRemovedEventType
CustomTextTemplateRemovedEventType = orgEventTypePrefix + policy.CustomTextTemplateRemovedEventType
)
type CustomTextSetEvent struct {
policy.CustomTextSetEvent
}
func NewCustomTextSetEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
template,
key,
text string,
language language.Tag,
) *CustomTextSetEvent {
return &CustomTextSetEvent{
CustomTextSetEvent: *policy.NewCustomTextSetEvent(
eventstore.NewBaseEventForPush(ctx, aggregate, CustomTextSetEventType),
template,
key,
text,
language),
}
}
func CustomTextSetEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e, err := policy.CustomTextSetEventMapper(event)
if err != nil {
return nil, err
}
return &CustomTextSetEvent{CustomTextSetEvent: *e.(*policy.CustomTextSetEvent)}, nil
}
type CustomTextRemovedEvent struct {
policy.CustomTextRemovedEvent
}
func NewCustomTextRemovedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
template,
key string,
language language.Tag,
) *CustomTextRemovedEvent {
return &CustomTextRemovedEvent{
CustomTextRemovedEvent: *policy.NewCustomTextRemovedEvent(
eventstore.NewBaseEventForPush(ctx, aggregate, CustomTextRemovedEventType),
template,
key,
language,
),
}
}
func CustomTextRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e, err := policy.CustomTextRemovedEventMapper(event)
if err != nil {
return nil, err
}
return &CustomTextRemovedEvent{CustomTextRemovedEvent: *e.(*policy.CustomTextRemovedEvent)}, nil
}
type CustomTextTemplateRemovedEvent struct {
policy.CustomTextTemplateRemovedEvent
}
func NewCustomTextTemplateRemovedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
template string,
language language.Tag,
) *CustomTextTemplateRemovedEvent {
return &CustomTextTemplateRemovedEvent{
CustomTextTemplateRemovedEvent: *policy.NewCustomTextTemplateRemovedEvent(
eventstore.NewBaseEventForPush(ctx, aggregate, CustomTextTemplateRemovedEventType),
template,
language,
),
}
}
func CustomTextTemplateRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e, err := policy.CustomTextTemplateRemovedEventMapper(event)
if err != nil {
return nil, err
}
return &CustomTextTemplateRemovedEvent{CustomTextTemplateRemovedEvent: *e.(*policy.CustomTextTemplateRemovedEvent)}, nil
}

View File

@ -62,6 +62,9 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
RegisterFilterEventMapper(MailTextAddedEventType, MailTextAddedEventMapper).
RegisterFilterEventMapper(MailTextChangedEventType, MailTextChangedEventMapper).
RegisterFilterEventMapper(MailTextRemovedEventType, MailTextRemovedEventMapper).
RegisterFilterEventMapper(CustomTextSetEventType, CustomTextSetEventMapper).
RegisterFilterEventMapper(CustomTextRemovedEventType, CustomTextRemovedEventMapper).
RegisterFilterEventMapper(CustomTextTemplateRemovedEventType, CustomTextTemplateRemovedEventMapper).
RegisterFilterEventMapper(IDPConfigAddedEventType, IDPConfigAddedEventMapper).
RegisterFilterEventMapper(IDPConfigChangedEventType, IDPConfigChangedEventMapper).
RegisterFilterEventMapper(IDPConfigRemovedEventType, IDPConfigRemovedEventMapper).

View File

@ -0,0 +1,138 @@
package policy
import (
"encoding/json"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
)
const (
customTextPrefix = "customtext."
CustomTextSetEventType = customTextPrefix + "set"
CustomTextRemovedEventType = customTextPrefix + "removed"
CustomTextTemplateRemovedEventType = customTextPrefix + "template.removed"
)
type CustomTextSetEvent struct {
eventstore.BaseEvent `json:"-"`
Template string `json:"template,omitempty"`
Key string `json:"key,omitempty"`
Language language.Tag `json:"language,omitempty"`
Text string `json:"text,omitempty"`
}
func (e *CustomTextSetEvent) Data() interface{} {
return e
}
func (e *CustomTextSetEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return nil
}
func NewCustomTextSetEvent(
base *eventstore.BaseEvent,
template,
key,
text string,
language language.Tag,
) *CustomTextSetEvent {
return &CustomTextSetEvent{
BaseEvent: *base,
Template: template,
Key: key,
Language: language,
Text: text,
}
}
func CustomTextSetEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e := &CustomTextSetEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
}
err := json.Unmarshal(event.Data, e)
if err != nil {
return nil, errors.ThrowInternal(err, "TEXT-28dwe", "unable to unmarshal custom text")
}
return e, nil
}
type CustomTextRemovedEvent struct {
eventstore.BaseEvent `json:"-"`
Template string `json:"template,omitempty"`
Key string `json:"key,omitempty"`
Language language.Tag `json:"language,omitempty"`
}
func (e *CustomTextRemovedEvent) Data() interface{} {
return e
}
func (e *CustomTextRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return nil
}
func NewCustomTextRemovedEvent(base *eventstore.BaseEvent, template, key string, language language.Tag) *CustomTextRemovedEvent {
return &CustomTextRemovedEvent{
BaseEvent: *base,
Template: template,
Key: key,
Language: language,
}
}
func CustomTextRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e := &CustomTextRemovedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
}
err := json.Unmarshal(event.Data, e)
if err != nil {
return nil, errors.ThrowInternal(err, "TEXT-28sMf", "unable to unmarshal custom text removed")
}
return e, nil
}
type CustomTextTemplateRemovedEvent struct {
eventstore.BaseEvent `json:"-"`
Template string `json:"template,omitempty"`
Language language.Tag `json:"language,omitempty"`
}
func (e *CustomTextTemplateRemovedEvent) Data() interface{} {
return e
}
func (e *CustomTextTemplateRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return nil
}
func NewCustomTextTemplateRemovedEvent(base *eventstore.BaseEvent, template string, language language.Tag) *CustomTextTemplateRemovedEvent {
return &CustomTextTemplateRemovedEvent{
BaseEvent: *base,
Template: template,
Language: language,
}
}
func CustomTextTemplateRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e := &CustomTextTemplateRemovedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
}
err := json.Unmarshal(event.Data, e)
if err != nil {
return nil, errors.ThrowInternal(err, "TEXT-mKKRs", "unable to unmarshal custom text message removed")
}
return e, nil
}

View File

@ -21,6 +21,7 @@ type IAMSetUp struct {
Step13 *command.Step13
Step14 *command.Step14
Step15 *command.Step15
Step16 *command.Step16
}
func (setup *IAMSetUp) Steps(currentDone domain.Step) ([]command.Step, error) {
@ -42,6 +43,7 @@ func (setup *IAMSetUp) Steps(currentDone domain.Step) ([]command.Step, error) {
setup.Step13,
setup.Step14,
setup.Step15,
setup.Step16,
} {
if step.Step() <= currentDone {
continue

View File

@ -162,11 +162,11 @@ Errors:
NotChanged: Default Mail Template wurde nicht verändert
AlreadyExists: Default Mail Template existiert bereits
Invalid: Default Mail Template ist ungültig
MailText:
NotFound: Default Mail Text konnte nicht gefunden werden
NotChanged: Default Mail Text wurde nicht verändert
AlreadyExists: Default Mail Text existiert bereits
Invalid: Default Mail Text ist ungültig
CustomMessageText:
NotFound: Default Message Text konnte nicht gefunden werden
NotChanged: Default Message Text wurde nicht verändert
AlreadyExists: Default Message Text existiert bereits
Invalid: Default Message Text ist ungültig
PasswordComplexity:
NotFound: Password Komplexitäts Policy konnte nicht gefunden werden
Empty: Passwort Komplexitäts Policy ist leer
@ -276,11 +276,11 @@ Errors:
NotChanged: Default Mail Template wurde nicht verändert
AlreadyExists: Default Mail Template existiert bereits
Invalid: Default Mail Template ist ungültig
MailText:
NotFound: Default Mail Text konnte nicht gefunden werden
NotChanged: Default Mail Text wurde nicht verändert
AlreadyExists: Default Mail Text existiert bereits
Invalid: Default Mail Text ist ungültig
CustomMessageText:
NotFound: Default Message Text konnte nicht gefunden werden
NotChanged: Default Message Text wurde nicht verändert
AlreadyExists: Default Message Text existiert bereits
Invalid: Default Message Text ist ungültig
PasswordComplexityPolicy:
NotFound: Default Password Complexity Policy konnte nicht gefunden werden
NotExisting: Default Password Complexity Policy existiert nicht
@ -351,6 +351,10 @@ Errors:
AlreadyExists: Schritt ausgeführt existiert bereits
Features:
NotChanged: Feature hat nicht geändert
CustomText:
AlreadyExists: Kundenspezifischer Text existiert bereits
Invalid: Kundenspezifischer Text ist ungültig
NotFound: Kundenspezifischer Text nicht gefunden
EventTypes:
user:
added: Benutzer hinzugefügt
@ -569,6 +573,11 @@ EventTypes:
config:
added: SAML IDP Konfiguration hinzugefügt
changed: SAML IDP Konfiguration geändert
customtext:
set: Kundenspezifischer Text wurde gesetzt
removed: Kundenspezifischer Text wurde entfernt
template:
removed: Kundenspezifisches Text Template wurde entfernt
policy:
login:
added: Login Richtlinie hinzugefügt
@ -715,6 +724,9 @@ EventTypes:
config:
added: SAML IDP Konfiguration hinzugefügt
changed: SAML IDP Konfiguration geändert
customtext:
set: Text wurde gesetzt
removed: Text wurde entfernt
policy:
login:
added: Default Login Policy hinzugefügt

View File

@ -162,11 +162,11 @@ Errors:
NotChanged: Default Mail Template has not been changed
AlreadyExists: Default Mail Template already exists
Invalid: Default Mail Template is invalid
MailText:
NotFound: Default Mail Text not found
NotChanged: Default Mail Text has not been changed
AlreadyExists: Default Mail Text already exists
Invalid: Default Mail Text is invalid
CustomMessageText:
NotFound: Default Message Text not found
NotChanged: Default Message Text has not been changed
AlreadyExists: Default Message Text already exists
Invalid: Default Message Text is invalid
PasswordComplexity:
NotFound: Password Complexity Policy not found
Empty: Password Complexity Policy is empty
@ -276,11 +276,11 @@ Errors:
NotChanged: Default Mail Template has not been changed
AlreadyExists: Default Mail Template already exists
Invalid: Default Mail Template is invalid
MailText:
NotFound: Default Mail Text not found
NotChanged: Default Mail Text has not been changed
AlreadyExists: Default Mail Text already exists
Invalid: Default Mail Text is invalid
CustomMessageText:
NotFound: Default Message Text not found
NotChanged: Default Message Text has not been changed
AlreadyExists: Default Message Text already exists
Invalid: Default Message Text is invalid
PasswordComplexityPolicy:
NotFound: Default Private Label Policy not found
NotExisting: Default Password Complexity Policy not existing
@ -351,6 +351,10 @@ Errors:
AlreadyExists: Step done already exists
Features:
NotChanged: Feature hat nicht geändert
CustomText:
AlreadyExists: Custom text already exists
Invalid: Custom text invalid
NotFound: Custom text not found
EventTypes:
user:
added: User added

View File

@ -42,7 +42,7 @@ func (l *Login) handleLoginNameCheck(w http.ResponseWriter, r *http.Request) {
data := new(loginData)
authReq, err := l.getAuthRequestAndParseData(r, data)
if err != nil {
l.renderError(w, r, authReq, err)
l.renderLogin(w, r, authReq, err)
return
}
if data.Register {

Some files were not shown because too many files have changed in this diff Show More