1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-22 20:34:04 +00:00

[PM-27086 Change Password] Refactor ChangePasswordService methods and add new constructor to request model

This commit is contained in:
rr-bw
2026-01-22 12:28:37 -08:00
parent c9c6edd4f1
commit f629aa706a
2 changed files with 97 additions and 0 deletions

View File

@@ -1,3 +1,5 @@
import { firstValueFrom } from "rxjs";
// 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 { PasswordInputResult } from "@bitwarden/auth/angular";
@@ -7,6 +9,10 @@ import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.
import { UpdateTempPasswordRequest } from "@bitwarden/common/auth/models/request/update-temp-password.request";
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 {
MasterPasswordAuthenticationData,
MasterPasswordUnlockData,
} from "@bitwarden/common/key-management/master-password/types/master-password.types";
import { UserId } from "@bitwarden/common/types/guid";
import { UserKey } from "@bitwarden/common/types/key";
import { KeyService } from "@bitwarden/key-management";
@@ -74,6 +80,15 @@ export class DefaultChangePasswordService implements ChangePasswordService {
}
async changePassword(passwordInputResult: PasswordInputResult, userId: UserId | null) {
if (passwordInputResult.newApisWithInputPasswordFlagEnabled) {
const { newAuthenticationData, newUnlockData } =
await this.confirmCurrentPasswordAndMakeNewAuthAndUnlockData(passwordInputResult, userId);
const request = PasswordRequest.newConstructor(newAuthenticationData, newUnlockData);
await this.masterPasswordApiService.postPassword(request);
return; // EARLY RETURN for flagged logic
}
const request = new PasswordRequest();
const newMasterKeyEncryptedUserKey = await this.preparePasswordChange(
@@ -92,6 +107,19 @@ export class DefaultChangePasswordService implements ChangePasswordService {
}
async changePasswordForAccountRecovery(passwordInputResult: PasswordInputResult, userId: UserId) {
if (passwordInputResult.newApisWithInputPasswordFlagEnabled) {
const { newAuthenticationData, newUnlockData } =
await this.confirmCurrentPasswordAndMakeNewAuthAndUnlockData(passwordInputResult, userId);
const request = UpdateTempPasswordRequest.newConstructorWithHint(
newAuthenticationData,
newUnlockData,
passwordInputResult.newPasswordHint,
);
await this.masterPasswordApiService.putUpdateTempPassword(request);
return; // EARLY RETURN for flagged logic
}
const request = new UpdateTempPasswordRequest();
const newMasterKeyEncryptedUserKey = await this.preparePasswordChange(
@@ -109,4 +137,52 @@ export class DefaultChangePasswordService implements ChangePasswordService {
throw new Error("Could not change password");
}
}
private async confirmCurrentPasswordAndMakeNewAuthAndUnlockData(
passwordInputResult: PasswordInputResult,
userId: UserId | null,
): Promise<{
newAuthenticationData: MasterPasswordAuthenticationData;
newUnlockData: MasterPasswordUnlockData;
}> {
if (
!passwordInputResult.currentPassword ||
!passwordInputResult.newPassword ||
!passwordInputResult.salt ||
passwordInputResult.kdfConfig == null ||
passwordInputResult.newPasswordHint == null
) {
throw new Error("invalid PasswordInputResult credentials, could not change password");
}
// Confirm that the current password + unlock data can decrypt the user key (i.e the current password is valid)
const currentUnlockData = await firstValueFrom(
this.masterPasswordService.masterPasswordUnlockData$(userId),
);
const userKey = await this.masterPasswordService.unwrapUserKeyFromMasterPasswordUnlockData(
passwordInputResult.currentPassword,
currentUnlockData,
);
if (userKey == null) {
throw new Error("Could not decrypt user key");
}
// Create new authentication data with the new password (includes a new masterPasswordAuthenticationHash)
const newAuthenticationData =
await this.masterPasswordService.makeMasterPasswordAuthenticationData(
passwordInputResult.newPassword,
passwordInputResult.kdfConfig,
passwordInputResult.salt,
);
// Create new unlock data with the new password (includes a new masterKeyWrappedUserKey)
const newUnlockData = await this.masterPasswordService.makeMasterPasswordUnlockData(
passwordInputResult.newPassword,
passwordInputResult.kdfConfig,
passwordInputResult.salt,
userKey,
);
return { newAuthenticationData, newUnlockData };
}
}

View File

@@ -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 UpdateTempPasswordRequest 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,
): UpdateTempPasswordRequest {
const request = OrganizationUserResetPasswordRequest.newConstructor(
authenticationData,
unlockData,
) as UpdateTempPasswordRequest;
request.masterPasswordHint = masterPasswordHint;
return request;
}
}