diff --git a/Refresh.Database/GameDatabaseContext.Assets.cs b/Refresh.Database/GameDatabaseContext.Assets.cs
index 7d2ab535..d29da1f2 100644
--- a/Refresh.Database/GameDatabaseContext.Assets.cs
+++ b/Refresh.Database/GameDatabaseContext.Assets.cs
@@ -111,20 +111,30 @@ 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)
- => 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..58e2a531 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.UsernameLower == lowercaseUsername);
+ }
public DisallowedUser? GetDisallowedUserInfo(string username)
- => this.DisallowedUsers.FirstOrDefault(d => d.Username == username);
+ {
+ string lowercaseUsername = username.ToLower();
+ return this.DisallowedUsers.FirstOrDefault(d => d.UsernameLower == lowercaseUsername);
+ }
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,
+ UsernameLower = 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.AddressLower == emailAddressLower);
+ }
public DisallowedEmailAddress? GetDisallowedEmailAddressInfo(string emailAddress)
- => this.DisallowedEmailAddresses.FirstOrDefault(d => d.Address == emailAddress);
+ {
+ string emailAddressLower = emailAddress.ToLowerInvariant();
+ return this.DisallowedEmailAddresses.FirstOrDefault(d => d.AddressLower == 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,
+ AddressLower = 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.DomainLower == 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.DomainLower == 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,
+ DomainLower = 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;
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/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 b04f829c..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");
});
@@ -1602,12 +1602,12 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.Property("Entity")
.HasColumnType("smallint");
- b.Property("UploadCount")
- .HasColumnType("integer");
-
b.Property("ExpiryDate")
.HasColumnType("timestamp with time zone");
+ b.Property("UploadCount")
+ .HasColumnType("integer");
+
b.HasKey("UserId", "Entity");
b.ToTable("EntityUploadRateLimits");
diff --git a/Refresh.Database/Models/Users/DisallowedEmailAddress.cs b/Refresh.Database/Models/Users/DisallowedEmailAddress.cs
index a54c75df..f08153d4 100644
--- a/Refresh.Database/Models/Users/DisallowedEmailAddress.cs
+++ b/Refresh.Database/Models/Users/DisallowedEmailAddress.cs
@@ -4,8 +4,11 @@ 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 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 ae3e297d..1ee0e7d7 100644
--- a/Refresh.Database/Models/Users/DisallowedEmailDomain.cs
+++ b/Refresh.Database/Models/Users/DisallowedEmailDomain.cs
@@ -4,8 +4,11 @@ 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 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 7dcd2beb..b2f5424e 100644
--- a/Refresh.Database/Models/Users/DisallowedUser.cs
+++ b/Refresh.Database/Models/Users/DisallowedUser.cs
@@ -4,8 +4,11 @@ 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 UsernameLower { get; set; }
public string Reason { get; set; }
public DateTimeOffset DisallowedAt { get; set; }
}
\ No newline at end of file
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;
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/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);
}
diff --git a/RefreshTests.GameServer/Tests/Users/UserDisallowanceTests.cs b/RefreshTests.GameServer/Tests/Users/UserDisallowanceTests.cs
new file mode 100644
index 00000000..b6da0bb2
--- /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.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.AddressLower, 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!.AddressLower, 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.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.DomainLower, 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!.DomainLower, 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.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.UsernameLower, 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!.UsernameLower, 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