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