diff --git a/AceJobAgency/AceJobAgency.csproj b/AceJobAgency/AceJobAgency.csproj
index 94b379c..a273417 100644
--- a/AceJobAgency/AceJobAgency.csproj
+++ b/AceJobAgency/AceJobAgency.csproj
@@ -8,6 +8,7 @@
+
all
@@ -15,6 +16,7 @@
+
diff --git a/AceJobAgency/Controllers/UserController.cs b/AceJobAgency/Controllers/UserController.cs
index 8351f57..d993882 100644
--- a/AceJobAgency/Controllers/UserController.cs
+++ b/AceJobAgency/Controllers/UserController.cs
@@ -1,36 +1,165 @@
-using AceJobAgency.Data;
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using System.Text;
+using AceJobAgency.Data;
using AceJobAgency.Entities;
+using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.IdentityModel.Tokens;
namespace AceJobAgency.Controllers
{
[ApiController]
[Route("[controller]")]
- public class UserController(DataContext context, IConfiguration configuration) : Controller
+ public class UserController : Controller
{
- [HttpPost]
+ private readonly DataContext _context;
+ private readonly IConfiguration _configuration;
+
+ public UserController(DataContext context, IConfiguration configuration)
+ {
+ _context = context;
+ _configuration = configuration;
+ }
+
+ [HttpPost("register")]
public async Task Register(User user)
{
- var userEmailExists = context.Users.Any(u => u.Email == user.Email);
- var userNationalRegistrationIdentityCardNumberExists = context.Users.Any(
- u => u.NationalRegistrationIdentityCardNumber
- == user.NationalRegistrationIdentityCardNumber
- );
- if (userEmailExists || userNationalRegistrationIdentityCardNumberExists)
+ bool emailExists = _context.Users.Any(u => u.Email == user.Email);
+ bool nricExists = _context.Users.Any(u =>
+ u.NationalRegistrationIdentityCardNumber == user.NationalRegistrationIdentityCardNumber);
+ if (emailExists || nricExists)
{
- return BadRequest("User with the same email already exists.");
+ return BadRequest("User with the same email or NRIC already exists.");
}
- var passwordHash = BCrypt.Net.BCrypt.HashPassword(user.Password);
-
- user.Password = passwordHash;
- var userId = Guid.NewGuid().ToString();
- user.Id = userId;
+ user.Password = BCrypt.Net.BCrypt.HashPassword(user.Password);
+ user.Id = Guid.NewGuid().ToString();
user.IsActive = 1;
- await context.Users.AddAsync(user);
- await context.SaveChangesAsync();
+ await _context.Users.AddAsync(user);
+ await _context.SaveChangesAsync();
return Ok(user);
}
+
+ [HttpPost("login")]
+ public IActionResult Login([FromBody] LoginRequest request)
+ {
+ var user = _context.Users.FirstOrDefault(u => u.Email == request.Email && u.IsActive == 1);
+ if (user == null || !BCrypt.Net.BCrypt.Verify(request.Password, user.Password))
+ {
+ return Unauthorized("Invalid email or password.");
+ }
+
+ var token = GenerateJwtToken(user);
+ return Ok(new { token });
+ }
+
+ [Authorize]
+ [HttpGet("profile")]
+ public IActionResult GetProfile()
+ {
+ var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ var user = _context.Users.FirstOrDefault(u => u.Id == userId && u.IsActive == 1);
+ if (user == null)
+ {
+ return NotFound();
+ }
+ return Ok(user);
+ }
+
+ [Authorize]
+ [HttpPut("profile")]
+ public async Task UpdateProfile(User updatedUser)
+ {
+ var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ var user = _context.Users.FirstOrDefault(u => u.Id == userId && u.IsActive == 1);
+ if (user == null)
+ {
+ return NotFound();
+ }
+
+ user.FirstName = updatedUser.FirstName;
+ user.LastName = updatedUser.LastName;
+ user.DateOfBirth = updatedUser.DateOfBirth;
+ user.WhoAmI = updatedUser.WhoAmI;
+ user.ResumeName = updatedUser.ResumeName;
+ user.UpdatedAt = DateTime.Now;
+
+ _context.Users.Update(user);
+ await _context.SaveChangesAsync();
+ return Ok(user);
+ }
+
+ [Authorize]
+ [HttpPut("change-password")]
+ public async Task ChangePassword([FromBody] ChangePasswordRequest request)
+ {
+ var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ var user = _context.Users.FirstOrDefault(u => u.Id == userId && u.IsActive == 1);
+ if (user == null)
+ {
+ return NotFound();
+ }
+
+ if (!BCrypt.Net.BCrypt.Verify(request.CurrentPassword, user.Password))
+ {
+ return BadRequest("Current password is incorrect.");
+ }
+
+ user.Password = BCrypt.Net.BCrypt.HashPassword(request.NewPassword);
+ user.UpdatedAt = DateTime.Now;
+ _context.Users.Update(user);
+ await _context.SaveChangesAsync();
+ return Ok("Password updated successfully.");
+ }
+
+ [Authorize]
+ [HttpDelete("delete")]
+ public async Task DeleteAccount()
+ {
+ var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ var user = _context.Users.FirstOrDefault(u => u.Id == userId && u.IsActive == 1);
+ if (user == null)
+ {
+ return NotFound();
+ }
+
+ user.IsActive = 0;
+
+ _context.Users.Update(user);
+ await _context.SaveChangesAsync();
+ return Ok("Account deleted successfully.");
+ }
+
+ private string GenerateJwtToken(User user)
+ {
+ var tokenHandler = new JwtSecurityTokenHandler();
+ var key = Encoding.ASCII.GetBytes(_configuration["Authentication:Secret"] ?? "some_secret_key");
+ var tokenDescriptor = new SecurityTokenDescriptor
+ {
+ Subject = new ClaimsIdentity([
+ new Claim(ClaimTypes.NameIdentifier, user.Id),
+ new Claim(ClaimTypes.Email, user.Email)
+ ]),
+ Expires = DateTime.UtcNow.AddHours(2),
+ SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key),
+ SecurityAlgorithms.HmacSha256Signature)
+ };
+ var token = tokenHandler.CreateToken(tokenDescriptor);
+ return tokenHandler.WriteToken(token);
+ }
+ }
+
+ public class LoginRequest
+ {
+ public string Email { get; set; } = string.Empty;
+ public string Password { get; set; } = string.Empty;
+ }
+
+ public class ChangePasswordRequest
+ {
+ public string CurrentPassword { get; set; } = string.Empty;
+ public string NewPassword { get; set; } = string.Empty;
}
}
diff --git a/AceJobAgency/Program.cs b/AceJobAgency/Program.cs
index 372c551..bbc458c 100644
--- a/AceJobAgency/Program.cs
+++ b/AceJobAgency/Program.cs
@@ -1,4 +1,8 @@
+using System.Text;
using AceJobAgency.Data;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.IdentityModel.Tokens;
+using Microsoft.OpenApi.Models;
var builder = WebApplication.CreateBuilder(args);
@@ -7,9 +11,54 @@ var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddDbContext();
+// Authentication
+var secret = builder.Configuration.GetValue("Authentication:Secret");
+if (string.IsNullOrEmpty(secret))
+{
+ throw new Exception("Secret is required for JWT authentication.");
+}
+builder.Services
+ .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
+ .AddJwtBearer(options =>
+ {
+ options.TokenValidationParameters = new TokenValidationParameters
+ {
+ ValidateIssuer = false,
+ ValidateAudience = false,
+ ValidateLifetime = true,
+ ValidateIssuerSigningKey = true,
+ IssuerSigningKey = new SymmetricSecurityKey(
+ Encoding.UTF8.GetBytes(secret)
+ ),
+ };
+ }
+);
+
+
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
-builder.Services.AddSwaggerGen();
+builder.Services.AddSwaggerGen(options =>
+{
+ var securityScheme = new OpenApiSecurityScheme
+ {
+ In = ParameterLocation.Header,
+ Description = "Token",
+ Name = "Authorization",
+ Type = SecuritySchemeType.Http,
+ BearerFormat = "JWT",
+ Scheme = "Bearer",
+ Reference = new OpenApiReference
+ {
+ Type = ReferenceType.SecurityScheme,
+ Id = "Bearer"
+ }
+ };
+ options.AddSecurityDefinition("Bearer", securityScheme);
+ options.AddSecurityRequirement(new OpenApiSecurityRequirement
+ {
+ { securityScheme, new List() }
+ });
+});
var app = builder.Build();
diff --git a/AceJobAgency/appsettings.json b/AceJobAgency/appsettings.json
index 1b14d3e..3b1ae9d 100644
--- a/AceJobAgency/appsettings.json
+++ b/AceJobAgency/appsettings.json
@@ -3,8 +3,7 @@
"DefaultConnection": "server=HOSTNAME;port=3306;database=DB_NAME;user=USERNAME;password=PASSWORD"
},
"Authentication": {
- "Secret": "b8bc713a-d8d1-4d37-911e-1bb931d70ba5",
- "TokenExpiresDays": 30
+ "Secret": "b8bc713a-d8d1-4d37-911e-1bb931d70ba5"
},
"Logging": {
"LogLevel": {