diff --git a/src/Core/Exceptions/InvalidEmailException.cs b/src/Core/Exceptions/InvalidEmailException.cs new file mode 100644 index 0000000000..52a89e5233 --- /dev/null +++ b/src/Core/Exceptions/InvalidEmailException.cs @@ -0,0 +1,13 @@ +using System; + +namespace Bit.Core.Exceptions +{ + public class InvalidEmailException : Exception + { + public InvalidEmailException() + : base("Invalid email.") + { + + } + } +} diff --git a/src/Core/Exceptions/InvalidGatewayCustomerIdException.cs b/src/Core/Exceptions/InvalidGatewayCustomerIdException.cs new file mode 100644 index 0000000000..43f641d7ea --- /dev/null +++ b/src/Core/Exceptions/InvalidGatewayCustomerIdException.cs @@ -0,0 +1,13 @@ +using System; + +namespace Bit.Core.Exceptions +{ + public class InvalidGatewayCustomerIdException : Exception + { + public InvalidGatewayCustomerIdException() + : base("Invalid gateway customerId.") + { + + } + } +} diff --git a/src/Core/Services/IStripeSyncService.cs b/src/Core/Services/IStripeSyncService.cs new file mode 100644 index 0000000000..4f194075ef --- /dev/null +++ b/src/Core/Services/IStripeSyncService.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Bit.Core.Services +{ + public interface IStripeSyncService + { + Task UpdateCustomerEmailAddress(string gatewayCustomerId, string emailAddress); + } +} diff --git a/src/Core/Services/Implementations/StripeSyncService.cs b/src/Core/Services/Implementations/StripeSyncService.cs new file mode 100644 index 0000000000..10e3048f17 --- /dev/null +++ b/src/Core/Services/Implementations/StripeSyncService.cs @@ -0,0 +1,33 @@ +using System.Threading.Tasks; +using Bit.Core.Exceptions; + +namespace Bit.Core.Services +{ + public class StripeSyncService : IStripeSyncService + { + private readonly IStripeAdapter _stripeAdapter; + + public StripeSyncService(IStripeAdapter stripeAdapter) + { + _stripeAdapter = stripeAdapter; + } + + public async Task UpdateCustomerEmailAddress(string gatewayCustomerId, string emailAddress) + { + if (string.IsNullOrWhiteSpace(gatewayCustomerId)) + { + throw new InvalidGatewayCustomerIdException(); + } + + if (string.IsNullOrWhiteSpace(emailAddress)) + { + throw new InvalidEmailException(); + } + + var customer = await _stripeAdapter.CustomerGetAsync(gatewayCustomerId); + + await _stripeAdapter.CustomerUpdateAsync(customer.Id, + new Stripe.CustomerUpdateOptions { Email = emailAddress }); + } + } +} diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index 858eef5d34..2e53755160 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -52,6 +52,7 @@ namespace Bit.Core.Services private readonly IOrganizationService _organizationService; private readonly IProviderUserRepository _providerUserRepository; private readonly IDeviceRepository _deviceRepository; + private readonly IStripeSyncService _stripeSyncService; public UserService( IUserRepository userRepository, @@ -81,7 +82,8 @@ namespace Bit.Core.Services IGlobalSettings globalSettings, IOrganizationService organizationService, IProviderUserRepository providerUserRepository, - IDeviceRepository deviceRepository) + IDeviceRepository deviceRepository, + IStripeSyncService stripeSyncService) : base( store, optionsAccessor, @@ -117,6 +119,7 @@ namespace Bit.Core.Services _organizationService = organizationService; _providerUserRepository = providerUserRepository; _deviceRepository = deviceRepository; + _stripeSyncService = stripeSyncService; } public Guid? GetProperUserId(ClaimsPrincipal principal) @@ -543,6 +546,14 @@ namespace Bit.Core.Services return IdentityResult.Failed(_identityErrorDescriber.DuplicateEmail(newEmail)); } + var previousState = new + { + Key = user.Key, + MasterPassword = user.MasterPassword, + SecurityStamp = user.SecurityStamp, + Email = user.Email + }; + var result = await UpdatePasswordHash(user, newMasterPassword); if (!result.Succeeded) { @@ -554,6 +565,32 @@ namespace Bit.Core.Services user.EmailVerified = true; user.RevisionDate = user.AccountRevisionDate = DateTime.UtcNow; await _userRepository.ReplaceAsync(user); + + 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 + }); + } + } + await _pushService.PushLogOutAsync(user.Id); return IdentityResult.Success; diff --git a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs index b78587b8de..6819422d72 100644 --- a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs +++ b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs @@ -148,6 +148,7 @@ namespace Bit.SharedWeb.Utilities }; }); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddTokenizers();