From 1b9cea0e0cf493581538593f25639bc7c221290b Mon Sep 17 00:00:00 2001 From: Miguel Cabrerizo <30386061+doncicuto@users.noreply.github.com> Date: Tue, 28 Mar 2023 21:36:52 +0200 Subject: [PATCH] feat: add Help/Support e-mail for instance/org (#5445) feat: help and support email in privacy policy --- cmd/defaults.yaml | 1 + .../modules/policies/login-texts/helper.ts | 1 + .../privacy-policy.component.html | 6 + .../privacy-policy.component.scss | 3 +- .../privacy-policy.component.ts | 6 + .../settings-list.component.html | 2 +- console/src/assets/i18n/de.json | 1 + console/src/assets/i18n/en.json | 1 + console/src/assets/i18n/fr.json | 1 + console/src/assets/i18n/it.json | 3 +- console/src/assets/i18n/pl.json | 1 + console/src/assets/i18n/zh.json | 1 + .../manage/console/instance-settings.mdx | 15 +- .../img/guides/console/privacypolicy.png | Bin 69581 -> 28895 bytes internal/api/grpc/admin/export.go | 7 +- .../grpc/admin/privacy_policy_converter.go | 7 +- .../management/policy_privacy_converter.go | 14 +- internal/api/grpc/policy/privacy_policy.go | 9 +- internal/api/grpc/text/custom_text.go | 2 + internal/api/ui/login/renderer.go | 4 + internal/api/ui/login/static/i18n/de.yaml | 2 + internal/api/ui/login/static/i18n/en.yaml | 2 + internal/api/ui/login/static/i18n/fr.yaml | 6 +- internal/api/ui/login/static/i18n/it.yaml | 2 + internal/api/ui/login/static/i18n/pl.yaml | 2 + internal/api/ui/login/static/i18n/zh.yaml | 2 + .../api/ui/login/static/templates/footer.html | 53 +++++-- .../eventsourcing/eventstore/auth_request.go | 1 + internal/command/custom_login_text.go | 4 + internal/command/custom_login_text_model.go | 10 +- internal/command/instance.go | 9 +- internal/command/instance_converter.go | 1 + .../command/instance_custom_login_text.go | 1 - .../instance_custom_login_text_test.go | 38 +++++ internal/command/instance_policy_privacy.go | 22 ++- .../command/instance_policy_privacy_model.go | 5 + .../command/instance_policy_privacy_test.go | 113 ++++++++++---- internal/command/org_converter.go | 1 + .../command/org_custom_login_text_test.go | 32 ++++ internal/command/org_policy_privacy.go | 24 ++- internal/command/org_policy_privacy_model.go | 6 +- internal/command/org_policy_privacy_test.go | 144 ++++++++++++------ internal/command/policy_privacy_model.go | 13 +- internal/domain/custom_login_text.go | 2 + internal/domain/policy_privacy.go | 7 +- internal/iam/model/privacy_policy_view.go | 1 + internal/query/custom_text.go | 3 + internal/query/privacy_policy.go | 22 ++- internal/query/privacy_policy_test.go | 26 ++-- internal/query/projection/privacy_policy.go | 8 +- .../query/projection/privacy_policy_test.go | 34 +++-- .../repository/instance/policy_privacy.go | 6 +- internal/repository/org/policy_privacy.go | 6 +- internal/repository/policy/policy_privacy.go | 34 +++-- proto/zitadel/admin.proto | 7 + proto/zitadel/management.proto | 14 ++ proto/zitadel/policy.proto | 8 + proto/zitadel/text.proto | 3 +- 58 files changed, 572 insertions(+), 187 deletions(-) diff --git a/cmd/defaults.yaml b/cmd/defaults.yaml index 9814a40329..0798c7dd22 100644 --- a/cmd/defaults.yaml +++ b/cmd/defaults.yaml @@ -499,6 +499,7 @@ DefaultInstance: TOSLink: https://zitadel.com/docs/legal/terms-of-service PrivacyLink: https://zitadel.com/docs/legal/privacy-policy HelpLink: "" + SupportEmail: "" NotificationPolicy: PasswordChange: true LabelPolicy: diff --git a/console/src/app/modules/policies/login-texts/helper.ts b/console/src/app/modules/policies/login-texts/helper.ts index f71d75bfb9..d7fd2ed457 100644 --- a/console/src/app/modules/policies/login-texts/helper.ts +++ b/console/src/app/modules/policies/login-texts/helper.ts @@ -73,6 +73,7 @@ export function mapRequestValues(map: Partial, req: Req): Req { r3.setHelp(map.footerText?.help ?? ''); r3.setPrivacyPolicy(map.footerText?.privacyPolicy ?? ''); r3.setTos(map.footerText?.tos ?? ''); + r3.setSupportEmail(map.footerText?.supportEmail ?? ''); req.setFooterText(r3); const r4 = new InitMFADoneScreenText(); diff --git a/console/src/app/modules/policies/privacy-policy/privacy-policy.component.html b/console/src/app/modules/policies/privacy-policy/privacy-policy.component.html index 362b0f9544..70fd46927f 100644 --- a/console/src/app/modules/policies/privacy-policy/privacy-policy.component.html +++ b/console/src/app/modules/policies/privacy-policy/privacy-policy.component.html @@ -36,6 +36,12 @@ + + + {{ 'POLICY.PRIVACY_POLICY.SUPPORTEMAIL' | translate }} + + + diff --git a/console/src/app/modules/policies/privacy-policy/privacy-policy.component.scss b/console/src/app/modules/policies/privacy-policy/privacy-policy.component.scss index 507f364bcc..5f58269f24 100644 --- a/console/src/app/modules/policies/privacy-policy/privacy-policy.component.scss +++ b/console/src/app/modules/policies/privacy-policy/privacy-policy.component.scss @@ -7,9 +7,10 @@ display: grid; grid-template-columns: 1fr; column-gap: 1rem; + width: 75%; @media only screen and (min-width: 800px) { - grid-template-columns: 1fr 1fr 1fr; + grid-template-columns: 1fr 1fr; } } diff --git a/console/src/app/modules/policies/privacy-policy/privacy-policy.component.ts b/console/src/app/modules/policies/privacy-policy/privacy-policy.component.ts index 6444f3f802..a2b0f52b52 100644 --- a/console/src/app/modules/policies/privacy-policy/privacy-policy.component.ts +++ b/console/src/app/modules/policies/privacy-policy/privacy-policy.component.ts @@ -60,6 +60,7 @@ export class PrivacyPolicyComponent implements OnInit, OnDestroy { tosLink: ['', []], privacyLink: ['', []], helpLink: ['', []], + supportEmail: ['', []], }); this.canWrite$.pipe(take(1)).subscribe((canWrite) => { @@ -105,6 +106,7 @@ export class PrivacyPolicyComponent implements OnInit, OnDestroy { tosLink: '', privacyLink: '', helpLink: '', + supportEmail: '', }); } }) @@ -114,6 +116,7 @@ export class PrivacyPolicyComponent implements OnInit, OnDestroy { tosLink: '', privacyLink: '', helpLink: '', + supportEmail: '', }); }); } @@ -125,6 +128,7 @@ export class PrivacyPolicyComponent implements OnInit, OnDestroy { req.setPrivacyLink(this.form.get('privacyLink')?.value); req.setTosLink(this.form.get('tosLink')?.value); req.setHelpLink(this.form.get('helpLink')?.value); + req.setSupportEmail(this.form.get('supportEmail')?.value); (this.service as ManagementService) .addCustomPrivacyPolicy(req) .then(() => { @@ -137,6 +141,7 @@ export class PrivacyPolicyComponent implements OnInit, OnDestroy { req.setPrivacyLink(this.form.get('privacyLink')?.value); req.setTosLink(this.form.get('tosLink')?.value); req.setHelpLink(this.form.get('helpLink')?.value); + req.setSupportEmail(this.form.get('supportEmail')?.value); (this.service as ManagementService) .updateCustomPrivacyPolicy(req) @@ -151,6 +156,7 @@ export class PrivacyPolicyComponent implements OnInit, OnDestroy { req.setPrivacyLink(this.form.get('privacyLink')?.value); req.setTosLink(this.form.get('tosLink')?.value); req.setHelpLink(this.form.get('helpLink')?.value); + req.setSupportEmail(this.form.get('supportEmail')?.value); (this.service as AdminService) .updatePrivacyPolicy(req) diff --git a/console/src/app/modules/settings-list/settings-list.component.html b/console/src/app/modules/settings-list/settings-list.component.html index 74b535a463..5b24cac59e 100644 --- a/console/src/app/modules/settings-list/settings-list.component.html +++ b/console/src/app/modules/settings-list/settings-list.component.html @@ -53,6 +53,6 @@ - + diff --git a/console/src/assets/i18n/de.json b/console/src/assets/i18n/de.json index 4b48c22db7..980e4f3123 100644 --- a/console/src/assets/i18n/de.json +++ b/console/src/assets/i18n/de.json @@ -1185,6 +1185,7 @@ "TOSLINK": "Link zu den Allgemeinen Geschäftsbedingungen", "POLICYLINK": "Link zur den Datenschutzrichtlinien", "HELPLINK": "Link zur Hilfestellung", + "SUPPORTEMAIL": "Support E-Mail", "SAVED": "Saved successfully!", "RESET_TITLE": "Standardwerte wiederherstellen", "RESET_DESCRIPTION": "Sie sind im Begriff die Standardlinks für die AGBs und Datenschutzrichtlinie wiederherzustellen. Wollen Sie fortfahren?" diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json index 8c24142f44..41039461f0 100644 --- a/console/src/assets/i18n/en.json +++ b/console/src/assets/i18n/en.json @@ -1186,6 +1186,7 @@ "TOSLINK": "Link to Terms of Service", "POLICYLINK": "Link to Privacy Policy", "HELPLINK": "Link to Help", + "SUPPORTEMAIL": "Support Email", "SAVED": "Saved successfully!", "RESET_TITLE": "Restore Default Values", "RESET_DESCRIPTION": "You are about to restore the default Links for TOS and Privacy Policy. Do you really want to continue?" diff --git a/console/src/assets/i18n/fr.json b/console/src/assets/i18n/fr.json index 06f9a9b5e4..19aeaa8321 100644 --- a/console/src/assets/i18n/fr.json +++ b/console/src/assets/i18n/fr.json @@ -1185,6 +1185,7 @@ "TOSLINK": "Lien vers les conditions d'utilisation", "POLICYLINK": "Lien vers la politique de confidentialité", "HELPLINK": "Lien vers l'aide", + "SUPPORTEMAIL": "E-mail d'assistance", "SAVED": "Enregistré avec succès !", "RESET_TITLE": "Restaurer les valeurs par défaut", "RESET_DESCRIPTION": "Vous êtes sur le point de restaurer les liens par défaut pour les CGS et la politique de confidentialité. Voulez-vous vraiment continuer ?" diff --git a/console/src/assets/i18n/it.json b/console/src/assets/i18n/it.json index ac7ed3323a..fdb623c5e3 100644 --- a/console/src/assets/i18n/it.json +++ b/console/src/assets/i18n/it.json @@ -8,7 +8,7 @@ }, "FOOTER": { "LINKS": { - "CONTACT": "Kontakt", + "CONTACT": "Contatto", "TOS": "Terms of Service", "PP": "Privacy Policy" }, @@ -1186,6 +1186,7 @@ "TOSLINK": "Link ai termini di servizio", "POLICYLINK": "Link all'informativa sulla privacy", "HELPLINK": "link per l'aiuto", + "SUPPORTEMAIL": "e-mail di supporto", "SAVED": "Salvato con successo!", "RESET_TITLE": "Ripristina i valori predefiniti", "RESET_DESCRIPTION": "Stai per ripristinare i link predefiniti per i TOS e l'informativa sulla privacy. Vuoi davvero continuare?" diff --git a/console/src/assets/i18n/pl.json b/console/src/assets/i18n/pl.json index e77e3cc92a..85d724c57a 100644 --- a/console/src/assets/i18n/pl.json +++ b/console/src/assets/i18n/pl.json @@ -1185,6 +1185,7 @@ "TOSLINK": "Link do warunków korzystania", "POLICYLINK": "Link do polityki prywatności", "HELPLINK": "Link do pomocy", + "SUPPORTEMAIL": "E-mail wsparcia", "SAVED": "Pomyślnie zapisano!", "RESET_TITLE": "Przywróć wartości domyślne", "RESET_DESCRIPTION": "Masz zamiar przywrócić domyślne linki dla TOS i polityki prywatności. Czy na pewno chcesz kontynuować?" diff --git a/console/src/assets/i18n/zh.json b/console/src/assets/i18n/zh.json index bd989f7cca..f7d77cf61e 100644 --- a/console/src/assets/i18n/zh.json +++ b/console/src/assets/i18n/zh.json @@ -1184,6 +1184,7 @@ "TOSLINK": "链接到服务条款", "POLICYLINK": "链接到隐私政策", "HELPLINK": "链接到帮助", + "SUPPORTEMAIL": "支持邮箱", "SAVED": "保存成功!", "RESET_TITLE": "恢复默认值", "RESET_DESCRIPTION": "您即将恢复 TOS 和隐私政策的默认链接。你真的要继续吗?" diff --git a/docs/docs/guides/manage/console/instance-settings.mdx b/docs/docs/guides/manage/console/instance-settings.mdx index d0c2e10fe8..1472365102 100644 --- a/docs/docs/guides/manage/console/instance-settings.mdx +++ b/docs/docs/guides/manage/console/instance-settings.mdx @@ -57,7 +57,11 @@ At the moment Twilio is available as SMS provider. You can configure on which changes the users will be notified. The text of the message can be changed in the [Message texts](#message-texts) -Notification +Notification ### SMTP @@ -132,7 +136,6 @@ Configure the different lifetimes checks for the login process: - **Second Factor Check Lifetime** specifies after which period a user has to revalidate the 2-Factor during the login process - **External Login Check Lifetime** specifies after which period a user has to revalidate the Multi Factor during the login process - ## Identity Providers You can configure all kinds of external identity providers for identity brokering, which support OIDC (OpenID Connect). @@ -190,7 +193,7 @@ You can either set this attribute on your whole ZITADEL instance or just on some ## Privacy Policy and TOS -With this setting you are able to configure your privacy policy, terms of service and help links. +With this setting you are able to configure your privacy policy, terms of service, help links and help/support email address. On register each user has to accept these policies. This policy can be also be overriden by your organizations. @@ -231,7 +234,11 @@ You can set the locale of the translations on the right. These are the texts for the login. Just like for message texts, you can select the locale on the right. -Login texts +Login texts ## OIDC token lifetimes and expiration diff --git a/docs/static/img/guides/console/privacypolicy.png b/docs/static/img/guides/console/privacypolicy.png index c957ebb85471e421a53ee88019b03d7d1ca989b9..4a5c9a96eba911eb2627b158af1fdcf803a6f92d 100644 GIT binary patch literal 28895 zcmdqJbySqm+b;YdDvBs20wSPN(gM<@lr%^;(jC$<0~mBlHv-ZP(lOHA-O}ALz!2Zo z-+Rtl-#Y)CblvI?zBHs)Q4g4@Q-P4{*aMI9FQVNgkkajwyHZ&^gu15&=RSsW0<&@ZPMx-FS z$&muc#5Gu-ee&Ab%n~<-sFoxyj?ldq-*AKyy{AUeo`V#5^B_itO;1-JPX8c6^WSwL9_Q zz65O&$prI7)Rlg-!=?zX-V4Qu`=K-Zyc9i!C0)h`b4etV%xu<8J=yDAi{%Zwrh)32 z8Wf|Hwa!IFMg3iU@T<0)u9I4DfrJ9lRQV^G>P)4jVS6;H^8;aFv9P_yi0MLW85tRb zk=(yw;}WN#x%l?&XJl2C2!w-=Px|wfVEc^aX;6;SR>iF6S!AzE@@(sCZ&F*LJ%Mw=!3@JOz^v)7#vN4h~-DJ*7ll7f~ynEg{xe*P^8S1(nH zFoZaG9NjA*pzFAYK|p6NRrs+lHi>?j<-+N#Z_#|+`@*K)-m8w2fWGzbqwMS@DR?eb z)yh%6!6Wi)1LNw&ExY|YWPj&#;3jK6eBB*&w!?3m=pvIUAFbIhSmnG`(p(FJ%|}H? zm#81q9i7bM%9(8Jdz7!7BzTS6+~Q$xW7;1%T2bteS)M-1!dCA~_E=av(R3y+VQOn` z-nsr$6W>)_CFjqJBy%Oi=`mzQd#j{mI0!omev}u&yPG*^8dEe&9i1ZRoC)smdQbQX zqvp|x1u^8XM_uCjRohT$hJ1$m??k8Gs3PDY0uh}K4KTLXT+;GyKPD#k?aj@QH|L;E zP?l;Id6}#XFX^1V_-CYe;(T90f9vYnnqwbh(yu)>GV;q4ndxB%_p?LiCxs?F*D3!V z@JV9hqSt+Q#(pdO?B6=LMqH*Qe2P#ULVH_UdmAz}%Z*y5ij9{J+ha!V@2<=ocLcQ* zgNXVwkIYTxbu4~OV|`h*%6*(nN+NlL-)nQNYD_;){pV+%aW@n3IoIZel zx=SVT)o`As>%LR5ghk#|D`KEdzw4D<^jSU1IQLh~B64>7y{*}%7c2t~*wf+fHIZ@O z+KGZFNI3#$(x^)Noh;Y)Bv9jvE zGl$`=gK!N^NSOA>VE@o0y7bcNXETZD-z;#!j1u<0-q#a`{@ClF9YhPIW{1kVo}NX| zsqKKMyJYrmrON14sdik$4Hnb1H0spo=IbWLA(Uc0>6^V9reMW z+=X0Rs%&(4D?iP4(U9qAIz1DYlyy`yC^KolYxg-LaBa5EO{GCscN`UJGgHH9Sll!y z%sX98!5BO^_V@{2P*6aioV2w?94~SP?e~Oys0+!nOEjo2ML^^Y>%a7OL5na7xkhlj zO)t=_hke`JUkq2!jXqzOBJ4kLx$e>%-UXwhqKb8e^F1<=!(&FRb9Xi%K*~=jqO&b6 z?PXTK?u)s*-rw9^5ii!hOiyd<&6Rj`HgUJ?6eNrlDSS|=vG2DkZ+>+5D;mUm%5xgA zrKqXte019U(86l1KhAOcC;kC0)Kivt9~#%*eaHK2hB>P&WVXhiS*K#Pv-18HQzf}e z&8&lfyJ6(+^6AK=KU$d$v5>lYwg2Sh)D#a?wX^wzY`NZWA+h;=zsmT?arkD5NxRSC zA*MD(Sz=;81chW_@6A_Oc3z%ti%WgQczD|@o5*Q+%U}tQ2+K#QpG;pr&B$nlkg1v| zc$_37V;9-Q&%k$hpjh-ilwV4+!HEI-&$5l5w-cS8kl$OZPABXqh1; s6>Eq^>+Z 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�cyaz7myWXMgl){V3B%Sy@?! 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

`v7rotQ-_xgmYn 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 MYxHZ*S^L2M0tL2bi~s-t diff --git a/internal/api/grpc/admin/export.go b/internal/api/grpc/admin/export.go index 81f897dd58..800de828cb 100644 --- a/internal/api/grpc/admin/export.go +++ b/internal/api/grpc/admin/export.go @@ -521,9 +521,10 @@ func (s *Server) getPrivacyPolicy(ctx context.Context, orgID string) (_ *managem } if !queriedPrivacy.IsDefault { return &management_pb.AddCustomPrivacyPolicyRequest{ - TosLink: queriedPrivacy.TOSLink, - PrivacyLink: queriedPrivacy.PrivacyLink, - HelpLink: queriedPrivacy.HelpLink, + TosLink: queriedPrivacy.TOSLink, + PrivacyLink: queriedPrivacy.PrivacyLink, + HelpLink: queriedPrivacy.HelpLink, + SupportEmail: string(queriedPrivacy.SupportEmail), }, nil } return nil, nil diff --git a/internal/api/grpc/admin/privacy_policy_converter.go b/internal/api/grpc/admin/privacy_policy_converter.go index 829a3a0393..910267e14e 100644 --- a/internal/api/grpc/admin/privacy_policy_converter.go +++ b/internal/api/grpc/admin/privacy_policy_converter.go @@ -7,8 +7,9 @@ import ( func UpdatePrivacyPolicyToDomain(req *admin_pb.UpdatePrivacyPolicyRequest) *domain.PrivacyPolicy { return &domain.PrivacyPolicy{ - TOSLink: req.TosLink, - PrivacyLink: req.PrivacyLink, - HelpLink: req.HelpLink, + TOSLink: req.TosLink, + PrivacyLink: req.PrivacyLink, + HelpLink: req.HelpLink, + SupportEmail: domain.EmailAddress(req.SupportEmail), } } diff --git a/internal/api/grpc/management/policy_privacy_converter.go b/internal/api/grpc/management/policy_privacy_converter.go index 810ecda9c9..323c162142 100644 --- a/internal/api/grpc/management/policy_privacy_converter.go +++ b/internal/api/grpc/management/policy_privacy_converter.go @@ -7,16 +7,18 @@ import ( func AddPrivacyPolicyToDomain(req *mgmt_pb.AddCustomPrivacyPolicyRequest) *domain.PrivacyPolicy { return &domain.PrivacyPolicy{ - TOSLink: req.TosLink, - PrivacyLink: req.PrivacyLink, - HelpLink: req.HelpLink, + TOSLink: req.TosLink, + PrivacyLink: req.PrivacyLink, + HelpLink: req.HelpLink, + SupportEmail: domain.EmailAddress(req.SupportEmail), } } func UpdatePrivacyPolicyToDomain(req *mgmt_pb.UpdateCustomPrivacyPolicyRequest) *domain.PrivacyPolicy { return &domain.PrivacyPolicy{ - TOSLink: req.TosLink, - PrivacyLink: req.PrivacyLink, - HelpLink: req.HelpLink, + TOSLink: req.TosLink, + PrivacyLink: req.PrivacyLink, + HelpLink: req.HelpLink, + SupportEmail: domain.EmailAddress(req.SupportEmail), } } diff --git a/internal/api/grpc/policy/privacy_policy.go b/internal/api/grpc/policy/privacy_policy.go index 9f168199e8..f86bc48e3f 100644 --- a/internal/api/grpc/policy/privacy_policy.go +++ b/internal/api/grpc/policy/privacy_policy.go @@ -8,10 +8,11 @@ import ( func ModelPrivacyPolicyToPb(policy *query.PrivacyPolicy) *policy_pb.PrivacyPolicy { return &policy_pb.PrivacyPolicy{ - IsDefault: policy.IsDefault, - TosLink: policy.TOSLink, - PrivacyLink: policy.PrivacyLink, - HelpLink: policy.HelpLink, + IsDefault: policy.IsDefault, + TosLink: policy.TOSLink, + PrivacyLink: policy.PrivacyLink, + HelpLink: policy.HelpLink, + SupportEmail: string(policy.SupportEmail), Details: object.ToViewDetailsPb( policy.Sequence, policy.CreationDate, diff --git a/internal/api/grpc/text/custom_text.go b/internal/api/grpc/text/custom_text.go index 92b6f90097..0f3da00b53 100644 --- a/internal/api/grpc/text/custom_text.go +++ b/internal/api/grpc/text/custom_text.go @@ -458,6 +458,7 @@ func FooterTextToPb(text domain.FooterText) *text_pb.FooterText { Tos: text.TOS, PrivacyPolicy: text.PrivacyPolicy, Help: text.Help, + SupportEmail: text.SupportEmail, } } @@ -949,5 +950,6 @@ func FooterTextPbToDomain(text *text_pb.FooterText) domain.FooterText { TOS: text.Tos, PrivacyPolicy: text.PrivacyPolicy, Help: text.Help, + SupportEmail: text.SupportEmail, } } diff --git a/internal/api/ui/login/renderer.go b/internal/api/ui/login/renderer.go index b08d452ac2..7c9cf74c6c 100644 --- a/internal/api/ui/login/renderer.go +++ b/internal/api/ui/login/renderer.go @@ -442,6 +442,9 @@ func (l *Login) setLinksOnBaseData(baseData baseData, privacyPolicy *domain.Priv if link, err := templates.ParseTemplateText(privacyPolicy.HelpLink, lang); err == nil { baseData.HelpLink = link } + if link, err := templates.ParseTemplateText(string(privacyPolicy.SupportEmail), lang); err == nil { + baseData.SupportEmail = link + } return baseData } @@ -602,6 +605,7 @@ type baseData struct { TOSLink string PrivacyLink string HelpLink string + SupportEmail string AuthReqID string CSRF template.HTML Nonce string diff --git a/internal/api/ui/login/static/i18n/de.yaml b/internal/api/ui/login/static/i18n/de.yaml index be2f1e4c95..b634fff2d5 100644 --- a/internal/api/ui/login/static/i18n/de.yaml +++ b/internal/api/ui/login/static/i18n/de.yaml @@ -172,6 +172,7 @@ PasswordChange: NewPasswordConfirmLabel: Passwort Bestätigung CancelButtonText: abbrechen NextButtonText: weiter + Footer: Fusszeile PasswordChangeDone: Title: Passwort ändern @@ -318,6 +319,7 @@ Footer: Tos: AGB PrivacyPolicy: Datenschutzerklärung Help: Hilfe + SupportEmail: Support E-Mail Errors: Internal: Es ist ein interner Fehler aufgetreten diff --git a/internal/api/ui/login/static/i18n/en.yaml b/internal/api/ui/login/static/i18n/en.yaml index 2ca0b37d26..cf7de142fb 100644 --- a/internal/api/ui/login/static/i18n/en.yaml +++ b/internal/api/ui/login/static/i18n/en.yaml @@ -172,6 +172,7 @@ PasswordChange: NewPasswordConfirmLabel: Password confirmation CancelButtonText: cancel NextButtonText: next + Footer: Footer PasswordChangeDone: Title: Change Password @@ -318,6 +319,7 @@ Footer: Tos: TOS PrivacyPolicy: Privacy policy Help: Help + SupportEmail: Support E-mail Errors: Internal: An internal error occurred diff --git a/internal/api/ui/login/static/i18n/fr.yaml b/internal/api/ui/login/static/i18n/fr.yaml index f19a3e0b79..f1eae1f15c 100644 --- a/internal/api/ui/login/static/i18n/fr.yaml +++ b/internal/api/ui/login/static/i18n/fr.yaml @@ -172,6 +172,7 @@ PasswordChange: NewPasswordConfirmLabel: Confirmation du mot de passe CancelButtonText: annuler NextButtonText: suivant + Footer: Bas de page PasswordChangeDone: Title: Changer le mot de passe @@ -318,6 +319,7 @@ Footer: Tos: TOS PrivacyPolicy: Politique de confidentialité Help: Aide + SupportEmail: E-mail d'assistance Errors: Internal: Une erreur interne s'est produite @@ -408,8 +410,8 @@ Errors: ExternalUserIDEmpty: L'ID de l'utilisateur externe est vide UserDisplayNameEmpty: Le nom d'affichage de l'utilisateur est vide NoExternalUserData: Aucune donnée d'utilisateur externe reçue - CreationNotAllowed : La création d'un nouvel utilisateur n'est pas autorisée sur ce fournisseur. - LinkingNotAllowed : La création d'un lien vers un utilisateur n'est pas autorisée pour ce fournisseur. + CreationNotAllowed: La création d'un nouvel utilisateur n'est pas autorisée sur ce fournisseur. + LinkingNotAllowed: La création d'un lien vers un utilisateur n'est pas autorisée pour ce fournisseur. GrantRequired: Connexion impossible. L'utilisateur doit avoir au moins une subvention sur l'application. Veuillez contacter votre administrateur. ProjectRequired: Connexion impossible. L'organisation de l'utilisateur doit être accordée au projet. Veuillez contacter votre administrateur. IdentityProvider: diff --git a/internal/api/ui/login/static/i18n/it.yaml b/internal/api/ui/login/static/i18n/it.yaml index 0e72249d9b..7231632c6e 100644 --- a/internal/api/ui/login/static/i18n/it.yaml +++ b/internal/api/ui/login/static/i18n/it.yaml @@ -172,6 +172,7 @@ PasswordChange: NewPasswordConfirmLabel: Conferma della password CancelButtonText: annulla NextButtonText: Avanti + Footer: Piè di pagina PasswordChangeDone: Title: Reimposta password @@ -318,6 +319,7 @@ Footer: Tos: Termini di servizio PrivacyPolicy: l'informativa sulla privacy Help: Aiuto + SupportEmail: E-mail di supporto Errors: Internal: Si è verificato un errore interno diff --git a/internal/api/ui/login/static/i18n/pl.yaml b/internal/api/ui/login/static/i18n/pl.yaml index e8910efe5f..bdc4e33760 100644 --- a/internal/api/ui/login/static/i18n/pl.yaml +++ b/internal/api/ui/login/static/i18n/pl.yaml @@ -172,6 +172,7 @@ PasswordChange: NewPasswordConfirmLabel: Potwierdzenie hasła CancelButtonText: anuluj NextButtonText: dalej + Footer: Stopka PasswordChangeDone: Title: Zmiana hasła @@ -318,6 +319,7 @@ Footer: Tos: TOS PrivacyPolicy: Polityka prywatności Help: Pomoc + SupportEmail: E-mail wsparcia Errors: Internal: Wewnętrzny błąd diff --git a/internal/api/ui/login/static/i18n/zh.yaml b/internal/api/ui/login/static/i18n/zh.yaml index 112c99c2e8..80f9e4f0a9 100644 --- a/internal/api/ui/login/static/i18n/zh.yaml +++ b/internal/api/ui/login/static/i18n/zh.yaml @@ -172,6 +172,7 @@ PasswordChange: NewPasswordConfirmLabel: 确认密码 CancelButtonText: 取消 NextButtonText: 继续 + Footer: 页脚 PasswordChangeDone: Title: 更改密码 @@ -318,6 +319,7 @@ Footer: Tos: 服务条款 PrivacyPolicy: 隐私政策 Help: 帮助 + SupportEmail: 支持邮箱 Errors: Internal: 发生了内部错误 diff --git a/internal/api/ui/login/static/templates/footer.html b/internal/api/ui/login/static/templates/footer.html index e4f7578b51..9df79949d3 100644 --- a/internal/api/ui/login/static/templates/footer.html +++ b/internal/api/ui/login/static/templates/footer.html @@ -1,20 +1,41 @@ {{define "footer"}} {{end}} diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request.go b/internal/auth/repository/eventsourcing/eventstore/auth_request.go index 7c77b61639..4b1ca88910 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request.go @@ -1158,6 +1158,7 @@ func privacyPolicyToDomain(p *query.PrivacyPolicy) *domain.PrivacyPolicy { TOSLink: p.TOSLink, PrivacyLink: p.PrivacyLink, HelpLink: p.HelpLink, + SupportEmail: p.SupportEmail, } } diff --git a/internal/command/custom_login_text.go b/internal/command/custom_login_text.go index 6802a8d666..21147c616d 100644 --- a/internal/command/custom_login_text.go +++ b/internal/command/custom_login_text.go @@ -1093,6 +1093,10 @@ func (c *Commands) createFooterTextEvents(ctx context.Context, agg *eventstore.A if event != nil { events = append(events, event) } + event = c.createCustomLoginTextEvent(ctx, agg, domain.LoginKeyFooterSupportEmail, existingText.FooterSupportEmail, text.Footer.SupportEmail, text.Language, defaultText) + if event != nil { + events = append(events, event) + } return events } diff --git a/internal/command/custom_login_text_model.go b/internal/command/custom_login_text_model.go index c38893342c..0672092260 100644 --- a/internal/command/custom_login_text_model.go +++ b/internal/command/custom_login_text_model.go @@ -289,7 +289,7 @@ type CustomLoginTextReadModel struct { FooterTOS string FooterPrivacyPolicy string FooterHelp string - FooterHelpLink string + FooterSupportEmail string } func (wm *CustomLoginTextReadModel) Reduce() error { @@ -2515,6 +2515,10 @@ func (wm *CustomLoginTextReadModel) handleFooterTextSetEvent(e *policy.CustomTex wm.FooterHelp = e.Text return } + if e.Key == domain.LoginKeyFooterSupportEmail { + wm.FooterSupportEmail = e.Text + return + } } func (wm *CustomLoginTextReadModel) handleFooterTextRemoveEvent(e *policy.CustomTextRemovedEvent) { @@ -2530,4 +2534,8 @@ func (wm *CustomLoginTextReadModel) handleFooterTextRemoveEvent(e *policy.Custom wm.FooterHelp = "" return } + if e.Key == domain.LoginKeyFooterSupportEmail { + wm.FooterSupportEmail = "" + return + } } diff --git a/internal/command/instance.go b/internal/command/instance.go index 230607dc93..436b61b2bf 100644 --- a/internal/command/instance.go +++ b/internal/command/instance.go @@ -87,9 +87,10 @@ type InstanceSetup struct { PasswordChange bool } PrivacyPolicy struct { - TOSLink string - PrivacyLink string - HelpLink string + TOSLink string + PrivacyLink string + HelpLink string + SupportEmail domain.EmailAddress } LabelPolicy struct { PrimaryColor string @@ -242,7 +243,7 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str prepareAddSecondFactorToDefaultLoginPolicy(instanceAgg, domain.SecondFactorTypeU2F), prepareAddMultiFactorToDefaultLoginPolicy(instanceAgg, domain.MultiFactorTypeU2FWithPIN), - prepareAddDefaultPrivacyPolicy(instanceAgg, setup.PrivacyPolicy.TOSLink, setup.PrivacyPolicy.PrivacyLink, setup.PrivacyPolicy.HelpLink), + prepareAddDefaultPrivacyPolicy(instanceAgg, setup.PrivacyPolicy.TOSLink, setup.PrivacyPolicy.PrivacyLink, setup.PrivacyPolicy.HelpLink, setup.PrivacyPolicy.SupportEmail), prepareAddDefaultNotificationPolicy(instanceAgg, setup.NotificationPolicy.PasswordChange), prepareAddDefaultLockoutPolicy(instanceAgg, setup.LockoutPolicy.MaxAttempts, setup.LockoutPolicy.ShouldShowLockoutFailure), diff --git a/internal/command/instance_converter.go b/internal/command/instance_converter.go index 2eda0acb4e..d4e334ef2f 100644 --- a/internal/command/instance_converter.go +++ b/internal/command/instance_converter.go @@ -127,6 +127,7 @@ func writeModelToPrivacyPolicy(wm *PrivacyPolicyWriteModel) *domain.PrivacyPolic TOSLink: wm.TOSLink, PrivacyLink: wm.PrivacyLink, HelpLink: wm.HelpLink, + SupportEmail: wm.SupportEmail, } } diff --git a/internal/command/instance_custom_login_text.go b/internal/command/instance_custom_login_text.go index 9c99963b4b..7b332b0a15 100644 --- a/internal/command/instance_custom_login_text.go +++ b/internal/command/instance_custom_login_text.go @@ -56,7 +56,6 @@ func (c *Commands) setCustomInstanceLoginText(ctx context.Context, instanceAgg * if !text.IsValid() { return nil, nil, caos_errs.ThrowInvalidArgument(nil, "Instance-kd9fs", "Errors.CustomText.Invalid") } - existingLoginText, err := c.defaultLoginTextWriteModelByID(ctx, text.Language) if err != nil { return nil, nil, err diff --git a/internal/command/instance_custom_login_text_test.go b/internal/command/instance_custom_login_text_test.go index e1a1c407b4..e3fbe1119b 100644 --- a/internal/command/instance_custom_login_text_test.go +++ b/internal/command/instance_custom_login_text_test.go @@ -1370,6 +1370,12 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) { &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterHelp, "Help", language.English, ), ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterSupportEmail, "Support Email", language.English, + ), + ), }, ), ), @@ -1664,6 +1670,7 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) { TOS: "TOS", PrivacyPolicy: "PrivacyPolicy", Help: "Help", + SupportEmail: "Support Email", }, }, }, @@ -2993,6 +3000,12 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) { &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterHelp, "Help", language.English, ), ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterSupportEmail, "Support Email", language.English, + ), + ), ), expectPush( []*repository.Event{ @@ -4310,6 +4323,12 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) { &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterHelp, language.English, ), ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextRemovedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterSupportEmail, language.English, + ), + ), }, ), ), @@ -5681,6 +5700,12 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) { &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterHelp, "Help", language.English, ), ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterSupportEmail, "Support Email", language.English, + ), + ), eventFromEventPusherWithInstanceID( "INSTANCE", instance.NewCustomTextRemovedEvent(context.Background(), @@ -6996,6 +7021,12 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) { &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterHelp, language.English, ), ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextRemovedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterSupportEmail, language.English, + ), + ), ), expectPush( []*repository.Event{ @@ -8313,6 +8344,12 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) { &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterHelp, "Help", language.English, ), ), + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewCustomTextSetEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterSupportEmail, "Support Email", language.English, + ), + ), }, ), ), @@ -8607,6 +8644,7 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) { TOS: "TOS", PrivacyPolicy: "PrivacyPolicy", Help: "Help", + SupportEmail: "Support Email", }, }, }, diff --git a/internal/command/instance_policy_privacy.go b/internal/command/instance_policy_privacy.go index 141b327658..5768108475 100644 --- a/internal/command/instance_policy_privacy.go +++ b/internal/command/instance_policy_privacy.go @@ -12,9 +12,9 @@ import ( "github.com/zitadel/zitadel/internal/telemetry/tracing" ) -func (c *Commands) AddDefaultPrivacyPolicy(ctx context.Context, tosLink, privacyLink, helpLink string) (*domain.ObjectDetails, error) { +func (c *Commands) AddDefaultPrivacyPolicy(ctx context.Context, tosLink, privacyLink, helpLink string, supportEmail domain.EmailAddress) (*domain.ObjectDetails, error) { instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID()) - cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareAddDefaultPrivacyPolicy(instanceAgg, tosLink, privacyLink, helpLink)) + cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareAddDefaultPrivacyPolicy(instanceAgg, tosLink, privacyLink, helpLink, supportEmail)) if err != nil { return nil, err } @@ -26,6 +26,13 @@ func (c *Commands) AddDefaultPrivacyPolicy(ctx context.Context, tosLink, privacy } func (c *Commands) ChangeDefaultPrivacyPolicy(ctx context.Context, policy *domain.PrivacyPolicy) (*domain.PrivacyPolicy, error) { + if policy.SupportEmail != "" { + if err := policy.SupportEmail.Validate(); err != nil { + return nil, err + } + policy.SupportEmail = policy.SupportEmail.Normalize() + } + existingPolicy, err := c.defaultPrivacyPolicyWriteModelByID(ctx) if err != nil { return nil, err @@ -35,7 +42,7 @@ func (c *Commands) ChangeDefaultPrivacyPolicy(ctx context.Context, policy *domai } instanceAgg := InstanceAggregateFromWriteModel(&existingPolicy.PrivacyPolicyWriteModel.WriteModel) - changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, instanceAgg, policy.TOSLink, policy.PrivacyLink, policy.HelpLink) + changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, instanceAgg, policy.TOSLink, policy.PrivacyLink, policy.HelpLink, policy.SupportEmail) if !hasChanged { return nil, caos_errs.ThrowPreconditionFailed(nil, "INSTANCE-9jJfs", "Errors.IAM.PrivacyPolicy.NotChanged") } @@ -81,8 +88,15 @@ func prepareAddDefaultPrivacyPolicy( tosLink, privacyLink, helpLink string, + supportEmail domain.EmailAddress, ) preparation.Validation { return func() (preparation.CreateCommands, error) { + if supportEmail != "" { + if err := supportEmail.Validate(); err != nil { + return nil, err + } + supportEmail = supportEmail.Normalize() + } return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { writeModel := NewInstancePrivacyPolicyWriteModel(ctx) events, err := filter(ctx, writeModel.Query()) @@ -97,7 +111,7 @@ func prepareAddDefaultPrivacyPolicy( return nil, caos_errs.ThrowAlreadyExists(nil, "INSTANCE-M00rJ", "Errors.Instance.PrivacyPolicy.AlreadyExists") } return []eventstore.Command{ - instance.NewPrivacyPolicyAddedEvent(ctx, &a.Aggregate, tosLink, privacyLink, helpLink), + instance.NewPrivacyPolicyAddedEvent(ctx, &a.Aggregate, tosLink, privacyLink, helpLink, supportEmail), }, nil }, nil } diff --git a/internal/command/instance_policy_privacy_model.go b/internal/command/instance_policy_privacy_model.go index d83e61fd7a..24263a05de 100644 --- a/internal/command/instance_policy_privacy_model.go +++ b/internal/command/instance_policy_privacy_model.go @@ -4,6 +4,7 @@ import ( "context" "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/instance" "github.com/zitadel/zitadel/internal/repository/policy" @@ -57,6 +58,7 @@ func (wm *InstancePrivacyPolicyWriteModel) NewChangedEvent( tosLink, privacyLink, helpLink string, + supportEmail domain.EmailAddress, ) (*instance.PrivacyPolicyChangedEvent, bool) { changes := make([]policy.PrivacyPolicyChanges, 0) @@ -69,6 +71,9 @@ func (wm *InstancePrivacyPolicyWriteModel) NewChangedEvent( if wm.HelpLink != helpLink { changes = append(changes, policy.ChangeHelpLink(helpLink)) } + if wm.SupportEmail != supportEmail { + changes = append(changes, policy.ChangeSupportEmail(supportEmail)) + } if len(changes) == 0 { return nil, false } diff --git a/internal/command/instance_policy_privacy_test.go b/internal/command/instance_policy_privacy_test.go index 707afa8300..eddf8a0c18 100644 --- a/internal/command/instance_policy_privacy_test.go +++ b/internal/command/instance_policy_privacy_test.go @@ -22,10 +22,11 @@ func TestCommandSide_AddDefaultPrivacyPolicy(t *testing.T) { eventstore *eventstore.Eventstore } type args struct { - ctx context.Context - tosLink string - privacyLink string - helpLink string + ctx context.Context + tosLink string + privacyLink string + helpLink string + supportEmail domain.EmailAddress } type res struct { want *domain.ObjectDetails @@ -49,16 +50,18 @@ func TestCommandSide_AddDefaultPrivacyPolicy(t *testing.T) { "TOSLink", "PrivacyLink", "HelpLink", + "support@example.com", ), ), ), ), }, args: args{ - ctx: context.Background(), - tosLink: "TOSLink", - privacyLink: "PrivacyLink", - helpLink: "HelpLink", + ctx: context.Background(), + tosLink: "TOSLink", + privacyLink: "PrivacyLink", + helpLink: "HelpLink", + supportEmail: "support@example.com", }, res: res{ err: caos_errs.IsErrorAlreadyExists, @@ -79,6 +82,7 @@ func TestCommandSide_AddDefaultPrivacyPolicy(t *testing.T) { "TOSLink", "PrivacyLink", "HelpLink", + "support@example.com", ), ), }, @@ -86,10 +90,11 @@ func TestCommandSide_AddDefaultPrivacyPolicy(t *testing.T) { ), }, args: args{ - ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), - tosLink: "TOSLink", - privacyLink: "PrivacyLink", - helpLink: "HelpLink", + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + tosLink: "TOSLink", + privacyLink: "PrivacyLink", + helpLink: "HelpLink", + supportEmail: "support@example.com", }, res: res{ want: &domain.ObjectDetails{ @@ -97,6 +102,24 @@ func TestCommandSide_AddDefaultPrivacyPolicy(t *testing.T) { }, }, }, + { + name: "wrong email, can't add policy", + fields: fields{ + eventstore: eventstoreExpect( + t, + ), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + tosLink: "TOSLink", + privacyLink: "PrivacyLink", + helpLink: "HelpLink", + supportEmail: "wrong email", + }, + res: res{ + err: caos_errs.IsErrorInvalidArgument, + }, + }, { name: "add empty policy,ok", fields: fields{ @@ -112,6 +135,7 @@ func TestCommandSide_AddDefaultPrivacyPolicy(t *testing.T) { "", "", "", + "", ), ), }, @@ -119,10 +143,11 @@ func TestCommandSide_AddDefaultPrivacyPolicy(t *testing.T) { ), }, args: args{ - ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), - tosLink: "", - privacyLink: "", - helpLink: "", + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + tosLink: "", + privacyLink: "", + helpLink: "", + supportEmail: "", }, res: res{ want: &domain.ObjectDetails{ @@ -136,7 +161,7 @@ func TestCommandSide_AddDefaultPrivacyPolicy(t *testing.T) { r := &Commands{ eventstore: tt.fields.eventstore, } - got, err := r.AddDefaultPrivacyPolicy(tt.args.ctx, tt.args.tosLink, tt.args.privacyLink, tt.args.helpLink) + got, err := r.AddDefaultPrivacyPolicy(tt.args.ctx, tt.args.tosLink, tt.args.privacyLink, tt.args.helpLink, tt.args.supportEmail) if tt.res.err == nil { assert.NoError(t, err) } @@ -179,9 +204,10 @@ func TestCommandSide_ChangeDefaultPrivacyPolicy(t *testing.T) { args: args{ ctx: context.Background(), policy: &domain.PrivacyPolicy{ - TOSLink: "TOSLink", - PrivacyLink: "PrivacyLink", - HelpLink: "HelpLink", + TOSLink: "TOSLink", + PrivacyLink: "PrivacyLink", + HelpLink: "HelpLink", + SupportEmail: "support@example.com", }, }, res: res{ @@ -200,6 +226,7 @@ func TestCommandSide_ChangeDefaultPrivacyPolicy(t *testing.T) { "TOSLink", "PrivacyLink", "HelpLink", + "support@example.com", ), ), ), @@ -208,15 +235,36 @@ func TestCommandSide_ChangeDefaultPrivacyPolicy(t *testing.T) { args: args{ ctx: context.Background(), policy: &domain.PrivacyPolicy{ - TOSLink: "TOSLink", - PrivacyLink: "PrivacyLink", - HelpLink: "HelpLink", + TOSLink: "TOSLink", + PrivacyLink: "PrivacyLink", + HelpLink: "HelpLink", + SupportEmail: "support@example.com", }, }, res: res{ err: caos_errs.IsPreconditionFailed, }, }, + { + name: "wrong email, can't change policy", + fields: fields{ + eventstore: eventstoreExpect( + t, + ), + }, + args: args{ + ctx: context.Background(), + policy: &domain.PrivacyPolicy{ + TOSLink: "TOSLink", + PrivacyLink: "PrivacyLink", + HelpLink: "HelpLink", + SupportEmail: "wrong email", + }, + }, + res: res{ + err: caos_errs.IsErrorInvalidArgument, + }, + }, { name: "change, ok", fields: fields{ @@ -229,6 +277,7 @@ func TestCommandSide_ChangeDefaultPrivacyPolicy(t *testing.T) { "TOSLink", "PrivacyLink", "HelpLink", + "support@example.com", ), ), ), @@ -239,6 +288,7 @@ func TestCommandSide_ChangeDefaultPrivacyPolicy(t *testing.T) { "TOSLinkChanged", "PrivacyLinkChanged", "HelpLinkChanged", + "support2@example.com", ), ), }, @@ -248,9 +298,10 @@ func TestCommandSide_ChangeDefaultPrivacyPolicy(t *testing.T) { args: args{ ctx: context.Background(), policy: &domain.PrivacyPolicy{ - TOSLink: "TOSLinkChanged", - PrivacyLink: "PrivacyLinkChanged", - HelpLink: "HelpLinkChanged", + TOSLink: "TOSLinkChanged", + PrivacyLink: "PrivacyLinkChanged", + HelpLink: "HelpLinkChanged", + SupportEmail: "support2@example.com", }, }, res: res{ @@ -259,9 +310,10 @@ func TestCommandSide_ChangeDefaultPrivacyPolicy(t *testing.T) { AggregateID: "INSTANCE", ResourceOwner: "INSTANCE", }, - TOSLink: "TOSLinkChanged", - PrivacyLink: "PrivacyLinkChanged", - HelpLink: "HelpLinkChanged", + TOSLink: "TOSLinkChanged", + PrivacyLink: "PrivacyLinkChanged", + HelpLink: "HelpLinkChanged", + SupportEmail: "support2@example.com", }, }, }, @@ -285,13 +337,14 @@ func TestCommandSide_ChangeDefaultPrivacyPolicy(t *testing.T) { } } -func newDefaultPrivacyPolicyChangedEvent(ctx context.Context, tosLink, privacyLink, helpLink string) *instance.PrivacyPolicyChangedEvent { +func newDefaultPrivacyPolicyChangedEvent(ctx context.Context, tosLink, privacyLink, helpLink, supportEmail string) *instance.PrivacyPolicyChangedEvent { event, _ := instance.NewPrivacyPolicyChangedEvent(ctx, &instance.NewAggregate("INSTANCE").Aggregate, []policy.PrivacyPolicyChanges{ policy.ChangeTOSLink(tosLink), policy.ChangePrivacyLink(privacyLink), policy.ChangeHelpLink(helpLink), + policy.ChangeSupportEmail(domain.EmailAddress(supportEmail)), }, ) return event diff --git a/internal/command/org_converter.go b/internal/command/org_converter.go index 7533f6963e..667dae1984 100644 --- a/internal/command/org_converter.go +++ b/internal/command/org_converter.go @@ -50,5 +50,6 @@ func orgWriteModelToPrivacyPolicy(wm *OrgPrivacyPolicyWriteModel) *domain.Privac TOSLink: wm.TOSLink, PrivacyLink: wm.PrivacyLink, HelpLink: wm.HelpLink, + SupportEmail: wm.SupportEmail, } } diff --git a/internal/command/org_custom_login_text_test.go b/internal/command/org_custom_login_text_test.go index 24e055738a..3c5cb207e2 100644 --- a/internal/command/org_custom_login_text_test.go +++ b/internal/command/org_custom_login_text_test.go @@ -1161,6 +1161,11 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) { &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterHelp, "Help", language.English, ), ), + eventFromEventPusher( + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterSupportEmail, "Support Email", language.English, + ), + ), }, ), ), @@ -1455,6 +1460,7 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) { TOS: "TOS", PrivacyPolicy: "PrivacyPolicy", Help: "Help", + SupportEmail: "Support Email", }, }, }, @@ -2560,6 +2566,11 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) { &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterHelp, "Help", language.English, ), ), + eventFromEventPusher( + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterSupportEmail, "Support Email", language.English, + ), + ), ), expectPush( []*repository.Event{ @@ -3653,6 +3664,11 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) { &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterHelp, language.English, ), ), + eventFromEventPusher( + org.NewCustomTextRemovedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterSupportEmail, language.English, + ), + ), }, ), ), @@ -4800,6 +4816,11 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) { &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterHelp, "Help", language.English, ), ), + eventFromEventPusher( + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterSupportEmail, "Support Email", language.English, + ), + ), eventFromEventPusher( org.NewCustomTextRemovedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeySelectAccountTitle, language.English, @@ -5890,6 +5911,11 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) { &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterHelp, language.English, ), ), + eventFromEventPusher( + org.NewCustomTextRemovedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterSupportEmail, language.English, + ), + ), ), expectPush( []*repository.Event{ @@ -6983,6 +7009,11 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) { &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterHelp, "Help", language.English, ), ), + eventFromEventPusher( + org.NewCustomTextSetEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterSupportEmail, "Support Email", language.English, + ), + ), }, ), ), @@ -7277,6 +7308,7 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) { TOS: "TOS", PrivacyPolicy: "PrivacyPolicy", Help: "Help", + SupportEmail: "Support Email", }, }, }, diff --git a/internal/command/org_policy_privacy.go b/internal/command/org_policy_privacy.go index 4385ee22ed..04f730f8f1 100644 --- a/internal/command/org_policy_privacy.go +++ b/internal/command/org_policy_privacy.go @@ -29,6 +29,14 @@ func (c *Commands) orgPrivacyPolicyWriteModelByID(ctx context.Context, orgID str } func (c *Commands) AddPrivacyPolicy(ctx context.Context, resourceOwner string, policy *domain.PrivacyPolicy) (*domain.PrivacyPolicy, error) { + + if policy.SupportEmail != "" { + if err := policy.SupportEmail.Validate(); err != nil { + return nil, err + } + policy.SupportEmail = policy.SupportEmail.Normalize() + } + if resourceOwner == "" { return nil, caos_errs.ThrowInvalidArgument(nil, "Org-MMk9fs", "Errors.ResourceOwnerMissing") } @@ -49,7 +57,8 @@ func (c *Commands) AddPrivacyPolicy(ctx context.Context, resourceOwner string, p orgAgg, policy.TOSLink, policy.PrivacyLink, - policy.HelpLink)) + policy.HelpLink, + policy.SupportEmail)) if err != nil { return nil, err } @@ -61,12 +70,19 @@ func (c *Commands) AddPrivacyPolicy(ctx context.Context, resourceOwner string, p } func (c *Commands) ChangePrivacyPolicy(ctx context.Context, resourceOwner string, policy *domain.PrivacyPolicy) (*domain.PrivacyPolicy, error) { + + if policy.SupportEmail != "" { + if err := policy.SupportEmail.Validate(); err != nil { + return nil, err + } + policy.SupportEmail = policy.SupportEmail.Normalize() + } + if resourceOwner == "" { return nil, caos_errs.ThrowInvalidArgument(nil, "Org-22N89f", "Errors.ResourceOwnerMissing") } - existingPolicy := NewOrgPrivacyPolicyWriteModel(resourceOwner) - err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy) + existingPolicy, err := c.orgPrivacyPolicyWriteModelByID(ctx, resourceOwner) if err != nil { return nil, err } @@ -75,7 +91,7 @@ func (c *Commands) ChangePrivacyPolicy(ctx context.Context, resourceOwner string } orgAgg := OrgAggregateFromWriteModel(&existingPolicy.PrivacyPolicyWriteModel.WriteModel) - changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, orgAgg, policy.TOSLink, policy.PrivacyLink, policy.HelpLink) + changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, orgAgg, policy.TOSLink, policy.PrivacyLink, policy.HelpLink, policy.SupportEmail) if !hasChanged { return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-4N9fs", "Errors.Org.PrivacyPolicy.NotChanged") } diff --git a/internal/command/org_policy_privacy_model.go b/internal/command/org_policy_privacy_model.go index acff496263..262935e0ff 100644 --- a/internal/command/org_policy_privacy_model.go +++ b/internal/command/org_policy_privacy_model.go @@ -3,8 +3,8 @@ package command import ( "context" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/repository/org" "github.com/zitadel/zitadel/internal/repository/policy" ) @@ -59,6 +59,7 @@ func (wm *OrgPrivacyPolicyWriteModel) NewChangedEvent( tosLink, privacyLink, helpLink string, + supportEmail domain.EmailAddress, ) (*org.PrivacyPolicyChangedEvent, bool) { changes := make([]policy.PrivacyPolicyChanges, 0) @@ -71,6 +72,9 @@ func (wm *OrgPrivacyPolicyWriteModel) NewChangedEvent( if wm.HelpLink != helpLink { changes = append(changes, policy.ChangeHelpLink(helpLink)) } + if wm.SupportEmail != supportEmail { + changes = append(changes, policy.ChangeSupportEmail(supportEmail)) + } if len(changes) == 0 { return nil, false } diff --git a/internal/command/org_policy_privacy_test.go b/internal/command/org_policy_privacy_test.go index ac9ba384b2..986e7a0213 100644 --- a/internal/command/org_policy_privacy_test.go +++ b/internal/command/org_policy_privacy_test.go @@ -44,9 +44,10 @@ func TestCommandSide_AddPrivacyPolicy(t *testing.T) { args: args{ ctx: context.Background(), policy: &domain.PrivacyPolicy{ - TOSLink: "TOSLink", - PrivacyLink: "PrivacyLink", - HelpLink: "HelpLink", + TOSLink: "TOSLink", + PrivacyLink: "PrivacyLink", + HelpLink: "HelpLink", + SupportEmail: "support@example.com", }, }, res: res{ @@ -65,6 +66,7 @@ func TestCommandSide_AddPrivacyPolicy(t *testing.T) { "TOSLink", "PrivacyLink", "HelpLink", + "support@example.com", ), ), ), @@ -74,9 +76,10 @@ func TestCommandSide_AddPrivacyPolicy(t *testing.T) { ctx: context.Background(), orgID: "org1", policy: &domain.PrivacyPolicy{ - TOSLink: "TOSLink", - PrivacyLink: "PrivacyLink", - HelpLink: "HelpLink", + TOSLink: "TOSLink", + PrivacyLink: "PrivacyLink", + HelpLink: "HelpLink", + SupportEmail: "support@example.com", }, }, res: res{ @@ -97,6 +100,7 @@ func TestCommandSide_AddPrivacyPolicy(t *testing.T) { "TOSLink", "PrivacyLink", "HelpLink", + "support@example.com", ), ), }, @@ -107,9 +111,10 @@ func TestCommandSide_AddPrivacyPolicy(t *testing.T) { ctx: context.Background(), orgID: "org1", policy: &domain.PrivacyPolicy{ - TOSLink: "TOSLink", - PrivacyLink: "PrivacyLink", - HelpLink: "HelpLink", + TOSLink: "TOSLink", + PrivacyLink: "PrivacyLink", + HelpLink: "HelpLink", + SupportEmail: "support@example.com", }, }, res: res{ @@ -118,14 +123,36 @@ func TestCommandSide_AddPrivacyPolicy(t *testing.T) { AggregateID: "org1", ResourceOwner: "org1", }, - TOSLink: "TOSLink", - PrivacyLink: "PrivacyLink", - HelpLink: "HelpLink", + TOSLink: "TOSLink", + PrivacyLink: "PrivacyLink", + HelpLink: "HelpLink", + SupportEmail: "support@example.com", }, }, }, { - name: "add policy empty links, ok", + name: "wrong email, can't add policy", + fields: fields{ + eventstore: eventstoreExpect( + t, + ), + }, + args: args{ + ctx: context.Background(), + orgID: "org1", + policy: &domain.PrivacyPolicy{ + TOSLink: "TOSLink", + PrivacyLink: "PrivacyLink", + HelpLink: "HelpLink", + SupportEmail: "wrong email", + }, + }, + res: res{ + err: caos_errs.IsErrorInvalidArgument, + }, + }, + { + name: "add policy empty links and empty support email, ok", fields: fields{ eventstore: eventstoreExpect( t, @@ -138,6 +165,7 @@ func TestCommandSide_AddPrivacyPolicy(t *testing.T) { "", "", "", + "", ), ), }, @@ -148,9 +176,10 @@ func TestCommandSide_AddPrivacyPolicy(t *testing.T) { ctx: context.Background(), orgID: "org1", policy: &domain.PrivacyPolicy{ - TOSLink: "", - PrivacyLink: "", - HelpLink: "", + TOSLink: "", + PrivacyLink: "", + HelpLink: "", + SupportEmail: "", }, }, res: res{ @@ -159,9 +188,10 @@ func TestCommandSide_AddPrivacyPolicy(t *testing.T) { AggregateID: "org1", ResourceOwner: "org1", }, - TOSLink: "", - PrivacyLink: "", - HelpLink: "", + TOSLink: "", + PrivacyLink: "", + HelpLink: "", + SupportEmail: "", }, }, }, @@ -214,9 +244,10 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) { args: args{ ctx: context.Background(), policy: &domain.PrivacyPolicy{ - TOSLink: "TOSLink", - PrivacyLink: "PrivacyLink", - HelpLink: "HelpLink", + TOSLink: "TOSLink", + PrivacyLink: "PrivacyLink", + HelpLink: "HelpLink", + SupportEmail: "support@example.com", }, }, res: res{ @@ -235,9 +266,10 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) { ctx: context.Background(), orgID: "org1", policy: &domain.PrivacyPolicy{ - TOSLink: "TOSLink", - PrivacyLink: "PrivacyLink", - HelpLink: "HelpLink", + TOSLink: "TOSLink", + PrivacyLink: "PrivacyLink", + HelpLink: "HelpLink", + SupportEmail: "support@example.com", }, }, res: res{ @@ -256,6 +288,7 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) { "TOSLink", "PrivacyLink", "HelpLink", + "support@example.com", ), ), ), @@ -265,15 +298,32 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) { ctx: context.Background(), orgID: "org1", policy: &domain.PrivacyPolicy{ - TOSLink: "TOSLink", - PrivacyLink: "PrivacyLink", - HelpLink: "HelpLink", + TOSLink: "TOSLink", + PrivacyLink: "PrivacyLink", + HelpLink: "HelpLink", + SupportEmail: "support@example.com", }, }, res: res{ err: caos_errs.IsPreconditionFailed, }, }, + { + name: "wrong email, can't change policy", + args: args{ + ctx: context.Background(), + orgID: "org1", + policy: &domain.PrivacyPolicy{ + TOSLink: "TOSLinkChange", + PrivacyLink: "PrivacyLinkChange", + HelpLink: "HelpLinkChange", + SupportEmail: "wrong email", + }, + }, + res: res{ + err: caos_errs.IsErrorInvalidArgument, + }, + }, { name: "change, ok", fields: fields{ @@ -286,13 +336,14 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) { "TOSLink", "PrivacyLink", "HelpLink", + "support@example.com", ), ), ), expectPush( []*repository.Event{ eventFromEventPusher( - newPrivacyPolicyChangedEvent(context.Background(), "org1", "TOSLinkChange", "PrivacyLinkChange", "HelpLinkChange"), + newPrivacyPolicyChangedEvent(context.Background(), "org1", "TOSLinkChange", "PrivacyLinkChange", "HelpLinkChange", "support2@example.com"), ), }, ), @@ -302,9 +353,10 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) { ctx: context.Background(), orgID: "org1", policy: &domain.PrivacyPolicy{ - TOSLink: "TOSLinkChange", - PrivacyLink: "PrivacyLinkChange", - HelpLink: "HelpLinkChange", + TOSLink: "TOSLinkChange", + PrivacyLink: "PrivacyLinkChange", + HelpLink: "HelpLinkChange", + SupportEmail: "support2@example.com", }, }, res: res{ @@ -313,9 +365,10 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) { AggregateID: "org1", ResourceOwner: "org1", }, - TOSLink: "TOSLinkChange", - PrivacyLink: "PrivacyLinkChange", - HelpLink: "HelpLinkChange", + TOSLink: "TOSLinkChange", + PrivacyLink: "PrivacyLinkChange", + HelpLink: "HelpLinkChange", + SupportEmail: "support2@example.com", }, }, }, @@ -331,13 +384,14 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) { "TOSLink", "PrivacyLink", "HelpLink", + "support@example.com", ), ), ), expectPush( []*repository.Event{ eventFromEventPusher( - newPrivacyPolicyChangedEvent(context.Background(), "org1", "", "", ""), + newPrivacyPolicyChangedEvent(context.Background(), "org1", "", "", "", ""), ), }, ), @@ -347,9 +401,10 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) { ctx: context.Background(), orgID: "org1", policy: &domain.PrivacyPolicy{ - TOSLink: "", - PrivacyLink: "", - HelpLink: "", + TOSLink: "", + PrivacyLink: "", + HelpLink: "", + SupportEmail: "", }, }, res: res{ @@ -358,9 +413,10 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) { AggregateID: "org1", ResourceOwner: "org1", }, - TOSLink: "", - PrivacyLink: "", - HelpLink: "", + TOSLink: "", + PrivacyLink: "", + HelpLink: "", + SupportEmail: "", }, }, }, @@ -444,6 +500,7 @@ func TestCommandSide_RemovePrivacyPolicy(t *testing.T) { "TOSLink", "PrivacyLink", "HelpLink", + "support@example.com", ), ), ), @@ -487,13 +544,14 @@ func TestCommandSide_RemovePrivacyPolicy(t *testing.T) { } } -func newPrivacyPolicyChangedEvent(ctx context.Context, orgID string, tosLink, privacyLink, helpLink string) *org.PrivacyPolicyChangedEvent { +func newPrivacyPolicyChangedEvent(ctx context.Context, orgID string, tosLink, privacyLink, helpLink, supportEmail string) *org.PrivacyPolicyChangedEvent { event, _ := org.NewPrivacyPolicyChangedEvent(ctx, &org.NewAggregate(orgID).Aggregate, []policy.PrivacyPolicyChanges{ policy.ChangeTOSLink(tosLink), policy.ChangePrivacyLink(privacyLink), policy.ChangeHelpLink(helpLink), + policy.ChangeSupportEmail(domain.EmailAddress(supportEmail)), }, ) return event diff --git a/internal/command/policy_privacy_model.go b/internal/command/policy_privacy_model.go index 2283ee0cf4..1831176d7a 100644 --- a/internal/command/policy_privacy_model.go +++ b/internal/command/policy_privacy_model.go @@ -9,10 +9,11 @@ import ( type PrivacyPolicyWriteModel struct { eventstore.WriteModel - TOSLink string - PrivacyLink string - HelpLink string - State domain.PolicyState + TOSLink string + PrivacyLink string + HelpLink string + SupportEmail domain.EmailAddress + State domain.PolicyState } func (wm *PrivacyPolicyWriteModel) Reduce() error { @@ -22,6 +23,7 @@ func (wm *PrivacyPolicyWriteModel) Reduce() error { wm.TOSLink = e.TOSLink wm.PrivacyLink = e.PrivacyLink wm.HelpLink = e.HelpLink + wm.SupportEmail = e.SupportEmail wm.State = domain.PolicyStateActive case *policy.PrivacyPolicyChangedEvent: if e.PrivacyLink != nil { @@ -33,6 +35,9 @@ func (wm *PrivacyPolicyWriteModel) Reduce() error { if e.HelpLink != nil { wm.HelpLink = *e.HelpLink } + if e.SupportEmail != nil { + wm.SupportEmail = *e.SupportEmail + } case *policy.PrivacyPolicyRemovedEvent: wm.State = domain.PolicyStateRemoved } diff --git a/internal/domain/custom_login_text.go b/internal/domain/custom_login_text.go index d459dfe643..18d999ce93 100644 --- a/internal/domain/custom_login_text.go +++ b/internal/domain/custom_login_text.go @@ -296,6 +296,7 @@ const ( LoginKeyFooterTOS = LoginKeyFooter + "Tos" LoginKeyFooterPrivacyPolicy = LoginKeyFooter + "PrivacyPolicy" LoginKeyFooterHelp = LoginKeyFooter + "Help" + LoginKeyFooterSupportEmail = LoginKeyFooter + "SupportEmail" ) type CustomLoginText struct { @@ -639,6 +640,7 @@ type FooterText struct { TOS string PrivacyPolicy string Help string + SupportEmail string } type PasswordlessPromptScreenText struct { diff --git a/internal/domain/policy_privacy.go b/internal/domain/policy_privacy.go index b018c73f89..0851582815 100644 --- a/internal/domain/policy_privacy.go +++ b/internal/domain/policy_privacy.go @@ -10,7 +10,8 @@ type PrivacyPolicy struct { State PolicyState Default bool - TOSLink string - PrivacyLink string - HelpLink string + TOSLink string + PrivacyLink string + HelpLink string + SupportEmail EmailAddress } diff --git a/internal/iam/model/privacy_policy_view.go b/internal/iam/model/privacy_policy_view.go index 92d5031d4f..6d40dd3937 100644 --- a/internal/iam/model/privacy_policy_view.go +++ b/internal/iam/model/privacy_policy_view.go @@ -10,6 +10,7 @@ type PrivacyPolicyView struct { AggregateID string TOSLink string PrivacyLink string + SupportEmail string Default bool CreationDate time.Time diff --git a/internal/query/custom_text.go b/internal/query/custom_text.go index dcf9628d2b..415cbe3023 100644 --- a/internal/query/custom_text.go +++ b/internal/query/custom_text.go @@ -1181,4 +1181,7 @@ func footerKeyToDomain(text *CustomText, result *domain.CustomLoginText) { if text.Key == domain.LoginKeyFooterHelp { result.Footer.Help = text.Text } + if text.Key == domain.LoginKeyFooterSupportEmail { + result.Footer.SupportEmail = text.Text + } } diff --git a/internal/query/privacy_policy.go b/internal/query/privacy_policy.go index 0cbfe9f287..2874c6d22a 100644 --- a/internal/query/privacy_policy.go +++ b/internal/query/privacy_policy.go @@ -24,9 +24,10 @@ type PrivacyPolicy struct { ResourceOwner string State domain.PolicyState - TOSLink string - PrivacyLink string - HelpLink string + TOSLink string + PrivacyLink string + HelpLink string + SupportEmail domain.EmailAddress IsDefault bool } @@ -72,6 +73,10 @@ var ( name: projection.PrivacyPolicyHelpLinkCol, table: privacyTable, } + PrivacyColSupportEmail = Column{ + name: projection.PrivacyPolicySupportEmailCol, + table: privacyTable, + } PrivacyColIsDefault = Column{ name: projection.PrivacyPolicyIsDefaultCol, table: privacyTable, @@ -148,6 +153,7 @@ func preparePrivacyPolicyQuery(ctx context.Context, db prepareDatabase) (sq.Sele PrivacyColPrivacyLink.identifier(), PrivacyColTOSLink.identifier(), PrivacyColHelpLink.identifier(), + PrivacyColSupportEmail.identifier(), PrivacyColIsDefault.identifier(), PrivacyColState.identifier(), ). @@ -164,6 +170,7 @@ func preparePrivacyPolicyQuery(ctx context.Context, db prepareDatabase) (sq.Sele &policy.PrivacyLink, &policy.TOSLink, &policy.HelpLink, + &policy.SupportEmail, &policy.IsDefault, &policy.State, ) @@ -179,9 +186,10 @@ func preparePrivacyPolicyQuery(ctx context.Context, db prepareDatabase) (sq.Sele func (p *PrivacyPolicy) ToDomain() *domain.PrivacyPolicy { return &domain.PrivacyPolicy{ - TOSLink: p.TOSLink, - PrivacyLink: p.PrivacyLink, - HelpLink: p.HelpLink, - Default: p.IsDefault, + TOSLink: p.TOSLink, + PrivacyLink: p.PrivacyLink, + HelpLink: p.HelpLink, + SupportEmail: p.SupportEmail, + Default: p.IsDefault, } } diff --git a/internal/query/privacy_policy_test.go b/internal/query/privacy_policy_test.go index b9966c2b7d..ea68af11b2 100644 --- a/internal/query/privacy_policy_test.go +++ b/internal/query/privacy_policy_test.go @@ -13,17 +13,18 @@ import ( ) var ( - preparePrivacyPolicyStmt = `SELECT projections.privacy_policies2.id,` + - ` projections.privacy_policies2.sequence,` + - ` projections.privacy_policies2.creation_date,` + - ` projections.privacy_policies2.change_date,` + - ` projections.privacy_policies2.resource_owner,` + - ` projections.privacy_policies2.privacy_link,` + - ` projections.privacy_policies2.tos_link,` + - ` projections.privacy_policies2.help_link,` + - ` projections.privacy_policies2.is_default,` + - ` projections.privacy_policies2.state` + - ` FROM projections.privacy_policies2` + + preparePrivacyPolicyStmt = `SELECT projections.privacy_policies3.id,` + + ` projections.privacy_policies3.sequence,` + + ` projections.privacy_policies3.creation_date,` + + ` projections.privacy_policies3.change_date,` + + ` projections.privacy_policies3.resource_owner,` + + ` projections.privacy_policies3.privacy_link,` + + ` projections.privacy_policies3.tos_link,` + + ` projections.privacy_policies3.help_link,` + + ` projections.privacy_policies3.support_email,` + + ` projections.privacy_policies3.is_default,` + + ` projections.privacy_policies3.state` + + ` FROM projections.privacy_policies3` + ` AS OF SYSTEM TIME '-1 ms'` preparePrivacyPolicyCols = []string{ "id", @@ -34,6 +35,7 @@ var ( "privacy_link", "tos_link", "help_link", + "support_email", "is_default", "state", } @@ -84,6 +86,7 @@ func Test_PrivacyPolicyPrepares(t *testing.T) { "privacy.ch", "tos.ch", "help.ch", + "support@example.com", true, domain.PolicyStateActive, }, @@ -99,6 +102,7 @@ func Test_PrivacyPolicyPrepares(t *testing.T) { PrivacyLink: "privacy.ch", TOSLink: "tos.ch", HelpLink: "help.ch", + SupportEmail: "support@example.com", IsDefault: true, }, }, diff --git a/internal/query/projection/privacy_policy.go b/internal/query/projection/privacy_policy.go index ca0b8b3bab..525363e42f 100644 --- a/internal/query/projection/privacy_policy.go +++ b/internal/query/projection/privacy_policy.go @@ -14,7 +14,7 @@ import ( ) const ( - PrivacyPolicyTable = "projections.privacy_policies2" + PrivacyPolicyTable = "projections.privacy_policies3" PrivacyPolicyIDCol = "id" PrivacyPolicyCreationDateCol = "creation_date" @@ -27,6 +27,7 @@ const ( PrivacyPolicyPrivacyLinkCol = "privacy_link" PrivacyPolicyTOSLinkCol = "tos_link" PrivacyPolicyHelpLinkCol = "help_link" + PrivacyPolicySupportEmailCol = "support_email" PrivacyPolicyOwnerRemovedCol = "owner_removed" ) @@ -51,6 +52,7 @@ func newPrivacyPolicyProjection(ctx context.Context, config crdb.StatementHandle crdb.NewColumn(PrivacyPolicyPrivacyLinkCol, crdb.ColumnTypeText), crdb.NewColumn(PrivacyPolicyTOSLinkCol, crdb.ColumnTypeText), crdb.NewColumn(PrivacyPolicyHelpLinkCol, crdb.ColumnTypeText), + crdb.NewColumn(PrivacyPolicySupportEmailCol, crdb.ColumnTypeText), crdb.NewColumn(PrivacyPolicyOwnerRemovedCol, crdb.ColumnTypeBool, crdb.Default(false)), }, crdb.NewPrimaryKey(PrivacyPolicyInstanceIDCol, PrivacyPolicyIDCol), @@ -128,6 +130,7 @@ func (p *privacyPolicyProjection) reduceAdded(event eventstore.Event) (*handler. handler.NewCol(PrivacyPolicyPrivacyLinkCol, policyEvent.PrivacyLink), handler.NewCol(PrivacyPolicyTOSLinkCol, policyEvent.TOSLink), handler.NewCol(PrivacyPolicyHelpLinkCol, policyEvent.HelpLink), + handler.NewCol(PrivacyPolicySupportEmailCol, policyEvent.SupportEmail), handler.NewCol(PrivacyPolicyIsDefaultCol, isDefault), handler.NewCol(PrivacyPolicyResourceOwnerCol, policyEvent.Aggregate().ResourceOwner), handler.NewCol(PrivacyPolicyInstanceIDCol, policyEvent.Aggregate().InstanceID), @@ -157,6 +160,9 @@ func (p *privacyPolicyProjection) reduceChanged(event eventstore.Event) (*handle if policyEvent.HelpLink != nil { cols = append(cols, handler.NewCol(PrivacyPolicyHelpLinkCol, *policyEvent.HelpLink)) } + if policyEvent.SupportEmail != nil { + cols = append(cols, handler.NewCol(PrivacyPolicySupportEmailCol, *policyEvent.SupportEmail)) + } return crdb.NewUpdateStatement( &policyEvent, cols, diff --git a/internal/query/projection/privacy_policy_test.go b/internal/query/projection/privacy_policy_test.go index 61866e01b5..f976c64e9b 100644 --- a/internal/query/projection/privacy_policy_test.go +++ b/internal/query/projection/privacy_policy_test.go @@ -31,8 +31,8 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) { []byte(`{ "tosLink": "http://tos.link", "privacyLink": "http://privacy.link", - "helpLink": "http://help.link" -}`), + "helpLink": "http://help.link", + "supportEmail": "support@example.com"}`), ), org.PrivacyPolicyAddedEventMapper), }, reduce: (&privacyPolicyProjection{}).reduceAdded, @@ -43,7 +43,7 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.privacy_policies2 (creation_date, change_date, sequence, id, state, privacy_link, tos_link, help_link, is_default, resource_owner, instance_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", + expectedStmt: "INSERT INTO projections.privacy_policies3 (creation_date, change_date, sequence, id, state, privacy_link, tos_link, help_link, support_email, is_default, resource_owner, instance_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)", expectedArgs: []interface{}{ anyArg{}, anyArg{}, @@ -53,6 +53,7 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) { "http://privacy.link", "http://tos.link", "http://help.link", + domain.EmailAddress("support@example.com"), false, "ro-id", "instance-id", @@ -72,8 +73,8 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) { []byte(`{ "tosLink": "http://tos.link", "privacyLink": "http://privacy.link", - "helpLink": "http://help.link" - }`), + "helpLink": "http://help.link", + "supportEmail": "support@example.com"}`), ), org.PrivacyPolicyChangedEventMapper), }, want: wantReduce{ @@ -83,13 +84,14 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.privacy_policies2 SET (change_date, sequence, privacy_link, tos_link, help_link) = ($1, $2, $3, $4, $5) WHERE (id = $6) AND (instance_id = $7)", + expectedStmt: "UPDATE projections.privacy_policies3 SET (change_date, sequence, privacy_link, tos_link, help_link, support_email) = ($1, $2, $3, $4, $5, $6) WHERE (id = $7) AND (instance_id = $8)", expectedArgs: []interface{}{ anyArg{}, uint64(15), "http://privacy.link", "http://tos.link", "http://help.link", + domain.EmailAddress("support@example.com"), "agg-id", "instance-id", }, @@ -115,7 +117,7 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.privacy_policies2 WHERE (id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.privacy_policies3 WHERE (id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -141,7 +143,7 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.privacy_policies2 WHERE (instance_id = $1)", + expectedStmt: "DELETE FROM projections.privacy_policies3 WHERE (instance_id = $1)", expectedArgs: []interface{}{ "agg-id", }, @@ -160,8 +162,8 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) { []byte(`{ "tosLink": "http://tos.link", "privacyLink": "http://privacy.link", - "helpLink": "http://help.link" - }`), + "helpLink": "http://help.link", + "supportEmail": "support@example.com"}`), ), instance.PrivacyPolicyAddedEventMapper), }, want: wantReduce{ @@ -171,7 +173,7 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.privacy_policies2 (creation_date, change_date, sequence, id, state, privacy_link, tos_link, help_link, is_default, resource_owner, instance_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)", + expectedStmt: "INSERT INTO projections.privacy_policies3 (creation_date, change_date, sequence, id, state, privacy_link, tos_link, help_link, support_email, is_default, resource_owner, instance_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)", expectedArgs: []interface{}{ anyArg{}, anyArg{}, @@ -181,6 +183,7 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) { "http://privacy.link", "http://tos.link", "http://help.link", + domain.EmailAddress("support@example.com"), true, "ro-id", "instance-id", @@ -200,8 +203,8 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) { []byte(`{ "tosLink": "http://tos.link", "privacyLink": "http://privacy.link", - "helpLink": "http://help.link" - }`), + "helpLink": "http://help.link", + "supportEmail": "support@example.com"}`), ), instance.PrivacyPolicyChangedEventMapper), }, want: wantReduce{ @@ -211,13 +214,14 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.privacy_policies2 SET (change_date, sequence, privacy_link, tos_link, help_link) = ($1, $2, $3, $4, $5) WHERE (id = $6) AND (instance_id = $7)", + expectedStmt: "UPDATE projections.privacy_policies3 SET (change_date, sequence, privacy_link, tos_link, help_link, support_email) = ($1, $2, $3, $4, $5, $6) WHERE (id = $7) AND (instance_id = $8)", expectedArgs: []interface{}{ anyArg{}, uint64(15), "http://privacy.link", "http://tos.link", "http://help.link", + domain.EmailAddress("support@example.com"), "agg-id", "instance-id", }, @@ -243,7 +247,7 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.privacy_policies2 SET (change_date, sequence, owner_removed) = ($1, $2, $3) WHERE (instance_id = $4) AND (resource_owner = $5)", + expectedStmt: "UPDATE projections.privacy_policies3 SET (change_date, sequence, owner_removed) = ($1, $2, $3) WHERE (instance_id = $4) AND (resource_owner = $5)", expectedArgs: []interface{}{ anyArg{}, uint64(15), diff --git a/internal/repository/instance/policy_privacy.go b/internal/repository/instance/policy_privacy.go index c629894eea..2851ba784b 100644 --- a/internal/repository/instance/policy_privacy.go +++ b/internal/repository/instance/policy_privacy.go @@ -3,8 +3,8 @@ package instance import ( "context" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/eventstore/repository" "github.com/zitadel/zitadel/internal/repository/policy" ) @@ -24,6 +24,7 @@ func NewPrivacyPolicyAddedEvent( tosLink, privacyLink, helpLink string, + supportEmail domain.EmailAddress, ) *PrivacyPolicyAddedEvent { return &PrivacyPolicyAddedEvent{ PrivacyPolicyAddedEvent: *policy.NewPrivacyPolicyAddedEvent( @@ -33,7 +34,8 @@ func NewPrivacyPolicyAddedEvent( PrivacyPolicyAddedEventType), tosLink, privacyLink, - helpLink), + helpLink, + supportEmail), } } diff --git a/internal/repository/org/policy_privacy.go b/internal/repository/org/policy_privacy.go index 78cb7beaa9..380711a3fd 100644 --- a/internal/repository/org/policy_privacy.go +++ b/internal/repository/org/policy_privacy.go @@ -3,8 +3,8 @@ package org import ( "context" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/eventstore/repository" "github.com/zitadel/zitadel/internal/repository/policy" ) @@ -25,6 +25,7 @@ func NewPrivacyPolicyAddedEvent( tosLink, privacyLink, helpLink string, + supportEmail domain.EmailAddress, ) *PrivacyPolicyAddedEvent { return &PrivacyPolicyAddedEvent{ PrivacyPolicyAddedEvent: *policy.NewPrivacyPolicyAddedEvent( @@ -34,7 +35,8 @@ func NewPrivacyPolicyAddedEvent( PrivacyPolicyAddedEventType), tosLink, privacyLink, - helpLink), + helpLink, + supportEmail), } } diff --git a/internal/repository/policy/policy_privacy.go b/internal/repository/policy/policy_privacy.go index faccca0924..a168a91cf3 100644 --- a/internal/repository/policy/policy_privacy.go +++ b/internal/repository/policy/policy_privacy.go @@ -3,9 +3,9 @@ package policy import ( "encoding/json" - "github.com/zitadel/zitadel/internal/eventstore" - + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/errors" + "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore/repository" ) @@ -18,9 +18,10 @@ const ( type PrivacyPolicyAddedEvent struct { eventstore.BaseEvent `json:"-"` - TOSLink string `json:"tosLink,omitempty"` - PrivacyLink string `json:"privacyLink,omitempty"` - HelpLink string `json:"helpLink,omitempty"` + TOSLink string `json:"tosLink,omitempty"` + PrivacyLink string `json:"privacyLink,omitempty"` + HelpLink string `json:"helpLink,omitempty"` + SupportEmail domain.EmailAddress `json:"supportEmail,omitempty"` } func (e *PrivacyPolicyAddedEvent) Data() interface{} { @@ -36,12 +37,14 @@ func NewPrivacyPolicyAddedEvent( tosLink, privacyLink, helpLink string, + supportEmail domain.EmailAddress, ) *PrivacyPolicyAddedEvent { return &PrivacyPolicyAddedEvent{ - BaseEvent: *base, - TOSLink: tosLink, - PrivacyLink: privacyLink, - HelpLink: helpLink, + BaseEvent: *base, + TOSLink: tosLink, + PrivacyLink: privacyLink, + HelpLink: helpLink, + SupportEmail: supportEmail, } } @@ -60,9 +63,10 @@ func PrivacyPolicyAddedEventMapper(event *repository.Event) (eventstore.Event, e type PrivacyPolicyChangedEvent struct { eventstore.BaseEvent `json:"-"` - TOSLink *string `json:"tosLink,omitempty"` - PrivacyLink *string `json:"privacyLink,omitempty"` - HelpLink *string `json:"helpLink,omitempty"` + TOSLink *string `json:"tosLink,omitempty"` + PrivacyLink *string `json:"privacyLink,omitempty"` + HelpLink *string `json:"helpLink,omitempty"` + SupportEmail *domain.EmailAddress `json:"supportEmail,omitempty"` } func (e *PrivacyPolicyChangedEvent) Data() interface{} { @@ -109,6 +113,12 @@ func ChangeHelpLink(helpLink string) func(*PrivacyPolicyChangedEvent) { } } +func ChangeSupportEmail(supportEmail domain.EmailAddress) func(*PrivacyPolicyChangedEvent) { + return func(e *PrivacyPolicyChangedEvent) { + e.SupportEmail = &supportEmail + } +} + func PrivacyPolicyChangedEventMapper(event *repository.Event) (eventstore.Event, error) { e := &PrivacyPolicyChangedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(event), diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto index beae4750ec..5bcc5284ad 100644 --- a/proto/zitadel/admin.proto +++ b/proto/zitadel/admin.proto @@ -5460,6 +5460,13 @@ message UpdatePrivacyPolicyRequest { example: "\"https://zitadel.com/docs/manuals/introduction\""; } ]; + string support_email = 4 [ + (validate.rules).string = {ignore_empty: true, max_len: 320, email: true}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"support-email@test.com\""; + description: "help / support email address." + } + ]; } message UpdatePrivacyPolicyResponse { diff --git a/proto/zitadel/management.proto b/proto/zitadel/management.proto index 73fe75eb4a..71dd9b99ff 100644 --- a/proto/zitadel/management.proto +++ b/proto/zitadel/management.proto @@ -9834,6 +9834,13 @@ message AddCustomPrivacyPolicyRequest { example: "\"https://zitadel.com/docs/manuals/introduction\""; } ]; + string support_email = 4 [ + (validate.rules).string = {ignore_empty: true, max_len: 320, email: true}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"support-email@test.com\""; + description: "help / support email address." + } + ]; } message AddCustomPrivacyPolicyResponse { @@ -9859,6 +9866,13 @@ message UpdateCustomPrivacyPolicyRequest { example: "\"https://zitadel.com/docs/manuals/introduction\""; } ]; + string support_email = 4 [ + (validate.rules).string = {ignore_empty: true, max_len: 320, email: true}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"support-email@test.com\""; + description: "help / support email address." + } + ]; } message UpdateCustomPrivacyPolicyResponse { diff --git a/proto/zitadel/policy.proto b/proto/zitadel/policy.proto index c7d87d092c..c6cbc7a462 100644 --- a/proto/zitadel/policy.proto +++ b/proto/zitadel/policy.proto @@ -4,6 +4,7 @@ import "zitadel/object.proto"; import "zitadel/idp.proto"; import "google/protobuf/duration.proto"; import "protoc-gen-openapiv2/options/annotations.proto"; +import "validate/validate.proto"; package zitadel.policy.v1; @@ -345,6 +346,13 @@ message PrivacyPolicy { example: "\"https://zitadel.com/docs/manuals/introduction\""; } ]; + string support_email = 6 [ + (validate.rules).string = {ignore_empty: true, max_len: 320, email: true}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"support-email@test.com\""; + description: "help / support email address." + } + ]; } message NotificationPolicy { diff --git a/proto/zitadel/text.proto b/proto/zitadel/text.proto index 05689f2aff..602f7f9caf 100644 --- a/proto/zitadel/text.proto +++ b/proto/zitadel/text.proto @@ -394,11 +394,12 @@ message LogoutDoneScreenText { } message FooterText { - reserved 2, 4, 6; + reserved 2, 4, 6, 8; reserved "tos_link", "privacy_policy_link", "help_link"; string tos = 1 [(validate.rules).string = {max_len: 200}]; string privacy_policy = 3 [(validate.rules).string = {max_len: 200}]; string help = 5 [(validate.rules).string = {max_len: 200}]; + string support_email = 7 [(validate.rules).string = {max_len: 200}]; } message PasswordlessPromptScreenText {