From dee8b0cd2e8ef91320623f71e1f662d870251664 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Thu, 18 Jun 2026 22:55:33 -0400 Subject: [PATCH 01/29] PM-38137 - Per-provider 2FA request and response models on client MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirror the server-side per-provider model rewrite on the client: - Existing PUT/DELETE setup request models drop SecretVerificationRequest inheritance and become standalone with explicit userVerificationToken fields. two-factor-email.request keeps its inheritance because it also serves the login flow. - New per-provider delete request models (YubiKey, Duo, Email, OrganizationDuo, WebAuthn delete-all) — token-only shape mirroring the server. - Authenticator delete request file/class renamed from disable-two-factor-authenticator to delete-two-factor-authenticator to match server naming. - New TwoFactorWebAuthnChallengeResponse wrapper around the FIDO2 options + minted token (replaces the bare ChallengeResponse payload from get-webauthn-challenge). - Response models for Duo, Email, WebAuthn, and YubiKey gain userVerificationToken. - Obsolete two-factor-provider.request deleted (no remaining consumers after the disable-model rewrite). --- ...delete-two-factor-authenticator.request.ts | 4 ++++ ...isable-two-factor-authenticator.request.ts | 8 -------- .../request/two-factor-duo-delete.request.ts | 3 +++ .../two-factor-email-delete.request.ts | 3 +++ .../request/two-factor-email.request.ts | 1 + ...-factor-organization-duo-delete.request.ts | 3 +++ .../request/two-factor-provider.request.ts | 9 --------- ...two-factor-web-authn-delete-all.request.ts | 3 +++ .../two-factor-yubikey-delete.request.ts | 3 +++ ...update-two-factor-authenticator.request.ts | 12 ++++------- .../request/update-two-factor-duo.request.ts | 13 +++++------- .../update-two-factor-email.request.ts | 11 ++++------ ...ate-two-factor-web-authn-delete.request.ts | 9 +++------ .../update-two-factor-web-authn.request.ts | 13 +++++------- .../update-two-factor-yubikey-otp.request.ts | 19 ++++++++---------- .../response/two-factor-duo.response.ts | 2 ++ .../response/two-factor-email.response.ts | 2 ++ ...two-factor-web-authn-challenge.response.ts | 20 +++++++++++++++++++ .../response/two-factor-web-authn.response.ts | 2 ++ .../response/two-factor-yubi-key.response.ts | 2 ++ 20 files changed, 77 insertions(+), 65 deletions(-) create mode 100644 libs/common/src/auth/models/request/delete-two-factor-authenticator.request.ts delete mode 100644 libs/common/src/auth/models/request/disable-two-factor-authenticator.request.ts create mode 100644 libs/common/src/auth/models/request/two-factor-duo-delete.request.ts create mode 100644 libs/common/src/auth/models/request/two-factor-email-delete.request.ts create mode 100644 libs/common/src/auth/models/request/two-factor-organization-duo-delete.request.ts delete mode 100644 libs/common/src/auth/models/request/two-factor-provider.request.ts create mode 100644 libs/common/src/auth/models/request/two-factor-web-authn-delete-all.request.ts create mode 100644 libs/common/src/auth/models/request/two-factor-yubikey-delete.request.ts create mode 100644 libs/common/src/auth/models/response/two-factor-web-authn-challenge.response.ts diff --git a/libs/common/src/auth/models/request/delete-two-factor-authenticator.request.ts b/libs/common/src/auth/models/request/delete-two-factor-authenticator.request.ts new file mode 100644 index 000000000000..09b570f84c1f --- /dev/null +++ b/libs/common/src/auth/models/request/delete-two-factor-authenticator.request.ts @@ -0,0 +1,4 @@ +export class DeleteTwoFactorAuthenticatorRequest { + userVerificationToken!: string; + key!: string; +} 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 deleted file mode 100644 index 37337720e1f6..000000000000 --- a/libs/common/src/auth/models/request/disable-two-factor-authenticator.request.ts +++ /dev/null @@ -1,8 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -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/two-factor-duo-delete.request.ts b/libs/common/src/auth/models/request/two-factor-duo-delete.request.ts new file mode 100644 index 000000000000..e9e39eba3449 --- /dev/null +++ b/libs/common/src/auth/models/request/two-factor-duo-delete.request.ts @@ -0,0 +1,3 @@ +export class TwoFactorDuoDeleteRequest { + userVerificationToken!: string; +} diff --git a/libs/common/src/auth/models/request/two-factor-email-delete.request.ts b/libs/common/src/auth/models/request/two-factor-email-delete.request.ts new file mode 100644 index 000000000000..f29f1cbc7590 --- /dev/null +++ b/libs/common/src/auth/models/request/two-factor-email-delete.request.ts @@ -0,0 +1,3 @@ +export class TwoFactorEmailDeleteRequest { + userVerificationToken!: string; +} diff --git a/libs/common/src/auth/models/request/two-factor-email.request.ts b/libs/common/src/auth/models/request/two-factor-email.request.ts index bdff27cd7004..4a0a9cf3dc4f 100644 --- a/libs/common/src/auth/models/request/two-factor-email.request.ts +++ b/libs/common/src/auth/models/request/two-factor-email.request.ts @@ -7,4 +7,5 @@ export class TwoFactorEmailRequest extends SecretVerificationRequest { deviceIdentifier: string; authRequestId: string; ssoEmail2FaSessionToken?: string; + userVerificationToken?: string; } diff --git a/libs/common/src/auth/models/request/two-factor-organization-duo-delete.request.ts b/libs/common/src/auth/models/request/two-factor-organization-duo-delete.request.ts new file mode 100644 index 000000000000..966f0c01461a --- /dev/null +++ b/libs/common/src/auth/models/request/two-factor-organization-duo-delete.request.ts @@ -0,0 +1,3 @@ +export class TwoFactorOrganizationDuoDeleteRequest { + userVerificationToken!: string; +} diff --git a/libs/common/src/auth/models/request/two-factor-provider.request.ts b/libs/common/src/auth/models/request/two-factor-provider.request.ts deleted file mode 100644 index 05cec8c421f4..000000000000 --- a/libs/common/src/auth/models/request/two-factor-provider.request.ts +++ /dev/null @@ -1,9 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { TwoFactorProviderType } from "../../enums/two-factor-provider-type"; - -import { SecretVerificationRequest } from "./secret-verification.request"; - -export class TwoFactorProviderRequest extends SecretVerificationRequest { - type: TwoFactorProviderType; -} diff --git a/libs/common/src/auth/models/request/two-factor-web-authn-delete-all.request.ts b/libs/common/src/auth/models/request/two-factor-web-authn-delete-all.request.ts new file mode 100644 index 000000000000..b81ec226571f --- /dev/null +++ b/libs/common/src/auth/models/request/two-factor-web-authn-delete-all.request.ts @@ -0,0 +1,3 @@ +export class TwoFactorWebAuthnDeleteAllRequest { + userVerificationToken!: string; +} diff --git a/libs/common/src/auth/models/request/two-factor-yubikey-delete.request.ts b/libs/common/src/auth/models/request/two-factor-yubikey-delete.request.ts new file mode 100644 index 000000000000..53eb765981ff --- /dev/null +++ b/libs/common/src/auth/models/request/two-factor-yubikey-delete.request.ts @@ -0,0 +1,3 @@ +export class TwoFactorYubiKeyDeleteRequest { + 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 501802cb474b..6cd24029d87a 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 @@ -1,9 +1,5 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { SecretVerificationRequest } from "./secret-verification.request"; - -export class UpdateTwoFactorAuthenticatorRequest extends SecretVerificationRequest { - token: string; - key: string; - userVerificationToken: string; +export class UpdateTwoFactorAuthenticatorRequest { + token!: string; + key!: string; + userVerificationToken!: string; } diff --git a/libs/common/src/auth/models/request/update-two-factor-duo.request.ts b/libs/common/src/auth/models/request/update-two-factor-duo.request.ts index 9d9d2596450f..3d432b3516a5 100644 --- a/libs/common/src/auth/models/request/update-two-factor-duo.request.ts +++ b/libs/common/src/auth/models/request/update-two-factor-duo.request.ts @@ -1,9 +1,6 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { SecretVerificationRequest } from "./secret-verification.request"; - -export class UpdateTwoFactorDuoRequest extends SecretVerificationRequest { - clientId: string; - clientSecret: string; - host: string; +export class UpdateTwoFactorDuoRequest { + clientId!: string; + clientSecret!: string; + host!: string; + userVerificationToken!: string; } diff --git a/libs/common/src/auth/models/request/update-two-factor-email.request.ts b/libs/common/src/auth/models/request/update-two-factor-email.request.ts index 6baa0ed32fc5..d5700efeb757 100644 --- a/libs/common/src/auth/models/request/update-two-factor-email.request.ts +++ b/libs/common/src/auth/models/request/update-two-factor-email.request.ts @@ -1,8 +1,5 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { SecretVerificationRequest } from "./secret-verification.request"; - -export class UpdateTwoFactorEmailRequest extends SecretVerificationRequest { - token: string; - email: string; +export class UpdateTwoFactorEmailRequest { + token!: string; + email!: string; + userVerificationToken!: string; } diff --git a/libs/common/src/auth/models/request/update-two-factor-web-authn-delete.request.ts b/libs/common/src/auth/models/request/update-two-factor-web-authn-delete.request.ts index 73068c1e7ddb..23eeff21458d 100644 --- a/libs/common/src/auth/models/request/update-two-factor-web-authn-delete.request.ts +++ b/libs/common/src/auth/models/request/update-two-factor-web-authn-delete.request.ts @@ -1,7 +1,4 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { SecretVerificationRequest } from "./secret-verification.request"; - -export class UpdateTwoFactorWebAuthnDeleteRequest extends SecretVerificationRequest { - id: number; +export class UpdateTwoFactorWebAuthnDeleteRequest { + id!: number; + userVerificationToken!: string; } diff --git a/libs/common/src/auth/models/request/update-two-factor-web-authn.request.ts b/libs/common/src/auth/models/request/update-two-factor-web-authn.request.ts index 57b1655eac14..de38fe836ea9 100644 --- a/libs/common/src/auth/models/request/update-two-factor-web-authn.request.ts +++ b/libs/common/src/auth/models/request/update-two-factor-web-authn.request.ts @@ -1,9 +1,6 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { SecretVerificationRequest } from "./secret-verification.request"; - -export class UpdateTwoFactorWebAuthnRequest extends SecretVerificationRequest { - deviceResponse: PublicKeyCredential; - name: string; - id: number; +export class UpdateTwoFactorWebAuthnRequest { + deviceResponse!: PublicKeyCredential; + name!: string; + id!: number; + userVerificationToken!: string; } diff --git a/libs/common/src/auth/models/request/update-two-factor-yubikey-otp.request.ts b/libs/common/src/auth/models/request/update-two-factor-yubikey-otp.request.ts index 148e6c02fab6..374609804980 100644 --- a/libs/common/src/auth/models/request/update-two-factor-yubikey-otp.request.ts +++ b/libs/common/src/auth/models/request/update-two-factor-yubikey-otp.request.ts @@ -1,12 +1,9 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { SecretVerificationRequest } from "./secret-verification.request"; - -export class UpdateTwoFactorYubikeyOtpRequest extends SecretVerificationRequest { - key1: string; - key2: string; - key3: string; - key4: string; - key5: string; - nfc: boolean; +export class UpdateTwoFactorYubikeyOtpRequest { + key1!: string; + key2!: string; + key3!: string; + key4!: string; + key5!: string; + nfc!: boolean; + userVerificationToken!: string; } diff --git a/libs/common/src/auth/models/response/two-factor-duo.response.ts b/libs/common/src/auth/models/response/two-factor-duo.response.ts index a195aa236dda..36ca6c63fcca 100644 --- a/libs/common/src/auth/models/response/two-factor-duo.response.ts +++ b/libs/common/src/auth/models/response/two-factor-duo.response.ts @@ -5,6 +5,7 @@ export class TwoFactorDuoResponse extends BaseResponse { host: string; clientSecret: string; clientId: string; + userVerificationToken: string; constructor(response: any) { super(response); @@ -12,5 +13,6 @@ export class TwoFactorDuoResponse extends BaseResponse { this.host = this.getResponseProperty("Host"); this.clientSecret = this.getResponseProperty("ClientSecret"); this.clientId = this.getResponseProperty("ClientId"); + this.userVerificationToken = this.getResponseProperty("UserVerificationToken"); } } diff --git a/libs/common/src/auth/models/response/two-factor-email.response.ts b/libs/common/src/auth/models/response/two-factor-email.response.ts index a0c81745feed..7462ae8ae11a 100644 --- a/libs/common/src/auth/models/response/two-factor-email.response.ts +++ b/libs/common/src/auth/models/response/two-factor-email.response.ts @@ -3,10 +3,12 @@ import { BaseResponse } from "../../../models/response/base.response"; export class TwoFactorEmailResponse extends BaseResponse { enabled: boolean; email: string; + userVerificationToken: string; constructor(response: any) { super(response); this.enabled = this.getResponseProperty("Enabled"); this.email = this.getResponseProperty("Email"); + this.userVerificationToken = this.getResponseProperty("UserVerificationToken"); } } diff --git a/libs/common/src/auth/models/response/two-factor-web-authn-challenge.response.ts b/libs/common/src/auth/models/response/two-factor-web-authn-challenge.response.ts new file mode 100644 index 000000000000..09eca6f53160 --- /dev/null +++ b/libs/common/src/auth/models/response/two-factor-web-authn-challenge.response.ts @@ -0,0 +1,20 @@ +import { BaseResponse } from "../../../models/response/base.response"; + +import { ChallengeResponse } from "./two-factor-web-authn.response"; + +/** + * Wrapper around {@link ChallengeResponse} that adds the user-verification token minted by + * the server-side `GetWebAuthnChallenge` endpoint. Replaces the previous response shape, which + * returned the FIDO2 options object directly and had no place to attach the token. + */ +export class TwoFactorWebAuthnChallengeResponse extends BaseResponse { + options: ChallengeResponse | null; + userVerificationToken: string; + + constructor(response: any) { + super(response); + const options = this.getResponseProperty("Options"); + this.options = options == null ? null : new ChallengeResponse(options); + this.userVerificationToken = this.getResponseProperty("UserVerificationToken"); + } +} diff --git a/libs/common/src/auth/models/response/two-factor-web-authn.response.ts b/libs/common/src/auth/models/response/two-factor-web-authn.response.ts index 855dfc06d5e9..17e2a98ef002 100644 --- a/libs/common/src/auth/models/response/two-factor-web-authn.response.ts +++ b/libs/common/src/auth/models/response/two-factor-web-authn.response.ts @@ -4,12 +4,14 @@ import { Utils } from "../../../platform/misc/utils"; export class TwoFactorWebAuthnResponse extends BaseResponse { enabled: boolean; keys: KeyResponse[]; + userVerificationToken: string; constructor(response: any) { super(response); this.enabled = this.getResponseProperty("Enabled"); const keys = this.getResponseProperty("Keys"); this.keys = keys == null ? null : keys.map((k: any) => new KeyResponse(k)); + this.userVerificationToken = this.getResponseProperty("UserVerificationToken"); } } diff --git a/libs/common/src/auth/models/response/two-factor-yubi-key.response.ts b/libs/common/src/auth/models/response/two-factor-yubi-key.response.ts index cfdf41cce719..45a4e4f92d37 100644 --- a/libs/common/src/auth/models/response/two-factor-yubi-key.response.ts +++ b/libs/common/src/auth/models/response/two-factor-yubi-key.response.ts @@ -8,6 +8,7 @@ export class TwoFactorYubiKeyResponse extends BaseResponse { key4: string; key5: string; nfc: boolean; + userVerificationToken: string; constructor(response: any) { super(response); @@ -18,5 +19,6 @@ export class TwoFactorYubiKeyResponse extends BaseResponse { this.key4 = this.getResponseProperty("Key4"); this.key5 = this.getResponseProperty("Key5"); this.nfc = this.getResponseProperty("Nfc"); + this.userVerificationToken = this.getResponseProperty("UserVerificationToken"); } } From 0bd18c6bd4f689cdb59a24f63b49095f7e9956f0 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Thu, 18 Jun 2026 22:55:45 -0400 Subject: [PATCH 02/29] PM-38137 - Expose per-provider delete methods on 2FA service layer Both TwoFactorApiService and TwoFactorService (abstractions and implementations) pick up: - New methods deleteTwoFactorYubiKey, deleteTwoFactorDuo, deleteTwoFactorEmail, deleteTwoFactorOrganizationDuo, and deleteTwoFactorWebAuthnAll routing to the corresponding per-provider DELETE endpoints. - Removal of legacy putTwoFactorDisable and putTwoFactorOrganizationDisable (server endpoints are gone). - Updated return type on getTwoFactorWebAuthnChallenge to the new TwoFactorWebAuthnChallengeResponse wrapper. DefaultTwoFactorApiService spec: legacy put*Disable tests removed, five new deleteTwoFactor* tests added, WebAuthn challenge test asserts the new wrapper. --- .../abstractions/two-factor-api.service.ts | 140 +++++++++------ .../abstractions/two-factor.service.ts | 89 ++++++--- .../default-two-factor-api.service.spec.ts | 169 ++++++++++++------ .../default-two-factor-api.service.ts | 89 ++++++--- .../services/default-two-factor.service.ts | 46 +++-- 5 files changed, 360 insertions(+), 173 deletions(-) diff --git a/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts b/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts index 278813c4c303..ebef1d1475fd 100644 --- a/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts +++ b/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts @@ -1,7 +1,11 @@ -import { DisableTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/disable-two-factor-authenticator.request"; +import { DeleteTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/delete-two-factor-authenticator.request"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; +import { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-delete.request"; +import { TwoFactorEmailDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-email-delete.request"; import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request"; -import { TwoFactorProviderRequest } from "@bitwarden/common/auth/models/request/two-factor-provider.request"; +import { TwoFactorOrganizationDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-organization-duo-delete.request"; +import { TwoFactorWebAuthnDeleteAllRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-delete-all.request"; +import { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-delete.request"; import { UpdateTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/update-two-factor-authenticator.request"; import { UpdateTwoFactorDuoRequest } from "@bitwarden/common/auth/models/request/update-two-factor-duo.request"; import { UpdateTwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/update-two-factor-email.request"; @@ -13,10 +17,8 @@ import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two import { TwoFactorEmailResponse } from "@bitwarden/common/auth/models/response/two-factor-email.response"; import { TwoFactorProviderResponse } from "@bitwarden/common/auth/models/response/two-factor-provider.response"; import { TwoFactorRecoverResponse } from "@bitwarden/common/auth/models/response/two-factor-recover.response"; -import { - ChallengeResponse, - TwoFactorWebAuthnResponse, -} from "@bitwarden/common/auth/models/response/two-factor-web-authn.response"; +import { TwoFactorWebAuthnChallengeResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-challenge.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 { ListResponse } from "@bitwarden/common/models/response/list.response"; @@ -26,7 +28,7 @@ import { ListResponse } from "@bitwarden/common/models/response/list.response"; * authenticator apps (TOTP), email, Duo, YubiKey, WebAuthn (FIDO2), and recovery codes. * * All methods that retrieve sensitive configuration data require user verification via - * SecretVerificationRequest. Premium-tier providers (Duo, YubiKey) require an active + * SecretVerificationRequest. Update/enable methods for Duo and YubiKey require an active * premium subscription. Organization-level methods require appropriate administrative permissions. */ export abstract class TwoFactorApiService { @@ -62,7 +64,7 @@ export abstract class TwoFactorApiService { /** * Gets the email two-factor configuration for the current user. - * Returns the configured email address and enabled status. + * Returns the configured email address, enabled status, and a user verification token. * Requires user verification via master password or OTP. * * @param request The secret verification request to authorize the operation. @@ -72,8 +74,8 @@ export abstract class TwoFactorApiService { /** * Gets the Duo two-factor configuration for the current user. - * Returns Duo integration configuration details. - * Requires user verification and an active premium subscription. + * Returns Duo integration configuration details and a user verification token. + * Requires user verification via master password or OTP. * * @param request The secret verification request to authorize the operation. * @returns A promise that resolves to the Duo configuration. @@ -82,7 +84,7 @@ export abstract class TwoFactorApiService { /** * Gets the Duo two-factor configuration for an organization. - * Returns organization-level Duo integration configuration. + * Returns organization-level Duo integration configuration and a user verification token. * Requires user verification and organization policy management permissions. * * @param organizationId The ID of the organization. @@ -96,8 +98,8 @@ export abstract class TwoFactorApiService { /** * Gets the YubiKey OTP two-factor configuration for the current user. - * Returns configured YubiKey device identifiers (multiple keys supported). - * Requires user verification and an active premium subscription. + * Returns configured YubiKey device identifiers and a user verification token. + * Requires user verification via master password or OTP. * * @param request The secret verification request to authorize the operation. * @returns A promise that resolves to the YubiKey configuration. @@ -108,7 +110,8 @@ export abstract class TwoFactorApiService { /** * Gets the WebAuthn (FIDO2) two-factor configuration for the current user. - * Returns a list of registered WebAuthn credentials with their names and IDs. + * Returns a list of registered WebAuthn credentials with their names and IDs, + * and a user verification token. * Requires user verification via master password or OTP. * * @param request The secret verification request to authorize the operation. @@ -121,15 +124,16 @@ export abstract class TwoFactorApiService { /** * Gets a WebAuthn challenge for registering a new WebAuthn credential. * This must be called before putTwoFactorWebAuthn to obtain the cryptographic challenge - * required for credential creation. The challenge is used by the browser's WebAuthn API. + * required for credential creation. The challenge is wrapped together with a user verification + * token used to authorize the subsequent PUT. * Requires user verification via master password or OTP. * * @param request The secret verification request to authorize the operation. - * @returns A promise that resolves to the credential creation options containing the challenge. + * @returns A promise that resolves to the wrapped challenge response. */ abstract getTwoFactorWebAuthnChallenge( request: SecretVerificationRequest, - ): Promise; + ): Promise; /** * Gets the recovery code configuration for the current user. @@ -157,40 +161,62 @@ export abstract class TwoFactorApiService { ): Promise; /** - * Disables the authenticator (TOTP) two-factor provider for the current user. - * Requires user verification token to confirm the operation. + * Removes the authenticator (TOTP) two-factor enrollment for the current user. + * Requires a user verification token to confirm the operation. * - * @param request The request containing verification credentials to disable the provider. + * @param request The request containing the user verification token and key. * @returns A promise that resolves to the updated provider status. */ abstract deleteTwoFactorAuthenticator( - request: DisableTwoFactorAuthenticatorRequest, + request: DeleteTwoFactorAuthenticatorRequest, ): Promise; /** * Enables or updates the email two-factor provider. * Validates the email verification token sent via postTwoFactorEmailSetup before enabling. - * The token must match the code sent to the specified email address. * * @param request The request containing the email configuration and verification token. * @returns A promise that resolves to the updated email two-factor configuration. */ abstract putTwoFactorEmail(request: UpdateTwoFactorEmailRequest): Promise; + /** + * Removes the email two-factor enrollment for the current user. + * Requires a user verification token to confirm the operation. + * + * @param request The request containing the user verification token. + * @returns A promise that resolves to the updated provider status. + */ + abstract deleteTwoFactorEmail( + request: TwoFactorEmailDeleteRequest, + ): 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. + * Requires an active premium subscription. * * @param request The request containing the Duo integration configuration. * @returns A promise that resolves to the updated Duo configuration. */ abstract putTwoFactorDuo(request: UpdateTwoFactorDuoRequest): Promise; + /** + * Removes the Duo two-factor enrollment for the current user. + * Requires a user verification token to confirm the operation. + * Does NOT require premium — disabling must always be available even if premium has lapsed. + * + * @param request The request containing the user verification token. + * @returns A promise that resolves to the updated provider status. + */ + abstract deleteTwoFactorDuo( + request: TwoFactorDuoDeleteRequest, + ): 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. + * Requires organization policy management permissions. * * @param organizationId The ID of the organization. * @param request The request containing the Duo integration configuration. @@ -201,12 +227,25 @@ export abstract class TwoFactorApiService { request: UpdateTwoFactorDuoRequest, ): Promise; + /** + * Removes the Duo two-factor enrollment for an organization. + * Requires a user verification token to confirm the operation and + * organization policy management permissions. + * + * @param organizationId The ID of the organization. + * @param request The request containing the user verification token. + * @returns A promise that resolves to the updated provider status. + */ + abstract deleteTwoFactorOrganizationDuo( + organizationId: string, + request: TwoFactorOrganizationDuoDeleteRequest, + ): Promise; + /** * Enables or updates the YubiKey OTP two-factor provider. * Validates each provided YubiKey by testing an OTP from the device. * Supports up to 5 YubiKey devices. Empty key slots are allowed. - * Requires user verification and an active premium subscription. - * Includes a 2-second delay on validation failure to prevent timing attacks. + * Requires an active premium subscription. * * @param request The request containing YubiKey device identifiers and test OTPs. * @returns A promise that resolves to the updated YubiKey configuration. @@ -215,13 +254,24 @@ export abstract class TwoFactorApiService { request: UpdateTwoFactorYubikeyOtpRequest, ): Promise; + /** + * Removes the YubiKey two-factor enrollment for the current user. + * Requires a user verification token to confirm the operation. + * Does NOT require premium — disabling must always be available even if premium has lapsed. + * + * @param request The request containing the user verification token. + * @returns A promise that resolves to the updated provider status. + */ + abstract deleteTwoFactorYubiKey( + request: TwoFactorYubiKeyDeleteRequest, + ): Promise; + /** * Registers a new WebAuthn (FIDO2) credential for two-factor authentication. * Must be called after getTwoFactorWebAuthnChallenge to complete the registration flow. * The device response contains the signed challenge from the authenticator device. - * Requires user verification via master password or OTP. * - * @param request The request containing the WebAuthn credential creation response from the browser. + * @param request The request containing the WebAuthn credential creation response and verification token. * @returns A promise that resolves to the updated WebAuthn configuration with the new credential. */ abstract putTwoFactorWebAuthn( @@ -232,9 +282,9 @@ export abstract class TwoFactorApiService { * 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. + * Server refuses to remove the last registered credential — use deleteTwoFactorWebAuthnAll instead. * - * @param request The request containing the credential ID to remove. + * @param request The request containing the credential ID and verification token. * @returns A promise that resolves to the updated WebAuthn configuration. */ abstract deleteTwoFactorWebAuthn( @@ -242,29 +292,15 @@ export abstract class TwoFactorApiService { ): 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. + * Removes the entire WebAuthn (FIDO2) two-factor enrollment for the current user — all + * credentials are removed and the provider is disabled in a single round-trip. The only path + * that can clear the last registered credential, since per-credential delete refuses by design. * - * @param request The request specifying which provider type to disable. + * @param request The request containing the user verification token. * @returns A promise that resolves to the updated provider status. */ - abstract putTwoFactorDisable( - request: TwoFactorProviderRequest, - ): Promise; - - /** - * Disables a specific two-factor provider for an organization. - * The provider will no longer be available for organization members. - * Requires user verification and organization policy management permissions. - * - * @param organizationId The ID of the organization. - * @param request The request specifying which provider type to disable. - * @returns A promise that resolves to the updated provider status. - */ - abstract putTwoFactorOrganizationDisable( - organizationId: string, - request: TwoFactorProviderRequest, + abstract deleteTwoFactorWebAuthnAll( + request: TwoFactorWebAuthnDeleteAllRequest, ): Promise; /** @@ -272,9 +308,9 @@ export abstract class TwoFactorApiService { * 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. + * Requires a user verification token (from a prior getTwoFactorEmail call). * - * @param request The request containing the email address for two-factor setup. + * @param request The request containing the email address and verification token for two-factor setup. * @returns A promise that resolves when the verification email has been sent. */ abstract postTwoFactorEmailSetup(request: TwoFactorEmailRequest): Promise; 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 index 4cba40716e10..2648857392ef 100644 --- a/libs/common/src/auth/two-factor/abstractions/two-factor.service.ts +++ b/libs/common/src/auth/two-factor/abstractions/two-factor.service.ts @@ -1,10 +1,14 @@ 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 { DeleteTwoFactorAuthenticatorRequest } from "../../models/request/delete-two-factor-authenticator.request"; import { SecretVerificationRequest } from "../../models/request/secret-verification.request"; +import { TwoFactorDuoDeleteRequest } from "../../models/request/two-factor-duo-delete.request"; +import { TwoFactorEmailDeleteRequest } from "../../models/request/two-factor-email-delete.request"; import { TwoFactorEmailRequest } from "../../models/request/two-factor-email.request"; -import { TwoFactorProviderRequest } from "../../models/request/two-factor-provider.request"; +import { TwoFactorOrganizationDuoDeleteRequest } from "../../models/request/two-factor-organization-duo-delete.request"; +import { TwoFactorWebAuthnDeleteAllRequest } from "../../models/request/two-factor-web-authn-delete-all.request"; +import { TwoFactorYubiKeyDeleteRequest } from "../../models/request/two-factor-yubikey-delete.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"; @@ -17,10 +21,8 @@ import { TwoFactorDuoResponse } from "../../models/response/two-factor-duo.respo 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 { TwoFactorWebAuthnChallengeResponse } from "../../models/response/two-factor-web-authn-challenge.response"; +import { TwoFactorWebAuthnResponse } from "../../models/response/two-factor-web-authn.response"; import { TwoFactorYubiKeyResponse } from "../../models/response/two-factor-yubi-key.response"; /** @@ -310,7 +312,7 @@ export abstract class TwoFactorService { */ abstract getTwoFactorWebAuthnChallenge( request: SecretVerificationRequest, - ): Promise; + ): Promise; /** * Gets the recovery code configuration for the current user from the API. @@ -341,16 +343,15 @@ export abstract class TwoFactorService { ): Promise; /** - * Disables the authenticator (TOTP) two-factor provider for the current user. - * Requires user verification token to confirm the operation. + * Removes the authenticator (TOTP) two-factor enrollment for the current user. + * Requires a user verification token to confirm the operation. * Used for settings management. * - * @param request The {@link DisableTwoFactorAuthenticatorRequest} to prove authentication. + * @param request The {@link DeleteTwoFactorAuthenticatorRequest} containing the user verification token and key. * @returns A promise that resolves to the updated provider status. - * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. */ abstract deleteTwoFactorAuthenticator( - request: DisableTwoFactorAuthenticatorRequest, + request: DeleteTwoFactorAuthenticatorRequest, ): Promise; /** @@ -439,33 +440,69 @@ export abstract class TwoFactorService { ): 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. + * Removes the YubiKey two-factor enrollment for the current user. + * Requires a user verification token to confirm the operation. + * Does NOT require premium — disabling must always be available even if premium has lapsed. * Used for settings management. * - * @param request The {@link TwoFactorProviderRequest} to prove authentication. + * @param request The {@link TwoFactorYubiKeyDeleteRequest} containing the user verification token. * @returns A promise that resolves to the updated provider status. - * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. */ - abstract putTwoFactorDisable( - request: TwoFactorProviderRequest, + abstract deleteTwoFactorYubiKey( + request: TwoFactorYubiKeyDeleteRequest, ): 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. + * Removes the Duo two-factor enrollment for the current user. + * Requires a user verification token to confirm the operation. + * Does NOT require premium — disabling must always be available even if premium has lapsed. + * Used for settings management. + * + * @param request The {@link TwoFactorDuoDeleteRequest} containing the user verification token. + * @returns A promise that resolves to the updated provider status. + */ + abstract deleteTwoFactorDuo( + request: TwoFactorDuoDeleteRequest, + ): Promise; + + /** + * Removes the email two-factor enrollment for the current user. + * Requires a user verification token to confirm the operation. + * Used for settings management. + * + * @param request The {@link TwoFactorEmailDeleteRequest} containing the user verification token. + * @returns A promise that resolves to the updated provider status. + */ + abstract deleteTwoFactorEmail( + request: TwoFactorEmailDeleteRequest, + ): Promise; + + /** + * Removes the Duo two-factor enrollment for an organization. + * Requires a user verification token to confirm the operation and + * organization policy management permissions. * Used for settings management. * * @param organizationId The ID of the organization. - * @param request The {@link TwoFactorProviderRequest} to prove authentication. + * @param request The {@link TwoFactorOrganizationDuoDeleteRequest} containing the user verification token. * @returns A promise that resolves to the updated provider status. - * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. */ - abstract putTwoFactorOrganizationDisable( + abstract deleteTwoFactorOrganizationDuo( organizationId: string, - request: TwoFactorProviderRequest, + request: TwoFactorOrganizationDuoDeleteRequest, + ): Promise; + + /** + * Removes the entire WebAuthn (FIDO2) two-factor enrollment for the current user — all + * credentials are removed and the provider is disabled in a single round-trip. The only path + * that can clear the last registered credential, since per-credential delete refuses by design. + * Used for settings management. + * + * @param request The {@link TwoFactorWebAuthnDeleteAllRequest} containing the user verification token. + * @returns A promise that resolves to the updated provider status. + */ + abstract deleteTwoFactorWebAuthnAll( + request: TwoFactorWebAuthnDeleteAllRequest, ): Promise; /** diff --git a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts index c54790d9f76e..0259df9b3a72 100644 --- a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts +++ b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts @@ -1,10 +1,14 @@ import { mock, MockProxy } from "jest-mock-extended"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { DisableTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/disable-two-factor-authenticator.request"; +import { DeleteTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/delete-two-factor-authenticator.request"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; +import { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-delete.request"; +import { TwoFactorEmailDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-email-delete.request"; import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request"; -import { TwoFactorProviderRequest } from "@bitwarden/common/auth/models/request/two-factor-provider.request"; +import { TwoFactorOrganizationDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-organization-duo-delete.request"; +import { TwoFactorWebAuthnDeleteAllRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-delete-all.request"; +import { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-delete.request"; import { UpdateTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/update-two-factor-authenticator.request"; import { UpdateTwoFactorDuoRequest } from "@bitwarden/common/auth/models/request/update-two-factor-duo.request"; import { UpdateTwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/update-two-factor-email.request"; @@ -16,10 +20,8 @@ import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two import { TwoFactorEmailResponse } from "@bitwarden/common/auth/models/response/two-factor-email.response"; import { TwoFactorProviderResponse } from "@bitwarden/common/auth/models/response/two-factor-provider.response"; import { TwoFactorRecoverResponse } from "@bitwarden/common/auth/models/response/two-factor-recover.response"; -import { - TwoFactorWebAuthnResponse, - ChallengeResponse, -} from "@bitwarden/common/auth/models/response/two-factor-web-authn.response"; +import { TwoFactorWebAuthnChallengeResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-challenge.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 { ListResponse } from "@bitwarden/common/models/response/list.response"; @@ -136,8 +138,9 @@ describe("TwoFactorApiService", () => { describe("deleteTwoFactorAuthenticator", () => { it("disables authenticator two-factor authentication", async () => { - const request = new DisableTwoFactorAuthenticatorRequest(); - request.masterPasswordHash = "master-password-hash"; + const request = new DeleteTwoFactorAuthenticatorRequest(); + request.userVerificationToken = "uv-token"; + request.key = "MFRGGZDFMZTWQ2LK"; const mockResponse = { Enabled: false, Type: 0, @@ -466,20 +469,23 @@ describe("TwoFactorApiService", () => { }); describe("getTwoFactorWebAuthnChallenge", () => { - it("obtains cryptographic challenge for WebAuthn credential registration", async () => { + it("obtains the wrapped challenge and user verification token", async () => { const request = new SecretVerificationRequest(); request.masterPasswordHash = "master-password-hash"; const mockResponse = { - challenge: "Y2hhbGxlbmdlLXN0cmluZw", - rp: { name: "Bitwarden" }, - user: { - id: "dXNlci1pZA", - name: "user@example.com", - displayName: "User", + Options: { + challenge: "Y2hhbGxlbmdlLXN0cmluZw", + rp: { name: "Bitwarden" }, + user: { + id: "dXNlci1pZA", + name: "user@example.com", + displayName: "User", + }, + pubKeyCredParams: [{ type: "public-key", alg: -7 }], // ES256 + excludeCredentials: [] as PublicKeyCredentialDescriptor[], + timeout: 60000, }, - pubKeyCredParams: [{ type: "public-key", alg: -7 }], // ES256 - excludeCredentials: [] as PublicKeyCredentialDescriptor[], - timeout: 60000, + UserVerificationToken: "uv-token", }; apiService.send.mockResolvedValue(mockResponse); @@ -492,14 +498,11 @@ describe("TwoFactorApiService", () => { true, true, ); - expect(result).toBeInstanceOf(ChallengeResponse); - expect(result.challenge).toBeDefined(); - expect(result.rp).toHaveProperty("name", "Bitwarden"); - expect(result.user).toHaveProperty("id"); - expect(result.user).toHaveProperty("name"); - expect(result.user).toHaveProperty("displayName", "User"); - expect(result.pubKeyCredParams).toHaveLength(1); - expect(Number(result.timeout)).toBeTruthy(); + expect(result).toBeInstanceOf(TwoFactorWebAuthnChallengeResponse); + expect(result.options).toBeDefined(); + expect(result.options.challenge).toBeDefined(); + expect(result.options.rp).toHaveProperty("name", "Bitwarden"); + expect(result.userVerificationToken).toBe("uv-token"); }); }); @@ -588,7 +591,7 @@ describe("TwoFactorApiService", () => { it("removes specific WebAuthn credential while preserving other registered keys", async () => { const request = new UpdateTwoFactorWebAuthnDeleteRequest(); request.id = 1; - request.masterPasswordHash = "master-password-hash"; + request.userVerificationToken = "uv-token"; const mockResponse = { Enabled: true, Keys: [{ Name: "Security Key", Id: 2, Migrated: true }], // Key with id:1 removed @@ -637,60 +640,118 @@ describe("TwoFactorApiService", () => { }); }); - describe("Disable APIs", () => { - describe("putTwoFactorDisable", () => { - it("disables specified two-factor provider for current user", async () => { - const request = new TwoFactorProviderRequest(); - request.type = 0; // Authenticator - request.masterPasswordHash = "master-password-hash"; - const mockResponse = { - Enabled: false, - Type: 0, - }; + describe("Per-provider Delete APIs", () => { + describe("deleteTwoFactorYubiKey", () => { + it("removes YubiKey two-factor enrollment for the current user", async () => { + const request = new TwoFactorYubiKeyDeleteRequest(); + request.userVerificationToken = "uv-token"; + const mockResponse = { Enabled: false, Type: 3 }; // YubiKey apiService.send.mockResolvedValue(mockResponse); - const result = await twoFactorApiService.putTwoFactorDisable(request); + const result = await twoFactorApiService.deleteTwoFactorYubiKey(request); expect(apiService.send).toHaveBeenCalledWith( - "PUT", - "/two-factor/disable", + "DELETE", + "/two-factor/yubikey", request, true, true, ); expect(result).toBeInstanceOf(TwoFactorProviderResponse); expect(result.enabled).toBe(false); - expect(result.type).toBe(0); // Authenticator + expect(result.type).toBe(3); + }); + }); + + describe("deleteTwoFactorDuo", () => { + it("removes Duo two-factor enrollment for the current user", async () => { + const request = new TwoFactorDuoDeleteRequest(); + request.userVerificationToken = "uv-token"; + const mockResponse = { Enabled: false, Type: 2 }; // Duo + apiService.send.mockResolvedValue(mockResponse); + + const result = await twoFactorApiService.deleteTwoFactorDuo(request); + + expect(apiService.send).toHaveBeenCalledWith( + "DELETE", + "/two-factor/duo", + request, + true, + true, + ); + expect(result).toBeInstanceOf(TwoFactorProviderResponse); + expect(result.enabled).toBe(false); + expect(result.type).toBe(2); + }); + }); + + describe("deleteTwoFactorEmail", () => { + it("removes email two-factor enrollment for the current user", async () => { + const request = new TwoFactorEmailDeleteRequest(); + request.userVerificationToken = "uv-token"; + const mockResponse = { Enabled: false, Type: 1 }; // Email + apiService.send.mockResolvedValue(mockResponse); + + const result = await twoFactorApiService.deleteTwoFactorEmail(request); + + expect(apiService.send).toHaveBeenCalledWith( + "DELETE", + "/two-factor/email", + request, + true, + true, + ); + expect(result).toBeInstanceOf(TwoFactorProviderResponse); + expect(result.enabled).toBe(false); + expect(result.type).toBe(1); }); }); - describe("putTwoFactorOrganizationDisable", () => { - it("disables two-factor provider for organization with policy management permissions", async () => { + describe("deleteTwoFactorOrganizationDuo", () => { + it("removes Duo two-factor enrollment for an organization with policy management permissions", async () => { const organizationId = "org-123"; - const request = new TwoFactorProviderRequest(); - request.type = 6; // Duo - request.masterPasswordHash = "master-password-hash"; - const mockResponse = { - Enabled: false, - Type: 6, - }; + const request = new TwoFactorOrganizationDuoDeleteRequest(); + request.userVerificationToken = "uv-token"; + const mockResponse = { Enabled: false, Type: 6 }; // OrganizationDuo apiService.send.mockResolvedValue(mockResponse); - const result = await twoFactorApiService.putTwoFactorOrganizationDisable( + const result = await twoFactorApiService.deleteTwoFactorOrganizationDuo( organizationId, request, ); expect(apiService.send).toHaveBeenCalledWith( - "PUT", - `/organizations/${organizationId}/two-factor/disable`, + "DELETE", + `/organizations/${organizationId}/two-factor/duo`, + request, + true, + true, + ); + expect(result).toBeInstanceOf(TwoFactorProviderResponse); + expect(result.enabled).toBe(false); + expect(result.type).toBe(6); + }); + }); + + describe("deleteTwoFactorWebAuthnAll", () => { + it("removes the entire WebAuthn enrollment in a single round-trip", async () => { + const request = new TwoFactorWebAuthnDeleteAllRequest(); + request.userVerificationToken = "uv-token"; + const mockResponse = { Enabled: false, Type: 7 }; // WebAuthn + apiService.send.mockResolvedValue(mockResponse); + + const result = await twoFactorApiService.deleteTwoFactorWebAuthnAll(request); + + expect(apiService.send).toHaveBeenCalledWith( + "DELETE", + "/two-factor/webauthn/all", request, true, true, ); expect(result).toBeInstanceOf(TwoFactorProviderResponse); expect(result.enabled).toBe(false); - expect(result.type).toBe(6); // Duo + expect(result.type).toBe(7); }); }); }); diff --git a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts index b9778e460ea6..c7116e4a2f22 100644 --- a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts +++ b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts @@ -1,8 +1,12 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { DisableTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/disable-two-factor-authenticator.request"; +import { DeleteTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/delete-two-factor-authenticator.request"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; +import { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-delete.request"; +import { TwoFactorEmailDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-email-delete.request"; import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request"; -import { TwoFactorProviderRequest } from "@bitwarden/common/auth/models/request/two-factor-provider.request"; +import { TwoFactorOrganizationDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-organization-duo-delete.request"; +import { TwoFactorWebAuthnDeleteAllRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-delete-all.request"; +import { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-delete.request"; import { UpdateTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/update-two-factor-authenticator.request"; import { UpdateTwoFactorDuoRequest } from "@bitwarden/common/auth/models/request/update-two-factor-duo.request"; import { UpdateTwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/update-two-factor-email.request"; @@ -14,10 +18,8 @@ import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two import { TwoFactorEmailResponse } from "@bitwarden/common/auth/models/response/two-factor-email.response"; import { TwoFactorProviderResponse } from "@bitwarden/common/auth/models/response/two-factor-provider.response"; import { TwoFactorRecoverResponse } from "@bitwarden/common/auth/models/response/two-factor-recover.response"; -import { - TwoFactorWebAuthnResponse, - ChallengeResponse, -} from "@bitwarden/common/auth/models/response/two-factor-web-authn.response"; +import { TwoFactorWebAuthnChallengeResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-challenge.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 { ListResponse } from "@bitwarden/common/models/response/list.response"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -76,7 +78,7 @@ export class DefaultTwoFactorApiService implements TwoFactorApiService { } async deleteTwoFactorAuthenticator( - request: DisableTwoFactorAuthenticatorRequest, + request: DeleteTwoFactorAuthenticatorRequest, ): Promise { const response = await this.apiService.send( "DELETE", @@ -114,6 +116,13 @@ export class DefaultTwoFactorApiService implements TwoFactorApiService { return new TwoFactorEmailResponse(response); } + async deleteTwoFactorEmail( + request: TwoFactorEmailDeleteRequest, + ): Promise { + const response = await this.apiService.send("DELETE", "/two-factor/email", request, true, true); + return new TwoFactorProviderResponse(response); + } + // Duo async getTwoFactorDuo(request: SecretVerificationRequest): Promise { @@ -140,6 +149,11 @@ export class DefaultTwoFactorApiService implements TwoFactorApiService { return new TwoFactorDuoResponse(response); } + async deleteTwoFactorDuo(request: TwoFactorDuoDeleteRequest): Promise { + const response = await this.apiService.send("DELETE", "/two-factor/duo", request, true, true); + return new TwoFactorProviderResponse(response); + } + async putTwoFactorOrganizationDuo( organizationId: string, request: UpdateTwoFactorDuoRequest, @@ -154,6 +168,20 @@ export class DefaultTwoFactorApiService implements TwoFactorApiService { return new TwoFactorDuoResponse(response); } + async deleteTwoFactorOrganizationDuo( + organizationId: string, + request: TwoFactorOrganizationDuoDeleteRequest, + ): Promise { + const response = await this.apiService.send( + "DELETE", + `/organizations/${organizationId}/two-factor/duo`, + request, + true, + true, + ); + return new TwoFactorProviderResponse(response); + } + // YubiKey async getTwoFactorYubiKey(request: SecretVerificationRequest): Promise { @@ -174,6 +202,19 @@ export class DefaultTwoFactorApiService implements TwoFactorApiService { return new TwoFactorYubiKeyResponse(response); } + async deleteTwoFactorYubiKey( + request: TwoFactorYubiKeyDeleteRequest, + ): Promise { + const response = await this.apiService.send( + "DELETE", + "/two-factor/yubikey", + request, + true, + true, + ); + return new TwoFactorProviderResponse(response); + } + // WebAuthn async getTwoFactorWebAuthn( @@ -191,7 +232,7 @@ export class DefaultTwoFactorApiService implements TwoFactorApiService { async getTwoFactorWebAuthnChallenge( request: SecretVerificationRequest, - ): Promise { + ): Promise { const response = await this.apiService.send( "POST", "/two-factor/get-webauthn-challenge", @@ -199,7 +240,7 @@ export class DefaultTwoFactorApiService implements TwoFactorApiService { true, true, ); - return new ChallengeResponse(response); + return new TwoFactorWebAuthnChallengeResponse(response); } async putTwoFactorWebAuthn( @@ -236,37 +277,29 @@ export class DefaultTwoFactorApiService implements TwoFactorApiService { return new TwoFactorWebAuthnResponse(response); } - // Recovery Code - - async getTwoFactorRecover(request: SecretVerificationRequest): Promise { + async deleteTwoFactorWebAuthnAll( + request: TwoFactorWebAuthnDeleteAllRequest, + ): Promise { const response = await this.apiService.send( - "POST", - "/two-factor/get-recover", + "DELETE", + "/two-factor/webauthn/all", request, true, true, ); - return new TwoFactorRecoverResponse(response); - } - - // Disable - - async putTwoFactorDisable(request: TwoFactorProviderRequest): Promise { - const response = await this.apiService.send("PUT", "/two-factor/disable", request, true, true); return new TwoFactorProviderResponse(response); } - async putTwoFactorOrganizationDisable( - organizationId: string, - request: TwoFactorProviderRequest, - ): Promise { + // Recovery Code + + async getTwoFactorRecover(request: SecretVerificationRequest): Promise { const response = await this.apiService.send( - "PUT", - `/organizations/${organizationId}/two-factor/disable`, + "POST", + "/two-factor/get-recover", request, true, true, ); - return new TwoFactorProviderResponse(response); + return new TwoFactorRecoverResponse(response); } } 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 index 2acc4a7e9399..a274f9523b00 100644 --- 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 @@ -9,10 +9,14 @@ import { PlatformUtilsService } from "../../../platform/abstractions/platform-ut 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 { DeleteTwoFactorAuthenticatorRequest } from "../../models/request/delete-two-factor-authenticator.request"; import { SecretVerificationRequest } from "../../models/request/secret-verification.request"; +import { TwoFactorDuoDeleteRequest } from "../../models/request/two-factor-duo-delete.request"; +import { TwoFactorEmailDeleteRequest } from "../../models/request/two-factor-email-delete.request"; import { TwoFactorEmailRequest } from "../../models/request/two-factor-email.request"; -import { TwoFactorProviderRequest } from "../../models/request/two-factor-provider.request"; +import { TwoFactorOrganizationDuoDeleteRequest } from "../../models/request/two-factor-organization-duo-delete.request"; +import { TwoFactorWebAuthnDeleteAllRequest } from "../../models/request/two-factor-web-authn-delete-all.request"; +import { TwoFactorYubiKeyDeleteRequest } from "../../models/request/two-factor-yubikey-delete.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"; @@ -25,10 +29,8 @@ import { TwoFactorDuoResponse } from "../../models/response/two-factor-duo.respo 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 { TwoFactorWebAuthnChallengeResponse } from "../../models/response/two-factor-web-authn-challenge.response"; +import { TwoFactorWebAuthnResponse } from "../../models/response/two-factor-web-authn.response"; import { TwoFactorYubiKeyResponse } from "../../models/response/two-factor-yubi-key.response"; import { PROVIDERS, @@ -205,7 +207,9 @@ export class DefaultTwoFactorService implements TwoFactorServiceAbstraction { return this.twoFactorApiService.getTwoFactorWebAuthn(request); } - getTwoFactorWebAuthnChallenge(request: SecretVerificationRequest): Promise { + getTwoFactorWebAuthnChallenge( + request: SecretVerificationRequest, + ): Promise { return this.twoFactorApiService.getTwoFactorWebAuthnChallenge(request); } @@ -220,7 +224,7 @@ export class DefaultTwoFactorService implements TwoFactorServiceAbstraction { } deleteTwoFactorAuthenticator( - request: DisableTwoFactorAuthenticatorRequest, + request: DeleteTwoFactorAuthenticatorRequest, ): Promise { return this.twoFactorApiService.deleteTwoFactorAuthenticator(request); } @@ -258,15 +262,31 @@ export class DefaultTwoFactorService implements TwoFactorServiceAbstraction { return this.twoFactorApiService.deleteTwoFactorWebAuthn(request); } - putTwoFactorDisable(request: TwoFactorProviderRequest): Promise { - return this.twoFactorApiService.putTwoFactorDisable(request); + deleteTwoFactorYubiKey( + request: TwoFactorYubiKeyDeleteRequest, + ): Promise { + return this.twoFactorApiService.deleteTwoFactorYubiKey(request); + } + + deleteTwoFactorDuo(request: TwoFactorDuoDeleteRequest): Promise { + return this.twoFactorApiService.deleteTwoFactorDuo(request); } - putTwoFactorOrganizationDisable( + deleteTwoFactorEmail(request: TwoFactorEmailDeleteRequest): Promise { + return this.twoFactorApiService.deleteTwoFactorEmail(request); + } + + deleteTwoFactorOrganizationDuo( organizationId: string, - request: TwoFactorProviderRequest, + request: TwoFactorOrganizationDuoDeleteRequest, + ): Promise { + return this.twoFactorApiService.deleteTwoFactorOrganizationDuo(organizationId, request); + } + + deleteTwoFactorWebAuthnAll( + request: TwoFactorWebAuthnDeleteAllRequest, ): Promise { - return this.twoFactorApiService.putTwoFactorOrganizationDisable(organizationId, request); + return this.twoFactorApiService.deleteTwoFactorWebAuthnAll(request); } postTwoFactorEmailSetup(request: TwoFactorEmailRequest): Promise { From 1c0100e881455ea7a6507dadf44347684a612dca Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Thu, 18 Jun 2026 22:56:01 -0400 Subject: [PATCH 03/29] PM-38137 - Thread UV token through web 2FA setup components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per-provider setup components (Authenticator, YubiKey, Duo, Email, WebAuthn) instantiate request models directly, cache the user verification token from the GET response, and thread it through every PUT / DELETE / setup-POST call. Each component implements its own disableMethod against the appropriate per-provider DELETE endpoint: - Authenticator, YubiKey, Email each call their corresponding deleteTwoFactor* method. - Duo branches on organizationId between deleteTwoFactorDuo and deleteTwoFactorOrganizationDuo (the component is shared between personal Duo and OrgDuo setup). - WebAuthn's "Disable All Keys" button calls deleteTwoFactorWebAuthnAll (single round-trip; server-side handles the wipe atomically). Per-credential remove continues to use deleteTwoFactorWebAuthn. - WebAuthn challenge consumer reads options + userVerificationToken from the new wrapper response. Base setup component disableMethod becomes protected abstract — every subclass provides its own override. Parent settings page stops rendering the lapsed-premium-only secondary "Disable" button; the standard "Manage" button is now enabled for lapsed-premium users on already-enrolled premium providers, so the same GET → DELETE flow handles them. --- ...wo-factor-setup-authenticator.component.ts | 7 ++- .../two-factor-setup-duo.component.ts | 37 +++++++++++++- .../two-factor-setup-email.component.ts | 34 +++++++++++-- .../two-factor-setup-method-base.component.ts | 35 ++----------- .../two-factor-setup-webauthn.component.ts | 49 ++++++++++++++++--- .../two-factor-setup-yubikey.component.ts | 29 ++++++++++- .../two-factor-setup.component.html | 29 ++++------- .../two-factor/two-factor-setup.component.ts | 46 ----------------- 8 files changed, 155 insertions(+), 111 deletions(-) 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 1857430eadd6..8cc575e34da5 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 @@ -9,7 +9,7 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; 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 { DeleteTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/delete-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 { TwoFactorService } from "@bitwarden/common/auth/two-factor"; @@ -155,7 +155,7 @@ export class TwoFactorSetupAuthenticatorComponent }; protected async enable() { - const request = await this.buildRequestModel(UpdateTwoFactorAuthenticatorRequest); + const request = new UpdateTwoFactorAuthenticatorRequest(); request.token = this.formGroup.value.token; request.key = this.key; request.userVerificationToken = this.userVerificationToken; @@ -176,8 +176,7 @@ export class TwoFactorSetupAuthenticatorComponent return; } - const request = await this.buildRequestModel(DisableTwoFactorAuthenticatorRequest); - request.type = this.type; + const request = new DeleteTwoFactorAuthenticatorRequest(); request.key = this.key; request.userVerificationToken = this.userVerificationToken; await this.twoFactorService.deleteTwoFactorAuthenticator(request); 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 bac4f7754860..5beb444b439b 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 @@ -4,6 +4,8 @@ import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; 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 { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-delete.request"; +import { TwoFactorOrganizationDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-organization-duo-delete.request"; 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 { TwoFactorService } from "@bitwarden/common/auth/two-factor"; @@ -64,6 +66,7 @@ export class TwoFactorSetupDuoComponent host: ["", [Validators.required]], }); override componentName = "app-two-factor-duo"; + private userVerificationToken!: string; constructor( @Inject(DIALOG_DATA) protected data: TwoFactorDuoComponentConfig, @@ -135,10 +138,11 @@ export class TwoFactorSetupDuoComponent }; protected async enable() { - const request = await this.buildRequestModel(UpdateTwoFactorDuoRequest); + const request = new UpdateTwoFactorDuoRequest(); request.clientId = this.clientId; request.clientSecret = this.clientSecret; request.host = this.host; + request.userVerificationToken = this.userVerificationToken; let response: TwoFactorDuoResponse; @@ -155,6 +159,36 @@ export class TwoFactorSetupDuoComponent this.onUpdated.emit(true); } + protected override async disableMethod() { + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "disable" }, + content: { key: "twoStepDisableDesc" }, + type: "warning", + }); + + if (!confirmed) { + return; + } + + if (this.organizationId != null) { + const request = new TwoFactorOrganizationDuoDeleteRequest(); + request.userVerificationToken = this.userVerificationToken; + await this.twoFactorService.deleteTwoFactorOrganizationDuo(this.organizationId, request); + } else { + const request = new TwoFactorDuoDeleteRequest(); + request.userVerificationToken = this.userVerificationToken; + await this.twoFactorService.deleteTwoFactorDuo(request); + } + + this.enabled = false; + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("twoStepDisabled"), + }); + this.onUpdated.emit(false); + } + onClose = () => { void this.dialogRef.close(this.enabled); }; @@ -164,6 +198,7 @@ export class TwoFactorSetupDuoComponent this.clientSecret = response.clientSecret; this.host = response.host; this.enabled = response.enabled; + this.userVerificationToken = response.userVerificationToken; } /** 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 fafc766d95c0..7a35c83ed35b 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 @@ -6,6 +6,7 @@ import { firstValueFrom, map } from "rxjs"; 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 { TwoFactorEmailDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-email-delete.request"; 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"; @@ -63,6 +64,7 @@ export class TwoFactorSetupEmailComponent sentEmail: string = ""; emailPromise: Promise | undefined; override componentName = "app-two-factor-email"; + private userVerificationToken!: string; formGroup = this.formBuilder.group({ token: ["", [Validators.required]], email: ["", [Validators.email, Validators.required]], @@ -129,27 +131,52 @@ export class TwoFactorSetupEmailComponent }; private disableEmail() { - return super.disableMethod(); + return this.disableMethod(); } sendEmail = async () => { - const request = await this.buildRequestModel(TwoFactorEmailRequest); + const request = new TwoFactorEmailRequest(); request.email = this.email; + request.userVerificationToken = this.userVerificationToken; this.emailPromise = this.twoFactorService.postTwoFactorEmailSetup(request); await this.emailPromise; this.sentEmail = this.email; }; protected async enable() { - const request = await this.buildRequestModel(UpdateTwoFactorEmailRequest); + const request = new UpdateTwoFactorEmailRequest(); request.email = this.email; request.token = this.token; + request.userVerificationToken = this.userVerificationToken; const response = await this.twoFactorService.putTwoFactorEmail(request); await this.processResponse(response); this.onUpdated.emit(true); } + protected override async disableMethod() { + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "disable" }, + content: { key: "twoStepDisableDesc" }, + type: "warning", + }); + + if (!confirmed) { + return; + } + + const request = new TwoFactorEmailDeleteRequest(); + request.userVerificationToken = this.userVerificationToken; + await this.twoFactorService.deleteTwoFactorEmail(request); + this.enabled = false; + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("twoStepDisabled"), + }); + this.onUpdated.emit(false); + } + onClose = () => { void this.dialogRef.close(this.enabled); }; @@ -158,6 +185,7 @@ export class TwoFactorSetupEmailComponent this.token = null; this.email = response.email; this.enabled = response.enabled; + this.userVerificationToken = response.userVerificationToken; if (!this.enabled && (this.email == null || this.email === "")) { this.email = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.email)), 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 5494353449dd..44731ff62aa0 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 @@ -4,7 +4,6 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { VerificationType } from "@bitwarden/common/auth/enums/verification-type"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; -import { TwoFactorProviderRequest } from "@bitwarden/common/auth/models/request/two-factor-provider.request"; import { 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"; @@ -14,6 +13,10 @@ import { DialogService, ToastService } from "@bitwarden/components"; /** * Base class for two-factor setup components (ex: email, yubikey, webauthn, duo). + * + * Subclasses must implement `disableMethod()` themselves — each provider routes to its own + * per-provider DELETE endpoint via the threaded user-verification token, so there is no + * meaningful generic implementation to share. */ @Directive({}) export abstract class TwoFactorSetupMethodBaseComponent { @@ -47,35 +50,7 @@ export abstract class TwoFactorSetupMethodBaseComponent { this.authed = true; } - protected async disableMethod() { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "disable" }, - content: { key: "twoStepDisableDesc" }, - type: "warning", - }); - - if (!confirmed) { - return; - } - - 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) { - await this.twoFactorService.putTwoFactorOrganizationDisable(this.organizationId, request); - } else { - await this.twoFactorService.putTwoFactorDisable(request); - } - this.enabled = false; - this.toastService.showToast({ - variant: "success", - title: "", - message: this.i18nService.t("twoStepDisabled"), - }); - this.onUpdated.emit(false); - } + protected abstract disableMethod(): Promise; protected async buildRequestModel( requestClass: new () => T, 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 9fc6e94ec26b..37e85209acdc 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 @@ -6,8 +6,10 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; 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 { TwoFactorWebAuthnDeleteAllRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-delete-all.request"; import { UpdateTwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/models/request/update-two-factor-web-authn-delete.request"; import { UpdateTwoFactorWebAuthnRequest } from "@bitwarden/common/auth/models/request/update-two-factor-web-authn.request"; +import { TwoFactorWebAuthnChallengeResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-challenge.response"; import { ChallengeResponse, TwoFactorWebAuthnResponse, @@ -73,7 +75,8 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom webAuthnError: boolean = false; webAuthnListening: boolean = false; webAuthnResponse: PublicKeyCredential | null = null; - challengePromise: Promise | undefined; + challengePromise: Promise | undefined; + private userVerificationToken!: string; override componentName = "app-two-factor-webauthn"; @@ -120,15 +123,15 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom }; protected async enable() { - const request = await this.buildRequestModel(UpdateTwoFactorWebAuthnRequest); - if (this.webAuthnResponse == undefined || this.keyIdAvailable == undefined) { throw new Error("WebAuthn response or key ID is missing"); } + const request = new UpdateTwoFactorWebAuthnRequest(); request.deviceResponse = this.webAuthnResponse; request.id = this.keyIdAvailable; request.name = this.formGroup.value.name || ""; + request.userVerificationToken = this.userVerificationToken; const response = await this.twoFactorService.putTwoFactorWebAuthn(request); this.processResponse(response); @@ -148,6 +151,31 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom } }; + protected override async disableMethod() { + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "disable" }, + content: { key: "twoStepDisableDesc" }, + type: "warning", + }); + + if (!confirmed) { + return; + } + + // Server's per-credential DELETE refuses to remove the last registered credential + // (lockout-prevention), so the only path to disable WebAuthn entirely is the bulk endpoint. + const request = new TwoFactorWebAuthnDeleteAllRequest(); + request.userVerificationToken = this.userVerificationToken; + await this.twoFactorService.deleteTwoFactorWebAuthnAll(request); + this.enabled = false; + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("twoStepDisabled"), + }); + this.onUpdated.emit(false); + } + async remove(key: Key) { if (this.keysConfiguredCount <= 1 || key.removePromise != null) { return; @@ -163,8 +191,9 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom if (!confirmed) { return; } - const request = await this.buildRequestModel(UpdateTwoFactorWebAuthnDeleteRequest); + const request = new UpdateTwoFactorWebAuthnDeleteRequest(); request.id = key.id; + request.userVerificationToken = this.userVerificationToken; try { key.removePromise = this.twoFactorService.deleteTwoFactorWebAuthn(request); const response = await key.removePromise; @@ -181,8 +210,13 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom } const request = await this.buildRequestModel(SecretVerificationRequest); this.challengePromise = this.twoFactorService.getTwoFactorWebAuthnChallenge(request); - const challenge = await this.challengePromise; - this.readDevice(challenge); + const wrappedChallenge = await this.challengePromise; + if (wrappedChallenge.options == null) { + this.webAuthnError = true; + return; + } + this.userVerificationToken = wrappedChallenge.userVerificationToken; + this.readDevice(wrappedChallenge.options); }; private readDevice(webAuthnChallenge: ChallengeResponse) { @@ -272,6 +306,9 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom this.keyIdAvailable = nextId; this.enabled = response.enabled; + if (response.userVerificationToken) { + this.userVerificationToken = response.userVerificationToken; + } this.onUpdated.emit(this.enabled); } 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 b4d6aef5f46b..82eea6841948 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 @@ -11,6 +11,7 @@ import { import { JslibModule } from "@bitwarden/angular/jslib.module"; 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 { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-delete.request"; 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 { TwoFactorService } from "@bitwarden/common/auth/two-factor"; @@ -72,6 +73,7 @@ export class TwoFactorSetupYubiKeyComponent type = TwoFactorProviderType.Yubikey; keys: Key[] = []; anyKeyHasNfc = false; + private userVerificationToken!: string; override componentName = "app-two-factor-yubikey"; formGroup: @@ -166,13 +168,14 @@ export class TwoFactorSetupYubiKeyComponent return; } const keys = this.formGroup.controls.formKeys.value; - const request = await this.buildRequestModel(UpdateTwoFactorYubikeyOtpRequest); + const request = new UpdateTwoFactorYubikeyOtpRequest(); request.key1 = keys != null && keys.length > 0 ? (keys[0]?.key ?? "") : ""; request.key2 = keys != null && keys.length > 1 ? (keys[1]?.key ?? "") : ""; request.key3 = keys != null && keys.length > 2 ? (keys[2]?.key ?? "") : ""; request.key4 = keys != null && keys.length > 3 ? (keys[3]?.key ?? "") : ""; request.key5 = keys != null && keys.length > 4 ? (keys[4]?.key ?? "") : ""; request.nfc = this.formGroup.value.anyKeyHasNfc ?? false; + request.userVerificationToken = this.userVerificationToken; this.processResponse(await this.twoFactorService.putTwoFactorYubiKey(request)); this.refreshFormArrayData(); @@ -184,6 +187,29 @@ export class TwoFactorSetupYubiKeyComponent this.onUpdated.emit(this.enabled); } + protected override async disableMethod() { + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "disable" }, + content: { key: "twoStepDisableDesc" }, + type: "warning", + }); + + if (!confirmed) { + return; + } + + const request = new TwoFactorYubiKeyDeleteRequest(); + request.userVerificationToken = this.userVerificationToken; + await this.twoFactorService.deleteTwoFactorYubiKey(request); + this.enabled = false; + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("twoStepDisabled"), + }); + this.onUpdated.emit(false); + } + remove(pos: number) { this.keys[pos].key = ""; this.keys[pos].existingKey = ""; @@ -201,6 +227,7 @@ export class TwoFactorSetupYubiKeyComponent private processResponse(response: TwoFactorYubiKeyResponse) { this.enabled = response.enabled; this.anyKeyHasNfc = response.nfc || !response.enabled; + this.userVerificationToken = response.userVerificationToken; this.keys = [ { key: response.key1, existingKey: this.padRight(response.key1) }, { key: response.key2, existingKey: this.padRight(response.key2) }, 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 44e3c0d3978a..2a995083aa97 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 @@ -69,26 +69,15 @@

{{ 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 104feb474580..8a701729eb4f 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts @@ -13,14 +13,12 @@ import { import { TwoFactorIconComponent } from "@bitwarden/angular/auth/components/two-factor-icon.component"; 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"; @@ -154,50 +152,6 @@ 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(); From 3b5202649303b4054e50a98411b31331816dc2cd Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Mon, 22 Jun 2026 12:16:59 -0400 Subject: [PATCH 04/29] PM-38137 - Preserve lapsed-premium 2FA disable shortcut on settings list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restores the dedicated "Disable" button shown on the 2FA settings list when a user has lost premium but still has an enrolled premium provider (YubiKey or Duo), and restores the disabled "Manage" button for unenrolled premium providers — i.e., the exact UX present before this branch. Under the hood the shortcut now uses the new per-provider DELETE architecture: in the user-verification dialog's verificationFn the component calls the per-provider GET (now non-premium-gated) to mint a UV token, then the per-provider DELETE with that token. Two server round-trips behind one UV dialog interaction. Avoids the regression where the standard manage flow would have opened the full provider configuration screen to a lapsed-premium user and let them attempt to add more keys before failing at PUT-time on the server. --- .../two-factor-setup.component.html | 29 +++++--- .../two-factor/two-factor-setup.component.ts | 67 +++++++++++++++++++ 2 files changed, 87 insertions(+), 9 deletions(-) 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 2a995083aa97..44e3c0d3978a 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 @@ -69,15 +69,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 8a701729eb4f..282ef7553861 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts @@ -13,12 +13,16 @@ import { import { TwoFactorIconComponent } from "@bitwarden/angular/auth/components/two-factor-icon.component"; 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 { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; +import { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-delete.request"; +import { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-delete.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"; @@ -152,6 +156,69 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { return await lastValueFrom(twoFactorVerifyDialogRef.closed); } + /** + * Lapsed-premium escape hatch: a user who previously enrolled a premium provider (YubiKey or + * Duo) while subscribed should still be able to disable it after their premium subscription + * lapses. Surfaces a UV dialog rather than the full management screen so the user cannot + * accidentally attempt to add more credentials (which would fail at PUT-time on the server). + * + * Under the hood: the per-provider GET (now non-premium-gated) mints a UV token, which the + * per-provider DELETE then consumes — same single dialog interaction as before, two server + * round-trips instead of one. + */ + async disablePremium2faTypeForNonPremiumUser(type: TwoFactorProviderType) { + const result = await UserVerificationDialogComponent.open(this.dialogService, { + title: "twoStepLogin", + verificationType: { + type: "custom", + verificationFn: async (secret) => { + const getRequest = + await this.userVerificationService.buildRequest( + secret, + SecretVerificationRequest, + ); + + switch (type) { + case TwoFactorProviderType.Yubikey: { + const response = await this.twoFactorService.getTwoFactorYubiKey(getRequest); + const deleteRequest = new TwoFactorYubiKeyDeleteRequest(); + deleteRequest.userVerificationToken = response.userVerificationToken; + await this.twoFactorService.deleteTwoFactorYubiKey(deleteRequest); + break; + } + case TwoFactorProviderType.Duo: { + const response = await this.twoFactorService.getTwoFactorDuo(getRequest); + const deleteRequest = new TwoFactorDuoDeleteRequest(); + deleteRequest.userVerificationToken = response.userVerificationToken; + await this.twoFactorService.deleteTwoFactorDuo(deleteRequest); + break; + } + default: + throw new Error( + "disablePremium2faTypeForNonPremiumUser only supports YubiKey and Duo", + ); + } + 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(); From bdf2c1c11af0cdf6b5789ef41fe9c8ad868ebef8 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Mon, 22 Jun 2026 12:43:29 -0400 Subject: [PATCH 05/29] PM-38137 - Add TODO to get rid of base 2FA setup component. --- .../two-factor/two-factor-setup-method-base.component.ts | 1 + 1 file changed, 1 insertion(+) 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 44731ff62aa0..bc90fef2c87b 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 @@ -11,6 +11,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { DialogService, ToastService } from "@bitwarden/components"; +// TODO: PM-39385 - Remove this base class and replace with a more flexible composition-based approach. /** * Base class for two-factor setup components (ex: email, yubikey, webauthn, duo). * From 144d34b9860d73e50f7086ada37d610319302586 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Mon, 22 Jun 2026 20:14:11 -0400 Subject: [PATCH 06/29] PM-38137 - Rename 2FA update request models to TwoFactorUpdate shape Aligns every client 2FA update request-model class to the file-wide TwoFactor prefix already used by the Delete family: UpdateTwoFactorAuthenticatorRequest -> TwoFactorAuthenticatorUpdateRequest UpdateTwoFactorDuoRequest -> TwoFactorDuoUpdateRequest UpdateTwoFactorYubikeyOtpRequest -> TwoFactorYubiKeyUpdateRequest UpdateTwoFactorEmailRequest -> TwoFactorEmailUpdateRequest UpdateTwoFactorWebAuthnRequest -> TwoFactorWebAuthnUpdateRequest UpdateTwoFactorWebAuthnDeleteRequest -> TwoFactorWebAuthnDeleteRequest The YubiKey rename also aligns the class name with the TwoFactorProviderType enum value (YubiKey, not YubicoOtp). No HTTP route or wire-shape changes. --- ...wo-factor-setup-authenticator.component.ts | 4 +- .../two-factor-setup-duo.component.ts | 4 +- .../two-factor-setup-email.component.ts | 4 +- .../two-factor-setup-webauthn.component.ts | 8 ++-- .../two-factor-setup-yubikey.component.ts | 4 +- ...wo-factor-authenticator-update.request.ts} | 2 +- ...st.ts => two-factor-duo-update.request.ts} | 2 +- ....ts => two-factor-email-update.request.ts} | 2 +- .../two-factor-web-authn-delete.request.ts | 4 ++ ...=> two-factor-web-authn-update.request.ts} | 2 +- ...s => two-factor-yubikey-update.request.ts} | 2 +- ...ate-two-factor-web-authn-delete.request.ts | 4 -- .../abstractions/two-factor-api.service.ts | 26 ++++++------ .../abstractions/two-factor.service.ts | 40 +++++++++---------- .../default-two-factor-api.service.spec.ts | 28 ++++++------- .../default-two-factor-api.service.ts | 26 ++++++------ .../services/default-two-factor.service.ts | 28 ++++++------- 17 files changed, 94 insertions(+), 96 deletions(-) rename libs/common/src/auth/models/request/{update-two-factor-authenticator.request.ts => two-factor-authenticator-update.request.ts} (57%) rename libs/common/src/auth/models/request/{update-two-factor-duo.request.ts => two-factor-duo-update.request.ts} (70%) rename libs/common/src/auth/models/request/{update-two-factor-email.request.ts => two-factor-email-update.request.ts} (62%) create mode 100644 libs/common/src/auth/models/request/two-factor-web-authn-delete.request.ts rename libs/common/src/auth/models/request/{update-two-factor-web-authn.request.ts => two-factor-web-authn-update.request.ts} (70%) rename libs/common/src/auth/models/request/{update-two-factor-yubikey-otp.request.ts => two-factor-yubikey-update.request.ts} (74%) delete mode 100644 libs/common/src/auth/models/request/update-two-factor-web-authn-delete.request.ts 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 8cc575e34da5..f30fcf2e4e75 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 @@ -10,7 +10,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv 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 { DeleteTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/delete-two-factor-authenticator.request"; -import { UpdateTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/update-two-factor-authenticator.request"; +import { TwoFactorAuthenticatorUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-authenticator-update.request"; import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator.response"; import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; @@ -155,7 +155,7 @@ export class TwoFactorSetupAuthenticatorComponent }; protected async enable() { - const request = new UpdateTwoFactorAuthenticatorRequest(); + const request = new TwoFactorAuthenticatorUpdateRequest(); request.token = this.formGroup.value.token; request.key = this.key; request.userVerificationToken = this.userVerificationToken; 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 5beb444b439b..929585946fbb 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 @@ -5,8 +5,8 @@ import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; 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 { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-delete.request"; +import { TwoFactorDuoUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-update.request"; import { TwoFactorOrganizationDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-organization-duo-delete.request"; -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 { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; @@ -138,7 +138,7 @@ export class TwoFactorSetupDuoComponent }; protected async enable() { - const request = new UpdateTwoFactorDuoRequest(); + const request = new TwoFactorDuoUpdateRequest(); request.clientId = this.clientId; request.clientSecret = this.clientSecret; request.host = this.host; 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 7a35c83ed35b..ae8e3c296e5b 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 @@ -7,8 +7,8 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv 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 { TwoFactorEmailDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-email-delete.request"; +import { TwoFactorEmailUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-email-update.request"; 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 { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; @@ -144,7 +144,7 @@ export class TwoFactorSetupEmailComponent }; protected async enable() { - const request = new UpdateTwoFactorEmailRequest(); + const request = new TwoFactorEmailUpdateRequest(); request.email = this.email; request.token = this.token; request.userVerificationToken = this.userVerificationToken; 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 37e85209acdc..0dd087f1e26a 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 @@ -7,8 +7,8 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; import { TwoFactorWebAuthnDeleteAllRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-delete-all.request"; -import { UpdateTwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/models/request/update-two-factor-web-authn-delete.request"; -import { UpdateTwoFactorWebAuthnRequest } from "@bitwarden/common/auth/models/request/update-two-factor-web-authn.request"; +import { TwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-delete.request"; +import { TwoFactorWebAuthnUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-update.request"; import { TwoFactorWebAuthnChallengeResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-challenge.response"; import { ChallengeResponse, @@ -127,7 +127,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom throw new Error("WebAuthn response or key ID is missing"); } - const request = new UpdateTwoFactorWebAuthnRequest(); + const request = new TwoFactorWebAuthnUpdateRequest(); request.deviceResponse = this.webAuthnResponse; request.id = this.keyIdAvailable; request.name = this.formGroup.value.name || ""; @@ -191,7 +191,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom if (!confirmed) { return; } - const request = new UpdateTwoFactorWebAuthnDeleteRequest(); + const request = new TwoFactorWebAuthnDeleteRequest(); request.id = key.id; request.userVerificationToken = this.userVerificationToken; try { 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 82eea6841948..9c7fb39f1852 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 @@ -12,7 +12,7 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; 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 { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-delete.request"; -import { UpdateTwoFactorYubikeyOtpRequest } from "@bitwarden/common/auth/models/request/update-two-factor-yubikey-otp.request"; +import { TwoFactorYubiKeyUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-update.request"; import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key.response"; import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; @@ -168,7 +168,7 @@ export class TwoFactorSetupYubiKeyComponent return; } const keys = this.formGroup.controls.formKeys.value; - const request = new UpdateTwoFactorYubikeyOtpRequest(); + const request = new TwoFactorYubiKeyUpdateRequest(); request.key1 = keys != null && keys.length > 0 ? (keys[0]?.key ?? "") : ""; request.key2 = keys != null && keys.length > 1 ? (keys[1]?.key ?? "") : ""; request.key3 = keys != null && keys.length > 2 ? (keys[2]?.key ?? "") : ""; diff --git a/libs/common/src/auth/models/request/update-two-factor-authenticator.request.ts b/libs/common/src/auth/models/request/two-factor-authenticator-update.request.ts similarity index 57% rename from libs/common/src/auth/models/request/update-two-factor-authenticator.request.ts rename to libs/common/src/auth/models/request/two-factor-authenticator-update.request.ts index 6cd24029d87a..124d28f1adc1 100644 --- a/libs/common/src/auth/models/request/update-two-factor-authenticator.request.ts +++ b/libs/common/src/auth/models/request/two-factor-authenticator-update.request.ts @@ -1,4 +1,4 @@ -export class UpdateTwoFactorAuthenticatorRequest { +export class TwoFactorAuthenticatorUpdateRequest { token!: string; key!: string; userVerificationToken!: string; diff --git a/libs/common/src/auth/models/request/update-two-factor-duo.request.ts b/libs/common/src/auth/models/request/two-factor-duo-update.request.ts similarity index 70% rename from libs/common/src/auth/models/request/update-two-factor-duo.request.ts rename to libs/common/src/auth/models/request/two-factor-duo-update.request.ts index 3d432b3516a5..03d35547eb40 100644 --- a/libs/common/src/auth/models/request/update-two-factor-duo.request.ts +++ b/libs/common/src/auth/models/request/two-factor-duo-update.request.ts @@ -1,4 +1,4 @@ -export class UpdateTwoFactorDuoRequest { +export class TwoFactorDuoUpdateRequest { clientId!: string; clientSecret!: string; host!: string; diff --git a/libs/common/src/auth/models/request/update-two-factor-email.request.ts b/libs/common/src/auth/models/request/two-factor-email-update.request.ts similarity index 62% rename from libs/common/src/auth/models/request/update-two-factor-email.request.ts rename to libs/common/src/auth/models/request/two-factor-email-update.request.ts index d5700efeb757..79157fdff82b 100644 --- a/libs/common/src/auth/models/request/update-two-factor-email.request.ts +++ b/libs/common/src/auth/models/request/two-factor-email-update.request.ts @@ -1,4 +1,4 @@ -export class UpdateTwoFactorEmailRequest { +export class TwoFactorEmailUpdateRequest { token!: string; email!: string; userVerificationToken!: string; diff --git a/libs/common/src/auth/models/request/two-factor-web-authn-delete.request.ts b/libs/common/src/auth/models/request/two-factor-web-authn-delete.request.ts new file mode 100644 index 000000000000..9f418b7035ed --- /dev/null +++ b/libs/common/src/auth/models/request/two-factor-web-authn-delete.request.ts @@ -0,0 +1,4 @@ +export class TwoFactorWebAuthnDeleteRequest { + id!: number; + userVerificationToken!: string; +} diff --git a/libs/common/src/auth/models/request/update-two-factor-web-authn.request.ts b/libs/common/src/auth/models/request/two-factor-web-authn-update.request.ts similarity index 70% rename from libs/common/src/auth/models/request/update-two-factor-web-authn.request.ts rename to libs/common/src/auth/models/request/two-factor-web-authn-update.request.ts index de38fe836ea9..7d087b9d7bde 100644 --- a/libs/common/src/auth/models/request/update-two-factor-web-authn.request.ts +++ b/libs/common/src/auth/models/request/two-factor-web-authn-update.request.ts @@ -1,4 +1,4 @@ -export class UpdateTwoFactorWebAuthnRequest { +export class TwoFactorWebAuthnUpdateRequest { deviceResponse!: PublicKeyCredential; name!: string; id!: number; diff --git a/libs/common/src/auth/models/request/update-two-factor-yubikey-otp.request.ts b/libs/common/src/auth/models/request/two-factor-yubikey-update.request.ts similarity index 74% rename from libs/common/src/auth/models/request/update-two-factor-yubikey-otp.request.ts rename to libs/common/src/auth/models/request/two-factor-yubikey-update.request.ts index 374609804980..8a25f3035ea0 100644 --- a/libs/common/src/auth/models/request/update-two-factor-yubikey-otp.request.ts +++ b/libs/common/src/auth/models/request/two-factor-yubikey-update.request.ts @@ -1,4 +1,4 @@ -export class UpdateTwoFactorYubikeyOtpRequest { +export class TwoFactorYubiKeyUpdateRequest { key1!: string; key2!: string; key3!: string; diff --git a/libs/common/src/auth/models/request/update-two-factor-web-authn-delete.request.ts b/libs/common/src/auth/models/request/update-two-factor-web-authn-delete.request.ts deleted file mode 100644 index 23eeff21458d..000000000000 --- a/libs/common/src/auth/models/request/update-two-factor-web-authn-delete.request.ts +++ /dev/null @@ -1,4 +0,0 @@ -export class UpdateTwoFactorWebAuthnDeleteRequest { - id!: number; - userVerificationToken!: string; -} diff --git a/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts b/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts index ebef1d1475fd..d96ac185c3a0 100644 --- a/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts +++ b/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts @@ -1,17 +1,17 @@ import { DeleteTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/delete-two-factor-authenticator.request"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; +import { TwoFactorAuthenticatorUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-authenticator-update.request"; import { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-delete.request"; +import { TwoFactorDuoUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-update.request"; import { TwoFactorEmailDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-email-delete.request"; +import { TwoFactorEmailUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-email-update.request"; import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request"; import { TwoFactorOrganizationDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-organization-duo-delete.request"; import { TwoFactorWebAuthnDeleteAllRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-delete-all.request"; +import { TwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-delete.request"; +import { TwoFactorWebAuthnUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-update.request"; import { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-delete.request"; -import { UpdateTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/update-two-factor-authenticator.request"; -import { UpdateTwoFactorDuoRequest } from "@bitwarden/common/auth/models/request/update-two-factor-duo.request"; -import { UpdateTwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/update-two-factor-email.request"; -import { UpdateTwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/models/request/update-two-factor-web-authn-delete.request"; -import { UpdateTwoFactorWebAuthnRequest } from "@bitwarden/common/auth/models/request/update-two-factor-web-authn.request"; -import { UpdateTwoFactorYubikeyOtpRequest } from "@bitwarden/common/auth/models/request/update-two-factor-yubikey-otp.request"; +import { TwoFactorYubiKeyUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-update.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"; @@ -157,7 +157,7 @@ export abstract class TwoFactorApiService { * @returns A promise that resolves to the updated authenticator configuration. */ abstract putTwoFactorAuthenticator( - request: UpdateTwoFactorAuthenticatorRequest, + request: TwoFactorAuthenticatorUpdateRequest, ): Promise; /** @@ -178,7 +178,7 @@ export abstract class TwoFactorApiService { * @param request The request containing the email configuration and verification token. * @returns A promise that resolves to the updated email two-factor configuration. */ - abstract putTwoFactorEmail(request: UpdateTwoFactorEmailRequest): Promise; + abstract putTwoFactorEmail(request: TwoFactorEmailUpdateRequest): Promise; /** * Removes the email two-factor enrollment for the current user. @@ -199,7 +199,7 @@ export abstract class TwoFactorApiService { * @param request The request containing the Duo integration configuration. * @returns A promise that resolves to the updated Duo configuration. */ - abstract putTwoFactorDuo(request: UpdateTwoFactorDuoRequest): Promise; + abstract putTwoFactorDuo(request: TwoFactorDuoUpdateRequest): Promise; /** * Removes the Duo two-factor enrollment for the current user. @@ -224,7 +224,7 @@ export abstract class TwoFactorApiService { */ abstract putTwoFactorOrganizationDuo( organizationId: string, - request: UpdateTwoFactorDuoRequest, + request: TwoFactorDuoUpdateRequest, ): Promise; /** @@ -251,7 +251,7 @@ export abstract class TwoFactorApiService { * @returns A promise that resolves to the updated YubiKey configuration. */ abstract putTwoFactorYubiKey( - request: UpdateTwoFactorYubikeyOtpRequest, + request: TwoFactorYubiKeyUpdateRequest, ): Promise; /** @@ -275,7 +275,7 @@ export abstract class TwoFactorApiService { * @returns A promise that resolves to the updated WebAuthn configuration with the new credential. */ abstract putTwoFactorWebAuthn( - request: UpdateTwoFactorWebAuthnRequest, + request: TwoFactorWebAuthnUpdateRequest, ): Promise; /** @@ -288,7 +288,7 @@ export abstract class TwoFactorApiService { * @returns A promise that resolves to the updated WebAuthn configuration. */ abstract deleteTwoFactorWebAuthn( - request: UpdateTwoFactorWebAuthnDeleteRequest, + request: TwoFactorWebAuthnDeleteRequest, ): Promise; /** 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 index 2648857392ef..7b575b493a5e 100644 --- a/libs/common/src/auth/two-factor/abstractions/two-factor.service.ts +++ b/libs/common/src/auth/two-factor/abstractions/two-factor.service.ts @@ -3,18 +3,18 @@ import { KeyDefinition, TWO_FACTOR_MEMORY } from "../../../platform/state"; import { TwoFactorProviderType } from "../../enums/two-factor-provider-type"; import { DeleteTwoFactorAuthenticatorRequest } from "../../models/request/delete-two-factor-authenticator.request"; import { SecretVerificationRequest } from "../../models/request/secret-verification.request"; +import { TwoFactorAuthenticatorUpdateRequest } from "../../models/request/two-factor-authenticator-update.request"; import { TwoFactorDuoDeleteRequest } from "../../models/request/two-factor-duo-delete.request"; +import { TwoFactorDuoUpdateRequest } from "../../models/request/two-factor-duo-update.request"; import { TwoFactorEmailDeleteRequest } from "../../models/request/two-factor-email-delete.request"; +import { TwoFactorEmailUpdateRequest } from "../../models/request/two-factor-email-update.request"; import { TwoFactorEmailRequest } from "../../models/request/two-factor-email.request"; import { TwoFactorOrganizationDuoDeleteRequest } from "../../models/request/two-factor-organization-duo-delete.request"; import { TwoFactorWebAuthnDeleteAllRequest } from "../../models/request/two-factor-web-authn-delete-all.request"; +import { TwoFactorWebAuthnDeleteRequest } from "../../models/request/two-factor-web-authn-delete.request"; +import { TwoFactorWebAuthnUpdateRequest } from "../../models/request/two-factor-web-authn-update.request"; import { TwoFactorYubiKeyDeleteRequest } from "../../models/request/two-factor-yubikey-delete.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 { TwoFactorYubiKeyUpdateRequest } from "../../models/request/two-factor-yubikey-update.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"; @@ -334,12 +334,12 @@ export abstract class TwoFactorService { * 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. + * @param request The {@link TwoFactorAuthenticatorUpdateRequest} 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, + request: TwoFactorAuthenticatorUpdateRequest, ): Promise; /** @@ -360,11 +360,11 @@ export abstract class TwoFactorService { * The token must match the code sent to the specified email address. * Used for settings management. * - * @param request The {@link UpdateTwoFactorEmailRequest} to prove authentication. + * @param request The {@link TwoFactorEmailUpdateRequest} 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; + abstract putTwoFactorEmail(request: TwoFactorEmailUpdateRequest): Promise; /** * Enables or updates the Duo two-factor provider for the current user. @@ -372,11 +372,11 @@ export abstract class TwoFactorService { * Requires user verification and an active premium subscription. * Used for settings management. * - * @param request The {@link UpdateTwoFactorDuoRequest} to prove authentication. + * @param request The {@link TwoFactorDuoUpdateRequest} 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; + abstract putTwoFactorDuo(request: TwoFactorDuoUpdateRequest): Promise; /** * Enables or updates the Duo two-factor provider for an organization. @@ -385,13 +385,13 @@ export abstract class TwoFactorService { * Used for settings management. * * @param organizationId The ID of the organization. - * @param request The {@link UpdateTwoFactorDuoRequest} to prove authentication. + * @param request The {@link TwoFactorDuoUpdateRequest} 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, + request: TwoFactorDuoUpdateRequest, ): Promise; /** @@ -401,12 +401,12 @@ export abstract class TwoFactorService { * Requires user verification and an active premium subscription. * Used for settings management. * - * @param request The {@link UpdateTwoFactorYubikeyOtpRequest} to prove authentication. + * @param request The {@link TwoFactorYubiKeyUpdateRequest} 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, + request: TwoFactorYubiKeyUpdateRequest, ): Promise; /** @@ -416,12 +416,12 @@ export abstract class TwoFactorService { * Requires user verification via master password or OTP. * Used for settings management. * - * @param request The {@link UpdateTwoFactorWebAuthnRequest} to prove authentication. + * @param request The {@link TwoFactorWebAuthnUpdateRequest} 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, + request: TwoFactorWebAuthnUpdateRequest, ): Promise; /** @@ -431,12 +431,12 @@ export abstract class TwoFactorService { * Requires user verification via master password or OTP. * Used for settings management. * - * @param request The {@link UpdateTwoFactorWebAuthnDeleteRequest} to prove authentication. + * @param request The {@link TwoFactorWebAuthnDeleteRequest} 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, + request: TwoFactorWebAuthnDeleteRequest, ): Promise; /** diff --git a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts index 0259df9b3a72..70cad137b5e9 100644 --- a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts +++ b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts @@ -3,18 +3,18 @@ import { mock, MockProxy } from "jest-mock-extended"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { DeleteTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/delete-two-factor-authenticator.request"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; +import { TwoFactorAuthenticatorUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-authenticator-update.request"; import { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-delete.request"; +import { TwoFactorDuoUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-update.request"; import { TwoFactorEmailDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-email-delete.request"; +import { TwoFactorEmailUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-email-update.request"; import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request"; import { TwoFactorOrganizationDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-organization-duo-delete.request"; import { TwoFactorWebAuthnDeleteAllRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-delete-all.request"; +import { TwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-delete.request"; +import { TwoFactorWebAuthnUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-update.request"; import { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-delete.request"; -import { UpdateTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/update-two-factor-authenticator.request"; -import { UpdateTwoFactorDuoRequest } from "@bitwarden/common/auth/models/request/update-two-factor-duo.request"; -import { UpdateTwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/update-two-factor-email.request"; -import { UpdateTwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/models/request/update-two-factor-web-authn-delete.request"; -import { UpdateTwoFactorWebAuthnRequest } from "@bitwarden/common/auth/models/request/update-two-factor-web-authn.request"; -import { UpdateTwoFactorYubikeyOtpRequest } from "@bitwarden/common/auth/models/request/update-two-factor-yubikey-otp.request"; +import { TwoFactorYubiKeyUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-update.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"; @@ -112,7 +112,7 @@ describe("TwoFactorApiService", () => { describe("putTwoFactorAuthenticator", () => { it("enables authenticator after validating the provided token", async () => { - const request = new UpdateTwoFactorAuthenticatorRequest(); + const request = new TwoFactorAuthenticatorUpdateRequest(); request.token = "123456"; request.key = "MFRGGZDFMZTWQ2LK"; const mockResponse = { @@ -227,7 +227,7 @@ describe("TwoFactorApiService", () => { describe("putTwoFactorEmail", () => { it("enables email two-factor after validating the verification code", async () => { - const request = new UpdateTwoFactorEmailRequest(); + const request = new TwoFactorEmailUpdateRequest(); request.email = "user@example.com"; request.token = "verification-code"; const mockResponse = { @@ -317,7 +317,7 @@ describe("TwoFactorApiService", () => { describe("putTwoFactorDuo", () => { it("enables Duo two-factor for premium user with valid integration details", async () => { - const request = new UpdateTwoFactorDuoRequest(); + const request = new TwoFactorDuoUpdateRequest(); request.host = "api-abc123.duosecurity.com"; request.clientId = "DI9ABC1DEFGH2JKL"; request.clientSecret = "client-secret-value-here"; @@ -343,7 +343,7 @@ describe("TwoFactorApiService", () => { describe("putTwoFactorOrganizationDuo", () => { it("enables organization-level Duo with policy management permissions", async () => { const organizationId = "org-123"; - const request = new UpdateTwoFactorDuoRequest(); + const request = new TwoFactorDuoUpdateRequest(); request.host = "api-xyz789.duosecurity.com"; request.clientId = "DI4XYZ9MNOP3QRS"; request.clientSecret = "orgcli-secret-value-here"; @@ -406,7 +406,7 @@ describe("TwoFactorApiService", () => { describe("putTwoFactorYubiKey", () => { it("enables YubiKey two-factor for premium user after validating device OTPs", async () => { - const request = new UpdateTwoFactorYubikeyOtpRequest(); + const request = new TwoFactorYubiKeyUpdateRequest(); request.key1 = "ccccccccccccjkhbhbhrkcitringjkrjirfjuunlnlvcghnkrtgfj"; request.key2 = "ddddddddddddvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv"; const mockResponse = { @@ -520,7 +520,7 @@ describe("TwoFactorApiService", () => { getClientExtensionResults: jest.fn().mockReturnValue({}), }; - const request = new UpdateTwoFactorWebAuthnRequest(); + const request = new TwoFactorWebAuthnUpdateRequest(); request.deviceResponse = mockCredential as PublicKeyCredential; request.name = "My Security Key"; @@ -572,7 +572,7 @@ describe("TwoFactorApiService", () => { getClientExtensionResults: jest.fn().mockReturnValue({}), }; - const request = new UpdateTwoFactorWebAuthnRequest(); + const request = new TwoFactorWebAuthnUpdateRequest(); request.deviceResponse = mockCredential as PublicKeyCredential; request.name = "My Security Key"; @@ -589,7 +589,7 @@ describe("TwoFactorApiService", () => { describe("deleteTwoFactorWebAuthn", () => { it("removes specific WebAuthn credential while preserving other registered keys", async () => { - const request = new UpdateTwoFactorWebAuthnDeleteRequest(); + const request = new TwoFactorWebAuthnDeleteRequest(); request.id = 1; request.userVerificationToken = "uv-token"; const mockResponse = { diff --git a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts index c7116e4a2f22..019a923c7e92 100644 --- a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts +++ b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts @@ -1,18 +1,18 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { DeleteTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/delete-two-factor-authenticator.request"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; +import { TwoFactorAuthenticatorUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-authenticator-update.request"; import { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-delete.request"; +import { TwoFactorDuoUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-update.request"; import { TwoFactorEmailDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-email-delete.request"; +import { TwoFactorEmailUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-email-update.request"; import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request"; import { TwoFactorOrganizationDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-organization-duo-delete.request"; import { TwoFactorWebAuthnDeleteAllRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-delete-all.request"; +import { TwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-delete.request"; +import { TwoFactorWebAuthnUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-update.request"; import { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-delete.request"; -import { UpdateTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/update-two-factor-authenticator.request"; -import { UpdateTwoFactorDuoRequest } from "@bitwarden/common/auth/models/request/update-two-factor-duo.request"; -import { UpdateTwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/update-two-factor-email.request"; -import { UpdateTwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/models/request/update-two-factor-web-authn-delete.request"; -import { UpdateTwoFactorWebAuthnRequest } from "@bitwarden/common/auth/models/request/update-two-factor-web-authn.request"; -import { UpdateTwoFactorYubikeyOtpRequest } from "@bitwarden/common/auth/models/request/update-two-factor-yubikey-otp.request"; +import { TwoFactorYubiKeyUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-update.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"; @@ -65,7 +65,7 @@ export class DefaultTwoFactorApiService implements TwoFactorApiService { } async putTwoFactorAuthenticator( - request: UpdateTwoFactorAuthenticatorRequest, + request: TwoFactorAuthenticatorUpdateRequest, ): Promise { const response = await this.apiService.send( "PUT", @@ -111,7 +111,7 @@ export class DefaultTwoFactorApiService implements TwoFactorApiService { return this.apiService.send("POST", "/two-factor/send-email-login", request, false, false); } - async putTwoFactorEmail(request: UpdateTwoFactorEmailRequest): Promise { + async putTwoFactorEmail(request: TwoFactorEmailUpdateRequest): Promise { const response = await this.apiService.send("PUT", "/two-factor/email", request, true, true); return new TwoFactorEmailResponse(response); } @@ -144,7 +144,7 @@ export class DefaultTwoFactorApiService implements TwoFactorApiService { return new TwoFactorDuoResponse(response); } - async putTwoFactorDuo(request: UpdateTwoFactorDuoRequest): Promise { + async putTwoFactorDuo(request: TwoFactorDuoUpdateRequest): Promise { const response = await this.apiService.send("PUT", "/two-factor/duo", request, true, true); return new TwoFactorDuoResponse(response); } @@ -156,7 +156,7 @@ export class DefaultTwoFactorApiService implements TwoFactorApiService { async putTwoFactorOrganizationDuo( organizationId: string, - request: UpdateTwoFactorDuoRequest, + request: TwoFactorDuoUpdateRequest, ): Promise { const response = await this.apiService.send( "PUT", @@ -196,7 +196,7 @@ export class DefaultTwoFactorApiService implements TwoFactorApiService { } async putTwoFactorYubiKey( - request: UpdateTwoFactorYubikeyOtpRequest, + request: TwoFactorYubiKeyUpdateRequest, ): Promise { const response = await this.apiService.send("PUT", "/two-factor/yubikey", request, true, true); return new TwoFactorYubiKeyResponse(response); @@ -244,7 +244,7 @@ export class DefaultTwoFactorApiService implements TwoFactorApiService { } async putTwoFactorWebAuthn( - request: UpdateTwoFactorWebAuthnRequest, + request: TwoFactorWebAuthnUpdateRequest, ): Promise { const deviceResponse = request.deviceResponse.response as AuthenticatorAttestationResponse; const body: any = Object.assign({}, request); @@ -265,7 +265,7 @@ export class DefaultTwoFactorApiService implements TwoFactorApiService { } async deleteTwoFactorWebAuthn( - request: UpdateTwoFactorWebAuthnDeleteRequest, + request: TwoFactorWebAuthnDeleteRequest, ): Promise { const response = await this.apiService.send( "DELETE", 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 index a274f9523b00..ab818f8146c9 100644 --- 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 @@ -11,18 +11,18 @@ import { GlobalStateProvider } from "../../../platform/state"; import { TwoFactorProviderType } from "../../enums/two-factor-provider-type"; import { DeleteTwoFactorAuthenticatorRequest } from "../../models/request/delete-two-factor-authenticator.request"; import { SecretVerificationRequest } from "../../models/request/secret-verification.request"; +import { TwoFactorAuthenticatorUpdateRequest } from "../../models/request/two-factor-authenticator-update.request"; import { TwoFactorDuoDeleteRequest } from "../../models/request/two-factor-duo-delete.request"; +import { TwoFactorDuoUpdateRequest } from "../../models/request/two-factor-duo-update.request"; import { TwoFactorEmailDeleteRequest } from "../../models/request/two-factor-email-delete.request"; +import { TwoFactorEmailUpdateRequest } from "../../models/request/two-factor-email-update.request"; import { TwoFactorEmailRequest } from "../../models/request/two-factor-email.request"; import { TwoFactorOrganizationDuoDeleteRequest } from "../../models/request/two-factor-organization-duo-delete.request"; import { TwoFactorWebAuthnDeleteAllRequest } from "../../models/request/two-factor-web-authn-delete-all.request"; +import { TwoFactorWebAuthnDeleteRequest } from "../../models/request/two-factor-web-authn-delete.request"; +import { TwoFactorWebAuthnUpdateRequest } from "../../models/request/two-factor-web-authn-update.request"; import { TwoFactorYubiKeyDeleteRequest } from "../../models/request/two-factor-yubikey-delete.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 { TwoFactorYubiKeyUpdateRequest } from "../../models/request/two-factor-yubikey-update.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"; @@ -218,7 +218,7 @@ export class DefaultTwoFactorService implements TwoFactorServiceAbstraction { } putTwoFactorAuthenticator( - request: UpdateTwoFactorAuthenticatorRequest, + request: TwoFactorAuthenticatorUpdateRequest, ): Promise { return this.twoFactorApiService.putTwoFactorAuthenticator(request); } @@ -229,35 +229,33 @@ export class DefaultTwoFactorService implements TwoFactorServiceAbstraction { return this.twoFactorApiService.deleteTwoFactorAuthenticator(request); } - putTwoFactorEmail(request: UpdateTwoFactorEmailRequest): Promise { + putTwoFactorEmail(request: TwoFactorEmailUpdateRequest): Promise { return this.twoFactorApiService.putTwoFactorEmail(request); } - putTwoFactorDuo(request: UpdateTwoFactorDuoRequest): Promise { + putTwoFactorDuo(request: TwoFactorDuoUpdateRequest): Promise { return this.twoFactorApiService.putTwoFactorDuo(request); } putTwoFactorOrganizationDuo( organizationId: string, - request: UpdateTwoFactorDuoRequest, + request: TwoFactorDuoUpdateRequest, ): Promise { return this.twoFactorApiService.putTwoFactorOrganizationDuo(organizationId, request); } - putTwoFactorYubiKey( - request: UpdateTwoFactorYubikeyOtpRequest, - ): Promise { + putTwoFactorYubiKey(request: TwoFactorYubiKeyUpdateRequest): Promise { return this.twoFactorApiService.putTwoFactorYubiKey(request); } putTwoFactorWebAuthn( - request: UpdateTwoFactorWebAuthnRequest, + request: TwoFactorWebAuthnUpdateRequest, ): Promise { return this.twoFactorApiService.putTwoFactorWebAuthn(request); } deleteTwoFactorWebAuthn( - request: UpdateTwoFactorWebAuthnDeleteRequest, + request: TwoFactorWebAuthnDeleteRequest, ): Promise { return this.twoFactorApiService.deleteTwoFactorWebAuthn(request); } From e6c1c6b8861141997fb13eb3becaa1bfb7971549 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Mon, 22 Jun 2026 20:18:18 -0400 Subject: [PATCH 07/29] PM-38137 - Split Email 2FA request models by flow The shared TwoFactorEmailRequest split into purpose-specific models: - TwoFactorEmailLoginRequest (anonymous login flow, secret-based) drops the userVerificationToken field that the server-side login model no longer accepts. - TwoFactorEmailSetupRequest (authenticated setup-send, token-only: email + userVerificationToken). postTwoFactorEmailSetup now takes this narrower shape instead of the union. --- apps/cli/src/auth/commands/login.command.ts | 4 ++-- .../two-factor/two-factor-setup-email.component.ts | 4 ++-- .../two-factor-auth-email.component.ts | 6 +++--- ...request.ts => two-factor-email-login.request.ts} | 3 +-- .../request/two-factor-email-setup.request.ts | 4 ++++ .../abstractions/two-factor-api.service.ts | 7 ++++--- .../two-factor/abstractions/two-factor.service.ts | 13 ++++++------- .../services/default-two-factor-api.service.spec.ts | 10 +++++----- .../services/default-two-factor-api.service.ts | 7 ++++--- .../services/default-two-factor.service.ts | 7 ++++--- 10 files changed, 35 insertions(+), 30 deletions(-) rename libs/common/src/auth/models/request/{two-factor-email.request.ts => two-factor-email-login.request.ts} (72%) create mode 100644 libs/common/src/auth/models/request/two-factor-email-setup.request.ts diff --git a/apps/cli/src/auth/commands/login.command.ts b/apps/cli/src/auth/commands/login.command.ts index bc55314a3a68..63881fe9de0a 100644 --- a/apps/cli/src/auth/commands/login.command.ts +++ b/apps/cli/src/auth/commands/login.command.ts @@ -25,7 +25,7 @@ import { 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 { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request"; +import { TwoFactorEmailLoginRequest } from "@bitwarden/common/auth/models/request/two-factor-email-login.request"; 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"; @@ -300,7 +300,7 @@ export class LoginCommand { } if (twoFactorToken == null && selectedProvider.type === TwoFactorProviderType.Email) { - const emailReq = new TwoFactorEmailRequest(); + const emailReq = new TwoFactorEmailLoginRequest(); emailReq.email = await this.loginStrategyService.getEmail(); // if the user was logging in with SSO, we need to include the SSO session token if (response.ssoEmail2FaSessionToken != null) { 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 ae8e3c296e5b..ffdc4d76a783 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 @@ -7,8 +7,8 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv 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 { TwoFactorEmailDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-email-delete.request"; +import { TwoFactorEmailSetupRequest } from "@bitwarden/common/auth/models/request/two-factor-email-setup.request"; import { TwoFactorEmailUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-email-update.request"; -import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request"; import { TwoFactorEmailResponse } from "@bitwarden/common/auth/models/response/two-factor-email.response"; import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; @@ -135,7 +135,7 @@ export class TwoFactorSetupEmailComponent } sendEmail = async () => { - const request = new TwoFactorEmailRequest(); + const request = new TwoFactorEmailSetupRequest(); request.email = this.email; request.userVerificationToken = this.userVerificationToken; this.emailPromise = this.twoFactorService.postTwoFactorEmailSetup(request); 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 b3db3dbca4a3..090f20a4224b 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 @@ -5,7 +5,7 @@ import { ReactiveFormsModule, FormsModule, FormControl } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; -import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request"; +import { TwoFactorEmailLoginRequest } from "@bitwarden/common/auth/models/request/two-factor-email-login.request"; 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"; @@ -124,7 +124,7 @@ export class TwoFactorAuthEmailComponent implements OnInit { } // TODO: PM-17545 - consider building a method on the login strategy service to get a mostly - // initialized TwoFactorEmailRequest in 1 call instead of 5 like we do today. + // initialized TwoFactorEmailLoginRequest in 1 call instead of 5 like we do today. const email = await this.loginStrategyService.getEmail(); if (email == null) { @@ -137,7 +137,7 @@ export class TwoFactorAuthEmailComponent implements OnInit { } try { - const request = new TwoFactorEmailRequest(); + const request = new TwoFactorEmailLoginRequest(); request.email = email; request.masterPasswordHash = (await this.loginStrategyService.getMasterPasswordHash()) ?? ""; diff --git a/libs/common/src/auth/models/request/two-factor-email.request.ts b/libs/common/src/auth/models/request/two-factor-email-login.request.ts similarity index 72% rename from libs/common/src/auth/models/request/two-factor-email.request.ts rename to libs/common/src/auth/models/request/two-factor-email-login.request.ts index 4a0a9cf3dc4f..4edca711c838 100644 --- a/libs/common/src/auth/models/request/two-factor-email.request.ts +++ b/libs/common/src/auth/models/request/two-factor-email-login.request.ts @@ -2,10 +2,9 @@ // @ts-strict-ignore import { SecretVerificationRequest } from "./secret-verification.request"; -export class TwoFactorEmailRequest extends SecretVerificationRequest { +export class TwoFactorEmailLoginRequest extends SecretVerificationRequest { email: string; deviceIdentifier: string; authRequestId: string; ssoEmail2FaSessionToken?: string; - userVerificationToken?: string; } diff --git a/libs/common/src/auth/models/request/two-factor-email-setup.request.ts b/libs/common/src/auth/models/request/two-factor-email-setup.request.ts new file mode 100644 index 000000000000..7b35f57c0092 --- /dev/null +++ b/libs/common/src/auth/models/request/two-factor-email-setup.request.ts @@ -0,0 +1,4 @@ +export class TwoFactorEmailSetupRequest { + email!: string; + userVerificationToken!: string; +} diff --git a/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts b/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts index d96ac185c3a0..a1133e956a0a 100644 --- a/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts +++ b/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts @@ -4,8 +4,9 @@ import { TwoFactorAuthenticatorUpdateRequest } from "@bitwarden/common/auth/mode import { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-delete.request"; import { TwoFactorDuoUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-update.request"; import { TwoFactorEmailDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-email-delete.request"; +import { TwoFactorEmailLoginRequest } from "@bitwarden/common/auth/models/request/two-factor-email-login.request"; +import { TwoFactorEmailSetupRequest } from "@bitwarden/common/auth/models/request/two-factor-email-setup.request"; import { TwoFactorEmailUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-email-update.request"; -import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request"; import { TwoFactorOrganizationDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-organization-duo-delete.request"; import { TwoFactorWebAuthnDeleteAllRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-delete-all.request"; import { TwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-delete.request"; @@ -313,7 +314,7 @@ export abstract class TwoFactorApiService { * @param request The request containing the email address and verification token for two-factor setup. * @returns A promise that resolves when the verification email has been sent. */ - abstract postTwoFactorEmailSetup(request: TwoFactorEmailRequest): Promise; + abstract postTwoFactorEmailSetup(request: TwoFactorEmailSetupRequest): Promise; /** * Sends a two-factor authentication code via email during the login flow. @@ -324,5 +325,5 @@ export abstract class TwoFactorApiService { * @param request The request to send the two-factor code, optionally including SSO or auth request tokens. * @returns A promise that resolves when the authentication email has been sent. */ - abstract postTwoFactorEmail(request: TwoFactorEmailRequest): Promise; + abstract postTwoFactorEmail(request: TwoFactorEmailLoginRequest): Promise; } 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 index 7b575b493a5e..ae24b2321cd2 100644 --- a/libs/common/src/auth/two-factor/abstractions/two-factor.service.ts +++ b/libs/common/src/auth/two-factor/abstractions/two-factor.service.ts @@ -7,8 +7,9 @@ import { TwoFactorAuthenticatorUpdateRequest } from "../../models/request/two-fa import { TwoFactorDuoDeleteRequest } from "../../models/request/two-factor-duo-delete.request"; import { TwoFactorDuoUpdateRequest } from "../../models/request/two-factor-duo-update.request"; import { TwoFactorEmailDeleteRequest } from "../../models/request/two-factor-email-delete.request"; +import { TwoFactorEmailLoginRequest } from "../../models/request/two-factor-email-login.request"; +import { TwoFactorEmailSetupRequest } from "../../models/request/two-factor-email-setup.request"; import { TwoFactorEmailUpdateRequest } from "../../models/request/two-factor-email-update.request"; -import { TwoFactorEmailRequest } from "../../models/request/two-factor-email.request"; import { TwoFactorOrganizationDuoDeleteRequest } from "../../models/request/two-factor-organization-duo-delete.request"; import { TwoFactorWebAuthnDeleteAllRequest } from "../../models/request/two-factor-web-authn-delete-all.request"; import { TwoFactorWebAuthnDeleteRequest } from "../../models/request/two-factor-web-authn-delete.request"; @@ -513,11 +514,10 @@ export abstract class TwoFactorService { * Requires user verification via master password or OTP. * Used for settings management. * - * @param request The {@link TwoFactorEmailRequest} to prove authentication. + * @param request The {@link TwoFactorEmailSetupRequest} 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; + abstract postTwoFactorEmailSetup(request: TwoFactorEmailSetupRequest): Promise; /** * Sends a two-factor authentication code via email during the login flow. @@ -526,9 +526,8 @@ export abstract class TwoFactorService { * May be called without authentication for login scenarios. * Used during authentication flows. * - * @param request The {@link TwoFactorEmailRequest} to prove authentication. + * @param request The {@link TwoFactorEmailLoginRequest} 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; + abstract postTwoFactorEmail(request: TwoFactorEmailLoginRequest): Promise; } diff --git a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts index 70cad137b5e9..525e28701376 100644 --- a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts +++ b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts @@ -7,8 +7,9 @@ import { TwoFactorAuthenticatorUpdateRequest } from "@bitwarden/common/auth/mode import { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-delete.request"; import { TwoFactorDuoUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-update.request"; import { TwoFactorEmailDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-email-delete.request"; +import { TwoFactorEmailLoginRequest } from "@bitwarden/common/auth/models/request/two-factor-email-login.request"; +import { TwoFactorEmailSetupRequest } from "@bitwarden/common/auth/models/request/two-factor-email-setup.request"; import { TwoFactorEmailUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-email-update.request"; -import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request"; import { TwoFactorOrganizationDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-organization-duo-delete.request"; import { TwoFactorWebAuthnDeleteAllRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-delete-all.request"; import { TwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-delete.request"; @@ -191,9 +192,9 @@ describe("TwoFactorApiService", () => { describe("postTwoFactorEmailSetup", () => { it("sends verification code to email address during two-factor setup", async () => { - const request = new TwoFactorEmailRequest(); + const request = new TwoFactorEmailSetupRequest(); request.email = "user@example.com"; - request.masterPasswordHash = "master-password-hash"; + request.userVerificationToken = "user-verification-token"; await twoFactorApiService.postTwoFactorEmailSetup(request); @@ -209,9 +210,8 @@ describe("TwoFactorApiService", () => { describe("postTwoFactorEmail", () => { it("sends two-factor authentication code during login flow", async () => { - const request = new TwoFactorEmailRequest(); + const request = new TwoFactorEmailLoginRequest(); request.email = "user@example.com"; - // Note: masterPasswordHash not required for login flow await twoFactorApiService.postTwoFactorEmail(request); diff --git a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts index 019a923c7e92..711b7cbbed7e 100644 --- a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts +++ b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts @@ -5,8 +5,9 @@ import { TwoFactorAuthenticatorUpdateRequest } from "@bitwarden/common/auth/mode import { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-delete.request"; import { TwoFactorDuoUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-update.request"; import { TwoFactorEmailDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-email-delete.request"; +import { TwoFactorEmailLoginRequest } from "@bitwarden/common/auth/models/request/two-factor-email-login.request"; +import { TwoFactorEmailSetupRequest } from "@bitwarden/common/auth/models/request/two-factor-email-setup.request"; import { TwoFactorEmailUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-email-update.request"; -import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request"; import { TwoFactorOrganizationDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-organization-duo-delete.request"; import { TwoFactorWebAuthnDeleteAllRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-delete-all.request"; import { TwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-delete.request"; @@ -103,11 +104,11 @@ export class DefaultTwoFactorApiService implements TwoFactorApiService { return new TwoFactorEmailResponse(response); } - async postTwoFactorEmailSetup(request: TwoFactorEmailRequest): Promise { + async postTwoFactorEmailSetup(request: TwoFactorEmailSetupRequest): Promise { return this.apiService.send("POST", "/two-factor/send-email", request, true, false); } - async postTwoFactorEmail(request: TwoFactorEmailRequest): Promise { + async postTwoFactorEmail(request: TwoFactorEmailLoginRequest): Promise { return this.apiService.send("POST", "/two-factor/send-email-login", request, false, false); } 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 index ab818f8146c9..1cea93564a88 100644 --- 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 @@ -15,8 +15,9 @@ import { TwoFactorAuthenticatorUpdateRequest } from "../../models/request/two-fa import { TwoFactorDuoDeleteRequest } from "../../models/request/two-factor-duo-delete.request"; import { TwoFactorDuoUpdateRequest } from "../../models/request/two-factor-duo-update.request"; import { TwoFactorEmailDeleteRequest } from "../../models/request/two-factor-email-delete.request"; +import { TwoFactorEmailLoginRequest } from "../../models/request/two-factor-email-login.request"; +import { TwoFactorEmailSetupRequest } from "../../models/request/two-factor-email-setup.request"; import { TwoFactorEmailUpdateRequest } from "../../models/request/two-factor-email-update.request"; -import { TwoFactorEmailRequest } from "../../models/request/two-factor-email.request"; import { TwoFactorOrganizationDuoDeleteRequest } from "../../models/request/two-factor-organization-duo-delete.request"; import { TwoFactorWebAuthnDeleteAllRequest } from "../../models/request/two-factor-web-authn-delete-all.request"; import { TwoFactorWebAuthnDeleteRequest } from "../../models/request/two-factor-web-authn-delete.request"; @@ -287,11 +288,11 @@ export class DefaultTwoFactorService implements TwoFactorServiceAbstraction { return this.twoFactorApiService.deleteTwoFactorWebAuthnAll(request); } - postTwoFactorEmailSetup(request: TwoFactorEmailRequest): Promise { + postTwoFactorEmailSetup(request: TwoFactorEmailSetupRequest): Promise { return this.twoFactorApiService.postTwoFactorEmailSetup(request); } - postTwoFactorEmail(request: TwoFactorEmailRequest): Promise { + postTwoFactorEmail(request: TwoFactorEmailLoginRequest): Promise { return this.twoFactorApiService.postTwoFactorEmail(request); } } From 47736fbbe3b56bb7aadc3c183ed02fcf274d3187 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Mon, 22 Jun 2026 21:10:31 -0400 Subject: [PATCH 08/29] PM-38137 - Guard cached UV token against null overwrite on PUT response Only PUT responses that re-mint a user-verification token should overwrite the cached value on the component. Without the guard, a PUT response with a null token clobbers the live token from the prior GET, breaking any follow-up DELETE that still runs against the same component instance. Matches the existing guard pattern in two-factor-setup-webauthn.component.ts. --- .../two-factor/two-factor-setup-authenticator.component.ts | 4 +++- .../settings/two-factor/two-factor-setup-duo.component.ts | 4 +++- .../settings/two-factor/two-factor-setup-email.component.ts | 4 +++- .../settings/two-factor/two-factor-setup-yubikey.component.ts | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) 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 f30fcf2e4e75..65ca0f59ba1d 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 @@ -193,7 +193,9 @@ export class TwoFactorSetupAuthenticatorComponent this.formGroup.get("token").setValue(null); this.enabled = response.enabled; this.key = response.key; - this.userVerificationToken = response.userVerificationToken; + if (response.userVerificationToken) { + this.userVerificationToken = response.userVerificationToken; + } await this.waitForQRiousToLoadOrError().catch((error) => { this.logService.error(error); 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 929585946fbb..5a013c32f9c7 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 @@ -198,7 +198,9 @@ export class TwoFactorSetupDuoComponent this.clientSecret = response.clientSecret; this.host = response.host; this.enabled = response.enabled; - this.userVerificationToken = response.userVerificationToken; + if (response.userVerificationToken) { + this.userVerificationToken = response.userVerificationToken; + } } /** 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 ffdc4d76a783..66bdcc142202 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 @@ -185,7 +185,9 @@ export class TwoFactorSetupEmailComponent this.token = null; this.email = response.email; this.enabled = response.enabled; - this.userVerificationToken = response.userVerificationToken; + if (response.userVerificationToken) { + this.userVerificationToken = response.userVerificationToken; + } if (!this.enabled && (this.email == null || this.email === "")) { this.email = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.email)), 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 9c7fb39f1852..119a15240a45 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 @@ -227,7 +227,9 @@ export class TwoFactorSetupYubiKeyComponent private processResponse(response: TwoFactorYubiKeyResponse) { this.enabled = response.enabled; this.anyKeyHasNfc = response.nfc || !response.enabled; - this.userVerificationToken = response.userVerificationToken; + if (response.userVerificationToken) { + this.userVerificationToken = response.userVerificationToken; + } this.keys = [ { key: response.key1, existingKey: this.padRight(response.key1) }, { key: response.key2, existingKey: this.padRight(response.key2) }, From 1129bf4560f01d8a54bcacfd31564e3845d71f9c Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Tue, 23 Jun 2026 17:01:05 -0400 Subject: [PATCH 09/29] PM-38137 - Refactor 2FA request DTOs to constructor parameters Convert the 13 per-provider 2FA request models added in this branch from field-assignment classes (with `!` definite-assignment assertions) to constructor-parameter classes. Callers now pass every required field at construction; missing fields become compile-time errors instead of silent `undefined` payloads. Consumers updated: - apps/web/src/app/auth/settings/two-factor/two-factor-setup-{authenticator,duo,email,webauthn,yubikey,}.component.ts - libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts --- ...wo-factor-setup-authenticator.component.ts | 13 ++- .../two-factor-setup-duo.component.ts | 17 ++-- .../two-factor-setup-email.component.ts | 16 ++-- .../two-factor-setup-webauthn.component.ts | 18 ++-- .../two-factor-setup-yubikey.component.ts | 20 ++-- .../two-factor/two-factor-setup.component.ts | 8 +- ...delete-two-factor-authenticator.request.ts | 6 +- ...two-factor-authenticator-update.request.ts | 8 +- .../request/two-factor-duo-delete.request.ts | 2 +- .../request/two-factor-duo-update.request.ts | 10 +- .../two-factor-email-delete.request.ts | 2 +- .../request/two-factor-email-setup.request.ts | 6 +- .../two-factor-email-update.request.ts | 8 +- ...-factor-organization-duo-delete.request.ts | 2 +- ...two-factor-web-authn-delete-all.request.ts | 2 +- .../two-factor-web-authn-delete.request.ts | 6 +- .../two-factor-web-authn-update.request.ts | 10 +- .../two-factor-yubikey-delete.request.ts | 2 +- .../two-factor-yubikey-update.request.ts | 16 ++-- .../default-two-factor-api.service.spec.ts | 92 ++++++++++--------- 20 files changed, 141 insertions(+), 123 deletions(-) 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 65ca0f59ba1d..fb8848345853 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 @@ -155,10 +155,11 @@ export class TwoFactorSetupAuthenticatorComponent }; protected async enable() { - const request = new TwoFactorAuthenticatorUpdateRequest(); - request.token = this.formGroup.value.token; - request.key = this.key; - request.userVerificationToken = this.userVerificationToken; + const request = new TwoFactorAuthenticatorUpdateRequest( + this.formGroup.value.token, + this.key, + this.userVerificationToken, + ); const response = await this.twoFactorService.putTwoFactorAuthenticator(request); await this.processResponse(response); @@ -176,9 +177,7 @@ export class TwoFactorSetupAuthenticatorComponent return; } - const request = new DeleteTwoFactorAuthenticatorRequest(); - request.key = this.key; - request.userVerificationToken = this.userVerificationToken; + const request = new DeleteTwoFactorAuthenticatorRequest(this.key, this.userVerificationToken); await this.twoFactorService.deleteTwoFactorAuthenticator(request); this.enabled = false; this.toastService.showToast({ 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 5a013c32f9c7..dfc66cba4de1 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 @@ -138,11 +138,12 @@ export class TwoFactorSetupDuoComponent }; protected async enable() { - const request = new TwoFactorDuoUpdateRequest(); - request.clientId = this.clientId; - request.clientSecret = this.clientSecret; - request.host = this.host; - request.userVerificationToken = this.userVerificationToken; + const request = new TwoFactorDuoUpdateRequest( + this.clientId, + this.clientSecret, + this.host, + this.userVerificationToken, + ); let response: TwoFactorDuoResponse; @@ -171,12 +172,10 @@ export class TwoFactorSetupDuoComponent } if (this.organizationId != null) { - const request = new TwoFactorOrganizationDuoDeleteRequest(); - request.userVerificationToken = this.userVerificationToken; + const request = new TwoFactorOrganizationDuoDeleteRequest(this.userVerificationToken); await this.twoFactorService.deleteTwoFactorOrganizationDuo(this.organizationId, request); } else { - const request = new TwoFactorDuoDeleteRequest(); - request.userVerificationToken = this.userVerificationToken; + const request = new TwoFactorDuoDeleteRequest(this.userVerificationToken); await this.twoFactorService.deleteTwoFactorDuo(request); } 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 66bdcc142202..94c61654ec04 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 @@ -135,19 +135,18 @@ export class TwoFactorSetupEmailComponent } sendEmail = async () => { - const request = new TwoFactorEmailSetupRequest(); - request.email = this.email; - request.userVerificationToken = this.userVerificationToken; + const request = new TwoFactorEmailSetupRequest(this.email, this.userVerificationToken); this.emailPromise = this.twoFactorService.postTwoFactorEmailSetup(request); await this.emailPromise; this.sentEmail = this.email; }; protected async enable() { - const request = new TwoFactorEmailUpdateRequest(); - request.email = this.email; - request.token = this.token; - request.userVerificationToken = this.userVerificationToken; + const request = new TwoFactorEmailUpdateRequest( + this.token, + this.email, + this.userVerificationToken, + ); const response = await this.twoFactorService.putTwoFactorEmail(request); await this.processResponse(response); @@ -165,8 +164,7 @@ export class TwoFactorSetupEmailComponent return; } - const request = new TwoFactorEmailDeleteRequest(); - request.userVerificationToken = this.userVerificationToken; + const request = new TwoFactorEmailDeleteRequest(this.userVerificationToken); await this.twoFactorService.deleteTwoFactorEmail(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 0dd087f1e26a..ec4bb51764dd 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 @@ -127,11 +127,12 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom throw new Error("WebAuthn response or key ID is missing"); } - const request = new TwoFactorWebAuthnUpdateRequest(); - request.deviceResponse = this.webAuthnResponse; - request.id = this.keyIdAvailable; - request.name = this.formGroup.value.name || ""; - request.userVerificationToken = this.userVerificationToken; + const request = new TwoFactorWebAuthnUpdateRequest( + this.webAuthnResponse, + this.formGroup.value.name || "", + this.keyIdAvailable, + this.userVerificationToken, + ); const response = await this.twoFactorService.putTwoFactorWebAuthn(request); this.processResponse(response); @@ -164,8 +165,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom // Server's per-credential DELETE refuses to remove the last registered credential // (lockout-prevention), so the only path to disable WebAuthn entirely is the bulk endpoint. - const request = new TwoFactorWebAuthnDeleteAllRequest(); - request.userVerificationToken = this.userVerificationToken; + const request = new TwoFactorWebAuthnDeleteAllRequest(this.userVerificationToken); await this.twoFactorService.deleteTwoFactorWebAuthnAll(request); this.enabled = false; this.toastService.showToast({ @@ -191,9 +191,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom if (!confirmed) { return; } - const request = new TwoFactorWebAuthnDeleteRequest(); - request.id = key.id; - request.userVerificationToken = this.userVerificationToken; + const request = new TwoFactorWebAuthnDeleteRequest(key.id, this.userVerificationToken); try { key.removePromise = this.twoFactorService.deleteTwoFactorWebAuthn(request); const response = await key.removePromise; 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 119a15240a45..a70b1785d93b 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 @@ -168,14 +168,15 @@ export class TwoFactorSetupYubiKeyComponent return; } const keys = this.formGroup.controls.formKeys.value; - const request = new TwoFactorYubiKeyUpdateRequest(); - request.key1 = keys != null && keys.length > 0 ? (keys[0]?.key ?? "") : ""; - request.key2 = keys != null && keys.length > 1 ? (keys[1]?.key ?? "") : ""; - request.key3 = keys != null && keys.length > 2 ? (keys[2]?.key ?? "") : ""; - request.key4 = keys != null && keys.length > 3 ? (keys[3]?.key ?? "") : ""; - request.key5 = keys != null && keys.length > 4 ? (keys[4]?.key ?? "") : ""; - request.nfc = this.formGroup.value.anyKeyHasNfc ?? false; - request.userVerificationToken = this.userVerificationToken; + const request = new TwoFactorYubiKeyUpdateRequest( + keys != null && keys.length > 0 ? (keys[0]?.key ?? "") : "", + keys != null && keys.length > 1 ? (keys[1]?.key ?? "") : "", + keys != null && keys.length > 2 ? (keys[2]?.key ?? "") : "", + keys != null && keys.length > 3 ? (keys[3]?.key ?? "") : "", + keys != null && keys.length > 4 ? (keys[4]?.key ?? "") : "", + this.formGroup.value.anyKeyHasNfc ?? false, + this.userVerificationToken, + ); this.processResponse(await this.twoFactorService.putTwoFactorYubiKey(request)); this.refreshFormArrayData(); @@ -198,8 +199,7 @@ export class TwoFactorSetupYubiKeyComponent return; } - const request = new TwoFactorYubiKeyDeleteRequest(); - request.userVerificationToken = this.userVerificationToken; + const request = new TwoFactorYubiKeyDeleteRequest(this.userVerificationToken); await this.twoFactorService.deleteTwoFactorYubiKey(request); this.enabled = false; this.toastService.showToast({ 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 282ef7553861..1528faa40af5 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 @@ -181,15 +181,15 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { switch (type) { case TwoFactorProviderType.Yubikey: { const response = await this.twoFactorService.getTwoFactorYubiKey(getRequest); - const deleteRequest = new TwoFactorYubiKeyDeleteRequest(); - deleteRequest.userVerificationToken = response.userVerificationToken; + const deleteRequest = new TwoFactorYubiKeyDeleteRequest( + response.userVerificationToken, + ); await this.twoFactorService.deleteTwoFactorYubiKey(deleteRequest); break; } case TwoFactorProviderType.Duo: { const response = await this.twoFactorService.getTwoFactorDuo(getRequest); - const deleteRequest = new TwoFactorDuoDeleteRequest(); - deleteRequest.userVerificationToken = response.userVerificationToken; + const deleteRequest = new TwoFactorDuoDeleteRequest(response.userVerificationToken); await this.twoFactorService.deleteTwoFactorDuo(deleteRequest); break; } diff --git a/libs/common/src/auth/models/request/delete-two-factor-authenticator.request.ts b/libs/common/src/auth/models/request/delete-two-factor-authenticator.request.ts index 09b570f84c1f..39165a70648d 100644 --- a/libs/common/src/auth/models/request/delete-two-factor-authenticator.request.ts +++ b/libs/common/src/auth/models/request/delete-two-factor-authenticator.request.ts @@ -1,4 +1,6 @@ export class DeleteTwoFactorAuthenticatorRequest { - userVerificationToken!: string; - key!: string; + constructor( + public key: string, + public userVerificationToken: string, + ) {} } diff --git a/libs/common/src/auth/models/request/two-factor-authenticator-update.request.ts b/libs/common/src/auth/models/request/two-factor-authenticator-update.request.ts index 124d28f1adc1..f4fc67557c33 100644 --- a/libs/common/src/auth/models/request/two-factor-authenticator-update.request.ts +++ b/libs/common/src/auth/models/request/two-factor-authenticator-update.request.ts @@ -1,5 +1,7 @@ export class TwoFactorAuthenticatorUpdateRequest { - token!: string; - key!: string; - userVerificationToken!: string; + constructor( + public token: string, + public key: string, + public userVerificationToken: string, + ) {} } diff --git a/libs/common/src/auth/models/request/two-factor-duo-delete.request.ts b/libs/common/src/auth/models/request/two-factor-duo-delete.request.ts index e9e39eba3449..d315e912543e 100644 --- a/libs/common/src/auth/models/request/two-factor-duo-delete.request.ts +++ b/libs/common/src/auth/models/request/two-factor-duo-delete.request.ts @@ -1,3 +1,3 @@ export class TwoFactorDuoDeleteRequest { - userVerificationToken!: string; + constructor(public userVerificationToken: string) {} } diff --git a/libs/common/src/auth/models/request/two-factor-duo-update.request.ts b/libs/common/src/auth/models/request/two-factor-duo-update.request.ts index 03d35547eb40..ae0fb101917f 100644 --- a/libs/common/src/auth/models/request/two-factor-duo-update.request.ts +++ b/libs/common/src/auth/models/request/two-factor-duo-update.request.ts @@ -1,6 +1,8 @@ export class TwoFactorDuoUpdateRequest { - clientId!: string; - clientSecret!: string; - host!: string; - userVerificationToken!: string; + constructor( + public clientId: string, + public clientSecret: string, + public host: string, + public userVerificationToken: string, + ) {} } diff --git a/libs/common/src/auth/models/request/two-factor-email-delete.request.ts b/libs/common/src/auth/models/request/two-factor-email-delete.request.ts index f29f1cbc7590..7ef7315bd2c5 100644 --- a/libs/common/src/auth/models/request/two-factor-email-delete.request.ts +++ b/libs/common/src/auth/models/request/two-factor-email-delete.request.ts @@ -1,3 +1,3 @@ export class TwoFactorEmailDeleteRequest { - userVerificationToken!: string; + constructor(public userVerificationToken: string) {} } diff --git a/libs/common/src/auth/models/request/two-factor-email-setup.request.ts b/libs/common/src/auth/models/request/two-factor-email-setup.request.ts index 7b35f57c0092..aefcf2eec1a5 100644 --- a/libs/common/src/auth/models/request/two-factor-email-setup.request.ts +++ b/libs/common/src/auth/models/request/two-factor-email-setup.request.ts @@ -1,4 +1,6 @@ export class TwoFactorEmailSetupRequest { - email!: string; - userVerificationToken!: string; + constructor( + public email: string, + public userVerificationToken: string, + ) {} } diff --git a/libs/common/src/auth/models/request/two-factor-email-update.request.ts b/libs/common/src/auth/models/request/two-factor-email-update.request.ts index 79157fdff82b..8646edfe97cf 100644 --- a/libs/common/src/auth/models/request/two-factor-email-update.request.ts +++ b/libs/common/src/auth/models/request/two-factor-email-update.request.ts @@ -1,5 +1,7 @@ export class TwoFactorEmailUpdateRequest { - token!: string; - email!: string; - userVerificationToken!: string; + constructor( + public token: string, + public email: string, + public userVerificationToken: string, + ) {} } diff --git a/libs/common/src/auth/models/request/two-factor-organization-duo-delete.request.ts b/libs/common/src/auth/models/request/two-factor-organization-duo-delete.request.ts index 966f0c01461a..aff7ff49bd50 100644 --- a/libs/common/src/auth/models/request/two-factor-organization-duo-delete.request.ts +++ b/libs/common/src/auth/models/request/two-factor-organization-duo-delete.request.ts @@ -1,3 +1,3 @@ export class TwoFactorOrganizationDuoDeleteRequest { - userVerificationToken!: string; + constructor(public userVerificationToken: string) {} } diff --git a/libs/common/src/auth/models/request/two-factor-web-authn-delete-all.request.ts b/libs/common/src/auth/models/request/two-factor-web-authn-delete-all.request.ts index b81ec226571f..24edff3ed6bf 100644 --- a/libs/common/src/auth/models/request/two-factor-web-authn-delete-all.request.ts +++ b/libs/common/src/auth/models/request/two-factor-web-authn-delete-all.request.ts @@ -1,3 +1,3 @@ export class TwoFactorWebAuthnDeleteAllRequest { - userVerificationToken!: string; + constructor(public userVerificationToken: string) {} } diff --git a/libs/common/src/auth/models/request/two-factor-web-authn-delete.request.ts b/libs/common/src/auth/models/request/two-factor-web-authn-delete.request.ts index 9f418b7035ed..206ce7e312af 100644 --- a/libs/common/src/auth/models/request/two-factor-web-authn-delete.request.ts +++ b/libs/common/src/auth/models/request/two-factor-web-authn-delete.request.ts @@ -1,4 +1,6 @@ export class TwoFactorWebAuthnDeleteRequest { - id!: number; - userVerificationToken!: string; + constructor( + public id: number, + public userVerificationToken: string, + ) {} } diff --git a/libs/common/src/auth/models/request/two-factor-web-authn-update.request.ts b/libs/common/src/auth/models/request/two-factor-web-authn-update.request.ts index 7d087b9d7bde..5c4d4f7f2680 100644 --- a/libs/common/src/auth/models/request/two-factor-web-authn-update.request.ts +++ b/libs/common/src/auth/models/request/two-factor-web-authn-update.request.ts @@ -1,6 +1,8 @@ export class TwoFactorWebAuthnUpdateRequest { - deviceResponse!: PublicKeyCredential; - name!: string; - id!: number; - userVerificationToken!: string; + constructor( + public deviceResponse: PublicKeyCredential, + public name: string, + public id: number, + public userVerificationToken: string, + ) {} } diff --git a/libs/common/src/auth/models/request/two-factor-yubikey-delete.request.ts b/libs/common/src/auth/models/request/two-factor-yubikey-delete.request.ts index 53eb765981ff..f6d0daee4972 100644 --- a/libs/common/src/auth/models/request/two-factor-yubikey-delete.request.ts +++ b/libs/common/src/auth/models/request/two-factor-yubikey-delete.request.ts @@ -1,3 +1,3 @@ export class TwoFactorYubiKeyDeleteRequest { - userVerificationToken!: string; + constructor(public userVerificationToken: string) {} } diff --git a/libs/common/src/auth/models/request/two-factor-yubikey-update.request.ts b/libs/common/src/auth/models/request/two-factor-yubikey-update.request.ts index 8a25f3035ea0..4ac1fa4ba984 100644 --- a/libs/common/src/auth/models/request/two-factor-yubikey-update.request.ts +++ b/libs/common/src/auth/models/request/two-factor-yubikey-update.request.ts @@ -1,9 +1,11 @@ export class TwoFactorYubiKeyUpdateRequest { - key1!: string; - key2!: string; - key3!: string; - key4!: string; - key5!: string; - nfc!: boolean; - userVerificationToken!: string; + constructor( + public key1: string, + public key2: string, + public key3: string, + public key4: string, + public key5: string, + public nfc: boolean, + public userVerificationToken: string, + ) {} } diff --git a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts index 525e28701376..db5d0a523f0b 100644 --- a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts +++ b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts @@ -113,9 +113,7 @@ describe("TwoFactorApiService", () => { describe("putTwoFactorAuthenticator", () => { it("enables authenticator after validating the provided token", async () => { - const request = new TwoFactorAuthenticatorUpdateRequest(); - request.token = "123456"; - request.key = "MFRGGZDFMZTWQ2LK"; + const request = new TwoFactorAuthenticatorUpdateRequest("123456", "MFRGGZDFMZTWQ2LK", ""); const mockResponse = { Enabled: true, Key: "MFRGGZDFMZTWQ2LK", @@ -139,9 +137,7 @@ describe("TwoFactorApiService", () => { describe("deleteTwoFactorAuthenticator", () => { it("disables authenticator two-factor authentication", async () => { - const request = new DeleteTwoFactorAuthenticatorRequest(); - request.userVerificationToken = "uv-token"; - request.key = "MFRGGZDFMZTWQ2LK"; + const request = new DeleteTwoFactorAuthenticatorRequest("MFRGGZDFMZTWQ2LK", "uv-token"); const mockResponse = { Enabled: false, Type: 0, @@ -192,9 +188,10 @@ describe("TwoFactorApiService", () => { describe("postTwoFactorEmailSetup", () => { it("sends verification code to email address during two-factor setup", async () => { - const request = new TwoFactorEmailSetupRequest(); - request.email = "user@example.com"; - request.userVerificationToken = "user-verification-token"; + const request = new TwoFactorEmailSetupRequest( + "user@example.com", + "user-verification-token", + ); await twoFactorApiService.postTwoFactorEmailSetup(request); @@ -227,9 +224,11 @@ describe("TwoFactorApiService", () => { describe("putTwoFactorEmail", () => { it("enables email two-factor after validating the verification code", async () => { - const request = new TwoFactorEmailUpdateRequest(); - request.email = "user@example.com"; - request.token = "verification-code"; + const request = new TwoFactorEmailUpdateRequest( + "verification-code", + "user@example.com", + "", + ); const mockResponse = { Enabled: true, Email: "user@example.com", @@ -317,10 +316,12 @@ describe("TwoFactorApiService", () => { describe("putTwoFactorDuo", () => { it("enables Duo two-factor for premium user with valid integration details", async () => { - const request = new TwoFactorDuoUpdateRequest(); - request.host = "api-abc123.duosecurity.com"; - request.clientId = "DI9ABC1DEFGH2JKL"; - request.clientSecret = "client-secret-value-here"; + const request = new TwoFactorDuoUpdateRequest( + "DI9ABC1DEFGH2JKL", + "client-secret-value-here", + "api-abc123.duosecurity.com", + "", + ); const mockResponse = { Enabled: true, Host: "api-abc123.duosecurity.com", @@ -343,10 +344,12 @@ describe("TwoFactorApiService", () => { describe("putTwoFactorOrganizationDuo", () => { it("enables organization-level Duo with policy management permissions", async () => { const organizationId = "org-123"; - const request = new TwoFactorDuoUpdateRequest(); - request.host = "api-xyz789.duosecurity.com"; - request.clientId = "DI4XYZ9MNOP3QRS"; - request.clientSecret = "orgcli-secret-value-here"; + const request = new TwoFactorDuoUpdateRequest( + "DI4XYZ9MNOP3QRS", + "orgcli-secret-value-here", + "api-xyz789.duosecurity.com", + "", + ); const mockResponse = { Enabled: true, Host: "api-xyz789.duosecurity.com", @@ -406,9 +409,15 @@ describe("TwoFactorApiService", () => { describe("putTwoFactorYubiKey", () => { it("enables YubiKey two-factor for premium user after validating device OTPs", async () => { - const request = new TwoFactorYubiKeyUpdateRequest(); - request.key1 = "ccccccccccccjkhbhbhrkcitringjkrjirfjuunlnlvcghnkrtgfj"; - request.key2 = "ddddddddddddvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv"; + const request = new TwoFactorYubiKeyUpdateRequest( + "ccccccccccccjkhbhbhrkcitringjkrjirfjuunlnlvcghnkrtgfj", + "ddddddddddddvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv", + "", + "", + "", + false, + "", + ); const mockResponse = { Enabled: true, Key1: "cccccccccccc", @@ -520,9 +529,12 @@ describe("TwoFactorApiService", () => { getClientExtensionResults: jest.fn().mockReturnValue({}), }; - const request = new TwoFactorWebAuthnUpdateRequest(); - request.deviceResponse = mockCredential as PublicKeyCredential; - request.name = "My Security Key"; + const request = new TwoFactorWebAuthnUpdateRequest( + mockCredential as PublicKeyCredential, + "My Security Key", + 0, + "", + ); const mockResponse = { Enabled: true, @@ -572,9 +584,12 @@ describe("TwoFactorApiService", () => { getClientExtensionResults: jest.fn().mockReturnValue({}), }; - const request = new TwoFactorWebAuthnUpdateRequest(); - request.deviceResponse = mockCredential as PublicKeyCredential; - request.name = "My Security Key"; + const request = new TwoFactorWebAuthnUpdateRequest( + mockCredential as PublicKeyCredential, + "My Security Key", + 0, + "", + ); const originalDeviceResponse = request.deviceResponse; apiService.send.mockResolvedValue({ enabled: true, keys: [] }); @@ -589,9 +604,7 @@ describe("TwoFactorApiService", () => { describe("deleteTwoFactorWebAuthn", () => { it("removes specific WebAuthn credential while preserving other registered keys", async () => { - const request = new TwoFactorWebAuthnDeleteRequest(); - request.id = 1; - request.userVerificationToken = "uv-token"; + const request = new TwoFactorWebAuthnDeleteRequest(1, "uv-token"); const mockResponse = { Enabled: true, Keys: [{ Name: "Security Key", Id: 2, Migrated: true }], // Key with id:1 removed @@ -643,8 +656,7 @@ describe("TwoFactorApiService", () => { describe("Per-provider Delete APIs", () => { describe("deleteTwoFactorYubiKey", () => { it("removes YubiKey two-factor enrollment for the current user", async () => { - const request = new TwoFactorYubiKeyDeleteRequest(); - request.userVerificationToken = "uv-token"; + const request = new TwoFactorYubiKeyDeleteRequest("uv-token"); const mockResponse = { Enabled: false, Type: 3 }; // YubiKey apiService.send.mockResolvedValue(mockResponse); @@ -665,8 +677,7 @@ describe("TwoFactorApiService", () => { describe("deleteTwoFactorDuo", () => { it("removes Duo two-factor enrollment for the current user", async () => { - const request = new TwoFactorDuoDeleteRequest(); - request.userVerificationToken = "uv-token"; + const request = new TwoFactorDuoDeleteRequest("uv-token"); const mockResponse = { Enabled: false, Type: 2 }; // Duo apiService.send.mockResolvedValue(mockResponse); @@ -687,8 +698,7 @@ describe("TwoFactorApiService", () => { describe("deleteTwoFactorEmail", () => { it("removes email two-factor enrollment for the current user", async () => { - const request = new TwoFactorEmailDeleteRequest(); - request.userVerificationToken = "uv-token"; + const request = new TwoFactorEmailDeleteRequest("uv-token"); const mockResponse = { Enabled: false, Type: 1 }; // Email apiService.send.mockResolvedValue(mockResponse); @@ -710,8 +720,7 @@ describe("TwoFactorApiService", () => { describe("deleteTwoFactorOrganizationDuo", () => { it("removes Duo two-factor enrollment for an organization with policy management permissions", async () => { const organizationId = "org-123"; - const request = new TwoFactorOrganizationDuoDeleteRequest(); - request.userVerificationToken = "uv-token"; + const request = new TwoFactorOrganizationDuoDeleteRequest("uv-token"); const mockResponse = { Enabled: false, Type: 6 }; // OrganizationDuo apiService.send.mockResolvedValue(mockResponse); @@ -735,8 +744,7 @@ describe("TwoFactorApiService", () => { describe("deleteTwoFactorWebAuthnAll", () => { it("removes the entire WebAuthn enrollment in a single round-trip", async () => { - const request = new TwoFactorWebAuthnDeleteAllRequest(); - request.userVerificationToken = "uv-token"; + const request = new TwoFactorWebAuthnDeleteAllRequest("uv-token"); const mockResponse = { Enabled: false, Type: 7 }; // WebAuthn apiService.send.mockResolvedValue(mockResponse); From 29408bd602d1688de2c9f8806daac3ccb61ea090 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Tue, 23 Jun 2026 23:39:49 -0400 Subject: [PATCH 10/29] PM-38137 - Add 2FA provider Details and per-action response classes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Client mirror of the new server response shapes. Adds: - TwoFactorDetailsResponse — five per-provider Details types (Authenticator, Duo, Email, WebAuthn, YubiKey) carrying the shared provider state used by both GET and Update response wrappers. - TwoFactorUpdateResponse — five per-endpoint PUT responses, one per provider, each composing the matching Details type with no user-verification token slot. - TwoFactorOrganizationDuoResponse / TwoFactorOrganizationDuoUpdateResponse — split from the user-scoped Duo wrappers so the two scopes can evolve independently while sharing the inner TwoFactorDuoDetails. - TwoFactorWebAuthnDeleteResponse — returned by the per-credential WebAuthn DELETE; the updated credentials list travels in the body. No service-surface or component wiring yet; those land in the next commits. --- ...o-factor-authenticator-details.response.ts | 16 ++++++++++ ...wo-factor-authenticator-update.response.ts | 17 +++++++++++ .../two-factor-duo-details.response.ts | 20 +++++++++++++ .../two-factor-duo-update.response.ts | 15 ++++++++++ .../two-factor-email-details.response.ts | 16 ++++++++++ .../two-factor-email-update.response.ts | 15 ++++++++++ ...factor-organization-duo-update.response.ts | 16 ++++++++++ .../two-factor-organization-duo.response.ts | 18 +++++++++++ .../two-factor-web-authn-delete.response.ts | 18 +++++++++++ .../two-factor-web-authn-details.response.ts | 30 +++++++++++++++++++ .../two-factor-web-authn-update.response.ts | 15 ++++++++++ .../two-factor-yubi-key-details.response.ts | 26 ++++++++++++++++ .../two-factor-yubi-key-update.response.ts | 15 ++++++++++ 13 files changed, 237 insertions(+) create mode 100644 libs/common/src/auth/models/response/two-factor-authenticator-details.response.ts create mode 100644 libs/common/src/auth/models/response/two-factor-authenticator-update.response.ts create mode 100644 libs/common/src/auth/models/response/two-factor-duo-details.response.ts create mode 100644 libs/common/src/auth/models/response/two-factor-duo-update.response.ts create mode 100644 libs/common/src/auth/models/response/two-factor-email-details.response.ts create mode 100644 libs/common/src/auth/models/response/two-factor-email-update.response.ts create mode 100644 libs/common/src/auth/models/response/two-factor-organization-duo-update.response.ts create mode 100644 libs/common/src/auth/models/response/two-factor-organization-duo.response.ts create mode 100644 libs/common/src/auth/models/response/two-factor-web-authn-delete.response.ts create mode 100644 libs/common/src/auth/models/response/two-factor-web-authn-details.response.ts create mode 100644 libs/common/src/auth/models/response/two-factor-web-authn-update.response.ts create mode 100644 libs/common/src/auth/models/response/two-factor-yubi-key-details.response.ts create mode 100644 libs/common/src/auth/models/response/two-factor-yubi-key-update.response.ts diff --git a/libs/common/src/auth/models/response/two-factor-authenticator-details.response.ts b/libs/common/src/auth/models/response/two-factor-authenticator-details.response.ts new file mode 100644 index 000000000000..0cab8d6c9859 --- /dev/null +++ b/libs/common/src/auth/models/response/two-factor-authenticator-details.response.ts @@ -0,0 +1,16 @@ +import { BaseResponse } from "../../../models/response/base.response"; + +/** + * Authenticator (TOTP) provider details. Embedded by the per-action + * `TwoFactorAuthenticator{Get,Update}Response` wrappers. + */ +export class TwoFactorAuthenticatorDetailsResponse extends BaseResponse { + enabled: boolean; + key: string; + + constructor(response: any) { + super(response); + this.enabled = this.getResponseProperty("Enabled"); + this.key = this.getResponseProperty("Key"); + } +} diff --git a/libs/common/src/auth/models/response/two-factor-authenticator-update.response.ts b/libs/common/src/auth/models/response/two-factor-authenticator-update.response.ts new file mode 100644 index 000000000000..0f2152db8f35 --- /dev/null +++ b/libs/common/src/auth/models/response/two-factor-authenticator-update.response.ts @@ -0,0 +1,17 @@ +import { BaseResponse } from "../../../models/response/base.response"; + +import { TwoFactorAuthenticatorDetailsResponse } from "./two-factor-authenticator-details.response"; + +/** + * Response for `PUT /two-factor/authenticator`. Wraps the post-update provider details. + */ +export class TwoFactorAuthenticatorUpdateResponse extends BaseResponse { + authenticator: TwoFactorAuthenticatorDetailsResponse; + + constructor(response: any) { + super(response); + this.authenticator = new TwoFactorAuthenticatorDetailsResponse( + this.getResponseProperty("Authenticator"), + ); + } +} diff --git a/libs/common/src/auth/models/response/two-factor-duo-details.response.ts b/libs/common/src/auth/models/response/two-factor-duo-details.response.ts new file mode 100644 index 000000000000..0320e61191a2 --- /dev/null +++ b/libs/common/src/auth/models/response/two-factor-duo-details.response.ts @@ -0,0 +1,20 @@ +import { BaseResponse } from "../../../models/response/base.response"; + +/** + * Duo provider details for both user and organization scopes. Embedded by the per-action + * `TwoFactorDuo{Get,Update}Response` and `TwoFactorOrganizationDuo{Get,Update}Response` wrappers. + */ +export class TwoFactorDuoDetailsResponse extends BaseResponse { + enabled: boolean; + host: string; + clientId: string; + clientSecret: string; + + constructor(response: any) { + super(response); + this.enabled = this.getResponseProperty("Enabled"); + this.host = this.getResponseProperty("Host"); + this.clientId = this.getResponseProperty("ClientId"); + this.clientSecret = this.getResponseProperty("ClientSecret"); + } +} diff --git a/libs/common/src/auth/models/response/two-factor-duo-update.response.ts b/libs/common/src/auth/models/response/two-factor-duo-update.response.ts new file mode 100644 index 000000000000..e70de9da09ad --- /dev/null +++ b/libs/common/src/auth/models/response/two-factor-duo-update.response.ts @@ -0,0 +1,15 @@ +import { BaseResponse } from "../../../models/response/base.response"; + +import { TwoFactorDuoDetailsResponse } from "./two-factor-duo-details.response"; + +/** + * Response for `PUT /two-factor/duo`. Wraps the post-update user-scoped Duo provider details. + */ +export class TwoFactorDuoUpdateResponse extends BaseResponse { + duo: TwoFactorDuoDetailsResponse; + + constructor(response: any) { + super(response); + this.duo = new TwoFactorDuoDetailsResponse(this.getResponseProperty("Duo")); + } +} diff --git a/libs/common/src/auth/models/response/two-factor-email-details.response.ts b/libs/common/src/auth/models/response/two-factor-email-details.response.ts new file mode 100644 index 000000000000..351fc616af7c --- /dev/null +++ b/libs/common/src/auth/models/response/two-factor-email-details.response.ts @@ -0,0 +1,16 @@ +import { BaseResponse } from "../../../models/response/base.response"; + +/** + * Email provider details. Embedded by the per-action + * `TwoFactorEmail{Get,Update}Response` wrappers. + */ +export class TwoFactorEmailDetailsResponse extends BaseResponse { + enabled: boolean; + email: string; + + constructor(response: any) { + super(response); + this.enabled = this.getResponseProperty("Enabled"); + this.email = this.getResponseProperty("Email"); + } +} diff --git a/libs/common/src/auth/models/response/two-factor-email-update.response.ts b/libs/common/src/auth/models/response/two-factor-email-update.response.ts new file mode 100644 index 000000000000..6a0b422d5dc0 --- /dev/null +++ b/libs/common/src/auth/models/response/two-factor-email-update.response.ts @@ -0,0 +1,15 @@ +import { BaseResponse } from "../../../models/response/base.response"; + +import { TwoFactorEmailDetailsResponse } from "./two-factor-email-details.response"; + +/** + * Response for `PUT /two-factor/email`. Wraps the post-update provider details. + */ +export class TwoFactorEmailUpdateResponse extends BaseResponse { + email: TwoFactorEmailDetailsResponse; + + constructor(response: any) { + super(response); + this.email = new TwoFactorEmailDetailsResponse(this.getResponseProperty("Email")); + } +} diff --git a/libs/common/src/auth/models/response/two-factor-organization-duo-update.response.ts b/libs/common/src/auth/models/response/two-factor-organization-duo-update.response.ts new file mode 100644 index 000000000000..de474b941763 --- /dev/null +++ b/libs/common/src/auth/models/response/two-factor-organization-duo-update.response.ts @@ -0,0 +1,16 @@ +import { BaseResponse } from "../../../models/response/base.response"; + +import { TwoFactorDuoDetailsResponse } from "./two-factor-duo-details.response"; + +/** + * Response for `PUT /organizations/{id}/two-factor/duo`. Wraps the post-update + * organization-scoped Duo provider details. + */ +export class TwoFactorOrganizationDuoUpdateResponse extends BaseResponse { + duo: TwoFactorDuoDetailsResponse; + + constructor(response: any) { + super(response); + this.duo = new TwoFactorDuoDetailsResponse(this.getResponseProperty("Duo")); + } +} diff --git a/libs/common/src/auth/models/response/two-factor-organization-duo.response.ts b/libs/common/src/auth/models/response/two-factor-organization-duo.response.ts new file mode 100644 index 000000000000..ebb2237b400f --- /dev/null +++ b/libs/common/src/auth/models/response/two-factor-organization-duo.response.ts @@ -0,0 +1,18 @@ +import { BaseResponse } from "../../../models/response/base.response"; + +import { TwoFactorDuoDetailsResponse } from "./two-factor-duo-details.response"; + +/** + * Response for `POST /organizations/{id}/two-factor/get-duo`. Wraps the organization-scoped + * Duo provider details and the user-verification token minted by the GET endpoint. + */ +export class TwoFactorOrganizationDuoResponse extends BaseResponse { + duo: TwoFactorDuoDetailsResponse; + userVerificationToken: string; + + constructor(response: any) { + super(response); + this.duo = new TwoFactorDuoDetailsResponse(this.getResponseProperty("Duo")); + this.userVerificationToken = this.getResponseProperty("UserVerificationToken"); + } +} diff --git a/libs/common/src/auth/models/response/two-factor-web-authn-delete.response.ts b/libs/common/src/auth/models/response/two-factor-web-authn-delete.response.ts new file mode 100644 index 000000000000..4314263acada --- /dev/null +++ b/libs/common/src/auth/models/response/two-factor-web-authn-delete.response.ts @@ -0,0 +1,18 @@ +import { BaseResponse } from "../../../models/response/base.response"; + +import { TwoFactorWebAuthnDetailsResponse } from "./two-factor-web-authn-details.response"; + +/** + * Response for `DELETE /two-factor/webauthn` (per-credential). The operation modifies + * the WebAuthn provider's credentials list rather than destroying the provider, so the + * response carries the updated parent state. All other 2FA DELETE endpoints return + * 204 No Content. + */ +export class TwoFactorWebAuthnDeleteResponse extends BaseResponse { + webAuthn: TwoFactorWebAuthnDetailsResponse; + + constructor(response: any) { + super(response); + this.webAuthn = new TwoFactorWebAuthnDetailsResponse(this.getResponseProperty("WebAuthn")); + } +} diff --git a/libs/common/src/auth/models/response/two-factor-web-authn-details.response.ts b/libs/common/src/auth/models/response/two-factor-web-authn-details.response.ts new file mode 100644 index 000000000000..3b1ea09d5a18 --- /dev/null +++ b/libs/common/src/auth/models/response/two-factor-web-authn-details.response.ts @@ -0,0 +1,30 @@ +import { BaseResponse } from "../../../models/response/base.response"; + +/** + * WebAuthn provider details. Embedded by the per-action + * `TwoFactorWebAuthn{Get,Update,Delete}Response` wrappers. + */ +export class TwoFactorWebAuthnDetailsResponse extends BaseResponse { + enabled: boolean; + keys: WebAuthnKeyResponse[]; + + constructor(response: any) { + super(response); + this.enabled = this.getResponseProperty("Enabled"); + const keys = this.getResponseProperty("Keys"); + this.keys = keys == null ? null : keys.map((k: any) => new WebAuthnKeyResponse(k)); + } +} + +export class WebAuthnKeyResponse extends BaseResponse { + name: string; + id: number; + migrated: boolean; + + constructor(response: any) { + super(response); + this.name = this.getResponseProperty("Name"); + this.id = this.getResponseProperty("Id"); + this.migrated = this.getResponseProperty("Migrated"); + } +} diff --git a/libs/common/src/auth/models/response/two-factor-web-authn-update.response.ts b/libs/common/src/auth/models/response/two-factor-web-authn-update.response.ts new file mode 100644 index 000000000000..f55352788d7f --- /dev/null +++ b/libs/common/src/auth/models/response/two-factor-web-authn-update.response.ts @@ -0,0 +1,15 @@ +import { BaseResponse } from "../../../models/response/base.response"; + +import { TwoFactorWebAuthnDetailsResponse } from "./two-factor-web-authn-details.response"; + +/** + * Response for `PUT /two-factor/webauthn`. Wraps the post-update provider details. + */ +export class TwoFactorWebAuthnUpdateResponse extends BaseResponse { + webAuthn: TwoFactorWebAuthnDetailsResponse; + + constructor(response: any) { + super(response); + this.webAuthn = new TwoFactorWebAuthnDetailsResponse(this.getResponseProperty("WebAuthn")); + } +} diff --git a/libs/common/src/auth/models/response/two-factor-yubi-key-details.response.ts b/libs/common/src/auth/models/response/two-factor-yubi-key-details.response.ts new file mode 100644 index 000000000000..342bf20aee6a --- /dev/null +++ b/libs/common/src/auth/models/response/two-factor-yubi-key-details.response.ts @@ -0,0 +1,26 @@ +import { BaseResponse } from "../../../models/response/base.response"; + +/** + * YubiKey provider details. Embedded by the per-action + * `TwoFactorYubiKey{Get,Update}Response` wrappers. + */ +export class TwoFactorYubiKeyDetailsResponse extends BaseResponse { + enabled: boolean; + key1: string; + key2: string; + key3: string; + key4: string; + key5: string; + nfc: boolean; + + constructor(response: any) { + super(response); + this.enabled = this.getResponseProperty("Enabled"); + this.key1 = this.getResponseProperty("Key1"); + this.key2 = this.getResponseProperty("Key2"); + this.key3 = this.getResponseProperty("Key3"); + this.key4 = this.getResponseProperty("Key4"); + this.key5 = this.getResponseProperty("Key5"); + this.nfc = this.getResponseProperty("Nfc"); + } +} diff --git a/libs/common/src/auth/models/response/two-factor-yubi-key-update.response.ts b/libs/common/src/auth/models/response/two-factor-yubi-key-update.response.ts new file mode 100644 index 000000000000..de3231eb26d8 --- /dev/null +++ b/libs/common/src/auth/models/response/two-factor-yubi-key-update.response.ts @@ -0,0 +1,15 @@ +import { BaseResponse } from "../../../models/response/base.response"; + +import { TwoFactorYubiKeyDetailsResponse } from "./two-factor-yubi-key-details.response"; + +/** + * Response for `PUT /two-factor/yubikey`. Wraps the post-update provider details. + */ +export class TwoFactorYubiKeyUpdateResponse extends BaseResponse { + yubiKey: TwoFactorYubiKeyDetailsResponse; + + constructor(response: any) { + super(response); + this.yubiKey = new TwoFactorYubiKeyDetailsResponse(this.getResponseProperty("YubiKey")); + } +} From fe157b97afdb068e2b3921e8b2418f97a7ecbee9 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Tue, 23 Jun 2026 23:40:06 -0400 Subject: [PATCH 11/29] PM-38137 - Extract WebAuthn challenge response class and rename for clarity The FIDO2 credential-creation options class previously lived inside two-factor-web-authn.response.ts under the generic name ChallengeResponse, which read like it could be any challenge response. Moves the class to its own file (web-authn-challenge.response.ts) and renames it to WebAuthnChallengeResponse so its scope is obvious at every read site. Touches the 2FA WebAuthn challenge wrapper (TwoFactorWebAuthnChallengeResponse) and both passkey-login consumers (WebauthnLoginCredentialCreateOptionsResponse, CredentialCreateOptionsView) to import the renamed type from its new home. --- ...ogin-credential-create-options.response.ts | 6 +-- .../views/credential-create-options.view.ts | 4 +- ...two-factor-web-authn-challenge.response.ts | 8 ++-- .../response/web-authn-challenge.response.ts | 44 +++++++++++++++++++ 4 files changed, 53 insertions(+), 9 deletions(-) create mode 100644 libs/common/src/auth/models/response/web-authn-challenge.response.ts diff --git a/apps/web/src/app/auth/core/services/webauthn-login/response/webauthn-login-credential-create-options.response.ts b/apps/web/src/app/auth/core/services/webauthn-login/response/webauthn-login-credential-create-options.response.ts index ce588207727e..89d2e1fa53b7 100644 --- a/apps/web/src/app/auth/core/services/webauthn-login/response/webauthn-login-credential-create-options.response.ts +++ b/apps/web/src/app/auth/core/services/webauthn-login/response/webauthn-login-credential-create-options.response.ts @@ -1,4 +1,4 @@ -import { ChallengeResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn.response"; +import { WebAuthnChallengeResponse } from "@bitwarden/common/auth/models/response/web-authn-challenge.response"; import { BaseResponse } from "@bitwarden/common/models/response/base.response"; /** @@ -6,7 +6,7 @@ import { BaseResponse } from "@bitwarden/common/models/response/base.response"; */ export class WebauthnLoginCredentialCreateOptionsResponse extends BaseResponse { /** Options to be provided to the webauthn authenticator */ - options: ChallengeResponse; + options: WebAuthnChallengeResponse; /** * Contains an encrypted version of the {@link options}. @@ -16,7 +16,7 @@ export class WebauthnLoginCredentialCreateOptionsResponse extends BaseResponse { constructor(response: unknown) { super(response); - this.options = new ChallengeResponse(this.getResponseProperty("options")); + this.options = new WebAuthnChallengeResponse(this.getResponseProperty("options")); this.token = this.getResponseProperty("token"); } } diff --git a/apps/web/src/app/auth/core/views/credential-create-options.view.ts b/apps/web/src/app/auth/core/views/credential-create-options.view.ts index 0aef622abffe..0e0860e34a6b 100644 --- a/apps/web/src/app/auth/core/views/credential-create-options.view.ts +++ b/apps/web/src/app/auth/core/views/credential-create-options.view.ts @@ -1,8 +1,8 @@ -import { ChallengeResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn.response"; +import { WebAuthnChallengeResponse } from "@bitwarden/common/auth/models/response/web-authn-challenge.response"; export class CredentialCreateOptionsView { constructor( - readonly options: ChallengeResponse, + readonly options: WebAuthnChallengeResponse, readonly token: string, ) {} } diff --git a/libs/common/src/auth/models/response/two-factor-web-authn-challenge.response.ts b/libs/common/src/auth/models/response/two-factor-web-authn-challenge.response.ts index 09eca6f53160..38739a1df9d5 100644 --- a/libs/common/src/auth/models/response/two-factor-web-authn-challenge.response.ts +++ b/libs/common/src/auth/models/response/two-factor-web-authn-challenge.response.ts @@ -1,20 +1,20 @@ import { BaseResponse } from "../../../models/response/base.response"; -import { ChallengeResponse } from "./two-factor-web-authn.response"; +import { WebAuthnChallengeResponse } from "./web-authn-challenge.response"; /** - * Wrapper around {@link ChallengeResponse} that adds the user-verification token minted by + * Wrapper around {@link WebAuthnChallengeResponse} that adds the user-verification token minted by * the server-side `GetWebAuthnChallenge` endpoint. Replaces the previous response shape, which * returned the FIDO2 options object directly and had no place to attach the token. */ export class TwoFactorWebAuthnChallengeResponse extends BaseResponse { - options: ChallengeResponse | null; + options: WebAuthnChallengeResponse | null; userVerificationToken: string; constructor(response: any) { super(response); const options = this.getResponseProperty("Options"); - this.options = options == null ? null : new ChallengeResponse(options); + this.options = options == null ? null : new WebAuthnChallengeResponse(options); this.userVerificationToken = this.getResponseProperty("UserVerificationToken"); } } diff --git a/libs/common/src/auth/models/response/web-authn-challenge.response.ts b/libs/common/src/auth/models/response/web-authn-challenge.response.ts new file mode 100644 index 000000000000..02c6527876bc --- /dev/null +++ b/libs/common/src/auth/models/response/web-authn-challenge.response.ts @@ -0,0 +1,44 @@ +import { BaseResponse } from "../../../models/response/base.response"; +import { Utils } from "../../../platform/misc/utils"; + +/** + * WebAuthn credential-creation options minted by the server. Implements the + * `PublicKeyCredentialCreationOptions` shape consumed by `navigator.credentials.create()`. + * + * Reused by both the 2FA settings flow (wrapped in `TwoFactorWebAuthnChallengeResponse`) + * and the passkey-login flow (wrapped in `WebauthnLoginCredentialCreateOptionsResponse`). + */ +export class WebAuthnChallengeResponse + extends BaseResponse + implements PublicKeyCredentialCreationOptions +{ + attestation?: AttestationConveyancePreference; + authenticatorSelection?: AuthenticatorSelectionCriteria; + challenge: BufferSource; + excludeCredentials?: PublicKeyCredentialDescriptor[]; + extensions?: AuthenticationExtensionsClientInputs; + pubKeyCredParams: PublicKeyCredentialParameters[]; + rp: PublicKeyCredentialRpEntity; + timeout?: number; + user: PublicKeyCredentialUserEntity; + + constructor(response: any) { + super(response); + this.attestation = this.getResponseProperty("attestation"); + this.authenticatorSelection = this.getResponseProperty("authenticatorSelection"); + this.challenge = Utils.fromUrlB64ToArray(this.getResponseProperty("challenge")) as BufferSource; + this.excludeCredentials = this.getResponseProperty("excludeCredentials").map((c: any) => { + c.id = Utils.fromUrlB64ToArray(c.id).buffer; + return c; + }); + this.extensions = this.getResponseProperty("extensions"); + this.pubKeyCredParams = this.getResponseProperty("pubKeyCredParams"); + this.rp = this.getResponseProperty("rp"); + this.timeout = this.getResponseProperty("timeout"); + + const user = this.getResponseProperty("user"); + user.id = Utils.fromUrlB64ToArray(user.id); + + this.user = user; + } +} From 000ece640a4fede152080c42e1fcce0959286399 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Tue, 23 Jun 2026 23:40:30 -0400 Subject: [PATCH 12/29] PM-38137 - Refactor 2FA service surface and components to per-action response types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wires the client to the new per-action response shapes. The five existing TwoFactorResponse classes are repurposed as the GET response wrappers — each composes a TwoFactorDetailsResponse plus the freshly-minted user-verification token. Service surface updated: - DefaultTwoFactorApiService.getTwoFactor returns the GET wrapper; put returns the matching *UpdateResponse. - The six hard-delete methods return Promise and pass hasResponse: false to ApiService.send — the matching server endpoints now return 204 No Content with no body. deleteTwoFactorWebAuthn (per-credential) keeps a body and returns TwoFactorWebAuthnDeleteResponse. - TwoFactorService pass-through and abstractions updated to match. - TwoFactorResponse discriminated union updated to reference the per-provider GET response classes (plus TwoFactorOrganizationDuoResponse). Web setup components updated: - processResponse split into per-action handlers (GET, Update, Delete where applicable) so each handler reads from the nested data property on its specific response type. - The runtime `if (response.userVerificationToken)` cache guard from PM-38137-prior is dropped — the GET response type now guarantees the token non-optionally, and Update/Delete response types carry no token slot at all. - DTO read sites updated from flat (`response.host`) to nested (`response.duo.host`) across all five providers. - The admin-console organization parent setup component is updated for the new TwoFactorOrganizationDuoResponse name. --- .../settings/two-factor-setup.component.ts | 4 +- ...wo-factor-setup-authenticator.component.ts | 25 +- .../two-factor-setup-duo.component.ts | 39 ++- .../two-factor-setup-email.component.ts | 24 +- .../two-factor-setup-webauthn.component.ts | 49 +-- .../two-factor-setup-yubikey.component.ts | 34 +- .../two-factor-authenticator.response.ts | 14 +- .../response/two-factor-duo.response.ts | 16 +- .../response/two-factor-email.response.ts | 12 +- .../response/two-factor-web-authn.response.ts | 59 +--- .../response/two-factor-yubi-key.response.ts | 22 +- .../abstractions/two-factor-api.service.ts | 65 ++-- .../abstractions/two-factor.service.ts | 67 ++-- .../default-two-factor-api.service.spec.ts | 295 +++++++++--------- .../default-two-factor-api.service.ts | 100 +++--- .../services/default-two-factor.service.ts | 44 +-- .../src/auth/types/two-factor-response.ts | 2 + 17 files changed, 436 insertions(+), 435 deletions(-) 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 2f6f0642b193..ea7d1e7e1256 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 @@ -13,7 +13,7 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli 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 { TwoFactorOrganizationDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-organization-duo.response"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; @@ -93,7 +93,7 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent impleme const twoFactorVerifyDialogRef = TwoFactorVerifyComponent.open(this.dialogService, { data: { type: type, organizationId: this.organizationId }, }); - const result: AuthResponse = await lastValueFrom( + const result: AuthResponse = await lastValueFrom( twoFactorVerifyDialogRef.closed, ); if (!result) { 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 fb8848345853..6a1d4b3dc7ec 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 @@ -11,6 +11,7 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { DeleteTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/delete-two-factor-authenticator.request"; import { TwoFactorAuthenticatorUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-authenticator-update.request"; +import { TwoFactorAuthenticatorUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator-update.response"; import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator.response"; import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; @@ -85,7 +86,7 @@ export class TwoFactorSetupAuthenticatorComponent @Output() onChangeStatus = new EventEmitter(); type = TwoFactorProviderType.Authenticator; key: string; - private userVerificationToken: string; + private userVerificationToken!: string; override componentName = "app-two-factor-authenticator"; qrScriptError = false; @@ -138,7 +139,7 @@ export class TwoFactorSetupAuthenticatorComponent async auth(authResponse: AuthResponse) { super.auth(authResponse); - return this.processResponse(authResponse.response); + return this.processGetResponse(authResponse.response); } submit = async () => { @@ -162,7 +163,7 @@ export class TwoFactorSetupAuthenticatorComponent ); const response = await this.twoFactorService.putTwoFactorAuthenticator(request); - await this.processResponse(response); + await this.processUpdateResponse(response); this.onUpdated.emit(true); } @@ -188,13 +189,19 @@ export class TwoFactorSetupAuthenticatorComponent this.onUpdated.emit(false); } - private async processResponse(response: TwoFactorAuthenticatorResponse) { + private async processGetResponse(response: TwoFactorAuthenticatorResponse) { + this.userVerificationToken = response.userVerificationToken; + await this.applyAuthenticatorState(response.authenticator.enabled, response.authenticator.key); + } + + private async processUpdateResponse(response: TwoFactorAuthenticatorUpdateResponse) { + await this.applyAuthenticatorState(response.authenticator.enabled, response.authenticator.key); + } + + private async applyAuthenticatorState(enabled: boolean, key: string) { this.formGroup.get("token").setValue(null); - this.enabled = response.enabled; - this.key = response.key; - if (response.userVerificationToken) { - this.userVerificationToken = response.userVerificationToken; - } + this.enabled = enabled; + this.key = key; await this.waitForQRiousToLoadOrError().catch((error) => { this.logService.error(error); 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 dfc66cba4de1..b92fbf6ba364 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 @@ -7,7 +7,11 @@ import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-p import { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-delete.request"; import { TwoFactorDuoUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-update.request"; import { TwoFactorOrganizationDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-organization-duo-delete.request"; +import { TwoFactorDuoDetailsResponse } from "@bitwarden/common/auth/models/response/two-factor-duo-details.response"; +import { TwoFactorDuoUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-duo-update.response"; import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-duo.response"; +import { TwoFactorOrganizationDuoUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-organization-duo-update.response"; +import { TwoFactorOrganizationDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-organization-duo.response"; 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"; @@ -32,6 +36,11 @@ import { I18nPipe } from "@bitwarden/ui-common"; import { TwoFactorSetupMethodBaseComponent } from "./two-factor-setup-method-base.component"; +type TwoFactorDuoResponseUnion = TwoFactorDuoResponse | TwoFactorOrganizationDuoResponse; +type TwoFactorDuoUpdateResponseUnion = + | TwoFactorDuoUpdateResponse + | TwoFactorOrganizationDuoUpdateResponse; + // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ @@ -116,7 +125,7 @@ export class TwoFactorSetupDuoComponent } super.auth(this.data.authResponse); - this.processResponse(this.data.authResponse.response); + this.processGetResponse(this.data.authResponse.response); if (this.data.organizationId) { this.type = TwoFactorProviderType.OrganizationDuo; @@ -145,7 +154,7 @@ export class TwoFactorSetupDuoComponent this.userVerificationToken, ); - let response: TwoFactorDuoResponse; + let response: TwoFactorDuoUpdateResponseUnion; if (this.organizationId != null) { response = await this.twoFactorService.putTwoFactorOrganizationDuo( @@ -156,7 +165,7 @@ export class TwoFactorSetupDuoComponent response = await this.twoFactorService.putTwoFactorDuo(request); } - this.processResponse(response); + this.processUpdateResponse(response); this.onUpdated.emit(true); } @@ -192,14 +201,20 @@ export class TwoFactorSetupDuoComponent void this.dialogRef.close(this.enabled); }; - private processResponse(response: TwoFactorDuoResponse) { - this.clientId = response.clientId; - this.clientSecret = response.clientSecret; - this.host = response.host; - this.enabled = response.enabled; - if (response.userVerificationToken) { - this.userVerificationToken = response.userVerificationToken; - } + private processGetResponse(response: TwoFactorDuoResponseUnion) { + this.userVerificationToken = response.userVerificationToken; + this.applyDuoState(response.duo); + } + + private processUpdateResponse(response: TwoFactorDuoUpdateResponseUnion) { + this.applyDuoState(response.duo); + } + + private applyDuoState(duo: TwoFactorDuoDetailsResponse) { + this.clientId = duo.clientId; + this.clientSecret = duo.clientSecret; + this.host = duo.host; + this.enabled = duo.enabled; } /** @@ -219,6 +234,6 @@ export class TwoFactorSetupDuoComponent } type TwoFactorDuoComponentConfig = { - authResponse: AuthResponse; + authResponse: AuthResponse; organizationId?: string; }; 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 94c61654ec04..42a4753938d8 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,6 +9,8 @@ import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-p import { TwoFactorEmailDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-email-delete.request"; import { TwoFactorEmailSetupRequest } from "@bitwarden/common/auth/models/request/two-factor-email-setup.request"; import { TwoFactorEmailUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-email-update.request"; +import { TwoFactorEmailDetailsResponse } from "@bitwarden/common/auth/models/response/two-factor-email-details.response"; +import { TwoFactorEmailUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-email-update.response"; import { TwoFactorEmailResponse } from "@bitwarden/common/auth/models/response/two-factor-email.response"; import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; @@ -112,7 +114,7 @@ export class TwoFactorSetupEmailComponent auth(authResponse: AuthResponse) { super.auth(authResponse); - return this.processResponse(authResponse.response); + return this.processGetResponse(authResponse.response); } submit = async () => { @@ -149,7 +151,7 @@ export class TwoFactorSetupEmailComponent ); const response = await this.twoFactorService.putTwoFactorEmail(request); - await this.processResponse(response); + await this.processUpdateResponse(response); this.onUpdated.emit(true); } @@ -179,13 +181,19 @@ export class TwoFactorSetupEmailComponent void this.dialogRef.close(this.enabled); }; - private async processResponse(response: TwoFactorEmailResponse) { + private async processGetResponse(response: TwoFactorEmailResponse) { + this.userVerificationToken = response.userVerificationToken; + await this.applyEmailState(response.email); + } + + private async processUpdateResponse(response: TwoFactorEmailUpdateResponse) { + await this.applyEmailState(response.email); + } + + private async applyEmailState(emailData: TwoFactorEmailDetailsResponse) { this.token = null; - this.email = response.email; - this.enabled = response.enabled; - if (response.userVerificationToken) { - this.userVerificationToken = response.userVerificationToken; - } + this.email = emailData.email; + this.enabled = emailData.enabled; if (!this.enabled && (this.email == null || this.email === "")) { this.email = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.email)), 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 ec4bb51764dd..06505bfa755e 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 @@ -10,10 +10,11 @@ import { TwoFactorWebAuthnDeleteAllRequest } from "@bitwarden/common/auth/models import { TwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-delete.request"; import { TwoFactorWebAuthnUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-update.request"; import { TwoFactorWebAuthnChallengeResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-challenge.response"; -import { - ChallengeResponse, - TwoFactorWebAuthnResponse, -} from "@bitwarden/common/auth/models/response/two-factor-web-authn.response"; +import { TwoFactorWebAuthnDeleteResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-delete.response"; +import { TwoFactorWebAuthnDetailsResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-details.response"; +import { TwoFactorWebAuthnUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-update.response"; +import { TwoFactorWebAuthnResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn.response"; +import { WebAuthnChallengeResponse } from "@bitwarden/common/auth/models/response/web-authn-challenge.response"; 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"; @@ -43,7 +44,7 @@ interface Key { name: string; configured: boolean; migrated?: boolean; - removePromise: Promise | null; + removePromise: Promise | null; } // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush @@ -111,7 +112,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom auth(authResponse: AuthResponse) { super.auth(authResponse); - this.processResponse(authResponse.response); + this.processGetResponse(authResponse.response); } submit = async () => { @@ -135,13 +136,13 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom ); const response = await this.twoFactorService.putTwoFactorWebAuthn(request); - this.processResponse(response); + this.processUpdateResponse(response); this.toastService.showToast({ title: this.i18nService.t("success"), message: this.i18nService.t("twoFactorProviderEnabled"), variant: "success", }); - this.onUpdated.emit(response.enabled); + this.onUpdated.emit(response.webAuthn.enabled); } disable = async () => { @@ -196,7 +197,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom key.removePromise = this.twoFactorService.deleteTwoFactorWebAuthn(request); const response = await key.removePromise; key.removePromise = null; - await this.processResponse(response); + this.processDeleteResponse(response); } catch (e) { this.logService.error(e); } @@ -217,7 +218,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom this.readDevice(wrappedChallenge.options); }; - private readDevice(webAuthnChallenge: ChallengeResponse) { + private readDevice(webAuthnChallenge: WebAuthnChallengeResponse) { // eslint-disable-next-line console.log("listening for key..."); this.resetWebAuthn(true); @@ -259,9 +260,22 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom throw new Error("Unable to find next available key ID"); } - private processResponse(response: TwoFactorWebAuthnResponse) { - if (!response.keys || response.keys.length === 0) { - response.keys = []; + private processGetResponse(response: TwoFactorWebAuthnResponse) { + this.userVerificationToken = response.userVerificationToken; + this.applyWebAuthnState(response.webAuthn); + } + + private processUpdateResponse(response: TwoFactorWebAuthnUpdateResponse) { + this.applyWebAuthnState(response.webAuthn); + } + + private processDeleteResponse(response: TwoFactorWebAuthnDeleteResponse) { + this.applyWebAuthnState(response.webAuthn); + } + + private applyWebAuthnState(webAuthn: TwoFactorWebAuthnDetailsResponse) { + if (!webAuthn.keys || webAuthn.keys.length === 0) { + webAuthn.keys = []; } this.resetWebAuthn(); this.keys = []; @@ -274,7 +288,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom this.keysConfiguredCount = 0; // Build configured keys - for (const key of response.keys) { + for (const key of webAuthn.keys) { this.keysConfiguredCount++; this.keys.push({ id: key.id, @@ -291,7 +305,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom // While we don't have any technical constraints _at this time_, we should avoid // unbounded growth of key IDs over time as users add/remove keys; // this strategy gap-fills key IDs. - const existingIds = new Set(response.keys.map((k) => k.id)); + const existingIds = new Set(webAuthn.keys.map((k) => k.id)); const nextId = this.findNextAvailableKeyId(existingIds); // Add unconfigured slot, which can be used to add a new key @@ -303,10 +317,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom }); this.keyIdAvailable = nextId; - this.enabled = response.enabled; - if (response.userVerificationToken) { - this.userVerificationToken = response.userVerificationToken; - } + this.enabled = webAuthn.enabled; this.onUpdated.emit(this.enabled); } 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 a70b1785d93b..c9ef4a021b6e 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,6 +13,8 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-delete.request"; import { TwoFactorYubiKeyUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-update.request"; +import { TwoFactorYubiKeyDetailsResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key-details.response"; +import { TwoFactorYubiKeyUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key-update.response"; import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key.response"; import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; @@ -139,7 +141,7 @@ export class TwoFactorSetupYubiKeyComponent auth(authResponse: AuthResponse) { super.auth(authResponse); - this.processResponse(authResponse.response); + this.processGetResponse(authResponse.response); } submit = async () => { @@ -178,7 +180,7 @@ export class TwoFactorSetupYubiKeyComponent this.userVerificationToken, ); - this.processResponse(await this.twoFactorService.putTwoFactorYubiKey(request)); + this.processUpdateResponse(await this.twoFactorService.putTwoFactorYubiKey(request)); this.refreshFormArrayData(); this.toastService.showToast({ title: this.i18nService.t("success"), @@ -224,18 +226,24 @@ export class TwoFactorSetupYubiKeyComponent }); } - private processResponse(response: TwoFactorYubiKeyResponse) { - this.enabled = response.enabled; - this.anyKeyHasNfc = response.nfc || !response.enabled; - if (response.userVerificationToken) { - this.userVerificationToken = response.userVerificationToken; - } + private processGetResponse(response: TwoFactorYubiKeyResponse) { + this.userVerificationToken = response.userVerificationToken; + this.applyYubiKeyState(response.yubiKey); + } + + private processUpdateResponse(response: TwoFactorYubiKeyUpdateResponse) { + this.applyYubiKeyState(response.yubiKey); + } + + private applyYubiKeyState(yubiKey: TwoFactorYubiKeyDetailsResponse) { + this.enabled = yubiKey.enabled; + this.anyKeyHasNfc = yubiKey.nfc || !yubiKey.enabled; this.keys = [ - { key: response.key1, existingKey: this.padRight(response.key1) }, - { key: response.key2, existingKey: this.padRight(response.key2) }, - { key: response.key3, existingKey: this.padRight(response.key3) }, - { key: response.key4, existingKey: this.padRight(response.key4) }, - { key: response.key5, existingKey: this.padRight(response.key5) }, + { key: yubiKey.key1, existingKey: this.padRight(yubiKey.key1) }, + { key: yubiKey.key2, existingKey: this.padRight(yubiKey.key2) }, + { key: yubiKey.key3, existingKey: this.padRight(yubiKey.key3) }, + { key: yubiKey.key4, existingKey: this.padRight(yubiKey.key4) }, + { key: yubiKey.key5, existingKey: this.padRight(yubiKey.key5) }, ]; } 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 f8ca1092be98..9689bab0c00f 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 @@ -1,14 +1,20 @@ import { BaseResponse } from "../../../models/response/base.response"; +import { TwoFactorAuthenticatorDetailsResponse } from "./two-factor-authenticator-details.response"; + +/** + * Response for `POST /two-factor/get-authenticator`. Wraps the provider details and the + * user-verification token minted by the GET endpoint. + */ export class TwoFactorAuthenticatorResponse extends BaseResponse { - enabled: boolean; - key: string; + authenticator: TwoFactorAuthenticatorDetailsResponse; userVerificationToken: string; constructor(response: any) { super(response); - this.enabled = this.getResponseProperty("Enabled"); - this.key = this.getResponseProperty("Key"); + this.authenticator = new TwoFactorAuthenticatorDetailsResponse( + this.getResponseProperty("Authenticator"), + ); this.userVerificationToken = this.getResponseProperty("UserVerificationToken"); } } diff --git a/libs/common/src/auth/models/response/two-factor-duo.response.ts b/libs/common/src/auth/models/response/two-factor-duo.response.ts index 36ca6c63fcca..df17b8a115cb 100644 --- a/libs/common/src/auth/models/response/two-factor-duo.response.ts +++ b/libs/common/src/auth/models/response/two-factor-duo.response.ts @@ -1,18 +1,18 @@ import { BaseResponse } from "../../../models/response/base.response"; +import { TwoFactorDuoDetailsResponse } from "./two-factor-duo-details.response"; + +/** + * Response for `POST /two-factor/get-duo`. Wraps the user-scoped Duo provider details + * and the user-verification token minted by the GET endpoint. + */ export class TwoFactorDuoResponse extends BaseResponse { - enabled: boolean; - host: string; - clientSecret: string; - clientId: string; + duo: TwoFactorDuoDetailsResponse; userVerificationToken: string; constructor(response: any) { super(response); - this.enabled = this.getResponseProperty("Enabled"); - this.host = this.getResponseProperty("Host"); - this.clientSecret = this.getResponseProperty("ClientSecret"); - this.clientId = this.getResponseProperty("ClientId"); + this.duo = new TwoFactorDuoDetailsResponse(this.getResponseProperty("Duo")); this.userVerificationToken = this.getResponseProperty("UserVerificationToken"); } } diff --git a/libs/common/src/auth/models/response/two-factor-email.response.ts b/libs/common/src/auth/models/response/two-factor-email.response.ts index 7462ae8ae11a..fa809991ab9b 100644 --- a/libs/common/src/auth/models/response/two-factor-email.response.ts +++ b/libs/common/src/auth/models/response/two-factor-email.response.ts @@ -1,14 +1,18 @@ import { BaseResponse } from "../../../models/response/base.response"; +import { TwoFactorEmailDetailsResponse } from "./two-factor-email-details.response"; + +/** + * Response for `POST /two-factor/get-email`. Wraps the provider details and the + * user-verification token minted by the GET endpoint. + */ export class TwoFactorEmailResponse extends BaseResponse { - enabled: boolean; - email: string; + email: TwoFactorEmailDetailsResponse; userVerificationToken: string; constructor(response: any) { super(response); - this.enabled = this.getResponseProperty("Enabled"); - this.email = this.getResponseProperty("Email"); + this.email = new TwoFactorEmailDetailsResponse(this.getResponseProperty("Email")); this.userVerificationToken = this.getResponseProperty("UserVerificationToken"); } } diff --git a/libs/common/src/auth/models/response/two-factor-web-authn.response.ts b/libs/common/src/auth/models/response/two-factor-web-authn.response.ts index 17e2a98ef002..a83dccce1f4e 100644 --- a/libs/common/src/auth/models/response/two-factor-web-authn.response.ts +++ b/libs/common/src/auth/models/response/two-factor-web-authn.response.ts @@ -1,61 +1,18 @@ import { BaseResponse } from "../../../models/response/base.response"; -import { Utils } from "../../../platform/misc/utils"; +import { TwoFactorWebAuthnDetailsResponse } from "./two-factor-web-authn-details.response"; + +/** + * Response for `POST /two-factor/get-webauthn`. Wraps the provider details and the + * user-verification token minted by the GET endpoint. + */ export class TwoFactorWebAuthnResponse extends BaseResponse { - enabled: boolean; - keys: KeyResponse[]; + webAuthn: TwoFactorWebAuthnDetailsResponse; userVerificationToken: string; constructor(response: any) { super(response); - this.enabled = this.getResponseProperty("Enabled"); - const keys = this.getResponseProperty("Keys"); - this.keys = keys == null ? null : keys.map((k: any) => new KeyResponse(k)); + this.webAuthn = new TwoFactorWebAuthnDetailsResponse(this.getResponseProperty("WebAuthn")); this.userVerificationToken = this.getResponseProperty("UserVerificationToken"); } } - -export class KeyResponse extends BaseResponse { - name: string; - id: number; - migrated: boolean; - - constructor(response: any) { - super(response); - this.name = this.getResponseProperty("Name"); - this.id = this.getResponseProperty("Id"); - this.migrated = this.getResponseProperty("Migrated"); - } -} - -export class ChallengeResponse extends BaseResponse implements PublicKeyCredentialCreationOptions { - attestation?: AttestationConveyancePreference; - authenticatorSelection?: AuthenticatorSelectionCriteria; - challenge: BufferSource; - excludeCredentials?: PublicKeyCredentialDescriptor[]; - extensions?: AuthenticationExtensionsClientInputs; - pubKeyCredParams: PublicKeyCredentialParameters[]; - rp: PublicKeyCredentialRpEntity; - timeout?: number; - user: PublicKeyCredentialUserEntity; - - constructor(response: any) { - super(response); - this.attestation = this.getResponseProperty("attestation"); - this.authenticatorSelection = this.getResponseProperty("authenticatorSelection"); - this.challenge = Utils.fromUrlB64ToArray(this.getResponseProperty("challenge")) as BufferSource; - this.excludeCredentials = this.getResponseProperty("excludeCredentials").map((c: any) => { - c.id = Utils.fromUrlB64ToArray(c.id).buffer; - return c; - }); - this.extensions = this.getResponseProperty("extensions"); - this.pubKeyCredParams = this.getResponseProperty("pubKeyCredParams"); - this.rp = this.getResponseProperty("rp"); - this.timeout = this.getResponseProperty("timeout"); - - const user = this.getResponseProperty("user"); - user.id = Utils.fromUrlB64ToArray(user.id); - - this.user = user; - } -} diff --git a/libs/common/src/auth/models/response/two-factor-yubi-key.response.ts b/libs/common/src/auth/models/response/two-factor-yubi-key.response.ts index 45a4e4f92d37..c6d81b7b9d3b 100644 --- a/libs/common/src/auth/models/response/two-factor-yubi-key.response.ts +++ b/libs/common/src/auth/models/response/two-factor-yubi-key.response.ts @@ -1,24 +1,18 @@ import { BaseResponse } from "../../../models/response/base.response"; +import { TwoFactorYubiKeyDetailsResponse } from "./two-factor-yubi-key-details.response"; + +/** + * Response for `POST /two-factor/get-yubikey`. Wraps the provider details and the + * user-verification token minted by the GET endpoint. + */ export class TwoFactorYubiKeyResponse extends BaseResponse { - enabled: boolean; - key1: string; - key2: string; - key3: string; - key4: string; - key5: string; - nfc: boolean; + yubiKey: TwoFactorYubiKeyDetailsResponse; userVerificationToken: string; constructor(response: any) { super(response); - this.enabled = this.getResponseProperty("Enabled"); - this.key1 = this.getResponseProperty("Key1"); - this.key2 = this.getResponseProperty("Key2"); - this.key3 = this.getResponseProperty("Key3"); - this.key4 = this.getResponseProperty("Key4"); - this.key5 = this.getResponseProperty("Key5"); - this.nfc = this.getResponseProperty("Nfc"); + this.yubiKey = new TwoFactorYubiKeyDetailsResponse(this.getResponseProperty("YubiKey")); this.userVerificationToken = this.getResponseProperty("UserVerificationToken"); } } diff --git a/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts b/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts index a1133e956a0a..86c2bf05b8b1 100644 --- a/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts +++ b/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts @@ -13,13 +13,21 @@ import { TwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/models/re import { TwoFactorWebAuthnUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-update.request"; import { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-delete.request"; import { TwoFactorYubiKeyUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-update.request"; +import { TwoFactorAuthenticatorUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator-update.response"; import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator.response"; +import { TwoFactorDuoUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-duo-update.response"; import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-duo.response"; +import { TwoFactorEmailUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-email-update.response"; import { TwoFactorEmailResponse } from "@bitwarden/common/auth/models/response/two-factor-email.response"; +import { TwoFactorOrganizationDuoUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-organization-duo-update.response"; +import { TwoFactorOrganizationDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-organization-duo.response"; import { TwoFactorProviderResponse } from "@bitwarden/common/auth/models/response/two-factor-provider.response"; import { TwoFactorRecoverResponse } from "@bitwarden/common/auth/models/response/two-factor-recover.response"; import { TwoFactorWebAuthnChallengeResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-challenge.response"; +import { TwoFactorWebAuthnDeleteResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-delete.response"; +import { TwoFactorWebAuthnUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-update.response"; import { TwoFactorWebAuthnResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn.response"; +import { TwoFactorYubiKeyUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key-update.response"; import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key.response"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; @@ -95,7 +103,7 @@ export abstract class TwoFactorApiService { abstract getTwoFactorOrganizationDuo( organizationId: string, request: SecretVerificationRequest, - ): Promise; + ): Promise; /** * Gets the YubiKey OTP two-factor configuration for the current user. @@ -159,18 +167,17 @@ export abstract class TwoFactorApiService { */ abstract putTwoFactorAuthenticator( request: TwoFactorAuthenticatorUpdateRequest, - ): Promise; + ): Promise; /** * Removes the authenticator (TOTP) two-factor enrollment for the current user. - * Requires a user verification token to confirm the operation. + * Requires a user verification token to confirm the operation. Returns 204 No Content. * * @param request The request containing the user verification token and key. - * @returns A promise that resolves to the updated provider status. */ abstract deleteTwoFactorAuthenticator( request: DeleteTwoFactorAuthenticatorRequest, - ): Promise; + ): Promise; /** * Enables or updates the email two-factor provider. @@ -179,18 +186,17 @@ export abstract class TwoFactorApiService { * @param request The request containing the email configuration and verification token. * @returns A promise that resolves to the updated email two-factor configuration. */ - abstract putTwoFactorEmail(request: TwoFactorEmailUpdateRequest): Promise; + abstract putTwoFactorEmail( + request: TwoFactorEmailUpdateRequest, + ): Promise; /** * Removes the email two-factor enrollment for the current user. - * Requires a user verification token to confirm the operation. + * Requires a user verification token to confirm the operation. Returns 204 No Content. * * @param request The request containing the user verification token. - * @returns A promise that resolves to the updated provider status. */ - abstract deleteTwoFactorEmail( - request: TwoFactorEmailDeleteRequest, - ): Promise; + abstract deleteTwoFactorEmail(request: TwoFactorEmailDeleteRequest): Promise; /** * Enables or updates the Duo two-factor provider for the current user. @@ -200,19 +206,16 @@ export abstract class TwoFactorApiService { * @param request The request containing the Duo integration configuration. * @returns A promise that resolves to the updated Duo configuration. */ - abstract putTwoFactorDuo(request: TwoFactorDuoUpdateRequest): Promise; + abstract putTwoFactorDuo(request: TwoFactorDuoUpdateRequest): Promise; /** * Removes the Duo two-factor enrollment for the current user. - * Requires a user verification token to confirm the operation. + * Requires a user verification token to confirm the operation. Returns 204 No Content. * Does NOT require premium — disabling must always be available even if premium has lapsed. * * @param request The request containing the user verification token. - * @returns A promise that resolves to the updated provider status. */ - abstract deleteTwoFactorDuo( - request: TwoFactorDuoDeleteRequest, - ): Promise; + abstract deleteTwoFactorDuo(request: TwoFactorDuoDeleteRequest): Promise; /** * Enables or updates the Duo two-factor provider for an organization. @@ -226,21 +229,20 @@ export abstract class TwoFactorApiService { abstract putTwoFactorOrganizationDuo( organizationId: string, request: TwoFactorDuoUpdateRequest, - ): Promise; + ): Promise; /** - * Removes the Duo two-factor enrollment for an organization. + * Removes the Duo two-factor enrollment for an organization. Returns 204 No Content. * Requires a user verification token to confirm the operation and * organization policy management permissions. * * @param organizationId The ID of the organization. * @param request The request containing the user verification token. - * @returns A promise that resolves to the updated provider status. */ abstract deleteTwoFactorOrganizationDuo( organizationId: string, request: TwoFactorOrganizationDuoDeleteRequest, - ): Promise; + ): Promise; /** * Enables or updates the YubiKey OTP two-factor provider. @@ -253,19 +255,16 @@ export abstract class TwoFactorApiService { */ abstract putTwoFactorYubiKey( request: TwoFactorYubiKeyUpdateRequest, - ): Promise; + ): Promise; /** - * Removes the YubiKey two-factor enrollment for the current user. + * Removes the YubiKey two-factor enrollment for the current user. Returns 204 No Content. * Requires a user verification token to confirm the operation. * Does NOT require premium — disabling must always be available even if premium has lapsed. * * @param request The request containing the user verification token. - * @returns A promise that resolves to the updated provider status. */ - abstract deleteTwoFactorYubiKey( - request: TwoFactorYubiKeyDeleteRequest, - ): Promise; + abstract deleteTwoFactorYubiKey(request: TwoFactorYubiKeyDeleteRequest): Promise; /** * Registers a new WebAuthn (FIDO2) credential for two-factor authentication. @@ -277,32 +276,32 @@ export abstract class TwoFactorApiService { */ abstract putTwoFactorWebAuthn( request: TwoFactorWebAuthnUpdateRequest, - ): Promise; + ): 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. * Server refuses to remove the last registered credential — use deleteTwoFactorWebAuthnAll instead. + * The operation modifies the WebAuthn provider rather than destroying it, so the response + * carries the updated parent state. * * @param request The request containing the credential ID and verification token. * @returns A promise that resolves to the updated WebAuthn configuration. */ abstract deleteTwoFactorWebAuthn( request: TwoFactorWebAuthnDeleteRequest, - ): Promise; + ): Promise; /** * Removes the entire WebAuthn (FIDO2) two-factor enrollment for the current user — all * credentials are removed and the provider is disabled in a single round-trip. The only path * that can clear the last registered credential, since per-credential delete refuses by design. + * Returns 204 No Content. * * @param request The request containing the user verification token. - * @returns A promise that resolves to the updated provider status. */ - abstract deleteTwoFactorWebAuthnAll( - request: TwoFactorWebAuthnDeleteAllRequest, - ): Promise; + abstract deleteTwoFactorWebAuthnAll(request: TwoFactorWebAuthnDeleteAllRequest): Promise; /** * Initiates email two-factor setup by sending a verification code to the specified email address. 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 index ae24b2321cd2..3ec9bdaab5e6 100644 --- a/libs/common/src/auth/two-factor/abstractions/two-factor.service.ts +++ b/libs/common/src/auth/two-factor/abstractions/two-factor.service.ts @@ -17,13 +17,21 @@ import { TwoFactorWebAuthnUpdateRequest } from "../../models/request/two-factor- import { TwoFactorYubiKeyDeleteRequest } from "../../models/request/two-factor-yubikey-delete.request"; import { TwoFactorYubiKeyUpdateRequest } from "../../models/request/two-factor-yubikey-update.request"; import { IdentityTwoFactorResponse } from "../../models/response/identity-two-factor.response"; +import { TwoFactorAuthenticatorUpdateResponse } from "../../models/response/two-factor-authenticator-update.response"; import { TwoFactorAuthenticatorResponse } from "../../models/response/two-factor-authenticator.response"; +import { TwoFactorDuoUpdateResponse } from "../../models/response/two-factor-duo-update.response"; import { TwoFactorDuoResponse } from "../../models/response/two-factor-duo.response"; +import { TwoFactorEmailUpdateResponse } from "../../models/response/two-factor-email-update.response"; import { TwoFactorEmailResponse } from "../../models/response/two-factor-email.response"; +import { TwoFactorOrganizationDuoUpdateResponse } from "../../models/response/two-factor-organization-duo-update.response"; +import { TwoFactorOrganizationDuoResponse } from "../../models/response/two-factor-organization-duo.response"; import { TwoFactorProviderResponse } from "../../models/response/two-factor-provider.response"; import { TwoFactorRecoverResponse } from "../../models/response/two-factor-recover.response"; import { TwoFactorWebAuthnChallengeResponse } from "../../models/response/two-factor-web-authn-challenge.response"; +import { TwoFactorWebAuthnDeleteResponse } from "../../models/response/two-factor-web-authn-delete.response"; +import { TwoFactorWebAuthnUpdateResponse } from "../../models/response/two-factor-web-authn-update.response"; import { TwoFactorWebAuthnResponse } from "../../models/response/two-factor-web-authn.response"; +import { TwoFactorYubiKeyUpdateResponse } from "../../models/response/two-factor-yubi-key-update.response"; import { TwoFactorYubiKeyResponse } from "../../models/response/two-factor-yubi-key.response"; /** @@ -272,7 +280,7 @@ export abstract class TwoFactorService { abstract getTwoFactorOrganizationDuo( organizationId: string, request: SecretVerificationRequest, - ): Promise; + ): Promise; /** * Gets the YubiKey OTP two-factor configuration for the current user from the API. @@ -341,19 +349,18 @@ export abstract class TwoFactorService { */ abstract putTwoFactorAuthenticator( request: TwoFactorAuthenticatorUpdateRequest, - ): Promise; + ): Promise; /** * Removes the authenticator (TOTP) two-factor enrollment for the current user. - * Requires a user verification token to confirm the operation. + * Requires a user verification token to confirm the operation. Returns 204 No Content. * Used for settings management. * * @param request The {@link DeleteTwoFactorAuthenticatorRequest} containing the user verification token and key. - * @returns A promise that resolves to the updated provider status. */ abstract deleteTwoFactorAuthenticator( request: DeleteTwoFactorAuthenticatorRequest, - ): Promise; + ): Promise; /** * Enables or updates the email two-factor provider for the current user. @@ -365,7 +372,9 @@ export abstract class TwoFactorService { * @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: TwoFactorEmailUpdateRequest): Promise; + abstract putTwoFactorEmail( + request: TwoFactorEmailUpdateRequest, + ): Promise; /** * Enables or updates the Duo two-factor provider for the current user. @@ -377,7 +386,7 @@ export abstract class TwoFactorService { * @returns A promise that resolves to the updated Duo configuration. * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. */ - abstract putTwoFactorDuo(request: TwoFactorDuoUpdateRequest): Promise; + abstract putTwoFactorDuo(request: TwoFactorDuoUpdateRequest): Promise; /** * Enables or updates the Duo two-factor provider for an organization. @@ -393,7 +402,7 @@ export abstract class TwoFactorService { abstract putTwoFactorOrganizationDuo( organizationId: string, request: TwoFactorDuoUpdateRequest, - ): Promise; + ): Promise; /** * Enables or updates the YubiKey OTP two-factor provider for the current user. @@ -408,7 +417,7 @@ export abstract class TwoFactorService { */ abstract putTwoFactorYubiKey( request: TwoFactorYubiKeyUpdateRequest, - ): Promise; + ): Promise; /** * Registers a new WebAuthn (FIDO2) credential for two-factor authentication for the current user. @@ -423,7 +432,7 @@ export abstract class TwoFactorService { */ abstract putTwoFactorWebAuthn( request: TwoFactorWebAuthnUpdateRequest, - ): Promise; + ): Promise; /** * Removes a specific WebAuthn (FIDO2) credential from the user's account. @@ -438,73 +447,61 @@ export abstract class TwoFactorService { */ abstract deleteTwoFactorWebAuthn( request: TwoFactorWebAuthnDeleteRequest, - ): Promise; + ): Promise; /** * Removes the YubiKey two-factor enrollment for the current user. - * Requires a user verification token to confirm the operation. + * Requires a user verification token to confirm the operation. Returns 204 No Content. * Does NOT require premium — disabling must always be available even if premium has lapsed. * Used for settings management. * * @param request The {@link TwoFactorYubiKeyDeleteRequest} containing the user verification token. - * @returns A promise that resolves to the updated provider status. */ - abstract deleteTwoFactorYubiKey( - request: TwoFactorYubiKeyDeleteRequest, - ): Promise; + abstract deleteTwoFactorYubiKey(request: TwoFactorYubiKeyDeleteRequest): Promise; /** * Removes the Duo two-factor enrollment for the current user. - * Requires a user verification token to confirm the operation. + * Requires a user verification token to confirm the operation. Returns 204 No Content. * Does NOT require premium — disabling must always be available even if premium has lapsed. * Used for settings management. * * @param request The {@link TwoFactorDuoDeleteRequest} containing the user verification token. - * @returns A promise that resolves to the updated provider status. */ - abstract deleteTwoFactorDuo( - request: TwoFactorDuoDeleteRequest, - ): Promise; + abstract deleteTwoFactorDuo(request: TwoFactorDuoDeleteRequest): Promise; /** * Removes the email two-factor enrollment for the current user. - * Requires a user verification token to confirm the operation. + * Requires a user verification token to confirm the operation. Returns 204 No Content. * Used for settings management. * * @param request The {@link TwoFactorEmailDeleteRequest} containing the user verification token. - * @returns A promise that resolves to the updated provider status. */ - abstract deleteTwoFactorEmail( - request: TwoFactorEmailDeleteRequest, - ): Promise; + abstract deleteTwoFactorEmail(request: TwoFactorEmailDeleteRequest): Promise; /** - * Removes the Duo two-factor enrollment for an organization. + * Removes the Duo two-factor enrollment for an organization. Returns 204 No Content. * Requires a user verification token to confirm the operation and * organization policy management permissions. * Used for settings management. * * @param organizationId The ID of the organization. * @param request The {@link TwoFactorOrganizationDuoDeleteRequest} containing the user verification token. - * @returns A promise that resolves to the updated provider status. */ abstract deleteTwoFactorOrganizationDuo( organizationId: string, request: TwoFactorOrganizationDuoDeleteRequest, - ): Promise; + ): Promise; /** * Removes the entire WebAuthn (FIDO2) two-factor enrollment for the current user — all - * credentials are removed and the provider is disabled in a single round-trip. The only path - * that can clear the last registered credential, since per-credential delete refuses by design. + * credentials are removed and the provider is disabled in a single round-trip. Returns + * 204 No Content. The only path that can clear the last registered credential, since + * per-credential delete refuses by design. * Used for settings management. * * @param request The {@link TwoFactorWebAuthnDeleteAllRequest} containing the user verification token. - * @returns A promise that resolves to the updated provider status. */ - abstract deleteTwoFactorWebAuthnAll( - request: TwoFactorWebAuthnDeleteAllRequest, - ): Promise; + abstract deleteTwoFactorWebAuthnAll(request: TwoFactorWebAuthnDeleteAllRequest): Promise; /** * Initiates email two-factor setup by sending a verification code to the specified email address. diff --git a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts index db5d0a523f0b..baf29cf33984 100644 --- a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts +++ b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts @@ -16,13 +16,21 @@ import { TwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/models/re import { TwoFactorWebAuthnUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-update.request"; import { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-delete.request"; import { TwoFactorYubiKeyUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-update.request"; +import { TwoFactorAuthenticatorUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator-update.response"; import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator.response"; +import { TwoFactorDuoUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-duo-update.response"; import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-duo.response"; +import { TwoFactorEmailUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-email-update.response"; import { TwoFactorEmailResponse } from "@bitwarden/common/auth/models/response/two-factor-email.response"; +import { TwoFactorOrganizationDuoUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-organization-duo-update.response"; +import { TwoFactorOrganizationDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-organization-duo.response"; import { TwoFactorProviderResponse } from "@bitwarden/common/auth/models/response/two-factor-provider.response"; import { TwoFactorRecoverResponse } from "@bitwarden/common/auth/models/response/two-factor-recover.response"; import { TwoFactorWebAuthnChallengeResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-challenge.response"; +import { TwoFactorWebAuthnDeleteResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-delete.response"; +import { TwoFactorWebAuthnUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-update.response"; import { TwoFactorWebAuthnResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn.response"; +import { TwoFactorYubiKeyUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key-update.response"; import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key.response"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; @@ -92,8 +100,11 @@ describe("TwoFactorApiService", () => { const request = new SecretVerificationRequest(); request.masterPasswordHash = "master-password-hash"; const mockResponse = { - Enabled: false, - Key: "MFRGGZDFMZTWQ2LK", + Authenticator: { + Enabled: false, + Key: "MFRGGZDFMZTWQ2LK", + }, + UserVerificationToken: "uv-token", }; apiService.send.mockResolvedValue(mockResponse); @@ -107,7 +118,9 @@ describe("TwoFactorApiService", () => { true, ); expect(result).toBeInstanceOf(TwoFactorAuthenticatorResponse); - expect(result.enabled).toBe(false); + expect(result.authenticator.enabled).toBe(false); + expect(result.authenticator.key).toBe("MFRGGZDFMZTWQ2LK"); + expect(result.userVerificationToken).toBe("uv-token"); }); }); @@ -115,8 +128,10 @@ describe("TwoFactorApiService", () => { it("enables authenticator after validating the provided token", async () => { const request = new TwoFactorAuthenticatorUpdateRequest("123456", "MFRGGZDFMZTWQ2LK", ""); const mockResponse = { - Enabled: true, - Key: "MFRGGZDFMZTWQ2LK", + Authenticator: { + Enabled: true, + Key: "MFRGGZDFMZTWQ2LK", + }, }; apiService.send.mockResolvedValue(mockResponse); @@ -129,33 +144,25 @@ describe("TwoFactorApiService", () => { true, true, ); - expect(result).toBeInstanceOf(TwoFactorAuthenticatorResponse); - expect(result.enabled).toBe(true); - expect(result.key).toBeDefined(); + expect(result).toBeInstanceOf(TwoFactorAuthenticatorUpdateResponse); + expect(result.authenticator.enabled).toBe(true); + expect(result.authenticator.key).toBeDefined(); }); }); describe("deleteTwoFactorAuthenticator", () => { - it("disables authenticator two-factor authentication", async () => { + it("disables authenticator two-factor authentication and expects no body", async () => { const request = new DeleteTwoFactorAuthenticatorRequest("MFRGGZDFMZTWQ2LK", "uv-token"); - const mockResponse = { - Enabled: false, - Type: 0, - }; - apiService.send.mockResolvedValue(mockResponse); - const result = await twoFactorApiService.deleteTwoFactorAuthenticator(request); + await twoFactorApiService.deleteTwoFactorAuthenticator(request); expect(apiService.send).toHaveBeenCalledWith( "DELETE", "/two-factor/authenticator", request, true, - true, + false, ); - expect(result).toBeInstanceOf(TwoFactorProviderResponse); - expect(result.enabled).toBe(false); - expect(result.type).toBe(0); // Authenticator }); }); }); @@ -166,8 +173,11 @@ describe("TwoFactorApiService", () => { const request = new SecretVerificationRequest(); request.masterPasswordHash = "master-password-hash"; const mockResponse = { - Enabled: true, - Email: "user@example.com", + Email: { + Enabled: true, + Email: "user@example.com", + }, + UserVerificationToken: "uv-token", }; apiService.send.mockResolvedValue(mockResponse); @@ -181,8 +191,9 @@ describe("TwoFactorApiService", () => { true, ); expect(result).toBeInstanceOf(TwoFactorEmailResponse); - expect(result.enabled).toBe(true); - expect(result.email).toBeDefined(); + expect(result.email.enabled).toBe(true); + expect(result.email.email).toBe("user@example.com"); + expect(result.userVerificationToken).toBe("uv-token"); }); }); @@ -230,8 +241,10 @@ describe("TwoFactorApiService", () => { "", ); const mockResponse = { - Enabled: true, - Email: "user@example.com", + Email: { + Enabled: true, + Email: "user@example.com", + }, }; apiService.send.mockResolvedValue(mockResponse); @@ -244,9 +257,9 @@ describe("TwoFactorApiService", () => { true, true, ); - expect(result).toBeInstanceOf(TwoFactorEmailResponse); - expect(result.enabled).toBe(true); - expect(result.email).toBeDefined(); + expect(result).toBeInstanceOf(TwoFactorEmailUpdateResponse); + expect(result.email.enabled).toBe(true); + expect(result.email.email).toBe("user@example.com"); }); }); }); @@ -257,10 +270,13 @@ describe("TwoFactorApiService", () => { const request = new SecretVerificationRequest(); request.masterPasswordHash = "master-password-hash"; const mockResponse = { - Enabled: true, - Host: "api-abc123.duosecurity.com", - ClientId: "DI9ABC1DEFGH2JKL", - ClientSecret: "client******", + Duo: { + Enabled: true, + Host: "api-abc123.duosecurity.com", + ClientId: "DI9ABC1DEFGH2JKL", + ClientSecret: "client******", + }, + UserVerificationToken: "uv-token", }; apiService.send.mockResolvedValue(mockResponse); @@ -274,10 +290,11 @@ describe("TwoFactorApiService", () => { true, ); expect(result).toBeInstanceOf(TwoFactorDuoResponse); - expect(result.enabled).toBe(true); - expect(result.host).toBeDefined(); - expect(result.clientId).toBeDefined(); - expect(result.clientSecret).toContain("******"); + expect(result.duo.enabled).toBe(true); + expect(result.duo.host).toBe("api-abc123.duosecurity.com"); + expect(result.duo.clientId).toBe("DI9ABC1DEFGH2JKL"); + expect(result.duo.clientSecret).toContain("******"); + expect(result.userVerificationToken).toBe("uv-token"); }); }); @@ -287,10 +304,13 @@ describe("TwoFactorApiService", () => { const request = new SecretVerificationRequest(); request.masterPasswordHash = "master-password-hash"; const mockResponse = { - Enabled: true, - Host: "api-xyz789.duosecurity.com", - ClientId: "DI4XYZ9MNOP3QRS", - ClientSecret: "orgcli******", + Duo: { + Enabled: true, + Host: "api-xyz789.duosecurity.com", + ClientId: "DI4XYZ9MNOP3QRS", + ClientSecret: "orgcli******", + }, + UserVerificationToken: "uv-token", }; apiService.send.mockResolvedValue(mockResponse); @@ -306,11 +326,12 @@ describe("TwoFactorApiService", () => { true, true, ); - expect(result).toBeInstanceOf(TwoFactorDuoResponse); - expect(result.enabled).toBe(true); - expect(result.host).toBeDefined(); - expect(result.clientId).toBeDefined(); - expect(result.clientSecret).toContain("******"); + expect(result).toBeInstanceOf(TwoFactorOrganizationDuoResponse); + expect(result.duo.enabled).toBe(true); + expect(result.duo.host).toBe("api-xyz789.duosecurity.com"); + expect(result.duo.clientId).toBe("DI4XYZ9MNOP3QRS"); + expect(result.duo.clientSecret).toContain("******"); + expect(result.userVerificationToken).toBe("uv-token"); }); }); @@ -323,21 +344,23 @@ describe("TwoFactorApiService", () => { "", ); const mockResponse = { - Enabled: true, - Host: "api-abc123.duosecurity.com", - ClientId: "DI9ABC1DEFGH2JKL", - ClientSecret: "client******", + Duo: { + Enabled: true, + Host: "api-abc123.duosecurity.com", + ClientId: "DI9ABC1DEFGH2JKL", + ClientSecret: "client******", + }, }; apiService.send.mockResolvedValue(mockResponse); const result = await twoFactorApiService.putTwoFactorDuo(request); expect(apiService.send).toHaveBeenCalledWith("PUT", "/two-factor/duo", request, true, true); - expect(result).toBeInstanceOf(TwoFactorDuoResponse); - expect(result.enabled).toBe(true); - expect(result.host).toBeDefined(); - expect(result.clientId).toBeDefined(); - expect(result.clientSecret).toContain("******"); + expect(result).toBeInstanceOf(TwoFactorDuoUpdateResponse); + expect(result.duo.enabled).toBe(true); + expect(result.duo.host).toBeDefined(); + expect(result.duo.clientId).toBeDefined(); + expect(result.duo.clientSecret).toContain("******"); }); }); @@ -351,10 +374,12 @@ describe("TwoFactorApiService", () => { "", ); const mockResponse = { - Enabled: true, - Host: "api-xyz789.duosecurity.com", - ClientId: "DI4XYZ9MNOP3QRS", - ClientSecret: "orgcli******", + Duo: { + Enabled: true, + Host: "api-xyz789.duosecurity.com", + ClientId: "DI4XYZ9MNOP3QRS", + ClientSecret: "orgcli******", + }, }; apiService.send.mockResolvedValue(mockResponse); @@ -370,11 +395,11 @@ describe("TwoFactorApiService", () => { true, true, ); - expect(result).toBeInstanceOf(TwoFactorDuoResponse); - expect(result.enabled).toBe(true); - expect(result.host).toBeDefined(); - expect(result.clientId).toBeDefined(); - expect(result.clientSecret).toContain("******"); + expect(result).toBeInstanceOf(TwoFactorOrganizationDuoUpdateResponse); + expect(result.duo.enabled).toBe(true); + expect(result.duo.host).toBeDefined(); + expect(result.duo.clientId).toBeDefined(); + expect(result.duo.clientSecret).toContain("******"); }); }); }); @@ -385,9 +410,12 @@ describe("TwoFactorApiService", () => { const request = new SecretVerificationRequest(); request.masterPasswordHash = "master-password-hash"; const mockResponse = { - Enabled: true, - Key1: "cccccccccccc", - Key2: "dddddddddddd", + YubiKey: { + Enabled: true, + Key1: "cccccccccccc", + Key2: "dddddddddddd", + }, + UserVerificationToken: "uv-token", }; apiService.send.mockResolvedValue(mockResponse); @@ -401,9 +429,10 @@ describe("TwoFactorApiService", () => { true, ); expect(result).toBeInstanceOf(TwoFactorYubiKeyResponse); - expect(result.enabled).toBe(true); - expect(result.key1).toBeDefined(); - expect(result.key2).toBeDefined(); + expect(result.yubiKey.enabled).toBe(true); + expect(result.yubiKey.key1).toBe("cccccccccccc"); + expect(result.yubiKey.key2).toBe("dddddddddddd"); + expect(result.userVerificationToken).toBe("uv-token"); }); }); @@ -419,10 +448,12 @@ describe("TwoFactorApiService", () => { "", ); const mockResponse = { - Enabled: true, - Key1: "cccccccccccc", - Key2: "dddddddddddd", - Nfc: false, + YubiKey: { + Enabled: true, + Key1: "cccccccccccc", + Key2: "dddddddddddd", + Nfc: false, + }, }; apiService.send.mockResolvedValue(mockResponse); @@ -435,10 +466,10 @@ describe("TwoFactorApiService", () => { true, true, ); - expect(result).toBeInstanceOf(TwoFactorYubiKeyResponse); - expect(result.enabled).toBe(true); - expect(result.key1).toBeDefined(); - expect(result.key2).toBeDefined(); + expect(result).toBeInstanceOf(TwoFactorYubiKeyUpdateResponse); + expect(result.yubiKey.enabled).toBe(true); + expect(result.yubiKey.key1).toBeDefined(); + expect(result.yubiKey.key2).toBeDefined(); }); }); }); @@ -449,11 +480,14 @@ describe("TwoFactorApiService", () => { const request = new SecretVerificationRequest(); request.masterPasswordHash = "master-password-hash"; const mockResponse = { - Enabled: true, - Keys: [ - { Name: "YubiKey 5", Id: 1, Migrated: false }, - { Name: "Security Key", Id: 2, Migrated: true }, - ], + WebAuthn: { + Enabled: true, + Keys: [ + { Name: "YubiKey 5", Id: 1, Migrated: false }, + { Name: "Security Key", Id: 2, Migrated: true }, + ], + }, + UserVerificationToken: "uv-token", }; apiService.send.mockResolvedValue(mockResponse); @@ -467,13 +501,14 @@ describe("TwoFactorApiService", () => { true, ); expect(result).toBeInstanceOf(TwoFactorWebAuthnResponse); - expect(result.enabled).toBe(true); - expect(result.keys).toHaveLength(2); - result.keys.forEach((key) => { + expect(result.webAuthn.enabled).toBe(true); + expect(result.webAuthn.keys).toHaveLength(2); + result.webAuthn.keys.forEach((key) => { expect(key).toHaveProperty("name"); expect(key).toHaveProperty("id"); expect(key).toHaveProperty("migrated"); }); + expect(result.userVerificationToken).toBe("uv-token"); }); }); @@ -537,8 +572,10 @@ describe("TwoFactorApiService", () => { ); const mockResponse = { - Enabled: true, - Keys: [{ Name: "My Security Key", Id: 1, Migrated: false }], + WebAuthn: { + Enabled: true, + Keys: [{ Name: "My Security Key", Id: 1, Migrated: false }], + }, }; apiService.send.mockResolvedValue(mockResponse); @@ -563,12 +600,12 @@ describe("TwoFactorApiService", () => { true, true, ); - expect(result).toBeInstanceOf(TwoFactorWebAuthnResponse); - expect(result.enabled).toBe(true); - expect(result.keys).toHaveLength(1); - expect(result.keys[0].name).toBeDefined(); - expect(result.keys[0].id).toBeDefined(); - expect(result.keys[0].migrated).toBeDefined(); + expect(result).toBeInstanceOf(TwoFactorWebAuthnUpdateResponse); + expect(result.webAuthn.enabled).toBe(true); + expect(result.webAuthn.keys).toHaveLength(1); + expect(result.webAuthn.keys[0].name).toBeDefined(); + expect(result.webAuthn.keys[0].id).toBeDefined(); + expect(result.webAuthn.keys[0].migrated).toBeDefined(); }); it("preserves original request object without mutation during serialization", async () => { @@ -592,7 +629,7 @@ describe("TwoFactorApiService", () => { ); const originalDeviceResponse = request.deviceResponse; - apiService.send.mockResolvedValue({ enabled: true, keys: [] }); + apiService.send.mockResolvedValue({ WebAuthn: { Enabled: true, Keys: [] } }); await twoFactorApiService.putTwoFactorWebAuthn(request); @@ -606,8 +643,10 @@ describe("TwoFactorApiService", () => { it("removes specific WebAuthn credential while preserving other registered keys", async () => { const request = new TwoFactorWebAuthnDeleteRequest(1, "uv-token"); const mockResponse = { - Enabled: true, - Keys: [{ Name: "Security Key", Id: 2, Migrated: true }], // Key with id:1 removed + WebAuthn: { + Enabled: true, + Keys: [{ Name: "Security Key", Id: 2, Migrated: true }], // Key with id:1 removed + }, }; apiService.send.mockResolvedValue(mockResponse); @@ -620,9 +659,9 @@ describe("TwoFactorApiService", () => { true, true, ); - expect(result).toBeInstanceOf(TwoFactorWebAuthnResponse); - expect(result.keys).toHaveLength(1); - expect(result.keys[0].id).toBe(2); + expect(result).toBeInstanceOf(TwoFactorWebAuthnDeleteResponse); + expect(result.webAuthn.keys).toHaveLength(1); + expect(result.webAuthn.keys[0].id).toBe(2); }); }); }); @@ -655,111 +694,83 @@ describe("TwoFactorApiService", () => { describe("Per-provider Delete APIs", () => { describe("deleteTwoFactorYubiKey", () => { - it("removes YubiKey two-factor enrollment for the current user", async () => { + it("removes YubiKey two-factor enrollment for the current user and expects no body", async () => { const request = new TwoFactorYubiKeyDeleteRequest("uv-token"); - const mockResponse = { Enabled: false, Type: 3 }; // YubiKey - apiService.send.mockResolvedValue(mockResponse); - const result = await twoFactorApiService.deleteTwoFactorYubiKey(request); + await twoFactorApiService.deleteTwoFactorYubiKey(request); expect(apiService.send).toHaveBeenCalledWith( "DELETE", "/two-factor/yubikey", request, true, - true, + false, ); - expect(result).toBeInstanceOf(TwoFactorProviderResponse); - expect(result.enabled).toBe(false); - expect(result.type).toBe(3); }); }); describe("deleteTwoFactorDuo", () => { - it("removes Duo two-factor enrollment for the current user", async () => { + it("removes Duo two-factor enrollment for the current user and expects no body", async () => { const request = new TwoFactorDuoDeleteRequest("uv-token"); - const mockResponse = { Enabled: false, Type: 2 }; // Duo - apiService.send.mockResolvedValue(mockResponse); - const result = await twoFactorApiService.deleteTwoFactorDuo(request); + await twoFactorApiService.deleteTwoFactorDuo(request); expect(apiService.send).toHaveBeenCalledWith( "DELETE", "/two-factor/duo", request, true, - true, + false, ); - expect(result).toBeInstanceOf(TwoFactorProviderResponse); - expect(result.enabled).toBe(false); - expect(result.type).toBe(2); }); }); describe("deleteTwoFactorEmail", () => { - it("removes email two-factor enrollment for the current user", async () => { + it("removes email two-factor enrollment for the current user and expects no body", async () => { const request = new TwoFactorEmailDeleteRequest("uv-token"); - const mockResponse = { Enabled: false, Type: 1 }; // Email - apiService.send.mockResolvedValue(mockResponse); - const result = await twoFactorApiService.deleteTwoFactorEmail(request); + await twoFactorApiService.deleteTwoFactorEmail(request); expect(apiService.send).toHaveBeenCalledWith( "DELETE", "/two-factor/email", request, true, - true, + false, ); - expect(result).toBeInstanceOf(TwoFactorProviderResponse); - expect(result.enabled).toBe(false); - expect(result.type).toBe(1); }); }); describe("deleteTwoFactorOrganizationDuo", () => { - it("removes Duo two-factor enrollment for an organization with policy management permissions", async () => { + it("removes Duo two-factor enrollment for an organization and expects no body", async () => { const organizationId = "org-123"; const request = new TwoFactorOrganizationDuoDeleteRequest("uv-token"); - const mockResponse = { Enabled: false, Type: 6 }; // OrganizationDuo - apiService.send.mockResolvedValue(mockResponse); - const result = await twoFactorApiService.deleteTwoFactorOrganizationDuo( - organizationId, - request, - ); + await twoFactorApiService.deleteTwoFactorOrganizationDuo(organizationId, request); expect(apiService.send).toHaveBeenCalledWith( "DELETE", `/organizations/${organizationId}/two-factor/duo`, request, true, - true, + false, ); - expect(result).toBeInstanceOf(TwoFactorProviderResponse); - expect(result.enabled).toBe(false); - expect(result.type).toBe(6); }); }); describe("deleteTwoFactorWebAuthnAll", () => { - it("removes the entire WebAuthn enrollment in a single round-trip", async () => { + it("removes the entire WebAuthn enrollment in a single round-trip and expects no body", async () => { const request = new TwoFactorWebAuthnDeleteAllRequest("uv-token"); - const mockResponse = { Enabled: false, Type: 7 }; // WebAuthn - apiService.send.mockResolvedValue(mockResponse); - const result = await twoFactorApiService.deleteTwoFactorWebAuthnAll(request); + await twoFactorApiService.deleteTwoFactorWebAuthnAll(request); expect(apiService.send).toHaveBeenCalledWith( "DELETE", "/two-factor/webauthn/all", request, true, - true, + false, ); - expect(result).toBeInstanceOf(TwoFactorProviderResponse); - expect(result.enabled).toBe(false); - expect(result.type).toBe(7); }); }); }); diff --git a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts index 711b7cbbed7e..f808ee118033 100644 --- a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts +++ b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts @@ -14,13 +14,21 @@ import { TwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/models/re import { TwoFactorWebAuthnUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-update.request"; import { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-delete.request"; import { TwoFactorYubiKeyUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-update.request"; +import { TwoFactorAuthenticatorUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator-update.response"; import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator.response"; +import { TwoFactorDuoUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-duo-update.response"; import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-duo.response"; +import { TwoFactorEmailUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-email-update.response"; import { TwoFactorEmailResponse } from "@bitwarden/common/auth/models/response/two-factor-email.response"; +import { TwoFactorOrganizationDuoUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-organization-duo-update.response"; +import { TwoFactorOrganizationDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-organization-duo.response"; import { TwoFactorProviderResponse } from "@bitwarden/common/auth/models/response/two-factor-provider.response"; import { TwoFactorRecoverResponse } from "@bitwarden/common/auth/models/response/two-factor-recover.response"; import { TwoFactorWebAuthnChallengeResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-challenge.response"; +import { TwoFactorWebAuthnDeleteResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-delete.response"; +import { TwoFactorWebAuthnUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-update.response"; import { TwoFactorWebAuthnResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn.response"; +import { TwoFactorYubiKeyUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key-update.response"; import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key.response"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -67,7 +75,7 @@ export class DefaultTwoFactorApiService implements TwoFactorApiService { async putTwoFactorAuthenticator( request: TwoFactorAuthenticatorUpdateRequest, - ): Promise { + ): Promise { const response = await this.apiService.send( "PUT", "/two-factor/authenticator", @@ -75,20 +83,11 @@ export class DefaultTwoFactorApiService implements TwoFactorApiService { true, true, ); - return new TwoFactorAuthenticatorResponse(response); + return new TwoFactorAuthenticatorUpdateResponse(response); } - async deleteTwoFactorAuthenticator( - request: DeleteTwoFactorAuthenticatorRequest, - ): Promise { - const response = await this.apiService.send( - "DELETE", - "/two-factor/authenticator", - request, - true, - true, - ); - return new TwoFactorProviderResponse(response); + async deleteTwoFactorAuthenticator(request: DeleteTwoFactorAuthenticatorRequest): Promise { + await this.apiService.send("DELETE", "/two-factor/authenticator", request, true, false); } // Email @@ -112,16 +111,15 @@ export class DefaultTwoFactorApiService implements TwoFactorApiService { return this.apiService.send("POST", "/two-factor/send-email-login", request, false, false); } - async putTwoFactorEmail(request: TwoFactorEmailUpdateRequest): Promise { + async putTwoFactorEmail( + request: TwoFactorEmailUpdateRequest, + ): Promise { const response = await this.apiService.send("PUT", "/two-factor/email", request, true, true); - return new TwoFactorEmailResponse(response); + return new TwoFactorEmailUpdateResponse(response); } - async deleteTwoFactorEmail( - request: TwoFactorEmailDeleteRequest, - ): Promise { - const response = await this.apiService.send("DELETE", "/two-factor/email", request, true, true); - return new TwoFactorProviderResponse(response); + async deleteTwoFactorEmail(request: TwoFactorEmailDeleteRequest): Promise { + await this.apiService.send("DELETE", "/two-factor/email", request, true, false); } // Duo @@ -134,7 +132,7 @@ export class DefaultTwoFactorApiService implements TwoFactorApiService { async getTwoFactorOrganizationDuo( organizationId: string, request: SecretVerificationRequest, - ): Promise { + ): Promise { const response = await this.apiService.send( "POST", `/organizations/${organizationId}/two-factor/get-duo`, @@ -142,23 +140,22 @@ export class DefaultTwoFactorApiService implements TwoFactorApiService { true, true, ); - return new TwoFactorDuoResponse(response); + return new TwoFactorOrganizationDuoResponse(response); } - async putTwoFactorDuo(request: TwoFactorDuoUpdateRequest): Promise { + async putTwoFactorDuo(request: TwoFactorDuoUpdateRequest): Promise { const response = await this.apiService.send("PUT", "/two-factor/duo", request, true, true); - return new TwoFactorDuoResponse(response); + return new TwoFactorDuoUpdateResponse(response); } - async deleteTwoFactorDuo(request: TwoFactorDuoDeleteRequest): Promise { - const response = await this.apiService.send("DELETE", "/two-factor/duo", request, true, true); - return new TwoFactorProviderResponse(response); + async deleteTwoFactorDuo(request: TwoFactorDuoDeleteRequest): Promise { + await this.apiService.send("DELETE", "/two-factor/duo", request, true, false); } async putTwoFactorOrganizationDuo( organizationId: string, request: TwoFactorDuoUpdateRequest, - ): Promise { + ): Promise { const response = await this.apiService.send( "PUT", `/organizations/${organizationId}/two-factor/duo`, @@ -166,21 +163,20 @@ export class DefaultTwoFactorApiService implements TwoFactorApiService { true, true, ); - return new TwoFactorDuoResponse(response); + return new TwoFactorOrganizationDuoUpdateResponse(response); } async deleteTwoFactorOrganizationDuo( organizationId: string, request: TwoFactorOrganizationDuoDeleteRequest, - ): Promise { - const response = await this.apiService.send( + ): Promise { + await this.apiService.send( "DELETE", `/organizations/${organizationId}/two-factor/duo`, request, true, - true, + false, ); - return new TwoFactorProviderResponse(response); } // YubiKey @@ -198,22 +194,13 @@ export class DefaultTwoFactorApiService implements TwoFactorApiService { async putTwoFactorYubiKey( request: TwoFactorYubiKeyUpdateRequest, - ): Promise { + ): Promise { const response = await this.apiService.send("PUT", "/two-factor/yubikey", request, true, true); - return new TwoFactorYubiKeyResponse(response); + return new TwoFactorYubiKeyUpdateResponse(response); } - async deleteTwoFactorYubiKey( - request: TwoFactorYubiKeyDeleteRequest, - ): Promise { - const response = await this.apiService.send( - "DELETE", - "/two-factor/yubikey", - request, - true, - true, - ); - return new TwoFactorProviderResponse(response); + async deleteTwoFactorYubiKey(request: TwoFactorYubiKeyDeleteRequest): Promise { + await this.apiService.send("DELETE", "/two-factor/yubikey", request, true, false); } // WebAuthn @@ -246,7 +233,7 @@ export class DefaultTwoFactorApiService implements TwoFactorApiService { async putTwoFactorWebAuthn( request: TwoFactorWebAuthnUpdateRequest, - ): Promise { + ): Promise { const deviceResponse = request.deviceResponse.response as AuthenticatorAttestationResponse; const body: any = Object.assign({}, request); @@ -262,12 +249,12 @@ export class DefaultTwoFactorApiService implements TwoFactorApiService { }; const response = await this.apiService.send("PUT", "/two-factor/webauthn", body, true, true); - return new TwoFactorWebAuthnResponse(response); + return new TwoFactorWebAuthnUpdateResponse(response); } async deleteTwoFactorWebAuthn( request: TwoFactorWebAuthnDeleteRequest, - ): Promise { + ): Promise { const response = await this.apiService.send( "DELETE", "/two-factor/webauthn", @@ -275,20 +262,11 @@ export class DefaultTwoFactorApiService implements TwoFactorApiService { true, true, ); - return new TwoFactorWebAuthnResponse(response); + return new TwoFactorWebAuthnDeleteResponse(response); } - async deleteTwoFactorWebAuthnAll( - request: TwoFactorWebAuthnDeleteAllRequest, - ): Promise { - const response = await this.apiService.send( - "DELETE", - "/two-factor/webauthn/all", - request, - true, - true, - ); - return new TwoFactorProviderResponse(response); + async deleteTwoFactorWebAuthnAll(request: TwoFactorWebAuthnDeleteAllRequest): Promise { + await this.apiService.send("DELETE", "/two-factor/webauthn/all", request, true, false); } // Recovery Code 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 index 1cea93564a88..80f69eb5fc32 100644 --- 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 @@ -25,13 +25,21 @@ import { TwoFactorWebAuthnUpdateRequest } from "../../models/request/two-factor- import { TwoFactorYubiKeyDeleteRequest } from "../../models/request/two-factor-yubikey-delete.request"; import { TwoFactorYubiKeyUpdateRequest } from "../../models/request/two-factor-yubikey-update.request"; import { IdentityTwoFactorResponse } from "../../models/response/identity-two-factor.response"; +import { TwoFactorAuthenticatorUpdateResponse } from "../../models/response/two-factor-authenticator-update.response"; import { TwoFactorAuthenticatorResponse } from "../../models/response/two-factor-authenticator.response"; +import { TwoFactorDuoUpdateResponse } from "../../models/response/two-factor-duo-update.response"; import { TwoFactorDuoResponse } from "../../models/response/two-factor-duo.response"; +import { TwoFactorEmailUpdateResponse } from "../../models/response/two-factor-email-update.response"; import { TwoFactorEmailResponse } from "../../models/response/two-factor-email.response"; +import { TwoFactorOrganizationDuoUpdateResponse } from "../../models/response/two-factor-organization-duo-update.response"; +import { TwoFactorOrganizationDuoResponse } from "../../models/response/two-factor-organization-duo.response"; import { TwoFactorProviderResponse } from "../../models/response/two-factor-provider.response"; import { TwoFactorRecoverResponse } from "../../models/response/two-factor-recover.response"; import { TwoFactorWebAuthnChallengeResponse } from "../../models/response/two-factor-web-authn-challenge.response"; +import { TwoFactorWebAuthnDeleteResponse } from "../../models/response/two-factor-web-authn-delete.response"; +import { TwoFactorWebAuthnUpdateResponse } from "../../models/response/two-factor-web-authn-update.response"; import { TwoFactorWebAuthnResponse } from "../../models/response/two-factor-web-authn.response"; +import { TwoFactorYubiKeyUpdateResponse } from "../../models/response/two-factor-yubi-key-update.response"; import { TwoFactorYubiKeyResponse } from "../../models/response/two-factor-yubi-key.response"; import { PROVIDERS, @@ -196,7 +204,7 @@ export class DefaultTwoFactorService implements TwoFactorServiceAbstraction { getTwoFactorOrganizationDuo( organizationId: string, request: SecretVerificationRequest, - ): Promise { + ): Promise { return this.twoFactorApiService.getTwoFactorOrganizationDuo(organizationId, request); } @@ -220,71 +228,67 @@ export class DefaultTwoFactorService implements TwoFactorServiceAbstraction { putTwoFactorAuthenticator( request: TwoFactorAuthenticatorUpdateRequest, - ): Promise { + ): Promise { return this.twoFactorApiService.putTwoFactorAuthenticator(request); } - deleteTwoFactorAuthenticator( - request: DeleteTwoFactorAuthenticatorRequest, - ): Promise { + deleteTwoFactorAuthenticator(request: DeleteTwoFactorAuthenticatorRequest): Promise { return this.twoFactorApiService.deleteTwoFactorAuthenticator(request); } - putTwoFactorEmail(request: TwoFactorEmailUpdateRequest): Promise { + putTwoFactorEmail(request: TwoFactorEmailUpdateRequest): Promise { return this.twoFactorApiService.putTwoFactorEmail(request); } - putTwoFactorDuo(request: TwoFactorDuoUpdateRequest): Promise { + putTwoFactorDuo(request: TwoFactorDuoUpdateRequest): Promise { return this.twoFactorApiService.putTwoFactorDuo(request); } putTwoFactorOrganizationDuo( organizationId: string, request: TwoFactorDuoUpdateRequest, - ): Promise { + ): Promise { return this.twoFactorApiService.putTwoFactorOrganizationDuo(organizationId, request); } - putTwoFactorYubiKey(request: TwoFactorYubiKeyUpdateRequest): Promise { + putTwoFactorYubiKey( + request: TwoFactorYubiKeyUpdateRequest, + ): Promise { return this.twoFactorApiService.putTwoFactorYubiKey(request); } putTwoFactorWebAuthn( request: TwoFactorWebAuthnUpdateRequest, - ): Promise { + ): Promise { return this.twoFactorApiService.putTwoFactorWebAuthn(request); } deleteTwoFactorWebAuthn( request: TwoFactorWebAuthnDeleteRequest, - ): Promise { + ): Promise { return this.twoFactorApiService.deleteTwoFactorWebAuthn(request); } - deleteTwoFactorYubiKey( - request: TwoFactorYubiKeyDeleteRequest, - ): Promise { + deleteTwoFactorYubiKey(request: TwoFactorYubiKeyDeleteRequest): Promise { return this.twoFactorApiService.deleteTwoFactorYubiKey(request); } - deleteTwoFactorDuo(request: TwoFactorDuoDeleteRequest): Promise { + deleteTwoFactorDuo(request: TwoFactorDuoDeleteRequest): Promise { return this.twoFactorApiService.deleteTwoFactorDuo(request); } - deleteTwoFactorEmail(request: TwoFactorEmailDeleteRequest): Promise { + deleteTwoFactorEmail(request: TwoFactorEmailDeleteRequest): Promise { return this.twoFactorApiService.deleteTwoFactorEmail(request); } deleteTwoFactorOrganizationDuo( organizationId: string, request: TwoFactorOrganizationDuoDeleteRequest, - ): Promise { + ): Promise { return this.twoFactorApiService.deleteTwoFactorOrganizationDuo(organizationId, request); } - deleteTwoFactorWebAuthnAll( - request: TwoFactorWebAuthnDeleteAllRequest, - ): Promise { + deleteTwoFactorWebAuthnAll(request: TwoFactorWebAuthnDeleteAllRequest): Promise { return this.twoFactorApiService.deleteTwoFactorWebAuthnAll(request); } diff --git a/libs/common/src/auth/types/two-factor-response.ts b/libs/common/src/auth/types/two-factor-response.ts index 2b2875b1bf96..69a95288106d 100644 --- a/libs/common/src/auth/types/two-factor-response.ts +++ b/libs/common/src/auth/types/two-factor-response.ts @@ -1,6 +1,7 @@ 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 { TwoFactorOrganizationDuoResponse } from "../models/response/two-factor-organization-duo.response"; import { TwoFactorRecoverResponse } from "../models/response/two-factor-recover.response"; import { TwoFactorWebAuthnResponse } from "../models/response/two-factor-web-authn.response"; import { TwoFactorYubiKeyResponse } from "../models/response/two-factor-yubi-key.response"; @@ -8,6 +9,7 @@ import { TwoFactorYubiKeyResponse } from "../models/response/two-factor-yubi-key export type TwoFactorResponse = | TwoFactorRecoverResponse | TwoFactorDuoResponse + | TwoFactorOrganizationDuoResponse | TwoFactorEmailResponse | TwoFactorWebAuthnResponse | TwoFactorAuthenticatorResponse From 7062529eb62a9cd99139b4998972f6e3267c4b8b Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Wed, 24 Jun 2026 17:30:09 -0400 Subject: [PATCH 13/29] PM-38137 - Move 2FA request/response models into two-factor feature folder Relocates 2FA request and response model files from the convention folders (libs/common/src/auth/models/{request,response}/) into the two-factor feature folder, per the libs/common/src/auth/CLAUDE.md rule that new auth code belongs in feature folders. Adds request/ and response/ barrels and re-exports them from the two-factor index. Updates consumer imports across web, cli, libs/auth, and libs/common. Leaves identity-two-factor.response.ts (login-flow response, identity-token group) and web-authn-challenge.response.ts (shared with webauthn-login) in place to avoid cross-feature coupling. --- apps/cli/src/auth/commands/login.command.ts | 2 +- .../settings/two-factor-setup.component.ts | 2 +- .../account/change-email.component.spec.ts | 2 +- .../two-factor-recovery.component.ts | 2 +- ...wo-factor-setup-authenticator.component.ts | 8 +-- .../two-factor-setup-duo.component.ts | 16 ++--- .../two-factor-setup-email.component.ts | 12 ++-- .../two-factor-setup-webauthn.component.ts | 16 ++--- .../two-factor-setup-yubikey.component.ts | 10 ++-- .../two-factor/two-factor-setup.component.ts | 14 ++--- .../two-factor-auth-email.component.ts | 2 +- .../abstractions/two-factor-api.service.ts | 60 +++++++++---------- .../abstractions/two-factor.service.ts | 60 +++++++++---------- libs/common/src/auth/two-factor/index.ts | 2 + ...delete-two-factor-authenticator.request.ts | 0 .../src/auth/two-factor/request/index.ts | 14 +++++ ...two-factor-authenticator-update.request.ts | 0 .../request/two-factor-duo-delete.request.ts | 0 .../request/two-factor-duo-update.request.ts | 0 .../two-factor-email-delete.request.ts | 0 .../request/two-factor-email-login.request.ts | 2 +- .../request/two-factor-email-setup.request.ts | 0 .../two-factor-email-update.request.ts | 0 ...-factor-organization-duo-delete.request.ts | 0 ...two-factor-web-authn-delete-all.request.ts | 0 .../two-factor-web-authn-delete.request.ts | 0 .../two-factor-web-authn-update.request.ts | 0 .../two-factor-yubikey-delete.request.ts | 0 .../two-factor-yubikey-update.request.ts | 0 .../src/auth/two-factor/response/index.ts | 21 +++++++ ...o-factor-authenticator-details.response.ts | 0 ...wo-factor-authenticator-update.response.ts | 0 .../two-factor-authenticator.response.ts | 0 .../two-factor-duo-details.response.ts | 0 .../two-factor-duo-update.response.ts | 0 .../response/two-factor-duo.response.ts | 0 .../two-factor-email-details.response.ts | 0 .../two-factor-email-update.response.ts | 0 .../response/two-factor-email.response.ts | 0 ...factor-organization-duo-update.response.ts | 0 .../two-factor-organization-duo.response.ts | 0 .../response/two-factor-provider.response.ts | 0 .../response/two-factor-recover.response.ts | 0 ...two-factor-web-authn-challenge.response.ts | 3 +- .../two-factor-web-authn-delete.response.ts | 0 .../two-factor-web-authn-details.response.ts | 0 .../two-factor-web-authn-update.response.ts | 0 .../response/two-factor-web-authn.response.ts | 0 .../two-factor-yubi-key-details.response.ts | 0 .../two-factor-yubi-key-update.response.ts | 0 .../response/two-factor-yubi-key.response.ts | 0 .../default-two-factor-api.service.spec.ts | 60 +++++++++---------- .../default-two-factor-api.service.ts | 60 +++++++++---------- .../services/default-two-factor.service.ts | 60 +++++++++---------- .../src/auth/types/two-factor-response.ts | 14 ++--- 55 files changed, 239 insertions(+), 203 deletions(-) rename libs/common/src/auth/{models => two-factor}/request/delete-two-factor-authenticator.request.ts (100%) create mode 100644 libs/common/src/auth/two-factor/request/index.ts rename libs/common/src/auth/{models => two-factor}/request/two-factor-authenticator-update.request.ts (100%) rename libs/common/src/auth/{models => two-factor}/request/two-factor-duo-delete.request.ts (100%) rename libs/common/src/auth/{models => two-factor}/request/two-factor-duo-update.request.ts (100%) rename libs/common/src/auth/{models => two-factor}/request/two-factor-email-delete.request.ts (100%) rename libs/common/src/auth/{models => two-factor}/request/two-factor-email-login.request.ts (74%) rename libs/common/src/auth/{models => two-factor}/request/two-factor-email-setup.request.ts (100%) rename libs/common/src/auth/{models => two-factor}/request/two-factor-email-update.request.ts (100%) rename libs/common/src/auth/{models => two-factor}/request/two-factor-organization-duo-delete.request.ts (100%) rename libs/common/src/auth/{models => two-factor}/request/two-factor-web-authn-delete-all.request.ts (100%) rename libs/common/src/auth/{models => two-factor}/request/two-factor-web-authn-delete.request.ts (100%) rename libs/common/src/auth/{models => two-factor}/request/two-factor-web-authn-update.request.ts (100%) rename libs/common/src/auth/{models => two-factor}/request/two-factor-yubikey-delete.request.ts (100%) rename libs/common/src/auth/{models => two-factor}/request/two-factor-yubikey-update.request.ts (100%) create mode 100644 libs/common/src/auth/two-factor/response/index.ts rename libs/common/src/auth/{models => two-factor}/response/two-factor-authenticator-details.response.ts (100%) rename libs/common/src/auth/{models => two-factor}/response/two-factor-authenticator-update.response.ts (100%) rename libs/common/src/auth/{models => two-factor}/response/two-factor-authenticator.response.ts (100%) rename libs/common/src/auth/{models => two-factor}/response/two-factor-duo-details.response.ts (100%) rename libs/common/src/auth/{models => two-factor}/response/two-factor-duo-update.response.ts (100%) rename libs/common/src/auth/{models => two-factor}/response/two-factor-duo.response.ts (100%) rename libs/common/src/auth/{models => two-factor}/response/two-factor-email-details.response.ts (100%) rename libs/common/src/auth/{models => two-factor}/response/two-factor-email-update.response.ts (100%) rename libs/common/src/auth/{models => two-factor}/response/two-factor-email.response.ts (100%) rename libs/common/src/auth/{models => two-factor}/response/two-factor-organization-duo-update.response.ts (100%) rename libs/common/src/auth/{models => two-factor}/response/two-factor-organization-duo.response.ts (100%) rename libs/common/src/auth/{models => two-factor}/response/two-factor-provider.response.ts (100%) rename libs/common/src/auth/{models => two-factor}/response/two-factor-recover.response.ts (100%) rename libs/common/src/auth/{models => two-factor}/response/two-factor-web-authn-challenge.response.ts (89%) rename libs/common/src/auth/{models => two-factor}/response/two-factor-web-authn-delete.response.ts (100%) rename libs/common/src/auth/{models => two-factor}/response/two-factor-web-authn-details.response.ts (100%) rename libs/common/src/auth/{models => two-factor}/response/two-factor-web-authn-update.response.ts (100%) rename libs/common/src/auth/{models => two-factor}/response/two-factor-web-authn.response.ts (100%) rename libs/common/src/auth/{models => two-factor}/response/two-factor-yubi-key-details.response.ts (100%) rename libs/common/src/auth/{models => two-factor}/response/two-factor-yubi-key-update.response.ts (100%) rename libs/common/src/auth/{models => two-factor}/response/two-factor-yubi-key.response.ts (100%) diff --git a/apps/cli/src/auth/commands/login.command.ts b/apps/cli/src/auth/commands/login.command.ts index 63881fe9de0a..d7ad16e5303c 100644 --- a/apps/cli/src/auth/commands/login.command.ts +++ b/apps/cli/src/auth/commands/login.command.ts @@ -25,8 +25,8 @@ import { 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 { TwoFactorEmailLoginRequest } from "@bitwarden/common/auth/models/request/two-factor-email-login.request"; import { TwoFactorService, TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorEmailLoginRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-login.request"; import { ClientType } from "@bitwarden/common/enums"; import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { EncryptedMigrator } from "@bitwarden/common/key-management/encrypted-migrator/encrypted-migrator.abstraction"; 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 ea7d1e7e1256..aafe040a83fd 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 @@ -13,9 +13,9 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli 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 { TwoFactorOrganizationDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-organization-duo.response"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorOrganizationDuoResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-organization-duo.response"; 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"; 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 20faa861fc09..834ce2e788b2 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 @@ -5,9 +5,9 @@ import { firstValueFrom } from "rxjs"; 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 { ChangeEmailService } from "@bitwarden/common/auth/services/change-email/change-email.service"; import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorProviderResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-provider.response"; 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"; diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-recovery.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-recovery.component.ts index 543c4236d893..4521d81d8466 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-recovery.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-recovery.component.ts @@ -2,7 +2,7 @@ import { CommonModule } from "@angular/common"; import { Component, Inject } from "@angular/core"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; -import { TwoFactorRecoverResponse } from "@bitwarden/common/auth/models/response/two-factor-recover.response"; +import { TwoFactorRecoverResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-recover.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ButtonModule, 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 6a1d4b3dc7ec..0ac7eb286370 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 @@ -9,11 +9,11 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; 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 { DeleteTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/delete-two-factor-authenticator.request"; -import { TwoFactorAuthenticatorUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-authenticator-update.request"; -import { TwoFactorAuthenticatorUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator-update.response"; -import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator.response"; import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; +import { DeleteTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/two-factor/request/delete-two-factor-authenticator.request"; +import { TwoFactorAuthenticatorUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-authenticator-update.request"; +import { TwoFactorAuthenticatorUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-authenticator-update.response"; +import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-authenticator.response"; 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"; 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 b92fbf6ba364..1f95d4abd2c0 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 @@ -4,15 +4,15 @@ import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; 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 { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-delete.request"; -import { TwoFactorDuoUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-update.request"; -import { TwoFactorOrganizationDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-organization-duo-delete.request"; -import { TwoFactorDuoDetailsResponse } from "@bitwarden/common/auth/models/response/two-factor-duo-details.response"; -import { TwoFactorDuoUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-duo-update.response"; -import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-duo.response"; -import { TwoFactorOrganizationDuoUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-organization-duo-update.response"; -import { TwoFactorOrganizationDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-organization-duo.response"; import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-duo-delete.request"; +import { TwoFactorDuoUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-duo-update.request"; +import { TwoFactorOrganizationDuoDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-organization-duo-delete.request"; +import { TwoFactorDuoDetailsResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-duo-details.response"; +import { TwoFactorDuoUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-duo-update.response"; +import { TwoFactorDuoResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-duo.response"; +import { TwoFactorOrganizationDuoUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-organization-duo-update.response"; +import { TwoFactorOrganizationDuoResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-organization-duo.response"; 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"; 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 42a4753938d8..6e83e950cfca 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 @@ -6,13 +6,13 @@ import { firstValueFrom, map } from "rxjs"; 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 { TwoFactorEmailDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-email-delete.request"; -import { TwoFactorEmailSetupRequest } from "@bitwarden/common/auth/models/request/two-factor-email-setup.request"; -import { TwoFactorEmailUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-email-update.request"; -import { TwoFactorEmailDetailsResponse } from "@bitwarden/common/auth/models/response/two-factor-email-details.response"; -import { TwoFactorEmailUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-email-update.response"; -import { TwoFactorEmailResponse } from "@bitwarden/common/auth/models/response/two-factor-email.response"; import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorEmailDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-delete.request"; +import { TwoFactorEmailSetupRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-setup.request"; +import { TwoFactorEmailUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-update.request"; +import { TwoFactorEmailDetailsResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-email-details.response"; +import { TwoFactorEmailUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-email-update.response"; +import { TwoFactorEmailResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-email.response"; 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"; 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 06505bfa755e..275502643c8a 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 @@ -6,16 +6,16 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; 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 { TwoFactorWebAuthnDeleteAllRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-delete-all.request"; -import { TwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-delete.request"; -import { TwoFactorWebAuthnUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-update.request"; -import { TwoFactorWebAuthnChallengeResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-challenge.response"; -import { TwoFactorWebAuthnDeleteResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-delete.response"; -import { TwoFactorWebAuthnDetailsResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-details.response"; -import { TwoFactorWebAuthnUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-update.response"; -import { TwoFactorWebAuthnResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn.response"; import { WebAuthnChallengeResponse } from "@bitwarden/common/auth/models/response/web-authn-challenge.response"; import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorWebAuthnDeleteAllRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-delete-all.request"; +import { TwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-delete.request"; +import { TwoFactorWebAuthnUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-update.request"; +import { TwoFactorWebAuthnChallengeResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn-challenge.response"; +import { TwoFactorWebAuthnDeleteResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn-delete.response"; +import { TwoFactorWebAuthnDetailsResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn-details.response"; +import { TwoFactorWebAuthnUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn-update.response"; +import { TwoFactorWebAuthnResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn.response"; 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"; 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 c9ef4a021b6e..bcb06b292dc2 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 @@ -11,12 +11,12 @@ import { import { JslibModule } from "@bitwarden/angular/jslib.module"; 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 { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-delete.request"; -import { TwoFactorYubiKeyUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-update.request"; -import { TwoFactorYubiKeyDetailsResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key-details.response"; -import { TwoFactorYubiKeyUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key-update.response"; -import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key.response"; import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-yubikey-delete.request"; +import { TwoFactorYubiKeyUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-yubikey-update.request"; +import { TwoFactorYubiKeyDetailsResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-yubi-key-details.response"; +import { TwoFactorYubiKeyUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-yubi-key-update.response"; +import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-yubi-key.response"; 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"; 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 1528faa40af5..1a07d6b3d7a5 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 @@ -21,15 +21,15 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv 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 { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-delete.request"; -import { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-delete.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 { TwoFactorService, TwoFactorProviders } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-duo-delete.request"; +import { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-yubikey-delete.request"; +import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-authenticator.response"; +import { TwoFactorDuoResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-duo.response"; +import { TwoFactorEmailResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-email.response"; +import { TwoFactorWebAuthnResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn.response"; +import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-yubi-key.response"; 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"; 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 090f20a4224b..20fee2d2a9c2 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 @@ -5,8 +5,8 @@ import { ReactiveFormsModule, FormsModule, FormControl } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; -import { TwoFactorEmailLoginRequest } from "@bitwarden/common/auth/models/request/two-factor-email-login.request"; import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorEmailLoginRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-login.request"; 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"; diff --git a/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts b/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts index 86c2bf05b8b1..926f255e0f4f 100644 --- a/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts +++ b/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts @@ -1,34 +1,34 @@ -import { DeleteTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/delete-two-factor-authenticator.request"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; -import { TwoFactorAuthenticatorUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-authenticator-update.request"; -import { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-delete.request"; -import { TwoFactorDuoUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-update.request"; -import { TwoFactorEmailDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-email-delete.request"; -import { TwoFactorEmailLoginRequest } from "@bitwarden/common/auth/models/request/two-factor-email-login.request"; -import { TwoFactorEmailSetupRequest } from "@bitwarden/common/auth/models/request/two-factor-email-setup.request"; -import { TwoFactorEmailUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-email-update.request"; -import { TwoFactorOrganizationDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-organization-duo-delete.request"; -import { TwoFactorWebAuthnDeleteAllRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-delete-all.request"; -import { TwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-delete.request"; -import { TwoFactorWebAuthnUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-update.request"; -import { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-delete.request"; -import { TwoFactorYubiKeyUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-update.request"; -import { TwoFactorAuthenticatorUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator-update.response"; -import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator.response"; -import { TwoFactorDuoUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-duo-update.response"; -import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-duo.response"; -import { TwoFactorEmailUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-email-update.response"; -import { TwoFactorEmailResponse } from "@bitwarden/common/auth/models/response/two-factor-email.response"; -import { TwoFactorOrganizationDuoUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-organization-duo-update.response"; -import { TwoFactorOrganizationDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-organization-duo.response"; -import { TwoFactorProviderResponse } from "@bitwarden/common/auth/models/response/two-factor-provider.response"; -import { TwoFactorRecoverResponse } from "@bitwarden/common/auth/models/response/two-factor-recover.response"; -import { TwoFactorWebAuthnChallengeResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-challenge.response"; -import { TwoFactorWebAuthnDeleteResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-delete.response"; -import { TwoFactorWebAuthnUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-update.response"; -import { TwoFactorWebAuthnResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn.response"; -import { TwoFactorYubiKeyUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key-update.response"; -import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key.response"; +import { DeleteTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/two-factor/request/delete-two-factor-authenticator.request"; +import { TwoFactorAuthenticatorUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-authenticator-update.request"; +import { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-duo-delete.request"; +import { TwoFactorDuoUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-duo-update.request"; +import { TwoFactorEmailDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-delete.request"; +import { TwoFactorEmailLoginRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-login.request"; +import { TwoFactorEmailSetupRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-setup.request"; +import { TwoFactorEmailUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-update.request"; +import { TwoFactorOrganizationDuoDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-organization-duo-delete.request"; +import { TwoFactorWebAuthnDeleteAllRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-delete-all.request"; +import { TwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-delete.request"; +import { TwoFactorWebAuthnUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-update.request"; +import { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-yubikey-delete.request"; +import { TwoFactorYubiKeyUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-yubikey-update.request"; +import { TwoFactorAuthenticatorUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-authenticator-update.response"; +import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-authenticator.response"; +import { TwoFactorDuoUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-duo-update.response"; +import { TwoFactorDuoResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-duo.response"; +import { TwoFactorEmailUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-email-update.response"; +import { TwoFactorEmailResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-email.response"; +import { TwoFactorOrganizationDuoUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-organization-duo-update.response"; +import { TwoFactorOrganizationDuoResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-organization-duo.response"; +import { TwoFactorProviderResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-provider.response"; +import { TwoFactorRecoverResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-recover.response"; +import { TwoFactorWebAuthnChallengeResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn-challenge.response"; +import { TwoFactorWebAuthnDeleteResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn-delete.response"; +import { TwoFactorWebAuthnUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn-update.response"; +import { TwoFactorWebAuthnResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn.response"; +import { TwoFactorYubiKeyUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-yubi-key-update.response"; +import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-yubi-key.response"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; /** 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 index 3ec9bdaab5e6..6782201b6a25 100644 --- a/libs/common/src/auth/two-factor/abstractions/two-factor.service.ts +++ b/libs/common/src/auth/two-factor/abstractions/two-factor.service.ts @@ -1,38 +1,38 @@ import { ListResponse } from "../../../models/response/list.response"; import { KeyDefinition, TWO_FACTOR_MEMORY } from "../../../platform/state"; import { TwoFactorProviderType } from "../../enums/two-factor-provider-type"; -import { DeleteTwoFactorAuthenticatorRequest } from "../../models/request/delete-two-factor-authenticator.request"; import { SecretVerificationRequest } from "../../models/request/secret-verification.request"; -import { TwoFactorAuthenticatorUpdateRequest } from "../../models/request/two-factor-authenticator-update.request"; -import { TwoFactorDuoDeleteRequest } from "../../models/request/two-factor-duo-delete.request"; -import { TwoFactorDuoUpdateRequest } from "../../models/request/two-factor-duo-update.request"; -import { TwoFactorEmailDeleteRequest } from "../../models/request/two-factor-email-delete.request"; -import { TwoFactorEmailLoginRequest } from "../../models/request/two-factor-email-login.request"; -import { TwoFactorEmailSetupRequest } from "../../models/request/two-factor-email-setup.request"; -import { TwoFactorEmailUpdateRequest } from "../../models/request/two-factor-email-update.request"; -import { TwoFactorOrganizationDuoDeleteRequest } from "../../models/request/two-factor-organization-duo-delete.request"; -import { TwoFactorWebAuthnDeleteAllRequest } from "../../models/request/two-factor-web-authn-delete-all.request"; -import { TwoFactorWebAuthnDeleteRequest } from "../../models/request/two-factor-web-authn-delete.request"; -import { TwoFactorWebAuthnUpdateRequest } from "../../models/request/two-factor-web-authn-update.request"; -import { TwoFactorYubiKeyDeleteRequest } from "../../models/request/two-factor-yubikey-delete.request"; -import { TwoFactorYubiKeyUpdateRequest } from "../../models/request/two-factor-yubikey-update.request"; import { IdentityTwoFactorResponse } from "../../models/response/identity-two-factor.response"; -import { TwoFactorAuthenticatorUpdateResponse } from "../../models/response/two-factor-authenticator-update.response"; -import { TwoFactorAuthenticatorResponse } from "../../models/response/two-factor-authenticator.response"; -import { TwoFactorDuoUpdateResponse } from "../../models/response/two-factor-duo-update.response"; -import { TwoFactorDuoResponse } from "../../models/response/two-factor-duo.response"; -import { TwoFactorEmailUpdateResponse } from "../../models/response/two-factor-email-update.response"; -import { TwoFactorEmailResponse } from "../../models/response/two-factor-email.response"; -import { TwoFactorOrganizationDuoUpdateResponse } from "../../models/response/two-factor-organization-duo-update.response"; -import { TwoFactorOrganizationDuoResponse } from "../../models/response/two-factor-organization-duo.response"; -import { TwoFactorProviderResponse } from "../../models/response/two-factor-provider.response"; -import { TwoFactorRecoverResponse } from "../../models/response/two-factor-recover.response"; -import { TwoFactorWebAuthnChallengeResponse } from "../../models/response/two-factor-web-authn-challenge.response"; -import { TwoFactorWebAuthnDeleteResponse } from "../../models/response/two-factor-web-authn-delete.response"; -import { TwoFactorWebAuthnUpdateResponse } from "../../models/response/two-factor-web-authn-update.response"; -import { TwoFactorWebAuthnResponse } from "../../models/response/two-factor-web-authn.response"; -import { TwoFactorYubiKeyUpdateResponse } from "../../models/response/two-factor-yubi-key-update.response"; -import { TwoFactorYubiKeyResponse } from "../../models/response/two-factor-yubi-key.response"; +import { DeleteTwoFactorAuthenticatorRequest } from "../request/delete-two-factor-authenticator.request"; +import { TwoFactorAuthenticatorUpdateRequest } from "../request/two-factor-authenticator-update.request"; +import { TwoFactorDuoDeleteRequest } from "../request/two-factor-duo-delete.request"; +import { TwoFactorDuoUpdateRequest } from "../request/two-factor-duo-update.request"; +import { TwoFactorEmailDeleteRequest } from "../request/two-factor-email-delete.request"; +import { TwoFactorEmailLoginRequest } from "../request/two-factor-email-login.request"; +import { TwoFactorEmailSetupRequest } from "../request/two-factor-email-setup.request"; +import { TwoFactorEmailUpdateRequest } from "../request/two-factor-email-update.request"; +import { TwoFactorOrganizationDuoDeleteRequest } from "../request/two-factor-organization-duo-delete.request"; +import { TwoFactorWebAuthnDeleteAllRequest } from "../request/two-factor-web-authn-delete-all.request"; +import { TwoFactorWebAuthnDeleteRequest } from "../request/two-factor-web-authn-delete.request"; +import { TwoFactorWebAuthnUpdateRequest } from "../request/two-factor-web-authn-update.request"; +import { TwoFactorYubiKeyDeleteRequest } from "../request/two-factor-yubikey-delete.request"; +import { TwoFactorYubiKeyUpdateRequest } from "../request/two-factor-yubikey-update.request"; +import { TwoFactorAuthenticatorUpdateResponse } from "../response/two-factor-authenticator-update.response"; +import { TwoFactorAuthenticatorResponse } from "../response/two-factor-authenticator.response"; +import { TwoFactorDuoUpdateResponse } from "../response/two-factor-duo-update.response"; +import { TwoFactorDuoResponse } from "../response/two-factor-duo.response"; +import { TwoFactorEmailUpdateResponse } from "../response/two-factor-email-update.response"; +import { TwoFactorEmailResponse } from "../response/two-factor-email.response"; +import { TwoFactorOrganizationDuoUpdateResponse } from "../response/two-factor-organization-duo-update.response"; +import { TwoFactorOrganizationDuoResponse } from "../response/two-factor-organization-duo.response"; +import { TwoFactorProviderResponse } from "../response/two-factor-provider.response"; +import { TwoFactorRecoverResponse } from "../response/two-factor-recover.response"; +import { TwoFactorWebAuthnChallengeResponse } from "../response/two-factor-web-authn-challenge.response"; +import { TwoFactorWebAuthnDeleteResponse } from "../response/two-factor-web-authn-delete.response"; +import { TwoFactorWebAuthnUpdateResponse } from "../response/two-factor-web-authn-update.response"; +import { TwoFactorWebAuthnResponse } from "../response/two-factor-web-authn.response"; +import { TwoFactorYubiKeyUpdateResponse } from "../response/two-factor-yubi-key-update.response"; +import { TwoFactorYubiKeyResponse } from "../response/two-factor-yubi-key.response"; /** * Metadata and display information for a two-factor authentication provider. diff --git a/libs/common/src/auth/two-factor/index.ts b/libs/common/src/auth/two-factor/index.ts index fd6edf0ac3c3..8dce333f5e8b 100644 --- a/libs/common/src/auth/two-factor/index.ts +++ b/libs/common/src/auth/two-factor/index.ts @@ -1,2 +1,4 @@ export * from "./abstractions"; +export * from "./request"; +export * from "./response"; export * from "./services"; diff --git a/libs/common/src/auth/models/request/delete-two-factor-authenticator.request.ts b/libs/common/src/auth/two-factor/request/delete-two-factor-authenticator.request.ts similarity index 100% rename from libs/common/src/auth/models/request/delete-two-factor-authenticator.request.ts rename to libs/common/src/auth/two-factor/request/delete-two-factor-authenticator.request.ts diff --git a/libs/common/src/auth/two-factor/request/index.ts b/libs/common/src/auth/two-factor/request/index.ts new file mode 100644 index 000000000000..228424f25d0a --- /dev/null +++ b/libs/common/src/auth/two-factor/request/index.ts @@ -0,0 +1,14 @@ +export * from "./delete-two-factor-authenticator.request"; +export * from "./two-factor-authenticator-update.request"; +export * from "./two-factor-duo-delete.request"; +export * from "./two-factor-duo-update.request"; +export * from "./two-factor-email-delete.request"; +export * from "./two-factor-email-login.request"; +export * from "./two-factor-email-setup.request"; +export * from "./two-factor-email-update.request"; +export * from "./two-factor-organization-duo-delete.request"; +export * from "./two-factor-web-authn-delete-all.request"; +export * from "./two-factor-web-authn-delete.request"; +export * from "./two-factor-web-authn-update.request"; +export * from "./two-factor-yubikey-delete.request"; +export * from "./two-factor-yubikey-update.request"; diff --git a/libs/common/src/auth/models/request/two-factor-authenticator-update.request.ts b/libs/common/src/auth/two-factor/request/two-factor-authenticator-update.request.ts similarity index 100% rename from libs/common/src/auth/models/request/two-factor-authenticator-update.request.ts rename to libs/common/src/auth/two-factor/request/two-factor-authenticator-update.request.ts diff --git a/libs/common/src/auth/models/request/two-factor-duo-delete.request.ts b/libs/common/src/auth/two-factor/request/two-factor-duo-delete.request.ts similarity index 100% rename from libs/common/src/auth/models/request/two-factor-duo-delete.request.ts rename to libs/common/src/auth/two-factor/request/two-factor-duo-delete.request.ts diff --git a/libs/common/src/auth/models/request/two-factor-duo-update.request.ts b/libs/common/src/auth/two-factor/request/two-factor-duo-update.request.ts similarity index 100% rename from libs/common/src/auth/models/request/two-factor-duo-update.request.ts rename to libs/common/src/auth/two-factor/request/two-factor-duo-update.request.ts diff --git a/libs/common/src/auth/models/request/two-factor-email-delete.request.ts b/libs/common/src/auth/two-factor/request/two-factor-email-delete.request.ts similarity index 100% rename from libs/common/src/auth/models/request/two-factor-email-delete.request.ts rename to libs/common/src/auth/two-factor/request/two-factor-email-delete.request.ts diff --git a/libs/common/src/auth/models/request/two-factor-email-login.request.ts b/libs/common/src/auth/two-factor/request/two-factor-email-login.request.ts similarity index 74% rename from libs/common/src/auth/models/request/two-factor-email-login.request.ts rename to libs/common/src/auth/two-factor/request/two-factor-email-login.request.ts index 4edca711c838..d75d6b5e2bcc 100644 --- a/libs/common/src/auth/models/request/two-factor-email-login.request.ts +++ b/libs/common/src/auth/two-factor/request/two-factor-email-login.request.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { SecretVerificationRequest } from "./secret-verification.request"; +import { SecretVerificationRequest } from "../../models/request/secret-verification.request"; export class TwoFactorEmailLoginRequest extends SecretVerificationRequest { email: string; diff --git a/libs/common/src/auth/models/request/two-factor-email-setup.request.ts b/libs/common/src/auth/two-factor/request/two-factor-email-setup.request.ts similarity index 100% rename from libs/common/src/auth/models/request/two-factor-email-setup.request.ts rename to libs/common/src/auth/two-factor/request/two-factor-email-setup.request.ts diff --git a/libs/common/src/auth/models/request/two-factor-email-update.request.ts b/libs/common/src/auth/two-factor/request/two-factor-email-update.request.ts similarity index 100% rename from libs/common/src/auth/models/request/two-factor-email-update.request.ts rename to libs/common/src/auth/two-factor/request/two-factor-email-update.request.ts diff --git a/libs/common/src/auth/models/request/two-factor-organization-duo-delete.request.ts b/libs/common/src/auth/two-factor/request/two-factor-organization-duo-delete.request.ts similarity index 100% rename from libs/common/src/auth/models/request/two-factor-organization-duo-delete.request.ts rename to libs/common/src/auth/two-factor/request/two-factor-organization-duo-delete.request.ts diff --git a/libs/common/src/auth/models/request/two-factor-web-authn-delete-all.request.ts b/libs/common/src/auth/two-factor/request/two-factor-web-authn-delete-all.request.ts similarity index 100% rename from libs/common/src/auth/models/request/two-factor-web-authn-delete-all.request.ts rename to libs/common/src/auth/two-factor/request/two-factor-web-authn-delete-all.request.ts diff --git a/libs/common/src/auth/models/request/two-factor-web-authn-delete.request.ts b/libs/common/src/auth/two-factor/request/two-factor-web-authn-delete.request.ts similarity index 100% rename from libs/common/src/auth/models/request/two-factor-web-authn-delete.request.ts rename to libs/common/src/auth/two-factor/request/two-factor-web-authn-delete.request.ts diff --git a/libs/common/src/auth/models/request/two-factor-web-authn-update.request.ts b/libs/common/src/auth/two-factor/request/two-factor-web-authn-update.request.ts similarity index 100% rename from libs/common/src/auth/models/request/two-factor-web-authn-update.request.ts rename to libs/common/src/auth/two-factor/request/two-factor-web-authn-update.request.ts diff --git a/libs/common/src/auth/models/request/two-factor-yubikey-delete.request.ts b/libs/common/src/auth/two-factor/request/two-factor-yubikey-delete.request.ts similarity index 100% rename from libs/common/src/auth/models/request/two-factor-yubikey-delete.request.ts rename to libs/common/src/auth/two-factor/request/two-factor-yubikey-delete.request.ts diff --git a/libs/common/src/auth/models/request/two-factor-yubikey-update.request.ts b/libs/common/src/auth/two-factor/request/two-factor-yubikey-update.request.ts similarity index 100% rename from libs/common/src/auth/models/request/two-factor-yubikey-update.request.ts rename to libs/common/src/auth/two-factor/request/two-factor-yubikey-update.request.ts diff --git a/libs/common/src/auth/two-factor/response/index.ts b/libs/common/src/auth/two-factor/response/index.ts new file mode 100644 index 000000000000..5d52dcf9d927 --- /dev/null +++ b/libs/common/src/auth/two-factor/response/index.ts @@ -0,0 +1,21 @@ +export * from "./two-factor-authenticator-details.response"; +export * from "./two-factor-authenticator-update.response"; +export * from "./two-factor-authenticator.response"; +export * from "./two-factor-duo-details.response"; +export * from "./two-factor-duo-update.response"; +export * from "./two-factor-duo.response"; +export * from "./two-factor-email-details.response"; +export * from "./two-factor-email-update.response"; +export * from "./two-factor-email.response"; +export * from "./two-factor-organization-duo-update.response"; +export * from "./two-factor-organization-duo.response"; +export * from "./two-factor-provider.response"; +export * from "./two-factor-recover.response"; +export * from "./two-factor-web-authn-challenge.response"; +export * from "./two-factor-web-authn-delete.response"; +export * from "./two-factor-web-authn-details.response"; +export * from "./two-factor-web-authn-update.response"; +export * from "./two-factor-web-authn.response"; +export * from "./two-factor-yubi-key-details.response"; +export * from "./two-factor-yubi-key-update.response"; +export * from "./two-factor-yubi-key.response"; diff --git a/libs/common/src/auth/models/response/two-factor-authenticator-details.response.ts b/libs/common/src/auth/two-factor/response/two-factor-authenticator-details.response.ts similarity index 100% rename from libs/common/src/auth/models/response/two-factor-authenticator-details.response.ts rename to libs/common/src/auth/two-factor/response/two-factor-authenticator-details.response.ts diff --git a/libs/common/src/auth/models/response/two-factor-authenticator-update.response.ts b/libs/common/src/auth/two-factor/response/two-factor-authenticator-update.response.ts similarity index 100% rename from libs/common/src/auth/models/response/two-factor-authenticator-update.response.ts rename to libs/common/src/auth/two-factor/response/two-factor-authenticator-update.response.ts diff --git a/libs/common/src/auth/models/response/two-factor-authenticator.response.ts b/libs/common/src/auth/two-factor/response/two-factor-authenticator.response.ts similarity index 100% rename from libs/common/src/auth/models/response/two-factor-authenticator.response.ts rename to libs/common/src/auth/two-factor/response/two-factor-authenticator.response.ts diff --git a/libs/common/src/auth/models/response/two-factor-duo-details.response.ts b/libs/common/src/auth/two-factor/response/two-factor-duo-details.response.ts similarity index 100% rename from libs/common/src/auth/models/response/two-factor-duo-details.response.ts rename to libs/common/src/auth/two-factor/response/two-factor-duo-details.response.ts diff --git a/libs/common/src/auth/models/response/two-factor-duo-update.response.ts b/libs/common/src/auth/two-factor/response/two-factor-duo-update.response.ts similarity index 100% rename from libs/common/src/auth/models/response/two-factor-duo-update.response.ts rename to libs/common/src/auth/two-factor/response/two-factor-duo-update.response.ts diff --git a/libs/common/src/auth/models/response/two-factor-duo.response.ts b/libs/common/src/auth/two-factor/response/two-factor-duo.response.ts similarity index 100% rename from libs/common/src/auth/models/response/two-factor-duo.response.ts rename to libs/common/src/auth/two-factor/response/two-factor-duo.response.ts diff --git a/libs/common/src/auth/models/response/two-factor-email-details.response.ts b/libs/common/src/auth/two-factor/response/two-factor-email-details.response.ts similarity index 100% rename from libs/common/src/auth/models/response/two-factor-email-details.response.ts rename to libs/common/src/auth/two-factor/response/two-factor-email-details.response.ts diff --git a/libs/common/src/auth/models/response/two-factor-email-update.response.ts b/libs/common/src/auth/two-factor/response/two-factor-email-update.response.ts similarity index 100% rename from libs/common/src/auth/models/response/two-factor-email-update.response.ts rename to libs/common/src/auth/two-factor/response/two-factor-email-update.response.ts diff --git a/libs/common/src/auth/models/response/two-factor-email.response.ts b/libs/common/src/auth/two-factor/response/two-factor-email.response.ts similarity index 100% rename from libs/common/src/auth/models/response/two-factor-email.response.ts rename to libs/common/src/auth/two-factor/response/two-factor-email.response.ts diff --git a/libs/common/src/auth/models/response/two-factor-organization-duo-update.response.ts b/libs/common/src/auth/two-factor/response/two-factor-organization-duo-update.response.ts similarity index 100% rename from libs/common/src/auth/models/response/two-factor-organization-duo-update.response.ts rename to libs/common/src/auth/two-factor/response/two-factor-organization-duo-update.response.ts diff --git a/libs/common/src/auth/models/response/two-factor-organization-duo.response.ts b/libs/common/src/auth/two-factor/response/two-factor-organization-duo.response.ts similarity index 100% rename from libs/common/src/auth/models/response/two-factor-organization-duo.response.ts rename to libs/common/src/auth/two-factor/response/two-factor-organization-duo.response.ts diff --git a/libs/common/src/auth/models/response/two-factor-provider.response.ts b/libs/common/src/auth/two-factor/response/two-factor-provider.response.ts similarity index 100% rename from libs/common/src/auth/models/response/two-factor-provider.response.ts rename to libs/common/src/auth/two-factor/response/two-factor-provider.response.ts diff --git a/libs/common/src/auth/models/response/two-factor-recover.response.ts b/libs/common/src/auth/two-factor/response/two-factor-recover.response.ts similarity index 100% rename from libs/common/src/auth/models/response/two-factor-recover.response.ts rename to libs/common/src/auth/two-factor/response/two-factor-recover.response.ts diff --git a/libs/common/src/auth/models/response/two-factor-web-authn-challenge.response.ts b/libs/common/src/auth/two-factor/response/two-factor-web-authn-challenge.response.ts similarity index 89% rename from libs/common/src/auth/models/response/two-factor-web-authn-challenge.response.ts rename to libs/common/src/auth/two-factor/response/two-factor-web-authn-challenge.response.ts index 38739a1df9d5..9ca5e61f41d0 100644 --- a/libs/common/src/auth/models/response/two-factor-web-authn-challenge.response.ts +++ b/libs/common/src/auth/two-factor/response/two-factor-web-authn-challenge.response.ts @@ -1,6 +1,5 @@ import { BaseResponse } from "../../../models/response/base.response"; - -import { WebAuthnChallengeResponse } from "./web-authn-challenge.response"; +import { WebAuthnChallengeResponse } from "../../models/response/web-authn-challenge.response"; /** * Wrapper around {@link WebAuthnChallengeResponse} that adds the user-verification token minted by diff --git a/libs/common/src/auth/models/response/two-factor-web-authn-delete.response.ts b/libs/common/src/auth/two-factor/response/two-factor-web-authn-delete.response.ts similarity index 100% rename from libs/common/src/auth/models/response/two-factor-web-authn-delete.response.ts rename to libs/common/src/auth/two-factor/response/two-factor-web-authn-delete.response.ts diff --git a/libs/common/src/auth/models/response/two-factor-web-authn-details.response.ts b/libs/common/src/auth/two-factor/response/two-factor-web-authn-details.response.ts similarity index 100% rename from libs/common/src/auth/models/response/two-factor-web-authn-details.response.ts rename to libs/common/src/auth/two-factor/response/two-factor-web-authn-details.response.ts diff --git a/libs/common/src/auth/models/response/two-factor-web-authn-update.response.ts b/libs/common/src/auth/two-factor/response/two-factor-web-authn-update.response.ts similarity index 100% rename from libs/common/src/auth/models/response/two-factor-web-authn-update.response.ts rename to libs/common/src/auth/two-factor/response/two-factor-web-authn-update.response.ts diff --git a/libs/common/src/auth/models/response/two-factor-web-authn.response.ts b/libs/common/src/auth/two-factor/response/two-factor-web-authn.response.ts similarity index 100% rename from libs/common/src/auth/models/response/two-factor-web-authn.response.ts rename to libs/common/src/auth/two-factor/response/two-factor-web-authn.response.ts diff --git a/libs/common/src/auth/models/response/two-factor-yubi-key-details.response.ts b/libs/common/src/auth/two-factor/response/two-factor-yubi-key-details.response.ts similarity index 100% rename from libs/common/src/auth/models/response/two-factor-yubi-key-details.response.ts rename to libs/common/src/auth/two-factor/response/two-factor-yubi-key-details.response.ts diff --git a/libs/common/src/auth/models/response/two-factor-yubi-key-update.response.ts b/libs/common/src/auth/two-factor/response/two-factor-yubi-key-update.response.ts similarity index 100% rename from libs/common/src/auth/models/response/two-factor-yubi-key-update.response.ts rename to libs/common/src/auth/two-factor/response/two-factor-yubi-key-update.response.ts diff --git a/libs/common/src/auth/models/response/two-factor-yubi-key.response.ts b/libs/common/src/auth/two-factor/response/two-factor-yubi-key.response.ts similarity index 100% rename from libs/common/src/auth/models/response/two-factor-yubi-key.response.ts rename to libs/common/src/auth/two-factor/response/two-factor-yubi-key.response.ts diff --git a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts index baf29cf33984..00cd8c29bd7b 100644 --- a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts +++ b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts @@ -1,37 +1,37 @@ import { mock, MockProxy } from "jest-mock-extended"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { DeleteTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/delete-two-factor-authenticator.request"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; -import { TwoFactorAuthenticatorUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-authenticator-update.request"; -import { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-delete.request"; -import { TwoFactorDuoUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-update.request"; -import { TwoFactorEmailDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-email-delete.request"; -import { TwoFactorEmailLoginRequest } from "@bitwarden/common/auth/models/request/two-factor-email-login.request"; -import { TwoFactorEmailSetupRequest } from "@bitwarden/common/auth/models/request/two-factor-email-setup.request"; -import { TwoFactorEmailUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-email-update.request"; -import { TwoFactorOrganizationDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-organization-duo-delete.request"; -import { TwoFactorWebAuthnDeleteAllRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-delete-all.request"; -import { TwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-delete.request"; -import { TwoFactorWebAuthnUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-update.request"; -import { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-delete.request"; -import { TwoFactorYubiKeyUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-update.request"; -import { TwoFactorAuthenticatorUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator-update.response"; -import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator.response"; -import { TwoFactorDuoUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-duo-update.response"; -import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-duo.response"; -import { TwoFactorEmailUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-email-update.response"; -import { TwoFactorEmailResponse } from "@bitwarden/common/auth/models/response/two-factor-email.response"; -import { TwoFactorOrganizationDuoUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-organization-duo-update.response"; -import { TwoFactorOrganizationDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-organization-duo.response"; -import { TwoFactorProviderResponse } from "@bitwarden/common/auth/models/response/two-factor-provider.response"; -import { TwoFactorRecoverResponse } from "@bitwarden/common/auth/models/response/two-factor-recover.response"; -import { TwoFactorWebAuthnChallengeResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-challenge.response"; -import { TwoFactorWebAuthnDeleteResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-delete.response"; -import { TwoFactorWebAuthnUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-update.response"; -import { TwoFactorWebAuthnResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn.response"; -import { TwoFactorYubiKeyUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key-update.response"; -import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key.response"; +import { DeleteTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/two-factor/request/delete-two-factor-authenticator.request"; +import { TwoFactorAuthenticatorUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-authenticator-update.request"; +import { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-duo-delete.request"; +import { TwoFactorDuoUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-duo-update.request"; +import { TwoFactorEmailDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-delete.request"; +import { TwoFactorEmailLoginRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-login.request"; +import { TwoFactorEmailSetupRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-setup.request"; +import { TwoFactorEmailUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-update.request"; +import { TwoFactorOrganizationDuoDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-organization-duo-delete.request"; +import { TwoFactorWebAuthnDeleteAllRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-delete-all.request"; +import { TwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-delete.request"; +import { TwoFactorWebAuthnUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-update.request"; +import { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-yubikey-delete.request"; +import { TwoFactorYubiKeyUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-yubikey-update.request"; +import { TwoFactorAuthenticatorUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-authenticator-update.response"; +import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-authenticator.response"; +import { TwoFactorDuoUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-duo-update.response"; +import { TwoFactorDuoResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-duo.response"; +import { TwoFactorEmailUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-email-update.response"; +import { TwoFactorEmailResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-email.response"; +import { TwoFactorOrganizationDuoUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-organization-duo-update.response"; +import { TwoFactorOrganizationDuoResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-organization-duo.response"; +import { TwoFactorProviderResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-provider.response"; +import { TwoFactorRecoverResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-recover.response"; +import { TwoFactorWebAuthnChallengeResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn-challenge.response"; +import { TwoFactorWebAuthnDeleteResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn-delete.response"; +import { TwoFactorWebAuthnUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn-update.response"; +import { TwoFactorWebAuthnResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn.response"; +import { TwoFactorYubiKeyUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-yubi-key-update.response"; +import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-yubi-key.response"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { DefaultTwoFactorApiService } from "./default-two-factor-api.service"; diff --git a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts index f808ee118033..b2c2927cb3dd 100644 --- a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts +++ b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts @@ -1,35 +1,35 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { DeleteTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/delete-two-factor-authenticator.request"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; -import { TwoFactorAuthenticatorUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-authenticator-update.request"; -import { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-delete.request"; -import { TwoFactorDuoUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-duo-update.request"; -import { TwoFactorEmailDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-email-delete.request"; -import { TwoFactorEmailLoginRequest } from "@bitwarden/common/auth/models/request/two-factor-email-login.request"; -import { TwoFactorEmailSetupRequest } from "@bitwarden/common/auth/models/request/two-factor-email-setup.request"; -import { TwoFactorEmailUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-email-update.request"; -import { TwoFactorOrganizationDuoDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-organization-duo-delete.request"; -import { TwoFactorWebAuthnDeleteAllRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-delete-all.request"; -import { TwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-delete.request"; -import { TwoFactorWebAuthnUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-web-authn-update.request"; -import { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-delete.request"; -import { TwoFactorYubiKeyUpdateRequest } from "@bitwarden/common/auth/models/request/two-factor-yubikey-update.request"; -import { TwoFactorAuthenticatorUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator-update.response"; -import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator.response"; -import { TwoFactorDuoUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-duo-update.response"; -import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-duo.response"; -import { TwoFactorEmailUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-email-update.response"; -import { TwoFactorEmailResponse } from "@bitwarden/common/auth/models/response/two-factor-email.response"; -import { TwoFactorOrganizationDuoUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-organization-duo-update.response"; -import { TwoFactorOrganizationDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-organization-duo.response"; -import { TwoFactorProviderResponse } from "@bitwarden/common/auth/models/response/two-factor-provider.response"; -import { TwoFactorRecoverResponse } from "@bitwarden/common/auth/models/response/two-factor-recover.response"; -import { TwoFactorWebAuthnChallengeResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-challenge.response"; -import { TwoFactorWebAuthnDeleteResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-delete.response"; -import { TwoFactorWebAuthnUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn-update.response"; -import { TwoFactorWebAuthnResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn.response"; -import { TwoFactorYubiKeyUpdateResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key-update.response"; -import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key.response"; +import { DeleteTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/two-factor/request/delete-two-factor-authenticator.request"; +import { TwoFactorAuthenticatorUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-authenticator-update.request"; +import { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-duo-delete.request"; +import { TwoFactorDuoUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-duo-update.request"; +import { TwoFactorEmailDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-delete.request"; +import { TwoFactorEmailLoginRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-login.request"; +import { TwoFactorEmailSetupRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-setup.request"; +import { TwoFactorEmailUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-update.request"; +import { TwoFactorOrganizationDuoDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-organization-duo-delete.request"; +import { TwoFactorWebAuthnDeleteAllRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-delete-all.request"; +import { TwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-delete.request"; +import { TwoFactorWebAuthnUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-update.request"; +import { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-yubikey-delete.request"; +import { TwoFactorYubiKeyUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-yubikey-update.request"; +import { TwoFactorAuthenticatorUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-authenticator-update.response"; +import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-authenticator.response"; +import { TwoFactorDuoUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-duo-update.response"; +import { TwoFactorDuoResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-duo.response"; +import { TwoFactorEmailUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-email-update.response"; +import { TwoFactorEmailResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-email.response"; +import { TwoFactorOrganizationDuoUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-organization-duo-update.response"; +import { TwoFactorOrganizationDuoResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-organization-duo.response"; +import { TwoFactorProviderResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-provider.response"; +import { TwoFactorRecoverResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-recover.response"; +import { TwoFactorWebAuthnChallengeResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn-challenge.response"; +import { TwoFactorWebAuthnDeleteResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn-delete.response"; +import { TwoFactorWebAuthnUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn-update.response"; +import { TwoFactorWebAuthnResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn.response"; +import { TwoFactorYubiKeyUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-yubi-key-update.response"; +import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-yubi-key.response"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { Utils } from "@bitwarden/common/platform/misc/utils"; 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 index 80f69eb5fc32..52940fdc7015 100644 --- 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 @@ -9,38 +9,8 @@ import { PlatformUtilsService } from "../../../platform/abstractions/platform-ut import { Utils } from "../../../platform/misc/utils"; import { GlobalStateProvider } from "../../../platform/state"; import { TwoFactorProviderType } from "../../enums/two-factor-provider-type"; -import { DeleteTwoFactorAuthenticatorRequest } from "../../models/request/delete-two-factor-authenticator.request"; import { SecretVerificationRequest } from "../../models/request/secret-verification.request"; -import { TwoFactorAuthenticatorUpdateRequest } from "../../models/request/two-factor-authenticator-update.request"; -import { TwoFactorDuoDeleteRequest } from "../../models/request/two-factor-duo-delete.request"; -import { TwoFactorDuoUpdateRequest } from "../../models/request/two-factor-duo-update.request"; -import { TwoFactorEmailDeleteRequest } from "../../models/request/two-factor-email-delete.request"; -import { TwoFactorEmailLoginRequest } from "../../models/request/two-factor-email-login.request"; -import { TwoFactorEmailSetupRequest } from "../../models/request/two-factor-email-setup.request"; -import { TwoFactorEmailUpdateRequest } from "../../models/request/two-factor-email-update.request"; -import { TwoFactorOrganizationDuoDeleteRequest } from "../../models/request/two-factor-organization-duo-delete.request"; -import { TwoFactorWebAuthnDeleteAllRequest } from "../../models/request/two-factor-web-authn-delete-all.request"; -import { TwoFactorWebAuthnDeleteRequest } from "../../models/request/two-factor-web-authn-delete.request"; -import { TwoFactorWebAuthnUpdateRequest } from "../../models/request/two-factor-web-authn-update.request"; -import { TwoFactorYubiKeyDeleteRequest } from "../../models/request/two-factor-yubikey-delete.request"; -import { TwoFactorYubiKeyUpdateRequest } from "../../models/request/two-factor-yubikey-update.request"; import { IdentityTwoFactorResponse } from "../../models/response/identity-two-factor.response"; -import { TwoFactorAuthenticatorUpdateResponse } from "../../models/response/two-factor-authenticator-update.response"; -import { TwoFactorAuthenticatorResponse } from "../../models/response/two-factor-authenticator.response"; -import { TwoFactorDuoUpdateResponse } from "../../models/response/two-factor-duo-update.response"; -import { TwoFactorDuoResponse } from "../../models/response/two-factor-duo.response"; -import { TwoFactorEmailUpdateResponse } from "../../models/response/two-factor-email-update.response"; -import { TwoFactorEmailResponse } from "../../models/response/two-factor-email.response"; -import { TwoFactorOrganizationDuoUpdateResponse } from "../../models/response/two-factor-organization-duo-update.response"; -import { TwoFactorOrganizationDuoResponse } from "../../models/response/two-factor-organization-duo.response"; -import { TwoFactorProviderResponse } from "../../models/response/two-factor-provider.response"; -import { TwoFactorRecoverResponse } from "../../models/response/two-factor-recover.response"; -import { TwoFactorWebAuthnChallengeResponse } from "../../models/response/two-factor-web-authn-challenge.response"; -import { TwoFactorWebAuthnDeleteResponse } from "../../models/response/two-factor-web-authn-delete.response"; -import { TwoFactorWebAuthnUpdateResponse } from "../../models/response/two-factor-web-authn-update.response"; -import { TwoFactorWebAuthnResponse } from "../../models/response/two-factor-web-authn.response"; -import { TwoFactorYubiKeyUpdateResponse } from "../../models/response/two-factor-yubi-key-update.response"; -import { TwoFactorYubiKeyResponse } from "../../models/response/two-factor-yubi-key.response"; import { PROVIDERS, SELECTED_PROVIDER, @@ -48,6 +18,36 @@ import { TwoFactorProviders, TwoFactorService as TwoFactorServiceAbstraction, } from "../abstractions/two-factor.service"; +import { DeleteTwoFactorAuthenticatorRequest } from "../request/delete-two-factor-authenticator.request"; +import { TwoFactorAuthenticatorUpdateRequest } from "../request/two-factor-authenticator-update.request"; +import { TwoFactorDuoDeleteRequest } from "../request/two-factor-duo-delete.request"; +import { TwoFactorDuoUpdateRequest } from "../request/two-factor-duo-update.request"; +import { TwoFactorEmailDeleteRequest } from "../request/two-factor-email-delete.request"; +import { TwoFactorEmailLoginRequest } from "../request/two-factor-email-login.request"; +import { TwoFactorEmailSetupRequest } from "../request/two-factor-email-setup.request"; +import { TwoFactorEmailUpdateRequest } from "../request/two-factor-email-update.request"; +import { TwoFactorOrganizationDuoDeleteRequest } from "../request/two-factor-organization-duo-delete.request"; +import { TwoFactorWebAuthnDeleteAllRequest } from "../request/two-factor-web-authn-delete-all.request"; +import { TwoFactorWebAuthnDeleteRequest } from "../request/two-factor-web-authn-delete.request"; +import { TwoFactorWebAuthnUpdateRequest } from "../request/two-factor-web-authn-update.request"; +import { TwoFactorYubiKeyDeleteRequest } from "../request/two-factor-yubikey-delete.request"; +import { TwoFactorYubiKeyUpdateRequest } from "../request/two-factor-yubikey-update.request"; +import { TwoFactorAuthenticatorUpdateResponse } from "../response/two-factor-authenticator-update.response"; +import { TwoFactorAuthenticatorResponse } from "../response/two-factor-authenticator.response"; +import { TwoFactorDuoUpdateResponse } from "../response/two-factor-duo-update.response"; +import { TwoFactorDuoResponse } from "../response/two-factor-duo.response"; +import { TwoFactorEmailUpdateResponse } from "../response/two-factor-email-update.response"; +import { TwoFactorEmailResponse } from "../response/two-factor-email.response"; +import { TwoFactorOrganizationDuoUpdateResponse } from "../response/two-factor-organization-duo-update.response"; +import { TwoFactorOrganizationDuoResponse } from "../response/two-factor-organization-duo.response"; +import { TwoFactorProviderResponse } from "../response/two-factor-provider.response"; +import { TwoFactorRecoverResponse } from "../response/two-factor-recover.response"; +import { TwoFactorWebAuthnChallengeResponse } from "../response/two-factor-web-authn-challenge.response"; +import { TwoFactorWebAuthnDeleteResponse } from "../response/two-factor-web-authn-delete.response"; +import { TwoFactorWebAuthnUpdateResponse } from "../response/two-factor-web-authn-update.response"; +import { TwoFactorWebAuthnResponse } from "../response/two-factor-web-authn.response"; +import { TwoFactorYubiKeyUpdateResponse } from "../response/two-factor-yubi-key-update.response"; +import { TwoFactorYubiKeyResponse } from "../response/two-factor-yubi-key.response"; export class DefaultTwoFactorService implements TwoFactorServiceAbstraction { private providersState = this.globalStateProvider.get(PROVIDERS); diff --git a/libs/common/src/auth/types/two-factor-response.ts b/libs/common/src/auth/types/two-factor-response.ts index 69a95288106d..fb09cccc24c5 100644 --- a/libs/common/src/auth/types/two-factor-response.ts +++ b/libs/common/src/auth/types/two-factor-response.ts @@ -1,10 +1,10 @@ -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 { TwoFactorOrganizationDuoResponse } from "../models/response/two-factor-organization-duo.response"; -import { TwoFactorRecoverResponse } from "../models/response/two-factor-recover.response"; -import { TwoFactorWebAuthnResponse } from "../models/response/two-factor-web-authn.response"; -import { TwoFactorYubiKeyResponse } from "../models/response/two-factor-yubi-key.response"; +import { TwoFactorAuthenticatorResponse } from "../two-factor/response/two-factor-authenticator.response"; +import { TwoFactorDuoResponse } from "../two-factor/response/two-factor-duo.response"; +import { TwoFactorEmailResponse } from "../two-factor/response/two-factor-email.response"; +import { TwoFactorOrganizationDuoResponse } from "../two-factor/response/two-factor-organization-duo.response"; +import { TwoFactorRecoverResponse } from "../two-factor/response/two-factor-recover.response"; +import { TwoFactorWebAuthnResponse } from "../two-factor/response/two-factor-web-authn.response"; +import { TwoFactorYubiKeyResponse } from "../two-factor/response/two-factor-yubi-key.response"; export type TwoFactorResponse = | TwoFactorRecoverResponse From d710e368d44f043fd7a6514340b67eaecb2c1b8c Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Wed, 24 Jun 2026 17:33:23 -0400 Subject: [PATCH 14/29] PM-38137 - Align authenticator delete request naming with sibling DTOs Renames DeleteTwoFactorAuthenticatorRequest to TwoFactorAuthenticatorDeleteRequest (file: two-factor-authenticator-delete.request.ts) so the authenticator delete request matches the TwoFactorDelete shape used by the duo, email, yubikey, web-authn, and organization-duo deletes. --- .../two-factor/two-factor-setup-authenticator.component.ts | 4 ++-- .../auth/two-factor/abstractions/two-factor-api.service.ts | 4 ++-- .../src/auth/two-factor/abstractions/two-factor.service.ts | 6 +++--- libs/common/src/auth/two-factor/request/index.ts | 2 +- ...equest.ts => two-factor-authenticator-delete.request.ts} | 2 +- .../services/default-two-factor-api.service.spec.ts | 4 ++-- .../two-factor/services/default-two-factor-api.service.ts | 4 ++-- .../auth/two-factor/services/default-two-factor.service.ts | 4 ++-- 8 files changed, 15 insertions(+), 15 deletions(-) rename libs/common/src/auth/two-factor/request/{delete-two-factor-authenticator.request.ts => two-factor-authenticator-delete.request.ts} (63%) 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 0ac7eb286370..969c4d1ab59c 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 @@ -10,7 +10,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv 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 { TwoFactorService } from "@bitwarden/common/auth/two-factor"; -import { DeleteTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/two-factor/request/delete-two-factor-authenticator.request"; +import { TwoFactorAuthenticatorDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-authenticator-delete.request"; import { TwoFactorAuthenticatorUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-authenticator-update.request"; import { TwoFactorAuthenticatorUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-authenticator-update.response"; import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-authenticator.response"; @@ -178,7 +178,7 @@ export class TwoFactorSetupAuthenticatorComponent return; } - const request = new DeleteTwoFactorAuthenticatorRequest(this.key, this.userVerificationToken); + const request = new TwoFactorAuthenticatorDeleteRequest(this.key, this.userVerificationToken); await this.twoFactorService.deleteTwoFactorAuthenticator(request); this.enabled = false; this.toastService.showToast({ diff --git a/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts b/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts index 926f255e0f4f..376cae0b582b 100644 --- a/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts +++ b/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts @@ -1,5 +1,5 @@ import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; -import { DeleteTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/two-factor/request/delete-two-factor-authenticator.request"; +import { TwoFactorAuthenticatorDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-authenticator-delete.request"; import { TwoFactorAuthenticatorUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-authenticator-update.request"; import { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-duo-delete.request"; import { TwoFactorDuoUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-duo-update.request"; @@ -176,7 +176,7 @@ export abstract class TwoFactorApiService { * @param request The request containing the user verification token and key. */ abstract deleteTwoFactorAuthenticator( - request: DeleteTwoFactorAuthenticatorRequest, + request: TwoFactorAuthenticatorDeleteRequest, ): Promise; /** 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 index 6782201b6a25..38d1b91baf7e 100644 --- a/libs/common/src/auth/two-factor/abstractions/two-factor.service.ts +++ b/libs/common/src/auth/two-factor/abstractions/two-factor.service.ts @@ -3,7 +3,7 @@ import { KeyDefinition, TWO_FACTOR_MEMORY } from "../../../platform/state"; import { TwoFactorProviderType } from "../../enums/two-factor-provider-type"; import { SecretVerificationRequest } from "../../models/request/secret-verification.request"; import { IdentityTwoFactorResponse } from "../../models/response/identity-two-factor.response"; -import { DeleteTwoFactorAuthenticatorRequest } from "../request/delete-two-factor-authenticator.request"; +import { TwoFactorAuthenticatorDeleteRequest } from "../request/two-factor-authenticator-delete.request"; import { TwoFactorAuthenticatorUpdateRequest } from "../request/two-factor-authenticator-update.request"; import { TwoFactorDuoDeleteRequest } from "../request/two-factor-duo-delete.request"; import { TwoFactorDuoUpdateRequest } from "../request/two-factor-duo-update.request"; @@ -356,10 +356,10 @@ export abstract class TwoFactorService { * Requires a user verification token to confirm the operation. Returns 204 No Content. * Used for settings management. * - * @param request The {@link DeleteTwoFactorAuthenticatorRequest} containing the user verification token and key. + * @param request The {@link TwoFactorAuthenticatorDeleteRequest} containing the user verification token and key. */ abstract deleteTwoFactorAuthenticator( - request: DeleteTwoFactorAuthenticatorRequest, + request: TwoFactorAuthenticatorDeleteRequest, ): Promise; /** diff --git a/libs/common/src/auth/two-factor/request/index.ts b/libs/common/src/auth/two-factor/request/index.ts index 228424f25d0a..5240890969ca 100644 --- a/libs/common/src/auth/two-factor/request/index.ts +++ b/libs/common/src/auth/two-factor/request/index.ts @@ -1,4 +1,4 @@ -export * from "./delete-two-factor-authenticator.request"; +export * from "./two-factor-authenticator-delete.request"; export * from "./two-factor-authenticator-update.request"; export * from "./two-factor-duo-delete.request"; export * from "./two-factor-duo-update.request"; diff --git a/libs/common/src/auth/two-factor/request/delete-two-factor-authenticator.request.ts b/libs/common/src/auth/two-factor/request/two-factor-authenticator-delete.request.ts similarity index 63% rename from libs/common/src/auth/two-factor/request/delete-two-factor-authenticator.request.ts rename to libs/common/src/auth/two-factor/request/two-factor-authenticator-delete.request.ts index 39165a70648d..8262921fdd5c 100644 --- a/libs/common/src/auth/two-factor/request/delete-two-factor-authenticator.request.ts +++ b/libs/common/src/auth/two-factor/request/two-factor-authenticator-delete.request.ts @@ -1,4 +1,4 @@ -export class DeleteTwoFactorAuthenticatorRequest { +export class TwoFactorAuthenticatorDeleteRequest { constructor( public key: string, public userVerificationToken: string, diff --git a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts index 00cd8c29bd7b..d95c8c6d8cdb 100644 --- a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts +++ b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts @@ -2,7 +2,7 @@ import { mock, MockProxy } from "jest-mock-extended"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; -import { DeleteTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/two-factor/request/delete-two-factor-authenticator.request"; +import { TwoFactorAuthenticatorDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-authenticator-delete.request"; import { TwoFactorAuthenticatorUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-authenticator-update.request"; import { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-duo-delete.request"; import { TwoFactorDuoUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-duo-update.request"; @@ -152,7 +152,7 @@ describe("TwoFactorApiService", () => { describe("deleteTwoFactorAuthenticator", () => { it("disables authenticator two-factor authentication and expects no body", async () => { - const request = new DeleteTwoFactorAuthenticatorRequest("MFRGGZDFMZTWQ2LK", "uv-token"); + const request = new TwoFactorAuthenticatorDeleteRequest("MFRGGZDFMZTWQ2LK", "uv-token"); await twoFactorApiService.deleteTwoFactorAuthenticator(request); diff --git a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts index b2c2927cb3dd..5f336e6daeb5 100644 --- a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts +++ b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts @@ -1,6 +1,6 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; -import { DeleteTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/two-factor/request/delete-two-factor-authenticator.request"; +import { TwoFactorAuthenticatorDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-authenticator-delete.request"; import { TwoFactorAuthenticatorUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-authenticator-update.request"; import { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-duo-delete.request"; import { TwoFactorDuoUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-duo-update.request"; @@ -86,7 +86,7 @@ export class DefaultTwoFactorApiService implements TwoFactorApiService { return new TwoFactorAuthenticatorUpdateResponse(response); } - async deleteTwoFactorAuthenticator(request: DeleteTwoFactorAuthenticatorRequest): Promise { + async deleteTwoFactorAuthenticator(request: TwoFactorAuthenticatorDeleteRequest): Promise { await this.apiService.send("DELETE", "/two-factor/authenticator", request, true, false); } 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 index 52940fdc7015..2c2ee5c46acd 100644 --- 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 @@ -18,7 +18,7 @@ import { TwoFactorProviders, TwoFactorService as TwoFactorServiceAbstraction, } from "../abstractions/two-factor.service"; -import { DeleteTwoFactorAuthenticatorRequest } from "../request/delete-two-factor-authenticator.request"; +import { TwoFactorAuthenticatorDeleteRequest } from "../request/two-factor-authenticator-delete.request"; import { TwoFactorAuthenticatorUpdateRequest } from "../request/two-factor-authenticator-update.request"; import { TwoFactorDuoDeleteRequest } from "../request/two-factor-duo-delete.request"; import { TwoFactorDuoUpdateRequest } from "../request/two-factor-duo-update.request"; @@ -232,7 +232,7 @@ export class DefaultTwoFactorService implements TwoFactorServiceAbstraction { return this.twoFactorApiService.putTwoFactorAuthenticator(request); } - deleteTwoFactorAuthenticator(request: DeleteTwoFactorAuthenticatorRequest): Promise { + deleteTwoFactorAuthenticator(request: TwoFactorAuthenticatorDeleteRequest): Promise { return this.twoFactorApiService.deleteTwoFactorAuthenticator(request); } From c5a628d45ad8b501d7bb3b684957b1c14186c9a7 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Wed, 24 Jun 2026 17:51:17 -0400 Subject: [PATCH 15/29] PM-38137 - Move 2FA type aliases into two-factor feature folder with clearer names Relocates the 2FA type aliases out of the convention folder (libs/common/src/auth/types/) and into the two-factor feature folder per the libs/common/src/auth/CLAUDE.md feature-folder rule. Renames the types so they describe what they actually are: AuthResponseBase becomes TwoFactorUserVerificationResult (the master-password/OTP verification proof threaded into 2FA management request DTOs) and AuthResponse becomes TwoFactorSetupDialogData (the payload passed as DIALOG_DATA into per- provider 2FA setup dialogs). Consumers now import from the @bitwarden/common/auth/two-factor barrel. --- .../settings/two-factor-setup.component.ts | 8 +++---- ...wo-factor-setup-authenticator.component.ts | 9 ++++---- .../two-factor-setup-duo.component.ts | 5 ++-- .../two-factor-setup-email.component.ts | 13 +++++------ .../two-factor-setup-method-base.component.ts | 9 ++++---- .../two-factor-setup-webauthn.component.ts | 13 +++++------ .../two-factor-setup-yubikey.component.ts | 13 +++++------ .../two-factor/two-factor-setup.component.ts | 13 +++++------ .../two-factor/two-factor-verify.component.ts | 8 +++---- libs/common/src/auth/two-factor/index.ts | 1 + .../common/src/auth/two-factor/types/index.ts | 2 ++ .../two-factor/types/two-factor-response.ts | 16 +++++++++++++ .../types/two-factor-setup-dialog-data.ts | 23 +++++++++++++++++++ libs/common/src/auth/types/auth-response.ts | 12 ---------- .../src/auth/types/two-factor-response.ts | 16 ------------- 15 files changed, 82 insertions(+), 79 deletions(-) create mode 100644 libs/common/src/auth/two-factor/types/index.ts create mode 100644 libs/common/src/auth/two-factor/types/two-factor-response.ts create mode 100644 libs/common/src/auth/two-factor/types/two-factor-setup-dialog-data.ts delete mode 100644 libs/common/src/auth/types/auth-response.ts delete mode 100644 libs/common/src/auth/types/two-factor-response.ts 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 aafe040a83fd..29ecb8544564 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 @@ -14,9 +14,8 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv 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 { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorService , TwoFactorSetupDialogData } from "@bitwarden/common/auth/two-factor"; import { TwoFactorOrganizationDuoResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-organization-duo.response"; -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"; @@ -93,9 +92,8 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent impleme const twoFactorVerifyDialogRef = TwoFactorVerifyComponent.open(this.dialogService, { data: { type: type, organizationId: this.organizationId }, }); - const result: AuthResponse = await lastValueFrom( - twoFactorVerifyDialogRef.closed, - ); + const result: TwoFactorSetupDialogData = + await lastValueFrom(twoFactorVerifyDialogRef.closed); if (!result) { return; } 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 969c4d1ab59c..b81a9e642cec 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 @@ -9,12 +9,11 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; 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 { TwoFactorService } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorService , TwoFactorSetupDialogData } from "@bitwarden/common/auth/two-factor"; import { TwoFactorAuthenticatorDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-authenticator-delete.request"; import { TwoFactorAuthenticatorUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-authenticator-update.request"; import { TwoFactorAuthenticatorUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-authenticator-update.response"; import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-authenticator.response"; -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"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -97,7 +96,7 @@ export class TwoFactorSetupAuthenticatorComponent }); constructor( - @Inject(DIALOG_DATA) protected data: AuthResponse, + @Inject(DIALOG_DATA) protected data: TwoFactorSetupDialogData, private dialogRef: DialogRef, twoFactorService: TwoFactorService, i18nService: I18nService, @@ -137,7 +136,7 @@ export class TwoFactorSetupAuthenticatorComponent this.formGroup.controls.token.markAsTouched(); } - async auth(authResponse: AuthResponse) { + async auth(authResponse: TwoFactorSetupDialogData) { super.auth(authResponse); return this.processGetResponse(authResponse.response); } @@ -245,7 +244,7 @@ export class TwoFactorSetupAuthenticatorComponent static open( dialogService: DialogService, - config: DialogConfig>, + config: DialogConfig>, ) { return dialogService.open(TwoFactorSetupAuthenticatorComponent, config); } 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 1f95d4abd2c0..699c0e7ab7db 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 @@ -4,7 +4,7 @@ import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; 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 { TwoFactorService } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorService , TwoFactorSetupDialogData } from "@bitwarden/common/auth/two-factor"; import { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-duo-delete.request"; import { TwoFactorDuoUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-duo-update.request"; import { TwoFactorOrganizationDuoDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-organization-duo-delete.request"; @@ -13,7 +13,6 @@ import { TwoFactorDuoUpdateResponse } from "@bitwarden/common/auth/two-factor/re import { TwoFactorDuoResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-duo.response"; import { TwoFactorOrganizationDuoUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-organization-duo-update.response"; import { TwoFactorOrganizationDuoResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-organization-duo.response"; -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"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -234,6 +233,6 @@ export class TwoFactorSetupDuoComponent } type TwoFactorDuoComponentConfig = { - authResponse: AuthResponse; + authResponse: TwoFactorSetupDialogData; organizationId?: string; }; 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 6e83e950cfca..aa4626cb39df 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 @@ -6,14 +6,13 @@ import { firstValueFrom, map } from "rxjs"; 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 { TwoFactorService } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorService , TwoFactorSetupDialogData } from "@bitwarden/common/auth/two-factor"; import { TwoFactorEmailDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-delete.request"; import { TwoFactorEmailSetupRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-setup.request"; import { TwoFactorEmailUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-update.request"; import { TwoFactorEmailDetailsResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-email-details.response"; import { TwoFactorEmailUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-email-update.response"; import { TwoFactorEmailResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-email.response"; -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"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -73,7 +72,7 @@ export class TwoFactorSetupEmailComponent }); constructor( - @Inject(DIALOG_DATA) protected data: AuthResponse, + @Inject(DIALOG_DATA) protected data: TwoFactorSetupDialogData, twoFactorService: TwoFactorService, i18nService: I18nService, platformUtilsService: PlatformUtilsService, @@ -112,7 +111,7 @@ export class TwoFactorSetupEmailComponent await this.auth(this.data); } - auth(authResponse: AuthResponse) { + auth(authResponse: TwoFactorSetupDialogData) { super.auth(authResponse); return this.processGetResponse(authResponse.response); } @@ -207,11 +206,11 @@ export class TwoFactorSetupEmailComponent */ static open( dialogService: DialogService, - config: DialogConfig>, + config: DialogConfig>, ) { - return dialogService.open>( + return dialogService.open>( TwoFactorSetupEmailComponent, - config as DialogConfig, boolean>, + config as DialogConfig, boolean>, ); } } 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 bc90fef2c87b..db27ad292df8 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 @@ -4,8 +4,7 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { VerificationType } from "@bitwarden/common/auth/enums/verification-type"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; -import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; -import { AuthResponseBase } from "@bitwarden/common/auth/types/auth-response"; +import { TwoFactorService , TwoFactorUserVerificationResult } from "@bitwarden/common/auth/two-factor"; 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"; @@ -45,9 +44,9 @@ export abstract class TwoFactorSetupMethodBaseComponent { protected toastService: ToastService, ) {} - protected auth(authResponse: AuthResponseBase) { - this.secret = authResponse.secret; - this.verificationType = authResponse.verificationType; + protected auth(verificationResult: TwoFactorUserVerificationResult) { + this.secret = verificationResult.secret; + this.verificationType = verificationResult.verificationType; this.authed = true; } 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 275502643c8a..c395ae8599ef 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 @@ -7,7 +7,7 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; import { WebAuthnChallengeResponse } from "@bitwarden/common/auth/models/response/web-authn-challenge.response"; -import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorService , TwoFactorSetupDialogData } from "@bitwarden/common/auth/two-factor"; import { TwoFactorWebAuthnDeleteAllRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-delete-all.request"; import { TwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-delete.request"; import { TwoFactorWebAuthnUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-update.request"; @@ -16,7 +16,6 @@ import { TwoFactorWebAuthnDeleteResponse } from "@bitwarden/common/auth/two-fact import { TwoFactorWebAuthnDetailsResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn-details.response"; import { TwoFactorWebAuthnUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn-update.response"; import { TwoFactorWebAuthnResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn.response"; -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"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -84,7 +83,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom protected formGroup: FormGroup; constructor( - @Inject(DIALOG_DATA) protected data: AuthResponse, + @Inject(DIALOG_DATA) protected data: TwoFactorSetupDialogData, private dialogRef: DialogRef, twoFactorService: TwoFactorService, i18nService: I18nService, @@ -110,7 +109,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom this.auth(data); } - auth(authResponse: AuthResponse) { + auth(authResponse: TwoFactorSetupDialogData) { super.auth(authResponse); this.processGetResponse(authResponse.response); } @@ -323,11 +322,11 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom static open( dialogService: DialogService, - config: DialogConfig>, + config: DialogConfig>, ) { - return dialogService.open>( + return dialogService.open>( TwoFactorSetupWebAuthnComponent, - config as DialogConfig, boolean>, + config as DialogConfig, boolean>, ); } } 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 bcb06b292dc2..b1c103d27c3d 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 @@ -11,13 +11,12 @@ import { import { JslibModule } from "@bitwarden/angular/jslib.module"; 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 { TwoFactorService } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorService , TwoFactorSetupDialogData } from "@bitwarden/common/auth/two-factor"; import { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-yubikey-delete.request"; import { TwoFactorYubiKeyUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-yubikey-update.request"; import { TwoFactorYubiKeyDetailsResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-yubi-key-details.response"; import { TwoFactorYubiKeyUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-yubi-key-update.response"; import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-yubi-key.response"; -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"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -94,7 +93,7 @@ export class TwoFactorSetupYubiKeyComponent } constructor( - @Inject(DIALOG_DATA) protected data: AuthResponse, + @Inject(DIALOG_DATA) protected data: TwoFactorSetupDialogData, twoFactorService: TwoFactorService, i18nService: I18nService, platformUtilsService: PlatformUtilsService, @@ -139,7 +138,7 @@ export class TwoFactorSetupYubiKeyComponent }); } - auth(authResponse: AuthResponse) { + auth(authResponse: TwoFactorSetupDialogData) { super.auth(authResponse); this.processGetResponse(authResponse.response); } @@ -260,11 +259,11 @@ export class TwoFactorSetupYubiKeyComponent static open( dialogService: DialogService, - config: DialogConfig>, + config: DialogConfig>, ) { - return dialogService.open>( + return dialogService.open>( TwoFactorSetupYubiKeyComponent, - config as DialogConfig, boolean>, + config as DialogConfig, boolean>, ); } } 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 1a07d6b3d7a5..bd1b48185d10 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 @@ -22,7 +22,7 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { TwoFactorService, TwoFactorProviders } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorService, TwoFactorProviders , TwoFactorSetupDialogData } from "@bitwarden/common/auth/two-factor"; import { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-duo-delete.request"; import { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-yubikey-delete.request"; import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-authenticator.response"; @@ -30,7 +30,6 @@ import { TwoFactorDuoResponse } from "@bitwarden/common/auth/two-factor/response import { TwoFactorEmailResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-email.response"; import { TwoFactorWebAuthnResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn.response"; import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-yubi-key.response"; -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"; @@ -225,7 +224,7 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { switch (type) { case TwoFactorProviderType.Authenticator: { - const result: AuthResponse = + const result: TwoFactorSetupDialogData = await this.callTwoFactorVerifyDialog(type); if (!result) { return; @@ -243,7 +242,7 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { break; } case TwoFactorProviderType.Yubikey: { - const result: AuthResponse = + const result: TwoFactorSetupDialogData = await this.callTwoFactorVerifyDialog(type); if (!result) { return; @@ -260,7 +259,7 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { break; } case TwoFactorProviderType.Duo: { - const result: AuthResponse = + const result: TwoFactorSetupDialogData = await this.callTwoFactorVerifyDialog(type); if (!result) { return; @@ -282,7 +281,7 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { break; } case TwoFactorProviderType.Email: { - const result: AuthResponse = + const result: TwoFactorSetupDialogData = await this.callTwoFactorVerifyDialog(type); if (!result) { return; @@ -302,7 +301,7 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { break; } case TwoFactorProviderType.WebAuthn: { - const result: AuthResponse = + const result: TwoFactorSetupDialogData = await this.callTwoFactorVerifyDialog(type); if (!result) { return; 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 e922ddf610c5..b49799118300 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,9 +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 { 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 { TwoFactorService , TwoFactorSetupDialogData , TwoFactorResponse } from "@bitwarden/common/auth/two-factor"; import { VerificationWithSecret } from "@bitwarden/common/auth/types/verification"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -132,9 +130,9 @@ export class TwoFactorVerifyComponent { } static open(dialogService: DialogService, config: DialogConfig) { - return dialogService.open, TwoFactorVerifyDialogData>( + return dialogService.open, TwoFactorVerifyDialogData>( TwoFactorVerifyComponent, - config as DialogConfig>, + config as DialogConfig>, ); } } diff --git a/libs/common/src/auth/two-factor/index.ts b/libs/common/src/auth/two-factor/index.ts index 8dce333f5e8b..29764d54f456 100644 --- a/libs/common/src/auth/two-factor/index.ts +++ b/libs/common/src/auth/two-factor/index.ts @@ -2,3 +2,4 @@ export * from "./abstractions"; export * from "./request"; export * from "./response"; export * from "./services"; +export * from "./types"; diff --git a/libs/common/src/auth/two-factor/types/index.ts b/libs/common/src/auth/two-factor/types/index.ts new file mode 100644 index 000000000000..0be08d834fe0 --- /dev/null +++ b/libs/common/src/auth/two-factor/types/index.ts @@ -0,0 +1,2 @@ +export * from "./two-factor-response"; +export * from "./two-factor-setup-dialog-data"; diff --git a/libs/common/src/auth/two-factor/types/two-factor-response.ts b/libs/common/src/auth/two-factor/types/two-factor-response.ts new file mode 100644 index 000000000000..d9b2a6542f41 --- /dev/null +++ b/libs/common/src/auth/two-factor/types/two-factor-response.ts @@ -0,0 +1,16 @@ +import { TwoFactorAuthenticatorResponse } from "../response/two-factor-authenticator.response"; +import { TwoFactorDuoResponse } from "../response/two-factor-duo.response"; +import { TwoFactorEmailResponse } from "../response/two-factor-email.response"; +import { TwoFactorOrganizationDuoResponse } from "../response/two-factor-organization-duo.response"; +import { TwoFactorRecoverResponse } from "../response/two-factor-recover.response"; +import { TwoFactorWebAuthnResponse } from "../response/two-factor-web-authn.response"; +import { TwoFactorYubiKeyResponse } from "../response/two-factor-yubi-key.response"; + +export type TwoFactorResponse = + | TwoFactorRecoverResponse + | TwoFactorDuoResponse + | TwoFactorOrganizationDuoResponse + | TwoFactorEmailResponse + | TwoFactorWebAuthnResponse + | TwoFactorAuthenticatorResponse + | TwoFactorYubiKeyResponse; diff --git a/libs/common/src/auth/two-factor/types/two-factor-setup-dialog-data.ts b/libs/common/src/auth/two-factor/types/two-factor-setup-dialog-data.ts new file mode 100644 index 000000000000..e67741630814 --- /dev/null +++ b/libs/common/src/auth/two-factor/types/two-factor-setup-dialog-data.ts @@ -0,0 +1,23 @@ +import { VerificationType } from "../../enums/verification-type"; + +import { TwoFactorResponse } from "./two-factor-response"; + +/** + * Proof that the user re-authenticated (via master password / OTP) before managing 2FA. + * Threaded into request DTOs for the per-provider PUT/DELETE endpoints via + * `UserVerificationService.buildRequest`. + */ +export type TwoFactorUserVerificationResult = { + secret: string; + verificationType: VerificationType; +}; + +/** + * Payload passed as `DIALOG_DATA` into per-provider 2FA setup dialogs. Bundles the + * user-verification proof with the provider's current server state fetched alongside + * the verification step. + */ +export type TwoFactorSetupDialogData = + TwoFactorUserVerificationResult & { + response: T; + }; diff --git a/libs/common/src/auth/types/auth-response.ts b/libs/common/src/auth/types/auth-response.ts deleted file mode 100644 index b5fad923de6a..000000000000 --- a/libs/common/src/auth/types/auth-response.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { VerificationType } from "../enums/verification-type"; - -import { TwoFactorResponse } from "./two-factor-response"; - -export type AuthResponseBase = { - secret: string; - verificationType: VerificationType; -}; - -export type AuthResponse = AuthResponseBase & { - response: T; -}; diff --git a/libs/common/src/auth/types/two-factor-response.ts b/libs/common/src/auth/types/two-factor-response.ts deleted file mode 100644 index fb09cccc24c5..000000000000 --- a/libs/common/src/auth/types/two-factor-response.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TwoFactorAuthenticatorResponse } from "../two-factor/response/two-factor-authenticator.response"; -import { TwoFactorDuoResponse } from "../two-factor/response/two-factor-duo.response"; -import { TwoFactorEmailResponse } from "../two-factor/response/two-factor-email.response"; -import { TwoFactorOrganizationDuoResponse } from "../two-factor/response/two-factor-organization-duo.response"; -import { TwoFactorRecoverResponse } from "../two-factor/response/two-factor-recover.response"; -import { TwoFactorWebAuthnResponse } from "../two-factor/response/two-factor-web-authn.response"; -import { TwoFactorYubiKeyResponse } from "../two-factor/response/two-factor-yubi-key.response"; - -export type TwoFactorResponse = - | TwoFactorRecoverResponse - | TwoFactorDuoResponse - | TwoFactorOrganizationDuoResponse - | TwoFactorEmailResponse - | TwoFactorWebAuthnResponse - | TwoFactorAuthenticatorResponse - | TwoFactorYubiKeyResponse; From cf079a6488ed4233b136a89e5f8f5b9104455737 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Wed, 24 Jun 2026 17:56:55 -0400 Subject: [PATCH 16/29] PM-38137 - Correct 2FA dialog-data type docs to match constructor-param flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TwoFactorUserVerificationResult feeds the initial GET / WebAuthn challenge POST that mints the user-verification token, not the per-provider PUT/DELETE endpoints — those take the cached UV token string directly. Also clarifies that TwoFactorSetupDialogData is the return type of TwoFactorVerifyDialog before being forwarded as DIALOG_DATA to the per-provider setup dialogs. --- .../types/two-factor-setup-dialog-data.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/libs/common/src/auth/two-factor/types/two-factor-setup-dialog-data.ts b/libs/common/src/auth/two-factor/types/two-factor-setup-dialog-data.ts index e67741630814..110b4e721fd0 100644 --- a/libs/common/src/auth/two-factor/types/two-factor-setup-dialog-data.ts +++ b/libs/common/src/auth/two-factor/types/two-factor-setup-dialog-data.ts @@ -4,8 +4,10 @@ import { TwoFactorResponse } from "./two-factor-response"; /** * Proof that the user re-authenticated (via master password / OTP) before managing 2FA. - * Threaded into request DTOs for the per-provider PUT/DELETE endpoints via - * `UserVerificationService.buildRequest`. + * Consumed by `UserVerificationService.buildRequest` to construct the `SecretVerificationRequest` + * sent to the per-provider GET endpoint (or the WebAuthn challenge POST) that mints the + * user-verification token. Subsequent per-provider PUT/DELETE calls thread that token + * directly, not this result. */ export type TwoFactorUserVerificationResult = { secret: string; @@ -13,9 +15,10 @@ export type TwoFactorUserVerificationResult = { }; /** - * Payload passed as `DIALOG_DATA` into per-provider 2FA setup dialogs. Bundles the - * user-verification proof with the provider's current server state fetched alongside - * the verification step. + * Return type of `TwoFactorVerifyDialogComponent`. Bundles the user-verification proof + * with the provider's current server state fetched alongside the verification step, + * and is then passed as `DIALOG_DATA` into the per-provider 2FA setup dialogs + * (authenticator, email, yubikey, webauthn, duo). */ export type TwoFactorSetupDialogData = TwoFactorUserVerificationResult & { From f60578fb1dcfe3e88d111d0e634d89e5e01df45e Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Wed, 24 Jun 2026 17:58:07 -0400 Subject: [PATCH 17/29] PM-38137 - Split TwoFactorUserVerificationResult into its own file Extracts TwoFactorUserVerificationResult from two-factor-setup-dialog-data.ts into two-factor-user-verification-result.ts so each type lives in its own file. Consumers continue to import from the @bitwarden/common/auth/two-factor barrel and are unchanged. --- libs/common/src/auth/two-factor/types/index.ts | 1 + .../types/two-factor-setup-dialog-data.ts | 15 +-------------- .../types/two-factor-user-verification-result.ts | 13 +++++++++++++ 3 files changed, 15 insertions(+), 14 deletions(-) create mode 100644 libs/common/src/auth/two-factor/types/two-factor-user-verification-result.ts diff --git a/libs/common/src/auth/two-factor/types/index.ts b/libs/common/src/auth/two-factor/types/index.ts index 0be08d834fe0..09b330939524 100644 --- a/libs/common/src/auth/two-factor/types/index.ts +++ b/libs/common/src/auth/two-factor/types/index.ts @@ -1,2 +1,3 @@ export * from "./two-factor-response"; export * from "./two-factor-setup-dialog-data"; +export * from "./two-factor-user-verification-result"; diff --git a/libs/common/src/auth/two-factor/types/two-factor-setup-dialog-data.ts b/libs/common/src/auth/two-factor/types/two-factor-setup-dialog-data.ts index 110b4e721fd0..f217423de196 100644 --- a/libs/common/src/auth/two-factor/types/two-factor-setup-dialog-data.ts +++ b/libs/common/src/auth/two-factor/types/two-factor-setup-dialog-data.ts @@ -1,18 +1,5 @@ -import { VerificationType } from "../../enums/verification-type"; - import { TwoFactorResponse } from "./two-factor-response"; - -/** - * Proof that the user re-authenticated (via master password / OTP) before managing 2FA. - * Consumed by `UserVerificationService.buildRequest` to construct the `SecretVerificationRequest` - * sent to the per-provider GET endpoint (or the WebAuthn challenge POST) that mints the - * user-verification token. Subsequent per-provider PUT/DELETE calls thread that token - * directly, not this result. - */ -export type TwoFactorUserVerificationResult = { - secret: string; - verificationType: VerificationType; -}; +import { TwoFactorUserVerificationResult } from "./two-factor-user-verification-result"; /** * Return type of `TwoFactorVerifyDialogComponent`. Bundles the user-verification proof diff --git a/libs/common/src/auth/two-factor/types/two-factor-user-verification-result.ts b/libs/common/src/auth/two-factor/types/two-factor-user-verification-result.ts new file mode 100644 index 000000000000..12d8a97c512c --- /dev/null +++ b/libs/common/src/auth/two-factor/types/two-factor-user-verification-result.ts @@ -0,0 +1,13 @@ +import { VerificationType } from "../../enums/verification-type"; + +/** + * Proof that the user re-authenticated (via master password / OTP) before managing 2FA. + * Consumed by `UserVerificationService.buildRequest` to construct the `SecretVerificationRequest` + * sent to the per-provider GET endpoint (or the WebAuthn challenge POST) that mints the + * user-verification token. Subsequent per-provider PUT/DELETE calls thread that token + * directly, not this result. + */ +export type TwoFactorUserVerificationResult = { + secret: string; + verificationType: VerificationType; +}; From 9b56f34f32698ad62ba9656ba947c9663677363d Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Wed, 24 Jun 2026 18:10:32 -0400 Subject: [PATCH 18/29] PM-38137 - Convert two-factor API service imports to relative paths Restores relative imports inside the three two-factor API service files to match the pattern that landed on main. Sibling two-factor service files were already relative; these three drifted to absolute @bitwarden/common paths during the refactor. --- .../abstractions/two-factor-api.service.ts | 64 ++++++++--------- .../default-two-factor-api.service.spec.ts | 66 +++++++++--------- .../default-two-factor-api.service.ts | 69 +++++++++---------- 3 files changed, 99 insertions(+), 100 deletions(-) diff --git a/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts b/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts index 376cae0b582b..32d252da676e 100644 --- a/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts +++ b/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts @@ -1,35 +1,35 @@ -import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; -import { TwoFactorAuthenticatorDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-authenticator-delete.request"; -import { TwoFactorAuthenticatorUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-authenticator-update.request"; -import { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-duo-delete.request"; -import { TwoFactorDuoUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-duo-update.request"; -import { TwoFactorEmailDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-delete.request"; -import { TwoFactorEmailLoginRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-login.request"; -import { TwoFactorEmailSetupRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-setup.request"; -import { TwoFactorEmailUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-update.request"; -import { TwoFactorOrganizationDuoDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-organization-duo-delete.request"; -import { TwoFactorWebAuthnDeleteAllRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-delete-all.request"; -import { TwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-delete.request"; -import { TwoFactorWebAuthnUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-update.request"; -import { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-yubikey-delete.request"; -import { TwoFactorYubiKeyUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-yubikey-update.request"; -import { TwoFactorAuthenticatorUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-authenticator-update.response"; -import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-authenticator.response"; -import { TwoFactorDuoUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-duo-update.response"; -import { TwoFactorDuoResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-duo.response"; -import { TwoFactorEmailUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-email-update.response"; -import { TwoFactorEmailResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-email.response"; -import { TwoFactorOrganizationDuoUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-organization-duo-update.response"; -import { TwoFactorOrganizationDuoResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-organization-duo.response"; -import { TwoFactorProviderResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-provider.response"; -import { TwoFactorRecoverResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-recover.response"; -import { TwoFactorWebAuthnChallengeResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn-challenge.response"; -import { TwoFactorWebAuthnDeleteResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn-delete.response"; -import { TwoFactorWebAuthnUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn-update.response"; -import { TwoFactorWebAuthnResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn.response"; -import { TwoFactorYubiKeyUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-yubi-key-update.response"; -import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-yubi-key.response"; -import { ListResponse } from "@bitwarden/common/models/response/list.response"; +import { ListResponse } from "../../../models/response/list.response"; +import { SecretVerificationRequest } from "../../models/request/secret-verification.request"; +import { TwoFactorAuthenticatorDeleteRequest } from "../request/two-factor-authenticator-delete.request"; +import { TwoFactorAuthenticatorUpdateRequest } from "../request/two-factor-authenticator-update.request"; +import { TwoFactorDuoDeleteRequest } from "../request/two-factor-duo-delete.request"; +import { TwoFactorDuoUpdateRequest } from "../request/two-factor-duo-update.request"; +import { TwoFactorEmailDeleteRequest } from "../request/two-factor-email-delete.request"; +import { TwoFactorEmailLoginRequest } from "../request/two-factor-email-login.request"; +import { TwoFactorEmailSetupRequest } from "../request/two-factor-email-setup.request"; +import { TwoFactorEmailUpdateRequest } from "../request/two-factor-email-update.request"; +import { TwoFactorOrganizationDuoDeleteRequest } from "../request/two-factor-organization-duo-delete.request"; +import { TwoFactorWebAuthnDeleteAllRequest } from "../request/two-factor-web-authn-delete-all.request"; +import { TwoFactorWebAuthnDeleteRequest } from "../request/two-factor-web-authn-delete.request"; +import { TwoFactorWebAuthnUpdateRequest } from "../request/two-factor-web-authn-update.request"; +import { TwoFactorYubiKeyDeleteRequest } from "../request/two-factor-yubikey-delete.request"; +import { TwoFactorYubiKeyUpdateRequest } from "../request/two-factor-yubikey-update.request"; +import { TwoFactorAuthenticatorUpdateResponse } from "../response/two-factor-authenticator-update.response"; +import { TwoFactorAuthenticatorResponse } from "../response/two-factor-authenticator.response"; +import { TwoFactorDuoUpdateResponse } from "../response/two-factor-duo-update.response"; +import { TwoFactorDuoResponse } from "../response/two-factor-duo.response"; +import { TwoFactorEmailUpdateResponse } from "../response/two-factor-email-update.response"; +import { TwoFactorEmailResponse } from "../response/two-factor-email.response"; +import { TwoFactorOrganizationDuoUpdateResponse } from "../response/two-factor-organization-duo-update.response"; +import { TwoFactorOrganizationDuoResponse } from "../response/two-factor-organization-duo.response"; +import { TwoFactorProviderResponse } from "../response/two-factor-provider.response"; +import { TwoFactorRecoverResponse } from "../response/two-factor-recover.response"; +import { TwoFactorWebAuthnChallengeResponse } from "../response/two-factor-web-authn-challenge.response"; +import { TwoFactorWebAuthnDeleteResponse } from "../response/two-factor-web-authn-delete.response"; +import { TwoFactorWebAuthnUpdateResponse } from "../response/two-factor-web-authn-update.response"; +import { TwoFactorWebAuthnResponse } from "../response/two-factor-web-authn.response"; +import { TwoFactorYubiKeyUpdateResponse } from "../response/two-factor-yubi-key-update.response"; +import { TwoFactorYubiKeyResponse } from "../response/two-factor-yubi-key.response"; /** * Service abstraction for two-factor authentication API operations. diff --git a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts index d95c8c6d8cdb..cca14167fcaf 100644 --- a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts +++ b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts @@ -1,38 +1,38 @@ import { mock, MockProxy } from "jest-mock-extended"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; -import { TwoFactorAuthenticatorDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-authenticator-delete.request"; -import { TwoFactorAuthenticatorUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-authenticator-update.request"; -import { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-duo-delete.request"; -import { TwoFactorDuoUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-duo-update.request"; -import { TwoFactorEmailDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-delete.request"; -import { TwoFactorEmailLoginRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-login.request"; -import { TwoFactorEmailSetupRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-setup.request"; -import { TwoFactorEmailUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-update.request"; -import { TwoFactorOrganizationDuoDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-organization-duo-delete.request"; -import { TwoFactorWebAuthnDeleteAllRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-delete-all.request"; -import { TwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-delete.request"; -import { TwoFactorWebAuthnUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-update.request"; -import { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-yubikey-delete.request"; -import { TwoFactorYubiKeyUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-yubikey-update.request"; -import { TwoFactorAuthenticatorUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-authenticator-update.response"; -import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-authenticator.response"; -import { TwoFactorDuoUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-duo-update.response"; -import { TwoFactorDuoResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-duo.response"; -import { TwoFactorEmailUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-email-update.response"; -import { TwoFactorEmailResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-email.response"; -import { TwoFactorOrganizationDuoUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-organization-duo-update.response"; -import { TwoFactorOrganizationDuoResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-organization-duo.response"; -import { TwoFactorProviderResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-provider.response"; -import { TwoFactorRecoverResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-recover.response"; -import { TwoFactorWebAuthnChallengeResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn-challenge.response"; -import { TwoFactorWebAuthnDeleteResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn-delete.response"; -import { TwoFactorWebAuthnUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn-update.response"; -import { TwoFactorWebAuthnResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn.response"; -import { TwoFactorYubiKeyUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-yubi-key-update.response"; -import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-yubi-key.response"; -import { ListResponse } from "@bitwarden/common/models/response/list.response"; +import { ApiService } from "../../../abstractions/api.service"; +import { ListResponse } from "../../../models/response/list.response"; +import { SecretVerificationRequest } from "../../models/request/secret-verification.request"; +import { TwoFactorAuthenticatorDeleteRequest } from "../request/two-factor-authenticator-delete.request"; +import { TwoFactorAuthenticatorUpdateRequest } from "../request/two-factor-authenticator-update.request"; +import { TwoFactorDuoDeleteRequest } from "../request/two-factor-duo-delete.request"; +import { TwoFactorDuoUpdateRequest } from "../request/two-factor-duo-update.request"; +import { TwoFactorEmailDeleteRequest } from "../request/two-factor-email-delete.request"; +import { TwoFactorEmailLoginRequest } from "../request/two-factor-email-login.request"; +import { TwoFactorEmailSetupRequest } from "../request/two-factor-email-setup.request"; +import { TwoFactorEmailUpdateRequest } from "../request/two-factor-email-update.request"; +import { TwoFactorOrganizationDuoDeleteRequest } from "../request/two-factor-organization-duo-delete.request"; +import { TwoFactorWebAuthnDeleteAllRequest } from "../request/two-factor-web-authn-delete-all.request"; +import { TwoFactorWebAuthnDeleteRequest } from "../request/two-factor-web-authn-delete.request"; +import { TwoFactorWebAuthnUpdateRequest } from "../request/two-factor-web-authn-update.request"; +import { TwoFactorYubiKeyDeleteRequest } from "../request/two-factor-yubikey-delete.request"; +import { TwoFactorYubiKeyUpdateRequest } from "../request/two-factor-yubikey-update.request"; +import { TwoFactorAuthenticatorUpdateResponse } from "../response/two-factor-authenticator-update.response"; +import { TwoFactorAuthenticatorResponse } from "../response/two-factor-authenticator.response"; +import { TwoFactorDuoUpdateResponse } from "../response/two-factor-duo-update.response"; +import { TwoFactorDuoResponse } from "../response/two-factor-duo.response"; +import { TwoFactorEmailUpdateResponse } from "../response/two-factor-email-update.response"; +import { TwoFactorEmailResponse } from "../response/two-factor-email.response"; +import { TwoFactorOrganizationDuoUpdateResponse } from "../response/two-factor-organization-duo-update.response"; +import { TwoFactorOrganizationDuoResponse } from "../response/two-factor-organization-duo.response"; +import { TwoFactorProviderResponse } from "../response/two-factor-provider.response"; +import { TwoFactorRecoverResponse } from "../response/two-factor-recover.response"; +import { TwoFactorWebAuthnChallengeResponse } from "../response/two-factor-web-authn-challenge.response"; +import { TwoFactorWebAuthnDeleteResponse } from "../response/two-factor-web-authn-delete.response"; +import { TwoFactorWebAuthnUpdateResponse } from "../response/two-factor-web-authn-update.response"; +import { TwoFactorWebAuthnResponse } from "../response/two-factor-web-authn.response"; +import { TwoFactorYubiKeyUpdateResponse } from "../response/two-factor-yubi-key-update.response"; +import { TwoFactorYubiKeyResponse } from "../response/two-factor-yubi-key.response"; import { DefaultTwoFactorApiService } from "./default-two-factor-api.service"; diff --git a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts index 5f336e6daeb5..acdd45dabaf8 100644 --- a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts +++ b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts @@ -1,39 +1,38 @@ -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; -import { TwoFactorAuthenticatorDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-authenticator-delete.request"; -import { TwoFactorAuthenticatorUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-authenticator-update.request"; -import { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-duo-delete.request"; -import { TwoFactorDuoUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-duo-update.request"; -import { TwoFactorEmailDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-delete.request"; -import { TwoFactorEmailLoginRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-login.request"; -import { TwoFactorEmailSetupRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-setup.request"; -import { TwoFactorEmailUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-update.request"; -import { TwoFactorOrganizationDuoDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-organization-duo-delete.request"; -import { TwoFactorWebAuthnDeleteAllRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-delete-all.request"; -import { TwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-delete.request"; -import { TwoFactorWebAuthnUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-update.request"; -import { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-yubikey-delete.request"; -import { TwoFactorYubiKeyUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-yubikey-update.request"; -import { TwoFactorAuthenticatorUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-authenticator-update.response"; -import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-authenticator.response"; -import { TwoFactorDuoUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-duo-update.response"; -import { TwoFactorDuoResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-duo.response"; -import { TwoFactorEmailUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-email-update.response"; -import { TwoFactorEmailResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-email.response"; -import { TwoFactorOrganizationDuoUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-organization-duo-update.response"; -import { TwoFactorOrganizationDuoResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-organization-duo.response"; -import { TwoFactorProviderResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-provider.response"; -import { TwoFactorRecoverResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-recover.response"; -import { TwoFactorWebAuthnChallengeResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn-challenge.response"; -import { TwoFactorWebAuthnDeleteResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn-delete.response"; -import { TwoFactorWebAuthnUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn-update.response"; -import { TwoFactorWebAuthnResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn.response"; -import { TwoFactorYubiKeyUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-yubi-key-update.response"; -import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-yubi-key.response"; -import { ListResponse } from "@bitwarden/common/models/response/list.response"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; - +import { ApiService } from "../../../abstractions/api.service"; +import { ListResponse } from "../../../models/response/list.response"; +import { Utils } from "../../../platform/misc/utils"; +import { SecretVerificationRequest } from "../../models/request/secret-verification.request"; import { TwoFactorApiService } from "../abstractions/two-factor-api.service"; +import { TwoFactorAuthenticatorDeleteRequest } from "../request/two-factor-authenticator-delete.request"; +import { TwoFactorAuthenticatorUpdateRequest } from "../request/two-factor-authenticator-update.request"; +import { TwoFactorDuoDeleteRequest } from "../request/two-factor-duo-delete.request"; +import { TwoFactorDuoUpdateRequest } from "../request/two-factor-duo-update.request"; +import { TwoFactorEmailDeleteRequest } from "../request/two-factor-email-delete.request"; +import { TwoFactorEmailLoginRequest } from "../request/two-factor-email-login.request"; +import { TwoFactorEmailSetupRequest } from "../request/two-factor-email-setup.request"; +import { TwoFactorEmailUpdateRequest } from "../request/two-factor-email-update.request"; +import { TwoFactorOrganizationDuoDeleteRequest } from "../request/two-factor-organization-duo-delete.request"; +import { TwoFactorWebAuthnDeleteAllRequest } from "../request/two-factor-web-authn-delete-all.request"; +import { TwoFactorWebAuthnDeleteRequest } from "../request/two-factor-web-authn-delete.request"; +import { TwoFactorWebAuthnUpdateRequest } from "../request/two-factor-web-authn-update.request"; +import { TwoFactorYubiKeyDeleteRequest } from "../request/two-factor-yubikey-delete.request"; +import { TwoFactorYubiKeyUpdateRequest } from "../request/two-factor-yubikey-update.request"; +import { TwoFactorAuthenticatorUpdateResponse } from "../response/two-factor-authenticator-update.response"; +import { TwoFactorAuthenticatorResponse } from "../response/two-factor-authenticator.response"; +import { TwoFactorDuoUpdateResponse } from "../response/two-factor-duo-update.response"; +import { TwoFactorDuoResponse } from "../response/two-factor-duo.response"; +import { TwoFactorEmailUpdateResponse } from "../response/two-factor-email-update.response"; +import { TwoFactorEmailResponse } from "../response/two-factor-email.response"; +import { TwoFactorOrganizationDuoUpdateResponse } from "../response/two-factor-organization-duo-update.response"; +import { TwoFactorOrganizationDuoResponse } from "../response/two-factor-organization-duo.response"; +import { TwoFactorProviderResponse } from "../response/two-factor-provider.response"; +import { TwoFactorRecoverResponse } from "../response/two-factor-recover.response"; +import { TwoFactorWebAuthnChallengeResponse } from "../response/two-factor-web-authn-challenge.response"; +import { TwoFactorWebAuthnDeleteResponse } from "../response/two-factor-web-authn-delete.response"; +import { TwoFactorWebAuthnUpdateResponse } from "../response/two-factor-web-authn-update.response"; +import { TwoFactorWebAuthnResponse } from "../response/two-factor-web-authn.response"; +import { TwoFactorYubiKeyUpdateResponse } from "../response/two-factor-yubi-key-update.response"; +import { TwoFactorYubiKeyResponse } from "../response/two-factor-yubi-key.response"; export class DefaultTwoFactorApiService implements TwoFactorApiService { constructor(private apiService: ApiService) {} From 85724e0dd3f2df84c80ca47c8779f17b48da64b3 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Wed, 24 Jun 2026 19:28:13 -0400 Subject: [PATCH 19/29] PM-38137 - Drop !: on cached userVerificationToken in 2FA setup components Replaces the definite-assignment assertion on the cached userVerificationToken with an honest string | undefined type and a requireUserVerificationToken() helper that throws if the field hasn't been populated yet. Aligns with the typescript-strict ADR (!: is reserved for required @Input properties) and turns a silent undefined-into-request hazard into an explicit runtime error. The base-class composition refactor tracked by PM-39385 will eventually eliminate the cached field entirely. --- ...wo-factor-setup-authenticator.component.ts | 4 ++-- .../two-factor-setup-duo.component.ts | 19 ++++++++++++++----- .../two-factor-setup-email.component.ts | 17 ++++++++++++----- .../two-factor-setup-webauthn.component.ts | 17 ++++++++++++----- .../two-factor-setup-yubikey.component.ts | 15 +++++++++++---- 5 files changed, 51 insertions(+), 21 deletions(-) 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 b81a9e642cec..2d5b1cd4cf44 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 @@ -9,7 +9,7 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; 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 { TwoFactorService , TwoFactorSetupDialogData } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorService, TwoFactorSetupDialogData } from "@bitwarden/common/auth/two-factor"; import { TwoFactorAuthenticatorDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-authenticator-delete.request"; import { TwoFactorAuthenticatorUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-authenticator-update.request"; import { TwoFactorAuthenticatorUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-authenticator-update.response"; @@ -85,7 +85,7 @@ export class TwoFactorSetupAuthenticatorComponent @Output() onChangeStatus = new EventEmitter(); type = TwoFactorProviderType.Authenticator; key: string; - private userVerificationToken!: string; + private userVerificationToken: string | undefined; override componentName = "app-two-factor-authenticator"; qrScriptError = false; 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 699c0e7ab7db..5ab00f48a7c5 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 @@ -4,7 +4,7 @@ import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; 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 { TwoFactorService , TwoFactorSetupDialogData } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorService, TwoFactorSetupDialogData } from "@bitwarden/common/auth/two-factor"; import { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-duo-delete.request"; import { TwoFactorDuoUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-duo-update.request"; import { TwoFactorOrganizationDuoDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-organization-duo-delete.request"; @@ -74,7 +74,14 @@ export class TwoFactorSetupDuoComponent host: ["", [Validators.required]], }); override componentName = "app-two-factor-duo"; - private userVerificationToken!: string; + private userVerificationToken: string | undefined; + + private requireUserVerificationToken(): string { + if (this.userVerificationToken === undefined) { + throw new Error("User verification token is missing"); + } + return this.userVerificationToken; + } constructor( @Inject(DIALOG_DATA) protected data: TwoFactorDuoComponentConfig, @@ -150,7 +157,7 @@ export class TwoFactorSetupDuoComponent this.clientId, this.clientSecret, this.host, - this.userVerificationToken, + this.requireUserVerificationToken(), ); let response: TwoFactorDuoUpdateResponseUnion; @@ -180,10 +187,12 @@ export class TwoFactorSetupDuoComponent } if (this.organizationId != null) { - const request = new TwoFactorOrganizationDuoDeleteRequest(this.userVerificationToken); + const request = new TwoFactorOrganizationDuoDeleteRequest( + this.requireUserVerificationToken(), + ); await this.twoFactorService.deleteTwoFactorOrganizationDuo(this.organizationId, request); } else { - const request = new TwoFactorDuoDeleteRequest(this.userVerificationToken); + const request = new TwoFactorDuoDeleteRequest(this.requireUserVerificationToken()); await this.twoFactorService.deleteTwoFactorDuo(request); } 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 aa4626cb39df..52203a1aa57e 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 @@ -6,7 +6,7 @@ import { firstValueFrom, map } from "rxjs"; 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 { TwoFactorService , TwoFactorSetupDialogData } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorService, TwoFactorSetupDialogData } from "@bitwarden/common/auth/two-factor"; import { TwoFactorEmailDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-delete.request"; import { TwoFactorEmailSetupRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-setup.request"; import { TwoFactorEmailUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-update.request"; @@ -65,7 +65,14 @@ export class TwoFactorSetupEmailComponent sentEmail: string = ""; emailPromise: Promise | undefined; override componentName = "app-two-factor-email"; - private userVerificationToken!: string; + private userVerificationToken: string | undefined; + + private requireUserVerificationToken(): string { + if (this.userVerificationToken === undefined) { + throw new Error("User verification token is missing"); + } + return this.userVerificationToken; + } formGroup = this.formBuilder.group({ token: ["", [Validators.required]], email: ["", [Validators.email, Validators.required]], @@ -136,7 +143,7 @@ export class TwoFactorSetupEmailComponent } sendEmail = async () => { - const request = new TwoFactorEmailSetupRequest(this.email, this.userVerificationToken); + const request = new TwoFactorEmailSetupRequest(this.email, this.requireUserVerificationToken()); this.emailPromise = this.twoFactorService.postTwoFactorEmailSetup(request); await this.emailPromise; this.sentEmail = this.email; @@ -146,7 +153,7 @@ export class TwoFactorSetupEmailComponent const request = new TwoFactorEmailUpdateRequest( this.token, this.email, - this.userVerificationToken, + this.requireUserVerificationToken(), ); const response = await this.twoFactorService.putTwoFactorEmail(request); @@ -165,7 +172,7 @@ export class TwoFactorSetupEmailComponent return; } - const request = new TwoFactorEmailDeleteRequest(this.userVerificationToken); + const request = new TwoFactorEmailDeleteRequest(this.requireUserVerificationToken()); await this.twoFactorService.deleteTwoFactorEmail(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 c395ae8599ef..e8f7b755e708 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 @@ -7,7 +7,7 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; import { WebAuthnChallengeResponse } from "@bitwarden/common/auth/models/response/web-authn-challenge.response"; -import { TwoFactorService , TwoFactorSetupDialogData } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorService, TwoFactorSetupDialogData } from "@bitwarden/common/auth/two-factor"; import { TwoFactorWebAuthnDeleteAllRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-delete-all.request"; import { TwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-delete.request"; import { TwoFactorWebAuthnUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-update.request"; @@ -76,7 +76,14 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom webAuthnListening: boolean = false; webAuthnResponse: PublicKeyCredential | null = null; challengePromise: Promise | undefined; - private userVerificationToken!: string; + private userVerificationToken: string | undefined; + + private requireUserVerificationToken(): string { + if (this.userVerificationToken === undefined) { + throw new Error("User verification token is missing"); + } + return this.userVerificationToken; + } override componentName = "app-two-factor-webauthn"; @@ -131,7 +138,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom this.webAuthnResponse, this.formGroup.value.name || "", this.keyIdAvailable, - this.userVerificationToken, + this.requireUserVerificationToken(), ); const response = await this.twoFactorService.putTwoFactorWebAuthn(request); @@ -165,7 +172,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom // Server's per-credential DELETE refuses to remove the last registered credential // (lockout-prevention), so the only path to disable WebAuthn entirely is the bulk endpoint. - const request = new TwoFactorWebAuthnDeleteAllRequest(this.userVerificationToken); + const request = new TwoFactorWebAuthnDeleteAllRequest(this.requireUserVerificationToken()); await this.twoFactorService.deleteTwoFactorWebAuthnAll(request); this.enabled = false; this.toastService.showToast({ @@ -191,7 +198,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom if (!confirmed) { return; } - const request = new TwoFactorWebAuthnDeleteRequest(key.id, this.userVerificationToken); + const request = new TwoFactorWebAuthnDeleteRequest(key.id, this.requireUserVerificationToken()); try { key.removePromise = this.twoFactorService.deleteTwoFactorWebAuthn(request); const response = await key.removePromise; 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 b1c103d27c3d..eb2803d9727c 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 @@ -11,7 +11,7 @@ import { import { JslibModule } from "@bitwarden/angular/jslib.module"; 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 { TwoFactorService , TwoFactorSetupDialogData } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorService, TwoFactorSetupDialogData } from "@bitwarden/common/auth/two-factor"; import { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-yubikey-delete.request"; import { TwoFactorYubiKeyUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-yubikey-update.request"; import { TwoFactorYubiKeyDetailsResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-yubi-key-details.response"; @@ -74,7 +74,14 @@ export class TwoFactorSetupYubiKeyComponent type = TwoFactorProviderType.Yubikey; keys: Key[] = []; anyKeyHasNfc = false; - private userVerificationToken!: string; + private userVerificationToken: string | undefined; + + private requireUserVerificationToken(): string { + if (this.userVerificationToken === undefined) { + throw new Error("User verification token is missing"); + } + return this.userVerificationToken; + } override componentName = "app-two-factor-yubikey"; formGroup: @@ -176,7 +183,7 @@ export class TwoFactorSetupYubiKeyComponent keys != null && keys.length > 3 ? (keys[3]?.key ?? "") : "", keys != null && keys.length > 4 ? (keys[4]?.key ?? "") : "", this.formGroup.value.anyKeyHasNfc ?? false, - this.userVerificationToken, + this.requireUserVerificationToken(), ); this.processUpdateResponse(await this.twoFactorService.putTwoFactorYubiKey(request)); @@ -200,7 +207,7 @@ export class TwoFactorSetupYubiKeyComponent return; } - const request = new TwoFactorYubiKeyDeleteRequest(this.userVerificationToken); + const request = new TwoFactorYubiKeyDeleteRequest(this.requireUserVerificationToken()); await this.twoFactorService.deleteTwoFactorYubiKey(request); this.enabled = false; this.toastService.showToast({ From daabc4ff34519810aa2de1ac87159bdb6ff32c13 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Wed, 24 Jun 2026 19:42:43 -0400 Subject: [PATCH 20/29] PM-38137 - Rename applyXxxState param to details for clarity The applyXxxState helpers in the Duo, Email, WebAuthn, and YubiKey setup components took a TwoFactorXxxDetailsResponse but named the parameter after the wrapper field (yubiKey, duo, emailData, webAuthn), which made accesses like yubiKey.key1 read as if poking the wrapper rather than the details. Renames the parameter to details across all four; the enclosing function name already establishes which provider it is. --- .../two-factor/two-factor-setup-duo.component.ts | 10 +++++----- .../two-factor-setup-email.component.ts | 6 +++--- .../two-factor-setup-webauthn.component.ts | 12 ++++++------ .../two-factor-setup-yubikey.component.ts | 16 ++++++++-------- 4 files changed, 22 insertions(+), 22 deletions(-) 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 5ab00f48a7c5..01dfec6c3c5a 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 @@ -218,11 +218,11 @@ export class TwoFactorSetupDuoComponent this.applyDuoState(response.duo); } - private applyDuoState(duo: TwoFactorDuoDetailsResponse) { - this.clientId = duo.clientId; - this.clientSecret = duo.clientSecret; - this.host = duo.host; - this.enabled = duo.enabled; + private applyDuoState(details: TwoFactorDuoDetailsResponse) { + this.clientId = details.clientId; + this.clientSecret = details.clientSecret; + this.host = details.host; + this.enabled = details.enabled; } /** 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 52203a1aa57e..5640b2e79721 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 @@ -196,10 +196,10 @@ export class TwoFactorSetupEmailComponent await this.applyEmailState(response.email); } - private async applyEmailState(emailData: TwoFactorEmailDetailsResponse) { + private async applyEmailState(details: TwoFactorEmailDetailsResponse) { this.token = null; - this.email = emailData.email; - this.enabled = emailData.enabled; + this.email = details.email; + this.enabled = details.enabled; if (!this.enabled && (this.email == null || this.email === "")) { this.email = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.email)), 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 e8f7b755e708..daaf78917510 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 @@ -279,9 +279,9 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom this.applyWebAuthnState(response.webAuthn); } - private applyWebAuthnState(webAuthn: TwoFactorWebAuthnDetailsResponse) { - if (!webAuthn.keys || webAuthn.keys.length === 0) { - webAuthn.keys = []; + private applyWebAuthnState(details: TwoFactorWebAuthnDetailsResponse) { + if (!details.keys || details.keys.length === 0) { + details.keys = []; } this.resetWebAuthn(); this.keys = []; @@ -294,7 +294,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom this.keysConfiguredCount = 0; // Build configured keys - for (const key of webAuthn.keys) { + for (const key of details.keys) { this.keysConfiguredCount++; this.keys.push({ id: key.id, @@ -311,7 +311,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom // While we don't have any technical constraints _at this time_, we should avoid // unbounded growth of key IDs over time as users add/remove keys; // this strategy gap-fills key IDs. - const existingIds = new Set(webAuthn.keys.map((k) => k.id)); + const existingIds = new Set(details.keys.map((k) => k.id)); const nextId = this.findNextAvailableKeyId(existingIds); // Add unconfigured slot, which can be used to add a new key @@ -323,7 +323,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom }); this.keyIdAvailable = nextId; - this.enabled = webAuthn.enabled; + this.enabled = details.enabled; this.onUpdated.emit(this.enabled); } 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 eb2803d9727c..65c3403ade91 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 @@ -241,15 +241,15 @@ export class TwoFactorSetupYubiKeyComponent this.applyYubiKeyState(response.yubiKey); } - private applyYubiKeyState(yubiKey: TwoFactorYubiKeyDetailsResponse) { - this.enabled = yubiKey.enabled; - this.anyKeyHasNfc = yubiKey.nfc || !yubiKey.enabled; + private applyYubiKeyState(details: TwoFactorYubiKeyDetailsResponse) { + this.enabled = details.enabled; + this.anyKeyHasNfc = details.nfc || !details.enabled; this.keys = [ - { key: yubiKey.key1, existingKey: this.padRight(yubiKey.key1) }, - { key: yubiKey.key2, existingKey: this.padRight(yubiKey.key2) }, - { key: yubiKey.key3, existingKey: this.padRight(yubiKey.key3) }, - { key: yubiKey.key4, existingKey: this.padRight(yubiKey.key4) }, - { key: yubiKey.key5, existingKey: this.padRight(yubiKey.key5) }, + { key: details.key1, existingKey: this.padRight(details.key1) }, + { key: details.key2, existingKey: this.padRight(details.key2) }, + { key: details.key3, existingKey: this.padRight(details.key3) }, + { key: details.key4, existingKey: this.padRight(details.key4) }, + { key: details.key5, existingKey: this.padRight(details.key5) }, ]; } From 20ea54c27329904a70f56f5c8120cb4c9d123928 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Wed, 24 Jun 2026 19:46:28 -0400 Subject: [PATCH 21/29] PM-38137 - 2FA Setup comp base - add TODO --- .../two-factor/two-factor-setup-method-base.component.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 db27ad292df8..ced976218b91 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 @@ -4,7 +4,10 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { VerificationType } from "@bitwarden/common/auth/enums/verification-type"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; -import { TwoFactorService , TwoFactorUserVerificationResult } from "@bitwarden/common/auth/two-factor"; +import { + TwoFactorService, + TwoFactorUserVerificationResult, +} from "@bitwarden/common/auth/two-factor"; 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"; @@ -50,6 +53,7 @@ export abstract class TwoFactorSetupMethodBaseComponent { this.authed = true; } + // TODO: PM-39385 - For each subclass, rename disable as delete since they are hard deletes. protected abstract disableMethod(): Promise; protected async buildRequestModel( From 6fdab9644ee98ac130cd414275117dc75453eb8f Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Wed, 24 Jun 2026 19:50:52 -0400 Subject: [PATCH 22/29] PM-38137 - Prefix applyXxxState param with provider name Renames the applyXxxState parameter from details to Details in the Duo, Email, WebAuthn, and YubiKey setup components. Trades a few extra characters per access for clearer reading at the call site (e.g. yubiKeyDetails.key1 over details.key1). --- .../two-factor/two-factor-setup-duo.component.ts | 10 +++++----- .../two-factor-setup-email.component.ts | 6 +++--- .../two-factor-setup-webauthn.component.ts | 12 ++++++------ .../two-factor-setup-yubikey.component.ts | 16 ++++++++-------- 4 files changed, 22 insertions(+), 22 deletions(-) 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 01dfec6c3c5a..ba7870acde70 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 @@ -218,11 +218,11 @@ export class TwoFactorSetupDuoComponent this.applyDuoState(response.duo); } - private applyDuoState(details: TwoFactorDuoDetailsResponse) { - this.clientId = details.clientId; - this.clientSecret = details.clientSecret; - this.host = details.host; - this.enabled = details.enabled; + private applyDuoState(duoDetails: TwoFactorDuoDetailsResponse) { + this.clientId = duoDetails.clientId; + this.clientSecret = duoDetails.clientSecret; + this.host = duoDetails.host; + this.enabled = duoDetails.enabled; } /** 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 5640b2e79721..da5a392c3c5d 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 @@ -196,10 +196,10 @@ export class TwoFactorSetupEmailComponent await this.applyEmailState(response.email); } - private async applyEmailState(details: TwoFactorEmailDetailsResponse) { + private async applyEmailState(emailDetails: TwoFactorEmailDetailsResponse) { this.token = null; - this.email = details.email; - this.enabled = details.enabled; + this.email = emailDetails.email; + this.enabled = emailDetails.enabled; if (!this.enabled && (this.email == null || this.email === "")) { this.email = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.email)), 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 daaf78917510..c14b0ad4aeee 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 @@ -279,9 +279,9 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom this.applyWebAuthnState(response.webAuthn); } - private applyWebAuthnState(details: TwoFactorWebAuthnDetailsResponse) { - if (!details.keys || details.keys.length === 0) { - details.keys = []; + private applyWebAuthnState(webAuthnDetails: TwoFactorWebAuthnDetailsResponse) { + if (!webAuthnDetails.keys || webAuthnDetails.keys.length === 0) { + webAuthnDetails.keys = []; } this.resetWebAuthn(); this.keys = []; @@ -294,7 +294,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom this.keysConfiguredCount = 0; // Build configured keys - for (const key of details.keys) { + for (const key of webAuthnDetails.keys) { this.keysConfiguredCount++; this.keys.push({ id: key.id, @@ -311,7 +311,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom // While we don't have any technical constraints _at this time_, we should avoid // unbounded growth of key IDs over time as users add/remove keys; // this strategy gap-fills key IDs. - const existingIds = new Set(details.keys.map((k) => k.id)); + const existingIds = new Set(webAuthnDetails.keys.map((k) => k.id)); const nextId = this.findNextAvailableKeyId(existingIds); // Add unconfigured slot, which can be used to add a new key @@ -323,7 +323,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom }); this.keyIdAvailable = nextId; - this.enabled = details.enabled; + this.enabled = webAuthnDetails.enabled; this.onUpdated.emit(this.enabled); } 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 65c3403ade91..106bac286dc7 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 @@ -241,15 +241,15 @@ export class TwoFactorSetupYubiKeyComponent this.applyYubiKeyState(response.yubiKey); } - private applyYubiKeyState(details: TwoFactorYubiKeyDetailsResponse) { - this.enabled = details.enabled; - this.anyKeyHasNfc = details.nfc || !details.enabled; + private applyYubiKeyState(yubiKeyDetails: TwoFactorYubiKeyDetailsResponse) { + this.enabled = yubiKeyDetails.enabled; + this.anyKeyHasNfc = yubiKeyDetails.nfc || !yubiKeyDetails.enabled; this.keys = [ - { key: details.key1, existingKey: this.padRight(details.key1) }, - { key: details.key2, existingKey: this.padRight(details.key2) }, - { key: details.key3, existingKey: this.padRight(details.key3) }, - { key: details.key4, existingKey: this.padRight(details.key4) }, - { key: details.key5, existingKey: this.padRight(details.key5) }, + { key: yubiKeyDetails.key1, existingKey: this.padRight(yubiKeyDetails.key1) }, + { key: yubiKeyDetails.key2, existingKey: this.padRight(yubiKeyDetails.key2) }, + { key: yubiKeyDetails.key3, existingKey: this.padRight(yubiKeyDetails.key3) }, + { key: yubiKeyDetails.key4, existingKey: this.padRight(yubiKeyDetails.key4) }, + { key: yubiKeyDetails.key5, existingKey: this.padRight(yubiKeyDetails.key5) }, ]; } From c08eb461abd38602b40ee91767f43af1f78ea3c6 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Wed, 24 Jun 2026 20:02:01 -0400 Subject: [PATCH 23/29] PM-38137 - Rename applyXxxState to applyXxxDetails in 2FA setup components The applyXxxState helpers project a per-provider details payload into local form controls and display flags; "State" misleadingly suggested writes to the app's state framework. Renames to applyXxxDetails across authenticator, duo, email, webauthn, and yubikey setup components. --- .../two-factor-setup-authenticator.component.ts | 12 +++++++++--- .../two-factor/two-factor-setup-duo.component.ts | 6 +++--- .../two-factor/two-factor-setup-email.component.ts | 6 +++--- .../two-factor-setup-webauthn.component.ts | 8 ++++---- .../two-factor/two-factor-setup-yubikey.component.ts | 6 +++--- 5 files changed, 22 insertions(+), 16 deletions(-) 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 2d5b1cd4cf44..c337d05d2f40 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 @@ -190,14 +190,20 @@ export class TwoFactorSetupAuthenticatorComponent private async processGetResponse(response: TwoFactorAuthenticatorResponse) { this.userVerificationToken = response.userVerificationToken; - await this.applyAuthenticatorState(response.authenticator.enabled, response.authenticator.key); + await this.applyAuthenticatorDetails( + response.authenticator.enabled, + response.authenticator.key, + ); } private async processUpdateResponse(response: TwoFactorAuthenticatorUpdateResponse) { - await this.applyAuthenticatorState(response.authenticator.enabled, response.authenticator.key); + await this.applyAuthenticatorDetails( + response.authenticator.enabled, + response.authenticator.key, + ); } - private async applyAuthenticatorState(enabled: boolean, key: string) { + private async applyAuthenticatorDetails(enabled: boolean, key: string) { this.formGroup.get("token").setValue(null); this.enabled = enabled; this.key = key; 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 ba7870acde70..71893883c9eb 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 @@ -211,14 +211,14 @@ export class TwoFactorSetupDuoComponent private processGetResponse(response: TwoFactorDuoResponseUnion) { this.userVerificationToken = response.userVerificationToken; - this.applyDuoState(response.duo); + this.applyDuoDetails(response.duo); } private processUpdateResponse(response: TwoFactorDuoUpdateResponseUnion) { - this.applyDuoState(response.duo); + this.applyDuoDetails(response.duo); } - private applyDuoState(duoDetails: TwoFactorDuoDetailsResponse) { + private applyDuoDetails(duoDetails: TwoFactorDuoDetailsResponse) { this.clientId = duoDetails.clientId; this.clientSecret = duoDetails.clientSecret; this.host = duoDetails.host; 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 da5a392c3c5d..493bb60931b7 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 @@ -189,14 +189,14 @@ export class TwoFactorSetupEmailComponent private async processGetResponse(response: TwoFactorEmailResponse) { this.userVerificationToken = response.userVerificationToken; - await this.applyEmailState(response.email); + await this.applyEmailDetails(response.email); } private async processUpdateResponse(response: TwoFactorEmailUpdateResponse) { - await this.applyEmailState(response.email); + await this.applyEmailDetails(response.email); } - private async applyEmailState(emailDetails: TwoFactorEmailDetailsResponse) { + private async applyEmailDetails(emailDetails: TwoFactorEmailDetailsResponse) { this.token = null; this.email = emailDetails.email; this.enabled = emailDetails.enabled; 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 c14b0ad4aeee..88a38912da70 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 @@ -268,18 +268,18 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom private processGetResponse(response: TwoFactorWebAuthnResponse) { this.userVerificationToken = response.userVerificationToken; - this.applyWebAuthnState(response.webAuthn); + this.applyWebAuthnDetails(response.webAuthn); } private processUpdateResponse(response: TwoFactorWebAuthnUpdateResponse) { - this.applyWebAuthnState(response.webAuthn); + this.applyWebAuthnDetails(response.webAuthn); } private processDeleteResponse(response: TwoFactorWebAuthnDeleteResponse) { - this.applyWebAuthnState(response.webAuthn); + this.applyWebAuthnDetails(response.webAuthn); } - private applyWebAuthnState(webAuthnDetails: TwoFactorWebAuthnDetailsResponse) { + private applyWebAuthnDetails(webAuthnDetails: TwoFactorWebAuthnDetailsResponse) { if (!webAuthnDetails.keys || webAuthnDetails.keys.length === 0) { webAuthnDetails.keys = []; } 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 106bac286dc7..67bd41194dd4 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 @@ -234,14 +234,14 @@ export class TwoFactorSetupYubiKeyComponent private processGetResponse(response: TwoFactorYubiKeyResponse) { this.userVerificationToken = response.userVerificationToken; - this.applyYubiKeyState(response.yubiKey); + this.applyYubiKeyDetails(response.yubiKey); } private processUpdateResponse(response: TwoFactorYubiKeyUpdateResponse) { - this.applyYubiKeyState(response.yubiKey); + this.applyYubiKeyDetails(response.yubiKey); } - private applyYubiKeyState(yubiKeyDetails: TwoFactorYubiKeyDetailsResponse) { + private applyYubiKeyDetails(yubiKeyDetails: TwoFactorYubiKeyDetailsResponse) { this.enabled = yubiKeyDetails.enabled; this.anyKeyHasNfc = yubiKeyDetails.nfc || !yubiKeyDetails.enabled; this.keys = [ From 71e4b64753310bb00188a2eb50441806fd55524d Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Wed, 24 Jun 2026 20:10:39 -0400 Subject: [PATCH 24/29] PM-38137 - Run prettier on 2FA setup/verify components --- .../organizations/settings/two-factor-setup.component.ts | 2 +- .../auth/settings/two-factor/two-factor-setup.component.ts | 6 +++++- .../auth/settings/two-factor/two-factor-verify.component.ts | 6 +++++- 3 files changed, 11 insertions(+), 3 deletions(-) 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 29ecb8544564..b708b7ed9649 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 @@ -14,7 +14,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv 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 { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { TwoFactorService , TwoFactorSetupDialogData } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorService, TwoFactorSetupDialogData } from "@bitwarden/common/auth/two-factor"; import { TwoFactorOrganizationDuoResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-organization-duo.response"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; 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 bd1b48185d10..67a25c785454 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 @@ -22,7 +22,11 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { TwoFactorService, TwoFactorProviders , TwoFactorSetupDialogData } from "@bitwarden/common/auth/two-factor"; +import { + TwoFactorService, + TwoFactorProviders, + TwoFactorSetupDialogData, +} from "@bitwarden/common/auth/two-factor"; import { TwoFactorDuoDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-duo-delete.request"; import { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-yubikey-delete.request"; import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-authenticator.response"; 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 b49799118300..b9faf84dadd3 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,11 @@ 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 { TwoFactorService , TwoFactorSetupDialogData , TwoFactorResponse } from "@bitwarden/common/auth/two-factor"; +import { + TwoFactorService, + TwoFactorSetupDialogData, + TwoFactorResponse, +} from "@bitwarden/common/auth/two-factor"; import { VerificationWithSecret } from "@bitwarden/common/auth/types/verification"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; From ef05dbc5972b5654301026326d778d198ec28303 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Wed, 24 Jun 2026 20:22:17 -0400 Subject: [PATCH 25/29] PM-38137 - Reword 2FA delete doc strings to drop stale disable verbiage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updates the per-provider delete docstrings on the 2FA service abstractions and one inline WebAuthn comment that still described the operation as "disabling the provider" — the operation is a hard delete, not a soft disable. User-facing UI strings (the "Disable" button label, dialog title) and method names that mirror that UI concept are unchanged. --- .../two-factor/two-factor-setup-webauthn.component.ts | 3 ++- .../two-factor/abstractions/two-factor-api.service.ts | 9 ++++----- .../auth/two-factor/abstractions/two-factor.service.ts | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) 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 88a38912da70..384af314115e 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 @@ -171,7 +171,8 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom } // Server's per-credential DELETE refuses to remove the last registered credential - // (lockout-prevention), so the only path to disable WebAuthn entirely is the bulk endpoint. + // (lockout-prevention), so the only path to delete the WebAuthn enrollment entirely is the + // bulk endpoint. const request = new TwoFactorWebAuthnDeleteAllRequest(this.requireUserVerificationToken()); await this.twoFactorService.deleteTwoFactorWebAuthnAll(request); this.enabled = false; diff --git a/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts b/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts index 32d252da676e..1426fa3e4666 100644 --- a/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts +++ b/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts @@ -211,7 +211,7 @@ export abstract class TwoFactorApiService { /** * Removes the Duo two-factor enrollment for the current user. * Requires a user verification token to confirm the operation. Returns 204 No Content. - * Does NOT require premium — disabling must always be available even if premium has lapsed. + * Does NOT require premium — deletion must always be available even if premium has lapsed. * * @param request The request containing the user verification token. */ @@ -260,7 +260,7 @@ export abstract class TwoFactorApiService { /** * Removes the YubiKey two-factor enrollment for the current user. Returns 204 No Content. * Requires a user verification token to confirm the operation. - * Does NOT require premium — disabling must always be available even if premium has lapsed. + * Does NOT require premium — deletion must always be available even if premium has lapsed. * * @param request The request containing the user verification token. */ @@ -295,9 +295,8 @@ export abstract class TwoFactorApiService { /** * Removes the entire WebAuthn (FIDO2) two-factor enrollment for the current user — all - * credentials are removed and the provider is disabled in a single round-trip. The only path - * that can clear the last registered credential, since per-credential delete refuses by design. - * Returns 204 No Content. + * credentials are deleted in a single round-trip. The only path that can clear the last + * registered credential, since per-credential delete refuses by design. Returns 204 No Content. * * @param request The request containing the user verification token. */ 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 index 38d1b91baf7e..6d0442a7d155 100644 --- a/libs/common/src/auth/two-factor/abstractions/two-factor.service.ts +++ b/libs/common/src/auth/two-factor/abstractions/two-factor.service.ts @@ -452,7 +452,7 @@ export abstract class TwoFactorService { /** * Removes the YubiKey two-factor enrollment for the current user. * Requires a user verification token to confirm the operation. Returns 204 No Content. - * Does NOT require premium — disabling must always be available even if premium has lapsed. + * Does NOT require premium — deletion must always be available even if premium has lapsed. * Used for settings management. * * @param request The {@link TwoFactorYubiKeyDeleteRequest} containing the user verification token. @@ -462,7 +462,7 @@ export abstract class TwoFactorService { /** * Removes the Duo two-factor enrollment for the current user. * Requires a user verification token to confirm the operation. Returns 204 No Content. - * Does NOT require premium — disabling must always be available even if premium has lapsed. + * Does NOT require premium — deletion must always be available even if premium has lapsed. * Used for settings management. * * @param request The {@link TwoFactorDuoDeleteRequest} containing the user verification token. @@ -494,9 +494,9 @@ export abstract class TwoFactorService { /** * Removes the entire WebAuthn (FIDO2) two-factor enrollment for the current user — all - * credentials are removed and the provider is disabled in a single round-trip. Returns - * 204 No Content. The only path that can clear the last registered credential, since - * per-credential delete refuses by design. + * credentials are deleted in a single round-trip. Returns 204 No Content. The only path + * that can clear the last registered credential, since per-credential delete refuses by + * design. * Used for settings management. * * @param request The {@link TwoFactorWebAuthnDeleteAllRequest} containing the user verification token. From db712e87b3caa792bef698d39764a303c8a00165 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Wed, 24 Jun 2026 20:29:17 -0400 Subject: [PATCH 26/29] PM-38137 - Inline processUpdate/processDeleteResponse pass-throughs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes the processUpdateResponse and processDeleteResponse helpers in the 5 2FA setup components — each body was a single-line wrapper that called applyXxxDetails(response.xxx). Call sites now apply the details directly, which makes the unwrap visible inline. The processGetResponse helper stays because it bundles two coordinated steps (cache user-verification token + apply details). Also drops now-unused TwoFactor*UpdateResponse imports in each file. --- .../two-factor-setup-authenticator.component.ts | 13 ++++--------- .../two-factor/two-factor-setup-duo.component.ts | 6 +----- .../two-factor/two-factor-setup-email.component.ts | 7 +------ .../two-factor-setup-webauthn.component.ts | 13 ++----------- .../two-factor-setup-yubikey.component.ts | 8 ++------ 5 files changed, 10 insertions(+), 37 deletions(-) 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 c337d05d2f40..9e4e4efa8953 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,6 @@ import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-p import { TwoFactorService, TwoFactorSetupDialogData } from "@bitwarden/common/auth/two-factor"; import { TwoFactorAuthenticatorDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-authenticator-delete.request"; import { TwoFactorAuthenticatorUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-authenticator-update.request"; -import { TwoFactorAuthenticatorUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-authenticator-update.response"; import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-authenticator.response"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -162,7 +161,10 @@ export class TwoFactorSetupAuthenticatorComponent ); const response = await this.twoFactorService.putTwoFactorAuthenticator(request); - await this.processUpdateResponse(response); + await this.applyAuthenticatorDetails( + response.authenticator.enabled, + response.authenticator.key, + ); this.onUpdated.emit(true); } @@ -196,13 +198,6 @@ export class TwoFactorSetupAuthenticatorComponent ); } - private async processUpdateResponse(response: TwoFactorAuthenticatorUpdateResponse) { - await this.applyAuthenticatorDetails( - response.authenticator.enabled, - response.authenticator.key, - ); - } - private async applyAuthenticatorDetails(enabled: boolean, key: string) { this.formGroup.get("token").setValue(null); this.enabled = enabled; 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 71893883c9eb..9d10cadbf407 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 @@ -171,7 +171,7 @@ export class TwoFactorSetupDuoComponent response = await this.twoFactorService.putTwoFactorDuo(request); } - this.processUpdateResponse(response); + this.applyDuoDetails(response.duo); this.onUpdated.emit(true); } @@ -214,10 +214,6 @@ export class TwoFactorSetupDuoComponent this.applyDuoDetails(response.duo); } - private processUpdateResponse(response: TwoFactorDuoUpdateResponseUnion) { - this.applyDuoDetails(response.duo); - } - private applyDuoDetails(duoDetails: TwoFactorDuoDetailsResponse) { this.clientId = duoDetails.clientId; this.clientSecret = duoDetails.clientSecret; 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 493bb60931b7..cd1ac2847061 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 @@ -11,7 +11,6 @@ import { TwoFactorEmailDeleteRequest } from "@bitwarden/common/auth/two-factor/r import { TwoFactorEmailSetupRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-setup.request"; import { TwoFactorEmailUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-email-update.request"; import { TwoFactorEmailDetailsResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-email-details.response"; -import { TwoFactorEmailUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-email-update.response"; import { TwoFactorEmailResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-email.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -157,7 +156,7 @@ export class TwoFactorSetupEmailComponent ); const response = await this.twoFactorService.putTwoFactorEmail(request); - await this.processUpdateResponse(response); + await this.applyEmailDetails(response.email); this.onUpdated.emit(true); } @@ -192,10 +191,6 @@ export class TwoFactorSetupEmailComponent await this.applyEmailDetails(response.email); } - private async processUpdateResponse(response: TwoFactorEmailUpdateResponse) { - await this.applyEmailDetails(response.email); - } - private async applyEmailDetails(emailDetails: TwoFactorEmailDetailsResponse) { this.token = null; this.email = emailDetails.email; 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 384af314115e..21bd11c2bc25 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 @@ -14,7 +14,6 @@ import { TwoFactorWebAuthnUpdateRequest } from "@bitwarden/common/auth/two-facto import { TwoFactorWebAuthnChallengeResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn-challenge.response"; import { TwoFactorWebAuthnDeleteResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn-delete.response"; import { TwoFactorWebAuthnDetailsResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn-details.response"; -import { TwoFactorWebAuthnUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn-update.response"; import { TwoFactorWebAuthnResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-web-authn.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -142,7 +141,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom ); const response = await this.twoFactorService.putTwoFactorWebAuthn(request); - this.processUpdateResponse(response); + this.applyWebAuthnDetails(response.webAuthn); this.toastService.showToast({ title: this.i18nService.t("success"), message: this.i18nService.t("twoFactorProviderEnabled"), @@ -204,7 +203,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom key.removePromise = this.twoFactorService.deleteTwoFactorWebAuthn(request); const response = await key.removePromise; key.removePromise = null; - this.processDeleteResponse(response); + this.applyWebAuthnDetails(response.webAuthn); } catch (e) { this.logService.error(e); } @@ -272,14 +271,6 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom this.applyWebAuthnDetails(response.webAuthn); } - private processUpdateResponse(response: TwoFactorWebAuthnUpdateResponse) { - this.applyWebAuthnDetails(response.webAuthn); - } - - private processDeleteResponse(response: TwoFactorWebAuthnDeleteResponse) { - this.applyWebAuthnDetails(response.webAuthn); - } - private applyWebAuthnDetails(webAuthnDetails: TwoFactorWebAuthnDetailsResponse) { if (!webAuthnDetails.keys || webAuthnDetails.keys.length === 0) { webAuthnDetails.keys = []; 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 67bd41194dd4..2e0578483779 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 @@ -15,7 +15,6 @@ import { TwoFactorService, TwoFactorSetupDialogData } from "@bitwarden/common/au import { TwoFactorYubiKeyDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-yubikey-delete.request"; import { TwoFactorYubiKeyUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-yubikey-update.request"; import { TwoFactorYubiKeyDetailsResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-yubi-key-details.response"; -import { TwoFactorYubiKeyUpdateResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-yubi-key-update.response"; import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/two-factor/response/two-factor-yubi-key.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -186,7 +185,8 @@ export class TwoFactorSetupYubiKeyComponent this.requireUserVerificationToken(), ); - this.processUpdateResponse(await this.twoFactorService.putTwoFactorYubiKey(request)); + const response = await this.twoFactorService.putTwoFactorYubiKey(request); + this.applyYubiKeyDetails(response.yubiKey); this.refreshFormArrayData(); this.toastService.showToast({ title: this.i18nService.t("success"), @@ -237,10 +237,6 @@ export class TwoFactorSetupYubiKeyComponent this.applyYubiKeyDetails(response.yubiKey); } - private processUpdateResponse(response: TwoFactorYubiKeyUpdateResponse) { - this.applyYubiKeyDetails(response.yubiKey); - } - private applyYubiKeyDetails(yubiKeyDetails: TwoFactorYubiKeyDetailsResponse) { this.enabled = yubiKeyDetails.enabled; this.anyKeyHasNfc = yubiKeyDetails.nfc || !yubiKeyDetails.enabled; From c4db105ff411adf3979e94dc3770edf422644799 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Wed, 24 Jun 2026 20:39:19 -0400 Subject: [PATCH 27/29] PM-38137 - Replace endpoint-fragile 2FA response docstrings with single sentences MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each 2FA wrapper response file had a docstring naming the specific server endpoint that produced it (e.g. \`POST /two-factor/get-authenticator\`), which breaks the moment the server renames an endpoint. Replaces with one-sentence descriptions that describe what the response represents — no endpoint coupling. --- .../response/two-factor-authenticator-update.response.ts | 2 +- .../response/two-factor-authenticator.response.ts | 3 +-- .../two-factor/response/two-factor-duo-update.response.ts | 2 +- .../src/auth/two-factor/response/two-factor-duo.response.ts | 3 +-- .../two-factor/response/two-factor-email-update.response.ts | 2 +- .../auth/two-factor/response/two-factor-email.response.ts | 3 +-- .../response/two-factor-organization-duo-update.response.ts | 3 +-- .../response/two-factor-organization-duo.response.ts | 3 +-- .../response/two-factor-web-authn-challenge.response.ts | 4 +--- .../response/two-factor-web-authn-delete.response.ts | 6 ++---- .../response/two-factor-web-authn-update.response.ts | 2 +- .../two-factor/response/two-factor-web-authn.response.ts | 3 +-- .../response/two-factor-yubi-key-update.response.ts | 2 +- .../two-factor/response/two-factor-yubi-key.response.ts | 3 +-- 14 files changed, 15 insertions(+), 26 deletions(-) diff --git a/libs/common/src/auth/two-factor/response/two-factor-authenticator-update.response.ts b/libs/common/src/auth/two-factor/response/two-factor-authenticator-update.response.ts index 0f2152db8f35..f45dcf7af5c4 100644 --- a/libs/common/src/auth/two-factor/response/two-factor-authenticator-update.response.ts +++ b/libs/common/src/auth/two-factor/response/two-factor-authenticator-update.response.ts @@ -3,7 +3,7 @@ import { BaseResponse } from "../../../models/response/base.response"; import { TwoFactorAuthenticatorDetailsResponse } from "./two-factor-authenticator-details.response"; /** - * Response for `PUT /two-factor/authenticator`. Wraps the post-update provider details. + * Response from updating a user's authenticator (TOTP) two factor provider data. */ export class TwoFactorAuthenticatorUpdateResponse extends BaseResponse { authenticator: TwoFactorAuthenticatorDetailsResponse; diff --git a/libs/common/src/auth/two-factor/response/two-factor-authenticator.response.ts b/libs/common/src/auth/two-factor/response/two-factor-authenticator.response.ts index 9689bab0c00f..98dd77ba52ee 100644 --- a/libs/common/src/auth/two-factor/response/two-factor-authenticator.response.ts +++ b/libs/common/src/auth/two-factor/response/two-factor-authenticator.response.ts @@ -3,8 +3,7 @@ import { BaseResponse } from "../../../models/response/base.response"; import { TwoFactorAuthenticatorDetailsResponse } from "./two-factor-authenticator-details.response"; /** - * Response for `POST /two-factor/get-authenticator`. Wraps the provider details and the - * user-verification token minted by the GET endpoint. + * Response from retrieving a user's authenticator (TOTP) two factor provider data. */ export class TwoFactorAuthenticatorResponse extends BaseResponse { authenticator: TwoFactorAuthenticatorDetailsResponse; diff --git a/libs/common/src/auth/two-factor/response/two-factor-duo-update.response.ts b/libs/common/src/auth/two-factor/response/two-factor-duo-update.response.ts index e70de9da09ad..f1d5dfee7294 100644 --- a/libs/common/src/auth/two-factor/response/two-factor-duo-update.response.ts +++ b/libs/common/src/auth/two-factor/response/two-factor-duo-update.response.ts @@ -3,7 +3,7 @@ import { BaseResponse } from "../../../models/response/base.response"; import { TwoFactorDuoDetailsResponse } from "./two-factor-duo-details.response"; /** - * Response for `PUT /two-factor/duo`. Wraps the post-update user-scoped Duo provider details. + * Response from updating a user's Duo two factor provider data. */ export class TwoFactorDuoUpdateResponse extends BaseResponse { duo: TwoFactorDuoDetailsResponse; diff --git a/libs/common/src/auth/two-factor/response/two-factor-duo.response.ts b/libs/common/src/auth/two-factor/response/two-factor-duo.response.ts index df17b8a115cb..b1aa7f20b24e 100644 --- a/libs/common/src/auth/two-factor/response/two-factor-duo.response.ts +++ b/libs/common/src/auth/two-factor/response/two-factor-duo.response.ts @@ -3,8 +3,7 @@ import { BaseResponse } from "../../../models/response/base.response"; import { TwoFactorDuoDetailsResponse } from "./two-factor-duo-details.response"; /** - * Response for `POST /two-factor/get-duo`. Wraps the user-scoped Duo provider details - * and the user-verification token minted by the GET endpoint. + * Response from retrieving a user's Duo two factor provider data. */ export class TwoFactorDuoResponse extends BaseResponse { duo: TwoFactorDuoDetailsResponse; diff --git a/libs/common/src/auth/two-factor/response/two-factor-email-update.response.ts b/libs/common/src/auth/two-factor/response/two-factor-email-update.response.ts index 6a0b422d5dc0..019b6bfff4c7 100644 --- a/libs/common/src/auth/two-factor/response/two-factor-email-update.response.ts +++ b/libs/common/src/auth/two-factor/response/two-factor-email-update.response.ts @@ -3,7 +3,7 @@ import { BaseResponse } from "../../../models/response/base.response"; import { TwoFactorEmailDetailsResponse } from "./two-factor-email-details.response"; /** - * Response for `PUT /two-factor/email`. Wraps the post-update provider details. + * Response from updating a user's email two factor provider data. */ export class TwoFactorEmailUpdateResponse extends BaseResponse { email: TwoFactorEmailDetailsResponse; diff --git a/libs/common/src/auth/two-factor/response/two-factor-email.response.ts b/libs/common/src/auth/two-factor/response/two-factor-email.response.ts index fa809991ab9b..122de1c54739 100644 --- a/libs/common/src/auth/two-factor/response/two-factor-email.response.ts +++ b/libs/common/src/auth/two-factor/response/two-factor-email.response.ts @@ -3,8 +3,7 @@ import { BaseResponse } from "../../../models/response/base.response"; import { TwoFactorEmailDetailsResponse } from "./two-factor-email-details.response"; /** - * Response for `POST /two-factor/get-email`. Wraps the provider details and the - * user-verification token minted by the GET endpoint. + * Response from retrieving a user's email two factor provider data. */ export class TwoFactorEmailResponse extends BaseResponse { email: TwoFactorEmailDetailsResponse; diff --git a/libs/common/src/auth/two-factor/response/two-factor-organization-duo-update.response.ts b/libs/common/src/auth/two-factor/response/two-factor-organization-duo-update.response.ts index de474b941763..588f5e21b807 100644 --- a/libs/common/src/auth/two-factor/response/two-factor-organization-duo-update.response.ts +++ b/libs/common/src/auth/two-factor/response/two-factor-organization-duo-update.response.ts @@ -3,8 +3,7 @@ import { BaseResponse } from "../../../models/response/base.response"; import { TwoFactorDuoDetailsResponse } from "./two-factor-duo-details.response"; /** - * Response for `PUT /organizations/{id}/two-factor/duo`. Wraps the post-update - * organization-scoped Duo provider details. + * Response from updating an organization's Duo two factor provider data. */ export class TwoFactorOrganizationDuoUpdateResponse extends BaseResponse { duo: TwoFactorDuoDetailsResponse; diff --git a/libs/common/src/auth/two-factor/response/two-factor-organization-duo.response.ts b/libs/common/src/auth/two-factor/response/two-factor-organization-duo.response.ts index ebb2237b400f..9d705e0477ad 100644 --- a/libs/common/src/auth/two-factor/response/two-factor-organization-duo.response.ts +++ b/libs/common/src/auth/two-factor/response/two-factor-organization-duo.response.ts @@ -3,8 +3,7 @@ import { BaseResponse } from "../../../models/response/base.response"; import { TwoFactorDuoDetailsResponse } from "./two-factor-duo-details.response"; /** - * Response for `POST /organizations/{id}/two-factor/get-duo`. Wraps the organization-scoped - * Duo provider details and the user-verification token minted by the GET endpoint. + * Response from retrieving an organization's Duo two factor provider data. */ export class TwoFactorOrganizationDuoResponse extends BaseResponse { duo: TwoFactorDuoDetailsResponse; diff --git a/libs/common/src/auth/two-factor/response/two-factor-web-authn-challenge.response.ts b/libs/common/src/auth/two-factor/response/two-factor-web-authn-challenge.response.ts index 9ca5e61f41d0..c51493292730 100644 --- a/libs/common/src/auth/two-factor/response/two-factor-web-authn-challenge.response.ts +++ b/libs/common/src/auth/two-factor/response/two-factor-web-authn-challenge.response.ts @@ -2,9 +2,7 @@ import { BaseResponse } from "../../../models/response/base.response"; import { WebAuthnChallengeResponse } from "../../models/response/web-authn-challenge.response"; /** - * Wrapper around {@link WebAuthnChallengeResponse} that adds the user-verification token minted by - * the server-side `GetWebAuthnChallenge` endpoint. Replaces the previous response shape, which - * returned the FIDO2 options object directly and had no place to attach the token. + * Response from requesting a WebAuthn (FIDO2) credential creation challenge for two factor setup. */ export class TwoFactorWebAuthnChallengeResponse extends BaseResponse { options: WebAuthnChallengeResponse | null; diff --git a/libs/common/src/auth/two-factor/response/two-factor-web-authn-delete.response.ts b/libs/common/src/auth/two-factor/response/two-factor-web-authn-delete.response.ts index 4314263acada..cddf3194ef62 100644 --- a/libs/common/src/auth/two-factor/response/two-factor-web-authn-delete.response.ts +++ b/libs/common/src/auth/two-factor/response/two-factor-web-authn-delete.response.ts @@ -3,10 +3,8 @@ import { BaseResponse } from "../../../models/response/base.response"; import { TwoFactorWebAuthnDetailsResponse } from "./two-factor-web-authn-details.response"; /** - * Response for `DELETE /two-factor/webauthn` (per-credential). The operation modifies - * the WebAuthn provider's credentials list rather than destroying the provider, so the - * response carries the updated parent state. All other 2FA DELETE endpoints return - * 204 No Content. + * Response from removing a single credential from a user's WebAuthn (FIDO2) two factor + * provider, returning the updated provider data. */ export class TwoFactorWebAuthnDeleteResponse extends BaseResponse { webAuthn: TwoFactorWebAuthnDetailsResponse; diff --git a/libs/common/src/auth/two-factor/response/two-factor-web-authn-update.response.ts b/libs/common/src/auth/two-factor/response/two-factor-web-authn-update.response.ts index f55352788d7f..335521be2a95 100644 --- a/libs/common/src/auth/two-factor/response/two-factor-web-authn-update.response.ts +++ b/libs/common/src/auth/two-factor/response/two-factor-web-authn-update.response.ts @@ -3,7 +3,7 @@ import { BaseResponse } from "../../../models/response/base.response"; import { TwoFactorWebAuthnDetailsResponse } from "./two-factor-web-authn-details.response"; /** - * Response for `PUT /two-factor/webauthn`. Wraps the post-update provider details. + * Response from updating a user's WebAuthn (FIDO2) two factor provider data. */ export class TwoFactorWebAuthnUpdateResponse extends BaseResponse { webAuthn: TwoFactorWebAuthnDetailsResponse; diff --git a/libs/common/src/auth/two-factor/response/two-factor-web-authn.response.ts b/libs/common/src/auth/two-factor/response/two-factor-web-authn.response.ts index a83dccce1f4e..3be300366fce 100644 --- a/libs/common/src/auth/two-factor/response/two-factor-web-authn.response.ts +++ b/libs/common/src/auth/two-factor/response/two-factor-web-authn.response.ts @@ -3,8 +3,7 @@ import { BaseResponse } from "../../../models/response/base.response"; import { TwoFactorWebAuthnDetailsResponse } from "./two-factor-web-authn-details.response"; /** - * Response for `POST /two-factor/get-webauthn`. Wraps the provider details and the - * user-verification token minted by the GET endpoint. + * Response from retrieving a user's WebAuthn (FIDO2) two factor provider data. */ export class TwoFactorWebAuthnResponse extends BaseResponse { webAuthn: TwoFactorWebAuthnDetailsResponse; diff --git a/libs/common/src/auth/two-factor/response/two-factor-yubi-key-update.response.ts b/libs/common/src/auth/two-factor/response/two-factor-yubi-key-update.response.ts index de3231eb26d8..9d8874637dca 100644 --- a/libs/common/src/auth/two-factor/response/two-factor-yubi-key-update.response.ts +++ b/libs/common/src/auth/two-factor/response/two-factor-yubi-key-update.response.ts @@ -3,7 +3,7 @@ import { BaseResponse } from "../../../models/response/base.response"; import { TwoFactorYubiKeyDetailsResponse } from "./two-factor-yubi-key-details.response"; /** - * Response for `PUT /two-factor/yubikey`. Wraps the post-update provider details. + * Response from updating a user's YubiKey two factor provider data. */ export class TwoFactorYubiKeyUpdateResponse extends BaseResponse { yubiKey: TwoFactorYubiKeyDetailsResponse; diff --git a/libs/common/src/auth/two-factor/response/two-factor-yubi-key.response.ts b/libs/common/src/auth/two-factor/response/two-factor-yubi-key.response.ts index c6d81b7b9d3b..86d696fb516a 100644 --- a/libs/common/src/auth/two-factor/response/two-factor-yubi-key.response.ts +++ b/libs/common/src/auth/two-factor/response/two-factor-yubi-key.response.ts @@ -3,8 +3,7 @@ import { BaseResponse } from "../../../models/response/base.response"; import { TwoFactorYubiKeyDetailsResponse } from "./two-factor-yubi-key-details.response"; /** - * Response for `POST /two-factor/get-yubikey`. Wraps the provider details and the - * user-verification token minted by the GET endpoint. + * Response from retrieving a user's YubiKey two factor provider data. */ export class TwoFactorYubiKeyResponse extends BaseResponse { yubiKey: TwoFactorYubiKeyDetailsResponse; From 279e72b18f343e4d4f3b4cbf11307ac62a5dfb79 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Thu, 25 Jun 2026 16:07:37 -0400 Subject: [PATCH 28/29] PM-38137 - Replay UV token on get-webauthn-challenge client call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switches the WebAuthn challenge client call from a single-use SecretVerificationRequest to the new TwoFactorWebAuthnChallengeRequest carrying the cached user-verification token minted by getTwoFactorWebAuthn. Drops the now-stale userVerificationToken field from TwoFactorWebAuthnChallengeResponse — the original token stays valid through the subsequent PUT. Fixes the passwordless (TDE / Key Connector) WebAuthn enrollment failure where the second use of the OTP was rejected. --- .../two-factor/two-factor-setup-webauthn.component.ts | 5 ++--- .../two-factor/abstractions/two-factor-api.service.ts | 11 ++++++----- .../two-factor/abstractions/two-factor.service.ts | 11 ++++++----- libs/common/src/auth/two-factor/request/index.ts | 1 + .../request/two-factor-web-authn-challenge.request.ts | 3 +++ .../two-factor-web-authn-challenge.response.ts | 2 -- .../services/default-two-factor-api.service.spec.ts | 8 +++----- .../services/default-two-factor-api.service.ts | 3 ++- .../two-factor/services/default-two-factor.service.ts | 3 ++- 9 files changed, 25 insertions(+), 22 deletions(-) create mode 100644 libs/common/src/auth/two-factor/request/two-factor-web-authn-challenge.request.ts 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 21bd11c2bc25..bea0b7965f0e 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 @@ -5,9 +5,9 @@ import { FormControl, FormGroup, ReactiveFormsModule, Validators } from "@angula import { JslibModule } from "@bitwarden/angular/jslib.module"; 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 { WebAuthnChallengeResponse } from "@bitwarden/common/auth/models/response/web-authn-challenge.response"; import { TwoFactorService, TwoFactorSetupDialogData } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorWebAuthnChallengeRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-challenge.request"; import { TwoFactorWebAuthnDeleteAllRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-delete-all.request"; import { TwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-delete.request"; import { TwoFactorWebAuthnUpdateRequest } from "@bitwarden/common/auth/two-factor/request/two-factor-web-authn-update.request"; @@ -213,14 +213,13 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom if (this.keyIdAvailable == null) { return; } - const request = await this.buildRequestModel(SecretVerificationRequest); + const request = new TwoFactorWebAuthnChallengeRequest(this.requireUserVerificationToken()); this.challengePromise = this.twoFactorService.getTwoFactorWebAuthnChallenge(request); const wrappedChallenge = await this.challengePromise; if (wrappedChallenge.options == null) { this.webAuthnError = true; return; } - this.userVerificationToken = wrappedChallenge.userVerificationToken; this.readDevice(wrappedChallenge.options); }; diff --git a/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts b/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts index 1426fa3e4666..3c9e74bf444e 100644 --- a/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts +++ b/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts @@ -9,6 +9,7 @@ import { TwoFactorEmailLoginRequest } from "../request/two-factor-email-login.re import { TwoFactorEmailSetupRequest } from "../request/two-factor-email-setup.request"; import { TwoFactorEmailUpdateRequest } from "../request/two-factor-email-update.request"; import { TwoFactorOrganizationDuoDeleteRequest } from "../request/two-factor-organization-duo-delete.request"; +import { TwoFactorWebAuthnChallengeRequest } from "../request/two-factor-web-authn-challenge.request"; import { TwoFactorWebAuthnDeleteAllRequest } from "../request/two-factor-web-authn-delete-all.request"; import { TwoFactorWebAuthnDeleteRequest } from "../request/two-factor-web-authn-delete.request"; import { TwoFactorWebAuthnUpdateRequest } from "../request/two-factor-web-authn-update.request"; @@ -133,15 +134,15 @@ export abstract class TwoFactorApiService { /** * Gets a WebAuthn challenge for registering a new WebAuthn credential. * This must be called before putTwoFactorWebAuthn to obtain the cryptographic challenge - * required for credential creation. The challenge is wrapped together with a user verification - * token used to authorize the subsequent PUT. - * Requires user verification via master password or OTP. + * required for credential creation. Authorized by replaying the user-verification token + * minted by the prior getTwoFactorWebAuthn call; that same token stays valid for the + * subsequent PUT. * - * @param request The secret verification request to authorize the operation. + * @param request The request carrying the user-verification token from getTwoFactorWebAuthn. * @returns A promise that resolves to the wrapped challenge response. */ abstract getTwoFactorWebAuthnChallenge( - request: SecretVerificationRequest, + request: TwoFactorWebAuthnChallengeRequest, ): Promise; /** 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 index 6d0442a7d155..10acf0507e17 100644 --- a/libs/common/src/auth/two-factor/abstractions/two-factor.service.ts +++ b/libs/common/src/auth/two-factor/abstractions/two-factor.service.ts @@ -12,6 +12,7 @@ import { TwoFactorEmailLoginRequest } from "../request/two-factor-email-login.re import { TwoFactorEmailSetupRequest } from "../request/two-factor-email-setup.request"; import { TwoFactorEmailUpdateRequest } from "../request/two-factor-email-update.request"; import { TwoFactorOrganizationDuoDeleteRequest } from "../request/two-factor-organization-duo-delete.request"; +import { TwoFactorWebAuthnChallengeRequest } from "../request/two-factor-web-authn-challenge.request"; import { TwoFactorWebAuthnDeleteAllRequest } from "../request/two-factor-web-authn-delete-all.request"; import { TwoFactorWebAuthnDeleteRequest } from "../request/two-factor-web-authn-delete.request"; import { TwoFactorWebAuthnUpdateRequest } from "../request/two-factor-web-authn-update.request"; @@ -311,16 +312,16 @@ export abstract class TwoFactorService { /** * 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. + * required for credential creation. Authorized by replaying the user-verification token + * minted by the prior getTwoFactorWebAuthn call (token-replay chain step); that same + * token stays valid for the subsequent PUT. * Used for settings management. * - * @param request The {@link SecretVerificationRequest} to prove authentication. + * @param request The {@link TwoFactorWebAuthnChallengeRequest} carrying the user-verification token. * @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, + request: TwoFactorWebAuthnChallengeRequest, ): Promise; /** diff --git a/libs/common/src/auth/two-factor/request/index.ts b/libs/common/src/auth/two-factor/request/index.ts index 5240890969ca..e7e627a57fe9 100644 --- a/libs/common/src/auth/two-factor/request/index.ts +++ b/libs/common/src/auth/two-factor/request/index.ts @@ -7,6 +7,7 @@ export * from "./two-factor-email-login.request"; export * from "./two-factor-email-setup.request"; export * from "./two-factor-email-update.request"; export * from "./two-factor-organization-duo-delete.request"; +export * from "./two-factor-web-authn-challenge.request"; export * from "./two-factor-web-authn-delete-all.request"; export * from "./two-factor-web-authn-delete.request"; export * from "./two-factor-web-authn-update.request"; diff --git a/libs/common/src/auth/two-factor/request/two-factor-web-authn-challenge.request.ts b/libs/common/src/auth/two-factor/request/two-factor-web-authn-challenge.request.ts new file mode 100644 index 000000000000..255248c7a77f --- /dev/null +++ b/libs/common/src/auth/two-factor/request/two-factor-web-authn-challenge.request.ts @@ -0,0 +1,3 @@ +export class TwoFactorWebAuthnChallengeRequest { + constructor(public userVerificationToken: string) {} +} diff --git a/libs/common/src/auth/two-factor/response/two-factor-web-authn-challenge.response.ts b/libs/common/src/auth/two-factor/response/two-factor-web-authn-challenge.response.ts index c51493292730..d61c3e8b2130 100644 --- a/libs/common/src/auth/two-factor/response/two-factor-web-authn-challenge.response.ts +++ b/libs/common/src/auth/two-factor/response/two-factor-web-authn-challenge.response.ts @@ -6,12 +6,10 @@ import { WebAuthnChallengeResponse } from "../../models/response/web-authn-chall */ export class TwoFactorWebAuthnChallengeResponse extends BaseResponse { options: WebAuthnChallengeResponse | null; - userVerificationToken: string; constructor(response: any) { super(response); const options = this.getResponseProperty("Options"); this.options = options == null ? null : new WebAuthnChallengeResponse(options); - this.userVerificationToken = this.getResponseProperty("UserVerificationToken"); } } diff --git a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts index cca14167fcaf..c41a050aafb7 100644 --- a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts +++ b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts @@ -12,6 +12,7 @@ import { TwoFactorEmailLoginRequest } from "../request/two-factor-email-login.re import { TwoFactorEmailSetupRequest } from "../request/two-factor-email-setup.request"; import { TwoFactorEmailUpdateRequest } from "../request/two-factor-email-update.request"; import { TwoFactorOrganizationDuoDeleteRequest } from "../request/two-factor-organization-duo-delete.request"; +import { TwoFactorWebAuthnChallengeRequest } from "../request/two-factor-web-authn-challenge.request"; import { TwoFactorWebAuthnDeleteAllRequest } from "../request/two-factor-web-authn-delete-all.request"; import { TwoFactorWebAuthnDeleteRequest } from "../request/two-factor-web-authn-delete.request"; import { TwoFactorWebAuthnUpdateRequest } from "../request/two-factor-web-authn-update.request"; @@ -513,9 +514,8 @@ describe("TwoFactorApiService", () => { }); describe("getTwoFactorWebAuthnChallenge", () => { - it("obtains the wrapped challenge and user verification token", async () => { - const request = new SecretVerificationRequest(); - request.masterPasswordHash = "master-password-hash"; + it("replays the cached user-verification token and obtains the wrapped challenge", async () => { + const request = new TwoFactorWebAuthnChallengeRequest("uv-token"); const mockResponse = { Options: { challenge: "Y2hhbGxlbmdlLXN0cmluZw", @@ -529,7 +529,6 @@ describe("TwoFactorApiService", () => { excludeCredentials: [] as PublicKeyCredentialDescriptor[], timeout: 60000, }, - UserVerificationToken: "uv-token", }; apiService.send.mockResolvedValue(mockResponse); @@ -546,7 +545,6 @@ describe("TwoFactorApiService", () => { expect(result.options).toBeDefined(); expect(result.options.challenge).toBeDefined(); expect(result.options.rp).toHaveProperty("name", "Bitwarden"); - expect(result.userVerificationToken).toBe("uv-token"); }); }); diff --git a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts index acdd45dabaf8..1afb86379f60 100644 --- a/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts +++ b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts @@ -12,6 +12,7 @@ import { TwoFactorEmailLoginRequest } from "../request/two-factor-email-login.re import { TwoFactorEmailSetupRequest } from "../request/two-factor-email-setup.request"; import { TwoFactorEmailUpdateRequest } from "../request/two-factor-email-update.request"; import { TwoFactorOrganizationDuoDeleteRequest } from "../request/two-factor-organization-duo-delete.request"; +import { TwoFactorWebAuthnChallengeRequest } from "../request/two-factor-web-authn-challenge.request"; import { TwoFactorWebAuthnDeleteAllRequest } from "../request/two-factor-web-authn-delete-all.request"; import { TwoFactorWebAuthnDeleteRequest } from "../request/two-factor-web-authn-delete.request"; import { TwoFactorWebAuthnUpdateRequest } from "../request/two-factor-web-authn-update.request"; @@ -218,7 +219,7 @@ export class DefaultTwoFactorApiService implements TwoFactorApiService { } async getTwoFactorWebAuthnChallenge( - request: SecretVerificationRequest, + request: TwoFactorWebAuthnChallengeRequest, ): Promise { const response = await this.apiService.send( "POST", 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 index 2c2ee5c46acd..e4814a1c383f 100644 --- 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 @@ -27,6 +27,7 @@ import { TwoFactorEmailLoginRequest } from "../request/two-factor-email-login.re import { TwoFactorEmailSetupRequest } from "../request/two-factor-email-setup.request"; import { TwoFactorEmailUpdateRequest } from "../request/two-factor-email-update.request"; import { TwoFactorOrganizationDuoDeleteRequest } from "../request/two-factor-organization-duo-delete.request"; +import { TwoFactorWebAuthnChallengeRequest } from "../request/two-factor-web-authn-challenge.request"; import { TwoFactorWebAuthnDeleteAllRequest } from "../request/two-factor-web-authn-delete-all.request"; import { TwoFactorWebAuthnDeleteRequest } from "../request/two-factor-web-authn-delete.request"; import { TwoFactorWebAuthnUpdateRequest } from "../request/two-factor-web-authn-update.request"; @@ -217,7 +218,7 @@ export class DefaultTwoFactorService implements TwoFactorServiceAbstraction { } getTwoFactorWebAuthnChallenge( - request: SecretVerificationRequest, + request: TwoFactorWebAuthnChallengeRequest, ): Promise { return this.twoFactorApiService.getTwoFactorWebAuthnChallenge(request); } From a8eba068f2b520c8c07a61d907c9990ab2f861b7 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Thu, 25 Jun 2026 17:28:25 -0400 Subject: [PATCH 29/29] PM-38137 - Guard cached UV token in 2FA authenticator setup component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the requireUserVerificationToken() helper already present in the duo/email/webauthn/yubikey setup components. Authenticator was the only setup component still passing the raw string | undefined field into request DTOs — silently allowed only because the file is @ts-strict-ignore. The helper throws if the token wasn't populated, keeping all five components consistent. --- .../two-factor-setup-authenticator.component.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) 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 9e4e4efa8953..2cbb059c1ebd 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 @@ -86,6 +86,13 @@ export class TwoFactorSetupAuthenticatorComponent key: string; private userVerificationToken: string | undefined; + private requireUserVerificationToken(): string { + if (this.userVerificationToken === undefined) { + throw new Error("User verification token is missing"); + } + return this.userVerificationToken; + } + override componentName = "app-two-factor-authenticator"; qrScriptError = false; private qrScript: HTMLScriptElement; @@ -157,7 +164,7 @@ export class TwoFactorSetupAuthenticatorComponent const request = new TwoFactorAuthenticatorUpdateRequest( this.formGroup.value.token, this.key, - this.userVerificationToken, + this.requireUserVerificationToken(), ); const response = await this.twoFactorService.putTwoFactorAuthenticator(request); @@ -179,7 +186,10 @@ export class TwoFactorSetupAuthenticatorComponent return; } - const request = new TwoFactorAuthenticatorDeleteRequest(this.key, this.userVerificationToken); + const request = new TwoFactorAuthenticatorDeleteRequest( + this.key, + this.requireUserVerificationToken(), + ); await this.twoFactorService.deleteTwoFactorAuthenticator(request); this.enabled = false; this.toastService.showToast({