mirror of
https://github.com/bitwarden/server.git
synced 2025-07-01 08:02:49 -05: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:
@ -33,6 +33,9 @@ public class RegisterFinishRequestModel : IValidatableObject
|
||||
|
||||
public string? OrgSponsoredFreeFamilyPlanToken { get; set; }
|
||||
|
||||
public string? AcceptEmergencyAccessInviteToken { get; set; }
|
||||
public Guid? AcceptEmergencyAccessId { get; set; }
|
||||
|
||||
public User ToUser()
|
||||
{
|
||||
var user = new User
|
||||
|
@ -34,9 +34,31 @@ public interface IRegisterUserCommand
|
||||
/// <param name="user">The <see cref="User"/> to create</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>
|
||||
/// <returns></returns>
|
||||
/// <returns><see cref="IdentityResult"/></returns>
|
||||
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);
|
||||
|
||||
/// <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 IDataProtectorTokenFactory<EmergencyAccessInviteTokenable> _emergencyAccessInviteTokenDataFactory;
|
||||
|
||||
private readonly string _disabledUserRegistrationExceptionMsg = "Open registration has been disabled by the system administrator.";
|
||||
|
||||
public RegisterUserCommand(
|
||||
@ -53,7 +55,8 @@ public class RegisterUserCommand : IRegisterUserCommand
|
||||
ICurrentContext currentContext,
|
||||
IUserService userService,
|
||||
IMailService mailService,
|
||||
IValidateRedemptionTokenCommand validateRedemptionTokenCommand
|
||||
IValidateRedemptionTokenCommand validateRedemptionTokenCommand,
|
||||
IDataProtectorTokenFactory<EmergencyAccessInviteTokenable> emergencyAccessInviteTokenDataFactory
|
||||
)
|
||||
{
|
||||
_globalSettings = globalSettings;
|
||||
@ -71,6 +74,7 @@ public class RegisterUserCommand : IRegisterUserCommand
|
||||
_mailService = mailService;
|
||||
|
||||
_validateRedemptionTokenCommand = validateRedemptionTokenCommand;
|
||||
_emergencyAccessInviteTokenDataFactory = emergencyAccessInviteTokenDataFactory;
|
||||
}
|
||||
|
||||
|
||||
@ -278,6 +282,27 @@ public class RegisterUserCommand : IRegisterUserCommand
|
||||
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()
|
||||
{
|
||||
// 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.");
|
||||
}
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user