< Summary

Information
Class: Backend.Services.Impl.TokenService
Assembly: Backend
File(s): D:\a\smart-meal-planner\smart-meal-planner\backend\Backend\Services\Impl\TokenService.cs
Line coverage
100%
Covered lines: 94
Uncovered lines: 0
Coverable lines: 94
Total lines: 156
Line coverage: 100%
Branch coverage
75%
Covered branches: 15
Total branches: 20
Branch coverage: 75%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity Line coverage
.ctor(...)100%1100%
GenerateAccessToken(...)50%4100%
GenerateRefreshToken()50%2100%
FindRefreshToken()100%2100%
RevokeRefreshToken()100%1100%
GenerateResetToken(...)50%2100%
ValidateResetToken(...)90%10100%

File(s)

D:\a\smart-meal-planner\smart-meal-planner\backend\Backend\Services\Impl\TokenService.cs

#LineLine coverage
 1using System.IdentityModel.Tokens.Jwt;
 2using System.Security.Claims;
 3using System.Security.Cryptography;
 4using System.Text;
 5using Microsoft.EntityFrameworkCore;
 6using Microsoft.IdentityModel.Tokens;
 7using Backend.Model;
 8
 9namespace Backend.Services.Impl
 10{
 11    public class TokenService : ITokenService
 12    {
 13        private readonly IConfiguration _config;
 14        private readonly PlannerContext _context;
 15        private readonly ILogger<TokenService> _logger;
 1216        private readonly string _resetStr = "reset";
 1217        public TokenService(IConfiguration config, PlannerContext context, ILogger<TokenService> logger)
 18        {
 1219            _config = config;
 1220            _context = context;
 1221            _logger = logger;
 1222        }
 23
 24        public string GenerateAccessToken(User user)
 25        {
 126            var claims = new[]
 127            {
 128                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
 129                new Claim(ClaimTypes.Name, user.Email)
 130            };
 31
 132            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"] ?? ""));
 133            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
 34
 135            var token = new JwtSecurityToken(
 136                issuer: _config["Jwt:Issuer"],
 137                audience: _config["Jwt:Audience"],
 138                claims: claims,
 139                expires: DateTime.UtcNow.AddMinutes(int.Parse(_config["Jwt:ExpireMinutes"] ?? "15")),
 140                signingCredentials: creds
 141            );
 42
 143            _logger.LogInformation("Generated JWT token for user {UserId} at {Time}", user.Id, DateTime.UtcNow);
 144            return new JwtSecurityTokenHandler().WriteToken(token);
 45        }
 46
 47        public async Task<RefreshToken> GenerateRefreshToken(User user, string ipAddress)
 48        {
 149            var randomBytes = new byte[64];
 150            using var rng = RandomNumberGenerator.Create();
 151            rng.GetBytes(randomBytes);
 152            var refreshToken = new RefreshToken
 153            {
 154                Token = Convert.ToBase64String(randomBytes),
 155                Expires = DateTime.UtcNow.AddDays(int.Parse(_config["Jwt:RefreshExpireDays"] ?? "7")),
 156                Created = DateTime.UtcNow,
 157                UserId = user.Id,
 158                CreatedByIp = ipAddress,
 159            };
 60
 161            _context.RefreshTokens.Add(refreshToken);
 162            await _context.SaveChangesAsync();
 63
 164            _logger.LogInformation("Generated refresh token for user {UserId} at {Time}", user.Id, DateTime.UtcNow);
 65
 166            return refreshToken;
 167        }
 68
 69        public async Task<RefreshToken?> FindRefreshToken(string tokenStr)
 70        {
 371            var token = await _context.RefreshTokens.FirstOrDefaultAsync(t => t.Token == tokenStr);
 372            _logger.LogInformation("Found refresh token for user {UserId} at {Time}", token?.UserId, DateTime.UtcNow);
 373            return token;
 374        }
 75
 76        public async Task RevokeRefreshToken(RefreshToken token)
 77        {
 178            token.IsRevoked = true;
 179            _context.RefreshTokens.Update(token);
 180            await _context.SaveChangesAsync();
 181            _logger.LogInformation("Revoked refresh token for user {UserId} at {Time}", token.UserId, DateTime.UtcNow);
 182        }
 83
 84        public string GenerateResetToken(User user)
 85        {
 386            var tokenHandler = new JwtSecurityTokenHandler();
 387            var key = Encoding.UTF8.GetBytes(_config["Jwt:Key"] ?? "default_secret_key");
 88
 389            var claims = new[]
 390            {
 391                new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
 392                new Claim(_resetStr, "true") // custom claim to mark as reset token
 393            };
 94
 395            var tokenDescriptor = new SecurityTokenDescriptor
 396            {
 397                Subject = new ClaimsIdentity(claims),
 398                Expires = DateTime.UtcNow.AddHours(1), // token valid for 1 hour
 399                SigningCredentials = new SigningCredentials(
 3100                    new SymmetricSecurityKey(key),
 3101                    SecurityAlgorithms.HmacSha256Signature
 3102                ),
 3103                Audience = _config["Jwt:Audience"],
 3104                Issuer = _config["Jwt:Issuer"]
 3105            };
 106
 3107            var token = tokenHandler.CreateToken(tokenDescriptor);
 3108            return tokenHandler.WriteToken(token);
 109        }
 110
 111        public int? ValidateResetToken(string token)
 112        {
 6113            var tokenHandler = new JwtSecurityTokenHandler();
 6114            var key = Encoding.UTF8.GetBytes(_config["Jwt:Key"] ?? "default_secret_key");
 115
 116            try
 117            {
 6118                var principal = tokenHandler.ValidateToken(token, new TokenValidationParameters
 6119                {
 6120                    ValidateIssuerSigningKey = true,
 6121                    IssuerSigningKey = new SymmetricSecurityKey(key),
 6122                    ValidateIssuer = true,
 6123                    ValidIssuer = _config["Jwt:Issuer"],
 6124                    ValidateAudience = true,
 6125                    ValidAudience = _config["Jwt:Audience"],
 6126                    ValidateLifetime = true,
 6127                    ClockSkew = TimeSpan.Zero
 6128                }, out var validatedToken);
 129
 130                // Make sure it's actually a reset token
 4131                var resetClaim = principal.FindFirst(_resetStr)?.Value;
 4132                if (resetClaim != "true")
 133                {
 1134                    _logger.LogWarning("Invalid reset token: {Token}", token);
 1135                    return null;
 136                }
 137
 3138                var sub = principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
 139
 3140                int.TryParse(sub, out var userId);
 3141                if (userId <= 0)
 142                {
 2143                    _logger.LogWarning("Invalid user ID in reset token: {Token}", token);
 2144                    return null;
 145                }
 146
 1147                return userId;
 148            }
 2149            catch (Exception ex)
 150            {
 2151                _logger.LogError(ex, "Error validating reset token: {Token}", token);
 2152                return null; // invalid or expired token
 153            }
 6154        }
 155    }
 156}