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

[PM-17709] Send New Device Login email for all new devices (#5340)

* Send New Device Login email regardless of New Device Verification

* Adjusted tests

* Linting

* Clarified test names.
This commit is contained in:
Todd Martin 2025-01-31 10:46:09 -05:00 committed by GitHub
parent d239170c1c
commit e43a8011f1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 21 additions and 25 deletions

View File

@ -85,28 +85,17 @@ public class DeviceValidator(
} }
} }
// At this point we have established either new device verification is not required or the NewDeviceOtp is valid // At this point we have established either new device verification is not required or the NewDeviceOtp is valid,
// so we save the device to the database and proceed with authentication
requestDevice.UserId = context.User.Id; requestDevice.UserId = context.User.Id;
await _deviceService.SaveAsync(requestDevice); await _deviceService.SaveAsync(requestDevice);
context.Device = requestDevice; context.Device = requestDevice;
// backwards compatibility -- If NewDeviceVerification not enabled send the new login emails if (!_globalSettings.DisableEmailNewDevice)
// PM-13340: removal Task; remove entire if block emails should no longer be sent
if (!_featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification))
{ {
// This ensures the user doesn't receive a "new device" email on the first login await SendNewDeviceLoginEmail(context.User, requestDevice);
var now = DateTime.UtcNow;
if (now - context.User.CreationDate > TimeSpan.FromMinutes(10))
{
var deviceType = requestDevice.Type.GetType().GetMember(requestDevice.Type.ToString())
.FirstOrDefault()?.GetCustomAttribute<DisplayAttribute>()?.GetName();
if (!_globalSettings.DisableEmailNewDevice)
{
await _mailService.SendNewDeviceLoggedInEmail(context.User.Email, deviceType, now,
_currentContext.IpAddress);
}
}
} }
return true; return true;
} }
@ -174,6 +163,19 @@ public class DeviceValidator(
return DeviceValidationResultType.NewDeviceVerificationRequired; return DeviceValidationResultType.NewDeviceVerificationRequired;
} }
private async Task SendNewDeviceLoginEmail(User user, Device requestDevice)
{
// Ensure that the user doesn't receive a "new device" email on the first login
var now = DateTime.UtcNow;
if (now - user.CreationDate > TimeSpan.FromMinutes(10))
{
var deviceType = requestDevice.Type.GetType().GetMember(requestDevice.Type.ToString())
.FirstOrDefault()?.GetCustomAttribute<DisplayAttribute>()?.GetName();
await _mailService.SendNewDeviceLoggedInEmail(user.Email, deviceType, now,
_currentContext.IpAddress);
}
}
public async Task<Device> GetKnownDeviceAsync(User user, Device device) public async Task<Device> GetKnownDeviceAsync(User user, Device device)
{ {
if (user == null || device == null) if (user == null || device == null)

View File

@ -227,7 +227,7 @@ public class DeviceValidatorTests
} }
[Theory, BitAutoData] [Theory, BitAutoData]
public async void ValidateRequestDeviceAsync_NewDeviceVerificationFeatureFlagFalse_SendsEmail_ReturnsTrue( public async void ValidateRequestDeviceAsync_ExistingUserNewDeviceLogin_SendNewDeviceLoginEmail_ReturnsTrue(
CustomValidatorRequestContext context, CustomValidatorRequestContext context,
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request) [AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
{ {
@ -237,8 +237,6 @@ public class DeviceValidatorTests
_globalSettings.DisableEmailNewDevice = false; _globalSettings.DisableEmailNewDevice = false;
_deviceRepository.GetByIdentifierAsync(context.Device.Identifier, context.User.Id) _deviceRepository.GetByIdentifierAsync(context.Device.Identifier, context.User.Id)
.Returns(null as Device); .Returns(null as Device);
_featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification)
.Returns(false);
// set user creation to more than 10 minutes ago // set user creation to more than 10 minutes ago
context.User.CreationDate = DateTime.UtcNow - TimeSpan.FromMinutes(11); context.User.CreationDate = DateTime.UtcNow - TimeSpan.FromMinutes(11);
@ -253,7 +251,7 @@ public class DeviceValidatorTests
} }
[Theory, BitAutoData] [Theory, BitAutoData]
public async void ValidateRequestDeviceAsync_NewDeviceVerificationFeatureFlagFalse_NewUser_DoesNotSendEmail_ReturnsTrue( public async void ValidateRequestDeviceAsync_NewUserNewDeviceLogin_DoesNotSendNewDeviceLoginEmail_ReturnsTrue(
CustomValidatorRequestContext context, CustomValidatorRequestContext context,
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request) [AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
{ {
@ -263,8 +261,6 @@ public class DeviceValidatorTests
_globalSettings.DisableEmailNewDevice = false; _globalSettings.DisableEmailNewDevice = false;
_deviceRepository.GetByIdentifierAsync(context.Device.Identifier, context.User.Id) _deviceRepository.GetByIdentifierAsync(context.Device.Identifier, context.User.Id)
.Returns(null as Device); .Returns(null as Device);
_featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification)
.Returns(false);
// set user creation to less than 10 minutes ago // set user creation to less than 10 minutes ago
context.User.CreationDate = DateTime.UtcNow - TimeSpan.FromMinutes(9); context.User.CreationDate = DateTime.UtcNow - TimeSpan.FromMinutes(9);
@ -279,7 +275,7 @@ public class DeviceValidatorTests
} }
[Theory, BitAutoData] [Theory, BitAutoData]
public async void ValidateRequestDeviceAsync_NewDeviceVerificationFeatureFlagFalse_DisableEmailTrue_DoesNotSendEmail_ReturnsTrue( public async void ValidateRequestDeviceAsynce_DisableNewDeviceLoginEmailTrue_DoesNotSendNewDeviceEmail_ReturnsTrue(
CustomValidatorRequestContext context, CustomValidatorRequestContext context,
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request) [AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
{ {
@ -289,8 +285,6 @@ public class DeviceValidatorTests
_globalSettings.DisableEmailNewDevice = true; _globalSettings.DisableEmailNewDevice = true;
_deviceRepository.GetByIdentifierAsync(context.Device.Identifier, context.User.Id) _deviceRepository.GetByIdentifierAsync(context.Device.Identifier, context.User.Id)
.Returns(null as Device); .Returns(null as Device);
_featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification)
.Returns(false);
// Act // Act
var result = await _sut.ValidateRequestDeviceAsync(request, context); var result = await _sut.ValidateRequestDeviceAsync(request, context);