Tark_Wight

DS

Jan 7th, 2024
107
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.56 KB | None | 0 0
  1.  
  2. using WebNET.Source.Service.Interface;
  3. using WebNET.Source.DTO;
  4. using Microsoft.EntityFrameworkCore;
  5. using Microsoft.IdentityModel.Tokens;
  6. using System.IdentityModel.Tokens.Jwt;
  7. using System.Security.Claims;
  8. using System.Security.Cryptography;
  9. using System.Text.RegularExpressions;
  10. using WebNET.Source.Model;
  11. using System.Text;
  12. using WebNET.Data;
  13.  
  14.  
  15. namespace WebNET.Source.Service
  16. {
  17.  
  18. public class DoctorService : IDoctorService
  19. {
  20.  
  21. private readonly ApplicationDBContext _dbContext;
  22.  
  23. public DoctorService(ApplicationDBContext dbContext)
  24. {
  25. _dbContext = dbContext;
  26. }
  27.  
  28. public async Task<TokenDTO> Registration(DoctorDTO signupData)
  29. {
  30. AssertValidEmail(signupData.Email);
  31. AssertValidPhone(signupData.PhoneNumber);
  32.  
  33. var specialtyExists = await CheckSpecialtyAvailability(signupData.SpecialityId);
  34. if (!specialtyExists)
  35. {
  36. throw new ArgumentException($"Специальность с данным Id не найдена.");
  37. }
  38.  
  39. signupData.Email = FormatEmailForStorage(signupData.Email);
  40. await PreventDuplicateEmail(signupData.Email);
  41. ValidateUserDetails(signupData.Gender, signupData.BirthDate);
  42.  
  43. var passwordHash = CreatePasswordHash(signupData.Password);
  44. DoctorModel newUser = BuildNewUser(signupData, passwordHash);
  45.  
  46. _ = await _dbContext.Doctors.AddAsync(newUser);
  47. _ = await _dbContext.SaveChangesAsync();
  48.  
  49. LoginDTO loginPayload = new()
  50. {
  51. Email = signupData.Email,
  52. Password = signupData.Password
  53. };
  54.  
  55. return await Login(loginPayload);
  56. }
  57.  
  58. public async Task<TokenDTO> Login(LoginDTO loginData)
  59. {
  60. DoctorModel? user = await _dbContext.Doctors.FirstOrDefaultAsync(u => u.Email == loginData.Email) ?? throw new UnauthorizedAccessException("User not found.");
  61. if (!VerifyPasswordHash(loginData.Password, user.Password))
  62. {
  63. Exception excep = new();
  64. excep.Data.Add(StatusCodes.Status400BadRequest.ToString(), "Invalid password!");
  65. throw new UnauthorizedAccessException("Invalid password.");
  66. }
  67.  
  68. ClaimsIdentity claimsIdentity = GetClaimsIdentity(user);
  69.  
  70. // Генерация токена JWT с использованием ClaimsIdentity
  71. var token = GenerateJwtToken(claimsIdentity);
  72. return new TokenDTO { Token = token };
  73. }
  74.  
  75. private ClaimsIdentity GetClaimsIdentity(DoctorModel user)
  76. {
  77. List<Claim> claims = new()
  78. {
  79. new Claim(ClaimsIdentity.DefaultNameClaimType, user.Id.ToString())
  80. };
  81.  
  82. return new ClaimsIdentity(claims, "Token", ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType);
  83. }
  84.  
  85. private string GenerateJwtToken(ClaimsIdentity identity)
  86. {
  87. var now = DateTime.UtcNow;
  88. var jwtToken = new JwtSecurityToken(
  89. issuer: Token.Issuer,
  90. audience: Token.Audience,
  91. notBefore: now,
  92. claims: identity.Claims,
  93. expires: now.AddMinutes(Token.Lifetime),
  94. signingCredentials: new SigningCredentials(Token.GetSymmetricSecurityKey(), SecurityAlgorithms.HmacSha256)
  95. );
  96.  
  97. return new JwtSecurityTokenHandler().WriteToken(jwtToken);
  98. }
  99.  
  100.  
  101.  
  102.  
  103. private bool VerifyPasswordHash(string password, string storedHash)
  104. {
  105. var hashBytes = Convert.FromBase64String(storedHash);
  106. var salt = new byte[16];
  107. Array.Copy(hashBytes, 0, salt, 0, 16);
  108.  
  109. Rfc2898DeriveBytes pbkdf2 = new(password, salt, 100000);
  110. var hash = pbkdf2.GetBytes(20);
  111.  
  112.  
  113. for (var i = 0; i < 20; i++)
  114. {
  115. if (hashBytes[i + 16] != hash[i])
  116. {
  117. return false;
  118. }
  119. }
  120. return true;
  121. }
  122.  
  123. private void ValidateUserDetails(string gender, DateTime? birthDate)
  124. {
  125. ValidateGender(gender);
  126. ValidateBirthDate(birthDate);
  127. }
  128.  
  129. private DoctorModel BuildNewUser(DoctorDTO userData, string passwordHash)
  130. {
  131. return new DoctorModel
  132. {
  133. Id = Guid.NewGuid(),
  134. Name = userData.Name,
  135. Email = userData.Email,
  136. Password = passwordHash,
  137. PhoneNumber = userData.PhoneNumber,
  138. BirthDate = userData.BirthDate,
  139. Gender = userData.Gender,
  140. SpecialityId = userData.SpecialityId,
  141. CreateTime = DateTime.UtcNow
  142. };
  143. }
  144.  
  145.  
  146. private void ValidateGender(string gender)
  147. {
  148. List<string> validGenders = new() { "Male", "Female", "Other" };
  149. if (!validGenders.Contains(gender))
  150. {
  151. throw new ArgumentException($"Invalid gender. Valid options are: {string.Join(", ", validGenders)}.");
  152. }
  153. }
  154.  
  155. private string CreatePasswordHash(string password)
  156. {
  157. if (string.IsNullOrWhiteSpace(password))
  158. {
  159. throw new ArgumentException("Password is required.");
  160. }
  161.  
  162. byte[] salt;
  163. new RNGCryptoServiceProvider().GetBytes(salt = new byte[16]);
  164.  
  165. Rfc2898DeriveBytes pbkdf2 = new(password, salt, 100000);
  166. var hash = pbkdf2.GetBytes(20);
  167.  
  168. var hashBytes = new byte[36];
  169. Array.Copy(salt, 0, hashBytes, 0, 16);
  170. Array.Copy(hash, 0, hashBytes, 16, 20);
  171.  
  172. return Convert.ToBase64String(hashBytes);
  173. }
  174.  
  175. private void ValidateBirthDate(DateTime? birthDate)
  176. {
  177. if (!birthDate.HasValue)
  178. {
  179. throw new ArgumentException("Birth date is required.");
  180. }
  181.  
  182. if (birthDate.Value > DateTime.Now)
  183. {
  184. throw new ArgumentException("Birth date cannot be in the future.");
  185. }
  186.  
  187. var minimumAge = new DateTime(DateTime.Now.Subtract(birthDate.Value).Ticks).Year - 1;
  188. if (minimumAge < 18)
  189. {
  190. throw new ArgumentException("User must be at least 18 years old.");
  191. }
  192. }
  193.  
  194. private void AssertValidEmail(string email)
  195. {
  196. if (email == null)
  197. {
  198. throw new ArgumentNullException(nameof(email), "Email cannot be null.");
  199. }
  200.  
  201. if (!Regex.IsMatch(email, @"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$"))
  202. {
  203. throw new ArgumentException("Invalid email format.");
  204. }
  205. }
  206.  
  207. private void AssertValidPhone(string phone)
  208. {
  209. if (!Regex.IsMatch(phone, @"^\+7\d{10}$"))
  210. {
  211. throw new ArgumentException("Invalid phone format. Use the format +7xxxxxxxxxx.");
  212. }
  213. }
  214.  
  215. private async Task<bool> CheckSpecialtyAvailability(Guid specialtyId)
  216. {
  217. return await _dbContext.SpecialityModels.AnyAsync(s => s.Id == specialtyId);
  218. }
  219.  
  220. private string FormatEmailForStorage(string email)
  221. {
  222. return email.ToLower().Trim();
  223. }
  224.  
  225.  
  226. private async Task PreventDuplicateEmail(string email)
  227. {
  228. var emailExists = await _dbContext.Doctors.AnyAsync(u => u.Email == email);
  229.  
  230. if (emailExists)
  231. {
  232. throw new ArgumentException($"An account with email '{email}' already exists.", nameof(email));
  233. }
  234. }
  235.  
  236.  
  237. public async Task<ServiceResult> LogoutAsync(string token)
  238. {
  239. if (string.IsNullOrWhiteSpace(token))
  240. {
  241. return ServiceResult.ErrorResult("Token is required!", 400);
  242. }
  243.  
  244. JwtSecurityTokenHandler handler = new();
  245. JwtSecurityToken jwtToken = handler.ReadJwtToken(token);
  246. var userId = jwtToken.Claims.First(claim => claim.Type == "sub").Value;
  247.  
  248. var tokenAlreadyBlacklisted = await _dbContext.BLTokenModel.AnyAsync(bt => bt.Token == token);
  249. if (tokenAlreadyBlacklisted)
  250. {
  251. return ServiceResult.ErrorResult("This token has already been used to log out!", 400);
  252. }
  253.  
  254. BLTokenModel blacklistedToken = new()
  255. {
  256. Token = token,
  257. UserId = Guid.Parse(userId),
  258. BlacklistedDate = DateTime.UtcNow
  259. };
  260. _ = await _dbContext.BLTokenModel.AddAsync(blacklistedToken);
  261. _ = await _dbContext.SaveChangesAsync();
  262.  
  263. return ServiceResult.SuccessResult("Logged out successfully.");
  264. }
  265.  
  266. public class ServiceResult
  267. {
  268. public bool Success { get; set; }
  269. public string Message { get; set; }
  270. public int StatusCode { get; set; }
  271.  
  272. public static ServiceResult SuccessResult(string message)
  273. {
  274. return new ServiceResult { Success = true, Message = message, StatusCode = 200 };
  275. }
  276.  
  277. public static ServiceResult ErrorResult(string message, int statusCode)
  278. {
  279. return new ServiceResult { Success = false, Message = message, StatusCode = statusCode };
  280. }
  281. }
  282.  
  283.  
  284. public async Task<GetProfileDoctorDTO> GetProfileAsync(Guid userId, string token)
  285. {
  286.  
  287. var isTokenBlacklisted = await _dbContext.BLTokenModel.AnyAsync(bt => bt.Token == token);
  288. if (isTokenBlacklisted)
  289. {
  290. throw new InvalidOperationException("The user is logged out.");
  291. }
  292.  
  293. DoctorModel? user = await _dbContext.Doctors.FindAsync(userId);
  294. return user == null
  295. ? throw new KeyNotFoundException("User not found")
  296. : new GetProfileDoctorDTO
  297. {
  298. Id = user.Id,
  299. Name = user.Name,
  300. Email = user.Email,
  301. BirthDate = user.BirthDate,
  302. PhoneNumber = user.PhoneNumber,
  303. Gender = user.Gender
  304. };
  305. }
  306.  
  307.  
  308. public async Task UpdateProfileAsync(Guid userId, UpdateProfileDTO updateProfileDTO, string token)
  309. {
  310.  
  311. var isTokenBlacklisted = await _dbContext.BLTokenModel.AnyAsync(bt => bt.Token == token);
  312. if (isTokenBlacklisted)
  313. {
  314. throw new InvalidOperationException("The user is logged out.");
  315. }
  316.  
  317.  
  318. AssertValidEmail(updateProfileDTO.Email);
  319. AssertValidPhone(updateProfileDTO.Phone);
  320. ValidateBirthDate(updateProfileDTO.Birthday);
  321. ValidateGender(updateProfileDTO.Gender);
  322.  
  323. DoctorModel? user = await _dbContext.Doctors.FindAsync(userId) ?? throw new KeyNotFoundException("User not found");
  324. user.Email = updateProfileDTO.Email;
  325. user.Name = updateProfileDTO.Name;
  326. user.BirthDate = updateProfileDTO.Birthday;
  327. user.Gender = updateProfileDTO.Gender;
  328. user.PhoneNumber = updateProfileDTO.Phone;
  329.  
  330. _ = _dbContext.Doctors.Update(user);
  331. _ = await _dbContext.SaveChangesAsync();
  332. }
  333.  
  334. }
  335. }
Add Comment
Please, Sign In to add comment