1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-24 22:32:22 -05:00
bitwarden/src/Core/Services/Implementations/PostalMailDeliveryService.cs
Thomas Rittson e1908cd6b5
Add support for international domain names (IDN) in email addresses (#1512)
* Adjust email address checking to handle unicode

* ASCII only in local part
* allow unicode in second-level and top-level domain

* Add PunyEncoding/Decoding methods and tests

* Use PunyEncoding for outbound email recipients

* Use MailKit for punycode, handle edge cases

* Punyencode all email addresses in mailServices

* Remove punyencoding from HandlebarsMailService

* Add to punyencoding tests

* Use more inclusive e-mail error

* Fix comment wording

* Apply StrictEmail checking to emergency access invite

* Remove punyDecode helper
2021-08-31 13:49:11 +10:00

120 lines
4.3 KiB
C#

using System;
using System.Threading.Tasks;
using Bit.Core.Settings;
using Microsoft.Extensions.Logging;
using System.Net.Http;
using System.Collections.Generic;
using Newtonsoft.Json;
using Microsoft.AspNetCore.Hosting;
using System.Text;
using Bit.Core.Utilities;
namespace Bit.Core.Services
{
public class PostalMailDeliveryService : IMailDeliveryService
{
private readonly GlobalSettings _globalSettings;
private readonly ILogger<PostalMailDeliveryService> _logger;
private readonly IHttpClientFactory _clientFactory;
private readonly string _baseTag;
private readonly string _from;
private readonly string _reply;
public PostalMailDeliveryService(
GlobalSettings globalSettings,
ILogger<PostalMailDeliveryService> logger,
IWebHostEnvironment hostingEnvironment,
IHttpClientFactory clientFactory)
{
var postalDomain = CoreHelpers.PunyEncode(globalSettings.Mail.PostalDomain);
var replyToEmail = CoreHelpers.PunyEncode(globalSettings.Mail.ReplyToEmail);
_globalSettings = globalSettings;
_logger = logger;
_clientFactory = clientFactory;
_baseTag = $"Env_{hostingEnvironment.EnvironmentName}-" +
$"Server_{globalSettings.ProjectName?.Replace(' ', '_')}";
_from = $"\"{globalSettings.SiteName}\" <no-reply@{postalDomain}>";
_reply = $"\"{globalSettings.SiteName}\" <{replyToEmail}>";
}
public async Task SendEmailAsync(Models.Mail.MailMessage message)
{
var httpClient = _clientFactory.CreateClient("PostalMailDeliveryService");
httpClient.DefaultRequestHeaders.Add("X-Server-API-Key", _globalSettings.Mail.PostalApiKey);
var request = new PostalRequest
{
subject = message.Subject,
from = _from,
reply_to = _reply,
html_body = message.HtmlContent,
to = new List<string>(),
tag = _baseTag
};
foreach (var address in message.ToEmails)
{
request.to.Add(CoreHelpers.PunyEncode(address));
}
if (message.BccEmails != null)
{
request.bcc = new List<string>();
foreach (var address in message.BccEmails)
{
request.bcc.Add(CoreHelpers.PunyEncode(address));
}
}
if (!string.IsNullOrWhiteSpace(message.TextContent))
{
request.plain_body = message.TextContent;
}
if (!string.IsNullOrWhiteSpace(message.Category))
{
request.tag = string.Concat(request.tag, "-Cat_", message.Category);
}
var reqJson = JsonConvert.SerializeObject(request);
var responseMessage = await httpClient.PostAsync(
$"https://{_globalSettings.Mail.PostalDomain}/api/v1/send/message",
new StringContent(reqJson, Encoding.UTF8, "application/json"));
if (responseMessage.IsSuccessStatusCode)
{
var json = await responseMessage.Content.ReadAsStringAsync();
var response = JsonConvert.DeserializeObject<PostalResponse>(json);
if (response.status != "success")
{
_logger.LogError("Postal send status was not successful: {0}, {1}",
response.status, response.message);
}
}
else
{
_logger.LogError("Postal send failed: {0}", responseMessage.StatusCode);
}
}
public class PostalRequest
{
public List<string> to { get; set; }
public List<string> cc { get; set; }
public List<string> bcc { get; set; }
public string tag { get; set; }
public string from { get; set; }
public string reply_to { get; set; }
public string plain_body { get; set; }
public string html_body { get; set; }
public string subject { get; set; }
}
public class PostalResponse
{
public string status { get; set; }
public string message { get; set; }
}
}
}