Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

AuthenticationService.cs 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. using Microsoft.AspNetCore.WebUtilities;
  2. using Microsoft.Extensions.Logging;
  3. using System.Net;
  4. namespace Diligent.WebAPI.Business.Services
  5. {
  6. public class AuthenticationService : IAuthenticationService
  7. {
  8. private readonly AuthorizationSettings _authSettings;
  9. private readonly FrontEndSettings _frontEndSettings;
  10. private readonly UserManager<User> _userManager;
  11. private readonly DatabaseContext _databaseContext;
  12. private readonly IEmailer _emailer;
  13. private readonly ILogger<AuthenticationService> _logger;
  14. private readonly IHttpClientService _httpClient;
  15. public AuthenticationService(IOptions<AuthorizationSettings> authSettings,
  16. IOptions<FrontEndSettings> frontEndSettings,
  17. UserManager<User> userManager,
  18. DatabaseContext databaseContext,
  19. IEmailer emailer,
  20. ILogger<AuthenticationService> logger,
  21. IHttpClientService httpClient)
  22. {
  23. _authSettings = authSettings.Value;
  24. _frontEndSettings = frontEndSettings.Value;
  25. _userManager = userManager;
  26. _databaseContext = databaseContext;
  27. _httpClient = httpClient;
  28. _emailer = emailer;
  29. _logger = logger;
  30. }
  31. public async Task<ServiceResponseDTO<AuthenticateResponseDto>> Authenticate(AuthenticateRequestDto model)
  32. {
  33. var user = await _userManager.FindByNameAsync(model.Username);
  34. // return null if user not found
  35. if (user == null)
  36. {
  37. return new ServiceResponseDTO<AuthenticateResponseDto>
  38. {
  39. IsError = true,
  40. ErrorMessage = "Username is not valid"
  41. };
  42. }
  43. var result = await _userManager.CheckPasswordAsync(user, model.Password);
  44. // return null if user is disabled
  45. if (user.IsEnabled == false)
  46. {
  47. return new ServiceResponseDTO<AuthenticateResponseDto>
  48. {
  49. IsError = true,
  50. ErrorMessage = $"User with email {model.Username} has no permission to log in."
  51. };
  52. }
  53. // password is not correct
  54. if (!result)
  55. {
  56. await _userManager.AccessFailedAsync(user);
  57. return new ServiceResponseDTO<AuthenticateResponseDto>
  58. {
  59. IsError = true,
  60. ErrorMessage = "Password is not correct"
  61. };
  62. }
  63. return await GenerateToken(user);
  64. }
  65. public async Task<ServiceResponseDTO<AuthenticateResponseDto>> Authenticate(GoogleApiModel model)
  66. {
  67. if (!(await _httpClient.IsTokenValid(model.Token)))
  68. {
  69. return new ServiceResponseDTO<AuthenticateResponseDto>
  70. {
  71. IsError = true,
  72. ErrorMessage = "Invalid Google Api Token"
  73. };
  74. }
  75. var user = await _userManager.FindByEmailAsync(model.User.email);
  76. // return null if user not found
  77. if (user == null)
  78. {
  79. return new ServiceResponseDTO<AuthenticateResponseDto>
  80. {
  81. IsError = true,
  82. ErrorMessage = $"User with email {model.User.email} does not exist in database"
  83. };
  84. }
  85. if (user.IsEnabled == false)
  86. {
  87. return new ServiceResponseDTO<AuthenticateResponseDto>
  88. {
  89. IsError = true,
  90. ErrorMessage = $"User with email {model.User.email} has no permission to log in."
  91. };
  92. }
  93. return await GenerateToken(user);
  94. }
  95. private async Task<ServiceResponseDTO<AuthenticateResponseDto>> GenerateToken(User user)
  96. {
  97. var isLocked = await _userManager.IsLockedOutAsync(user);
  98. if (isLocked)
  99. return new ServiceResponseDTO<AuthenticateResponseDto>
  100. {
  101. IsError = true,
  102. ErrorMessage = "The account is locked out"
  103. };
  104. // authentication successful so generate jwt token
  105. var token = await GenerateJwtToken(user, true);
  106. var data = new AuthenticateResponseDto
  107. {
  108. Id = user.Id,
  109. Username = user.UserName,
  110. FirstName = user.FirstName,
  111. LastName = user.LastName,
  112. Token = token,
  113. RefreshToken = token
  114. };
  115. return new ServiceResponseDTO<AuthenticateResponseDto>
  116. {
  117. Data = data
  118. };
  119. }
  120. private async Task<string> GenerateJwtToken(User user, bool authenticate = false)
  121. {
  122. // generate token that is valid for 7 days
  123. var tokenHandler = new JwtSecurityTokenHandler();
  124. var key = Encoding.ASCII.GetBytes(_authSettings.Secret);
  125. var tokenDescriptor = new SecurityTokenDescriptor
  126. {
  127. Subject = new ClaimsIdentity(new[] {
  128. new Claim(JwtRegisteredClaimNames.Jti, user.Id.ToString()),
  129. new Claim("id", user.Id.ToString())
  130. }),
  131. Expires = DateTime.UtcNow.AddMinutes(_authSettings.JwtExpiredTime),
  132. SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
  133. };
  134. var token = tokenHandler.CreateToken(tokenDescriptor);
  135. var writedToken = tokenHandler.WriteToken(token);
  136. var refreshToken = new RefreshToken
  137. {
  138. Token = writedToken,
  139. JwtId = user.Id.ToString(),
  140. UserId = user.Id,
  141. User = user,
  142. CreationDate = DateTime.UtcNow,
  143. ExpiryDate = DateTime.UtcNow.AddMinutes(_authSettings.JwtRefreshExpiredTime)
  144. };
  145. var existRefreshToken = await _databaseContext.RefreshTokens.Where(x => x.UserId == user.Id).FirstOrDefaultAsync();
  146. if (existRefreshToken != null)
  147. {
  148. existRefreshToken.Token = writedToken;
  149. existRefreshToken.JwtId = token.Id;
  150. existRefreshToken.CreationDate = DateTime.UtcNow;
  151. existRefreshToken.ExpiryDate = DateTime.UtcNow.AddMinutes(_authSettings.JwtRefreshExpiredTime);
  152. if (authenticate)
  153. {
  154. existRefreshToken.Used = false;
  155. existRefreshToken.Invalidated = false;
  156. }
  157. _databaseContext.RefreshTokens.Update(existRefreshToken);
  158. await UpdateRefreshToken(existRefreshToken);
  159. }
  160. else
  161. {
  162. await _databaseContext.RefreshTokens.AddAsync(refreshToken);
  163. }
  164. await _databaseContext.SaveChangesAsync();
  165. return writedToken;
  166. }
  167. public async Task<RefreshTokenResultDto> RefreshTokenAsync(RefreshTokenRequestDto model)
  168. {
  169. var validatedToken = GetPrincipalFromToken(model.Token, false);
  170. if (validatedToken == null)
  171. {
  172. return new RefreshTokenResultDto { Error = "Invalid token" };
  173. }
  174. var jti = validatedToken.Claims.Single(x => x.Type == JwtRegisteredClaimNames.Jti).Value;
  175. var storedRefreshToken = await _databaseContext.RefreshTokens.SingleOrDefaultAsync(x => x.JwtId == jti);
  176. if (storedRefreshToken == null)
  177. {
  178. return new RefreshTokenResultDto { Error = "This refresh token does not exist" };
  179. }
  180. var userk = await _databaseContext.Users.Where(u => u.Id == storedRefreshToken.UserId).FirstOrDefaultAsync();
  181. if (userk == null)
  182. {
  183. return new RefreshTokenResultDto { Error = "There is no user which is associated with refresh token" };
  184. }
  185. var expiryDateUnix = long.Parse(validatedToken.Claims.Single(x => x.Type == JwtRegisteredClaimNames.Exp).Value);
  186. var expiryDateTimeUtc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)
  187. .AddMinutes(expiryDateUnix);
  188. if (expiryDateTimeUtc < DateTime.UtcNow)
  189. {
  190. return new RefreshTokenResultDto
  191. {
  192. Data = new AuthenticateResponseDto
  193. {
  194. Id = userk.Id,
  195. FirstName = userk.FirstName,
  196. LastName = userk.LastName,
  197. Username = userk.UserName,
  198. Token = model.Token,
  199. RefreshToken = model.RefreshToken
  200. }
  201. };
  202. }
  203. if (DateTime.UtcNow > storedRefreshToken.ExpiryDate)
  204. {
  205. return new RefreshTokenResultDto { Error = "This refresh token has expired" };
  206. }
  207. if (storedRefreshToken.Invalidated)
  208. {
  209. return new RefreshTokenResultDto { Error = "This refresh token has been invalidated" };
  210. }
  211. if (storedRefreshToken.JwtId != jti)
  212. {
  213. return new RefreshTokenResultDto { Error = "This refresh token does not match this JWT" };
  214. }
  215. storedRefreshToken.ExpiryDate = DateTime.UtcNow.AddMinutes(_authSettings.JwtRefreshExpiredTime);
  216. await _databaseContext.SaveChangesAsync();
  217. var user = await _userManager.FindByIdAsync(validatedToken.Claims.Single(x => x.Type == "id").Value);
  218. var token = await GenerateJwtToken(user);
  219. return new RefreshTokenResultDto
  220. {
  221. Data = new AuthenticateResponseDto
  222. {
  223. Id = userk.Id,
  224. FirstName = userk.FirstName,
  225. LastName = userk.LastName,
  226. Username = userk.UserName,
  227. Token = model.Token,
  228. RefreshToken = model.RefreshToken
  229. }
  230. };
  231. }
  232. public async Task<ServiceResponseDTO<string>> DeleteRefreshToken(int userId)
  233. {
  234. var refreshToken = await _databaseContext.RefreshTokens.Where(r => r.UserId == userId).FirstOrDefaultAsync();
  235. if (refreshToken is null)
  236. return new ServiceResponseDTO<string>
  237. {
  238. IsError = true,
  239. ErrorMessage = "There is no refresh token for user"
  240. };
  241. _databaseContext.RefreshTokens.Remove(refreshToken);
  242. var result = await _databaseContext.SaveChangesAsync() > 0;
  243. if (!result)
  244. return new ServiceResponseDTO<string>
  245. {
  246. IsError = true,
  247. ErrorMessage = "Problem with saving changes into database"
  248. };
  249. return new ServiceResponseDTO<string>
  250. {
  251. Data = null
  252. };
  253. }
  254. private ClaimsPrincipal? GetPrincipalFromToken(string token, bool validateLifetime)
  255. {
  256. var tokenHandler = new JwtSecurityTokenHandler();
  257. var key = Encoding.ASCII.GetBytes(_authSettings.Secret);
  258. var tokenValidationParameters = new TokenValidationParameters
  259. {
  260. ValidateIssuerSigningKey = true,
  261. IssuerSigningKey = new SymmetricSecurityKey(key),
  262. ValidateIssuer = false,
  263. ValidateAudience = false,
  264. RequireExpirationTime = false,
  265. ValidateLifetime = validateLifetime,
  266. // set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)
  267. //ClockSkew = TimeSpan.Zero
  268. };
  269. try
  270. {
  271. var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out var validatedToken);
  272. if (!IsJwtWithValidSecurityAlgorithm(validatedToken))
  273. {
  274. return null;
  275. }
  276. return principal;
  277. }
  278. catch (Exception)
  279. {
  280. return null;
  281. }
  282. }
  283. private static bool IsJwtWithValidSecurityAlgorithm(SecurityToken validatedToken)
  284. {
  285. return (validatedToken is JwtSecurityToken jwtSecurityToken) &&
  286. jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256,
  287. StringComparison.InvariantCultureIgnoreCase);
  288. }
  289. public async Task<RefreshToken?> GetRefreshTokenByUserId(int userId)
  290. {
  291. return await _databaseContext.RefreshTokens.Where(x => x.UserId == userId).FirstOrDefaultAsync();
  292. }
  293. public async Task UpdateRefreshToken(RefreshToken refreshToken)
  294. {
  295. _databaseContext.RefreshTokens.Update(refreshToken);
  296. await _databaseContext.SaveChangesAsync();
  297. }
  298. public async Task<ServiceResponseDTO<object>> GetEmailConfirmationUrlAsync(string email)
  299. {
  300. var user = await _userManager.FindByEmailAsync(email);
  301. if (user == null)
  302. {
  303. return new ServiceResponseDTO<object>
  304. {
  305. IsError = true,
  306. ErrorMessage = "Email did not find."
  307. };
  308. }
  309. var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
  310. token = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(token));
  311. await _emailer.SendEmailAndWriteToDbAsync(email, "Reset password", HTMLHelper.RenderForgotPasswordPage($"{_frontEndSettings.BaseUrl}/reset-password?token={token}&email={email}"), isHtml: true);
  312. user.PasswordResetToken = token;
  313. await _databaseContext.SaveChangesAsync();
  314. return new ServiceResponseDTO<object>
  315. {
  316. Data = new { code = token, email = email }
  317. };
  318. }
  319. public async Task<ServiceResponseDTO<object>> PasswordResetAsync(string email, string code, string password)
  320. {
  321. var user = await _userManager.FindByEmailAsync(email);
  322. if (user == null)
  323. {
  324. return new ServiceResponseDTO<object>
  325. {
  326. IsError = true,
  327. ErrorMessage = "Email did not find."
  328. };
  329. }
  330. // FOR SOME REASON USERMANAGER.RESETPASSWORDASYNC returns InvalidToken. In future change this
  331. //var passwordResetToken = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
  332. //IdentityResult resetResult = await _userManager.ResetPasswordAsync(user, passwordResetToken, password);
  333. //if (resetResult.Succeeded)
  334. await _userManager.RemovePasswordAsync(user);
  335. await _userManager.AddPasswordAsync(user, password);
  336. if (user.PasswordResetToken == code)
  337. {
  338. if (await _userManager.IsLockedOutAsync(user))
  339. {
  340. await _userManager.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow);
  341. }
  342. return new ServiceResponseDTO<object> { Data = true };
  343. }
  344. //var errors = resetResult.Errors.Select(x => x.Description);
  345. return new ServiceResponseDTO<object>
  346. {
  347. IsError = true,
  348. ErrorMessage = "Invalid reset password token"
  349. };
  350. }
  351. }
  352. }