diff --git a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.spec.ts b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.spec.ts index e442294d632e..54dc8af17e5f 100644 --- a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.spec.ts +++ b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.spec.ts @@ -21,7 +21,6 @@ import { MasterPasswordSalt, MasterPasswordUnlockData, } from "@bitwarden/common/key-management/master-password/types/master-password.types"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { EncryptionType } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -29,8 +28,8 @@ import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/sym import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; import { CsprngArray } from "@bitwarden/common/types/csprng"; import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; -import { UserKey, OrgKey, MasterKey } from "@bitwarden/common/types/key"; -import { DEFAULT_KDF_CONFIG, KdfConfig, KdfType, KeyService } from "@bitwarden/key-management"; +import { UserKey, OrgKey } from "@bitwarden/common/types/key"; +import { DEFAULT_KDF_CONFIG, KdfConfig, KeyService } from "@bitwarden/key-management"; import { OrganizationUserResetPasswordService } from "./organization-user-reset-password.service"; @@ -49,7 +48,6 @@ describe("OrganizationUserResetPasswordService", () => { const mockUserId = Utils.newGuid() as UserId; let accountService: FakeAccountService; let masterPasswordService: FakeMasterPasswordService; - let configService: MockProxy; beforeAll(() => { keyService = mock(); @@ -60,7 +58,6 @@ describe("OrganizationUserResetPasswordService", () => { i18nService = mock(); accountService = mockAccountServiceWith(mockUserId); masterPasswordService = new FakeMasterPasswordService(); - configService = mock(); sut = new OrganizationUserResetPasswordService( keyService, @@ -71,7 +68,6 @@ describe("OrganizationUserResetPasswordService", () => { i18nService, accountService, masterPasswordService, - configService, ); }); @@ -144,82 +140,13 @@ describe("OrganizationUserResetPasswordService", () => { }); }); - /** - * @deprecated This 'describe' 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("resetMasterPassword [PM27086_UpdateAuthenticationApisForInputPassword flag DISABLED]", () => { - const PM27086_UpdateAuthenticationApisForInputPasswordFlagEnabled = false; - - const mockNewMP = "new-password"; - const mockEmail = "test@example.com"; - const mockOrgUserId = "test-org-user-id"; - const mockOrgId = "test-org-id"; - - beforeEach(() => { - configService.getFeatureFlag.mockResolvedValue( - PM27086_UpdateAuthenticationApisForInputPasswordFlagEnabled, - ); - - organizationUserApiService.getOrganizationUserResetPasswordDetails.mockResolvedValue( - new OrganizationUserResetPasswordDetailsResponse({ - kdf: KdfType.PBKDF2_SHA256, - kdfIterations: 5000, - resetPasswordKey: "test-reset-password-key", - encryptedPrivateKey: "test-encrypted-private-key", - }), - ); - - const mockRandomBytes = new Uint8Array(64) as CsprngArray; - const mockOrgKey = new SymmetricCryptoKey(mockRandomBytes) as OrgKey; - keyService.orgKeys$.mockReturnValue( - of({ [mockOrgId]: mockOrgKey } as Record), - ); - - encryptService.decryptToBytes.mockResolvedValue(mockRandomBytes); - - encryptService.rsaDecrypt.mockResolvedValue(mockRandomBytes); - const mockMasterKey = new SymmetricCryptoKey(mockRandomBytes) as MasterKey; - keyService.makeMasterKey.mockResolvedValue(mockMasterKey); - keyService.hashMasterKey.mockResolvedValue("test-master-key-hash"); - - const mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey; - keyService.encryptUserKeyWithMasterKey.mockResolvedValue([ - mockUserKey, - new EncString(EncryptionType.AesCbc256_HmacSha256_B64, "test-encrypted-user-key"), - ]); - }); - - it("should reset the user's master password", async () => { - await sut.resetMasterPassword(mockNewMP, mockEmail, mockOrgUserId, mockOrgId); - expect(organizationUserApiService.putOrganizationUserRecoverAccount).toHaveBeenCalled(); - }); - - it("should throw an error if the user details are null", async () => { - organizationUserApiService.getOrganizationUserResetPasswordDetails.mockResolvedValue(null); - await expect( - sut.resetMasterPassword(mockNewMP, mockEmail, mockOrgUserId, mockOrgId), - ).rejects.toThrow(); - }); - - it("should throw an error if the org key is null", async () => { - keyService.orgKeys$.mockReturnValue(of(null)); - await expect( - sut.resetMasterPassword(mockNewMP, mockEmail, mockOrgUserId, mockOrgId), - ).rejects.toThrow(); - }); - }); - - describe("resetMasterPassword [PM27086_UpdateAuthenticationApisForInputPassword flag ENABLED]", () => { + describe("resetMasterPassword", () => { // Mock sut method parameters const newMasterPassword = "new-master-password"; const email = "user@example.com"; const orgUserId = "org-user-id"; const orgId = "org-id" as OrganizationId; - // Mock feature flag value - const PM27086_UpdateAuthenticationApisForInputPasswordFlagEnabled = true; - // Mock method data let organizationUserResetPasswordDetailsResponse: OrganizationUserResetPasswordDetailsResponse; let salt: MasterPasswordSalt; @@ -229,11 +156,6 @@ describe("OrganizationUserResetPasswordService", () => { let userKey: UserKey; beforeEach(() => { - // Mock feature flag value - configService.getFeatureFlag.mockResolvedValue( - PM27086_UpdateAuthenticationApisForInputPasswordFlagEnabled, - ); - // Mock method data kdfConfig = DEFAULT_KDF_CONFIG; @@ -381,9 +303,7 @@ describe("OrganizationUserResetPasswordService", () => { let unlockData: MasterPasswordUnlockData; /** Sets up mocks needed when resetMasterPassword is true */ - function setupPasswordResetMocks(flagEnabled: boolean) { - configService.getFeatureFlag.mockResolvedValue(flagEnabled); - + function setupPasswordResetMocks() { kdfConfig = DEFAULT_KDF_CONFIG; organizationUserApiService.getOrganizationUserResetPasswordDetails.mockResolvedValue( @@ -406,46 +326,29 @@ describe("OrganizationUserResetPasswordService", () => { const mockDecryptedUserKey = new SymmetricCryptoKey(new Uint8Array(64).fill(3)); encryptService.decapsulateKeyUnsigned.mockResolvedValue(mockDecryptedUserKey); - if (flagEnabled) { - salt = email as MasterPasswordSalt; - masterPasswordService.mock.emailToSalt.mockReturnValue(salt); - - authenticationData = { - salt, - kdf: kdfConfig, - masterPasswordAuthenticationHash: - "masterPasswordAuthenticationHash" as MasterPasswordAuthenticationHash, - }; - - unlockData = { - salt, - kdf: kdfConfig, - masterKeyWrappedUserKey: "masterKeyWrappedUserKey" as MasterKeyWrappedUserKey, - } as MasterPasswordUnlockData; - - masterPasswordService.mock.makeMasterPasswordAuthenticationData.mockResolvedValue( - authenticationData, - ); - masterPasswordService.mock.makeMasterPasswordUnlockData.mockResolvedValue(unlockData); - } else { - const mockRandomBytes = new Uint8Array(64) as CsprngArray; - const mockMasterKey = new SymmetricCryptoKey(mockRandomBytes) as MasterKey; - keyService.makeMasterKey.mockResolvedValue(mockMasterKey); - keyService.hashMasterKey.mockResolvedValue("test-master-key-hash"); - const mockEncryptedUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey; - keyService.encryptUserKeyWithMasterKey.mockResolvedValue([ - mockEncryptedUserKey, - new EncString(EncryptionType.AesCbc256_HmacSha256_B64, "test-encrypted-user-key"), - ]); - } + salt = email as MasterPasswordSalt; + masterPasswordService.mock.emailToSalt.mockReturnValue(salt); + + authenticationData = { + salt, + kdf: kdfConfig, + masterPasswordAuthenticationHash: + "masterPasswordAuthenticationHash" as MasterPasswordAuthenticationHash, + }; + + unlockData = { + salt, + kdf: kdfConfig, + masterKeyWrappedUserKey: "masterKeyWrappedUserKey" as MasterKeyWrappedUserKey, + } as MasterPasswordUnlockData; + + masterPasswordService.mock.makeMasterPasswordAuthenticationData.mockResolvedValue( + authenticationData, + ); + masterPasswordService.mock.makeMasterPasswordUnlockData.mockResolvedValue(unlockData); } describe("reset 2FA only", () => { - beforeEach(() => { - // No feature flag or password details needed for 2FA-only reset - configService.getFeatureFlag.mockResolvedValue(false); - }); - it("should call putOrganizationUserRecoverAccount with resetTwoFactor: true and resetMasterPassword: false", async () => { await sut.recoverAccount({ organizationUserId: orgUserId, @@ -487,62 +390,9 @@ describe("OrganizationUserResetPasswordService", () => { }); }); - describe("reset master password only [PM27086_UpdateAuthenticationApisForInputPassword flag DISABLED]", () => { - beforeEach(() => { - setupPasswordResetMocks(false); - }); - - it("should call putOrganizationUserRecoverAccount with resetMasterPassword: true and resetTwoFactor: false", async () => { - await sut.recoverAccount({ - organizationUserId: orgUserId, - organizationId: orgId, - resetMasterPassword: true, - resetTwoFactor: false, - newMasterPassword, - email, - }); - - expect(organizationUserApiService.putOrganizationUserRecoverAccount).toHaveBeenCalledWith( - orgId, - orgUserId, - expect.objectContaining({ resetMasterPassword: true, resetTwoFactor: false }), - ); - }); - - it("should throw if reset password details are null", async () => { - organizationUserApiService.getOrganizationUserResetPasswordDetails.mockResolvedValue(null); - - await expect( - sut.recoverAccount({ - organizationUserId: orgUserId, - organizationId: orgId, - resetMasterPassword: true, - resetTwoFactor: false, - newMasterPassword, - email, - }), - ).rejects.toThrow(); - }); - - it("should throw if org key is null", async () => { - keyService.orgKeys$.mockReturnValue(of(null)); - - await expect( - sut.recoverAccount({ - organizationUserId: orgUserId, - organizationId: orgId, - resetMasterPassword: true, - resetTwoFactor: false, - newMasterPassword, - email, - }), - ).rejects.toThrow(); - }); - }); - - describe("reset master password only [PM27086_UpdateAuthenticationApisForInputPassword flag ENABLED]", () => { + describe("reset master password only", () => { beforeEach(() => { - setupPasswordResetMocks(true); + setupPasswordResetMocks(); }); it("should call putOrganizationUserRecoverAccount with password data and resetTwoFactor: false", async () => { @@ -598,9 +448,9 @@ describe("OrganizationUserResetPasswordService", () => { }); }); - describe("reset both master password and 2FA [PM27086_UpdateAuthenticationApisForInputPassword flag ENABLED]", () => { + describe("reset both master password and 2FA", () => { beforeEach(() => { - setupPasswordResetMocks(true); + setupPasswordResetMocks(); }); it("should call putOrganizationUserRecoverAccount with both flags true and password data", async () => { @@ -692,7 +542,7 @@ describe("OrganizationUserResetPasswordService", () => { function createOrganization(id: string, name: string, resetPasswordEnrolled = true): Organization { const org = new Organization(); - org.id = id; + org.id = id as OrganizationId; org.name = name; org.identifier = name; org.isMember = true; diff --git a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts index 571d98475d19..801190fdced6 100644 --- a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts +++ b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts @@ -12,7 +12,6 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncryptedString, @@ -20,7 +19,6 @@ import { } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { MasterPasswordServiceAbstraction } 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 { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; @@ -63,7 +61,6 @@ export class OrganizationUserResetPasswordService implements UserKeyRotationKeyR private i18nService: I18nService, private accountService: AccountService, private masterPasswordService: MasterPasswordServiceAbstraction, - private configService: ConfigService, ) {} /** @@ -129,23 +126,12 @@ export class OrganizationUserResetPasswordService implements UserKeyRotationKeyR request.organizationId, ); - const newApisEnabled = await this.configService.getFeatureFlag( - FeatureFlag.PM27086_UpdateAuthenticationApisForInputPassword, - ); - - ({ newMasterPasswordHash, key } = newApisEnabled - ? await this.buildResetPasswordRequestV2( - request.newMasterPassword, - request.email, - kdfConfig, - existingUserKey, - ) - : await this.buildMasterPasswordRequest( - request.newMasterPassword, - request.email, - kdfConfig, - existingUserKey, - )); + ({ newMasterPasswordHash, key } = await this.buildResetPasswordRequest( + request.newMasterPassword, + request.email, + kdfConfig, + existingUserKey, + )); } await this.organizationUserApiService.putOrganizationUserRecoverAccount( @@ -280,11 +266,7 @@ export class OrganizationUserResetPasswordService implements UserKeyRotationKeyR )) as UserKey; } - /** - * Builds a reset password request using the new authentication APIs - * (feature flag PM27086_UpdateAuthenticationApisForInputPassword). - */ - private async buildResetPasswordRequestV2( + private async buildResetPasswordRequest( newMasterPassword: string, email: string, kdfConfig: KdfConfig, @@ -316,28 +298,4 @@ export class OrganizationUserResetPasswordService implements UserKeyRotationKeyR key: unlockData.masterKeyWrappedUserKey, }; } - - /** Builds a reset password request using the legacy crypto path. */ - private async buildMasterPasswordRequest( - newMasterPassword: string, - email: string, - kdfConfig: KdfConfig, - existingUserKey: UserKey, - ): Promise> { - const newMasterKey = await this.keyService.makeMasterKey( - newMasterPassword, - email.trim().toLowerCase(), - kdfConfig, - ); - const newMasterKeyHash = await this.keyService.hashMasterKey(newMasterPassword, newMasterKey); - const newUserKey = await this.keyService.encryptUserKeyWithMasterKey( - newMasterKey, - existingUserKey, - ); - - return { - newMasterPasswordHash: newMasterKeyHash, - key: newUserKey[1].encryptedString, - }; - } }