mirror of
https://github.com/LBPUnion/ProjectLighthouse.git
synced 2025-06-03 22:52:27 +00:00
Merge branch 'main' into crowdin
This commit is contained in:
commit
bac1cad9f2
73 changed files with 1091 additions and 272 deletions
|
@ -7,6 +7,12 @@
|
||||||
"commands": [
|
"commands": [
|
||||||
"dotnet-ef"
|
"dotnet-ef"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"dotnet-trace": {
|
||||||
|
"version": "6.0.328102",
|
||||||
|
"commands": [
|
||||||
|
"dotnet-trace"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -4,6 +4,9 @@
|
||||||
<option name="PROGRAM_PARAMETERS" value="" />
|
<option name="PROGRAM_PARAMETERS" value="" />
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/ProjectLighthouse" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/ProjectLighthouse" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
|
<envs>
|
||||||
|
<env name="ASPNETCORE_ENVIRONMENT" value="Development" />
|
||||||
|
</envs>
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
<option name="RUNTIME_ARGUMENTS" value="" />
|
<option name="RUNTIME_ARGUMENTS" value="" />
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
<option name="PROGRAM_PARAMETERS" value="" />
|
<option name="PROGRAM_PARAMETERS" value="" />
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/ProjectLighthouse" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/ProjectLighthouse" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
|
<envs>
|
||||||
|
<env name="ASPNETCORE_ENVIRONMENT" value="Development" />
|
||||||
|
</envs>
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
<option name="RUNTIME_ARGUMENTS" value="" />
|
<option name="RUNTIME_ARGUMENTS" value="" />
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
<option name="PROGRAM_PARAMETERS" value="" />
|
<option name="PROGRAM_PARAMETERS" value="" />
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/ProjectLighthouse" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/ProjectLighthouse" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
|
<envs>
|
||||||
|
<env name="ASPNETCORE_ENVIRONMENT" value="Development" />
|
||||||
|
</envs>
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
<option name="RUNTIME_ARGUMENTS" value="" />
|
<option name="RUNTIME_ARGUMENTS" value="" />
|
||||||
|
|
|
@ -17,9 +17,6 @@ Once you've gotten MySQL running you can run Lighthouse. It will take care of th
|
||||||
PS3 is difficult to set up, so I will be going over how to set up RPCS3 instead. A guide will be coming for PS3 closer
|
PS3 is difficult to set up, so I will be going over how to set up RPCS3 instead. A guide will be coming for PS3 closer
|
||||||
to release. You can also follow this guide if you want to learn how to modify your EBOOT.
|
to release. You can also follow this guide if you want to learn how to modify your EBOOT.
|
||||||
|
|
||||||
*Note: This requires a patched copy of RPCS3. You can find a working
|
|
||||||
version [on our GitHub](https://github.com/LBPUnion/rpcs3).*
|
|
||||||
|
|
||||||
Start by getting a copy of LittleBigPlanet 1/2 installed. (Check the LittleBigPlanet 1 section, since you'll need to do
|
Start by getting a copy of LittleBigPlanet 1/2 installed. (Check the LittleBigPlanet 1 section, since you'll need to do
|
||||||
extra steps for your game to not crash upon entering pod computer).
|
extra steps for your game to not crash upon entering pod computer).
|
||||||
|
|
||||||
|
@ -44,8 +41,8 @@ Now, copy the `EBOOTlocalhost.elf` file to where you got your `EBOOT.elf` file f
|
||||||
|
|
||||||
To launch the game with the patched EBOOT, open up RPCS3, go to File, Boot SELF/ELF, and open up `EBOOTlocalhost.elf`.
|
To launch the game with the patched EBOOT, open up RPCS3, go to File, Boot SELF/ELF, and open up `EBOOTlocalhost.elf`.
|
||||||
|
|
||||||
Assuming you are running the patched version of RPCS3, you patched the file correctly, the database is migrated, and
|
Assuming you patched the file correctly, the database is migrated, and
|
||||||
Project Lighthouse is running, the game should now connect and you may begin contributing!
|
Project Lighthouse is running, the game should now connect, and you may begin contributing!
|
||||||
|
|
||||||
### LittleBigPlanet 1
|
### LittleBigPlanet 1
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#nullable enable
|
#nullable enable
|
||||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||||
using LBPUnion.ProjectLighthouse.Types;
|
using LBPUnion.ProjectLighthouse.PlayerData;
|
||||||
|
using LBPUnion.ProjectLighthouse.Helpers;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
@ -54,4 +55,33 @@ public class UserEndpoints : ApiEndpointController
|
||||||
|
|
||||||
return this.Ok(userStatus);
|
return this.Ok(userStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("user/inviteToken")]
|
||||||
|
public async Task<IActionResult> CreateUserInviteToken()
|
||||||
|
{
|
||||||
|
if (Configuration.ServerConfiguration.Instance.Authentication.PrivateRegistration ||
|
||||||
|
Configuration.ServerConfiguration.Instance.Authentication.RegistrationEnabled)
|
||||||
|
{
|
||||||
|
|
||||||
|
string authHeader = this.Request.Headers["Authorization"];
|
||||||
|
if (!string.IsNullOrWhiteSpace(authHeader))
|
||||||
|
{
|
||||||
|
string authToken = authHeader.Substring(authHeader.IndexOf(' ') + 1);
|
||||||
|
|
||||||
|
APIKey? apiKey = await this.database.APIKeys.FirstOrDefaultAsync(k => k.Key == authToken);
|
||||||
|
if (apiKey == null) return this.StatusCode(403, null);
|
||||||
|
|
||||||
|
RegistrationToken token = new();
|
||||||
|
token.Created = DateTime.Now;
|
||||||
|
token.Token = CryptoHelper.GenerateAuthToken();
|
||||||
|
|
||||||
|
this.database.RegistrationTokens.Add(token);
|
||||||
|
await this.database.SaveChangesAsync();
|
||||||
|
|
||||||
|
return Ok(token.Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return this.NotFound();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -29,7 +29,7 @@ public class ClientConfigurationController : ControllerBase
|
||||||
HostString hostname = this.Request.Host;
|
HostString hostname = this.Request.Host;
|
||||||
return this.Ok
|
return this.Ok
|
||||||
(
|
(
|
||||||
"ProbabilityOfPacketDelay 0.0\nMinPacketDelayFrames 0\nMaxPacketDelayFrames 3\nProbabilityOfPacketDrop 0.0\nEnableFakeConditionsForLoopback true\nNumberOfFramesPredictionAllowedForNonLocalPlayer 1000\nEnablePrediction true\nMinPredictedFrames 0\nMaxPredictedFrames 10\nAllowGameRendCameraSplit true\nFramesBeforeAgressiveCatchup 30\nPredictionPadSides 200\nPredictionPadTop 200\nPredictionPadBottom 200\nShowErrorNumbers true\nAllowModeratedLevels false\nAllowModeratedPoppetItems false\nTIMEOUT_WAIT_FOR_JOIN_RESPONSE_FROM_PREV_PARTY_HOST 50.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_HOST 30.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_MEMBER 45.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN_FRIEND 15.0\nTIMEOUT_WAIT_FOR_CONNECTION_FROM_HOST 30.0\nTIMEOUT_WAIT_FOR_ROOM_ID_TO_JOIN 60.0\nTIMEOUT_WAIT_FOR_GET_NUM_PLAYERS_ONLINE 60.0\nTIMEOUT_WAIT_FOR_SIGNALLING_CONNECTIONS 120.0\nTIMEOUT_WAIT_FOR_PARTY_DATA 60.0\nTIME_TO_WAIT_FOR_LEAVE_MESSAGE_TO_COME_BACK 20.0\nTIME_TO_WAIT_FOR_FOLLOWING_REQUESTS_TO_ARRIVE 30.0\nTIMEOUT_WAIT_FOR_FINISHED_MIGRATING_HOST 30.0\nTIMEOUT_WAIT_FOR_PARTY_LEADER_FINISH_JOINING 45.0\nTIMEOUT_WAIT_FOR_QUICKPLAY_LEVEL 60.0\nTIMEOUT_WAIT_FOR_PLAYERS_TO_JOIN 30.0\nTIMEOUT_WAIT_FOR_DIVE_IN_PLAYERS 240.0\nTIMEOUT_WAIT_FOR_FIND_BEST_ROOM 60.0\nTIMEOUT_DIVE_IN_TOTAL 300.0\nTIMEOUT_WAIT_FOR_SOCKET_CONNECTION 120.0\nTIMEOUT_WAIT_FOR_REQUEST_RESOURCE_MESSAGE 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_GET_RESOURCE_LIST 120.0\nTIMEOUT_WAIT_FOR_CLIENT_TO_LOAD_RESOURCES 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_SAVE_GAME_STATE 30.0\nTIMEOUT_WAIT_FOR_ADD_PLAYERS_TO_TAKE 30.0\nTIMEOUT_WAIT_FOR_UPDATE_FROM_CLIENT 90.0\nTIMEOUT_WAIT_FOR_HOST_TO_GET_RESOURCE_LIST 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_SAVE_GAME_STATE 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_ADD_US 30.0\nTIMEOUT_WAIT_FOR_UPDATE 60.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN 50.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_PRESENCE 60.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_CONNECTION 120.0\nSECONDS_BETWEEN_PINS_AWARDED_UPLOADS 300.0\nEnableKeepAlive true\nAllowVoIPRecordingPlayback true\nOverheatingThresholdDisallowMidgameJoin 0.95\nMaxCatchupFrames 3\nMaxLagBeforeShowLoading 23\nMinLagBeforeHideLoading 30\nLagImprovementInflectionPoint -1.0\nFlickerThreshold 2.0\nClosedDemo2014Version 1\nClosedDemo2014Expired false\nEnablePlayedFilter true\nEnableCommunityDecorations true\nGameStateUpdateRate 10.0\nGameStateUpdateRateWithConsumers 1.0\nDisableDLCPublishCheck false\nEnableDiveIn true\nEnableHackChecks false\nAllowOnlineCreate true" +
|
"ProbabilityOfPacketDelay 0.0\nMinPacketDelayFrames 0\nMaxPacketDelayFrames 3\nProbabilityOfPacketDrop 0.0\nEnableFakeConditionsForLoopback true\nNumberOfFramesPredictionAllowedForNonLocalPlayer 1000\nEnablePrediction true\nMinPredictedFrames 0\nMaxPredictedFrames 10\nAllowGameRendCameraSplit true\nFramesBeforeAgressiveCatchup 30\nPredictionPadSides 200\nPredictionPadTop 200\nPredictionPadBottom 200\nShowErrorNumbers true\nAllowModeratedLevels false\nAllowModeratedPoppetItems false\nTIMEOUT_WAIT_FOR_JOIN_RESPONSE_FROM_PREV_PARTY_HOST 50.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_HOST 30.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_MEMBER 45.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN_FRIEND 15.0\nTIMEOUT_WAIT_FOR_CONNECTION_FROM_HOST 30.0\nTIMEOUT_WAIT_FOR_ROOM_ID_TO_JOIN 60.0\nTIMEOUT_WAIT_FOR_GET_NUM_PLAYERS_ONLINE 60.0\nTIMEOUT_WAIT_FOR_SIGNALLING_CONNECTIONS 120.0\nTIMEOUT_WAIT_FOR_PARTY_DATA 60.0\nTIME_TO_WAIT_FOR_LEAVE_MESSAGE_TO_COME_BACK 20.0\nTIME_TO_WAIT_FOR_FOLLOWING_REQUESTS_TO_ARRIVE 30.0\nTIMEOUT_WAIT_FOR_FINISHED_MIGRATING_HOST 30.0\nTIMEOUT_WAIT_FOR_PARTY_LEADER_FINISH_JOINING 45.0\nTIMEOUT_WAIT_FOR_QUICKPLAY_LEVEL 60.0\nTIMEOUT_WAIT_FOR_PLAYERS_TO_JOIN 30.0\nTIMEOUT_WAIT_FOR_DIVE_IN_PLAYERS 240.0\nTIMEOUT_WAIT_FOR_FIND_BEST_ROOM 60.0\nTIMEOUT_DIVE_IN_TOTAL 300.0\nTIMEOUT_WAIT_FOR_SOCKET_CONNECTION 120.0\nTIMEOUT_WAIT_FOR_REQUEST_RESOURCE_MESSAGE 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_GET_RESOURCE_LIST 120.0\nTIMEOUT_WAIT_FOR_CLIENT_TO_LOAD_RESOURCES 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_SAVE_GAME_STATE 30.0\nTIMEOUT_WAIT_FOR_ADD_PLAYERS_TO_TAKE 30.0\nTIMEOUT_WAIT_FOR_UPDATE_FROM_CLIENT 90.0\nTIMEOUT_WAIT_FOR_HOST_TO_GET_RESOURCE_LIST 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_SAVE_GAME_STATE 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_ADD_US 30.0\nTIMEOUT_WAIT_FOR_UPDATE 60.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN 50.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_PRESENCE 60.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_CONNECTION 120.0\nSECONDS_BETWEEN_PINS_AWARDED_UPLOADS 300.0\nEnableKeepAlive true\nAllowVoIPRecordingPlayback true\nOverheatingThresholdDisallowMidgameJoin 0.95\nMaxCatchupFrames 3\nMaxLagBeforeShowLoading 23\nMinLagBeforeHideLoading 30\nLagImprovementInflectionPoint -1.0\nFlickerThreshold 2.0\nClosedDemo2014Version 1\nClosedDemo2014Expired false\nEnablePlayedFilter true\nEnableCommunityDecorations true\nGameStateUpdateRate 10.0\nGameStateUpdateRateWithConsumers 1.0\nDisableDLCPublishCheck false\nEnableDiveIn true\nEnableHackChecks false\nAllowOnlineCreate true\n" +
|
||||||
$"TelemetryServer {hostname}\n" +
|
$"TelemetryServer {hostname}\n" +
|
||||||
$"CDNHostName {hostname}\n" +
|
$"CDNHostName {hostname}\n" +
|
||||||
$"ShowLevelBoos {ServerConfiguration.Instance.UserGeneratedContentLimits.BooingEnabled.ToString().ToLower()}\n"
|
$"ShowLevelBoos {ServerConfiguration.Instance.UserGeneratedContentLimits.BooingEnabled.ToString().ToLower()}\n"
|
||||||
|
|
|
@ -82,7 +82,7 @@ public class LoginController : ControllerBase
|
||||||
|
|
||||||
if (ServerConfiguration.Instance.Authentication.UseExternalAuth)
|
if (ServerConfiguration.Instance.Authentication.UseExternalAuth)
|
||||||
{
|
{
|
||||||
if (this.database.UserApprovedIpAddresses.Where(a => a.UserId == user.UserId).Select(a => a.IpAddress).Contains(ipAddress))
|
if (user.ApprovedIPAddress == ipAddress)
|
||||||
{
|
{
|
||||||
token.Approved = true;
|
token.Approved = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ using LBPUnion.ProjectLighthouse.Configuration;
|
||||||
using LBPUnion.ProjectLighthouse.Files;
|
using LBPUnion.ProjectLighthouse.Files;
|
||||||
using LBPUnion.ProjectLighthouse.Helpers;
|
using LBPUnion.ProjectLighthouse.Helpers;
|
||||||
using LBPUnion.ProjectLighthouse.Levels;
|
using LBPUnion.ProjectLighthouse.Levels;
|
||||||
|
using LBPUnion.ProjectLighthouse.Logging;
|
||||||
using LBPUnion.ProjectLighthouse.PlayerData;
|
using LBPUnion.ProjectLighthouse.PlayerData;
|
||||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||||
using LBPUnion.ProjectLighthouse.Serialization;
|
using LBPUnion.ProjectLighthouse.Serialization;
|
||||||
|
@ -82,34 +83,67 @@ public class PublishController : ControllerBase
|
||||||
GameToken gameToken = userAndToken.Value.Item2;
|
GameToken gameToken = userAndToken.Value.Item2;
|
||||||
Slot? slot = await this.getSlotFromBody();
|
Slot? slot = await this.getSlotFromBody();
|
||||||
|
|
||||||
if (slot == null) return this.BadRequest();
|
if (slot == null)
|
||||||
|
{
|
||||||
|
Logger.Warn("Rejecting level upload, slot is null", LogArea.Publish);
|
||||||
|
return this.BadRequest();
|
||||||
|
}
|
||||||
|
|
||||||
if (slot.Location == null) return this.BadRequest();
|
if (slot.Location == null)
|
||||||
|
{
|
||||||
|
Logger.Warn("Rejecting level upload, slot location is null", LogArea.Publish);
|
||||||
|
return this.BadRequest();
|
||||||
|
}
|
||||||
|
|
||||||
if (slot.Description.Length > 500) return this.BadRequest();
|
if (slot.Description.Length > 512)
|
||||||
|
{
|
||||||
|
Logger.Warn($"Rejecting level upload, description too long ({slot.Description.Length} characters)", LogArea.Publish);
|
||||||
|
return this.BadRequest();
|
||||||
|
}
|
||||||
|
|
||||||
if (slot.Name.Length > 64) return this.BadRequest();
|
if (slot.Name.Length > 64)
|
||||||
|
{
|
||||||
|
Logger.Warn($"Rejecting level upload, title too long ({slot.Name.Length} characters)", LogArea.Publish);
|
||||||
|
return this.BadRequest();
|
||||||
|
}
|
||||||
|
|
||||||
if (slot.Resources.Any(resource => !FileHelper.ResourceExists(resource)))
|
if (slot.Resources.Any(resource => !FileHelper.ResourceExists(resource)))
|
||||||
{
|
{
|
||||||
|
Logger.Warn("Rejecting level upload, missing resource(s)", LogArea.Publish);
|
||||||
return this.BadRequest();
|
return this.BadRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
LbpFile? rootLevel = LbpFile.FromHash(slot.RootLevel);
|
LbpFile? rootLevel = LbpFile.FromHash(slot.RootLevel);
|
||||||
|
|
||||||
if (rootLevel == null) return this.BadRequest();
|
if (rootLevel == null)
|
||||||
|
{
|
||||||
|
Logger.Warn("Rejecting level upload, unable to find rootLevel", LogArea.Publish);
|
||||||
|
return this.BadRequest();
|
||||||
|
}
|
||||||
|
|
||||||
if (rootLevel.FileType != LbpFileType.Level) return this.BadRequest();
|
if (rootLevel.FileType != LbpFileType.Level)
|
||||||
|
{
|
||||||
|
Logger.Warn("Rejecting level upload, rootLevel is not a level", LogArea.Publish);
|
||||||
|
return this.BadRequest();
|
||||||
|
}
|
||||||
|
|
||||||
// Republish logic
|
// Republish logic
|
||||||
if (slot.SlotId != 0)
|
if (slot.SlotId != 0)
|
||||||
{
|
{
|
||||||
Slot? oldSlot = await this.database.Slots.Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == slot.SlotId);
|
Slot? oldSlot = await this.database.Slots.Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == slot.SlotId);
|
||||||
if (oldSlot == null) return this.NotFound();
|
if (oldSlot == null)
|
||||||
|
{
|
||||||
|
Logger.Warn("Rejecting level republish, wasn't able to find old slot", LogArea.Publish);
|
||||||
|
return this.NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
if (oldSlot.Location == null) throw new ArgumentNullException();
|
if (oldSlot.Location == null) throw new ArgumentNullException();
|
||||||
|
|
||||||
if (oldSlot.CreatorId != user.UserId) return this.BadRequest();
|
if (oldSlot.CreatorId != user.UserId)
|
||||||
|
{
|
||||||
|
Logger.Warn("Rejecting level republish, old level not owned by current user", LogArea.Publish);
|
||||||
|
return this.BadRequest();
|
||||||
|
}
|
||||||
|
|
||||||
oldSlot.Location.X = slot.Location.X;
|
oldSlot.Location.X = slot.Location.X;
|
||||||
oldSlot.Location.Y = slot.Location.Y;
|
oldSlot.Location.Y = slot.Location.Y;
|
||||||
|
@ -166,7 +200,8 @@ public class PublishController : ControllerBase
|
||||||
|
|
||||||
if (user.GetUsedSlotsForGame(gameToken.GameVersion) > ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)
|
if (user.GetUsedSlotsForGame(gameToken.GameVersion) > ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)
|
||||||
{
|
{
|
||||||
return this.StatusCode(403, "");
|
Logger.Warn("Rejecting level upload, too many published slots", LogArea.Publish);
|
||||||
|
return this.BadRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: parse location in body
|
//TODO: parse location in body
|
||||||
|
@ -197,6 +232,8 @@ public class PublishController : ControllerBase
|
||||||
"New level published!",
|
"New level published!",
|
||||||
$"**{user.Username}** just published a new level: [**{slot.Name}**]({ServerConfiguration.Instance.ExternalUrl}/slot/{slot.SlotId})\n{slot.Description}"
|
$"**{user.Username}** just published a new level: [**{slot.Name}**]({ServerConfiguration.Instance.ExternalUrl}/slot/{slot.SlotId})\n{slot.Description}"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Logger.Success($"Successfully published level {slot.Name} (id: {slot.SlotId}) by {user.Username} (id: {user.UserId})", LogArea.Publish);
|
||||||
|
|
||||||
return this.Ok(slot.Serialize(gameToken.GameVersion));
|
return this.Ok(slot.Serialize(gameToken.GameVersion));
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,17 +99,14 @@ public class UserController : ControllerBase
|
||||||
|
|
||||||
if (update.Biography != null)
|
if (update.Biography != null)
|
||||||
{
|
{
|
||||||
if (update.Biography.Length > 100) return this.BadRequest();
|
if (update.Biography.Length > 512) return this.BadRequest();
|
||||||
|
|
||||||
user.Biography = update.Biography;
|
user.Biography = update.Biography;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (string? resource in new[]
|
foreach (string? resource in new[]{update.IconHash, update.YayHash, update.MehHash, update.BooHash, update.PlanetHash,})
|
||||||
{
|
|
||||||
update.IconHash, update.YayHash, update.MehHash, update.BooHash, update.PlanetHash,
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
if (resource != null && !FileHelper.ResourceExists(resource)) return this.BadRequest();
|
if (resource != null && !resource.StartsWith('g') && !FileHelper.ResourceExists(resource)) return this.BadRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (update.IconHash != null) user.IconHash = update.IconHash;
|
if (update.IconHash != null) user.IconHash = update.IconHash;
|
||||||
|
|
|
@ -46,4 +46,17 @@ public class RoomVisualizerController : ControllerBase
|
||||||
return this.Redirect("/debug/roomVisualizer");
|
return this.Redirect("/debug/roomVisualizer");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("createRoomsWithDuplicatePlayers")]
|
||||||
|
public async Task<IActionResult> CreateRoomsWithDuplicatePlayers()
|
||||||
|
{
|
||||||
|
#if !DEBUG
|
||||||
|
return this.NotFound();
|
||||||
|
#else
|
||||||
|
List<int> users = await this.database.Users.OrderByDescending(_ => EF.Functions.Random()).Take(1).Select(u => u.UserId).ToListAsync();
|
||||||
|
RoomHelper.CreateRoom(users, GameVersion.LittleBigPlanet2, Platform.PS3);
|
||||||
|
RoomHelper.CreateRoom(users, GameVersion.LittleBigPlanet2, Platform.PS3);
|
||||||
|
return this.Redirect("/debug/roomVisualizer");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -32,15 +32,8 @@ public class AutoApprovalController : ControllerBase
|
||||||
if (authAttempt.GameToken.UserId != user.UserId) return this.Redirect("/login");
|
if (authAttempt.GameToken.UserId != user.UserId) return this.Redirect("/login");
|
||||||
|
|
||||||
authAttempt.GameToken.Approved = true;
|
authAttempt.GameToken.Approved = true;
|
||||||
|
user.ApprovedIPAddress = authAttempt.IPAddress;
|
||||||
UserApprovedIpAddress approvedIpAddress = new()
|
|
||||||
{
|
|
||||||
UserId = user.UserId,
|
|
||||||
User = user,
|
|
||||||
IpAddress = authAttempt.IPAddress,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.database.UserApprovedIpAddresses.Add(approvedIpAddress);
|
|
||||||
this.database.AuthenticationAttempts.Remove(authAttempt);
|
this.database.AuthenticationAttempts.Remove(authAttempt);
|
||||||
|
|
||||||
await this.database.SaveChangesAsync();
|
await this.database.SaveChangesAsync();
|
||||||
|
@ -48,20 +41,16 @@ public class AutoApprovalController : ControllerBase
|
||||||
return this.Redirect("/authentication");
|
return this.Redirect("/authentication");
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("revokeAutoApproval/{id:int}")]
|
[HttpGet("revokeAutoApproval")]
|
||||||
public async Task<IActionResult> RevokeAutoApproval([FromRoute] int id)
|
public async Task<IActionResult> RevokeAutoApproval()
|
||||||
{
|
{
|
||||||
User? user = this.database.UserFromWebRequest(this.Request);
|
User? user = this.database.UserFromWebRequest(this.Request);
|
||||||
if (user == null) return this.Redirect("/login");
|
if (user == null) return this.Redirect("/login");
|
||||||
|
|
||||||
UserApprovedIpAddress? approvedIpAddress = await this.database.UserApprovedIpAddresses.FirstOrDefaultAsync(a => a.UserApprovedIpAddressId == id);
|
user.ApprovedIPAddress = null;
|
||||||
if (approvedIpAddress == null) return this.BadRequest();
|
|
||||||
if (approvedIpAddress.UserId != user.UserId) return this.Redirect("/login");
|
|
||||||
|
|
||||||
this.database.UserApprovedIpAddresses.Remove(approvedIpAddress);
|
|
||||||
|
|
||||||
await this.database.SaveChangesAsync();
|
await this.database.SaveChangesAsync();
|
||||||
|
|
||||||
return this.Redirect("/authentication/autoApprovals");
|
return this.Redirect("/authentication");
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
using LBPUnion.ProjectLighthouse.Files;
|
using LBPUnion.ProjectLighthouse.Files;
|
||||||
using LBPUnion.ProjectLighthouse.Helpers;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using IOFile = System.IO.File;
|
using IOFile = System.IO.File;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
@page "/admin/keys"
|
||||||
|
|
||||||
|
@using LBPUnion.ProjectLighthouse.PlayerData
|
||||||
|
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Admin.AdminAPIKeyPageModel
|
||||||
|
@{
|
||||||
|
Layout = "Layouts/BaseLayout";
|
||||||
|
Model.Title = "API Keys";
|
||||||
|
}
|
||||||
|
|
||||||
|
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Antiforgery
|
||||||
|
@{
|
||||||
|
var token = Antiforgery.GetAndStoreTokens(HttpContext).RequestToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
<script>function deleteKey(keyID) {
|
||||||
|
document.getElementById("trashbutton-".concat(keyID)).classList.add('loading');
|
||||||
|
fetch("@Url.RouteUrl(ViewContext.RouteData.Values)", {
|
||||||
|
method: 'post',
|
||||||
|
headers: {
|
||||||
|
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"
|
||||||
|
},
|
||||||
|
credentials: 'same-origin',
|
||||||
|
body: 'keyID='.concat(keyID).concat("&__RequestVerificationToken=@token")
|
||||||
|
})
|
||||||
|
.then(function (data) {
|
||||||
|
document.getElementById("keyitem-".concat(keyID)).remove();
|
||||||
|
window.location.reload(true);
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
console.log('Request failed', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
}</script>
|
||||||
|
|
||||||
|
<p>There are <b>@Model.KeyCount</b> API keys registered.</p>
|
||||||
|
@if (Model.KeyCount == 0)
|
||||||
|
{
|
||||||
|
<p>To create one, you can use the "Create API key" command in the admin panel.</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="ui four column grid">
|
||||||
|
@foreach (APIKey key in Model.APIKeys)
|
||||||
|
{
|
||||||
|
<div id="keyitem-@key.Id" class="five wide column">
|
||||||
|
<div class="ui blue segment">
|
||||||
|
<div class="ui tiny bottom left attached label">
|
||||||
|
Created at: @key.Created.ToString()
|
||||||
|
</div>
|
||||||
|
<button id="trashbutton-@key.Id" class="right floated circular ui icon button" onclick="deleteKey(@key.Id);">
|
||||||
|
<i class="trash can icon"></i>
|
||||||
|
</button>
|
||||||
|
<h2>@key.Description</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
|
@ -0,0 +1,43 @@
|
||||||
|
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||||
|
using LBPUnion.ProjectLighthouse.PlayerData;
|
||||||
|
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.Admin
|
||||||
|
{
|
||||||
|
public class AdminAPIKeyPageModel : BaseLayout
|
||||||
|
{
|
||||||
|
public List<APIKey> APIKeys = new();
|
||||||
|
public int KeyCount;
|
||||||
|
|
||||||
|
public AdminAPIKeyPageModel(Database database) : base(database)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public async Task<IActionResult> OnGet()
|
||||||
|
{
|
||||||
|
User? user = this.Database.UserFromWebRequest(this.Request);
|
||||||
|
if (user == null) return this.Redirect("~/login");
|
||||||
|
if (!user.IsAdmin) return this.NotFound();
|
||||||
|
|
||||||
|
this.APIKeys = await this.Database.APIKeys.OrderByDescending(k => k.Id).ToListAsync();
|
||||||
|
this.KeyCount = this.APIKeys.Count;
|
||||||
|
|
||||||
|
return this.Page();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> OnPost(string keyID)
|
||||||
|
{
|
||||||
|
User? user = this.Database.UserFromWebRequest(this.Request);
|
||||||
|
if (user == null || !user.IsAdmin) return this.NotFound();
|
||||||
|
|
||||||
|
APIKey? apiKey = await this.Database.APIKeys.FirstOrDefaultAsync(k => k.Id == int.Parse(keyID));
|
||||||
|
if (apiKey == null) return this.NotFound();
|
||||||
|
this.Database.APIKeys.Remove(apiKey);
|
||||||
|
await this.Database.SaveChangesAsync();
|
||||||
|
|
||||||
|
return this.Page();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,7 @@ public class AdminPanelPage : BaseLayout
|
||||||
{
|
{
|
||||||
public List<ICommand> Commands = MaintenanceHelper.Commands;
|
public List<ICommand> Commands = MaintenanceHelper.Commands;
|
||||||
public AdminPanelPage(Database database) : base(database)
|
public AdminPanelPage(Database database) : base(database)
|
||||||
{}
|
{ }
|
||||||
|
|
||||||
public List<AdminPanelStatistic> Statistics = new();
|
public List<AdminPanelStatistic> Statistics = new();
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ public class AdminPanelPage : BaseLayout
|
||||||
this.Statistics.Add(new AdminPanelStatistic("Slots", await StatisticsHelper.SlotCount()));
|
this.Statistics.Add(new AdminPanelStatistic("Slots", await StatisticsHelper.SlotCount()));
|
||||||
this.Statistics.Add(new AdminPanelStatistic("Photos", await StatisticsHelper.PhotoCount()));
|
this.Statistics.Add(new AdminPanelStatistic("Photos", await StatisticsHelper.PhotoCount()));
|
||||||
this.Statistics.Add(new AdminPanelStatistic("Reports", await StatisticsHelper.ReportCount(), "reports/0"));
|
this.Statistics.Add(new AdminPanelStatistic("Reports", await StatisticsHelper.ReportCount(), "reports/0"));
|
||||||
|
this.Statistics.Add(new AdminPanelStatistic("API Keys", await StatisticsHelper.APIKeyCount(), "keys"));
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(command))
|
if (!string.IsNullOrEmpty(command))
|
||||||
{
|
{
|
||||||
|
|
|
@ -44,6 +44,10 @@
|
||||||
<div class="ui blue button">Create Fake Room</div>
|
<div class="ui blue button">Create Fake Room</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a href="/debug/roomVisualizer/createRoomsWithDuplicatePlayers">
|
||||||
|
<div class="ui blue button">Create Rooms With Duplicate Players</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
<a href="/debug/roomVisualizer/deleteRooms">
|
<a href="/debug/roomVisualizer/deleteRooms">
|
||||||
<div class="ui red button">Nuke all rooms</div>
|
<div class="ui red button">Nuke all rooms</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -21,18 +21,24 @@ else
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<a href="/authentication/autoApprovals">
|
@if (Model.User!.ApprovedIPAddress != null)
|
||||||
<button class="ui small blue button">
|
{
|
||||||
<i class="cog icon"></i>
|
<a href="/authentication/revokeAutoApproval">
|
||||||
<span>Manage automatically approved IP addresses</span>
|
<button class="ui red button">
|
||||||
</button>
|
<i class="trash icon"></i>
|
||||||
</a>
|
<span>Revoke automatically approved IP Address (@Model.User!.ApprovedIPAddress)</span>
|
||||||
<a href="/authentication/denyAll">
|
</button>
|
||||||
<button class="ui small red button">
|
</a>
|
||||||
<i class="x icon"></i>
|
}
|
||||||
<span>Deny all</span>
|
@if (Model.AuthenticationAttempts.Count > 1)
|
||||||
</button>
|
{
|
||||||
</a>
|
<a href="/authentication/denyAll">
|
||||||
|
<button class="ui red button">
|
||||||
|
<i class="x icon"></i>
|
||||||
|
<span>Deny all</span>
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
|
||||||
@foreach (AuthenticationAttempt authAttempt in Model.AuthenticationAttempts)
|
@foreach (AuthenticationAttempt authAttempt in Model.AuthenticationAttempts)
|
||||||
{
|
{
|
||||||
|
@ -41,19 +47,19 @@ else
|
||||||
<p>A <b>@authAttempt.Platform</b> authentication request was logged at <b>@timestamp.ToString("MM/dd/yyyy @ h:mm tt") UTC</b> from the IP address <b>@authAttempt.IPAddress</b>.</p>
|
<p>A <b>@authAttempt.Platform</b> authentication request was logged at <b>@timestamp.ToString("MM/dd/yyyy @ h:mm tt") UTC</b> from the IP address <b>@authAttempt.IPAddress</b>.</p>
|
||||||
<div>
|
<div>
|
||||||
<a href="/authentication/autoApprove/@authAttempt.AuthenticationAttemptId">
|
<a href="/authentication/autoApprove/@authAttempt.AuthenticationAttemptId">
|
||||||
<button class="ui tiny green button">
|
<button class="ui small green button">
|
||||||
<i class="check icon"></i>
|
<i class="check icon"></i>
|
||||||
<span>Automatically approve every time</span>
|
<span>Automatically approve every time</span>
|
||||||
</button>
|
</button>
|
||||||
</a>
|
</a>
|
||||||
<a href="/authentication/approve/@authAttempt.AuthenticationAttemptId">
|
<a href="/authentication/approve/@authAttempt.AuthenticationAttemptId">
|
||||||
<button class="ui tiny yellow button">
|
<button class="ui small yellow button">
|
||||||
<i class="check icon"></i>
|
<i class="check icon"></i>
|
||||||
<span>Approve this time</span>
|
<span>Approve this time</span>
|
||||||
</button>
|
</button>
|
||||||
</a>
|
</a>
|
||||||
<a href="/authentication/deny/@authAttempt.AuthenticationAttemptId">
|
<a href="/authentication/deny/@authAttempt.AuthenticationAttemptId">
|
||||||
<button class="ui tiny red button">
|
<button class="ui small red button">
|
||||||
<i class="x icon"></i>
|
<i class="x icon"></i>
|
||||||
<span>Deny</span>
|
<span>Deny</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
@page "/authentication/autoApprovals"
|
|
||||||
@using LBPUnion.ProjectLighthouse.PlayerData.Profiles
|
|
||||||
@using LBPUnion.ProjectLighthouse.Types
|
|
||||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.ExternalAuth.ManageUserApprovedIpAddressesPage
|
|
||||||
|
|
||||||
@{
|
|
||||||
Layout = "Layouts/BaseLayout";
|
|
||||||
Model.Title = "Automatically approved IP addresses";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@foreach (UserApprovedIpAddress approvedIpAddress in Model.ApprovedIpAddresses)
|
|
||||||
{
|
|
||||||
<div class="ui blue segment">
|
|
||||||
<p>@approvedIpAddress.IpAddress</p>
|
|
||||||
<a href="/authentication/revokeAutoApproval/@approvedIpAddress.UserApprovedIpAddressId">
|
|
||||||
<button class="ui red button">
|
|
||||||
<i class="trash icon"></i>
|
|
||||||
<span>Revoke</span>
|
|
||||||
</button>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
#nullable enable
|
|
||||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
|
||||||
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
|
||||||
using LBPUnion.ProjectLighthouse.Types;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.ExternalAuth;
|
|
||||||
|
|
||||||
public class ManageUserApprovedIpAddressesPage : BaseLayout
|
|
||||||
{
|
|
||||||
public List<UserApprovedIpAddress> ApprovedIpAddresses = new();
|
|
||||||
|
|
||||||
public ManageUserApprovedIpAddressesPage(Database database) : base(database)
|
|
||||||
{}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnGet()
|
|
||||||
{
|
|
||||||
User? user = this.Database.UserFromWebRequest(this.Request);
|
|
||||||
if (user == null) return this.Redirect("/login");
|
|
||||||
|
|
||||||
this.ApprovedIpAddresses = await this.Database.UserApprovedIpAddresses.Where(a => a.UserId == user.UserId).ToListAsync();
|
|
||||||
|
|
||||||
return this.Page();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -45,7 +45,7 @@ else
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<br>
|
<br><br>
|
||||||
|
|
||||||
<div class="@(isMobile ? "" : "ui center aligned grid")">
|
<div class="@(isMobile ? "" : "ui center aligned grid")">
|
||||||
<div class="eight wide column">
|
<div class="eight wide column">
|
||||||
|
|
|
@ -26,20 +26,23 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
Model.IsMobile = Model.Request.IsMobile();
|
Model.IsMobile = Model.Request.IsMobile();
|
||||||
|
|
||||||
|
string title;
|
||||||
|
if (Model.Title == string.Empty)
|
||||||
|
{
|
||||||
|
title = ServerConfiguration.Instance.Customization.ServerName;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
title = $"{Model.Title} - {ServerConfiguration.Instance.Customization.ServerName}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
@if (Model.Title == string.Empty)
|
<title>@title</title>
|
||||||
{
|
|
||||||
<title>@ServerConfiguration.Instance.Customization.ServerName</title>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<title>@ServerConfiguration.Instance.Customization.ServerName - @Model.Title</title>
|
|
||||||
}
|
|
||||||
<link rel="stylesheet" type="text/css" href="~/css/styles.css">
|
<link rel="stylesheet" type="text/css" href="~/css/styles.css">
|
||||||
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/fomantic-ui@2.8.8/dist/semantic.min.css">
|
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/fomantic-ui@2.8.8/dist/semantic.min.css">
|
||||||
|
|
||||||
|
@ -53,7 +56,7 @@
|
||||||
|
|
||||||
@* Embed Stuff *@
|
@* Embed Stuff *@
|
||||||
<meta name="theme-color" data-react-helmet="true" content="#008cff">
|
<meta name="theme-color" data-react-helmet="true" content="#008cff">
|
||||||
<meta content="@ServerConfiguration.Instance.Customization.ServerName - @Model.Title" property="og:title">
|
<meta content="@title" property="og:title">
|
||||||
@if (!string.IsNullOrEmpty(Model.Description))
|
@if (!string.IsNullOrEmpty(Model.Description))
|
||||||
{
|
{
|
||||||
<meta content="@Model.Description" property="og:description">
|
<meta content="@Model.Description" property="og:description">
|
||||||
|
|
|
@ -11,11 +11,16 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function onSubmit(form) {
|
function onSubmit(form) {
|
||||||
|
if (document.referrer !== null && document.referrer !== "") {
|
||||||
|
const url = new URL(document.referrer);
|
||||||
|
if (url.pathname !== "/logout" && url.pathname !== "/login")
|
||||||
|
document.getElementById("redirect").value = url.pathname;
|
||||||
|
}
|
||||||
|
|
||||||
const passwordInput = document.getElementById("password");
|
const passwordInput = document.getElementById("password");
|
||||||
const passwordSubmit = document.getElementById("password-submit");
|
const passwordSubmit = document.getElementById("password-submit");
|
||||||
|
|
||||||
passwordSubmit.value = sha256(passwordInput.value);
|
passwordSubmit.value = sha256(passwordInput.value);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -32,6 +37,7 @@
|
||||||
|
|
||||||
<form class="ui form" onsubmit="return onSubmit(this)" method="post">
|
<form class="ui form" onsubmit="return onSubmit(this)" method="post">
|
||||||
@Html.AntiForgeryToken()
|
@Html.AntiForgeryToken()
|
||||||
|
<input type="hidden" id="redirect" name="redirect">
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>Username</label>
|
<label>Username</label>
|
||||||
|
@ -54,15 +60,23 @@
|
||||||
{
|
{
|
||||||
@await Html.PartialAsync("Partials/CaptchaPartial")
|
@await Html.PartialAsync("Partials/CaptchaPartial")
|
||||||
}
|
}
|
||||||
|
|
||||||
<input type="submit" value="Log in" id="submit" class="ui blue button">
|
<div class="row">
|
||||||
@if (ServerConfiguration.Instance.Authentication.RegistrationEnabled)
|
<input type="submit" value="Log in" id="submit" class="ui blue button">
|
||||||
{
|
@if (ServerConfiguration.Instance.Authentication.RegistrationEnabled)
|
||||||
<a href="/register">
|
{
|
||||||
|
<a href="/register">
|
||||||
|
<div class="ui button">
|
||||||
|
<i class="user alternate add icon"></i>
|
||||||
|
Register
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<a href="/passwordResetRequest">
|
||||||
<div class="ui button">
|
<div class="ui button">
|
||||||
<i class="user alternate add icon"></i>
|
Forgot Password?
|
||||||
Register
|
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
}
|
|
||||||
</form>
|
</form>
|
|
@ -22,7 +22,7 @@ public class LoginForm : BaseLayout
|
||||||
public string? Error { get; private set; }
|
public string? Error { get; private set; }
|
||||||
|
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public async Task<IActionResult> OnPost(string username, string password)
|
public async Task<IActionResult> OnPost(string username, string password, string redirect)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(username))
|
if (string.IsNullOrWhiteSpace(username))
|
||||||
{
|
{
|
||||||
|
@ -105,9 +105,19 @@ public class LoginForm : BaseLayout
|
||||||
if (user.PasswordResetRequired) return this.Redirect("~/passwordResetRequired");
|
if (user.PasswordResetRequired) return this.Redirect("~/passwordResetRequired");
|
||||||
if (ServerConfiguration.Instance.Mail.MailEnabled && !user.EmailAddressVerified) return this.Redirect("~/login/sendVerificationEmail");
|
if (ServerConfiguration.Instance.Mail.MailEnabled && !user.EmailAddressVerified) return this.Redirect("~/login/sendVerificationEmail");
|
||||||
|
|
||||||
return this.RedirectToPage(nameof(LandingPage));
|
if (string.IsNullOrWhiteSpace(redirect))
|
||||||
|
{
|
||||||
|
return this.RedirectToPage(nameof(LandingPage));
|
||||||
|
}
|
||||||
|
return this.Redirect(redirect);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public IActionResult OnGet() => this.Page();
|
public IActionResult OnGet()
|
||||||
|
{
|
||||||
|
if (this.Database.UserFromWebRequest(this.Request) != null)
|
||||||
|
return this.RedirectToPage(nameof(LandingPage));
|
||||||
|
|
||||||
|
return this.Page();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,17 @@
|
||||||
@using LBPUnion.ProjectLighthouse.Configuration
|
@using LBPUnion.ProjectLighthouse.Configuration
|
||||||
@if (ServerConfiguration.Instance.Captcha.CaptchaEnabled)
|
@if (ServerConfiguration.Instance.Captcha.CaptchaEnabled)
|
||||||
{
|
{
|
||||||
<div class="h-captcha" data-sitekey="@ServerConfiguration.Instance.Captcha.SiteKey"></div>
|
@switch (ServerConfiguration.Instance.Captcha.Type)
|
||||||
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
|
{
|
||||||
|
case CaptchaType.HCaptcha:
|
||||||
|
<div class="h-captcha" data-sitekey="@ServerConfiguration.Instance.Captcha.SiteKey"></div>
|
||||||
|
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
|
||||||
|
break;
|
||||||
|
case CaptchaType.ReCaptcha:
|
||||||
|
<div class="g-recaptcha" data-sitekey="@ServerConfiguration.Instance.Captcha.SiteKey"></div>
|
||||||
|
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
@using System.Web
|
@using System.Web
|
||||||
|
@using System.IO
|
||||||
@using LBPUnion.ProjectLighthouse.PlayerData.Profiles
|
@using LBPUnion.ProjectLighthouse.PlayerData.Profiles
|
||||||
<div class="ui yellow segment" id="comments">
|
<div class="ui yellow segment" id="comments">
|
||||||
<h2>Comments</h2>
|
<h2>Comments</h2>
|
||||||
|
@ -47,7 +48,14 @@
|
||||||
int rating = comment.ThumbsUp - comment.ThumbsDown;
|
int rating = comment.ThumbsUp - comment.ThumbsDown;
|
||||||
|
|
||||||
<div style="display: flex" id="@comment.CommentId">
|
<div style="display: flex" id="@comment.CommentId">
|
||||||
<div class="voting">
|
@{
|
||||||
|
string style = "";
|
||||||
|
if (Model.User?.UserId == comment.PosterUserId)
|
||||||
|
{
|
||||||
|
style = "visibility: hidden";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<div class="voting" style="@(style)">
|
||||||
<a href="@url/rateComment?commentId=@(comment.CommentId)&rating=@(comment.YourThumb == 1 ? 0 : 1)">
|
<a href="@url/rateComment?commentId=@(comment.CommentId)&rating=@(comment.YourThumb == 1 ? 0 : 1)">
|
||||||
<i class="fitted @(comment.YourThumb == 1 ? "green" : "grey") arrow up link icon" style="display: block"></i>
|
<i class="fitted @(comment.YourThumb == 1 ? "green" : "grey") arrow up link icon" style="display: block"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
@using System.Web
|
||||||
@using LBPUnion.ProjectLighthouse
|
@using LBPUnion.ProjectLighthouse
|
||||||
@using LBPUnion.ProjectLighthouse.Configuration
|
@using LBPUnion.ProjectLighthouse.Configuration
|
||||||
@using LBPUnion.ProjectLighthouse.PlayerData
|
@using LBPUnion.ProjectLighthouse.PlayerData
|
||||||
|
@ -10,8 +11,8 @@
|
||||||
|
|
||||||
await using Database database = new();
|
await using Database database = new();
|
||||||
|
|
||||||
string slotName = string.IsNullOrEmpty(Model.Name) ? "Unnamed Level" : Model.Name;
|
string slotName = HttpUtility.HtmlDecode(string.IsNullOrEmpty(Model!.Name) ? "Unnamed Level" : Model.Name);
|
||||||
|
|
||||||
bool isMobile = (bool?)ViewData["IsMobile"] ?? false;
|
bool isMobile = (bool?)ViewData["IsMobile"] ?? false;
|
||||||
bool mini = (bool?)ViewData["IsMini"] ?? false;
|
bool mini = (bool?)ViewData["IsMini"] ?? false;
|
||||||
|
|
||||||
|
@ -36,7 +37,9 @@
|
||||||
}
|
}
|
||||||
<div>
|
<div>
|
||||||
<img src="~/assets/slotCardOverlay.png" style="min-width: @(size)px; width: @(size)px; height: @(size)px; pointer-events: none; position: absolute">
|
<img src="~/assets/slotCardOverlay.png" style="min-width: @(size)px; width: @(size)px; height: @(size)px; pointer-events: none; position: absolute">
|
||||||
<img class="cardIcon slotCardIcon" src="/gameAssets/@iconHash" style="min-width: @(size)px; width: @(size)px; height: @(size)px">
|
<img src="~/assets/slotCardBackground.png" style="min-width: @(size)px; width: @(size)px; height: @(size)px; position: absolute; z-index: -1;">
|
||||||
|
<img class="cardIcon slotCardIcon" src="/gameAssets/@iconHash" style="min-width: @(size)px; width: @(size)px; height: @(size)px;"
|
||||||
|
onerror="this.onerror='';this.src='/gameAssets/@ServerConfiguration.Instance.WebsiteConfiguration.MissingIconHash'">
|
||||||
</div>
|
</div>
|
||||||
<div class="cardStats">
|
<div class="cardStats">
|
||||||
@if (!mini)
|
@if (!mini)
|
||||||
|
@ -79,7 +82,7 @@
|
||||||
@if (Model.GameVersion == GameVersion.LittleBigPlanet1)
|
@if (Model.GameVersion == GameVersion.LittleBigPlanet1)
|
||||||
{
|
{
|
||||||
<i class="yellow star icon" title="LBP1 Stars"></i>
|
<i class="yellow star icon" title="LBP1 Stars"></i>
|
||||||
<span>@Model.RatingLBP1</span>
|
<span>@(Math.Round(Model.RatingLBP1 * 10) / 10)</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
|
|
|
@ -4,7 +4,6 @@ using LBPUnion.ProjectLighthouse.Configuration;
|
||||||
using LBPUnion.ProjectLighthouse.Helpers;
|
using LBPUnion.ProjectLighthouse.Helpers;
|
||||||
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||||
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||||
using LBPUnion.ProjectLighthouse.Types;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
||||||
|
@ -19,8 +18,21 @@ public class PasswordResetPage : BaseLayout
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public async Task<IActionResult> OnPost(string password, string confirmPassword)
|
public async Task<IActionResult> OnPost(string password, string confirmPassword)
|
||||||
{
|
{
|
||||||
User? user = this.Database.UserFromWebRequest(this.Request);
|
User? user;
|
||||||
if (user == null) return this.Redirect("~/login");
|
if (Request.Query.ContainsKey("token"))
|
||||||
|
{
|
||||||
|
user = await this.Database.UserFromPasswordResetToken(Request.Query["token"][0]);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
this.Error = "This password reset link either is invalid or has expired. Please try again.";
|
||||||
|
return this.Page();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
user = this.Database.UserFromWebRequest(this.Request);
|
||||||
|
if (user == null) return this.Redirect("~/login");
|
||||||
|
}
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(password))
|
if (string.IsNullOrWhiteSpace(password))
|
||||||
{
|
{
|
||||||
|
@ -48,6 +60,8 @@ public class PasswordResetPage : BaseLayout
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public IActionResult OnGet()
|
public IActionResult OnGet()
|
||||||
{
|
{
|
||||||
|
if (this.Request.Query.ContainsKey("token")) return this.Page();
|
||||||
|
|
||||||
User? user = this.Database.UserFromWebRequest(this.Request);
|
User? user = this.Database.UserFromWebRequest(this.Request);
|
||||||
if (user == null) return this.Redirect("~/login");
|
if (user == null) return this.Redirect("~/login");
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
@page "/passwordResetRequest"
|
||||||
|
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.PasswordResetRequestForm
|
||||||
|
|
||||||
|
@{
|
||||||
|
Layout = "Layouts/BaseLayout";
|
||||||
|
Model.Title = "Password Reset";
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (!string.IsNullOrWhiteSpace(Model.Error))
|
||||||
|
{
|
||||||
|
<div class="ui negative message">
|
||||||
|
<div class="header">
|
||||||
|
Uh oh!
|
||||||
|
</div>
|
||||||
|
<p style="white-space: pre-line">@Model.Error</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (!string.IsNullOrWhiteSpace(Model.Status))
|
||||||
|
{
|
||||||
|
<div class="ui positive message">
|
||||||
|
<div class="header">
|
||||||
|
Success!
|
||||||
|
</div>
|
||||||
|
<p style="white-space: pre-line">@Model.Status</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<form class="ui form" method="post">
|
||||||
|
@Html.AntiForgeryToken()
|
||||||
|
|
||||||
|
<input type="text" autocomplete="no" id="username" placeholder="Username" name="username"/><br/><br/>
|
||||||
|
<input type="submit" value="Request Password Reset" class="ui blue button"/>
|
||||||
|
</form>
|
|
@ -0,0 +1,67 @@
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using LBPUnion.ProjectLighthouse.Configuration;
|
||||||
|
using LBPUnion.ProjectLighthouse.Helpers;
|
||||||
|
using LBPUnion.ProjectLighthouse.PlayerData;
|
||||||
|
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
||||||
|
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
||||||
|
|
||||||
|
public class PasswordResetRequestForm : BaseLayout
|
||||||
|
{
|
||||||
|
|
||||||
|
public string? Error { get; private set; }
|
||||||
|
|
||||||
|
public string? Status { get; private set; }
|
||||||
|
|
||||||
|
public PasswordResetRequestForm(Database database) : base(database)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
[UsedImplicitly]
|
||||||
|
public async Task<IActionResult> OnPost(string username)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!ServerConfiguration.Instance.Mail.MailEnabled)
|
||||||
|
{
|
||||||
|
this.Error = "Email is not configured on this server, so password resets cannot be issued. Please contact your instance administrator for more details.";
|
||||||
|
return this.Page();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(username))
|
||||||
|
{
|
||||||
|
this.Error = "The username field is required.";
|
||||||
|
return this.Page();
|
||||||
|
}
|
||||||
|
|
||||||
|
User? user = await this.Database.Users.FirstOrDefaultAsync(u => u.Username == username);
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
this.Error = "User does not exist.";
|
||||||
|
return this.Page();
|
||||||
|
}
|
||||||
|
|
||||||
|
PasswordResetToken token = new()
|
||||||
|
{
|
||||||
|
Created = DateTime.Now,
|
||||||
|
UserId = user.UserId,
|
||||||
|
ResetToken = CryptoHelper.GenerateAuthToken(),
|
||||||
|
};
|
||||||
|
|
||||||
|
string messageBody = $"Hello, {user.Username}.\n\n" +
|
||||||
|
"A request to reset your account's password was issued. If this wasn't you, this can probably be ignored.\n\n" +
|
||||||
|
$"If this was you, your {ServerConfiguration.Instance.Customization.ServerName} password can be reset at the following link:\n" +
|
||||||
|
$"{ServerConfiguration.Instance.ExternalUrl}/passwordReset?token={token.ResetToken}";
|
||||||
|
|
||||||
|
SMTPHelper.SendEmail(user.EmailAddress, $"Project Lighthouse Password Reset Request for {user.Username}", messageBody);
|
||||||
|
|
||||||
|
this.Database.PasswordResetTokens.Add(token);
|
||||||
|
await this.Database.SaveChangesAsync();
|
||||||
|
|
||||||
|
this.Status = $"Password reset email sent to {CensorHelper.MaskEmail(user.EmailAddress)}.";
|
||||||
|
return this.Page();
|
||||||
|
}
|
||||||
|
public void OnGet() => this.Page();
|
||||||
|
}
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
<form action="/photos/0">
|
<form action="/photos/0">
|
||||||
<div class="ui icon input">
|
<div class="ui icon input">
|
||||||
<input type="text" name="name" placeholder="Search photos..." value="@Model.SearchValue">
|
<input type="text" autocomplete="off" name="name" placeholder="Search photos..." value="@Model.SearchValue">
|
||||||
<i class="search icon"></i>
|
<i class="search icon"></i>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -15,7 +15,7 @@ namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
|
||||||
public class RegisterForm : BaseLayout
|
public class RegisterForm : BaseLayout
|
||||||
{
|
{
|
||||||
public RegisterForm(Database database) : base(database)
|
public RegisterForm(Database database) : base(database)
|
||||||
{}
|
{ }
|
||||||
|
|
||||||
public string? Error { get; private set; }
|
public string? Error { get; private set; }
|
||||||
|
|
||||||
|
@ -23,7 +23,22 @@ public class RegisterForm : BaseLayout
|
||||||
[SuppressMessage("ReSharper", "SpecifyStringComparison")]
|
[SuppressMessage("ReSharper", "SpecifyStringComparison")]
|
||||||
public async Task<IActionResult> OnPost(string username, string password, string confirmPassword, string emailAddress)
|
public async Task<IActionResult> OnPost(string username, string password, string confirmPassword, string emailAddress)
|
||||||
{
|
{
|
||||||
if (!ServerConfiguration.Instance.Authentication.RegistrationEnabled) return this.NotFound();
|
if (ServerConfiguration.Instance.Authentication.PrivateRegistration)
|
||||||
|
{
|
||||||
|
if (this.Request.Query.ContainsKey("token"))
|
||||||
|
{
|
||||||
|
if (!this.Database.IsRegistrationTokenValid(this.Request.Query["token"]))
|
||||||
|
return this.StatusCode(403, "Invalid Token");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return this.NotFound();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!ServerConfiguration.Instance.Authentication.RegistrationEnabled)
|
||||||
|
{
|
||||||
|
return this.NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(username))
|
if (string.IsNullOrWhiteSpace(username))
|
||||||
{
|
{
|
||||||
|
@ -68,6 +83,11 @@ public class RegisterForm : BaseLayout
|
||||||
return this.Page();
|
return this.Page();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.Request.Query.ContainsKey("token"))
|
||||||
|
{
|
||||||
|
await Database.RemoveRegistrationToken(this.Request.Query["token"]);
|
||||||
|
}
|
||||||
|
|
||||||
User user = await this.Database.CreateUser(username, CryptoHelper.BCryptHash(password), emailAddress);
|
User user = await this.Database.CreateUser(username, CryptoHelper.BCryptHash(password), emailAddress);
|
||||||
|
|
||||||
WebToken webToken = new()
|
WebToken webToken = new()
|
||||||
|
@ -91,7 +111,22 @@ public class RegisterForm : BaseLayout
|
||||||
public IActionResult OnGet()
|
public IActionResult OnGet()
|
||||||
{
|
{
|
||||||
this.Error = string.Empty;
|
this.Error = string.Empty;
|
||||||
if (!ServerConfiguration.Instance.Authentication.RegistrationEnabled) return this.NotFound();
|
if (ServerConfiguration.Instance.Authentication.PrivateRegistration)
|
||||||
|
{
|
||||||
|
if (this.Request.Query.ContainsKey("token"))
|
||||||
|
{
|
||||||
|
if (!this.Database.IsRegistrationTokenValid(this.Request.Query["token"]))
|
||||||
|
return this.StatusCode(403, "Invalid Token");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return this.NotFound();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!ServerConfiguration.Instance.Authentication.RegistrationEnabled)
|
||||||
|
{
|
||||||
|
return this.NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
return this.Page();
|
return this.Page();
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
<form action="/admin/reports/0">
|
<form action="/admin/reports/0">
|
||||||
<div class="ui icon input">
|
<div class="ui icon input">
|
||||||
<input type="text" name="name" placeholder="Search reports..." value="@Model.SearchValue">
|
<input type="text" autocomplete="off" name="name" placeholder="Search reports..." value="@Model.SearchValue">
|
||||||
<i class="search icon"></i>
|
<i class="search icon"></i>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -4,15 +4,14 @@
|
||||||
@using LBPUnion.ProjectLighthouse.Configuration
|
@using LBPUnion.ProjectLighthouse.Configuration
|
||||||
@using LBPUnion.ProjectLighthouse.Extensions
|
@using LBPUnion.ProjectLighthouse.Extensions
|
||||||
@using LBPUnion.ProjectLighthouse.PlayerData.Reviews
|
@using LBPUnion.ProjectLighthouse.PlayerData.Reviews
|
||||||
@using LBPUnion.ProjectLighthouse.Types
|
|
||||||
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.SlotPage
|
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.SlotPage
|
||||||
|
|
||||||
@{
|
@{
|
||||||
Layout = "Layouts/BaseLayout";
|
Layout = "Layouts/BaseLayout";
|
||||||
Model.ShowTitleInPage = false;
|
Model.ShowTitleInPage = false;
|
||||||
|
|
||||||
Model.Title = Model.Slot?.Name ?? "";
|
Model.Title = HttpUtility.HtmlDecode(Model.Slot?.Name ?? "");
|
||||||
Model.Description = Model.Slot?.Description ?? "";
|
Model.Description = HttpUtility.HtmlDecode(Model.Slot?.Description ?? "");
|
||||||
|
|
||||||
bool isMobile = this.Request.IsMobile();
|
bool isMobile = this.Request.IsMobile();
|
||||||
}
|
}
|
||||||
|
@ -38,15 +37,14 @@
|
||||||
<div class="eight wide column">
|
<div class="eight wide column">
|
||||||
<div class="ui blue segment">
|
<div class="ui blue segment">
|
||||||
<h2>Description</h2>
|
<h2>Description</h2>
|
||||||
<p>@HttpUtility.HtmlDecode(string.IsNullOrEmpty(Model.Slot?.Description) ? "This level has no description." : Model.Slot.Description)</p>
|
<p style="overflow-wrap: anywhere">@HttpUtility.HtmlDecode(string.IsNullOrEmpty(Model.Slot?.Description) ? "This level has no description." : Model.Slot.Description)</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="eight wide column">
|
<div class="eight wide column">
|
||||||
<div class="ui red segment">
|
<div class="ui red segment">
|
||||||
<h2>Tags</h2>
|
<h2>Tags</h2>
|
||||||
@{
|
@{
|
||||||
string[] authorLabels = Model.Slot?.AuthorLabels.Split(",") ?? new string[]
|
string[] authorLabels = Model.Slot?.AuthorLabels.Split(",") ?? new string[]{};
|
||||||
{};
|
|
||||||
if (authorLabels.Length == 1) // ..?? ok c#
|
if (authorLabels.Length == 1) // ..?? ok c#
|
||||||
{
|
{
|
||||||
<p>This level has no tags.</p>
|
<p>This level has no tags.</p>
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
<form action="/slots/0">
|
<form action="/slots/0">
|
||||||
<div class="ui icon input">
|
<div class="ui icon input">
|
||||||
<input type="text" name="name" placeholder="Search levels..." value="@Model.SearchValue">
|
<input type="text" autocomplete="off" name="name" placeholder="Search levels..." value="@Model.SearchValue">
|
||||||
<i class="search icon"></i>
|
<i class="search icon"></i>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
<form action="/users/0">
|
<form action="/users/0">
|
||||||
<div class="ui icon input">
|
<div class="ui icon input">
|
||||||
<input type="text" name="name" placeholder="Search users..." value="@Model.SearchValue">
|
<input type="text" autocomplete="off" name="name" placeholder="Search users..." value="@Model.SearchValue">
|
||||||
<i class="search icon"></i>
|
<i class="search icon"></i>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="6.0.5" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="6.0.7" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
||||||
|
|
|
@ -58,7 +58,10 @@ public class WebsiteStartup
|
||||||
|
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
|
|
||||||
app.UseStaticFiles();
|
app.UseStaticFiles(new StaticFileOptions
|
||||||
|
{
|
||||||
|
ServeUnknownFileTypes = true,
|
||||||
|
});
|
||||||
|
|
||||||
app.UseRequestLocalization();
|
app.UseRequestLocalization();
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="JetBrains.Annotations" Version="2022.1.0" />
|
<PackageReference Include="JetBrains.Annotations" Version="2022.1.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.5" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.7" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.5">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.7">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|
|
@ -9,14 +9,14 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="JetBrains.Annotations" Version="2022.1.0" />
|
<PackageReference Include="JetBrains.Annotations" Version="2022.1.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.5" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.7" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.5">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.7">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||||
<PackageReference Include="Selenium.WebDriver" Version="4.2.0" />
|
<PackageReference Include="Selenium.WebDriver" Version="4.3.0" />
|
||||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="101.0.4951.4100" />
|
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="103.0.5060.13400" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
|
|
@ -14,8 +14,8 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="JetBrains.Annotations" Version="2022.1.0" />
|
<PackageReference Include="JetBrains.Annotations" Version="2022.1.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.5" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.7" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.5">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.7">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using LBPUnion.ProjectLighthouse.Logging;
|
||||||
|
using LBPUnion.ProjectLighthouse.PlayerData;
|
||||||
|
using LBPUnion.ProjectLighthouse.Helpers;
|
||||||
|
|
||||||
|
namespace LBPUnion.ProjectLighthouse.Administration.Maintenance.Commands
|
||||||
|
{
|
||||||
|
public class CreateAPIKeyCommand : ICommand
|
||||||
|
{
|
||||||
|
public string Name() => "Create API Key";
|
||||||
|
public string[] Aliases() => new[] { "createAPIKey", };
|
||||||
|
public string Arguments() => "<description>";
|
||||||
|
public int RequiredArgs() => 1;
|
||||||
|
|
||||||
|
public async Task Run(string[] args, Logger logger)
|
||||||
|
{
|
||||||
|
APIKey key = new();
|
||||||
|
key.Description = args[0];
|
||||||
|
if (string.IsNullOrWhiteSpace(key.Description))
|
||||||
|
{
|
||||||
|
key.Description = "<no description specified>";
|
||||||
|
}
|
||||||
|
key.Key = CryptoHelper.GenerateAuthToken();
|
||||||
|
key.Created = DateTime.Now;
|
||||||
|
Database database = new();
|
||||||
|
await database.APIKeys.AddAsync(key);
|
||||||
|
await database.SaveChangesAsync();
|
||||||
|
logger.LogSuccess($"The API key has been created (id: {key.Id}), however for security the token will only be shown once.", LogArea.Command);
|
||||||
|
logger.LogInfo($"Key: {key.Key}", LogArea.Command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
16
ProjectLighthouse/Configuration/CaptchaType.cs
Normal file
16
ProjectLighthouse/Configuration/CaptchaType.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
namespace LBPUnion.ProjectLighthouse.Configuration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The service to be used for presenting captchas to the user.
|
||||||
|
/// </summary>
|
||||||
|
public enum CaptchaType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A privacy-first captcha. https://www.hcaptcha.com/
|
||||||
|
/// </summary>
|
||||||
|
HCaptcha,
|
||||||
|
/// <summary>
|
||||||
|
/// A captcha service by Google. https://developers.google.com/recaptcha/
|
||||||
|
/// </summary>
|
||||||
|
ReCaptcha,
|
||||||
|
}
|
|
@ -8,5 +8,6 @@ public class AuthenticationConfiguration
|
||||||
public bool BlockDeniedUsers { get; set; }
|
public bool BlockDeniedUsers { get; set; }
|
||||||
|
|
||||||
public bool RegistrationEnabled { get; set; } = true;
|
public bool RegistrationEnabled { get; set; } = true;
|
||||||
|
public bool PrivateRegistration { get; set; } = false;
|
||||||
public bool UseExternalAuth { get; set; }
|
public bool UseExternalAuth { get; set; }
|
||||||
}
|
}
|
|
@ -2,11 +2,10 @@ namespace LBPUnion.ProjectLighthouse.Configuration.ConfigurationCategories;
|
||||||
|
|
||||||
public class CaptchaConfiguration
|
public class CaptchaConfiguration
|
||||||
{
|
{
|
||||||
// TODO: support recaptcha, not just hcaptcha
|
|
||||||
// use an enum to define which captcha services can be used?
|
|
||||||
// LBPUnion.ProjectLighthouse.Types.Settings.CaptchaService
|
|
||||||
public bool CaptchaEnabled { get; set; }
|
public bool CaptchaEnabled { get; set; }
|
||||||
|
|
||||||
|
public CaptchaType Type { get; set; } = CaptchaType.HCaptcha;
|
||||||
|
|
||||||
public string SiteKey { get; set; } = "";
|
public string SiteKey { get; set; } = "";
|
||||||
|
|
||||||
public string Secret { get; set; } = "";
|
public string Secret { get; set; } = "";
|
||||||
|
|
|
@ -23,7 +23,7 @@ public class ServerConfiguration
|
||||||
// You can use an ObsoleteAttribute instead. Make sure you set it to error, though.
|
// You can use an ObsoleteAttribute instead. Make sure you set it to error, though.
|
||||||
//
|
//
|
||||||
// Thanks for listening~
|
// Thanks for listening~
|
||||||
public const int CurrentConfigVersion = 5;
|
public const int CurrentConfigVersion = 7;
|
||||||
|
|
||||||
#region Meta
|
#region Meta
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ public class ServerConfiguration
|
||||||
private static FileSystemWatcher fileWatcher;
|
private static FileSystemWatcher fileWatcher;
|
||||||
|
|
||||||
// ReSharper disable once NotNullMemberIsNotInitialized
|
// ReSharper disable once NotNullMemberIsNotInitialized
|
||||||
#pragma warning disable CS8618
|
#pragma warning disable CS8618
|
||||||
static ServerConfiguration()
|
static ServerConfiguration()
|
||||||
{
|
{
|
||||||
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
|
||||||
|
@ -54,7 +54,7 @@ public class ServerConfiguration
|
||||||
// If a valid YML configuration is available!
|
// If a valid YML configuration is available!
|
||||||
if (File.Exists(ConfigFileName) && (tempConfig = fromFile(ConfigFileName)) != null)
|
if (File.Exists(ConfigFileName) && (tempConfig = fromFile(ConfigFileName)) != null)
|
||||||
{
|
{
|
||||||
// Instance = JsonSerializer.Deserialize<ServerConfiguration>(configFile) ?? throw new ArgumentNullException(nameof(ConfigFileName));
|
// Instance = JsonSerializer.Deserialize<ServerConfiguration>(configFile) ?? throw new ArgumentNullException(nameof(ConfigFileName));
|
||||||
Instance = tempConfig;
|
Instance = tempConfig;
|
||||||
|
|
||||||
if (Instance.ConfigVersion < CurrentConfigVersion)
|
if (Instance.ConfigVersion < CurrentConfigVersion)
|
||||||
|
@ -114,7 +114,7 @@ public class ServerConfiguration
|
||||||
fileWatcher.EnableRaisingEvents = true; // begin watching
|
fileWatcher.EnableRaisingEvents = true; // begin watching
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#pragma warning restore CS8618
|
#pragma warning restore CS8618
|
||||||
|
|
||||||
private static void onConfigChanged(object sender, FileSystemEventArgs e)
|
private static void onConfigChanged(object sender, FileSystemEventArgs e)
|
||||||
{
|
{
|
||||||
|
@ -178,11 +178,11 @@ public class ServerConfiguration
|
||||||
public string ExternalUrl { get; set; } = "http://localhost:10060";
|
public string ExternalUrl { get; set; } = "http://localhost:10060";
|
||||||
public bool ConfigReloading { get; set; }
|
public bool ConfigReloading { get; set; }
|
||||||
public string EulaText { get; set; } = "";
|
public string EulaText { get; set; } = "";
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
public string AnnounceText { get; set; } = "You are now logged in as %user.";
|
public string AnnounceText { get; set; } = "You are now logged in as %user.";
|
||||||
#else
|
#else
|
||||||
public string AnnounceText { get; set; } = "You are now logged in as %user (id: %id).";
|
public string AnnounceText { get; set; } = "You are now logged in as %user (id: %id).";
|
||||||
#endif
|
#endif
|
||||||
public bool CheckForUnsafeFiles { get; set; } = true;
|
public bool CheckForUnsafeFiles { get; set; } = true;
|
||||||
|
|
||||||
public FilterMode UserInputFilterMode { get; set; } = FilterMode.None;
|
public FilterMode UserInputFilterMode { get; set; } = FilterMode.None;
|
||||||
|
|
|
@ -41,17 +41,19 @@ public class Database : DbContext
|
||||||
public DbSet<AuthenticationAttempt> AuthenticationAttempts { get; set; }
|
public DbSet<AuthenticationAttempt> AuthenticationAttempts { get; set; }
|
||||||
public DbSet<Review> Reviews { get; set; }
|
public DbSet<Review> Reviews { get; set; }
|
||||||
public DbSet<RatedReview> RatedReviews { get; set; }
|
public DbSet<RatedReview> RatedReviews { get; set; }
|
||||||
public DbSet<UserApprovedIpAddress> UserApprovedIpAddresses { get; set; }
|
|
||||||
public DbSet<DatabaseCategory> CustomCategories { get; set; }
|
public DbSet<DatabaseCategory> CustomCategories { get; set; }
|
||||||
public DbSet<Reaction> Reactions { get; set; }
|
public DbSet<Reaction> Reactions { get; set; }
|
||||||
public DbSet<GriefReport> Reports { get; set; }
|
public DbSet<GriefReport> Reports { get; set; }
|
||||||
public DbSet<EmailVerificationToken> EmailVerificationTokens { get; set; }
|
public DbSet<EmailVerificationToken> EmailVerificationTokens { get; set; }
|
||||||
public DbSet<EmailSetToken> EmailSetTokens { get; set; }
|
public DbSet<EmailSetToken> EmailSetTokens { get; set; }
|
||||||
|
public DbSet<PasswordResetToken> PasswordResetTokens { get; set; }
|
||||||
|
public DbSet<RegistrationToken> RegistrationTokens { get; set; }
|
||||||
|
public DbSet<APIKey> APIKeys { get; set; }
|
||||||
|
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder options)
|
protected override void OnConfiguring(DbContextOptionsBuilder options)
|
||||||
=> options.UseMySql(ServerConfiguration.Instance.DbConnectionString, MySqlServerVersion.LatestSupportedServerVersion);
|
=> options.UseMySql(ServerConfiguration.Instance.DbConnectionString, MySqlServerVersion.LatestSupportedServerVersion);
|
||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
public async Task<User> CreateUser(string username, string password, string? emailAddress = null)
|
public async Task<User> CreateUser(string username, string password, string? emailAddress = null)
|
||||||
{
|
{
|
||||||
if (!password.StartsWith('$')) throw new ArgumentException(nameof(password) + " is not a BCrypt hash");
|
if (!password.StartsWith('$')) throw new ArgumentException(nameof(password) + " is not a BCrypt hash");
|
||||||
|
@ -357,6 +359,49 @@ public class Database : DbContext
|
||||||
return this.WebTokens.FirstOrDefault(t => t.UserToken == lighthouseToken);
|
return this.WebTokens.FirstOrDefault(t => t.UserToken == lighthouseToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<User?> UserFromPasswordResetToken(string resetToken)
|
||||||
|
{
|
||||||
|
|
||||||
|
PasswordResetToken? token = await this.PasswordResetTokens.FirstOrDefaultAsync(token => token.ResetToken == resetToken);
|
||||||
|
if (token == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token.Created < DateTime.Now.AddHours(-1)) // if token is expired
|
||||||
|
{
|
||||||
|
this.PasswordResetTokens.Remove(token);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return await this.Users.FirstOrDefaultAsync(user => user.UserId == token.UserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsRegistrationTokenValid(string tokenString)
|
||||||
|
{
|
||||||
|
RegistrationToken? token = this.RegistrationTokens.FirstOrDefault(t => t.Token == tokenString);
|
||||||
|
|
||||||
|
if (token == null) return false;
|
||||||
|
|
||||||
|
if (token.Created < DateTime.Now.AddDays(-7)) // if token is expired
|
||||||
|
{
|
||||||
|
this.RegistrationTokens.Remove(token);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RemoveRegistrationToken(string tokenString)
|
||||||
|
{
|
||||||
|
RegistrationToken? token = await this.RegistrationTokens.FirstOrDefaultAsync(t => t.Token == tokenString);
|
||||||
|
|
||||||
|
if (token == null) return;
|
||||||
|
|
||||||
|
this.RegistrationTokens.Remove(token);
|
||||||
|
|
||||||
|
await this.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public async Task<Photo?> PhotoFromSubject(PhotoSubject subject)
|
public async Task<Photo?> PhotoFromSubject(PhotoSubject subject)
|
||||||
|
@ -398,5 +443,5 @@ public class Database : DbContext
|
||||||
|
|
||||||
if (saveChanges) await this.SaveChangesAsync();
|
if (saveChanges) await this.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
#nullable disable
|
#nullable disable
|
||||||
}
|
}
|
|
@ -15,7 +15,21 @@ namespace LBPUnion.ProjectLighthouse.Extensions;
|
||||||
|
|
||||||
public static class RequestExtensions
|
public static class RequestExtensions
|
||||||
{
|
{
|
||||||
|
static RequestExtensions()
|
||||||
|
{
|
||||||
|
Uri captchaUri = ServerConfiguration.Instance.Captcha.Type switch
|
||||||
|
{
|
||||||
|
CaptchaType.HCaptcha => new Uri("https://hcaptcha.com"),
|
||||||
|
CaptchaType.ReCaptcha => new Uri("https://www.google.com/recaptcha/api/"),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(),
|
||||||
|
};
|
||||||
|
|
||||||
|
client = new HttpClient
|
||||||
|
{
|
||||||
|
BaseAddress = captchaUri,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#region Mobile Checking
|
#region Mobile Checking
|
||||||
|
|
||||||
// yoinked and adapted from https://stackoverflow.com/a/68641796
|
// yoinked and adapted from https://stackoverflow.com/a/68641796
|
||||||
|
@ -32,10 +46,7 @@ public static class RequestExtensions
|
||||||
|
|
||||||
#region Captcha
|
#region Captcha
|
||||||
|
|
||||||
private static readonly HttpClient client = new()
|
private static readonly HttpClient client;
|
||||||
{
|
|
||||||
BaseAddress = new Uri("https://hcaptcha.com"),
|
|
||||||
};
|
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeNotEvident")]
|
[SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeNotEvident")]
|
||||||
private static async Task<bool> verifyCaptcha(string token)
|
private static async Task<bool> verifyCaptcha(string token)
|
||||||
|
@ -48,7 +59,7 @@ public static class RequestExtensions
|
||||||
new("response", token),
|
new("response", token),
|
||||||
};
|
};
|
||||||
|
|
||||||
HttpResponseMessage response = await client.PostAsync("/siteverify", new FormUrlEncodedContent(payload));
|
HttpResponseMessage response = await client.PostAsync("siteverify", new FormUrlEncodedContent(payload));
|
||||||
|
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
@ -63,7 +74,14 @@ public static class RequestExtensions
|
||||||
{
|
{
|
||||||
if (ServerConfiguration.Instance.Captcha.CaptchaEnabled)
|
if (ServerConfiguration.Instance.Captcha.CaptchaEnabled)
|
||||||
{
|
{
|
||||||
bool gotCaptcha = request.Form.TryGetValue("h-captcha-response", out StringValues values);
|
string keyName = ServerConfiguration.Instance.Captcha.Type switch
|
||||||
|
{
|
||||||
|
CaptchaType.HCaptcha => "h-captcha-response",
|
||||||
|
CaptchaType.ReCaptcha => "g-recaptcha-response",
|
||||||
|
_ => throw new ArgumentOutOfRangeException(),
|
||||||
|
};
|
||||||
|
|
||||||
|
bool gotCaptcha = request.Form.TryGetValue(keyName, out StringValues values);
|
||||||
if (!gotCaptcha) return false;
|
if (!gotCaptcha) return false;
|
||||||
|
|
||||||
if (!await verifyCaptcha(values[0])) return false;
|
if (!await verifyCaptcha(values[0])) return false;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using LBPUnion.ProjectLighthouse.Configuration;
|
using LBPUnion.ProjectLighthouse.Configuration;
|
||||||
using LBPUnion.ProjectLighthouse.Types;
|
using LBPUnion.ProjectLighthouse.Types;
|
||||||
|
@ -94,4 +95,23 @@ public static class CensorHelper
|
||||||
|
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string MaskEmail(string email)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(email) || !email.Contains('@')) return email;
|
||||||
|
|
||||||
|
string[] emailArr = email.Split('@');
|
||||||
|
string domainExt = Path.GetExtension(email);
|
||||||
|
|
||||||
|
string maskedEmail = string.Format("{0}****{1}@{2}****{3}{4}",
|
||||||
|
emailArr[0][0],
|
||||||
|
emailArr[0].Substring(emailArr[0].Length - 1),
|
||||||
|
emailArr[1][0],
|
||||||
|
emailArr[1]
|
||||||
|
.Substring(emailArr[1].Length - domainExt.Length - 1,
|
||||||
|
1),
|
||||||
|
domainExt);
|
||||||
|
|
||||||
|
return maskedEmail;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -36,6 +36,8 @@ public static class MatchHelper
|
||||||
return recentlyDivedIn.Contains(otherUserId);
|
return recentlyDivedIn.Contains(otherUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool ClearUserRecentDiveIns(int userId) => UserRecentlyDivedIn.Remove(userId);
|
||||||
|
|
||||||
// This is the function used to show people how laughably awful LBP's protocol is. Beware.
|
// This is the function used to show people how laughably awful LBP's protocol is. Beware.
|
||||||
public static IMatchCommand? Deserialize(string data)
|
public static IMatchCommand? Deserialize(string data)
|
||||||
{
|
{
|
||||||
|
|
|
@ -25,4 +25,6 @@ public static class StatisticsHelper
|
||||||
public static async Task<int> PhotoCount() => await database.Photos.CountAsync();
|
public static async Task<int> PhotoCount() => await database.Photos.CountAsync();
|
||||||
|
|
||||||
public static async Task<int> ReportCount() => await database.Reports.CountAsync();
|
public static async Task<int> ReportCount() => await database.Reports.CountAsync();
|
||||||
|
|
||||||
|
public static async Task<int> APIKeyCount() => await database.APIKeys.CountAsync();
|
||||||
}
|
}
|
|
@ -21,4 +21,5 @@ public enum LogArea
|
||||||
Redis,
|
Redis,
|
||||||
Command,
|
Command,
|
||||||
Admin,
|
Admin,
|
||||||
|
Publish,
|
||||||
}
|
}
|
|
@ -43,7 +43,18 @@ public class Room
|
||||||
public bool IsLookingForPlayers => this.State == RoomState.PlayingLevel || this.State == RoomState.DivingInWaiting;
|
public bool IsLookingForPlayers => this.State == RoomState.PlayingLevel || this.State == RoomState.DivingInWaiting;
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public int HostId => this.PlayerIds[0];
|
public int HostId {
|
||||||
|
get {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return this.PlayerIds[0];
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
public override bool Equals(object? obj)
|
public override bool Equals(object? obj)
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -46,10 +48,10 @@ public class RoomHelper
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
IEnumerable<Room> rooms = Rooms;
|
Random random = new();
|
||||||
|
IEnumerable<Room> rooms = Rooms.OrderBy(_ => random.Next());
|
||||||
|
|
||||||
rooms = rooms.OrderBy(r => r.IsLookingForPlayers);
|
rooms = rooms.OrderBy(r => r.IsLookingForPlayers);
|
||||||
|
|
||||||
rooms = rooms.Where(r => r.RoomVersion == roomVersion).ToList();
|
rooms = rooms.Where(r => r.RoomVersion == roomVersion).ToList();
|
||||||
if (platform != null) rooms = rooms.Where(r => r.RoomPlatform == platform).ToList();
|
if (platform != null) rooms = rooms.Where(r => r.RoomPlatform == platform).ToList();
|
||||||
|
|
||||||
|
@ -136,6 +138,12 @@ public class RoomHelper
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
MatchHelper.ClearUserRecentDiveIns(user.UserId);
|
||||||
|
Logger.Info($"Cleared {user.Username} (id: {user.UserId})'s recent dive-ins", LogArea.Match);
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,49 +202,94 @@ public class RoomHelper
|
||||||
[SuppressMessage("ReSharper", "InvertIf")]
|
[SuppressMessage("ReSharper", "InvertIf")]
|
||||||
public static void CleanupRooms(int? hostId = null, Room? newRoom = null, Database? database = null)
|
public static void CleanupRooms(int? hostId = null, Room? newRoom = null, Database? database = null)
|
||||||
{
|
{
|
||||||
|
#if DEBUG
|
||||||
|
Stopwatch stopwatch = new();
|
||||||
|
stopwatch.Start();
|
||||||
|
#endif
|
||||||
lock(RoomLock)
|
lock(RoomLock)
|
||||||
{
|
{
|
||||||
int roomCountBeforeCleanup = Rooms.Count();
|
StorableList<Room> rooms = Rooms; // cache rooms so we dont gen a new one every time
|
||||||
|
List<Room> roomsToUpdate = new();
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
Logger.Debug($"Cleaning up rooms... (took {stopwatch.ElapsedMilliseconds}ms to get lock on {nameof(RoomLock)})", LogArea.Match);
|
||||||
|
#endif
|
||||||
|
int roomCountBeforeCleanup = rooms.Count();
|
||||||
|
|
||||||
// Remove offline players from rooms
|
// Remove offline players from rooms
|
||||||
foreach (Room room in Rooms)
|
foreach (Room room in rooms)
|
||||||
{
|
{
|
||||||
List<User> players = room.GetPlayers(database ?? new Database());
|
List<User> players = room.GetPlayers(database ?? new Database());
|
||||||
|
|
||||||
List<int> playersToRemove = players.Where(player => player.Status.StatusType == StatusType.Offline).Select(player => player.UserId).ToList();
|
List<int> playersToRemove = players.Where(player => player.Status.StatusType == StatusType.Offline).Select(player => player.UserId).ToList();
|
||||||
|
|
||||||
foreach (int player in playersToRemove) room.PlayerIds.Remove(player);
|
foreach (int player in playersToRemove) room.PlayerIds.Remove(player);
|
||||||
|
|
||||||
|
roomsToUpdate.Add(room);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DO NOT REMOVE ROOMS BEFORE THIS POINT!
|
||||||
|
// this will cause the room to be added back to the database
|
||||||
|
foreach (Room room in roomsToUpdate)
|
||||||
|
{
|
||||||
|
rooms.Update(room);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete old rooms based on host
|
// Delete old rooms based on host
|
||||||
if (hostId != null)
|
if (hostId != null)
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Rooms.RemoveAll(r => r.HostId == hostId);
|
rooms.RemoveAll(r => r.PlayerIds.Contains((int)hostId));
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// TODO: detect the room that failed and remove it
|
// TODO: detect the room that failed and remove it
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Remove players in this new room from other rooms
|
// Remove rooms containing players in this new room
|
||||||
if (newRoom != null)
|
if (newRoom != null)
|
||||||
foreach (Room room in Rooms)
|
|
||||||
{
|
|
||||||
if (room == newRoom) continue;
|
|
||||||
|
|
||||||
foreach (int newRoomPlayer in newRoom.PlayerIds) room.PlayerIds.RemoveAll(p => p == newRoomPlayer);
|
|
||||||
}
|
|
||||||
|
|
||||||
Rooms.RemoveAll(r => r.PlayerIds.Count == 0); // Remove empty rooms
|
|
||||||
Rooms.RemoveAll(r => r.PlayerIds.Count > 4); // Remove obviously bogus rooms
|
|
||||||
|
|
||||||
int roomCountAfterCleanup = Rooms.Count();
|
|
||||||
|
|
||||||
if (roomCountBeforeCleanup != roomCountAfterCleanup)
|
|
||||||
{
|
{
|
||||||
Logger.Debug($"Cleaned up {roomCountBeforeCleanup - roomCountAfterCleanup} rooms.",
|
foreach (Room room in rooms.Where(room => room != newRoom))
|
||||||
LogArea.Match);
|
{
|
||||||
|
foreach (int newRoomPlayer in newRoom.PlayerIds)
|
||||||
|
{
|
||||||
|
if (room.PlayerIds.Contains(newRoomPlayer)) rooms.Remove(room);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rooms.RemoveAll(r => r.PlayerIds.Count == 0); // Remove empty rooms
|
||||||
|
rooms.RemoveAll(r => r.HostId == -1); // Remove rooms with broken hosts
|
||||||
|
rooms.RemoveAll(r => r.PlayerIds.Count > 4); // Remove obviously bogus rooms
|
||||||
|
|
||||||
|
int roomCountAfterCleanup = rooms.Count();
|
||||||
|
|
||||||
|
// Log the amount of rooms cleaned up.
|
||||||
|
// If we didnt clean any rooms, it's not useful to log in a
|
||||||
|
// production environment but it's still quite useful for debugging.
|
||||||
|
//
|
||||||
|
// So, we handle that case here:
|
||||||
|
int roomsCleanedUp = roomCountBeforeCleanup - roomCountAfterCleanup;
|
||||||
|
string logText = $"Cleaned up {roomsCleanedUp} rooms.";
|
||||||
|
|
||||||
|
if (roomsCleanedUp == 0)
|
||||||
|
{
|
||||||
|
Logger.Debug(logText, LogArea.Match);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Info(logText, LogArea.Match);
|
||||||
|
}
|
||||||
|
|
||||||
|
logText = $"Updated {roomsToUpdate.Count} rooms.";
|
||||||
|
if (roomsToUpdate.Count == 0)
|
||||||
|
{
|
||||||
|
Logger.Debug(logText, LogArea.Match);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Info(logText, LogArea.Match);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
using LBPUnion.ProjectLighthouse;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ProjectLighthouse.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(Database))]
|
||||||
|
[Migration("20220611221819_OnlyAllowSingleApprovedIP")]
|
||||||
|
public class OnlyAllowSingleApprovedIP : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "UserApprovedIpAddresses");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "ApprovedIPAddress",
|
||||||
|
table: "Users",
|
||||||
|
type: "longtext",
|
||||||
|
nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "ApprovedIPAddress",
|
||||||
|
table: "Users");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "UserApprovedIpAddresses",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
UserApprovedIpAddressId = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
UserId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
IpAddress = table.Column<string>(type: "longtext", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4")
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_UserApprovedIpAddresses", x => x.UserApprovedIpAddressId);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_UserApprovedIpAddresses_Users_UserId",
|
||||||
|
column: x => x.UserId,
|
||||||
|
principalTable: "Users",
|
||||||
|
principalColumn: "UserId",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_UserApprovedIpAddresses_UserId",
|
||||||
|
table: "UserApprovedIpAddresses",
|
||||||
|
column: "UserId");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
using System;
|
||||||
|
using LBPUnion.ProjectLighthouse;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ProjectLighthouse.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(Database))]
|
||||||
|
[Migration("20220624210701_AddedPasswordResetTokens")]
|
||||||
|
public partial class AddedPasswordResetTokens : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "PasswordResetTokens",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
TokenId = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
UserId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
ResetToken = table.Column<string>(type: "longtext", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Created = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_PasswordResetTokens", x => x.TokenId);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "PasswordResetTokens");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
62
ProjectLighthouse/Migrations/20220715222906_UserInvite.cs
Normal file
62
ProjectLighthouse/Migrations/20220715222906_UserInvite.cs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using LBPUnion.ProjectLighthouse;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ProjectLighthouse.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(Database))]
|
||||||
|
[Migration("20220715222906_UserInvite")]
|
||||||
|
public partial class UserInvite : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "APIKeys",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
Description = table.Column<string>(type: "longtext", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Key = table.Column<string>(type: "longtext", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Created = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||||
|
Enabled = table.Column<bool>(type: "tinyint(1)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_APIKeys", x => x.Id);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "RegistrationTokens",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
TokenId = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
Token = table.Column<string>(type: "longtext", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Created = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_RegistrationTokens", x => x.TokenId);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "APIKeys");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "RegistrationTokens");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using LBPUnion.ProjectLighthouse;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ProjectLighthouse.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(Database))]
|
||||||
|
[Migration("20220716234844_RemovedAPIKeyEnabled")]
|
||||||
|
public partial class RemovedAPIKeyEnabled : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Enabled",
|
||||||
|
table: "APIKeys");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "Enabled",
|
||||||
|
table: "APIKeys",
|
||||||
|
type: "tinyint(1)",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,7 @@ namespace ProjectLighthouse.Migrations
|
||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "6.0.5")
|
.HasAnnotation("ProductVersion", "6.0.7")
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||||
|
|
||||||
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Administration.CompletedMigration", b =>
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Administration.CompletedMigration", b =>
|
||||||
|
@ -331,6 +331,26 @@ namespace ProjectLighthouse.Migrations
|
||||||
b.ToTable("VisitedLevels");
|
b.ToTable("VisitedLevels");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.PlayerData.APIKey", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("APIKeys");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LBPUnion.ProjectLighthouse.PlayerData.AuthenticationAttempt", b =>
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.PlayerData.AuthenticationAttempt", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("AuthenticationAttemptId")
|
b.Property<int>("AuthenticationAttemptId")
|
||||||
|
@ -390,6 +410,26 @@ namespace ProjectLighthouse.Migrations
|
||||||
b.ToTable("GameTokens");
|
b.ToTable("GameTokens");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.PlayerData.PasswordResetToken", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("TokenId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("ResetToken")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("TokenId");
|
||||||
|
|
||||||
|
b.ToTable("PasswordResetTokens");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LBPUnion.ProjectLighthouse.PlayerData.Photo", b =>
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.PlayerData.Photo", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("PhotoId")
|
b.Property<int>("PhotoId")
|
||||||
|
@ -595,6 +635,9 @@ namespace ProjectLighthouse.Migrations
|
||||||
b.Property<int>("AdminGrantedSlots")
|
b.Property<int>("AdminGrantedSlots")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("ApprovedIPAddress")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
b.Property<bool>("Banned")
|
b.Property<bool>("Banned")
|
||||||
.HasColumnType("tinyint(1)");
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
@ -659,25 +702,6 @@ namespace ProjectLighthouse.Migrations
|
||||||
b.ToTable("Users");
|
b.ToTable("Users");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LBPUnion.ProjectLighthouse.PlayerData.Profiles.UserApprovedIpAddress", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("UserApprovedIpAddressId")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<string>("IpAddress")
|
|
||||||
.HasColumnType("longtext");
|
|
||||||
|
|
||||||
b.Property<int>("UserId")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.HasKey("UserApprovedIpAddressId");
|
|
||||||
|
|
||||||
b.HasIndex("UserId");
|
|
||||||
|
|
||||||
b.ToTable("UserApprovedIpAddresses");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("LBPUnion.ProjectLighthouse.PlayerData.Reaction", b =>
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.PlayerData.Reaction", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("RatingId")
|
b.Property<int>("RatingId")
|
||||||
|
@ -698,6 +722,23 @@ namespace ProjectLighthouse.Migrations
|
||||||
b.ToTable("Reactions");
|
b.ToTable("Reactions");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.PlayerData.RegistrationToken", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("TokenId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Token")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("TokenId");
|
||||||
|
|
||||||
|
b.ToTable("RegistrationTokens");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LBPUnion.ProjectLighthouse.PlayerData.Reviews.RatedReview", b =>
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.PlayerData.Reviews.RatedReview", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("RatedReviewId")
|
b.Property<int>("RatedReviewId")
|
||||||
|
@ -1035,17 +1076,6 @@ namespace ProjectLighthouse.Migrations
|
||||||
b.Navigation("Location");
|
b.Navigation("Location");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("LBPUnion.ProjectLighthouse.PlayerData.Profiles.UserApprovedIpAddress", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("LBPUnion.ProjectLighthouse.PlayerData.Profiles.User", "User")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("UserId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("User");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("LBPUnion.ProjectLighthouse.PlayerData.Reviews.RatedReview", b =>
|
modelBuilder.Entity("LBPUnion.ProjectLighthouse.PlayerData.Reviews.RatedReview", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("LBPUnion.ProjectLighthouse.PlayerData.Reviews.Review", "Review")
|
b.HasOne("LBPUnion.ProjectLighthouse.PlayerData.Reviews.Review", "Review")
|
||||||
|
|
19
ProjectLighthouse/PlayerData/APIKey.cs
Normal file
19
ProjectLighthouse/PlayerData/APIKey.cs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace LBPUnion.ProjectLighthouse.PlayerData
|
||||||
|
{
|
||||||
|
public class APIKey
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
public string Key { get; set; }
|
||||||
|
|
||||||
|
public DateTime Created { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
17
ProjectLighthouse/PlayerData/PasswordResetToken.cs
Normal file
17
ProjectLighthouse/PlayerData/PasswordResetToken.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace LBPUnion.ProjectLighthouse.PlayerData;
|
||||||
|
|
||||||
|
public class PasswordResetToken
|
||||||
|
{
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
[Key]
|
||||||
|
public int TokenId { get; set; }
|
||||||
|
|
||||||
|
public int UserId { get; set; }
|
||||||
|
|
||||||
|
public string ResetToken { get; set; }
|
||||||
|
|
||||||
|
public DateTime Created { get; set; }
|
||||||
|
}
|
|
@ -143,6 +143,11 @@ public class User
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string BannedReason { get; set; }
|
public string BannedReason { get; set; }
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
[JsonIgnore]
|
||||||
|
public string? ApprovedIPAddress { get; set; }
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
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) +
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
|
||||||
|
|
||||||
namespace LBPUnion.ProjectLighthouse.PlayerData.Profiles;
|
|
||||||
|
|
||||||
public class UserApprovedIpAddress
|
|
||||||
{
|
|
||||||
[Key]
|
|
||||||
public int UserApprovedIpAddressId { get; set; }
|
|
||||||
|
|
||||||
public int UserId { get; set; }
|
|
||||||
|
|
||||||
[ForeignKey(nameof(UserId))]
|
|
||||||
public User User { get; set; }
|
|
||||||
|
|
||||||
public string IpAddress { get; set; }
|
|
||||||
}
|
|
16
ProjectLighthouse/PlayerData/RegistrationToken.cs
Normal file
16
ProjectLighthouse/PlayerData/RegistrationToken.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace LBPUnion.ProjectLighthouse.PlayerData
|
||||||
|
{
|
||||||
|
public class RegistrationToken
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public int TokenId { get; set; }
|
||||||
|
|
||||||
|
public string Token { get; set; }
|
||||||
|
|
||||||
|
public DateTime Created { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -12,19 +12,19 @@
|
||||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||||
<PackageReference Include="DDSReader" Version="1.0.8-pre" />
|
<PackageReference Include="DDSReader" Version="1.0.8-pre" />
|
||||||
<PackageReference Include="Discord.Net.Webhook" Version="3.7.2" />
|
<PackageReference Include="Discord.Net.Webhook" Version="3.7.2" />
|
||||||
<PackageReference Include="InfluxDB.Client" Version="4.2.0" />
|
<PackageReference Include="InfluxDB.Client" Version="4.3.0" />
|
||||||
<PackageReference Include="JetBrains.Annotations" Version="2022.1.0" />
|
<PackageReference Include="JetBrains.Annotations" Version="2022.1.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="6.0.5" />
|
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="6.0.7" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.5" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.7" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.5">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.7">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.1" />
|
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.2" />
|
||||||
<PackageReference Include="Redis.OM" Version="0.1.9" />
|
<PackageReference Include="Redis.OM" Version="0.2.1" />
|
||||||
<PackageReference Include="SharpZipLib" Version="1.3.3" />
|
<PackageReference Include="SharpZipLib" Version="1.3.3" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.3.1" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||||
<PackageReference Include="YamlDotNet" Version="11.2.1" />
|
<PackageReference Include="YamlDotNet" Version="12.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
BIN
ProjectLighthouse/StaticFiles/assets/slotCardBackground.png
Normal file
BIN
ProjectLighthouse/StaticFiles/assets/slotCardBackground.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
Binary file not shown.
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 26 KiB |
|
@ -27,6 +27,7 @@ canvas.hide-subjects {
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Redis.OM.Searching;
|
using Redis.OM.Searching;
|
||||||
|
|
||||||
|
@ -17,15 +18,15 @@ public class RedisStorableList<T> : StorableList<T>
|
||||||
this.redisNormalCollection.Insert(item);
|
this.redisNormalCollection.Insert(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task RemoveAsync(T item) => this.redisNormalCollection.Delete(item);
|
public override Task RemoveAsync(T item) => this.redisNormalCollection.DeleteAsync(item);
|
||||||
public override void Remove(T item)
|
public override void Remove(T item)
|
||||||
{
|
{
|
||||||
this.redisNormalCollection.DeleteSync(item);
|
this.redisNormalCollection.Delete(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task UpdateAsync(T item) => this.redisNormalCollection.Update(item);
|
public override Task UpdateAsync(T item) => this.redisNormalCollection.UpdateAsync(item);
|
||||||
public override void Update(T item)
|
public override void Update(T item)
|
||||||
{
|
{
|
||||||
this.redisNormalCollection.UpdateSync(item);
|
this.redisNormalCollection.Update(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
16
README.md
16
README.md
|
@ -20,7 +20,7 @@ environment until release**.
|
||||||
This is because we have not entirely nailed security down yet, and **your instance WILL get attacked** as a result. It's
|
This is because we have not entirely nailed security down yet, and **your instance WILL get attacked** as a result. It's
|
||||||
happened before, and it'll happen again.
|
happened before, and it'll happen again.
|
||||||
|
|
||||||
Simply put, **Project Lighthouse is not ready for the general public yet**.
|
Simply put, **Project Lighthouse is not ready for the public yet**.
|
||||||
|
|
||||||
In addition, we're not responsible if someone hacks your machine and wipes your database, so make frequent backups, and
|
In addition, we're not responsible if someone hacks your machine and wipes your database, so make frequent backups, and
|
||||||
be sure to report any vulnerabilities. Thank you in advance.
|
be sure to report any vulnerabilities. Thank you in advance.
|
||||||
|
@ -61,12 +61,12 @@ information.
|
||||||
|
|
||||||
## Compatibility across games and platforms
|
## Compatibility across games and platforms
|
||||||
|
|
||||||
| Game | Console (PS3/Vita/PSP) | Emulator (RPCS3/Vita3k/PPSSPP) | Next-Gen (PS4/PS5/Vita) |
|
| Game | Console (PS3/Vita/PSP) | Emulator (RPCS3/Vita3k/PPSSPP) | Next-Gen (PS4/PS5/Adrenaline) |
|
||||||
|----------|-------------------------------------|--------------------------------------------------------------|-------------------------|
|
|----------|------------------------|-------------------------------------------|-------------------------------|
|
||||||
| LBP1 | Compatible | Compatible with patched RPCS3 build | No next-gen equivalent |
|
| LBP1 | Compatible | Compatible | No next-gen equivalent |
|
||||||
| LBP2 | Compatible | Compatible with patched RPCS3 build | No next-gen equivalent |
|
| LBP2 | Compatible | Compatible | No next-gen equivalent |
|
||||||
| LBP3 | Mostly compatible, frequent crashes | Mostly compatible with patched RPCS3 build, frequent crashes | Incompatible |
|
| LBP3 | Mostly compatible | Mostly compatible | Incompatible |
|
||||||
| LBP Vita | Compatible | Incompatible, marked as "Intro" on Vita3k | No next-gen equivalent |
|
| LBP Vita | Compatible | Incompatible, PSN not supported on Vita3k | No next-gen equivalent |
|
||||||
| LBP PSP | Potentially compatible | Incompatible, PSN not supported on PPSSPP | Potentially Compatible |
|
| LBP PSP | Potentially compatible | Incompatible, PSN not supported on PPSSPP | Potentially Compatible |
|
||||||
|
|
||||||
Project Lighthouse is mostly a work in progress, so this chart is subject to change at any point.
|
Project Lighthouse is mostly a work in progress, so this chart is subject to change at any point.
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#!/bin/sh
|
||||||
# Build script for production
|
# Build script for production
|
||||||
#
|
#
|
||||||
# No arguments
|
# No arguments
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#!/bin/sh
|
||||||
# Startup script for production
|
# Startup script for production
|
||||||
#
|
#
|
||||||
# $1: Server to start; case sensitive!!!!!
|
# $1: Server to start; case sensitive!!!!!
|
||||||
|
|
12
scripts-and-tools/update.sh
Normal file
12
scripts-and-tools/update.sh
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# Update script for production
|
||||||
|
#
|
||||||
|
# No arguments
|
||||||
|
# Called manually
|
||||||
|
|
||||||
|
sudo systemctl stop project-lighthouse*
|
||||||
|
|
||||||
|
cd /srv/lighthouse || return
|
||||||
|
sudo -u lighthouse -i /srv/lighthouse/build.sh
|
||||||
|
|
||||||
|
sudo systemctl start project-lighthouse*
|
Loading…
Add table
Add a link
Reference in a new issue