mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 15:42:48 -05:00
[PM-15614] Allow Users to opt out of new device verification (#5176)
feat(NewDeviceVerification) : * Created database migration scripts for VerifyDevices column in [dbo].[User]. * Updated DeviceValidator to check if user has opted out of device verification. * Added endpoint to AccountsController.cs to allow editing of new User.VerifyDevices property. * Added tests for new methods and endpoint. * Updating queries to track [dbo].[User].[VerifyDevices]. * Updated DeviceValidator to set `User.EmailVerified` property during the New Device Verification flow.
This commit is contained in:
@ -969,11 +969,28 @@ public class AccountsController : Controller
|
||||
[RequireFeature(FeatureFlagKeys.NewDeviceVerification)]
|
||||
[AllowAnonymous]
|
||||
[HttpPost("resend-new-device-otp")]
|
||||
public async Task ResendNewDeviceOtpAsync([FromBody] UnauthenticatedSecretVerificatioRequestModel request)
|
||||
public async Task ResendNewDeviceOtpAsync([FromBody] UnauthenticatedSecretVerificationRequestModel request)
|
||||
{
|
||||
await _userService.ResendNewDeviceVerificationEmail(request.Email, request.Secret);
|
||||
}
|
||||
|
||||
[RequireFeature(FeatureFlagKeys.NewDeviceVerification)]
|
||||
[HttpPost("verify-devices")]
|
||||
[HttpPut("verify-devices")]
|
||||
public async Task SetUserVerifyDevicesAsync([FromBody] SetVerifyDevicesRequestModel request)
|
||||
{
|
||||
var user = await _userService.GetUserByPrincipalAsync(User) ?? throw new UnauthorizedAccessException();
|
||||
|
||||
if (!await _userService.VerifySecretAsync(user, request.Secret))
|
||||
{
|
||||
await Task.Delay(2000);
|
||||
throw new BadRequestException(string.Empty, "User verification failed.");
|
||||
}
|
||||
user.VerifyDevices = request.VerifyDevices;
|
||||
|
||||
await _userService.SaveUserAsync(user);
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<Guid>> GetOrganizationIdsManagingUserAsync(Guid userId)
|
||||
{
|
||||
var organizationManagingUser = await _userService.GetOrganizationsManagingUserAsync(userId);
|
||||
|
@ -0,0 +1,9 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Bit.Api.Auth.Models.Request.Accounts;
|
||||
|
||||
public class SetVerifyDevicesRequestModel : SecretVerificationRequestModel
|
||||
{
|
||||
[Required]
|
||||
public bool VerifyDevices { get; set; }
|
||||
}
|
@ -3,7 +3,7 @@ using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Api.Auth.Models.Request.Accounts;
|
||||
|
||||
public class UnauthenticatedSecretVerificatioRequestModel : SecretVerificationRequestModel
|
||||
public class UnauthenticatedSecretVerificationRequestModel : SecretVerificationRequestModel
|
||||
{
|
||||
[Required]
|
||||
[StrictEmailAddress]
|
@ -72,6 +72,7 @@ public class User : ITableObject<Guid>, IStorableSubscriber, IRevisable, ITwoFac
|
||||
public DateTime? LastKdfChangeDate { get; set; }
|
||||
public DateTime? LastKeyRotationDate { get; set; }
|
||||
public DateTime? LastEmailChangeDate { get; set; }
|
||||
public bool VerifyDevices { get; set; } = true;
|
||||
|
||||
public void SetNewId()
|
||||
{
|
||||
|
@ -115,7 +115,7 @@ public class DeviceValidator(
|
||||
/// </summary>
|
||||
/// <param name="user">user attempting to authenticate</param>
|
||||
/// <param name="ValidatedRequest">The Request is used to check for the NewDeviceOtp and for the raw device data</param>
|
||||
/// <returns>returns deviceValtaionResultType</returns>
|
||||
/// <returns>returns deviceValidationResultType</returns>
|
||||
private async Task<DeviceValidationResultType> HandleNewDeviceVerificationAsync(User user, ValidatedRequest request)
|
||||
{
|
||||
// currently unreachable due to backward compatibility
|
||||
@ -125,6 +125,12 @@ public class DeviceValidator(
|
||||
return DeviceValidationResultType.InvalidUser;
|
||||
}
|
||||
|
||||
// Has the User opted out of new device verification
|
||||
if (!user.VerifyDevices)
|
||||
{
|
||||
return DeviceValidationResultType.Success;
|
||||
}
|
||||
|
||||
// CS exception flow
|
||||
// Check cache for user information
|
||||
var cacheKey = string.Format(AuthConstants.NewDeviceVerificationExceptionCacheKeyFormat, user.Id.ToString());
|
||||
@ -146,6 +152,12 @@ public class DeviceValidator(
|
||||
var otpValid = await _userService.VerifyOTPAsync(user, newDeviceOtp);
|
||||
if (otpValid)
|
||||
{
|
||||
// In order to get here they would have to have access to their email so we verify it if it's not already
|
||||
if (!user.EmailVerified)
|
||||
{
|
||||
user.EmailVerified = true;
|
||||
await _userService.SaveUserAsync(user);
|
||||
}
|
||||
return DeviceValidationResultType.Success;
|
||||
}
|
||||
return DeviceValidationResultType.InvalidNewDeviceOtp;
|
||||
|
@ -40,7 +40,8 @@
|
||||
@LastPasswordChangeDate DATETIME2(7) = NULL,
|
||||
@LastKdfChangeDate DATETIME2(7) = NULL,
|
||||
@LastKeyRotationDate DATETIME2(7) = NULL,
|
||||
@LastEmailChangeDate DATETIME2(7) = NULL
|
||||
@LastEmailChangeDate DATETIME2(7) = NULL,
|
||||
@VerifyDevices BIT = 1
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
@ -88,7 +89,8 @@ BEGIN
|
||||
[LastPasswordChangeDate],
|
||||
[LastKdfChangeDate],
|
||||
[LastKeyRotationDate],
|
||||
[LastEmailChangeDate]
|
||||
[LastEmailChangeDate],
|
||||
[VerifyDevices]
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
@ -133,6 +135,7 @@ BEGIN
|
||||
@LastPasswordChangeDate,
|
||||
@LastKdfChangeDate,
|
||||
@LastKeyRotationDate,
|
||||
@LastEmailChangeDate
|
||||
@LastEmailChangeDate,
|
||||
@VerifyDevices
|
||||
)
|
||||
END
|
||||
|
@ -40,7 +40,8 @@
|
||||
@LastPasswordChangeDate DATETIME2(7) = NULL,
|
||||
@LastKdfChangeDate DATETIME2(7) = NULL,
|
||||
@LastKeyRotationDate DATETIME2(7) = NULL,
|
||||
@LastEmailChangeDate DATETIME2(7) = NULL
|
||||
@LastEmailChangeDate DATETIME2(7) = NULL,
|
||||
@VerifyDevices BIT = 1
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
@ -88,7 +89,8 @@ BEGIN
|
||||
[LastPasswordChangeDate] = @LastPasswordChangeDate,
|
||||
[LastKdfChangeDate] = @LastKdfChangeDate,
|
||||
[LastKeyRotationDate] = @LastKeyRotationDate,
|
||||
[LastEmailChangeDate] = @LastEmailChangeDate
|
||||
[LastEmailChangeDate] = @LastEmailChangeDate,
|
||||
[VerifyDevices] = @VerifyDevices
|
||||
WHERE
|
||||
[Id] = @Id
|
||||
END
|
||||
|
@ -36,11 +36,12 @@
|
||||
[UsesKeyConnector] BIT NOT NULL,
|
||||
[FailedLoginCount] INT CONSTRAINT [D_User_FailedLoginCount] DEFAULT ((0)) NOT NULL,
|
||||
[LastFailedLoginDate] DATETIME2 (7) NULL,
|
||||
[AvatarColor] VARCHAR(7) NULL,
|
||||
[AvatarColor] VARCHAR(7) NULL,
|
||||
[LastPasswordChangeDate] DATETIME2 (7) NULL,
|
||||
[LastKdfChangeDate] DATETIME2 (7) NULL,
|
||||
[LastKeyRotationDate] DATETIME2 (7) NULL,
|
||||
[LastEmailChangeDate] DATETIME2 (7) NULL,
|
||||
[VerifyDevices] BIT DEFAULT ((1)) NOT NULL,
|
||||
CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED ([Id] ASC)
|
||||
);
|
||||
|
||||
|
Reference in New Issue
Block a user