diff --git a/apps/web/src/app/auth/core/services/registration/__snapshots__/web-registration-finish.service.spec.ts.snap b/apps/web/src/app/auth/core/services/registration/__snapshots__/web-registration-finish.service.spec.ts.snap index fafd44efe68c..d2c418a3d9de 100644 --- a/apps/web/src/app/auth/core/services/registration/__snapshots__/web-registration-finish.service.spec.ts.snap +++ b/apps/web/src/app/auth/core/services/registration/__snapshots__/web-registration-finish.service.spec.ts.snap @@ -1,132 +1,7 @@ // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing -exports[`WebRegistrationFinishService finishRegistration() when feature flag is OFF (old API) it registers the user with org invite when given an org invite 1`] = ` -RegisterFinishRequest { - "acceptEmergencyAccessId": undefined, - "acceptEmergencyAccessInviteToken": undefined, - "email": "test@email.com", - "emailVerificationToken": undefined, - "kdf": 0, - "kdfIterations": 600000, - "kdfMemory": undefined, - "kdfParallelism": undefined, - "masterPasswordHash": "newServerMasterKeyHash", - "masterPasswordHint": "newPasswordHint", - "orgInviteToken": "orgInviteToken", - "orgSponsoredFreeFamilyPlanToken": undefined, - "organizationUserId": "organizationUserId", - "providerInviteToken": undefined, - "providerUserId": undefined, - "userAsymmetricKeys": KeysRequest { - "encryptedPrivateKey": "privateKey", - "publicKey": "publicKey", - }, - "userSymmetricKey": "userKeyEncrypted", -} -`; - -exports[`WebRegistrationFinishService finishRegistration() when feature flag is OFF (old API) registers the user when given a provider invite token 1`] = ` -RegisterFinishRequest { - "acceptEmergencyAccessId": undefined, - "acceptEmergencyAccessInviteToken": undefined, - "email": "test@email.com", - "emailVerificationToken": undefined, - "kdf": 0, - "kdfIterations": 600000, - "kdfMemory": undefined, - "kdfParallelism": undefined, - "masterPasswordHash": "newServerMasterKeyHash", - "masterPasswordHint": "newPasswordHint", - "orgInviteToken": undefined, - "orgSponsoredFreeFamilyPlanToken": undefined, - "organizationUserId": undefined, - "providerInviteToken": "providerInviteToken", - "providerUserId": "providerUserId", - "userAsymmetricKeys": KeysRequest { - "encryptedPrivateKey": "privateKey", - "publicKey": "publicKey", - }, - "userSymmetricKey": "userKeyEncrypted", -} -`; - -exports[`WebRegistrationFinishService finishRegistration() when feature flag is OFF (old API) registers the user when given an emergency access invite token 1`] = ` -RegisterFinishRequest { - "acceptEmergencyAccessId": "emergencyAccessId", - "acceptEmergencyAccessInviteToken": "acceptEmergencyAccessInviteToken", - "email": "test@email.com", - "emailVerificationToken": undefined, - "kdf": 0, - "kdfIterations": 600000, - "kdfMemory": undefined, - "kdfParallelism": undefined, - "masterPasswordHash": "newServerMasterKeyHash", - "masterPasswordHint": "newPasswordHint", - "orgInviteToken": undefined, - "orgSponsoredFreeFamilyPlanToken": undefined, - "organizationUserId": undefined, - "providerInviteToken": undefined, - "providerUserId": undefined, - "userAsymmetricKeys": KeysRequest { - "encryptedPrivateKey": "privateKey", - "publicKey": "publicKey", - }, - "userSymmetricKey": "userKeyEncrypted", -} -`; - -exports[`WebRegistrationFinishService finishRegistration() when feature flag is OFF (old API) registers the user when given an org sponsored free family plan token 1`] = ` -RegisterFinishRequest { - "acceptEmergencyAccessId": undefined, - "acceptEmergencyAccessInviteToken": undefined, - "email": "test@email.com", - "emailVerificationToken": undefined, - "kdf": 0, - "kdfIterations": 600000, - "kdfMemory": undefined, - "kdfParallelism": undefined, - "masterPasswordHash": "newServerMasterKeyHash", - "masterPasswordHint": "newPasswordHint", - "orgInviteToken": undefined, - "orgSponsoredFreeFamilyPlanToken": "orgSponsoredFreeFamilyPlanToken", - "organizationUserId": undefined, - "providerInviteToken": undefined, - "providerUserId": undefined, - "userAsymmetricKeys": KeysRequest { - "encryptedPrivateKey": "privateKey", - "publicKey": "publicKey", - }, - "userSymmetricKey": "userKeyEncrypted", -} -`; - -exports[`WebRegistrationFinishService finishRegistration() when feature flag is OFF (old API) registers the user with KDF fields when given valid email verification input 1`] = ` +exports[`WebRegistrationFinishService finishRegistration() derives the master key and registers the user 1`] = ` RegisterFinishRequest { - "acceptEmergencyAccessId": undefined, - "acceptEmergencyAccessInviteToken": undefined, - "email": "test@email.com", - "emailVerificationToken": "emailVerificationToken", - "kdf": 0, - "kdfIterations": 600000, - "kdfMemory": undefined, - "kdfParallelism": undefined, - "masterPasswordHash": "newServerMasterKeyHash", - "masterPasswordHint": "newPasswordHint", - "orgInviteToken": undefined, - "orgSponsoredFreeFamilyPlanToken": undefined, - "organizationUserId": undefined, - "providerInviteToken": undefined, - "providerUserId": undefined, - "userAsymmetricKeys": KeysRequest { - "encryptedPrivateKey": "privateKey", - "publicKey": "publicKey", - }, - "userSymmetricKey": "userKeyEncrypted", -} -`; - -exports[`WebRegistrationFinishService finishRegistration() when feature flag is ON (new API) derives the master key and registers the user with new data types 1`] = ` -RegisterFinishRequestWithAuthUnlockDataTypes { "acceptEmergencyAccessId": undefined, "acceptEmergencyAccessInviteToken": undefined, "email": "test@email.com", @@ -160,8 +35,8 @@ RegisterFinishRequestWithAuthUnlockDataTypes { } `; -exports[`WebRegistrationFinishService finishRegistration() when feature flag is ON (new API) it registers the user with org invite when given an org invite 1`] = ` -RegisterFinishRequestWithAuthUnlockDataTypes { +exports[`WebRegistrationFinishService finishRegistration() it registers the user with org invite when given an org invite 1`] = ` +RegisterFinishRequest { "acceptEmergencyAccessId": undefined, "acceptEmergencyAccessInviteToken": undefined, "email": "test@email.com", @@ -195,8 +70,8 @@ RegisterFinishRequestWithAuthUnlockDataTypes { } `; -exports[`WebRegistrationFinishService finishRegistration() when feature flag is ON (new API) registers the user when given a provider invite token 1`] = ` -RegisterFinishRequestWithAuthUnlockDataTypes { +exports[`WebRegistrationFinishService finishRegistration() registers the user when given a provider invite token 1`] = ` +RegisterFinishRequest { "acceptEmergencyAccessId": undefined, "acceptEmergencyAccessInviteToken": undefined, "email": "test@email.com", @@ -230,8 +105,8 @@ RegisterFinishRequestWithAuthUnlockDataTypes { } `; -exports[`WebRegistrationFinishService finishRegistration() when feature flag is ON (new API) registers the user when given an emergency access invite token 1`] = ` -RegisterFinishRequestWithAuthUnlockDataTypes { +exports[`WebRegistrationFinishService finishRegistration() registers the user when given an emergency access invite token 1`] = ` +RegisterFinishRequest { "acceptEmergencyAccessId": "emergencyAccessId", "acceptEmergencyAccessInviteToken": "acceptEmergencyAccessInviteToken", "email": "test@email.com", @@ -265,8 +140,8 @@ RegisterFinishRequestWithAuthUnlockDataTypes { } `; -exports[`WebRegistrationFinishService finishRegistration() when feature flag is ON (new API) registers the user when given an org sponsored free family plan token 1`] = ` -RegisterFinishRequestWithAuthUnlockDataTypes { +exports[`WebRegistrationFinishService finishRegistration() registers the user when given an org sponsored free family plan token 1`] = ` +RegisterFinishRequest { "acceptEmergencyAccessId": undefined, "acceptEmergencyAccessInviteToken": undefined, "email": "test@email.com", diff --git a/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts b/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts index 08061a9e4fff..7fef7fcfdea8 100644 --- a/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts +++ b/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts @@ -9,7 +9,6 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-api.service"; -import { RegisterFinishRequestWithAuthUnlockDataTypes } from "@bitwarden/common/auth/models/request/registration/register-finish-request-with-auth-unlock-data.types"; import { RegisterFinishRequest } from "@bitwarden/common/auth/models/request/registration/register-finish.request"; import { OrganizationInvite } from "@bitwarden/common/auth/services/organization-invite/organization-invite"; import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.service"; @@ -22,7 +21,6 @@ import { MasterPasswordAuthenticationHash, MasterKeyWrappedUserKey, } from "@bitwarden/common/key-management/master-password/types/master-password.types"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { MasterKey, UserKey } from "@bitwarden/common/types/key"; @@ -40,7 +38,6 @@ describe("WebRegistrationFinishService", () => { let logService: MockProxy; let policyService: MockProxy; let masterPasswordService: MockProxy; - let configService: MockProxy; beforeEach(() => { keyService = mock(); @@ -50,13 +47,11 @@ describe("WebRegistrationFinishService", () => { logService = mock(); policyService = mock(); masterPasswordService = mock(); - configService = mock(); service = new WebRegistrationFinishService( keyService, accountApiService, masterPasswordService, - configService, organizationInviteService, policyApiService, logService, @@ -196,17 +191,17 @@ describe("WebRegistrationFinishService", () => { email = "test@email.com"; emailVerificationToken = "emailVerificationToken"; masterKey = new SymmetricCryptoKey(new Uint8Array(64)) as MasterKey; + salt = "salt" as MasterPasswordSalt; + passwordInputResult = { - newMasterKey: masterKey, - newServerMasterKeyHash: "newServerMasterKeyHash", + newPassword: "newPassword", kdfConfig: DEFAULT_KDF_CONFIG, newPasswordHint: "newPasswordHint", - newPassword: "newPassword", + salt: salt, }; userKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey; userKeyEncString = new EncString("userKeyEncrypted"); - userKeyPair = ["publicKey", new EncString("privateKey")]; orgInvite = new OrganizationInvite(); @@ -219,12 +214,12 @@ describe("WebRegistrationFinishService", () => { providerInviteToken = "providerInviteToken"; providerUserId = "providerUserId"; + keyService.makeMasterKey.mockResolvedValue(masterKey); keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]); keyService.makeKeyPair.mockResolvedValue(userKeyPair); accountApiService.registerFinish.mockResolvedValue(); organizationInviteService.getOrganizationInvite.mockResolvedValue(null); - salt = "salt" as MasterPasswordSalt; masterPasswordAuthentication = { salt, kdf: DEFAULT_KDF_CONFIG, @@ -235,6 +230,10 @@ describe("WebRegistrationFinishService", () => { DEFAULT_KDF_CONFIG, "masterKeyWrappedUserKey" as MasterKeyWrappedUserKey, ); + masterPasswordService.makeMasterPasswordAuthenticationData.mockResolvedValue( + masterPasswordAuthentication, + ); + masterPasswordService.makeMasterPasswordUnlockData.mockResolvedValue(masterPasswordUnlock); }); it("throws an error if the user key cannot be created", async () => { @@ -245,272 +244,129 @@ describe("WebRegistrationFinishService", () => { ); }); - describe("when feature flag is OFF (old API)", () => { - it("registers the user with KDF fields when given valid email verification input", async () => { - await service.finishRegistration(email, passwordInputResult, emailVerificationToken); - - expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey); - expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey); - - const registerCall = accountApiService.registerFinish.mock - .calls[0][0] as RegisterFinishRequest; - expect(registerCall).toBeInstanceOf(RegisterFinishRequest); - - // Old API sends flat KDF and master password hash fields - expect(registerCall.kdf).toBeDefined(); - expect(registerCall.kdfIterations).toBeDefined(); - expect(registerCall.masterPasswordHash).toBeDefined(); - expect(registerCall.userSymmetricKey).toBeDefined(); - - // Unique to this flow: emailVerificationToken is populated - expect(registerCall.emailVerificationToken).toEqual(emailVerificationToken); - - expect(registerCall).toMatchSnapshot(); - }); - - it("it registers the user with org invite when given an org invite", async () => { - organizationInviteService.getOrganizationInvite.mockResolvedValue(orgInvite); - - await service.finishRegistration(email, passwordInputResult); - - const registerCall = accountApiService.registerFinish.mock - .calls[0][0] as RegisterFinishRequest; - expect(registerCall).toBeInstanceOf(RegisterFinishRequest); - - // Unique to this flow: org invite fields are populated - expect(registerCall.orgInviteToken).toEqual(orgInvite.token); - expect(registerCall.organizationUserId).toEqual(orgInvite.organizationUserId); - - expect(registerCall).toMatchSnapshot(); - }); - - it("registers the user when given an org sponsored free family plan token", async () => { - await service.finishRegistration( - email, - passwordInputResult, - undefined, - orgSponsoredFreeFamilyPlanToken, - ); - - const registerCall = accountApiService.registerFinish.mock - .calls[0][0] as RegisterFinishRequest; - expect(registerCall).toBeInstanceOf(RegisterFinishRequest); - - // Unique to this flow: org sponsored free family plan token is populated - expect(registerCall.orgSponsoredFreeFamilyPlanToken).toEqual( - orgSponsoredFreeFamilyPlanToken, - ); - - expect(registerCall).toMatchSnapshot(); - }); - - it("registers the user when given an emergency access invite token", async () => { - await service.finishRegistration( - email, - passwordInputResult, - undefined, - undefined, - acceptEmergencyAccessInviteToken, - emergencyAccessId, - ); - - expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey); - expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey); - - const registerCall = accountApiService.registerFinish.mock - .calls[0][0] as RegisterFinishRequest; - expect(registerCall).toBeInstanceOf(RegisterFinishRequest); - - // Unique to this flow: emergency access fields are populated - expect(registerCall.acceptEmergencyAccessInviteToken).toEqual( - acceptEmergencyAccessInviteToken, - ); - expect(registerCall.acceptEmergencyAccessId).toEqual(emergencyAccessId); - - expect(registerCall).toMatchSnapshot(); - }); - - it("registers the user when given a provider invite token", async () => { - await service.finishRegistration( - email, - passwordInputResult, - undefined, - undefined, - undefined, - undefined, - providerInviteToken, - providerUserId, - ); - - expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey); - expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey); - - const registerCall = accountApiService.registerFinish.mock - .calls[0][0] as RegisterFinishRequest; - expect(registerCall).toBeInstanceOf(RegisterFinishRequest); - - // Unique to this flow: provider invite fields are populated - expect(registerCall.providerInviteToken).toEqual(providerInviteToken); - expect(registerCall.providerUserId).toEqual(providerUserId); - - expect(registerCall).toMatchSnapshot(); - }); + it("derives the master key and registers the user", async () => { + await service.finishRegistration(email, passwordInputResult, emailVerificationToken); + + // Verify master key is derived internally + expect(keyService.makeMasterKey).toHaveBeenCalledWith( + passwordInputResult.newPassword, + passwordInputResult.salt, + passwordInputResult.kdfConfig, + ); + expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey); + expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey); + + const registerCall = accountApiService.registerFinish.mock + .calls[0][0] as RegisterFinishRequest; + expect(registerCall).toBeInstanceOf(RegisterFinishRequest); + + expect(registerCall.masterPasswordAuthentication).toBeDefined(); + expect(registerCall.masterPasswordUnlock).toBeDefined(); + + // Unique to this flow: emailVerificationToken is populated + expect(registerCall.emailVerificationToken).toEqual(emailVerificationToken); + + expect(registerCall).toMatchSnapshot(); + }); + + it("it registers the user with org invite when given an org invite", async () => { + organizationInviteService.getOrganizationInvite.mockResolvedValue(orgInvite); + + await service.finishRegistration(email, passwordInputResult); + + expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey); + expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey); + + const registerCall = accountApiService.registerFinish.mock + .calls[0][0] as RegisterFinishRequest; + expect(registerCall).toBeInstanceOf(RegisterFinishRequest); + expect(registerCall.masterPasswordAuthentication).toBeDefined(); + expect(registerCall.masterPasswordUnlock).toBeDefined(); + + // Unique to this flow: org invite fields are populated + expect(registerCall.orgInviteToken).toEqual(orgInvite.token); + expect(registerCall.organizationUserId).toEqual(orgInvite.organizationUserId); + + expect(registerCall).toMatchSnapshot(); }); - describe("when feature flag is ON (new API)", () => { - beforeEach(() => { - // When the Auth flag is ON, InputPasswordComponent emits newApisWithInputPasswordFlagEnabled: true - // and does NOT emit newMasterKey, newServerMasterKeyHash. - passwordInputResult = { - newPassword: "newPassword", - kdfConfig: DEFAULT_KDF_CONFIG, - newPasswordHint: "newPasswordHint", - newApisWithInputPasswordFlagEnabled: true, - salt: salt, - }; - - // The service derives the master key internally when the Auth flag is ON - keyService.makeMasterKey.mockResolvedValue(masterKey); - - masterPasswordService.makeMasterPasswordAuthenticationData.mockResolvedValue( - masterPasswordAuthentication, - ); - masterPasswordService.makeMasterPasswordUnlockData.mockResolvedValue(masterPasswordUnlock); - }); - - it("derives the master key and registers the user with new data types", async () => { - await service.finishRegistration(email, passwordInputResult, emailVerificationToken); - - // Verify master key is derived internally - expect(keyService.makeMasterKey).toHaveBeenCalledWith( - passwordInputResult.newPassword, - passwordInputResult.salt, - passwordInputResult.kdfConfig, - ); - expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey); - expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey); - - const registerCall = accountApiService.registerFinish.mock - .calls[0][0] as RegisterFinishRequestWithAuthUnlockDataTypes; - expect(registerCall).toBeInstanceOf(RegisterFinishRequestWithAuthUnlockDataTypes); - - // New API sends structured authentication and unlock data - expect(registerCall.masterPasswordAuthentication).toBeDefined(); - expect(registerCall.masterPasswordUnlock).toBeDefined(); - - // Old API flat fields must NOT be present - expect((registerCall as any).masterPasswordHash).toBeUndefined(); - expect((registerCall as any).userSymmetricKey).toBeUndefined(); - expect((registerCall as any).kdf).toBeUndefined(); - expect((registerCall as any).kdfIterations).toBeUndefined(); - - // Unique to this flow: emailVerificationToken is populated - expect(registerCall.emailVerificationToken).toEqual(emailVerificationToken); - - expect(registerCall).toMatchSnapshot(); - }); - - it("it registers the user with org invite when given an org invite", async () => { - organizationInviteService.getOrganizationInvite.mockResolvedValue(orgInvite); - - await service.finishRegistration(email, passwordInputResult); - - expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey); - expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey); - - const registerCall = accountApiService.registerFinish.mock - .calls[0][0] as RegisterFinishRequestWithAuthUnlockDataTypes; - expect(registerCall).toBeInstanceOf(RegisterFinishRequestWithAuthUnlockDataTypes); - expect(registerCall.masterPasswordAuthentication).toBeDefined(); - expect(registerCall.masterPasswordUnlock).toBeDefined(); - - // Unique to this flow: org invite fields are populated - expect(registerCall.orgInviteToken).toEqual(orgInvite.token); - expect(registerCall.organizationUserId).toEqual(orgInvite.organizationUserId); - - expect(registerCall).toMatchSnapshot(); - }); - - it("registers the user when given an org sponsored free family plan token", async () => { - await service.finishRegistration( - email, - passwordInputResult, - undefined, - orgSponsoredFreeFamilyPlanToken, - ); - - expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey); - expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey); - - const registerCall = accountApiService.registerFinish.mock - .calls[0][0] as RegisterFinishRequestWithAuthUnlockDataTypes; - expect(registerCall).toBeInstanceOf(RegisterFinishRequestWithAuthUnlockDataTypes); - expect(registerCall.masterPasswordAuthentication).toBeDefined(); - expect(registerCall.masterPasswordUnlock).toBeDefined(); - - // Unique to this flow: org sponsored free family plan token is populated - expect(registerCall.orgSponsoredFreeFamilyPlanToken).toEqual( - orgSponsoredFreeFamilyPlanToken, - ); - - expect(registerCall).toMatchSnapshot(); - }); - - it("registers the user when given an emergency access invite token", async () => { - await service.finishRegistration( - email, - passwordInputResult, - undefined, - undefined, - acceptEmergencyAccessInviteToken, - emergencyAccessId, - ); - - expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey); - expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey); - - const registerCall = accountApiService.registerFinish.mock - .calls[0][0] as RegisterFinishRequestWithAuthUnlockDataTypes; - expect(registerCall).toBeInstanceOf(RegisterFinishRequestWithAuthUnlockDataTypes); - expect(registerCall.masterPasswordAuthentication).toBeDefined(); - expect(registerCall.masterPasswordUnlock).toBeDefined(); - - // Unique to this flow: emergency access fields are populated - expect(registerCall.acceptEmergencyAccessInviteToken).toEqual( - acceptEmergencyAccessInviteToken, - ); - expect(registerCall.acceptEmergencyAccessId).toEqual(emergencyAccessId); - - expect(registerCall).toMatchSnapshot(); - }); - - it("registers the user when given a provider invite token", async () => { - await service.finishRegistration( - email, - passwordInputResult, - undefined, - undefined, - undefined, - undefined, - providerInviteToken, - providerUserId, - ); - - expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey); - expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey); - - const registerCall = accountApiService.registerFinish.mock - .calls[0][0] as RegisterFinishRequestWithAuthUnlockDataTypes; - expect(registerCall).toBeInstanceOf(RegisterFinishRequestWithAuthUnlockDataTypes); - expect(registerCall.masterPasswordAuthentication).toBeDefined(); - expect(registerCall.masterPasswordUnlock).toBeDefined(); - - // Unique to this flow: provider invite fields are populated - expect(registerCall.providerInviteToken).toEqual(providerInviteToken); - expect(registerCall.providerUserId).toEqual(providerUserId); - - expect(registerCall).toMatchSnapshot(); - }); + it("registers the user when given an org sponsored free family plan token", async () => { + await service.finishRegistration( + email, + passwordInputResult, + undefined, + orgSponsoredFreeFamilyPlanToken, + ); + + expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey); + expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey); + + const registerCall = accountApiService.registerFinish.mock + .calls[0][0] as RegisterFinishRequest; + expect(registerCall).toBeInstanceOf(RegisterFinishRequest); + expect(registerCall.masterPasswordAuthentication).toBeDefined(); + expect(registerCall.masterPasswordUnlock).toBeDefined(); + + // Unique to this flow: org sponsored free family plan token is populated + expect(registerCall.orgSponsoredFreeFamilyPlanToken).toEqual(orgSponsoredFreeFamilyPlanToken); + + expect(registerCall).toMatchSnapshot(); + }); + + it("registers the user when given an emergency access invite token", async () => { + await service.finishRegistration( + email, + passwordInputResult, + undefined, + undefined, + acceptEmergencyAccessInviteToken, + emergencyAccessId, + ); + + expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey); + expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey); + + const registerCall = accountApiService.registerFinish.mock + .calls[0][0] as RegisterFinishRequest; + expect(registerCall).toBeInstanceOf(RegisterFinishRequest); + expect(registerCall.masterPasswordAuthentication).toBeDefined(); + expect(registerCall.masterPasswordUnlock).toBeDefined(); + + // Unique to this flow: emergency access fields are populated + expect(registerCall.acceptEmergencyAccessInviteToken).toEqual( + acceptEmergencyAccessInviteToken, + ); + expect(registerCall.acceptEmergencyAccessId).toEqual(emergencyAccessId); + + expect(registerCall).toMatchSnapshot(); + }); + + it("registers the user when given a provider invite token", async () => { + await service.finishRegistration( + email, + passwordInputResult, + undefined, + undefined, + undefined, + undefined, + providerInviteToken, + providerUserId, + ); + + expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey); + expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey); + + const registerCall = accountApiService.registerFinish.mock + .calls[0][0] as RegisterFinishRequest; + expect(registerCall).toBeInstanceOf(RegisterFinishRequest); + expect(registerCall.masterPasswordAuthentication).toBeDefined(); + expect(registerCall.masterPasswordUnlock).toBeDefined(); + + // Unique to this flow: provider invite fields are populated + expect(registerCall.providerInviteToken).toEqual(providerInviteToken); + expect(registerCall.providerUserId).toEqual(providerUserId); + + expect(registerCall).toMatchSnapshot(); }); }); }); diff --git a/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.ts b/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.ts index 10fdde602064..354bf6145487 100644 --- a/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.ts +++ b/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.ts @@ -12,15 +12,10 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-api.service"; -import { RegisterFinishRequestWithAuthUnlockDataTypes } from "@bitwarden/common/auth/models/request/registration/register-finish-request-with-auth-unlock-data.types"; import { RegisterFinishRequest } from "@bitwarden/common/auth/models/request/registration/register-finish.request"; import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.service"; -import { - EncryptedString, - EncString, -} from "@bitwarden/common/key-management/crypto/models/enc-string"; +import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { UserKey } from "@bitwarden/common/types/key"; import { KeyService } from "@bitwarden/key-management"; @@ -33,13 +28,12 @@ export class WebRegistrationFinishService protected keyService: KeyService, protected accountApiService: AccountApiService, protected masterPasswordService: MasterPasswordServiceAbstraction, - protected configService: ConfigService, private organizationInviteService: OrganizationInviteService, private policyApiService: PolicyApiServiceAbstraction, private logService: LogService, private policyService: PolicyService, ) { - super(keyService, accountApiService, masterPasswordService, configService); + super(keyService, accountApiService, masterPasswordService); } override async getOrgNameFromOrgInvite(): Promise { @@ -87,7 +81,6 @@ export class WebRegistrationFinishService newUserKey: UserKey, email: string, passwordInputResult: PasswordInputResult, - encryptedUserKey: EncryptedString, userAsymmetricKeys: [string, EncString], emailVerificationToken?: string, orgSponsoredFreeFamilyPlanToken?: string, @@ -95,12 +88,11 @@ export class WebRegistrationFinishService emergencyAccessId?: string, providerInviteToken?: string, providerUserId?: string, - ): Promise { + ): Promise { const registerRequest = await super.buildRegisterRequest( newUserKey, email, passwordInputResult, - encryptedUserKey, userAsymmetricKeys, emailVerificationToken, ); diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index c8a65f0377af..424f35fdc2f5 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -294,7 +294,6 @@ const safeProviders: SafeProvider[] = [ KeyServiceAbstraction, AccountApiServiceAbstraction, MasterPasswordServiceAbstraction, - ConfigService, OrganizationInviteService, PolicyApiServiceAbstraction, LogService, diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 03c1eeaa98f3..7a828a9fe505 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1694,12 +1694,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: RegistrationFinishServiceAbstraction, useClass: DefaultRegistrationFinishService, - deps: [ - KeyService, - AccountApiServiceAbstraction, - MasterPasswordServiceAbstraction, - ConfigService, - ], + deps: [KeyService, AccountApiServiceAbstraction, MasterPasswordServiceAbstraction], }), safeProvider({ provide: TwoFactorAuthComponentService, diff --git a/libs/auth/src/angular/registration/registration-finish/__snapshots__/default-registration-finish.service.spec.ts.snap b/libs/auth/src/angular/registration/registration-finish/__snapshots__/default-registration-finish.service.spec.ts.snap index 6a82899dcc18..237f57ca8ad1 100644 --- a/libs/auth/src/angular/registration/registration-finish/__snapshots__/default-registration-finish.service.spec.ts.snap +++ b/libs/auth/src/angular/registration/registration-finish/__snapshots__/default-registration-finish.service.spec.ts.snap @@ -1,32 +1,7 @@ // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing -exports[`DefaultRegistrationFinishService finishRegistration() when feature flag is OFF (old API) registers the user with KDF fields when given valid email verification input 1`] = ` +exports[`DefaultRegistrationFinishService finishRegistration() derives the master key and registers the user 1`] = ` RegisterFinishRequest { - "acceptEmergencyAccessId": undefined, - "acceptEmergencyAccessInviteToken": undefined, - "email": "test@email.com", - "emailVerificationToken": "emailVerificationToken", - "kdf": 0, - "kdfIterations": 600000, - "kdfMemory": undefined, - "kdfParallelism": undefined, - "masterPasswordHash": "newServerMasterKeyHash", - "masterPasswordHint": "newPasswordHint", - "orgInviteToken": undefined, - "orgSponsoredFreeFamilyPlanToken": undefined, - "organizationUserId": undefined, - "providerInviteToken": undefined, - "providerUserId": undefined, - "userAsymmetricKeys": KeysRequest { - "encryptedPrivateKey": "privateKey", - "publicKey": "publicKey", - }, - "userSymmetricKey": "userKeyEncrypted", -} -`; - -exports[`DefaultRegistrationFinishService finishRegistration() when feature flag is ON (new API) derives the master key and registers the user with new data types 1`] = ` -RegisterFinishRequestWithAuthUnlockDataTypes { "acceptEmergencyAccessId": undefined, "acceptEmergencyAccessInviteToken": undefined, "email": "test@email.com", diff --git a/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.spec.ts b/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.spec.ts index 3e190f71afa5..4f40d4fa4b43 100644 --- a/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.spec.ts +++ b/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.spec.ts @@ -1,7 +1,6 @@ import { MockProxy, mock } from "jest-mock-extended"; import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-api.service"; -import { RegisterFinishRequestWithAuthUnlockDataTypes } from "@bitwarden/common/auth/models/request/registration/register-finish-request-with-auth-unlock-data.types"; import { RegisterFinishRequest } from "@bitwarden/common/auth/models/request/registration/register-finish.request"; import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; @@ -12,10 +11,9 @@ import { MasterPasswordSalt, MasterKeyWrappedUserKey, } from "@bitwarden/common/key-management/master-password/types/master-password.types"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { MasterKey, UserKey } from "@bitwarden/common/types/key"; -import { DEFAULT_KDF_CONFIG, KeyService, KdfType } from "@bitwarden/key-management"; +import { DEFAULT_KDF_CONFIG, KeyService } from "@bitwarden/key-management"; import { PasswordInputResult } from "../../input-password/password-input-result"; @@ -27,19 +25,16 @@ describe("DefaultRegistrationFinishService", () => { let keyService: MockProxy; let accountApiService: MockProxy; let masterPasswordService: MockProxy; - let configService: MockProxy; beforeEach(() => { keyService = mock(); accountApiService = mock(); masterPasswordService = mock(); - configService = mock(); service = new DefaultRegistrationFinishService( keyService, accountApiService, masterPasswordService, - configService, ); }); @@ -71,163 +66,119 @@ describe("DefaultRegistrationFinishService", () => { let userKey: UserKey; let userKeyEncString: EncString; let userKeyPair: [string, EncString]; + let salt: MasterPasswordSalt; + let masterPasswordAuthentication: MasterPasswordAuthenticationData; + let masterPasswordUnlock: MasterPasswordUnlockData; beforeEach(() => { email = "test@email.com"; emailVerificationToken = "emailVerificationToken"; masterKey = new SymmetricCryptoKey(new Uint8Array(64)) as MasterKey; + salt = "test@email.com" as MasterPasswordSalt; + passwordInputResult = { - newMasterKey: masterKey, - newServerMasterKeyHash: "newServerMasterKeyHash", + newPassword: "newPassword", kdfConfig: DEFAULT_KDF_CONFIG, newPasswordHint: "newPasswordHint", - newPassword: "newPassword", + salt: salt, }; userKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey; userKeyEncString = new EncString("userKeyEncrypted"); - userKeyPair = ["publicKey", new EncString("privateKey")]; + + keyService.makeMasterKey.mockResolvedValue(masterKey); + + masterPasswordAuthentication = { + salt, + kdf: DEFAULT_KDF_CONFIG, + masterPasswordAuthenticationHash: "authHash" as MasterPasswordAuthenticationHash, + }; + masterPasswordUnlock = new MasterPasswordUnlockData( + salt, + DEFAULT_KDF_CONFIG, + "wrappedUserKey" as MasterKeyWrappedUserKey, + ); + masterPasswordService.makeMasterPasswordAuthenticationData.mockResolvedValue( + masterPasswordAuthentication, + ); + masterPasswordService.makeMasterPasswordUnlockData.mockResolvedValue(masterPasswordUnlock); + }); + + ["newPassword", "salt"].forEach((key) => { + it(`should throw if ${key} is an empty string (falsy) on the PasswordInputResult object`, async () => { + // Arrange + const invalidPasswordInputResult: PasswordInputResult = { + ...passwordInputResult, + [key]: "", + }; + + // Act + const promise = service.finishRegistration(email, invalidPasswordInputResult); + + // Assert + await expect(promise).rejects.toThrow(`${key} is falsy. Could not finish registration.`); + }); + }); + + it("should throw if kdfConfig is undefined on the PasswordInputResult object", async () => { + // Arrange + const invalidPasswordInputResult: PasswordInputResult = { + ...passwordInputResult, + kdfConfig: undefined, + }; + + // Act + const promise = service.finishRegistration(email, invalidPasswordInputResult); + + // Assert + await expect(promise).rejects.toThrow( + "kdfConfig is null or undefined. Could not finish registration.", + ); }); it("throws an error if the user key cannot be created", async () => { - keyService.makeUserKey.mockResolvedValue([null, null]); + keyService.makeUserKey.mockResolvedValue([null, null] as any); await expect(service.finishRegistration(email, passwordInputResult)).rejects.toThrow( "User key could not be created", ); }); - describe("when feature flag is OFF (old API)", () => { - it("registers the user with KDF fields when given valid email verification input", async () => { - keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]); - keyService.makeKeyPair.mockResolvedValue(userKeyPair); - accountApiService.registerFinish.mockResolvedValue(); - - await service.finishRegistration(email, passwordInputResult, emailVerificationToken); - - expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey); - expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey); - expect(accountApiService.registerFinish).toHaveBeenCalledWith( - expect.objectContaining({ - email, - emailVerificationToken: emailVerificationToken, - masterPasswordHash: passwordInputResult.newServerMasterKeyHash, - masterPasswordHint: passwordInputResult.newPasswordHint, - userSymmetricKey: userKeyEncString.encryptedString, - userAsymmetricKeys: { - publicKey: userKeyPair[0], - encryptedPrivateKey: userKeyPair[1].encryptedString, - }, - kdf: KdfType.PBKDF2_SHA256, - kdfIterations: DEFAULT_KDF_CONFIG.iterations, - kdfMemory: undefined, - kdfParallelism: undefined, - }), - ); - - // Verify makeMasterKey is NOT called — the master key comes from passwordInputResult directly - expect(keyService.makeMasterKey).not.toHaveBeenCalled(); - - // Verify old API fields are present - const registerCall = accountApiService.registerFinish.mock.calls[0][0]; - expect(registerCall).toBeInstanceOf(RegisterFinishRequest); - expect((registerCall as RegisterFinishRequest).kdf).toBeDefined(); - expect((registerCall as RegisterFinishRequest).kdfIterations).toBeDefined(); - - // Verify new API fields are NOT present - expect((registerCall as any).masterPasswordAuthentication).toBeUndefined(); - expect((registerCall as any).masterPasswordUnlock).toBeUndefined(); - - expect(registerCall).toMatchSnapshot(); - }); - }); + it("derives the master key and registers the user", async () => { + keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]); + keyService.makeKeyPair.mockResolvedValue(userKeyPair); + accountApiService.registerFinish.mockResolvedValue(); - describe("when feature flag is ON (new API)", () => { - let salt: MasterPasswordSalt; - let masterPasswordAuthentication: MasterPasswordAuthenticationData; - let masterPasswordUnlock: MasterPasswordUnlockData; - - beforeEach(() => { - salt = "test@email.com" as MasterPasswordSalt; - - // When the Auth flag is ON, InputPasswordComponent emits newApisWithInputPasswordFlagEnabled: true - // and does NOT emit newMasterKey, newServerMasterKeyHash. - passwordInputResult = { - newPassword: "newPassword", - kdfConfig: DEFAULT_KDF_CONFIG, - newPasswordHint: "newPasswordHint", - newApisWithInputPasswordFlagEnabled: true, - salt: salt, - }; + await service.finishRegistration(email, passwordInputResult, emailVerificationToken); - // The service derives the master key internally when the Auth flag is ON - keyService.makeMasterKey.mockResolvedValue(masterKey); + expect(keyService.makeMasterKey).toHaveBeenCalledWith( + passwordInputResult.newPassword, + passwordInputResult.salt, + passwordInputResult.kdfConfig, + ); + expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey); + expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey); + expect(accountApiService.registerFinish).toHaveBeenCalledWith( + expect.objectContaining({ + email, + emailVerificationToken: emailVerificationToken, + masterPasswordHint: passwordInputResult.newPasswordHint, + userAsymmetricKeys: { + publicKey: userKeyPair[0], + encryptedPrivateKey: userKeyPair[1].encryptedString, + }, + masterPasswordAuthentication: masterPasswordAuthentication, + masterPasswordUnlock: masterPasswordUnlock, + }), + ); - masterPasswordAuthentication = { - salt, - kdf: DEFAULT_KDF_CONFIG, - masterPasswordAuthenticationHash: "authHash" as MasterPasswordAuthenticationHash, - }; - masterPasswordUnlock = new MasterPasswordUnlockData( - salt, - DEFAULT_KDF_CONFIG, - "wrappedUserKey" as MasterKeyWrappedUserKey, - ); - masterPasswordService.makeMasterPasswordAuthenticationData.mockResolvedValue( - masterPasswordAuthentication, - ); - masterPasswordService.makeMasterPasswordUnlockData.mockResolvedValue(masterPasswordUnlock); - }); + const registerCall = accountApiService.registerFinish.mock.calls[0][0]; + expect(registerCall).toBeInstanceOf(RegisterFinishRequest); + expect((registerCall as RegisterFinishRequest).masterPasswordAuthentication).toBeDefined(); + expect((registerCall as RegisterFinishRequest).masterPasswordUnlock).toBeDefined(); - it("derives the master key and registers the user with new data types", async () => { - keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]); - keyService.makeKeyPair.mockResolvedValue(userKeyPair); - accountApiService.registerFinish.mockResolvedValue(); - - await service.finishRegistration(email, passwordInputResult, emailVerificationToken); - - // Verify master key is derived internally (not from passwordInputResult) - expect(keyService.makeMasterKey).toHaveBeenCalledWith( - passwordInputResult.newPassword, - passwordInputResult.salt, - passwordInputResult.kdfConfig, - ); - expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey); - expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey); - expect(accountApiService.registerFinish).toHaveBeenCalledWith( - expect.objectContaining({ - email, - emailVerificationToken: emailVerificationToken, - masterPasswordHint: passwordInputResult.newPasswordHint, - userAsymmetricKeys: { - publicKey: userKeyPair[0], - encryptedPrivateKey: userKeyPair[1].encryptedString, - }, - masterPasswordAuthentication: masterPasswordAuthentication, - masterPasswordUnlock: masterPasswordUnlock, - }), - ); - - // Verify new API fields are present - const registerCall = accountApiService.registerFinish.mock.calls[0][0]; - expect(registerCall).toBeInstanceOf(RegisterFinishRequestWithAuthUnlockDataTypes); - expect( - (registerCall as RegisterFinishRequestWithAuthUnlockDataTypes) - .masterPasswordAuthentication, - ).toBeDefined(); - expect( - (registerCall as RegisterFinishRequestWithAuthUnlockDataTypes).masterPasswordUnlock, - ).toBeDefined(); - - // Verify old API fields are NOT present (including masterPasswordHash which is in masterPasswordAuthentication) - expect((registerCall as any).masterPasswordHash).toBeUndefined(); - expect((registerCall as any).userSymmetricKey).toBeUndefined(); - expect((registerCall as any).kdf).toBeUndefined(); - expect((registerCall as any).kdfIterations).toBeUndefined(); - - expect(registerCall).toMatchSnapshot(); - }); + expect(registerCall).toMatchSnapshot(); }); }); }); diff --git a/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.ts b/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.ts index 43c73e0b68df..94d53035125f 100644 --- a/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.ts +++ b/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.ts @@ -2,16 +2,11 @@ // @ts-strict-ignore import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-api.service"; -import { RegisterFinishRequestWithAuthUnlockDataTypes } from "@bitwarden/common/auth/models/request/registration/register-finish-request-with-auth-unlock-data.types"; import { RegisterFinishRequest } from "@bitwarden/common/auth/models/request/registration/register-finish.request"; import { assertNonNullish, assertTruthy } from "@bitwarden/common/auth/utils"; -import { - EncryptedString, - EncString, -} from "@bitwarden/common/key-management/crypto/models/enc-string"; +import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { KeysRequest } from "@bitwarden/common/models/request/keys.request"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { UserKey } from "@bitwarden/common/types/key"; import { KeyService } from "@bitwarden/key-management"; @@ -24,7 +19,6 @@ export class DefaultRegistrationFinishService implements RegistrationFinishServi protected keyService: KeyService, protected accountApiService: AccountApiService, protected masterPasswordService: MasterPasswordServiceAbstraction, - protected configService: ConfigService, ) {} getOrgNameFromOrgInvite(): Promise { @@ -45,56 +39,18 @@ export class DefaultRegistrationFinishService implements RegistrationFinishServi providerInviteToken?: string, providerUserId?: string, ): Promise { - /** - * "KM flag" = (KM team has ongoing work for this here: PM-24223) - * "Auth flag" = PM27086_UpdateAuthenticationApisForInputPassword (checked in InputPasswordComponent - * and passed through via PasswordInputResult.newApisWithInputPasswordFlagEnabled) - * - * Flag unwinding will depend on which flag gets unwound first: - * - If KM flag gets unwound first, remove all code after the KM V2 path, - * as the V2Encryption method is the end-goal. - * - If Auth flag gets unwound first (in PM-28143), keep the KM code & early return, - * but unwind the auth flagging logic and remove the unflagged `else` block logic following - * the "Scenario 2" code. - */ - - // Scenario 1: KM V2 flag ON (placeholder — to be added when KM's registration V2 PR lands) - // const accountEncryptionV2 = await this.configService.getFeatureFlag( - // FeatureFlag.EnableAccountEncryptionV2Registration, // flag name TBD by KM team - // ); - // if (accountEncryptionV2) { - // // SDK path — end goal. Replaces all key derivation below. - // return; - // } - - let newUserKey: UserKey; - let newEncUserKey: EncString; - - // Scenario 2: KM flag OFF, Auth flag ON - if (passwordInputResult.newApisWithInputPasswordFlagEnabled) { - /** - * If the Auth flag is enabled, it means the InputPasswordComponent will not emit a newMasterKey. - * So we must create it here for registration. - * - * This is a temporary state. The end-goal will be to use KM's V2Encryption method above. - */ - const ctx = "Could not finish registration."; - assertTruthy(passwordInputResult.newPassword, "newPassword", ctx); - assertNonNullish(passwordInputResult.kdfConfig, "kdfConfig", ctx); - assertTruthy(passwordInputResult.salt, "salt", ctx); - - const newMasterKey = await this.keyService.makeMasterKey( - passwordInputResult.newPassword, - passwordInputResult.salt, - passwordInputResult.kdfConfig, - ); + const ctx = "Could not finish registration."; + assertTruthy(passwordInputResult.newPassword, "newPassword", ctx); + assertNonNullish(passwordInputResult.kdfConfig, "kdfConfig", ctx); + assertTruthy(passwordInputResult.salt, "salt", ctx); + + const newMasterKey = await this.keyService.makeMasterKey( + passwordInputResult.newPassword, + passwordInputResult.salt, + passwordInputResult.kdfConfig, + ); - [newUserKey, newEncUserKey] = await this.keyService.makeUserKey(newMasterKey); - } else { - [newUserKey, newEncUserKey] = await this.keyService.makeUserKey( - passwordInputResult.newMasterKey, - ); - } + const [newUserKey, newEncUserKey] = await this.keyService.makeUserKey(newMasterKey); if (!newUserKey || !newEncUserKey) { throw new Error("User key could not be created"); @@ -105,7 +61,6 @@ export class DefaultRegistrationFinishService implements RegistrationFinishServi newUserKey, email, passwordInputResult, - newEncUserKey.encryptedString, userAsymmetricKeys, emailVerificationToken, orgSponsoredFreeFamilyPlanToken, @@ -122,7 +77,6 @@ export class DefaultRegistrationFinishService implements RegistrationFinishServi newUserKey: UserKey, email: string, passwordInputResult: PasswordInputResult, - encryptedUserKey: EncryptedString, userAsymmetricKeys: [string, EncString], emailVerificationToken?: string, orgSponsoredFreeFamilyPlanToken?: string, // web only @@ -130,62 +84,38 @@ export class DefaultRegistrationFinishService implements RegistrationFinishServi emergencyAccessId?: string, // web only providerInviteToken?: string, // web only providerUserId?: string, // web only - ): Promise { + ): Promise { const userAsymmetricKeysRequest = new KeysRequest( userAsymmetricKeys[0], userAsymmetricKeys[1].encryptedString, ); - const useNewApi = passwordInputResult.newApisWithInputPasswordFlagEnabled ?? false; - - if (useNewApi) { - // New API path - use new request with new data types - - const masterPasswordAuthentication = - await this.masterPasswordService.makeMasterPasswordAuthenticationData( - passwordInputResult.newPassword, - passwordInputResult.kdfConfig, - passwordInputResult.salt, - ); - - const masterPasswordUnlock = await this.masterPasswordService.makeMasterPasswordUnlockData( + const masterPasswordAuthentication = + await this.masterPasswordService.makeMasterPasswordAuthenticationData( passwordInputResult.newPassword, passwordInputResult.kdfConfig, passwordInputResult.salt, - newUserKey, - ); - - const registerFinishRequest = new RegisterFinishRequestWithAuthUnlockDataTypes( - email, - passwordInputResult.newPasswordHint, - userAsymmetricKeysRequest, - masterPasswordAuthentication, - masterPasswordUnlock, ); - if (emailVerificationToken) { - registerFinishRequest.emailVerificationToken = emailVerificationToken; - } - - return registerFinishRequest; - } else { - // Old API path - use original request with KDF fields - - const registerFinishRequest = new RegisterFinishRequest( - email, - passwordInputResult.newServerMasterKeyHash, - passwordInputResult.newPasswordHint, - encryptedUserKey, - userAsymmetricKeysRequest, - passwordInputResult.kdfConfig.kdfType, - passwordInputResult.kdfConfig.iterations, - ); + const masterPasswordUnlock = await this.masterPasswordService.makeMasterPasswordUnlockData( + passwordInputResult.newPassword, + passwordInputResult.kdfConfig, + passwordInputResult.salt, + newUserKey, + ); - if (emailVerificationToken) { - registerFinishRequest.emailVerificationToken = emailVerificationToken; - } + const registerFinishRequest = new RegisterFinishRequest( + email, + passwordInputResult.newPasswordHint, + userAsymmetricKeysRequest, + masterPasswordAuthentication, + masterPasswordUnlock, + ); - return registerFinishRequest; + if (emailVerificationToken) { + registerFinishRequest.emailVerificationToken = emailVerificationToken; } + + return registerFinishRequest; } } diff --git a/libs/common/src/auth/abstractions/account-api.service.ts b/libs/common/src/auth/abstractions/account-api.service.ts index 972c7d848dea..1c38f2676810 100644 --- a/libs/common/src/auth/abstractions/account-api.service.ts +++ b/libs/common/src/auth/abstractions/account-api.service.ts @@ -1,4 +1,3 @@ -import { RegisterFinishRequestWithAuthUnlockDataTypes } from "../models/request/registration/register-finish-request-with-auth-unlock-data.types"; import { RegisterFinishRequest } from "../models/request/registration/register-finish.request"; import { RegisterSendVerificationEmailRequest } from "../models/request/registration/register-send-verification-email.request"; import { RegisterVerificationEmailClickedRequest } from "../models/request/registration/register-verification-email-clicked.request"; @@ -48,19 +47,9 @@ export abstract class AccountApiService { * @param request - The user's email verification token, the email, newly created user key, and new * asymmetric user key pair along with the KDF information, hashed MP and salt (in the form of the * MasterPasswordAuthenticationData and MasterPasswordUnlockData) used during the process. - * - * OLD DESCRIPTION FOR ORIGINAL API CONTRACT: (the rest of this JSDoc below can be removed in PM-28143) - * - * Completes the registration process. - * - * @param request - The request object containing the user's email verification token, - * the email, hashed MP, newly created user key, and new asymmetric user key pair along - * with the KDF information used during the process. * @returns A promise that resolves when the registration process is successfully completed. */ - abstract registerFinish( - request: RegisterFinishRequest | RegisterFinishRequestWithAuthUnlockDataTypes, - ): Promise; + abstract registerFinish(request: RegisterFinishRequest): Promise; /** * Sets the [dbo].[User].[VerifyDevices] flag to true or false. diff --git a/libs/common/src/auth/models/request/registration/register-finish-request-with-auth-unlock-data.types.ts b/libs/common/src/auth/models/request/registration/register-finish-request-with-auth-unlock-data.types.ts deleted file mode 100644 index 7c3adfeb7447..000000000000 --- a/libs/common/src/auth/models/request/registration/register-finish-request-with-auth-unlock-data.types.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { - MasterPasswordAuthenticationData, - MasterPasswordUnlockData, -} from "../../../../key-management/master-password/types/master-password.types"; -import { KeysRequest } from "../../../../models/request/keys.request"; - -export class RegisterFinishRequestWithAuthUnlockDataTypes { - constructor( - public email: string, - public masterPasswordHint: string, - - public userAsymmetricKeys: KeysRequest, - - public masterPasswordAuthentication: MasterPasswordAuthenticationData, - public masterPasswordUnlock: MasterPasswordUnlockData, - - public emailVerificationToken?: string, - public orgSponsoredFreeFamilyPlanToken?: string, - public acceptEmergencyAccessInviteToken?: string, - public acceptEmergencyAccessId?: string, - public providerInviteToken?: string, - public providerUserId?: string, - - // Org Invite data (only applies on web) - public organizationUserId?: string, - public orgInviteToken?: string, - ) {} -} diff --git a/libs/common/src/auth/models/request/registration/register-finish.request.ts b/libs/common/src/auth/models/request/registration/register-finish.request.ts index b388e8aee497..5c2d04a28882 100644 --- a/libs/common/src/auth/models/request/registration/register-finish.request.ts +++ b/libs/common/src/auth/models/request/registration/register-finish.request.ts @@ -1,24 +1,18 @@ -// 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 { KdfType } from "@bitwarden/key-management"; - -import { EncryptedString } from "../../../../key-management/crypto/models/enc-string"; +import { + MasterPasswordAuthenticationData, + MasterPasswordUnlockData, +} from "../../../../key-management/master-password/types/master-password.types"; import { KeysRequest } from "../../../../models/request/keys.request"; export class RegisterFinishRequest { constructor( public email: string, - - public masterPasswordHash: string, public masterPasswordHint: string, - public userSymmetricKey: EncryptedString, public userAsymmetricKeys: KeysRequest, - public kdf: KdfType, - public kdfIterations: number, - public kdfMemory?: number, - public kdfParallelism?: number, + public masterPasswordAuthentication: MasterPasswordAuthenticationData, + public masterPasswordUnlock: MasterPasswordUnlockData, public emailVerificationToken?: string, public orgSponsoredFreeFamilyPlanToken?: string, diff --git a/libs/common/src/auth/services/account-api.service.ts b/libs/common/src/auth/services/account-api.service.ts index b922484b3fd1..923153a0a0b1 100644 --- a/libs/common/src/auth/services/account-api.service.ts +++ b/libs/common/src/auth/services/account-api.service.ts @@ -7,7 +7,6 @@ import { LogService } from "../../platform/abstractions/log.service"; import { AccountApiService } from "../abstractions/account-api.service"; import { InternalAccountService } from "../abstractions/account.service"; import { UserVerificationService } from "../abstractions/user-verification/user-verification.service.abstraction"; -import { RegisterFinishRequestWithAuthUnlockDataTypes } from "../models/request/registration/register-finish-request-with-auth-unlock-data.types"; import { RegisterFinishRequest } from "../models/request/registration/register-finish.request"; import { RegisterSendVerificationEmailRequest } from "../models/request/registration/register-send-verification-email.request"; import { RegisterVerificationEmailClickedRequest } from "../models/request/registration/register-verification-email-clicked.request"; @@ -85,9 +84,7 @@ export class AccountApiServiceImplementation implements AccountApiService { } } - async registerFinish( - request: RegisterFinishRequest | RegisterFinishRequestWithAuthUnlockDataTypes, - ): Promise { + async registerFinish(request: RegisterFinishRequest): Promise { const env = await firstValueFrom(this.environmentService.environment$); try {