diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 87ba9197..355cc520 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,9 +13,9 @@ jobs: fail-fast: false matrix: os: - - { prettyName: Windows, fullName: windows-latest, database: true } - - { prettyName: macOS, fullName: macos-latest, database: true } - - { prettyName: Linux, fullName: ubuntu-latest, database: true } + - { prettyName: Windows, fullName: windows-latest, database: true, webTest: false } + - { prettyName: macOS, fullName: macos-latest, database: true, webTest: false } + - { prettyName: Linux, fullName: ubuntu-latest, database: true, webTest: true } timeout-minutes: 10 env: DB_DATABASE: lighthouse @@ -44,11 +44,19 @@ jobs: - name: Compile run: dotnet build -c Debug - - name: Test + - name: Run tests on ProjectLighthouse.Tests continue-on-error: true - run: dotnet test --logger "trx;LogFileName=${{github.workspace}}/TestResults-${{matrix.os.prettyName}}.trx" + run: dotnet test --logger "trx;LogFileName=${{github.workspace}}/TestResults-${{matrix.os.prettyName}}-Tests.trx" ProjectLighthouse.Tests + - name: Run tests on ProjectLighthouse.Tests.GameApiTests + continue-on-error: true + run: dotnet test --logger "trx;LogFileName=${{github.workspace}}/TestResults-${{matrix.os.prettyName}}-GameApiTests.trx" ProjectLighthouse.Tests.GameApiTests + - name: Run tests on ProjectLighthouse.Tests.WebsiteTests + if: ${{ matrix.os.webTest }} + continue-on-error: true + run: dotnet test --logger "trx;LogFileName=${{github.workspace}}/TestResults-${{matrix.os.prettyName}}-WebsiteTests.trx" ProjectLighthouse.Tests.WebsiteTests + # Attempt to upload results even if test fails. # https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#always - name: Upload Test Results @@ -56,7 +64,7 @@ jobs: if: ${{ always() }} with: name: lighthouse-test-results-${{matrix.os.prettyName}} - path: ${{github.workspace}}/TestResults-${{matrix.os.prettyName}}.trx + path: ${{github.workspace}}/TestResults-${{matrix.os.prettyName}}-*.trx - name: Process Test Results id: process-trx diff --git a/.idea/.idea.ProjectLighthouse/.idea/dataSources.xml b/.idea/.idea.ProjectLighthouse/.idea/dataSources.xml index 702fe04b..c8fd1969 100644 --- a/.idea/.idea.ProjectLighthouse/.idea/dataSources.xml +++ b/.idea/.idea.ProjectLighthouse/.idea/dataSources.xml @@ -8,7 +8,7 @@ jdbc:mysql://localhost:3306/lighthouse $ProjectFileDir$ - + mariadb true org.mariadb.jdbc.Driver diff --git a/ProjectLighthouse.Tests/Tests/AuthenticationTests.cs b/ProjectLighthouse.Tests.GameApiTests/AuthenticationTests.cs similarity index 94% rename from ProjectLighthouse.Tests/Tests/AuthenticationTests.cs rename to ProjectLighthouse.Tests.GameApiTests/AuthenticationTests.cs index d7f5fd19..05f0c413 100644 --- a/ProjectLighthouse.Tests/Tests/AuthenticationTests.cs +++ b/ProjectLighthouse.Tests.GameApiTests/AuthenticationTests.cs @@ -1,13 +1,14 @@ using System.Net; using System.Net.Http; using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Tests; using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types.Settings; using Xunit; -namespace LBPUnion.ProjectLighthouse.Tests +namespace ProjectLighthouse.Tests.GameApiTests { - public class AuthenticationTests : LighthouseTest + public class AuthenticationTests : LighthouseServerTest { [Fact] public async Task ShouldReturnErrorOnNoPostData() diff --git a/ProjectLighthouse.Tests/Tests/DatabaseTests.cs b/ProjectLighthouse.Tests.GameApiTests/DatabaseTests.cs similarity index 81% rename from ProjectLighthouse.Tests/Tests/DatabaseTests.cs rename to ProjectLighthouse.Tests.GameApiTests/DatabaseTests.cs index 19416bcb..86b26ccd 100644 --- a/ProjectLighthouse.Tests/Tests/DatabaseTests.cs +++ b/ProjectLighthouse.Tests.GameApiTests/DatabaseTests.cs @@ -1,12 +1,14 @@ using System; using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse; using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.Tests; using LBPUnion.ProjectLighthouse.Types; using Xunit; -namespace LBPUnion.ProjectLighthouse.Tests +namespace ProjectLighthouse.Tests.GameApiTests { - public class DatabaseTests : LighthouseTest + public class DatabaseTests : LighthouseServerTest { [DatabaseFact] public async Task CanCreateUserTwice() @@ -21,8 +23,6 @@ namespace LBPUnion.ProjectLighthouse.Tests Assert.NotNull(userB); await database.RemoveUser(userA); // Only remove userA since userA and userB are the same user - - await database.SaveChangesAsync(); } } } \ No newline at end of file diff --git a/ProjectLighthouse.Tests/Tests/MatchTests.cs b/ProjectLighthouse.Tests.GameApiTests/MatchTests.cs similarity index 93% rename from ProjectLighthouse.Tests/Tests/MatchTests.cs rename to ProjectLighthouse.Tests.GameApiTests/MatchTests.cs index 721dc941..c7e83feb 100644 --- a/ProjectLighthouse.Tests/Tests/MatchTests.cs +++ b/ProjectLighthouse.Tests.GameApiTests/MatchTests.cs @@ -4,12 +4,13 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.Tests; using LBPUnion.ProjectLighthouse.Types; using Xunit; -namespace LBPUnion.ProjectLighthouse.Tests +namespace ProjectLighthouse.Tests.GameApiTests { - public class MatchTests : LighthouseTest + public class MatchTests : LighthouseServerTest { private static readonly SemaphoreSlim semaphore = new(1, 1); diff --git a/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj b/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj new file mode 100644 index 00000000..4a5850d3 --- /dev/null +++ b/ProjectLighthouse.Tests.GameApiTests/ProjectLighthouse.Tests.GameApiTests.csproj @@ -0,0 +1,41 @@ + + + + net6.0 + enable + + false + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + PreserveNewest + + + + + diff --git a/ProjectLighthouse.Tests/Tests/SlotTests.cs b/ProjectLighthouse.Tests.GameApiTests/SlotTests.cs similarity index 94% rename from ProjectLighthouse.Tests/Tests/SlotTests.cs rename to ProjectLighthouse.Tests.GameApiTests/SlotTests.cs index 4eef03b6..568a9ed4 100644 --- a/ProjectLighthouse.Tests/Tests/SlotTests.cs +++ b/ProjectLighthouse.Tests.GameApiTests/SlotTests.cs @@ -1,15 +1,17 @@ using System; using System.Net.Http; using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse; using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.Tests; using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Profiles; using Xunit; -namespace LBPUnion.ProjectLighthouse.Tests +namespace ProjectLighthouse.Tests.GameApiTests { - public class SlotTests : LighthouseTest + public class SlotTests : LighthouseServerTest { [DatabaseFact] public async Task ShouldOnlyShowUsersLevels() diff --git a/ProjectLighthouse.Tests/Tests/UploadTests.cs b/ProjectLighthouse.Tests.GameApiTests/UploadTests.cs similarity index 93% rename from ProjectLighthouse.Tests/Tests/UploadTests.cs rename to ProjectLighthouse.Tests.GameApiTests/UploadTests.cs index 8c7f0bd5..faac8aa9 100644 --- a/ProjectLighthouse.Tests/Tests/UploadTests.cs +++ b/ProjectLighthouse.Tests.GameApiTests/UploadTests.cs @@ -3,11 +3,12 @@ using System.IO; using System.Net; using System.Net.Http; using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Tests; using Xunit; -namespace LBPUnion.ProjectLighthouse.Tests +namespace ProjectLighthouse.Tests.GameApiTests { - public class UploadTests : LighthouseTest + public class UploadTests : LighthouseServerTest { public UploadTests() { diff --git a/ProjectLighthouse.Tests.WebsiteTests/AdminTests.cs b/ProjectLighthouse.Tests.WebsiteTests/AdminTests.cs new file mode 100644 index 00000000..83d2118b --- /dev/null +++ b/ProjectLighthouse.Tests.WebsiteTests/AdminTests.cs @@ -0,0 +1,64 @@ +using System; +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse; +using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.Tests; +using LBPUnion.ProjectLighthouse.Types; +using OpenQA.Selenium; +using Xunit; + +namespace ProjectLighthouse.Tests.WebsiteTests +{ + public class AdminTests : LighthouseWebTest + { + public const string AdminPanelButtonXPath = "/html/body/div/header/div/div/div/a[2]"; + + [DatabaseFact] + public async Task ShouldShowAdminPanelButtonWhenAdmin() + { + await using Database database = new(); + Random random = new(); + User user = await database.CreateUser($"unitTestUser{random.Next()}", HashHelper.BCryptHash("i'm an engineering failure")); + + WebToken webToken = new() + { + UserId = user.UserId, + UserToken = HashHelper.GenerateAuthToken(), + }; + + database.WebTokens.Add(webToken); + user.IsAdmin = true; + await database.SaveChangesAsync(); + + this.Driver.Navigate().GoToUrl(this.BaseAddress + "/"); + this.Driver.Manage().Cookies.AddCookie(new Cookie("LighthouseToken", webToken.UserToken)); + this.Driver.Navigate().Refresh(); + + Assert.Contains("Admin Panel", this.Driver.FindElement(By.XPath(AdminPanelButtonXPath)).Text); + } + + [DatabaseFact] + public async Task ShouldNotShowAdminPanelButtonWhenNotAdmin() + { + await using Database database = new(); + Random random = new(); + User user = await database.CreateUser($"unitTestUser{random.Next()}", HashHelper.BCryptHash("i'm an engineering failure")); + + WebToken webToken = new() + { + UserId = user.UserId, + UserToken = HashHelper.GenerateAuthToken(), + }; + + database.WebTokens.Add(webToken); + user.IsAdmin = false; + await database.SaveChangesAsync(); + + this.Driver.Navigate().GoToUrl(this.BaseAddress + "/"); + this.Driver.Manage().Cookies.AddCookie(new Cookie("LighthouseToken", webToken.UserToken)); + this.Driver.Navigate().Refresh(); + + Assert.DoesNotContain("Admin Panel", this.Driver.FindElement(By.XPath(AdminPanelButtonXPath)).Text); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse.Tests.WebsiteTests/AuthenticationTests.cs b/ProjectLighthouse.Tests.WebsiteTests/AuthenticationTests.cs new file mode 100644 index 00000000..598969d9 --- /dev/null +++ b/ProjectLighthouse.Tests.WebsiteTests/AuthenticationTests.cs @@ -0,0 +1,106 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse; +using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.Tests; +using LBPUnion.ProjectLighthouse.Types; +using Microsoft.EntityFrameworkCore; +using OpenQA.Selenium; +using Xunit; + +namespace ProjectLighthouse.Tests.WebsiteTests +{ + public class AuthenticationTests : LighthouseWebTest + { + [DatabaseFact] + public async Task ShouldLoginWithPassword() + { + await using Database database = new(); + Random random = new(); + + string password = HashHelper.Sha256Hash(HashHelper.GenerateRandomBytes(64).ToArray()); + User user = await database.CreateUser($"unitTestUser{random.Next()}", HashHelper.BCryptHash(HashHelper.Sha256Hash(password))); + + this.Driver.Navigate().GoToUrl(this.BaseAddress + "/login"); + + this.Driver.FindElement(By.Id("text")).SendKeys(user.Username); + this.Driver.FindElement(By.Id("password")).SendKeys(password); + + this.Driver.FindElement(By.Id("submit")).Click(); + + WebToken? webToken = await database.WebTokens.FirstOrDefaultAsync(t => t.UserId == user.UserId); + Assert.NotNull(webToken); + + await database.RemoveUser(user); + } + + [DatabaseFact] + public async Task ShouldNotLoginWithNoPassword() + { + await using Database database = new(); + Random random = new(); + User user = await database.CreateUser($"unitTestUser{random.Next()}", HashHelper.BCryptHash("just like the hindenberg,")); + + this.Driver.Navigate().GoToUrl(this.BaseAddress + "/login"); + + this.Driver.FindElement(By.Id("text")).SendKeys(user.Username); + + this.Driver.FindElement(By.Id("submit")).Click(); + + WebToken? webToken = await database.WebTokens.FirstOrDefaultAsync(t => t.UserId == user.UserId); + Assert.Null(webToken); + + await database.RemoveUser(user); + } + + [DatabaseFact] + public async Task ShouldNotLoginWithWrongPassword() + { + await using Database database = new(); + Random random = new(); + User user = await database.CreateUser($"unitTestUser{random.Next()}", HashHelper.BCryptHash("i'm an engineering failure")); + + this.Driver.Navigate().GoToUrl(this.BaseAddress + "/login"); + + this.Driver.FindElement(By.Id("text")).SendKeys(user.Username); + this.Driver.FindElement(By.Id("password")).SendKeys("nah man"); + + this.Driver.FindElement(By.Id("submit")).Click(); + + WebToken? webToken = await database.WebTokens.FirstOrDefaultAsync(t => t.UserId == user.UserId); + Assert.Null(webToken); + + await database.RemoveUser(user); + } + + [DatabaseFact] + public async Task ShouldLoginWithInjectedCookie() + { + const string loggedInAsUsernameTextXPath = "/html/body/div/div/div/p[1]/b"; + + await using Database database = new(); + Random random = new(); + User user = await database.CreateUser($"unitTestUser{random.Next()}", HashHelper.BCryptHash("i'm an engineering failure")); + + WebToken webToken = new() + { + UserId = user.UserId, + UserToken = HashHelper.GenerateAuthToken(), + }; + + database.WebTokens.Add(webToken); + await database.SaveChangesAsync(); + + INavigation navigation = this.Driver.Navigate(); + + navigation.GoToUrl(this.BaseAddress + "/"); + this.Driver.Manage().Cookies.AddCookie(new Cookie("LighthouseToken", webToken.UserToken)); + Assert.Throws(() => this.Driver.FindElement(By.XPath(loggedInAsUsernameTextXPath))); + navigation.Refresh(); + Assert.True(this.Driver.FindElement(By.XPath(loggedInAsUsernameTextXPath)).Text == user.Username); + + await database.RemoveUser(user); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse.Tests.WebsiteTests/LighthouseWebTest.cs b/ProjectLighthouse.Tests.WebsiteTests/LighthouseWebTest.cs new file mode 100644 index 00000000..dc96a596 --- /dev/null +++ b/ProjectLighthouse.Tests.WebsiteTests/LighthouseWebTest.cs @@ -0,0 +1,50 @@ +using System; +using System.Linq; +using LBPUnion.ProjectLighthouse; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server.Features; +using OpenQA.Selenium; +using OpenQA.Selenium.Chrome; +using Xunit; + +namespace ProjectLighthouse.Tests.WebsiteTests +{ + [Collection(nameof(LighthouseWebTest))] + public class LighthouseWebTest : IDisposable + { + public readonly IWebHost WebHost = new WebHostBuilder().UseKestrel().UseStartup().UseWebRoot("StaticFiles").Build(); + public readonly string BaseAddress; + + public readonly IWebDriver Driver; + + public LighthouseWebTest() + { + this.WebHost.Start(); + + IServerAddressesFeature? serverAddressesFeature = WebHost.ServerFeatures.Get(); + if (serverAddressesFeature == null) throw new ArgumentNullException(); + + this.BaseAddress = serverAddressesFeature.Addresses.First(); + + ChromeOptions chromeOptions = new(); + if (Convert.ToBoolean(Environment.GetEnvironmentVariable("CI") ?? "false")) + { + chromeOptions.AddArgument("headless"); + chromeOptions.AddArgument("no-sandbox"); + chromeOptions.AddArgument("disable-dev-shm-usage"); + Console.WriteLine("We are in a CI environment, so chrome headless mode has been enabled."); + } + + this.Driver = new ChromeDriver(chromeOptions); + } + + public void Dispose() + { + this.Driver.Close(); + this.Driver.Dispose(); + this.WebHost.Dispose(); + + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj b/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj new file mode 100644 index 00000000..93e88c08 --- /dev/null +++ b/ProjectLighthouse.Tests.WebsiteTests/ProjectLighthouse.Tests.WebsiteTests.csproj @@ -0,0 +1,36 @@ + + + + net6.0 + enable + + false + + + + + + + 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.WebsiteTests/RegisterTests.cs b/ProjectLighthouse.Tests.WebsiteTests/RegisterTests.cs new file mode 100644 index 00000000..2c3cf855 --- /dev/null +++ b/ProjectLighthouse.Tests.WebsiteTests/RegisterTests.cs @@ -0,0 +1,84 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse; +using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.Tests; +using LBPUnion.ProjectLighthouse.Types; +using Microsoft.EntityFrameworkCore; +using OpenQA.Selenium; +using Xunit; + +namespace ProjectLighthouse.Tests.WebsiteTests +{ + public class RegisterTests : LighthouseWebTest + { + [DatabaseFact] + public async Task ShouldRegister() + { + await using Database database = new(); + + string username = "unitTestUser" + new Random().Next(); + string password = HashHelper.Sha256Hash(HashHelper.GenerateRandomBytes(64).ToArray()); + + this.Driver.Navigate().GoToUrl(this.BaseAddress + "/register"); + + this.Driver.FindElement(By.Id("text")).SendKeys(username); + + this.Driver.FindElement(By.Id("password")).SendKeys(password); + this.Driver.FindElement(By.Id("confirmPassword")).SendKeys(password); + + this.Driver.FindElement(By.Id("submit")).Click(); + + User? user = await database.Users.FirstOrDefaultAsync(u => u.Username == username); + Assert.NotNull(user); + + await database.RemoveUser(user); + } + + [DatabaseFact] + public async Task ShouldNotRegisterWithMismatchingPasswords() + { + await using Database database = new(); + + string username = "unitTestUser" + new Random().Next(); + string password = HashHelper.Sha256Hash(HashHelper.GenerateRandomBytes(64).ToArray()); + + this.Driver.Navigate().GoToUrl(this.BaseAddress + "/register"); + + this.Driver.FindElement(By.Id("text")).SendKeys(username); + + this.Driver.FindElement(By.Id("password")).SendKeys(password); + this.Driver.FindElement(By.Id("confirmPassword")).SendKeys(password + "a"); + + this.Driver.FindElement(By.Id("submit")).Click(); + + User? user = await database.Users.FirstOrDefaultAsync(u => u.Username == username); + Assert.Null(user); + } + + [DatabaseFact] + public async Task ShouldNotRegisterWithTakenUsername() + { + await using Database database = new(); + + string username = "unitTestUser" + new Random().Next(); + string password = HashHelper.Sha256Hash(HashHelper.GenerateRandomBytes(64).ToArray()); + + await database.CreateUser(username, HashHelper.BCryptHash(password)); + User? user = await database.Users.FirstOrDefaultAsync(u => u.Username == username); + Assert.NotNull(user); + + this.Driver.Navigate().GoToUrl(this.BaseAddress + "/register"); + + this.Driver.FindElement(By.Id("text")).SendKeys(username); + + this.Driver.FindElement(By.Id("password")).SendKeys(password); + this.Driver.FindElement(By.Id("confirmPassword")).SendKeys(password); + + this.Driver.FindElement(By.Id("submit")).Click(); + + Assert.Contains("The username you've chosen is already taken.", this.Driver.PageSource); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse.Tests/Tests/FileTypeTests.cs b/ProjectLighthouse.Tests/FileTypeTests.cs similarity index 100% rename from ProjectLighthouse.Tests/Tests/FileTypeTests.cs rename to ProjectLighthouse.Tests/FileTypeTests.cs diff --git a/ProjectLighthouse.Tests/LighthouseTest.cs b/ProjectLighthouse.Tests/LighthouseServerTest.cs similarity index 98% rename from ProjectLighthouse.Tests/LighthouseTest.cs rename to ProjectLighthouse.Tests/LighthouseServerTest.cs index dd96dbf0..4e79d578 100644 --- a/ProjectLighthouse.Tests/LighthouseTest.cs +++ b/ProjectLighthouse.Tests/LighthouseServerTest.cs @@ -14,15 +14,14 @@ using Microsoft.EntityFrameworkCore; namespace LBPUnion.ProjectLighthouse.Tests { [SuppressMessage("ReSharper", "UnusedMember.Global")] - public class LighthouseTest + public class LighthouseServerTest { public readonly HttpClient Client; public readonly TestServer Server; - public LighthouseTest() + public LighthouseServerTest() { this.Server = new TestServer(new WebHostBuilder().UseStartup()); - this.Client = this.Server.CreateClient(); } public async Task AuthenticateResponse(int number = -1, bool createUser = true) diff --git a/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj b/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj index 0f656960..91fa33ee 100644 --- a/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj +++ b/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj @@ -32,9 +32,9 @@ - PreserveNewest + diff --git a/ProjectLighthouse.Tests/Tests/SerializerTests.cs b/ProjectLighthouse.Tests/SerializerTests.cs similarity index 96% rename from ProjectLighthouse.Tests/Tests/SerializerTests.cs rename to ProjectLighthouse.Tests/SerializerTests.cs index 2c8af05e..39a12ce4 100644 --- a/ProjectLighthouse.Tests/Tests/SerializerTests.cs +++ b/ProjectLighthouse.Tests/SerializerTests.cs @@ -4,7 +4,7 @@ using Xunit; namespace LBPUnion.ProjectLighthouse.Tests { - public class SerializerTests : LighthouseTest + public class SerializerTests { [Fact] public void BlankElementWorks() diff --git a/ProjectLighthouse.sln b/ProjectLighthouse.sln index 96db324b..9f80122f 100644 --- a/ProjectLighthouse.sln +++ b/ProjectLighthouse.sln @@ -4,6 +4,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectLighthouse", "Projec EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectLighthouse.Tests", "ProjectLighthouse.Tests\ProjectLighthouse.Tests.csproj", "{AFC74569-B289-4ACC-B21C-313A3A62C017}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{D360C08E-EA47-43AC-A566-FDF413442980}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectLighthouse.Tests.GameApiTests", "ProjectLighthouse.Tests.GameApiTests\ProjectLighthouse.Tests.GameApiTests.csproj", "{200EED99-FE3E-45C6-A51E-76ED9819CA2B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectLighthouse.Tests.WebsiteTests", "ProjectLighthouse.Tests.WebsiteTests\ProjectLighthouse.Tests.WebsiteTests.csproj", "{CF65EB5B-5364-4D2A-8639-F147A67F08E7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -17,6 +23,16 @@ Global {AFC74569-B289-4ACC-B21C-313A3A62C017}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AFC74569-B289-4ACC-B21C-313A3A62C017}.Debug|Any CPU.Build.0 = Debug|Any CPU {AFC74569-B289-4ACC-B21C-313A3A62C017}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AFC74569-B289-4ACC-B21C-313A3A62C017}.Release|Any CPU.Build.0 = Release|Any CPU + {200EED99-FE3E-45C6-A51E-76ED9819CA2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {200EED99-FE3E-45C6-A51E-76ED9819CA2B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {200EED99-FE3E-45C6-A51E-76ED9819CA2B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF65EB5B-5364-4D2A-8639-F147A67F08E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF65EB5B-5364-4D2A-8639-F147A67F08E7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF65EB5B-5364-4D2A-8639-F147A67F08E7}.Release|Any CPU.ActiveCfg = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {AFC74569-B289-4ACC-B21C-313A3A62C017} = {D360C08E-EA47-43AC-A566-FDF413442980} + {200EED99-FE3E-45C6-A51E-76ED9819CA2B} = {D360C08E-EA47-43AC-A566-FDF413442980} + {CF65EB5B-5364-4D2A-8639-F147A67F08E7} = {D360C08E-EA47-43AC-A566-FDF413442980} EndGlobalSection EndGlobal diff --git a/ProjectLighthouse.sln.DotSettings b/ProjectLighthouse.sln.DotSettings index 8176a998..15d27f85 100644 --- a/ProjectLighthouse.sln.DotSettings +++ b/ProjectLighthouse.sln.DotSettings @@ -90,6 +90,7 @@ True True True + True True True True diff --git a/ProjectLighthouse/Controllers/LoginController.cs b/ProjectLighthouse/Controllers/LoginController.cs index 173d49fb..a2c381af 100644 --- a/ProjectLighthouse/Controllers/LoginController.cs +++ b/ProjectLighthouse/Controllers/LoginController.cs @@ -73,7 +73,8 @@ namespace LBPUnion.ProjectLighthouse.Controllers } User? user = await this.database.UserFromGameToken(token, true); - if (user == null) + + if (user == null || user.Banned) { Logger.Log("unable to find a user from a token, rejecting login", LoggerLevelLogin.Instance); return this.StatusCode(403, ""); diff --git a/ProjectLighthouse/Controllers/MessageController.cs b/ProjectLighthouse/Controllers/MessageController.cs index c16f6921..98680ffb 100644 --- a/ProjectLighthouse/Controllers/MessageController.cs +++ b/ProjectLighthouse/Controllers/MessageController.cs @@ -28,7 +28,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers 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"); + return this.Ok($"{EulaHelper.License}\n{ServerSettings.Instance.EulaText}"); } [HttpGet("announce")] @@ -47,19 +47,24 @@ namespace LBPUnion.ProjectLighthouse.Controllers GameToken gameToken = userAndToken.Value.Item2; #endif + string announceText = ServerSettings.Instance.AnnounceText; + + announceText = announceText.Replace("%user", user.Username); + announceText = announceText.Replace("%id", user.UserId.ToString()); + return this.Ok ( - $"You are now logged in as {user.Username}.\n\n" + + announceText + #if DEBUG - "---DEBUG INFO---\n" + + "\n\n---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" + + "---DEBUG INFO---" + #endif - ServerSettings.Instance.EulaText + "\n" ); } diff --git a/ProjectLighthouse/Controllers/SlotsController.cs b/ProjectLighthouse/Controllers/SlotsController.cs index c64d2558..ba047e86 100644 --- a/ProjectLighthouse/Controllers/SlotsController.cs +++ b/ProjectLighthouse/Controllers/SlotsController.cs @@ -91,7 +91,21 @@ namespace LBPUnion.ProjectLighthouse.Controllers [HttpGet("slots/lbp2cool")] [HttpGet("slots/cool")] - public async Task CoolSlots([FromQuery] int page) => await this.LuckyDipSlots(30 * page, 30, 69); + public async Task CoolSlots + ( + [FromQuery] int pageStart, + [FromQuery] int pageSize, + [FromQuery] string gameFilterType, + [FromQuery] int players, + [FromQuery] Boolean move, + [FromQuery] int? page = null + ) + { + int _pageStart = pageStart; + if (page != null) _pageStart = (int)page * 30; + // bit of a better placeholder until we can track average user interaction with /stream endpoint + return await ThumbsSlots(_pageStart, Math.Min(pageSize, 30), gameFilterType, players, move, "thisWeek"); + } [HttpGet("slots")] public async Task NewestSlots([FromQuery] int pageStart, [FromQuery] int pageSize) @@ -198,5 +212,137 @@ namespace LBPUnion.ProjectLighthouse.Controllers ) ); } + + [HttpGet("slots/thumbs")] + public async Task ThumbsSlots + ( + [FromQuery] int pageStart, + [FromQuery] int pageSize, + [FromQuery] string gameFilterType, + [FromQuery] int players, + [FromQuery] Boolean move, + [FromQuery] string? dateFilterType = null + ) + { + Random rand = new(); + + IEnumerable slots = FilterByRequest(gameFilterType, dateFilterType) + .AsEnumerable() + .OrderByDescending(s => s.Thumbsup) + .ThenBy(_ => rand.Next()) + .Skip(pageStart - 1) + .Take(Math.Min(pageSize, 30)); + + string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize()); + + return this.Ok(LbpSerializer.TaggedStringElement("slots", response, "hint_start", pageStart + Math.Min(pageSize, 30))); + } + + [HttpGet("slots/mostUniquePlays")] + public async Task MostUniquePlaysSlots + ( + [FromQuery] int pageStart, + [FromQuery] int pageSize, + [FromQuery] string gameFilterType, + [FromQuery] int players, + [FromQuery] Boolean move, + [FromQuery] string? dateFilterType = null + ) + { + Random rand = new(); + + IEnumerable slots = FilterByRequest(gameFilterType, dateFilterType) + .AsEnumerable() + .OrderByDescending + ( + s => + { + // probably not the best way to do this? + return GetGameFilter(gameFilterType) switch + { + GameVersion.LittleBigPlanet1 => s.PlaysLBP1Unique, + GameVersion.LittleBigPlanet2 => s.PlaysLBP2Unique, + GameVersion.LittleBigPlanet3 => s.PlaysLBP3Unique, + GameVersion.LittleBigPlanetVita => s.PlaysLBPVitaUnique, + _ => s.PlaysUnique, + }; + } + ) + .ThenBy(_ => rand.Next()) + .Skip(pageStart - 1) + .Take(Math.Min(pageSize, 30)); + + string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize()); + + return this.Ok(LbpSerializer.TaggedStringElement("slots", response, "hint_start", pageStart + Math.Min(pageSize, 30))); + } + + [HttpGet("slots/mostHearted")] + public async Task MostHeartedSlots + ( + [FromQuery] int pageStart, + [FromQuery] int pageSize, + [FromQuery] string gameFilterType, + [FromQuery] int players, + [FromQuery] Boolean move, + [FromQuery] string? dateFilterType = null + ) + { + Random rand = new(); + + IEnumerable slots = FilterByRequest(gameFilterType, dateFilterType) + .AsEnumerable() + .OrderByDescending(s => s.Hearts) + .ThenBy(_ => rand.Next()) + .Skip(pageStart - 1) + .Take(Math.Min(pageSize, 30)); + + string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize()); + + return this.Ok(LbpSerializer.TaggedStringElement("slots", response, "hint_start", pageStart + Math.Min(pageSize, 30))); + } + + public GameVersion GetGameFilter(string gameFilterType) + { + return gameFilterType switch + { + "lbp1" => GameVersion.LittleBigPlanet1, + "lbp2" => GameVersion.LittleBigPlanet2, + "lbp3" => GameVersion.LittleBigPlanet3, + "both" => GameVersion.LittleBigPlanet2, // LBP2 default option + _ => GameVersion.Unknown, + }; + } + + public IQueryable FilterByRequest(string gameFilterType, string? dateFilterType) + { + string _dateFilterType = dateFilterType ?? ""; + + long oldestTime = _dateFilterType switch + { + "thisWeek" => DateTimeOffset.Now.AddDays(-7).ToUnixTimeMilliseconds(), + "thisMonth" => DateTimeOffset.Now.AddDays(-31).ToUnixTimeMilliseconds(), + _ => 0, + }; + + GameVersion gameVersion = GetGameFilter(gameFilterType); + + IQueryable whereSlots; + + // ReSharper disable once ConvertIfStatementToConditionalTernaryExpression + if (gameFilterType == "both") + { + // Get game versions less than the current version + // Needs support for LBP3 ("both" = LBP1+2) + whereSlots = this.database.Slots.Where(s => s.GameVersion <= gameVersion && s.FirstUploaded >= oldestTime); + } + else + { + // Get game versions exactly equal to gamefiltertype + whereSlots = this.database.Slots.Where(s => s.GameVersion == gameVersion && s.FirstUploaded >= oldestTime); + } + + return whereSlots.Include(s => s.Creator).Include(s => s.Location); + } } } \ No newline at end of file diff --git a/ProjectLighthouse/Controllers/Website/Admin/AdminSlotController.cs b/ProjectLighthouse/Controllers/Website/Admin/AdminSlotController.cs index 70c973ef..4787f1a7 100644 --- a/ProjectLighthouse/Controllers/Website/Admin/AdminSlotController.cs +++ b/ProjectLighthouse/Controllers/Website/Admin/AdminSlotController.cs @@ -19,7 +19,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers.Website.Admin this.database = database; } - [Route("teamPick")] + [HttpGet("teamPick")] public async Task TeamPick([FromRoute] int id) { User? user = this.database.UserFromWebRequest(this.Request); @@ -35,7 +35,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers.Website.Admin return this.Ok(); } - [Route("removeTeamPick")] + [HttpGet("removeTeamPick")] public async Task RemoveTeamPick([FromRoute] int id) { User? user = this.database.UserFromWebRequest(this.Request); @@ -51,7 +51,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers.Website.Admin return this.Ok(); } - [Route("delete")] + [HttpGet("delete")] public async Task DeleteLevel([FromRoute] int id) { User? user = this.database.UserFromWebRequest(this.Request); diff --git a/ProjectLighthouse/Controllers/Website/Admin/AdminUserController.cs b/ProjectLighthouse/Controllers/Website/Admin/AdminUserController.cs new file mode 100644 index 00000000..e92266fe --- /dev/null +++ b/ProjectLighthouse/Controllers/Website/Admin/AdminUserController.cs @@ -0,0 +1,37 @@ +#nullable enable +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Types; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace LBPUnion.ProjectLighthouse.Controllers.Website.Admin +{ + [ApiController] + [Route("admin/user/{id:int}")] + public class AdminUserController : ControllerBase + { + private readonly Database database; + + public AdminUserController(Database database) + { + this.database = database; + } + + [HttpGet("unban")] + public async Task UnbanUser([FromRoute] int id) + { + User? user = this.database.UserFromWebRequest(this.Request); + if (user == null || !user.IsAdmin) return this.NotFound(); + + User? targetedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id); + ; + if (targetedUser == null) return this.NotFound(); + + targetedUser.Banned = false; + targetedUser.BannedReason = null; + + await this.database.SaveChangesAsync(); + return this.Redirect($"/user/{targetedUser.UserId}"); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Maintenance/Commands/DeleteUserCommand.cs b/ProjectLighthouse/Maintenance/Commands/DeleteUserCommand.cs index d7c7802c..740c5c99 100644 --- a/ProjectLighthouse/Maintenance/Commands/DeleteUserCommand.cs +++ b/ProjectLighthouse/Maintenance/Commands/DeleteUserCommand.cs @@ -11,11 +11,11 @@ namespace LBPUnion.ProjectLighthouse.Maintenance.Commands public class DeleteUserCommand : ICommand { private readonly Database database = new(); - public string Name() => "Delete/Ban User"; + public string Name() => "Delete User"; public string[] Aliases() => new[] { - "deleteUser", "wipeUser", "banUser", + "deleteUser", "wipeUser", }; public string Arguments() => ""; public int RequiredArgs() => 1; diff --git a/ProjectLighthouse/Migrations/20211217000749_AddBannedPropertiesToUser.cs b/ProjectLighthouse/Migrations/20211217000749_AddBannedPropertiesToUser.cs new file mode 100644 index 00000000..6f2f5a64 --- /dev/null +++ b/ProjectLighthouse/Migrations/20211217000749_AddBannedPropertiesToUser.cs @@ -0,0 +1,41 @@ +using LBPUnion.ProjectLighthouse; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ProjectLighthouse.Migrations +{ + [DbContext(typeof(Database))] + [Migration("20211217000749_AddBannedPropertiesToUser")] + public partial class AddBannedPropertiesToUser : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Banned", + table: "Users", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "BannedReason", + table: "Users", + type: "longtext", + nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Banned", + table: "Users"); + + migrationBuilder.DropColumn( + name: "BannedReason", + table: "Users"); + } + } +} diff --git a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs index cdb0c96a..b29e1cc0 100644 --- a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs +++ b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs @@ -546,6 +546,12 @@ namespace ProjectLighthouse.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + b.Property("Banned") + .HasColumnType("tinyint(1)"); + + b.Property("BannedReason") + .HasColumnType("longtext"); + b.Property("Biography") .HasColumnType("longtext"); diff --git a/ProjectLighthouse/Pages/Admin/AdminBanUserPage.cshtml b/ProjectLighthouse/Pages/Admin/AdminBanUserPage.cshtml new file mode 100644 index 00000000..2fd9e27a --- /dev/null +++ b/ProjectLighthouse/Pages/Admin/AdminBanUserPage.cshtml @@ -0,0 +1,20 @@ +@page "/admin/user/{id:int}/ban" +@model LBPUnion.ProjectLighthouse.Pages.Admin.AdminBanUserPage + +@{ + Layout = "Layouts/BaseLayout"; + Model.Title = "Ban " + Model.TargetedUser!.Username + "?"; +} + +

Are you sure you want to ban this user?

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


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

@Model.Error

+

@Model.Error

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

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

+

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

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

@Model.Title

@Model.ProfileUser!.Status

+
@Model.ProfileUser.Hearts @Model.ProfileUser.Comments @@ -53,6 +75,13 @@ Reset Password } + @if (Model.User != null && Model.User.IsAdmin && !Model.ProfileUser.Banned) + { + + + Ban User + + }
diff --git a/ProjectLighthouse/Startup.cs b/ProjectLighthouse/Startup.cs index fe68aea5..cecb0370 100644 --- a/ProjectLighthouse/Startup.cs +++ b/ProjectLighthouse/Startup.cs @@ -87,11 +87,34 @@ namespace LBPUnion.ProjectLighthouse Stopwatch requestStopwatch = new(); requestStopwatch.Start(); + context.Request.EnableBuffering(); // Allows us to reset the position of Request.Body for later logging + // Log all headers. // foreach (KeyValuePair header in context.Request.Headers) Logger.Log($"{header.Key}: {header.Value}"); - context.Request.EnableBuffering(); // Allows us to reset the position of Request.Body for later logging + await next(context); // Handle the request so we can get the status code from it + requestStopwatch.Stop(); + + Logger.Log + ( + $"{context.Response.StatusCode}, {requestStopwatch.ElapsedMilliseconds}ms: {context.Request.Method} {context.Request.Path}{context.Request.QueryString}", + LoggerLevelHttp.Instance + ); + + if (context.Request.Method == "POST") + { + context.Request.Body.Position = 0; + Logger.Log(await new StreamReader(context.Request.Body).ReadToEndAsync(), LoggerLevelHttp.Instance); + } + } + ); + + // Digest check + app.Use + ( + async (context, next) => + { // Client digest check. if (!context.Request.Cookies.TryGetValue("MM_AUTH", out string authCookie)) authCookie = string.Empty; string digestPath = context.Request.Path; @@ -119,7 +142,7 @@ namespace LBPUnion.ProjectLighthouse Stream oldResponseStream = context.Response.Body; context.Response.Body = responseBuffer; - await next(); // Handle the request so we can get the status code from it + await next(context); // Handle the request so we can get the server digest hash // Compute the server digest hash. if (computeDigests) @@ -138,7 +161,13 @@ namespace LBPUnion.ProjectLighthouse responseBuffer.Position = 0; await responseBuffer.CopyToAsync(oldResponseStream); context.Response.Body = oldResponseStream; + } + ); + app.Use + ( + async (context, next) => + { #nullable enable // Log LastContact for LBP1. This is done on LBP2/3/V on a Match request. if (context.Request.Path.ToString().StartsWith("/LITTLEBIGPLANETPS3_XML")) @@ -153,19 +182,7 @@ namespace LBPUnion.ProjectLighthouse } #nullable disable - requestStopwatch.Stop(); - - Logger.Log - ( - $"{context.Response.StatusCode}, {requestStopwatch.ElapsedMilliseconds}ms: {context.Request.Method} {context.Request.Path}{context.Request.QueryString}", - LoggerLevelHttp.Instance - ); - - if (context.Request.Method == "POST") - { - context.Request.Body.Position = 0; - Logger.Log(await new StreamReader(context.Request.Body).ReadToEndAsync(), LoggerLevelHttp.Instance); - } + await next(context); } ); diff --git a/ProjectLighthouse/Types/Settings/ServerSettings.cs b/ProjectLighthouse/Types/Settings/ServerSettings.cs index bdbb7676..67a3aeec 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 = 13; // MUST BE INCREMENTED FOR EVERY CONFIG CHANGE! + public const int CurrentConfigVersion = 14; // 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 @@ -74,6 +74,12 @@ namespace LBPUnion.ProjectLighthouse.Types.Settings public string EulaText { get; set; } = ""; + #if !DEBUG + public string AnnounceText { get; set; } = "You are now logged in as %user."; + #else + public string AnnounceText { get; set; } = "You are now logged in as %user (id: %id)."; + #endif + public string DbConnectionString { get; set; } = "server=127.0.0.1;uid=root;pwd=lighthouse;database=lighthouse"; public string ExternalUrl { get; set; } = "http://localhost:10060"; diff --git a/ProjectLighthouse/Types/User.cs b/ProjectLighthouse/Types/User.cs index 726c706e..b941d656 100644 --- a/ProjectLighthouse/Types/User.cs +++ b/ProjectLighthouse/Types/User.cs @@ -125,6 +125,10 @@ namespace LBPUnion.ProjectLighthouse.Types } #nullable disable + public bool Banned { get; set; } + + public string BannedReason { get; set; } + public string Serialize(GameVersion gameVersion = GameVersion.LittleBigPlanet1) { string user = LbpSerializer.TaggedStringElement("npHandle", this.Username, "icon", this.IconHash) + diff --git a/README.md b/README.md index 9ece56f8..d87d9bf7 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # Project Lighthouse -Project Lighthouse is an umbrella project for all work to investigate and develop private servers for LittleBigPlanet. -This project is the main server component that LittleBigPlanet games connect to. +Project Lighthouse is a clean-room, open-source custom server for LittleBigPlanet. This is a project conducted by the [LBP Union Ministry of Technology Research and Development team.](https://www.lbpunion.com/technology) For concerns and inquiries about the project, please [contact us here.](https://www.lbpunion.com/contact) For general questions and discussion about Project Lighthouse, please see the [megathread](https://www.lbpunion.com/forum/union-hall/project-lighthouse-littlebigplanet-private-servers-megathread) on our forum. ## WARNING!