2022-06-29 19:46:41 -04:00
using System.Security.Claims ;
2022-01-21 09:36:25 -05:00
using System.Text.Json ;
2021-02-04 12:54:21 -06:00
using Bit.Core.Context ;
2022-01-11 10:40:51 +01:00
using Bit.Core.Entities ;
2021-11-09 16:37:32 +01:00
using Bit.Core.Enums ;
2017-07-01 23:20:19 -04:00
using Bit.Core.Exceptions ;
2021-11-09 16:37:32 +01:00
using Bit.Core.Models ;
using Bit.Core.Models.Business ;
using Bit.Core.Repositories ;
2021-02-22 15:35:16 -06:00
using Bit.Core.Settings ;
2021-11-09 16:37:32 +01:00
using Bit.Core.Utilities ;
2021-03-22 23:21:43 +01:00
using Fido2NetLib ;
using Fido2NetLib.Objects ;
2021-11-09 16:37:32 +01:00
using Microsoft.AspNetCore.DataProtection ;
using Microsoft.AspNetCore.Identity ;
using Microsoft.Extensions.Logging ;
using Microsoft.Extensions.Options ;
using File = System . IO . File ;
2015-12-08 22:57:38 -05:00
2022-08-29 15:53:48 -04:00
namespace Bit.Core.Services
2022-08-29 14:53:16 -04:00
{
2022-08-29 15:53:48 -04:00
public class UserService : UserManager < User > , IUserService , IDisposable
{
private const string PremiumPlanId = "premium-annually" ;
private const string StoragePlanId = "storage-gb-annually" ;
private readonly IUserRepository _userRepository ;
private readonly ICipherRepository _cipherRepository ;
private readonly IOrganizationUserRepository _organizationUserRepository ;
private readonly IOrganizationRepository _organizationRepository ;
private readonly IMailService _mailService ;
private readonly IPushNotificationService _pushService ;
private readonly IdentityErrorDescriber _identityErrorDescriber ;
private readonly IdentityOptions _identityOptions ;
private readonly IPasswordHasher < User > _passwordHasher ;
private readonly IEnumerable < IPasswordValidator < User > > _passwordValidators ;
private readonly ILicensingService _licenseService ;
private readonly IEventService _eventService ;
private readonly IApplicationCacheService _applicationCacheService ;
private readonly IPaymentService _paymentService ;
private readonly IPolicyRepository _policyRepository ;
private readonly IDataProtector _organizationServiceDataProtector ;
private readonly IReferenceEventService _referenceEventService ;
private readonly IFido2 _fido2 ;
private readonly ICurrentContext _currentContext ;
private readonly IGlobalSettings _globalSettings ;
private readonly IOrganizationService _organizationService ;
private readonly IProviderUserRepository _providerUserRepository ;
private readonly IDeviceRepository _deviceRepository ;
private readonly IStripeSyncService _stripeSyncService ;
public UserService (
IUserRepository userRepository ,
ICipherRepository cipherRepository ,
IOrganizationUserRepository organizationUserRepository ,
IOrganizationRepository organizationRepository ,
IMailService mailService ,
IPushNotificationService pushService ,
IUserStore < User > store ,
IOptions < IdentityOptions > optionsAccessor ,
IPasswordHasher < User > passwordHasher ,
IEnumerable < IUserValidator < User > > userValidators ,
IEnumerable < IPasswordValidator < User > > passwordValidators ,
ILookupNormalizer keyNormalizer ,
IdentityErrorDescriber errors ,
IServiceProvider services ,
ILogger < UserManager < User > > logger ,
ILicensingService licenseService ,
IEventService eventService ,
IApplicationCacheService applicationCacheService ,
IDataProtectionProvider dataProtectionProvider ,
IPaymentService paymentService ,
IPolicyRepository policyRepository ,
IReferenceEventService referenceEventService ,
IFido2 fido2 ,
ICurrentContext currentContext ,
IGlobalSettings globalSettings ,
IOrganizationService organizationService ,
IProviderUserRepository providerUserRepository ,
IDeviceRepository deviceRepository ,
IStripeSyncService stripeSyncService )
: base (
store ,
optionsAccessor ,
passwordHasher ,
userValidators ,
passwordValidators ,
keyNormalizer ,
errors ,
services ,
logger )
{
_userRepository = userRepository ;
_cipherRepository = cipherRepository ;
_organizationUserRepository = organizationUserRepository ;
_organizationRepository = organizationRepository ;
_mailService = mailService ;
_pushService = pushService ;
_identityOptions = optionsAccessor ? . Value ? ? new IdentityOptions ( ) ;
_identityErrorDescriber = errors ;
_passwordHasher = passwordHasher ;
_passwordValidators = passwordValidators ;
_licenseService = licenseService ;
_eventService = eventService ;
_applicationCacheService = applicationCacheService ;
_paymentService = paymentService ;
_policyRepository = policyRepository ;
_organizationServiceDataProtector = dataProtectionProvider . CreateProtector (
"OrganizationServiceDataProtector" ) ;
_referenceEventService = referenceEventService ;
_fido2 = fido2 ;
_currentContext = currentContext ;
_globalSettings = globalSettings ;
_organizationService = organizationService ;
_providerUserRepository = providerUserRepository ;
_deviceRepository = deviceRepository ;
_stripeSyncService = stripeSyncService ;
}
public Guid ? GetProperUserId ( ClaimsPrincipal principal )
{
if ( ! Guid . TryParse ( GetUserId ( principal ) , out var userIdGuid ) )
{
return null ;
}
2017-01-14 10:02:37 -05:00
2022-08-29 15:53:48 -04:00
return userIdGuid ;
2022-08-29 14:53:16 -04:00
}
2017-01-24 22:15:21 -05:00
2022-08-29 15:53:48 -04:00
public async Task < User > GetUserByIdAsync ( string userId )
2022-08-29 14:53:16 -04:00
{
2022-08-29 15:53:48 -04:00
if ( _currentContext ? . User ! = null & &
string . Equals ( _currentContext . User . Id . ToString ( ) , userId , StringComparison . InvariantCultureIgnoreCase ) )
{
return _currentContext . User ;
}
if ( ! Guid . TryParse ( userId , out var userIdGuid ) )
{
return null ;
}
_currentContext . User = await _userRepository . GetByIdAsync ( userIdGuid ) ;
2017-01-24 22:15:21 -05:00
return _currentContext . User ;
2017-01-11 21:46:36 -05:00
}
2022-08-29 15:53:48 -04:00
public async Task < User > GetUserByIdAsync ( Guid userId )
2015-12-08 22:57:38 -05:00
{
2022-08-29 15:53:48 -04:00
if ( _currentContext ? . User ! = null & & _currentContext . User . Id = = userId )
{
return _currentContext . User ;
}
2017-01-24 22:46:54 -05:00
2022-08-29 15:53:48 -04:00
_currentContext . User = await _userRepository . GetByIdAsync ( userId ) ;
2022-08-29 14:53:16 -04:00
return _currentContext . User ;
2017-01-24 22:46:54 -05:00
}
2022-08-29 15:53:48 -04:00
public async Task < User > GetUserByPrincipalAsync ( ClaimsPrincipal principal )
2017-01-14 10:02:37 -05:00
{
2022-08-29 15:53:48 -04:00
var userId = GetProperUserId ( principal ) ;
if ( ! userId . HasValue )
{
return null ;
}
2022-08-29 14:53:16 -04:00
2022-08-29 15:53:48 -04:00
return await GetUserByIdAsync ( userId . Value ) ;
}
2022-08-29 14:53:16 -04:00
2022-08-29 15:53:48 -04:00
public async Task < DateTime > GetAccountRevisionDateByIdAsync ( Guid userId )
2015-12-08 22:57:38 -05:00
{
2022-08-29 15:53:48 -04:00
return await _userRepository . GetAccountRevisionDateAsync ( userId ) ;
2022-08-29 14:53:16 -04:00
}
2016-02-21 00:15:17 -05:00
2022-08-29 15:53:48 -04:00
public async Task SaveUserAsync ( User user , bool push = false )
2022-08-29 14:53:16 -04:00
{
2022-08-29 15:53:48 -04:00
if ( user . Id = = default ( Guid ) )
{
throw new ApplicationException ( "Use register method to create a new user." ) ;
}
user . RevisionDate = user . AccountRevisionDate = DateTime . UtcNow ;
await _userRepository . ReplaceAsync ( user ) ;
if ( push )
{
// push
await _pushService . PushSyncSettingsAsync ( user . Id ) ;
}
2015-12-08 22:57:38 -05:00
}
2022-08-29 15:53:48 -04:00
public override async Task < IdentityResult > DeleteAsync ( User user )
2017-04-27 17:28:39 -04:00
{
2022-08-29 15:53:48 -04:00
// Check if user is the only owner of any organizations.
var onlyOwnerCount = await _organizationUserRepository . GetCountByOnlyOwnerAsync ( user . Id ) ;
if ( onlyOwnerCount > 0 )
2017-04-27 17:28:39 -04:00
{
2022-08-29 15:53:48 -04:00
var deletedOrg = false ;
var orgs = await _organizationUserRepository . GetManyDetailsByUserAsync ( user . Id ,
OrganizationUserStatusType . Confirmed ) ;
if ( orgs . Count = = 1 )
2017-04-27 17:28:39 -04:00
{
2022-08-29 15:53:48 -04:00
var org = await _organizationRepository . GetByIdAsync ( orgs . First ( ) . OrganizationId ) ;
if ( org ! = null & & ( ! org . Enabled | | string . IsNullOrWhiteSpace ( org . GatewaySubscriptionId ) ) )
2018-12-03 10:56:55 -05:00
{
2022-08-29 15:53:48 -04:00
var orgCount = await _organizationUserRepository . GetCountByOrganizationIdAsync ( org . Id ) ;
if ( orgCount < = 1 )
{
await _organizationRepository . DeleteAsync ( org ) ;
deletedOrg = true ;
}
2018-12-03 10:56:55 -05:00
}
}
2022-08-29 15:53:48 -04:00
if ( ! deletedOrg )
{
return IdentityResult . Failed ( new IdentityError
{
Description = "Cannot delete this user because it is the sole owner of at least one organization. Please delete these organizations or upgrade another user." ,
} ) ;
}
2017-04-27 17:28:39 -04:00
}
2022-08-29 15:53:48 -04:00
var onlyOwnerProviderCount = await _providerUserRepository . GetCountByOnlyOwnerAsync ( user . Id ) ;
if ( onlyOwnerProviderCount > 0 )
2021-09-15 20:34:06 +02:00
{
return IdentityResult . Failed ( new IdentityError
{
2022-08-29 15:53:48 -04:00
Description = "Cannot delete this user because it is the sole owner of at least one provider. Please delete these providers or upgrade another user." ,
2021-09-15 20:34:06 +02:00
} ) ;
}
2022-08-29 15:53:48 -04:00
if ( ! string . IsNullOrWhiteSpace ( user . GatewaySubscriptionId ) )
2017-07-11 11:19:58 -04:00
{
2022-08-29 15:53:48 -04:00
try
{
await CancelPremiumAsync ( user , null , true ) ;
}
catch ( GatewayException ) { }
}
await _userRepository . DeleteAsync ( user ) ;
await _referenceEventService . RaiseEventAsync (
new ReferenceEvent ( ReferenceEventType . DeleteAccount , user ) ) ;
await _pushService . PushLogOutAsync ( user . Id ) ;
return IdentityResult . Success ;
2017-04-27 17:28:39 -04:00
}
2022-08-29 15:53:48 -04:00
public async Task < IdentityResult > DeleteAsync ( User user , string token )
2017-08-09 10:53:42 -04:00
{
2022-08-29 15:53:48 -04:00
if ( ! ( await VerifyUserTokenAsync ( user , TokenOptions . DefaultProvider , "DeleteAccount" , token ) ) )
2017-08-09 10:53:42 -04:00
{
2022-08-29 15:53:48 -04:00
return IdentityResult . Failed ( ErrorDescriber . InvalidToken ( ) ) ;
2017-08-09 10:53:42 -04:00
}
2022-08-29 15:53:48 -04:00
return await DeleteAsync ( user ) ;
2022-08-29 14:53:16 -04:00
}
2022-08-29 15:53:48 -04:00
public async Task SendDeleteConfirmationAsync ( string email )
2022-08-29 14:53:16 -04:00
{
2022-08-29 15:53:48 -04:00
var user = await _userRepository . GetByEmailAsync ( email ) ;
if ( user = = null )
{
// No user exists.
return ;
}
2022-08-29 14:53:16 -04:00
2022-08-29 15:53:48 -04:00
var token = await base . GenerateUserTokenAsync ( user , TokenOptions . DefaultProvider , "DeleteAccount" ) ;
await _mailService . SendVerifyDeleteEmailAsync ( user . Email , user . Id , token ) ;
2022-08-29 14:53:16 -04:00
}
2018-05-24 16:53:07 -04:00
2022-08-29 15:53:48 -04:00
public async Task < IdentityResult > RegisterUserAsync ( User user , string masterPassword ,
string token , Guid ? orgUserId )
2022-08-29 14:53:16 -04:00
{
2022-08-29 15:53:48 -04:00
var tokenValid = false ;
if ( _globalSettings . DisableUserRegistration & & ! string . IsNullOrWhiteSpace ( token ) & & orgUserId . HasValue )
{
tokenValid = CoreHelpers . UserInviteTokenIsValid ( _organizationServiceDataProtector , token ,
user . Email , orgUserId . Value , _globalSettings ) ;
}
2017-08-28 07:50:57 -04:00
2022-08-29 15:53:48 -04:00
if ( _globalSettings . DisableUserRegistration & & ! tokenValid )
2020-02-28 09:14:33 -05:00
{
2022-08-29 15:53:48 -04:00
throw new BadRequestException ( "Open registration has been disabled by the system administrator." ) ;
}
if ( orgUserId . HasValue )
{
var orgUser = await _organizationUserRepository . GetByIdAsync ( orgUserId . Value ) ;
if ( orgUser ! = null )
2020-02-28 09:14:33 -05:00
{
2022-08-29 15:53:48 -04:00
var twoFactorPolicy = await _policyRepository . GetByOrganizationIdTypeAsync ( orgUser . OrganizationId ,
PolicyType . TwoFactorAuthentication ) ;
if ( twoFactorPolicy ! = null & & twoFactorPolicy . Enabled )
2020-02-28 09:14:33 -05:00
{
2022-08-29 15:53:48 -04:00
user . SetTwoFactorProviders ( new Dictionary < TwoFactorProviderType , TwoFactorProvider >
2022-08-29 14:53:16 -04:00
{
2022-08-29 15:53:48 -04:00
[TwoFactorProviderType.Email] = new TwoFactorProvider
{
MetaData = new Dictionary < string , object > { [ "Email" ] = user . Email . ToLowerInvariant ( ) } ,
Enabled = true
}
} ) ;
SetTwoFactorProvider ( user , TwoFactorProviderType . Email ) ;
}
2020-02-28 09:14:33 -05:00
}
}
2020-08-13 17:30:10 -04:00
2022-08-29 15:53:48 -04:00
user . ApiKey = CoreHelpers . SecureRandomString ( 30 ) ;
var result = await base . CreateAsync ( user , masterPassword ) ;
if ( result = = IdentityResult . Success )
{
await _mailService . SendWelcomeEmailAsync ( user ) ;
await _referenceEventService . RaiseEventAsync ( new ReferenceEvent ( ReferenceEventType . Signup , user ) ) ;
}
2022-08-29 14:53:16 -04:00
2022-08-29 15:53:48 -04:00
return result ;
2020-08-13 17:30:10 -04:00
}
2022-08-29 15:53:48 -04:00
public async Task < IdentityResult > RegisterUserAsync ( User user )
2020-08-13 17:30:10 -04:00
{
2022-08-29 15:53:48 -04:00
var result = await base . CreateAsync ( user ) ;
if ( result = = IdentityResult . Success )
{
await _mailService . SendWelcomeEmailAsync ( user ) ;
await _referenceEventService . RaiseEventAsync ( new ReferenceEvent ( ReferenceEventType . Signup , user ) ) ;
}
2015-12-08 22:57:38 -05:00
2022-08-29 15:53:48 -04:00
return result ;
2015-12-08 22:57:38 -05:00
}
2022-08-29 15:53:48 -04:00
public async Task SendMasterPasswordHintAsync ( string email )
2015-12-08 22:57:38 -05:00
{
2022-08-29 15:53:48 -04:00
var user = await _userRepository . GetByEmailAsync ( email ) ;
if ( user = = null )
{
// No user exists. Do we want to send an email telling them this in the future?
return ;
}
2015-12-08 22:57:38 -05:00
2022-08-29 15:53:48 -04:00
if ( string . IsNullOrWhiteSpace ( user . MasterPasswordHint ) )
{
await _mailService . SendNoMasterPasswordHintEmailAsync ( email ) ;
return ;
}
2015-12-08 22:57:38 -05:00
2022-08-29 15:53:48 -04:00
await _mailService . SendMasterPasswordHintEmailAsync ( email , user . MasterPasswordHint ) ;
2015-12-08 22:57:38 -05:00
}
2022-08-29 15:53:48 -04:00
public async Task SendTwoFactorEmailAsync ( User user , bool isBecauseNewDeviceLogin = false )
2017-06-20 09:21:35 -04:00
{
2022-08-29 15:53:48 -04:00
var provider = user . GetTwoFactorProvider ( TwoFactorProviderType . Email ) ;
if ( provider = = null | | provider . MetaData = = null | | ! provider . MetaData . ContainsKey ( "Email" ) )
{
throw new ArgumentNullException ( "No email." ) ;
}
2017-06-20 09:21:35 -04:00
2022-08-29 15:53:48 -04:00
var email = ( ( string ) provider . MetaData [ "Email" ] ) . ToLowerInvariant ( ) ;
var token = await base . GenerateUserTokenAsync ( user , TokenOptions . DefaultEmailProvider ,
"2faEmail:" + email ) ;
2022-04-01 17:08:47 -03:00
2022-08-29 15:53:48 -04:00
if ( isBecauseNewDeviceLogin )
2017-06-20 09:21:35 -04:00
{
2022-08-29 15:53:48 -04:00
await _mailService . SendNewDeviceLoginTwoFactorEmailAsync ( email , token ) ;
}
else
{
await _mailService . SendTwoFactorEmailAsync ( email , token ) ;
}
2017-06-20 09:21:35 -04:00
}
2022-08-29 15:53:48 -04:00
public async Task < bool > VerifyTwoFactorEmailAsync ( User user , string token )
2017-06-21 22:33:45 -04:00
{
2022-08-29 15:53:48 -04:00
var provider = user . GetTwoFactorProvider ( TwoFactorProviderType . Email ) ;
if ( provider = = null | | provider . MetaData = = null | | ! provider . MetaData . ContainsKey ( "Email" ) )
{
throw new ArgumentNullException ( "No email." ) ;
}
2017-06-21 22:33:45 -04:00
2022-08-29 15:53:48 -04:00
var email = ( ( string ) provider . MetaData [ "Email" ] ) . ToLowerInvariant ( ) ;
return await base . VerifyUserTokenAsync ( user , TokenOptions . DefaultEmailProvider ,
"2faEmail:" + email , token ) ;
}
2017-06-21 22:33:45 -04:00
2022-08-29 15:53:48 -04:00
public async Task < CredentialCreateOptions > StartWebAuthnRegistrationAsync ( User user )
2022-08-29 14:53:16 -04:00
{
2022-08-29 15:53:48 -04:00
var providers = user . GetTwoFactorProviders ( ) ;
if ( providers = = null )
{
providers = new Dictionary < TwoFactorProviderType , TwoFactorProvider > ( ) ;
}
var provider = user . GetTwoFactorProvider ( TwoFactorProviderType . WebAuthn ) ;
if ( provider = = null )
{
provider = new TwoFactorProvider
{
Enabled = false
} ;
}
if ( provider . MetaData = = null )
{
provider . MetaData = new Dictionary < string , object > ( ) ;
}
2018-10-08 14:38:11 -04:00
2022-08-29 15:53:48 -04:00
var fidoUser = new Fido2User
{
DisplayName = user . Name ,
Name = user . Email ,
Id = user . Id . ToByteArray ( ) ,
} ;
2018-10-08 14:38:11 -04:00
2022-08-29 15:53:48 -04:00
var excludeCredentials = provider . MetaData
. Where ( k = > k . Key . StartsWith ( "Key" ) )
. Select ( k = > new TwoFactorProvider . WebAuthnData ( ( dynamic ) k . Value ) . Descriptor )
. ToList ( ) ;
2018-10-10 15:21:54 -04:00
2022-08-29 15:53:48 -04:00
var authenticatorSelection = new AuthenticatorSelection
{
AuthenticatorAttachment = null ,
RequireResidentKey = false ,
UserVerification = UserVerificationRequirement . Discouraged
} ;
var options = _fido2 . RequestNewCredential ( fidoUser , excludeCredentials , authenticatorSelection , AttestationConveyancePreference . None ) ;
2021-03-22 23:21:43 +01:00
2022-08-29 15:53:48 -04:00
provider . MetaData [ "pending" ] = options . ToJson ( ) ;
providers [ TwoFactorProviderType . WebAuthn ] = provider ;
user . SetTwoFactorProviders ( providers ) ;
await UpdateTwoFactorProviderAsync ( user , TwoFactorProviderType . WebAuthn , false ) ;
2021-03-22 23:21:43 +01:00
2022-08-29 15:53:48 -04:00
return options ;
}
2021-03-22 23:21:43 +01:00
2022-08-29 15:53:48 -04:00
public async Task < bool > CompleteWebAuthRegistrationAsync ( User user , int id , string name , AuthenticatorAttestationRawResponse attestationResponse )
2022-08-29 14:53:16 -04:00
{
2022-08-29 15:53:48 -04:00
var keyId = $"Key{id}" ;
2021-03-22 23:21:43 +01:00
2022-08-29 15:53:48 -04:00
var provider = user . GetTwoFactorProvider ( TwoFactorProviderType . WebAuthn ) ;
if ( ! provider ? . MetaData ? . ContainsKey ( "pending" ) ? ? true )
{
return false ;
}
2021-03-22 23:21:43 +01:00
2022-08-29 15:53:48 -04:00
var options = CredentialCreateOptions . FromJson ( ( string ) provider . MetaData [ "pending" ] ) ;
2021-03-22 23:21:43 +01:00
2022-08-29 15:53:48 -04:00
// Callback to ensure credential id is unique. Always return true since we don't care if another
// account uses the same 2fa key.
IsCredentialIdUniqueToUserAsyncDelegate callback = args = > Task . FromResult ( true ) ;
2017-06-21 22:33:45 -04:00
2022-08-29 15:53:48 -04:00
var success = await _fido2 . MakeNewCredentialAsync ( attestationResponse , options , callback ) ;
2018-10-08 14:38:11 -04:00
2022-08-29 15:53:48 -04:00
provider . MetaData . Remove ( "pending" ) ;
provider . MetaData [ keyId ] = new TwoFactorProvider . WebAuthnData
{
Name = name ,
Descriptor = new PublicKeyCredentialDescriptor ( success . Result . CredentialId ) ,
PublicKey = success . Result . PublicKey ,
UserHandle = success . Result . User . Id ,
SignatureCounter = success . Result . Counter ,
CredType = success . Result . CredType ,
RegDate = DateTime . Now ,
AaGuid = success . Result . Aaguid
} ;
2018-10-08 14:38:11 -04:00
2022-08-29 15:53:48 -04:00
var providers = user . GetTwoFactorProviders ( ) ;
providers [ TwoFactorProviderType . WebAuthn ] = provider ;
user . SetTwoFactorProviders ( providers ) ;
await UpdateTwoFactorProviderAsync ( user , TwoFactorProviderType . WebAuthn ) ;
2018-10-08 14:38:11 -04:00
2022-08-29 15:53:48 -04:00
return true ;
2017-06-21 22:33:45 -04:00
}
2022-08-29 15:53:48 -04:00
public async Task < bool > DeleteWebAuthnKeyAsync ( User user , int id )
2017-07-01 23:20:19 -04:00
{
2022-08-29 15:53:48 -04:00
var providers = user . GetTwoFactorProviders ( ) ;
if ( providers = = null )
{
return false ;
}
2017-07-01 23:20:19 -04:00
2022-08-29 15:53:48 -04:00
var keyName = $"Key{id}" ;
var provider = user . GetTwoFactorProvider ( TwoFactorProviderType . WebAuthn ) ;
if ( ! provider ? . MetaData ? . ContainsKey ( keyName ) ? ? true )
{
return false ;
}
2022-08-29 14:53:16 -04:00
2022-08-29 15:53:48 -04:00
if ( provider . MetaData . Count < 2 )
{
return false ;
}
2017-07-01 23:20:19 -04:00
2022-08-29 15:53:48 -04:00
provider . MetaData . Remove ( keyName ) ;
providers [ TwoFactorProviderType . WebAuthn ] = provider ;
user . SetTwoFactorProviders ( providers ) ;
await UpdateTwoFactorProviderAsync ( user , TwoFactorProviderType . WebAuthn ) ;
return true ;
2022-08-29 14:53:16 -04:00
}
2015-12-08 22:57:38 -05:00
2022-08-29 15:53:48 -04:00
public async Task SendEmailVerificationAsync ( User user )
2022-08-29 14:53:16 -04:00
{
2022-08-29 15:53:48 -04:00
if ( user . EmailVerified )
{
throw new BadRequestException ( "Email already verified." ) ;
}
var token = await base . GenerateEmailConfirmationTokenAsync ( user ) ;
await _mailService . SendVerifyEmailEmailAsync ( user . Email , user . Id , token ) ;
2015-12-08 22:57:38 -05:00
}
2022-08-29 15:53:48 -04:00
public async Task InitiateEmailChangeAsync ( User user , string newEmail )
2022-08-29 14:53:16 -04:00
{
2022-08-29 15:53:48 -04:00
var existingUser = await _userRepository . GetByEmailAsync ( newEmail ) ;
if ( existingUser ! = null )
{
await _mailService . SendChangeEmailAlreadyExistsEmailAsync ( user . Email , newEmail ) ;
return ;
}
2022-08-29 14:53:16 -04:00
2022-08-29 15:53:48 -04:00
var token = await base . GenerateChangeEmailTokenAsync ( user , newEmail ) ;
await _mailService . SendChangeEmailEmailAsync ( newEmail , token ) ;
2022-08-29 14:53:16 -04:00
}
2022-08-29 15:53:48 -04:00
public async Task < IdentityResult > ChangeEmailAsync ( User user , string masterPassword , string newEmail ,
string newMasterPassword , string token , string key )
2015-12-08 22:57:38 -05:00
{
2022-08-29 15:53:48 -04:00
var verifyPasswordResult = _passwordHasher . VerifyHashedPassword ( user , user . MasterPassword , masterPassword ) ;
if ( verifyPasswordResult = = PasswordVerificationResult . Failed )
2015-12-08 22:57:38 -05:00
{
2022-08-29 15:53:48 -04:00
return IdentityResult . Failed ( _identityErrorDescriber . PasswordMismatch ( ) ) ;
2015-12-08 22:57:38 -05:00
}
2022-08-29 14:53:16 -04:00
2022-08-29 15:53:48 -04:00
if ( ! await base . VerifyUserTokenAsync ( user , _identityOptions . Tokens . ChangeEmailTokenProvider ,
GetChangeEmailTokenPurpose ( newEmail ) , token ) )
{
return IdentityResult . Failed ( _identityErrorDescriber . InvalidToken ( ) ) ;
2015-12-08 22:57:38 -05:00
}
2022-08-29 14:53:16 -04:00
2022-08-29 15:53:48 -04:00
var existingUser = await _userRepository . GetByEmailAsync ( newEmail ) ;
if ( existingUser ! = null & & existingUser . Id ! = user . Id )
{
return IdentityResult . Failed ( _identityErrorDescriber . DuplicateEmail ( newEmail ) ) ;
}
2022-08-29 14:53:16 -04:00
2022-08-29 15:53:48 -04:00
var previousState = new
{
Key = user . Key ,
MasterPassword = user . MasterPassword ,
SecurityStamp = user . SecurityStamp ,
Email = user . Email
} ;
2022-08-29 14:53:16 -04:00
2016-02-21 00:15:17 -05:00
var result = await UpdatePasswordHash ( user , newMasterPassword ) ;
2020-03-27 14:36:37 -04:00
if ( ! result . Succeeded )
2016-02-21 00:15:17 -05:00
{
return result ;
}
2015-12-08 22:57:38 -05:00
2022-08-29 14:53:16 -04:00
user . Key = key ;
2022-08-29 15:53:48 -04:00
user . Email = newEmail ;
user . EmailVerified = true ;
user . RevisionDate = user . AccountRevisionDate = DateTime . UtcNow ;
2022-08-29 14:53:16 -04:00
await _userRepository . ReplaceAsync ( user ) ;
2022-08-29 15:53:48 -04:00
if ( user . Gateway = = GatewayType . Stripe )
{
try
{
await _stripeSyncService . UpdateCustomerEmailAddress ( user . GatewayCustomerId ,
user . BillingEmailAddress ( ) ) ;
}
catch ( Exception ex )
{
//if sync to strip fails, update email and securityStamp to previous
user . Key = previousState . Key ;
user . Email = previousState . Email ;
user . RevisionDate = user . AccountRevisionDate = DateTime . UtcNow ;
user . MasterPassword = previousState . MasterPassword ;
user . SecurityStamp = previousState . SecurityStamp ;
await _userRepository . ReplaceAsync ( user ) ;
return IdentityResult . Failed ( new IdentityError
{
Description = ex . Message
} ) ;
}
}
2018-08-28 08:29:54 -04:00
await _pushService . PushLogOutAsync ( user . Id ) ;
2016-10-05 22:03:02 -04:00
2015-12-08 22:57:38 -05:00
return IdentityResult . Success ;
}
2022-08-29 15:53:48 -04:00
public override Task < IdentityResult > ChangePasswordAsync ( User user , string masterPassword , string newMasterPassword )
2015-12-08 22:57:38 -05:00
{
2022-08-29 15:53:48 -04:00
throw new NotImplementedException ( ) ;
2015-12-08 22:57:38 -05:00
}
2022-08-29 15:53:48 -04:00
public async Task < IdentityResult > ChangePasswordAsync ( User user , string masterPassword , string newMasterPassword , string passwordHint ,
string key )
2015-12-08 22:57:38 -05:00
{
2022-08-29 15:53:48 -04:00
if ( user = = null )
{
throw new ArgumentNullException ( nameof ( user ) ) ;
}
2016-02-21 01:10:31 -05:00
2022-08-29 15:53:48 -04:00
if ( await CheckPasswordAsync ( user , masterPassword ) )
{
var result = await UpdatePasswordHash ( user , newMasterPassword ) ;
if ( ! result . Succeeded )
{
return result ;
}
2017-12-01 10:07:14 -05:00
2022-08-29 15:53:48 -04:00
user . RevisionDate = user . AccountRevisionDate = DateTime . UtcNow ;
user . Key = key ;
user . MasterPasswordHint = passwordHint ;
2017-05-31 09:54:32 -04:00
2022-08-29 15:53:48 -04:00
await _userRepository . ReplaceAsync ( user ) ;
await _eventService . LogUserEventAsync ( user . Id , EventType . User_ChangedPassword ) ;
await _pushService . PushLogOutAsync ( user . Id ) ;
2017-05-31 09:54:32 -04:00
2022-08-29 15:53:48 -04:00
return IdentityResult . Success ;
}
2020-08-13 17:30:10 -04:00
2022-08-29 15:53:48 -04:00
Logger . LogWarning ( "Change password failed for user {userId}." , user . Id ) ;
return IdentityResult . Failed ( _identityErrorDescriber . PasswordMismatch ( ) ) ;
}
2020-08-12 17:03:09 -04:00
2022-08-29 15:53:48 -04:00
public async Task < IdentityResult > SetPasswordAsync ( User user , string masterPassword , string key ,
string orgIdentifier = null )
2022-08-29 14:53:16 -04:00
{
2022-08-29 15:53:48 -04:00
if ( user = = null )
{
throw new ArgumentNullException ( nameof ( user ) ) ;
}
2020-08-13 17:30:10 -04:00
2022-08-29 15:53:48 -04:00
if ( ! string . IsNullOrWhiteSpace ( user . MasterPassword ) )
{
Logger . LogWarning ( "Change password failed for user {userId} - already has password." , user . Id ) ;
return IdentityResult . Failed ( _identityErrorDescriber . UserAlreadyHasPassword ( ) ) ;
}
2020-08-12 17:03:09 -04:00
2022-08-29 15:53:48 -04:00
var result = await UpdatePasswordHash ( user , masterPassword , true , false ) ;
if ( ! result . Succeeded )
{
return result ;
}
2020-08-12 17:03:09 -04:00
2022-08-29 15:53:48 -04:00
user . RevisionDate = user . AccountRevisionDate = DateTime . UtcNow ;
user . Key = key ;
2021-11-09 16:37:32 +01:00
2022-08-29 15:53:48 -04:00
await _userRepository . ReplaceAsync ( user ) ;
await _eventService . LogUserEventAsync ( user . Id , EventType . User_ChangedPassword ) ;
2021-11-09 16:37:32 +01:00
2022-08-29 15:53:48 -04:00
if ( ! string . IsNullOrWhiteSpace ( orgIdentifier ) )
{
await _organizationService . AcceptUserAsync ( orgIdentifier , user , this ) ;
}
return IdentityResult . Success ;
2020-08-12 17:03:09 -04:00
}
2021-10-25 15:09:14 +02:00
2022-08-29 15:53:48 -04:00
public async Task < IdentityResult > SetKeyConnectorKeyAsync ( User user , string key , string orgIdentifier )
{
var identityResult = CheckCanUseKeyConnector ( user ) ;
if ( identityResult ! = null )
{
return identityResult ;
}
2021-10-25 15:09:14 +02:00
2022-08-29 15:53:48 -04:00
user . RevisionDate = user . AccountRevisionDate = DateTime . UtcNow ;
user . Key = key ;
user . UsesKeyConnector = true ;
2021-10-25 15:09:14 +02:00
2022-08-29 15:53:48 -04:00
await _userRepository . ReplaceAsync ( user ) ;
await _eventService . LogUserEventAsync ( user . Id , EventType . User_MigratedKeyToKeyConnector ) ;
2021-10-25 15:09:14 +02:00
2022-08-29 15:53:48 -04:00
await _organizationService . AcceptUserAsync ( orgIdentifier , user , this ) ;
2021-10-25 15:09:14 +02:00
2022-08-29 15:53:48 -04:00
return IdentityResult . Success ;
2021-10-25 15:09:14 +02:00
}
2022-08-29 15:53:48 -04:00
public async Task < IdentityResult > ConvertToKeyConnectorAsync ( User user )
2021-11-18 21:56:13 +01:00
{
2022-08-29 15:53:48 -04:00
var identityResult = CheckCanUseKeyConnector ( user ) ;
if ( identityResult ! = null )
{
return identityResult ;
}
2021-11-18 21:56:13 +01:00
2022-08-29 15:53:48 -04:00
user . RevisionDate = user . AccountRevisionDate = DateTime . UtcNow ;
user . MasterPassword = null ;
user . UsesKeyConnector = true ;
2021-11-18 21:56:13 +01:00
2022-08-29 15:53:48 -04:00
await _userRepository . ReplaceAsync ( user ) ;
await _eventService . LogUserEventAsync ( user . Id , EventType . User_MigratedKeyToKeyConnector ) ;
2021-11-18 21:56:13 +01:00
2022-08-29 15:53:48 -04:00
return IdentityResult . Success ;
2021-11-18 21:56:13 +01:00
}
2022-08-29 15:53:48 -04:00
private IdentityResult CheckCanUseKeyConnector ( User user )
2021-11-09 16:37:32 +01:00
{
2022-08-29 15:53:48 -04:00
if ( user = = null )
{
throw new ArgumentNullException ( nameof ( user ) ) ;
}
2021-11-09 16:37:32 +01:00
2022-08-29 15:53:48 -04:00
if ( user . UsesKeyConnector )
{
Logger . LogWarning ( "Already uses Key Connector." ) ;
return IdentityResult . Failed ( _identityErrorDescriber . UserAlreadyHasPassword ( ) ) ;
}
2021-11-09 16:37:32 +01:00
2022-08-29 15:53:48 -04:00
if ( _currentContext . Organizations . Any ( u = >
u . Type is OrganizationUserType . Owner or OrganizationUserType . Admin ) )
{
throw new BadRequestException ( "Cannot use Key Connector when admin or owner of an organization." ) ;
}
2021-11-09 16:37:32 +01:00
2022-08-29 15:53:48 -04:00
return null ;
2021-11-09 16:37:32 +01:00
}
2022-08-29 15:53:48 -04:00
public async Task < IdentityResult > AdminResetPasswordAsync ( OrganizationUserType callingUserType , Guid orgId , Guid id , string newMasterPassword , string key )
2021-04-20 16:58:57 -05:00
{
2022-08-29 15:53:48 -04:00
// Org must be able to use reset password
var org = await _organizationRepository . GetByIdAsync ( orgId ) ;
if ( org = = null | | ! org . UseResetPassword )
{
throw new BadRequestException ( "Organization does not allow password reset." ) ;
}
2021-11-09 16:37:32 +01:00
2022-08-29 15:53:48 -04:00
// Enterprise policy must be enabled
var resetPasswordPolicy =
await _policyRepository . GetByOrganizationIdTypeAsync ( orgId , PolicyType . ResetPassword ) ;
if ( resetPasswordPolicy = = null | | ! resetPasswordPolicy . Enabled )
{
throw new BadRequestException ( "Organization does not have the password reset policy enabled." ) ;
}
2021-11-09 16:37:32 +01:00
2022-08-29 15:53:48 -04:00
// Org User must be confirmed and have a ResetPasswordKey
var orgUser = await _organizationUserRepository . GetByIdAsync ( id ) ;
if ( orgUser = = null | | orgUser . Status ! = OrganizationUserStatusType . Confirmed | |
orgUser . OrganizationId ! = orgId | | string . IsNullOrEmpty ( orgUser . ResetPasswordKey ) | |
! orgUser . UserId . HasValue )
{
throw new BadRequestException ( "Organization User not valid" ) ;
}
2021-11-09 16:37:32 +01:00
2022-08-29 15:53:48 -04:00
// Calling User must be of higher/equal user type to reset user's password
var canAdjustPassword = false ;
switch ( callingUserType )
{
case OrganizationUserType . Owner :
canAdjustPassword = true ;
break ;
case OrganizationUserType . Admin :
canAdjustPassword = orgUser . Type ! = OrganizationUserType . Owner ;
break ;
case OrganizationUserType . Custom :
canAdjustPassword = orgUser . Type ! = OrganizationUserType . Owner & &
orgUser . Type ! = OrganizationUserType . Admin ;
break ;
}
2021-05-19 09:40:32 -05:00
2022-08-29 15:53:48 -04:00
if ( ! canAdjustPassword )
{
throw new BadRequestException ( "Calling user does not have permission to reset this user's master password" ) ;
}
2021-05-19 09:40:32 -05:00
2022-08-29 15:53:48 -04:00
var user = await GetUserByIdAsync ( orgUser . UserId . Value ) ;
if ( user = = null )
{
throw new NotFoundException ( ) ;
}
2021-11-09 16:37:32 +01:00
2022-08-29 15:53:48 -04:00
if ( user . UsesKeyConnector )
{
throw new BadRequestException ( "Cannot reset password of a user with Key Connector." ) ;
}
2021-11-09 16:37:32 +01:00
2022-08-29 15:53:48 -04:00
var result = await UpdatePasswordHash ( user , newMasterPassword ) ;
if ( ! result . Succeeded )
{
return result ;
}
2021-04-20 16:58:57 -05:00
2022-08-29 15:53:48 -04:00
user . RevisionDate = user . AccountRevisionDate = DateTime . UtcNow ;
user . Key = key ;
user . ForcePasswordReset = true ;
2021-04-20 16:58:57 -05:00
2022-08-29 15:53:48 -04:00
await _userRepository . ReplaceAsync ( user ) ;
await _mailService . SendAdminResetPasswordEmailAsync ( user . Email , user . Name , org . Name ) ;
await _eventService . LogOrganizationUserEventAsync ( orgUser , EventType . OrganizationUser_AdminResetPassword ) ;
await _pushService . PushLogOutAsync ( user . Id ) ;
2021-04-20 16:58:57 -05:00
2022-08-29 15:53:48 -04:00
return IdentityResult . Success ;
2021-04-20 16:58:57 -05:00
}
2021-11-09 16:37:32 +01:00
2022-08-29 15:53:48 -04:00
public async Task < IdentityResult > UpdateTempPasswordAsync ( User user , string newMasterPassword , string key , string hint )
2021-07-22 09:20:14 -05:00
{
2022-08-29 15:53:48 -04:00
if ( ! user . ForcePasswordReset )
{
throw new BadRequestException ( "User does not have a temporary password to update." ) ;
}
2021-07-22 09:20:14 -05:00
var result = await UpdatePasswordHash ( user , newMasterPassword ) ;
if ( ! result . Succeeded )
{
return result ;
}
user . RevisionDate = user . AccountRevisionDate = DateTime . UtcNow ;
2022-08-29 15:53:48 -04:00
user . ForcePasswordReset = false ;
2021-07-22 09:20:14 -05:00
user . Key = key ;
2022-08-29 15:53:48 -04:00
user . MasterPasswordHint = hint ;
2021-07-22 09:20:14 -05:00
await _userRepository . ReplaceAsync ( user ) ;
2022-08-29 15:53:48 -04:00
await _mailService . SendUpdatedTempPasswordEmailAsync ( user . Email , user . Name ) ;
await _eventService . LogUserEventAsync ( user . Id , EventType . User_UpdatedTempPassword ) ;
2021-07-22 09:20:14 -05:00
await _pushService . PushLogOutAsync ( user . Id ) ;
2022-08-29 15:53:48 -04:00
2021-07-22 09:20:14 -05:00
return IdentityResult . Success ;
}
2017-05-31 09:54:32 -04:00
2022-08-29 15:53:48 -04:00
public async Task < IdentityResult > ChangeKdfAsync ( User user , string masterPassword , string newMasterPassword ,
string key , KdfType kdf , int kdfIterations )
2017-05-31 09:54:32 -04:00
{
2022-08-29 15:53:48 -04:00
if ( user = = null )
{
throw new ArgumentNullException ( nameof ( user ) ) ;
}
if ( await CheckPasswordAsync ( user , masterPassword ) )
{
var result = await UpdatePasswordHash ( user , newMasterPassword ) ;
if ( ! result . Succeeded )
{
return result ;
}
user . RevisionDate = user . AccountRevisionDate = DateTime . UtcNow ;
user . Key = key ;
user . Kdf = kdf ;
user . KdfIterations = kdfIterations ;
await _userRepository . ReplaceAsync ( user ) ;
await _pushService . PushLogOutAsync ( user . Id ) ;
return IdentityResult . Success ;
}
Logger . LogWarning ( "Change KDF failed for user {userId}." , user . Id ) ;
return IdentityResult . Failed ( _identityErrorDescriber . PasswordMismatch ( ) ) ;
2015-12-08 22:57:38 -05:00
}
2022-08-29 15:53:48 -04:00
public async Task < IdentityResult > UpdateKeyAsync ( User user , string masterPassword , string key , string privateKey ,
IEnumerable < Cipher > ciphers , IEnumerable < Folder > folders , IEnumerable < Send > sends )
2015-12-08 22:57:38 -05:00
{
2022-08-29 15:53:48 -04:00
if ( user = = null )
2015-12-08 22:57:38 -05:00
{
2022-08-29 15:53:48 -04:00
throw new ArgumentNullException ( nameof ( user ) ) ;
2015-12-08 22:57:38 -05:00
}
2022-08-29 15:53:48 -04:00
if ( await CheckPasswordAsync ( user , masterPassword ) )
2015-12-08 22:57:38 -05:00
{
2022-08-29 15:53:48 -04:00
user . RevisionDate = user . AccountRevisionDate = DateTime . UtcNow ;
user . SecurityStamp = Guid . NewGuid ( ) . ToString ( ) ;
user . Key = key ;
user . PrivateKey = privateKey ;
if ( ciphers . Any ( ) | | folders . Any ( ) | | sends . Any ( ) )
{
await _cipherRepository . UpdateUserKeysAndCiphersAsync ( user , ciphers , folders , sends ) ;
}
else
{
await _userRepository . ReplaceAsync ( user ) ;
}
await _pushService . PushLogOutAsync ( user . Id ) ;
return IdentityResult . Success ;
2015-12-08 22:57:38 -05:00
}
2022-08-29 15:53:48 -04:00
Logger . LogWarning ( "Update key failed for user {userId}." , user . Id ) ;
return IdentityResult . Failed ( _identityErrorDescriber . PasswordMismatch ( ) ) ;
2015-12-08 22:57:38 -05:00
}
2022-08-29 15:53:48 -04:00
public async Task < IdentityResult > RefreshSecurityStampAsync ( User user , string secret )
{
if ( user = = null )
{
throw new ArgumentNullException ( nameof ( user ) ) ;
}
2022-08-29 14:53:16 -04:00
2022-08-29 15:53:48 -04:00
if ( await VerifySecretAsync ( user , secret ) )
{
var result = await base . UpdateSecurityStampAsync ( user ) ;
if ( ! result . Succeeded )
{
return result ;
}
await SaveUserAsync ( user ) ;
await _pushService . PushLogOutAsync ( user . Id ) ;
return IdentityResult . Success ;
}
Logger . LogWarning ( "Refresh security stamp failed for user {userId}." , user . Id ) ;
return IdentityResult . Failed ( _identityErrorDescriber . PasswordMismatch ( ) ) ;
}
public async Task UpdateTwoFactorProviderAsync ( User user , TwoFactorProviderType type , bool setEnabled = true , bool logEvent = true )
2017-06-07 14:14:34 -04:00
{
2022-08-29 15:53:48 -04:00
SetTwoFactorProvider ( user , type , setEnabled ) ;
await SaveUserAsync ( user ) ;
if ( logEvent )
{
await _eventService . LogUserEventAsync ( user . Id , EventType . User_Updated2fa ) ;
}
2017-06-19 22:08:10 -04:00
}
2022-08-29 15:53:48 -04:00
public async Task DisableTwoFactorProviderAsync ( User user , TwoFactorProviderType type ,
IOrganizationService organizationService )
2017-06-19 22:08:10 -04:00
{
2022-08-29 15:53:48 -04:00
var providers = user . GetTwoFactorProviders ( ) ;
if ( ! providers ? . ContainsKey ( type ) ? ? true )
2017-06-19 22:08:10 -04:00
{
2022-08-29 15:53:48 -04:00
return ;
2017-06-19 22:08:10 -04:00
}
2022-08-29 15:53:48 -04:00
providers . Remove ( type ) ;
user . SetTwoFactorProviders ( providers ) ;
2016-02-21 01:10:31 -05:00
await SaveUserAsync ( user ) ;
2022-08-29 15:53:48 -04:00
await _eventService . LogUserEventAsync ( user . Id , EventType . User_Disabled2fa ) ;
2022-08-29 14:53:16 -04:00
2022-08-29 15:53:48 -04:00
if ( ! await TwoFactorIsEnabledAsync ( user ) )
{
await CheckPoliciesOnTwoFactorRemovalAsync ( user , organizationService ) ;
}
2022-08-29 14:53:16 -04:00
}
2016-11-14 23:32:15 -05:00
2022-08-29 15:53:48 -04:00
public async Task < bool > RecoverTwoFactorAsync ( string email , string secret , string recoveryCode ,
IOrganizationService organizationService )
2022-08-29 14:53:16 -04:00
{
2022-08-29 15:53:48 -04:00
var user = await _userRepository . GetByEmailAsync ( email ) ;
if ( user = = null )
{
// No user exists. Do we want to send an email telling them this in the future?
return false ;
}
2016-11-14 23:32:15 -05:00
2022-08-29 15:53:48 -04:00
if ( ! await VerifySecretAsync ( user , secret ) )
{
return false ;
}
2016-11-14 23:32:15 -05:00
2022-08-29 15:53:48 -04:00
if ( ! CoreHelpers . FixedTimeEquals ( user . TwoFactorRecoveryCode , recoveryCode ) )
{
return false ;
}
user . TwoFactorProviders = null ;
user . TwoFactorRecoveryCode = CoreHelpers . SecureRandomString ( 32 , upper : false , special : false ) ;
await SaveUserAsync ( user ) ;
await _mailService . SendRecoverTwoFactorEmail ( user . Email , DateTime . UtcNow , _currentContext . IpAddress ) ;
await _eventService . LogUserEventAsync ( user . Id , EventType . User_Recovered2fa ) ;
2020-02-28 10:23:19 -05:00
await CheckPoliciesOnTwoFactorRemovalAsync ( user , organizationService ) ;
2022-08-29 15:53:48 -04:00
return true ;
2016-11-14 23:32:15 -05:00
}
2022-08-29 15:53:48 -04:00
public async Task < Tuple < bool , string > > SignUpPremiumAsync ( User user , string paymentToken ,
PaymentMethodType paymentMethodType , short additionalStorageGb , UserLicense license ,
TaxInfo taxInfo )
2017-07-06 14:55:58 -04:00
{
2022-08-29 15:53:48 -04:00
if ( user . Premium )
{
throw new BadRequestException ( "Already a premium user." ) ;
}
if ( additionalStorageGb < 0 )
{
throw new BadRequestException ( "You can't subtract storage!" ) ;
}
if ( ( paymentMethodType = = PaymentMethodType . GoogleInApp | |
paymentMethodType = = PaymentMethodType . AppleInApp ) & & additionalStorageGb > 0 )
{
throw new BadRequestException ( "You cannot add storage with this payment method." ) ;
}
string paymentIntentClientSecret = null ;
IPaymentService paymentService = null ;
if ( _globalSettings . SelfHosted )
{
if ( license = = null | | ! _licenseService . VerifyLicense ( license ) )
{
throw new BadRequestException ( "Invalid license." ) ;
}
if ( ! license . CanUse ( user ) )
{
throw new BadRequestException ( "This license is not valid for this user." ) ;
}
var dir = $"{_globalSettings.LicenseDirectory}/user" ;
Directory . CreateDirectory ( dir ) ;
using var fs = File . OpenWrite ( Path . Combine ( dir , $"{user.Id}.json" ) ) ;
await JsonSerializer . SerializeAsync ( fs , license , JsonHelpers . Indented ) ;
}
else
{
paymentIntentClientSecret = await _paymentService . PurchasePremiumAsync ( user , paymentMethodType ,
paymentToken , additionalStorageGb , taxInfo ) ;
}
user . Premium = true ;
user . RevisionDate = DateTime . UtcNow ;
if ( _globalSettings . SelfHosted )
{
user . MaxStorageGb = 10240 ; // 10 TB
user . LicenseKey = license . LicenseKey ;
user . PremiumExpirationDate = license . Expires ;
}
else
{
user . MaxStorageGb = ( short ) ( 1 + additionalStorageGb ) ;
user . LicenseKey = CoreHelpers . SecureRandomString ( 20 ) ;
}
2017-08-11 22:55:25 -04:00
2022-08-29 15:53:48 -04:00
try
{
await SaveUserAsync ( user ) ;
await _pushService . PushSyncVaultAsync ( user . Id ) ;
await _referenceEventService . RaiseEventAsync (
new ReferenceEvent ( ReferenceEventType . UpgradePlan , user )
{
Storage = user . MaxStorageGb ,
PlanName = PremiumPlanId ,
} ) ;
}
catch when ( ! _globalSettings . SelfHosted )
{
await paymentService . CancelAndRecoverChargesAsync ( user ) ;
throw ;
}
return new Tuple < bool , string > ( string . IsNullOrWhiteSpace ( paymentIntentClientSecret ) ,
paymentIntentClientSecret ) ;
2017-07-06 14:55:58 -04:00
}
2022-08-29 15:53:48 -04:00
public async Task IapCheckAsync ( User user , PaymentMethodType paymentMethodType )
2019-09-19 08:46:26 -04:00
{
2022-08-29 15:53:48 -04:00
if ( paymentMethodType ! = PaymentMethodType . AppleInApp )
{
throw new BadRequestException ( "Payment method not supported for in-app purchases." ) ;
}
2019-09-19 08:46:26 -04:00
2022-08-29 15:53:48 -04:00
if ( user . Premium )
{
throw new BadRequestException ( "Already a premium user." ) ;
}
2022-08-29 14:53:16 -04:00
2022-08-29 15:53:48 -04:00
if ( ! string . IsNullOrWhiteSpace ( user . GatewayCustomerId ) )
{
var customerService = new Stripe . CustomerService ( ) ;
var customer = await customerService . GetAsync ( user . GatewayCustomerId ) ;
if ( customer ! = null & & customer . Balance ! = 0 )
{
throw new BadRequestException ( "Customer balance cannot exist when using in-app purchases." ) ;
}
}
2019-09-19 08:46:26 -04:00
}
2022-08-29 15:53:48 -04:00
public async Task UpdateLicenseAsync ( User user , UserLicense license )
2017-08-14 12:08:57 -04:00
{
2022-08-29 15:53:48 -04:00
if ( ! _globalSettings . SelfHosted )
{
throw new InvalidOperationException ( "Licenses require self hosting." ) ;
}
2017-08-14 12:08:57 -04:00
2022-08-29 15:53:48 -04:00
if ( license ? . LicenseType ! = null & & license . LicenseType ! = LicenseType . User )
{
throw new BadRequestException ( "Organization licenses cannot be applied to a user. "
+ "Upload this license from the Organization settings page." ) ;
}
2022-02-07 09:43:00 +01:00
2020-03-27 14:36:37 -04:00
if ( license = = null | | ! _licenseService . VerifyLicense ( license ) )
2017-08-14 12:08:57 -04:00
{
throw new BadRequestException ( "Invalid license." ) ;
}
2020-03-27 14:36:37 -04:00
if ( ! license . CanUse ( user ) )
2017-08-16 15:43:11 -04:00
{
throw new BadRequestException ( "This license is not valid for this user." ) ;
}
2017-08-14 12:08:57 -04:00
var dir = $"{_globalSettings.LicenseDirectory}/user" ;
Directory . CreateDirectory ( dir ) ;
2022-01-21 09:36:25 -05:00
using var fs = File . OpenWrite ( Path . Combine ( dir , $"{user.Id}.json" ) ) ;
await JsonSerializer . SerializeAsync ( fs , license , JsonHelpers . Indented ) ;
2022-08-29 14:53:16 -04:00
2022-08-29 15:53:48 -04:00
user . Premium = license . Premium ;
user . RevisionDate = DateTime . UtcNow ;
user . MaxStorageGb = _globalSettings . SelfHosted ? 10240 : license . MaxStorageGb ; // 10 TB
2017-08-14 12:08:57 -04:00
user . LicenseKey = license . LicenseKey ;
user . PremiumExpirationDate = license . Expires ;
2022-08-29 15:53:48 -04:00
await SaveUserAsync ( user ) ;
2022-08-29 14:53:16 -04:00
}
2019-02-19 17:13:21 -05:00
2022-08-29 15:53:48 -04:00
public async Task < string > AdjustStorageAsync ( User user , short storageAdjustmentGb )
2022-08-29 14:53:16 -04:00
{
2022-08-29 15:53:48 -04:00
if ( user = = null )
{
throw new ArgumentNullException ( nameof ( user ) ) ;
}
if ( ! user . Premium )
{
throw new BadRequestException ( "Not a premium user." ) ;
}
var secret = await BillingHelpers . AdjustStorageAsync ( _paymentService , user , storageAdjustmentGb ,
StoragePlanId ) ;
2020-07-07 12:01:34 -04:00
await _referenceEventService . RaiseEventAsync (
2022-08-29 15:53:48 -04:00
new ReferenceEvent ( ReferenceEventType . AdjustStorage , user )
2020-07-07 12:01:34 -04:00
{
2022-08-29 15:53:48 -04:00
Storage = storageAdjustmentGb ,
PlanName = StoragePlanId ,
2020-07-07 12:01:34 -04:00
} ) ;
2022-08-29 15:53:48 -04:00
await SaveUserAsync ( user ) ;
return secret ;
2022-08-29 14:53:16 -04:00
}
2017-08-13 00:33:37 -04:00
2022-08-29 15:53:48 -04:00
public async Task ReplacePaymentMethodAsync ( User user , string paymentToken , PaymentMethodType paymentMethodType , TaxInfo taxInfo )
2022-08-29 14:53:16 -04:00
{
2022-08-29 15:53:48 -04:00
if ( paymentToken . StartsWith ( "btok_" ) )
{
throw new BadRequestException ( "Invalid token." ) ;
}
2017-07-06 14:55:58 -04:00
2022-08-29 15:53:48 -04:00
var updated = await _paymentService . UpdatePaymentMethodAsync ( user , paymentMethodType , paymentToken , taxInfo : taxInfo ) ;
if ( updated )
2018-12-31 13:34:02 -05:00
{
2022-08-29 15:53:48 -04:00
await SaveUserAsync ( user ) ;
2018-12-31 13:34:02 -05:00
}
2017-07-06 14:55:58 -04:00
}
2022-08-29 15:53:48 -04:00
public async Task CancelPremiumAsync ( User user , bool? endOfPeriod = null , bool accountDelete = false )
2017-07-06 14:55:58 -04:00
{
2022-08-29 15:53:48 -04:00
var eop = endOfPeriod . GetValueOrDefault ( true ) ;
if ( ! endOfPeriod . HasValue & & user . PremiumExpirationDate . HasValue & &
user . PremiumExpirationDate . Value < DateTime . UtcNow )
{
eop = false ;
}
await _paymentService . CancelSubscriptionAsync ( user , eop , accountDelete ) ;
await _referenceEventService . RaiseEventAsync (
new ReferenceEvent ( ReferenceEventType . CancelSubscription , user )
{
EndOfPeriod = eop ,
} ) ;
2017-07-06 14:55:58 -04:00
}
2022-08-29 15:53:48 -04:00
public async Task ReinstatePremiumAsync ( User user )
2019-08-09 23:56:26 -04:00
{
2022-08-29 15:53:48 -04:00
await _paymentService . ReinstateSubscriptionAsync ( user ) ;
await _referenceEventService . RaiseEventAsync (
new ReferenceEvent ( ReferenceEventType . ReinstateSubscription , user ) ) ;
2019-08-09 23:56:26 -04:00
}
2022-08-29 15:53:48 -04:00
public async Task EnablePremiumAsync ( Guid userId , DateTime ? expirationDate )
2019-08-09 23:56:26 -04:00
{
2022-08-29 15:53:48 -04:00
var user = await _userRepository . GetByIdAsync ( userId ) ;
await EnablePremiumAsync ( user , expirationDate ) ;
2019-08-09 23:56:26 -04:00
}
2022-08-29 15:53:48 -04:00
public async Task EnablePremiumAsync ( User user , DateTime ? expirationDate )
2017-07-25 09:04:22 -04:00
{
2022-08-29 15:53:48 -04:00
if ( user ! = null & & ! user . Premium & & user . Gateway . HasValue )
{
user . Premium = true ;
user . PremiumExpirationDate = expirationDate ;
user . RevisionDate = DateTime . UtcNow ;
await _userRepository . ReplaceAsync ( user ) ;
}
2017-08-16 17:08:20 -04:00
}
2022-08-29 15:53:48 -04:00
public async Task DisablePremiumAsync ( Guid userId , DateTime ? expirationDate )
2017-08-16 17:08:20 -04:00
{
2022-08-29 15:53:48 -04:00
var user = await _userRepository . GetByIdAsync ( userId ) ;
await DisablePremiumAsync ( user , expirationDate ) ;
2017-08-12 22:16:42 -04:00
}
2022-08-29 15:53:48 -04:00
public async Task DisablePremiumAsync ( User user , DateTime ? expirationDate )
2017-08-12 22:16:42 -04:00
{
2022-08-29 15:53:48 -04:00
if ( user ! = null & & user . Premium )
2017-08-30 11:23:55 -04:00
{
2022-08-29 15:53:48 -04:00
user . Premium = false ;
user . PremiumExpirationDate = expirationDate ;
user . RevisionDate = DateTime . UtcNow ;
await _userRepository . ReplaceAsync ( user ) ;
}
2022-08-29 14:53:16 -04:00
}
2017-08-30 11:23:55 -04:00
2022-08-29 15:53:48 -04:00
public async Task UpdatePremiumExpirationAsync ( Guid userId , DateTime ? expirationDate )
2022-08-29 14:53:16 -04:00
{
2022-08-29 15:53:48 -04:00
var user = await _userRepository . GetByIdAsync ( userId ) ;
if ( user ! = null )
{
user . PremiumExpirationDate = expirationDate ;
user . RevisionDate = DateTime . UtcNow ;
await _userRepository . ReplaceAsync ( user ) ;
}
2017-08-30 11:23:55 -04:00
}
2022-08-29 15:53:48 -04:00
public async Task < UserLicense > GenerateLicenseAsync ( User user , SubscriptionInfo subscriptionInfo = null ,
int? version = null )
2018-04-17 08:10:17 -04:00
{
2022-08-29 15:53:48 -04:00
if ( user = = null )
2018-04-17 08:10:17 -04:00
{
2022-08-29 15:53:48 -04:00
throw new NotFoundException ( ) ;
}
2018-04-17 08:10:17 -04:00
2022-08-29 15:53:48 -04:00
if ( subscriptionInfo = = null & & user . Gateway ! = null )
{
subscriptionInfo = await _paymentService . GetSubscriptionAsync ( user ) ;
}
2018-04-17 08:10:17 -04:00
2022-08-29 15:53:48 -04:00
return subscriptionInfo = = null ? new UserLicense ( user , _licenseService ) :
new UserLicense ( user , subscriptionInfo , _licenseService ) ;
}
2022-08-29 14:53:16 -04:00
2022-08-29 15:53:48 -04:00
public override async Task < bool > CheckPasswordAsync ( User user , string password )
2022-08-29 14:53:16 -04:00
{
2022-08-29 15:53:48 -04:00
if ( user = = null )
{
return false ;
}
2022-08-29 14:53:16 -04:00
2022-08-29 15:53:48 -04:00
var result = await base . VerifyPasswordAsync ( Store as IUserPasswordStore < User > , user , password ) ;
if ( result = = PasswordVerificationResult . SuccessRehashNeeded )
{
await UpdatePasswordHash ( user , password , false , false ) ;
user . RevisionDate = DateTime . UtcNow ;
await _userRepository . ReplaceAsync ( user ) ;
}
2018-04-17 08:10:17 -04:00
2022-08-29 15:53:48 -04:00
var success = result ! = PasswordVerificationResult . Failed ;
if ( ! success )
{
Logger . LogWarning ( 0 , "Invalid password for user {userId}." , user . Id ) ;
}
return success ;
2022-08-29 14:53:16 -04:00
}
2022-06-17 06:30:50 +10:00
2022-08-29 15:53:48 -04:00
public async Task < bool > CanAccessPremium ( ITwoFactorProvidersUser user )
2022-08-29 14:53:16 -04:00
{
2022-08-29 15:53:48 -04:00
var userId = user . GetUserId ( ) ;
if ( ! userId . HasValue )
{
return false ;
}
2022-06-17 06:30:50 +10:00
2022-08-29 15:53:48 -04:00
return user . GetPremium ( ) | | await this . HasPremiumFromOrganization ( user ) ;
2022-08-29 14:53:16 -04:00
}
2022-06-17 06:30:50 +10:00
2022-08-29 15:53:48 -04:00
public async Task < bool > HasPremiumFromOrganization ( ITwoFactorProvidersUser user )
2022-08-29 14:53:16 -04:00
{
2022-08-29 15:53:48 -04:00
var userId = user . GetUserId ( ) ;
if ( ! userId . HasValue )
{
return false ;
}
2022-06-17 06:30:50 +10:00
2022-08-29 15:53:48 -04:00
// orgUsers in the Invited status are not associated with a userId yet, so this will get
// orgUsers in Accepted and Confirmed states only
var orgUsers = await _organizationUserRepository . GetManyByUserAsync ( userId . Value ) ;
2022-06-17 06:30:50 +10:00
2022-08-29 15:53:48 -04:00
if ( ! orgUsers . Any ( ) )
{
return false ;
}
2018-08-28 16:23:58 -04:00
2022-08-29 15:53:48 -04:00
var orgAbilities = await _applicationCacheService . GetOrganizationAbilitiesAsync ( ) ;
return orgUsers . Any ( ou = >
orgAbilities . TryGetValue ( ou . OrganizationId , out var orgAbility ) & &
orgAbility . UsersGetPremium & &
orgAbility . Enabled ) ;
2022-08-29 14:53:16 -04:00
}
2018-12-19 10:47:53 -05:00
2022-08-29 15:53:48 -04:00
public async Task < bool > TwoFactorIsEnabledAsync ( ITwoFactorProvidersUser user )
2022-08-29 14:53:16 -04:00
{
2022-08-29 15:53:48 -04:00
var providers = user . GetTwoFactorProviders ( ) ;
if ( providers = = null )
{
return false ;
}
2022-08-29 14:53:16 -04:00
2022-08-29 15:53:48 -04:00
foreach ( var p in providers )
{
if ( p . Value ? . Enabled ? ? false )
{
if ( ! TwoFactorProvider . RequiresPremium ( p . Key ) )
{
return true ;
}
if ( await CanAccessPremium ( user ) )
{
return true ;
}
}
}
2018-12-19 10:47:53 -05:00
return false ;
}
2022-08-29 15:53:48 -04:00
public async Task < bool > TwoFactorProviderIsEnabledAsync ( TwoFactorProviderType provider , ITwoFactorProvidersUser user )
2018-12-19 10:47:53 -05:00
{
2022-08-29 15:53:48 -04:00
var providers = user . GetTwoFactorProviders ( ) ;
if ( providers = = null | | ! providers . ContainsKey ( provider ) | | ! providers [ provider ] . Enabled )
{
return false ;
}
2018-12-19 10:47:53 -05:00
2022-08-29 15:53:48 -04:00
if ( ! TwoFactorProvider . RequiresPremium ( provider ) )
{
return true ;
}
2018-12-19 10:47:53 -05:00
2022-08-29 15:53:48 -04:00
return await CanAccessPremium ( user ) ;
2018-12-19 10:47:53 -05:00
}
2022-08-29 15:53:48 -04:00
public async Task < string > GenerateSignInTokenAsync ( User user , string purpose )
2020-08-26 14:12:04 -04:00
{
2022-08-29 15:53:48 -04:00
var token = await GenerateUserTokenAsync ( user , Options . Tokens . PasswordResetTokenProvider ,
purpose ) ;
return token ;
2020-08-26 14:12:04 -04:00
}
2021-11-09 16:37:32 +01:00
2022-08-29 15:53:48 -04:00
private async Task < IdentityResult > UpdatePasswordHash ( User user , string newPassword ,
bool validatePassword = true , bool refreshStamp = true )
2015-12-08 22:57:38 -05:00
{
2022-08-29 15:53:48 -04:00
if ( validatePassword )
2015-12-08 22:57:38 -05:00
{
2022-08-29 15:53:48 -04:00
var validate = await ValidatePasswordInternal ( user , newPassword ) ;
if ( ! validate . Succeeded )
2022-08-29 14:53:16 -04:00
{
2022-08-29 15:53:48 -04:00
return validate ;
2015-12-08 22:57:38 -05:00
}
}
2022-08-29 15:53:48 -04:00
user . MasterPassword = _passwordHasher . HashPassword ( user , newPassword ) ;
if ( refreshStamp )
{
user . SecurityStamp = Guid . NewGuid ( ) . ToString ( ) ;
}
2015-12-08 22:57:38 -05:00
2022-08-29 15:53:48 -04:00
return IdentityResult . Success ;
2022-08-29 14:53:16 -04:00
}
2015-12-08 22:57:38 -05:00
2022-08-29 15:53:48 -04:00
private async Task < IdentityResult > ValidatePasswordInternal ( User user , string password )
2020-02-28 09:14:33 -05:00
{
2022-08-29 15:53:48 -04:00
var errors = new List < IdentityError > ( ) ;
foreach ( var v in _passwordValidators )
{
var result = await v . ValidateAsync ( this , user , password ) ;
if ( ! result . Succeeded )
{
errors . AddRange ( result . Errors ) ;
}
}
if ( errors . Count > 0 )
2020-02-28 09:14:33 -05:00
{
2022-08-29 15:53:48 -04:00
Logger . LogWarning ( "User {userId} password validation failed: {errors}." , await GetUserIdAsync ( user ) ,
string . Join ( ";" , errors . Select ( e = > e . Code ) ) ) ;
return IdentityResult . Failed ( errors . ToArray ( ) ) ;
2020-02-28 09:14:33 -05:00
}
2022-08-29 15:53:48 -04:00
return IdentityResult . Success ;
2022-08-29 14:53:16 -04:00
}
2020-02-28 09:14:33 -05:00
2022-08-29 15:53:48 -04:00
public void SetTwoFactorProvider ( User user , TwoFactorProviderType type , bool setEnabled = true )
2022-08-29 14:53:16 -04:00
{
2022-08-29 15:53:48 -04:00
var providers = user . GetTwoFactorProviders ( ) ;
if ( ! providers ? . ContainsKey ( type ) ? ? true )
{
return ;
}
2020-02-28 09:14:33 -05:00
2022-08-29 15:53:48 -04:00
if ( setEnabled )
{
providers [ type ] . Enabled = true ;
}
user . SetTwoFactorProviders ( providers ) ;
2022-08-29 14:53:16 -04:00
2022-08-29 15:53:48 -04:00
if ( string . IsNullOrWhiteSpace ( user . TwoFactorRecoveryCode ) )
2020-02-28 09:14:33 -05:00
{
2022-08-29 15:53:48 -04:00
user . TwoFactorRecoveryCode = CoreHelpers . SecureRandomString ( 32 , upper : false , special : false ) ;
2020-02-28 09:14:33 -05:00
}
}
2020-02-28 10:23:19 -05:00
2022-08-29 15:53:48 -04:00
private async Task CheckPoliciesOnTwoFactorRemovalAsync ( User user , IOrganizationService organizationService )
2020-02-28 10:23:19 -05:00
{
2022-08-29 15:53:48 -04:00
var twoFactorPolicies = await _policyRepository . GetManyByTypeApplicableToUserIdAsync ( user . Id ,
PolicyType . TwoFactorAuthentication ) ;
2021-09-28 06:54:28 +10:00
2022-08-29 15:53:48 -04:00
var removeOrgUserTasks = twoFactorPolicies . Select ( async p = >
{
await organizationService . DeleteUserAsync ( p . OrganizationId , user . Id ) ;
var organization = await _organizationRepository . GetByIdAsync ( p . OrganizationId ) ;
await _mailService . SendOrganizationUserRemovedForPolicyTwoStepEmailAsync (
organization . Name , user . Email ) ;
} ) . ToArray ( ) ;
2021-09-28 06:54:28 +10:00
2022-08-29 15:53:48 -04:00
await Task . WhenAll ( removeOrgUserTasks ) ;
2020-02-28 10:23:19 -05:00
}
2020-07-15 12:38:45 -04:00
2022-08-29 15:53:48 -04:00
public override async Task < IdentityResult > ConfirmEmailAsync ( User user , string token )
2020-07-15 12:38:45 -04:00
{
2022-08-29 15:53:48 -04:00
var result = await base . ConfirmEmailAsync ( user , token ) ;
if ( result . Succeeded )
{
await _referenceEventService . RaiseEventAsync (
new ReferenceEvent ( ReferenceEventType . ConfirmEmailAddress , user ) ) ;
}
return result ;
2020-07-15 12:38:45 -04:00
}
2020-11-10 15:15:29 -05:00
2022-08-29 15:53:48 -04:00
public async Task RotateApiKeyAsync ( User user )
2020-11-10 15:15:29 -05:00
{
2022-08-29 15:53:48 -04:00
user . ApiKey = CoreHelpers . SecureRandomString ( 30 ) ;
user . RevisionDate = DateTime . UtcNow ;
await _userRepository . ReplaceAsync ( user ) ;
2020-11-10 15:15:29 -05:00
}
2021-11-09 16:37:32 +01:00
2022-08-29 15:53:48 -04:00
public async Task SendOTPAsync ( User user )
2022-08-29 14:53:16 -04:00
{
2022-08-29 15:53:48 -04:00
if ( user . Email = = null )
{
throw new BadRequestException ( "No user email." ) ;
}
2021-11-09 16:37:32 +01:00
2022-08-29 15:53:48 -04:00
if ( ! user . UsesKeyConnector )
{
throw new BadRequestException ( "Not using Key Connector." ) ;
}
2021-11-09 16:37:32 +01:00
2022-08-29 15:53:48 -04:00
var token = await base . GenerateUserTokenAsync ( user , TokenOptions . DefaultEmailProvider ,
"otp:" + user . Email ) ;
await _mailService . SendOTPEmailAsync ( user . Email , token ) ;
2021-11-09 16:37:32 +01:00
}
2022-08-29 14:53:16 -04:00
2022-08-29 15:53:48 -04:00
public Task < bool > VerifyOTPAsync ( User user , string token )
2021-11-09 16:37:32 +01:00
{
2022-08-29 15:53:48 -04:00
return base . VerifyUserTokenAsync ( user , TokenOptions . DefaultEmailProvider ,
"otp:" + user . Email , token ) ;
2021-11-09 16:37:32 +01:00
}
2022-04-28 13:14:09 -03:00
2022-08-29 15:53:48 -04:00
public async Task < bool > VerifySecretAsync ( User user , string secret )
2022-04-28 13:14:09 -03:00
{
2022-08-29 15:53:48 -04:00
return user . UsesKeyConnector
? await VerifyOTPAsync ( user , secret )
: await CheckPasswordAsync ( user , secret ) ;
2022-04-28 13:14:09 -03:00
}
2022-08-29 15:53:48 -04:00
public async Task < bool > Needs2FABecauseNewDeviceAsync ( User user , string deviceIdentifier , string grantType )
2022-06-06 14:52:50 -03:00
{
2022-08-29 15:53:48 -04:00
return CanEditDeviceVerificationSettings ( user )
& & user . UnknownDeviceVerificationEnabled
& & grantType ! = "authorization_code"
& & await IsNewDeviceAndNotTheFirstOneAsync ( user , deviceIdentifier ) ;
2022-06-06 14:52:50 -03:00
}
2022-08-29 15:53:48 -04:00
public bool CanEditDeviceVerificationSettings ( User user )
2022-04-28 13:14:09 -03:00
{
2022-08-29 15:53:48 -04:00
return _globalSettings . TwoFactorAuth . EmailOnNewDeviceLogin
& & user . EmailVerified
& & ! user . UsesKeyConnector
& & ! ( user . GetTwoFactorProviders ( ) ? . Any ( ) ? ? false ) ;
2022-04-28 13:14:09 -03:00
}
2022-08-29 14:53:16 -04:00
2022-08-29 15:53:48 -04:00
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 ) ;
}
2015-12-08 22:57:38 -05:00
}
}