From 93c9631a75d9844a18af023eff4aa4bceee745f4 Mon Sep 17 00:00:00 2001 From: Patrick Pimentel Date: Mon, 12 Jan 2026 15:11:58 -0500 Subject: [PATCH] test(register): [PM-27084] Account Register Uses New Data Types - Addressed feedback and added tests. --- .../Accounts/RegisterFinishRequestModel.cs | 6 ++++++ src/Identity/Controllers/AccountsController.cs | 14 +++++++------- .../Accounts/RegisterFinishRequestModelTests.cs | 16 +++++++++++----- .../Factories/IdentityApplicationFactory.cs | 1 - 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/Core/Auth/Models/Api/Request/Accounts/RegisterFinishRequestModel.cs b/src/Core/Auth/Models/Api/Request/Accounts/RegisterFinishRequestModel.cs index 418efe5997..cb66540a6b 100644 --- a/src/Core/Auth/Models/Api/Request/Accounts/RegisterFinishRequestModel.cs +++ b/src/Core/Auth/Models/Api/Request/Accounts/RegisterFinishRequestModel.cs @@ -125,6 +125,12 @@ public class RegisterFinishRequestModel : IValidatableObject $"{nameof(MasterPasswordAuthentication.MasterPasswordAuthenticationHash)} and root level {nameof(MasterPasswordHash)} provided and are not equal. Only provide one.", [nameof(MasterPasswordAuthentication.MasterPasswordAuthenticationHash), nameof(MasterPasswordHash)]); } + } // 1.5 if there is no master password hash that is unacceptable even though they are both optional in the model + else if (MasterPasswordAuthentication == null && MasterPasswordHash == null) + { + yield return new ValidationResult( + $"{nameof(MasterPasswordAuthentication.MasterPasswordAuthenticationHash)} and {nameof(MasterPasswordHash)} not found on request, one needs to be defined.", + [nameof(MasterPasswordAuthentication.MasterPasswordAuthenticationHash), nameof(MasterPasswordHash)]); } // 2. Validate kdf settings. diff --git a/src/Identity/Controllers/AccountsController.cs b/src/Identity/Controllers/AccountsController.cs index 2e42b690dd..e9807fb1fc 100644 --- a/src/Identity/Controllers/AccountsController.cs +++ b/src/Identity/Controllers/AccountsController.cs @@ -147,22 +147,22 @@ public class AccountsController : Controller IdentityResult? identityResult = null; // PM-28143 - Just use the MasterPasswordAuthenticationData.MasterPasswordAuthenticationHash - string masterPasswordHash = model.MasterPasswordAuthentication?.MasterPasswordAuthenticationHash - ?? model.MasterPasswordHash ?? throw new BadRequestException("MasterPasswordHash couldn't be found on either the MasterPasswordAuthenticationData or the MasterPasswordHash property passed in."); + string masterPasswordAuthenticationHash = model.MasterPasswordAuthentication?.MasterPasswordAuthenticationHash + ?? model.MasterPasswordHash!; switch (model.GetTokenType()) { case RegisterFinishTokenType.EmailVerification: identityResult = await _registerUserCommand.RegisterUserViaEmailVerificationToken( user, - masterPasswordHash, + masterPasswordAuthenticationHash, model.EmailVerificationToken!); return ProcessRegistrationResult(identityResult, user); case RegisterFinishTokenType.OrganizationInvite: identityResult = await _registerUserCommand.RegisterUserViaOrganizationInviteToken( user, - masterPasswordHash, + masterPasswordAuthenticationHash, model.OrgInviteToken!, model.OrganizationUserId); return ProcessRegistrationResult(identityResult, user); @@ -170,14 +170,14 @@ public class AccountsController : Controller case RegisterFinishTokenType.OrgSponsoredFreeFamilyPlan: identityResult = await _registerUserCommand.RegisterUserViaOrganizationSponsoredFreeFamilyPlanInviteToken( user, - masterPasswordHash, + masterPasswordAuthenticationHash, model.OrgSponsoredFreeFamilyPlanToken!); return ProcessRegistrationResult(identityResult, user); case RegisterFinishTokenType.EmergencyAccessInvite: identityResult = await _registerUserCommand.RegisterUserViaAcceptEmergencyAccessInviteToken( user, - masterPasswordHash, + masterPasswordAuthenticationHash, model.AcceptEmergencyAccessInviteToken!, (Guid)model.AcceptEmergencyAccessId!); return ProcessRegistrationResult(identityResult, user); @@ -185,7 +185,7 @@ public class AccountsController : Controller case RegisterFinishTokenType.ProviderInvite: identityResult = await _registerUserCommand.RegisterUserViaProviderInviteToken( user, - masterPasswordHash, + masterPasswordAuthenticationHash, model.ProviderInviteToken!, (Guid)model.ProviderUserId!); return ProcessRegistrationResult(identityResult, user); diff --git a/test/Core.Test/Auth/Models/Api/Request/Accounts/RegisterFinishRequestModelTests.cs b/test/Core.Test/Auth/Models/Api/Request/Accounts/RegisterFinishRequestModelTests.cs index 16ba8dbf16..3c099ce962 100644 --- a/test/Core.Test/Auth/Models/Api/Request/Accounts/RegisterFinishRequestModelTests.cs +++ b/test/Core.Test/Auth/Models/Api/Request/Accounts/RegisterFinishRequestModelTests.cs @@ -275,22 +275,28 @@ public class RegisterFinishRequestModelTests } [Fact] - public void Validate_WhenNeitherAuthNorUnlock_AndValidRootKdf_IsValid() + public void Validate_WhenAuthAndRootHashBothMissing_ReturnsMissingHashErrorOnly() { var model = new RegisterFinishRequestModel { Email = "user@example.com", UserAsymmetricKeys = new KeysRequestModel { PublicKey = "pk", EncryptedPrivateKey = "sk" }, + // Both MasterPasswordAuthentication and MasterPasswordHash are missing + MasterPasswordAuthentication = null, + MasterPasswordHash = null, + // Provide valid root KDF to avoid root KDF errors Kdf = KdfType.PBKDF2_SHA256, KdfIterations = AuthConstants.PBKDF2_ITERATIONS.Default, - // Memory and Parallelism irrelevant for PBKDF2 - EmailVerificationToken = "token" + EmailVerificationToken = "token" // avoid token error }; var results = Validate(model); - Assert.DoesNotContain(results, r => r.ErrorMessage?.Contains("Kdf") == true); - Assert.Empty(results.Where(r => r.ErrorMessage == "No valid registration token provided")); + // Only the new missing hash error should be present + Assert.Single(results); + Assert.Equal($"{nameof(MasterPasswordAuthenticationDataRequestModel.MasterPasswordAuthenticationHash)} and {nameof(RegisterFinishRequestModel.MasterPasswordHash)} not found on request, one needs to be defined.", results[0].ErrorMessage); + Assert.Contains(nameof(MasterPasswordAuthenticationDataRequestModel.MasterPasswordAuthenticationHash), results[0].MemberNames); + Assert.Contains(nameof(RegisterFinishRequestModel.MasterPasswordHash), results[0].MemberNames); } [Fact] diff --git a/test/IntegrationTestCommon/Factories/IdentityApplicationFactory.cs b/test/IntegrationTestCommon/Factories/IdentityApplicationFactory.cs index 266bdba823..55cb6da747 100644 --- a/test/IntegrationTestCommon/Factories/IdentityApplicationFactory.cs +++ b/test/IntegrationTestCommon/Factories/IdentityApplicationFactory.cs @@ -239,7 +239,6 @@ public class IdentityApplicationFactory : WebApplicationFactoryBase if (requestModel.MasterPasswordUnlock != null) { var unlock = requestModel.MasterPasswordUnlock; - // PM-28143 - Once UserSymmetricKey is removed and UnlockData is required, delete the fallback to UserSymmetricKey below. // Always force a valid encrypted string for tests to avoid model validation failures. var masterKeyWrappedUserKey = DefaultEncryptedString; requestModel.MasterPasswordUnlock = new MasterPasswordUnlockDataRequestModel