From 886003ba88a51b9672f478894de5a6b1da16b140 Mon Sep 17 00:00:00 2001 From: Dave <3836813+enmande@users.noreply.github.com> Date: Mon, 13 Oct 2025 12:48:25 -0400 Subject: [PATCH] feat(two-factor-api-service) [PM-26465]: (Refactor) Two-Factor API Service (#16747) * feat(two-factor-api-service) [PM-26465]: Add TwoFactorApiServiceAbstraction. * feat(two-factor-api-service) [PM-26465]: Add TwoFactorApiService implementation. * feat(two-factor-api-service) [PM-26465]: Add test suite for TwoFactorApiService. * feat(two-factor-api-service) [PM-26465]: Replace ApiService dependencies with TwoFactorApiService for all refactored methods. * feat(two-factor-api-service) [PM-26465]: Finish removal of Two-Factor API methods from ApiService. * fix(two-factor-api-service) [PM-26465]: Correct endpoint spelling. * feat(two-factor-api-service) [PM-26465]: Update dependency support for CLI. * fix(two-factor-api-service) [PM-26465]: Update tests/deps for corrected spelling. * feat(two-factor-api-service) [PM-26465]: Add TwoFactorApiService to Browser services module. * fix(two-factor-api-service) [PM-26465]: Re-spell dependencies to take *Abstraction throughout, move to JslibServices module for cleaner importing across clients. * feat(two-factor-api-service) [PM-26465]: Move new services to a feature area, rename abstract and concrete/default. * feat(two-factor-api-service) [PM-26465]: Move the feature area to common/auth, not auth/common. * feat(two-factor-api-service) [PM-26465]: Remove now-unneeded include from auth/tsconfig. --- apps/cli/src/auth/commands/login.command.ts | 6 +- apps/cli/src/program.ts | 2 +- .../service-container/service-container.ts | 4 + .../settings/two-factor-setup.component.ts | 8 +- .../account/change-email.component.spec.ts | 6 +- .../account/change-email.component.ts | 4 +- ...account-verify-devices-dialog.component.ts | 6 +- ...wo-factor-setup-authenticator.component.ts | 10 +- .../two-factor-setup-duo.component.ts | 13 +- .../two-factor-setup-email.component.ts | 10 +- .../two-factor-setup-method-base.component.ts | 15 +- .../two-factor-setup-webauthn.component.ts | 12 +- .../two-factor-setup-yubikey.component.ts | 8 +- .../two-factor/two-factor-setup.component.ts | 6 +- .../two-factor/two-factor-verify.component.ts | 18 +- .../src/services/jslib-services.module.ts | 6 + .../two-factor-auth-email.component.ts | 6 +- libs/common/src/abstractions/api.service.ts | 81 -- .../request/device-verification.request.ts | 7 - .../response/device-verification.response.ts | 16 - .../default-two-factor-api.service.ts | 272 +++++++ libs/common/src/auth/two-factor/index.ts | 2 + .../two-factor/two-factor-api.service.spec.ts | 697 ++++++++++++++++++ .../auth/two-factor/two-factor-api.service.ts | 292 ++++++++ libs/common/src/services/api.service.ts | 220 ------ 25 files changed, 1344 insertions(+), 383 deletions(-) delete mode 100644 libs/common/src/auth/models/request/device-verification.request.ts delete mode 100644 libs/common/src/auth/models/response/device-verification.response.ts create mode 100644 libs/common/src/auth/two-factor/default-two-factor-api.service.ts create mode 100644 libs/common/src/auth/two-factor/index.ts create mode 100644 libs/common/src/auth/two-factor/two-factor-api.service.spec.ts create mode 100644 libs/common/src/auth/two-factor/two-factor-api.service.ts diff --git a/apps/cli/src/auth/commands/login.command.ts b/apps/cli/src/auth/commands/login.command.ts index 7e5058dcff0..e1a3d123441 100644 --- a/apps/cli/src/auth/commands/login.command.ts +++ b/apps/cli/src/auth/commands/login.command.ts @@ -14,7 +14,6 @@ import { SsoUrlService, UserApiLoginCredentials, } from "@bitwarden/auth/common"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; @@ -29,6 +28,7 @@ import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/ide import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request"; import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request"; import { UpdateTempPasswordRequest } from "@bitwarden/common/auth/models/request/update-temp-password.request"; +import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; import { ClientType } from "@bitwarden/common/enums"; import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; @@ -62,7 +62,7 @@ export class LoginCommand { constructor( protected loginStrategyService: LoginStrategyServiceAbstraction, protected authService: AuthService, - protected apiService: ApiService, + protected twoFactorApiService: TwoFactorApiService, protected masterPasswordApiService: MasterPasswordApiService, protected cryptoFunctionService: CryptoFunctionService, protected environmentService: EnvironmentService, @@ -279,7 +279,7 @@ export class LoginCommand { const emailReq = new TwoFactorEmailRequest(); emailReq.email = await this.loginStrategyService.getEmail(); emailReq.masterPasswordHash = await this.loginStrategyService.getMasterPasswordHash(); - await this.apiService.postTwoFactorEmail(emailReq); + await this.twoFactorApiService.postTwoFactorEmail(emailReq); } if (twoFactorToken == null) { diff --git a/apps/cli/src/program.ts b/apps/cli/src/program.ts index 8f202bc0845..e8c9bff746e 100644 --- a/apps/cli/src/program.ts +++ b/apps/cli/src/program.ts @@ -175,7 +175,7 @@ export class Program extends BaseProgram { const command = new LoginCommand( this.serviceContainer.loginStrategyService, this.serviceContainer.authService, - this.serviceContainer.apiService, + this.serviceContainer.twoFactorApiService, this.serviceContainer.masterPasswordApiService, this.serviceContainer.cryptoFunctionService, this.serviceContainer.environmentService, diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index d13d251bce0..7c4b708576b 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -49,6 +49,7 @@ import { TokenService } from "@bitwarden/common/auth/services/token.service"; import { TwoFactorService } from "@bitwarden/common/auth/services/two-factor.service"; import { UserVerificationApiService } from "@bitwarden/common/auth/services/user-verification/user-verification-api.service"; import { UserVerificationService } from "@bitwarden/common/auth/services/user-verification/user-verification.service"; +import { TwoFactorApiService, DefaultTwoFactorApiService } from "@bitwarden/common/auth/two-factor"; import { AutofillSettingsService, AutofillSettingsServiceAbstraction, @@ -228,6 +229,7 @@ export class ServiceContainer { tokenService: TokenService; appIdService: AppIdService; apiService: NodeApiService; + twoFactorApiService: TwoFactorApiService; hibpApiService: HibpApiService; environmentService: EnvironmentService; cipherService: CipherService; @@ -528,6 +530,8 @@ export class ServiceContainer { this.configApiService = new ConfigApiService(this.apiService); + this.twoFactorApiService = new DefaultTwoFactorApiService(this.apiService); + this.authService = new AuthService( this.accountService, this.messagingService, diff --git a/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts b/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts index 020a16dd932..3151e0a702f 100644 --- a/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts +++ b/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts @@ -5,7 +5,6 @@ import { ActivatedRoute } from "@angular/router"; import { concatMap, takeUntil, map, lastValueFrom, firstValueFrom } from "rxjs"; import { first, tap } from "rxjs/operators"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { getOrganizationById, OrganizationService, @@ -15,6 +14,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-duo.response"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -35,7 +35,7 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent impleme tabbedHeader = false; constructor( dialogService: DialogService, - apiService: ApiService, + twoFactorApiService: TwoFactorApiService, messagingService: MessagingService, policyService: PolicyService, private route: ActivatedRoute, @@ -47,7 +47,7 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent impleme ) { super( dialogService, - apiService, + twoFactorApiService, messagingService, policyService, billingAccountProfileStateService, @@ -116,7 +116,7 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent impleme } protected getTwoFactorProviders() { - return this.apiService.getTwoFactorOrganizationProviders(this.organizationId); + return this.twoFactorApiService.getTwoFactorOrganizationProviders(this.organizationId); } protected filterProvider(type: TwoFactorProviderType): boolean { diff --git a/apps/web/src/app/auth/settings/account/change-email.component.spec.ts b/apps/web/src/app/auth/settings/account/change-email.component.spec.ts index bd0d9df9f06..934de0f6453 100644 --- a/apps/web/src/app/auth/settings/account/change-email.component.spec.ts +++ b/apps/web/src/app/auth/settings/account/change-email.component.spec.ts @@ -7,6 +7,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorProviderResponse } from "@bitwarden/common/auth/models/response/two-factor-provider.response"; +import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -22,12 +23,14 @@ describe("ChangeEmailComponent", () => { let fixture: ComponentFixture; let apiService: MockProxy; + let twoFactorApiService: MockProxy; let accountService: FakeAccountService; let keyService: MockProxy; let kdfConfigService: MockProxy; beforeEach(async () => { apiService = mock(); + twoFactorApiService = mock(); keyService = mock(); kdfConfigService = mock(); accountService = mockAccountServiceWith("UserId" as UserId); @@ -37,6 +40,7 @@ describe("ChangeEmailComponent", () => { providers: [ { provide: AccountService, useValue: accountService }, { provide: ApiService, useValue: apiService }, + { provide: TwoFactorApiService, useValue: twoFactorApiService }, { provide: I18nService, useValue: { t: (key: string) => key } }, { provide: KeyService, useValue: keyService }, { provide: MessagingService, useValue: mock() }, @@ -57,7 +61,7 @@ describe("ChangeEmailComponent", () => { describe("ngOnInit", () => { beforeEach(() => { - apiService.getTwoFactorProviders.mockResolvedValue({ + twoFactorApiService.getTwoFactorProviders.mockResolvedValue({ data: [{ type: TwoFactorProviderType.Email, enabled: true } as TwoFactorProviderResponse], } as ListResponse); }); diff --git a/apps/web/src/app/auth/settings/account/change-email.component.ts b/apps/web/src/app/auth/settings/account/change-email.component.ts index a55846a5c0f..b6ca39c6413 100644 --- a/apps/web/src/app/auth/settings/account/change-email.component.ts +++ b/apps/web/src/app/auth/settings/account/change-email.component.ts @@ -8,6 +8,7 @@ import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-p import { EmailTokenRequest } from "@bitwarden/common/auth/models/request/email-token.request"; import { EmailRequest } from "@bitwarden/common/auth/models/request/email.request"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { UserId } from "@bitwarden/common/types/guid"; @@ -37,6 +38,7 @@ export class ChangeEmailComponent implements OnInit { constructor( private accountService: AccountService, private apiService: ApiService, + private twoFactorApiService: TwoFactorApiService, private i18nService: I18nService, private keyService: KeyService, private messagingService: MessagingService, @@ -48,7 +50,7 @@ export class ChangeEmailComponent implements OnInit { async ngOnInit() { this.userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); - const twoFactorProviders = await this.apiService.getTwoFactorProviders(); + const twoFactorProviders = await this.twoFactorApiService.getTwoFactorProviders(); this.showTwoFactorEmailWarning = twoFactorProviders.data.some( (p) => p.type === TwoFactorProviderType.Email && p.enabled, ); diff --git a/apps/web/src/app/auth/settings/account/set-account-verify-devices-dialog.component.ts b/apps/web/src/app/auth/settings/account/set-account-verify-devices-dialog.component.ts index 63a26f08eee..c66f31f6c3b 100644 --- a/apps/web/src/app/auth/settings/account/set-account-verify-devices-dialog.component.ts +++ b/apps/web/src/app/auth/settings/account/set-account-verify-devices-dialog.component.ts @@ -5,11 +5,11 @@ import { firstValueFrom, Subject, takeUntil } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { UserVerificationFormInputComponent } from "@bitwarden/auth/angular"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { SetVerifyDevicesRequest } from "@bitwarden/common/auth/models/request/set-verify-devices.request"; +import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; import { Verification } from "@bitwarden/common/auth/types/verification"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -64,7 +64,7 @@ export class SetAccountVerifyDevicesDialogComponent implements OnInit, OnDestroy private userVerificationService: UserVerificationService, private dialogRef: DialogRef, private toastService: ToastService, - private apiService: ApiService, + private twoFactorApiService: TwoFactorApiService, ) { this.accountService.accountVerifyNewDeviceLogin$ .pipe(takeUntil(this.destroy$)) @@ -74,7 +74,7 @@ export class SetAccountVerifyDevicesDialogComponent implements OnInit, OnDestroy } async ngOnInit() { - const twoFactorProviders = await this.apiService.getTwoFactorProviders(); + const twoFactorProviders = await this.twoFactorApiService.getTwoFactorProviders(); this.has2faConfigured = twoFactorProviders.data.length > 0; } diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.ts index 698e0911b04..d57d6eca894 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.ts @@ -6,13 +6,13 @@ import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from "@angu import { firstValueFrom, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { DisableTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/disable-two-factor-authenticator.request"; import { UpdateTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/update-two-factor-authenticator.request"; import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator.response"; +import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -92,7 +92,7 @@ export class TwoFactorSetupAuthenticatorComponent constructor( @Inject(DIALOG_DATA) protected data: AuthResponse, private dialogRef: DialogRef, - apiService: ApiService, + twoFactorApiService: TwoFactorApiService, i18nService: I18nService, userVerificationService: UserVerificationService, private formBuilder: FormBuilder, @@ -104,7 +104,7 @@ export class TwoFactorSetupAuthenticatorComponent protected toastService: ToastService, ) { super( - apiService, + twoFactorApiService, i18nService, platformUtilsService, logService, @@ -154,7 +154,7 @@ export class TwoFactorSetupAuthenticatorComponent request.key = this.key; request.userVerificationToken = this.userVerificationToken; - const response = await this.apiService.putTwoFactorAuthenticator(request); + const response = await this.twoFactorApiService.putTwoFactorAuthenticator(request); await this.processResponse(response); this.onUpdated.emit(true); } @@ -174,7 +174,7 @@ export class TwoFactorSetupAuthenticatorComponent request.type = this.type; request.key = this.key; request.userVerificationToken = this.userVerificationToken; - await this.apiService.deleteTwoFactorAuthenticator(request); + await this.twoFactorApiService.deleteTwoFactorAuthenticator(request); this.enabled = false; this.toastService.showToast({ variant: "success", diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-duo.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-duo.component.ts index 0efd0c79b4e..bf820e32917 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-duo.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-duo.component.ts @@ -2,11 +2,11 @@ import { CommonModule } from "@angular/common"; import { Component, EventEmitter, Inject, OnInit, Output } from "@angular/core"; import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { UpdateTwoFactorDuoRequest } from "@bitwarden/common/auth/models/request/update-two-factor-duo.request"; import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-duo.response"; +import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -63,7 +63,7 @@ export class TwoFactorSetupDuoComponent constructor( @Inject(DIALOG_DATA) protected data: TwoFactorDuoComponentConfig, - apiService: ApiService, + twoFactorApiService: TwoFactorApiService, i18nService: I18nService, platformUtilsService: PlatformUtilsService, logService: LogService, @@ -74,7 +74,7 @@ export class TwoFactorSetupDuoComponent protected toastService: ToastService, ) { super( - apiService, + twoFactorApiService, i18nService, platformUtilsService, logService, @@ -139,9 +139,12 @@ export class TwoFactorSetupDuoComponent let response: TwoFactorDuoResponse; if (this.organizationId != null) { - response = await this.apiService.putTwoFactorOrganizationDuo(this.organizationId, request); + response = await this.twoFactorApiService.putTwoFactorOrganizationDuo( + this.organizationId, + request, + ); } else { - response = await this.apiService.putTwoFactorDuo(request); + response = await this.twoFactorApiService.putTwoFactorDuo(request); } this.processResponse(response); diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-email.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-email.component.ts index 544f3850ea6..138d541d551 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-email.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-email.component.ts @@ -3,13 +3,13 @@ import { Component, EventEmitter, Inject, OnInit, Output } from "@angular/core"; import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; import { firstValueFrom, map } from "rxjs"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request"; import { UpdateTwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/update-two-factor-email.request"; import { TwoFactorEmailResponse } from "@bitwarden/common/auth/models/response/two-factor-email.response"; +import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -66,7 +66,7 @@ export class TwoFactorSetupEmailComponent constructor( @Inject(DIALOG_DATA) protected data: AuthResponse, - apiService: ApiService, + twoFactorApiService: TwoFactorApiService, i18nService: I18nService, platformUtilsService: PlatformUtilsService, logService: LogService, @@ -78,7 +78,7 @@ export class TwoFactorSetupEmailComponent protected toastService: ToastService, ) { super( - apiService, + twoFactorApiService, i18nService, platformUtilsService, logService, @@ -131,7 +131,7 @@ export class TwoFactorSetupEmailComponent sendEmail = async () => { const request = await this.buildRequestModel(TwoFactorEmailRequest); request.email = this.email; - this.emailPromise = this.apiService.postTwoFactorEmailSetup(request); + this.emailPromise = this.twoFactorApiService.postTwoFactorEmailSetup(request); await this.emailPromise; this.sentEmail = this.email; }; @@ -141,7 +141,7 @@ export class TwoFactorSetupEmailComponent request.email = this.email; request.token = this.token; - const response = await this.apiService.putTwoFactorEmail(request); + const response = await this.twoFactorApiService.putTwoFactorEmail(request); await this.processResponse(response); this.onUpdated.emit(true); } diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-method-base.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-method-base.component.ts index 7569577e781..aa3b9e1def3 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-method-base.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-method-base.component.ts @@ -1,11 +1,11 @@ import { Directive, EventEmitter, Output } from "@angular/core"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { VerificationType } from "@bitwarden/common/auth/enums/verification-type"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; import { TwoFactorProviderRequest } from "@bitwarden/common/auth/models/request/two-factor-provider.request"; +import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; import { AuthResponseBase } from "@bitwarden/common/auth/types/auth-response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -30,7 +30,7 @@ export abstract class TwoFactorSetupMethodBaseComponent { protected componentName = ""; constructor( - protected apiService: ApiService, + protected twoFactorApiService: TwoFactorApiService, protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, protected logService: LogService, @@ -77,9 +77,12 @@ export abstract class TwoFactorSetupMethodBaseComponent { } request.type = this.type; if (this.organizationId != null) { - promise = this.apiService.putTwoFactorOrganizationDisable(this.organizationId, request); + promise = this.twoFactorApiService.putTwoFactorOrganizationDisable( + this.organizationId, + request, + ); } else { - promise = this.apiService.putTwoFactorDisable(request); + promise = this.twoFactorApiService.putTwoFactorDisable(request); } await promise; this.enabled = false; @@ -111,9 +114,9 @@ export abstract class TwoFactorSetupMethodBaseComponent { } request.type = this.type; if (this.organizationId != null) { - await this.apiService.putTwoFactorOrganizationDisable(this.organizationId, request); + await this.twoFactorApiService.putTwoFactorOrganizationDisable(this.organizationId, request); } else { - await this.apiService.putTwoFactorDisable(request); + await this.twoFactorApiService.putTwoFactorDisable(request); } this.enabled = false; this.toastService.showToast({ diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.ts index 66cd3596063..ff0e971461e 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.ts @@ -3,7 +3,6 @@ import { Component, Inject, NgZone } from "@angular/core"; import { FormControl, FormGroup, ReactiveFormsModule } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; @@ -13,6 +12,7 @@ import { ChallengeResponse, TwoFactorWebAuthnResponse, } from "@bitwarden/common/auth/models/response/two-factor-web-authn.response"; +import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -79,7 +79,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom constructor( @Inject(DIALOG_DATA) protected data: AuthResponse, private dialogRef: DialogRef, - apiService: ApiService, + twoFactorApiService: TwoFactorApiService, i18nService: I18nService, platformUtilsService: PlatformUtilsService, private ngZone: NgZone, @@ -89,7 +89,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom toastService: ToastService, ) { super( - apiService, + twoFactorApiService, i18nService, platformUtilsService, logService, @@ -127,7 +127,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom request.id = this.keyIdAvailable; request.name = this.formGroup.value.name || ""; - const response = await this.apiService.putTwoFactorWebAuthn(request); + const response = await this.twoFactorApiService.putTwoFactorWebAuthn(request); this.processResponse(response); this.toastService.showToast({ title: this.i18nService.t("success"), @@ -163,7 +163,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom const request = await this.buildRequestModel(UpdateTwoFactorWebAuthnDeleteRequest); request.id = key.id; try { - key.removePromise = this.apiService.deleteTwoFactorWebAuthn(request); + key.removePromise = this.twoFactorApiService.deleteTwoFactorWebAuthn(request); const response = await key.removePromise; key.removePromise = null; await this.processResponse(response); @@ -177,7 +177,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom return; } const request = await this.buildRequestModel(SecretVerificationRequest); - this.challengePromise = this.apiService.getTwoFactorWebAuthnChallenge(request); + this.challengePromise = this.twoFactorApiService.getTwoFactorWebAuthnChallenge(request); const challenge = await this.challengePromise; this.readDevice(challenge); }; diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-yubikey.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-yubikey.component.ts index 0b85d219928..4e4691a5f60 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-yubikey.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-yubikey.component.ts @@ -9,11 +9,11 @@ import { } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { UpdateTwoFactorYubikeyOtpRequest } from "@bitwarden/common/auth/models/request/update-two-factor-yubikey-otp.request"; import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key.response"; +import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -93,7 +93,7 @@ export class TwoFactorSetupYubiKeyComponent constructor( @Inject(DIALOG_DATA) protected data: AuthResponse, - apiService: ApiService, + twoFactorApiService: TwoFactorApiService, i18nService: I18nService, platformUtilsService: PlatformUtilsService, logService: LogService, @@ -103,7 +103,7 @@ export class TwoFactorSetupYubiKeyComponent protected toastService: ToastService, ) { super( - apiService, + twoFactorApiService, i18nService, platformUtilsService, logService, @@ -176,7 +176,7 @@ export class TwoFactorSetupYubiKeyComponent request.key5 = keys != null && keys.length > 4 ? (keys[4]?.key ?? "") : ""; request.nfc = this.formGroup.value.anyKeyHasNfc ?? false; - this.processResponse(await this.apiService.putTwoFactorYubiKey(request)); + this.processResponse(await this.twoFactorApiService.putTwoFactorYubiKey(request)); this.refreshFormArrayData(); this.toastService.showToast({ title: this.i18nService.t("success"), diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts index 043c27998cd..c3a55ad661e 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts @@ -13,7 +13,6 @@ import { } from "rxjs"; import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/premium-badge"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; @@ -26,6 +25,7 @@ import { TwoFactorWebAuthnResponse } from "@bitwarden/common/auth/models/respons import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key.response"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { TwoFactorProviders } from "@bitwarden/common/auth/services/two-factor.service"; +import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { ProductTierType } from "@bitwarden/common/billing/enums"; @@ -68,7 +68,7 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { constructor( protected dialogService: DialogService, - protected apiService: ApiService, + protected twoFactorApiService: TwoFactorApiService, protected messagingService: MessagingService, protected policyService: PolicyService, billingAccountProfileStateService: BillingAccountProfileStateService, @@ -270,7 +270,7 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { } protected getTwoFactorProviders() { - return this.apiService.getTwoFactorProviders(); + return this.twoFactorApiService.getTwoFactorProviders(); } protected filterProvider(type: TwoFactorProviderType): boolean { diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-verify.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-verify.component.ts index 07939db7eff..a2c734ed2d5 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-verify.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-verify.component.ts @@ -2,11 +2,11 @@ import { Component, EventEmitter, Inject, Output } from "@angular/core"; import { FormControl, FormGroup, ReactiveFormsModule } from "@angular/forms"; import { UserVerificationFormInputComponent } from "@bitwarden/auth/angular"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { VerificationType } from "@bitwarden/common/auth/enums/verification-type"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; +import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; import { TwoFactorResponse } from "@bitwarden/common/auth/types/two-factor-response"; import { Verification } from "@bitwarden/common/auth/types/verification"; @@ -55,7 +55,7 @@ export class TwoFactorVerifyComponent { constructor( @Inject(DIALOG_DATA) protected data: TwoFactorVerifyDialogData, private dialogRef: DialogRef, - private apiService: ApiService, + private twoFactorApiService: TwoFactorApiService, private i18nService: I18nService, private userVerificationService: UserVerificationService, ) { @@ -116,22 +116,22 @@ export class TwoFactorVerifyComponent { private apiCall(request: SecretVerificationRequest): Promise { switch (this.type) { case -1 as TwoFactorProviderType: - return this.apiService.getTwoFactorRecover(request); + return this.twoFactorApiService.getTwoFactorRecover(request); case TwoFactorProviderType.Duo: case TwoFactorProviderType.OrganizationDuo: if (this.organizationId != null) { - return this.apiService.getTwoFactorOrganizationDuo(this.organizationId, request); + return this.twoFactorApiService.getTwoFactorOrganizationDuo(this.organizationId, request); } else { - return this.apiService.getTwoFactorDuo(request); + return this.twoFactorApiService.getTwoFactorDuo(request); } case TwoFactorProviderType.Email: - return this.apiService.getTwoFactorEmail(request); + return this.twoFactorApiService.getTwoFactorEmail(request); case TwoFactorProviderType.WebAuthn: - return this.apiService.getTwoFactorWebAuthn(request); + return this.twoFactorApiService.getTwoFactorWebAuthn(request); case TwoFactorProviderType.Authenticator: - return this.apiService.getTwoFactorAuthenticator(request); + return this.twoFactorApiService.getTwoFactorAuthenticator(request); case TwoFactorProviderType.Yubikey: - return this.apiService.getTwoFactorYubiKey(request); + return this.twoFactorApiService.getTwoFactorYubiKey(request); default: throw new Error(`Unknown two-factor type: ${this.type}`); } diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index c66c74a3ea9..3f9657b8115 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -127,6 +127,7 @@ import { UserVerificationService } from "@bitwarden/common/auth/services/user-ve import { WebAuthnLoginApiService } from "@bitwarden/common/auth/services/webauthn-login/webauthn-login-api.service"; import { WebAuthnLoginPrfKeyService } from "@bitwarden/common/auth/services/webauthn-login/webauthn-login-prf-key.service"; import { WebAuthnLoginService } from "@bitwarden/common/auth/services/webauthn-login/webauthn-login.service"; +import { TwoFactorApiService, DefaultTwoFactorApiService } from "@bitwarden/common/auth/two-factor"; import { AutofillSettingsService, AutofillSettingsServiceAbstraction, @@ -1519,6 +1520,11 @@ const safeProviders: SafeProvider[] = [ useClass: DefaultTwoFactorAuthWebAuthnComponentService, deps: [], }), + safeProvider({ + provide: TwoFactorApiService, + useClass: DefaultTwoFactorApiService, + deps: [ApiServiceAbstraction], + }), safeProvider({ provide: ViewCacheService, useExisting: NoopViewCacheService, diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts index 9b402f3a956..084e8e6e851 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts @@ -4,10 +4,10 @@ import { ReactiveFormsModule, FormsModule, FormControl } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request"; +import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -62,7 +62,7 @@ export class TwoFactorAuthEmailComponent implements OnInit { protected loginStrategyService: LoginStrategyServiceAbstraction, protected platformUtilsService: PlatformUtilsService, protected logService: LogService, - protected apiService: ApiService, + protected twoFactorApiService: TwoFactorApiService, protected appIdService: AppIdService, private toastService: ToastService, private cacheService: TwoFactorAuthEmailComponentCacheService, @@ -131,7 +131,7 @@ export class TwoFactorAuthEmailComponent implements OnInit { request.deviceIdentifier = await this.appIdService.getAppId(); request.authRequestAccessCode = (await this.loginStrategyService.getAccessCode()) ?? ""; request.authRequestId = (await this.loginStrategyService.getAuthRequestId()) ?? ""; - this.emailPromise = this.apiService.postTwoFactorEmail(request); + this.emailPromise = this.twoFactorApiService.postTwoFactorEmail(request); await this.emailPromise; this.emailSent = true; diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index 1cab48148e9..93e47a6d9a8 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -37,8 +37,6 @@ import { ProviderUserUserDetailsResponse, } from "../admin-console/models/response/provider/provider-user.response"; import { SelectionReadOnlyResponse } from "../admin-console/models/response/selection-read-only.response"; -import { DeviceVerificationRequest } from "../auth/models/request/device-verification.request"; -import { DisableTwoFactorAuthenticatorRequest } from "../auth/models/request/disable-two-factor-authenticator.request"; import { EmailTokenRequest } from "../auth/models/request/email-token.request"; import { EmailRequest } from "../auth/models/request/email.request"; import { PasswordTokenRequest } from "../auth/models/request/identity-token/password-token.request"; @@ -48,34 +46,15 @@ import { WebAuthnLoginTokenRequest } from "../auth/models/request/identity-token import { PasswordHintRequest } from "../auth/models/request/password-hint.request"; import { PasswordlessAuthRequest } from "../auth/models/request/passwordless-auth.request"; import { SecretVerificationRequest } from "../auth/models/request/secret-verification.request"; -import { TwoFactorEmailRequest } from "../auth/models/request/two-factor-email.request"; -import { TwoFactorProviderRequest } from "../auth/models/request/two-factor-provider.request"; import { UpdateProfileRequest } from "../auth/models/request/update-profile.request"; -import { UpdateTwoFactorAuthenticatorRequest } from "../auth/models/request/update-two-factor-authenticator.request"; -import { UpdateTwoFactorDuoRequest } from "../auth/models/request/update-two-factor-duo.request"; -import { UpdateTwoFactorEmailRequest } from "../auth/models/request/update-two-factor-email.request"; -import { UpdateTwoFactorWebAuthnDeleteRequest } from "../auth/models/request/update-two-factor-web-authn-delete.request"; -import { UpdateTwoFactorWebAuthnRequest } from "../auth/models/request/update-two-factor-web-authn.request"; -import { UpdateTwoFactorYubikeyOtpRequest } from "../auth/models/request/update-two-factor-yubikey-otp.request"; import { ApiKeyResponse } from "../auth/models/response/api-key.response"; import { AuthRequestResponse } from "../auth/models/response/auth-request.response"; -import { DeviceVerificationResponse } from "../auth/models/response/device-verification.response"; import { IdentityDeviceVerificationResponse } from "../auth/models/response/identity-device-verification.response"; import { IdentityTokenResponse } from "../auth/models/response/identity-token.response"; import { IdentityTwoFactorResponse } from "../auth/models/response/identity-two-factor.response"; import { KeyConnectorUserKeyResponse } from "../auth/models/response/key-connector-user-key.response"; import { PreloginResponse } from "../auth/models/response/prelogin.response"; import { SsoPreValidateResponse } from "../auth/models/response/sso-pre-validate.response"; -import { TwoFactorAuthenticatorResponse } from "../auth/models/response/two-factor-authenticator.response"; -import { TwoFactorDuoResponse } from "../auth/models/response/two-factor-duo.response"; -import { TwoFactorEmailResponse } from "../auth/models/response/two-factor-email.response"; -import { TwoFactorProviderResponse } from "../auth/models/response/two-factor-provider.response"; -import { TwoFactorRecoverResponse } from "../auth/models/response/two-factor-recover.response"; -import { - ChallengeResponse, - TwoFactorWebAuthnResponse, -} from "../auth/models/response/two-factor-web-authn.response"; -import { TwoFactorYubiKeyResponse } from "../auth/models/response/two-factor-yubi-key.response"; import { BitPayInvoiceRequest } from "../billing/models/request/bit-pay-invoice.request"; import { BillingHistoryResponse } from "../billing/models/response/billing-history.response"; import { PaymentResponse } from "../billing/models/response/payment.response"; @@ -306,66 +285,6 @@ export abstract class ApiService { abstract getSettingsDomains(): Promise; abstract putSettingsDomains(request: UpdateDomainsRequest): Promise; - abstract getTwoFactorProviders(): Promise>; - abstract getTwoFactorOrganizationProviders( - organizationId: string, - ): Promise>; - abstract getTwoFactorAuthenticator( - request: SecretVerificationRequest, - ): Promise; - abstract getTwoFactorEmail(request: SecretVerificationRequest): Promise; - abstract getTwoFactorDuo(request: SecretVerificationRequest): Promise; - abstract getTwoFactorOrganizationDuo( - organizationId: string, - request: SecretVerificationRequest, - ): Promise; - abstract getTwoFactorYubiKey( - request: SecretVerificationRequest, - ): Promise; - abstract getTwoFactorWebAuthn( - request: SecretVerificationRequest, - ): Promise; - abstract getTwoFactorWebAuthnChallenge( - request: SecretVerificationRequest, - ): Promise; - abstract getTwoFactorRecover( - request: SecretVerificationRequest, - ): Promise; - abstract putTwoFactorAuthenticator( - request: UpdateTwoFactorAuthenticatorRequest, - ): Promise; - abstract deleteTwoFactorAuthenticator( - request: DisableTwoFactorAuthenticatorRequest, - ): Promise; - abstract putTwoFactorEmail(request: UpdateTwoFactorEmailRequest): Promise; - abstract putTwoFactorDuo(request: UpdateTwoFactorDuoRequest): Promise; - abstract putTwoFactorOrganizationDuo( - organizationId: string, - request: UpdateTwoFactorDuoRequest, - ): Promise; - abstract putTwoFactorYubiKey( - request: UpdateTwoFactorYubikeyOtpRequest, - ): Promise; - abstract putTwoFactorWebAuthn( - request: UpdateTwoFactorWebAuthnRequest, - ): Promise; - abstract deleteTwoFactorWebAuthn( - request: UpdateTwoFactorWebAuthnDeleteRequest, - ): Promise; - abstract putTwoFactorDisable( - request: TwoFactorProviderRequest, - ): Promise; - abstract putTwoFactorOrganizationDisable( - organizationId: string, - request: TwoFactorProviderRequest, - ): Promise; - abstract postTwoFactorEmailSetup(request: TwoFactorEmailRequest): Promise; - abstract postTwoFactorEmail(request: TwoFactorEmailRequest): Promise; - abstract getDeviceVerificationSettings(): Promise; - abstract putDeviceVerificationSettings( - request: DeviceVerificationRequest, - ): Promise; - abstract getCloudCommunicationsEnabled(): Promise; abstract getOrganizationConnection( id: string, diff --git a/libs/common/src/auth/models/request/device-verification.request.ts b/libs/common/src/auth/models/request/device-verification.request.ts deleted file mode 100644 index 5e119efa190..00000000000 --- a/libs/common/src/auth/models/request/device-verification.request.ts +++ /dev/null @@ -1,7 +0,0 @@ -export class DeviceVerificationRequest { - unknownDeviceVerificationEnabled: boolean; - - constructor(unknownDeviceVerificationEnabled: boolean) { - this.unknownDeviceVerificationEnabled = unknownDeviceVerificationEnabled; - } -} diff --git a/libs/common/src/auth/models/response/device-verification.response.ts b/libs/common/src/auth/models/response/device-verification.response.ts deleted file mode 100644 index d703605703f..00000000000 --- a/libs/common/src/auth/models/response/device-verification.response.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { BaseResponse } from "../../../models/response/base.response"; - -export class DeviceVerificationResponse extends BaseResponse { - isDeviceVerificationSectionEnabled: boolean; - unknownDeviceVerificationEnabled: boolean; - - constructor(response: any) { - super(response); - this.isDeviceVerificationSectionEnabled = this.getResponseProperty( - "IsDeviceVerificationSectionEnabled", - ); - this.unknownDeviceVerificationEnabled = this.getResponseProperty( - "UnknownDeviceVerificationEnabled", - ); - } -} diff --git a/libs/common/src/auth/two-factor/default-two-factor-api.service.ts b/libs/common/src/auth/two-factor/default-two-factor-api.service.ts new file mode 100644 index 00000000000..93f7b207922 --- /dev/null +++ b/libs/common/src/auth/two-factor/default-two-factor-api.service.ts @@ -0,0 +1,272 @@ +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { DisableTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/disable-two-factor-authenticator.request"; +import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; +import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request"; +import { TwoFactorProviderRequest } from "@bitwarden/common/auth/models/request/two-factor-provider.request"; +import { UpdateTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/update-two-factor-authenticator.request"; +import { UpdateTwoFactorDuoRequest } from "@bitwarden/common/auth/models/request/update-two-factor-duo.request"; +import { UpdateTwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/update-two-factor-email.request"; +import { UpdateTwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/models/request/update-two-factor-web-authn-delete.request"; +import { UpdateTwoFactorWebAuthnRequest } from "@bitwarden/common/auth/models/request/update-two-factor-web-authn.request"; +import { UpdateTwoFactorYubikeyOtpRequest } from "@bitwarden/common/auth/models/request/update-two-factor-yubikey-otp.request"; +import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator.response"; +import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-duo.response"; +import { TwoFactorEmailResponse } from "@bitwarden/common/auth/models/response/two-factor-email.response"; +import { TwoFactorProviderResponse } from "@bitwarden/common/auth/models/response/two-factor-provider.response"; +import { TwoFactorRecoverResponse } from "@bitwarden/common/auth/models/response/two-factor-recover.response"; +import { + TwoFactorWebAuthnResponse, + ChallengeResponse, +} from "@bitwarden/common/auth/models/response/two-factor-web-authn.response"; +import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key.response"; +import { ListResponse } from "@bitwarden/common/models/response/list.response"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; + +import { TwoFactorApiService } from "./two-factor-api.service"; + +export class DefaultTwoFactorApiService implements TwoFactorApiService { + constructor(private apiService: ApiService) {} + + // Providers + + async getTwoFactorProviders(): Promise> { + const response = await this.apiService.send("GET", "/two-factor", null, true, true); + return new ListResponse(response, TwoFactorProviderResponse); + } + + async getTwoFactorOrganizationProviders( + organizationId: string, + ): Promise> { + const response = await this.apiService.send( + "GET", + `/organizations/${organizationId}/two-factor`, + null, + true, + true, + ); + return new ListResponse(response, TwoFactorProviderResponse); + } + + // Authenticator (TOTP) + + async getTwoFactorAuthenticator( + request: SecretVerificationRequest, + ): Promise { + const response = await this.apiService.send( + "POST", + "/two-factor/get-authenticator", + request, + true, + true, + ); + return new TwoFactorAuthenticatorResponse(response); + } + + async putTwoFactorAuthenticator( + request: UpdateTwoFactorAuthenticatorRequest, + ): Promise { + const response = await this.apiService.send( + "PUT", + "/two-factor/authenticator", + request, + true, + true, + ); + return new TwoFactorAuthenticatorResponse(response); + } + + async deleteTwoFactorAuthenticator( + request: DisableTwoFactorAuthenticatorRequest, + ): Promise { + const response = await this.apiService.send( + "DELETE", + "/two-factor/authenticator", + request, + true, + true, + ); + return new TwoFactorProviderResponse(response); + } + + // Email + + async getTwoFactorEmail(request: SecretVerificationRequest): Promise { + const response = await this.apiService.send( + "POST", + "/two-factor/get-email", + request, + true, + true, + ); + return new TwoFactorEmailResponse(response); + } + + async postTwoFactorEmailSetup(request: TwoFactorEmailRequest): Promise { + return this.apiService.send("POST", "/two-factor/send-email", request, true, false); + } + + async postTwoFactorEmail(request: TwoFactorEmailRequest): Promise { + return this.apiService.send("POST", "/two-factor/send-email-login", request, false, false); + } + + async putTwoFactorEmail(request: UpdateTwoFactorEmailRequest): Promise { + const response = await this.apiService.send("PUT", "/two-factor/email", request, true, true); + return new TwoFactorEmailResponse(response); + } + + // Duo + + async getTwoFactorDuo(request: SecretVerificationRequest): Promise { + const response = await this.apiService.send("POST", "/two-factor/get-duo", request, true, true); + return new TwoFactorDuoResponse(response); + } + + async getTwoFactorOrganizationDuo( + organizationId: string, + request: SecretVerificationRequest, + ): Promise { + const response = await this.apiService.send( + "POST", + `/organizations/${organizationId}/two-factor/get-duo`, + request, + true, + true, + ); + return new TwoFactorDuoResponse(response); + } + + async putTwoFactorDuo(request: UpdateTwoFactorDuoRequest): Promise { + const response = await this.apiService.send("PUT", "/two-factor/duo", request, true, true); + return new TwoFactorDuoResponse(response); + } + + async putTwoFactorOrganizationDuo( + organizationId: string, + request: UpdateTwoFactorDuoRequest, + ): Promise { + const response = await this.apiService.send( + "PUT", + `/organizations/${organizationId}/two-factor/duo`, + request, + true, + true, + ); + return new TwoFactorDuoResponse(response); + } + + // YubiKey + + async getTwoFactorYubiKey(request: SecretVerificationRequest): Promise { + const response = await this.apiService.send( + "POST", + "/two-factor/get-yubikey", + request, + true, + true, + ); + return new TwoFactorYubiKeyResponse(response); + } + + async putTwoFactorYubiKey( + request: UpdateTwoFactorYubikeyOtpRequest, + ): Promise { + const response = await this.apiService.send("PUT", "/two-factor/yubikey", request, true, true); + return new TwoFactorYubiKeyResponse(response); + } + + // WebAuthn + + async getTwoFactorWebAuthn( + request: SecretVerificationRequest, + ): Promise { + const response = await this.apiService.send( + "POST", + "/two-factor/get-webauthn", + request, + true, + true, + ); + return new TwoFactorWebAuthnResponse(response); + } + + async getTwoFactorWebAuthnChallenge( + request: SecretVerificationRequest, + ): Promise { + const response = await this.apiService.send( + "POST", + "/two-factor/get-webauthn-challenge", + request, + true, + true, + ); + return new ChallengeResponse(response); + } + + async putTwoFactorWebAuthn( + request: UpdateTwoFactorWebAuthnRequest, + ): Promise { + const deviceResponse = request.deviceResponse.response as AuthenticatorAttestationResponse; + const body: any = Object.assign({}, request); + + body.deviceResponse = { + id: request.deviceResponse.id, + rawId: btoa(request.deviceResponse.id), + type: request.deviceResponse.type, + extensions: request.deviceResponse.getClientExtensionResults(), + response: { + AttestationObject: Utils.fromBufferToB64(deviceResponse.attestationObject), + clientDataJson: Utils.fromBufferToB64(deviceResponse.clientDataJSON), + }, + }; + + const response = await this.apiService.send("PUT", "/two-factor/webauthn", body, true, true); + return new TwoFactorWebAuthnResponse(response); + } + + async deleteTwoFactorWebAuthn( + request: UpdateTwoFactorWebAuthnDeleteRequest, + ): Promise { + const response = await this.apiService.send( + "DELETE", + "/two-factor/webauthn", + request, + true, + true, + ); + return new TwoFactorWebAuthnResponse(response); + } + + // Recovery Code + + async getTwoFactorRecover(request: SecretVerificationRequest): Promise { + const response = await this.apiService.send( + "POST", + "/two-factor/get-recover", + request, + true, + true, + ); + return new TwoFactorRecoverResponse(response); + } + + // Disable + + async putTwoFactorDisable(request: TwoFactorProviderRequest): Promise { + const response = await this.apiService.send("PUT", "/two-factor/disable", request, true, true); + return new TwoFactorProviderResponse(response); + } + + async putTwoFactorOrganizationDisable( + organizationId: string, + request: TwoFactorProviderRequest, + ): Promise { + const response = await this.apiService.send( + "PUT", + `/organizations/${organizationId}/two-factor/disable`, + request, + true, + true, + ); + return new TwoFactorProviderResponse(response); + } +} diff --git a/libs/common/src/auth/two-factor/index.ts b/libs/common/src/auth/two-factor/index.ts new file mode 100644 index 00000000000..85e072403b7 --- /dev/null +++ b/libs/common/src/auth/two-factor/index.ts @@ -0,0 +1,2 @@ +export { TwoFactorApiService } from "./two-factor-api.service"; +export { DefaultTwoFactorApiService } from "./default-two-factor-api.service"; diff --git a/libs/common/src/auth/two-factor/two-factor-api.service.spec.ts b/libs/common/src/auth/two-factor/two-factor-api.service.spec.ts new file mode 100644 index 00000000000..c54790d9f76 --- /dev/null +++ b/libs/common/src/auth/two-factor/two-factor-api.service.spec.ts @@ -0,0 +1,697 @@ +import { mock, MockProxy } from "jest-mock-extended"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { DisableTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/disable-two-factor-authenticator.request"; +import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; +import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request"; +import { TwoFactorProviderRequest } from "@bitwarden/common/auth/models/request/two-factor-provider.request"; +import { UpdateTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/update-two-factor-authenticator.request"; +import { UpdateTwoFactorDuoRequest } from "@bitwarden/common/auth/models/request/update-two-factor-duo.request"; +import { UpdateTwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/update-two-factor-email.request"; +import { UpdateTwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/models/request/update-two-factor-web-authn-delete.request"; +import { UpdateTwoFactorWebAuthnRequest } from "@bitwarden/common/auth/models/request/update-two-factor-web-authn.request"; +import { UpdateTwoFactorYubikeyOtpRequest } from "@bitwarden/common/auth/models/request/update-two-factor-yubikey-otp.request"; +import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator.response"; +import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-duo.response"; +import { TwoFactorEmailResponse } from "@bitwarden/common/auth/models/response/two-factor-email.response"; +import { TwoFactorProviderResponse } from "@bitwarden/common/auth/models/response/two-factor-provider.response"; +import { TwoFactorRecoverResponse } from "@bitwarden/common/auth/models/response/two-factor-recover.response"; +import { + TwoFactorWebAuthnResponse, + ChallengeResponse, +} from "@bitwarden/common/auth/models/response/two-factor-web-authn.response"; +import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key.response"; +import { ListResponse } from "@bitwarden/common/models/response/list.response"; + +import { DefaultTwoFactorApiService } from "./default-two-factor-api.service"; + +describe("TwoFactorApiService", () => { + let apiService: MockProxy; + let twoFactorApiService: DefaultTwoFactorApiService; + + beforeEach(() => { + apiService = mock(); + twoFactorApiService = new DefaultTwoFactorApiService(apiService); + }); + + describe("Two-Factor Providers", () => { + describe("getTwoFactorProviders", () => { + it("retrieves all enabled two-factor providers for the current user", async () => { + const mockResponse = { + data: [ + { Type: 0, Enabled: true }, + { Type: 1, Enabled: true }, + ], + }; + apiService.send.mockResolvedValue(mockResponse); + + const result = await twoFactorApiService.getTwoFactorProviders(); + + expect(apiService.send).toHaveBeenCalledWith("GET", "/two-factor", null, true, true); + expect(result).toBeInstanceOf(ListResponse); + expect(result.data).toHaveLength(2); + for (let i = 0; i < result.data.length; i++) { + expect(result.data[i]).toBeInstanceOf(TwoFactorProviderResponse); + expect(result.data[i].type).toBe(i); + expect(result.data[i].enabled).toBe(true); + } + }); + }); + + describe("getTwoFactorOrganizationProviders", () => { + it("retrieves all enabled two-factor providers for a specific organization", async () => { + const organizationId = "org-123"; + const mockResponse = { + data: [{ Type: 6, Enabled: true }], + }; + apiService.send.mockResolvedValue(mockResponse); + + const result = await twoFactorApiService.getTwoFactorOrganizationProviders(organizationId); + + expect(apiService.send).toHaveBeenCalledWith( + "GET", + `/organizations/${organizationId}/two-factor`, + null, + true, + true, + ); + expect(result).toBeInstanceOf(ListResponse); + expect(result.data[0]).toBeInstanceOf(TwoFactorProviderResponse); + expect(result.data[0].enabled).toBe(true); + expect(result.data[0].type).toBe(6); // Duo + }); + }); + }); + + describe("Authenticator (TOTP) APIs", () => { + describe("getTwoFactorAuthenticator", () => { + it("retrieves authenticator configuration with secret key after user verification", async () => { + const request = new SecretVerificationRequest(); + request.masterPasswordHash = "master-password-hash"; + const mockResponse = { + Enabled: false, + Key: "MFRGGZDFMZTWQ2LK", + }; + apiService.send.mockResolvedValue(mockResponse); + + const result = await twoFactorApiService.getTwoFactorAuthenticator(request); + + expect(apiService.send).toHaveBeenCalledWith( + "POST", + "/two-factor/get-authenticator", + request, + true, + true, + ); + expect(result).toBeInstanceOf(TwoFactorAuthenticatorResponse); + expect(result.enabled).toBe(false); + }); + }); + + describe("putTwoFactorAuthenticator", () => { + it("enables authenticator after validating the provided token", async () => { + const request = new UpdateTwoFactorAuthenticatorRequest(); + request.token = "123456"; + request.key = "MFRGGZDFMZTWQ2LK"; + const mockResponse = { + Enabled: true, + Key: "MFRGGZDFMZTWQ2LK", + }; + apiService.send.mockResolvedValue(mockResponse); + + const result = await twoFactorApiService.putTwoFactorAuthenticator(request); + + expect(apiService.send).toHaveBeenCalledWith( + "PUT", + "/two-factor/authenticator", + request, + true, + true, + ); + expect(result).toBeInstanceOf(TwoFactorAuthenticatorResponse); + expect(result.enabled).toBe(true); + expect(result.key).toBeDefined(); + }); + }); + + describe("deleteTwoFactorAuthenticator", () => { + it("disables authenticator two-factor authentication", async () => { + const request = new DisableTwoFactorAuthenticatorRequest(); + request.masterPasswordHash = "master-password-hash"; + const mockResponse = { + Enabled: false, + Type: 0, + }; + apiService.send.mockResolvedValue(mockResponse); + + const result = await twoFactorApiService.deleteTwoFactorAuthenticator(request); + + expect(apiService.send).toHaveBeenCalledWith( + "DELETE", + "/two-factor/authenticator", + request, + true, + true, + ); + expect(result).toBeInstanceOf(TwoFactorProviderResponse); + expect(result.enabled).toBe(false); + expect(result.type).toBe(0); // Authenticator + }); + }); + }); + + describe("Email APIs", () => { + describe("getTwoFactorEmail", () => { + it("retrieves email two-factor configuration after user verification", async () => { + const request = new SecretVerificationRequest(); + request.masterPasswordHash = "master-password-hash"; + const mockResponse = { + Enabled: true, + Email: "user@example.com", + }; + apiService.send.mockResolvedValue(mockResponse); + + const result = await twoFactorApiService.getTwoFactorEmail(request); + + expect(apiService.send).toHaveBeenCalledWith( + "POST", + "/two-factor/get-email", + request, + true, + true, + ); + expect(result).toBeInstanceOf(TwoFactorEmailResponse); + expect(result.enabled).toBe(true); + expect(result.email).toBeDefined(); + }); + }); + + describe("postTwoFactorEmailSetup", () => { + it("sends verification code to email address during two-factor setup", async () => { + const request = new TwoFactorEmailRequest(); + request.email = "user@example.com"; + request.masterPasswordHash = "master-password-hash"; + + await twoFactorApiService.postTwoFactorEmailSetup(request); + + expect(apiService.send).toHaveBeenCalledWith( + "POST", + "/two-factor/send-email", + request, + true, + false, + ); + }); + }); + + describe("postTwoFactorEmail", () => { + it("sends two-factor authentication code during login flow", async () => { + const request = new TwoFactorEmailRequest(); + request.email = "user@example.com"; + // Note: masterPasswordHash not required for login flow + + await twoFactorApiService.postTwoFactorEmail(request); + + expect(apiService.send).toHaveBeenCalledWith( + "POST", + "/two-factor/send-email-login", + request, + false, + false, + ); + }); + }); + + describe("putTwoFactorEmail", () => { + it("enables email two-factor after validating the verification code", async () => { + const request = new UpdateTwoFactorEmailRequest(); + request.email = "user@example.com"; + request.token = "verification-code"; + const mockResponse = { + Enabled: true, + Email: "user@example.com", + }; + apiService.send.mockResolvedValue(mockResponse); + + const result = await twoFactorApiService.putTwoFactorEmail(request); + + expect(apiService.send).toHaveBeenCalledWith( + "PUT", + "/two-factor/email", + request, + true, + true, + ); + expect(result).toBeInstanceOf(TwoFactorEmailResponse); + expect(result.enabled).toBe(true); + expect(result.email).toBeDefined(); + }); + }); + }); + + describe("Duo APIs", () => { + describe("getTwoFactorDuo", () => { + it("retrieves Duo configuration for premium user after verification", async () => { + const request = new SecretVerificationRequest(); + request.masterPasswordHash = "master-password-hash"; + const mockResponse = { + Enabled: true, + Host: "api-abc123.duosecurity.com", + ClientId: "DI9ABC1DEFGH2JKL", + ClientSecret: "client******", + }; + apiService.send.mockResolvedValue(mockResponse); + + const result = await twoFactorApiService.getTwoFactorDuo(request); + + expect(apiService.send).toHaveBeenCalledWith( + "POST", + "/two-factor/get-duo", + request, + true, + true, + ); + expect(result).toBeInstanceOf(TwoFactorDuoResponse); + expect(result.enabled).toBe(true); + expect(result.host).toBeDefined(); + expect(result.clientId).toBeDefined(); + expect(result.clientSecret).toContain("******"); + }); + }); + + describe("getTwoFactorOrganizationDuo", () => { + it("retrieves Duo configuration for organization with admin permissions", async () => { + const organizationId = "org-123"; + const request = new SecretVerificationRequest(); + request.masterPasswordHash = "master-password-hash"; + const mockResponse = { + Enabled: true, + Host: "api-xyz789.duosecurity.com", + ClientId: "DI4XYZ9MNOP3QRS", + ClientSecret: "orgcli******", + }; + apiService.send.mockResolvedValue(mockResponse); + + const result = await twoFactorApiService.getTwoFactorOrganizationDuo( + organizationId, + request, + ); + + expect(apiService.send).toHaveBeenCalledWith( + "POST", + `/organizations/${organizationId}/two-factor/get-duo`, + request, + true, + true, + ); + expect(result).toBeInstanceOf(TwoFactorDuoResponse); + expect(result.enabled).toBe(true); + expect(result.host).toBeDefined(); + expect(result.clientId).toBeDefined(); + expect(result.clientSecret).toContain("******"); + }); + }); + + describe("putTwoFactorDuo", () => { + it("enables Duo two-factor for premium user with valid integration details", async () => { + const request = new UpdateTwoFactorDuoRequest(); + request.host = "api-abc123.duosecurity.com"; + request.clientId = "DI9ABC1DEFGH2JKL"; + request.clientSecret = "client-secret-value-here"; + const mockResponse = { + Enabled: true, + Host: "api-abc123.duosecurity.com", + ClientId: "DI9ABC1DEFGH2JKL", + ClientSecret: "client******", + }; + apiService.send.mockResolvedValue(mockResponse); + + const result = await twoFactorApiService.putTwoFactorDuo(request); + + expect(apiService.send).toHaveBeenCalledWith("PUT", "/two-factor/duo", request, true, true); + expect(result).toBeInstanceOf(TwoFactorDuoResponse); + expect(result.enabled).toBe(true); + expect(result.host).toBeDefined(); + expect(result.clientId).toBeDefined(); + expect(result.clientSecret).toContain("******"); + }); + }); + + describe("putTwoFactorOrganizationDuo", () => { + it("enables organization-level Duo with policy management permissions", async () => { + const organizationId = "org-123"; + const request = new UpdateTwoFactorDuoRequest(); + request.host = "api-xyz789.duosecurity.com"; + request.clientId = "DI4XYZ9MNOP3QRS"; + request.clientSecret = "orgcli-secret-value-here"; + const mockResponse = { + Enabled: true, + Host: "api-xyz789.duosecurity.com", + ClientId: "DI4XYZ9MNOP3QRS", + ClientSecret: "orgcli******", + }; + apiService.send.mockResolvedValue(mockResponse); + + const result = await twoFactorApiService.putTwoFactorOrganizationDuo( + organizationId, + request, + ); + + expect(apiService.send).toHaveBeenCalledWith( + "PUT", + `/organizations/${organizationId}/two-factor/duo`, + request, + true, + true, + ); + expect(result).toBeInstanceOf(TwoFactorDuoResponse); + expect(result.enabled).toBe(true); + expect(result.host).toBeDefined(); + expect(result.clientId).toBeDefined(); + expect(result.clientSecret).toContain("******"); + }); + }); + }); + + describe("YubiKey APIs", () => { + describe("getTwoFactorYubiKey", () => { + it("retrieves YubiKey configuration for premium user after verification", async () => { + const request = new SecretVerificationRequest(); + request.masterPasswordHash = "master-password-hash"; + const mockResponse = { + Enabled: true, + Key1: "cccccccccccc", + Key2: "dddddddddddd", + }; + apiService.send.mockResolvedValue(mockResponse); + + const result = await twoFactorApiService.getTwoFactorYubiKey(request); + + expect(apiService.send).toHaveBeenCalledWith( + "POST", + "/two-factor/get-yubikey", + request, + true, + true, + ); + expect(result).toBeInstanceOf(TwoFactorYubiKeyResponse); + expect(result.enabled).toBe(true); + expect(result.key1).toBeDefined(); + expect(result.key2).toBeDefined(); + }); + }); + + describe("putTwoFactorYubiKey", () => { + it("enables YubiKey two-factor for premium user after validating device OTPs", async () => { + const request = new UpdateTwoFactorYubikeyOtpRequest(); + request.key1 = "ccccccccccccjkhbhbhrkcitringjkrjirfjuunlnlvcghnkrtgfj"; + request.key2 = "ddddddddddddvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv"; + const mockResponse = { + Enabled: true, + Key1: "cccccccccccc", + Key2: "dddddddddddd", + Nfc: false, + }; + apiService.send.mockResolvedValue(mockResponse); + + const result = await twoFactorApiService.putTwoFactorYubiKey(request); + + expect(apiService.send).toHaveBeenCalledWith( + "PUT", + "/two-factor/yubikey", + request, + true, + true, + ); + expect(result).toBeInstanceOf(TwoFactorYubiKeyResponse); + expect(result.enabled).toBe(true); + expect(result.key1).toBeDefined(); + expect(result.key2).toBeDefined(); + }); + }); + }); + + describe("WebAuthn APIs", () => { + describe("getTwoFactorWebAuthn", () => { + it("retrieves list of registered WebAuthn credentials after verification", async () => { + const request = new SecretVerificationRequest(); + request.masterPasswordHash = "master-password-hash"; + const mockResponse = { + Enabled: true, + Keys: [ + { Name: "YubiKey 5", Id: 1, Migrated: false }, + { Name: "Security Key", Id: 2, Migrated: true }, + ], + }; + apiService.send.mockResolvedValue(mockResponse); + + const result = await twoFactorApiService.getTwoFactorWebAuthn(request); + + expect(apiService.send).toHaveBeenCalledWith( + "POST", + "/two-factor/get-webauthn", + request, + true, + true, + ); + expect(result).toBeInstanceOf(TwoFactorWebAuthnResponse); + expect(result.enabled).toBe(true); + expect(result.keys).toHaveLength(2); + result.keys.forEach((key) => { + expect(key).toHaveProperty("name"); + expect(key).toHaveProperty("id"); + expect(key).toHaveProperty("migrated"); + }); + }); + }); + + describe("getTwoFactorWebAuthnChallenge", () => { + it("obtains cryptographic challenge for WebAuthn credential registration", async () => { + const request = new SecretVerificationRequest(); + request.masterPasswordHash = "master-password-hash"; + const mockResponse = { + challenge: "Y2hhbGxlbmdlLXN0cmluZw", + rp: { name: "Bitwarden" }, + user: { + id: "dXNlci1pZA", + name: "user@example.com", + displayName: "User", + }, + pubKeyCredParams: [{ type: "public-key", alg: -7 }], // ES256 + excludeCredentials: [] as PublicKeyCredentialDescriptor[], + timeout: 60000, + }; + apiService.send.mockResolvedValue(mockResponse); + + const result = await twoFactorApiService.getTwoFactorWebAuthnChallenge(request); + + expect(apiService.send).toHaveBeenCalledWith( + "POST", + "/two-factor/get-webauthn-challenge", + request, + true, + true, + ); + expect(result).toBeInstanceOf(ChallengeResponse); + expect(result.challenge).toBeDefined(); + expect(result.rp).toHaveProperty("name", "Bitwarden"); + expect(result.user).toHaveProperty("id"); + expect(result.user).toHaveProperty("name"); + expect(result.user).toHaveProperty("displayName", "User"); + expect(result.pubKeyCredParams).toHaveLength(1); + expect(Number(result.timeout)).toBeTruthy(); + }); + }); + + describe("putTwoFactorWebAuthn", () => { + it("registers new WebAuthn credential by serializing browser credential to JSON", async () => { + const mockAttestationResponse: Partial = { + clientDataJSON: new Uint8Array([1, 2, 3]).buffer, + attestationObject: new Uint8Array([4, 5, 6]).buffer, + }; + + const mockCredential: Partial = { + id: "credential-id", + type: "public-key", + response: mockAttestationResponse as AuthenticatorAttestationResponse, + getClientExtensionResults: jest.fn().mockReturnValue({}), + }; + + const request = new UpdateTwoFactorWebAuthnRequest(); + request.deviceResponse = mockCredential as PublicKeyCredential; + request.name = "My Security Key"; + + const mockResponse = { + Enabled: true, + Keys: [{ Name: "My Security Key", Id: 1, Migrated: false }], + }; + apiService.send.mockResolvedValue(mockResponse); + + const result = await twoFactorApiService.putTwoFactorWebAuthn(request); + + expect(apiService.send).toHaveBeenCalledWith( + "PUT", + "/two-factor/webauthn", + expect.objectContaining({ + name: "My Security Key", + deviceResponse: expect.objectContaining({ + id: "credential-id", + rawId: expect.any(String), // base64 encoded + type: "public-key", + extensions: {}, + response: expect.objectContaining({ + AttestationObject: expect.any(String), // base64 encoded + clientDataJson: expect.any(String), // base64 encoded + }), + }), + }), + true, + true, + ); + expect(result).toBeInstanceOf(TwoFactorWebAuthnResponse); + expect(result.enabled).toBe(true); + expect(result.keys).toHaveLength(1); + expect(result.keys[0].name).toBeDefined(); + expect(result.keys[0].id).toBeDefined(); + expect(result.keys[0].migrated).toBeDefined(); + }); + + it("preserves original request object without mutation during serialization", async () => { + const mockAttestationResponse: Partial = { + clientDataJSON: new Uint8Array([1, 2, 3]).buffer, + attestationObject: new Uint8Array([4, 5, 6]).buffer, + }; + + const mockCredential: Partial = { + id: "credential-id", + type: "public-key", + response: mockAttestationResponse as AuthenticatorAttestationResponse, + getClientExtensionResults: jest.fn().mockReturnValue({}), + }; + + const request = new UpdateTwoFactorWebAuthnRequest(); + request.deviceResponse = mockCredential as PublicKeyCredential; + request.name = "My Security Key"; + + const originalDeviceResponse = request.deviceResponse; + apiService.send.mockResolvedValue({ enabled: true, keys: [] }); + + await twoFactorApiService.putTwoFactorWebAuthn(request); + + // Do not mutate the original request object + expect(request.deviceResponse).toBe(originalDeviceResponse); + expect(request.deviceResponse.response).toBe(mockAttestationResponse); + }); + }); + + describe("deleteTwoFactorWebAuthn", () => { + it("removes specific WebAuthn credential while preserving other registered keys", async () => { + const request = new UpdateTwoFactorWebAuthnDeleteRequest(); + request.id = 1; + request.masterPasswordHash = "master-password-hash"; + const mockResponse = { + Enabled: true, + Keys: [{ Name: "Security Key", Id: 2, Migrated: true }], // Key with id:1 removed + }; + apiService.send.mockResolvedValue(mockResponse); + + const result = await twoFactorApiService.deleteTwoFactorWebAuthn(request); + + expect(apiService.send).toHaveBeenCalledWith( + "DELETE", + "/two-factor/webauthn", + request, + true, + true, + ); + expect(result).toBeInstanceOf(TwoFactorWebAuthnResponse); + expect(result.keys).toHaveLength(1); + expect(result.keys[0].id).toBe(2); + }); + }); + }); + + describe("Recovery Code APIs", () => { + describe("getTwoFactorRecover", () => { + it("retrieves recovery code for regaining access when two-factor is unavailable", async () => { + const request = new SecretVerificationRequest(); + request.masterPasswordHash = "master-password-hash"; + const mockResponse = { + Code: "ABCD-EFGH-IJKL-MNOP-QRST-UVWX-YZ12", + }; + apiService.send.mockResolvedValue(mockResponse); + + const result = await twoFactorApiService.getTwoFactorRecover(request); + + expect(apiService.send).toHaveBeenCalledWith( + "POST", + "/two-factor/get-recover", + request, + true, + true, + ); + expect(result).toBeInstanceOf(TwoFactorRecoverResponse); + expect(result.code).toBeDefined(); + expect(result.code).toMatch(/^[A-Z0-9-]+$/); + }); + }); + }); + + describe("Disable APIs", () => { + describe("putTwoFactorDisable", () => { + it("disables specified two-factor provider for current user", async () => { + const request = new TwoFactorProviderRequest(); + request.type = 0; // Authenticator + request.masterPasswordHash = "master-password-hash"; + const mockResponse = { + Enabled: false, + Type: 0, + }; + apiService.send.mockResolvedValue(mockResponse); + + const result = await twoFactorApiService.putTwoFactorDisable(request); + + expect(apiService.send).toHaveBeenCalledWith( + "PUT", + "/two-factor/disable", + request, + true, + true, + ); + expect(result).toBeInstanceOf(TwoFactorProviderResponse); + expect(result.enabled).toBe(false); + expect(result.type).toBe(0); // Authenticator + }); + }); + + describe("putTwoFactorOrganizationDisable", () => { + it("disables two-factor provider for organization with policy management permissions", async () => { + const organizationId = "org-123"; + const request = new TwoFactorProviderRequest(); + request.type = 6; // Duo + request.masterPasswordHash = "master-password-hash"; + const mockResponse = { + Enabled: false, + Type: 6, + }; + apiService.send.mockResolvedValue(mockResponse); + + const result = await twoFactorApiService.putTwoFactorOrganizationDisable( + organizationId, + request, + ); + + expect(apiService.send).toHaveBeenCalledWith( + "PUT", + `/organizations/${organizationId}/two-factor/disable`, + request, + true, + true, + ); + expect(result).toBeInstanceOf(TwoFactorProviderResponse); + expect(result.enabled).toBe(false); + expect(result.type).toBe(6); // Duo + }); + }); + }); +}); diff --git a/libs/common/src/auth/two-factor/two-factor-api.service.ts b/libs/common/src/auth/two-factor/two-factor-api.service.ts new file mode 100644 index 00000000000..278813c4c30 --- /dev/null +++ b/libs/common/src/auth/two-factor/two-factor-api.service.ts @@ -0,0 +1,292 @@ +import { DisableTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/disable-two-factor-authenticator.request"; +import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; +import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request"; +import { TwoFactorProviderRequest } from "@bitwarden/common/auth/models/request/two-factor-provider.request"; +import { UpdateTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/update-two-factor-authenticator.request"; +import { UpdateTwoFactorDuoRequest } from "@bitwarden/common/auth/models/request/update-two-factor-duo.request"; +import { UpdateTwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/update-two-factor-email.request"; +import { UpdateTwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/models/request/update-two-factor-web-authn-delete.request"; +import { UpdateTwoFactorWebAuthnRequest } from "@bitwarden/common/auth/models/request/update-two-factor-web-authn.request"; +import { UpdateTwoFactorYubikeyOtpRequest } from "@bitwarden/common/auth/models/request/update-two-factor-yubikey-otp.request"; +import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator.response"; +import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-duo.response"; +import { TwoFactorEmailResponse } from "@bitwarden/common/auth/models/response/two-factor-email.response"; +import { TwoFactorProviderResponse } from "@bitwarden/common/auth/models/response/two-factor-provider.response"; +import { TwoFactorRecoverResponse } from "@bitwarden/common/auth/models/response/two-factor-recover.response"; +import { + ChallengeResponse, + TwoFactorWebAuthnResponse, +} from "@bitwarden/common/auth/models/response/two-factor-web-authn.response"; +import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key.response"; +import { ListResponse } from "@bitwarden/common/models/response/list.response"; + +/** + * Service abstraction for two-factor authentication API operations. + * Provides methods for managing various two-factor authentication providers including + * authenticator apps (TOTP), email, Duo, YubiKey, WebAuthn (FIDO2), and recovery codes. + * + * All methods that retrieve sensitive configuration data require user verification via + * SecretVerificationRequest. Premium-tier providers (Duo, YubiKey) require an active + * premium subscription. Organization-level methods require appropriate administrative permissions. + */ +export abstract class TwoFactorApiService { + /** + * Gets a list of all enabled two-factor providers for the current user. + * + * @returns A promise that resolves to a list response containing enabled two-factor provider configurations. + */ + abstract getTwoFactorProviders(): Promise>; + + /** + * Gets a list of all enabled two-factor providers for an organization. + * Requires organization administrator permissions. + * + * @param organizationId The ID of the organization. + * @returns A promise that resolves to a list response containing enabled two-factor provider configurations. + */ + abstract getTwoFactorOrganizationProviders( + organizationId: string, + ): Promise>; + + /** + * Gets the authenticator (TOTP) two-factor configuration for the current user. + * Returns the shared secret key and user verification token needed for setup. + * Requires user verification via master password or OTP. + * + * @param request The secret verification request to authorize the operation. + * @returns A promise that resolves to the authenticator configuration including the secret key. + */ + abstract getTwoFactorAuthenticator( + request: SecretVerificationRequest, + ): Promise; + + /** + * Gets the email two-factor configuration for the current user. + * Returns the configured email address and enabled status. + * Requires user verification via master password or OTP. + * + * @param request The secret verification request to authorize the operation. + * @returns A promise that resolves to the email two-factor configuration. + */ + abstract getTwoFactorEmail(request: SecretVerificationRequest): Promise; + + /** + * Gets the Duo two-factor configuration for the current user. + * Returns Duo integration configuration details. + * Requires user verification and an active premium subscription. + * + * @param request The secret verification request to authorize the operation. + * @returns A promise that resolves to the Duo configuration. + */ + abstract getTwoFactorDuo(request: SecretVerificationRequest): Promise; + + /** + * Gets the Duo two-factor configuration for an organization. + * Returns organization-level Duo integration configuration. + * Requires user verification and organization policy management permissions. + * + * @param organizationId The ID of the organization. + * @param request The secret verification request to authorize the operation. + * @returns A promise that resolves to the organization Duo configuration. + */ + abstract getTwoFactorOrganizationDuo( + organizationId: string, + request: SecretVerificationRequest, + ): Promise; + + /** + * Gets the YubiKey OTP two-factor configuration for the current user. + * Returns configured YubiKey device identifiers (multiple keys supported). + * Requires user verification and an active premium subscription. + * + * @param request The secret verification request to authorize the operation. + * @returns A promise that resolves to the YubiKey configuration. + */ + abstract getTwoFactorYubiKey( + request: SecretVerificationRequest, + ): Promise; + + /** + * Gets the WebAuthn (FIDO2) two-factor configuration for the current user. + * Returns a list of registered WebAuthn credentials with their names and IDs. + * Requires user verification via master password or OTP. + * + * @param request The secret verification request to authorize the operation. + * @returns A promise that resolves to the WebAuthn configuration including registered credentials. + */ + abstract getTwoFactorWebAuthn( + request: SecretVerificationRequest, + ): Promise; + + /** + * Gets a WebAuthn challenge for registering a new WebAuthn credential. + * This must be called before putTwoFactorWebAuthn to obtain the cryptographic challenge + * required for credential creation. The challenge is used by the browser's WebAuthn API. + * Requires user verification via master password or OTP. + * + * @param request The secret verification request to authorize the operation. + * @returns A promise that resolves to the credential creation options containing the challenge. + */ + abstract getTwoFactorWebAuthnChallenge( + request: SecretVerificationRequest, + ): Promise; + + /** + * Gets the recovery code configuration for the current user. + * Returns the recovery code that can be used to regain access if other two-factor methods are unavailable. + * The recovery code should be stored securely by the user. + * Requires user verification via master password or OTP. + * + * @param request The secret verification request to authorize the operation. + * @returns A promise that resolves to the recovery code configuration. + */ + abstract getTwoFactorRecover( + request: SecretVerificationRequest, + ): Promise; + + /** + * Enables or updates the authenticator (TOTP) two-factor provider. + * Validates the provided token against the shared secret before enabling. + * The token must be generated by an authenticator app using the secret key. + * + * @param request The request containing the authenticator configuration and verification token. + * @returns A promise that resolves to the updated authenticator configuration. + */ + abstract putTwoFactorAuthenticator( + request: UpdateTwoFactorAuthenticatorRequest, + ): Promise; + + /** + * Disables the authenticator (TOTP) two-factor provider for the current user. + * Requires user verification token to confirm the operation. + * + * @param request The request containing verification credentials to disable the provider. + * @returns A promise that resolves to the updated provider status. + */ + abstract deleteTwoFactorAuthenticator( + request: DisableTwoFactorAuthenticatorRequest, + ): Promise; + + /** + * Enables or updates the email two-factor provider. + * Validates the email verification token sent via postTwoFactorEmailSetup before enabling. + * The token must match the code sent to the specified email address. + * + * @param request The request containing the email configuration and verification token. + * @returns A promise that resolves to the updated email two-factor configuration. + */ + abstract putTwoFactorEmail(request: UpdateTwoFactorEmailRequest): Promise; + + /** + * Enables or updates the Duo two-factor provider for the current user. + * Validates the Duo configuration (client ID, client secret, and host) before enabling. + * Requires user verification and an active premium subscription. + * + * @param request The request containing the Duo integration configuration. + * @returns A promise that resolves to the updated Duo configuration. + */ + abstract putTwoFactorDuo(request: UpdateTwoFactorDuoRequest): Promise; + + /** + * Enables or updates the Duo two-factor provider for an organization. + * Validates the Duo configuration (client ID, client secret, and host) before enabling. + * Requires user verification and organization policy management permissions. + * + * @param organizationId The ID of the organization. + * @param request The request containing the Duo integration configuration. + * @returns A promise that resolves to the updated organization Duo configuration. + */ + abstract putTwoFactorOrganizationDuo( + organizationId: string, + request: UpdateTwoFactorDuoRequest, + ): Promise; + + /** + * Enables or updates the YubiKey OTP two-factor provider. + * Validates each provided YubiKey by testing an OTP from the device. + * Supports up to 5 YubiKey devices. Empty key slots are allowed. + * Requires user verification and an active premium subscription. + * Includes a 2-second delay on validation failure to prevent timing attacks. + * + * @param request The request containing YubiKey device identifiers and test OTPs. + * @returns A promise that resolves to the updated YubiKey configuration. + */ + abstract putTwoFactorYubiKey( + request: UpdateTwoFactorYubikeyOtpRequest, + ): Promise; + + /** + * Registers a new WebAuthn (FIDO2) credential for two-factor authentication. + * Must be called after getTwoFactorWebAuthnChallenge to complete the registration flow. + * The device response contains the signed challenge from the authenticator device. + * Requires user verification via master password or OTP. + * + * @param request The request containing the WebAuthn credential creation response from the browser. + * @returns A promise that resolves to the updated WebAuthn configuration with the new credential. + */ + abstract putTwoFactorWebAuthn( + request: UpdateTwoFactorWebAuthnRequest, + ): Promise; + + /** + * Removes a specific WebAuthn (FIDO2) credential from the user's account. + * The credential will no longer be usable for two-factor authentication. + * Other registered WebAuthn credentials remain active. + * Requires user verification via master password or OTP. + * + * @param request The request containing the credential ID to remove. + * @returns A promise that resolves to the updated WebAuthn configuration. + */ + abstract deleteTwoFactorWebAuthn( + request: UpdateTwoFactorWebAuthnDeleteRequest, + ): Promise; + + /** + * Disables a specific two-factor provider for the current user. + * The provider will no longer be required or usable for authentication. + * Requires user verification via master password or OTP. + * + * @param request The request specifying which provider type to disable. + * @returns A promise that resolves to the updated provider status. + */ + abstract putTwoFactorDisable( + request: TwoFactorProviderRequest, + ): Promise; + + /** + * Disables a specific two-factor provider for an organization. + * The provider will no longer be available for organization members. + * Requires user verification and organization policy management permissions. + * + * @param organizationId The ID of the organization. + * @param request The request specifying which provider type to disable. + * @returns A promise that resolves to the updated provider status. + */ + abstract putTwoFactorOrganizationDisable( + organizationId: string, + request: TwoFactorProviderRequest, + ): Promise; + + /** + * Initiates email two-factor setup by sending a verification code to the specified email address. + * This is the first step in enabling email two-factor authentication. + * The verification code must be provided to putTwoFactorEmail to complete setup. + * Only used during initial configuration, not during login flows. + * Requires user verification via master password or OTP. + * + * @param request The request containing the email address for two-factor setup. + * @returns A promise that resolves when the verification email has been sent. + */ + abstract postTwoFactorEmailSetup(request: TwoFactorEmailRequest): Promise; + + /** + * Sends a two-factor authentication code via email during the login flow. + * Supports multiple authentication contexts including standard login, SSO, and passwordless flows. + * This is used to deliver codes during authentication, not during initial setup. + * May be called without authentication for login scenarios. + * + * @param request The request to send the two-factor code, optionally including SSO or auth request tokens. + * @returns A promise that resolves when the authentication email has been sent. + */ + abstract postTwoFactorEmail(request: TwoFactorEmailRequest): Promise; +} diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index f000f35f126..3b4fef9c5c4 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -48,8 +48,6 @@ import { import { SelectionReadOnlyResponse } from "../admin-console/models/response/selection-read-only.response"; import { AccountService } from "../auth/abstractions/account.service"; import { TokenService } from "../auth/abstractions/token.service"; -import { DeviceVerificationRequest } from "../auth/models/request/device-verification.request"; -import { DisableTwoFactorAuthenticatorRequest } from "../auth/models/request/disable-two-factor-authenticator.request"; import { EmailTokenRequest } from "../auth/models/request/email-token.request"; import { EmailRequest } from "../auth/models/request/email.request"; import { DeviceRequest } from "../auth/models/request/identity-token/device.request"; @@ -61,34 +59,15 @@ import { WebAuthnLoginTokenRequest } from "../auth/models/request/identity-token import { PasswordHintRequest } from "../auth/models/request/password-hint.request"; import { PasswordlessAuthRequest } from "../auth/models/request/passwordless-auth.request"; import { SecretVerificationRequest } from "../auth/models/request/secret-verification.request"; -import { TwoFactorEmailRequest } from "../auth/models/request/two-factor-email.request"; -import { TwoFactorProviderRequest } from "../auth/models/request/two-factor-provider.request"; import { UpdateProfileRequest } from "../auth/models/request/update-profile.request"; -import { UpdateTwoFactorAuthenticatorRequest } from "../auth/models/request/update-two-factor-authenticator.request"; -import { UpdateTwoFactorDuoRequest } from "../auth/models/request/update-two-factor-duo.request"; -import { UpdateTwoFactorEmailRequest } from "../auth/models/request/update-two-factor-email.request"; -import { UpdateTwoFactorWebAuthnDeleteRequest } from "../auth/models/request/update-two-factor-web-authn-delete.request"; -import { UpdateTwoFactorWebAuthnRequest } from "../auth/models/request/update-two-factor-web-authn.request"; -import { UpdateTwoFactorYubikeyOtpRequest } from "../auth/models/request/update-two-factor-yubikey-otp.request"; import { ApiKeyResponse } from "../auth/models/response/api-key.response"; import { AuthRequestResponse } from "../auth/models/response/auth-request.response"; -import { DeviceVerificationResponse } from "../auth/models/response/device-verification.response"; import { IdentityDeviceVerificationResponse } from "../auth/models/response/identity-device-verification.response"; import { IdentityTokenResponse } from "../auth/models/response/identity-token.response"; import { IdentityTwoFactorResponse } from "../auth/models/response/identity-two-factor.response"; import { KeyConnectorUserKeyResponse } from "../auth/models/response/key-connector-user-key.response"; import { PreloginResponse } from "../auth/models/response/prelogin.response"; import { SsoPreValidateResponse } from "../auth/models/response/sso-pre-validate.response"; -import { TwoFactorAuthenticatorResponse } from "../auth/models/response/two-factor-authenticator.response"; -import { TwoFactorDuoResponse } from "../auth/models/response/two-factor-duo.response"; -import { TwoFactorEmailResponse } from "../auth/models/response/two-factor-email.response"; -import { TwoFactorProviderResponse } from "../auth/models/response/two-factor-provider.response"; -import { TwoFactorRecoverResponse } from "../auth/models/response/two-factor-recover.response"; -import { - ChallengeResponse, - TwoFactorWebAuthnResponse, -} from "../auth/models/response/two-factor-web-authn.response"; -import { TwoFactorYubiKeyResponse } from "../auth/models/response/two-factor-yubi-key.response"; import { BitPayInvoiceRequest } from "../billing/models/request/bit-pay-invoice.request"; import { BillingHistoryResponse } from "../billing/models/response/billing-history.response"; import { PaymentResponse } from "../billing/models/response/payment.response"; @@ -809,205 +788,6 @@ export class ApiService implements ApiServiceAbstraction { return new SyncResponse(r); } - // Two-factor APIs - - async getTwoFactorProviders(): Promise> { - const r = await this.send("GET", "/two-factor", null, true, true); - return new ListResponse(r, TwoFactorProviderResponse); - } - - async getTwoFactorOrganizationProviders( - organizationId: string, - ): Promise> { - const r = await this.send( - "GET", - "/organizations/" + organizationId + "/two-factor", - null, - true, - true, - ); - return new ListResponse(r, TwoFactorProviderResponse); - } - - async getTwoFactorAuthenticator( - request: SecretVerificationRequest, - ): Promise { - const r = await this.send("POST", "/two-factor/get-authenticator", request, true, true); - return new TwoFactorAuthenticatorResponse(r); - } - - async getTwoFactorEmail(request: SecretVerificationRequest): Promise { - const r = await this.send("POST", "/two-factor/get-email", request, true, true); - return new TwoFactorEmailResponse(r); - } - - async getTwoFactorDuo(request: SecretVerificationRequest): Promise { - const r = await this.send("POST", "/two-factor/get-duo", request, true, true); - return new TwoFactorDuoResponse(r); - } - - async getTwoFactorOrganizationDuo( - organizationId: string, - request: SecretVerificationRequest, - ): Promise { - const r = await this.send( - "POST", - "/organizations/" + organizationId + "/two-factor/get-duo", - request, - true, - true, - ); - return new TwoFactorDuoResponse(r); - } - - async getTwoFactorYubiKey(request: SecretVerificationRequest): Promise { - const r = await this.send("POST", "/two-factor/get-yubikey", request, true, true); - return new TwoFactorYubiKeyResponse(r); - } - - async getTwoFactorWebAuthn( - request: SecretVerificationRequest, - ): Promise { - const r = await this.send("POST", "/two-factor/get-webauthn", request, true, true); - return new TwoFactorWebAuthnResponse(r); - } - - async getTwoFactorWebAuthnChallenge( - request: SecretVerificationRequest, - ): Promise { - const r = await this.send("POST", "/two-factor/get-webauthn-challenge", request, true, true); - return new ChallengeResponse(r); - } - - async getTwoFactorRecover(request: SecretVerificationRequest): Promise { - const r = await this.send("POST", "/two-factor/get-recover", request, true, true); - return new TwoFactorRecoverResponse(r); - } - - async putTwoFactorAuthenticator( - request: UpdateTwoFactorAuthenticatorRequest, - ): Promise { - const r = await this.send("PUT", "/two-factor/authenticator", request, true, true); - return new TwoFactorAuthenticatorResponse(r); - } - - async deleteTwoFactorAuthenticator( - request: DisableTwoFactorAuthenticatorRequest, - ): Promise { - const r = await this.send("DELETE", "/two-factor/authenticator", request, true, true); - return new TwoFactorProviderResponse(r); - } - - async putTwoFactorEmail(request: UpdateTwoFactorEmailRequest): Promise { - const r = await this.send("PUT", "/two-factor/email", request, true, true); - return new TwoFactorEmailResponse(r); - } - - async putTwoFactorDuo(request: UpdateTwoFactorDuoRequest): Promise { - const r = await this.send("PUT", "/two-factor/duo", request, true, true); - return new TwoFactorDuoResponse(r); - } - - async putTwoFactorOrganizationDuo( - organizationId: string, - request: UpdateTwoFactorDuoRequest, - ): Promise { - const r = await this.send( - "PUT", - "/organizations/" + organizationId + "/two-factor/duo", - request, - true, - true, - ); - return new TwoFactorDuoResponse(r); - } - - async putTwoFactorYubiKey( - request: UpdateTwoFactorYubikeyOtpRequest, - ): Promise { - const r = await this.send("PUT", "/two-factor/yubikey", request, true, true); - return new TwoFactorYubiKeyResponse(r); - } - - async putTwoFactorWebAuthn( - request: UpdateTwoFactorWebAuthnRequest, - ): Promise { - const response = request.deviceResponse.response as AuthenticatorAttestationResponse; - const data: any = Object.assign({}, request); - - data.deviceResponse = { - id: request.deviceResponse.id, - rawId: btoa(request.deviceResponse.id), - type: request.deviceResponse.type, - extensions: request.deviceResponse.getClientExtensionResults(), - response: { - AttestationObject: Utils.fromBufferToB64(response.attestationObject), - clientDataJson: Utils.fromBufferToB64(response.clientDataJSON), - }, - }; - - const r = await this.send("PUT", "/two-factor/webauthn", data, true, true); - return new TwoFactorWebAuthnResponse(r); - } - - async deleteTwoFactorWebAuthn( - request: UpdateTwoFactorWebAuthnDeleteRequest, - ): Promise { - const r = await this.send("DELETE", "/two-factor/webauthn", request, true, true); - return new TwoFactorWebAuthnResponse(r); - } - - async putTwoFactorDisable(request: TwoFactorProviderRequest): Promise { - const r = await this.send("PUT", "/two-factor/disable", request, true, true); - return new TwoFactorProviderResponse(r); - } - - async putTwoFactorOrganizationDisable( - organizationId: string, - request: TwoFactorProviderRequest, - ): Promise { - const r = await this.send( - "PUT", - "/organizations/" + organizationId + "/two-factor/disable", - request, - true, - true, - ); - return new TwoFactorProviderResponse(r); - } - - postTwoFactorEmailSetup(request: TwoFactorEmailRequest): Promise { - return this.send("POST", "/two-factor/send-email", request, true, false); - } - - postTwoFactorEmail(request: TwoFactorEmailRequest): Promise { - return this.send("POST", "/two-factor/send-email-login", request, false, false); - } - - async getDeviceVerificationSettings(): Promise { - const r = await this.send( - "GET", - "/two-factor/get-device-verification-settings", - null, - true, - true, - ); - return new DeviceVerificationResponse(r); - } - - async putDeviceVerificationSettings( - request: DeviceVerificationRequest, - ): Promise { - const r = await this.send( - "PUT", - "/two-factor/device-verification-settings", - request, - true, - true, - ); - return new DeviceVerificationResponse(r); - } - // Organization APIs async getCloudCommunicationsEnabled(): Promise {