aboutsummaryrefslogtreecommitdiff
path: root/Timeline/Services/UserTokenManager.cs
blob: 6decf8f9c533779d6644de482080ec5feebbbc04 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
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
    {
        /// <summary>
        /// Try to create a token for given username and password.
        /// </summary>
        /// <param name="username">The username.</param>
        /// <param name="password">The password.</param>
        /// <param name="expireAt">The expire time of the token.</param>
        /// <returns>The created token and the user info.</returns>
        /// <exception cref="ArgumentNullException">Thrown when <paramref name="username"/> or <paramref name="password"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when <paramref name="username"/> is of bad format.</exception>
        /// <exception cref="UserNotExistException">Thrown when the user with <paramref name="username"/> does not exist.</exception>
        /// <exception cref="BadPasswordException">Thrown when <paramref name="password"/> is wrong.</exception>
        public Task<UserTokenCreateResult> CreateToken(string username, string password, DateTime? expireAt = null);

        /// <summary>
        /// Verify a token and get the saved user info. This also check the database for existence of the user.
        /// </summary>
        /// <param name="token">The token.</param>
        /// <returns>The user stored in token.</returns>
        /// <exception cref="ArgumentNullException">Thrown when <paramref name="token"/> is null.</exception>
        /// <exception cref="UserTokenTimeExpireException">Thrown when the token is expired.</exception>
        /// <exception cref="UserTokenBadVersionException">Thrown when the token is of bad version.</exception>
        /// <exception cref="UserTokenBadFormatException">Thrown when the token is of bad format.</exception>
        /// <exception cref="UserNotExistException">Thrown when the user specified by the token does not exist. Usually the user had been deleted after the token was issued.</exception>
        public Task<User> VerifyToken(string token);
    }

    public class UserTokenManager : IUserTokenManager
    {
        private readonly ILogger<UserTokenManager> _logger;
        private readonly IUserService _userService;
        private readonly IUserTokenService _userTokenService;
        private readonly IClock _clock;

        public UserTokenManager(ILogger<UserTokenManager> logger, IUserService userService, IUserTokenService userTokenService, IClock clock)
        {
            _logger = logger;
            _userService = userService;
            _userTokenService = userTokenService;
            _clock = clock;
        }

        public async Task<UserTokenCreateResult> 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!.Value, Version = user.Version!.Value, ExpireAt = expireAt });

            return new UserTokenCreateResult { Token = token, User = user };
        }


        public async Task<User> 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.Value);

            return user;
        }
    }
}