From db72c8d45a725410b6eb7836aaf091e8a5ccb369 Mon Sep 17 00:00:00 2001 From: Wind-Explorer Date: Fri, 7 Feb 2025 15:53:43 +0800 Subject: [PATCH] CRUD --- AceJobAgency/AceJobAgency.csproj | 2 + AceJobAgency/Controllers/UserController.cs | 163 ++++++++++++++++++--- AceJobAgency/Program.cs | 51 ++++++- AceJobAgency/appsettings.json | 3 +- 4 files changed, 199 insertions(+), 20 deletions(-) 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": {