diff --git a/ProjectLighthouse.Localization/General.resx b/ProjectLighthouse.Localization/General.resx index 138736e8..fc10126d 100644 --- a/ProjectLighthouse.Localization/General.resx +++ b/ProjectLighthouse.Localization/General.resx @@ -25,7 +25,7 @@ Password - Register + Create an account Forgot Password? diff --git a/ProjectLighthouse.Servers.Website/Middlewares/HandlePageErrorMiddleware.cs b/ProjectLighthouse.Servers.Website/Middlewares/HandlePageErrorMiddleware.cs deleted file mode 100644 index 6e8177bc..00000000 --- a/ProjectLighthouse.Servers.Website/Middlewares/HandlePageErrorMiddleware.cs +++ /dev/null @@ -1,26 +0,0 @@ -using LBPUnion.ProjectLighthouse.Middlewares; - -namespace LBPUnion.ProjectLighthouse.Servers.Website.Middlewares; - -public class HandlePageErrorMiddleware : Middleware -{ - public HandlePageErrorMiddleware(RequestDelegate next) : base(next) - {} - - public override async Task InvokeAsync(HttpContext ctx) - { - await this.next(ctx); - if (ctx.Response.StatusCode == 404 && !ctx.Request.Path.StartsWithSegments("/gameAssets")) - { - try - { - ctx.Request.Path = "/404"; - } - finally - { - // not much we can do to save us, carry on anyways - await this.next(ctx); - } - } - } -} \ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Pages/Email/CompleteEmailVerificationPage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/Email/CompleteEmailVerificationPage.cshtml.cs index da9fc1c4..4e702978 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Email/CompleteEmailVerificationPage.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/Email/CompleteEmailVerificationPage.cshtml.cs @@ -1,5 +1,7 @@ #nullable enable using LBPUnion.ProjectLighthouse.Configuration; +using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.PlayerData.Profiles.Email; using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts; @@ -19,9 +21,6 @@ public class CompleteEmailVerificationPage : BaseLayout { if (!ServerConfiguration.Instance.Mail.MailEnabled) return this.NotFound(); - User? user = this.Database.UserFromWebRequest(this.Request); - if (user == null) return this.Redirect("~/login"); - EmailVerificationToken? emailVerifyToken = await this.Database.EmailVerificationTokens.FirstOrDefaultAsync(e => e.EmailToken == token); if (emailVerifyToken == null) { @@ -29,6 +28,8 @@ public class CompleteEmailVerificationPage : BaseLayout return this.Page(); } + User user = await this.Database.Users.FirstAsync(u => u.UserId == emailVerifyToken.UserId); + if (DateTime.Now > emailVerifyToken.ExpiresAt) { this.Error = "This token has expired"; @@ -44,9 +45,27 @@ public class CompleteEmailVerificationPage : BaseLayout this.Database.EmailVerificationTokens.Remove(emailVerifyToken); user.EmailAddressVerified = true; - await this.Database.SaveChangesAsync(); - return this.Page(); + if (user.Password != null) return this.Page(); + + // if user's account was created automatically + WebToken webToken = new() + { + ExpiresAt = DateTime.Now.AddDays(7), + Verified = true, + UserId = user.UserId, + UserToken = CryptoHelper.GenerateAuthToken(), + }; + user.PasswordResetRequired = true; + this.Database.WebTokens.Add(webToken); + await this.Database.SaveChangesAsync(); + this.Response.Cookies.Append("LighthouseToken", + webToken.UserToken, + new CookieOptions + { + Expires = DateTimeOffset.Now.AddDays(7), + }); + return this.Redirect("/passwordReset"); } } \ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Pages/Email/SendVerificationEmailPage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/Email/SendVerificationEmailPage.cshtml.cs index b6924b23..f0a70b52 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Email/SendVerificationEmailPage.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/Email/SendVerificationEmailPage.cshtml.cs @@ -1,13 +1,9 @@ #nullable enable -using System.Collections.Concurrent; using LBPUnion.ProjectLighthouse.Configuration; -using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Helpers; 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; namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.Email; @@ -16,9 +12,6 @@ public class SendVerificationEmailPage : BaseLayout public SendVerificationEmailPage(Database database) : base(database) {} - // (User id, timestamp of last request + 30 seconds) - private static readonly ConcurrentDictionary recentlySentEmail = new(); - public bool Success { get; set; } public async Task OnGet() @@ -28,70 +21,9 @@ public class SendVerificationEmailPage : BaseLayout User? user = this.Database.UserFromWebRequest(this.Request); if (user == null) return this.Redirect("/login"); - // `using` weirdness here. I tried to fix it, but I couldn't. - // The user should never see this page once they've been verified, so assert here. - System.Diagnostics.Debug.Assert(!user.EmailAddressVerified); + if (user.EmailAddressVerified) return this.Redirect("/"); - // Othewise, on a release build, just silently redirect them to the landing page. - #if !DEBUG - if (user.EmailAddressVerified) - { - return this.Redirect("/"); - } - #endif - - // Remove expired entries - for (int i = recentlySentEmail.Count - 1; i >= 0; i--) - { - KeyValuePair entry = recentlySentEmail.ElementAt(i); - bool valueExists = recentlySentEmail.TryGetValue(entry.Key, out long timestamp); - if (!valueExists) - { - recentlySentEmail.TryRemove(entry.Key, out _); - continue; - } - if (TimeHelper.TimestampMillis > timestamp) recentlySentEmail.TryRemove(entry.Key, out _); - } - - - if (recentlySentEmail.ContainsKey(user.UserId)) - { - bool valueExists = recentlySentEmail.TryGetValue(user.UserId, out long timestamp); - if (!valueExists) - { - recentlySentEmail.TryRemove(user.UserId, out _); - } - else if (timestamp > TimeHelper.TimestampMillis) - { - this.Success = true; - return this.Page(); - } - } - - string? existingToken = await this.Database.EmailVerificationTokens.Where(v => v.UserId == user.UserId).Select(v => v.EmailToken).FirstOrDefaultAsync(); - if (existingToken != null) - this.Database.EmailVerificationTokens.RemoveWhere(t => t.EmailToken == existingToken); - - EmailVerificationToken verifyToken = new() - { - UserId = user.UserId, - User = user, - EmailToken = CryptoHelper.GenerateAuthToken(), - ExpiresAt = DateTime.Now.AddHours(6), - }; - - this.Database.EmailVerificationTokens.Add(verifyToken); - await this.Database.SaveChangesAsync(); - - string body = "Hello,\n\n" + - $"This email is a request to verify this email for your (likely new!) Project Lighthouse account ({user.Username}).\n\n" + - $"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 = 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); + this.Success = await SMTPHelper.SendVerificationEmail(this.Database, user); return this.Page(); } diff --git a/ProjectLighthouse.Servers.Website/Pages/Errors/NotFoundPage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/Errors/NotFoundPage.cshtml.cs index a1d72fc4..bd4ca775 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Errors/NotFoundPage.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/Errors/NotFoundPage.cshtml.cs @@ -1,5 +1,5 @@ using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts; -using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.Mvc; namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.Errors; @@ -7,9 +7,6 @@ public class NotFoundPage : BaseLayout { public NotFoundPage(Database database) : base(database) {} - - public void OnGet() - { - - } + + public IActionResult OnGet() => this.Page(); } \ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Pages/Login/LoginForm.cshtml b/ProjectLighthouse.Servers.Website/Pages/Login/LoginForm.cshtml index 656c5762..bd44c620 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Login/LoginForm.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/Login/LoginForm.cshtml @@ -6,6 +6,7 @@ @{ Layout = "Layouts/BaseLayout"; Model.Title = Model.Translate(GeneralStrings.LogIn); + Model.ShowTitleInPage = false; } @@ -26,61 +27,70 @@ } -@if (!string.IsNullOrWhiteSpace(Model.Error)) -{ -
-
- @Model.Translate(GeneralStrings.Error) -
-

@Model.Error

-
-} - -
- @Html.AntiForgeryToken() - - -
- @{ - string username = ServerConfiguration.Instance.Mail.MailEnabled ? Model.Translate(GeneralStrings.Email) : Model.Translate(GeneralStrings.Username); - } - -
- - -
-
- -
- -
- - - -
-
- - @if (ServerConfiguration.Instance.Captcha.CaptchaEnabled) - { - @await Html.PartialAsync("Partials/CaptchaPartial") - } - -
- - @if (ServerConfiguration.Instance.Authentication.RegistrationEnabled) +
+ + @if (!string.IsNullOrWhiteSpace(Model.Error)) { - - \ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Pages/Login/PasswordResetPage.cshtml b/ProjectLighthouse.Servers.Website/Pages/Login/PasswordResetPage.cshtml index dce63a3f..de60fd5a 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Login/PasswordResetPage.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/Login/PasswordResetPage.cshtml @@ -5,6 +5,7 @@ @{ Layout = "Layouts/BaseLayout"; Model.Title = "Password Reset"; + Model.ShowTitleInPage = false; } @@ -33,20 +34,39 @@
} +
@Html.AntiForgeryToken() -
- - - -


+
+

+ +
+ @Model.Title +
+

-
- - - -



+
+
+ +
+ + + +
+
-
- \ No newline at end of file +
+ +
+ + + +
+
+
+ +
+
+ +
\ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Pages/Login/PasswordResetRequiredPage.cshtml b/ProjectLighthouse.Servers.Website/Pages/Login/PasswordResetRequiredPage.cshtml index bbe18b08..f7434a0c 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Login/PasswordResetRequiredPage.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/Login/PasswordResetRequiredPage.cshtml @@ -6,6 +6,7 @@ Model.Title = "Password Reset Required"; } +

An administrator has deemed it necessary that you reset your password. Please do so.

diff --git a/ProjectLighthouse.Servers.Website/Startup/WebsiteStartup.cs b/ProjectLighthouse.Servers.Website/Startup/WebsiteStartup.cs index 9202b73d..4ce3b61e 100644 --- a/ProjectLighthouse.Servers.Website/Startup/WebsiteStartup.cs +++ b/ProjectLighthouse.Servers.Website/Startup/WebsiteStartup.cs @@ -1,11 +1,9 @@ using System.Globalization; -using System.Reflection; using LBPUnion.ProjectLighthouse.Localization; using LBPUnion.ProjectLighthouse.Middlewares; using LBPUnion.ProjectLighthouse.Servers.Website.Middlewares; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Localization; -using Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation; using Microsoft.Extensions.FileProviders; #if !DEBUG @@ -76,9 +74,10 @@ public class WebsiteStartup app.UseDeveloperExceptionPage(); #endif + app.UseStatusCodePagesWithReExecute("/404"); + app.UseForwardedHeaders(); - app.UseMiddleware(); app.UseMiddleware(); app.UseMiddleware(); app.UseMiddleware(); diff --git a/ProjectLighthouse/Helpers/EmailHelper.cs b/ProjectLighthouse/Helpers/EmailHelper.cs new file mode 100644 index 00000000..9dc28705 --- /dev/null +++ b/ProjectLighthouse/Helpers/EmailHelper.cs @@ -0,0 +1,77 @@ +#nullable enable +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Configuration; +using LBPUnion.ProjectLighthouse.Extensions; +using LBPUnion.ProjectLighthouse.PlayerData.Profiles; +using LBPUnion.ProjectLighthouse.PlayerData.Profiles.Email; +using Microsoft.EntityFrameworkCore; + +namespace LBPUnion.ProjectLighthouse.Helpers; + +public partial class SMTPHelper +{ + // (User id, timestamp of last request + 30 seconds) + private static readonly ConcurrentDictionary recentlySentEmail = new(); + + public static async Task SendVerificationEmail(Database database, User user) + { + // Remove expired entries + for (int i = recentlySentEmail.Count - 1; i >= 0; i--) + { + KeyValuePair entry = recentlySentEmail.ElementAt(i); + bool valueExists = recentlySentEmail.TryGetValue(entry.Key, out long timestamp); + if (!valueExists) + { + recentlySentEmail.TryRemove(entry.Key, out _); + continue; + } + + if (TimeHelper.TimestampMillis > timestamp) recentlySentEmail.TryRemove(entry.Key, out _); + } + + + if (recentlySentEmail.ContainsKey(user.UserId)) + { + bool valueExists = recentlySentEmail.TryGetValue(user.UserId, out long timestamp); + if (!valueExists) + { + recentlySentEmail.TryRemove(user.UserId, out _); + } + else if (timestamp > TimeHelper.TimestampMillis) + { + return true; + } + } + + string? existingToken = await database.EmailVerificationTokens.Where(v => v.UserId == user.UserId) + .Select(v => v.EmailToken) + .FirstOrDefaultAsync(); + if (existingToken != null) database.EmailVerificationTokens.RemoveWhere(t => t.EmailToken == existingToken); + + EmailVerificationToken verifyToken = new() + { + UserId = user.UserId, + User = user, + EmailToken = CryptoHelper.GenerateAuthToken(), + ExpiresAt = DateTime.Now.AddHours(6), + }; + + database.EmailVerificationTokens.Add(verifyToken); + await database.SaveChangesAsync(); + + string body = "Hello,\n\n" + + $"This email is a request to verify this email for your (likely new!) Project Lighthouse account ({user.Username}).\n\n" + + $"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."; + + bool success = await SendEmailAsync(user.EmailAddress, "Project Lighthouse Email Verification", body); + + // Don't send another email for 30 seconds + recentlySentEmail.TryAdd(user.UserId, TimeHelper.TimestampMillis + 30 * 1000); + return success; + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Helpers/SMTPHelper.cs b/ProjectLighthouse/Helpers/SMTPHelper.cs index e3dc8146..90ab1a46 100644 --- a/ProjectLighthouse/Helpers/SMTPHelper.cs +++ b/ProjectLighthouse/Helpers/SMTPHelper.cs @@ -9,9 +9,8 @@ using LBPUnion.ProjectLighthouse.Logging; namespace LBPUnion.ProjectLighthouse.Helpers; -public class SMTPHelper +public partial class SMTPHelper { - internal static readonly SMTPHelper Instance = new(); private readonly MailAddress fromAddress;