using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; using System.Text; using System.Threading.Tasks; using LaDOSE.Business.Interface; using LaDOSE.DTO; using LaDOSE.Entity; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; namespace LaDOSE.Api.Controllers { [Authorize] [Produces("application/json")] [Route("[controller]")] public class UsersController : ControllerBase { private IUserService _userService; private readonly IConfiguration _configuration; public UsersController( IUserService userService, IConfiguration configuration ) { _userService = userService; _configuration = configuration; } /// Public view of a user: no password, no hash, no salt. private static ApplicationUserDTO ToDto(ApplicationUser user) { return new ApplicationUserDTO { Id = user.Id, Username = user.Username, FirstName = user.FirstName, LastName = user.LastName, Roles = user.Names() }; } /// The id the JWT was issued for; null if the request is not authenticated. private int? CurrentUserId() { return int.TryParse(User?.Identity?.Name, out var id) ? id : (int?)null; } [AllowAnonymous] [HttpPost("auth")] [ProducesResponseType(typeof(ApplicationUserDTO), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public IActionResult Authenticate([FromBody]ApplicationUserDTO userDto) { var user = _userService.Authenticate(userDto?.Username, userDto?.Password); if (user == null) return BadRequest(new { message = "Username or password is incorrect" }); var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.ASCII.GetBytes(this._configuration["JWTTokenSecret"]); var tokenDescriptor = new SecurityTokenDescriptor { // Only the user id goes in the token. Roles are read from the database on // every request instead, so granting or revoking Admin takes effect at // once rather than whenever the current token happens to expire. Subject = new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Name, user.Id.ToString()), }), Expires = DateTime.UtcNow.AddMinutes(16), SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) }; var token = tokenHandler.CreateToken(tokenDescriptor); var tokenString = tokenHandler.WriteToken(token); // return basic user info (without password) and token to store client side var dto = ToDto(user); dto.Token = tokenString; dto.Expire = token.ValidTo; return Ok(dto); } /// Every account, for the admin user-management screen. [Authorize(Roles = Roles.Admin)] [HttpGet] [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] public IActionResult GetUsers() { var users = _userService.GetAll() .OrderBy(user => user.Username) .Select(ToDto) .ToList(); return Ok(users); } /// The role names that may be assigned, from the applicationrole table. [Authorize(Roles = Roles.Admin)] [HttpGet("Roles")] [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] public IActionResult GetRoles() { return Ok(_userService.GetAllRoles().Select(role => role.Name).ToList()); } /// /// Creates an account. This replaces the old anonymous register endpoint — /// only an admin may create users now, so the very first admin has to be promoted /// directly in the database (see Sql/2026-08-05_roles.sql). /// [Authorize(Roles = Roles.Admin)] [HttpPost("AddUser")] [ProducesResponseType(typeof(ApplicationUserDTO), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public IActionResult AddUser([FromBody]ApplicationUserDTO userDto) { if (userDto == null) return BadRequest(new { message = "No user supplied" }); try { var created = _userService.Create( new ApplicationUser { Username = userDto.Username?.Trim(), FirstName = userDto.FirstName, LastName = userDto.LastName }, userDto.Password, userDto.Roles); return Ok(ToDto(created)); } catch (Exception ex) { // return error message if there was an exception return BadRequest(new { message = ex.Message }); } } /// /// Deletes an account. Refuses to delete the caller: since only an admin can reach /// this, and an admin cannot remove themselves, at least one admin always survives /// — which matters because there is no anonymous way back in any more. /// [Authorize(Roles = Roles.Admin)] [HttpDelete("{id}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] public IActionResult DeleteUser(int id) { var user = _userService.GetById(id); if (user == null) return NotFound(new { message = "User not found" }); if (CurrentUserId() == id) return BadRequest(new { message = "You cannot delete your own account" }); try { _userService.Delete(id); return NoContent(); } catch (Exception ex) { return BadRequest(new { message = ex.Message }); } } } }