diff --git a/.idea/.idea.ProjectLighthouse/.idea/dataSources.xml b/.idea/.idea.ProjectLighthouse/.idea/dataSources.xml index 702fe04b..c8fd1969 100644 --- a/.idea/.idea.ProjectLighthouse/.idea/dataSources.xml +++ b/.idea/.idea.ProjectLighthouse/.idea/dataSources.xml @@ -8,7 +8,7 @@ jdbc:mysql://localhost:3306/lighthouse $ProjectFileDir$ - + mariadb true org.mariadb.jdbc.Driver diff --git a/ProjectLighthouse/Controllers/LoginController.cs b/ProjectLighthouse/Controllers/LoginController.cs index 173d49fb..a2c381af 100644 --- a/ProjectLighthouse/Controllers/LoginController.cs +++ b/ProjectLighthouse/Controllers/LoginController.cs @@ -73,7 +73,8 @@ namespace LBPUnion.ProjectLighthouse.Controllers } User? user = await this.database.UserFromGameToken(token, true); - if (user == null) + + if (user == null || user.Banned) { Logger.Log("unable to find a user from a token, rejecting login", LoggerLevelLogin.Instance); return this.StatusCode(403, ""); diff --git a/ProjectLighthouse/Controllers/Website/Admin/AdminSlotController.cs b/ProjectLighthouse/Controllers/Website/Admin/AdminSlotController.cs index 70c973ef..4787f1a7 100644 --- a/ProjectLighthouse/Controllers/Website/Admin/AdminSlotController.cs +++ b/ProjectLighthouse/Controllers/Website/Admin/AdminSlotController.cs @@ -19,7 +19,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers.Website.Admin this.database = database; } - [Route("teamPick")] + [HttpGet("teamPick")] public async Task TeamPick([FromRoute] int id) { User? user = this.database.UserFromWebRequest(this.Request); @@ -35,7 +35,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers.Website.Admin return this.Ok(); } - [Route("removeTeamPick")] + [HttpGet("removeTeamPick")] public async Task RemoveTeamPick([FromRoute] int id) { User? user = this.database.UserFromWebRequest(this.Request); @@ -51,7 +51,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers.Website.Admin return this.Ok(); } - [Route("delete")] + [HttpGet("delete")] public async Task DeleteLevel([FromRoute] int id) { User? user = this.database.UserFromWebRequest(this.Request); diff --git a/ProjectLighthouse/Controllers/Website/Admin/AdminUserController.cs b/ProjectLighthouse/Controllers/Website/Admin/AdminUserController.cs new file mode 100644 index 00000000..e92266fe --- /dev/null +++ b/ProjectLighthouse/Controllers/Website/Admin/AdminUserController.cs @@ -0,0 +1,37 @@ +#nullable enable +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Types; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace LBPUnion.ProjectLighthouse.Controllers.Website.Admin +{ + [ApiController] + [Route("admin/user/{id:int}")] + public class AdminUserController : ControllerBase + { + private readonly Database database; + + public AdminUserController(Database database) + { + this.database = database; + } + + [HttpGet("unban")] + public async Task UnbanUser([FromRoute] int id) + { + User? user = this.database.UserFromWebRequest(this.Request); + if (user == null || !user.IsAdmin) return this.NotFound(); + + User? targetedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id); + ; + if (targetedUser == null) return this.NotFound(); + + targetedUser.Banned = false; + targetedUser.BannedReason = null; + + await this.database.SaveChangesAsync(); + return this.Redirect($"/user/{targetedUser.UserId}"); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Maintenance/Commands/DeleteUserCommand.cs b/ProjectLighthouse/Maintenance/Commands/DeleteUserCommand.cs index d7c7802c..740c5c99 100644 --- a/ProjectLighthouse/Maintenance/Commands/DeleteUserCommand.cs +++ b/ProjectLighthouse/Maintenance/Commands/DeleteUserCommand.cs @@ -11,11 +11,11 @@ namespace LBPUnion.ProjectLighthouse.Maintenance.Commands public class DeleteUserCommand : ICommand { private readonly Database database = new(); - public string Name() => "Delete/Ban User"; + public string Name() => "Delete User"; public string[] Aliases() => new[] { - "deleteUser", "wipeUser", "banUser", + "deleteUser", "wipeUser", }; public string Arguments() => ""; public int RequiredArgs() => 1; diff --git a/ProjectLighthouse/Migrations/20211217000749_AddBannedPropertiesToUser.cs b/ProjectLighthouse/Migrations/20211217000749_AddBannedPropertiesToUser.cs new file mode 100644 index 00000000..6f2f5a64 --- /dev/null +++ b/ProjectLighthouse/Migrations/20211217000749_AddBannedPropertiesToUser.cs @@ -0,0 +1,41 @@ +using LBPUnion.ProjectLighthouse; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ProjectLighthouse.Migrations +{ + [DbContext(typeof(Database))] + [Migration("20211217000749_AddBannedPropertiesToUser")] + public partial class AddBannedPropertiesToUser : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Banned", + table: "Users", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "BannedReason", + table: "Users", + type: "longtext", + nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Banned", + table: "Users"); + + migrationBuilder.DropColumn( + name: "BannedReason", + table: "Users"); + } + } +} diff --git a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs index cdb0c96a..b29e1cc0 100644 --- a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs +++ b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs @@ -546,6 +546,12 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + b.Property("Banned") + .HasColumnType("tinyint(1)"); + + b.Property("BannedReason") + .HasColumnType("longtext"); + b.Property("Biography") .HasColumnType("longtext"); diff --git a/ProjectLighthouse/Pages/Admin/AdminBanUserPage.cshtml b/ProjectLighthouse/Pages/Admin/AdminBanUserPage.cshtml new file mode 100644 index 00000000..2fd9e27a --- /dev/null +++ b/ProjectLighthouse/Pages/Admin/AdminBanUserPage.cshtml @@ -0,0 +1,20 @@ +@page "/admin/user/{id:int}/ban" +@model LBPUnion.ProjectLighthouse.Pages.Admin.AdminBanUserPage + +@{ + Layout = "Layouts/BaseLayout"; + Model.Title = "Ban " + Model.TargetedUser!.Username + "?"; +} + +

Are you sure you want to ban this user?

+ +
+ @Html.AntiForgeryToken() + +
+ + +


+ +
+
\ No newline at end of file diff --git a/ProjectLighthouse/Pages/Admin/AdminBanUserPage.cshtml.cs b/ProjectLighthouse/Pages/Admin/AdminBanUserPage.cshtml.cs new file mode 100644 index 00000000..34eff362 --- /dev/null +++ b/ProjectLighthouse/Pages/Admin/AdminBanUserPage.cshtml.cs @@ -0,0 +1,49 @@ +#nullable enable +using System.Linq; +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Pages.Layouts; +using LBPUnion.ProjectLighthouse.Types; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace LBPUnion.ProjectLighthouse.Pages.Admin; + +public class AdminBanUserPage : BaseLayout +{ + public AdminBanUserPage(Database database) : base(database) + {} + + public User? TargetedUser; + + public async Task OnGet([FromRoute] int id) + { + User? user = this.Database.UserFromWebRequest(this.Request); + if (user == null || !user.IsAdmin) return this.NotFound(); + + this.TargetedUser = await this.Database.Users.FirstOrDefaultAsync(u => u.UserId == id); + if (this.TargetedUser == null) return this.NotFound(); + + return this.Page(); + } + + public async Task OnPost([FromRoute] int id, string reason) + { + User? user = this.Database.UserFromWebRequest(this.Request); + if (user == null || !user.IsAdmin) return this.NotFound(); + + this.TargetedUser = await this.Database.Users.FirstOrDefaultAsync(u => u.UserId == id); + if (this.TargetedUser == null) return this.NotFound(); + + this.TargetedUser.Banned = true; + this.TargetedUser.BannedReason = reason; + + // invalidate all currently active gametokens + this.Database.GameTokens.RemoveRange(this.Database.GameTokens.Where(t => t.UserId == this.TargetedUser.UserId)); + + // invalidate all currently active webtokens + this.Database.WebTokens.RemoveRange(this.Database.WebTokens.Where(t => t.UserId == this.TargetedUser.UserId)); + + await this.Database.SaveChangesAsync(); + return this.Redirect($"/user/{this.TargetedUser.UserId}"); + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Pages/AdminPanelPage.cshtml b/ProjectLighthouse/Pages/Admin/AdminPanelPage.cshtml similarity index 96% rename from ProjectLighthouse/Pages/AdminPanelPage.cshtml rename to ProjectLighthouse/Pages/Admin/AdminPanelPage.cshtml index 891e8891..9321859f 100644 --- a/ProjectLighthouse/Pages/AdminPanelPage.cshtml +++ b/ProjectLighthouse/Pages/Admin/AdminPanelPage.cshtml @@ -1,7 +1,7 @@ @page "/admin" @using LBPUnion.ProjectLighthouse.Helpers @using LBPUnion.ProjectLighthouse.Maintenance -@model LBPUnion.ProjectLighthouse.Pages.AdminPanelPage +@model LBPUnion.ProjectLighthouse.Pages.Admin.AdminPanelPage @{ Layout = "Layouts/BaseLayout"; diff --git a/ProjectLighthouse/Pages/AdminPanelPage.cshtml.cs b/ProjectLighthouse/Pages/Admin/AdminPanelPage.cshtml.cs similarity index 96% rename from ProjectLighthouse/Pages/AdminPanelPage.cshtml.cs rename to ProjectLighthouse/Pages/Admin/AdminPanelPage.cshtml.cs index e12a2b4a..8881f236 100644 --- a/ProjectLighthouse/Pages/AdminPanelPage.cshtml.cs +++ b/ProjectLighthouse/Pages/Admin/AdminPanelPage.cshtml.cs @@ -7,7 +7,7 @@ using LBPUnion.ProjectLighthouse.Pages.Layouts; using LBPUnion.ProjectLighthouse.Types; using Microsoft.AspNetCore.Mvc; -namespace LBPUnion.ProjectLighthouse.Pages +namespace LBPUnion.ProjectLighthouse.Pages.Admin { public class AdminPanelPage : BaseLayout { diff --git a/ProjectLighthouse/Pages/LoginForm.cshtml b/ProjectLighthouse/Pages/LoginForm.cshtml index 5967a096..f89075af 100644 --- a/ProjectLighthouse/Pages/LoginForm.cshtml +++ b/ProjectLighthouse/Pages/LoginForm.cshtml @@ -24,7 +24,7 @@
Uh oh!
-

@Model.Error

+

@Model.Error

} diff --git a/ProjectLighthouse/Pages/LoginForm.cshtml.cs b/ProjectLighthouse/Pages/LoginForm.cshtml.cs index c7871109..45f6d9cb 100644 --- a/ProjectLighthouse/Pages/LoginForm.cshtml.cs +++ b/ProjectLighthouse/Pages/LoginForm.cshtml.cs @@ -1,7 +1,9 @@ #nullable enable using System.Threading.Tasks; using JetBrains.Annotations; +using Kettu; using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Pages.Layouts; using LBPUnion.ProjectLighthouse.Types; using Microsoft.AspNetCore.Mvc; @@ -36,16 +38,25 @@ namespace LBPUnion.ProjectLighthouse.Pages User? user = await this.Database.Users.FirstOrDefaultAsync(u => u.Username == username); if (user == null) { + Logger.Log($"User {username} failed to login on web due to invalid username", LoggerLevelLogin.Instance); this.Error = "The username or password you entered is invalid."; return this.Page(); } if (!BCrypt.Net.BCrypt.Verify(password, user.Password)) { + Logger.Log($"User {user.Username} (id: {user.UserId}) failed to login on web due to invalid password", LoggerLevelLogin.Instance); this.Error = "The username or password you entered is invalid."; return this.Page(); } + if (user.Banned) + { + Logger.Log($"User {user.Username} (id: {user.UserId}) failed to login on web due to being banned", LoggerLevelLogin.Instance); + this.Error = "You have been banned. Please contact an administrator for more information.\nReason: " + user.BannedReason; + return this.Page(); + } + WebToken webToken = new() { UserId = user.UserId, diff --git a/ProjectLighthouse/Pages/PasswordResetRequiredPage.cshtml b/ProjectLighthouse/Pages/PasswordResetRequiredPage.cshtml index 616d2f8a..afe20489 100644 --- a/ProjectLighthouse/Pages/PasswordResetRequiredPage.cshtml +++ b/ProjectLighthouse/Pages/PasswordResetRequiredPage.cshtml @@ -6,7 +6,7 @@ Model.Title = "Password Reset Required"; } -

An admin has deemed it necessary that you reset your password. Please do so.

+

An administrator has deemed it necessary that you reset your password. Please do so.

Reset Password
diff --git a/ProjectLighthouse/Pages/UserPage.cshtml b/ProjectLighthouse/Pages/UserPage.cshtml index 6f89c86c..1437fa80 100644 --- a/ProjectLighthouse/Pages/UserPage.cshtml +++ b/ProjectLighthouse/Pages/UserPage.cshtml @@ -14,12 +14,34 @@ Model.Description = Model.ProfileUser!.Biography; } +@if (Model.ProfileUser.Banned) +{ +
+} +

@Model.Title

@Model.ProfileUser!.Status

+
@Model.ProfileUser.Hearts @Model.ProfileUser.Comments @@ -53,6 +75,13 @@ Reset Password } + @if (Model.User != null && Model.User.IsAdmin && !Model.ProfileUser.Banned) + { + + + Ban User + + }
diff --git a/ProjectLighthouse/Startup.cs b/ProjectLighthouse/Startup.cs index fe68aea5..cecb0370 100644 --- a/ProjectLighthouse/Startup.cs +++ b/ProjectLighthouse/Startup.cs @@ -87,11 +87,34 @@ namespace LBPUnion.ProjectLighthouse Stopwatch requestStopwatch = new(); requestStopwatch.Start(); + context.Request.EnableBuffering(); // Allows us to reset the position of Request.Body for later logging + // Log all headers. // foreach (KeyValuePair header in context.Request.Headers) Logger.Log($"{header.Key}: {header.Value}"); - context.Request.EnableBuffering(); // Allows us to reset the position of Request.Body for later logging + await next(context); // Handle the request so we can get the status code from it + requestStopwatch.Stop(); + + Logger.Log + ( + $"{context.Response.StatusCode}, {requestStopwatch.ElapsedMilliseconds}ms: {context.Request.Method} {context.Request.Path}{context.Request.QueryString}", + LoggerLevelHttp.Instance + ); + + if (context.Request.Method == "POST") + { + context.Request.Body.Position = 0; + Logger.Log(await new StreamReader(context.Request.Body).ReadToEndAsync(), LoggerLevelHttp.Instance); + } + } + ); + + // Digest check + app.Use + ( + async (context, next) => + { // Client digest check. if (!context.Request.Cookies.TryGetValue("MM_AUTH", out string authCookie)) authCookie = string.Empty; string digestPath = context.Request.Path; @@ -119,7 +142,7 @@ namespace LBPUnion.ProjectLighthouse Stream oldResponseStream = context.Response.Body; context.Response.Body = responseBuffer; - await next(); // Handle the request so we can get the status code from it + await next(context); // Handle the request so we can get the server digest hash // Compute the server digest hash. if (computeDigests) @@ -138,7 +161,13 @@ namespace LBPUnion.ProjectLighthouse responseBuffer.Position = 0; await responseBuffer.CopyToAsync(oldResponseStream); context.Response.Body = oldResponseStream; + } + ); + app.Use + ( + async (context, next) => + { #nullable enable // Log LastContact for LBP1. This is done on LBP2/3/V on a Match request. if (context.Request.Path.ToString().StartsWith("/LITTLEBIGPLANETPS3_XML")) @@ -153,19 +182,7 @@ namespace LBPUnion.ProjectLighthouse } #nullable disable - requestStopwatch.Stop(); - - Logger.Log - ( - $"{context.Response.StatusCode}, {requestStopwatch.ElapsedMilliseconds}ms: {context.Request.Method} {context.Request.Path}{context.Request.QueryString}", - LoggerLevelHttp.Instance - ); - - if (context.Request.Method == "POST") - { - context.Request.Body.Position = 0; - Logger.Log(await new StreamReader(context.Request.Body).ReadToEndAsync(), LoggerLevelHttp.Instance); - } + await next(context); } ); diff --git a/ProjectLighthouse/Types/User.cs b/ProjectLighthouse/Types/User.cs index 726c706e..b941d656 100644 --- a/ProjectLighthouse/Types/User.cs +++ b/ProjectLighthouse/Types/User.cs @@ -125,6 +125,10 @@ namespace LBPUnion.ProjectLighthouse.Types } #nullable disable + public bool Banned { get; set; } + + public string BannedReason { get; set; } + public string Serialize(GameVersion gameVersion = GameVersion.LittleBigPlanet1) { string user = LbpSerializer.TaggedStringElement("npHandle", this.Username, "icon", this.IconHash) +