1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-06 13:38:13 -05:00

new device logged in email notification

This commit is contained in:
Kyle Spearrin 2019-01-24 22:37:49 -05:00
parent 43967ebbc1
commit b19628c6f8
9 changed files with 110 additions and 1 deletions

View File

@ -11,6 +11,11 @@
<EmbeddedResource Include="MailTemplates\Handlebars\**\*.hbs" /> <EmbeddedResource Include="MailTemplates\Handlebars\**\*.hbs" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Remove="MailTemplates\Handlebars\NewDeviceLoggedIn.html.hbs" />
<None Remove="MailTemplates\Handlebars\NewDeviceLoggedIn.text.hbs" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Handlebars.Net" Version="1.9.5" /> <PackageReference Include="Handlebars.Net" Version="1.9.5" />
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.6.0" /> <PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.6.0" />

View File

@ -1,27 +1,50 @@
namespace Bit.Core.Enums using System.ComponentModel.DataAnnotations;
namespace Bit.Core.Enums
{ {
public enum DeviceType : byte public enum DeviceType : byte
{ {
[Display(Name = "Android")]
Android = 0, Android = 0,
[Display(Name = "iOS")]
iOS = 1, iOS = 1,
[Display(Name = "Chrome Extension")]
ChromeExtension = 2, ChromeExtension = 2,
[Display(Name = "Firefox Extension")]
FirefoxExtension = 3, FirefoxExtension = 3,
[Display(Name = "Opera Extension")]
OperaExtension = 4, OperaExtension = 4,
[Display(Name = "Edge Extension")]
EdgeExtension = 5, EdgeExtension = 5,
[Display(Name = "Windows")]
WindowsDesktop = 6, WindowsDesktop = 6,
[Display(Name = "macOS")]
MacOsDesktop = 7, MacOsDesktop = 7,
[Display(Name = "Linux")]
LinuxDesktop = 8, LinuxDesktop = 8,
[Display(Name = "Chrome")]
ChromeBrowser = 9, ChromeBrowser = 9,
[Display(Name = "Firefox")]
FirefoxBrowser = 10, FirefoxBrowser = 10,
[Display(Name = "Opera")]
OperaBrowser = 11, OperaBrowser = 11,
[Display(Name = "Edge")]
EdgeBrowser = 12, EdgeBrowser = 12,
[Display(Name = "Internet Explorer")]
IEBrowser = 13, IEBrowser = 13,
[Display(Name = "Unknown Browser")]
UnknownBrowser = 14, UnknownBrowser = 14,
[Display(Name = "Android")]
AndroidAmazon = 15, AndroidAmazon = 15,
[Display(Name = "UWP")]
UWP = 16, UWP = 16,
[Display(Name = "Safari")]
SafariBrowser = 17, SafariBrowser = 17,
[Display(Name = "Vivaldi")]
VivaldiBrowser = 18, VivaldiBrowser = 18,
[Display(Name = "Vivaldi Extension")]
VivaldiExtension = 19, VivaldiExtension = 19,
[Display(Name = "Safari Extension")]
SafariExtension = 20 SafariExtension = 20
} }
} }

View File

@ -15,6 +15,8 @@ using Bit.Core.Models;
using Bit.Core.Identity; using Bit.Core.Identity;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
namespace Bit.Core.IdentityServer namespace Bit.Core.IdentityServer
{ {
@ -29,6 +31,7 @@ namespace Bit.Core.IdentityServer
private readonly IOrganizationRepository _organizationRepository; private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IApplicationCacheService _applicationCacheService; private readonly IApplicationCacheService _applicationCacheService;
private readonly IMailService _mailService;
private readonly CurrentContext _currentContext; private readonly CurrentContext _currentContext;
public ResourceOwnerPasswordValidator( public ResourceOwnerPasswordValidator(
@ -41,6 +44,7 @@ namespace Bit.Core.IdentityServer
IOrganizationRepository organizationRepository, IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository, IOrganizationUserRepository organizationUserRepository,
IApplicationCacheService applicationCacheService, IApplicationCacheService applicationCacheService,
IMailService mailService,
CurrentContext currentContext) CurrentContext currentContext)
{ {
_userManager = userManager; _userManager = userManager;
@ -52,6 +56,7 @@ namespace Bit.Core.IdentityServer
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository; _organizationUserRepository = organizationUserRepository;
_applicationCacheService = applicationCacheService; _applicationCacheService = applicationCacheService;
_mailService = mailService;
_currentContext = currentContext; _currentContext = currentContext;
} }
@ -373,6 +378,16 @@ namespace Bit.Core.IdentityServer
{ {
device.UserId = user.Id; device.UserId = user.Id;
await _deviceService.SaveAsync(device); await _deviceService.SaveAsync(device);
var now = DateTime.UtcNow;
if(now - user.CreationDate > TimeSpan.FromMinutes(10))
{
var deviceType = device.Type.GetType().GetMember(device.Type.ToString())
.FirstOrDefault()?.GetCustomAttribute<DisplayAttribute>()?.GetName();
await _mailService.SendNewDeviceLoggedInEmail(user.Email, deviceType, now,
_currentContext.IpAddress);
}
return device; return device;
} }

View File

@ -0,0 +1,21 @@
{{#>FullHtmlLayout}}
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none;" valign="top">
Your Bitwarden account was just logged into from a new device.
</td>
</tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none;" valign="top">
<b style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">Date:</b> {{TheDate}} at {{TheTime}} {{TimeZone}}<br style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;" />
<b style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">IP Address:</b> {{IpAddress}}<br style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;" />
<b style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">Device Type:</b> {{DeviceType}}
</td>
</tr>
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block last" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0; -webkit-text-size-adjust: none;" valign="top">
You can deauthorize all devices that have access to your account from the <a target="_blank" clicktracking=off href="{{{WebVaultUrl}}}" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #3c8dbc; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; text-decoration: underline;">web vault</a> under Settings &rarr; My Account &rarr; Deauthorize Sessions.
</td>
</tr>
</table>
{{/FullHtmlLayout}}

View File

@ -0,0 +1,10 @@
{{#>BasicTextLayout}}
Your Bitwarden account was just logged into from a new device.
Date: {{TheDate}} at {{TheTime}} {{TimeZone}}
IP Address: {{IpAddress}}
Device Type: {{DeviceType}}
You can deauthorize all devices that have access to your account from the
web vault under Settings > My Account > Deauthorize Sessions.
{{/BasicTextLayout}}

View File

@ -0,0 +1,11 @@
namespace Bit.Core.Models.Mail
{
public class NewDeviceLoggedInModel : BaseMailModel
{
public string TheDate { get; set; }
public string TheTime { get; set; }
public string TimeZone { get; set; }
public string IpAddress { get; set; }
public string DeviceType { get; set; }
}
}

View File

@ -20,5 +20,6 @@ namespace Bit.Core.Services
Task SendOrganizationConfirmedEmailAsync(string organizationName, string email); Task SendOrganizationConfirmedEmailAsync(string organizationName, string email);
Task SendPasswordlessSignInAsync(string returnUrl, string token, string email); Task SendPasswordlessSignInAsync(string returnUrl, string token, string email);
Task SendInvoiceUpcomingAsync(string email, decimal amount, DateTime dueDate, List<string> items, bool mentionInvoices); Task SendInvoiceUpcomingAsync(string email, decimal amount, DateTime dueDate, List<string> items, bool mentionInvoices);
Task SendNewDeviceLoggedInEmail(string email, string deviceType, DateTime timestamp, string ip);
} }
} }

View File

@ -236,6 +236,24 @@ namespace Bit.Core.Services
await _mailDeliveryService.SendEmailAsync(message); await _mailDeliveryService.SendEmailAsync(message);
} }
public async Task SendNewDeviceLoggedInEmail(string email, string deviceType, DateTime timestamp, string ip)
{
var message = CreateDefaultMessage($"New Device Logged In From {deviceType}", email);
var model = new NewDeviceLoggedInModel
{
WebVaultUrl = _globalSettings.BaseServiceUri.VaultWithHash,
SiteName = _globalSettings.SiteName,
DeviceType = deviceType,
TheDate = timestamp.ToLongDateString(),
TheTime = timestamp.ToShortTimeString(),
TimeZone = "UTC",
IpAddress = ip
};
await AddMessageContentAsync(message, "NewDeviceLoggedIn", model);
message.MetaData.Add("SendGridCategories", new List<string> { "NewDeviceLoggedIn" });
await _mailDeliveryService.SendEmailAsync(message);
}
private MailMessage CreateDefaultMessage(string subject, string toEmail) private MailMessage CreateDefaultMessage(string subject, string toEmail)
{ {
return CreateDefaultMessage(subject, new List<string> { toEmail }); return CreateDefaultMessage(subject, new List<string> { toEmail });

View File

@ -72,5 +72,10 @@ namespace Bit.Core.Services
{ {
return Task.FromResult(0); return Task.FromResult(0);
} }
public Task SendNewDeviceLoggedInEmail(string email, string deviceType, DateTime timestamp, string ip)
{
return Task.FromResult(0);
}
} }
} }