From 464631985fdb398f797b44a8c2a05c3fe8d25a83 Mon Sep 17 00:00:00 2001 From: Vasilis The Pikachu Date: Thu, 13 Nov 2025 16:45:39 +0100 Subject: [PATCH 1/2] Email account owner off changes in their account --- .../Account/ConfirmEmailChange.cshtml.cs | 14 ++++++++++- .../Account/Manage/ChangePassword.cshtml.cs | 10 ++++++++ .../Pages/Account/Manage/Disable2fa.cshtml.cs | 17 ++++++++++--- .../Manage/EnableAuthenticator.cshtml.cs | 20 +++++++++++---- .../Manage/GenerateRecoveryCodes.cshtml.cs | 17 ++++++++++--- .../Pages/Account/Manage/Index.cshtml.cs | 25 +++++++++++++------ .../Account/Manage/PersonalData.cshtml.cs | 10 ++++++++ .../Manage/ResetAuthenticator.cshtml.cs | 14 +++++++++-- 8 files changed, 106 insertions(+), 21 deletions(-) diff --git a/SS14.Web/Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml.cs b/SS14.Web/Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml.cs index ef361a8..20838c3 100644 --- a/SS14.Web/Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml.cs +++ b/SS14.Web/Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Text.Encodings.Web; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; @@ -9,6 +10,7 @@ using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.WebUtilities; using SS14.Auth.Shared.Data; +using SS14.Auth.Shared.Emails; namespace SS14.Web.Areas.Identity.Pages.Account; @@ -17,17 +19,20 @@ public class ConfirmEmailChangeModel : PageModel { private readonly SpaceUserManager _userManager; private readonly SignInManager _signInManager; + private readonly IEmailSender _emailSender; private readonly ApplicationDbContext _dbContext; private readonly AccountLogManager _accountLogManager; public ConfirmEmailChangeModel( SpaceUserManager userManager, SignInManager signInManager, + IEmailSender emailSender, ApplicationDbContext dbContext, AccountLogManager accountLogManager) { _userManager = userManager; _signInManager = signInManager; + _emailSender = emailSender; _dbContext = dbContext; _accountLogManager = accountLogManager; } @@ -68,6 +73,13 @@ await _accountLogManager.LogAndSave( await _signInManager.RefreshSignInAsync(user); StatusMessage = "Thank you for confirming your email change."; + + await _emailSender.SendEmailAsync( + oldEmail, + "Your Space Station 14 account email was changed", + $"This email was sent to the old email address for security, if this was you feel free to ignore this email." + + $"\n\nFurther emails from this point forward will go to {email}." + + $"\n\nIf this was not you, send an email to support@spacestation14.com immediately."); return Page(); } -} \ No newline at end of file +} diff --git a/SS14.Web/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml.cs b/SS14.Web/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml.cs index 47fa46b..5135512 100644 --- a/SS14.Web/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml.cs +++ b/SS14.Web/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Logging; using SS14.Auth.Shared.Data; +using SS14.Auth.Shared.Emails; using SS14.Auth.Shared.Sessions; namespace SS14.Web.Areas.Identity.Pages.Account.Manage; @@ -18,17 +19,20 @@ public class ChangePasswordModel : PageModel private readonly SessionManager _sessionManager; private readonly AccountLogManager _logManager; private readonly SignInManager _signInManager; + private readonly IEmailSender _emailSender; private readonly ILogger _logger; public ChangePasswordModel( UserManager userManager, SignInManager signInManager, + IEmailSender emailSender, ILogger logger, SessionManager sessionManager, AccountLogManager logManager) { _userManager = userManager; _signInManager = signInManager; + _emailSender = emailSender; _logger = logger; _sessionManager = sessionManager; _logManager = logManager; @@ -105,6 +109,12 @@ public async Task OnPostAsync() _logger.LogInformation("User changed their password successfully."); StatusMessage = "Your password has been changed."; + var userEmail = await _userManager.GetEmailAsync(user); + await _emailSender.SendEmailAsync(userEmail, + "Your Space Station 14 account password was changed", + $"This email was sent to you to confirm your password change. If this was you feel free to ignore this email." + + $"\n\nIf this was not you, send an email to support@spacestation14.com immediately."); + return RedirectToPage(); } } diff --git a/SS14.Web/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml.cs b/SS14.Web/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml.cs index 9515863..5d708bb 100644 --- a/SS14.Web/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml.cs +++ b/SS14.Web/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Logging; using SS14.Auth.Shared.Data; +using SS14.Auth.Shared.Emails; namespace SS14.Web.Areas.Identity.Pages.Account.Manage; @@ -14,6 +15,7 @@ public class Disable2faModel : PageModel { private readonly SpaceUserManager _userManager; private readonly SignInManager _signInManager; + private readonly IEmailSender _emailSender; private readonly ILogger _logger; private readonly ApplicationDbContext _dbContext; private readonly AccountLogManager _accountLogManager; @@ -21,12 +23,14 @@ public class Disable2faModel : PageModel public Disable2faModel( SpaceUserManager userManager, SignInManager signInManager, + IEmailSender emailSender, ILogger logger, ApplicationDbContext dbContext, AccountLogManager accountLogManager) { _userManager = userManager; _signInManager = signInManager; + _emailSender = emailSender; _logger = logger; _dbContext = dbContext; _accountLogManager = accountLogManager; @@ -70,11 +74,18 @@ public async Task OnPostAsync() } await tx.CommitAsync(); - + await _signInManager.RefreshSignInAsync(user); - + _logger.LogInformation("User with ID '{UserId}' has disabled 2FA.", _userManager.GetUserId(User)); StatusMessage = "2FA has been disabled. You can re-enable 2FA when you setup an authenticator app"; + + var userEmail = await _userManager.GetEmailAsync(user); + await _emailSender.SendEmailAsync(userEmail, + "Your Space Station 14 account 2fa was disabled", + $"This email was sent to you to confirm that 2fa has been disabled on your account. If this was you feel free to ignore this email." + + $"\n\nIf this was not you, send an email to support@spacestation14.com immediately."); + return RedirectToPage("./TwoFactorAuthentication"); } -} \ No newline at end of file +} diff --git a/SS14.Web/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs b/SS14.Web/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs index 40a395d..8120b87 100644 --- a/SS14.Web/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs +++ b/SS14.Web/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Logging; using SS14.Auth.Shared.Data; +using SS14.Auth.Shared.Emails; namespace SS14.Web.Areas.Identity.Pages.Account.Manage; @@ -20,6 +21,7 @@ public class EnableAuthenticatorModel : PageModel private readonly ILogger _logger; private readonly UrlEncoder _urlEncoder; private readonly SignInManager _signInManager; + private readonly IEmailSender _emailSender; private readonly ApplicationDbContext _dbContext; private readonly AccountLogManager _accountLogManager; @@ -28,6 +30,7 @@ public class EnableAuthenticatorModel : PageModel public EnableAuthenticatorModel( SpaceUserManager userManager, ILogger logger, + IEmailSender emailSender, UrlEncoder urlEncoder, SignInManager signInManager, ApplicationDbContext dbContext, @@ -37,6 +40,7 @@ public EnableAuthenticatorModel( _logger = logger; _urlEncoder = urlEncoder; _signInManager = signInManager; + _emailSender = emailSender; _dbContext = dbContext; _accountLogManager = accountLogManager; } @@ -91,7 +95,7 @@ public async Task OnPostAsync() } await using var tx = await _dbContext.Database.BeginTransactionAsync(); - + // Strip spaces and hypens var verificationCode = Input.Code.Replace(" ", string.Empty).Replace("-", string.Empty); @@ -110,15 +114,21 @@ public async Task OnPostAsync() await _userManager.SetTwoFactorEnabledAsync(user, true); var userId = await _userManager.GetUserIdAsync(user); _logger.LogInformation("User with ID '{UserId}' has enabled 2FA with an authenticator app.", userId); - + StatusMessage = "Your authenticator app has been verified."; await _signInManager.RefreshSignInAsync(user); + var userEmail = await _userManager.GetEmailAsync(user); + await _emailSender.SendEmailAsync(userEmail, + "Your Space Station 14 account 2fa was enabled", + $"This email was sent to you to confirm that 2fa has been enabled on your account. If this was you feel free to ignore this email." + + $"\n\nIf this was not you, send an email to support@spacestation14.com immediately."); + if (await _userManager.CountRecoveryCodesAsync(user) == 0) { var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); RecoveryCodes = recoveryCodes.ToArray(); - + await tx.CommitAsync(); return RedirectToPage("./ShowRecoveryCodes"); } @@ -137,7 +147,7 @@ private async Task LoadSharedKeyAndQrCodeUriAsync(SpaceUser user) { await _userManager.ResetAuthenticatorKeyAsync(user); unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user); - + await _signInManager.RefreshSignInAsync(user); } @@ -171,4 +181,4 @@ private string GenerateQrCodeUri(string userName, string unformattedKey) _urlEncoder.Encode(userName), unformattedKey); } -} \ No newline at end of file +} diff --git a/SS14.Web/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml.cs b/SS14.Web/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml.cs index b6a858e..d0fd73e 100644 --- a/SS14.Web/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml.cs +++ b/SS14.Web/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml.cs @@ -5,23 +5,27 @@ using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Logging; using SS14.Auth.Shared.Data; +using SS14.Auth.Shared.Emails; namespace SS14.Web.Areas.Identity.Pages.Account.Manage; public class GenerateRecoveryCodesModel : PageModel { private readonly SpaceUserManager _userManager; + private readonly IEmailSender _emailSender; private readonly ApplicationDbContext _dbContext; private readonly ILogger _logger; private readonly AccountLogManager _accountLogManager; public GenerateRecoveryCodesModel( SpaceUserManager userManager, + IEmailSender emailSender, ApplicationDbContext dbContext, ILogger logger, AccountLogManager accountLogManager) { _userManager = userManager; + _emailSender = emailSender; _dbContext = dbContext; _logger = logger; _accountLogManager = accountLogManager; @@ -71,14 +75,21 @@ public async Task OnPostAsync() await _accountLogManager.LogAndSave(user, new AccountLogRecoveryCodesGenerated()); await _userManager.UpdateAsync(user); - + var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); RecoveryCodes = recoveryCodes.ToArray(); await tx.CommitAsync(); - + _logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", userId); StatusMessage = "You have generated new recovery codes."; + + var userEmail = await _userManager.GetEmailAsync(user); + await _emailSender.SendEmailAsync(userEmail, + "Your Space Station 14 account 2fa recovery codes were regenerated", + $"This email was sent to you to confirm that 2fa recovery codes have been regenerated on your account. If this was you feel free to ignore this email." + + $"\n\nIf this was not you, send an email to support@spacestation14.com immediately."); + return RedirectToPage("./ShowRecoveryCodes"); } -} \ No newline at end of file +} diff --git a/SS14.Web/Areas/Identity/Pages/Account/Manage/Index.cshtml.cs b/SS14.Web/Areas/Identity/Pages/Account/Manage/Index.cshtml.cs index 79253f6..6d852e7 100644 --- a/SS14.Web/Areas/Identity/Pages/Account/Manage/Index.cshtml.cs +++ b/SS14.Web/Areas/Identity/Pages/Account/Manage/Index.cshtml.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Options; using SS14.Auth.Shared.Data; +using SS14.Auth.Shared.Emails; namespace SS14.Web.Areas.Identity.Pages.Account.Manage; @@ -12,6 +13,7 @@ public partial class IndexModel : PageModel { private readonly SpaceUserManager _userManager; private readonly SignInManager _signInManager; + private readonly IEmailSender _emailSender; private readonly IOptions _options; private readonly ApplicationDbContext _dbContext; private readonly AccountLogManager _accountLogManager; @@ -22,13 +24,15 @@ public partial class IndexModel : PageModel public IndexModel( SpaceUserManager userManager, - SignInManager signInManager, + SignInManager signInManager, + IEmailSender emailSender, IOptions options, ApplicationDbContext dbContext, AccountLogManager accountLogManager) { _userManager = userManager; _signInManager = signInManager; + _emailSender = emailSender; _options = options; _dbContext = dbContext; _accountLogManager = accountLogManager; @@ -88,13 +92,13 @@ public async Task OnPostUsernameAsync() await LoadAsync(user); return Page(); } - + Username = Username.Trim(); if (Username == user.UserName) { return RedirectToPage(); } - + UpdateCanEditUsername(user); if (!CanEditUsername) { @@ -107,7 +111,7 @@ public async Task OnPostUsernameAsync() await using var tx = await _dbContext.Database.BeginTransactionAsync(); var result = await _userManager.SetUserNameAsync(user, Username); - + if (!result.Succeeded) { foreach (var error in result.Errors) @@ -118,17 +122,24 @@ public async Task OnPostUsernameAsync() await LoadAsync(user); return Page(); } - + user.LastUsernameChange = DateTime.UtcNow; await _accountLogManager.LogNameChanged(user, oldName, user.UserName); await _signInManager.RefreshSignInAsync(user); StatusMessage = "Your username has been changed. Note that it may take some time to visibly update in some places, such as the launcher."; - + await _dbContext.SaveChangesAsync(); await tx.CommitAsync(); + var userEmail = await _userManager.GetEmailAsync(user); + await _emailSender.SendEmailAsync(userEmail, + "Your Space Station 14 account username was changed", + $"This email was sent to you to confirm your username change, you were known as {oldName} but from now on will be known as {user.UserName}. " + + $"If this was you feel free to ignore this email." + + $"\n\nIf this was not you, send an email to support@spacestation14.com immediately."); + return RedirectToPage(); } -} \ No newline at end of file +} diff --git a/SS14.Web/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml.cs b/SS14.Web/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml.cs index e643136..39d08d6 100644 --- a/SS14.Web/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml.cs +++ b/SS14.Web/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml.cs @@ -5,11 +5,13 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using SS14.Auth.Shared.Data; +using SS14.Auth.Shared.Emails; namespace SS14.Web.Areas.Identity.Pages.Account.Manage; public class PersonalDataModel( UserManager userManager, + IEmailSender emailSender, PersonalDataCollector personalDataCollector) : PageModel { @@ -34,6 +36,14 @@ public async Task OnPostDownloadPersonalDataAsync(CancellationTok var data = await personalDataCollector.CollectPersonalData(user, cancel); Response.Headers.Add("Content-Disposition", $"attachment; filename={user.UserName}-PersonalData.zip"); + + // TODO: Once net 9 is in, in order to not hit MaxEmailsPerHour. This should have a rate limit to only allow data download once per hour or 1 email per hour + // var userEmail = await userManager.GetEmailAsync(user); + // await emailSender.SendEmailAsync(userEmail, + // "Your Space Station 14 account data was requested", + // $"This email was sent to you to confirm your account data was requested. If this was you feel free to ignore this email." + + // $"\n\nIf this was not you, send an email to support@spacestation14.com immediately."); + return new FileStreamResult(data, MediaTypeNames.Application.Zip); } } diff --git a/SS14.Web/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml.cs b/SS14.Web/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml.cs index 539cc0d..7e2489d 100644 --- a/SS14.Web/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml.cs +++ b/SS14.Web/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Logging; using SS14.Auth.Shared.Data; +using SS14.Auth.Shared.Emails; namespace SS14.Web.Areas.Identity.Pages.Account.Manage; @@ -14,6 +15,7 @@ public class ResetAuthenticatorModel : PageModel { private readonly SpaceUserManager _userManager; private readonly SignInManager _signInManager; + private readonly IEmailSender _emailSender; private readonly ApplicationDbContext _dbContext; ILogger _logger; private readonly AccountLogManager _accountLogManager; @@ -21,12 +23,14 @@ public class ResetAuthenticatorModel : PageModel public ResetAuthenticatorModel( SpaceUserManager userManager, SignInManager signInManager, + IEmailSender emailSender, ApplicationDbContext dbContext, ILogger logger, AccountLogManager accountLogManager) { _userManager = userManager; _signInManager = signInManager; + _emailSender = emailSender; _dbContext = dbContext; _logger = logger; _accountLogManager = accountLogManager; @@ -63,10 +67,16 @@ public async Task OnPostAsync() await _accountLogManager.LogAndSave(user, new AccountLogAuthenticatorReset()); await tx.CommitAsync(); - + await _signInManager.RefreshSignInAsync(user); StatusMessage = "Your authenticator app key has been reset, you will need to configure your authenticator app using the new key."; + var userEmail = await _userManager.GetEmailAsync(user); + await _emailSender.SendEmailAsync(userEmail, + "Your Space Station 14 account 2fa was reset", + $"This email was sent to you to confirm that 2fa has been reset on your account. If this was you feel free to ignore this email." + + $"\n\nIf this was not you, send an email to support@spacestation14.com immediately."); + return RedirectToPage("./EnableAuthenticator"); } -} \ No newline at end of file +} From aca2f7f82ad196ffb36b181ec074ddf4b7161549 Mon Sep 17 00:00:00 2001 From: Vasilis The Pikachu Date: Thu, 13 Nov 2025 16:49:24 +0100 Subject: [PATCH 2/2] Minor fixes --- .../Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs | 1 + .../Areas/Identity/Pages/Account/Manage/PersonalData.cshtml.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/SS14.Web/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs b/SS14.Web/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs index 8120b87..73f97ce 100644 --- a/SS14.Web/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs +++ b/SS14.Web/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs @@ -122,6 +122,7 @@ public async Task OnPostAsync() await _emailSender.SendEmailAsync(userEmail, "Your Space Station 14 account 2fa was enabled", $"This email was sent to you to confirm that 2fa has been enabled on your account. If this was you feel free to ignore this email." + + $"(And make sure you wrote down your recovery codes)" + $"\n\nIf this was not you, send an email to support@spacestation14.com immediately."); if (await _userManager.CountRecoveryCodesAsync(user) == 0) diff --git a/SS14.Web/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml.cs b/SS14.Web/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml.cs index 39d08d6..772a918 100644 --- a/SS14.Web/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml.cs +++ b/SS14.Web/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml.cs @@ -38,6 +38,7 @@ public async Task OnPostDownloadPersonalDataAsync(CancellationTok Response.Headers.Add("Content-Disposition", $"attachment; filename={user.UserName}-PersonalData.zip"); // TODO: Once net 9 is in, in order to not hit MaxEmailsPerHour. This should have a rate limit to only allow data download once per hour or 1 email per hour + // Not doing it now because i dont wanna do db migrations and mess up julians pr // var userEmail = await userManager.GetEmailAsync(user); // await emailSender.SendEmailAsync(userEmail, // "Your Space Station 14 account data was requested",