feat(cnsl): docs link can be customized and custom button is available (#7840)

* feat: customize doc link and additional custom link

* feat: add e2e tests

* fix: update docs

* fix: add @peintnermax changes about cache

* fix: golangci-lint complains preparation.PrepareCommands

---------

Co-authored-by: Max Peintner <max@caos.ch>
This commit is contained in:
Miguel Cabrerizo 2024-05-13 16:01:50 +02:00 committed by GitHub
parent 6942324741
commit 15d5338b91
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
61 changed files with 1000 additions and 286 deletions

View File

@ -23,7 +23,7 @@ Tracing:
Type: none # ZITADEL_TRACING_TYPE Type: none # ZITADEL_TRACING_TYPE
Fraction: 1.0 # ZITADEL_TRACING_FRACTION Fraction: 1.0 # ZITADEL_TRACING_FRACTION
# The endpoint of the otel collector endpoint # The endpoint of the otel collector endpoint
Endpoint: '' #ZITADEL_TRACING_ENDPOINT Endpoint: "" #ZITADEL_TRACING_ENDPOINT
Telemetry: Telemetry:
# As long as Enabled is true, ZITADEL tries to send usage data to the configured Telemetry.Endpoints. # As long as Enabled is true, ZITADEL tries to send usage data to the configured Telemetry.Endpoints.
@ -264,7 +264,7 @@ Auth:
# See Projections.BulkLimit # See Projections.BulkLimit
SearchLimit: 1000 # ZITADEL_AUTH_SEARCHLIMIT SearchLimit: 1000 # ZITADEL_AUTH_SEARCHLIMIT
Spooler: Spooler:
# See Projections.TransationDuration # See Projections.TransationDuration
TransactionDuration: 10s #ZITADEL_AUTH_SPOOLER_TRANSACTIONDURATION TransactionDuration: 10s #ZITADEL_AUTH_SPOOLER_TRANSACTIONDURATION
# See Projections.BulkLimit # See Projections.BulkLimit
BulkLimit: 100 #ZITADEL_AUTH_SPOOLER_BULKLIMIT BulkLimit: 100 #ZITADEL_AUTH_SPOOLER_BULKLIMIT
@ -704,6 +704,9 @@ DefaultInstance:
PrivacyLink: https://zitadel.com/docs/legal/privacy-policy # ZITADEL_DEFAULTINSTANCE_PRIVACYPOLICY_PRIVACYLINK PrivacyLink: https://zitadel.com/docs/legal/privacy-policy # ZITADEL_DEFAULTINSTANCE_PRIVACYPOLICY_PRIVACYLINK
HelpLink: "" # ZITADEL_DEFAULTINSTANCE_PRIVACYPOLICY_HELPLINK HelpLink: "" # ZITADEL_DEFAULTINSTANCE_PRIVACYPOLICY_HELPLINK
SupportEmail: "" # ZITADEL_DEFAULTINSTANCE_PRIVACYPOLICY_SUPPORTEMAIL SupportEmail: "" # ZITADEL_DEFAULTINSTANCE_PRIVACYPOLICY_SUPPORTEMAIL
DocsLink: https://zitadel.com/docs # ZITADEL_DEFAULTINSTANCE_PRIVACYPOLICY_DOCSLINK
CustomLink: "" # ZITADEL_DEFAULTINSTANCE_PRIVACYPOLICY_CUSTOMLINK
CustomLinkText: "" # ZITADEL_DEFAULTINSTANCE_PRIVACYPOLICY_CUSTOMLINKTEXT
NotificationPolicy: NotificationPolicy:
PasswordChange: true # ZITADEL_DEFAULTINSTANCE_NOTIFICATIONPOLICY_PASSWORDCHANGE PasswordChange: true # ZITADEL_DEFAULTINSTANCE_NOTIFICATIONPOLICY_PASSWORDCHANGE
LabelPolicy: LabelPolicy:

View File

@ -1,11 +1,11 @@
<div class="footer-wrapper"> <div class="footer-wrapper">
<div class="footer-row"> <div class="footer-row">
<div class="footer-links"> <div class="footer-links" *ngIf="authService.privacypolicy | async as pP">
<a target="_blank" *ngIf="policy?.tosLink" rel="noreferrer" [href]="policy?.tosLink" external> <a target="_blank" *ngIf="pP?.tosLink" rel="noreferrer" [href]="pP?.tosLink" external>
<span>{{ 'FOOTER.LINKS.TOS' | translate }}</span> <span>{{ 'FOOTER.LINKS.TOS' | translate }}</span>
<i class="las la-external-link-alt"></i> <i class="las la-external-link-alt"></i>
</a> </a>
<a target="_blank" *ngIf="policy?.privacyLink" rel="noreferrer" [href]="policy?.privacyLink" external> <a target="_blank" *ngIf="pP?.privacyLink" rel="noreferrer" [href]="pP?.privacyLink" external>
<span>{{ 'FOOTER.LINKS.PP' | translate }}</span> <span>{{ 'FOOTER.LINKS.PP' | translate }}</span>
<i class="las la-external-link-alt"></i> <i class="las la-external-link-alt"></i>
</a> </a>

View File

@ -8,16 +8,7 @@ import { faXTwitter } from '@fortawesome/free-brands-svg-icons';
templateUrl: './footer.component.html', templateUrl: './footer.component.html',
styleUrls: ['./footer.component.scss'], styleUrls: ['./footer.component.scss'],
}) })
export class FooterComponent implements OnInit { export class FooterComponent {
public policy?: PrivacyPolicy.AsObject;
public faXTwitter = faXTwitter; public faXTwitter = faXTwitter;
constructor(public authService: GrpcAuthService) {} constructor(public authService: GrpcAuthService) {}
ngOnInit(): void {
this.authService.getMyPrivacyPolicy().then((policyResp) => {
if (policyResp.policy) {
this.policy = policyResp.policy;
}
});
}
} }

View File

@ -168,7 +168,11 @@
<span class="fill-space"></span> <span class="fill-space"></span>
<a class="doc-link" href="https://zitadel.com/docs" mat-stroked-button target="_blank"> <a class="custom-link" *ngIf="customLink && customLinkText" href="{{ customLink }}" mat-stroked-button target="_blank">
{{ customLinkText }}
</a>
<a class="doc-link" *ngIf="docsLink" href="{{ docsLink }}" mat-stroked-button target="_blank">
{{ 'MENU.DOCUMENTATION' | translate }} {{ 'MENU.DOCUMENTATION' | translate }}
</a> </a>

View File

@ -224,7 +224,8 @@
flex: 1; flex: 1;
} }
.doc-link { .doc-link,
.custom-link {
margin-right: 1rem; margin-right: 1rem;
@media only screen and (max-width: 800px) { @media only screen and (max-width: 800px) {

View File

@ -1,5 +1,5 @@
import { ConnectedPosition, ConnectionPositionPair } from '@angular/cdk/overlay'; import { ConnectedPosition, ConnectionPositionPair } from '@angular/cdk/overlay';
import { Component, ElementRef, EventEmitter, Input, OnDestroy, Output, ViewChild } from '@angular/core'; import { Component, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { BehaviorSubject, Observable, of, Subject } from 'rxjs'; import { BehaviorSubject, Observable, of, Subject } from 'rxjs';
import { Org } from 'src/app/proto/generated/zitadel/org_pb'; import { Org } from 'src/app/proto/generated/zitadel/org_pb';
@ -8,8 +8,8 @@ import { AuthenticationService } from 'src/app/services/authentication.service';
import { BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service'; import { BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service'; import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ManagementService } from 'src/app/services/mgmt.service'; import { ManagementService } from 'src/app/services/mgmt.service';
import { ActionKeysType } from '../action-keys/action-keys.component'; import { ActionKeysType } from '../action-keys/action-keys.component';
import { GetPrivacyPolicyResponse } from 'src/app/proto/generated/zitadel/management_pb';
@Component({ @Component({
selector: 'cnsl-header', selector: 'cnsl-header',
@ -31,6 +31,9 @@ export class HeaderComponent implements OnDestroy {
private destroy$: Subject<void> = new Subject(); private destroy$: Subject<void> = new Subject();
public BreadcrumbType: any = BreadcrumbType; public BreadcrumbType: any = BreadcrumbType;
public ActionKeysType: any = ActionKeysType; public ActionKeysType: any = ActionKeysType;
public docsLink = 'https://zitadel.com/docs';
public customLink = '';
public customLinkText = '';
public positions: ConnectedPosition[] = [ public positions: ConnectedPosition[] = [
new ConnectionPositionPair({ originX: 'start', originY: 'bottom' }, { overlayX: 'start', overlayY: 'top' }, 0, 10), new ConnectionPositionPair({ originX: 'start', originY: 'bottom' }, { overlayX: 'start', overlayY: 'top' }, 0, 10),
@ -47,7 +50,25 @@ export class HeaderComponent implements OnDestroy {
public mgmtService: ManagementService, public mgmtService: ManagementService,
public breadcrumbService: BreadcrumbService, public breadcrumbService: BreadcrumbService,
public router: Router, public router: Router,
) {} ) {
this.loadData();
}
public async loadData(): Promise<any> {
const getData = (): Promise<GetPrivacyPolicyResponse.AsObject> => {
return this.mgmtService.getPrivacyPolicy();
};
getData()
.then((resp) => {
if (resp.policy) {
this.docsLink = resp.policy.docsLink;
this.customLink = resp.policy.customLink;
this.customLinkText = resp.policy.customLinkText;
}
})
.catch(() => {});
}
public ngOnDestroy() { public ngOnDestroy() {
this.destroy$.next(); this.destroy$.next();

View File

@ -9,6 +9,7 @@
color="warn" color="warn"
(click)="resetDefault()" (click)="resetDefault()"
mat-stroked-button mat-stroked-button
data-e2e="reset-button"
> >
{{ 'POLICY.RESET' | translate }} {{ 'POLICY.RESET' | translate }}
</button> </button>
@ -40,7 +41,23 @@
<cnsl-form-field class="privacy-policy-formfield"> <cnsl-form-field class="privacy-policy-formfield">
<cnsl-label>{{ 'POLICY.PRIVACY_POLICY.SUPPORTEMAIL' | translate }}</cnsl-label> <cnsl-label>{{ 'POLICY.PRIVACY_POLICY.SUPPORTEMAIL' | translate }}</cnsl-label>
<input cnslInput name="supportEmail" formControlName="supportEmail" /> <input cnslInput name="supportEmail" formControlName="supportEmail" />
<template [ngTemplateOutlet]="templateRef" [ngTemplateOutletContext]="{ key: 'supportEmail' }"></template> </cnsl-form-field>
<cnsl-form-field class="privacy-policy-formfield">
<cnsl-label>{{ 'POLICY.PRIVACY_POLICY.CUSTOMLINK' | translate }}</cnsl-label>
<input cnslInput name="customLink" formControlName="customLink" />
<template [ngTemplateOutlet]="templateRef" [ngTemplateOutletContext]="{ key: 'customLink' }"></template>
</cnsl-form-field>
<cnsl-form-field class="privacy-policy-formfield">
<cnsl-label>{{ 'POLICY.PRIVACY_POLICY.CUSTOMLINKTEXT' | translate }}</cnsl-label>
<input cnslInput name="customLinkText" formControlName="customLinkText" />
</cnsl-form-field>
<cnsl-form-field class="privacy-policy-formfield">
<cnsl-label>{{ 'POLICY.PRIVACY_POLICY.DOCSLINK' | translate }}</cnsl-label>
<input cnslInput name="docsLink" formControlName="docsLink" />
<template [ngTemplateOutlet]="templateRef" [ngTemplateOutletContext]="{ key: 'docsLink' }"></template>
</cnsl-form-field> </cnsl-form-field>
</form> </form>
</div> </div>
@ -53,6 +70,7 @@
color="primary" color="primary"
type="submit" type="submit"
mat-raised-button mat-raised-button
data-e2e="save-button"
> >
{{ 'ACTIONS.SAVE' | translate }} {{ 'ACTIONS.SAVE' | translate }}
</button> </button>

View File

@ -61,6 +61,9 @@ export class PrivacyPolicyComponent implements OnInit, OnDestroy {
privacyLink: ['', []], privacyLink: ['', []],
helpLink: ['', []], helpLink: ['', []],
supportEmail: ['', []], supportEmail: ['', []],
docsLink: ['', []],
customLink: ['', []],
customLinkText: ['', []],
}); });
this.canWrite$.pipe(take(1)).subscribe((canWrite) => { this.canWrite$.pipe(take(1)).subscribe((canWrite) => {
@ -107,6 +110,9 @@ export class PrivacyPolicyComponent implements OnInit, OnDestroy {
privacyLink: '', privacyLink: '',
helpLink: '', helpLink: '',
supportEmail: '', supportEmail: '',
docsLink: '',
customLink: '',
customLinkText: '',
}); });
} }
}) })
@ -117,6 +123,9 @@ export class PrivacyPolicyComponent implements OnInit, OnDestroy {
privacyLink: '', privacyLink: '',
helpLink: '', helpLink: '',
supportEmail: '', supportEmail: '',
docsLink: '',
customLink: '',
customLinkText: '',
}); });
}); });
} }
@ -129,11 +138,16 @@ export class PrivacyPolicyComponent implements OnInit, OnDestroy {
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); req.setSupportEmail(this.form.get('supportEmail')?.value);
req.setDocsLink(this.form.get('docsLink')?.value);
req.setCustomLink(this.form.get('customLink')?.value);
req.setCustomLinkText(this.form.get('customLinkText')?.value);
(this.service as ManagementService) (this.service as ManagementService)
.addCustomPrivacyPolicy(req) .addCustomPrivacyPolicy(req)
.then(() => { .then(() => {
this.toast.showInfo('POLICY.PRIVACY_POLICY.SAVED', true); this.toast.showInfo('POLICY.PRIVACY_POLICY.SAVED', true);
this.loadData(); this.loadData();
// Reload console as links may have changed
this.reloadConsole();
}) })
.catch((error) => this.toast.showError(error)); .catch((error) => this.toast.showError(error));
} else { } else {
@ -142,12 +156,17 @@ export class PrivacyPolicyComponent implements OnInit, OnDestroy {
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); req.setSupportEmail(this.form.get('supportEmail')?.value);
req.setDocsLink(this.form.get('docsLink')?.value);
req.setCustomLink(this.form.get('customLink')?.value);
req.setCustomLinkText(this.form.get('customLinkText')?.value);
(this.service as ManagementService) (this.service as ManagementService)
.updateCustomPrivacyPolicy(req) .updateCustomPrivacyPolicy(req)
.then(() => { .then(() => {
this.toast.showInfo('POLICY.PRIVACY_POLICY.SAVED', true); this.toast.showInfo('POLICY.PRIVACY_POLICY.SAVED', true);
this.loadData(); this.loadData();
// Reload console as links may have changed
this.reloadConsole();
}) })
.catch((error) => this.toast.showError(error)); .catch((error) => this.toast.showError(error));
} }
@ -157,12 +176,17 @@ export class PrivacyPolicyComponent implements OnInit, OnDestroy {
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); req.setSupportEmail(this.form.get('supportEmail')?.value);
req.setDocsLink(this.form.get('docsLink')?.value);
req.setCustomLink(this.form.get('customLink')?.value);
req.setCustomLinkText(this.form.get('customLinkText')?.value);
(this.service as AdminService) (this.service as AdminService)
.updatePrivacyPolicy(req) .updatePrivacyPolicy(req)
.then(() => { .then(() => {
this.toast.showInfo('POLICY.PRIVACY_POLICY.SAVED', true); this.toast.showInfo('POLICY.PRIVACY_POLICY.SAVED', true);
this.loadData(); this.loadData();
// Reload console as links may have changed
this.reloadConsole();
}) })
.catch((error) => this.toast.showError(error)); .catch((error) => this.toast.showError(error));
} }
@ -188,6 +212,7 @@ export class PrivacyPolicyComponent implements OnInit, OnDestroy {
.then(() => { .then(() => {
setTimeout(() => { setTimeout(() => {
this.loadData(); this.loadData();
window.location.reload();
}, 1000); }, 1000);
}) })
.catch((error) => { .catch((error) => {
@ -209,4 +234,10 @@ export class PrivacyPolicyComponent implements OnInit, OnDestroy {
return false; return false;
} }
} }
private reloadConsole(): void {
setTimeout(() => {
window.location.reload();
}, 1000);
}
} }

View File

@ -36,7 +36,7 @@ export const APPEARANCE_GROUP: SettingLinks = {
}; };
export const PRIVACY_POLICY: SettingLinks = { export const PRIVACY_POLICY: SettingLinks = {
i18nTitle: 'SETTINGS.LIST.PRIVACYPOLICY', i18nTitle: 'DESCRIPTIONS.SETTINGS.PRIVACY_POLICY.TITLE',
i18nDesc: 'POLICY.PRIVACY_POLICY.DESCRIPTION', i18nDesc: 'POLICY.PRIVACY_POLICY.DESCRIPTION',
iamRouterLink: ['/settings'], iamRouterLink: ['/settings'],
orgRouterLink: ['/org-settings'], orgRouterLink: ['/org-settings'],

View File

@ -187,7 +187,7 @@ export const LOGINTEXTS: SidenavSetting = {
export const PRIVACYPOLICY: SidenavSetting = { export const PRIVACYPOLICY: SidenavSetting = {
id: 'privacypolicy', id: 'privacypolicy',
i18nKey: 'SETTINGS.LIST.PRIVACYPOLICY', i18nKey: 'DESCRIPTIONS.SETTINGS.PRIVACY_POLICY.TITLE',
groupI18nKey: 'SETTINGS.GROUPS.OTHER', groupI18nKey: 'SETTINGS.GROUPS.OTHER',
requiredRoles: { requiredRoles: {
[PolicyComponentServiceType.MGMT]: ['policy.read'], [PolicyComponentServiceType.MGMT]: ['policy.read'],

View File

@ -27,7 +27,6 @@ import {
GetMyPhoneRequest, GetMyPhoneRequest,
GetMyPhoneResponse, GetMyPhoneResponse,
GetMyPrivacyPolicyRequest, GetMyPrivacyPolicyRequest,
GetMyPrivacyPolicyResponse,
GetMyProfileRequest, GetMyProfileRequest,
GetMyProfileResponse, GetMyProfileResponse,
GetMyUserRequest, GetMyUserRequest,
@ -99,11 +98,10 @@ import { ChangeQuery } from '../proto/generated/zitadel/change_pb';
import { MetadataQuery } from '../proto/generated/zitadel/metadata_pb'; import { MetadataQuery } from '../proto/generated/zitadel/metadata_pb';
import { ListQuery } from '../proto/generated/zitadel/object_pb'; import { ListQuery } from '../proto/generated/zitadel/object_pb';
import { Org, OrgFieldName, OrgQuery } from '../proto/generated/zitadel/org_pb'; import { Org, OrgFieldName, OrgQuery } from '../proto/generated/zitadel/org_pb';
import { LabelPolicy } from '../proto/generated/zitadel/policy_pb'; import { LabelPolicy, PrivacyPolicy } from '../proto/generated/zitadel/policy_pb';
import { Gender, MembershipQuery, User, WebAuthNVerification } from '../proto/generated/zitadel/user_pb'; import { Gender, MembershipQuery, User, WebAuthNVerification } from '../proto/generated/zitadel/user_pb';
import { GrpcService } from './grpc.service'; import { GrpcService } from './grpc.service';
import { StorageKey, StorageLocation, StorageService } from './storage.service'; import { StorageKey, StorageLocation, StorageService } from './storage.service';
import { ThemeService } from './theme.service';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@ -137,11 +135,18 @@ export class GrpcAuthService {
>(undefined); >(undefined);
labelPolicyLoading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true); labelPolicyLoading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
public privacypolicy$!: Observable<PrivacyPolicy.AsObject>;
public privacypolicy: BehaviorSubject<PrivacyPolicy.AsObject | undefined> = new BehaviorSubject<
PrivacyPolicy.AsObject | undefined
>(undefined);
privacyPolicyLoading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
public zitadelPermissions: BehaviorSubject<string[]> = new BehaviorSubject<string[]>([]); public zitadelPermissions: BehaviorSubject<string[]> = new BehaviorSubject<string[]>([]);
public readonly fetchedZitadelPermissions: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false); public readonly fetchedZitadelPermissions: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public cachedOrgs: BehaviorSubject<Org.AsObject[]> = new BehaviorSubject<Org.AsObject[]>([]); public cachedOrgs: BehaviorSubject<Org.AsObject[]> = new BehaviorSubject<Org.AsObject[]>([]);
private cachedLabelPolicies: { [orgId: string]: LabelPolicy.AsObject } = {}; private cachedLabelPolicies: { [orgId: string]: LabelPolicy.AsObject } = {};
private cachedPrivacyPolicies: { [orgId: string]: PrivacyPolicy.AsObject } = {};
constructor( constructor(
private readonly grpcService: GrpcService, private readonly grpcService: GrpcService,
@ -169,6 +174,25 @@ export class GrpcAuthService {
}, },
}); });
this.privacypolicy$ = this.activeOrgChanged.pipe(
switchMap((org) => {
this.privacyPolicyLoading$.next(true);
return from(this.getMyPrivacyPolicy(org ? org.id : ''));
}),
filter((policy) => !!policy),
);
this.privacypolicy$.subscribe({
next: (policy) => {
this.privacypolicy.next(policy);
this.privacyPolicyLoading$.next(false);
},
error: (error) => {
console.error(error);
this.privacyPolicyLoading$.next(false);
},
});
this.user = forkJoin([ this.user = forkJoin([
of(this.oauthService.getAccessToken()), of(this.oauthService.getAccessToken()),
this.oauthService.events.pipe( this.oauthService.events.pipe(
@ -697,7 +721,23 @@ export class GrpcAuthService {
} }
} }
public getMyPrivacyPolicy(): Promise<GetMyPrivacyPolicyResponse.AsObject> { public getMyPrivacyPolicy(orgIdForCache?: string): Promise<PrivacyPolicy.AsObject> {
return this.grpcService.auth.getMyPrivacyPolicy(new GetMyPrivacyPolicyRequest(), null).then((resp) => resp.toObject()); if (orgIdForCache && this.cachedPrivacyPolicies[orgIdForCache]) {
return Promise.resolve(this.cachedPrivacyPolicies[orgIdForCache]);
} else {
return this.grpcService.auth
.getMyPrivacyPolicy(new GetMyPrivacyPolicyRequest(), null)
.then((resp) => resp.toObject())
.then((resp) => {
if (resp.policy) {
if (orgIdForCache) {
this.cachedPrivacyPolicies[orgIdForCache] = resp.policy;
}
return Promise.resolve(resp.policy);
} else {
return Promise.reject();
}
});
}
} }
} }

View File

@ -129,7 +129,7 @@
}, },
"PRIVACY_POLICY": { "PRIVACY_POLICY": {
"TITLE": "Външни връзки", "TITLE": "Външни връзки",
"DESCRIPTION": "Насочете вашите потребители към персонализирани външни ресурси, показани на страницата за вход. Потребителите трябва да приемат Условията за ползване и Политиката за поверителност, преди да могат да се регистрират." "DESCRIPTION": "Насочете потребителите си към персонализирани външни ресурси, показани на страницата за вход. Потребителите трябва да приемат Общите условия и Политиката за поверителност, преди да могат да се регистрират. Променете връзката към вашата документация или задайте празен низ, за ​​да скриете бутона за документация от конзолата. Добавете персонализирана външна връзка и персонализиран текст за тази връзка в конзолата или ги оставете празни, за да скриете този бутон."
}, },
"SMTP_PROVIDER": { "SMTP_PROVIDER": {
"TITLE": "Настройки на SMTP", "TITLE": "Настройки на SMTP",
@ -1564,6 +1564,9 @@
"POLICYLINK": "Връзка към Политика за поверителност", "POLICYLINK": "Връзка към Политика за поверителност",
"HELPLINK": "Връзка към Помощ", "HELPLINK": "Връзка към Помощ",
"SUPPORTEMAIL": "Имейл за поддръжка", "SUPPORTEMAIL": "Имейл за поддръжка",
"DOCSLINK": "Връзка към документи (Console)",
"CUSTOMLINK": "Персонализирана връзка (Console)",
"CUSTOMLINKTEXT": "Персонализиран текст на връзката (Console)",
"SAVED": "Запазено успешно!", "SAVED": "Запазено успешно!",
"RESET_TITLE": "Възстановяване на стойностите по подразбиране", "RESET_TITLE": "Възстановяване на стойностите по подразбиране",
"RESET_DESCRIPTION": "На път сте да възстановите връзките по подразбиране за TOS и Политика за поверителност. " "RESET_DESCRIPTION": "На път сте да възстановите връзките по подразбиране за TOS и Политика за поверителност. "

View File

@ -129,7 +129,7 @@
}, },
"PRIVACY_POLICY": { "PRIVACY_POLICY": {
"TITLE": "Externí odkazy", "TITLE": "Externí odkazy",
"DESCRIPTION": "Navede vaše uživatele k vlastním externím zdrojům zobrazeným na přihlašovací stránce. Uživatelé musí přijmout Podmínky služby a Zásady ochrany osobních údajů, než se mohou zaregistrovat." "DESCRIPTION": "Naveďte své uživatele k vlastním externím zdrojům zobrazeným na přihlašovací stránce. Než se uživatelé mohou zaregistrovat, musí přijmout podmínky služby a zásady ochrany osobních údajů. Změňte odkaz na dokumentaci nebo nastavte prázdný řetězec, abyste skryli tlačítko dokumentace z konzoly. Přidejte vlastní externí odkaz a vlastní text pro tento odkaz v konzole nebo je nastavte na prázdné, abyste toto tlačítko skryli."
}, },
"SMTP_PROVIDER": { "SMTP_PROVIDER": {
"TITLE": "Nastavení SMTP", "TITLE": "Nastavení SMTP",
@ -1571,6 +1571,9 @@
"POLICYLINK": "Odkaz na Zásady ochrany osobních údajů", "POLICYLINK": "Odkaz na Zásady ochrany osobních údajů",
"HELPLINK": "Odkaz na pomoc", "HELPLINK": "Odkaz na pomoc",
"SUPPORTEMAIL": "E-mailová podpora", "SUPPORTEMAIL": "E-mailová podpora",
"DOCSLINK": "Odkaz na Dokumenty (Console)",
"CUSTOMLINK": "Vlastní odkaz (Console)",
"CUSTOMLINKTEXT": "Text vlastního odkazu (Console)",
"SAVED": "Úspěšně uloženo!", "SAVED": "Úspěšně uloženo!",
"RESET_TITLE": "Obnovit výchozí hodnoty", "RESET_TITLE": "Obnovit výchozí hodnoty",
"RESET_DESCRIPTION": "Chystáte se obnovit výchozí odkazy pro Podmínky služby a Zásady ochrany osobních údajů. Opravdu chcete pokračovat?" "RESET_DESCRIPTION": "Chystáte se obnovit výchozí odkazy pro Podmínky služby a Zásady ochrany osobních údajů. Opravdu chcete pokračovat?"

View File

@ -129,7 +129,7 @@
}, },
"PRIVACY_POLICY": { "PRIVACY_POLICY": {
"TITLE": "Externe Links", "TITLE": "Externe Links",
"DESCRIPTION": "Leite deine Benutzer zu benutzerdefinierten externen Ressourcen, die auf der Anmeldeseite angezeigt werden. Benutzer müssen die Allgemeinen Geschäftsbedingungen und die Datenschutzrichtlinie akzeptieren, bevor sie sich anmelden können." "DESCRIPTION": "Leiten Sie Ihre Benutzer zu benutzerdefinierten externen Ressourcen, die auf der Anmeldeseite angezeigt werden. Benutzer müssen die Nutzungsbedingungen und Datenschutzrichtlinien akzeptieren, bevor sie sich anmelden können. Ändern Sie den Link zu Ihrer Dokumentation oder legen Sie eine leere Zeichenfolge fest, um die Dokumentationsschaltfläche in der Konsole auszublenden. Fügen Sie in der Konsole einen benutzerdefinierten externen Link und einen benutzerdefinierten Text für diesen Link hinzu oder setzen Sie sie leer, um diese Schaltfläche auszublenden."
}, },
"SMTP_PROVIDER": { "SMTP_PROVIDER": {
"TITLE": "SMTP-Einstellungen", "TITLE": "SMTP-Einstellungen",
@ -1570,6 +1570,9 @@
"POLICYLINK": "Link zur den Datenschutzrichtlinien", "POLICYLINK": "Link zur den Datenschutzrichtlinien",
"HELPLINK": "Link zur Hilfestellung", "HELPLINK": "Link zur Hilfestellung",
"SUPPORTEMAIL": "Support E-Mail", "SUPPORTEMAIL": "Support E-Mail",
"DOCSLINK": "Link zu Dokumenten (Console)",
"CUSTOMLINK": "Benutzerdefinierter Link (Console)",
"CUSTOMLINKTEXT": "Benutzerdefinierter Linktext (Console)",
"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

@ -129,7 +129,7 @@
}, },
"PRIVACY_POLICY": { "PRIVACY_POLICY": {
"TITLE": "External Links", "TITLE": "External Links",
"DESCRIPTION": "Guide your users to custom external resources shown on the login page. Users need to accept the Terms of Service and Privacy Policy before they can sign up." "DESCRIPTION": "Guide your users to custom external resources shown on the login page. Users need to accept the Terms of Service and Privacy Policy before they can sign up. Change the link to your documentation or set an empty string to hide the documentation button from the console. Add a custom external link and a custom text for that link in the console, or set them empty to hide that button."
}, },
"SMTP_PROVIDER": { "SMTP_PROVIDER": {
"TITLE": "SMTP Settings", "TITLE": "SMTP Settings",
@ -1335,7 +1335,7 @@
"DOMAIN": "Domain settings", "DOMAIN": "Domain settings",
"LOGINTEXTS": "Login Interface Texts", "LOGINTEXTS": "Login Interface Texts",
"BRANDING": "Branding", "BRANDING": "Branding",
"PRIVACYPOLICY": "Privacy Policy", "PRIVACYPOLICY": "External links",
"OIDC": "OIDC Token lifetime and expiration", "OIDC": "OIDC Token lifetime and expiration",
"SECRETS": "Secret Generator", "SECRETS": "Secret Generator",
"SECURITY": "Security settings", "SECURITY": "Security settings",
@ -1571,6 +1571,9 @@
"POLICYLINK": "Link to Privacy Policy", "POLICYLINK": "Link to Privacy Policy",
"HELPLINK": "Link to Help", "HELPLINK": "Link to Help",
"SUPPORTEMAIL": "Support Email", "SUPPORTEMAIL": "Support Email",
"DOCSLINK": "Docs Link (Console)",
"CUSTOMLINK": "Custom Link (Console)",
"CUSTOMLINKTEXT": "Custom Link Text (Console)",
"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

@ -129,7 +129,7 @@
}, },
"PRIVACY_POLICY": { "PRIVACY_POLICY": {
"TITLE": "Enlaces Externos", "TITLE": "Enlaces Externos",
"DESCRIPTION": "Guía a tus usuarios hacia recursos externos personalizados mostrados en la página de inicio de sesión. Los usuarios necesitan aceptar los Términos de Servicio y la Política de Privacidad antes de que puedan registrarse." "DESCRIPTION": "Guía a tus usuarios a recursos externos personalizados que se muestran en la página de inicio de sesión. Los usuarios deben aceptar los Términos de servicio y la Política de privacidad antes de poder registrarse. Cambia el enlace a tu documentación o introduce una cadena de texto vacía para ocultar el botón de documentación de la consola. Agrega un enlace externo personalizado y un texto personalizado para dicho enlace en la consola, o déjalos vacíos para ocultar ese botón."
}, },
"SMTP_PROVIDER": { "SMTP_PROVIDER": {
"TITLE": "Configuración de SMTP", "TITLE": "Configuración de SMTP",
@ -1572,6 +1572,9 @@
"POLICYLINK": "Enlace a Política de privacidad", "POLICYLINK": "Enlace a Política de privacidad",
"HELPLINK": "Enlace de ayuda", "HELPLINK": "Enlace de ayuda",
"SUPPORTEMAIL": "Email de soporte", "SUPPORTEMAIL": "Email de soporte",
"DOCSLINK": "Enlace de documentos (Console)",
"CUSTOMLINK": "Enlace personalizado (Console)",
"CUSTOMLINKTEXT": "Texto de enlace personalizado (Console)",
"SAVED": "¡Se guardó con éxito!", "SAVED": "¡Se guardó con éxito!",
"RESET_TITLE": "Restaurar valores por defecto", "RESET_TITLE": "Restaurar valores por defecto",
"RESET_DESCRIPTION": "Estás a punto de restaurar los enlaces por defecto para los TDS y la política de privacida. ¿Quieres continuar?" "RESET_DESCRIPTION": "Estás a punto de restaurar los enlaces por defecto para los TDS y la política de privacida. ¿Quieres continuar?"

View File

@ -129,7 +129,7 @@
}, },
"PRIVACY_POLICY": { "PRIVACY_POLICY": {
"TITLE": "Liens Externes", "TITLE": "Liens Externes",
"DESCRIPTION": "Guide tes utilisateurs vers des ressources externes personnalisées affichées sur la page de connexion. Les utilisateurs doivent accepter les Termes de Service et la Politique de Confidentialité avant de pouvoir s'inscrire." "DESCRIPTION": "Guidez vos utilisateurs vers des ressources externes personnalisées affichées sur la page de connexion. Les utilisateurs doivent accepter les conditions d'utilisation et la politique de confidentialité avant de pouvoir s'inscrire. Modifiez le lien vers votre documentation ou définissez une chaîne vide pour masquer le bouton de documentation de la console. Ajoutez un lien externe personnalisé et un texte personnalisé pour ce lien dans la console, ou définissez-les vides pour masquer ce bouton."
}, },
"SMTP_PROVIDER": { "SMTP_PROVIDER": {
"TITLE": "Paramètres SMTP", "TITLE": "Paramètres SMTP",
@ -1570,6 +1570,9 @@
"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", "SUPPORTEMAIL": "E-mail d'assistance",
"DOCSLINK": "Lien Docs (Console)",
"CUSTOMLINK": "Lien personnalisé (Console)",
"CUSTOMLINKTEXT": "Texte de lien personnalisé (Console)",
"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

@ -129,7 +129,7 @@
}, },
"PRIVACY_POLICY": { "PRIVACY_POLICY": {
"TITLE": "Link Esterni", "TITLE": "Link Esterni",
"DESCRIPTION": "Guida i tuoi utenti verso risorse esterne personalizzate mostrate nella pagina di login. Gli utenti devono accettare i Termini di Servizio e la Politica sulla Privacy prima che possano iscriversi." "DESCRIPTION": "Guida i tuoi utenti alle risorse esterne personalizzate mostrate nella pagina di accesso. Gli utenti devono accettare i Termini di servizio e l'Informativa sulla privacy prima di potersi registrare. Cambia il collegamento alla tua documentazione o imposta una stringa vuota per nascondere il pulsante della documentazione dalla console. Aggiungi un collegamento esterno personalizzato e un testo personalizzato per quel collegamento nella console oppure impostali vuoti per nascondere quel pulsante."
}, },
"SMTP_PROVIDER": { "SMTP_PROVIDER": {
"TITLE": "Impostazioni SMTP", "TITLE": "Impostazioni SMTP",
@ -1570,6 +1570,9 @@
"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", "SUPPORTEMAIL": "e-mail di supporto",
"DOCSLINK": "Collegamento a Documenti (Console)",
"CUSTOMLINK": "Collegamento personalizzato (Console)",
"CUSTOMLINKTEXT": "Testo del collegamento personalizzato (Console)",
"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

@ -129,7 +129,7 @@
}, },
"PRIVACY_POLICY": { "PRIVACY_POLICY": {
"TITLE": "外部リンク", "TITLE": "外部リンク",
"DESCRIPTION": "ログインページに表示されるカスタム外部リソースへのユーザーガイドです。ユーザーは、サインアップする前に利用規約とプライバシーポリシーを受け入れる必要があります。" "DESCRIPTION": "ログイン ページに表示されるカスタム外部リソースにユーザーを誘導します。ユーザーはサインアップする前に、サービス利用規約とプライバシー ポリシーに同意する必要があります。ドキュメントへのリンクを変更するか、空の文字列を設定してコンソールからドキュメント ボタンを非表示にします。カスタム外部リンクとそのリンクのカスタム テキストをコンソールに追加するか、それらを空に設定してそのボタンを非表示にします。"
}, },
"SMTP_PROVIDER": { "SMTP_PROVIDER": {
"TITLE": "SMTP設定", "TITLE": "SMTP設定",
@ -1566,6 +1566,10 @@
"TOSLINK": "利用規約へのリンク", "TOSLINK": "利用規約へのリンク",
"POLICYLINK": "プライバシーポリシーへのリンク", "POLICYLINK": "プライバシーポリシーへのリンク",
"HELPLINK": "ヘルプへのリンク", "HELPLINK": "ヘルプへのリンク",
"SUPPORTEMAIL": "サポートメール",
"DOCSLINK": "ドキュメントリンク (Console)",
"CUSTOMLINK": "カスタムリンク(Console)",
"CUSTOMLINKTEXT": "カスタム リンク テキスト (Console)",
"SAVED": "正常に保存されました!", "SAVED": "正常に保存されました!",
"RESET_TITLE": "デフォルト値を復元する", "RESET_TITLE": "デフォルト値を復元する",
"RESET_DESCRIPTION": "TOSおよびプライバシーポリシーのデフォルトリンクを復元しようとしています。本当によろしいですか" "RESET_DESCRIPTION": "TOSおよびプライバシーポリシーのデフォルトリンクを復元しようとしています。本当によろしいですか"

View File

@ -129,7 +129,7 @@
}, },
"PRIVACY_POLICY": { "PRIVACY_POLICY": {
"TITLE": "Надворешни врски", "TITLE": "Надворешни врски",
"DESCRIPTION": "Упатете ги вашите корисници кон прилагодени надворешни ресурси прикажани на страницата за најава. Корисниците треба да ги прифатат условите за користење и политиката за приватност пред да се регистрираат." "DESCRIPTION": "Водете ги вашите корисници до сопствени надворешни ресурси прикажани на страницата за најавување. Корисниците треба да ги прифатат Условите за користење и Политиката за приватност пред да можат да се регистрираат. Променете ја врската до вашата документација или поставете празна низа за да го скриете копчето за документација од конзолата. Додајте приспособена надворешна врска и прилагоден текст за таа врска во конзолата или поставете ги празни за да го скриете тоа копче."
}, },
"SMTP_PROVIDER": { "SMTP_PROVIDER": {
"TITLE": "SMTP поставки", "TITLE": "SMTP поставки",
@ -1572,6 +1572,9 @@
"POLICYLINK": "Линк кон Политиката за приватност", "POLICYLINK": "Линк кон Политиката за приватност",
"HELPLINK": "Линк кон Помош", "HELPLINK": "Линк кон Помош",
"SUPPORTEMAIL": "Е-пошта за поддршка", "SUPPORTEMAIL": "Е-пошта за поддршка",
"DOCSLINK": "Врска за документи (Console)",
"CUSTOMLINK": "Прилагодена врска (Console)",
"CUSTOMLINKTEXT": "Текст за приспособена врска (Console)",
"SAVED": "Успешно зачувано!", "SAVED": "Успешно зачувано!",
"RESET_TITLE": "Врати на стандардни вредности", "RESET_TITLE": "Врати на стандардни вредности",
"RESET_DESCRIPTION": "Се подготвувате да ги вратите стандардните линкови за Условите за користење и Политиката за приватност. Дали сте сигурни дека сакате да продолжите?" "RESET_DESCRIPTION": "Се подготвувате да ги вратите стандардните линкови за Условите за користење и Политиката за приватност. Дали сте сигурни дека сакате да продолжите?"

View File

@ -129,7 +129,7 @@
}, },
"PRIVACY_POLICY": { "PRIVACY_POLICY": {
"TITLE": "Externe links", "TITLE": "Externe links",
"DESCRIPTION": "Leid je gebruikers naar aangepaste externe bronnen die worden getoond op de inlogpagina. Gebruikers moeten de Algemene Voorwaarden en het Privacybeleid accepteren voordat ze zich kunnen aanmelden." "DESCRIPTION": "Leid uw gebruikers naar aangepaste externe bronnen die op de inlogpagina worden weergegeven. Gebruikers moeten de Servicevoorwaarden en het Privacybeleid accepteren voordat ze zich kunnen aanmelden. Wijzig de link naar uw documentatie of stel een lege string in om de documentatieknop voor de console te verbergen. Voeg een aangepaste externe link en een aangepaste tekst voor die link toe in de console, of stel ze leeg om die knop te verbergen."
}, },
"SMTP_PROVIDER": { "SMTP_PROVIDER": {
"TITLE": "SMTP-instellingen", "TITLE": "SMTP-instellingen",
@ -1571,6 +1571,9 @@
"POLICYLINK": "Link naar Privacybeleid", "POLICYLINK": "Link naar Privacybeleid",
"HELPLINK": "Link naar Help", "HELPLINK": "Link naar Help",
"SUPPORTEMAIL": "Ondersteuning Email", "SUPPORTEMAIL": "Ondersteuning Email",
"DOCSLINK": "Documentenlink (Console)",
"CUSTOMLINK": "Aangepaste link (Console)",
"CUSTOMLINKTEXT": "Aangepaste linktekst (Console)",
"SAVED": "Succesvol opgeslagen!", "SAVED": "Succesvol opgeslagen!",
"RESET_TITLE": "Herstel Standaard Waarden", "RESET_TITLE": "Herstel Standaard Waarden",
"RESET_DESCRIPTION": "U staat op het punt de standaard Links voor TOS en Privacybeleid te herstellen. Weet u zeker dat u wilt doorgaan?" "RESET_DESCRIPTION": "U staat op het punt de standaard Links voor TOS en Privacybeleid te herstellen. Weet u zeker dat u wilt doorgaan?"

View File

@ -129,7 +129,7 @@
}, },
"PRIVACY_POLICY": { "PRIVACY_POLICY": {
"TITLE": "Linki zewnętrzne", "TITLE": "Linki zewnętrzne",
"DESCRIPTION": "Przekieruj użytkowników do niestandardowych zasobów zewnętrznych pokazanych na stronie logowania. Użytkownicy muszą zaakceptować Warunki korzystania z usługi i Politykę prywatności, zanim będą mogli się zarejestrować." "DESCRIPTION": "Poprowadź użytkowników do niestandardowych zasobów zewnętrznych wyświetlanych na stronie logowania. Użytkownicy muszą zaakceptować Warunki świadczenia usług i Politykę prywatności, zanim będą mogli się zarejestrować. Zmień łącze do dokumentacji lub ustaw pusty ciąg, aby ukryć przycisk dokumentacji w konsoli. Dodaj niestandardowy link zewnętrzny i niestandardowy tekst dla tego łącza w konsoli lub ustaw je puste, aby ukryć ten przycisk."
}, },
"SMTP_PROVIDER": { "SMTP_PROVIDER": {
"TITLE": "Ustawienia SMTP", "TITLE": "Ustawienia SMTP",
@ -1570,6 +1570,9 @@
"POLICYLINK": "Link do polityki prywatności", "POLICYLINK": "Link do polityki prywatności",
"HELPLINK": "Link do pomocy", "HELPLINK": "Link do pomocy",
"SUPPORTEMAIL": "E-mail wsparcia", "SUPPORTEMAIL": "E-mail wsparcia",
"DOCSLINK": "Link do Dokumentów (Console)",
"CUSTOMLINK": "Link niestandardowy (Console)",
"CUSTOMLINKTEXT": "Niestandardowy tekst łącza (Console)",
"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

@ -129,7 +129,7 @@
}, },
"PRIVACY_POLICY": { "PRIVACY_POLICY": {
"TITLE": "Links Externos", "TITLE": "Links Externos",
"DESCRIPTION": "Guie seus usuários para recursos externos personalizados mostrados na página de login. Os usuários precisam aceitar os Termos de Serviço e a Política de Privacidade antes de poderem se inscrever." "DESCRIPTION": "Oriente seus usuários sobre recursos externos personalizados mostrados na página de login. Os usuários precisam aceitar os Termos de Serviço e a Política de Privacidade antes de se inscreverem. Altere o link para sua documentação ou defina uma string vazia para ocultar o botão de documentação do console. Adicione um link externo personalizado e um texto personalizado para esse link no console ou deixe-os vazios para ocultar esse botão."
}, },
"SMTP_PROVIDER": { "SMTP_PROVIDER": {
"TITLE": "Configurações de SMTP", "TITLE": "Configurações de SMTP",
@ -1572,6 +1572,9 @@
"POLICYLINK": "Link para a Política de Privacidade", "POLICYLINK": "Link para a Política de Privacidade",
"HELPLINK": "Link para Ajuda", "HELPLINK": "Link para Ajuda",
"SUPPORTEMAIL": "E-mail de suporte", "SUPPORTEMAIL": "E-mail de suporte",
"DOCSLINK": "Link do Documentos (Console)",
"CUSTOMLINK": "Link personalizado (Console)",
"CUSTOMLINKTEXT": "Texto do link personalizado (Console)",
"SAVED": "Salvo com sucesso!", "SAVED": "Salvo com sucesso!",
"RESET_TITLE": "Restaurar valores padrão", "RESET_TITLE": "Restaurar valores padrão",
"RESET_DESCRIPTION": "Você está prestes a restaurar os Links padrão para TOS e Política de Privacidade. Deseja realmente continuar?" "RESET_DESCRIPTION": "Você está prestes a restaurar os Links padrão para TOS e Política de Privacidade. Deseja realmente continuar?"

View File

@ -129,7 +129,7 @@
}, },
"PRIVACY_POLICY": { "PRIVACY_POLICY": {
"TITLE": "Внешние ссылки", "TITLE": "Внешние ссылки",
"DESCRIPTION": "Направьте ваших пользователей к пользовательским внешним ресурсам, показанным на странице входа. Пользователи должны принять Условия обслуживания и Политику конфиденциальности, прежде чем они смогут зарегистрироваться." "DESCRIPTION": "Guide your users to custom external resources shown on the login page. Users need to accept the Terms of Service and Privacy Policy before they can sign up. Change the link to your documentation or set an empty string to hide the documentation button from the console. Add a custom external link and a custom text for that link in the console, or set them empty to hide that button."
}, },
"SMTP_PROVIDER": { "SMTP_PROVIDER": {
"TITLE": "Настройки SMTP", "TITLE": "Настройки SMTP",
@ -1626,6 +1626,9 @@
"POLICYLINK": "Ссылка на Политику конфиденциальности", "POLICYLINK": "Ссылка на Политику конфиденциальности",
"HELPLINK": "Ссылка на Помощь", "HELPLINK": "Ссылка на Помощь",
"SUPPORTEMAIL": "Электронная почта поддержки", "SUPPORTEMAIL": "Электронная почта поддержки",
"DOCSLINK": "Ссылка на Документы (Console)",
"CUSTOMLINK": "Пользовательская ссылка (Console)",
"CUSTOMLINKTEXT": "Пользовательский текст ссылки (Console)",
"SAVED": "Успешно сохранено!", "SAVED": "Успешно сохранено!",
"RESET_TITLE": "Восстановить значения по умолчанию", "RESET_TITLE": "Восстановить значения по умолчанию",
"RESET_DESCRIPTION": "Вы собираетесь восстановить ссылки по умолчанию для Пользовательского соглашения и Политики конфиденциальности. Вы действительно хотите продолжить?" "RESET_DESCRIPTION": "Вы собираетесь восстановить ссылки по умолчанию для Пользовательского соглашения и Политики конфиденциальности. Вы действительно хотите продолжить?"

View File

@ -129,7 +129,7 @@
}, },
"PRIVACY_POLICY": { "PRIVACY_POLICY": {
"TITLE": "外部链接", "TITLE": "外部链接",
"DESCRIPTION": "引导您的用户到登录页面上显示的自定义外部资源。用户需要在注册前接受服务条款和隐私政策。" "DESCRIPTION": "引导您的用户访问登录页面上显示的自定义外部资源。用户需要先接受服务条款和隐私政策,然后才能注册。更改文档的链接或设置空字符串以在控制台中隐藏文档按钮。在控制台中添加自定义外部链接和该链接的自定义文本,或将它们设置为空以隐藏该按钮。"
}, },
"SMTP_PROVIDER": { "SMTP_PROVIDER": {
"TITLE": "SMTP设置", "TITLE": "SMTP设置",
@ -1569,6 +1569,9 @@
"POLICYLINK": "链接到隐私政策", "POLICYLINK": "链接到隐私政策",
"HELPLINK": "链接到帮助", "HELPLINK": "链接到帮助",
"SUPPORTEMAIL": "支持邮箱", "SUPPORTEMAIL": "支持邮箱",
"DOCSLINK": "文档链接Console",
"CUSTOMLINK": "自定义链接Console",
"CUSTOMLINKTEXT": "自定义链接文本Console",
"SAVED": "保存成功!", "SAVED": "保存成功!",
"RESET_TITLE": "恢复默认值", "RESET_TITLE": "恢复默认值",
"RESET_DESCRIPTION": "您即将恢复 TOS 和隐私政策的默认链接。你真的要继续吗?" "RESET_DESCRIPTION": "您即将恢复 TOS 和隐私政策的默认链接。你真的要继续吗?"

View File

@ -26,8 +26,7 @@ When you configure your default settings, you can set the following:
- [**Branding**](#branding): Appearance of the login interface. - [**Branding**](#branding): Appearance of the login interface.
- [**Message Texts**](#message-texts): Text and internationalization for emails - [**Message Texts**](#message-texts): Text and internationalization for emails
- [**Login Interface Texts**](#login-interface-texts): Text and internationalization for the login interface - [**Login Interface Texts**](#login-interface-texts): Text and internationalization for the login interface
- [**Languages**](#languages): Select which supported langauges are shown to your users. Set the default language if no context is provided. - [**External Links**](#external-links): Links to your own Terms of Service and Privacy Policy regulations, Help Page, Support email, documentation link...
- [**Privacy Policy**](#privacy-policy-and-tos): Links to your own Terms of Service and Privacy Policy regulations. Link to Help Page.
- [**OIDC Token Lifetimes and Expiration**](#oidc-token-lifetimes-and-expiration): Token lifetime and expiration settings. - [**OIDC Token Lifetimes and Expiration**](#oidc-token-lifetimes-and-expiration): Token lifetime and expiration settings.
- [**Secret Generator**](#secret-generator): Appearance and expiration of the generated codes and secrets used in mails for verification etc. - [**Secret Generator**](#secret-generator): Appearance and expiration of the generated codes and secrets used in mails for verification etc.
@ -256,9 +255,10 @@ You can either set this attribute on your whole ZITADEL instance or just on some
Please refer to the [configuration guide](/docs/guides/solution-scenarios/configurations#use-email-to-login) for more information. Please refer to the [configuration guide](/docs/guides/solution-scenarios/configurations#use-email-to-login) for more information.
## Privacy Policy and TOS ## External links
With this setting you are able to configure your privacy policy, terms of service, help links and help/support email address. 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.
@ -269,8 +269,16 @@ Example:
`https://demo.com/tos-{{.Lang}}` `https://demo.com/tos-{{.Lang}}`
<img <img
src="/docs/img/guides/console/privacypolicy.png" src="/docs/img/guides/console/external_links_1.png"
alt="Privacy Policy" alt="External Links"
width="600px"
/>
Also you can set the link associated to the Documentation button in the console. Set an empty text if you don't want to show a Documentation button in your console. If you need a custom button to be shown in the console you can set the button text and the link associated to the button (if the button text is button no text will be shown).
<img
src="/docs/img/guides/console/external_links_2.png"
alt="Custom button"
width="600px" width="600px"
/> />

View File

@ -114,7 +114,7 @@ Those settings are the same as on your instance.
- [**Branding**](./default-settings#branding): Appearance of the login interface. - [**Branding**](./default-settings#branding): Appearance of the login interface.
- [**Message Texts**](./default-settings#message-texts): Text and internationalization for emails - [**Message Texts**](./default-settings#message-texts): Text and internationalization for emails
- [**Login Interface Texts**](./default-settings#login-interface-texts): Text and internationalization for the login interface - [**Login Interface Texts**](./default-settings#login-interface-texts): Text and internationalization for the login interface
- [**Privacy Policy**](./default-settings#privacy-policy-and-tos): Links to your own Terms of Service and Privacy Policy regulations. Link to Help Page. - [**External Links**](./default-settings#external-links): Links to your own Terms of Service and Privacy Policy regulations, Help Page, Support email, documentation link...
If you need custom branding on a organization (for example in a B2B scenario, where organizations are allowed to use their custom design), navigate back to the home page, choose your organization in the header above, navigate to the organization settings and set the custom design here. If you need custom branding on a organization (for example in a B2B scenario, where organizations are allowed to use their custom design), navigate back to the home page, choose your organization in the header above, navigate to the organization settings and set the custom design here.

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

View File

@ -0,0 +1,104 @@
import { ensureExternalLinksSettingsSet } from 'support/api/external-links-settings';
import { apiAuth } from '../../support/api/apiauth';
describe('instance external link settings', () => {
const externalLinkSettingsPath = `/instance?id=privacypolicy`;
const tosLink = 'https://zitadel.com/docs/legal/terms-of-service';
const privacyPolicyLink = 'https://zitadel.com/docs/legal/privacy-policy';
const helpLink = '';
const supportEmail = '';
const customLink = '';
const customLinkText = '';
const docsLink = 'https://zitadel.com/docs';
beforeEach(`ensure they are set`, () => {
apiAuth().then((apiCallProperties) => {
ensureExternalLinksSettingsSet(apiCallProperties, tosLink, privacyPolicyLink, docsLink);
cy.visit(externalLinkSettingsPath);
});
});
it(`should have default settings`, () => {
cy.get('[formcontrolname="tosLink"]').should('value', tosLink);
cy.get('[formcontrolname="privacyLink"]').should('value', privacyPolicyLink);
cy.get('[formcontrolname="helpLink"]').should('value', helpLink);
cy.get('[formcontrolname="supportEmail"]').should('value', supportEmail);
cy.get('[formcontrolname="customLink"]').should('value', customLink);
cy.get('[formcontrolname="customLinkText"]').should('value', customLinkText);
cy.get('[formcontrolname="docsLink"]').should('value', docsLink);
});
it(`should update external links`, () => {
cy.get('[formcontrolname="tosLink"]').clear().type('tosLink2');
cy.get('[formcontrolname="privacyLink"]').clear().type('privacyLink2');
cy.get('[formcontrolname="helpLink"]').clear().type('helpLink');
cy.get('[formcontrolname="supportEmail"]').clear().type('support@example.com');
cy.get('[formcontrolname="customLink"]').clear().type('customLink');
cy.get('[formcontrolname="customLinkText"]').clear().type('customLinkText');
cy.get('[formcontrolname="docsLink"]').clear().type('docsLink');
cy.get('[data-e2e="save-button"]').click();
cy.shouldConfirmSuccess();
});
it(`should return to default values`, () => {
cy.get('[formcontrolname="tosLink"]').should('value', tosLink);
cy.get('[formcontrolname="privacyLink"]').should('value', privacyPolicyLink);
cy.get('[formcontrolname="helpLink"]').should('value', helpLink);
cy.get('[formcontrolname="supportEmail"]').should('value', supportEmail);
cy.get('[formcontrolname="customLink"]').should('value', customLink);
cy.get('[formcontrolname="customLinkText"]').should('value', customLinkText);
cy.get('[formcontrolname="docsLink"]').should('value', docsLink);
});
});
describe('instance external link settings', () => {
const externalLinkSettingsPath = `/org-settings?id=privacypolicy`;
const tosLink = 'https://zitadel.com/docs/legal/terms-of-service';
const privacyPolicyLink = 'https://zitadel.com/docs/legal/privacy-policy';
const helpLink = '';
const supportEmail = '';
const customLink = '';
const customLinkText = '';
const docsLink = 'https://zitadel.com/docs';
beforeEach(() => {
cy.context().as('ctx');
cy.visit(externalLinkSettingsPath);
});
it(`should have default settings`, () => {
cy.get('[formcontrolname="tosLink"]').should('value', tosLink);
cy.get('[formcontrolname="privacyLink"]').should('value', privacyPolicyLink);
cy.get('[formcontrolname="helpLink"]').should('value', helpLink);
cy.get('[formcontrolname="supportEmail"]').should('value', supportEmail);
cy.get('[formcontrolname="customLink"]').should('value', customLink);
cy.get('[formcontrolname="customLinkText"]').should('value', customLinkText);
cy.get('[formcontrolname="docsLink"]').should('value', docsLink);
});
it(`should update external links`, () => {
cy.get('[formcontrolname="tosLink"]').clear().type('tosLink2');
cy.get('[formcontrolname="privacyLink"]').clear().type('privacyLink2');
cy.get('[formcontrolname="helpLink"]').clear().type('helpLink');
cy.get('[formcontrolname="supportEmail"]').clear().type('support@example.com');
cy.get('[formcontrolname="customLink"]').clear().type('customLink');
cy.get('[formcontrolname="customLinkText"]').clear().type('customLinkText');
cy.get('[formcontrolname="docsLink"]').clear().type('docsLink');
cy.get('[data-e2e="save-button"]').click();
cy.shouldConfirmSuccess();
});
it(`should return to default values`, () => {
cy.get('[data-e2e="reset-button"]').click();
cy.get('[data-e2e="confirm-dialog-button"]').click();
cy.get('[formcontrolname="tosLink"]').should('value', tosLink);
cy.get('[formcontrolname="privacyLink"]').should('value', privacyPolicyLink);
cy.get('[formcontrolname="helpLink"]').should('value', helpLink);
cy.get('[formcontrolname="supportEmail"]').should('value', supportEmail);
cy.get('[formcontrolname="customLink"]').should('value', customLink);
cy.get('[formcontrolname="customLinkText"]').should('value', customLinkText);
cy.get('[formcontrolname="docsLink"]').should('value', docsLink);
});
});

View File

@ -0,0 +1,32 @@
import { ensureSetting } from './ensure';
import { API } from './types';
export function ensureExternalLinksSettingsSet(api: API, tosLink: string, privacyPolicyLink: string, docsLink: string) {
return ensureSetting(
api,
`${api.adminBaseURL}/policies/privacy`,
(body: any) => {
const result = {
sequence: body.policy?.details?.sequence,
id: body.policy.id,
entity: null,
};
if (
body.policy &&
body.policy.tosLink === tosLink &&
body.policy.privacyLink === privacyPolicyLink &&
body.policy.docsLink === docsLink
) {
return { ...result, entity: body.policy };
}
return result;
},
`${api.adminBaseURL}/policies/privacy`,
{
tosLink,
privacyLink: privacyPolicyLink,
docsLink,
},
);
}

View File

@ -534,10 +534,13 @@ func (s *Server) getPrivacyPolicy(ctx context.Context, orgID string) (_ *managem
} }
if !queriedPrivacy.IsDefault { if !queriedPrivacy.IsDefault {
return &management_pb.AddCustomPrivacyPolicyRequest{ return &management_pb.AddCustomPrivacyPolicyRequest{
TosLink: queriedPrivacy.TOSLink, TosLink: queriedPrivacy.TOSLink,
PrivacyLink: queriedPrivacy.PrivacyLink, PrivacyLink: queriedPrivacy.PrivacyLink,
HelpLink: queriedPrivacy.HelpLink, HelpLink: queriedPrivacy.HelpLink,
SupportEmail: string(queriedPrivacy.SupportEmail), SupportEmail: string(queriedPrivacy.SupportEmail),
DocsLink: queriedPrivacy.DocsLink,
CustomLink: queriedPrivacy.CustomLink,
CustomLinkText: queriedPrivacy.CustomLinkText,
}, nil }, nil
} }
return nil, nil return nil, nil

View File

@ -7,9 +7,12 @@ import (
func UpdatePrivacyPolicyToDomain(req *admin_pb.UpdatePrivacyPolicyRequest) *domain.PrivacyPolicy { func UpdatePrivacyPolicyToDomain(req *admin_pb.UpdatePrivacyPolicyRequest) *domain.PrivacyPolicy {
return &domain.PrivacyPolicy{ return &domain.PrivacyPolicy{
TOSLink: req.TosLink, TOSLink: req.TosLink,
PrivacyLink: req.PrivacyLink, PrivacyLink: req.PrivacyLink,
HelpLink: req.HelpLink, HelpLink: req.HelpLink,
SupportEmail: domain.EmailAddress(req.SupportEmail), SupportEmail: domain.EmailAddress(req.SupportEmail),
DocsLink: req.DocsLink,
CustomLink: req.CustomLink,
CustomLinkText: req.CustomLinkText,
} }
} }

View File

@ -7,18 +7,24 @@ import (
func AddPrivacyPolicyToDomain(req *mgmt_pb.AddCustomPrivacyPolicyRequest) *domain.PrivacyPolicy { func AddPrivacyPolicyToDomain(req *mgmt_pb.AddCustomPrivacyPolicyRequest) *domain.PrivacyPolicy {
return &domain.PrivacyPolicy{ return &domain.PrivacyPolicy{
TOSLink: req.TosLink, TOSLink: req.TosLink,
PrivacyLink: req.PrivacyLink, PrivacyLink: req.PrivacyLink,
HelpLink: req.HelpLink, HelpLink: req.HelpLink,
SupportEmail: domain.EmailAddress(req.SupportEmail), SupportEmail: domain.EmailAddress(req.SupportEmail),
DocsLink: req.DocsLink,
CustomLink: req.CustomLink,
CustomLinkText: req.CustomLinkText,
} }
} }
func UpdatePrivacyPolicyToDomain(req *mgmt_pb.UpdateCustomPrivacyPolicyRequest) *domain.PrivacyPolicy { func UpdatePrivacyPolicyToDomain(req *mgmt_pb.UpdateCustomPrivacyPolicyRequest) *domain.PrivacyPolicy {
return &domain.PrivacyPolicy{ return &domain.PrivacyPolicy{
TOSLink: req.TosLink, TOSLink: req.TosLink,
PrivacyLink: req.PrivacyLink, PrivacyLink: req.PrivacyLink,
HelpLink: req.HelpLink, HelpLink: req.HelpLink,
SupportEmail: domain.EmailAddress(req.SupportEmail), SupportEmail: domain.EmailAddress(req.SupportEmail),
DocsLink: req.DocsLink,
CustomLink: req.CustomLink,
CustomLinkText: req.CustomLinkText,
} }
} }

View File

@ -19,5 +19,8 @@ func ModelPrivacyPolicyToPb(policy *query.PrivacyPolicy) *policy_pb.PrivacyPolic
policy.ChangeDate, policy.ChangeDate,
policy.ResourceOwner, policy.ResourceOwner,
), ),
DocsLink: policy.DocsLink,
CustomLink: policy.CustomLink,
CustomLinkText: policy.CustomLinkText,
} }
} }

View File

@ -154,6 +154,9 @@ func legalAndSupportSettingsToPb(current *query.PrivacyPolicy) *settings.LegalAn
HelpLink: current.HelpLink, HelpLink: current.HelpLink,
SupportEmail: string(current.SupportEmail), SupportEmail: string(current.SupportEmail),
ResourceOwnerType: isDefaultToResourceOwnerTypePb(current.IsDefault), ResourceOwnerType: isDefaultToResourceOwnerTypePb(current.IsDefault),
DocsLink: current.DocsLink,
CustomLink: current.CustomLink,
CustomLinkText: current.CustomLinkText,
} }
} }

View File

@ -316,17 +316,23 @@ func Test_domainSettingsToPb(t *testing.T) {
func Test_legalSettingsToPb(t *testing.T) { func Test_legalSettingsToPb(t *testing.T) {
arg := &query.PrivacyPolicy{ arg := &query.PrivacyPolicy{
TOSLink: "http://example.com/tos", TOSLink: "http://example.com/tos",
PrivacyLink: "http://example.com/pricacy", PrivacyLink: "http://example.com/pricacy",
HelpLink: "http://example.com/help", HelpLink: "http://example.com/help",
SupportEmail: "support@zitadel.com", SupportEmail: "support@zitadel.com",
IsDefault: true, IsDefault: true,
DocsLink: "http://example.com/docs",
CustomLink: "http://example.com/custom",
CustomLinkText: "Custom",
} }
want := &settings.LegalAndSupportSettings{ want := &settings.LegalAndSupportSettings{
TosLink: "http://example.com/tos", TosLink: "http://example.com/tos",
PrivacyPolicyLink: "http://example.com/pricacy", PrivacyPolicyLink: "http://example.com/pricacy",
HelpLink: "http://example.com/help", HelpLink: "http://example.com/help",
SupportEmail: "support@zitadel.com", SupportEmail: "support@zitadel.com",
DocsLink: "http://example.com/docs",
CustomLink: "http://example.com/custom",
CustomLinkText: "Custom",
ResourceOwnerType: settings.ResourceOwnerType_RESOURCE_OWNER_TYPE_INSTANCE, ResourceOwnerType: settings.ResourceOwnerType_RESOURCE_OWNER_TYPE_INSTANCE,
} }
got := legalAndSupportSettingsToPb(arg) got := legalAndSupportSettingsToPb(arg)

View File

@ -1286,12 +1286,15 @@ func privacyPolicyToDomain(p *query.PrivacyPolicy) *domain.PrivacyPolicy {
CreationDate: p.CreationDate, CreationDate: p.CreationDate,
ChangeDate: p.ChangeDate, ChangeDate: p.ChangeDate,
}, },
State: p.State, State: p.State,
Default: p.IsDefault, Default: p.IsDefault,
TOSLink: p.TOSLink, TOSLink: p.TOSLink,
PrivacyLink: p.PrivacyLink, PrivacyLink: p.PrivacyLink,
HelpLink: p.HelpLink, HelpLink: p.HelpLink,
SupportEmail: p.SupportEmail, SupportEmail: p.SupportEmail,
DocsLink: p.DocsLink,
CustomLink: p.CustomLink,
CustomLinkText: p.CustomLinkText,
} }
} }

View File

@ -81,10 +81,13 @@ type InstanceSetup struct {
PasswordChange bool PasswordChange bool
} }
PrivacyPolicy struct { PrivacyPolicy struct {
TOSLink string TOSLink string
PrivacyLink string PrivacyLink string
HelpLink string HelpLink string
SupportEmail domain.EmailAddress SupportEmail domain.EmailAddress
DocsLink string
CustomLink string
CustomLinkText string
} }
LabelPolicy struct { LabelPolicy struct {
PrimaryColor string PrimaryColor string
@ -270,7 +273,7 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str
*/ */
prepareAddMultiFactorToDefaultLoginPolicy(instanceAgg, domain.MultiFactorTypeU2FWithPIN), prepareAddMultiFactorToDefaultLoginPolicy(instanceAgg, domain.MultiFactorTypeU2FWithPIN),
prepareAddDefaultPrivacyPolicy(instanceAgg, setup.PrivacyPolicy.TOSLink, setup.PrivacyPolicy.PrivacyLink, setup.PrivacyPolicy.HelpLink, setup.PrivacyPolicy.SupportEmail), prepareAddDefaultPrivacyPolicy(instanceAgg, setup.PrivacyPolicy.TOSLink, setup.PrivacyPolicy.PrivacyLink, setup.PrivacyPolicy.HelpLink, setup.PrivacyPolicy.SupportEmail, setup.PrivacyPolicy.DocsLink, setup.PrivacyPolicy.CustomLink, setup.PrivacyPolicy.CustomLinkText),
prepareAddDefaultNotificationPolicy(instanceAgg, setup.NotificationPolicy.PasswordChange), prepareAddDefaultNotificationPolicy(instanceAgg, setup.NotificationPolicy.PasswordChange),
prepareAddDefaultLockoutPolicy(instanceAgg, setup.LockoutPolicy.MaxPasswordAttempts, setup.LockoutPolicy.MaxOTPAttempts, setup.LockoutPolicy.ShouldShowLockoutFailure), prepareAddDefaultLockoutPolicy(instanceAgg, setup.LockoutPolicy.MaxPasswordAttempts, setup.LockoutPolicy.MaxOTPAttempts, setup.LockoutPolicy.ShouldShowLockoutFailure),

View File

@ -116,11 +116,14 @@ func writeModelToLockoutPolicy(wm *LockoutPolicyWriteModel) *domain.LockoutPolic
func writeModelToPrivacyPolicy(wm *PrivacyPolicyWriteModel) *domain.PrivacyPolicy { func writeModelToPrivacyPolicy(wm *PrivacyPolicyWriteModel) *domain.PrivacyPolicy {
return &domain.PrivacyPolicy{ return &domain.PrivacyPolicy{
ObjectRoot: writeModelToObjectRoot(wm.WriteModel), ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
TOSLink: wm.TOSLink, TOSLink: wm.TOSLink,
PrivacyLink: wm.PrivacyLink, PrivacyLink: wm.PrivacyLink,
HelpLink: wm.HelpLink, HelpLink: wm.HelpLink,
SupportEmail: wm.SupportEmail, SupportEmail: wm.SupportEmail,
DocsLink: wm.DocsLink,
CustomLink: wm.CustomLink,
CustomLinkText: wm.CustomLinkText,
} }
} }

View File

@ -12,17 +12,37 @@ import (
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
) )
func (c *Commands) AddDefaultPrivacyPolicy(ctx context.Context, tosLink, privacyLink, helpLink string, supportEmail domain.EmailAddress) (*domain.ObjectDetails, error) { func (c *Commands) AddDefaultPrivacyPolicy(ctx context.Context, tosLink, privacyLink, helpLink string, supportEmail domain.EmailAddress, docsLink, customLink, customLinkText string) (*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, supportEmail))
if supportEmail != "" {
if err := supportEmail.Validate(); err != nil {
return nil, err
}
supportEmail = supportEmail.Normalize()
}
writeModel := NewInstancePrivacyPolicyWriteModel(ctx)
err := c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil { if err != nil {
return nil, err return nil, err
} }
pushedEvents, err := c.eventstore.Push(ctx, cmds...) if writeModel.State.Exists() {
return nil, zerrors.ThrowAlreadyExists(nil, "INSTANCE-M00rJ", "Errors.Instance.PrivacyPolicy.AlreadyExists")
}
event := instance.NewPrivacyPolicyAddedEvent(ctx, &instanceAgg.Aggregate, tosLink, privacyLink, helpLink, supportEmail, docsLink, customLink, customLinkText)
pushedEvents, err := c.eventstore.Push(ctx, event)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return pushedEventsToObjectDetails(pushedEvents), nil err = AppendAndReduce(writeModel, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&writeModel.WriteModel), nil
} }
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) {
@ -42,7 +62,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, policy.SupportEmail) changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, instanceAgg, policy.TOSLink, policy.PrivacyLink, policy.HelpLink, policy.SupportEmail, policy.DocsLink, policy.CustomLink, policy.CustomLinkText)
if !hasChanged { if !hasChanged {
return nil, zerrors.ThrowPreconditionFailed(nil, "INSTANCE-9jJfs", "Errors.IAM.PrivacyPolicy.NotChanged") return nil, zerrors.ThrowPreconditionFailed(nil, "INSTANCE-9jJfs", "Errors.IAM.PrivacyPolicy.NotChanged")
} }
@ -89,6 +109,7 @@ func prepareAddDefaultPrivacyPolicy(
privacyLink, privacyLink,
helpLink string, helpLink string,
supportEmail domain.EmailAddress, supportEmail domain.EmailAddress,
docsLink, customLink, customLinkText string,
) preparation.Validation { ) preparation.Validation {
return func() (preparation.CreateCommands, error) { return func() (preparation.CreateCommands, error) {
if supportEmail != "" { if supportEmail != "" {
@ -111,7 +132,7 @@ func prepareAddDefaultPrivacyPolicy(
return nil, zerrors.ThrowAlreadyExists(nil, "INSTANCE-M00rJ", "Errors.Instance.PrivacyPolicy.AlreadyExists") return nil, zerrors.ThrowAlreadyExists(nil, "INSTANCE-M00rJ", "Errors.Instance.PrivacyPolicy.AlreadyExists")
} }
return []eventstore.Command{ return []eventstore.Command{
instance.NewPrivacyPolicyAddedEvent(ctx, &a.Aggregate, tosLink, privacyLink, helpLink, supportEmail), instance.NewPrivacyPolicyAddedEvent(ctx, &a.Aggregate, tosLink, privacyLink, helpLink, supportEmail, docsLink, customLink, customLinkText),
}, nil }, nil
}, nil }, nil
} }

View File

@ -59,6 +59,7 @@ func (wm *InstancePrivacyPolicyWriteModel) NewChangedEvent(
privacyLink, privacyLink,
helpLink string, helpLink string,
supportEmail domain.EmailAddress, supportEmail domain.EmailAddress,
docsLink, customLink, customLinkText string,
) (*instance.PrivacyPolicyChangedEvent, bool) { ) (*instance.PrivacyPolicyChangedEvent, bool) {
changes := make([]policy.PrivacyPolicyChanges, 0) changes := make([]policy.PrivacyPolicyChanges, 0)
@ -74,6 +75,15 @@ func (wm *InstancePrivacyPolicyWriteModel) NewChangedEvent(
if wm.SupportEmail != supportEmail { if wm.SupportEmail != supportEmail {
changes = append(changes, policy.ChangeSupportEmail(supportEmail)) changes = append(changes, policy.ChangeSupportEmail(supportEmail))
} }
if wm.DocsLink != docsLink {
changes = append(changes, policy.ChangeDocsLink(docsLink))
}
if wm.CustomLink != customLink {
changes = append(changes, policy.ChangeCustomLink(customLink))
}
if wm.CustomLinkText != customLinkText {
changes = append(changes, policy.ChangeCustomLinkText(customLinkText))
}
if len(changes) == 0 { if len(changes) == 0 {
return nil, false return nil, false
} }

View File

@ -20,11 +20,14 @@ func TestCommandSide_AddDefaultPrivacyPolicy(t *testing.T) {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore
} }
type args struct { type args struct {
ctx context.Context ctx context.Context
tosLink string tosLink string
privacyLink string privacyLink string
helpLink string helpLink string
supportEmail domain.EmailAddress supportEmail domain.EmailAddress
docsLink string
customLink string
customLinkText string
} }
type res struct { type res struct {
want *domain.ObjectDetails want *domain.ObjectDetails
@ -49,17 +52,23 @@ func TestCommandSide_AddDefaultPrivacyPolicy(t *testing.T) {
"PrivacyLink", "PrivacyLink",
"HelpLink", "HelpLink",
"support@example.com", "support@example.com",
"DocsLink",
"CustomLink",
"Custom",
), ),
), ),
), ),
), ),
}, },
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
tosLink: "TOSLink", tosLink: "TOSLink",
privacyLink: "PrivacyLink", privacyLink: "PrivacyLink",
helpLink: "HelpLink", helpLink: "HelpLink",
supportEmail: "support@example.com", supportEmail: "support@example.com",
docsLink: "DocsLink",
customLink: "CustomLink",
customLinkText: "Custom",
}, },
res: res{ res: res{
err: zerrors.IsErrorAlreadyExists, err: zerrors.IsErrorAlreadyExists,
@ -78,16 +87,22 @@ func TestCommandSide_AddDefaultPrivacyPolicy(t *testing.T) {
"PrivacyLink", "PrivacyLink",
"HelpLink", "HelpLink",
"support@example.com", "support@example.com",
"DocsLink",
"CustomLink",
"Custom",
), ),
), ),
), ),
}, },
args: args{ args: args{
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
tosLink: "TOSLink", tosLink: "TOSLink",
privacyLink: "PrivacyLink", privacyLink: "PrivacyLink",
helpLink: "HelpLink", helpLink: "HelpLink",
supportEmail: "support@example.com", supportEmail: "support@example.com",
docsLink: "DocsLink",
customLink: "CustomLink",
customLinkText: "Custom",
}, },
res: res{ res: res{
want: &domain.ObjectDetails{ want: &domain.ObjectDetails{
@ -103,11 +118,14 @@ func TestCommandSide_AddDefaultPrivacyPolicy(t *testing.T) {
), ),
}, },
args: args{ args: args{
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
tosLink: "TOSLink", tosLink: "TOSLink",
privacyLink: "PrivacyLink", privacyLink: "PrivacyLink",
helpLink: "HelpLink", helpLink: "HelpLink",
supportEmail: "wrong email", supportEmail: "wrong email",
docsLink: "DocsLink",
customLink: "CustomLink",
customLinkText: "Custom",
}, },
res: res{ res: res{
err: zerrors.IsErrorInvalidArgument, err: zerrors.IsErrorInvalidArgument,
@ -126,6 +144,9 @@ func TestCommandSide_AddDefaultPrivacyPolicy(t *testing.T) {
"", "",
"", "",
"", "",
"",
"",
"",
), ),
), ),
), ),
@ -149,7 +170,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, tt.args.supportEmail) got, err := r.AddDefaultPrivacyPolicy(tt.args.ctx, tt.args.tosLink, tt.args.privacyLink, tt.args.helpLink, tt.args.supportEmail, tt.args.docsLink, tt.args.customLink, tt.args.customLinkText)
if tt.res.err == nil { if tt.res.err == nil {
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -192,10 +213,13 @@ func TestCommandSide_ChangeDefaultPrivacyPolicy(t *testing.T) {
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
policy: &domain.PrivacyPolicy{ policy: &domain.PrivacyPolicy{
TOSLink: "TOSLink", TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink", PrivacyLink: "PrivacyLink",
HelpLink: "HelpLink", HelpLink: "HelpLink",
SupportEmail: "support@example.com", SupportEmail: "support@example.com",
DocsLink: "DocsLink",
CustomLink: "CustomLink",
CustomLinkText: "CustomLinkText",
}, },
}, },
res: res{ res: res{
@ -215,6 +239,9 @@ func TestCommandSide_ChangeDefaultPrivacyPolicy(t *testing.T) {
"PrivacyLink", "PrivacyLink",
"HelpLink", "HelpLink",
"support@example.com", "support@example.com",
"DocsLink",
"CustomLink",
"CustomLinkText",
), ),
), ),
), ),
@ -223,10 +250,13 @@ func TestCommandSide_ChangeDefaultPrivacyPolicy(t *testing.T) {
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
policy: &domain.PrivacyPolicy{ policy: &domain.PrivacyPolicy{
TOSLink: "TOSLink", TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink", PrivacyLink: "PrivacyLink",
HelpLink: "HelpLink", HelpLink: "HelpLink",
SupportEmail: "support@example.com", SupportEmail: "support@example.com",
DocsLink: "DocsLink",
CustomLink: "CustomLink",
CustomLinkText: "CustomLinkText",
}, },
}, },
res: res{ res: res{
@ -243,10 +273,13 @@ func TestCommandSide_ChangeDefaultPrivacyPolicy(t *testing.T) {
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
policy: &domain.PrivacyPolicy{ policy: &domain.PrivacyPolicy{
TOSLink: "TOSLink", TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink", PrivacyLink: "PrivacyLink",
HelpLink: "HelpLink", HelpLink: "HelpLink",
SupportEmail: "wrong email", SupportEmail: "wrong email",
DocsLink: "DocsLink",
CustomLink: "CustomLink",
CustomLinkText: "CustomLinkText",
}, },
}, },
res: res{ res: res{
@ -266,6 +299,9 @@ func TestCommandSide_ChangeDefaultPrivacyPolicy(t *testing.T) {
"PrivacyLink", "PrivacyLink",
"HelpLink", "HelpLink",
"support@example.com", "support@example.com",
"DocsLink",
"CustomLink",
"CustomLinkText",
), ),
), ),
), ),
@ -275,6 +311,9 @@ func TestCommandSide_ChangeDefaultPrivacyPolicy(t *testing.T) {
"PrivacyLinkChanged", "PrivacyLinkChanged",
"HelpLinkChanged", "HelpLinkChanged",
"support2@example.com", "support2@example.com",
"DocsLinkChanged",
"CustomLinkChanged",
"CustomLinkTextChanged",
), ),
), ),
), ),
@ -282,10 +321,13 @@ func TestCommandSide_ChangeDefaultPrivacyPolicy(t *testing.T) {
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
policy: &domain.PrivacyPolicy{ policy: &domain.PrivacyPolicy{
TOSLink: "TOSLinkChanged", TOSLink: "TOSLinkChanged",
PrivacyLink: "PrivacyLinkChanged", PrivacyLink: "PrivacyLinkChanged",
HelpLink: "HelpLinkChanged", HelpLink: "HelpLinkChanged",
SupportEmail: "support2@example.com", SupportEmail: "support2@example.com",
DocsLink: "DocsLinkChanged",
CustomLink: "CustomLinkChanged",
CustomLinkText: "CustomLinkTextChanged",
}, },
}, },
res: res{ res: res{
@ -295,10 +337,13 @@ func TestCommandSide_ChangeDefaultPrivacyPolicy(t *testing.T) {
ResourceOwner: "INSTANCE", ResourceOwner: "INSTANCE",
InstanceID: "INSTANCE", InstanceID: "INSTANCE",
}, },
TOSLink: "TOSLinkChanged", TOSLink: "TOSLinkChanged",
PrivacyLink: "PrivacyLinkChanged", PrivacyLink: "PrivacyLinkChanged",
HelpLink: "HelpLinkChanged", HelpLink: "HelpLinkChanged",
SupportEmail: "support2@example.com", SupportEmail: "support2@example.com",
DocsLink: "DocsLinkChanged",
CustomLink: "CustomLinkChanged",
CustomLinkText: "CustomLinkTextChanged",
}, },
}, },
}, },
@ -322,7 +367,7 @@ func TestCommandSide_ChangeDefaultPrivacyPolicy(t *testing.T) {
} }
} }
func newDefaultPrivacyPolicyChangedEvent(ctx context.Context, tosLink, privacyLink, helpLink, supportEmail string) *instance.PrivacyPolicyChangedEvent { func newDefaultPrivacyPolicyChangedEvent(ctx context.Context, tosLink, privacyLink, helpLink, supportEmail, docsLink, customLink, customLinkText string) *instance.PrivacyPolicyChangedEvent {
event, _ := instance.NewPrivacyPolicyChangedEvent(ctx, event, _ := instance.NewPrivacyPolicyChangedEvent(ctx,
&instance.NewAggregate("INSTANCE").Aggregate, &instance.NewAggregate("INSTANCE").Aggregate,
[]policy.PrivacyPolicyChanges{ []policy.PrivacyPolicyChanges{
@ -330,6 +375,9 @@ func newDefaultPrivacyPolicyChangedEvent(ctx context.Context, tosLink, privacyLi
policy.ChangePrivacyLink(privacyLink), policy.ChangePrivacyLink(privacyLink),
policy.ChangeHelpLink(helpLink), policy.ChangeHelpLink(helpLink),
policy.ChangeSupportEmail(domain.EmailAddress(supportEmail)), policy.ChangeSupportEmail(domain.EmailAddress(supportEmail)),
policy.ChangeDocsLink(docsLink),
policy.ChangeCustomLink(customLink),
policy.ChangeCustomLinkText(customLinkText),
}, },
) )
return event return event

View File

@ -46,10 +46,13 @@ func orgDomainWriteModelToOrgDomain(wm *OrgDomainWriteModel) *domain.OrgDomain {
func orgWriteModelToPrivacyPolicy(wm *OrgPrivacyPolicyWriteModel) *domain.PrivacyPolicy { func orgWriteModelToPrivacyPolicy(wm *OrgPrivacyPolicyWriteModel) *domain.PrivacyPolicy {
return &domain.PrivacyPolicy{ return &domain.PrivacyPolicy{
ObjectRoot: writeModelToObjectRoot(wm.PrivacyPolicyWriteModel.WriteModel), ObjectRoot: writeModelToObjectRoot(wm.PrivacyPolicyWriteModel.WriteModel),
TOSLink: wm.TOSLink, TOSLink: wm.TOSLink,
PrivacyLink: wm.PrivacyLink, PrivacyLink: wm.PrivacyLink,
HelpLink: wm.HelpLink, HelpLink: wm.HelpLink,
SupportEmail: wm.SupportEmail, SupportEmail: wm.SupportEmail,
DocsLink: wm.DocsLink,
CustomLink: wm.CustomLink,
CustomLinkText: wm.CustomLinkText,
} }
} }

View File

@ -58,7 +58,10 @@ func (c *Commands) AddPrivacyPolicy(ctx context.Context, resourceOwner string, p
policy.TOSLink, policy.TOSLink,
policy.PrivacyLink, policy.PrivacyLink,
policy.HelpLink, policy.HelpLink,
policy.SupportEmail)) policy.SupportEmail,
policy.DocsLink,
policy.CustomLink,
policy.CustomLinkText))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -91,7 +94,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, policy.SupportEmail) changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, orgAgg, policy.TOSLink, policy.PrivacyLink, policy.HelpLink, policy.SupportEmail, policy.DocsLink, policy.CustomLink, policy.CustomLinkText)
if !hasChanged { if !hasChanged {
return nil, zerrors.ThrowPreconditionFailed(nil, "Org-4N9fs", "Errors.Org.PrivacyPolicy.NotChanged") return nil, zerrors.ThrowPreconditionFailed(nil, "Org-4N9fs", "Errors.Org.PrivacyPolicy.NotChanged")
} }

View File

@ -60,6 +60,7 @@ func (wm *OrgPrivacyPolicyWriteModel) NewChangedEvent(
privacyLink, privacyLink,
helpLink string, helpLink string,
supportEmail domain.EmailAddress, supportEmail domain.EmailAddress,
docsLink, customLink, customLinkText string,
) (*org.PrivacyPolicyChangedEvent, bool) { ) (*org.PrivacyPolicyChangedEvent, bool) {
changes := make([]policy.PrivacyPolicyChanges, 0) changes := make([]policy.PrivacyPolicyChanges, 0)
@ -75,6 +76,15 @@ func (wm *OrgPrivacyPolicyWriteModel) NewChangedEvent(
if wm.SupportEmail != supportEmail { if wm.SupportEmail != supportEmail {
changes = append(changes, policy.ChangeSupportEmail(supportEmail)) changes = append(changes, policy.ChangeSupportEmail(supportEmail))
} }
if wm.DocsLink != docsLink {
changes = append(changes, policy.ChangeDocsLink(docsLink))
}
if wm.CustomLink != customLink {
changes = append(changes, policy.ChangeCustomLink(customLink))
}
if wm.CustomLinkText != customLinkText {
changes = append(changes, policy.ChangeCustomLinkText(customLinkText))
}
if len(changes) == 0 { if len(changes) == 0 {
return nil, false return nil, false
} }

View File

@ -43,10 +43,13 @@ func TestCommandSide_AddPrivacyPolicy(t *testing.T) {
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
policy: &domain.PrivacyPolicy{ policy: &domain.PrivacyPolicy{
TOSLink: "TOSLink", TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink", PrivacyLink: "PrivacyLink",
HelpLink: "HelpLink", HelpLink: "HelpLink",
SupportEmail: "support@example.com", SupportEmail: "support@example.com",
DocsLink: "DocsLink",
CustomLink: "CustomLink",
CustomLinkText: "CustomLinkText",
}, },
}, },
res: res{ res: res{
@ -66,7 +69,9 @@ func TestCommandSide_AddPrivacyPolicy(t *testing.T) {
"PrivacyLink", "PrivacyLink",
"HelpLink", "HelpLink",
"support@example.com", "support@example.com",
), "DocsLink",
"CustomLink",
"CustomLinkText"),
), ),
), ),
), ),
@ -75,10 +80,13 @@ func TestCommandSide_AddPrivacyPolicy(t *testing.T) {
ctx: context.Background(), ctx: context.Background(),
orgID: "org1", orgID: "org1",
policy: &domain.PrivacyPolicy{ policy: &domain.PrivacyPolicy{
TOSLink: "TOSLink", TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink", PrivacyLink: "PrivacyLink",
HelpLink: "HelpLink", HelpLink: "HelpLink",
SupportEmail: "support@example.com", SupportEmail: "support@example.com",
DocsLink: "DocsLink",
CustomLink: "CustomLink",
CustomLinkText: "CustomLinkText",
}, },
}, },
res: res{ res: res{
@ -98,6 +106,9 @@ func TestCommandSide_AddPrivacyPolicy(t *testing.T) {
"PrivacyLink", "PrivacyLink",
"HelpLink", "HelpLink",
"support@example.com", "support@example.com",
"DocsLink",
"CustomLink",
"CustomLinkText",
), ),
), ),
), ),
@ -106,10 +117,13 @@ func TestCommandSide_AddPrivacyPolicy(t *testing.T) {
ctx: context.Background(), ctx: context.Background(),
orgID: "org1", orgID: "org1",
policy: &domain.PrivacyPolicy{ policy: &domain.PrivacyPolicy{
TOSLink: "TOSLink", TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink", PrivacyLink: "PrivacyLink",
HelpLink: "HelpLink", HelpLink: "HelpLink",
SupportEmail: "support@example.com", SupportEmail: "support@example.com",
DocsLink: "DocsLink",
CustomLink: "CustomLink",
CustomLinkText: "CustomLinkText",
}, },
}, },
res: res{ res: res{
@ -118,10 +132,13 @@ func TestCommandSide_AddPrivacyPolicy(t *testing.T) {
AggregateID: "org1", AggregateID: "org1",
ResourceOwner: "org1", ResourceOwner: "org1",
}, },
TOSLink: "TOSLink", TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink", PrivacyLink: "PrivacyLink",
HelpLink: "HelpLink", HelpLink: "HelpLink",
SupportEmail: "support@example.com", SupportEmail: "support@example.com",
DocsLink: "DocsLink",
CustomLink: "CustomLink",
CustomLinkText: "CustomLinkText",
}, },
}, },
}, },
@ -136,10 +153,13 @@ func TestCommandSide_AddPrivacyPolicy(t *testing.T) {
ctx: context.Background(), ctx: context.Background(),
orgID: "org1", orgID: "org1",
policy: &domain.PrivacyPolicy{ policy: &domain.PrivacyPolicy{
TOSLink: "TOSLink", TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink", PrivacyLink: "PrivacyLink",
HelpLink: "HelpLink", HelpLink: "HelpLink",
SupportEmail: "wrong email", SupportEmail: "wrong email",
DocsLink: "DocsLink",
CustomLink: "CustomLink",
CustomLinkText: "CustomLinkText",
}, },
}, },
res: res{ res: res{
@ -159,6 +179,9 @@ func TestCommandSide_AddPrivacyPolicy(t *testing.T) {
"", "",
"", "",
"", "",
"",
"",
"",
), ),
), ),
), ),
@ -167,10 +190,13 @@ func TestCommandSide_AddPrivacyPolicy(t *testing.T) {
ctx: context.Background(), ctx: context.Background(),
orgID: "org1", orgID: "org1",
policy: &domain.PrivacyPolicy{ policy: &domain.PrivacyPolicy{
TOSLink: "", TOSLink: "",
PrivacyLink: "", PrivacyLink: "",
HelpLink: "", HelpLink: "",
SupportEmail: "", SupportEmail: "",
DocsLink: "",
CustomLink: "",
CustomLinkText: "",
}, },
}, },
res: res{ res: res{
@ -179,10 +205,13 @@ func TestCommandSide_AddPrivacyPolicy(t *testing.T) {
AggregateID: "org1", AggregateID: "org1",
ResourceOwner: "org1", ResourceOwner: "org1",
}, },
TOSLink: "", TOSLink: "",
PrivacyLink: "", PrivacyLink: "",
HelpLink: "", HelpLink: "",
SupportEmail: "", SupportEmail: "",
DocsLink: "",
CustomLink: "",
CustomLinkText: "",
}, },
}, },
}, },
@ -235,10 +264,13 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) {
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
policy: &domain.PrivacyPolicy{ policy: &domain.PrivacyPolicy{
TOSLink: "TOSLink", TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink", PrivacyLink: "PrivacyLink",
HelpLink: "HelpLink", HelpLink: "HelpLink",
SupportEmail: "support@example.com", SupportEmail: "support@example.com",
DocsLink: "DocsLink",
CustomLink: "CustomLink",
CustomLinkText: "CustomLinkText",
}, },
}, },
res: res{ res: res{
@ -257,10 +289,13 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) {
ctx: context.Background(), ctx: context.Background(),
orgID: "org1", orgID: "org1",
policy: &domain.PrivacyPolicy{ policy: &domain.PrivacyPolicy{
TOSLink: "TOSLink", TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink", PrivacyLink: "PrivacyLink",
HelpLink: "HelpLink", HelpLink: "HelpLink",
SupportEmail: "support@example.com", SupportEmail: "support@example.com",
DocsLink: "DocsLink",
CustomLink: "CustomLink",
CustomLinkText: "CustomLinkText",
}, },
}, },
res: res{ res: res{
@ -280,6 +315,9 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) {
"PrivacyLink", "PrivacyLink",
"HelpLink", "HelpLink",
"support@example.com", "support@example.com",
"DocsLink",
"CustomLink",
"CustomLinkText",
), ),
), ),
), ),
@ -289,10 +327,13 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) {
ctx: context.Background(), ctx: context.Background(),
orgID: "org1", orgID: "org1",
policy: &domain.PrivacyPolicy{ policy: &domain.PrivacyPolicy{
TOSLink: "TOSLink", TOSLink: "TOSLink",
PrivacyLink: "PrivacyLink", PrivacyLink: "PrivacyLink",
HelpLink: "HelpLink", HelpLink: "HelpLink",
SupportEmail: "support@example.com", SupportEmail: "support@example.com",
DocsLink: "DocsLink",
CustomLink: "CustomLink",
CustomLinkText: "CustomLinkText",
}, },
}, },
res: res{ res: res{
@ -305,10 +346,13 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) {
ctx: context.Background(), ctx: context.Background(),
orgID: "org1", orgID: "org1",
policy: &domain.PrivacyPolicy{ policy: &domain.PrivacyPolicy{
TOSLink: "TOSLinkChange", TOSLink: "TOSLinkChange",
PrivacyLink: "PrivacyLinkChange", PrivacyLink: "PrivacyLinkChange",
HelpLink: "HelpLinkChange", HelpLink: "HelpLinkChange",
SupportEmail: "wrong email", SupportEmail: "wrong email",
DocsLink: "DocsLink",
CustomLink: "CustomLink",
CustomLinkText: "CustomLinkText",
}, },
}, },
res: res{ res: res{
@ -328,11 +372,14 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) {
"PrivacyLink", "PrivacyLink",
"HelpLink", "HelpLink",
"support@example.com", "support@example.com",
"DocsLink",
"CustomLink",
"CustomLinkText",
), ),
), ),
), ),
expectPush( expectPush(
newPrivacyPolicyChangedEvent(context.Background(), "org1", "TOSLinkChange", "PrivacyLinkChange", "HelpLinkChange", "support2@example.com"), newPrivacyPolicyChangedEvent(context.Background(), "org1", "TOSLinkChange", "PrivacyLinkChange", "HelpLinkChange", "support2@example.com", "DocsLinkChange", "CustomLinkChange", "CustomLinkTextChange"),
), ),
), ),
}, },
@ -340,10 +387,13 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) {
ctx: context.Background(), ctx: context.Background(),
orgID: "org1", orgID: "org1",
policy: &domain.PrivacyPolicy{ policy: &domain.PrivacyPolicy{
TOSLink: "TOSLinkChange", TOSLink: "TOSLinkChange",
PrivacyLink: "PrivacyLinkChange", PrivacyLink: "PrivacyLinkChange",
HelpLink: "HelpLinkChange", HelpLink: "HelpLinkChange",
SupportEmail: "support2@example.com", SupportEmail: "support2@example.com",
DocsLink: "DocsLinkChange",
CustomLink: "CustomLinkChange",
CustomLinkText: "CustomLinkTextChange",
}, },
}, },
res: res{ res: res{
@ -352,10 +402,13 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) {
AggregateID: "org1", AggregateID: "org1",
ResourceOwner: "org1", ResourceOwner: "org1",
}, },
TOSLink: "TOSLinkChange", TOSLink: "TOSLinkChange",
PrivacyLink: "PrivacyLinkChange", PrivacyLink: "PrivacyLinkChange",
HelpLink: "HelpLinkChange", HelpLink: "HelpLinkChange",
SupportEmail: "support2@example.com", SupportEmail: "support2@example.com",
DocsLink: "DocsLinkChange",
CustomLink: "CustomLinkChange",
CustomLinkText: "CustomLinkTextChange",
}, },
}, },
}, },
@ -372,11 +425,14 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) {
"PrivacyLink", "PrivacyLink",
"HelpLink", "HelpLink",
"support@example.com", "support@example.com",
"DocsLink",
"CustomLink",
"CustomLinkText",
), ),
), ),
), ),
expectPush( expectPush(
newPrivacyPolicyChangedEvent(context.Background(), "org1", "", "", "", ""), newPrivacyPolicyChangedEvent(context.Background(), "org1", "", "", "", "", "", "", ""),
), ),
), ),
}, },
@ -384,10 +440,13 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) {
ctx: context.Background(), ctx: context.Background(),
orgID: "org1", orgID: "org1",
policy: &domain.PrivacyPolicy{ policy: &domain.PrivacyPolicy{
TOSLink: "", TOSLink: "",
PrivacyLink: "", PrivacyLink: "",
HelpLink: "", HelpLink: "",
SupportEmail: "", SupportEmail: "",
DocsLink: "",
CustomLink: "",
CustomLinkText: "",
}, },
}, },
res: res{ res: res{
@ -396,10 +455,13 @@ func TestCommandSide_ChangePrivacyPolicy(t *testing.T) {
AggregateID: "org1", AggregateID: "org1",
ResourceOwner: "org1", ResourceOwner: "org1",
}, },
TOSLink: "", TOSLink: "",
PrivacyLink: "", PrivacyLink: "",
HelpLink: "", HelpLink: "",
SupportEmail: "", SupportEmail: "",
DocsLink: "",
CustomLink: "",
CustomLinkText: "",
}, },
}, },
}, },
@ -484,6 +546,9 @@ func TestCommandSide_RemovePrivacyPolicy(t *testing.T) {
"PrivacyLink", "PrivacyLink",
"HelpLink", "HelpLink",
"support@example.com", "support@example.com",
"DocsLink",
"CustomLink",
"CustomLinkText",
), ),
), ),
), ),
@ -523,7 +588,7 @@ func TestCommandSide_RemovePrivacyPolicy(t *testing.T) {
} }
} }
func newPrivacyPolicyChangedEvent(ctx context.Context, orgID string, tosLink, privacyLink, helpLink, supportEmail string) *org.PrivacyPolicyChangedEvent { func newPrivacyPolicyChangedEvent(ctx context.Context, orgID string, tosLink, privacyLink, helpLink, supportEmail, docsLink, customLink, customLinkText string) *org.PrivacyPolicyChangedEvent {
event, _ := org.NewPrivacyPolicyChangedEvent(ctx, event, _ := org.NewPrivacyPolicyChangedEvent(ctx,
&org.NewAggregate(orgID).Aggregate, &org.NewAggregate(orgID).Aggregate,
[]policy.PrivacyPolicyChanges{ []policy.PrivacyPolicyChanges{
@ -531,6 +596,9 @@ func newPrivacyPolicyChangedEvent(ctx context.Context, orgID string, tosLink, pr
policy.ChangePrivacyLink(privacyLink), policy.ChangePrivacyLink(privacyLink),
policy.ChangeHelpLink(helpLink), policy.ChangeHelpLink(helpLink),
policy.ChangeSupportEmail(domain.EmailAddress(supportEmail)), policy.ChangeSupportEmail(domain.EmailAddress(supportEmail)),
policy.ChangeDocsLink(docsLink),
policy.ChangeCustomLink(customLink),
policy.ChangeCustomLinkText(customLinkText),
}, },
) )
return event return event

View File

@ -9,11 +9,14 @@ import (
type PrivacyPolicyWriteModel struct { type PrivacyPolicyWriteModel struct {
eventstore.WriteModel eventstore.WriteModel
TOSLink string TOSLink string
PrivacyLink string PrivacyLink string
HelpLink string HelpLink string
SupportEmail domain.EmailAddress SupportEmail domain.EmailAddress
State domain.PolicyState State domain.PolicyState
DocsLink string
CustomLink string
CustomLinkText string
} }
func (wm *PrivacyPolicyWriteModel) Reduce() error { func (wm *PrivacyPolicyWriteModel) Reduce() error {
@ -25,6 +28,9 @@ func (wm *PrivacyPolicyWriteModel) Reduce() error {
wm.HelpLink = e.HelpLink wm.HelpLink = e.HelpLink
wm.SupportEmail = e.SupportEmail wm.SupportEmail = e.SupportEmail
wm.State = domain.PolicyStateActive wm.State = domain.PolicyStateActive
wm.DocsLink = e.DocsLink
wm.CustomLink = e.CustomLink
wm.CustomLinkText = e.CustomLinkText
case *policy.PrivacyPolicyChangedEvent: case *policy.PrivacyPolicyChangedEvent:
if e.PrivacyLink != nil { if e.PrivacyLink != nil {
wm.PrivacyLink = *e.PrivacyLink wm.PrivacyLink = *e.PrivacyLink
@ -38,6 +44,15 @@ func (wm *PrivacyPolicyWriteModel) Reduce() error {
if e.SupportEmail != nil { if e.SupportEmail != nil {
wm.SupportEmail = *e.SupportEmail wm.SupportEmail = *e.SupportEmail
} }
if e.DocsLink != nil {
wm.DocsLink = *e.DocsLink
}
if e.CustomLink != nil {
wm.CustomLink = *e.CustomLink
}
if e.CustomLinkText != nil {
wm.CustomLinkText = *e.CustomLinkText
}
case *policy.PrivacyPolicyRemovedEvent: case *policy.PrivacyPolicyRemovedEvent:
wm.State = domain.PolicyStateRemoved wm.State = domain.PolicyStateRemoved
} }

View File

@ -10,8 +10,11 @@ type PrivacyPolicy struct {
State PolicyState State PolicyState
Default bool Default bool
TOSLink string TOSLink string
PrivacyLink string PrivacyLink string
HelpLink string HelpLink string
SupportEmail EmailAddress SupportEmail EmailAddress
DocsLink string
CustomLink string
CustomLinkText string
} }

View File

@ -26,10 +26,13 @@ type PrivacyPolicy struct {
ResourceOwner string ResourceOwner string
State domain.PolicyState State domain.PolicyState
TOSLink string TOSLink string
PrivacyLink string PrivacyLink string
HelpLink string HelpLink string
SupportEmail domain.EmailAddress SupportEmail domain.EmailAddress
DocsLink string
CustomLink string
CustomLinkText string
IsDefault bool IsDefault bool
} }
@ -91,6 +94,18 @@ var (
name: projection.PrivacyPolicyOwnerRemovedCol, name: projection.PrivacyPolicyOwnerRemovedCol,
table: privacyTable, table: privacyTable,
} }
PrivacyColDocsLink = Column{
name: projection.PrivacyPolicyDocsLinkCol,
table: privacyTable,
}
PrivacyColCustomLink = Column{
name: projection.PrivacyPolicyCustomLinkCol,
table: privacyTable,
}
PrivacyColCustomLinkText = Column{
name: projection.PrivacyPolicyCustomLinkTextCol,
table: privacyTable,
}
) )
func (q *Queries) PrivacyPolicyByOrg(ctx context.Context, shouldTriggerBulk bool, orgID string, withOwnerRemoved bool) (policy *PrivacyPolicy, err error) { func (q *Queries) PrivacyPolicyByOrg(ctx context.Context, shouldTriggerBulk bool, orgID string, withOwnerRemoved bool) (policy *PrivacyPolicy, err error) {
@ -168,6 +183,9 @@ func preparePrivacyPolicyQuery(ctx context.Context, db prepareDatabase) (sq.Sele
PrivacyColTOSLink.identifier(), PrivacyColTOSLink.identifier(),
PrivacyColHelpLink.identifier(), PrivacyColHelpLink.identifier(),
PrivacyColSupportEmail.identifier(), PrivacyColSupportEmail.identifier(),
PrivacyColDocsLink.identifier(),
PrivacyColCustomLink.identifier(),
PrivacyColCustomLinkText.identifier(),
PrivacyColIsDefault.identifier(), PrivacyColIsDefault.identifier(),
PrivacyColState.identifier(), PrivacyColState.identifier(),
). ).
@ -185,6 +203,9 @@ func preparePrivacyPolicyQuery(ctx context.Context, db prepareDatabase) (sq.Sele
&policy.TOSLink, &policy.TOSLink,
&policy.HelpLink, &policy.HelpLink,
&policy.SupportEmail, &policy.SupportEmail,
&policy.DocsLink,
&policy.CustomLink,
&policy.CustomLinkText,
&policy.IsDefault, &policy.IsDefault,
&policy.State, &policy.State,
) )
@ -200,10 +221,13 @@ func preparePrivacyPolicyQuery(ctx context.Context, db prepareDatabase) (sq.Sele
func (p *PrivacyPolicy) ToDomain() *domain.PrivacyPolicy { func (p *PrivacyPolicy) ToDomain() *domain.PrivacyPolicy {
return &domain.PrivacyPolicy{ return &domain.PrivacyPolicy{
TOSLink: p.TOSLink, TOSLink: p.TOSLink,
PrivacyLink: p.PrivacyLink, PrivacyLink: p.PrivacyLink,
HelpLink: p.HelpLink, HelpLink: p.HelpLink,
SupportEmail: p.SupportEmail, SupportEmail: p.SupportEmail,
Default: p.IsDefault, Default: p.IsDefault,
DocsLink: p.DocsLink,
CustomLink: p.CustomLink,
CustomLinkText: p.CustomLinkText,
} }
} }

View File

@ -13,18 +13,21 @@ import (
) )
var ( var (
preparePrivacyPolicyStmt = `SELECT projections.privacy_policies3.id,` + preparePrivacyPolicyStmt = `SELECT projections.privacy_policies4.id,` +
` projections.privacy_policies3.sequence,` + ` projections.privacy_policies4.sequence,` +
` projections.privacy_policies3.creation_date,` + ` projections.privacy_policies4.creation_date,` +
` projections.privacy_policies3.change_date,` + ` projections.privacy_policies4.change_date,` +
` projections.privacy_policies3.resource_owner,` + ` projections.privacy_policies4.resource_owner,` +
` projections.privacy_policies3.privacy_link,` + ` projections.privacy_policies4.privacy_link,` +
` projections.privacy_policies3.tos_link,` + ` projections.privacy_policies4.tos_link,` +
` projections.privacy_policies3.help_link,` + ` projections.privacy_policies4.help_link,` +
` projections.privacy_policies3.support_email,` + ` projections.privacy_policies4.support_email,` +
` projections.privacy_policies3.is_default,` + ` projections.privacy_policies4.docs_link,` +
` projections.privacy_policies3.state` + ` projections.privacy_policies4.custom_link,` +
` FROM projections.privacy_policies3` + ` projections.privacy_policies4.custom_link_text,` +
` projections.privacy_policies4.is_default,` +
` projections.privacy_policies4.state` +
` FROM projections.privacy_policies4` +
` AS OF SYSTEM TIME '-1 ms'` ` AS OF SYSTEM TIME '-1 ms'`
preparePrivacyPolicyCols = []string{ preparePrivacyPolicyCols = []string{
"id", "id",
@ -36,6 +39,9 @@ var (
"tos_link", "tos_link",
"help_link", "help_link",
"support_email", "support_email",
"docs_link",
"custom_link",
"custom_link_text",
"is_default", "is_default",
"state", "state",
} }
@ -87,23 +93,29 @@ func Test_PrivacyPolicyPrepares(t *testing.T) {
"tos.ch", "tos.ch",
"help.ch", "help.ch",
"support@example.com", "support@example.com",
"zitadel.com/docs",
"zitadel.com",
"Zitadel",
true, true,
domain.PolicyStateActive, domain.PolicyStateActive,
}, },
), ),
}, },
object: &PrivacyPolicy{ object: &PrivacyPolicy{
ID: "pol-id", ID: "pol-id",
CreationDate: testNow, CreationDate: testNow,
ChangeDate: testNow, ChangeDate: testNow,
Sequence: 20211109, Sequence: 20211109,
ResourceOwner: "ro", ResourceOwner: "ro",
State: domain.PolicyStateActive, State: domain.PolicyStateActive,
PrivacyLink: "privacy.ch", PrivacyLink: "privacy.ch",
TOSLink: "tos.ch", TOSLink: "tos.ch",
HelpLink: "help.ch", HelpLink: "help.ch",
SupportEmail: "support@example.com", SupportEmail: "support@example.com",
IsDefault: true, DocsLink: "zitadel.com/docs",
CustomLink: "zitadel.com",
CustomLinkText: "Zitadel",
IsDefault: true,
}, },
}, },
{ {

View File

@ -14,21 +14,24 @@ import (
) )
const ( const (
PrivacyPolicyTable = "projections.privacy_policies3" PrivacyPolicyTable = "projections.privacy_policies4"
PrivacyPolicyIDCol = "id" PrivacyPolicyIDCol = "id"
PrivacyPolicyCreationDateCol = "creation_date" PrivacyPolicyCreationDateCol = "creation_date"
PrivacyPolicyChangeDateCol = "change_date" PrivacyPolicyChangeDateCol = "change_date"
PrivacyPolicySequenceCol = "sequence" PrivacyPolicySequenceCol = "sequence"
PrivacyPolicyStateCol = "state" PrivacyPolicyStateCol = "state"
PrivacyPolicyIsDefaultCol = "is_default" PrivacyPolicyIsDefaultCol = "is_default"
PrivacyPolicyResourceOwnerCol = "resource_owner" PrivacyPolicyResourceOwnerCol = "resource_owner"
PrivacyPolicyInstanceIDCol = "instance_id" PrivacyPolicyInstanceIDCol = "instance_id"
PrivacyPolicyPrivacyLinkCol = "privacy_link" PrivacyPolicyPrivacyLinkCol = "privacy_link"
PrivacyPolicyTOSLinkCol = "tos_link" PrivacyPolicyTOSLinkCol = "tos_link"
PrivacyPolicyHelpLinkCol = "help_link" PrivacyPolicyHelpLinkCol = "help_link"
PrivacyPolicySupportEmailCol = "support_email" PrivacyPolicySupportEmailCol = "support_email"
PrivacyPolicyOwnerRemovedCol = "owner_removed" PrivacyPolicyDocsLinkCol = "docs_link"
PrivacyPolicyCustomLinkCol = "custom_link"
PrivacyPolicyCustomLinkTextCol = "custom_link_text"
PrivacyPolicyOwnerRemovedCol = "owner_removed"
) )
type privacyPolicyProjection struct{} type privacyPolicyProjection struct{}
@ -56,6 +59,9 @@ func (*privacyPolicyProjection) Init() *old_handler.Check {
handler.NewColumn(PrivacyPolicyTOSLinkCol, handler.ColumnTypeText), handler.NewColumn(PrivacyPolicyTOSLinkCol, handler.ColumnTypeText),
handler.NewColumn(PrivacyPolicyHelpLinkCol, handler.ColumnTypeText), handler.NewColumn(PrivacyPolicyHelpLinkCol, handler.ColumnTypeText),
handler.NewColumn(PrivacyPolicySupportEmailCol, handler.ColumnTypeText), handler.NewColumn(PrivacyPolicySupportEmailCol, handler.ColumnTypeText),
handler.NewColumn(PrivacyPolicyDocsLinkCol, handler.ColumnTypeText, handler.Default("https://zitadel.com/docs")),
handler.NewColumn(PrivacyPolicyCustomLinkCol, handler.ColumnTypeText),
handler.NewColumn(PrivacyPolicyCustomLinkTextCol, handler.ColumnTypeText),
handler.NewColumn(PrivacyPolicyOwnerRemovedCol, handler.ColumnTypeBool, handler.Default(false)), handler.NewColumn(PrivacyPolicyOwnerRemovedCol, handler.ColumnTypeBool, handler.Default(false)),
}, },
handler.NewPrimaryKey(PrivacyPolicyInstanceIDCol, PrivacyPolicyIDCol), handler.NewPrimaryKey(PrivacyPolicyInstanceIDCol, PrivacyPolicyIDCol),
@ -132,6 +138,9 @@ func (p *privacyPolicyProjection) reduceAdded(event eventstore.Event) (*handler.
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(PrivacyPolicySupportEmailCol, policyEvent.SupportEmail),
handler.NewCol(PrivacyPolicyDocsLinkCol, policyEvent.DocsLink),
handler.NewCol(PrivacyPolicyCustomLinkCol, policyEvent.CustomLink),
handler.NewCol(PrivacyPolicyCustomLinkTextCol, policyEvent.CustomLinkText),
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),
@ -164,6 +173,15 @@ func (p *privacyPolicyProjection) reduceChanged(event eventstore.Event) (*handle
if policyEvent.SupportEmail != nil { if policyEvent.SupportEmail != nil {
cols = append(cols, handler.NewCol(PrivacyPolicySupportEmailCol, *policyEvent.SupportEmail)) cols = append(cols, handler.NewCol(PrivacyPolicySupportEmailCol, *policyEvent.SupportEmail))
} }
if policyEvent.DocsLink != nil {
cols = append(cols, handler.NewCol(PrivacyPolicyDocsLinkCol, *policyEvent.DocsLink))
}
if policyEvent.CustomLink != nil {
cols = append(cols, handler.NewCol(PrivacyPolicyCustomLinkCol, *policyEvent.CustomLink))
}
if policyEvent.CustomLinkText != nil {
cols = append(cols, handler.NewCol(PrivacyPolicyCustomLinkTextCol, *policyEvent.CustomLinkText))
}
return handler.NewUpdateStatement( return handler.NewUpdateStatement(
&policyEvent, &policyEvent,
cols, cols,

View File

@ -32,6 +32,9 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) {
"tosLink": "http://tos.link", "tosLink": "http://tos.link",
"privacyLink": "http://privacy.link", "privacyLink": "http://privacy.link",
"helpLink": "http://help.link", "helpLink": "http://help.link",
"docsLink": "http://docs.link",
"customLink": "http://custom.link",
"customLinkText": "Custom Link",
"supportEmail": "support@example.com"}`), "supportEmail": "support@example.com"}`),
), org.PrivacyPolicyAddedEventMapper), ), org.PrivacyPolicyAddedEventMapper),
}, },
@ -42,7 +45,7 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
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)", expectedStmt: "INSERT INTO projections.privacy_policies4 (creation_date, change_date, sequence, id, state, privacy_link, tos_link, help_link, support_email, docs_link, custom_link, custom_link_text, is_default, resource_owner, instance_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
anyArg{}, anyArg{},
@ -53,6 +56,9 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) {
"http://tos.link", "http://tos.link",
"http://help.link", "http://help.link",
domain.EmailAddress("support@example.com"), domain.EmailAddress("support@example.com"),
"http://docs.link",
"http://custom.link",
"Custom Link",
false, false,
"ro-id", "ro-id",
"instance-id", "instance-id",
@ -74,6 +80,9 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) {
"tosLink": "http://tos.link", "tosLink": "http://tos.link",
"privacyLink": "http://privacy.link", "privacyLink": "http://privacy.link",
"helpLink": "http://help.link", "helpLink": "http://help.link",
"docsLink": "http://docs.link",
"customLink": "http://custom.link",
"customLinkText": "Custom Link",
"supportEmail": "support@example.com"}`), "supportEmail": "support@example.com"}`),
), org.PrivacyPolicyChangedEventMapper), ), org.PrivacyPolicyChangedEventMapper),
}, },
@ -83,7 +92,7 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
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)", expectedStmt: "UPDATE projections.privacy_policies4 SET (change_date, sequence, privacy_link, tos_link, help_link, support_email, docs_link, custom_link, custom_link_text) = ($1, $2, $3, $4, $5, $6, $7, $8, $9) WHERE (id = $10) AND (instance_id = $11)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -91,6 +100,9 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) {
"http://tos.link", "http://tos.link",
"http://help.link", "http://help.link",
domain.EmailAddress("support@example.com"), domain.EmailAddress("support@example.com"),
"http://docs.link",
"http://custom.link",
"Custom Link",
"agg-id", "agg-id",
"instance-id", "instance-id",
}, },
@ -116,7 +128,7 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "DELETE FROM projections.privacy_policies3 WHERE (id = $1) AND (instance_id = $2)", expectedStmt: "DELETE FROM projections.privacy_policies4 WHERE (id = $1) AND (instance_id = $2)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
"instance-id", "instance-id",
@ -142,7 +154,7 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "DELETE FROM projections.privacy_policies3 WHERE (instance_id = $1)", expectedStmt: "DELETE FROM projections.privacy_policies4 WHERE (instance_id = $1)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
}, },
@ -163,6 +175,9 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) {
"tosLink": "http://tos.link", "tosLink": "http://tos.link",
"privacyLink": "http://privacy.link", "privacyLink": "http://privacy.link",
"helpLink": "http://help.link", "helpLink": "http://help.link",
"docsLink": "http://docs.link",
"customLink": "http://custom.link",
"customLinkText": "Custom Link",
"supportEmail": "support@example.com"}`), "supportEmail": "support@example.com"}`),
), instance.PrivacyPolicyAddedEventMapper), ), instance.PrivacyPolicyAddedEventMapper),
}, },
@ -172,7 +187,7 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
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)", expectedStmt: "INSERT INTO projections.privacy_policies4 (creation_date, change_date, sequence, id, state, privacy_link, tos_link, help_link, support_email, docs_link, custom_link, custom_link_text, is_default, resource_owner, instance_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
anyArg{}, anyArg{},
@ -183,6 +198,9 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) {
"http://tos.link", "http://tos.link",
"http://help.link", "http://help.link",
domain.EmailAddress("support@example.com"), domain.EmailAddress("support@example.com"),
"http://docs.link",
"http://custom.link",
"Custom Link",
true, true,
"ro-id", "ro-id",
"instance-id", "instance-id",
@ -204,6 +222,9 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) {
"tosLink": "http://tos.link", "tosLink": "http://tos.link",
"privacyLink": "http://privacy.link", "privacyLink": "http://privacy.link",
"helpLink": "http://help.link", "helpLink": "http://help.link",
"docsLink": "http://docs.link",
"customLink": "http://custom.link",
"customLinkText": "Custom Link",
"supportEmail": "support@example.com"}`), "supportEmail": "support@example.com"}`),
), instance.PrivacyPolicyChangedEventMapper), ), instance.PrivacyPolicyChangedEventMapper),
}, },
@ -213,7 +234,7 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
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)", expectedStmt: "UPDATE projections.privacy_policies4 SET (change_date, sequence, privacy_link, tos_link, help_link, support_email, docs_link, custom_link, custom_link_text) = ($1, $2, $3, $4, $5, $6, $7, $8, $9) WHERE (id = $10) AND (instance_id = $11)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -221,6 +242,9 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) {
"http://tos.link", "http://tos.link",
"http://help.link", "http://help.link",
domain.EmailAddress("support@example.com"), domain.EmailAddress("support@example.com"),
"http://docs.link",
"http://custom.link",
"Custom Link",
"agg-id", "agg-id",
"instance-id", "instance-id",
}, },
@ -246,7 +270,7 @@ func TestPrivacyPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "DELETE FROM projections.privacy_policies3 WHERE (instance_id = $1) AND (resource_owner = $2)", expectedStmt: "DELETE FROM projections.privacy_policies4 WHERE (instance_id = $1) AND (resource_owner = $2)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"instance-id", "instance-id",
"agg-id", "agg-id",

View File

@ -24,6 +24,7 @@ func NewPrivacyPolicyAddedEvent(
privacyLink, privacyLink,
helpLink string, helpLink string,
supportEmail domain.EmailAddress, supportEmail domain.EmailAddress,
docsLink, customLink, customLinkText string,
) *PrivacyPolicyAddedEvent { ) *PrivacyPolicyAddedEvent {
return &PrivacyPolicyAddedEvent{ return &PrivacyPolicyAddedEvent{
PrivacyPolicyAddedEvent: *policy.NewPrivacyPolicyAddedEvent( PrivacyPolicyAddedEvent: *policy.NewPrivacyPolicyAddedEvent(
@ -34,7 +35,10 @@ func NewPrivacyPolicyAddedEvent(
tosLink, tosLink,
privacyLink, privacyLink,
helpLink, helpLink,
supportEmail), supportEmail,
docsLink,
customLink,
customLinkText),
} }
} }

View File

@ -25,6 +25,7 @@ func NewPrivacyPolicyAddedEvent(
privacyLink, privacyLink,
helpLink string, helpLink string,
supportEmail domain.EmailAddress, supportEmail domain.EmailAddress,
docsLink, customLink, customLinkText string,
) *PrivacyPolicyAddedEvent { ) *PrivacyPolicyAddedEvent {
return &PrivacyPolicyAddedEvent{ return &PrivacyPolicyAddedEvent{
PrivacyPolicyAddedEvent: *policy.NewPrivacyPolicyAddedEvent( PrivacyPolicyAddedEvent: *policy.NewPrivacyPolicyAddedEvent(
@ -35,7 +36,10 @@ func NewPrivacyPolicyAddedEvent(
tosLink, tosLink,
privacyLink, privacyLink,
helpLink, helpLink,
supportEmail), supportEmail,
docsLink,
customLink,
customLinkText),
} }
} }

View File

@ -15,10 +15,13 @@ const (
type PrivacyPolicyAddedEvent struct { type PrivacyPolicyAddedEvent struct {
eventstore.BaseEvent `json:"-"` eventstore.BaseEvent `json:"-"`
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"` SupportEmail domain.EmailAddress `json:"supportEmail,omitempty"`
DocsLink string `json:"docsLink,omitempty"`
CustomLink string `json:"customLink,omitempty"`
CustomLinkText string `json:"customLinkText,omitempty"`
} }
func (e *PrivacyPolicyAddedEvent) Payload() interface{} { func (e *PrivacyPolicyAddedEvent) Payload() interface{} {
@ -35,13 +38,17 @@ func NewPrivacyPolicyAddedEvent(
privacyLink, privacyLink,
helpLink string, helpLink string,
supportEmail domain.EmailAddress, supportEmail domain.EmailAddress,
docsLink, customLink, customLinkText string,
) *PrivacyPolicyAddedEvent { ) *PrivacyPolicyAddedEvent {
return &PrivacyPolicyAddedEvent{ return &PrivacyPolicyAddedEvent{
BaseEvent: *base, BaseEvent: *base,
TOSLink: tosLink, TOSLink: tosLink,
PrivacyLink: privacyLink, PrivacyLink: privacyLink,
HelpLink: helpLink, HelpLink: helpLink,
SupportEmail: supportEmail, SupportEmail: supportEmail,
DocsLink: docsLink,
CustomLink: customLink,
CustomLinkText: customLinkText,
} }
} }
@ -60,10 +67,13 @@ func PrivacyPolicyAddedEventMapper(event eventstore.Event) (eventstore.Event, er
type PrivacyPolicyChangedEvent struct { type PrivacyPolicyChangedEvent struct {
eventstore.BaseEvent `json:"-"` eventstore.BaseEvent `json:"-"`
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"` SupportEmail *domain.EmailAddress `json:"supportEmail,omitempty"`
DocsLink *string `json:"docsLink,omitempty"`
CustomLink *string `json:"customLink,omitempty"`
CustomLinkText *string `json:"customLinkText,omitempty"`
} }
func (e *PrivacyPolicyChangedEvent) Payload() interface{} { func (e *PrivacyPolicyChangedEvent) Payload() interface{} {
@ -116,6 +126,24 @@ func ChangeSupportEmail(supportEmail domain.EmailAddress) func(*PrivacyPolicyCha
} }
} }
func ChangeDocsLink(docsLink string) func(*PrivacyPolicyChangedEvent) {
return func(e *PrivacyPolicyChangedEvent) {
e.DocsLink = &docsLink
}
}
func ChangeCustomLink(customLink string) func(*PrivacyPolicyChangedEvent) {
return func(e *PrivacyPolicyChangedEvent) {
e.CustomLink = &customLink
}
}
func ChangeCustomLinkText(customLinkText string) func(*PrivacyPolicyChangedEvent) {
return func(e *PrivacyPolicyChangedEvent) {
e.CustomLinkText = &customLinkText
}
}
func PrivacyPolicyChangedEventMapper(event eventstore.Event) (eventstore.Event, error) { func PrivacyPolicyChangedEventMapper(event eventstore.Event) (eventstore.Event, error) {
e := &PrivacyPolicyChangedEvent{ e := &PrivacyPolicyChangedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event), BaseEvent: *eventstore.BaseEventFromRepo(event),

View File

@ -6817,6 +6817,23 @@ message UpdatePrivacyPolicyRequest {
description: "help / support email address." description: "help / support email address."
} }
]; ];
string docs_link = 5 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"https://zitadel.com/docs\"";
}
];
string custom_link = 6 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Link to an external resource that will be available to users in the console.";
example: "\"https://external.link\"";
}
];
string custom_link_text = 7 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "The button text that would be shown in console pointing to custom link.";
example: "\"External\"";
}
];
} }
message UpdatePrivacyPolicyResponse { message UpdatePrivacyPolicyResponse {

View File

@ -10495,6 +10495,24 @@ message AddCustomPrivacyPolicyRequest {
description: "help / support email address." description: "help / support email address."
} }
]; ];
string docs_link = 5 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Link to documentation to be shown in the console.";
example: "\"https://zitadel.com/docs\"";
}
];
string custom_link = 6 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Link to an external resource that will be available to users in the console.";
example: "\"https://external.link\"";
}
];
string custom_link_text = 7 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "The button text that would be shown in console pointing to custom link.";
example: "\"External\"";
}
];
} }
message AddCustomPrivacyPolicyResponse { message AddCustomPrivacyPolicyResponse {
@ -10527,6 +10545,24 @@ message UpdateCustomPrivacyPolicyRequest {
description: "help / support email address." description: "help / support email address."
} }
]; ];
string docs_link = 5 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Link to documentation to be shown in the console.";
example: "\"https://zitadel.com/docs\"";
}
];
string custom_link = 6 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Link to an external resource that will be available to users in the console.";
example: "\"https://external.link\"";
}
];
string custom_link_text = 7 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "The button text that would be shown in console pointing to custom link.";
example: "\"External\"";
}
];
} }
message UpdateCustomPrivacyPolicyResponse { message UpdateCustomPrivacyPolicyResponse {

View File

@ -375,6 +375,25 @@ message PrivacyPolicy {
description: "help / support email address." description: "help / support email address."
} }
]; ];
string docs_link = 7 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Link to documentation to be shown in the console.";
example: "\"https://zitadel.com/docs\"";
}
];
string custom_link = 8 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Link to an external resource that will be available to users in the console.";
example: "\"https://external.link\"";
}
];
string custom_link_text = 9 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "The button text that would be shown in console pointing to custom link.";
example: "\"External\"";
}
];
} }
message NotificationPolicy { message NotificationPolicy {

View File

@ -37,4 +37,22 @@ message LegalAndSupportSettings {
description: "resource_owner_type returns if the setting is managed on the organization or on the instance"; description: "resource_owner_type returns if the setting is managed on the organization or on the instance";
} }
]; ];
string docs_link = 6 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Link to documentation to be shown in the console.";
example: "\"https://zitadel.com/docs\"";
}
];
string custom_link = 7 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Link to an external resource that will be available to users in the console.";
example: "\"https://external.link\"";
}
];
string custom_link_text = 8 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "The button text that would be shown in console pointing to custom link.";
example: "\"External\"";
}
];
} }