diff --git a/apps/browser/src/popup/services/init.service.ts b/apps/browser/src/popup/services/init.service.ts index 1de1731013de..91b6f0ff1050 100644 --- a/apps/browser/src/popup/services/init.service.ts +++ b/apps/browser/src/popup/services/init.service.ts @@ -2,7 +2,7 @@ import { DOCUMENT } from "@angular/common"; import { inject, Inject, Injectable } from "@angular/core"; import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; diff --git a/apps/cli/src/auth/commands/login.command.ts b/apps/cli/src/auth/commands/login.command.ts index aa43b353f9c8..d0ab062d0b30 100644 --- a/apps/cli/src/auth/commands/login.command.ts +++ b/apps/cli/src/auth/commands/login.command.ts @@ -20,7 +20,6 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; @@ -28,7 +27,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 { TwoFactorService, 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"; diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index ebfb76eab2fa..365205a13299 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -49,10 +49,14 @@ import { DefaultActiveUserAccessor } from "@bitwarden/common/auth/services/defau import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation"; import { MasterPasswordApiService } from "@bitwarden/common/auth/services/master-password/master-password-api.service.implementation"; 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 { + DefaultTwoFactorService, + TwoFactorService, + TwoFactorApiService, + DefaultTwoFactorApiService, +} from "@bitwarden/common/auth/two-factor"; import { AutofillSettingsService, AutofillSettingsServiceAbstraction, @@ -627,10 +631,11 @@ export class ServiceContainer { this.stateProvider, ); - this.twoFactorService = new TwoFactorService( + this.twoFactorService = new DefaultTwoFactorService( this.i18nService, this.platformUtilsService, this.globalStateProvider, + this.twoFactorApiService, ); const sdkClientFactory = flagEnabled("sdk") diff --git a/apps/desktop/src/app/services/init.service.ts b/apps/desktop/src/app/services/init.service.ts index ae633bd4a69f..73c4d38d3b2b 100644 --- a/apps/desktop/src/app/services/init.service.ts +++ b/apps/desktop/src/app/services/init.service.ts @@ -6,7 +6,7 @@ import { AbstractThemingService } from "@bitwarden/angular/platform/services/the import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { DefaultVaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -39,7 +39,7 @@ export class InitService { private vaultTimeoutService: DefaultVaultTimeoutService, private i18nService: I18nServiceAbstraction, private eventUploadService: EventUploadServiceAbstraction, - private twoFactorService: TwoFactorServiceAbstraction, + private twoFactorService: TwoFactorService, private notificationsService: ServerNotificationsService, private platformUtilsService: PlatformUtilsServiceAbstraction, private stateService: StateServiceAbstraction, 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 46e39a112bfc..95081af3a532 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 @@ -11,16 +11,17 @@ import { } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; 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 { 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 { TwoFactorService } 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"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { DialogRef, DialogService } from "@bitwarden/components"; +import { DialogRef, DialogService, ToastService } from "@bitwarden/components"; import { TwoFactorSetupDuoComponent } from "../../../auth/settings/two-factor/two-factor-setup-duo.component"; import { TwoFactorSetupComponent as BaseTwoFactorSetupComponent } from "../../../auth/settings/two-factor/two-factor-setup.component"; @@ -37,7 +38,7 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent impleme tabbedHeader = false; constructor( dialogService: DialogService, - twoFactorApiService: TwoFactorApiService, + twoFactorService: TwoFactorService, messagingService: MessagingService, policyService: PolicyService, private route: ActivatedRoute, @@ -46,16 +47,20 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent impleme protected accountService: AccountService, configService: ConfigService, i18nService: I18nService, + protected userVerificationService: UserVerificationService, + protected toastService: ToastService, ) { super( dialogService, - twoFactorApiService, + twoFactorService, messagingService, policyService, billingAccountProfileStateService, accountService, configService, i18nService, + userVerificationService, + toastService, ); } @@ -118,7 +123,7 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent impleme } protected getTwoFactorProviders() { - return this.twoFactorApiService.getTwoFactorOrganizationProviders(this.organizationId); + return this.twoFactorService.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 934de0f6453a..56f2bfe21127 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,7 +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 { TwoFactorService } 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"; @@ -23,14 +23,14 @@ describe("ChangeEmailComponent", () => { let fixture: ComponentFixture; let apiService: MockProxy; - let twoFactorApiService: MockProxy; + let twoFactorService: MockProxy; let accountService: FakeAccountService; let keyService: MockProxy; let kdfConfigService: MockProxy; beforeEach(async () => { apiService = mock(); - twoFactorApiService = mock(); + twoFactorService = mock(); keyService = mock(); kdfConfigService = mock(); accountService = mockAccountServiceWith("UserId" as UserId); @@ -40,7 +40,7 @@ describe("ChangeEmailComponent", () => { providers: [ { provide: AccountService, useValue: accountService }, { provide: ApiService, useValue: apiService }, - { provide: TwoFactorApiService, useValue: twoFactorApiService }, + { provide: TwoFactorService, useValue: twoFactorService }, { provide: I18nService, useValue: { t: (key: string) => key } }, { provide: KeyService, useValue: keyService }, { provide: MessagingService, useValue: mock() }, @@ -61,7 +61,7 @@ describe("ChangeEmailComponent", () => { describe("ngOnInit", () => { beforeEach(() => { - twoFactorApiService.getTwoFactorProviders.mockResolvedValue({ + twoFactorService.getEnabledTwoFactorProviders.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 ee29e0c8a9c0..3daf2240fb2a 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,7 +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 { TwoFactorService } 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"; @@ -40,7 +40,7 @@ export class ChangeEmailComponent implements OnInit { constructor( private accountService: AccountService, private apiService: ApiService, - private twoFactorApiService: TwoFactorApiService, + private twoFactorService: TwoFactorService, private i18nService: I18nService, private keyService: KeyService, private messagingService: MessagingService, @@ -52,7 +52,7 @@ export class ChangeEmailComponent implements OnInit { async ngOnInit() { this.userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); - const twoFactorProviders = await this.twoFactorApiService.getTwoFactorProviders(); + const twoFactorProviders = await this.twoFactorService.getEnabledTwoFactorProviders(); 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 01be46c45b3b..97f50df24c8e 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 @@ -9,7 +9,7 @@ import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-a 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 { TwoFactorService } 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"; @@ -66,7 +66,7 @@ export class SetAccountVerifyDevicesDialogComponent implements OnInit, OnDestroy private userVerificationService: UserVerificationService, private dialogRef: DialogRef, private toastService: ToastService, - private twoFactorApiService: TwoFactorApiService, + private twoFactorService: TwoFactorService, ) { this.accountService.accountVerifyNewDeviceLogin$ .pipe(takeUntil(this.destroy$)) @@ -76,7 +76,7 @@ export class SetAccountVerifyDevicesDialogComponent implements OnInit, OnDestroy } async ngOnInit() { - const twoFactorProviders = await this.twoFactorApiService.getTwoFactorProviders(); + const twoFactorProviders = await this.twoFactorService.getEnabledTwoFactorProviders(); 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 20c3c742db63..d93a59474450 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 @@ -12,7 +12,7 @@ import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-p 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 { TwoFactorService } 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"; @@ -96,7 +96,7 @@ export class TwoFactorSetupAuthenticatorComponent constructor( @Inject(DIALOG_DATA) protected data: AuthResponse, private dialogRef: DialogRef, - twoFactorApiService: TwoFactorApiService, + twoFactorService: TwoFactorService, i18nService: I18nService, userVerificationService: UserVerificationService, private formBuilder: FormBuilder, @@ -108,7 +108,7 @@ export class TwoFactorSetupAuthenticatorComponent protected toastService: ToastService, ) { super( - twoFactorApiService, + twoFactorService, i18nService, platformUtilsService, logService, @@ -158,7 +158,7 @@ export class TwoFactorSetupAuthenticatorComponent request.key = this.key; request.userVerificationToken = this.userVerificationToken; - const response = await this.twoFactorApiService.putTwoFactorAuthenticator(request); + const response = await this.twoFactorService.putTwoFactorAuthenticator(request); await this.processResponse(response); this.onUpdated.emit(true); } @@ -178,7 +178,7 @@ export class TwoFactorSetupAuthenticatorComponent request.type = this.type; request.key = this.key; request.userVerificationToken = this.userVerificationToken; - await this.twoFactorApiService.deleteTwoFactorAuthenticator(request); + await this.twoFactorService.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 1a476f2206d1..b4c8ece92a74 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 @@ -6,7 +6,7 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use 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 { TwoFactorService } 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"; @@ -67,7 +67,7 @@ export class TwoFactorSetupDuoComponent constructor( @Inject(DIALOG_DATA) protected data: TwoFactorDuoComponentConfig, - twoFactorApiService: TwoFactorApiService, + twoFactorService: TwoFactorService, i18nService: I18nService, platformUtilsService: PlatformUtilsService, logService: LogService, @@ -78,7 +78,7 @@ export class TwoFactorSetupDuoComponent protected toastService: ToastService, ) { super( - twoFactorApiService, + twoFactorService, i18nService, platformUtilsService, logService, @@ -143,12 +143,12 @@ export class TwoFactorSetupDuoComponent let response: TwoFactorDuoResponse; if (this.organizationId != null) { - response = await this.twoFactorApiService.putTwoFactorOrganizationDuo( + response = await this.twoFactorService.putTwoFactorOrganizationDuo( this.organizationId, request, ); } else { - response = await this.twoFactorApiService.putTwoFactorDuo(request); + response = await this.twoFactorService.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 4219fb0b6873..1402d6b89694 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 @@ -9,7 +9,7 @@ import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-p 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 { TwoFactorService } 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"; @@ -70,7 +70,7 @@ export class TwoFactorSetupEmailComponent constructor( @Inject(DIALOG_DATA) protected data: AuthResponse, - twoFactorApiService: TwoFactorApiService, + twoFactorService: TwoFactorService, i18nService: I18nService, platformUtilsService: PlatformUtilsService, logService: LogService, @@ -82,7 +82,7 @@ export class TwoFactorSetupEmailComponent protected toastService: ToastService, ) { super( - twoFactorApiService, + twoFactorService, i18nService, platformUtilsService, logService, @@ -135,7 +135,7 @@ export class TwoFactorSetupEmailComponent sendEmail = async () => { const request = await this.buildRequestModel(TwoFactorEmailRequest); request.email = this.email; - this.emailPromise = this.twoFactorApiService.postTwoFactorEmailSetup(request); + this.emailPromise = this.twoFactorService.postTwoFactorEmailSetup(request); await this.emailPromise; this.sentEmail = this.email; }; @@ -145,7 +145,7 @@ export class TwoFactorSetupEmailComponent request.email = this.email; request.token = this.token; - const response = await this.twoFactorApiService.putTwoFactorEmail(request); + const response = await this.twoFactorService.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 ad8d401d3fca..5494353449dd 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 @@ -5,7 +5,7 @@ import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-p 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 { TwoFactorService } 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"; @@ -32,7 +32,7 @@ export abstract class TwoFactorSetupMethodBaseComponent { protected componentName = ""; constructor( - protected twoFactorApiService: TwoFactorApiService, + protected twoFactorService: TwoFactorService, protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, protected logService: LogService, @@ -47,58 +47,6 @@ export abstract class TwoFactorSetupMethodBaseComponent { this.authed = true; } - /** @deprecated used for formPromise flows.*/ - protected async enable(enableFunction: () => Promise) { - try { - await enableFunction(); - this.onUpdated.emit(true); - } catch (e) { - this.logService.error(e); - } - } - - /** - * @deprecated used for formPromise flows. - * TODO: Remove this method when formPromises are removed from all flows. - * */ - protected async disable(promise: Promise) { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "disable" }, - content: { key: "twoStepDisableDesc" }, - type: "warning", - }); - - if (!confirmed) { - return; - } - - try { - const request = await this.buildRequestModel(TwoFactorProviderRequest); - if (this.type === undefined) { - throw new Error("Two-factor provider type is required"); - } - request.type = this.type; - if (this.organizationId != null) { - promise = this.twoFactorApiService.putTwoFactorOrganizationDisable( - this.organizationId, - request, - ); - } else { - promise = this.twoFactorApiService.putTwoFactorDisable(request); - } - await promise; - this.enabled = false; - this.toastService.showToast({ - variant: "success", - title: "", - message: this.i18nService.t("twoStepDisabled"), - }); - this.onUpdated.emit(false); - } catch (e) { - this.logService.error(e); - } - } - protected async disableMethod() { const confirmed = await this.dialogService.openSimpleDialog({ title: { key: "disable" }, @@ -116,9 +64,9 @@ export abstract class TwoFactorSetupMethodBaseComponent { } request.type = this.type; if (this.organizationId != null) { - await this.twoFactorApiService.putTwoFactorOrganizationDisable(this.organizationId, request); + await this.twoFactorService.putTwoFactorOrganizationDisable(this.organizationId, request); } else { - await this.twoFactorApiService.putTwoFactorDisable(request); + await this.twoFactorService.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 acf83ab278ed..11ba59559021 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 @@ -12,7 +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 { TwoFactorService } 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"; @@ -72,7 +72,6 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom webAuthnListening: boolean = false; webAuthnResponse: PublicKeyCredential | null = null; challengePromise: Promise | undefined; - formPromise: Promise | undefined; override componentName = "app-two-factor-webauthn"; @@ -81,7 +80,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom constructor( @Inject(DIALOG_DATA) protected data: AuthResponse, private dialogRef: DialogRef, - twoFactorApiService: TwoFactorApiService, + twoFactorService: TwoFactorService, i18nService: I18nService, platformUtilsService: PlatformUtilsService, private ngZone: NgZone, @@ -91,7 +90,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom toastService: ToastService, ) { super( - twoFactorApiService, + twoFactorService, i18nService, platformUtilsService, logService, @@ -129,7 +128,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom request.id = this.keyIdAvailable; request.name = this.formGroup.value.name || ""; - const response = await this.twoFactorApiService.putTwoFactorWebAuthn(request); + const response = await this.twoFactorService.putTwoFactorWebAuthn(request); this.processResponse(response); this.toastService.showToast({ title: this.i18nService.t("success"), @@ -165,7 +164,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom const request = await this.buildRequestModel(UpdateTwoFactorWebAuthnDeleteRequest); request.id = key.id; try { - key.removePromise = this.twoFactorApiService.deleteTwoFactorWebAuthn(request); + key.removePromise = this.twoFactorService.deleteTwoFactorWebAuthn(request); const response = await key.removePromise; key.removePromise = null; await this.processResponse(response); @@ -179,7 +178,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom return; } const request = await this.buildRequestModel(SecretVerificationRequest); - this.challengePromise = this.twoFactorApiService.getTwoFactorWebAuthnChallenge(request); + this.challengePromise = this.twoFactorService.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 09fb1ad308fe..a58c659796dc 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 @@ -13,7 +13,7 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use 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 { TwoFactorService } 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"; @@ -74,9 +74,6 @@ export class TwoFactorSetupYubiKeyComponent keys: Key[] = []; anyKeyHasNfc = false; - formPromise: Promise | undefined; - disablePromise: Promise | undefined; - override componentName = "app-two-factor-yubikey"; formGroup: | FormGroup<{ @@ -95,7 +92,7 @@ export class TwoFactorSetupYubiKeyComponent constructor( @Inject(DIALOG_DATA) protected data: AuthResponse, - twoFactorApiService: TwoFactorApiService, + twoFactorService: TwoFactorService, i18nService: I18nService, platformUtilsService: PlatformUtilsService, logService: LogService, @@ -105,7 +102,7 @@ export class TwoFactorSetupYubiKeyComponent protected toastService: ToastService, ) { super( - twoFactorApiService, + twoFactorService, i18nService, platformUtilsService, logService, @@ -178,7 +175,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.twoFactorApiService.putTwoFactorYubiKey(request)); + this.processResponse(await this.twoFactorService.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.html b/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.html index ee2d4dd7b636..69a0dbf4145a 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.html +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.html @@ -71,15 +71,26 @@

{{ p.description }}
- + @if (p.premium && p.enabled && !(canAccessPremium$ | async)) { + + } @else { + + } 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 024455cc1bf8..a85bf65ab745 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 @@ -12,26 +12,28 @@ import { } from "rxjs"; import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/premium-badge"; +import { UserVerificationDialogComponent } from "@bitwarden/auth/angular"; 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"; 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 { TwoFactorProviderRequest } from "@bitwarden/common/auth/models/request/two-factor-provider.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 { 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 { 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 { TwoFactorService, TwoFactorProviders } 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"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { DialogRef, DialogService, ItemModule } from "@bitwarden/components"; +import { DialogRef, DialogService, ItemModule, ToastService } from "@bitwarden/components"; import { HeaderModule } from "../../../layouts/header/header.module"; import { SharedModule } from "../../../shared/shared.module"; @@ -59,7 +61,6 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { recoveryCodeWarningMessage: string; showPolicyWarning = false; loading = true; - formPromise: Promise; tabbedHeader = true; @@ -69,13 +70,15 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { constructor( protected dialogService: DialogService, - protected twoFactorApiService: TwoFactorApiService, + protected twoFactorService: TwoFactorService, protected messagingService: MessagingService, protected policyService: PolicyService, billingAccountProfileStateService: BillingAccountProfileStateService, protected accountService: AccountService, protected configService: ConfigService, protected i18nService: I18nService, + protected userVerificationService: UserVerificationService, + protected toastService: ToastService, ) { this.canAccessPremium$ = this.accountService.activeAccount$.pipe( switchMap((account) => @@ -150,6 +153,50 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { return await lastValueFrom(twoFactorVerifyDialogRef.closed); } + /** + * For users who enabled a premium-only 2fa provider, + * they should still be allowed to disable that provider + * (without otherwise modifying) if they no longer have + * premium access [PM-21204] + * @param type the 2FA Provider Type + */ + async disablePremium2faTypeForNonPremiumUser(type: TwoFactorProviderType) { + // Use UserVerificationDialogComponent instead of TwoFactorVerifyComponent + // because the latter makes GET API calls that require premium for YubiKey/Duo. + // The disable endpoint only requires user verification, not provider configuration. + const result = await UserVerificationDialogComponent.open(this.dialogService, { + title: "twoStepLogin", + verificationType: { + type: "custom", + verificationFn: async (secret) => { + const request = await this.userVerificationService.buildRequest( + secret, + TwoFactorProviderRequest, + ); + request.type = type; + + await this.twoFactorService.putTwoFactorDisable(request); + return true; + }, + }, + }); + + if (result.userAction === "cancel") { + return; + } + + if (!result.verificationSuccess) { + return; + } + + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("twoStepDisabled"), + }); + this.updateStatus(false, type); + } + async manage(type: TwoFactorProviderType) { // clear any existing subscriptions before creating a new one this.twoFactorSetupSubscription?.unsubscribe(); @@ -264,7 +311,7 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { } protected getTwoFactorProviders() { - return this.twoFactorApiService.getTwoFactorProviders(); + return this.twoFactorService.getEnabledTwoFactorProviders(); } 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 075d3bdf5626..04c84ca01973 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 @@ -5,7 +5,7 @@ import { UserVerificationFormInputComponent } from "@bitwarden/auth/angular"; 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"; -import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorService } 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 { VerificationWithSecret } from "@bitwarden/common/auth/types/verification"; @@ -54,7 +54,7 @@ export class TwoFactorVerifyComponent { constructor( @Inject(DIALOG_DATA) protected data: TwoFactorVerifyDialogData, private dialogRef: DialogRef, - private twoFactorApiService: TwoFactorApiService, + private twoFactorService: TwoFactorService, private i18nService: I18nService, private userVerificationService: UserVerificationService, ) { @@ -110,22 +110,22 @@ export class TwoFactorVerifyComponent { private apiCall(request: SecretVerificationRequest): Promise { switch (this.type) { case -1 as TwoFactorProviderType: - return this.twoFactorApiService.getTwoFactorRecover(request); + return this.twoFactorService.getTwoFactorRecover(request); case TwoFactorProviderType.Duo: case TwoFactorProviderType.OrganizationDuo: if (this.organizationId != null) { - return this.twoFactorApiService.getTwoFactorOrganizationDuo(this.organizationId, request); + return this.twoFactorService.getTwoFactorOrganizationDuo(this.organizationId, request); } else { - return this.twoFactorApiService.getTwoFactorDuo(request); + return this.twoFactorService.getTwoFactorDuo(request); } case TwoFactorProviderType.Email: - return this.twoFactorApiService.getTwoFactorEmail(request); + return this.twoFactorService.getTwoFactorEmail(request); case TwoFactorProviderType.WebAuthn: - return this.twoFactorApiService.getTwoFactorWebAuthn(request); + return this.twoFactorService.getTwoFactorWebAuthn(request); case TwoFactorProviderType.Authenticator: - return this.twoFactorApiService.getTwoFactorAuthenticator(request); + return this.twoFactorService.getTwoFactorAuthenticator(request); case TwoFactorProviderType.Yubikey: - return this.twoFactorApiService.getTwoFactorYubiKey(request); + return this.twoFactorService.getTwoFactorYubiKey(request); default: throw new Error(`Unknown two-factor type: ${this.type}`); } diff --git a/apps/web/src/app/core/init.service.ts b/apps/web/src/app/core/init.service.ts index 6b03913ef7af..71e3578a7c6b 100644 --- a/apps/web/src/app/core/init.service.ts +++ b/apps/web/src/app/core/init.service.ts @@ -6,7 +6,7 @@ import { AbstractThemingService } from "@bitwarden/angular/platform/services/the import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { DefaultVaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -31,7 +31,7 @@ export class InitService { private vaultTimeoutService: DefaultVaultTimeoutService, private i18nService: I18nServiceAbstraction, private eventUploadService: EventUploadServiceAbstraction, - private twoFactorService: TwoFactorServiceAbstraction, + private twoFactorService: TwoFactorService, private keyService: KeyServiceAbstraction, private themingService: AbstractThemingService, private encryptService: EncryptService, diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 1b0460e2aa68..17b887c46434 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -12161,5 +12161,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index b1215654cfde..ff5caff540c7 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -102,7 +102,6 @@ import { MasterPasswordApiService as MasterPasswordApiServiceAbstraction } from import { PasswordResetEnrollmentServiceAbstraction } from "@bitwarden/common/auth/abstractions/password-reset-enrollment.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service"; -import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification-api.service.abstraction"; import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { WebAuthnLoginApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login-api.service.abstraction"; @@ -125,13 +124,17 @@ import { OrganizationInviteService } from "@bitwarden/common/auth/services/organ import { PasswordResetEnrollmentServiceImplementation } from "@bitwarden/common/auth/services/password-reset-enrollment.service.implementation"; import { SsoLoginService } from "@bitwarden/common/auth/services/sso-login.service"; 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 { 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 { + TwoFactorApiService, + DefaultTwoFactorApiService, + TwoFactorService, + DefaultTwoFactorService, +} from "@bitwarden/common/auth/two-factor"; import { AutofillSettingsService, AutofillSettingsServiceAbstraction, @@ -527,7 +530,7 @@ const safeProviders: SafeProvider[] = [ KeyConnectorServiceAbstraction, EnvironmentService, StateServiceAbstraction, - TwoFactorServiceAbstraction, + TwoFactorService, I18nServiceAbstraction, EncryptService, PasswordStrengthServiceAbstraction, @@ -1165,9 +1168,14 @@ const safeProviders: SafeProvider[] = [ deps: [StateProvider], }), safeProvider({ - provide: TwoFactorServiceAbstraction, - useClass: TwoFactorService, - deps: [I18nServiceAbstraction, PlatformUtilsServiceAbstraction, GlobalStateProvider], + provide: TwoFactorService, + useClass: DefaultTwoFactorService, + deps: [ + I18nServiceAbstraction, + PlatformUtilsServiceAbstraction, + GlobalStateProvider, + TwoFactorApiService, + ], }), safeProvider({ provide: FormValidationErrorsServiceAbstraction, 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 000d391b62cc..8976236f48c4 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,9 @@ import { ReactiveFormsModule, FormsModule, FormControl } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; -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 { TwoFactorService } 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"; @@ -68,7 +67,6 @@ export class TwoFactorAuthEmailComponent implements OnInit { protected loginStrategyService: LoginStrategyServiceAbstraction, protected platformUtilsService: PlatformUtilsService, protected logService: LogService, - protected twoFactorApiService: TwoFactorApiService, protected appIdService: AppIdService, private toastService: ToastService, private cacheService: TwoFactorAuthEmailComponentCacheService, @@ -137,7 +135,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.twoFactorApiService.postTwoFactorEmail(request); + this.emailPromise = this.twoFactorService.postTwoFactorEmail(request); await this.emailPromise; this.emailSent = true; diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts index 71a91ec20e72..54d6f7c5f775 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts @@ -6,8 +6,8 @@ import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { WebAuthnIFrame } from "@bitwarden/common/auth/webauthn-iframe"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts index 5d36fd384cac..af9fb03e01e2 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts @@ -18,12 +18,12 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; import { InternalMasterPasswordServiceAbstraction, diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index 52f20b046012..8e10539823d8 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -32,12 +32,12 @@ import { import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.guard.spec.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.guard.spec.ts index 116da73173f8..37fbf6bf7942 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.guard.spec.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.guard.spec.ts @@ -4,8 +4,8 @@ import { provideRouter, Router } from "@angular/router"; import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { LoginStrategyServiceAbstraction } from "../../common"; diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.guard.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.guard.ts index 2aec0bae4416..a7ed4010ef5b 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.guard.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.guard.ts @@ -8,7 +8,7 @@ import { } from "@angular/router"; import { firstValueFrom } from "rxjs"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { LoginStrategyServiceAbstraction } from "../../common"; diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-options.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-options.component.ts index d0ad9be61031..d8b2ab2508b1 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-options.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-options.component.ts @@ -9,11 +9,8 @@ import { TwoFactorAuthWebAuthnIcon, TwoFactorAuthYubicoIcon, } from "@bitwarden/assets/svg"; -import { - TwoFactorProviderDetails, - TwoFactorService, -} from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; +import { TwoFactorProviderDetails, TwoFactorService } from "@bitwarden/common/auth/two-factor"; // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. // eslint-disable-next-line no-restricted-imports import { diff --git a/libs/auth/src/angular/user-verification/user-verification-dialog.component.ts b/libs/auth/src/angular/user-verification/user-verification-dialog.component.ts index 09d428d4ba7f..1304df2a7638 100644 --- a/libs/auth/src/angular/user-verification/user-verification-dialog.component.ts +++ b/libs/auth/src/angular/user-verification/user-verification-dialog.component.ts @@ -277,13 +277,13 @@ export class UserVerificationDialogComponent { }); } } - } catch (e) { + } catch { // Catch handles OTP and MP verification scenarios as those throw errors on verification failure instead of returning false like PIN and biometrics. this.invalidSecret = true; this.toastService.showToast({ variant: "error", title: this.i18nService.t("error"), - message: e.message, + message: this.i18nService.t("userVerificationFailed"), }); return; } diff --git a/libs/auth/src/common/login-strategies/auth-request-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/auth-request-login.strategy.spec.ts index 0b19fecdc4e0..b536ae0dc4bd 100644 --- a/libs/auth/src/common/login-strategies/auth-request-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/auth-request-login.strategy.spec.ts @@ -3,8 +3,8 @@ import { BehaviorSubject } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; diff --git a/libs/auth/src/common/login-strategies/login.strategy.spec.ts b/libs/auth/src/common/login-strategies/login.strategy.spec.ts index a23f8034238a..e9eed27d5a1f 100644 --- a/libs/auth/src/common/login-strategies/login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/login.strategy.spec.ts @@ -5,7 +5,6 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; @@ -16,6 +15,7 @@ import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/id import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response"; import { MasterPasswordPolicyResponse } from "@bitwarden/common/auth/models/response/master-password-policy.response"; import { IUserDecryptionOptionsServerResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/user-decryption-options.response"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; diff --git a/libs/auth/src/common/login-strategies/login.strategy.ts b/libs/auth/src/common/login-strategies/login.strategy.ts index 7ad5cd243537..35f13246593c 100644 --- a/libs/auth/src/common/login-strategies/login.strategy.ts +++ b/libs/auth/src/common/login-strategies/login.strategy.ts @@ -3,7 +3,6 @@ import { BehaviorSubject, filter, firstValueFrom, timeout, Observable } from "rx import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; @@ -16,6 +15,7 @@ import { WebAuthnLoginTokenRequest } from "@bitwarden/common/auth/models/request import { IdentityDeviceVerificationResponse } from "@bitwarden/common/auth/models/response/identity-device-verification.response"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; diff --git a/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts index 3f709e7472b5..26ae1270f392 100644 --- a/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts @@ -5,12 +5,12 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response"; import { MasterPasswordPolicyResponse } from "@bitwarden/common/auth/models/response/master-password-policy.response"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service"; diff --git a/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts index bce05b35e62c..03de5f36c2d3 100644 --- a/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts @@ -3,12 +3,12 @@ import { BehaviorSubject, of } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { AdminAuthRequestStorable } from "@bitwarden/common/auth/models/domain/admin-auth-req-storable"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IUserDecryptionOptionsServerResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/user-decryption-options.response"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncryptedString } from "@bitwarden/common/key-management/crypto/models/enc-string"; diff --git a/libs/auth/src/common/login-strategies/user-api-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/user-api-login.strategy.spec.ts index a6446401f70e..9bf282dee110 100644 --- a/libs/auth/src/common/login-strategies/user-api-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/user-api-login.strategy.spec.ts @@ -3,7 +3,7 @@ import { BehaviorSubject } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; diff --git a/libs/auth/src/common/login-strategies/webauthn-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/webauthn-login.strategy.spec.ts index 25ae8a31ef69..53bc1c57905a 100644 --- a/libs/auth/src/common/login-strategies/webauthn-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/webauthn-login.strategy.spec.ts @@ -3,11 +3,11 @@ import { BehaviorSubject } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IUserDecryptionOptionsServerResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/user-decryption-options.response"; import { WebAuthnLoginAssertionResponseRequest } from "@bitwarden/common/auth/services/webauthn-login/request/webauthn-login-assertion-response.request"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service"; diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts b/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts index f97158477f14..831692c160fc 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts @@ -4,13 +4,13 @@ import { BehaviorSubject } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response"; import { PreloginResponse } from "@bitwarden/common/auth/models/response/prelogin.response"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts index 084fafc4aea0..5720fce254ed 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts @@ -13,10 +13,10 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; diff --git a/libs/common/src/auth/abstractions/two-factor.service.ts b/libs/common/src/auth/abstractions/two-factor.service.ts deleted file mode 100644 index 528e52bf5dac..000000000000 --- a/libs/common/src/auth/abstractions/two-factor.service.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { TwoFactorProviderType } from "../enums/two-factor-provider-type"; -import { IdentityTwoFactorResponse } from "../models/response/identity-two-factor.response"; - -export interface TwoFactorProviderDetails { - type: TwoFactorProviderType; - name: string; - description: string; - priority: number; - sort: number; - premium: boolean; -} -export abstract class TwoFactorService { - /** - * Initializes the client-side's TwoFactorProviders const with translations. - */ - abstract init(): void; - - /** - * Gets a list of two-factor providers from state that are supported on the current client. - * E.g., WebAuthn and Duo are not available on all clients. - * @returns A list of supported two-factor providers or an empty list if none are stored in state. - */ - abstract getSupportedProviders(win: Window): Promise; - - /** - * Gets the previously selected two-factor provider or the default two factor provider based on priority. - * @param webAuthnSupported - Whether or not WebAuthn is supported by the client. Prevents WebAuthn from being the default provider if false. - */ - abstract getDefaultProvider(webAuthnSupported: boolean): Promise; - - /** - * Sets the selected two-factor provider in state. - * @param type - The type of two-factor provider to set as the selected provider. - */ - abstract setSelectedProvider(type: TwoFactorProviderType): Promise; - - /** - * Clears the selected two-factor provider from state. - */ - abstract clearSelectedProvider(): Promise; - - /** - * Sets the list of available two-factor providers in state. - * @param response - the response from Identity for when 2FA is required. Includes the list of available 2FA providers. - */ - abstract setProviders(response: IdentityTwoFactorResponse): Promise; - - /** - * Clears the list of available two-factor providers from state. - */ - abstract clearProviders(): Promise; - - /** - * Gets the list of two-factor providers from state. - * Note: no filtering is done here, so this will return all providers, including potentially - * unsupported ones for the current client. - * @returns A list of two-factor providers or null if none are stored in state. - */ - abstract getProviders(): Promise | null>; -} diff --git a/libs/common/src/auth/services/two-factor.service.ts b/libs/common/src/auth/services/two-factor.service.ts deleted file mode 100644 index 83e113268a21..000000000000 --- a/libs/common/src/auth/services/two-factor.service.ts +++ /dev/null @@ -1,212 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { firstValueFrom, map } from "rxjs"; - -import { I18nService } from "../../platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service"; -import { Utils } from "../../platform/misc/utils"; -import { GlobalStateProvider, KeyDefinition, TWO_FACTOR_MEMORY } from "../../platform/state"; -import { - TwoFactorProviderDetails, - TwoFactorService as TwoFactorServiceAbstraction, -} from "../abstractions/two-factor.service"; -import { TwoFactorProviderType } from "../enums/two-factor-provider-type"; -import { IdentityTwoFactorResponse } from "../models/response/identity-two-factor.response"; - -export const TwoFactorProviders: Partial> = - { - [TwoFactorProviderType.Authenticator]: { - type: TwoFactorProviderType.Authenticator, - name: null as string, - description: null as string, - priority: 1, - sort: 2, - premium: false, - }, - [TwoFactorProviderType.Yubikey]: { - type: TwoFactorProviderType.Yubikey, - name: null as string, - description: null as string, - priority: 3, - sort: 4, - premium: true, - }, - [TwoFactorProviderType.Duo]: { - type: TwoFactorProviderType.Duo, - name: "Duo", - description: null as string, - priority: 2, - sort: 5, - premium: true, - }, - [TwoFactorProviderType.OrganizationDuo]: { - type: TwoFactorProviderType.OrganizationDuo, - name: "Duo (Organization)", - description: null as string, - priority: 10, - sort: 6, - premium: false, - }, - [TwoFactorProviderType.Email]: { - type: TwoFactorProviderType.Email, - name: null as string, - description: null as string, - priority: 0, - sort: 1, - premium: false, - }, - [TwoFactorProviderType.WebAuthn]: { - type: TwoFactorProviderType.WebAuthn, - name: null as string, - description: null as string, - priority: 4, - sort: 3, - premium: false, - }, - }; - -// Memory storage as only required during authentication process -export const PROVIDERS = KeyDefinition.record, TwoFactorProviderType>( - TWO_FACTOR_MEMORY, - "providers", - { - deserializer: (obj) => obj, - }, -); - -// Memory storage as only required during authentication process -export const SELECTED_PROVIDER = new KeyDefinition( - TWO_FACTOR_MEMORY, - "selected", - { - deserializer: (obj) => obj, - }, -); - -export class TwoFactorService implements TwoFactorServiceAbstraction { - private providersState = this.globalStateProvider.get(PROVIDERS); - private selectedState = this.globalStateProvider.get(SELECTED_PROVIDER); - readonly providers$ = this.providersState.state$.pipe( - map((providers) => Utils.recordToMap(providers)), - ); - readonly selected$ = this.selectedState.state$; - - constructor( - private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, - private globalStateProvider: GlobalStateProvider, - ) {} - - init() { - TwoFactorProviders[TwoFactorProviderType.Email].name = this.i18nService.t("emailTitle"); - TwoFactorProviders[TwoFactorProviderType.Email].description = this.i18nService.t("emailDescV2"); - - TwoFactorProviders[TwoFactorProviderType.Authenticator].name = - this.i18nService.t("authenticatorAppTitle"); - TwoFactorProviders[TwoFactorProviderType.Authenticator].description = - this.i18nService.t("authenticatorAppDescV2"); - - TwoFactorProviders[TwoFactorProviderType.Duo].description = this.i18nService.t("duoDescV2"); - - TwoFactorProviders[TwoFactorProviderType.OrganizationDuo].name = - "Duo (" + this.i18nService.t("organization") + ")"; - TwoFactorProviders[TwoFactorProviderType.OrganizationDuo].description = - this.i18nService.t("duoOrganizationDesc"); - - TwoFactorProviders[TwoFactorProviderType.WebAuthn].name = this.i18nService.t("webAuthnTitle"); - TwoFactorProviders[TwoFactorProviderType.WebAuthn].description = - this.i18nService.t("webAuthnDesc"); - - TwoFactorProviders[TwoFactorProviderType.Yubikey].name = this.i18nService.t("yubiKeyTitleV2"); - TwoFactorProviders[TwoFactorProviderType.Yubikey].description = - this.i18nService.t("yubiKeyDesc"); - } - - async getSupportedProviders(win: Window): Promise { - const data = await firstValueFrom(this.providers$); - const providers: any[] = []; - if (data == null) { - return providers; - } - - if ( - data.has(TwoFactorProviderType.OrganizationDuo) && - this.platformUtilsService.supportsDuo() - ) { - providers.push(TwoFactorProviders[TwoFactorProviderType.OrganizationDuo]); - } - - if (data.has(TwoFactorProviderType.Authenticator)) { - providers.push(TwoFactorProviders[TwoFactorProviderType.Authenticator]); - } - - if (data.has(TwoFactorProviderType.Yubikey)) { - providers.push(TwoFactorProviders[TwoFactorProviderType.Yubikey]); - } - - if (data.has(TwoFactorProviderType.Duo) && this.platformUtilsService.supportsDuo()) { - providers.push(TwoFactorProviders[TwoFactorProviderType.Duo]); - } - - if ( - data.has(TwoFactorProviderType.WebAuthn) && - this.platformUtilsService.supportsWebAuthn(win) - ) { - providers.push(TwoFactorProviders[TwoFactorProviderType.WebAuthn]); - } - - if (data.has(TwoFactorProviderType.Email)) { - providers.push(TwoFactorProviders[TwoFactorProviderType.Email]); - } - - return providers; - } - - async getDefaultProvider(webAuthnSupported: boolean): Promise { - const data = await firstValueFrom(this.providers$); - const selected = await firstValueFrom(this.selected$); - if (data == null) { - return null; - } - - if (selected != null && data.has(selected)) { - return selected; - } - - let providerType: TwoFactorProviderType = null; - let providerPriority = -1; - data.forEach((_value, type) => { - const provider = (TwoFactorProviders as any)[type]; - if (provider != null && provider.priority > providerPriority) { - if (type === TwoFactorProviderType.WebAuthn && !webAuthnSupported) { - return; - } - - providerType = type; - providerPriority = provider.priority; - } - }); - - return providerType; - } - - async setSelectedProvider(type: TwoFactorProviderType): Promise { - await this.selectedState.update(() => type); - } - - async clearSelectedProvider(): Promise { - await this.selectedState.update(() => null); - } - - async setProviders(response: IdentityTwoFactorResponse): Promise { - await this.providersState.update(() => response.twoFactorProviders2); - } - - async clearProviders(): Promise { - await this.providersState.update(() => null); - } - - getProviders(): Promise | null> { - return firstValueFrom(this.providers$); - } -} diff --git a/libs/common/src/auth/two-factor/abstractions/index.ts b/libs/common/src/auth/two-factor/abstractions/index.ts new file mode 100644 index 000000000000..00789048ebec --- /dev/null +++ b/libs/common/src/auth/two-factor/abstractions/index.ts @@ -0,0 +1,2 @@ +export * from "./two-factor-api.service"; +export * from "./two-factor.service"; diff --git a/libs/common/src/auth/two-factor/two-factor-api.service.ts b/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts similarity index 100% rename from libs/common/src/auth/two-factor/two-factor-api.service.ts rename to libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts diff --git a/libs/common/src/auth/two-factor/abstractions/two-factor.service.ts b/libs/common/src/auth/two-factor/abstractions/two-factor.service.ts new file mode 100644 index 000000000000..4cba40716e10 --- /dev/null +++ b/libs/common/src/auth/two-factor/abstractions/two-factor.service.ts @@ -0,0 +1,497 @@ +import { ListResponse } from "../../../models/response/list.response"; +import { KeyDefinition, TWO_FACTOR_MEMORY } from "../../../platform/state"; +import { TwoFactorProviderType } from "../../enums/two-factor-provider-type"; +import { DisableTwoFactorAuthenticatorRequest } from "../../models/request/disable-two-factor-authenticator.request"; +import { SecretVerificationRequest } from "../../models/request/secret-verification.request"; +import { TwoFactorEmailRequest } from "../../models/request/two-factor-email.request"; +import { TwoFactorProviderRequest } from "../../models/request/two-factor-provider.request"; +import { UpdateTwoFactorAuthenticatorRequest } from "../../models/request/update-two-factor-authenticator.request"; +import { UpdateTwoFactorDuoRequest } from "../../models/request/update-two-factor-duo.request"; +import { UpdateTwoFactorEmailRequest } from "../../models/request/update-two-factor-email.request"; +import { UpdateTwoFactorWebAuthnDeleteRequest } from "../../models/request/update-two-factor-web-authn-delete.request"; +import { UpdateTwoFactorWebAuthnRequest } from "../../models/request/update-two-factor-web-authn.request"; +import { UpdateTwoFactorYubikeyOtpRequest } from "../../models/request/update-two-factor-yubikey-otp.request"; +import { IdentityTwoFactorResponse } from "../../models/response/identity-two-factor.response"; +import { TwoFactorAuthenticatorResponse } from "../../models/response/two-factor-authenticator.response"; +import { TwoFactorDuoResponse } from "../../models/response/two-factor-duo.response"; +import { TwoFactorEmailResponse } from "../../models/response/two-factor-email.response"; +import { TwoFactorProviderResponse } from "../../models/response/two-factor-provider.response"; +import { TwoFactorRecoverResponse } from "../../models/response/two-factor-recover.response"; +import { + ChallengeResponse, + TwoFactorWebAuthnResponse, +} from "../../models/response/two-factor-web-authn.response"; +import { TwoFactorYubiKeyResponse } from "../../models/response/two-factor-yubi-key.response"; + +/** + * Metadata and display information for a two-factor authentication provider. + * Used by UI components to render provider selection and configuration screens. + */ +export interface TwoFactorProviderDetails { + /** The unique identifier for this provider type. */ + type: TwoFactorProviderType; + + /** + * Display name for the provider, localized via {@link TwoFactorService.init}. + * Examples: "Authenticator App", "Email", "YubiKey". + */ + name: string | null; + + /** + * User-facing description explaining what this provider is and how it works. + * Localized via {@link TwoFactorService.init}. + */ + description: string | null; + + /** + * Selection priority during login when multiple providers are available. + * Higher values are preferred. Used to determine the default provider. + * Range: 0 (lowest) to 10 (highest). + */ + priority: number; + + /** + * Display order in provider lists within settings UI. + * Lower values appear first (1 = first position). + */ + sort: number; + + /** + * Whether this provider requires an active premium subscription. + * Premium providers: Duo (personal), YubiKey. + * Organization providers (e.g., OrganizationDuo) do not require personal premium. + */ + premium: boolean; +} + +/** + * Registry of all supported two-factor authentication providers with their metadata. + * Strings (name, description) are initialized as null and populated with localized + * translations when {@link TwoFactorService.init} is called during application startup. + * + * @remarks + * This constant is mutated during initialization. Components should not access it before + * the service's init() method has been called. + * + * @example + * ```typescript + * // During app init + * twoFactorService.init(); + * + * // In components + * const authenticator = TwoFactorProviders[TwoFactorProviderType.Authenticator]; + * console.log(authenticator.name); // "Authenticator App" (localized) + * ``` + */ +export const TwoFactorProviders: Partial> = + { + [TwoFactorProviderType.Authenticator]: { + type: TwoFactorProviderType.Authenticator, + name: null, + description: null, + priority: 1, + sort: 2, + premium: false, + }, + [TwoFactorProviderType.Yubikey]: { + type: TwoFactorProviderType.Yubikey, + name: null, + description: null, + priority: 3, + sort: 4, + premium: true, + }, + [TwoFactorProviderType.Duo]: { + type: TwoFactorProviderType.Duo, + name: "Duo", + description: null, + priority: 2, + sort: 5, + premium: true, + }, + [TwoFactorProviderType.OrganizationDuo]: { + type: TwoFactorProviderType.OrganizationDuo, + name: "Duo (Organization)", + description: null, + priority: 10, + sort: 6, + premium: false, + }, + [TwoFactorProviderType.Email]: { + type: TwoFactorProviderType.Email, + name: null, + description: null, + priority: 0, + sort: 1, + premium: false, + }, + [TwoFactorProviderType.WebAuthn]: { + type: TwoFactorProviderType.WebAuthn, + name: null, + description: null, + priority: 4, + sort: 3, + premium: false, + }, + }; + +// Memory storage as only required during authentication process +export const PROVIDERS = KeyDefinition.record, TwoFactorProviderType>( + TWO_FACTOR_MEMORY, + "providers", + { + deserializer: (obj) => obj, + }, +); + +// Memory storage as only required during authentication process +export const SELECTED_PROVIDER = new KeyDefinition( + TWO_FACTOR_MEMORY, + "selected", + { + deserializer: (obj) => obj, + }, +); + +export abstract class TwoFactorService { + /** + * Initializes the client-side's TwoFactorProviders const with translations. + */ + abstract init(): void; + + /** + * Gets a list of two-factor providers from state that are supported on the current client. + * E.g., WebAuthn and Duo are not available on all clients. + * @returns A list of supported two-factor providers or an empty list if none are stored in state. + */ + abstract getSupportedProviders(win: Window): Promise; + + /** + * Gets the previously selected two-factor provider or the default two factor provider based on priority. + * @param webAuthnSupported - Whether or not WebAuthn is supported by the client. Prevents WebAuthn from being the default provider if false. + */ + abstract getDefaultProvider(webAuthnSupported: boolean): Promise; + + /** + * Sets the selected two-factor provider in state. + * @param type - The type of two-factor provider to set as the selected provider. + */ + abstract setSelectedProvider(type: TwoFactorProviderType): Promise; + + /** + * Clears the selected two-factor provider from state. + */ + abstract clearSelectedProvider(): Promise; + + /** + * Sets the list of available two-factor providers in state. + * @param response - the response from Identity for when 2FA is required. Includes the list of available 2FA providers. + */ + abstract setProviders(response: IdentityTwoFactorResponse): Promise; + + /** + * Clears the list of available two-factor providers from state. + */ + abstract clearProviders(): Promise; + + /** + * Gets the list of two-factor providers from state. + * Note: no filtering is done here, so this will return all providers, including potentially + * unsupported ones for the current client. + * @returns A list of two-factor providers or null if none are stored in state. + */ + abstract getProviders(): Promise | null>; + + /** + * Gets the enabled two-factor providers for the current user from the API. + * Used for settings management. + * @returns A promise that resolves to a list response containing enabled two-factor provider configurations. + */ + abstract getEnabledTwoFactorProviders(): Promise>; + + /** + * Gets the enabled two-factor providers for an organization from the API. + * Requires organization administrator permissions. + * Used for settings management. + * + * @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 from the API. + * Requires user verification via master password or OTP. + * Used for settings management. + * + * @param request The {@link SecretVerificationRequest} to prove authentication. + * @returns A promise that resolves to the authenticator configuration including the secret key. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract getTwoFactorAuthenticator( + request: SecretVerificationRequest, + ): Promise; + + /** + * Gets the email two-factor configuration for the current user from the API. + * Requires user verification via master password or OTP. + * Used for settings management. + * + * @param request The {@link SecretVerificationRequest} to prove authentication. + * @returns A promise that resolves to the email two-factor configuration. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract getTwoFactorEmail(request: SecretVerificationRequest): Promise; + + /** + * Gets the Duo two-factor configuration for the current user from the API. + * Requires user verification and an active premium subscription. + * Used for settings management. + * + * @param request The {@link SecretVerificationRequest} to prove authentication. + * @returns A promise that resolves to the Duo configuration. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract getTwoFactorDuo(request: SecretVerificationRequest): Promise; + + /** + * Gets the Duo two-factor configuration for an organization from the API. + * Requires user verification and organization policy management permissions. + * Used for settings management. + * + * @param organizationId The ID of the organization. + * @param request The {@link SecretVerificationRequest} to prove authentication. + * @returns A promise that resolves to the organization Duo configuration. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract getTwoFactorOrganizationDuo( + organizationId: string, + request: SecretVerificationRequest, + ): Promise; + + /** + * Gets the YubiKey OTP two-factor configuration for the current user from the API. + * Requires user verification and an active premium subscription. + * Used for settings management. + * + * @param request The {@link SecretVerificationRequest} to prove authentication. + * @returns A promise that resolves to the YubiKey configuration. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract getTwoFactorYubiKey( + request: SecretVerificationRequest, + ): Promise; + + /** + * Gets the WebAuthn (FIDO2) two-factor configuration for the current user from the API. + * Requires user verification via master password or OTP. + * Used for settings management. + * + * @param request The {@link SecretVerificationRequest} to authentication. + * @returns A promise that resolves to the WebAuthn configuration including registered credentials. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract getTwoFactorWebAuthn( + request: SecretVerificationRequest, + ): Promise; + + /** + * Gets a WebAuthn challenge for registering a new WebAuthn credential from the API. + * 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. + * Used for settings management. + * + * @param request The {@link SecretVerificationRequest} to prove authentication. + * @returns A promise that resolves to the credential creation options containing the challenge. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract getTwoFactorWebAuthnChallenge( + request: SecretVerificationRequest, + ): Promise; + + /** + * Gets the recovery code configuration for the current user from the API. + * The recovery code should be stored securely by the user. + * Requires user verification via master password or OTP. + * Used for settings management. + * + * @param verification The verification information to prove authentication. + * @returns A promise that resolves to the recovery code configuration. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + 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. + * Used for settings management. + * + * @param request The {@link UpdateTwoFactorAuthenticatorRequest} to prove authentication. + * @returns A promise that resolves to the updated authenticator configuration. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract putTwoFactorAuthenticator( + request: UpdateTwoFactorAuthenticatorRequest, + ): Promise; + + /** + * Disables the authenticator (TOTP) two-factor provider for the current user. + * Requires user verification token to confirm the operation. + * Used for settings management. + * + * @param request The {@link DisableTwoFactorAuthenticatorRequest} to prove authentication. + * @returns A promise that resolves to the updated provider status. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract deleteTwoFactorAuthenticator( + request: DisableTwoFactorAuthenticatorRequest, + ): Promise; + + /** + * Enables or updates the email two-factor provider for the current user. + * Validates the email verification token sent via postTwoFactorEmailSetup before enabling. + * The token must match the code sent to the specified email address. + * Used for settings management. + * + * @param request The {@link UpdateTwoFactorEmailRequest} to prove authentication. + * @returns A promise that resolves to the updated email two-factor configuration. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + 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. + * Used for settings management. + * + * @param request The {@link UpdateTwoFactorDuoRequest} to prove authentication. + * @returns A promise that resolves to the updated Duo configuration. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + 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. + * Used for settings management. + * + * @param organizationId The ID of the organization. + * @param request The {@link UpdateTwoFactorDuoRequest} to prove authentication. + * @returns A promise that resolves to the updated organization Duo configuration. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract putTwoFactorOrganizationDuo( + organizationId: string, + request: UpdateTwoFactorDuoRequest, + ): Promise; + + /** + * Enables or updates the YubiKey OTP two-factor provider for the current user. + * 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. + * Used for settings management. + * + * @param request The {@link UpdateTwoFactorYubikeyOtpRequest} to prove authentication. + * @returns A promise that resolves to the updated YubiKey configuration. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract putTwoFactorYubiKey( + request: UpdateTwoFactorYubikeyOtpRequest, + ): Promise; + + /** + * Registers a new WebAuthn (FIDO2) credential for two-factor authentication for the current user. + * 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. + * Used for settings management. + * + * @param request The {@link UpdateTwoFactorWebAuthnRequest} to prove authentication. + * @returns A promise that resolves to the updated WebAuthn configuration with the new credential. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + 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. + * Used for settings management. + * + * @param request The {@link UpdateTwoFactorWebAuthnDeleteRequest} to prove authentication. + * @returns A promise that resolves to the updated WebAuthn configuration. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + 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. + * Used for settings management. + * + * @param request The {@link TwoFactorProviderRequest} to prove authentication. + * @returns A promise that resolves to the updated provider status. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + 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. + * Used for settings management. + * + * @param organizationId The ID of the organization. + * @param request The {@link TwoFactorProviderRequest} to prove authentication. + * @returns A promise that resolves to the updated provider status. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + 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. + * Used for settings management. + * + * @param request The {@link TwoFactorEmailRequest} to prove authentication. + * @returns A promise that resolves when the verification email has been sent. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + 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. + * Used during authentication flows. + * + * @param request The {@link TwoFactorEmailRequest} to prove authentication. + * @returns A promise that resolves when the authentication email has been sent. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract postTwoFactorEmail(request: TwoFactorEmailRequest): Promise; +} diff --git a/libs/common/src/auth/two-factor/index.ts b/libs/common/src/auth/two-factor/index.ts index 85e072403b76..fd6edf0ac3c3 100644 --- a/libs/common/src/auth/two-factor/index.ts +++ b/libs/common/src/auth/two-factor/index.ts @@ -1,2 +1,2 @@ -export { TwoFactorApiService } from "./two-factor-api.service"; -export { DefaultTwoFactorApiService } from "./default-two-factor-api.service"; +export * from "./abstractions"; +export * from "./services"; diff --git a/libs/common/src/auth/two-factor/two-factor-api.service.spec.ts b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts similarity index 100% rename from libs/common/src/auth/two-factor/two-factor-api.service.spec.ts rename to libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts diff --git a/libs/common/src/auth/two-factor/default-two-factor-api.service.ts b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts similarity index 99% rename from libs/common/src/auth/two-factor/default-two-factor-api.service.ts rename to libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts index 93f7b207922a..b9778e460ea6 100644 --- a/libs/common/src/auth/two-factor/default-two-factor-api.service.ts +++ b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts @@ -22,7 +22,7 @@ import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/models/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"; +import { TwoFactorApiService } from "../abstractions/two-factor-api.service"; export class DefaultTwoFactorApiService implements TwoFactorApiService { constructor(private apiService: ApiService) {} diff --git a/libs/common/src/auth/two-factor/services/default-two-factor.service.ts b/libs/common/src/auth/two-factor/services/default-two-factor.service.ts new file mode 100644 index 000000000000..2acc4a7e9399 --- /dev/null +++ b/libs/common/src/auth/two-factor/services/default-two-factor.service.ts @@ -0,0 +1,279 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { firstValueFrom, map } from "rxjs"; + +import { TwoFactorApiService } from ".."; +import { ListResponse } from "../../../models/response/list.response"; +import { I18nService } from "../../../platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service"; +import { Utils } from "../../../platform/misc/utils"; +import { GlobalStateProvider } from "../../../platform/state"; +import { TwoFactorProviderType } from "../../enums/two-factor-provider-type"; +import { DisableTwoFactorAuthenticatorRequest } from "../../models/request/disable-two-factor-authenticator.request"; +import { SecretVerificationRequest } from "../../models/request/secret-verification.request"; +import { TwoFactorEmailRequest } from "../../models/request/two-factor-email.request"; +import { TwoFactorProviderRequest } from "../../models/request/two-factor-provider.request"; +import { UpdateTwoFactorAuthenticatorRequest } from "../../models/request/update-two-factor-authenticator.request"; +import { UpdateTwoFactorDuoRequest } from "../../models/request/update-two-factor-duo.request"; +import { UpdateTwoFactorEmailRequest } from "../../models/request/update-two-factor-email.request"; +import { UpdateTwoFactorWebAuthnDeleteRequest } from "../../models/request/update-two-factor-web-authn-delete.request"; +import { UpdateTwoFactorWebAuthnRequest } from "../../models/request/update-two-factor-web-authn.request"; +import { UpdateTwoFactorYubikeyOtpRequest } from "../../models/request/update-two-factor-yubikey-otp.request"; +import { IdentityTwoFactorResponse } from "../../models/response/identity-two-factor.response"; +import { TwoFactorAuthenticatorResponse } from "../../models/response/two-factor-authenticator.response"; +import { TwoFactorDuoResponse } from "../../models/response/two-factor-duo.response"; +import { TwoFactorEmailResponse } from "../../models/response/two-factor-email.response"; +import { TwoFactorProviderResponse } from "../../models/response/two-factor-provider.response"; +import { TwoFactorRecoverResponse } from "../../models/response/two-factor-recover.response"; +import { + TwoFactorWebAuthnResponse, + ChallengeResponse, +} from "../../models/response/two-factor-web-authn.response"; +import { TwoFactorYubiKeyResponse } from "../../models/response/two-factor-yubi-key.response"; +import { + PROVIDERS, + SELECTED_PROVIDER, + TwoFactorProviderDetails, + TwoFactorProviders, + TwoFactorService as TwoFactorServiceAbstraction, +} from "../abstractions/two-factor.service"; + +export class DefaultTwoFactorService implements TwoFactorServiceAbstraction { + private providersState = this.globalStateProvider.get(PROVIDERS); + private selectedState = this.globalStateProvider.get(SELECTED_PROVIDER); + readonly providers$ = this.providersState.state$.pipe( + map((providers) => Utils.recordToMap(providers)), + ); + readonly selected$ = this.selectedState.state$; + + constructor( + private i18nService: I18nService, + private platformUtilsService: PlatformUtilsService, + private globalStateProvider: GlobalStateProvider, + private twoFactorApiService: TwoFactorApiService, + ) {} + + init() { + TwoFactorProviders[TwoFactorProviderType.Email].name = this.i18nService.t("emailTitle"); + TwoFactorProviders[TwoFactorProviderType.Email].description = this.i18nService.t("emailDescV2"); + + TwoFactorProviders[TwoFactorProviderType.Authenticator].name = + this.i18nService.t("authenticatorAppTitle"); + TwoFactorProviders[TwoFactorProviderType.Authenticator].description = + this.i18nService.t("authenticatorAppDescV2"); + + TwoFactorProviders[TwoFactorProviderType.Duo].description = this.i18nService.t("duoDescV2"); + + TwoFactorProviders[TwoFactorProviderType.OrganizationDuo].name = + "Duo (" + this.i18nService.t("organization") + ")"; + TwoFactorProviders[TwoFactorProviderType.OrganizationDuo].description = + this.i18nService.t("duoOrganizationDesc"); + + TwoFactorProviders[TwoFactorProviderType.WebAuthn].name = this.i18nService.t("webAuthnTitle"); + TwoFactorProviders[TwoFactorProviderType.WebAuthn].description = + this.i18nService.t("webAuthnDesc"); + + TwoFactorProviders[TwoFactorProviderType.Yubikey].name = this.i18nService.t("yubiKeyTitleV2"); + TwoFactorProviders[TwoFactorProviderType.Yubikey].description = + this.i18nService.t("yubiKeyDesc"); + } + + async getSupportedProviders(win: Window): Promise { + const data = await firstValueFrom(this.providers$); + const providers: any[] = []; + if (data == null) { + return providers; + } + + if ( + data.has(TwoFactorProviderType.OrganizationDuo) && + this.platformUtilsService.supportsDuo() + ) { + providers.push(TwoFactorProviders[TwoFactorProviderType.OrganizationDuo]); + } + + if (data.has(TwoFactorProviderType.Authenticator)) { + providers.push(TwoFactorProviders[TwoFactorProviderType.Authenticator]); + } + + if (data.has(TwoFactorProviderType.Yubikey)) { + providers.push(TwoFactorProviders[TwoFactorProviderType.Yubikey]); + } + + if (data.has(TwoFactorProviderType.Duo) && this.platformUtilsService.supportsDuo()) { + providers.push(TwoFactorProviders[TwoFactorProviderType.Duo]); + } + + if ( + data.has(TwoFactorProviderType.WebAuthn) && + this.platformUtilsService.supportsWebAuthn(win) + ) { + providers.push(TwoFactorProviders[TwoFactorProviderType.WebAuthn]); + } + + if (data.has(TwoFactorProviderType.Email)) { + providers.push(TwoFactorProviders[TwoFactorProviderType.Email]); + } + + return providers; + } + + async getDefaultProvider(webAuthnSupported: boolean): Promise { + const data = await firstValueFrom(this.providers$); + const selected = await firstValueFrom(this.selected$); + if (data == null) { + return null; + } + + if (selected != null && data.has(selected)) { + return selected; + } + + let providerType: TwoFactorProviderType = null; + let providerPriority = -1; + data.forEach((_value, type) => { + const provider = (TwoFactorProviders as any)[type]; + if (provider != null && provider.priority > providerPriority) { + if (type === TwoFactorProviderType.WebAuthn && !webAuthnSupported) { + return; + } + + providerType = type; + providerPriority = provider.priority; + } + }); + + return providerType; + } + + async setSelectedProvider(type: TwoFactorProviderType): Promise { + await this.selectedState.update(() => type); + } + + async clearSelectedProvider(): Promise { + await this.selectedState.update(() => null); + } + + async setProviders(response: IdentityTwoFactorResponse): Promise { + await this.providersState.update(() => response.twoFactorProviders2); + } + + async clearProviders(): Promise { + await this.providersState.update(() => null); + } + + getProviders(): Promise | null> { + return firstValueFrom(this.providers$); + } + + getEnabledTwoFactorProviders(): Promise> { + return this.twoFactorApiService.getTwoFactorProviders(); + } + + getTwoFactorOrganizationProviders( + organizationId: string, + ): Promise> { + return this.twoFactorApiService.getTwoFactorOrganizationProviders(organizationId); + } + + getTwoFactorAuthenticator( + request: SecretVerificationRequest, + ): Promise { + return this.twoFactorApiService.getTwoFactorAuthenticator(request); + } + + getTwoFactorEmail(request: SecretVerificationRequest): Promise { + return this.twoFactorApiService.getTwoFactorEmail(request); + } + + getTwoFactorDuo(request: SecretVerificationRequest): Promise { + return this.twoFactorApiService.getTwoFactorDuo(request); + } + + getTwoFactorOrganizationDuo( + organizationId: string, + request: SecretVerificationRequest, + ): Promise { + return this.twoFactorApiService.getTwoFactorOrganizationDuo(organizationId, request); + } + + getTwoFactorYubiKey(request: SecretVerificationRequest): Promise { + return this.twoFactorApiService.getTwoFactorYubiKey(request); + } + + getTwoFactorWebAuthn(request: SecretVerificationRequest): Promise { + return this.twoFactorApiService.getTwoFactorWebAuthn(request); + } + + getTwoFactorWebAuthnChallenge(request: SecretVerificationRequest): Promise { + return this.twoFactorApiService.getTwoFactorWebAuthnChallenge(request); + } + + getTwoFactorRecover(request: SecretVerificationRequest): Promise { + return this.twoFactorApiService.getTwoFactorRecover(request); + } + + putTwoFactorAuthenticator( + request: UpdateTwoFactorAuthenticatorRequest, + ): Promise { + return this.twoFactorApiService.putTwoFactorAuthenticator(request); + } + + deleteTwoFactorAuthenticator( + request: DisableTwoFactorAuthenticatorRequest, + ): Promise { + return this.twoFactorApiService.deleteTwoFactorAuthenticator(request); + } + + putTwoFactorEmail(request: UpdateTwoFactorEmailRequest): Promise { + return this.twoFactorApiService.putTwoFactorEmail(request); + } + + putTwoFactorDuo(request: UpdateTwoFactorDuoRequest): Promise { + return this.twoFactorApiService.putTwoFactorDuo(request); + } + + putTwoFactorOrganizationDuo( + organizationId: string, + request: UpdateTwoFactorDuoRequest, + ): Promise { + return this.twoFactorApiService.putTwoFactorOrganizationDuo(organizationId, request); + } + + putTwoFactorYubiKey( + request: UpdateTwoFactorYubikeyOtpRequest, + ): Promise { + return this.twoFactorApiService.putTwoFactorYubiKey(request); + } + + putTwoFactorWebAuthn( + request: UpdateTwoFactorWebAuthnRequest, + ): Promise { + return this.twoFactorApiService.putTwoFactorWebAuthn(request); + } + + deleteTwoFactorWebAuthn( + request: UpdateTwoFactorWebAuthnDeleteRequest, + ): Promise { + return this.twoFactorApiService.deleteTwoFactorWebAuthn(request); + } + + putTwoFactorDisable(request: TwoFactorProviderRequest): Promise { + return this.twoFactorApiService.putTwoFactorDisable(request); + } + + putTwoFactorOrganizationDisable( + organizationId: string, + request: TwoFactorProviderRequest, + ): Promise { + return this.twoFactorApiService.putTwoFactorOrganizationDisable(organizationId, request); + } + + postTwoFactorEmailSetup(request: TwoFactorEmailRequest): Promise { + return this.twoFactorApiService.postTwoFactorEmailSetup(request); + } + + postTwoFactorEmail(request: TwoFactorEmailRequest): Promise { + return this.twoFactorApiService.postTwoFactorEmail(request); + } +} diff --git a/libs/common/src/auth/two-factor/services/index.ts b/libs/common/src/auth/two-factor/services/index.ts new file mode 100644 index 000000000000..283f7b34631d --- /dev/null +++ b/libs/common/src/auth/two-factor/services/index.ts @@ -0,0 +1,2 @@ +export * from "./default-two-factor-api.service"; +export * from "./default-two-factor.service";