Email verification with attribute

This commit is contained in:
Zaprit 2025-08-13 21:13:17 +01:00
commit 456f51f0b7
19 changed files with 65 additions and 39 deletions

View file

@ -21,4 +21,7 @@
<data name="username_notice" xml:space="preserve">
<value>Caution: Your username MUST match your PSN/RPCN username in order to be able to sign in from in-game.</value>
</data>
<data name="email_verify_notice" xml:space="preserve">
<value>The game will be read-only until you verify your email</value>
</data>
</root>

View file

@ -3,6 +3,7 @@ using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Middlewares;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
using LBPUnion.ProjectLighthouse.Types.Filter;
@ -29,6 +30,7 @@ public class CommentController : ControllerBase
[HttpPost("rateUserComment/{username}")]
[HttpPost("rateComment/{slotType}/{slotId:int}")]
[EmailVerification]
public async Task<IActionResult> RateComment([FromQuery] int commentId, [FromQuery] int rating, string? username, string? slotType, int slotId)
{
GameTokenEntity token = this.GetToken();
@ -113,6 +115,7 @@ public class CommentController : ControllerBase
[HttpPost("postUserComment/{username}")]
[HttpPost("postComment/{slotType}/{slotId:int}")]
[EmailVerification]
public async Task<IActionResult> PostComment(string? username, string? slotType, int slotId)
{
GameTokenEntity token = this.GetToken();
@ -152,6 +155,7 @@ public class CommentController : ControllerBase
[HttpPost("deleteUserComment/{username}")]
[HttpPost("deleteComment/{slotType}/{slotId:int}")]
[EmailVerification]
public async Task<IActionResult> DeleteComment([FromQuery] int commentId, string? username, string? slotType, int slotId)
{
GameTokenEntity token = this.GetToken();

View file

@ -1,6 +1,7 @@
#nullable enable
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Middlewares;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Users;
using LBPUnion.ProjectLighthouse.StorableLists.Stores;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
@ -17,6 +18,7 @@ namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")]
[EmailVerification]
public class FriendsController : ControllerBase
{
private readonly DatabaseContext database;

View file

@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis;
using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Middlewares;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Users;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Serialization;
@ -70,6 +71,7 @@ public class ClientConfigurationController : ControllerBase
[HttpPost("privacySettings")]
[Produces("text/xml")]
[EmailVerification]
public async Task<IActionResult> SetPrivacySetting()
{
UserEntity? user = await this.database.UserFromGameToken(this.GetToken());

View file

@ -2,6 +2,7 @@
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Middlewares;
using LBPUnion.ProjectLighthouse.Types.Entities.Interaction;
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
@ -16,6 +17,7 @@ namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Matching;
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")]
[EmailVerification]
public class EnterLevelController : ControllerBase
{
private readonly DatabaseContext database;

View file

@ -5,6 +5,7 @@ using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Middlewares;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
using LBPUnion.ProjectLighthouse.Types.Logging;
@ -21,6 +22,7 @@ namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Matching;
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")]
[EmailVerification]
public class MatchController : ControllerBase
{
private readonly DatabaseContext database;

View file

@ -13,6 +13,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Token;
using LBPUnion.ProjectLighthouse.Types.Filter;
using LBPUnion.ProjectLighthouse.Types.Logging;
using LBPUnion.ProjectLighthouse.Types.Mail;
using LBPUnion.ProjectLighthouse.Types.Notifications;
using LBPUnion.ProjectLighthouse.Types.Serialization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@ -96,6 +97,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.";
StringBuilder builder = new();
UserEntity? user = await this.database.UserFromGameToken(token);
if (user?.EmailAddressVerified == false)
{
GameNotification verifyEmailNotification = new();
verifyEmailNotification.Type = NotificationType.ModerationNotification;
verifyEmailNotification.Text = LocalizationManager.GetLocalizedString(TranslationAreas.Register, user.Language, "email_verify_notice");
builder.AppendLine(LighthouseSerializer.Serialize(this.HttpContext.RequestServices, verifyEmailNotification));
}
foreach (NotificationEntity notification in notifications)
{
builder.AppendLine(LighthouseSerializer.Serialize(this.HttpContext.RequestServices,

View file

@ -5,6 +5,7 @@ using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Files;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Middlewares;
using LBPUnion.ProjectLighthouse.Types.Entities.Moderation;
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
using LBPUnion.ProjectLighthouse.Types.Moderation.Reports;
@ -18,6 +19,7 @@ namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers;
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")]
[EmailVerification]
public class ReportController : ControllerBase
{
private readonly DatabaseContext database;

View file

@ -6,6 +6,7 @@ using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Files;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Middlewares;
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
@ -33,6 +34,7 @@ public class PhotosController : ControllerBase
}
[HttpPost("uploadPhoto")]
[EmailVerification]
public async Task<IActionResult> UploadPhoto()
{
GameTokenEntity token = this.GetToken();
@ -234,6 +236,7 @@ public class PhotosController : ControllerBase
}
[HttpPost("deletePhoto/{id:int}")]
[EmailVerification]
public async Task<IActionResult> DeletePhoto(int id)
{
GameTokenEntity token = this.GetToken();

View file

@ -1,9 +1,9 @@
#nullable enable
using System.Text;
using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Files;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Middlewares;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Misc;
using LBPUnion.ProjectLighthouse.Types.Logging;
using LBPUnion.ProjectLighthouse.Types.Resources;
@ -52,6 +52,7 @@ public class ResourcesController : ControllerBase
[HttpPost("upload/{hash}/unattributed")]
[HttpPost("upload/{hash}")]
[EmailVerification]
public async Task<IActionResult> UploadResource(string hash)
{
string assetsDirectory = FileHelper.ResourcePath;

View file

@ -1,6 +1,7 @@
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Middlewares;
using LBPUnion.ProjectLighthouse.Types.Entities.Interaction;
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
@ -41,6 +42,7 @@ public class LevelTagsController : ControllerBase
}
[HttpPost("tag/{slotType}/{id:int}")]
[EmailVerification]
public async Task<IActionResult> PostTag([FromForm(Name = "t")] string tagName, [FromRoute] string slotType, [FromRoute] int id)
{
GameTokenEntity token = this.GetToken();

View file

@ -2,6 +2,7 @@
using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Middlewares;
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
using LBPUnion.ProjectLighthouse.Types.Serialization;
@ -41,6 +42,7 @@ public class PlaylistController : ControllerBase
}
[HttpPost("playlists/{playlistId:int}/delete")]
[EmailVerification]
public async Task<IActionResult> DeletePlaylist(int playlistId)
{
GameTokenEntity token = this.GetToken();
@ -60,6 +62,7 @@ public class PlaylistController : ControllerBase
[HttpPost("playlists/{playlistId:int}/slots")]
[HttpPost("playlists/{playlistId:int}/slots/{slotId:int}/delete")]
[HttpPost("playlists/{playlistId:int}/order_slots")]
[EmailVerification]
public async Task<IActionResult> UpdatePlaylist(int playlistId, int slotId)
{
GameTokenEntity token = this.GetToken();
@ -124,6 +127,7 @@ public class PlaylistController : ControllerBase
}
[HttpPost("playlists")]
[EmailVerification]
public async Task<IActionResult> CreatePlaylist()
{
GameTokenEntity token = this.GetToken();

View file

@ -6,6 +6,7 @@ using LBPUnion.ProjectLighthouse.Files;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Helpers;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Middlewares;
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
@ -24,6 +25,7 @@ namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots;
[Authorize]
[Route("LITTLEBIGPLANETPS3_XML/")]
[Produces("text/xml")]
[EmailVerification]
public class PublishController : ControllerBase
{
private readonly DatabaseContext database;

View file

@ -3,6 +3,7 @@ using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Middlewares;
using LBPUnion.ProjectLighthouse.Types.Entities.Interaction;
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
@ -29,6 +30,7 @@ public class ReviewController : ControllerBase
// LBP1 rating
[HttpPost("rate/user/{slotId:int}")]
[EmailVerification]
public async Task<IActionResult> Rate(int slotId, int rating)
{
GameTokenEntity token = this.GetToken();
@ -58,6 +60,7 @@ public class ReviewController : ControllerBase
// LBP2 and beyond rating
[HttpPost("dpadrate/user/{slotId:int}")]
[EmailVerification]
public async Task<IActionResult> DPadRate(int slotId, int rating)
{
GameTokenEntity token = this.GetToken();
@ -89,6 +92,7 @@ public class ReviewController : ControllerBase
}
[HttpPost("postReview/user/{slotId:int}")]
[EmailVerification]
public async Task<IActionResult> PostReview(int slotId)
{
GameTokenEntity token = this.GetToken();
@ -202,6 +206,7 @@ public class ReviewController : ControllerBase
}
[HttpPost("rateReview/user/{slotId:int}/{username}")]
[EmailVerification]
public async Task<IActionResult> RateReview(int slotId, string username, int rating = 0)
{
GameTokenEntity token = this.GetToken();
@ -255,6 +260,7 @@ public class ReviewController : ControllerBase
}
[HttpPost("deleteReview/user/{slotId:int}/{username}")]
[EmailVerification]
public async Task<IActionResult> DeleteReview(int slotId, string username)
{
GameTokenEntity token = this.GetToken();

View file

@ -3,6 +3,7 @@ using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Middlewares;
using LBPUnion.ProjectLighthouse.StorableLists.Stores;
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
@ -42,6 +43,7 @@ public class ScoreController : ControllerBase
[HttpPost("scoreboard/{slotType}/{id:int}")]
[HttpPost("scoreboard/{slotType}/{id:int}/{childId:int}")]
[EmailVerification]
public async Task<IActionResult> SubmitScore(string slotType, int id, int childId)
{
GameTokenEntity token = this.GetToken();

View file

@ -6,6 +6,7 @@ using LBPUnion.ProjectLighthouse.Files;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Helpers;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Middlewares;
using LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Users;
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
@ -63,6 +64,7 @@ public class UserController : ControllerBase
}
[HttpPost("updateUser")]
[EmailVerification]
public async Task<IActionResult> UpdateUser()
{
GameTokenEntity token = this.GetToken();
@ -171,6 +173,7 @@ public class UserController : ControllerBase
[HttpPost("update_my_pins")]
[Produces("text/json")]
[EmailVerification]
public async Task<IActionResult> UpdateMyPins()
{
UserEntity? user = await this.database.UserFromGameToken(this.GetToken());

View file

@ -1,57 +1,30 @@
using System.Diagnostics;
using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Middlewares;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
using Microsoft.AspNetCore.Http.Features;
namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Middlewares;
public class EmailVerificationAttribute : Attribute;
public class EmailVerificationMiddleware: MiddlewareDBContext
{
private readonly bool requireVerification;
private static readonly HashSet<string> verifyPathList =
[
"uploadPhoto",
"deletePhoto",
"upload",
"publish",
"rateUserComment",
"rateComment",
"postUserComment",
"postComment",
"deleteUserComment",
"deleteComment",
"npdata",
"grief",
"updateUser",
"update_my_pins",
"match",
"play",
"enterLevel",
"startPublish",
];
public EmailVerificationMiddleware(RequestDelegate next, bool requireVerification) : base(next)
public EmailVerificationMiddleware(RequestDelegate next) : base(next)
{
this.requireVerification = requireVerification;
}
public override async Task InvokeAsync(HttpContext context, DatabaseContext database)
{
if (requireVerification)
if (ServerConfiguration.Instance.Mail.RequireEmailVerification)
{
if (context.Request.Path.Value == null)
{
await this.next(context);
return;
}
const string url = "/LITTLEBIGPLANETPS3_XML";
string verifyPath = context.Request.Path;
string strippedPath = verifyPath.Contains(url) ? verifyPath[url.Length..].Split("/")[0] : "";
Endpoint? endpoint = context.Features.Get<IEndpointFeature>()?.Endpoint;
EmailVerificationAttribute? attribute = endpoint?.Metadata.GetMetadata<EmailVerificationAttribute>();
if (verifyPathList.Contains(strippedPath))
if (attribute != null)
{
GameTokenEntity? gameToken = await database.GameTokenFromRequest(context.Request);
if (gameToken == null)
@ -63,7 +36,7 @@ public class EmailVerificationMiddleware: MiddlewareDBContext
UserEntity? user = await database.UserFromGameToken(gameToken);
if (!user!.EmailAddressVerified)
{
context.Response.StatusCode = 403;
context.Response.StatusCode = 401; // 403 will cause a re-auth
context.Abort();
return;
}

View file

@ -106,6 +106,7 @@ public class GameServerStartup
app.UseMiddleware<RequestLogMiddleware>();
app.UseMiddleware<RateLimitMiddleware>();
app.UseMiddleware<DigestMiddleware>(computeDigests);
app.UseMiddleware<EmailVerificationMiddleware>();
app.UseMiddleware<SetLastContactMiddleware>();
app.UseRouting();

View file

@ -17,4 +17,6 @@ public class MailConfiguration
public string Password { get; set; } = "";
public bool UseSSL { get; set; } = true;
public bool RequireEmailVerification { get; set; } = true;
}