using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using RecNet.Application.Common.Interfaces; using RecNet.Application.Common.Tokens; using RecNet.Domain.Profiles; namespace RecNet.Infrastructure.Services.Tokens; public class TokenService(IOptions options) : ITokenService { public TokenResult GenerateProfileToken(Profile profile) { var jwtOptions = options.Value; if (string.IsNullOrWhiteSpace(jwtOptions.Secret)) throw new InvalidOperationException("JWT secret is not configured."); var expiresAt = DateTimeOffset.UtcNow.AddMinutes(jwtOptions.ExpiryMinutes); var signingCredentials = new SigningCredentials( CreateSecurityKey(jwtOptions), SecurityAlgorithms.HmacSha256); var claims = new List { new(JwtRegisteredClaimNames.Sub, profile.ProfileId.ToString()), new("rn.plat", ((int)profile.Platform).ToString()), new("rn.platid", profile.PlatformId) }; if (profile.IsModerator) claims.Add(new Claim("role", "moderator")); var token = new JwtSecurityToken( issuer: jwtOptions.Issuer, audience: jwtOptions.Audience, claims: claims, expires: expiresAt.UtcDateTime, signingCredentials: signingCredentials); return new TokenResult( new JwtSecurityTokenHandler().WriteToken(token), jwtOptions.ExpiryMinutes * 60); } public TokenVerifyResult VerifyToken(string token) { var jwtOptions = options.Value; if (string.IsNullOrWhiteSpace(jwtOptions.Secret)) throw new InvalidOperationException("JWT secret is not configured."); if (string.IsNullOrWhiteSpace(token)) return TokenVerifyResult.Failure(); var tokenHandler = new JwtSecurityTokenHandler(); if (!tokenHandler.CanReadToken(token)) return TokenVerifyResult.Failure(); var validationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = CreateSecurityKey(jwtOptions), ValidateIssuer = true, ValidIssuer = jwtOptions.Issuer, ValidateAudience = true, ValidAudience = jwtOptions.Audience, ValidateLifetime = true, ClockSkew = TimeSpan.FromMinutes(1) }; try { var principal = tokenHandler.ValidateToken(token, validationParameters, out _); var profileIdClaim = principal.FindFirst(JwtRegisteredClaimNames.Sub)?.Value ?? principal.FindFirst(ClaimTypes.NameIdentifier)?.Value; return Guid.TryParse(profileIdClaim, out var profileId) ? TokenVerifyResult.Success(profileId) : TokenVerifyResult.Failure(); } catch (SecurityTokenException) { return TokenVerifyResult.Failure(); } catch (ArgumentException) { return TokenVerifyResult.Failure(); } } private static SymmetricSecurityKey CreateSecurityKey(JwtOptions jwtOptions) => new(Encoding.UTF8.GetBytes(jwtOptions.Secret)); }