mirror of
				https://github.com/dani-garcia/vaultwarden.git
				synced 2025-10-31 13:51:14 +00:00 
			
		
		
		
	Async/Awaited all db methods
This is a rather large PR which updates the async branch to have all the database methods as an async fn. Some iter/map logic needed to be changed to a stream::iter().then(), but besides that most changes were just adding async/await where needed.
This commit is contained in:
		
					parent
					
						
							
								2d5f172e77
							
						
					
				
			
			
				commit
				
					
						775d07e9a0
					
				
			
		
					 34 changed files with 1424 additions and 1198 deletions
				
			
		|  | @ -1,7 +1,7 @@ | |||
| version = "Two" | ||||
| edition = "2018" | ||||
| #version = "One" | ||||
| edition = "2021" | ||||
| max_width = 120 | ||||
| newline_style = "Unix" | ||||
| use_small_heuristics = "Off" | ||||
| struct_lit_single_line = false | ||||
| overflow_delimited_expr = true | ||||
| #struct_lit_single_line = false | ||||
| #overflow_delimited_expr = true | ||||
|  |  | |||
							
								
								
									
										147
									
								
								src/api/admin.rs
									
										
									
									
									
								
							
							
						
						
									
										147
									
								
								src/api/admin.rs
									
										
									
									
									
								
							|  | @ -25,6 +25,8 @@ use crate::{ | |||
|     CONFIG, VERSION, | ||||
| }; | ||||
| 
 | ||||
| use futures::{stream, stream::StreamExt}; | ||||
| 
 | ||||
| pub fn routes() -> Vec<Route> { | ||||
|     if !CONFIG.disable_admin_token() && !CONFIG.is_admin_token_set() { | ||||
|         return routes![admin_disabled]; | ||||
|  | @ -253,8 +255,8 @@ struct InviteData { | |||
|     email: String, | ||||
| } | ||||
| 
 | ||||
| fn get_user_or_404(uuid: &str, conn: &DbConn) -> ApiResult<User> { | ||||
|     if let Some(user) = User::find_by_uuid(uuid, conn) { | ||||
| async fn get_user_or_404(uuid: &str, conn: &DbConn) -> ApiResult<User> { | ||||
|     if let Some(user) = User::find_by_uuid(uuid, conn).await { | ||||
|         Ok(user) | ||||
|     } else { | ||||
|         err_code!("User doesn't exist", Status::NotFound.code); | ||||
|  | @ -262,30 +264,28 @@ fn get_user_or_404(uuid: &str, conn: &DbConn) -> ApiResult<User> { | |||
| } | ||||
| 
 | ||||
| #[post("/invite", data = "<data>")] | ||||
| fn invite_user(data: Json<InviteData>, _token: AdminToken, conn: DbConn) -> JsonResult { | ||||
| async fn invite_user(data: Json<InviteData>, _token: AdminToken, conn: DbConn) -> JsonResult { | ||||
|     let data: InviteData = data.into_inner(); | ||||
|     let email = data.email.clone(); | ||||
|     if User::find_by_mail(&data.email, &conn).is_some() { | ||||
|     if User::find_by_mail(&data.email, &conn).await.is_some() { | ||||
|         err_code!("User already exists", Status::Conflict.code) | ||||
|     } | ||||
| 
 | ||||
|     let mut user = User::new(email); | ||||
| 
 | ||||
|     // TODO: After try_blocks is stabilized, this can be made more readable
 | ||||
|     // See: https://github.com/rust-lang/rust/issues/31436
 | ||||
|     (|| { | ||||
|     async fn _generate_invite(user: &User, conn: &DbConn) -> EmptyResult { | ||||
|         if CONFIG.mail_enabled() { | ||||
|             mail::send_invite(&user.email, &user.uuid, None, None, &CONFIG.invitation_org_name(), None)?; | ||||
|             mail::send_invite(&user.email, &user.uuid, None, None, &CONFIG.invitation_org_name(), None) | ||||
|         } else { | ||||
|             let invitation = Invitation::new(user.email.clone()); | ||||
|             invitation.save(&conn)?; | ||||
|             invitation.save(conn).await | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|         user.save(&conn) | ||||
|     })() | ||||
|     .map_err(|e| e.with_code(Status::InternalServerError.code))?; | ||||
|     _generate_invite(&user, &conn).await.map_err(|e| e.with_code(Status::InternalServerError.code))?; | ||||
|     user.save(&conn).await.map_err(|e| e.with_code(Status::InternalServerError.code))?; | ||||
| 
 | ||||
|     Ok(Json(user.to_json(&conn))) | ||||
|     Ok(Json(user.to_json(&conn).await)) | ||||
| } | ||||
| 
 | ||||
| #[post("/test/smtp", data = "<data>")] | ||||
|  | @ -306,84 +306,90 @@ fn logout(cookies: &CookieJar, referer: Referer) -> Redirect { | |||
| } | ||||
| 
 | ||||
| #[get("/users")] | ||||
| fn get_users_json(_token: AdminToken, conn: DbConn) -> Json<Value> { | ||||
|     let users = User::get_all(&conn); | ||||
|     let users_json: Vec<Value> = users.iter().map(|u| u.to_json(&conn)).collect(); | ||||
| async fn get_users_json(_token: AdminToken, conn: DbConn) -> Json<Value> { | ||||
|     let users_json = stream::iter(User::get_all(&conn).await) | ||||
|         .then(|u| async { | ||||
|             let u = u; // Move out this single variable
 | ||||
|             u.to_json(&conn).await | ||||
|         }) | ||||
|         .collect::<Vec<Value>>() | ||||
|         .await; | ||||
| 
 | ||||
|     Json(Value::Array(users_json)) | ||||
| } | ||||
| 
 | ||||
| #[get("/users/overview")] | ||||
| fn users_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<String>> { | ||||
|     let users = User::get_all(&conn); | ||||
|     let dt_fmt = "%Y-%m-%d %H:%M:%S %Z"; | ||||
|     let users_json: Vec<Value> = users | ||||
|         .iter() | ||||
|         .map(|u| { | ||||
|             let mut usr = u.to_json(&conn); | ||||
|             usr["cipher_count"] = json!(Cipher::count_owned_by_user(&u.uuid, &conn)); | ||||
|             usr["attachment_count"] = json!(Attachment::count_by_user(&u.uuid, &conn)); | ||||
|             usr["attachment_size"] = json!(get_display_size(Attachment::size_by_user(&u.uuid, &conn) as i32)); | ||||
| async fn users_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<String>> { | ||||
|     const DT_FMT: &str = "%Y-%m-%d %H:%M:%S %Z"; | ||||
| 
 | ||||
|     let users_json = stream::iter(User::get_all(&conn).await) | ||||
|         .then(|u| async { | ||||
|             let u = u; // Move out this single variable
 | ||||
|             let mut usr = u.to_json(&conn).await; | ||||
|             usr["cipher_count"] = json!(Cipher::count_owned_by_user(&u.uuid, &conn).await); | ||||
|             usr["attachment_count"] = json!(Attachment::count_by_user(&u.uuid, &conn).await); | ||||
|             usr["attachment_size"] = json!(get_display_size(Attachment::size_by_user(&u.uuid, &conn).await as i32)); | ||||
|             usr["user_enabled"] = json!(u.enabled); | ||||
|             usr["created_at"] = json!(format_naive_datetime_local(&u.created_at, dt_fmt)); | ||||
|             usr["last_active"] = match u.last_active(&conn) { | ||||
|                 Some(dt) => json!(format_naive_datetime_local(&dt, dt_fmt)), | ||||
|             usr["created_at"] = json!(format_naive_datetime_local(&u.created_at, DT_FMT)); | ||||
|             usr["last_active"] = match u.last_active(&conn).await { | ||||
|                 Some(dt) => json!(format_naive_datetime_local(&dt, DT_FMT)), | ||||
|                 None => json!("Never"), | ||||
|             }; | ||||
|             usr | ||||
|         }) | ||||
|         .collect(); | ||||
|         .collect::<Vec<Value>>() | ||||
|         .await; | ||||
| 
 | ||||
|     let text = AdminTemplateData::with_data("admin/users", json!(users_json)).render()?; | ||||
|     Ok(Html(text)) | ||||
| } | ||||
| 
 | ||||
| #[get("/users/<uuid>")] | ||||
| fn get_user_json(uuid: String, _token: AdminToken, conn: DbConn) -> JsonResult { | ||||
|     let user = get_user_or_404(&uuid, &conn)?; | ||||
| async fn get_user_json(uuid: String, _token: AdminToken, conn: DbConn) -> JsonResult { | ||||
|     let user = get_user_or_404(&uuid, &conn).await?; | ||||
| 
 | ||||
|     Ok(Json(user.to_json(&conn))) | ||||
|     Ok(Json(user.to_json(&conn).await)) | ||||
| } | ||||
| 
 | ||||
| #[post("/users/<uuid>/delete")] | ||||
| fn delete_user(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult { | ||||
|     let user = get_user_or_404(&uuid, &conn)?; | ||||
|     user.delete(&conn) | ||||
| async fn delete_user(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult { | ||||
|     let user = get_user_or_404(&uuid, &conn).await?; | ||||
|     user.delete(&conn).await | ||||
| } | ||||
| 
 | ||||
| #[post("/users/<uuid>/deauth")] | ||||
| fn deauth_user(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult { | ||||
|     let mut user = get_user_or_404(&uuid, &conn)?; | ||||
|     Device::delete_all_by_user(&user.uuid, &conn)?; | ||||
| async fn deauth_user(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult { | ||||
|     let mut user = get_user_or_404(&uuid, &conn).await?; | ||||
|     Device::delete_all_by_user(&user.uuid, &conn).await?; | ||||
|     user.reset_security_stamp(); | ||||
| 
 | ||||
|     user.save(&conn) | ||||
|     user.save(&conn).await | ||||
| } | ||||
| 
 | ||||
| #[post("/users/<uuid>/disable")] | ||||
| fn disable_user(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult { | ||||
|     let mut user = get_user_or_404(&uuid, &conn)?; | ||||
|     Device::delete_all_by_user(&user.uuid, &conn)?; | ||||
| async fn disable_user(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult { | ||||
|     let mut user = get_user_or_404(&uuid, &conn).await?; | ||||
|     Device::delete_all_by_user(&user.uuid, &conn).await?; | ||||
|     user.reset_security_stamp(); | ||||
|     user.enabled = false; | ||||
| 
 | ||||
|     user.save(&conn) | ||||
|     user.save(&conn).await | ||||
| } | ||||
| 
 | ||||
| #[post("/users/<uuid>/enable")] | ||||
| fn enable_user(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult { | ||||
|     let mut user = get_user_or_404(&uuid, &conn)?; | ||||
| async fn enable_user(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult { | ||||
|     let mut user = get_user_or_404(&uuid, &conn).await?; | ||||
|     user.enabled = true; | ||||
| 
 | ||||
|     user.save(&conn) | ||||
|     user.save(&conn).await | ||||
| } | ||||
| 
 | ||||
| #[post("/users/<uuid>/remove-2fa")] | ||||
| fn remove_2fa(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult { | ||||
|     let mut user = get_user_or_404(&uuid, &conn)?; | ||||
|     TwoFactor::delete_all_by_user(&user.uuid, &conn)?; | ||||
| async fn remove_2fa(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult { | ||||
|     let mut user = get_user_or_404(&uuid, &conn).await?; | ||||
|     TwoFactor::delete_all_by_user(&user.uuid, &conn).await?; | ||||
|     user.totp_recover = None; | ||||
|     user.save(&conn) | ||||
|     user.save(&conn).await | ||||
| } | ||||
| 
 | ||||
| #[derive(Deserialize, Debug)] | ||||
|  | @ -394,10 +400,10 @@ struct UserOrgTypeData { | |||
| } | ||||
| 
 | ||||
| #[post("/users/org_type", data = "<data>")] | ||||
| fn update_user_org_type(data: Json<UserOrgTypeData>, _token: AdminToken, conn: DbConn) -> EmptyResult { | ||||
| async fn update_user_org_type(data: Json<UserOrgTypeData>, _token: AdminToken, conn: DbConn) -> EmptyResult { | ||||
|     let data: UserOrgTypeData = data.into_inner(); | ||||
| 
 | ||||
|     let mut user_to_edit = match UserOrganization::find_by_user_and_org(&data.user_uuid, &data.org_uuid, &conn) { | ||||
|     let mut user_to_edit = match UserOrganization::find_by_user_and_org(&data.user_uuid, &data.org_uuid, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("The specified user isn't member of the organization"), | ||||
|     }; | ||||
|  | @ -409,7 +415,8 @@ fn update_user_org_type(data: Json<UserOrgTypeData>, _token: AdminToken, conn: D | |||
| 
 | ||||
|     if user_to_edit.atype == UserOrgType::Owner && new_type != UserOrgType::Owner { | ||||
|         // Removing owner permmission, check that there are at least another owner
 | ||||
|         let num_owners = UserOrganization::find_by_org_and_type(&data.org_uuid, UserOrgType::Owner as i32, &conn).len(); | ||||
|         let num_owners = | ||||
|             UserOrganization::find_by_org_and_type(&data.org_uuid, UserOrgType::Owner as i32, &conn).await.len(); | ||||
| 
 | ||||
|         if num_owners <= 1 { | ||||
|             err!("Can't change the type of the last owner") | ||||
|  | @ -417,37 +424,37 @@ fn update_user_org_type(data: Json<UserOrgTypeData>, _token: AdminToken, conn: D | |||
|     } | ||||
| 
 | ||||
|     user_to_edit.atype = new_type as i32; | ||||
|     user_to_edit.save(&conn) | ||||
|     user_to_edit.save(&conn).await | ||||
| } | ||||
| 
 | ||||
| #[post("/users/update_revision")] | ||||
| fn update_revision_users(_token: AdminToken, conn: DbConn) -> EmptyResult { | ||||
|     User::update_all_revisions(&conn) | ||||
| async fn update_revision_users(_token: AdminToken, conn: DbConn) -> EmptyResult { | ||||
|     User::update_all_revisions(&conn).await | ||||
| } | ||||
| 
 | ||||
| #[get("/organizations/overview")] | ||||
| fn organizations_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<String>> { | ||||
|     let organizations = Organization::get_all(&conn); | ||||
|     let organizations_json: Vec<Value> = organizations | ||||
|         .iter() | ||||
|         .map(|o| { | ||||
| async fn organizations_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<String>> { | ||||
|     let organizations_json = stream::iter(Organization::get_all(&conn).await) | ||||
|         .then(|o| async { | ||||
|             let o = o; //Move out this single variable
 | ||||
|             let mut org = o.to_json(); | ||||
|             org["user_count"] = json!(UserOrganization::count_by_org(&o.uuid, &conn)); | ||||
|             org["cipher_count"] = json!(Cipher::count_by_org(&o.uuid, &conn)); | ||||
|             org["attachment_count"] = json!(Attachment::count_by_org(&o.uuid, &conn)); | ||||
|             org["attachment_size"] = json!(get_display_size(Attachment::size_by_org(&o.uuid, &conn) as i32)); | ||||
|             org["user_count"] = json!(UserOrganization::count_by_org(&o.uuid, &conn).await); | ||||
|             org["cipher_count"] = json!(Cipher::count_by_org(&o.uuid, &conn).await); | ||||
|             org["attachment_count"] = json!(Attachment::count_by_org(&o.uuid, &conn).await); | ||||
|             org["attachment_size"] = json!(get_display_size(Attachment::size_by_org(&o.uuid, &conn).await as i32)); | ||||
|             org | ||||
|         }) | ||||
|         .collect(); | ||||
|         .collect::<Vec<Value>>() | ||||
|         .await; | ||||
| 
 | ||||
|     let text = AdminTemplateData::with_data("admin/organizations", json!(organizations_json)).render()?; | ||||
|     Ok(Html(text)) | ||||
| } | ||||
| 
 | ||||
| #[post("/organizations/<uuid>/delete")] | ||||
| fn delete_organization(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult { | ||||
|     let org = Organization::find_by_uuid(&uuid, &conn).map_res("Organization doesn't exist")?; | ||||
|     org.delete(&conn) | ||||
| async fn delete_organization(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult { | ||||
|     let org = Organization::find_by_uuid(&uuid, &conn).await.map_res("Organization doesn't exist")?; | ||||
|     org.delete(&conn).await | ||||
| } | ||||
| 
 | ||||
| #[derive(Deserialize)] | ||||
|  |  | |||
|  | @ -63,11 +63,11 @@ struct KeysData { | |||
| } | ||||
| 
 | ||||
| #[post("/accounts/register", data = "<data>")] | ||||
| fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult { | ||||
| async fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult { | ||||
|     let data: RegisterData = data.into_inner().data; | ||||
|     let email = data.Email.to_lowercase(); | ||||
| 
 | ||||
|     let mut user = match User::find_by_mail(&email, &conn) { | ||||
|     let mut user = match User::find_by_mail(&email, &conn).await { | ||||
|         Some(user) => { | ||||
|             if !user.password_hash.is_empty() { | ||||
|                 if CONFIG.is_signup_allowed(&email) { | ||||
|  | @ -84,13 +84,13 @@ fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult { | |||
|                 } else { | ||||
|                     err!("Registration email does not match invite email") | ||||
|                 } | ||||
|             } else if Invitation::take(&email, &conn) { | ||||
|                 for mut user_org in UserOrganization::find_invited_by_user(&user.uuid, &conn).iter_mut() { | ||||
|             } else if Invitation::take(&email, &conn).await { | ||||
|                 for mut user_org in UserOrganization::find_invited_by_user(&user.uuid, &conn).await.iter_mut() { | ||||
|                     user_org.status = UserOrgStatus::Accepted as i32; | ||||
|                     user_org.save(&conn)?; | ||||
|                     user_org.save(&conn).await?; | ||||
|                 } | ||||
|                 user | ||||
|             } else if EmergencyAccess::find_invited_by_grantee_email(&email, &conn).is_some() { | ||||
|             } else if EmergencyAccess::find_invited_by_grantee_email(&email, &conn).await.is_some() { | ||||
|                 user | ||||
|             } else if CONFIG.is_signup_allowed(&email) { | ||||
|                 err!("Account with this email already exists") | ||||
|  | @ -102,7 +102,7 @@ fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult { | |||
|             // Order is important here; the invitation check must come first
 | ||||
|             // because the vaultwarden admin can invite anyone, regardless
 | ||||
|             // of other signup restrictions.
 | ||||
|             if Invitation::take(&email, &conn) || CONFIG.is_signup_allowed(&email) { | ||||
|             if Invitation::take(&email, &conn).await || CONFIG.is_signup_allowed(&email) { | ||||
|                 User::new(email.clone()) | ||||
|             } else { | ||||
|                 err!("Registration not allowed or user already exists") | ||||
|  | @ -111,7 +111,7 @@ fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult { | |||
|     }; | ||||
| 
 | ||||
|     // Make sure we don't leave a lingering invitation.
 | ||||
|     Invitation::take(&email, &conn); | ||||
|     Invitation::take(&email, &conn).await; | ||||
| 
 | ||||
|     if let Some(client_kdf_iter) = data.KdfIterations { | ||||
|         user.client_kdf_iter = client_kdf_iter; | ||||
|  | @ -150,12 +150,12 @@ fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     user.save(&conn) | ||||
|     user.save(&conn).await | ||||
| } | ||||
| 
 | ||||
| #[get("/accounts/profile")] | ||||
| fn profile(headers: Headers, conn: DbConn) -> Json<Value> { | ||||
|     Json(headers.user.to_json(&conn)) | ||||
| async fn profile(headers: Headers, conn: DbConn) -> Json<Value> { | ||||
|     Json(headers.user.to_json(&conn).await) | ||||
| } | ||||
| 
 | ||||
| #[derive(Deserialize, Debug)] | ||||
|  | @ -168,12 +168,12 @@ struct ProfileData { | |||
| } | ||||
| 
 | ||||
| #[put("/accounts/profile", data = "<data>")] | ||||
| fn put_profile(data: JsonUpcase<ProfileData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     post_profile(data, headers, conn) | ||||
| async fn put_profile(data: JsonUpcase<ProfileData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     post_profile(data, headers, conn).await | ||||
| } | ||||
| 
 | ||||
| #[post("/accounts/profile", data = "<data>")] | ||||
| fn post_profile(data: JsonUpcase<ProfileData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn post_profile(data: JsonUpcase<ProfileData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let data: ProfileData = data.into_inner().data; | ||||
| 
 | ||||
|     let mut user = headers.user; | ||||
|  | @ -183,13 +183,13 @@ fn post_profile(data: JsonUpcase<ProfileData>, headers: Headers, conn: DbConn) - | |||
|         Some(ref h) if h.is_empty() => None, | ||||
|         _ => data.MasterPasswordHint, | ||||
|     }; | ||||
|     user.save(&conn)?; | ||||
|     Ok(Json(user.to_json(&conn))) | ||||
|     user.save(&conn).await?; | ||||
|     Ok(Json(user.to_json(&conn).await)) | ||||
| } | ||||
| 
 | ||||
| #[get("/users/<uuid>/public-key")] | ||||
| fn get_public_keys(uuid: String, _headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let user = match User::find_by_uuid(&uuid, &conn) { | ||||
| async fn get_public_keys(uuid: String, _headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let user = match User::find_by_uuid(&uuid, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("User doesn't exist"), | ||||
|     }; | ||||
|  | @ -202,7 +202,7 @@ fn get_public_keys(uuid: String, _headers: Headers, conn: DbConn) -> JsonResult | |||
| } | ||||
| 
 | ||||
| #[post("/accounts/keys", data = "<data>")] | ||||
| fn post_keys(data: JsonUpcase<KeysData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn post_keys(data: JsonUpcase<KeysData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let data: KeysData = data.into_inner().data; | ||||
| 
 | ||||
|     let mut user = headers.user; | ||||
|  | @ -210,7 +210,7 @@ fn post_keys(data: JsonUpcase<KeysData>, headers: Headers, conn: DbConn) -> Json | |||
|     user.private_key = Some(data.EncryptedPrivateKey); | ||||
|     user.public_key = Some(data.PublicKey); | ||||
| 
 | ||||
|     user.save(&conn)?; | ||||
|     user.save(&conn).await?; | ||||
| 
 | ||||
|     Ok(Json(json!({ | ||||
|         "PrivateKey": user.private_key, | ||||
|  | @ -228,7 +228,7 @@ struct ChangePassData { | |||
| } | ||||
| 
 | ||||
| #[post("/accounts/password", data = "<data>")] | ||||
| fn post_password(data: JsonUpcase<ChangePassData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn post_password(data: JsonUpcase<ChangePassData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
|     let data: ChangePassData = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
| 
 | ||||
|  | @ -241,7 +241,7 @@ fn post_password(data: JsonUpcase<ChangePassData>, headers: Headers, conn: DbCon | |||
|         Some(vec![String::from("post_rotatekey"), String::from("get_contacts"), String::from("get_public_keys")]), | ||||
|     ); | ||||
|     user.akey = data.Key; | ||||
|     user.save(&conn) | ||||
|     user.save(&conn).await | ||||
| } | ||||
| 
 | ||||
| #[derive(Deserialize)] | ||||
|  | @ -256,7 +256,7 @@ struct ChangeKdfData { | |||
| } | ||||
| 
 | ||||
| #[post("/accounts/kdf", data = "<data>")] | ||||
| fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
|     let data: ChangeKdfData = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
| 
 | ||||
|  | @ -268,7 +268,7 @@ fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, conn: DbConn) -> | |||
|     user.client_kdf_type = data.Kdf; | ||||
|     user.set_password(&data.NewMasterPasswordHash, None); | ||||
|     user.akey = data.Key; | ||||
|     user.save(&conn) | ||||
|     user.save(&conn).await | ||||
| } | ||||
| 
 | ||||
| #[derive(Deserialize)] | ||||
|  | @ -291,7 +291,7 @@ struct KeyData { | |||
| } | ||||
| 
 | ||||
| #[post("/accounts/key", data = "<data>")] | ||||
| fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbConn, nt: Notify) -> EmptyResult { | ||||
| async fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult { | ||||
|     let data: KeyData = data.into_inner().data; | ||||
| 
 | ||||
|     if !headers.user.check_valid_password(&data.MasterPasswordHash) { | ||||
|  | @ -302,7 +302,7 @@ fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbConn, nt: | |||
| 
 | ||||
|     // Update folder data
 | ||||
|     for folder_data in data.Folders { | ||||
|         let mut saved_folder = match Folder::find_by_uuid(&folder_data.Id, &conn) { | ||||
|         let mut saved_folder = match Folder::find_by_uuid(&folder_data.Id, &conn).await { | ||||
|             Some(folder) => folder, | ||||
|             None => err!("Folder doesn't exist"), | ||||
|         }; | ||||
|  | @ -312,14 +312,14 @@ fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbConn, nt: | |||
|         } | ||||
| 
 | ||||
|         saved_folder.name = folder_data.Name; | ||||
|         saved_folder.save(&conn)? | ||||
|         saved_folder.save(&conn).await? | ||||
|     } | ||||
| 
 | ||||
|     // Update cipher data
 | ||||
|     use super::ciphers::update_cipher_from_data; | ||||
| 
 | ||||
|     for cipher_data in data.Ciphers { | ||||
|         let mut saved_cipher = match Cipher::find_by_uuid(cipher_data.Id.as_ref().unwrap(), &conn) { | ||||
|         let mut saved_cipher = match Cipher::find_by_uuid(cipher_data.Id.as_ref().unwrap(), &conn).await { | ||||
|             Some(cipher) => cipher, | ||||
|             None => err!("Cipher doesn't exist"), | ||||
|         }; | ||||
|  | @ -330,7 +330,7 @@ fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbConn, nt: | |||
| 
 | ||||
|         // Prevent triggering cipher updates via WebSockets by settings UpdateType::None
 | ||||
|         // The user sessions are invalidated because all the ciphers were re-encrypted and thus triggering an update could cause issues.
 | ||||
|         update_cipher_from_data(&mut saved_cipher, cipher_data, &headers, false, &conn, &nt, UpdateType::None)? | ||||
|         update_cipher_from_data(&mut saved_cipher, cipher_data, &headers, false, &conn, &nt, UpdateType::None).await? | ||||
|     } | ||||
| 
 | ||||
|     // Update user data
 | ||||
|  | @ -340,11 +340,11 @@ fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbConn, nt: | |||
|     user.private_key = Some(data.PrivateKey); | ||||
|     user.reset_security_stamp(); | ||||
| 
 | ||||
|     user.save(&conn) | ||||
|     user.save(&conn).await | ||||
| } | ||||
| 
 | ||||
| #[post("/accounts/security-stamp", data = "<data>")] | ||||
| fn post_sstamp(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn post_sstamp(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
|     let data: PasswordData = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
| 
 | ||||
|  | @ -352,9 +352,9 @@ fn post_sstamp(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) - | |||
|         err!("Invalid password") | ||||
|     } | ||||
| 
 | ||||
|     Device::delete_all_by_user(&user.uuid, &conn)?; | ||||
|     Device::delete_all_by_user(&user.uuid, &conn).await?; | ||||
|     user.reset_security_stamp(); | ||||
|     user.save(&conn) | ||||
|     user.save(&conn).await | ||||
| } | ||||
| 
 | ||||
| #[derive(Deserialize)] | ||||
|  | @ -365,7 +365,7 @@ struct EmailTokenData { | |||
| } | ||||
| 
 | ||||
| #[post("/accounts/email-token", data = "<data>")] | ||||
| fn post_email_token(data: JsonUpcase<EmailTokenData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn post_email_token(data: JsonUpcase<EmailTokenData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
|     let data: EmailTokenData = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
| 
 | ||||
|  | @ -373,7 +373,7 @@ fn post_email_token(data: JsonUpcase<EmailTokenData>, headers: Headers, conn: Db | |||
|         err!("Invalid password") | ||||
|     } | ||||
| 
 | ||||
|     if User::find_by_mail(&data.NewEmail, &conn).is_some() { | ||||
|     if User::find_by_mail(&data.NewEmail, &conn).await.is_some() { | ||||
|         err!("Email already in use"); | ||||
|     } | ||||
| 
 | ||||
|  | @ -391,7 +391,7 @@ fn post_email_token(data: JsonUpcase<EmailTokenData>, headers: Headers, conn: Db | |||
| 
 | ||||
|     user.email_new = Some(data.NewEmail); | ||||
|     user.email_new_token = Some(token); | ||||
|     user.save(&conn) | ||||
|     user.save(&conn).await | ||||
| } | ||||
| 
 | ||||
| #[derive(Deserialize)] | ||||
|  | @ -406,7 +406,7 @@ struct ChangeEmailData { | |||
| } | ||||
| 
 | ||||
| #[post("/accounts/email", data = "<data>")] | ||||
| fn post_email(data: JsonUpcase<ChangeEmailData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn post_email(data: JsonUpcase<ChangeEmailData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
|     let data: ChangeEmailData = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
| 
 | ||||
|  | @ -414,7 +414,7 @@ fn post_email(data: JsonUpcase<ChangeEmailData>, headers: Headers, conn: DbConn) | |||
|         err!("Invalid password") | ||||
|     } | ||||
| 
 | ||||
|     if User::find_by_mail(&data.NewEmail, &conn).is_some() { | ||||
|     if User::find_by_mail(&data.NewEmail, &conn).await.is_some() { | ||||
|         err!("Email already in use"); | ||||
|     } | ||||
| 
 | ||||
|  | @ -449,7 +449,7 @@ fn post_email(data: JsonUpcase<ChangeEmailData>, headers: Headers, conn: DbConn) | |||
|     user.set_password(&data.NewMasterPasswordHash, None); | ||||
|     user.akey = data.Key; | ||||
| 
 | ||||
|     user.save(&conn) | ||||
|     user.save(&conn).await | ||||
| } | ||||
| 
 | ||||
| #[post("/accounts/verify-email")] | ||||
|  | @ -475,10 +475,10 @@ struct VerifyEmailTokenData { | |||
| } | ||||
| 
 | ||||
| #[post("/accounts/verify-email-token", data = "<data>")] | ||||
| fn post_verify_email_token(data: JsonUpcase<VerifyEmailTokenData>, conn: DbConn) -> EmptyResult { | ||||
| async fn post_verify_email_token(data: JsonUpcase<VerifyEmailTokenData>, conn: DbConn) -> EmptyResult { | ||||
|     let data: VerifyEmailTokenData = data.into_inner().data; | ||||
| 
 | ||||
|     let mut user = match User::find_by_uuid(&data.UserId, &conn) { | ||||
|     let mut user = match User::find_by_uuid(&data.UserId, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("User doesn't exist"), | ||||
|     }; | ||||
|  | @ -493,7 +493,7 @@ fn post_verify_email_token(data: JsonUpcase<VerifyEmailTokenData>, conn: DbConn) | |||
|     user.verified_at = Some(Utc::now().naive_utc()); | ||||
|     user.last_verifying_at = None; | ||||
|     user.login_verify_count = 0; | ||||
|     if let Err(e) = user.save(&conn) { | ||||
|     if let Err(e) = user.save(&conn).await { | ||||
|         error!("Error saving email verification: {:#?}", e); | ||||
|     } | ||||
| 
 | ||||
|  | @ -507,13 +507,11 @@ struct DeleteRecoverData { | |||
| } | ||||
| 
 | ||||
| #[post("/accounts/delete-recover", data = "<data>")] | ||||
| fn post_delete_recover(data: JsonUpcase<DeleteRecoverData>, conn: DbConn) -> EmptyResult { | ||||
| async fn post_delete_recover(data: JsonUpcase<DeleteRecoverData>, conn: DbConn) -> EmptyResult { | ||||
|     let data: DeleteRecoverData = data.into_inner().data; | ||||
| 
 | ||||
|     let user = User::find_by_mail(&data.Email, &conn); | ||||
| 
 | ||||
|     if CONFIG.mail_enabled() { | ||||
|         if let Some(user) = user { | ||||
|         if let Some(user) = User::find_by_mail(&data.Email, &conn).await { | ||||
|             if let Err(e) = mail::send_delete_account(&user.email, &user.uuid) { | ||||
|                 error!("Error sending delete account email: {:#?}", e); | ||||
|             } | ||||
|  | @ -536,10 +534,10 @@ struct DeleteRecoverTokenData { | |||
| } | ||||
| 
 | ||||
| #[post("/accounts/delete-recover-token", data = "<data>")] | ||||
| fn post_delete_recover_token(data: JsonUpcase<DeleteRecoverTokenData>, conn: DbConn) -> EmptyResult { | ||||
| async fn post_delete_recover_token(data: JsonUpcase<DeleteRecoverTokenData>, conn: DbConn) -> EmptyResult { | ||||
|     let data: DeleteRecoverTokenData = data.into_inner().data; | ||||
| 
 | ||||
|     let user = match User::find_by_uuid(&data.UserId, &conn) { | ||||
|     let user = match User::find_by_uuid(&data.UserId, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("User doesn't exist"), | ||||
|     }; | ||||
|  | @ -551,16 +549,16 @@ fn post_delete_recover_token(data: JsonUpcase<DeleteRecoverTokenData>, conn: DbC | |||
|     if claims.sub != user.uuid { | ||||
|         err!("Invalid claim"); | ||||
|     } | ||||
|     user.delete(&conn) | ||||
|     user.delete(&conn).await | ||||
| } | ||||
| 
 | ||||
| #[post("/accounts/delete", data = "<data>")] | ||||
| fn post_delete_account(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
|     delete_account(data, headers, conn) | ||||
| async fn post_delete_account(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
|     delete_account(data, headers, conn).await | ||||
| } | ||||
| 
 | ||||
| #[delete("/accounts", data = "<data>")] | ||||
| fn delete_account(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn delete_account(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
|     let data: PasswordData = data.into_inner().data; | ||||
|     let user = headers.user; | ||||
| 
 | ||||
|  | @ -568,7 +566,7 @@ fn delete_account(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn | |||
|         err!("Invalid password") | ||||
|     } | ||||
| 
 | ||||
|     user.delete(&conn) | ||||
|     user.delete(&conn).await | ||||
| } | ||||
| 
 | ||||
| #[get("/accounts/revision-date")] | ||||
|  | @ -584,7 +582,7 @@ struct PasswordHintData { | |||
| } | ||||
| 
 | ||||
| #[post("/accounts/password-hint", data = "<data>")] | ||||
| fn password_hint(data: JsonUpcase<PasswordHintData>, conn: DbConn) -> EmptyResult { | ||||
| async fn password_hint(data: JsonUpcase<PasswordHintData>, conn: DbConn) -> EmptyResult { | ||||
|     if !CONFIG.mail_enabled() && !CONFIG.show_password_hint() { | ||||
|         err!("This server is not configured to provide password hints."); | ||||
|     } | ||||
|  | @ -594,7 +592,7 @@ fn password_hint(data: JsonUpcase<PasswordHintData>, conn: DbConn) -> EmptyResul | |||
|     let data: PasswordHintData = data.into_inner().data; | ||||
|     let email = &data.Email; | ||||
| 
 | ||||
|     match User::find_by_mail(email, &conn) { | ||||
|     match User::find_by_mail(email, &conn).await { | ||||
|         None => { | ||||
|             // To prevent user enumeration, act as if the user exists.
 | ||||
|             if CONFIG.mail_enabled() { | ||||
|  | @ -633,10 +631,10 @@ struct PreloginData { | |||
| } | ||||
| 
 | ||||
| #[post("/accounts/prelogin", data = "<data>")] | ||||
| fn prelogin(data: JsonUpcase<PreloginData>, conn: DbConn) -> Json<Value> { | ||||
| async fn prelogin(data: JsonUpcase<PreloginData>, conn: DbConn) -> Json<Value> { | ||||
|     let data: PreloginData = data.into_inner().data; | ||||
| 
 | ||||
|     let (kdf_type, kdf_iter) = match User::find_by_mail(&data.Email, &conn) { | ||||
|     let (kdf_type, kdf_iter) = match User::find_by_mail(&data.Email, &conn).await { | ||||
|         Some(user) => (user.client_kdf_type, user.client_kdf_iter), | ||||
|         None => (User::CLIENT_KDF_TYPE_DEFAULT, User::CLIENT_KDF_ITER_DEFAULT), | ||||
|     }; | ||||
|  | @ -666,7 +664,7 @@ fn verify_password(data: JsonUpcase<SecretVerificationRequest>, headers: Headers | |||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| fn _api_key(data: JsonUpcase<SecretVerificationRequest>, rotate: bool, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn _api_key(data: JsonUpcase<SecretVerificationRequest>, rotate: bool, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let data: SecretVerificationRequest = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
| 
 | ||||
|  | @ -676,7 +674,7 @@ fn _api_key(data: JsonUpcase<SecretVerificationRequest>, rotate: bool, headers: | |||
| 
 | ||||
|     if rotate || user.api_key.is_none() { | ||||
|         user.api_key = Some(crypto::generate_api_key()); | ||||
|         user.save(&conn).expect("Error saving API key"); | ||||
|         user.save(&conn).await.expect("Error saving API key"); | ||||
|     } | ||||
| 
 | ||||
|     Ok(Json(json!({ | ||||
|  | @ -686,11 +684,11 @@ fn _api_key(data: JsonUpcase<SecretVerificationRequest>, rotate: bool, headers: | |||
| } | ||||
| 
 | ||||
| #[post("/accounts/api-key", data = "<data>")] | ||||
| fn api_key(data: JsonUpcase<SecretVerificationRequest>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     _api_key(data, false, headers, conn) | ||||
| async fn api_key(data: JsonUpcase<SecretVerificationRequest>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     _api_key(data, false, headers, conn).await | ||||
| } | ||||
| 
 | ||||
| #[post("/accounts/rotate-api-key", data = "<data>")] | ||||
| fn rotate_api_key(data: JsonUpcase<SecretVerificationRequest>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     _api_key(data, true, headers, conn) | ||||
| async fn rotate_api_key(data: JsonUpcase<SecretVerificationRequest>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     _api_key(data, true, headers, conn).await | ||||
| } | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -11,6 +11,8 @@ use crate::{ | |||
|     mail, CONFIG, | ||||
| }; | ||||
| 
 | ||||
| use futures::{stream, stream::StreamExt}; | ||||
| 
 | ||||
| pub fn routes() -> Vec<Route> { | ||||
|     routes![ | ||||
|         get_contacts, | ||||
|  | @ -36,13 +38,17 @@ pub fn routes() -> Vec<Route> { | |||
| // region get
 | ||||
| 
 | ||||
| #[get("/emergency-access/trusted")] | ||||
| fn get_contacts(headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn get_contacts(headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     check_emergency_access_allowed()?; | ||||
| 
 | ||||
|     let emergency_access_list = EmergencyAccess::find_all_by_grantor_uuid(&headers.user.uuid, &conn); | ||||
| 
 | ||||
|     let emergency_access_list_json: Vec<Value> = | ||||
|         emergency_access_list.iter().map(|e| e.to_json_grantee_details(&conn)).collect(); | ||||
|     let emergency_access_list_json = | ||||
|         stream::iter(EmergencyAccess::find_all_by_grantor_uuid(&headers.user.uuid, &conn).await) | ||||
|             .then(|e| async { | ||||
|                 let e = e; // Move out this single variable
 | ||||
|                 e.to_json_grantee_details(&conn).await | ||||
|             }) | ||||
|             .collect::<Vec<Value>>() | ||||
|             .await; | ||||
| 
 | ||||
|     Ok(Json(json!({ | ||||
|       "Data": emergency_access_list_json, | ||||
|  | @ -52,13 +58,17 @@ fn get_contacts(headers: Headers, conn: DbConn) -> JsonResult { | |||
| } | ||||
| 
 | ||||
| #[get("/emergency-access/granted")] | ||||
| fn get_grantees(headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn get_grantees(headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     check_emergency_access_allowed()?; | ||||
| 
 | ||||
|     let emergency_access_list = EmergencyAccess::find_all_by_grantee_uuid(&headers.user.uuid, &conn); | ||||
| 
 | ||||
|     let emergency_access_list_json: Vec<Value> = | ||||
|         emergency_access_list.iter().map(|e| e.to_json_grantor_details(&conn)).collect(); | ||||
|     let emergency_access_list_json = | ||||
|         stream::iter(EmergencyAccess::find_all_by_grantee_uuid(&headers.user.uuid, &conn).await) | ||||
|             .then(|e| async { | ||||
|                 let e = e; // Move out this single variable
 | ||||
|                 e.to_json_grantor_details(&conn).await | ||||
|             }) | ||||
|             .collect::<Vec<Value>>() | ||||
|             .await; | ||||
| 
 | ||||
|     Ok(Json(json!({ | ||||
|       "Data": emergency_access_list_json, | ||||
|  | @ -68,11 +78,11 @@ fn get_grantees(headers: Headers, conn: DbConn) -> JsonResult { | |||
| } | ||||
| 
 | ||||
| #[get("/emergency-access/<emer_id>")] | ||||
| fn get_emergency_access(emer_id: String, conn: DbConn) -> JsonResult { | ||||
| async fn get_emergency_access(emer_id: String, conn: DbConn) -> JsonResult { | ||||
|     check_emergency_access_allowed()?; | ||||
| 
 | ||||
|     match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | ||||
|         Some(emergency_access) => Ok(Json(emergency_access.to_json_grantee_details(&conn))), | ||||
|     match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|         Some(emergency_access) => Ok(Json(emergency_access.to_json_grantee_details(&conn).await)), | ||||
|         None => err!("Emergency access not valid."), | ||||
|     } | ||||
| } | ||||
|  | @ -90,17 +100,25 @@ struct EmergencyAccessUpdateData { | |||
| } | ||||
| 
 | ||||
| #[put("/emergency-access/<emer_id>", data = "<data>")] | ||||
| fn put_emergency_access(emer_id: String, data: JsonUpcase<EmergencyAccessUpdateData>, conn: DbConn) -> JsonResult { | ||||
|     post_emergency_access(emer_id, data, conn) | ||||
| async fn put_emergency_access( | ||||
|     emer_id: String, | ||||
|     data: JsonUpcase<EmergencyAccessUpdateData>, | ||||
|     conn: DbConn, | ||||
| ) -> JsonResult { | ||||
|     post_emergency_access(emer_id, data, conn).await | ||||
| } | ||||
| 
 | ||||
| #[post("/emergency-access/<emer_id>", data = "<data>")] | ||||
| fn post_emergency_access(emer_id: String, data: JsonUpcase<EmergencyAccessUpdateData>, conn: DbConn) -> JsonResult { | ||||
| async fn post_emergency_access( | ||||
|     emer_id: String, | ||||
|     data: JsonUpcase<EmergencyAccessUpdateData>, | ||||
|     conn: DbConn, | ||||
| ) -> JsonResult { | ||||
|     check_emergency_access_allowed()?; | ||||
| 
 | ||||
|     let data: EmergencyAccessUpdateData = data.into_inner().data; | ||||
| 
 | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|         Some(emergency_access) => emergency_access, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
|  | @ -114,7 +132,7 @@ fn post_emergency_access(emer_id: String, data: JsonUpcase<EmergencyAccessUpdate | |||
|     emergency_access.wait_time_days = data.WaitTimeDays; | ||||
|     emergency_access.key_encrypted = data.KeyEncrypted; | ||||
| 
 | ||||
|     emergency_access.save(&conn)?; | ||||
|     emergency_access.save(&conn).await?; | ||||
|     Ok(Json(emergency_access.to_json())) | ||||
| } | ||||
| 
 | ||||
|  | @ -123,12 +141,12 @@ fn post_emergency_access(emer_id: String, data: JsonUpcase<EmergencyAccessUpdate | |||
| // region delete
 | ||||
| 
 | ||||
| #[delete("/emergency-access/<emer_id>")] | ||||
| fn delete_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn delete_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
|     check_emergency_access_allowed()?; | ||||
| 
 | ||||
|     let grantor_user = headers.user; | ||||
| 
 | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|         Some(emer) => { | ||||
|             if emer.grantor_uuid != grantor_user.uuid && emer.grantee_uuid != Some(grantor_user.uuid) { | ||||
|                 err!("Emergency access not valid.") | ||||
|  | @ -137,13 +155,13 @@ fn delete_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> E | |||
|         } | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
|     emergency_access.delete(&conn)?; | ||||
|     emergency_access.delete(&conn).await?; | ||||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| #[post("/emergency-access/<emer_id>/delete")] | ||||
| fn post_delete_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
|     delete_emergency_access(emer_id, headers, conn) | ||||
| async fn post_delete_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
|     delete_emergency_access(emer_id, headers, conn).await | ||||
| } | ||||
| 
 | ||||
| // endregion
 | ||||
|  | @ -159,7 +177,7 @@ struct EmergencyAccessInviteData { | |||
| } | ||||
| 
 | ||||
| #[post("/emergency-access/invite", data = "<data>")] | ||||
| fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
|     check_emergency_access_allowed()?; | ||||
| 
 | ||||
|     let data: EmergencyAccessInviteData = data.into_inner().data; | ||||
|  | @ -180,7 +198,7 @@ fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Headers, co | |||
|         err!("You can not set yourself as an emergency contact.") | ||||
|     } | ||||
| 
 | ||||
|     let grantee_user = match User::find_by_mail(&email, &conn) { | ||||
|     let grantee_user = match User::find_by_mail(&email, &conn).await { | ||||
|         None => { | ||||
|             if !CONFIG.invitations_allowed() { | ||||
|                 err!(format!("Grantee user does not exist: {}", email)) | ||||
|  | @ -192,11 +210,11 @@ fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Headers, co | |||
| 
 | ||||
|             if !CONFIG.mail_enabled() { | ||||
|                 let invitation = Invitation::new(email.clone()); | ||||
|                 invitation.save(&conn)?; | ||||
|                 invitation.save(&conn).await?; | ||||
|             } | ||||
| 
 | ||||
|             let mut user = User::new(email.clone()); | ||||
|             user.save(&conn)?; | ||||
|             user.save(&conn).await?; | ||||
|             user | ||||
|         } | ||||
|         Some(user) => user, | ||||
|  | @ -208,6 +226,7 @@ fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Headers, co | |||
|         &grantee_user.email, | ||||
|         &conn, | ||||
|     ) | ||||
|     .await | ||||
|     .is_some() | ||||
|     { | ||||
|         err!(format!("Grantee user already invited: {}", email)) | ||||
|  | @ -220,7 +239,7 @@ fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Headers, co | |||
|         new_type, | ||||
|         wait_time_days, | ||||
|     ); | ||||
|     new_emergency_access.save(&conn)?; | ||||
|     new_emergency_access.save(&conn).await?; | ||||
| 
 | ||||
|     if CONFIG.mail_enabled() { | ||||
|         mail::send_emergency_access_invite( | ||||
|  | @ -232,9 +251,9 @@ fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Headers, co | |||
|         )?; | ||||
|     } else { | ||||
|         // Automatically mark user as accepted if no email invites
 | ||||
|         match User::find_by_mail(&email, &conn) { | ||||
|         match User::find_by_mail(&email, &conn).await { | ||||
|             Some(user) => { | ||||
|                 match accept_invite_process(user.uuid, new_emergency_access.uuid, Some(email), conn.borrow()) { | ||||
|                 match accept_invite_process(user.uuid, new_emergency_access.uuid, Some(email), conn.borrow()).await { | ||||
|                     Ok(v) => (v), | ||||
|                     Err(e) => err!(e.to_string()), | ||||
|                 } | ||||
|  | @ -247,10 +266,10 @@ fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Headers, co | |||
| } | ||||
| 
 | ||||
| #[post("/emergency-access/<emer_id>/reinvite")] | ||||
| fn resend_invite(emer_id: String, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn resend_invite(emer_id: String, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
|     check_emergency_access_allowed()?; | ||||
| 
 | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
|  | @ -268,7 +287,7 @@ fn resend_invite(emer_id: String, headers: Headers, conn: DbConn) -> EmptyResult | |||
|         None => err!("Email not valid."), | ||||
|     }; | ||||
| 
 | ||||
|     let grantee_user = match User::find_by_mail(&email, &conn) { | ||||
|     let grantee_user = match User::find_by_mail(&email, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Grantee user not found."), | ||||
|     }; | ||||
|  | @ -284,13 +303,15 @@ fn resend_invite(emer_id: String, headers: Headers, conn: DbConn) -> EmptyResult | |||
|             Some(grantor_user.email), | ||||
|         )?; | ||||
|     } else { | ||||
|         if Invitation::find_by_mail(&email, &conn).is_none() { | ||||
|         if Invitation::find_by_mail(&email, &conn).await.is_none() { | ||||
|             let invitation = Invitation::new(email); | ||||
|             invitation.save(&conn)?; | ||||
|             invitation.save(&conn).await?; | ||||
|         } | ||||
| 
 | ||||
|         // Automatically mark user as accepted if no email invites
 | ||||
|         match accept_invite_process(grantee_user.uuid, emergency_access.uuid, emergency_access.email, conn.borrow()) { | ||||
|         match accept_invite_process(grantee_user.uuid, emergency_access.uuid, emergency_access.email, conn.borrow()) | ||||
|             .await | ||||
|         { | ||||
|             Ok(v) => (v), | ||||
|             Err(e) => err!(e.to_string()), | ||||
|         } | ||||
|  | @ -306,28 +327,28 @@ struct AcceptData { | |||
| } | ||||
| 
 | ||||
| #[post("/emergency-access/<emer_id>/accept", data = "<data>")] | ||||
| fn accept_invite(emer_id: String, data: JsonUpcase<AcceptData>, conn: DbConn) -> EmptyResult { | ||||
| async fn accept_invite(emer_id: String, data: JsonUpcase<AcceptData>, conn: DbConn) -> EmptyResult { | ||||
|     check_emergency_access_allowed()?; | ||||
| 
 | ||||
|     let data: AcceptData = data.into_inner().data; | ||||
|     let token = &data.Token; | ||||
|     let claims = decode_emergency_access_invite(token)?; | ||||
| 
 | ||||
|     let grantee_user = match User::find_by_mail(&claims.email, &conn) { | ||||
|     let grantee_user = match User::find_by_mail(&claims.email, &conn).await { | ||||
|         Some(user) => { | ||||
|             Invitation::take(&claims.email, &conn); | ||||
|             Invitation::take(&claims.email, &conn).await; | ||||
|             user | ||||
|         } | ||||
|         None => err!("Invited user not found"), | ||||
|     }; | ||||
| 
 | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
| 
 | ||||
|     // get grantor user to send Accepted email
 | ||||
|     let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn) { | ||||
|     let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Grantor user not found."), | ||||
|     }; | ||||
|  | @ -336,7 +357,7 @@ fn accept_invite(emer_id: String, data: JsonUpcase<AcceptData>, conn: DbConn) -> | |||
|         && (claims.grantor_name.is_some() && grantor_user.name == claims.grantor_name.unwrap()) | ||||
|         && (claims.grantor_email.is_some() && grantor_user.email == claims.grantor_email.unwrap()) | ||||
|     { | ||||
|         match accept_invite_process(grantee_user.uuid.clone(), emer_id, Some(grantee_user.email.clone()), &conn) { | ||||
|         match accept_invite_process(grantee_user.uuid.clone(), emer_id, Some(grantee_user.email.clone()), &conn).await { | ||||
|             Ok(v) => (v), | ||||
|             Err(e) => err!(e.to_string()), | ||||
|         } | ||||
|  | @ -351,8 +372,13 @@ fn accept_invite(emer_id: String, data: JsonUpcase<AcceptData>, conn: DbConn) -> | |||
|     } | ||||
| } | ||||
| 
 | ||||
| fn accept_invite_process(grantee_uuid: String, emer_id: String, email: Option<String>, conn: &DbConn) -> EmptyResult { | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, conn) { | ||||
| async fn accept_invite_process( | ||||
|     grantee_uuid: String, | ||||
|     emer_id: String, | ||||
|     email: Option<String>, | ||||
|     conn: &DbConn, | ||||
| ) -> EmptyResult { | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
|  | @ -369,7 +395,7 @@ fn accept_invite_process(grantee_uuid: String, emer_id: String, email: Option<St | |||
|     emergency_access.status = EmergencyAccessStatus::Accepted as i32; | ||||
|     emergency_access.grantee_uuid = Some(grantee_uuid); | ||||
|     emergency_access.email = None; | ||||
|     emergency_access.save(conn) | ||||
|     emergency_access.save(conn).await | ||||
| } | ||||
| 
 | ||||
| #[derive(Deserialize)] | ||||
|  | @ -379,7 +405,7 @@ struct ConfirmData { | |||
| } | ||||
| 
 | ||||
| #[post("/emergency-access/<emer_id>/confirm", data = "<data>")] | ||||
| fn confirm_emergency_access( | ||||
| async fn confirm_emergency_access( | ||||
|     emer_id: String, | ||||
|     data: JsonUpcase<ConfirmData>, | ||||
|     headers: Headers, | ||||
|  | @ -391,7 +417,7 @@ fn confirm_emergency_access( | |||
|     let data: ConfirmData = data.into_inner().data; | ||||
|     let key = data.Key; | ||||
| 
 | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
|  | @ -402,13 +428,13 @@ fn confirm_emergency_access( | |||
|         err!("Emergency access not valid.") | ||||
|     } | ||||
| 
 | ||||
|     let grantor_user = match User::find_by_uuid(&confirming_user.uuid, &conn) { | ||||
|     let grantor_user = match User::find_by_uuid(&confirming_user.uuid, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Grantor user not found."), | ||||
|     }; | ||||
| 
 | ||||
|     if let Some(grantee_uuid) = emergency_access.grantee_uuid.as_ref() { | ||||
|         let grantee_user = match User::find_by_uuid(grantee_uuid, &conn) { | ||||
|         let grantee_user = match User::find_by_uuid(grantee_uuid, &conn).await { | ||||
|             Some(user) => user, | ||||
|             None => err!("Grantee user not found."), | ||||
|         }; | ||||
|  | @ -417,7 +443,7 @@ fn confirm_emergency_access( | |||
|         emergency_access.key_encrypted = Some(key); | ||||
|         emergency_access.email = None; | ||||
| 
 | ||||
|         emergency_access.save(&conn)?; | ||||
|         emergency_access.save(&conn).await?; | ||||
| 
 | ||||
|         if CONFIG.mail_enabled() { | ||||
|             mail::send_emergency_access_invite_confirmed(&grantee_user.email, &grantor_user.name)?; | ||||
|  | @ -433,11 +459,11 @@ fn confirm_emergency_access( | |||
| // region access emergency access
 | ||||
| 
 | ||||
| #[post("/emergency-access/<emer_id>/initiate")] | ||||
| fn initiate_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn initiate_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     check_emergency_access_allowed()?; | ||||
| 
 | ||||
|     let initiating_user = headers.user; | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
|  | @ -448,7 +474,7 @@ fn initiate_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> | |||
|         err!("Emergency access not valid.") | ||||
|     } | ||||
| 
 | ||||
|     let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn) { | ||||
|     let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Grantor user not found."), | ||||
|     }; | ||||
|  | @ -458,7 +484,7 @@ fn initiate_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> | |||
|     emergency_access.updated_at = now; | ||||
|     emergency_access.recovery_initiated_at = Some(now); | ||||
|     emergency_access.last_notification_at = Some(now); | ||||
|     emergency_access.save(&conn)?; | ||||
|     emergency_access.save(&conn).await?; | ||||
| 
 | ||||
|     if CONFIG.mail_enabled() { | ||||
|         mail::send_emergency_access_recovery_initiated( | ||||
|  | @ -472,11 +498,11 @@ fn initiate_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> | |||
| } | ||||
| 
 | ||||
| #[post("/emergency-access/<emer_id>/approve")] | ||||
| fn approve_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn approve_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     check_emergency_access_allowed()?; | ||||
| 
 | ||||
|     let approving_user = headers.user; | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
|  | @ -487,19 +513,19 @@ fn approve_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> | |||
|         err!("Emergency access not valid.") | ||||
|     } | ||||
| 
 | ||||
|     let grantor_user = match User::find_by_uuid(&approving_user.uuid, &conn) { | ||||
|     let grantor_user = match User::find_by_uuid(&approving_user.uuid, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Grantor user not found."), | ||||
|     }; | ||||
| 
 | ||||
|     if let Some(grantee_uuid) = emergency_access.grantee_uuid.as_ref() { | ||||
|         let grantee_user = match User::find_by_uuid(grantee_uuid, &conn) { | ||||
|         let grantee_user = match User::find_by_uuid(grantee_uuid, &conn).await { | ||||
|             Some(user) => user, | ||||
|             None => err!("Grantee user not found."), | ||||
|         }; | ||||
| 
 | ||||
|         emergency_access.status = EmergencyAccessStatus::RecoveryApproved as i32; | ||||
|         emergency_access.save(&conn)?; | ||||
|         emergency_access.save(&conn).await?; | ||||
| 
 | ||||
|         if CONFIG.mail_enabled() { | ||||
|             mail::send_emergency_access_recovery_approved(&grantee_user.email, &grantor_user.name)?; | ||||
|  | @ -511,11 +537,11 @@ fn approve_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> | |||
| } | ||||
| 
 | ||||
| #[post("/emergency-access/<emer_id>/reject")] | ||||
| fn reject_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn reject_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     check_emergency_access_allowed()?; | ||||
| 
 | ||||
|     let rejecting_user = headers.user; | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
|  | @ -527,19 +553,19 @@ fn reject_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> J | |||
|         err!("Emergency access not valid.") | ||||
|     } | ||||
| 
 | ||||
|     let grantor_user = match User::find_by_uuid(&rejecting_user.uuid, &conn) { | ||||
|     let grantor_user = match User::find_by_uuid(&rejecting_user.uuid, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Grantor user not found."), | ||||
|     }; | ||||
| 
 | ||||
|     if let Some(grantee_uuid) = emergency_access.grantee_uuid.as_ref() { | ||||
|         let grantee_user = match User::find_by_uuid(grantee_uuid, &conn) { | ||||
|         let grantee_user = match User::find_by_uuid(grantee_uuid, &conn).await { | ||||
|             Some(user) => user, | ||||
|             None => err!("Grantee user not found."), | ||||
|         }; | ||||
| 
 | ||||
|         emergency_access.status = EmergencyAccessStatus::Confirmed as i32; | ||||
|         emergency_access.save(&conn)?; | ||||
|         emergency_access.save(&conn).await?; | ||||
| 
 | ||||
|         if CONFIG.mail_enabled() { | ||||
|             mail::send_emergency_access_recovery_rejected(&grantee_user.email, &grantor_user.name)?; | ||||
|  | @ -555,12 +581,12 @@ fn reject_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> J | |||
| // region action
 | ||||
| 
 | ||||
| #[post("/emergency-access/<emer_id>/view")] | ||||
| fn view_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn view_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     check_emergency_access_allowed()?; | ||||
| 
 | ||||
|     let requesting_user = headers.user; | ||||
|     let host = headers.host; | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
|  | @ -569,10 +595,13 @@ fn view_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> Jso | |||
|         err!("Emergency access not valid.") | ||||
|     } | ||||
| 
 | ||||
|     let ciphers = Cipher::find_owned_by_user(&emergency_access.grantor_uuid, &conn); | ||||
| 
 | ||||
|     let ciphers_json: Vec<Value> = | ||||
|         ciphers.iter().map(|c| c.to_json(&host, &emergency_access.grantor_uuid, &conn)).collect(); | ||||
|     let ciphers_json = stream::iter(Cipher::find_owned_by_user(&emergency_access.grantor_uuid, &conn).await) | ||||
|         .then(|c| async { | ||||
|             let c = c; // Move out this single variable
 | ||||
|             c.to_json(&host, &emergency_access.grantor_uuid, &conn).await | ||||
|         }) | ||||
|         .collect::<Vec<Value>>() | ||||
|         .await; | ||||
| 
 | ||||
|     Ok(Json(json!({ | ||||
|       "Ciphers": ciphers_json, | ||||
|  | @ -582,11 +611,11 @@ fn view_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> Jso | |||
| } | ||||
| 
 | ||||
| #[post("/emergency-access/<emer_id>/takeover")] | ||||
| fn takeover_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn takeover_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     check_emergency_access_allowed()?; | ||||
| 
 | ||||
|     let requesting_user = headers.user; | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
|  | @ -595,7 +624,7 @@ fn takeover_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> | |||
|         err!("Emergency access not valid.") | ||||
|     } | ||||
| 
 | ||||
|     let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn) { | ||||
|     let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Grantor user not found."), | ||||
|     }; | ||||
|  | @ -616,7 +645,7 @@ struct EmergencyAccessPasswordData { | |||
| } | ||||
| 
 | ||||
| #[post("/emergency-access/<emer_id>/password", data = "<data>")] | ||||
| fn password_emergency_access( | ||||
| async fn password_emergency_access( | ||||
|     emer_id: String, | ||||
|     data: JsonUpcase<EmergencyAccessPasswordData>, | ||||
|     headers: Headers, | ||||
|  | @ -629,7 +658,7 @@ fn password_emergency_access( | |||
|     let key = data.Key; | ||||
| 
 | ||||
|     let requesting_user = headers.user; | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
|  | @ -638,7 +667,7 @@ fn password_emergency_access( | |||
|         err!("Emergency access not valid.") | ||||
|     } | ||||
| 
 | ||||
|     let mut grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn) { | ||||
|     let mut grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Grantor user not found."), | ||||
|     }; | ||||
|  | @ -646,18 +675,15 @@ fn password_emergency_access( | |||
|     // change grantor_user password
 | ||||
|     grantor_user.set_password(new_master_password_hash, None); | ||||
|     grantor_user.akey = key; | ||||
|     grantor_user.save(&conn)?; | ||||
|     grantor_user.save(&conn).await?; | ||||
| 
 | ||||
|     // Disable TwoFactor providers since they will otherwise block logins
 | ||||
|     TwoFactor::delete_all_by_user(&grantor_user.uuid, &conn)?; | ||||
| 
 | ||||
|     // Removing owner, check that there are at least another owner
 | ||||
|     let user_org_grantor = UserOrganization::find_any_state_by_user(&grantor_user.uuid, &conn); | ||||
|     TwoFactor::delete_all_by_user(&grantor_user.uuid, &conn).await?; | ||||
| 
 | ||||
|     // Remove grantor from all organisations unless Owner
 | ||||
|     for user_org in user_org_grantor { | ||||
|     for user_org in UserOrganization::find_any_state_by_user(&grantor_user.uuid, &conn).await { | ||||
|         if user_org.atype != UserOrgType::Owner as i32 { | ||||
|             user_org.delete(&conn)?; | ||||
|             user_org.delete(&conn).await?; | ||||
|         } | ||||
|     } | ||||
|     Ok(()) | ||||
|  | @ -666,9 +692,9 @@ fn password_emergency_access( | |||
| // endregion
 | ||||
| 
 | ||||
| #[get("/emergency-access/<emer_id>/policies")] | ||||
| fn policies_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn policies_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let requesting_user = headers.user; | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
|  | @ -677,13 +703,13 @@ fn policies_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> | |||
|         err!("Emergency access not valid.") | ||||
|     } | ||||
| 
 | ||||
|     let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn) { | ||||
|     let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Grantor user not found."), | ||||
|     }; | ||||
| 
 | ||||
|     let policies = OrgPolicy::find_confirmed_by_user(&grantor_user.uuid, &conn); | ||||
|     let policies_json: Vec<Value> = policies.iter().map(OrgPolicy::to_json).collect(); | ||||
|     let policies_json: Vec<Value> = policies.await.iter().map(OrgPolicy::to_json).collect(); | ||||
| 
 | ||||
|     Ok(Json(json!({ | ||||
|         "Data": policies_json, | ||||
|  | @ -716,7 +742,7 @@ pub async fn emergency_request_timeout_job(pool: DbPool) { | |||
|     } | ||||
| 
 | ||||
|     if let Ok(conn) = pool.get().await { | ||||
|         let emergency_access_list = EmergencyAccess::find_all_recoveries(&conn); | ||||
|         let emergency_access_list = EmergencyAccess::find_all_recoveries(&conn).await; | ||||
| 
 | ||||
|         if emergency_access_list.is_empty() { | ||||
|             debug!("No emergency request timeout to approve"); | ||||
|  | @ -728,15 +754,17 @@ pub async fn emergency_request_timeout_job(pool: DbPool) { | |||
|                     >= emer.recovery_initiated_at.unwrap() + Duration::days(emer.wait_time_days as i64) | ||||
|             { | ||||
|                 emer.status = EmergencyAccessStatus::RecoveryApproved as i32; | ||||
|                 emer.save(&conn).expect("Cannot save emergency access on job"); | ||||
|                 emer.save(&conn).await.expect("Cannot save emergency access on job"); | ||||
| 
 | ||||
|                 if CONFIG.mail_enabled() { | ||||
|                     // get grantor user to send Accepted email
 | ||||
|                     let grantor_user = User::find_by_uuid(&emer.grantor_uuid, &conn).expect("Grantor user not found."); | ||||
|                     let grantor_user = | ||||
|                         User::find_by_uuid(&emer.grantor_uuid, &conn).await.expect("Grantor user not found."); | ||||
| 
 | ||||
|                     // get grantee user to send Accepted email
 | ||||
|                     let grantee_user = | ||||
|                         User::find_by_uuid(&emer.grantee_uuid.clone().expect("Grantee user invalid."), &conn) | ||||
|                             .await | ||||
|                             .expect("Grantee user not found."); | ||||
| 
 | ||||
|                     mail::send_emergency_access_recovery_timed_out( | ||||
|  | @ -763,7 +791,7 @@ pub async fn emergency_notification_reminder_job(pool: DbPool) { | |||
|     } | ||||
| 
 | ||||
|     if let Ok(conn) = pool.get().await { | ||||
|         let emergency_access_list = EmergencyAccess::find_all_recoveries(&conn); | ||||
|         let emergency_access_list = EmergencyAccess::find_all_recoveries(&conn).await; | ||||
| 
 | ||||
|         if emergency_access_list.is_empty() { | ||||
|             debug!("No emergency request reminder notification to send"); | ||||
|  | @ -777,15 +805,17 @@ pub async fn emergency_notification_reminder_job(pool: DbPool) { | |||
|                     || (emer.last_notification_at.is_some() | ||||
|                         && Utc::now().naive_utc() >= emer.last_notification_at.unwrap() + Duration::days(1))) | ||||
|             { | ||||
|                 emer.save(&conn).expect("Cannot save emergency access on job"); | ||||
|                 emer.save(&conn).await.expect("Cannot save emergency access on job"); | ||||
| 
 | ||||
|                 if CONFIG.mail_enabled() { | ||||
|                     // get grantor user to send Accepted email
 | ||||
|                     let grantor_user = User::find_by_uuid(&emer.grantor_uuid, &conn).expect("Grantor user not found."); | ||||
|                     let grantor_user = | ||||
|                         User::find_by_uuid(&emer.grantor_uuid, &conn).await.expect("Grantor user not found."); | ||||
| 
 | ||||
|                     // get grantee user to send Accepted email
 | ||||
|                     let grantee_user = | ||||
|                         User::find_by_uuid(&emer.grantee_uuid.clone().expect("Grantee user invalid."), &conn) | ||||
|                             .await | ||||
|                             .expect("Grantee user not found."); | ||||
| 
 | ||||
|                     mail::send_emergency_access_recovery_reminder( | ||||
|  |  | |||
|  | @ -12,9 +12,8 @@ pub fn routes() -> Vec<rocket::Route> { | |||
| } | ||||
| 
 | ||||
| #[get("/folders")] | ||||
| fn get_folders(headers: Headers, conn: DbConn) -> Json<Value> { | ||||
|     let folders = Folder::find_by_user(&headers.user.uuid, &conn); | ||||
| 
 | ||||
| async fn get_folders(headers: Headers, conn: DbConn) -> Json<Value> { | ||||
|     let folders = Folder::find_by_user(&headers.user.uuid, &conn).await; | ||||
|     let folders_json: Vec<Value> = folders.iter().map(Folder::to_json).collect(); | ||||
| 
 | ||||
|     Json(json!({ | ||||
|  | @ -25,8 +24,8 @@ fn get_folders(headers: Headers, conn: DbConn) -> Json<Value> { | |||
| } | ||||
| 
 | ||||
| #[get("/folders/<uuid>")] | ||||
| fn get_folder(uuid: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let folder = match Folder::find_by_uuid(&uuid, &conn) { | ||||
| async fn get_folder(uuid: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let folder = match Folder::find_by_uuid(&uuid, &conn).await { | ||||
|         Some(folder) => folder, | ||||
|         _ => err!("Invalid folder"), | ||||
|     }; | ||||
|  | @ -45,27 +44,39 @@ pub struct FolderData { | |||
| } | ||||
| 
 | ||||
| #[post("/folders", data = "<data>")] | ||||
| fn post_folders(data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { | ||||
| async fn post_folders(data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult { | ||||
|     let data: FolderData = data.into_inner().data; | ||||
| 
 | ||||
|     let mut folder = Folder::new(headers.user.uuid, data.Name); | ||||
| 
 | ||||
|     folder.save(&conn)?; | ||||
|     folder.save(&conn).await?; | ||||
|     nt.send_folder_update(UpdateType::FolderCreate, &folder); | ||||
| 
 | ||||
|     Ok(Json(folder.to_json())) | ||||
| } | ||||
| 
 | ||||
| #[post("/folders/<uuid>", data = "<data>")] | ||||
| fn post_folder(uuid: String, data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { | ||||
|     put_folder(uuid, data, headers, conn, nt) | ||||
| async fn post_folder( | ||||
|     uuid: String, | ||||
|     data: JsonUpcase<FolderData>, | ||||
|     headers: Headers, | ||||
|     conn: DbConn, | ||||
|     nt: Notify<'_>, | ||||
| ) -> JsonResult { | ||||
|     put_folder(uuid, data, headers, conn, nt).await | ||||
| } | ||||
| 
 | ||||
| #[put("/folders/<uuid>", data = "<data>")] | ||||
| fn put_folder(uuid: String, data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { | ||||
| async fn put_folder( | ||||
|     uuid: String, | ||||
|     data: JsonUpcase<FolderData>, | ||||
|     headers: Headers, | ||||
|     conn: DbConn, | ||||
|     nt: Notify<'_>, | ||||
| ) -> JsonResult { | ||||
|     let data: FolderData = data.into_inner().data; | ||||
| 
 | ||||
|     let mut folder = match Folder::find_by_uuid(&uuid, &conn) { | ||||
|     let mut folder = match Folder::find_by_uuid(&uuid, &conn).await { | ||||
|         Some(folder) => folder, | ||||
|         _ => err!("Invalid folder"), | ||||
|     }; | ||||
|  | @ -76,20 +87,20 @@ fn put_folder(uuid: String, data: JsonUpcase<FolderData>, headers: Headers, conn | |||
| 
 | ||||
|     folder.name = data.Name; | ||||
| 
 | ||||
|     folder.save(&conn)?; | ||||
|     folder.save(&conn).await?; | ||||
|     nt.send_folder_update(UpdateType::FolderUpdate, &folder); | ||||
| 
 | ||||
|     Ok(Json(folder.to_json())) | ||||
| } | ||||
| 
 | ||||
| #[post("/folders/<uuid>/delete")] | ||||
| fn delete_folder_post(uuid: String, headers: Headers, conn: DbConn, nt: Notify) -> EmptyResult { | ||||
|     delete_folder(uuid, headers, conn, nt) | ||||
| async fn delete_folder_post(uuid: String, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult { | ||||
|     delete_folder(uuid, headers, conn, nt).await | ||||
| } | ||||
| 
 | ||||
| #[delete("/folders/<uuid>")] | ||||
| fn delete_folder(uuid: String, headers: Headers, conn: DbConn, nt: Notify) -> EmptyResult { | ||||
|     let folder = match Folder::find_by_uuid(&uuid, &conn) { | ||||
| async fn delete_folder(uuid: String, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult { | ||||
|     let folder = match Folder::find_by_uuid(&uuid, &conn).await { | ||||
|         Some(folder) => folder, | ||||
|         _ => err!("Invalid folder"), | ||||
|     }; | ||||
|  | @ -99,7 +110,7 @@ fn delete_folder(uuid: String, headers: Headers, conn: DbConn, nt: Notify) -> Em | |||
|     } | ||||
| 
 | ||||
|     // Delete the actual folder entry
 | ||||
|     folder.delete(&conn)?; | ||||
|     folder.delete(&conn).await?; | ||||
| 
 | ||||
|     nt.send_folder_update(UpdateType::FolderDelete, &folder); | ||||
|     Ok(()) | ||||
|  |  | |||
|  | @ -121,7 +121,7 @@ struct EquivDomainData { | |||
| } | ||||
| 
 | ||||
| #[post("/settings/domains", data = "<data>")] | ||||
| fn post_eq_domains(data: JsonUpcase<EquivDomainData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn post_eq_domains(data: JsonUpcase<EquivDomainData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let data: EquivDomainData = data.into_inner().data; | ||||
| 
 | ||||
|     let excluded_globals = data.ExcludedGlobalEquivalentDomains.unwrap_or_default(); | ||||
|  | @ -133,14 +133,14 @@ fn post_eq_domains(data: JsonUpcase<EquivDomainData>, headers: Headers, conn: Db | |||
|     user.excluded_globals = to_string(&excluded_globals).unwrap_or_else(|_| "[]".to_string()); | ||||
|     user.equivalent_domains = to_string(&equivalent_domains).unwrap_or_else(|_| "[]".to_string()); | ||||
| 
 | ||||
|     user.save(&conn)?; | ||||
|     user.save(&conn).await?; | ||||
| 
 | ||||
|     Ok(Json(json!({}))) | ||||
| } | ||||
| 
 | ||||
| #[put("/settings/domains", data = "<data>")] | ||||
| fn put_eq_domains(data: JsonUpcase<EquivDomainData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     post_eq_domains(data, headers, conn) | ||||
| async fn put_eq_domains(data: JsonUpcase<EquivDomainData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     post_eq_domains(data, headers, conn).await | ||||
| } | ||||
| 
 | ||||
| #[get("/hibp/breach?<username>")] | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -35,7 +35,7 @@ pub fn routes() -> Vec<rocket::Route> { | |||
| pub async fn purge_sends(pool: DbPool) { | ||||
|     debug!("Purging sends"); | ||||
|     if let Ok(conn) = pool.get().await { | ||||
|         Send::purge(&conn); | ||||
|         Send::purge(&conn).await; | ||||
|     } else { | ||||
|         error!("Failed to get DB connection while purging sends") | ||||
|     } | ||||
|  | @ -68,10 +68,10 @@ struct SendData { | |||
| ///
 | ||||
| /// There is also a Vaultwarden-specific `sends_allowed` config setting that
 | ||||
| /// controls this policy globally.
 | ||||
| fn enforce_disable_send_policy(headers: &Headers, conn: &DbConn) -> EmptyResult { | ||||
| async fn enforce_disable_send_policy(headers: &Headers, conn: &DbConn) -> EmptyResult { | ||||
|     let user_uuid = &headers.user.uuid; | ||||
|     let policy_type = OrgPolicyType::DisableSend; | ||||
|     if !CONFIG.sends_allowed() || OrgPolicy::is_applicable_to_user(user_uuid, policy_type, conn) { | ||||
|     if !CONFIG.sends_allowed() || OrgPolicy::is_applicable_to_user(user_uuid, policy_type, conn).await { | ||||
|         err!("Due to an Enterprise Policy, you are only able to delete an existing Send.") | ||||
|     } | ||||
|     Ok(()) | ||||
|  | @ -83,10 +83,10 @@ fn enforce_disable_send_policy(headers: &Headers, conn: &DbConn) -> EmptyResult | |||
| /// but is allowed to remove this option from an existing Send.
 | ||||
| ///
 | ||||
| /// Ref: https://bitwarden.com/help/article/policies/#send-options
 | ||||
| fn enforce_disable_hide_email_policy(data: &SendData, headers: &Headers, conn: &DbConn) -> EmptyResult { | ||||
| async fn enforce_disable_hide_email_policy(data: &SendData, headers: &Headers, conn: &DbConn) -> EmptyResult { | ||||
|     let user_uuid = &headers.user.uuid; | ||||
|     let hide_email = data.HideEmail.unwrap_or(false); | ||||
|     if hide_email && OrgPolicy::is_hide_email_disabled(user_uuid, conn) { | ||||
|     if hide_email && OrgPolicy::is_hide_email_disabled(user_uuid, conn).await { | ||||
|         err!( | ||||
|             "Due to an Enterprise Policy, you are not allowed to hide your email address \ | ||||
|               from recipients when creating or editing a Send." | ||||
|  | @ -95,7 +95,7 @@ fn enforce_disable_hide_email_policy(data: &SendData, headers: &Headers, conn: & | |||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| fn create_send(data: SendData, user_uuid: String) -> ApiResult<Send> { | ||||
| async fn create_send(data: SendData, user_uuid: String) -> ApiResult<Send> { | ||||
|     let data_val = if data.Type == SendType::Text as i32 { | ||||
|         data.Text | ||||
|     } else if data.Type == SendType::File as i32 { | ||||
|  | @ -117,7 +117,7 @@ fn create_send(data: SendData, user_uuid: String) -> ApiResult<Send> { | |||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     let mut send = Send::new(data.Type, data.Name, data_str, data.Key, data.DeletionDate.naive_utc()); | ||||
|     let mut send = Send::new(data.Type, data.Name, data_str, data.Key, data.DeletionDate.naive_utc()).await; | ||||
|     send.user_uuid = Some(user_uuid); | ||||
|     send.notes = data.Notes; | ||||
|     send.max_access_count = match data.MaxAccessCount { | ||||
|  | @ -135,9 +135,9 @@ fn create_send(data: SendData, user_uuid: String) -> ApiResult<Send> { | |||
| } | ||||
| 
 | ||||
| #[get("/sends")] | ||||
| fn get_sends(headers: Headers, conn: DbConn) -> Json<Value> { | ||||
| async fn get_sends(headers: Headers, conn: DbConn) -> Json<Value> { | ||||
|     let sends = Send::find_by_user(&headers.user.uuid, &conn); | ||||
|     let sends_json: Vec<Value> = sends.iter().map(|s| s.to_json()).collect(); | ||||
|     let sends_json: Vec<Value> = sends.await.iter().map(|s| s.to_json()).collect(); | ||||
| 
 | ||||
|     Json(json!({ | ||||
|       "Data": sends_json, | ||||
|  | @ -147,8 +147,8 @@ fn get_sends(headers: Headers, conn: DbConn) -> Json<Value> { | |||
| } | ||||
| 
 | ||||
| #[get("/sends/<uuid>")] | ||||
| fn get_send(uuid: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let send = match Send::find_by_uuid(&uuid, &conn) { | ||||
| async fn get_send(uuid: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let send = match Send::find_by_uuid(&uuid, &conn).await { | ||||
|         Some(send) => send, | ||||
|         None => err!("Send not found"), | ||||
|     }; | ||||
|  | @ -161,19 +161,19 @@ fn get_send(uuid: String, headers: Headers, conn: DbConn) -> JsonResult { | |||
| } | ||||
| 
 | ||||
| #[post("/sends", data = "<data>")] | ||||
| fn post_send(data: JsonUpcase<SendData>, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { | ||||
|     enforce_disable_send_policy(&headers, &conn)?; | ||||
| async fn post_send(data: JsonUpcase<SendData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult { | ||||
|     enforce_disable_send_policy(&headers, &conn).await?; | ||||
| 
 | ||||
|     let data: SendData = data.into_inner().data; | ||||
|     enforce_disable_hide_email_policy(&data, &headers, &conn)?; | ||||
|     enforce_disable_hide_email_policy(&data, &headers, &conn).await?; | ||||
| 
 | ||||
|     if data.Type == SendType::File as i32 { | ||||
|         err!("File sends should use /api/sends/file") | ||||
|     } | ||||
| 
 | ||||
|     let mut send = create_send(data, headers.user.uuid)?; | ||||
|     send.save(&conn)?; | ||||
|     nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&conn)); | ||||
|     let mut send = create_send(data, headers.user.uuid).await?; | ||||
|     send.save(&conn).await?; | ||||
|     nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&conn).await); | ||||
| 
 | ||||
|     Ok(Json(send.to_json())) | ||||
| } | ||||
|  | @ -186,7 +186,7 @@ struct UploadData<'f> { | |||
| 
 | ||||
| #[post("/sends/file", format = "multipart/form-data", data = "<data>")] | ||||
| async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult { | ||||
|     enforce_disable_send_policy(&headers, &conn)?; | ||||
|     enforce_disable_send_policy(&headers, &conn).await?; | ||||
| 
 | ||||
|     let UploadData { | ||||
|         model, | ||||
|  | @ -194,7 +194,7 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbCo | |||
|     } = data.into_inner(); | ||||
|     let model = model.into_inner().data; | ||||
| 
 | ||||
|     enforce_disable_hide_email_policy(&model, &headers, &conn)?; | ||||
|     enforce_disable_hide_email_policy(&model, &headers, &conn).await?; | ||||
| 
 | ||||
|     // Get the file length and add an extra 5% to avoid issues
 | ||||
|     const SIZE_525_MB: u64 = 550_502_400; | ||||
|  | @ -202,7 +202,7 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbCo | |||
|     let size_limit = match CONFIG.user_attachment_limit() { | ||||
|         Some(0) => err!("File uploads are disabled"), | ||||
|         Some(limit_kb) => { | ||||
|             let left = (limit_kb * 1024) - Attachment::size_by_user(&headers.user.uuid, &conn); | ||||
|             let left = (limit_kb * 1024) - Attachment::size_by_user(&headers.user.uuid, &conn).await; | ||||
|             if left <= 0 { | ||||
|                 err!("Attachment storage limit reached! Delete some attachments to free up space") | ||||
|             } | ||||
|  | @ -211,7 +211,7 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbCo | |||
|         None => SIZE_525_MB, | ||||
|     }; | ||||
| 
 | ||||
|     let mut send = create_send(model, headers.user.uuid)?; | ||||
|     let mut send = create_send(model, headers.user.uuid).await?; | ||||
|     if send.atype != SendType::File as i32 { | ||||
|         err!("Send content is not a file"); | ||||
|     } | ||||
|  | @ -236,8 +236,8 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbCo | |||
|     send.data = serde_json::to_string(&data_value)?; | ||||
| 
 | ||||
|     // Save the changes in the database
 | ||||
|     send.save(&conn)?; | ||||
|     nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&conn)); | ||||
|     send.save(&conn).await?; | ||||
|     nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&conn).await); | ||||
| 
 | ||||
|     Ok(Json(send.to_json())) | ||||
| } | ||||
|  | @ -249,8 +249,8 @@ pub struct SendAccessData { | |||
| } | ||||
| 
 | ||||
| #[post("/sends/access/<access_id>", data = "<data>")] | ||||
| fn post_access(access_id: String, data: JsonUpcase<SendAccessData>, conn: DbConn) -> JsonResult { | ||||
|     let mut send = match Send::find_by_access_id(&access_id, &conn) { | ||||
| async fn post_access(access_id: String, data: JsonUpcase<SendAccessData>, conn: DbConn) -> JsonResult { | ||||
|     let mut send = match Send::find_by_access_id(&access_id, &conn).await { | ||||
|         Some(s) => s, | ||||
|         None => err_code!(SEND_INACCESSIBLE_MSG, 404), | ||||
|     }; | ||||
|  | @ -288,20 +288,20 @@ fn post_access(access_id: String, data: JsonUpcase<SendAccessData>, conn: DbConn | |||
|         send.access_count += 1; | ||||
|     } | ||||
| 
 | ||||
|     send.save(&conn)?; | ||||
|     send.save(&conn).await?; | ||||
| 
 | ||||
|     Ok(Json(send.to_json_access(&conn))) | ||||
|     Ok(Json(send.to_json_access(&conn).await)) | ||||
| } | ||||
| 
 | ||||
| #[post("/sends/<send_id>/access/file/<file_id>", data = "<data>")] | ||||
| fn post_access_file( | ||||
| async fn post_access_file( | ||||
|     send_id: String, | ||||
|     file_id: String, | ||||
|     data: JsonUpcase<SendAccessData>, | ||||
|     host: Host, | ||||
|     conn: DbConn, | ||||
| ) -> JsonResult { | ||||
|     let mut send = match Send::find_by_uuid(&send_id, &conn) { | ||||
|     let mut send = match Send::find_by_uuid(&send_id, &conn).await { | ||||
|         Some(s) => s, | ||||
|         None => err_code!(SEND_INACCESSIBLE_MSG, 404), | ||||
|     }; | ||||
|  | @ -336,7 +336,7 @@ fn post_access_file( | |||
| 
 | ||||
|     send.access_count += 1; | ||||
| 
 | ||||
|     send.save(&conn)?; | ||||
|     send.save(&conn).await?; | ||||
| 
 | ||||
|     let token_claims = crate::auth::generate_send_claims(&send_id, &file_id); | ||||
|     let token = crate::auth::encode_jwt(&token_claims); | ||||
|  | @ -358,13 +358,19 @@ async fn download_send(send_id: SafeString, file_id: SafeString, t: String) -> O | |||
| } | ||||
| 
 | ||||
| #[put("/sends/<id>", data = "<data>")] | ||||
| fn put_send(id: String, data: JsonUpcase<SendData>, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { | ||||
|     enforce_disable_send_policy(&headers, &conn)?; | ||||
| async fn put_send( | ||||
|     id: String, | ||||
|     data: JsonUpcase<SendData>, | ||||
|     headers: Headers, | ||||
|     conn: DbConn, | ||||
|     nt: Notify<'_>, | ||||
| ) -> JsonResult { | ||||
|     enforce_disable_send_policy(&headers, &conn).await?; | ||||
| 
 | ||||
|     let data: SendData = data.into_inner().data; | ||||
|     enforce_disable_hide_email_policy(&data, &headers, &conn)?; | ||||
|     enforce_disable_hide_email_policy(&data, &headers, &conn).await?; | ||||
| 
 | ||||
|     let mut send = match Send::find_by_uuid(&id, &conn) { | ||||
|     let mut send = match Send::find_by_uuid(&id, &conn).await { | ||||
|         Some(s) => s, | ||||
|         None => err!("Send not found"), | ||||
|     }; | ||||
|  | @ -411,15 +417,15 @@ fn put_send(id: String, data: JsonUpcase<SendData>, headers: Headers, conn: DbCo | |||
|         send.set_password(Some(&password)); | ||||
|     } | ||||
| 
 | ||||
|     send.save(&conn)?; | ||||
|     nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&conn)); | ||||
|     send.save(&conn).await?; | ||||
|     nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&conn).await); | ||||
| 
 | ||||
|     Ok(Json(send.to_json())) | ||||
| } | ||||
| 
 | ||||
| #[delete("/sends/<id>")] | ||||
| fn delete_send(id: String, headers: Headers, conn: DbConn, nt: Notify) -> EmptyResult { | ||||
|     let send = match Send::find_by_uuid(&id, &conn) { | ||||
| async fn delete_send(id: String, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult { | ||||
|     let send = match Send::find_by_uuid(&id, &conn).await { | ||||
|         Some(s) => s, | ||||
|         None => err!("Send not found"), | ||||
|     }; | ||||
|  | @ -428,17 +434,17 @@ fn delete_send(id: String, headers: Headers, conn: DbConn, nt: Notify) -> EmptyR | |||
|         err!("Send is not owned by user") | ||||
|     } | ||||
| 
 | ||||
|     send.delete(&conn)?; | ||||
|     nt.send_send_update(UpdateType::SyncSendDelete, &send, &send.update_users_revision(&conn)); | ||||
|     send.delete(&conn).await?; | ||||
|     nt.send_send_update(UpdateType::SyncSendDelete, &send, &send.update_users_revision(&conn).await); | ||||
| 
 | ||||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| #[put("/sends/<id>/remove-password")] | ||||
| fn put_remove_password(id: String, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { | ||||
|     enforce_disable_send_policy(&headers, &conn)?; | ||||
| async fn put_remove_password(id: String, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult { | ||||
|     enforce_disable_send_policy(&headers, &conn).await?; | ||||
| 
 | ||||
|     let mut send = match Send::find_by_uuid(&id, &conn) { | ||||
|     let mut send = match Send::find_by_uuid(&id, &conn).await { | ||||
|         Some(s) => s, | ||||
|         None => err!("Send not found"), | ||||
|     }; | ||||
|  | @ -448,8 +454,8 @@ fn put_remove_password(id: String, headers: Headers, conn: DbConn, nt: Notify) - | |||
|     } | ||||
| 
 | ||||
|     send.set_password(None); | ||||
|     send.save(&conn)?; | ||||
|     nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&conn)); | ||||
|     send.save(&conn).await?; | ||||
|     nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&conn).await); | ||||
| 
 | ||||
|     Ok(Json(send.to_json())) | ||||
| } | ||||
|  |  | |||
|  | @ -21,7 +21,7 @@ pub fn routes() -> Vec<Route> { | |||
| } | ||||
| 
 | ||||
| #[post("/two-factor/get-authenticator", data = "<data>")] | ||||
| fn generate_authenticator(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn generate_authenticator(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let data: PasswordData = data.into_inner().data; | ||||
|     let user = headers.user; | ||||
| 
 | ||||
|  | @ -30,7 +30,7 @@ fn generate_authenticator(data: JsonUpcase<PasswordData>, headers: Headers, conn | |||
|     } | ||||
| 
 | ||||
|     let type_ = TwoFactorType::Authenticator as i32; | ||||
|     let twofactor = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn); | ||||
|     let twofactor = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await; | ||||
| 
 | ||||
|     let (enabled, key) = match twofactor { | ||||
|         Some(tf) => (true, tf.data), | ||||
|  | @ -53,7 +53,7 @@ struct EnableAuthenticatorData { | |||
| } | ||||
| 
 | ||||
| #[post("/two-factor/authenticator", data = "<data>")] | ||||
| fn activate_authenticator( | ||||
| async fn activate_authenticator( | ||||
|     data: JsonUpcase<EnableAuthenticatorData>, | ||||
|     headers: Headers, | ||||
|     ip: ClientIp, | ||||
|  | @ -81,9 +81,9 @@ fn activate_authenticator( | |||
|     } | ||||
| 
 | ||||
|     // Validate the token provided with the key, and save new twofactor
 | ||||
|     validate_totp_code(&user.uuid, &token, &key.to_uppercase(), &ip, &conn)?; | ||||
|     validate_totp_code(&user.uuid, &token, &key.to_uppercase(), &ip, &conn).await?; | ||||
| 
 | ||||
|     _generate_recover_code(&mut user, &conn); | ||||
|     _generate_recover_code(&mut user, &conn).await; | ||||
| 
 | ||||
|     Ok(Json(json!({ | ||||
|         "Enabled": true, | ||||
|  | @ -93,16 +93,16 @@ fn activate_authenticator( | |||
| } | ||||
| 
 | ||||
| #[put("/two-factor/authenticator", data = "<data>")] | ||||
| fn activate_authenticator_put( | ||||
| async fn activate_authenticator_put( | ||||
|     data: JsonUpcase<EnableAuthenticatorData>, | ||||
|     headers: Headers, | ||||
|     ip: ClientIp, | ||||
|     conn: DbConn, | ||||
| ) -> JsonResult { | ||||
|     activate_authenticator(data, headers, ip, conn) | ||||
|     activate_authenticator(data, headers, ip, conn).await | ||||
| } | ||||
| 
 | ||||
| pub fn validate_totp_code_str( | ||||
| pub async fn validate_totp_code_str( | ||||
|     user_uuid: &str, | ||||
|     totp_code: &str, | ||||
|     secret: &str, | ||||
|  | @ -113,10 +113,16 @@ pub fn validate_totp_code_str( | |||
|         err!("TOTP code is not a number"); | ||||
|     } | ||||
| 
 | ||||
|     validate_totp_code(user_uuid, totp_code, secret, ip, conn) | ||||
|     validate_totp_code(user_uuid, totp_code, secret, ip, conn).await | ||||
| } | ||||
| 
 | ||||
| pub fn validate_totp_code(user_uuid: &str, totp_code: &str, secret: &str, ip: &ClientIp, conn: &DbConn) -> EmptyResult { | ||||
| pub async fn validate_totp_code( | ||||
|     user_uuid: &str, | ||||
|     totp_code: &str, | ||||
|     secret: &str, | ||||
|     ip: &ClientIp, | ||||
|     conn: &DbConn, | ||||
| ) -> EmptyResult { | ||||
|     use totp_lite::{totp_custom, Sha1}; | ||||
| 
 | ||||
|     let decoded_secret = match BASE32.decode(secret.as_bytes()) { | ||||
|  | @ -124,10 +130,11 @@ pub fn validate_totp_code(user_uuid: &str, totp_code: &str, secret: &str, ip: &C | |||
|         Err(_) => err!("Invalid TOTP secret"), | ||||
|     }; | ||||
| 
 | ||||
|     let mut twofactor = match TwoFactor::find_by_user_and_type(user_uuid, TwoFactorType::Authenticator as i32, conn) { | ||||
|         Some(tf) => tf, | ||||
|         _ => TwoFactor::new(user_uuid.to_string(), TwoFactorType::Authenticator, secret.to_string()), | ||||
|     }; | ||||
|     let mut twofactor = | ||||
|         match TwoFactor::find_by_user_and_type(user_uuid, TwoFactorType::Authenticator as i32, conn).await { | ||||
|             Some(tf) => tf, | ||||
|             _ => TwoFactor::new(user_uuid.to_string(), TwoFactorType::Authenticator, secret.to_string()), | ||||
|         }; | ||||
| 
 | ||||
|     // The amount of steps back and forward in time
 | ||||
|     // Also check if we need to disable time drifted TOTP codes.
 | ||||
|  | @ -156,7 +163,7 @@ pub fn validate_totp_code(user_uuid: &str, totp_code: &str, secret: &str, ip: &C | |||
|             // Save the last used time step so only totp time steps higher then this one are allowed.
 | ||||
|             // This will also save a newly created twofactor if the code is correct.
 | ||||
|             twofactor.last_used = time_step as i32; | ||||
|             twofactor.save(conn)?; | ||||
|             twofactor.save(conn).await?; | ||||
|             return Ok(()); | ||||
|         } else if generated == totp_code && time_step <= twofactor.last_used as i64 { | ||||
|             warn!("This TOTP or a TOTP code within {} steps back or forward has already been used!", steps); | ||||
|  |  | |||
|  | @ -89,14 +89,14 @@ impl DuoStatus { | |||
| const DISABLED_MESSAGE_DEFAULT: &str = "<To use the global Duo keys, please leave these fields untouched>"; | ||||
| 
 | ||||
| #[post("/two-factor/get-duo", data = "<data>")] | ||||
| fn get_duo(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn get_duo(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let data: PasswordData = data.into_inner().data; | ||||
| 
 | ||||
|     if !headers.user.check_valid_password(&data.MasterPasswordHash) { | ||||
|         err!("Invalid password"); | ||||
|     } | ||||
| 
 | ||||
|     let data = get_user_duo_data(&headers.user.uuid, &conn); | ||||
|     let data = get_user_duo_data(&headers.user.uuid, &conn).await; | ||||
| 
 | ||||
|     let (enabled, data) = match data { | ||||
|         DuoStatus::Global(_) => (true, Some(DuoData::secret())), | ||||
|  | @ -171,9 +171,9 @@ async fn activate_duo(data: JsonUpcase<EnableDuoData>, headers: Headers, conn: D | |||
| 
 | ||||
|     let type_ = TwoFactorType::Duo; | ||||
|     let twofactor = TwoFactor::new(user.uuid.clone(), type_, data_str); | ||||
|     twofactor.save(&conn)?; | ||||
|     twofactor.save(&conn).await?; | ||||
| 
 | ||||
|     _generate_recover_code(&mut user, &conn); | ||||
|     _generate_recover_code(&mut user, &conn).await; | ||||
| 
 | ||||
|     Ok(Json(json!({ | ||||
|         "Enabled": true, | ||||
|  | @ -223,11 +223,11 @@ const AUTH_PREFIX: &str = "AUTH"; | |||
| const DUO_PREFIX: &str = "TX"; | ||||
| const APP_PREFIX: &str = "APP"; | ||||
| 
 | ||||
| fn get_user_duo_data(uuid: &str, conn: &DbConn) -> DuoStatus { | ||||
| async fn get_user_duo_data(uuid: &str, conn: &DbConn) -> DuoStatus { | ||||
|     let type_ = TwoFactorType::Duo as i32; | ||||
| 
 | ||||
|     // If the user doesn't have an entry, disabled
 | ||||
|     let twofactor = match TwoFactor::find_by_user_and_type(uuid, type_, conn) { | ||||
|     let twofactor = match TwoFactor::find_by_user_and_type(uuid, type_, conn).await { | ||||
|         Some(t) => t, | ||||
|         None => return DuoStatus::Disabled(DuoData::global().is_some()), | ||||
|     }; | ||||
|  | @ -247,19 +247,20 @@ fn get_user_duo_data(uuid: &str, conn: &DbConn) -> DuoStatus { | |||
| } | ||||
| 
 | ||||
| // let (ik, sk, ak, host) = get_duo_keys();
 | ||||
| fn get_duo_keys_email(email: &str, conn: &DbConn) -> ApiResult<(String, String, String, String)> { | ||||
|     let data = User::find_by_mail(email, conn) | ||||
|         .and_then(|u| get_user_duo_data(&u.uuid, conn).data()) | ||||
|         .or_else(DuoData::global) | ||||
|         .map_res("Can't fetch Duo keys")?; | ||||
| async fn get_duo_keys_email(email: &str, conn: &DbConn) -> ApiResult<(String, String, String, String)> { | ||||
|     let data = match User::find_by_mail(email, conn).await { | ||||
|         Some(u) => get_user_duo_data(&u.uuid, conn).await.data(), | ||||
|         _ => DuoData::global(), | ||||
|     } | ||||
|     .map_res("Can't fetch Duo Keys")?; | ||||
| 
 | ||||
|     Ok((data.ik, data.sk, CONFIG.get_duo_akey(), data.host)) | ||||
| } | ||||
| 
 | ||||
| pub fn generate_duo_signature(email: &str, conn: &DbConn) -> ApiResult<(String, String)> { | ||||
| pub async fn generate_duo_signature(email: &str, conn: &DbConn) -> ApiResult<(String, String)> { | ||||
|     let now = Utc::now().timestamp(); | ||||
| 
 | ||||
|     let (ik, sk, ak, host) = get_duo_keys_email(email, conn)?; | ||||
|     let (ik, sk, ak, host) = get_duo_keys_email(email, conn).await?; | ||||
| 
 | ||||
|     let duo_sign = sign_duo_values(&sk, email, &ik, DUO_PREFIX, now + DUO_EXPIRE); | ||||
|     let app_sign = sign_duo_values(&ak, email, &ik, APP_PREFIX, now + APP_EXPIRE); | ||||
|  | @ -274,7 +275,7 @@ fn sign_duo_values(key: &str, email: &str, ikey: &str, prefix: &str, expire: i64 | |||
|     format!("{}|{}", cookie, crypto::hmac_sign(key, &cookie)) | ||||
| } | ||||
| 
 | ||||
| pub fn validate_duo_login(email: &str, response: &str, conn: &DbConn) -> EmptyResult { | ||||
| pub async fn validate_duo_login(email: &str, response: &str, conn: &DbConn) -> EmptyResult { | ||||
|     // email is as entered by the user, so it needs to be normalized before
 | ||||
|     // comparison with auth_user below.
 | ||||
|     let email = &email.to_lowercase(); | ||||
|  | @ -289,7 +290,7 @@ pub fn validate_duo_login(email: &str, response: &str, conn: &DbConn) -> EmptyRe | |||
| 
 | ||||
|     let now = Utc::now().timestamp(); | ||||
| 
 | ||||
|     let (ik, sk, ak, _host) = get_duo_keys_email(email, conn)?; | ||||
|     let (ik, sk, ak, _host) = get_duo_keys_email(email, conn).await?; | ||||
| 
 | ||||
|     let auth_user = parse_duo_values(&sk, auth_sig, &ik, AUTH_PREFIX, now)?; | ||||
|     let app_user = parse_duo_values(&ak, app_sig, &ik, APP_PREFIX, now)?; | ||||
|  |  | |||
|  | @ -28,13 +28,13 @@ struct SendEmailLoginData { | |||
| /// User is trying to login and wants to use email 2FA.
 | ||||
| /// Does not require Bearer token
 | ||||
| #[post("/two-factor/send-email-login", data = "<data>")] // JsonResult
 | ||||
| fn send_email_login(data: JsonUpcase<SendEmailLoginData>, conn: DbConn) -> EmptyResult { | ||||
| async fn send_email_login(data: JsonUpcase<SendEmailLoginData>, conn: DbConn) -> EmptyResult { | ||||
|     let data: SendEmailLoginData = data.into_inner().data; | ||||
| 
 | ||||
|     use crate::db::models::User; | ||||
| 
 | ||||
|     // Get the user
 | ||||
|     let user = match User::find_by_mail(&data.Email, &conn) { | ||||
|     let user = match User::find_by_mail(&data.Email, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Username or password is incorrect. Try again."), | ||||
|     }; | ||||
|  | @ -48,22 +48,23 @@ fn send_email_login(data: JsonUpcase<SendEmailLoginData>, conn: DbConn) -> Empty | |||
|         err!("Email 2FA is disabled") | ||||
|     } | ||||
| 
 | ||||
|     send_token(&user.uuid, &conn)?; | ||||
|     send_token(&user.uuid, &conn).await?; | ||||
| 
 | ||||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| /// Generate the token, save the data for later verification and send email to user
 | ||||
| pub fn send_token(user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
| pub async fn send_token(user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|     let type_ = TwoFactorType::Email as i32; | ||||
|     let mut twofactor = TwoFactor::find_by_user_and_type(user_uuid, type_, conn).map_res("Two factor not found")?; | ||||
|     let mut twofactor = | ||||
|         TwoFactor::find_by_user_and_type(user_uuid, type_, conn).await.map_res("Two factor not found")?; | ||||
| 
 | ||||
|     let generated_token = crypto::generate_email_token(CONFIG.email_token_size()); | ||||
| 
 | ||||
|     let mut twofactor_data = EmailTokenData::from_json(&twofactor.data)?; | ||||
|     twofactor_data.set_token(generated_token); | ||||
|     twofactor.data = twofactor_data.to_json(); | ||||
|     twofactor.save(conn)?; | ||||
|     twofactor.save(conn).await?; | ||||
| 
 | ||||
|     mail::send_token(&twofactor_data.email, &twofactor_data.last_token.map_res("Token is empty")?)?; | ||||
| 
 | ||||
|  | @ -72,7 +73,7 @@ pub fn send_token(user_uuid: &str, conn: &DbConn) -> EmptyResult { | |||
| 
 | ||||
| /// When user clicks on Manage email 2FA show the user the related information
 | ||||
| #[post("/two-factor/get-email", data = "<data>")] | ||||
| fn get_email(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn get_email(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let data: PasswordData = data.into_inner().data; | ||||
|     let user = headers.user; | ||||
| 
 | ||||
|  | @ -80,13 +81,14 @@ fn get_email(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> | |||
|         err!("Invalid password"); | ||||
|     } | ||||
| 
 | ||||
|     let (enabled, mfa_email) = match TwoFactor::find_by_user_and_type(&user.uuid, TwoFactorType::Email as i32, &conn) { | ||||
|         Some(x) => { | ||||
|             let twofactor_data = EmailTokenData::from_json(&x.data)?; | ||||
|             (true, json!(twofactor_data.email)) | ||||
|         } | ||||
|         _ => (false, json!(null)), | ||||
|     }; | ||||
|     let (enabled, mfa_email) = | ||||
|         match TwoFactor::find_by_user_and_type(&user.uuid, TwoFactorType::Email as i32, &conn).await { | ||||
|             Some(x) => { | ||||
|                 let twofactor_data = EmailTokenData::from_json(&x.data)?; | ||||
|                 (true, json!(twofactor_data.email)) | ||||
|             } | ||||
|             _ => (false, json!(null)), | ||||
|         }; | ||||
| 
 | ||||
|     Ok(Json(json!({ | ||||
|         "Email": mfa_email, | ||||
|  | @ -105,7 +107,7 @@ struct SendEmailData { | |||
| 
 | ||||
| /// Send a verification email to the specified email address to check whether it exists/belongs to user.
 | ||||
| #[post("/two-factor/send-email", data = "<data>")] | ||||
| fn send_email(data: JsonUpcase<SendEmailData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn send_email(data: JsonUpcase<SendEmailData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
|     let data: SendEmailData = data.into_inner().data; | ||||
|     let user = headers.user; | ||||
| 
 | ||||
|  | @ -119,8 +121,8 @@ fn send_email(data: JsonUpcase<SendEmailData>, headers: Headers, conn: DbConn) - | |||
| 
 | ||||
|     let type_ = TwoFactorType::Email as i32; | ||||
| 
 | ||||
|     if let Some(tf) = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn) { | ||||
|         tf.delete(&conn)?; | ||||
|     if let Some(tf) = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await { | ||||
|         tf.delete(&conn).await?; | ||||
|     } | ||||
| 
 | ||||
|     let generated_token = crypto::generate_email_token(CONFIG.email_token_size()); | ||||
|  | @ -128,7 +130,7 @@ fn send_email(data: JsonUpcase<SendEmailData>, headers: Headers, conn: DbConn) - | |||
| 
 | ||||
|     // Uses EmailVerificationChallenge as type to show that it's not verified yet.
 | ||||
|     let twofactor = TwoFactor::new(user.uuid, TwoFactorType::EmailVerificationChallenge, twofactor_data.to_json()); | ||||
|     twofactor.save(&conn)?; | ||||
|     twofactor.save(&conn).await?; | ||||
| 
 | ||||
|     mail::send_token(&twofactor_data.email, &twofactor_data.last_token.map_res("Token is empty")?)?; | ||||
| 
 | ||||
|  | @ -145,7 +147,7 @@ struct EmailData { | |||
| 
 | ||||
| /// Verify email belongs to user and can be used for 2FA email codes.
 | ||||
| #[put("/two-factor/email", data = "<data>")] | ||||
| fn email(data: JsonUpcase<EmailData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn email(data: JsonUpcase<EmailData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let data: EmailData = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
| 
 | ||||
|  | @ -154,7 +156,8 @@ fn email(data: JsonUpcase<EmailData>, headers: Headers, conn: DbConn) -> JsonRes | |||
|     } | ||||
| 
 | ||||
|     let type_ = TwoFactorType::EmailVerificationChallenge as i32; | ||||
|     let mut twofactor = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).map_res("Two factor not found")?; | ||||
|     let mut twofactor = | ||||
|         TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await.map_res("Two factor not found")?; | ||||
| 
 | ||||
|     let mut email_data = EmailTokenData::from_json(&twofactor.data)?; | ||||
| 
 | ||||
|  | @ -170,9 +173,9 @@ fn email(data: JsonUpcase<EmailData>, headers: Headers, conn: DbConn) -> JsonRes | |||
|     email_data.reset_token(); | ||||
|     twofactor.atype = TwoFactorType::Email as i32; | ||||
|     twofactor.data = email_data.to_json(); | ||||
|     twofactor.save(&conn)?; | ||||
|     twofactor.save(&conn).await?; | ||||
| 
 | ||||
|     _generate_recover_code(&mut user, &conn); | ||||
|     _generate_recover_code(&mut user, &conn).await; | ||||
| 
 | ||||
|     Ok(Json(json!({ | ||||
|         "Email": email_data.email, | ||||
|  | @ -182,9 +185,10 @@ fn email(data: JsonUpcase<EmailData>, headers: Headers, conn: DbConn) -> JsonRes | |||
| } | ||||
| 
 | ||||
| /// Validate the email code when used as TwoFactor token mechanism
 | ||||
| pub fn validate_email_code_str(user_uuid: &str, token: &str, data: &str, conn: &DbConn) -> EmptyResult { | ||||
| pub async fn validate_email_code_str(user_uuid: &str, token: &str, data: &str, conn: &DbConn) -> EmptyResult { | ||||
|     let mut email_data = EmailTokenData::from_json(data)?; | ||||
|     let mut twofactor = TwoFactor::find_by_user_and_type(user_uuid, TwoFactorType::Email as i32, conn) | ||||
|         .await | ||||
|         .map_res("Two factor not found")?; | ||||
|     let issued_token = match &email_data.last_token { | ||||
|         Some(t) => t, | ||||
|  | @ -197,14 +201,14 @@ pub fn validate_email_code_str(user_uuid: &str, token: &str, data: &str, conn: & | |||
|             email_data.reset_token(); | ||||
|         } | ||||
|         twofactor.data = email_data.to_json(); | ||||
|         twofactor.save(conn)?; | ||||
|         twofactor.save(conn).await?; | ||||
| 
 | ||||
|         err!("Token is invalid") | ||||
|     } | ||||
| 
 | ||||
|     email_data.reset_token(); | ||||
|     twofactor.data = email_data.to_json(); | ||||
|     twofactor.save(conn)?; | ||||
|     twofactor.save(conn).await?; | ||||
| 
 | ||||
|     let date = NaiveDateTime::from_timestamp(email_data.token_sent, 0); | ||||
|     let max_time = CONFIG.email_expiration_time() as i64; | ||||
|  |  | |||
|  | @ -33,8 +33,8 @@ pub fn routes() -> Vec<Route> { | |||
| } | ||||
| 
 | ||||
| #[get("/two-factor")] | ||||
| fn get_twofactor(headers: Headers, conn: DbConn) -> Json<Value> { | ||||
|     let twofactors = TwoFactor::find_by_user(&headers.user.uuid, &conn); | ||||
| async fn get_twofactor(headers: Headers, conn: DbConn) -> Json<Value> { | ||||
|     let twofactors = TwoFactor::find_by_user(&headers.user.uuid, &conn).await; | ||||
|     let twofactors_json: Vec<Value> = twofactors.iter().map(TwoFactor::to_json_provider).collect(); | ||||
| 
 | ||||
|     Json(json!({ | ||||
|  | @ -68,13 +68,13 @@ struct RecoverTwoFactor { | |||
| } | ||||
| 
 | ||||
| #[post("/two-factor/recover", data = "<data>")] | ||||
| fn recover(data: JsonUpcase<RecoverTwoFactor>, conn: DbConn) -> JsonResult { | ||||
| async fn recover(data: JsonUpcase<RecoverTwoFactor>, conn: DbConn) -> JsonResult { | ||||
|     let data: RecoverTwoFactor = data.into_inner().data; | ||||
| 
 | ||||
|     use crate::db::models::User; | ||||
| 
 | ||||
|     // Get the user
 | ||||
|     let mut user = match User::find_by_mail(&data.Email, &conn) { | ||||
|     let mut user = match User::find_by_mail(&data.Email, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Username or password is incorrect. Try again."), | ||||
|     }; | ||||
|  | @ -90,19 +90,19 @@ fn recover(data: JsonUpcase<RecoverTwoFactor>, conn: DbConn) -> JsonResult { | |||
|     } | ||||
| 
 | ||||
|     // Remove all twofactors from the user
 | ||||
|     TwoFactor::delete_all_by_user(&user.uuid, &conn)?; | ||||
|     TwoFactor::delete_all_by_user(&user.uuid, &conn).await?; | ||||
| 
 | ||||
|     // Remove the recovery code, not needed without twofactors
 | ||||
|     user.totp_recover = None; | ||||
|     user.save(&conn)?; | ||||
|     user.save(&conn).await?; | ||||
|     Ok(Json(json!({}))) | ||||
| } | ||||
| 
 | ||||
| fn _generate_recover_code(user: &mut User, conn: &DbConn) { | ||||
| async fn _generate_recover_code(user: &mut User, conn: &DbConn) { | ||||
|     if user.totp_recover.is_none() { | ||||
|         let totp_recover = BASE32.encode(&crypto::get_random(vec![0u8; 20])); | ||||
|         user.totp_recover = Some(totp_recover); | ||||
|         user.save(conn).ok(); | ||||
|         user.save(conn).await.ok(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -114,7 +114,7 @@ struct DisableTwoFactorData { | |||
| } | ||||
| 
 | ||||
| #[post("/two-factor/disable", data = "<data>")] | ||||
| fn disable_twofactor(data: JsonUpcase<DisableTwoFactorData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn disable_twofactor(data: JsonUpcase<DisableTwoFactorData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let data: DisableTwoFactorData = data.into_inner().data; | ||||
|     let password_hash = data.MasterPasswordHash; | ||||
|     let user = headers.user; | ||||
|  | @ -125,23 +125,24 @@ fn disable_twofactor(data: JsonUpcase<DisableTwoFactorData>, headers: Headers, c | |||
| 
 | ||||
|     let type_ = data.Type.into_i32()?; | ||||
| 
 | ||||
|     if let Some(twofactor) = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn) { | ||||
|         twofactor.delete(&conn)?; | ||||
|     if let Some(twofactor) = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await { | ||||
|         twofactor.delete(&conn).await?; | ||||
|     } | ||||
| 
 | ||||
|     let twofactor_disabled = TwoFactor::find_by_user(&user.uuid, &conn).is_empty(); | ||||
|     let twofactor_disabled = TwoFactor::find_by_user(&user.uuid, &conn).await.is_empty(); | ||||
| 
 | ||||
|     if twofactor_disabled { | ||||
|         let policy_type = OrgPolicyType::TwoFactorAuthentication; | ||||
|         let org_list = UserOrganization::find_by_user_and_policy(&user.uuid, policy_type, &conn); | ||||
| 
 | ||||
|         for user_org in org_list.into_iter() { | ||||
|         for user_org in | ||||
|             UserOrganization::find_by_user_and_policy(&user.uuid, OrgPolicyType::TwoFactorAuthentication, &conn) | ||||
|                 .await | ||||
|                 .into_iter() | ||||
|         { | ||||
|             if user_org.atype < UserOrgType::Admin { | ||||
|                 if CONFIG.mail_enabled() { | ||||
|                     let org = Organization::find_by_uuid(&user_org.org_uuid, &conn).unwrap(); | ||||
|                     let org = Organization::find_by_uuid(&user_org.org_uuid, &conn).await.unwrap(); | ||||
|                     mail::send_2fa_removed_from_org(&user.email, &org.name)?; | ||||
|                 } | ||||
|                 user_org.delete(&conn)?; | ||||
|                 user_org.delete(&conn).await?; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | @ -154,8 +155,8 @@ fn disable_twofactor(data: JsonUpcase<DisableTwoFactorData>, headers: Headers, c | |||
| } | ||||
| 
 | ||||
| #[put("/two-factor/disable", data = "<data>")] | ||||
| fn disable_twofactor_put(data: JsonUpcase<DisableTwoFactorData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     disable_twofactor(data, headers, conn) | ||||
| async fn disable_twofactor_put(data: JsonUpcase<DisableTwoFactorData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     disable_twofactor(data, headers, conn).await | ||||
| } | ||||
| 
 | ||||
| pub async fn send_incomplete_2fa_notifications(pool: DbPool) { | ||||
|  | @ -175,15 +176,16 @@ pub async fn send_incomplete_2fa_notifications(pool: DbPool) { | |||
| 
 | ||||
|     let now = Utc::now().naive_utc(); | ||||
|     let time_limit = Duration::minutes(CONFIG.incomplete_2fa_time_limit()); | ||||
|     let incomplete_logins = TwoFactorIncomplete::find_logins_before(&(now - time_limit), &conn); | ||||
|     let time_before = now - time_limit; | ||||
|     let incomplete_logins = TwoFactorIncomplete::find_logins_before(&time_before, &conn).await; | ||||
|     for login in incomplete_logins { | ||||
|         let user = User::find_by_uuid(&login.user_uuid, &conn).expect("User not found"); | ||||
|         let user = User::find_by_uuid(&login.user_uuid, &conn).await.expect("User not found"); | ||||
|         info!( | ||||
|             "User {} did not complete a 2FA login within the configured time limit. IP: {}", | ||||
|             user.email, login.ip_address | ||||
|         ); | ||||
|         mail::send_incomplete_2fa_login(&user.email, &login.ip_address, &login.login_time, &login.device_name) | ||||
|             .expect("Error sending incomplete 2FA email"); | ||||
|         login.delete(&conn).expect("Error deleting incomplete 2FA record"); | ||||
|         login.delete(&conn).await.expect("Error deleting incomplete 2FA record"); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -32,7 +32,7 @@ pub fn routes() -> Vec<Route> { | |||
| } | ||||
| 
 | ||||
| #[post("/two-factor/get-u2f", data = "<data>")] | ||||
| fn generate_u2f(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn generate_u2f(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     if !CONFIG.domain_set() { | ||||
|         err!("`DOMAIN` environment variable is not set. U2F disabled") | ||||
|     } | ||||
|  | @ -42,7 +42,7 @@ fn generate_u2f(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) | |||
|         err!("Invalid password"); | ||||
|     } | ||||
| 
 | ||||
|     let (enabled, keys) = get_u2f_registrations(&headers.user.uuid, &conn)?; | ||||
|     let (enabled, keys) = get_u2f_registrations(&headers.user.uuid, &conn).await?; | ||||
|     let keys_json: Vec<Value> = keys.iter().map(U2FRegistration::to_json).collect(); | ||||
| 
 | ||||
|     Ok(Json(json!({ | ||||
|  | @ -53,7 +53,7 @@ fn generate_u2f(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) | |||
| } | ||||
| 
 | ||||
| #[post("/two-factor/get-u2f-challenge", data = "<data>")] | ||||
| fn generate_u2f_challenge(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn generate_u2f_challenge(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let data: PasswordData = data.into_inner().data; | ||||
| 
 | ||||
|     if !headers.user.check_valid_password(&data.MasterPasswordHash) { | ||||
|  | @ -61,7 +61,7 @@ fn generate_u2f_challenge(data: JsonUpcase<PasswordData>, headers: Headers, conn | |||
|     } | ||||
| 
 | ||||
|     let _type = TwoFactorType::U2fRegisterChallenge; | ||||
|     let challenge = _create_u2f_challenge(&headers.user.uuid, _type, &conn).challenge; | ||||
|     let challenge = _create_u2f_challenge(&headers.user.uuid, _type, &conn).await.challenge; | ||||
| 
 | ||||
|     Ok(Json(json!({ | ||||
|         "UserId": headers.user.uuid, | ||||
|  | @ -137,7 +137,7 @@ impl From<RegisterResponseCopy> for RegisterResponse { | |||
| } | ||||
| 
 | ||||
| #[post("/two-factor/u2f", data = "<data>")] | ||||
| fn activate_u2f(data: JsonUpcase<EnableU2FData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn activate_u2f(data: JsonUpcase<EnableU2FData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let data: EnableU2FData = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
| 
 | ||||
|  | @ -146,13 +146,13 @@ fn activate_u2f(data: JsonUpcase<EnableU2FData>, headers: Headers, conn: DbConn) | |||
|     } | ||||
| 
 | ||||
|     let tf_type = TwoFactorType::U2fRegisterChallenge as i32; | ||||
|     let tf_challenge = match TwoFactor::find_by_user_and_type(&user.uuid, tf_type, &conn) { | ||||
|     let tf_challenge = match TwoFactor::find_by_user_and_type(&user.uuid, tf_type, &conn).await { | ||||
|         Some(c) => c, | ||||
|         None => err!("Can't recover challenge"), | ||||
|     }; | ||||
| 
 | ||||
|     let challenge: Challenge = serde_json::from_str(&tf_challenge.data)?; | ||||
|     tf_challenge.delete(&conn)?; | ||||
|     tf_challenge.delete(&conn).await?; | ||||
| 
 | ||||
|     let response: RegisterResponseCopy = serde_json::from_str(&data.DeviceResponse)?; | ||||
| 
 | ||||
|  | @ -172,13 +172,13 @@ fn activate_u2f(data: JsonUpcase<EnableU2FData>, headers: Headers, conn: DbConn) | |||
|         migrated: None, | ||||
|     }; | ||||
| 
 | ||||
|     let mut regs = get_u2f_registrations(&user.uuid, &conn)?.1; | ||||
|     let mut regs = get_u2f_registrations(&user.uuid, &conn).await?.1; | ||||
| 
 | ||||
|     // TODO: Check that there is no repeat Id
 | ||||
|     regs.push(full_registration); | ||||
|     save_u2f_registrations(&user.uuid, ®s, &conn)?; | ||||
|     save_u2f_registrations(&user.uuid, ®s, &conn).await?; | ||||
| 
 | ||||
|     _generate_recover_code(&mut user, &conn); | ||||
|     _generate_recover_code(&mut user, &conn).await; | ||||
| 
 | ||||
|     let keys_json: Vec<Value> = regs.iter().map(U2FRegistration::to_json).collect(); | ||||
|     Ok(Json(json!({ | ||||
|  | @ -189,8 +189,8 @@ fn activate_u2f(data: JsonUpcase<EnableU2FData>, headers: Headers, conn: DbConn) | |||
| } | ||||
| 
 | ||||
| #[put("/two-factor/u2f", data = "<data>")] | ||||
| fn activate_u2f_put(data: JsonUpcase<EnableU2FData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     activate_u2f(data, headers, conn) | ||||
| async fn activate_u2f_put(data: JsonUpcase<EnableU2FData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     activate_u2f(data, headers, conn).await | ||||
| } | ||||
| 
 | ||||
| #[derive(Deserialize, Debug)] | ||||
|  | @ -201,7 +201,7 @@ struct DeleteU2FData { | |||
| } | ||||
| 
 | ||||
| #[delete("/two-factor/u2f", data = "<data>")] | ||||
| fn delete_u2f(data: JsonUpcase<DeleteU2FData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn delete_u2f(data: JsonUpcase<DeleteU2FData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let data: DeleteU2FData = data.into_inner().data; | ||||
| 
 | ||||
|     let id = data.Id.into_i32()?; | ||||
|  | @ -211,7 +211,7 @@ fn delete_u2f(data: JsonUpcase<DeleteU2FData>, headers: Headers, conn: DbConn) - | |||
|     } | ||||
| 
 | ||||
|     let type_ = TwoFactorType::U2f as i32; | ||||
|     let mut tf = match TwoFactor::find_by_user_and_type(&headers.user.uuid, type_, &conn) { | ||||
|     let mut tf = match TwoFactor::find_by_user_and_type(&headers.user.uuid, type_, &conn).await { | ||||
|         Some(tf) => tf, | ||||
|         None => err!("U2F data not found!"), | ||||
|     }; | ||||
|  | @ -226,7 +226,7 @@ fn delete_u2f(data: JsonUpcase<DeleteU2FData>, headers: Headers, conn: DbConn) - | |||
|     let new_data_str = serde_json::to_string(&data)?; | ||||
| 
 | ||||
|     tf.data = new_data_str; | ||||
|     tf.save(&conn)?; | ||||
|     tf.save(&conn).await?; | ||||
| 
 | ||||
|     let keys_json: Vec<Value> = data.iter().map(U2FRegistration::to_json).collect(); | ||||
| 
 | ||||
|  | @ -237,23 +237,24 @@ fn delete_u2f(data: JsonUpcase<DeleteU2FData>, headers: Headers, conn: DbConn) - | |||
|     }))) | ||||
| } | ||||
| 
 | ||||
| fn _create_u2f_challenge(user_uuid: &str, type_: TwoFactorType, conn: &DbConn) -> Challenge { | ||||
| async fn _create_u2f_challenge(user_uuid: &str, type_: TwoFactorType, conn: &DbConn) -> Challenge { | ||||
|     let challenge = U2F.generate_challenge().unwrap(); | ||||
| 
 | ||||
|     TwoFactor::new(user_uuid.into(), type_, serde_json::to_string(&challenge).unwrap()) | ||||
|         .save(conn) | ||||
|         .await | ||||
|         .expect("Error saving challenge"); | ||||
| 
 | ||||
|     challenge | ||||
| } | ||||
| 
 | ||||
| fn save_u2f_registrations(user_uuid: &str, regs: &[U2FRegistration], conn: &DbConn) -> EmptyResult { | ||||
|     TwoFactor::new(user_uuid.into(), TwoFactorType::U2f, serde_json::to_string(regs)?).save(conn) | ||||
| async fn save_u2f_registrations(user_uuid: &str, regs: &[U2FRegistration], conn: &DbConn) -> EmptyResult { | ||||
|     TwoFactor::new(user_uuid.into(), TwoFactorType::U2f, serde_json::to_string(regs)?).save(conn).await | ||||
| } | ||||
| 
 | ||||
| fn get_u2f_registrations(user_uuid: &str, conn: &DbConn) -> Result<(bool, Vec<U2FRegistration>), Error> { | ||||
| async fn get_u2f_registrations(user_uuid: &str, conn: &DbConn) -> Result<(bool, Vec<U2FRegistration>), Error> { | ||||
|     let type_ = TwoFactorType::U2f as i32; | ||||
|     let (enabled, regs) = match TwoFactor::find_by_user_and_type(user_uuid, type_, conn) { | ||||
|     let (enabled, regs) = match TwoFactor::find_by_user_and_type(user_uuid, type_, conn).await { | ||||
|         Some(tf) => (tf.enabled, tf.data), | ||||
|         None => return Ok((false, Vec::new())), // If no data, return empty list
 | ||||
|     }; | ||||
|  | @ -279,7 +280,7 @@ fn get_u2f_registrations(user_uuid: &str, conn: &DbConn) -> Result<(bool, Vec<U2 | |||
|             }]; | ||||
| 
 | ||||
|             // Save new format
 | ||||
|             save_u2f_registrations(user_uuid, &new_regs, conn)?; | ||||
|             save_u2f_registrations(user_uuid, &new_regs, conn).await?; | ||||
| 
 | ||||
|             new_regs | ||||
|         } | ||||
|  | @ -297,10 +298,10 @@ fn _old_parse_registrations(registations: &str) -> Vec<Registration> { | |||
|     regs.into_iter().map(|r| serde_json::from_value(r).unwrap()).map(|Helper(r)| r).collect() | ||||
| } | ||||
| 
 | ||||
| pub fn generate_u2f_login(user_uuid: &str, conn: &DbConn) -> ApiResult<U2fSignRequest> { | ||||
|     let challenge = _create_u2f_challenge(user_uuid, TwoFactorType::U2fLoginChallenge, conn); | ||||
| pub async fn generate_u2f_login(user_uuid: &str, conn: &DbConn) -> ApiResult<U2fSignRequest> { | ||||
|     let challenge = _create_u2f_challenge(user_uuid, TwoFactorType::U2fLoginChallenge, conn).await; | ||||
| 
 | ||||
|     let registrations: Vec<_> = get_u2f_registrations(user_uuid, conn)?.1.into_iter().map(|r| r.reg).collect(); | ||||
|     let registrations: Vec<_> = get_u2f_registrations(user_uuid, conn).await?.1.into_iter().map(|r| r.reg).collect(); | ||||
| 
 | ||||
|     if registrations.is_empty() { | ||||
|         err!("No U2F devices registered") | ||||
|  | @ -309,20 +310,20 @@ pub fn generate_u2f_login(user_uuid: &str, conn: &DbConn) -> ApiResult<U2fSignRe | |||
|     Ok(U2F.sign_request(challenge, registrations)) | ||||
| } | ||||
| 
 | ||||
| pub fn validate_u2f_login(user_uuid: &str, response: &str, conn: &DbConn) -> EmptyResult { | ||||
| pub async fn validate_u2f_login(user_uuid: &str, response: &str, conn: &DbConn) -> EmptyResult { | ||||
|     let challenge_type = TwoFactorType::U2fLoginChallenge as i32; | ||||
|     let tf_challenge = TwoFactor::find_by_user_and_type(user_uuid, challenge_type, conn); | ||||
|     let tf_challenge = TwoFactor::find_by_user_and_type(user_uuid, challenge_type, conn).await; | ||||
| 
 | ||||
|     let challenge = match tf_challenge { | ||||
|         Some(tf_challenge) => { | ||||
|             let challenge: Challenge = serde_json::from_str(&tf_challenge.data)?; | ||||
|             tf_challenge.delete(conn)?; | ||||
|             tf_challenge.delete(conn).await?; | ||||
|             challenge | ||||
|         } | ||||
|         None => err!("Can't recover login challenge"), | ||||
|     }; | ||||
|     let response: SignResponse = serde_json::from_str(response)?; | ||||
|     let mut registrations = get_u2f_registrations(user_uuid, conn)?.1; | ||||
|     let mut registrations = get_u2f_registrations(user_uuid, conn).await?.1; | ||||
|     if registrations.is_empty() { | ||||
|         err!("No U2F devices registered") | ||||
|     } | ||||
|  | @ -332,13 +333,13 @@ pub fn validate_u2f_login(user_uuid: &str, response: &str, conn: &DbConn) -> Emp | |||
|         match response { | ||||
|             Ok(new_counter) => { | ||||
|                 reg.counter = new_counter; | ||||
|                 save_u2f_registrations(user_uuid, ®istrations, conn)?; | ||||
|                 save_u2f_registrations(user_uuid, ®istrations, conn).await?; | ||||
| 
 | ||||
|                 return Ok(()); | ||||
|             } | ||||
|             Err(u2f::u2ferror::U2fError::CounterTooLow) => { | ||||
|                 reg.compromised = true; | ||||
|                 save_u2f_registrations(user_uuid, ®istrations, conn)?; | ||||
|                 save_u2f_registrations(user_uuid, ®istrations, conn).await?; | ||||
| 
 | ||||
|                 err!("This device might be compromised!"); | ||||
|             } | ||||
|  |  | |||
|  | @ -80,7 +80,7 @@ impl WebauthnRegistration { | |||
| } | ||||
| 
 | ||||
| #[post("/two-factor/get-webauthn", data = "<data>")] | ||||
| fn get_webauthn(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn get_webauthn(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     if !CONFIG.domain_set() { | ||||
|         err!("`DOMAIN` environment variable is not set. Webauthn disabled") | ||||
|     } | ||||
|  | @ -89,7 +89,7 @@ fn get_webauthn(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) | |||
|         err!("Invalid password"); | ||||
|     } | ||||
| 
 | ||||
|     let (enabled, registrations) = get_webauthn_registrations(&headers.user.uuid, &conn)?; | ||||
|     let (enabled, registrations) = get_webauthn_registrations(&headers.user.uuid, &conn).await?; | ||||
|     let registrations_json: Vec<Value> = registrations.iter().map(WebauthnRegistration::to_json).collect(); | ||||
| 
 | ||||
|     Ok(Json(json!({ | ||||
|  | @ -100,12 +100,13 @@ fn get_webauthn(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) | |||
| } | ||||
| 
 | ||||
| #[post("/two-factor/get-webauthn-challenge", data = "<data>")] | ||||
| fn generate_webauthn_challenge(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn generate_webauthn_challenge(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     if !headers.user.check_valid_password(&data.data.MasterPasswordHash) { | ||||
|         err!("Invalid password"); | ||||
|     } | ||||
| 
 | ||||
|     let registrations = get_webauthn_registrations(&headers.user.uuid, &conn)? | ||||
|     let registrations = get_webauthn_registrations(&headers.user.uuid, &conn) | ||||
|         .await? | ||||
|         .1 | ||||
|         .into_iter() | ||||
|         .map(|r| r.credential.cred_id) // We return the credentialIds to the clients to avoid double registering
 | ||||
|  | @ -121,7 +122,7 @@ fn generate_webauthn_challenge(data: JsonUpcase<PasswordData>, headers: Headers, | |||
|     )?; | ||||
| 
 | ||||
|     let type_ = TwoFactorType::WebauthnRegisterChallenge; | ||||
|     TwoFactor::new(headers.user.uuid, type_, serde_json::to_string(&state)?).save(&conn)?; | ||||
|     TwoFactor::new(headers.user.uuid, type_, serde_json::to_string(&state)?).save(&conn).await?; | ||||
| 
 | ||||
|     let mut challenge_value = serde_json::to_value(challenge.public_key)?; | ||||
|     challenge_value["status"] = "ok".into(); | ||||
|  | @ -218,7 +219,7 @@ impl From<PublicKeyCredentialCopy> for PublicKeyCredential { | |||
| } | ||||
| 
 | ||||
| #[post("/two-factor/webauthn", data = "<data>")] | ||||
| fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let data: EnableWebauthnData = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
| 
 | ||||
|  | @ -228,10 +229,10 @@ fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Headers, con | |||
| 
 | ||||
|     // Retrieve and delete the saved challenge state
 | ||||
|     let type_ = TwoFactorType::WebauthnRegisterChallenge as i32; | ||||
|     let state = match TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn) { | ||||
|     let state = match TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await { | ||||
|         Some(tf) => { | ||||
|             let state: RegistrationState = serde_json::from_str(&tf.data)?; | ||||
|             tf.delete(&conn)?; | ||||
|             tf.delete(&conn).await?; | ||||
|             state | ||||
|         } | ||||
|         None => err!("Can't recover challenge"), | ||||
|  | @ -241,7 +242,7 @@ fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Headers, con | |||
|     let (credential, _data) = | ||||
|         WebauthnConfig::load().register_credential(&data.DeviceResponse.into(), &state, |_| Ok(false))?; | ||||
| 
 | ||||
|     let mut registrations: Vec<_> = get_webauthn_registrations(&user.uuid, &conn)?.1; | ||||
|     let mut registrations: Vec<_> = get_webauthn_registrations(&user.uuid, &conn).await?.1; | ||||
|     // TODO: Check for repeated ID's
 | ||||
|     registrations.push(WebauthnRegistration { | ||||
|         id: data.Id.into_i32()?, | ||||
|  | @ -252,8 +253,10 @@ fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Headers, con | |||
|     }); | ||||
| 
 | ||||
|     // Save the registrations and return them
 | ||||
|     TwoFactor::new(user.uuid.clone(), TwoFactorType::Webauthn, serde_json::to_string(®istrations)?).save(&conn)?; | ||||
|     _generate_recover_code(&mut user, &conn); | ||||
|     TwoFactor::new(user.uuid.clone(), TwoFactorType::Webauthn, serde_json::to_string(®istrations)?) | ||||
|         .save(&conn) | ||||
|         .await?; | ||||
|     _generate_recover_code(&mut user, &conn).await; | ||||
| 
 | ||||
|     let keys_json: Vec<Value> = registrations.iter().map(WebauthnRegistration::to_json).collect(); | ||||
|     Ok(Json(json!({ | ||||
|  | @ -264,8 +267,8 @@ fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Headers, con | |||
| } | ||||
| 
 | ||||
| #[put("/two-factor/webauthn", data = "<data>")] | ||||
| fn activate_webauthn_put(data: JsonUpcase<EnableWebauthnData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     activate_webauthn(data, headers, conn) | ||||
| async fn activate_webauthn_put(data: JsonUpcase<EnableWebauthnData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     activate_webauthn(data, headers, conn).await | ||||
| } | ||||
| 
 | ||||
| #[derive(Deserialize, Debug)] | ||||
|  | @ -276,13 +279,14 @@ struct DeleteU2FData { | |||
| } | ||||
| 
 | ||||
| #[delete("/two-factor/webauthn", data = "<data>")] | ||||
| fn delete_webauthn(data: JsonUpcase<DeleteU2FData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn delete_webauthn(data: JsonUpcase<DeleteU2FData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let id = data.data.Id.into_i32()?; | ||||
|     if !headers.user.check_valid_password(&data.data.MasterPasswordHash) { | ||||
|         err!("Invalid password"); | ||||
|     } | ||||
| 
 | ||||
|     let mut tf = match TwoFactor::find_by_user_and_type(&headers.user.uuid, TwoFactorType::Webauthn as i32, &conn) { | ||||
|     let mut tf = match TwoFactor::find_by_user_and_type(&headers.user.uuid, TwoFactorType::Webauthn as i32, &conn).await | ||||
|     { | ||||
|         Some(tf) => tf, | ||||
|         None => err!("Webauthn data not found!"), | ||||
|     }; | ||||
|  | @ -296,11 +300,12 @@ fn delete_webauthn(data: JsonUpcase<DeleteU2FData>, headers: Headers, conn: DbCo | |||
| 
 | ||||
|     let removed_item = data.remove(item_pos); | ||||
|     tf.data = serde_json::to_string(&data)?; | ||||
|     tf.save(&conn)?; | ||||
|     tf.save(&conn).await?; | ||||
|     drop(tf); | ||||
| 
 | ||||
|     // If entry is migrated from u2f, delete the u2f entry as well
 | ||||
|     if let Some(mut u2f) = TwoFactor::find_by_user_and_type(&headers.user.uuid, TwoFactorType::U2f as i32, &conn) { | ||||
|     if let Some(mut u2f) = TwoFactor::find_by_user_and_type(&headers.user.uuid, TwoFactorType::U2f as i32, &conn).await | ||||
|     { | ||||
|         use crate::api::core::two_factor::u2f::U2FRegistration; | ||||
|         let mut data: Vec<U2FRegistration> = match serde_json::from_str(&u2f.data) { | ||||
|             Ok(d) => d, | ||||
|  | @ -311,7 +316,7 @@ fn delete_webauthn(data: JsonUpcase<DeleteU2FData>, headers: Headers, conn: DbCo | |||
|         let new_data_str = serde_json::to_string(&data)?; | ||||
| 
 | ||||
|         u2f.data = new_data_str; | ||||
|         u2f.save(&conn)?; | ||||
|         u2f.save(&conn).await?; | ||||
|     } | ||||
| 
 | ||||
|     let keys_json: Vec<Value> = data.iter().map(WebauthnRegistration::to_json).collect(); | ||||
|  | @ -323,18 +328,21 @@ fn delete_webauthn(data: JsonUpcase<DeleteU2FData>, headers: Headers, conn: DbCo | |||
|     }))) | ||||
| } | ||||
| 
 | ||||
| pub fn get_webauthn_registrations(user_uuid: &str, conn: &DbConn) -> Result<(bool, Vec<WebauthnRegistration>), Error> { | ||||
| pub async fn get_webauthn_registrations( | ||||
|     user_uuid: &str, | ||||
|     conn: &DbConn, | ||||
| ) -> Result<(bool, Vec<WebauthnRegistration>), Error> { | ||||
|     let type_ = TwoFactorType::Webauthn as i32; | ||||
|     match TwoFactor::find_by_user_and_type(user_uuid, type_, conn) { | ||||
|     match TwoFactor::find_by_user_and_type(user_uuid, type_, conn).await { | ||||
|         Some(tf) => Ok((tf.enabled, serde_json::from_str(&tf.data)?)), | ||||
|         None => Ok((false, Vec::new())), // If no data, return empty list
 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub fn generate_webauthn_login(user_uuid: &str, conn: &DbConn) -> JsonResult { | ||||
| pub async fn generate_webauthn_login(user_uuid: &str, conn: &DbConn) -> JsonResult { | ||||
|     // Load saved credentials
 | ||||
|     let creds: Vec<Credential> = | ||||
|         get_webauthn_registrations(user_uuid, conn)?.1.into_iter().map(|r| r.credential).collect(); | ||||
|         get_webauthn_registrations(user_uuid, conn).await?.1.into_iter().map(|r| r.credential).collect(); | ||||
| 
 | ||||
|     if creds.is_empty() { | ||||
|         err!("No Webauthn devices registered") | ||||
|  | @ -346,18 +354,19 @@ pub fn generate_webauthn_login(user_uuid: &str, conn: &DbConn) -> JsonResult { | |||
| 
 | ||||
|     // Save the challenge state for later validation
 | ||||
|     TwoFactor::new(user_uuid.into(), TwoFactorType::WebauthnLoginChallenge, serde_json::to_string(&state)?) | ||||
|         .save(conn)?; | ||||
|         .save(conn) | ||||
|         .await?; | ||||
| 
 | ||||
|     // Return challenge to the clients
 | ||||
|     Ok(Json(serde_json::to_value(response.public_key)?)) | ||||
| } | ||||
| 
 | ||||
| pub fn validate_webauthn_login(user_uuid: &str, response: &str, conn: &DbConn) -> EmptyResult { | ||||
| pub async fn validate_webauthn_login(user_uuid: &str, response: &str, conn: &DbConn) -> EmptyResult { | ||||
|     let type_ = TwoFactorType::WebauthnLoginChallenge as i32; | ||||
|     let state = match TwoFactor::find_by_user_and_type(user_uuid, type_, conn) { | ||||
|     let state = match TwoFactor::find_by_user_and_type(user_uuid, type_, conn).await { | ||||
|         Some(tf) => { | ||||
|             let state: AuthenticationState = serde_json::from_str(&tf.data)?; | ||||
|             tf.delete(conn)?; | ||||
|             tf.delete(conn).await?; | ||||
|             state | ||||
|         } | ||||
|         None => err!("Can't recover login challenge"), | ||||
|  | @ -366,7 +375,7 @@ pub fn validate_webauthn_login(user_uuid: &str, response: &str, conn: &DbConn) - | |||
|     let rsp: crate::util::UpCase<PublicKeyCredentialCopy> = serde_json::from_str(response)?; | ||||
|     let rsp: PublicKeyCredential = rsp.data.into(); | ||||
| 
 | ||||
|     let mut registrations = get_webauthn_registrations(user_uuid, conn)?.1; | ||||
|     let mut registrations = get_webauthn_registrations(user_uuid, conn).await?.1; | ||||
| 
 | ||||
|     // If the credential we received is migrated from U2F, enable the U2F compatibility
 | ||||
|     //let use_u2f = registrations.iter().any(|r| r.migrated && r.credential.cred_id == rsp.raw_id.0);
 | ||||
|  | @ -377,7 +386,8 @@ pub fn validate_webauthn_login(user_uuid: &str, response: &str, conn: &DbConn) - | |||
|             reg.credential.counter = auth_data.counter; | ||||
| 
 | ||||
|             TwoFactor::new(user_uuid.to_string(), TwoFactorType::Webauthn, serde_json::to_string(®istrations)?) | ||||
|                 .save(conn)?; | ||||
|                 .save(conn) | ||||
|                 .await?; | ||||
|             return Ok(()); | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -78,7 +78,7 @@ fn verify_yubikey_otp(otp: String) -> EmptyResult { | |||
| } | ||||
| 
 | ||||
| #[post("/two-factor/get-yubikey", data = "<data>")] | ||||
| fn generate_yubikey(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn generate_yubikey(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     // Make sure the credentials are set
 | ||||
|     get_yubico_credentials()?; | ||||
| 
 | ||||
|  | @ -92,7 +92,7 @@ fn generate_yubikey(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbCo | |||
|     let user_uuid = &user.uuid; | ||||
|     let yubikey_type = TwoFactorType::YubiKey as i32; | ||||
| 
 | ||||
|     let r = TwoFactor::find_by_user_and_type(user_uuid, yubikey_type, &conn); | ||||
|     let r = TwoFactor::find_by_user_and_type(user_uuid, yubikey_type, &conn).await; | ||||
| 
 | ||||
|     if let Some(r) = r { | ||||
|         let yubikey_metadata: YubikeyMetadata = serde_json::from_str(&r.data)?; | ||||
|  | @ -113,7 +113,7 @@ fn generate_yubikey(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbCo | |||
| } | ||||
| 
 | ||||
| #[post("/two-factor/yubikey", data = "<data>")] | ||||
| fn activate_yubikey(data: JsonUpcase<EnableYubikeyData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn activate_yubikey(data: JsonUpcase<EnableYubikeyData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let data: EnableYubikeyData = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
| 
 | ||||
|  | @ -122,10 +122,11 @@ fn activate_yubikey(data: JsonUpcase<EnableYubikeyData>, headers: Headers, conn: | |||
|     } | ||||
| 
 | ||||
|     // Check if we already have some data
 | ||||
|     let mut yubikey_data = match TwoFactor::find_by_user_and_type(&user.uuid, TwoFactorType::YubiKey as i32, &conn) { | ||||
|         Some(data) => data, | ||||
|         None => TwoFactor::new(user.uuid.clone(), TwoFactorType::YubiKey, String::new()), | ||||
|     }; | ||||
|     let mut yubikey_data = | ||||
|         match TwoFactor::find_by_user_and_type(&user.uuid, TwoFactorType::YubiKey as i32, &conn).await { | ||||
|             Some(data) => data, | ||||
|             None => TwoFactor::new(user.uuid.clone(), TwoFactorType::YubiKey, String::new()), | ||||
|         }; | ||||
| 
 | ||||
|     let yubikeys = parse_yubikeys(&data); | ||||
| 
 | ||||
|  | @ -154,9 +155,9 @@ fn activate_yubikey(data: JsonUpcase<EnableYubikeyData>, headers: Headers, conn: | |||
|     }; | ||||
| 
 | ||||
|     yubikey_data.data = serde_json::to_string(&yubikey_metadata).unwrap(); | ||||
|     yubikey_data.save(&conn)?; | ||||
|     yubikey_data.save(&conn).await?; | ||||
| 
 | ||||
|     _generate_recover_code(&mut user, &conn); | ||||
|     _generate_recover_code(&mut user, &conn).await; | ||||
| 
 | ||||
|     let mut result = jsonify_yubikeys(yubikey_metadata.Keys); | ||||
| 
 | ||||
|  | @ -168,8 +169,8 @@ fn activate_yubikey(data: JsonUpcase<EnableYubikeyData>, headers: Headers, conn: | |||
| } | ||||
| 
 | ||||
| #[put("/two-factor/yubikey", data = "<data>")] | ||||
| fn activate_yubikey_put(data: JsonUpcase<EnableYubikeyData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     activate_yubikey(data, headers, conn) | ||||
| async fn activate_yubikey_put(data: JsonUpcase<EnableYubikeyData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     activate_yubikey(data, headers, conn).await | ||||
| } | ||||
| 
 | ||||
| pub fn validate_yubikey_login(response: &str, twofactor_data: &str) -> EmptyResult { | ||||
|  |  | |||
|  | @ -745,6 +745,7 @@ async fn download_icon(domain: &str) -> Result<(Bytes, Option<&str>), Error> { | |||
|                     buffer = stream_to_bytes_limit(res, 512 * 1024).await?; // 512 KB for each icon max
 | ||||
|                                                                             // Check if the icon type is allowed, else try an icon from the list.
 | ||||
|                     icon_type = get_icon_type(&buffer); | ||||
|                     // Check if the icon type is allowed, else try an icon from the list.
 | ||||
|                     if icon_type.is_none() { | ||||
|                         buffer.clear(); | ||||
|                         debug!("Icon from {}, is not a valid image type", icon.href); | ||||
|  |  | |||
|  | @ -23,13 +23,13 @@ pub fn routes() -> Vec<Route> { | |||
| } | ||||
| 
 | ||||
| #[post("/connect/token", data = "<data>")] | ||||
| fn login(data: Form<ConnectData>, conn: DbConn, ip: ClientIp) -> JsonResult { | ||||
| async fn login(data: Form<ConnectData>, conn: DbConn, ip: ClientIp) -> JsonResult { | ||||
|     let data: ConnectData = data.into_inner(); | ||||
| 
 | ||||
|     match data.grant_type.as_ref() { | ||||
|         "refresh_token" => { | ||||
|             _check_is_some(&data.refresh_token, "refresh_token cannot be blank")?; | ||||
|             _refresh_login(data, conn) | ||||
|             _refresh_login(data, conn).await | ||||
|         } | ||||
|         "password" => { | ||||
|             _check_is_some(&data.client_id, "client_id cannot be blank")?; | ||||
|  | @ -41,34 +41,34 @@ fn login(data: Form<ConnectData>, conn: DbConn, ip: ClientIp) -> JsonResult { | |||
|             _check_is_some(&data.device_name, "device_name cannot be blank")?; | ||||
|             _check_is_some(&data.device_type, "device_type cannot be blank")?; | ||||
| 
 | ||||
|             _password_login(data, conn, &ip) | ||||
|             _password_login(data, conn, &ip).await | ||||
|         } | ||||
|         "client_credentials" => { | ||||
|             _check_is_some(&data.client_id, "client_id cannot be blank")?; | ||||
|             _check_is_some(&data.client_secret, "client_secret cannot be blank")?; | ||||
|             _check_is_some(&data.scope, "scope cannot be blank")?; | ||||
| 
 | ||||
|             _api_key_login(data, conn, &ip) | ||||
|             _api_key_login(data, conn, &ip).await | ||||
|         } | ||||
|         t => err!("Invalid type", t), | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn _refresh_login(data: ConnectData, conn: DbConn) -> JsonResult { | ||||
| async fn _refresh_login(data: ConnectData, conn: DbConn) -> JsonResult { | ||||
|     // Extract token
 | ||||
|     let token = data.refresh_token.unwrap(); | ||||
| 
 | ||||
|     // Get device by refresh token
 | ||||
|     let mut device = Device::find_by_refresh_token(&token, &conn).map_res("Invalid refresh token")?; | ||||
|     let mut device = Device::find_by_refresh_token(&token, &conn).await.map_res("Invalid refresh token")?; | ||||
| 
 | ||||
|     let scope = "api offline_access"; | ||||
|     let scope_vec = vec!["api".into(), "offline_access".into()]; | ||||
| 
 | ||||
|     // Common
 | ||||
|     let user = User::find_by_uuid(&device.user_uuid, &conn).unwrap(); | ||||
|     let orgs = UserOrganization::find_confirmed_by_user(&user.uuid, &conn); | ||||
|     let user = User::find_by_uuid(&device.user_uuid, &conn).await.unwrap(); | ||||
|     let orgs = UserOrganization::find_confirmed_by_user(&user.uuid, &conn).await; | ||||
|     let (access_token, expires_in) = device.refresh_tokens(&user, orgs, scope_vec); | ||||
|     device.save(&conn)?; | ||||
|     device.save(&conn).await?; | ||||
| 
 | ||||
|     Ok(Json(json!({ | ||||
|         "access_token": access_token, | ||||
|  | @ -86,7 +86,7 @@ fn _refresh_login(data: ConnectData, conn: DbConn) -> JsonResult { | |||
|     }))) | ||||
| } | ||||
| 
 | ||||
| fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult { | ||||
| async fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult { | ||||
|     // Validate scope
 | ||||
|     let scope = data.scope.as_ref().unwrap(); | ||||
|     if scope != "api offline_access" { | ||||
|  | @ -99,7 +99,7 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult | |||
| 
 | ||||
|     // Get the user
 | ||||
|     let username = data.username.as_ref().unwrap(); | ||||
|     let user = match User::find_by_mail(username, &conn) { | ||||
|     let user = match User::find_by_mail(username, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Username or password is incorrect. Try again", format!("IP: {}. Username: {}.", ip.ip, username)), | ||||
|     }; | ||||
|  | @ -130,7 +130,7 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult | |||
|                 user.last_verifying_at = Some(now); | ||||
|                 user.login_verify_count += 1; | ||||
| 
 | ||||
|                 if let Err(e) = user.save(&conn) { | ||||
|                 if let Err(e) = user.save(&conn).await { | ||||
|                     error!("Error updating user: {:#?}", e); | ||||
|                 } | ||||
| 
 | ||||
|  | @ -144,9 +144,9 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult | |||
|         err!("Please verify your email before trying again.", format!("IP: {}. Username: {}.", ip.ip, username)) | ||||
|     } | ||||
| 
 | ||||
|     let (mut device, new_device) = get_device(&data, &conn, &user); | ||||
|     let (mut device, new_device) = get_device(&data, &conn, &user).await; | ||||
| 
 | ||||
|     let twofactor_token = twofactor_auth(&user.uuid, &data, &mut device, ip, &conn)?; | ||||
|     let twofactor_token = twofactor_auth(&user.uuid, &data, &mut device, ip, &conn).await?; | ||||
| 
 | ||||
|     if CONFIG.mail_enabled() && new_device { | ||||
|         if let Err(e) = mail::send_new_device_logged_in(&user.email, &ip.ip.to_string(), &now, &device.name) { | ||||
|  | @ -159,9 +159,9 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult | |||
|     } | ||||
| 
 | ||||
|     // Common
 | ||||
|     let orgs = UserOrganization::find_confirmed_by_user(&user.uuid, &conn); | ||||
|     let orgs = UserOrganization::find_confirmed_by_user(&user.uuid, &conn).await; | ||||
|     let (access_token, expires_in) = device.refresh_tokens(&user, orgs, scope_vec); | ||||
|     device.save(&conn)?; | ||||
|     device.save(&conn).await?; | ||||
| 
 | ||||
|     let mut result = json!({ | ||||
|         "access_token": access_token, | ||||
|  | @ -187,7 +187,7 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult | |||
|     Ok(Json(result)) | ||||
| } | ||||
| 
 | ||||
| fn _api_key_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult { | ||||
| async fn _api_key_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult { | ||||
|     // Validate scope
 | ||||
|     let scope = data.scope.as_ref().unwrap(); | ||||
|     if scope != "api" { | ||||
|  | @ -204,7 +204,7 @@ fn _api_key_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult | |||
|         Some(uuid) => uuid, | ||||
|         None => err!("Malformed client_id", format!("IP: {}.", ip.ip)), | ||||
|     }; | ||||
|     let user = match User::find_by_uuid(user_uuid, &conn) { | ||||
|     let user = match User::find_by_uuid(user_uuid, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Invalid client_id", format!("IP: {}.", ip.ip)), | ||||
|     }; | ||||
|  | @ -220,7 +220,7 @@ fn _api_key_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult | |||
|         err!("Incorrect client_secret", format!("IP: {}. Username: {}.", ip.ip, user.email)) | ||||
|     } | ||||
| 
 | ||||
|     let (mut device, new_device) = get_device(&data, &conn, &user); | ||||
|     let (mut device, new_device) = get_device(&data, &conn, &user).await; | ||||
| 
 | ||||
|     if CONFIG.mail_enabled() && new_device { | ||||
|         let now = Utc::now().naive_utc(); | ||||
|  | @ -234,9 +234,9 @@ fn _api_key_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult | |||
|     } | ||||
| 
 | ||||
|     // Common
 | ||||
|     let orgs = UserOrganization::find_confirmed_by_user(&user.uuid, &conn); | ||||
|     let orgs = UserOrganization::find_confirmed_by_user(&user.uuid, &conn).await; | ||||
|     let (access_token, expires_in) = device.refresh_tokens(&user, orgs, scope_vec); | ||||
|     device.save(&conn)?; | ||||
|     device.save(&conn).await?; | ||||
| 
 | ||||
|     info!("User {} logged in successfully via API key. IP: {}", user.email, ip.ip); | ||||
| 
 | ||||
|  | @ -258,7 +258,7 @@ fn _api_key_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult | |||
| } | ||||
| 
 | ||||
| /// Retrieves an existing device or creates a new device from ConnectData and the User
 | ||||
| fn get_device(data: &ConnectData, conn: &DbConn, user: &User) -> (Device, bool) { | ||||
| async fn get_device(data: &ConnectData, conn: &DbConn, user: &User) -> (Device, bool) { | ||||
|     // On iOS, device_type sends "iOS", on others it sends a number
 | ||||
|     let device_type = util::try_parse_string(data.device_type.as_ref()).unwrap_or(0); | ||||
|     let device_id = data.device_identifier.clone().expect("No device id provided"); | ||||
|  | @ -266,7 +266,7 @@ fn get_device(data: &ConnectData, conn: &DbConn, user: &User) -> (Device, bool) | |||
| 
 | ||||
|     let mut new_device = false; | ||||
|     // Find device or create new
 | ||||
|     let device = match Device::find_by_uuid(&device_id, conn) { | ||||
|     let device = match Device::find_by_uuid(&device_id, conn).await { | ||||
|         Some(device) => { | ||||
|             // Check if owned device, and recreate if not
 | ||||
|             if device.user_uuid != user.uuid { | ||||
|  | @ -286,28 +286,28 @@ fn get_device(data: &ConnectData, conn: &DbConn, user: &User) -> (Device, bool) | |||
|     (device, new_device) | ||||
| } | ||||
| 
 | ||||
| fn twofactor_auth( | ||||
| async fn twofactor_auth( | ||||
|     user_uuid: &str, | ||||
|     data: &ConnectData, | ||||
|     device: &mut Device, | ||||
|     ip: &ClientIp, | ||||
|     conn: &DbConn, | ||||
| ) -> ApiResult<Option<String>> { | ||||
|     let twofactors = TwoFactor::find_by_user(user_uuid, conn); | ||||
|     let twofactors = TwoFactor::find_by_user(user_uuid, conn).await; | ||||
| 
 | ||||
|     // No twofactor token if twofactor is disabled
 | ||||
|     if twofactors.is_empty() { | ||||
|         return Ok(None); | ||||
|     } | ||||
| 
 | ||||
|     TwoFactorIncomplete::mark_incomplete(user_uuid, &device.uuid, &device.name, ip, conn)?; | ||||
|     TwoFactorIncomplete::mark_incomplete(user_uuid, &device.uuid, &device.name, ip, conn).await?; | ||||
| 
 | ||||
|     let twofactor_ids: Vec<_> = twofactors.iter().map(|tf| tf.atype).collect(); | ||||
|     let selected_id = data.two_factor_provider.unwrap_or(twofactor_ids[0]); // If we aren't given a two factor provider, asume the first one
 | ||||
| 
 | ||||
|     let twofactor_code = match data.two_factor_token { | ||||
|         Some(ref code) => code, | ||||
|         None => err_json!(_json_err_twofactor(&twofactor_ids, user_uuid, conn)?, "2FA token not provided"), | ||||
|         None => err_json!(_json_err_twofactor(&twofactor_ids, user_uuid, conn).await?, "2FA token not provided"), | ||||
|     }; | ||||
| 
 | ||||
|     let selected_twofactor = twofactors.into_iter().find(|tf| tf.atype == selected_id && tf.enabled); | ||||
|  | @ -320,16 +320,18 @@ fn twofactor_auth( | |||
| 
 | ||||
|     match TwoFactorType::from_i32(selected_id) { | ||||
|         Some(TwoFactorType::Authenticator) => { | ||||
|             _tf::authenticator::validate_totp_code_str(user_uuid, twofactor_code, &selected_data?, ip, conn)? | ||||
|             _tf::authenticator::validate_totp_code_str(user_uuid, twofactor_code, &selected_data?, ip, conn).await? | ||||
|         } | ||||
|         Some(TwoFactorType::U2f) => _tf::u2f::validate_u2f_login(user_uuid, twofactor_code, conn).await?, | ||||
|         Some(TwoFactorType::Webauthn) => { | ||||
|             _tf::webauthn::validate_webauthn_login(user_uuid, twofactor_code, conn).await? | ||||
|         } | ||||
|         Some(TwoFactorType::U2f) => _tf::u2f::validate_u2f_login(user_uuid, twofactor_code, conn)?, | ||||
|         Some(TwoFactorType::Webauthn) => _tf::webauthn::validate_webauthn_login(user_uuid, twofactor_code, conn)?, | ||||
|         Some(TwoFactorType::YubiKey) => _tf::yubikey::validate_yubikey_login(twofactor_code, &selected_data?)?, | ||||
|         Some(TwoFactorType::Duo) => { | ||||
|             _tf::duo::validate_duo_login(data.username.as_ref().unwrap(), twofactor_code, conn)? | ||||
|             _tf::duo::validate_duo_login(data.username.as_ref().unwrap(), twofactor_code, conn).await? | ||||
|         } | ||||
|         Some(TwoFactorType::Email) => { | ||||
|             _tf::email::validate_email_code_str(user_uuid, twofactor_code, &selected_data?, conn)? | ||||
|             _tf::email::validate_email_code_str(user_uuid, twofactor_code, &selected_data?, conn).await? | ||||
|         } | ||||
| 
 | ||||
|         Some(TwoFactorType::Remember) => { | ||||
|  | @ -338,14 +340,17 @@ fn twofactor_auth( | |||
|                     remember = 1; // Make sure we also return the token here, otherwise it will only remember the first time
 | ||||
|                 } | ||||
|                 _ => { | ||||
|                     err_json!(_json_err_twofactor(&twofactor_ids, user_uuid, conn)?, "2FA Remember token not provided") | ||||
|                     err_json!( | ||||
|                         _json_err_twofactor(&twofactor_ids, user_uuid, conn).await?, | ||||
|                         "2FA Remember token not provided" | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         _ => err!("Invalid two factor provider"), | ||||
|     } | ||||
| 
 | ||||
|     TwoFactorIncomplete::mark_complete(user_uuid, &device.uuid, conn)?; | ||||
|     TwoFactorIncomplete::mark_complete(user_uuid, &device.uuid, conn).await?; | ||||
| 
 | ||||
|     if !CONFIG.disable_2fa_remember() && remember == 1 { | ||||
|         Ok(Some(device.refresh_twofactor_remember())) | ||||
|  | @ -359,7 +364,7 @@ fn _selected_data(tf: Option<TwoFactor>) -> ApiResult<String> { | |||
|     tf.map(|t| t.data).map_res("Two factor doesn't exist") | ||||
| } | ||||
| 
 | ||||
| fn _json_err_twofactor(providers: &[i32], user_uuid: &str, conn: &DbConn) -> ApiResult<Value> { | ||||
| async fn _json_err_twofactor(providers: &[i32], user_uuid: &str, conn: &DbConn) -> ApiResult<Value> { | ||||
|     use crate::api::core::two_factor; | ||||
| 
 | ||||
|     let mut result = json!({ | ||||
|  | @ -376,7 +381,7 @@ fn _json_err_twofactor(providers: &[i32], user_uuid: &str, conn: &DbConn) -> Api | |||
|             Some(TwoFactorType::Authenticator) => { /* Nothing to do for TOTP */ } | ||||
| 
 | ||||
|             Some(TwoFactorType::U2f) if CONFIG.domain_set() => { | ||||
|                 let request = two_factor::u2f::generate_u2f_login(user_uuid, conn)?; | ||||
|                 let request = two_factor::u2f::generate_u2f_login(user_uuid, conn).await?; | ||||
|                 let mut challenge_list = Vec::new(); | ||||
| 
 | ||||
|                 for key in request.registered_keys { | ||||
|  | @ -396,17 +401,17 @@ fn _json_err_twofactor(providers: &[i32], user_uuid: &str, conn: &DbConn) -> Api | |||
|             } | ||||
| 
 | ||||
|             Some(TwoFactorType::Webauthn) if CONFIG.domain_set() => { | ||||
|                 let request = two_factor::webauthn::generate_webauthn_login(user_uuid, conn)?; | ||||
|                 let request = two_factor::webauthn::generate_webauthn_login(user_uuid, conn).await?; | ||||
|                 result["TwoFactorProviders2"][provider.to_string()] = request.0; | ||||
|             } | ||||
| 
 | ||||
|             Some(TwoFactorType::Duo) => { | ||||
|                 let email = match User::find_by_uuid(user_uuid, conn) { | ||||
|                 let email = match User::find_by_uuid(user_uuid, conn).await { | ||||
|                     Some(u) => u.email, | ||||
|                     None => err!("User does not exist"), | ||||
|                 }; | ||||
| 
 | ||||
|                 let (signature, host) = duo::generate_duo_signature(&email, conn)?; | ||||
|                 let (signature, host) = duo::generate_duo_signature(&email, conn).await?; | ||||
| 
 | ||||
|                 result["TwoFactorProviders2"][provider.to_string()] = json!({ | ||||
|                     "Host": host, | ||||
|  | @ -415,7 +420,7 @@ fn _json_err_twofactor(providers: &[i32], user_uuid: &str, conn: &DbConn) -> Api | |||
|             } | ||||
| 
 | ||||
|             Some(tf_type @ TwoFactorType::YubiKey) => { | ||||
|                 let twofactor = match TwoFactor::find_by_user_and_type(user_uuid, tf_type as i32, conn) { | ||||
|                 let twofactor = match TwoFactor::find_by_user_and_type(user_uuid, tf_type as i32, conn).await { | ||||
|                     Some(tf) => tf, | ||||
|                     None => err!("No YubiKey devices registered"), | ||||
|                 }; | ||||
|  | @ -430,14 +435,14 @@ fn _json_err_twofactor(providers: &[i32], user_uuid: &str, conn: &DbConn) -> Api | |||
|             Some(tf_type @ TwoFactorType::Email) => { | ||||
|                 use crate::api::core::two_factor as _tf; | ||||
| 
 | ||||
|                 let twofactor = match TwoFactor::find_by_user_and_type(user_uuid, tf_type as i32, conn) { | ||||
|                 let twofactor = match TwoFactor::find_by_user_and_type(user_uuid, tf_type as i32, conn).await { | ||||
|                     Some(tf) => tf, | ||||
|                     None => err!("No twofactor email registered"), | ||||
|                 }; | ||||
| 
 | ||||
|                 // Send email immediately if email is the only 2FA option
 | ||||
|                 if providers.len() == 1 { | ||||
|                     _tf::email::send_token(user_uuid, conn)? | ||||
|                     _tf::email::send_token(user_uuid, conn).await? | ||||
|                 } | ||||
| 
 | ||||
|                 let email_data = EmailTokenData::from_json(&twofactor.data)?; | ||||
|  | @ -492,7 +497,7 @@ struct ConnectData { | |||
|     device_type: Option<String>, | ||||
|     #[field(name = uncased("device_push_token"))] | ||||
|     #[field(name = uncased("devicepushtoken"))] | ||||
|     device_push_token: Option<String>, // Unused; mobile device push not yet supported.
 | ||||
|     _device_push_token: Option<String>, // Unused; mobile device push not yet supported.
 | ||||
| 
 | ||||
|     // Needed for two-factor auth
 | ||||
|     #[field(name = uncased("two_factor_provider"))] | ||||
|  |  | |||
							
								
								
									
										12
									
								
								src/auth.rs
									
										
									
									
									
								
							
							
						
						
									
										12
									
								
								src/auth.rs
									
										
									
									
									
								
							|  | @ -350,12 +350,12 @@ impl<'r> FromRequest<'r> for Headers { | |||
|             _ => err_handler!("Error getting DB"), | ||||
|         }; | ||||
| 
 | ||||
|         let device = match Device::find_by_uuid(&device_uuid, &conn) { | ||||
|         let device = match Device::find_by_uuid(&device_uuid, &conn).await { | ||||
|             Some(device) => device, | ||||
|             None => err_handler!("Invalid device id"), | ||||
|         }; | ||||
| 
 | ||||
|         let user = match User::find_by_uuid(&user_uuid, &conn) { | ||||
|         let user = match User::find_by_uuid(&user_uuid, &conn).await { | ||||
|             Some(user) => user, | ||||
|             None => err_handler!("Device has no user associated"), | ||||
|         }; | ||||
|  | @ -377,7 +377,7 @@ impl<'r> FromRequest<'r> for Headers { | |||
|                     // This prevents checking this stamp exception for new requests.
 | ||||
|                     let mut user = user; | ||||
|                     user.reset_stamp_exception(); | ||||
|                     if let Err(e) = user.save(&conn) { | ||||
|                     if let Err(e) = user.save(&conn).await { | ||||
|                         error!("Error updating user: {:#?}", e); | ||||
|                     } | ||||
|                     err_handler!("Stamp exception is expired") | ||||
|  | @ -441,7 +441,7 @@ impl<'r> FromRequest<'r> for OrgHeaders { | |||
|                 }; | ||||
| 
 | ||||
|                 let user = headers.user; | ||||
|                 let org_user = match UserOrganization::find_by_user_and_org(&user.uuid, &org_id, &conn) { | ||||
|                 let org_user = match UserOrganization::find_by_user_and_org(&user.uuid, &org_id, &conn).await { | ||||
|                     Some(user) => { | ||||
|                         if user.status == UserOrgStatus::Confirmed as i32 { | ||||
|                             user | ||||
|  | @ -553,7 +553,9 @@ impl<'r> FromRequest<'r> for ManagerHeaders { | |||
|                     }; | ||||
| 
 | ||||
|                     if !headers.org_user.has_full_access() { | ||||
|                         match CollectionUser::find_by_collection_and_user(&col_id, &headers.org_user.user_uuid, &conn) { | ||||
|                         match CollectionUser::find_by_collection_and_user(&col_id, &headers.org_user.user_uuid, &conn) | ||||
|                             .await | ||||
|                         { | ||||
|                             Some(_) => (), | ||||
|                             None => err_handler!("The current user isn't a manager for this collection"), | ||||
|                         } | ||||
|  |  | |||
|  | @ -30,7 +30,7 @@ pub mod __mysql_schema; | |||
| #[path = "schemas/postgresql/schema.rs"] | ||||
| pub mod __postgresql_schema; | ||||
| 
 | ||||
| // There changes are based on Rocket 0.5-rc wrapper of Diesel: https://github.com/SergioBenitez/Rocket/blob/v0.5-rc/contrib/sync_db_pools
 | ||||
| // These changes are based on Rocket 0.5-rc wrapper of Diesel: https://github.com/SergioBenitez/Rocket/blob/v0.5-rc/contrib/sync_db_pools
 | ||||
| 
 | ||||
| // A wrapper around spawn_blocking that propagates panics to the calling code.
 | ||||
| pub async fn run_blocking<F, R>(job: F) -> R | ||||
|  |  | |||
|  | @ -60,7 +60,7 @@ use crate::error::MapResult; | |||
| 
 | ||||
| /// Database methods
 | ||||
| impl Attachment { | ||||
|     pub fn save(&self, conn: &DbConn) -> EmptyResult { | ||||
|     pub async fn save(&self, conn: &DbConn) -> EmptyResult { | ||||
|         db_run! { conn: | ||||
|             sqlite, mysql { | ||||
|                 match diesel::replace_into(attachments::table) | ||||
|  | @ -92,7 +92,7 @@ impl Attachment { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete(&self, conn: &DbConn) -> EmptyResult { | ||||
|     pub async fn delete(&self, conn: &DbConn) -> EmptyResult { | ||||
|         db_run! { conn: { | ||||
|             crate::util::retry( | ||||
|                 || diesel::delete(attachments::table.filter(attachments::id.eq(&self.id))).execute(conn), | ||||
|  | @ -116,14 +116,14 @@ impl Attachment { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         for attachment in Attachment::find_by_cipher(cipher_uuid, conn) { | ||||
|             attachment.delete(conn)?; | ||||
|     pub async fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         for attachment in Attachment::find_by_cipher(cipher_uuid, conn).await { | ||||
|             attachment.delete(conn).await?; | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_id(id: &str, conn: &DbConn) -> Option<Self> { | ||||
|     pub async fn find_by_id(id: &str, conn: &DbConn) -> Option<Self> { | ||||
|         db_run! { conn: { | ||||
|             attachments::table | ||||
|                 .filter(attachments::id.eq(id.to_lowercase())) | ||||
|  | @ -133,7 +133,7 @@ impl Attachment { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_cipher(cipher_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_by_cipher(cipher_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! { conn: { | ||||
|             attachments::table | ||||
|                 .filter(attachments::cipher_uuid.eq(cipher_uuid)) | ||||
|  | @ -143,7 +143,7 @@ impl Attachment { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn size_by_user(user_uuid: &str, conn: &DbConn) -> i64 { | ||||
|     pub async fn size_by_user(user_uuid: &str, conn: &DbConn) -> i64 { | ||||
|         db_run! { conn: { | ||||
|             let result: Option<i64> = attachments::table | ||||
|                 .left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid))) | ||||
|  | @ -155,7 +155,7 @@ impl Attachment { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn count_by_user(user_uuid: &str, conn: &DbConn) -> i64 { | ||||
|     pub async fn count_by_user(user_uuid: &str, conn: &DbConn) -> i64 { | ||||
|         db_run! { conn: { | ||||
|             attachments::table | ||||
|                 .left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid))) | ||||
|  | @ -166,7 +166,7 @@ impl Attachment { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn size_by_org(org_uuid: &str, conn: &DbConn) -> i64 { | ||||
|     pub async fn size_by_org(org_uuid: &str, conn: &DbConn) -> i64 { | ||||
|         db_run! { conn: { | ||||
|             let result: Option<i64> = attachments::table | ||||
|                 .left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid))) | ||||
|  | @ -178,7 +178,7 @@ impl Attachment { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn count_by_org(org_uuid: &str, conn: &DbConn) -> i64 { | ||||
|     pub async fn count_by_org(org_uuid: &str, conn: &DbConn) -> i64 { | ||||
|         db_run! { conn: { | ||||
|             attachments::table | ||||
|                 .left_join(ciphers::table.on(ciphers::uuid.eq(attachments::cipher_uuid))) | ||||
|  |  | |||
|  | @ -82,10 +82,10 @@ use crate::error::MapResult; | |||
| 
 | ||||
| /// Database methods
 | ||||
| impl Cipher { | ||||
|     pub fn to_json(&self, host: &str, user_uuid: &str, conn: &DbConn) -> Value { | ||||
|     pub async fn to_json(&self, host: &str, user_uuid: &str, conn: &DbConn) -> Value { | ||||
|         use crate::util::format_date; | ||||
| 
 | ||||
|         let attachments = Attachment::find_by_cipher(&self.uuid, conn); | ||||
|         let attachments = Attachment::find_by_cipher(&self.uuid, conn).await; | ||||
|         // When there are no attachments use null instead of an empty array
 | ||||
|         let attachments_json = if attachments.is_empty() { | ||||
|             Value::Null | ||||
|  | @ -97,7 +97,7 @@ impl Cipher { | |||
|         let password_history_json = | ||||
|             self.password_history.as_ref().and_then(|s| serde_json::from_str(s).ok()).unwrap_or(Value::Null); | ||||
| 
 | ||||
|         let (read_only, hide_passwords) = match self.get_access_restrictions(user_uuid, conn) { | ||||
|         let (read_only, hide_passwords) = match self.get_access_restrictions(user_uuid, conn).await { | ||||
|             Some((ro, hp)) => (ro, hp), | ||||
|             None => { | ||||
|                 error!("Cipher ownership assertion failure"); | ||||
|  | @ -144,8 +144,8 @@ impl Cipher { | |||
|             "Type": self.atype, | ||||
|             "RevisionDate": format_date(&self.updated_at), | ||||
|             "DeletedDate": self.deleted_at.map_or(Value::Null, |d| Value::String(format_date(&d))), | ||||
|             "FolderId": self.get_folder_uuid(user_uuid, conn), | ||||
|             "Favorite": self.is_favorite(user_uuid, conn), | ||||
|             "FolderId": self.get_folder_uuid(user_uuid, conn).await, | ||||
|             "Favorite": self.is_favorite(user_uuid, conn).await, | ||||
|             "Reprompt": self.reprompt.unwrap_or(RepromptType::None as i32), | ||||
|             "OrganizationId": self.organization_uuid, | ||||
|             "Attachments": attachments_json, | ||||
|  | @ -154,7 +154,7 @@ impl Cipher { | |||
|             "OrganizationUseTotp": true, | ||||
| 
 | ||||
|             // This field is specific to the cipherDetails type.
 | ||||
|             "CollectionIds": self.get_collections(user_uuid, conn), | ||||
|             "CollectionIds": self.get_collections(user_uuid, conn).await, | ||||
| 
 | ||||
|             "Name": self.name, | ||||
|             "Notes": self.notes, | ||||
|  | @ -189,28 +189,28 @@ impl Cipher { | |||
|         json_object | ||||
|     } | ||||
| 
 | ||||
|     pub fn update_users_revision(&self, conn: &DbConn) -> Vec<String> { | ||||
|     pub async fn update_users_revision(&self, conn: &DbConn) -> Vec<String> { | ||||
|         let mut user_uuids = Vec::new(); | ||||
|         match self.user_uuid { | ||||
|             Some(ref user_uuid) => { | ||||
|                 User::update_uuid_revision(user_uuid, conn); | ||||
|                 User::update_uuid_revision(user_uuid, conn).await; | ||||
|                 user_uuids.push(user_uuid.clone()) | ||||
|             } | ||||
|             None => { | ||||
|                 // Belongs to Organization, need to update affected users
 | ||||
|                 if let Some(ref org_uuid) = self.organization_uuid { | ||||
|                     UserOrganization::find_by_cipher_and_org(&self.uuid, org_uuid, conn).iter().for_each(|user_org| { | ||||
|                         User::update_uuid_revision(&user_org.user_uuid, conn); | ||||
|                     for user_org in UserOrganization::find_by_cipher_and_org(&self.uuid, org_uuid, conn).await.iter() { | ||||
|                         User::update_uuid_revision(&user_org.user_uuid, conn).await; | ||||
|                         user_uuids.push(user_org.user_uuid.clone()) | ||||
|                     }); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|         user_uuids | ||||
|     } | ||||
| 
 | ||||
|     pub fn save(&mut self, conn: &DbConn) -> EmptyResult { | ||||
|         self.update_users_revision(conn); | ||||
|     pub async fn save(&mut self, conn: &DbConn) -> EmptyResult { | ||||
|         self.update_users_revision(conn).await; | ||||
|         self.updated_at = Utc::now().naive_utc(); | ||||
| 
 | ||||
|         db_run! { conn: | ||||
|  | @ -244,13 +244,13 @@ impl Cipher { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete(&self, conn: &DbConn) -> EmptyResult { | ||||
|         self.update_users_revision(conn); | ||||
|     pub async fn delete(&self, conn: &DbConn) -> EmptyResult { | ||||
|         self.update_users_revision(conn).await; | ||||
| 
 | ||||
|         FolderCipher::delete_all_by_cipher(&self.uuid, conn)?; | ||||
|         CollectionCipher::delete_all_by_cipher(&self.uuid, conn)?; | ||||
|         Attachment::delete_all_by_cipher(&self.uuid, conn)?; | ||||
|         Favorite::delete_all_by_cipher(&self.uuid, conn)?; | ||||
|         FolderCipher::delete_all_by_cipher(&self.uuid, conn).await?; | ||||
|         CollectionCipher::delete_all_by_cipher(&self.uuid, conn).await?; | ||||
|         Attachment::delete_all_by_cipher(&self.uuid, conn).await?; | ||||
|         Favorite::delete_all_by_cipher(&self.uuid, conn).await?; | ||||
| 
 | ||||
|         db_run! { conn: { | ||||
|             diesel::delete(ciphers::table.filter(ciphers::uuid.eq(&self.uuid))) | ||||
|  | @ -259,54 +259,55 @@ impl Cipher { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         for cipher in Self::find_by_org(org_uuid, conn) { | ||||
|             cipher.delete(conn)?; | ||||
|     pub async fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         // TODO: Optimize this by executing a DELETE directly on the database, instead of first fetching.
 | ||||
|         for cipher in Self::find_by_org(org_uuid, conn).await { | ||||
|             cipher.delete(conn).await?; | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         for cipher in Self::find_owned_by_user(user_uuid, conn) { | ||||
|             cipher.delete(conn)?; | ||||
|     pub async fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         for cipher in Self::find_owned_by_user(user_uuid, conn).await { | ||||
|             cipher.delete(conn).await?; | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     /// Purge all ciphers that are old enough to be auto-deleted.
 | ||||
|     pub fn purge_trash(conn: &DbConn) { | ||||
|     pub async fn purge_trash(conn: &DbConn) { | ||||
|         if let Some(auto_delete_days) = CONFIG.trash_auto_delete_days() { | ||||
|             let now = Utc::now().naive_utc(); | ||||
|             let dt = now - Duration::days(auto_delete_days); | ||||
|             for cipher in Self::find_deleted_before(&dt, conn) { | ||||
|                 cipher.delete(conn).ok(); | ||||
|             for cipher in Self::find_deleted_before(&dt, conn).await { | ||||
|                 cipher.delete(conn).await.ok(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn move_to_folder(&self, folder_uuid: Option<String>, user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         User::update_uuid_revision(user_uuid, conn); | ||||
|     pub async fn move_to_folder(&self, folder_uuid: Option<String>, user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         User::update_uuid_revision(user_uuid, conn).await; | ||||
| 
 | ||||
|         match (self.get_folder_uuid(user_uuid, conn), folder_uuid) { | ||||
|         match (self.get_folder_uuid(user_uuid, conn).await, folder_uuid) { | ||||
|             // No changes
 | ||||
|             (None, None) => Ok(()), | ||||
|             (Some(ref old), Some(ref new)) if old == new => Ok(()), | ||||
| 
 | ||||
|             // Add to folder
 | ||||
|             (None, Some(new)) => FolderCipher::new(&new, &self.uuid).save(conn), | ||||
|             (None, Some(new)) => FolderCipher::new(&new, &self.uuid).save(conn).await, | ||||
| 
 | ||||
|             // Remove from folder
 | ||||
|             (Some(old), None) => match FolderCipher::find_by_folder_and_cipher(&old, &self.uuid, conn) { | ||||
|                 Some(old) => old.delete(conn), | ||||
|             (Some(old), None) => match FolderCipher::find_by_folder_and_cipher(&old, &self.uuid, conn).await { | ||||
|                 Some(old) => old.delete(conn).await, | ||||
|                 None => err!("Couldn't move from previous folder"), | ||||
|             }, | ||||
| 
 | ||||
|             // Move to another folder
 | ||||
|             (Some(old), Some(new)) => { | ||||
|                 if let Some(old) = FolderCipher::find_by_folder_and_cipher(&old, &self.uuid, conn) { | ||||
|                     old.delete(conn)?; | ||||
|                 if let Some(old) = FolderCipher::find_by_folder_and_cipher(&old, &self.uuid, conn).await { | ||||
|                     old.delete(conn).await?; | ||||
|                 } | ||||
|                 FolderCipher::new(&new, &self.uuid).save(conn) | ||||
|                 FolderCipher::new(&new, &self.uuid).save(conn).await | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | @ -317,9 +318,9 @@ impl Cipher { | |||
|     } | ||||
| 
 | ||||
|     /// Returns whether this cipher is owned by an org in which the user has full access.
 | ||||
|     pub fn is_in_full_access_org(&self, user_uuid: &str, conn: &DbConn) -> bool { | ||||
|     pub async fn is_in_full_access_org(&self, user_uuid: &str, conn: &DbConn) -> bool { | ||||
|         if let Some(ref org_uuid) = self.organization_uuid { | ||||
|             if let Some(user_org) = UserOrganization::find_by_user_and_org(user_uuid, org_uuid, conn) { | ||||
|             if let Some(user_org) = UserOrganization::find_by_user_and_org(user_uuid, org_uuid, conn).await { | ||||
|                 return user_org.has_full_access(); | ||||
|             } | ||||
|         } | ||||
|  | @ -332,11 +333,11 @@ impl Cipher { | |||
|     /// not in any collection the user has access to. Otherwise, the user has
 | ||||
|     /// access to this cipher, and Some(read_only, hide_passwords) represents
 | ||||
|     /// the access restrictions.
 | ||||
|     pub fn get_access_restrictions(&self, user_uuid: &str, conn: &DbConn) -> Option<(bool, bool)> { | ||||
|     pub async fn get_access_restrictions(&self, user_uuid: &str, conn: &DbConn) -> Option<(bool, bool)> { | ||||
|         // Check whether this cipher is directly owned by the user, or is in
 | ||||
|         // a collection that the user has full access to. If so, there are no
 | ||||
|         // access restrictions.
 | ||||
|         if self.is_owned_by_user(user_uuid) || self.is_in_full_access_org(user_uuid, conn) { | ||||
|         if self.is_owned_by_user(user_uuid) || self.is_in_full_access_org(user_uuid, conn).await { | ||||
|             return Some((false, false)); | ||||
|         } | ||||
| 
 | ||||
|  | @ -379,31 +380,31 @@ impl Cipher { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn is_write_accessible_to_user(&self, user_uuid: &str, conn: &DbConn) -> bool { | ||||
|         match self.get_access_restrictions(user_uuid, conn) { | ||||
|     pub async fn is_write_accessible_to_user(&self, user_uuid: &str, conn: &DbConn) -> bool { | ||||
|         match self.get_access_restrictions(user_uuid, conn).await { | ||||
|             Some((read_only, _hide_passwords)) => !read_only, | ||||
|             None => false, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn is_accessible_to_user(&self, user_uuid: &str, conn: &DbConn) -> bool { | ||||
|         self.get_access_restrictions(user_uuid, conn).is_some() | ||||
|     pub async fn is_accessible_to_user(&self, user_uuid: &str, conn: &DbConn) -> bool { | ||||
|         self.get_access_restrictions(user_uuid, conn).await.is_some() | ||||
|     } | ||||
| 
 | ||||
|     // Returns whether this cipher is a favorite of the specified user.
 | ||||
|     pub fn is_favorite(&self, user_uuid: &str, conn: &DbConn) -> bool { | ||||
|         Favorite::is_favorite(&self.uuid, user_uuid, conn) | ||||
|     pub async fn is_favorite(&self, user_uuid: &str, conn: &DbConn) -> bool { | ||||
|         Favorite::is_favorite(&self.uuid, user_uuid, conn).await | ||||
|     } | ||||
| 
 | ||||
|     // Sets whether this cipher is a favorite of the specified user.
 | ||||
|     pub fn set_favorite(&self, favorite: Option<bool>, user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|     pub async fn set_favorite(&self, favorite: Option<bool>, user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         match favorite { | ||||
|             None => Ok(()), // No change requested.
 | ||||
|             Some(status) => Favorite::set_favorite(status, &self.uuid, user_uuid, conn), | ||||
|             Some(status) => Favorite::set_favorite(status, &self.uuid, user_uuid, conn).await, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn get_folder_uuid(&self, user_uuid: &str, conn: &DbConn) -> Option<String> { | ||||
|     pub async fn get_folder_uuid(&self, user_uuid: &str, conn: &DbConn) -> Option<String> { | ||||
|         db_run! {conn: { | ||||
|             folders_ciphers::table | ||||
|                 .inner_join(folders::table) | ||||
|  | @ -415,7 +416,7 @@ impl Cipher { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|     pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|         db_run! {conn: { | ||||
|             ciphers::table | ||||
|                 .filter(ciphers::uuid.eq(uuid)) | ||||
|  | @ -437,7 +438,7 @@ impl Cipher { | |||
|     // true, then the non-interesting ciphers will not be returned. As a
 | ||||
|     // result, those ciphers will not appear in "My Vault" for the org
 | ||||
|     // owner/admin, but they can still be accessed via the org vault view.
 | ||||
|     pub fn find_by_user(user_uuid: &str, visible_only: bool, conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_by_user(user_uuid: &str, visible_only: bool, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! {conn: { | ||||
|             let mut query = ciphers::table | ||||
|                 .left_join(ciphers_collections::table.on( | ||||
|  | @ -472,12 +473,12 @@ impl Cipher { | |||
|     } | ||||
| 
 | ||||
|     // Find all ciphers visible to the specified user.
 | ||||
|     pub fn find_by_user_visible(user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         Self::find_by_user(user_uuid, true, conn) | ||||
|     pub async fn find_by_user_visible(user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         Self::find_by_user(user_uuid, true, conn).await | ||||
|     } | ||||
| 
 | ||||
|     // Find all ciphers directly owned by the specified user.
 | ||||
|     pub fn find_owned_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_owned_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! {conn: { | ||||
|             ciphers::table | ||||
|                 .filter( | ||||
|  | @ -488,7 +489,7 @@ impl Cipher { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn count_owned_by_user(user_uuid: &str, conn: &DbConn) -> i64 { | ||||
|     pub async fn count_owned_by_user(user_uuid: &str, conn: &DbConn) -> i64 { | ||||
|         db_run! {conn: { | ||||
|             ciphers::table | ||||
|                 .filter(ciphers::user_uuid.eq(user_uuid)) | ||||
|  | @ -499,7 +500,7 @@ impl Cipher { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! {conn: { | ||||
|             ciphers::table | ||||
|                 .filter(ciphers::organization_uuid.eq(org_uuid)) | ||||
|  | @ -507,7 +508,7 @@ impl Cipher { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn count_by_org(org_uuid: &str, conn: &DbConn) -> i64 { | ||||
|     pub async fn count_by_org(org_uuid: &str, conn: &DbConn) -> i64 { | ||||
|         db_run! {conn: { | ||||
|             ciphers::table | ||||
|                 .filter(ciphers::organization_uuid.eq(org_uuid)) | ||||
|  | @ -518,7 +519,7 @@ impl Cipher { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_folder(folder_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_by_folder(folder_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! {conn: { | ||||
|             folders_ciphers::table.inner_join(ciphers::table) | ||||
|                 .filter(folders_ciphers::folder_uuid.eq(folder_uuid)) | ||||
|  | @ -528,7 +529,7 @@ impl Cipher { | |||
|     } | ||||
| 
 | ||||
|     /// Find all ciphers that were deleted before the specified datetime.
 | ||||
|     pub fn find_deleted_before(dt: &NaiveDateTime, conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_deleted_before(dt: &NaiveDateTime, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! {conn: { | ||||
|             ciphers::table | ||||
|                 .filter(ciphers::deleted_at.lt(dt)) | ||||
|  | @ -536,7 +537,7 @@ impl Cipher { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn get_collections(&self, user_id: &str, conn: &DbConn) -> Vec<String> { | ||||
|     pub async fn get_collections(&self, user_id: &str, conn: &DbConn) -> Vec<String> { | ||||
|         db_run! {conn: { | ||||
|             ciphers_collections::table | ||||
|             .inner_join(collections::table.on( | ||||
|  |  | |||
|  | @ -57,11 +57,11 @@ impl Collection { | |||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     pub fn to_json_details(&self, user_uuid: &str, conn: &DbConn) -> Value { | ||||
|     pub async fn to_json_details(&self, user_uuid: &str, conn: &DbConn) -> Value { | ||||
|         let mut json_object = self.to_json(); | ||||
|         json_object["Object"] = json!("collectionDetails"); | ||||
|         json_object["ReadOnly"] = json!(!self.is_writable_by_user(user_uuid, conn)); | ||||
|         json_object["HidePasswords"] = json!(self.hide_passwords_for_user(user_uuid, conn)); | ||||
|         json_object["ReadOnly"] = json!(!self.is_writable_by_user(user_uuid, conn).await); | ||||
|         json_object["HidePasswords"] = json!(self.hide_passwords_for_user(user_uuid, conn).await); | ||||
|         json_object | ||||
|     } | ||||
| } | ||||
|  | @ -73,8 +73,8 @@ use crate::error::MapResult; | |||
| 
 | ||||
| /// Database methods
 | ||||
| impl Collection { | ||||
|     pub fn save(&self, conn: &DbConn) -> EmptyResult { | ||||
|         self.update_users_revision(conn); | ||||
|     pub async fn save(&self, conn: &DbConn) -> EmptyResult { | ||||
|         self.update_users_revision(conn).await; | ||||
| 
 | ||||
|         db_run! { conn: | ||||
|             sqlite, mysql { | ||||
|  | @ -107,10 +107,10 @@ impl Collection { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete(self, conn: &DbConn) -> EmptyResult { | ||||
|         self.update_users_revision(conn); | ||||
|         CollectionCipher::delete_all_by_collection(&self.uuid, conn)?; | ||||
|         CollectionUser::delete_all_by_collection(&self.uuid, conn)?; | ||||
|     pub async fn delete(self, conn: &DbConn) -> EmptyResult { | ||||
|         self.update_users_revision(conn).await; | ||||
|         CollectionCipher::delete_all_by_collection(&self.uuid, conn).await?; | ||||
|         CollectionUser::delete_all_by_collection(&self.uuid, conn).await?; | ||||
| 
 | ||||
|         db_run! { conn: { | ||||
|             diesel::delete(collections::table.filter(collections::uuid.eq(self.uuid))) | ||||
|  | @ -119,20 +119,20 @@ impl Collection { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         for collection in Self::find_by_organization(org_uuid, conn) { | ||||
|             collection.delete(conn)?; | ||||
|     pub async fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         for collection in Self::find_by_organization(org_uuid, conn).await { | ||||
|             collection.delete(conn).await?; | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     pub fn update_users_revision(&self, conn: &DbConn) { | ||||
|         UserOrganization::find_by_collection_and_org(&self.uuid, &self.org_uuid, conn).iter().for_each(|user_org| { | ||||
|             User::update_uuid_revision(&user_org.user_uuid, conn); | ||||
|         }); | ||||
|     pub async fn update_users_revision(&self, conn: &DbConn) { | ||||
|         for user_org in UserOrganization::find_by_collection_and_org(&self.uuid, &self.org_uuid, conn).await.iter() { | ||||
|             User::update_uuid_revision(&user_org.user_uuid, conn).await; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|     pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|         db_run! { conn: { | ||||
|             collections::table | ||||
|                 .filter(collections::uuid.eq(uuid)) | ||||
|  | @ -142,7 +142,7 @@ impl Collection { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_user_uuid(user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_by_user_uuid(user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! { conn: { | ||||
|             collections::table | ||||
|             .left_join(users_collections::table.on( | ||||
|  | @ -167,11 +167,11 @@ impl Collection { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_organization_and_user_uuid(org_uuid: &str, user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         Self::find_by_user_uuid(user_uuid, conn).into_iter().filter(|c| c.org_uuid == org_uuid).collect() | ||||
|     pub async fn find_by_organization_and_user_uuid(org_uuid: &str, user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         Self::find_by_user_uuid(user_uuid, conn).await.into_iter().filter(|c| c.org_uuid == org_uuid).collect() | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_organization(org_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_by_organization(org_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! { conn: { | ||||
|             collections::table | ||||
|                 .filter(collections::org_uuid.eq(org_uuid)) | ||||
|  | @ -181,7 +181,7 @@ impl Collection { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_uuid_and_org(uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|     pub async fn find_by_uuid_and_org(uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|         db_run! { conn: { | ||||
|             collections::table | ||||
|                 .filter(collections::uuid.eq(uuid)) | ||||
|  | @ -193,7 +193,7 @@ impl Collection { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_uuid_and_user(uuid: &str, user_uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|     pub async fn find_by_uuid_and_user(uuid: &str, user_uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|         db_run! { conn: { | ||||
|             collections::table | ||||
|             .left_join(users_collections::table.on( | ||||
|  | @ -219,8 +219,8 @@ impl Collection { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn is_writable_by_user(&self, user_uuid: &str, conn: &DbConn) -> bool { | ||||
|         match UserOrganization::find_by_user_and_org(user_uuid, &self.org_uuid, conn) { | ||||
|     pub async fn is_writable_by_user(&self, user_uuid: &str, conn: &DbConn) -> bool { | ||||
|         match UserOrganization::find_by_user_and_org(user_uuid, &self.org_uuid, conn).await { | ||||
|             None => false, // Not in Org
 | ||||
|             Some(user_org) => { | ||||
|                 if user_org.has_full_access() { | ||||
|  | @ -241,8 +241,8 @@ impl Collection { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn hide_passwords_for_user(&self, user_uuid: &str, conn: &DbConn) -> bool { | ||||
|         match UserOrganization::find_by_user_and_org(user_uuid, &self.org_uuid, conn) { | ||||
|     pub async fn hide_passwords_for_user(&self, user_uuid: &str, conn: &DbConn) -> bool { | ||||
|         match UserOrganization::find_by_user_and_org(user_uuid, &self.org_uuid, conn).await { | ||||
|             None => true, // Not in Org
 | ||||
|             Some(user_org) => { | ||||
|                 if user_org.has_full_access() { | ||||
|  | @ -266,7 +266,7 @@ impl Collection { | |||
| 
 | ||||
| /// Database methods
 | ||||
| impl CollectionUser { | ||||
|     pub fn find_by_organization_and_user_uuid(org_uuid: &str, user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_by_organization_and_user_uuid(org_uuid: &str, user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! { conn: { | ||||
|             users_collections::table | ||||
|                 .filter(users_collections::user_uuid.eq(user_uuid)) | ||||
|  | @ -279,14 +279,14 @@ impl CollectionUser { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn save( | ||||
|     pub async fn save( | ||||
|         user_uuid: &str, | ||||
|         collection_uuid: &str, | ||||
|         read_only: bool, | ||||
|         hide_passwords: bool, | ||||
|         conn: &DbConn, | ||||
|     ) -> EmptyResult { | ||||
|         User::update_uuid_revision(user_uuid, conn); | ||||
|         User::update_uuid_revision(user_uuid, conn).await; | ||||
| 
 | ||||
|         db_run! { conn: | ||||
|             sqlite, mysql { | ||||
|  | @ -337,8 +337,8 @@ impl CollectionUser { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete(self, conn: &DbConn) -> EmptyResult { | ||||
|         User::update_uuid_revision(&self.user_uuid, conn); | ||||
|     pub async fn delete(self, conn: &DbConn) -> EmptyResult { | ||||
|         User::update_uuid_revision(&self.user_uuid, conn).await; | ||||
| 
 | ||||
|         db_run! { conn: { | ||||
|             diesel::delete( | ||||
|  | @ -351,7 +351,7 @@ impl CollectionUser { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_collection(collection_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_by_collection(collection_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! { conn: { | ||||
|             users_collections::table | ||||
|                 .filter(users_collections::collection_uuid.eq(collection_uuid)) | ||||
|  | @ -362,7 +362,7 @@ impl CollectionUser { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_collection_and_user(collection_uuid: &str, user_uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|     pub async fn find_by_collection_and_user(collection_uuid: &str, user_uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|         db_run! { conn: { | ||||
|             users_collections::table | ||||
|                 .filter(users_collections::collection_uuid.eq(collection_uuid)) | ||||
|  | @ -374,10 +374,10 @@ impl CollectionUser { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete_all_by_collection(collection_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         CollectionUser::find_by_collection(collection_uuid, conn).iter().for_each(|collection| { | ||||
|             User::update_uuid_revision(&collection.user_uuid, conn); | ||||
|         }); | ||||
|     pub async fn delete_all_by_collection(collection_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         for collection in CollectionUser::find_by_collection(collection_uuid, conn).await.iter() { | ||||
|             User::update_uuid_revision(&collection.user_uuid, conn).await; | ||||
|         } | ||||
| 
 | ||||
|         db_run! { conn: { | ||||
|             diesel::delete(users_collections::table.filter(users_collections::collection_uuid.eq(collection_uuid))) | ||||
|  | @ -386,8 +386,8 @@ impl CollectionUser { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete_all_by_user_and_org(user_uuid: &str, org_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         let collectionusers = Self::find_by_organization_and_user_uuid(org_uuid, user_uuid, conn); | ||||
|     pub async fn delete_all_by_user_and_org(user_uuid: &str, org_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         let collectionusers = Self::find_by_organization_and_user_uuid(org_uuid, user_uuid, conn).await; | ||||
| 
 | ||||
|         db_run! { conn: { | ||||
|             for user in collectionusers { | ||||
|  | @ -405,8 +405,8 @@ impl CollectionUser { | |||
| 
 | ||||
| /// Database methods
 | ||||
| impl CollectionCipher { | ||||
|     pub fn save(cipher_uuid: &str, collection_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         Self::update_users_revision(collection_uuid, conn); | ||||
|     pub async fn save(cipher_uuid: &str, collection_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         Self::update_users_revision(collection_uuid, conn).await; | ||||
| 
 | ||||
|         db_run! { conn: | ||||
|             sqlite, mysql { | ||||
|  | @ -435,8 +435,8 @@ impl CollectionCipher { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete(cipher_uuid: &str, collection_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         Self::update_users_revision(collection_uuid, conn); | ||||
|     pub async fn delete(cipher_uuid: &str, collection_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         Self::update_users_revision(collection_uuid, conn).await; | ||||
| 
 | ||||
|         db_run! { conn: { | ||||
|             diesel::delete( | ||||
|  | @ -449,7 +449,7 @@ impl CollectionCipher { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|     pub async fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         db_run! { conn: { | ||||
|             diesel::delete(ciphers_collections::table.filter(ciphers_collections::cipher_uuid.eq(cipher_uuid))) | ||||
|                 .execute(conn) | ||||
|  | @ -457,7 +457,7 @@ impl CollectionCipher { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete_all_by_collection(collection_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|     pub async fn delete_all_by_collection(collection_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         db_run! { conn: { | ||||
|             diesel::delete(ciphers_collections::table.filter(ciphers_collections::collection_uuid.eq(collection_uuid))) | ||||
|                 .execute(conn) | ||||
|  | @ -465,9 +465,9 @@ impl CollectionCipher { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn update_users_revision(collection_uuid: &str, conn: &DbConn) { | ||||
|         if let Some(collection) = Collection::find_by_uuid(collection_uuid, conn) { | ||||
|             collection.update_users_revision(conn); | ||||
|     pub async fn update_users_revision(collection_uuid: &str, conn: &DbConn) { | ||||
|         if let Some(collection) = Collection::find_by_uuid(collection_uuid, conn).await { | ||||
|             collection.update_users_revision(conn).await; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -118,7 +118,7 @@ use crate::error::MapResult; | |||
| 
 | ||||
| /// Database methods
 | ||||
| impl Device { | ||||
|     pub fn save(&mut self, conn: &DbConn) -> EmptyResult { | ||||
|     pub async fn save(&mut self, conn: &DbConn) -> EmptyResult { | ||||
|         self.updated_at = Utc::now().naive_utc(); | ||||
| 
 | ||||
|         db_run! { conn: | ||||
|  | @ -138,7 +138,7 @@ impl Device { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete(self, conn: &DbConn) -> EmptyResult { | ||||
|     pub async fn delete(self, conn: &DbConn) -> EmptyResult { | ||||
|         db_run! { conn: { | ||||
|             diesel::delete(devices::table.filter(devices::uuid.eq(self.uuid))) | ||||
|                 .execute(conn) | ||||
|  | @ -146,14 +146,14 @@ impl Device { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         for device in Self::find_by_user(user_uuid, conn) { | ||||
|             device.delete(conn)?; | ||||
|     pub async fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         for device in Self::find_by_user(user_uuid, conn).await { | ||||
|             device.delete(conn).await?; | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|     pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|         db_run! { conn: { | ||||
|             devices::table | ||||
|                 .filter(devices::uuid.eq(uuid)) | ||||
|  | @ -163,7 +163,7 @@ impl Device { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_refresh_token(refresh_token: &str, conn: &DbConn) -> Option<Self> { | ||||
|     pub async fn find_by_refresh_token(refresh_token: &str, conn: &DbConn) -> Option<Self> { | ||||
|         db_run! { conn: { | ||||
|             devices::table | ||||
|                 .filter(devices::refresh_token.eq(refresh_token)) | ||||
|  | @ -173,7 +173,7 @@ impl Device { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! { conn: { | ||||
|             devices::table | ||||
|                 .filter(devices::user_uuid.eq(user_uuid)) | ||||
|  | @ -183,7 +183,7 @@ impl Device { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_latest_active_by_user(user_uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|     pub async fn find_latest_active_by_user(user_uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|         db_run! { conn: { | ||||
|             devices::table | ||||
|                 .filter(devices::user_uuid.eq(user_uuid)) | ||||
|  |  | |||
|  | @ -73,8 +73,8 @@ impl EmergencyAccess { | |||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     pub fn to_json_grantor_details(&self, conn: &DbConn) -> Value { | ||||
|         let grantor_user = User::find_by_uuid(&self.grantor_uuid, conn).expect("Grantor user not found."); | ||||
|     pub async fn to_json_grantor_details(&self, conn: &DbConn) -> Value { | ||||
|         let grantor_user = User::find_by_uuid(&self.grantor_uuid, conn).await.expect("Grantor user not found."); | ||||
| 
 | ||||
|         json!({ | ||||
|             "Id": self.uuid, | ||||
|  | @ -89,11 +89,11 @@ impl EmergencyAccess { | |||
|     } | ||||
| 
 | ||||
|     #[allow(clippy::manual_map)] | ||||
|     pub fn to_json_grantee_details(&self, conn: &DbConn) -> Value { | ||||
|     pub async fn to_json_grantee_details(&self, conn: &DbConn) -> Value { | ||||
|         let grantee_user = if let Some(grantee_uuid) = self.grantee_uuid.as_deref() { | ||||
|             Some(User::find_by_uuid(grantee_uuid, conn).expect("Grantee user not found.")) | ||||
|             Some(User::find_by_uuid(grantee_uuid, conn).await.expect("Grantee user not found.")) | ||||
|         } else if let Some(email) = self.email.as_deref() { | ||||
|             Some(User::find_by_mail(email, conn).expect("Grantee user not found.")) | ||||
|             Some(User::find_by_mail(email, conn).await.expect("Grantee user not found.")) | ||||
|         } else { | ||||
|             None | ||||
|         }; | ||||
|  | @ -155,8 +155,8 @@ use crate::api::EmptyResult; | |||
| use crate::error::MapResult; | ||||
| 
 | ||||
| impl EmergencyAccess { | ||||
|     pub fn save(&mut self, conn: &DbConn) -> EmptyResult { | ||||
|         User::update_uuid_revision(&self.grantor_uuid, conn); | ||||
|     pub async fn save(&mut self, conn: &DbConn) -> EmptyResult { | ||||
|         User::update_uuid_revision(&self.grantor_uuid, conn).await; | ||||
|         self.updated_at = Utc::now().naive_utc(); | ||||
| 
 | ||||
|         db_run! { conn: | ||||
|  | @ -190,18 +190,18 @@ impl EmergencyAccess { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         for ea in Self::find_all_by_grantor_uuid(user_uuid, conn) { | ||||
|             ea.delete(conn)?; | ||||
|     pub async fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         for ea in Self::find_all_by_grantor_uuid(user_uuid, conn).await { | ||||
|             ea.delete(conn).await?; | ||||
|         } | ||||
|         for ea in Self::find_all_by_grantee_uuid(user_uuid, conn) { | ||||
|             ea.delete(conn)?; | ||||
|         for ea in Self::find_all_by_grantee_uuid(user_uuid, conn).await { | ||||
|             ea.delete(conn).await?; | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete(self, conn: &DbConn) -> EmptyResult { | ||||
|         User::update_uuid_revision(&self.grantor_uuid, conn); | ||||
|     pub async fn delete(self, conn: &DbConn) -> EmptyResult { | ||||
|         User::update_uuid_revision(&self.grantor_uuid, conn).await; | ||||
| 
 | ||||
|         db_run! { conn: { | ||||
|             diesel::delete(emergency_access::table.filter(emergency_access::uuid.eq(self.uuid))) | ||||
|  | @ -210,7 +210,7 @@ impl EmergencyAccess { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|     pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|         db_run! { conn: { | ||||
|             emergency_access::table | ||||
|                 .filter(emergency_access::uuid.eq(uuid)) | ||||
|  | @ -219,7 +219,7 @@ impl EmergencyAccess { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_grantor_uuid_and_grantee_uuid_or_email( | ||||
|     pub async fn find_by_grantor_uuid_and_grantee_uuid_or_email( | ||||
|         grantor_uuid: &str, | ||||
|         grantee_uuid: &str, | ||||
|         email: &str, | ||||
|  | @ -234,7 +234,7 @@ impl EmergencyAccess { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_all_recoveries(conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_all_recoveries(conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! { conn: { | ||||
|             emergency_access::table | ||||
|                 .filter(emergency_access::status.eq(EmergencyAccessStatus::RecoveryInitiated as i32)) | ||||
|  | @ -242,7 +242,7 @@ impl EmergencyAccess { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_uuid_and_grantor_uuid(uuid: &str, grantor_uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|     pub async fn find_by_uuid_and_grantor_uuid(uuid: &str, grantor_uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|         db_run! { conn: { | ||||
|             emergency_access::table | ||||
|                 .filter(emergency_access::uuid.eq(uuid)) | ||||
|  | @ -252,7 +252,7 @@ impl EmergencyAccess { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_all_by_grantee_uuid(grantee_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_all_by_grantee_uuid(grantee_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! { conn: { | ||||
|             emergency_access::table | ||||
|                 .filter(emergency_access::grantee_uuid.eq(grantee_uuid)) | ||||
|  | @ -260,7 +260,7 @@ impl EmergencyAccess { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_invited_by_grantee_email(grantee_email: &str, conn: &DbConn) -> Option<Self> { | ||||
|     pub async fn find_invited_by_grantee_email(grantee_email: &str, conn: &DbConn) -> Option<Self> { | ||||
|         db_run! { conn: { | ||||
|             emergency_access::table | ||||
|                 .filter(emergency_access::email.eq(grantee_email)) | ||||
|  | @ -270,7 +270,7 @@ impl EmergencyAccess { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_all_by_grantor_uuid(grantor_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_all_by_grantor_uuid(grantor_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! { conn: { | ||||
|             emergency_access::table | ||||
|                 .filter(emergency_access::grantor_uuid.eq(grantor_uuid)) | ||||
|  |  | |||
|  | @ -19,7 +19,7 @@ use crate::error::MapResult; | |||
| 
 | ||||
| impl Favorite { | ||||
|     // Returns whether the specified cipher is a favorite of the specified user.
 | ||||
|     pub fn is_favorite(cipher_uuid: &str, user_uuid: &str, conn: &DbConn) -> bool { | ||||
|     pub async fn is_favorite(cipher_uuid: &str, user_uuid: &str, conn: &DbConn) -> bool { | ||||
|         db_run! { conn: { | ||||
|             let query = favorites::table | ||||
|                 .filter(favorites::cipher_uuid.eq(cipher_uuid)) | ||||
|  | @ -31,11 +31,11 @@ impl Favorite { | |||
|     } | ||||
| 
 | ||||
|     // Sets whether the specified cipher is a favorite of the specified user.
 | ||||
|     pub fn set_favorite(favorite: bool, cipher_uuid: &str, user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         let (old, new) = (Self::is_favorite(cipher_uuid, user_uuid, conn), favorite); | ||||
|     pub async fn set_favorite(favorite: bool, cipher_uuid: &str, user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         let (old, new) = (Self::is_favorite(cipher_uuid, user_uuid, conn).await, favorite); | ||||
|         match (old, new) { | ||||
|             (false, true) => { | ||||
|                 User::update_uuid_revision(user_uuid, conn); | ||||
|                 User::update_uuid_revision(user_uuid, conn).await; | ||||
|                 db_run! { conn: { | ||||
|                 diesel::insert_into(favorites::table) | ||||
|                     .values(( | ||||
|  | @ -47,7 +47,7 @@ impl Favorite { | |||
|                 }} | ||||
|             } | ||||
|             (true, false) => { | ||||
|                 User::update_uuid_revision(user_uuid, conn); | ||||
|                 User::update_uuid_revision(user_uuid, conn).await; | ||||
|                 db_run! { conn: { | ||||
|                     diesel::delete( | ||||
|                         favorites::table | ||||
|  | @ -64,7 +64,7 @@ impl Favorite { | |||
|     } | ||||
| 
 | ||||
|     // Delete all favorite entries associated with the specified cipher.
 | ||||
|     pub fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|     pub async fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         db_run! { conn: { | ||||
|             diesel::delete(favorites::table.filter(favorites::cipher_uuid.eq(cipher_uuid))) | ||||
|                 .execute(conn) | ||||
|  | @ -73,7 +73,7 @@ impl Favorite { | |||
|     } | ||||
| 
 | ||||
|     // Delete all favorite entries associated with the specified user.
 | ||||
|     pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|     pub async fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         db_run! { conn: { | ||||
|             diesel::delete(favorites::table.filter(favorites::user_uuid.eq(user_uuid))) | ||||
|                 .execute(conn) | ||||
|  |  | |||
|  | @ -70,8 +70,8 @@ use crate::error::MapResult; | |||
| 
 | ||||
| /// Database methods
 | ||||
| impl Folder { | ||||
|     pub fn save(&mut self, conn: &DbConn) -> EmptyResult { | ||||
|         User::update_uuid_revision(&self.user_uuid, conn); | ||||
|     pub async fn save(&mut self, conn: &DbConn) -> EmptyResult { | ||||
|         User::update_uuid_revision(&self.user_uuid, conn).await; | ||||
|         self.updated_at = Utc::now().naive_utc(); | ||||
| 
 | ||||
|         db_run! { conn: | ||||
|  | @ -105,9 +105,9 @@ impl Folder { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete(&self, conn: &DbConn) -> EmptyResult { | ||||
|         User::update_uuid_revision(&self.user_uuid, conn); | ||||
|         FolderCipher::delete_all_by_folder(&self.uuid, conn)?; | ||||
|     pub async fn delete(&self, conn: &DbConn) -> EmptyResult { | ||||
|         User::update_uuid_revision(&self.user_uuid, conn).await; | ||||
|         FolderCipher::delete_all_by_folder(&self.uuid, conn).await?; | ||||
| 
 | ||||
|         db_run! { conn: { | ||||
|             diesel::delete(folders::table.filter(folders::uuid.eq(&self.uuid))) | ||||
|  | @ -116,14 +116,14 @@ impl Folder { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         for folder in Self::find_by_user(user_uuid, conn) { | ||||
|             folder.delete(conn)?; | ||||
|     pub async fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         for folder in Self::find_by_user(user_uuid, conn).await { | ||||
|             folder.delete(conn).await?; | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|     pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|         db_run! { conn: { | ||||
|             folders::table | ||||
|                 .filter(folders::uuid.eq(uuid)) | ||||
|  | @ -133,7 +133,7 @@ impl Folder { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! { conn: { | ||||
|             folders::table | ||||
|                 .filter(folders::user_uuid.eq(user_uuid)) | ||||
|  | @ -145,7 +145,7 @@ impl Folder { | |||
| } | ||||
| 
 | ||||
| impl FolderCipher { | ||||
|     pub fn save(&self, conn: &DbConn) -> EmptyResult { | ||||
|     pub async fn save(&self, conn: &DbConn) -> EmptyResult { | ||||
|         db_run! { conn: | ||||
|             sqlite, mysql { | ||||
|                 // Not checking for ForeignKey Constraints here.
 | ||||
|  | @ -167,7 +167,7 @@ impl FolderCipher { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete(self, conn: &DbConn) -> EmptyResult { | ||||
|     pub async fn delete(self, conn: &DbConn) -> EmptyResult { | ||||
|         db_run! { conn: { | ||||
|             diesel::delete( | ||||
|                 folders_ciphers::table | ||||
|  | @ -179,7 +179,7 @@ impl FolderCipher { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|     pub async fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         db_run! { conn: { | ||||
|             diesel::delete(folders_ciphers::table.filter(folders_ciphers::cipher_uuid.eq(cipher_uuid))) | ||||
|                 .execute(conn) | ||||
|  | @ -187,7 +187,7 @@ impl FolderCipher { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete_all_by_folder(folder_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|     pub async fn delete_all_by_folder(folder_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         db_run! { conn: { | ||||
|             diesel::delete(folders_ciphers::table.filter(folders_ciphers::folder_uuid.eq(folder_uuid))) | ||||
|                 .execute(conn) | ||||
|  | @ -195,7 +195,7 @@ impl FolderCipher { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_folder_and_cipher(folder_uuid: &str, cipher_uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|     pub async fn find_by_folder_and_cipher(folder_uuid: &str, cipher_uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|         db_run! { conn: { | ||||
|             folders_ciphers::table | ||||
|                 .filter(folders_ciphers::folder_uuid.eq(folder_uuid)) | ||||
|  | @ -206,7 +206,7 @@ impl FolderCipher { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_folder(folder_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_by_folder(folder_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! { conn: { | ||||
|             folders_ciphers::table | ||||
|                 .filter(folders_ciphers::folder_uuid.eq(folder_uuid)) | ||||
|  |  | |||
|  | @ -72,7 +72,7 @@ impl OrgPolicy { | |||
| 
 | ||||
| /// Database methods
 | ||||
| impl OrgPolicy { | ||||
|     pub fn save(&self, conn: &DbConn) -> EmptyResult { | ||||
|     pub async fn save(&self, conn: &DbConn) -> EmptyResult { | ||||
|         db_run! { conn: | ||||
|             sqlite, mysql { | ||||
|                 match diesel::replace_into(org_policies::table) | ||||
|  | @ -115,7 +115,7 @@ impl OrgPolicy { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete(self, conn: &DbConn) -> EmptyResult { | ||||
|     pub async fn delete(self, conn: &DbConn) -> EmptyResult { | ||||
|         db_run! { conn: { | ||||
|             diesel::delete(org_policies::table.filter(org_policies::uuid.eq(self.uuid))) | ||||
|                 .execute(conn) | ||||
|  | @ -123,7 +123,7 @@ impl OrgPolicy { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|     pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|         db_run! { conn: { | ||||
|             org_policies::table | ||||
|                 .filter(org_policies::uuid.eq(uuid)) | ||||
|  | @ -133,7 +133,7 @@ impl OrgPolicy { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! { conn: { | ||||
|             org_policies::table | ||||
|                 .filter(org_policies::org_uuid.eq(org_uuid)) | ||||
|  | @ -143,7 +143,7 @@ impl OrgPolicy { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_confirmed_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_confirmed_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! { conn: { | ||||
|             org_policies::table | ||||
|                 .inner_join( | ||||
|  | @ -161,7 +161,7 @@ impl OrgPolicy { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_org_and_type(org_uuid: &str, atype: i32, conn: &DbConn) -> Option<Self> { | ||||
|     pub async fn find_by_org_and_type(org_uuid: &str, atype: i32, conn: &DbConn) -> Option<Self> { | ||||
|         db_run! { conn: { | ||||
|             org_policies::table | ||||
|                 .filter(org_policies::org_uuid.eq(org_uuid)) | ||||
|  | @ -172,7 +172,7 @@ impl OrgPolicy { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|     pub async fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         db_run! { conn: { | ||||
|             diesel::delete(org_policies::table.filter(org_policies::org_uuid.eq(org_uuid))) | ||||
|                 .execute(conn) | ||||
|  | @ -183,12 +183,12 @@ impl OrgPolicy { | |||
|     /// Returns true if the user belongs to an org that has enabled the specified policy type,
 | ||||
|     /// and the user is not an owner or admin of that org. This is only useful for checking
 | ||||
|     /// applicability of policy types that have these particular semantics.
 | ||||
|     pub fn is_applicable_to_user(user_uuid: &str, policy_type: OrgPolicyType, conn: &DbConn) -> bool { | ||||
|     pub async fn is_applicable_to_user(user_uuid: &str, policy_type: OrgPolicyType, conn: &DbConn) -> bool { | ||||
|         // TODO: Should check confirmed and accepted users
 | ||||
|         for policy in OrgPolicy::find_confirmed_by_user(user_uuid, conn) { | ||||
|         for policy in OrgPolicy::find_confirmed_by_user(user_uuid, conn).await { | ||||
|             if policy.enabled && policy.has_type(policy_type) { | ||||
|                 let org_uuid = &policy.org_uuid; | ||||
|                 if let Some(user) = UserOrganization::find_by_user_and_org(user_uuid, org_uuid, conn) { | ||||
|                 if let Some(user) = UserOrganization::find_by_user_and_org(user_uuid, org_uuid, conn).await { | ||||
|                     if user.atype < UserOrgType::Admin { | ||||
|                         return true; | ||||
|                     } | ||||
|  | @ -200,11 +200,11 @@ impl OrgPolicy { | |||
| 
 | ||||
|     /// Returns true if the user belongs to an org that has enabled the `DisableHideEmail`
 | ||||
|     /// option of the `Send Options` policy, and the user is not an owner or admin of that org.
 | ||||
|     pub fn is_hide_email_disabled(user_uuid: &str, conn: &DbConn) -> bool { | ||||
|         for policy in OrgPolicy::find_confirmed_by_user(user_uuid, conn) { | ||||
|     pub async fn is_hide_email_disabled(user_uuid: &str, conn: &DbConn) -> bool { | ||||
|         for policy in OrgPolicy::find_confirmed_by_user(user_uuid, conn).await { | ||||
|             if policy.enabled && policy.has_type(OrgPolicyType::SendOptions) { | ||||
|                 let org_uuid = &policy.org_uuid; | ||||
|                 if let Some(user) = UserOrganization::find_by_user_and_org(user_uuid, org_uuid, conn) { | ||||
|                 if let Some(user) = UserOrganization::find_by_user_and_org(user_uuid, org_uuid, conn).await { | ||||
|                     if user.atype < UserOrgType::Admin { | ||||
|                         match serde_json::from_str::<UpCase<SendOptionsPolicyData>>(&policy.data) { | ||||
|                             Ok(opts) => { | ||||
|  | @ -220,12 +220,4 @@ impl OrgPolicy { | |||
|         } | ||||
|         false | ||||
|     } | ||||
| 
 | ||||
|     /*pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
 | ||||
|         db_run! { conn: { | ||||
|             diesel::delete(twofactor::table.filter(twofactor::user_uuid.eq(user_uuid))) | ||||
|                 .execute(conn) | ||||
|                 .map_res("Error deleting twofactors") | ||||
|         }} | ||||
|     }*/ | ||||
| } | ||||
|  |  | |||
|  | @ -193,10 +193,10 @@ use crate::error::MapResult; | |||
| 
 | ||||
| /// Database methods
 | ||||
| impl Organization { | ||||
|     pub fn save(&self, conn: &DbConn) -> EmptyResult { | ||||
|         UserOrganization::find_by_org(&self.uuid, conn).iter().for_each(|user_org| { | ||||
|             User::update_uuid_revision(&user_org.user_uuid, conn); | ||||
|         }); | ||||
|     pub async fn save(&self, conn: &DbConn) -> EmptyResult { | ||||
|         for user_org in UserOrganization::find_by_org(&self.uuid, conn).await.iter() { | ||||
|             User::update_uuid_revision(&user_org.user_uuid, conn).await; | ||||
|         } | ||||
| 
 | ||||
|         db_run! { conn: | ||||
|             sqlite, mysql { | ||||
|  | @ -230,13 +230,13 @@ impl Organization { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete(self, conn: &DbConn) -> EmptyResult { | ||||
|     pub async fn delete(self, conn: &DbConn) -> EmptyResult { | ||||
|         use super::{Cipher, Collection}; | ||||
| 
 | ||||
|         Cipher::delete_all_by_organization(&self.uuid, conn)?; | ||||
|         Collection::delete_all_by_organization(&self.uuid, conn)?; | ||||
|         UserOrganization::delete_all_by_organization(&self.uuid, conn)?; | ||||
|         OrgPolicy::delete_all_by_organization(&self.uuid, conn)?; | ||||
|         Cipher::delete_all_by_organization(&self.uuid, conn).await?; | ||||
|         Collection::delete_all_by_organization(&self.uuid, conn).await?; | ||||
|         UserOrganization::delete_all_by_organization(&self.uuid, conn).await?; | ||||
|         OrgPolicy::delete_all_by_organization(&self.uuid, conn).await?; | ||||
| 
 | ||||
|         db_run! { conn: { | ||||
|             diesel::delete(organizations::table.filter(organizations::uuid.eq(self.uuid))) | ||||
|  | @ -245,7 +245,7 @@ impl Organization { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|     pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|         db_run! { conn: { | ||||
|             organizations::table | ||||
|                 .filter(organizations::uuid.eq(uuid)) | ||||
|  | @ -254,7 +254,7 @@ impl Organization { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn get_all(conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn get_all(conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! { conn: { | ||||
|             organizations::table.load::<OrganizationDb>(conn).expect("Error loading organizations").from_db() | ||||
|         }} | ||||
|  | @ -262,8 +262,8 @@ impl Organization { | |||
| } | ||||
| 
 | ||||
| impl UserOrganization { | ||||
|     pub fn to_json(&self, conn: &DbConn) -> Value { | ||||
|         let org = Organization::find_by_uuid(&self.org_uuid, conn).unwrap(); | ||||
|     pub async fn to_json(&self, conn: &DbConn) -> Value { | ||||
|         let org = Organization::find_by_uuid(&self.org_uuid, conn).await.unwrap(); | ||||
| 
 | ||||
|         json!({ | ||||
|             "Id": self.org_uuid, | ||||
|  | @ -322,8 +322,8 @@ impl UserOrganization { | |||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     pub fn to_json_user_details(&self, conn: &DbConn) -> Value { | ||||
|         let user = User::find_by_uuid(&self.user_uuid, conn).unwrap(); | ||||
|     pub async fn to_json_user_details(&self, conn: &DbConn) -> Value { | ||||
|         let user = User::find_by_uuid(&self.user_uuid, conn).await.unwrap(); | ||||
| 
 | ||||
|         json!({ | ||||
|             "Id": self.uuid, | ||||
|  | @ -347,11 +347,12 @@ impl UserOrganization { | |||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     pub fn to_json_details(&self, conn: &DbConn) -> Value { | ||||
|     pub async fn to_json_details(&self, conn: &DbConn) -> Value { | ||||
|         let coll_uuids = if self.access_all { | ||||
|             vec![] // If we have complete access, no need to fill the array
 | ||||
|         } else { | ||||
|             let collections = CollectionUser::find_by_organization_and_user_uuid(&self.org_uuid, &self.user_uuid, conn); | ||||
|             let collections = | ||||
|                 CollectionUser::find_by_organization_and_user_uuid(&self.org_uuid, &self.user_uuid, conn).await; | ||||
|             collections | ||||
|                 .iter() | ||||
|                 .map(|c| { | ||||
|  | @ -376,8 +377,8 @@ impl UserOrganization { | |||
|             "Object": "organizationUserDetails", | ||||
|         }) | ||||
|     } | ||||
|     pub fn save(&self, conn: &DbConn) -> EmptyResult { | ||||
|         User::update_uuid_revision(&self.user_uuid, conn); | ||||
|     pub async fn save(&self, conn: &DbConn) -> EmptyResult { | ||||
|         User::update_uuid_revision(&self.user_uuid, conn).await; | ||||
| 
 | ||||
|         db_run! { conn: | ||||
|             sqlite, mysql { | ||||
|  | @ -410,10 +411,10 @@ impl UserOrganization { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete(self, conn: &DbConn) -> EmptyResult { | ||||
|         User::update_uuid_revision(&self.user_uuid, conn); | ||||
|     pub async fn delete(self, conn: &DbConn) -> EmptyResult { | ||||
|         User::update_uuid_revision(&self.user_uuid, conn).await; | ||||
| 
 | ||||
|         CollectionUser::delete_all_by_user_and_org(&self.user_uuid, &self.org_uuid, conn)?; | ||||
|         CollectionUser::delete_all_by_user_and_org(&self.user_uuid, &self.org_uuid, conn).await?; | ||||
| 
 | ||||
|         db_run! { conn: { | ||||
|             diesel::delete(users_organizations::table.filter(users_organizations::uuid.eq(self.uuid))) | ||||
|  | @ -422,23 +423,23 @@ impl UserOrganization { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         for user_org in Self::find_by_org(org_uuid, conn) { | ||||
|             user_org.delete(conn)?; | ||||
|     pub async fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         for user_org in Self::find_by_org(org_uuid, conn).await { | ||||
|             user_org.delete(conn).await?; | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         for user_org in Self::find_any_state_by_user(user_uuid, conn) { | ||||
|             user_org.delete(conn)?; | ||||
|     pub async fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         for user_org in Self::find_any_state_by_user(user_uuid, conn).await { | ||||
|             user_org.delete(conn).await?; | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_email_and_org(email: &str, org_id: &str, conn: &DbConn) -> Option<UserOrganization> { | ||||
|         if let Some(user) = super::User::find_by_mail(email, conn) { | ||||
|             if let Some(user_org) = UserOrganization::find_by_user_and_org(&user.uuid, org_id, conn) { | ||||
|     pub async fn find_by_email_and_org(email: &str, org_id: &str, conn: &DbConn) -> Option<UserOrganization> { | ||||
|         if let Some(user) = super::User::find_by_mail(email, conn).await { | ||||
|             if let Some(user_org) = UserOrganization::find_by_user_and_org(&user.uuid, org_id, conn).await { | ||||
|                 return Some(user_org); | ||||
|             } | ||||
|         } | ||||
|  | @ -458,7 +459,7 @@ impl UserOrganization { | |||
|         (self.access_all || self.atype >= UserOrgType::Admin) && self.has_status(UserOrgStatus::Confirmed) | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|     pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|         db_run! { conn: { | ||||
|             users_organizations::table | ||||
|                 .filter(users_organizations::uuid.eq(uuid)) | ||||
|  | @ -467,7 +468,7 @@ impl UserOrganization { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_uuid_and_org(uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|     pub async fn find_by_uuid_and_org(uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|         db_run! { conn: { | ||||
|             users_organizations::table | ||||
|                 .filter(users_organizations::uuid.eq(uuid)) | ||||
|  | @ -477,7 +478,7 @@ impl UserOrganization { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_confirmed_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_confirmed_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! { conn: { | ||||
|             users_organizations::table | ||||
|                 .filter(users_organizations::user_uuid.eq(user_uuid)) | ||||
|  | @ -487,7 +488,7 @@ impl UserOrganization { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_invited_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_invited_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! { conn: { | ||||
|             users_organizations::table | ||||
|                 .filter(users_organizations::user_uuid.eq(user_uuid)) | ||||
|  | @ -497,7 +498,7 @@ impl UserOrganization { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_any_state_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_any_state_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! { conn: { | ||||
|             users_organizations::table | ||||
|                 .filter(users_organizations::user_uuid.eq(user_uuid)) | ||||
|  | @ -506,7 +507,7 @@ impl UserOrganization { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! { conn: { | ||||
|             users_organizations::table | ||||
|                 .filter(users_organizations::org_uuid.eq(org_uuid)) | ||||
|  | @ -515,7 +516,7 @@ impl UserOrganization { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn count_by_org(org_uuid: &str, conn: &DbConn) -> i64 { | ||||
|     pub async fn count_by_org(org_uuid: &str, conn: &DbConn) -> i64 { | ||||
|         db_run! { conn: { | ||||
|             users_organizations::table | ||||
|                 .filter(users_organizations::org_uuid.eq(org_uuid)) | ||||
|  | @ -526,7 +527,7 @@ impl UserOrganization { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_org_and_type(org_uuid: &str, atype: i32, conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_by_org_and_type(org_uuid: &str, atype: i32, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! { conn: { | ||||
|             users_organizations::table | ||||
|                 .filter(users_organizations::org_uuid.eq(org_uuid)) | ||||
|  | @ -536,7 +537,7 @@ impl UserOrganization { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_user_and_org(user_uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|     pub async fn find_by_user_and_org(user_uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|         db_run! { conn: { | ||||
|             users_organizations::table | ||||
|                 .filter(users_organizations::user_uuid.eq(user_uuid)) | ||||
|  | @ -546,7 +547,7 @@ impl UserOrganization { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_user_and_policy(user_uuid: &str, policy_type: OrgPolicyType, conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_by_user_and_policy(user_uuid: &str, policy_type: OrgPolicyType, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! { conn: { | ||||
|             users_organizations::table | ||||
|                 .inner_join( | ||||
|  | @ -565,7 +566,7 @@ impl UserOrganization { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_cipher_and_org(cipher_uuid: &str, org_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_by_cipher_and_org(cipher_uuid: &str, org_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! { conn: { | ||||
|             users_organizations::table | ||||
|             .filter(users_organizations::org_uuid.eq(org_uuid)) | ||||
|  | @ -587,7 +588,7 @@ impl UserOrganization { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_collection_and_org(collection_uuid: &str, org_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_by_collection_and_org(collection_uuid: &str, org_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! { conn: { | ||||
|             users_organizations::table | ||||
|             .filter(users_organizations::org_uuid.eq(org_uuid)) | ||||
|  |  | |||
|  | @ -47,7 +47,7 @@ pub enum SendType { | |||
| } | ||||
| 
 | ||||
| impl Send { | ||||
|     pub fn new(atype: i32, name: String, data: String, akey: String, deletion_date: NaiveDateTime) -> Self { | ||||
|     pub async fn new(atype: i32, name: String, data: String, akey: String, deletion_date: NaiveDateTime) -> Self { | ||||
|         let now = Utc::now().naive_utc(); | ||||
| 
 | ||||
|         Self { | ||||
|  | @ -103,7 +103,7 @@ impl Send { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn creator_identifier(&self, conn: &DbConn) -> Option<String> { | ||||
|     pub async fn creator_identifier(&self, conn: &DbConn) -> Option<String> { | ||||
|         if let Some(hide_email) = self.hide_email { | ||||
|             if hide_email { | ||||
|                 return None; | ||||
|  | @ -111,7 +111,7 @@ impl Send { | |||
|         } | ||||
| 
 | ||||
|         if let Some(user_uuid) = &self.user_uuid { | ||||
|             if let Some(user) = User::find_by_uuid(user_uuid, conn) { | ||||
|             if let Some(user) = User::find_by_uuid(user_uuid, conn).await { | ||||
|                 return Some(user.email); | ||||
|             } | ||||
|         } | ||||
|  | @ -150,7 +150,7 @@ impl Send { | |||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     pub fn to_json_access(&self, conn: &DbConn) -> Value { | ||||
|     pub async fn to_json_access(&self, conn: &DbConn) -> Value { | ||||
|         use crate::util::format_date; | ||||
| 
 | ||||
|         let data: Value = serde_json::from_str(&self.data).unwrap_or_default(); | ||||
|  | @ -164,7 +164,7 @@ impl Send { | |||
|             "File": if self.atype == SendType::File as i32 { Some(&data) } else { None }, | ||||
| 
 | ||||
|             "ExpirationDate": self.expiration_date.as_ref().map(format_date), | ||||
|             "CreatorIdentifier": self.creator_identifier(conn), | ||||
|             "CreatorIdentifier": self.creator_identifier(conn).await, | ||||
|             "Object": "send-access", | ||||
|         }) | ||||
|     } | ||||
|  | @ -176,8 +176,8 @@ use crate::api::EmptyResult; | |||
| use crate::error::MapResult; | ||||
| 
 | ||||
| impl Send { | ||||
|     pub fn save(&mut self, conn: &DbConn) -> EmptyResult { | ||||
|         self.update_users_revision(conn); | ||||
|     pub async fn save(&mut self, conn: &DbConn) -> EmptyResult { | ||||
|         self.update_users_revision(conn).await; | ||||
|         self.revision_date = Utc::now().naive_utc(); | ||||
| 
 | ||||
|         db_run! { conn: | ||||
|  | @ -211,8 +211,8 @@ impl Send { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete(&self, conn: &DbConn) -> EmptyResult { | ||||
|         self.update_users_revision(conn); | ||||
|     pub async fn delete(&self, conn: &DbConn) -> EmptyResult { | ||||
|         self.update_users_revision(conn).await; | ||||
| 
 | ||||
|         if self.atype == SendType::File as i32 { | ||||
|             std::fs::remove_dir_all(std::path::Path::new(&crate::CONFIG.sends_folder()).join(&self.uuid)).ok(); | ||||
|  | @ -226,17 +226,17 @@ impl Send { | |||
|     } | ||||
| 
 | ||||
|     /// Purge all sends that are past their deletion date.
 | ||||
|     pub fn purge(conn: &DbConn) { | ||||
|         for send in Self::find_by_past_deletion_date(conn) { | ||||
|             send.delete(conn).ok(); | ||||
|     pub async fn purge(conn: &DbConn) { | ||||
|         for send in Self::find_by_past_deletion_date(conn).await { | ||||
|             send.delete(conn).await.ok(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn update_users_revision(&self, conn: &DbConn) -> Vec<String> { | ||||
|     pub async fn update_users_revision(&self, conn: &DbConn) -> Vec<String> { | ||||
|         let mut user_uuids = Vec::new(); | ||||
|         match &self.user_uuid { | ||||
|             Some(user_uuid) => { | ||||
|                 User::update_uuid_revision(user_uuid, conn); | ||||
|                 User::update_uuid_revision(user_uuid, conn).await; | ||||
|                 user_uuids.push(user_uuid.clone()) | ||||
|             } | ||||
|             None => { | ||||
|  | @ -246,14 +246,14 @@ impl Send { | |||
|         user_uuids | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         for send in Self::find_by_user(user_uuid, conn) { | ||||
|             send.delete(conn)?; | ||||
|     pub async fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         for send in Self::find_by_user(user_uuid, conn).await { | ||||
|             send.delete(conn).await?; | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_access_id(access_id: &str, conn: &DbConn) -> Option<Self> { | ||||
|     pub async fn find_by_access_id(access_id: &str, conn: &DbConn) -> Option<Self> { | ||||
|         use data_encoding::BASE64URL_NOPAD; | ||||
|         use uuid::Uuid; | ||||
| 
 | ||||
|  | @ -267,10 +267,10 @@ impl Send { | |||
|             Err(_) => return None, | ||||
|         }; | ||||
| 
 | ||||
|         Self::find_by_uuid(&uuid, conn) | ||||
|         Self::find_by_uuid(&uuid, conn).await | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|     pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|         db_run! {conn: { | ||||
|             sends::table | ||||
|                 .filter(sends::uuid.eq(uuid)) | ||||
|  | @ -280,7 +280,7 @@ impl Send { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! {conn: { | ||||
|             sends::table | ||||
|                 .filter(sends::user_uuid.eq(user_uuid)) | ||||
|  | @ -288,7 +288,7 @@ impl Send { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! {conn: { | ||||
|             sends::table | ||||
|                 .filter(sends::organization_uuid.eq(org_uuid)) | ||||
|  | @ -296,7 +296,7 @@ impl Send { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_past_deletion_date(conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_by_past_deletion_date(conn: &DbConn) -> Vec<Self> { | ||||
|         let now = Utc::now().naive_utc(); | ||||
|         db_run! {conn: { | ||||
|             sends::table | ||||
|  |  | |||
|  | @ -71,7 +71,7 @@ impl TwoFactor { | |||
| 
 | ||||
| /// Database methods
 | ||||
| impl TwoFactor { | ||||
|     pub fn save(&self, conn: &DbConn) -> EmptyResult { | ||||
|     pub async fn save(&self, conn: &DbConn) -> EmptyResult { | ||||
|         db_run! { conn: | ||||
|             sqlite, mysql { | ||||
|                 match diesel::replace_into(twofactor::table) | ||||
|  | @ -110,7 +110,7 @@ impl TwoFactor { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete(self, conn: &DbConn) -> EmptyResult { | ||||
|     pub async fn delete(self, conn: &DbConn) -> EmptyResult { | ||||
|         db_run! { conn: { | ||||
|             diesel::delete(twofactor::table.filter(twofactor::uuid.eq(self.uuid))) | ||||
|                 .execute(conn) | ||||
|  | @ -118,7 +118,7 @@ impl TwoFactor { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! { conn: { | ||||
|             twofactor::table | ||||
|                 .filter(twofactor::user_uuid.eq(user_uuid)) | ||||
|  | @ -129,7 +129,7 @@ impl TwoFactor { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_user_and_type(user_uuid: &str, atype: i32, conn: &DbConn) -> Option<Self> { | ||||
|     pub async fn find_by_user_and_type(user_uuid: &str, atype: i32, conn: &DbConn) -> Option<Self> { | ||||
|         db_run! { conn: { | ||||
|             twofactor::table | ||||
|                 .filter(twofactor::user_uuid.eq(user_uuid)) | ||||
|  | @ -140,7 +140,7 @@ impl TwoFactor { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|     pub async fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         db_run! { conn: { | ||||
|             diesel::delete(twofactor::table.filter(twofactor::user_uuid.eq(user_uuid))) | ||||
|                 .execute(conn) | ||||
|  | @ -148,7 +148,7 @@ impl TwoFactor { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn migrate_u2f_to_webauthn(conn: &DbConn) -> EmptyResult { | ||||
|     pub async fn migrate_u2f_to_webauthn(conn: &DbConn) -> EmptyResult { | ||||
|         let u2f_factors = db_run! { conn: { | ||||
|             twofactor::table | ||||
|                 .filter(twofactor::atype.eq(TwoFactorType::U2f as i32)) | ||||
|  | @ -168,7 +168,7 @@ impl TwoFactor { | |||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             let (_, mut webauthn_regs) = get_webauthn_registrations(&u2f.user_uuid, conn)?; | ||||
|             let (_, mut webauthn_regs) = get_webauthn_registrations(&u2f.user_uuid, conn).await?; | ||||
| 
 | ||||
|             // If the user already has webauthn registrations saved, don't overwrite them
 | ||||
|             if !webauthn_regs.is_empty() { | ||||
|  | @ -207,10 +207,11 @@ impl TwoFactor { | |||
|             } | ||||
| 
 | ||||
|             u2f.data = serde_json::to_string(®s)?; | ||||
|             u2f.save(conn)?; | ||||
|             u2f.save(conn).await?; | ||||
| 
 | ||||
|             TwoFactor::new(u2f.user_uuid.clone(), TwoFactorType::Webauthn, serde_json::to_string(&webauthn_regs)?) | ||||
|                 .save(conn)?; | ||||
|                 .save(conn) | ||||
|                 .await?; | ||||
|         } | ||||
| 
 | ||||
|         Ok(()) | ||||
|  |  | |||
|  | @ -22,7 +22,7 @@ db_object! { | |||
| } | ||||
| 
 | ||||
| impl TwoFactorIncomplete { | ||||
|     pub fn mark_incomplete( | ||||
|     pub async fn mark_incomplete( | ||||
|         user_uuid: &str, | ||||
|         device_uuid: &str, | ||||
|         device_name: &str, | ||||
|  | @ -36,7 +36,7 @@ impl TwoFactorIncomplete { | |||
|         // Don't update the data for an existing user/device pair, since that
 | ||||
|         // would allow an attacker to arbitrarily delay notifications by
 | ||||
|         // sending repeated 2FA attempts to reset the timer.
 | ||||
|         let existing = Self::find_by_user_and_device(user_uuid, device_uuid, conn); | ||||
|         let existing = Self::find_by_user_and_device(user_uuid, device_uuid, conn).await; | ||||
|         if existing.is_some() { | ||||
|             return Ok(()); | ||||
|         } | ||||
|  | @ -55,15 +55,15 @@ impl TwoFactorIncomplete { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn mark_complete(user_uuid: &str, device_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|     pub async fn mark_complete(user_uuid: &str, device_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         if CONFIG.incomplete_2fa_time_limit() <= 0 || !CONFIG.mail_enabled() { | ||||
|             return Ok(()); | ||||
|         } | ||||
| 
 | ||||
|         Self::delete_by_user_and_device(user_uuid, device_uuid, conn) | ||||
|         Self::delete_by_user_and_device(user_uuid, device_uuid, conn).await | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_user_and_device(user_uuid: &str, device_uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|     pub async fn find_by_user_and_device(user_uuid: &str, device_uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|         db_run! { conn: { | ||||
|             twofactor_incomplete::table | ||||
|                 .filter(twofactor_incomplete::user_uuid.eq(user_uuid)) | ||||
|  | @ -74,7 +74,7 @@ impl TwoFactorIncomplete { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_logins_before(dt: &NaiveDateTime, conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn find_logins_before(dt: &NaiveDateTime, conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! {conn: { | ||||
|             twofactor_incomplete::table | ||||
|                 .filter(twofactor_incomplete::login_time.lt(dt)) | ||||
|  | @ -84,11 +84,11 @@ impl TwoFactorIncomplete { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete(self, conn: &DbConn) -> EmptyResult { | ||||
|         Self::delete_by_user_and_device(&self.user_uuid, &self.device_uuid, conn) | ||||
|     pub async fn delete(self, conn: &DbConn) -> EmptyResult { | ||||
|         Self::delete_by_user_and_device(&self.user_uuid, &self.device_uuid, conn).await | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete_by_user_and_device(user_uuid: &str, device_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|     pub async fn delete_by_user_and_device(user_uuid: &str, device_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         db_run! { conn: { | ||||
|             diesel::delete(twofactor_incomplete::table | ||||
|                            .filter(twofactor_incomplete::user_uuid.eq(user_uuid)) | ||||
|  | @ -98,7 +98,7 @@ impl TwoFactorIncomplete { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|     pub async fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|         db_run! { conn: { | ||||
|             diesel::delete(twofactor_incomplete::table.filter(twofactor_incomplete::user_uuid.eq(user_uuid))) | ||||
|                 .execute(conn) | ||||
|  |  | |||
|  | @ -192,12 +192,20 @@ use crate::db::DbConn; | |||
| use crate::api::EmptyResult; | ||||
| use crate::error::MapResult; | ||||
| 
 | ||||
| use futures::{stream, stream::StreamExt}; | ||||
| 
 | ||||
| /// Database methods
 | ||||
| impl User { | ||||
|     pub fn to_json(&self, conn: &DbConn) -> Value { | ||||
|         let orgs = UserOrganization::find_confirmed_by_user(&self.uuid, conn); | ||||
|         let orgs_json: Vec<Value> = orgs.iter().map(|c| c.to_json(conn)).collect(); | ||||
|         let twofactor_enabled = !TwoFactor::find_by_user(&self.uuid, conn).is_empty(); | ||||
|     pub async fn to_json(&self, conn: &DbConn) -> Value { | ||||
|         let orgs_json = stream::iter(UserOrganization::find_confirmed_by_user(&self.uuid, conn).await) | ||||
|             .then(|c| async { | ||||
|                 let c = c; // Move out this single variable
 | ||||
|                 c.to_json(conn).await | ||||
|             }) | ||||
|             .collect::<Vec<Value>>() | ||||
|             .await; | ||||
| 
 | ||||
|         let twofactor_enabled = !TwoFactor::find_by_user(&self.uuid, conn).await.is_empty(); | ||||
| 
 | ||||
|         // TODO: Might want to save the status field in the DB
 | ||||
|         let status = if self.password_hash.is_empty() { | ||||
|  | @ -227,7 +235,7 @@ impl User { | |||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     pub fn save(&mut self, conn: &DbConn) -> EmptyResult { | ||||
|     pub async fn save(&mut self, conn: &DbConn) -> EmptyResult { | ||||
|         if self.email.trim().is_empty() { | ||||
|             err!("User email can't be empty") | ||||
|         } | ||||
|  | @ -265,26 +273,26 @@ impl User { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete(self, conn: &DbConn) -> EmptyResult { | ||||
|         for user_org in UserOrganization::find_confirmed_by_user(&self.uuid, conn) { | ||||
|     pub async fn delete(self, conn: &DbConn) -> EmptyResult { | ||||
|         for user_org in UserOrganization::find_confirmed_by_user(&self.uuid, conn).await { | ||||
|             if user_org.atype == UserOrgType::Owner { | ||||
|                 let owner_type = UserOrgType::Owner as i32; | ||||
|                 if UserOrganization::find_by_org_and_type(&user_org.org_uuid, owner_type, conn).len() <= 1 { | ||||
|                 if UserOrganization::find_by_org_and_type(&user_org.org_uuid, owner_type, conn).await.len() <= 1 { | ||||
|                     err!("Can't delete last owner") | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         Send::delete_all_by_user(&self.uuid, conn)?; | ||||
|         EmergencyAccess::delete_all_by_user(&self.uuid, conn)?; | ||||
|         UserOrganization::delete_all_by_user(&self.uuid, conn)?; | ||||
|         Cipher::delete_all_by_user(&self.uuid, conn)?; | ||||
|         Favorite::delete_all_by_user(&self.uuid, conn)?; | ||||
|         Folder::delete_all_by_user(&self.uuid, conn)?; | ||||
|         Device::delete_all_by_user(&self.uuid, conn)?; | ||||
|         TwoFactor::delete_all_by_user(&self.uuid, conn)?; | ||||
|         TwoFactorIncomplete::delete_all_by_user(&self.uuid, conn)?; | ||||
|         Invitation::take(&self.email, conn); // Delete invitation if any
 | ||||
|         Send::delete_all_by_user(&self.uuid, conn).await?; | ||||
|         EmergencyAccess::delete_all_by_user(&self.uuid, conn).await?; | ||||
|         UserOrganization::delete_all_by_user(&self.uuid, conn).await?; | ||||
|         Cipher::delete_all_by_user(&self.uuid, conn).await?; | ||||
|         Favorite::delete_all_by_user(&self.uuid, conn).await?; | ||||
|         Folder::delete_all_by_user(&self.uuid, conn).await?; | ||||
|         Device::delete_all_by_user(&self.uuid, conn).await?; | ||||
|         TwoFactor::delete_all_by_user(&self.uuid, conn).await?; | ||||
|         TwoFactorIncomplete::delete_all_by_user(&self.uuid, conn).await?; | ||||
|         Invitation::take(&self.email, conn).await; // Delete invitation if any
 | ||||
| 
 | ||||
|         db_run! {conn: { | ||||
|             diesel::delete(users::table.filter(users::uuid.eq(self.uuid))) | ||||
|  | @ -293,13 +301,13 @@ impl User { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn update_uuid_revision(uuid: &str, conn: &DbConn) { | ||||
|         if let Err(e) = Self::_update_revision(uuid, &Utc::now().naive_utc(), conn) { | ||||
|     pub async fn update_uuid_revision(uuid: &str, conn: &DbConn) { | ||||
|         if let Err(e) = Self::_update_revision(uuid, &Utc::now().naive_utc(), conn).await { | ||||
|             warn!("Failed to update revision for {}: {:#?}", uuid, e); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn update_all_revisions(conn: &DbConn) -> EmptyResult { | ||||
|     pub async fn update_all_revisions(conn: &DbConn) -> EmptyResult { | ||||
|         let updated_at = Utc::now().naive_utc(); | ||||
| 
 | ||||
|         db_run! {conn: { | ||||
|  | @ -312,13 +320,13 @@ impl User { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn update_revision(&mut self, conn: &DbConn) -> EmptyResult { | ||||
|     pub async fn update_revision(&mut self, conn: &DbConn) -> EmptyResult { | ||||
|         self.updated_at = Utc::now().naive_utc(); | ||||
| 
 | ||||
|         Self::_update_revision(&self.uuid, &self.updated_at, conn) | ||||
|         Self::_update_revision(&self.uuid, &self.updated_at, conn).await | ||||
|     } | ||||
| 
 | ||||
|     fn _update_revision(uuid: &str, date: &NaiveDateTime, conn: &DbConn) -> EmptyResult { | ||||
|     async fn _update_revision(uuid: &str, date: &NaiveDateTime, conn: &DbConn) -> EmptyResult { | ||||
|         db_run! {conn: { | ||||
|             crate::util::retry(|| { | ||||
|                 diesel::update(users::table.filter(users::uuid.eq(uuid))) | ||||
|  | @ -329,7 +337,7 @@ impl User { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_mail(mail: &str, conn: &DbConn) -> Option<Self> { | ||||
|     pub async fn find_by_mail(mail: &str, conn: &DbConn) -> Option<Self> { | ||||
|         let lower_mail = mail.to_lowercase(); | ||||
|         db_run! {conn: { | ||||
|             users::table | ||||
|  | @ -340,20 +348,20 @@ impl User { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|     pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> { | ||||
|         db_run! {conn: { | ||||
|             users::table.filter(users::uuid.eq(uuid)).first::<UserDb>(conn).ok().from_db() | ||||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn get_all(conn: &DbConn) -> Vec<Self> { | ||||
|     pub async fn get_all(conn: &DbConn) -> Vec<Self> { | ||||
|         db_run! {conn: { | ||||
|             users::table.load::<UserDb>(conn).expect("Error loading users").from_db() | ||||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn last_active(&self, conn: &DbConn) -> Option<NaiveDateTime> { | ||||
|         match Device::find_latest_active_by_user(&self.uuid, conn) { | ||||
|     pub async fn last_active(&self, conn: &DbConn) -> Option<NaiveDateTime> { | ||||
|         match Device::find_latest_active_by_user(&self.uuid, conn).await { | ||||
|             Some(device) => Some(device.updated_at), | ||||
|             None => None, | ||||
|         } | ||||
|  | @ -368,7 +376,7 @@ impl Invitation { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn save(&self, conn: &DbConn) -> EmptyResult { | ||||
|     pub async fn save(&self, conn: &DbConn) -> EmptyResult { | ||||
|         if self.email.trim().is_empty() { | ||||
|             err!("Invitation email can't be empty") | ||||
|         } | ||||
|  | @ -393,7 +401,7 @@ impl Invitation { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete(self, conn: &DbConn) -> EmptyResult { | ||||
|     pub async fn delete(self, conn: &DbConn) -> EmptyResult { | ||||
|         db_run! {conn: { | ||||
|             diesel::delete(invitations::table.filter(invitations::email.eq(self.email))) | ||||
|                 .execute(conn) | ||||
|  | @ -401,7 +409,7 @@ impl Invitation { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn find_by_mail(mail: &str, conn: &DbConn) -> Option<Self> { | ||||
|     pub async fn find_by_mail(mail: &str, conn: &DbConn) -> Option<Self> { | ||||
|         let lower_mail = mail.to_lowercase(); | ||||
|         db_run! {conn: { | ||||
|             invitations::table | ||||
|  | @ -412,9 +420,9 @@ impl Invitation { | |||
|         }} | ||||
|     } | ||||
| 
 | ||||
|     pub fn take(mail: &str, conn: &DbConn) -> bool { | ||||
|         match Self::find_by_mail(mail, conn) { | ||||
|             Some(invitation) => invitation.delete(conn).is_ok(), | ||||
|     pub async fn take(mail: &str, conn: &DbConn) -> bool { | ||||
|         match Self::find_by_mail(mail, conn).await { | ||||
|             Some(invitation) => invitation.delete(conn).await.is_ok(), | ||||
|             None => false, | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -1,4 +1,6 @@ | |||
| #![forbid(unsafe_code)] | ||||
| // #![warn(rust_2018_idioms)]
 | ||||
| #![warn(rust_2021_compatibility)] | ||||
| #![cfg_attr(feature = "unstable", feature(ip))] | ||||
| // The recursion_limit is mainly triggered by the json!() macro.
 | ||||
| // The more key/value pairs there are the more recursion occurs.
 | ||||
|  | @ -72,7 +74,7 @@ async fn main() -> Result<(), Error> { | |||
| 
 | ||||
|     let pool = create_db_pool(); | ||||
|     schedule_jobs(pool.clone()).await; | ||||
|     crate::db::models::TwoFactor::migrate_u2f_to_webauthn(&pool.get().await.unwrap()).unwrap(); | ||||
|     crate::db::models::TwoFactor::migrate_u2f_to_webauthn(&pool.get().await.unwrap()).await.unwrap(); | ||||
| 
 | ||||
|     launch_rocket(pool, extra_debug).await // Blocks until program termination.
 | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue