Files
clients/libs/auth
b88e564e78 Auth/PM-3813 - 2FA Management Endpoints - User Verification Refactor (#21385)
* PM-38137 - Per-provider 2FA request and response models on client

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).

* 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.

* PM-38137 - Thread UV token through web 2FA setup components

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.

* PM-38137 - Preserve lapsed-premium 2FA disable shortcut on settings list

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.

* PM-38137 - Add TODO to get rid of base 2FA setup component.

* PM-38137 - Rename 2FA update request models to TwoFactor<Provider>Update shape

Aligns every client 2FA update request-model class to the file-wide
TwoFactor<Provider> 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.

* 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.

* 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.

* 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

* PM-38137 - Add 2FA provider Details and per-action response classes

Client mirror of the new server response shapes. Adds:

- TwoFactor<Provider>DetailsResponse — five per-provider Details types
  (Authenticator, Duo, Email, WebAuthn, YubiKey) carrying the shared
  provider state used by both GET and Update response wrappers.
- TwoFactor<Provider>UpdateResponse — 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.

* 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.

* PM-38137 - Refactor 2FA service surface and components to per-action response types

Wires the client to the new per-action response shapes. The five
existing TwoFactor<Provider>Response classes are repurposed as the GET
response wrappers — each composes a TwoFactor<Provider>DetailsResponse
plus the freshly-minted user-verification token.

Service surface updated:
- DefaultTwoFactorApiService.getTwoFactor<Provider> returns the GET
  wrapper; put returns the matching *UpdateResponse.
- The six hard-delete methods return Promise<void> 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.

* 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.

* 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 TwoFactor<Provider>Delete shape used by the duo, email,
yubikey, web-authn, and organization-duo deletes.

* 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<T> becomes
TwoFactorSetupDialogData<T> (the payload passed as DIALOG_DATA into per-
provider 2FA setup dialogs). Consumers now import from the
@bitwarden/common/auth/two-factor barrel.

* PM-38137 - Correct 2FA dialog-data type docs to match constructor-param flow

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<T> is the return type of TwoFactorVerifyDialog
before being forwarded as DIALOG_DATA to the per-provider setup dialogs.

* 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.

* 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.

* 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.

* 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.

* PM-38137 - 2FA Setup comp base - add TODO

* PM-38137 - Prefix applyXxxState param with provider name

Renames the applyXxxState parameter from details to <provider>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).

* 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.

* PM-38137 - Run prettier on 2FA setup/verify components

* PM-38137 - Reword 2FA delete doc strings to drop stale disable verbiage

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.

* PM-38137 - Inline processUpdate/processDeleteResponse pass-throughs

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.

* PM-38137 - Replace endpoint-fragile 2FA response docstrings with single sentences

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.

* PM-38137 - Replay UV token on get-webauthn-challenge client call

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.

* PM-38137 - Guard cached UV token in 2FA authenticator setup component

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.

---------

Co-authored-by: Patrick-Pimentel-Bitwarden <[email protected]>
2026-07-13 14:04:15 -04:00
..

Auth

This lib represents the public API of the Auth team at Bitwarden. Modules are imported using @bitwarden/auth.