Browse Source

Added email and customer info to admin view

master
Teknikode 1 month ago
parent
commit
270615be90
  1. 35
      BillingService/Logger.cs
  2. 14
      BillingService/Program.cs
  3. 66
      MailService/EmptyMailService.cs
  4. 101
      MailService/HMailService.cs
  5. 18
      MailService/IMailService.cs
  6. 1
      MailService/MailService.csproj
  7. 7
      ServiceWorker/Program.cs
  8. 9
      Teknik/Areas/API/V1/Controllers/BillingAPIv1Controller.cs
  9. 45
      Teknik/Areas/Admin/Controllers/AdminController.cs
  10. 3
      Teknik/Areas/Admin/ViewModels/UserInfoViewModel.cs
  11. 31
      Teknik/Areas/Admin/Views/Admin/UserInfo.cshtml
  12. 2
      Teknik/Areas/Admin/Views/Admin/UserSearch.cshtml
  13. 17
      Teknik/Areas/Billing/BillingHelper.cs
  14. 17
      Teknik/Areas/Contact/Controllers/ContactController.cs
  15. 21
      Teknik/Areas/Contact/Views/Contact/Index.cshtml
  16. 31
      Teknik/Areas/User/Controllers/UserController.cs
  17. 150
      Teknik/Areas/User/Utility/UserHelper.cs
  18. 2
      Teknik/Areas/User/Views/User/ViewProfile.cshtml
  19. 35
      Teknik/Scripts/Admin/UserInfo.js
  20. 4
      Teknik/Startup.cs

35
BillingService/Logger.cs

@ -0,0 +1,35 @@ @@ -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>(TState state)
{
return null;
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
_textWriter.WriteLine($"[{logLevel}] {formatter(state, exception)}");
}
}
}

14
BillingService/Program.cs

@ -6,6 +6,7 @@ using System.Reflection; @@ -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; @@ -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 @@ -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<string> OutputEvent;
@ -78,6 +81,9 @@ namespace Teknik.BillingService @@ -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 @@ -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 @@ -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 @@ -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);

66
MailService/EmptyMailService.cs

@ -0,0 +1,66 @@ @@ -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();
}
}
}

101
MailService/HMailService.cs

@ -1,4 +1,5 @@ @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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;
}
}
}

18
MailService/IMailService.cs

@ -8,20 +8,22 @@ namespace Teknik.MailService @@ -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);
}
}

1
MailService/MailService.csproj

@ -8,6 +8,7 @@ @@ -8,6 +8,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
<PackageReference Include="MySql.Data" Version="8.0.29" />
</ItemGroup>

7
ServiceWorker/Program.cs

@ -2,6 +2,7 @@ @@ -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 @@ -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 @@ -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;

9
Teknik/Areas/API/V1/Controllers/BillingAPIv1Controller.cs

@ -14,6 +14,7 @@ using Teknik.BillingCore.Models; @@ -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 @@ -21,7 +22,7 @@ namespace Teknik.Areas.API.V1.Controllers
{
public BillingAPIv1Controller(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { }
public async Task<IActionResult> HandleCheckoutCompleteEvent()
public async Task<IActionResult> HandleCheckoutCompleteEvent([FromServices] IMailService mailService)
{
if (!_config.BillingConfig.Enabled)
return Forbid();
@ -38,13 +39,13 @@ namespace Teknik.Areas.API.V1.Controllers @@ -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<IActionResult> HandleSubscriptionChange()
public async Task<IActionResult> HandleSubscriptionChange([FromServices] IMailService mailService)
{
if (!_config.BillingConfig.Enabled)
return Forbid();
@ -60,7 +61,7 @@ namespace Teknik.Areas.API.V1.Controllers @@ -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();
}

45
Teknik/Areas/Admin/Controllers/AdminController.cs

@ -18,6 +18,7 @@ using Teknik.ViewModels; @@ -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 @@ -45,7 +46,7 @@ namespace Teknik.Areas.Admin.Controllers
[HttpGet]
[TrackPageView]
public async Task<IActionResult> UserInfo(string username)
public async Task<IActionResult> UserInfo(string username, [FromServices] IMailService mailService)
{
if (UserHelper.UserExists(_dbContext, username))
{
@ -59,6 +60,13 @@ namespace Teknik.Areas.Admin.Controllers @@ -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 @@ -89,11 +97,12 @@ namespace Teknik.Areas.Admin.Controllers
}
[HttpPost]
public async Task<IActionResult> GetUserSearchResults(string query, [FromServices] ICompositeViewEngine viewEngine)
public async Task<IActionResult> GetUserSearchResults(string query, [FromServices] ICompositeViewEngine viewEngine, [FromServices] IMailService mailService)
{
List<UserResultViewModel> models = new List<UserResultViewModel>();
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 @@ -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 @@ -192,12 +201,12 @@ namespace Teknik.Areas.Admin.Controllers
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditUserAccountType(string username, AccountType accountType)
public async Task<IActionResult> 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 @@ -205,12 +214,28 @@ namespace Teknik.Areas.Admin.Controllers
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditUserAccountStatus(string username, AccountStatus accountStatus)
public async Task<IActionResult> 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 @@ -241,14 +266,14 @@ namespace Teknik.Areas.Admin.Controllers
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteAccount(string username)
public async Task<IActionResult> 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 });
}
}

3
Teknik/Areas/Admin/ViewModels/UserInfoViewModel.cs

@ -12,5 +12,8 @@ namespace Teknik.Areas.Admin.ViewModels @@ -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; }
}
}

31
Teknik/Areas/Admin/Views/Admin/UserInfo.cshtml

@ -6,6 +6,7 @@ @@ -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';
</script>
@ -52,6 +53,36 @@ @@ -52,6 +53,36 @@
</div>
</div>
<br />
<div class="row">
<div class="col-sm-2 col-sm-offset-1">
Email:
</div>
<div class="col-sm-8">
@Model.Email
</div>
</div>
<br />
<div class="row">
<div class="col-sm-2 col-sm-offset-1">
Email Active:
</div>
<div class="col-sm-8">
<select class="userEmailActive">
<!option @(Model.EmailEnabled ? "selected" : string.Empty)>Enabled</!option>
<!option @(!Model.EmailEnabled ? "selected" : string.Empty)>Disabled</!option>
</select>
</div>
</div>
<br />
<div class="row">
<div class="col-sm-2 col-sm-offset-1">
Email Max Size:
</div>
<div class="col-sm-8">
<input class="emailMaxSize" value="@Model.MaxEmailStorage" />
</div>
</div>
<br />
<div class="row">
<div class="col-sm-2 col-sm-offset-1">
<button type="button" class="btn btn-info" id="createInviteCode">Create Invite Code</button>

2
Teknik/Areas/Admin/Views/Admin/UserSearch.cshtml

@ -10,7 +10,7 @@ @@ -10,7 +10,7 @@
<div class="col-sm-6 col-sm-offset-3">
<!form>
<div class="form-group center-block">
<input type="text" class="form-control" id="query" name="query" placeholder="Username" />
<input type="text" class="form-control" id="query" name="query" placeholder="Username/Customer ID" />
</div>
</!form>
</div>

17
Teknik/Areas/Billing/BillingHelper.cs

@ -6,6 +6,7 @@ using Teknik.BillingCore; @@ -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 @@ -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 @@ -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 @@ -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 @@ -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);
}
}
}

17
Teknik/Areas/Contact/Controllers/ContactController.cs

@ -10,6 +10,8 @@ using Teknik.Data; @@ -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 @@ -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]

21
Teknik/Areas/Contact/Views/Contact/Index.cshtml

@ -1,22 +1,5 @@ @@ -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);
}
}
}
<div class="container">
<div class="row">
<div class="col-sm-12 col-lg-12">
@ -39,7 +22,7 @@ @@ -39,7 +22,7 @@
<label for="Name">
Name
</label>
<input type="text" class="form-control" id="Name" name="Name" placeholder="Enter name" required="required" value="@username" />
<input type="text" class="form-control" id="Name" name="Name" placeholder="Enter name" required="required" value="@Model.Name" />
</div>
<div class="form-group">
@Html.ValidationMessageFor(u => u.Email)
@ -50,7 +33,7 @@ @@ -50,7 +33,7 @@
<span class="input-group-addon">
<span class="fa fa-envelope"></span>
</span>
<input type="email" class="form-control" id="Email" name="Email" placeholder="Enter email" required="required" value="@email" />
<input type="email" class="form-control" id="Email" name="Email" placeholder="Enter email" required="required" value="@Model.Email" />
</div>
</div>
<div class="form-group">

31
Teknik/Areas/User/Controllers/UserController.cs

@ -25,6 +25,7 @@ using Microsoft.AspNetCore.Http; @@ -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 @@ -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 @@ -102,7 +103,7 @@ namespace Teknik.Areas.Users.Controllers
[HttpOptions]
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Register([Bind(Prefix = "Register")]RegisterViewModel model)
public async Task<IActionResult> 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 @@ -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 @@ -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 @@ -184,7 +185,7 @@ namespace Teknik.Areas.Users.Controllers
// GET: Profile/Profile
[AllowAnonymous]
[TrackPageView]
public async Task<IActionResult> ViewProfile(string username)
public async Task<IActionResult> ViewProfile(string username, [FromServices] IMailService mailService)
{
if (string.IsNullOrEmpty(username))
{
@ -208,13 +209,15 @@ namespace Teknik.Areas.Users.Controllers @@ -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 @@ -745,7 +748,7 @@ namespace Teknik.Areas.Users.Controllers
return Json(new { error = "Invalid Parameters" });
}
public async Task<IActionResult> ChangePassword(AccountSettingsViewModel settings)
public async Task<IActionResult> ChangePassword(AccountSettingsViewModel settings, [FromServices] IMailService mailService)
{
if (ModelState.IsValid)
@ -775,7 +778,7 @@ namespace Teknik.Areas.Users.Controllers @@ -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 @@ -791,7 +794,7 @@ namespace Teknik.Areas.Users.Controllers
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Delete()
public async Task<IActionResult> Delete([FromServices] IMailService mailService)
{
if (ModelState.IsValid)
{
@ -800,7 +803,7 @@ namespace Teknik.Areas.Users.Controllers @@ -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 @@ -945,7 +948,7 @@ namespace Teknik.Areas.Users.Controllers
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SetUserPassword(SetPasswordViewModel passwordViewModel)
public async Task<IActionResult> SetUserPassword(SetPasswordViewModel passwordViewModel, [FromServices] IMailService mailService)
{
if (ModelState.IsValid)
{
@ -973,7 +976,7 @@ namespace Teknik.Areas.Users.Controllers @@ -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");

150
Teknik/Areas/User/Utility/UserHelper.cs

@ -33,6 +33,7 @@ using Newtonsoft.Json.Linq; @@ -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 @@ -81,7 +82,7 @@ namespace Teknik.Areas.Users.Utility
return isValid;
}
public static async Task<bool> UsernameAvailable(TeknikEntities db, Config config, string username)
public static async Task<bool> UsernameAvailable(TeknikEntities db, Config config, IMailService mailService, string username)
{
bool isAvailable = true;
@ -89,27 +90,27 @@ namespace Teknik.Areas.Users.Utility @@ -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<DateTime> GetLastAccountActivity(TeknikEntities db, Config config, string username)
public static async Task<DateTime> 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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -733,15 +734,14 @@ If you recieved this email and you did not reset your password, you can ignore t
}