From 0b4ba6399b1198804436dfafafbb300b2569e9e3 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Wed, 12 Apr 2017 11:00:40 -0400 Subject: [PATCH] email notifications on user approval process --- src/Core/Services/IMailService.cs | 3 ++ .../Implementations/NoopMailService.cs | 11 +++++++ .../Implementations/OrganizationService.cs | 23 ++++++++------ .../Implementations/SendGridMailService.cs | 31 ++++++++++++++++++- 4 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/Core/Services/IMailService.cs b/src/Core/Services/IMailService.cs index d1a85ff404..dd1d5bc7d5 100644 --- a/src/Core/Services/IMailService.cs +++ b/src/Core/Services/IMailService.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Bit.Core.Models.Table; +using System.Collections.Generic; namespace Bit.Core.Services { @@ -11,5 +12,7 @@ namespace Bit.Core.Services Task SendNoMasterPasswordHintEmailAsync(string email); Task SendMasterPasswordHintEmailAsync(string email, string hint); Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, string token); + Task SendOrganizationAcceptedEmailAsync(string organizationName, string userEmail, IEnumerable adminEmails); + Task SendOrganizationConfirmedEmailAsync(string organizationName, string email); } } \ No newline at end of file diff --git a/src/Core/Services/Implementations/NoopMailService.cs b/src/Core/Services/Implementations/NoopMailService.cs index 50cb74cc50..cd553a7245 100644 --- a/src/Core/Services/Implementations/NoopMailService.cs +++ b/src/Core/Services/Implementations/NoopMailService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Bit.Core.Models.Table; @@ -26,6 +27,16 @@ namespace Bit.Core.Services return Task.FromResult(0); } + public Task SendOrganizationAcceptedEmailAsync(string organizationName, string userEmail, IEnumerable adminEmails) + { + throw new NotImplementedException(); + } + + public Task SendOrganizationConfirmedEmailAsync(string organizationName, string email) + { + throw new NotImplementedException(); + } + public Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, string token) { return Task.FromResult(0); diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index 5e2cd2cb9f..672a627436 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -632,7 +632,7 @@ namespace Bit.Core.Services } public async Task InviteUserAsync(Guid organizationId, Guid invitingUserId, string email, - Enums.OrganizationUserType type, IEnumerable subvaults) + OrganizationUserType type, IEnumerable subvaults) { var organization = await _organizationRepository.GetByIdAsync(organizationId); if(organization == null) @@ -667,7 +667,7 @@ namespace Bit.Core.Services Email = email, Key = null, Type = type, - Status = Enums.OrganizationUserStatusType.Invited, + Status = OrganizationUserStatusType.Invited, CreationDate = DateTime.UtcNow, RevisionDate = DateTime.UtcNow }; @@ -683,7 +683,7 @@ namespace Bit.Core.Services { var orgUser = await _organizationUserRepository.GetByIdAsync(organizationUserId); if(orgUser == null || orgUser.OrganizationId != organizationId || - orgUser.Status != Enums.OrganizationUserStatusType.Invited) + orgUser.Status != OrganizationUserStatusType.Invited) { throw new BadRequestException("User invalid."); } @@ -708,7 +708,7 @@ namespace Bit.Core.Services throw new BadRequestException("User invalid."); } - if(orgUser.Status != Enums.OrganizationUserStatusType.Invited) + if(orgUser.Status != OrganizationUserStatusType.Invited) { throw new BadRequestException("Already accepted."); } @@ -741,12 +741,12 @@ namespace Bit.Core.Services throw new BadRequestException("Invalid token."); } - orgUser.Status = Enums.OrganizationUserStatusType.Accepted; + orgUser.Status = OrganizationUserStatusType.Accepted; orgUser.UserId = user.Id; orgUser.Email = null; await _organizationUserRepository.ReplaceAsync(orgUser); - // TODO: send email + // TODO: send notification emails to org admins return orgUser; } @@ -761,12 +761,17 @@ namespace Bit.Core.Services throw new BadRequestException("User not valid."); } - orgUser.Status = Enums.OrganizationUserStatusType.Confirmed; + orgUser.Status = OrganizationUserStatusType.Confirmed; orgUser.Key = key; orgUser.Email = null; await _organizationUserRepository.ReplaceAsync(orgUser); - // TODO: send email + var user = await _userRepository.GetByIdAsync(orgUser.UserId.Value); + var org = await _organizationRepository.GetByIdAsync(organizationId); + if(user != null && org != null) + { + await _mailService.SendOrganizationConfirmedEmailAsync(org.Name, user.Email); + } return orgUser; } @@ -779,7 +784,7 @@ namespace Bit.Core.Services } var confirmedOwners = (await GetConfirmedOwnersAsync(user.OrganizationId)).ToList(); - if(user.Type != Enums.OrganizationUserType.Owner && confirmedOwners.Count == 1 && confirmedOwners[0].Id == user.Id) + if(user.Type != OrganizationUserType.Owner && confirmedOwners.Count == 1 && confirmedOwners[0].Id == user.Id) { throw new BadRequestException("Organization must have at least one confirmed owner."); } diff --git a/src/Core/Services/Implementations/SendGridMailService.cs b/src/Core/Services/Implementations/SendGridMailService.cs index 31be28d6fb..777dfe4d56 100644 --- a/src/Core/Services/Implementations/SendGridMailService.cs +++ b/src/Core/Services/Implementations/SendGridMailService.cs @@ -5,6 +5,7 @@ using Bit.Core.Models.Table; using SendGrid; using SendGrid.Helpers.Mail; using System.Net; +using System.Linq; namespace Bit.Core.Services { @@ -16,6 +17,8 @@ namespace Bit.Core.Services private const string NoMasterPasswordHintTemplateId = "136eb299-e102-495a-88bd-f96736eea159"; private const string MasterPasswordHintTemplateId = "be77cfde-95dd-4cb9-b5e0-8286b53885f1"; private const string OrganizationInviteTemplateId = "1eff5512-e36c-49a8-b9e2-2b215d6bbced"; + private const string OrganizationAcceptedTemplateId = "28f7f741-598e-449c-85fe-601e1cc32ba3"; + private const string OrganizationConfirmedTemplateId = "a8afe2a0-6161-4eb9-b40c-08a7f520ec50"; private const string AdministrativeCategoryName = "Administrative"; private const string MarketingCategoryName = "Marketing"; @@ -101,7 +104,33 @@ namespace Bit.Core.Services message.AddSubstitution("{{token}}", token); message.AddSubstitution("{{email}}", WebUtility.UrlEncode(orgUser.Email)); message.AddSubstitution("{{organizationNameUrlEncoded}}", WebUtility.UrlEncode(organizationName)); - message.AddCategories(new List { AdministrativeCategoryName, "Organization Invite" }); + message.AddCategories(new List { AdministrativeCategoryName, "Organization User Invite" }); + + await _client.SendEmailAsync(message); + } + + public async Task SendOrganizationAcceptedEmailAsync(string organizationName, string userEmail, + IEnumerable adminEmails) + { + var message = CreateDefaultMessage(OrganizationAcceptedTemplateId); + + message.Subject = $"User {userEmail} Has Accepted Invite"; + message.AddTos(adminEmails.Select(e => new EmailAddress(e)).ToList()); + message.AddSubstitution("{{userEmail}}", userEmail); + message.AddSubstitution("{{organizationName}}", organizationName); + message.AddCategories(new List { AdministrativeCategoryName, "Organization User Accepted" }); + + await _client.SendEmailAsync(message); + } + + public async Task SendOrganizationConfirmedEmailAsync(string organizationName, string email) + { + var message = CreateDefaultMessage(OrganizationConfirmedTemplateId); + + message.Subject = $"You Have Been Confirmed To {organizationName}"; + message.AddTo(new EmailAddress(email)); + message.AddSubstitution("{{organizationName}}", organizationName); + message.AddCategories(new List { AdministrativeCategoryName, "Organization User Confirmed" }); await _client.SendEmailAsync(message); }