1 /**
2 * Copyright © DiamondMVC 2018
3 * License: MIT (https://github.com/DiamondMVC/Diamond/blob/master/LICENSE)
4 * Author: Jacob Jensen (bausshf)
5 */
6 module diamond.authentication.auth;
7 
8 import diamond.core.apptype;
9 
10 static if (isWeb)
11 {
12   import core.time : minutes;
13   import std.datetime : Clock;
14 
15   import diamond.http;
16   import diamond.errors.checks;
17   import diamond.authentication.roles;
18 
19   /// The token validator.
20   private static __gshared TokenValidator tokenValidator;
21 
22   /// The token setter.
23   private static __gshared TokenSetter tokenSetter;
24 
25   /// The token invalidator.
26   private static __gshared TokenInvalidator tokenInvalidator;
27 
28   /// Static constructor for the module.
29   shared static this()
30   {
31     tokenValidator = new TokenValidator;
32     tokenSetter = new TokenSetter;
33     tokenInvalidator = new TokenInvalidator;
34   }
35 
36   /// The cookie key for auth tokens.
37   private static const __gshared authCookieKey = "__D_AUTH_TOKEN";
38 
39   /// Wrapper for the token validator.
40   private class TokenValidator
41   {
42     /// Function pointer.
43     Role function(string,HttpClient) f;
44 
45     /// Delegate.
46     Role delegate(string,HttpClient) d;
47 
48     /**
49     * Validates the token.
50     * Params:
51     *   token =    The token to validate.
52     *   client =   The client.
53     * Returns:
54     *   The role to associate with the token.
55     */
56     Role validate(string token, HttpClient client)
57     {
58       if (f) return f(token, client);
59       else if (d) return d(token, client);
60 
61       return null;
62     }
63   }
64 
65   /// Wrapper for the token setter.
66   private class TokenSetter
67   {
68     /// Function pointer.
69     string function(HttpClient) f;
70 
71     /// Delegate.
72     string delegate(HttpClient) d;
73 
74     /**
75     * Sets the token and gets the result.
76     * Params:
77     *   client = The client.
78     * Returns:
79     *   Returns the token result. This should be the generated token.
80     */
81     string getAndSetToken(HttpClient client)
82     {
83       if (f) return f(client);
84       else if (d) return d(client);
85 
86       return null;
87     }
88   }
89 
90   /// Wrapper for the token invalidator.
91   private class TokenInvalidator
92   {
93     /// Function pointer.
94     void function(string,HttpClient) f;
95 
96     /// Delegate.
97     void delegate(string,HttpClient) d;
98 
99     /**
100     * Invalidates the token.
101     * Params:
102     *   token =    The token to invalidate.
103     *   client =   The client.
104     */
105     void invalidate(string token, HttpClient client)
106     {
107       if (f) f(token, client);
108       else if (d) d(token, client);
109     }
110   }
111 
112   /**
113   * Sets the token validator.
114   * Params:
115   *   validator = The validator.
116   */
117   void setTokenValidator(Role function(string,HttpClient) validator)
118   {
119     tokenValidator.f = validator;
120     tokenValidator.d = null;
121   }
122 
123   /// ditto.
124   void setTokenValidator(Role delegate(string,HttpClient) validator)
125   {
126     tokenValidator.f = null;
127     tokenValidator.d = validator;
128   }
129 
130   /**
131   * Sets the token setter.
132   * Params:
133   *   setter = The setter.
134   */
135   void setTokenSetter(string function(HttpClient) setter)
136   {
137     tokenSetter.f = setter;
138     tokenSetter.d = null;
139   }
140 
141   /// Ditto.
142   void setTokenSetter(string delegate(HttpClient) setter)
143   {
144     tokenSetter.f = null;
145     tokenSetter.d = setter;
146   }
147 
148   /**
149   * Sets the token invalidator.
150   * Params:
151   *   invalidator = The invalidator.
152   */
153   void setTokenInvalidator(void function(string,HttpClient) invalidator)
154   {
155     tokenInvalidator.f = invalidator;
156     tokenInvalidator.d = null;
157   }
158 
159   /// Ditto.
160   void setTokenInvalidator(void delegate(string,HttpClient) invalidator)
161   {
162     tokenInvalidator.f = null;
163     tokenInvalidator.d = invalidator;
164   }
165 
166   /**
167   * Validates the authentication.
168   * This also sets the role etc.
169   * Params:
170   *   client = The client.
171   */
172   void validateAuthentication(HttpClient client)
173   {
174     if (setRoleFromSession(client, true))
175     {
176       return;
177     }
178 
179     enforce(tokenValidator.f !is null || tokenValidator.d !is null, "No token validator found.");
180 
181     auto token = client.cookies.get(authCookieKey);
182     Role role;
183 
184     if (token)
185     {
186       role = tokenValidator.validate(token, client);
187     }
188 
189     if (!role)
190     {
191       role = getRole("");
192     }
193 
194     setRole(client, role);
195   }
196 
197   /**
198   * Logs the user in.
199   * Params:
200   *   client =   The client.
201   *   loginTime = The time the user can be logged in. (In minutes)
202   *   role =      The role to login as. (If the role is null then the session won't have a role, causing every request to be authenticated.)
203   */
204   void login(HttpClient client, long loginTime, Role role)
205   {
206     enforce(tokenSetter.f !is null || tokenSetter.d !is null, "No token setter found.");
207 
208     if (role !is null)
209     {
210       setSessionRole(client, role);
211     }
212 
213     client.session.updateEndTime(Clock.currTime() + loginTime.minutes);
214 
215     auto token = enforceInput(tokenSetter.getAndSetToken(client), "Could not set token.");
216 
217     client.cookies.create(authCookieKey, token, loginTime * 60);
218 
219     validateAuthentication(client);
220   }
221 
222   /**
223   * Logs the user out.
224   * Params:
225   *   client =  The client.
226   */
227   void logout(HttpClient client)
228   {
229     enforce(tokenInvalidator.f !is null || tokenInvalidator.d !is null, "No token invalidator found.");
230 
231     client.session.clearValues();
232     client.cookies.remove(authCookieKey);
233     setRoleFromSession(client, false);
234 
235     auto token = client.cookies.get(authCookieKey);
236 
237     if (token)
238     {
239       tokenInvalidator.invalidate(token, client);
240     }
241   }
242 
243   /**
244   * Gets the auth cookie from a client.
245   * Params:
246   *   client = The client to get the auth cookie from.
247   * Returns:
248   *   Returns the auth cookie.
249   */
250   string getAuthCookie(HttpClient client)
251   {
252     return client.cookies.get(authCookieKey);
253   }
254 
255   /**
256   * Checks whether the client has the auth cookie or not.
257   * Params:
258   *   client = The client.
259   * Returns:
260   *   True if the client has the auth cookie, false otherwise.
261   */
262   bool hasAuthCookie(HttpClient client)
263   {
264     return client.cookies.has(authCookieKey);
265   }
266 }