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,
"tools": {
"dotnet-ef": {
"version": "6.0.5",
"version": "6.0.7",
"commands": [
"dotnet-ef"
]

View file

@ -57,9 +57,10 @@ public class LoginController : ControllerBase
string ipAddress = remoteIpAddress.ToString();
await this.database.RemoveExpiredTokens();
// Get an existing token from the IP & username
GameToken? token = await this.database.GameTokens.Include
(t => t.User)
GameToken? token = await this.database.GameTokens.Include(t => t.User)
.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
@ -67,7 +68,8 @@ public class LoginController : ControllerBase
token = await this.database.AuthenticateUser(npTicket, ipAddress);
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.
}
}

View file

@ -1,4 +1,5 @@
#nullable enable
using System.Globalization;
using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Helpers;
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.UserLocation: {gameToken.UserLocation}\n" +
$"token.GameVersion: {gameToken.GameVersion}\n" +
$"token.ExpiresAt: {gameToken.ExpiresAt.ToString(CultureInfo.CurrentCulture)}\n" +
"---DEBUG INFO---" +
#endif
"\n"

View file

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

View file

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

View file

@ -49,15 +49,18 @@ public class SetEmailForm : BaseLayout
UserId = user.UserId,
User = user,
EmailToken = CryptoHelper.GenerateAuthToken(),
ExpiresAt = DateTime.Now + TimeSpan.FromHours(6),
};
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.
// TODO: insecure
WebToken webToken = new()
{
UserId = user.UserId,
UserToken = CryptoHelper.GenerateAuthToken(),
ExpiresAt = DateTime.Now + TimeSpan.FromDays(7),
};
this.Response.Cookies.Append

View file

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

View file

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

View file

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Administration;
using LBPUnion.ProjectLighthouse.Administration.Reports;
using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Extensions;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.Levels.Categories;
@ -111,6 +112,8 @@ public class Database : DbContext
UserLocation = userLocation,
GameVersion = npTicket.GameVersion,
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);
@ -289,6 +292,13 @@ public class Database : DbContext
if (token == null) 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);
}
@ -314,6 +324,13 @@ public class Database : DbContext
if (token == null) return null;
if (!allowUnapproved && !token.Approved) return null;
if (DateTime.Now > token.ExpiresAt)
{
this.Remove(token);
await this.SaveChangesAsync();
return null;
}
return token;
}
@ -326,6 +343,13 @@ public class Database : DbContext
if (token == null) 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);
if (user == null) return null;
@ -342,6 +366,13 @@ public class Database : DbContext
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 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;
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)
{
PasswordResetToken? token = await this.PasswordResetTokens.FirstOrDefaultAsync(token => token.ResetToken == resetToken);
if (token == null)
{
@ -371,8 +411,10 @@ public class Database : DbContext
if (token.Created < DateTime.Now.AddHours(-1)) // if token is expired
{
this.PasswordResetTokens.Remove(token);
await this.SaveChangesAsync();
return null;
}
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
{
this.RegistrationTokens.Remove(token);
this.SaveChanges();
return false;
}
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)
{
RegistrationToken? token = await this.RegistrationTokens.FirstOrDefaultAsync(t => t.Token == tokenString);
@ -398,7 +451,6 @@ public class Database : DbContext
if (token == null) return;
this.RegistrationTokens.Remove(token);
await this.SaveChangesAsync();
}
@ -426,10 +478,10 @@ public class Database : DbContext
this.RatedLevels.RemoveRange(this.RatedLevels.Where(r => r.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.Reactions.RemoveRange(this.Reactions.Where(p => p.UserId == 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.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);

View file

@ -56,6 +56,9 @@ public static class DatabaseExtensions
return query;
}
public static async Task<bool> Has<T>(this IQueryable<T> queryable, Expression<Func<T, bool>> predicate) =>
await queryable.FirstOrDefaultAsync(predicate) != null;
public static async Task<bool> Has<T>(this IQueryable<T> queryable, Expression<Func<T, bool>> predicate)
=> 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")
.HasColumnType("tinyint(1)");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("datetime(6)");
b.Property<int>("GameVersion")
.HasColumnType("int");
@ -419,6 +422,9 @@ namespace ProjectLighthouse.Migrations
b.Property<DateTime>("Created")
.HasColumnType("datetime(6)");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("datetime(6)");
b.Property<string>("ResetToken")
.HasColumnType("longtext");
@ -540,6 +546,9 @@ namespace ProjectLighthouse.Migrations
b.Property<string>("EmailToken")
.HasColumnType("longtext");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("datetime(6)");
b.Property<int>("UserId")
.HasColumnType("int");
@ -559,6 +568,9 @@ namespace ProjectLighthouse.Migrations
b.Property<string>("EmailToken")
.HasColumnType("longtext");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("datetime(6)");
b.Property<int>("UserId")
.HasColumnType("int");
@ -731,6 +743,9 @@ namespace ProjectLighthouse.Migrations
b.Property<DateTime>("Created")
.HasColumnType("datetime(6)");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("datetime(6)");
b.Property<string>("Token")
.HasColumnType("longtext");
@ -841,6 +856,9 @@ namespace ProjectLighthouse.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("datetime(6)");
b.Property<int>("UserId")
.HasColumnType("int");

View file

@ -1,3 +1,4 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
@ -28,4 +29,6 @@ public class GameToken
// Set to true on login
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.Schema;
@ -14,4 +15,6 @@ public class EmailSetToken
public User User { 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.Schema;
@ -14,4 +15,6 @@ public class EmailVerificationToken
public User User { get; set; }
public string EmailToken { get; set; }
public DateTime ExpiresAt { get; set; }
}

View file

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