diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..03ec5454 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "nuget" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "daily" diff --git a/.gitignore b/.gitignore index d6ee8a61..39e45d7c 100644 --- a/.gitignore +++ b/.gitignore @@ -43,5 +43,3 @@ backup* *.tmp *.bin -*.png -*.jpg diff --git a/.idea/.idea.ProjectLighthouse/.idea/jsLibraryMappings.xml b/.idea/.idea.ProjectLighthouse/.idea/jsLibraryMappings.xml index e0f60e14..6f9a9516 100644 --- a/.idea/.idea.ProjectLighthouse/.idea/jsLibraryMappings.xml +++ b/.idea/.idea.ProjectLighthouse/.idea/jsLibraryMappings.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj b/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj index 4a5850d3..b41b9102 100644 --- a/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj +++ b/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj @@ -9,8 +9,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -20,7 +20,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj b/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj index 93e88c08..ff0847fb 100644 --- a/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj +++ b/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj @@ -9,20 +9,20 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj b/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj index 91fa33ee..f9693564 100644 --- a/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj +++ b/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj @@ -14,8 +14,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -25,7 +25,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/ProjectLighthouse/Controllers/GameApi/CommentController.cs b/ProjectLighthouse/Controllers/GameApi/CommentController.cs index 334b7f04..f1960429 100644 --- a/ProjectLighthouse/Controllers/GameApi/CommentController.cs +++ b/ProjectLighthouse/Controllers/GameApi/CommentController.cs @@ -1,4 +1,5 @@ #nullable enable +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -7,6 +8,7 @@ using System.Xml.Serialization; using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Types; +using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Profiles; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -24,37 +26,130 @@ public class CommentController : ControllerBase this.database = database; } - [HttpGet("userComments/{username}")] - public async Task GetComments(string username) + [HttpPost("rateUserComment/{username}")] + [HttpPost("rateComment/user/{slotId:int}")] + public async Task RateComment([FromQuery] int commentId, [FromQuery] int rating, string? username, int? slotId) { - List comments = await this.database.Comments.Include - (c => c.Target) + User? user = await this.database.UserFromGameRequest(this.Request); + if (user == null) return this.StatusCode(403, ""); + + Comment? comment = await this.database.Comments.Include(c => c.Poster).FirstOrDefaultAsync(c => commentId == c.CommentId); + + if (comment == null) return this.BadRequest(); + + Reaction? reaction = await this.database.Reactions.FirstOrDefaultAsync(r => r.UserId == user.UserId && r.TargetId == commentId); + if (reaction == null) + { + Reaction newReaction = new Reaction() + { + UserId = user.UserId, + TargetId = commentId, + Rating = 0, + }; + this.database.Reactions.Add(newReaction); + await this.database.SaveChangesAsync(); + reaction = newReaction; + } + int oldRating = reaction.Rating; + if (oldRating == rating) return this.Ok(); + + reaction.Rating = rating; + // if rating changed then we count the number of reactions to ensure accuracy + List reactions = await this.database.Reactions + .Where(c => c.TargetId == commentId) + .ToListAsync(); + int yay = 0; + int boo = 0; + foreach (Reaction r in reactions) + { + switch (r.Rating) + { + case -1: + boo++; + break; + case 1: + yay++; + break; + } + } + + comment.ThumbsDown = boo; + comment.ThumbsUp = yay; + await this.database.SaveChangesAsync(); + return this.Ok(); + } + + + [HttpGet("comments/user/{slotId:int}")] + [HttpGet("userComments/{username}")] + public async Task GetComments([FromQuery] int pageStart, [FromQuery] int pageSize, string? username, int? slotId) + { + User? user = await this.database.UserFromGameRequest(this.Request); + if (user == null) return this.StatusCode(403, ""); + + int targetId = slotId.GetValueOrDefault(); + CommentType type = CommentType.Level; + if (!string.IsNullOrWhiteSpace(username)) + { + targetId = this.database.Users.First(u => u.Username.Equals(username)).UserId; + type = CommentType.Profile; + } + + List comments = await this.database.Comments .Include(c => c.Poster) - .Where(c => c.Target.Username == username) + .Where(c => c.TargetId == targetId && c.Type == type) .OrderByDescending(c => c.Timestamp) + .Skip(pageStart - 1) + .Take(Math.Min(pageSize, + 30)) .ToListAsync(); - string outputXml = comments.Aggregate(string.Empty, (current, comment) => current + comment.Serialize()); + string outputXml = comments.Aggregate(string.Empty, (current, comment) => current + + comment.Serialize(this.getReaction(user.UserId, comment.CommentId).Result)); return this.Ok(LbpSerializer.StringElement("comments", outputXml)); } - [HttpPost("postUserComment/{username}")] - public async Task PostComment(string username) + public async Task getReaction(int userId, int commentId) { - this.Request.Body.Position = 0; + Reaction? reaction = await this.database.Reactions.FirstOrDefaultAsync(r => r.UserId == userId && r.TargetId == commentId); + if (reaction == null) return 0; + return reaction.Rating; + } + + [HttpPost("postUserComment/{username}")] + [HttpPost("postComment/user/{slotId:int}")] + public async Task PostComment(string? username, int? slotId) + { + this.Request.Body.Position = 0; string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync(); XmlSerializer serializer = new(typeof(Comment)); - Comment? comment = (Comment?)serializer.Deserialize(new StringReader(bodyString)); + Comment? comment = (Comment?) serializer.Deserialize(new StringReader(bodyString)); + + CommentType type = (slotId.GetValueOrDefault() == 0 ? CommentType.Profile : CommentType.Level); User? poster = await this.database.UserFromGameRequest(this.Request); if (poster == null) return this.StatusCode(403, ""); - User? target = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); - if (comment == null || target == null) return this.BadRequest(); + if (comment == null) return this.BadRequest(); + + int targetId = slotId.GetValueOrDefault(); + + if (type == CommentType.Profile) + { + User? target = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); + if (target == null) return this.BadRequest(); + targetId = target.UserId; + } + else + { + Slot? target = await this.database.Slots.FirstOrDefaultAsync(u => u.SlotId == slotId); + if (target == null) return this.BadRequest(); + } comment.PosterUserId = poster.UserId; - comment.TargetUserId = target.UserId; + comment.TargetId = targetId; + comment.Type = type; comment.Timestamp = TimeHelper.UnixTimeMilliseconds(); @@ -64,17 +159,40 @@ public class CommentController : ControllerBase } [HttpPost("deleteUserComment/{username}")] - public async Task DeleteComment([FromQuery] int commentId, string username) + [HttpPost("deleteComment/user/{slotId:int}")] + public async Task DeleteComment([FromQuery] int commentId, string? username, int? slotId) { User? user = await this.database.UserFromGameRequest(this.Request); if (user == null) return this.StatusCode(403, ""); Comment? comment = await this.database.Comments.FirstOrDefaultAsync(c => c.CommentId == commentId); if (comment == null) return this.NotFound(); + // if you are not the poster + if (comment.PosterUserId != user.UserId) + { + if (comment.Type == CommentType.Profile) + { + // if you aren't the poster and aren't the profile owner + if (comment.TargetId != user.UserId) + { + return this.StatusCode(403, ""); + } + } + else + { + Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == comment.TargetId); + // if you aren't the creator of the level + if (slot == null || slot.CreatorId != user.UserId || slotId.GetValueOrDefault() != slot.SlotId) + { + return this.StatusCode(403, ""); + } + } + } - if (comment.TargetUserId != user.UserId && comment.PosterUserId != user.UserId) return this.StatusCode(403, ""); + comment.Deleted = true; + comment.DeletedBy = user.Username; + comment.DeletedType = "user"; - this.database.Comments.Remove(comment); await this.database.SaveChangesAsync(); return this.Ok(); diff --git a/ProjectLighthouse/Controllers/GameApi/Slots/ReviewController.cs b/ProjectLighthouse/Controllers/GameApi/Slots/ReviewController.cs index 6389d02f..e7ad7d7f 100644 --- a/ProjectLighthouse/Controllers/GameApi/Slots/ReviewController.cs +++ b/ProjectLighthouse/Controllers/GameApi/Slots/ReviewController.cs @@ -74,7 +74,7 @@ public class ReviewController : ControllerBase this.database.RatedLevels.Add(ratedLevel); } - ratedLevel.Rating = Math.Max(Math.Min(1, rating), -1); + ratedLevel.Rating = Math.Clamp(rating, -1, 1); Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == user.UserId); if (review != null) review.Thumb = ratedLevel.Rating; @@ -161,12 +161,12 @@ public class ReviewController : ControllerBase yourReview = new Review(); yourReview.ReviewerId = user.UserId; yourReview.Reviewer = user; - yourReview.Thumb = ratedLevel?.Rating == null ? 0 : ratedLevel.Rating; + yourReview.Thumb = ratedLevel?.Rating ?? 0; yourReview.Slot = slot; yourReview.SlotId = slotId; yourReview.Deleted = false; yourReview.DeletedBy = DeletedBy.None; - yourReview.Text = "You haven't reviewed this level yet. Edit this blank review to upload one!"; + yourReview.Text = "You haven't reviewed this level yet. Edit this to write one!"; yourReview.LabelCollection = ""; yourReview.Timestamp = TimeHelper.UnixTimeMilliseconds(); } diff --git a/ProjectLighthouse/Database.cs b/ProjectLighthouse/Database.cs index 365319fd..bead8124 100644 --- a/ProjectLighthouse/Database.cs +++ b/ProjectLighthouse/Database.cs @@ -36,6 +36,7 @@ public class Database : DbContext public DbSet RatedReviews { get; set; } public DbSet UserApprovedIpAddresses { get; set; } public DbSet CustomCategories { get; set; } + public DbSet Reactions { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder options) => options.UseMySql(ServerSettings.Instance.DbConnectionString, MySqlServerVersion.LatestSupportedServerVersion); @@ -272,6 +273,7 @@ public class Database : DbContext this.Comments.RemoveRange(this.Comments.Where(c => c.PosterUserId == user.UserId)); this.Reviews.RemoveRange(this.Reviews.Where(r => r.ReviewerId == user.UserId)); this.Photos.RemoveRange(this.Photos.Where(p => p.CreatorId == user.UserId)); + this.Reactions.RemoveRange(this.Reactions.Where(p => p.UserId == user.UserId)); this.Users.Remove(user); diff --git a/ProjectLighthouse/Helpers/CaptchaHelper.cs b/ProjectLighthouse/Helpers/CaptchaHelper.cs new file mode 100644 index 00000000..3008b084 --- /dev/null +++ b/ProjectLighthouse/Helpers/CaptchaHelper.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Types.Settings; +using Newtonsoft.Json.Linq; + +namespace LBPUnion.ProjectLighthouse.Helpers; + +public static class CaptchaHelper +{ + private static readonly HttpClient client = new() + { + BaseAddress = new Uri("https://hcaptcha.com"), + }; + + public static async Task Verify(string token) + { + if (!ServerSettings.Instance.HCaptchaEnabled) return true; + + List> payload = new() + { + new("secret", ServerSettings.Instance.HCaptchaSecret), + new("response", token), + }; + + HttpResponseMessage response = await client.PostAsync("/siteverify", new FormUrlEncodedContent(payload)); + + response.EnsureSuccessStatusCode(); + + string responseBody = await response.Content.ReadAsStringAsync(); + + // We only really care about the success result, nothing else that hcaptcha sends us, so lets only parse that. + bool success = bool.Parse(JObject.Parse(responseBody)["success"]?.ToString() ?? "false"); + return success; + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Helpers/Extensions/RequestExtensions.cs b/ProjectLighthouse/Helpers/Extensions/RequestExtensions.cs index 05208124..b8738c4d 100644 --- a/ProjectLighthouse/Helpers/Extensions/RequestExtensions.cs +++ b/ProjectLighthouse/Helpers/Extensions/RequestExtensions.cs @@ -1,5 +1,9 @@ +#nullable enable using System.Text.RegularExpressions; +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Types.Settings; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; namespace LBPUnion.ProjectLighthouse.Helpers.Extensions; @@ -11,4 +15,17 @@ public static class RequestExtensions ("Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled); public static bool IsMobile(this HttpRequest request) => mobileCheck.IsMatch(request.Headers[HeaderNames.UserAgent].ToString()); + + public static async Task CheckCaptchaValidity(this HttpRequest request) + { + if (ServerSettings.Instance.HCaptchaEnabled) + { + bool gotCaptcha = request.Form.TryGetValue("h-captcha-response", out StringValues values); + if (!gotCaptcha) return false; + + if (!await CaptchaHelper.Verify(values[0])) return false; + } + + return true; + } } \ No newline at end of file diff --git a/ProjectLighthouse/Helpers/FileHelper.cs b/ProjectLighthouse/Helpers/FileHelper.cs index d67384ef..9d8c4f20 100644 --- a/ProjectLighthouse/Helpers/FileHelper.cs +++ b/ProjectLighthouse/Helpers/FileHelper.cs @@ -21,6 +21,7 @@ public static class FileHelper return file.FileType switch { + LbpFileType.MotionRecording => true, LbpFileType.FileArchive => false, LbpFileType.Painting => true, LbpFileType.Unknown => false, @@ -56,17 +57,18 @@ public static class FileHelper return Encoding.ASCII.GetString(header) switch { + "REC" => LbpFileType.MotionRecording, "PTG" => LbpFileType.Painting, "TEX" => LbpFileType.Texture, "FSH" => LbpFileType.Script, "VOP" => LbpFileType.Voice, "LVL" => LbpFileType.Level, "PLN" => LbpFileType.Plan, - _ => determineFileTypePartTwoWeirdName(reader), + _ => readAlternateHeader(reader), }; } - private static LbpFileType determineFileTypePartTwoWeirdName(BinaryReader reader) + private static LbpFileType readAlternateHeader(BinaryReader reader) { reader.BaseStream.Position = 0; diff --git a/ProjectLighthouse/Migrations/20220205132152_CommentRefactor.cs b/ProjectLighthouse/Migrations/20220205132152_CommentRefactor.cs new file mode 100644 index 00000000..bd829b58 --- /dev/null +++ b/ProjectLighthouse/Migrations/20220205132152_CommentRefactor.cs @@ -0,0 +1,114 @@ +using LBPUnion.ProjectLighthouse; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ProjectLighthouse.Migrations +{ + [DbContext(typeof(Database))] + [Migration("20220205132152_CommentRefactor")] + public partial class CommentRefactor : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Comments_Users_TargetUserId", + table: "Comments"); + + migrationBuilder.DropIndex( + name: "IX_Comments_TargetUserId", + table: "Comments"); + + migrationBuilder.RenameColumn( + name: "TargetUserId", + table: "Comments", + newName: "TargetId"); + + migrationBuilder.AddColumn( + name: "Deleted", + table: "Comments", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "DeletedBy", + table: "Comments", + type: "longtext", + nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AddColumn( + name: "DeletedType", + table: "Comments", + type: "longtext", + nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AddColumn( + name: "Type", + table: "Comments", + type: "int", + nullable: false, + defaultValue: 0); + + migrationBuilder.CreateTable( + name: "Reactions", + columns: table => new + { + RatingId = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + UserId = table.Column(type: "int", nullable: false), + TargetId = table.Column(type: "int", nullable: false), + Rating = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Reactions", x => x.RatingId); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Reactions"); + + migrationBuilder.DropColumn( + name: "Deleted", + table: "Comments"); + + migrationBuilder.DropColumn( + name: "DeletedBy", + table: "Comments"); + + migrationBuilder.DropColumn( + name: "DeletedType", + table: "Comments"); + + migrationBuilder.DropColumn( + name: "Type", + table: "Comments"); + + migrationBuilder.RenameColumn( + name: "TargetId", + table: "Comments", + newName: "TargetUserId"); + + migrationBuilder.CreateIndex( + name: "IX_Comments_TargetUserId", + table: "Comments", + column: "TargetUserId"); + + migrationBuilder.AddForeignKey( + name: "FK_Comments_Users_TargetUserId", + table: "Comments", + column: "TargetUserId", + principalTable: "Users", + principalColumn: "UserId", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs index 28c95e9d..15bc4226 100644 --- a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs +++ b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs @@ -412,13 +412,22 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + b.Property("Deleted") + .HasColumnType("tinyint(1)"); + + b.Property("DeletedBy") + .HasColumnType("longtext"); + + b.Property("DeletedType") + .HasColumnType("longtext"); + b.Property("Message") .HasColumnType("longtext"); b.Property("PosterUserId") .HasColumnType("int"); - b.Property("TargetUserId") + b.Property("TargetId") .HasColumnType("int"); b.Property("ThumbsDown") @@ -430,12 +439,13 @@ namespace ProjectLighthouse.Migrations b.Property("Timestamp") .HasColumnType("bigint"); + b.Property("Type") + .HasColumnType("int"); + b.HasKey("CommentId"); b.HasIndex("PosterUserId"); - b.HasIndex("TargetUserId"); - b.ToTable("Comments"); }); @@ -473,6 +483,26 @@ namespace ProjectLighthouse.Migrations b.ToTable("Locations"); }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Reaction", b => + { + b.Property("RatingId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Rating") + .HasColumnType("int"); + + b.Property("TargetId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("RatingId"); + + b.ToTable("Reactions"); + }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Reviews.RatedReview", b => { b.Property("RatedReviewId") @@ -829,15 +859,7 @@ namespace ProjectLighthouse.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Target") - .WithMany() - .HasForeignKey("TargetUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - b.Navigation("Poster"); - - b.Navigation("Target"); }); modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Reviews.RatedReview", b => diff --git a/ProjectLighthouse/Pages/Admin/AdminPanelPage.cshtml b/ProjectLighthouse/Pages/Admin/AdminPanelPage.cshtml index 97228b43..58440e43 100644 --- a/ProjectLighthouse/Pages/Admin/AdminPanelPage.cshtml +++ b/ProjectLighthouse/Pages/Admin/AdminPanelPage.cshtml @@ -35,19 +35,24 @@ else

@command.Name()

+ @if (command.RequiredArgs() > 0) { -
+
+
-
-
} - - + else + { + + }
} diff --git a/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml b/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml index b2c9cd9a..279ca575 100644 --- a/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml +++ b/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml @@ -74,17 +74,6 @@ gtag('config', '@ServerSettings.Instance.GoogleAnalyticsId'); } - -
diff --git a/ProjectLighthouse/Pages/LoginForm.cshtml b/ProjectLighthouse/Pages/LoginForm.cshtml index 2e5494a4..f07daa9f 100644 --- a/ProjectLighthouse/Pages/LoginForm.cshtml +++ b/ProjectLighthouse/Pages/LoginForm.cshtml @@ -50,6 +50,11 @@
+ @if (ServerSettings.Instance.HCaptchaEnabled) + { + @await Html.PartialAsync("Partials/CaptchaPartial") + } + @if (ServerSettings.Instance.RegistrationEnabled) { diff --git a/ProjectLighthouse/Pages/LoginForm.cshtml.cs b/ProjectLighthouse/Pages/LoginForm.cshtml.cs index 5e19c5a4..f2a7ff64 100644 --- a/ProjectLighthouse/Pages/LoginForm.cshtml.cs +++ b/ProjectLighthouse/Pages/LoginForm.cshtml.cs @@ -1,11 +1,14 @@ #nullable enable +using System; using System.Threading.Tasks; using JetBrains.Annotations; using Kettu; using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.Helpers.Extensions; using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Pages.Layouts; using LBPUnion.ProjectLighthouse.Types; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -18,8 +21,6 @@ public class LoginForm : BaseLayout public string Error { get; private set; } - public bool WasLoginRequest { get; private set; } - [UsedImplicitly] public async Task OnPost(string username, string password) { @@ -35,6 +36,12 @@ public class LoginForm : BaseLayout return this.Page(); } + if (!await Request.CheckCaptchaValidity()) + { + this.Error = "You must complete the captcha correctly."; + return this.Page(); + } + User? user = await this.Database.Users.FirstOrDefaultAsync(u => u.Username == username); if (user == null) { @@ -66,7 +73,17 @@ public class LoginForm : BaseLayout this.Database.WebTokens.Add(webToken); await this.Database.SaveChangesAsync(); - this.Response.Cookies.Append("LighthouseToken", webToken.UserToken); + this.Response.Cookies.Append + ( + "LighthouseToken", + webToken.UserToken, + new CookieOptions + { + Expires = DateTimeOffset.Now.AddDays(7), + } + ); + + Logger.Log($"User {user.Username} (id: {user.UserId}) successfully logged in on web", LoggerLevelLogin.Instance); if (user.PasswordResetRequired) return this.Redirect("~/passwordResetRequired"); diff --git a/ProjectLighthouse/Pages/Partials/CaptchaPartial.cshtml b/ProjectLighthouse/Pages/Partials/CaptchaPartial.cshtml new file mode 100644 index 00000000..e1891722 --- /dev/null +++ b/ProjectLighthouse/Pages/Partials/CaptchaPartial.cshtml @@ -0,0 +1,6 @@ +@using LBPUnion.ProjectLighthouse.Types.Settings +@if (ServerSettings.Instance.HCaptchaEnabled) +{ +
+ +} \ No newline at end of file diff --git a/ProjectLighthouse/Pages/Partials/SlotCardPartial.cshtml b/ProjectLighthouse/Pages/Partials/SlotCardPartial.cshtml index 45e31797..36318404 100644 --- a/ProjectLighthouse/Pages/Partials/SlotCardPartial.cshtml +++ b/ProjectLighthouse/Pages/Partials/SlotCardPartial.cshtml @@ -32,7 +32,9 @@ @{ int size = isMobile ? 50 : 100; } -
+
+ +
@if (showLink) diff --git a/ProjectLighthouse/Pages/PasswordResetPage.cshtml b/ProjectLighthouse/Pages/PasswordResetPage.cshtml index 7cc087e1..c6e0f631 100644 --- a/ProjectLighthouse/Pages/PasswordResetPage.cshtml +++ b/ProjectLighthouse/Pages/PasswordResetPage.cshtml @@ -10,11 +10,13 @@