using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; using System.Threading.Tasks; using Timeline.Configs; using Timeline.Entities; using Timeline.Helpers; using Timeline.Services.Exceptions; namespace Timeline.Services.Token { public class UserTokenCreateResult { public string Token { get; set; } = default!; public UserEntity User { get; set; } = default!; } public interface IUserTokenManager { /// /// Try to create a token for given username and password. /// /// The username. /// The password. /// The expire time of the token. /// The created token and the user info. /// Thrown when or is null. /// Thrown when is of bad format. /// Thrown when the user with does not exist. /// Thrown when is wrong. public Task CreateToken(string username, string password, DateTime? expireAt = null); /// /// Verify a token and get the saved user info. This also check the database for existence of the user. /// /// The token. /// The user stored in token. /// Thrown when is null. /// Thrown when the token is expired. /// Thrown when the token is of bad version. /// Thrown when the token is of bad format. /// Thrown when the user specified by the token does not exist. Usually the user had been deleted after the token was issued. public Task VerifyToken(string token); } public class UserTokenManager : IUserTokenManager { private readonly ILogger _logger; private readonly IOptionsMonitor _tokenOptionsMonitor; private readonly IUserService _userService; private readonly IUserCredentialService _userCredentialService; private readonly IUserTokenHandler _userTokenService; private readonly IClock _clock; public UserTokenManager(ILogger logger, IOptionsMonitor tokenOptionsMonitor, IUserService userService, IUserCredentialService userCredentialService, IUserTokenHandler userTokenService, IClock clock) { _logger = logger; _tokenOptionsMonitor = tokenOptionsMonitor; _userService = userService; _userCredentialService = userCredentialService; _userTokenService = userTokenService; _clock = clock; } public async Task CreateToken(string username, string password, DateTime? expireAt = null) { expireAt = expireAt?.MyToUtc(); if (username == null) throw new ArgumentNullException(nameof(username)); if (password == null) throw new ArgumentNullException(nameof(password)); var userId = await _userCredentialService.VerifyCredential(username, password); var user = await _userService.GetUser(userId); var token = _userTokenService.GenerateToken(new UserTokenInfo { Id = user.Id, Version = user.Version, ExpireAt = expireAt ?? _clock.GetCurrentTime() + TimeSpan.FromSeconds(_tokenOptionsMonitor.CurrentValue.DefaultExpireSeconds) }); return new UserTokenCreateResult { Token = token, User = user }; } public async Task VerifyToken(string token) { if (token == null) throw new ArgumentNullException(nameof(token)); var tokenInfo = _userTokenService.VerifyToken(token); var currentTime = _clock.GetCurrentTime(); if (tokenInfo.ExpireAt < currentTime) throw new UserTokenTimeExpiredException(token, tokenInfo.ExpireAt, currentTime); try { var user = await _userService.GetUser(tokenInfo.Id); if (tokenInfo.Version < user.Version) throw new UserTokenVersionExpiredException(token, tokenInfo.Version, user.Version); return user; } catch (UserNotExistException e) { throw new UserTokenUserNotExistException(token, e); } } } }