diff --git a/src/Api/Controllers/AccountsController.cs b/src/Api/Controllers/AccountsController.cs index c615410862..b3668e299e 100644 --- a/src/Api/Controllers/AccountsController.cs +++ b/src/Api/Controllers/AccountsController.cs @@ -797,5 +797,28 @@ namespace Bit.Api.Controllers return response; } } + + [HttpPost("update-temp-password")] + public async Task PostUpdateTempPasswordAsync([FromBody]UpdateTempPasswordRequestModel model) + { + var user = await _userService.GetUserByPrincipalAsync(User); + if (user == null) + { + throw new UnauthorizedAccessException(); + } + + var result = await _userService.UpdateTempPasswordAsync(user, model.NewMasterPasswordHash, model.Key); + if (result.Succeeded) + { + return; + } + + foreach (var error in result.Errors) + { + ModelState.AddModelError(string.Empty, error.Description); + } + + throw new BadRequestException(ModelState); + } } } diff --git a/src/Core/Enums/EventType.cs b/src/Core/Enums/EventType.cs index c3882ece0c..6bbcc74f56 100644 --- a/src/Core/Enums/EventType.cs +++ b/src/Core/Enums/EventType.cs @@ -10,6 +10,7 @@ User_FailedLogIn = 1005, User_FailedLogIn2fa = 1006, User_ClientExportedVault = 1007, + User_UpdatedTempPassword = 1008, Cipher_Created = 1100, Cipher_Updated = 1101, diff --git a/src/Core/MailTemplates/Handlebars/UpdatedTempPassword.html.hbs b/src/Core/MailTemplates/Handlebars/UpdatedTempPassword.html.hbs new file mode 100644 index 0000000000..a19a55cec6 --- /dev/null +++ b/src/Core/MailTemplates/Handlebars/UpdatedTempPassword.html.hbs @@ -0,0 +1,9 @@ +{{#>FullHtmlLayout}} + + + + +
+ The temporary master password set by an administrator for {{UserName}} has been changed. If you did not initiate this request, please reach out to your administrator immediately. +
+{{/FullHtmlLayout}} diff --git a/src/Core/MailTemplates/Handlebars/UpdatedTempPassword.text.hbs b/src/Core/MailTemplates/Handlebars/UpdatedTempPassword.text.hbs new file mode 100644 index 0000000000..bad34ed922 --- /dev/null +++ b/src/Core/MailTemplates/Handlebars/UpdatedTempPassword.text.hbs @@ -0,0 +1,3 @@ +{{#>BasicTextLayout}} +The temporary master password set by an administrator for {{UserName}} has been changed. If you did not initiate this request, please reach out to your administrator immediately. +{{/BasicTextLayout}} diff --git a/src/Core/Models/Api/Request/Accounts/UpdateTempPasswordRequestModel.cs b/src/Core/Models/Api/Request/Accounts/UpdateTempPasswordRequestModel.cs new file mode 100644 index 0000000000..0cccf68354 --- /dev/null +++ b/src/Core/Models/Api/Request/Accounts/UpdateTempPasswordRequestModel.cs @@ -0,0 +1,7 @@ +namespace Bit.Core.Models.Api.Request.Accounts +{ + public class UpdateTempPasswordRequestModel : OrganizationUserResetPasswordRequestModel + { + + } +} diff --git a/src/Core/Models/Mail/UpdateTempPasswordViewModel.cs b/src/Core/Models/Mail/UpdateTempPasswordViewModel.cs new file mode 100644 index 0000000000..ed35d3e97b --- /dev/null +++ b/src/Core/Models/Mail/UpdateTempPasswordViewModel.cs @@ -0,0 +1,7 @@ +namespace Bit.Core.Models.Mail +{ + public class UpdateTempPasswordViewModel + { + public string UserName { get; set; } + } +} diff --git a/src/Core/Services/IMailService.cs b/src/Core/Services/IMailService.cs index 94d3478ba2..970d1e07e7 100644 --- a/src/Core/Services/IMailService.cs +++ b/src/Core/Services/IMailService.cs @@ -46,5 +46,6 @@ namespace Bit.Core.Services Task SendProviderInviteEmailAsync(string providerName, ProviderUser providerUser, string token, string email); Task SendProviderConfirmedEmailAsync(string providerName, string email); Task SendProviderUserRemoved(string providerName, string email); + Task SendUpdatedTempPasswordEmailAsync(string email, string userName); } } diff --git a/src/Core/Services/IUserService.cs b/src/Core/Services/IUserService.cs index 9cf5f7450d..80a70e718f 100644 --- a/src/Core/Services/IUserService.cs +++ b/src/Core/Services/IUserService.cs @@ -35,6 +35,7 @@ namespace Bit.Core.Services Task ChangePasswordAsync(User user, string masterPassword, string newMasterPassword, string key); Task SetPasswordAsync(User user, string newMasterPassword, string key, string orgIdentifier = null); Task AdminResetPasswordAsync(OrganizationUserType type, Guid orgId, Guid id, string newMasterPassword, string key); + Task UpdateTempPasswordAsync(User user, string newMasterPassword, string key); Task ChangeKdfAsync(User user, string masterPassword, string newMasterPassword, string key, KdfType kdf, int kdfIterations); Task UpdateKeyAsync(User user, string masterPassword, string key, string privateKey, diff --git a/src/Core/Services/Implementations/HandlebarsMailService.cs b/src/Core/Services/Implementations/HandlebarsMailService.cs index d4e7c581bf..c35f198363 100644 --- a/src/Core/Services/Implementations/HandlebarsMailService.cs +++ b/src/Core/Services/Implementations/HandlebarsMailService.cs @@ -714,5 +714,17 @@ namespace Bit.Core.Services message.Category = "ProviderUserRemoved"; await _mailDeliveryService.SendEmailAsync(message); } + + public async Task SendUpdatedTempPasswordEmailAsync(string email, string userName) + { + var message = CreateDefaultMessage("Master Password Has Been Changed", email); + var model = new UpdateTempPasswordViewModel() + { + UserName = CoreHelpers.SanitizeForEmail(userName) + }; + await AddMessageContentAsync(message, "UpdatedTempPassword", model); + message.Category = "UpdatedTempPassword"; + await _mailDeliveryService.SendEmailAsync(message); + } } } diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index 0222715489..4519aa7c6d 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -690,6 +690,7 @@ namespace Bit.Core.Services user.RevisionDate = user.AccountRevisionDate = DateTime.UtcNow; user.Key = key; + user.ForcePasswordReset = true; await _userRepository.ReplaceAsync(user); await _mailService.SendAdminResetPasswordEmailAsync(user.Email, user.Name ?? user.Email, org.Name); @@ -698,6 +699,31 @@ namespace Bit.Core.Services return IdentityResult.Success; } + + public async Task UpdateTempPasswordAsync(User user, string newMasterPassword, string key) + { + if (!user.ForcePasswordReset) + { + throw new BadRequestException("User does not have a temporary password to update."); + } + + var result = await UpdatePasswordHash(user, newMasterPassword); + if (!result.Succeeded) + { + return result; + } + + user.RevisionDate = user.AccountRevisionDate = DateTime.UtcNow; + user.ForcePasswordReset = false; + user.Key = key; + + await _userRepository.ReplaceAsync(user); + await _mailService.SendUpdatedTempPasswordEmailAsync(user.Email, user.Name ?? user.Email); + await _eventService.LogUserEventAsync(user.Id, EventType.User_UpdatedTempPassword); + await _pushService.PushLogOutAsync(user.Id); + + return IdentityResult.Success; + } public async Task ChangeKdfAsync(User user, string masterPassword, string newMasterPassword, string key, KdfType kdf, int kdfIterations) diff --git a/src/Core/Services/NoopImplementations/NoopMailService.cs b/src/Core/Services/NoopImplementations/NoopMailService.cs index 5ca5997d19..4fa9805318 100644 --- a/src/Core/Services/NoopImplementations/NoopMailService.cs +++ b/src/Core/Services/NoopImplementations/NoopMailService.cs @@ -185,5 +185,10 @@ namespace Bit.Core.Services { return Task.FromResult(0); } + + public Task SendUpdatedTempPasswordEmailAsync(string email, string userName) + { + return Task.FromResult(0); + } } }