1 /**
2 * Copyright © DiamondMVC 2019
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   package(diamond) void initializeAuth()
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   /**
40   * Gets the auth cookie key based on the client's host.
41   * If the host isn't found then the defauly key is used.
42   * Params:
43   *   client = The client to retrieve the host from.
44   * Returns:
45   *   The auth cookie key.
46   */
47   private string getAuthCookieKey(HttpClient client)
48   {
49     import diamond.core.senc;
50     import diamond.core.webconfig;
51 
52     string key = "";
53 
54     if (webConfig && webConfig.mappedAuthKeys && webConfig.mappedAuthKeys.length)
55     {
56       key = webConfig.mappedAuthKeys.get(client.host, "");
57     }
58 
59     return SENC.encode(key) ~ _authCookieKey;
60   }
61 
62   /// Wrapper for the token validator.
63   private class TokenValidator
64   {
65     /// Function pointer.
66     Role function(string,HttpClient) f;
67 
68     /// Delegate.
69     Role delegate(string,HttpClient) d;
70 
71     /**
72     * Validates the token.
73     * Params:
74     *   token =    The token to validate.
75     *   client =   The client.
76     * Returns:
77     *   The role to associate with the token.
78     */
79     Role validate(string token, HttpClient client)
80     {
81       if (f) return f(token, client);
82       else if (d) return d(token, client);
83 
84       return null;
85     }
86   }
87 
88   /// Wrapper for the token setter.
89   private class TokenSetter
90   {
91     /// Function pointer.
92     string function(HttpClient) f;
93 
94     /// Delegate.
95     string delegate(HttpClient) d;
96 
97     /**
98     * Sets the token and gets the result.
99     * Params:
100     *   client = The client.
101     * Returns:
102     *   Returns the token result. This should be the generated token.
103     */
104     string getAndSetToken(HttpClient client)
105     {
106       if (f) return f(client);
107       else if (d) return d(client);
108 
109       return null;
110     }
111   }
112 
113   /// Wrapper for the token invalidator.
114   private class TokenInvalidator
115   {
116     /// Function pointer.
117     void function(string,HttpClient) f;
118 
119     /// Delegate.
120     void delegate(string,HttpClient) d;
121 
122     /**
123     * Invalidates the token.
124     * Params:
125     *   token =    The token to invalidate.
126     *   client =   The client.
127     */
128     void invalidate(string token, HttpClient client)
129     {
130       if (f) f(token, client);
131       else if (d) d(token, client);
132     }
133   }
134 
135   /**
136   * Sets the token validator.
137   * Params:
138   *   validator = The validator.
139   */
140   void setTokenValidator(Role function(string,HttpClient) validator)
141   {
142     tokenValidator.f = validator;
143     tokenValidator.d = null;
144   }
145 
146   /// ditto.
147   void setTokenValidator(Role delegate(string,HttpClient) validator)
148   {
149     tokenValidator.f = null;
150     tokenValidator.d = validator;
151   }
152 
153   /**
154   * Sets the token setter.
155   * Params:
156   *   setter = The setter.
157   */
158   void setTokenSetter(string function(HttpClient) setter)
159   {
160     tokenSetter.f = setter;
161     tokenSetter.d = null;
162   }
163 
164   /// Ditto.
165   void setTokenSetter(string delegate(HttpClient) setter)
166   {
167     tokenSetter.f = null;
168     tokenSetter.d = setter;
169   }
170 
171   /**
172   * Sets the token invalidator.
173   * Params:
174   *   invalidator = The invalidator.
175   */
176   void setTokenInvalidator(void function(string,HttpClient) invalidator)
177   {
178     tokenInvalidator.f = invalidator;
179     tokenInvalidator.d = null;
180   }
181 
182   /// Ditto.
183   void setTokenInvalidator(void delegate(string,HttpClient) invalidator)
184   {
185     tokenInvalidator.f = null;
186     tokenInvalidator.d = invalidator;
187   }
188 
189   /**
190   * Validates the authentication.
191   * This also sets the role etc.
192   * Params:
193   *   client = The client.
194   */
195   void validateAuthentication(HttpClient client)
196   {
197     if (setRoleFromSession(client, true))
198     {
199       return;
200     }
201 
202     enforce(tokenValidator.f !is null || tokenValidator.d !is null, "No token validator found.");
203 
204     auto token = client.cookies.get(getAuthCookieKey(client));
205     Role role;
206 
207     if (token)
208     {
209       role = tokenValidator.validate(token, client);
210     }
211 
212     if (!role)
213     {
214       role = getRole("");
215     }
216 
217     setRole(client, role);
218   }
219 
220   /**
221   * Logs the user in.
222   * Params:
223   *   client =   The client.
224   *   loginTime = The time the user can be logged in. (In minutes)
225   *   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.)
226   */
227   void login(HttpClient client, long loginTime, Role role)
228   {
229     enforce(tokenSetter.f !is null || tokenSetter.d !is null, "No token setter found.");
230 
231     if (role !is null)
232     {
233       setSessionRole(client, role);
234     }
235 
236     client.session.updateEndTime(Clock.currTime() + loginTime.minutes);
237 
238     auto token = enforceInput(tokenSetter.getAndSetToken(client), "Could not set token.");
239 
240     client.cookies.create(HttpCookieType.functional, getAuthCookieKey(client), token, loginTime * 60);
241 
242     validateAuthentication(client);
243   }
244 
245   /**
246   * Logs the user out.
247   * Params:
248   *   client =  The client.
249   */
250   void logout(HttpClient client)
251   {
252     enforce(tokenInvalidator.f !is null || tokenInvalidator.d !is null, "No token invalidator found.");
253 
254     auto authCookieKey = getAuthCookieKey(client);
255 
256     client.session.clearValues();
257     client.cookies.remove(authCookieKey);
258     setRoleFromSession(client, false);
259 
260     auto token = client.cookies.get(authCookieKey);
261 
262     if (token)
263     {
264       tokenInvalidator.invalidate(token, client);
265     }
266   }
267 
268   /**
269   * Gets the auth cookie from a client.
270   * Params:
271   *   client = The client to get the auth cookie from.
272   * Returns:
273   *   Returns the auth cookie.
274   */
275   string getAuthCookie(HttpClient client)
276   {
277     return client.cookies.get(getAuthCookieKey(client));
278   }
279 
280   /**
281   * Checks whether the client has the auth cookie or not.
282   * Params:
283   *   client = The client.
284   * Returns:
285   *   True if the client has the auth cookie, false otherwise.
286   */
287   bool hasAuthCookie(HttpClient client)
288   {
289     return client.cookies.has(getAuthCookieKey(client));
290   }
291 }