diff --git a/apps/web/src/app/auth/core/services/change-password/web-change-password.service.spec.ts b/apps/web/src/app/auth/core/services/change-password/web-change-password.service.spec.ts index 151e837bd33e..8fae477bf7dd 100644 --- a/apps/web/src/app/auth/core/services/change-password/web-change-password.service.spec.ts +++ b/apps/web/src/app/auth/core/services/change-password/web-change-password.service.spec.ts @@ -40,10 +40,6 @@ describe("WebChangePasswordService", () => { }), }; - const currentPassword = "currentPassword"; - const newPassword = "newPassword"; - const newPasswordHint = "newPasswordHint"; - beforeEach(() => { keyService = mock(); masterPasswordApiService = mock(); @@ -64,23 +60,6 @@ describe("WebChangePasswordService", () => { ); }); - describe("rotateUserKeyMasterPasswordAndEncryptedData()", () => { - it("should call the method with the same name on the UserKeyRotationService with the correct arguments", async () => { - // Act - await sut.rotateUserKeyMasterPasswordAndEncryptedData( - currentPassword, - newPassword, - user, - newPasswordHint, - ); - - // Assert - expect( - userKeyRotationService.rotateUserKeyMasterPasswordAndEncryptedData, - ).toHaveBeenCalledWith(currentPassword, newPassword, user, newPasswordHint); - }); - }); - describe("changePasswordAndRotateUserKey()", () => { // Mock method params let passwordInputResult: PasswordInputResult; @@ -93,7 +72,6 @@ describe("WebChangePasswordService", () => { newPasswordHint: "new-password-hint", kdfConfig: DEFAULT_KDF_CONFIG, salt: "salt" as MasterPasswordSalt, - newApisWithInputPasswordFlagEnabled: true, }; // Mock returned/resolved values @@ -119,11 +97,11 @@ describe("WebChangePasswordService", () => { }); }); - it("should throw if newPasswordHint is null on the PasswordInputResult object", async () => { + it("should throw if newPasswordHint is undefined on the PasswordInputResult object", async () => { // Arrange const invalidPasswordInputResult: PasswordInputResult = { ...passwordInputResult, - newPasswordHint: null, + newPasswordHint: undefined, }; // Act diff --git a/apps/web/src/app/auth/core/services/change-password/web-change-password.service.ts b/apps/web/src/app/auth/core/services/change-password/web-change-password.service.ts index ff0cf0db9dfd..8d7c93a67470 100644 --- a/apps/web/src/app/auth/core/services/change-password/web-change-password.service.ts +++ b/apps/web/src/app/auth/core/services/change-password/web-change-password.service.ts @@ -57,23 +57,6 @@ export class WebChangePasswordService ); } - /** - * @deprecated To be removed in PM-28143 - */ - override async rotateUserKeyMasterPasswordAndEncryptedData( - currentPassword: string, - newPassword: string, - user: Account, - newPasswordHint: string, - ): Promise { - await this.userKeyRotationService.rotateUserKeyMasterPasswordAndEncryptedData( - currentPassword, - newPassword, - user, - newPasswordHint, - ); - } - async clearDeeplinkState() { await this.routerService.getAndClearLoginRedirectUrl(); } diff --git a/libs/angular/src/auth/password-management/change-password/change-password.component.ts b/libs/angular/src/auth/password-management/change-password/change-password.component.ts index 57ae84049233..e9c5b38e32d1 100644 --- a/libs/angular/src/auth/password-management/change-password/change-password.component.ts +++ b/libs/angular/src/auth/password-management/change-password/change-password.component.ts @@ -23,7 +23,6 @@ import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key- import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { SyncService } from "@bitwarden/common/platform/sync"; import { UserId } from "@bitwarden/common/types/guid"; import { AnonLayoutWrapperDataService, @@ -82,7 +81,6 @@ export class ChangePasswordComponent implements OnInit { private messagingService: MessagingService, private policyService: PolicyService, private toastService: ToastService, - private syncService: SyncService, private dialogService: DialogService, private logService: LogService, private logoutService: LogoutService, @@ -159,31 +157,10 @@ export class ChangePasswordComponent implements OnInit { throw new Error("activeAccount not found"); } - if (passwordInputResult.newApisWithInputPasswordFlagEnabled) { - await this.changePasswordService.changePasswordAndRotateUserKey( - passwordInputResult, - this.activeAccount, - ); - this.passwordChanged.emit(); - return; // EARLY RETURN for flagged logic - } - - if ( - passwordInputResult.currentPassword == null || - passwordInputResult.newPasswordHint == null - ) { - throw new Error("currentPassword or newPasswordHint not found"); - } - - await this.syncService.fullSync(true); - - await this.changePasswordService.rotateUserKeyMasterPasswordAndEncryptedData( - passwordInputResult.currentPassword, - passwordInputResult.newPassword, + await this.changePasswordService.changePasswordAndRotateUserKey( + passwordInputResult, this.activeAccount, - passwordInputResult.newPasswordHint, ); - this.passwordChanged.emit(); } else { if (!this.userId) { @@ -210,17 +187,13 @@ export class ChangePasswordComponent implements OnInit { this.passwordChanged.emit(); - if (passwordInputResult.newApisWithInputPasswordFlagEnabled) { - // TODO: investigate refactoring logout and follow-up routing in https://bitwarden.atlassian.net/browse/PM-32660 - await this.logoutService.logout(this.userId); + // TODO: investigate refactoring logout and follow-up routing in https://bitwarden.atlassian.net/browse/PM-32660 + await this.logoutService.logout(this.userId); - const shouldNavigateToRoot = this.changePasswordService.shouldNavigateToRoot(); - if (shouldNavigateToRoot) { - // navigate to root so redirect guard can properly route next active user (account switching) or null user to correct page - await this.router.navigate(["/"]); - } - } else { - this.messagingService.send("logout"); + const shouldNavigateToRoot = this.changePasswordService.shouldNavigateToRoot(); + if (shouldNavigateToRoot) { + // navigate to root so redirect guard can properly route next active user (account switching) or null user to correct page + await this.router.navigate(["/"]); } // Close the popout if we are in a browser extension popout. diff --git a/libs/angular/src/auth/password-management/change-password/change-password.service.abstraction.ts b/libs/angular/src/auth/password-management/change-password/change-password.service.abstraction.ts index 92b14c59605d..19f7a3937899 100644 --- a/libs/angular/src/auth/password-management/change-password/change-password.service.abstraction.ts +++ b/libs/angular/src/auth/password-management/change-password/change-password.service.abstraction.ts @@ -27,26 +27,6 @@ export abstract class ChangePasswordService { user: Account, ): Promise; - /** - * @deprecated To be removed in PM-28143 - * - * Creates a new user key and re-encrypts all required data with it. - * - does so by calling the underlying method on the `UserKeyRotationService` - * - implemented in Web only - * - * @param currentPassword the current password - * @param newPassword the new password - * @param user the user account - * @param newPasswordHint the new password hint - * @throws if called from a non-Web client - */ - abstract rotateUserKeyMasterPasswordAndEncryptedData( - currentPassword: string, - newPassword: string, - user: Account, - newPasswordHint: string, - ): Promise; - /** * Changes the user's password by building a `PasswordRequest` object that gets POSTed to the server. * @@ -55,18 +35,6 @@ export abstract class ChangePasswordService { * @throws if required values are not found on the `PasswordInputResult` * @throws an `InvalidCurrentPasswordError` if `proofOfDecryption` fails (i.e. if the current password is incorrect) * @throws if there is an error during the API call - * - * OLD DESCRIPTION FOR UNFLAGGED LOGIC: (the rest of this JSDoc below can be removed in PM-28143) - * - * Changes the user's password and re-encrypts the user key with the `newMasterKey`. - * - Specifically, this method uses credentials from the `passwordInputResult` to: - * 1. Decrypt the user key with the `currentMasterKey` - * 2. Re-encrypt that user key with the `newMasterKey`, resulting in a `newMasterKeyEncryptedUserKey` - * 3. Build a `PasswordRequest` object that gets POSTed to `"/accounts/password"` - * - * @param passwordInputResult credentials object received from the `InputPasswordComponent` - * @param userId the `userId` - * @throws if the `userId`, `currentMasterKey`, or `currentServerMasterKeyHash` is not found */ abstract changePassword(passwordInputResult: PasswordInputResult, userId: UserId): Promise; @@ -83,17 +51,6 @@ export abstract class ChangePasswordService { * @throws if required values are not found on the `PasswordInputResult` * @throws an `InvalidCurrentPasswordError` if `proofOfDecryption` fails (i.e. if the current password is incorrect) * @throws if there is an error during the API call - * - * OLD DESCRIPTION FOR UNFLAGGED LOGIC: (the rest of this JSDoc below can be removed in PM-28143) - * - * Changes the user's password and re-encrypts the user key with the `newMasterKey`. - * - Specifically, this method uses credentials from the `passwordInputResult` to: - * 1. Decrypt the user key with the `currentMasterKey` - * 2. Re-encrypt that user key with the `newMasterKey`, resulting in a `newMasterKeyEncryptedUserKey` - * 3. Build a `PasswordRequest` object that gets PUTed to `"/accounts/update-temp-password"` so that the - * ForcePasswordReset gets set to false. - * @param passwordInputResult - * @param userId */ abstract changePasswordForAccountRecovery( passwordInputResult: PasswordInputResult, diff --git a/libs/angular/src/auth/password-management/change-password/default-change-password.service.spec.ts b/libs/angular/src/auth/password-management/change-password/default-change-password.service.spec.ts index 6012a878e2c9..68f61be57e1d 100644 --- a/libs/angular/src/auth/password-management/change-password/default-change-password.service.spec.ts +++ b/libs/angular/src/auth/password-management/change-password/default-change-password.service.spec.ts @@ -8,7 +8,6 @@ import { Account } from "@bitwarden/common/auth/abstractions/account.service"; import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request"; import { UpdateTempPasswordRequest } from "@bitwarden/common/auth/models/request/update-temp-password.request"; -import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { MasterPasswordUnlockService } from "@bitwarden/common/key-management/master-password/abstractions/master-password-unlock.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { @@ -18,11 +17,10 @@ import { MasterPasswordSalt, MasterPasswordUnlockData, } from "@bitwarden/common/key-management/master-password/types/master-password.types"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { makeSymmetricCryptoKey, mockAccountInfoWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; -import { MasterKey, UserKey } from "@bitwarden/common/types/key"; -import { DEFAULT_KDF_CONFIG, KeyService, PBKDF2KdfConfig } from "@bitwarden/key-management"; +import { UserKey } from "@bitwarden/common/types/key"; +import { DEFAULT_KDF_CONFIG, KeyService } from "@bitwarden/key-management"; import { ChangePasswordService, @@ -49,25 +47,6 @@ describe("DefaultChangePasswordService", () => { }), }; - const passwordInputResult: PasswordInputResult = { - currentMasterKey: new SymmetricCryptoKey(new Uint8Array(32)) as MasterKey, - currentServerMasterKeyHash: "currentServerMasterKeyHash", - - newPassword: "newPassword", - newPasswordHint: "newPasswordHint", - newMasterKey: new SymmetricCryptoKey(new Uint8Array(32)) as MasterKey, - newServerMasterKeyHash: "newServerMasterKeyHash", - - kdfConfig: PBKDF2KdfConfig.createDefault(), - newApisWithInputPasswordFlagEnabled: false, - }; - - const decryptedUserKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey; - const newMasterKeyEncryptedUserKey: [UserKey, EncString] = [ - decryptedUserKey, - { encryptedString: "newMasterKeyEncryptedUserKey" } as EncString, - ]; - beforeEach(() => { keyService = mock(); masterPasswordApiService = mock(); @@ -80,9 +59,6 @@ describe("DefaultChangePasswordService", () => { masterPasswordService, masterPasswordUnlockService, ); - - masterPasswordService.decryptUserKeyWithMasterKey.mockResolvedValue(decryptedUserKey); - keyService.encryptUserKeyWithMasterKey.mockResolvedValue(newMasterKeyEncryptedUserKey); }); describe("changePasswordAndRotateUserKey()", () => { @@ -97,7 +73,6 @@ describe("DefaultChangePasswordService", () => { newPasswordHint: "new-password-hint", kdfConfig: DEFAULT_KDF_CONFIG, salt: "salt" as MasterPasswordSalt, - newApisWithInputPasswordFlagEnabled: true, }; }); @@ -112,7 +87,7 @@ describe("DefaultChangePasswordService", () => { }); }); - describe("changePassword() and changePasswordForAccountRecovery() [PM27086_UpdateAuthenticationApisForInputPassword flag ENABLED]", () => { + describe("changePassword() and changePasswordForAccountRecovery()", () => { // Mock method params let passwordInputResult: PasswordInputResult; @@ -129,22 +104,21 @@ describe("DefaultChangePasswordService", () => { newPasswordHint: "new-password-hint", kdfConfig: DEFAULT_KDF_CONFIG, salt: "salt" as MasterPasswordSalt, - newApisWithInputPasswordFlagEnabled: true, }; // Mock method data userKey = makeSymmetricCryptoKey(64) as UserKey; newAuthenticationData = { - salt: passwordInputResult.salt, - kdf: passwordInputResult.kdfConfig, + salt: passwordInputResult.salt!, + kdf: passwordInputResult.kdfConfig!, masterPasswordAuthenticationHash: "newMasterPasswordAuthenticationHash" as MasterPasswordAuthenticationHash, }; newUnlockData = { - salt: passwordInputResult.salt, - kdf: passwordInputResult.kdfConfig, + salt: passwordInputResult.salt!, + kdf: passwordInputResult.kdfConfig!, masterKeyWrappedUserKey: "newMasterKeyWrappedUserKey" as MasterKeyWrappedUserKey, } as MasterPasswordUnlockData; @@ -160,8 +134,8 @@ describe("DefaultChangePasswordService", () => { beforeEach(() => { currentAuthenticationData = { - salt: passwordInputResult.salt, - kdf: passwordInputResult.kdfConfig, + salt: passwordInputResult.salt!, + kdf: passwordInputResult.kdfConfig!, masterPasswordAuthenticationHash: "currentMasterPasswordAuthenticationHash" as MasterPasswordAuthenticationHash, }; @@ -170,7 +144,7 @@ describe("DefaultChangePasswordService", () => { currentAuthenticationData.masterPasswordAuthenticationHash, newAuthenticationData, newUnlockData, - passwordInputResult.newPasswordHint, + passwordInputResult.newPasswordHint!, ); masterPasswordService.makeMasterPasswordAuthenticationData @@ -285,7 +259,7 @@ describe("DefaultChangePasswordService", () => { request = UpdateTempPasswordRequest.newConstructorWithHint( newAuthenticationData, newUnlockData, - passwordInputResult.newPasswordHint, + passwordInputResult.newPasswordHint!, ); masterPasswordService.makeMasterPasswordAuthenticationData.mockResolvedValue( @@ -401,166 +375,4 @@ describe("DefaultChangePasswordService", () => { expect(shouldNavigateToRoot).toBe(false); }); }); - - /** - * @deprecated To be removed in PM-28143. When you remove this, check also if there are any imports/properties - * in the test setup above that are now un-used and can also be removed. - */ - describe("changePassword() [PM27086_UpdateAuthenticationApisForInputPassword flag DISABLED]", () => { - it("should call the postPassword() API method with a the correct PasswordRequest credentials", async () => { - // Act - await sut.changePassword(passwordInputResult, userId); - - // Assert - expect(masterPasswordApiService.postPassword).toHaveBeenCalledWith( - expect.objectContaining({ - masterPasswordHash: passwordInputResult.currentServerMasterKeyHash, - masterPasswordHint: passwordInputResult.newPasswordHint, - newMasterPasswordHash: passwordInputResult.newServerMasterKeyHash, - key: newMasterKeyEncryptedUserKey[1].encryptedString, - }), - ); - }); - - it("should call decryptUserKeyWithMasterKey and encryptUserKeyWithMasterKey", async () => { - // Act - await sut.changePassword(passwordInputResult, userId); - - // Assert - expect(masterPasswordService.decryptUserKeyWithMasterKey).toHaveBeenCalledWith( - passwordInputResult.currentMasterKey, - userId, - ); - expect(keyService.encryptUserKeyWithMasterKey).toHaveBeenCalledWith( - passwordInputResult.newMasterKey, - decryptedUserKey, - ); - }); - - it("should throw if a userId was not found", async () => { - // Arrange - const userId: null = null; - - // Act - const testFn = sut.changePassword(passwordInputResult, userId); - - // Assert - await expect(testFn).rejects.toThrow("userId not found"); - }); - - it("should throw if a currentMasterKey was not found", async () => { - // Arrange - const incorrectPasswordInputResult = { ...passwordInputResult }; - incorrectPasswordInputResult.currentMasterKey = undefined; - - // Act - const testFn = sut.changePassword(incorrectPasswordInputResult, userId); - - // Assert - await expect(testFn).rejects.toThrow( - "invalid PasswordInputResult credentials, could not change password", - ); - }); - - it("should throw if a currentServerMasterKeyHash was not found", async () => { - // Arrange - const incorrectPasswordInputResult = { ...passwordInputResult }; - incorrectPasswordInputResult.currentServerMasterKeyHash = undefined; - - // Act - const testFn = sut.changePassword(incorrectPasswordInputResult, userId); - - // Assert - await expect(testFn).rejects.toThrow( - "invalid PasswordInputResult credentials, could not change password", - ); - }); - - it("should throw an error if user key decryption fails", async () => { - // Arrange - masterPasswordService.decryptUserKeyWithMasterKey.mockResolvedValue(null); - - // Act - const testFn = sut.changePassword(passwordInputResult, userId); - - // Assert - await expect(testFn).rejects.toThrow("Could not decrypt user key"); - }); - - it("should throw an error if postPassword() fails", async () => { - // Arrange - masterPasswordApiService.postPassword.mockRejectedValueOnce(new Error("error")); - - // Act - const testFn = sut.changePassword(passwordInputResult, userId); - - // Assert - await expect(testFn).rejects.toThrow("Could not change password"); - expect(masterPasswordApiService.postPassword).toHaveBeenCalled(); - }); - }); - - /** - * @deprecated To be removed in PM-28143. When you remove this, check also if there are any imports/properties - * in the test setup above that are now un-used and can also be removed. - */ - describe("rotateUserKeyMasterPasswordAndEncryptedData()", () => { - it("should throw an error (the method is only implemented in Web)", async () => { - // Act - const promise = sut.rotateUserKeyMasterPasswordAndEncryptedData( - "currentPassword", - "newPassword", - user, - "newPasswordHint", - ); - - // Assert - await expect(promise).rejects.toThrow( - "rotateUserKeyMasterPasswordAndEncryptedData() is only implemented in Web", - ); - }); - }); - - /** - * @deprecated To be removed in PM-28143. When you remove this, check also if there are any imports/properties - * in the test setup above that are now un-used and can also be removed. - */ - describe("changePasswordForAccountRecovery() [PM27086_UpdateAuthenticationApisForInputPassword flag DISABLED]", () => { - it("should call the putUpdateTempPassword() API method with the correct UpdateTempPasswordRequest credentials", async () => { - // Act - await sut.changePasswordForAccountRecovery(passwordInputResult, userId); - - // Assert - expect(masterPasswordApiService.putUpdateTempPassword).toHaveBeenCalledWith( - expect.objectContaining({ - newMasterPasswordHash: passwordInputResult.newServerMasterKeyHash, - masterPasswordHint: passwordInputResult.newPasswordHint, - key: newMasterKeyEncryptedUserKey[1].encryptedString, - }), - ); - }); - - it("should throw an error if user key decryption fails", async () => { - // Arrange - masterPasswordService.decryptUserKeyWithMasterKey.mockResolvedValue(null); - - // Act - const testFn = sut.changePasswordForAccountRecovery(passwordInputResult, userId); - - // Assert - await expect(testFn).rejects.toThrow("Could not decrypt user key"); - }); - - it("should throw an error if putUpdateTempPassword() fails", async () => { - // Arrange - masterPasswordApiService.putUpdateTempPassword.mockRejectedValueOnce(new Error("error")); - - // Act - const testFn = sut.changePasswordForAccountRecovery(passwordInputResult, userId); - - // Assert - await expect(testFn).rejects.toThrow("Could not change password"); - expect(masterPasswordApiService.putUpdateTempPassword).toHaveBeenCalled(); - }); - }); }); diff --git a/libs/angular/src/auth/password-management/change-password/default-change-password.service.ts b/libs/angular/src/auth/password-management/change-password/default-change-password.service.ts index fb4fe8a86de6..7fac3eb68b5d 100644 --- a/libs/angular/src/auth/password-management/change-password/default-change-password.service.ts +++ b/libs/angular/src/auth/password-management/change-password/default-change-password.service.ts @@ -6,7 +6,6 @@ import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/ma import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request"; import { UpdateTempPasswordRequest } from "@bitwarden/common/auth/models/request/update-temp-password.request"; import { assertNonNullish, assertTruthy } from "@bitwarden/common/auth/utils"; -import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { MasterPasswordUnlockService } from "@bitwarden/common/key-management/master-password/abstractions/master-password-unlock.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { @@ -39,187 +38,90 @@ export class DefaultChangePasswordService implements ChangePasswordService { throw new Error("changePasswordAndRotateUserKey() is only implemented in Web"); } - async rotateUserKeyMasterPasswordAndEncryptedData( - currentPassword: string, - newPassword: string, - user: Account, - hint: string, - ): Promise { - throw new Error("rotateUserKeyMasterPasswordAndEncryptedData() is only implemented in Web"); - } - - /** - * @deprecated To be removed in PM-28143. - */ - private async preparePasswordChange( - passwordInputResult: PasswordInputResult, - userId: UserId | null, - request: PasswordRequest | UpdateTempPasswordRequest, - ): Promise<[UserKey, EncString]> { - if (!userId) { - throw new Error("userId not found"); - } - if ( - !passwordInputResult.currentMasterKey || - !passwordInputResult.currentServerMasterKeyHash || - !passwordInputResult.newMasterKey || - !passwordInputResult.newServerMasterKeyHash || - passwordInputResult.newPasswordHint == null - ) { - throw new Error("invalid PasswordInputResult credentials, could not change password"); - } - - const decryptedUserKey = await this.masterPasswordService.decryptUserKeyWithMasterKey( - passwordInputResult.currentMasterKey, + async changePassword(passwordInputResult: PasswordInputResult, userId: UserId) { + const context = "Could not change password."; + assertTruthy(passwordInputResult.currentPassword, "currentPassword", context); + assertTruthy(passwordInputResult.newPassword, "newPassword", context); + assertNonNullish(passwordInputResult.kdfConfig, "kdfConfig", context); + assertTruthy(passwordInputResult.salt, "salt", context); + assertNonNullish(passwordInputResult.newPasswordHint, "newPasswordHint", context); // can have an empty string as a meaningful value, so check non-nullish + + // Verify that the current password is correct + const currentPasswordVerified = await this.masterPasswordUnlockService.proofOfDecryption( + passwordInputResult.currentPassword, userId, ); - if (decryptedUserKey == null) { - throw new Error("Could not decrypt user key"); + if (!currentPasswordVerified) { + throw new InvalidCurrentPasswordError(); } - const newKeyValue = await this.keyService.encryptUserKeyWithMasterKey( - passwordInputResult.newMasterKey, - decryptedUserKey, - ); + // Current password has been verified, so get the user key from state + const userKey = await firstValueFromOrThrow(this.keyService.userKey$(userId), "userKey"); - if (request instanceof PasswordRequest) { - request.masterPasswordHash = passwordInputResult.currentServerMasterKeyHash; - request.newMasterPasswordHash = passwordInputResult.newServerMasterKeyHash; - request.masterPasswordHint = passwordInputResult.newPasswordHint; - } else if (request instanceof UpdateTempPasswordRequest) { - request.newMasterPasswordHash = passwordInputResult.newServerMasterKeyHash; - request.masterPasswordHint = passwordInputResult.newPasswordHint; - } - - return newKeyValue; - } - - async changePassword(passwordInputResult: PasswordInputResult, userId: UserId) { - if (passwordInputResult.newApisWithInputPasswordFlagEnabled) { - const context = "Could not change password."; - assertTruthy(passwordInputResult.currentPassword, "currentPassword", context); - assertTruthy(passwordInputResult.newPassword, "newPassword", context); - assertNonNullish(passwordInputResult.kdfConfig, "kdfConfig", context); - assertTruthy(passwordInputResult.salt, "salt", context); - assertNonNullish(passwordInputResult.newPasswordHint, "newPasswordHint", context); // can have an empty string as a meaningful value, so check non-nullish - - // Verify that the current password is correct - const currentPasswordVerified = await this.masterPasswordUnlockService.proofOfDecryption( + // Use current password to make current auth data so we can send the current auth hash to the server + const currentAuthenticationData = + await this.masterPasswordService.makeMasterPasswordAuthenticationData( passwordInputResult.currentPassword, - userId, - ); - - if (!currentPasswordVerified) { - throw new InvalidCurrentPasswordError(); - } - - // Current password has been verified, so get the user key from state - const userKey = await firstValueFromOrThrow(this.keyService.userKey$(userId), "userKey"); - - // Use current password to make current auth data so we can send the current auth hash to the server - const currentAuthenticationData = - await this.masterPasswordService.makeMasterPasswordAuthenticationData( - passwordInputResult.currentPassword, - passwordInputResult.kdfConfig, - passwordInputResult.salt, - ); - - // Use new password to make new auth and unlock data that we can send to the server - const { newAuthenticationData, newUnlockData } = await this.makeNewAuthAndUnlockData( - passwordInputResult.newPassword, passwordInputResult.kdfConfig, passwordInputResult.salt, - userKey, - ); - - const request = PasswordRequest.newConstructor( - currentAuthenticationData.masterPasswordAuthenticationHash, - newAuthenticationData, - newUnlockData, - passwordInputResult.newPasswordHint, ); - await this.masterPasswordApiService.postPassword(request); - - return; // EARLY RETURN for flagged logic - } - - const request = new PasswordRequest(); - - const newMasterKeyEncryptedUserKey = await this.preparePasswordChange( - passwordInputResult, - userId, - request, + // Use new password to make new auth and unlock data that we can send to the server + const { newAuthenticationData, newUnlockData } = await this.makeNewAuthAndUnlockData( + passwordInputResult.newPassword, + passwordInputResult.kdfConfig, + passwordInputResult.salt, + userKey, ); - request.key = newMasterKeyEncryptedUserKey[1].encryptedString as string; + const request = PasswordRequest.newConstructor( + currentAuthenticationData.masterPasswordAuthenticationHash, + newAuthenticationData, + newUnlockData, + passwordInputResult.newPasswordHint, + ); - try { - await this.masterPasswordApiService.postPassword(request); - } catch { - throw new Error("Could not change password"); - } + await this.masterPasswordApiService.postPassword(request); } async changePasswordForAccountRecovery(passwordInputResult: PasswordInputResult, userId: UserId) { - if (passwordInputResult.newApisWithInputPasswordFlagEnabled) { - const context = "Could not change password for account recovery."; - assertTruthy(passwordInputResult.currentPassword, "currentPassword", context); - assertTruthy(passwordInputResult.newPassword, "newPassword", context); - assertNonNullish(passwordInputResult.kdfConfig, "kdfConfig", context); - assertTruthy(passwordInputResult.salt, "salt", context); - assertNonNullish(passwordInputResult.newPasswordHint, "newPasswordHint", context); // can have an empty string as a meaningful value, so check non-nullish - - // Verify that the current password is correct - const currentPasswordVerified = await this.masterPasswordUnlockService.proofOfDecryption( - passwordInputResult.currentPassword, - userId, - ); - - if (!currentPasswordVerified) { - throw new InvalidCurrentPasswordError(); - } - - // Current password has been verified, so get the user key from state - const userKey = await firstValueFromOrThrow(this.keyService.userKey$(userId), "userKey"); - - // Use new password to make new auth and unlock data that we can send to the server - const { newAuthenticationData, newUnlockData } = await this.makeNewAuthAndUnlockData( - passwordInputResult.newPassword, - passwordInputResult.kdfConfig, - passwordInputResult.salt, - userKey, - ); - - const request = UpdateTempPasswordRequest.newConstructorWithHint( - newAuthenticationData, - newUnlockData, - passwordInputResult.newPasswordHint, - ); - - // TODO: PM-23047 will look to consolidate this into the change password endpoint. - await this.masterPasswordApiService.putUpdateTempPassword(request); + const context = "Could not change password for account recovery."; + assertTruthy(passwordInputResult.currentPassword, "currentPassword", context); + assertTruthy(passwordInputResult.newPassword, "newPassword", context); + assertNonNullish(passwordInputResult.kdfConfig, "kdfConfig", context); + assertTruthy(passwordInputResult.salt, "salt", context); + assertNonNullish(passwordInputResult.newPasswordHint, "newPasswordHint", context); // can have an empty string as a meaningful value, so check non-nullish + + // Verify that the current password is correct + const currentPasswordVerified = await this.masterPasswordUnlockService.proofOfDecryption( + passwordInputResult.currentPassword, + userId, + ); - return; // EARLY RETURN for flagged logic + if (!currentPasswordVerified) { + throw new InvalidCurrentPasswordError(); } - const request = new UpdateTempPasswordRequest(); + // Current password has been verified, so get the user key from state + const userKey = await firstValueFromOrThrow(this.keyService.userKey$(userId), "userKey"); - const newMasterKeyEncryptedUserKey = await this.preparePasswordChange( - passwordInputResult, - userId, - request, + // Use new password to make new auth and unlock data that we can send to the server + const { newAuthenticationData, newUnlockData } = await this.makeNewAuthAndUnlockData( + passwordInputResult.newPassword, + passwordInputResult.kdfConfig, + passwordInputResult.salt, + userKey, ); - request.key = newMasterKeyEncryptedUserKey[1].encryptedString as string; + const request = UpdateTempPasswordRequest.newConstructorWithHint( + newAuthenticationData, + newUnlockData, + passwordInputResult.newPasswordHint, + ); - try { - // TODO: PM-23047 will look to consolidate this into the change password endpoint. - await this.masterPasswordApiService.putUpdateTempPassword(request); - } catch { - throw new Error("Could not change password"); - } + // TODO: PM-23047 will look to consolidate this into the change password endpoint. + await this.masterPasswordApiService.putUpdateTempPassword(request); } private async makeNewAuthAndUnlockData( diff --git a/libs/auth/src/angular/input-password/input-password.component.ts b/libs/auth/src/angular/input-password/input-password.component.ts index 67e551729fc6..a019d2fa21e4 100644 --- a/libs/auth/src/angular/input-password/input-password.component.ts +++ b/libs/auth/src/angular/input-password/input-password.component.ts @@ -331,7 +331,7 @@ export class InputPasswordComponent implements OnInit { throw new Error("KdfConfig not found."); } - // Determine salt. Branches on userId presence: + // 2. Determine salt. Branches on userId presence: // - SetInitialPasswordAccountRegistration: no userId -> derives salt from email via emailToSalt() // - SetInitialPasswordAuthedUser, ChangePassword, ChangePasswordWithOptionalUserKeyRotation: // have an active userId -> retrieves stored salt via saltForUser$() @@ -356,24 +356,6 @@ export class InputPasswordComponent implements OnInit { FeatureFlag.PM27086_UpdateAuthenticationApisForInputPassword, ); - // Remove this current password verification block in PM-28143. Current password verification - // is performed by consumers when flag is on. - if (!newApisWithInputPasswordFlagEnabled) { - // 2. Verify current password is correct (if necessary) - if ( - this.flow === InputPasswordFlow.ChangePassword || - this.flow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation - ) { - const currentPasswordVerified = await this.verifyCurrentPassword( - currentPassword, - this.kdfConfig, - ); - if (!currentPasswordVerified) { - return; - } - } - } - // 3. Verify new password const newPasswordVerified = await this.verifyNewPassword( newPassword, @@ -532,47 +514,6 @@ export class InputPasswordComponent implements OnInit { return passwordInputResult; } - /** - * @deprecated To be removed in PM-28143 - * - * Returns `true` if the current password is correct (it can be used to successfully decrypt - * the masterKeyEncryptedUserKey), `false` otherwise - */ - private async verifyCurrentPassword( - currentPassword: string, - kdfConfig: KdfConfig, - ): Promise { - if (!this.email) { - throw new Error("Email is required to verify current password."); - } - if (!this.userId) { - throw new Error("userId is required to verify current password."); - } - - const currentMasterKey = await this.keyService.makeMasterKey( - currentPassword, - this.email, - kdfConfig, - ); - - const decryptedUserKey = await this.masterPasswordService.decryptUserKeyWithMasterKey( - currentMasterKey, - this.userId, - ); - - if (decryptedUserKey == null) { - this.toastService.showToast({ - variant: "error", - title: "", - message: this.i18nService.t("invalidMasterPassword"), - }); - - return false; - } - - return true; - } - /** * Returns `true` if the new password is not weak or breached and it passes * any enforced org policy options, `false` otherwise