183 lines
6.7 KiB
C#
183 lines
6.7 KiB
C#
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;
|
|
}
|
|
|
|
/// <summary>Public view of a user: no password, no hash, no salt.</summary>
|
|
private static ApplicationUserDTO ToDto(ApplicationUser user)
|
|
{
|
|
return new ApplicationUserDTO
|
|
{
|
|
Id = user.Id,
|
|
Username = user.Username,
|
|
FirstName = user.FirstName,
|
|
LastName = user.LastName,
|
|
Roles = user.Names()
|
|
};
|
|
}
|
|
|
|
/// <summary>The id the JWT was issued for; null if the request is not authenticated.</summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>Every account, for the admin user-management screen.</summary>
|
|
[Authorize(Roles = Roles.Admin)]
|
|
[HttpGet]
|
|
[ProducesResponseType(typeof(List<ApplicationUserDTO>), StatusCodes.Status200OK)]
|
|
public IActionResult GetUsers()
|
|
{
|
|
var users = _userService.GetAll()
|
|
.OrderBy(user => user.Username)
|
|
.Select(ToDto)
|
|
.ToList();
|
|
|
|
return Ok(users);
|
|
}
|
|
|
|
/// <summary>The role names that may be assigned, from the applicationrole table.</summary>
|
|
[Authorize(Roles = Roles.Admin)]
|
|
[HttpGet("Roles")]
|
|
[ProducesResponseType(typeof(List<string>), StatusCodes.Status200OK)]
|
|
public IActionResult GetRoles()
|
|
{
|
|
return Ok(_userService.GetAllRoles().Select(role => role.Name).ToList());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an account. This replaces the old anonymous <c>register</c> 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).
|
|
/// </summary>
|
|
[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 });
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
[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 });
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|