1 /++ 2 A module containing a class that handles users data 3 4 Copyright: © 2018-2020 Szabo Bogdan 5 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 Authors: Szabo Bogdan 7 +/ 8 9 module vibeauth.data.user; 10 11 import vibeauth.data.usermodel; 12 import vibeauth.data.token; 13 14 import std.datetime; 15 import std.conv; 16 import std.algorithm; 17 import std.uuid; 18 import std.array; 19 20 import vibe.crypto.cryptorand; 21 22 import vibe.data.json; 23 24 /// Class used to manage one user 25 class User { 26 27 /// Event type raised when the user data has changed 28 alias ChangedEvent = void delegate(User); 29 30 /// Event raised when the user changed 31 ChangedEvent onChange; 32 33 private { 34 UserModel userData; 35 } 36 37 /// 38 this() { } 39 40 /// 41 this(UserModel userData) { 42 this.userData = userData; 43 } 44 45 /// 46 this(string email, string password) { 47 this.userData.email = email; 48 setPassword(password); 49 } 50 51 /// Convert the user object ot a Json pretty string 52 override string toString() { 53 return toJson.toPrettyString; 54 } 55 56 @property { 57 /// Get the user id 58 auto id() const { 59 return userData._id; 60 } 61 62 /// Set the user id 63 void id(ulong value) { 64 userData._id = value.to!string; 65 userData.lastActivity = Clock.currTime.toUnixTime!long; 66 67 if(onChange) { 68 onChange(this); 69 } 70 } 71 72 /// Check if the user is active 73 bool isActive() const { 74 return userData.isActive; 75 } 76 77 /// Check the user active status 78 void isActive(bool value) { 79 userData.isActive = value; 80 userData.lastActivity = Clock.currTime.toUnixTime!long; 81 82 if(onChange) { 83 onChange(this); 84 } 85 } 86 87 /// Get the user email 88 string email() const { 89 return userData.email; 90 } 91 92 /// Set the user email 93 void email(string value) { 94 userData.email = value; 95 userData.lastActivity = Clock.currTime.toUnixTime!long; 96 97 if(onChange) { 98 onChange(this); 99 } 100 } 101 102 /// Get the user title 103 auto salutation() const { 104 return userData.salutation; 105 } 106 107 /// Set the user title 108 void salutation(string value) { 109 userData.salutation = value; 110 userData.lastActivity = Clock.currTime.toUnixTime!long; 111 112 if(onChange) { 113 onChange(this); 114 } 115 } 116 117 /// Get the user title 118 auto title() const { 119 return userData.title; 120 } 121 122 /// Set the user title 123 void title(string value) { 124 userData.title = value; 125 userData.lastActivity = Clock.currTime.toUnixTime!long; 126 127 if(onChange) { 128 onChange(this); 129 } 130 } 131 132 /// Get the user first name 133 auto firstName() const { 134 return userData.firstName; 135 } 136 137 /// Set the user first name 138 void firstName(string value) { 139 userData.firstName = value; 140 userData.lastActivity = Clock.currTime.toUnixTime!long; 141 142 if(onChange) { 143 onChange(this); 144 } 145 } 146 147 /// Get the user last name 148 auto lastName() const { 149 return userData.lastName; 150 } 151 152 /// Set the user last name 153 void lastName(string value) { 154 userData.lastName = value; 155 userData.lastActivity = Clock.currTime.toUnixTime!long; 156 157 if(onChange) { 158 onChange(this); 159 } 160 } 161 162 /// Get the user alias name 163 auto username() const { 164 return userData.username; 165 } 166 167 /// Set the user alias name 168 void username(string value) { 169 userData.username = value; 170 userData.lastActivity = Clock.currTime.toUnixTime!long; 171 172 if(onChange) { 173 onChange(this); 174 } 175 } 176 177 /// Get the last user activity timestamp 178 auto lastActivity() const { 179 return userData.lastActivity; 180 } 181 182 /// Set the last user activity timestam[] 183 void lastActivity(ulong value) { 184 userData.lastActivity = value; 185 186 if(onChange) { 187 onChange(this); 188 } 189 } 190 } 191 192 /// Revoke a token 193 void revoke(string token) { 194 userData.tokens = userData.tokens.filter!(a => a.name != token).array; 195 userData.lastActivity = Clock.currTime.toUnixTime!long; 196 197 if(onChange) { 198 onChange(this); 199 } 200 } 201 202 const { 203 /// Get the user scopes assigned to a particullar token 204 string[] getScopes(string token) { 205 return userData.tokens.filter!(a => a.name == token).front.scopes.to!(string[]); 206 } 207 208 /// Get all user scopes 209 string[] getScopes() { 210 return userData.scopes.dup; 211 } 212 213 /// Check if an user can access a scope 214 bool can(string access)() { 215 return userData.scopes.canFind(access); 216 } 217 218 /// Get a range of tokens of a certain type 219 auto getTokensByType(string type) { 220 auto now = Clock.currTime; 221 return userData.tokens.filter!(a => a.type == type && a.expire > now); 222 } 223 224 /// Validate a password 225 bool isValidPassword(string password) { 226 return sha1UUID(userData.salt ~ "." ~ password).to!string == userData.password; 227 } 228 229 /// Validate a token 230 bool isValidToken(string token) { 231 auto now = Clock.currTime; 232 return userData.tokens.filter!(a => a.expire > now).map!(a => a.name).canFind(token); 233 } 234 235 /// Validate a token against a scope 236 bool isValidToken(string token, string requiredScope) { 237 auto now = Clock.currTime; 238 return userData.tokens.filter!(a => a.scopes.canFind(requiredScope) && a.expire > now).map!(a => a.name).canFind(token); 239 } 240 } 241 242 void removeExpiredTokens() { 243 auto now = Clock.currTime; 244 auto newTokenList = userData.tokens.filter!(a => a.expire > now).array; 245 246 if(newTokenList.length != userData.tokens.length) { 247 userData.tokens = newTokenList; 248 249 if(onChange) { 250 onChange(this); 251 } 252 } 253 } 254 255 /// Change the user password 256 void setPassword(string password) { 257 ubyte[16] secret; 258 secureRNG.read(secret[]); 259 auto uuid = UUID(secret); 260 261 userData.salt = uuid.to!string; 262 userData.password = sha1UUID(userData.salt ~ "." ~ password).to!string; 263 userData.lastActivity = Clock.currTime.toUnixTime!long; 264 265 if(onChange) { 266 onChange(this); 267 } 268 } 269 270 /// Change the user password by providing a salting string 271 void setPassword(string password, string salt) { 272 userData.salt = salt; 273 userData.password = password; 274 userData.lastActivity = Clock.currTime.toUnixTime!long; 275 276 if(onChange) { 277 onChange(this); 278 } 279 } 280 281 /// Add a scope to the user 282 void addScope(string access) { 283 userData.scopes ~= access; 284 userData.lastActivity = Clock.currTime.toUnixTime!long; 285 286 if(onChange) { 287 onChange(this); 288 } 289 } 290 291 /// Remove a scope from user 292 void removeScope(string access) { 293 userData.scopes = userData.scopes 294 .filter!(a => a != access).array; 295 userData.lastActivity = Clock.currTime.toUnixTime!long; 296 297 if(onChange) { 298 onChange(this); 299 } 300 } 301 302 /// Create an user token 303 Token createToken(SysTime expire, string[] scopes = [], string type = "Bearer", string[string] meta = null) { 304 ubyte[16] secret; 305 secureRNG.read(secret[]); 306 auto uuid = UUID(secret); 307 308 auto token = Token(uuid.to!string, expire, scopes, type, meta); 309 userData.tokens ~= token; 310 311 if(onChange) { 312 onChange(this); 313 } 314 315 return token; 316 } 317 318 /// Convert the object to a json. It's not safe to share this value 319 /// with the outside world. Use it to store the user to db. 320 Json toJson() const { 321 auto result = userData.serializeToJson; 322 323 result.remove("name"); 324 325 return result; 326 } 327 328 /// Convert the object to a json that can be shared with the outside world 329 Json toPublicJson() const { 330 Json data = Json.emptyObject; 331 332 data["id"] = id; 333 data["salutation"] = salutation; 334 data["title"] = title; 335 data["firstName"] = firstName; 336 data["lastName"] = lastName; 337 data["username"] = username; 338 data["email"] = email; 339 data["lastActivity"] = lastActivity; 340 data["scopes"] = Json.emptyArray; 341 342 foreach(s; userData.scopes) { 343 data["scopes"] ~= s; 344 } 345 346 return data; 347 } 348 349 /// Restore the user from a json value 350 static User fromJson(Json data) { 351 if(data["lastActivity"].type != Json.Type.Int) { 352 data["lastActivity"] = 0; 353 } 354 355 if(data["name"].type == Json.Type..string && data["name"] != "") { 356 data["firstName"] = data["name"]; 357 } 358 359 return new User(data.deserializeJson!UserModel); 360 } 361 }