feat: manage restrictions in console (#6965)

* feat: return 404 or 409 if org reg disallowed

* fix: system limit permissions

* feat: add iam limits api

* feat: disallow public org registrations on default instance

* add integration test

* test: integration

* fix test

* docs: describe public org registrations

* avoid updating docs deps

* fix system limits integration test

* silence integration tests

* fix linting

* ignore strange linter complaints

* review

* improve reset properties naming

* redefine the api

* use restrictions aggregate

* test query

* simplify and test projection

* test commands

* fix unit tests

* move integration test

* support restrictions on default instance

* also test GetRestrictions

* self review

* lint

* abstract away resource owner

* fix tests

* configure supported languages

* fix allowed languages

* fix tests

* default lang must not be restricted

* preferred language must be allowed

* change preferred languages

* check languages everywhere

* lint

* test command side

* lint

* add integration test

* add integration test

* restrict supported ui locales

* lint

* lint

* cleanup

* lint

* allow undefined preferred language

* fix integration tests

* update main

* fix env var

* ignore linter

* ignore linter

* improve integration test config

* reduce cognitive complexity

* compile

* fix(console): switch back to saved language

* feat(API): get allowed languages

* fix(console): only make allowed languages selectable

* warn when editing not allowed languages

* feat: manage restrictions in console

* check for duplicates

* remove useless restriction checks

* review

* revert restriction renaming

* manage languages

* fix language restrictions

* lint

* generate

* allow custom texts for supported langs for now

* fix tests

* cleanup

* cleanup

* cleanup

* lint

* unsupported preferred lang is allowed

* fix integration test

* allow unsupported preferred languages

* lint

* fix languages lists

* simplify default language selection

* translate

* discard

* lint

* load languages for tests

* load languages

* lint

* cleanup

* lint

* cleanup

* get allowed only on admin

* cleanup

* reduce flakiness on very limited postgres

* simplify langSvc

* refactor according to suggestions in pr

* lint

* improve ux

* update central allowed languages

* set first allowed language as default

* readd lost translations

* disable sorting disallowed languages

* fix permissions

* lint

* selectionchange for language in msg texts

* initialize login texts

* init message texts

* lint

* fix drag and drop list styles

* start from 1

* cleanup

* prettier

* correct orgdefaultlabel

* unsubscribe

* lint

* docs: describe language settings

---------

Co-authored-by: peintnermax <[email protected]>
This commit is contained in:
Elio Bischof
2023-12-07 12:31:01 +00:00
committed by GitHub
co-authored by peintnermax
parent 3842319d07
commit d639c5200a
34 changed files with 961 additions and 449 deletions
@@ -1,27 +0,0 @@
<h2>{{ 'SETTING.DEFAULTLANGUAGE' | translate }}</h2>
<div class="spinner-wr">
<mat-spinner diameter="30" *ngIf="loading" color="primary"></mat-spinner>
</div>
<cnsl-form-field class="default-language" label="Default Language" required="true">
<cnsl-label>{{ 'SETTING.DEFAULTLANGUAGE' | translate }}</cnsl-label>
<mat-select [(ngModel)]="defaultLanguage" [disabled]="(['iam.policy.write'] | hasRole | async) === false">
<mat-option *ngFor="let lang of defaultLanguageOptions" [value]="lang">
{{ lang }} - {{ 'SETTING.LANGUAGE.' + lang | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
<div class="general-btn-container">
<button
class="save-button"
(click)="savePolicy()"
color="primary"
type="submit"
mat-raised-button
[disabled]="(['iam.policy.write'] | hasRole | async) === false"
>
{{ 'ACTIONS.SAVE' | translate }}
</button>
</div>
@@ -1,18 +0,0 @@
.spinner-wr {
margin: 0.5rem 0;
}
.default-language {
max-width: 400px;
display: block;
}
.general-btn-container {
display: flex;
justify-content: flex-start;
margin-top: 1rem;
.save-button {
display: block;
}
}
@@ -1,56 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { SetDefaultLanguageResponse } from 'src/app/proto/generated/zitadel/admin_pb';
import { AdminService } from 'src/app/services/admin.service';
import { ToastService } from 'src/app/services/toast.service';
@Component({
selector: 'cnsl-general-settings',
templateUrl: './general-settings.component.html',
styleUrls: ['./general-settings.component.scss'],
})
export class GeneralSettingsComponent implements OnInit {
public defaultLanguage: string = '';
public defaultLanguageOptions: string[] = [];
public loading: boolean = false;
constructor(
private service: AdminService,
private toast: ToastService,
) {}
ngOnInit(): void {
this.fetchData();
}
private fetchData(): void {
this.service.getDefaultLanguage().then((langResp) => {
this.defaultLanguage = langResp.language;
});
this.service.getAllowedLanguages().then((supportedResp) => {
this.defaultLanguageOptions = supportedResp.languagesList;
});
}
private updateData(): Promise<SetDefaultLanguageResponse.AsObject> {
return (this.service as AdminService).setDefaultLanguage(this.defaultLanguage);
}
public savePolicy(): void {
const prom = this.updateData();
this.loading = true;
if (prom) {
prom
.then(() => {
this.toast.showInfo('POLICY.LOGIN_POLICY.SAVED', true);
this.loading = false;
setTimeout(() => {
this.fetchData();
}, 2000);
})
.catch((error) => {
this.loading = false;
this.toast.showError(error);
});
}
}
}
@@ -1,29 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select';
import { TranslateModule } from '@ngx-translate/core';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
import { CardModule } from '../../card/card.module';
import { FormFieldModule } from '../../form-field/form-field.module';
import { GeneralSettingsComponent } from './general-settings.component';
@NgModule({
declarations: [GeneralSettingsComponent],
imports: [
CommonModule,
CardModule,
FormsModule,
MatButtonModule,
FormFieldModule,
MatProgressSpinnerModule,
MatSelectModule,
HasRolePipeModule,
TranslateModule,
],
exports: [GeneralSettingsComponent],
})
export class GeneralSettingsModule {}
@@ -0,0 +1,135 @@
<h2>{{ 'SETTING.LANGUAGES.TITLE' | translate }}</h2>
<div class="spinner-wr">
<mat-spinner diameter="30" *ngIf="loading" color="primary"></mat-spinner>
</div>
<div>
<form class="languages-container-wrapper">
<div class="languages-container">
<div
class="languages-list"
cdkDropList
id="allowed"
[cdkDropListConnectedTo]="['notallowed']"
(cdkDropListDropped)="drop($event)"
[cdkDropListData]="(localState$ | async)!.allowed"
>
<div class="languages-top-row">
<span class="label">{{ 'SETTING.LANGUAGES.ALLOWED' | translate }}</span>
<button
class="list-button"
[disabled]="(canWriteRestrictions$ | async) === false"
(click)="allowAll()"
mat-stroked-button
>
<div class="cnsl-action-button">
<i class="las la-object-group"></i><span>{{ 'SETTING.LANGUAGES.ALLOW_ALL' | translate }}</span>
</div>
</button>
</div>
<div class="languages-drop-target">
<div
class="card languages-box"
*ngFor="let lang of (localState$ | async)!.allowed; index as i"
[cdkDragData]="lang"
cdkDrag
[cdkDragDisabled]="(canWriteRestrictions$ | async) === false"
mat-list-item
>
<span class="index">{{ i + 1 }}</span>
<span class="locale">{{ lang }}</span>
<span class="lang">{{ 'SETTING.LANGUAGES.OPTIONS.' + lang | translate }} </span>
<span *ngIf="lang === (defaultLang$ | async)" class="state orgdefaultlabel">{{
'SETTING.LANGUAGES.DEFAULT' | translate
}}</span>
<button
(click)="$event.stopPropagation()"
*ngIf="lang !== (defaultLang$ | async) && (isRemotelyAllowed$(lang) | async) === true"
mat-icon-button
class="more-button"
[matMenuTriggerFor]="actions"
data-e2e="table-actions-button"
>
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #actions="matMenu" xPosition="before" class="language-actions-menu">
<button
menuActions
mat-menu-item
(click)="setDefaultLang(lang)"
[disabled]="(canWriteDefaultLanguage$ | async) === false"
>
{{ 'SETTING.LANGUAGES.SETASDEFAULT' | translate }}
</button>
</mat-menu>
</div>
</div>
</div>
</div>
<div class="languages-container">
<div
class="languages-list"
cdkDropList
id="notallowed"
[cdkDropListConnectedTo]="['allowed']"
(cdkDropListDropped)="drop($event)"
[cdkDropListData]="(localState$ | async)!.notAllowed"
[cdkDropListEnterPredicate]="defaultLangPredicate"
cdkDropListSortingDisabled
>
<div class="languages-top-row">
<span class="label">{{ 'SETTING.LANGUAGES.NOT_ALLOWED' | translate }}</span>
<button
class="list-button"
[disabled]="(canWriteRestrictions$ | async) === false"
(click)="disallowAll()"
mat-stroked-button
>
<div class="cnsl-action-button">
<i class="las la-object-group"></i><span>{{ 'SETTING.LANGUAGES.DISALLOW_ALL' | translate }}</span>
</div>
</button>
</div>
<div class="languages-drop-target">
<div
class="card languages-box"
*ngFor="let lang of (localState$ | async)!.notAllowed"
[cdkDragData]="lang"
cdkDrag
[cdkDragDisabled]="(canWriteRestrictions$ | async) === false"
mat-list-item
>
<span class="locale">{{ lang }}</span>
<span class="lang">{{ 'SETTING.LANGUAGES.OPTIONS.' + lang | translate }} </span>
</div>
</div>
</div>
</div>
<div class="general-btn-container">
<button
class="reset-button"
[disabled]="(canWriteRestrictions$ | async) === false"
(click)="discard()"
color="message-text-warn"
mat-stroked-button
>
<div class="cnsl-action-button">
<i class="las la-history"></i><span>{{ 'ACTIONS.UNSAVED.DIALOG.DISCARD' | translate }}</span>
</div>
</button>
<button
class="save-button"
color="primary"
type="submit"
(click)="submit()"
mat-raised-button
[disabled]="(canWriteRestrictions$ | async) === false"
>
{{ 'ACTIONS.SAVE' | translate }}
</button>
</div>
</form>
</div>
@@ -0,0 +1,112 @@
.languages-container-wrapper {
display: grid;
grid-template-columns: 1fr 1fr;
column-gap: 2rem;
@media only screen and (max-width: 700px) {
grid-template-columns: 1fr;
}
.languages-container {
display: inline-block;
max-width: 400px;
vertical-align: top;
width: 100%;
.spinner-wr {
margin: 0.5rem 0;
}
.default-language {
max-width: 400px;
display: block;
}
}
.general-btn-container {
display: flex;
justify-content: flex-start;
margin-top: 1rem;
.save-button {
display: block;
margin-left: 1rem;
}
}
}
.languages-list {
overflow: hidden;
display: block;
height: 100%;
.languages-top-row {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 1rem;
margin-top: 1.5rem;
.label {
margin-right: 1rem;
flex: 1;
white-space: nowrap;
}
.list-button {
white-space: nowrap;
}
}
}
.languages-box {
position: relative;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 0.5rem 0.5rem 0.5rem 2rem;
height: 56px;
cursor: move;
margin: 2px 0;
.index {
position: absolute;
top: 4px;
left: 4px;
opacity: 0.5;
font-size: 10px;
}
.locale {
width: 35px;
margin-right: 1rem;
}
.lang {
flex: 1;
}
[hoveractions] {
display: none;
}
.more-button {
height: 40px;
width: 40px;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
}
&:hover {
[hoveractions] {
display: flex;
}
}
.defaultlanglabel {
margin-left: 0.5rem;
}
}
@@ -1,19 +1,19 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { GeneralSettingsComponent } from './general-settings.component';
import { LanguageSettingsComponent } from './language-settings.component';
describe('GeneralSettingsComponent', () => {
let component: GeneralSettingsComponent;
let fixture: ComponentFixture<GeneralSettingsComponent>;
describe('LanguageSettingsComponent', () => {
let component: LanguageSettingsComponent;
let fixture: ComponentFixture<LanguageSettingsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [GeneralSettingsComponent],
declarations: [LanguageSettingsComponent],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(GeneralSettingsComponent);
fixture = TestBed.createComponent(LanguageSettingsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
@@ -0,0 +1,116 @@
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { AdminService } from 'src/app/services/admin.service';
import { ToastService } from 'src/app/services/toast.service';
import { UntypedFormBuilder } from '@angular/forms';
import { LanguagesService } from '../../../services/languages.service';
import { BehaviorSubject, concat, forkJoin, from, Observable, of, Subject, switchMap, take, takeUntil } from 'rxjs';
import { GrpcAuthService } from '../../../services/grpc-auth.service';
import { CdkDrag, CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop';
import { catchError, map } from 'rxjs/operators';
interface State {
allowed: string[];
notAllowed: string[];
}
@Component({
selector: 'cnsl-language-settings',
templateUrl: './language-settings.component.html',
styleUrls: ['./language-settings.component.scss'],
})
export class LanguageSettingsComponent {
public canWriteRestrictions$: Observable<boolean> = this.authService.isAllowed(['iam.restrictions.write']);
public canWriteDefaultLanguage$: Observable<boolean> = this.authService.isAllowed(['iam.write']);
public localState$ = new BehaviorSubject<State>({ allowed: [], notAllowed: [] });
public remoteState$ = new BehaviorSubject<State>({ allowed: [], notAllowed: [] });
public defaultLang$ = new BehaviorSubject<string>('');
public loading: boolean = false;
constructor(
private service: AdminService,
private toast: ToastService,
private langSvc: LanguagesService,
private authService: GrpcAuthService,
) {
const sub = forkJoin([
langSvc.allowed$.pipe(take(1)),
langSvc.notAllowed$.pipe(take(1)),
from(this.service.getDefaultLanguage()).pipe(take(1)),
]).subscribe({
next: ([allowed, notAllowed, { language: defaultLang }]) => {
this.defaultLang$.next(defaultLang);
this.remoteState$.next({ notAllowed: [...notAllowed], ...{ allowed: [...allowed] } });
this.localState$.next({ notAllowed: [...notAllowed], ...{ allowed: [...allowed] } });
},
error: this.toast.showError,
complete: () => {
sub.unsubscribe();
},
});
}
drop(event: CdkDragDrop<string[]>) {
if (event.previousContainer === event.container) {
moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
} else {
transferArrayItem(event.previousContainer.data, event.container.data, event.previousIndex, event.currentIndex);
}
}
public defaultLangPredicate = (lang: CdkDrag<string>) => {
return !!lang?.data && lang.data !== this.defaultLang$.value;
};
public isRemotelyAllowed$(lang: string): Observable<boolean> {
return this.remoteState$.pipe(map(({ allowed }) => allowed.includes(lang)));
}
public allowAll(): void {
this.localState$.next({ allowed: [...this.allLocalLangs()], notAllowed: [] });
}
public disallowAll(): void {
const disallowed = this.allLocalLangs().filter((lang) => lang !== this.defaultLang$.value);
this.localState$.next({ allowed: [this.defaultLang$.value], notAllowed: disallowed });
}
public submit(): void {
const { allowed, notAllowed } = this.localState$.value;
const sub = from(this.service.setRestrictions(undefined, allowed)).subscribe({
next: () => {
this.remoteState$.next({
allowed: [...allowed],
notAllowed: [...notAllowed],
});
this.langSvc.newAllowed(allowed);
this.toast.showInfo('SETTING.LANGUAGES.ALLOWED_SAVED', true);
},
error: this.toast.showError,
complete: () => {
sub.unsubscribe();
},
});
}
public discard(): void {
this.localState$.next(this.remoteState$.value);
}
public setDefaultLang(lang: string): void {
const sub = from(this.service.setDefaultLanguage(lang)).subscribe({
next: () => {
this.defaultLang$.next(lang);
this.toast.showInfo('SETTING.LANGUAGES.DEFAULT_SAVED', true);
},
error: this.toast.showError,
complete: () => {
sub.unsubscribe();
},
});
}
private allLocalLangs(): string[] {
return [...this.localState$.value.allowed, ...this.localState$.value.notAllowed];
}
}
@@ -0,0 +1,46 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select';
import { TranslateModule } from '@ngx-translate/core';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
import { CardModule } from '../../card/card.module';
import { FormFieldModule } from '../../form-field/form-field.module';
import { LanguageSettingsComponent } from './language-settings.component';
import { MatListModule } from '@angular/material/list';
import { MatFormFieldModule } from '@angular/material/form-field';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { MatRadioModule } from '@angular/material/radio';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TableActionsModule } from '../../table-actions/table-actions.module';
import { MatMenuModule } from '@angular/material/menu';
import { MatIconModule } from '@angular/material/icon';
@NgModule({
declarations: [LanguageSettingsComponent],
imports: [
CommonModule,
CardModule,
FormsModule,
ReactiveFormsModule,
MatFormFieldModule,
MatButtonModule,
MatSelectModule,
FormFieldModule,
MatProgressSpinnerModule,
MatSelectModule,
HasRolePipeModule,
TranslateModule,
MatListModule,
DragDropModule,
MatRadioModule,
MatTooltipModule,
MatMenuModule,
MatIconModule,
],
exports: [LanguageSettingsComponent],
})
export class LanguageSettingsModule {}
@@ -242,14 +242,20 @@
| async) === false
"
>
{{ 'POLICY.DATA.ALLOWREGISTER' | translate }}
{{ 'POLICY.DATA.ALLOWREGISTERUSERS' | translate }}
</mat-checkbox>
</div>
<div class="login-policy-row">
<mat-checkbox
class="login-policy-toggle"
color="primary"
ngDefaultControl
[(ngModel)]="allowOrgRegistration"
*ngIf="serviceType === PolicyComponentServiceType.ADMIN"
[disabled]="(['iam.restrictions.write'] | hasRole | async) === false"
>
{{ 'POLICY.DATA.ALLOWREGISTERORGS' | translate }}
</mat-checkbox>
<!-- <ng-template #regInfo>
<cnsl-info-section class="info">
{{ 'POLICY.DATA.ALLOWREGISTER_DESC' | translate }}
</cnsl-info-section>
</ng-template> -->
</div>
<div class="login-policy-row">
<mat-checkbox
@@ -428,6 +434,7 @@
type="submit"
mat-raised-button
[disabled]="
(['iam.restrictions.write'] | hasRole | async) === false &&
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.policy.write'
@@ -1,8 +1,8 @@
import { Component, Injector, Input, OnInit, Type } from '@angular/core';
import { Component, Injector, Input, OnDestroy, OnInit, Type } from '@angular/core';
import { AbstractControl, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { Duration } from 'google-protobuf/google/protobuf/duration_pb';
import { take } from 'rxjs';
import { firstValueFrom, forkJoin, from, Observable, of, Subject, take } from 'rxjs';
import {
GetLoginPolicyResponse as AdminGetLoginPolicyResponse,
UpdateLoginPolicyRequest,
@@ -24,19 +24,21 @@ import { InfoSectionType } from '../../info-section/info-section.component';
import { WarnDialogComponent } from '../../warn-dialog/warn-dialog.component';
import { PolicyComponentServiceType } from '../policy-component-types.enum';
import { LoginMethodComponentType } from './factor-table/factor-table.component';
import { catchError, map, takeUntil } from 'rxjs/operators';
@Component({
selector: 'cnsl-login-policy',
templateUrl: './login-policy.component.html',
styleUrls: ['./login-policy.component.scss'],
})
export class LoginPolicyComponent implements OnInit {
export class LoginPolicyComponent implements OnInit, OnDestroy {
public LoginMethodComponentType: any = LoginMethodComponentType;
public passwordlessTypes: Array<PasswordlessType> = [
PasswordlessType.PASSWORDLESS_TYPE_NOT_ALLOWED,
PasswordlessType.PASSWORDLESS_TYPE_ALLOWED,
];
public loginData?: LoginPolicy.AsObject;
public allowOrgRegistration: boolean = false;
public service!: ManagementService | AdminService;
public PolicyComponentServiceType: any = PolicyComponentServiceType;
@@ -52,6 +54,8 @@ export class LoginPolicyComponent implements OnInit {
secondFactorCheckLifetime: [{ disabled: true }, [requiredValidator]],
multiFactorCheckLifetime: [{ disabled: true }, [requiredValidator]],
});
private destroy$: Subject<void> = new Subject();
constructor(
private toast: ToastService,
private injector: Injector,
@@ -60,43 +64,59 @@ export class LoginPolicyComponent implements OnInit {
private dialog: MatDialog,
) {}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
public fetchData(): void {
this.getData()
.then((resp) => {
if (resp.policy) {
this.loginData = resp.policy;
this.loading = false;
const data$ = forkJoin([
this.serviceType === PolicyComponentServiceType.ADMIN
? from((this.service as AdminService).getRestrictions()).pipe(
map(({ disallowPublicOrgRegistration }) => disallowPublicOrgRegistration),
)
: of(true),
from(this.getData()),
]);
this.passwordCheckLifetime?.setValue(
this.loginData.passwordCheckLifetime?.seconds ? this.loginData.passwordCheckLifetime?.seconds / 60 / 60 : 0,
);
this.externalLoginCheckLifetime?.setValue(
this.loginData.externalLoginCheckLifetime?.seconds
? this.loginData.externalLoginCheckLifetime?.seconds / 60 / 60
: 0,
);
this.mfaInitSkipLifetime?.setValue(
this.loginData.mfaInitSkipLifetime?.seconds ? this.loginData.mfaInitSkipLifetime?.seconds / 60 / 60 : 0,
);
this.secondFactorCheckLifetime?.setValue(
this.loginData.secondFactorCheckLifetime?.seconds
? this.loginData.secondFactorCheckLifetime?.seconds / 60 / 60
: 0,
);
this.multiFactorCheckLifetime?.setValue(
this.loginData.multiFactorCheckLifetime?.seconds
? this.loginData.multiFactorCheckLifetime?.seconds / 60 / 60
: 0,
);
const sub = data$.pipe(takeUntil(this.destroy$)).subscribe({
next: ([disallowPublicOrgRegistration, resp]) => {
this.allowOrgRegistration = !disallowPublicOrgRegistration;
if (!resp.policy) {
return;
}
})
.catch((error) => {
this.toast.showError(error);
});
this.loginData = resp.policy;
this.loading = false;
this.passwordCheckLifetime?.setValue(
this.loginData.passwordCheckLifetime?.seconds ? this.loginData.passwordCheckLifetime?.seconds / 60 / 60 : 0,
);
this.externalLoginCheckLifetime?.setValue(
this.loginData.externalLoginCheckLifetime?.seconds
? this.loginData.externalLoginCheckLifetime?.seconds / 60 / 60
: 0,
);
this.mfaInitSkipLifetime?.setValue(
this.loginData.mfaInitSkipLifetime?.seconds ? this.loginData.mfaInitSkipLifetime?.seconds / 60 / 60 : 0,
);
this.secondFactorCheckLifetime?.setValue(
this.loginData.secondFactorCheckLifetime?.seconds
? this.loginData.secondFactorCheckLifetime?.seconds / 60 / 60
: 0,
);
this.multiFactorCheckLifetime?.setValue(
this.loginData.multiFactorCheckLifetime?.seconds ? this.loginData.multiFactorCheckLifetime?.seconds / 60 / 60 : 0,
);
},
error: this.toast.showError,
complete: () => {
sub.unsubscribe();
},
});
}
public ngOnInit(): void {
@@ -142,7 +162,9 @@ export class LoginPolicyComponent implements OnInit {
}
}
private async updateData(): Promise<UpdateLoginPolicyResponse.AsObject> {
private async updateData(): Promise<any> {
const calls: Observable<any>[] = [];
if (this.loginData) {
switch (this.serviceType) {
case PolicyComponentServiceType.MGMT:
@@ -179,7 +201,8 @@ export class LoginPolicyComponent implements OnInit {
mgmtreq.setIgnoreUnknownUsernames(this.loginData.ignoreUnknownUsernames);
mgmtreq.setDefaultRedirectUri(this.loginData.defaultRedirectUri);
return (this.service as ManagementService).addCustomLoginPolicy(mgmtreq);
calls.push(from((this.service as ManagementService).addCustomLoginPolicy(mgmtreq)));
break;
} else {
const mgmtreq = new UpdateCustomLoginPolicyRequest();
mgmtreq.setAllowExternalIdp(this.loginData.allowExternalIdp);
@@ -211,7 +234,8 @@ export class LoginPolicyComponent implements OnInit {
mgmtreq.setIgnoreUnknownUsernames(this.loginData.ignoreUnknownUsernames);
mgmtreq.setDefaultRedirectUri(this.loginData.defaultRedirectUri);
return (this.service as ManagementService).updateCustomLoginPolicy(mgmtreq);
calls.push(from((this.service as ManagementService).updateCustomLoginPolicy(mgmtreq)));
break;
}
case PolicyComponentServiceType.ADMIN:
const adminreq = new UpdateLoginPolicyRequest();
@@ -243,11 +267,21 @@ export class LoginPolicyComponent implements OnInit {
adminreq.setIgnoreUnknownUsernames(this.loginData.ignoreUnknownUsernames);
adminreq.setDefaultRedirectUri(this.loginData.defaultRedirectUri);
return (this.service as AdminService).updateLoginPolicy(adminreq);
calls.push(from((this.service as AdminService).setRestrictions(!this.allowOrgRegistration)));
calls.push(from((this.service as AdminService).updateLoginPolicy(adminreq)));
break;
}
} else {
return Promise.reject();
calls.push(from(Promise.reject()));
}
return firstValueFrom(
forkJoin(calls).pipe(
catchError((error, caught) => {
// We just ignore the policy not changed error!
return (error as { message: string }).message.includes('INSTANCE-5M9vdd') ? of(true) : caught;
}),
),
);
}
public savePolicy(): void {
@@ -6,9 +6,6 @@
[settingsList]="settingsList"
queryParam="id"
>
<ng-container *ngIf="currentSetting === 'general' && serviceType === PolicyComponentServiceType.ADMIN">
<cnsl-general-settings></cnsl-general-settings>
</ng-container>
<ng-container *ngIf="currentSetting === 'complexity'">
<cnsl-password-complexity-policy [serviceType]="serviceType"></cnsl-password-complexity-policy>
</ng-container>
@@ -57,4 +54,7 @@
<ng-container *ngIf="currentSetting === 'privacypolicy'">
<cnsl-privacy-policy [serviceType]="serviceType"></cnsl-privacy-policy>
</ng-container>
<ng-container *ngIf="currentSetting === 'languages' && serviceType === PolicyComponentServiceType.ADMIN">
<cnsl-language-settings></cnsl-language-settings>
</ng-container>
</cnsl-sidenav>
@@ -7,7 +7,7 @@ import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.mod
import { CardModule } from '../card/card.module';
import DomainsModule from '../domains/domains.module';
import { DomainPolicyModule } from '../policies/domain-policy/domain-policy.module';
import { GeneralSettingsModule } from '../policies/general-settings/general-settings.module';
import { LanguageSettingsModule } from '../policies/language-settings/language-settings.module';
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';
@@ -36,7 +36,7 @@ import { SettingsListComponent } from './settings-list.component';
PasswordComplexityPolicyModule,
PasswordLockoutPolicyModule,
PrivateLabelingPolicyModule,
GeneralSettingsModule,
LanguageSettingsModule,
NotificationPolicyModule,
IdpSettingsModule,
PrivacyPolicyModule,
@@ -1,9 +1,9 @@
import { PolicyComponentServiceType } from '../policies/policy-component-types.enum';
import { SidenavSetting } from '../sidenav/sidenav.component';
export const GENERAL: SidenavSetting = {
id: 'general',
i18nKey: 'SETTINGS.LIST.GENERAL',
export const LANGUAGES: SidenavSetting = {
id: 'languages',
i18nKey: 'SETTINGS.LIST.LANGUAGES',
requiredRoles: {
[PolicyComponentServiceType.ADMIN]: ['iam.policy.read'],
},