Files
Antifraude.Net/Antifraude.Net/ApiDenuncias/Controllers/AuthController.cs
2026-05-06 13:48:23 +02:00

132 lines
4.5 KiB
C#

using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Text.RegularExpressions;
using ApiDenuncias.Configuration;
using GestionaDenuncias.Shared.Models;
using ApiDenuncias.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
namespace ApiDenuncias.Controllers;
[ApiController]
[Route("api/auth")]
public sealed class AuthController : ControllerBase
{
private readonly GlobalLeaksClient _globalLeaksClient;
private readonly GlobalLeaksSessionStore _sessionStore;
private readonly LoginRateLimiter _rateLimiter;
private readonly JwtOptions _jwtOptions;
public AuthController(
GlobalLeaksClient globalLeaksClient,
GlobalLeaksSessionStore sessionStore,
LoginRateLimiter rateLimiter,
IOptions<JwtOptions> jwtOptions)
{
_globalLeaksClient = globalLeaksClient;
_sessionStore = sessionStore;
_rateLimiter = rateLimiter;
_jwtOptions = jwtOptions.Value;
}
[HttpPost("login")]
[AllowAnonymous]
public async Task<ActionResult<ApiLoginResponse>> Login(LoginRequest request, CancellationToken cancellationToken)
{
var ip = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
if (!_rateLimiter.AllowAttempt(ip))
{
return StatusCode(StatusCodes.Status429TooManyRequests, new ApiError("Demasiados intentos. Espera un minuto."));
}
if (string.IsNullOrWhiteSpace(request.Username) ||
string.IsNullOrWhiteSpace(request.Password) ||
string.IsNullOrWhiteSpace(request.Authcode))
{
return BadRequest(new ApiError("Debes indicar usuario, contrasena y codigo 2FA."));
}
if (!Regex.IsMatch(request.Authcode.Trim(), @"^\d{6}$"))
{
return BadRequest(new ApiError("El codigo 2FA debe tener exactamente 6 digitos."));
}
try
{
var session = await _globalLeaksClient.LoginAsync(
request.Username.Trim(),
request.Password,
request.Authcode.Trim(),
cancellationToken);
var username = string.IsNullOrWhiteSpace(session.Username)
? request.Username.Trim()
: session.Username.Trim();
await _sessionStore.SaveAsync(username, request.Password, session.Id, session.Role, cancellationToken);
var expiresAtUtc = DateTimeOffset.UtcNow.AddMinutes(Math.Max(5, _jwtOptions.ExpirationMinutes));
var token = CreateJwt(username, session.Role, expiresAtUtc);
return Ok(new ApiLoginResponse(username, token, expiresAtUtc, session.Role));
}
catch (GlobalLeaksValidationException ex)
{
return StatusCode(ex.StatusCode, new ApiError(ex.Message));
}
catch
{
return StatusCode(StatusCodes.Status502BadGateway, new ApiError("No se ha podido conectar con GlobalLeaks."));
}
}
[HttpPost("logout")]
[Authorize]
public async Task<IActionResult> Logout(CancellationToken cancellationToken)
{
var username = User.Identity?.Name;
if (!string.IsNullOrWhiteSpace(username))
{
await _sessionStore.DeleteAsync(username, cancellationToken);
}
return Ok(new { ok = true });
}
private string CreateJwt(string username, string? role, DateTimeOffset expiresAtUtc)
{
if (string.IsNullOrWhiteSpace(_jwtOptions.SigningKey))
{
throw new InvalidOperationException("Falta Jwt:SigningKey en la configuracion de ApiDenuncias.");
}
var claims = new List<Claim>
{
new(JwtRegisteredClaimNames.Sub, username),
new(ClaimTypes.Name, username),
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString("N"))
};
if (!string.IsNullOrWhiteSpace(role))
{
claims.Add(new Claim("gl_role", role));
}
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtOptions.SigningKey));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _jwtOptions.Issuer,
audience: _jwtOptions.Audience,
claims: claims,
notBefore: DateTime.UtcNow,
expires: expiresAtUtc.UtcDateTime,
signingCredentials: credentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}