diff --git a/tests/SIGCM2.Application.Tests/Infrastructure/JwtServiceTests.cs b/tests/SIGCM2.Application.Tests/Infrastructure/JwtServiceTests.cs index 3a1fc34..cea3266 100644 --- a/tests/SIGCM2.Application.Tests/Infrastructure/JwtServiceTests.cs +++ b/tests/SIGCM2.Application.Tests/Infrastructure/JwtServiceTests.cs @@ -122,6 +122,65 @@ public class JwtServiceTests : IDisposable Assert.Equal("2", parsed2.Subject); } + // T-040: GetPrincipalFromExpiredToken + + [Fact] + public void GetPrincipalFromExpiredToken_ValidSignatureExpired_ReturnsPrincipal() + { + // Generate a token that will expire in 1 second, then manually create an expired JWT + // using JwtSecurityTokenHandler directly (bypassing the service to control timestamps). + var signingKey = new RsaSecurityKey(_rsa); + var credentials = new SigningCredentials(signingKey, SecurityAlgorithms.RsaSha256); + + var past = DateTime.UtcNow.AddHours(-2); + var descriptor = new SecurityTokenDescriptor + { + Subject = new System.Security.Claims.ClaimsIdentity( + [new System.Security.Claims.Claim("sub", "1")]), + Issuer = "sigcm2.api", + Audience = "sigcm2.web", + IssuedAt = past, + NotBefore = past, + Expires = past.AddMinutes(60), // expired 1h ago + SigningCredentials = credentials, + }; + + var handler = new JwtSecurityTokenHandler(); + var token = handler.CreateToken(descriptor); + var expiredToken = handler.WriteToken(token); + + // Now use the service — it must validate the signature but ignore expiry + var principal = _jwtService.GetPrincipalFromExpiredToken(expiredToken); + + Assert.NotNull(principal); + // The JWT handler maps "sub" to ClaimTypes.NameIdentifier by default, + // but our JwtService uses a custom "sub" claim. Check both. + var sub = principal.FindFirst("sub")?.Value + ?? principal.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; + Assert.Equal("1", sub); + } + + [Fact] + public void GetPrincipalFromExpiredToken_InvalidSignature_Throws() + { + // Sign with a different RSA key + using var otherRsa = System.Security.Cryptography.RSA.Create(2048); + var otherOptions = new JwtOptions { Issuer = "sigcm2.api", Audience = "sigcm2.web", AccessTokenMinutes = 60 }; + var otherService = new JwtService(otherRsa, otherOptions); + var tokenFromOtherKey = otherService.GenerateAccessToken(MakeUsuario()); + + // Validating with the correct key should throw + Assert.Throws( + () => _jwtService.GetPrincipalFromExpiredToken(tokenFromOtherKey)); + } + + [Fact] + public void GetPrincipalFromExpiredToken_MalformedToken_Throws() + { + Assert.ThrowsAny( + () => _jwtService.GetPrincipalFromExpiredToken("not.a.valid.jwt")); + } + private static Usuario MakeUsuario(int id = 1, string username = "admin") => new(id, username, "$2a$12$hash", "Administrador", "Sistema", null, "admin", "[\"*\"]", true); }