Newsbot.Collector/Newsbot.Collector.Services/IdentityService.cs

282 lines
8.8 KiB
C#

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<IdentityUser> _userManager;
private readonly RoleManager<IdentityRole> _roleManager;
private readonly JwtSettings _jwtSettings;
private readonly TokenValidationParameters _tokenValidationParameters;
private readonly IRefreshTokenRepository _refreshTokenRepository;
public IdentityService(UserManager<IdentityUser> userManager, JwtSettings jwtSettings, TokenValidationParameters tokenValidationParameters, IRefreshTokenRepository refreshTokenRepository, RoleManager<IdentityRole> 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<string>(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<string> { "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<string> { "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<string> { "The refresh token does not exist" }
};
}
if (DateTime.UtcNow > storedToken.ExpiryDate)
{
return new AuthenticationResult
{
ErrorMessage = new List<string> { "The refresh token has expired" }
};
}
if (storedToken.Invalidated)
{
return new AuthenticationResult
{
ErrorMessage = new List<string> { "The token is not valid" }
};
}
if (storedToken.Used)
{
return new AuthenticationResult
{
ErrorMessage = new List<string> { "The token has been used" }
};
}
if (storedToken.JwtId != jti)
{
return new AuthenticationResult
{
ErrorMessage = new List<string> { "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<string> { "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<Claim>
{
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
};
}
}