mirror of
https://github.com/bitwarden/server.git
synced 2025-06-14 06:50:47 -05:00

* 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!
180 lines
7.4 KiB
C#
180 lines
7.4 KiB
C#
using Bit.Core;
|
|
using Bit.Core.Auth.Enums;
|
|
using Bit.Core.Auth.Models.Api.Request.Accounts;
|
|
using Bit.Core.Auth.Models.Api.Response.Accounts;
|
|
using Bit.Core.Auth.Models.Business.Tokenables;
|
|
using Bit.Core.Auth.Services;
|
|
using Bit.Core.Auth.UserFeatures.Registration;
|
|
using Bit.Core.Auth.UserFeatures.WebAuthnLogin;
|
|
using Bit.Core.Auth.Utilities;
|
|
using Bit.Core.Context;
|
|
using Bit.Core.Entities;
|
|
using Bit.Core.Enums;
|
|
using Bit.Core.Exceptions;
|
|
using Bit.Core.Models.Data;
|
|
using Bit.Core.Repositories;
|
|
using Bit.Core.Services;
|
|
using Bit.Core.Tokens;
|
|
using Bit.Core.Tools.Enums;
|
|
using Bit.Core.Tools.Models.Business;
|
|
using Bit.Core.Tools.Services;
|
|
using Bit.Core.Utilities;
|
|
using Bit.Identity.Models.Request.Accounts;
|
|
using Bit.Identity.Models.Response.Accounts;
|
|
using Bit.SharedWeb.Utilities;
|
|
using Microsoft.AspNetCore.Identity;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
namespace Bit.Identity.Controllers;
|
|
|
|
[Route("accounts")]
|
|
[ExceptionHandlerFilter]
|
|
public class AccountsController : Controller
|
|
{
|
|
private readonly ICurrentContext _currentContext;
|
|
private readonly ILogger<AccountsController> _logger;
|
|
private readonly IUserRepository _userRepository;
|
|
private readonly IRegisterUserCommand _registerUserCommand;
|
|
private readonly ICaptchaValidationService _captchaValidationService;
|
|
private readonly IDataProtectorTokenFactory<WebAuthnLoginAssertionOptionsTokenable> _assertionOptionsDataProtector;
|
|
private readonly IGetWebAuthnLoginCredentialAssertionOptionsCommand _getWebAuthnLoginCredentialAssertionOptionsCommand;
|
|
private readonly ISendVerificationEmailForRegistrationCommand _sendVerificationEmailForRegistrationCommand;
|
|
private readonly IReferenceEventService _referenceEventService;
|
|
private readonly IFeatureService _featureService;
|
|
|
|
public AccountsController(
|
|
ICurrentContext currentContext,
|
|
ILogger<AccountsController> logger,
|
|
IUserRepository userRepository,
|
|
IRegisterUserCommand registerUserCommand,
|
|
ICaptchaValidationService captchaValidationService,
|
|
IDataProtectorTokenFactory<WebAuthnLoginAssertionOptionsTokenable> assertionOptionsDataProtector,
|
|
IGetWebAuthnLoginCredentialAssertionOptionsCommand getWebAuthnLoginCredentialAssertionOptionsCommand,
|
|
ISendVerificationEmailForRegistrationCommand sendVerificationEmailForRegistrationCommand,
|
|
IReferenceEventService referenceEventService,
|
|
IFeatureService featureService
|
|
)
|
|
{
|
|
_currentContext = currentContext;
|
|
_logger = logger;
|
|
_userRepository = userRepository;
|
|
_registerUserCommand = registerUserCommand;
|
|
_captchaValidationService = captchaValidationService;
|
|
_assertionOptionsDataProtector = assertionOptionsDataProtector;
|
|
_getWebAuthnLoginCredentialAssertionOptionsCommand = getWebAuthnLoginCredentialAssertionOptionsCommand;
|
|
_sendVerificationEmailForRegistrationCommand = sendVerificationEmailForRegistrationCommand;
|
|
_referenceEventService = referenceEventService;
|
|
_featureService = featureService;
|
|
}
|
|
|
|
[HttpPost("register")]
|
|
[CaptchaProtected]
|
|
public async Task<RegisterResponseModel> PostRegister([FromBody] RegisterRequestModel model)
|
|
{
|
|
var user = model.ToUser();
|
|
var identityResult = await _registerUserCommand.RegisterUserWithOptionalOrgInvite(user, model.MasterPasswordHash,
|
|
model.Token, model.OrganizationUserId);
|
|
// delaysEnabled false is only for the new registration with email verification process
|
|
return await ProcessRegistrationResult(identityResult, user, delaysEnabled: true);
|
|
}
|
|
|
|
[RequireFeature(FeatureFlagKeys.EmailVerification)]
|
|
[HttpPost("register/send-verification-email")]
|
|
public async Task<IActionResult> PostRegisterSendVerificationEmail([FromBody] RegisterSendVerificationEmailRequestModel model)
|
|
{
|
|
var token = await _sendVerificationEmailForRegistrationCommand.Run(model.Email, model.Name,
|
|
model.ReceiveMarketingEmails);
|
|
|
|
var refEvent = new ReferenceEvent
|
|
{
|
|
Type = ReferenceEventType.SignupEmailSubmit,
|
|
ClientId = _currentContext.ClientId,
|
|
ClientVersion = _currentContext.ClientVersion,
|
|
Source = ReferenceEventSource.RegistrationStart
|
|
};
|
|
await _referenceEventService.RaiseEventAsync(refEvent);
|
|
|
|
if (token != null)
|
|
{
|
|
return Ok(token);
|
|
}
|
|
|
|
return NoContent();
|
|
}
|
|
|
|
[RequireFeature(FeatureFlagKeys.EmailVerification)]
|
|
[HttpPost("register/finish")]
|
|
public async Task<RegisterResponseModel> PostRegisterFinish([FromBody] RegisterFinishRequestModel model)
|
|
{
|
|
var user = model.ToUser();
|
|
|
|
// Users will either have an org invite token or an email verification token - not both.
|
|
|
|
IdentityResult identityResult = null;
|
|
var delaysEnabled = !_featureService.IsEnabled(FeatureFlagKeys.EmailVerificationDisableTimingDelays);
|
|
|
|
if (!string.IsNullOrEmpty(model.OrgInviteToken) && model.OrganizationUserId.HasValue)
|
|
{
|
|
identityResult = await _registerUserCommand.RegisterUserWithOptionalOrgInvite(user, model.MasterPasswordHash,
|
|
model.OrgInviteToken, model.OrganizationUserId);
|
|
|
|
return await ProcessRegistrationResult(identityResult, user, delaysEnabled);
|
|
}
|
|
|
|
identityResult = await _registerUserCommand.RegisterUserViaEmailVerificationToken(user, model.MasterPasswordHash, model.EmailVerificationToken);
|
|
|
|
return await ProcessRegistrationResult(identityResult, user, delaysEnabled);
|
|
}
|
|
|
|
private async Task<RegisterResponseModel> ProcessRegistrationResult(IdentityResult result, User user, bool delaysEnabled)
|
|
{
|
|
if (result.Succeeded)
|
|
{
|
|
var captchaBypassToken = _captchaValidationService.GenerateCaptchaBypassToken(user);
|
|
return new RegisterResponseModel(captchaBypassToken);
|
|
}
|
|
|
|
foreach (var error in result.Errors.Where(e => e.Code != "DuplicateUserName"))
|
|
{
|
|
ModelState.AddModelError(string.Empty, error.Description);
|
|
}
|
|
|
|
if (delaysEnabled)
|
|
{
|
|
await Task.Delay(Random.Shared.Next(100, 130));
|
|
}
|
|
throw new BadRequestException(ModelState);
|
|
}
|
|
|
|
// Moved from API, If you modify this endpoint, please update API as well. Self hosted installs still use the API endpoints.
|
|
[HttpPost("prelogin")]
|
|
public async Task<PreloginResponseModel> PostPrelogin([FromBody] PreloginRequestModel model)
|
|
{
|
|
var kdfInformation = await _userRepository.GetKdfInformationByEmailAsync(model.Email);
|
|
if (kdfInformation == null)
|
|
{
|
|
kdfInformation = new UserKdfInformation
|
|
{
|
|
Kdf = KdfType.PBKDF2_SHA256,
|
|
KdfIterations = AuthConstants.PBKDF2_ITERATIONS.Default,
|
|
};
|
|
}
|
|
return new PreloginResponseModel(kdfInformation);
|
|
}
|
|
|
|
[HttpGet("webauthn/assertion-options")]
|
|
public WebAuthnLoginAssertionOptionsResponseModel GetWebAuthnLoginAssertionOptions()
|
|
{
|
|
var options = _getWebAuthnLoginCredentialAssertionOptionsCommand.GetWebAuthnLoginCredentialAssertionOptions();
|
|
|
|
var tokenable = new WebAuthnLoginAssertionOptionsTokenable(WebAuthnLoginAssertionOptionsScope.Authentication, options);
|
|
var token = _assertionOptionsDataProtector.Protect(tokenable);
|
|
|
|
return new WebAuthnLoginAssertionOptionsResponseModel
|
|
{
|
|
Options = options,
|
|
Token = token
|
|
};
|
|
}
|
|
}
|