reactredux/webapi/WeatherForecast/Services/UserService.cs

169 lines
5.5 KiB
C#

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 {
/// <summary>
///
/// </summary>
public interface IUserService {
/// <summary>
///
/// </summary>
/// <param name="requestData"></param>
/// <returns></returns>
(string?, IDomainResult) CreateToken(AuthenticationRequestModel requestData);
/// <summary>
///
/// </summary>
/// <param name="userId"></param>
/// <param name="token"></param>
/// <param name="controller"></param>
/// <param name="actions"></param>
/// <returns></returns>
IDomainResult VerifyToken(Guid? userId, string? token, WebapiControllers? controller, CrudActions? actions);
}
/// <summary>
///
/// </summary>
public class UserService : ServiceBase<UserService>, IUserService {
private readonly IAesKey? _aesKey;
private readonly IUserDataProvider _userDataProvider;
private readonly IJWTService _jwtService;
/// <summary>
///
/// </summary>
/// <param name="logger"></param>
/// <param name="options"></param>
/// <param name="userDataProvider"></param>
/// <param name="jwtService"></param>
public UserService (
ILogger<UserService> logger,
IOptions<Configuration> options,
IUserDataProvider userDataProvider,
IJWTService jwtService
) : base(logger) {
_aesKey = options.Value.JwtTokenEncryption;
_userDataProvider = userDataProvider;
_jwtService = jwtService;
}
/// <summary>
///
/// </summary>
/// <param name="requestData"></param>
/// <returns></returns>
public (string?, IDomainResult) CreateToken(AuthenticationRequestModel requestData) {
if (_aesKey?.IV == null || _aesKey?.Key == null)
return IDomainResult.Failed<string?>("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<string?>("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<string?>();
// 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<Token>();
return IDomainResult.Failed<string?>("Password is expired, create new password.");
}
// Creating JWT token
var claims = new List<KeyValuePair<string, string>> {
new KeyValuePair<string, string>("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<string, string>("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<string?>();
return IDomainResult.Success(token);
}
/// <summary>
///
/// </summary>
/// <param name="userId"></param>
/// <param name="token"></param>
/// <param name="controller"></param>
/// <param name="action"></param>
/// <returns></returns>
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();
}
}
}