From 0929b1b276e07b8d89ef54ce1efc205d75bab30d Mon Sep 17 00:00:00 2001 From: rr-bw <102181210+rr-bw@users.noreply.github.com> Date: Sun, 4 Jan 2026 21:12:45 -0800 Subject: [PATCH] [PM-27086 TDE Offboarding] Use new KM APIs; create new methods --- ...initial-password.service.implementation.ts | 55 ++++++++++++++++++- .../set-initial-password.component.ts | 39 ++++++++++++- ...et-initial-password.service.abstraction.ts | 20 +++++++ ...update-tde-offboarding-password.request.ts | 21 +++++++ 4 files changed, 133 insertions(+), 2 deletions(-) diff --git a/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.implementation.ts b/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.implementation.ts index 8722f339b4a..21ed6b59d1b 100644 --- a/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.implementation.ts +++ b/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.implementation.ts @@ -19,7 +19,11 @@ import { AccountCryptographicStateService } from "@bitwarden/common/key-manageme import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; -import { MasterPasswordSalt } from "@bitwarden/common/key-management/master-password/types/master-password.types"; +import { + MasterPasswordAuthenticationData, + MasterPasswordSalt, + MasterPasswordUnlockData, +} from "@bitwarden/common/key-management/master-password/types/master-password.types"; import { KeysRequest } from "@bitwarden/common/models/request/keys.request"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -32,6 +36,7 @@ import { SetInitialPasswordCredentials, SetInitialPasswordUserType, SetInitialPasswordTdeOffboardingCredentialsOld, + SetInitialPasswordTdeOffboardingCredentials, } from "./set-initial-password.service.abstraction"; export class DefaultSetInitialPasswordService implements SetInitialPasswordService { @@ -362,4 +367,52 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi // Clear force set password reason to allow navigation back to vault. await this.masterPasswordService.setForceSetPasswordReason(ForceSetPasswordReason.None, userId); } + + async setInitialPasswordTdeOffboarding( + credentials: SetInitialPasswordTdeOffboardingCredentials, + userId: UserId, + ) { + const { newPassword, kdfConfig, salt, newPasswordHint } = credentials; + + for (const [key, value] of Object.entries(credentials)) { + if (value == null) { + throw new Error(`${key} not found. Could not set password.`); + } + } + + if (userId == null) { + throw new Error("userId not found. Could not set password."); + } + + const userKey = await firstValueFrom(this.keyService.userKey$(userId)); + if (userKey == null) { + throw new Error("userKey not found. Could not set password."); + } + + const authenticationData: MasterPasswordAuthenticationData = + await this.masterPasswordService.makeMasterPasswordAuthenticationData( + newPassword, + kdfConfig, + salt, + ); + + const unlockData: MasterPasswordUnlockData = + await this.masterPasswordService.makeMasterPasswordUnlockData( + newPassword, + kdfConfig, + salt, + userKey, + ); + + const request = UpdateTdeOffboardingPasswordRequest.newConstructorWithHint( + authenticationData, + unlockData, + newPasswordHint, + ); + + await this.masterPasswordApiService.putUpdateTdeOffboardingPassword(request); + + // Clear force set password reason to allow navigation back to vault. + await this.masterPasswordService.setForceSetPasswordReason(ForceSetPasswordReason.None, userId); + } } diff --git a/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.component.ts b/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.component.ts index f4ed38464ef..f45c657583c 100644 --- a/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.component.ts +++ b/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.component.ts @@ -43,6 +43,7 @@ import { I18nPipe } from "@bitwarden/ui-common"; import { SetInitialPasswordCredentials, SetInitialPasswordService, + SetInitialPasswordTdeOffboardingCredentials, SetInitialPasswordTdeOffboardingCredentialsOld, SetInitialPasswordUserType, } from "./set-initial-password.service.abstraction"; @@ -276,7 +277,7 @@ export class SetInitialPasswordComponent implements OnInit { case SetInitialPasswordUserType.OFFBOARDED_TDE_ORG_USER: // Remove wrapping "if" check and early return in PM-28143 if (passwordInputResult.newApisFlagEnabled) { - // ... + await this.setInitialPasswordTdeOffboarding(passwordInputResult); return; } @@ -340,6 +341,42 @@ export class SetInitialPasswordComponent implements OnInit { } } + private async setInitialPasswordTdeOffboarding(passwordInputResult: PasswordInputResult) { + const ctx = "Could not set initial password."; + + assertTruthy(passwordInputResult.newPassword, "newPassword", ctx); + assertTruthy(passwordInputResult.kdfConfig, "kdfConfig", ctx); + assertTruthy(passwordInputResult.salt, "salt", ctx); + assertNonNullish(passwordInputResult.newPasswordHint, "newPasswordHint", ctx); // can have an empty string as a valid value, so check non-nullish + + assertTruthy(this.userId, "userId", ctx); + + try { + const credentials: SetInitialPasswordTdeOffboardingCredentials = { + newPassword: passwordInputResult.newPassword, + kdfConfig: passwordInputResult.kdfConfig, + salt: passwordInputResult.salt, + newPasswordHint: passwordInputResult.newPasswordHint, + }; + + await this.setInitialPasswordService.setInitialPasswordTdeOffboarding( + credentials, + this.userId, + ); + + this.showSuccessToastByUserType(); + + await this.logoutService.logout(this.userId); + // navigate to root so redirect guard can properly route next active user or null user to correct page + await this.router.navigate(["/"]); + } catch (e) { + this.logService.error("Error setting initial password during TDE offboarding", e); + this.validationService.showError(e); + } finally { + this.submitting = false; + } + } + /** * @deprecated To be removed in PM-28143 */ diff --git a/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.service.abstraction.ts b/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.service.abstraction.ts index 52815e51bbd..2b3a4daaaad 100644 --- a/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.service.abstraction.ts +++ b/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.service.abstraction.ts @@ -64,6 +64,13 @@ export interface SetInitialPasswordTdeOffboardingCredentialsOld { newPasswordHint: string; } +export interface SetInitialPasswordTdeOffboardingCredentials { + newPassword: string; + kdfConfig: KdfConfig; + salt: MasterPasswordSalt; + newPasswordHint: string; +} + /** * Handles setting an initial password for an existing authed user. * @@ -102,4 +109,17 @@ export abstract class SetInitialPasswordService { credentials: SetInitialPasswordTdeOffboardingCredentialsOld, userId: UserId, ) => Promise; + + /** + * Sets an initial password for a user who logs in after their org offboarded from + * trusted device encryption and is now a master-password-encryption org: + * - {@link SetInitialPasswordUserType.OFFBOARDED_TDE_ORG_USER} + * + * @param passwordInputResult credentials object received from the `InputPasswordComponent` + * @param userId the account `userId` + */ + abstract setInitialPasswordTdeOffboarding: ( + credentials: SetInitialPasswordTdeOffboardingCredentials, + userId: UserId, + ) => Promise; } diff --git a/libs/common/src/auth/models/request/update-tde-offboarding-password.request.ts b/libs/common/src/auth/models/request/update-tde-offboarding-password.request.ts index 8a107aa6c32..f63774ac8bc 100644 --- a/libs/common/src/auth/models/request/update-tde-offboarding-password.request.ts +++ b/libs/common/src/auth/models/request/update-tde-offboarding-password.request.ts @@ -3,7 +3,28 @@ // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. // eslint-disable-next-line no-restricted-imports import { OrganizationUserResetPasswordRequest } from "@bitwarden/admin-console/common"; +import { + MasterPasswordAuthenticationData, + MasterPasswordUnlockData, +} from "@bitwarden/common/key-management/master-password/types/master-password.types"; export class UpdateTdeOffboardingPasswordRequest extends OrganizationUserResetPasswordRequest { masterPasswordHint: string; + + // This will eventually be changed to be an actual constructor, once all callers are updated. + // The body of this request will be changed to carry the authentication data and unlock data. + // https://bitwarden.atlassian.net/browse/PM-23234 + static newConstructorWithHint( + authenticationData: MasterPasswordAuthenticationData, + unlockData: MasterPasswordUnlockData, + masterPasswordHint: string, + ): UpdateTdeOffboardingPasswordRequest { + const request = OrganizationUserResetPasswordRequest.newConstructor( + authenticationData, + unlockData, + ) as UpdateTdeOffboardingPasswordRequest; + + request.masterPasswordHint = masterPasswordHint; + return request; + } }