1 module appbase.security.token;
2 
3 import core.thread;
4 
5 import std.bitmanip;
6 import std.exception : enforce;
7 import std.datetime;
8 import std.concurrency;
9 import std.base64;
10 
11 import crypto.rsa;
12 
13 import appbase.utils.utility;
14 
15 class Token
16 {
17     static ubyte[] generate(ulong userId, RSAKeyInfo publicKey)
18     {
19         ubyte[] token = new ubyte[ulong.sizeof + long.sizeof];
20         token.write!ulong(userId, 0);
21         long ticks = currTimeTick();
22         token.write!long(ticks, ulong.sizeof);
23         token ~= strToByte_hex(MD5(token)[0 .. 4]);
24         token = RSA.encrypt(publicKey, token);
25 
26         insertCache(userId, token, ticks);
27 
28         return token;
29     }
30 
31     static bool check(ulong userId, string token, RSAKeyInfo privateKey, const int tokenExpire)
32     {
33         scope(failure) { return false; }
34 
35         if (!cleanTaskRunning)
36         {
37             synchronized(Token.classinfo)
38             {
39                 if (!cleanTaskRunning)
40                 {
41                     spawn(&cleanTask, tokenExpire);
42                     cleanTaskRunning = true;
43                 }
44             }
45         }
46 
47         ubyte[] _token = Base64.decode(token);
48 
49         if ((userId in pool) && (pool[userId].token == _token))
50         {
51             return true;
52         }
53 
54         ubyte[] data = RSA.decrypt(privateKey, _token);
55 
56         if (data.length < ulong.sizeof + long.sizeof + 2)
57         {
58             return false;
59         }
60 
61         if (strToByte_hex(MD5(data[0 .. $ - 2])[0 .. 4]) != data[$ - 2 .. $])
62         {
63             return false;
64         }
65 
66         ulong _id = data.peek!ulong(0);
67         if (_id != userId)
68         {
69             return false;
70         }
71 
72         long ticks = data.peek!long(ulong.sizeof);
73         if ((Clock.currTime() - currTimeFromTick(ticks)).total!"minutes" > tokenExpire)
74         {
75             return false;
76         }
77 
78         insertCache(userId, _token, ticks);
79 
80         return true;
81     }
82 
83     static string getTokenInPool(ulong userId)
84     {
85         if (userId !in pool)
86         {
87             return string.init;
88         }
89 
90         return Base64.encode(pool[userId].token);
91     }
92 
93 private:
94 
95     static void insertCache(ulong userId, in ubyte[] token, long ticks)
96     {
97         CacheTokenData cache;
98         cache.token = token.dup;
99         cache.ticks = ticks;
100         pool[userId] = cache;
101     }
102 
103 package:
104 
105     __gshared static CacheTokenData[ulong] pool;
106     __gshared static bool cleanTaskRunning = false;
107 }
108 
109 private:
110 
111 struct CacheTokenData
112 {
113     ubyte[] token;
114     long ticks;
115 }
116 
117 void cleanTask(const int tokenExpire)
118 {
119     while (true)
120     {
121         Thread.sleep(1.hours);
122 
123         synchronized(Token.classinfo)
124         {
125             foreach(userId, data; Token.pool)
126             {
127                 if ((Clock.currTime() - currTimeFromTick(data.ticks)).total!"minutes" > tokenExpire)
128                 {
129                     Token.pool.remove(userId);
130                 }
131             }
132         }
133     }
134 }