- Fix MSTest compatibility with .NET 9.0 by upgrading to MSTest.TestFramework 4.2.3 - Move Tests directory to solution level (Tests/) to prevent test files from being compiled with GermanApp - Update test project references to point to GermanApp/GermanApp.csproj - Add InternalsVisibleTo attributes for test assemblies - Make RefreshToken properties internal for testability - Fix User.ChangeEmail null handling - Add 46 comprehensive unit tests for User and RefreshToken domain entities - All tests passing successfully Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
159 lines
5 KiB
C#
159 lines
5 KiB
C#
using GermanApp.Application.DTOs.Auth;
|
|
using GermanApp.Application.Interfaces;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using System.Net;
|
|
|
|
namespace GermanApp.Presentation.Controllers;
|
|
|
|
/// <summary>
|
|
/// Controller for authentication endpoints.
|
|
/// Part of the Presentation layer.
|
|
/// </summary>
|
|
[ApiController]
|
|
[Route("api/[controller]")]
|
|
public class AuthController : ControllerBase
|
|
{
|
|
private readonly IAuthService _authService;
|
|
|
|
public AuthController(IAuthService authService)
|
|
{
|
|
_authService = authService;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register a new user.
|
|
/// </summary>
|
|
/// <param name="registerDto">Registration data</param>
|
|
/// <returns>Authentication response with JWT token</returns>
|
|
[HttpPost("register")]
|
|
[ProducesResponseType(typeof(AuthResponse), (int)HttpStatusCode.OK)]
|
|
[ProducesResponseType(typeof(string), (int)HttpStatusCode.BadRequest)]
|
|
public async Task<IActionResult> Register([FromBody] RegisterDto registerDto)
|
|
{
|
|
try
|
|
{
|
|
var result = await _authService.RegisterAsync(registerDto);
|
|
return Ok(result);
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
return BadRequest(ex.Message);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return StatusCode((int)HttpStatusCode.InternalServerError, ex.Message);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Login an existing user.
|
|
/// </summary>
|
|
/// <param name="loginDto">Login data</param>
|
|
/// <returns>Authentication response with JWT token</returns>
|
|
[HttpPost("login")]
|
|
[ProducesResponseType(typeof(AuthResponse), (int)HttpStatusCode.OK)]
|
|
[ProducesResponseType(typeof(string), (int)HttpStatusCode.Unauthorized)]
|
|
public async Task<IActionResult> Login([FromBody] LoginDto loginDto)
|
|
{
|
|
try
|
|
{
|
|
var result = await _authService.LoginAsync(loginDto);
|
|
return Ok(result);
|
|
}
|
|
catch (UnauthorizedAccessException ex)
|
|
{
|
|
return Unauthorized(ex.Message);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return StatusCode((int)HttpStatusCode.InternalServerError, ex.Message);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get current authenticated user information.
|
|
/// </summary>
|
|
/// <returns>Current user information</returns>
|
|
[HttpGet("me")]
|
|
[Authorize]
|
|
[ProducesResponseType(typeof(AuthResponse), (int)HttpStatusCode.OK)]
|
|
[ProducesResponseType((int)HttpStatusCode.Unauthorized)]
|
|
public async Task<IActionResult> GetCurrentUser()
|
|
{
|
|
try
|
|
{
|
|
var userId = int.Parse(User.FindFirst("nameid")?.Value ?? "0");
|
|
if (userId == 0)
|
|
return Unauthorized();
|
|
|
|
var user = await _authService.GetCurrentUserAsync(userId);
|
|
if (user == null)
|
|
return Unauthorized();
|
|
|
|
return Ok(new AuthResponse
|
|
{
|
|
UserId = user.Id,
|
|
Username = user.Username,
|
|
Email = user.Email
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return StatusCode((int)HttpStatusCode.InternalServerError, ex.Message);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Refresh the access token using a refresh token.
|
|
/// </summary>
|
|
/// <param name="refreshToken">The refresh token</param>
|
|
/// <returns>New access token and refresh token</returns>
|
|
[HttpPost("refresh")]
|
|
[ProducesResponseType(typeof(RefreshTokenResponse), (int)HttpStatusCode.OK)]
|
|
[ProducesResponseType(typeof(string), (int)HttpStatusCode.Unauthorized)]
|
|
[ProducesResponseType(typeof(string), (int)HttpStatusCode.BadRequest)]
|
|
public async Task<IActionResult> Refresh([FromBody] string refreshToken)
|
|
{
|
|
try
|
|
{
|
|
var result = await _authService.RefreshTokenAsync(refreshToken);
|
|
return Ok(result);
|
|
}
|
|
catch (UnauthorizedAccessException ex)
|
|
{
|
|
return Unauthorized(ex.Message);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return StatusCode((int)HttpStatusCode.InternalServerError, ex.Message);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Revoke a refresh token.
|
|
/// </summary>
|
|
/// <param name="refreshToken">The refresh token to revoke</param>
|
|
/// <returns>Success or error response</returns>
|
|
[HttpPost("revoke-refresh")]
|
|
[Authorize]
|
|
[ProducesResponseType((int)HttpStatusCode.OK)]
|
|
[ProducesResponseType(typeof(string), (int)HttpStatusCode.Unauthorized)]
|
|
[ProducesResponseType(typeof(string), (int)HttpStatusCode.BadRequest)]
|
|
public async Task<IActionResult> RevokeRefreshToken([FromBody] string refreshToken)
|
|
{
|
|
try
|
|
{
|
|
await _authService.RevokeRefreshTokenAsync(refreshToken);
|
|
return Ok(new { message = "Refresh token revoked successfully" });
|
|
}
|
|
catch (UnauthorizedAccessException ex)
|
|
{
|
|
return Unauthorized(ex.Message);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return StatusCode((int)HttpStatusCode.InternalServerError, ex.Message);
|
|
}
|
|
}
|
|
}
|