using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; namespace Fitness_core_BE.Helpers { // Mints/validates the short-lived "requestAccessToken" used between mobile-OTP verification // and onboarding-request creation. Separate from the RSA-signed AuthHex tokens validated by // CustomMiddleware, since this repo only holds the AuthHex *public* key (no local RSA signing key). public class RequestAccessTokenHelper { private readonly string _signingKey; private readonly int _expiresInMinutes; public RequestAccessTokenHelper(IConfiguration configuration) { _signingKey = configuration["RequestAccessToken:SigningKey"] ?? throw new InvalidOperationException("RequestAccessToken:SigningKey is not configured."); _expiresInMinutes = int.TryParse(configuration["RequestAccessToken:ExpiresInMinutes"], out var m) ? m : 15; } public (string token, int expiresIn) Generate(string mobileNumber) { var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_signingKey)); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var claims = new[] { new Claim("MobileNumber", mobileNumber), new Claim("Purpose", "RequestAccess") }; var expires = DateTime.UtcNow.AddMinutes(_expiresInMinutes); var token = new JwtSecurityToken( claims: claims, expires: expires, signingCredentials: creds); return (new JwtSecurityTokenHandler().WriteToken(token), _expiresInMinutes * 60); } public string? ValidateAndGetMobileNumber(string token) { var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_signingKey)); var handler = new JwtSecurityTokenHandler(); try { var principal = handler.ValidateToken(token, new TokenValidationParameters { ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = true, ValidateIssuerSigningKey = true, IssuerSigningKey = key, ClockSkew = TimeSpan.FromMinutes(2) }, out _); if (principal.FindFirst("Purpose")?.Value != "RequestAccess") return null; return principal.FindFirst("MobileNumber")?.Value; } catch { return null; } } } }