using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Microsoft.AspNetCore.Identity; using Microsoft.IdentityModel.Tokens; using Newsbot.Collector.Api.Domain.Consts; using Newsbot.Collector.Domain.Results; using Newsbot.Collector.Domain.Entities; using Newsbot.Collector.Domain.Interfaces; using Newsbot.Collector.Domain.Models.Config; using JwtRegisteredClaimNames = Microsoft.IdentityModel.JsonWebTokens.JwtRegisteredClaimNames; namespace Newsbot.Collector.Services; public interface IIdentityService { AuthenticationResult Register(string email, string password); AuthenticationResult Login(string email, string password); AuthenticationResult RefreshToken(string token, string refreshToken); void AddRole(string name, string userId); } public class IdentityService : IIdentityService { private readonly UserManager _userManager; private readonly RoleManager _roleManager; private readonly JwtSettings _jwtSettings; private readonly TokenValidationParameters _tokenValidationParameters; private readonly IRefreshTokenRepository _refreshTokenRepository; public IdentityService(UserManager userManager, JwtSettings jwtSettings, TokenValidationParameters tokenValidationParameters, IRefreshTokenRepository refreshTokenRepository, RoleManager roleManager) { _userManager = userManager; _jwtSettings = jwtSettings; _tokenValidationParameters = tokenValidationParameters; _refreshTokenRepository = refreshTokenRepository; _roleManager = roleManager; } public AuthenticationResult Register(string email, string password) { var userExists = _userManager.FindByEmailAsync(email); userExists.Wait(); if (userExists.Result != null) { return new AuthenticationResult { ErrorMessage = new[] { "A user with this email address already exists" } }; } var newUser = new IdentityUser { UserName = email, Email = email }; var createdUser = _userManager.CreateAsync(newUser, password); createdUser.Wait(); if (!createdUser.Result.Succeeded) { return new AuthenticationResult { ErrorMessage = new List(createdUser.Result.Errors.Select(x => x.Description)) }; } var addRole = _userManager.AddToRoleAsync(newUser, Authorization.UsersRole); addRole.Wait(); return GenerateJwtToken(newUser); } public AuthenticationResult Login(string email, string password) { var user =_userManager.FindByEmailAsync(email); user.Wait(); if (user.Result == null) { return new AuthenticationResult { ErrorMessage = new[] { "User does not exist" } }; } var hasValidPassword = _userManager.CheckPasswordAsync(user.Result ?? new IdentityUser(), password); hasValidPassword.Wait(); if (!hasValidPassword.Result) { return new AuthenticationResult() { ErrorMessage = new[] { "Password is invalid" } }; } return GenerateJwtToken(user.Result ?? new IdentityUser()); } public AuthenticationResult RefreshToken(string token, string refreshToken) { var validatedToken = CheckTokenSigner(token); if (validatedToken is null) { return new AuthenticationResult { ErrorMessage = new List { "Invalid Token" } }; } // Get the expire datetime of the token var expiryDateUnix = long.Parse(validatedToken.Claims.Single(x => x.Type == JwtRegisteredClaimNames.Exp).Value); // generate the unix epoc, add expiry time var unixTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); var expiryDateTimeUtc = unixTime.AddSeconds(expiryDateUnix); // if it expires in the future if (expiryDateTimeUtc > DateTime.Now) { return new AuthenticationResult { ErrorMessage = new List { "The token has not expired yet" } }; } var jti = validatedToken.Claims.Single(x => x.Type == JwtRegisteredClaimNames.Jti).Value; var storedToken = _refreshTokenRepository.Get(token); if (storedToken is null) { return new AuthenticationResult { ErrorMessage = new List { "The refresh token does not exist" } }; } if (DateTime.UtcNow > storedToken.ExpiryDate) { return new AuthenticationResult { ErrorMessage = new List { "The refresh token has expired" } }; } if (storedToken.Invalidated) { return new AuthenticationResult { ErrorMessage = new List { "The token is not valid" } }; } if (storedToken.Used) { return new AuthenticationResult { ErrorMessage = new List { "The token has been used" } }; } if (storedToken.JwtId != jti) { return new AuthenticationResult { ErrorMessage = new List { "The token does not match this JWT" } }; } _refreshTokenRepository.UpdateTokenIsUsed(token); var user = _userManager.FindByIdAsync(validatedToken.Claims.Single(x => x.Type == "id").Value); user.Wait(); if (user.Result is null) { return new AuthenticationResult { ErrorMessage = new List { "Unable to find user" } }; } return GenerateJwtToken(user.Result); } public void AddRole(string name, string userId) { var user = _userManager.FindByIdAsync(userId); user.Wait(); if (user.Result is null) { throw new Exception("User was not found"); } if (!name.Equals(Authorization.AdministratorClaim) || !name.Equals(Authorization.UserClaim)) { throw new Exception("Invalid role"); } var addRole = _userManager.AddToRoleAsync(user.Result, name); addRole.Wait(); } private ClaimsPrincipal? CheckTokenSigner(string token) { var tokenHandler = new JwtSecurityTokenHandler(); try { var principal = tokenHandler.ValidateToken(token, _tokenValidationParameters, out var validatedToken); if (IsSecurityTokenValidSecurity(validatedToken)) { return null; } return principal; } catch { return null; } } private bool IsSecurityTokenValidSecurity(SecurityToken token) { return (token is JwtSecurityToken jwtSecurityToken) && jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha512, StringComparison.InvariantCultureIgnoreCase); } private AuthenticationResult GenerateJwtToken(IdentityUser user) { var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.ASCII.GetBytes(_jwtSettings.Secret ?? ""); var claims = new List { new Claim(JwtRegisteredClaimNames.Sub, user.Email ?? ""), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim(JwtRegisteredClaimNames.Email, user.Email ?? ""), new Claim("id", user.Id) }; var userRoles = _userManager.GetRolesAsync(user); userRoles.Wait(); foreach (var role in userRoles.Result) { claims.Add(new Claim(ClaimTypes.Role, role)); } var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(claims), Expires = DateTime.UtcNow.AddHours(3), SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) }; var token = tokenHandler.CreateToken(tokenDescriptor); var refreshToken = new RefreshTokenEntity { Token = token.Id, JwtId = token.Id, UserId = user.Id, CreatedDate = DateTime.UtcNow, ExpiryDate = DateTime.UtcNow.AddMonths(6) }; _refreshTokenRepository.Add(refreshToken); return new AuthenticationResult { IsSuccessful = true, Token = tokenHandler.WriteToken(token), RefreshToken = refreshToken.Token }; } }