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.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.Mvc; namespace Bit.Identity.Controllers; [Route("accounts")] [ExceptionHandlerFilter] public class AccountsController : Controller { private readonly ICurrentContext _currentContext; private readonly ILogger _logger; private readonly IUserRepository _userRepository; private readonly IUserService _userService; private readonly ICaptchaValidationService _captchaValidationService; private readonly IDataProtectorTokenFactory _assertionOptionsDataProtector; private readonly IGetWebAuthnLoginCredentialAssertionOptionsCommand _getWebAuthnLoginCredentialAssertionOptionsCommand; private readonly ISendVerificationEmailForRegistrationCommand _sendVerificationEmailForRegistrationCommand; private readonly IReferenceEventService _referenceEventService; public AccountsController( ICurrentContext currentContext, ILogger logger, IUserRepository userRepository, IUserService userService, ICaptchaValidationService captchaValidationService, IDataProtectorTokenFactory assertionOptionsDataProtector, IGetWebAuthnLoginCredentialAssertionOptionsCommand getWebAuthnLoginCredentialAssertionOptionsCommand, ISendVerificationEmailForRegistrationCommand sendVerificationEmailForRegistrationCommand, IReferenceEventService referenceEventService ) { _currentContext = currentContext; _logger = logger; _userRepository = userRepository; _userService = userService; _captchaValidationService = captchaValidationService; _assertionOptionsDataProtector = assertionOptionsDataProtector; _getWebAuthnLoginCredentialAssertionOptionsCommand = getWebAuthnLoginCredentialAssertionOptionsCommand; _sendVerificationEmailForRegistrationCommand = sendVerificationEmailForRegistrationCommand; _referenceEventService = referenceEventService; } [HttpPost("register")] [CaptchaProtected] public async Task PostRegister([FromBody] RegisterRequestModel model) { var user = model.ToUser(); var result = await _userService.RegisterUserAsync(user, model.MasterPasswordHash, model.Token, model.OrganizationUserId); 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); } await Task.Delay(2000); throw new BadRequestException(ModelState); } [RequireFeature(FeatureFlagKeys.EmailVerification)] [HttpPost("register/send-verification-email")] public async Task 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(); } // 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 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 }; } }