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);
+ }
}
}