diff --git a/AceJobAgency/Controllers/UserController.cs b/AceJobAgency/Controllers/UserController.cs index fef9a20..b80e234 100644 --- a/AceJobAgency/Controllers/UserController.cs +++ b/AceJobAgency/Controllers/UserController.cs @@ -1,7 +1,6 @@ using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; -using System.Text.RegularExpressions; using AceJobAgency.Data; using AceJobAgency.Entities; using AceJobAgency.Utilities; @@ -17,11 +16,13 @@ namespace AceJobAgency.Controllers { private readonly DataContext _context; private readonly IConfiguration _configuration; + private readonly IEncryptionService _encryptionService; - public UserController(DataContext context, IConfiguration configuration) + public UserController(DataContext context, IConfiguration configuration, IEncryptionService encryptionService) { _context = context; _configuration = configuration; + _encryptionService = encryptionService; } [HttpPost("register")] @@ -32,9 +33,10 @@ namespace AceJobAgency.Controllers return BadRequest("Password must be at least 12 characters long and include uppercase, lowercase, number, and special character."); } + var encryptedNric = _encryptionService.Encrypt(user.NationalRegistrationIdentityCardNumber); + var emailExists = _context.Users.Any(u => u.Email == user.Email); - var nricExists = _context.Users.Any(u => - u.NationalRegistrationIdentityCardNumber == user.NationalRegistrationIdentityCardNumber); + var nricExists = _context.Users.Any(u => u.NationalRegistrationIdentityCardNumber == encryptedNric); if (emailExists || nricExists) { return BadRequest("User with the same email or NRIC already exists."); @@ -43,10 +45,23 @@ namespace AceJobAgency.Controllers user.Password = BCrypt.Net.BCrypt.HashPassword(user.Password); user.Id = Guid.NewGuid().ToString(); user.IsActive = 1; + user.NationalRegistrationIdentityCardNumber = encryptedNric; await _context.Users.AddAsync(user); await _context.SaveChangesAsync(); - return Ok(user); + + var response = new + { + user.Id, + user.Email, + NationalRegistrationIdentityCardNumber = _encryptionService.Decrypt(user.NationalRegistrationIdentityCardNumber), + user.FirstName, + user.LastName, + user.DateOfBirth, + user.WhoAmI, + user.ResumeName, + }; + return Ok(response); } [HttpPost("login")] @@ -72,7 +87,20 @@ namespace AceJobAgency.Controllers { return NotFound(); } - return Ok(user); + + var decryptedNric = _encryptionService.Decrypt(user.NationalRegistrationIdentityCardNumber); + var response = new + { + user.Id, + user.Email, + NationalRegistrationIdentityCardNumber = decryptedNric, + user.FirstName, + user.LastName, + user.DateOfBirth, + user.WhoAmI, + user.ResumeName, + }; + return Ok(response); } [Authorize] @@ -174,4 +202,4 @@ namespace AceJobAgency.Controllers public string CurrentPassword { get; set; } = string.Empty; public string NewPassword { get; set; } = string.Empty; } -} +} \ No newline at end of file diff --git a/AceJobAgency/Entities/User.cs b/AceJobAgency/Entities/User.cs index 8228ddc..3db6b60 100644 --- a/AceJobAgency/Entities/User.cs +++ b/AceJobAgency/Entities/User.cs @@ -20,7 +20,7 @@ namespace AceJobAgency.Entities public required int Gender { get; set; } [Required] - [StringLength(9, MinimumLength = 9)] + [StringLength(255)] public required string NationalRegistrationIdentityCardNumber { get; set; } [Required] diff --git a/AceJobAgency/Migrations/20250207154242_UpdatedNRIC.Designer.cs b/AceJobAgency/Migrations/20250207154242_UpdatedNRIC.Designer.cs new file mode 100644 index 0000000..a55ad6e --- /dev/null +++ b/AceJobAgency/Migrations/20250207154242_UpdatedNRIC.Designer.cs @@ -0,0 +1,91 @@ +// +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("20250207154242_UpdatedNRIC")] + partial class UpdatedNRIC + { + /// + 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.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("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Gender") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("int"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("NationalRegistrationIdentityCardNumber") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("Password") + .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(255) + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/AceJobAgency/Migrations/20250207154242_UpdatedNRIC.cs b/AceJobAgency/Migrations/20250207154242_UpdatedNRIC.cs new file mode 100644 index 0000000..789728d --- /dev/null +++ b/AceJobAgency/Migrations/20250207154242_UpdatedNRIC.cs @@ -0,0 +1,42 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace AceJobAgency.Migrations +{ + /// + public partial class UpdatedNRIC : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "NationalRegistrationIdentityCardNumber", + table: "Users", + type: "varchar(255)", + maxLength: 255, + nullable: false, + oldClrType: typeof(string), + oldType: "varchar(9)", + oldMaxLength: 9) + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "NationalRegistrationIdentityCardNumber", + table: "Users", + type: "varchar(9)", + maxLength: 9, + nullable: false, + oldClrType: typeof(string), + oldType: "varchar(255)", + oldMaxLength: 255) + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + } + } +} diff --git a/AceJobAgency/Migrations/DataContextModelSnapshot.cs b/AceJobAgency/Migrations/DataContextModelSnapshot.cs index 963de11..f7868ce 100644 --- a/AceJobAgency/Migrations/DataContextModelSnapshot.cs +++ b/AceJobAgency/Migrations/DataContextModelSnapshot.cs @@ -57,8 +57,8 @@ namespace AceJobAgency.Migrations b.Property("NationalRegistrationIdentityCardNumber") .IsRequired() - .HasMaxLength(9) - .HasColumnType("varchar(9)"); + .HasMaxLength(255) + .HasColumnType("varchar(255)"); b.Property("Password") .IsRequired() diff --git a/AceJobAgency/Program.cs b/AceJobAgency/Program.cs index bbc458c..ee744a1 100644 --- a/AceJobAgency/Program.cs +++ b/AceJobAgency/Program.cs @@ -1,5 +1,6 @@ using System.Text; using AceJobAgency.Data; +using AceJobAgency.Utilities; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; @@ -60,6 +61,8 @@ builder.Services.AddSwaggerGen(options => }); }); +builder.Services.AddScoped(); + var app = builder.Build(); // Configure the HTTP request pipeline. diff --git a/AceJobAgency/Utilities/Cryptography.cs b/AceJobAgency/Utilities/Cryptography.cs new file mode 100644 index 0000000..293873c --- /dev/null +++ b/AceJobAgency/Utilities/Cryptography.cs @@ -0,0 +1,61 @@ +using System.Security.Cryptography; + +namespace AceJobAgency.Utilities; + +public interface IEncryptionService +{ + string Encrypt(string plainText); + string Decrypt(string cipherText); +} + +public class AesEncryptionService : IEncryptionService +{ + private readonly byte[] _key; + + public AesEncryptionService(IConfiguration configuration) + { + var encryptionKey = configuration["Encryption:Key"]; + if (string.IsNullOrEmpty(encryptionKey)) + throw new ArgumentException("Encryption key is not configured."); + _key = Convert.FromBase64String(encryptionKey); + } + + public string Encrypt(string plainText) + { + using var aes = Aes.Create(); + aes.Key = _key; + aes.GenerateIV(); + + using var encryptor = aes.CreateEncryptor(aes.Key, aes.IV); + using var ms = new MemoryStream(); + using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write)) + using (var sw = new StreamWriter(cs)) + sw.Write(plainText); + + var encryptedBytes = ms.ToArray(); + var iv = aes.IV; + var combined = new byte[iv.Length + encryptedBytes.Length]; + Buffer.BlockCopy(iv, 0, combined, 0, iv.Length); + Buffer.BlockCopy(encryptedBytes, 0, combined, iv.Length, encryptedBytes.Length); + return Convert.ToBase64String(combined); + } + + public string Decrypt(string cipherText) + { + var combined = Convert.FromBase64String(cipherText); + var iv = new byte[16]; + var encryptedBytes = new byte[combined.Length - iv.Length]; + Buffer.BlockCopy(combined, 0, iv, 0, iv.Length); + Buffer.BlockCopy(combined, iv.Length, encryptedBytes, 0, encryptedBytes.Length); + + using var aes = Aes.Create(); + aes.Key = _key; + aes.IV = iv; + + using var decryptor = aes.CreateDecryptor(aes.Key, aes.IV); + using var ms = new MemoryStream(encryptedBytes); + using var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read); + using var sr = new StreamReader(cs); + return sr.ReadToEnd(); + } +} \ No newline at end of file diff --git a/AceJobAgency/appsettings.json b/AceJobAgency/appsettings.json index 3b1ae9d..04b889e 100644 --- a/AceJobAgency/appsettings.json +++ b/AceJobAgency/appsettings.json @@ -5,6 +5,9 @@ "Authentication": { "Secret": "b8bc713a-d8d1-4d37-911e-1bb931d70ba5" }, + "Encryption": { + "Key": "laVspGq/dNFECeUP93fUF05T422cLjieKPotTI8Wgfc=" + }, "Logging": { "LogLevel": { "Default": "Information",