using System.Security.Claims; using MealPlan.Api.DTOs.Auth; using MealPlan.Api.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.RateLimiting; namespace MealPlan.Api.Controllers; [ApiController] [Route("api/auth")] public class AuthController : ControllerBase { private readonly IAuthService _authService; public AuthController(IAuthService authService) => _authService = authService; /// Creates a new user account and returns an access + refresh token pair. [HttpPost("register")] [AllowAnonymous] [EnableRateLimiting("login")] [ProducesResponseType(typeof(TokenResponseDto), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status409Conflict)] public async Task Register([FromBody] RegisterRequestDto request, CancellationToken ct) { var result = await _authService.RegisterAsync(request, ct); if (!result.Succeeded) { var status = result.Error?.Contains("already exists", StringComparison.OrdinalIgnoreCase) == true ? StatusCodes.Status409Conflict : StatusCodes.Status400BadRequest; return StatusCode(status, new { error = result.Error }); } return StatusCode(StatusCodes.Status201Created, result.Tokens); } /// Authenticates a user and returns an access + refresh token pair. [HttpPost("login")] [AllowAnonymous] [EnableRateLimiting("login")] [ProducesResponseType(typeof(TokenResponseDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] public async Task Login([FromBody] LoginRequestDto request, CancellationToken ct) { var result = await _authService.LoginAsync(request, ct); if (!result.Succeeded) { return Unauthorized(new { error = result.Error }); } return Ok(result.Tokens); } /// Rotates a refresh token and issues a fresh token pair. [HttpPost("refresh")] [AllowAnonymous] [ProducesResponseType(typeof(TokenResponseDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] public async Task Refresh([FromBody] RefreshRequestDto request, CancellationToken ct) { var result = await _authService.RefreshAsync(request.RefreshToken, ct); if (!result.Succeeded) { return Unauthorized(new { error = result.Error }); } return Ok(result.Tokens); } /// Revokes the supplied refresh token. Always returns 204. [HttpPost("logout")] [AllowAnonymous] [ProducesResponseType(StatusCodes.Status204NoContent)] public IActionResult Logout([FromBody] RefreshRequestDto request) { _authService.Logout(request.RefreshToken); return NoContent(); } /// Returns the currently authenticated user's profile. [HttpGet("me")] [Authorize] [ProducesResponseType(typeof(UserInfoDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] public async Task Me(CancellationToken ct) { var userId = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"); if (string.IsNullOrEmpty(userId)) { return Unauthorized(); } var info = await _authService.GetUserInfoAsync(userId, ct); return info is null ? Unauthorized() : Ok(info); } }