141 lines
4.9 KiB
C#
141 lines
4.9 KiB
C#
using System.IdentityModel.Tokens.Jwt;
|
|
using System.Security.Claims;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
|
using cuqmbr.TravelGuide.Domain.Entities;
|
|
using MediatR;
|
|
using Microsoft.Extensions.Options;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
|
|
namespace cuqmbr.TravelGuide.Application.Authenticaion.Queries.Login;
|
|
|
|
public class LoginQueryHandler : IRequestHandler<LoginQuery, TokensModel>
|
|
{
|
|
private readonly UnitOfWork _unitOfWork;
|
|
private readonly PasswordHasherService _passwordHasher;
|
|
private readonly JsonWebTokenConfigurationOptions _jwtConfiguration;
|
|
|
|
public LoginQueryHandler(UnitOfWork unitOfWork,
|
|
PasswordHasherService passwordHasher,
|
|
IOptions<ConfigurationOptions> configurationOptions)
|
|
{
|
|
_unitOfWork = unitOfWork;
|
|
_passwordHasher = passwordHasher;
|
|
_jwtConfiguration = configurationOptions.Value.JsonWebToken;
|
|
}
|
|
|
|
public async Task<TokensModel> Handle(
|
|
LoginQuery request, CancellationToken cancellationToken)
|
|
{
|
|
var account = await _unitOfWork.AccountRepository
|
|
.GetOneAsync(
|
|
a =>
|
|
a.Email == request.EmailOrUsername ||
|
|
a.Username == request.EmailOrUsername,
|
|
a => a.AccountRoles, cancellationToken);
|
|
|
|
if (account == null)
|
|
{
|
|
throw new LoginException("No users registered with given email.");
|
|
}
|
|
|
|
var hash = Convert.FromBase64String(account.PasswordHash);
|
|
var salt = Convert.FromBase64String(account.PasswordSalt);
|
|
var password = Encoding.UTF8.GetBytes(request.Password);
|
|
|
|
var isValidPassword = await _passwordHasher
|
|
.IsValidHashAsync(hash, password, salt, cancellationToken);
|
|
|
|
if (!isValidPassword)
|
|
{
|
|
throw new LoginException("Given password is incorrect.");
|
|
}
|
|
|
|
var jwtSecurityToken = await CreateJwtAsync(account, cancellationToken);
|
|
var accessToken = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
|
|
|
|
var refreshToken = (await _unitOfWork.RefreshTokenRepository
|
|
.GetPageAsync(
|
|
e =>
|
|
e.AccountId == account.Id &&
|
|
e.RevocationTime == null &&
|
|
e.ExpirationTime > DateTimeOffset.UtcNow,
|
|
1, int.MaxValue, cancellationToken))
|
|
.Items.FirstOrDefault();
|
|
|
|
if (refreshToken == null)
|
|
{
|
|
refreshToken = CreateRefreshToken();
|
|
refreshToken.AccountId = account.Id;
|
|
|
|
await _unitOfWork.RefreshTokenRepository
|
|
.AddOneAsync(refreshToken, cancellationToken);
|
|
}
|
|
|
|
await _unitOfWork.SaveAsync(cancellationToken);
|
|
_unitOfWork.Dispose();
|
|
|
|
return new TokensModel(accessToken, refreshToken.Value);
|
|
}
|
|
|
|
private async Task<JwtSecurityToken> CreateJwtAsync(
|
|
Account account, CancellationToken cancellationToken)
|
|
{
|
|
var roleIds = account.AccountRoles.Select(ar => ar.RoleId);
|
|
|
|
var roles = (await _unitOfWork.RoleRepository
|
|
.GetPageAsync(
|
|
r => roleIds.Contains(r.Id),
|
|
1, roleIds.Count(), cancellationToken))
|
|
.Items.Select(r => r.Value);
|
|
|
|
var roleClaims = new List<Claim>();
|
|
foreach (var role in roles)
|
|
{
|
|
roleClaims.Add(new Claim("roles", role.Name));
|
|
}
|
|
|
|
var claims = new List<Claim>()
|
|
{
|
|
new Claim(JwtRegisteredClaimNames.Sub, account.Guid.ToString()),
|
|
new Claim(JwtRegisteredClaimNames.Nickname, account.Username),
|
|
new Claim(JwtRegisteredClaimNames.Email, account.Email)
|
|
}
|
|
.Union(roleClaims);
|
|
|
|
var expirationDateTimeUtc = DateTime.UtcNow.Add(
|
|
_jwtConfiguration.AccessTokenValidity);
|
|
|
|
var symmetricSecurityKey = new SymmetricSecurityKey(
|
|
Encoding.UTF8.GetBytes(_jwtConfiguration.IssuerSigningKey));
|
|
var signingCredentials = new SigningCredentials(
|
|
symmetricSecurityKey, SecurityAlgorithms.HmacSha256);
|
|
|
|
var jwtSecurityToken = new JwtSecurityToken(
|
|
issuer: _jwtConfiguration.Issuer,
|
|
audience: _jwtConfiguration.Audience,
|
|
claims: claims,
|
|
expires: expirationDateTimeUtc,
|
|
signingCredentials: signingCredentials);
|
|
|
|
return jwtSecurityToken;
|
|
}
|
|
|
|
private RefreshToken CreateRefreshToken()
|
|
{
|
|
var token = RandomNumberGenerator.GetBytes(128 / 8);
|
|
|
|
return new RefreshToken
|
|
{
|
|
Guid = Guid.NewGuid(),
|
|
Value = Convert.ToBase64String(token),
|
|
CreationTime = DateTimeOffset.UtcNow,
|
|
ExpirationTime = DateTimeOffset.UtcNow.Add(
|
|
_jwtConfiguration.RefreshTokenValidity)
|
|
};
|
|
}
|
|
}
|