From beac85a6417d76952406d16fa17f5cd0eac377e2 Mon Sep 17 00:00:00 2001 From: Toaster2 Date: Tue, 31 Mar 2026 16:07:09 +0200 Subject: [PATCH 1/7] Introduce email domain blocking to DB, differenciate from address blocking --- .../GameDatabaseContext.Registration.cs | 65 +++++++++++++++---- Refresh.Database/GameDatabaseContext.cs | 3 +- .../Models/Users/DisallowedEmail.cs | 9 --- .../Models/Users/DisallowedEmailAddress.cs | 9 +++ .../Models/Users/DisallowedEmailDomain.cs | 9 +++ .../Tests/ApiV3/UserApiTests.cs | 4 +- .../Tests/Users/UserActionTests.cs | 4 +- 7 files changed, 78 insertions(+), 25 deletions(-) delete mode 100644 Refresh.Database/Models/Users/DisallowedEmail.cs create mode 100644 Refresh.Database/Models/Users/DisallowedEmailAddress.cs create mode 100644 Refresh.Database/Models/Users/DisallowedEmailDomain.cs diff --git a/Refresh.Database/GameDatabaseContext.Registration.cs b/Refresh.Database/GameDatabaseContext.Registration.cs index ba44dfa2a..a00fccccd 100644 --- a/Refresh.Database/GameDatabaseContext.Registration.cs +++ b/Refresh.Database/GameDatabaseContext.Registration.cs @@ -115,7 +115,7 @@ public bool IsEmailTaken(string emailAddress) { return this.GameUsers.Any(u => u.EmailAddress == emailAddress) || this.QueuedRegistrations.Any(r => r.EmailAddress == emailAddress) || - this.IsEmailDisallowed(emailAddress); + this.IsEmailAddressDisallowed(emailAddress); } public void AddRegistrationToQueue(string username, string emailAddress, string passwordBcrypt) @@ -253,34 +253,77 @@ public bool IsUserDisallowed(string username) return this.DisallowedUsers.FirstOrDefault(u => u.Username == username) != null; } - public bool DisallowEmail(string email) + public bool DisallowEmailAddress(string emailAddress) { - if (this.IsEmailDisallowed(email)) + if (this.IsEmailAddressDisallowed(emailAddress)) return false; - this.DisallowedEmails.Add(new() + this.DisallowedEmailAddresses.Add(new() { - Email = email, + Address = emailAddress, }); this.SaveChanges(); return true; } - public bool ReallowEmail(string email) + public bool ReallowEmailAddress(string emailAddress) { - DisallowedEmail? disallowedEmail = this.DisallowedEmails.FirstOrDefault(u => u.Email == email); - if (disallowedEmail == null) + DisallowedEmailAddress? DisallowedEmailAddress = this.DisallowedEmailAddresses.FirstOrDefault(u => u.Address == emailAddress); + if (DisallowedEmailAddress == null) return false; - this.DisallowedEmails.Remove(disallowedEmail); + this.DisallowedEmailAddresses.Remove(DisallowedEmailAddress); this.SaveChanges(); return true; } - public bool IsEmailDisallowed(string email) + public bool IsEmailAddressDisallowed(string emailAddress) { - return this.DisallowedEmails.Any(u => u.Email == email); + return this.DisallowedEmailAddresses.Any(u => u.Address == emailAddress); + } + + private string GetEmailDomainFromAddress(string emailAddress) + => emailAddress.Split('@').Last(); + + public bool DisallowEmailDomainByAddress(string emailAddress) + => this.DisallowEmailDomain(this.GetEmailDomainFromAddress(emailAddress)); + + public bool DisallowEmailDomain(string emailDomain) + { + if (this.IsEmailDomainDisallowed(emailDomain)) + return false; + + this.DisallowedEmailDomains.Add(new() + { + Domain = emailDomain, + }); + this.SaveChanges(); + + return true; + } + + public bool ReallowEmailDomainByAddress(string emailAddress) + => this.ReallowEmailDomain(this.GetEmailDomainFromAddress(emailAddress)); + + public bool ReallowEmailDomain(string emailDomain) + { + DisallowedEmailDomain? disallowedDomain = this.DisallowedEmailDomains.FirstOrDefault(u => u.Domain == emailDomain); + if (disallowedDomain == null) + return false; + + this.DisallowedEmailDomains.Remove(disallowedDomain); + this.SaveChanges(); + + return true; + } + + public bool IsEmailDomainDisallowedByAddress(string emailAddress) + => this.IsEmailDomainDisallowed(this.GetEmailDomainFromAddress(emailAddress)); + + public bool IsEmailDomainDisallowed(string emailDomain) + { + return this.DisallowedEmailDomains.Any(u => u.Domain == emailDomain); } } \ No newline at end of file diff --git a/Refresh.Database/GameDatabaseContext.cs b/Refresh.Database/GameDatabaseContext.cs index 2acf5b115..ea8516682 100644 --- a/Refresh.Database/GameDatabaseContext.cs +++ b/Refresh.Database/GameDatabaseContext.cs @@ -64,7 +64,8 @@ public partial class GameDatabaseContext : DbContext, IDatabaseContext internal DbSet AssetDependencyRelations { get; set; } internal DbSet GameReviews { get; set; } internal DbSet DisallowedUsers { get; set; } - internal DbSet DisallowedEmails { get; set; } + internal DbSet DisallowedEmailAddresses { get; set; } + internal DbSet DisallowedEmailDomains { get; set; } internal DbSet RateReviewRelations { get; set; } internal DbSet TagLevelRelations { get; set; } internal DbSet GamePlaylists { get; set; } diff --git a/Refresh.Database/Models/Users/DisallowedEmail.cs b/Refresh.Database/Models/Users/DisallowedEmail.cs deleted file mode 100644 index 27889601c..000000000 --- a/Refresh.Database/Models/Users/DisallowedEmail.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Refresh.Database.Models.Users; - -#nullable disable - -public partial class DisallowedEmail -{ - [Key] - public string Email { get; set; } -} \ No newline at end of file diff --git a/Refresh.Database/Models/Users/DisallowedEmailAddress.cs b/Refresh.Database/Models/Users/DisallowedEmailAddress.cs new file mode 100644 index 000000000..2cbdb2027 --- /dev/null +++ b/Refresh.Database/Models/Users/DisallowedEmailAddress.cs @@ -0,0 +1,9 @@ +namespace Refresh.Database.Models.Users; + +#nullable disable + +public partial class DisallowedEmailAddress +{ + [Key] + public string Address { get; set; } +} \ No newline at end of file diff --git a/Refresh.Database/Models/Users/DisallowedEmailDomain.cs b/Refresh.Database/Models/Users/DisallowedEmailDomain.cs new file mode 100644 index 000000000..d0b69039e --- /dev/null +++ b/Refresh.Database/Models/Users/DisallowedEmailDomain.cs @@ -0,0 +1,9 @@ +namespace Refresh.Database.Models.Users; + +#nullable disable + +public partial class DisallowedEmailDomain +{ + [Key] + public string Domain { get; set; } +} \ No newline at end of file diff --git a/RefreshTests.GameServer/Tests/ApiV3/UserApiTests.cs b/RefreshTests.GameServer/Tests/ApiV3/UserApiTests.cs index fdcc370da..b585ce13e 100644 --- a/RefreshTests.GameServer/Tests/ApiV3/UserApiTests.cs +++ b/RefreshTests.GameServer/Tests/ApiV3/UserApiTests.cs @@ -47,12 +47,12 @@ public void RegisterAccount() } [Test] - public void CannotRegisterAccountWithDisallowedEmail() + public void CannotRegisterAccountWithDisallowedEmailAddress() { using TestContext context = this.GetServer(); const string email = "guy@lil.com"; - context.Database.DisallowEmail(email); + context.Database.DisallowEmailAddress(email); ApiResponse? response = context.Http.PostData("/api/v3/register", new ApiRegisterRequest { diff --git a/RefreshTests.GameServer/Tests/Users/UserActionTests.cs b/RefreshTests.GameServer/Tests/Users/UserActionTests.cs index 20e3b7479..22a9d6d79 100644 --- a/RefreshTests.GameServer/Tests/Users/UserActionTests.cs +++ b/RefreshTests.GameServer/Tests/Users/UserActionTests.cs @@ -88,13 +88,13 @@ public void DeletingUserDoesNotDisallowEmail() using TestContext context = this.GetServer(); GameUser publisher = context.CreateUser(); Assert.That(publisher.EmailAddress, Is.Not.Null); - Assert.That(context.Database.IsEmailDisallowed(publisher.EmailAddress!), Is.False); + Assert.That(context.Database.IsEmailAddressDisallowed(publisher.EmailAddress!), Is.False); string email = publisher.EmailAddress!; // Delete publisher context.Database.DeleteUser(publisher); context.Database.Refresh(); - Assert.That(context.Database.IsEmailDisallowed(email), Is.False); + Assert.That(context.Database.IsEmailAddressDisallowed(email), Is.False); } } \ No newline at end of file From 57f433170bbf54cb77dbfd37e18b3b1ab0989460 Mon Sep 17 00:00:00 2001 From: Toaster2 Date: Tue, 31 Mar 2026 16:08:01 +0200 Subject: [PATCH 2/7] Block registrations if email domain is disallowed --- .../Endpoints/AuthenticationApiEndpoints.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Refresh.Interfaces.APIv3/Endpoints/AuthenticationApiEndpoints.cs b/Refresh.Interfaces.APIv3/Endpoints/AuthenticationApiEndpoints.cs index a2d87a3ba..941620b28 100644 --- a/Refresh.Interfaces.APIv3/Endpoints/AuthenticationApiEndpoints.cs +++ b/Refresh.Interfaces.APIv3/Endpoints/AuthenticationApiEndpoints.cs @@ -347,7 +347,7 @@ public ApiResponse Register(RequestContext context, if (!smtpService.CheckEmailDomainValidity(body.EmailAddress)) return ApiValidationError.EmailDoesNotActuallyExistError; - if (database.IsUserDisallowed(body.Username) || database.IsEmailDisallowed(body.EmailAddress)) + if (database.IsUserDisallowed(body.Username) || database.IsEmailAddressDisallowed(body.EmailAddress) || database.IsEmailDomainDisallowedByAddress(body.EmailAddress)) return new ApiAuthenticationError("You aren't allowed to play on this instance."); if (!database.IsUsernameValid(body.Username)) From 86e8605213412c512eabd3aae0ec114e9403edbf Mon Sep 17 00:00:00 2001 From: Toaster2 Date: Tue, 31 Mar 2026 16:14:16 +0200 Subject: [PATCH 3/7] Ability to disallow email domains using CLI --- .../GameDatabaseContext.Registration.cs | 6 +-- Refresh.GameServer/CommandLineManager.cs | 40 +++++++++++++++---- Refresh.GameServer/RefreshGameServer.cs | 26 +++++++++--- 3 files changed, 55 insertions(+), 17 deletions(-) diff --git a/Refresh.Database/GameDatabaseContext.Registration.cs b/Refresh.Database/GameDatabaseContext.Registration.cs index a00fccccd..a15fc1c52 100644 --- a/Refresh.Database/GameDatabaseContext.Registration.cs +++ b/Refresh.Database/GameDatabaseContext.Registration.cs @@ -290,7 +290,7 @@ private string GetEmailDomainFromAddress(string emailAddress) public bool DisallowEmailDomainByAddress(string emailAddress) => this.DisallowEmailDomain(this.GetEmailDomainFromAddress(emailAddress)); - public bool DisallowEmailDomain(string emailDomain) + private bool DisallowEmailDomain(string emailDomain) { if (this.IsEmailDomainDisallowed(emailDomain)) return false; @@ -307,7 +307,7 @@ public bool DisallowEmailDomain(string emailDomain) public bool ReallowEmailDomainByAddress(string emailAddress) => this.ReallowEmailDomain(this.GetEmailDomainFromAddress(emailAddress)); - public bool ReallowEmailDomain(string emailDomain) + private bool ReallowEmailDomain(string emailDomain) { DisallowedEmailDomain? disallowedDomain = this.DisallowedEmailDomains.FirstOrDefault(u => u.Domain == emailDomain); if (disallowedDomain == null) @@ -322,7 +322,7 @@ public bool ReallowEmailDomain(string emailDomain) public bool IsEmailDomainDisallowedByAddress(string emailAddress) => this.IsEmailDomainDisallowed(this.GetEmailDomainFromAddress(emailAddress)); - public bool IsEmailDomainDisallowed(string emailDomain) + private bool IsEmailDomainDisallowed(string emailDomain) { return this.DisallowedEmailDomains.Any(u => u.Domain == emailDomain); } diff --git a/Refresh.GameServer/CommandLineManager.cs b/Refresh.GameServer/CommandLineManager.cs index d87373406..bff16e94c 100644 --- a/Refresh.GameServer/CommandLineManager.cs +++ b/Refresh.GameServer/CommandLineManager.cs @@ -60,11 +60,17 @@ private class Options [Option("reallow-user", HelpText = "Re-allow a user to register. Username option is required if this is set.")] public bool ReallowUser { get; set; } - [Option("disallow-email", HelpText = "Disallow the email from being used by anyone in the future. Email option is required if this is set.")] - public bool DisallowEmail { get; set; } + [Option("disallow-email", HelpText = "Disallow the email address from being used by anyone in the future. Email option is required if this is set.")] + public bool DisallowEmailAddress { get; set; } - [Option("reallow-email", HelpText = "Re-allow the email to be used by anyone. Email option is required if this is set")] - public bool ReallowEmail { get; set; } + [Option("reallow-email", HelpText = "Re-allow the email address to be used for account registration. Email option is required if this is set")] + public bool ReallowEmailAddress { get; set; } + + [Option("disallow-email-domain", HelpText = "Disallow the email domain from being used by anyone in the future. Email option is required if this is set. If a whole Email address is given, only the substring after the last @ will be used.")] + public bool DisallowEmailDomain { get; set; } + + [Option("reallow-email-domain", HelpText = "Re-allow the email domain to be used by anyone. Email option is required if this is set. If a whole Email address is given, only the substring after the last @ will be used.")] + public bool ReallowEmailDomain { get; set; } [Option("rename-user", HelpText = "Changes a user's username. (old) username or Email option is required if this is set.")] public string? RenameUser { get; set; } @@ -194,24 +200,42 @@ private void StartWithOptions(Options options) } else Fail("No username was provided"); } - else if (options.DisallowEmail) + else if (options.DisallowEmailAddress) { if (options.EmailAddress != null) { - if (!this._server.DisallowEmail(options.EmailAddress)) + if (!this._server.DisallowEmailAddress(options.EmailAddress)) Fail("Email address is already disallowed"); } else Fail("No email address was provided"); } - else if (options.ReallowEmail) + else if (options.ReallowEmailAddress) { if (options.EmailAddress != null) { - if (!this._server.ReallowEmail(options.EmailAddress)) + if (!this._server.ReallowEmailAddress(options.EmailAddress)) Fail("Email address is already allowed"); } else Fail("No email address was provided"); } + else if (options.DisallowEmailDomain) + { + if (options.EmailAddress != null) + { + if (!this._server.DisallowEmailDomain(options.EmailAddress)) + Fail("Email domain is already disallowed"); + } + else Fail("No email domain was provided"); + } + else if (options.ReallowEmailDomain) + { + if (options.EmailAddress != null) + { + if (!this._server.ReallowEmailDomain(options.EmailAddress)) + Fail("Email domain is already allowed"); + } + else Fail("No email domain was provided"); + } else if (options.RenameUser != null) { if(string.IsNullOrWhiteSpace(options.RenameUser)) diff --git a/Refresh.GameServer/RefreshGameServer.cs b/Refresh.GameServer/RefreshGameServer.cs index a78ba4cc3..9c48c0fd3 100644 --- a/Refresh.GameServer/RefreshGameServer.cs +++ b/Refresh.GameServer/RefreshGameServer.cs @@ -285,18 +285,32 @@ public bool ReallowUser(string username) return context.ReallowUser(username); } - public bool DisallowEmail(string email) + public bool DisallowEmailAddress(string address) { using GameDatabaseContext context = this.GetContext(); - - return context.DisallowEmail(email); + + return context.DisallowEmailAddress(address); } - public bool ReallowEmail(string email) + public bool ReallowEmailAddress(string address) { using GameDatabaseContext context = this.GetContext(); - - return context.ReallowEmail(email); + + return context.ReallowEmailAddress(address); + } + + public bool DisallowEmailDomain(string domain) + { + using GameDatabaseContext context = this.GetContext(); + + return context.DisallowEmailDomainByAddress(domain); + } + + public bool ReallowEmailDomain(string domain) + { + using GameDatabaseContext context = this.GetContext(); + + return context.ReallowEmailDomainByAddress(domain); } public void RenameUser(GameUser user, string newUsername, bool force = false) From d5e5fd2424d964421b7665944f86dd5fd8eda76c Mon Sep 17 00:00:00 2001 From: Toaster2 Date: Tue, 31 Mar 2026 16:35:30 +0200 Subject: [PATCH 4/7] Test email domain blocking, adjust address blocking test --- .../Tests/ApiV3/UserApiTests.cs | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/RefreshTests.GameServer/Tests/ApiV3/UserApiTests.cs b/RefreshTests.GameServer/Tests/ApiV3/UserApiTests.cs index b585ce13e..f62d520f4 100644 --- a/RefreshTests.GameServer/Tests/ApiV3/UserApiTests.cs +++ b/RefreshTests.GameServer/Tests/ApiV3/UserApiTests.cs @@ -52,7 +52,12 @@ public void CannotRegisterAccountWithDisallowedEmailAddress() using TestContext context = this.GetServer(); const string email = "guy@lil.com"; + // Not somehow already disallowed + Assert.That(context.Database.IsEmailAddressDisallowed(email), Is.False); context.Database.DisallowEmailAddress(email); + + context.Database.Refresh(); + Assert.That(context.Database.IsEmailAddressDisallowed(email), Is.True); ApiResponse? response = context.Http.PostData("/api/v3/register", new ApiRegisterRequest { @@ -66,6 +71,86 @@ public void CannotRegisterAccountWithDisallowedEmailAddress() 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); + } + + [Test] + public void CannotRegisterAccountsWithDisallowedEmailDomain() + { + using TestContext context = this.GetServer(); + + const string email = "guy@moron.com"; + // Not somehow already disallowed + Assert.That(context.Database.IsEmailDomainDisallowedByAddress(email), Is.False); + context.Database.DisallowEmailDomainByAddress(email); + + context.Database.Refresh(); + Assert.That(context.Database.IsEmailDomainDisallowedByAddress(email), Is.True); + + // 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.ReallowEmailDomainByAddress(email); + context.Database.Refresh(); + Assert.That(context.Database.IsEmailDomainDisallowedByAddress(email), Is.False); } [Test] From f6c0a00ee72bfbae6986fa229ac77a7601f2c2e9 Mon Sep 17 00:00:00 2001 From: Toaster2 Date: Tue, 31 Mar 2026 16:53:56 +0200 Subject: [PATCH 5/7] Add email address/domain migration --- ...143640_AddAbilityToDisallowEmailDomains.cs | 45 +++++++++++++++++++ .../GameDatabaseContextModelSnapshot.cs | 18 ++++++-- 2 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 Refresh.Database/Migrations/20260331143640_AddAbilityToDisallowEmailDomains.cs diff --git a/Refresh.Database/Migrations/20260331143640_AddAbilityToDisallowEmailDomains.cs b/Refresh.Database/Migrations/20260331143640_AddAbilityToDisallowEmailDomains.cs new file mode 100644 index 000000000..52f69f3ef --- /dev/null +++ b/Refresh.Database/Migrations/20260331143640_AddAbilityToDisallowEmailDomains.cs @@ -0,0 +1,45 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Refresh.Database.Migrations +{ + /// + [DbContext(typeof(GameDatabaseContext))] + [Migration("20260331143640_AddAbilityToDisallowEmailDomains")] + public partial class AddAbilityToDisallowEmailDomains : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameTable(name: "DisallowedEmails", newName: "DisallowedEmailAddresses"); + migrationBuilder.DropPrimaryKey(name: "PK_DisallowedEmails", table: "DisallowedEmailAddresses"); + migrationBuilder.RenameColumn(name: "Email", table: "DisallowedEmailAddresses", newName: "Address"); + migrationBuilder.AddPrimaryKey(name: "PK_DisallowedEmailAddresses", table: "DisallowedEmailAddresses", column: "Address"); + + migrationBuilder.CreateTable( + name: "DisallowedEmailDomains", + columns: table => new + { + Domain = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DisallowedEmailDomains", x => x.Domain); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "DisallowedEmailDomains"); + + migrationBuilder.RenameTable(name: "DisallowedEmailAddresses", newName: "DisallowedEmails"); + migrationBuilder.DropPrimaryKey(name: "PK_DisallowedEmailAddresses", table: "DisallowedEmails"); + migrationBuilder.RenameColumn(name: "Address", table: "DisallowedEmails", newName: "Email"); + migrationBuilder.AddPrimaryKey(name: "PK_DisallowedEmails", table: "DisallowedEmails", column: "Email"); + } + } +} diff --git a/Refresh.Database/Migrations/GameDatabaseContextModelSnapshot.cs b/Refresh.Database/Migrations/GameDatabaseContextModelSnapshot.cs index 7d567b6c4..2ff6b5f44 100644 --- a/Refresh.Database/Migrations/GameDatabaseContextModelSnapshot.cs +++ b/Refresh.Database/Migrations/GameDatabaseContextModelSnapshot.cs @@ -1510,14 +1510,24 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("RequestStatistics"); }); - modelBuilder.Entity("Refresh.Database.Models.Users.DisallowedEmail", b => + modelBuilder.Entity("Refresh.Database.Models.Users.DisallowedEmailAddress", b => { - b.Property("Email") + b.Property("Address") .HasColumnType("text"); - b.HasKey("Email"); + b.HasKey("Address"); - b.ToTable("DisallowedEmails"); + b.ToTable("DisallowedEmailAddresses"); + }); + + modelBuilder.Entity("Refresh.Database.Models.Users.DisallowedEmailDomain", b => + { + b.Property("Domain") + .HasColumnType("text"); + + b.HasKey("Domain"); + + b.ToTable("DisallowedEmailDomains"); }); modelBuilder.Entity("Refresh.Database.Models.Users.DisallowedUser", b => From 5effb24599f72a6a2ba035237866374409687c36 Mon Sep 17 00:00:00 2001 From: Toaster2 Date: Sat, 4 Apr 2026 13:55:35 +0200 Subject: [PATCH 6/7] No separate DB methods for email domain blocking by address or domain --- .../GameDatabaseContext.Registration.cs | 18 ++++++------------ Refresh.GameServer/RefreshGameServer.cs | 4 ++-- .../Endpoints/AuthenticationApiEndpoints.cs | 2 +- .../Tests/ApiV3/UserApiTests.cs | 10 +++++----- 4 files changed, 14 insertions(+), 20 deletions(-) diff --git a/Refresh.Database/GameDatabaseContext.Registration.cs b/Refresh.Database/GameDatabaseContext.Registration.cs index a15fc1c52..919c647fa 100644 --- a/Refresh.Database/GameDatabaseContext.Registration.cs +++ b/Refresh.Database/GameDatabaseContext.Registration.cs @@ -287,11 +287,9 @@ public bool IsEmailAddressDisallowed(string emailAddress) private string GetEmailDomainFromAddress(string emailAddress) => emailAddress.Split('@').Last(); - public bool DisallowEmailDomainByAddress(string emailAddress) - => this.DisallowEmailDomain(this.GetEmailDomainFromAddress(emailAddress)); - - private bool DisallowEmailDomain(string emailDomain) + public bool DisallowEmailDomain(string emailAddress) { + string emailDomain = this.GetEmailDomainFromAddress(emailAddress); if (this.IsEmailDomainDisallowed(emailDomain)) return false; @@ -304,11 +302,9 @@ private bool DisallowEmailDomain(string emailDomain) return true; } - public bool ReallowEmailDomainByAddress(string emailAddress) - => this.ReallowEmailDomain(this.GetEmailDomainFromAddress(emailAddress)); - - private bool ReallowEmailDomain(string emailDomain) + public bool ReallowEmailDomain(string emailAddress) { + string emailDomain = this.GetEmailDomainFromAddress(emailAddress); DisallowedEmailDomain? disallowedDomain = this.DisallowedEmailDomains.FirstOrDefault(u => u.Domain == emailDomain); if (disallowedDomain == null) return false; @@ -319,11 +315,9 @@ private bool ReallowEmailDomain(string emailDomain) return true; } - public bool IsEmailDomainDisallowedByAddress(string emailAddress) - => this.IsEmailDomainDisallowed(this.GetEmailDomainFromAddress(emailAddress)); - - private bool IsEmailDomainDisallowed(string emailDomain) + public bool IsEmailDomainDisallowed(string emailAddress) { + string emailDomain = this.GetEmailDomainFromAddress(emailAddress); return this.DisallowedEmailDomains.Any(u => u.Domain == emailDomain); } } \ No newline at end of file diff --git a/Refresh.GameServer/RefreshGameServer.cs b/Refresh.GameServer/RefreshGameServer.cs index 9c48c0fd3..f012fafa1 100644 --- a/Refresh.GameServer/RefreshGameServer.cs +++ b/Refresh.GameServer/RefreshGameServer.cs @@ -303,14 +303,14 @@ public bool DisallowEmailDomain(string domain) { using GameDatabaseContext context = this.GetContext(); - return context.DisallowEmailDomainByAddress(domain); + return context.DisallowEmailDomain(domain); } public bool ReallowEmailDomain(string domain) { using GameDatabaseContext context = this.GetContext(); - return context.ReallowEmailDomainByAddress(domain); + return context.ReallowEmailDomain(domain); } public void RenameUser(GameUser user, string newUsername, bool force = false) diff --git a/Refresh.Interfaces.APIv3/Endpoints/AuthenticationApiEndpoints.cs b/Refresh.Interfaces.APIv3/Endpoints/AuthenticationApiEndpoints.cs index 3e97b6f45..52e14adee 100644 --- a/Refresh.Interfaces.APIv3/Endpoints/AuthenticationApiEndpoints.cs +++ b/Refresh.Interfaces.APIv3/Endpoints/AuthenticationApiEndpoints.cs @@ -347,7 +347,7 @@ public ApiResponse Register(RequestContext context, if (!smtpService.CheckEmailDomainValidity(body.EmailAddress)) return ApiValidationError.EmailDoesNotActuallyExistError; - if (database.IsUserDisallowed(body.Username) || database.IsEmailAddressDisallowed(body.EmailAddress) || database.IsEmailDomainDisallowedByAddress(body.EmailAddress)) + if (database.IsUserDisallowed(body.Username) || database.IsEmailAddressDisallowed(body.EmailAddress) || database.IsEmailDomainDisallowed(body.EmailAddress)) return new ApiAuthenticationError("You aren't allowed to play on this instance."); if (!database.IsUsernameValid(body.Username)) diff --git a/RefreshTests.GameServer/Tests/ApiV3/UserApiTests.cs b/RefreshTests.GameServer/Tests/ApiV3/UserApiTests.cs index f62d520f4..0bcbb1591 100644 --- a/RefreshTests.GameServer/Tests/ApiV3/UserApiTests.cs +++ b/RefreshTests.GameServer/Tests/ApiV3/UserApiTests.cs @@ -85,11 +85,11 @@ public void CannotRegisterAccountsWithDisallowedEmailDomain() const string email = "guy@moron.com"; // Not somehow already disallowed - Assert.That(context.Database.IsEmailDomainDisallowedByAddress(email), Is.False); - context.Database.DisallowEmailDomainByAddress(email); + Assert.That(context.Database.IsEmailDomainDisallowed(email), Is.False); + context.Database.DisallowEmailDomain(email); context.Database.Refresh(); - Assert.That(context.Database.IsEmailDomainDisallowedByAddress(email), Is.True); + Assert.That(context.Database.IsEmailDomainDisallowed(email), Is.True); // Attempt 1 (block) ApiResponse? response = context.Http.PostData("/api/v3/register", new ApiRegisterRequest @@ -148,9 +148,9 @@ public void CannotRegisterAccountsWithDisallowedEmailDomain() Assert.That(quacker!.Username, Is.EqualTo("a_lil_guy")); // Undo - context.Database.ReallowEmailDomainByAddress(email); + context.Database.ReallowEmailDomain(email); context.Database.Refresh(); - Assert.That(context.Database.IsEmailDomainDisallowedByAddress(email), Is.False); + Assert.That(context.Database.IsEmailDomainDisallowed(email), Is.False); } [Test] From e654ee7edafe097e9217b2c121b5e64e8d26e26c Mon Sep 17 00:00:00 2001 From: Toaster2 Date: Fri, 10 Apr 2026 11:20:47 +0200 Subject: [PATCH 7/7] Add forgotten test case --- .../Tests/ApiV3/UserApiTests.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/RefreshTests.GameServer/Tests/ApiV3/UserApiTests.cs b/RefreshTests.GameServer/Tests/ApiV3/UserApiTests.cs index 0bcbb1591..8f4c28774 100644 --- a/RefreshTests.GameServer/Tests/ApiV3/UserApiTests.cs +++ b/RefreshTests.GameServer/Tests/ApiV3/UserApiTests.cs @@ -79,17 +79,18 @@ public void CannotRegisterAccountWithDisallowedEmailAddress() } [Test] - public void CannotRegisterAccountsWithDisallowedEmailDomain() + [TestCase("guy@moron.com")] // whole address + [TestCase("moron.com")] // just the domain + public void CannotRegisterAccountsWithDisallowedEmailDomain(string addressToBlockWith) { using TestContext context = this.GetServer(); - const string email = "guy@moron.com"; // Not somehow already disallowed - Assert.That(context.Database.IsEmailDomainDisallowed(email), Is.False); - context.Database.DisallowEmailDomain(email); + Assert.That(context.Database.IsEmailDomainDisallowed(addressToBlockWith), Is.False); + context.Database.DisallowEmailDomain(addressToBlockWith); context.Database.Refresh(); - Assert.That(context.Database.IsEmailDomainDisallowed(email), Is.True); + Assert.That(context.Database.IsEmailDomainDisallowed(addressToBlockWith), Is.True); // Attempt 1 (block) ApiResponse? response = context.Http.PostData("/api/v3/register", new ApiRegisterRequest @@ -148,9 +149,9 @@ public void CannotRegisterAccountsWithDisallowedEmailDomain() Assert.That(quacker!.Username, Is.EqualTo("a_lil_guy")); // Undo - context.Database.ReallowEmailDomain(email); + context.Database.ReallowEmailDomain(addressToBlockWith); context.Database.Refresh(); - Assert.That(context.Database.IsEmailDomainDisallowed(email), Is.False); + Assert.That(context.Database.IsEmailDomainDisallowed(addressToBlockWith), Is.False); } [Test]