mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-12 02:54:20 +00:00
feat(console): u2f (#1080)
* fix user table count * grpc ge * move grpc * u2f * add u2f funcs * rm local grpc, u2f dialog * dialog u2f * 2fa button * mfa u2f credentialoptions * decode base64 to bytearray, id, challenge * u2f verify * spinner, remove, attribute col * delete mfa * add forcemfa to policy * add id to remove * fix: add missing remove u2f in management * user mgmt u2f delete, login policy * rm log * show attr in mgmt user mfa * add missing id of mfa * mfa table * multifaktor for admin, org * add secondfactor to gen component * remove circular dependency * lint * revert identity prov * add divider * login policy lint * Update console/src/app/modules/policies/login-policy/login-policy.component.html * Update console/src/app/modules/policies/login-policy/login-policy.component.html Co-authored-by: Maximilian Peintner <csaq7175@uibk.ac.at> Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
parent
cd44213e99
commit
c6fed8ae86
@ -0,0 +1,19 @@
|
||||
<h1 mat-dialog-title class="title"><span>{{data.title | translate}}</span></h1>
|
||||
<div mat-dialog-content>
|
||||
<p class="desc">{{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-button (click)="closeDialog()"><span translate>ACTIONS.CLOSE</span></button>
|
||||
<button mat-raised-button class="ok-button" color="primary" (click)="closeDialogWithCode()"><span
|
||||
translate>ACTIONS.OK</span>
|
||||
</button>
|
||||
</div>
|
@ -0,0 +1,22 @@
|
||||
.title {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 14px;
|
||||
color: var(--grey);
|
||||
}
|
||||
|
||||
.form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.action {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
button {
|
||||
margin-left: .5rem;
|
||||
border-radius: .5rem;
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
|
||||
import { MultiFactorType as AdminMultiFactorType } from 'src/app/proto/generated/admin_pb';
|
||||
import { MultiFactorType as MgmtMultiFactorType } from 'src/app/proto/generated/management_pb';
|
||||
|
||||
enum LoginMethodComponentType {
|
||||
MultiFactor = 1,
|
||||
SecondFactor = 2,
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-dialog-add-type',
|
||||
templateUrl: './dialog-add-type.component.html',
|
||||
styleUrls: ['./dialog-add-type.component.scss'],
|
||||
})
|
||||
export class DialogAddTypeComponent {
|
||||
public LoginMethodComponentType: any = LoginMethodComponentType;
|
||||
public newMfaType!: AdminMultiFactorType | MgmtMultiFactorType;
|
||||
public availableMfaTypes: Array<AdminMultiFactorType | MgmtMultiFactorType> = [];
|
||||
constructor(public dialogRef: MatDialogRef<DialogAddTypeComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any) {
|
||||
this.availableMfaTypes = data.types;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
public closeDialogWithCode(): void {
|
||||
this.dialogRef.close(this.newMfaType);
|
||||
}
|
||||
}
|
15
console/src/app/modules/mfa-table/mfa-table.component.html
Normal file
15
console/src/app/modules/mfa-table/mfa-table.component.html
Normal file
@ -0,0 +1,15 @@
|
||||
<div class="sp_wrapper">
|
||||
<mat-spinner diameter="30" *ngIf="loading$ | async"></mat-spinner>
|
||||
</div>
|
||||
<div class="mfa-list">
|
||||
<div class="mfa" *ngFor="let mfa of mfas">
|
||||
<button [disabled]="disabled" mat-icon-button (click)="removeMfa(mfa)" class="rm">
|
||||
<mat-icon matTooltip="{{'ACTIONS.REMOVE' | translate}}">
|
||||
remove_circle</mat-icon>
|
||||
</button>
|
||||
{{(componentType == LoginMethodComponentType.SecondFactor ? 'MFA.SECONDFACTORTYPES.': LoginMethodComponentType.MultiFactor ? 'MFA.MULTIFACTORTYPES.': '')+mfa | translate}}
|
||||
</div>
|
||||
<div class="mfa" (click)="addMfa()">
|
||||
<mat-icon>add</mat-icon>
|
||||
</div>
|
||||
</div>
|
42
console/src/app/modules/mfa-table/mfa-table.component.scss
Normal file
42
console/src/app/modules/mfa-table/mfa-table.component.scss
Normal file
@ -0,0 +1,42 @@
|
||||
|
||||
.t .sp_wrapper {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mfa-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 -.5rem;
|
||||
|
||||
.mfa {
|
||||
border: 1px solid var(--grey);
|
||||
border-radius: .5rem;
|
||||
display: grid;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: .5rem;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
min-height: 70px;
|
||||
min-width: 150px;
|
||||
|
||||
.rm {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
cursor: pointer;
|
||||
|
||||
&[disabled] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.disabled) {
|
||||
&:hover {
|
||||
background-color: #ffffff10;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
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();
|
||||
});
|
||||
});
|
220
console/src/app/modules/mfa-table/mfa-table.component.ts
Normal file
220
console/src/app/modules/mfa-table/mfa-table.component.ts
Normal file
@ -0,0 +1,220 @@
|
||||
import { Component, Input, OnInit, ViewChild } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatPaginator } from '@angular/material/paginator';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import {
|
||||
MultiFactor as AdminMultiFactor, MultiFactorType as AdminMultiFactorType,
|
||||
SecondFactor as AdminSecondFactor, SecondFactorType as AdminSecondFactorType,
|
||||
} from 'src/app/proto/generated/admin_pb';
|
||||
import {
|
||||
MultiFactor as MgmtMultiFactor, MultiFactorType as MgmtMultiFactorType,
|
||||
SecondFactor as MgmtSecondFactor, SecondFactorType as MgmtSecondFactorType,
|
||||
} from 'src/app/proto/generated/management_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 { PolicyComponentServiceType } from '../policies/policy-component-types.enum';
|
||||
import { WarnDialogComponent } from '../warn-dialog/warn-dialog.component';
|
||||
import { DialogAddTypeComponent } from './dialog-add-type/dialog-add-type.component';
|
||||
|
||||
export enum LoginMethodComponentType {
|
||||
MultiFactor = 1,
|
||||
SecondFactor = 2,
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-mfa-table',
|
||||
templateUrl: './mfa-table.component.html',
|
||||
styleUrls: ['./mfa-table.component.scss'],
|
||||
})
|
||||
export class MfaTableComponent implements OnInit {
|
||||
public LoginMethodComponentType: any = LoginMethodComponentType;
|
||||
@Input() componentType!: LoginMethodComponentType;
|
||||
@Input() public serviceType!: PolicyComponentServiceType;
|
||||
@Input() service!: AdminService | ManagementService;
|
||||
@Input() disabled: boolean = false;
|
||||
@ViewChild(MatPaginator) public paginator!: MatPaginator;
|
||||
public mfas: Array<AdminMultiFactorType | MgmtMultiFactorType | MgmtSecondFactorType | AdminSecondFactorType> = [];
|
||||
|
||||
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
|
||||
|
||||
public PolicyComponentServiceType: any = PolicyComponentServiceType;
|
||||
|
||||
constructor(public translate: TranslateService, private toast: ToastService, private dialog: MatDialog) { }
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.getData();
|
||||
}
|
||||
|
||||
public removeMfa(type: MgmtMultiFactorType | AdminMultiFactorType | MgmtSecondFactorType | AdminSecondFactorType): void {
|
||||
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
||||
data: {
|
||||
confirmKey: 'ACTIONS.DELETE',
|
||||
cancelKey: 'ACTIONS.CANCEL',
|
||||
titleKey: 'MFA.DELETE.TITLE',
|
||||
descriptionKey: 'MFA.DELETE.DESCRIPTION',
|
||||
},
|
||||
width: '400px',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(resp => {
|
||||
if (resp) {
|
||||
if (this.serviceType === PolicyComponentServiceType.MGMT) {
|
||||
if (this.componentType === LoginMethodComponentType.MultiFactor) {
|
||||
const req = new MgmtMultiFactor();
|
||||
req.setMultiFactor(type as MgmtMultiFactorType);
|
||||
(this.service as ManagementService).RemoveMultiFactorFromLoginPolicy(req).then(() => {
|
||||
this.toast.showInfo('MFA.TOAST.DELETED', true);
|
||||
this.refreshPageAfterTimout(2000);
|
||||
});
|
||||
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
|
||||
const req = new MgmtSecondFactor();
|
||||
req.setSecondFactor(type as MgmtSecondFactorType);
|
||||
(this.service as ManagementService).RemoveSecondFactorFromLoginPolicy(req).then(() => {
|
||||
this.toast.showInfo('MFA.TOAST.DELETED', true);
|
||||
this.refreshPageAfterTimout(2000);
|
||||
});
|
||||
}
|
||||
} else if (this.serviceType === PolicyComponentServiceType.ADMIN) {
|
||||
if (this.componentType === LoginMethodComponentType.MultiFactor) {
|
||||
const req = new AdminMultiFactor();
|
||||
req.setMultiFactor(type as AdminMultiFactorType);
|
||||
(this.service as AdminService).RemoveMultiFactorFromDefaultLoginPolicy(req).then(() => {
|
||||
this.toast.showInfo('MFA.TOAST.DELETED', true);
|
||||
this.refreshPageAfterTimout(2000);
|
||||
});
|
||||
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
|
||||
const req = new AdminSecondFactor();
|
||||
req.setSecondFactor(type as AdminSecondFactorType);
|
||||
(this.service as AdminService).RemoveSecondFactorFromDefaultLoginPolicy(req).then(() => {
|
||||
this.toast.showInfo('MFA.TOAST.DELETED', true);
|
||||
this.refreshPageAfterTimout(2000);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public addMfa(): void {
|
||||
|
||||
let selection: any[] = [];
|
||||
|
||||
if (this.componentType === LoginMethodComponentType.MultiFactor) {
|
||||
selection = this.serviceType === PolicyComponentServiceType.MGMT ?
|
||||
[MgmtMultiFactorType.MULTIFACTORTYPE_U2F_WITH_PIN] :
|
||||
this.serviceType === PolicyComponentServiceType.ADMIN ?
|
||||
[AdminMultiFactorType.MULTIFACTORTYPE_U2F_WITH_PIN] :
|
||||
[];
|
||||
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
|
||||
selection = this.serviceType === PolicyComponentServiceType.MGMT ?
|
||||
[MgmtSecondFactorType.SECONDFACTORTYPE_U2F, MgmtSecondFactorType.SECONDFACTORTYPE_U2F] :
|
||||
this.serviceType === PolicyComponentServiceType.ADMIN ?
|
||||
[AdminSecondFactorType.SECONDFACTORTYPE_OTP, AdminSecondFactorType.SECONDFACTORTYPE_U2F] :
|
||||
[];
|
||||
}
|
||||
const dialogRef = this.dialog.open(DialogAddTypeComponent, {
|
||||
data: {
|
||||
title: 'MFA.CREATE.TITLE',
|
||||
desc: 'MFA.CREATE.DESCRIPTION',
|
||||
componentType: this.componentType,
|
||||
types: selection,
|
||||
},
|
||||
width: '400px',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((mfaType: AdminMultiFactorType | MgmtMultiFactorType |
|
||||
AdminSecondFactorType | MgmtSecondFactorType) => {
|
||||
if (mfaType) {
|
||||
if (this.serviceType === PolicyComponentServiceType.MGMT) {
|
||||
if (this.componentType === LoginMethodComponentType.MultiFactor) {
|
||||
const req = new MgmtMultiFactor();
|
||||
req.setMultiFactor(mfaType as MgmtMultiFactorType);
|
||||
(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 MgmtSecondFactor();
|
||||
req.setSecondFactor(mfaType as MgmtSecondFactorType);
|
||||
(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 AdminMultiFactor();
|
||||
req.setMultiFactor(mfaType as AdminMultiFactorType);
|
||||
(this.service as AdminService).addMultiFactorToDefaultLoginPolicy(req).then(() => {
|
||||
this.refreshPageAfterTimout(2000);
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
|
||||
const req = new AdminSecondFactor();
|
||||
req.setSecondFactor(mfaType as AdminSecondFactorType);
|
||||
(this.service as AdminService).AddSecondFactorToDefaultLoginPolicy(req).then(() => {
|
||||
this.refreshPageAfterTimout(2000);
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async getData(): Promise<void> {
|
||||
this.loadingSubject.next(true);
|
||||
|
||||
if (this.serviceType === PolicyComponentServiceType.MGMT) {
|
||||
if (this.componentType === LoginMethodComponentType.MultiFactor) {
|
||||
(this.service as ManagementService).GetLoginPolicyMultiFactors().then(resp => {
|
||||
this.mfas = resp.toObject().multiFactorsList;
|
||||
this.loadingSubject.next(false);
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
this.loadingSubject.next(false);
|
||||
});
|
||||
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
|
||||
(this.service as ManagementService).GetLoginPolicySecondFactors().then(resp => {
|
||||
this.mfas = resp.toObject().secondFactorsList;
|
||||
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).getDefaultLoginPolicyMultiFactors().then(resp => {
|
||||
this.mfas = resp.toObject().multiFactorsList;
|
||||
this.loadingSubject.next(false);
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
this.loadingSubject.next(false);
|
||||
});
|
||||
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
|
||||
(this.service as AdminService).GetDefaultLoginPolicySecondFactors().then(resp => {
|
||||
this.mfas = resp.toObject().secondFactorsList;
|
||||
this.loadingSubject.next(false);
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
this.loadingSubject.next(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public refreshPageAfterTimout(to: number): void {
|
||||
setTimeout(() => {
|
||||
this.getData();
|
||||
}, to);
|
||||
}
|
||||
}
|
44
console/src/app/modules/mfa-table/mfa-table.module.ts
Normal file
44
console/src/app/modules/mfa-table/mfa-table.module.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||
import { RefreshTableModule } from 'src/app/modules/refresh-table/refresh-table.module';
|
||||
import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe/localized-date-pipe.module';
|
||||
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe/timestamp-to-date-pipe.module';
|
||||
import { TruncatePipeModule } from 'src/app/pipes/truncate-pipe/truncate-pipe.module';
|
||||
|
||||
import { MfaTableComponent } from './mfa-table.component';
|
||||
import { DialogAddTypeComponent } from './dialog-add-type/dialog-add-type.component';
|
||||
import { InputModule } from '../input/input.module';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
|
||||
@NgModule({
|
||||
declarations: [MfaTableComponent, DialogAddTypeComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
InputModule,
|
||||
MatSelectModule,
|
||||
MatTooltipModule,
|
||||
TranslateModule,
|
||||
TimestampToDatePipeModule,
|
||||
HasRoleModule,
|
||||
MatProgressSpinnerModule,
|
||||
],
|
||||
exports: [
|
||||
MfaTableComponent,
|
||||
],
|
||||
})
|
||||
export class MfaTableModule { }
|
@ -44,8 +44,34 @@
|
||||
</mat-slide-toggle>
|
||||
<p> {{'POLICY.DATA.ALLOWEXTERNALIDP_DESC' | translate}} </p>
|
||||
</div>
|
||||
<div class="row">
|
||||
<mat-slide-toggle class="toggle" color="primary" [disabled]="disabled" ngDefaultControl
|
||||
[(ngModel)]="loginData.forceMfa">
|
||||
{{'POLICY.DATA.FORCEMFA' | translate}}
|
||||
</mat-slide-toggle>
|
||||
<p> {{'POLICY.DATA.FORCEMFA_DESC' | translate}} </p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button [disabled]="disabled" class="save-button" (click)="savePolicy()" color="primary" type="submit"
|
||||
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<h3 class="subheader">{{ 'MFA.LIST.MULTIFACTORTITLE' | translate }}</h3>
|
||||
<p class="subdesc">{{ 'MFA.LIST.MULTIFACTORDESCRIPTION' | translate }}</p>
|
||||
<app-mfa-table [service]="service" [serviceType]="serviceType"
|
||||
[componentType]="LoginMethodComponentType.MultiFactor"
|
||||
[disabled]="([serviceType == PolicyComponentServiceType.ADMIN ? 'iam.policy.write' : serviceType == PolicyComponentServiceType.MGMT ? 'org.policy.write' : ''] | hasRole | async) == false">
|
||||
</app-mfa-table>
|
||||
|
||||
<h3 class="subheader">{{ 'MFA.LIST.SECONDFACTORTITLE' | translate }}</h3>
|
||||
<p class="subdesc">{{ 'MFA.LIST.SECONDFACTORDESCRIPTION' | translate }}</p>
|
||||
<app-mfa-table [service]="service" [serviceType]="serviceType"
|
||||
[componentType]="LoginMethodComponentType.SecondFactor"
|
||||
[disabled]="([serviceType == PolicyComponentServiceType.ADMIN ? 'iam.policy.write' : serviceType == PolicyComponentServiceType.MGMT ? 'org.policy.write' : ''] | hasRole | async) == false">
|
||||
</app-mfa-table>
|
||||
|
||||
<h3 class="subheader">{{'LOGINPOLICY.IDPS' | translate}}</h3>
|
||||
|
||||
<div class="idps">
|
||||
@ -63,9 +89,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button [disabled]="disabled" class="save-button" (click)="savePolicy()" color="primary" type="submit"
|
||||
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
|
||||
|
||||
<ng-template appHasRole [appHasRole]="['org.idp.read']">
|
||||
<h2>{{ 'IDP.LIST.TITLE' | translate }}</h2>
|
||||
<p>{{ 'IDP.LIST.DESCRIPTION' | translate }}</p>
|
||||
@ -73,4 +96,4 @@
|
||||
[disabled]="([serviceType == PolicyComponentServiceType.ADMIN ? 'iam.idp.write' : serviceType == PolicyComponentServiceType.MGMT ? 'org.idp.write' : ''] | hasRole | async) == false">
|
||||
</app-idp-table>
|
||||
</ng-template>
|
||||
</app-detail-layout>
|
||||
</app-detail-layout>
|
||||
|
@ -37,6 +37,11 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.subdesc {
|
||||
color: var(--grey);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.idps {
|
||||
display: flex;
|
||||
margin: 0 -.5rem;
|
||||
@ -93,3 +98,10 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: var(--grey);
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
import { PolicyComponentServiceType } from '../policy-component-types.enum';
|
||||
import { AddIdpDialogComponent } from './add-idp-dialog/add-idp-dialog.component';
|
||||
import { LoginMethodComponentType } from 'src/app/modules/mfa-table/mfa-table.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login-policy',
|
||||
@ -29,6 +30,7 @@ import { AddIdpDialogComponent } from './add-idp-dialog/add-idp-dialog.component
|
||||
styleUrls: ['./login-policy.component.scss'],
|
||||
})
|
||||
export class LoginPolicyComponent implements OnDestroy {
|
||||
public LoginMethodComponentType: any = LoginMethodComponentType;
|
||||
public loginData!: LoginPolicyView.AsObject | DefaultLoginPolicyView.AsObject;
|
||||
|
||||
private sub: Subscription = new Subscription();
|
||||
@ -112,6 +114,8 @@ export class LoginPolicyComponent implements OnDestroy {
|
||||
mgmtreq.setAllowExternalIdp(this.loginData.allowExternalIdp);
|
||||
mgmtreq.setAllowRegister(this.loginData.allowRegister);
|
||||
mgmtreq.setAllowUsernamePassword(this.loginData.allowUsernamePassword);
|
||||
mgmtreq.setForceMfa(this.loginData.forceMfa);
|
||||
// console.log(mgmtreq.toObject());
|
||||
if ((this.loginData as LoginPolicyView.AsObject).pb_default) {
|
||||
return (this.service as ManagementService).CreateLoginPolicy(mgmtreq);
|
||||
} else {
|
||||
@ -122,6 +126,9 @@ export class LoginPolicyComponent implements OnDestroy {
|
||||
adminreq.setAllowExternalIdp(this.loginData.allowExternalIdp);
|
||||
adminreq.setAllowRegister(this.loginData.allowRegister);
|
||||
adminreq.setAllowUsernamePassword(this.loginData.allowUsernamePassword);
|
||||
adminreq.setForceMfa(this.loginData.forceMfa);
|
||||
// console.log(adminreq.toObject());
|
||||
|
||||
return (this.service as AdminService).UpdateDefaultLoginPolicy(adminreq);
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.mod
|
||||
import { AddIdpDialogModule } from './add-idp-dialog/add-idp-dialog.module';
|
||||
import { LoginPolicyRoutingModule } from './login-policy-routing.module';
|
||||
import { LoginPolicyComponent } from './login-policy.component';
|
||||
import { MfaTableModule } from 'src/app/modules/mfa-table/mfa-table.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [LoginPolicyComponent],
|
||||
@ -36,6 +37,7 @@ import { LoginPolicyComponent } from './login-policy.component';
|
||||
DetailLayoutModule,
|
||||
AddIdpDialogModule,
|
||||
IdpTableModule,
|
||||
MfaTableModule,
|
||||
MatProgressSpinnerModule,
|
||||
],
|
||||
})
|
||||
|
@ -1,11 +1,19 @@
|
||||
<app-card title="{{'USER.MFA.TITLE' | translate}}" description="{{'USER.MFA.DESCRIPTION' | translate}}">
|
||||
<app-refresh-table [loading]="loading$ | async" (refreshed)="getOTP()" [dataSize]="dataSource?.data?.length">
|
||||
<app-refresh-table [loading]="loading$ | async" (refreshed)="getMFAs()" [dataSize]="dataSource?.data?.length">
|
||||
<table class="table" mat-table [dataSource]="dataSource">
|
||||
<ng-container matColumnDef="type">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MFA.TABLETYPE' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let mfa"> {{'USER.MFA.TYPE.'+ mfa.type | translate}} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="attr">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MFA.ATTRIBUTE' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let mfa"><span *ngIf="mfa?.attr" class="centered">
|
||||
{{ mfa.attr }}
|
||||
</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="state">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MFA.TABLESTATE' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let mfa"><span class="centered">
|
||||
@ -20,7 +28,7 @@
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MFA.TABLEACTIONS' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let mfa">
|
||||
<button matTooltip="{{'ACTIONS.REMOVE' | translate}}" color="warn" mat-icon-button
|
||||
(click)="deleteMFA(mfa.type)">
|
||||
(click)="deleteMFA(mfa.type, mfa.id)">
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
@ -35,6 +43,11 @@
|
||||
matTooltip="{{'ACTIONS.NEW' | translate}}">
|
||||
<mat-icon class="icon" svgIcon="mdi_radar"></mat-icon>{{'USER.MFA.OTP' | translate}}
|
||||
</button>
|
||||
<button class="button" *ngIf="otpAvailable" (click)="addU2F()" mat-stroked-button color="primary"
|
||||
matTooltip="{{'ACTIONS.NEW' | translate}}">
|
||||
<i class="las la-fingerprint"></i>
|
||||
{{'USER.MFA.U2F' | translate}}
|
||||
</button>
|
||||
</div>
|
||||
<div class="table-wrapper">
|
||||
<div class="spinner-container" *ngIf="loading$ | async">
|
||||
|
@ -2,6 +2,7 @@
|
||||
.add-row {
|
||||
display: flex;
|
||||
margin: -.5rem;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.button {
|
||||
margin: .5rem;
|
||||
|
@ -4,11 +4,32 @@ import { MatSort } from '@angular/material/sort';
|
||||
import { MatTable, MatTableDataSource } from '@angular/material/table';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
|
||||
import { MfaOtpResponse, MFAState, MfaType, MultiFactor } from 'src/app/proto/generated/auth_pb';
|
||||
import { MfaOtpResponse, MFAState, MfaType, MultiFactor, WebAuthNResponse } from 'src/app/proto/generated/auth_pb';
|
||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
import { DialogOtpComponent } from '../dialog-otp/dialog-otp.component';
|
||||
import { DialogU2FComponent } from '../dialog-u2f/dialog-u2f.component';
|
||||
|
||||
export function _base64ToArrayBuffer(base64: string): any {
|
||||
const binaryString = atob(base64);
|
||||
const len = binaryString.length;
|
||||
const bytes = new Uint8Array(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
return bytes.buffer;
|
||||
}
|
||||
|
||||
export interface WebAuthNOptions {
|
||||
challenge: string;
|
||||
rp: { name: string, id: string; };
|
||||
user: { name: string, id: string, displayName: string; };
|
||||
pubKeyCredParams: any;
|
||||
authenticatorSelection: { userVerification: string; };
|
||||
timeout: number;
|
||||
attestation: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-auth-user-mfa',
|
||||
@ -16,7 +37,7 @@ import { DialogOtpComponent } from '../dialog-otp/dialog-otp.component';
|
||||
styleUrls: ['./auth-user-mfa.component.scss'],
|
||||
})
|
||||
export class AuthUserMfaComponent implements OnInit, OnDestroy {
|
||||
public displayedColumns: string[] = ['type', 'state', 'actions'];
|
||||
public displayedColumns: string[] = ['type', 'attr', 'state', 'actions'];
|
||||
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
|
||||
|
||||
@ -29,10 +50,12 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
|
||||
|
||||
public error: string = '';
|
||||
public otpAvailable: boolean = false;
|
||||
constructor(private service: GrpcAuthService, private toast: ToastService, private dialog: MatDialog) { }
|
||||
constructor(private service: GrpcAuthService,
|
||||
private toast: ToastService,
|
||||
private dialog: MatDialog) { }
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.getOTP();
|
||||
this.getMFAs();
|
||||
}
|
||||
|
||||
public ngOnDestroy(): void {
|
||||
@ -50,7 +73,7 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
|
||||
dialogRef.afterClosed().subscribe((code) => {
|
||||
if (code) {
|
||||
this.service.VerifyMfaOTP(code).then(() => {
|
||||
this.getOTP();
|
||||
this.getMFAs();
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -59,7 +82,40 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
public getOTP(): void {
|
||||
public verifyU2f(): void {
|
||||
|
||||
}
|
||||
|
||||
public addU2F(): void {
|
||||
this.service.AddMyMfaU2F().then((u2fresp) => {
|
||||
const webauthn: WebAuthNResponse.AsObject = u2fresp.toObject();
|
||||
const credOptions: CredentialCreationOptions = JSON.parse(atob(webauthn.publicKey as string));
|
||||
|
||||
if (credOptions.publicKey?.challenge) {
|
||||
credOptions.publicKey.challenge = _base64ToArrayBuffer(credOptions.publicKey.challenge as any);
|
||||
credOptions.publicKey.user.id = _base64ToArrayBuffer(credOptions.publicKey.user.id as any);
|
||||
const dialogRef = this.dialog.open(DialogU2FComponent, {
|
||||
width: '400px',
|
||||
data: {
|
||||
credOptions,
|
||||
},
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(done => {
|
||||
if (done) {
|
||||
this.getMFAs();
|
||||
} else {
|
||||
this.getMFAs();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}, error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
public getMFAs(): void {
|
||||
this.service.GetMyMfas().then(mfas => {
|
||||
this.dataSource = new MatTableDataSource(mfas.toObject().mfasList);
|
||||
this.dataSource.sort = this.sort;
|
||||
@ -73,13 +129,13 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
public deleteMFA(type: MfaType): void {
|
||||
public deleteMFA(type: MfaType, id?: string): void {
|
||||
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
||||
data: {
|
||||
confirmKey: 'ACTIONS.DELETE',
|
||||
cancelKey: 'ACTIONS.CANCEL',
|
||||
titleKey: 'USER.MFA.DIALOG.OTP_DELETE_TITLE',
|
||||
descriptionKey: 'USER.MFA.DIALOG.OTP_DELETE_DESCRIPTION',
|
||||
titleKey: 'USER.MFA.DIALOG.MFA_DELETE_TITLE',
|
||||
descriptionKey: 'USER.MFA.DIALOG.MFA_DELETE_DESCRIPTION',
|
||||
},
|
||||
width: '400px',
|
||||
});
|
||||
@ -94,7 +150,19 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
|
||||
if (index > -1) {
|
||||
this.dataSource.data.splice(index, 1);
|
||||
}
|
||||
this.getOTP();
|
||||
this.getMFAs();
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
} else if (type === MfaType.MFATYPE_U2F && id) {
|
||||
this.service.RemoveMyMfaU2F(id).then(() => {
|
||||
this.toast.showInfo('USER.TOAST.U2FREMOVED', true);
|
||||
|
||||
const index = this.dataSource.data.findIndex(mfa => mfa.type === type);
|
||||
if (index > -1) {
|
||||
this.dataSource.data.splice(index, 1);
|
||||
}
|
||||
this.getMFAs();
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
@ -102,4 +170,6 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
<h1 mat-dialog-title>{{'USER.MFA.OTP_DIALOG_TITLE' | translate}}</h1>
|
||||
<div mat-dialog-content>
|
||||
<p translate>{{'USER.MFA.OTP_DIALOG_DESCRIPTION' | translate}}</p>
|
||||
<p>{{'USER.MFA.OTP_DIALOG_DESCRIPTION' | translate}}</p>
|
||||
<div class="qrcode-wrapper">
|
||||
<qrcode *ngIf="data" class="qrcode" [qrdata]="data" [width]="150" [errorCorrectionLevel]="'M'"></qrcode>
|
||||
</div>
|
||||
@ -15,4 +15,4 @@
|
||||
<button mat-raised-button class="ok-button" color="primary" (click)="closeDialogWithCode()"><span
|
||||
translate>ACTIONS.OK</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -0,0 +1,18 @@
|
||||
<h1 mat-dialog-title>{{'USER.MFA.U2F_DIALOG_TITLE' | translate}}</h1>
|
||||
<div mat-dialog-content>
|
||||
<p>{{'USER.MFA.U2F_DIALOG_DESCRIPTION' | translate}}</p>
|
||||
|
||||
<cnsl-form-field class="form-field" label="Name" required="true">
|
||||
<cnsl-label>{{'USER.MFA.U2F_NAME' | translate}}</cnsl-label>
|
||||
<input cnslInput [(ngModel)]="name" required/>
|
||||
</cnsl-form-field>
|
||||
|
||||
<mat-spinner diameter="30" *ngIf="loading"></mat-spinner>
|
||||
|
||||
<p class="error">{{error}}</p>
|
||||
</div>
|
||||
<div mat-dialog-actions class="action">
|
||||
<button mat-button (click)="closeDialog()">{{'ACTIONS.CLOSE' | translate}}</button>
|
||||
<button [disabled]="!name" mat-raised-button class="ok-button" color="primary" (click)="closeDialogWithCode()">{{'ACTIONS.VERIFY' | translate}}
|
||||
</button>
|
||||
</div>
|
@ -0,0 +1,27 @@
|
||||
.qrcode-wrapper {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
|
||||
.qrcode {
|
||||
margin: 1rem auto;
|
||||
}
|
||||
}
|
||||
|
||||
.form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #f44336;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.action {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
button {
|
||||
margin-left: .5rem;
|
||||
border-radius: .5rem;
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
export function _arrayBufferToBase64(buffer: any): string {
|
||||
let binary = '';
|
||||
const bytes = new Uint8Array(buffer);
|
||||
const len = bytes.byteLength;
|
||||
for (let i = 0; i < len; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return btoa(binary).replace(/\+/g, '-')
|
||||
.replace(/\//g, '_')
|
||||
.replace(/=/g, '');
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-dialog-u2f',
|
||||
templateUrl: './dialog-u2f.component.html',
|
||||
styleUrls: ['./dialog-u2f.component.scss'],
|
||||
})
|
||||
export class DialogU2FComponent {
|
||||
public name: string = '';
|
||||
public error: string = '';
|
||||
public loading: boolean = false;
|
||||
|
||||
constructor(public dialogRef: MatDialogRef<DialogU2FComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: { credOptions: any; },
|
||||
private service: GrpcAuthService, private translate: TranslateService, private toast: ToastService) { }
|
||||
|
||||
public closeDialog(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
public closeDialogWithCode(): void {
|
||||
this.error = '';
|
||||
this.loading = true;
|
||||
if (this.name && this.data.credOptions.publicKey) {
|
||||
// this.data.credOptions.publicKey.rp.id = 'localhost';
|
||||
navigator.credentials.create(this.data.credOptions).then((resp) => {
|
||||
if (resp &&
|
||||
(resp as any).response.attestationObject &&
|
||||
(resp as any).response.clientDataJSON &&
|
||||
(resp as any).rawId) {
|
||||
|
||||
const attestationObject = (resp as any).response.attestationObject;
|
||||
const clientDataJSON = (resp as any).response.clientDataJSON;
|
||||
const rawId = (resp as any).rawId;
|
||||
|
||||
const data = JSON.stringify({
|
||||
id: resp.id,
|
||||
rawId: _arrayBufferToBase64(rawId),
|
||||
type: resp.type,
|
||||
response: {
|
||||
attestationObject: _arrayBufferToBase64(attestationObject),
|
||||
clientDataJSON: _arrayBufferToBase64(clientDataJSON),
|
||||
},
|
||||
});
|
||||
|
||||
const base64 = btoa(data);
|
||||
this.service.VerifyMyMfaU2F(base64, this.name).then(() => {
|
||||
this.translate.get('USER.MFA.U2F_SUCCESS').pipe(take(1)).subscribe(msg => {
|
||||
this.toast.showInfo(msg);
|
||||
});
|
||||
this.dialogRef.close(true);
|
||||
this.loading = false;
|
||||
}).catch(error => {
|
||||
this.loading = false;
|
||||
this.toast.showError(error);
|
||||
});
|
||||
} else {
|
||||
this.loading = false;
|
||||
this.translate.get('USER.MFA.U2F_ERROR').pipe(take(1)).subscribe(msg => {
|
||||
this.toast.showInfo(msg);
|
||||
});
|
||||
this.dialogRef.close(true);
|
||||
}
|
||||
}).catch(error => {
|
||||
this.loading = false;
|
||||
this.error = error;
|
||||
this.toast.showInfo(error.message);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -33,6 +33,7 @@ import { AuthUserDetailComponent } from './auth-user-detail/auth-user-detail.com
|
||||
import { AuthUserMfaComponent } from './auth-user-detail/auth-user-mfa/auth-user-mfa.component';
|
||||
import { CodeDialogComponent } from './auth-user-detail/code-dialog/code-dialog.component';
|
||||
import { DialogOtpComponent } from './auth-user-detail/dialog-otp/dialog-otp.component';
|
||||
import { DialogU2FComponent } from './auth-user-detail/dialog-u2f/dialog-u2f.component';
|
||||
import { EditDialogComponent } from './auth-user-detail/edit-dialog/edit-dialog.component';
|
||||
import { ResendEmailDialogComponent } from './auth-user-detail/resend-email-dialog/resend-email-dialog.component';
|
||||
import { ThemeSettingComponent } from './auth-user-detail/theme-setting/theme-setting.component';
|
||||
@ -65,6 +66,7 @@ import { UserMfaComponent } from './user-detail/user-mfa/user-mfa.component';
|
||||
ExternalIdpsComponent,
|
||||
ContactComponent,
|
||||
ResendEmailDialogComponent,
|
||||
DialogU2FComponent,
|
||||
],
|
||||
imports: [
|
||||
UserDetailRoutingModule,
|
||||
|
@ -1,11 +1,19 @@
|
||||
<app-card title="{{'USER.MFA.TITLE' | translate}}" description="{{'USER.MFA.DESCRIPTION' | translate}}">
|
||||
<app-refresh-table [loading]="loading$ | async" (refreshed)="getOTP()" [dataSize]="dataSource?.data?.length">
|
||||
<app-refresh-table [loading]="loading$ | async" (refreshed)="getMFAs()" [dataSize]="dataSource?.data?.length">
|
||||
<table class="table" mat-table [dataSource]="dataSource">
|
||||
<ng-container matColumnDef="type">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MFA.TABLETYPE' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let mfa"> {{'USER.MFA.TYPE.'+ mfa.type | translate}} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="attr">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MFA.ATTRIBUTE' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let mfa"><span *ngIf="mfa?.attr" class="centered">
|
||||
{{ mfa.attr }}
|
||||
</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="state">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MFA.TABLESTATE' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let mfa">
|
||||
@ -21,7 +29,7 @@
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'USER.MFA.TABLEACTIONS' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let mfa">
|
||||
<button matTooltip="{{'ACTIONS.REMOVE' | translate}}" color="warn" mat-icon-button
|
||||
(click)="deleteMFA(mfa.type)">
|
||||
(click)="deleteMFA(mfa.type, mfa.id)">
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
|
@ -20,7 +20,7 @@ export interface MFAItem {
|
||||
styleUrls: ['./user-mfa.component.scss'],
|
||||
})
|
||||
export class UserMfaComponent implements OnInit, OnDestroy {
|
||||
public displayedColumns: string[] = ['type', 'state', 'actions'];
|
||||
public displayedColumns: string[] = ['type', 'attr', 'state', 'actions'];
|
||||
@Input() private user!: UserView.AsObject;
|
||||
public mfaSubject: BehaviorSubject<UserMultiFactor.AsObject[]> = new BehaviorSubject<UserMultiFactor.AsObject[]>([]);
|
||||
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
@ -37,7 +37,7 @@ export class UserMfaComponent implements OnInit, OnDestroy {
|
||||
constructor(private mgmtUserService: ManagementService, private dialog: MatDialog, private toast: ToastService) { }
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.getOTP();
|
||||
this.getMFAs();
|
||||
}
|
||||
|
||||
public ngOnDestroy(): void {
|
||||
@ -45,7 +45,7 @@ export class UserMfaComponent implements OnInit, OnDestroy {
|
||||
this.loadingSubject.complete();
|
||||
}
|
||||
|
||||
public getOTP(): void {
|
||||
public getMFAs(): void {
|
||||
this.mgmtUserService.getUserMfas(this.user.id).then(mfas => {
|
||||
this.dataSource = new MatTableDataSource(mfas.toObject().mfasList);
|
||||
this.dataSource.sort = this.sort;
|
||||
@ -54,13 +54,14 @@ export class UserMfaComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
public deleteMFA(type: MfaType): void {
|
||||
public deleteMFA(type: MfaType, id?: string): void {
|
||||
console.log(type, id);
|
||||
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
||||
data: {
|
||||
confirmKey: 'ACTIONS.DELETE',
|
||||
cancelKey: 'ACTIONS.CANCEL',
|
||||
titleKey: 'USER.MFA.DIALOG.OTP_DELETE_TITLE',
|
||||
descriptionKey: 'USER.MFA.DIALOG.OTP_DELETE_DESCRIPTION',
|
||||
titleKey: 'USER.MFA.DIALOG.MFA_DELETE_TITLE',
|
||||
descriptionKey: 'USER.MFA.DIALOG.MFA_DELETE_DESCRIPTION',
|
||||
},
|
||||
width: '400px',
|
||||
});
|
||||
@ -75,7 +76,19 @@ export class UserMfaComponent implements OnInit, OnDestroy {
|
||||
if (index > -1) {
|
||||
this.dataSource.data.splice(index, 1);
|
||||
}
|
||||
this.getOTP();
|
||||
this.getMFAs();
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
} else if (type === MfaType.MFATYPE_U2F && id) {
|
||||
this.mgmtUserService.RemoveMfaU2F(id).then(() => {
|
||||
this.toast.showInfo('USER.TOAST.U2FREMOVED', true);
|
||||
|
||||
const index = this.dataSource.data.findIndex(mfa => mfa.type === type);
|
||||
if (index > -1) {
|
||||
this.dataSource.data.splice(index, 1);
|
||||
}
|
||||
this.getMFAs();
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
<app-refresh-table [loading]="loading$ | async" (refreshed)="refreshPage()" [dataSize]="dataSource.data.length"
|
||||
<app-refresh-table [loading]="loading$ | async" (refreshed)="refreshPage()" [dataSize]="userResult?.totalResult"
|
||||
[timestamp]="userResult?.viewTimestamp" [selection]="selection"
|
||||
[emitRefreshOnPreviousRoutes]="refreshOnPreviousRoutes">
|
||||
<cnsl-form-field @appearfade *ngIf="userSearchKey != undefined" actions class="filtername">
|
||||
|
@ -36,6 +36,8 @@ import {
|
||||
IdpSearchRequest,
|
||||
IdpSearchResponse,
|
||||
IdpView,
|
||||
MultiFactor,
|
||||
MultiFactorsResult,
|
||||
OidcIdpConfig,
|
||||
OidcIdpConfigCreate,
|
||||
OidcIdpConfigUpdate,
|
||||
@ -46,6 +48,8 @@ import {
|
||||
OrgSetUpRequest,
|
||||
OrgSetUpResponse,
|
||||
RemoveIamMemberRequest,
|
||||
SecondFactor,
|
||||
SecondFactorsResult,
|
||||
ViewID,
|
||||
Views,
|
||||
} from '../proto/generated/admin_pb';
|
||||
@ -73,6 +77,32 @@ export class AdminService {
|
||||
return this.grpcService.admin.setUpOrg(req);
|
||||
}
|
||||
|
||||
public getDefaultLoginPolicyMultiFactors(): Promise<MultiFactorsResult> {
|
||||
const req = new Empty();
|
||||
return this.grpcService.admin.getDefaultLoginPolicyMultiFactors(req);
|
||||
}
|
||||
|
||||
public addMultiFactorToDefaultLoginPolicy(req: MultiFactor): Promise<MultiFactor> {
|
||||
return this.grpcService.admin.addMultiFactorToDefaultLoginPolicy(req);
|
||||
}
|
||||
|
||||
public RemoveMultiFactorFromDefaultLoginPolicy(req: MultiFactor): Promise<Empty> {
|
||||
return this.grpcService.admin.removeMultiFactorFromDefaultLoginPolicy(req);
|
||||
}
|
||||
|
||||
public GetDefaultLoginPolicySecondFactors(): Promise<SecondFactorsResult> {
|
||||
const req = new Empty();
|
||||
return this.grpcService.admin.getDefaultLoginPolicySecondFactors(req);
|
||||
}
|
||||
|
||||
public AddSecondFactorToDefaultLoginPolicy(req: SecondFactor): Promise<SecondFactor> {
|
||||
return this.grpcService.admin.addSecondFactorToDefaultLoginPolicy(req);
|
||||
}
|
||||
|
||||
public RemoveSecondFactorFromDefaultLoginPolicy(req: SecondFactor): Promise<Empty> {
|
||||
return this.grpcService.admin.removeSecondFactorFromDefaultLoginPolicy(req);
|
||||
}
|
||||
|
||||
public GetIamMemberRoles(): Promise<IamMemberRoles> {
|
||||
const req = new Empty();
|
||||
return this.grpcService.admin.getIamMemberRoles(req);
|
||||
|
@ -5,34 +5,37 @@ import { BehaviorSubject, from, merge, Observable, of, Subject } from 'rxjs';
|
||||
import { catchError, filter, finalize, first, map, mergeMap, switchMap, take, timeout } from 'rxjs/operators';
|
||||
|
||||
import {
|
||||
Changes,
|
||||
ChangesRequest,
|
||||
ExternalIDPRemoveRequest,
|
||||
ExternalIDPSearchRequest,
|
||||
ExternalIDPSearchResponse,
|
||||
Gender,
|
||||
MfaOtpResponse,
|
||||
MultiFactors,
|
||||
MyPermissions,
|
||||
MyProjectOrgSearchQuery,
|
||||
MyProjectOrgSearchRequest,
|
||||
MyProjectOrgSearchResponse,
|
||||
Org,
|
||||
PasswordChange,
|
||||
PasswordComplexityPolicy,
|
||||
UpdateUserAddressRequest,
|
||||
UpdateUserEmailRequest,
|
||||
UpdateUserPhoneRequest,
|
||||
UpdateUserProfileRequest,
|
||||
UserAddress,
|
||||
UserEmail,
|
||||
UserPhone,
|
||||
UserProfile,
|
||||
UserProfileView,
|
||||
UserSessionViews,
|
||||
UserView,
|
||||
VerifyMfaOtp,
|
||||
VerifyUserPhoneRequest,
|
||||
Changes,
|
||||
ChangesRequest,
|
||||
ExternalIDPRemoveRequest,
|
||||
ExternalIDPSearchRequest,
|
||||
ExternalIDPSearchResponse,
|
||||
Gender,
|
||||
MfaOtpResponse,
|
||||
MultiFactors,
|
||||
MyPermissions,
|
||||
MyProjectOrgSearchQuery,
|
||||
MyProjectOrgSearchRequest,
|
||||
MyProjectOrgSearchResponse,
|
||||
Org,
|
||||
PasswordChange,
|
||||
PasswordComplexityPolicy,
|
||||
UpdateUserAddressRequest,
|
||||
UpdateUserEmailRequest,
|
||||
UpdateUserPhoneRequest,
|
||||
UpdateUserProfileRequest,
|
||||
UserAddress,
|
||||
UserEmail,
|
||||
UserPhone,
|
||||
UserProfile,
|
||||
UserProfileView,
|
||||
UserSessionViews,
|
||||
UserView,
|
||||
VerifyMfaOtp,
|
||||
VerifyUserPhoneRequest,
|
||||
VerifyWebAuthN,
|
||||
WebAuthNResponse,
|
||||
WebAuthNTokenID,
|
||||
} from '../proto/generated/auth_pb';
|
||||
import { GrpcService } from './grpc.service';
|
||||
import { StorageKey, StorageService } from './storage.service';
|
||||
@ -328,9 +331,31 @@ export class GrpcAuthService {
|
||||
);
|
||||
}
|
||||
|
||||
public AddMyMfaU2F(): Promise<WebAuthNResponse> {
|
||||
return this.grpcService.auth.addMyMfaU2F(
|
||||
new Empty(),
|
||||
);
|
||||
}
|
||||
|
||||
public RemoveMyMfaU2F(id: string): Promise<Empty> {
|
||||
const req = new WebAuthNTokenID();
|
||||
req.setId(id);
|
||||
return this.grpcService.auth.removeMyMfaU2F(req);
|
||||
}
|
||||
|
||||
public VerifyMyMfaU2F(credential: string, tokenname: string): Promise<Empty> {
|
||||
const req = new VerifyWebAuthN();
|
||||
req.setPublicKeyCredential(credential);
|
||||
req.setTokenName(tokenname);
|
||||
|
||||
return this.grpcService.auth.verifyMyMfaU2F(
|
||||
req,
|
||||
);
|
||||
}
|
||||
|
||||
public RemoveMfaOTP(): Promise<Empty> {
|
||||
return this.grpcService.auth.removeMfaOTP(
|
||||
new Empty(),
|
||||
new Empty(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
|
||||
import { Empty } from 'google-protobuf/google/protobuf/empty_pb';
|
||||
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { MultiFactorsResult } from '../proto/generated/admin_pb';
|
||||
|
||||
import {
|
||||
AddMachineKeyRequest,
|
||||
@ -50,6 +51,7 @@ import {
|
||||
MachineKeySearchResponse,
|
||||
MachineKeyType,
|
||||
MachineResponse,
|
||||
MultiFactor,
|
||||
NotificationType,
|
||||
OIDCApplicationCreate,
|
||||
OIDCConfig,
|
||||
@ -122,6 +124,8 @@ import {
|
||||
ProjectView,
|
||||
RemoveOrgDomainRequest,
|
||||
RemoveOrgMemberRequest,
|
||||
SecondFactor,
|
||||
SecondFactorsResult,
|
||||
SetPasswordNotificationRequest,
|
||||
UpdateMachineRequest,
|
||||
UpdateUserAddressRequest,
|
||||
@ -152,6 +156,7 @@ import {
|
||||
UserSearchResponse,
|
||||
UserView,
|
||||
ValidateOrgDomainRequest,
|
||||
WebAuthNTokenID,
|
||||
ZitadelDocs,
|
||||
} from '../proto/generated/management_pb';
|
||||
import { GrpcService } from './grpc.service';
|
||||
@ -185,6 +190,32 @@ export class ManagementService {
|
||||
return this.grpcService.mgmt.searchIdps(req);
|
||||
}
|
||||
|
||||
public GetLoginPolicyMultiFactors(): Promise<MultiFactorsResult> {
|
||||
const req = new Empty();
|
||||
return this.grpcService.mgmt.getLoginPolicyMultiFactors(req);
|
||||
}
|
||||
|
||||
public AddMultiFactorToLoginPolicy(req: MultiFactor): Promise<MultiFactor> {
|
||||
return this.grpcService.mgmt.addMultiFactorToLoginPolicy(req);
|
||||
}
|
||||
|
||||
public RemoveMultiFactorFromLoginPolicy(req: MultiFactor): Promise<Empty> {
|
||||
return this.grpcService.mgmt.removeMultiFactorFromLoginPolicy(req);
|
||||
}
|
||||
|
||||
public GetLoginPolicySecondFactors(): Promise<SecondFactorsResult> {
|
||||
const req = new Empty();
|
||||
return this.grpcService.mgmt.getLoginPolicySecondFactors(req);
|
||||
}
|
||||
|
||||
public AddSecondFactorToLoginPolicy(req: SecondFactor): Promise<SecondFactor> {
|
||||
return this.grpcService.mgmt.addSecondFactorToLoginPolicy(req);
|
||||
}
|
||||
|
||||
public RemoveSecondFactorFromLoginPolicy(req: SecondFactor): Promise<Empty> {
|
||||
return this.grpcService.mgmt.removeSecondFactorFromLoginPolicy(req);
|
||||
}
|
||||
|
||||
public GetLoginPolicy(): Promise<LoginPolicyView> {
|
||||
const req = new Empty();
|
||||
return this.grpcService.mgmt.getLoginPolicy(req);
|
||||
@ -683,6 +714,12 @@ export class ManagementService {
|
||||
return this.grpcService.mgmt.removeMfaOTP(req);
|
||||
}
|
||||
|
||||
public RemoveMfaU2F(id: string): Promise<Empty> {
|
||||
const req = new WebAuthNTokenID();
|
||||
req.setId(id);
|
||||
return this.grpcService.mgmt.removeMfaU2F(req);
|
||||
}
|
||||
|
||||
public SaveUserProfile(
|
||||
id: string,
|
||||
firstName?: string,
|
||||
|
@ -155,6 +155,7 @@
|
||||
"MFA": {
|
||||
"TABLETYPE":"Typ",
|
||||
"TABLESTATE":"Status",
|
||||
"ATTRIBUTE":"Attribut",
|
||||
"TABLEACTIONS":"Aktionen",
|
||||
"TITLE": "Multifaktor-Authentisierung",
|
||||
"DESCRIPTION": "Füge einen zusätzlichen Faktor hinzu, um Dein Konto optimal zu schützen.",
|
||||
@ -162,6 +163,12 @@
|
||||
"OTP": "OTP konfigurieren",
|
||||
"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 hinzufügen",
|
||||
"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_ERROR":"Ein Fehler ist aufgetreten!",
|
||||
"U2F_NAME":"U2F Name",
|
||||
"TYPE": {
|
||||
"0":"Keine MFA definiert",
|
||||
"1":"OTP",
|
||||
@ -174,8 +181,8 @@
|
||||
"3": "Gelöscht"
|
||||
},
|
||||
"DIALOG": {
|
||||
"OTP_DELETE_TITLE":"OTP Faktor entfernen",
|
||||
"OTP_DELETE_DESCRIPTION":"Sie sind im Begriff OTP als Zweitfaktormethode zu entfernen. Sind sie sicher?"
|
||||
"MFA_DELETE_TITLE":"Zweiten Faktor entfernen",
|
||||
"MFA_DELETE_DESCRIPTION":"Sie sind im Begriff eine Zweitfaktormethode zu entfernen. Sind sie sicher?"
|
||||
}
|
||||
},
|
||||
"EXTERNALIDP": {
|
||||
@ -345,6 +352,7 @@
|
||||
"PHONEVERIFICATIONSENT":"Bestätigungscode per Telefonnummer gesendet.",
|
||||
"EMAILVERIFICATIONSENT":"Bestätigungscode per E-Mail gesendet.",
|
||||
"OTPREMOVED":"OTP entfernt.",
|
||||
"U2FREMOVED":"U2F entfernt.",
|
||||
"INITIALPASSWORDSET":"Initiales Passwort gesetzt.",
|
||||
"PASSWORDNOTIFICATIONSENT":"Passwortänderung mittgeteilt.",
|
||||
"PASSWORDCHANGED":"Passwort geändert.",
|
||||
@ -551,7 +559,9 @@
|
||||
"ALLOWREGISTER":"Registrieren erlaubt",
|
||||
"ALLOWUSERNAMEPASSWORD_DESC":"Der konventionelle Login mit Benutzername und Passwort wird erlaubt.",
|
||||
"ALLOWEXTERNALIDP_DESC":"Der Login wird für die darunter liegenden Identity Provider erlaubt.",
|
||||
"ALLOWREGISTER_DESC":"Ist die Option gewählt, erscheint im Login ein zusätzlicher Schritt zum Registrieren eines Benutzers."
|
||||
"ALLOWREGISTER_DESC":"Ist die Option gewählt, erscheint im Login ein zusätzlicher Schritt zum Registrieren eines Benutzers.",
|
||||
"FORCEMFA":"Mfa erzwingen",
|
||||
"FORCEMFA_DESC":"Ist die Option gewählt, müssen Benutzer einen zweiten Faktor für den Login verwenden."
|
||||
},
|
||||
"RESET":"Richtlinie zurücksetzen",
|
||||
"CREATECUSTOM":"Benutzerdefinierte Richtlinie erstellen",
|
||||
@ -763,7 +773,7 @@
|
||||
},
|
||||
"IDP":{
|
||||
"LIST": {
|
||||
"TITLE":"Identitäts Providers",
|
||||
"TITLE":"Identitäts Provider",
|
||||
"DESCRIPTION":"Definieren Sie hier Ihre zusätzlichen Idps, die sie für die Authentifizierung in Ihren Organisationen verwenden können."
|
||||
},
|
||||
"CREATE": {
|
||||
@ -826,6 +836,37 @@
|
||||
"DELETED":"Idp erfolgreich gelöscht!"
|
||||
}
|
||||
},
|
||||
"MFA":{
|
||||
"LIST": {
|
||||
"MULTIFACTORTITLE":"Multifaktoren",
|
||||
"MULTIFACTORDESCRIPTION":"Definieren Sie hier Ihre Multifaktoren, die sie für die Authentifizierung verwenden können.",
|
||||
"SECONDFACTORTITLE":"Zweitfaktoren",
|
||||
"SECONDFACTORDESCRIPTION":"Definieren Sie hier Ihre Zweitfaktoren, die sie für die Authentifizierung verwenden können."
|
||||
},
|
||||
"CREATE": {
|
||||
"TITLE":"Neuer Faktor",
|
||||
"DESCRIPTION":"Definieren Sie hier den gewünschten Typ."
|
||||
},
|
||||
"DELETE": {
|
||||
"TITLE":"Faktor löschen",
|
||||
"DESCRIPTION":"Sie sind im Begriff einen Faktor zu löschen. Die daruch hervorgerufenen Änderungen sind unwiederruflich. Wollen Sie dies wirklich tun?"
|
||||
},
|
||||
"TOAST": {
|
||||
"ADDED":"Erfolgreich hinzugefügt.",
|
||||
"SAVED": "Erfolgreich gespeichert.",
|
||||
"DELETED":"Mfa erfolgreich gelöscht!"
|
||||
},
|
||||
"TYPE":"Typ",
|
||||
"MULTIFACTORTYPES": {
|
||||
"0":"Unknown",
|
||||
"1":"U2F with Pin"
|
||||
},
|
||||
"SECONDFACTORTYPES": {
|
||||
"0":"Unknown",
|
||||
"1":"OTP",
|
||||
"2":"U2F"
|
||||
}
|
||||
},
|
||||
"LOGINPOLICY": {
|
||||
"CREATE": {
|
||||
"TITLE":"Login Policy",
|
||||
|
@ -155,6 +155,7 @@
|
||||
"MFA": {
|
||||
"TABLETYPE":"Type",
|
||||
"TABLESTATE":"Status",
|
||||
"ATTRIBUTE":"Attribut",
|
||||
"TABLEACTIONS":"Actions",
|
||||
"TITLE": "Multifactor Authentication",
|
||||
"DESCRIPTION": "Add a second factor to ensure optimal security for your account.",
|
||||
@ -162,6 +163,12 @@
|
||||
"OTP": "Configure OTP",
|
||||
"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":"Add U2F",
|
||||
"U2F_DIALOG_TITLE": "Verify U2F",
|
||||
"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",
|
||||
"TYPE": {
|
||||
"0": "No MFA defined",
|
||||
"1": "OTP",
|
||||
@ -174,8 +181,8 @@
|
||||
"3": "Deleted"
|
||||
},
|
||||
"DIALOG": {
|
||||
"OTP_DELETE_TITLE":"Remove OTP Factor",
|
||||
"OTP_DELETE_DESCRIPTION":"You are about to delete OTP as second factor. Are you sure?"
|
||||
"MFA_DELETE_TITLE":"Remove Secondfactor",
|
||||
"MFA_DELETE_DESCRIPTION":"You are about to delete a second factor. Are you sure?"
|
||||
}
|
||||
},
|
||||
"EXTERNALIDP": {
|
||||
@ -345,6 +352,7 @@
|
||||
"PHONEVERIFICATIONSENT":"Phone verification code sent.",
|
||||
"EMAILVERIFICATIONSENT":"E-mail verification code sent.",
|
||||
"OTPREMOVED":"OTP removed.",
|
||||
"U2FREMOVED":"U2F removed.",
|
||||
"INITIALPASSWORDSET":"Initial password set.",
|
||||
"PASSWORDNOTIFICATIONSENT":"Password change notification sent.",
|
||||
"PASSWORDCHANGED":"Password changed successfully.",
|
||||
@ -551,7 +559,9 @@
|
||||
"ALLOWREGISTER":"Register allowed",
|
||||
"ALLOWUSERNAMEPASSWORD_DESC":"The conventional login with user name and password is allowed.",
|
||||
"ALLOWEXTERNALIDP_DESC":"The login is allowed for the underlying identity providers",
|
||||
"ALLOWREGISTER_DESC":"If the option is selected, an additional step for registering a user appears in the login."
|
||||
"ALLOWREGISTER_DESC":"If the option is selected, an additional step for registering a user appears in the login.",
|
||||
"FORCEMFA":"Force MFA",
|
||||
"FORCEMFA_DESC":"If the option is selected, users have to configure a second factor for login."
|
||||
},
|
||||
"RESET":"Reset Policy",
|
||||
"CREATECUSTOM":"Create Custom Policy",
|
||||
@ -826,6 +836,37 @@
|
||||
"DELETED":"Idp removed successfully!"
|
||||
}
|
||||
},
|
||||
"MFA":{
|
||||
"LIST": {
|
||||
"MULTIFACTORTITLE":"Multifactors",
|
||||
"MULTIFACTORDESCRIPTION":"Define your Multifactors for Authentication here.",
|
||||
"SECONDFACTORTITLE":"Secondfactors",
|
||||
"SECONDFACTORDESCRIPTION":"Define your Secondfactors for Authentication here."
|
||||
},
|
||||
"CREATE": {
|
||||
"TITLE":"New Factor",
|
||||
"DESCRIPTION":"Select your new Factor type."
|
||||
},
|
||||
"DELETE": {
|
||||
"TITLE":"Delete Factor",
|
||||
"DESCRIPTION":"You are about to delete a Factor from Login Policy. Are you sure?"
|
||||
},
|
||||
"TOAST": {
|
||||
"ADDED":"Added successfully.",
|
||||
"SAVED": "Saved successfully.",
|
||||
"DELETED":"Removed successfully"
|
||||
},
|
||||
"TYPE":"Type",
|
||||
"MULTIFACTORTYPES": {
|
||||
"0":"Unknown",
|
||||
"1":"U2F with Pin"
|
||||
},
|
||||
"SECONDFACTORTYPES": {
|
||||
"0":"Unknown",
|
||||
"1":"OTP",
|
||||
"2":"U2F"
|
||||
}
|
||||
},
|
||||
"LOGINPOLICY": {
|
||||
"CREATE": {
|
||||
"TITLE":"Login Policy",
|
||||
|
@ -226,6 +226,11 @@ func (s *Server) RemoveMfaOTP(ctx context.Context, userID *management.UserID) (*
|
||||
return &empty.Empty{}, err
|
||||
}
|
||||
|
||||
func (s *Server) RemoveMfaU2F(ctx context.Context, webAuthNTokenID *management.WebAuthNTokenID) (*empty.Empty, error) {
|
||||
err := s.user.RemoveU2F(ctx, webAuthNTokenID.UserId, webAuthNTokenID.Id)
|
||||
return &empty.Empty{}, err
|
||||
}
|
||||
|
||||
func (s *Server) SearchUserMemberships(ctx context.Context, in *management.UserMembershipSearchRequest) (*management.UserMembershipSearchResponse, error) {
|
||||
request := userMembershipSearchRequestsToModel(in)
|
||||
request.AppendUserIDQuery(in.UserId)
|
||||
|
@ -2,14 +2,15 @@ package management
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/caos/logging"
|
||||
"github.com/caos/zitadel/internal/model"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"golang.org/x/text/language"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
"github.com/caos/zitadel/internal/model"
|
||||
usr_model "github.com/caos/zitadel/internal/user/model"
|
||||
"github.com/caos/zitadel/pkg/grpc/management"
|
||||
"github.com/caos/zitadel/pkg/grpc/message"
|
||||
@ -504,6 +505,7 @@ func mfaFromModel(mfa *usr_model.MultiFactor) *management.UserMultiFactor {
|
||||
State: mfaStateFromModel(mfa.State),
|
||||
Type: mfaTypeFromModel(mfa.Type),
|
||||
Attribute: mfa.Attribute,
|
||||
Id: mfa.ID,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -231,6 +231,10 @@ func (repo *UserRepo) RemoveOTP(ctx context.Context, userID string) error {
|
||||
return repo.UserEvents.RemoveOTP(ctx, userID)
|
||||
}
|
||||
|
||||
func (repo *UserRepo) RemoveU2F(ctx context.Context, userID, webAuthNTokenID string) error {
|
||||
return repo.UserEvents.RemoveU2FToken(ctx, userID, webAuthNTokenID)
|
||||
}
|
||||
|
||||
func (repo *UserRepo) SetOneTimePassword(ctx context.Context, password *usr_model.Password) (*usr_model.Password, error) {
|
||||
policy, err := repo.View.PasswordComplexityPolicyByAggregateID(authz.GetCtxData(ctx).OrgID)
|
||||
if err != nil && caos_errs.IsNotFound(err) {
|
||||
|
@ -32,6 +32,7 @@ type UserRepository interface {
|
||||
|
||||
UserMFAs(ctx context.Context, userID string) ([]*model.MultiFactor, error)
|
||||
RemoveOTP(ctx context.Context, userID string) error
|
||||
RemoveU2F(ctx context.Context, userID, webAuthNTokenID string) error
|
||||
|
||||
SearchExternalIDPs(ctx context.Context, request *model.ExternalIDPSearchRequest) (*model.ExternalIDPSearchResponse, error)
|
||||
RemoveExternalIDP(ctx context.Context, externalIDP *model.ExternalIDP) error
|
||||
|
@ -399,6 +399,16 @@ service ManagementService {
|
||||
};
|
||||
}
|
||||
|
||||
rpc RemoveMfaU2F(WebAuthNTokenID) returns (google.protobuf.Empty) {
|
||||
option (google.api.http) = {
|
||||
delete: "/users/{user_id}/mfas/u2f/{id}"
|
||||
};
|
||||
|
||||
option (caos.zitadel.utils.v1.auth_option) = {
|
||||
permission: "user.write"
|
||||
};
|
||||
}
|
||||
|
||||
// Sends an Notification (Email/SMS) with a password reset Link
|
||||
rpc SendSetPasswordNotification(SetPasswordNotificationRequest) returns (google.protobuf.Empty) {
|
||||
option (google.api.http) = {
|
||||
@ -1646,6 +1656,11 @@ message UserID {
|
||||
string id = 1 [(validate.rules).string.min_len = 1];
|
||||
}
|
||||
|
||||
message WebAuthNTokenID {
|
||||
string user_id = 1 [(validate.rules).string.min_len = 1];
|
||||
string id = 2 [(validate.rules).string.min_len = 1];
|
||||
}
|
||||
|
||||
message LoginName {
|
||||
string login_name = 1 [(validate.rules).string.min_len = 1];
|
||||
}
|
||||
@ -2030,6 +2045,7 @@ message UserMultiFactor {
|
||||
MfaType type = 1;
|
||||
MFAState state = 2;
|
||||
string attribute = 3;
|
||||
string id = 4;
|
||||
}
|
||||
|
||||
enum MfaType {
|
||||
|
Loading…
Reference in New Issue
Block a user