mirror of
https://github.com/bitwarden/server.git
synced 2025-07-01 08:02:49 -05:00
Auth/PM-7322 - Registration with Email verification - Finish registration endpoint (#4182)
* PM-7322 - AccountsController.cs - create empty method + empty req model to be able to create draft PR. * PM-7322 - Start on RegisterFinishRequestModel.cs * PM-7322 - WIP on Complete Registration endpoint * PM-7322 - UserService.cs - RegisterUserAsync - Tweak of token to be orgInviteToken as we are adding a new email verification token to the mix. * PM-7322 - UserService - Rename MP to MPHash * PM-7322 - More WIP progress on getting new finish registration process in place. * PM-7322 Create IRegisterUserCommand * PM-7322 - RegisterUserCommand.cs - first WIP draft * PM-7322 - Implement use of new command in Identity. * PM-7322 - Rename RegisterUserViaOrgInvite to just be RegisterUser as orgInvite is optional. * PM07322 - Test RegisterUserCommand.RegisterUser(...) happy paths and one bad request path. * PM-7322 - More WIP on RegisterUserCommand.cs and tests * PM-7322 - RegisterUserCommand.cs - refactor ValidateOrgInviteToken logic to always validate the token if we have one. * PM-7322 - RegisterUserCommand.cs - Refactor OrgInviteToken validation to be more clear + validate org invite token even in open registration scenarios + added tests. * PM-7322 - Add more test coverage to RegisterUserWithOptionalOrgInvite * PM-7322 - IRegisterUserCommand - DOCS * PM-7322 - Test RegisterUser * PM-7322 - IRegisterUserCommand - Add more docs. * PM-7322 - Finish updating all existing user service register calls to use the new command. * PM-7322 - RegistrationEmailVerificationTokenable.cs changes + tests * PM-7322 - RegistrationEmailVerificationTokenable.cs changed to only verify email as it's the only thing we need to verify + updated tests. * PM-7322 - Get RegisterUserViaEmailVerificationToken built and tested * PM-7322 - AccountsController.cs - get bones of PostRegisterFinish in place * PM-7322 - SendVerificationEmailForRegistrationCommand - Feature flag timing attack delays per architecture discussion with a default of keeping them around. * PM-7322 - RegisterFinishRequestModel.cs - EmailVerificationToken must be optional for org invite scenarios. * PM-7322 - HandlebarsMailService.cs - SendRegistrationVerificationEmailAsync - must URL encode email to avoid invalid email upon submission to server on complete registration step * PM-7322 - RegisterUserCommandTests.cs - add API key assertions * PM-7322 - Clean up RegisterUserCommand.cs * PM-7322 - Refactor AccountsController.cs existing org invite method and new process to consider new feature flag for delays. * PM-7322 - Add feature flag svc to AccountsControllerTests.cs + add TODO * PM-7322 - AccountsController.cs - Refactor shared IdentityResult logic into private helper. * PM-7322 - Work on getting PostRegisterFinish tests in place. * PM-7322 - AccountsControllerTests.cs - test new method. * PM-7322 - RegisterFinishRequestModel.cs - Update to use required keyword instead of required annotations as it is easier to catch mistakes. * PM-7322 - Fix misspelling * PM-7322 - Integration tests for RegistrationWithEmailVerification * PM-7322 - Fix leaky integration tests. * PM-7322 - Another leaky test fix. * PM-7322 - AccountsControllerTests.cs - fix RegistrationWithEmailVerification_WithOrgInviteToken_Succeeds * PM-7322 - AccountsControllerTests.cs - Finish out integration test suite!
This commit is contained in:
@ -17,8 +17,8 @@ public interface IUserService
|
||||
Task<User> GetUserByPrincipalAsync(ClaimsPrincipal principal);
|
||||
Task<DateTime> GetAccountRevisionDateByIdAsync(Guid userId);
|
||||
Task SaveUserAsync(User user, bool push = false);
|
||||
Task<IdentityResult> RegisterUserAsync(User user, string masterPassword, string token, Guid? orgUserId);
|
||||
Task<IdentityResult> RegisterUserAsync(User user);
|
||||
Task<IdentityResult> CreateUserAsync(User user);
|
||||
Task<IdentityResult> CreateUserAsync(User user, string masterPasswordHash);
|
||||
Task SendMasterPasswordHintAsync(string email);
|
||||
Task SendTwoFactorEmailAsync(User user);
|
||||
Task<bool> VerifyTwoFactorEmailAsync(User user, string token);
|
||||
@ -77,6 +77,9 @@ public interface IUserService
|
||||
Task<bool> VerifyOTPAsync(User user, string token);
|
||||
Task<bool> VerifySecretAsync(User user, string secret);
|
||||
|
||||
|
||||
void SetTwoFactorProvider(User user, TwoFactorProviderType type, bool setEnabled = true);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the user is a legacy user. Legacy users use their master key as their encryption key.
|
||||
/// We force these users to the web to migrate their encryption scheme.
|
||||
|
@ -59,7 +59,7 @@ public class HandlebarsMailService : IMailService
|
||||
var model = new RegisterVerifyEmail
|
||||
{
|
||||
Token = WebUtility.UrlEncode(token),
|
||||
Email = email,
|
||||
Email = WebUtility.UrlEncode(email),
|
||||
WebVaultUrl = _globalSettings.BaseServiceUri.VaultWithHash,
|
||||
SiteName = _globalSettings.SiteName
|
||||
};
|
||||
|
@ -25,7 +25,6 @@ using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
using File = System.IO.File;
|
||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||
|
||||
@ -289,89 +288,14 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
||||
await _mailService.SendVerifyDeleteEmailAsync(user.Email, user.Id, token);
|
||||
}
|
||||
|
||||
public async Task<IdentityResult> RegisterUserAsync(User user, string masterPassword,
|
||||
string token, Guid? orgUserId)
|
||||
public async Task<IdentityResult> CreateUserAsync(User user)
|
||||
{
|
||||
var tokenValid = false;
|
||||
if (_globalSettings.DisableUserRegistration && !string.IsNullOrWhiteSpace(token) && orgUserId.HasValue)
|
||||
{
|
||||
// TODO: PM-4142 - remove old token validation logic once 3 releases of backwards compatibility are complete
|
||||
var newTokenValid = OrgUserInviteTokenable.ValidateOrgUserInviteStringToken(
|
||||
_orgUserInviteTokenDataFactory, token, orgUserId.Value, user.Email);
|
||||
|
||||
tokenValid = newTokenValid ||
|
||||
CoreHelpers.UserInviteTokenIsValid(_organizationServiceDataProtector, token,
|
||||
user.Email, orgUserId.Value, _globalSettings);
|
||||
}
|
||||
|
||||
if (_globalSettings.DisableUserRegistration && !tokenValid)
|
||||
{
|
||||
throw new BadRequestException("Open registration has been disabled by the system administrator.");
|
||||
}
|
||||
|
||||
if (orgUserId.HasValue)
|
||||
{
|
||||
var orgUser = await _organizationUserRepository.GetByIdAsync(orgUserId.Value);
|
||||
if (orgUser != null)
|
||||
{
|
||||
var twoFactorPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(orgUser.OrganizationId,
|
||||
PolicyType.TwoFactorAuthentication);
|
||||
if (twoFactorPolicy != null && twoFactorPolicy.Enabled)
|
||||
{
|
||||
user.SetTwoFactorProviders(new Dictionary<TwoFactorProviderType, TwoFactorProvider>
|
||||
{
|
||||
|
||||
[TwoFactorProviderType.Email] = new TwoFactorProvider
|
||||
{
|
||||
MetaData = new Dictionary<string, object> { ["Email"] = user.Email.ToLowerInvariant() },
|
||||
Enabled = true
|
||||
}
|
||||
});
|
||||
SetTwoFactorProvider(user, TwoFactorProviderType.Email);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
user.ApiKey = CoreHelpers.SecureRandomString(30);
|
||||
var result = await base.CreateAsync(user, masterPassword);
|
||||
if (result == IdentityResult.Success)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(user.ReferenceData))
|
||||
{
|
||||
var referenceData = JsonConvert.DeserializeObject<Dictionary<string, object>>(user.ReferenceData);
|
||||
if (referenceData.TryGetValue("initiationPath", out var value))
|
||||
{
|
||||
var initiationPath = value.ToString();
|
||||
await SendAppropriateWelcomeEmailAsync(user, initiationPath);
|
||||
if (!string.IsNullOrEmpty(initiationPath))
|
||||
{
|
||||
await _referenceEventService.RaiseEventAsync(
|
||||
new ReferenceEvent(ReferenceEventType.Signup, user, _currentContext)
|
||||
{
|
||||
SignupInitiationPath = initiationPath
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.Signup, user, _currentContext));
|
||||
}
|
||||
|
||||
return result;
|
||||
return await CreateAsync(user);
|
||||
}
|
||||
|
||||
public async Task<IdentityResult> RegisterUserAsync(User user)
|
||||
public async Task<IdentityResult> CreateUserAsync(User user, string masterPasswordHash)
|
||||
{
|
||||
var result = await base.CreateAsync(user);
|
||||
if (result == IdentityResult.Success)
|
||||
{
|
||||
await _mailService.SendWelcomeEmailAsync(user);
|
||||
await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.Signup, user, _currentContext));
|
||||
}
|
||||
|
||||
return result;
|
||||
return await CreateAsync(user, masterPasswordHash);
|
||||
}
|
||||
|
||||
public async Task SendMasterPasswordHintAsync(string email)
|
||||
|
Reference in New Issue
Block a user