mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 07:36:14 -05:00
Auth/PM-6198 - Registration with Email Verification - Add email clicked endpoint (#4520)
* PM-6198 - RegistrationEmailVerificationTokenable - add new static validate token method * PM-6198 - Rename RegistrationStart to Registration as we now have to add another anonymous reference event. * PM-6198 - rest of work * PM-6198 - Unit test new account controller method. * PM-6198 - Integration test new account controller endpoint
This commit is contained in:
@ -268,6 +268,43 @@ public class AccountsControllerTests : IClassFixture<IdentityApplicationFactory>
|
||||
Assert.Equal(kdfParallelism, user.KdfParallelism);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PostRegisterVerificationEmailClicked_Success(
|
||||
[Required, StringLength(20)] string name,
|
||||
string emailVerificationToken)
|
||||
{
|
||||
// Arrange
|
||||
// Localize substitutions to this test.
|
||||
var localFactory = new IdentityApplicationFactory();
|
||||
|
||||
var email = $"test+register+{name}@email.com";
|
||||
var registrationEmailVerificationTokenable = new RegistrationEmailVerificationTokenable(email);
|
||||
|
||||
localFactory.SubstituteService<IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable>>(emailVerificationTokenDataProtectorFactory =>
|
||||
{
|
||||
emailVerificationTokenDataProtectorFactory.TryUnprotect(Arg.Is(emailVerificationToken), out Arg.Any<RegistrationEmailVerificationTokenable>())
|
||||
.Returns(callInfo =>
|
||||
{
|
||||
callInfo[1] = registrationEmailVerificationTokenable;
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
var requestModel = new RegisterVerificationEmailClickedRequestModel
|
||||
{
|
||||
Email = email,
|
||||
EmailVerificationToken = emailVerificationToken
|
||||
};
|
||||
|
||||
// Act
|
||||
var httpContext = await localFactory.PostRegisterVerificationEmailClicked(requestModel);
|
||||
|
||||
var body = await httpContext.ReadBodyAsStringAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(StatusCodes.Status200OK, httpContext.Response.StatusCode);
|
||||
}
|
||||
|
||||
private async Task<User> CreateUserAsync(string email, string name, IdentityApplicationFactory factory = null)
|
||||
{
|
||||
var factoryToUse = factory ?? _factory;
|
||||
|
@ -41,6 +41,8 @@ public class AccountsControllerTests : IDisposable
|
||||
private readonly ISendVerificationEmailForRegistrationCommand _sendVerificationEmailForRegistrationCommand;
|
||||
private readonly IReferenceEventService _referenceEventService;
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable> _registrationEmailVerificationTokenDataFactory;
|
||||
|
||||
|
||||
public AccountsControllerTests()
|
||||
{
|
||||
@ -54,6 +56,8 @@ public class AccountsControllerTests : IDisposable
|
||||
_sendVerificationEmailForRegistrationCommand = Substitute.For<ISendVerificationEmailForRegistrationCommand>();
|
||||
_referenceEventService = Substitute.For<IReferenceEventService>();
|
||||
_featureService = Substitute.For<IFeatureService>();
|
||||
_registrationEmailVerificationTokenDataFactory = Substitute.For<IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable>>();
|
||||
|
||||
_sut = new AccountsController(
|
||||
_currentContext,
|
||||
_logger,
|
||||
@ -64,7 +68,8 @@ public class AccountsControllerTests : IDisposable
|
||||
_getWebAuthnLoginCredentialAssertionOptionsCommand,
|
||||
_sendVerificationEmailForRegistrationCommand,
|
||||
_referenceEventService,
|
||||
_featureService
|
||||
_featureService,
|
||||
_registrationEmailVerificationTokenDataFactory
|
||||
);
|
||||
}
|
||||
|
||||
@ -380,4 +385,105 @@ public class AccountsControllerTests : IDisposable
|
||||
Assert.Equal(duplicateUserEmailErrorDesc, modelError.ErrorMessage);
|
||||
}
|
||||
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PostRegisterVerificationEmailClicked_WhenTokenIsValid_ShouldReturnOk(string email, string emailVerificationToken)
|
||||
{
|
||||
// Arrange
|
||||
var registrationEmailVerificationTokenable = new RegistrationEmailVerificationTokenable(email);
|
||||
_registrationEmailVerificationTokenDataFactory
|
||||
.TryUnprotect(emailVerificationToken, out Arg.Any<RegistrationEmailVerificationTokenable>())
|
||||
.Returns(callInfo =>
|
||||
{
|
||||
callInfo[1] = registrationEmailVerificationTokenable;
|
||||
return true;
|
||||
});
|
||||
|
||||
_userRepository.GetByEmailAsync(email).ReturnsNull(); // no existing user
|
||||
|
||||
var requestModel = new RegisterVerificationEmailClickedRequestModel
|
||||
{
|
||||
Email = email,
|
||||
EmailVerificationToken = emailVerificationToken
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await _sut.PostRegisterVerificationEmailClicked(requestModel);
|
||||
|
||||
// Assert
|
||||
var okResult = Assert.IsType<OkResult>(result);
|
||||
Assert.Equal(200, okResult.StatusCode);
|
||||
|
||||
await _referenceEventService.Received(1).RaiseEventAsync(Arg.Is<ReferenceEvent>(e =>
|
||||
e.Type == ReferenceEventType.SignupEmailClicked
|
||||
&& e.EmailVerificationTokenValid == true
|
||||
&& e.UserAlreadyExists == false
|
||||
));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PostRegisterVerificationEmailClicked_WhenTokenIsInvalid_ShouldReturnBadRequest(string email, string emailVerificationToken)
|
||||
{
|
||||
// Arrange
|
||||
var registrationEmailVerificationTokenable = new RegistrationEmailVerificationTokenable("wrongEmail");
|
||||
_registrationEmailVerificationTokenDataFactory
|
||||
.TryUnprotect(emailVerificationToken, out Arg.Any<RegistrationEmailVerificationTokenable>())
|
||||
.Returns(callInfo =>
|
||||
{
|
||||
callInfo[1] = registrationEmailVerificationTokenable;
|
||||
return true;
|
||||
});
|
||||
|
||||
_userRepository.GetByEmailAsync(email).ReturnsNull(); // no existing user
|
||||
|
||||
var requestModel = new RegisterVerificationEmailClickedRequestModel
|
||||
{
|
||||
Email = email,
|
||||
EmailVerificationToken = emailVerificationToken
|
||||
};
|
||||
|
||||
// Act & assert
|
||||
await Assert.ThrowsAsync<BadRequestException>(() => _sut.PostRegisterVerificationEmailClicked(requestModel));
|
||||
|
||||
await _referenceEventService.Received(1).RaiseEventAsync(Arg.Is<ReferenceEvent>(e =>
|
||||
e.Type == ReferenceEventType.SignupEmailClicked
|
||||
&& e.EmailVerificationTokenValid == false
|
||||
&& e.UserAlreadyExists == false
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PostRegisterVerificationEmailClicked_WhenTokenIsValidButExistingUser_ShouldReturnBadRequest(string email, string emailVerificationToken, User existingUser)
|
||||
{
|
||||
// Arrange
|
||||
var registrationEmailVerificationTokenable = new RegistrationEmailVerificationTokenable(email);
|
||||
_registrationEmailVerificationTokenDataFactory
|
||||
.TryUnprotect(emailVerificationToken, out Arg.Any<RegistrationEmailVerificationTokenable>())
|
||||
.Returns(callInfo =>
|
||||
{
|
||||
callInfo[1] = registrationEmailVerificationTokenable;
|
||||
return true;
|
||||
});
|
||||
|
||||
_userRepository.GetByEmailAsync(email).Returns(existingUser);
|
||||
|
||||
var requestModel = new RegisterVerificationEmailClickedRequestModel
|
||||
{
|
||||
Email = email,
|
||||
EmailVerificationToken = emailVerificationToken
|
||||
};
|
||||
|
||||
// Act & assert
|
||||
await Assert.ThrowsAsync<BadRequestException>(() => _sut.PostRegisterVerificationEmailClicked(requestModel));
|
||||
|
||||
await _referenceEventService.Received(1).RaiseEventAsync(Arg.Is<ReferenceEvent>(e =>
|
||||
e.Type == ReferenceEventType.SignupEmailClicked
|
||||
&& e.EmailVerificationTokenValid == true
|
||||
&& e.UserAlreadyExists == true
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -29,6 +29,11 @@ public class IdentityApplicationFactory : WebApplicationFactoryBase<Startup>
|
||||
return await Server.PostAsync("/accounts/register/finish", JsonContent.Create(model));
|
||||
}
|
||||
|
||||
public async Task<HttpContext> PostRegisterVerificationEmailClicked(RegisterVerificationEmailClickedRequestModel model)
|
||||
{
|
||||
return await Server.PostAsync("/accounts/register/verification-email-clicked", JsonContent.Create(model));
|
||||
}
|
||||
|
||||
public async Task<(string Token, string RefreshToken)> TokenFromPasswordAsync(string username,
|
||||
string password,
|
||||
string deviceIdentifier = DefaultDeviceIdentifier,
|
||||
|
Reference in New Issue
Block a user