diff --git a/AceJobAgency/Controllers/UserController.cs b/AceJobAgency/Controllers/UserController.cs
index bed6426..731ca1e 100644
--- a/AceJobAgency/Controllers/UserController.cs
+++ b/AceJobAgency/Controllers/UserController.cs
@@ -178,12 +178,29 @@ namespace AceJobAgency.Controllers
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))
{
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.");
}
+ 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.UpdatedAt = DateTime.Now;
_context.Users.Update(user);
diff --git a/AceJobAgency/Entities/User.cs b/AceJobAgency/Entities/User.cs
index f38ab36..638f0df 100644
--- a/AceJobAgency/Entities/User.cs
+++ b/AceJobAgency/Entities/User.cs
@@ -57,5 +57,13 @@ namespace AceJobAgency.Entities
[DataType(DataType.DateTime)]
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;
}
}
\ No newline at end of file
diff --git a/AceJobAgency/Migrations/20250211015120_AddedTwoPasswordHistories.Designer.cs b/AceJobAgency/Migrations/20250211015120_AddedTwoPasswordHistories.Designer.cs
new file mode 100644
index 0000000..2acd4eb
--- /dev/null
+++ b/AceJobAgency/Migrations/20250211015120_AddedTwoPasswordHistories.Designer.cs
@@ -0,0 +1,139 @@
+//
+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
+ {
+ ///
+ 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("Id")
+ .HasMaxLength(36)
+ .HasColumnType("varchar(36)");
+
+ b.Property("Activity")
+ .IsRequired()
+ .HasMaxLength(255)
+ .HasColumnType("varchar(255)");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("IpAddress")
+ .IsRequired()
+ .HasMaxLength(15)
+ .HasColumnType("varchar(15)");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasMaxLength(36)
+ .HasColumnType("varchar(36)");
+
+ b.HasKey("Id");
+
+ b.ToTable("ActivityLogs");
+ });
+
+ modelBuilder.Entity("AceJobAgency.Entities.User", b =>
+ {
+ b.Property("Id")
+ .HasMaxLength(36)
+ .HasColumnType("varchar(36)");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("DateOfBirth")
+ .HasColumnType("datetime(6)");
+
+ b.Property("Email")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("varchar(128)");
+
+ b.Property("FailedLoginAttempts")
+ .HasColumnType("int");
+
+ b.Property("FirstName")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("varchar(50)");
+
+ b.Property("Gender")
+ .HasColumnType("int");
+
+ b.Property("IsActive")
+ .HasColumnType("int");
+
+ b.Property("IsLockedOut")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("LastName")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("varchar(50)");
+
+ b.Property("LockoutEndTime")
+ .HasColumnType("datetime(6)");
+
+ b.Property("NationalRegistrationIdentityCardNumber")
+ .IsRequired()
+ .HasMaxLength(255)
+ .HasColumnType("varchar(255)");
+
+ b.Property("Password")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("varchar(128)");
+
+ b.Property("PreviousPassword1")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("varchar(128)");
+
+ b.Property("PreviousPassword2")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("varchar(128)");
+
+ b.Property("ResumeName")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("varchar(128)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime(6)");
+
+ b.Property("WhoAmI")
+ .IsRequired()
+ .HasMaxLength(2048)
+ .HasColumnType("varchar(2048)");
+
+ b.HasKey("Id");
+
+ b.ToTable("Users");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/AceJobAgency/Migrations/20250211015120_AddedTwoPasswordHistories.cs b/AceJobAgency/Migrations/20250211015120_AddedTwoPasswordHistories.cs
new file mode 100644
index 0000000..1eb73b4
--- /dev/null
+++ b/AceJobAgency/Migrations/20250211015120_AddedTwoPasswordHistories.cs
@@ -0,0 +1,44 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace AceJobAgency.Migrations
+{
+ ///
+ public partial class AddedTwoPasswordHistories : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "PreviousPassword1",
+ table: "Users",
+ type: "varchar(128)",
+ maxLength: 128,
+ nullable: false,
+ defaultValue: "")
+ .Annotation("MySql:CharSet", "utf8mb4");
+
+ migrationBuilder.AddColumn(
+ name: "PreviousPassword2",
+ table: "Users",
+ type: "varchar(128)",
+ maxLength: 128,
+ nullable: false,
+ defaultValue: "")
+ .Annotation("MySql:CharSet", "utf8mb4");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "PreviousPassword1",
+ table: "Users");
+
+ migrationBuilder.DropColumn(
+ name: "PreviousPassword2",
+ table: "Users");
+ }
+ }
+}
diff --git a/AceJobAgency/Migrations/DataContextModelSnapshot.cs b/AceJobAgency/Migrations/DataContextModelSnapshot.cs
index 720725e..828df83 100644
--- a/AceJobAgency/Migrations/DataContextModelSnapshot.cs
+++ b/AceJobAgency/Migrations/DataContextModelSnapshot.cs
@@ -103,6 +103,16 @@ namespace AceJobAgency.Migrations
.HasMaxLength(128)
.HasColumnType("varchar(128)");
+ b.Property("PreviousPassword1")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("varchar(128)");
+
+ b.Property("PreviousPassword2")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("varchar(128)");
+
b.Property("ResumeName")
.IsRequired()
.HasMaxLength(128)