using Core.Abstractions; using Core.DomainObjects; using DataProviders.Collections; using DomainResults.Common; using ExtensionMethods; using CryptoProvider; using JWTService; using WeatherForecast.Models.Requests; using Microsoft.Extensions.Options; namespace WeatherForecast.Services { /// /// /// public interface IAuthenticationService { /// /// /// /// /// (string?, IDomainResult) Post(AuthenticationRequestModel requestData); /// /// /// /// /// IDomainResult Get(string? token); } /// /// /// public class AutheticationService : ServiceBase, IAuthenticationService { private readonly IAesKey? _aesKey; private readonly IUserDataProvider _userDataProvider; private readonly IJWTService _jwtService; /// /// /// /// /// /// /// public AutheticationService ( ILogger logger, IOptions options, IUserDataProvider userDataProvider, IJWTService jwtService ) : base(logger) { _aesKey = options.Value.JwtTokenEncryption; _userDataProvider = userDataProvider; _jwtService = jwtService; } /// /// /// /// /// public (string?, IDomainResult) Post(AuthenticationRequestModel requestData) { if (_aesKey?.IV == null || _aesKey?.Key == null) return IDomainResult.Failed("IV or Key are not set"); // Retrieve user from database by userName var (user, getUserResult) = _userDataProvider.GetByUsername(requestData.Username); if (!getUserResult.IsSuccess || user == null) return (null, getUserResult); if (user.Passwords.Password == null) return IDomainResult.Failed("Password is not set, create new password."); // Check provided password hash with the stored one var (salt, hash) = HashService.CreateSaltedHash(requestData.Password); if (!HashService.ValidateHash(requestData.Password, salt, hash)) return IDomainResult.Unauthorized(); // Check password expiration if enabled if (user.Passwords.Expiration.Enabled && DateTime.UtcNow > user.Passwords.Password.Created.AddDays(user.Passwords.Expiration.Days)) { user.Passwords.Expired.Add(user.Passwords.Password.Prototype()); user.Passwords.Password = null; user.Tokens = new List(); return IDomainResult.Failed("Password is expired, create new password."); } // Creating JWT token var claims = new List> { new KeyValuePair("UserId", $"{user.Id}") }; var created = DateTime.UtcNow; var expires = created.AddDays(365); var token = _jwtService.CreateJwtToken(expires, claims); user.Tokens.Add(new Token { Value = AesService.EncryptString(_aesKey.IV, _aesKey.Key, token), Created = created, Expires = expires, }); var (_, usdateUserResult) = _userDataProvider.Update(user); if (!usdateUserResult.IsSuccess) return IDomainResult.Failed(); return IDomainResult.Success(token); } /// /// /// /// /// public IDomainResult Get(string? token) { if (_aesKey?.IV == null || _aesKey?.Key == null) return IDomainResult.Failed("IV or Key are not set"); if (token == null) return IDomainResult.Failed(); #region Retrieve user id from token claim var (claims, getClaimsResult) = _jwtService.JwtTokenClaims(token); if (!getClaimsResult.IsSuccess || claims == null) return IDomainResult.Failed(); var userId = claims.SingleOrDefault(x => x.Key == "UserId").Value.ToGuid(); if (userId == Guid.Empty) return IDomainResult.Failed(); #endregion var (user, getUserResult) = _userDataProvider.Get(userId); if (!getUserResult.IsSuccess || user == null) return IDomainResult.Failed(); #region Tokens cleanup var userTokens = user.Tokens.Where(x => x.Expires > DateTime.UtcNow).ToList(); if (user.Tokens.Count != userTokens.Count) { user.Tokens = userTokens; _userDataProvider.Update(user); } #endregion return userTokens.Select(x => AesService.DecryptString(_aesKey.IV, _aesKey.Key, x.Value)).Any(x => string.Compare(x, token) == 0) ? IDomainResult.Success() : IDomainResult.Failed(); } } }