using System; using System.Collections.Generic; using System.Threading.Tasks; using Bit.Core.Models.Table; using Bit.Core.Models.Mail; using System.IO; using System.Net; using System.Reflection; namespace Bit.Core.Services { public class MarkdownMailService : IMailService { private const string Namespace = "Bit.Core.MailTemplates.Markdown"; private readonly GlobalSettings _globalSettings; private readonly IMailDeliveryService _mailDeliveryService; public MarkdownMailService( GlobalSettings globalSettings, IMailDeliveryService mailDeliveryService) { _globalSettings = globalSettings; _mailDeliveryService = mailDeliveryService; } public async Task SendVerifyEmailEmailAsync(string email, Guid userId, string token) { var model = new Dictionary { ["url"] = string.Format("{0}/verify-email?userId={1}&token={2}", _globalSettings.BaseServiceUri.VaultWithHash, userId, WebUtility.UrlEncode(token)) }; var message = await CreateMessageAsync("Verify Your Email", email, "VerifyEmail", model); message.MetaData.Add("SendGridBypassListManagement", true); await _mailDeliveryService.SendEmailAsync(message); } public async Task SendVerifyDeleteEmailAsync(string email, Guid userId, string token) { var model = new Dictionary { ["url"] = string.Format("{0}/verify-recover-delete?userId={1}&token={2}&email={3}", _globalSettings.BaseServiceUri.VaultWithHash, userId, WebUtility.UrlEncode(token), WebUtility.UrlEncode(email)), ["email"] = WebUtility.HtmlEncode(email) }; var message = await CreateMessageAsync("Delete Your Account", email, "VerifyDelete", model); message.MetaData.Add("SendGridBypassListManagement", true); await _mailDeliveryService.SendEmailAsync(message); } public async Task SendChangeEmailAlreadyExistsEmailAsync(string fromEmail, string toEmail) { var model = new Dictionary { ["fromEmail"] = WebUtility.HtmlEncode(fromEmail), ["toEmail"] = WebUtility.HtmlEncode(toEmail), }; var message = await CreateMessageAsync("Your Email Change", toEmail, "ChangeEmailAlreadyExists", model); message.MetaData.Add("SendGridBypassListManagement", true); await _mailDeliveryService.SendEmailAsync(message); } public async Task SendChangeEmailEmailAsync(string newEmailAddress, string token) { var model = new Dictionary { ["token"] = token }; var message = await CreateMessageAsync("Your Email Change", newEmailAddress, "ChangeEmail", model); message.MetaData.Add("SendGridBypassListManagement", true); await _mailDeliveryService.SendEmailAsync(message); } public async Task SendTwoFactorEmailAsync(string email, string token) { var model = new Dictionary { ["token"] = token }; var message = await CreateMessageAsync("Your Two-step Login Verification Code", email, "TwoFactorEmail", model); message.MetaData.Add("SendGridBypassListManagement", true); await _mailDeliveryService.SendEmailAsync(message); } public async Task SendMasterPasswordHintEmailAsync(string email, string hint) { var model = new Dictionary { ["hint"] = WebUtility.HtmlEncode(hint), ["vaultUrl"] = _globalSettings.BaseServiceUri.VaultWithHash }; var message = await CreateMessageAsync("Your Master Password Hint", email, "MasterPasswordHint", model); message.MetaData.Add("SendGridBypassListManagement", true); await _mailDeliveryService.SendEmailAsync(message); } public async Task SendNoMasterPasswordHintEmailAsync(string email) { var message = await CreateMessageAsync("Your Master Password Hint", email, "NoMasterPasswordHint", null); message.MetaData.Add("SendGridBypassListManagement", true); await _mailDeliveryService.SendEmailAsync(message); } public async Task SendOrganizationAcceptedEmailAsync(string organizationName, string userEmail, IEnumerable adminEmails) { var model = new Dictionary { ["userEmail"] = WebUtility.HtmlEncode(userEmail), ["organizationName"] = WebUtility.HtmlEncode(organizationName) }; var message = await CreateMessageAsync($"User {userEmail} Has Accepted Invite", adminEmails, "OrganizationUserAccepted", model); message.MetaData.Add("SendGridBypassListManagement", true); await _mailDeliveryService.SendEmailAsync(message); } public async Task SendOrganizationConfirmedEmailAsync(string organizationName, string email) { var model = new Dictionary { ["organizationName"] = WebUtility.HtmlEncode(organizationName) }; var message = await CreateMessageAsync($"You Have Been Confirmed To {organizationName}", email, "OrganizationUserConfirmed", model); message.MetaData.Add("SendGridBypassListManagement", true); await _mailDeliveryService.SendEmailAsync(message); } public async Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, string token) { var model = new Dictionary { ["organizationName"] = WebUtility.HtmlEncode(organizationName), ["url"] = string.Format("{0}/accept-organization?organizationId={1}&organizationUserId={2}" + "&email={3}&organizationName={4}&token={5}", _globalSettings.BaseServiceUri.VaultWithHash, orgUser.OrganizationId, orgUser.Id, WebUtility.UrlEncode(orgUser.Email), WebUtility.UrlEncode(organizationName), WebUtility.UrlEncode(token)) }; var message = await CreateMessageAsync($"Join {organizationName}", orgUser.Email, "OrganizationUserInvited", model); message.MetaData.Add("SendGridBypassListManagement", true); await _mailDeliveryService.SendEmailAsync(message); } public async Task SendWelcomeEmailAsync(User user) { var model = new Dictionary { ["vaultUrl"] = _globalSettings.BaseServiceUri.VaultWithHash }; var message = await CreateMessageAsync("Welcome", user.Email, "Welcome", model); await _mailDeliveryService.SendEmailAsync(message); } private async Task CreateMessageAsync(string subject, string toEmail, string fileName, Dictionary model) { return await CreateMessageAsync(subject, new List { toEmail }, fileName, model); } private async Task CreateMessageAsync(string subject, IEnumerable toEmails, string fileName, Dictionary model) { var message = new MailMessage { ToEmails = toEmails, Subject = subject, MetaData = new Dictionary() }; var assembly = typeof(MarkdownMailService).GetTypeInfo().Assembly; using(var s = assembly.GetManifestResourceStream($"{Namespace}.{fileName}.md")) using(var sr = new StreamReader(s)) { var markdown = await sr.ReadToEndAsync(); if(model != null) { foreach(var prop in model) { markdown = markdown.Replace($"{{{{{prop.Key}}}}}", prop.Value); } } message.HtmlContent = CommonMark.CommonMarkConverter.Convert(markdown); message.TextContent = markdown; } return message; } } }