diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index 439d6fcc38..0ed515e975 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -14,11 +14,24 @@ + + + + + + + + + + + + + diff --git a/src/Core/MailTemplates/ChangeEmail.cshtml b/src/Core/MailTemplates/ChangeEmail.cshtml new file mode 100644 index 0000000000..3aa571ee17 --- /dev/null +++ b/src/Core/MailTemplates/ChangeEmail.cshtml @@ -0,0 +1,7 @@ +@model Bit.Core.Models.Mail.ChangeEmailViewModel +@{ + Layout = "_BasicMailLayout"; +} +

+ To finalize changing your email address enter the following code in the pop-up window: @Model.Token +

diff --git a/src/Core/MailTemplates/ChangeEmail.text.cshtml b/src/Core/MailTemplates/ChangeEmail.text.cshtml new file mode 100644 index 0000000000..14a20324c0 --- /dev/null +++ b/src/Core/MailTemplates/ChangeEmail.text.cshtml @@ -0,0 +1,6 @@ +@model Bit.Core.Models.Mail.ChangeEmailViewModel +@{ + Layout = "_BasicMailLayout"; +} +To finalize changing your email address enter the +following code in the pop-up window: @Model.Token diff --git a/src/Core/MailTemplates/ChangeEmailAlreadyExists.cshtml b/src/Core/MailTemplates/ChangeEmailAlreadyExists.cshtml new file mode 100644 index 0000000000..fd0ec2e08a --- /dev/null +++ b/src/Core/MailTemplates/ChangeEmailAlreadyExists.cshtml @@ -0,0 +1,11 @@ +@model Bit.Core.Models.Mail.ChangeEmailExistsViewModel +@{ + Layout = "_BasicMailLayout"; +} +

+ A user (@Model.FromEmail) recently tried to change their account to use this + email address (@Model.ToEmail). An account already exists with this email (@Model.ToEmail). +

+

+ If you did not try to change an email address, you can safely ignore this email. +

\ No newline at end of file diff --git a/src/Core/MailTemplates/ChangeEmailAlreadyExists.text.cshtml b/src/Core/MailTemplates/ChangeEmailAlreadyExists.text.cshtml new file mode 100644 index 0000000000..5e8d2a6bf7 --- /dev/null +++ b/src/Core/MailTemplates/ChangeEmailAlreadyExists.text.cshtml @@ -0,0 +1,12 @@ +@model Bit.Core.Models.Mail.ChangeEmailExistsViewModel +@{ + Layout = "_BasicMailLayout"; +} +A user ({{fromEmail}}) recently tried to +change their account to use this email +address ({{toEmail}}). An account already +exists with this email ({{toEmail}}). + +If you did not try to change an email +address, you can safely ignore this +email. diff --git a/src/Core/MailTemplates/MasterPasswordHint.text.cshtml b/src/Core/MailTemplates/MasterPasswordHint.text.cshtml index 2df848e378..dfec7d69c7 100644 --- a/src/Core/MailTemplates/MasterPasswordHint.text.cshtml +++ b/src/Core/MailTemplates/MasterPasswordHint.text.cshtml @@ -2,10 +2,12 @@ @{ Layout = "_BasicMailLayout.text"; } -You (or someone) recently requested your master password hint. +You (or someone) recently requested your master +password hint. Your hint is: "@Model.Hint" Login: @Model.WebVaultUrl -If you did not request your master password hint you can safely ignore this email. +If you did not request your master password hint +you can safely ignore this email. diff --git a/src/Core/MailTemplates/NoMasterPasswordHint.text.cshtml b/src/Core/MailTemplates/NoMasterPasswordHint.text.cshtml index 6ff6cdd03c..f9ddbf84ce 100644 --- a/src/Core/MailTemplates/NoMasterPasswordHint.text.cshtml +++ b/src/Core/MailTemplates/NoMasterPasswordHint.text.cshtml @@ -2,6 +2,9 @@ @{ Layout = "_BasicMailLayout"; } -You (or someone) recently requested your master password hint. Unfortunately, your account does not have a master password hint. +You (or someone) recently requested your +master password hint. Unfortunately, your +account does not have a master password hint. -If you did not request your master password hint you can safely ignore this email. +If you did not request your master password +hint you can safely ignore this email. diff --git a/src/Core/MailTemplates/OrganizationUserAccepted.cshtml b/src/Core/MailTemplates/OrganizationUserAccepted.cshtml new file mode 100644 index 0000000000..8a6558c8ae --- /dev/null +++ b/src/Core/MailTemplates/OrganizationUserAccepted.cshtml @@ -0,0 +1,7 @@ +@model Bit.Core.Models.Mail.OrganizationUserAcceptedViewModel +@{ + Layout = "_BasicMailLayout"; +} +

This email is to notify you that @Model.UserEmail has accepted your invitation to join @Model.OrganizationName.

+

To confirm this user, log into the bitwarden web vault, manage your organization "People, and confirm the user.

+

If you do not wish to confirm this user, you can also remove them from the organization on the same page.

diff --git a/src/Core/MailTemplates/OrganizationUserAccepted.text.cshtml b/src/Core/MailTemplates/OrganizationUserAccepted.text.cshtml new file mode 100644 index 0000000000..0d645fc09b --- /dev/null +++ b/src/Core/MailTemplates/OrganizationUserAccepted.text.cshtml @@ -0,0 +1,15 @@ +@model Bit.Core.Models.Mail.OrganizationUserConfirmedViewModel +@{ + Layout = "_BasicMailLayout.text"; +} +This email is to notify you that @Model.UserEmail +has accepted your invitation to join +@Model.OrganizationName. + +To confirm this user, log into the bitwarden +web vault, manage your organization "People" +and confirm the user. + +If you do not wish to confirm this user, +you can also remove them from the +organization on the same page. diff --git a/src/Core/MailTemplates/OrganizationUserConfirmed.cshtml b/src/Core/MailTemplates/OrganizationUserConfirmed.cshtml new file mode 100644 index 0000000000..b44ae3d610 --- /dev/null +++ b/src/Core/MailTemplates/OrganizationUserConfirmed.cshtml @@ -0,0 +1,6 @@ +@model Bit.Core.Models.Mail.OrganizationUserConfirmedViewModel +@{ + Layout = "_BasicMailLayout"; +} +

This email is to notify you that you have been confirmed as a user of @(Model.OrganizationName).

+

Any collections and logins being shared with you by this organization will now appear in your bitwarden vault.

diff --git a/src/Core/MailTemplates/OrganizationUserConfirmed.text.cshtml b/src/Core/MailTemplates/OrganizationUserConfirmed.text.cshtml new file mode 100644 index 0000000000..415605a684 --- /dev/null +++ b/src/Core/MailTemplates/OrganizationUserConfirmed.text.cshtml @@ -0,0 +1,10 @@ +@model Bit.Core.Models.Mail.OrganizationUserConfirmedViewModel +@{ + Layout = "_BasicMailLayout.text"; +} +This email is to notify you that you have been +confirmed as a user of @(Model.OrganizationName). + +Any collections and logins being shared with +you by this organization will now appear in +your bitwarden vault. \ No newline at end of file diff --git a/src/Core/MailTemplates/OrganizationUserInvited.cshtml b/src/Core/MailTemplates/OrganizationUserInvited.cshtml new file mode 100644 index 0000000000..61001f94e4 --- /dev/null +++ b/src/Core/MailTemplates/OrganizationUserInvited.cshtml @@ -0,0 +1,13 @@ +@model Bit.Core.Models.Mail.OrganizationUserInvitedViewModel +@{ + Layout = "_BasicMailLayout"; +} +

+ You have been invited to join the @Model.OrganizationName organization. + To accept this invite, click the following link: +

+

@Model.Url

+

+ If you do not wish to join this organization, you can safely ignore + this email. +

\ No newline at end of file diff --git a/src/Core/MailTemplates/OrganizationUserInvited.text.cshtml b/src/Core/MailTemplates/OrganizationUserInvited.text.cshtml new file mode 100644 index 0000000000..7f2dcd1714 --- /dev/null +++ b/src/Core/MailTemplates/OrganizationUserInvited.text.cshtml @@ -0,0 +1,14 @@ +@model Bit.Core.Models.Mail.OrganizationUserInvitedViewModel +@{ + Layout = "_BasicMailLayout.text"; +} +You have been invited to join the +@Model.OrganizationName organization. +To accept this invite, click the +following link: + +@Model.Url + +If you do not wish to join this +organization, you can safely ignore +this email. \ No newline at end of file diff --git a/src/Core/MailTemplates/Welcome.cshtml b/src/Core/MailTemplates/Welcome.cshtml index 3938405461..05e0461d2b 100644 --- a/src/Core/MailTemplates/Welcome.cshtml +++ b/src/Core/MailTemplates/Welcome.cshtml @@ -1,10 +1,110 @@ @model Bit.Core.Models.Mail.BaseMailModel -@{ +@{ Layout = "_BasicMailLayout"; } - - - - - - \ No newline at end of file + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Thank you for creating an account with bitwarden. You may now log in with your new account. +
+ Did you know that bitwarden is free to sync with all of your devices? Download bitwarden today on: +
+ Mobile +
+ + + + + +
+ + Get bitwarden on Google Play + + + + Get bitwarden for iOS + +
+
+ Desktop +
+ + + + + + +
ChromeFirefoxOpera
+
+ + + + + + +
+ + Chrome Extension + + + + Firefox Extension + + + + Opera Extension + +
+
+ Web +
+ You can also access your vault from any web-enabled device using our web vault at: +
+ If you have any questions or problems you can email us from our website at https://bitwarden.com/contact. +
+ Thank you!
+ The bitwarden Team +
+
diff --git a/src/Core/MailTemplates/Welcome.text.cshtml b/src/Core/MailTemplates/Welcome.text.cshtml new file mode 100644 index 0000000000..aaa190d063 --- /dev/null +++ b/src/Core/MailTemplates/Welcome.text.cshtml @@ -0,0 +1,45 @@ +@model Bit.Core.Models.Mail.BaseMailModel +@{ + Layout = "_BasicMailLayout"; +} +Thank you for creating an account with bitwarden. +You may now log in with your new account. + +Did you know that bitwarden is free to sync with +all of your devices? Download bitwarden today on: + +Mobile +============ + +## iOS +https://itunes.apple.com/us/app/bitwarden-free-password-manager/id1137397744?mt=8 + +## Android +https://play.google.com/store/apps/details?id=com.x8bit.bitwarden + +Desktop +============ + +## Chrome Extension +https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb?utm_source=welcome_email&utm_medium=email + +## Firefox Extension +https://addons.mozilla.org/firefox/addon/bitwarden-password-manager/ + +## Opera Extension +https://addons.opera.com/extensions/details/bitwarden-free-password-manager/?utm_source=welcome_email&utm_medium=email + +Web +============ + +You can also access your vault from any +web-enabled device using our web vault at: +@Model.WebVaultUrl/?utm_source=welcome_email&utm_medium=email + + +If you have any questions or problems you can +email us from our website at: +https://bitwarden.com/contact/?utm_source=welcome_email&utm_medium=email + +Thank you! +The bitwarden Team diff --git a/src/Core/MailTemplates/_BasicMailLayout.cshtml b/src/Core/MailTemplates/_BasicMailLayout.cshtml index 98272d8d19..da5c946b2d 100644 --- a/src/Core/MailTemplates/_BasicMailLayout.cshtml +++ b/src/Core/MailTemplates/_BasicMailLayout.cshtml @@ -7,6 +7,6 @@ @RenderBody() Regards,
- The bitwarden team + The bitwarden Team diff --git a/src/Core/MailTemplates/_BasicMailLayout.text.cshtml b/src/Core/MailTemplates/_BasicMailLayout.text.cshtml index cc72885a74..2ffdb58713 100644 --- a/src/Core/MailTemplates/_BasicMailLayout.text.cshtml +++ b/src/Core/MailTemplates/_BasicMailLayout.text.cshtml @@ -1,3 +1,3 @@ @RenderBody() Regards, -The bitwarden team \ No newline at end of file +The bitwarden Team \ No newline at end of file diff --git a/src/Core/MailTemplates/_MailLayout.cshtml b/src/Core/MailTemplates/_MailLayout.cshtml new file mode 100644 index 0000000000..59d1cd0ba2 --- /dev/null +++ b/src/Core/MailTemplates/_MailLayout.cshtml @@ -0,0 +1,135 @@ + + + + + + bitwarden + + + + + + + + + + + +
+ + + + +
+ + @RenderBody() + + + + + + + + + +
+
+ + diff --git a/src/Core/MailTemplates/_MailLayout.text.cshtml b/src/Core/MailTemplates/_MailLayout.text.cshtml new file mode 100644 index 0000000000..d2cdadeff3 --- /dev/null +++ b/src/Core/MailTemplates/_MailLayout.text.cshtml @@ -0,0 +1,8 @@ +@RenderBody() +---------------------------- + +8bit Solutions LLC + +- https://twitter.com/bitwarden_app +- https://www.facebook.com/bitwarden/ +- https://plus.google.com/114869903467947368993 \ No newline at end of file diff --git a/src/Core/Models/Mail/ChangeEmailExistsViewModel.cs b/src/Core/Models/Mail/ChangeEmailExistsViewModel.cs new file mode 100644 index 0000000000..8eda668828 --- /dev/null +++ b/src/Core/Models/Mail/ChangeEmailExistsViewModel.cs @@ -0,0 +1,8 @@ +namespace Bit.Core.Models.Mail +{ + public class ChangeEmailExistsViewModel : BaseMailModel + { + public string FromEmail { get; set; } + public string ToEmail { get; set; } + } +} diff --git a/src/Core/Models/Mail/ChangeEmailViewModel.cs b/src/Core/Models/Mail/ChangeEmailViewModel.cs new file mode 100644 index 0000000000..7b753cbc0f --- /dev/null +++ b/src/Core/Models/Mail/ChangeEmailViewModel.cs @@ -0,0 +1,7 @@ +namespace Bit.Core.Models.Mail +{ + public class ChangeEmailViewModel : BaseMailModel + { + public string Token { get; set; } + } +} diff --git a/src/Core/Models/Mail/OrganizationUserAcceptedViewModel.cs b/src/Core/Models/Mail/OrganizationUserAcceptedViewModel.cs new file mode 100644 index 0000000000..f90b5e2304 --- /dev/null +++ b/src/Core/Models/Mail/OrganizationUserAcceptedViewModel.cs @@ -0,0 +1,8 @@ +namespace Bit.Core.Models.Mail +{ + public class OrganizationUserAcceptedViewModel : BaseMailModel + { + public string OrganizationName { get; set; } + public string UserEmail { get; set; } + } +} diff --git a/src/Core/Models/Mail/OrganizationUserConfirmedViewModel.cs b/src/Core/Models/Mail/OrganizationUserConfirmedViewModel.cs new file mode 100644 index 0000000000..e15cf54eed --- /dev/null +++ b/src/Core/Models/Mail/OrganizationUserConfirmedViewModel.cs @@ -0,0 +1,7 @@ +namespace Bit.Core.Models.Mail +{ + public class OrganizationUserConfirmedViewModel : BaseMailModel + { + public string OrganizationName { get; set; } + } +} diff --git a/src/Core/Models/Mail/OrganizationUserInvitedViewModel.cs b/src/Core/Models/Mail/OrganizationUserInvitedViewModel.cs new file mode 100644 index 0000000000..653dccef6f --- /dev/null +++ b/src/Core/Models/Mail/OrganizationUserInvitedViewModel.cs @@ -0,0 +1,20 @@ +namespace Bit.Core.Models.Mail +{ + public class OrganizationUserInvitedViewModel : BaseMailModel + { + public string OrganizationName { get; set; } + public string OrganizationId { get; set; } + public string OrganizationUserId { get; set; } + public string Email { get; set; } + public string OrganizationNameUrlEncoded { get; set; } + public string Token { get; set; } + public string Url => string.Format("{0}/accept-organization?organizationId={1}&" + + "organizationUserId={2}&email={3}&organizationName={4}&token={5}", + WebVaultUrl, + OrganizationId, + OrganizationUserId, + Email, + OrganizationNameUrlEncoded, + Token); + } +} diff --git a/src/Core/Services/Implementations/RazorMailService.cs b/src/Core/Services/Implementations/RazorMailService.cs deleted file mode 100644 index c42c910b6b..0000000000 --- a/src/Core/Services/Implementations/RazorMailService.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Bit.Core.Models.Table; -using RazorLight; -using Bit.Core.Models.Mail; -using RazorLight.Templating; -using System.IO; - -namespace Bit.Core.Services -{ - public class RazorMailService : IMailService - { - private readonly GlobalSettings _globalSettings; - private readonly IRazorLightEngine _engine; - private readonly IMailDeliveryService _mailDeliveryService; - - public RazorMailService( - GlobalSettings globalSettings, - IMailDeliveryService mailDeliveryService) - { - _globalSettings = globalSettings; - _mailDeliveryService = mailDeliveryService; - - var manager = new CustomEmbeddedResourceTemplateManager("Bit.Core.MailTemplates"); - var core = new EngineCore(manager, EngineConfiguration.Default); - var pageFactory = new DefaultPageFactory(core.KeyCompile); - var lookup = new DefaultPageLookup(pageFactory); - _engine = new RazorLightEngine(core, lookup); - } - - public Task SendChangeEmailAlreadyExistsEmailAsync(string fromEmail, string toEmail) - { - throw new NotImplementedException(); - } - - public Task SendChangeEmailEmailAsync(string newEmailAddress, string token) - { - throw new NotImplementedException(); - } - - public async Task SendMasterPasswordHintEmailAsync(string email, string hint) - { - var message = CreateDefaultMessage("Your Master Password Hint", email); - var model = new MasterPasswordHintViewModel - { - Hint = hint - }; - message.HtmlContent = _engine.Parse("MasterPasswordHint", model); - message.TextContent = _engine.Parse("MasterPasswordHint.text", model); - await _mailDeliveryService.SendEmailAsync(message); - } - - public async Task SendNoMasterPasswordHintEmailAsync(string email) - { - var message = CreateDefaultMessage("Your Master Password Hint", email); - var model = new BaseMailModel(); - message.HtmlContent = _engine.Parse("NoMasterPasswordHint", model); - message.TextContent = _engine.Parse("NoMasterPasswordHint.text", model); - await _mailDeliveryService.SendEmailAsync(message); - } - - 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) - { - throw new NotImplementedException(); - } - - public Task SendWelcomeEmailAsync(User user) - { - throw new NotImplementedException(); - } - - private MailMessage CreateDefaultMessage(string subject, string toEmail) - { - return CreateDefaultMessage(subject, new List { toEmail }); - } - - private MailMessage CreateDefaultMessage(string subject, IEnumerable toEmails) - { - var message = new MailMessage - { - MetaData = new Dictionary(), - ToEmails = toEmails, - Subject = subject - }; - - return message; - } - - public class CustomEmbeddedResourceTemplateManager : ITemplateManager - { - public CustomEmbeddedResourceTemplateManager(string rootNamespace) - { - if(rootNamespace == null) - { - throw new ArgumentNullException(nameof(rootNamespace)); - } - - Namespace = rootNamespace; - } - - public string Namespace { get; } - - public ITemplateSource Resolve(string key) - { - var assembly = GetType().Assembly; - using(var stream = assembly.GetManifestResourceStream(Namespace + "." + key + ".cshtml")) - { - if(stream == null) - { - throw new RazorLightException(string.Format("Couldn't load resource '{0}.{1}.cshtml'.", Namespace, key)); - } - - using(var reader = new StreamReader(stream)) - { - return new LoadedTemplateSource(reader.ReadToEnd()); - } - } - } - } - } -} diff --git a/src/Core/Services/Implementations/RazorViewMailService.cs b/src/Core/Services/Implementations/RazorViewMailService.cs new file mode 100644 index 0000000000..69ddce9b9f --- /dev/null +++ b/src/Core/Services/Implementations/RazorViewMailService.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Bit.Core.Models.Table; +using RazorLight; +using Bit.Core.Models.Mail; +using RazorLight.Templating; +using System.IO; +using System.Net; + +namespace Bit.Core.Services +{ + public class RazorViewMailService : IMailService + { + private readonly GlobalSettings _globalSettings; + private readonly IRazorLightEngine _engine; + private readonly IMailDeliveryService _mailDeliveryService; + + public RazorViewMailService( + GlobalSettings globalSettings, + IMailDeliveryService mailDeliveryService) + { + _globalSettings = globalSettings; + _mailDeliveryService = mailDeliveryService; + + var manager = new CustomEmbeddedResourceTemplateManager("Bit.Core.MailTemplates"); + var core = new EngineCore(manager, EngineConfiguration.Default); + var pageFactory = new DefaultPageFactory(core.KeyCompile); + var lookup = new DefaultPageLookup(pageFactory); + _engine = new RazorLightEngine(core, lookup); + } + + public async Task SendChangeEmailAlreadyExistsEmailAsync(string fromEmail, string toEmail) + { + var message = CreateDefaultMessage("Your Email Change", toEmail); + var model = new ChangeEmailExistsViewModel + { + FromEmail = fromEmail, + ToEmail = toEmail, + WebVaultUrl = _globalSettings.BaseVaultUri, + SiteName = _globalSettings.SiteName + }; + message.HtmlContent = _engine.Parse("ChangeEmailAlreadyExists", model); + message.TextContent = _engine.Parse("ChangeEmailAlreadyExists.text", model); + await _mailDeliveryService.SendEmailAsync(message); + } + + public async Task SendChangeEmailEmailAsync(string newEmailAddress, string token) + { + var message = CreateDefaultMessage("Your Email Change", newEmailAddress); + var model = new ChangeEmailViewModel + { + Token = token, + WebVaultUrl = _globalSettings.BaseVaultUri, + SiteName = _globalSettings.SiteName + }; + message.HtmlContent = _engine.Parse("ChangeEmail", model); + message.TextContent = _engine.Parse("ChangeEmail.text", model); + message.MetaData.Add("SendGridBypassListManagement", true); + + await _mailDeliveryService.SendEmailAsync(message); + } + + public async Task SendMasterPasswordHintEmailAsync(string email, string hint) + { + var message = CreateDefaultMessage("Your Master Password Hint", email); + var model = new MasterPasswordHintViewModel + { + Hint = hint, + WebVaultUrl = _globalSettings.BaseVaultUri, + SiteName = _globalSettings.SiteName + }; + message.HtmlContent = _engine.Parse("MasterPasswordHint", model); + message.TextContent = _engine.Parse("MasterPasswordHint.text", model); + await _mailDeliveryService.SendEmailAsync(message); + } + + public async Task SendNoMasterPasswordHintEmailAsync(string email) + { + var message = CreateDefaultMessage("Your Master Password Hint", email); + var model = new BaseMailModel + { + WebVaultUrl = _globalSettings.BaseVaultUri, + SiteName = _globalSettings.SiteName + }; + message.HtmlContent = _engine.Parse("NoMasterPasswordHint", model); + message.TextContent = _engine.Parse("NoMasterPasswordHint.text", model); + await _mailDeliveryService.SendEmailAsync(message); + } + + public async Task SendOrganizationAcceptedEmailAsync(string organizationName, string userEmail, + IEnumerable adminEmails) + { + var message = CreateDefaultMessage($"User {userEmail} Has Accepted Invite", adminEmails); + var model = new OrganizationUserAcceptedViewModel + { + OrganizationName = organizationName, + UserEmail = userEmail, + WebVaultUrl = _globalSettings.BaseVaultUri, + SiteName = _globalSettings.SiteName + }; + message.HtmlContent = _engine.Parse("OrganizationUserInvited", model); + message.TextContent = _engine.Parse("OrganizationUserInvited.text", model); + await _mailDeliveryService.SendEmailAsync(message); + } + + public async Task SendOrganizationConfirmedEmailAsync(string organizationName, string email) + { + var message = CreateDefaultMessage($"You Have Been Confirmed To {organizationName}", email); + var model = new OrganizationUserConfirmedViewModel + { + OrganizationName = organizationName, + WebVaultUrl = _globalSettings.BaseVaultUri, + SiteName = _globalSettings.SiteName + }; + message.HtmlContent = _engine.Parse("OrganizationUserConfirmed", model); + message.TextContent = _engine.Parse("OrganizationUserConfirmed.text", model); + await _mailDeliveryService.SendEmailAsync(message); + } + + public async Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, string token) + { + var message = CreateDefaultMessage($"Join {organizationName}", orgUser.Email); + var model = new OrganizationUserInvitedViewModel + { + OrganizationName = organizationName, + Email = WebUtility.UrlEncode(orgUser.Email), + OrganizationId = orgUser.OrganizationId.ToString(), + OrganizationUserId = orgUser.UserId.ToString(), + Token = token, + OrganizationNameUrlEncoded = WebUtility.UrlEncode(organizationName), + WebVaultUrl = _globalSettings.BaseVaultUri, + SiteName = _globalSettings.SiteName + }; + message.HtmlContent = _engine.Parse("OrganizationUserInvited", model); + message.TextContent = _engine.Parse("OrganizationUserInvited.text", model); + await _mailDeliveryService.SendEmailAsync(message); + } + + public async Task SendWelcomeEmailAsync(User user) + { + throw new NotImplementedException(); + } + + private MailMessage CreateDefaultMessage(string subject, string toEmail) + { + return CreateDefaultMessage(subject, new List { toEmail }); + } + + private MailMessage CreateDefaultMessage(string subject, IEnumerable toEmails) + { + return new MailMessage + { + ToEmails = toEmails, + Subject = subject + }; + } + + public class CustomEmbeddedResourceTemplateManager : ITemplateManager + { + public CustomEmbeddedResourceTemplateManager(string rootNamespace) + { + Namespace = rootNamespace ?? throw new ArgumentNullException(nameof(rootNamespace)); + } + + public string Namespace { get; } + + public ITemplateSource Resolve(string key) + { + var assembly = GetType().Assembly; + using(var stream = assembly.GetManifestResourceStream(Namespace + "." + key + ".cshtml")) + { + if(stream == null) + { + throw new RazorLightException(string.Format("Couldn't load resource '{0}.{1}.cshtml'.", Namespace, key)); + } + + using(var reader = new StreamReader(stream)) + { + return new LoadedTemplateSource(reader.ReadToEnd()); + } + } + } + } + } +} diff --git a/src/Core/Services/Implementations/SendGridMailDeliveryService.cs b/src/Core/Services/Implementations/SendGridMailDeliveryService.cs index 683e1f6a31..bce8c9d985 100644 --- a/src/Core/Services/Implementations/SendGridMailDeliveryService.cs +++ b/src/Core/Services/Implementations/SendGridMailDeliveryService.cs @@ -36,26 +36,26 @@ namespace Bit.Core.Services sendGridMessage.AddTos(message.ToEmails.Select(e => new EmailAddress(e)).ToList()); - if(message.MetaData.ContainsKey("SendGridTemplateId")) + if(message.MetaData?.ContainsKey("SendGridTemplateId") ?? false) { sendGridMessage.HtmlContent = " "; sendGridMessage.PlainTextContent = " "; sendGridMessage.TemplateId = message.MetaData["SendGridTemplateId"].ToString(); } - if(message.MetaData.ContainsKey("SendGridSubstitutions")) + if(message.MetaData?.ContainsKey("SendGridSubstitutions") ?? false) { var subs = message.MetaData["SendGridSubstitutions"] as Dictionary; sendGridMessage.AddSubstitutions(subs); } - if(message.MetaData.ContainsKey("SendGridCategories")) + if(message.MetaData?.ContainsKey("SendGridCategories") ?? false) { var cats = message.MetaData["SendGridCategories"] as List; sendGridMessage.AddCategories(cats); } - if(message.MetaData.ContainsKey("SendGridBypassListManagement")) + if(message.MetaData?.ContainsKey("SendGridBypassListManagement") ?? false) { var bypass = message.MetaData["SendGridBypassListManagement"] as bool?; sendGridMessage.SetBypassListManagement(bypass.GetValueOrDefault(false)); diff --git a/src/Core/Utilities/ServiceCollectionExtensions.cs b/src/Core/Utilities/ServiceCollectionExtensions.cs index 6dbf2751bc..3d88857b41 100644 --- a/src/Core/Utilities/ServiceCollectionExtensions.cs +++ b/src/Core/Utilities/ServiceCollectionExtensions.cs @@ -50,7 +50,7 @@ namespace Bit.Core.Utilities public static void AddDefaultServices(this IServiceCollection services) { //services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton();