282 lines
8.8 KiB
C#
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
|
|
};
|
|
}
|
|
} |