diff --git a/apps/web/src/app/auth/settings/two-factor-authenticator.component.ts b/apps/web/src/app/auth/settings/two-factor-authenticator.component.ts index 7672ef7028..b4bef9f74e 100644 --- a/apps/web/src/app/auth/settings/two-factor-authenticator.component.ts +++ b/apps/web/src/app/auth/settings/two-factor-authenticator.component.ts @@ -7,9 +7,12 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; +import { DisableTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/disable-two-factor-authenticator.request"; import { UpdateTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/update-two-factor-authenticator.request"; import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator.response"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -43,6 +46,7 @@ export class TwoFactorAuthenticatorComponent @Output() onChangeStatus = new EventEmitter(); type = TwoFactorProviderType.Authenticator; key: string; + private userVerificationToken: string; override componentName = "app-two-factor-authenticator"; qrScriptError = false; @@ -63,6 +67,7 @@ export class TwoFactorAuthenticatorComponent logService: LogService, private accountService: AccountService, dialogService: DialogService, + private configService: ConfigService, ) { super( apiService, @@ -112,16 +117,46 @@ export class TwoFactorAuthenticatorComponent const request = await this.buildRequestModel(UpdateTwoFactorAuthenticatorRequest); request.token = this.formGroup.value.token; request.key = this.key; + request.userVerificationToken = this.userVerificationToken; const response = await this.apiService.putTwoFactorAuthenticator(request); await this.processResponse(response); this.onUpdated.emit(true); } + protected override async disableMethod() { + const twoFactorAuthenticatorTokenFeatureFlag = await this.configService.getFeatureFlag( + FeatureFlag.AuthenticatorTwoFactorToken, + ); + if (twoFactorAuthenticatorTokenFeatureFlag === false) { + return super.disableMethod(); + } + + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "disable" }, + content: { key: "twoStepDisableDesc" }, + type: "warning", + }); + + if (!confirmed) { + return; + } + + const request = await this.buildRequestModel(DisableTwoFactorAuthenticatorRequest); + request.type = this.type; + request.key = this.key; + request.userVerificationToken = this.userVerificationToken; + await this.apiService.deleteTwoFactorAuthenticator(request); + this.enabled = false; + this.platformUtilsService.showToast("success", null, this.i18nService.t("twoStepDisabled")); + this.onUpdated.emit(false); + } + private async processResponse(response: TwoFactorAuthenticatorResponse) { this.formGroup.get("token").setValue(null); this.enabled = response.enabled; this.key = response.key; + this.userVerificationToken = response.userVerificationToken; await this.waitForQRiousToLoadOrError().catch((error) => { this.logService.error(error); diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index d5939bf987..465f3f4348 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -30,6 +30,7 @@ import { import { SelectionReadOnlyResponse } from "../admin-console/models/response/selection-read-only.response"; import { CreateAuthRequest } from "../auth/models/request/create-auth.request"; import { DeviceVerificationRequest } from "../auth/models/request/device-verification.request"; +import { DisableTwoFactorAuthenticatorRequest } from "../auth/models/request/disable-two-factor-authenticator.request"; import { EmailTokenRequest } from "../auth/models/request/email-token.request"; import { EmailRequest } from "../auth/models/request/email.request"; import { PasswordTokenRequest } from "../auth/models/request/identity-token/password-token.request"; @@ -323,6 +324,9 @@ export abstract class ApiService { putTwoFactorAuthenticator: ( request: UpdateTwoFactorAuthenticatorRequest, ) => Promise; + deleteTwoFactorAuthenticator: ( + request: DisableTwoFactorAuthenticatorRequest, + ) => Promise; putTwoFactorEmail: (request: UpdateTwoFactorEmailRequest) => Promise; putTwoFactorDuo: (request: UpdateTwoFactorDuoRequest) => Promise; putTwoFactorOrganizationDuo: ( diff --git a/libs/common/src/auth/models/request/disable-two-factor-authenticator.request.ts b/libs/common/src/auth/models/request/disable-two-factor-authenticator.request.ts new file mode 100644 index 0000000000..7a9d758846 --- /dev/null +++ b/libs/common/src/auth/models/request/disable-two-factor-authenticator.request.ts @@ -0,0 +1,6 @@ +import { TwoFactorProviderRequest } from "./two-factor-provider.request"; + +export class DisableTwoFactorAuthenticatorRequest extends TwoFactorProviderRequest { + key: string; + userVerificationToken: string; +} diff --git a/libs/common/src/auth/models/request/update-two-factor-authenticator.request.ts b/libs/common/src/auth/models/request/update-two-factor-authenticator.request.ts index 7c61c6943d..d39f55fe8c 100644 --- a/libs/common/src/auth/models/request/update-two-factor-authenticator.request.ts +++ b/libs/common/src/auth/models/request/update-two-factor-authenticator.request.ts @@ -3,4 +3,5 @@ import { SecretVerificationRequest } from "./secret-verification.request"; export class UpdateTwoFactorAuthenticatorRequest extends SecretVerificationRequest { token: string; key: string; + userVerificationToken: string; } diff --git a/libs/common/src/auth/models/response/two-factor-authenticator.response.ts b/libs/common/src/auth/models/response/two-factor-authenticator.response.ts index 05a5517eb7..f8ca1092be 100644 --- a/libs/common/src/auth/models/response/two-factor-authenticator.response.ts +++ b/libs/common/src/auth/models/response/two-factor-authenticator.response.ts @@ -3,10 +3,12 @@ import { BaseResponse } from "../../../models/response/base.response"; export class TwoFactorAuthenticatorResponse extends BaseResponse { enabled: boolean; key: string; + userVerificationToken: string; constructor(response: any) { super(response); this.enabled = this.getResponseProperty("Enabled"); this.key = this.getResponseProperty("Key"); + this.userVerificationToken = this.getResponseProperty("UserVerificationToken"); } } diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index b5a617bc7e..0e70e6db44 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -27,6 +27,7 @@ export enum FeatureFlag { VaultBulkManagementAction = "vault-bulk-management-action", AC2828_ProviderPortalMembersPage = "AC-2828_provider-portal-members-page", DeviceTrustLogging = "pm-8285-device-trust-logging", + AuthenticatorTwoFactorToken = "authenticator-2fa-token", } export type AllowedFeatureFlagTypes = boolean | number | string; @@ -64,6 +65,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.VaultBulkManagementAction]: FALSE, [FeatureFlag.AC2828_ProviderPortalMembersPage]: FALSE, [FeatureFlag.DeviceTrustLogging]: FALSE, + [FeatureFlag.AuthenticatorTwoFactorToken]: FALSE, } satisfies Record; export type DefaultFeatureFlagValueType = typeof DefaultFeatureFlagValue; diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index 04fc95a04e..28be70221f 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -37,6 +37,7 @@ import { SelectionReadOnlyResponse } from "../admin-console/models/response/sele import { TokenService } from "../auth/abstractions/token.service"; import { CreateAuthRequest } from "../auth/models/request/create-auth.request"; import { DeviceVerificationRequest } from "../auth/models/request/device-verification.request"; +import { DisableTwoFactorAuthenticatorRequest } from "../auth/models/request/disable-two-factor-authenticator.request"; import { EmailTokenRequest } from "../auth/models/request/email-token.request"; import { EmailRequest } from "../auth/models/request/email.request"; import { DeviceRequest } from "../auth/models/request/identity-token/device.request"; @@ -998,6 +999,13 @@ export class ApiService implements ApiServiceAbstraction { return new TwoFactorAuthenticatorResponse(r); } + async deleteTwoFactorAuthenticator( + request: DisableTwoFactorAuthenticatorRequest, + ): Promise { + const r = await this.send("DELETE", "/two-factor/authenticator", request, true, true); + return new TwoFactorProviderResponse(r); + } + async putTwoFactorEmail(request: UpdateTwoFactorEmailRequest): Promise { const r = await this.send("PUT", "/two-factor/email", request, true, true); return new TwoFactorEmailResponse(r);