1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-22 12:15:36 +01:00

[EC-400] Code clean up Device Verification (#2601)

* EC-400 Clean up code regarding Unknown Device Verification

* EC-400 Fix formatting
This commit is contained in:
Federico Maccaroni 2023-02-17 10:15:28 -03:00 committed by GitHub
parent 7594ca1122
commit 69511160cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 6876 additions and 469 deletions

View File

@ -295,21 +295,14 @@ public class TwoFactorController : Controller
if (await _verifyAuthRequestCommand
.VerifyAuthRequestAsync(new Guid(model.AuthRequestId), model.AuthRequestAccessCode))
{
var isBecauseNewDeviceLogin = await IsNewDeviceLoginAsync(user, model);
await _userService.SendTwoFactorEmailAsync(user, isBecauseNewDeviceLogin);
await _userService.SendTwoFactorEmailAsync(user);
return;
}
}
else
else if (await _userService.VerifySecretAsync(user, model.Secret))
{
if (await _userService.VerifySecretAsync(user, model.Secret))
{
var isBecauseNewDeviceLogin = await IsNewDeviceLoginAsync(user, model);
await _userService.SendTwoFactorEmailAsync(user, isBecauseNewDeviceLogin);
return;
}
await _userService.SendTwoFactorEmailAsync(user);
return;
}
}
@ -390,41 +383,18 @@ public class TwoFactorController : Controller
}
}
[Obsolete("Leaving this for backwards compatibilty on clients")]
[HttpGet("get-device-verification-settings")]
public async Task<DeviceVerificationResponseModel> GetDeviceVerificationSettings()
public Task<DeviceVerificationResponseModel> GetDeviceVerificationSettings()
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
if (User.Claims.HasSsoIdP())
{
return new DeviceVerificationResponseModel(false, false);
}
var canUserEditDeviceVerificationSettings = _userService.CanEditDeviceVerificationSettings(user);
return new DeviceVerificationResponseModel(canUserEditDeviceVerificationSettings, canUserEditDeviceVerificationSettings && user.UnknownDeviceVerificationEnabled);
return Task.FromResult(new DeviceVerificationResponseModel(false, false));
}
[Obsolete("Leaving this for backwards compatibilty on clients")]
[HttpPut("device-verification-settings")]
public async Task<DeviceVerificationResponseModel> PutDeviceVerificationSettings([FromBody] DeviceVerificationRequestModel model)
public Task<DeviceVerificationResponseModel> PutDeviceVerificationSettings([FromBody] DeviceVerificationRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
if (!_userService.CanEditDeviceVerificationSettings(user)
|| User.Claims.HasSsoIdP())
{
throw new InvalidOperationException("Can't update device verification settings");
}
model.ToUser(user);
await _userService.SaveUserAsync(user);
return new DeviceVerificationResponseModel(true, user.UnknownDeviceVerificationEnabled);
return Task.FromResult(new DeviceVerificationResponseModel(false, false));
}
private async Task<User> CheckAsync(SecretVerificationRequestModel model, bool premium)
@ -467,17 +437,4 @@ public class TwoFactorController : Controller
await Task.Delay(500);
}
}
private async Task<bool> IsNewDeviceLoginAsync(User user, TwoFactorEmailRequestModel model)
{
if (user.GetTwoFactorProvider(TwoFactorProviderType.Email) is null
&&
await _userService.Needs2FABecauseNewDeviceAsync(user, model.DeviceIdentifier, null))
{
model.ToUser(user);
return true;
}
return false;
}
}

View File

@ -1,16 +1,10 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.Entities;
namespace Bit.Api.Models.Request;
public class DeviceVerificationRequestModel
{
[Obsolete("Leaving this for backwards compatibilty on clients")]
[Required]
public bool UnknownDeviceVerificationEnabled { get; set; }
public User ToUser(User user)
{
user.UnknownDeviceVerificationEnabled = UnknownDeviceVerificationEnabled;
return user;
}
}

View File

@ -202,8 +202,6 @@ public class TwoFactorEmailRequestModel : SecretVerificationRequestModel
[StringLength(256)]
public string Email { get; set; }
public string DeviceIdentifier { get; set; }
public string AuthRequestId { get; set; }
public User ToUser(User extistingUser)

View File

@ -2,6 +2,7 @@
namespace Bit.Api.Models.Response;
[Obsolete("Leaving this for backwards compatibilty on clients")]
public class DeviceVerificationResponseModel : ResponseModel
{
public DeviceVerificationResponseModel(bool isDeviceVerificationSectionEnabled, bool unknownDeviceVerificationEnabled)

View File

@ -62,7 +62,6 @@ public class User : ITableObject<Guid>, ISubscriber, IStorable, IStorableSubscri
public bool UsesKeyConnector { get; set; }
public int FailedLoginCount { get; set; }
public DateTime? LastFailedLoginDate { get; set; }
public bool UnknownDeviceVerificationEnabled { get; set; }
[MaxLength(7)]
public string AvatarColor { get; set; }
public DateTime? LastPasswordChangeDate { get; set; }

View File

@ -1,19 +0,0 @@
{{#>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 two-step verification code is: <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;">{{Token}}</b>
</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">
Use this code to complete logging in with Bitwarden.
</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">
This email was sent because you are logging in from a device we dont recognize. If you did not request this code, you may want to <a target="_blank" clicktracking=off href="https://bitwarden.com/help/master-password/#change-master-password" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #175DDC; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; text-decoration: underline;">change your master password</a>. You can view our tips for selecting a secure master password <a target="_blank" clicktracking=off href="https://bitwarden.com/blog/picking-the-right-password-for-your-password-manager/" style="-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; box-sizing: border-box; color: #175DDC; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 25px; margin: 0; text-decoration: underline;">here</a>.
</td>
</tr>
</table>
{{/FullHtmlLayout}}

View File

@ -1,7 +0,0 @@
{{#>BasicTextLayout}}
Your two-step verification code is: {{Token}}
Use this code to complete logging in with Bitwarden.
This email was sent because you are logging in from a device we dont recognize. If you did not request this code, you may want to change your master password (https://bitwarden.com/help/master-password/#change-master-password). You can view our tips for selecting a secure master password here (https://bitwarden.com/blog/picking-the-right-password-for-your-password-manager/).
{{/BasicTextLayout}}

View File

@ -13,7 +13,6 @@ public interface IMailService
Task SendChangeEmailAlreadyExistsEmailAsync(string fromEmail, string toEmail);
Task SendChangeEmailEmailAsync(string newEmailAddress, string token);
Task SendTwoFactorEmailAsync(string email, string token);
Task SendNewDeviceLoginTwoFactorEmailAsync(string email, string token);
Task SendNoMasterPasswordHintEmailAsync(string email);
Task SendMasterPasswordHintEmailAsync(string email, string hint);
Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, ExpiringToken token);

View File

@ -19,7 +19,7 @@ public interface IUserService
Task<IdentityResult> RegisterUserAsync(User user, string masterPassword, string token, Guid? orgUserId);
Task<IdentityResult> RegisterUserAsync(User user);
Task SendMasterPasswordHintAsync(string email);
Task SendTwoFactorEmailAsync(User user, bool isBecauseNewDeviceLogin = false);
Task SendTwoFactorEmailAsync(User user);
Task<bool> VerifyTwoFactorEmailAsync(User user, string token);
Task<CredentialCreateOptions> StartWebAuthnRegistrationAsync(User user);
Task<bool> DeleteWebAuthnKeyAsync(User user, int id);
@ -76,6 +76,4 @@ public interface IUserService
Task SendOTPAsync(User user);
Task<bool> VerifyOTPAsync(User user, string token);
Task<bool> VerifySecretAsync(User user, string secret);
Task<bool> Needs2FABecauseNewDeviceAsync(User user, string deviceIdentifier, string grantType);
bool CanEditDeviceVerificationSettings(User user);
}

View File

@ -323,7 +323,6 @@ public class EmergencyAccessService : IEmergencyAccessService
grantor.Key = key;
// Disable TwoFactor providers since they will otherwise block logins
grantor.SetTwoFactorProviders(new Dictionary<TwoFactorProviderType, TwoFactorProvider>());
grantor.UnknownDeviceVerificationEnabled = false;
await _userRepository.ReplaceAsync(grantor);
// Remove grantor from all organizations unless Owner

View File

@ -113,21 +113,6 @@ public class HandlebarsMailService : IMailService
await _mailDeliveryService.SendEmailAsync(message);
}
public async Task SendNewDeviceLoginTwoFactorEmailAsync(string email, string token)
{
var message = CreateDefaultMessage("New Device Login Verification Code", email);
var model = new EmailTokenViewModel
{
Token = token,
WebVaultUrl = _globalSettings.BaseServiceUri.VaultWithHash,
SiteName = _globalSettings.SiteName
};
await AddMessageContentAsync(message, "NewDeviceLoginTwoFactorEmail", model);
message.MetaData.Add("SendGridBypassListManagement", true);
message.Category = "TwoFactorEmail";
await _mailDeliveryService.SendEmailAsync(message);
}
public async Task SendMasterPasswordHintEmailAsync(string email, string hint)
{
var message = CreateDefaultMessage("Your Master Password Hint", email);

View File

@ -46,7 +46,6 @@ public class UserService : UserManager<User>, IUserService, IDisposable
private readonly IGlobalSettings _globalSettings;
private readonly IOrganizationService _organizationService;
private readonly IProviderUserRepository _providerUserRepository;
private readonly IDeviceRepository _deviceRepository;
private readonly IStripeSyncService _stripeSyncService;
public UserService(
@ -77,7 +76,6 @@ public class UserService : UserManager<User>, IUserService, IDisposable
IGlobalSettings globalSettings,
IOrganizationService organizationService,
IProviderUserRepository providerUserRepository,
IDeviceRepository deviceRepository,
IStripeSyncService stripeSyncService)
: base(
store,
@ -113,7 +111,6 @@ public class UserService : UserManager<User>, IUserService, IDisposable
_globalSettings = globalSettings;
_organizationService = organizationService;
_providerUserRepository = providerUserRepository;
_deviceRepository = deviceRepository;
_stripeSyncService = stripeSyncService;
}
@ -353,7 +350,7 @@ public class UserService : UserManager<User>, IUserService, IDisposable
await _mailService.SendMasterPasswordHintEmailAsync(email, user.MasterPasswordHint);
}
public async Task SendTwoFactorEmailAsync(User user, bool isBecauseNewDeviceLogin = false)
public async Task SendTwoFactorEmailAsync(User user)
{
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Email);
if (provider == null || provider.MetaData == null || !provider.MetaData.ContainsKey("Email"))
@ -365,14 +362,7 @@ public class UserService : UserManager<User>, IUserService, IDisposable
var token = await base.GenerateUserTokenAsync(user, TokenOptions.DefaultEmailProvider,
"2faEmail:" + email);
if (isBecauseNewDeviceLogin)
{
await _mailService.SendNewDeviceLoginTwoFactorEmailAsync(email, token);
}
else
{
await _mailService.SendTwoFactorEmailAsync(email, token);
}
await _mailService.SendTwoFactorEmailAsync(email, token);
}
public async Task<bool> VerifyTwoFactorEmailAsync(User user, string token)
@ -1478,36 +1468,4 @@ public class UserService : UserManager<User>, IUserService, IDisposable
? await VerifyOTPAsync(user, secret)
: await CheckPasswordAsync(user, secret);
}
public async Task<bool> Needs2FABecauseNewDeviceAsync(User user, string deviceIdentifier, string grantType)
{
return CanEditDeviceVerificationSettings(user)
&& user.UnknownDeviceVerificationEnabled
&& grantType != "authorization_code"
&& await IsNewDeviceAndNotTheFirstOneAsync(user, deviceIdentifier);
}
public bool CanEditDeviceVerificationSettings(User user)
{
return _globalSettings.TwoFactorAuth.EmailOnNewDeviceLogin
&& user.EmailVerified
&& !user.UsesKeyConnector
&& !(user.GetTwoFactorProviders()?.Any() ?? false);
}
private async Task<bool> IsNewDeviceAndNotTheFirstOneAsync(User user, string deviceIdentifier)
{
if (user == null)
{
return default;
}
var devices = await _deviceRepository.GetManyByUserIdAsync(user.Id);
if (!devices.Any())
{
return false;
}
return !devices.Any(d => d.Identifier == deviceIdentifier);
}
}

View File

@ -72,11 +72,6 @@ public class NoopMailService : IMailService
return Task.FromResult(0);
}
public Task SendNewDeviceLoginTwoFactorEmailAsync(string email, string token)
{
return Task.CompletedTask;
}
public Task SendWelcomeEmailAsync(User user)
{
return Task.FromResult(0);

View File

@ -73,7 +73,6 @@ public class GlobalSettings : IGlobalSettings
public virtual AppleIapSettings AppleIap { get; set; } = new AppleIapSettings();
public virtual ISsoSettings Sso { get; set; } = new SsoSettings();
public virtual StripeSettings Stripe { get; set; } = new StripeSettings();
public virtual ITwoFactorAuthSettings TwoFactorAuth { get; set; } = new TwoFactorAuthSettings();
public virtual DistributedIpRateLimitingSettings DistributedIpRateLimiting { get; set; } =
new DistributedIpRateLimitingSettings();
public virtual IPasswordlessAuthSettings PasswordlessAuth { get; set; } = new PasswordlessAuthSettings();
@ -511,11 +510,6 @@ public class GlobalSettings : IGlobalSettings
public int MaxNetworkRetries { get; set; } = 2;
}
public class TwoFactorAuthSettings : ITwoFactorAuthSettings
{
public bool EmailOnNewDeviceLogin { get; set; } = false;
}
public class DistributedIpRateLimitingSettings
{
public bool Enabled { get; set; } = true;

View File

@ -13,7 +13,6 @@ public interface IGlobalSettings
IFileStorageSettings Attachment { get; set; }
IConnectionStringSettings Storage { get; set; }
IBaseServiceUriSettings BaseServiceUri { get; set; }
ITwoFactorAuthSettings TwoFactorAuth { get; set; }
ISsoSettings Sso { get; set; }
ILogLevelSettings MinLogLevel { get; set; }
IPasswordlessAuthSettings PasswordlessAuth { get; set; }

View File

@ -1,6 +0,0 @@
namespace Bit.Core.Settings;
public interface ITwoFactorAuthSettings
{
bool EmailOnNewDeviceLogin { get; set; }
}

View File

@ -100,24 +100,20 @@ public abstract class BaseRequestValidator<T> where T : class
return;
}
var (isTwoFactorRequired, requires2FABecauseNewDevice, twoFactorOrganization) = await RequiresTwoFactorAsync(user, request);
var (isTwoFactorRequired, twoFactorOrganization) = await RequiresTwoFactorAsync(user, request);
if (isTwoFactorRequired)
{
// Just defaulting it
var twoFactorProviderType = TwoFactorProviderType.Authenticator;
if (!twoFactorRequest || !Enum.TryParse(twoFactorProvider, out twoFactorProviderType))
{
await BuildTwoFactorResultAsync(user, twoFactorOrganization, context, requires2FABecauseNewDevice);
await BuildTwoFactorResultAsync(user, twoFactorOrganization, context);
return;
}
BeforeVerifyTwoFactor(user, twoFactorProviderType, requires2FABecauseNewDevice);
var verified = await VerifyTwoFactor(user, twoFactorOrganization,
twoFactorProviderType, twoFactorToken);
AfterVerifyTwoFactor(user, twoFactorProviderType, requires2FABecauseNewDevice);
if ((!verified || isBot) && twoFactorProviderType != TwoFactorProviderType.Remember)
{
await UpdateFailedAuthDetailsAsync(user, true, !validatorContext.KnownDevice);
@ -128,7 +124,7 @@ public abstract class BaseRequestValidator<T> where T : class
{
// Delay for brute force.
await Task.Delay(2000);
await BuildTwoFactorResultAsync(user, twoFactorOrganization, context, requires2FABecauseNewDevice);
await BuildTwoFactorResultAsync(user, twoFactorOrganization, context);
return;
}
}
@ -201,7 +197,7 @@ public abstract class BaseRequestValidator<T> where T : class
await SetSuccessResult(context, user, claims, customResponse);
}
protected async Task BuildTwoFactorResultAsync(User user, Organization organization, T context, bool requires2FABecauseNewDevice)
protected async Task BuildTwoFactorResultAsync(User user, Organization organization, T context)
{
var providerKeys = new List<byte>();
var providers = new Dictionary<string, Dictionary<string, object>>();
@ -226,23 +222,8 @@ public abstract class BaseRequestValidator<T> where T : class
if (!enabledProviders.Any())
{
if (!requires2FABecauseNewDevice)
{
await BuildErrorResultAsync("No two-step providers enabled.", false, context, user);
return;
}
var emailProvider = new TwoFactorProvider
{
MetaData = new Dictionary<string, object> { ["Email"] = user.Email.ToLowerInvariant() },
Enabled = true
};
enabledProviders.Add(new KeyValuePair<TwoFactorProviderType, TwoFactorProvider>(
TwoFactorProviderType.Email, emailProvider));
user.SetTwoFactorProviders(new Dictionary<TwoFactorProviderType, TwoFactorProvider>
{
[TwoFactorProviderType.Email] = emailProvider
});
await BuildErrorResultAsync("No two-step providers enabled.", false, context, user);
return;
}
foreach (var provider in enabledProviders)
@ -262,7 +243,7 @@ public abstract class BaseRequestValidator<T> where T : class
if (enabledProviders.Count() == 1 && enabledProviders.First().Key == TwoFactorProviderType.Email)
{
// Send email now if this is their only 2FA method
await _userService.SendTwoFactorEmailAsync(user, requires2FABecauseNewDevice);
await _userService.SendTwoFactorEmailAsync(user);
}
}
@ -298,12 +279,12 @@ public abstract class BaseRequestValidator<T> where T : class
protected abstract void SetErrorResult(T context, Dictionary<string, object> customResponse);
private async Task<Tuple<bool, bool, Organization>> RequiresTwoFactorAsync(User user, ValidatedTokenRequest request)
private async Task<Tuple<bool, Organization>> RequiresTwoFactorAsync(User user, ValidatedTokenRequest request)
{
if (request.GrantType == "client_credentials")
{
// Do not require MFA for api key logins
return new Tuple<bool, bool, Organization>(false, false, null);
return new Tuple<bool, Organization>(false, null);
}
var individualRequired = _userManager.SupportsUserTwoFactor &&
@ -325,17 +306,7 @@ public abstract class BaseRequestValidator<T> where T : class
}
}
var requires2FA = individualRequired || firstEnabledOrg != null;
var requires2FABecauseNewDevice = !requires2FA
&&
await _userService.Needs2FABecauseNewDeviceAsync(
user,
GetDeviceFromRequest(request)?.Identifier,
request.GrantType);
requires2FA = requires2FA || requires2FABecauseNewDevice;
return new Tuple<bool, bool, Organization>(requires2FA, requires2FABecauseNewDevice, firstEnabledOrg);
return new Tuple<bool, Organization>(individualRequired || firstEnabledOrg != null, firstEnabledOrg);
}
private async Task<bool> IsValidAuthTypeAsync(User user, string grantType)
@ -413,29 +384,6 @@ public abstract class BaseRequestValidator<T> where T : class
};
}
private void BeforeVerifyTwoFactor(User user, TwoFactorProviderType type, bool requires2FABecauseNewDevice)
{
if (type == TwoFactorProviderType.Email && requires2FABecauseNewDevice)
{
user.SetTwoFactorProviders(new Dictionary<TwoFactorProviderType, TwoFactorProvider>
{
[TwoFactorProviderType.Email] = new TwoFactorProvider
{
MetaData = new Dictionary<string, object> { ["Email"] = user.Email.ToLowerInvariant() },
Enabled = true
}
});
}
}
private void AfterVerifyTwoFactor(User user, TwoFactorProviderType type, bool requires2FABecauseNewDevice)
{
if (type == TwoFactorProviderType.Email && requires2FABecauseNewDevice)
{
user.ClearTwoFactorProviders();
}
}
private async Task<bool> VerifyTwoFactor(User user, Organization organization, TwoFactorProviderType type,
string token)
{

View File

@ -0,0 +1,120 @@
CREATE PROCEDURE [dbo].[User_Create]
@Id UNIQUEIDENTIFIER OUTPUT,
@Name NVARCHAR(50),
@Email NVARCHAR(256),
@EmailVerified BIT,
@MasterPassword NVARCHAR(300),
@MasterPasswordHint NVARCHAR(50),
@Culture NVARCHAR(10),
@SecurityStamp NVARCHAR(50),
@TwoFactorProviders NVARCHAR(MAX),
@TwoFactorRecoveryCode NVARCHAR(32),
@EquivalentDomains NVARCHAR(MAX),
@ExcludedGlobalEquivalentDomains NVARCHAR(MAX),
@AccountRevisionDate DATETIME2(7),
@Key NVARCHAR(MAX),
@PublicKey NVARCHAR(MAX),
@PrivateKey NVARCHAR(MAX),
@Premium BIT,
@PremiumExpirationDate DATETIME2(7),
@RenewalReminderDate DATETIME2(7),
@Storage BIGINT,
@MaxStorageGb SMALLINT,
@Gateway TINYINT,
@GatewayCustomerId VARCHAR(50),
@GatewaySubscriptionId VARCHAR(50),
@ReferenceData VARCHAR(MAX),
@LicenseKey VARCHAR(100),
@Kdf TINYINT,
@KdfIterations INT,
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7),
@ApiKey VARCHAR(30),
@ForcePasswordReset BIT = 0,
@UsesKeyConnector BIT = 0,
@FailedLoginCount INT = 0,
@LastFailedLoginDate DATETIME2(7),
@AvatarColor VARCHAR(7) = NULL
AS
BEGIN
SET NOCOUNT ON
INSERT INTO [dbo].[User]
(
[Id],
[Name],
[Email],
[EmailVerified],
[MasterPassword],
[MasterPasswordHint],
[Culture],
[SecurityStamp],
[TwoFactorProviders],
[TwoFactorRecoveryCode],
[EquivalentDomains],
[ExcludedGlobalEquivalentDomains],
[AccountRevisionDate],
[Key],
[PublicKey],
[PrivateKey],
[Premium],
[PremiumExpirationDate],
[RenewalReminderDate],
[Storage],
[MaxStorageGb],
[Gateway],
[GatewayCustomerId],
[GatewaySubscriptionId],
[ReferenceData],
[LicenseKey],
[Kdf],
[KdfIterations],
[CreationDate],
[RevisionDate],
[ApiKey],
[ForcePasswordReset],
[UsesKeyConnector],
[FailedLoginCount],
[LastFailedLoginDate],
[AvatarColor]
)
VALUES
(
@Id,
@Name,
@Email,
@EmailVerified,
@MasterPassword,
@MasterPasswordHint,
@Culture,
@SecurityStamp,
@TwoFactorProviders,
@TwoFactorRecoveryCode,
@EquivalentDomains,
@ExcludedGlobalEquivalentDomains,
@AccountRevisionDate,
@Key,
@PublicKey,
@PrivateKey,
@Premium,
@PremiumExpirationDate,
@RenewalReminderDate,
@Storage,
@MaxStorageGb,
@Gateway,
@GatewayCustomerId,
@GatewaySubscriptionId,
@ReferenceData,
@LicenseKey,
@Kdf,
@KdfIterations,
@CreationDate,
@RevisionDate,
@ApiKey,
@ForcePasswordReset,
@UsesKeyConnector,
@FailedLoginCount,
@LastFailedLoginDate,
@AvatarColor
)
END

View File

@ -0,0 +1,82 @@
CREATE PROCEDURE [dbo].[User_Update]
@Id UNIQUEIDENTIFIER,
@Name NVARCHAR(50),
@Email NVARCHAR(256),
@EmailVerified BIT,
@MasterPassword NVARCHAR(300),
@MasterPasswordHint NVARCHAR(50),
@Culture NVARCHAR(10),
@SecurityStamp NVARCHAR(50),
@TwoFactorProviders NVARCHAR(MAX),
@TwoFactorRecoveryCode NVARCHAR(32),
@EquivalentDomains NVARCHAR(MAX),
@ExcludedGlobalEquivalentDomains NVARCHAR(MAX),
@AccountRevisionDate DATETIME2(7),
@Key NVARCHAR(MAX),
@PublicKey NVARCHAR(MAX),
@PrivateKey NVARCHAR(MAX),
@Premium BIT,
@PremiumExpirationDate DATETIME2(7),
@RenewalReminderDate DATETIME2(7),
@Storage BIGINT,
@MaxStorageGb SMALLINT,
@Gateway TINYINT,
@GatewayCustomerId VARCHAR(50),
@GatewaySubscriptionId VARCHAR(50),
@ReferenceData VARCHAR(MAX),
@LicenseKey VARCHAR(100),
@Kdf TINYINT,
@KdfIterations INT,
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7),
@ApiKey VARCHAR(30),
@ForcePasswordReset BIT = 0,
@UsesKeyConnector BIT = 0,
@FailedLoginCount INT,
@LastFailedLoginDate DATETIME2(7),
@AvatarColor VARCHAR(7)
AS
BEGIN
SET NOCOUNT ON
UPDATE
[dbo].[User]
SET
[Name] = @Name,
[Email] = @Email,
[EmailVerified] = @EmailVerified,
[MasterPassword] = @MasterPassword,
[MasterPasswordHint] = @MasterPasswordHint,
[Culture] = @Culture,
[SecurityStamp] = @SecurityStamp,
[TwoFactorProviders] = @TwoFactorProviders,
[TwoFactorRecoveryCode] = @TwoFactorRecoveryCode,
[EquivalentDomains] = @EquivalentDomains,
[ExcludedGlobalEquivalentDomains] = @ExcludedGlobalEquivalentDomains,
[AccountRevisionDate] = @AccountRevisionDate,
[Key] = @Key,
[PublicKey] = @PublicKey,
[PrivateKey] = @PrivateKey,
[Premium] = @Premium,
[PremiumExpirationDate] = @PremiumExpirationDate,
[RenewalReminderDate] = @RenewalReminderDate,
[Storage] = @Storage,
[MaxStorageGb] = @MaxStorageGb,
[Gateway] = @Gateway,
[GatewayCustomerId] = @GatewayCustomerId,
[GatewaySubscriptionId] = @GatewaySubscriptionId,
[ReferenceData] = @ReferenceData,
[LicenseKey] = @LicenseKey,
[Kdf] = @Kdf,
[KdfIterations] = @KdfIterations,
[CreationDate] = @CreationDate,
[RevisionDate] = @RevisionDate,
[ApiKey] = @ApiKey,
[ForcePasswordReset] = @ForcePasswordReset,
[UsesKeyConnector] = @UsesKeyConnector,
[FailedLoginCount] = @FailedLoginCount,
[LastFailedLoginDate] = @LastFailedLoginDate,
[AvatarColor] = @AvatarColor
WHERE
[Id] = @Id
END

View File

@ -0,0 +1,49 @@
CREATE TABLE [dbo].[User] (
[Id] UNIQUEIDENTIFIER NOT NULL,
[Name] NVARCHAR (50) NULL,
[Email] NVARCHAR (256) NOT NULL,
[EmailVerified] BIT NOT NULL,
[MasterPassword] NVARCHAR (300) NULL,
[MasterPasswordHint] NVARCHAR (50) NULL,
[Culture] NVARCHAR (10) NOT NULL,
[SecurityStamp] NVARCHAR (50) NOT NULL,
[TwoFactorProviders] NVARCHAR (MAX) NULL,
[TwoFactorRecoveryCode] NVARCHAR (32) NULL,
[EquivalentDomains] NVARCHAR (MAX) NULL,
[ExcludedGlobalEquivalentDomains] NVARCHAR (MAX) NULL,
[AccountRevisionDate] DATETIME2 (7) NOT NULL,
[Key] VARCHAR (MAX) NULL,
[PublicKey] VARCHAR (MAX) NULL,
[PrivateKey] VARCHAR (MAX) NULL,
[Premium] BIT NOT NULL,
[PremiumExpirationDate] DATETIME2 (7) NULL,
[RenewalReminderDate] DATETIME2 (7) NULL,
[Storage] BIGINT NULL,
[MaxStorageGb] SMALLINT NULL,
[Gateway] TINYINT NULL,
[GatewayCustomerId] VARCHAR (50) NULL,
[GatewaySubscriptionId] VARCHAR (50) NULL,
[ReferenceData] NVARCHAR (MAX) NULL,
[LicenseKey] VARCHAR (100) NULL,
[Kdf] TINYINT NOT NULL,
[KdfIterations] INT NOT NULL,
[CreationDate] DATETIME2 (7) NOT NULL,
[RevisionDate] DATETIME2 (7) NOT NULL,
[ApiKey] VARCHAR (30) NOT NULL,
[ForcePasswordReset] BIT NOT NULL,
[UsesKeyConnector] BIT NOT NULL,
[FailedLoginCount] INT CONSTRAINT [D_User_FailedLoginCount] DEFAULT ((0)) NOT NULL,
[LastFailedLoginDate] DATETIME2 (7) NULL,
[AvatarColor] VARCHAR(7) NULL,
CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED ([Id] ASC)
);
GO
CREATE UNIQUE NONCLUSTERED INDEX [IX_User_Email]
ON [dbo].[User]([Email] ASC);
GO
CREATE NONCLUSTERED INDEX [IX_User_Premium_PremiumExpirationDate_RenewalReminderDate]
ON [dbo].[User]([Premium] ASC, [PremiumExpirationDate] ASC, [RenewalReminderDate] ASC);

View File

@ -138,11 +138,10 @@ public class EmergencyAccessServiceTests
}
[Theory, BitAutoData]
public async Task PasswordAsync_Disables_2FA_Providers_And_Unknown_Device_Verification_On_The_Grantor(
public async Task PasswordAsync_Disables_2FA_Providers_On_The_Grantor(
SutProvider<EmergencyAccessService> sutProvider, User requestingUser, User grantor)
{
grantor.UsesKeyConnector = true;
grantor.UnknownDeviceVerificationEnabled = true;
grantor.SetTwoFactorProviders(new Dictionary<TwoFactorProviderType, TwoFactorProvider>
{
[TwoFactorProviderType.Email] = new TwoFactorProvider
@ -164,7 +163,6 @@ public class EmergencyAccessServiceTests
await sutProvider.Sut.PasswordAsync(Guid.NewGuid(), requestingUser, "blablahash", "blablakey");
Assert.False(grantor.UnknownDeviceVerificationEnabled);
Assert.Empty(grantor.GetTwoFactorProviders());
await sutProvider.GetDependency<IUserRepository>().Received().ReplaceAsync(grantor);
}

View File

@ -95,37 +95,6 @@ public class UserServiceTests
.SendTwoFactorEmailAsync(email, token);
}
[Theory, BitAutoData]
public async Task SendTwoFactorEmailBecauseNewDeviceLoginAsync_Success(SutProvider<UserService> sutProvider, User user)
{
var email = user.Email.ToLowerInvariant();
var token = "thisisatokentocompare";
var userTwoFactorTokenProvider = Substitute.For<IUserTwoFactorTokenProvider<User>>();
userTwoFactorTokenProvider
.CanGenerateTwoFactorTokenAsync(Arg.Any<UserManager<User>>(), user)
.Returns(Task.FromResult(true));
userTwoFactorTokenProvider
.GenerateAsync("2faEmail:" + email, Arg.Any<UserManager<User>>(), user)
.Returns(Task.FromResult(token));
sutProvider.Sut.RegisterTokenProvider("Email", userTwoFactorTokenProvider);
user.SetTwoFactorProviders(new Dictionary<TwoFactorProviderType, TwoFactorProvider>
{
[TwoFactorProviderType.Email] = new TwoFactorProvider
{
MetaData = new Dictionary<string, object> { ["Email"] = email },
Enabled = true
}
});
await sutProvider.Sut.SendTwoFactorEmailAsync(user, true);
await sutProvider.GetDependency<IMailService>()
.Received(1)
.SendNewDeviceLoginTwoFactorEmailAsync(email, token);
}
[Theory, BitAutoData]
public async Task SendTwoFactorEmailAsync_ExceptionBecauseNoProviderOnUser(SutProvider<UserService> sutProvider, User user)
{
@ -164,199 +133,6 @@ public class UserServiceTests
await Assert.ThrowsAsync<ArgumentNullException>("No email.", () => sutProvider.Sut.SendTwoFactorEmailAsync(user));
}
[Theory, BitAutoData]
public async Task Needs2FABecauseNewDeviceAsync_ReturnsTrue(SutProvider<UserService> sutProvider, User user)
{
user.EmailVerified = true;
user.TwoFactorProviders = null;
user.UnknownDeviceVerificationEnabled = true;
const string deviceIdToCheck = "7b01b586-b210-499f-8d52-0c3fdaa646fc";
const string deviceIdInRepo = "ea29126c-91b7-4cc4-8ce6-00105b37f64a";
sutProvider.GetDependency<IDeviceRepository>()
.GetManyByUserIdAsync(user.Id)
.Returns(Task.FromResult<ICollection<Device>>(new List<Device>
{
new Device { Identifier = deviceIdInRepo }
}));
sutProvider.GetDependency<Settings.IGlobalSettings>().TwoFactorAuth.EmailOnNewDeviceLogin.Returns(true);
Assert.True(await sutProvider.Sut.Needs2FABecauseNewDeviceAsync(user, deviceIdToCheck, "password"));
}
[Theory, BitAutoData]
public async Task Needs2FABecauseNewDeviceAsync_ReturnsFalse_When_GranType_Is_AuthorizationCode(SutProvider<UserService> sutProvider, User user)
{
user.EmailVerified = true;
user.TwoFactorProviders = null;
const string deviceIdToCheck = "7b01b586-b210-499f-8d52-0c3fdaa646fc";
const string deviceIdInRepo = "ea29126c-91b7-4cc4-8ce6-00105b37f64a";
sutProvider.GetDependency<IDeviceRepository>()
.GetManyByUserIdAsync(user.Id)
.Returns(Task.FromResult<ICollection<Device>>(new List<Device>
{
new Device { Identifier = deviceIdInRepo }
}));
Assert.False(await sutProvider.Sut.Needs2FABecauseNewDeviceAsync(user, deviceIdToCheck, "authorization_code"));
}
[Theory, BitAutoData]
public async Task Needs2FABecauseNewDeviceAsync_ReturnsFalse_When_Email_Is_Not_Verified(SutProvider<UserService> sutProvider, User user)
{
user.EmailVerified = false;
user.TwoFactorProviders = null;
const string deviceIdToCheck = "7b01b586-b210-499f-8d52-0c3fdaa646fc";
const string deviceIdInRepo = "ea29126c-91b7-4cc4-8ce6-00105b37f64a";
sutProvider.GetDependency<IDeviceRepository>()
.GetManyByUserIdAsync(user.Id)
.Returns(Task.FromResult<ICollection<Device>>(new List<Device>
{
new Device { Identifier = deviceIdInRepo }
}));
Assert.False(await sutProvider.Sut.Needs2FABecauseNewDeviceAsync(user, deviceIdToCheck, "password"));
}
[Theory, BitAutoData]
public async Task Needs2FABecauseNewDeviceAsync_ReturnsFalse_When_Is_The_First_Device(SutProvider<UserService> sutProvider, User user)
{
user.EmailVerified = true;
user.TwoFactorProviders = null;
const string deviceIdToCheck = "7b01b586-b210-499f-8d52-0c3fdaa646fc";
sutProvider.GetDependency<IDeviceRepository>()
.GetManyByUserIdAsync(user.Id)
.Returns(Task.FromResult<ICollection<Device>>(new List<Device>()));
Assert.False(await sutProvider.Sut.Needs2FABecauseNewDeviceAsync(user, deviceIdToCheck, "password"));
}
[Theory, BitAutoData]
public async Task Needs2FABecauseNewDeviceAsync_ReturnsFalse_When_DeviceId_Is_Already_In_Repo(SutProvider<UserService> sutProvider, User user)
{
user.EmailVerified = true;
user.TwoFactorProviders = null;
const string deviceIdToCheck = "7b01b586-b210-499f-8d52-0c3fdaa646fc";
sutProvider.GetDependency<IDeviceRepository>()
.GetManyByUserIdAsync(user.Id)
.Returns(Task.FromResult<ICollection<Device>>(new List<Device>
{
new Device { Identifier = deviceIdToCheck }
}));
Assert.False(await sutProvider.Sut.Needs2FABecauseNewDeviceAsync(user, deviceIdToCheck, "password"));
}
[Theory, BitAutoData]
public async Task Needs2FABecauseNewDeviceAsync_ReturnsFalse_When_GlobalSettings_2FA_EmailOnNewDeviceLogin_Is_Disabled(SutProvider<UserService> sutProvider, User user)
{
user.EmailVerified = true;
user.TwoFactorProviders = null;
const string deviceIdToCheck = "7b01b586-b210-499f-8d52-0c3fdaa646fc";
const string deviceIdInRepo = "ea29126c-91b7-4cc4-8ce6-00105b37f64a";
sutProvider.GetDependency<IDeviceRepository>()
.GetManyByUserIdAsync(user.Id)
.Returns(Task.FromResult<ICollection<Device>>(new List<Device>
{
new Device { Identifier = deviceIdInRepo }
}));
sutProvider.GetDependency<Settings.IGlobalSettings>().TwoFactorAuth.EmailOnNewDeviceLogin.Returns(false);
Assert.False(await sutProvider.Sut.Needs2FABecauseNewDeviceAsync(user, deviceIdToCheck, "password"));
}
[Theory, BitAutoData]
public async Task Needs2FABecauseNewDeviceAsync_ReturnsFalse_When_UnknownDeviceVerification_Is_Disabled(SutProvider<UserService> sutProvider, User user)
{
user.EmailVerified = true;
user.TwoFactorProviders = null;
user.UnknownDeviceVerificationEnabled = false;
const string deviceIdToCheck = "7b01b586-b210-499f-8d52-0c3fdaa646fc";
const string deviceIdInRepo = "ea29126c-91b7-4cc4-8ce6-00105b37f64a";
sutProvider.GetDependency<IDeviceRepository>()
.GetManyByUserIdAsync(user.Id)
.Returns(Task.FromResult<ICollection<Device>>(new List<Device>
{
new Device { Identifier = deviceIdInRepo }
}));
sutProvider.GetDependency<Settings.IGlobalSettings>().TwoFactorAuth.EmailOnNewDeviceLogin.Returns(true);
Assert.False(await sutProvider.Sut.Needs2FABecauseNewDeviceAsync(user, deviceIdToCheck, "password"));
}
[Theory, BitAutoData]
public void CanEditDeviceVerificationSettings_ReturnsTrue(SutProvider<UserService> sutProvider, User user)
{
user.EmailVerified = true;
user.TwoFactorProviders = null;
sutProvider.GetDependency<Settings.IGlobalSettings>().TwoFactorAuth.EmailOnNewDeviceLogin.Returns(true);
Assert.True(sutProvider.Sut.CanEditDeviceVerificationSettings(user));
}
[Theory, BitAutoData]
public void CanEditDeviceVerificationSettings_ReturnsFalse_When_GlobalSettings_2FA_EmailOnNewDeviceLogin_Is_Disabled(SutProvider<UserService> sutProvider, User user)
{
user.EmailVerified = true;
user.TwoFactorProviders = null;
sutProvider.GetDependency<Settings.IGlobalSettings>().TwoFactorAuth.EmailOnNewDeviceLogin.Returns(false);
Assert.False(sutProvider.Sut.CanEditDeviceVerificationSettings(user));
}
[Theory, BitAutoData]
public void CanEditDeviceVerificationSettings_ReturnsFalse_When_Email_Is_Not_Verified(SutProvider<UserService> sutProvider, User user)
{
user.EmailVerified = false;
user.TwoFactorProviders = null;
sutProvider.GetDependency<Settings.IGlobalSettings>().TwoFactorAuth.EmailOnNewDeviceLogin.Returns(true);
Assert.False(sutProvider.Sut.CanEditDeviceVerificationSettings(user));
}
[Theory, BitAutoData]
public void CanEditDeviceVerificationSettings_ReturnsFalse_When_User_Uses_Key_Connector(SutProvider<UserService> sutProvider, User user)
{
user.EmailVerified = true;
user.TwoFactorProviders = null;
user.UsesKeyConnector = true;
sutProvider.GetDependency<Settings.IGlobalSettings>().TwoFactorAuth.EmailOnNewDeviceLogin.Returns(true);
Assert.False(sutProvider.Sut.CanEditDeviceVerificationSettings(user));
}
[Theory, BitAutoData]
public void CanEditDeviceVerificationSettings_ReturnsFalse_When_User_Has_A_2FA_Already_Set_Up(SutProvider<UserService> sutProvider, User user)
{
user.EmailVerified = true;
user.SetTwoFactorProviders(new Dictionary<TwoFactorProviderType, TwoFactorProvider>
{
[TwoFactorProviderType.Email] = new TwoFactorProvider
{
MetaData = new Dictionary<string, object> { ["Email"] = "asdfasf" },
Enabled = true
}
});
sutProvider.GetDependency<Settings.IGlobalSettings>().TwoFactorAuth.EmailOnNewDeviceLogin.Returns(true);
Assert.False(sutProvider.Sut.CanEditDeviceVerificationSettings(user));
}
[Theory, BitAutoData]
public async void HasPremiumFromOrganization_Returns_False_If_No_Orgs(SutProvider<UserService> sutProvider, User user)
{

View File

@ -0,0 +1,230 @@
-- Table: User (UnknownDeviceVerificationEnabled)
IF COL_LENGTH('[dbo].[User]', 'UnknownDeviceVerificationEnabled') IS NOT NULL
BEGIN
ALTER TABLE
[dbo].[User]
DROP CONSTRAINT
[D_User_UnknownDeviceVerificationEnabled]
ALTER TABLE
[dbo].[User]
DROP COLUMN
[UnknownDeviceVerificationEnabled]
END
GO
-- View: User
CREATE OR ALTER VIEW [dbo].[UserView]
AS
SELECT
*
FROM
[dbo].[User]
GO
-- Stored Procedure: User_Create
CREATE OR ALTER PROCEDURE [dbo].[User_Create]
@Id UNIQUEIDENTIFIER OUTPUT,
@Name NVARCHAR(50),
@Email NVARCHAR(256),
@EmailVerified BIT,
@MasterPassword NVARCHAR(300),
@MasterPasswordHint NVARCHAR(50),
@Culture NVARCHAR(10),
@SecurityStamp NVARCHAR(50),
@TwoFactorProviders NVARCHAR(MAX),
@TwoFactorRecoveryCode NVARCHAR(32),
@EquivalentDomains NVARCHAR(MAX),
@ExcludedGlobalEquivalentDomains NVARCHAR(MAX),
@AccountRevisionDate DATETIME2(7),
@Key NVARCHAR(MAX),
@PublicKey NVARCHAR(MAX),
@PrivateKey NVARCHAR(MAX),
@Premium BIT,
@PremiumExpirationDate DATETIME2(7),
@RenewalReminderDate DATETIME2(7),
@Storage BIGINT,
@MaxStorageGb SMALLINT,
@Gateway TINYINT,
@GatewayCustomerId VARCHAR(50),
@GatewaySubscriptionId VARCHAR(50),
@ReferenceData VARCHAR(MAX),
@LicenseKey VARCHAR(100),
@Kdf TINYINT,
@KdfIterations INT,
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7),
@ApiKey VARCHAR(30),
@ForcePasswordReset BIT = 0,
@UsesKeyConnector BIT = 0,
@FailedLoginCount INT = 0,
@LastFailedLoginDate DATETIME2(7),
@AvatarColor VARCHAR(7) = NULL
AS
BEGIN
SET NOCOUNT ON
INSERT INTO [dbo].[User]
(
[Id],
[Name],
[Email],
[EmailVerified],
[MasterPassword],
[MasterPasswordHint],
[Culture],
[SecurityStamp],
[TwoFactorProviders],
[TwoFactorRecoveryCode],
[EquivalentDomains],
[ExcludedGlobalEquivalentDomains],
[AccountRevisionDate],
[Key],
[PublicKey],
[PrivateKey],
[Premium],
[PremiumExpirationDate],
[RenewalReminderDate],
[Storage],
[MaxStorageGb],
[Gateway],
[GatewayCustomerId],
[GatewaySubscriptionId],
[ReferenceData],
[LicenseKey],
[Kdf],
[KdfIterations],
[CreationDate],
[RevisionDate],
[ApiKey],
[ForcePasswordReset],
[UsesKeyConnector],
[FailedLoginCount],
[LastFailedLoginDate],
[AvatarColor]
)
VALUES
(
@Id,
@Name,
@Email,
@EmailVerified,
@MasterPassword,
@MasterPasswordHint,
@Culture,
@SecurityStamp,
@TwoFactorProviders,
@TwoFactorRecoveryCode,
@EquivalentDomains,
@ExcludedGlobalEquivalentDomains,
@AccountRevisionDate,
@Key,
@PublicKey,
@PrivateKey,
@Premium,
@PremiumExpirationDate,
@RenewalReminderDate,
@Storage,
@MaxStorageGb,
@Gateway,
@GatewayCustomerId,
@GatewaySubscriptionId,
@ReferenceData,
@LicenseKey,
@Kdf,
@KdfIterations,
@CreationDate,
@RevisionDate,
@ApiKey,
@ForcePasswordReset,
@UsesKeyConnector,
@FailedLoginCount,
@LastFailedLoginDate,
@AvatarColor
)
END
GO
-- Stored Procedure: User_Update
CREATE OR ALTER PROCEDURE [dbo].[User_Update]
@Id UNIQUEIDENTIFIER,
@Name NVARCHAR(50),
@Email NVARCHAR(256),
@EmailVerified BIT,
@MasterPassword NVARCHAR(300),
@MasterPasswordHint NVARCHAR(50),
@Culture NVARCHAR(10),
@SecurityStamp NVARCHAR(50),
@TwoFactorProviders NVARCHAR(MAX),
@TwoFactorRecoveryCode NVARCHAR(32),
@EquivalentDomains NVARCHAR(MAX),
@ExcludedGlobalEquivalentDomains NVARCHAR(MAX),
@AccountRevisionDate DATETIME2(7),
@Key NVARCHAR(MAX),
@PublicKey NVARCHAR(MAX),
@PrivateKey NVARCHAR(MAX),
@Premium BIT,
@PremiumExpirationDate DATETIME2(7),
@RenewalReminderDate DATETIME2(7),
@Storage BIGINT,
@MaxStorageGb SMALLINT,
@Gateway TINYINT,
@GatewayCustomerId VARCHAR(50),
@GatewaySubscriptionId VARCHAR(50),
@ReferenceData VARCHAR(MAX),
@LicenseKey VARCHAR(100),
@Kdf TINYINT,
@KdfIterations INT,
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7),
@ApiKey VARCHAR(30),
@ForcePasswordReset BIT = 0,
@UsesKeyConnector BIT = 0,
@FailedLoginCount INT,
@LastFailedLoginDate DATETIME2(7),
@AvatarColor VARCHAR(7)
AS
BEGIN
SET NOCOUNT ON
UPDATE
[dbo].[User]
SET
[Name] = @Name,
[Email] = @Email,
[EmailVerified] = @EmailVerified,
[MasterPassword] = @MasterPassword,
[MasterPasswordHint] = @MasterPasswordHint,
[Culture] = @Culture,
[SecurityStamp] = @SecurityStamp,
[TwoFactorProviders] = @TwoFactorProviders,
[TwoFactorRecoveryCode] = @TwoFactorRecoveryCode,
[EquivalentDomains] = @EquivalentDomains,
[ExcludedGlobalEquivalentDomains] = @ExcludedGlobalEquivalentDomains,
[AccountRevisionDate] = @AccountRevisionDate,
[Key] = @Key,
[PublicKey] = @PublicKey,
[PrivateKey] = @PrivateKey,
[Premium] = @Premium,
[PremiumExpirationDate] = @PremiumExpirationDate,
[RenewalReminderDate] = @RenewalReminderDate,
[Storage] = @Storage,
[MaxStorageGb] = @MaxStorageGb,
[Gateway] = @Gateway,
[GatewayCustomerId] = @GatewayCustomerId,
[GatewaySubscriptionId] = @GatewaySubscriptionId,
[ReferenceData] = @ReferenceData,
[LicenseKey] = @LicenseKey,
[Kdf] = @Kdf,
[KdfIterations] = @KdfIterations,
[CreationDate] = @CreationDate,
[RevisionDate] = @RevisionDate,
[ApiKey] = @ApiKey,
[ForcePasswordReset] = @ForcePasswordReset,
[UsesKeyConnector] = @UsesKeyConnector,
[FailedLoginCount] = @FailedLoginCount,
[LastFailedLoginDate] = @LastFailedLoginDate,
[AvatarColor] = @AvatarColor
WHERE
[Id] = @Id
END
GO

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.MySqlMigrations.Migrations;
public partial class RemoveDeviceUnknownVerification : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "UnknownDeviceVerificationEnabled",
table: "User");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "UnknownDeviceVerificationEnabled",
table: "User",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
}
}

View File

@ -1329,9 +1329,6 @@ namespace Bit.MySqlMigrations.Migrations
.HasMaxLength(32)
.HasColumnType("varchar(32)");
b.Property<bool>("UnknownDeviceVerificationEnabled")
.HasColumnType("tinyint(1)");
b.Property<bool>("UsesKeyConnector")
.HasColumnType("tinyint(1)");

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.PostgresMigrations.Migrations;
public partial class RemoveDeviceUnknownVerification : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "UnknownDeviceVerificationEnabled",
table: "User");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "UnknownDeviceVerificationEnabled",
table: "User",
type: "boolean",
nullable: false,
defaultValue: false);
}
}

View File

@ -1340,9 +1340,6 @@ namespace Bit.PostgresMigrations.Migrations
.HasMaxLength(32)
.HasColumnType("character varying(32)");
b.Property<bool>("UnknownDeviceVerificationEnabled")
.HasColumnType("boolean");
b.Property<bool>("UsesKeyConnector")
.HasColumnType("boolean");

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.SqliteMigrations.Migrations;
public partial class RemoveDeviceUnknownVerification : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "UnknownDeviceVerificationEnabled",
table: "User");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "UnknownDeviceVerificationEnabled",
table: "User",
type: "INTEGER",
nullable: false,
defaultValue: false);
}
}

View File

@ -1287,9 +1287,6 @@ namespace Bit.SqliteMigrations.Migrations
.HasMaxLength(32)
.HasColumnType("TEXT");
b.Property<bool>("UnknownDeviceVerificationEnabled")
.HasColumnType("INTEGER");
b.Property<bool>("UsesKeyConnector")
.HasColumnType("INTEGER");