using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Utilities; namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations; public record ResellerClientOrganizationSignUpResponse( Organization Organization, OrganizationUser OwnerOrganizationUser); /// /// Command for signing up reseller client organizations in a pending state. /// public interface IResellerClientOrganizationSignUpCommand { /// /// Sign up a reseller client organization. The organization will be created in a pending state /// (disabled and with Pending status) and the owner will be invited via email. The organization /// will become active once the owner accepts the invitation. /// /// The organization to create. /// The email of the organization owner who will be invited. /// A response containing the created pending organization and invited owner user. Task SignUpResellerClientAsync( Organization organization, string ownerEmail); } public class ResellerClientOrganizationSignUpCommand : IResellerClientOrganizationSignUpCommand { private readonly IOrganizationRepository _organizationRepository; private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository; private readonly IApplicationCacheService _applicationCacheService; private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IEventService _eventService; private readonly ISendOrganizationInvitesCommand _sendOrganizationInvitesCommand; private readonly IPaymentService _paymentService; public ResellerClientOrganizationSignUpCommand( IOrganizationRepository organizationRepository, IOrganizationApiKeyRepository organizationApiKeyRepository, IApplicationCacheService applicationCacheService, IOrganizationUserRepository organizationUserRepository, IEventService eventService, ISendOrganizationInvitesCommand sendOrganizationInvitesCommand, IPaymentService paymentService) { _organizationRepository = organizationRepository; _organizationApiKeyRepository = organizationApiKeyRepository; _applicationCacheService = applicationCacheService; _organizationUserRepository = organizationUserRepository; _eventService = eventService; _sendOrganizationInvitesCommand = sendOrganizationInvitesCommand; _paymentService = paymentService; } public async Task SignUpResellerClientAsync( Organization organization, string ownerEmail) { try { var createdOrganization = await CreateOrganizationAsync(organization); var ownerOrganizationUser = await CreateAndInviteOwnerAsync(createdOrganization, ownerEmail); await _eventService.LogOrganizationUserEventAsync(ownerOrganizationUser, EventType.OrganizationUser_Invited); return new ResellerClientOrganizationSignUpResponse(organization, ownerOrganizationUser); } catch { await _paymentService.CancelAndRecoverChargesAsync(organization); if (organization.Id != default) { // Deletes the organization and all related data, including its owner user await _organizationRepository.DeleteAsync(organization); await _applicationCacheService.DeleteOrganizationAbilityAsync(organization.Id); } throw; } } private async Task CreateOrganizationAsync(Organization organization) { organization.Id = CoreHelpers.GenerateComb(); organization.Enabled = false; organization.Status = OrganizationStatusType.Pending; await _organizationRepository.CreateAsync(organization); await _organizationApiKeyRepository.CreateAsync(new OrganizationApiKey { OrganizationId = organization.Id, ApiKey = CoreHelpers.SecureRandomString(30), Type = OrganizationApiKeyType.Default, RevisionDate = DateTime.UtcNow, }); await _applicationCacheService.UpsertOrganizationAbilityAsync(organization); return organization; } private async Task CreateAndInviteOwnerAsync(Organization organization, string ownerEmail) { var ownerOrganizationUser = new OrganizationUser { OrganizationId = organization.Id, UserId = null, Email = ownerEmail, Key = null, Type = OrganizationUserType.Owner, Status = OrganizationUserStatusType.Invited, }; await _organizationUserRepository.CreateAsync(ownerOrganizationUser); await _sendOrganizationInvitesCommand.SendInvitesAsync(new SendInvitesRequest( users: [ownerOrganizationUser], organization: organization, initOrganization: true)); return ownerOrganizationUser; } }