aboutsummaryrefslogtreecommitdiff
path: root/BackEnd/Timeline/Services/Token/SecureRandomUserTokenService.cs
blob: 2ab263deaaf42984676e53fe22a8913b2b6c73b7 (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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Timeline.Configs;
using Timeline.Entities;

namespace Timeline.Services.Token
{
    public class SecureRandomUserTokenService : IUserTokenService, IDisposable
    {
        private DatabaseContext _databaseContext;
        private ILogger<SecureRandomUserTokenService> _logger;
        private RandomNumberGenerator _secureRandom;
        private IOptionsMonitor<TokenOptions> _optionMonitor;
        private IClock _clock;

        public SecureRandomUserTokenService(DatabaseContext databaseContext, ILogger<SecureRandomUserTokenService> logger, IOptionsMonitor<TokenOptions> optionMonitor, IClock clock)
        {
            _databaseContext = databaseContext;
            _logger = logger;
            _secureRandom = RandomNumberGenerator.Create();
            _optionMonitor = optionMonitor;
            _clock = clock;
        }

        public void Dispose()
        {
            _secureRandom.Dispose();
        }

        private string GenerateSecureRandomTokenString()
        {
            var option = _optionMonitor.CurrentValue;
            var tokenLength = option.TokenLength ?? 32;
            var buffer = new byte[tokenLength];
            _secureRandom.GetBytes(buffer);
            return Convert.ToHexString(buffer);
        }

        /// <inheritdoc/>
        public async Task<string> CreateTokenAsync(long userId, DateTime? expireTime)
        {
            var currentTime = _clock.GetCurrentTime();

            if (expireTime is not null && expireTime > currentTime)
            {
                _logger.LogWarning("The expire time of the token has already passed.");
            }

            UserTokenEntity entity = new UserTokenEntity
            {
                UserId = userId,
                Token = GenerateSecureRandomTokenString(),
                ExpireAt = expireTime,
                CreateAt = currentTime,
                Deleted = false
            };

            _databaseContext.UserTokens.Add(entity);
            await _databaseContext.SaveChangesAsync();

            _logger.LogInformation("A user token is created with user id {}.", userId);

            return entity.Token;
        }

        /// <inheritdoc/>
        public async Task<UserTokenInfo> ValidateTokenAsync(string token)
        {
            var entity = await _databaseContext.UserTokens.Where(t => t.Token == token && !t.Deleted).SingleOrDefaultAsync();

            if (entity is null)
            {
                throw new UserTokenException(token, Resource.ExceptionUserTokenInvalid);
            }

            var currentTime = _clock.GetCurrentTime();

            if (entity.ExpireAt.HasValue && entity.ExpireAt.Value <= currentTime)
            {
                throw new UserTokenExpiredException(token, entity.ExpireAt.Value, currentTime);
            }

            return new UserTokenInfo()
            {
                UserId = entity.UserId,
                ExpireAt = entity.ExpireAt,
                CreateAt = entity.CreateAt
            };
        }

        /// <inheritdoc/>
        public async Task<bool> RevokeTokenAsync(string token)
        {
            var entity = await _databaseContext.UserTokens.Where(t => t.Token == token && t.Deleted == false).SingleOrDefaultAsync();
            if (entity is not null)
            {
                entity.Deleted = true;
                await _databaseContext.SaveChangesAsync();

                _logger.LogInformation("A token is revoked with user id {}.", entity.UserId);

                return entity.ExpireAt <= _clock.GetCurrentTime();
            }
            return false;
        }

        /// <inheritdoc/>
        public async Task RevokeAllTokenByUserIdAsync(long userId)
        {
            List<UserTokenEntity> entities = await _databaseContext.UserTokens.Where(t => t.UserId == userId && t.Deleted == false).ToListAsync();
            foreach (var entity in entities)
            {
                entity.Deleted = true;
            }
            await _databaseContext.SaveChangesAsync();
            _logger.LogInformation("All tokens of user with id {} are revoked.", userId);
        }
    }
}