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 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 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 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 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 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 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); } }