using Microsoft.Extensions.Logging; using System; using System.Threading.Tasks; using Timeline.Models; namespace Timeline.Services { public class UserTokenCreateResult { public string Token { get; set; } = default!; public User 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 IUserService _userService; private readonly IUserTokenService _userTokenService; private readonly IClock _clock; public UserTokenManager(ILogger logger, IUserService userService, IUserTokenService userTokenService, IClock clock) { _logger = logger; _userService = userService; _userTokenService = userTokenService; _clock = clock; } public async Task CreateToken(string username, string password, DateTime? expireAt = null) { if (username == null) throw new ArgumentNullException(nameof(username)); if (password == null) throw new ArgumentNullException(nameof(password)); var user = await _userService.VerifyCredential(username, password); var token = _userTokenService.GenerateToken(new UserTokenInfo { Id = user.Id, Version = user.Version, ExpireAt = expireAt }); 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); if (tokenInfo.ExpireAt.HasValue) { var currentTime = _clock.GetCurrentTime(); if (tokenInfo.ExpireAt < currentTime) throw new UserTokenTimeExpireException(token, tokenInfo.ExpireAt.Value, currentTime); } var user = await _userService.GetUserById(tokenInfo.Id); if (tokenInfo.Version < user.Version) throw new UserTokenBadVersionException(token, tokenInfo.Version, user.Version); return user; } } }