Files
SIG-CM2.0/src/api/SIGCM2.Infrastructure/Security/JwtService.cs
dmolinari a9838427a4 feat(udt-011): T400.30 — inject TimeProvider into Infrastructure critical services
AuditLogger, SecurityEventLogger: inject TimeProvider and use
_timeProvider.GetUtcNow().UtcDateTime for occurredAt timestamps.
JwtService: inject TimeProvider; use GetUtcNow() for token IssuedAt/Expires.
DI: update JwtService factory to pass sp.GetRequiredService<TimeProvider>().
Repositories: remove ?? DateTime.UtcNow fallback in UpdateAsync since callers
always provide FechaModificacion via domain mutators.
2026-04-18 10:12:24 -03:00

83 lines
2.9 KiB
C#

using System.Security.Claims;
using System.Security.Cryptography;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using SIGCM2.Application.Abstractions.Security;
using SIGCM2.Domain.Entities;
namespace SIGCM2.Infrastructure.Security;
public sealed class JwtService : IJwtService
{
private readonly RSA _rsa;
private readonly JwtOptions _options;
private readonly TimeProvider _timeProvider;
public JwtService(RSA rsa, JwtOptions options, TimeProvider timeProvider)
{
_rsa = rsa;
_options = options;
_timeProvider = timeProvider;
}
/// <inheritdoc/>
public ClaimsPrincipal GetPrincipalFromExpiredToken(string accessToken)
{
var parameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = _options.Issuer,
ValidateAudience = true,
ValidAudience = _options.Audience,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new RsaSecurityKey(_rsa),
ValidateLifetime = false, // Key: accept expired tokens in refresh flow
ClockSkew = TimeSpan.Zero,
};
var handler = new JwtSecurityTokenHandler();
var principal = handler.ValidateToken(accessToken, parameters, out var securityToken);
if (securityToken is not JwtSecurityToken jwt ||
!jwt.Header.Alg.Equals(SecurityAlgorithms.RsaSha256, StringComparison.OrdinalIgnoreCase))
throw new SecurityTokenException("Invalid token algorithm");
return principal;
}
/// <summary>
/// UDT-009: Generates an access token with minimal claims.
/// Claim 'permisos' has been removed — authorization handler resolves permissions
/// from DB per-request using IUsuarioRepository + PermisoResolver.
/// Token claims: sub, jti, name, rol (+ standard iat/exp/nbf).
/// </summary>
public string GenerateAccessToken(Usuario usuario)
{
var signingKey = new RsaSecurityKey(_rsa);
var credentials = new SigningCredentials(signingKey, SecurityAlgorithms.RsaSha256);
var claims = new List<Claim>
{
new(JwtRegisteredClaimNames.Sub, usuario.Id.ToString()),
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new("name", usuario.Username),
new("rol", usuario.Rol),
};
var now = _timeProvider.GetUtcNow().UtcDateTime;
var descriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Issuer = _options.Issuer,
Audience = _options.Audience,
IssuedAt = now,
Expires = now.AddMinutes(_options.AccessTokenMinutes),
SigningCredentials = credentials
};
var handler = new JwtSecurityTokenHandler();
var token = handler.CreateToken(descriptor);
return handler.WriteToken(token);
}
}