Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using WebNET.Source.Service.Interface;
- using WebNET.Source.DTO;
- using Microsoft.EntityFrameworkCore;
- using Microsoft.IdentityModel.Tokens;
- using System.IdentityModel.Tokens.Jwt;
- using System.Security.Claims;
- using System.Security.Cryptography;
- using System.Text.RegularExpressions;
- using WebNET.Source.Model;
- using System.Text;
- using WebNET.Data;
- namespace WebNET.Source.Service
- {
- public class DoctorService : IDoctorService
- {
- private readonly ApplicationDBContext _dbContext;
- public DoctorService(ApplicationDBContext dbContext)
- {
- _dbContext = dbContext;
- }
- public async Task<TokenDTO> Registration(DoctorDTO signupData)
- {
- AssertValidEmail(signupData.Email);
- AssertValidPhone(signupData.PhoneNumber);
- var specialtyExists = await CheckSpecialtyAvailability(signupData.SpecialityId);
- if (!specialtyExists)
- {
- throw new ArgumentException($"Специальность с данным Id не найдена.");
- }
- signupData.Email = FormatEmailForStorage(signupData.Email);
- await PreventDuplicateEmail(signupData.Email);
- ValidateUserDetails(signupData.Gender, signupData.BirthDate);
- var passwordHash = CreatePasswordHash(signupData.Password);
- DoctorModel newUser = BuildNewUser(signupData, passwordHash);
- _ = await _dbContext.Doctors.AddAsync(newUser);
- _ = await _dbContext.SaveChangesAsync();
- LoginDTO loginPayload = new()
- {
- Email = signupData.Email,
- Password = signupData.Password
- };
- return await Login(loginPayload);
- }
- public async Task<TokenDTO> Login(LoginDTO loginData)
- {
- DoctorModel? user = await _dbContext.Doctors.FirstOrDefaultAsync(u => u.Email == loginData.Email) ?? throw new UnauthorizedAccessException("User not found.");
- if (!VerifyPasswordHash(loginData.Password, user.Password))
- {
- Exception excep = new();
- excep.Data.Add(StatusCodes.Status400BadRequest.ToString(), "Invalid password!");
- throw new UnauthorizedAccessException("Invalid password.");
- }
- ClaimsIdentity claimsIdentity = GetClaimsIdentity(user);
- // Генерация токена JWT с использованием ClaimsIdentity
- var token = GenerateJwtToken(claimsIdentity);
- return new TokenDTO { Token = token };
- }
- private ClaimsIdentity GetClaimsIdentity(DoctorModel user)
- {
- List<Claim> claims = new()
- {
- new Claim(ClaimsIdentity.DefaultNameClaimType, user.Id.ToString())
- };
- return new ClaimsIdentity(claims, "Token", ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType);
- }
- private string GenerateJwtToken(ClaimsIdentity identity)
- {
- var now = DateTime.UtcNow;
- var jwtToken = new JwtSecurityToken(
- issuer: Token.Issuer,
- audience: Token.Audience,
- notBefore: now,
- claims: identity.Claims,
- expires: now.AddMinutes(Token.Lifetime),
- signingCredentials: new SigningCredentials(Token.GetSymmetricSecurityKey(), SecurityAlgorithms.HmacSha256)
- );
- return new JwtSecurityTokenHandler().WriteToken(jwtToken);
- }
- private bool VerifyPasswordHash(string password, string storedHash)
- {
- var hashBytes = Convert.FromBase64String(storedHash);
- var salt = new byte[16];
- Array.Copy(hashBytes, 0, salt, 0, 16);
- Rfc2898DeriveBytes pbkdf2 = new(password, salt, 100000);
- var hash = pbkdf2.GetBytes(20);
- for (var i = 0; i < 20; i++)
- {
- if (hashBytes[i + 16] != hash[i])
- {
- return false;
- }
- }
- return true;
- }
- private void ValidateUserDetails(string gender, DateTime? birthDate)
- {
- ValidateGender(gender);
- ValidateBirthDate(birthDate);
- }
- private DoctorModel BuildNewUser(DoctorDTO userData, string passwordHash)
- {
- return new DoctorModel
- {
- Id = Guid.NewGuid(),
- Name = userData.Name,
- Email = userData.Email,
- Password = passwordHash,
- PhoneNumber = userData.PhoneNumber,
- BirthDate = userData.BirthDate,
- Gender = userData.Gender,
- SpecialityId = userData.SpecialityId,
- CreateTime = DateTime.UtcNow
- };
- }
- private void ValidateGender(string gender)
- {
- List<string> validGenders = new() { "Male", "Female", "Other" };
- if (!validGenders.Contains(gender))
- {
- throw new ArgumentException($"Invalid gender. Valid options are: {string.Join(", ", validGenders)}.");
- }
- }
- private string CreatePasswordHash(string password)
- {
- if (string.IsNullOrWhiteSpace(password))
- {
- throw new ArgumentException("Password is required.");
- }
- byte[] salt;
- new RNGCryptoServiceProvider().GetBytes(salt = new byte[16]);
- Rfc2898DeriveBytes pbkdf2 = new(password, salt, 100000);
- var hash = pbkdf2.GetBytes(20);
- var hashBytes = new byte[36];
- Array.Copy(salt, 0, hashBytes, 0, 16);
- Array.Copy(hash, 0, hashBytes, 16, 20);
- return Convert.ToBase64String(hashBytes);
- }
- private void ValidateBirthDate(DateTime? birthDate)
- {
- if (!birthDate.HasValue)
- {
- throw new ArgumentException("Birth date is required.");
- }
- if (birthDate.Value > DateTime.Now)
- {
- throw new ArgumentException("Birth date cannot be in the future.");
- }
- var minimumAge = new DateTime(DateTime.Now.Subtract(birthDate.Value).Ticks).Year - 1;
- if (minimumAge < 18)
- {
- throw new ArgumentException("User must be at least 18 years old.");
- }
- }
- private void AssertValidEmail(string email)
- {
- if (email == null)
- {
- throw new ArgumentNullException(nameof(email), "Email cannot be null.");
- }
- if (!Regex.IsMatch(email, @"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$"))
- {
- throw new ArgumentException("Invalid email format.");
- }
- }
- private void AssertValidPhone(string phone)
- {
- if (!Regex.IsMatch(phone, @"^\+7\d{10}$"))
- {
- throw new ArgumentException("Invalid phone format. Use the format +7xxxxxxxxxx.");
- }
- }
- private async Task<bool> CheckSpecialtyAvailability(Guid specialtyId)
- {
- return await _dbContext.SpecialityModels.AnyAsync(s => s.Id == specialtyId);
- }
- private string FormatEmailForStorage(string email)
- {
- return email.ToLower().Trim();
- }
- private async Task PreventDuplicateEmail(string email)
- {
- var emailExists = await _dbContext.Doctors.AnyAsync(u => u.Email == email);
- if (emailExists)
- {
- throw new ArgumentException($"An account with email '{email}' already exists.", nameof(email));
- }
- }
- public async Task<ServiceResult> LogoutAsync(string token)
- {
- if (string.IsNullOrWhiteSpace(token))
- {
- return ServiceResult.ErrorResult("Token is required!", 400);
- }
- JwtSecurityTokenHandler handler = new();
- JwtSecurityToken jwtToken = handler.ReadJwtToken(token);
- var userId = jwtToken.Claims.First(claim => claim.Type == "sub").Value;
- var tokenAlreadyBlacklisted = await _dbContext.BLTokenModel.AnyAsync(bt => bt.Token == token);
- if (tokenAlreadyBlacklisted)
- {
- return ServiceResult.ErrorResult("This token has already been used to log out!", 400);
- }
- BLTokenModel blacklistedToken = new()
- {
- Token = token,
- UserId = Guid.Parse(userId),
- BlacklistedDate = DateTime.UtcNow
- };
- _ = await _dbContext.BLTokenModel.AddAsync(blacklistedToken);
- _ = await _dbContext.SaveChangesAsync();
- return ServiceResult.SuccessResult("Logged out successfully.");
- }
- public class ServiceResult
- {
- public bool Success { get; set; }
- public string Message { get; set; }
- public int StatusCode { get; set; }
- public static ServiceResult SuccessResult(string message)
- {
- return new ServiceResult { Success = true, Message = message, StatusCode = 200 };
- }
- public static ServiceResult ErrorResult(string message, int statusCode)
- {
- return new ServiceResult { Success = false, Message = message, StatusCode = statusCode };
- }
- }
- public async Task<GetProfileDoctorDTO> GetProfileAsync(Guid userId, string token)
- {
- var isTokenBlacklisted = await _dbContext.BLTokenModel.AnyAsync(bt => bt.Token == token);
- if (isTokenBlacklisted)
- {
- throw new InvalidOperationException("The user is logged out.");
- }
- DoctorModel? user = await _dbContext.Doctors.FindAsync(userId);
- return user == null
- ? throw new KeyNotFoundException("User not found")
- : new GetProfileDoctorDTO
- {
- Id = user.Id,
- Name = user.Name,
- Email = user.Email,
- BirthDate = user.BirthDate,
- PhoneNumber = user.PhoneNumber,
- Gender = user.Gender
- };
- }
- public async Task UpdateProfileAsync(Guid userId, UpdateProfileDTO updateProfileDTO, string token)
- {
- var isTokenBlacklisted = await _dbContext.BLTokenModel.AnyAsync(bt => bt.Token == token);
- if (isTokenBlacklisted)
- {
- throw new InvalidOperationException("The user is logged out.");
- }
- AssertValidEmail(updateProfileDTO.Email);
- AssertValidPhone(updateProfileDTO.Phone);
- ValidateBirthDate(updateProfileDTO.Birthday);
- ValidateGender(updateProfileDTO.Gender);
- DoctorModel? user = await _dbContext.Doctors.FindAsync(userId) ?? throw new KeyNotFoundException("User not found");
- user.Email = updateProfileDTO.Email;
- user.Name = updateProfileDTO.Name;
- user.BirthDate = updateProfileDTO.Birthday;
- user.Gender = updateProfileDTO.Gender;
- user.PhoneNumber = updateProfileDTO.Phone;
- _ = _dbContext.Doctors.Update(user);
- _ = await _dbContext.SaveChangesAsync();
- }
- }
- }
Add Comment
Please, Sign In to add comment