diff --git a/Refresh.GameServer/CommandLineManager.cs b/Refresh.GameServer/CommandLineManager.cs index 963be16a..fbc8f18f 100644 --- a/Refresh.GameServer/CommandLineManager.cs +++ b/Refresh.GameServer/CommandLineManager.cs @@ -41,6 +41,12 @@ private class Options [Option('f', "force", Required = false, HelpText = "Force all operations to happen, skipping user consent")] public bool Force { get; set; } + + [Option('b', "disallow_user", Required = false, HelpText = "Disallow a user from registering. Username option is required if this is set.")] + public bool DisallowUser { get; set; } + + [Option('r', "reallow_user", Required = false, HelpText = "Re-allow a user to register. Username option is requried if this is set.")] + public bool ReallowUser { get; set; } } internal void StartWithArgs(string[] args) @@ -99,5 +105,39 @@ private void StartWithOptions(Options options) Environment.Exit(1); } } + + if (options.DisallowUser) + { + if (options.Username != null) + { + if (!this._server.DisallowUser(options.Username)) + { + Console.WriteLine("User is already disallowed"); + Environment.Exit(1); + } + } + else + { + Console.WriteLine("No user was provided, cannot continue."); + Environment.Exit(1); + } + } + + if (options.ReallowUser) + { + if (options.Username != null) + { + if (!this._server.ReallowUser(options.Username)) + { + Console.WriteLine("User is already allowed"); + Environment.Exit(1); + } + } + else + { + Console.WriteLine("No user was provided, cannot continue."); + Environment.Exit(1); + } + } } } \ No newline at end of file diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Registration.cs b/Refresh.GameServer/Database/GameDatabaseContext.Registration.cs index 7623bf77..bc9c1bf7 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Registration.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Registration.cs @@ -191,4 +191,39 @@ public void RemoveEmailVerificationCode(EmailVerificationCode code) this._realm.Remove(code); }); } + + public bool DisallowUser(string username) + { + if (this._realm.Find(username) != null) + return false; + + this._realm.Write(() => + { + this._realm.Add(new DisallowedUser + { + Username = username, + }); + }); + + return true; + } + + public bool ReallowUser(string username) + { + DisallowedUser? disallowedUser = this._realm.Find(username); + if (disallowedUser == null) + return false; + + this._realm.Write(() => + { + this._realm.Remove(disallowedUser); + }); + + return true; + } + + public bool IsUserDisallowed(string username) + { + return this._realm.Find(username) != null; + } } \ No newline at end of file diff --git a/Refresh.GameServer/Database/GameDatabaseProvider.cs b/Refresh.GameServer/Database/GameDatabaseProvider.cs index 9c6a2480..107a21b0 100644 --- a/Refresh.GameServer/Database/GameDatabaseProvider.cs +++ b/Refresh.GameServer/Database/GameDatabaseProvider.cs @@ -33,7 +33,7 @@ protected GameDatabaseProvider(IDateTimeProvider time) this._time = time; } - protected override ulong SchemaVersion => 117; + protected override ulong SchemaVersion => 118; protected override string Filename => "refreshGameServer.realm"; @@ -74,6 +74,7 @@ protected GameDatabaseProvider(IDateTimeProvider time) typeof(ScreenRect), typeof(Slot), typeof(GameReview), + typeof(DisallowedUser) }; public override void Warmup() diff --git a/Refresh.GameServer/Endpoints/ApiV3/AuthenticationApiEndpoints.cs b/Refresh.GameServer/Endpoints/ApiV3/AuthenticationApiEndpoints.cs index 59d80f23..5bd22225 100644 --- a/Refresh.GameServer/Endpoints/ApiV3/AuthenticationApiEndpoints.cs +++ b/Refresh.GameServer/Endpoints/ApiV3/AuthenticationApiEndpoints.cs @@ -218,6 +218,9 @@ public ApiResponse Register(RequestContext context, if (!CommonPatterns.EmailAddressRegex().IsMatch(body.EmailAddress)) return new ApiValidationError("The email address given is invalid."); + if (database.IsUserDisallowed(body.Username)) + return new ApiAuthenticationError("This username is disallowed from being registered."); + if (database.IsUsernameTaken(body.Username) || database.IsEmailTaken(body.EmailAddress)) { return new ApiAuthenticationError( diff --git a/Refresh.GameServer/RefreshGameServer.cs b/Refresh.GameServer/RefreshGameServer.cs index 74d7c0d6..2c8ebef9 100644 --- a/Refresh.GameServer/RefreshGameServer.cs +++ b/Refresh.GameServer/RefreshGameServer.cs @@ -223,6 +223,20 @@ public void SetAdminFromEmailAddress(string emailAddress) context.SetUserRole(user, GameUserRole.Admin); } + + public bool DisallowUser(string username) + { + using GameDatabaseContext context = this.GetContext(); + + return context.DisallowUser(username); + } + + public bool ReallowUser(string username) + { + using GameDatabaseContext context = this.GetContext(); + + return context.ReallowUser(username); + } public override void Dispose() { diff --git a/Refresh.GameServer/Types/UserData/DisallowedUser.cs b/Refresh.GameServer/Types/UserData/DisallowedUser.cs new file mode 100644 index 00000000..16fdf7d6 --- /dev/null +++ b/Refresh.GameServer/Types/UserData/DisallowedUser.cs @@ -0,0 +1,9 @@ +using Realms; + +namespace Refresh.GameServer.Types.UserData; + +public partial class DisallowedUser : IRealmObject +{ + [PrimaryKey] + public string Username { 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 55c4ecdc..3240b6b4 100644 --- a/RefreshTests.GameServer/Tests/ApiV3/UserApiTests.cs +++ b/RefreshTests.GameServer/Tests/ApiV3/UserApiTests.cs @@ -1,6 +1,7 @@ using Refresh.GameServer.Authentication; using Refresh.GameServer.Endpoints.ApiV3.ApiTypes; using Refresh.GameServer.Endpoints.ApiV3.ApiTypes.Errors; +using Refresh.GameServer.Endpoints.ApiV3.DataTypes.Request.Authentication; using Refresh.GameServer.Endpoints.ApiV3.DataTypes.Response; using Refresh.GameServer.Types.UserData; using RefreshTests.GameServer.Extensions; @@ -22,6 +23,48 @@ public void GetsUserByUsername() Assert.That(response, Is.Not.Null); response!.AssertErrorIsEqual(ApiNotFoundError.UserMissingError); } + + [Test] + public void RegisterAccount() + { + using TestContext context = this.GetServer(); + + const string username = "a_lil_guy"; + + ApiResponse? response = context.Http.PostData("/api/v3/register", new ApiRegisterRequest + { + Username = username, + EmailAddress = "guy@lil.com", + PasswordSha512 = "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff", + }); + Assert.That(response, Is.Not.EqualTo(null)); + + context.Database.Refresh(); + Assert.That(context.Database.GetUserByUsername(username), Is.Not.EqualTo(null)); + } + + [Test] + public void CannotRegisterAccountWithDisallowedUsername() + { + using TestContext context = this.GetServer(); + + const string username = "a_lil_guy"; + + context.Database.DisallowUser(username); + + ApiResponse? response = context.Http.PostData("/api/v3/register", new ApiRegisterRequest + { + Username = username, + EmailAddress = "guy@lil.com", + PasswordSha512 = "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff", + }); + Assert.That(response, Is.Not.EqualTo(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.EqualTo(null)); + } [Test] public void GetsUserByUuid()