using Core.Abstractions; using Core.DomainObjects; using DataProviders.Collections; using DomainResults.Common; using CryptoProvider; using JWTService; using WeatherForecast.Models.Requests; using Microsoft.Extensions.Options; using Core.Enumerations; namespace WeatherForecast.Services { /// /// /// public interface IUserService { /// /// /// /// /// (string?, IDomainResult) CreateToken(AuthenticationRequestModel requestData); /// /// /// /// /// /// /// /// IDomainResult VerifyToken(Guid? userId, string? token, WebapiControllers? controller, CrudActions? actions); } /// /// /// public class UserService : ServiceBase, IUserService { private readonly IAesKey? _aesKey; private readonly IUserDataProvider _userDataProvider; private readonly IJWTService _jwtService; /// /// /// /// /// /// /// public UserService ( ILogger logger, IOptions options, IUserDataProvider userDataProvider, IJWTService jwtService ) : base(logger) { _aesKey = options.Value.JwtTokenEncryption; _userDataProvider = userDataProvider; _jwtService = jwtService; } /// /// /// /// /// public (string?, IDomainResult) CreateToken(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("unique_name", $"{user.Id}"), // (JWT ID): Unique identifier; can be used to prevent the JWT from being replayed (allows a token to be used only once) new KeyValuePair("jti", $"{Guid.NewGuid()}") }; 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 VerifyToken(Guid? userId, string? token, WebapiControllers? controller, CrudActions? action) { if (_aesKey?.IV == null || _aesKey?.Key == null) return IDomainResult.Failed("IV or Key are not set"); if (token == null || userId == null) return IDomainResult.Failed(); var (user, getUserResult) = _userDataProvider.Get(userId.Value); if (!getUserResult.IsSuccess || user == null) return IDomainResult.Failed(); #region Tokens cleanup var userTokens = user.Tokens.Where(x => x.Expires > DateTime.UtcNow).OrderByDescending(x => x.Expires).Take(10).ToList(); if (user.Tokens.Count != userTokens.Count) { user.Tokens = userTokens; _userDataProvider.Update(user); } #endregion // Check if whitelisted if(!userTokens.Select(x => AesService.DecryptString(_aesKey.IV, _aesKey.Key, x.Value)).Any(x => string.Compare(x, token) == 0)) IDomainResult.Unauthorized(); // Check if authorized if (controller != null) { if (!user.Authorizations.Any(x => x.Controller == controller)) return IDomainResult.Unauthorized(); if(action != null) if (!user.Authorizations.SingleOrDefault(x => x.Controller == controller).Actions.Any(x => x == action)) return IDomainResult.Unauthorized(); } return IDomainResult.Success(); } } }