132 lines
4.5 KiB
C#
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);
|
|
}
|
|
}
|