feat(console): add otp sms and otp email as factor (#6343)

* cli, core

* material cdk

* schematics

* chore(deps-dev): bump eslint from 8.40.0 to 8.44.0 in /console (#6127)

Bumps [eslint](https://github.com/eslint/eslint) from 8.40.0 to 8.44.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.40.0...v8.44.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* npm

* feat: add otp sms button

* lock

* rm package-lock

* prompt to setup sms otp on verify

* cli, core

* material, cdk

* schematics

* otp email

* show type

* show type on mgmt page

* disable selection on add

* rename totp

* fix totp i18n

* directly use data, fix styles

* sms req

* fix type check

* fix delete

* rm comment

---------

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Livio Spring <[email protected]>
This commit is contained in:
Max Peintner
2023-08-16 04:37:16 +00:00
committed by GitHub
co-authored by dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Livio Spring
parent 26b28ed2af
commit a262595fc2
31 changed files with 3641 additions and 2547 deletions
@@ -0,0 +1,34 @@
<span class="title" mat-dialog-title>{{ data.titleKey | translate : data.titleParam }}</span>
<div mat-dialog-content>
<div class="icon-wrapper" *ngIf="data.icon">
<i class="icon {{ data.icon }}"></i>
</div>
<p class="desc cnsl-secondary-text">{{ data.descriptionKey | translate : data.descriptionParam }}</p>
<cnsl-info-section *ngIf="data.infoSectionKey" [type]="InfoSectionType.INFO">{{
data.warnSectionKey | translate
}}</cnsl-info-section>
<p *ngIf="data.hintKey" class="desc cnsl-secondary-text">{{ data.hintKey | translate : { value: data.confirmation } }}</p>
<cnsl-form-field *ngIf="data.confirmation && data.confirmationKey" class="formfield">
<cnsl-label>{{ data.confirmationKey | translate : { value: data.confirmation } }}</cnsl-label>
<input cnslInput [(ngModel)]="confirm" data-e2e="confirm-dialog-input" />
</cnsl-form-field>
</div>
<div mat-dialog-actions class="action">
<button *ngIf="data.cancelKey" mat-stroked-button (click)="closeDialog()">
{{ data.cancelKey | translate }}
</button>
<span class="fill-space"></span>
<button
color="primary"
[disabled]="data.confirmation && confirm !== data.confirmation"
mat-raised-button
class="ok-button"
(click)="closeDialogWithSuccess()"
data-e2e="confirm-dialog-button"
>
{{ data.confirmKey | translate }}
</button>
</div>
@@ -0,0 +1,36 @@
.title {
font-size: 1.2rem;
margin-top: 0;
}
.icon-wrapper {
display: flex;
justify-content: center;
padding: 1rem;
margin: 1rem;
align-items: center;
.icon {
font-size: 3rem;
}
}
.desc {
font-size: 0.9rem;
}
.action {
display: flex;
button {
border-radius: 0.5rem;
}
.ok-button {
margin-left: 0.5rem;
}
.fill-space {
flex: 1;
}
}
@@ -0,0 +1,24 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { InfoDialogComponent } from './info-dialog.component';
describe('InfoDialogComponent', () => {
let component: InfoDialogComponent;
let fixture: ComponentFixture<InfoDialogComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [InfoDialogComponent],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(InfoDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,26 @@
import { Component, Inject } from '@angular/core';
import {
MatLegacyDialogRef as MatDialogRef,
MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA,
} from '@angular/material/legacy-dialog';
import { InfoSectionType } from '../info-section/info-section.component';
@Component({
selector: 'cnsl-info-dialog',
templateUrl: './info-dialog.component.html',
styleUrls: ['./info-dialog.component.scss'],
})
export class InfoDialogComponent {
public confirm: string = '';
InfoSectionType: any = InfoSectionType;
constructor(public dialogRef: MatDialogRef<InfoDialogComponent>, @Inject(MAT_DIALOG_DATA) public data: any) {}
public closeDialog(): void {
this.dialogRef.close(false);
}
public closeDialogWithSuccess(): void {
this.dialogRef.close(true);
}
}
@@ -0,0 +1,15 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { TranslateModule } from '@ngx-translate/core';
import { InfoSectionModule } from '../info-section/info-section.module';
import { InputModule } from '../input/input.module';
import { InfoDialogComponent } from './info-dialog.component';
@NgModule({
declarations: [InfoDialogComponent],
imports: [CommonModule, FormsModule, TranslateModule, InfoSectionModule, MatButtonModule, InputModule],
})
export class InfoDialogModule {}
@@ -31,7 +31,6 @@
.info-section-content {
flex: 1;
padding: 0.25rem 0;
}
&.info {
@@ -7,27 +7,130 @@
<div class="type-selection">
<button
mat-raised-button
color="primary"
mat-stroked-button
[disabled]="data.otpDisabled$ | async"
(click)="selectType(AuthFactorType.OTP)"
data-e2e="add-factor-otp"
>
<div class="otp-btn">
<mat-icon class="icon" svgIcon="mdi_radar"></mat-icon>
<div class="icon-row">
<svg
class="authenticator-logo"
version="1.1"
baseProfile="basic"
id="Layer_1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 512 512"
xml:space="preserve"
>
<path
fill="#1A73E8"
d="M440,255.99997v0.00006C440,273.12085,426.12085,287,409.00003,287H302l-46-93.01001l49.6507-85.9951
c8.56021-14.82629,27.51834-19.9065,42.34518-11.34724l0.00586,0.0034c14.82776,8.55979,19.90875,27.51928,11.34857,42.34682
L309.70001,225h99.30002C426.12085,225,440,238.87917,440,255.99997z"
/>
<path
fill="#EA4335"
d="M348.00174,415.34897l-0.00586,0.00339c-14.82684,8.55927-33.78497,3.47903-42.34518-11.34723L256,318.01001
l-49.65065,85.99509c-8.5602,14.82629-27.51834,19.90652-42.34517,11.34729l-0.00591-0.00342
c-14.82777-8.55978-19.90875-27.51929-11.34859-42.34683L202.29999,287L256,285l53.70001,2l49.6503,86.00214
C367.91049,387.82968,362.8295,406.78918,348.00174,415.34897z"
/>
<path
fill="#FBBC04"
d="M256,193.98999L242,232l-39.70001-7l-49.6503-86.00212
c-8.56017-14.82755-3.47919-33.78705,11.34859-42.34684l0.00591-0.00341c14.82683-8.55925,33.78497-3.47903,42.34517,11.34726
L256,193.98999z"
/>
<path
fill="#34A853"
d="M248,225l-36,62H102.99997C85.87916,287,72,273.12085,72,256.00003v-0.00006
C72,238.87917,85.87916,225,102.99997,225H248z"
/>
<polygon fill="#185DB7" points="309.70001,287 202.29999,287 256,193.98999 " />
</svg>
</div>
<span>{{ 'USER.MFA.OTP' | translate }}</span>
</div>
</button>
<button mat-raised-button color="primary" (click)="selectType(AuthFactorType.U2F)">
<button mat-stroked-button (click)="selectType(AuthFactorType.U2F)">
<div class="u2f-btn">
<div class="icon-row">
<i matTooltip="Fingerprint" class="las la-fingerprint"></i>
<i matTooltip="Security Key" class="lab la-usb"></i>
<mat-icon matTooltip="NFC">nfc</mat-icon>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="fingerprint"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M7.864 4.243A7.5 7.5 0 0119.5 10.5c0 2.92-.556 5.709-1.568 8.268M5.742 6.364A7.465 7.465 0 004.5 10.5a7.464 7.464 0 01-1.15 3.993m1.989 3.559A11.209 11.209 0 008.25 10.5a3.75 3.75 0 117.5 0c0 .527-.021 1.049-.064 1.565M12 10.5a14.94 14.94 0 01-3.6 9.75m6.633-4.596a18.666 18.666 0 01-2.485 5.33"
/>
</svg>
</div>
<span>{{ 'USER.MFA.U2F' | translate }}</span>
</div>
</button>
<button
[disabled]="!data.phoneVerified || (data.otpSmsDisabled$ | async)"
mat-stroked-button
(click)="selectType(AuthFactorType.OTPSMS)"
>
<div class="otp-btn">
<div class="col">
<div class="row">
<div class="icon-row">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="sms"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M10.5 1.5H8.25A2.25 2.25 0 006 3.75v16.5a2.25 2.25 0 002.25 2.25h7.5A2.25 2.25 0 0018 20.25V3.75a2.25 2.25 0 00-2.25-2.25H13.5m-3 0V3h3V1.5m-3 0h3m-3 18.75h3"
/>
</svg>
</div>
<span>{{ 'USER.MFA.OTPSMS' | translate }}</span>
</div>
<cnsl-info-section class="info" [type]="InfoSectionType.ALERT" *ngIf="!data.phoneVerified"
><span>{{ 'USER.MFA.OTPSMSPHONEMUSTBEVERIFIED' | translate }}</span></cnsl-info-section
>
</div>
</div>
</button>
<button [disabled]="data.otpEmailDisabled$ | async" mat-stroked-button (click)="selectType(AuthFactorType.OTPEMAIL)">
<div class="otp-btn">
<div class="icon-row">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="email"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75"
/>
</svg>
</div>
<span>{{ 'USER.MFA.OTPEMAIL' | translate }}</span>
</div>
</button>
</div>
</ng-container>
@@ -1,21 +1,67 @@
.desc {
margin-top: 0;
}
.type-selection {
display: flex;
flex-direction: column;
margin: 0 -0.5rem;
.otp-btn,
.u2f-btn {
flex: 1;
min-height: 100px;
border-radius: 0.5rem;
display: flex;
flex-direction: column;
justify-content: center;
flex-direction: row;
align-items: center;
margin: 1rem;
box-sizing: border-box;
white-space: normal;
line-height: 1.5rem;
padding: 0.5rem 0;
.col {
display: flex;
flex-direction: column;
.info {
margin-left: calc(40px + 1rem);
}
}
.row {
display: flex;
flex-direction: row;
align-items: center;
}
.authenticator-logo {
height: 40px;
width: 40px;
}
* {
white-space: normal;
}
.icon-row {
height: 40px;
width: 40px;
display: flex;
align-items: center;
margin-right: 1rem;
.sms,
.email,
.fingerprint {
height: 100%;
width: 100%;
padding: 0.25rem;
}
}
span {
text-align: start;
flex: 1;
}
}
@@ -8,12 +8,15 @@ import { take } from 'rxjs/operators';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ToastService } from 'src/app/services/toast.service';
import { InfoSectionType } from 'src/app/modules/info-section/info-section.component';
import { _base64ToArrayBuffer } from '../../u2f-util';
import { _arrayBufferToBase64 } from '../u2f_util';
export enum AuthFactorType {
OTP,
U2F,
OTPSMS,
OTPEMAIL,
}
@Component({
@@ -32,10 +35,13 @@ export class AuthFactorDialogComponent {
public u2fLoading: boolean = false;
public u2fError: string = '';
public phoneVerified: boolean = false;
AuthFactorType: any = AuthFactorType;
selectedType!: AuthFactorType;
public copied: string = '';
public InfoSectionType: any = InfoSectionType;
constructor(
private authService: GrpcAuthService,
private toast: ToastService,
@@ -52,18 +58,19 @@ export class AuthFactorDialogComponent {
this.selectedType = type;
if (type === AuthFactorType.OTP) {
this.authService.addMyMultiFactorOTP().then(
(otpresp) => {
this.authService
.addMyMultiFactorOTP()
.then((otpresp) => {
this.otpurl = otpresp.url;
this.otpsecret = otpresp.secret;
},
(error) => {
})
.catch((error) => {
this.toast.showError(error);
},
);
});
} else if (type === AuthFactorType.U2F) {
this.authService.addMyMultiFactorU2F().then(
(u2fresp) => {
this.authService
.addMyMultiFactorU2F()
.then((u2fresp) => {
if (u2fresp.key) {
const credOptions: CredentialCreationOptions = JSON.parse(atob(u2fresp.key?.publicKey as string));
@@ -79,11 +86,42 @@ export class AuthFactorDialogComponent {
this.u2fCredentialOptions = credOptions;
}
}
},
(error) => {
})
.catch((error) => {
this.toast.showError(error);
},
);
});
} else if (type === AuthFactorType.OTPSMS) {
this.authService
.addMyAuthFactorOTPSMS()
.then(() => {
this.dialogRef.close(true);
this.translate
.get('USER.MFA.OTPSMSSUCCESS')
.pipe(take(1))
.subscribe((msg) => {
this.toast.showInfo(msg);
});
})
.catch((error) => {
this.dialogRef.close(false);
this.toast.showError(error);
});
} else if (type === AuthFactorType.OTPEMAIL) {
this.authService
.addMyAuthFactorOTPEmail()
.then(() => {
this.dialogRef.close(true);
this.translate
.get('USER.MFA.OTPEMAILSUCCESS')
.pipe(take(1))
.subscribe((msg) => {
this.toast.showInfo(msg);
});
})
.catch((error) => {
this.dialogRef.close(false);
this.toast.showError(error);
});
}
}
@@ -115,7 +115,11 @@
<cnsl-auth-passwordless *ngIf="user" #mfaComponent></cnsl-auth-passwordless>
<cnsl-auth-user-mfa *ngIf="user" #mfaComponent></cnsl-auth-user-mfa>
<cnsl-auth-user-mfa
[phoneVerified]="user.human?.phone?.isPhoneVerified ?? false"
*ngIf="user"
#mfaComponent
></cnsl-auth-user-mfa>
</ng-container>
<ng-container *ngIf="currentSetting === 'grants'">
@@ -9,6 +9,7 @@ import { Buffer } from 'buffer';
import { Subscription, take } from 'rxjs';
import { ChangeType } from 'src/app/modules/changes/changes.component';
import { phoneValidator, requiredValidator } from 'src/app/modules/form-field/validators/validators';
import { InfoDialogComponent } from 'src/app/modules/info-dialog/info-dialog.component';
import { MetadataDialogComponent } from 'src/app/modules/metadata/metadata-dialog/metadata-dialog.component';
import { PolicyComponentServiceType } from 'src/app/modules/policies/policy-component-types.enum';
import { SidenavSetting } from 'src/app/modules/sidenav/sidenav.component';
@@ -223,12 +224,38 @@ export class AuthUserDetailComponent implements OnDestroy {
.then(() => {
this.toast.showInfo('USER.TOAST.PHONESAVED', true);
this.refreshUser();
this.promptSetupforSMSOTP();
})
.catch((error) => {
this.toast.showError(error);
});
}
public promptSetupforSMSOTP(): void {
const dialogRef = this.dialog.open(InfoDialogComponent, {
data: {
confirmKey: 'ACTIONS.CONTINUE',
cancelKey: 'ACTIONS.CANCEL',
titleKey: 'USER.MFA.OTPSMS',
descriptionKey: 'USER.MFA.SETUPOTPSMSDESCRIPTION',
},
width: '400px',
});
dialogRef.afterClosed().subscribe((resp) => {
if (resp) {
this.userService.addMyAuthFactorOTPSMS().then(() => {
this.translate
.get('USER.MFA.OTPSMSSUCCESS')
.pipe(take(1))
.subscribe((msg) => {
this.toast.showInfo(msg);
});
});
}
});
}
public changedLanguage(language: string): void {
this.translate.use(language);
}
@@ -31,8 +31,10 @@
<ng-container matColumnDef="type">
<th mat-header-cell *matHeaderCellDef>{{ 'USER.MFA.TABLETYPE' | translate }}</th>
<td mat-cell *matCellDef="let mfa">
<span *ngIf="mfa.otp !== undefined">OTP (One-Time Password)</span>
<span *ngIf="mfa.otp !== undefined">TOTP (Time-based One-Time Password)</span>
<span *ngIf="mfa.u2f !== undefined">U2F (Universal 2nd Factor)</span>
<span *ngIf="mfa.otpSms !== undefined">One-Time Password SMS</span>
<span *ngIf="mfa.otpEmail !== undefined">One-Time Password Email</span>
</td>
</ng-container>
@@ -1,4 +1,4 @@
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
import { MatLegacyTable as MatTable, MatLegacyTableDataSource as MatTableDataSource } from '@angular/material/legacy-table';
import { MatSort } from '@angular/material/sort';
@@ -32,12 +32,15 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
@ViewChild(MatTable) public table!: MatTable<AuthFactor.AsObject>;
@ViewChild(MatSort) public sort!: MatSort;
@Input() public phoneVerified: boolean = false;
public dataSource: MatTableDataSource<AuthFactor.AsObject> = new MatTableDataSource<AuthFactor.AsObject>([]);
public AuthFactorState: any = AuthFactorState;
public error: string = '';
public otpDisabled$ = new BehaviorSubject<boolean>(true);
public otpSmsDisabled$ = new BehaviorSubject<boolean>(true);
public otpEmailDisabled$ = new BehaviorSubject<boolean>(true);
constructor(private service: GrpcAuthService, private toast: ToastService, private dialog: MatDialog) {}
@@ -53,6 +56,9 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
const dialogRef = this.dialog.open(AuthFactorDialogComponent, {
data: {
otpDisabled$: this.otpDisabled$,
otpSmsDisabled$: this.otpSmsDisabled$,
otpEmailDisabled$: this.otpEmailDisabled$,
phoneVerified: this.phoneVerified,
},
});
@@ -73,12 +79,39 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
if (index === -1) {
this.otpDisabled$.next(false);
}
const sms = list.findIndex((mfa) => mfa.otpSms);
if (sms === -1) {
this.otpSmsDisabled$.next(false);
}
const email = list.findIndex((mfa) => mfa.otpEmail);
if (email === -1) {
this.otpEmailDisabled$.next(false);
}
})
.catch((error) => {
this.error = error.message;
});
}
private cleanupList(): void {
const totp = this.dataSource.data.findIndex((mfa) => !!mfa.otp);
if (totp > -1) {
this.dataSource.data.splice(totp, 1);
}
const sms = this.dataSource.data.findIndex((mfa) => !!mfa.otpSms);
if (sms > -1) {
this.dataSource.data.splice(sms, 1);
}
const email = this.dataSource.data.findIndex((mfa) => !!mfa.otpEmail);
if (email > -1) {
this.dataSource.data.splice(email, 1);
}
}
public deleteMFA(factor: AuthFactor.AsObject): void {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
@@ -98,10 +131,7 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
.then(() => {
this.toast.showInfo('USER.TOAST.OTPREMOVED', true);
const index = this.dataSource.data.findIndex((mfa) => !!mfa.otp);
if (index > -1) {
this.dataSource.data.splice(index, 1);
}
this.cleanupList();
this.getMFAs();
})
.catch((error) => {
@@ -113,10 +143,31 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
.then(() => {
this.toast.showInfo('USER.TOAST.U2FREMOVED', true);
const index = this.dataSource.data.findIndex((mfa) => !!mfa.u2f);
if (index > -1) {
this.dataSource.data.splice(index, 1);
}
this.cleanupList();
this.getMFAs();
})
.catch((error) => {
this.toast.showError(error);
});
} else if (factor.otpEmail) {
this.service
.removeMyAuthFactorOTPEmail()
.then(() => {
this.toast.showInfo('USER.TOAST.U2FREMOVED', true);
this.cleanupList();
this.getMFAs();
})
.catch((error) => {
this.toast.showError(error);
});
} else if (factor.otpSms) {
this.service
.removeMyAuthFactorOTPSMS()
.then(() => {
this.toast.showInfo('USER.TOAST.U2FREMOVED', true);
this.cleanupList();
this.getMFAs();
})
.catch((error) => {
@@ -3,6 +3,7 @@ import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
import { Human, UserState } from 'src/app/proto/generated/zitadel/user_pb';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { CodeDialogComponent } from '../auth-user-detail/code-dialog/code-dialog.component';
import { EditDialogType } from '../auth-user-detail/edit-dialog/edit-dialog.component';
@@ -25,15 +26,19 @@ export class ContactComponent {
public UserState: any = UserState;
public EditDialogType: any = EditDialogType;
constructor(private dialog: MatDialog) {}
constructor(private dialog: MatDialog, private authService: GrpcAuthService) {}
async emitDeletePhone(): Promise<void> {
const { resultList } = await this.authService.listMyMultiFactors();
const hasSMSOTP = !!resultList.find((mfa) => mfa.otpSms);
emitDeletePhone(): void {
const dialogRef = this.dialog.open(WarnDialogComponent, {
data: {
confirmKey: 'ACTIONS.DELETE',
cancelKey: 'ACTIONS.CANCEL',
titleKey: 'USER.LOGINMETHODS.PHONE.DELETETITLE',
descriptionKey: 'USER.LOGINMETHODS.PHONE.DELETEDESC',
warnSectionKey: hasSMSOTP ? 'USER.LOGINMETHODS.PHONE.OTPSMSREMOVALWARNING' : '',
},
width: '400px',
});
@@ -38,6 +38,7 @@ import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe/
import { MatLegacySelectModule as MatSelectModule } from '@angular/material/legacy-select';
import { QRCodeModule } from 'angularx-qrcode';
import { InfoDialogModule } from 'src/app/modules/info-dialog/info-dialog.module';
import { MetadataModule } from 'src/app/modules/metadata/metadata.module';
import { CountryCallingCodesService } from 'src/app/services/country-calling-codes.service';
import { InfoRowModule } from '../../../modules/info-row/info-row.module';
@@ -86,6 +87,7 @@ import { UserMfaComponent } from './user-detail/user-mfa/user-mfa.component';
ChangesModule,
CommonModule,
SidenavModule,
InfoDialogModule,
FormsModule,
ReactiveFormsModule,
MembershipsTableModule,
@@ -18,8 +18,10 @@
<ng-container matColumnDef="type">
<th mat-header-cell *matHeaderCellDef>{{ 'USER.MFA.TABLETYPE' | translate }}</th>
<td mat-cell *matCellDef="let mfa">
<span *ngIf="mfa.otp !== undefined">OTP (One-Time Password)</span>
<span *ngIf="mfa.otp !== undefined">TOTP (Time-based One-Time Password)</span>
<span *ngIf="mfa.u2f !== undefined">U2F (Universal 2nd Factor)</span>
<span *ngIf="mfa.otpSms !== undefined">One-Time Password SMS</span>
<span *ngIf="mfa.otpEmail !== undefined">One-Time Password Email</span>
</td>
</ng-container>
@@ -16,8 +16,12 @@ import {
} from 'rxjs/operators';
import {
AddMyAuthFactorOTPEmailRequest,
AddMyAuthFactorOTPEmailResponse,
AddMyAuthFactorOTPRequest,
AddMyAuthFactorOTPResponse,
AddMyAuthFactorOTPSMSRequest,
AddMyAuthFactorOTPSMSResponse,
AddMyAuthFactorU2FRequest,
AddMyAuthFactorU2FResponse,
AddMyPasswordlessLinkRequest,
@@ -61,8 +65,12 @@ import {
ListMyUserSessionsResponse,
ListMyZitadelPermissionsRequest,
ListMyZitadelPermissionsResponse,
RemoveMyAuthFactorOTPEmailRequest,
RemoveMyAuthFactorOTPEmailResponse,
RemoveMyAuthFactorOTPRequest,
RemoveMyAuthFactorOTPResponse,
RemoveMyAuthFactorOTPSMSRequest,
RemoveMyAuthFactorOTPSMSResponse,
RemoveMyAuthFactorU2FRequest,
RemoveMyAuthFactorU2FResponse,
RemoveMyAvatarRequest,
@@ -557,6 +565,18 @@ export class GrpcAuthService {
return this.grpcService.auth.addMyAuthFactorOTP(new AddMyAuthFactorOTPRequest(), null).then((resp) => resp.toObject());
}
public addMyAuthFactorOTPSMS(): Promise<AddMyAuthFactorOTPSMSResponse.AsObject> {
return this.grpcService.auth
.addMyAuthFactorOTPSMS(new AddMyAuthFactorOTPSMSRequest(), null)
.then((resp) => resp.toObject());
}
public addMyAuthFactorOTPEmail(): Promise<AddMyAuthFactorOTPEmailResponse.AsObject> {
return this.grpcService.auth
.addMyAuthFactorOTPEmail(new AddMyAuthFactorOTPEmailRequest(), null)
.then((resp) => resp.toObject());
}
public addMyMultiFactorU2F(): Promise<AddMyAuthFactorU2FResponse.AsObject> {
return this.grpcService.auth.addMyAuthFactorU2F(new AddMyAuthFactorU2FRequest(), null).then((resp) => resp.toObject());
}
@@ -617,6 +637,18 @@ export class GrpcAuthService {
.then((resp) => resp.toObject());
}
public removeMyAuthFactorOTPSMS(): Promise<RemoveMyAuthFactorOTPSMSResponse.AsObject> {
return this.grpcService.auth
.removeMyAuthFactorOTPSMS(new RemoveMyAuthFactorOTPSMSRequest(), null)
.then((resp) => resp.toObject());
}
public removeMyAuthFactorOTPEmail(): Promise<RemoveMyAuthFactorOTPEmailResponse.AsObject> {
return this.grpcService.auth
.removeMyAuthFactorOTPEmail(new RemoveMyAuthFactorOTPEmailRequest(), null)
.then((resp) => resp.toObject());
}
public verifyMyMultiFactorOTP(code: string): Promise<VerifyMyAuthFactorOTPResponse.AsObject> {
const req = new VerifyMyAuthFactorOTPRequest();
req.setCode(code);