Files
meezi/src/Meezi.API/Controllers/AuthController.cs
T

126 lines
5.3 KiB
C#
Raw Normal View History

2026-05-27 21:33:48 +03:30
using FluentValidation;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Meezi.API.Models.Auth;
using Meezi.API.Services;
using Meezi.Core.Constants;
using Meezi.Shared;
namespace Meezi.API.Controllers;
[ApiController]
[Route("api/auth")]
public class AuthController : ControllerBase
{
private readonly IAuthService _authService;
private readonly IValidator<SendOtpRequest> _sendOtpValidator;
private readonly IValidator<VerifyOtpRequest> _verifyOtpValidator;
private readonly IValidator<RefreshTokenRequest> _refreshValidator;
public AuthController(
IAuthService authService,
IValidator<SendOtpRequest> sendOtpValidator,
IValidator<VerifyOtpRequest> verifyOtpValidator,
IValidator<RefreshTokenRequest> refreshValidator)
{
_authService = authService;
_sendOtpValidator = sendOtpValidator;
_verifyOtpValidator = verifyOtpValidator;
_refreshValidator = refreshValidator;
}
[HttpPost("send-otp")]
[EnableRateLimiting("auth-otp")]
[ProducesResponseType(typeof(ApiResponse<SendOtpResponse>), StatusCodes.Status200OK)]
public async Task<IActionResult> SendOtp([FromBody] SendOtpRequest request, CancellationToken cancellationToken)
{
var validation = await _sendOtpValidator.ValidateAsync(request, cancellationToken);
if (!validation.IsValid)
return BadRequest(ValidationError(validation));
var (success, data, code, message) = await _authService.SendOtpAsync(request, cancellationToken);
if (!success)
return ErrorResult(code!, message!);
return Ok(new ApiResponse<SendOtpResponse>(true, data));
}
[HttpPost("verify-otp")]
[ProducesResponseType(typeof(ApiResponse<AuthTokenResponse>), StatusCodes.Status200OK)]
public async Task<IActionResult> VerifyOtp([FromBody] VerifyOtpRequest request, CancellationToken cancellationToken)
{
var validation = await _verifyOtpValidator.ValidateAsync(request, cancellationToken);
if (!validation.IsValid)
return BadRequest(ValidationError(validation));
var (success, data, code, message) = await _authService.VerifyOtpAsync(request, cancellationToken);
if (!success)
return ErrorResult(code!, message!);
return Ok(new ApiResponse<AuthTokenResponse>(true, data));
}
[HttpPost("refresh")]
[ProducesResponseType(typeof(ApiResponse<AuthTokenResponse>), StatusCodes.Status200OK)]
public async Task<IActionResult> Refresh([FromBody] RefreshTokenRequest request, CancellationToken cancellationToken)
{
var validation = await _refreshValidator.ValidateAsync(request, cancellationToken);
if (!validation.IsValid)
return BadRequest(ValidationError(validation));
var (success, data, code, message) = await _authService.RefreshAsync(request, cancellationToken);
if (!success)
return ErrorResult(code!, message!);
return Ok(new ApiResponse<AuthTokenResponse>(true, data));
}
[HttpGet("me")]
[Authorize]
[ProducesResponseType(typeof(ApiResponse<AuthTokenResponse>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public IActionResult GetMe()
{
var userId = User.FindFirstValue(JwtRegisteredClaimNames.Sub)
?? User.FindFirstValue(ClaimTypes.NameIdentifier)
?? string.Empty;
var expClaim = User.FindFirstValue(JwtRegisteredClaimNames.Exp);
var expiresAt = expClaim != null && long.TryParse(expClaim, out var exp)
? DateTimeOffset.FromUnixTimeSeconds(exp).UtcDateTime
: DateTime.UtcNow;
var data = new AuthTokenResponse(
AccessToken: string.Empty,
RefreshToken: string.Empty,
ExpiresAt: expiresAt,
UserId: userId,
CafeId: User.FindFirstValue(MeeziClaimTypes.CafeId) ?? string.Empty,
Role: User.FindFirstValue(MeeziClaimTypes.Role) ?? string.Empty,
PlanTier: User.FindFirstValue(MeeziClaimTypes.PlanTier) ?? string.Empty,
Language: User.FindFirstValue(MeeziClaimTypes.Language) ?? string.Empty,
Actor: User.FindFirstValue(MeeziClaimTypes.Actor) ?? MeeziActorKinds.Merchant,
BranchId: User.FindFirstValue(MeeziClaimTypes.BranchId));
return Ok(new ApiResponse<AuthTokenResponse>(true, data));
}
private static ApiResponse<object> ValidationError(FluentValidation.Results.ValidationResult validation)
{
var first = validation.Errors.First();
return new ApiResponse<object>(false, null, new ApiError("VALIDATION_ERROR", first.ErrorMessage, first.PropertyName));
}
private IActionResult ErrorResult(string code, string message) => code switch
{
"RATE_LIMITED" => StatusCode(StatusCodes.Status429TooManyRequests,
new ApiResponse<object>(false, null, new ApiError(code, message))),
"NOT_FOUND" => NotFound(new ApiResponse<object>(false, null, new ApiError(code, message))),
"INVALID_OTP" or "INVALID_TOKEN" => Unauthorized(new ApiResponse<object>(false, null, new ApiError(code, message))),
_ => BadRequest(new ApiResponse<object>(false, null, new ApiError(code, message)))
};
}