1 use chrono::{Duration, NaiveDateTime, Utc}; 2 use serde_json::Value; 3 4 use crate::crypto; 5 use crate::CONFIG; 6 7 db_object! { 8 #[derive(Identifiable, Queryable, Insertable, AsChangeset)] 9 #[table_name = "users"] 10 #[changeset_options(treat_none_as_null="true")] 11 #[primary_key(uuid)] 12 pub struct User { 13 pub uuid: String, 14 pub enabled: bool, 15 pub created_at: NaiveDateTime, 16 pub updated_at: NaiveDateTime, 17 pub verified_at: Option<NaiveDateTime>, 18 pub last_verifying_at: Option<NaiveDateTime>, 19 pub login_verify_count: i32, 20 21 pub email: String, 22 pub email_new: Option<String>, 23 pub email_new_token: Option<String>, 24 pub name: String, 25 26 pub password_hash: Vec<u8>, 27 pub salt: Vec<u8>, 28 pub password_iterations: i32, 29 pub password_hint: Option<String>, 30 31 pub akey: String, 32 pub private_key: Option<String>, 33 pub public_key: Option<String>, 34 35 #[column_name = "totp_secret"] // Note, this is only added to the UserDb structs, not to User 36 _totp_secret: Option<String>, 37 pub totp_recover: Option<String>, 38 39 pub security_stamp: String, 40 pub stamp_exception: Option<String>, 41 42 pub equivalent_domains: String, 43 pub excluded_globals: String, 44 45 pub client_kdf_type: i32, 46 pub client_kdf_iter: i32, 47 } 48 49 50 #[derive(Identifiable, Queryable, Insertable)] 51 #[table_name = "invitations"] 52 #[primary_key(email)] 53 pub struct Invitation { 54 pub email: String, 55 } 56 } 57 58 enum UserStatus { 59 Enabled = 0, 60 Invited = 1, 61 _Disabled = 2, 62 } 63 64 #[derive(Serialize, Deserialize)] 65 pub struct UserStampException { 66 pub routes: Vec<String>, 67 pub security_stamp: String, 68 pub expire: i64, 69 } 70 71 /// Local methods 72 impl User { 73 pub const CLIENT_KDF_TYPE_DEFAULT: i32 = 0; // PBKDF2: 0 74 pub const CLIENT_KDF_ITER_DEFAULT: i32 = 100_000; 75 new(email: String) -> Self76 pub fn new(email: String) -> Self { 77 let now = Utc::now().naive_utc(); 78 let email = email.to_lowercase(); 79 80 Self { 81 uuid: crate::util::get_uuid(), 82 enabled: true, 83 created_at: now, 84 updated_at: now, 85 verified_at: None, 86 last_verifying_at: None, 87 login_verify_count: 0, 88 name: email.clone(), 89 email, 90 akey: String::new(), 91 email_new: None, 92 email_new_token: None, 93 94 password_hash: Vec::new(), 95 salt: crypto::get_random_64(), 96 password_iterations: CONFIG.password_iterations(), 97 98 security_stamp: crate::util::get_uuid(), 99 stamp_exception: None, 100 101 password_hint: None, 102 private_key: None, 103 public_key: None, 104 105 _totp_secret: None, 106 totp_recover: None, 107 108 equivalent_domains: "[]".to_string(), 109 excluded_globals: "[]".to_string(), 110 111 client_kdf_type: Self::CLIENT_KDF_TYPE_DEFAULT, 112 client_kdf_iter: Self::CLIENT_KDF_ITER_DEFAULT, 113 } 114 } 115 check_valid_password(&self, password: &str) -> bool116 pub fn check_valid_password(&self, password: &str) -> bool { 117 crypto::verify_password_hash( 118 password.as_bytes(), 119 &self.salt, 120 &self.password_hash, 121 self.password_iterations as u32, 122 ) 123 } 124 check_valid_recovery_code(&self, recovery_code: &str) -> bool125 pub fn check_valid_recovery_code(&self, recovery_code: &str) -> bool { 126 if let Some(ref totp_recover) = self.totp_recover { 127 crate::crypto::ct_eq(recovery_code, totp_recover.to_lowercase()) 128 } else { 129 false 130 } 131 } 132 133 /// Set the password hash generated 134 /// And resets the security_stamp. Based upon the allow_next_route the security_stamp will be different. 135 /// 136 /// # Arguments 137 /// 138 /// * `password` - A str which contains a hashed version of the users master password. 139 /// * `allow_next_route` - A Option<Vec<String>> with the function names of the next allowed (rocket) routes. 140 /// These routes are able to use the previous stamp id for the next 2 minutes. 141 /// After these 2 minutes this stamp will expire. 142 /// set_password(&mut self, password: &str, allow_next_route: Option<Vec<String>>)143 pub fn set_password(&mut self, password: &str, allow_next_route: Option<Vec<String>>) { 144 self.password_hash = crypto::hash_password(password.as_bytes(), &self.salt, self.password_iterations as u32); 145 146 if let Some(route) = allow_next_route { 147 self.set_stamp_exception(route); 148 } 149 150 self.reset_security_stamp() 151 } 152 reset_security_stamp(&mut self)153 pub fn reset_security_stamp(&mut self) { 154 self.security_stamp = crate::util::get_uuid(); 155 } 156 157 /// Set the stamp_exception to only allow a subsequent request matching a specific route using the current security-stamp. 158 /// 159 /// # Arguments 160 /// * `route_exception` - A Vec<String> with the function names of the next allowed (rocket) routes. 161 /// These routes are able to use the previous stamp id for the next 2 minutes. 162 /// After these 2 minutes this stamp will expire. 163 /// set_stamp_exception(&mut self, route_exception: Vec<String>)164 pub fn set_stamp_exception(&mut self, route_exception: Vec<String>) { 165 let stamp_exception = UserStampException { 166 routes: route_exception, 167 security_stamp: self.security_stamp.to_string(), 168 expire: (Utc::now().naive_utc() + Duration::minutes(2)).timestamp(), 169 }; 170 self.stamp_exception = Some(serde_json::to_string(&stamp_exception).unwrap_or_default()); 171 } 172 173 /// Resets the stamp_exception to prevent re-use of the previous security-stamp reset_stamp_exception(&mut self)174 pub fn reset_stamp_exception(&mut self) { 175 self.stamp_exception = None; 176 } 177 } 178 179 use super::{ 180 Cipher, Device, EmergencyAccess, Favorite, Folder, Send, TwoFactor, TwoFactorIncomplete, UserOrgType, 181 UserOrganization, 182 }; 183 use crate::db::DbConn; 184 185 use crate::api::EmptyResult; 186 use crate::error::MapResult; 187 188 /// Database methods 189 impl User { to_json(&self, conn: &DbConn) -> Value190 pub fn to_json(&self, conn: &DbConn) -> Value { 191 let orgs = UserOrganization::find_confirmed_by_user(&self.uuid, conn); 192 let orgs_json: Vec<Value> = orgs.iter().map(|c| c.to_json(conn)).collect(); 193 let twofactor_enabled = !TwoFactor::find_by_user(&self.uuid, conn).is_empty(); 194 195 // TODO: Might want to save the status field in the DB 196 let status = if self.password_hash.is_empty() { 197 UserStatus::Invited 198 } else { 199 UserStatus::Enabled 200 }; 201 202 json!({ 203 "_Status": status as i32, 204 "Id": self.uuid, 205 "Name": self.name, 206 "Email": self.email, 207 "EmailVerified": !CONFIG.mail_enabled() || self.verified_at.is_some(), 208 "Premium": true, 209 "MasterPasswordHint": self.password_hint, 210 "Culture": "en-US", 211 "TwoFactorEnabled": twofactor_enabled, 212 "Key": self.akey, 213 "PrivateKey": self.private_key, 214 "SecurityStamp": self.security_stamp, 215 "Organizations": orgs_json, 216 "Providers": [], 217 "ProviderOrganizations": [], 218 "ForcePasswordReset": false, 219 "Object": "profile", 220 }) 221 } 222 save(&mut self, conn: &DbConn) -> EmptyResult223 pub fn save(&mut self, conn: &DbConn) -> EmptyResult { 224 if self.email.trim().is_empty() { 225 err!("User email can't be empty") 226 } 227 228 self.updated_at = Utc::now().naive_utc(); 229 230 db_run! {conn: 231 sqlite, mysql { 232 match diesel::replace_into(users::table) 233 .values(UserDb::to_db(self)) 234 .execute(conn) 235 { 236 Ok(_) => Ok(()), 237 // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. 238 Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => { 239 diesel::update(users::table) 240 .filter(users::uuid.eq(&self.uuid)) 241 .set(UserDb::to_db(self)) 242 .execute(conn) 243 .map_res("Error saving user") 244 } 245 Err(e) => Err(e.into()), 246 }.map_res("Error saving user") 247 } 248 postgresql { 249 let value = UserDb::to_db(self); 250 diesel::insert_into(users::table) // Insert or update 251 .values(&value) 252 .on_conflict(users::uuid) 253 .do_update() 254 .set(&value) 255 .execute(conn) 256 .map_res("Error saving user") 257 } 258 } 259 } 260 delete(self, conn: &DbConn) -> EmptyResult261 pub fn delete(self, conn: &DbConn) -> EmptyResult { 262 for user_org in UserOrganization::find_confirmed_by_user(&self.uuid, conn) { 263 if user_org.atype == UserOrgType::Owner { 264 let owner_type = UserOrgType::Owner as i32; 265 if UserOrganization::find_by_org_and_type(&user_org.org_uuid, owner_type, conn).len() <= 1 { 266 err!("Can't delete last owner") 267 } 268 } 269 } 270 271 Send::delete_all_by_user(&self.uuid, conn)?; 272 EmergencyAccess::delete_all_by_user(&self.uuid, conn)?; 273 UserOrganization::delete_all_by_user(&self.uuid, conn)?; 274 Cipher::delete_all_by_user(&self.uuid, conn)?; 275 Favorite::delete_all_by_user(&self.uuid, conn)?; 276 Folder::delete_all_by_user(&self.uuid, conn)?; 277 Device::delete_all_by_user(&self.uuid, conn)?; 278 TwoFactor::delete_all_by_user(&self.uuid, conn)?; 279 TwoFactorIncomplete::delete_all_by_user(&self.uuid, conn)?; 280 Invitation::take(&self.email, conn); // Delete invitation if any 281 282 db_run! {conn: { 283 diesel::delete(users::table.filter(users::uuid.eq(self.uuid))) 284 .execute(conn) 285 .map_res("Error deleting user") 286 }} 287 } 288 update_uuid_revision(uuid: &str, conn: &DbConn)289 pub fn update_uuid_revision(uuid: &str, conn: &DbConn) { 290 if let Err(e) = Self::_update_revision(uuid, &Utc::now().naive_utc(), conn) { 291 warn!("Failed to update revision for {}: {:#?}", uuid, e); 292 } 293 } 294 update_all_revisions(conn: &DbConn) -> EmptyResult295 pub fn update_all_revisions(conn: &DbConn) -> EmptyResult { 296 let updated_at = Utc::now().naive_utc(); 297 298 db_run! {conn: { 299 crate::util::retry(|| { 300 diesel::update(users::table) 301 .set(users::updated_at.eq(updated_at)) 302 .execute(conn) 303 }, 10) 304 .map_res("Error updating revision date for all users") 305 }} 306 } 307 update_revision(&mut self, conn: &DbConn) -> EmptyResult308 pub fn update_revision(&mut self, conn: &DbConn) -> EmptyResult { 309 self.updated_at = Utc::now().naive_utc(); 310 311 Self::_update_revision(&self.uuid, &self.updated_at, conn) 312 } 313 _update_revision(uuid: &str, date: &NaiveDateTime, conn: &DbConn) -> EmptyResult314 fn _update_revision(uuid: &str, date: &NaiveDateTime, conn: &DbConn) -> EmptyResult { 315 db_run! {conn: { 316 crate::util::retry(|| { 317 diesel::update(users::table.filter(users::uuid.eq(uuid))) 318 .set(users::updated_at.eq(date)) 319 .execute(conn) 320 }, 10) 321 .map_res("Error updating user revision") 322 }} 323 } 324 find_by_mail(mail: &str, conn: &DbConn) -> Option<Self>325 pub fn find_by_mail(mail: &str, conn: &DbConn) -> Option<Self> { 326 let lower_mail = mail.to_lowercase(); 327 db_run! {conn: { 328 users::table 329 .filter(users::email.eq(lower_mail)) 330 .first::<UserDb>(conn) 331 .ok() 332 .from_db() 333 }} 334 } 335 find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self>336 pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> { 337 db_run! {conn: { 338 users::table.filter(users::uuid.eq(uuid)).first::<UserDb>(conn).ok().from_db() 339 }} 340 } 341 get_all(conn: &DbConn) -> Vec<Self>342 pub fn get_all(conn: &DbConn) -> Vec<Self> { 343 db_run! {conn: { 344 users::table.load::<UserDb>(conn).expect("Error loading users").from_db() 345 }} 346 } 347 last_active(&self, conn: &DbConn) -> Option<NaiveDateTime>348 pub fn last_active(&self, conn: &DbConn) -> Option<NaiveDateTime> { 349 match Device::find_latest_active_by_user(&self.uuid, conn) { 350 Some(device) => Some(device.updated_at), 351 None => None, 352 } 353 } 354 } 355 356 impl Invitation { new(email: String) -> Self357 pub fn new(email: String) -> Self { 358 let email = email.to_lowercase(); 359 Self { 360 email, 361 } 362 } 363 save(&self, conn: &DbConn) -> EmptyResult364 pub fn save(&self, conn: &DbConn) -> EmptyResult { 365 if self.email.trim().is_empty() { 366 err!("Invitation email can't be empty") 367 } 368 369 db_run! {conn: 370 sqlite, mysql { 371 // Not checking for ForeignKey Constraints here 372 // Table invitations does not have any ForeignKey Constraints. 373 diesel::replace_into(invitations::table) 374 .values(InvitationDb::to_db(self)) 375 .execute(conn) 376 .map_res("Error saving invitation") 377 } 378 postgresql { 379 diesel::insert_into(invitations::table) 380 .values(InvitationDb::to_db(self)) 381 .on_conflict(invitations::email) 382 .do_nothing() 383 .execute(conn) 384 .map_res("Error saving invitation") 385 } 386 } 387 } 388 delete(self, conn: &DbConn) -> EmptyResult389 pub fn delete(self, conn: &DbConn) -> EmptyResult { 390 db_run! {conn: { 391 diesel::delete(invitations::table.filter(invitations::email.eq(self.email))) 392 .execute(conn) 393 .map_res("Error deleting invitation") 394 }} 395 } 396 find_by_mail(mail: &str, conn: &DbConn) -> Option<Self>397 pub fn find_by_mail(mail: &str, conn: &DbConn) -> Option<Self> { 398 let lower_mail = mail.to_lowercase(); 399 db_run! {conn: { 400 invitations::table 401 .filter(invitations::email.eq(lower_mail)) 402 .first::<InvitationDb>(conn) 403 .ok() 404 .from_db() 405 }} 406 } 407 take(mail: &str, conn: &DbConn) -> bool408 pub fn take(mail: &str, conn: &DbConn) -> bool { 409 match Self::find_by_mail(mail, conn) { 410 Some(invitation) => invitation.delete(conn).is_ok(), 411 None => false, 412 } 413 } 414 } 415