using System.IdentityModel.Tokens.Jwt; using System.Net.Http.Headers; using System.Security.Claims; using System.Text; using Microsoft.IdentityModel.Tokens; namespace SatelliteProvider.IntegrationTests; public static class JwtTestHelpers { public const string JwtSecretEnvVar = "JWT_SECRET"; public const string DefaultSubject = "integration-tests"; public static string ResolveSecretOrThrow() { var secret = Environment.GetEnvironmentVariable(JwtSecretEnvVar); if (string.IsNullOrWhiteSpace(secret)) { throw new InvalidOperationException( $"{JwtSecretEnvVar} is not set in the integration test environment. " + "It must match the JWT_SECRET configured for the API container."); } var byteLength = Encoding.UTF8.GetByteCount(secret); if (byteLength < 32) { throw new InvalidOperationException( $"{JwtSecretEnvVar} is {byteLength} bytes; the test runner requires at least 32 bytes to match API validation."); } return secret; } public static string MintValidToken(string secret, string subject = DefaultSubject, TimeSpan? lifetime = null, IEnumerable? extraClaims = null) { ArgumentNullException.ThrowIfNull(secret); var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret)); var credentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256); var now = DateTime.UtcNow; var claims = new List { new(JwtRegisteredClaimNames.Sub, subject), new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString("N")) }; if (extraClaims is not null) { claims.AddRange(extraClaims); } var token = new JwtSecurityToken( issuer: null, audience: null, claims: claims, notBefore: now, expires: now.Add(lifetime ?? TimeSpan.FromHours(1)), signingCredentials: credentials); return new JwtSecurityTokenHandler().WriteToken(token); } public static string MintExpiredToken(string secret, string subject = DefaultSubject) { return MintValidToken(secret, subject, lifetime: TimeSpan.FromMinutes(-10)); } public static string TamperSignature(string token) { var parts = token.Split('.'); if (parts.Length != 3) { throw new ArgumentException("JWT must have three dot-separated segments.", nameof(token)); } var signature = parts[2]; var firstChar = signature[0]; parts[2] = (firstChar == 'A' ? 'B' : 'A') + signature[1..]; return string.Join('.', parts); } public static void AttachDefaultAuthorization(HttpClient httpClient, string token) { httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); } }