autobus-api_old/AutobusApi.Infrastructure/Services/IdentityService.cs

177 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<int> 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());
return (await _userManager.FindByEmailAsync(email)).Id;
}
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"]))
};
}
}