Implement checking for the Patchwork user agent (#1090)
Some checks failed
Continuous Integration / Build & Test (push) Has been cancelled
Upload Translations to Crowdin / crowdin-sync (push) Has been cancelled
Build Docker Image / Build and Publish (push) Has been cancelled
Qodana / qodana (push) Has been cancelled

* Implement checking for the Patchwork user agent, move logout into standalone method

* Quick fixes (awesome name)

* 403 user at login instead of logging out at /announce

* Move configuration and revert logout changes

* Rework parsing to check against GameVersion enum and game token GameVersion

* Fix logic error

oopsie

* Fix Zaprit suggestions

* Simplify patchwork game version test

* Test patchwork user agent with regex instead

* Fix Qodana warnings

* Fix remaining Qodana warnings
This commit is contained in:
FeTetra 2025-06-04 08:14:40 -04:00 committed by GitHub
commit aeba706391
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 85 additions and 4 deletions

View file

@ -3,6 +3,7 @@ using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Helpers;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Tickets;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
@ -212,6 +213,14 @@ public class LoginController : ControllerBase
return this.Forbid();
}
if (ServerConfiguration.Instance.Authentication.RequirePatchworkUserAgent)
{
if (!PatchworkHelper.IsValidPatchworkUserAgent(this.Request.Headers.UserAgent.ToString()))
{
return this.Forbid();
}
}
Logger.Success($"Successfully logged in user {user.Username} as {token.GameVersion} client", LogArea.Login);
user.LastLogin = TimeHelper.TimestampMillis;

View file

@ -38,5 +38,4 @@ public class LogoutController : ControllerBase
return this.Ok();
}
}

View file

@ -0,0 +1,24 @@
using LBPUnion.ProjectLighthouse.Configuration;
using System.Text.RegularExpressions;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Helpers;
public static partial class PatchworkHelper
{
private static readonly int requiredMajor = ServerConfiguration.Instance.Authentication.PatchworkMajorVersionMinimum;
private static readonly int requiredMinor = ServerConfiguration.Instance.Authentication.PatchworkMinorVersionMinimum;
[GeneratedRegex(@"^PatchworkLBP[123V] (\d{1,5})\.(\d{1,5})$")]
private static partial Regex PatchworkUserAgentRegex();
public static bool IsValidPatchworkUserAgent(string userAgent)
{
Match result = PatchworkUserAgentRegex().Match(userAgent);
if (!result.Success) return false;
if (!int.TryParse(result.Groups[1].Value, out int major) || !int.TryParse(result.Groups[2].Value, out int minor))
return false;
return major >= requiredMajor && minor >= requiredMinor;
}
}

View file

@ -0,0 +1,44 @@
using LBPUnion.ProjectLighthouse.Servers.GameServer.Helpers;
using Xunit;
namespace ProjectLighthouse.Tests.GameApiTests.Unit;
[Trait("Category", "Unit")]
public class PatchworkUserAgentTests
{
[Fact]
public void CanValidatePatchworkUserAgents()
{
string[] validUserAgents = {
"PatchworkLBP1 1.0",
"PatchworkLBP2 2.0",
"PatchworkLBP3 3.0",
"PatchworkLBPV 4.0",
"PatchworkLBP1 1.5",
};
string[] invalidUserAgents = {
// Matching
"patchworklbp1 1.0", // Case sensitive
"ptchwrklbp1 1.0", // Misspelled
"PatchworkLBP1 1", // Missing major/minor
"PatchworkLBP1 1.000001", // Major/minor too long
// Data
"PatchworkLBP1 0.5", // Version number too low
"PatchworkLBP1 A.0" // Int cannot be parsed
};
bool result;
foreach (string userAgent in validUserAgents)
{
result = PatchworkHelper.IsValidPatchworkUserAgent(userAgent);
Assert.True(result, $"Valid user agent: \"{userAgent}\" was evaluated as {result}.");
}
foreach (string userAgent in invalidUserAgents)
{
result = PatchworkHelper.IsValidPatchworkUserAgent(userAgent);
Assert.False(result, $"Invalid user agent: \"{userAgent}\" was evaluated as {result}.");
}
}
}

View file

@ -10,4 +10,10 @@ public class AuthenticationConfiguration
public bool AllowPSNSignup { get; set; } = true;
// Require use of Zaprit's "Patchwork" prx plugin's user agent when connecting to the server
// Major and minor version minimums can be left alone if patchwork is not required
public bool RequirePatchworkUserAgent { get; set; } = false;
public int PatchworkMajorVersionMinimum { get; set; } = 1;
public int PatchworkMinorVersionMinimum { get; set; } = 0;
}

View file

@ -11,7 +11,7 @@ public class ServerConfiguration : ConfigurationBase<ServerConfiguration>
// This is so Lighthouse can properly identify outdated configurations and update them with newer settings accordingly.
// If you are modifying anything here, this value MUST be incremented.
// Thanks for listening~
public override int ConfigVersion { get; set; } = 27;
public override int ConfigVersion { get; set; } = 30;
public override string ConfigName { get; set; } = "lighthouse.yml";
public string WebsiteListenUrl { get; set; } = "http://localhost:10060";
@ -31,7 +31,6 @@ public class ServerConfiguration : ConfigurationBase<ServerConfiguration>
public bool CheckForUnsafeFiles { get; set; } = true;
public bool LogChatFiltering { get; set; } = false;
public bool LogChatMessages { get; set; } = false;
public AuthenticationConfiguration Authentication { get; set; } = new();
public CaptchaConfiguration Captcha { get; set; } = new();
public DigestKeyConfiguration DigestKey { get; set; } = new();