Private Email Service Support (#1028)
* Added ServerSettingKey's for SMTP and moved email service code to Kavita. Nothing integrated in the UI yet. * Undo all the custom SMTP stuff and prepare for custom email service url. * Foundation for email service to use a custom url is setup. * Implemented the ability to hook up custom email url
This commit is contained in:
parent
2517ee75b2
commit
2ae9f8c203
21 changed files with 193 additions and 54 deletions
|
@ -101,6 +101,9 @@
|
|||
<Compile Remove="logs\**" />
|
||||
<Compile Remove="temp\**" />
|
||||
<Compile Remove="covers\**" />
|
||||
<Compile Remove="DTOs\Email\SmtpConfig.cs" />
|
||||
<Compile Remove="DTOs\Email\EmailOptionsDto.cs" />
|
||||
<Compile Remove="Helpers\Converters\SmtpConverter.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
|
@ -17,8 +15,6 @@ using API.Errors;
|
|||
using API.Extensions;
|
||||
using API.Services;
|
||||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using Flurl.Util;
|
||||
using Kavita.Common;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
@ -113,14 +109,6 @@ namespace API.Controllers
|
|||
ApiKey = HashUtil.ApiKey()
|
||||
};
|
||||
|
||||
// I am removing Authentication disabled code
|
||||
// var settings = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync();
|
||||
// if (!settings.EnableAuthentication && !registerDto.IsAdmin)
|
||||
// {
|
||||
// _logger.LogInformation("User {UserName} is being registered as non-admin with no server authentication. Using default password", registerDto.Username);
|
||||
// registerDto.Password = AccountService.DefaultPassword;
|
||||
// }
|
||||
|
||||
var result = await _userManager.CreateAsync(user, registerDto.Password);
|
||||
if (!result.Succeeded) return BadRequest(result.Errors);
|
||||
|
||||
|
@ -132,22 +120,6 @@ namespace API.Controllers
|
|||
var roleResult = await _userManager.AddToRoleAsync(user, PolicyConstants.AdminRole);
|
||||
if (!roleResult.Succeeded) return BadRequest(result.Errors);
|
||||
|
||||
// // When we register an admin, we need to grant them access to all Libraries.
|
||||
// if (registerDto.IsAdmin)
|
||||
// {
|
||||
// _logger.LogInformation("{UserName} is being registered as admin. Granting access to all libraries",
|
||||
// user.UserName);
|
||||
// var libraries = (await _unitOfWork.LibraryRepository.GetLibrariesAsync()).ToList();
|
||||
// foreach (var lib in libraries)
|
||||
// {
|
||||
// lib.AppUsers ??= new List<AppUser>();
|
||||
// lib.AppUsers.Add(user);
|
||||
// }
|
||||
//
|
||||
// if (libraries.Any() && !await _unitOfWork.CommitAsync())
|
||||
// _logger.LogError("There was an issue granting library access. Please do this manually");
|
||||
// }
|
||||
|
||||
return new UserDto
|
||||
{
|
||||
Username = user.UserName,
|
||||
|
|
|
@ -4,14 +4,17 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.DTOs.Email;
|
||||
using API.DTOs.Settings;
|
||||
using API.Entities.Enums;
|
||||
using API.Extensions;
|
||||
using API.Helpers.Converters;
|
||||
using API.Services;
|
||||
using AutoMapper;
|
||||
using Flurl.Http;
|
||||
using Kavita.Common;
|
||||
using Kavita.Common.Extensions;
|
||||
using Kavita.Common.Helpers;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
@ -25,15 +28,17 @@ namespace API.Controllers
|
|||
private readonly ITaskScheduler _taskScheduler;
|
||||
private readonly IDirectoryService _directoryService;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IEmailService _emailService;
|
||||
|
||||
public SettingsController(ILogger<SettingsController> logger, IUnitOfWork unitOfWork, ITaskScheduler taskScheduler,
|
||||
IDirectoryService directoryService, IMapper mapper)
|
||||
IDirectoryService directoryService, IMapper mapper, IEmailService emailService)
|
||||
{
|
||||
_logger = logger;
|
||||
_unitOfWork = unitOfWork;
|
||||
_taskScheduler = taskScheduler;
|
||||
_directoryService = directoryService;
|
||||
_mapper = mapper;
|
||||
_emailService = emailService;
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
|
@ -64,6 +69,36 @@ namespace API.Controllers
|
|||
return await UpdateSettings(_mapper.Map<ServerSettingDto>(Seed.DefaultSettings));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the email service url
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
[HttpPost("reset-email-url")]
|
||||
public async Task<ActionResult<ServerSettingDto>> ResetEmailServiceUrlSettings()
|
||||
{
|
||||
_logger.LogInformation("{UserName} is resetting Email Service Url Setting", User.GetUsername());
|
||||
var emailSetting = await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.EmailServiceUrl);
|
||||
emailSetting.Value = EmailService.DefaultApiUrl;
|
||||
_unitOfWork.SettingsRepository.Update(emailSetting);
|
||||
|
||||
if (!await _unitOfWork.CommitAsync())
|
||||
{
|
||||
await _unitOfWork.RollbackAsync();
|
||||
}
|
||||
|
||||
return Ok(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync());
|
||||
}
|
||||
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
[HttpPost("test-email-url")]
|
||||
public async Task<ActionResult<bool>> TestEmailServiceUrl(TestEmailDto dto)
|
||||
{
|
||||
return Ok(await _emailService.TestConnectivity(dto.Url));
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<ServerSettingDto>> UpdateSettings(ServerSettingDto updateSettingsDto)
|
||||
|
@ -173,6 +208,15 @@ namespace API.Controllers
|
|||
await _taskScheduler.ScheduleStatsTasks();
|
||||
}
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.EmailServiceUrl && updateSettingsDto.EmailServiceUrl + string.Empty != setting.Value)
|
||||
{
|
||||
setting.Value = string.IsNullOrEmpty(updateSettingsDto.EmailServiceUrl) ? EmailService.DefaultApiUrl : updateSettingsDto.EmailServiceUrl;
|
||||
FlurlHttp.ConfigureClient(setting.Value, cli =>
|
||||
cli.Settings.HttpClientFactory = new UntrustedCertClientFactory());
|
||||
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
}
|
||||
|
||||
if (!_unitOfWork.HasChanges()) return Ok(updateSettingsDto);
|
||||
|
|
6
API/DTOs/Email/TestEmailDto.cs
Normal file
6
API/DTOs/Email/TestEmailDto.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace API.DTOs.Email;
|
||||
|
||||
public class TestEmailDto
|
||||
{
|
||||
public string Url { get; set; }
|
||||
}
|
|
@ -32,5 +32,10 @@ namespace API.DTOs.Settings
|
|||
/// </summary>
|
||||
/// <remarks>If null or empty string, will default back to default install setting aka <see cref="DirectoryService.BookmarkDirectory"/></remarks>
|
||||
public string BookmarksDirectory { get; set; }
|
||||
/// <summary>
|
||||
/// Email service to use for the invite user flow, forgot password, etc.
|
||||
/// </summary>
|
||||
/// <remarks>If null or empty string, will default back to default install setting aka <see cref="EmailService.DefaultApiUrl"/></remarks>
|
||||
public string EmailServiceUrl { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ namespace API.Data
|
|||
new () {Key = ServerSettingKey.InstallId, Value = HashUtil.AnonymousToken()},
|
||||
new () {Key = ServerSettingKey.InstallVersion, Value = BuildInfo.Version.ToString()},
|
||||
new () {Key = ServerSettingKey.BookmarkDirectory, Value = directoryService.BookmarkDirectory},
|
||||
new () {Key = ServerSettingKey.EmailServiceUrl, Value = EmailService.DefaultApiUrl},
|
||||
};
|
||||
|
||||
foreach (var defaultSetting in DefaultSettings)
|
||||
|
|
|
@ -71,6 +71,10 @@ namespace API.Entities.Enums
|
|||
/// </summary>
|
||||
[Description("BookmarkDirectory")]
|
||||
BookmarkDirectory = 12,
|
||||
|
||||
/// <summary>
|
||||
/// If SMTP is enabled on the server
|
||||
/// </summary>
|
||||
[Description("CustomEmailService")]
|
||||
EmailServiceUrl = 13,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,6 @@ namespace API.Extensions
|
|||
services.AddScoped<IAccountService, AccountService>();
|
||||
services.AddScoped<IEmailService, EmailService>();
|
||||
|
||||
|
||||
services.AddScoped<IFileSystem, FileSystem>();
|
||||
services.AddScoped<IFileService, FileService>();
|
||||
services.AddScoped<ICacheHelper, CacheHelper>();
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
using System.Linq;
|
||||
using API.DTOs;
|
||||
using API.DTOs.CollectionTags;
|
||||
using API.DTOs.Email;
|
||||
using API.DTOs.Metadata;
|
||||
using API.DTOs.Reader;
|
||||
using API.DTOs.ReadingLists;
|
||||
|
@ -148,7 +149,6 @@ namespace API.Helpers
|
|||
|
||||
CreateMap<IEnumerable<ServerSetting>, ServerSettingDto>()
|
||||
.ConvertUsing<ServerSettingConverter>();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,6 +42,9 @@ namespace API.Helpers.Converters
|
|||
case ServerSettingKey.BookmarkDirectory:
|
||||
destination.BookmarksDirectory = row.Value;
|
||||
break;
|
||||
case ServerSettingKey.EmailServiceUrl:
|
||||
destination.EmailServiceUrl = row.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.DTOs.Email;
|
||||
using API.Services.Tasks;
|
||||
using API.Entities.Enums;
|
||||
using Flurl.Http;
|
||||
using Kavita.Common.EnvironmentInfo;
|
||||
using Kavita.Common.Helpers;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
@ -16,25 +17,39 @@ public interface IEmailService
|
|||
Task<bool> CheckIfAccessible(string host);
|
||||
Task SendMigrationEmail(EmailMigrationDto data);
|
||||
Task SendPasswordResetEmail(PasswordResetEmailDto data);
|
||||
Task<bool> TestConnectivity(string emailUrl);
|
||||
}
|
||||
|
||||
public class EmailService : IEmailService
|
||||
{
|
||||
private readonly ILogger<EmailService> _logger;
|
||||
private const string ApiUrl = "https://email.kavitareader.com";
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
|
||||
public EmailService(ILogger<EmailService> logger)
|
||||
/// <summary>
|
||||
/// This is used to initially set or reset the ServerSettingKey. Do not access from the code, access via UnitOfWork
|
||||
/// </summary>
|
||||
public const string DefaultApiUrl = "https://email.kavitareader.com";
|
||||
|
||||
public EmailService(ILogger<EmailService> logger, IUnitOfWork unitOfWork)
|
||||
{
|
||||
_logger = logger;
|
||||
_unitOfWork = unitOfWork;
|
||||
|
||||
FlurlHttp.ConfigureClient(ApiUrl, cli =>
|
||||
FlurlHttp.ConfigureClient(DefaultApiUrl, cli =>
|
||||
cli.Settings.HttpClientFactory = new UntrustedCertClientFactory());
|
||||
}
|
||||
|
||||
public async Task<bool> TestConnectivity(string emailUrl)
|
||||
{
|
||||
FlurlHttp.ConfigureClient(emailUrl, cli =>
|
||||
cli.Settings.HttpClientFactory = new UntrustedCertClientFactory());
|
||||
|
||||
return await SendEmailWithGet(emailUrl + "/api/email/test");
|
||||
}
|
||||
|
||||
public async Task SendConfirmationEmail(ConfirmationEmailDto data)
|
||||
{
|
||||
|
||||
var success = await SendEmailWithPost(ApiUrl + "/api/email/confirm", data);
|
||||
var success = await SendEmailWithPost(DefaultApiUrl + "/api/email/confirm", data);
|
||||
if (!success)
|
||||
{
|
||||
_logger.LogError("There was a critical error sending Confirmation email");
|
||||
|
@ -43,17 +58,20 @@ public class EmailService : IEmailService
|
|||
|
||||
public async Task<bool> CheckIfAccessible(string host)
|
||||
{
|
||||
return await SendEmailWithGet(ApiUrl + "/api/email/reachable?host=" + host);
|
||||
// This is the only exception for using the default because we need an external service to check if the server is accessible for emails
|
||||
return await SendEmailWithGet(DefaultApiUrl + "/api/email/reachable?host=" + host);
|
||||
}
|
||||
|
||||
public async Task SendMigrationEmail(EmailMigrationDto data)
|
||||
{
|
||||
await SendEmailWithPost(ApiUrl + "/api/email/email-migration", data);
|
||||
var emailLink = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.EmailServiceUrl)).Value;
|
||||
await SendEmailWithPost(emailLink + "/api/email/email-migration", data);
|
||||
}
|
||||
|
||||
public async Task SendPasswordResetEmail(PasswordResetEmailDto data)
|
||||
{
|
||||
await SendEmailWithPost(ApiUrl + "/api/email/email-password-reset", data);
|
||||
var emailLink = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.EmailServiceUrl)).Value;
|
||||
await SendEmailWithPost(emailLink + "/api/email/email-password-reset", data);
|
||||
}
|
||||
|
||||
private static async Task<bool> SendEmailWithGet(string url)
|
||||
|
@ -106,4 +124,5 @@ public class EmailService : IEmailService
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ using API.DTOs.Stats;
|
|||
using API.Entities.Enums;
|
||||
using Flurl.Http;
|
||||
using Kavita.Common.EnvironmentInfo;
|
||||
using Kavita.Common.Helpers;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using API.DTOs.Update;
|
||||
using API.SignalR;
|
||||
using API.SignalR.Presence;
|
||||
using Flurl.Http;
|
||||
using Flurl.Http.Configuration;
|
||||
using Kavita.Common.EnvironmentInfo;
|
||||
using Kavita.Common.Helpers;
|
||||
using MarkdownDeep;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
@ -44,15 +43,6 @@ internal class GithubReleaseMetadata
|
|||
public string Published_At { get; init; }
|
||||
}
|
||||
|
||||
public class UntrustedCertClientFactory : DefaultHttpClientFactory
|
||||
{
|
||||
public override HttpMessageHandler CreateMessageHandler() {
|
||||
return new HttpClientHandler {
|
||||
ServerCertificateCustomValidationCallback = (_, _, _, _) => true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public interface IVersionUpdaterService
|
||||
{
|
||||
Task<UpdateNotificationDto> CheckForUpdate();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue