Merge branch 'main' into lbp3-collections

This commit is contained in:
jvyden 2022-01-11 02:59:18 -05:00
commit 757a96d435
No known key found for this signature in database
GPG key ID: 18BCF2BE0262B278
39 changed files with 840 additions and 59 deletions

View file

@ -13,9 +13,9 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
os: os:
- { prettyName: Windows, fullName: windows-latest, database: true } - { prettyName: Windows, fullName: windows-latest, database: true, webTest: false }
- { prettyName: macOS, fullName: macos-latest, database: true } - { prettyName: macOS, fullName: macos-latest, database: true, webTest: false }
- { prettyName: Linux, fullName: ubuntu-latest, database: true } - { prettyName: Linux, fullName: ubuntu-latest, database: true, webTest: true }
timeout-minutes: 10 timeout-minutes: 10
env: env:
DB_DATABASE: lighthouse DB_DATABASE: lighthouse
@ -44,11 +44,19 @@ jobs:
- name: Compile - name: Compile
run: dotnet build -c Debug run: dotnet build -c Debug
- name: Test - name: Run tests on ProjectLighthouse.Tests
continue-on-error: true 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. # Attempt to upload results even if test fails.
# https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#always # https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#always
- name: Upload Test Results - name: Upload Test Results
@ -56,7 +64,7 @@ jobs:
if: ${{ always() }} if: ${{ always() }}
with: with:
name: lighthouse-test-results-${{matrix.os.prettyName}} 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 - name: Process Test Results
id: process-trx id: process-trx

View file

@ -8,7 +8,7 @@
<jdbc-url>jdbc:mysql://localhost:3306/lighthouse</jdbc-url> <jdbc-url>jdbc:mysql://localhost:3306/lighthouse</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir> <working-dir>$ProjectFileDir$</working-dir>
</data-source> </data-source>
<data-source source="LOCAL" name="Lighthouse Production" uuid="b323608d-d984-40d0-942e-2c2ea35006d8"> <data-source source="LOCAL" name="Lighthouse Production" read-only="true" uuid="b323608d-d984-40d0-942e-2c2ea35006d8">
<driver-ref>mariadb</driver-ref> <driver-ref>mariadb</driver-ref>
<synchronize>true</synchronize> <synchronize>true</synchronize>
<jdbc-driver>org.mariadb.jdbc.Driver</jdbc-driver> <jdbc-driver>org.mariadb.jdbc.Driver</jdbc-driver>

View file

@ -1,13 +1,14 @@
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Tests;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Settings; using LBPUnion.ProjectLighthouse.Types.Settings;
using Xunit; using Xunit;
namespace LBPUnion.ProjectLighthouse.Tests namespace ProjectLighthouse.Tests.GameApiTests
{ {
public class AuthenticationTests : LighthouseTest public class AuthenticationTests : LighthouseServerTest
{ {
[Fact] [Fact]
public async Task ShouldReturnErrorOnNoPostData() public async Task ShouldReturnErrorOnNoPostData()

View file

@ -1,12 +1,14 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Tests;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using Xunit; using Xunit;
namespace LBPUnion.ProjectLighthouse.Tests namespace ProjectLighthouse.Tests.GameApiTests
{ {
public class DatabaseTests : LighthouseTest public class DatabaseTests : LighthouseServerTest
{ {
[DatabaseFact] [DatabaseFact]
public async Task CanCreateUserTwice() public async Task CanCreateUserTwice()
@ -21,8 +23,6 @@ namespace LBPUnion.ProjectLighthouse.Tests
Assert.NotNull(userB); Assert.NotNull(userB);
await database.RemoveUser(userA); // Only remove userA since userA and userB are the same user await database.RemoveUser(userA); // Only remove userA since userA and userB are the same user
await database.SaveChangesAsync();
} }
} }
} }

View file

@ -4,12 +4,13 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Tests;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using Xunit; 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); private static readonly SemaphoreSlim semaphore = new(1, 1);

View file

@ -0,0 +1,41 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0"/>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.0"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0"/>
<PackageReference Include="xunit" Version="2.4.1"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ProjectLighthouse.Tests\ProjectLighthouse.Tests.csproj"/>
<ProjectReference Include="..\ProjectLighthouse\ProjectLighthouse.csproj"/>
</ItemGroup>
<ItemGroup>
<Content Include="..\ProjectLighthouse.Tests\ExampleFiles\**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<ProjectReference Include="..\ProjectLighthouse\ProjectLighthouse.csproj"/>
</ItemGroup>
</Project>

View file

@ -1,15 +1,17 @@
using System; using System;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Tests;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Types.Profiles; using LBPUnion.ProjectLighthouse.Types.Profiles;
using Xunit; using Xunit;
namespace LBPUnion.ProjectLighthouse.Tests namespace ProjectLighthouse.Tests.GameApiTests
{ {
public class SlotTests : LighthouseTest public class SlotTests : LighthouseServerTest
{ {
[DatabaseFact] [DatabaseFact]
public async Task ShouldOnlyShowUsersLevels() public async Task ShouldOnlyShowUsersLevels()

View file

@ -3,11 +3,12 @@ using System.IO;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Tests;
using Xunit; using Xunit;
namespace LBPUnion.ProjectLighthouse.Tests namespace ProjectLighthouse.Tests.GameApiTests
{ {
public class UploadTests : LighthouseTest public class UploadTests : LighthouseServerTest
{ {
public UploadTests() public UploadTests()
{ {

View file

@ -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);
}
}
}

View file

@ -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<NoSuchElementException>(() => this.Driver.FindElement(By.XPath(loggedInAsUsernameTextXPath)));
navigation.Refresh();
Assert.True(this.Driver.FindElement(By.XPath(loggedInAsUsernameTextXPath)).Text == user.Username);
await database.RemoveUser(user);
}
}
}

View file

@ -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<TestStartup>().UseWebRoot("StaticFiles").Build();
public readonly string BaseAddress;
public readonly IWebDriver Driver;
public LighthouseWebTest()
{
this.WebHost.Start();
IServerAddressesFeature? serverAddressesFeature = WebHost.ServerFeatures.Get<IServerAddressesFeature>();
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);
}
}
}

View file

@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0"/>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.0"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0"/>
<PackageReference Include="Selenium.WebDriver" Version="4.1.0"/>
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="96.0.4664.4500"/>
<PackageReference Include="xunit" Version="2.4.1"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ProjectLighthouse.Tests\ProjectLighthouse.Tests.csproj"/>
<ProjectReference Include="..\ProjectLighthouse\ProjectLighthouse.csproj"/>
</ItemGroup>
</Project>

View file

@ -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);
}
}
}

View file

@ -14,15 +14,14 @@ using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Tests namespace LBPUnion.ProjectLighthouse.Tests
{ {
[SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("ReSharper", "UnusedMember.Global")]
public class LighthouseTest public class LighthouseServerTest
{ {
public readonly HttpClient Client; public readonly HttpClient Client;
public readonly TestServer Server; public readonly TestServer Server;
public LighthouseTest() public LighthouseServerTest()
{ {
this.Server = new TestServer(new WebHostBuilder().UseStartup<TestStartup>()); this.Server = new TestServer(new WebHostBuilder().UseStartup<TestStartup>());
this.Client = this.Server.CreateClient(); this.Client = this.Server.CreateClient();
} }
public async Task<HttpResponseMessage> AuthenticateResponse(int number = -1, bool createUser = true) public async Task<HttpResponseMessage> AuthenticateResponse(int number = -1, bool createUser = true)

View file

@ -32,9 +32,9 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\ProjectLighthouse\ProjectLighthouse.csproj"/>
<Content Include="ExampleFiles\**"> <Content Include="ExampleFiles\**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<ProjectReference Include="..\ProjectLighthouse\ProjectLighthouse.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -4,7 +4,7 @@ using Xunit;
namespace LBPUnion.ProjectLighthouse.Tests namespace LBPUnion.ProjectLighthouse.Tests
{ {
public class SerializerTests : LighthouseTest public class SerializerTests
{ {
[Fact] [Fact]
public void BlankElementWorks() public void BlankElementWorks()

View file

@ -4,6 +4,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectLighthouse", "Projec
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectLighthouse.Tests", "ProjectLighthouse.Tests\ProjectLighthouse.Tests.csproj", "{AFC74569-B289-4ACC-B21C-313A3A62C017}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectLighthouse.Tests", "ProjectLighthouse.Tests\ProjectLighthouse.Tests.csproj", "{AFC74569-B289-4ACC-B21C-313A3A62C017}"
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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.ActiveCfg = Debug|Any CPU
{AFC74569-B289-4ACC-B21C-313A3A62C017}.Debug|Any CPU.Build.0 = 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.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 EndGlobalSection
EndGlobal EndGlobal

View file

@ -90,6 +90,7 @@
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Affero/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Affero/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=airfryer/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=airfryer/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=asdf/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=BCAS/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=BCAS/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=BCES/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=BCES/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=BCET/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=BCET/@EntryIndexedValue">True</s:Boolean>

View file

@ -73,7 +73,8 @@ namespace LBPUnion.ProjectLighthouse.Controllers
} }
User? user = await this.database.UserFromGameToken(token, true); 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); Logger.Log("unable to find a user from a token, rejecting login", LoggerLevelLogin.Instance);
return this.StatusCode(403, ""); return this.StatusCode(403, "");

View file

@ -28,7 +28,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers
User? user = await this.database.UserFromGameRequest(this.Request); User? user = await this.database.UserFromGameRequest(this.Request);
if (user == null) return this.StatusCode(403, ""); 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")] [HttpGet("announce")]
@ -47,19 +47,24 @@ namespace LBPUnion.ProjectLighthouse.Controllers
GameToken gameToken = userAndToken.Value.Item2; GameToken gameToken = userAndToken.Value.Item2;
#endif #endif
string announceText = ServerSettings.Instance.AnnounceText;
announceText = announceText.Replace("%user", user.Username);
announceText = announceText.Replace("%id", user.UserId.ToString());
return this.Ok return this.Ok
( (
$"You are now logged in as {user.Username}.\n\n" + announceText +
#if DEBUG #if DEBUG
"---DEBUG INFO---\n" + "\n\n---DEBUG INFO---\n" +
$"user.UserId: {user.UserId}\n" + $"user.UserId: {user.UserId}\n" +
$"token.Approved: {gameToken.Approved}\n" + $"token.Approved: {gameToken.Approved}\n" +
$"token.Used: {gameToken.Used}\n" + $"token.Used: {gameToken.Used}\n" +
$"token.UserLocation: {gameToken.UserLocation}\n" + $"token.UserLocation: {gameToken.UserLocation}\n" +
$"token.GameVersion: {gameToken.GameVersion}\n" + $"token.GameVersion: {gameToken.GameVersion}\n" +
"---DEBUG INFO---\n\n" + "---DEBUG INFO---" +
#endif #endif
ServerSettings.Instance.EulaText "\n"
); );
} }

View file

@ -91,7 +91,21 @@ namespace LBPUnion.ProjectLighthouse.Controllers
[HttpGet("slots/lbp2cool")] [HttpGet("slots/lbp2cool")]
[HttpGet("slots/cool")] [HttpGet("slots/cool")]
public async Task<IActionResult> CoolSlots([FromQuery] int page) => await this.LuckyDipSlots(30 * page, 30, 69); public async Task<IActionResult> 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")] [HttpGet("slots")]
public async Task<IActionResult> NewestSlots([FromQuery] int pageStart, [FromQuery] int pageSize) public async Task<IActionResult> NewestSlots([FromQuery] int pageStart, [FromQuery] int pageSize)
@ -198,5 +212,137 @@ namespace LBPUnion.ProjectLighthouse.Controllers
) )
); );
} }
[HttpGet("slots/thumbs")]
public async Task<IActionResult> ThumbsSlots
(
[FromQuery] int pageStart,
[FromQuery] int pageSize,
[FromQuery] string gameFilterType,
[FromQuery] int players,
[FromQuery] Boolean move,
[FromQuery] string? dateFilterType = null
)
{
Random rand = new();
IEnumerable<Slot> 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<IActionResult> MostUniquePlaysSlots
(
[FromQuery] int pageStart,
[FromQuery] int pageSize,
[FromQuery] string gameFilterType,
[FromQuery] int players,
[FromQuery] Boolean move,
[FromQuery] string? dateFilterType = null
)
{
Random rand = new();
IEnumerable<Slot> 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<IActionResult> MostHeartedSlots
(
[FromQuery] int pageStart,
[FromQuery] int pageSize,
[FromQuery] string gameFilterType,
[FromQuery] int players,
[FromQuery] Boolean move,
[FromQuery] string? dateFilterType = null
)
{
Random rand = new();
IEnumerable<Slot> 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<Slot> 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<Slot> 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);
}
} }
} }

View file

@ -19,7 +19,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers.Website.Admin
this.database = database; this.database = database;
} }
[Route("teamPick")] [HttpGet("teamPick")]
public async Task<IActionResult> TeamPick([FromRoute] int id) public async Task<IActionResult> TeamPick([FromRoute] int id)
{ {
User? user = this.database.UserFromWebRequest(this.Request); User? user = this.database.UserFromWebRequest(this.Request);
@ -35,7 +35,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers.Website.Admin
return this.Ok(); return this.Ok();
} }
[Route("removeTeamPick")] [HttpGet("removeTeamPick")]
public async Task<IActionResult> RemoveTeamPick([FromRoute] int id) public async Task<IActionResult> RemoveTeamPick([FromRoute] int id)
{ {
User? user = this.database.UserFromWebRequest(this.Request); User? user = this.database.UserFromWebRequest(this.Request);
@ -51,7 +51,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers.Website.Admin
return this.Ok(); return this.Ok();
} }
[Route("delete")] [HttpGet("delete")]
public async Task<IActionResult> DeleteLevel([FromRoute] int id) public async Task<IActionResult> DeleteLevel([FromRoute] int id)
{ {
User? user = this.database.UserFromWebRequest(this.Request); User? user = this.database.UserFromWebRequest(this.Request);

View file

@ -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<IActionResult> 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}");
}
}
}

View file

@ -11,11 +11,11 @@ namespace LBPUnion.ProjectLighthouse.Maintenance.Commands
public class DeleteUserCommand : ICommand public class DeleteUserCommand : ICommand
{ {
private readonly Database database = new(); private readonly Database database = new();
public string Name() => "Delete/Ban User"; public string Name() => "Delete User";
public string[] Aliases() public string[] Aliases()
=> new[] => new[]
{ {
"deleteUser", "wipeUser", "banUser", "deleteUser", "wipeUser",
}; };
public string Arguments() => "<username/userId>"; public string Arguments() => "<username/userId>";
public int RequiredArgs() => 1; public int RequiredArgs() => 1;

View file

@ -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<bool>(
name: "Banned",
table: "Users",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<string>(
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");
}
}
}

View file

@ -546,6 +546,12 @@ namespace ProjectLighthouse.Migrations
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("int"); .HasColumnType("int");
b.Property<bool>("Banned")
.HasColumnType("tinyint(1)");
b.Property<string>("BannedReason")
.HasColumnType("longtext");
b.Property<string>("Biography") b.Property<string>("Biography")
.HasColumnType("longtext"); .HasColumnType("longtext");

View file

@ -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 + "?";
}
<p>Are you sure you want to ban this user?</p>
<form method="post">
@Html.AntiForgeryToken()
<div class="ui left labeled input">
<label for="text" class="ui blue label">Reason: </label>
<input type="text" name="reason" id="text">
</div><br><br>
<input type="submit" value="Yes, ban @Model.TargetedUser.Username!" id="submit" class="ui red button"><br>
</form>

View file

@ -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<IActionResult> 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<IActionResult> 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}");
}
}

View file

@ -1,7 +1,7 @@
@page "/admin" @page "/admin"
@using LBPUnion.ProjectLighthouse.Helpers @using LBPUnion.ProjectLighthouse.Helpers
@using LBPUnion.ProjectLighthouse.Maintenance @using LBPUnion.ProjectLighthouse.Maintenance
@model LBPUnion.ProjectLighthouse.Pages.AdminPanelPage @model LBPUnion.ProjectLighthouse.Pages.Admin.AdminPanelPage
@{ @{
Layout = "Layouts/BaseLayout"; Layout = "Layouts/BaseLayout";

View file

@ -7,7 +7,7 @@ using LBPUnion.ProjectLighthouse.Pages.Layouts;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace LBPUnion.ProjectLighthouse.Pages namespace LBPUnion.ProjectLighthouse.Pages.Admin
{ {
public class AdminPanelPage : BaseLayout public class AdminPanelPage : BaseLayout
{ {

View file

@ -24,7 +24,7 @@
<div class="header"> <div class="header">
Uh oh! Uh oh!
</div> </div>
<p>@Model.Error</p> <p style="white-space: pre-line">@Model.Error</p>
</div> </div>
} }

View file

@ -1,7 +1,9 @@
#nullable enable #nullable enable
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using Kettu;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Pages.Layouts; using LBPUnion.ProjectLighthouse.Pages.Layouts;
using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -36,16 +38,25 @@ namespace LBPUnion.ProjectLighthouse.Pages
User? user = await this.Database.Users.FirstOrDefaultAsync(u => u.Username == username); User? user = await this.Database.Users.FirstOrDefaultAsync(u => u.Username == username);
if (user == null) 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."; this.Error = "The username or password you entered is invalid.";
return this.Page(); return this.Page();
} }
if (!BCrypt.Net.BCrypt.Verify(password, user.Password)) 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."; this.Error = "The username or password you entered is invalid.";
return this.Page(); 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() WebToken webToken = new()
{ {
UserId = user.UserId, UserId = user.UserId,

View file

@ -6,7 +6,7 @@
Model.Title = "Password Reset Required"; Model.Title = "Password Reset Required";
} }
<p>An admin has deemed it necessary that you reset your password. Please do so.</p> <p>An administrator has deemed it necessary that you reset your password. Please do so.</p>
<a href="/passwordReset"> <a href="/passwordReset">
<div class="ui blue button">Reset Password</div> <div class="ui blue button">Reset Password</div>

View file

@ -14,12 +14,34 @@
Model.Description = Model.ProfileUser!.Biography; Model.Description = Model.ProfileUser!.Biography;
} }
@if (Model.ProfileUser.Banned)
{
<div class="ui inverted red segment">
<p>
<b>User is currently banned!</b>
</p>
@if (Model.User != null && Model.User.IsAdmin)
{
<p>Reason: @Model.ProfileUser.BannedReason</p>
<a class="ui inverted button" href="/admin/user/@Model.ProfileUser.UserId/unban">
<i class="ban icon"></i>
<span>Unban User</span>
</a>
}
else
{
<p>For shame...</p>
}
</div>
}
<div class="ui grid"> <div class="ui grid">
<div class="eight wide column"> <div class="eight wide column">
<h1>@Model.Title</h1> <h1>@Model.Title</h1>
<p> <p>
<i>@Model.ProfileUser!.Status</i> <i>@Model.ProfileUser!.Status</i>
</p> </p>
<div class="statsUnderTitle"> <div class="statsUnderTitle">
<i class="pink heart icon" title="Hearts"></i> <span>@Model.ProfileUser.Hearts</span> <i class="pink heart icon" title="Hearts"></i> <span>@Model.ProfileUser.Hearts</span>
<i class="blue comment icon" title="Comments"></i> <span>@Model.ProfileUser.Comments</span> <i class="blue comment icon" title="Comments"></i> <span>@Model.ProfileUser.Comments</span>
@ -53,6 +75,13 @@
<span>Reset Password</span> <span>Reset Password</span>
</a> </a>
} }
@if (Model.User != null && Model.User.IsAdmin && !Model.ProfileUser.Banned)
{
<a class="ui red button" href="/admin/user/@Model.ProfileUser.UserId/ban">
<i class="ban icon"></i>
<span>Ban User</span>
</a>
}
</div> </div>
<div class="eight wide column"> <div class="eight wide column">
<div class="ui blue segment"> <div class="ui blue segment">

View file

@ -87,11 +87,34 @@ namespace LBPUnion.ProjectLighthouse
Stopwatch requestStopwatch = new(); Stopwatch requestStopwatch = new();
requestStopwatch.Start(); requestStopwatch.Start();
context.Request.EnableBuffering(); // Allows us to reset the position of Request.Body for later logging
// Log all headers. // Log all headers.
// foreach (KeyValuePair<string, StringValues> header in context.Request.Headers) Logger.Log($"{header.Key}: {header.Value}"); // foreach (KeyValuePair<string, StringValues> 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. // Client digest check.
if (!context.Request.Cookies.TryGetValue("MM_AUTH", out string authCookie)) authCookie = string.Empty; if (!context.Request.Cookies.TryGetValue("MM_AUTH", out string authCookie)) authCookie = string.Empty;
string digestPath = context.Request.Path; string digestPath = context.Request.Path;
@ -119,7 +142,7 @@ namespace LBPUnion.ProjectLighthouse
Stream oldResponseStream = context.Response.Body; Stream oldResponseStream = context.Response.Body;
context.Response.Body = responseBuffer; 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. // Compute the server digest hash.
if (computeDigests) if (computeDigests)
@ -138,7 +161,13 @@ namespace LBPUnion.ProjectLighthouse
responseBuffer.Position = 0; responseBuffer.Position = 0;
await responseBuffer.CopyToAsync(oldResponseStream); await responseBuffer.CopyToAsync(oldResponseStream);
context.Response.Body = oldResponseStream; context.Response.Body = oldResponseStream;
}
);
app.Use
(
async (context, next) =>
{
#nullable enable #nullable enable
// Log LastContact for LBP1. This is done on LBP2/3/V on a Match request. // Log LastContact for LBP1. This is done on LBP2/3/V on a Match request.
if (context.Request.Path.ToString().StartsWith("/LITTLEBIGPLANETPS3_XML")) if (context.Request.Path.ToString().StartsWith("/LITTLEBIGPLANETPS3_XML"))
@ -153,19 +182,7 @@ namespace LBPUnion.ProjectLighthouse
} }
#nullable disable #nullable disable
requestStopwatch.Stop(); await next(context);
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);
}
} }
); );

View file

@ -12,7 +12,7 @@ namespace LBPUnion.ProjectLighthouse.Types.Settings
public class ServerSettings 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() static ServerSettings()
{ {
if (ServerStatics.IsUnitTesting) return; // Unit testing, we don't want to read configurations here since the tests will provide their own 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; } = ""; 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 DbConnectionString { get; set; } = "server=127.0.0.1;uid=root;pwd=lighthouse;database=lighthouse";
public string ExternalUrl { get; set; } = "http://localhost:10060"; public string ExternalUrl { get; set; } = "http://localhost:10060";

View file

@ -125,6 +125,10 @@ namespace LBPUnion.ProjectLighthouse.Types
} }
#nullable disable #nullable disable
public bool Banned { get; set; }
public string BannedReason { get; set; }
public string Serialize(GameVersion gameVersion = GameVersion.LittleBigPlanet1) public string Serialize(GameVersion gameVersion = GameVersion.LittleBigPlanet1)
{ {
string user = LbpSerializer.TaggedStringElement("npHandle", this.Username, "icon", this.IconHash) + string user = LbpSerializer.TaggedStringElement("npHandle", this.Username, "icon", this.IconHash) +

View file

@ -1,7 +1,6 @@
# Project Lighthouse # Project Lighthouse
Project Lighthouse is an umbrella project for all work to investigate and develop private servers for LittleBigPlanet. 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.
This project is the main server component that LittleBigPlanet games connect to.
## WARNING! ## WARNING!