using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Microsoft.Extensions.Configuration; using Microsoft.IdentityModel.Tokens; using YesChef.Api.Auth; using YesChef.Api.Entities; namespace YesChef.Api.UnitTests.Auth; public class JwtTokenServiceTests { private const string Secret = "this-is-a-test-secret-that-is-definitely-long-enough-for-hs256"; private static JwtTokenService BuildService(string secret = Secret) { var config = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary { ["Jwt:Secret"] = secret }) .Build(); return new JwtTokenService(config); } private static JwtSecurityToken Decode(string token) => new JwtSecurityTokenHandler().ReadJwtToken(token); [Test] public async Task GenerateToken_includes_user_id_name_and_family_claims() { var service = BuildService(); var user = new User { Id = 42, Name = "alice", PasswordHash = "x" }; var jwt = Decode(service.GenerateToken(user, familyId: 7, role: FamilyRole.Admin)); await Assert.That(jwt.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value).IsEqualTo("42"); await Assert.That(jwt.Claims.First(c => c.Type == ClaimTypes.Name).Value).IsEqualTo("alice"); await Assert.That(jwt.Claims.First(c => c.Type == JwtTokenService.FamilyIdClaim).Value).IsEqualTo("7"); await Assert.That(jwt.Claims.First(c => c.Type == JwtTokenService.FamilyRoleClaim).Value).IsEqualTo("Admin"); } [Test] public async Task GenerateToken_expires_in_about_30_days() { var service = BuildService(); var user = new User { Id = 1, Name = "bob", PasswordHash = "x" }; var jwt = Decode(service.GenerateToken(user, familyId: 1, role: FamilyRole.Member)); var expectedExpiry = DateTime.UtcNow.AddDays(30); var delta = (jwt.ValidTo - expectedExpiry).Duration(); await Assert.That(delta).IsLessThan(TimeSpan.FromMinutes(1)); } [Test] public async Task GenerateToken_signs_with_hs256_using_configured_secret() { var service = BuildService(); var user = new User { Id = 7, Name = "carol", PasswordHash = "x" }; var token = service.GenerateToken(user, familyId: 1, role: FamilyRole.Member); var validator = new JwtSecurityTokenHandler(); var parameters = new TokenValidationParameters { ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = true, ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Secret)) }; var principal = validator.ValidateToken(token, parameters, out var validatedToken); await Assert.That(principal.Identity!.IsAuthenticated).IsTrue(); await Assert.That(((JwtSecurityToken)validatedToken).SignatureAlgorithm) .IsEqualTo(SecurityAlgorithms.HmacSha256); } [Test] public async Task GenerateToken_with_different_secret_fails_validation() { var service = BuildService(); var token = service.GenerateToken(new User { Id = 1, Name = "x", PasswordHash = "x" }, familyId: 1, role: FamilyRole.Member); var validator = new JwtSecurityTokenHandler(); var parameters = new TokenValidationParameters { ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = true, IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes("a-completely-different-secret-of-sufficient-length")) }; await Assert.That(() => validator.ValidateToken(token, parameters, out _)) .ThrowsExactly(); } }