Disallow 2 old passwords

This commit is contained in:
2025-02-11 10:04:58 +08:00
parent 495fff3ac9
commit 44ab95b64d
5 changed files with 218 additions and 0 deletions

View File

@@ -178,12 +178,29 @@ namespace AceJobAgency.Controllers
return BadRequest("Current password is incorrect."); return BadRequest("Current password is incorrect.");
} }
if (BCrypt.Net.BCrypt.Verify(request.NewPassword, user.Password))
{
new ActivityLogController(_context).LogUserActivity(user.Id, "Change password failed: Tried changing to the same password", ipAddress);
return BadRequest("New password can't be the same as the old password.");
}
if (!AccountManagement.IsPasswordComplex(request.NewPassword)) if (!AccountManagement.IsPasswordComplex(request.NewPassword))
{ {
new ActivityLogController(_context).LogUserActivity(user.Id, "Change password failed: Password not complex enough", ipAddress); new ActivityLogController(_context).LogUserActivity(user.Id, "Change password failed: Password not complex enough", ipAddress);
return BadRequest("Password must be at least 12 characters long and include uppercase, lowercase, number, and special character."); return BadRequest("Password must be at least 12 characters long and include uppercase, lowercase, number, and special character.");
} }
if ((!string.IsNullOrEmpty(user.Password) && BCrypt.Net.BCrypt.Verify(request.NewPassword, user.Password)) ||
(!string.IsNullOrEmpty(user.PreviousPassword1) && BCrypt.Net.BCrypt.Verify(request.NewPassword, user.PreviousPassword1)) ||
(!string.IsNullOrEmpty(user.PreviousPassword2) && BCrypt.Net.BCrypt.Verify(request.NewPassword, user.PreviousPassword2)))
{
new ActivityLogController(_context).LogUserActivity(user.Id, "Change password failed: New password matches one of the previous passwords", ipAddress);
return BadRequest("New password cannot be one of the last two passwords.");
}
user.PreviousPassword2 = user.PreviousPassword1;
user.PreviousPassword1 = user.Password;
user.Password = BCrypt.Net.BCrypt.HashPassword(request.NewPassword); user.Password = BCrypt.Net.BCrypt.HashPassword(request.NewPassword);
user.UpdatedAt = DateTime.Now; user.UpdatedAt = DateTime.Now;
_context.Users.Update(user); _context.Users.Update(user);

View File

@@ -57,5 +57,13 @@ namespace AceJobAgency.Entities
[DataType(DataType.DateTime)] [DataType(DataType.DateTime)]
public DateTime? LockoutEndTime { get; set; } public DateTime? LockoutEndTime { get; set; }
[DataType(DataType.Password)]
[MaxLength(128)]
public string PreviousPassword1 { get; set; } = string.Empty;
[DataType(DataType.Password)]
[MaxLength(128)]
public string PreviousPassword2 { get; set; } = string.Empty;
} }
} }

View File

@@ -0,0 +1,139 @@
// <auto-generated />
using System;
using AceJobAgency.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace AceJobAgency.Migrations
{
[DbContext(typeof(DataContext))]
[Migration("20250211015120_AddedTwoPasswordHistories")]
partial class AddedTwoPasswordHistories
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.2")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
modelBuilder.Entity("AceJobAgency.Entities.ActivityLog", b =>
{
b.Property<string>("Id")
.HasMaxLength(36)
.HasColumnType("varchar(36)");
b.Property<string>("Activity")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("varchar(255)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("IpAddress")
.IsRequired()
.HasMaxLength(15)
.HasColumnType("varchar(15)");
b.Property<string>("UserId")
.IsRequired()
.HasMaxLength(36)
.HasColumnType("varchar(36)");
b.HasKey("Id");
b.ToTable("ActivityLogs");
});
modelBuilder.Entity("AceJobAgency.Entities.User", b =>
{
b.Property<string>("Id")
.HasMaxLength(36)
.HasColumnType("varchar(36)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<DateTime>("DateOfBirth")
.HasColumnType("datetime(6)");
b.Property<string>("Email")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("varchar(128)");
b.Property<int>("FailedLoginAttempts")
.HasColumnType("int");
b.Property<string>("FirstName")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("varchar(50)");
b.Property<int>("Gender")
.HasColumnType("int");
b.Property<int>("IsActive")
.HasColumnType("int");
b.Property<bool>("IsLockedOut")
.HasColumnType("tinyint(1)");
b.Property<string>("LastName")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("varchar(50)");
b.Property<DateTime?>("LockoutEndTime")
.HasColumnType("datetime(6)");
b.Property<string>("NationalRegistrationIdentityCardNumber")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("varchar(255)");
b.Property<string>("Password")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("varchar(128)");
b.Property<string>("PreviousPassword1")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("varchar(128)");
b.Property<string>("PreviousPassword2")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("varchar(128)");
b.Property<string>("ResumeName")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("varchar(128)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("WhoAmI")
.IsRequired()
.HasMaxLength(2048)
.HasColumnType("varchar(2048)");
b.HasKey("Id");
b.ToTable("Users");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,44 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace AceJobAgency.Migrations
{
/// <inheritdoc />
public partial class AddedTwoPasswordHistories : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "PreviousPassword1",
table: "Users",
type: "varchar(128)",
maxLength: 128,
nullable: false,
defaultValue: "")
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AddColumn<string>(
name: "PreviousPassword2",
table: "Users",
type: "varchar(128)",
maxLength: 128,
nullable: false,
defaultValue: "")
.Annotation("MySql:CharSet", "utf8mb4");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "PreviousPassword1",
table: "Users");
migrationBuilder.DropColumn(
name: "PreviousPassword2",
table: "Users");
}
}
}

View File

@@ -103,6 +103,16 @@ namespace AceJobAgency.Migrations
.HasMaxLength(128) .HasMaxLength(128)
.HasColumnType("varchar(128)"); .HasColumnType("varchar(128)");
b.Property<string>("PreviousPassword1")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("varchar(128)");
b.Property<string>("PreviousPassword2")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("varchar(128)");
b.Property<string>("ResumeName") b.Property<string>("ResumeName")
.IsRequired() .IsRequired()
.HasMaxLength(128) .HasMaxLength(128)