From 5b8d9cdb1032f017ab45a0ae2f3abbe8666131f1 Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 13 Dec 2021 14:04:39 -0500 Subject: [PATCH 01/30] Show authentication attempts on landing page --- ProjectLighthouse/Pages/LandingPage.cshtml | 8 ++++++++ ProjectLighthouse/Pages/LandingPage.cshtml.cs | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/ProjectLighthouse/Pages/LandingPage.cshtml b/ProjectLighthouse/Pages/LandingPage.cshtml index fc67aaae..3a977b88 100644 --- a/ProjectLighthouse/Pages/LandingPage.cshtml +++ b/ProjectLighthouse/Pages/LandingPage.cshtml @@ -1,5 +1,6 @@ @page "/" @using LBPUnion.ProjectLighthouse.Types +@using LBPUnion.ProjectLighthouse.Types.Settings @model LBPUnion.ProjectLighthouse.Pages.LandingPage @{ @@ -11,6 +12,12 @@ @if (Model.User != null) {

You are currently logged in as @Model.User.Username.

+ if (ServerSettings.Instance.UseExternalAuth && Model.AuthenticationAttemptsCount > 0) + { +

+ You have @Model.AuthenticationAttemptsCount authentication attempts pending. Click here to view them. +

+ } } @if (Model.PlayersOnlineCount == 1) @@ -21,6 +28,7 @@ @user.Username } } + else if (Model.PlayersOnlineCount == 0) {

There are no users online. Why not hop on?

diff --git a/ProjectLighthouse/Pages/LandingPage.cshtml.cs b/ProjectLighthouse/Pages/LandingPage.cshtml.cs index 23289463..de398d10 100644 --- a/ProjectLighthouse/Pages/LandingPage.cshtml.cs +++ b/ProjectLighthouse/Pages/LandingPage.cshtml.cs @@ -16,6 +16,8 @@ namespace LBPUnion.ProjectLighthouse.Pages public List PlayersOnline; public int PlayersOnlineCount; + + public int AuthenticationAttemptsCount; public LandingPage(Database database) : base(database) {} @@ -27,6 +29,13 @@ namespace LBPUnion.ProjectLighthouse.Pages this.PlayersOnlineCount = await StatisticsHelper.RecentMatches(); + if (user != null) + { + this.AuthenticationAttemptsCount = await this.Database.AuthenticationAttempts.Include + (a => a.GameToken) + .CountAsync(a => a.GameToken.UserId == user.UserId); + } + List userIds = await this.Database.LastContacts.Where(l => TimestampHelper.Timestamp - l.Timestamp < 300).Select(l => l.UserId).ToListAsync(); this.PlayersOnline = await this.Database.Users.Where(u => userIds.Contains(u.UserId)).ToListAsync(); From e0156400b816d9da95f04f734e09be61d10ffe45 Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 13 Dec 2021 14:19:22 -0500 Subject: [PATCH 02/30] Fix AuthenticationPage title bug --- ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml b/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml index 58521e46..e185814e 100644 --- a/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml +++ b/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml @@ -4,8 +4,8 @@ @{ Layout = "Layouts/BaseLayout"; + Model.Title = "Authentication"; } -

Authentication

@if (Model.AuthenticationAttempts.Count == 0) { From d609291d8f00f39b2a070dbe3ade1c63bf50f06a Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 13 Dec 2021 14:53:42 -0500 Subject: [PATCH 03/30] Redesign authentication page --- .../ExternalAuth/AuthenticationPage.cshtml | 32 +++++++++++++++++-- .../ExternalAuth/AuthenticationPage.cshtml.cs | 7 ++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml b/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml index e185814e..97278a6a 100644 --- a/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml +++ b/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml @@ -14,8 +14,22 @@ else {

You have @Model.AuthenticationAttempts.Count authentication attempts pending.

+ @if (Model.IpAddress != null) + { +

This device's IP address is @(Model.IpAddress.ToString()). If this matches with an authentication attempt below, then it's safe to assume the authentication attempt came from the same network as this device.

+ } + + + + - + } @@ -25,11 +39,23 @@ else

A @authAttempt.Platform authentication request was logged at @timestamp.ToString("MM/dd/yyyy @ h:mm tt") UTC from the IP address @authAttempt.IPAddress.

diff --git a/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml.cs b/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml.cs index 13a1f5eb..6ca2576a 100644 --- a/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml.cs +++ b/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml.cs @@ -1,5 +1,7 @@ +#nullable enable using System.Collections.Generic; using System.Linq; +using System.Net; using System.Threading.Tasks; using LBPUnion.ProjectLighthouse.Pages.Layouts; using LBPUnion.ProjectLighthouse.Types; @@ -13,12 +15,17 @@ namespace LBPUnion.ProjectLighthouse.Pages.ExternalAuth { public List AuthenticationAttempts; + + public IPAddress? IpAddress; public AuthenticationPage(Database database) : base(database) {} public async Task OnGet() { if (!ServerSettings.Instance.UseExternalAuth) return this.NotFound(); + if (this.User == null) return this.StatusCode(403, ""); + + this.IpAddress = this.HttpContext.Connection.RemoteIpAddress; this.AuthenticationAttempts = this.Database.AuthenticationAttempts.Include (a => a.GameToken) From 4a7a713cf9c9bbd6b188a3fb0a383c4a27fffe87 Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 13 Dec 2021 15:59:33 -0500 Subject: [PATCH 04/30] Add ability to approve an IP address --- .../ExternalAuth/AutoApprovalController.cs | 67 +++++++++++++++++++ ProjectLighthouse/Database.cs | 2 +- ...211213195540_AddUserApprovedIpAddresses.cs | 50 ++++++++++++++ .../Migrations/DatabaseModelSnapshot.cs | 30 +++++++++ .../ExternalAuth/AuthenticationPage.cshtml | 26 +++---- .../ManageUserApprovedIpAddressesPage.cshtml | 22 ++++++ ...anageUserApprovedIpAddressesPage.cshtml.cs | 29 ++++++++ .../Types/UserApprovedIpAddress.cs | 18 +++++ 8 files changed, 230 insertions(+), 14 deletions(-) create mode 100644 ProjectLighthouse/Controllers/Website/ExternalAuth/AutoApprovalController.cs create mode 100644 ProjectLighthouse/Migrations/20211213195540_AddUserApprovedIpAddresses.cs create mode 100644 ProjectLighthouse/Pages/ExternalAuth/ManageUserApprovedIpAddressesPage.cshtml create mode 100644 ProjectLighthouse/Pages/ExternalAuth/ManageUserApprovedIpAddressesPage.cshtml.cs create mode 100644 ProjectLighthouse/Types/UserApprovedIpAddress.cs diff --git a/ProjectLighthouse/Controllers/Website/ExternalAuth/AutoApprovalController.cs b/ProjectLighthouse/Controllers/Website/ExternalAuth/AutoApprovalController.cs new file mode 100644 index 00000000..f801b055 --- /dev/null +++ b/ProjectLighthouse/Controllers/Website/ExternalAuth/AutoApprovalController.cs @@ -0,0 +1,67 @@ +#nullable enable +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Types; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace LBPUnion.ProjectLighthouse.Controllers.Website.ExternalAuth +{ + [ApiController] + [Route("/authentication")] + public class AutoApprovalController : ControllerBase + { + private readonly Database database; + + public AutoApprovalController(Database database) + { + this.database = database; + } + + [HttpGet("autoApprove/{id:int}")] + public async Task AutoApprove([FromRoute] int id) + { + User? user = this.database.UserFromWebRequest(this.Request); + if (user == null) return this.Redirect("/login"); + + AuthenticationAttempt? authAttempt = await this.database.AuthenticationAttempts.Include + (a => a.GameToken) + .FirstOrDefaultAsync(a => a.AuthenticationAttemptId == id); + + if (authAttempt == null) return this.BadRequest(); + if (authAttempt.GameToken.UserId != user.UserId) return this.Redirect("/login"); + + authAttempt.GameToken.Approved = true; + + UserApprovedIpAddress approvedIpAddress = new() + { + UserId = user.UserId, + User = user, + IpAddress = authAttempt.IPAddress, + }; + + this.database.UserApprovedIpAddresses.Add(approvedIpAddress); + this.database.AuthenticationAttempts.Remove(authAttempt); + + await this.database.SaveChangesAsync(); + + return this.Redirect("/authentication"); + } + + [HttpGet("revokeAutoApproval/{id:int}")] + public async Task RevokeAutoApproval([FromRoute] int id) + { + User? user = this.database.UserFromWebRequest(this.Request); + if (user == null) return this.Redirect("/login"); + + UserApprovedIpAddress? approvedIpAddress = await this.database.UserApprovedIpAddresses.FirstOrDefaultAsync(a => a.UserApprovedIpAddressId == id); + if (approvedIpAddress == null) return this.BadRequest(); + if (approvedIpAddress.UserId != user.UserId) return this.Redirect("/login"); + + this.database.UserApprovedIpAddresses.Remove(approvedIpAddress); + + await this.database.SaveChangesAsync(); + + return this.Redirect("/authentication/autoApprovals"); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Database.cs b/ProjectLighthouse/Database.cs index 2af08e30..9fda3cab 100644 --- a/ProjectLighthouse/Database.cs +++ b/ProjectLighthouse/Database.cs @@ -34,6 +34,7 @@ namespace LBPUnion.ProjectLighthouse public DbSet AuthenticationAttempts { get; set; } public DbSet Reviews { get; set; } public DbSet RatedReviews { get; set; } + public DbSet UserApprovedIpAddresses { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder options) => options.UseMySql(ServerSettings.Instance.DbConnectionString, MySqlServerVersion.LatestSupportedServerVersion); @@ -66,7 +67,6 @@ namespace LBPUnion.ProjectLighthouse #nullable enable public async Task AuthenticateUser(LoginData loginData, string userLocation, string titleId = "") { - // TODO: don't use psn name to authenticate User? user = await this.Users.FirstOrDefaultAsync(u => u.Username == loginData.Username); if (user == null) return null; diff --git a/ProjectLighthouse/Migrations/20211213195540_AddUserApprovedIpAddresses.cs b/ProjectLighthouse/Migrations/20211213195540_AddUserApprovedIpAddresses.cs new file mode 100644 index 00000000..4f9264df --- /dev/null +++ b/ProjectLighthouse/Migrations/20211213195540_AddUserApprovedIpAddresses.cs @@ -0,0 +1,50 @@ +using LBPUnion.ProjectLighthouse; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ProjectLighthouse.Migrations +{ + [DbContext(typeof(Database))] + [Migration("20211213195540_AddUserApprovedIpAddresses")] + public partial class AddUserApprovedIpAddresses : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "UserApprovedIpAddresses", + columns: table => new + { + UserApprovedIpAddressId = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + UserId = table.Column(type: "int", nullable: false), + IpAddress = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + table.PrimaryKey("PK_UserApprovedIpAddresses", x => x.UserApprovedIpAddressId); + table.ForeignKey( + name: "FK_UserApprovedIpAddresses_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "UserId", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_UserApprovedIpAddresses_UserId", + table: "UserApprovedIpAddresses", + column: "UserId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UserApprovedIpAddresses"); + } + } +} diff --git a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs index 4afc7b51..64dfac1f 100644 --- a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs +++ b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs @@ -587,6 +587,25 @@ namespace ProjectLighthouse.Migrations b.ToTable("Users"); }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.UserApprovedIpAddress", b => + { + b.Property("UserApprovedIpAddressId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("IpAddress") + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("UserApprovedIpAddressId"); + + b.HasIndex("UserId"); + + b.ToTable("UserApprovedIpAddresses"); + }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.WebToken", b => { b.Property("TokenId") @@ -829,6 +848,17 @@ namespace ProjectLighthouse.Migrations b.Navigation("Location"); }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.UserApprovedIpAddress", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); #pragma warning restore 612, 618 } } diff --git a/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml b/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml index 97278a6a..d5a56773 100644 --- a/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml +++ b/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml @@ -18,21 +18,21 @@ else {

This device's IP address is @(Model.IpAddress.ToString()). If this matches with an authentication attempt below, then it's safe to assume the authentication attempt came from the same network as this device.

} - - - - - - - } + + + + + + + @foreach (AuthenticationAttempt authAttempt in Model.AuthenticationAttempts) { DateTimeOffset timestamp = DateTimeOffset.FromUnixTimeSeconds(authAttempt.Timestamp); diff --git a/ProjectLighthouse/Pages/ExternalAuth/ManageUserApprovedIpAddressesPage.cshtml b/ProjectLighthouse/Pages/ExternalAuth/ManageUserApprovedIpAddressesPage.cshtml new file mode 100644 index 00000000..dde2fa8a --- /dev/null +++ b/ProjectLighthouse/Pages/ExternalAuth/ManageUserApprovedIpAddressesPage.cshtml @@ -0,0 +1,22 @@ +@page "/authentication/autoApprovals" +@using LBPUnion.ProjectLighthouse.Types +@model LBPUnion.ProjectLighthouse.Pages.ExternalAuth.ManageUserApprovedIpAddressesPage + +@{ + Layout = "Layouts/BaseLayout"; + Model.Title = "Automatically approved IP addresses"; +} + + +@foreach (UserApprovedIpAddress approvedIpAddress in Model.ApprovedIpAddresses) +{ +
+

@approvedIpAddress.IpAddress

+ + + +
+} \ No newline at end of file diff --git a/ProjectLighthouse/Pages/ExternalAuth/ManageUserApprovedIpAddressesPage.cshtml.cs b/ProjectLighthouse/Pages/ExternalAuth/ManageUserApprovedIpAddressesPage.cshtml.cs new file mode 100644 index 00000000..00dfa3be --- /dev/null +++ b/ProjectLighthouse/Pages/ExternalAuth/ManageUserApprovedIpAddressesPage.cshtml.cs @@ -0,0 +1,29 @@ +#nullable enable +using System.Collections.Generic; +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.ExternalAuth +{ + public class ManageUserApprovedIpAddressesPage : BaseLayout + { + public ManageUserApprovedIpAddressesPage(Database database) : base(database) + {} + + public List ApprovedIpAddresses; + + public async Task OnGet() + { + User? user = this.Database.UserFromWebRequest(this.Request); + if (user == null) return this.Redirect("/login"); + + this.ApprovedIpAddresses = await this.Database.UserApprovedIpAddresses.Where(a => a.UserId == user.UserId).ToListAsync(); + + return this.Page(); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Types/UserApprovedIpAddress.cs b/ProjectLighthouse/Types/UserApprovedIpAddress.cs new file mode 100644 index 00000000..06c84183 --- /dev/null +++ b/ProjectLighthouse/Types/UserApprovedIpAddress.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace LBPUnion.ProjectLighthouse.Types +{ + public class UserApprovedIpAddress + { + [Key] + public int UserApprovedIpAddressId { get; set; } + + public int UserId { get; set; } + + [ForeignKey(nameof(UserId))] + public User User { get; set; } + + public string IpAddress { get; set; } + } +} \ No newline at end of file From 65e7771ce34c83fd84b62264f8725a8453382123 Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 13 Dec 2021 15:59:33 -0500 Subject: [PATCH 05/30] Add ability to approve an IP address --- .../Controllers/LoginController.cs | 51 ++++++++------ .../Controllers/MessageController.cs | 12 +++- .../ExternalAuth/AutoApprovalController.cs | 67 +++++++++++++++++++ ProjectLighthouse/Database.cs | 2 +- ...211213195540_AddUserApprovedIpAddresses.cs | 50 ++++++++++++++ .../Migrations/DatabaseModelSnapshot.cs | 30 +++++++++ .../ExternalAuth/AuthenticationPage.cshtml | 26 +++---- .../ManageUserApprovedIpAddressesPage.cshtml | 22 ++++++ ...anageUserApprovedIpAddressesPage.cshtml.cs | 29 ++++++++ .../Types/Settings/ServerSettings.cs | 4 +- .../Types/UserApprovedIpAddress.cs | 18 +++++ 11 files changed, 273 insertions(+), 38 deletions(-) create mode 100644 ProjectLighthouse/Controllers/Website/ExternalAuth/AutoApprovalController.cs create mode 100644 ProjectLighthouse/Migrations/20211213195540_AddUserApprovedIpAddresses.cs create mode 100644 ProjectLighthouse/Pages/ExternalAuth/ManageUserApprovedIpAddressesPage.cshtml create mode 100644 ProjectLighthouse/Pages/ExternalAuth/ManageUserApprovedIpAddressesPage.cshtml.cs create mode 100644 ProjectLighthouse/Types/UserApprovedIpAddress.cs diff --git a/ProjectLighthouse/Controllers/LoginController.cs b/ProjectLighthouse/Controllers/LoginController.cs index a8126a94..d5085096 100644 --- a/ProjectLighthouse/Controllers/LoginController.cs +++ b/ProjectLighthouse/Controllers/LoginController.cs @@ -1,4 +1,5 @@ #nullable enable +using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; @@ -43,12 +44,12 @@ namespace LBPUnion.ProjectLighthouse.Controllers } if (loginData == null) return this.BadRequest(); - IPAddress? ipAddress = this.HttpContext.Connection.RemoteIpAddress; - if (ipAddress == null) return this.StatusCode(403, ""); // 403 probably isnt the best status code for this, but whatever + IPAddress? remoteIpAddress = this.HttpContext.Connection.RemoteIpAddress; + if (remoteIpAddress == null) return this.StatusCode(403, ""); // 403 probably isnt the best status code for this, but whatever - string userLocation = ipAddress.ToString(); + string ipAddress = remoteIpAddress.ToString(); - GameToken? token = await this.database.AuthenticateUser(loginData, userLocation, titleId); + GameToken? token = await this.database.AuthenticateUser(loginData, ipAddress, titleId); if (token == null) return this.StatusCode(403, ""); User? user = await this.database.UserFromGameToken(token, true); @@ -56,28 +57,38 @@ namespace LBPUnion.ProjectLighthouse.Controllers if (ServerSettings.Instance.UseExternalAuth) { - string ipAddressAndName = $"{token.UserLocation}|{user.Username}"; - if (DeniedAuthenticationHelper.RecentlyDenied(ipAddressAndName) || DeniedAuthenticationHelper.GetAttempts(ipAddressAndName) > 3) + if (ServerSettings.Instance.BlockDeniedUsers) { - this.database.AuthenticationAttempts.RemoveRange - (this.database.AuthenticationAttempts.Include(a => a.GameToken).Where(a => a.GameToken.UserId == user.UserId)); + string ipAddressAndName = $"{token.UserLocation}|{user.Username}"; + if (DeniedAuthenticationHelper.RecentlyDenied(ipAddressAndName) || DeniedAuthenticationHelper.GetAttempts(ipAddressAndName) > 3) + { + this.database.AuthenticationAttempts.RemoveRange + (this.database.AuthenticationAttempts.Include(a => a.GameToken).Where(a => a.GameToken.UserId == user.UserId)); - DeniedAuthenticationHelper.AddAttempt(ipAddressAndName); + DeniedAuthenticationHelper.AddAttempt(ipAddressAndName); - await this.database.SaveChangesAsync(); - return this.StatusCode(403, ""); + await this.database.SaveChangesAsync(); + return this.StatusCode(403, ""); + } } - AuthenticationAttempt authAttempt = new() - { - GameToken = token, - GameTokenId = token.TokenId, - Timestamp = TimestampHelper.Timestamp, - IPAddress = userLocation, - Platform = token.GameVersion == GameVersion.LittleBigPlanetVita ? Platform.Vita : Platform.PS3, // TODO: properly identify RPCS3 - }; + List approvedIpAddresses = await this.database.UserApprovedIpAddresses.Where(a => a.UserId == user.UserId).ToListAsync(); + bool ipAddressApproved = approvedIpAddresses.Select(a => a.IpAddress).Contains(ipAddress); - this.database.AuthenticationAttempts.Add(authAttempt); + if (ipAddressApproved) token.Approved = true; + else + { + AuthenticationAttempt authAttempt = new() + { + GameToken = token, + GameTokenId = token.TokenId, + Timestamp = TimestampHelper.Timestamp, + IPAddress = ipAddress, + Platform = token.GameVersion == GameVersion.LittleBigPlanetVita ? Platform.Vita : Platform.PS3, // TODO: properly identify RPCS3 + }; + + this.database.AuthenticationAttempts.Add(authAttempt); + } } else { diff --git a/ProjectLighthouse/Controllers/MessageController.cs b/ProjectLighthouse/Controllers/MessageController.cs index 80851bbe..83531921 100644 --- a/ProjectLighthouse/Controllers/MessageController.cs +++ b/ProjectLighthouse/Controllers/MessageController.cs @@ -1,3 +1,4 @@ +#nullable enable using System.IO; using System.Threading.Tasks; using Kettu; @@ -27,10 +28,15 @@ namespace LBPUnion.ProjectLighthouse.Controllers [HttpGet("announce")] public async Task Announce() { - User user = await this.database.UserFromGameRequest(this.Request, true); - if (user == null) return this.StatusCode(403, ""); + (User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request); - if (ServerSettings.Instance.UseExternalAuth) + if (userAndToken == null) return this.StatusCode(403, ""); + + // ReSharper disable once PossibleInvalidOperationException + User user = userAndToken.Value.Item1; + GameToken gameToken = userAndToken.Value.Item2; + + if (ServerSettings.Instance.UseExternalAuth && !gameToken.Approved) return this.Ok ( "Please stay on this screen.\n" + diff --git a/ProjectLighthouse/Controllers/Website/ExternalAuth/AutoApprovalController.cs b/ProjectLighthouse/Controllers/Website/ExternalAuth/AutoApprovalController.cs new file mode 100644 index 00000000..f801b055 --- /dev/null +++ b/ProjectLighthouse/Controllers/Website/ExternalAuth/AutoApprovalController.cs @@ -0,0 +1,67 @@ +#nullable enable +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Types; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace LBPUnion.ProjectLighthouse.Controllers.Website.ExternalAuth +{ + [ApiController] + [Route("/authentication")] + public class AutoApprovalController : ControllerBase + { + private readonly Database database; + + public AutoApprovalController(Database database) + { + this.database = database; + } + + [HttpGet("autoApprove/{id:int}")] + public async Task AutoApprove([FromRoute] int id) + { + User? user = this.database.UserFromWebRequest(this.Request); + if (user == null) return this.Redirect("/login"); + + AuthenticationAttempt? authAttempt = await this.database.AuthenticationAttempts.Include + (a => a.GameToken) + .FirstOrDefaultAsync(a => a.AuthenticationAttemptId == id); + + if (authAttempt == null) return this.BadRequest(); + if (authAttempt.GameToken.UserId != user.UserId) return this.Redirect("/login"); + + authAttempt.GameToken.Approved = true; + + UserApprovedIpAddress approvedIpAddress = new() + { + UserId = user.UserId, + User = user, + IpAddress = authAttempt.IPAddress, + }; + + this.database.UserApprovedIpAddresses.Add(approvedIpAddress); + this.database.AuthenticationAttempts.Remove(authAttempt); + + await this.database.SaveChangesAsync(); + + return this.Redirect("/authentication"); + } + + [HttpGet("revokeAutoApproval/{id:int}")] + public async Task RevokeAutoApproval([FromRoute] int id) + { + User? user = this.database.UserFromWebRequest(this.Request); + if (user == null) return this.Redirect("/login"); + + UserApprovedIpAddress? approvedIpAddress = await this.database.UserApprovedIpAddresses.FirstOrDefaultAsync(a => a.UserApprovedIpAddressId == id); + if (approvedIpAddress == null) return this.BadRequest(); + if (approvedIpAddress.UserId != user.UserId) return this.Redirect("/login"); + + this.database.UserApprovedIpAddresses.Remove(approvedIpAddress); + + await this.database.SaveChangesAsync(); + + return this.Redirect("/authentication/autoApprovals"); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Database.cs b/ProjectLighthouse/Database.cs index 2af08e30..9fda3cab 100644 --- a/ProjectLighthouse/Database.cs +++ b/ProjectLighthouse/Database.cs @@ -34,6 +34,7 @@ namespace LBPUnion.ProjectLighthouse public DbSet AuthenticationAttempts { get; set; } public DbSet Reviews { get; set; } public DbSet RatedReviews { get; set; } + public DbSet UserApprovedIpAddresses { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder options) => options.UseMySql(ServerSettings.Instance.DbConnectionString, MySqlServerVersion.LatestSupportedServerVersion); @@ -66,7 +67,6 @@ namespace LBPUnion.ProjectLighthouse #nullable enable public async Task AuthenticateUser(LoginData loginData, string userLocation, string titleId = "") { - // TODO: don't use psn name to authenticate User? user = await this.Users.FirstOrDefaultAsync(u => u.Username == loginData.Username); if (user == null) return null; diff --git a/ProjectLighthouse/Migrations/20211213195540_AddUserApprovedIpAddresses.cs b/ProjectLighthouse/Migrations/20211213195540_AddUserApprovedIpAddresses.cs new file mode 100644 index 00000000..4f9264df --- /dev/null +++ b/ProjectLighthouse/Migrations/20211213195540_AddUserApprovedIpAddresses.cs @@ -0,0 +1,50 @@ +using LBPUnion.ProjectLighthouse; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ProjectLighthouse.Migrations +{ + [DbContext(typeof(Database))] + [Migration("20211213195540_AddUserApprovedIpAddresses")] + public partial class AddUserApprovedIpAddresses : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "UserApprovedIpAddresses", + columns: table => new + { + UserApprovedIpAddressId = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + UserId = table.Column(type: "int", nullable: false), + IpAddress = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + table.PrimaryKey("PK_UserApprovedIpAddresses", x => x.UserApprovedIpAddressId); + table.ForeignKey( + name: "FK_UserApprovedIpAddresses_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "UserId", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_UserApprovedIpAddresses_UserId", + table: "UserApprovedIpAddresses", + column: "UserId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UserApprovedIpAddresses"); + } + } +} diff --git a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs index 4afc7b51..64dfac1f 100644 --- a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs +++ b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs @@ -587,6 +587,25 @@ namespace ProjectLighthouse.Migrations b.ToTable("Users"); }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.UserApprovedIpAddress", b => + { + b.Property("UserApprovedIpAddressId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("IpAddress") + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("UserApprovedIpAddressId"); + + b.HasIndex("UserId"); + + b.ToTable("UserApprovedIpAddresses"); + }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.WebToken", b => { b.Property("TokenId") @@ -829,6 +848,17 @@ namespace ProjectLighthouse.Migrations b.Navigation("Location"); }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.UserApprovedIpAddress", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); #pragma warning restore 612, 618 } } diff --git a/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml b/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml index 97278a6a..d5a56773 100644 --- a/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml +++ b/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml @@ -18,21 +18,21 @@ else {

This device's IP address is @(Model.IpAddress.ToString()). If this matches with an authentication attempt below, then it's safe to assume the authentication attempt came from the same network as this device.

} - - - - - - - } + + + + + + + @foreach (AuthenticationAttempt authAttempt in Model.AuthenticationAttempts) { DateTimeOffset timestamp = DateTimeOffset.FromUnixTimeSeconds(authAttempt.Timestamp); diff --git a/ProjectLighthouse/Pages/ExternalAuth/ManageUserApprovedIpAddressesPage.cshtml b/ProjectLighthouse/Pages/ExternalAuth/ManageUserApprovedIpAddressesPage.cshtml new file mode 100644 index 00000000..dde2fa8a --- /dev/null +++ b/ProjectLighthouse/Pages/ExternalAuth/ManageUserApprovedIpAddressesPage.cshtml @@ -0,0 +1,22 @@ +@page "/authentication/autoApprovals" +@using LBPUnion.ProjectLighthouse.Types +@model LBPUnion.ProjectLighthouse.Pages.ExternalAuth.ManageUserApprovedIpAddressesPage + +@{ + Layout = "Layouts/BaseLayout"; + Model.Title = "Automatically approved IP addresses"; +} + + +@foreach (UserApprovedIpAddress approvedIpAddress in Model.ApprovedIpAddresses) +{ +
+

@approvedIpAddress.IpAddress

+ + + +
+} \ No newline at end of file diff --git a/ProjectLighthouse/Pages/ExternalAuth/ManageUserApprovedIpAddressesPage.cshtml.cs b/ProjectLighthouse/Pages/ExternalAuth/ManageUserApprovedIpAddressesPage.cshtml.cs new file mode 100644 index 00000000..00dfa3be --- /dev/null +++ b/ProjectLighthouse/Pages/ExternalAuth/ManageUserApprovedIpAddressesPage.cshtml.cs @@ -0,0 +1,29 @@ +#nullable enable +using System.Collections.Generic; +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.ExternalAuth +{ + public class ManageUserApprovedIpAddressesPage : BaseLayout + { + public ManageUserApprovedIpAddressesPage(Database database) : base(database) + {} + + public List ApprovedIpAddresses; + + public async Task OnGet() + { + User? user = this.Database.UserFromWebRequest(this.Request); + if (user == null) return this.Redirect("/login"); + + this.ApprovedIpAddresses = await this.Database.UserApprovedIpAddresses.Where(a => a.UserId == user.UserId).ToListAsync(); + + return this.Page(); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Types/Settings/ServerSettings.cs b/ProjectLighthouse/Types/Settings/ServerSettings.cs index ef1943e3..bdbb7676 100644 --- a/ProjectLighthouse/Types/Settings/ServerSettings.cs +++ b/ProjectLighthouse/Types/Settings/ServerSettings.cs @@ -12,7 +12,7 @@ namespace LBPUnion.ProjectLighthouse.Types.Settings public class ServerSettings { - public const int CurrentConfigVersion = 12; // MUST BE INCREMENTED FOR EVERY CONFIG CHANGE! + public const int CurrentConfigVersion = 13; // MUST BE INCREMENTED FOR EVERY CONFIG CHANGE! static ServerSettings() { if (ServerStatics.IsUnitTesting) return; // Unit testing, we don't want to read configurations here since the tests will provide their own @@ -97,6 +97,8 @@ namespace LBPUnion.ProjectLighthouse.Types.Settings public string GoogleAnalyticsId { get; set; } = ""; + public bool BlockDeniedUsers = true; + #region Meta [NotNull] diff --git a/ProjectLighthouse/Types/UserApprovedIpAddress.cs b/ProjectLighthouse/Types/UserApprovedIpAddress.cs new file mode 100644 index 00000000..06c84183 --- /dev/null +++ b/ProjectLighthouse/Types/UserApprovedIpAddress.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace LBPUnion.ProjectLighthouse.Types +{ + public class UserApprovedIpAddress + { + [Key] + public int UserApprovedIpAddressId { get; set; } + + public int UserId { get; set; } + + [ForeignKey(nameof(UserId))] + public User User { get; set; } + + public string IpAddress { get; set; } + } +} \ No newline at end of file From 9157fd39ae505a6fb6bffc180838c27990a153d0 Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 13 Dec 2021 18:36:29 -0500 Subject: [PATCH 06/30] Reject unapproved tokens on login --- ProjectLighthouse/Controllers/LoginController.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ProjectLighthouse/Controllers/LoginController.cs b/ProjectLighthouse/Controllers/LoginController.cs index d5085096..ced0fe44 100644 --- a/ProjectLighthouse/Controllers/LoginController.cs +++ b/ProjectLighthouse/Controllers/LoginController.cs @@ -97,6 +97,8 @@ namespace LBPUnion.ProjectLighthouse.Controllers await this.database.SaveChangesAsync(); + if (!token.Approved) return this.StatusCode(403, ""); + Logger.Log($"Successfully logged in user {user.Username} as {token.GameVersion} client ({titleId})", LoggerLevelLogin.Instance); // Create a new room on LBP2+/Vita From 78e3bad38e4bb03ce0a64a94c5ebee3b9a332a5d Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 13 Dec 2021 20:10:17 -0500 Subject: [PATCH 07/30] Redo login process --- .../Controllers/LoginController.cs | 30 +++++++--- .../Controllers/MessageController.cs | 27 +++------ .../20211214005427_AddUsedBoolToGameToken.cs | 55 +++++++++++++++++++ .../Migrations/DatabaseModelSnapshot.cs | 16 ++++++ ProjectLighthouse/Types/GameToken.cs | 10 +++- 5 files changed, 111 insertions(+), 27 deletions(-) create mode 100644 ProjectLighthouse/Migrations/20211214005427_AddUsedBoolToGameToken.cs diff --git a/ProjectLighthouse/Controllers/LoginController.cs b/ProjectLighthouse/Controllers/LoginController.cs index ced0fe44..85c04ef5 100644 --- a/ProjectLighthouse/Controllers/LoginController.cs +++ b/ProjectLighthouse/Controllers/LoginController.cs @@ -1,5 +1,4 @@ #nullable enable -using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; @@ -49,8 +48,16 @@ namespace LBPUnion.ProjectLighthouse.Controllers string ipAddress = remoteIpAddress.ToString(); - GameToken? token = await this.database.AuthenticateUser(loginData, ipAddress, titleId); - if (token == null) return this.StatusCode(403, ""); + // Get an existing token from the IP & username + GameToken? token = await this.database.GameTokens.Include + (t => t.User) + .FirstOrDefaultAsync(t => t.UserLocation == ipAddress && t.User.Username == loginData.Username && t.Approved && !t.Used); + + if (token == null) // If we cant find an existing token, try to generate a new one + { + token = await this.database.AuthenticateUser(loginData, ipAddress, titleId); + if (token == null) return this.StatusCode(403, ""); // If not, then 403. + } User? user = await this.database.UserFromGameToken(token, true); if (user == null) return this.StatusCode(403, ""); @@ -72,10 +79,10 @@ namespace LBPUnion.ProjectLighthouse.Controllers } } - List approvedIpAddresses = await this.database.UserApprovedIpAddresses.Where(a => a.UserId == user.UserId).ToListAsync(); - bool ipAddressApproved = approvedIpAddresses.Select(a => a.IpAddress).Contains(ipAddress); - - if (ipAddressApproved) token.Approved = true; + if (this.database.UserApprovedIpAddresses.Where + (a => a.UserId == user.UserId) + .Select(a => a.IpAddress) + .Contains(ipAddress)) token.Approved = true; else { AuthenticationAttempt authAttempt = new() @@ -100,8 +107,15 @@ namespace LBPUnion.ProjectLighthouse.Controllers if (!token.Approved) return this.StatusCode(403, ""); Logger.Log($"Successfully logged in user {user.Username} as {token.GameVersion} client ({titleId})", LoggerLevelLogin.Instance); + // After this point we are now considering this session as logged in. - // Create a new room on LBP2+/Vita + // We just logged in with the token. Mark it as used so someone else doesnt try to use it, + // and so we don't pick the same token up when logging in later. + token.Used = true; + + await this.database.SaveChangesAsync(); + + // Create a new room on LBP2/3/Vita if (token.GameVersion != GameVersion.LittleBigPlanet1) RoomHelper.CreateRoom(user); return this.Ok diff --git a/ProjectLighthouse/Controllers/MessageController.cs b/ProjectLighthouse/Controllers/MessageController.cs index 83531921..f1ec7748 100644 --- a/ProjectLighthouse/Controllers/MessageController.cs +++ b/ProjectLighthouse/Controllers/MessageController.cs @@ -23,28 +23,19 @@ namespace LBPUnion.ProjectLighthouse.Controllers } [HttpGet("eula")] - public IActionResult Eula() => this.Ok(ServerSettings.Instance.EulaText + "\n" + $"{EulaHelper.License}\n"); + public async Task Eula() + { + User? user = await this.database.UserFromGameRequest(this.Request); + if (user == null) return this.StatusCode(403, ""); + + return this.Ok(ServerSettings.Instance.EulaText + "\n" + $"{EulaHelper.License}\n"); + } [HttpGet("announce")] public async Task Announce() { - (User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request); - - if (userAndToken == null) return this.StatusCode(403, ""); - - // ReSharper disable once PossibleInvalidOperationException - User user = userAndToken.Value.Item1; - GameToken gameToken = userAndToken.Value.Item2; - - if (ServerSettings.Instance.UseExternalAuth && !gameToken.Approved) - return this.Ok - ( - "Please stay on this screen.\n" + - $"Before continuing, you must approve this session at {ServerSettings.Instance.ExternalUrl}.\n" + - "Please keep in mind that if the session is denied you may have to wait up to 5-10 minutes to try logging in again.\n" + - "Once approved, you may press X and continue.\n\n" + - ServerSettings.Instance.EulaText - ); + User? user = await this.database.UserFromGameRequest(this.Request); + if (user == null) return this.StatusCode(403, ""); return this.Ok($"You are now logged in as {user.Username} (id: {user.UserId}).\n\n" + ServerSettings.Instance.EulaText); } diff --git a/ProjectLighthouse/Migrations/20211214005427_AddUsedBoolToGameToken.cs b/ProjectLighthouse/Migrations/20211214005427_AddUsedBoolToGameToken.cs new file mode 100644 index 00000000..359a10ef --- /dev/null +++ b/ProjectLighthouse/Migrations/20211214005427_AddUsedBoolToGameToken.cs @@ -0,0 +1,55 @@ +using LBPUnion.ProjectLighthouse; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ProjectLighthouse.Migrations +{ + [DbContext(typeof(Database))] + [Migration("20211214005427_AddUsedBoolToGameToken")] + public partial class AddUsedBoolToGameToken : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + // Incompatible with old tokens + migrationBuilder.Sql("DELETE FROM AuthenticationAttempts"); + migrationBuilder.Sql("DELETE FROM GameTokens"); + + migrationBuilder.AddColumn( + name: "Used", + table: "GameTokens", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + + migrationBuilder.CreateIndex( + name: "IX_GameTokens_UserId", + table: "GameTokens", + column: "UserId"); + + migrationBuilder.AddForeignKey( + name: "FK_GameTokens_Users_UserId", + table: "GameTokens", + column: "UserId", + principalTable: "Users", + principalColumn: "UserId", + onDelete: ReferentialAction.Cascade); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_GameTokens_Users_UserId", + table: "GameTokens"); + + migrationBuilder.DropIndex( + name: "IX_GameTokens_UserId", + table: "GameTokens"); + + migrationBuilder.DropColumn( + name: "Used", + table: "GameTokens"); + } + } +} diff --git a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs index 64dfac1f..cdb0c96a 100644 --- a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs +++ b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs @@ -55,6 +55,9 @@ namespace ProjectLighthouse.Migrations b.Property("GameVersion") .HasColumnType("int"); + b.Property("Used") + .HasColumnType("tinyint(1)"); + b.Property("UserId") .HasColumnType("int"); @@ -66,6 +69,8 @@ namespace ProjectLighthouse.Migrations b.HasKey("TokenId"); + b.HasIndex("UserId"); + b.ToTable("GameTokens"); }); @@ -634,6 +639,17 @@ namespace ProjectLighthouse.Migrations b.Navigation("GameToken"); }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.GameToken", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.HeartedProfile", b => { b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "HeartedUser") diff --git a/ProjectLighthouse/Types/GameToken.cs b/ProjectLighthouse/Types/GameToken.cs index 4fdca27d..74ea937d 100644 --- a/ProjectLighthouse/Types/GameToken.cs +++ b/ProjectLighthouse/Types/GameToken.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace LBPUnion.ProjectLighthouse.Types { @@ -10,12 +11,19 @@ namespace LBPUnion.ProjectLighthouse.Types public int UserId { get; set; } + [ForeignKey(nameof(UserId))] + public User User { get; set; } + public string UserToken { get; set; } public string UserLocation { get; set; } public GameVersion GameVersion { get; set; } - public bool Approved { get; set; } = false; + // Set by /authentication webpage + public bool Approved { get; set; } + + // Set to true on login + public bool Used { get; set; } } } \ No newline at end of file From adb5dbef450b41ccef8cf41a2e07289773d1cc1b Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 13 Dec 2021 20:52:58 -0500 Subject: [PATCH 08/30] Show debug info on announce screen --- .../Controllers/LoginController.cs | 10 +++---- .../Controllers/MessageController.cs | 27 +++++++++++++++++-- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/ProjectLighthouse/Controllers/LoginController.cs b/ProjectLighthouse/Controllers/LoginController.cs index 85c04ef5..02ed9740 100644 --- a/ProjectLighthouse/Controllers/LoginController.cs +++ b/ProjectLighthouse/Controllers/LoginController.cs @@ -51,7 +51,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers // Get an existing token from the IP & username GameToken? token = await this.database.GameTokens.Include (t => t.User) - .FirstOrDefaultAsync(t => t.UserLocation == ipAddress && t.User.Username == loginData.Username && t.Approved && !t.Used); + .FirstOrDefaultAsync(t => t.UserLocation == ipAddress && t.User.Username == loginData.Username && !t.Used); if (token == null) // If we cant find an existing token, try to generate a new one { @@ -79,10 +79,10 @@ namespace LBPUnion.ProjectLighthouse.Controllers } } - if (this.database.UserApprovedIpAddresses.Where - (a => a.UserId == user.UserId) - .Select(a => a.IpAddress) - .Contains(ipAddress)) token.Approved = true; + if (this.database.UserApprovedIpAddresses.Where(a => a.UserId == user.UserId).Select(a => a.IpAddress).Contains(ipAddress)) + { + token.Approved = true; + } else { AuthenticationAttempt authAttempt = new() diff --git a/ProjectLighthouse/Controllers/MessageController.cs b/ProjectLighthouse/Controllers/MessageController.cs index f1ec7748..c16f6921 100644 --- a/ProjectLighthouse/Controllers/MessageController.cs +++ b/ProjectLighthouse/Controllers/MessageController.cs @@ -34,10 +34,33 @@ namespace LBPUnion.ProjectLighthouse.Controllers [HttpGet("announce")] public async Task Announce() { + #if !DEBUG User? user = await this.database.UserFromGameRequest(this.Request); if (user == null) return this.StatusCode(403, ""); + #else + (User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request); - return this.Ok($"You are now logged in as {user.Username} (id: {user.UserId}).\n\n" + ServerSettings.Instance.EulaText); + if (userAndToken == null) return this.StatusCode(403, ""); + + // ReSharper disable once PossibleInvalidOperationException + User user = userAndToken.Value.Item1; + GameToken gameToken = userAndToken.Value.Item2; + #endif + + return this.Ok + ( + $"You are now logged in as {user.Username}.\n\n" + + #if DEBUG + "---DEBUG INFO---\n" + + $"user.UserId: {user.UserId}\n" + + $"token.Approved: {gameToken.Approved}\n" + + $"token.Used: {gameToken.Used}\n" + + $"token.UserLocation: {gameToken.UserLocation}\n" + + $"token.GameVersion: {gameToken.GameVersion}\n" + + "---DEBUG INFO---\n\n" + + #endif + ServerSettings.Instance.EulaText + ); } [HttpGet("notification")] @@ -49,7 +72,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers [HttpPost("filter")] public async Task Filter() { - User user = await this.database.UserFromGameRequest(this.Request); + User? user = await this.database.UserFromGameRequest(this.Request); if (user == null) return this.StatusCode(403, ""); string loggedText = await new StreamReader(this.Request.Body).ReadToEndAsync(); From 4967b2de556df3875efb932d3ca7703def65b434 Mon Sep 17 00:00:00 2001 From: LumaLivy <7350336+LumaLivy@users.noreply.github.com> Date: Fri, 17 Dec 2021 16:29:30 -0500 Subject: [PATCH 09/30] Decode escaped HTML sequences --- ProjectLighthouse/Pages/UserPage.cshtml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ProjectLighthouse/Pages/UserPage.cshtml b/ProjectLighthouse/Pages/UserPage.cshtml index 128a7883..ce5440d5 100644 --- a/ProjectLighthouse/Pages/UserPage.cshtml +++ b/ProjectLighthouse/Pages/UserPage.cshtml @@ -2,6 +2,8 @@ @using LBPUnion.ProjectLighthouse.Types @using LBPUnion.ProjectLighthouse.Types.Profiles @using LBPUnion.ProjectLighthouse.Types.Settings +@using System.Web; +@using System.IO; @model LBPUnion.ProjectLighthouse.Pages.UserPage @{ @@ -92,9 +94,12 @@ @foreach (Comment comment in Model.Comments!) { DateTimeOffset timestamp = DateTimeOffset.FromUnixTimeSeconds(comment.Timestamp / 1000); + StringWriter messageWriter = new StringWriter(); + HttpUtility.HtmlDecode(comment.Message, messageWriter); + String decodedMessage = messageWriter.ToString();
@comment.Poster.Username: - @comment.Message + @decodedMessage

@timestamp.ToString("MM/dd/yyyy @ h:mm tt") UTC

From 436ef8b17ea1e388fdc37677029ecc992ec948a4 Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 13 Dec 2021 14:53:42 -0500 Subject: [PATCH 10/30] Redesign authentication page --- .../Pages/ExternalAuth/AuthenticationPage.cshtml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml b/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml index d5a56773..ecbfb91f 100644 --- a/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml +++ b/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml @@ -18,6 +18,19 @@ else {

This device's IP address is @(Model.IpAddress.ToString()). If this matches with an authentication attempt below, then it's safe to assume the authentication attempt came from the same network as this device.

} + + + + + + + } From 6cc8061775d02c1ea08d2ca9405237354eac5083 Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 13 Dec 2021 15:59:33 -0500 Subject: [PATCH 11/30] Add ability to approve an IP address --- .../Controllers/LoginController.cs | 21 +++++++------------ .../Controllers/MessageController.cs | 19 ++++++++++------- .../ExternalAuth/AuthenticationPage.cshtml | 13 ------------ 3 files changed, 19 insertions(+), 34 deletions(-) diff --git a/ProjectLighthouse/Controllers/LoginController.cs b/ProjectLighthouse/Controllers/LoginController.cs index 02ed9740..f871cd5d 100644 --- a/ProjectLighthouse/Controllers/LoginController.cs +++ b/ProjectLighthouse/Controllers/LoginController.cs @@ -1,4 +1,5 @@ #nullable enable +using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; @@ -48,16 +49,8 @@ namespace LBPUnion.ProjectLighthouse.Controllers string ipAddress = remoteIpAddress.ToString(); - // Get an existing token from the IP & username - GameToken? token = await this.database.GameTokens.Include - (t => t.User) - .FirstOrDefaultAsync(t => t.UserLocation == ipAddress && t.User.Username == loginData.Username && !t.Used); - - if (token == null) // If we cant find an existing token, try to generate a new one - { - token = await this.database.AuthenticateUser(loginData, ipAddress, titleId); - if (token == null) return this.StatusCode(403, ""); // If not, then 403. - } + GameToken? token = await this.database.AuthenticateUser(loginData, ipAddress, titleId); + if (token == null) return this.StatusCode(403, ""); User? user = await this.database.UserFromGameToken(token, true); if (user == null) return this.StatusCode(403, ""); @@ -79,10 +72,10 @@ namespace LBPUnion.ProjectLighthouse.Controllers } } - if (this.database.UserApprovedIpAddresses.Where(a => a.UserId == user.UserId).Select(a => a.IpAddress).Contains(ipAddress)) - { - token.Approved = true; - } + List approvedIpAddresses = await this.database.UserApprovedIpAddresses.Where(a => a.UserId == user.UserId).ToListAsync(); + bool ipAddressApproved = approvedIpAddresses.Select(a => a.IpAddress).Contains(ipAddress); + + if (ipAddressApproved) token.Approved = true; else { AuthenticationAttempt authAttempt = new() diff --git a/ProjectLighthouse/Controllers/MessageController.cs b/ProjectLighthouse/Controllers/MessageController.cs index c16f6921..77507dc2 100644 --- a/ProjectLighthouse/Controllers/MessageController.cs +++ b/ProjectLighthouse/Controllers/MessageController.cs @@ -34,10 +34,6 @@ namespace LBPUnion.ProjectLighthouse.Controllers [HttpGet("announce")] public async Task Announce() { - #if !DEBUG - User? user = await this.database.UserFromGameRequest(this.Request); - if (user == null) return this.StatusCode(403, ""); - #else (User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request); if (userAndToken == null) return this.StatusCode(403, ""); @@ -45,12 +41,21 @@ namespace LBPUnion.ProjectLighthouse.Controllers // ReSharper disable once PossibleInvalidOperationException User user = userAndToken.Value.Item1; GameToken gameToken = userAndToken.Value.Item2; - #endif + + if (ServerSettings.Instance.UseExternalAuth && !gameToken.Approved) + return this.Ok + ( + "Please stay on this screen.\n" + + $"Before continuing, you must approve this session at {ServerSettings.Instance.ExternalUrl}.\n" + + "Please keep in mind that if the session is denied you may have to wait up to 5-10 minutes to try logging in again.\n" + + "Once approved, you may press X and continue.\n\n" + + ServerSettings.Instance.EulaText + ); return this.Ok ( $"You are now logged in as {user.Username}.\n\n" + - #if DEBUG +#if DEBUG "---DEBUG INFO---\n" + $"user.UserId: {user.UserId}\n" + $"token.Approved: {gameToken.Approved}\n" + @@ -58,7 +63,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers $"token.UserLocation: {gameToken.UserLocation}\n" + $"token.GameVersion: {gameToken.GameVersion}\n" + "---DEBUG INFO---\n\n" + - #endif +#endif ServerSettings.Instance.EulaText ); } diff --git a/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml b/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml index ecbfb91f..d5a56773 100644 --- a/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml +++ b/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml @@ -18,19 +18,6 @@ else {

This device's IP address is @(Model.IpAddress.ToString()). If this matches with an authentication attempt below, then it's safe to assume the authentication attempt came from the same network as this device.

} - -
- - - - - } From 23657f942d315f1f2ce0a6fbaf9e511b61bcc639 Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 13 Dec 2021 18:36:29 -0500 Subject: [PATCH 12/30] Reject unapproved tokens on login --- ProjectLighthouse/Controllers/LoginController.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ProjectLighthouse/Controllers/LoginController.cs b/ProjectLighthouse/Controllers/LoginController.cs index f871cd5d..af659692 100644 --- a/ProjectLighthouse/Controllers/LoginController.cs +++ b/ProjectLighthouse/Controllers/LoginController.cs @@ -99,6 +99,8 @@ namespace LBPUnion.ProjectLighthouse.Controllers if (!token.Approved) return this.StatusCode(403, ""); + Logger.Log($"Successfully logged in user {user.Username} as {token.GameVersion} client ({titleId})", LoggerLevelLogin.Instance); + Logger.Log($"Successfully logged in user {user.Username} as {token.GameVersion} client ({titleId})", LoggerLevelLogin.Instance); // After this point we are now considering this session as logged in. From ea6af58aa0ddc2c4f75fffa8ee869fcf2b7e1744 Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 13 Dec 2021 20:10:17 -0500 Subject: [PATCH 13/30] Redo login process --- .../Controllers/LoginController.cs | 23 +++++++++++-------- .../Controllers/MessageController.cs | 19 ++------------- 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/ProjectLighthouse/Controllers/LoginController.cs b/ProjectLighthouse/Controllers/LoginController.cs index af659692..85c04ef5 100644 --- a/ProjectLighthouse/Controllers/LoginController.cs +++ b/ProjectLighthouse/Controllers/LoginController.cs @@ -1,5 +1,4 @@ #nullable enable -using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; @@ -49,8 +48,16 @@ namespace LBPUnion.ProjectLighthouse.Controllers string ipAddress = remoteIpAddress.ToString(); - GameToken? token = await this.database.AuthenticateUser(loginData, ipAddress, titleId); - if (token == null) return this.StatusCode(403, ""); + // Get an existing token from the IP & username + GameToken? token = await this.database.GameTokens.Include + (t => t.User) + .FirstOrDefaultAsync(t => t.UserLocation == ipAddress && t.User.Username == loginData.Username && t.Approved && !t.Used); + + if (token == null) // If we cant find an existing token, try to generate a new one + { + token = await this.database.AuthenticateUser(loginData, ipAddress, titleId); + if (token == null) return this.StatusCode(403, ""); // If not, then 403. + } User? user = await this.database.UserFromGameToken(token, true); if (user == null) return this.StatusCode(403, ""); @@ -72,10 +79,10 @@ namespace LBPUnion.ProjectLighthouse.Controllers } } - List approvedIpAddresses = await this.database.UserApprovedIpAddresses.Where(a => a.UserId == user.UserId).ToListAsync(); - bool ipAddressApproved = approvedIpAddresses.Select(a => a.IpAddress).Contains(ipAddress); - - if (ipAddressApproved) token.Approved = true; + if (this.database.UserApprovedIpAddresses.Where + (a => a.UserId == user.UserId) + .Select(a => a.IpAddress) + .Contains(ipAddress)) token.Approved = true; else { AuthenticationAttempt authAttempt = new() @@ -99,8 +106,6 @@ namespace LBPUnion.ProjectLighthouse.Controllers if (!token.Approved) return this.StatusCode(403, ""); - Logger.Log($"Successfully logged in user {user.Username} as {token.GameVersion} client ({titleId})", LoggerLevelLogin.Instance); - Logger.Log($"Successfully logged in user {user.Username} as {token.GameVersion} client ({titleId})", LoggerLevelLogin.Instance); // After this point we are now considering this session as logged in. diff --git a/ProjectLighthouse/Controllers/MessageController.cs b/ProjectLighthouse/Controllers/MessageController.cs index 77507dc2..c6c55721 100644 --- a/ProjectLighthouse/Controllers/MessageController.cs +++ b/ProjectLighthouse/Controllers/MessageController.cs @@ -34,23 +34,8 @@ namespace LBPUnion.ProjectLighthouse.Controllers [HttpGet("announce")] public async Task Announce() { - (User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request); - - if (userAndToken == null) return this.StatusCode(403, ""); - - // ReSharper disable once PossibleInvalidOperationException - User user = userAndToken.Value.Item1; - GameToken gameToken = userAndToken.Value.Item2; - - if (ServerSettings.Instance.UseExternalAuth && !gameToken.Approved) - return this.Ok - ( - "Please stay on this screen.\n" + - $"Before continuing, you must approve this session at {ServerSettings.Instance.ExternalUrl}.\n" + - "Please keep in mind that if the session is denied you may have to wait up to 5-10 minutes to try logging in again.\n" + - "Once approved, you may press X and continue.\n\n" + - ServerSettings.Instance.EulaText - ); + User? user = await this.database.UserFromGameRequest(this.Request); + if (user == null) return this.StatusCode(403, ""); return this.Ok ( From 55707e9e3ef718353f9370ecd66011729d45574c Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 13 Dec 2021 20:52:58 -0500 Subject: [PATCH 14/30] Show debug info on announce screen --- ProjectLighthouse/Controllers/LoginController.cs | 10 +++++----- ProjectLighthouse/Controllers/MessageController.cs | 10 ++++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/ProjectLighthouse/Controllers/LoginController.cs b/ProjectLighthouse/Controllers/LoginController.cs index 85c04ef5..02ed9740 100644 --- a/ProjectLighthouse/Controllers/LoginController.cs +++ b/ProjectLighthouse/Controllers/LoginController.cs @@ -51,7 +51,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers // Get an existing token from the IP & username GameToken? token = await this.database.GameTokens.Include (t => t.User) - .FirstOrDefaultAsync(t => t.UserLocation == ipAddress && t.User.Username == loginData.Username && t.Approved && !t.Used); + .FirstOrDefaultAsync(t => t.UserLocation == ipAddress && t.User.Username == loginData.Username && !t.Used); if (token == null) // If we cant find an existing token, try to generate a new one { @@ -79,10 +79,10 @@ namespace LBPUnion.ProjectLighthouse.Controllers } } - if (this.database.UserApprovedIpAddresses.Where - (a => a.UserId == user.UserId) - .Select(a => a.IpAddress) - .Contains(ipAddress)) token.Approved = true; + if (this.database.UserApprovedIpAddresses.Where(a => a.UserId == user.UserId).Select(a => a.IpAddress).Contains(ipAddress)) + { + token.Approved = true; + } else { AuthenticationAttempt authAttempt = new() diff --git a/ProjectLighthouse/Controllers/MessageController.cs b/ProjectLighthouse/Controllers/MessageController.cs index c6c55721..3a339632 100644 --- a/ProjectLighthouse/Controllers/MessageController.cs +++ b/ProjectLighthouse/Controllers/MessageController.cs @@ -34,8 +34,18 @@ namespace LBPUnion.ProjectLighthouse.Controllers [HttpGet("announce")] public async Task Announce() { +#if !DEBUG User? user = await this.database.UserFromGameRequest(this.Request); if (user == null) return this.StatusCode(403, ""); +#else + (User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request); + + if (userAndToken == null) return this.StatusCode(403, ""); + + // ReSharper disable once PossibleInvalidOperationException + User user = userAndToken.Value.Item1; + GameToken gameToken = userAndToken.Value.Item2; +#endif return this.Ok ( From 9e7af4fdd321409459e50c3588e8e1d664a9e57a Mon Sep 17 00:00:00 2001 From: LumaLivy <7350336+LumaLivy@users.noreply.github.com> Date: Fri, 17 Dec 2021 16:50:59 -0500 Subject: [PATCH 15/30] Revert weird vscode formatting --- ProjectLighthouse/Controllers/MessageController.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ProjectLighthouse/Controllers/MessageController.cs b/ProjectLighthouse/Controllers/MessageController.cs index 3a339632..c16f6921 100644 --- a/ProjectLighthouse/Controllers/MessageController.cs +++ b/ProjectLighthouse/Controllers/MessageController.cs @@ -34,10 +34,10 @@ namespace LBPUnion.ProjectLighthouse.Controllers [HttpGet("announce")] public async Task Announce() { -#if !DEBUG + #if !DEBUG User? user = await this.database.UserFromGameRequest(this.Request); if (user == null) return this.StatusCode(403, ""); -#else + #else (User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request); if (userAndToken == null) return this.StatusCode(403, ""); @@ -45,12 +45,12 @@ namespace LBPUnion.ProjectLighthouse.Controllers // ReSharper disable once PossibleInvalidOperationException User user = userAndToken.Value.Item1; GameToken gameToken = userAndToken.Value.Item2; -#endif + #endif return this.Ok ( $"You are now logged in as {user.Username}.\n\n" + -#if DEBUG + #if DEBUG "---DEBUG INFO---\n" + $"user.UserId: {user.UserId}\n" + $"token.Approved: {gameToken.Approved}\n" + @@ -58,7 +58,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers $"token.UserLocation: {gameToken.UserLocation}\n" + $"token.GameVersion: {gameToken.GameVersion}\n" + "---DEBUG INFO---\n\n" + -#endif + #endif ServerSettings.Instance.EulaText ); } From 5cb2627320f0809cef721b5ef0c935ec42186556 Mon Sep 17 00:00:00 2001 From: LumaLivy <7350336+LumaLivy@users.noreply.github.com> Date: Fri, 17 Dec 2021 23:58:16 -0500 Subject: [PATCH 16/30] Photo subject proposal --- .../Pages/Layouts/BaseLayout.cshtml | 11 ++ .../Pages/Partials/PhotoPartial.cshtml | 115 +++++++++++++++++- 2 files changed, 121 insertions(+), 5 deletions(-) diff --git a/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml b/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml index 2295cea3..d4e14ade 100644 --- a/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml +++ b/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml @@ -64,6 +64,17 @@ gtag('config', '@ServerSettings.Instance.GoogleAnalyticsId'); } + +
diff --git a/ProjectLighthouse/Pages/Partials/PhotoPartial.cshtml b/ProjectLighthouse/Pages/Partials/PhotoPartial.cshtml index 33a8fbc1..af350119 100644 --- a/ProjectLighthouse/Pages/Partials/PhotoPartial.cshtml +++ b/ProjectLighthouse/Pages/Partials/PhotoPartial.cshtml @@ -1,7 +1,15 @@ @using LBPUnion.ProjectLighthouse.Types + @model LBPUnion.ProjectLighthouse.Types.Photo - + + +
+ + +

@@ -16,7 +24,104 @@

Photo contains @Model.Subjects.Count @(Model.Subjects.Count == 1 ? "person" : "people"):

-@foreach (PhotoSubject subject in Model.Subjects) -{ -
@subject.User.Username -} \ No newline at end of file +
+ @foreach (PhotoSubject subject in Model.Subjects) + { + @subject.User.Username + } +
+ + + \ No newline at end of file From 390edd7286f93a0086495959e6a6af561a9bfe7e Mon Sep 17 00:00:00 2001 From: LumaLivy <7350336+LumaLivy@users.noreply.github.com> Date: Sat, 18 Dec 2021 00:19:05 -0500 Subject: [PATCH 17/30] Change timeout to page load event --- ProjectLighthouse/Pages/Partials/PhotoPartial.cshtml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ProjectLighthouse/Pages/Partials/PhotoPartial.cshtml b/ProjectLighthouse/Pages/Partials/PhotoPartial.cshtml index af350119..23312584 100644 --- a/ProjectLighthouse/Pages/Partials/PhotoPartial.cshtml +++ b/ProjectLighthouse/Pages/Partials/PhotoPartial.cshtml @@ -33,9 +33,8 @@ \ No newline at end of file From 28057ae0572257a275a79f84568fa2401cb8ff1c Mon Sep 17 00:00:00 2001 From: LumaLivy <7350336+LumaLivy@users.noreply.github.com> Date: Sat, 18 Dec 2021 00:19:50 -0500 Subject: [PATCH 18/30] Tidy up JS --- ProjectLighthouse/Pages/Partials/PhotoPartial.cshtml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ProjectLighthouse/Pages/Partials/PhotoPartial.cshtml b/ProjectLighthouse/Pages/Partials/PhotoPartial.cshtml index 23312584..4d41a79d 100644 --- a/ProjectLighthouse/Pages/Partials/PhotoPartial.cshtml +++ b/ProjectLighthouse/Pages/Partials/PhotoPartial.cshtml @@ -42,10 +42,10 @@ const image = document.getElementById("game-image-@Model.PhotoId"); hoverer.addEventListener('mouseenter', function () { - canvas.className = "photo-subjects" + canvas.className = "photo-subjects"; }); hoverer.addEventListener('mouseleave', function () { - canvas.className = "photo-subjects hide-subjects" + canvas.className = "photo-subjects hide-subjects"; }); var context = canvas.getContext('2d'); @@ -62,11 +62,11 @@ const hw = w / 2; const hh = h / 2; - const colors = ["#a5599f", "#477f84", "#5fa559", "#a5595f"]; + const colours = ["#a5599f", "#477f84", "#5fa559", "#a5595f"]; subjects.forEach((s, si) => { - const colour = colors[si % 4] + const colour = colours[si % 4]; // Bounding box const bounds = s.bounds.split(",").map(parseFloat); From df292dc156443151abd15581073cef4fd9a1c0c7 Mon Sep 17 00:00:00 2001 From: LumaLivy <7350336+LumaLivy@users.noreply.github.com> Date: Sat, 18 Dec 2021 00:28:27 -0500 Subject: [PATCH 19/30] Fix bug w/ label bg rendering bad on left overflow --- ProjectLighthouse/Pages/Partials/PhotoPartial.cshtml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ProjectLighthouse/Pages/Partials/PhotoPartial.cshtml b/ProjectLighthouse/Pages/Partials/PhotoPartial.cshtml index 4d41a79d..081a3bcc 100644 --- a/ProjectLighthouse/Pages/Partials/PhotoPartial.cshtml +++ b/ProjectLighthouse/Pages/Partials/PhotoPartial.cshtml @@ -6,7 +6,7 @@
+ style="position: absolute; transform: rotate(180deg)">
@@ -100,17 +100,17 @@ // Check if the label will flow off the left of the frame const overflowLeft = (bounds[2] * hw - tw) < (-hw); + // Set alignment + context.textAlign = overflowLeft ? "start" : "end"; + // Text x / y const lx = overflowLeft ? -bw + 6 : -6; const ly = overflowBottom ? -bh - 6 : 16; // Label background x / y - const lbx = overflowLeft ? -bw : -2; + const lbx = overflowLeft ? bw - tw - 12 : -2; const lby = overflowBottom ? bh : -24; - // Set alignment - context.textAlign = overflowLeft ? "start" : "end"; - // Draw background context.fillRect(lbx, lby, tw + 16, th); From d2ec5504217c7d424c04c92a0aa5fe01ca5c1aca Mon Sep 17 00:00:00 2001 From: jvyden Date: Sat, 18 Dec 2021 15:43:16 -0500 Subject: [PATCH 20/30] Fix performance on photos page --- .../Pages/Partials/PhotoPartial.cshtml | 26 +++++++++---------- ProjectLighthouse/Types/PhotoSubject.cs | 2 ++ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/ProjectLighthouse/Pages/Partials/PhotoPartial.cshtml b/ProjectLighthouse/Pages/Partials/PhotoPartial.cshtml index 081a3bcc..761f63b8 100644 --- a/ProjectLighthouse/Pages/Partials/PhotoPartial.cshtml +++ b/ProjectLighthouse/Pages/Partials/PhotoPartial.cshtml @@ -1,14 +1,13 @@ @using LBPUnion.ProjectLighthouse.Types - @model LBPUnion.ProjectLighthouse.Types.Photo -
+ style="position: absolute; transform: rotate(180deg)"> + + style="width: 100%; height: auto; border-radius: .28571429rem;">

@@ -31,14 +30,16 @@ }
+@{ + PhotoSubject[] subjects = Model.Subjects.ToArray(); + foreach (PhotoSubject subject in subjects) subject.Username = subject.User.Username; +} } - -
diff --git a/ProjectLighthouse/StaticFiles/css/styles.css b/ProjectLighthouse/StaticFiles/css/styles.css index 53cbfaa5..08316168 100644 --- a/ProjectLighthouse/StaticFiles/css/styles.css +++ b/ProjectLighthouse/StaticFiles/css/styles.css @@ -16,3 +16,12 @@ div.statsUnderTitle > span { margin-right: 5px; } +canvas.photo-subjects { + opacity: 1; + transition: opacity 0.3s; +} + +canvas.hide-subjects { + opacity: 0; +} + From 370637ecd255ac4b11ed8cf889a3aaeffb29165e Mon Sep 17 00:00:00 2001 From: jvyden Date: Sat, 18 Dec 2021 15:49:55 -0500 Subject: [PATCH 24/30] Oh, so that's why it's in the BaseLayout. --- ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml | 11 +++++++++++ ProjectLighthouse/StaticFiles/css/styles.css | 9 --------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml b/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml index 2295cea3..d4e14ade 100644 --- a/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml +++ b/ProjectLighthouse/Pages/Layouts/BaseLayout.cshtml @@ -64,6 +64,17 @@ gtag('config', '@ServerSettings.Instance.GoogleAnalyticsId'); } + +
diff --git a/ProjectLighthouse/StaticFiles/css/styles.css b/ProjectLighthouse/StaticFiles/css/styles.css index 08316168..53cbfaa5 100644 --- a/ProjectLighthouse/StaticFiles/css/styles.css +++ b/ProjectLighthouse/StaticFiles/css/styles.css @@ -16,12 +16,3 @@ div.statsUnderTitle > span { margin-right: 5px; } -canvas.photo-subjects { - opacity: 1; - transition: opacity 0.3s; -} - -canvas.hide-subjects { - opacity: 0; -} - From 8289e6c04e1074fd333fae5420381c0c724ff89b Mon Sep 17 00:00:00 2001 From: jvyden Date: Sun, 19 Dec 2021 22:20:25 -0500 Subject: [PATCH 25/30] Fix semicolon appearing after photos on UserPage --- ProjectLighthouse/Pages/UserPage.cshtml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ProjectLighthouse/Pages/UserPage.cshtml b/ProjectLighthouse/Pages/UserPage.cshtml index ce5440d5..2ad6916e 100644 --- a/ProjectLighthouse/Pages/UserPage.cshtml +++ b/ProjectLighthouse/Pages/UserPage.cshtml @@ -1,9 +1,9 @@ @page "/user/{userId:int}" +@using System.IO +@using System.Web @using LBPUnion.ProjectLighthouse.Types @using LBPUnion.ProjectLighthouse.Types.Profiles @using LBPUnion.ProjectLighthouse.Types.Settings -@using System.Web; -@using System.IO; @model LBPUnion.ProjectLighthouse.Pages.UserPage @{ @@ -76,7 +76,7 @@ @foreach (Photo photo in Model.Photos) {
- @await Html.PartialAsync("Partials/PhotoPartial", photo); + @await Html.PartialAsync("Partials/PhotoPartial", photo)
}
From 0d682dceeb7f66e00b64fcdcb3952f63056a85f7 Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 20 Dec 2021 01:05:23 -0500 Subject: [PATCH 26/30] Fix authentication (mostly) and file upload tests failing --- .../{DatabaseFact.cs => DatabaseFactAttribute.cs} | 13 +++++++++---- ProjectLighthouse.Tests/LighthouseTest.cs | 14 +++++++++----- ProjectLighthouse.Tests/Tests/UploadTests.cs | 6 ++++++ ProjectLighthouse/Database.cs | 1 + ProjectLighthouse/ProjectLighthouse.csproj | 4 ---- 5 files changed, 25 insertions(+), 13 deletions(-) rename ProjectLighthouse.Tests/{DatabaseFact.cs => DatabaseFactAttribute.cs} (61%) diff --git a/ProjectLighthouse.Tests/DatabaseFact.cs b/ProjectLighthouse.Tests/DatabaseFactAttribute.cs similarity index 61% rename from ProjectLighthouse.Tests/DatabaseFact.cs rename to ProjectLighthouse.Tests/DatabaseFactAttribute.cs index a62ecd5a..3089bdc8 100644 --- a/ProjectLighthouse.Tests/DatabaseFact.cs +++ b/ProjectLighthouse.Tests/DatabaseFactAttribute.cs @@ -4,9 +4,11 @@ using Xunit; namespace LBPUnion.ProjectLighthouse.Tests { - public sealed class DatabaseFact : FactAttribute + public sealed class DatabaseFactAttribute : FactAttribute { - public DatabaseFact() + private static readonly object migrateLock = new(); + + public DatabaseFactAttribute() { ServerSettings.Instance = new ServerSettings(); ServerSettings.Instance.DbConnectionString = "server=127.0.0.1;uid=root;pwd=lighthouse;database=lighthouse"; @@ -16,8 +18,11 @@ namespace LBPUnion.ProjectLighthouse.Tests } else { - using Database database = new(); - database.Database.Migrate(); + lock(migrateLock) + { + using Database database = new(); + database.Database.Migrate(); + } } } } diff --git a/ProjectLighthouse.Tests/LighthouseTest.cs b/ProjectLighthouse.Tests/LighthouseTest.cs index 22babd5c..dd96dbf0 100644 --- a/ProjectLighthouse.Tests/LighthouseTest.cs +++ b/ProjectLighthouse.Tests/LighthouseTest.cs @@ -1,7 +1,7 @@ +using System; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Net.Http; -using System.Text; using System.Threading.Tasks; using System.Xml.Serialization; using LBPUnion.ProjectLighthouse.Helpers; @@ -25,9 +25,13 @@ namespace LBPUnion.ProjectLighthouse.Tests this.Client = this.Server.CreateClient(); } - - public async Task AuthenticateResponse(int number = 0, bool createUser = true) + public async Task AuthenticateResponse(int number = -1, bool createUser = true) { + if (number == -1) + { + number = new Random().Next(); + } + const string username = "unitTestUser"; if (createUser) { @@ -65,8 +69,8 @@ namespace LBPUnion.ProjectLighthouse.Tests public async Task UploadFileEndpointRequest(string filePath) { - byte[] bytes = Encoding.UTF8.GetBytes(await File.ReadAllTextAsync(filePath)); - string hash = HashHelper.Sha1Hash(bytes); + byte[] bytes = await File.ReadAllBytesAsync(filePath); + string hash = HashHelper.Sha1Hash(bytes).ToLower(); return await this.Client.PostAsync($"/LITTLEBIGPLANETPS3_XML/upload/{hash}", new ByteArrayContent(bytes)); } diff --git a/ProjectLighthouse.Tests/Tests/UploadTests.cs b/ProjectLighthouse.Tests/Tests/UploadTests.cs index fb73be01..8c7f0bd5 100644 --- a/ProjectLighthouse.Tests/Tests/UploadTests.cs +++ b/ProjectLighthouse.Tests/Tests/UploadTests.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Net; using System.Net.Http; using System.Threading.Tasks; using Xunit; @@ -18,6 +19,7 @@ namespace LBPUnion.ProjectLighthouse.Tests public async Task ShouldNotAcceptScript() { HttpResponseMessage response = await this.UploadFileEndpointRequest("ExampleFiles/TestScript.ff"); + Assert.False(response.StatusCode == HttpStatusCode.Forbidden); Assert.False(response.IsSuccessStatusCode); } @@ -25,6 +27,7 @@ namespace LBPUnion.ProjectLighthouse.Tests public async Task ShouldNotAcceptFarc() { HttpResponseMessage response = await this.UploadFileEndpointRequest("ExampleFiles/TestFarc.farc"); + Assert.False(response.StatusCode == HttpStatusCode.Forbidden); Assert.False(response.IsSuccessStatusCode); } @@ -32,6 +35,7 @@ namespace LBPUnion.ProjectLighthouse.Tests public async Task ShouldNotAcceptGarbage() { HttpResponseMessage response = await this.UploadFileEndpointRequest("ExampleFiles/TestGarbage.bin"); + Assert.False(response.StatusCode == HttpStatusCode.Forbidden); Assert.False(response.IsSuccessStatusCode); } @@ -39,6 +43,7 @@ namespace LBPUnion.ProjectLighthouse.Tests public async Task ShouldAcceptTexture() { HttpResponseMessage response = await this.UploadFileEndpointRequest("ExampleFiles/TestTexture.tex"); + Assert.False(response.StatusCode == HttpStatusCode.Forbidden); Assert.True(response.IsSuccessStatusCode); } @@ -46,6 +51,7 @@ namespace LBPUnion.ProjectLighthouse.Tests public async Task ShouldAcceptLevel() { HttpResponseMessage response = await this.UploadFileEndpointRequest("ExampleFiles/TestLevel.lvl"); + Assert.False(response.StatusCode == HttpStatusCode.Forbidden); Assert.True(response.IsSuccessStatusCode); } } diff --git a/ProjectLighthouse/Database.cs b/ProjectLighthouse/Database.cs index 9fda3cab..e28957ba 100644 --- a/ProjectLighthouse/Database.cs +++ b/ProjectLighthouse/Database.cs @@ -73,6 +73,7 @@ namespace LBPUnion.ProjectLighthouse GameToken gameToken = new() { UserToken = HashHelper.GenerateAuthToken(), + User = user, UserId = user.UserId, UserLocation = userLocation, GameVersion = GameVersionHelper.FromTitleId(titleId), diff --git a/ProjectLighthouse/ProjectLighthouse.csproj b/ProjectLighthouse/ProjectLighthouse.csproj index 0fef627e..ffc2f979 100644 --- a/ProjectLighthouse/ProjectLighthouse.csproj +++ b/ProjectLighthouse/ProjectLighthouse.csproj @@ -33,10 +33,6 @@ - - <_ContentIncludedByDefault Remove="Pages\Admin\Index.cshtml"/> - - From 2c8190c5da213c43c816c9d6ed5c7670b8f901e1 Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 20 Dec 2021 01:49:16 -0500 Subject: [PATCH 27/30] Update README --- README.md | 52 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index e68ac31e..9ece56f8 100644 --- a/README.md +++ b/README.md @@ -5,15 +5,18 @@ This project is the main server component that LittleBigPlanet games connect to. ## WARNING! -This is beta software, and thus is not ready for public use yet. We're not responsible if someone connects and hacks -your entire machine and deletes all your files. +This is **beta software**, and thus is **not stable**. -That said, feel free to develop privately! +We're not responsible if someone hacks your machine and wipes your database. + +Make frequent backups, and be sure to report any vulnerabilities. ## Building This will be written when we're out of beta. Consider this your barrier to entry ;). +It is recommended to build with Release if you plan to use Lighthouse in a production environment. + ## Running Lighthouse requires a MySQL database at this time. For Linux users running docker, one can be set up using @@ -31,18 +34,21 @@ Once you've gotten MySQL running you can run Lighthouse. It will take care of th PS3 is difficult to set up, so I will be going over how to set up RPCS3 instead. A guide will be coming for PS3 closer to release. You can also follow this guide if you want to learn how to modify your EBOOT. -*Note: This requires a modified copy of RPCS3. You can find a working -patch [here](https://gist.github.com/jvyden/0d9619f7dd3dbc49f7583486bdacad75).* +There are also community-provided guides in [the official LBP Union Discord](https://www.lbpunion.com/discord), which +you can follow at your own discretion. -Start by getting a copy of LittleBigPlanet 2 installed. It can be digital (NPUA80662) or disc (BCUS98245). I won't get -into how because if you got this far you should already know what you're doing. For those that don't, -the [RPCS3 Quickstart Guide](https://rpcs3.net/quickstart) should cover it. +*Note: This requires a modified copy of RPCS3. You can find a working +version [on our GitHub](https://github.com/LBPUnion/rpcs3).* + +Start by getting a copy of LittleBigPlanet 2 installed. It can be digital (NPUA80662) or disc (BCUS98245). For those +that don't, the [RPCS3 Quickstart Guide](https://rpcs3.net/quickstart) should cover it. Next, download [UnionPatcher](https://github.com/LBPUnion/UnionPatcher/). Binaries can be found by reading the README.md file. You should have everything you need now, so open up RPCS3 and go to Utilities -> Decrypt PS3 Binaries. Point this -to `rpcs3/dev_hdd0/game/(title id)/USRDIR/EBOOT.BIN`. +to `rpcs3/dev_hdd0/game/(title id)/USRDIR/EBOOT.BIN`. You can grab your title id by right clicking the game in RPCS3 and +clicking Copy Info -> Copy Serial. This should give you a file named `EBOOT.elf` in the same folder. Next, fire up UnionPatcher (making sure to select the correct project to start, e.g. on Mac launch `UnionPatcher.Gui.MacOS`.) @@ -68,25 +74,35 @@ Some modifications may require updates to the database schema. You can automatic 1. Making sure the tools are installed. You can do this by running `dotnet tool restore`. 2. Making sure `LIGHTHOUSE_DB_CONNECTION_STRING` is set correctly. See the `Running` section for more details. -3. Making your changes to the database. I won't cover this since if you're making database changes you should know what - you're doing. +3. Modifying the database schema via the C# portion of the code. Do not modify the actual SQL database. 4. Running `dotnet ef migrations add --project ProjectLighthouse`. +This process will create a migration file from the changes made in the C# code. + +The new migrations will automatically be applied upon starting Lighthouse. + ### Running tests You can run tests either through your IDE or by running `dotnet tests`. Keep in mind while running database tests you need to have `LIGHTHOUSE_DB_CONNECTION_STRING` set. +### Continuous Integration (CI) Tips + +- You can skip CI runs for a commit if you specify `[skip ci]` at the beginning of the commit name. This is useful for + formatting changes, etc. +- When creating your first pull request, CI will not run initially. A team member will have to approve you for use of + running CI on a pull request. This is because of GitHub policy. + ## Compatibility across games and platforms -| Game | Console (PS3/Vita/PSP) | Emulator (RPCS3/Vita3k/PPSSPP) | Next-Gen (PS4/PS5) | -|----------|---------------------------------------|----------------------------------------------------------|------------------------| -| LBP1 | Compatible | Incompatible, crashes on entering pod computer | N/A | -| LBP2 | Compatible | Compatible with patched RPCS3 | N/A | -| LBP3 | Somewhat compatible, frequent crashes | Somewhat compatible with patched RPCS3, frequent crashes | Incompatible | -| LBP Vita | Compatible | Incompatible, marked as "bootable" on Vita3k | N/A | -| LBP PSP | Potentially compatible | Incompatible, PSN not supported on PPSSPP | Potentially Compatible | +| Game | Console (PS3/Vita/PSP) | Emulator (RPCS3/Vita3k/PPSSPP) | Next-Gen (PS4/PS5/Vita) | +|----------|---------------------------------------|----------------------------------------------------------|-------------------------| +| LBP1 | Compatible | Incompatible, crashes on entering pod computer | N/A | +| LBP2 | Compatible | Compatible with patched RPCS3 | N/A | +| LBP3 | Somewhat compatible, frequent crashes | Somewhat compatible with patched RPCS3, frequent crashes | Incompatible | +| LBP Vita | Compatible | Incompatible, marked as "bootable" on Vita3k | N/A | +| LBP PSP | Potentially compatible | Incompatible, PSN not supported on PPSSPP | Potentially Compatible | While LBP Vita and LBP PSP can be supported, they are not properly seperated from the mainline games at this time. We recommend you run seperate instances for these games to avoid problems. From d16b56976a8af5b49c3cb2d89b9acf6f263b854e Mon Sep 17 00:00:00 2001 From: jvyden Date: Wed, 22 Dec 2021 17:03:03 -0500 Subject: [PATCH 28/30] Update process test results ci step --- .github/workflows/ci.yml | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5477e569..639f02a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,18 +58,12 @@ jobs: name: lighthouse-test-results-${{matrix.os.prettyName}} path: ${{github.workspace}}/TestResults-${{matrix.os.prettyName}}.trx - - name: Process Test Results (Control) - if: ${{ matrix.os.prettyName == 'Linux' }} - uses: im-open/process-dotnet-test-results@v2.0.1 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - - name: Process Test Results - if: ${{ matrix.os.prettyName != 'Linux' }} - uses: im-open/process-dotnet-test-results@v2.0.1 + id: process-trx + uses: im-open/process-dotnet-test-results@v2.0.2 with: github-token: ${{ secrets.GITHUB_TOKEN }} - create-status-check: false + create-status-check: ${{ matrix.os.prettyName == 'Linux' && 'true' || 'false' }} create-pr-comment: false update-comment-if-one-exists: false From 02085268507f354845a8abdabf07ce81d559c653 Mon Sep 17 00:00:00 2001 From: jvyden Date: Wed, 22 Dec 2021 17:05:49 -0500 Subject: [PATCH 29/30] [skip ci] Always process test results --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 639f02a9..8e375ce2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,6 +60,7 @@ jobs: - name: Process Test Results id: process-trx + if: ${{ always() }} uses: im-open/process-dotnet-test-results@v2.0.2 with: github-token: ${{ secrets.GITHUB_TOKEN }} From d1a13c14967cc4284b62778fd27e668de8b61baf Mon Sep 17 00:00:00 2001 From: jvyden Date: Wed, 22 Dec 2021 17:21:47 -0500 Subject: [PATCH 30/30] Don't create a status check on CI (breaks prs) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8e375ce2..87ba9197 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,7 +64,7 @@ jobs: uses: im-open/process-dotnet-test-results@v2.0.2 with: github-token: ${{ secrets.GITHUB_TOKEN }} - create-status-check: ${{ matrix.os.prettyName == 'Linux' && 'true' || 'false' }} + create-status-check: false create-pr-comment: false update-comment-if-one-exists: false