diff --git a/src/api/SIGCM2.Infrastructure/Security/JwtService.cs b/src/api/SIGCM2.Infrastructure/Security/JwtService.cs index 02ac8c7..85ab73e 100644 --- a/src/api/SIGCM2.Infrastructure/Security/JwtService.cs +++ b/src/api/SIGCM2.Infrastructure/Security/JwtService.cs @@ -1,6 +1,5 @@ using System.Security.Claims; using System.Security.Cryptography; -using System.Text.Json; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using SIGCM2.Application.Abstractions.Security; @@ -44,13 +43,17 @@ public sealed class JwtService : IJwtService return principal; } + /// + /// 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). + /// public string GenerateAccessToken(Usuario usuario) { var signingKey = new RsaSecurityKey(_rsa); var credentials = new SigningCredentials(signingKey, SecurityAlgorithms.RsaSha256); - var permisos = DeserializePermisos(usuario.PermisosJson); - var claims = new List { new(JwtRegisteredClaimNames.Sub, usuario.Id.ToString()), @@ -59,10 +62,6 @@ public sealed class JwtService : IJwtService new("rol", usuario.Rol), }; - // Add each permission as a separate claim - foreach (var permiso in permisos) - claims.Add(new Claim("permisos", permiso)); - var now = DateTime.UtcNow; var descriptor = new SecurityTokenDescriptor { @@ -78,16 +77,4 @@ public sealed class JwtService : IJwtService var token = handler.CreateToken(descriptor); return handler.WriteToken(token); } - - private static string[] DeserializePermisos(string permisosJson) - { - try - { - return JsonSerializer.Deserialize(permisosJson) ?? []; - } - catch - { - return []; - } - } } diff --git a/tests/SIGCM2.Application.Tests/Infrastructure/JwtServiceTests.cs b/tests/SIGCM2.Application.Tests/Infrastructure/JwtServiceTests.cs index cea3266..c080c8f 100644 --- a/tests/SIGCM2.Application.Tests/Infrastructure/JwtServiceTests.cs +++ b/tests/SIGCM2.Application.Tests/Infrastructure/JwtServiceTests.cs @@ -55,6 +55,59 @@ public class JwtServiceTests : IDisposable Assert.Contains("sigcm2.web", parsed.Audiences); // aud Assert.Contains(parsed.Claims, c => c.Type == "name" && c.Value == "admin"); Assert.Contains(parsed.Claims, c => c.Type == "rol" && c.Value == "admin"); + + // J-01 (UDT-009): token must NOT contain 'permisos' claim post-UDT-009 + Assert.DoesNotContain(parsed.Claims, c => c.Type == "permisos"); + } + + // J-01: token post-UDT-009 does NOT have 'permisos' claim + [Fact] + public void GenerateAccessToken_DoesNotContainPermisosClaim() + { + var usuario = MakeUsuario(); + var token = _jwtService.GenerateAccessToken(usuario); + + var handler = new JwtSecurityTokenHandler(); + var parsed = handler.ReadJwtToken(token); + + Assert.DoesNotContain(parsed.Claims, c => c.Type == "permisos"); + } + + // J-02: claims present are sub, jti, name, rol (+ iat/exp/nbf) — no extras + [Fact] + public void GenerateAccessToken_HasExactlyExpectedClaims_NoPermisos() + { + var usuario = MakeUsuario(); + var token = _jwtService.GenerateAccessToken(usuario); + + var handler = new JwtSecurityTokenHandler(); + var parsed = handler.ReadJwtToken(token); + + // Must have sub, name, rol, jti + Assert.Contains(parsed.Claims, c => c.Type == "sub"); + Assert.Contains(parsed.Claims, c => c.Type == "name"); + Assert.Contains(parsed.Claims, c => c.Type == "rol"); + Assert.Contains(parsed.Claims, c => c.Type == "jti"); + + // Must NOT have permisos + Assert.DoesNotContain(parsed.Claims, c => c.Type == "permisos"); + } + + // J-03: MakeUsuario with '["*"]' PermisosJson → token still has no 'permisos' claim + [Fact] + public void GenerateAccessToken_WithLegacyPermisosJson_NoPermisosClaim() + { + // MakeUsuario already uses '[\"*\"]' — this explicitly tests J-03 + var usuario = MakeUsuario(); + Assert.Equal("[\"*\"]", usuario.PermisosJson); // verify the helper + + var token = _jwtService.GenerateAccessToken(usuario); + + var handler = new JwtSecurityTokenHandler(); + var parsed = handler.ReadJwtToken(token); + + // Post-UDT-009: JwtService ignores PermisosJson entirely — no claim emitted + Assert.DoesNotContain(parsed.Claims, c => c.Type == "permisos"); } // Scenario: token is verifiable with the public key