fix(console-v2): settings permission restriction, u2f naming, asset error handling (#3658)

* fix permission on nav

* restrict settings access

* fido table

* u2f i18n, permission

* factor, image fallback

Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
Max Peintner 2022-05-20 11:23:16 +02:00 committed by GitHub
parent 62c4a4d08d
commit 40d7dba574
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 633 additions and 222 deletions

View File

@ -116,6 +116,11 @@ export class AppComponent implements OnInit, OnDestroy {
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/lightbulb-off-outline.svg'),
);
this.matIconRegistry.addSvgIcon(
'usb',
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/usb-flash-drive-outline.svg'),
);
this.matIconRegistry.addSvgIcon('mdi_radar', this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/radar.svg'));
this.matIconRegistry.addSvgIcon(

View File

@ -1,12 +1,23 @@
<div class="avatar-circle dontcloseonclick" matRipple [matRippleColor]="'#ffffff20'" [matRippleUnbounded]="false"
[ngStyle]="{'height': size+'px', 'width': size+'px', 'fontSize': (fontSize-1)+'px','fontWeight': fontWeight, 'background': (themeService.isDarkTheme | async) ? color[900]: color[300], 'color': (themeService.isDarkTheme | async) ? color[200]:color[900]}"
[ngClass]="{'active': active}">
<ng-container *ngIf="isMachine; else human">
<i class="machine-icon las la-robot"></i>
</ng-container>
<ng-template #human>
<span class="dontcloseonclick">{{credentials}}</span>
<img class="dontcloseonclick" *ngIf="avatarUrl" [src]="avatarUrl"
onerror="this.src='./assets/images/transparent.png';this.onerror='';" />
</ng-template>
</div>
<div
class="avatar-circle dontcloseonclick"
matRipple
[matRippleColor]="'#ffffff20'"
[matRippleUnbounded]="false"
[ngStyle]="{
height: size + 'px',
width: size + 'px',
fontSize: fontSize - 1 + 'px',
fontWeight: fontWeight,
background: (themeService.isDarkTheme | async) ? color[900] : color[300],
color: (themeService.isDarkTheme | async) ? color[200] : color[900]
}"
[ngClass]="{ active: active }"
>
<ng-container *ngIf="isMachine; else human">
<i class="machine-icon las la-robot"></i>
</ng-container>
<ng-template #human>
<span class="dontcloseonclick">{{ credentials }}</span>
<img class="dontcloseonclick" *ngIf="avatarUrl" [src]="avatarUrl" (error)="errorHandler($event)" />
</ng-template>
</div>

View File

@ -52,4 +52,8 @@ export class AvatarComponent implements OnInit {
const toGen = this.forColor || this.name || '';
return getColorHash(toGen);
}
public errorHandler(event: any) {
(event.target as HTMLImageElement).style.display = 'none';
}
}

View File

@ -12,22 +12,63 @@
color="warn"
(click)="removePolicy()"
mat-stroked-button
[disabled]="
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.policy.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'policy.write'
: ''
]
| hasRole
| async) === false
"
>
{{ 'POLICY.RESET' | translate }}
</button>
<!-- </ng-template> -->
<!-- [disabled]="(['domain.policy.write'] | hasRole | async) === false" -->
<div class="content" *ngIf="domainData">
<div class="row">
<mat-checkbox color="primary" name="hasNumber" ngDefaultControl [(ngModel)]="domainData.userLoginMustBeDomain">
<mat-checkbox
color="primary"
name="hasNumber"
ngDefaultControl
[(ngModel)]="domainData.userLoginMustBeDomain"
[disabled]="
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.policy.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'policy.write'
: ''
]
| hasRole
| async) === false
"
>
{{ 'POLICY.DATA.USERLOGINMUSTBEDOMAIN' | translate }}
</mat-checkbox>
</div>
<!-- [disabled]="(['domain.policy.write'] | hasRole | async) === false" -->
<div class="row">
<mat-checkbox color="primary" name="hasNumber" ngDefaultControl [(ngModel)]="domainData.validateOrgDomains">
<mat-checkbox
color="primary"
name="hasNumber"
ngDefaultControl
[(ngModel)]="domainData.validateOrgDomains"
[disabled]="
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.policy.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'policy.write'
: ''
]
| hasRole
| async) === false
"
>
{{ 'POLICY.DATA.VALIDATEORGDOMAINS' | translate }}
</mat-checkbox>
</div>
@ -38,15 +79,41 @@
name="hasNumber"
ngDefaultControl
[(ngModel)]="domainData.smtpSenderAddressMatchesInstanceDomain"
[disabled]="
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.policy.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'policy.write'
: ''
]
| hasRole
| async) === false
"
>
{{ 'POLICY.DATA.SMTPSENDERADDRESSMATCHESINSTANCEDOMAIN' | translate }}
</mat-checkbox>
</div>
</div>
<!-- [disabled]="(['domain.policy.write'] | hasRole | async) === false" -->
<div class="btn-container">
<button (click)="savePolicy()" color="primary" type="submit" mat-raised-button>
<button
(click)="savePolicy()"
color="primary"
type="submit"
mat-raised-button
[disabled]="
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.policy.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'policy.write'
: ''
]
| hasRole
| async) === false
"
>
{{ 'ACTIONS.SAVE' | translate }}
</button>
</div>

View File

@ -1,17 +1,27 @@
<h2>{{ 'SETTING.DEFAULTLANGUAGE' | translate }}</h2>
<div class="spinner-wr">
<mat-spinner diameter="30" *ngIf="loading" color="primary"></mat-spinner>
</div>
<h2>{{ 'SETTING.DEFAULTLANGUAGE' | translate }}</h2>
<cnsl-form-field class="default-language" label="Default Language" required="true">
<cnsl-label>{{ 'SETTING.DEFAULTLANGUAGE' | translate }}</cnsl-label>
<mat-select [(ngModel)]="defaultLanguage">
<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>
<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>

View File

@ -5,6 +5,7 @@ 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';
@ -20,6 +21,7 @@ import { GeneralSettingsComponent } from './general-settings.component';
FormFieldModule,
MatProgressSpinnerModule,
MatSelectModule,
HasRolePipeModule,
TranslateModule,
],
exports: [GeneralSettingsComponent],

View File

@ -0,0 +1,29 @@
<h1 mat-dialog-title class="title">
<span>{{ data.title | translate }}</span>
</h1>
<div mat-dialog-content>
<p class="desc cnsl-secondary-text">{{ data.desc | translate }}</p>
<cnsl-form-field class="form-field" label="Access Code" required="true">
<cnsl-label>{{ 'MFA.TYPE' | translate }}</cnsl-label>
<mat-select [(ngModel)]="newMfaType">
<mat-option *ngFor="let mfa of availableMfaTypes" [value]="mfa">
{{
(data.componentType === LoginMethodComponentType.SecondFactor
? 'MFA.SECONDFACTORTYPES.'
: LoginMethodComponentType.MultiFactor
? 'MFA.MULTIFACTORTYPES.'
: '') + mfa | translate
}}
</mat-option>
</mat-select>
</cnsl-form-field>
</div>
<div mat-dialog-actions class="action">
<button mat-stroked-button (click)="closeDialog()">
<span>{{ 'ACTIONS.CLOSE' | translate }}</span>
</button>
<button [disabled]="false" mat-raised-button class="ok-button" color="primary" (click)="closeDialogWithCode()">
<span>{{ 'ACTIONS.OK' | translate }}</span>
</button>
</div>

View File

@ -16,8 +16,8 @@ export class DialogAddTypeComponent {
public LoginMethodComponentType: any = LoginMethodComponentType;
public availableMfaTypes: Array<MultiFactorType | SecondFactorType> = [];
public newMfaType!: MultiFactorType | SecondFactorType;
constructor(public dialogRef: MatDialogRef<DialogAddTypeComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) {
constructor(public dialogRef: MatDialogRef<DialogAddTypeComponent>, @Inject(MAT_DIALOG_DATA) public data: any) {
this.availableMfaTypes = data.types;
}

View File

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

View File

@ -6,16 +6,16 @@ import { BehaviorSubject, Observable } from 'rxjs';
import { PolicyComponentServiceType } from 'src/app/modules/policies/policy-component-types.enum';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
import {
AddMultiFactorToLoginPolicyRequest as AdminAddMultiFactorToLoginPolicyRequest,
AddSecondFactorToLoginPolicyRequest as AdminAddSecondFactorToLoginPolicyRequest,
RemoveMultiFactorFromLoginPolicyRequest as AdminRemoveMultiFactorFromLoginPolicyRequest,
RemoveSecondFactorFromLoginPolicyRequest as AdminRemoveSecondFactorFromLoginPolicyRequest,
AddMultiFactorToLoginPolicyRequest as AdminAddMultiFactorToLoginPolicyRequest,
AddSecondFactorToLoginPolicyRequest as AdminAddSecondFactorToLoginPolicyRequest,
RemoveMultiFactorFromLoginPolicyRequest as AdminRemoveMultiFactorFromLoginPolicyRequest,
RemoveSecondFactorFromLoginPolicyRequest as AdminRemoveSecondFactorFromLoginPolicyRequest,
} from 'src/app/proto/generated/zitadel/admin_pb';
import {
AddMultiFactorToLoginPolicyRequest as MgmtAddMultiFactorToLoginPolicyRequest,
AddSecondFactorToLoginPolicyRequest as MgmtAddSecondFactorToLoginPolicyRequest,
RemoveMultiFactorFromLoginPolicyRequest as MgmtRemoveMultiFactorFromLoginPolicyRequest,
RemoveSecondFactorFromLoginPolicyRequest as MgmtRemoveSecondFactorFromLoginPolicyRequest,
AddMultiFactorToLoginPolicyRequest as MgmtAddMultiFactorToLoginPolicyRequest,
AddSecondFactorToLoginPolicyRequest as MgmtAddSecondFactorToLoginPolicyRequest,
RemoveMultiFactorFromLoginPolicyRequest as MgmtRemoveMultiFactorFromLoginPolicyRequest,
RemoveSecondFactorFromLoginPolicyRequest as MgmtRemoveSecondFactorFromLoginPolicyRequest,
} from 'src/app/proto/generated/zitadel/management_pb';
import { MultiFactorType, SecondFactorType } from 'src/app/proto/generated/zitadel/policy_pb';
import { AdminService } from 'src/app/services/admin.service';
@ -30,11 +30,11 @@ export enum LoginMethodComponentType {
}
@Component({
selector: 'cnsl-mfa-table',
templateUrl: './mfa-table.component.html',
styleUrls: ['./mfa-table.component.scss'],
selector: 'cnsl-factor-table',
templateUrl: './factor-table.component.html',
styleUrls: ['./factor-table.component.scss'],
})
export class MfaTableComponent implements OnInit {
export class FactorTableComponent implements OnInit {
public LoginMethodComponentType: any = LoginMethodComponentType;
@Input() componentType!: LoginMethodComponentType;
@Input() public serviceType!: PolicyComponentServiceType;
@ -48,7 +48,7 @@ export class MfaTableComponent implements OnInit {
public PolicyComponentServiceType: any = PolicyComponentServiceType;
constructor(public translate: TranslateService, private toast: ToastService, private dialog: MatDialog) { }
constructor(public translate: TranslateService, private toast: ToastService, private dialog: MatDialog) {}
public ngOnInit(): void {
this.getData();
@ -65,7 +65,7 @@ export class MfaTableComponent implements OnInit {
width: '400px',
});
dialogRef.afterClosed().subscribe(resp => {
dialogRef.afterClosed().subscribe((resp) => {
if (resp) {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
if (this.componentType === LoginMethodComponentType.MultiFactor) {
@ -105,7 +105,6 @@ export class MfaTableComponent implements OnInit {
}
public addMfa(): void {
let selection: any[] = [];
if (this.componentType === LoginMethodComponentType.MultiFactor) {
@ -114,8 +113,8 @@ export class MfaTableComponent implements OnInit {
selection = [SecondFactorType.SECOND_FACTOR_TYPE_U2F, SecondFactorType.SECOND_FACTOR_TYPE_OTP];
}
this.mfas.forEach(mfa => {
const index = selection.findIndex(sel => sel === mfa);
this.mfas.forEach((mfa) => {
const index = selection.findIndex((sel) => sel === mfa);
if (index > -1) {
selection.splice(index, 1);
}
@ -137,37 +136,49 @@ export class MfaTableComponent implements OnInit {
if (this.componentType === LoginMethodComponentType.MultiFactor) {
const req = new MgmtAddMultiFactorToLoginPolicyRequest();
req.setType(mfaType as MultiFactorType);
(this.service as ManagementService).addMultiFactorToLoginPolicy(req).then(() => {
this.refreshPageAfterTimout(2000);
}).catch(error => {
this.toast.showError(error);
});
(this.service as ManagementService)
.addMultiFactorToLoginPolicy(req)
.then(() => {
this.refreshPageAfterTimout(2000);
})
.catch((error) => {
this.toast.showError(error);
});
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
const req = new MgmtAddSecondFactorToLoginPolicyRequest();
req.setType(mfaType as SecondFactorType);
(this.service as ManagementService).addSecondFactorToLoginPolicy(req).then(() => {
this.refreshPageAfterTimout(2000);
}).catch(error => {
this.toast.showError(error);
});
(this.service as ManagementService)
.addSecondFactorToLoginPolicy(req)
.then(() => {
this.refreshPageAfterTimout(2000);
})
.catch((error) => {
this.toast.showError(error);
});
}
} else if (this.serviceType === PolicyComponentServiceType.ADMIN) {
if (this.componentType === LoginMethodComponentType.MultiFactor) {
const req = new AdminAddMultiFactorToLoginPolicyRequest();
req.setType(mfaType as MultiFactorType);
(this.service as AdminService).addMultiFactorToLoginPolicy(req).then(() => {
this.refreshPageAfterTimout(2000);
}).catch(error => {
this.toast.showError(error);
});
(this.service as AdminService)
.addMultiFactorToLoginPolicy(req)
.then(() => {
this.refreshPageAfterTimout(2000);
})
.catch((error) => {
this.toast.showError(error);
});
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
const req = new AdminAddSecondFactorToLoginPolicyRequest();
req.setType(mfaType as SecondFactorType);
(this.service as AdminService).addSecondFactorToLoginPolicy(req).then(() => {
this.refreshPageAfterTimout(2000);
}).catch(error => {
this.toast.showError(error);
});
(this.service as AdminService)
.addSecondFactorToLoginPolicy(req)
.then(() => {
this.refreshPageAfterTimout(2000);
})
.catch((error) => {
this.toast.showError(error);
});
}
}
}
@ -179,39 +190,51 @@ export class MfaTableComponent implements OnInit {
if (this.serviceType === PolicyComponentServiceType.MGMT) {
if (this.componentType === LoginMethodComponentType.MultiFactor) {
(this.service as ManagementService).listLoginPolicyMultiFactors().then(resp => {
this.mfas = resp.resultList;
this.loadingSubject.next(false);
}).catch(error => {
this.toast.showError(error);
this.loadingSubject.next(false);
});
(this.service as ManagementService)
.listLoginPolicyMultiFactors()
.then((resp) => {
this.mfas = resp.resultList;
this.loadingSubject.next(false);
})
.catch((error) => {
this.toast.showError(error);
this.loadingSubject.next(false);
});
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
(this.service as ManagementService).listLoginPolicySecondFactors().then(resp => {
this.mfas = resp.resultList;
this.loadingSubject.next(false);
}).catch(error => {
this.toast.showError(error);
this.loadingSubject.next(false);
});
(this.service as ManagementService)
.listLoginPolicySecondFactors()
.then((resp) => {
this.mfas = resp.resultList;
this.loadingSubject.next(false);
})
.catch((error) => {
this.toast.showError(error);
this.loadingSubject.next(false);
});
}
} else if (this.serviceType === PolicyComponentServiceType.ADMIN) {
if (this.componentType === LoginMethodComponentType.MultiFactor) {
(this.service as AdminService).listLoginPolicyMultiFactors().then(resp => {
this.mfas = resp.resultList;
this.loadingSubject.next(false);
}).catch(error => {
this.toast.showError(error);
this.loadingSubject.next(false);
});
(this.service as AdminService)
.listLoginPolicyMultiFactors()
.then((resp) => {
this.mfas = resp.resultList;
this.loadingSubject.next(false);
})
.catch((error) => {
this.toast.showError(error);
this.loadingSubject.next(false);
});
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
(this.service as AdminService).listLoginPolicySecondFactors().then(resp => {
this.mfas = resp.resultList;
this.loadingSubject.next(false);
}).catch(error => {
this.toast.showError(error);
this.loadingSubject.next(false);
});
(this.service as AdminService)
.listLoginPolicySecondFactors()
.then((resp) => {
this.mfas = resp.resultList;
this.loadingSubject.next(false);
})
.catch((error) => {
this.toast.showError(error);
this.loadingSubject.next(false);
});
}
}
}

View File

@ -29,7 +29,20 @@
<div class="login-policy-row" *ngIf="loginData">
<cnsl-form-field class="passwordless-allowed" label="Access Code" required="true">
<cnsl-label>{{ 'LOGINPOLICY.PASSWORDLESS' | translate }}</cnsl-label>
<mat-select [(ngModel)]="loginData.passwordlessType">
<mat-select
[(ngModel)]="loginData.passwordlessType"
[disabled]="
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.policy.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'policy.write'
: ''
]
| hasRole
| async) === false
"
>
<mat-option *ngFor="let pt of passwordlessTypes" [value]="pt">
{{ 'LOGINPOLICY.PASSWORDLESSTYPE.' + pt | translate }}
</mat-option>
@ -38,7 +51,7 @@
</div>
<cnsl-card class="max-card-width">
<cnsl-mfa-table
<cnsl-factor-table
[service]="service"
[serviceType]="serviceType"
[componentType]="LoginMethodComponentType.MultiFactor"
@ -55,7 +68,7 @@
| async) === false
"
>
</cnsl-mfa-table>
</cnsl-factor-table>
</cnsl-card>
<br />
@ -64,12 +77,29 @@
<p class="cnsl-secondary-text">{{ 'MFA.LIST.SECONDFACTORDESCRIPTION' | translate }}</p>
<div *ngIf="loginData" class="login-policy-row">
<mat-checkbox card-actions class="login-policy-toggle" color="primary" ngDefaultControl [(ngModel)]="loginData.forceMfa">
<mat-checkbox
card-actions
class="login-policy-toggle"
color="primary"
ngDefaultControl
[(ngModel)]="loginData.forceMfa"
[disabled]="
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.policy.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'policy.write'
: ''
]
| hasRole
| async) === false
"
>
{{ 'POLICY.DATA.FORCEMFA' | translate }}
</mat-checkbox>
</div>
<cnsl-card class="max-card-width">
<cnsl-mfa-table
<cnsl-factor-table
[service]="service"
[serviceType]="serviceType"
[componentType]="LoginMethodComponentType.SecondFactor"
@ -85,7 +115,7 @@
| async) === false
"
>
</cnsl-mfa-table>
</cnsl-factor-table>
</cnsl-card>
<br />
@ -149,6 +179,17 @@
matTooltip="{{ 'POLICY.DATA.FORCEMFA_DESC' | translate }}"
ngDefaultControl
[(ngModel)]="loginData.allowUsernamePassword"
[disabled]="
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.policy.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'policy.write'
: ''
]
| hasRole
| async) === false
"
>
{{ 'POLICY.DATA.ALLOWUSERNAMEPASSWORD' | translate }}
</mat-checkbox>
@ -158,7 +199,23 @@
</cnsl-info-section> -->
</div>
<div class="login-policy-row">
<mat-checkbox class="login-policy-toggle" color="primary" ngDefaultControl [(ngModel)]="loginData.allowRegister">
<mat-checkbox
class="login-policy-toggle"
color="primary"
ngDefaultControl
[(ngModel)]="loginData.allowRegister"
[disabled]="
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.policy.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'policy.write'
: ''
]
| hasRole
| async) === false
"
>
{{ 'POLICY.DATA.ALLOWREGISTER' | translate }}
</mat-checkbox>
@ -169,7 +226,23 @@
</ng-template> -->
</div>
<div class="login-policy-row">
<mat-checkbox class="login-policy-toggle" color="primary" ngDefaultControl [(ngModel)]="loginData.allowExternalIdp">
<mat-checkbox
class="login-policy-toggle"
color="primary"
ngDefaultControl
[(ngModel)]="loginData.allowExternalIdp"
[disabled]="
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.policy.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'policy.write'
: ''
]
| hasRole
| async) === false
"
>
{{ 'POLICY.DATA.ALLOWEXTERNALIDP' | translate }}
</mat-checkbox>
@ -181,7 +254,23 @@
</div>
<div class="login-policy-row">
<mat-checkbox class="login-policy-toggle" color="primary" ngDefaultControl [(ngModel)]="loginData.hidePasswordReset">
<mat-checkbox
class="login-policy-toggle"
color="primary"
ngDefaultControl
[(ngModel)]="loginData.hidePasswordReset"
[disabled]="
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.policy.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'policy.write'
: ''
]
| hasRole
| async) === false
"
>
{{ 'POLICY.DATA.HIDEPASSWORDRESET' | translate }}
</mat-checkbox>
@ -198,6 +287,17 @@
color="primary"
ngDefaultControl
[(ngModel)]="loginData.ignoreUnknownUsernames"
[disabled]="
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.policy.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'policy.write'
: ''
]
| hasRole
| async) === false
"
>
{{ 'POLICY.DATA.IGNOREUNKNOWNUSERNAMES' | translate }}
</mat-checkbox>
@ -206,7 +306,22 @@
<div class="login-policy-row">
<cnsl-form-field class="form-field" label="Access Code" required="true">
<cnsl-label>{{ 'POLICY.DATA.DEFAULTREDIRECTURI' | translate }}</cnsl-label>
<input cnslInput placeholder="https://" [(ngModel)]="loginData.defaultRedirectUri" />
<input
cnslInput
placeholder="https://"
[(ngModel)]="loginData.defaultRedirectUri"
[disabled]="
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.policy.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'policy.write'
: ''
]
| hasRole
| async) === false
"
/>
</cnsl-form-field>
</div>
</div>
@ -214,7 +329,24 @@
<br />
<div class="login-policy-btn-container">
<button class="login-policy-save-button" (click)="savePolicy()" color="primary" type="submit" mat-raised-button>
<button
class="login-policy-save-button"
(click)="savePolicy()"
color="primary"
type="submit"
mat-raised-button
[disabled]="
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.policy.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'policy.write'
: ''
]
| hasRole
| async) === false
"
>
{{ 'ACTIONS.SAVE' | translate }}
</button>
</div>

View File

@ -1,6 +1,7 @@
import { Component, Injector, Input, OnInit, Type } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Duration } from 'google-protobuf/google/protobuf/duration_pb';
import { take } from 'rxjs';
import {
GetLoginPolicyResponse as AdminGetLoginPolicyResponse,
UpdateLoginPolicyRequest,
@ -12,12 +13,13 @@ import {
} from 'src/app/proto/generated/zitadel/management_pb';
import { LoginPolicy, PasswordlessType } from 'src/app/proto/generated/zitadel/policy_pb';
import { AdminService } from 'src/app/services/admin.service';
import { GrpcAuthService } from 'src/app/services/grpc-auth.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 { PolicyComponentServiceType } from '../policy-component-types.enum';
import { LoginMethodComponentType } from './mfa-table/mfa-table.component';
import { LoginMethodComponentType } from './factor-table/factor-table.component';
@Component({
selector: 'cnsl-login-policy',
@ -40,21 +42,24 @@ export class LoginPolicyComponent implements OnInit {
public InfoSectionType: any = InfoSectionType;
public PasswordlessType: any = PasswordlessType;
public lifetimeForm!: FormGroup;
constructor(private toast: ToastService, private injector: Injector, private fb: FormBuilder) {
constructor(
private toast: ToastService,
private injector: Injector,
private fb: FormBuilder,
private authService: GrpcAuthService,
) {
this.lifetimeForm = this.fb.group({
passwordCheckLifetime: [240, [Validators.required]],
externalLoginCheckLifetime: [12, [Validators.required]],
mfaInitSkipLifetime: [720, [Validators.required]],
secondFactorCheckLifetime: [12, [Validators.required]],
multiFactorCheckLifetime: [12, [Validators.required]],
passwordCheckLifetime: [{ disabled: true, value: 240 }, [Validators.required]],
externalLoginCheckLifetime: [{ disabled: true, value: 12 }, [Validators.required]],
mfaInitSkipLifetime: [{ disabled: true, value: 720 }, [Validators.required]],
secondFactorCheckLifetime: [{ disabled: true, value: 12 }, [Validators.required]],
multiFactorCheckLifetime: [{ disabled: true, value: 12 }, [Validators.required]],
});
}
private fetchData(): void {
this.getData()
.then((resp) => {
console.log(resp);
if (resp.policy) {
this.loginData = resp.policy;
this.loading = false;
@ -107,6 +112,21 @@ export class LoginPolicyComponent implements OnInit {
break;
}
this.fetchData();
this.authService
.isAllowed(
this.serviceType === PolicyComponentServiceType.ADMIN
? ['iam.policy.write']
: this.serviceType === PolicyComponentServiceType.MGMT
? ['policy.write']
: [],
)
.pipe(take(1))
.subscribe((allowed) => {
console.log(allowed);
if (allowed) {
this.lifetimeForm.enable();
}
});
}
private async getData(): Promise<AdminGetLoginPolicyResponse.AsObject | MgmtGetLoginPolicyResponse.AsObject> {

View File

@ -18,13 +18,13 @@ import { InputModule } from 'src/app/modules/input/input.module';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
import { InfoSectionModule } from '../../info-section/info-section.module';
import { DialogAddTypeComponent } from './factor-table/dialog-add-type/dialog-add-type.component';
import { FactorTableComponent } from './factor-table/factor-table.component';
import { LoginPolicyRoutingModule } from './login-policy-routing.module';
import { LoginPolicyComponent } from './login-policy.component';
import { DialogAddTypeComponent } from './mfa-table/dialog-add-type/dialog-add-type.component';
import { MfaTableComponent } from './mfa-table/mfa-table.component';
@NgModule({
declarations: [LoginPolicyComponent, MfaTableComponent, DialogAddTypeComponent],
declarations: [LoginPolicyComponent, FactorTableComponent, DialogAddTypeComponent],
imports: [
LoginPolicyRoutingModule,
CommonModule,

View File

@ -1,20 +0,0 @@
<h1 mat-dialog-title class="title"><span>{{data.title | translate}}</span></h1>
<div mat-dialog-content>
<p class="desc cnsl-secondary-text">{{data.desc | translate}}</p>
<cnsl-form-field class="form-field" label="Access Code" required="true">
<cnsl-label>{{'MFA.TYPE' | translate}}</cnsl-label>
<mat-select [(ngModel)]="newMfaType">
<mat-option *ngFor="let mfa of availableMfaTypes" [value]="mfa">
{{(data.componentType === LoginMethodComponentType.SecondFactor ? 'MFA.SECONDFACTORTYPES.':
LoginMethodComponentType.MultiFactor ? 'MFA.MULTIFACTORTYPES.': '')+mfa | translate}}
</mat-option>
</mat-select>
</cnsl-form-field>
</div>
<div mat-dialog-actions class="action">
<button mat-stroked-button (click)="closeDialog()"><span>{{'ACTIONS.CLOSE' | translate}}</span></button>
<button [disabled]="false" mat-raised-button class="ok-button" color="primary"
(click)="closeDialogWithCode()"><span>{{'ACTIONS.OK' | translate}}</span>
</button>
</div>

View File

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

View File

@ -1,5 +1,10 @@
<h2>{{ 'POLICY.LOGIN_TEXTS.TITLE' | translate }}</h2>
<p class="cnsl-secondary-text">{{ 'POLICY.LOGIN_TEXTS.DESCRIPTION' | translate }}</p>
<div *ngIf="loading" class="spinner-wr">
<mat-spinner diameter="30" color="primary"></mat-spinner>
</div>
<div class="date">
<div>
<p class="newer-title" *ngIf="newerVersionExists">{{ 'POLICY.LOGIN_TEXTS.NEWERVERSIONEXISTS' | translate }}</p>

View File

@ -93,6 +93,7 @@ const REQUESTMAP = {
styleUrls: ['./login-texts.component.scss'],
})
export class LoginTextsComponent implements OnInit, OnDestroy {
public loading: boolean = false;
public currentPolicyChangeDate!: Timestamp.AsObject | undefined;
public newerPolicyChangeDate!: Timestamp.AsObject | undefined;
@ -208,6 +209,7 @@ export class LoginTextsComponent implements OnInit, OnDestroy {
}
public async loadData(): Promise<any> {
this.loading = true;
const reqDefaultInit = REQUESTMAP[this.serviceType].getDefault;
reqDefaultInit.setLanguage(this.locale);
this.getDefaultInitMessageTextMap$ = from(this.getDefaultValues(reqDefaultInit)).pipe(map((m) => m[this.currentSubMap]));
@ -215,12 +217,14 @@ export class LoginTextsComponent implements OnInit, OnDestroy {
const reqCustomInit = REQUESTMAP[this.serviceType].get.setLanguage(this.locale);
return this.getCurrentValues(reqCustomInit)
.then((policy) => {
this.loading = false;
if (policy) {
this.totalCustomPolicy = policy;
this.getCustomInitMessageTextMap$.next(policy[this.currentSubMap]);
}
})
.catch((error) => {
this.loading = false;
this.toast.showError(error);
});
}

View File

@ -1,6 +1,10 @@
<h2>{{ 'POLICY.MESSAGE_TEXTS.TITLE' | translate }}</h2>
<p class="cnsl-secondary-text">{{ 'POLICY.MESSAGE_TEXTS.DESCRIPTION' | translate }}</p>
<div *ngIf="loading" class="spinner-wr">
<mat-spinner diameter="30" color="primary"></mat-spinner>
</div>
<div class="message-texts-top-actions">
<cnsl-form-field class="type">
<cnsl-label>{{ 'POLICY.MESSAGE_TEXTS.TYPE' | translate }}</cnsl-label>

View File

@ -37,7 +37,6 @@ import { MessageCustomText } from 'src/app/proto/generated/zitadel/text_pb';
import { AdminService } from 'src/app/services/admin.service';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { StorageService } from 'src/app/services/storage.service';
import { ToastService } from 'src/app/services/toast.service';
import { InfoSectionType } from '../../info-section/info-section.component';
@ -275,6 +274,7 @@ const REQUESTMAP = {
styleUrls: ['./message-texts.component.scss'],
})
export class MessageTextsComponent implements OnInit, OnDestroy {
public loading: boolean = false;
public getDefaultInitMessageTextMap$: Observable<{ [key: string]: string }> = of({});
public getCustomInitMessageTextMap$: BehaviorSubject<{ [key: string]: string | boolean }> = new BehaviorSubject({}); // boolean because of isDefault
@ -400,7 +400,6 @@ export class MessageTextsComponent implements OnInit, OnDestroy {
private toast: ToastService,
private injector: Injector,
private dialog: MatDialog,
private storageService: StorageService,
) {}
ngOnInit(): void {
@ -493,11 +492,14 @@ export class MessageTextsComponent implements OnInit, OnDestroy {
}
const reqCustomInit = REQUESTMAP[this.serviceType][type].get.setLanguage(this.locale);
this.loading = true;
return this.getCurrentValues(type, reqCustomInit)
?.then((data) => {
this.loading = false;
this.getCustomInitMessageTextMap$.next(data);
})
.catch((error) => {
this.loading = false;
this.toast.showError(error);
});
}

View File

@ -1,9 +1,9 @@
<h2>{{ 'SETTING.SMTP.TITLE' | translate }}</h2>
<div class="spinner-wr">
<mat-spinner diameter="30" *ngIf="smtpLoading || smsProvidersLoading" color="primary"></mat-spinner>
</div>
<h2>{{ 'SETTING.SMTP.TITLE' | translate }}</h2>
<cnsl-info-section
*ngIf="!smtpLoading && !form.valid"
class="info-section-warn"

View File

@ -8,7 +8,7 @@
@include input-theme($theme);
@include cnsl-label-theme($theme);
@include mat.all-component-themes($theme);
// @include mat.all-component-themes($theme);
$primary: map-get($theme, primary);
$primary-color: mat.get-color-from-palette($primary, 500);

View File

@ -70,6 +70,17 @@
mat-raised-button
color="primary"
(click)="activatePolicy()"
[disabled]="
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.policy.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'policy.write'
: ''
]
| hasRole
| async) === false
"
>
{{ 'POLICY.PRIVATELABELING.ACTIVATEPREVIEW' | translate }}
</button>
@ -391,7 +402,18 @@
class="toggle"
color="primary"
ngDefaultControl
[disabled]="view === View.CURRENT"
[disabled]="
view === View.CURRENT ||
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.policy.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'policy.write'
: ''
]
| hasRole
| async) === false
"
[(ngModel)]="view === View.CURRENT ? data.hideLoginNameSuffix : previewData.hideLoginNameSuffix"
(change)="savePolicy()"
>
@ -402,9 +424,20 @@
class="toggle"
color="primary"
ngDefaultControl
[disabled]="view === View.CURRENT"
[(ngModel)]="view === View.CURRENT ? data.disableWatermark : previewData.disableWatermark"
(change)="savePolicy()"
[disabled]="
view === View.CURRENT ||
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.policy.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'policy.write'
: ''
]
| hasRole
| async) === false
"
>
{{ 'POLICY.DATA.DISABLEWATERMARK' | translate }}
</mat-slide-toggle>

View File

@ -12,6 +12,7 @@ import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
import { ColorChromeModule } from 'ngx-color/chrome';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
import { DropzoneModule } from '../../../directives/dropzone/dropzone.module';
import { CardModule } from '../../card/card.module';
@ -42,6 +43,7 @@ import { PrivateLabelingPolicyComponent } from './private-labeling-policy.compon
TranslateModule,
DetailLayoutModule,
DropzoneModule,
HasRolePipeModule,
MatProgressSpinnerModule,
MatExpansionModule,
InfoSectionModule,

View File

@ -1,72 +1,125 @@
import { PolicyComponentServiceType } from '../policies/policy-component-types.enum';
import { SidenavSetting } from '../sidenav/sidenav.component';
export const GENERAL: SidenavSetting = {
id: 'general',
i18nKey: 'SETTINGS.LIST.GENERAL',
requiredRoles: {
[PolicyComponentServiceType.ADMIN]: ['iam.policy.read'],
},
};
export const OIDC: SidenavSetting = {
id: 'oidc',
i18nKey: 'SETTINGS.LIST.OIDC',
requiredRoles: {
[PolicyComponentServiceType.ADMIN]: ['iam.policy.read'],
},
};
export const SECRETS: SidenavSetting = {
id: 'secrets',
i18nKey: 'SETTINGS.LIST.SECRETS',
requiredRoles: {
[PolicyComponentServiceType.ADMIN]: ['iam.policy.read'],
},
};
export const LOGIN: SidenavSetting = {
id: 'login',
i18nKey: 'SETTINGS.LIST.LOGIN',
groupI18nKey: 'SETTINGS.GROUPS.LOGIN',
requiredRoles: {
[PolicyComponentServiceType.MGMT]: ['policy.read'],
[PolicyComponentServiceType.ADMIN]: ['iam.policy.read'],
},
};
export const DOMAIN: SidenavSetting = {
id: 'domain',
i18nKey: 'SETTINGS.LIST.DOMAIN',
groupI18nKey: 'SETTINGS.GROUPS.DOMAIN',
requiredRoles: {
[PolicyComponentServiceType.MGMT]: ['policy.read'],
[PolicyComponentServiceType.ADMIN]: ['iam.policy.read'],
},
};
export const LOCKOUT: SidenavSetting = {
id: 'lockout',
i18nKey: 'SETTINGS.LIST.LOCKOUT',
groupI18nKey: 'SETTINGS.GROUPS.LOGIN',
requiredRoles: {
[PolicyComponentServiceType.MGMT]: ['policy.read'],
[PolicyComponentServiceType.ADMIN]: ['iam.policy.read'],
},
};
export const COMPLEXITY: SidenavSetting = {
id: 'complexity',
i18nKey: 'SETTINGS.LIST.COMPLEXITY',
groupI18nKey: 'SETTINGS.GROUPS.LOGIN',
requiredRoles: {
[PolicyComponentServiceType.MGMT]: ['policy.read'],
[PolicyComponentServiceType.ADMIN]: ['iam.policy.read'],
},
};
export const IDP: SidenavSetting = { id: 'idp', i18nKey: 'SETTINGS.LIST.IDP', groupI18nKey: 'SETTINGS.GROUPS.LOGIN' };
export const IDP: SidenavSetting = {
id: 'idp',
i18nKey: 'SETTINGS.LIST.IDP',
groupI18nKey: 'SETTINGS.GROUPS.LOGIN',
requiredRoles: {
[PolicyComponentServiceType.MGMT]: ['policy.read'],
[PolicyComponentServiceType.ADMIN]: ['iam.policy.read'],
},
};
export const NOTIFICATIONS: SidenavSetting = {
id: 'notifications',
i18nKey: 'SETTINGS.LIST.NOTIFICATIONS',
groupI18nKey: 'SETTINGS.GROUPS.NOTIFICATIONS',
requiredRoles: {
[PolicyComponentServiceType.ADMIN]: ['iam.policy.read'],
},
};
export const MESSAGETEXTS: SidenavSetting = {
id: 'messagetexts',
i18nKey: 'SETTINGS.LIST.MESSAGETEXTS',
groupI18nKey: 'SETTINGS.GROUPS.APPEARANCE',
requiredRoles: {
[PolicyComponentServiceType.MGMT]: ['policy.read'],
[PolicyComponentServiceType.ADMIN]: ['iam.policy.read'],
},
};
export const LOGINTEXTS: SidenavSetting = {
id: 'logintexts',
i18nKey: 'SETTINGS.LIST.LOGINTEXTS',
groupI18nKey: 'SETTINGS.GROUPS.APPEARANCE',
requiredRoles: {
[PolicyComponentServiceType.MGMT]: ['policy.read'],
[PolicyComponentServiceType.ADMIN]: ['iam.policy.read'],
},
};
export const PRIVACYPOLICY: SidenavSetting = {
id: 'privacypolicy',
i18nKey: 'SETTINGS.LIST.PRIVACYPOLICY',
groupI18nKey: 'SETTINGS.GROUPS.OTHER',
requiredRoles: {
[PolicyComponentServiceType.MGMT]: ['policy.read'],
[PolicyComponentServiceType.ADMIN]: ['iam.policy.read'],
},
};
export const BRANDING: SidenavSetting = {
id: 'branding',
i18nKey: 'SETTINGS.LIST.BRANDING',
groupI18nKey: 'SETTINGS.GROUPS.APPEARANCE',
requiredRoles: {
[PolicyComponentServiceType.MGMT]: ['policy.read'],
[PolicyComponentServiceType.ADMIN]: ['iam.policy.read'],
},
};

View File

@ -28,6 +28,11 @@
<button
(click)="value = setting.id"
*ngIf="
!setting.requiredRoles ||
(setting.requiredRoles.mgmt && (setting.requiredRoles.mgmt | hasRole | async)) ||
(setting.requiredRoles.admin && (setting.requiredRoles.admin | hasRole | async))
"
class="sidenav-setting-list-element hide-on-mobile"
[ngClass]="{ active: currentSetting === setting.id, show: currentSetting === undefined }"
>

View File

@ -8,7 +8,10 @@ export interface SidenavSetting {
id: string;
i18nKey: string;
groupI18nKey?: string;
requiredRoles?: { [serviceType in PolicyComponentServiceType]: string[] };
requiredRoles?: {
[PolicyComponentServiceType.MGMT]?: string[];
[PolicyComponentServiceType.ADMIN]?: string[];
};
showWarn?: boolean;
}
@ -26,6 +29,7 @@ export class SidenavComponent implements ControlValueAccessor, OnInit {
@Input() public settingsList: SidenavSetting[] = [];
@Input() public queryParam: string = '';
public PolicyComponentServiceType: any = PolicyComponentServiceType;
constructor(private router: Router, private route: ActivatedRoute) {}
ngOnInit(): void {

View File

@ -4,12 +4,13 @@ import { FormsModule } from '@angular/forms';
import { MatIconModule } from '@angular/material/icon';
import { RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
import { SidenavComponent } from './sidenav.component';
@NgModule({
declarations: [SidenavComponent],
imports: [CommonModule, FormsModule, RouterModule, MatIconModule, TranslateModule],
imports: [CommonModule, FormsModule, RouterModule, HasRolePipeModule, MatIconModule, TranslateModule],
exports: [SidenavComponent],
})
export class SidenavModule {}

View File

@ -35,6 +35,7 @@
(changedLanguage)="changedLanguage($event)"
(changeUsernameClicked)="changeUsername()"
(submitData)="saveProfile($event)"
(avatarChanged)="refreshUser()"
>
</cnsl-detail-form>
</cnsl-card>

View File

@ -38,13 +38,13 @@ export class AuthUserDetailComponent implements OnDestroy {
public refreshChanges$: EventEmitter<void> = new EventEmitter();
public settingsList: SidenavSetting[] = [
{ id: 'general', i18nKey: 'USER.SETTINGS.GENERAL'},
{ id: 'idp', i18nKey: 'USER.SETTINGS.IDP'},
{ id: 'passwordless', i18nKey: 'USER.SETTINGS.PASSWORDLESS'},
{ id: 'mfa', i18nKey: 'USER.SETTINGS.MFA'},
{ id: 'grants', i18nKey: 'USER.SETTINGS.USERGRANTS'},
{ id: 'memberships', i18nKey: 'USER.SETTINGS.MEMBERSHIPS'},
{ id: 'metadata', i18nKey: 'USER.SETTINGS.METADATA'},
{ id: 'general', i18nKey: 'USER.SETTINGS.GENERAL' },
{ id: 'idp', i18nKey: 'USER.SETTINGS.IDP' },
{ id: 'passwordless', i18nKey: 'USER.SETTINGS.PASSWORDLESS' },
{ id: 'mfa', i18nKey: 'USER.SETTINGS.MFA' },
{ id: 'grants', i18nKey: 'USER.SETTINGS.USERGRANTS' },
{ id: 'memberships', i18nKey: 'USER.SETTINGS.MEMBERSHIPS' },
{ id: 'metadata', i18nKey: 'USER.SETTINGS.METADATA' },
];
public currentSetting: string | undefined = this.settingsList[0].id;

View File

@ -22,6 +22,7 @@ export class DetailFormComponent implements OnDestroy, OnChanges {
@Output() public submitData: EventEmitter<Profile.AsObject> = new EventEmitter<Profile.AsObject>();
@Output() public changedLanguage: EventEmitter<string> = new EventEmitter<string>();
@Output() public changeUsernameClicked: EventEmitter<void> = new EventEmitter();
@Output() public avatarChanged: EventEmitter<void> = new EventEmitter();
public profileForm!: FormGroup;
@ -79,8 +80,9 @@ export class DetailFormComponent implements OnDestroy, OnChanges {
width: '400px',
});
dialogRef.afterClosed().subscribe((resp) => {
if (resp) {
dialogRef.afterClosed().subscribe((shouldReload) => {
if (shouldReload) {
this.avatarChanged.emit();
}
});
}

View File

@ -53,6 +53,6 @@ export class ProfilePictureComponent {
}
public closeDialog(): void {
this.dialogRef.close(false);
this.dialogRef.close(true);
}
}

View File

@ -1,5 +1,6 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { lastValueFrom } from 'rxjs';
import { PolicyComponentServiceType } from '../modules/policies/policy-component-types.enum';
import { Theme } from '../modules/policies/private-labeling-policy/private-labeling-policy.component';
@ -80,9 +81,7 @@ export class AssetService {
}
private async getServiceUrl(): Promise<string> {
const url = await this.http
.get('./assets/environment.json')
.toPromise()
const url = await lastValueFrom(this.http.get('./assets/environment.json'))
.then((data: any) => {
if (data && data.api) {
return data.api;

View File

@ -251,6 +251,19 @@ export class GrpcAuthService {
.then((resp) => resp.toObject());
}
public loadMyUser(): void {
from(this.getMyUser())
.pipe(
map((resp) => resp.user),
catchError((_) => {
return of(undefined);
}),
)
.subscribe((user) => {
this.userSubject.next(user);
});
}
public getMyUser(): Promise<GetMyUserResponse.AsObject> {
return this.grpcService.auth.getMyUser(new GetMyUserRequest(), null).then((resp) => resp.toObject());
}

View File

@ -277,8 +277,8 @@
"U2F_NAME": "Authentifikator Name",
"TYPE": {
"0": "Keine MFA definiert",
"1": "OTP",
"2": "U2F"
"1": "One Time Password (OTP)",
"2": "Fingerabdruck, Security Keys, Face ID und andere"
},
"STATE": {
"0": "Kein Status",
@ -324,16 +324,16 @@
"OTP": "OTP (One-Time Password)",
"OTP_DIALOG_TITLE": "OTP hinzufügen",
"OTP_DIALOG_DESCRIPTION": "Scanne den QR-Code mit einer Authenticator App und verifiziere den erhaltenen Code, um OTP zu aktivieren.",
"U2F": "U2F (Universal 2nd Factor)",
"U2F_DIALOG_TITLE": "U2F hinzufügen",
"U2F_DIALOG_DESCRIPTION": "Gib einen Namen für den von dir verwendeten Universellen Multifaktor an.",
"U2F_SUCCESS": "U2F erfolgreich erstellt!",
"U2F": "Fingerabdruck, Security Key, Face ID oder andere",
"U2F_DIALOG_TITLE": "Faktor hinzufügen",
"U2F_DIALOG_DESCRIPTION": "Gib einen Namen für den von dir verwendeten Authentikator an.",
"U2F_SUCCESS": "Faktor erfolgreich hinzugefügt!",
"U2F_ERROR": "Ein Fehler ist aufgetreten!",
"U2F_NAME": "U2F Name",
"U2F_NAME": "Authenticator Name",
"TYPE": {
"0": "Keine MFA definiert",
"1": "OTP",
"2": "U2F"
"2": "Fingerabdruck, Security Key, Face ID oder andere"
},
"STATE": {
"0": "Kein Status",
@ -558,7 +558,7 @@
"PHONEVERIFICATIONSENT": "Bestätigungscode per Telefonnummer gesendet.",
"EMAILVERIFICATIONSENT": "Bestätigungscode per E-Mail gesendet.",
"OTPREMOVED": "OTP entfernt.",
"U2FREMOVED": "U2F entfernt.",
"U2FREMOVED": "Faktor entfernt.",
"PASSWORDLESSREMOVED": "Passwortlos entfernt.",
"INITIALPASSWORDSET": "Initiales Passwort gesetzt.",
"PASSWORDNOTIFICATIONSENT": "Passwortänderung mittgeteilt.",
@ -912,7 +912,7 @@
},
"PRIVATELABELING": {
"TITLE": "Branding",
"DESCRIPTION": "Verleihe dem Login deinen benutzerdefinierten Style und passe das Verhalten an.",
"DESCRIPTION": "Verleihen Sie dem Login Ihren benutzerdefinierten Style und passen Sie das Verhalten an.",
"PREVIEW_DESCRIPTION": "Änderungen dieser Richtlinie werden automatisch in der Preview Umgebung verfügbar.",
"BTN": "Datei auswählen",
"ACTIVATEPREVIEW": "Konfiguration übernehmen",
@ -1495,12 +1495,12 @@
"TYPE": "Typ",
"MULTIFACTORTYPES": {
"0": "Unknown",
"1": "U2F with Pin"
"1": "Fingerabdruck, Security Keys, Face ID und andere"
},
"SECONDFACTORTYPES": {
"0": "Unknown",
"1": "OTP",
"2": "U2F"
"1": "One Time Password (OTP)",
"2": "Fingerabdruck, Security Keys, Face ID und andere"
}
},
"LOGINPOLICY": {

View File

@ -273,12 +273,12 @@
"U2F_DIALOG_TITLE": "Verify authenticator",
"U2F_DIALOG_DESCRIPTION": "Enter a name for your used passwordless Login",
"U2F_SUCCESS": "Passwordless Auth created successfully!",
"U2F_ERROR": "An error during U2F setup occurred!",
"U2F_ERROR": "An error during setup occurred!",
"U2F_NAME": "Authenticator Name",
"TYPE": {
"0": "No MFA defined",
"1": "OTP",
"2": "U2F"
"1": "One Time Password (OTP)",
"2": "Fingerprint, Security Keys, Face ID and other"
},
"STATE": {
"0": "No State",
@ -324,16 +324,16 @@
"OTP": "OTP (One-Time Password)",
"OTP_DIALOG_TITLE": "Add OTP",
"OTP_DIALOG_DESCRIPTION": "Scan the QR code with an authenticator app and enter the code below to verify and activate the OTP method.",
"U2F": "U2F (Universal 2nd Factor)",
"U2F_DIALOG_TITLE": "Verify U2F",
"U2F": "Fingerprint, Security Keys, Face ID and other",
"U2F_DIALOG_TITLE": "Verify Factor",
"U2F_DIALOG_DESCRIPTION": "Enter a name for your used universal Multifactor.",
"U2F_SUCCESS": "U2F created successfully!",
"U2F_ERROR": "An error during U2F setup occurred!",
"U2F_NAME": "U2F Name",
"U2F_SUCCESS": "Factor added successfully!",
"U2F_ERROR": "An error during setup occurred!",
"U2F_NAME": "Authenticator Name",
"TYPE": {
"0": "No MFA defined",
"1": "OTP",
"2": "U2F"
"1": "One Time Password (OTP)",
"2": "Fingerprint, Security Keys, Face ID and other"
},
"STATE": {
"0": "No State",
@ -558,7 +558,7 @@
"PHONEVERIFICATIONSENT": "Phone verification code sent.",
"EMAILVERIFICATIONSENT": "E-mail verification code sent.",
"OTPREMOVED": "OTP removed.",
"U2FREMOVED": "U2F removed.",
"U2FREMOVED": "Factor removed.",
"PASSWORDLESSREMOVED": "Passwordless removed.",
"INITIALPASSWORDSET": "Initial password set.",
"PASSWORDNOTIFICATIONSENT": "Password change notification sent.",
@ -1495,12 +1495,12 @@
"TYPE": "Type",
"MULTIFACTORTYPES": {
"0": "Unknown",
"1": "U2F with Pin"
"1": "Fingerprint, Security Keys, Face ID and other"
},
"SECONDFACTORTYPES": {
"0": "Unknown",
"1": "OTP",
"2": "U2F"
"1": "One Time Password (OTP)",
"2": "Fingerprint, Security Keys, Face ID and other"
}
},
"LOGINPOLICY": {

View File

@ -273,12 +273,12 @@
"U2F_DIALOG_TITLE": "Verifica autenticatore",
"U2F_DIALOG_DESCRIPTION": "Inserisci un nome per il tuo authenticatore o dispositivo usato.",
"U2F_SUCCESS": "Autorizzazione passwordless creata con successo!",
"U2F_ERROR": "Si \u00e8 verificato un errore durante la configurazione di U2F!",
"U2F_ERROR": "Si \u00e8 verificato un errore durante la configurazione!",
"U2F_NAME": "Nome dell'autenticatore",
"TYPE": {
"0": "Nessun MFA definito",
"1": "OTP",
"2": "U2F"
"1": "One Time Password (OTP)",
"2": "Impronta digitale, chiave di sicurezza, Face ID e altri"
},
"STATE": {
"0": "Nessuno Stato",
@ -324,16 +324,16 @@
"OTP": "OTP (One-Time Password)",
"OTP_DIALOG_TITLE": "Aggiungi OTP",
"OTP_DIALOG_DESCRIPTION": "Scansiona il codice QR con un'app di autenticazione e inserisci il codice nel campo sottostante per verificare e attivare il metodo OTP.",
"U2F": "U2F (Universal 2nd Factor)",
"U2F_DIALOG_TITLE": "Verifica U2F",
"U2F": "Impronta digitale, chiave di sicurezza, Face ID e altri",
"U2F_DIALOG_TITLE": "Verifica Fattore",
"U2F_DIALOG_DESCRIPTION": "Inserisci un nome per il tuo methodo o dispositivo.",
"U2F_SUCCESS": "U2F creato con successo!",
"U2F_ERROR": "Si \u00e8 verificato un errore durante la configurazione di U2F!",
"U2F_NAME": "Nome U2F",
"U2F_SUCCESS": "Fattore aggiunto con successo!",
"U2F_ERROR": "Si \u00e8 verificato un errore durante la configurazione!",
"U2F_NAME": "Nome dell'autenticatore",
"TYPE": {
"0": "Nessun altro fattore definito",
"1": "OTP",
"2": "U2F"
"1": "One Time Password (OTP)",
"2": "Impronta digitale, chiave di sicurezza, Face ID e altri"
},
"STATE": {
"0": "Nessuno Stato",
@ -557,8 +557,8 @@
"PHONEVERIFIED": "Telefono verificato con successo.",
"PHONEVERIFICATIONSENT": "Codice di verifica telefonica inviato.",
"EMAILVERIFICATIONSENT": "Codice di verifica e-mail inviato.",
"OTPREMOVED": "OTP rimosso.",
"U2FREMOVED": "U2F rimosso.",
"OTPREMOVED": "One Time Password (OTP) rimosso.",
"U2FREMOVED": "Fattore rimosso.",
"PASSWORDLESSREMOVED": "Rimosso senza password.",
"INITIALPASSWORDSET": "Password iniziale impostata.",
"PASSWORDNOTIFICATIONSENT": "Notifica di cambio password inviata.",
@ -1495,12 +1495,12 @@
"TYPE": "Tipo",
"MULTIFACTORTYPES": {
"0": "Sconosciuto",
"1": "U2F con Pin"
"1": "Impronta digitale, chiave di sicurezza, Face ID e altri"
},
"SECONDFACTORTYPES": {
"0": "Sconosciuto",
"1": "OTP",
"2": "U2F"
"1": "One Time Password (OTP)",
"2": "Impronta digitale, chiave di sicurezza, Face ID e altri"
}
},
"LOGINPOLICY": {

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M8 13C9.66 13 11 14.34 11 16C11 17.66 9.66 19 8 19C6.34 19 5 17.66 5 16C5 14.34 6.34 13 8 13M8 15C7.45 15 7 15.45 7 16C7 16.55 7.45 17 8 17C8.55 17 9 16.55 9 16C9 15.45 8.55 15 8 15M9.77 4.33L10.5 5.08L14.29 1.29C14.47 1.11 14.72 1 15 1C15.28 1 15.53 1.11 15.71 1.29L22.78 8.36L22.78 8.37C22.92 8.54 23 8.76 23 9C23 9.3 22.87 9.57 22.66 9.76L22.66 9.76L18.93 13.5L19.67 14.23L12.95 20.95C11.68 22.22 9.93 23 8 23C4.13 23 1 19.87 1 16C1 14.07 1.78 12.32 3.05 11.05L9.77 4.33M11.54 19.54L16.84 14.23L9.77 7.16L4.46 12.46C3.56 13.37 3 14.62 3 16C3 18.76 5.24 21 8 21C9.38 21 10.63 20.44 11.54 19.54M15.07 4.69L16.5 6.1L15.07 7.5L13.66 6.1L15.07 4.69M17.9 7.5L19.31 8.93L17.9 10.34L16.5 8.93L17.9 7.5M20.59 9L15 3.41L11.93 6.5L17.5 12.08L20.59 9Z" /></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -51,7 +51,7 @@
@import 'src/app/pages/actions/add-action-dialog/add-action-dialog.component';
@import 'src/app/modules/project-role-chip/project-role-chip.component';
@import 'src/app/pages/home/home.component.scss';
@import 'src/app/modules/policies/login-policy/mfa-table/mfa-table.component.scss';
@import 'src/app/modules/policies/login-policy/factor-table/factor-table.component.scss';
@import 'src/app/modules/info-overlay/info-overlay.component.scss';
@import './styles/codemirror.scss';