feat(backend): add admin evaluation history, engine, simulation, bot API controllers and global exception middleware
This commit is contained in:
@@ -21,17 +21,15 @@ public record UserProfileResponseDto(
|
||||
[property: JsonPropertyName("role")] string Role
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Request payload for changing a user's initial (admin-issued) password. The target user is derived
|
||||
/// from the caller's own JWT identity claim (see <see cref="AuthController.ChangeInitialPassword"/>),
|
||||
/// not from this payload, so it deliberately carries no user identifier.
|
||||
/// </summary>
|
||||
public record ChangeInitialPasswordDto(
|
||||
[property: JsonPropertyName("userId")] Guid UserId,
|
||||
[property: JsonPropertyName("newPassword")] string NewPassword
|
||||
);
|
||||
|
||||
public class ForgotPasswordRequestDto
|
||||
{
|
||||
[JsonPropertyName("email")]
|
||||
public string Email { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
[ApiController]
|
||||
[Route("api/v1")]
|
||||
public class AuthController : ControllerBase
|
||||
@@ -45,7 +43,13 @@ public class AuthController : ControllerBase
|
||||
|
||||
/// <summary>
|
||||
/// Authenticates a user and returns a JWT token.
|
||||
/// Deliberately the single anonymous authentication entry point of the Gateway (Rules.md §7): a client
|
||||
/// has no JWT to present before it has logged in, so there is no way to require authentication here.
|
||||
/// Every other route in the Gateway requires authentication except three other sanctioned exceptions:
|
||||
/// the Docker healthcheck (<c>GET /health</c>), the static asset-logo endpoint (<c>GET /api/v1/logo/{isin}</c>,
|
||||
/// which image loaders cannot attach a bearer token to), and the Flutter Web SPA fallback file.
|
||||
/// </summary>
|
||||
[AllowAnonymous]
|
||||
[HttpPost("auth/login")]
|
||||
public async Task<IActionResult> Login([FromBody] LoginRequestDto request, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -64,17 +68,29 @@ public class AuthController : ControllerBase
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the initial password required by an admin reset or creation.
|
||||
/// Changes the initial password required after an admin-driven account creation or password reset.
|
||||
/// Requires authentication (Rules.md §7): a caller always already holds a valid JWT at this point,
|
||||
/// because <see cref="Login"/> issues one immediately, carrying <c>RequiresPasswordChange</c> in the
|
||||
/// response body, before the client ever calls this endpoint. The target user is derived from the
|
||||
/// caller's own token claim rather than accepted as a request parameter, so a caller can never
|
||||
/// change another account's initial password by supplying an arbitrary user identifier.
|
||||
/// </summary>
|
||||
[Authorize]
|
||||
[HttpPost("auth/change-initial-password")]
|
||||
public async Task<IActionResult> ChangeInitialPassword([FromBody] ChangeInitialPasswordDto request, CancellationToken cancellationToken)
|
||||
{
|
||||
if (request.UserId == Guid.Empty || string.IsNullOrWhiteSpace(request.NewPassword))
|
||||
if (string.IsNullOrWhiteSpace(request?.NewPassword))
|
||||
{
|
||||
return BadRequest(new { error = "UserId and NewPassword are required." });
|
||||
return BadRequest(new { error = "NewPassword is required." });
|
||||
}
|
||||
|
||||
bool changed = await _userService.ChangeInitialPasswordAsync(request.UserId, request.NewPassword, cancellationToken);
|
||||
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? User.FindFirst("sub")?.Value;
|
||||
if (!Guid.TryParse(userIdClaim, out var userId))
|
||||
{
|
||||
return Unauthorized(new { error = "Invalid User Token claim." });
|
||||
}
|
||||
|
||||
bool changed = await _userService.ChangeInitialPasswordAsync(userId, request.NewPassword, cancellationToken);
|
||||
if (!changed)
|
||||
{
|
||||
return BadRequest(new { error = "Password change failed. User not found or password change not required." });
|
||||
|
||||
Reference in New Issue
Block a user