This commit is contained in:
2025-02-07 15:53:43 +08:00
parent a9b125233d
commit db72c8d45a
4 changed files with 199 additions and 20 deletions

View File

@@ -8,6 +8,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" /> <PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.2" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.2"> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
@@ -15,6 +16,7 @@
</PackageReference> </PackageReference>
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" /> <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.4.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -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 AceJobAgency.Entities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
namespace AceJobAgency.Controllers namespace AceJobAgency.Controllers
{ {
[ApiController] [ApiController]
[Route("[controller]")] [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<IActionResult> Register(User user) public async Task<IActionResult> Register(User user)
{ {
var userEmailExists = context.Users.Any(u => u.Email == user.Email); bool emailExists = _context.Users.Any(u => u.Email == user.Email);
var userNationalRegistrationIdentityCardNumberExists = context.Users.Any( bool nricExists = _context.Users.Any(u =>
u => u.NationalRegistrationIdentityCardNumber u.NationalRegistrationIdentityCardNumber == user.NationalRegistrationIdentityCardNumber);
== user.NationalRegistrationIdentityCardNumber if (emailExists || nricExists)
);
if (userEmailExists || userNationalRegistrationIdentityCardNumberExists)
{ {
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 = BCrypt.Net.BCrypt.HashPassword(user.Password);
user.Id = Guid.NewGuid().ToString();
user.Password = passwordHash;
var userId = Guid.NewGuid().ToString();
user.Id = userId;
user.IsActive = 1; user.IsActive = 1;
await context.Users.AddAsync(user); await _context.Users.AddAsync(user);
await context.SaveChangesAsync(); await _context.SaveChangesAsync();
return Ok(user); 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<IActionResult> 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<IActionResult> 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<IActionResult> 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;
} }
} }

View File

@@ -1,4 +1,8 @@
using System.Text;
using AceJobAgency.Data; using AceJobAgency.Data;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@@ -7,9 +11,54 @@ var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(); builder.Services.AddControllers();
builder.Services.AddDbContext<DataContext>(); builder.Services.AddDbContext<DataContext>();
// Authentication
var secret = builder.Configuration.GetValue<string>("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 // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer(); 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<string>() }
});
});
var app = builder.Build(); var app = builder.Build();

View File

@@ -3,8 +3,7 @@
"DefaultConnection": "server=HOSTNAME;port=3306;database=DB_NAME;user=USERNAME;password=PASSWORD" "DefaultConnection": "server=HOSTNAME;port=3306;database=DB_NAME;user=USERNAME;password=PASSWORD"
}, },
"Authentication": { "Authentication": {
"Secret": "b8bc713a-d8d1-4d37-911e-1bb931d70ba5", "Secret": "b8bc713a-d8d1-4d37-911e-1bb931d70ba5"
"TokenExpiresDays": 30
}, },
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {