From 021e0529cb0b231ddd6570ffefa899913a9f2fca Mon Sep 17 00:00:00 2001 From: Toaster2 Date: Wed, 27 May 2026 21:11:18 +0200 Subject: [PATCH 1/8] Migrate existing disallowance rows to have lowercased PKs --- ...72459_DisallowEntitiesCaseInsensitively.cs | 85 +++++++++++++++++++ .../GameDatabaseContextModelSnapshot.cs | 7 +- 2 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 Refresh.Database/Migrations/20260527172459_DisallowEntitiesCaseInsensitively.cs diff --git a/Refresh.Database/Migrations/20260527172459_DisallowEntitiesCaseInsensitively.cs b/Refresh.Database/Migrations/20260527172459_DisallowEntitiesCaseInsensitively.cs new file mode 100644 index 00000000..ac7b4384 --- /dev/null +++ b/Refresh.Database/Migrations/20260527172459_DisallowEntitiesCaseInsensitively.cs @@ -0,0 +1,85 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Refresh.Database.Migrations +{ + /// + [DbContext(typeof(GameDatabaseContext))] + [Migration("20260527172459_DisallowEntitiesCaseInsensitively")] + public partial class DisallowEntitiesCaseInsensitively : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + // Remove case-insensitively duplicate entries before lowercasing primary keys to not cause duplicate keys + // Email Addresses + migrationBuilder.Sql + (""" + DELETE FROM "DisallowedEmailAddresses" + WHERE "Address" NOT IN ( + SELECT min("Address") + FROM "DisallowedEmailAddresses" + GROUP BY lower("Address") + ) + """); + // for some reason, Postgres won't actually execute these separately if we use semicolons, so we have to do separate method calls + migrationBuilder.Sql + (""" + UPDATE "DisallowedEmailAddresses" SET "Address" = lower("Address"); + """); + + // Email Domains + migrationBuilder.Sql + (""" + DELETE FROM "DisallowedEmailDomains" + WHERE "Domain" NOT IN ( + SELECT min("Domain") + FROM "DisallowedEmailDomains" + GROUP BY lower("Domain") + ) + """); + migrationBuilder.Sql + (""" + UPDATE "DisallowedEmailDomains" SET "Domain" = lower("Domain"); + """); + + // Usernames + migrationBuilder.Sql + (""" + DELETE FROM "DisallowedUsers" + WHERE "Username" NOT IN ( + SELECT min("Username") + FROM "DisallowedUsers" + GROUP BY lower("Username") + ) + """); + migrationBuilder.Sql + (""" + UPDATE "DisallowedUsers" SET "Username" = lower("Username"); + """); + + // Assets + migrationBuilder.Sql + (""" + DELETE FROM "DisallowedAssets" + WHERE "AssetHash" NOT IN ( + SELECT min("AssetHash") + FROM "DisallowedAssets" + GROUP BY lower("AssetHash") + ) + """); + migrationBuilder.Sql + (""" + UPDATE "DisallowedAssets" SET "AssetHash" = lower("AssetHash"); + """); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/Refresh.Database/Migrations/GameDatabaseContextModelSnapshot.cs b/Refresh.Database/Migrations/GameDatabaseContextModelSnapshot.cs index b04f829c..9316c4da 100644 --- a/Refresh.Database/Migrations/GameDatabaseContextModelSnapshot.cs +++ b/Refresh.Database/Migrations/GameDatabaseContextModelSnapshot.cs @@ -1602,12 +1602,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Entity") .HasColumnType("smallint"); - b.Property("UploadCount") - .HasColumnType("integer"); - b.Property("ExpiryDate") .HasColumnType("timestamp with time zone"); + // EF badly wanted to move this below ExpiryDate fsr + b.Property("UploadCount") + .HasColumnType("integer"); + b.HasKey("UserId", "Entity"); b.ToTable("EntityUploadRateLimits"); From ffbb37569ff2c6190405d7b1a187287883b8d73b Mon Sep 17 00:00:00 2001 From: Toaster2 Date: Wed, 27 May 2026 21:51:25 +0200 Subject: [PATCH 2/8] Lookup/create/remove disallowance rows case-insensitively --- .../GameDatabaseContext.Assets.cs | 13 +++-- .../GameDatabaseContext.Registration.cs | 58 ++++++++++++------- 2 files changed, 46 insertions(+), 25 deletions(-) diff --git a/Refresh.Database/GameDatabaseContext.Assets.cs b/Refresh.Database/GameDatabaseContext.Assets.cs index 7d2ab535..7386356f 100644 --- a/Refresh.Database/GameDatabaseContext.Assets.cs +++ b/Refresh.Database/GameDatabaseContext.Assets.cs @@ -112,19 +112,23 @@ public void SetMainlinePhotoHash(GameAsset asset, string hash) => }); public DisallowedAsset? GetDisallowedAssetInfo(string hash) - => this.DisallowedAssets.FirstOrDefault(d => d.AssetHash == hash); + { + string hashLower = hash.ToLower(); + return this.DisallowedAssets.FirstOrDefault(d => d.AssetHash == hashLower); + } /// /// The asset's disallowance info + whether the asset wasn't already disallowed before /// public bool ReallowAsset(string hash) { - DisallowedAsset? existing = this.GetDisallowedAssetInfo(hash); + string hashLower = hash.ToLower(); + DisallowedAsset? existing = this.GetDisallowedAssetInfo(hashLower); if (existing == null) return false; this.DisallowedAssets.Remove(existing); diff --git a/Refresh.Database/GameDatabaseContext.Registration.cs b/Refresh.Database/GameDatabaseContext.Registration.cs index 215b8e23..7d180eb2 100644 --- a/Refresh.Database/GameDatabaseContext.Registration.cs +++ b/Refresh.Database/GameDatabaseContext.Registration.cs @@ -219,22 +219,29 @@ public void RemoveEmailVerificationCode(EmailVerificationCode code) } public bool IsUserDisallowed(string username) - => this.DisallowedUsers.Any(u => u.Username == username); + { + string lowercaseUsername = username.ToLower(); + return this.DisallowedUsers.Any(u => u.Username == username); + } public DisallowedUser? GetDisallowedUserInfo(string username) - => this.DisallowedUsers.FirstOrDefault(d => d.Username == username); + { + string lowercaseUsername = username.ToLower(); + return this.DisallowedUsers.FirstOrDefault(d => d.Username == username); + } public DatabaseList GetDisallowedUsers(int skip, int count) => new(this.DisallowedUsers.OrderByDescending(d => d.DisallowedAt), skip, count); public (DisallowedUser, bool) DisallowUser(string username, string reason) { - DisallowedUser? existing = this.GetDisallowedUserInfo(username); + string lowercaseUsername = username.ToLower(); + DisallowedUser? existing = this.GetDisallowedUserInfo(lowercaseUsername); if (existing != null) return (existing, false); DisallowedUser disallowed = new() { - Username = username, + Username = lowercaseUsername, Reason = reason, DisallowedAt = this._time.Now, }; @@ -246,7 +253,8 @@ public DatabaseList GetDisallowedUsers(int skip, int count) public bool ReallowUser(string username) { - DisallowedUser? disallowedUser = this.GetDisallowedUserInfo(username); + string lowercaseUsername = username.ToLower(); + DisallowedUser? disallowedUser = this.GetDisallowedUserInfo(lowercaseUsername); if (disallowedUser == null) return false; @@ -257,22 +265,29 @@ public bool ReallowUser(string username) } public bool IsEmailAddressDisallowed(string emailAddress) - => this.DisallowedEmailAddresses.Any(u => u.Address == emailAddress); + { + string emailAddressLower = emailAddress.ToLowerInvariant(); + return this.DisallowedEmailAddresses.Any(u => u.Address == emailAddressLower); + } public DisallowedEmailAddress? GetDisallowedEmailAddressInfo(string emailAddress) - => this.DisallowedEmailAddresses.FirstOrDefault(d => d.Address == emailAddress); + { + string emailAddressLower = emailAddress.ToLowerInvariant(); + return this.DisallowedEmailAddresses.FirstOrDefault(d => d.Address == emailAddressLower); + } public DatabaseList GetDisallowedEmailAddresses(int skip, int count) => new(this.DisallowedEmailAddresses.OrderByDescending(d => d.DisallowedAt), skip, count); public (DisallowedEmailAddress, bool) DisallowEmailAddress(string emailAddress, string reason) { - DisallowedEmailAddress? existing = this.GetDisallowedEmailAddressInfo(emailAddress); + string emailAddressLower = emailAddress.ToLowerInvariant(); + DisallowedEmailAddress? existing = this.GetDisallowedEmailAddressInfo(emailAddressLower); if (existing != null) return (existing, false); DisallowedEmailAddress disallowed = new() { - Address = emailAddress, + Address = emailAddressLower, Reason = reason, DisallowedAt = this._time.Now, }; @@ -284,7 +299,8 @@ public DatabaseList GetDisallowedEmailAddresses(int skip public bool ReallowEmailAddress(string emailAddress) { - DisallowedEmailAddress? disallowed = this.GetDisallowedEmailAddressInfo(emailAddress); + string emailAddressLower = emailAddress.ToLowerInvariant(); + DisallowedEmailAddress? disallowed = this.GetDisallowedEmailAddressInfo(emailAddressLower); if (disallowed == null) return false; @@ -294,19 +310,19 @@ public bool ReallowEmailAddress(string emailAddress) return true; } - private string GetEmailDomainFromAddress(string emailAddress) - => emailAddress.Split('@').Last(); + private string GetLowercaseEmailDomainFromAddress(string emailAddress) + => emailAddress.Split('@').Last().ToLowerInvariant(); public bool IsEmailDomainDisallowed(string emailAddress) { - string emailDomain = this.GetEmailDomainFromAddress(emailAddress); - return this.DisallowedEmailDomains.Any(u => u.Domain == emailDomain); + string emailDomainLower = this.GetLowercaseEmailDomainFromAddress(emailAddress); + return this.DisallowedEmailDomains.Any(u => u.Domain == emailDomainLower); } public DisallowedEmailDomain? GetDisallowedEmailDomainInfo(string emailAddress) { - string emailDomain = this.GetEmailDomainFromAddress(emailAddress); - return this.DisallowedEmailDomains.FirstOrDefault(d => d.Domain == emailDomain); + string emailDomainLower = this.GetLowercaseEmailDomainFromAddress(emailAddress); + return this.DisallowedEmailDomains.FirstOrDefault(d => d.Domain == emailDomainLower); } public DatabaseList GetDisallowedEmailDomains(int skip, int count) @@ -314,13 +330,13 @@ public DatabaseList GetDisallowedEmailDomains(int skip, i public (DisallowedEmailDomain, bool) DisallowEmailDomain(string emailAddress, string reason) { - string emailDomain = this.GetEmailDomainFromAddress(emailAddress); - DisallowedEmailDomain? existing = this.GetDisallowedEmailDomainInfo(emailDomain); + string emailDomainLower = this.GetLowercaseEmailDomainFromAddress(emailAddress); + DisallowedEmailDomain? existing = this.GetDisallowedEmailDomainInfo(emailDomainLower); if (existing != null) return (existing, false); DisallowedEmailDomain disallowed = new() { - Domain = emailDomain, + Domain = emailDomainLower, Reason = reason, DisallowedAt = this._time.Now, }; @@ -332,8 +348,8 @@ public DatabaseList GetDisallowedEmailDomains(int skip, i public bool ReallowEmailDomain(string emailAddress) { - string emailDomain = this.GetEmailDomainFromAddress(emailAddress); - DisallowedEmailDomain? disallowedDomain = this.GetDisallowedEmailDomainInfo(emailDomain); + string emailDomainLower = this.GetLowercaseEmailDomainFromAddress(emailAddress); + DisallowedEmailDomain? disallowedDomain = this.GetDisallowedEmailDomainInfo(emailDomainLower); if (disallowedDomain == null) return false; From 6f79104a809a088067fdf445258aca2f7a8d0d84 Mon Sep 17 00:00:00 2001 From: Toaster2 Date: Sat, 30 May 2026 14:33:13 +0200 Subject: [PATCH 3/8] Implement IsAssetDisallowed method for more consistency --- Refresh.Database/GameDatabaseContext.Assets.cs | 6 ++++++ Refresh.Interfaces.APIv3/Endpoints/ResourceApiEndpoints.cs | 2 +- Refresh.Interfaces.Game/Endpoints/ResourceEndpoints.cs | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Refresh.Database/GameDatabaseContext.Assets.cs b/Refresh.Database/GameDatabaseContext.Assets.cs index 7386356f..d29da1f2 100644 --- a/Refresh.Database/GameDatabaseContext.Assets.cs +++ b/Refresh.Database/GameDatabaseContext.Assets.cs @@ -111,6 +111,12 @@ public void SetMainlinePhotoHash(GameAsset asset, string hash) => asset.AsMainlinePhotoHash = hash; }); + public bool IsAssetDisallowed(string hash) + { + string hashLower = hash.ToLower(); + return this.DisallowedAssets.Any(u => u.AssetHash == hashLower); + } + public DisallowedAsset? GetDisallowedAssetInfo(string hash) { string hashLower = hash.ToLower(); diff --git a/Refresh.Interfaces.APIv3/Endpoints/ResourceApiEndpoints.cs b/Refresh.Interfaces.APIv3/Endpoints/ResourceApiEndpoints.cs index 7f5893db..89ad3214 100644 --- a/Refresh.Interfaces.APIv3/Endpoints/ResourceApiEndpoints.cs +++ b/Refresh.Interfaces.APIv3/Endpoints/ResourceApiEndpoints.cs @@ -200,7 +200,7 @@ IntegrationConfig integration return new ApiValidationError($"You have exceeded your filesize quota."); } - if (database.GetDisallowedAssetInfo(hash) != null) + if (database.IsAssetDisallowed(hash)) { context.Logger.LogWarning(BunkumCategory.UserContent, "User {0} has tried to upload a disallowed asset, rejecting.", user); return ApiModerationError.AssetDisallowedError; diff --git a/Refresh.Interfaces.Game/Endpoints/ResourceEndpoints.cs b/Refresh.Interfaces.Game/Endpoints/ResourceEndpoints.cs index 8b6dc48c..e63759f3 100644 --- a/Refresh.Interfaces.Game/Endpoints/ResourceEndpoints.cs +++ b/Refresh.Interfaces.Game/Endpoints/ResourceEndpoints.cs @@ -62,7 +62,7 @@ public Response UploadAsset(RequestContext context, string hash, string type, by return RequestEntityTooLarge; } - if (database.GetDisallowedAssetInfo(hash) != null) + if (database.IsAssetDisallowed(hash)) { context.Logger.LogWarning(BunkumCategory.UserContent, "User {0} has tried to upload a disallowed asset, rejecting.", user); return Unauthorized; From b6a06a6db22be83ca4f6d3c3549a438e53a07160 Mon Sep 17 00:00:00 2001 From: Toaster2 Date: Sat, 30 May 2026 14:34:52 +0200 Subject: [PATCH 4/8] Better explain that disallowed usernames/addresses/domains are case-insensitive --- Refresh.Database/Models/Users/DisallowedEmailAddress.cs | 3 +++ Refresh.Database/Models/Users/DisallowedEmailDomain.cs | 3 +++ Refresh.Database/Models/Users/DisallowedUser.cs | 3 +++ 3 files changed, 9 insertions(+) diff --git a/Refresh.Database/Models/Users/DisallowedEmailAddress.cs b/Refresh.Database/Models/Users/DisallowedEmailAddress.cs index a54c75df..5a8db89f 100644 --- a/Refresh.Database/Models/Users/DisallowedEmailAddress.cs +++ b/Refresh.Database/Models/Users/DisallowedEmailAddress.cs @@ -4,6 +4,9 @@ namespace Refresh.Database.Models.Users; public partial class DisallowedEmailAddress { + /// + /// Lower-case email address to allow case-insensitive lookup. + /// [Key] public string Address { get; set; } public string Reason { get; set; } diff --git a/Refresh.Database/Models/Users/DisallowedEmailDomain.cs b/Refresh.Database/Models/Users/DisallowedEmailDomain.cs index ae3e297d..1a3ae239 100644 --- a/Refresh.Database/Models/Users/DisallowedEmailDomain.cs +++ b/Refresh.Database/Models/Users/DisallowedEmailDomain.cs @@ -4,6 +4,9 @@ namespace Refresh.Database.Models.Users; public partial class DisallowedEmailDomain { + /// + /// Lower-case email domain to allow case-insensitive lookup. + /// [Key] public string Domain { get; set; } public string Reason { get; set; } diff --git a/Refresh.Database/Models/Users/DisallowedUser.cs b/Refresh.Database/Models/Users/DisallowedUser.cs index 7dcd2beb..d48bd81f 100644 --- a/Refresh.Database/Models/Users/DisallowedUser.cs +++ b/Refresh.Database/Models/Users/DisallowedUser.cs @@ -4,6 +4,9 @@ namespace Refresh.Database.Models.Users; public partial class DisallowedUser { + /// + /// Lower-case username to allow case-insensitive lookup. + /// [Key] public string Username { get; set; } public string Reason { get; set; } From 064695d2e1347bb18912ed473bc7435345c7ffc8 Mon Sep 17 00:00:00 2001 From: Toaster2 Date: Sat, 30 May 2026 15:20:18 +0200 Subject: [PATCH 5/8] Fix a goofy --- Refresh.Database/GameDatabaseContext.Registration.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Refresh.Database/GameDatabaseContext.Registration.cs b/Refresh.Database/GameDatabaseContext.Registration.cs index 7d180eb2..122bf2f0 100644 --- a/Refresh.Database/GameDatabaseContext.Registration.cs +++ b/Refresh.Database/GameDatabaseContext.Registration.cs @@ -221,13 +221,13 @@ public void RemoveEmailVerificationCode(EmailVerificationCode code) public bool IsUserDisallowed(string username) { string lowercaseUsername = username.ToLower(); - return this.DisallowedUsers.Any(u => u.Username == username); + return this.DisallowedUsers.Any(u => u.Username == lowercaseUsername); } public DisallowedUser? GetDisallowedUserInfo(string username) { string lowercaseUsername = username.ToLower(); - return this.DisallowedUsers.FirstOrDefault(d => d.Username == username); + return this.DisallowedUsers.FirstOrDefault(d => d.Username == lowercaseUsername); } public DatabaseList GetDisallowedUsers(int skip, int count) From 5e7478b4d4d7a4aa8af2ad07209d38e6e2977bed Mon Sep 17 00:00:00 2001 From: Toaster2 Date: Sat, 30 May 2026 15:21:34 +0200 Subject: [PATCH 6/8] Move user disallowance tests to separate class, test case-insensitivety, more asserts in general --- .../Tests/ApiV3/UserApiTests.cs | 166 ------------ .../Tests/Users/UserDisallowanceTests.cs | 251 ++++++++++++++++++ 2 files changed, 251 insertions(+), 166 deletions(-) create mode 100644 RefreshTests.GameServer/Tests/Users/UserDisallowanceTests.cs diff --git a/RefreshTests.GameServer/Tests/ApiV3/UserApiTests.cs b/RefreshTests.GameServer/Tests/ApiV3/UserApiTests.cs index 920df4ef..409ce93d 100644 --- a/RefreshTests.GameServer/Tests/ApiV3/UserApiTests.cs +++ b/RefreshTests.GameServer/Tests/ApiV3/UserApiTests.cs @@ -46,172 +46,6 @@ public void RegisterAccount() Assert.That(context.Database.GetUserByUsername(username), Is.Not.EqualTo(null)); } - [Test] - public void CannotRegisterAccountWithDisallowedEmailAddress() - { - using TestContext context = this.GetServer(); - - const string email = "guy@lil.com"; - const string disallowReason = "being lil"; - // Not somehow already disallowed - Assert.That(context.Database.IsEmailAddressDisallowed(email), Is.False); - Assert.That(context.Database.GetDisallowedEmailAddressInfo(email), Is.Null); - context.Database.DisallowEmailAddress(email, disallowReason); - - context.Database.Refresh(); - Assert.That(context.Database.IsEmailAddressDisallowed(email), Is.True); - DisallowedEmailAddress? disallowed = context.Database.GetDisallowedEmailAddressInfo(email); - Assert.That(disallowed, Is.Not.Null); - Assert.That(disallowed!.Address, Is.EqualTo(email)); - Assert.That(disallowed!.Reason, Is.EqualTo(disallowReason)); - - ApiResponse? response = context.Http.PostData("/api/v3/register", new ApiRegisterRequest - { - Username = "a_lil_guy", - EmailAddress = email, - PasswordSha512 = "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff", - }, false, true); - Assert.That(response, Is.Not.Null); - Assert.That(response!.Error, Is.Not.Null); - Assert.That(response.Error!.Name, Is.EqualTo("ApiAuthenticationError")); - - context.Database.Refresh(); - Assert.That(context.Database.GetUserByEmailAddress(email), Is.Null); - - // Undo - context.Database.ReallowEmailAddress(email); - context.Database.Refresh(); - Assert.That(context.Database.IsEmailAddressDisallowed(email), Is.False); - Assert.That(context.Database.GetDisallowedEmailAddressInfo(email), Is.Null); - } - - [Test] - [TestCase("guy@moron.com")] // whole address - [TestCase("moron.com")] // just the domain - public void CannotRegisterAccountsWithDisallowedEmailDomain(string addressToBlockWith) - { - using TestContext context = this.GetServer(); - const string disallowReason = "moron email moment"; - const string domain = "moron.com"; - - // Not somehow already disallowed - Assert.That(context.Database.IsEmailDomainDisallowed(addressToBlockWith), Is.False); - Assert.That(context.Database.IsEmailDomainDisallowed(domain), Is.False); - Assert.That(context.Database.GetDisallowedEmailDomainInfo(addressToBlockWith), Is.Null); - context.Database.DisallowEmailDomain(addressToBlockWith, disallowReason); - - context.Database.Refresh(); - Assert.That(context.Database.IsEmailDomainDisallowed(addressToBlockWith), Is.True); - Assert.That(context.Database.IsEmailDomainDisallowed(domain), Is.True); - DisallowedEmailDomain? disallowed = context.Database.GetDisallowedEmailDomainInfo(addressToBlockWith); - Assert.That(disallowed, Is.Not.Null); - Assert.That(disallowed!.Domain, Is.EqualTo(domain)); - Assert.That(disallowed!.Reason, Is.EqualTo(disallowReason)); - - // Attempt 1 (block) - ApiResponse? response = context.Http.PostData("/api/v3/register", new ApiRegisterRequest - { - Username = "a_lil_guy", - EmailAddress = "pisser@moron.com", - PasswordSha512 = "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff", - }, false, true); - Assert.That(response, Is.Not.Null); - Assert.That(response!.Error, Is.Not.Null); - Assert.That(response.Error!.Name, Is.EqualTo("ApiAuthenticationError")); - context.Database.Refresh(); - Assert.That(context.Database.GetUserByEmailAddress("pisser@moron.com"), Is.Null); - - // Attempt 2 (block) - response = context.Http.PostData("/api/v3/register", new ApiRegisterRequest - { - Username = "a_lil_guy", - EmailAddress = "shitter@moron.com", - PasswordSha512 = "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff", - }, false, true); - Assert.That(response, Is.Not.Null); - Assert.That(response!.Error, Is.Not.Null); - Assert.That(response.Error!.Name, Is.EqualTo("ApiAuthenticationError")); - context.Database.Refresh(); - Assert.That(context.Database.GetUserByEmailAddress("shitter@moron.com"), Is.Null); - - // Attempt 3 (block) - response = context.Http.PostData("/api/v3/register", new ApiRegisterRequest - { - Username = "a_lil_guy", - EmailAddress = ".@moron.com", - PasswordSha512 = "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff", - }, false, true); - Assert.That(response, Is.Not.Null); - Assert.That(response!.Error, Is.Not.Null); - Assert.That(response.Error!.Name, Is.EqualTo("ApiAuthenticationError")); - context.Database.Refresh(); - Assert.That(context.Database.GetUserByEmailAddress(".@moron.com"), Is.Null); - - // Attempt 4 (allow) - response = context.Http.PostData("/api/v3/register", new ApiRegisterRequest - { - Username = "a_lil_guy", - EmailAddress = "quacker@hi.com", - PasswordSha512 = "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff", - }); - Assert.That(response, Is.Not.Null); - Assert.That(response!.Error, Is.Null); - Assert.That(response!.Data, Is.Not.Null); - context.Database.Refresh(); - - GameUser? quacker = context.Database.GetUserByEmailAddress("quacker@hi.com"); - Assert.That(quacker, Is.Not.Null); - Assert.That(quacker!.UserId.ToString(), Is.EqualTo(response.Data!.UserId)); - Assert.That(quacker!.Username, Is.EqualTo("a_lil_guy")); - - // Undo - context.Database.ReallowEmailDomain(addressToBlockWith); - context.Database.Refresh(); - Assert.That(context.Database.IsEmailDomainDisallowed(addressToBlockWith), Is.False); - Assert.That(context.Database.IsEmailDomainDisallowed(domain), Is.False); - Assert.That(context.Database.GetDisallowedEmailDomainInfo(addressToBlockWith), Is.Null); - } - - [Test] - public void CannotRegisterAccountWithDisallowedUsername() - { - using TestContext context = this.GetServer(); - const string username = "a_lil_guy"; - const string disallowReason = "writing these is fun lol"; - - // Not somehow already disallowed - Assert.That(context.Database.IsUserDisallowed(username), Is.False); - Assert.That(context.Database.GetDisallowedUserInfo(username), Is.Null); - - context.Database.DisallowUser(username, disallowReason); - context.Database.Refresh(); - - Assert.That(context.Database.IsUserDisallowed(username), Is.True); - DisallowedUser? disallowed = context.Database.GetDisallowedUserInfo(username); - Assert.That(disallowed, Is.Not.Null); - Assert.That(disallowed!.Username, Is.EqualTo(username)); - Assert.That(disallowed!.Reason, Is.EqualTo(disallowReason)); - - ApiResponse? response = context.Http.PostData("/api/v3/register", new ApiRegisterRequest - { - Username = username, - EmailAddress = "guy@lil.com", - PasswordSha512 = "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff", - }, false, true); - Assert.That(response, Is.Not.Null); - Assert.That(response!.Error, Is.Not.EqualTo(null)); - Assert.That(response.Error!.Name, Is.EqualTo("ApiAuthenticationError")); - - context.Database.Refresh(); - Assert.That(context.Database.GetUserByUsername(username), Is.Null); - - // Undo - context.Database.ReallowUser(username); - context.Database.Refresh(); - Assert.That(context.Database.IsUserDisallowed(username), Is.False); - Assert.That(context.Database.GetDisallowedUserInfo(username), Is.Null); - } - [Test] public void CannotRegisterAccountWithPreviouslyTakenUsername() { diff --git a/RefreshTests.GameServer/Tests/Users/UserDisallowanceTests.cs b/RefreshTests.GameServer/Tests/Users/UserDisallowanceTests.cs new file mode 100644 index 00000000..608e4bf5 --- /dev/null +++ b/RefreshTests.GameServer/Tests/Users/UserDisallowanceTests.cs @@ -0,0 +1,251 @@ +using Refresh.Database.Models.Users; +using Refresh.Interfaces.APIv3.Endpoints.ApiTypes; +using Refresh.Interfaces.APIv3.Endpoints.DataTypes.Request.Authentication; +using Refresh.Interfaces.APIv3.Endpoints.DataTypes.Response.Users; +using RefreshTests.GameServer.Extensions; + +namespace RefreshTests.GameServer.Tests.Users; + +public class UserDisallowanceTests : GameServerTest +{ + [Test] + [TestCase("guy@lil.com")] + [TestCase("Guy@LiL.coM")] + [TestCase("GUY@LIL.COM")] + public void CannotRegisterAccountWithDisallowedEmailAddressCaseInsensitively(string emailAddress) + { + using TestContext context = this.GetServer(); + + const string emailAddressLower = "guy@lil.com"; + const string disallowReason = "being lil"; + // Not somehow already disallowed + Assert.That(context.Database.IsEmailAddressDisallowed(emailAddress), Is.False); + Assert.That(context.Database.GetDisallowedEmailAddressInfo(emailAddress), Is.Null); + + // Disallow + (DisallowedEmailAddress disallowanceReturn, bool success) = context.Database.DisallowEmailAddress(emailAddress, disallowReason); + Assert.That(disallowanceReturn.Address, Is.EqualTo(emailAddressLower)); + Assert.That(disallowanceReturn.Reason, Is.EqualTo(disallowReason)); + Assert.That(success, Is.True); + context.Database.Refresh(); + + // Try to disallow again + (disallowanceReturn, success) = context.Database.DisallowEmailAddress(emailAddress, disallowReason); + Assert.That(disallowanceReturn.Address, Is.EqualTo(emailAddressLower)); + Assert.That(disallowanceReturn.Reason, Is.EqualTo(disallowReason)); + Assert.That(success, Is.False); + context.Database.Refresh(); + + // Ensure it's actually disallowed + Assert.That(context.Database.IsEmailAddressDisallowed(emailAddress), Is.True); + DisallowedEmailAddress? disallowed = context.Database.GetDisallowedEmailAddressInfo(emailAddress); + Assert.That(disallowed, Is.Not.Null); + Assert.That(disallowed!.Address, Is.EqualTo(emailAddressLower)); + Assert.That(disallowed!.Reason, Is.EqualTo(disallowReason)); + + // Try to register + ApiResponse? response = context.Http.PostData("/api/v3/register", new ApiRegisterRequest + { + Username = "a_lil_guy", + EmailAddress = emailAddress, + PasswordSha512 = "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff", + }, false, true); + Assert.That(response, Is.Not.Null); + Assert.That(response!.Error, Is.Not.Null); + Assert.That(response.Error!.Name, Is.EqualTo("ApiAuthenticationError")); + + context.Database.Refresh(); + Assert.That(context.Database.GetUserByEmailAddress(emailAddress), Is.Null); + + // Undo + success = context.Database.ReallowEmailAddress(emailAddress); + Assert.That(success, Is.True); + context.Database.Refresh(); + Assert.That(context.Database.IsEmailAddressDisallowed(emailAddress), Is.False); + Assert.That(context.Database.GetDisallowedEmailAddressInfo(emailAddress), Is.Null); + + // Try to undo again + success = context.Database.ReallowEmailAddress(emailAddress); + Assert.That(success, Is.False); + context.Database.Refresh(); + Assert.That(context.Database.IsEmailAddressDisallowed(emailAddress), Is.False); + Assert.That(context.Database.GetDisallowedEmailAddressInfo(emailAddress), Is.Null); + } + + [Test] + [TestCase("guy@moron.com")] // whole address + [TestCase("moron.com")] // just the domain + [TestCase("MORON.Com")] + [TestCase("GUY@MORoN.cOm")] + public void CannotRegisterAccountsWithDisallowedEmailDomainCaseInsensitively(string addressToBlockWith) + { + using TestContext context = this.GetServer(); + const string disallowReason = "moron email moment"; + const string domain = "moron.com"; + + // Not somehow already disallowed + Assert.That(context.Database.IsEmailDomainDisallowed(addressToBlockWith), Is.False); + Assert.That(context.Database.IsEmailDomainDisallowed(domain), Is.False); + Assert.That(context.Database.GetDisallowedEmailDomainInfo(addressToBlockWith), Is.Null); + + // Disallow + (DisallowedEmailDomain disallowanceReturn, bool success) = context.Database.DisallowEmailDomain(addressToBlockWith, disallowReason); + Assert.That(disallowanceReturn.Domain, Is.EqualTo(domain)); + Assert.That(disallowanceReturn.Reason, Is.EqualTo(disallowReason)); + Assert.That(success, Is.True); + context.Database.Refresh(); + + // Try to disallow again + (disallowanceReturn, success) = context.Database.DisallowEmailDomain(addressToBlockWith, disallowReason); + Assert.That(disallowanceReturn.Domain, Is.EqualTo(domain)); + Assert.That(disallowanceReturn.Reason, Is.EqualTo(disallowReason)); + Assert.That(success, Is.False); + context.Database.Refresh(); + + // Ensure it's disallowed + Assert.That(context.Database.IsEmailDomainDisallowed(addressToBlockWith), Is.True); + Assert.That(context.Database.IsEmailDomainDisallowed(domain), Is.True); + DisallowedEmailDomain? disallowed = context.Database.GetDisallowedEmailDomainInfo(addressToBlockWith); + Assert.That(disallowed, Is.Not.Null); + Assert.That(disallowed!.Domain, Is.EqualTo(domain)); + Assert.That(disallowed!.Reason, Is.EqualTo(disallowReason)); + + // Attempt 1 (block) + ApiResponse? response = context.Http.PostData("/api/v3/register", new ApiRegisterRequest + { + Username = "a_lil_guy", + EmailAddress = "pisser@moron.com", + PasswordSha512 = "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff", + }, false, true); + Assert.That(response, Is.Not.Null); + Assert.That(response!.Error, Is.Not.Null); + Assert.That(response.Error!.Name, Is.EqualTo("ApiAuthenticationError")); + context.Database.Refresh(); + Assert.That(context.Database.GetUserByEmailAddress("pisser@moron.com"), Is.Null); + + // Attempt 2 (block) + response = context.Http.PostData("/api/v3/register", new ApiRegisterRequest + { + Username = "a_lil_guy", + EmailAddress = "shitter@moron.com", + PasswordSha512 = "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff", + }, false, true); + Assert.That(response, Is.Not.Null); + Assert.That(response!.Error, Is.Not.Null); + Assert.That(response.Error!.Name, Is.EqualTo("ApiAuthenticationError")); + context.Database.Refresh(); + Assert.That(context.Database.GetUserByEmailAddress("shitter@moron.com"), Is.Null); + + // Attempt 3 (block) + response = context.Http.PostData("/api/v3/register", new ApiRegisterRequest + { + Username = "a_lil_guy", + EmailAddress = ".@moron.com", + PasswordSha512 = "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff", + }, false, true); + Assert.That(response, Is.Not.Null); + Assert.That(response!.Error, Is.Not.Null); + Assert.That(response.Error!.Name, Is.EqualTo("ApiAuthenticationError")); + context.Database.Refresh(); + Assert.That(context.Database.GetUserByEmailAddress(".@moron.com"), Is.Null); + + // Attempt 4 (allow) + response = context.Http.PostData("/api/v3/register", new ApiRegisterRequest + { + Username = "a_lil_guy", + EmailAddress = "quacker@hi.com", + PasswordSha512 = "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff", + }); + Assert.That(response, Is.Not.Null); + Assert.That(response!.Error, Is.Null); + Assert.That(response!.Data, Is.Not.Null); + context.Database.Refresh(); + + // Ensure Quacker has successfully registered and hasn't been blocked + GameUser? quacker = context.Database.GetUserByEmailAddress("quacker@hi.com"); + Assert.That(quacker, Is.Not.Null); + Assert.That(quacker!.UserId.ToString(), Is.EqualTo(response.Data!.UserId)); + Assert.That(quacker!.Username, Is.EqualTo("a_lil_guy")); + + // Undo + success = context.Database.ReallowEmailDomain(addressToBlockWith); + Assert.That(success, Is.True); + context.Database.Refresh(); + Assert.That(context.Database.IsEmailDomainDisallowed(addressToBlockWith), Is.False); + Assert.That(context.Database.IsEmailDomainDisallowed(domain), Is.False); + Assert.That(context.Database.GetDisallowedEmailDomainInfo(addressToBlockWith), Is.Null); + + // Try to undo again + success = context.Database.ReallowEmailDomain(addressToBlockWith); + Assert.That(success, Is.False); + context.Database.Refresh(); + Assert.That(context.Database.IsEmailDomainDisallowed(addressToBlockWith), Is.False); + Assert.That(context.Database.IsEmailDomainDisallowed(domain), Is.False); + Assert.That(context.Database.GetDisallowedEmailDomainInfo(addressToBlockWith), Is.Null); + } + + [Test] + [TestCase("a_lil_guy")] + [TestCase("a_LiL_guY")] + [TestCase("A_LIL_GUY")] + public void CannotRegisterAccountWithDisallowedUsernameCaseInsensitively(string username) + { + using TestContext context = this.GetServer(); + const string usernameLower = "a_lil_guy"; + const string disallowReason = "writing these is fun lol"; + + // Not somehow already disallowed + Assert.That(context.Database.IsUserDisallowed(username), Is.False); + Assert.That(context.Database.GetDisallowedUserInfo(username), Is.Null); + + // Disallow + (DisallowedUser disallowanceReturn, bool success) = context.Database.DisallowUser(username, disallowReason); + Assert.That(disallowanceReturn.Username, Is.EqualTo(usernameLower)); + Assert.That(disallowanceReturn.Reason, Is.EqualTo(disallowReason)); + Assert.That(success, Is.True); + context.Database.Refresh(); + + // Try to disallow again + (disallowanceReturn, success) = context.Database.DisallowUser(username, disallowReason); + Assert.That(disallowanceReturn.Username, Is.EqualTo(usernameLower)); + Assert.That(disallowanceReturn.Reason, Is.EqualTo(disallowReason)); + Assert.That(success, Is.False); + context.Database.Refresh(); + + // Ensure it's disallowed + Assert.That(context.Database.IsUserDisallowed(username), Is.True); + DisallowedUser? disallowed = context.Database.GetDisallowedUserInfo(username); + Assert.That(disallowed, Is.Not.Null); + Assert.That(disallowed!.Username, Is.EqualTo(usernameLower)); + Assert.That(disallowed!.Reason, Is.EqualTo(disallowReason)); + + // Try to register + ApiResponse? response = context.Http.PostData("/api/v3/register", new ApiRegisterRequest + { + Username = username, + EmailAddress = "guy@lil.com", + PasswordSha512 = "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff", + }, false, true); + Assert.That(response, Is.Not.Null); + Assert.That(response!.Error, Is.Not.EqualTo(null)); + Assert.That(response.Error!.Name, Is.EqualTo("ApiAuthenticationError")); + + // Ensure registration actually failed + context.Database.Refresh(); + Assert.That(context.Database.GetUserByUsername(username), Is.Null); + + // Undo + success = context.Database.ReallowUser(username); + Assert.That(success, Is.True); + context.Database.Refresh(); + Assert.That(context.Database.IsUserDisallowed(username), Is.False); + Assert.That(context.Database.GetDisallowedUserInfo(username), Is.Null); + + // Try to undo again + success = context.Database.ReallowUser(username); + Assert.That(success, Is.False); + context.Database.Refresh(); + Assert.That(context.Database.IsUserDisallowed(username), Is.False); + Assert.That(context.Database.GetDisallowedUserInfo(username), Is.Null); + } +} \ No newline at end of file From 4836307a09a3c79e08c7d82a6cb985a8812b6d0a Mon Sep 17 00:00:00 2001 From: Toaster2 Date: Sat, 30 May 2026 15:22:28 +0200 Subject: [PATCH 7/8] Test asset disallowance case-insensitivety, test IsAssetDisallowed method --- .../Tests/Assets/AssetDisallowanceTests.cs | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/RefreshTests.GameServer/Tests/Assets/AssetDisallowanceTests.cs b/RefreshTests.GameServer/Tests/Assets/AssetDisallowanceTests.cs index bbeaba9b..d2597e7a 100644 --- a/RefreshTests.GameServer/Tests/Assets/AssetDisallowanceTests.cs +++ b/RefreshTests.GameServer/Tests/Assets/AssetDisallowanceTests.cs @@ -14,35 +14,42 @@ namespace RefreshTests.GameServer.Tests.Assets; public class AssetDisallowanceTests : GameServerTest { [Test] - public void CanDisallowAndReallowAsset() + [TestCase("trash")] + [TestCase("Trash")] + [TestCase("TRASH")] + public void CanDisallowAndReallowAssetCaseInsensitively(string hash) { using TestContext context = this.GetServer(); - string hash = "trash"; + string hashLower = hash.ToLower(); GameAssetType type = GameAssetType.Mesh; // Ensure that the asset isn't already disallowed + Assert.That(context.Database.IsAssetDisallowed(hash), Is.False); Assert.That(context.Database.GetDisallowedAssetInfo(hash), Is.Null); // Disallow (DisallowedAsset disallowed, bool success) = context.Database.DisallowAsset(hash, type, "too ugly"); Assert.That(success, Is.True); - Assert.That(disallowed.AssetHash, Is.EqualTo(hash)); + Assert.That(disallowed.AssetHash, Is.EqualTo(hashLower)); Assert.That(disallowed.AssetType, Is.EqualTo(type)); Assert.That(disallowed.Reason, Is.EqualTo("too ugly")); + context.Database.Refresh(); - // Ensure that the same entity is gotten again, and the DB method doesn't try to insert a new one + // Try to disallow again, and ensure the DB method doesn't try to insert a new one (disallowed, success) = context.Database.DisallowAsset(hash, type, "too ugly"); Assert.That(success, Is.False); - Assert.That(disallowed.AssetHash, Is.EqualTo(hash)); + Assert.That(disallowed.AssetHash, Is.EqualTo(hashLower)); Assert.That(disallowed.AssetType, Is.EqualTo(type)); Assert.That(disallowed.Reason, Is.EqualTo("too ugly")); + context.Database.Refresh(); // ensure that the separately gotten entity is also the same + Assert.That(context.Database.IsAssetDisallowed(hash), Is.True); DisallowedAsset? gottenAgain = context.Database.GetDisallowedAssetInfo(hash); Assert.That(gottenAgain, Is.Not.Null); Assert.That(success, Is.False); - Assert.That(disallowed.AssetHash, Is.EqualTo(hash)); + Assert.That(disallowed.AssetHash, Is.EqualTo(hashLower)); Assert.That(disallowed.AssetType, Is.EqualTo(type)); Assert.That(disallowed.Reason, Is.EqualTo("too ugly")); @@ -52,11 +59,13 @@ public void CanDisallowAndReallowAsset() // Reallow success = context.Database.ReallowAsset(hash); Assert.That(success, Is.True); + Assert.That(context.Database.IsAssetDisallowed(hash), Is.False); Assert.That(context.Database.GetDisallowedAssetInfo(hash), Is.Null); // Reallow again success = context.Database.ReallowAsset(hash); Assert.That(success, Is.False); + Assert.That(context.Database.IsAssetDisallowed(hash), Is.False); Assert.That(context.Database.GetDisallowedAssetInfo(hash), Is.Null); } From 611da3dad7c9e8a96bef3180dca4d765c4e7f3e8 Mon Sep 17 00:00:00 2001 From: Toaster2 Date: Sat, 6 Jun 2026 12:20:24 +0200 Subject: [PATCH 8/8] Add "Lower" to some disallowance primary key names for clarity --- .../GameDatabaseContext.Registration.cs | 18 +++---- ...6101615_RenameDisallowancePKsForClarity.cs | 51 +++++++++++++++++++ .../GameDatabaseContextModelSnapshot.cs | 13 +++-- .../Models/Users/DisallowedEmailAddress.cs | 2 +- .../Models/Users/DisallowedEmailDomain.cs | 2 +- .../Models/Users/DisallowedUser.cs | 2 +- .../Tests/Users/UserDisallowanceTests.cs | 18 +++---- 7 files changed, 78 insertions(+), 28 deletions(-) create mode 100644 Refresh.Database/Migrations/20260606101615_RenameDisallowancePKsForClarity.cs diff --git a/Refresh.Database/GameDatabaseContext.Registration.cs b/Refresh.Database/GameDatabaseContext.Registration.cs index 122bf2f0..58e2a531 100644 --- a/Refresh.Database/GameDatabaseContext.Registration.cs +++ b/Refresh.Database/GameDatabaseContext.Registration.cs @@ -221,13 +221,13 @@ public void RemoveEmailVerificationCode(EmailVerificationCode code) public bool IsUserDisallowed(string username) { string lowercaseUsername = username.ToLower(); - return this.DisallowedUsers.Any(u => u.Username == lowercaseUsername); + return this.DisallowedUsers.Any(u => u.UsernameLower == lowercaseUsername); } public DisallowedUser? GetDisallowedUserInfo(string username) { string lowercaseUsername = username.ToLower(); - return this.DisallowedUsers.FirstOrDefault(d => d.Username == lowercaseUsername); + return this.DisallowedUsers.FirstOrDefault(d => d.UsernameLower == lowercaseUsername); } public DatabaseList GetDisallowedUsers(int skip, int count) @@ -241,7 +241,7 @@ public DatabaseList GetDisallowedUsers(int skip, int count) DisallowedUser disallowed = new() { - Username = lowercaseUsername, + UsernameLower = lowercaseUsername, Reason = reason, DisallowedAt = this._time.Now, }; @@ -267,13 +267,13 @@ public bool ReallowUser(string username) public bool IsEmailAddressDisallowed(string emailAddress) { string emailAddressLower = emailAddress.ToLowerInvariant(); - return this.DisallowedEmailAddresses.Any(u => u.Address == emailAddressLower); + return this.DisallowedEmailAddresses.Any(u => u.AddressLower == emailAddressLower); } public DisallowedEmailAddress? GetDisallowedEmailAddressInfo(string emailAddress) { string emailAddressLower = emailAddress.ToLowerInvariant(); - return this.DisallowedEmailAddresses.FirstOrDefault(d => d.Address == emailAddressLower); + return this.DisallowedEmailAddresses.FirstOrDefault(d => d.AddressLower == emailAddressLower); } public DatabaseList GetDisallowedEmailAddresses(int skip, int count) @@ -287,7 +287,7 @@ public DatabaseList GetDisallowedEmailAddresses(int skip DisallowedEmailAddress disallowed = new() { - Address = emailAddressLower, + AddressLower = emailAddressLower, Reason = reason, DisallowedAt = this._time.Now, }; @@ -316,13 +316,13 @@ private string GetLowercaseEmailDomainFromAddress(string emailAddress) public bool IsEmailDomainDisallowed(string emailAddress) { string emailDomainLower = this.GetLowercaseEmailDomainFromAddress(emailAddress); - return this.DisallowedEmailDomains.Any(u => u.Domain == emailDomainLower); + return this.DisallowedEmailDomains.Any(u => u.DomainLower == emailDomainLower); } public DisallowedEmailDomain? GetDisallowedEmailDomainInfo(string emailAddress) { string emailDomainLower = this.GetLowercaseEmailDomainFromAddress(emailAddress); - return this.DisallowedEmailDomains.FirstOrDefault(d => d.Domain == emailDomainLower); + return this.DisallowedEmailDomains.FirstOrDefault(d => d.DomainLower == emailDomainLower); } public DatabaseList GetDisallowedEmailDomains(int skip, int count) @@ -336,7 +336,7 @@ public DatabaseList GetDisallowedEmailDomains(int skip, i DisallowedEmailDomain disallowed = new() { - Domain = emailDomainLower, + DomainLower = emailDomainLower, Reason = reason, DisallowedAt = this._time.Now, }; diff --git a/Refresh.Database/Migrations/20260606101615_RenameDisallowancePKsForClarity.cs b/Refresh.Database/Migrations/20260606101615_RenameDisallowancePKsForClarity.cs new file mode 100644 index 00000000..dbc1b4e6 --- /dev/null +++ b/Refresh.Database/Migrations/20260606101615_RenameDisallowancePKsForClarity.cs @@ -0,0 +1,51 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Refresh.Database.Migrations +{ + /// + [DbContext(typeof(GameDatabaseContext))] + [Migration("20260606101615_RenameDisallowancePKsForClarity")] + public partial class RenameDisallowancePKsForClarity : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "Username", + table: "DisallowedUsers", + newName: "UsernameLower"); + + migrationBuilder.RenameColumn( + name: "Domain", + table: "DisallowedEmailDomains", + newName: "DomainLower"); + + migrationBuilder.RenameColumn( + name: "Address", + table: "DisallowedEmailAddresses", + newName: "AddressLower"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "UsernameLower", + table: "DisallowedUsers", + newName: "Username"); + + migrationBuilder.RenameColumn( + name: "DomainLower", + table: "DisallowedEmailDomains", + newName: "Domain"); + + migrationBuilder.RenameColumn( + name: "AddressLower", + table: "DisallowedEmailAddresses", + newName: "Address"); + } + } +} diff --git a/Refresh.Database/Migrations/GameDatabaseContextModelSnapshot.cs b/Refresh.Database/Migrations/GameDatabaseContextModelSnapshot.cs index 9316c4da..86774e7d 100644 --- a/Refresh.Database/Migrations/GameDatabaseContextModelSnapshot.cs +++ b/Refresh.Database/Migrations/GameDatabaseContextModelSnapshot.cs @@ -1532,7 +1532,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Refresh.Database.Models.Users.DisallowedEmailAddress", b => { - b.Property("Address") + b.Property("AddressLower") .HasColumnType("text"); b.Property("DisallowedAt") @@ -1541,14 +1541,14 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Reason") .HasColumnType("text"); - b.HasKey("Address"); + b.HasKey("AddressLower"); b.ToTable("DisallowedEmailAddresses"); }); modelBuilder.Entity("Refresh.Database.Models.Users.DisallowedEmailDomain", b => { - b.Property("Domain") + b.Property("DomainLower") .HasColumnType("text"); b.Property("DisallowedAt") @@ -1557,14 +1557,14 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Reason") .HasColumnType("text"); - b.HasKey("Domain"); + b.HasKey("DomainLower"); b.ToTable("DisallowedEmailDomains"); }); modelBuilder.Entity("Refresh.Database.Models.Users.DisallowedUser", b => { - b.Property("Username") + b.Property("UsernameLower") .HasColumnType("text"); b.Property("DisallowedAt") @@ -1573,7 +1573,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Reason") .HasColumnType("text"); - b.HasKey("Username"); + b.HasKey("UsernameLower"); b.ToTable("DisallowedUsers"); }); @@ -1605,7 +1605,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ExpiryDate") .HasColumnType("timestamp with time zone"); - // EF badly wanted to move this below ExpiryDate fsr b.Property("UploadCount") .HasColumnType("integer"); diff --git a/Refresh.Database/Models/Users/DisallowedEmailAddress.cs b/Refresh.Database/Models/Users/DisallowedEmailAddress.cs index 5a8db89f..f08153d4 100644 --- a/Refresh.Database/Models/Users/DisallowedEmailAddress.cs +++ b/Refresh.Database/Models/Users/DisallowedEmailAddress.cs @@ -8,7 +8,7 @@ public partial class DisallowedEmailAddress /// Lower-case email address to allow case-insensitive lookup. /// [Key] - public string Address { get; set; } + public string AddressLower { get; set; } public string Reason { get; set; } public DateTimeOffset DisallowedAt { get; set; } } \ No newline at end of file diff --git a/Refresh.Database/Models/Users/DisallowedEmailDomain.cs b/Refresh.Database/Models/Users/DisallowedEmailDomain.cs index 1a3ae239..1ee0e7d7 100644 --- a/Refresh.Database/Models/Users/DisallowedEmailDomain.cs +++ b/Refresh.Database/Models/Users/DisallowedEmailDomain.cs @@ -8,7 +8,7 @@ public partial class DisallowedEmailDomain /// Lower-case email domain to allow case-insensitive lookup. /// [Key] - public string Domain { get; set; } + public string DomainLower { get; set; } public string Reason { get; set; } public DateTimeOffset DisallowedAt { get; set; } } \ No newline at end of file diff --git a/Refresh.Database/Models/Users/DisallowedUser.cs b/Refresh.Database/Models/Users/DisallowedUser.cs index d48bd81f..b2f5424e 100644 --- a/Refresh.Database/Models/Users/DisallowedUser.cs +++ b/Refresh.Database/Models/Users/DisallowedUser.cs @@ -8,7 +8,7 @@ public partial class DisallowedUser /// Lower-case username to allow case-insensitive lookup. /// [Key] - public string Username { get; set; } + public string UsernameLower { get; set; } public string Reason { get; set; } public DateTimeOffset DisallowedAt { get; set; } } \ No newline at end of file diff --git a/RefreshTests.GameServer/Tests/Users/UserDisallowanceTests.cs b/RefreshTests.GameServer/Tests/Users/UserDisallowanceTests.cs index 608e4bf5..b6da0bb2 100644 --- a/RefreshTests.GameServer/Tests/Users/UserDisallowanceTests.cs +++ b/RefreshTests.GameServer/Tests/Users/UserDisallowanceTests.cs @@ -24,14 +24,14 @@ public void CannotRegisterAccountWithDisallowedEmailAddressCaseInsensitively(str // Disallow (DisallowedEmailAddress disallowanceReturn, bool success) = context.Database.DisallowEmailAddress(emailAddress, disallowReason); - Assert.That(disallowanceReturn.Address, Is.EqualTo(emailAddressLower)); + Assert.That(disallowanceReturn.AddressLower, Is.EqualTo(emailAddressLower)); Assert.That(disallowanceReturn.Reason, Is.EqualTo(disallowReason)); Assert.That(success, Is.True); context.Database.Refresh(); // Try to disallow again (disallowanceReturn, success) = context.Database.DisallowEmailAddress(emailAddress, disallowReason); - Assert.That(disallowanceReturn.Address, Is.EqualTo(emailAddressLower)); + Assert.That(disallowanceReturn.AddressLower, Is.EqualTo(emailAddressLower)); Assert.That(disallowanceReturn.Reason, Is.EqualTo(disallowReason)); Assert.That(success, Is.False); context.Database.Refresh(); @@ -40,7 +40,7 @@ public void CannotRegisterAccountWithDisallowedEmailAddressCaseInsensitively(str Assert.That(context.Database.IsEmailAddressDisallowed(emailAddress), Is.True); DisallowedEmailAddress? disallowed = context.Database.GetDisallowedEmailAddressInfo(emailAddress); Assert.That(disallowed, Is.Not.Null); - Assert.That(disallowed!.Address, Is.EqualTo(emailAddressLower)); + Assert.That(disallowed!.AddressLower, Is.EqualTo(emailAddressLower)); Assert.That(disallowed!.Reason, Is.EqualTo(disallowReason)); // Try to register @@ -90,14 +90,14 @@ public void CannotRegisterAccountsWithDisallowedEmailDomainCaseInsensitively(str // Disallow (DisallowedEmailDomain disallowanceReturn, bool success) = context.Database.DisallowEmailDomain(addressToBlockWith, disallowReason); - Assert.That(disallowanceReturn.Domain, Is.EqualTo(domain)); + Assert.That(disallowanceReturn.DomainLower, Is.EqualTo(domain)); Assert.That(disallowanceReturn.Reason, Is.EqualTo(disallowReason)); Assert.That(success, Is.True); context.Database.Refresh(); // Try to disallow again (disallowanceReturn, success) = context.Database.DisallowEmailDomain(addressToBlockWith, disallowReason); - Assert.That(disallowanceReturn.Domain, Is.EqualTo(domain)); + Assert.That(disallowanceReturn.DomainLower, Is.EqualTo(domain)); Assert.That(disallowanceReturn.Reason, Is.EqualTo(disallowReason)); Assert.That(success, Is.False); context.Database.Refresh(); @@ -107,7 +107,7 @@ public void CannotRegisterAccountsWithDisallowedEmailDomainCaseInsensitively(str Assert.That(context.Database.IsEmailDomainDisallowed(domain), Is.True); DisallowedEmailDomain? disallowed = context.Database.GetDisallowedEmailDomainInfo(addressToBlockWith); Assert.That(disallowed, Is.Not.Null); - Assert.That(disallowed!.Domain, Is.EqualTo(domain)); + Assert.That(disallowed!.DomainLower, Is.EqualTo(domain)); Assert.That(disallowed!.Reason, Is.EqualTo(disallowReason)); // Attempt 1 (block) @@ -200,14 +200,14 @@ public void CannotRegisterAccountWithDisallowedUsernameCaseInsensitively(string // Disallow (DisallowedUser disallowanceReturn, bool success) = context.Database.DisallowUser(username, disallowReason); - Assert.That(disallowanceReturn.Username, Is.EqualTo(usernameLower)); + Assert.That(disallowanceReturn.UsernameLower, Is.EqualTo(usernameLower)); Assert.That(disallowanceReturn.Reason, Is.EqualTo(disallowReason)); Assert.That(success, Is.True); context.Database.Refresh(); // Try to disallow again (disallowanceReturn, success) = context.Database.DisallowUser(username, disallowReason); - Assert.That(disallowanceReturn.Username, Is.EqualTo(usernameLower)); + Assert.That(disallowanceReturn.UsernameLower, Is.EqualTo(usernameLower)); Assert.That(disallowanceReturn.Reason, Is.EqualTo(disallowReason)); Assert.That(success, Is.False); context.Database.Refresh(); @@ -216,7 +216,7 @@ public void CannotRegisterAccountWithDisallowedUsernameCaseInsensitively(string Assert.That(context.Database.IsUserDisallowed(username), Is.True); DisallowedUser? disallowed = context.Database.GetDisallowedUserInfo(username); Assert.That(disallowed, Is.Not.Null); - Assert.That(disallowed!.Username, Is.EqualTo(usernameLower)); + Assert.That(disallowed!.UsernameLower, Is.EqualTo(usernameLower)); Assert.That(disallowed!.Reason, Is.EqualTo(disallowReason)); // Try to register