From 8e79c20dce01946e493a7cf6a70332035a730205 Mon Sep 17 00:00:00 2001 From: Gbubemi Smith Date: Thu, 16 Jun 2022 17:45:26 +0100 Subject: [PATCH] [SG-72] Sync changed email address with stripe (#2042) * sync changed email address with strip * sync changed email address with strip * fixed formatting * throw exception if not successful * Added revert if stripe sync fails * Added revert if stripe sync fails * Added revert if stripe sync fails * created stripe sync service * fixed lint issue * reverted to use stripe exception message * added null checks to customer id and email address * added braces * removed empty email --- src/Core/Exceptions/InvalidEmailException.cs | 13 +++++++ .../InvalidGatewayCustomerIdException.cs | 13 +++++++ src/Core/Services/IStripeSyncService.cs | 9 +++++ .../Implementations/StripeSyncService.cs | 33 ++++++++++++++++ .../Services/Implementations/UserService.cs | 39 ++++++++++++++++++- .../Utilities/ServiceCollectionExtensions.cs | 1 + 6 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 src/Core/Exceptions/InvalidEmailException.cs create mode 100644 src/Core/Exceptions/InvalidGatewayCustomerIdException.cs create mode 100644 src/Core/Services/IStripeSyncService.cs create mode 100644 src/Core/Services/Implementations/StripeSyncService.cs 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();