using System; using System.Threading.Tasks; using Microsoft.AspNet.Authorization; using Microsoft.AspNet.DataProtection; using Microsoft.AspNet.Mvc; using Bit.Api.Models; using Bit.Core.Exceptions; using Bit.Core.Services; using Microsoft.AspNet.Identity; using Bit.Core.Domains; using Bit.Core.Enums; using Bit.Core; using System.Security.Claims; using System.Linq; namespace Bit.Api.Controllers { [Route("accounts")] [Authorize("Application")] public class AccountsController : Controller { private readonly IUserService _userService; private readonly ICipherService _cipherService; private readonly UserManager _userManager; private readonly CurrentContext _currentContext; public AccountsController( IUserService userService, ICipherService cipherService, UserManager userManager, CurrentContext currentContext) { _userService = userService; _cipherService = cipherService; _userManager = userManager; _currentContext = currentContext; } [HttpPost("register-token")] [AllowAnonymous] public async Task PostRegisterToken([FromBody]RegisterTokenRequestModel model) { await _userService.InitiateRegistrationAsync(model.Email); } [HttpPost("register")] [AllowAnonymous] public async Task PostRegister([FromBody]RegisterRequestModel model) { var result = await _userService.RegisterUserAsync(model.Token, model.ToUser(), model.MasterPasswordHash); if(result.Succeeded) { return; } foreach(var error in result.Errors) { ModelState.AddModelError(string.Empty, error.Description); } await Task.Delay(2000); throw new BadRequestException(ModelState); } [HttpPost("password-hint")] [AllowAnonymous] public async Task PostPasswordHint([FromBody]PasswordHintRequestModel model) { await _userService.SendMasterPasswordHintAsync(model.Email); } [HttpPost("email-token")] public async Task PostEmailToken([FromBody]EmailTokenRequestModel model) { if(!await _userManager.CheckPasswordAsync(_currentContext.User, model.MasterPasswordHash)) { await Task.Delay(2000); throw new BadRequestException("MasterPasswordHash", "Invalid password."); } await _userService.InitiateEmailChangeAsync(_currentContext.User, model.NewEmail); } [HttpPut("email")] public async Task PutEmail([FromBody]EmailRequestModel model) { // NOTE: It is assumed that the eventual repository call will make sure the updated // ciphers belong to user making this call. Therefore, no check is done here. var ciphers = CipherRequestModel.ToDynamicCiphers(model.Ciphers, User.GetUserId()); var result = await _userService.ChangeEmailAsync( _currentContext.User, model.MasterPasswordHash, model.NewEmail, model.NewMasterPasswordHash, model.Token, ciphers); if(result.Succeeded) { return; } foreach(var error in result.Errors) { ModelState.AddModelError(string.Empty, error.Description); } await Task.Delay(2000); throw new BadRequestException(ModelState); } [HttpPut("password")] public async Task PutPassword([FromBody]PasswordRequestModel model) { // NOTE: It is assumed that the eventual repository call will make sure the updated // ciphers belong to user making this call. Therefore, no check is done here. var ciphers = CipherRequestModel.ToDynamicCiphers(model.Ciphers, User.GetUserId()); var result = await _userService.ChangePasswordAsync( _currentContext.User, model.MasterPasswordHash, model.NewMasterPasswordHash, ciphers); if(result.Succeeded) { return; } foreach(var error in result.Errors) { ModelState.AddModelError(string.Empty, error.Description); } await Task.Delay(2000); throw new BadRequestException(ModelState); } [HttpPut("security-stamp")] public async Task PutSecurityStamp([FromBody]SecurityStampRequestModel model) { var result = await _userService.RefreshSecurityStampAsync(_currentContext.User, model.MasterPasswordHash); if(result.Succeeded) { return; } foreach(var error in result.Errors) { ModelState.AddModelError(string.Empty, error.Description); } await Task.Delay(2000); throw new BadRequestException(ModelState); } [HttpGet("profile")] public Task GetProfile() { var response = new ProfileResponseModel(_currentContext.User); return Task.FromResult(response); } [HttpPut("profile")] public async Task PutProfile([FromBody]UpdateProfileRequestModel model) { await _userService.SaveUserAsync(model.ToUser(_currentContext.User)); var response = new ProfileResponseModel(_currentContext.User); return response; } [HttpGet("two-factor")] public async Task GetTwoFactor(string masterPasswordHash, TwoFactorProvider provider) { var user = _currentContext.User; if(!await _userManager.CheckPasswordAsync(user, masterPasswordHash)) { await Task.Delay(2000); throw new BadRequestException("MasterPasswordHash", "Invalid password."); } await _userService.GetTwoFactorAsync(user, provider); var response = new TwoFactorResponseModel(user); return response; } [HttpPut("two-factor")] public async Task PutTwoFactor([FromBody]UpdateTwoFactorRequestModel model) { var user = _currentContext.User; if(!await _userManager.CheckPasswordAsync(user, model.MasterPasswordHash)) { await Task.Delay(2000); throw new BadRequestException("MasterPasswordHash", "Invalid password."); } if(model.Enabled.Value && !await _userManager.VerifyTwoFactorTokenAsync(user, "Authenticator", model.Token)) { await Task.Delay(2000); throw new BadRequestException("Token", "Invalid token."); } user.TwoFactorEnabled = model.Enabled.Value; await _userService.SaveUserAsync(user); var response = new TwoFactorResponseModel(user); return response; } [HttpPost("import")] public async Task PostImport([FromBody]ImportRequestModel model) { await _cipherService.ImportCiphersAsync( model.Folders.Select(f => f.ToFolder(User.GetUserId())).ToList(), model.Sites.Select(s => s.ToSite(User.GetUserId())).ToList(), model.SiteRelationships); } } }