using AsbCloudApp.Data.User; 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 { /// 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)); } /// public async Task 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); } /// public async Task 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; } /// public int Register(UserRegistrationDto userDto) { if (userDto.Login is null || userDto.Login.Length is < 3 or > 50) return -1; if (userDto.Password is null || userDto.Password.Length is < 3 or > 50) return -2; if (userDto.Email.Length > 255) return -3; if (userDto.Phone?.Length > 50) return -4; if (userDto.Position?.Length > 255) return -5; var user = db.Users.FirstOrDefault(u => u.Login == userDto.Login); if (user is not null) return -6; 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); try { db.SaveChanges(); db.RelationUserUserRoles.Add(new RelationUserUserRole { IdUser = newUser.Id, IdUserRole = 2 }); db.SaveChanges(); } catch { return -7; } return 0; } /// public int ChangePassword(string userLogin, string newPassword) { var user = db.Users.AsNoTracking().FirstOrDefault(u => u.Login == userLogin); if (user == null) return -1; var salt = GenerateSalt(); user.PasswordHash = salt + ComputeHash(salt, newPassword); db.SaveChanges(); return 0; } /// public int ChangePassword(int idUser, string newPassword) { var user = db.Users.FirstOrDefault(u => u.Id == idUser); if (user == null) return -1; var salt = GenerateSalt(); user.PasswordHash = salt + ComputeHash(salt, newPassword); db.SaveChanges(); return 0; } private async Task 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(); dto.Permissions = userRepository.GetNestedPermissions(userDto.Id); dto.Token = MakeToken(identity.Claims); return dto; } private static string MakeToken(IEnumerable 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 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 { 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; } } }