mirror of
https://github.com/bitwarden/server
synced 2026-02-17 09:59:14 +00:00
feat(emergency-access) [PM-29584] Create Email for Emergency Access Removal (#6793)
* feat(emergency-access) [PM-29584]: Add email template. * refactor(emergency-access) [PM-29584]: Move Emergency Access to Auth/UserFeatures. * refactor(emergency-access) [PM-29584]: Move EmergencyAccess tests to UserFeatures space. * feat(emergency-access) [PM-29584]: Add compiled EmergencyAccess templates. * test(emergency-access) [PM-29584]: Add mailer-specific tests. * refactor(emergency-access) [PM-29584]: Move mail to UserFeatures area. * feat(emergency-access) [PM-29584]: Update link for help pages, not web vault. * test(emergency-access) [PM-29584]: Update mail tests for new URL and single responsibility. * refactor(emergency-access) [PM-29584]: Add comments for added test.
This commit is contained in:
@@ -7,7 +7,7 @@ using Bit.Api.Auth.Models.Request;
|
||||
using Bit.Api.Auth.Models.Response;
|
||||
using Bit.Api.Models.Response;
|
||||
using Bit.Api.Vault.Models.Response;
|
||||
using Bit.Core.Auth.Services;
|
||||
using Bit.Core.Auth.UserFeatures.EmergencyAccess;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// FIXME: Update this file to be null safe and then delete the line below
|
||||
#nullable disable
|
||||
|
||||
using Bit.Core.Auth.Services;
|
||||
using Bit.Core.Auth.UserFeatures.EmergencyAccess;
|
||||
using Bit.Core.Jobs;
|
||||
using Quartz;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// FIXME: Update this file to be null safe and then delete the line below
|
||||
#nullable disable
|
||||
|
||||
using Bit.Core.Auth.Services;
|
||||
using Bit.Core.Auth.UserFeatures.EmergencyAccess;
|
||||
using Bit.Core.Jobs;
|
||||
using Quartz;
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
@@ -19,7 +18,7 @@ using Bit.Core.Vault.Models.Data;
|
||||
using Bit.Core.Vault.Repositories;
|
||||
using Bit.Core.Vault.Services;
|
||||
|
||||
namespace Bit.Core.Auth.Services;
|
||||
namespace Bit.Core.Auth.UserFeatures.EmergencyAccess;
|
||||
|
||||
public class EmergencyAccessService : IEmergencyAccessService
|
||||
{
|
||||
@@ -61,7 +60,7 @@ public class EmergencyAccessService : IEmergencyAccessService
|
||||
_removeOrganizationUserCommand = removeOrganizationUserCommand;
|
||||
}
|
||||
|
||||
public async Task<EmergencyAccess> InviteAsync(User grantorUser, string emergencyContactEmail, EmergencyAccessType accessType, int waitTime)
|
||||
public async Task<Entities.EmergencyAccess> InviteAsync(User grantorUser, string emergencyContactEmail, EmergencyAccessType accessType, int waitTime)
|
||||
{
|
||||
if (!await _userService.CanAccessPremium(grantorUser))
|
||||
{
|
||||
@@ -73,7 +72,7 @@ public class EmergencyAccessService : IEmergencyAccessService
|
||||
throw new BadRequestException("You cannot use Emergency Access Takeover because you are using Key Connector.");
|
||||
}
|
||||
|
||||
var emergencyAccess = new EmergencyAccess
|
||||
var emergencyAccess = new Entities.EmergencyAccess
|
||||
{
|
||||
GrantorId = grantorUser.Id,
|
||||
Email = emergencyContactEmail.ToLowerInvariant(),
|
||||
@@ -113,7 +112,7 @@ public class EmergencyAccessService : IEmergencyAccessService
|
||||
await SendInviteAsync(emergencyAccess, NameOrEmail(grantorUser));
|
||||
}
|
||||
|
||||
public async Task<EmergencyAccess> AcceptUserAsync(Guid emergencyAccessId, User granteeUser, string token, IUserService userService)
|
||||
public async Task<Entities.EmergencyAccess> AcceptUserAsync(Guid emergencyAccessId, User granteeUser, string token, IUserService userService)
|
||||
{
|
||||
var emergencyAccess = await _emergencyAccessRepository.GetByIdAsync(emergencyAccessId);
|
||||
if (emergencyAccess == null)
|
||||
@@ -175,7 +174,7 @@ public class EmergencyAccessService : IEmergencyAccessService
|
||||
await _emergencyAccessRepository.DeleteAsync(emergencyAccess);
|
||||
}
|
||||
|
||||
public async Task<EmergencyAccess> ConfirmUserAsync(Guid emergencyAccessId, string key, Guid grantorId)
|
||||
public async Task<Entities.EmergencyAccess> ConfirmUserAsync(Guid emergencyAccessId, string key, Guid grantorId)
|
||||
{
|
||||
var emergencyAccess = await _emergencyAccessRepository.GetByIdAsync(emergencyAccessId);
|
||||
if (emergencyAccess == null || emergencyAccess.Status != EmergencyAccessStatusType.Accepted ||
|
||||
@@ -201,7 +200,7 @@ public class EmergencyAccessService : IEmergencyAccessService
|
||||
return emergencyAccess;
|
||||
}
|
||||
|
||||
public async Task SaveAsync(EmergencyAccess emergencyAccess, User grantorUser)
|
||||
public async Task SaveAsync(Entities.EmergencyAccess emergencyAccess, User grantorUser)
|
||||
{
|
||||
if (!await _userService.CanAccessPremium(grantorUser))
|
||||
{
|
||||
@@ -311,7 +310,7 @@ public class EmergencyAccessService : IEmergencyAccessService
|
||||
}
|
||||
|
||||
// TODO PM-21687: rename this to something like InitiateRecoveryTakeoverAsync
|
||||
public async Task<(EmergencyAccess, User)> TakeoverAsync(Guid emergencyAccessId, User granteeUser)
|
||||
public async Task<(Entities.EmergencyAccess, User)> TakeoverAsync(Guid emergencyAccessId, User granteeUser)
|
||||
{
|
||||
var emergencyAccess = await _emergencyAccessRepository.GetByIdAsync(emergencyAccessId);
|
||||
|
||||
@@ -429,7 +428,7 @@ public class EmergencyAccessService : IEmergencyAccessService
|
||||
return await _cipherService.GetAttachmentDownloadDataAsync(cipher, attachmentId);
|
||||
}
|
||||
|
||||
private async Task SendInviteAsync(EmergencyAccess emergencyAccess, string invitingUsersName)
|
||||
private async Task SendInviteAsync(Entities.EmergencyAccess emergencyAccess, string invitingUsersName)
|
||||
{
|
||||
var token = _dataProtectorTokenizer.Protect(new EmergencyAccessInviteTokenable(emergencyAccess, _globalSettings.OrganizationInviteExpirationHours));
|
||||
await _mailService.SendEmergencyAccessInviteEmailAsync(emergencyAccess, invitingUsersName, token);
|
||||
@@ -449,7 +448,7 @@ public class EmergencyAccessService : IEmergencyAccessService
|
||||
*/
|
||||
//TODO PM-21687: this IsValidRequest() checks the validity based on the granteeUser. There should be a complementary method for the grantorUser
|
||||
private static bool IsValidRequest(
|
||||
EmergencyAccess availableAccess,
|
||||
Entities.EmergencyAccess availableAccess,
|
||||
User requestingUser,
|
||||
EmergencyAccessType requestedAccessType)
|
||||
{
|
||||
@@ -1,5 +1,4 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Entities;
|
||||
@@ -7,7 +6,7 @@ using Bit.Core.Enums;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Vault.Models.Data;
|
||||
|
||||
namespace Bit.Core.Auth.Services;
|
||||
namespace Bit.Core.Auth.UserFeatures.EmergencyAccess;
|
||||
|
||||
public interface IEmergencyAccessService
|
||||
{
|
||||
@@ -20,7 +19,7 @@ public interface IEmergencyAccessService
|
||||
/// <param name="accessType">Type of emergency access allowed to the emergency contact</param>
|
||||
/// <param name="waitTime">The amount of time to pass before the invite is auto confirmed</param>
|
||||
/// <returns>a new Emergency Access object</returns>
|
||||
Task<EmergencyAccess> InviteAsync(User grantorUser, string emergencyContactEmail, EmergencyAccessType accessType, int waitTime);
|
||||
Task<Entities.EmergencyAccess> InviteAsync(User grantorUser, string emergencyContactEmail, EmergencyAccessType accessType, int waitTime);
|
||||
/// <summary>
|
||||
/// Sends an invite to the emergency contact associated with the emergency access id.
|
||||
/// </summary>
|
||||
@@ -37,7 +36,7 @@ public interface IEmergencyAccessService
|
||||
/// <param name="token">the tokenable that was sent via email</param>
|
||||
/// <param name="userService">service dependency</param>
|
||||
/// <returns>void</returns>
|
||||
Task<EmergencyAccess> AcceptUserAsync(Guid emergencyAccessId, User granteeUser, string token, IUserService userService);
|
||||
Task<Entities.EmergencyAccess> AcceptUserAsync(Guid emergencyAccessId, User granteeUser, string token, IUserService userService);
|
||||
/// <summary>
|
||||
/// The creator of the emergency access request can delete the request.
|
||||
/// </summary>
|
||||
@@ -53,7 +52,7 @@ public interface IEmergencyAccessService
|
||||
/// <param name="key">The grantor user key encrypted by the grantee public key; grantee.PubicKey(grantor.User.Key)</param>
|
||||
/// <param name="grantorId">Id of grantor user</param>
|
||||
/// <returns>emergency access object associated with the Id passed in</returns>
|
||||
Task<EmergencyAccess> ConfirmUserAsync(Guid emergencyAccessId, string key, Guid grantorId);
|
||||
Task<Entities.EmergencyAccess> ConfirmUserAsync(Guid emergencyAccessId, string key, Guid grantorId);
|
||||
/// <summary>
|
||||
/// Fetches an emergency access object. The grantor user must own the object being fetched.
|
||||
/// </summary>
|
||||
@@ -67,7 +66,7 @@ public interface IEmergencyAccessService
|
||||
/// <param name="emergencyAccess">emergency access entity being updated</param>
|
||||
/// <param name="grantorUser">grantor user</param>
|
||||
/// <returns>void</returns>
|
||||
Task SaveAsync(EmergencyAccess emergencyAccess, User grantorUser);
|
||||
Task SaveAsync(Entities.EmergencyAccess emergencyAccess, User grantorUser);
|
||||
/// <summary>
|
||||
/// Initiates the recovery process. For either Takeover or view. Will send an email to the Grantor User notifying of the initiation.
|
||||
/// </summary>
|
||||
@@ -107,7 +106,7 @@ public interface IEmergencyAccessService
|
||||
/// <param name="emergencyAccessId">Id of entity being accessed</param>
|
||||
/// <param name="granteeUser">grantee user of the emergency access entity</param>
|
||||
/// <returns>emergency access entity and the grantorUser</returns>
|
||||
Task<(EmergencyAccess, User)> TakeoverAsync(Guid emergencyAccessId, User granteeUser);
|
||||
Task<(Entities.EmergencyAccess, User)> TakeoverAsync(Guid emergencyAccessId, User granteeUser);
|
||||
/// <summary>
|
||||
/// Updates the grantor's password hash and updates the key for the EmergencyAccess entity.
|
||||
/// </summary>
|
||||
@@ -0,0 +1,14 @@
|
||||
using Bit.Core.Platform.Mail.Mailer;
|
||||
|
||||
namespace Bit.Core.Auth.UserFeatures.EmergencyAccess.Mail;
|
||||
|
||||
public class EmergencyAccessRemoveGranteesMailView : BaseMailView
|
||||
{
|
||||
public required IEnumerable<string> RemovedGranteeNames { get; set; }
|
||||
public string EmergencyAccessHelpPageUrl => "https://bitwarden.com/help/emergency-access/";
|
||||
}
|
||||
|
||||
public class EmergencyAccessRemoveGranteesMail : BaseMail<EmergencyAccessRemoveGranteesMailView>
|
||||
{
|
||||
public override string Subject { get; set; } = "Emergency contacts removed";
|
||||
}
|
||||
@@ -0,0 +1,499 @@
|
||||
<!doctype html>
|
||||
<html lang="und" dir="auto" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
<head>
|
||||
<title></title>
|
||||
<!--[if !mso]><!-->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<!--<![endif]-->
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style type="text/css">
|
||||
#outlook a { padding:0; }
|
||||
body { margin:0;padding:0;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%; }
|
||||
table, td { border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt; }
|
||||
img { border:0;height:auto;line-height:100%; outline:none;text-decoration:none;-ms-interpolation-mode:bicubic; }
|
||||
p { display:block;margin:13px 0; }
|
||||
</style>
|
||||
<!--[if mso]>
|
||||
<noscript>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:AllowPNG/>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
</noscript>
|
||||
<![endif]-->
|
||||
<!--[if lte mso 11]>
|
||||
<style type="text/css">
|
||||
.mj-outlook-group-fix { width:100% !important; }
|
||||
</style>
|
||||
<![endif]-->
|
||||
|
||||
|
||||
<style type="text/css">
|
||||
@media only screen and (min-width:480px) {
|
||||
.mj-column-per-70 { width:70% !important; max-width: 70%; }
|
||||
.mj-column-per-30 { width:30% !important; max-width: 30%; }
|
||||
.mj-column-per-100 { width:100% !important; max-width: 100%; }
|
||||
}
|
||||
</style>
|
||||
<style media="screen and (min-width:480px)">
|
||||
.moz-text-html .mj-column-per-70 { width:70% !important; max-width: 70%; }
|
||||
.moz-text-html .mj-column-per-30 { width:30% !important; max-width: 30%; }
|
||||
.moz-text-html .mj-column-per-100 { width:100% !important; max-width: 100%; }
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
|
||||
<style type="text/css">
|
||||
|
||||
@media only screen and (max-width:480px) {
|
||||
.mj-bw-hero-responsive-img {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (max-width:479px) {
|
||||
table.mj-full-width-mobile { width: 100% !important; }
|
||||
td.mj-full-width-mobile { width: auto !important; }
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<style type="text/css">
|
||||
.border-fix > table {
|
||||
border-collapse: separate !important;
|
||||
}
|
||||
.border-fix > table > tbody > tr > td {
|
||||
border-radius: 3px;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body style="word-spacing:normal;background-color:#e6e9ef;">
|
||||
|
||||
|
||||
<div class="border-fix" style="background-color:#e6e9ef;" lang="und" dir="auto">
|
||||
<!-- Blue Header Section -->
|
||||
|
||||
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="border-fix-outlook" role="presentation" style="width:660px;" width="660" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
|
||||
|
||||
<div class="border-fix" style="margin:0px auto;max-width:660px;">
|
||||
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction:ltr;font-size:0px;padding:20px 20px 0px 20px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" width="660px" ><![endif]-->
|
||||
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#175ddc;background-color:#175ddc;width:100%;border-radius:4px 4px 0px 0px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
|
||||
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:620px;" width="620" bgcolor="#175ddc" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
|
||||
|
||||
<div style="margin:0px auto;border-radius:4px 4px 0px 0px;max-width:620px;">
|
||||
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;border-radius:4px 4px 0px 0px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:434px;" ><![endif]-->
|
||||
|
||||
<div class="mj-column-per-70 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width:150px;">
|
||||
|
||||
<img alt src="https://bitwarden.com/images/logo-horizontal-white.png" style="border:0;display:block;outline:none;text-decoration:none;height:30px;width:100%;font-size:16px;" width="150" height="30">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;padding-top:0;padding-bottom:0;word-break:break-word;">
|
||||
|
||||
<div style="font-family:'Helvetica Neue', Helvetica, Arial, sans-serif;font-size:16px;line-height:1;text-align:left;color:#ffffff;"><h1 style="font-weight: normal; font-size: 24px; line-height: 32px">
|
||||
|
||||
</h1></div>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
<!--[if mso | IE]></td><td class="" style="vertical-align:bottom;width:186px;" ><![endif]-->
|
||||
|
||||
<div class="mj-column-per-30 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:bottom;width:100%;">
|
||||
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:bottom;" width="100%">
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<td align="center" class="mj-bw-hero-responsive-img" style="font-size:0px;padding:0px;word-break:break-word;">
|
||||
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width:155px;">
|
||||
|
||||
<img alt src="undefined" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:16px;" width="155" height="auto">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
|
||||
<!-- Main Content -->
|
||||
|
||||
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:660px;" width="660" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
|
||||
|
||||
<div style="margin:0px auto;max-width:660px;">
|
||||
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction:ltr;font-size:0px;padding:0px 20px 0px 20px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" width="660px" ><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:620px;" width="620" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
|
||||
|
||||
<div style="background:#ffffff;background-color:#ffffff;margin:0px auto;max-width:620px;">
|
||||
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#ffffff;background-color:#ffffff;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction:ltr;font-size:0px;padding:0px 10px 0px 10px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
|
||||
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 15px;word-break:break-word;">
|
||||
|
||||
<div style="font-family:'Helvetica Neue', Helvetica, Arial, sans-serif;font-size:16px;line-height:24px;text-align:left;color:#1B2029;">The following emergency contacts have been removed from your account:
|
||||
<ul>
|
||||
{{#each RemovedGranteeNames}}
|
||||
<li>{{this}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
Learn more about <a href="{{EmergencyAccessHelpPageUrl}}">emergency access</a>.</div>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!--[if mso | IE]></td></tr></table></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
|
||||
<!-- Footer -->
|
||||
|
||||
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:660px;" width="660" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
|
||||
|
||||
|
||||
<div style="margin:0px auto;max-width:660px;">
|
||||
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="direction:ltr;font-size:0px;padding:5px 20px 10px 20px;text-align:center;">
|
||||
<!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:620px;" ><![endif]-->
|
||||
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:0;word-break:break-word;">
|
||||
|
||||
|
||||
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" ><tr><td><![endif]-->
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<td style="padding:8px;vertical-align:middle;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
|
||||
<a href="https://x.com/bitwarden" target="_blank">
|
||||
<img alt height="24" src="https://assets.bitwarden.com/email/v1/social-icons-x-twitter.png" style="border-radius:3px;display:block;" width="24">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
<!--[if mso | IE]></td><td><![endif]-->
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<td style="padding:8px;vertical-align:middle;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
|
||||
<a href="https://www.reddit.com/r/Bitwarden/" target="_blank">
|
||||
<img alt height="24" src="https://assets.bitwarden.com/email/v1/social-icons-reddit.png" style="border-radius:3px;display:block;" width="24">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
<!--[if mso | IE]></td><td><![endif]-->
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<td style="padding:8px;vertical-align:middle;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
|
||||
<a href="https://community.bitwarden.com/" target="_blank">
|
||||
<img alt height="24" src="https://assets.bitwarden.com/email/v1/social-icons-discourse.png" style="border-radius:3px;display:block;" width="24">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
<!--[if mso | IE]></td><td><![endif]-->
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<td style="padding:8px;vertical-align:middle;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
|
||||
<a href="https://github.com/bitwarden" target="_blank">
|
||||
<img alt height="24" src="https://assets.bitwarden.com/email/v1/social-icons-github.png" style="border-radius:3px;display:block;" width="24">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
<!--[if mso | IE]></td><td><![endif]-->
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<td style="padding:8px;vertical-align:middle;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
|
||||
<a href="https://www.youtube.com/channel/UCId9a_jQqvJre0_dE2lE_Rw" target="_blank">
|
||||
<img alt height="24" src="https://assets.bitwarden.com/email/v1/social-icons-youtube.png" style="border-radius:3px;display:block;" width="24">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
<!--[if mso | IE]></td><td><![endif]-->
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<td style="padding:8px;vertical-align:middle;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
|
||||
<a href="https://www.linkedin.com/company/bitwarden1/" target="_blank">
|
||||
<img alt height="24" src="https://assets.bitwarden.com/email/v1/social-icons-linkedin.png" style="border-radius:3px;display:block;" width="24">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
<!--[if mso | IE]></td><td><![endif]-->
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<td style="padding:8px;vertical-align:middle;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
|
||||
<a href="https://www.facebook.com/bitwarden/" target="_blank">
|
||||
<img alt height="24" src="https://assets.bitwarden.com/email/v1/social-icons-facebook.png" style="border-radius:3px;display:block;" width="24">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
|
||||
<div style="font-family:'Helvetica Neue', Helvetica, Arial, sans-serif;font-size:12px;line-height:16px;text-align:center;color:#5A6D91;"><p style="margin-bottom: 5px; margin-top: 5px">
|
||||
© 2025 Bitwarden Inc. 1 N. Calle Cesar Chavez, Suite 102, Santa
|
||||
Barbara, CA, USA
|
||||
</p>
|
||||
<p style="margin-top: 5px">
|
||||
Always confirm you are on a trusted Bitwarden domain before logging
|
||||
in:<br>
|
||||
<a href="https://bitwarden.com/" style="text-decoration:none;color:#175ddc; font-weight:400">bitwarden.com</a> |
|
||||
<a href="https://bitwarden.com/help/emails-from-bitwarden/" style="text-decoration:none; color:#175ddc; font-weight:400">Learn why we include this</a>
|
||||
</p></div>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!--[if mso | IE]></td></tr></table><![endif]-->
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
The following emergency contacts have been removed from your account:
|
||||
|
||||
{{#each RemovedGranteeNames}}
|
||||
{{this}}
|
||||
{{/each}}
|
||||
|
||||
Learn more about emergency access at {{EmergencyAccessHelpPageUrl}}
|
||||
@@ -0,0 +1,31 @@
|
||||
<mjml>
|
||||
<mj-head>
|
||||
<mj-include path="../../../../components/head.mjml" />
|
||||
</mj-head>
|
||||
<mj-body css-class="border-fix">
|
||||
<!-- Blue Header Section -->
|
||||
<mj-wrapper css-class="border-fix" padding="20px 20px 0px 20px">
|
||||
<mj-bw-hero title=""/>
|
||||
</mj-wrapper>
|
||||
|
||||
<!-- Main Content -->
|
||||
<mj-wrapper padding="0px 20px 0px 20px">
|
||||
<mj-section background-color="#fff" padding="0px 10px 0px 10px">
|
||||
<mj-column>
|
||||
<mj-text font-size="16px" line-height="24px" padding="10px 15px">
|
||||
The following emergency contacts have been removed from your account:
|
||||
<ul>
|
||||
{{#each RemovedGranteeNames}}
|
||||
<li>{{this}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
Learn more about <a href="{{EmergencyAccessHelpPageUrl}}">emergency access</a>.
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
</mj-wrapper>
|
||||
|
||||
<!-- Footer -->
|
||||
<mj-include path="../../../../components/footer.mjml" />
|
||||
</mj-body>
|
||||
</mjml>
|
||||
@@ -22,6 +22,7 @@ using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Auth.Services;
|
||||
using Bit.Core.Auth.Services.Implementations;
|
||||
using Bit.Core.Auth.UserFeatures;
|
||||
using Bit.Core.Auth.UserFeatures.EmergencyAccess;
|
||||
using Bit.Core.Auth.UserFeatures.PasswordValidation;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Billing.Services.Implementations;
|
||||
|
||||
Reference in New Issue
Block a user