From 980a2af3c660a186c36a63049fd56f3d3fdccf85 Mon Sep 17 00:00:00 2001 From: FeTetra Date: Mon, 16 Dec 2024 19:42:05 -0500 Subject: [PATCH 01/22] Add email blacklist and refactor email validity checks --- .../Controllers/MessageController.cs | 4 +- .../Pages/Email/SetEmailForm.cshtml.cs | 2 +- .../Login/PasswordResetRequestForm.cshtml.cs | 4 +- .../Pages/UserSettingsPage.cshtml.cs | 12 ++---- .../EnforceEmailConfiguration.cs | 25 ++++++++++++ ProjectLighthouse/Helpers/EmailHelper.cs | 38 ++++++++++++++++++- .../Helpers/SanitizationHelper.cs | 9 ----- 7 files changed, 71 insertions(+), 23 deletions(-) create mode 100644 ProjectLighthouse/Configuration/EnforceEmailConfiguration.cs delete mode 100644 ProjectLighthouse/Helpers/SanitizationHelper.cs diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs index 42b1d1b7b..2a519d84f 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs @@ -129,9 +129,9 @@ public async Task Filter(IMailService mailService) if (message.StartsWith("/setemail ") && ServerConfiguration.Instance.Mail.MailEnabled) { string email = message[(message.IndexOf(" ", StringComparison.Ordinal)+1)..]; - if (!SanitizationHelper.IsValidEmail(email)) return this.Ok(); - if (await this.database.Users.AnyAsync(u => u.EmailAddress == email)) return this.Ok(); + // Return a bad request on invalid email address + if (!SMTPHelper.IsValidEmail(this.database, email)) return this.BadRequest(); UserEntity? user = await this.database.UserFromGameToken(token); if (user == null || user.EmailAddressVerified) return this.Ok(); diff --git a/ProjectLighthouse.Servers.Website/Pages/Email/SetEmailForm.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/Email/SetEmailForm.cshtml.cs index b760f03e0..7cca3b9c0 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Email/SetEmailForm.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/Email/SetEmailForm.cshtml.cs @@ -39,7 +39,7 @@ public async Task OnPost(string emailAddress) UserEntity? user = await this.Database.Users.FirstOrDefaultAsync(u => u.UserId == token.UserId); if (user == null) return this.Redirect("~/login"); - if (!SanitizationHelper.IsValidEmail(emailAddress)) + if (!SMTPHelper.IsValidEmail(this.Database, emailAddress)) { this.Error = this.Translate(ErrorStrings.EmailInvalid); return this.Page(); diff --git a/ProjectLighthouse.Servers.Website/Pages/Login/PasswordResetRequestForm.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/Login/PasswordResetRequestForm.cshtml.cs index 9d9aeff72..c4bb84dab 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Login/PasswordResetRequestForm.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/Login/PasswordResetRequestForm.cshtml.cs @@ -38,9 +38,9 @@ public async Task OnPost(string email) return this.Page(); } - if (!SanitizationHelper.IsValidEmail(email)) + if (!SMTPHelper.IsValidEmail(this.Database, email)) { - this.Error = "This email is in an invalid format"; + this.Error = "This email is invalid"; return this.Page(); } diff --git a/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml.cs index 55375a425..d1a9c0a82 100644 --- a/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml.cs @@ -65,17 +65,13 @@ [FromForm] string? language } if (ServerConfiguration.Instance.Mail.MailEnabled && - SanitizationHelper.IsValidEmail(email) && + SMTPHelper.IsValidEmail(this.Database, email) && (this.User == this.ProfileUser || this.User.IsAdmin)) { - // if email hasn't already been used - if (!await this.Database.Users.AnyAsync(u => u.EmailAddress != null && u.EmailAddress.ToLower() == email!.ToLower())) + if (this.ProfileUser.EmailAddress != email) { - if (this.ProfileUser.EmailAddress != email) - { - this.ProfileUser.EmailAddress = email; - this.ProfileUser.EmailAddressVerified = false; - } + this.ProfileUser.EmailAddress = email; + this.ProfileUser.EmailAddressVerified = false; } } diff --git a/ProjectLighthouse/Configuration/EnforceEmailConfiguration.cs b/ProjectLighthouse/Configuration/EnforceEmailConfiguration.cs new file mode 100644 index 000000000..c3354fdb0 --- /dev/null +++ b/ProjectLighthouse/Configuration/EnforceEmailConfiguration.cs @@ -0,0 +1,25 @@ +#nullable enable +using System.Collections.Generic; +using System.IO; +using YamlDotNet.Serialization; + +namespace LBPUnion.ProjectLighthouse.Configuration; + +public class EnforceEmailConfiguration : ConfigurationBase +{ + public override int ConfigVersion { get; set; } = 2; + + public override string ConfigName { get; set; } = "enforce-email.yml"; + + public override bool NeedsConfiguration { get; set; } = false; + + public bool EnableEmailEnforcement { get; set; } = false; + public bool EnableEmailBlacklist { get; set; } = false; + + // No blacklist by default, add path to blacklist + public string BlacklistFilePath { get; set; } = ""; + + public override ConfigurationBase Deserialize + (IDeserializer deserializer, string text) => + deserializer.Deserialize(text); +} \ No newline at end of file diff --git a/ProjectLighthouse/Helpers/EmailHelper.cs b/ProjectLighthouse/Helpers/EmailHelper.cs index 52c69f9da..bb780a3f5 100644 --- a/ProjectLighthouse/Helpers/EmailHelper.cs +++ b/ProjectLighthouse/Helpers/EmailHelper.cs @@ -2,9 +2,11 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; using System.Linq; using System.Threading.Tasks; -using LBPUnion.ProjectLighthouse.Configuration; +using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Types.Entities.Profile; @@ -20,6 +22,13 @@ public static class SMTPHelper private static readonly ConcurrentDictionary recentlySentMail = new(); private const long emailCooldown = 1000 * 30; + + // To prevent ReadAllLines() exception when BlacklistFilePath is empty + private static readonly string[] blacklistFile = + !string.IsNullOrWhiteSpace(EnforceEmailConfiguration.Instance.BlacklistFilePath) + ? File.ReadAllLines(EnforceEmailConfiguration.Instance.BlacklistFilePath) : []; + + private static readonly HashSet blacklistedDomains = new(blacklistFile); private static bool CanSendMail(UserEntity user) { @@ -68,6 +77,33 @@ public static async Task SendPasswordResetEmail(DatabaseContext database, IMailS recentlySentMail.TryAdd(user.UserId, TimeHelper.TimestampMillis + emailCooldown); } + + // Accumulate checks to determine email validity + public static bool IsValidEmail(DatabaseContext database, string email) + { + // Email should not be empty, should be an actual email, and shouldn't already be used by an account + if (!string.IsNullOrWhiteSpace(email) && new EmailAddressAttribute().IsValid(email) && !EmailIsUsed(database, email).Result) + { + // Get domain after '@' character + string domain = email.Split('@')[1]; + + // Don't even bother if there are no domains in blacklist (AKA file path is empty/invalid, or file itself is empty) + if (EnforceEmailConfiguration.Instance.EnableEmailBlacklist && blacklistedDomains.Count > 0) return !DomainIsInBlacklist(domain); + + return true; + } + + return false; + } + + // Check if email is already in use by an account + private static async Task EmailIsUsed(DatabaseContext database, string email) + { + return await database.Users.AnyAsync(u => u.EmailAddress != null && u.EmailAddress.ToLower() == email.ToLower()); + } + + // Check if domain blacklist contains input domain + private static bool DomainIsInBlacklist(string domain) => blacklistedDomains.Contains(domain); public static void SendRegistrationEmail(IMailService mail, UserEntity user) { diff --git a/ProjectLighthouse/Helpers/SanitizationHelper.cs b/ProjectLighthouse/Helpers/SanitizationHelper.cs deleted file mode 100644 index 0065bc9f8..000000000 --- a/ProjectLighthouse/Helpers/SanitizationHelper.cs +++ /dev/null @@ -1,9 +0,0 @@ -#nullable enable -using System.ComponentModel.DataAnnotations; - -namespace LBPUnion.ProjectLighthouse.Helpers; - -public static class SanitizationHelper -{ - public static bool IsValidEmail(string? email) => !string.IsNullOrWhiteSpace(email) && new EmailAddressAttribute().IsValid(email); -} \ No newline at end of file From fbe3ceec7c4315a2eb9ead9628a54e53a000d6a5 Mon Sep 17 00:00:00 2001 From: FeTetra Date: Mon, 16 Dec 2024 23:07:30 -0500 Subject: [PATCH 02/22] Slight method refactor --- ProjectLighthouse/Helpers/EmailHelper.cs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/ProjectLighthouse/Helpers/EmailHelper.cs b/ProjectLighthouse/Helpers/EmailHelper.cs index bb780a3f5..25e749916 100644 --- a/ProjectLighthouse/Helpers/EmailHelper.cs +++ b/ProjectLighthouse/Helpers/EmailHelper.cs @@ -82,13 +82,17 @@ public static async Task SendPasswordResetEmail(DatabaseContext database, IMailS public static bool IsValidEmail(DatabaseContext database, string email) { // Email should not be empty, should be an actual email, and shouldn't already be used by an account - if (!string.IsNullOrWhiteSpace(email) && new EmailAddressAttribute().IsValid(email) && !EmailIsUsed(database, email).Result) + if (!string.IsNullOrWhiteSpace(email) && emailValidator.IsValid(email) && !EmailIsUsed(database, email).Result) { - // Get domain after '@' character - string domain = email.Split('@')[1]; - // Don't even bother if there are no domains in blacklist (AKA file path is empty/invalid, or file itself is empty) - if (EnforceEmailConfiguration.Instance.EnableEmailBlacklist && blacklistedDomains.Count > 0) return !DomainIsInBlacklist(domain); + if (EnforceEmailConfiguration.Instance.EnableEmailBlacklist && blacklistedDomains.Count > 0) + { + // Get domain by splitting at '@' character + string domain = email.Split('@')[1]; + + // Return true if domain is found in blacklist + return blacklistedDomains.Contains(domain); + } return true; } @@ -96,15 +100,15 @@ public static bool IsValidEmail(DatabaseContext database, string email) return false; } + // Don't want to allocate every single time we call EmailAddressAttribute.IsValidEmail() + private static readonly EmailAddressAttribute emailValidator = new(); + // Check if email is already in use by an account private static async Task EmailIsUsed(DatabaseContext database, string email) { return await database.Users.AnyAsync(u => u.EmailAddress != null && u.EmailAddress.ToLower() == email.ToLower()); } - // Check if domain blacklist contains input domain - private static bool DomainIsInBlacklist(string domain) => blacklistedDomains.Contains(domain); - public static void SendRegistrationEmail(IMailService mail, UserEntity user) { // There is intentionally no cooldown here because this is only used for registration From 9e9c96e1a3d4e2aef341d2a95487c2aa3a137a19 Mon Sep 17 00:00:00 2001 From: FeTetra Date: Tue, 17 Dec 2024 02:19:53 -0500 Subject: [PATCH 03/22] Prevent access to GameServer features if email enforcement is enabled and email is unverified --- .../Controllers/CommentController.cs | 16 ++++++++++++++++ .../Controllers/Matching/EnterLevelController.cs | 16 ++++++++++++++++ .../Controllers/Matching/MatchController.cs | 4 ++++ 3 files changed, 36 insertions(+) diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs index 7e3baeb9e..3eb709ed6 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs @@ -22,6 +22,8 @@ namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers; public class CommentController : ControllerBase { private readonly DatabaseContext database; + + private static readonly bool emailEnforcementEnabled = EnforceEmailConfiguration.Instance.EnableEmailEnforcement; public CommentController(DatabaseContext database) { this.database = database; @@ -33,9 +35,13 @@ public async Task RateComment([FromQuery] int commentId, [FromQue { GameTokenEntity token = this.GetToken(); + UserEntity? user = await this.database.UserFromGameToken(token); + // Return bad request if both are true or both are false if ((slotId == 0 || SlotHelper.IsTypeInvalid(slotType)) == (username == null)) return this.BadRequest(); + if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); + bool success = await this.database.RateComment(token.UserId, commentId, rating); if (!success) return this.BadRequest(); @@ -53,6 +59,8 @@ public async Task GetComments(string? username, string? slotType, if ((slotId == 0 || SlotHelper.IsTypeInvalid(slotType)) == (username == null)) return this.BadRequest(); + if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); + int originalSlotId = slotId; if (slotType == "developer") slotId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer); @@ -117,9 +125,13 @@ public async Task PostComment(string? username, string? slotType, { GameTokenEntity token = this.GetToken(); + UserEntity? user = await this.database.UserFromGameToken(token); + // Deny request if in read-only mode if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest(); + if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); + GameComment? comment = await this.DeserializeBody(); if (comment?.Message == null) return this.BadRequest(); @@ -156,11 +168,15 @@ public async Task DeleteComment([FromQuery] int commentId, string { GameTokenEntity token = this.GetToken(); + UserEntity? user = await this.database.UserFromGameToken(token); + // Deny request if in read-only mode if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest(); if ((slotId == 0 || SlotHelper.IsTypeInvalid(slotType)) == (username == null)) return this.BadRequest(); + if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); + CommentEntity? comment = await this.database.Comments.FirstOrDefaultAsync(c => c.CommentId == commentId); if (comment == null) return this.NotFound(); diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Matching/EnterLevelController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Matching/EnterLevelController.cs index e8b2aa37a..bda3a3497 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Matching/EnterLevelController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Matching/EnterLevelController.cs @@ -1,9 +1,13 @@ #nullable enable +using System.Runtime.CompilerServices; using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.Configuration; +using LBPUnion.ProjectLighthouse.Migrations; using LBPUnion.ProjectLighthouse.Types.Entities.Interaction; using LBPUnion.ProjectLighthouse.Types.Entities.Level; +using LBPUnion.ProjectLighthouse.Types.Entities.Profile; using LBPUnion.ProjectLighthouse.Types.Entities.Token; using LBPUnion.ProjectLighthouse.Types.Users; using Microsoft.AspNetCore.Authorization; @@ -20,6 +24,8 @@ public class EnterLevelController : ControllerBase { private readonly DatabaseContext database; + private static readonly bool emailEnforcementEnabled = EnforceEmailConfiguration.Instance.EnableEmailEnforcement; + public EnterLevelController(DatabaseContext database) { this.database = database; @@ -30,8 +36,13 @@ public async Task PlayLevel(string slotType, int slotId) { GameTokenEntity token = this.GetToken(); + UserEntity? user = await this.database.UserFromGameToken(token); + if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest(); + // Return bad request on unverified email if enforcement is enabled + if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); + // don't count plays for developer slots if (slotType == "developer") return this.Ok(); @@ -100,8 +111,13 @@ public async Task EnterLevel(string slotType, int slotId) { GameTokenEntity token = this.GetToken(); + UserEntity? user = await this.database.UserFromGameToken(token); + if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest(); + // Return bad request on unverified email if enforcement is enabled + if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); + if (slotType == "developer") return this.Ok(); SlotEntity? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId); diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Matching/MatchController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Matching/MatchController.cs index 2b3b295b9..597031164 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Matching/MatchController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Matching/MatchController.cs @@ -25,6 +25,8 @@ public class MatchController : ControllerBase { private readonly DatabaseContext database; + private static readonly bool emailEnforcementEnabled = EnforceEmailConfiguration.Instance.EnableEmailEnforcement; + public MatchController(DatabaseContext database) { this.database = database; @@ -43,6 +45,8 @@ public async Task Match() UserEntity? user = await this.database.UserFromGameToken(token); if (user == null) return this.Forbid(); + if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); + await LastContactHelper.SetLastContact(this.database, user, token.GameVersion, token.Platform); // Do not allow matchmaking if it has been disabled From 7495c41d4437fdfa39640fadceeaccbde5eddcaa Mon Sep 17 00:00:00 2001 From: FeTetra Date: Tue, 17 Dec 2024 02:51:03 -0500 Subject: [PATCH 04/22] Add announcement text to notify users of email enforcement requirements --- .../Controllers/MessageController.cs | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs index 2a519d84f..7339de5ca 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs @@ -55,18 +55,40 @@ public async Task Announce() { GameTokenEntity token = this.GetToken(); - string username = await this.database.UsernameFromGameToken(token); + UserEntity? user = await this.database.UserFromGameToken(token); StringBuilder announceText = new(ServerConfiguration.Instance.AnnounceText); - announceText.Replace("%user", username); - announceText.Replace("%id", token.UserId.ToString()); + announceText.Replace("%user", user.Username); + announceText.Replace("%id", user.UserId.ToString()); if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) { announceText.Insert(0, BaseLayoutStrings.ReadOnlyWarn.Translate(LocalizationManager.DefaultLang) + "\n\n"); } + if (EnforceEmailConfiguration.Instance.EnableEmailEnforcement && user.EmailAddress == null) + { + if (user.EmailAddress == null) + { + // This will probably need translations + announceText.Insert(0, + "This server instance has email enforcement enabled, " + + "you will need to set and verify an email address to use most features.\n" + + "You can set an email by opening the text chat and typing " + + "\"/setemail [youremail@example.com]\" (do not include the brackets.\n\n"); + } + if (!user.EmailAddressVerified) + { + // More stuff to translate later + announceText.Insert(0, + "You have set an email address on your account, but you have not verified " + + "it. Make sure to check your inbox for a verification email. " + + "If you have not received an email, please contact an instance " + + "administrator for further assistance.\n\n"); + } + } + #if DEBUG announceText.Append("\n\n---DEBUG INFO---\n" + $"user.UserId: {token.UserId}\n" + From cb6d77203b32969860af7e663fb222b4906e0a62 Mon Sep 17 00:00:00 2001 From: FeTetra Date: Tue, 17 Dec 2024 03:12:26 -0500 Subject: [PATCH 05/22] Fix comments and unauthorize null users --- .../Controllers/CommentController.cs | 10 ++++++++-- .../Controllers/Matching/EnterLevelController.cs | 6 ++++-- .../Controllers/Matching/MatchController.cs | 3 ++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs index 3eb709ed6..81edeee78 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs @@ -36,10 +36,12 @@ public async Task RateComment([FromQuery] int commentId, [FromQue GameTokenEntity token = this.GetToken(); UserEntity? user = await this.database.UserFromGameToken(token); + if (user == null) return this.Unauthorized(); // Return bad request if both are true or both are false if ((slotId == 0 || SlotHelper.IsTypeInvalid(slotType)) == (username == null)) return this.BadRequest(); + // Return bad request on unverified email if enforcement is enabled if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); bool success = await this.database.RateComment(token.UserId, commentId, rating); @@ -59,6 +61,7 @@ public async Task GetComments(string? username, string? slotType, if ((slotId == 0 || SlotHelper.IsTypeInvalid(slotType)) == (username == null)) return this.BadRequest(); + // Return bad request on unverified email if enforcement is enabled if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); int originalSlotId = slotId; @@ -126,11 +129,13 @@ public async Task PostComment(string? username, string? slotType, GameTokenEntity token = this.GetToken(); UserEntity? user = await this.database.UserFromGameToken(token); + if (user == null) return this.Unauthorized(); // Deny request if in read-only mode if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest(); - if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); + // Return bad request on unverified email if enforcement is enabled + if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); GameComment? comment = await this.DeserializeBody(); if (comment?.Message == null) return this.BadRequest(); @@ -175,7 +180,8 @@ public async Task DeleteComment([FromQuery] int commentId, string if ((slotId == 0 || SlotHelper.IsTypeInvalid(slotType)) == (username == null)) return this.BadRequest(); - if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); + // Return bad request on unverified email if enforcement is enabled + if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); CommentEntity? comment = await this.database.Comments.FirstOrDefaultAsync(c => c.CommentId == commentId); if (comment == null) return this.NotFound(); diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Matching/EnterLevelController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Matching/EnterLevelController.cs index bda3a3497..338dea0fe 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Matching/EnterLevelController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Matching/EnterLevelController.cs @@ -37,11 +37,12 @@ public async Task PlayLevel(string slotType, int slotId) GameTokenEntity token = this.GetToken(); UserEntity? user = await this.database.UserFromGameToken(token); + if (user == null) return this.Unauthorized(); if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest(); // Return bad request on unverified email if enforcement is enabled - if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); + if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); // don't count plays for developer slots if (slotType == "developer") return this.Ok(); @@ -112,11 +113,12 @@ public async Task EnterLevel(string slotType, int slotId) GameTokenEntity token = this.GetToken(); UserEntity? user = await this.database.UserFromGameToken(token); + if (user == null) return this.Unauthorized(); if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest(); // Return bad request on unverified email if enforcement is enabled - if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); + if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); if (slotType == "developer") return this.Ok(); diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Matching/MatchController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Matching/MatchController.cs index 597031164..e0fc6afcc 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Matching/MatchController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Matching/MatchController.cs @@ -43,8 +43,9 @@ public async Task Match() GameTokenEntity token = this.GetToken(); UserEntity? user = await this.database.UserFromGameToken(token); - if (user == null) return this.Forbid(); + if (user == null) return this.Unauthorized(); + // Return bad request on unverified email if enforcement is enabled if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); await LastContactHelper.SetLastContact(this.database, user, token.GameVersion, token.Platform); From 5bdb1c050187e213c6080518f884ebe0cd0ffa17 Mon Sep 17 00:00:00 2001 From: FeTetra Date: Thu, 19 Dec 2024 22:45:42 -0500 Subject: [PATCH 06/22] General cleanup, move warning messages, fix bugs --- .../.idea/sqldialects.xml | 6 ------ .../StringLists/BaseLayoutStrings.cs | 5 +++++ .../Controllers/MessageController.cs | 20 +++++++------------ .../EnforceEmailConfiguration.cs | 17 ++++++++++++++++ ProjectLighthouse/Helpers/EmailHelper.cs | 4 ++-- 5 files changed, 31 insertions(+), 21 deletions(-) delete mode 100644 .idea/.idea.ProjectLighthouse/.idea/sqldialects.xml diff --git a/.idea/.idea.ProjectLighthouse/.idea/sqldialects.xml b/.idea/.idea.ProjectLighthouse/.idea/sqldialects.xml deleted file mode 100644 index 56782cab2..000000000 --- a/.idea/.idea.ProjectLighthouse/.idea/sqldialects.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/ProjectLighthouse.Localization/StringLists/BaseLayoutStrings.cs b/ProjectLighthouse.Localization/StringLists/BaseLayoutStrings.cs index f46cb4e60..38e123335 100644 --- a/ProjectLighthouse.Localization/StringLists/BaseLayoutStrings.cs +++ b/ProjectLighthouse.Localization/StringLists/BaseLayoutStrings.cs @@ -26,5 +26,10 @@ public static class BaseLayoutStrings public static readonly TranslatableString ReadOnlyWarnTitle = create("read_only_warn_title"); public static readonly TranslatableString ReadOnlyWarn = create("read_only_warn"); + public static readonly TranslatableString EmailEnforcementWarnMain = create("email_enforcement_message_main"); + public static readonly TranslatableString EmailEnforcementWarnNoEmail = create("email_enforcement_message_no_email"); + public static readonly TranslatableString EmailEnforcementWarnVerifyEmail = create("email_enforcement_message_verify_email"); + + private static TranslatableString create(string key) => new(TranslationAreas.BaseLayout, key); } \ No newline at end of file diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs index 7339de5ca..99bc79299 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs @@ -60,32 +60,26 @@ public async Task Announce() StringBuilder announceText = new(ServerConfiguration.Instance.AnnounceText); announceText.Replace("%user", user.Username); - announceText.Replace("%id", user.UserId.ToString()); + announceText.Replace("%id", token.UserId.ToString()); if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) { announceText.Insert(0, BaseLayoutStrings.ReadOnlyWarn.Translate(LocalizationManager.DefaultLang) + "\n\n"); } - if (EnforceEmailConfiguration.Instance.EnableEmailEnforcement && user.EmailAddress == null) + if (EnforceEmailConfiguration.Instance.EnableEmailEnforcement) { + announceText.Insert(0, EnforceEmailConfiguration.Instance.EmailEnforcementMessageMain); + if (user.EmailAddress == null) { // This will probably need translations - announceText.Insert(0, - "This server instance has email enforcement enabled, " + - "you will need to set and verify an email address to use most features.\n" + - "You can set an email by opening the text chat and typing " + - "\"/setemail [youremail@example.com]\" (do not include the brackets.\n\n"); + announceText.Insert(0, EnforceEmailConfiguration.Instance.EmailEnforcementMessageNoEmail); } - if (!user.EmailAddressVerified) + else if (!user.EmailAddressVerified) { // More stuff to translate later - announceText.Insert(0, - "You have set an email address on your account, but you have not verified " + - "it. Make sure to check your inbox for a verification email. " + - "If you have not received an email, please contact an instance " + - "administrator for further assistance.\n\n"); + announceText.Insert(0, EnforceEmailConfiguration.Instance.EmailEnforcementMessageVerify); } } diff --git a/ProjectLighthouse/Configuration/EnforceEmailConfiguration.cs b/ProjectLighthouse/Configuration/EnforceEmailConfiguration.cs index c3354fdb0..b044df994 100644 --- a/ProjectLighthouse/Configuration/EnforceEmailConfiguration.cs +++ b/ProjectLighthouse/Configuration/EnforceEmailConfiguration.cs @@ -18,6 +18,23 @@ public class EnforceEmailConfiguration : ConfigurationBase/setemail " + + "[youremail@example.com]\" (do not include the brackets.)\n\n"; + + public string EmailEnforcementMessageVerify { get; set; } = + "You have set an email address on your account, but you have not " + + "verified it. Make sure to check your inbox for a verification email. " + + "If you have not received an email, please contact an instance " + + "administrator for further assistance.\n\n"; public override ConfigurationBase Deserialize (IDeserializer deserializer, string text) => diff --git a/ProjectLighthouse/Helpers/EmailHelper.cs b/ProjectLighthouse/Helpers/EmailHelper.cs index 25e749916..697c3bf53 100644 --- a/ProjectLighthouse/Helpers/EmailHelper.cs +++ b/ProjectLighthouse/Helpers/EmailHelper.cs @@ -90,8 +90,8 @@ public static bool IsValidEmail(DatabaseContext database, string email) // Get domain by splitting at '@' character string domain = email.Split('@')[1]; - // Return true if domain is found in blacklist - return blacklistedDomains.Contains(domain); + // Return false if domain is found in blacklist + return !blacklistedDomains.Contains(domain); } return true; From f6cbafd524bbe3ec3f5ba9f53d4e2204cc310f93 Mon Sep 17 00:00:00 2001 From: FeTetra Date: Mon, 23 Dec 2024 00:30:37 -0500 Subject: [PATCH 07/22] Slight refactor and finish enforcement on endpoints --- .../Controllers/CommentController.cs | 33 ++++++++----------- .../Matching/EnterLevelController.cs | 8 ----- .../Controllers/Matching/MatchController.cs | 1 - .../Controllers/Resources/PhotosController.cs | 19 +++++++++-- .../Resources/ResourcesController.cs | 25 +++++++++++++- .../Controllers/UserController.cs | 17 ++++++++-- ProjectLighthouse/Helpers/EmailHelper.cs | 10 +++++- 7 files changed, 78 insertions(+), 35 deletions(-) diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs index 81edeee78..70765ea05 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs @@ -34,16 +34,15 @@ public CommentController(DatabaseContext database) public async Task RateComment([FromQuery] int commentId, [FromQuery] int rating, string? username, string? slotType, int slotId) { GameTokenEntity token = this.GetToken(); - UserEntity? user = await this.database.UserFromGameToken(token); if (user == null) return this.Unauthorized(); + // Return bad request on unverified email if enforcement is enabled + if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); + // Return bad request if both are true or both are false if ((slotId == 0 || SlotHelper.IsTypeInvalid(slotType)) == (username == null)) return this.BadRequest(); - // Return bad request on unverified email if enforcement is enabled - if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); - bool success = await this.database.RateComment(token.UserId, commentId, rating); if (!success) return this.BadRequest(); @@ -55,15 +54,14 @@ public async Task RateComment([FromQuery] int commentId, [FromQue public async Task GetComments(string? username, string? slotType, int slotId) { GameTokenEntity token = this.GetToken(); - - UserEntity? user = await this.database.UserFromGameToken(token); + UserEntity? user = await this.database.UserFromGameToken(token); if (user == null) return this.Unauthorized(); - if ((slotId == 0 || SlotHelper.IsTypeInvalid(slotType)) == (username == null)) return this.BadRequest(); - - // Return bad request on unverified email if enforcement is enabled + // Return bad request on unverified email if enforcement is enabled if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); + if ((slotId == 0 || SlotHelper.IsTypeInvalid(slotType)) == (username == null)) return this.BadRequest(); + int originalSlotId = slotId; if (slotType == "developer") slotId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer); @@ -108,8 +106,7 @@ from blockedProfile in this.database.BlockedProfiles .ApplyPagination(pageData) .ToListAsync()).ToSerializableList(c => GameComment.CreateFromEntity(c, token.UserId)); - if (type == CommentType.Level && slotType == "developer" && user.IsModerator && pageData.PageStart == 1) - { + if (type == CommentType.Level && slotType == "developer" && user.IsModerator && pageData.PageStart == 1) { comments.Insert(0, new GameComment { CommentId = 0, @@ -127,16 +124,15 @@ from blockedProfile in this.database.BlockedProfiles public async Task PostComment(string? username, string? slotType, int slotId) { GameTokenEntity token = this.GetToken(); - UserEntity? user = await this.database.UserFromGameToken(token); if (user == null) return this.Unauthorized(); + // Return bad request on unverified email if enforcement is enabled + if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); + // Deny request if in read-only mode if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest(); - // Return bad request on unverified email if enforcement is enabled - if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); - GameComment? comment = await this.DeserializeBody(); if (comment?.Message == null) return this.BadRequest(); @@ -172,17 +168,16 @@ public async Task PostComment(string? username, string? slotType, public async Task DeleteComment([FromQuery] int commentId, string? username, string? slotType, int slotId) { GameTokenEntity token = this.GetToken(); - UserEntity? user = await this.database.UserFromGameToken(token); + // Return bad request on unverified email if enforcement is enabled + if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); + // Deny request if in read-only mode if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest(); if ((slotId == 0 || SlotHelper.IsTypeInvalid(slotType)) == (username == null)) return this.BadRequest(); - // Return bad request on unverified email if enforcement is enabled - if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); - CommentEntity? comment = await this.database.Comments.FirstOrDefaultAsync(c => c.CommentId == commentId); if (comment == null) return this.NotFound(); diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Matching/EnterLevelController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Matching/EnterLevelController.cs index 338dea0fe..d2394f3a5 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Matching/EnterLevelController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Matching/EnterLevelController.cs @@ -24,8 +24,6 @@ public class EnterLevelController : ControllerBase { private readonly DatabaseContext database; - private static readonly bool emailEnforcementEnabled = EnforceEmailConfiguration.Instance.EnableEmailEnforcement; - public EnterLevelController(DatabaseContext database) { this.database = database; @@ -41,9 +39,6 @@ public async Task PlayLevel(string slotType, int slotId) if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest(); - // Return bad request on unverified email if enforcement is enabled - if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); - // don't count plays for developer slots if (slotType == "developer") return this.Ok(); @@ -117,9 +112,6 @@ public async Task EnterLevel(string slotType, int slotId) if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest(); - // Return bad request on unverified email if enforcement is enabled - if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); - if (slotType == "developer") return this.Ok(); SlotEntity? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId); diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Matching/MatchController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Matching/MatchController.cs index e0fc6afcc..cec697991 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Matching/MatchController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Matching/MatchController.cs @@ -41,7 +41,6 @@ public MatchController(DatabaseContext database) public async Task Match() { GameTokenEntity token = this.GetToken(); - UserEntity? user = await this.database.UserFromGameToken(token); if (user == null) return this.Unauthorized(); diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Resources/PhotosController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Resources/PhotosController.cs index 4c73d0a04..2feb0e3cf 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Resources/PhotosController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Resources/PhotosController.cs @@ -27,6 +27,8 @@ public class PhotosController : ControllerBase { private readonly DatabaseContext database; + private static readonly bool emailEnforcementEnabled = EnforceEmailConfiguration.Instance.EnableEmailEnforcement; + public PhotosController(DatabaseContext database) { this.database = database; @@ -36,6 +38,11 @@ public PhotosController(DatabaseContext database) public async Task UploadPhoto() { GameTokenEntity token = this.GetToken(); + UserEntity? user = await this.database.UserFromGameToken(token); + if (user == null) return this.Unauthorized(); + + // Return bad request on unverified email if enforcement is enabled + if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); // Deny request if in read-only mode if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest(); @@ -174,6 +181,11 @@ await WebhookHelper.SendWebhook [HttpGet("photos/{slotType}/{id:int}")] public async Task SlotPhotos(string slotType, int id, [FromQuery] string? by) { + GameTokenEntity token = this.GetToken(); + UserEntity? user = await this.database.UserFromGameToken(token); + + // Return bad request on unverified email if enforcement is enabled + if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest(); @@ -202,7 +214,6 @@ public async Task SlotPhotos(string slotType, int id, [FromQuery] [HttpGet("photos/by")] public async Task UserPhotosBy(string user) { - int targetUserId = await this.database.UserIdFromUsername(user); if (targetUserId == 0) return this.NotFound(); @@ -218,7 +229,7 @@ public async Task UserPhotosBy(string user) [HttpGet("photos/with")] public async Task UserPhotosWith(string user) - { + { int targetUserId = await this.database.UserIdFromUsername(user); if (targetUserId == 0) return this.NotFound(); @@ -237,6 +248,10 @@ public async Task UserPhotosWith(string user) public async Task DeletePhoto(int id) { GameTokenEntity token = this.GetToken(); + UserEntity? user = await this.database.UserFromGameToken(token); + + // Return bad request on unverified email if enforcement is enabled + if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); PhotoEntity? photo = await this.database.Photos.FirstOrDefaultAsync(p => p.PhotoId == id); if (photo == null) return this.NotFound(); diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Resources/ResourcesController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Resources/ResourcesController.cs index 7231ccafb..800046a1f 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Resources/ResourcesController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Resources/ResourcesController.cs @@ -1,12 +1,15 @@ #nullable enable using System.Text; using LBPUnion.ProjectLighthouse.Configuration; +using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Files; using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Misc; +using LBPUnion.ProjectLighthouse.Types.Entities.Profile; using LBPUnion.ProjectLighthouse.Types.Logging; using LBPUnion.ProjectLighthouse.Types.Resources; +using LBPUnion.ProjectLighthouse.Types.Entities.Token; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using IOFile = System.IO.File; @@ -19,10 +22,18 @@ namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Resources; [Route("LITTLEBIGPLANETPS3_XML")] public class ResourcesController : ControllerBase { + private readonly DatabaseContext database; + + public ResourcesController(DatabaseContext database) + { + this.database = database; + } [HttpPost("showModerated")] public IActionResult ShowModerated() => this.Ok(new ResourceList()); + private static readonly bool emailEnforcementEnabled = EnforceEmailConfiguration.Instance.EnableEmailEnforcement; + [HttpPost("filterResources")] [HttpPost("showNotUploaded")] public async Task FilterResources() @@ -36,8 +47,14 @@ public async Task FilterResources() } [HttpGet("r/{hash}")] - public IActionResult GetResource(string hash) + public async Task GetResource(string hash) { + GameTokenEntity token = this.GetToken(); + UserEntity? user = await this.database.UserFromGameToken(token); + + // Return bad request on unverified email if enforcement is enabled + if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); + string path = FileHelper.GetResourcePath(hash); string fullPath = Path.GetFullPath(path); @@ -54,6 +71,12 @@ public IActionResult GetResource(string hash) [HttpPost("upload/{hash}")] public async Task UploadResource(string hash) { + GameTokenEntity token = this.GetToken(); + UserEntity? user = await this.database.UserFromGameToken(token); + + // Return bad request on unverified email if enforcement is enabled + if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); + string assetsDirectory = FileHelper.ResourcePath; string path = FileHelper.GetResourcePath(hash); string fullPath = Path.GetFullPath(path); diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs index 7301bc52c..c2675d13d 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs @@ -29,6 +29,8 @@ public class UserController : ControllerBase { private readonly DatabaseContext database; + private static readonly bool emailEnforcementEnabled = EnforceEmailConfiguration.Instance.EnableEmailEnforcement; + public UserController(DatabaseContext database) { this.database = database; @@ -36,7 +38,11 @@ public UserController(DatabaseContext database) [HttpGet("user/{username}")] public async Task GetUser(string username) - { + { + // Return bad request on unverified email if enforcement is enabled + GameTokenEntity token = this.GetToken(); + if (emailEnforcementEnabled && !token.User.EmailAddressVerified) return this.BadRequest(); + UserEntity? user = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); if (user == null) return this.NotFound(); @@ -66,9 +72,11 @@ public async Task GetUserAlt([FromQuery(Name = "u")] string[] use public async Task UpdateUser() { GameTokenEntity token = this.GetToken(); - UserEntity? user = await this.database.UserFromGameToken(token); - if (user == null) return this.Forbid(); + if (user == null) return this.Forbid(); + + // Return bad request on unverified email if enforcement is enabled + if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); UserUpdate? update = await this.DeserializeBody("updateUser", "user"); @@ -176,6 +184,9 @@ public async Task UpdateMyPins() UserEntity? user = await this.database.UserFromGameToken(this.GetToken()); if (user == null) return this.Forbid(); + // Return bad request on unverified email if enforcement is enabled + if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); + string bodyString = await this.ReadBodyAsync(); Pins? pinJson = JsonSerializer.Deserialize(bodyString); diff --git a/ProjectLighthouse/Helpers/EmailHelper.cs b/ProjectLighthouse/Helpers/EmailHelper.cs index 697c3bf53..63b7ea21a 100644 --- a/ProjectLighthouse/Helpers/EmailHelper.cs +++ b/ProjectLighthouse/Helpers/EmailHelper.cs @@ -9,8 +9,10 @@ using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Extensions; +using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Types.Entities.Profile; using LBPUnion.ProjectLighthouse.Types.Entities.Token; +using LBPUnion.ProjectLighthouse.Types.Logging; using LBPUnion.ProjectLighthouse.Types.Mail; using Microsoft.EntityFrameworkCore; @@ -91,7 +93,13 @@ public static bool IsValidEmail(DatabaseContext database, string email) string domain = email.Split('@')[1]; // Return false if domain is found in blacklist - return !blacklistedDomains.Contains(domain); + if (blacklistedDomains.Contains(domain)) + { + Logger.Info($"Invalid email address {email} submitted by user.", LogArea.Email); + return false; + } + + return true; } return true; From a322a32b389ba0ff6f38ab9151aa6da58dc7f976 Mon Sep 17 00:00:00 2001 From: FeTetra Date: Sun, 26 Jan 2025 20:40:42 -0500 Subject: [PATCH 08/22] Commit of shame --- .../Controllers/UserController.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs index c2675d13d..ee537228f 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs @@ -39,14 +39,16 @@ public UserController(DatabaseContext database) [HttpGet("user/{username}")] public async Task GetUser(string username) { - // Return bad request on unverified email if enforcement is enabled GameTokenEntity token = this.GetToken(); - if (emailEnforcementEnabled && !token.User.EmailAddressVerified) return this.BadRequest(); + UserEntity? user = await this.database.UserFromGameToken(token); + + // Return bad request on unverified email if enforcement is enabled + if (emailEnforcementEnabled && !token.User.EmailAddressVerified || user == null) return this.BadRequest(); - UserEntity? user = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); - if (user == null) return this.NotFound(); + UserEntity? targetUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); + if (targetUser == null) return this.NotFound(); - return this.Ok(GameUser.CreateFromEntity(user, this.GetToken().GameVersion)); + return this.Ok(GameUser.CreateFromEntity(targetUser, this.GetToken().GameVersion)); } [HttpGet("users")] From 561e1d4e26e2de687fe16a47a6492424b4e20c84 Mon Sep 17 00:00:00 2001 From: FeTetra Date: Sun, 16 Feb 2025 19:34:15 -0500 Subject: [PATCH 09/22] Move everything into middleware --- .../Controllers/CommentController.cs | 15 ---- .../Matching/EnterLevelController.cs | 3 - .../Controllers/Matching/MatchController.cs | 5 -- .../Controllers/Resources/PhotosController.cs | 15 ---- .../Resources/ResourcesController.cs | 24 ----- .../Middlewares/EmailEnforcementMiddleware.cs | 89 +++++++++++++++++++ .../Startup/GameServerStartup.cs | 1 + .../EnforceEmailConfiguration.cs | 3 + 8 files changed, 93 insertions(+), 62 deletions(-) create mode 100644 ProjectLighthouse.Servers.GameServer/Middlewares/EmailEnforcementMiddleware.cs diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs index 70765ea05..22ce63d5a 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs @@ -22,8 +22,6 @@ namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers; public class CommentController : ControllerBase { private readonly DatabaseContext database; - - private static readonly bool emailEnforcementEnabled = EnforceEmailConfiguration.Instance.EnableEmailEnforcement; public CommentController(DatabaseContext database) { this.database = database; @@ -37,9 +35,6 @@ public async Task RateComment([FromQuery] int commentId, [FromQue UserEntity? user = await this.database.UserFromGameToken(token); if (user == null) return this.Unauthorized(); - // Return bad request on unverified email if enforcement is enabled - if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); - // Return bad request if both are true or both are false if ((slotId == 0 || SlotHelper.IsTypeInvalid(slotType)) == (username == null)) return this.BadRequest(); @@ -57,9 +52,6 @@ public async Task GetComments(string? username, string? slotType, UserEntity? user = await this.database.UserFromGameToken(token); if (user == null) return this.Unauthorized(); - // Return bad request on unverified email if enforcement is enabled - if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); - if ((slotId == 0 || SlotHelper.IsTypeInvalid(slotType)) == (username == null)) return this.BadRequest(); int originalSlotId = slotId; @@ -127,9 +119,6 @@ public async Task PostComment(string? username, string? slotType, UserEntity? user = await this.database.UserFromGameToken(token); if (user == null) return this.Unauthorized(); - // Return bad request on unverified email if enforcement is enabled - if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); - // Deny request if in read-only mode if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest(); @@ -168,10 +157,6 @@ public async Task PostComment(string? username, string? slotType, public async Task DeleteComment([FromQuery] int commentId, string? username, string? slotType, int slotId) { GameTokenEntity token = this.GetToken(); - UserEntity? user = await this.database.UserFromGameToken(token); - - // Return bad request on unverified email if enforcement is enabled - if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); // Deny request if in read-only mode if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest(); diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Matching/EnterLevelController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Matching/EnterLevelController.cs index d2394f3a5..aeebd756e 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Matching/EnterLevelController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Matching/EnterLevelController.cs @@ -1,10 +1,7 @@ #nullable enable -using System.Runtime.CompilerServices; using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Helpers; -using LBPUnion.ProjectLighthouse.Configuration; -using LBPUnion.ProjectLighthouse.Migrations; using LBPUnion.ProjectLighthouse.Types.Entities.Interaction; using LBPUnion.ProjectLighthouse.Types.Entities.Level; using LBPUnion.ProjectLighthouse.Types.Entities.Profile; diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Matching/MatchController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Matching/MatchController.cs index cec697991..fe210efdd 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Matching/MatchController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Matching/MatchController.cs @@ -25,8 +25,6 @@ public class MatchController : ControllerBase { private readonly DatabaseContext database; - private static readonly bool emailEnforcementEnabled = EnforceEmailConfiguration.Instance.EnableEmailEnforcement; - public MatchController(DatabaseContext database) { this.database = database; @@ -44,9 +42,6 @@ public async Task Match() UserEntity? user = await this.database.UserFromGameToken(token); if (user == null) return this.Unauthorized(); - // Return bad request on unverified email if enforcement is enabled - if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); - await LastContactHelper.SetLastContact(this.database, user, token.GameVersion, token.Platform); // Do not allow matchmaking if it has been disabled diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Resources/PhotosController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Resources/PhotosController.cs index 2feb0e3cf..2acc3dcb4 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Resources/PhotosController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Resources/PhotosController.cs @@ -27,8 +27,6 @@ public class PhotosController : ControllerBase { private readonly DatabaseContext database; - private static readonly bool emailEnforcementEnabled = EnforceEmailConfiguration.Instance.EnableEmailEnforcement; - public PhotosController(DatabaseContext database) { this.database = database; @@ -41,9 +39,6 @@ public async Task UploadPhoto() UserEntity? user = await this.database.UserFromGameToken(token); if (user == null) return this.Unauthorized(); - // Return bad request on unverified email if enforcement is enabled - if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); - // Deny request if in read-only mode if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest(); @@ -181,12 +176,6 @@ await WebhookHelper.SendWebhook [HttpGet("photos/{slotType}/{id:int}")] public async Task SlotPhotos(string slotType, int id, [FromQuery] string? by) { - GameTokenEntity token = this.GetToken(); - UserEntity? user = await this.database.UserFromGameToken(token); - - // Return bad request on unverified email if enforcement is enabled - if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); - if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest(); if (slotType == "developer") id = await SlotHelper.GetPlaceholderSlotId(this.database, id, SlotType.Developer); @@ -248,10 +237,6 @@ public async Task UserPhotosWith(string user) public async Task DeletePhoto(int id) { GameTokenEntity token = this.GetToken(); - UserEntity? user = await this.database.UserFromGameToken(token); - - // Return bad request on unverified email if enforcement is enabled - if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); PhotoEntity? photo = await this.database.Photos.FirstOrDefaultAsync(p => p.PhotoId == id); if (photo == null) return this.NotFound(); diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Resources/ResourcesController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Resources/ResourcesController.cs index 800046a1f..630be2c92 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Resources/ResourcesController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Resources/ResourcesController.cs @@ -1,15 +1,12 @@ #nullable enable using System.Text; using LBPUnion.ProjectLighthouse.Configuration; -using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Files; using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Misc; -using LBPUnion.ProjectLighthouse.Types.Entities.Profile; using LBPUnion.ProjectLighthouse.Types.Logging; using LBPUnion.ProjectLighthouse.Types.Resources; -using LBPUnion.ProjectLighthouse.Types.Entities.Token; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using IOFile = System.IO.File; @@ -22,18 +19,9 @@ namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Resources; [Route("LITTLEBIGPLANETPS3_XML")] public class ResourcesController : ControllerBase { - private readonly DatabaseContext database; - - public ResourcesController(DatabaseContext database) - { - this.database = database; - } - [HttpPost("showModerated")] public IActionResult ShowModerated() => this.Ok(new ResourceList()); - private static readonly bool emailEnforcementEnabled = EnforceEmailConfiguration.Instance.EnableEmailEnforcement; - [HttpPost("filterResources")] [HttpPost("showNotUploaded")] public async Task FilterResources() @@ -49,12 +37,6 @@ public async Task FilterResources() [HttpGet("r/{hash}")] public async Task GetResource(string hash) { - GameTokenEntity token = this.GetToken(); - UserEntity? user = await this.database.UserFromGameToken(token); - - // Return bad request on unverified email if enforcement is enabled - if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); - string path = FileHelper.GetResourcePath(hash); string fullPath = Path.GetFullPath(path); @@ -71,12 +53,6 @@ public async Task GetResource(string hash) [HttpPost("upload/{hash}")] public async Task UploadResource(string hash) { - GameTokenEntity token = this.GetToken(); - UserEntity? user = await this.database.UserFromGameToken(token); - - // Return bad request on unverified email if enforcement is enabled - if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); - string assetsDirectory = FileHelper.ResourcePath; string path = FileHelper.GetResourcePath(hash); string fullPath = Path.GetFullPath(path); diff --git a/ProjectLighthouse.Servers.GameServer/Middlewares/EmailEnforcementMiddleware.cs b/ProjectLighthouse.Servers.GameServer/Middlewares/EmailEnforcementMiddleware.cs new file mode 100644 index 000000000..c4cf8e8ee --- /dev/null +++ b/ProjectLighthouse.Servers.GameServer/Middlewares/EmailEnforcementMiddleware.cs @@ -0,0 +1,89 @@ +using LBPUnion.ProjectLighthouse.Configuration; +using LBPUnion.ProjectLighthouse.Database; +using LBPUnion.ProjectLighthouse.Middlewares; +using LBPUnion.ProjectLighthouse.Types.Entities.Profile; +using LBPUnion.ProjectLighthouse.Types.Entities.Token; + +namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Middlewares; + +public class EmailEnforcementMiddleware : MiddlewareDBContext +{ + private static readonly HashSet enforcedPaths = new() + { + "rateUserComment", + "rateComment", + "comments", + "userComments", + "postUserComment", + "postComment", + "deleteUserComment", + "deleteComment", + "slots", + "upload", + "r", + "uploadPhoto", + "photos", + "deletePhoto", + "match", + "play", + "enterLevel", + "user", + "users", + "updateUser", + "update_my_pins", + "startPublish", + "publish", + "unpublish", + "playlists", + "tags", + "tag", + "searches", + "genres", + }; + + public EmailEnforcementMiddleware(RequestDelegate next) : base(next) + { } + + public override async Task InvokeAsync(HttpContext context, DatabaseContext database) + { + // Split path into segments + string[] pathSegments = context.Request.Path.ToString().Split("/"); + bool emailEnforcementEnabled = EnforceEmailConfiguration.Instance.EnableEmailEnforcement; + + if (pathSegments[0] == "LITTLEBIGPLANETPS3_XML") + { + // Get user via GameToken + GameTokenEntity? token = await database.GameTokenFromRequest(context.Request); + UserEntity? user = await database.UserFromGameToken(token); + + // Check second part of path to see if client is within an enforced path + if (enforcedPaths.Contains(pathSegments[1])) + { + // Check if user is valid + if (user == null) + { + // Send bad request status + context.Response.StatusCode = StatusCodes.Status400BadRequest; + await context.Response.WriteAsync("Not a valid user"); + + // Don't go to next in pipeline + return; + } + + // Check if email is there and verified + if (emailEnforcementEnabled && (!user.EmailAddressVerified || user.EmailAddress == null)) + { + // Send bad request status + context.Response.StatusCode = StatusCodes.Status400BadRequest; + await context.Response.WriteAsync("Invalid user email address"); + + // Don't go to next in pipeline + return; + } + } + } + + // Go to next in pipeline + await this.next(context); + } +} \ No newline at end of file diff --git a/ProjectLighthouse.Servers.GameServer/Startup/GameServerStartup.cs b/ProjectLighthouse.Servers.GameServer/Startup/GameServerStartup.cs index 0d372e8f1..8b91a3e60 100644 --- a/ProjectLighthouse.Servers.GameServer/Startup/GameServerStartup.cs +++ b/ProjectLighthouse.Servers.GameServer/Startup/GameServerStartup.cs @@ -107,6 +107,7 @@ public virtual void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseMiddleware(); app.UseMiddleware(computeDigests); app.UseMiddleware(); + app.UseMiddleware(); app.UseRouting(); diff --git a/ProjectLighthouse/Configuration/EnforceEmailConfiguration.cs b/ProjectLighthouse/Configuration/EnforceEmailConfiguration.cs index b044df994..1b7477633 100644 --- a/ProjectLighthouse/Configuration/EnforceEmailConfiguration.cs +++ b/ProjectLighthouse/Configuration/EnforceEmailConfiguration.cs @@ -18,7 +18,10 @@ public class EnforceEmailConfiguration : ConfigurationBase Date: Sun, 16 Feb 2025 19:56:19 -0500 Subject: [PATCH 10/22] Oops missed one --- .../Middlewares/EmailEnforcementMiddleware.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/ProjectLighthouse.Servers.GameServer/Middlewares/EmailEnforcementMiddleware.cs b/ProjectLighthouse.Servers.GameServer/Middlewares/EmailEnforcementMiddleware.cs index c4cf8e8ee..5fead35bd 100644 --- a/ProjectLighthouse.Servers.GameServer/Middlewares/EmailEnforcementMiddleware.cs +++ b/ProjectLighthouse.Servers.GameServer/Middlewares/EmailEnforcementMiddleware.cs @@ -10,6 +10,7 @@ public class EmailEnforcementMiddleware : MiddlewareDBContext { private static readonly HashSet enforcedPaths = new() { + "showModerated", "rateUserComment", "rateComment", "comments", From 97329369ce94923d0f34b5bd92d54ef1acdc082d Mon Sep 17 00:00:00 2001 From: FeTetra Date: Sun, 16 Feb 2025 20:52:21 -0500 Subject: [PATCH 11/22] Missed another --- .../Middlewares/EmailEnforcementMiddleware.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/ProjectLighthouse.Servers.GameServer/Middlewares/EmailEnforcementMiddleware.cs b/ProjectLighthouse.Servers.GameServer/Middlewares/EmailEnforcementMiddleware.cs index 5fead35bd..ae605d7c6 100644 --- a/ProjectLighthouse.Servers.GameServer/Middlewares/EmailEnforcementMiddleware.cs +++ b/ProjectLighthouse.Servers.GameServer/Middlewares/EmailEnforcementMiddleware.cs @@ -22,6 +22,7 @@ public class EmailEnforcementMiddleware : MiddlewareDBContext "slots", "upload", "r", + "s", "uploadPhoto", "photos", "deletePhoto", From 64e21aba9fb8cd4872e40877ec735b74f7e013c4 Mon Sep 17 00:00:00 2001 From: FeTetra Date: Sun, 16 Feb 2025 21:31:08 -0500 Subject: [PATCH 12/22] Remove empty array items after split --- .../Middlewares/EmailEnforcementMiddleware.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ProjectLighthouse.Servers.GameServer/Middlewares/EmailEnforcementMiddleware.cs b/ProjectLighthouse.Servers.GameServer/Middlewares/EmailEnforcementMiddleware.cs index ae605d7c6..d0792352a 100644 --- a/ProjectLighthouse.Servers.GameServer/Middlewares/EmailEnforcementMiddleware.cs +++ b/ProjectLighthouse.Servers.GameServer/Middlewares/EmailEnforcementMiddleware.cs @@ -3,6 +3,7 @@ using LBPUnion.ProjectLighthouse.Middlewares; using LBPUnion.ProjectLighthouse.Types.Entities.Profile; using LBPUnion.ProjectLighthouse.Types.Entities.Token; +using LBPUnion.ProjectLighthouse.Types.Logging; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Middlewares; @@ -49,7 +50,7 @@ public EmailEnforcementMiddleware(RequestDelegate next) : base(next) public override async Task InvokeAsync(HttpContext context, DatabaseContext database) { // Split path into segments - string[] pathSegments = context.Request.Path.ToString().Split("/"); + string[] pathSegments = context.Request.Path.ToString().Split("/", StringSplitOptions.RemoveEmptyEntries); bool emailEnforcementEnabled = EnforceEmailConfiguration.Instance.EnableEmailEnforcement; if (pathSegments[0] == "LITTLEBIGPLANETPS3_XML") From 0c8ca731a4c8507aa54fe459de1da13555ce5535 Mon Sep 17 00:00:00 2001 From: FeTetra Date: Sun, 16 Feb 2025 21:51:56 -0500 Subject: [PATCH 13/22] Slight refactor and polish --- .../Controllers/MessageController.cs | 10 +- .../Controllers/UserController.cs | 2 +- .../Middlewares/EmailEnforcementMiddleware.cs | 92 +++++++------------ .../EmailEnforcementConfiguration.cs | 89 ++++++++++++++++++ .../EnforceEmailConfiguration.cs | 45 --------- ProjectLighthouse/Helpers/EmailHelper.cs | 6 +- 6 files changed, 128 insertions(+), 116 deletions(-) create mode 100644 ProjectLighthouse/Configuration/EmailEnforcementConfiguration.cs delete mode 100644 ProjectLighthouse/Configuration/EnforceEmailConfiguration.cs diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs index 99bc79299..6cf01e9dd 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs @@ -67,19 +67,17 @@ public async Task Announce() announceText.Insert(0, BaseLayoutStrings.ReadOnlyWarn.Translate(LocalizationManager.DefaultLang) + "\n\n"); } - if (EnforceEmailConfiguration.Instance.EnableEmailEnforcement) + if (EmailEnforcementConfiguration.Instance.EnableEmailEnforcement) { - announceText.Insert(0, EnforceEmailConfiguration.Instance.EmailEnforcementMessageMain); + announceText.Insert(0, EmailEnforcementConfiguration.Instance.EmailEnforcementMessageMain); if (user.EmailAddress == null) { - // This will probably need translations - announceText.Insert(0, EnforceEmailConfiguration.Instance.EmailEnforcementMessageNoEmail); + announceText.Insert(0, EmailEnforcementConfiguration.Instance.EmailEnforcementMessageNoEmail); } else if (!user.EmailAddressVerified) { - // More stuff to translate later - announceText.Insert(0, EnforceEmailConfiguration.Instance.EmailEnforcementMessageVerify); + announceText.Insert(0, EmailEnforcementConfiguration.Instance.EmailEnforcementMessageVerify); } } diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs index ee537228f..6c832f632 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs @@ -29,7 +29,7 @@ public class UserController : ControllerBase { private readonly DatabaseContext database; - private static readonly bool emailEnforcementEnabled = EnforceEmailConfiguration.Instance.EnableEmailEnforcement; + private static readonly bool emailEnforcementEnabled = EmailEnforcementConfiguration.Instance.EnableEmailEnforcement; public UserController(DatabaseContext database) { diff --git a/ProjectLighthouse.Servers.GameServer/Middlewares/EmailEnforcementMiddleware.cs b/ProjectLighthouse.Servers.GameServer/Middlewares/EmailEnforcementMiddleware.cs index d0792352a..0fbce0be1 100644 --- a/ProjectLighthouse.Servers.GameServer/Middlewares/EmailEnforcementMiddleware.cs +++ b/ProjectLighthouse.Servers.GameServer/Middlewares/EmailEnforcementMiddleware.cs @@ -3,46 +3,12 @@ using LBPUnion.ProjectLighthouse.Middlewares; using LBPUnion.ProjectLighthouse.Types.Entities.Profile; using LBPUnion.ProjectLighthouse.Types.Entities.Token; -using LBPUnion.ProjectLighthouse.Types.Logging; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Middlewares; public class EmailEnforcementMiddleware : MiddlewareDBContext { - private static readonly HashSet enforcedPaths = new() - { - "showModerated", - "rateUserComment", - "rateComment", - "comments", - "userComments", - "postUserComment", - "postComment", - "deleteUserComment", - "deleteComment", - "slots", - "upload", - "r", - "s", - "uploadPhoto", - "photos", - "deletePhoto", - "match", - "play", - "enterLevel", - "user", - "users", - "updateUser", - "update_my_pins", - "startPublish", - "publish", - "unpublish", - "playlists", - "tags", - "tag", - "searches", - "genres", - }; + private static readonly HashSet enforcedPaths = EmailEnforcementConfiguration.Instance.BlockedEndpoints; public EmailEnforcementMiddleware(RequestDelegate next) : base(next) { } @@ -51,37 +17,41 @@ public override async Task InvokeAsync(HttpContext context, DatabaseContext data { // Split path into segments string[] pathSegments = context.Request.Path.ToString().Split("/", StringSplitOptions.RemoveEmptyEntries); - bool emailEnforcementEnabled = EnforceEmailConfiguration.Instance.EnableEmailEnforcement; if (pathSegments[0] == "LITTLEBIGPLANETPS3_XML") { - // Get user via GameToken - GameTokenEntity? token = await database.GameTokenFromRequest(context.Request); - UserEntity? user = await database.UserFromGameToken(token); - - // Check second part of path to see if client is within an enforced path - if (enforcedPaths.Contains(pathSegments[1])) + if (EmailEnforcementConfiguration.Instance.EnableEmailEnforcement) { - // Check if user is valid - if (user == null) - { - // Send bad request status - context.Response.StatusCode = StatusCodes.Status400BadRequest; - await context.Response.WriteAsync("Not a valid user"); - - // Don't go to next in pipeline - return; - } - - // Check if email is there and verified - if (emailEnforcementEnabled && (!user.EmailAddressVerified || user.EmailAddress == null)) + // Get user via GameToken + GameTokenEntity? token = await database.GameTokenFromRequest(context.Request); + UserEntity? user = await database.UserFromGameToken(token); + + // Check second part of path to see if client is within an enforced path + // This could probably be reworked, seeing as you may want to check for a deeper sub-path + // But it should be perfectly fine for now + if (enforcedPaths.Contains(pathSegments[1])) { - // Send bad request status - context.Response.StatusCode = StatusCodes.Status400BadRequest; - await context.Response.WriteAsync("Invalid user email address"); - - // Don't go to next in pipeline - return; + // Check if user is valid, don't want any exceptions + if (user == null) + { + // Send bad request status + context.Response.StatusCode = StatusCodes.Status400BadRequest; + await context.Response.WriteAsync("Not a valid user"); + + // Don't go to next in pipeline + return; + } + + // Check if email is there and verified + if (!user.EmailAddressVerified || user.EmailAddress == null) + { + // Send bad request status + context.Response.StatusCode = StatusCodes.Status400BadRequest; + await context.Response.WriteAsync("Invalid user email address"); + + // Don't go to next in pipeline + return; + } } } } diff --git a/ProjectLighthouse/Configuration/EmailEnforcementConfiguration.cs b/ProjectLighthouse/Configuration/EmailEnforcementConfiguration.cs new file mode 100644 index 000000000..eb2ec25f2 --- /dev/null +++ b/ProjectLighthouse/Configuration/EmailEnforcementConfiguration.cs @@ -0,0 +1,89 @@ +#nullable enable +using System.Collections.Generic; +using System.IO; +using YamlDotNet.Serialization; + +namespace LBPUnion.ProjectLighthouse.Configuration; + +public class EmailEnforcementConfiguration : ConfigurationBase +{ + public override int ConfigVersion { get; set; } = 2; + + public override string ConfigName { get; set; } = "enforce-email.yml"; + + public override bool NeedsConfiguration { get; set; } = false; + + public bool EnableEmailEnforcement { get; set; } = false; + public bool EnableEmailBlacklist { get; set; } = false; + + // No blacklist by default, add path to blacklist + public string BlacklistFilePath { get; set; } = ""; + + // Endpoints to be blocked + // This is kind of a random list so some may need to be added or removed + public HashSet BlockedEndpoints { get; set; } = new() + { + // Comments + "rateUserComment", + "rateComment", + "comments", + "userComments", + "postUserComment", + "postComment", + "deleteUserComment", + "deleteComment", + + // Slots + "showModerated", + "startPublish", + "slots", + "s", + "tags", + "tag", + "searches", + "genres", + "publish", + "unpublish", + + // Misc Resources + "upload", + "r", + + // Photos + "uploadPhoto", + "photos", + "deletePhoto", + + // Gameplay + "match", + "play", + "enterLevel", + "playlists", + + // Users + "user", + "users", + "updateUser", + "update_my_pins", + }; + + public string EmailEnforcementMessageMain { get; set; } = + "This lighthouse instance has email enforcement enabled. " + + "If you haven't already, you will need to set and verify " + + "an email address to use most features.\\n"; + + public string EmailEnforcementMessageNoEmail { get; set; } = + "You do not have an email set on your account. You can set " + + "an email by opening the text chat and typing \"/setemail " + + "[youremail@example.com]\" (do not include the brackets.)\\n\\n"; + + public string EmailEnforcementMessageVerify { get; set; } = + "You have set an email address on your account, but you have not " + + "verified it. Make sure to check your inbox for a verification email. " + + "If you have not received an email, please contact an instance " + + "administrator for further assistance.\\n\\n"; + + public override ConfigurationBase Deserialize + (IDeserializer deserializer, string text) => + deserializer.Deserialize(text); +} \ No newline at end of file diff --git a/ProjectLighthouse/Configuration/EnforceEmailConfiguration.cs b/ProjectLighthouse/Configuration/EnforceEmailConfiguration.cs deleted file mode 100644 index 1b7477633..000000000 --- a/ProjectLighthouse/Configuration/EnforceEmailConfiguration.cs +++ /dev/null @@ -1,45 +0,0 @@ -#nullable enable -using System.Collections.Generic; -using System.IO; -using YamlDotNet.Serialization; - -namespace LBPUnion.ProjectLighthouse.Configuration; - -public class EnforceEmailConfiguration : ConfigurationBase -{ - public override int ConfigVersion { get; set; } = 2; - - public override string ConfigName { get; set; } = "enforce-email.yml"; - - public override bool NeedsConfiguration { get; set; } = false; - - public bool EnableEmailEnforcement { get; set; } = false; - public bool EnableEmailBlacklist { get; set; } = false; - - // No blacklist by default, add path to blacklist - public string BlacklistFilePath { get; set; } = ""; - - public string DomainWhitelist { get; set; } = ""; - - // TODO: Finalize what to do with these - // Keep warnings here for debug until pull - public string EmailEnforcementMessageMain { get; set; } = - "This lighthouse instance has email enforcement enabled. " + - "If you haven't already, you will need to set and verify " + - "an email address to use most features.\n"; - - public string EmailEnforcementMessageNoEmail { get; set; } = - "You do not have an email set on your account. You can set " + - "an email by opening the text chat and typing \"/setemail " + - "[youremail@example.com]\" (do not include the brackets.)\n\n"; - - public string EmailEnforcementMessageVerify { get; set; } = - "You have set an email address on your account, but you have not " + - "verified it. Make sure to check your inbox for a verification email. " + - "If you have not received an email, please contact an instance " + - "administrator for further assistance.\n\n"; - - public override ConfigurationBase Deserialize - (IDeserializer deserializer, string text) => - deserializer.Deserialize(text); -} \ No newline at end of file diff --git a/ProjectLighthouse/Helpers/EmailHelper.cs b/ProjectLighthouse/Helpers/EmailHelper.cs index 63b7ea21a..606b682b0 100644 --- a/ProjectLighthouse/Helpers/EmailHelper.cs +++ b/ProjectLighthouse/Helpers/EmailHelper.cs @@ -27,8 +27,8 @@ public static class SMTPHelper // To prevent ReadAllLines() exception when BlacklistFilePath is empty private static readonly string[] blacklistFile = - !string.IsNullOrWhiteSpace(EnforceEmailConfiguration.Instance.BlacklistFilePath) - ? File.ReadAllLines(EnforceEmailConfiguration.Instance.BlacklistFilePath) : []; + !string.IsNullOrWhiteSpace(EmailEnforcementConfiguration.Instance.BlacklistFilePath) + ? File.ReadAllLines(EmailEnforcementConfiguration.Instance.BlacklistFilePath) : []; private static readonly HashSet blacklistedDomains = new(blacklistFile); @@ -87,7 +87,7 @@ public static bool IsValidEmail(DatabaseContext database, string email) if (!string.IsNullOrWhiteSpace(email) && emailValidator.IsValid(email) && !EmailIsUsed(database, email).Result) { // Don't even bother if there are no domains in blacklist (AKA file path is empty/invalid, or file itself is empty) - if (EnforceEmailConfiguration.Instance.EnableEmailBlacklist && blacklistedDomains.Count > 0) + if (EmailEnforcementConfiguration.Instance.EnableEmailBlacklist && blacklistedDomains.Count > 0) { // Get domain by splitting at '@' character string domain = email.Split('@')[1]; From 479b1da903dfe420a4899ce9fa2fd59effda2921 Mon Sep 17 00:00:00 2001 From: FeTetra Date: Sun, 16 Feb 2025 22:08:32 -0500 Subject: [PATCH 14/22] Fix string formatting on announce text --- .../Controllers/MessageController.cs | 8 ++++---- .../Configuration/EmailEnforcementConfiguration.cs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs index 6cf01e9dd..1ab1830cf 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs @@ -64,20 +64,20 @@ public async Task Announce() if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) { - announceText.Insert(0, BaseLayoutStrings.ReadOnlyWarn.Translate(LocalizationManager.DefaultLang) + "\n\n"); + announceText.Append(BaseLayoutStrings.ReadOnlyWarn.Translate(LocalizationManager.DefaultLang) + "\n\n"); } if (EmailEnforcementConfiguration.Instance.EnableEmailEnforcement) { - announceText.Insert(0, EmailEnforcementConfiguration.Instance.EmailEnforcementMessageMain); + announceText.Append(EmailEnforcementConfiguration.Instance.EmailEnforcementMessageMain + "\n\n"); if (user.EmailAddress == null) { - announceText.Insert(0, EmailEnforcementConfiguration.Instance.EmailEnforcementMessageNoEmail); + announceText.Append(EmailEnforcementConfiguration.Instance.EmailEnforcementMessageNoEmail + "\n\n"); } else if (!user.EmailAddressVerified) { - announceText.Insert(0, EmailEnforcementConfiguration.Instance.EmailEnforcementMessageVerify); + announceText.Append(EmailEnforcementConfiguration.Instance.EmailEnforcementMessageVerify + "\n\n"); } } diff --git a/ProjectLighthouse/Configuration/EmailEnforcementConfiguration.cs b/ProjectLighthouse/Configuration/EmailEnforcementConfiguration.cs index eb2ec25f2..9f9e2d9c7 100644 --- a/ProjectLighthouse/Configuration/EmailEnforcementConfiguration.cs +++ b/ProjectLighthouse/Configuration/EmailEnforcementConfiguration.cs @@ -7,7 +7,7 @@ namespace LBPUnion.ProjectLighthouse.Configuration; public class EmailEnforcementConfiguration : ConfigurationBase { - public override int ConfigVersion { get; set; } = 2; + public override int ConfigVersion { get; set; } = 3; public override string ConfigName { get; set; } = "enforce-email.yml"; @@ -70,18 +70,18 @@ public class EmailEnforcementConfiguration : ConfigurationBase Deserialize (IDeserializer deserializer, string text) => From 3041d2c0fa4c60e4763941d6f2f1bc15d7513503 Mon Sep 17 00:00:00 2001 From: FeTetra Date: Sun, 16 Feb 2025 22:41:57 -0500 Subject: [PATCH 15/22] Replace configuration strings with translatable strings --- ProjectLighthouse.Localization/BaseLayout.resx | 9 +++++++++ .../Controllers/MessageController.cs | 8 ++++---- .../EmailEnforcementConfiguration.cs | 18 +----------------- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/ProjectLighthouse.Localization/BaseLayout.resx b/ProjectLighthouse.Localization/BaseLayout.resx index 65d1229f7..5a44aaead 100644 --- a/ProjectLighthouse.Localization/BaseLayout.resx +++ b/ProjectLighthouse.Localization/BaseLayout.resx @@ -93,4 +93,13 @@ This instance is currently in read-only mode. Level and photo uploads, comments, reviews, and certain profile changes will be restricted until read-only mode is disabled. + + This instance has email enforcement enabled. If you haven't already, you will need to set and verify an email address to use most features. + + + You do not have an email set on your account. You can set an email by opening the text chat and typing "/setemail [youremail@example.com]" (do not include the brackets.) + + + You have set an email address on your account, but you have not verified it. Make sure to check your inbox for a verification email. If you have not recieved an email, please contact an instance administrator for further assistance. + \ No newline at end of file diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs index 1ab1830cf..877d6be5c 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs @@ -57,7 +57,7 @@ public async Task Announce() UserEntity? user = await this.database.UserFromGameToken(token); - StringBuilder announceText = new(ServerConfiguration.Instance.AnnounceText); + StringBuilder announceText = new(ServerConfiguration.Instance.AnnounceText + "\n\n"); announceText.Replace("%user", user.Username); announceText.Replace("%id", token.UserId.ToString()); @@ -69,15 +69,15 @@ public async Task Announce() if (EmailEnforcementConfiguration.Instance.EnableEmailEnforcement) { - announceText.Append(EmailEnforcementConfiguration.Instance.EmailEnforcementMessageMain + "\n\n"); + announceText.Append(BaseLayoutStrings.EmailEnforcementWarnMain.Translate(LocalizationManager.DefaultLang) + "\n\n"); if (user.EmailAddress == null) { - announceText.Append(EmailEnforcementConfiguration.Instance.EmailEnforcementMessageNoEmail + "\n\n"); + announceText.Append(BaseLayoutStrings.EmailEnforcementWarnNoEmail.Translate(LocalizationManager.DefaultLang) + "\n\n"); } else if (!user.EmailAddressVerified) { - announceText.Append(EmailEnforcementConfiguration.Instance.EmailEnforcementMessageVerify + "\n\n"); + announceText.Append(BaseLayoutStrings.EmailEnforcementWarnVerifyEmail.Translate(LocalizationManager.DefaultLang) + "\n\n"); } } diff --git a/ProjectLighthouse/Configuration/EmailEnforcementConfiguration.cs b/ProjectLighthouse/Configuration/EmailEnforcementConfiguration.cs index 9f9e2d9c7..c6697df1f 100644 --- a/ProjectLighthouse/Configuration/EmailEnforcementConfiguration.cs +++ b/ProjectLighthouse/Configuration/EmailEnforcementConfiguration.cs @@ -7,7 +7,7 @@ namespace LBPUnion.ProjectLighthouse.Configuration; public class EmailEnforcementConfiguration : ConfigurationBase { - public override int ConfigVersion { get; set; } = 3; + public override int ConfigVersion { get; set; } = 4; public override string ConfigName { get; set; } = "enforce-email.yml"; @@ -67,22 +67,6 @@ public class EmailEnforcementConfiguration : ConfigurationBase Deserialize (IDeserializer deserializer, string text) => deserializer.Deserialize(text); From 3fb598ec44b95691573751c3fa00ba716dedd84d Mon Sep 17 00:00:00 2001 From: FeTetra Date: Sun, 16 Feb 2025 22:58:52 -0500 Subject: [PATCH 16/22] Edit invalid email message --- ProjectLighthouse.Localization/Error.resx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectLighthouse.Localization/Error.resx b/ProjectLighthouse.Localization/Error.resx index 1692f130f..826b8fc77 100644 --- a/ProjectLighthouse.Localization/Error.resx +++ b/ProjectLighthouse.Localization/Error.resx @@ -37,7 +37,7 @@ The email address you've chosen is already taken. - Email address field is required. + The email address you've chosen is invalid. You don't have permissions to perform this action. From 048f60cd9d7929670f76b00f331560aabaa7772f Mon Sep 17 00:00:00 2001 From: FeTetra Date: Mon, 17 Feb 2025 08:23:00 -0500 Subject: [PATCH 17/22] Remove remaining code from before middleware --- .../Controllers/CommentController.cs | 9 +++------ .../Controllers/Matching/EnterLevelController.cs | 3 --- .../Controllers/Matching/MatchController.cs | 1 + .../Controllers/Resources/PhotosController.cs | 2 -- .../Controllers/Slots/CategoryController.cs | 6 ------ .../Controllers/Slots/PublishController.cs | 2 +- .../Controllers/UserController.cs | 15 ++------------- 7 files changed, 7 insertions(+), 31 deletions(-) diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs index 22ce63d5a..d1886d7f3 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs @@ -32,8 +32,6 @@ public CommentController(DatabaseContext database) public async Task RateComment([FromQuery] int commentId, [FromQuery] int rating, string? username, string? slotType, int slotId) { GameTokenEntity token = this.GetToken(); - UserEntity? user = await this.database.UserFromGameToken(token); - if (user == null) return this.Unauthorized(); // Return bad request if both are true or both are false if ((slotId == 0 || SlotHelper.IsTypeInvalid(slotType)) == (username == null)) return this.BadRequest(); @@ -49,8 +47,9 @@ public async Task RateComment([FromQuery] int commentId, [FromQue public async Task GetComments(string? username, string? slotType, int slotId) { GameTokenEntity token = this.GetToken(); - UserEntity? user = await this.database.UserFromGameToken(token); - if (user == null) return this.Unauthorized(); + + UserEntity? user = await this.database.UserFromGameToken(token); + if (user == null) return this.Forbid(); if ((slotId == 0 || SlotHelper.IsTypeInvalid(slotType)) == (username == null)) return this.BadRequest(); @@ -116,8 +115,6 @@ from blockedProfile in this.database.BlockedProfiles public async Task PostComment(string? username, string? slotType, int slotId) { GameTokenEntity token = this.GetToken(); - UserEntity? user = await this.database.UserFromGameToken(token); - if (user == null) return this.Unauthorized(); // Deny request if in read-only mode if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest(); diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Matching/EnterLevelController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Matching/EnterLevelController.cs index aeebd756e..428580b8d 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Matching/EnterLevelController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Matching/EnterLevelController.cs @@ -31,9 +31,6 @@ public async Task PlayLevel(string slotType, int slotId) { GameTokenEntity token = this.GetToken(); - UserEntity? user = await this.database.UserFromGameToken(token); - if (user == null) return this.Unauthorized(); - if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest(); // don't count plays for developer slots diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Matching/MatchController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Matching/MatchController.cs index fe210efdd..ffba0d6a4 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Matching/MatchController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Matching/MatchController.cs @@ -39,6 +39,7 @@ public MatchController(DatabaseContext database) public async Task Match() { GameTokenEntity token = this.GetToken(); + UserEntity? user = await this.database.UserFromGameToken(token); if (user == null) return this.Unauthorized(); diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Resources/PhotosController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Resources/PhotosController.cs index 2acc3dcb4..4b13f252f 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Resources/PhotosController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Resources/PhotosController.cs @@ -36,8 +36,6 @@ public PhotosController(DatabaseContext database) public async Task UploadPhoto() { GameTokenEntity token = this.GetToken(); - UserEntity? user = await this.database.UserFromGameToken(token); - if (user == null) return this.Unauthorized(); // Deny request if in read-only mode if (ServerConfiguration.Instance.UserGeneratedContentLimits.ReadOnlyMode) return this.BadRequest(); diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/CategoryController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/CategoryController.cs index 60ac1c580..214bc82f0 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/CategoryController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/CategoryController.cs @@ -38,9 +38,6 @@ public async Task GenresAndSearches() { GameTokenEntity token = this.GetToken(); - UserEntity? user = await this.database.UserFromGameToken(token); - if (user == null) return this.Forbid(); - PaginationData pageData = this.Request.GetPaginationData(); pageData.TotalElements = CategoryHelper.Categories.Count(c => !string.IsNullOrWhiteSpace(c.Name)); @@ -72,9 +69,6 @@ public async Task GetCategorySlots(string endpointName) { GameTokenEntity token = this.GetToken(); - UserEntity? user = await this.database.UserFromGameToken(token); - if (user == null) return this.Forbid(); - Category? category = CategoryHelper.Categories.FirstOrDefault(c => c.Endpoint == endpointName); if (category == null) return this.NotFound(); diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/PublishController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/PublishController.cs index dfb42c710..aee40fa8c 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/PublishController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/PublishController.cs @@ -40,7 +40,7 @@ public PublishController(DatabaseContext database) public async Task StartPublish() { GameTokenEntity token = this.GetToken(); - + UserEntity? user = await this.database.UserFromGameToken(token); if (user == null) return this.Forbid(); diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs index 6c832f632..2b32dc326 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs @@ -39,12 +39,6 @@ public UserController(DatabaseContext database) [HttpGet("user/{username}")] public async Task GetUser(string username) { - GameTokenEntity token = this.GetToken(); - UserEntity? user = await this.database.UserFromGameToken(token); - - // Return bad request on unverified email if enforcement is enabled - if (emailEnforcementEnabled && !token.User.EmailAddressVerified || user == null) return this.BadRequest(); - UserEntity? targetUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); if (targetUser == null) return this.NotFound(); @@ -74,11 +68,9 @@ public async Task GetUserAlt([FromQuery(Name = "u")] string[] use public async Task UpdateUser() { GameTokenEntity token = this.GetToken(); - UserEntity? user = await this.database.UserFromGameToken(token); - if (user == null) return this.Forbid(); - // Return bad request on unverified email if enforcement is enabled - if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); + UserEntity? user = await this.database.UserFromGameToken(token); + if (user == null) return this.Forbid(); UserUpdate? update = await this.DeserializeBody("updateUser", "user"); @@ -186,9 +178,6 @@ public async Task UpdateMyPins() UserEntity? user = await this.database.UserFromGameToken(this.GetToken()); if (user == null) return this.Forbid(); - // Return bad request on unverified email if enforcement is enabled - if (emailEnforcementEnabled && !user.EmailAddressVerified) return this.BadRequest(); - string bodyString = await this.ReadBodyAsync(); Pins? pinJson = JsonSerializer.Deserialize(bodyString); From f77e5eeee239ac5f3943c15ad661bea554ad634f Mon Sep 17 00:00:00 2001 From: FeTetra Date: Mon, 17 Feb 2025 09:39:34 -0500 Subject: [PATCH 18/22] Revert translation change --- ProjectLighthouse.Localization/Error.resx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectLighthouse.Localization/Error.resx b/ProjectLighthouse.Localization/Error.resx index 826b8fc77..1692f130f 100644 --- a/ProjectLighthouse.Localization/Error.resx +++ b/ProjectLighthouse.Localization/Error.resx @@ -37,7 +37,7 @@ The email address you've chosen is already taken. - The email address you've chosen is invalid. + Email address field is required. You don't have permissions to perform this action. From 3fb1441ac1179a508b3aacb94cef83771b91ecbc Mon Sep 17 00:00:00 2001 From: FeTetra Date: Mon, 17 Feb 2025 22:06:23 -0500 Subject: [PATCH 19/22] Small fixup and fix unit test conflicts --- .../Controllers/MessageController.cs | 6 +++--- .../Middlewares/EmailEnforcementMiddleware.cs | 12 ++++++------ .../Unit/Controllers/MessageControllerTests.cs | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs index 877d6be5c..7a82deca6 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs @@ -57,7 +57,7 @@ public async Task Announce() UserEntity? user = await this.database.UserFromGameToken(token); - StringBuilder announceText = new(ServerConfiguration.Instance.AnnounceText + "\n\n"); + StringBuilder announceText = new(ServerConfiguration.Instance.AnnounceText); announceText.Replace("%user", user.Username); announceText.Replace("%id", token.UserId.ToString()); @@ -69,7 +69,7 @@ public async Task Announce() if (EmailEnforcementConfiguration.Instance.EnableEmailEnforcement) { - announceText.Append(BaseLayoutStrings.EmailEnforcementWarnMain.Translate(LocalizationManager.DefaultLang) + "\n\n"); + announceText.Append("\n\n" + BaseLayoutStrings.EmailEnforcementWarnMain.Translate(LocalizationManager.DefaultLang) + "\n\n"); if (user.EmailAddress == null) { @@ -148,7 +148,7 @@ public async Task Filter(IMailService mailService) if (!SMTPHelper.IsValidEmail(this.database, email)) return this.BadRequest(); UserEntity? user = await this.database.UserFromGameToken(token); - if (user == null || user.EmailAddressVerified) return this.Ok(); + if (user == null || user.EmailAddressVerified) return this.BadRequest(); user.EmailAddress = email; await SMTPHelper.SendVerificationEmail(this.database, mailService, user); diff --git a/ProjectLighthouse.Servers.GameServer/Middlewares/EmailEnforcementMiddleware.cs b/ProjectLighthouse.Servers.GameServer/Middlewares/EmailEnforcementMiddleware.cs index 0fbce0be1..b269c97ae 100644 --- a/ProjectLighthouse.Servers.GameServer/Middlewares/EmailEnforcementMiddleware.cs +++ b/ProjectLighthouse.Servers.GameServer/Middlewares/EmailEnforcementMiddleware.cs @@ -15,12 +15,12 @@ public EmailEnforcementMiddleware(RequestDelegate next) : base(next) public override async Task InvokeAsync(HttpContext context, DatabaseContext database) { - // Split path into segments - string[] pathSegments = context.Request.Path.ToString().Split("/", StringSplitOptions.RemoveEmptyEntries); + if (EmailEnforcementConfiguration.Instance.EnableEmailEnforcement) + { + // Split path into segments + string[] pathSegments = context.Request.Path.ToString().Split("/", StringSplitOptions.RemoveEmptyEntries); - if (pathSegments[0] == "LITTLEBIGPLANETPS3_XML") - { - if (EmailEnforcementConfiguration.Instance.EnableEmailEnforcement) + if (pathSegments[0] == "LITTLEBIGPLANETPS3_XML") { // Get user via GameToken GameTokenEntity? token = await database.GameTokenFromRequest(context.Request); @@ -35,7 +35,7 @@ public override async Task InvokeAsync(HttpContext context, DatabaseContext data if (user == null) { // Send bad request status - context.Response.StatusCode = StatusCodes.Status400BadRequest; + context.Response.StatusCode = StatusCodes.Status403Forbidden; await context.Response.WriteAsync("Not a valid user"); // Don't go to next in pipeline diff --git a/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/MessageControllerTests.cs b/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/MessageControllerTests.cs index a0b42a0af..acda5cbc8 100644 --- a/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/MessageControllerTests.cs +++ b/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/MessageControllerTests.cs @@ -224,7 +224,7 @@ public async Task Filter_ShouldNotSendEmail_WhenMailEnabled_AndEmailTaken() IActionResult result = await messageController.Filter(mailMock.Object); - Assert.IsType(result); + Assert.IsType(result); mailMock.Verify(x => x.SendEmailAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } @@ -249,7 +249,7 @@ public async Task Filter_ShouldNotSendEmail_WhenMailEnabled_AndEmailAlreadyVerif IActionResult result = await messageController.Filter(mailMock.Object); - Assert.IsType(result); + Assert.IsType(result); mailMock.Verify(x => x.SendEmailAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } @@ -271,7 +271,7 @@ public async Task Filter_ShouldNotSendEmail_WhenMailEnabled_AndEmailFormatInvali IActionResult result = await messageController.Filter(mailMock.Object); - Assert.IsType(result); + Assert.IsType(result); mailMock.Verify(x => x.SendEmailAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } } \ No newline at end of file From 40a4d5e2397ede19c47849d5240ffee7bec5cbdc Mon Sep 17 00:00:00 2001 From: FeTetra Date: Thu, 20 Feb 2025 21:12:34 -0500 Subject: [PATCH 20/22] Fix Koko suggestions --- .../Controllers/CommentController.cs | 3 +- .../Controllers/MessageController.cs | 3 +- .../Controllers/Resources/PhotosController.cs | 2 +- .../Resources/ResourcesController.cs | 8 ++-- .../Controllers/UserController.cs | 2 - .../Middlewares/EmailEnforcementMiddleware.cs | 4 +- .../Pages/UserSettingsPage.cshtml.cs | 4 +- .../EmailEnforcementConfiguration.cs | 9 ++-- .../Configuration/ServerConfiguration.cs | 4 +- ProjectLighthouse/Helpers/EmailHelper.cs | 43 +++++++++---------- 10 files changed, 40 insertions(+), 42 deletions(-) diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs index d1886d7f3..8f550acf6 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs @@ -97,7 +97,8 @@ from blockedProfile in this.database.BlockedProfiles .ApplyPagination(pageData) .ToListAsync()).ToSerializableList(c => GameComment.CreateFromEntity(c, token.UserId)); - if (type == CommentType.Level && slotType == "developer" && user.IsModerator && pageData.PageStart == 1) { + if (type == CommentType.Level && slotType == "developer" && user.IsModerator && pageData.PageStart == 1) + { comments.Insert(0, new GameComment { CommentId = 0, diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs index 7a82deca6..b6948dfd6 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/MessageController.cs @@ -56,6 +56,7 @@ public async Task Announce() GameTokenEntity token = this.GetToken(); UserEntity? user = await this.database.UserFromGameToken(token); + if (user == null) return this.BadRequest(); StringBuilder announceText = new(ServerConfiguration.Instance.AnnounceText); @@ -67,7 +68,7 @@ public async Task Announce() announceText.Append(BaseLayoutStrings.ReadOnlyWarn.Translate(LocalizationManager.DefaultLang) + "\n\n"); } - if (EmailEnforcementConfiguration.Instance.EnableEmailEnforcement) + if (ServerConfiguration.Instance.EmailEnforcement.EnableEmailEnforcement) { announceText.Append("\n\n" + BaseLayoutStrings.EmailEnforcementWarnMain.Translate(LocalizationManager.DefaultLang) + "\n\n"); diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Resources/PhotosController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Resources/PhotosController.cs index 4b13f252f..807980673 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Resources/PhotosController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Resources/PhotosController.cs @@ -216,7 +216,7 @@ public async Task UserPhotosBy(string user) [HttpGet("photos/with")] public async Task UserPhotosWith(string user) - { + { int targetUserId = await this.database.UserIdFromUsername(user); if (targetUserId == 0) return this.NotFound(); diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Resources/ResourcesController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Resources/ResourcesController.cs index 630be2c92..f48c7e7d0 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Resources/ResourcesController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Resources/ResourcesController.cs @@ -35,18 +35,18 @@ public async Task FilterResources() } [HttpGet("r/{hash}")] - public async Task GetResource(string hash) + public Task GetResource(string hash) { string path = FileHelper.GetResourcePath(hash); string fullPath = Path.GetFullPath(path); // Prevent directory traversal attacks - if (!fullPath.StartsWith(FileHelper.FullResourcePath)) return this.BadRequest(); + if (!fullPath.StartsWith(FileHelper.FullResourcePath)) return Task.FromResult(this.BadRequest()); - if (FileHelper.ResourceExists(hash)) return this.File(IOFile.OpenRead(path), "application/octet-stream"); + if (FileHelper.ResourceExists(hash)) return Task.FromResult(this.File(IOFile.OpenRead(path), "application/octet-stream")); - return this.NotFound(); + return Task.FromResult(this.NotFound()); } [HttpPost("upload/{hash}/unattributed")] diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs index 2b32dc326..ed0dbe12c 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs @@ -29,8 +29,6 @@ public class UserController : ControllerBase { private readonly DatabaseContext database; - private static readonly bool emailEnforcementEnabled = EmailEnforcementConfiguration.Instance.EnableEmailEnforcement; - public UserController(DatabaseContext database) { this.database = database; diff --git a/ProjectLighthouse.Servers.GameServer/Middlewares/EmailEnforcementMiddleware.cs b/ProjectLighthouse.Servers.GameServer/Middlewares/EmailEnforcementMiddleware.cs index b269c97ae..7482439c5 100644 --- a/ProjectLighthouse.Servers.GameServer/Middlewares/EmailEnforcementMiddleware.cs +++ b/ProjectLighthouse.Servers.GameServer/Middlewares/EmailEnforcementMiddleware.cs @@ -8,14 +8,14 @@ namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Middlewares; public class EmailEnforcementMiddleware : MiddlewareDBContext { - private static readonly HashSet enforcedPaths = EmailEnforcementConfiguration.Instance.BlockedEndpoints; + private static readonly HashSet enforcedPaths = ServerConfiguration.Instance.EmailEnforcement.BlockedEndpoints; public EmailEnforcementMiddleware(RequestDelegate next) : base(next) { } public override async Task InvokeAsync(HttpContext context, DatabaseContext database) { - if (EmailEnforcementConfiguration.Instance.EnableEmailEnforcement) + if (ServerConfiguration.Instance.EmailEnforcement.EnableEmailEnforcement) { // Split path into segments string[] pathSegments = context.Request.Path.ToString().Split("/", StringSplitOptions.RemoveEmptyEntries); diff --git a/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml.cs index d1a9c0a82..8904e294f 100644 --- a/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/UserSettingsPage.cshtml.cs @@ -64,8 +64,8 @@ [FromForm] string? language } } - if (ServerConfiguration.Instance.Mail.MailEnabled && - SMTPHelper.IsValidEmail(this.Database, email) && + if (ServerConfiguration.Instance.Mail.MailEnabled && + email != null && SMTPHelper.IsValidEmail(this.Database, email) && (this.User == this.ProfileUser || this.User.IsAdmin)) { if (this.ProfileUser.EmailAddress != email) diff --git a/ProjectLighthouse/Configuration/EmailEnforcementConfiguration.cs b/ProjectLighthouse/Configuration/EmailEnforcementConfiguration.cs index c6697df1f..56179d914 100644 --- a/ProjectLighthouse/Configuration/EmailEnforcementConfiguration.cs +++ b/ProjectLighthouse/Configuration/EmailEnforcementConfiguration.cs @@ -1,6 +1,5 @@ #nullable enable using System.Collections.Generic; -using System.IO; using YamlDotNet.Serialization; namespace LBPUnion.ProjectLighthouse.Configuration; @@ -13,15 +12,15 @@ public class EmailEnforcementConfiguration : ConfigurationBase false; + public bool EnableEmailBlacklist => false; // No blacklist by default, add path to blacklist - public string BlacklistFilePath { get; set; } = ""; + public string BlacklistFilePath => ""; // Endpoints to be blocked // This is kind of a random list so some may need to be added or removed - public HashSet BlockedEndpoints { get; set; } = new() + public HashSet BlockedEndpoints => new() { // Comments "rateUserComment", diff --git a/ProjectLighthouse/Configuration/ServerConfiguration.cs b/ProjectLighthouse/Configuration/ServerConfiguration.cs index ba9f7f4a1..584c1a0e1 100644 --- a/ProjectLighthouse/Configuration/ServerConfiguration.cs +++ b/ProjectLighthouse/Configuration/ServerConfiguration.cs @@ -11,7 +11,7 @@ public class ServerConfiguration : ConfigurationBase // This is so Lighthouse can properly identify outdated configurations and update them with newer settings accordingly. // If you are modifying anything here, this value MUST be incremented. // Thanks for listening~ - public override int ConfigVersion { get; set; } = 27; + public override int ConfigVersion { get; set; } = 28; public override string ConfigName { get; set; } = "lighthouse.yml"; public string WebsiteListenUrl { get; set; } = "http://localhost:10060"; @@ -46,5 +46,7 @@ public class ServerConfiguration : ConfigurationBase public RichPresenceConfiguration RichPresenceConfiguration { get; set; } = new(); public NotificationConfiguration NotificationConfiguration { get; set; } = new(); + public EmailEnforcementConfiguration EmailEnforcement { get; set; } = new(); + public override ConfigurationBase Deserialize(IDeserializer deserializer, string text) => deserializer.Deserialize(text); } \ No newline at end of file diff --git a/ProjectLighthouse/Helpers/EmailHelper.cs b/ProjectLighthouse/Helpers/EmailHelper.cs index 606b682b0..65124f960 100644 --- a/ProjectLighthouse/Helpers/EmailHelper.cs +++ b/ProjectLighthouse/Helpers/EmailHelper.cs @@ -6,7 +6,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; -using LBPUnion.ProjectLighthouse.Configuration; +using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Logging; @@ -20,15 +20,16 @@ namespace LBPUnion.ProjectLighthouse.Helpers; public static class SMTPHelper { + private static readonly string blacklistFilePath = ServerConfiguration.Instance.EmailEnforcement.BlacklistFilePath; + + // Null check blacklistFilePath and read into array + private static readonly string[] blacklistFile = + !string.IsNullOrWhiteSpace(blacklistFilePath) ? File.ReadAllLines(blacklistFilePath) : []; + // (User id, timestamp of last request + 30 seconds) private static readonly ConcurrentDictionary recentlySentMail = new(); private const long emailCooldown = 1000 * 30; - - // To prevent ReadAllLines() exception when BlacklistFilePath is empty - private static readonly string[] blacklistFile = - !string.IsNullOrWhiteSpace(EmailEnforcementConfiguration.Instance.BlacklistFilePath) - ? File.ReadAllLines(EmailEnforcementConfiguration.Instance.BlacklistFilePath) : []; private static readonly HashSet blacklistedDomains = new(blacklistFile); @@ -84,28 +85,24 @@ public static async Task SendPasswordResetEmail(DatabaseContext database, IMailS public static bool IsValidEmail(DatabaseContext database, string email) { // Email should not be empty, should be an actual email, and shouldn't already be used by an account - if (!string.IsNullOrWhiteSpace(email) && emailValidator.IsValid(email) && !EmailIsUsed(database, email).Result) - { - // Don't even bother if there are no domains in blacklist (AKA file path is empty/invalid, or file itself is empty) - if (EmailEnforcementConfiguration.Instance.EnableEmailBlacklist && blacklistedDomains.Count > 0) - { - // Get domain by splitting at '@' character - string domain = email.Split('@')[1]; + if (string.IsNullOrWhiteSpace(email) || !emailValidator.IsValid(email) || EmailIsUsed(database, email).Result) + return false; - // Return false if domain is found in blacklist - if (blacklistedDomains.Contains(domain)) - { - Logger.Info($"Invalid email address {email} submitted by user.", LogArea.Email); - return false; - } + // Don't even bother if there are no domains in blacklist (AKA file path is empty/invalid, or file itself is empty) + if (ServerConfiguration.Instance.EmailEnforcement.EnableEmailBlacklist && blacklistedDomains.Count > 0) + { + // Get domain by splitting at '@' character + string domain = email.Split('@')[1]; - return true; + // Return false if domain is found in blacklist + if (blacklistedDomains.Contains(domain)) + { + Logger.Info($"Invalid email address {email} submitted by user.", LogArea.Email); + return false; } - - return true; } - return false; + return true; } // Don't want to allocate every single time we call EmailAddressAttribute.IsValidEmail() From 0d3e1391edc88c632aac88d1b34b6989179878cd Mon Sep 17 00:00:00 2001 From: FeTetra Date: Sat, 1 Mar 2025 22:43:50 -0500 Subject: [PATCH 21/22] Fix more koko suggestions --- .../Resources/ResourcesController.cs | 8 ++++---- .../EmailEnforcementConfiguration.cs | 10 +++++----- ProjectLighthouse/Helpers/EmailHelper.cs | 20 +++++++++---------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Resources/ResourcesController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Resources/ResourcesController.cs index f48c7e7d0..081614d62 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Resources/ResourcesController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Resources/ResourcesController.cs @@ -35,18 +35,18 @@ public async Task FilterResources() } [HttpGet("r/{hash}")] - public Task GetResource(string hash) + public IActionResult GetResource(string hash) { string path = FileHelper.GetResourcePath(hash); string fullPath = Path.GetFullPath(path); // Prevent directory traversal attacks - if (!fullPath.StartsWith(FileHelper.FullResourcePath)) return Task.FromResult(this.BadRequest()); + if (!fullPath.StartsWith(FileHelper.FullResourcePath)) return this.BadRequest(); - if (FileHelper.ResourceExists(hash)) return Task.FromResult(this.File(IOFile.OpenRead(path), "application/octet-stream")); + if (FileHelper.ResourceExists(hash)) return this.File(IOFile.OpenRead(path), "application/octet-stream"); - return Task.FromResult(this.NotFound()); + return this.NotFound(); } [HttpPost("upload/{hash}/unattributed")] diff --git a/ProjectLighthouse/Configuration/EmailEnforcementConfiguration.cs b/ProjectLighthouse/Configuration/EmailEnforcementConfiguration.cs index 56179d914..ee3819267 100644 --- a/ProjectLighthouse/Configuration/EmailEnforcementConfiguration.cs +++ b/ProjectLighthouse/Configuration/EmailEnforcementConfiguration.cs @@ -6,21 +6,21 @@ namespace LBPUnion.ProjectLighthouse.Configuration; public class EmailEnforcementConfiguration : ConfigurationBase { - public override int ConfigVersion { get; set; } = 4; + public override int ConfigVersion { get; set; } = 1; public override string ConfigName { get; set; } = "enforce-email.yml"; public override bool NeedsConfiguration { get; set; } = false; - public bool EnableEmailEnforcement => false; - public bool EnableEmailBlacklist => false; + public bool EnableEmailEnforcement { get; set; } = false; + public bool EnableEmailBlacklist { get; set; } = false; // No blacklist by default, add path to blacklist - public string BlacklistFilePath => ""; + public string BlacklistFilePath { get; set; } = ""; // Endpoints to be blocked // This is kind of a random list so some may need to be added or removed - public HashSet BlockedEndpoints => new() + public HashSet BlockedEndpoints { get; set; } = new() { // Comments "rateUserComment", diff --git a/ProjectLighthouse/Helpers/EmailHelper.cs b/ProjectLighthouse/Helpers/EmailHelper.cs index 65124f960..92b5bb14b 100644 --- a/ProjectLighthouse/Helpers/EmailHelper.cs +++ b/ProjectLighthouse/Helpers/EmailHelper.cs @@ -89,17 +89,17 @@ public static bool IsValidEmail(DatabaseContext database, string email) return false; // Don't even bother if there are no domains in blacklist (AKA file path is empty/invalid, or file itself is empty) - if (ServerConfiguration.Instance.EmailEnforcement.EnableEmailBlacklist && blacklistedDomains.Count > 0) + if (!ServerConfiguration.Instance.EmailEnforcement.EnableEmailBlacklist || blacklistedDomains.Count <= 0) + return true; + + // Get domain by splitting at '@' character + string domain = email.Split('@')[1]; + + // Return false if domain is found in blacklist + if (blacklistedDomains.Contains(domain)) { - // Get domain by splitting at '@' character - string domain = email.Split('@')[1]; - - // Return false if domain is found in blacklist - if (blacklistedDomains.Contains(domain)) - { - Logger.Info($"Invalid email address {email} submitted by user.", LogArea.Email); - return false; - } + Logger.Info($"Invalid email address {email} submitted by user.", LogArea.Email); + return false; } return true; From ed9c1464a191ffbeb161969f1214d55a8674cfda Mon Sep 17 00:00:00 2001 From: FeTetra Date: Sat, 8 Mar 2025 12:46:58 -0500 Subject: [PATCH 22/22] Fix zaprit suggestion --- .../Controllers/UserController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs index ed0dbe12c..463e2feef 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs @@ -37,10 +37,10 @@ public UserController(DatabaseContext database) [HttpGet("user/{username}")] public async Task GetUser(string username) { - UserEntity? targetUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); - if (targetUser == null) return this.NotFound(); + UserEntity? user = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); + if (user == null) return this.NotFound(); - return this.Ok(GameUser.CreateFromEntity(targetUser, this.GetToken().GameVersion)); + return this.Ok(GameUser.CreateFromEntity(user, this.GetToken().GameVersion)); } [HttpGet("users")]