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 }