Add proper ban page when logging in (#773)

* Add proper ban page upon logging in

* Remove two extra line break tags that don't need to be there

* Fix timestamp formatting

* Properly display timestamps in correct timezone

* Fix formatting issues with ban page

* Remove extra parenthesis which would be rendered on-page

* Add to redirect middleware to prevent navigating to other pages

* Small nitpick, renaming UserBannedPage to BannedUserPage

* Resolve nitpicks from reviewers

* Remove un-necessary log message in LoginForm

* Fix ban reason translatable string argument

* Word choice nitpick ("Ban Created" -> "Ban Issued")

* Final adjustments and nitpicks, visual and grammatical

* Resolve requested changes from reviewers
This commit is contained in:
koko 2023-05-30 15:25:31 -04:00 committed by GitHub
parent c8c54d78de
commit 21dbdff20a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 203 additions and 23 deletions

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="suspension_heading" xml:space="preserve">
<value>Account Suspended</value>
</data>
<data name="suspension_explanation" xml:space="preserve">
<value>Your {0} account has been suspended due to code of conduct violations.</value>
</data>
<data name="suspension_expiration" xml:space="preserve">
<value>During this suspension, the following features will be limited until {0}:</value>
</data>
<data name="suspension_reason" xml:space="preserve">
<value>Reason for suspension: "{0}"</value>
</data>
<data name="suspension_circumvent_warning" xml:space="preserve">
<value>Attempts to circumvent this suspension will result in an extended suspension period.</value>
</data>
<data name="lbp_online_multiplayer" xml:space="preserve">
<value>LittleBigPlanet™ Online multiplayer</value>
</data>
<data name="does_not_expire" xml:space="preserve">
<value>Does not expire</value>
</data>
<data name="profile_visibility" xml:space="preserve">
<value>Profile visibility</value>
</data>
<data name="website_interactions" xml:space="preserve">
<value>Browsing levels, photos, and profiles</value>
</data>
<data name="account_profile_management" xml:space="preserve">
<value>Account and profile management</value>
</data>
</root>

View file

@ -44,6 +44,10 @@
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>TwoFactor.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Moderation.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Moderation.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>

View file

@ -0,0 +1,22 @@
namespace LBPUnion.ProjectLighthouse.Localization.StringLists;
public static class ModerationStrings
{
// Main page strings
public static readonly TranslatableString SuspensionHeading = create("suspension_heading");
public static readonly TranslatableString SuspensionExplanation = create("suspension_explanation");
public static readonly TranslatableString SuspensionExpiration = create("suspension_expiration");
public static readonly TranslatableString SuspensionReason = create("suspension_reason");
public static readonly TranslatableString SuspensionCircumventWarning = create("suspension_circumvent_warning");
// Translatable string in case a ban doesn't expire
public static readonly TranslatableString DoesNotExpire = create("does_not_expire");
// Restricted features strings
public static readonly TranslatableString LbpOnlineMultiplayer = create("lbp_online_multiplayer");
public static readonly TranslatableString WebsiteInteractions = create("website_interactions");
public static readonly TranslatableString ProfileVisibility = create("profile_visibility");
public static readonly TranslatableString AccountProfileManagement = create("account_profile_management");
private static TranslatableString create(string key) => new(TranslationAreas.Moderation, key);
}

View file

@ -12,4 +12,5 @@ public enum TranslationAreas
Profile,
ModPanel,
TwoFactor,
Moderation,
}

View file

@ -59,6 +59,18 @@ public class UserRequiredRedirectMiddleware : MiddlewareDBContext
return;
}
if (user.IsBanned)
{
if (!pathContains(ctx, "/banned"))
{
ctx.Response.Redirect("/banned");
return;
}
await this.next(ctx);
return;
}
if (user.EmailAddress == null && ServerConfiguration.Instance.Mail.MailEnabled)
{
if (!pathContains(ctx, "/login/setEmail"))

View file

@ -10,7 +10,7 @@
@{
Layout = "Layouts/BaseLayout";
Model.ShowTitleInPage = false;
bool isMobile = Request.IsMobile();
string language = Model.GetLanguage();
string timeZone = Model.GetTimeZone();
@ -25,7 +25,9 @@
if (Model.PendingAuthAttempts > 0)
{
<p>
<b><a href="/authentication">@Model.Translate(LandingPageStrings.AuthAttemptsPending, Model.PendingAuthAttempts)</a></b>
<b>
<a href="/authentication">@Model.Translate(LandingPageStrings.AuthAttemptsPending, Model.PendingAuthAttempts)</a>
</b>
</p>
}
}
@ -48,13 +50,13 @@
int i = 0;
foreach (UserEntity user in Model.PlayersOnline)
{
i++;
i++;
@await user.ToLink(Html, ViewData, language, timeZone, true)
@* whitespace has forced my hand *@
if (i != Model.PlayersOnline.Count)
{
<span>,</span>
}
}
}
}
@ -63,7 +65,9 @@
<div class="@(isMobile ? "" : "ui center aligned grid")">
<div class="eight wide column">
<div class="ui inverted pink segment">
<h1><i class="star icon"></i>@Model.Translate(LandingPageStrings.LatestTeamPicks)</h1>
<h1>
<i class="star icon"></i>@Model.Translate(LandingPageStrings.LatestTeamPicks)
</h1>
<div class="ui divider"></div>
<div class="ui left aligned segment">
@foreach (SlotEntity slot in Model.LatestTeamPicks!) @* Can't reach a point where this is null *@
@ -80,7 +84,9 @@
}
<div class="eight wide column">
<div class="ui inverted blue segment">
<h1><i class="globe americas icon"></i>@Model.Translate(LandingPageStrings.NewestLevels)</h1>
<h1>
<i class="globe americas icon"></i>@Model.Translate(LandingPageStrings.NewestLevels)
</h1>
<div class="ui divider"></div>
<div class="ui left aligned segment">
@foreach (SlotEntity slot in Model.NewestLevels!) @* Can't reach a point where this is null *@

View file

@ -13,14 +13,14 @@ namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages;
public class LandingPage : BaseLayout
{
public LandingPage(DatabaseContext database) : base(database)
{}
public List<SlotEntity>? LatestTeamPicks;
public List<SlotEntity>? NewestLevels;
public int PendingAuthAttempts;
public List<UserEntity> PlayersOnline = new();
public List<SlotEntity>? LatestTeamPicks;
public List<SlotEntity>? NewestLevels;
public LandingPage(DatabaseContext database) : base(database)
{ }
[UsedImplicitly]
public async Task<IActionResult> OnGet()
@ -29,10 +29,12 @@ public class LandingPage : BaseLayout
if (user != null && user.PasswordResetRequired) return this.Redirect("~/passwordResetRequired");
if (user != null)
this.PendingAuthAttempts = await this.Database.PlatformLinkAttempts
.CountAsync(l => l.UserId == user.UserId);
this.PendingAuthAttempts =
await this.Database.PlatformLinkAttempts.CountAsync(l => l.UserId == user.UserId);
List<int> userIds = await this.Database.LastContacts.Where(l => TimeHelper.Timestamp - l.Timestamp < 300).Select(l => l.UserId).ToListAsync();
List<int> userIds = await this.Database.LastContacts.Where(l => TimeHelper.Timestamp - l.Timestamp < 300)
.Select(l => l.UserId)
.ToListAsync();
this.PlayersOnline = await this.Database.Users.Where(u => userIds.Contains(u.UserId)).ToListAsync();

View file

@ -0,0 +1,48 @@
@page "/banned"
@using LBPUnion.ProjectLighthouse.Configuration
@using LBPUnion.ProjectLighthouse.Localization.StringLists
@model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Login.BannedUserPage
@{
Layout = "Layouts/BaseLayout";
Model.Title = Model.Translate(ModerationStrings.SuspensionHeading);
string timeZone = (string?)ViewData["TimeZone"] ?? TimeZoneInfo.Local.Id;
TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZone);
}
<div class="ui middle aligned left aligned">
<h1>@Model.Translate(ModerationStrings.SuspensionHeading)</h1>
<p>
@Model.Translate(ModerationStrings.SuspensionExplanation, ServerConfiguration.Instance.Customization.ServerName)
</p>
<p>
<span>
@if (Model.ModCase.ExpiresAt != null)
{
@Model.Translate(ModerationStrings.SuspensionExpiration, TimeZoneInfo.ConvertTime(Model.ModCase.ExpiresAt.Value, timeZoneInfo).ToString("M/d/yyyy @ h:mm tt"))
}
else
{
@Model.Translate(ModerationStrings.SuspensionExpiration, Model.Translate(ModerationStrings.DoesNotExpire))
}
</span>
<ul>
<li>@Model.Translate(ModerationStrings.LbpOnlineMultiplayer)</li>
<li>@Model.Translate(ModerationStrings.WebsiteInteractions)</li>
<li>@Model.Translate(ModerationStrings.ProfileVisibility)</li>
<li>@Model.Translate(ModerationStrings.AccountProfileManagement)</li>
</ul>
</p>
<p>
@Model.Translate(ModerationStrings.SuspensionReason, Model.ModCase.Reason)
</p>
<p>
@Model.Translate(ModerationStrings.SuspensionCircumventWarning)
</p>
</div>

View file

@ -0,0 +1,36 @@
using JetBrains.Annotations;
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
using LBPUnion.ProjectLighthouse.Types.Entities.Moderation;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Moderation.Cases;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.Login;
public class BannedUserPage : BaseLayout
{
public BannedUserPage(DatabaseContext database) : base(database)
{ }
public ModerationCaseEntity ModCase = null!;
[UsedImplicitly]
public async Task<IActionResult> OnGet()
{
UserEntity? user = this.Database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login");
if (!user.IsBanned) return this.Redirect("~/");
ModerationCaseEntity? modCase = await this.Database.Cases
.FirstOrDefaultAsync(c => c.AffectedId == user.UserId && c.Type == CaseType.UserBan);
if (modCase == null) return this.Redirect("~/");
this.ModCase = modCase;
return this.Page();
}
}

View file

@ -83,13 +83,6 @@ public class LoginForm : BaseLayout
return this.Page();
}
if (user.IsBanned)
{
Logger.Warn($"User {user.Username} (id: {user.UserId}) failed to login on web due to being banned", LogArea.Login);
this.Error = this.Translate(ErrorStrings.UserIsBanned, user.BannedReason);
return this.Page();
}
WebTokenEntity webToken = new()
{
UserId = user.UserId,
@ -120,6 +113,11 @@ public class LoginForm : BaseLayout
: this.Redirect("~/2fa" + "?redirect=" + HttpUtility.UrlEncode(redirect));
}
if (user.IsBanned)
{
return this.Redirect("~/banned");
}
if (user.PasswordResetRequired) return this.Redirect("~/passwordResetRequired");
return ServerConfiguration.Instance.Mail.MailEnabled switch

View file

@ -23,18 +23,18 @@
@if (Model.ProfileUser.IsBanned)
{
<div class="ui inverted red segment">
<h3 style="margin-bottom:3px;"><i class="ban icon"></i> This user is currently banned.</h3>
<h3 style="margin-bottom:5px;"><i class="ban icon"></i> This user is currently banned.</h3>
@if (Model.User != null && Model.User.IsModerator)
{
<b>Reason:</b>
<span>"@Model.ProfileUser.BannedReason"</span>
<span>"@Model.ProfileUser.BannedReason"</span> <br />
<p>
<i>Only you and other moderators may view the ban reason.</i>
</p>
}
else
{
<p>Users who engage in inappropriate, offensive, or violent actions will be moderated. Be sure to follow the rules!</p>
<p>This user has been banned for violating the Terms of Service. Remember to follow the rules!</p>
}
</div>
}