DD.WellWorkover.Cloud/AsbCloudInfrastructure/Services/AuthService.cs
2024-08-19 10:01:07 +05:00

228 lines
7.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using AsbCloudApp.Data.User;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using Mapster;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services;
/// <inheritdoc/>
public class AuthService : IAuthService
{
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));
}
/// <inheritdoc/>
public async Task<UserTokenDto?> LoginAsync(string login, string password,
CancellationToken token)
{
var user = await GetUserByLoginAsync(login, token);
if (user is null)
return null;
if (!CheckPassword(user.PasswordHash, password))
return null;
return await MakeUserTokenDto(user, token);
}
/// <inheritdoc/>
public async Task<UserTokenDto?> RefreshAsync(ClaimsPrincipal identity,
CancellationToken token)
{
var login = identity.FindFirst(ClaimsIdentity.DefaultNameClaimType)?.Value;
if (string.IsNullOrEmpty(login))
return null;
var user = await GetUserByLoginAsync(login, token);
if (user is null)
return null;
var dto = await MakeUserTokenDto(user, token);
return dto;
}
/// <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
{
IdUser = newUser.Id,
IdUserRole = 2
});
db.SaveChanges();
}
/// <inheritdoc/>
public void ChangePassword(string userLogin, string newPassword)
{
var user = db.Users.FirstOrDefault(u => u.Login == userLogin)
?? throw new ArgumentInvalidException(nameof(userLogin), "Логин не зарегистрирован");
var salt = GenerateSalt();
user.PasswordHash = salt + ComputeHash(salt, newPassword);
db.SaveChanges();
}
/// <inheritdoc/>
public void ChangePassword(int idUser, string newPassword)
{
var user = db.Users.FirstOrDefault(u => u.Id == idUser)
?? throw new ArgumentInvalidException(nameof(idUser), $"Пользователь с idUser:{idUser} не зарегистрирован");
var salt = GenerateSalt();
user.PasswordHash = salt + ComputeHash(salt, newPassword);
db.SaveChanges();
}
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;
}
private static string MakeToken(IEnumerable<Claim> claims)
{
var now = DateTime.Now;
var jwt = new JwtSecurityToken(
issuer,
audience,
notBefore: now,
claims: claims,
expires: now.Add(expiresTimespan),
signingCredentials: new SigningCredentials(securityKey, algorithms));
return new JwtSecurityTokenHandler().WriteToken(jwt);
}
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;
}
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));
var claimsIdentity = new ClaimsIdentity(claims, "Token", ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType);
return claimsIdentity;
}
private bool CheckPassword(string passwordHash, string password)
{
if (passwordHash?.Length == 0 && password.Length == 0)
return true;
if (passwordHash?.Length < PasswordSaltLength)
return false;
if (passwordHash is null)
return false;
var salt = passwordHash[0..PasswordSaltLength];
var hashDb = passwordHash[PasswordSaltLength..];
return hashDb == ComputeHash(salt, password);
}
private string ComputeHash(string salt, string password)
{
var hashBytes = hashAlgorithm.ComputeHash(encoding.GetBytes(salt + password));
var hashString = BitConverter.ToString(hashBytes)
.Replace("-", "")
.ToLower();
return hashString;
}
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;
}
}