228 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			228 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
using System.IdentityModel.Tokens.Jwt;
 | 
						|
using System.Security.Claims;
 | 
						|
using System.Text;
 | 
						|
using Marco.Pms.DataAccess.Data;
 | 
						|
using Marco.Pms.Model.Authentication;
 | 
						|
using Microsoft.EntityFrameworkCore;
 | 
						|
using Microsoft.Extensions.Caching.Memory;
 | 
						|
using Microsoft.IdentityModel.Tokens;
 | 
						|
 | 
						|
#nullable disable
 | 
						|
namespace MarcoBMS.Services.Service
 | 
						|
{
 | 
						|
    public class RefreshTokenService
 | 
						|
    {
 | 
						|
        private readonly ApplicationDbContext _context;
 | 
						|
        private readonly IMemoryCache _cache; // For optional JWT blacklisting
 | 
						|
        private readonly ILoggingService _logger;
 | 
						|
 | 
						|
 | 
						|
        public RefreshTokenService(ApplicationDbContext context, IMemoryCache cache, ILoggingService logger)
 | 
						|
        {
 | 
						|
            _context = context;
 | 
						|
            _cache = cache;
 | 
						|
            _logger = logger;
 | 
						|
        }
 | 
						|
 | 
						|
        public string GenerateJwtToken(string username, Guid tenantId, JwtSettings _jwtSettings)
 | 
						|
        {
 | 
						|
 | 
						|
            // Custom claims
 | 
						|
            var claims = new List<Claim>
 | 
						|
            {
 | 
						|
                new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
 | 
						|
                new Claim(JwtRegisteredClaimNames.Sub, username),
 | 
						|
                new Claim("TenantId", tenantId.ToString()), // Add TenantId claim
 | 
						|
                new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())            };
 | 
						|
 | 
						|
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.Key));
 | 
						|
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
 | 
						|
 | 
						|
            var token = new JwtSecurityToken(
 | 
						|
                issuer: _jwtSettings.Issuer,
 | 
						|
                audience: _jwtSettings.Audience,
 | 
						|
                claims: claims,
 | 
						|
                expires: DateTime.UtcNow.AddMinutes(_jwtSettings.ExpiresInMinutes),
 | 
						|
                signingCredentials: creds);
 | 
						|
 | 
						|
            return new JwtSecurityTokenHandler().WriteToken(token);
 | 
						|
        }
 | 
						|
 | 
						|
        public async Task<string> CreateRefreshToken(string userId, string tenantId, JwtSettings jwtSettings)
 | 
						|
        {
 | 
						|
            try
 | 
						|
            {
 | 
						|
                var claims = new[]
 | 
						|
                {
 | 
						|
                    new Claim(ClaimTypes.NameIdentifier, userId),
 | 
						|
                    new Claim("TenantId", tenantId),
 | 
						|
                    new Claim("token_type", "refresh")
 | 
						|
                };
 | 
						|
 | 
						|
                var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.Key));
 | 
						|
                var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256Signature);
 | 
						|
 | 
						|
                var tokenDescriptor = new SecurityTokenDescriptor
 | 
						|
                {
 | 
						|
                    Subject = new ClaimsIdentity(claims),
 | 
						|
                    Expires = DateTime.UtcNow.AddDays(jwtSettings.RefreshTokenExpiresInDays),
 | 
						|
                    Issuer = jwtSettings.Issuer,
 | 
						|
                    Audience = jwtSettings.Audience,
 | 
						|
                    SigningCredentials = credentials
 | 
						|
                };
 | 
						|
 | 
						|
                var tokenHandler = new JwtSecurityTokenHandler();
 | 
						|
                var refreshTokenString = tokenHandler.WriteToken(tokenHandler.CreateToken(tokenDescriptor));
 | 
						|
 | 
						|
                var refreshToken = new RefreshToken
 | 
						|
                {
 | 
						|
                    Token = refreshTokenString,
 | 
						|
                    UserId = userId,
 | 
						|
                    ExpiryDate = DateTime.UtcNow.AddDays(jwtSettings.RefreshTokenExpiresInDays),
 | 
						|
                    IsRevoked = false
 | 
						|
                };
 | 
						|
 | 
						|
                // Check if the record exists
 | 
						|
                var existingEntity = await _context.RefreshTokens.AnyAsync(c => c.UserId == userId);
 | 
						|
                if (existingEntity) { _context.RefreshTokens.Update(refreshToken); }
 | 
						|
                else
 | 
						|
                {
 | 
						|
                    _context.RefreshTokens.Add(refreshToken);
 | 
						|
                }
 | 
						|
                await _context.SaveChangesAsync();
 | 
						|
                return refreshTokenString;
 | 
						|
            }
 | 
						|
            catch (Exception ex)
 | 
						|
            {
 | 
						|
                _logger.LogError("{Error}", ex.Message);
 | 
						|
                throw;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        public string CreateMPINToken(string userId, string tenantId, JwtSettings jwtSettings)
 | 
						|
        {
 | 
						|
            try
 | 
						|
            {
 | 
						|
 | 
						|
                var claims = new[]
 | 
						|
                {
 | 
						|
                        new Claim(ClaimTypes.NameIdentifier, userId),
 | 
						|
                        new Claim("TenantId", tenantId),
 | 
						|
                        new Claim("token_type", "mpin")
 | 
						|
                    };
 | 
						|
 | 
						|
                var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.Key));
 | 
						|
                var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256Signature);
 | 
						|
 | 
						|
                var tokenDescriptor = new SecurityTokenDescriptor
 | 
						|
                {
 | 
						|
                    Subject = new ClaimsIdentity(claims),
 | 
						|
                    Issuer = jwtSettings.Issuer,
 | 
						|
                    Audience = jwtSettings.Audience,
 | 
						|
                    SigningCredentials = creds
 | 
						|
                    // No 'Expires' means the token won't expire
 | 
						|
                };
 | 
						|
 | 
						|
                var tokenHandler = new JwtSecurityTokenHandler();
 | 
						|
                var MPINToken = tokenHandler.WriteToken(tokenHandler.CreateToken(tokenDescriptor));
 | 
						|
                return MPINToken;
 | 
						|
 | 
						|
 | 
						|
 | 
						|
 | 
						|
            }
 | 
						|
            catch (Exception ex)
 | 
						|
            {
 | 
						|
                _logger.LogError("Error creating MPIN token for userId: {UserId}, tenantId: {TenantId}, error : {Error}", userId, tenantId, ex.Message);
 | 
						|
                throw;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        public async Task<RefreshToken> GetRefreshToken(string token)
 | 
						|
        {
 | 
						|
            return await _context.RefreshTokens.FirstOrDefaultAsync(rt => rt.Token == token && !rt.IsRevoked && !rt.IsUsed) ?? new RefreshToken();
 | 
						|
        }
 | 
						|
 | 
						|
        public async Task MarkRefreshTokenAsUsed(RefreshToken refreshToken)
 | 
						|
        {
 | 
						|
            refreshToken.IsUsed = true;
 | 
						|
            _context.RefreshTokens.Update(refreshToken);
 | 
						|
            await _context.SaveChangesAsync();
 | 
						|
        }
 | 
						|
 | 
						|
        public async Task RevokeRefreshToken(RefreshToken refreshToken)
 | 
						|
        {
 | 
						|
            refreshToken.IsRevoked = true;
 | 
						|
            refreshToken.RevokedAt = DateTime.UtcNow;
 | 
						|
 | 
						|
            _context.RefreshTokens.Update(refreshToken);
 | 
						|
            await _context.SaveChangesAsync();
 | 
						|
        }
 | 
						|
 | 
						|
        // Revoke refresh token
 | 
						|
        public async Task<bool> RevokeRefreshTokenAsync(string refreshToken)
 | 
						|
        {
 | 
						|
            var token = await _context.RefreshTokens.FirstOrDefaultAsync(t => t.Token == refreshToken);
 | 
						|
 | 
						|
            if (token == null || token.IsRevoked || token.ExpiryDate <= DateTime.UtcNow)
 | 
						|
                return false;
 | 
						|
 | 
						|
            token.IsRevoked = true;
 | 
						|
            token.RevokedAt = DateTime.UtcNow;
 | 
						|
 | 
						|
            await _context.SaveChangesAsync();
 | 
						|
            return true;
 | 
						|
        }
 | 
						|
 | 
						|
        // Optional: Blacklist JWT token
 | 
						|
        public Task BlacklistJwtTokenAsync(string jwtToken)
 | 
						|
        {
 | 
						|
            // Store the JWT token in memory cache with its expiry
 | 
						|
            var jwtExpiry = GetJwtExpiry(jwtToken);
 | 
						|
            if (jwtExpiry.HasValue)
 | 
						|
            {
 | 
						|
                _cache.Set(jwtToken, true, jwtExpiry.Value - DateTime.UtcNow);
 | 
						|
            }
 | 
						|
 | 
						|
            return Task.CompletedTask;
 | 
						|
        }
 | 
						|
 | 
						|
        private DateTime? GetJwtExpiry(string token)
 | 
						|
        {
 | 
						|
            var handler = new JwtSecurityTokenHandler();
 | 
						|
            var jwtToken = handler.ReadToken(token) as JwtSecurityToken;
 | 
						|
            return jwtToken?.ValidTo;
 | 
						|
        }
 | 
						|
 | 
						|
        public ClaimsPrincipal ValidateToken(string token, JwtSettings jwtSettings)
 | 
						|
        {
 | 
						|
            var tokenHandler = new JwtSecurityTokenHandler();
 | 
						|
            var key = System.Text.Encoding.ASCII.GetBytes(jwtSettings.Key);
 | 
						|
 | 
						|
            var validationParameters = new TokenValidationParameters
 | 
						|
            {
 | 
						|
                ValidateIssuerSigningKey = true,
 | 
						|
                IssuerSigningKey = new SymmetricSecurityKey(key),
 | 
						|
                ValidateIssuer = true,
 | 
						|
                ValidIssuer = jwtSettings.Issuer,
 | 
						|
                ValidateAudience = true,
 | 
						|
                ValidAudience = jwtSettings.Audience,
 | 
						|
                ValidateLifetime = false, // Disable lifetime validation (ignores expiration)
 | 
						|
                ClockSkew = TimeSpan.Zero // Optional: Remove time skew buffer
 | 
						|
            };
 | 
						|
 | 
						|
            try
 | 
						|
            {
 | 
						|
                var principal = tokenHandler.ValidateToken(token, validationParameters, out SecurityToken validatedToken);
 | 
						|
                return principal;
 | 
						|
            }
 | 
						|
            catch (Exception ex)
 | 
						|
            {
 | 
						|
                // Token is invalid
 | 
						|
                Console.WriteLine($"Token validation failed: {ex.Message}");
 | 
						|
                return null;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
    }
 | 
						|
}
 |