175 lines
6.0 KiB
C#
175 lines
6.0 KiB
C#
using System.IdentityModel.Tokens.Jwt;
|
|
using System.Security.Claims;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using AutobusApi.Application.Common.Exceptions;
|
|
using AutobusApi.Application.Common.Interfaces;
|
|
using AutobusApi.Application.Common.Models.Identity;
|
|
using AutobusApi.Domain.Enums;
|
|
using AutobusApi.Infrastructure.Identity;
|
|
using Microsoft.AspNetCore.Identity;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
|
|
namespace AutobusApi.Infrastructure.Services;
|
|
|
|
public class IdentityService : IIdentityService
|
|
{
|
|
private readonly UserManager<ApplicationUser> _userManager;
|
|
private readonly IConfiguration _configuration;
|
|
|
|
public IdentityService(
|
|
UserManager<ApplicationUser> userManager,
|
|
IConfiguration configuration)
|
|
{
|
|
_userManager = userManager;
|
|
_configuration = configuration;
|
|
}
|
|
|
|
public async Task RegisterAsync(
|
|
string email,
|
|
string password,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var userWithSameEmail = await _userManager.FindByEmailAsync(email);
|
|
if (userWithSameEmail is not null)
|
|
{
|
|
throw new RegistrationException("User with given email already registered.");
|
|
}
|
|
|
|
var newUser = new ApplicationUser
|
|
{
|
|
UserName = email,
|
|
Email = email
|
|
};
|
|
|
|
var createUserResult = await _userManager.CreateAsync(newUser, password);
|
|
var addToRoleResult = await _userManager.AddToRoleAsync(newUser, IdentityRoles.User.ToString());
|
|
}
|
|
|
|
public async Task<TokensModel> LoginAsync(
|
|
string email,
|
|
string password,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var user = await _userManager.FindByEmailAsync(email);
|
|
if (user is null)
|
|
{
|
|
throw new LoginException("No users registered with given email.");
|
|
}
|
|
|
|
var isPasswordCorrect = await _userManager.CheckPasswordAsync(user, password);
|
|
if (!isPasswordCorrect)
|
|
{
|
|
throw new LoginException("Given password is incorrect.");
|
|
}
|
|
|
|
var jwtSecurityToken = await CreateJwtAsync(user, cancellationToken);
|
|
var accessToken = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
|
|
|
|
var refreshToken = user.RefreshTokens.FirstOrDefault(t => t.IsActive);
|
|
if (refreshToken is null)
|
|
{
|
|
refreshToken = CreateRefreshToken();
|
|
user.RefreshTokens.Add(refreshToken);
|
|
await _userManager.UpdateAsync(user);
|
|
}
|
|
|
|
return new TokensModel(accessToken, refreshToken.Value);
|
|
}
|
|
|
|
public async Task<TokensModel> RenewAccessTokenAsync(
|
|
string refreshToken,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var user = await _userManager.Users.SingleOrDefaultAsync(u => u.RefreshTokens.Any(rt => rt.Value == refreshToken));
|
|
if (user is null)
|
|
{
|
|
throw new RenewAccessTokenException($"Refresh token {refreshToken} was not found.");
|
|
}
|
|
|
|
var refreshTokenObject = user.RefreshTokens.Single(rt => rt.Value == refreshToken);
|
|
if (!refreshTokenObject.IsActive)
|
|
{
|
|
throw new RenewAccessTokenException("Refresh token is inactive.");
|
|
}
|
|
|
|
var jwtSecurityToken = await CreateJwtAsync(user, cancellationToken);
|
|
var accessToken = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
|
|
|
|
return new TokensModel(accessToken, refreshToken);
|
|
}
|
|
|
|
public async Task RevokeRefreshTokenAsync(
|
|
string refreshToken,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var user = await _userManager.Users.SingleOrDefaultAsync(u => u.RefreshTokens.Any(t => t.Value == refreshToken));
|
|
if (user is null)
|
|
{
|
|
throw new RevokeRefreshTokenException("Invalid refreshToken");
|
|
}
|
|
|
|
var refreshTokenObject = user.RefreshTokens.Single(x => x.Value == refreshToken);
|
|
if (!refreshTokenObject.IsActive)
|
|
{
|
|
throw new RevokeRefreshTokenException("RefreshToken already revoked");
|
|
}
|
|
|
|
refreshTokenObject.RevokationDateTimeUtc = DateTime.UtcNow;
|
|
await _userManager.UpdateAsync(user);
|
|
}
|
|
|
|
private async Task<JwtSecurityToken> CreateJwtAsync(
|
|
ApplicationUser user,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var userClaims = await _userManager.GetClaimsAsync(user);
|
|
|
|
var roles = await _userManager.GetRolesAsync(user);
|
|
var roleClaims = new List<Claim>();
|
|
foreach (var role in roles)
|
|
{
|
|
roleClaims.Add(new Claim("roles", role));
|
|
}
|
|
|
|
var claims = new List<Claim>()
|
|
{
|
|
new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
|
|
new Claim(JwtRegisteredClaimNames.Email, user.Email)
|
|
}
|
|
.Union(userClaims)
|
|
.Union(roleClaims);
|
|
|
|
var jwtExpirationDateTimeUtc = DateTime.UtcNow.AddMinutes(Double.Parse(_configuration["Jwt:AccessTokenValidityInMinutes"]));
|
|
|
|
var symmetricSecurityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:IssuerSigningKey"]));
|
|
var signingCredentials = new SigningCredentials(symmetricSecurityKey, SecurityAlgorithms.HmacSha256);
|
|
|
|
var jwtSecurityToken = new JwtSecurityToken(
|
|
issuer: _configuration["Jwt:Issuer"],
|
|
audience: _configuration["Jwt:Audience"],
|
|
claims: claims,
|
|
expires: jwtExpirationDateTimeUtc,
|
|
signingCredentials: signingCredentials);
|
|
|
|
return jwtSecurityToken;
|
|
}
|
|
|
|
private RefreshToken CreateRefreshToken()
|
|
{
|
|
var randomNumber = new byte[32];
|
|
|
|
using var rng = RandomNumberGenerator.Create();
|
|
rng.GetNonZeroBytes(randomNumber);
|
|
|
|
return new RefreshToken
|
|
{
|
|
Value = Convert.ToBase64String(randomNumber),
|
|
CreationDateTimeUtc = DateTime.UtcNow,
|
|
ExpirationDateTimeUtc = DateTime.UtcNow.AddDays(Double.Parse(_configuration["Jwt:RefreshTokenValidityInDays"]))
|
|
};
|
|
}
|
|
}
|