* feat: User is no longer logged out when their email is changed. This must be behind a feature flag until salt and email are seperated.
Addressed tech-debt as well enforcing strict typing, signals, and onPush for all components in this PR.
Add base English keys (sharedFolder, sharedFolders, myFolder, myFolders)
to the web, browser, desktop, and CLI en/messages.json files. Existing
collection*/folder* keys are preserved intact for rollback.
feat: call SDK for password prelogin (PM-27060)
- Add PM27060_PasswordPreloginFromSdk feature flag (default off) to gate the new behavior
- Update `DefaultPasswordPreloginService` to call the SDK for prelogin when the flag is enabled, falling back to the existing API-based path otherwise
* 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 <ppimentel@bitwarden.com>
GovModeService is a shared Platform service that downstream teams consume to
gate FedRAMP-specific behavior.
The MVP implementation infers the gov-mode signal from the client-side Region
(true when `env.getRegion() === Region.Gov`). PM-36520 will replace the
implementation with a server-backed check while keeping this contract stable.
The service exposes two Observable surfaces: `globalIsGovMode$` (pre-login) and
`isGovMode$(userId)` (when a UserId is in scope). Consumers needing a Promise
snapshot bridge with `firstValueFrom` at the call-site -- the synthesis memory
A5086F94 captured the decision to omit Promise overloads from the service
surface.
Registered in all four app bootstraps: Angular DI (jslib-services.module),
browser background, CLI service-container, and desktop main. Unit tests cover
all four regions (Gov, US, EU, SelfHosted) for both API surfaces.
* [PM-38392] Route password-protected Send saves through the SDK
The SDK now exposes a `hashedPassword` SendAuthType variant (PM-38391,
sdk-internal #1197, present in the pinned 0.2.0-main.872) that forwards the
client-derived keyB64 verbatim without re-applying PBKDF2. Drop the temporary
workaround that routed password-protected saves to the legacy SendApiService.
- buildSendAuth: add AuthType.Password case returning
{ type: "hashedPassword", keyB64: sendView.password }, guarded against a
missing derived key. keyB64 is a proof-of-knowledge and is never logged.
- Remove the password-protected save guards from SendApiServiceSelector and
SendSdkApiService; the new-file-send fallback is unchanged.
- Update the now-stale workaround doc comments.
- Tests: flip selector password create/edit assertions to SDK routing; add
send-sdk-api.service.spec.ts covering the hashedPassword output, the
null-key guard, the none variant, and the new-file-send guard.
* [PM-38392] Use SDK high-level password auth for password-protected sends
The prior approach forwarded the client's pre-derived keyB64 to the SDK via
the hashedPassword variant. That is broken for creates: the SDK's create
generates its own send key, so a keyB64 derived over the client's (discarded)
key yields a password the user can never satisfy (verified end-to-end: correct
password rejected on receive). A maintainer also asked to move clients onto the
higher-level password API rather than the low-level hashed variant
(sdk-internal#1197).
Route password-protected sends through the SDK's plaintext `password` auth
variant so the SDK derives the proof-of-knowledge over the key it uses,
keeping password and key consistent by construction:
- Thread an optional plaintext password through SendApiService.save (abstraction
+ selector + legacy [ignored] + SDK impl) from the four callers that hold it
at the encrypt() call (CLI create/edit, send-ui form service, angular
add-edit).
- buildSendAuth: emit { type: "password", password } when a plaintext is present
(create / password-change); emit { type: "hashedPassword", keyB64 } only for
preserve-on-edit, where the SDK reuses the existing send key so the stored
keyB64 stays valid. The existing keyB64 is recovered from stored state via
getFromState (the freshly-encrypted Send carries none on a preserving edit).
- Plaintext password and keyB64 are Protected Data; never logged.
Verified: password create via SDK -> receive with correct password decrypts,
wrong password rejected. Unit tests cover all four buildSendAuth cases.
* [PM-38392] Only recover existing send password for password-protected edits
Address review S1: the existing-keyB64 recovery via getFromState fired on
every non-create save, including name-only edits of None/Email sends, whose
result buildSendAuth then ignores. Gate the state read on
send.authType === AuthType.Password so it only runs when the preserve-on-edit
branch can actually use it.
* Update apps/cli/src/tools/send/commands/create.command.ts
Co-authored-by: John Harrington <84741727+harr1424@users.noreply.github.com>
* Update apps/cli/src/tools/send/commands/edit.command.ts
Co-authored-by: John Harrington <84741727+harr1424@users.noreply.github.com>
---------
Co-authored-by: John Harrington <84741727+harr1424@users.noreply.github.com>
* feat(platform): add Region.Gov and Gov entry to PRODUCTION_REGIONS [PM-38018]
Adds Gov: "Gov" to the Region const object (between EU and SelfHosted)
and appends a third entry to PRODUCTION_REGIONS alongside US and EU.
The CloudRegion type (Exclude<Region, "Self-hosted">) automatically
includes Gov.
The Gov entry uses bitwarden-gov.com as its domain with all subdomain
URLs explicit. The send URL is stored bare (no trailing /#/) per the
convention established by PM-2588 — getSendUrl() appends the hash at
read time.
Region.Gov is the foundational constant for the GovMode / FedRAMP cloud
environment work tracked under PM-35087. Flag-gated visibility in the
environment selector is introduced separately in PM-38019 via
AvailableRegionsService; this commit deliberately makes the region
unconditionally returned by EnvironmentService.availableRegions().
DEFAULT_MARKETING_EMAILS_PREF_BY_REGION in registration-start.component
gets a Gov: false entry to satisfy the Record<Region, boolean> type.
* feat: introduce AvailableRegionsService for runtime region filtering
Creates AvailableRegionsService and DefaultAvailableRegionsService in
libs/common/src/platform/. The service exposes availableRegions$, a
flag-gated Observable that wraps EnvironmentService.availableRegions()
and filters Region.Gov by the FedRampGovRegion feature flag. US and EU
are always present regardless of flag state.
EnvironmentService cannot hold this filter directly: ConfigService
depends on EnvironmentService to resolve API URLs, so wiring the
feature-flag check into EnvironmentService would create a circular
dependency. AvailableRegionsService sits above both and composes them.
The implementation uses startWith(false) so Gov is hidden until the flag
value arrives from the server. This avoids a flicker where Gov would
briefly appear during cold start before the flag resolves.
Registered in the renderer Angular DI module (covers web and desktop
renderer), the browser background script, and the CLI service container.
apps/desktop/src/main.ts intentionally excludes the service: no
main-process consumer needs a flag-gated region list, and desktop main
does not construct ConfigService, so registering here would require a
feature-flag shim to satisfy a constructor dependency with no consumer.
The accompanying spec covers four behaviors: Gov inclusion when the flag
is true, Gov exclusion when the flag is false, Gov exclusion on the
initial startWith(false) seed before the flag emits, and the US/EU
invariant across both flag states. Asserts inspect the last emission of
a take(2) window rather than firstValueFrom because the startWith(false)
seed always emits first regardless of the flag value, so asserting the
first emission would falsely pass for the wrong reason.
The selector-component migration that consumes this service lands in
a follow-up commit on the same branch. Shipping both in one PR closes
the release window where Gov could leak into the environment selector
because unmigrated selectors read EnvironmentService.availableRegions()
directly and never observe the flag.
* refactor(auth): migrate environment selector components to AvailableRegionsService
Migrates the three environment-selector components from
EnvironmentService.availableRegions() to AvailableRegionsService.
availableRegions$. Each component receives AvailableRegionsService via
constructor injection and replaces the synchronous availableRegions
array with the flag-gated Observable. All three templates switch from
synchronous-field iteration to the | async pipe.
The web selector resolves currentRegion synchronously via
EnvironmentService.availableRegions() rather than the flag-gated stream,
because origin validation must succeed regardless of flag state. The dropdown
options continue to use availableRegions$ | async. This pattern matches the
JSDoc guidance on AvailableRegionsService: the service is the UI display
source, while EnvironmentService.availableRegions() is the source for URL
resolution, origin validation, and stored-region rehydration.
The shared and registration components use combineLatest to pair the
flag-gated region list with the active-environment stream so
selectedRegion$ stays in sync with changes to either source. The
registration component's Environment import becomes unused after
combineLatest infers the type that the previous map(env: Environment)
annotation required, and is removed to satisfy ESLint's
@typescript-eslint/no-unused-vars rule.
Shipping this migration in the same PR as the AvailableRegionsService
introduction closes the release window where Gov could leak into any
client's environment selector. Unmigrated selectors read
EnvironmentService.availableRegions() directly and would surface Gov
unconditionally; migrating them in this PR makes the FedRampGovRegion
flag the single gate for selector visibility.
These three components live in Auth-owned paths
(libs/angular/src/auth/, apps/web/src/app/components/, and
libs/auth/src/angular/registration/). Auth is a required reviewer on
this PR.
* fix(auth): provide AvailableRegionsService mock in stories
* fix(auth): align env-picker dropdown icon vertically
The trigger's <bit-icon bwi-angle-down> renders inline-block with
vertical-align: baseline, which hangs below the baseline of the
line box. Combined with bitLink's tw-leading-none, this clipped
the text descenders of the "Accessing:" label on desktop and
browser login footers.
Adding tw-align-middle to the icon restores normal line-box
metrics and eliminates the clipping.
Root cause latent since PR #18816 migrated the trigger icon from
<i class="bwi"> to <bit-icon>. Web is unaffected because its
trigger uses an inline-flex <a bitLink>. This fix is
flag-independent — FedRampGovRegion only gates dropdown contents,
not the trigger label.
Refs: PM-39401
* Revert "fix(auth): align env-picker dropdown icon vertically"
This reverts commit 2d51461be9.
---------
Co-authored-by: kmancusi0105 <kmancusi@bitwarden.com>
* [PM-31068] Wire send SDK repository for edit/removePassword
Add domain<->SDK Send conversions (toSdkSend/fromSdkSend/toSendData on
Send, SendText, SendFile), a SendRecordMapper backed by
SEND_USER_ENCRYPTED, and register the `send` repository in
client-managed-state so the SDK can read/write the existing send from the
same state legacy sync already populates.
SendClient.edit/get/list/remove_password resolve the existing send via
state().get::<Send>(); without a registered send repository that state was
empty, so SDK-backed send edit hung and remove-password failed (PM-39432).
Registering the repository fixes both (validated end-to-end on web + the
browser extension).
Requires an @bitwarden/sdk-internal version that exposes `send` in
Repositories (paired SDK PR). package.json is still pinned to 0.2.0-main.803;
bump to the published version once the SDK change lands — until then this
branch does not type-check/build on a clean checkout.
* Address review: fix stale mapper doc comment, normalize fromSdkSend text/file to null
* Bump @bitwarden/sdk-internal to 0.2.0-main.870 for send repository
* [PM-39436] Fix CLI SDK sends: construct sdkService before send services
SendApiServiceSelector was constructed with `new SendSdkApiService(this.sdkService, ...)`
before `this.sdkService` was assigned, so SendSdkApiService captured `undefined`. With
the pm-30110-sdk-sends-api flag on, every SDK-routed send mutation (create/edit/delete/
remove-password) threw `Cannot read properties of undefined (reading 'userClient$')`.
Reads worked because they read local state, not the SDK.
Move the sdkClientFactory/sdkLoadService/sdkService construction above the send-service
block. All DefaultSdkService dependencies are defined earlier in the method, and
registerSdkService still references sdkClientFactory from the same scope.
Validated end-to-end on a local self-hosted server (flag on): create, edit,
remove-password, and delete all succeed via the SDK path.
* Re-trigger CI on node 24/npm 11 workflows (post node-24 migration)
* Direct Keeper importer UI and a dummy access module (WIP)
* Add Keeper importer impl, fix it to run in the browser not node (WIP)
* Add request logging to Keeper client
* Auto-close Keeper device approval dialog on websocket notification
When user approves their device via email link, the websocket receives a
device_verified notification. Previously the dialog remained open requiring
manual dismissal. Now the dialog closes automatically when approval succeeds.
* Remove Continue button from Keeper device approval dialog
The button did not actually do anything useful since approval happens via
email link click. Updated dialog to show a message that it will close
automatically. Added missing Keeper i18n messages to web app.
* Add device approval method selection dialog for Keeper import
- Created KeeperApprovalMethodSelectComponent to let user choose between
Email and Keeper Push approval methods
- Removed TwoFactor from DeviceApprovalChannel (not supported yet)
- Show waiting dialog for Keeper Push approval (was missing)
- Added i18n messages for approval method selection
- Added debug logging for push requests
* Add Keeper data center region selection dropdown
- Added region dropdown with US, EU, AU, CA, JP, US (GOV) options
- Region is passed to Keeper client when opening vault
- Updated test script to select EU region
- Added i18n messages for region label
* Add verification code input to Keeper device approval dialog
Users can now either click the approval link in the email or enter the
6-digit verification code directly in the dialog. Both methods work to
approve the device and continue the import flow.
* Fix AES-CBC no-padding decryption for Keeper encryption params
Web Crypto API requires PKCS7 padding for AES-CBC decryption. The previous
workaround of appending zeros failed because decrypted zeros do not produce
valid PKCS7 padding bytes.
New approach: encrypt a proper PKCS7 padding block (16 bytes of 0x10) using
the last ciphertext block as IV, then append this encrypted padding to create
valid padded ciphertext that Web Crypto can decrypt.
* More scripts for testing
* Remove generated files we don't need
* Test scripts (remove later)
* Refactor the sync-down processing to be able to test easier
* Fix lint errors
* Cleanup
* New token
* New fixture and export
* Some tests
* Move the code into a new place
* Simplify Vault spec
* Use BaseImporter
* Add more record types
* Disable console.log in tests
* Update test data and tests
* Test each record type
* Fix folders
* Major refactor
* Decrypt shared record keys
* Remove account summary
* Add folders to records
* Build all paths
* Collect paths for all records
* uidToString
* Refactor base64 URL encoding/decoding using Utils from common library
* Add README for Direct Keeper importer and refactor VaultItem handling
* Refactor KeeperDirectImporter to streamline password and username handling in import process
* Implement reference collection and resolution in KeeperDirectImporter
* TODO
* More TODOs
* 2FA selection
* Handle SMS
* Add .proto and proto compiler to generate .ts from .proto
* Remove debugging code
* Delete debug scripts
* Update readme
* Update proto build script to disable ESLint checks for generated files
* Remove keeper-export.json
* Refactor ClientOptions to use KeeperRegion const object
* Remove includeSharedFolders option
* Make sure nx serve builds proto
* Clean up crypto
* Clean up crypto.ts
* cleanup
* Remove unnecessary exception class
* Move fixture into proper place, fix tests
* Move code around to match lastpass/access structure better
* Add protobuf generation step to CI test workflow
* Fix date expectations in keeper direct importer tests
* Build .proto files before typechecking
* Fix npm run test:types errors
* Rename .proto files to kebap case to prevent CI lint warnings about having capital case letters in filenames
* Commit generated protobuf TS files and remove build-time proto generation
The protobuf build step was causing CI issues. Generated TS files are now
committed directly. Proto source files remain as reference with a README
documenting how to regenerate.
* Change npx instruction to run directly from npm
* Add generated files README, update proto README
* Update TODO comment in vault.ts
* Add @protobuf-ts/runtime to Tools dep ownership in renovate config
* Implement Duo 2FA support for Keeper direct importer
- Fix Duo Passcode/SMS using wrong value type (TWO_FA_CODE_TOTP -> TWO_FA_CODE_DUO)
- Remove unnecessary socket wait after Passcode/SMS validation (token returned directly by API)
- Add waiting dialog for Duo Push/Voice methods instead of just showing a spinner
- Add code input support for Duo Passcode/SMS methods
- Make all Ui interface methods required
* Fix README
* Changes suggested by @quexten (doc comments)
* Fix TS type checked warnings
* Filter out unsupported 2FA methods from UI
* Skip file-based import flow for Keeper direct import
* Show error when no supported 2FA found but 2FA is required
* Migrate from @protobuf-ts/runtime to @bufbuild/protobuf
* Track possible cycles in the folder strcuture and break them (was suggested in review)
* Fix leftover merge conflict marker in web messages.json
* [PM-33198] angular modernization changes
* Remove KeeperPush from supported 2FAs for now, update README with more TODOs
* Reuse existing 'email' i18n key instead of keeperApprovalMethodEmail
* Reuse existing 'verificationCode' i18n key instead of keeperVerificationCode
* Replace keeperMFADesc with generic mfaTotpDesc i18n key
* Consolidate keeperTwoFactorSms and keeperDuoSms into generic textMessageSms i18n key
* Rename keeperTwoFactorBackup and keeperTwoFactorDuo to generic i18n keys
* Rename keeperDuo* i18n keys to generic duoVerification/duoPushWaiting/duoSmsWaiting/duoVoiceWaiting
* Consolidate keeperTwoFactorUnknown and keeperDuoUnknown into generic unknownMethod i18n key
* Reuse existing 'passcode' i18n key instead of keeperDuoPasscode
* Rename keeperRegion to generic dataCenterLocation i18n key
* Rename keeperSelectApprovalMethod* to generic selectApprovalMethod* i18n keys
* Rename keeperDeviceApprovalEmailDesc to generic approvalEmailDesc i18n key
* Rename generic keeper-prefixed i18n keys to content-driven names
* Rename keeperSelect* i18n keys to generic selectTwoFactorMethod/selectDuoMethod
* Rename remaining keeper-prefixed i18n keys to content-driven names
Consolidate keeperApprovalMethodPush and keeperTwoFactorKeeperPush into
single keeperPush key.
* Add Keeper DNA Push 2FA support for direct importer
* Add Keeper DNA manual code entry
* Add two-factor authentication as device approval method
* Mark DNA and backup codes as done.
* Add TryAnother sentinel and loop device approval flow
* Add 'Try another method' button to Keeper device approval
* Unify Keeper importer auth flow into a single dialog
* Remove unused per-state Keeper dialog components
* Hide empty Keeper email hint to remove gap below the field
* Defer Keeper password prompt until server requests it
* Split Keeper direct importer into its own format option
* Refine Keeper email and region field labels
* Redesign Keeper auth dialog and route Try another method back to selection
* Restyle Keeper master password dialog and route close to cancel
* Show Keeper email subtitle on every auth dialog stage
* Add Cancel to single-button Keeper auth stages and use Continue label
* Use radio buttons for Keeper two-factor method selection
* Group Keeper auth dialog by stage via reusable shell
Each @case now contains one self-contained <keeper-stage-shell> block
with a comment header. The shell wraps bit-dialog and holds the
stage-independent header bits (X close button, title with email
subtitle), forwarding bitDialogContent and bitDialogFooter slots through
via ngProjectAs. Footer projection is gated by an explicit [hasFooter]
input — bit-dialog detects its footer via contentChild on a real
DialogFooterDirective, and ngProjectAs alone does not satisfy that
check.
The shell also sets disableAnimations on the inner bit-dialog, so stage
transitions no longer slide.
* Use radio buttons for Keeper Duo and DNA method selection
* Move Keeper auth dialog footer buttons into the stage shell
* Match Keeper email approval dialog to design and fix Resend
- Split approvalCode email vs push: email gets "Email verification"
title, no spinner, and a Resend code link
- Update i18n copy for Approval methods, descriptions, and add
emailVerification, twoFactorMethod, plus refined approvalEmailDesc
- Fix Resend handling in handleDeviceApproval so re-requesting the
verification email keeps the dialog open instead of throwing
* Match Keeper push approval dialog to design
* Match Keeper 2FA code dialog to design and support resend
* Trigger 2FA push on device approval and wire resend
* Show method-specific copy for TOTP and SMS 2FA prompts
* Streamline Keeper DNA flow and pick up watch approval via socket
* Show loading spinner on Continue while validating master password
* Drop Try another method from 2FA and DNA push dialogs
* Tidy Keeper import callout copy and rename email field
* Unify device approval dialog titles and restore Try another method on 2FA approval
* Add Keeper Cloud SSO, admin approval, and browser auto-capture
* Add Keeper import Method dropdown for direct, CSV, and JSON
* Auto-capture Keeper SSO token in browser via spinner stage
* Mark temp feature for removal
* Retry Keeper approval and 2FA codes on server rejection
* Update Keeper push/admin approval dialog copy and add heading
* Skip Keeper SSO dialog on browser extension
* Confirm before cancelling Keeper admin approval flow
* Attach Keeper import async validator to email control
* Simplify Keeper spinner block spacing to typography defaults
* Restyle admin approval cancel confirmation as danger
* Restore missing ImportResult import after upstream merge
* Retry Keeper master password on server rejection
* Clean up PR hygiene: remove stray lock file and proto EOF blanks
* Use bitIconButton label input for accessible close button
* Reject Keeper SSO login when user closes IdP tab
* Restore Keeper csv/json importers for CLI and hide from web dropdown
* Move Keeper direct import out of async validator into explicit submit
* Fix strict-mode type errors in Keeper device approval flow
* Set organizationId on Keeper direct importer so org imports keep folders
* Require email for Keeper direct import and show login errors in the field
* Keep browser import in persistent context
* Fix package-lock.json to match @bufbuild/protobuf 2.11.0 in package.json
* Remove leftover merge conflict marker in web messages.json
* Fix formatting in vault.spec.ts
* Update Keeper importer README reviewers section
* Clarify Windows File Send test name
* Anchor Keeper SSO callback URL regex to https:// to prevent substring matches
* Route Keeper SSO script injection through BrowserApi abstraction
* Reject default Keeper SSO monitor promise instead of leaving it pending
* Replace string-matched Keeper auth errors with typed error codes
* Clean up Keeper importer dead code and i18n inconsistencies
* Resolve linked record keys and report skipped items in Keeper direct import
* Update partial import dialog copy and aggregate skipped items by type
* Show skipped item types in Keeper partial import dialog
* List skipped item names instead of record types in Keeper import dialog
* Revert "List skipped item names instead of record types in Keeper import dialog"
This reverts commit b504becf45.
* Identify Keeper import errors by record UID instead of decrypted name
* Relabel Keeper driverLicense import type as Driver's licence
* Fix NG0100 in import dialog when selecting Keeper format
* [PM-33198] adding opaque types for type safety
* [PM-33198] implement constant time checks to prevent timing attacks
* Address Keeper direct importer review feedback
- Move keeper from the pinned top of featuredImportOptions to its
alphabetical position after KeePass 2 (featured list renders in array order)
- Reword the import-keeper component comment from a review note into
production documentation
- Drop the now-resolved README note about the temporary pinning
* Abort Keeper import when a push socket message can't be processed
A push frame that failed to decrypt or parse was silently swallowed, so the
consumer racing on waitForMessage never resolved and the import hung with no
trace. Fail the socket instead: reject the waiting consumer, which unwinds the
login and surfaces a connection error to the user. The error carries only
structural metadata (stage, byte count, error name), never frame contents.
* Restart Keeper login when the login token expires during device approval
* Merge bitwarden:main
* Revert "Merge bitwarden:main"
This reverts commit b9c1f2df96.
* Map Keeper Direct import identity records to correct item types
driverLicense, ssnCard and passport now import as Identity and invalid SSH keys fall back to a secure note, matching the Keeper JSON importer.
---------
Co-authored-by: Alex Dragovich <adragovich@bitwarden.com>
Co-authored-by: Alex Dragovich <46065570+itsadrago@users.noreply.github.com>
* initial send controls
* initial send controls
* fix tests
* extract service and respond to other review comments
* remove unused import
* dry up policy service, null safety, and test coverage
* add missing import
* remove unused AccountBillingClient
* fix lints
* OR SendPolicyService status
* replace import lost during merge
* PM-31884 implemented controls for send policy access dropdown
* PM-31884 implemented enforce the policy on the web client
* [PM-31884] Add Send control policy access control fields
* PM-31884 support disabled use case in form and table
* Correct Send auth type option display logic, a couple other fixes
* PM-31884 refactored as WhoCanAccessType type
* PM-31884 iterating over strings
* PM-31884 resolved typos
* A few more fixes
* Update design, address PR comments
* Final design review tweaks, address PR comments, simplify translation key changes
* Address two comments
* Adjust select option type, add start value for enabled valueChange
* Cleanup from fixing merge conflicts, finalize design, add to browser
* Address AI review comments
* One more AI review fix
* Two design tweaks to policy editor
* Address some QA findings and standardize discard edits translation keys
* Correct design when disabled Send cannot be copied
* Address a couple bugs
* Resolve build error and one display bug
---------
Co-authored-by: John Harrington <84741727+harr1424@users.noreply.github.com>
Co-authored-by: Mike Amirault <mamirault@bitwarden.com>
* Add state service for V2UpgradeToken
* Update clear to not write to disk every call
* Fix unit test
* Add V2UpgradeToken handling to sync
* Add passing V2UpgradeToken to SDK unlocks
* Fix new DefaultUnlockService in main background
---------
Co-authored-by: Bernd Schoolmann <mail@quexten.com>
* fix(cli): harden bw serve origin-protection against DNS rebinding
Closes PM-36600 / VULN-536. HackerOne report 3684172.
The prior origin-protection middleware only checked for the presence
of an Origin header, which DNS-rebinding attacks bypass: the browser
rebinds an attacker-controlled hostname to 127.0.0.1, then issues
same-origin GETs that carry no Origin header (per the Fetch spec) but
carry the attacker's hostname in the Host header. Any malicious page
the victim visits could silently read the unlocked vault.
The fix introduces two middleware layers, guarding every route
method-agnostically:
Layer 1 (new) — Host-header allowlist. Requests are rejected unless
their Host header matches localhost:PORT, 127.0.0.1:PORT, [::1]:PORT,
or the user-configured --hostname value at the configured port. This
closes the DNS-rebinding vector: a rebound request carries the
attacker's hostname in Host, not a loopback identifier. The allowlist
construction is extracted into an exported helper
`buildServeAllowedHosts(hostname, port)` so it is unit-testable.
Layer 2 (retained, behavior unchanged) — Origin check. Rewritten from
`!= undefined` to `!== undefined` for stylistic consistency with Layer
1. This is a runtime no-op: Node's IncomingHttpHeaders types
`ctx.headers.origin` as `string | string[] | undefined` and never
produces JS null, so an HTTP `Origin: null` header arrives as the
string "null" and was already rejected by the prior check. Layer 2 is
retained as defense-in-depth for classical cross-origin fetches: any
defined Origin header value is rejected.
A new spec encodes the four-probe regression contract:
Probe 1: cross-origin attack (Origin: https://evil.example) -> 403
Probe 2: legitimate localhost (Host: 127.0.0.1, no Origin) -> 200
Probe 3: DNS-rebind (Host: evil.example, no Origin) -> 403 [THE FIX]
Probe 4: empty Origin (forward-regression guard) -> 403
Three additional unit tests cover the `buildServeAllowedHosts` helper.
No new npm dependencies introduced.
* fix(cli): refine Host allowlist for --hostname all and default ports
Two allowlist gaps were found in automated review of PR #20881 and
addressed here.
1. --hostname all: `buildServeAllowedHosts` produced a set containing
literal "all:PORT", so LAN clients sending `Host: 192.168.1.5:PORT`
received a 403. The operator already opted into multi-interface
binding by passing `--hostname all`; enumerating live IPs at runtime
is fragile. Fix: when `hostname === "all"`, pass `null` for
`allowedHosts`, disabling Layer 1 while keeping Layer 2 (Origin
check) active. A startup log entry makes the posture visible.
2. RFC 7230 §5.4 default-port omission: HTTP clients omit the default
port from `Host` when it matches the scheme default (80 for http,
443 for https). `bw serve --hostname bwapi.mydomain.com --port 80`
produced a 403 because `Host: bwapi.mydomain.com` was not in the
allowlist. Fix: when port is 80 or 443, also push bare-host variants
(`localhost`, `127.0.0.1`, `[::1]`, configured hostname) onto the
set. Non-default ports are intentionally excluded — a missing port
against a non-default port is a genuine client misconfiguration.
`buildOriginProtectionMiddleware`'s `allowedHosts` parameter widens to
`ReadonlySet<string> | null` to express "Layer 1 disabled."
7 new test cases added (12 passing total, up from 5): covers null
allowedHosts behavior and bare-host entries at ports 80, 443, and
8087 (negative case).
Refs: PM-36600
* review: coerce port to a number