fix(FE): allow only enabled factors to be displayed on user page (#9313)

# Which Problems Are Solved

- Hides for users MFA options are not allowed by org policy.
- Fix for "ng test" across "console"

# How the Problems Are Solved

- Before displaying MFA options we call "listMyMultiFactors" from parent
component to filter MFA allowed by org

# Additional Changes

- Dependency Injection was fixed around ng unit tests

# Additional Context

admin view
<img width="698" alt="Screenshot 2025-02-06 at 00 26 50"
src="https://github.com/user-attachments/assets/1b642c8a-a640-4bdd-a1ca-bde70c263567"
/>
user view
<img width="751" alt="Screenshot 2025-02-06 at 00 27 16"
src="https://github.com/user-attachments/assets/e1c99907-3226-46ce-b8bc-e993af4b4cae"
/>
test
<img width="1500" alt="Screenshot 2025-02-06 at 00 01 36"
src="https://github.com/user-attachments/assets/d2d8ead1-9f0f-4916-a2fc-f4db9c71cfa8"
/>

The issue: https://github.com/zitadel/zitadel/issues/9176
The bug report:
https://discord.com/channels/927474939156643850/1307006457815896094

---------

Co-authored-by: a k <rdyto1@macbook-pro-1.home>
Co-authored-by: a k <rdyto1@macbook-pro.home>
Co-authored-by: a k <rdyto1@macbook-pro-2.home>
Co-authored-by: Ramon <mail@conblem.me>
This commit is contained in:
AnthonyKot
2025-06-04 11:26:53 +02:00
committed by GitHub
parent 1a80e26502
commit 839c761357
27 changed files with 411 additions and 204 deletions

View File

@@ -82,6 +82,7 @@
"jasmine-spec-reporter": "~7.0.0", "jasmine-spec-reporter": "~7.0.0",
"karma": "^6.4.4", "karma": "^6.4.4",
"karma-chrome-launcher": "^3.2.0", "karma-chrome-launcher": "^3.2.0",
"karma-coverage": "^2.2.1",
"karma-coverage-istanbul-reporter": "^3.0.3", "karma-coverage-istanbul-reporter": "^3.0.3",
"karma-jasmine": "^5.1.0", "karma-jasmine": "^5.1.0",
"karma-jasmine-html-reporter": "^2.1.0", "karma-jasmine-html-reporter": "^2.1.0",

View File

@@ -1,16 +1,16 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { QuickstartComponent } from './quickstart.component'; import { OIDCConfigurationComponent } from './oidc-configuration.component';
describe('QuickstartComponent', () => { describe('QuickstartComponent', () => {
let component: QuickstartComponent; let component: OIDCConfigurationComponent;
let fixture: ComponentFixture<QuickstartComponent>; let fixture: ComponentFixture<OIDCConfigurationComponent>;
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [QuickstartComponent], declarations: [OIDCConfigurationComponent],
}); });
fixture = TestBed.createComponent(QuickstartComponent); fixture = TestBed.createComponent(OIDCConfigurationComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@@ -1,19 +1,19 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { OrgDomainsComponent } from './org-domains.component'; import { DomainsComponent } from './domains.component';
describe('OrgDomainsComponent', () => { describe('OrgDomainsComponent', () => {
let component: OrgDomainsComponent; let component: DomainsComponent;
let fixture: ComponentFixture<OrgDomainsComponent>; let fixture: ComponentFixture<DomainsComponent>;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [OrgDomainsComponent], declarations: [DomainsComponent],
}).compileComponents(); }).compileComponents();
}); });
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(OrgDomainsComponent); fixture = TestBed.createComponent(DomainsComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@@ -1,19 +1,19 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FilterUserComponent } from './filter-user.component'; import { FilterProjectComponent } from './filter-project.component';
describe('FilterUserComponent', () => { describe('FilterUserComponent', () => {
let component: FilterUserComponent; let component: FilterProjectComponent;
let fixture: ComponentFixture<FilterUserComponent>; let fixture: ComponentFixture<FilterProjectComponent>;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [FilterUserComponent], declarations: [FilterProjectComponent],
}).compileComponents(); }).compileComponents();
}); });
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(FilterUserComponent); fixture = TestBed.createComponent(FilterProjectComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@@ -1,8 +1,49 @@
import { Component, ElementRef, NgZone } from '@angular/core';
import { TestBed, ComponentFixture } from '@angular/core/testing';
import { InputDirective } from './input.directive'; import { InputDirective } from './input.directive';
import { Platform } from '@angular/cdk/platform';
import { NgControl, NgForm, FormGroupDirective } from '@angular/forms';
import { ErrorStateMatcher } from '@angular/material/core';
import { AutofillMonitor } from '@angular/cdk/text-field';
import { MatFormField } from '@angular/material/form-field';
import { MAT_INPUT_VALUE_ACCESSOR } from '@angular/material/input';
import { of } from 'rxjs';
import { By } from '@angular/platform-browser';
@Component({
template: `<input appInputDirective />`,
})
class TestHostComponent {}
describe('InputDirective', () => { describe('InputDirective', () => {
let fixture: ComponentFixture<TestHostComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [InputDirective, TestHostComponent],
providers: [
{ provide: ElementRef, useValue: new ElementRef(document.createElement('input')) },
Platform,
{ provide: NgControl, useValue: null },
{ provide: NgForm, useValue: null },
{ provide: FormGroupDirective, useValue: null },
ErrorStateMatcher,
{ provide: MAT_INPUT_VALUE_ACCESSOR, useValue: null },
{
provide: AutofillMonitor,
useValue: { monitor: () => of(), stopMonitoring: () => {} },
},
NgZone,
{ provide: MatFormField, useValue: null },
],
}).compileComponents();
fixture = TestBed.createComponent(TestHostComponent);
fixture.detectChanges();
});
it('should create an instance', () => { it('should create an instance', () => {
const directive = new InputDirective(); const directiveEl = fixture.debugElement.query(By.directive(InputDirective));
expect(directive).toBeTruthy(); expect(directiveEl).toBeTruthy();
}); });
}); });

View File

@@ -1,19 +1,19 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { AvatarComponent } from './avatar.component'; import { LabelComponent } from './label.component';
describe('AvatarComponent', () => { describe('AvatarComponent', () => {
let component: AvatarComponent; let component: LabelComponent;
let fixture: ComponentFixture<AvatarComponent>; let fixture: ComponentFixture<LabelComponent>;
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [AvatarComponent], declarations: [LabelComponent],
}).compileComponents(); }).compileComponents();
})); }));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(AvatarComponent); fixture = TestBed.createComponent(LabelComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@@ -2,14 +2,12 @@ import { Component, Injector, Input, OnDestroy, OnInit, Type } from '@angular/co
import { AbstractControl, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { AbstractControl, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { Duration } from 'google-protobuf/google/protobuf/duration_pb'; import { Duration } from 'google-protobuf/google/protobuf/duration_pb';
import { firstValueFrom, forkJoin, from, Observable, of, Subject, take } from 'rxjs'; import { forkJoin, from, of, Subject, take } from 'rxjs';
import { import {
GetLoginPolicyResponse as AdminGetLoginPolicyResponse, GetLoginPolicyResponse as AdminGetLoginPolicyResponse,
UpdateLoginPolicyRequest, UpdateLoginPolicyRequest,
UpdateLoginPolicyResponse,
} from 'src/app/proto/generated/zitadel/admin_pb'; } from 'src/app/proto/generated/zitadel/admin_pb';
import { import {
AddCustomLoginPolicyRequest,
GetLoginPolicyResponse as MgmtGetLoginPolicyResponse, GetLoginPolicyResponse as MgmtGetLoginPolicyResponse,
UpdateCustomLoginPolicyRequest, UpdateCustomLoginPolicyRequest,
} from 'src/app/proto/generated/zitadel/management_pb'; } from 'src/app/proto/generated/zitadel/management_pb';
@@ -24,8 +22,7 @@ import { InfoSectionType } from '../../info-section/info-section.component';
import { WarnDialogComponent } from '../../warn-dialog/warn-dialog.component'; import { WarnDialogComponent } from '../../warn-dialog/warn-dialog.component';
import { PolicyComponentServiceType } from '../policy-component-types.enum'; import { PolicyComponentServiceType } from '../policy-component-types.enum';
import { LoginMethodComponentType } from './factor-table/factor-table.component'; import { LoginMethodComponentType } from './factor-table/factor-table.component';
import { catchError, map, takeUntil } from 'rxjs/operators'; import { map, takeUntil } from 'rxjs/operators';
import { error } from 'console';
import { LoginPolicyService } from '../../../services/login-policy.service'; import { LoginPolicyService } from '../../../services/login-policy.service';
const minValueValidator = (minValue: number) => (control: AbstractControl) => { const minValueValidator = (minValue: number) => (control: AbstractControl) => {

View File

@@ -1,19 +1,19 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { LoginPolicyComponent } from './login-policy.component'; import { MessageTextsComponent } from './message-texts.component';
describe('LoginPolicyComponent', () => { describe('LoginPolicyComponent', () => {
let component: LoginPolicyComponent; let component: MessageTextsComponent;
let fixture: ComponentFixture<LoginPolicyComponent>; let fixture: ComponentFixture<MessageTextsComponent>;
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [LoginPolicyComponent], declarations: [MessageTextsComponent],
}).compileComponents(); }).compileComponents();
})); }));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(LoginPolicyComponent); fixture = TestBed.createComponent(MessageTextsComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

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

View File

@@ -1,19 +1,19 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { PasswordDialogComponent } from './password-dialog-sms-provider.component'; import { PasswordDialogSMSProviderComponent } from './password-dialog-sms-provider.component';
describe('PasswordDialogComponent', () => { describe('PasswordDialogComponent', () => {
let component: PasswordDialogComponent; let component: PasswordDialogSMSProviderComponent;
let fixture: ComponentFixture<PasswordDialogComponent>; let fixture: ComponentFixture<PasswordDialogSMSProviderComponent>;
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [PasswordDialogComponent], declarations: [PasswordDialogSMSProviderComponent],
}).compileComponents(); }).compileComponents();
})); }));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(PasswordDialogComponent); fixture = TestBed.createComponent(PasswordDialogSMSProviderComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@@ -1,19 +1,19 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { ProviderOAuthComponent } from './provider-oauth.component'; import { ProviderGithubESComponent } from './provider-github-es.component';
describe('ProviderOAuthComponent', () => { describe('ProviderOAuthComponent', () => {
let component: ProviderOAuthComponent; let component: ProviderGithubESComponent;
let fixture: ComponentFixture<ProviderOAuthComponent>; let fixture: ComponentFixture<ProviderGithubESComponent>;
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ProviderOAuthComponent], declarations: [ProviderGithubESComponent],
}).compileComponents(); }).compileComponents();
})); }));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(ProviderOAuthComponent); fixture = TestBed.createComponent(ProviderGithubESComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@@ -1,19 +1,19 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { ProviderGoogleComponent } from './provider-google.component'; import { ProviderGitlabSelfHostedComponent } from './provider-gitlab-self-hosted.component';
describe('ProviderGoogleComponent', () => { describe('ProviderGoogleComponent', () => {
let component: ProviderGoogleComponent; let component: ProviderGitlabSelfHostedComponent;
let fixture: ComponentFixture<ProviderGoogleComponent>; let fixture: ComponentFixture<ProviderGitlabSelfHostedComponent>;
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ProviderGoogleComponent], declarations: [ProviderGitlabSelfHostedComponent],
}).compileComponents(); }).compileComponents();
})); }));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(ProviderGoogleComponent); fixture = TestBed.createComponent(ProviderGitlabSelfHostedComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@@ -1,19 +1,19 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { ProviderGoogleComponent } from './provider-google.component'; import { ProviderGitlabComponent } from './provider-gitlab.component';
describe('ProviderGoogleComponent', () => { describe('ProviderGoogleComponent', () => {
let component: ProviderGoogleComponent; let component: ProviderGitlabComponent;
let fixture: ComponentFixture<ProviderGoogleComponent>; let fixture: ComponentFixture<ProviderGitlabComponent>;
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ProviderGoogleComponent], declarations: [ProviderGitlabComponent],
}).compileComponents(); }).compileComponents();
})); }));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(ProviderGoogleComponent); fixture = TestBed.createComponent(ProviderGitlabComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@@ -1,19 +1,19 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { ShowKeyDialogComponent } from './show-key-dialog.component'; import { ShowTokenDialogComponent } from './show-token-dialog.component';
describe('ShowKeyDialogComponent', () => { describe('ShowKeyDialogComponent', () => {
let component: ShowKeyDialogComponent; let component: ShowTokenDialogComponent;
let fixture: ComponentFixture<ShowKeyDialogComponent>; let fixture: ComponentFixture<ShowTokenDialogComponent>;
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ShowKeyDialogComponent], declarations: [ShowTokenDialogComponent],
}).compileComponents(); }).compileComponents();
})); }));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(ShowKeyDialogComponent); fixture = TestBed.createComponent(ShowTokenDialogComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@@ -1,19 +1,19 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { IdpTableComponent } from './smtp-table.component'; import { SMTPTableComponent } from './smtp-table.component';
describe('UserTableComponent', () => { describe('UserTableComponent', () => {
let component: IdpTableComponent; let component: SMTPTableComponent;
let fixture: ComponentFixture<IdpTableComponent>; let fixture: ComponentFixture<SMTPTableComponent>;
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [IdpTableComponent], declarations: [SMTPTableComponent],
}).compileComponents(); }).compileComponents();
})); }));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(IdpTableComponent); fixture = TestBed.createComponent(SMTPTableComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@@ -1,19 +1,19 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { AddKeyDialogComponent } from './add-key-dialog.component'; import { AddActionDialogComponent } from './add-action-dialog.component';
describe('AddKeyDialogComponent', () => { describe('AddKeyDialogComponent', () => {
let component: AddKeyDialogComponent; let component: AddActionDialogComponent;
let fixture: ComponentFixture<AddKeyDialogComponent>; let fixture: ComponentFixture<AddActionDialogComponent>;
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [AddKeyDialogComponent], declarations: [AddActionDialogComponent],
}).compileComponents(); }).compileComponents();
})); }));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(AddKeyDialogComponent); fixture = TestBed.createComponent(AddActionDialogComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@@ -1,19 +1,19 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { AddKeyDialogComponent } from './add-key-dialog.component'; import { AddFlowDialogComponent } from './add-flow-dialog.component';
describe('AddKeyDialogComponent', () => { describe('AddKeyDialogComponent', () => {
let component: AddKeyDialogComponent; let component: AddFlowDialogComponent;
let fixture: ComponentFixture<AddKeyDialogComponent>; let fixture: ComponentFixture<AddFlowDialogComponent>;
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [AddKeyDialogComponent], declarations: [AddFlowDialogComponent],
}).compileComponents(); }).compileComponents();
})); }));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(AddKeyDialogComponent); fixture = TestBed.createComponent(AddFlowDialogComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@@ -1,5 +1,5 @@
<h1 mat-dialog-title> <h1 mat-dialog-title>
<span class="title">{{ 'USER.MFA.DIALOG.ADD_MFA_TITLE' | translate }} {{ data?.number }}</span> <span class="title">{{ 'USER.MFA.DIALOG.ADD_MFA_TITLE' | translate }}</span>
</h1> </h1>
<div mat-dialog-content> <div mat-dialog-content>
<ng-container *ngIf="selectedType === undefined"> <ng-container *ngIf="selectedType === undefined">
@@ -7,6 +7,7 @@
<div class="type-selection"> <div class="type-selection">
<button <button
*ngIf="data.otp$ | async"
mat-stroked-button mat-stroked-button
[disabled]="data.otpDisabled$ | async" [disabled]="data.otpDisabled$ | async"
(click)="selectType(AuthFactorType.OTP)" (click)="selectType(AuthFactorType.OTP)"
@@ -56,7 +57,7 @@
<span>{{ 'USER.MFA.OTP' | translate }}</span> <span>{{ 'USER.MFA.OTP' | translate }}</span>
</div> </div>
</button> </button>
<button mat-stroked-button (click)="selectType(AuthFactorType.U2F)"> <button *ngIf="data.u2f$ | async" mat-stroked-button (click)="selectType(AuthFactorType.U2F)">
<div class="u2f-btn"> <div class="u2f-btn">
<div class="icon-row"> <div class="icon-row">
<svg <svg
@@ -78,6 +79,7 @@
</div> </div>
</button> </button>
<button <button
*ngIf="data.otpSms$ | async"
[disabled]="!data.phoneVerified || (data.otpSmsDisabled$ | async)" [disabled]="!data.phoneVerified || (data.otpSmsDisabled$ | async)"
mat-stroked-button mat-stroked-button
(click)="selectType(AuthFactorType.OTPSMS)" (click)="selectType(AuthFactorType.OTPSMS)"
@@ -110,7 +112,12 @@
</div> </div>
</div> </div>
</button> </button>
<button [disabled]="data.otpEmailDisabled$ | async" mat-stroked-button (click)="selectType(AuthFactorType.OTPEMAIL)"> <button
*ngIf="data.otpEmail$ | async"
[disabled]="data.otpEmailDisabled$ | async"
mat-stroked-button
(click)="selectType(AuthFactorType.OTPEMAIL)"
>
<div class="otp-btn"> <div class="otp-btn">
<div class="icon-row"> <div class="icon-row">
<svg <svg

View File

@@ -2,6 +2,7 @@ import { Component, Inject } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { take } from 'rxjs/operators'; import { take } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service'; import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
@@ -16,6 +17,17 @@ export enum AuthFactorType {
OTPEMAIL, OTPEMAIL,
} }
export type AddAuthFactorDialogData = {
otp$: Observable<boolean>;
u2f$: Observable<boolean>;
otpSms$: Observable<boolean>;
otpEmail$: Observable<boolean>;
otpDisabled$: Observable<boolean>;
otpSmsDisabled$: Observable<boolean>;
otpEmailDisabled$: Observable<boolean>;
phoneVerified: boolean;
};
@Component({ @Component({
selector: 'cnsl-auth-factor-dialog', selector: 'cnsl-auth-factor-dialog',
templateUrl: './auth-factor-dialog.component.html', templateUrl: './auth-factor-dialog.component.html',
@@ -44,7 +56,7 @@ export class AuthFactorDialogComponent {
private toast: ToastService, private toast: ToastService,
private translate: TranslateService, private translate: TranslateService,
public dialogRef: MatDialogRef<AuthFactorDialogComponent>, public dialogRef: MatDialogRef<AuthFactorDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any, @Inject(MAT_DIALOG_DATA) public data: AddAuthFactorDialogData,
) {} ) {}
closeDialog(code: string = ''): void { closeDialog(code: string = ''): void {

View File

@@ -1,24 +1,147 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { of } from 'rxjs';
import { AuthUserMfaComponent } from './auth-user-mfa.component'; import { AuthUserMfaComponent } from './auth-user-mfa.component';
import { ToastService } from 'src/app/services/toast.service';
import { MatDialog } from '@angular/material/dialog';
import { NewAuthService } from 'src/app/services/new-auth.service';
import { SecondFactorType } from 'src/app/proto/generated/zitadel/policy_pb';
import { CardComponent } from 'src/app/modules/card/card.component';
import { RefreshTableComponent } from 'src/app/modules/refresh-table/refresh-table.component';
import { TranslateModule } from '@ngx-translate/core';
import { MatIconModule } from '@angular/material/icon';
import { MatTableModule } from '@angular/material/table';
import { MatTooltipModule } from '@angular/material/tooltip';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { AuthFactor, AuthFactorState } from '@zitadel/proto/zitadel/user_pb';
describe('AuthUserMfaComponent', () => { describe('AuthUserMfaComponent', () => {
let component: AuthUserMfaComponent; // Create a test host component that extends the original component
let fixture: ComponentFixture<AuthUserMfaComponent>; class TestHostComponent extends AuthUserMfaComponent {
// Expose protected properties for testing
public getOtpEmailDisabled$() {
return this.otpEmailDisabled$;
}
public getOtpDisabled$() {
return this.otpDisabled$;
}
public getOtpSmsDisabled$() {
return this.otpSmsDisabled$;
}
}
let component: TestHostComponent;
let fixture: ComponentFixture<TestHostComponent>;
let serviceStub: Partial<NewAuthService>;
let toastStub: Partial<ToastService>;
let dialogStub: Partial<MatDialog>;
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
// Create stubs for required services
serviceStub = {
listMyMultiFactors: jasmine.createSpy('listMyMultiFactors').and.returnValue(
Promise.resolve({
result: [
{ type: { case: 'otp' }, state: AuthFactorState.READY, $typeName: 'zitadel.user.v1.AuthFactor' } as AuthFactor,
{
type: { case: 'otpSms' },
state: AuthFactorState.READY,
$typeName: 'zitadel.user.v1.AuthFactor',
} as AuthFactor,
{
type: { case: 'otpEmail' },
state: AuthFactorState.READY,
$typeName: 'zitadel.user.v1.AuthFactor',
} as AuthFactor,
],
}),
),
getMyLoginPolicy: jasmine.createSpy('getMyLoginPolicy').and.returnValue(
Promise.resolve({
policy: {
secondFactorsList: [
SecondFactorType.SECOND_FACTOR_TYPE_OTP,
SecondFactorType.SECOND_FACTOR_TYPE_U2F,
SecondFactorType.SECOND_FACTOR_TYPE_OTP_EMAIL,
SecondFactorType.SECOND_FACTOR_TYPE_OTP_SMS,
],
},
}),
),
removeMyMultiFactorOTP: jasmine.createSpy('removeMyMultiFactorOTP').and.returnValue(Promise.resolve()),
removeMyMultiFactorU2F: jasmine.createSpy('removeMyMultiFactorU2F').and.returnValue(Promise.resolve()),
removeMyAuthFactorOTPEmail: jasmine.createSpy('removeMyAuthFactorOTPEmail').and.returnValue(Promise.resolve()),
removeMyAuthFactorOTPSMS: jasmine.createSpy('removeMyAuthFactorOTPSMS').and.returnValue(Promise.resolve()),
};
toastStub = {
showInfo: jasmine.createSpy('showInfo'),
showError: jasmine.createSpy('showError'),
};
dialogStub = {
// Opened dialog returns a truthy value after closing
open: jasmine.createSpy('open').and.returnValue({
afterClosed: () => of(true),
}),
};
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [AuthUserMfaComponent], declarations: [TestHostComponent, CardComponent, RefreshTableComponent], // Use TestHostComponent instead
imports: [MatIconModule, TranslateModule.forRoot(), MatTooltipModule, MatTableModule, BrowserAnimationsModule],
providers: [
{ provide: NewAuthService, useValue: serviceStub },
{ provide: ToastService, useValue: toastStub },
{ provide: MatDialog, useValue: dialogStub },
],
}).compileComponents(); }).compileComponents();
})); }));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(AuthUserMfaComponent); fixture = TestBed.createComponent(TestHostComponent); // Use TestHostComponent
component = fixture.componentInstance; component = fixture.componentInstance;
// Optionally set the phoneVerified input if needed by your tests
component.phoneVerified = true;
fixture.detectChanges(); fixture.detectChanges();
}); });
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
}); });
it('should call getMFAs and update dataSource and disable flags', async () => {
// Call the method and wait for the Promise resolution
await component.getMFAs();
fixture.detectChanges();
expect(serviceStub.listMyMultiFactors).toHaveBeenCalled();
// Our stub returns 3 items
expect(component.dataSource.data.length).toBe(3);
// Use the public getter methods to access protected properties
component.getOtpDisabled$().subscribe((value) => {
expect(value).toBeTrue();
});
component.getOtpSmsDisabled$().subscribe((value) => {
expect(value).toBeTrue();
});
component.getOtpEmailDisabled$().subscribe((value) => {
expect(value).toBeTrue();
});
});
it('should call deleteMFA and remove OTP factor', async () => {
// OTP is set
const factor = {
type: { case: 'otp' },
state: AuthFactorState.READY,
$typeName: 'zitadel.user.v1.AuthFactor',
} as AuthFactor;
await component.deleteMFA(factor);
// Verify that the service method for OTP removal was called
expect(serviceStub.removeMyMultiFactorOTP).toHaveBeenCalled();
expect(serviceStub.listMyMultiFactors).toHaveBeenCalled();
});
}); });

View File

@@ -4,12 +4,12 @@ import { MatSort } from '@angular/material/sort';
import { MatTable, MatTableDataSource } from '@angular/material/table'; import { MatTable, MatTableDataSource } from '@angular/material/table';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component'; import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
import { AuthFactor, AuthFactorState } from 'src/app/proto/generated/zitadel/user_pb'; import { AuthFactorState } from 'src/app/proto/generated/zitadel/user_pb';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service'; import { NewAuthService } from 'src/app/services/new-auth.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
import { AddAuthFactorDialogData, AuthFactorDialogComponent } from '../auth-factor-dialog/auth-factor-dialog.component';
import { AuthFactorDialogComponent } from '../auth-factor-dialog/auth-factor-dialog.component'; import { AuthFactor } from '@zitadel/proto/zitadel/user_pb';
import { SecondFactorType } from '@zitadel/proto/zitadel/policy_pb';
export interface WebAuthNOptions { export interface WebAuthNOptions {
challenge: string; challenge: string;
rp: { name: string; id: string }; rp: { name: string; id: string };
@@ -30,26 +30,31 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false); private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable(); public loading$: Observable<boolean> = this.loadingSubject.asObservable();
@ViewChild(MatTable) public table!: MatTable<AuthFactor.AsObject>; @ViewChild(MatTable) public table!: MatTable<AuthFactor>;
@ViewChild(MatSort) public sort!: MatSort; @ViewChild(MatSort) public sort!: MatSort;
@Input() public phoneVerified: boolean = false; @Input() public phoneVerified: boolean = false;
public dataSource: MatTableDataSource<AuthFactor.AsObject> = new MatTableDataSource<AuthFactor.AsObject>([]);
public AuthFactorState: any = AuthFactorState; public AuthFactorState: any = AuthFactorState;
public dataSource: MatTableDataSource<AuthFactor> = new MatTableDataSource<AuthFactor>([]);
public error: string = ''; protected error: string = '';
public otpDisabled$ = new BehaviorSubject<boolean>(true);
public otpSmsDisabled$ = new BehaviorSubject<boolean>(true); protected otpAvailable$ = new BehaviorSubject<boolean>(false);
public otpEmailDisabled$ = new BehaviorSubject<boolean>(true); protected u2fAvailable$ = new BehaviorSubject<boolean>(false);
protected otpSmsAvailable$ = new BehaviorSubject<boolean>(false);
protected otpEmailAvailable$ = new BehaviorSubject<boolean>(false);
protected otpDisabled$ = new BehaviorSubject<boolean>(true);
protected otpSmsDisabled$ = new BehaviorSubject<boolean>(true);
protected otpEmailDisabled$ = new BehaviorSubject<boolean>(true);
constructor( constructor(
private service: GrpcAuthService, private readonly service: NewAuthService,
private toast: ToastService, private readonly toast: ToastService,
private dialog: MatDialog, private readonly dialog: MatDialog,
) {} ) {}
public ngOnInit(): void { public ngOnInit(): void {
this.getMFAs(); this.getMFAs();
this.applyOrgPolicy();
} }
public ngOnDestroy(): void { public ngOnDestroy(): void {
@@ -57,13 +62,19 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
} }
public addAuthFactor(): void { public addAuthFactor(): void {
const data: AddAuthFactorDialogData = {
otp$: this.otpAvailable$,
u2f$: this.u2fAvailable$,
otpSms$: this.otpSmsAvailable$,
otpEmail$: this.otpEmailAvailable$,
otpDisabled$: this.otpDisabled$,
otpSmsDisabled$: this.otpSmsDisabled$,
otpEmailDisabled$: this.otpEmailDisabled$,
phoneVerified: this.phoneVerified,
} as const;
const dialogRef = this.dialog.open(AuthFactorDialogComponent, { const dialogRef = this.dialog.open(AuthFactorDialogComponent, {
data: { data: data,
otpDisabled$: this.otpDisabled$,
otpSmsDisabled$: this.otpSmsDisabled$,
otpEmailDisabled$: this.otpEmailDisabled$,
phoneVerified: this.phoneVerified,
},
}); });
dialogRef.afterClosed().subscribe(() => { dialogRef.afterClosed().subscribe(() => {
@@ -75,48 +86,32 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
this.service this.service
.listMyMultiFactors() .listMyMultiFactors()
.then((mfas) => { .then((mfas) => {
const list = mfas.resultList; const list: AuthFactor[] = mfas.result;
this.dataSource = new MatTableDataSource(list); this.dataSource = new MatTableDataSource(list);
this.dataSource.sort = this.sort; this.dataSource.sort = this.sort;
const index = list.findIndex((mfa) => mfa.otp); this.disableAuthFactor(list, 'otp', this.otpDisabled$);
if (index === -1) { this.disableAuthFactor(list, 'otpSms', this.otpSmsDisabled$);
this.otpDisabled$.next(false); this.disableAuthFactor(list, 'otpEmail', this.otpEmailDisabled$);
}
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) => { .catch((error) => {
this.error = error.message; this.error = error.message;
}); });
} }
private cleanupList(): void { public applyOrgPolicy(): void {
const totp = this.dataSource.data.findIndex((mfa) => !!mfa.otp); this.service.getMyLoginPolicy().then((resp) => {
if (totp > -1) { if (resp && resp.policy) {
this.dataSource.data.splice(totp, 1); const secondFactors = resp.policy?.secondFactors;
} this.displayAuthFactorBasedOnPolicy(secondFactors, SecondFactorType.OTP, this.otpAvailable$);
this.displayAuthFactorBasedOnPolicy(secondFactors, SecondFactorType.U2F, this.u2fAvailable$);
const sms = this.dataSource.data.findIndex((mfa) => !!mfa.otpSms); this.displayAuthFactorBasedOnPolicy(secondFactors, SecondFactorType.OTP_EMAIL, this.otpEmailAvailable$);
if (sms > -1) { this.displayAuthFactorBasedOnPolicy(secondFactors, SecondFactorType.OTP_SMS, this.otpSmsAvailable$);
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 { public deleteMFA(factor: AuthFactor): void {
const dialogRef = this.dialog.open(WarnDialogComponent, { const dialogRef = this.dialog.open(WarnDialogComponent, {
data: { data: {
confirmKey: 'ACTIONS.DELETE', confirmKey: 'ACTIONS.DELETE',
@@ -129,7 +124,7 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
dialogRef.afterClosed().subscribe((resp) => { dialogRef.afterClosed().subscribe((resp) => {
if (resp) { if (resp) {
if (factor.otp) { if (factor.type.case === 'otp') {
this.service this.service
.removeMyMultiFactorOTP() .removeMyMultiFactorOTP()
.then(() => { .then(() => {
@@ -141,9 +136,9 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
.catch((error) => { .catch((error) => {
this.toast.showError(error); this.toast.showError(error);
}); });
} else if (factor.u2f) { } else if (factor.type.case === 'u2f') {
this.service this.service
.removeMyMultiFactorU2F(factor.u2f.id) .removeMyMultiFactorU2F(factor.type.value.id)
.then(() => { .then(() => {
this.toast.showInfo('USER.TOAST.U2FREMOVED', true); this.toast.showInfo('USER.TOAST.U2FREMOVED', true);
@@ -153,7 +148,7 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
.catch((error) => { .catch((error) => {
this.toast.showError(error); this.toast.showError(error);
}); });
} else if (factor.otpEmail) { } else if (factor.type.case === 'otpEmail') {
this.service this.service
.removeMyAuthFactorOTPEmail() .removeMyAuthFactorOTPEmail()
.then(() => { .then(() => {
@@ -165,7 +160,7 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
.catch((error) => { .catch((error) => {
this.toast.showError(error); this.toast.showError(error);
}); });
} else if (factor.otpSms) { } else if (factor.type.case === 'otpSms') {
this.service this.service
.removeMyAuthFactorOTPSMS() .removeMyAuthFactorOTPSMS()
.then(() => { .then(() => {
@@ -181,4 +176,22 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
} }
}); });
} }
private cleanupList(): void {
this.dataSource.data = this.dataSource.data.filter((mfa: AuthFactor) => {
return mfa.type.case;
});
}
private disableAuthFactor(mfas: AuthFactor[], key: string, subject: BehaviorSubject<boolean>): void {
subject.next(mfas.some((mfa) => mfa.type.case === key));
}
private displayAuthFactorBasedOnPolicy(
factors: SecondFactorType[],
factor: SecondFactorType,
subject: BehaviorSubject<boolean>,
): void {
subject.next(factors.some((f) => f === factor));
}
} }

View File

@@ -2,7 +2,7 @@ import { Component, EventEmitter, Input, Output } from '@angular/core';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component'; import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service'; import { NewAuthService } from 'src/app/services/new-auth.service';
import { CodeDialogComponent } from '../auth-user-detail/code-dialog/code-dialog.component'; import { CodeDialogComponent } from '../auth-user-detail/code-dialog/code-dialog.component';
import { EditDialogType } from '../auth-user-detail/edit-dialog/edit-dialog.component'; import { EditDialogType } from '../auth-user-detail/edit-dialog/edit-dialog.component';
import { HumanUser, UserState } from '@zitadel/proto/zitadel/user/v2/user_pb'; import { HumanUser, UserState } from '@zitadel/proto/zitadel/user/v2/user_pb';
@@ -28,12 +28,12 @@ export class ContactComponent {
public EditDialogType: any = EditDialogType; public EditDialogType: any = EditDialogType;
constructor( constructor(
private dialog: MatDialog, private dialog: MatDialog,
private authService: GrpcAuthService, private authService: NewAuthService,
) {} ) {}
async emitDeletePhone(): Promise<void> { async emitDeletePhone(): Promise<void> {
const { resultList } = await this.authService.listMyMultiFactors(); const { result } = await this.authService.listMyMultiFactors();
const hasSMSOTP = !!resultList.find((mfa) => mfa.otpSms); const hasSMSOTP = !!result.some((mfa) => mfa.type.case === 'otpSms');
const dialogRef = this.dialog.open(WarnDialogComponent, { const dialogRef = this.dialog.open(WarnDialogComponent, {
data: { data: {

View File

@@ -1,19 +1,19 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { DetailFormComponent } from './detail-form.component'; import { DetailFormMachineComponent } from './detail-form-machine.component';
describe('DetailFormComponent', () => { describe('DetailFormComponent', () => {
let component: DetailFormComponent; let component: DetailFormMachineComponent;
let fixture: ComponentFixture<DetailFormComponent>; let fixture: ComponentFixture<DetailFormMachineComponent>;
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [DetailFormComponent], declarations: [DetailFormMachineComponent],
}).compileComponents(); }).compileComponents();
})); }));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(DetailFormComponent); fixture = TestBed.createComponent(DetailFormMachineComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@@ -1,19 +1,19 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { AuthPasswordlessComponent } from './auth-passwordless.component'; import { PasswordlessComponent } from './passwordless.component';
describe('AuthPasswordlessComponent', () => { describe('AuthPasswordlessComponent', () => {
let component: AuthPasswordlessComponent; let component: PasswordlessComponent;
let fixture: ComponentFixture<AuthPasswordlessComponent>; let fixture: ComponentFixture<PasswordlessComponent>;
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [AuthPasswordlessComponent], declarations: [PasswordlessComponent],
}).compileComponents(); }).compileComponents();
})); }));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(AuthPasswordlessComponent); fixture = TestBed.createComponent(PasswordlessComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@@ -31,8 +31,6 @@ import {
GetMyEmailRequest, GetMyEmailRequest,
GetMyEmailResponse, GetMyEmailResponse,
GetMyLabelPolicyRequest, GetMyLabelPolicyRequest,
GetMyLoginPolicyRequest,
GetMyLoginPolicyResponse,
GetMyPasswordComplexityPolicyRequest, GetMyPasswordComplexityPolicyRequest,
GetMyPasswordComplexityPolicyResponse, GetMyPasswordComplexityPolicyResponse,
GetMyPhoneRequest, GetMyPhoneRequest,
@@ -42,8 +40,6 @@ import {
GetMyProfileResponse, GetMyProfileResponse,
GetMyUserRequest, GetMyUserRequest,
GetMyUserResponse, GetMyUserResponse,
ListMyAuthFactorsRequest,
ListMyAuthFactorsResponse,
ListMyLinkedIDPsRequest, ListMyLinkedIDPsRequest,
ListMyLinkedIDPsResponse, ListMyLinkedIDPsResponse,
ListMyMembershipsRequest, ListMyMembershipsRequest,
@@ -62,14 +58,6 @@ import {
ListMyUserSessionsResponse, ListMyUserSessionsResponse,
ListMyZitadelPermissionsRequest, ListMyZitadelPermissionsRequest,
ListMyZitadelPermissionsResponse, ListMyZitadelPermissionsResponse,
RemoveMyAuthFactorOTPEmailRequest,
RemoveMyAuthFactorOTPEmailResponse,
RemoveMyAuthFactorOTPRequest,
RemoveMyAuthFactorOTPResponse,
RemoveMyAuthFactorOTPSMSRequest,
RemoveMyAuthFactorOTPSMSResponse,
RemoveMyAuthFactorU2FRequest,
RemoveMyAuthFactorU2FResponse,
RemoveMyAvatarRequest, RemoveMyAvatarRequest,
RemoveMyAvatarResponse, RemoveMyAvatarResponse,
RemoveMyLinkedIDPRequest, RemoveMyLinkedIDPRequest,
@@ -357,10 +345,6 @@ export class GrpcAuthService {
return this.grpcService.auth.getMyUser(new GetMyUserRequest(), null).then((resp) => resp.toObject()); return this.grpcService.auth.getMyUser(new GetMyUserRequest(), null).then((resp) => resp.toObject());
} }
public listMyMultiFactors(): Promise<ListMyAuthFactorsResponse.AsObject> {
return this.grpcService.auth.listMyAuthFactors(new ListMyAuthFactorsRequest(), null).then((resp) => resp.toObject());
}
public async revalidateOrgs() { public async revalidateOrgs() {
const orgs = (await this.listMyProjectOrgs(ORG_LIMIT, 0)).resultList; const orgs = (await this.listMyProjectOrgs(ORG_LIMIT, 0)).resultList;
this.cachedOrgs.next(orgs); this.cachedOrgs.next(orgs);
@@ -488,11 +472,6 @@ export class GrpcAuthService {
return this.grpcService.auth.resendMyEmailVerification(req, null).then((resp) => resp.toObject()); return this.grpcService.auth.resendMyEmailVerification(req, null).then((resp) => resp.toObject());
} }
public getMyLoginPolicy(): Promise<GetMyLoginPolicyResponse.AsObject> {
const req = new GetMyLoginPolicyRequest();
return this.grpcService.auth.getMyLoginPolicy(req, null).then((resp) => resp.toObject());
}
public removeMyPhone(): Promise<RemoveMyPhoneResponse.AsObject> { public removeMyPhone(): Promise<RemoveMyPhoneResponse.AsObject> {
return this.grpcService.auth.removeMyPhone(new RemoveMyPhoneRequest(), null).then((resp) => resp.toObject()); return this.grpcService.auth.removeMyPhone(new RemoveMyPhoneRequest(), null).then((resp) => resp.toObject());
} }
@@ -576,12 +555,6 @@ export class GrpcAuthService {
return this.grpcService.auth.addMyAuthFactorU2F(new AddMyAuthFactorU2FRequest(), null).then((resp) => resp.toObject()); return this.grpcService.auth.addMyAuthFactorU2F(new AddMyAuthFactorU2FRequest(), null).then((resp) => resp.toObject());
} }
public removeMyMultiFactorU2F(tokenId: string): Promise<RemoveMyAuthFactorU2FResponse.AsObject> {
const req = new RemoveMyAuthFactorU2FRequest();
req.setTokenId(tokenId);
return this.grpcService.auth.removeMyAuthFactorU2F(req, null).then((resp) => resp.toObject());
}
public verifyMyMultiFactorU2F(credential: string, tokenname: string): Promise<VerifyMyAuthFactorU2FResponse.AsObject> { public verifyMyMultiFactorU2F(credential: string, tokenname: string): Promise<VerifyMyAuthFactorU2FResponse.AsObject> {
const req = new VerifyMyAuthFactorU2FRequest(); const req = new VerifyMyAuthFactorU2FRequest();
const verification = new WebAuthNVerification(); const verification = new WebAuthNVerification();
@@ -626,24 +599,6 @@ export class GrpcAuthService {
return this.grpcService.auth.addMyPasswordlessLink(req, null).then((resp) => resp.toObject()); return this.grpcService.auth.addMyPasswordlessLink(req, null).then((resp) => resp.toObject());
} }
public removeMyMultiFactorOTP(): Promise<RemoveMyAuthFactorOTPResponse.AsObject> {
return this.grpcService.auth
.removeMyAuthFactorOTP(new RemoveMyAuthFactorOTPRequest(), null)
.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> { public verifyMyMultiFactorOTP(code: string): Promise<VerifyMyAuthFactorOTPResponse.AsObject> {
const req = new VerifyMyAuthFactorOTPRequest(); const req = new VerifyMyAuthFactorOTPRequest();
req.setCode(code); req.setCode(code);

View File

@@ -1,9 +1,22 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { GrpcService } from './grpc.service'; import { GrpcService } from './grpc.service';
import { create } from '@bufbuild/protobuf';
import { import {
AddMyAuthFactorOTPSMSResponse, AddMyAuthFactorOTPSMSResponse,
GetMyLoginPolicyResponse,
GetMyLoginPolicyRequestSchema,
GetMyPasswordComplexityPolicyResponse, GetMyPasswordComplexityPolicyResponse,
GetMyUserResponse, GetMyUserResponse,
ListMyAuthFactorsRequestSchema,
ListMyAuthFactorsResponse,
RemoveMyAuthFactorOTPEmailRequestSchema,
RemoveMyAuthFactorOTPEmailResponse,
RemoveMyAuthFactorOTPRequestSchema,
RemoveMyAuthFactorOTPResponse,
RemoveMyAuthFactorU2FRequestSchema,
RemoveMyAuthFactorU2FResponse,
RemoveMyAuthFactorOTPSMSRequestSchema,
RemoveMyAuthFactorOTPSMSResponse,
ListMyMetadataResponse, ListMyMetadataResponse,
VerifyMyPhoneResponse, VerifyMyPhoneResponse,
} from '@zitadel/proto/zitadel/auth_pb'; } from '@zitadel/proto/zitadel/auth_pb';
@@ -30,6 +43,30 @@ export class NewAuthService {
return this.grpcService.authNew.listMyMetadata({}); return this.grpcService.authNew.listMyMetadata({});
} }
public listMyMultiFactors(): Promise<ListMyAuthFactorsResponse> {
return this.grpcService.authNew.listMyAuthFactors(create(ListMyAuthFactorsRequestSchema), null);
}
public removeMyAuthFactorOTPSMS(): Promise<RemoveMyAuthFactorOTPSMSResponse> {
return this.grpcService.authNew.removeMyAuthFactorOTPSMS(create(RemoveMyAuthFactorOTPSMSRequestSchema), null);
}
public getMyLoginPolicy(): Promise<GetMyLoginPolicyResponse> {
return this.grpcService.authNew.getMyLoginPolicy(create(GetMyLoginPolicyRequestSchema), null);
}
public removeMyMultiFactorOTP(): Promise<RemoveMyAuthFactorOTPResponse> {
return this.grpcService.authNew.removeMyAuthFactorOTP(create(RemoveMyAuthFactorOTPRequestSchema), null);
}
public removeMyMultiFactorU2F(tokenId: string): Promise<RemoveMyAuthFactorU2FResponse> {
return this.grpcService.authNew.removeMyAuthFactorU2F(create(RemoveMyAuthFactorU2FRequestSchema, { tokenId }), null);
}
public removeMyAuthFactorOTPEmail(): Promise<RemoveMyAuthFactorOTPEmailResponse> {
return this.grpcService.authNew.removeMyAuthFactorOTPEmail(create(RemoveMyAuthFactorOTPEmailRequestSchema), null);
}
public getMyPasswordComplexityPolicy(): Promise<GetMyPasswordComplexityPolicyResponse> { public getMyPasswordComplexityPolicy(): Promise<GetMyPasswordComplexityPolicyResponse> {
return this.grpcService.authNew.getMyPasswordComplexityPolicy({}); return this.grpcService.authNew.getMyPasswordComplexityPolicy({});
} }

View File

@@ -6038,7 +6038,7 @@ istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0:
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756"
integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==
istanbul-lib-instrument@^5.0.4: istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0:
version "5.2.1" version "5.2.1"
resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d"
integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==
@@ -6069,7 +6069,16 @@ istanbul-lib-source-maps@^3.0.6:
rimraf "^2.6.3" rimraf "^2.6.3"
source-map "^0.6.1" source-map "^0.6.1"
istanbul-reports@^3.0.2: istanbul-lib-source-maps@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551"
integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==
dependencies:
debug "^4.1.1"
istanbul-lib-coverage "^3.0.0"
source-map "^0.6.1"
istanbul-reports@^3.0.2, istanbul-reports@^3.0.5:
version "3.1.7" version "3.1.7"
resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b"
integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==
@@ -6279,6 +6288,18 @@ karma-coverage-istanbul-reporter@^3.0.3:
istanbul-reports "^3.0.2" istanbul-reports "^3.0.2"
minimatch "^3.0.4" minimatch "^3.0.4"
karma-coverage@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/karma-coverage/-/karma-coverage-2.2.1.tgz#e1cc074f93ace9dc4fb7e7aeca7135879c2e358c"
integrity sha512-yj7hbequkQP2qOSb20GuNSIyE//PgJWHwC2IydLE6XRtsnaflv+/OSGNssPjobYUlhVVagy99TQpqUt3vAUG7A==
dependencies:
istanbul-lib-coverage "^3.2.0"
istanbul-lib-instrument "^5.1.0"
istanbul-lib-report "^3.0.0"
istanbul-lib-source-maps "^4.0.1"
istanbul-reports "^3.0.5"
minimatch "^3.0.4"
karma-jasmine-html-reporter@^2.1.0: karma-jasmine-html-reporter@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-2.1.0.tgz#f951ad00b08d61d03595402c914d1a589c4930e3" resolved "https://registry.yarnpkg.com/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-2.1.0.tgz#f951ad00b08d61d03595402c914d1a589c4930e3"