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.
83 lines
2.9 KiB
C#
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);
|
|
}
|
|
}
|