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.User;
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 IUserTokenHandler _userTokenService;
private readonly IClock _clock;
public UserTokenManager(ILogger logger, IOptionsMonitor tokenOptionsMonitor, IUserService userService, IUserTokenHandler userTokenService, IClock clock)
{
_logger = logger;
_tokenOptionsMonitor = tokenOptionsMonitor;
_userService = userService;
_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 _userService.VerifyCredential(username, password);
var user = await _userService.GetUserAsync(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.GetUserAsync(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);
}
}
}
}