2024-07-04 11:02:45 +05:00
|
|
|
|
using AsbCloudApp.Data.User;
|
2023-09-28 17:09:44 +05:00
|
|
|
|
using AsbCloudApp.Exceptions;
|
2022-10-27 11:22:39 +05:00
|
|
|
|
using AsbCloudApp.Repositories;
|
2021-04-02 17:28:07 +05:00
|
|
|
|
using AsbCloudApp.Services;
|
|
|
|
|
using AsbCloudDb.Model;
|
2022-04-11 18:00:34 +05:00
|
|
|
|
using Mapster;
|
2021-08-24 10:59:10 +05:00
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
2021-04-02 17:28:07 +05:00
|
|
|
|
using Microsoft.IdentityModel.Tokens;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2022-04-11 18:00:34 +05:00
|
|
|
|
using System.IdentityModel.Tokens.Jwt;
|
2021-04-02 17:28:07 +05:00
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Security.Claims;
|
|
|
|
|
using System.Security.Cryptography;
|
|
|
|
|
using System.Text;
|
2021-08-11 16:54:42 +05:00
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
2021-04-02 17:28:07 +05:00
|
|
|
|
|
2023-04-18 16:22:53 +05:00
|
|
|
|
|
2024-08-19 10:01:07 +05:00
|
|
|
|
namespace AsbCloudInfrastructure.Services;
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
|
public class AuthService : IAuthService
|
2021-04-02 17:28:07 +05:00
|
|
|
|
{
|
2024-08-19 10:01:07 +05:00
|
|
|
|
private readonly IAsbCloudDbContext db;
|
|
|
|
|
private readonly IUserRepository userRepository;
|
|
|
|
|
|
|
|
|
|
public const string issuer = "a";
|
|
|
|
|
public const string audience = "a";
|
|
|
|
|
public static readonly SymmetricSecurityKey securityKey = new(Encoding.ASCII.GetBytes("супер секретный ключ для шифрования"));
|
|
|
|
|
private const string algorithms = SecurityAlgorithms.HmacSha256;
|
|
|
|
|
|
|
|
|
|
private static readonly TimeSpan expiresTimespan = TimeSpan.FromDays(365.25);
|
|
|
|
|
private static readonly Encoding encoding = Encoding.UTF8;
|
|
|
|
|
private const int PasswordSaltLength = 5;
|
|
|
|
|
private const string claimIdUser = "id";
|
|
|
|
|
private const string claimNameIdCompany = "idCompany";
|
|
|
|
|
private readonly HashAlgorithm hashAlgorithm;
|
|
|
|
|
private readonly Random rnd;
|
|
|
|
|
|
|
|
|
|
public AuthService(IAsbCloudDbContext db, IUserRepository userRepository)
|
|
|
|
|
{
|
|
|
|
|
this.db = db;
|
|
|
|
|
this.userRepository = userRepository;
|
|
|
|
|
hashAlgorithm = SHA384.Create();
|
|
|
|
|
rnd = new Random((int)(DateTime.Now.Ticks % 2147480161));
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-24 10:36:53 +05:00
|
|
|
|
/// <inheritdoc/>
|
2024-08-19 10:01:07 +05:00
|
|
|
|
public async Task<UserTokenDto?> LoginAsync(string login, string password,
|
|
|
|
|
CancellationToken token)
|
2021-04-02 17:28:07 +05:00
|
|
|
|
{
|
2024-08-19 10:01:07 +05:00
|
|
|
|
var user = await GetUserByLoginAsync(login, token);
|
|
|
|
|
if (user is null)
|
|
|
|
|
return null;
|
2021-04-02 17:28:07 +05:00
|
|
|
|
|
2024-08-19 10:01:07 +05:00
|
|
|
|
if (!CheckPassword(user.PasswordHash, password))
|
|
|
|
|
return null;
|
2021-11-29 17:34:53 +05:00
|
|
|
|
|
2024-08-19 10:01:07 +05:00
|
|
|
|
return await MakeUserTokenDto(user, token);
|
|
|
|
|
}
|
2021-04-02 17:28:07 +05:00
|
|
|
|
|
2024-08-19 10:01:07 +05:00
|
|
|
|
/// <inheritdoc/>
|
|
|
|
|
public async Task<UserTokenDto?> RefreshAsync(ClaimsPrincipal identity,
|
|
|
|
|
CancellationToken token)
|
|
|
|
|
{
|
|
|
|
|
var login = identity.FindFirst(ClaimsIdentity.DefaultNameClaimType)?.Value;
|
|
|
|
|
if (string.IsNullOrEmpty(login))
|
|
|
|
|
return null;
|
2022-10-24 10:36:53 +05:00
|
|
|
|
|
2024-08-19 10:01:07 +05:00
|
|
|
|
var user = await GetUserByLoginAsync(login, token);
|
|
|
|
|
if (user is null)
|
|
|
|
|
return null;
|
2022-10-24 10:36:53 +05:00
|
|
|
|
|
2024-08-19 10:01:07 +05:00
|
|
|
|
var dto = await MakeUserTokenDto(user, token);
|
|
|
|
|
return dto;
|
|
|
|
|
}
|
2021-04-02 17:28:07 +05:00
|
|
|
|
|
2024-08-19 10:01:07 +05:00
|
|
|
|
/// <inheritdoc/>
|
|
|
|
|
public void Register(UserRegistrationDto userDto)
|
|
|
|
|
{
|
|
|
|
|
var user = db.Users.FirstOrDefault(u => u.Login == userDto.Login)
|
|
|
|
|
?? throw new ArgumentInvalidException(nameof(userDto.Login), "Логин уже занят");
|
|
|
|
|
|
|
|
|
|
var salt = GenerateSalt();
|
|
|
|
|
|
|
|
|
|
var newUser = new User
|
|
|
|
|
{
|
|
|
|
|
IdCompany = userDto.IdCompany,
|
|
|
|
|
IdState = 0,
|
|
|
|
|
Name = userDto.Name,
|
|
|
|
|
Surname = userDto.Surname,
|
|
|
|
|
Patronymic = userDto.Patronymic,
|
|
|
|
|
Email = userDto.Email,
|
|
|
|
|
Phone = userDto.Phone,
|
|
|
|
|
Position = userDto.Position,
|
|
|
|
|
Login = userDto.Login,
|
|
|
|
|
PasswordHash = salt + ComputeHash(salt, userDto.Password),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
db.Users.Add(newUser);
|
|
|
|
|
db.SaveChanges();
|
|
|
|
|
db.RelationUserUserRoles.Add(new RelationUserUserRole
|
2021-04-02 17:28:07 +05:00
|
|
|
|
{
|
2024-08-19 10:01:07 +05:00
|
|
|
|
IdUser = newUser.Id,
|
|
|
|
|
IdUserRole = 2
|
|
|
|
|
});
|
|
|
|
|
db.SaveChanges();
|
|
|
|
|
}
|
2021-10-25 12:04:34 +05:00
|
|
|
|
|
2024-08-19 10:01:07 +05:00
|
|
|
|
/// <inheritdoc/>
|
|
|
|
|
public void ChangePassword(string userLogin, string newPassword)
|
|
|
|
|
{
|
|
|
|
|
var user = db.Users.FirstOrDefault(u => u.Login == userLogin)
|
|
|
|
|
?? throw new ArgumentInvalidException(nameof(userLogin), "Логин не зарегистрирован");
|
2021-04-02 17:28:07 +05:00
|
|
|
|
|
2024-08-19 10:01:07 +05:00
|
|
|
|
var salt = GenerateSalt();
|
|
|
|
|
user.PasswordHash = salt + ComputeHash(salt, newPassword);
|
|
|
|
|
db.SaveChanges();
|
|
|
|
|
}
|
2021-04-02 17:28:07 +05:00
|
|
|
|
|
2024-08-19 10:01:07 +05:00
|
|
|
|
/// <inheritdoc/>
|
|
|
|
|
public void ChangePassword(int idUser, string newPassword)
|
|
|
|
|
{
|
|
|
|
|
var user = db.Users.FirstOrDefault(u => u.Id == idUser)
|
|
|
|
|
?? throw new ArgumentInvalidException(nameof(idUser), $"Пользователь с idUser:{idUser} не зарегистрирован");
|
2021-04-02 17:28:07 +05:00
|
|
|
|
|
2024-08-19 10:01:07 +05:00
|
|
|
|
var salt = GenerateSalt();
|
|
|
|
|
user.PasswordHash = salt + ComputeHash(salt, newPassword);
|
|
|
|
|
db.SaveChanges();
|
|
|
|
|
}
|
2021-04-02 17:28:07 +05:00
|
|
|
|
|
2024-08-19 10:01:07 +05:00
|
|
|
|
private async Task<UserTokenDto?> MakeUserTokenDto(User user, CancellationToken token)
|
|
|
|
|
{
|
|
|
|
|
var identity = MakeClaims(user);
|
|
|
|
|
if (identity is null || user.IdState == 0)
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
var userDto = await userRepository.GetOrDefaultAsync(user.Id, token);
|
|
|
|
|
if (userDto is null)
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
var dto = userDto.Adapt<UserTokenDto>();
|
|
|
|
|
dto.Permissions = userRepository.GetNestedPermissions(userDto.Id);
|
|
|
|
|
dto.Token = MakeToken(identity.Claims);
|
|
|
|
|
return dto;
|
|
|
|
|
}
|
2021-04-02 17:28:07 +05:00
|
|
|
|
|
2024-08-19 10:01:07 +05:00
|
|
|
|
private static string MakeToken(IEnumerable<Claim> claims)
|
|
|
|
|
{
|
|
|
|
|
var now = DateTime.Now;
|
2021-04-02 17:28:07 +05:00
|
|
|
|
|
2024-08-19 10:01:07 +05:00
|
|
|
|
var jwt = new JwtSecurityToken(
|
|
|
|
|
issuer,
|
|
|
|
|
audience,
|
|
|
|
|
notBefore: now,
|
|
|
|
|
claims: claims,
|
|
|
|
|
expires: now.Add(expiresTimespan),
|
|
|
|
|
signingCredentials: new SigningCredentials(securityKey, algorithms));
|
2022-10-24 10:36:53 +05:00
|
|
|
|
|
2024-08-19 10:01:07 +05:00
|
|
|
|
return new JwtSecurityTokenHandler().WriteToken(jwt);
|
|
|
|
|
}
|
2022-10-24 10:36:53 +05:00
|
|
|
|
|
2024-08-19 10:01:07 +05:00
|
|
|
|
private async Task<User?> GetUserByLoginAsync(string login, CancellationToken token = default)
|
|
|
|
|
{
|
|
|
|
|
var user = await db.Users
|
|
|
|
|
.Include(e => e.Company)
|
|
|
|
|
.Where(e => e.Login == login)
|
|
|
|
|
.AsNoTracking()
|
|
|
|
|
.FirstOrDefaultAsync(token)
|
|
|
|
|
.ConfigureAwait(false);
|
|
|
|
|
return user;
|
|
|
|
|
}
|
2022-10-24 10:36:53 +05:00
|
|
|
|
|
2024-08-19 10:01:07 +05:00
|
|
|
|
private ClaimsIdentity MakeClaims(User user)
|
|
|
|
|
{
|
|
|
|
|
var claims = new List<Claim>
|
|
|
|
|
{
|
|
|
|
|
new (claimIdUser, user.Id.ToString()),
|
|
|
|
|
new (ClaimsIdentity.DefaultNameClaimType, user.Login),
|
|
|
|
|
new (claimNameIdCompany, user.IdCompany.ToString()),
|
|
|
|
|
};
|
|
|
|
|
var roles = userRepository.GetRolesByIdUser(user.Id);
|
|
|
|
|
if (roles is not null)
|
|
|
|
|
foreach (var role in roles)
|
|
|
|
|
claims.Add(new Claim(ClaimsIdentity.DefaultRoleClaimType, role.Caption));
|
2021-04-02 17:28:07 +05:00
|
|
|
|
|
2024-08-19 10:01:07 +05:00
|
|
|
|
var claimsIdentity = new ClaimsIdentity(claims, "Token", ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType);
|
|
|
|
|
return claimsIdentity;
|
|
|
|
|
}
|
2021-04-02 17:28:07 +05:00
|
|
|
|
|
2024-08-19 10:01:07 +05:00
|
|
|
|
private bool CheckPassword(string passwordHash, string password)
|
|
|
|
|
{
|
|
|
|
|
if (passwordHash?.Length == 0 && password.Length == 0)
|
|
|
|
|
return true;
|
2021-04-02 17:28:07 +05:00
|
|
|
|
|
2024-08-19 10:01:07 +05:00
|
|
|
|
if (passwordHash?.Length < PasswordSaltLength)
|
|
|
|
|
return false;
|
2021-04-02 17:28:07 +05:00
|
|
|
|
|
2024-08-19 10:01:07 +05:00
|
|
|
|
if (passwordHash is null)
|
|
|
|
|
return false;
|
2022-07-19 10:29:38 +05:00
|
|
|
|
|
2024-08-19 10:01:07 +05:00
|
|
|
|
var salt = passwordHash[0..PasswordSaltLength];
|
|
|
|
|
var hashDb = passwordHash[PasswordSaltLength..];
|
2021-04-02 17:28:07 +05:00
|
|
|
|
|
2024-08-19 10:01:07 +05:00
|
|
|
|
return hashDb == ComputeHash(salt, password);
|
|
|
|
|
}
|
2021-04-02 17:28:07 +05:00
|
|
|
|
|
2024-08-19 10:01:07 +05:00
|
|
|
|
private string ComputeHash(string salt, string password)
|
|
|
|
|
{
|
|
|
|
|
var hashBytes = hashAlgorithm.ComputeHash(encoding.GetBytes(salt + password));
|
|
|
|
|
var hashString = BitConverter.ToString(hashBytes)
|
|
|
|
|
.Replace("-", "")
|
|
|
|
|
.ToLower();
|
|
|
|
|
return hashString;
|
|
|
|
|
}
|
2021-04-02 17:28:07 +05:00
|
|
|
|
|
2024-08-19 10:01:07 +05:00
|
|
|
|
private string GenerateSalt()
|
|
|
|
|
{
|
|
|
|
|
const string saltChars = "sHwiaX7kZT1QRp0cPILGUuK2Sz=9q8lmejDNfoYCE3B_WtgyVv6M5OxAJ4Frbhnd";
|
|
|
|
|
var salt = "";
|
|
|
|
|
for (var i = 0; i < PasswordSaltLength - 1; i++)
|
|
|
|
|
salt += saltChars[rnd.Next(0, saltChars.Length)];
|
|
|
|
|
salt += "|";
|
|
|
|
|
return salt;
|
2021-04-02 17:28:07 +05:00
|
|
|
|
}
|
|
|
|
|
}
|