feat: add notification policy and password change message (#5065)

Implementation of new notification policy with functionality to send email when a password is changed
This commit is contained in:
Stefan Benz
2023-01-25 09:49:41 +01:00
committed by GitHub
parent 8b5894c0bb
commit 19621acfd3
73 changed files with 4196 additions and 83 deletions

View File

@@ -3,31 +3,39 @@ import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
import { MatLegacySelectChange as MatSelectChange } from '@angular/material/legacy-select';
import { BehaviorSubject, from, Observable, of, Subscription } from 'rxjs';
import {
GetCustomPasswordResetMessageTextRequest as AdminGetCustomPasswordResetMessageTextRequest,
GetDefaultInitMessageTextRequest as AdminGetDefaultInitMessageTextRequest,
GetDefaultVerifyEmailMessageTextRequest as AdminGetDefaultVerifyEmailMessageTextRequest,
GetDefaultVerifyPhoneMessageTextRequest as AdminGetDefaultVerifyPhoneMessageTextRequest,
SetDefaultDomainClaimedMessageTextRequest,
SetDefaultInitMessageTextRequest,
SetDefaultPasswordChangeMessageTextRequest,
SetDefaultPasswordlessRegistrationMessageTextRequest,
SetDefaultPasswordResetMessageTextRequest,
SetDefaultVerifyEmailMessageTextRequest,
SetDefaultVerifyPhoneMessageTextRequest,
GetDefaultPasswordChangeMessageTextRequest as AdminGetDefaultPasswordChangeMessageTextRequest,
GetDefaultInitMessageTextRequest as AdminGetDefaultInitMessageTextRequest,
GetDefaultVerifyEmailMessageTextRequest as AdminGetDefaultVerifyEmailMessageTextRequest,
GetDefaultVerifyPhoneMessageTextRequest as AdminGetDefaultVerifyPhoneMessageTextRequest,
GetDefaultPasswordResetMessageTextRequest as AdminGetDefaultPasswordResetMessageTextRequest,
GetDefaultDomainClaimedMessageTextRequest as AdminGetDefaultDomainClaimedMessageTextRequest,
GetDefaultPasswordlessRegistrationMessageTextRequest as AdminGetDefaultPasswordlessRegistrationMessageTextRequest,
} from 'src/app/proto/generated/zitadel/admin_pb';
import {
GetCustomDomainClaimedMessageTextRequest,
GetCustomInitMessageTextRequest,
GetCustomPasswordChangeMessageTextRequest,
GetCustomPasswordlessRegistrationMessageTextRequest,
GetCustomPasswordResetMessageTextRequest,
GetCustomVerifyEmailMessageTextRequest,
GetCustomVerifyPhoneMessageTextRequest,
GetDefaultDomainClaimedMessageTextRequest,
GetDefaultInitMessageTextRequest,
GetDefaultPasswordChangeMessageTextRequest,
GetDefaultPasswordlessRegistrationMessageTextRequest,
GetDefaultPasswordResetMessageTextRequest,
GetDefaultVerifyEmailMessageTextRequest,
GetDefaultVerifyPhoneMessageTextRequest,
SetCustomDomainClaimedMessageTextRequest,
SetCustomInitMessageTextRequest,
SetCustomPasswordChangeMessageTextRequest,
SetCustomPasswordlessRegistrationMessageTextRequest,
SetCustomPasswordResetMessageTextRequest,
SetCustomVerifyEmailMessageTextRequest,
@@ -50,12 +58,30 @@ enum MESSAGETYPES {
PASSWORDRESET = 'PR',
DOMAINCLAIMED = 'DC',
PASSWORDLESS = 'PL',
PASSWORDCHANGE = 'PC',
}
const REQUESTMAP = {
[PolicyComponentServiceType.MGMT]: {
[MESSAGETYPES.PASSWORDCHANGE]: {
get: new GetCustomPasswordChangeMessageTextRequest(),
set: new SetCustomPasswordChangeMessageTextRequest(),
getDefault: new GetDefaultPasswordChangeMessageTextRequest(),
setFcn: (map: Partial<MessageCustomText.AsObject>): SetCustomPasswordChangeMessageTextRequest => {
const req = new SetCustomPasswordChangeMessageTextRequest();
req.setButtonText(map.buttonText ?? '');
req.setFooterText(map.footerText ?? '');
req.setGreeting(map.greeting ?? '');
req.setPreHeader(map.preHeader ?? '');
req.setSubject(map.subject ?? '');
req.setText(map.text ?? '');
req.setTitle(map.title ?? '');
return req;
},
},
[MESSAGETYPES.INIT]: {
get: new GetDefaultInitMessageTextRequest(),
get: new GetCustomInitMessageTextRequest(),
set: new SetCustomInitMessageTextRequest(),
getDefault: new GetDefaultInitMessageTextRequest(),
setFcn: (map: Partial<MessageCustomText.AsObject>): SetCustomInitMessageTextRequest => {
@@ -164,6 +190,22 @@ const REQUESTMAP = {
},
},
[PolicyComponentServiceType.ADMIN]: {
[MESSAGETYPES.PASSWORDCHANGE]: {
get: new AdminGetDefaultPasswordChangeMessageTextRequest(),
set: new SetDefaultPasswordChangeMessageTextRequest(),
setFcn: (map: Partial<MessageCustomText.AsObject>): SetDefaultPasswordChangeMessageTextRequest => {
const req = new SetDefaultPasswordChangeMessageTextRequest();
req.setButtonText(map.buttonText ?? '');
req.setFooterText(map.footerText ?? '');
req.setGreeting(map.greeting ?? '');
req.setPreHeader(map.preHeader ?? '');
req.setSubject(map.subject ?? '');
req.setText(map.text ?? '');
req.setTitle(map.title ?? '');
return req;
},
},
[MESSAGETYPES.INIT]: {
get: new AdminGetDefaultInitMessageTextRequest(),
set: new SetDefaultInitMessageTextRequest(),
@@ -213,7 +255,7 @@ const REQUESTMAP = {
},
},
[MESSAGETYPES.PASSWORDRESET]: {
get: new AdminGetCustomPasswordResetMessageTextRequest(),
get: new AdminGetDefaultPasswordResetMessageTextRequest(),
set: new SetDefaultPasswordResetMessageTextRequest(),
setFcn: (
map: Partial<SetDefaultPasswordResetMessageTextRequest.AsObject>,
@@ -231,7 +273,7 @@ const REQUESTMAP = {
},
},
[MESSAGETYPES.DOMAINCLAIMED]: {
get: new GetDefaultDomainClaimedMessageTextRequest(),
get: new AdminGetDefaultDomainClaimedMessageTextRequest(),
set: new SetDefaultDomainClaimedMessageTextRequest(),
setFcn: (
map: Partial<SetDefaultDomainClaimedMessageTextRequest.AsObject>,
@@ -249,7 +291,7 @@ const REQUESTMAP = {
},
},
[MESSAGETYPES.PASSWORDLESS]: {
get: new GetDefaultPasswordlessRegistrationMessageTextRequest(),
get: new AdminGetDefaultPasswordlessRegistrationMessageTextRequest(),
set: new SetDefaultPasswordlessRegistrationMessageTextRequest(),
setFcn: (
map: Partial<SetDefaultPasswordlessRegistrationMessageTextRequest.AsObject>,
@@ -382,6 +424,20 @@ export class MessageTextsComponent implements OnInit, OnDestroy {
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.loginnames', value: '{{.LoginNames}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.changedate', value: '{{.ChangeDate}}' },
],
[MESSAGETYPES.PASSWORDCHANGE]: [
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.preferredLoginName', value: '{{.PreferredLoginName}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.username', value: '{{.UserName}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.firstname', value: '{{.FirstName}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.lastname', value: '{{.Lastname}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.nickName', value: '{{.NickName}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.displayName', value: '{{.DisplayName}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.lastEmail', value: '{{.LastEmail}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.verifiedEmail', value: '{{.VerifiedEmail}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.lastPhone', value: '{{.LastPhone}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.verifiedPhone', value: '{{.VerifiedPhone}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.loginnames', value: '{{.LoginNames}}' },
{ key: 'POLICY.MESSAGE_TEXTS.CHIPS.changedate', value: '{{.ChangeDate}}' },
],
};
public locale: string = 'en';
@@ -435,6 +491,8 @@ export class MessageTextsComponent implements OnInit, OnDestroy {
return this.stripDetails(this.service.getDefaultDomainClaimedMessageText(req));
case MESSAGETYPES.PASSWORDLESS:
return this.stripDetails(this.service.getDefaultPasswordlessRegistrationMessageText(req));
case MESSAGETYPES.PASSWORDCHANGE:
return this.stripDetails(this.service.getDefaultPasswordChangeMessageText(req));
}
}
@@ -453,6 +511,9 @@ export class MessageTextsComponent implements OnInit, OnDestroy {
return this.stripDetails((this.service as ManagementService).getCustomDomainClaimedMessageText(req));
case MESSAGETYPES.PASSWORDLESS:
return this.stripDetails((this.service as ManagementService).getCustomPasswordlessRegistrationMessageText(req));
case MESSAGETYPES.PASSWORDCHANGE:
return this.stripDetails((this.service as ManagementService).getCustomPasswordChangeMessageText(req));
default:
return undefined;
}
@@ -470,6 +531,8 @@ export class MessageTextsComponent implements OnInit, OnDestroy {
return this.stripDetails((this.service as AdminService).getCustomDomainClaimedMessageText(req));
case MESSAGETYPES.PASSWORDLESS:
return this.stripDetails((this.service as AdminService).getCustomPasswordlessRegistrationMessageText(req));
case MESSAGETYPES.PASSWORDCHANGE:
return this.stripDetails((this.service as AdminService).getCustomPasswordChangeMessageText(req));
default:
return undefined;
}
@@ -535,6 +598,8 @@ export class MessageTextsComponent implements OnInit, OnDestroy {
return handler(
(this.service as ManagementService).getCustomPasswordlessRegistrationMessageText(this.updateRequest),
);
case MESSAGETYPES.PASSWORDCHANGE:
return handler((this.service as ManagementService).getCustomPasswordChangeMessageText(this.updateRequest));
}
} else if (this.serviceType === PolicyComponentServiceType.ADMIN) {
switch (this.currentType) {
@@ -550,6 +615,8 @@ export class MessageTextsComponent implements OnInit, OnDestroy {
return handler((this.service as AdminService).setDefaultDomainClaimedMessageText(this.updateRequest));
case MESSAGETYPES.PASSWORDLESS:
return handler((this.service as AdminService).setDefaultPasswordlessRegistrationMessageText(this.updateRequest));
case MESSAGETYPES.PASSWORDCHANGE:
return handler((this.service as AdminService).setDefaultPasswordChangeMessageText(this.updateRequest));
}
}
}
@@ -595,6 +662,8 @@ export class MessageTextsComponent implements OnInit, OnDestroy {
return handler(
(this.service as ManagementService).resetCustomPasswordlessRegistrationMessageTextToDefault(this.locale),
);
case MESSAGETYPES.PASSWORDCHANGE:
return handler((this.service as ManagementService).resetCustomPasswordChangeMessageTextToDefault(this.locale));
default:
return Promise.reject();
}

View File

@@ -0,0 +1,49 @@
<h2>{{ 'POLICY.NOTIFICATION.TITLE' | translate }}</h2>
<p class="cnsl-secondary-text">{{ 'POLICY.NOTIFICATION.DESCRIPTION' | translate }}</p>
<div *ngIf="loading" class="spinner-wr">
<mat-spinner diameter="30" color="primary"></mat-spinner>
</div>
<ng-template cnslHasRole [hasRole]="['policy.delete']">
<button
*ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault"
matTooltip="{{ 'POLICY.RESET' | translate }}"
color="warn"
(click)="removePolicy()"
mat-stroked-button
>
{{ 'POLICY.RESET' | translate }}
</button>
</ng-template>
<div class="notification-policy-card">
<cnsl-card *ngIf="notificationData">
<div class="notification-policy-content">
<div class="row">
<mat-checkbox
class="slide-toggle"
color="primary"
name="hasUppercase"
ngDefaultControl
[(ngModel)]="notificationData.passwordChange"
[disabled]="(['policy.write'] | hasRole | async) === false"
>
{{ 'POLICY.NOTIFICATION.PASSWORDCHANGE' | translate }}
</mat-checkbox>
</div>
</div>
</cnsl-card>
</div>
<div class="btn-container">
<button
(click)="savePolicy()"
[disabled]="(['policy.write'] | hasRole | async) === false"
color="primary"
type="submit"
mat-raised-button
>
{{ 'ACTIONS.SAVE' | translate }}
</button>
</div>

View File

@@ -0,0 +1,32 @@
.spinner-wr {
margin: 0.5rem 0;
}
.policy-applied-to {
margin: -1rem 0 0 0;
font-size: 14px;
}
.notification-policy-card {
max-width: 400px;
.notification-policy-content {
display: flex;
flex-direction: column;
width: 100%;
.row {
display: flex;
align-items: center;
}
}
}
.btn-container {
display: flex;
justify-content: flex-start;
button {
display: block;
}
}

View File

@@ -0,0 +1,24 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { PasswordComplexityPolicyComponent } from './password-complexity-policy.component';
describe('PasswordComplexityPolicyComponent', () => {
let component: PasswordComplexityPolicyComponent;
let fixture: ComponentFixture<PasswordComplexityPolicyComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [PasswordComplexityPolicyComponent],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PasswordComplexityPolicyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,177 @@
import { Component, Injector, Input, OnInit, Type } from '@angular/core';
import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
import {
AddNotificationPolicyRequest,
GetNotificationPolicyResponse as AdminGetNotificationPolicyResponse,
UpdateNotificationPolicyRequest,
} from 'src/app/proto/generated/zitadel/admin_pb';
import {
AddCustomNotificationPolicyRequest,
GetNotificationPolicyResponse as MgmtGetNotificationPolicyResponse,
} from 'src/app/proto/generated/zitadel/management_pb';
import { NotificationPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
import { AdminService } from 'src/app/services/admin.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import { InfoSectionType } from '../../info-section/info-section.component';
import { WarnDialogComponent } from '../../warn-dialog/warn-dialog.component';
import { PolicyComponentServiceType } from '../policy-component-types.enum';
@Component({
selector: 'cnsl-notification-policy',
templateUrl: './notification-policy.component.html',
styleUrls: ['./notification-policy.component.scss'],
})
export class NotificationPolicyComponent implements OnInit {
@Input() public serviceType: PolicyComponentServiceType = PolicyComponentServiceType.MGMT;
public service!: ManagementService | AdminService;
public notificationData?: NotificationPolicy.AsObject = { isDefault: false, passwordChange: false };
public PolicyComponentServiceType: any = PolicyComponentServiceType;
public loading: boolean = false;
public InfoSectionType: any = InfoSectionType;
public isDefault: boolean = false;
private hasNotificationPolicy: boolean = false;
constructor(private toast: ToastService, private injector: Injector, private dialog: MatDialog) {}
public ngOnInit(): void {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
this.service = this.injector.get(ManagementService as Type<ManagementService>);
break;
case PolicyComponentServiceType.ADMIN:
this.service = this.injector.get(AdminService as Type<AdminService>);
break;
}
this.fetchData();
}
public fetchData(): void {
this.loading = true;
this.getData()
.then((data) => {
if (data.policy) {
this.hasNotificationPolicy = true;
this.notificationData = data.policy;
this.isDefault = data.policy.isDefault;
this.loading = false;
}
})
.catch((error) => {
this.loading = false;
if (error && error.code === 5) {
console.log(error);
this.hasNotificationPolicy = false;
}
});
}
private async getData(): Promise<
MgmtGetNotificationPolicyResponse.AsObject | AdminGetNotificationPolicyResponse.AsObject
> {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
return (this.service as ManagementService).getNotificationPolicy();
case PolicyComponentServiceType.ADMIN:
return (this.service as AdminService).getNotificationPolicy();
}
}
public removePolicy(): void {
if (this.service instanceof ManagementService) {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.RESET',
cancelKey: 'ACTIONS.CANCEL',
titleKey: 'SETTING.DIALOG.RESET.DEFAULTTITLE',
descriptionKey: 'SETTING.DIALOG.RESET.DEFAULTDESCRIPTION',
},
width: '400px',
});
dialogRef.afterClosed().subscribe((resp) => {
if (resp) {
(this.service as ManagementService)
.resetNotificationPolicyToDefault()
.then(() => {
this.toast.showInfo('POLICY.TOAST.RESETSUCCESS', true);
this.isDefault = true;
setTimeout(() => {
this.fetchData();
}, 1000);
})
.catch((error) => {
this.toast.showError(error);
});
}
});
}
}
public savePolicy(): void {
if (this.notificationData) {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
if ((this.notificationData as NotificationPolicy.AsObject).isDefault) {
const req = new AddCustomNotificationPolicyRequest();
req.setPasswordChange(this.notificationData.passwordChange);
(this.service as ManagementService)
.addCustomNotificationPolicy(req)
.then(() => {
this.isDefault = false;
this.toast.showInfo('POLICY.TOAST.SET', true);
})
.catch((error) => {
this.toast.showError(error);
});
} else {
const req = new UpdateNotificationPolicyRequest();
req.setPasswordChange(this.notificationData.passwordChange);
(this.service as ManagementService)
.updateCustomNotificationPolicy(req)
.then(() => {
this.isDefault = false;
this.toast.showInfo('POLICY.TOAST.SET', true);
})
.catch((error) => {
this.toast.showError(error);
});
}
break;
case PolicyComponentServiceType.ADMIN:
if (this.hasNotificationPolicy) {
const req = new UpdateNotificationPolicyRequest();
req.setPasswordChange(this.notificationData.passwordChange);
(this.service as AdminService)
.updateNotificationPolicy(req)
.then(() => {
this.isDefault = false;
this.toast.showInfo('POLICY.TOAST.SET', true);
})
.catch((error) => {
this.toast.showError(error);
});
} else {
const req = new AddNotificationPolicyRequest();
req.setPasswordChange(this.notificationData.passwordChange);
(this.service as AdminService)
.addNotificationPolicy(req)
.then(() => {
this.isDefault = false;
this.hasNotificationPolicy = true;
this.toast.showInfo('POLICY.TOAST.SET', true);
})
.catch((error) => {
this.toast.showError(error);
});
}
break;
}
}
}
}

View File

@@ -0,0 +1,43 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { MatLegacyCheckboxModule as MatCheckboxModule } from '@angular/material/legacy-checkbox';
import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog';
import { MatIconModule } from '@angular/material/icon';
import { MatLegacyProgressSpinnerModule as MatProgressSpinnerModule } from '@angular/material/legacy-progress-spinner';
import { MatLegacyTooltipModule as MatTooltipModule } from '@angular/material/legacy-tooltip';
import { TranslateModule } from '@ngx-translate/core';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
import { InputModule } from 'src/app/modules/input/input.module';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
import { CardModule } from '../../card/card.module';
import { InfoSectionModule } from '../../info-section/info-section.module';
import { WarnDialogModule } from '../../warn-dialog/warn-dialog.module';
import { NotificationPolicyComponent } from './notification-policy.component';
@NgModule({
declarations: [NotificationPolicyComponent],
imports: [
CommonModule,
FormsModule,
InputModule,
MatButtonModule,
MatIconModule,
HasRoleModule,
MatDialogModule,
MatTooltipModule,
MatCheckboxModule,
HasRolePipeModule,
TranslateModule,
WarnDialogModule,
DetailLayoutModule,
CardModule,
MatProgressSpinnerModule,
InfoSectionModule,
],
exports: [NotificationPolicyComponent],
})
export class NotificationPolicyModule {}

View File

@@ -1,20 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { PasswordComplexityPolicyComponent } from './password-complexity-policy.component';
const routes: Routes = [
{
path: '',
component: PasswordComplexityPolicyComponent,
data: {
animation: 'DetailPage',
},
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class PasswordComplexityPolicyRoutingModule {}

View File

@@ -16,13 +16,11 @@ import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.mod
import { CardModule } from '../../card/card.module';
import { InfoSectionModule } from '../../info-section/info-section.module';
import { WarnDialogModule } from '../../warn-dialog/warn-dialog.module';
import { PasswordComplexityPolicyRoutingModule } from './password-complexity-policy-routing.module';
import { PasswordComplexityPolicyComponent } from './password-complexity-policy.component';
@NgModule({
declarations: [PasswordComplexityPolicyComponent],
imports: [
PasswordComplexityPolicyRoutingModule,
CommonModule,
FormsModule,
InputModule,

View File

@@ -25,9 +25,14 @@
<cnsl-idp-settings [serviceType]="serviceType"></cnsl-idp-settings>
</ng-container>
<ng-container *ngIf="currentSetting === 'notifications' && serviceType === PolicyComponentServiceType.ADMIN">
<cnsl-notification-policy [serviceType]="serviceType"></cnsl-notification-policy>
<cnsl-notification-settings [serviceType]="serviceType"></cnsl-notification-settings>
</ng-container>
<ng-container *ngIf="currentSetting === 'notifications' && serviceType === PolicyComponentServiceType.MGMT">
<cnsl-notification-policy [serviceType]="serviceType"></cnsl-notification-policy
></ng-container>
<ng-container *ngIf="currentSetting === 'oidc' && serviceType === PolicyComponentServiceType.ADMIN">
<cnsl-oidc-configuration></cnsl-oidc-configuration>
</ng-container>

View File

@@ -11,6 +11,7 @@ import { IdpSettingsModule } from '../policies/idp-settings/idp-settings.module'
import { LoginPolicyModule } from '../policies/login-policy/login-policy.module';
import { LoginTextsPolicyModule } from '../policies/login-texts/login-texts.module';
import { MessageTextsPolicyModule } from '../policies/message-texts/message-texts.module';
import { NotificationPolicyModule } from '../policies/notification-policy/notification-policy.module';
import { NotificationSettingsModule } from '../policies/notification-settings/notification-settings.module';
import { OIDCConfigurationModule } from '../policies/oidc-configuration/oidc-configuration.module';
import { PasswordComplexityPolicyModule } from '../policies/password-complexity-policy/password-complexity-policy.module';
@@ -34,6 +35,7 @@ import { SettingsListComponent } from './settings-list.component';
PasswordLockoutPolicyModule,
PrivateLabelingPolicyModule,
GeneralSettingsModule,
NotificationPolicyModule,
IdpSettingsModule,
PrivacyPolicyModule,
MessageTextsPolicyModule,

View File

@@ -92,6 +92,15 @@ export const NOTIFICATIONS: SidenavSetting = {
},
};
export const NOTIFICATION_POLICY: SidenavSetting = {
id: 'notifications',
i18nKey: 'SETTINGS.LIST.NOTIFICATIONS',
groupI18nKey: 'SETTINGS.GROUPS.NOTIFICATIONS',
requiredRoles: {
[PolicyComponentServiceType.MGMT]: ['policy.read'],
},
};
export const MESSAGETEXTS: SidenavSetting = {
id: 'messagetexts',
i18nKey: 'SETTINGS.LIST.MESSAGETEXTS',

View File

@@ -11,6 +11,7 @@ import {
DOMAIN,
IDP,
LOCKOUT,
NOTIFICATION_POLICY,
LOGIN,
LOGINTEXTS,
MESSAGETEXTS,
@@ -30,6 +31,7 @@ export class OrgSettingsComponent {
IDP,
COMPLEXITY,
LOCKOUT,
NOTIFICATION_POLICY,
DOMAIN,
BRANDING,
MESSAGETEXTS,

View File

@@ -204,6 +204,18 @@ import {
GetSecurityPolicyResponse,
SetSecurityPolicyRequest,
SetSecurityPolicyResponse,
GetNotificationPolicyRequest,
GetNotificationPolicyResponse,
UpdateNotificationPolicyRequest,
UpdateNotificationPolicyResponse,
GetDefaultPasswordChangeMessageTextResponse,
GetDefaultPasswordChangeMessageTextRequest,
GetCustomPasswordChangeMessageTextResponse,
SetDefaultPasswordChangeMessageTextRequest,
SetDefaultPasswordChangeMessageTextResponse,
GetCustomPasswordChangeMessageTextRequest,
AddNotificationPolicyRequest,
AddNotificationPolicyResponse,
} from '../proto/generated/zitadel/admin_pb';
import { SearchQuery } from '../proto/generated/zitadel/member_pb';
import { ListQuery } from '../proto/generated/zitadel/object_pb';
@@ -346,6 +358,24 @@ export class AdminService {
return this.grpcService.admin.setDefaultPasswordlessRegistrationMessageText(req, null).then((resp) => resp.toObject());
}
public getDefaultPasswordChangeMessageText(
req: GetDefaultPasswordChangeMessageTextRequest,
): Promise<GetDefaultPasswordChangeMessageTextResponse.AsObject> {
return this.grpcService.admin.getDefaultPasswordChangeMessageText(req, null).then((resp) => resp.toObject());
}
public getCustomPasswordChangeMessageText(
req: GetCustomPasswordChangeMessageTextRequest,
): Promise<GetCustomPasswordChangeMessageTextResponse.AsObject> {
return this.grpcService.admin.getCustomPasswordChangeMessageText(req, null).then((resp) => resp.toObject());
}
public setDefaultPasswordChangeMessageText(
req: SetDefaultPasswordChangeMessageTextRequest,
): Promise<SetDefaultPasswordChangeMessageTextResponse.AsObject> {
return this.grpcService.admin.setDefaultPasswordChangeMessageText(req, null).then((resp) => resp.toObject());
}
public SetUpOrg(org: SetUpOrgRequest.Org, human: SetUpOrgRequest.Human): Promise<SetUpOrgResponse.AsObject> {
const req = new SetUpOrgRequest();
@@ -484,6 +514,21 @@ export class AdminService {
return this.grpcService.admin.setDefaultLanguage(req, null).then((resp) => resp.toObject());
}
/* notification policy */
public getNotificationPolicy(): Promise<GetNotificationPolicyResponse.AsObject> {
const req = new GetNotificationPolicyRequest();
return this.grpcService.admin.getNotificationPolicy(req, null).then((resp) => resp.toObject());
}
public updateNotificationPolicy(req: UpdateNotificationPolicyRequest): Promise<UpdateNotificationPolicyResponse.AsObject> {
return this.grpcService.admin.updateNotificationPolicy(req, null).then((resp) => resp.toObject());
}
public addNotificationPolicy(req: AddNotificationPolicyRequest): Promise<AddNotificationPolicyResponse.AsObject> {
return this.grpcService.admin.addNotificationPolicy(req, null).then((resp) => resp.toObject());
}
/* security policy */
public getSecurityPolicy(): Promise<GetSecurityPolicyResponse.AsObject> {

View File

@@ -24,6 +24,8 @@ import {
AddCustomLockoutPolicyResponse,
AddCustomLoginPolicyRequest,
AddCustomLoginPolicyResponse,
AddCustomNotificationPolicyRequest,
AddCustomNotificationPolicyResponse,
AddCustomPasswordAgePolicyRequest,
AddCustomPasswordAgePolicyResponse,
AddCustomPasswordComplexityPolicyRequest,
@@ -108,6 +110,8 @@ import {
GetCustomInitMessageTextResponse,
GetCustomLoginTextsRequest,
GetCustomLoginTextsResponse,
GetCustomPasswordChangeMessageTextRequest,
GetCustomPasswordChangeMessageTextResponse,
GetCustomPasswordlessRegistrationMessageTextRequest,
GetCustomPasswordlessRegistrationMessageTextResponse,
GetCustomPasswordResetMessageTextRequest,
@@ -124,6 +128,8 @@ import {
GetDefaultLabelPolicyResponse,
GetDefaultLoginTextsRequest,
GetDefaultLoginTextsResponse,
GetDefaultPasswordChangeMessageTextRequest,
GetDefaultPasswordChangeMessageTextResponse,
GetDefaultPasswordComplexityPolicyRequest,
GetDefaultPasswordComplexityPolicyResponse,
GetDefaultPasswordlessRegistrationMessageTextRequest,
@@ -156,6 +162,8 @@ import {
GetLoginPolicyResponse,
GetMyOrgRequest,
GetMyOrgResponse,
GetNotificationPolicyRequest,
GetNotificationPolicyResponse,
GetOIDCInformationRequest,
GetOIDCInformationResponse,
GetOrgByDomainGlobalRequest,
@@ -343,6 +351,8 @@ import {
ResetCustomInitMessageTextToDefaultResponse,
ResetCustomLoginTextsToDefaultRequest,
ResetCustomLoginTextsToDefaultResponse,
ResetCustomPasswordChangeMessageTextToDefaultRequest,
ResetCustomPasswordChangeMessageTextToDefaultResponse,
ResetCustomPasswordlessRegistrationMessageTextToDefaultRequest,
ResetCustomPasswordlessRegistrationMessageTextToDefaultResponse,
ResetCustomPasswordResetMessageTextToDefaultRequest,
@@ -357,6 +367,8 @@ import {
ResetLockoutPolicyToDefaultResponse,
ResetLoginPolicyToDefaultRequest,
ResetLoginPolicyToDefaultResponse,
ResetNotificationPolicyToDefaultRequest,
ResetNotificationPolicyToDefaultResponse,
ResetPasswordAgePolicyToDefaultRequest,
ResetPasswordAgePolicyToDefaultResponse,
ResetPasswordComplexityPolicyToDefaultRequest,
@@ -403,6 +415,8 @@ import {
UpdateCustomLockoutPolicyResponse,
UpdateCustomLoginPolicyRequest,
UpdateCustomLoginPolicyResponse,
UpdateCustomNotificationPolicyRequest,
UpdateCustomNotificationPolicyResponse,
UpdateCustomPasswordAgePolicyRequest,
UpdateCustomPasswordAgePolicyResponse,
UpdateCustomPasswordComplexityPolicyRequest,
@@ -631,6 +645,26 @@ export class ManagementService {
return this.grpcService.mgmt.getDefaultPasswordlessRegistrationMessageText(req, null).then((resp) => resp.toObject());
}
public getDefaultPasswordChangeMessageText(
req: GetDefaultPasswordChangeMessageTextRequest,
): Promise<GetDefaultPasswordChangeMessageTextResponse.AsObject> {
return this.grpcService.mgmt.getDefaultPasswordChangeMessageText(req, null).then((resp) => resp.toObject());
}
public getCustomPasswordChangeMessageText(
req: GetCustomPasswordChangeMessageTextRequest,
): Promise<GetCustomPasswordChangeMessageTextResponse.AsObject> {
return this.grpcService.mgmt.getCustomPasswordChangeMessageText(req, null).then((resp) => resp.toObject());
}
public resetCustomPasswordChangeMessageTextToDefault(
lang: string,
): Promise<ResetCustomPasswordChangeMessageTextToDefaultResponse.AsObject> {
const req = new ResetCustomPasswordChangeMessageTextToDefaultRequest();
req.setLanguage(lang);
return this.grpcService.mgmt.resetCustomPasswordChangeMessageTextToDefault(req, null).then((resp) => resp.toObject());
}
public getCustomPasswordlessRegistrationMessageText(
req: GetCustomPasswordlessRegistrationMessageTextRequest,
): Promise<GetCustomPasswordlessRegistrationMessageTextResponse.AsObject> {
@@ -1371,6 +1405,30 @@ export class ManagementService {
}
}
/* notification policy */
public getNotificationPolicy(): Promise<GetNotificationPolicyResponse.AsObject> {
const req = new GetNotificationPolicyRequest();
return this.grpcService.mgmt.getNotificationPolicy(req, null).then((resp) => resp.toObject());
}
public resetNotificationPolicyToDefault(): Promise<ResetNotificationPolicyToDefaultResponse.AsObject> {
const req = new ResetNotificationPolicyToDefaultRequest();
return this.grpcService.mgmt.resetNotificationPolicyToDefault(req, null).then((resp) => resp.toObject());
}
public addCustomNotificationPolicy(
req: AddCustomNotificationPolicyRequest,
): Promise<AddCustomNotificationPolicyResponse.AsObject> {
return this.grpcService.mgmt.addCustomNotificationPolicy(req, null).then((resp) => resp.toObject());
}
public updateCustomNotificationPolicy(
req: UpdateCustomNotificationPolicyRequest,
): Promise<UpdateCustomNotificationPolicyResponse.AsObject> {
return this.grpcService.mgmt.updateCustomNotificationPolicy(req, null).then((resp) => resp.toObject());
}
public getUserByID(id: string): Promise<GetUserByIDResponse.AsObject> {
const req = new GetUserByIDRequest();
req.setId(id);

View File

@@ -883,7 +883,7 @@
"LOGIN": "Loginverhalten und Sicherheit",
"LOCKOUT": "Sperrmechanismen",
"COMPLEXITY": "Passwordkomplexität",
"NOTIFICATIONS": "Benachrichtigungen",
"NOTIFICATIONS": "Benachrichtigungseinstellungen",
"NOTIFICATIONS_DESC": "SMTP und SMS Einstellungen",
"MESSAGETEXTS": "Benachrichtigungstexte",
"IDP": "Identity Provider",
@@ -1007,6 +1007,11 @@
"NUMBERERROR": "Muss eine Ziffer beinhalten.",
"PATTERNERROR": "Das Passwort erfüllt nicht die vorgeschriebene Richtlinie."
},
"NOTIFICATION": {
"TITLE": "Notification",
"DESCRIPTION": "Legt fest, bei welchen Änderungen Benachrichtigungen gesendet werden",
"PASSWORDCHANGE": "Passwordänderung"
},
"PRIVATELABELING": {
"TITLE": "Branding",
"DESCRIPTION": "Verleihen Sie dem Login Ihren benutzerdefinierten Style und passen Sie das Verhalten an.",
@@ -1146,7 +1151,8 @@
"VP": "Telefonnummerverifikation",
"PR": "Passwort Wiederherstellung",
"DC": "Domainbeanspruchung",
"PL": "Passwortlos"
"PL": "Passwortlos",
"PC": "Passwordwechsel"
},
"CHIPS": {
"firstname": "Vorname",

View File

@@ -883,7 +883,7 @@
"LOGIN": "Login Behavior and Security",
"LOCKOUT": "Lockout",
"COMPLEXITY": "Password complexity",
"NOTIFICATIONS": "Notification providers and SMTP",
"NOTIFICATIONS": "Notification settings",
"NOTIFICATIONS_DESC": "SMTP and SMS Settings",
"MESSAGETEXTS": "Message Texts",
"IDP": "Identity Providers",
@@ -1007,6 +1007,11 @@
"NUMBERERROR": "Must include a digit.",
"PATTERNERROR": "The password does not meet the required pattern."
},
"NOTIFICATION": {
"TITLE": "Notification",
"DESCRIPTION": "Determines on which changes, notifications will be sent.",
"PASSWORDCHANGE": "Password change"
},
"PRIVATELABELING": {
"TITLE": "Branding",
"DESCRIPTION": "Give the login your personalized style and modify its behavior.",
@@ -1146,7 +1151,8 @@
"VP": "Verify Phone",
"PR": "Password Reset",
"DC": "Domain Claim",
"PL": "Passwordless"
"PL": "Passwordless",
"PC": "Password Change"
},
"CHIPS": {
"firstname": "Firstname",

View File

@@ -883,7 +883,7 @@
"LOGIN": "Comportement de connexion et sécurité",
"LOCKOUT": "Verrouillage",
"COMPLEXITY": "Complexité du mot de passe",
"NOTIFICATIONS": "Fournisseurs de notifications et SMTP",
"NOTIFICATIONS": "Paramètres de notification",
"NOTIFICATIONS_DESC": "Paramètres SMTP et SMS",
"MESSAGETEXTS": "Textes des messages",
"IDP": "Fournisseurs d'identité",
@@ -1007,6 +1007,11 @@
"NUMBERERROR": "Doit inclure un chiffre.",
"PATTERNERROR": "Le mot de passe ne correspond pas au modèle requis."
},
"NOTIFICATION": {
"TITLE": "Notifications",
"DESCRIPTION": "Détermine sur quels changements, les notifications seront envoyées",
"PASSWORDCHANGE": "Changement de mot de passe"
},
"PRIVATELABELING": {
"TITLE": "Image de marque",
"DESCRIPTION": "Donnez au login votre style personnalisé et modifiez son comportement.",
@@ -1146,7 +1151,8 @@
"VP": "Vérifier le téléphone",
"PR": "Réinitialisation du mot de passe",
"DC": "Réclamation de domaine",
"PL": "Sans mot de passe"
"PL": "Sans mot de passe",
"PC": "Changement de mot de passe"
},
"CHIPS": {
"firstname": "Prénom",

View File

@@ -884,7 +884,7 @@
"LOGIN": "Comportamento login e sicurezza",
"LOCKOUT": "Meccanismi di bloccaggio",
"COMPLEXITY": "Complessità della password",
"NOTIFICATIONS": "Notifiche",
"NOTIFICATIONS": "Impostazioni di notifica",
"NOTIFICATIONS_DESC": "Impostazioni SMTP e SMS",
"MESSAGETEXTS": "Testi di notifica",
"IDP": "Identity Providers",
@@ -1008,6 +1008,11 @@
"NUMBERERROR": "Deve includere una cifra.",
"PATTERNERROR": "La password non corrisponde al modello richiesto."
},
"NOTIFICATION": {
"TITLE": "Notifiche",
"DESCRIPTION": "Determina su quali modifiche verranno inviate le notifiche",
"PASSWORDCHANGE": "Cambiamento della password"
},
"PRIVATELABELING": {
"TITLE": "Branding",
"DESCRIPTION": "Dai al login il tuo stile personalizzato e modifica il suo comportamento.",
@@ -1147,7 +1152,8 @@
"VP": "Verificazione del telefono",
"PR": "Ripristino della password",
"DC": "Rivendicazione del dominio",
"PL": "Autenticazione Passwordless"
"PL": "Autenticazione Passwordless",
"PC": "Cambiamento della password"
},
"CHIPS": {
"firstname": "Nome",

View File

@@ -883,7 +883,7 @@
"LOGIN": "登录行为和安全",
"LOCKOUT": "安全锁策略",
"COMPLEXITY": "密码复杂性",
"NOTIFICATIONS": "通知服务商",
"NOTIFICATIONS": "通知设置",
"NOTIFICATIONS_DESC": "SMTP 和 SMS 设置",
"MESSAGETEXTS": "消息文本",
"IDP": "身份提供者",
@@ -1007,6 +1007,11 @@
"NUMBERERROR": "密码必须包含数字。",
"PATTERNERROR": "密码不符合要求。"
},
"NOTIFICATION": {
"TITLE": "通知",
"DESCRIPTION": "确定将发送哪些更改、通知",
"PASSWORDCHANGE": "更改密码"
},
"PRIVATELABELING": {
"TITLE": "品牌标识",
"DESCRIPTION": "为登录提供您的个性化风格并修改其行为。",
@@ -1145,7 +1150,8 @@
"VP": "验证手机号码",
"PR": "重置密码",
"DC": "域名声明",
"PL": "无密码身份验证"
"PL": "无密码身份验证",
"PC": "修改密码"
},
"CHIPS": {
"firstname": "名",