diff --git a/ProjectLighthouse.Servers.Website/Middlewares/UserRequiredRedirectMiddleware.cs b/ProjectLighthouse.Servers.Website/Middlewares/UserRequiredRedirectMiddleware.cs index 0f8150cd..28b279dd 100644 --- a/ProjectLighthouse.Servers.Website/Middlewares/UserRequiredRedirectMiddleware.cs +++ b/ProjectLighthouse.Servers.Website/Middlewares/UserRequiredRedirectMiddleware.cs @@ -58,15 +58,20 @@ public class UserRequiredRedirectMiddleware : MiddlewareDBContext return; } - if (!user.EmailAddressVerified && ServerConfiguration.Instance.Mail.MailEnabled) + if (user.EmailAddress == null && ServerConfiguration.Instance.Mail.MailEnabled) { - // The normal flow is for users to set their email during login so just force them to log out - if (user.EmailAddress == null) + if (!pathContains(ctx, "/login/setEmail")) { - ctx.Response.Redirect("/logout"); + ctx.Response.Redirect("/login/setEmail"); return; } + + await this.next(ctx); + return; + } + if (!user.EmailAddressVerified && ServerConfiguration.Instance.Mail.MailEnabled) + { if (!pathContains(ctx, "/login/sendVerificationEmail", "/verifyEmail")) { ctx.Response.Redirect("/login/sendVerificationEmail"); diff --git a/ProjectLighthouse.Servers.Website/Pages/Email/SendVerificationEmailPage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/Email/SendVerificationEmailPage.cshtml.cs index cd3db846..b6924b23 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Email/SendVerificationEmailPage.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/Email/SendVerificationEmailPage.cshtml.cs @@ -88,7 +88,7 @@ public class SendVerificationEmailPage : BaseLayout $"To verify your account, click the following link: {ServerConfiguration.Instance.ExternalUrl}/verifyEmail?token={verifyToken.EmailToken}\n\n\n" + "If this wasn't you, feel free to ignore this email."; - this.Success = SMTPHelper.SendEmail(user.EmailAddress, "Project Lighthouse Email Verification", body); + this.Success = await SMTPHelper.SendEmailAsync(user.EmailAddress, "Project Lighthouse Email Verification", body); // Don't send another email for 30 seconds recentlySentEmail.TryAdd(user.UserId, TimeHelper.TimestampMillis + 30 * 1000); diff --git a/ProjectLighthouse.Servers.Website/Pages/Email/SetEmailForm.cshtml b/ProjectLighthouse.Servers.Website/Pages/Email/SetEmailForm.cshtml index 09137830..4d962981 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Email/SetEmailForm.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/Email/SetEmailForm.cshtml @@ -31,8 +31,6 @@ - - } diff --git a/ProjectLighthouse.Servers.Website/Pages/Email/SetEmailForm.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/Email/SetEmailForm.cshtml.cs index 197be21e..a54d84fc 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Email/SetEmailForm.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/Email/SetEmailForm.cshtml.cs @@ -3,10 +3,8 @@ using System.Diagnostics.CodeAnalysis; using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Localization.StringLists; -using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData.Profiles; -using LBPUnion.ProjectLighthouse.PlayerData.Profiles.Email; using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -18,75 +16,41 @@ public class SetEmailForm : BaseLayout public SetEmailForm(Database database) : base(database) {} - public EmailSetToken? EmailToken; - public string? Error { get; private set; } - public async Task OnGet(string? token = null) + public IActionResult OnGet() { if (!ServerConfiguration.Instance.Mail.MailEnabled) return this.NotFound(); + WebToken? token = this.Database.WebTokenFromRequest(this.Request); if (token == null) return this.Redirect("/login"); - EmailSetToken? emailToken = await this.Database.EmailSetTokens.FirstOrDefaultAsync(t => t.EmailToken == token); - if (emailToken == null) return this.Redirect("/login"); - - this.EmailToken = emailToken; - return this.Page(); } [SuppressMessage("ReSharper", "SpecifyStringComparison")] - public async Task OnPost(string emailAddress, string token) + public async Task OnPost(string emailAddress) { if (!ServerConfiguration.Instance.Mail.MailEnabled) return this.NotFound(); - EmailSetToken? emailToken = await this.Database.EmailSetTokens.Include(t => t.User).FirstOrDefaultAsync(t => t.EmailToken == token); - if (emailToken == null) return this.Redirect("/login"); + WebToken? token = this.Database.WebTokenFromRequest(this.Request); + if (token == null) return this.Redirect("~/login"); + + User? user = await this.Database.Users.FirstOrDefaultAsync(u => u.UserId == token.UserId); + if (user == null) return this.Redirect("~/login"); + + if (!SanitizationHelper.IsValidEmail(emailAddress)) + { + this.Error = this.Translate(ErrorStrings.EmailInvalid); + return this.Page(); + } if (await this.Database.Users.AnyAsync(u => u.EmailAddress != null && u.EmailAddress.ToLower() == emailAddress.ToLower())) { this.Error = this.Translate(ErrorStrings.EmailTaken); - this.EmailToken = emailToken; return this.Page(); } - emailToken.User.EmailAddress = emailAddress; - this.Database.EmailSetTokens.Remove(emailToken); - - User user = emailToken.User; - - EmailVerificationToken emailVerifyToken = new() - { - UserId = user.UserId, - User = user, - EmailToken = CryptoHelper.GenerateAuthToken(), - ExpiresAt = DateTime.Now + TimeSpan.FromHours(6), - }; - - this.Database.EmailVerificationTokens.Add(emailVerifyToken); - - // The user just set their email address. Now, let's grant them a token to proceed with verifying the email. - // TODO: insecure - WebToken webToken = new() - { - UserId = user.UserId, - UserToken = CryptoHelper.GenerateAuthToken(), - ExpiresAt = DateTime.Now + TimeSpan.FromDays(7), - }; - - this.Response.Cookies.Append - ( - "LighthouseToken", - webToken.UserToken, - new CookieOptions - { - Expires = DateTimeOffset.Now.AddDays(7), - } - ); - - Logger.Success($"User {user.Username} (id: {user.UserId}) successfully logged in on web after setting an email address", LogArea.Login); - - this.Database.WebTokens.Add(webToken); + user.EmailAddress = emailAddress; await this.Database.SaveChangesAsync(); return this.Redirect("/login/sendVerificationEmail"); diff --git a/ProjectLighthouse.Servers.Website/Pages/Login/LoginForm.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/Login/LoginForm.cshtml.cs index 92528a19..d8fddd54 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Login/LoginForm.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/Login/LoginForm.cshtml.cs @@ -8,7 +8,6 @@ using LBPUnion.ProjectLighthouse.Localization.StringLists; using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData.Profiles; -using LBPUnion.ProjectLighthouse.PlayerData.Profiles.Email; using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -85,24 +84,6 @@ public class LoginForm : BaseLayout return this.Page(); } - if (user.EmailAddress == null && ServerConfiguration.Instance.Mail.MailEnabled) - { - Logger.Warn($"User {user.Username} (id: {user.UserId}) failed to login; email not set", LogArea.Login); - - EmailSetToken emailSetToken = new() - { - UserId = user.UserId, - User = user, - EmailToken = CryptoHelper.GenerateAuthToken(), - ExpiresAt = DateTime.Now + TimeSpan.FromHours(6), - }; - - this.Database.EmailSetTokens.Add(emailSetToken); - await this.Database.SaveChangesAsync(); - - return this.Redirect("/login/setEmail?token=" + emailSetToken.EmailToken); - } - WebToken webToken = new() { UserId = user.UserId, @@ -126,9 +107,6 @@ public class LoginForm : BaseLayout Logger.Success($"User {user.Username} (id: {user.UserId}) successfully logged in on web", LogArea.Login); - if (user.PasswordResetRequired) return this.Redirect("~/passwordResetRequired"); - if (ServerConfiguration.Instance.Mail.MailEnabled && !user.EmailAddressVerified) return this.Redirect("~/login/sendVerificationEmail"); - if (!webToken.Verified) { return string.IsNullOrWhiteSpace(redirect) @@ -136,17 +114,20 @@ public class LoginForm : BaseLayout : this.Redirect("~/2fa" + "?redirect=" + HttpUtility.UrlEncode(redirect)); } + if (user.PasswordResetRequired) return this.Redirect("~/passwordResetRequired"); - if (string.IsNullOrWhiteSpace(redirect)) return this.Redirect("~/"); - - return this.Redirect(redirect); + return ServerConfiguration.Instance.Mail.MailEnabled switch + { + true when string.IsNullOrWhiteSpace(user.EmailAddress) => this.Redirect("~/login/setEmail"), + true when user.EmailAddressVerified => this.Redirect("~/login/sendVerificationEmail"), + _ => string.IsNullOrWhiteSpace(redirect) ? this.Redirect("~/") : this.Redirect(redirect), + }; } [UsedImplicitly] public IActionResult OnGet() { - if (this.Database.UserFromWebRequest(this.Request) != null) - return this.Redirect("~/"); + if (this.Database.UserFromWebRequest(this.Request) != null) return this.Redirect("~/"); return this.Page(); } diff --git a/ProjectLighthouse.Servers.Website/Pages/Login/LogoutPage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/Login/LogoutPage.cshtml.cs index 6c6118da..1657b71d 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Login/LogoutPage.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/Login/LogoutPage.cshtml.cs @@ -12,7 +12,7 @@ public class LogoutPage : BaseLayout public async Task OnGet() { WebToken? token = this.Database.WebTokenFromRequest(this.Request); - if (token == null) return this.BadRequest(); + if (token == null) return this.Redirect("~/"); this.Database.WebTokens.Remove(token); await this.Database.SaveChangesAsync(); diff --git a/ProjectLighthouse/Database.cs b/ProjectLighthouse/Database.cs index 282877bb..05037847 100644 --- a/ProjectLighthouse/Database.cs +++ b/ProjectLighthouse/Database.cs @@ -91,13 +91,12 @@ public class Database : DbContext await this.SaveChangesAsync(); - if (emailAddress != null && ServerConfiguration.Instance.Mail.MailEnabled) - { - string body = "An account for Project Lighthouse has been registered with this email address.\n\n" + - $"You can login at {ServerConfiguration.Instance.ExternalUrl}."; + if (!ServerConfiguration.Instance.Mail.MailEnabled || emailAddress == null) return user; - SMTPHelper.SendEmail(emailAddress, "Project Lighthouse Account Created: " + username, body); - } + string body = "An account for Project Lighthouse has been registered with this email address.\n\n" + + $"You can login at {ServerConfiguration.Instance.ExternalUrl}."; + + SMTPHelper.SendEmail(emailAddress, "Project Lighthouse Account Created: " + username, body); return user; } diff --git a/ProjectLighthouse/Helpers/SMTPHelper.cs b/ProjectLighthouse/Helpers/SMTPHelper.cs index e5a6a034..defd9db4 100644 --- a/ProjectLighthouse/Helpers/SMTPHelper.cs +++ b/ProjectLighthouse/Helpers/SMTPHelper.cs @@ -67,11 +67,17 @@ public class SMTPHelper Instance.emailThread.Dispose(); } - public static bool SendEmail(string recipientAddress, string subject, string body) + public static void SendEmail(string recipientAddress, string subject, string body) { TaskCompletionSource resultTask = new(); Instance.SendEmail(recipientAddress, subject, body, resultTask); - return resultTask.Task.Result; + } + + public static Task SendEmailAsync(string recipientAddress, string subject, string body) + { + TaskCompletionSource resultTask = new(); + Instance.SendEmail(recipientAddress, subject, body, resultTask); + return resultTask.Task; } public void SendEmail(string recipientAddress, string subject, string body, TaskCompletionSource resultTask)