Added EmailSenderEx. Improve error handling and reporting of email failures.

This commit is contained in:
Jared Goodwin 2020-03-09 08:47:00 -07:00
parent 5b852ae474
commit ac52bd5559
12 changed files with 135 additions and 68 deletions

View File

@ -24,7 +24,7 @@ namespace Remotely.Server.API
[ApiController]
public class OrganizationManagementController : ControllerBase
{
public OrganizationManagementController(DataService dataService, UserManager<RemotelyUser> userManager, IEmailSender emailSender)
public OrganizationManagementController(DataService dataService, UserManager<RemotelyUser> userManager, IEmailSenderEx emailSender)
{
this.DataService = dataService;
this.UserManager = userManager;
@ -32,7 +32,7 @@ namespace Remotely.Server.API
}
private DataService DataService { get; }
private IEmailSender EmailSender { get; }
private IEmailSenderEx EmailSender { get; }
private UserManager<RemotelyUser> UserManager { get; }
@ -268,14 +268,20 @@ namespace Remotely.Server.API
var newInvite = DataService.AddInvite(orgID, invite);
var inviteURL = $"{Request.Scheme}://{Request.Host}/Invite?id={newInvite.ID}";
await EmailSender.SendEmailAsync(invite.InvitedUser, "Invitation to Organization in Remotely",
var emailResult = await EmailSender.SendEmailAsync(invite.InvitedUser, "Invitation to Organization in Remotely",
$@"<img src='https://remotely.one/media/Remotely_Logo.png'/>
<br><br>
Hello!
<br><br>
You've been invited to join an organization in Remotely.
<br><br>
You can join the organization by <a href='{HtmlEncoder.Default.Encode(inviteURL)}'>clicking here</a>.");
You can join the organization by <a href='{HtmlEncoder.Default.Encode(inviteURL)}'>clicking here</a>.",
orgID);
if (!emailResult)
{
return Problem("There was an error sending the invitation email.");
}
return Ok();
}

View File

@ -19,11 +19,11 @@ namespace Remotely.Server.Areas.Identity.Pages.Account
public class ForgotPasswordModel : PageModel
{
private readonly UserManager<RemotelyUser> _userManager;
private readonly IEmailSender _emailSender;
private readonly IEmailSenderEx _emailSender;
private DataService DataService { get; }
public ForgotPasswordModel(UserManager<RemotelyUser> userManager, IEmailSender emailSender, DataService dataService)
public ForgotPasswordModel(UserManager<RemotelyUser> userManager, IEmailSenderEx emailSender, DataService dataService)
{
_userManager = userManager;
_emailSender = emailSender;
@ -63,11 +63,18 @@ namespace Remotely.Server.Areas.Identity.Pages.Account
DataService.WriteEvent($"Sending password reset for user {user.UserName}. Reset URL: {callbackUrl}", user.OrganizationID);
await _emailSender.SendEmailAsync(
var emailResult = await _emailSender.SendEmailAsync(
Input.Email,
"Reset Password",
$"<img src='https://remotely.one/media/Remotely_Logo.png'/><br><br>Please reset your Remotely password by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
if (!emailResult)
{
ModelState.AddModelError("EmailError", "Error sending email.");
return Page();
}
return RedirectToPage("./ForgotPasswordConfirmation");
}

View File

@ -7,11 +7,11 @@ using System.Threading.Tasks;
using Remotely.Shared.Models;
using Remotely.Server.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
using System.Text;
using Remotely.Server.Services;
namespace Remotely.Server.Areas.Identity.Pages.Account.Manage
{
@ -19,12 +19,12 @@ namespace Remotely.Server.Areas.Identity.Pages.Account.Manage
{
private readonly UserManager<RemotelyUser> _userManager;
private readonly SignInManager<RemotelyUser> _signInManager;
private readonly IEmailSender _emailSender;
private readonly IEmailSenderEx _emailSender;
public IndexModel(
UserManager<RemotelyUser> userManager,
SignInManager<RemotelyUser> signInManager,
IEmailSender emailSender)
IEmailSenderEx emailSender)
{
_userManager = userManager;
_signInManager = signInManager;
@ -131,7 +131,6 @@ namespace Remotely.Server.Areas.Identity.Pages.Account.Manage
}
var userId = await _userManager.GetUserIdAsync(user);
var email = await _userManager.GetEmailAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
@ -140,12 +139,20 @@ namespace Remotely.Server.Areas.Identity.Pages.Account.Manage
pageHandler: null,
values: new { area = "Identity", userId = user.Id, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(
var emailResult = await _emailSender.SendEmailAsync(
email,
"Confirm your email",
$"<img src='https://remotely.one/media/Remotely_Logo.png'/><br><br>Please confirm your Remotely account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
$"<img src='https://remotely.one/media/Remotely_Logo.png'/><br><br>Please confirm your Remotely account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.",
user.OrganizationID);
StatusMessage = "Verification email sent. Please check your email.";
if (!emailResult)
{
StatusMessage = "Error sending verification email.";
}
else
{
StatusMessage = "Verification email sent. Please check your email.";
}
return RedirectToPage();
}
}

View File

@ -18,7 +18,7 @@ namespace Remotely.Server.Areas.Identity.Pages.Account.Manage
{
public class OrganizationModel : PageModel
{
public OrganizationModel(DataService dataService, UserManager<RemotelyUser> userManager, IEmailSender emailSender)
public OrganizationModel(DataService dataService, UserManager<RemotelyUser> userManager, IEmailSenderEx emailSender)
{
DataService = dataService;
UserManager = userManager;
@ -45,7 +45,7 @@ namespace Remotely.Server.Areas.Identity.Pages.Account.Manage
private DataService DataService { get; }
private IEmailSender EmailSender { get; }
private IEmailSenderEx EmailSender { get; }
private UserManager<RemotelyUser> UserManager { get; }
@ -127,16 +127,23 @@ namespace Remotely.Server.Areas.Identity.Pages.Account.Manage
var newInvite = DataService.AddInvite(currentUser.OrganizationID, invite);
var inviteURL = $"{Request.Scheme}://{Request.Host}/Invite?id={newInvite.ID}";
await EmailSender.SendEmailAsync(invite.InvitedUser, "Invitation to Organization in Remotely",
$@"<img src='https://remotely.one/media/Remotely_Logo.png'/>
var emailResult = await EmailSender.SendEmailAsync(invite.InvitedUser, "Invitation to Organization in Remotely",
$@"<img src='https://remotely.one/media/Remotely_Logo.png'/>
<br><br>
Hello!
<br><br>
You've been invited to join an organization in Remotely.
<br><br>
You can join the organization by <a href='{HtmlEncoder.Default.Encode(inviteURL)}'>clicking here</a>.");
StatusMessage = "Invitation sent.";
You can join the organization by <a href='{HtmlEncoder.Default.Encode(inviteURL)}'>clicking here</a>.",
currentUser.OrganizationID);
if (emailResult)
{
StatusMessage = "Invitation sent.";
}
else
{
StatusMessage = "Error sending invititation email.";
}
return RedirectToPage();
}

View File

@ -56,7 +56,7 @@
<div class="form-group">
<label asp-for="AppSettingsInput.KnownProxies" class="control-label"></label>
<br />
<select id="knownProxiesSelect"
<select id="knownProxiesSelect"
asp-for="AppSettingsInput.KnownProxies"
asp-items="Model.AppSettingsInput.KnownProxies?.Select(x=>new SelectListItem(x,x))"
class="form-control"></select>
@ -170,9 +170,9 @@
<div class="form-group">
<label asp-for="AppSettingsInput.TrustedCorsOrigins" class="control-label"></label>
<br />
<select id="trustedCorsSelect"
asp-for="AppSettingsInput.TrustedCorsOrigins"
asp-items="Model.AppSettingsInput.TrustedCorsOrigins?.Select(x=>new SelectListItem(x,x))"
<select id="trustedCorsSelect"
asp-for="AppSettingsInput.TrustedCorsOrigins"
asp-items="Model.AppSettingsInput.TrustedCorsOrigins?.Select(x=>new SelectListItem(x,x))"
class="form-control"></select>
<div class="text-right mb-2 mt-1">
@ -233,8 +233,8 @@
<div class="form-group">
<label asp-for="ServerAdmins" class="control-label"></label>
<br />
<select id="serverAdminsSelect"
asp-for="ServerAdmins"
<select id="serverAdminsSelect"
asp-for="ServerAdmins"
asp-items="Model.ServerAdmins?.Select(x=>new SelectListItem(x,x))"
class="form-control"></select>
<div class="text-right mb-2 mt-1">

View File

@ -25,7 +25,7 @@ namespace Remotely.Server.Areas.Identity.Pages.Account
private readonly SignInManager<RemotelyUser> _signInManager;
private readonly UserManager<RemotelyUser> _userManager;
private readonly ILogger<RegisterModel> _logger;
private readonly IEmailSender _emailSender;
private readonly IEmailSenderEx _emailSender;
private readonly DataService _dataService;
private readonly ApplicationConfig _appConfig;
@ -33,7 +33,7 @@ namespace Remotely.Server.Areas.Identity.Pages.Account
UserManager<RemotelyUser> userManager,
SignInManager<RemotelyUser> signInManager,
ILogger<RegisterModel> logger,
IEmailSender emailSender,
IEmailSenderEx emailSender,
DataService dataService,
ApplicationConfig appConfig)
{

View File

@ -288,7 +288,7 @@ namespace Remotely.Server.Services
}
catch (Exception ex)
{
WriteEvent(ex);
WriteEvent(ex, organizationID);
return false;
}
@ -851,45 +851,62 @@ namespace Remotely.Server.Services
}
public void WriteEvent(EventLog eventLog)
{
RemotelyContext.EventLogs.Add(eventLog);
RemotelyContext.SaveChanges();
try
{
RemotelyContext.EventLogs.Add(eventLog);
RemotelyContext.SaveChanges();
}
catch { }
}
public void WriteEvent(Exception ex)
public void WriteEvent(Exception ex, string organizationID = null)
{
RemotelyContext.EventLogs.Add(new EventLog()
try
{
EventType = EventType.Error,
Message = ex.Message,
Source = ex.Source,
StackTrace = ex.StackTrace,
TimeStamp = DateTimeOffset.Now
});
RemotelyContext.SaveChanges();
RemotelyContext.EventLogs.Add(new EventLog()
{
EventType = EventType.Error,
Message = ex.Message,
Source = ex.Source,
StackTrace = ex.StackTrace,
TimeStamp = DateTimeOffset.Now,
OrganizationID = organizationID
});
RemotelyContext.SaveChanges();
}
catch { }
}
public void WriteEvent(string message, string organizationId)
public void WriteEvent(string message, string organizationID)
{
RemotelyContext.EventLogs.Add(new EventLog()
try
{
EventType = EventType.Info,
Message = message,
TimeStamp = DateTimeOffset.Now,
OrganizationID = organizationId
});
RemotelyContext.SaveChanges();
RemotelyContext.EventLogs.Add(new EventLog()
{
EventType = EventType.Info,
Message = message,
TimeStamp = DateTimeOffset.Now,
OrganizationID = organizationID
});
RemotelyContext.SaveChanges();
}
catch { }
}
public void WriteEvent(string message, EventType eventType, string organizationId)
public void WriteEvent(string message, EventType eventType, string organizationID)
{
RemotelyContext.EventLogs.Add(new EventLog()
try
{
EventType = eventType,
Message = message,
TimeStamp = DateTimeOffset.Now,
OrganizationID = organizationId
});
RemotelyContext.SaveChanges();
RemotelyContext.EventLogs.Add(new EventLog()
{
EventType = eventType,
Message = message,
TimeStamp = DateTimeOffset.Now,
OrganizationID = organizationID
});
RemotelyContext.SaveChanges();
}
catch { }
}
}
}

View File

@ -109,7 +109,7 @@ namespace Remotely.Server.Services
}
catch (Exception ex)
{
DataService.WriteEvent(ex);
DataService.WriteEvent(ex, Device?.OrganizationID);
}
Context.Abort();

View File

@ -1,5 +1,4 @@
using Remotely.Server.Data;
using Microsoft.AspNetCore.Identity.UI.Services;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
@ -8,12 +7,19 @@ using System.Linq;
using System.Net;
using System.Net.Mail;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity.UI.Services;
namespace Remotely.Server.Services
{
public class EmailSender : IEmailSender
public interface IEmailSenderEx
{
public EmailSender(ApplicationConfig appConfig, DataService dataService)
Task<bool> SendEmailAsync(string email, string replyTo, string subject, string htmlMessage, string organizationID = null);
Task<bool> SendEmailAsync(string email, string subject, string htmlMessage, string organizationID = null);
}
public class EmailSenderEx : IEmailSenderEx
{
public EmailSenderEx(ApplicationConfig appConfig, DataService dataService)
{
AppConfig = appConfig;
DataService = dataService;
@ -22,7 +28,7 @@ namespace Remotely.Server.Services
private ApplicationConfig AppConfig { get; }
private DataService DataService { get; }
public Task SendEmailAsync(string email, string replyTo, string subject, string htmlMessage)
public Task<bool> SendEmailAsync(string email, string replyTo, string subject, string htmlMessage, string organizationID = null)
{
try
{
@ -42,17 +48,33 @@ namespace Remotely.Server.Services
mailMessage.Body = htmlMessage;
mailMessage.ReplyToList.Add(new MailAddress(replyTo));
mailClient.Send(mailMessage);
return Task.FromResult(true);
}
catch (Exception ex)
{
DataService.WriteEvent(ex);
DataService.WriteEvent(ex, organizationID);
return Task.FromResult(false);
}
return Task.CompletedTask;
}
public Task<bool> SendEmailAsync(string email, string subject, string htmlMessage, string organizationID = null)
{
return SendEmailAsync(email, AppConfig.SmtpEmail, subject, htmlMessage, organizationID);
}
}
public class EmailSender : IEmailSender
{
public EmailSender(IEmailSenderEx emailSenderEx)
{
EmailSenderEx = emailSenderEx;
}
private IEmailSenderEx EmailSenderEx { get; }
public Task SendEmailAsync(string email, string subject, string htmlMessage)
{
return SendEmailAsync(email, AppConfig.SmtpEmail, subject, htmlMessage);
return EmailSenderEx.SendEmailAsync(email, subject, htmlMessage, string.Empty);
}
}
}

View File

@ -166,7 +166,8 @@ namespace Remotely.Server
});
services.AddLogging();
services.AddScoped<IEmailSender, EmailSender>();;
services.AddScoped<IEmailSenderEx, EmailSenderEx>();
services.AddScoped<IEmailSender, EmailSender>();
services.AddScoped<DataService>();
services.AddScoped<RemoteControlSessionRecorder>();
services.AddSingleton<ApplicationConfig>();

View File

@ -57,7 +57,7 @@ namespace Remotely.Tests
services.AddTransient<DataService>();
services.AddTransient<ApplicationConfig>();
services.AddTransient<IEmailSender, EmailSender>();
services.AddTransient<IEmailSenderEx, EmailSenderEx>();
IoCActivator.ServiceProvider = services.BuildServiceProvider();
}

View File

@ -78,7 +78,7 @@ namespace Remotely.Tests
{
var dataService = IoCActivator.ServiceProvider.GetRequiredService<DataService>();
var userManager = IoCActivator.ServiceProvider.GetRequiredService<UserManager<RemotelyUser>>();
var emailSender = IoCActivator.ServiceProvider.GetRequiredService<IEmailSender>();
var emailSender = IoCActivator.ServiceProvider.GetRequiredService<IEmailSenderEx>();
var organizationModel = new OrganizationModel(dataService, userManager, emailSender);