From 270615be90abd0feae4a9d3c6c12a4ca7ccb1262 Mon Sep 17 00:00:00 2001 From: Uncled1023 Date: Sun, 14 Aug 2022 17:32:48 -0700 Subject: [PATCH] Added email and customer info to admin view --- BillingService/Logger.cs | 35 ++++ BillingService/Program.cs | 14 +- MailService/EmptyMailService.cs | 66 ++++++++ MailService/HMailService.cs | 101 ++++++++---- MailService/IMailService.cs | 18 ++- MailService/MailService.csproj | 1 + ServiceWorker/Program.cs | 7 +- .../V1/Controllers/BillingAPIv1Controller.cs | 9 +- .../Admin/Controllers/AdminController.cs | 45 ++++-- .../Admin/ViewModels/UserInfoViewModel.cs | 3 + .../Areas/Admin/Views/Admin/UserInfo.cshtml | 31 ++++ .../Areas/Admin/Views/Admin/UserSearch.cshtml | 2 +- Teknik/Areas/Billing/BillingHelper.cs | 17 +- .../Contact/Controllers/ContactController.cs | 17 +- .../Areas/Contact/Views/Contact/Index.cshtml | 21 +-- .../Areas/User/Controllers/UserController.cs | 31 ++-- Teknik/Areas/User/Utility/UserHelper.cs | 150 ++++++++++-------- .../Areas/User/Views/User/ViewProfile.cshtml | 2 +- Teknik/Scripts/Admin/UserInfo.js | 35 ++++ Teknik/Startup.cs | 4 + 20 files changed, 436 insertions(+), 173 deletions(-) create mode 100644 BillingService/Logger.cs create mode 100644 MailService/EmptyMailService.cs diff --git a/BillingService/Logger.cs b/BillingService/Logger.cs new file mode 100644 index 0000000..1b6de2f --- /dev/null +++ b/BillingService/Logger.cs @@ -0,0 +1,35 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Teknik.BillingService +{ + internal class Logger : ILogger + { + private readonly TextWriter _textWriter; + + public Logger(TextWriter textWriter) + { + _textWriter = textWriter; + } + + public IDisposable BeginScope(TState state) + { + return null; + } + + public bool IsEnabled(LogLevel logLevel) + { + return true; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + _textWriter.WriteLine($"[{logLevel}] {formatter(state, exception)}"); + } + } +} diff --git a/BillingService/Program.cs b/BillingService/Program.cs index 076da8f..9aa1da9 100644 --- a/BillingService/Program.cs +++ b/BillingService/Program.cs @@ -6,6 +6,7 @@ using System.Reflection; using System.Threading.Tasks; using CommandLine; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; using Teknik.Areas.Billing; using Teknik.Areas.Users.Models; using Teknik.Areas.Users.Utility; @@ -13,6 +14,7 @@ using Teknik.BillingCore; using Teknik.BillingCore.Models; using Teknik.Configuration; using Teknik.Data; +using Teknik.MailService; using Teknik.Utilities; namespace Teknik.BillingService @@ -25,6 +27,7 @@ namespace Teknik.BillingService private static readonly object dbLock = new object(); private static readonly object scanStatsLock = new object(); + private static readonly ILogger logger = new Logger(Console.Out); public static event Action OutputEvent; @@ -78,6 +81,9 @@ namespace Teknik.BillingService // Get Biling Service var billingService = BillingFactory.GetBillingService(config.BillingConfig); + // Get Mail Service + var mailService = UserHelper.CreateMailService(config, logger); + // Get all customers var customers = billingService.GetCustomers(); if (customers != null) @@ -114,7 +120,7 @@ namespace Teknik.BillingService var emailPrice = subscriptions.SelectMany(s => s.Prices).FirstOrDefault(p => p.ProductId == config.BillingConfig.EmailProductId); if (emailPrice != null) { - BillingHelper.SetEmailLimits(config, user, emailPrice.Storage, true); + BillingHelper.SetEmailLimits(config, mailService, user, emailPrice.Storage, true); } else { @@ -122,9 +128,9 @@ namespace Teknik.BillingService var userInfo = await IdentityHelper.GetIdentityUserInfo(config, user.Username); if (userInfo != null && userInfo.AccountType == AccountType.Premium) - BillingHelper.SetEmailLimits(config, user, config.EmailConfig.MaxSize, true); + BillingHelper.SetEmailLimits(config, mailService, user, config.EmailConfig.MaxSize, true); else - BillingHelper.SetEmailLimits(config, user, config.EmailConfig.MaxSize, false); + BillingHelper.SetEmailLimits(config, mailService, user, config.EmailConfig.MaxSize, false); } } } @@ -132,7 +138,7 @@ namespace Teknik.BillingService public static void Output(string message) { - Console.WriteLine(message); + logger.Log(LogLevel.Information, message); if (OutputEvent != null) { OutputEvent(message); diff --git a/MailService/EmptyMailService.cs b/MailService/EmptyMailService.cs new file mode 100644 index 0000000..d7b71b6 --- /dev/null +++ b/MailService/EmptyMailService.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Teknik.MailService +{ + public class EmptyMailService : IMailService + { + public bool AccountExists(string username) + { + throw new NotImplementedException(); + } + + public bool CreateAccount(string username, string password, long size) + { + throw new NotImplementedException(); + } + + public bool DeleteAccount(string username) + { + throw new NotImplementedException(); + } + + public bool DisableAccount(string username) + { + throw new NotImplementedException(); + } + + public bool EditMaxEmailsPerDay(string username, int maxPerDay) + { + throw new NotImplementedException(); + } + + public bool EditMaxSize(string username, long size) + { + throw new NotImplementedException(); + } + + public bool EditPassword(string username, string password) + { + throw new NotImplementedException(); + } + + public bool EnableAccount(string username) + { + throw new NotImplementedException(); + } + + public long GetMaxSize(string username) + { + throw new NotImplementedException(); + } + + public bool IsEnabled(string username) + { + throw new NotImplementedException(); + } + + public DateTime LastActive(string username) + { + throw new NotImplementedException(); + } + } +} diff --git a/MailService/HMailService.cs b/MailService/HMailService.cs index fc2b521..bbad7be 100644 --- a/MailService/HMailService.cs +++ b/MailService/HMailService.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.Extensions.Logging; +using System; using System.Collections.Generic; using System.Text; @@ -7,6 +8,7 @@ namespace Teknik.MailService public class HMailService : IMailService { private readonly hMailServer.Application _App; + private readonly ILogger _logger; private string _Host { get; set; } private string _Username { get; set; } @@ -19,7 +21,16 @@ namespace Teknik.MailService private string _CounterPassword { get; set; } private int _CounterPort { get; set; } - public HMailService(string host, string username, string password, string domain, string counterServer, string counterDatabase, string counterUsername, string counterPassword, int counterPort) + public HMailService(string host, + string username, + string password, + string domain, + string counterServer, + string counterDatabase, + string counterUsername, + string counterPassword, + int counterPort, + ILogger logger) { _Host = host; _Username = username; @@ -32,10 +43,11 @@ namespace Teknik.MailService _CounterPassword = counterPassword; _CounterPort = counterPort; + _logger = logger; _App = InitApp(); } - public void CreateAccount(string username, string password, long size) + public bool CreateAccount(string username, string password, long size) { var domain = _App.Domains.ItemByName[_Domain]; var newAccount = domain.Accounts.Add(); @@ -45,73 +57,92 @@ namespace Teknik.MailService newAccount.MaxSize = (int)(size / 1000000); newAccount.Save(); + return true; } public bool AccountExists(string username) { - try - { - GetAccount(username); - // We didn't error out, so the email exists - return true; - } - catch { } - return false; + var account = GetAccount(username); + return account != null; } - public void DeleteAccount(string username) + public bool DeleteAccount(string username) { var account = GetAccount(username); if (account != null) { account.Delete(); + return true; } + return false; } - public void EnableAccount(string username) + public bool EnableAccount(string username) { - EditActivity(username, true); + return EditActivity(username, true); } - public void DisableAccount(string username) + public bool DisableAccount(string username) { - EditActivity(username, false); + return EditActivity(username, false); } - public void EditActivity(string username, bool active) + public bool EditActivity(string username, bool active) { var account = GetAccount(username); - account.Active = active; - account.Save(); + if (account != null) + { + account.Active = active; + account.Save(); + return true; + } + return false; } - public void EditMaxEmailsPerDay(string username, int maxPerDay) + public bool EditMaxEmailsPerDay(string username, int maxPerDay) { //We need to check the actual git database MysqlDatabase mySQL = new MysqlDatabase(_CounterServer, _CounterDatabase, _CounterUsername, _CounterPassword, _CounterPort); string sql = @"INSERT INTO mailcounter.counts (qname, lastdate, qlimit, count) VALUES ({1}, NOW(), {0}, 0) ON DUPLICATE KEY UPDATE qlimit = {0}"; mySQL.Execute(sql, new object[] { maxPerDay, username }); + return true; } - public void EditMaxSize(string username, long size) + public long GetMaxSize(string username) { var account = GetAccount(username); - account.MaxSize = (int)(size / 1000000); - account.Save(); + return account?.MaxSize ?? 0; } - public void EditPassword(string username, string password) + public bool EditMaxSize(string username, long size) { var account = GetAccount(username); - account.Password = password; - account.Save(); + if (account != null) + { + account.MaxSize = (int)(size / 1000000); + account.Save(); + return true; + } + return false; + } + + public bool EditPassword(string username, string password) + { + var account = GetAccount(username); + if (account != null) + { + account.Password = password; + account.Save(); + return true; + } + return false; } public DateTime LastActive(string username) { var account = GetAccount(username); - return (DateTime)account.LastLogonTime; + return (DateTime)(account?.LastLogonTime ?? DateTime.MinValue); } private hMailServer.Application InitApp() @@ -125,14 +156,22 @@ namespace Teknik.MailService private hMailServer.Account GetAccount(string username) { - var domain = _App.Domains.ItemByName[_Domain]; - return domain.Accounts.ItemByAddress[username]; + try + { + var domain = _App.Domains.ItemByName[_Domain]; + return domain.Accounts.ItemByAddress[username]; + } + catch (Exception ex) + { + _logger.LogError(ex, "Exception occurred getting Email Account"); + } + return null; } - public bool Enabled(string username) + public bool IsEnabled(string username) { var account = GetAccount(username); - return account.Active; + return account?.Active ?? false; } } } diff --git a/MailService/IMailService.cs b/MailService/IMailService.cs index f285c26..3b0310c 100644 --- a/MailService/IMailService.cs +++ b/MailService/IMailService.cs @@ -8,20 +8,22 @@ namespace Teknik.MailService DateTime LastActive(string username); - bool Enabled(string username); + bool IsEnabled(string username); - void CreateAccount(string username, string password, long size); + bool CreateAccount(string username, string password, long size); - void EditPassword(string username, string password); + bool EditPassword(string username, string password); - void EditMaxSize(string username, long size); + long GetMaxSize(string username); - void EditMaxEmailsPerDay(string username, int maxPerDay); + bool EditMaxSize(string username, long size); - void EnableAccount(string username); + bool EditMaxEmailsPerDay(string username, int maxPerDay); - void DisableAccount(string username); + bool EnableAccount(string username); - void DeleteAccount(string username); + bool DisableAccount(string username); + + bool DeleteAccount(string username); } } diff --git a/MailService/MailService.csproj b/MailService/MailService.csproj index 15037a7..2454f65 100644 --- a/MailService/MailService.csproj +++ b/MailService/MailService.csproj @@ -8,6 +8,7 @@ + diff --git a/ServiceWorker/Program.cs b/ServiceWorker/Program.cs index c2c715e..922e0d2 100644 --- a/ServiceWorker/Program.cs +++ b/ServiceWorker/Program.cs @@ -2,6 +2,7 @@ using Microsoft.EntityFrameworkCore; using nClam; using System; +using System.Buffers; using System.Collections.Generic; using System.IO; using System.Linq; @@ -159,8 +160,8 @@ namespace Teknik.ServiceWorker // If the IV is set, and Key is set, then scan it if (!string.IsNullOrEmpty(upload.Key) && !string.IsNullOrEmpty(upload.IV)) { - byte[] keyBytes = Encoding.UTF8.GetBytes(upload.Key); - byte[] ivBytes = Encoding.UTF8.GetBytes(upload.IV); + var keyArray = new PooledArray(Encoding.UTF8.GetBytes(upload.Key)); + var ivArray = new PooledArray(Encoding.UTF8.GetBytes(upload.IV)); long maxUploadSize = config.UploadConfig.MaxUploadFileSize; @@ -171,7 +172,7 @@ namespace Teknik.ServiceWorker maxUploadSize = upload.User.UploadSettings.MaxUploadFileSize.Value; } - using (AesCounterStream aesStream = new AesCounterStream(fileStream, false, keyBytes, ivBytes)) + using (AesCounterStream aesStream = new AesCounterStream(fileStream, false, keyArray, ivArray)) { ClamClient clam = new ClamClient(config.UploadConfig.ClamConfig.Server, config.UploadConfig.ClamConfig.Port); clam.MaxStreamSize = maxUploadSize; diff --git a/Teknik/Areas/API/V1/Controllers/BillingAPIv1Controller.cs b/Teknik/Areas/API/V1/Controllers/BillingAPIv1Controller.cs index c326f3f..2d8dd87 100644 --- a/Teknik/Areas/API/V1/Controllers/BillingAPIv1Controller.cs +++ b/Teknik/Areas/API/V1/Controllers/BillingAPIv1Controller.cs @@ -14,6 +14,7 @@ using Teknik.BillingCore.Models; using Teknik.Configuration; using Teknik.Data; using Teknik.Logging; +using Teknik.MailService; namespace Teknik.Areas.API.V1.Controllers { @@ -21,7 +22,7 @@ namespace Teknik.Areas.API.V1.Controllers { public BillingAPIv1Controller(ILogger logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { } - public async Task HandleCheckoutCompleteEvent() + public async Task HandleCheckoutCompleteEvent([FromServices] IMailService mailService) { if (!_config.BillingConfig.Enabled) return Forbid(); @@ -38,13 +39,13 @@ namespace Teknik.Areas.API.V1.Controllers { var subscription = billingService.GetSubscription(session.SubscriptionId); - BillingHelper.ProcessSubscription(_config, _dbContext, session.CustomerId, subscription); + BillingHelper.ProcessSubscription(_config, _dbContext, mailService, session.CustomerId, subscription); } return Ok(); } - public async Task HandleSubscriptionChange() + public async Task HandleSubscriptionChange([FromServices] IMailService mailService) { if (!_config.BillingConfig.Enabled) return Forbid(); @@ -60,7 +61,7 @@ namespace Teknik.Areas.API.V1.Controllers if (subscriptionEvent == null) return BadRequest(); - BillingHelper.ProcessSubscription(_config, _dbContext, subscriptionEvent.CustomerId, subscriptionEvent); + BillingHelper.ProcessSubscription(_config, _dbContext, mailService, subscriptionEvent.CustomerId, subscriptionEvent); return Ok(); } diff --git a/Teknik/Areas/Admin/Controllers/AdminController.cs b/Teknik/Areas/Admin/Controllers/AdminController.cs index b87835d..619f04e 100644 --- a/Teknik/Areas/Admin/Controllers/AdminController.cs +++ b/Teknik/Areas/Admin/Controllers/AdminController.cs @@ -18,6 +18,7 @@ using Teknik.ViewModels; using Teknik.Logging; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; +using Teknik.MailService; namespace Teknik.Areas.Admin.Controllers { @@ -45,7 +46,7 @@ namespace Teknik.Areas.Admin.Controllers [HttpGet] [TrackPageView] - public async Task UserInfo(string username) + public async Task UserInfo(string username, [FromServices] IMailService mailService) { if (UserHelper.UserExists(_dbContext, username)) { @@ -59,6 +60,13 @@ namespace Teknik.Areas.Admin.Controllers model.AccountType = info.AccountType.Value; if (info.AccountStatus.HasValue) model.AccountStatus = info.AccountStatus.Value; + + var email = UserHelper.GetUserEmailAddress(_config, username); + if (UserHelper.UserEmailExists(mailService, _config, email)) + model.Email = email; + model.EmailEnabled = UserHelper.UserEmailEnabled(mailService, _config, email); + model.MaxEmailStorage = UserHelper.GetUserEmailMaxSize(mailService, _config, email); + return View(model); } return new StatusCodeResult(StatusCodes.Status404NotFound); @@ -89,11 +97,12 @@ namespace Teknik.Areas.Admin.Controllers } [HttpPost] - public async Task GetUserSearchResults(string query, [FromServices] ICompositeViewEngine viewEngine) + public async Task GetUserSearchResults(string query, [FromServices] ICompositeViewEngine viewEngine, [FromServices] IMailService mailService) { List models = new List(); - var results = _dbContext.Users.Where(u => u.Username.Contains(query)).ToList(); + var results = _dbContext.Users.Where(u => u.Username.Contains(query) || + (u.BillingCustomer != null && u.BillingCustomer.CustomerId == query)).ToList(); if (results != null) { foreach (User user in results) @@ -110,7 +119,7 @@ namespace Teknik.Areas.Admin.Controllers if (info.CreationDate.HasValue) model.JoinDate = info.CreationDate.Value; - model.LastSeen = await UserHelper.GetLastAccountActivity(_dbContext, _config, user.Username); + model.LastSeen = await UserHelper.GetLastAccountActivity(_dbContext, _config, mailService, user.Username); models.Add(model); } catch (Exception) @@ -192,12 +201,12 @@ namespace Teknik.Areas.Admin.Controllers [HttpPost] [ValidateAntiForgeryToken] - public async Task EditUserAccountType(string username, AccountType accountType) + public async Task EditUserAccountType(string username, AccountType accountType, [FromServices] IMailService mailService) { if (UserHelper.UserExists(_dbContext, username)) { // Edit the user's account type - await UserHelper.EditAccountType(_dbContext, _config, username, accountType); + await UserHelper.EditAccountType(_dbContext, _config, mailService, username, accountType); return Json(new { result = new { success = true } }); } return new StatusCodeResult(StatusCodes.Status404NotFound); @@ -205,12 +214,28 @@ namespace Teknik.Areas.Admin.Controllers [HttpPost] [ValidateAntiForgeryToken] - public async Task EditUserAccountStatus(string username, AccountStatus accountStatus) + public async Task EditUserAccountStatus(string username, AccountStatus accountStatus, [FromServices] IMailService mailService) { if (UserHelper.UserExists(_dbContext, username)) { // Edit the user's account type - await UserHelper.EditAccountStatus(_dbContext, _config, username, accountStatus); + await UserHelper.EditAccountStatus(_dbContext, _config, mailService, username, accountStatus); + return Json(new { result = new { success = true } }); + } + return new StatusCodeResult(StatusCodes.Status404NotFound); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public IActionResult EditEmailActive(string username, string active, [FromServices] IMailService mailService) + { + if (UserHelper.UserExists(_dbContext, username)) + { + var email = UserHelper.GetUserEmailAddress(_config, username); + if (active == "Enabled") + UserHelper.EnableUserEmail(mailService, _config, email); + else + UserHelper.DisableUserEmail(mailService, _config, email); return Json(new { result = new { success = true } }); } return new StatusCodeResult(StatusCodes.Status404NotFound); @@ -241,14 +266,14 @@ namespace Teknik.Areas.Admin.Controllers [HttpPost] [ValidateAntiForgeryToken] - public async Task DeleteAccount(string username) + public async Task DeleteAccount(string username, [FromServices] IMailService mailService) { try { User user = UserHelper.GetUser(_dbContext, username); if (user != null) { - await UserHelper.DeleteAccount(_dbContext, _config, user); + await UserHelper.DeleteAccount(_dbContext, _config, mailService, user); return Json(new { result = true }); } } diff --git a/Teknik/Areas/Admin/ViewModels/UserInfoViewModel.cs b/Teknik/Areas/Admin/ViewModels/UserInfoViewModel.cs index 9c3901e..6c4b0fd 100644 --- a/Teknik/Areas/Admin/ViewModels/UserInfoViewModel.cs +++ b/Teknik/Areas/Admin/ViewModels/UserInfoViewModel.cs @@ -12,5 +12,8 @@ namespace Teknik.Areas.Admin.ViewModels public string Username { get; set; } public AccountType AccountType { get; set; } public AccountStatus AccountStatus { get; set; } + public string Email { get; set; } + public bool EmailEnabled { get; set; } + public long MaxEmailStorage { get; set; } } } diff --git a/Teknik/Areas/Admin/Views/Admin/UserInfo.cshtml b/Teknik/Areas/Admin/Views/Admin/UserInfo.cshtml index 5429faf..382736f 100644 --- a/Teknik/Areas/Admin/Views/Admin/UserInfo.cshtml +++ b/Teknik/Areas/Admin/Views/Admin/UserInfo.cshtml @@ -6,6 +6,7 @@ var deleteUserURL = '@Url.SubRouteUrl("admin", "Admin.Action", new { action = "DeleteAccount" })'; var editAccountType = '@Url.SubRouteUrl("admin", "Admin.Action", new { action = "EditUserAccountType" })'; var editAccountStatus = '@Url.SubRouteUrl("admin", "Admin.Action", new { action = "EditUserAccountStatus" })'; + var editEmailActive = '@Url.SubRouteUrl("admin", "Admin.Action", new { action = "EditEmailActive" })'; var createInviteCode = '@Url.SubRouteUrl("admin", "Admin.Action", new { action = "CreateInviteCode" })'; var username = '@Model.Username'; @@ -52,6 +53,36 @@
+
+
+ Email: +
+
+ @Model.Email +
+
+
+
+
+ Email Active: +
+
+ +
+
+
+
+
+ Email Max Size: +
+
+ +
+
+
diff --git a/Teknik/Areas/Admin/Views/Admin/UserSearch.cshtml b/Teknik/Areas/Admin/Views/Admin/UserSearch.cshtml index ab79a24..8015680 100644 --- a/Teknik/Areas/Admin/Views/Admin/UserSearch.cshtml +++ b/Teknik/Areas/Admin/Views/Admin/UserSearch.cshtml @@ -10,7 +10,7 @@
- +
diff --git a/Teknik/Areas/Billing/BillingHelper.cs b/Teknik/Areas/Billing/BillingHelper.cs index be251ac..fd300d2 100644 --- a/Teknik/Areas/Billing/BillingHelper.cs +++ b/Teknik/Areas/Billing/BillingHelper.cs @@ -6,6 +6,7 @@ using Teknik.BillingCore; using Teknik.BillingCore.Models; using Teknik.Configuration; using Teknik.Data; +using Teknik.MailService; namespace Teknik.Areas.Billing { @@ -41,7 +42,7 @@ namespace Teknik.Areas.Billing } } - public static void ProcessSubscription(Config config, TeknikEntities db, string customerId, Subscription subscription) + public static void ProcessSubscription(Config config, TeknikEntities db, IMailService mailService, string customerId, Subscription subscription) { // They have paid, so let's get their subscription and update their user settings var user = db.Users.FirstOrDefault(u => u.BillingCustomer != null && @@ -51,12 +52,12 @@ namespace Teknik.Areas.Billing var isActive = subscription.Status == SubscriptionStatus.Active; foreach (var price in subscription.Prices) { - ProcessPrice(config, db, user, price, isActive); + ProcessPrice(config, db, mailService, user, price, isActive); } } } - public static void ProcessPrice(Config config, TeknikEntities db, User user, Price price, bool active) + public static void ProcessPrice(Config config, TeknikEntities db, IMailService mailService, User user, Price price, bool active) { // What type of subscription is this if (config.BillingConfig.UploadProductId == price.ProductId) @@ -68,7 +69,7 @@ namespace Teknik.Areas.Billing } else if (config.BillingConfig.EmailProductId == price.ProductId) { - SetEmailLimits(config, user, price.Storage, active); + SetEmailLimits(config, mailService, user, price.Storage, active); } } @@ -81,18 +82,18 @@ namespace Teknik.Areas.Billing db.SaveChanges(); } - public static void SetEmailLimits(Config config, User user, long storage, bool active) + public static void SetEmailLimits(Config config, IMailService mailService, User user, long storage, bool active) { // Process an email subscription string email = UserHelper.GetUserEmailAddress(config, user.Username); if (active) { - UserHelper.EnableUserEmail(config, email); - UserHelper.EditUserEmailMaxSize(config, email, storage); + UserHelper.EnableUserEmail(mailService, config, email); + UserHelper.EditUserEmailMaxSize(mailService, config, email, storage); } else { - UserHelper.DisableUserEmail(config, email); + UserHelper.DisableUserEmail(mailService, config, email); } } } diff --git a/Teknik/Areas/Contact/Controllers/ContactController.cs b/Teknik/Areas/Contact/Controllers/ContactController.cs index f1d34c2..15f55aa 100644 --- a/Teknik/Areas/Contact/Controllers/ContactController.cs +++ b/Teknik/Areas/Contact/Controllers/ContactController.cs @@ -10,6 +10,8 @@ using Teknik.Data; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Teknik.Logging; +using Teknik.Areas.Users.Utility; +using Teknik.MailService; namespace Teknik.Areas.Contact.Controllers { @@ -21,12 +23,23 @@ namespace Teknik.Areas.Contact.Controllers [AllowAnonymous] [TrackPageView] - public IActionResult Index() + public IActionResult Index([FromServices] IMailService mailService) { ViewBag.Title = "Contact Us"; ViewBag.Description = "Contact Teknik Support"; + var model = new ContactViewModel(); - return View(new ContactViewModel()); + if (User.Identity.IsAuthenticated) + { + model.Name = User.Identity.Name; + + var email = UserHelper.GetUserEmailAddress(_config, User.Identity.Name); + if (UserHelper.UserEmailEnabled(mailService, _config, email)) + { + model.Email = email; + } + } + return View(model); } [HttpPost] diff --git a/Teknik/Areas/Contact/Views/Contact/Index.cshtml b/Teknik/Areas/Contact/Views/Contact/Index.cshtml index 0e97f1b..8aba204 100644 --- a/Teknik/Areas/Contact/Views/Contact/Index.cshtml +++ b/Teknik/Areas/Contact/Views/Contact/Index.cshtml @@ -1,22 +1,5 @@ @model Teknik.Areas.Contact.ViewModels.ContactViewModel -@using Teknik.Areas.Users.Utility - -@{ - string username = string.Empty; - string email = string.Empty; - - if (User.Identity.IsAuthenticated) - { - username = User.Identity.Name; - - if (UserHelper.UserEmailEnabled(Config, UserHelper.GetUserEmailAddress(Config, username))) - { - email = UserHelper.GetUserEmailAddress(Config, username); - } - } -} -
@@ -39,7 +22,7 @@ - +
@Html.ValidationMessageFor(u => u.Email) @@ -50,7 +33,7 @@ - +
diff --git a/Teknik/Areas/User/Controllers/UserController.cs b/Teknik/Areas/User/Controllers/UserController.cs index f558049..fc20635 100644 --- a/Teknik/Areas/User/Controllers/UserController.cs +++ b/Teknik/Areas/User/Controllers/UserController.cs @@ -25,6 +25,7 @@ using Microsoft.AspNetCore.Http; using IdentityServer4.Models; using Teknik.Utilities.Routing; using Teknik.BillingCore; +using Teknik.MailService; namespace Teknik.Areas.Users.Controllers { @@ -51,11 +52,11 @@ namespace Teknik.Areas.Users.Controllers } [HttpGet] - public IActionResult Login(string returnUrl) + public IActionResult Login(string returnUrl, [FromServices] IMailService mailService) { // Let's double check their email and git accounts to make sure they exist string email = UserHelper.GetUserEmailAddress(_config, User.Identity.Name); - if (_config.EmailConfig.Enabled && !UserHelper.UserEmailExists(_config, email)) + if (_config.EmailConfig.Enabled && !UserHelper.UserEmailExists(mailService, _config, email)) { //UserHelper.AddUserEmail(_config, email, model.Password); } @@ -102,7 +103,7 @@ namespace Teknik.Areas.Users.Controllers [HttpOptions] [HttpPost] [AllowAnonymous] - public async Task Register([Bind(Prefix = "Register")]RegisterViewModel model) + public async Task Register([Bind(Prefix = "Register")]RegisterViewModel model, [FromServices] IMailService mailService) { model.Error = false; model.ErrorMessage = string.Empty; @@ -115,7 +116,7 @@ namespace Teknik.Areas.Users.Controllers model.Error = true; model.ErrorMessage = "That username is not valid"; } - if (!model.Error && !(await UserHelper.UsernameAvailable(_dbContext, _config, model.Username))) + if (!model.Error && !(await UserHelper.UsernameAvailable(_dbContext, _config, mailService, model.Username))) { model.Error = true; model.ErrorMessage = "That username is not available"; @@ -152,7 +153,7 @@ namespace Teknik.Areas.Users.Controllers { try { - await UserHelper.CreateAccount(_dbContext, _config, Url, model.Username, model.Password, model.RecoveryEmail, model.InviteCode); + await UserHelper.CreateAccount(_dbContext, _config, mailService, Url, model.Username, model.Password, model.RecoveryEmail, model.InviteCode); } catch (Exception ex) { @@ -184,7 +185,7 @@ namespace Teknik.Areas.Users.Controllers // GET: Profile/Profile [AllowAnonymous] [TrackPageView] - public async Task ViewProfile(string username) + public async Task ViewProfile(string username, [FromServices] IMailService mailService) { if (string.IsNullOrEmpty(username)) { @@ -208,13 +209,15 @@ namespace Teknik.Areas.Users.Controllers model.Username = user.Username; if (_config.EmailConfig.Enabled) { - model.Email = string.Format("{0}@{1}", user.Username, _config.EmailConfig.Domain); + var email = UserHelper.GetUserEmailAddress(_config, user.Username); + if (UserHelper.UserEmailEnabled(mailService, _config, email)) + model.Email = email; } // Get the user claims for this user model.IdentityUserInfo = await IdentityHelper.GetIdentityUserInfo(_config, user.Username); - model.LastSeen = UserHelper.GetLastAccountActivity(_dbContext, _config, user.Username, model.IdentityUserInfo); + model.LastSeen = UserHelper.GetLastAccountActivity(_dbContext, _config, mailService, user.Username, model.IdentityUserInfo); model.UserSettings = user.UserSettings; model.BlogSettings = user.BlogSettings; @@ -745,7 +748,7 @@ namespace Teknik.Areas.Users.Controllers return Json(new { error = "Invalid Parameters" }); } - public async Task ChangePassword(AccountSettingsViewModel settings) + public async Task ChangePassword(AccountSettingsViewModel settings, [FromServices] IMailService mailService) { if (ModelState.IsValid) @@ -775,7 +778,7 @@ namespace Teknik.Areas.Users.Controllers return Json(new { error = "Password resets are disabled" }); // Change their password - await UserHelper.ChangeAccountPassword(_dbContext, _config, user.Username, settings.CurrentPassword, settings.NewPassword); + await UserHelper.ChangeAccountPassword(_dbContext, _config, mailService, user.Username, settings.CurrentPassword, settings.NewPassword); return Json(new { result = true }); } @@ -791,7 +794,7 @@ namespace Teknik.Areas.Users.Controllers [HttpPost] [ValidateAntiForgeryToken] - public async Task Delete() + public async Task Delete([FromServices] IMailService mailService) { if (ModelState.IsValid) { @@ -800,7 +803,7 @@ namespace Teknik.Areas.Users.Controllers User user = UserHelper.GetUser(_dbContext, User.Identity.Name); if (user != null) { - await UserHelper.DeleteAccount(_dbContext, _config, user); + await UserHelper.DeleteAccount(_dbContext, _config, mailService, user); // Sign Out await HttpContext.SignOutAsync("Cookies"); @@ -945,7 +948,7 @@ namespace Teknik.Areas.Users.Controllers [HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] - public async Task SetUserPassword(SetPasswordViewModel passwordViewModel) + public async Task SetUserPassword(SetPasswordViewModel passwordViewModel, [FromServices] IMailService mailService) { if (ModelState.IsValid) { @@ -973,7 +976,7 @@ namespace Teknik.Areas.Users.Controllers try { - await UserHelper.ResetAccountPassword(_dbContext, _config, username, code, passwordViewModel.Password); + await UserHelper.ResetAccountPassword(_dbContext, _config, mailService, username, code, passwordViewModel.Password); _session.Remove(_AuthSessionKey); _session.Remove("AuthCode"); diff --git a/Teknik/Areas/User/Utility/UserHelper.cs b/Teknik/Areas/User/Utility/UserHelper.cs index 9915535..cc6c2fc 100644 --- a/Teknik/Areas/User/Utility/UserHelper.cs +++ b/Teknik/Areas/User/Utility/UserHelper.cs @@ -33,6 +33,7 @@ using Newtonsoft.Json.Linq; using Newtonsoft.Json; using Microsoft.AspNetCore.Mvc; using Teknik.Utilities.Routing; +using Microsoft.Extensions.Logging; namespace Teknik.Areas.Users.Utility { @@ -81,7 +82,7 @@ namespace Teknik.Areas.Users.Utility return isValid; } - public static async Task UsernameAvailable(TeknikEntities db, Config config, string username) + public static async Task UsernameAvailable(TeknikEntities db, Config config, IMailService mailService, string username) { bool isAvailable = true; @@ -89,27 +90,27 @@ namespace Teknik.Areas.Users.Utility isAvailable &= !UsernameReserved(config, username); isAvailable &= !await IdentityHelper.UserExists(config, username); isAvailable &= !UserExists(db, username); - isAvailable &= !UserEmailExists(config, GetUserEmailAddress(config, username)); + isAvailable &= !UserEmailExists(mailService, config, GetUserEmailAddress(config, username)); isAvailable &= !UserGitExists(config, username); return isAvailable; } - public static async Task GetLastAccountActivity(TeknikEntities db, Config config, string username) + public static async Task GetLastAccountActivity(TeknikEntities db, Config config, IMailService mailService, string username) { var userInfo = await IdentityHelper.GetIdentityUserInfo(config, username); - return GetLastAccountActivity(db, config, username, userInfo); + return GetLastAccountActivity(db, config, mailService, username, userInfo); } - public static DateTime GetLastAccountActivity(TeknikEntities db, Config config, string username, IdentityUserInfo userInfo) + public static DateTime GetLastAccountActivity(TeknikEntities db, Config config, IMailService mailService, string username, IdentityUserInfo userInfo) { try { DateTime lastActive = new DateTime(1900, 1, 1); - if (UserEmailExists(config, GetUserEmailAddress(config, username))) + if (UserEmailExists(mailService, config, GetUserEmailAddress(config, username))) { - DateTime emailLastActive = UserEmailLastActive(config, GetUserEmailAddress(config, username)); + DateTime emailLastActive = UserEmailLastActive(mailService, config, GetUserEmailAddress(config, username)); if (lastActive < emailLastActive) lastActive = emailLastActive; } @@ -136,7 +137,7 @@ namespace Teknik.Areas.Users.Utility } } - public static async Task CreateAccount(TeknikEntities db, Config config, IUrlHelper url, string username, string password, string recoveryEmail, string inviteCode) + public static async Task CreateAccount(TeknikEntities db, Config config, IMailService mailService, IUrlHelper url, string username, string password, string recoveryEmail, string inviteCode) { try { @@ -147,10 +148,10 @@ namespace Teknik.Areas.Users.Utility string userId = (string)result.Data; // Create an Email Account - CreateUserEmail(config, GetUserEmailAddress(config, username), password); + CreateUserEmail(mailService, config, GetUserEmailAddress(config, username), password); // Disable the email account - DisableUserEmail(config, GetUserEmailAddress(config, username)); + DisableUserEmail(mailService, config, GetUserEmailAddress(config, username)); // Create a Git Account CreateUserGit(config, username, password, userId); @@ -189,12 +190,12 @@ namespace Teknik.Areas.Users.Utility } } - public static async Task ChangeAccountPassword(TeknikEntities db, Config config, string username, string currentPassword, string newPassword) + public static async Task ChangeAccountPassword(TeknikEntities db, Config config, IMailService mailService, string username, string currentPassword, string newPassword) { IdentityResult result = await IdentityHelper.UpdatePassword(config, username, currentPassword, newPassword); if (result.Success) { - ChangeServicePasswords(db, config, username, newPassword); + ChangeServicePasswords(db, config, mailService, username, newPassword); } else { @@ -202,12 +203,12 @@ namespace Teknik.Areas.Users.Utility } } - public static async Task ResetAccountPassword(TeknikEntities db, Config config, string username, string token, string newPassword) + public static async Task ResetAccountPassword(TeknikEntities db, Config config, IMailService mailService, string username, string token, string newPassword) { IdentityResult result = await IdentityHelper.ResetPassword(config, username, token, newPassword); if (result.Success) { - ChangeServicePasswords(db, config, username, newPassword); + ChangeServicePasswords(db, config, mailService, username, newPassword); } else { @@ -215,16 +216,16 @@ namespace Teknik.Areas.Users.Utility } } - public static void ChangeServicePasswords(TeknikEntities db, Config config, string username, string newPassword) + public static void ChangeServicePasswords(TeknikEntities db, Config config, IMailService mailService, string username, string newPassword) { try { // Make sure they have a git and email account before resetting their password string email = GetUserEmailAddress(config, username); - if (config.EmailConfig.Enabled && UserEmailExists(config, email)) + if (config.EmailConfig.Enabled && UserEmailExists(mailService, config, email)) { // Change email password - EditUserEmailPassword(config, GetUserEmailAddress(config, username), newPassword); + EditUserEmailPassword(mailService, config, GetUserEmailAddress(config, username), newPassword); } if (config.GitConfig.Enabled && UserGitExists(config, username)) @@ -239,7 +240,7 @@ namespace Teknik.Areas.Users.Utility } } - public static async Task EditAccountType(TeknikEntities db, Config config, string username, AccountType type) + public static async Task EditAccountType(TeknikEntities db, Config config, IMailService mailService, string username, AccountType type) { try { @@ -257,11 +258,11 @@ namespace Teknik.Areas.Users.Utility { case AccountType.Basic: // Disable their email - DisableUserEmail(config, email); + DisableUserEmail(mailService, config, email); break; case AccountType.Premium: // Enable their email account - EnableUserEmail(config, email); + EnableUserEmail(mailService, config, email); break; } } @@ -276,7 +277,7 @@ namespace Teknik.Areas.Users.Utility } } - public static async Task EditAccountStatus(TeknikEntities db, Config config, string username, AccountStatus status) + public static async Task EditAccountStatus(TeknikEntities db, Config config, IMailService mailService, string username, AccountStatus status) { try { @@ -294,13 +295,13 @@ namespace Teknik.Areas.Users.Utility { case AccountStatus.Active: // Enable Email - EnableUserEmail(config, email); + EnableUserEmail(mailService, config, email); // Enable Git EnableUserGit(config, username); break; case AccountStatus.Banned: // Disable Email - DisableUserEmail(config, email); + DisableUserEmail(mailService, config, email); // Disable Git DisableUserGit(config, username); break; @@ -317,7 +318,7 @@ namespace Teknik.Areas.Users.Utility } } - public static async Task DeleteAccount(TeknikEntities db, Config config, User user) + public static async Task DeleteAccount(TeknikEntities db, Config config, IMailService mailService, User user) { try { @@ -332,8 +333,8 @@ namespace Teknik.Areas.Users.Utility DeleteUser(db, config, user); // Delete Email Account - if (UserEmailExists(config, GetUserEmailAddress(config, username))) - DeleteUserEmail(config, GetUserEmailAddress(config, username)); + if (UserEmailExists(mailService, config, GetUserEmailAddress(config, username))) + DeleteUserEmail(mailService, config, GetUserEmailAddress(config, username)); // Delete Git Account if (UserGitExists(config, username)) @@ -621,58 +622,61 @@ If you recieved this email and you did not reset your password, you can ignore t #region Email Management public static string GetUserEmailAddress(Config config, string username) { + if (config.EmailConfig == null) + return null; return string.Format("{0}@{1}", username, config.EmailConfig.Domain); } - public static IMailService CreateMailService(Config config) + public static IMailService CreateMailService(Config config, ILogger logger) { - return new HMailService( - config.EmailConfig.MailHost, - config.EmailConfig.Username, - config.EmailConfig.Password, - config.EmailConfig.Domain, - config.EmailConfig.CounterDatabase.Server, - config.EmailConfig.CounterDatabase.Database, - config.EmailConfig.CounterDatabase.Username, - config.EmailConfig.CounterDatabase.Password, - config.EmailConfig.CounterDatabase.Port - ); + if (config.EmailConfig != null && + config.EmailConfig.Enabled) + return new HMailService( + config.EmailConfig.MailHost, + config.EmailConfig.Username, + config.EmailConfig.Password, + config.EmailConfig.Domain, + config.EmailConfig.CounterDatabase.Server, + config.EmailConfig.CounterDatabase.Database, + config.EmailConfig.CounterDatabase.Username, + config.EmailConfig.CounterDatabase.Password, + config.EmailConfig.CounterDatabase.Port, + logger + ); + return new EmptyMailService(); } - public static bool UserEmailExists(Config config, string email) + public static bool UserEmailExists(IMailService mailService, Config config, string email) { // If Email Server is enabled if (config.EmailConfig.Enabled) { - var svc = CreateMailService(config); - return svc.AccountExists(email); + return mailService.AccountExists(email); } return false; } - public static DateTime UserEmailLastActive(Config config, string email) + public static DateTime UserEmailLastActive(IMailService mailService, Config config, string email) { DateTime lastActive = new DateTime(1900, 1, 1); if (config.EmailConfig.Enabled) { - var svc = CreateMailService(config); - var lastEmail = svc.LastActive(email); + var lastEmail = mailService.LastActive(email); if (lastActive < lastEmail) lastActive = lastEmail; } return lastActive; } - public static bool UserEmailEnabled(Config config, string email) + public static bool UserEmailEnabled(IMailService mailService, Config config, string email) { try { // If Email Server is enabled if (config.EmailConfig.Enabled) { - var svc = CreateMailService(config); - return svc.Enabled(email); + return mailService.IsEnabled(email); } } catch (Exception ex) @@ -682,15 +686,14 @@ If you recieved this email and you did not reset your password, you can ignore t return false; } - public static void CreateUserEmail(Config config, string email, string password) + public static void CreateUserEmail(IMailService mailService, Config config, string email, string password) { try { // If Email Server is enabled if (config.EmailConfig.Enabled) { - var svc = CreateMailService(config); - svc.CreateAccount(email, password, config.EmailConfig.MaxSize); + mailService.CreateAccount(email, password, config.EmailConfig.MaxSize); } } catch (Exception ex) @@ -699,15 +702,14 @@ If you recieved this email and you did not reset your password, you can ignore t } } - public static void EnableUserEmail(Config config, string email) + public static void EnableUserEmail(IMailService mailService, Config config, string email) { try { // If Email Server is enabled if (config.EmailConfig.Enabled) { - var svc = CreateMailService(config); - svc.EnableAccount(email); + mailService.EnableAccount(email); } } catch (Exception ex) @@ -716,15 +718,14 @@ If you recieved this email and you did not reset your password, you can ignore t } } - public static void DisableUserEmail(Config config, string email) + public static void DisableUserEmail(IMailService mailService, Config config, string email) { try { // If Email Server is enabled if (config.EmailConfig.Enabled) { - var svc = CreateMailService(config); - svc.DisableAccount(email); + mailService.DisableAccount(email); } } catch (Exception ex) @@ -733,15 +734,14 @@ If you recieved this email and you did not reset your password, you can ignore t } } - public static void EditUserEmailPassword(Config config, string email, string password) + public static void EditUserEmailPassword(IMailService mailService, Config config, string email, string password) { try { // If Email Server is enabled if (config.EmailConfig.Enabled) { - var svc = CreateMailService(config); - svc.EditPassword(email, password); + mailService.EditPassword(email, password); } } catch (Exception ex) @@ -750,15 +750,14 @@ If you recieved this email and you did not reset your password, you can ignore t } } - public static void EditUserEmailMaxSize(Config config, string email, long size) + public static void EditUserEmailMaxSize(IMailService mailService, Config config, string email, long size) { try { // If Email Server is enabled if (config.EmailConfig.Enabled) { - var svc = CreateMailService(config); - svc.EditMaxSize(email, size); + mailService.EditMaxSize(email, size); } } catch (Exception ex) @@ -767,15 +766,31 @@ If you recieved this email and you did not reset your password, you can ignore t } } - public static void EditUserEmailMaxEmailsPerDay(Config config, string email, int maxPerDay) + public static long GetUserEmailMaxSize(IMailService mailService, Config config, string email) { try { // If Email Server is enabled if (config.EmailConfig.Enabled) { - var svc = CreateMailService(config); - svc.EditMaxEmailsPerDay(email, maxPerDay); + return mailService.GetMaxSize(email); + } + } + catch (Exception ex) + { + throw new Exception("Unable to edit email account mailbox size.", ex); + } + return -1; + } + + public static void EditUserEmailMaxEmailsPerDay(IMailService mailService, Config config, string email, int maxPerDay) + { + try + { + // If Email Server is enabled + if (config.EmailConfig.Enabled) + { + mailService.EditMaxEmailsPerDay(email, maxPerDay); } } catch (Exception ex) @@ -784,15 +799,14 @@ If you recieved this email and you did not reset your password, you can ignore t } } - public static void DeleteUserEmail(Config config, string email) + public static void DeleteUserEmail(IMailService mailService, Config config, string email) { try { // If Email Server is enabled if (config.EmailConfig.Enabled) { - var svc = CreateMailService(config); - svc.DeleteAccount(email); + mailService.DeleteAccount(email); } } catch (Exception ex) diff --git a/Teknik/Areas/User/Views/User/ViewProfile.cshtml b/Teknik/Areas/User/Views/User/ViewProfile.cshtml index e0e99a4..f0a266d 100644 --- a/Teknik/Areas/User/Views/User/ViewProfile.cshtml +++ b/Teknik/Areas/User/Views/User/ViewProfile.cshtml @@ -96,7 +96,7 @@ {
  • Public Key @pgpFingerprint64.AddStringAtInterval(4, " ")
  • } - @if (!string.IsNullOrEmpty(Model.Email) && Config.EmailConfig.Enabled && UserHelper.UserEmailEnabled(Config, Model.Email)) + @if (!string.IsNullOrEmpty(Model.Email)) {
  • Email @(Html.Raw(User.Identity.IsAuthenticated ? $"{Model.Email}" : $"{Model.Username} at {Config.EmailConfig?.Domain}"))
  • } diff --git a/Teknik/Scripts/Admin/UserInfo.js b/Teknik/Scripts/Admin/UserInfo.js index a4c4fb7..5057eac 100644 --- a/Teknik/Scripts/Admin/UserInfo.js +++ b/Teknik/Scripts/Admin/UserInfo.js @@ -20,6 +20,10 @@ $(function () { $("#top_msg").html('
    ' + parseErrorMessage(html) + '
    '); } } + else { + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
    No HTML Response.
    '); + } } }); }); @@ -43,6 +47,37 @@ $(function () { $("#top_msg").html('
    ' + parseErrorMessage(html) + '
    '); } } + else { + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
    No HTML Response.
    '); + } + } + }); + }); + + $('.userEmailActive').on('change', function () { + var selected = $(this).find("option:selected").val(); + + $.ajax({ + type: "POST", + url: editEmailActive, + data: AddAntiForgeryToken({ username: username, active: selected }), + success: function (html) { + if (html) { + if (html.result.success) { + $("#top_msg").css('display', 'none'); + $("#top_msg").html(''); + alert('Successfully changed the email active status for \'' + username + '\' to: ' + selected); + } + else { + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
    ' + parseErrorMessage(html) + '
    '); + } + } + else { + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
    No HTML Response.
    '); + } } }); }); diff --git a/Teknik/Startup.cs b/Teknik/Startup.cs index c7eb0fc..19f58a5 100644 --- a/Teknik/Startup.cs +++ b/Teknik/Startup.cs @@ -30,6 +30,8 @@ using Teknik.WebCommon.Middleware; using Teknik.WebCommon; using Teknik.Areas.Error.Controllers; using Teknik.Services; +using Teknik.MailService; +using Teknik.Areas.Users.Utility; namespace Teknik { @@ -67,6 +69,7 @@ namespace Teknik // Resolve the services from the service provider var config = sp.GetService(); + var logger = sp.GetService>(); var devEnv = config?.DevEnvironment ?? true; var defaultConn = config?.DbConnection ?? string.Empty; var authority = config?.UserConfig?.IdentityServerConfig?.Authority ?? string.Empty; @@ -102,6 +105,7 @@ namespace Teknik services.AddHostedService(); services.AddSingleton(); services.AddSingleton(c => new ObjectCache(300)); + services.AddSingleton(s => UserHelper.CreateMailService(config, logger)); services.AddHostedService(); services.AddScoped();