Make all tokens expire

Closes #335
This commit is contained in:
jvyden 2022-07-29 15:08:41 -04:00
parent a8410fe352
commit 4ba75f09a9
No known key found for this signature in database
GPG key ID: 18BCF2BE0262B278
16 changed files with 188 additions and 10 deletions

View file

@ -3,7 +3,7 @@
"isRoot": true, "isRoot": true,
"tools": { "tools": {
"dotnet-ef": { "dotnet-ef": {
"version": "6.0.5", "version": "6.0.7",
"commands": [ "commands": [
"dotnet-ef" "dotnet-ef"
] ]

View file

@ -57,9 +57,10 @@ public class LoginController : ControllerBase
string ipAddress = remoteIpAddress.ToString(); string ipAddress = remoteIpAddress.ToString();
await this.database.RemoveExpiredTokens();
// Get an existing token from the IP & username // Get an existing token from the IP & username
GameToken? token = await this.database.GameTokens.Include GameToken? token = await this.database.GameTokens.Include(t => t.User)
(t => t.User)
.FirstOrDefaultAsync(t => t.UserLocation == ipAddress && t.User.Username == npTicket.Username && !t.Used); .FirstOrDefaultAsync(t => t.UserLocation == ipAddress && t.User.Username == npTicket.Username && !t.Used);
if (token == null) // If we cant find an existing token, try to generate a new one if (token == null) // If we cant find an existing token, try to generate a new one
@ -67,7 +68,8 @@ public class LoginController : ControllerBase
token = await this.database.AuthenticateUser(npTicket, ipAddress); token = await this.database.AuthenticateUser(npTicket, ipAddress);
if (token == null) if (token == null)
{ {
Logger.Warn($"Unable to find/generate a token for username {npTicket.Username}", LogArea.Login); Logger.Warn($"Unable to " +
$"find/generate a token for username {npTicket.Username}", LogArea.Login);
return this.StatusCode(403, ""); // If not, then 403. return this.StatusCode(403, ""); // If not, then 403.
} }
} }

View file

@ -1,4 +1,5 @@
#nullable enable #nullable enable
using System.Globalization;
using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Logging;
@ -75,6 +76,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.";
$"token.Used: {gameToken.Used}\n" + $"token.Used: {gameToken.Used}\n" +
$"token.UserLocation: {gameToken.UserLocation}\n" + $"token.UserLocation: {gameToken.UserLocation}\n" +
$"token.GameVersion: {gameToken.GameVersion}\n" + $"token.GameVersion: {gameToken.GameVersion}\n" +
$"token.ExpiresAt: {gameToken.ExpiresAt.ToString(CultureInfo.CurrentCulture)}\n" +
"---DEBUG INFO---" + "---DEBUG INFO---" +
#endif #endif
"\n" "\n"

View file

@ -73,6 +73,7 @@ public class LoginForm : BaseLayout
UserId = user.UserId, UserId = user.UserId,
User = user, User = user,
EmailToken = CryptoHelper.GenerateAuthToken(), EmailToken = CryptoHelper.GenerateAuthToken(),
ExpiresAt = DateTime.Now + TimeSpan.FromHours(6),
}; };
this.Database.EmailSetTokens.Add(emailSetToken); this.Database.EmailSetTokens.Add(emailSetToken);
@ -85,6 +86,7 @@ public class LoginForm : BaseLayout
{ {
UserId = user.UserId, UserId = user.UserId,
UserToken = CryptoHelper.GenerateAuthToken(), UserToken = CryptoHelper.GenerateAuthToken(),
ExpiresAt = DateTime.Now + TimeSpan.FromDays(7),
}; };
this.Database.WebTokens.Add(webToken); this.Database.WebTokens.Add(webToken);

View file

@ -94,6 +94,7 @@ public class RegisterForm : BaseLayout
{ {
UserId = user.UserId, UserId = user.UserId,
UserToken = CryptoHelper.GenerateAuthToken(), UserToken = CryptoHelper.GenerateAuthToken(),
ExpiresAt = DateTime.Now + TimeSpan.FromDays(7),
}; };
this.Database.WebTokens.Add(webToken); this.Database.WebTokens.Add(webToken);

View file

@ -49,15 +49,18 @@ public class SetEmailForm : BaseLayout
UserId = user.UserId, UserId = user.UserId,
User = user, User = user,
EmailToken = CryptoHelper.GenerateAuthToken(), EmailToken = CryptoHelper.GenerateAuthToken(),
ExpiresAt = DateTime.Now + TimeSpan.FromHours(6),
}; };
this.Database.EmailVerificationTokens.Add(emailVerifyToken); this.Database.EmailVerificationTokens.Add(emailVerifyToken);
// The user just set their email address. Now, let's grant them a token to proceed with verifying the email. // The user just set their email address. Now, let's grant them a token to proceed with verifying the email.
// TODO: insecure
WebToken webToken = new() WebToken webToken = new()
{ {
UserId = user.UserId, UserId = user.UserId,
UserToken = CryptoHelper.GenerateAuthToken(), UserToken = CryptoHelper.GenerateAuthToken(),
ExpiresAt = DateTime.Now + TimeSpan.FromDays(7),
}; };
this.Response.Cookies.Append this.Response.Cookies.Append

View file

@ -25,6 +25,7 @@ public class AdminTests : LighthouseWebTest
{ {
UserId = user.UserId, UserId = user.UserId,
UserToken = CryptoHelper.GenerateAuthToken(), UserToken = CryptoHelper.GenerateAuthToken(),
ExpiresAt = DateTime.Now + TimeSpan.FromHours(1),
}; };
database.WebTokens.Add(webToken); database.WebTokens.Add(webToken);
@ -49,6 +50,7 @@ public class AdminTests : LighthouseWebTest
{ {
UserId = user.UserId, UserId = user.UserId,
UserToken = CryptoHelper.GenerateAuthToken(), UserToken = CryptoHelper.GenerateAuthToken(),
ExpiresAt = DateTime.Now + TimeSpan.FromHours(1),
}; };
database.WebTokens.Add(webToken); database.WebTokens.Add(webToken);

View file

@ -88,6 +88,7 @@ public class AuthenticationTests : LighthouseWebTest
{ {
UserId = user.UserId, UserId = user.UserId,
UserToken = CryptoHelper.GenerateAuthToken(), UserToken = CryptoHelper.GenerateAuthToken(),
ExpiresAt = DateTime.Now + TimeSpan.FromHours(1),
}; };
database.WebTokens.Add(webToken); database.WebTokens.Add(webToken);

View file

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Administration; using LBPUnion.ProjectLighthouse.Administration;
using LBPUnion.ProjectLighthouse.Administration.Reports; using LBPUnion.ProjectLighthouse.Administration.Reports;
using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Levels; using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.Levels.Categories; using LBPUnion.ProjectLighthouse.Levels.Categories;
@ -111,6 +112,8 @@ public class Database : DbContext
UserLocation = userLocation, UserLocation = userLocation,
GameVersion = npTicket.GameVersion, GameVersion = npTicket.GameVersion,
Platform = npTicket.Platform, Platform = npTicket.Platform,
// we can get away with a low expiry here since LBP will just get a new token everytime it gets 403'd
ExpiresAt = DateTime.Now + TimeSpan.FromHours(1),
}; };
this.GameTokens.Add(gameToken); this.GameTokens.Add(gameToken);
@ -289,6 +292,13 @@ public class Database : DbContext
if (token == null) return null; if (token == null) return null;
if (!allowUnapproved && !token.Approved) return null; if (!allowUnapproved && !token.Approved) return null;
if (DateTime.Now > token.ExpiresAt)
{
this.Remove(token);
await this.SaveChangesAsync();
return null;
}
return await this.Users.Include(u => u.Location).FirstOrDefaultAsync(u => u.UserId == token.UserId); return await this.Users.Include(u => u.Location).FirstOrDefaultAsync(u => u.UserId == token.UserId);
} }
@ -314,6 +324,13 @@ public class Database : DbContext
if (token == null) return null; if (token == null) return null;
if (!allowUnapproved && !token.Approved) return null; if (!allowUnapproved && !token.Approved) return null;
if (DateTime.Now > token.ExpiresAt)
{
this.Remove(token);
await this.SaveChangesAsync();
return null;
}
return token; return token;
} }
@ -326,6 +343,13 @@ public class Database : DbContext
if (token == null) return null; if (token == null) return null;
if (!allowUnapproved && !token.Approved) return null; if (!allowUnapproved && !token.Approved) return null;
if (DateTime.Now > token.ExpiresAt)
{
this.Remove(token);
await this.SaveChangesAsync();
return null;
}
User? user = await this.UserFromGameToken(token); User? user = await this.UserFromGameToken(token);
if (user == null) return null; if (user == null) return null;
@ -342,6 +366,13 @@ public class Database : DbContext
WebToken? token = this.WebTokens.FirstOrDefault(t => t.UserToken == lighthouseToken); WebToken? token = this.WebTokens.FirstOrDefault(t => t.UserToken == lighthouseToken);
if (token == null) return null; if (token == null) return null;
if (DateTime.Now > token.ExpiresAt)
{
this.Remove(token);
this.SaveChanges();
return null;
}
return this.Users.Include(u => u.Location).FirstOrDefault(u => u.UserId == token.UserId); return this.Users.Include(u => u.Location).FirstOrDefault(u => u.UserId == token.UserId);
} }
@ -356,12 +387,21 @@ public class Database : DbContext
{ {
if (!request.Cookies.TryGetValue("LighthouseToken", out string? lighthouseToken) || lighthouseToken == null) return null; if (!request.Cookies.TryGetValue("LighthouseToken", out string? lighthouseToken) || lighthouseToken == null) return null;
return this.WebTokens.FirstOrDefault(t => t.UserToken == lighthouseToken); WebToken? token = this.WebTokens.FirstOrDefault(t => t.UserToken == lighthouseToken);
if (token == null) return null;
if (DateTime.Now > token.ExpiresAt)
{
this.Remove(token);
this.SaveChanges();
return null;
}
return token;
} }
public async Task<User?> UserFromPasswordResetToken(string resetToken) public async Task<User?> UserFromPasswordResetToken(string resetToken)
{ {
PasswordResetToken? token = await this.PasswordResetTokens.FirstOrDefaultAsync(token => token.ResetToken == resetToken); PasswordResetToken? token = await this.PasswordResetTokens.FirstOrDefaultAsync(token => token.ResetToken == resetToken);
if (token == null) if (token == null)
{ {
@ -371,8 +411,10 @@ public class Database : DbContext
if (token.Created < DateTime.Now.AddHours(-1)) // if token is expired if (token.Created < DateTime.Now.AddHours(-1)) // if token is expired
{ {
this.PasswordResetTokens.Remove(token); this.PasswordResetTokens.Remove(token);
await this.SaveChangesAsync();
return null; return null;
} }
return await this.Users.FirstOrDefaultAsync(user => user.UserId == token.UserId); return await this.Users.FirstOrDefaultAsync(user => user.UserId == token.UserId);
} }
@ -385,12 +427,23 @@ public class Database : DbContext
if (token.Created < DateTime.Now.AddDays(-7)) // if token is expired if (token.Created < DateTime.Now.AddDays(-7)) // if token is expired
{ {
this.RegistrationTokens.Remove(token); this.RegistrationTokens.Remove(token);
this.SaveChanges();
return false; return false;
} }
return true; return true;
} }
public async Task RemoveExpiredTokens()
{
this.GameTokens.RemoveWhere(t => DateTime.Now > t.ExpiresAt);
this.WebTokens.RemoveWhere(t => DateTime.Now > t.ExpiresAt);
this.EmailVerificationTokens.RemoveWhere(t => DateTime.Now > t.ExpiresAt);
this.EmailSetTokens.RemoveWhere(t => DateTime.Now > t.ExpiresAt);
await this.SaveChangesAsync();
}
public async Task RemoveRegistrationToken(string tokenString) public async Task RemoveRegistrationToken(string tokenString)
{ {
RegistrationToken? token = await this.RegistrationTokens.FirstOrDefaultAsync(t => t.Token == tokenString); RegistrationToken? token = await this.RegistrationTokens.FirstOrDefaultAsync(t => t.Token == tokenString);
@ -398,7 +451,6 @@ public class Database : DbContext
if (token == null) return; if (token == null) return;
this.RegistrationTokens.Remove(token); this.RegistrationTokens.Remove(token);
await this.SaveChangesAsync(); await this.SaveChangesAsync();
} }
@ -426,10 +478,10 @@ public class Database : DbContext
this.RatedLevels.RemoveRange(this.RatedLevels.Where(r => r.UserId == user.UserId)); this.RatedLevels.RemoveRange(this.RatedLevels.Where(r => r.UserId == user.UserId));
this.GameTokens.RemoveRange(this.GameTokens.Where(t => t.UserId == user.UserId)); this.GameTokens.RemoveRange(this.GameTokens.Where(t => t.UserId == user.UserId));
this.WebTokens.RemoveRange(this.WebTokens.Where(t => t.UserId == user.UserId)); this.WebTokens.RemoveRange(this.WebTokens.Where(t => t.UserId == user.UserId));
this.Reactions.RemoveRange(this.Reactions.Where(p => p.UserId == user.UserId));
this.Comments.RemoveRange(this.Comments.Where(c => c.PosterUserId == user.UserId)); this.Comments.RemoveRange(this.Comments.Where(c => c.PosterUserId == user.UserId));
this.Reviews.RemoveRange(this.Reviews.Where(r => r.ReviewerId == user.UserId)); this.Reviews.RemoveRange(this.Reviews.Where(r => r.ReviewerId == user.UserId));
this.Photos.RemoveRange(this.Photos.Where(p => p.CreatorId == user.UserId)); this.Photos.RemoveRange(this.Photos.Where(p => p.CreatorId == user.UserId));
this.Reactions.RemoveRange(this.Reactions.Where(p => p.UserId == user.UserId));
this.Users.Remove(user); this.Users.Remove(user);

View file

@ -56,6 +56,9 @@ public static class DatabaseExtensions
return query; return query;
} }
public static async Task<bool> Has<T>(this IQueryable<T> queryable, Expression<Func<T, bool>> predicate) => public static async Task<bool> Has<T>(this IQueryable<T> queryable, Expression<Func<T, bool>> predicate)
await queryable.FirstOrDefaultAsync(predicate) != null; => await queryable.FirstOrDefaultAsync(predicate) != null;
public static void RemoveWhere<T>(this DbSet<T> queryable, Expression<Func<T, bool>> predicate) where T : class
=> queryable.RemoveRange(queryable.Where(predicate));
} }

View file

@ -0,0 +1,80 @@
using System;
using LBPUnion.ProjectLighthouse;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ProjectLighthouse.Migrations
{
[DbContext(typeof(Database))]
[Migration("20220729182709_AddExpiryTimesToTokens")]
public partial class AddExpiryTimesToTokens : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
// Remove existing tokens
migrationBuilder.Sql("DELETE FROM GameTokens;");
migrationBuilder.Sql("DELETE FROM WebTokens;");
migrationBuilder.Sql("DELETE FROM EmailSetTokens;");
migrationBuilder.Sql("DELETE FROM EmailVerificationTokens;");
migrationBuilder.Sql("DELETE FROM PasswordResetTokens;");
migrationBuilder.Sql("DELETE FROM RegistrationTokens;");
migrationBuilder.AddColumn<DateTime>(
name: "ExpiresAt",
table: "WebTokens",
type: "datetime(6)",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "ExpiresAt",
table: "GameTokens",
type: "datetime(6)",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "ExpiresAt",
table: "EmailVerificationTokens",
type: "datetime(6)",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "ExpiresAt",
table: "EmailSetTokens",
type: "datetime(6)",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ExpiresAt",
table: "WebTokens");
migrationBuilder.DropColumn(
name: "ExpiresAt",
table: "RegistrationTokens");
migrationBuilder.DropColumn(
name: "ExpiresAt",
table: "PasswordResetTokens");
migrationBuilder.DropColumn(
name: "ExpiresAt",
table: "GameTokens");
migrationBuilder.DropColumn(
name: "ExpiresAt",
table: "EmailVerificationTokens");
migrationBuilder.DropColumn(
name: "ExpiresAt",
table: "EmailSetTokens");
}
}
}

View file

@ -385,6 +385,9 @@ namespace ProjectLighthouse.Migrations
b.Property<bool>("Approved") b.Property<bool>("Approved")
.HasColumnType("tinyint(1)"); .HasColumnType("tinyint(1)");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("datetime(6)");
b.Property<int>("GameVersion") b.Property<int>("GameVersion")
.HasColumnType("int"); .HasColumnType("int");
@ -419,6 +422,9 @@ namespace ProjectLighthouse.Migrations
b.Property<DateTime>("Created") b.Property<DateTime>("Created")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("datetime(6)");
b.Property<string>("ResetToken") b.Property<string>("ResetToken")
.HasColumnType("longtext"); .HasColumnType("longtext");
@ -540,6 +546,9 @@ namespace ProjectLighthouse.Migrations
b.Property<string>("EmailToken") b.Property<string>("EmailToken")
.HasColumnType("longtext"); .HasColumnType("longtext");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("datetime(6)");
b.Property<int>("UserId") b.Property<int>("UserId")
.HasColumnType("int"); .HasColumnType("int");
@ -559,6 +568,9 @@ namespace ProjectLighthouse.Migrations
b.Property<string>("EmailToken") b.Property<string>("EmailToken")
.HasColumnType("longtext"); .HasColumnType("longtext");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("datetime(6)");
b.Property<int>("UserId") b.Property<int>("UserId")
.HasColumnType("int"); .HasColumnType("int");
@ -731,6 +743,9 @@ namespace ProjectLighthouse.Migrations
b.Property<DateTime>("Created") b.Property<DateTime>("Created")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("datetime(6)");
b.Property<string>("Token") b.Property<string>("Token")
.HasColumnType("longtext"); .HasColumnType("longtext");
@ -841,6 +856,9 @@ namespace ProjectLighthouse.Migrations
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("int"); .HasColumnType("int");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("datetime(6)");
b.Property<int>("UserId") b.Property<int>("UserId")
.HasColumnType("int"); .HasColumnType("int");

View file

@ -1,3 +1,4 @@
using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
@ -28,4 +29,6 @@ public class GameToken
// Set to true on login // Set to true on login
public bool Used { get; set; } public bool Used { get; set; }
public DateTime ExpiresAt { get; set; }
} }

View file

@ -1,3 +1,4 @@
using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
@ -14,4 +15,6 @@ public class EmailSetToken
public User User { get; set; } public User User { get; set; }
public string EmailToken { get; set; } public string EmailToken { get; set; }
public DateTime ExpiresAt { get; set; }
} }

View file

@ -1,3 +1,4 @@
using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
@ -14,4 +15,6 @@ public class EmailVerificationToken
public User User { get; set; } public User User { get; set; }
public string EmailToken { get; set; } public string EmailToken { get; set; }
public DateTime ExpiresAt { get; set; }
} }

View file

@ -1,3 +1,4 @@
using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
namespace LBPUnion.ProjectLighthouse.PlayerData; namespace LBPUnion.ProjectLighthouse.PlayerData;
@ -11,4 +12,6 @@ public class WebToken
public int UserId { get; set; } public int UserId { get; set; }
public string UserToken { get; set; } public string UserToken { get; set; }
public DateTime ExpiresAt { get; set; }
} }