feat: add Help/Support e-mail for instance/org (#5445)

feat: help and support email in privacy policy
This commit is contained in:
Miguel Cabrerizo 2023-03-28 21:36:52 +02:00 committed by GitHub
parent 12a7c4b994
commit 1b9cea0e0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 572 additions and 187 deletions

View File

@ -499,6 +499,7 @@ DefaultInstance:
TOSLink: https://zitadel.com/docs/legal/terms-of-service TOSLink: https://zitadel.com/docs/legal/terms-of-service
PrivacyLink: https://zitadel.com/docs/legal/privacy-policy PrivacyLink: https://zitadel.com/docs/legal/privacy-policy
HelpLink: "" HelpLink: ""
SupportEmail: ""
NotificationPolicy: NotificationPolicy:
PasswordChange: true PasswordChange: true
LabelPolicy: LabelPolicy:

View File

@ -73,6 +73,7 @@ export function mapRequestValues(map: Partial<Map>, req: Req): Req {
r3.setHelp(map.footerText?.help ?? ''); r3.setHelp(map.footerText?.help ?? '');
r3.setPrivacyPolicy(map.footerText?.privacyPolicy ?? ''); r3.setPrivacyPolicy(map.footerText?.privacyPolicy ?? '');
r3.setTos(map.footerText?.tos ?? ''); r3.setTos(map.footerText?.tos ?? '');
r3.setSupportEmail(map.footerText?.supportEmail ?? '');
req.setFooterText(r3); req.setFooterText(r3);
const r4 = new InitMFADoneScreenText(); const r4 = new InitMFADoneScreenText();

View File

@ -36,6 +36,12 @@
<input cnslInput name="helpLink" formControlName="helpLink" /> <input cnslInput name="helpLink" formControlName="helpLink" />
<template [ngTemplateOutlet]="templateRef" [ngTemplateOutletContext]="{ key: 'helpLink' }"></template> <template [ngTemplateOutlet]="templateRef" [ngTemplateOutletContext]="{ key: 'helpLink' }"></template>
</cnsl-form-field> </cnsl-form-field>
<cnsl-form-field class="privacy-policy-formfield">
<cnsl-label>{{ 'POLICY.PRIVACY_POLICY.SUPPORTEMAIL' | translate }}</cnsl-label>
<input cnslInput name="supportEmail" formControlName="supportEmail" />
<template [ngTemplateOutlet]="templateRef" [ngTemplateOutletContext]="{ key: 'supportEmail' }"></template>
</cnsl-form-field>
</form> </form>
</div> </div>

View File

@ -7,9 +7,10 @@
display: grid; display: grid;
grid-template-columns: 1fr; grid-template-columns: 1fr;
column-gap: 1rem; column-gap: 1rem;
width: 75%;
@media only screen and (min-width: 800px) { @media only screen and (min-width: 800px) {
grid-template-columns: 1fr 1fr 1fr; grid-template-columns: 1fr 1fr;
} }
} }

View File

@ -60,6 +60,7 @@ export class PrivacyPolicyComponent implements OnInit, OnDestroy {
tosLink: ['', []], tosLink: ['', []],
privacyLink: ['', []], privacyLink: ['', []],
helpLink: ['', []], helpLink: ['', []],
supportEmail: ['', []],
}); });
this.canWrite$.pipe(take(1)).subscribe((canWrite) => { this.canWrite$.pipe(take(1)).subscribe((canWrite) => {
@ -105,6 +106,7 @@ export class PrivacyPolicyComponent implements OnInit, OnDestroy {
tosLink: '', tosLink: '',
privacyLink: '', privacyLink: '',
helpLink: '', helpLink: '',
supportEmail: '',
}); });
} }
}) })
@ -114,6 +116,7 @@ export class PrivacyPolicyComponent implements OnInit, OnDestroy {
tosLink: '', tosLink: '',
privacyLink: '', privacyLink: '',
helpLink: '', helpLink: '',
supportEmail: '',
}); });
}); });
} }
@ -125,6 +128,7 @@ export class PrivacyPolicyComponent implements OnInit, OnDestroy {
req.setPrivacyLink(this.form.get('privacyLink')?.value); req.setPrivacyLink(this.form.get('privacyLink')?.value);
req.setTosLink(this.form.get('tosLink')?.value); req.setTosLink(this.form.get('tosLink')?.value);
req.setHelpLink(this.form.get('helpLink')?.value); req.setHelpLink(this.form.get('helpLink')?.value);
req.setSupportEmail(this.form.get('supportEmail')?.value);
(this.service as ManagementService) (this.service as ManagementService)
.addCustomPrivacyPolicy(req) .addCustomPrivacyPolicy(req)
.then(() => { .then(() => {
@ -137,6 +141,7 @@ export class PrivacyPolicyComponent implements OnInit, OnDestroy {
req.setPrivacyLink(this.form.get('privacyLink')?.value); req.setPrivacyLink(this.form.get('privacyLink')?.value);
req.setTosLink(this.form.get('tosLink')?.value); req.setTosLink(this.form.get('tosLink')?.value);
req.setHelpLink(this.form.get('helpLink')?.value); req.setHelpLink(this.form.get('helpLink')?.value);
req.setSupportEmail(this.form.get('supportEmail')?.value);
(this.service as ManagementService) (this.service as ManagementService)
.updateCustomPrivacyPolicy(req) .updateCustomPrivacyPolicy(req)
@ -151,6 +156,7 @@ export class PrivacyPolicyComponent implements OnInit, OnDestroy {
req.setPrivacyLink(this.form.get('privacyLink')?.value); req.setPrivacyLink(this.form.get('privacyLink')?.value);
req.setTosLink(this.form.get('tosLink')?.value); req.setTosLink(this.form.get('tosLink')?.value);
req.setHelpLink(this.form.get('helpLink')?.value); req.setHelpLink(this.form.get('helpLink')?.value);
req.setSupportEmail(this.form.get('supportEmail')?.value);
(this.service as AdminService) (this.service as AdminService)
.updatePrivacyPolicy(req) .updatePrivacyPolicy(req)

View File

@ -53,6 +53,6 @@
<cnsl-login-texts [serviceType]="serviceType"></cnsl-login-texts> <cnsl-login-texts [serviceType]="serviceType"></cnsl-login-texts>
</ng-container> </ng-container>
<ng-container *ngIf="currentSetting === 'privacypolicy'"> <ng-container *ngIf="currentSetting === 'privacypolicy'">
<cnsl-privacy-policy [serviceType]="PolicyComponentServiceType.ADMIN"></cnsl-privacy-policy> <cnsl-privacy-policy [serviceType]="serviceType"></cnsl-privacy-policy>
</ng-container> </ng-container>
</cnsl-sidenav> </cnsl-sidenav>

View File

@ -1185,6 +1185,7 @@
"TOSLINK": "Link zu den Allgemeinen Geschäftsbedingungen", "TOSLINK": "Link zu den Allgemeinen Geschäftsbedingungen",
"POLICYLINK": "Link zur den Datenschutzrichtlinien", "POLICYLINK": "Link zur den Datenschutzrichtlinien",
"HELPLINK": "Link zur Hilfestellung", "HELPLINK": "Link zur Hilfestellung",
"SUPPORTEMAIL": "Support E-Mail",
"SAVED": "Saved successfully!", "SAVED": "Saved successfully!",
"RESET_TITLE": "Standardwerte wiederherstellen", "RESET_TITLE": "Standardwerte wiederherstellen",
"RESET_DESCRIPTION": "Sie sind im Begriff die Standardlinks für die AGBs und Datenschutzrichtlinie wiederherzustellen. Wollen Sie fortfahren?" "RESET_DESCRIPTION": "Sie sind im Begriff die Standardlinks für die AGBs und Datenschutzrichtlinie wiederherzustellen. Wollen Sie fortfahren?"

View File

@ -1186,6 +1186,7 @@
"TOSLINK": "Link to Terms of Service", "TOSLINK": "Link to Terms of Service",
"POLICYLINK": "Link to Privacy Policy", "POLICYLINK": "Link to Privacy Policy",
"HELPLINK": "Link to Help", "HELPLINK": "Link to Help",
"SUPPORTEMAIL": "Support Email",
"SAVED": "Saved successfully!", "SAVED": "Saved successfully!",
"RESET_TITLE": "Restore Default Values", "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?" "RESET_DESCRIPTION": "You are about to restore the default Links for TOS and Privacy Policy. Do you really want to continue?"

View File

@ -1185,6 +1185,7 @@
"TOSLINK": "Lien vers les conditions d'utilisation", "TOSLINK": "Lien vers les conditions d'utilisation",
"POLICYLINK": "Lien vers la politique de confidentialité", "POLICYLINK": "Lien vers la politique de confidentialité",
"HELPLINK": "Lien vers l'aide", "HELPLINK": "Lien vers l'aide",
"SUPPORTEMAIL": "E-mail d'assistance",
"SAVED": "Enregistré avec succès !", "SAVED": "Enregistré avec succès !",
"RESET_TITLE": "Restaurer les valeurs par défaut", "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 ?" "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 ?"

View File

@ -8,7 +8,7 @@
}, },
"FOOTER": { "FOOTER": {
"LINKS": { "LINKS": {
"CONTACT": "Kontakt", "CONTACT": "Contatto",
"TOS": "Terms of Service", "TOS": "Terms of Service",
"PP": "Privacy Policy" "PP": "Privacy Policy"
}, },
@ -1186,6 +1186,7 @@
"TOSLINK": "Link ai termini di servizio", "TOSLINK": "Link ai termini di servizio",
"POLICYLINK": "Link all'informativa sulla privacy", "POLICYLINK": "Link all'informativa sulla privacy",
"HELPLINK": "link per l'aiuto", "HELPLINK": "link per l'aiuto",
"SUPPORTEMAIL": "e-mail di supporto",
"SAVED": "Salvato con successo!", "SAVED": "Salvato con successo!",
"RESET_TITLE": "Ripristina i valori predefiniti", "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?" "RESET_DESCRIPTION": "Stai per ripristinare i link predefiniti per i TOS e l'informativa sulla privacy. Vuoi davvero continuare?"

View File

@ -1185,6 +1185,7 @@
"TOSLINK": "Link do warunków korzystania", "TOSLINK": "Link do warunków korzystania",
"POLICYLINK": "Link do polityki prywatności", "POLICYLINK": "Link do polityki prywatności",
"HELPLINK": "Link do pomocy", "HELPLINK": "Link do pomocy",
"SUPPORTEMAIL": "E-mail wsparcia",
"SAVED": "Pomyślnie zapisano!", "SAVED": "Pomyślnie zapisano!",
"RESET_TITLE": "Przywróć wartości domyślne", "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ć?" "RESET_DESCRIPTION": "Masz zamiar przywrócić domyślne linki dla TOS i polityki prywatności. Czy na pewno chcesz kontynuować?"

View File

@ -1184,6 +1184,7 @@
"TOSLINK": "链接到服务条款", "TOSLINK": "链接到服务条款",
"POLICYLINK": "链接到隐私政策", "POLICYLINK": "链接到隐私政策",
"HELPLINK": "链接到帮助", "HELPLINK": "链接到帮助",
"SUPPORTEMAIL": "支持邮箱",
"SAVED": "保存成功!", "SAVED": "保存成功!",
"RESET_TITLE": "恢复默认值", "RESET_TITLE": "恢复默认值",
"RESET_DESCRIPTION": "您即将恢复 TOS 和隐私政策的默认链接。你真的要继续吗?" "RESET_DESCRIPTION": "您即将恢复 TOS 和隐私政策的默认链接。你真的要继续吗?"

View File

@ -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) 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)
<img src="/docs/img/guides/console/notification.png" alt="Notification" width="400px" /> <img
src="/docs/img/guides/console/notification.png"
alt="Notification"
width="400px"
/>
### SMTP ### 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 - **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 - **External Login Check Lifetime** specifies after which period a user has to revalidate the Multi Factor during the login process
## Identity Providers ## Identity Providers
You can configure all kinds of external identity providers for identity brokering, which support OIDC (OpenID Connect). 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 ## 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. On register each user has to accept these policies.
This policy can be also be overriden by your organizations. 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. These are the texts for the login. Just like for message texts, you can select the locale on the right.
<img src="/docs/img/guides/console/logintexts.png" alt="Login texts" width="600px" /> <img
src="/docs/img/guides/console/logintexts.png"
alt="Login texts"
width="600px"
/>
## OIDC token lifetimes and expiration ## OIDC token lifetimes and expiration

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -524,6 +524,7 @@ func (s *Server) getPrivacyPolicy(ctx context.Context, orgID string) (_ *managem
TosLink: queriedPrivacy.TOSLink, TosLink: queriedPrivacy.TOSLink,
PrivacyLink: queriedPrivacy.PrivacyLink, PrivacyLink: queriedPrivacy.PrivacyLink,
HelpLink: queriedPrivacy.HelpLink, HelpLink: queriedPrivacy.HelpLink,
SupportEmail: string(queriedPrivacy.SupportEmail),
}, nil }, nil
} }
return nil, nil return nil, nil

View File

@ -10,5 +10,6 @@ func UpdatePrivacyPolicyToDomain(req *admin_pb.UpdatePrivacyPolicyRequest) *doma
TOSLink: req.TosLink, TOSLink: req.TosLink,
PrivacyLink: req.PrivacyLink, PrivacyLink: req.PrivacyLink,
HelpLink: req.HelpLink, HelpLink: req.HelpLink,
SupportEmail: domain.EmailAddress(req.SupportEmail),
} }
} }

View File

@ -10,6 +10,7 @@ func AddPrivacyPolicyToDomain(req *mgmt_pb.AddCustomPrivacyPolicyRequest) *domai
TOSLink: req.TosLink, TOSLink: req.TosLink,
PrivacyLink: req.PrivacyLink, PrivacyLink: req.PrivacyLink,
HelpLink: req.HelpLink, HelpLink: req.HelpLink,
SupportEmail: domain.EmailAddress(req.SupportEmail),
} }
} }
@ -18,5 +19,6 @@ func UpdatePrivacyPolicyToDomain(req *mgmt_pb.UpdateCustomPrivacyPolicyRequest)
TOSLink: req.TosLink, TOSLink: req.TosLink,
PrivacyLink: req.PrivacyLink, PrivacyLink: req.PrivacyLink,
HelpLink: req.HelpLink, HelpLink: req.HelpLink,
SupportEmail: domain.EmailAddress(req.SupportEmail),
} }
} }

View File

@ -12,6 +12,7 @@ func ModelPrivacyPolicyToPb(policy *query.PrivacyPolicy) *policy_pb.PrivacyPolic
TosLink: policy.TOSLink, TosLink: policy.TOSLink,
PrivacyLink: policy.PrivacyLink, PrivacyLink: policy.PrivacyLink,
HelpLink: policy.HelpLink, HelpLink: policy.HelpLink,
SupportEmail: string(policy.SupportEmail),
Details: object.ToViewDetailsPb( Details: object.ToViewDetailsPb(
policy.Sequence, policy.Sequence,
policy.CreationDate, policy.CreationDate,

View File

@ -458,6 +458,7 @@ func FooterTextToPb(text domain.FooterText) *text_pb.FooterText {
Tos: text.TOS, Tos: text.TOS,
PrivacyPolicy: text.PrivacyPolicy, PrivacyPolicy: text.PrivacyPolicy,
Help: text.Help, Help: text.Help,
SupportEmail: text.SupportEmail,
} }
} }
@ -949,5 +950,6 @@ func FooterTextPbToDomain(text *text_pb.FooterText) domain.FooterText {
TOS: text.Tos, TOS: text.Tos,
PrivacyPolicy: text.PrivacyPolicy, PrivacyPolicy: text.PrivacyPolicy,
Help: text.Help, Help: text.Help,
SupportEmail: text.SupportEmail,
} }
} }

View File

@ -442,6 +442,9 @@ func (l *Login) setLinksOnBaseData(baseData baseData, privacyPolicy *domain.Priv
if link, err := templates.ParseTemplateText(privacyPolicy.HelpLink, lang); err == nil { if link, err := templates.ParseTemplateText(privacyPolicy.HelpLink, lang); err == nil {
baseData.HelpLink = link baseData.HelpLink = link
} }
if link, err := templates.ParseTemplateText(string(privacyPolicy.SupportEmail), lang); err == nil {
baseData.SupportEmail = link
}
return baseData return baseData
} }
@ -602,6 +605,7 @@ type baseData struct {
TOSLink string TOSLink string
PrivacyLink string PrivacyLink string
HelpLink string HelpLink string
SupportEmail string
AuthReqID string AuthReqID string
CSRF template.HTML CSRF template.HTML
Nonce string Nonce string

View File

@ -172,6 +172,7 @@ PasswordChange:
NewPasswordConfirmLabel: Passwort Bestätigung NewPasswordConfirmLabel: Passwort Bestätigung
CancelButtonText: abbrechen CancelButtonText: abbrechen
NextButtonText: weiter NextButtonText: weiter
Footer: Fusszeile
PasswordChangeDone: PasswordChangeDone:
Title: Passwort ändern Title: Passwort ändern
@ -318,6 +319,7 @@ Footer:
Tos: AGB Tos: AGB
PrivacyPolicy: Datenschutzerklärung PrivacyPolicy: Datenschutzerklärung
Help: Hilfe Help: Hilfe
SupportEmail: Support E-Mail
Errors: Errors:
Internal: Es ist ein interner Fehler aufgetreten Internal: Es ist ein interner Fehler aufgetreten

View File

@ -172,6 +172,7 @@ PasswordChange:
NewPasswordConfirmLabel: Password confirmation NewPasswordConfirmLabel: Password confirmation
CancelButtonText: cancel CancelButtonText: cancel
NextButtonText: next NextButtonText: next
Footer: Footer
PasswordChangeDone: PasswordChangeDone:
Title: Change Password Title: Change Password
@ -318,6 +319,7 @@ Footer:
Tos: TOS Tos: TOS
PrivacyPolicy: Privacy policy PrivacyPolicy: Privacy policy
Help: Help Help: Help
SupportEmail: Support E-mail
Errors: Errors:
Internal: An internal error occurred Internal: An internal error occurred

View File

@ -172,6 +172,7 @@ PasswordChange:
NewPasswordConfirmLabel: Confirmation du mot de passe NewPasswordConfirmLabel: Confirmation du mot de passe
CancelButtonText: annuler CancelButtonText: annuler
NextButtonText: suivant NextButtonText: suivant
Footer: Bas de page
PasswordChangeDone: PasswordChangeDone:
Title: Changer le mot de passe Title: Changer le mot de passe
@ -318,6 +319,7 @@ Footer:
Tos: TOS Tos: TOS
PrivacyPolicy: Politique de confidentialité PrivacyPolicy: Politique de confidentialité
Help: Aide Help: Aide
SupportEmail: E-mail d'assistance
Errors: Errors:
Internal: Une erreur interne s'est produite Internal: Une erreur interne s'est produite
@ -408,8 +410,8 @@ Errors:
ExternalUserIDEmpty: L'ID de l'utilisateur externe est vide ExternalUserIDEmpty: L'ID de l'utilisateur externe est vide
UserDisplayNameEmpty: Le nom d'affichage de l'utilisateur est vide UserDisplayNameEmpty: Le nom d'affichage de l'utilisateur est vide
NoExternalUserData: Aucune donnée d'utilisateur externe reçue 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. 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. 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. 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. ProjectRequired: Connexion impossible. L'organisation de l'utilisateur doit être accordée au projet. Veuillez contacter votre administrateur.
IdentityProvider: IdentityProvider:

View File

@ -172,6 +172,7 @@ PasswordChange:
NewPasswordConfirmLabel: Conferma della password NewPasswordConfirmLabel: Conferma della password
CancelButtonText: annulla CancelButtonText: annulla
NextButtonText: Avanti NextButtonText: Avanti
Footer: Piè di pagina
PasswordChangeDone: PasswordChangeDone:
Title: Reimposta password Title: Reimposta password
@ -318,6 +319,7 @@ Footer:
Tos: Termini di servizio Tos: Termini di servizio
PrivacyPolicy: l'informativa sulla privacy PrivacyPolicy: l'informativa sulla privacy
Help: Aiuto Help: Aiuto
SupportEmail: E-mail di supporto
Errors: Errors:
Internal: Si è verificato un errore interno Internal: Si è verificato un errore interno

View File

@ -172,6 +172,7 @@ PasswordChange:
NewPasswordConfirmLabel: Potwierdzenie hasła NewPasswordConfirmLabel: Potwierdzenie hasła
CancelButtonText: anuluj CancelButtonText: anuluj
NextButtonText: dalej NextButtonText: dalej
Footer: Stopka
PasswordChangeDone: PasswordChangeDone:
Title: Zmiana hasła Title: Zmiana hasła
@ -318,6 +319,7 @@ Footer:
Tos: TOS Tos: TOS
PrivacyPolicy: Polityka prywatności PrivacyPolicy: Polityka prywatności
Help: Pomoc Help: Pomoc
SupportEmail: E-mail wsparcia
Errors: Errors:
Internal: Wewnętrzny błąd Internal: Wewnętrzny błąd

View File

@ -172,6 +172,7 @@ PasswordChange:
NewPasswordConfirmLabel: 确认密码 NewPasswordConfirmLabel: 确认密码
CancelButtonText: 取消 CancelButtonText: 取消
NextButtonText: 继续 NextButtonText: 继续
Footer: 页脚
PasswordChangeDone: PasswordChangeDone:
Title: 更改密码 Title: 更改密码
@ -318,6 +319,7 @@ Footer:
Tos: 服务条款 Tos: 服务条款
PrivacyPolicy: 隐私政策 PrivacyPolicy: 隐私政策
Help: 帮助 Help: 帮助
SupportEmail: 支持邮箱
Errors: Errors:
Internal: 发生了内部错误 Internal: 发生了内部错误

View File

@ -1,20 +1,41 @@
{{define "footer"}} {{define "footer"}}
<footer> <footer>
{{ if hasWatermark .LabelPolicy }} {{ if hasWatermark .LabelPolicy }}
<span class="watermark" > <span class="watermark">
<span class="powered">{{t "Footer.PoweredBy"}}</span> <span class="powered">{{t "Footer.PoweredBy"}}</span>
<span class="lgn-logo-watermark" sourcelight="logo-light.svg" sourcedark="logo-dark.svg" alt="logo"></span> <span
class="lgn-logo-watermark"
sourcelight="logo-light.svg"
sourcedark="logo-dark.svg"
alt="logo"
></span>
</span> </span>
{{end}} {{end}}
<span class="fill-space"></span> <span class="fill-space"></span>
{{ if .TOSLink }} {{ if .TOSLink }}
<a href="{{.TOSLink}}" rel="noopener noreferrer" target="_blank" alt="TOS">{{t "Footer.Tos"}}</a> <a href="{{.TOSLink}}" rel="noopener noreferrer" target="_blank" alt="TOS"
{{ end }} >{{t "Footer.Tos"}}</a
{{ if .PrivacyLink }} >
<a href="{{.PrivacyLink}}" rel="noopener noreferrer" target="_blank" alt="Privacy Policy">{{t "Footer.PrivacyPolicy"}}</a> {{ end }} {{ if .PrivacyLink }}
{{end}} <a
{{ if .HelpLink }} href="{{.PrivacyLink}}"
<a href="{{.HelpLink}}" rel="noopener noreferrer" target="_blank" alt="Help">{{t "Footer.Help"}}</a> rel="noopener noreferrer"
target="_blank"
alt="Privacy Policy"
>{{t "Footer.PrivacyPolicy"}}</a
>
{{end}} {{ if .HelpLink }}
<a href="{{.HelpLink}}" rel="noopener noreferrer" target="_blank" alt="Help"
>{{t "Footer.Help"}}</a
>
{{end}} {{ if .SupportEmail }}
<a
href="mailto:{{.SupportEmail}}"
rel="noopener noreferrer"
target="_blank"
alt="Help"
>{{t "Footer.SupportEmail"}}</a
>
{{end}} {{end}}
</footer> </footer>
{{end}} {{end}}

View File

@ -1158,6 +1158,7 @@ func privacyPolicyToDomain(p *query.PrivacyPolicy) *domain.PrivacyPolicy {
TOSLink: p.TOSLink, TOSLink: p.TOSLink,
PrivacyLink: p.PrivacyLink, PrivacyLink: p.PrivacyLink,
HelpLink: p.HelpLink, HelpLink: p.HelpLink,
SupportEmail: p.SupportEmail,
} }
} }

View File

@ -1093,6 +1093,10 @@ func (c *Commands) createFooterTextEvents(ctx context.Context, agg *eventstore.A
if event != nil { if event != nil {
events = append(events, event) 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 return events
} }

View File

@ -289,7 +289,7 @@ type CustomLoginTextReadModel struct {
FooterTOS string FooterTOS string
FooterPrivacyPolicy string FooterPrivacyPolicy string
FooterHelp string FooterHelp string
FooterHelpLink string FooterSupportEmail string
} }
func (wm *CustomLoginTextReadModel) Reduce() error { func (wm *CustomLoginTextReadModel) Reduce() error {
@ -2515,6 +2515,10 @@ func (wm *CustomLoginTextReadModel) handleFooterTextSetEvent(e *policy.CustomTex
wm.FooterHelp = e.Text wm.FooterHelp = e.Text
return return
} }
if e.Key == domain.LoginKeyFooterSupportEmail {
wm.FooterSupportEmail = e.Text
return
}
} }
func (wm *CustomLoginTextReadModel) handleFooterTextRemoveEvent(e *policy.CustomTextRemovedEvent) { func (wm *CustomLoginTextReadModel) handleFooterTextRemoveEvent(e *policy.CustomTextRemovedEvent) {
@ -2530,4 +2534,8 @@ func (wm *CustomLoginTextReadModel) handleFooterTextRemoveEvent(e *policy.Custom
wm.FooterHelp = "" wm.FooterHelp = ""
return return
} }
if e.Key == domain.LoginKeyFooterSupportEmail {
wm.FooterSupportEmail = ""
return
}
} }

View File

@ -90,6 +90,7 @@ type InstanceSetup struct {
TOSLink string TOSLink string
PrivacyLink string PrivacyLink string
HelpLink string HelpLink string
SupportEmail domain.EmailAddress
} }
LabelPolicy struct { LabelPolicy struct {
PrimaryColor string PrimaryColor string
@ -242,7 +243,7 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str
prepareAddSecondFactorToDefaultLoginPolicy(instanceAgg, domain.SecondFactorTypeU2F), prepareAddSecondFactorToDefaultLoginPolicy(instanceAgg, domain.SecondFactorTypeU2F),
prepareAddMultiFactorToDefaultLoginPolicy(instanceAgg, domain.MultiFactorTypeU2FWithPIN), 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), prepareAddDefaultNotificationPolicy(instanceAgg, setup.NotificationPolicy.PasswordChange),
prepareAddDefaultLockoutPolicy(instanceAgg, setup.LockoutPolicy.MaxAttempts, setup.LockoutPolicy.ShouldShowLockoutFailure), prepareAddDefaultLockoutPolicy(instanceAgg, setup.LockoutPolicy.MaxAttempts, setup.LockoutPolicy.ShouldShowLockoutFailure),

View File

@ -127,6 +127,7 @@ func writeModelToPrivacyPolicy(wm *PrivacyPolicyWriteModel) *domain.PrivacyPolic
TOSLink: wm.TOSLink, TOSLink: wm.TOSLink,
PrivacyLink: wm.PrivacyLink, PrivacyLink: wm.PrivacyLink,
HelpLink: wm.HelpLink, HelpLink: wm.HelpLink,
SupportEmail: wm.SupportEmail,
} }
} }

View File

@ -56,7 +56,6 @@ func (c *Commands) setCustomInstanceLoginText(ctx context.Context, instanceAgg *
if !text.IsValid() { if !text.IsValid() {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "Instance-kd9fs", "Errors.CustomText.Invalid") return nil, nil, caos_errs.ThrowInvalidArgument(nil, "Instance-kd9fs", "Errors.CustomText.Invalid")
} }
existingLoginText, err := c.defaultLoginTextWriteModelByID(ctx, text.Language) existingLoginText, err := c.defaultLoginTextWriteModelByID(ctx, text.Language)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err

View File

@ -1370,6 +1370,12 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) {
&instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterHelp, "Help", language.English, &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", TOS: "TOS",
PrivacyPolicy: "PrivacyPolicy", PrivacyPolicy: "PrivacyPolicy",
Help: "Help", 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, &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( expectPush(
[]*repository.Event{ []*repository.Event{
@ -4310,6 +4323,12 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) {
&instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterHelp, language.English, &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, &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( eventFromEventPusherWithInstanceID(
"INSTANCE", "INSTANCE",
instance.NewCustomTextRemovedEvent(context.Background(), instance.NewCustomTextRemovedEvent(context.Background(),
@ -6996,6 +7021,12 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) {
&instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterHelp, language.English, &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( expectPush(
[]*repository.Event{ []*repository.Event{
@ -8313,6 +8344,12 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) {
&instance.NewAggregate("INSTANCE").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterHelp, "Help", language.English, &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", TOS: "TOS",
PrivacyPolicy: "PrivacyPolicy", PrivacyPolicy: "PrivacyPolicy",
Help: "Help", Help: "Help",
SupportEmail: "Support Email",
}, },
}, },
}, },

View File

@ -12,9 +12,9 @@ import (
"github.com/zitadel/zitadel/internal/telemetry/tracing" "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()) 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 { if err != nil {
return nil, err 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) { 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) existingPolicy, err := c.defaultPrivacyPolicyWriteModelByID(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
@ -35,7 +42,7 @@ func (c *Commands) ChangeDefaultPrivacyPolicy(ctx context.Context, policy *domai
} }
instanceAgg := InstanceAggregateFromWriteModel(&existingPolicy.PrivacyPolicyWriteModel.WriteModel) 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 { if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "INSTANCE-9jJfs", "Errors.IAM.PrivacyPolicy.NotChanged") return nil, caos_errs.ThrowPreconditionFailed(nil, "INSTANCE-9jJfs", "Errors.IAM.PrivacyPolicy.NotChanged")
} }
@ -81,8 +88,15 @@ func prepareAddDefaultPrivacyPolicy(
tosLink, tosLink,
privacyLink, privacyLink,
helpLink string, helpLink string,
supportEmail domain.EmailAddress,
) preparation.Validation { ) preparation.Validation {
return func() (preparation.CreateCommands, error) { 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) { return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
writeModel := NewInstancePrivacyPolicyWriteModel(ctx) writeModel := NewInstancePrivacyPolicyWriteModel(ctx)
events, err := filter(ctx, writeModel.Query()) 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 nil, caos_errs.ThrowAlreadyExists(nil, "INSTANCE-M00rJ", "Errors.Instance.PrivacyPolicy.AlreadyExists")
} }
return []eventstore.Command{ return []eventstore.Command{
instance.NewPrivacyPolicyAddedEvent(ctx, &a.Aggregate, tosLink, privacyLink, helpLink), instance.NewPrivacyPolicyAddedEvent(ctx, &a.Aggregate, tosLink, privacyLink, helpLink, supportEmail),
}, nil }, nil
}, nil }, nil
} }

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/instance" "github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/repository/policy" "github.com/zitadel/zitadel/internal/repository/policy"
@ -57,6 +58,7 @@ func (wm *InstancePrivacyPolicyWriteModel) NewChangedEvent(
tosLink, tosLink,
privacyLink, privacyLink,
helpLink string, helpLink string,
supportEmail domain.EmailAddress,
) (*instance.PrivacyPolicyChangedEvent, bool) { ) (*instance.PrivacyPolicyChangedEvent, bool) {
changes := make([]policy.PrivacyPolicyChanges, 0) changes := make([]policy.PrivacyPolicyChanges, 0)
@ -69,6 +71,9 @@ func (wm *InstancePrivacyPolicyWriteModel) NewChangedEvent(
if wm.HelpLink != helpLink { if wm.HelpLink != helpLink {
changes = append(changes, policy.ChangeHelpLink(helpLink)) changes = append(changes, policy.ChangeHelpLink(helpLink))
} }
if wm.SupportEmail != supportEmail {
changes = append(changes, policy.ChangeSupportEmail(supportEmail))
}
if len(changes) == 0 { if len(changes) == 0 {
return nil, false return nil, false
} }

View File

@ -26,6 +26,7 @@ func TestCommandSide_AddDefaultPrivacyPolicy(t *testing.T) {
tosLink string tosLink string
privacyLink string privacyLink string
helpLink string helpLink string
supportEmail domain.EmailAddress
} }
type res struct { type res struct {
want *domain.ObjectDetails want *domain.ObjectDetails
@ -49,6 +50,7 @@ func TestCommandSide_AddDefaultPrivacyPolicy(t *testing.T) {
"TOSLink", "TOSLink",
"PrivacyLink", "PrivacyLink",
"HelpLink", "HelpLink",
"support@example.com",
), ),
), ),
), ),
@ -59,6 +61,7 @@ func TestCommandSide_AddDefaultPrivacyPolicy(t *testing.T) {
tosLink: "TOSLink", tosLink: "TOSLink",
privacyLink: "PrivacyLink", privacyLink: "PrivacyLink",
helpLink: "HelpLink", helpLink: "HelpLink",
supportEmail: "support@example.com",
}, },
res: res{ res: res{
err: caos_errs.IsErrorAlreadyExists, err: caos_errs.IsErrorAlreadyExists,
@ -79,6 +82,7 @@ func TestCommandSide_AddDefaultPrivacyPolicy(t *testing.T) {
"TOSLink", "TOSLink",
"PrivacyLink", "PrivacyLink",
"HelpLink", "HelpLink",
"support@example.com",
), ),
), ),
}, },
@ -90,6 +94,7 @@ func TestCommandSide_AddDefaultPrivacyPolicy(t *testing.T) {
tosLink: "TOSLink", tosLink: "TOSLink",
privacyLink: "PrivacyLink", privacyLink: "PrivacyLink",
helpLink: "HelpLink", helpLink: "HelpLink",
supportEmail: "support@example.com",
}, },
res: res{ res: res{
want: &domain.ObjectDetails{ 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", name: "add empty policy,ok",
fields: fields{ fields: fields{
@ -112,6 +135,7 @@ func TestCommandSide_AddDefaultPrivacyPolicy(t *testing.T) {
"", "",
"", "",
"", "",
"",
), ),
), ),
}, },
@ -123,6 +147,7 @@ func TestCommandSide_AddDefaultPrivacyPolicy(t *testing.T) {
tosLink: "", tosLink: "",
privacyLink: "", privacyLink: "",
helpLink: "", helpLink: "",
supportEmail: "",
}, },
res: res{ res: res{
want: &domain.ObjectDetails{ want: &domain.ObjectDetails{
@ -136,7 +161,7 @@ func TestCommandSide_AddDefaultPrivacyPolicy(t *testing.T) {
r := &Commands{ r := &Commands{
eventstore: tt.fields.eventstore, 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 { if tt.res.err == nil {
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -182,6 +207,7 @@ func TestCommandSide_ChangeDefaultPrivacyPolicy(t *testing.T) {
TOSLink: "TOSLink", TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink", PrivacyLink: "PrivacyLink",
HelpLink: "HelpLink", HelpLink: "HelpLink",
SupportEmail: "support@example.com",
}, },
}, },
res: res{ res: res{
@ -200,6 +226,7 @@ func TestCommandSide_ChangeDefaultPrivacyPolicy(t *testing.T) {
"TOSLink", "TOSLink",
"PrivacyLink", "PrivacyLink",
"HelpLink", "HelpLink",
"support@example.com",
), ),
), ),
), ),
@ -211,12 +238,33 @@ func TestCommandSide_ChangeDefaultPrivacyPolicy(t *testing.T) {
TOSLink: "TOSLink", TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink", PrivacyLink: "PrivacyLink",
HelpLink: "HelpLink", HelpLink: "HelpLink",
SupportEmail: "support@example.com",
}, },
}, },
res: res{ res: res{
err: caos_errs.IsPreconditionFailed, 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", name: "change, ok",
fields: fields{ fields: fields{
@ -229,6 +277,7 @@ func TestCommandSide_ChangeDefaultPrivacyPolicy(t *testing.T) {
"TOSLink", "TOSLink",
"PrivacyLink", "PrivacyLink",
"HelpLink", "HelpLink",
"support@example.com",
), ),
), ),
), ),
@ -239,6 +288,7 @@ func TestCommandSide_ChangeDefaultPrivacyPolicy(t *testing.T) {
"TOSLinkChanged", "TOSLinkChanged",
"PrivacyLinkChanged", "PrivacyLinkChanged",
"HelpLinkChanged", "HelpLinkChanged",
"support2@example.com",
), ),
), ),
}, },
@ -251,6 +301,7 @@ func TestCommandSide_ChangeDefaultPrivacyPolicy(t *testing.T) {
TOSLink: "TOSLinkChanged", TOSLink: "TOSLinkChanged",
PrivacyLink: "PrivacyLinkChanged", PrivacyLink: "PrivacyLinkChanged",
HelpLink: "HelpLinkChanged", HelpLink: "HelpLinkChanged",
SupportEmail: "support2@example.com",
}, },
}, },
res: res{ res: res{
@ -262,6 +313,7 @@ func TestCommandSide_ChangeDefaultPrivacyPolicy(t *testing.T) {
TOSLink: "TOSLinkChanged", TOSLink: "TOSLinkChanged",
PrivacyLink: "PrivacyLinkChanged", PrivacyLink: "PrivacyLinkChanged",
HelpLink: "HelpLinkChanged", 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, event, _ := instance.NewPrivacyPolicyChangedEvent(ctx,
&instance.NewAggregate("INSTANCE").Aggregate, &instance.NewAggregate("INSTANCE").Aggregate,
[]policy.PrivacyPolicyChanges{ []policy.PrivacyPolicyChanges{
policy.ChangeTOSLink(tosLink), policy.ChangeTOSLink(tosLink),
policy.ChangePrivacyLink(privacyLink), policy.ChangePrivacyLink(privacyLink),
policy.ChangeHelpLink(helpLink), policy.ChangeHelpLink(helpLink),
policy.ChangeSupportEmail(domain.EmailAddress(supportEmail)),
}, },
) )
return event return event

View File

@ -50,5 +50,6 @@ func orgWriteModelToPrivacyPolicy(wm *OrgPrivacyPolicyWriteModel) *domain.Privac
TOSLink: wm.TOSLink, TOSLink: wm.TOSLink,
PrivacyLink: wm.PrivacyLink, PrivacyLink: wm.PrivacyLink,
HelpLink: wm.HelpLink, HelpLink: wm.HelpLink,
SupportEmail: wm.SupportEmail,
} }
} }

View File

@ -1161,6 +1161,11 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) {
&org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterHelp, "Help", language.English, &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", TOS: "TOS",
PrivacyPolicy: "PrivacyPolicy", PrivacyPolicy: "PrivacyPolicy",
Help: "Help", 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, &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( expectPush(
[]*repository.Event{ []*repository.Event{
@ -3653,6 +3664,11 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) {
&org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterHelp, language.English, &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, &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( eventFromEventPusher(
org.NewCustomTextRemovedEvent(context.Background(), org.NewCustomTextRemovedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeySelectAccountTitle, language.English, &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, &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( expectPush(
[]*repository.Event{ []*repository.Event{
@ -6983,6 +7009,11 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) {
&org.NewAggregate("org1").Aggregate, domain.LoginCustomText, domain.LoginKeyFooterHelp, "Help", language.English, &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", TOS: "TOS",
PrivacyPolicy: "PrivacyPolicy", PrivacyPolicy: "PrivacyPolicy",
Help: "Help", Help: "Help",
SupportEmail: "Support Email",
}, },
}, },
}, },

View File

@ -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) { 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 == "" { if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-MMk9fs", "Errors.ResourceOwnerMissing") 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, orgAgg,
policy.TOSLink, policy.TOSLink,
policy.PrivacyLink, policy.PrivacyLink,
policy.HelpLink)) policy.HelpLink,
policy.SupportEmail))
if err != nil { if err != nil {
return nil, err 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) { 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 == "" { if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-22N89f", "Errors.ResourceOwnerMissing") return nil, caos_errs.ThrowInvalidArgument(nil, "Org-22N89f", "Errors.ResourceOwnerMissing")
} }
existingPolicy := NewOrgPrivacyPolicyWriteModel(resourceOwner) existingPolicy, err := c.orgPrivacyPolicyWriteModelByID(ctx, resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -75,7 +91,7 @@ func (c *Commands) ChangePrivacyPolicy(ctx context.Context, resourceOwner string
} }
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.PrivacyPolicyWriteModel.WriteModel) 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 { if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-4N9fs", "Errors.Org.PrivacyPolicy.NotChanged") return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-4N9fs", "Errors.Org.PrivacyPolicy.NotChanged")
} }

View File

@ -3,8 +3,8 @@ package command
import ( import (
"context" "context"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/org" "github.com/zitadel/zitadel/internal/repository/org"
"github.com/zitadel/zitadel/internal/repository/policy" "github.com/zitadel/zitadel/internal/repository/policy"
) )
@ -59,6 +59,7 @@ func (wm *OrgPrivacyPolicyWriteModel) NewChangedEvent(
tosLink, tosLink,
privacyLink, privacyLink,
helpLink string, helpLink string,
supportEmail domain.EmailAddress,
) (*org.PrivacyPolicyChangedEvent, bool) { ) (*org.PrivacyPolicyChangedEvent, bool) {
changes := make([]policy.PrivacyPolicyChanges, 0) changes := make([]policy.PrivacyPolicyChanges, 0)
@ -71,6 +72,9 @@ func (wm *OrgPrivacyPolicyWriteModel) NewChangedEvent(
if wm.HelpLink != helpLink { if wm.HelpLink != helpLink {
changes = append(changes, policy.ChangeHelpLink(helpLink)) changes = append(changes, policy.ChangeHelpLink(helpLink))
} }
if wm.SupportEmail != supportEmail {
changes = append(changes, policy.ChangeSupportEmail(supportEmail))
}
if len(changes) == 0 { if len(changes) == 0 {
return nil, false return nil, false
} }

View File

@ -47,6 +47,7 @@ func TestCommandSide_AddPrivacyPolicy(t *testing.T) {
TOSLink: "TOSLink", TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink", PrivacyLink: "PrivacyLink",
HelpLink: "HelpLink", HelpLink: "HelpLink",
SupportEmail: "support@example.com",
}, },
}, },
res: res{ res: res{
@ -65,6 +66,7 @@ func TestCommandSide_AddPrivacyPolicy(t *testing.T) {
"TOSLink", "TOSLink",
"PrivacyLink", "PrivacyLink",
"HelpLink", "HelpLink",
"support@example.com",
), ),
), ),
), ),
@ -77,6 +79,7 @@ func TestCommandSide_AddPrivacyPolicy(t *testing.T) {
TOSLink: "TOSLink", TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink", PrivacyLink: "PrivacyLink",
HelpLink: "HelpLink", HelpLink: "HelpLink",
SupportEmail: "support@example.com",
}, },
}, },
res: res{ res: res{
@ -97,6 +100,7 @@ func TestCommandSide_AddPrivacyPolicy(t *testing.T) {
"TOSLink", "TOSLink",
"PrivacyLink", "PrivacyLink",
"HelpLink", "HelpLink",
"support@example.com",
), ),
), ),
}, },
@ -110,6 +114,7 @@ func TestCommandSide_AddPrivacyPolicy(t *testing.T) {
TOSLink: "TOSLink", TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink", PrivacyLink: "PrivacyLink",
HelpLink: "HelpLink", HelpLink: "HelpLink",
SupportEmail: "support@example.com",
}, },
}, },
res: res{ res: res{
@ -121,11 +126,33 @@ func TestCommandSide_AddPrivacyPolicy(t *testing.T) {
TOSLink: "TOSLink", TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink", PrivacyLink: "PrivacyLink",
HelpLink: "HelpLink", 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{ fields: fields{
eventstore: eventstoreExpect( eventstore: eventstoreExpect(
t, t,
@ -138,6 +165,7 @@ func TestCommandSide_AddPrivacyPolicy(t *testing.T) {
"", "",
"", "",
"", "",
"",
), ),
), ),
}, },
@ -151,6 +179,7 @@ func TestCommandSide_AddPrivacyPolicy(t *testing.T) {
TOSLink: "", TOSLink: "",
PrivacyLink: "", PrivacyLink: "",
HelpLink: "", HelpLink: "",
SupportEmail: "",
}, },
}, },
res: res{ res: res{
@ -162,6 +191,7 @@ func TestCommandSide_AddPrivacyPolicy(t *testing.T) {
TOSLink: "", TOSLink: "",
PrivacyLink: "", PrivacyLink: "",
HelpLink: "", HelpLink: "",
SupportEmail: "",
}, },
}, },
}, },
@ -217,6 +247,7 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) {
TOSLink: "TOSLink", TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink", PrivacyLink: "PrivacyLink",
HelpLink: "HelpLink", HelpLink: "HelpLink",
SupportEmail: "support@example.com",
}, },
}, },
res: res{ res: res{
@ -238,6 +269,7 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) {
TOSLink: "TOSLink", TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink", PrivacyLink: "PrivacyLink",
HelpLink: "HelpLink", HelpLink: "HelpLink",
SupportEmail: "support@example.com",
}, },
}, },
res: res{ res: res{
@ -256,6 +288,7 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) {
"TOSLink", "TOSLink",
"PrivacyLink", "PrivacyLink",
"HelpLink", "HelpLink",
"support@example.com",
), ),
), ),
), ),
@ -268,12 +301,29 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) {
TOSLink: "TOSLink", TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink", PrivacyLink: "PrivacyLink",
HelpLink: "HelpLink", HelpLink: "HelpLink",
SupportEmail: "support@example.com",
}, },
}, },
res: res{ res: res{
err: caos_errs.IsPreconditionFailed, 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", name: "change, ok",
fields: fields{ fields: fields{
@ -286,13 +336,14 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) {
"TOSLink", "TOSLink",
"PrivacyLink", "PrivacyLink",
"HelpLink", "HelpLink",
"support@example.com",
), ),
), ),
), ),
expectPush( expectPush(
[]*repository.Event{ []*repository.Event{
eventFromEventPusher( eventFromEventPusher(
newPrivacyPolicyChangedEvent(context.Background(), "org1", "TOSLinkChange", "PrivacyLinkChange", "HelpLinkChange"), newPrivacyPolicyChangedEvent(context.Background(), "org1", "TOSLinkChange", "PrivacyLinkChange", "HelpLinkChange", "support2@example.com"),
), ),
}, },
), ),
@ -305,6 +356,7 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) {
TOSLink: "TOSLinkChange", TOSLink: "TOSLinkChange",
PrivacyLink: "PrivacyLinkChange", PrivacyLink: "PrivacyLinkChange",
HelpLink: "HelpLinkChange", HelpLink: "HelpLinkChange",
SupportEmail: "support2@example.com",
}, },
}, },
res: res{ res: res{
@ -316,6 +368,7 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) {
TOSLink: "TOSLinkChange", TOSLink: "TOSLinkChange",
PrivacyLink: "PrivacyLinkChange", PrivacyLink: "PrivacyLinkChange",
HelpLink: "HelpLinkChange", HelpLink: "HelpLinkChange",
SupportEmail: "support2@example.com",
}, },
}, },
}, },
@ -331,13 +384,14 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) {
"TOSLink", "TOSLink",
"PrivacyLink", "PrivacyLink",
"HelpLink", "HelpLink",
"support@example.com",
), ),
), ),
), ),
expectPush( expectPush(
[]*repository.Event{ []*repository.Event{
eventFromEventPusher( eventFromEventPusher(
newPrivacyPolicyChangedEvent(context.Background(), "org1", "", "", ""), newPrivacyPolicyChangedEvent(context.Background(), "org1", "", "", "", ""),
), ),
}, },
), ),
@ -350,6 +404,7 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) {
TOSLink: "", TOSLink: "",
PrivacyLink: "", PrivacyLink: "",
HelpLink: "", HelpLink: "",
SupportEmail: "",
}, },
}, },
res: res{ res: res{
@ -361,6 +416,7 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) {
TOSLink: "", TOSLink: "",
PrivacyLink: "", PrivacyLink: "",
HelpLink: "", HelpLink: "",
SupportEmail: "",
}, },
}, },
}, },
@ -444,6 +500,7 @@ func TestCommandSide_RemovePrivacyPolicy(t *testing.T) {
"TOSLink", "TOSLink",
"PrivacyLink", "PrivacyLink",
"HelpLink", "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, event, _ := org.NewPrivacyPolicyChangedEvent(ctx,
&org.NewAggregate(orgID).Aggregate, &org.NewAggregate(orgID).Aggregate,
[]policy.PrivacyPolicyChanges{ []policy.PrivacyPolicyChanges{
policy.ChangeTOSLink(tosLink), policy.ChangeTOSLink(tosLink),
policy.ChangePrivacyLink(privacyLink), policy.ChangePrivacyLink(privacyLink),
policy.ChangeHelpLink(helpLink), policy.ChangeHelpLink(helpLink),
policy.ChangeSupportEmail(domain.EmailAddress(supportEmail)),
}, },
) )
return event return event

View File

@ -12,6 +12,7 @@ type PrivacyPolicyWriteModel struct {
TOSLink string TOSLink string
PrivacyLink string PrivacyLink string
HelpLink string HelpLink string
SupportEmail domain.EmailAddress
State domain.PolicyState State domain.PolicyState
} }
@ -22,6 +23,7 @@ func (wm *PrivacyPolicyWriteModel) Reduce() error {
wm.TOSLink = e.TOSLink wm.TOSLink = e.TOSLink
wm.PrivacyLink = e.PrivacyLink wm.PrivacyLink = e.PrivacyLink
wm.HelpLink = e.HelpLink wm.HelpLink = e.HelpLink
wm.SupportEmail = e.SupportEmail
wm.State = domain.PolicyStateActive wm.State = domain.PolicyStateActive
case *policy.PrivacyPolicyChangedEvent: case *policy.PrivacyPolicyChangedEvent:
if e.PrivacyLink != nil { if e.PrivacyLink != nil {
@ -33,6 +35,9 @@ func (wm *PrivacyPolicyWriteModel) Reduce() error {
if e.HelpLink != nil { if e.HelpLink != nil {
wm.HelpLink = *e.HelpLink wm.HelpLink = *e.HelpLink
} }
if e.SupportEmail != nil {
wm.SupportEmail = *e.SupportEmail
}
case *policy.PrivacyPolicyRemovedEvent: case *policy.PrivacyPolicyRemovedEvent:
wm.State = domain.PolicyStateRemoved wm.State = domain.PolicyStateRemoved
} }

View File

@ -296,6 +296,7 @@ const (
LoginKeyFooterTOS = LoginKeyFooter + "Tos" LoginKeyFooterTOS = LoginKeyFooter + "Tos"
LoginKeyFooterPrivacyPolicy = LoginKeyFooter + "PrivacyPolicy" LoginKeyFooterPrivacyPolicy = LoginKeyFooter + "PrivacyPolicy"
LoginKeyFooterHelp = LoginKeyFooter + "Help" LoginKeyFooterHelp = LoginKeyFooter + "Help"
LoginKeyFooterSupportEmail = LoginKeyFooter + "SupportEmail"
) )
type CustomLoginText struct { type CustomLoginText struct {
@ -639,6 +640,7 @@ type FooterText struct {
TOS string TOS string
PrivacyPolicy string PrivacyPolicy string
Help string Help string
SupportEmail string
} }
type PasswordlessPromptScreenText struct { type PasswordlessPromptScreenText struct {

View File

@ -13,4 +13,5 @@ type PrivacyPolicy struct {
TOSLink string TOSLink string
PrivacyLink string PrivacyLink string
HelpLink string HelpLink string
SupportEmail EmailAddress
} }

View File

@ -10,6 +10,7 @@ type PrivacyPolicyView struct {
AggregateID string AggregateID string
TOSLink string TOSLink string
PrivacyLink string PrivacyLink string
SupportEmail string
Default bool Default bool
CreationDate time.Time CreationDate time.Time

View File

@ -1181,4 +1181,7 @@ func footerKeyToDomain(text *CustomText, result *domain.CustomLoginText) {
if text.Key == domain.LoginKeyFooterHelp { if text.Key == domain.LoginKeyFooterHelp {
result.Footer.Help = text.Text result.Footer.Help = text.Text
} }
if text.Key == domain.LoginKeyFooterSupportEmail {
result.Footer.SupportEmail = text.Text
}
} }

View File

@ -27,6 +27,7 @@ type PrivacyPolicy struct {
TOSLink string TOSLink string
PrivacyLink string PrivacyLink string
HelpLink string HelpLink string
SupportEmail domain.EmailAddress
IsDefault bool IsDefault bool
} }
@ -72,6 +73,10 @@ var (
name: projection.PrivacyPolicyHelpLinkCol, name: projection.PrivacyPolicyHelpLinkCol,
table: privacyTable, table: privacyTable,
} }
PrivacyColSupportEmail = Column{
name: projection.PrivacyPolicySupportEmailCol,
table: privacyTable,
}
PrivacyColIsDefault = Column{ PrivacyColIsDefault = Column{
name: projection.PrivacyPolicyIsDefaultCol, name: projection.PrivacyPolicyIsDefaultCol,
table: privacyTable, table: privacyTable,
@ -148,6 +153,7 @@ func preparePrivacyPolicyQuery(ctx context.Context, db prepareDatabase) (sq.Sele
PrivacyColPrivacyLink.identifier(), PrivacyColPrivacyLink.identifier(),
PrivacyColTOSLink.identifier(), PrivacyColTOSLink.identifier(),
PrivacyColHelpLink.identifier(), PrivacyColHelpLink.identifier(),
PrivacyColSupportEmail.identifier(),
PrivacyColIsDefault.identifier(), PrivacyColIsDefault.identifier(),
PrivacyColState.identifier(), PrivacyColState.identifier(),
). ).
@ -164,6 +170,7 @@ func preparePrivacyPolicyQuery(ctx context.Context, db prepareDatabase) (sq.Sele
&policy.PrivacyLink, &policy.PrivacyLink,
&policy.TOSLink, &policy.TOSLink,
&policy.HelpLink, &policy.HelpLink,
&policy.SupportEmail,
&policy.IsDefault, &policy.IsDefault,
&policy.State, &policy.State,
) )
@ -182,6 +189,7 @@ func (p *PrivacyPolicy) ToDomain() *domain.PrivacyPolicy {
TOSLink: p.TOSLink, TOSLink: p.TOSLink,
PrivacyLink: p.PrivacyLink, PrivacyLink: p.PrivacyLink,
HelpLink: p.HelpLink, HelpLink: p.HelpLink,
SupportEmail: p.SupportEmail,
Default: p.IsDefault, Default: p.IsDefault,
} }
} }

View File

@ -13,17 +13,18 @@ import (
) )
var ( var (
preparePrivacyPolicyStmt = `SELECT projections.privacy_policies2.id,` + preparePrivacyPolicyStmt = `SELECT projections.privacy_policies3.id,` +
` projections.privacy_policies2.sequence,` + ` projections.privacy_policies3.sequence,` +
` projections.privacy_policies2.creation_date,` + ` projections.privacy_policies3.creation_date,` +
` projections.privacy_policies2.change_date,` + ` projections.privacy_policies3.change_date,` +
` projections.privacy_policies2.resource_owner,` + ` projections.privacy_policies3.resource_owner,` +
` projections.privacy_policies2.privacy_link,` + ` projections.privacy_policies3.privacy_link,` +
` projections.privacy_policies2.tos_link,` + ` projections.privacy_policies3.tos_link,` +
` projections.privacy_policies2.help_link,` + ` projections.privacy_policies3.help_link,` +
` projections.privacy_policies2.is_default,` + ` projections.privacy_policies3.support_email,` +
` projections.privacy_policies2.state` + ` projections.privacy_policies3.is_default,` +
` FROM projections.privacy_policies2` + ` projections.privacy_policies3.state` +
` FROM projections.privacy_policies3` +
` AS OF SYSTEM TIME '-1 ms'` ` AS OF SYSTEM TIME '-1 ms'`
preparePrivacyPolicyCols = []string{ preparePrivacyPolicyCols = []string{
"id", "id",
@ -34,6 +35,7 @@ var (
"privacy_link", "privacy_link",
"tos_link", "tos_link",
"help_link", "help_link",
"support_email",
"is_default", "is_default",
"state", "state",
} }
@ -84,6 +86,7 @@ func Test_PrivacyPolicyPrepares(t *testing.T) {
"privacy.ch", "privacy.ch",
"tos.ch", "tos.ch",
"help.ch", "help.ch",
"support@example.com",
true, true,
domain.PolicyStateActive, domain.PolicyStateActive,
}, },
@ -99,6 +102,7 @@ func Test_PrivacyPolicyPrepares(t *testing.T) {
PrivacyLink: "privacy.ch", PrivacyLink: "privacy.ch",
TOSLink: "tos.ch", TOSLink: "tos.ch",
HelpLink: "help.ch", HelpLink: "help.ch",
SupportEmail: "support@example.com",
IsDefault: true, IsDefault: true,
}, },
}, },

View File

@ -14,7 +14,7 @@ import (
) )
const ( const (
PrivacyPolicyTable = "projections.privacy_policies2" PrivacyPolicyTable = "projections.privacy_policies3"
PrivacyPolicyIDCol = "id" PrivacyPolicyIDCol = "id"
PrivacyPolicyCreationDateCol = "creation_date" PrivacyPolicyCreationDateCol = "creation_date"
@ -27,6 +27,7 @@ const (
PrivacyPolicyPrivacyLinkCol = "privacy_link" PrivacyPolicyPrivacyLinkCol = "privacy_link"
PrivacyPolicyTOSLinkCol = "tos_link" PrivacyPolicyTOSLinkCol = "tos_link"
PrivacyPolicyHelpLinkCol = "help_link" PrivacyPolicyHelpLinkCol = "help_link"
PrivacyPolicySupportEmailCol = "support_email"
PrivacyPolicyOwnerRemovedCol = "owner_removed" PrivacyPolicyOwnerRemovedCol = "owner_removed"
) )
@ -51,6 +52,7 @@ func newPrivacyPolicyProjection(ctx context.Context, config crdb.StatementHandle
crdb.NewColumn(PrivacyPolicyPrivacyLinkCol, crdb.ColumnTypeText), crdb.NewColumn(PrivacyPolicyPrivacyLinkCol, crdb.ColumnTypeText),
crdb.NewColumn(PrivacyPolicyTOSLinkCol, crdb.ColumnTypeText), crdb.NewColumn(PrivacyPolicyTOSLinkCol, crdb.ColumnTypeText),
crdb.NewColumn(PrivacyPolicyHelpLinkCol, crdb.ColumnTypeText), crdb.NewColumn(PrivacyPolicyHelpLinkCol, crdb.ColumnTypeText),
crdb.NewColumn(PrivacyPolicySupportEmailCol, crdb.ColumnTypeText),
crdb.NewColumn(PrivacyPolicyOwnerRemovedCol, crdb.ColumnTypeBool, crdb.Default(false)), crdb.NewColumn(PrivacyPolicyOwnerRemovedCol, crdb.ColumnTypeBool, crdb.Default(false)),
}, },
crdb.NewPrimaryKey(PrivacyPolicyInstanceIDCol, PrivacyPolicyIDCol), crdb.NewPrimaryKey(PrivacyPolicyInstanceIDCol, PrivacyPolicyIDCol),
@ -128,6 +130,7 @@ func (p *privacyPolicyProjection) reduceAdded(event eventstore.Event) (*handler.
handler.NewCol(PrivacyPolicyPrivacyLinkCol, policyEvent.PrivacyLink), handler.NewCol(PrivacyPolicyPrivacyLinkCol, policyEvent.PrivacyLink),
handler.NewCol(PrivacyPolicyTOSLinkCol, policyEvent.TOSLink), handler.NewCol(PrivacyPolicyTOSLinkCol, policyEvent.TOSLink),
handler.NewCol(PrivacyPolicyHelpLinkCol, policyEvent.HelpLink), handler.NewCol(PrivacyPolicyHelpLinkCol, policyEvent.HelpLink),
handler.NewCol(PrivacyPolicySupportEmailCol, policyEvent.SupportEmail),
handler.NewCol(PrivacyPolicyIsDefaultCol, isDefault), handler.NewCol(PrivacyPolicyIsDefaultCol, isDefault),
handler.NewCol(PrivacyPolicyResourceOwnerCol, policyEvent.Aggregate().ResourceOwner), handler.NewCol(PrivacyPolicyResourceOwnerCol, policyEvent.Aggregate().ResourceOwner),
handler.NewCol(PrivacyPolicyInstanceIDCol, policyEvent.Aggregate().InstanceID), handler.NewCol(PrivacyPolicyInstanceIDCol, policyEvent.Aggregate().InstanceID),
@ -157,6 +160,9 @@ func (p *privacyPolicyProjection) reduceChanged(event eventstore.Event) (*handle
if policyEvent.HelpLink != nil { if policyEvent.HelpLink != nil {
cols = append(cols, handler.NewCol(PrivacyPolicyHelpLinkCol, *policyEvent.HelpLink)) cols = append(cols, handler.NewCol(PrivacyPolicyHelpLinkCol, *policyEvent.HelpLink))
} }
if policyEvent.SupportEmail != nil {
cols = append(cols, handler.NewCol(PrivacyPolicySupportEmailCol, *policyEvent.SupportEmail))
}
return crdb.NewUpdateStatement( return crdb.NewUpdateStatement(
&policyEvent, &policyEvent,
cols, cols,

View File

@ -31,8 +31,8 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) {
[]byte(`{ []byte(`{
"tosLink": "http://tos.link", "tosLink": "http://tos.link",
"privacyLink": "http://privacy.link", "privacyLink": "http://privacy.link",
"helpLink": "http://help.link" "helpLink": "http://help.link",
}`), "supportEmail": "support@example.com"}`),
), org.PrivacyPolicyAddedEventMapper), ), org.PrivacyPolicyAddedEventMapper),
}, },
reduce: (&privacyPolicyProjection{}).reduceAdded, reduce: (&privacyPolicyProjection{}).reduceAdded,
@ -43,7 +43,7 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ 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{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
anyArg{}, anyArg{},
@ -53,6 +53,7 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) {
"http://privacy.link", "http://privacy.link",
"http://tos.link", "http://tos.link",
"http://help.link", "http://help.link",
domain.EmailAddress("support@example.com"),
false, false,
"ro-id", "ro-id",
"instance-id", "instance-id",
@ -72,8 +73,8 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) {
[]byte(`{ []byte(`{
"tosLink": "http://tos.link", "tosLink": "http://tos.link",
"privacyLink": "http://privacy.link", "privacyLink": "http://privacy.link",
"helpLink": "http://help.link" "helpLink": "http://help.link",
}`), "supportEmail": "support@example.com"}`),
), org.PrivacyPolicyChangedEventMapper), ), org.PrivacyPolicyChangedEventMapper),
}, },
want: wantReduce{ want: wantReduce{
@ -83,13 +84,14 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ 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{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
"http://privacy.link", "http://privacy.link",
"http://tos.link", "http://tos.link",
"http://help.link", "http://help.link",
domain.EmailAddress("support@example.com"),
"agg-id", "agg-id",
"instance-id", "instance-id",
}, },
@ -115,7 +117,7 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ 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{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
"instance-id", "instance-id",
@ -141,7 +143,7 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "DELETE FROM projections.privacy_policies2 WHERE (instance_id = $1)", expectedStmt: "DELETE FROM projections.privacy_policies3 WHERE (instance_id = $1)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
}, },
@ -160,8 +162,8 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) {
[]byte(`{ []byte(`{
"tosLink": "http://tos.link", "tosLink": "http://tos.link",
"privacyLink": "http://privacy.link", "privacyLink": "http://privacy.link",
"helpLink": "http://help.link" "helpLink": "http://help.link",
}`), "supportEmail": "support@example.com"}`),
), instance.PrivacyPolicyAddedEventMapper), ), instance.PrivacyPolicyAddedEventMapper),
}, },
want: wantReduce{ want: wantReduce{
@ -171,7 +173,7 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ 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{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
anyArg{}, anyArg{},
@ -181,6 +183,7 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) {
"http://privacy.link", "http://privacy.link",
"http://tos.link", "http://tos.link",
"http://help.link", "http://help.link",
domain.EmailAddress("support@example.com"),
true, true,
"ro-id", "ro-id",
"instance-id", "instance-id",
@ -200,8 +203,8 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) {
[]byte(`{ []byte(`{
"tosLink": "http://tos.link", "tosLink": "http://tos.link",
"privacyLink": "http://privacy.link", "privacyLink": "http://privacy.link",
"helpLink": "http://help.link" "helpLink": "http://help.link",
}`), "supportEmail": "support@example.com"}`),
), instance.PrivacyPolicyChangedEventMapper), ), instance.PrivacyPolicyChangedEventMapper),
}, },
want: wantReduce{ want: wantReduce{
@ -211,13 +214,14 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ 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{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
"http://privacy.link", "http://privacy.link",
"http://tos.link", "http://tos.link",
"http://help.link", "http://help.link",
domain.EmailAddress("support@example.com"),
"agg-id", "agg-id",
"instance-id", "instance-id",
}, },
@ -243,7 +247,7 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ 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{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),

View File

@ -3,8 +3,8 @@ package instance
import ( import (
"context" "context"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/repository" "github.com/zitadel/zitadel/internal/eventstore/repository"
"github.com/zitadel/zitadel/internal/repository/policy" "github.com/zitadel/zitadel/internal/repository/policy"
) )
@ -24,6 +24,7 @@ func NewPrivacyPolicyAddedEvent(
tosLink, tosLink,
privacyLink, privacyLink,
helpLink string, helpLink string,
supportEmail domain.EmailAddress,
) *PrivacyPolicyAddedEvent { ) *PrivacyPolicyAddedEvent {
return &PrivacyPolicyAddedEvent{ return &PrivacyPolicyAddedEvent{
PrivacyPolicyAddedEvent: *policy.NewPrivacyPolicyAddedEvent( PrivacyPolicyAddedEvent: *policy.NewPrivacyPolicyAddedEvent(
@ -33,7 +34,8 @@ func NewPrivacyPolicyAddedEvent(
PrivacyPolicyAddedEventType), PrivacyPolicyAddedEventType),
tosLink, tosLink,
privacyLink, privacyLink,
helpLink), helpLink,
supportEmail),
} }
} }

View File

@ -3,8 +3,8 @@ package org
import ( import (
"context" "context"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/repository" "github.com/zitadel/zitadel/internal/eventstore/repository"
"github.com/zitadel/zitadel/internal/repository/policy" "github.com/zitadel/zitadel/internal/repository/policy"
) )
@ -25,6 +25,7 @@ func NewPrivacyPolicyAddedEvent(
tosLink, tosLink,
privacyLink, privacyLink,
helpLink string, helpLink string,
supportEmail domain.EmailAddress,
) *PrivacyPolicyAddedEvent { ) *PrivacyPolicyAddedEvent {
return &PrivacyPolicyAddedEvent{ return &PrivacyPolicyAddedEvent{
PrivacyPolicyAddedEvent: *policy.NewPrivacyPolicyAddedEvent( PrivacyPolicyAddedEvent: *policy.NewPrivacyPolicyAddedEvent(
@ -34,7 +35,8 @@ func NewPrivacyPolicyAddedEvent(
PrivacyPolicyAddedEventType), PrivacyPolicyAddedEventType),
tosLink, tosLink,
privacyLink, privacyLink,
helpLink), helpLink,
supportEmail),
} }
} }

View File

@ -3,9 +3,9 @@ package policy
import ( import (
"encoding/json" "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/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/repository" "github.com/zitadel/zitadel/internal/eventstore/repository"
) )
@ -21,6 +21,7 @@ type PrivacyPolicyAddedEvent struct {
TOSLink string `json:"tosLink,omitempty"` TOSLink string `json:"tosLink,omitempty"`
PrivacyLink string `json:"privacyLink,omitempty"` PrivacyLink string `json:"privacyLink,omitempty"`
HelpLink string `json:"helpLink,omitempty"` HelpLink string `json:"helpLink,omitempty"`
SupportEmail domain.EmailAddress `json:"supportEmail,omitempty"`
} }
func (e *PrivacyPolicyAddedEvent) Data() interface{} { func (e *PrivacyPolicyAddedEvent) Data() interface{} {
@ -36,12 +37,14 @@ func NewPrivacyPolicyAddedEvent(
tosLink, tosLink,
privacyLink, privacyLink,
helpLink string, helpLink string,
supportEmail domain.EmailAddress,
) *PrivacyPolicyAddedEvent { ) *PrivacyPolicyAddedEvent {
return &PrivacyPolicyAddedEvent{ return &PrivacyPolicyAddedEvent{
BaseEvent: *base, BaseEvent: *base,
TOSLink: tosLink, TOSLink: tosLink,
PrivacyLink: privacyLink, PrivacyLink: privacyLink,
HelpLink: helpLink, HelpLink: helpLink,
SupportEmail: supportEmail,
} }
} }
@ -63,6 +66,7 @@ type PrivacyPolicyChangedEvent struct {
TOSLink *string `json:"tosLink,omitempty"` TOSLink *string `json:"tosLink,omitempty"`
PrivacyLink *string `json:"privacyLink,omitempty"` PrivacyLink *string `json:"privacyLink,omitempty"`
HelpLink *string `json:"helpLink,omitempty"` HelpLink *string `json:"helpLink,omitempty"`
SupportEmail *domain.EmailAddress `json:"supportEmail,omitempty"`
} }
func (e *PrivacyPolicyChangedEvent) Data() interface{} { 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) { func PrivacyPolicyChangedEventMapper(event *repository.Event) (eventstore.Event, error) {
e := &PrivacyPolicyChangedEvent{ e := &PrivacyPolicyChangedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event), BaseEvent: *eventstore.BaseEventFromRepo(event),

View File

@ -5460,6 +5460,13 @@ message UpdatePrivacyPolicyRequest {
example: "\"https://zitadel.com/docs/manuals/introduction\""; 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 { message UpdatePrivacyPolicyResponse {

View File

@ -9834,6 +9834,13 @@ message AddCustomPrivacyPolicyRequest {
example: "\"https://zitadel.com/docs/manuals/introduction\""; 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 { message AddCustomPrivacyPolicyResponse {
@ -9859,6 +9866,13 @@ message UpdateCustomPrivacyPolicyRequest {
example: "\"https://zitadel.com/docs/manuals/introduction\""; 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 { message UpdateCustomPrivacyPolicyResponse {

View File

@ -4,6 +4,7 @@ import "zitadel/object.proto";
import "zitadel/idp.proto"; import "zitadel/idp.proto";
import "google/protobuf/duration.proto"; import "google/protobuf/duration.proto";
import "protoc-gen-openapiv2/options/annotations.proto"; import "protoc-gen-openapiv2/options/annotations.proto";
import "validate/validate.proto";
package zitadel.policy.v1; package zitadel.policy.v1;
@ -345,6 +346,13 @@ message PrivacyPolicy {
example: "\"https://zitadel.com/docs/manuals/introduction\""; 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 { message NotificationPolicy {

View File

@ -394,11 +394,12 @@ message LogoutDoneScreenText {
} }
message FooterText { message FooterText {
reserved 2, 4, 6; reserved 2, 4, 6, 8;
reserved "tos_link", "privacy_policy_link", "help_link"; reserved "tos_link", "privacy_policy_link", "help_link";
string tos = 1 [(validate.rules).string = {max_len: 200}]; string tos = 1 [(validate.rules).string = {max_len: 200}];
string privacy_policy = 3 [(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 help = 5 [(validate.rules).string = {max_len: 200}];
string support_email = 7 [(validate.rules).string = {max_len: 200}];
} }
message PasswordlessPromptScreenText { message PasswordlessPromptScreenText {