Add ability to approve an IP address

This commit is contained in:
jvyden 2021-12-13 15:59:33 -05:00
commit 65e7771ce3
No known key found for this signature in database
GPG key ID: 18BCF2BE0262B278
11 changed files with 273 additions and 38 deletions

View file

@ -1,4 +1,5 @@
#nullable enable
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
@ -43,12 +44,12 @@ 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);
GameToken? token = await this.database.AuthenticateUser(loginData, ipAddress, titleId);
if (token == null) return this.StatusCode(403, "");
User? user = await this.database.UserFromGameToken(token, true);
@ -56,28 +57,38 @@ namespace LBPUnion.ProjectLighthouse.Controllers
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()
{
GameToken = token,
GameTokenId = token.TokenId,
Timestamp = TimestampHelper.Timestamp,
IPAddress = userLocation,
Platform = token.GameVersion == GameVersion.LittleBigPlanetVita ? Platform.Vita : Platform.PS3, // TODO: properly identify RPCS3
};
List<UserApprovedIpAddress> approvedIpAddresses = await this.database.UserApprovedIpAddresses.Where(a => a.UserId == user.UserId).ToListAsync();
bool ipAddressApproved = approvedIpAddresses.Select(a => a.IpAddress).Contains(ipAddress);
this.database.AuthenticationAttempts.Add(authAttempt);
if (ipAddressApproved) 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);
}
}
else
{

View file

@ -1,3 +1,4 @@
#nullable enable
using System.IO;
using System.Threading.Tasks;
using Kettu;
@ -27,10 +28,15 @@ namespace LBPUnion.ProjectLighthouse.Controllers
[HttpGet("announce")]
public async Task<IActionResult> Announce()
{
User user = await this.database.UserFromGameRequest(this.Request, true);
if (user == null) return this.StatusCode(403, "");
(User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request);
if (ServerSettings.Instance.UseExternalAuth)
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" +

View file

@ -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<IActionResult> 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<IActionResult> 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");
}
}
}

View file

@ -34,6 +34,7 @@ namespace LBPUnion.ProjectLighthouse
public DbSet<AuthenticationAttempt> AuthenticationAttempts { get; set; }
public DbSet<Review> Reviews { get; set; }
public DbSet<RatedReview> RatedReviews { get; set; }
public DbSet<UserApprovedIpAddress> 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<GameToken?> 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;

View file

@ -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<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");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "UserApprovedIpAddresses");
}
}
}

View file

@ -587,6 +587,25 @@ namespace ProjectLighthouse.Migrations
b.ToTable("Users");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.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.Types.WebToken", b =>
{
b.Property<int>("TokenId")
@ -829,6 +848,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
}
}

View file

@ -18,21 +18,21 @@ else
{
<p>This device's IP address is <b>@(Model.IpAddress.ToString())</b>. 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.</p>
}
<a href="/authentication/autoApprovals">
<button class="ui small blue button">
<i class="cog icon"></i>
<span>Manage automatically approved IP addresses</span>
</button>
</a>
<a href="/authentication/denyAll">
<button class="ui small red button">
<i class="x icon"></i>
<span>Deny all</span>
</button>
</a>
}
<a href="/authentication/autoApprovals">
<button class="ui small blue button">
<i class="cog icon"></i>
<span>Manage automatically approved IP addresses</span>
</button>
</a>
<a href="/authentication/denyAll">
<button class="ui small red button">
<i class="x icon"></i>
<span>Deny all</span>
</button>
</a>
@foreach (AuthenticationAttempt authAttempt in Model.AuthenticationAttempts)
{
DateTimeOffset timestamp = DateTimeOffset.FromUnixTimeSeconds(authAttempt.Timestamp);

View file

@ -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)
{
<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>
}

View file

@ -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<UserApprovedIpAddress> ApprovedIpAddresses;
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();
}
}
}

View file

@ -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]

View file

@ -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; }
}
}