mirror of
https://github.com/bitwarden/server.git
synced 2024-11-21 12:05:42 +01:00
Auth/PM-11969 - Registration with Email Verification - Accept Emergency Access Invite Flow (#4773)
* PM-11969 - Add new logic for registering a user via an AcceptEmergencyAccessInviteToken * PM-11969 - Unit test new RegisterUserViaAcceptEmergencyAccessInviteToken method. * PM-11969 - Integration test new method
This commit is contained in:
parent
7d8df767cd
commit
fd07de736d
@ -33,6 +33,9 @@ public class RegisterFinishRequestModel : IValidatableObject
|
|||||||
|
|
||||||
public string? OrgSponsoredFreeFamilyPlanToken { get; set; }
|
public string? OrgSponsoredFreeFamilyPlanToken { get; set; }
|
||||||
|
|
||||||
|
public string? AcceptEmergencyAccessInviteToken { get; set; }
|
||||||
|
public Guid? AcceptEmergencyAccessId { get; set; }
|
||||||
|
|
||||||
public User ToUser()
|
public User ToUser()
|
||||||
{
|
{
|
||||||
var user = new User
|
var user = new User
|
||||||
|
@ -34,9 +34,31 @@ public interface IRegisterUserCommand
|
|||||||
/// <param name="user">The <see cref="User"/> to create</param>
|
/// <param name="user">The <see cref="User"/> to create</param>
|
||||||
/// <param name="masterPasswordHash">The hashed master password the user entered</param>
|
/// <param name="masterPasswordHash">The hashed master password the user entered</param>
|
||||||
/// <param name="emailVerificationToken">The email verification token sent to the user via email</param>
|
/// <param name="emailVerificationToken">The email verification token sent to the user via email</param>
|
||||||
/// <returns></returns>
|
/// <returns><see cref="IdentityResult"/></returns>
|
||||||
public Task<IdentityResult> RegisterUserViaEmailVerificationToken(User user, string masterPasswordHash, string emailVerificationToken);
|
public Task<IdentityResult> RegisterUserViaEmailVerificationToken(User user, string masterPasswordHash, string emailVerificationToken);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new user with a given master password hash, sends a welcome email, and raises the signup reference event.
|
||||||
|
/// If a valid org sponsored free family plan invite token is provided, the user will be created with their email verified.
|
||||||
|
/// If the token is invalid or expired, an error will be thrown.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user">The <see cref="User"/> to create</param>
|
||||||
|
/// <param name="masterPasswordHash">The hashed master password the user entered</param>
|
||||||
|
/// <param name="orgSponsoredFreeFamilyPlanInviteToken">The org sponsored free family plan invite token sent to the user via email</param>
|
||||||
|
/// <returns><see cref="IdentityResult"/></returns>
|
||||||
public Task<IdentityResult> RegisterUserViaOrganizationSponsoredFreeFamilyPlanInviteToken(User user, string masterPasswordHash, string orgSponsoredFreeFamilyPlanInviteToken);
|
public Task<IdentityResult> RegisterUserViaOrganizationSponsoredFreeFamilyPlanInviteToken(User user, string masterPasswordHash, string orgSponsoredFreeFamilyPlanInviteToken);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new user with a given master password hash, sends a welcome email, and raises the signup reference event.
|
||||||
|
/// If a valid token is provided, the user will be created with their email verified.
|
||||||
|
/// If the token is invalid or expired, an error will be thrown.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user">The <see cref="User"/> to create</param>
|
||||||
|
/// <param name="masterPasswordHash">The hashed master password the user entered</param>
|
||||||
|
/// <param name="acceptEmergencyAccessInviteToken">The emergency access invite token sent to the user via email</param>
|
||||||
|
/// <param name="acceptEmergencyAccessId">The emergency access id (used to validate the token)</param>
|
||||||
|
/// <returns><see cref="IdentityResult"/></returns>
|
||||||
|
public Task<IdentityResult> RegisterUserViaAcceptEmergencyAccessInviteToken(User user, string masterPasswordHash,
|
||||||
|
string acceptEmergencyAccessInviteToken, Guid acceptEmergencyAccessId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,8 @@ public class RegisterUserCommand : IRegisterUserCommand
|
|||||||
|
|
||||||
private readonly IValidateRedemptionTokenCommand _validateRedemptionTokenCommand;
|
private readonly IValidateRedemptionTokenCommand _validateRedemptionTokenCommand;
|
||||||
|
|
||||||
|
private readonly IDataProtectorTokenFactory<EmergencyAccessInviteTokenable> _emergencyAccessInviteTokenDataFactory;
|
||||||
|
|
||||||
private readonly string _disabledUserRegistrationExceptionMsg = "Open registration has been disabled by the system administrator.";
|
private readonly string _disabledUserRegistrationExceptionMsg = "Open registration has been disabled by the system administrator.";
|
||||||
|
|
||||||
public RegisterUserCommand(
|
public RegisterUserCommand(
|
||||||
@ -53,7 +55,8 @@ public class RegisterUserCommand : IRegisterUserCommand
|
|||||||
ICurrentContext currentContext,
|
ICurrentContext currentContext,
|
||||||
IUserService userService,
|
IUserService userService,
|
||||||
IMailService mailService,
|
IMailService mailService,
|
||||||
IValidateRedemptionTokenCommand validateRedemptionTokenCommand
|
IValidateRedemptionTokenCommand validateRedemptionTokenCommand,
|
||||||
|
IDataProtectorTokenFactory<EmergencyAccessInviteTokenable> emergencyAccessInviteTokenDataFactory
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_globalSettings = globalSettings;
|
_globalSettings = globalSettings;
|
||||||
@ -71,6 +74,7 @@ public class RegisterUserCommand : IRegisterUserCommand
|
|||||||
_mailService = mailService;
|
_mailService = mailService;
|
||||||
|
|
||||||
_validateRedemptionTokenCommand = validateRedemptionTokenCommand;
|
_validateRedemptionTokenCommand = validateRedemptionTokenCommand;
|
||||||
|
_emergencyAccessInviteTokenDataFactory = emergencyAccessInviteTokenDataFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -278,6 +282,27 @@ public class RegisterUserCommand : IRegisterUserCommand
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: in future, consider how we can consolidate base registration logic to reduce code duplication
|
||||||
|
public async Task<IdentityResult> RegisterUserViaAcceptEmergencyAccessInviteToken(User user, string masterPasswordHash,
|
||||||
|
string acceptEmergencyAccessInviteToken, Guid acceptEmergencyAccessId)
|
||||||
|
{
|
||||||
|
ValidateOpenRegistrationAllowed();
|
||||||
|
ValidateAcceptEmergencyAccessInviteToken(acceptEmergencyAccessInviteToken, acceptEmergencyAccessId, user.Email);
|
||||||
|
|
||||||
|
user.EmailVerified = true;
|
||||||
|
user.ApiKey = CoreHelpers.SecureRandomString(30); // API key can't be null.
|
||||||
|
|
||||||
|
var result = await _userService.CreateUserAsync(user, masterPasswordHash);
|
||||||
|
if (result == IdentityResult.Success)
|
||||||
|
{
|
||||||
|
await _mailService.SendWelcomeEmailAsync(user);
|
||||||
|
await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.Signup, user, _currentContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private void ValidateOpenRegistrationAllowed()
|
private void ValidateOpenRegistrationAllowed()
|
||||||
{
|
{
|
||||||
// We validate open registration on send of initial email and here b/c a user could technically start the
|
// We validate open registration on send of initial email and here b/c a user could technically start the
|
||||||
@ -297,7 +322,15 @@ public class RegisterUserCommand : IRegisterUserCommand
|
|||||||
{
|
{
|
||||||
throw new BadRequestException("Invalid org sponsored free family plan token.");
|
throw new BadRequestException("Invalid org sponsored free family plan token.");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ValidateAcceptEmergencyAccessInviteToken(string acceptEmergencyAccessInviteToken, Guid acceptEmergencyAccessId, string userEmail)
|
||||||
|
{
|
||||||
|
_emergencyAccessInviteTokenDataFactory.TryUnprotect(acceptEmergencyAccessInviteToken, out var tokenable);
|
||||||
|
if (tokenable == null || !tokenable.Valid || !tokenable.IsValid(acceptEmergencyAccessId, userEmail))
|
||||||
|
{
|
||||||
|
throw new BadRequestException("Invalid accept emergency access invite token.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -144,7 +144,7 @@ public class AccountsController : Controller
|
|||||||
{
|
{
|
||||||
var user = model.ToUser();
|
var user = model.ToUser();
|
||||||
|
|
||||||
// Users will either have an org invite token or an email verification token - not both.
|
// Users will either have an emailed token or an email verification token - not both.
|
||||||
|
|
||||||
IdentityResult identityResult = null;
|
IdentityResult identityResult = null;
|
||||||
var delaysEnabled = !_featureService.IsEnabled(FeatureFlagKeys.EmailVerificationDisableTimingDelays);
|
var delaysEnabled = !_featureService.IsEnabled(FeatureFlagKeys.EmailVerificationDisableTimingDelays);
|
||||||
@ -164,9 +164,25 @@ public class AccountsController : Controller
|
|||||||
return await ProcessRegistrationResult(identityResult, user, delaysEnabled);
|
return await ProcessRegistrationResult(identityResult, user, delaysEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
identityResult = await _registerUserCommand.RegisterUserViaEmailVerificationToken(user, model.MasterPasswordHash, model.EmailVerificationToken);
|
if (!string.IsNullOrEmpty(model.AcceptEmergencyAccessInviteToken) && model.AcceptEmergencyAccessId.HasValue)
|
||||||
|
{
|
||||||
|
identityResult = await _registerUserCommand.RegisterUserViaAcceptEmergencyAccessInviteToken(user, model.MasterPasswordHash,
|
||||||
|
model.AcceptEmergencyAccessInviteToken, model.AcceptEmergencyAccessId.Value);
|
||||||
|
|
||||||
|
return await ProcessRegistrationResult(identityResult, user, delaysEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(model.EmailVerificationToken))
|
||||||
|
{
|
||||||
|
throw new BadRequestException("Invalid registration finish request");
|
||||||
|
}
|
||||||
|
|
||||||
|
identityResult =
|
||||||
|
await _registerUserCommand.RegisterUserViaEmailVerificationToken(user, model.MasterPasswordHash,
|
||||||
|
model.EmailVerificationToken);
|
||||||
|
|
||||||
return await ProcessRegistrationResult(identityResult, user, delaysEnabled);
|
return await ProcessRegistrationResult(identityResult, user, delaysEnabled);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<RegisterResponseModel> ProcessRegistrationResult(IdentityResult result, User user, bool delaysEnabled)
|
private async Task<RegisterResponseModel> ProcessRegistrationResult(IdentityResult result, User user, bool delaysEnabled)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.AdminConsole.Enums;
|
using Bit.Core.AdminConsole.Enums;
|
||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
|
using Bit.Core.Auth.Entities;
|
||||||
using Bit.Core.Auth.Enums;
|
using Bit.Core.Auth.Enums;
|
||||||
using Bit.Core.Auth.Models;
|
using Bit.Core.Auth.Models;
|
||||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||||
@ -449,7 +450,90 @@ public class RegisterUserCommandTests
|
|||||||
var result = await Assert.ThrowsAsync<BadRequestException>(() =>
|
var result = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||||
sutProvider.Sut.RegisterUserViaOrganizationSponsoredFreeFamilyPlanInviteToken(user, masterPasswordHash, orgSponsoredFreeFamilyPlanInviteToken));
|
sutProvider.Sut.RegisterUserViaOrganizationSponsoredFreeFamilyPlanInviteToken(user, masterPasswordHash, orgSponsoredFreeFamilyPlanInviteToken));
|
||||||
Assert.Equal("Open registration has been disabled by the system administrator.", result.Message);
|
Assert.Equal("Open registration has been disabled by the system administrator.", result.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterUserViaAcceptEmergencyAccessInviteToken
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task RegisterUserViaAcceptEmergencyAccessInviteToken_Succeeds(
|
||||||
|
SutProvider<RegisterUserCommand> sutProvider, User user, string masterPasswordHash,
|
||||||
|
EmergencyAccess emergencyAccess, string acceptEmergencyAccessInviteToken, Guid acceptEmergencyAccessId)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
emergencyAccess.Email = user.Email;
|
||||||
|
emergencyAccess.Id = acceptEmergencyAccessId;
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IDataProtectorTokenFactory<EmergencyAccessInviteTokenable>>()
|
||||||
|
.TryUnprotect(acceptEmergencyAccessInviteToken, out Arg.Any<EmergencyAccessInviteTokenable>())
|
||||||
|
.Returns(callInfo =>
|
||||||
|
{
|
||||||
|
callInfo[1] = new EmergencyAccessInviteTokenable(emergencyAccess, 10);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IUserService>()
|
||||||
|
.CreateUserAsync(user, masterPasswordHash)
|
||||||
|
.Returns(IdentityResult.Success);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await sutProvider.Sut.RegisterUserViaAcceptEmergencyAccessInviteToken(user, masterPasswordHash, acceptEmergencyAccessInviteToken, acceptEmergencyAccessId);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result.Succeeded);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IUserService>()
|
||||||
|
.Received(1)
|
||||||
|
.CreateUserAsync(Arg.Is<User>(u => u.Name == user.Name && u.EmailVerified == true && u.ApiKey != null), masterPasswordHash);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IMailService>()
|
||||||
|
.Received(1)
|
||||||
|
.SendWelcomeEmailAsync(user);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IReferenceEventService>()
|
||||||
|
.Received(1)
|
||||||
|
.RaiseEventAsync(Arg.Is<ReferenceEvent>(refEvent => refEvent.Type == ReferenceEventType.Signup));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task RegisterUserViaAcceptEmergencyAccessInviteToken_InvalidToken_ThrowsBadRequestException(SutProvider<RegisterUserCommand> sutProvider, User user,
|
||||||
|
string masterPasswordHash, EmergencyAccess emergencyAccess, string acceptEmergencyAccessInviteToken, Guid acceptEmergencyAccessId)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
emergencyAccess.Email = "wrong@email.com";
|
||||||
|
emergencyAccess.Id = acceptEmergencyAccessId;
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IDataProtectorTokenFactory<EmergencyAccessInviteTokenable>>()
|
||||||
|
.TryUnprotect(acceptEmergencyAccessInviteToken, out Arg.Any<EmergencyAccessInviteTokenable>())
|
||||||
|
.Returns(callInfo =>
|
||||||
|
{
|
||||||
|
callInfo[1] = new EmergencyAccessInviteTokenable(emergencyAccess, 10);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var result = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||||
|
sutProvider.Sut.RegisterUserViaAcceptEmergencyAccessInviteToken(user, masterPasswordHash, acceptEmergencyAccessInviteToken, acceptEmergencyAccessId));
|
||||||
|
Assert.Equal("Invalid accept emergency access invite token.", result.Message);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task RegisterUserViaAcceptEmergencyAccessInviteToken_DisabledOpenRegistration_ThrowsBadRequestException(SutProvider<RegisterUserCommand> sutProvider, User user,
|
||||||
|
string masterPasswordHash, string acceptEmergencyAccessInviteToken, Guid acceptEmergencyAccessId)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
sutProvider.GetDependency<IGlobalSettings>()
|
||||||
|
.DisableUserRegistration = true;
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var result = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||||
|
sutProvider.Sut.RegisterUserViaAcceptEmergencyAccessInviteToken(user, masterPasswordHash, acceptEmergencyAccessInviteToken, acceptEmergencyAccessId));
|
||||||
|
Assert.Equal("Open registration has been disabled by the system administrator.", result.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
|
using Bit.Core.Auth.Entities;
|
||||||
using Bit.Core.Auth.Models.Api.Request.Accounts;
|
using Bit.Core.Auth.Models.Api.Request.Accounts;
|
||||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
@ -400,6 +401,75 @@ public class AccountsControllerTests : IClassFixture<IdentityApplicationFactory>
|
|||||||
Assert.Equal(kdfParallelism, user.KdfParallelism);
|
Assert.Equal(kdfParallelism, user.KdfParallelism);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task RegistrationWithEmailVerification_WithAcceptEmergencyAccessInviteToken_Succeeds(
|
||||||
|
[StringLength(1000)] string masterPasswordHash, [StringLength(50)] string masterPasswordHint, string userSymmetricKey,
|
||||||
|
KeysRequestModel userAsymmetricKeys, int kdfMemory, int kdfParallelism, EmergencyAccess emergencyAccess)
|
||||||
|
{
|
||||||
|
|
||||||
|
// Localize factory to just this test.
|
||||||
|
var localFactory = new IdentityApplicationFactory();
|
||||||
|
|
||||||
|
// Hardcoded, valid data
|
||||||
|
var email = "jsnider+local79813655659549@bitwarden.com";
|
||||||
|
var acceptEmergencyAccessInviteToken = "CfDJ8HFsgwUNr89EtnCal5H72cwjvdjWmBp3J0ry7KoG6zDFub-EeoA3cfLBXONq7thKq7QTBh6KJ--jU0Det7t3P9EXqxmEacxIlgFlBgtywIUho9N8nVQeNcltkQO9g0vj_ASshnn6fWK3zpqS6Z8JueVZ2TMtdks5uc7DjZurWFLX27Dpii-UusFD78Z5tCY-D79bkjHy43g1ULk2F2ZtwiJvp3C9QvXW1-12IEsyHHSxU-9RELe-_joo2iDIR-cvMmEfbEXK7uvuzNT2V0r22jalaAKFvd84Gza9Q0YSFn8z_nAJxVqEXsAVKdG8SRN5Wa3K2mdNoBMt20RrzNuuJhe6vzX0yP35HtC4e1YXXzWB";
|
||||||
|
var acceptEmergencyAccessId = new Guid("8bc5e574-cef6-4ee7-b9ed-b1e90158c016");
|
||||||
|
|
||||||
|
emergencyAccess.Id = acceptEmergencyAccessId;
|
||||||
|
emergencyAccess.Email = email;
|
||||||
|
|
||||||
|
var emergencyAccessInviteTokenable = new EmergencyAccessInviteTokenable(emergencyAccess, 10) { };
|
||||||
|
|
||||||
|
localFactory.SubstituteService<IDataProtectorTokenFactory<EmergencyAccessInviteTokenable>>(dataProtectorTokenFactory =>
|
||||||
|
{
|
||||||
|
dataProtectorTokenFactory.TryUnprotect(Arg.Is(acceptEmergencyAccessInviteToken), out Arg.Any<EmergencyAccessInviteTokenable>())
|
||||||
|
.Returns(callInfo =>
|
||||||
|
{
|
||||||
|
callInfo[1] = emergencyAccessInviteTokenable;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var registerFinishReqModel = new RegisterFinishRequestModel
|
||||||
|
{
|
||||||
|
Email = email,
|
||||||
|
MasterPasswordHash = masterPasswordHash,
|
||||||
|
MasterPasswordHint = masterPasswordHint,
|
||||||
|
AcceptEmergencyAccessInviteToken = acceptEmergencyAccessInviteToken,
|
||||||
|
AcceptEmergencyAccessId = acceptEmergencyAccessId,
|
||||||
|
Kdf = KdfType.PBKDF2_SHA256,
|
||||||
|
KdfIterations = AuthConstants.PBKDF2_ITERATIONS.Default,
|
||||||
|
UserSymmetricKey = userSymmetricKey,
|
||||||
|
UserAsymmetricKeys = userAsymmetricKeys,
|
||||||
|
KdfMemory = kdfMemory,
|
||||||
|
KdfParallelism = kdfParallelism
|
||||||
|
};
|
||||||
|
|
||||||
|
var postRegisterFinishHttpContext = await localFactory.PostRegisterFinishAsync(registerFinishReqModel);
|
||||||
|
|
||||||
|
Assert.Equal(StatusCodes.Status200OK, postRegisterFinishHttpContext.Response.StatusCode);
|
||||||
|
|
||||||
|
var database = localFactory.GetDatabaseContext();
|
||||||
|
var user = await database.Users
|
||||||
|
.SingleAsync(u => u.Email == email);
|
||||||
|
|
||||||
|
Assert.NotNull(user);
|
||||||
|
|
||||||
|
// Assert user properties match the request model
|
||||||
|
Assert.Equal(email, user.Email);
|
||||||
|
Assert.NotEqual(masterPasswordHash, user.MasterPassword); // We execute server side hashing
|
||||||
|
Assert.NotNull(user.MasterPassword);
|
||||||
|
Assert.Equal(masterPasswordHint, user.MasterPasswordHint);
|
||||||
|
Assert.Equal(userSymmetricKey, user.Key);
|
||||||
|
Assert.Equal(userAsymmetricKeys.EncryptedPrivateKey, user.PrivateKey);
|
||||||
|
Assert.Equal(userAsymmetricKeys.PublicKey, user.PublicKey);
|
||||||
|
Assert.Equal(KdfType.PBKDF2_SHA256, user.Kdf);
|
||||||
|
Assert.Equal(AuthConstants.PBKDF2_ITERATIONS.Default, user.KdfIterations);
|
||||||
|
Assert.Equal(kdfMemory, user.KdfMemory);
|
||||||
|
Assert.Equal(kdfParallelism, user.KdfParallelism);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task PostRegisterVerificationEmailClicked_Success(
|
public async Task PostRegisterVerificationEmailClicked_Success(
|
||||||
|
Loading…
Reference in New Issue
Block a user