114 lines
4.4 KiB
C#
114 lines
4.4 KiB
C#
using Auth.Models;
|
|
using Auth.Services;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
using System.IdentityModel.Tokens.Jwt;
|
|
using System.Security.Claims;
|
|
using System.Text;
|
|
|
|
namespace Auth.Controllers;
|
|
|
|
[ApiController]
|
|
[Route("api/[controller]")]
|
|
public class AuthController : ControllerBase
|
|
{
|
|
private readonly UserService _users;
|
|
private readonly IConfiguration _cfg;
|
|
private readonly BlacklistService _blacklist;
|
|
|
|
public AuthController(UserService users, IConfiguration cfg, BlacklistService blacklist)
|
|
{
|
|
_users = users; _cfg = cfg; _blacklist = blacklist;
|
|
}
|
|
|
|
[HttpPost("register")]
|
|
public async Task<IActionResult> Register([FromBody] RegisterRequest req)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(req.Username) || string.IsNullOrWhiteSpace(req.Password))
|
|
return BadRequest("Username and password required");
|
|
|
|
if (await _users.GetByUsernameAsync(req.Username) != null)
|
|
return BadRequest("User already exists");
|
|
|
|
var hash = BCrypt.Net.BCrypt.HashPassword(req.Password);
|
|
var user = new User { Username = req.Username, PasswordHash = hash, Role = "USER", Email = req.Email };
|
|
await _users.CreateAsync(user);
|
|
return Ok("User created");
|
|
}
|
|
|
|
[HttpPost("login")]
|
|
public async Task<IActionResult> Login([FromBody] LoginRequest req)
|
|
{
|
|
var user = await _users.GetByUsernameAsync(req.Username);
|
|
if (user == null || !BCrypt.Net.BCrypt.Verify(req.Password, user.PasswordHash))
|
|
return Unauthorized();
|
|
|
|
var (accessToken, jti, expUtc) = GenerateJwtToken(user);
|
|
user.RefreshToken = Guid.NewGuid().ToString("N");
|
|
user.RefreshTokenExpiry = DateTime.UtcNow.AddDays(7);
|
|
await _users.UpdateAsync(user);
|
|
|
|
return Ok(new { accessToken, refreshToken = user.RefreshToken, user.Username, user.Role, jti, exp = expUtc });
|
|
}
|
|
|
|
[HttpPost("refresh")]
|
|
public async Task<IActionResult> Refresh([FromBody] RefreshRequest req)
|
|
{
|
|
var user = await _users.GetByUsernameAsync(req.Username);
|
|
if (user == null || user.RefreshToken != req.RefreshToken || user.RefreshTokenExpiry < DateTime.UtcNow)
|
|
return Unauthorized("Invalid or expired refresh token");
|
|
|
|
var (accessToken, _, expUtc) = GenerateJwtToken(user);
|
|
return Ok(new { accessToken, exp = expUtc });
|
|
}
|
|
|
|
[HttpPost("logout")]
|
|
[Authorize(Roles = "USER,SUPER")]
|
|
public async Task<IActionResult> Logout()
|
|
{
|
|
var token = HttpContext.Request.Headers["Authorization"].FirstOrDefault()?.Replace("Bearer ", "");
|
|
if (string.IsNullOrWhiteSpace(token)) return BadRequest("Token missing");
|
|
var jwt = new JwtSecurityTokenHandler().ReadJwtToken(token);
|
|
await _blacklist.AddToBlacklistAsync(jwt.Id, jwt.ValidTo);
|
|
return Ok("Logged out.");
|
|
}
|
|
|
|
[HttpPost("role")]
|
|
[Authorize(Roles = "SUPER")]
|
|
public async Task<IActionResult> ChangeUserRole([FromBody] ChangeRoleRequest req)
|
|
{
|
|
if (req.NewRole is not ("USER" or "SUPER")) return BadRequest("Role must be 'USER' or 'SUPER'");
|
|
var user = await _users.GetByUsernameAsync(req.Username);
|
|
if (user is null) return NotFound("User not found");
|
|
user.Role = req.NewRole;
|
|
await _users.UpdateAsync(user);
|
|
return Ok($"{req.Username}'s role updated to {req.NewRole}");
|
|
}
|
|
|
|
[HttpGet("users")]
|
|
[Authorize(Roles = "SUPER")]
|
|
public async Task<IActionResult> GetAllUsers() => Ok(await _users.GetAllAsync());
|
|
|
|
private (string token, string jti, DateTime expUtc) GenerateJwtToken(User user)
|
|
{
|
|
var key = Encoding.UTF8.GetBytes(_cfg["Jwt:Key"]!);
|
|
var issuer = _cfg["Jwt:Issuer"] ?? "GameAuthApi";
|
|
var audience = _cfg["Jwt:Audience"] ?? issuer;
|
|
|
|
var creds = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256);
|
|
var jti = Guid.NewGuid().ToString("N");
|
|
var claims = new[]
|
|
{
|
|
new Claim(ClaimTypes.Name, user.Username),
|
|
new Claim(ClaimTypes.NameIdentifier, user.Id),
|
|
new Claim(ClaimTypes.Role, user.Role),
|
|
new Claim(JwtRegisteredClaimNames.Jti, jti)
|
|
};
|
|
|
|
var exp = DateTime.UtcNow.AddMinutes(15);
|
|
var token = new JwtSecurityToken(issuer, audience, claims, expires: exp, signingCredentials: creds);
|
|
return (new JwtSecurityTokenHandler().WriteToken(token), jti, exp);
|
|
}
|
|
}
|