diff --git a/ProjectLighthouse/Controllers/LoginController.cs b/ProjectLighthouse/Controllers/LoginController.cs index ced0fe44..85c04ef5 100644 --- a/ProjectLighthouse/Controllers/LoginController.cs +++ b/ProjectLighthouse/Controllers/LoginController.cs @@ -1,5 +1,4 @@ #nullable enable -using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; @@ -49,8 +48,16 @@ namespace LBPUnion.ProjectLighthouse.Controllers string ipAddress = remoteIpAddress.ToString(); - GameToken? token = await this.database.AuthenticateUser(loginData, ipAddress, 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.Approved && !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) return this.StatusCode(403, ""); @@ -72,10 +79,10 @@ namespace LBPUnion.ProjectLighthouse.Controllers } } - List approvedIpAddresses = await this.database.UserApprovedIpAddresses.Where(a => a.UserId == user.UserId).ToListAsync(); - bool ipAddressApproved = approvedIpAddresses.Select(a => a.IpAddress).Contains(ipAddress); - - if (ipAddressApproved) token.Approved = true; + if (this.database.UserApprovedIpAddresses.Where + (a => a.UserId == user.UserId) + .Select(a => a.IpAddress) + .Contains(ipAddress)) token.Approved = true; else { AuthenticationAttempt authAttempt = new() @@ -100,8 +107,15 @@ namespace LBPUnion.ProjectLighthouse.Controllers if (!token.Approved) return this.StatusCode(403, ""); 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. - // Create a new room on LBP2+/Vita + // 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 83531921..f1ec7748 100644 --- a/ProjectLighthouse/Controllers/MessageController.cs +++ b/ProjectLighthouse/Controllers/MessageController.cs @@ -23,28 +23,19 @@ 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, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request); - - if (userAndToken == null) return this.StatusCode(403, ""); - - // ReSharper disable once PossibleInvalidOperationException - User user = userAndToken.Value.Item1; - GameToken gameToken = userAndToken.Value.Item2; - - if (ServerSettings.Instance.UseExternalAuth && !gameToken.Approved) - 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 - ); + User? user = await this.database.UserFromGameRequest(this.Request); + if (user == null) return this.StatusCode(403, ""); return this.Ok($"You are now logged in as {user.Username} (id: {user.UserId}).\n\n" + ServerSettings.Instance.EulaText); } 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 64dfac1f..cdb0c96a 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"); }); @@ -634,6 +639,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") 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