diff --git a/ProjectLighthouse/Controllers/LoginController.cs b/ProjectLighthouse/Controllers/LoginController.cs index f475ecb1..88368a2c 100644 --- a/ProjectLighthouse/Controllers/LoginController.cs +++ b/ProjectLighthouse/Controllers/LoginController.cs @@ -43,41 +43,59 @@ namespace LBPUnion.ProjectLighthouse.Controllers } if (loginData == null) return this.BadRequest(); - IPAddress? ipAddress = this.HttpContext.Connection.RemoteIpAddress; - if (ipAddress == null) return this.StatusCode(403, ""); // 403 probably isnt the best status code for this, but whatever + IPAddress? remoteIpAddress = this.HttpContext.Connection.RemoteIpAddress; + if (remoteIpAddress == null) return this.StatusCode(403, ""); // 403 probably isnt the best status code for this, but whatever - string userLocation = ipAddress.ToString(); + string ipAddress = remoteIpAddress.ToString(); - GameToken? token = await this.database.AuthenticateUser(loginData, userLocation, titleId); - if (token == null) return this.StatusCode(403, ""); + // Get an existing token from the IP & username + GameToken? token = await this.database.GameTokens.Include + (t => t.User) + .FirstOrDefaultAsync(t => t.UserLocation == ipAddress && t.User.Username == loginData.Username && !t.Used); + + if (token == null) // If we cant find an existing token, try to generate a new one + { + token = await this.database.AuthenticateUser(loginData, ipAddress, titleId); + if (token == null) return this.StatusCode(403, ""); // If not, then 403. + } User? user = await this.database.UserFromGameToken(token, true); if (user == null || user.Banned) return this.StatusCode(403, ""); if (ServerSettings.Instance.UseExternalAuth) { - string ipAddressAndName = $"{token.UserLocation}|{user.Username}"; - if (DeniedAuthenticationHelper.RecentlyDenied(ipAddressAndName) || DeniedAuthenticationHelper.GetAttempts(ipAddressAndName) > 3) + if (ServerSettings.Instance.BlockDeniedUsers) { - this.database.AuthenticationAttempts.RemoveRange - (this.database.AuthenticationAttempts.Include(a => a.GameToken).Where(a => a.GameToken.UserId == user.UserId)); + string ipAddressAndName = $"{token.UserLocation}|{user.Username}"; + if (DeniedAuthenticationHelper.RecentlyDenied(ipAddressAndName) || DeniedAuthenticationHelper.GetAttempts(ipAddressAndName) > 3) + { + this.database.AuthenticationAttempts.RemoveRange + (this.database.AuthenticationAttempts.Include(a => a.GameToken).Where(a => a.GameToken.UserId == user.UserId)); - DeniedAuthenticationHelper.AddAttempt(ipAddressAndName); + DeniedAuthenticationHelper.AddAttempt(ipAddressAndName); - await this.database.SaveChangesAsync(); - return this.StatusCode(403, ""); + await this.database.SaveChangesAsync(); + return this.StatusCode(403, ""); + } } - AuthenticationAttempt authAttempt = new() + if (this.database.UserApprovedIpAddresses.Where(a => a.UserId == user.UserId).Select(a => a.IpAddress).Contains(ipAddress)) { - GameToken = token, - GameTokenId = token.TokenId, - Timestamp = TimestampHelper.Timestamp, - IPAddress = userLocation, - Platform = token.GameVersion == GameVersion.LittleBigPlanetVita ? Platform.Vita : Platform.PS3, // TODO: properly identify RPCS3 - }; + token.Approved = true; + } + else + { + AuthenticationAttempt authAttempt = new() + { + GameToken = token, + GameTokenId = token.TokenId, + Timestamp = TimestampHelper.Timestamp, + IPAddress = ipAddress, + Platform = token.GameVersion == GameVersion.LittleBigPlanetVita ? Platform.Vita : Platform.PS3, // TODO: properly identify RPCS3 + }; - this.database.AuthenticationAttempts.Add(authAttempt); + this.database.AuthenticationAttempts.Add(authAttempt); + } } else { @@ -86,9 +104,18 @@ namespace LBPUnion.ProjectLighthouse.Controllers await this.database.SaveChangesAsync(); - Logger.Log($"Successfully logged in user {user.Username} as {token.GameVersion} client ({titleId})", LoggerLevelLogin.Instance); + if (!token.Approved) return this.StatusCode(403, ""); - // Create a new room on LBP2+/Vita + Logger.Log($"Successfully logged in user {user.Username} as {token.GameVersion} client ({titleId})", LoggerLevelLogin.Instance); + // After this point we are now considering this session as logged in. + + // We just logged in with the token. Mark it as used so someone else doesnt try to use it, + // and so we don't pick the same token up when logging in later. + token.Used = true; + + await this.database.SaveChangesAsync(); + + // Create a new room on LBP2/3/Vita if (token.GameVersion != GameVersion.LittleBigPlanet1) RoomHelper.CreateRoom(user); return this.Ok diff --git a/ProjectLighthouse/Controllers/MessageController.cs b/ProjectLighthouse/Controllers/MessageController.cs index 80851bbe..c16f6921 100644 --- a/ProjectLighthouse/Controllers/MessageController.cs +++ b/ProjectLighthouse/Controllers/MessageController.cs @@ -1,3 +1,4 @@ +#nullable enable using System.IO; using System.Threading.Tasks; using Kettu; @@ -22,25 +23,44 @@ namespace LBPUnion.ProjectLighthouse.Controllers } [HttpGet("eula")] - public IActionResult Eula() => this.Ok(ServerSettings.Instance.EulaText + "\n" + $"{EulaHelper.License}\n"); + public async Task Eula() + { + User? user = await this.database.UserFromGameRequest(this.Request); + if (user == null) return this.StatusCode(403, ""); + + return this.Ok(ServerSettings.Instance.EulaText + "\n" + $"{EulaHelper.License}\n"); + } [HttpGet("announce")] public async Task Announce() { - User user = await this.database.UserFromGameRequest(this.Request, true); + #if !DEBUG + User? user = await this.database.UserFromGameRequest(this.Request); if (user == null) return this.StatusCode(403, ""); + #else + (User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request); - if (ServerSettings.Instance.UseExternalAuth) - return this.Ok - ( - "Please stay on this screen.\n" + - $"Before continuing, you must approve this session at {ServerSettings.Instance.ExternalUrl}.\n" + - "Please keep in mind that if the session is denied you may have to wait up to 5-10 minutes to try logging in again.\n" + - "Once approved, you may press X and continue.\n\n" + - ServerSettings.Instance.EulaText - ); + if (userAndToken == null) return this.StatusCode(403, ""); - return this.Ok($"You are now logged in as {user.Username} (id: {user.UserId}).\n\n" + ServerSettings.Instance.EulaText); + // ReSharper disable once PossibleInvalidOperationException + User user = userAndToken.Value.Item1; + GameToken gameToken = userAndToken.Value.Item2; + #endif + + return this.Ok + ( + $"You are now logged in as {user.Username}.\n\n" + + #if DEBUG + "---DEBUG INFO---\n" + + $"user.UserId: {user.UserId}\n" + + $"token.Approved: {gameToken.Approved}\n" + + $"token.Used: {gameToken.Used}\n" + + $"token.UserLocation: {gameToken.UserLocation}\n" + + $"token.GameVersion: {gameToken.GameVersion}\n" + + "---DEBUG INFO---\n\n" + + #endif + ServerSettings.Instance.EulaText + ); } [HttpGet("notification")] @@ -52,7 +72,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers [HttpPost("filter")] public async Task Filter() { - User user = await this.database.UserFromGameRequest(this.Request); + User? user = await this.database.UserFromGameRequest(this.Request); if (user == null) return this.StatusCode(403, ""); string loggedText = await new StreamReader(this.Request.Body).ReadToEndAsync(); diff --git a/ProjectLighthouse/Controllers/Website/ExternalAuth/AutoApprovalController.cs b/ProjectLighthouse/Controllers/Website/ExternalAuth/AutoApprovalController.cs new file mode 100644 index 00000000..f801b055 --- /dev/null +++ b/ProjectLighthouse/Controllers/Website/ExternalAuth/AutoApprovalController.cs @@ -0,0 +1,67 @@ +#nullable enable +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Types; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace LBPUnion.ProjectLighthouse.Controllers.Website.ExternalAuth +{ + [ApiController] + [Route("/authentication")] + public class AutoApprovalController : ControllerBase + { + private readonly Database database; + + public AutoApprovalController(Database database) + { + this.database = database; + } + + [HttpGet("autoApprove/{id:int}")] + public async Task AutoApprove([FromRoute] int id) + { + User? user = this.database.UserFromWebRequest(this.Request); + if (user == null) return this.Redirect("/login"); + + AuthenticationAttempt? authAttempt = await this.database.AuthenticationAttempts.Include + (a => a.GameToken) + .FirstOrDefaultAsync(a => a.AuthenticationAttemptId == id); + + if (authAttempt == null) return this.BadRequest(); + if (authAttempt.GameToken.UserId != user.UserId) return this.Redirect("/login"); + + authAttempt.GameToken.Approved = true; + + UserApprovedIpAddress approvedIpAddress = new() + { + UserId = user.UserId, + User = user, + IpAddress = authAttempt.IPAddress, + }; + + this.database.UserApprovedIpAddresses.Add(approvedIpAddress); + this.database.AuthenticationAttempts.Remove(authAttempt); + + await this.database.SaveChangesAsync(); + + return this.Redirect("/authentication"); + } + + [HttpGet("revokeAutoApproval/{id:int}")] + public async Task RevokeAutoApproval([FromRoute] int id) + { + User? user = this.database.UserFromWebRequest(this.Request); + if (user == null) return this.Redirect("/login"); + + UserApprovedIpAddress? approvedIpAddress = await this.database.UserApprovedIpAddresses.FirstOrDefaultAsync(a => a.UserApprovedIpAddressId == id); + if (approvedIpAddress == null) return this.BadRequest(); + if (approvedIpAddress.UserId != user.UserId) return this.Redirect("/login"); + + this.database.UserApprovedIpAddresses.Remove(approvedIpAddress); + + await this.database.SaveChangesAsync(); + + return this.Redirect("/authentication/autoApprovals"); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Database.cs b/ProjectLighthouse/Database.cs index 2af08e30..9fda3cab 100644 --- a/ProjectLighthouse/Database.cs +++ b/ProjectLighthouse/Database.cs @@ -34,6 +34,7 @@ namespace LBPUnion.ProjectLighthouse public DbSet AuthenticationAttempts { get; set; } public DbSet Reviews { get; set; } public DbSet RatedReviews { get; set; } + public DbSet UserApprovedIpAddresses { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder options) => options.UseMySql(ServerSettings.Instance.DbConnectionString, MySqlServerVersion.LatestSupportedServerVersion); @@ -66,7 +67,6 @@ namespace LBPUnion.ProjectLighthouse #nullable enable public async Task AuthenticateUser(LoginData loginData, string userLocation, string titleId = "") { - // TODO: don't use psn name to authenticate User? user = await this.Users.FirstOrDefaultAsync(u => u.Username == loginData.Username); if (user == null) return null; diff --git a/ProjectLighthouse/Migrations/20211213195540_AddUserApprovedIpAddresses.cs b/ProjectLighthouse/Migrations/20211213195540_AddUserApprovedIpAddresses.cs new file mode 100644 index 00000000..4f9264df --- /dev/null +++ b/ProjectLighthouse/Migrations/20211213195540_AddUserApprovedIpAddresses.cs @@ -0,0 +1,50 @@ +using LBPUnion.ProjectLighthouse; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ProjectLighthouse.Migrations +{ + [DbContext(typeof(Database))] + [Migration("20211213195540_AddUserApprovedIpAddresses")] + public partial class AddUserApprovedIpAddresses : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "UserApprovedIpAddresses", + columns: table => new + { + UserApprovedIpAddressId = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + UserId = table.Column(type: "int", nullable: false), + IpAddress = table.Column(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"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UserApprovedIpAddresses"); + } + } +} diff --git a/ProjectLighthouse/Migrations/20211214005427_AddUsedBoolToGameToken.cs b/ProjectLighthouse/Migrations/20211214005427_AddUsedBoolToGameToken.cs new file mode 100644 index 00000000..359a10ef --- /dev/null +++ b/ProjectLighthouse/Migrations/20211214005427_AddUsedBoolToGameToken.cs @@ -0,0 +1,55 @@ +using LBPUnion.ProjectLighthouse; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ProjectLighthouse.Migrations +{ + [DbContext(typeof(Database))] + [Migration("20211214005427_AddUsedBoolToGameToken")] + public partial class AddUsedBoolToGameToken : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + // Incompatible with old tokens + migrationBuilder.Sql("DELETE FROM AuthenticationAttempts"); + migrationBuilder.Sql("DELETE FROM GameTokens"); + + migrationBuilder.AddColumn( + name: "Used", + table: "GameTokens", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + + migrationBuilder.CreateIndex( + name: "IX_GameTokens_UserId", + table: "GameTokens", + column: "UserId"); + + migrationBuilder.AddForeignKey( + name: "FK_GameTokens_Users_UserId", + table: "GameTokens", + column: "UserId", + principalTable: "Users", + principalColumn: "UserId", + onDelete: ReferentialAction.Cascade); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_GameTokens_Users_UserId", + table: "GameTokens"); + + migrationBuilder.DropIndex( + name: "IX_GameTokens_UserId", + table: "GameTokens"); + + migrationBuilder.DropColumn( + name: "Used", + table: "GameTokens"); + } + } +} diff --git a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs index def721ba..b29e1cc0 100644 --- a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs +++ b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs @@ -55,6 +55,9 @@ namespace ProjectLighthouse.Migrations b.Property("GameVersion") .HasColumnType("int"); + b.Property("Used") + .HasColumnType("tinyint(1)"); + b.Property("UserId") .HasColumnType("int"); @@ -66,6 +69,8 @@ namespace ProjectLighthouse.Migrations b.HasKey("TokenId"); + b.HasIndex("UserId"); + b.ToTable("GameTokens"); }); @@ -593,6 +598,25 @@ namespace ProjectLighthouse.Migrations b.ToTable("Users"); }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.UserApprovedIpAddress", b => + { + b.Property("UserApprovedIpAddressId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("IpAddress") + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("UserApprovedIpAddressId"); + + b.HasIndex("UserId"); + + b.ToTable("UserApprovedIpAddresses"); + }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.WebToken", b => { b.Property("TokenId") @@ -621,6 +645,17 @@ namespace ProjectLighthouse.Migrations b.Navigation("GameToken"); }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.GameToken", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.HeartedProfile", b => { b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "HeartedUser") @@ -835,6 +870,17 @@ namespace ProjectLighthouse.Migrations b.Navigation("Location"); }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.UserApprovedIpAddress", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); #pragma warning restore 612, 618 } } diff --git a/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml b/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml index 58521e46..d5a56773 100644 --- a/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml +++ b/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml @@ -4,8 +4,8 @@ @{ Layout = "Layouts/BaseLayout"; + Model.Title = "Authentication"; } -

Authentication

@if (Model.AuthenticationAttempts.Count == 0) { @@ -14,22 +14,48 @@ else {

You have @Model.AuthenticationAttempts.Count authentication attempts pending.

- - - + @if (Model.IpAddress != null) + { +

This device's IP address is @(Model.IpAddress.ToString()). If this matches with an authentication attempt below, then it's safe to assume the authentication attempt came from the same network as this device.

+ } } + + + + + + + @foreach (AuthenticationAttempt authAttempt in Model.AuthenticationAttempts) { DateTimeOffset timestamp = DateTimeOffset.FromUnixTimeSeconds(authAttempt.Timestamp);

A @authAttempt.Platform authentication request was logged at @timestamp.ToString("MM/dd/yyyy @ h:mm tt") UTC from the IP address @authAttempt.IPAddress.

diff --git a/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml.cs b/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml.cs index 13a1f5eb..6ca2576a 100644 --- a/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml.cs +++ b/ProjectLighthouse/Pages/ExternalAuth/AuthenticationPage.cshtml.cs @@ -1,5 +1,7 @@ +#nullable enable using System.Collections.Generic; using System.Linq; +using System.Net; using System.Threading.Tasks; using LBPUnion.ProjectLighthouse.Pages.Layouts; using LBPUnion.ProjectLighthouse.Types; @@ -13,12 +15,17 @@ namespace LBPUnion.ProjectLighthouse.Pages.ExternalAuth { public List AuthenticationAttempts; + + public IPAddress? IpAddress; public AuthenticationPage(Database database) : base(database) {} public async Task OnGet() { if (!ServerSettings.Instance.UseExternalAuth) return this.NotFound(); + if (this.User == null) return this.StatusCode(403, ""); + + this.IpAddress = this.HttpContext.Connection.RemoteIpAddress; this.AuthenticationAttempts = this.Database.AuthenticationAttempts.Include (a => a.GameToken) diff --git a/ProjectLighthouse/Pages/ExternalAuth/ManageUserApprovedIpAddressesPage.cshtml b/ProjectLighthouse/Pages/ExternalAuth/ManageUserApprovedIpAddressesPage.cshtml new file mode 100644 index 00000000..dde2fa8a --- /dev/null +++ b/ProjectLighthouse/Pages/ExternalAuth/ManageUserApprovedIpAddressesPage.cshtml @@ -0,0 +1,22 @@ +@page "/authentication/autoApprovals" +@using LBPUnion.ProjectLighthouse.Types +@model LBPUnion.ProjectLighthouse.Pages.ExternalAuth.ManageUserApprovedIpAddressesPage + +@{ + Layout = "Layouts/BaseLayout"; + Model.Title = "Automatically approved IP addresses"; +} + + +@foreach (UserApprovedIpAddress approvedIpAddress in Model.ApprovedIpAddresses) +{ +
+

@approvedIpAddress.IpAddress

+ + + +
+} \ No newline at end of file diff --git a/ProjectLighthouse/Pages/ExternalAuth/ManageUserApprovedIpAddressesPage.cshtml.cs b/ProjectLighthouse/Pages/ExternalAuth/ManageUserApprovedIpAddressesPage.cshtml.cs new file mode 100644 index 00000000..00dfa3be --- /dev/null +++ b/ProjectLighthouse/Pages/ExternalAuth/ManageUserApprovedIpAddressesPage.cshtml.cs @@ -0,0 +1,29 @@ +#nullable enable +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Pages.Layouts; +using LBPUnion.ProjectLighthouse.Types; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace LBPUnion.ProjectLighthouse.Pages.ExternalAuth +{ + public class ManageUserApprovedIpAddressesPage : BaseLayout + { + public ManageUserApprovedIpAddressesPage(Database database) : base(database) + {} + + public List ApprovedIpAddresses; + + public async Task 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(); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Pages/LandingPage.cshtml b/ProjectLighthouse/Pages/LandingPage.cshtml index fc67aaae..3a977b88 100644 --- a/ProjectLighthouse/Pages/LandingPage.cshtml +++ b/ProjectLighthouse/Pages/LandingPage.cshtml @@ -1,5 +1,6 @@ @page "/" @using LBPUnion.ProjectLighthouse.Types +@using LBPUnion.ProjectLighthouse.Types.Settings @model LBPUnion.ProjectLighthouse.Pages.LandingPage @{ @@ -11,6 +12,12 @@ @if (Model.User != null) {

You are currently logged in as @Model.User.Username.

+ if (ServerSettings.Instance.UseExternalAuth && Model.AuthenticationAttemptsCount > 0) + { +

+ You have @Model.AuthenticationAttemptsCount authentication attempts pending. Click here to view them. +

+ } } @if (Model.PlayersOnlineCount == 1) @@ -21,6 +28,7 @@ @user.Username } } + else if (Model.PlayersOnlineCount == 0) {

There are no users online. Why not hop on?

diff --git a/ProjectLighthouse/Pages/LandingPage.cshtml.cs b/ProjectLighthouse/Pages/LandingPage.cshtml.cs index 23289463..de398d10 100644 --- a/ProjectLighthouse/Pages/LandingPage.cshtml.cs +++ b/ProjectLighthouse/Pages/LandingPage.cshtml.cs @@ -16,6 +16,8 @@ namespace LBPUnion.ProjectLighthouse.Pages public List PlayersOnline; public int PlayersOnlineCount; + + public int AuthenticationAttemptsCount; public LandingPage(Database database) : base(database) {} @@ -27,6 +29,13 @@ namespace LBPUnion.ProjectLighthouse.Pages this.PlayersOnlineCount = await StatisticsHelper.RecentMatches(); + if (user != null) + { + this.AuthenticationAttemptsCount = await this.Database.AuthenticationAttempts.Include + (a => a.GameToken) + .CountAsync(a => a.GameToken.UserId == user.UserId); + } + List userIds = await this.Database.LastContacts.Where(l => TimestampHelper.Timestamp - l.Timestamp < 300).Select(l => l.UserId).ToListAsync(); this.PlayersOnline = await this.Database.Users.Where(u => userIds.Contains(u.UserId)).ToListAsync(); diff --git a/ProjectLighthouse/Pages/UserPage.cshtml b/ProjectLighthouse/Pages/UserPage.cshtml index a6c7a5af..1b294497 100644 --- a/ProjectLighthouse/Pages/UserPage.cshtml +++ b/ProjectLighthouse/Pages/UserPage.cshtml @@ -2,6 +2,8 @@ @using LBPUnion.ProjectLighthouse.Types @using LBPUnion.ProjectLighthouse.Types.Profiles @using LBPUnion.ProjectLighthouse.Types.Settings +@using System.Web; +@using System.IO; @model LBPUnion.ProjectLighthouse.Pages.UserPage @{ @@ -114,9 +116,12 @@ @foreach (Comment comment in Model.Comments!) { DateTimeOffset timestamp = DateTimeOffset.FromUnixTimeSeconds(comment.Timestamp / 1000); + StringWriter messageWriter = new StringWriter(); + HttpUtility.HtmlDecode(comment.Message, messageWriter); + String decodedMessage = messageWriter.ToString();
@comment.Poster.Username: - @comment.Message + @decodedMessage

@timestamp.ToString("MM/dd/yyyy @ h:mm tt") UTC

diff --git a/ProjectLighthouse/Types/GameToken.cs b/ProjectLighthouse/Types/GameToken.cs index 4fdca27d..74ea937d 100644 --- a/ProjectLighthouse/Types/GameToken.cs +++ b/ProjectLighthouse/Types/GameToken.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace LBPUnion.ProjectLighthouse.Types { @@ -10,12 +11,19 @@ namespace LBPUnion.ProjectLighthouse.Types public int UserId { get; set; } + [ForeignKey(nameof(UserId))] + public User User { get; set; } + public string UserToken { get; set; } public string UserLocation { get; set; } public GameVersion GameVersion { get; set; } - public bool Approved { get; set; } = false; + // Set by /authentication webpage + public bool Approved { get; set; } + + // Set to true on login + public bool Used { get; set; } } } \ No newline at end of file diff --git a/ProjectLighthouse/Types/Settings/ServerSettings.cs b/ProjectLighthouse/Types/Settings/ServerSettings.cs index ef1943e3..bdbb7676 100644 --- a/ProjectLighthouse/Types/Settings/ServerSettings.cs +++ b/ProjectLighthouse/Types/Settings/ServerSettings.cs @@ -12,7 +12,7 @@ namespace LBPUnion.ProjectLighthouse.Types.Settings public class ServerSettings { - public const int CurrentConfigVersion = 12; // MUST BE INCREMENTED FOR EVERY CONFIG CHANGE! + public const int CurrentConfigVersion = 13; // MUST BE INCREMENTED FOR EVERY CONFIG CHANGE! static ServerSettings() { if (ServerStatics.IsUnitTesting) return; // Unit testing, we don't want to read configurations here since the tests will provide their own @@ -97,6 +97,8 @@ namespace LBPUnion.ProjectLighthouse.Types.Settings public string GoogleAnalyticsId { get; set; } = ""; + public bool BlockDeniedUsers = true; + #region Meta [NotNull] diff --git a/ProjectLighthouse/Types/UserApprovedIpAddress.cs b/ProjectLighthouse/Types/UserApprovedIpAddress.cs new file mode 100644 index 00000000..06c84183 --- /dev/null +++ b/ProjectLighthouse/Types/UserApprovedIpAddress.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace LBPUnion.ProjectLighthouse.Types +{ + 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; } + } +} \ No newline at end of file