vw_small

Hardened fork of Vaultwarden (https://github.com/dani-garcia/vaultwarden) with fewer features.
git clone https://git.philomathiclife.com/repos/vw_small
Log | Files | Refs | README

commit 775d07e9a0d6e33f5af63abb571cae4e434795d9
parent 2d5f172e777e09a47c79d8437d94df160afd6e38
Author: BlackDex <black.dex@gmail.com>
Date:   Tue, 16 Nov 2021 17:07:55 +0100

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.

Diffstat:
Mrustfmt.toml | 8++++----
Msrc/api/admin.rs | 147+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msrc/api/core/accounts.rs | 124+++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/api/core/ciphers.rs | 490+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Msrc/api/core/emergency_access.rs | 216+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Msrc/api/core/folders.rs | 45++++++++++++++++++++++++++++-----------------
Msrc/api/core/mod.rs | 8++++----
Msrc/api/core/organizations.rs | 419++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Msrc/api/core/sends.rs | 96++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/api/core/two_factor/authenticator.rs | 37++++++++++++++++++++++---------------
Msrc/api/core/two_factor/duo.rs | 31++++++++++++++++---------------
Msrc/api/core/two_factor/email.rs | 54+++++++++++++++++++++++++++++-------------------------
Msrc/api/core/two_factor/mod.rs | 48+++++++++++++++++++++++++-----------------------
Msrc/api/core/two_factor/u2f.rs | 61+++++++++++++++++++++++++++++++------------------------------
Msrc/api/core/two_factor/webauthn.rs | 66++++++++++++++++++++++++++++++++++++++----------------------------
Msrc/api/core/two_factor/yubikey.rs | 23++++++++++++-----------
Msrc/api/icons.rs | 1+
Msrc/api/identity.rs | 91++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/auth.rs | 12+++++++-----
Msrc/db/mod.rs | 2+-
Msrc/db/models/attachment.rs | 22+++++++++++-----------
Msrc/db/models/cipher.rs | 123++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/db/models/collection.rs | 98++++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/db/models/device.rs | 18+++++++++---------
Msrc/db/models/emergency_access.rs | 42+++++++++++++++++++++---------------------
Msrc/db/models/favorite.rs | 14+++++++-------
Msrc/db/models/folder.rs | 32++++++++++++++++----------------
Msrc/db/models/org_policy.rs | 34+++++++++++++---------------------
Msrc/db/models/organization.rs | 87++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/db/models/send.rs | 46+++++++++++++++++++++++-----------------------
Msrc/db/models/two_factor.rs | 19++++++++++---------
Msrc/db/models/two_factor_incomplete.rs | 20++++++++++----------
Msrc/db/models/user.rs | 78+++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Msrc/main.rs | 4+++-
34 files changed, 1421 insertions(+), 1195 deletions(-)

diff --git a/rustfmt.toml b/rustfmt.toml @@ -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 diff --git a/src/api/admin.rs b/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)] diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs @@ -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 } diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs @@ -17,6 +17,8 @@ use crate::{ CONFIG, }; +use futures::{stream, stream::StreamExt}; + pub fn routes() -> Vec<Route> { // Note that many routes have an `admin` variant; this seems to be // because the stored procedure that upstream Bitwarden uses to determine @@ -83,7 +85,7 @@ pub fn routes() -> Vec<Route> { pub async fn purge_trashed_ciphers(pool: DbPool) { debug!("Purging trashed ciphers"); if let Ok(conn) = pool.get().await { - Cipher::purge_trash(&conn); + Cipher::purge_trash(&conn).await; } else { error!("Failed to get DB connection while purging trashed ciphers") } @@ -96,25 +98,33 @@ struct SyncData { } #[get("/sync?<data..>")] -fn sync(data: SyncData, headers: Headers, conn: DbConn) -> Json<Value> { - let user_json = headers.user.to_json(&conn); +async fn sync(data: SyncData, headers: Headers, conn: DbConn) -> Json<Value> { + let user_json = headers.user.to_json(&conn).await; - let folders = Folder::find_by_user(&headers.user.uuid, &conn); + let folders = Folder::find_by_user(&headers.user.uuid, &conn).await; let folders_json: Vec<Value> = folders.iter().map(Folder::to_json).collect(); - let collections = Collection::find_by_user_uuid(&headers.user.uuid, &conn); - let collections_json: Vec<Value> = - collections.iter().map(|c| c.to_json_details(&headers.user.uuid, &conn)).collect(); + let collections_json = stream::iter(Collection::find_by_user_uuid(&headers.user.uuid, &conn).await) + .then(|c| async { + let c = c; // Move out this single variable + c.to_json_details(&headers.user.uuid, &conn).await + }) + .collect::<Vec<Value>>() + .await; let policies = OrgPolicy::find_confirmed_by_user(&headers.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(); - let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &conn); - let ciphers_json: Vec<Value> = - ciphers.iter().map(|c| c.to_json(&headers.host, &headers.user.uuid, &conn)).collect(); + let ciphers_json = stream::iter(Cipher::find_by_user_visible(&headers.user.uuid, &conn).await) + .then(|c| async { + let c = c; // Move out this single variable + c.to_json(&headers.host, &headers.user.uuid, &conn).await + }) + .collect::<Vec<Value>>() + .await; 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(); let domains_json = if data.exclude_domains { Value::Null @@ -136,11 +146,14 @@ fn sync(data: SyncData, headers: Headers, conn: DbConn) -> Json<Value> { } #[get("/ciphers")] -fn get_ciphers(headers: Headers, conn: DbConn) -> Json<Value> { - let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &conn); - - let ciphers_json: Vec<Value> = - ciphers.iter().map(|c| c.to_json(&headers.host, &headers.user.uuid, &conn)).collect(); +async fn get_ciphers(headers: Headers, conn: DbConn) -> Json<Value> { + let ciphers_json = stream::iter(Cipher::find_by_user_visible(&headers.user.uuid, &conn).await) + .then(|c| async { + let c = c; // Move out this single variable + c.to_json(&headers.host, &headers.user.uuid, &conn).await + }) + .collect::<Vec<Value>>() + .await; Json(json!({ "Data": ciphers_json, @@ -150,28 +163,28 @@ fn get_ciphers(headers: Headers, conn: DbConn) -> Json<Value> { } #[get("/ciphers/<uuid>")] -fn get_cipher(uuid: String, headers: Headers, conn: DbConn) -> JsonResult { - let cipher = match Cipher::find_by_uuid(&uuid, &conn) { +async fn get_cipher(uuid: String, headers: Headers, conn: DbConn) -> JsonResult { + let cipher = match Cipher::find_by_uuid(&uuid, &conn).await { Some(cipher) => cipher, None => err!("Cipher doesn't exist"), }; - if !cipher.is_accessible_to_user(&headers.user.uuid, &conn) { + if !cipher.is_accessible_to_user(&headers.user.uuid, &conn).await { err!("Cipher is not owned by user") } - Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn))) + Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn).await)) } #[get("/ciphers/<uuid>/admin")] -fn get_cipher_admin(uuid: String, headers: Headers, conn: DbConn) -> JsonResult { +async fn get_cipher_admin(uuid: String, headers: Headers, conn: DbConn) -> JsonResult { // TODO: Implement this correctly - get_cipher(uuid, headers, conn) + get_cipher(uuid, headers, conn).await } #[get("/ciphers/<uuid>/details")] -fn get_cipher_details(uuid: String, headers: Headers, conn: DbConn) -> JsonResult { - get_cipher(uuid, headers, conn) +async fn get_cipher_details(uuid: String, headers: Headers, conn: DbConn) -> JsonResult { + get_cipher(uuid, headers, conn).await } #[derive(Deserialize, Debug)] @@ -229,15 +242,25 @@ pub struct Attachments2Data { /// Called when an org admin clones an org cipher. #[post("/ciphers/admin", data = "<data>")] -fn post_ciphers_admin(data: JsonUpcase<ShareCipherData>, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { - post_ciphers_create(data, headers, conn, nt) +async fn post_ciphers_admin( + data: JsonUpcase<ShareCipherData>, + headers: Headers, + conn: DbConn, + nt: Notify<'_>, +) -> JsonResult { + post_ciphers_create(data, headers, conn, nt).await } /// Called when creating a new org-owned cipher, or cloning a cipher (whether /// user- or org-owned). When cloning a cipher to a user-owned cipher, /// `organizationId` is null. #[post("/ciphers/create", data = "<data>")] -fn post_ciphers_create(data: JsonUpcase<ShareCipherData>, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { +async fn post_ciphers_create( + data: JsonUpcase<ShareCipherData>, + headers: Headers, + conn: DbConn, + nt: Notify<'_>, +) -> JsonResult { let mut data: ShareCipherData = data.into_inner().data; // Check if there are one more more collections selected when this cipher is part of an organization. @@ -249,11 +272,11 @@ fn post_ciphers_create(data: JsonUpcase<ShareCipherData>, headers: Headers, conn // This check is usually only needed in update_cipher_from_data(), but we // need it here as well to avoid creating an empty cipher in the call to // cipher.save() below. - enforce_personal_ownership_policy(Some(&data.Cipher), &headers, &conn)?; + enforce_personal_ownership_policy(Some(&data.Cipher), &headers, &conn).await?; let mut cipher = Cipher::new(data.Cipher.Type, data.Cipher.Name.clone()); cipher.user_uuid = Some(headers.user.uuid.clone()); - cipher.save(&conn)?; + cipher.save(&conn).await?; // When cloning a cipher, the Bitwarden clients seem to set this field // based on the cipher being cloned (when creating a new cipher, it's set @@ -263,12 +286,12 @@ fn post_ciphers_create(data: JsonUpcase<ShareCipherData>, headers: Headers, conn // or otherwise), we can just ignore this field entirely. data.Cipher.LastKnownRevisionDate = None; - share_cipher_by_uuid(&cipher.uuid, data, &headers, &conn, &nt) + share_cipher_by_uuid(&cipher.uuid, data, &headers, &conn, &nt).await } /// Called when creating a new user-owned cipher. #[post("/ciphers", data = "<data>")] -fn post_ciphers(data: JsonUpcase<CipherData>, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { +async fn post_ciphers(data: JsonUpcase<CipherData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult { let mut data: CipherData = data.into_inner().data; // The web/browser clients set this field to null as expected, but the @@ -278,9 +301,9 @@ fn post_ciphers(data: JsonUpcase<CipherData>, headers: Headers, conn: DbConn, nt data.LastKnownRevisionDate = None; let mut cipher = Cipher::new(data.Type, data.Name.clone()); - update_cipher_from_data(&mut cipher, data, &headers, false, &conn, &nt, UpdateType::CipherCreate)?; + update_cipher_from_data(&mut cipher, data, &headers, false, &conn, &nt, UpdateType::CipherCreate).await?; - Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn))) + Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn).await)) } /// Enforces the personal ownership policy on user-owned ciphers, if applicable. @@ -290,27 +313,27 @@ fn post_ciphers(data: JsonUpcase<CipherData>, headers: Headers, conn: DbConn, nt /// allowed to delete or share such ciphers to an org, however. /// /// Ref: https://bitwarden.com/help/article/policies/#personal-ownership -fn enforce_personal_ownership_policy(data: Option<&CipherData>, headers: &Headers, conn: &DbConn) -> EmptyResult { +async fn enforce_personal_ownership_policy(data: Option<&CipherData>, headers: &Headers, conn: &DbConn) -> EmptyResult { if data.is_none() || data.unwrap().OrganizationId.is_none() { let user_uuid = &headers.user.uuid; let policy_type = OrgPolicyType::PersonalOwnership; - if OrgPolicy::is_applicable_to_user(user_uuid, policy_type, conn) { + if OrgPolicy::is_applicable_to_user(user_uuid, policy_type, conn).await { err!("Due to an Enterprise Policy, you are restricted from saving items to your personal vault.") } } Ok(()) } -pub fn update_cipher_from_data( +pub async fn update_cipher_from_data( cipher: &mut Cipher, data: CipherData, headers: &Headers, shared_to_collection: bool, conn: &DbConn, - nt: &Notify, + nt: &Notify<'_>, ut: UpdateType, ) -> EmptyResult { - enforce_personal_ownership_policy(Some(&data), headers, conn)?; + enforce_personal_ownership_policy(Some(&data), headers, conn).await?; // Check that the client isn't updating an existing cipher with stale data. if let Some(dt) = data.LastKnownRevisionDate { @@ -329,12 +352,12 @@ pub fn update_cipher_from_data( } if let Some(org_id) = data.OrganizationId { - match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, conn) { + match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, conn).await { None => err!("You don't have permission to add item to organization"), Some(org_user) => { if shared_to_collection || org_user.has_full_access() - || cipher.is_write_accessible_to_user(&headers.user.uuid, conn) + || cipher.is_write_accessible_to_user(&headers.user.uuid, conn).await { cipher.organization_uuid = Some(org_id); // After some discussion in PR #1329 re-added the user_uuid = None again. @@ -353,7 +376,7 @@ pub fn update_cipher_from_data( } if let Some(ref folder_id) = data.FolderId { - match Folder::find_by_uuid(folder_id, conn) { + match Folder::find_by_uuid(folder_id, conn).await { Some(folder) => { if folder.user_uuid != headers.user.uuid { err!("Folder is not owned by user") @@ -366,7 +389,7 @@ pub fn update_cipher_from_data( // Modify attachments name and keys when rotating if let Some(attachments) = data.Attachments2 { for (id, attachment) in attachments { - let mut saved_att = match Attachment::find_by_id(&id, conn) { + let mut saved_att = match Attachment::find_by_id(&id, conn).await { Some(att) => att, None => err!("Attachment doesn't exist"), }; @@ -381,7 +404,7 @@ pub fn update_cipher_from_data( saved_att.akey = Some(attachment.Key); saved_att.file_name = attachment.FileName; - saved_att.save(conn)?; + saved_att.save(conn).await?; } } @@ -427,12 +450,12 @@ pub fn update_cipher_from_data( cipher.password_history = data.PasswordHistory.map(|f| f.to_string()); cipher.reprompt = data.Reprompt; - cipher.save(conn)?; - cipher.move_to_folder(data.FolderId, &headers.user.uuid, conn)?; - cipher.set_favorite(data.Favorite, &headers.user.uuid, conn)?; + cipher.save(conn).await?; + cipher.move_to_folder(data.FolderId, &headers.user.uuid, conn).await?; + cipher.set_favorite(data.Favorite, &headers.user.uuid, conn).await?; if ut != UpdateType::None { - nt.send_cipher_update(ut, cipher, &cipher.update_users_revision(conn)); + nt.send_cipher_update(ut, cipher, &cipher.update_users_revision(conn).await); } Ok(()) @@ -458,8 +481,13 @@ struct RelationsData { } #[post("/ciphers/import", data = "<data>")] -fn post_ciphers_import(data: JsonUpcase<ImportData>, headers: Headers, conn: DbConn, nt: Notify) -> EmptyResult { - enforce_personal_ownership_policy(None, &headers, &conn)?; +async fn post_ciphers_import( + data: JsonUpcase<ImportData>, + headers: Headers, + conn: DbConn, + nt: Notify<'_>, +) -> EmptyResult { + enforce_personal_ownership_policy(None, &headers, &conn).await?; let data: ImportData = data.into_inner().data; @@ -467,7 +495,7 @@ fn post_ciphers_import(data: JsonUpcase<ImportData>, headers: Headers, conn: DbC let mut folders: Vec<_> = Vec::new(); for folder in data.Folders.into_iter() { let mut new_folder = Folder::new(headers.user.uuid.clone(), folder.Name); - new_folder.save(&conn)?; + new_folder.save(&conn).await?; folders.push(new_folder); } @@ -485,48 +513,60 @@ fn post_ciphers_import(data: JsonUpcase<ImportData>, headers: Headers, conn: DbC cipher_data.FolderId = folder_uuid; let mut cipher = Cipher::new(cipher_data.Type, cipher_data.Name.clone()); - update_cipher_from_data(&mut cipher, cipher_data, &headers, false, &conn, &nt, UpdateType::None)?; + update_cipher_from_data(&mut cipher, cipher_data, &headers, false, &conn, &nt, UpdateType::None).await?; } let mut user = headers.user; - user.update_revision(&conn)?; + user.update_revision(&conn).await?; nt.send_user_update(UpdateType::Vault, &user); Ok(()) } /// Called when an org admin modifies an existing org cipher. #[put("/ciphers/<uuid>/admin", data = "<data>")] -fn put_cipher_admin( +async fn put_cipher_admin( uuid: String, data: JsonUpcase<CipherData>, headers: Headers, conn: DbConn, - nt: Notify, + nt: Notify<'_>, ) -> JsonResult { - put_cipher(uuid, data, headers, conn, nt) + put_cipher(uuid, data, headers, conn, nt).await } #[post("/ciphers/<uuid>/admin", data = "<data>")] -fn post_cipher_admin( +async fn post_cipher_admin( uuid: String, data: JsonUpcase<CipherData>, headers: Headers, conn: DbConn, - nt: Notify, + nt: Notify<'_>, ) -> JsonResult { - post_cipher(uuid, data, headers, conn, nt) + post_cipher(uuid, data, headers, conn, nt).await } #[post("/ciphers/<uuid>", data = "<data>")] -fn post_cipher(uuid: String, data: JsonUpcase<CipherData>, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { - put_cipher(uuid, data, headers, conn, nt) +async fn post_cipher( + uuid: String, + data: JsonUpcase<CipherData>, + headers: Headers, + conn: DbConn, + nt: Notify<'_>, +) -> JsonResult { + put_cipher(uuid, data, headers, conn, nt).await } #[put("/ciphers/<uuid>", data = "<data>")] -fn put_cipher(uuid: String, data: JsonUpcase<CipherData>, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { +async fn put_cipher( + uuid: String, + data: JsonUpcase<CipherData>, + headers: Headers, + conn: DbConn, + nt: Notify<'_>, +) -> JsonResult { let data: CipherData = data.into_inner().data; - let mut cipher = match Cipher::find_by_uuid(&uuid, &conn) { + let mut cipher = match Cipher::find_by_uuid(&uuid, &conn).await { Some(cipher) => cipher, None => err!("Cipher doesn't exist"), }; @@ -536,13 +576,13 @@ fn put_cipher(uuid: String, data: JsonUpcase<CipherData>, headers: Headers, conn // cipher itself, so the user shouldn't need write access to change these. // Interestingly, upstream Bitwarden doesn't properly handle this either. - if !cipher.is_write_accessible_to_user(&headers.user.uuid, &conn) { + if !cipher.is_write_accessible_to_user(&headers.user.uuid, &conn).await { err!("Cipher is not write accessible") } - update_cipher_from_data(&mut cipher, data, &headers, false, &conn, &nt, UpdateType::CipherUpdate)?; + update_cipher_from_data(&mut cipher, data, &headers, false, &conn, &nt, UpdateType::CipherUpdate).await?; - Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn))) + Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn).await)) } #[derive(Deserialize)] @@ -552,37 +592,37 @@ struct CollectionsAdminData { } #[put("/ciphers/<uuid>/collections", data = "<data>")] -fn put_collections_update( +async fn put_collections_update( uuid: String, data: JsonUpcase<CollectionsAdminData>, headers: Headers, conn: DbConn, ) -> EmptyResult { - post_collections_admin(uuid, data, headers, conn) + post_collections_admin(uuid, data, headers, conn).await } #[post("/ciphers/<uuid>/collections", data = "<data>")] -fn post_collections_update( +async fn post_collections_update( uuid: String, data: JsonUpcase<CollectionsAdminData>, headers: Headers, conn: DbConn, ) -> EmptyResult { - post_collections_admin(uuid, data, headers, conn) + post_collections_admin(uuid, data, headers, conn).await } #[put("/ciphers/<uuid>/collections-admin", data = "<data>")] -fn put_collections_admin( +async fn put_collections_admin( uuid: String, data: JsonUpcase<CollectionsAdminData>, headers: Headers, conn: DbConn, ) -> EmptyResult { - post_collections_admin(uuid, data, headers, conn) + post_collections_admin(uuid, data, headers, conn).await } #[post("/ciphers/<uuid>/collections-admin", data = "<data>")] -fn post_collections_admin( +async fn post_collections_admin( uuid: String, data: JsonUpcase<CollectionsAdminData>, headers: Headers, @@ -590,30 +630,30 @@ fn post_collections_admin( ) -> EmptyResult { let data: CollectionsAdminData = data.into_inner().data; - let cipher = match Cipher::find_by_uuid(&uuid, &conn) { + let cipher = match Cipher::find_by_uuid(&uuid, &conn).await { Some(cipher) => cipher, None => err!("Cipher doesn't exist"), }; - if !cipher.is_write_accessible_to_user(&headers.user.uuid, &conn) { + if !cipher.is_write_accessible_to_user(&headers.user.uuid, &conn).await { err!("Cipher is not write accessible") } let posted_collections: HashSet<String> = data.CollectionIds.iter().cloned().collect(); let current_collections: HashSet<String> = - cipher.get_collections(&headers.user.uuid, &conn).iter().cloned().collect(); + cipher.get_collections(&headers.user.uuid, &conn).await.iter().cloned().collect(); for collection in posted_collections.symmetric_difference(&current_collections) { - match Collection::find_by_uuid(collection, &conn) { + match Collection::find_by_uuid(collection, &conn).await { None => err!("Invalid collection ID provided"), Some(collection) => { - if collection.is_writable_by_user(&headers.user.uuid, &conn) { + if collection.is_writable_by_user(&headers.user.uuid, &conn).await { if posted_collections.contains(&collection.uuid) { // Add to collection - CollectionCipher::save(&cipher.uuid, &collection.uuid, &conn)?; + CollectionCipher::save(&cipher.uuid, &collection.uuid, &conn).await?; } else { // Remove from collection - CollectionCipher::delete(&cipher.uuid, &collection.uuid, &conn)?; + CollectionCipher::delete(&cipher.uuid, &collection.uuid, &conn).await?; } } else { err!("No rights to modify the collection") @@ -633,29 +673,29 @@ struct ShareCipherData { } #[post("/ciphers/<uuid>/share", data = "<data>")] -fn post_cipher_share( +async fn post_cipher_share( uuid: String, data: JsonUpcase<ShareCipherData>, headers: Headers, conn: DbConn, - nt: Notify, + nt: Notify<'_>, ) -> JsonResult { let data: ShareCipherData = data.into_inner().data; - share_cipher_by_uuid(&uuid, data, &headers, &conn, &nt) + share_cipher_by_uuid(&uuid, data, &headers, &conn, &nt).await } #[put("/ciphers/<uuid>/share", data = "<data>")] -fn put_cipher_share( +async fn put_cipher_share( uuid: String, data: JsonUpcase<ShareCipherData>, headers: Headers, conn: DbConn, - nt: Notify, + nt: Notify<'_>, ) -> JsonResult { let data: ShareCipherData = data.into_inner().data; - share_cipher_by_uuid(&uuid, data, &headers, &conn, &nt) + share_cipher_by_uuid(&uuid, data, &headers, &conn, &nt).await } #[derive(Deserialize)] @@ -666,11 +706,11 @@ struct ShareSelectedCipherData { } #[put("/ciphers/share", data = "<data>")] -fn put_cipher_share_selected( +async fn put_cipher_share_selected( data: JsonUpcase<ShareSelectedCipherData>, headers: Headers, conn: DbConn, - nt: Notify, + nt: Notify<'_>, ) -> EmptyResult { let mut data: ShareSelectedCipherData = data.into_inner().data; let mut cipher_ids: Vec<String> = Vec::new(); @@ -697,7 +737,7 @@ fn put_cipher_share_selected( }; match shared_cipher_data.Cipher.Id.take() { - Some(id) => share_cipher_by_uuid(&id, shared_cipher_data, &headers, &conn, &nt)?, + Some(id) => share_cipher_by_uuid(&id, shared_cipher_data, &headers, &conn, &nt).await?, None => err!("Request missing ids field"), }; } @@ -705,16 +745,16 @@ fn put_cipher_share_selected( Ok(()) } -fn share_cipher_by_uuid( +async fn share_cipher_by_uuid( uuid: &str, data: ShareCipherData, headers: &Headers, conn: &DbConn, - nt: &Notify, + nt: &Notify<'_>, ) -> JsonResult { - let mut cipher = match Cipher::find_by_uuid(uuid, conn) { + let mut cipher = match Cipher::find_by_uuid(uuid, conn).await { Some(cipher) => { - if cipher.is_write_accessible_to_user(&headers.user.uuid, conn) { + if cipher.is_write_accessible_to_user(&headers.user.uuid, conn).await { cipher } else { err!("Cipher is not write accessible") @@ -731,11 +771,11 @@ fn share_cipher_by_uuid( None => {} Some(organization_uuid) => { for uuid in &data.CollectionIds { - match Collection::find_by_uuid_and_org(uuid, &organization_uuid, conn) { + match Collection::find_by_uuid_and_org(uuid, &organization_uuid, conn).await { None => err!("Invalid collection ID provided"), Some(collection) => { - if collection.is_writable_by_user(&headers.user.uuid, conn) { - CollectionCipher::save(&cipher.uuid, &collection.uuid, conn)?; + if collection.is_writable_by_user(&headers.user.uuid, conn).await { + CollectionCipher::save(&cipher.uuid, &collection.uuid, conn).await?; shared_to_collection = true; } else { err!("No rights to modify the collection") @@ -754,9 +794,10 @@ fn share_cipher_by_uuid( conn, nt, UpdateType::CipherUpdate, - )?; + ) + .await?; - Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, conn))) + Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, conn).await)) } /// v2 API for downloading an attachment. This just redirects the client to @@ -766,8 +807,8 @@ fn share_cipher_by_uuid( /// their object storage service. For self-hosted instances, it basically just /// redirects to the same location as before the v2 API. #[get("/ciphers/<uuid>/attachment/<attachment_id>")] -fn get_attachment(uuid: String, attachment_id: String, headers: Headers, conn: DbConn) -> JsonResult { - match Attachment::find_by_id(&attachment_id, &conn) { +async fn get_attachment(uuid: String, attachment_id: String, headers: Headers, conn: DbConn) -> JsonResult { + match Attachment::find_by_id(&attachment_id, &conn).await { Some(attachment) if uuid == attachment.cipher_uuid => Ok(Json(attachment.to_json(&headers.host))), Some(_) => err!("Attachment doesn't belong to cipher"), None => err!("Attachment doesn't exist"), @@ -793,18 +834,18 @@ enum FileUploadType { /// For upstream's cloud-hosted service, it's an Azure object storage API. /// For self-hosted instances, it's another API on the local instance. #[post("/ciphers/<uuid>/attachment/v2", data = "<data>")] -fn post_attachment_v2( +async fn post_attachment_v2( uuid: String, data: JsonUpcase<AttachmentRequestData>, headers: Headers, conn: DbConn, ) -> JsonResult { - let cipher = match Cipher::find_by_uuid(&uuid, &conn) { + let cipher = match Cipher::find_by_uuid(&uuid, &conn).await { Some(cipher) => cipher, None => err!("Cipher doesn't exist"), }; - if !cipher.is_write_accessible_to_user(&headers.user.uuid, &conn) { + if !cipher.is_write_accessible_to_user(&headers.user.uuid, &conn).await { err!("Cipher is not write accessible") } @@ -812,7 +853,7 @@ fn post_attachment_v2( let data: AttachmentRequestData = data.into_inner().data; let attachment = Attachment::new(attachment_id.clone(), cipher.uuid.clone(), data.FileName, data.FileSize, Some(data.Key)); - attachment.save(&conn).expect("Error saving attachment"); + attachment.save(&conn).await.expect("Error saving attachment"); let url = format!("/ciphers/{}/attachment/{}", cipher.uuid, attachment_id); let response_key = match data.AdminRequest { @@ -825,7 +866,7 @@ fn post_attachment_v2( "AttachmentId": attachment_id, "Url": url, "FileUploadType": FileUploadType::Direct as i32, - response_key: cipher.to_json(&headers.host, &headers.user.uuid, &conn), + response_key: cipher.to_json(&headers.host, &headers.user.uuid, &conn).await, }))) } @@ -851,12 +892,12 @@ async fn save_attachment( conn: DbConn, nt: Notify<'_>, ) -> Result<(Cipher, DbConn), crate::error::Error> { - let cipher = match Cipher::find_by_uuid(&cipher_uuid, &conn) { + let cipher = match Cipher::find_by_uuid(&cipher_uuid, &conn).await { Some(cipher) => cipher, None => err!("Cipher doesn't exist"), }; - if !cipher.is_write_accessible_to_user(&headers.user.uuid, &conn) { + if !cipher.is_write_accessible_to_user(&headers.user.uuid, &conn).await { err!("Cipher is not write accessible") } @@ -871,7 +912,7 @@ async fn save_attachment( match CONFIG.user_attachment_limit() { Some(0) => err!("Attachments are disabled"), Some(limit_kb) => { - let left = (limit_kb * 1024) - Attachment::size_by_user(user_uuid, &conn) + size_adjust; + let left = (limit_kb * 1024) - Attachment::size_by_user(user_uuid, &conn).await + size_adjust; if left <= 0 { err!("Attachment storage limit reached! Delete some attachments to free up space") } @@ -883,7 +924,7 @@ async fn save_attachment( match CONFIG.org_attachment_limit() { Some(0) => err!("Attachments are disabled"), Some(limit_kb) => { - let left = (limit_kb * 1024) - Attachment::size_by_org(org_uuid, &conn) + size_adjust; + let left = (limit_kb * 1024) - Attachment::size_by_org(org_uuid, &conn).await + size_adjust; if left <= 0 { err!("Attachment storage limit reached! Delete some attachments to free up space") } @@ -927,10 +968,10 @@ async fn save_attachment( if size != attachment.file_size { // Update the attachment with the actual file size. attachment.file_size = size; - attachment.save(&conn).expect("Error updating attachment"); + attachment.save(&conn).await.expect("Error updating attachment"); } } else { - attachment.delete(&conn).ok(); + attachment.delete(&conn).await.ok(); err!(format!("Attachment size mismatch (expected within [{}, {}], got {})", min_size, max_size, size)); } @@ -945,12 +986,12 @@ async fn save_attachment( err!("No attachment key provided") } let attachment = Attachment::new(file_id, cipher_uuid.clone(), encrypted_filename.unwrap(), size, data.key); - attachment.save(&conn).expect("Error saving attachment"); + attachment.save(&conn).await.expect("Error saving attachment"); } data.data.persist_to(file_path).await?; - nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(&conn)); + nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(&conn).await); Ok((cipher, conn)) } @@ -968,7 +1009,7 @@ async fn post_attachment_v2_data( conn: DbConn, nt: Notify<'_>, ) -> EmptyResult { - let attachment = match Attachment::find_by_id(&attachment_id, &conn) { + let attachment = match Attachment::find_by_id(&attachment_id, &conn).await { Some(attachment) if uuid == attachment.cipher_uuid => Some(attachment), Some(_) => err!("Attachment doesn't belong to cipher"), None => err!("Attachment doesn't exist"), @@ -994,7 +1035,7 @@ async fn post_attachment( let (cipher, conn) = save_attachment(attachment, uuid, data, &headers, conn, nt).await?; - Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn))) + Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn).await)) } #[post("/ciphers/<uuid>/attachment-admin", format = "multipart/form-data", data = "<data>")] @@ -1017,131 +1058,162 @@ async fn post_attachment_share( conn: DbConn, nt: Notify<'_>, ) -> JsonResult { - _delete_cipher_attachment_by_id(&uuid, &attachment_id, &headers, &conn, &nt)?; + _delete_cipher_attachment_by_id(&uuid, &attachment_id, &headers, &conn, &nt).await?; post_attachment(uuid, data, headers, conn, nt).await } #[post("/ciphers/<uuid>/attachment/<attachment_id>/delete-admin")] -fn delete_attachment_post_admin( +async fn delete_attachment_post_admin( uuid: String, attachment_id: String, headers: Headers, conn: DbConn, - nt: Notify, + nt: Notify<'_>, ) -> EmptyResult { - delete_attachment(uuid, attachment_id, headers, conn, nt) + delete_attachment(uuid, attachment_id, headers, conn, nt).await } #[post("/ciphers/<uuid>/attachment/<attachment_id>/delete")] -fn delete_attachment_post( +async fn delete_attachment_post( uuid: String, attachment_id: String, headers: Headers, conn: DbConn, - nt: Notify, + nt: Notify<'_>, ) -> EmptyResult { - delete_attachment(uuid, attachment_id, headers, conn, nt) + delete_attachment(uuid, attachment_id, headers, conn, nt).await } #[delete("/ciphers/<uuid>/attachment/<attachment_id>")] -fn delete_attachment(uuid: String, attachment_id: String, headers: Headers, conn: DbConn, nt: Notify) -> EmptyResult { - _delete_cipher_attachment_by_id(&uuid, &attachment_id, &headers, &conn, &nt) +async fn delete_attachment( + uuid: String, + attachment_id: String, + headers: Headers, + conn: DbConn, + nt: Notify<'_>, +) -> EmptyResult { + _delete_cipher_attachment_by_id(&uuid, &attachment_id, &headers, &conn, &nt).await } #[delete("/ciphers/<uuid>/attachment/<attachment_id>/admin")] -fn delete_attachment_admin( +async fn delete_attachment_admin( uuid: String, attachment_id: String, headers: Headers, conn: DbConn, - nt: Notify, + nt: Notify<'_>, ) -> EmptyResult { - _delete_cipher_attachment_by_id(&uuid, &attachment_id, &headers, &conn, &nt) + _delete_cipher_attachment_by_id(&uuid, &attachment_id, &headers, &conn, &nt).await } #[post("/ciphers/<uuid>/delete")] -fn delete_cipher_post(uuid: String, headers: Headers, conn: DbConn, nt: Notify) -> EmptyResult { - _delete_cipher_by_uuid(&uuid, &headers, &conn, false, &nt) +async fn delete_cipher_post(uuid: String, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult { + _delete_cipher_by_uuid(&uuid, &headers, &conn, false, &nt).await } #[post("/ciphers/<uuid>/delete-admin")] -fn delete_cipher_post_admin(uuid: String, headers: Headers, conn: DbConn, nt: Notify) -> EmptyResult { - _delete_cipher_by_uuid(&uuid, &headers, &conn, false, &nt) +async fn delete_cipher_post_admin(uuid: String, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult { + _delete_cipher_by_uuid(&uuid, &headers, &conn, false, &nt).await } #[put("/ciphers/<uuid>/delete")] -fn delete_cipher_put(uuid: String, headers: Headers, conn: DbConn, nt: Notify) -> EmptyResult { - _delete_cipher_by_uuid(&uuid, &headers, &conn, true, &nt) +async fn delete_cipher_put(uuid: String, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult { + _delete_cipher_by_uuid(&uuid, &headers, &conn, true, &nt).await } #[put("/ciphers/<uuid>/delete-admin")] -fn delete_cipher_put_admin(uuid: String, headers: Headers, conn: DbConn, nt: Notify) -> EmptyResult { - _delete_cipher_by_uuid(&uuid, &headers, &conn, true, &nt) +async fn delete_cipher_put_admin(uuid: String, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult { + _delete_cipher_by_uuid(&uuid, &headers, &conn, true, &nt).await } #[delete("/ciphers/<uuid>")] -fn delete_cipher(uuid: String, headers: Headers, conn: DbConn, nt: Notify) -> EmptyResult { - _delete_cipher_by_uuid(&uuid, &headers, &conn, false, &nt) +async fn delete_cipher(uuid: String, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult { + _delete_cipher_by_uuid(&uuid, &headers, &conn, false, &nt).await } #[delete("/ciphers/<uuid>/admin")] -fn delete_cipher_admin(uuid: String, headers: Headers, conn: DbConn, nt: Notify) -> EmptyResult { - _delete_cipher_by_uuid(&uuid, &headers, &conn, false, &nt) +async fn delete_cipher_admin(uuid: String, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult { + _delete_cipher_by_uuid(&uuid, &headers, &conn, false, &nt).await } #[delete("/ciphers", data = "<data>")] -fn delete_cipher_selected(data: JsonUpcase<Value>, headers: Headers, conn: DbConn, nt: Notify) -> EmptyResult { - _delete_multiple_ciphers(data, headers, conn, false, nt) +async fn delete_cipher_selected( + data: JsonUpcase<Value>, + headers: Headers, + conn: DbConn, + nt: Notify<'_>, +) -> EmptyResult { + _delete_multiple_ciphers(data, headers, conn, false, nt).await } #[post("/ciphers/delete", data = "<data>")] -fn delete_cipher_selected_post(data: JsonUpcase<Value>, headers: Headers, conn: DbConn, nt: Notify) -> EmptyResult { - _delete_multiple_ciphers(data, headers, conn, false, nt) +async fn delete_cipher_selected_post( + data: JsonUpcase<Value>, + headers: Headers, + conn: DbConn, + nt: Notify<'_>, +) -> EmptyResult { + _delete_multiple_ciphers(data, headers, conn, false, nt).await } #[put("/ciphers/delete", data = "<data>")] -fn delete_cipher_selected_put(data: JsonUpcase<Value>, headers: Headers, conn: DbConn, nt: Notify) -> EmptyResult { - _delete_multiple_ciphers(data, headers, conn, true, nt) // soft delete +async fn delete_cipher_selected_put( + data: JsonUpcase<Value>, + headers: Headers, + conn: DbConn, + nt: Notify<'_>, +) -> EmptyResult { + _delete_multiple_ciphers(data, headers, conn, true, nt).await // soft delete } #[delete("/ciphers/admin", data = "<data>")] -fn delete_cipher_selected_admin(data: JsonUpcase<Value>, headers: Headers, conn: DbConn, nt: Notify) -> EmptyResult { - delete_cipher_selected(data, headers, conn, nt) +async fn delete_cipher_selected_admin( + data: JsonUpcase<Value>, + headers: Headers, + conn: DbConn, + nt: Notify<'_>, +) -> EmptyResult { + delete_cipher_selected(data, headers, conn, nt).await } #[post("/ciphers/delete-admin", data = "<data>")] -fn delete_cipher_selected_post_admin( +async fn delete_cipher_selected_post_admin( data: JsonUpcase<Value>, headers: Headers, conn: DbConn, - nt: Notify, + nt: Notify<'_>, ) -> EmptyResult { - delete_cipher_selected_post(data, headers, conn, nt) + delete_cipher_selected_post(data, headers, conn, nt).await } #[put("/ciphers/delete-admin", data = "<data>")] -fn delete_cipher_selected_put_admin( +async fn delete_cipher_selected_put_admin( data: JsonUpcase<Value>, headers: Headers, conn: DbConn, - nt: Notify, + nt: Notify<'_>, ) -> EmptyResult { - delete_cipher_selected_put(data, headers, conn, nt) + delete_cipher_selected_put(data, headers, conn, nt).await } #[put("/ciphers/<uuid>/restore")] -fn restore_cipher_put(uuid: String, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { - _restore_cipher_by_uuid(&uuid, &headers, &conn, &nt) +async fn restore_cipher_put(uuid: String, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult { + _restore_cipher_by_uuid(&uuid, &headers, &conn, &nt).await } #[put("/ciphers/<uuid>/restore-admin")] -fn restore_cipher_put_admin(uuid: String, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { - _restore_cipher_by_uuid(&uuid, &headers, &conn, &nt) +async fn restore_cipher_put_admin(uuid: String, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult { + _restore_cipher_by_uuid(&uuid, &headers, &conn, &nt).await } #[put("/ciphers/restore", data = "<data>")] -fn restore_cipher_selected(data: JsonUpcase<Value>, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { - _restore_multiple_ciphers(data, &headers, &conn, &nt) +async fn restore_cipher_selected( + data: JsonUpcase<Value>, + headers: Headers, + conn: DbConn, + nt: Notify<'_>, +) -> JsonResult { + _restore_multiple_ciphers(data, &headers, &conn, &nt).await } #[derive(Deserialize)] @@ -1152,12 +1224,17 @@ struct MoveCipherData { } #[post("/ciphers/move", data = "<data>")] -fn move_cipher_selected(data: JsonUpcase<MoveCipherData>, headers: Headers, conn: DbConn, nt: Notify) -> EmptyResult { +async fn move_cipher_selected( + data: JsonUpcase<MoveCipherData>, + headers: Headers, + conn: DbConn, + nt: Notify<'_>, +) -> EmptyResult { let data = data.into_inner().data; let user_uuid = headers.user.uuid; if let Some(ref folder_id) = data.FolderId { - match Folder::find_by_uuid(folder_id, &conn) { + match Folder::find_by_uuid(folder_id, &conn).await { Some(folder) => { if folder.user_uuid != user_uuid { err!("Folder is not owned by user") @@ -1168,17 +1245,17 @@ fn move_cipher_selected(data: JsonUpcase<MoveCipherData>, headers: Headers, conn } for uuid in data.Ids { - let cipher = match Cipher::find_by_uuid(&uuid, &conn) { + let cipher = match Cipher::find_by_uuid(&uuid, &conn).await { Some(cipher) => cipher, None => err!("Cipher doesn't exist"), }; - if !cipher.is_accessible_to_user(&user_uuid, &conn) { + if !cipher.is_accessible_to_user(&user_uuid, &conn).await { err!("Cipher is not accessible by user") } // Move cipher - cipher.move_to_folder(data.FolderId.clone(), &user_uuid, &conn)?; + cipher.move_to_folder(data.FolderId.clone(), &user_uuid, &conn).await?; nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &[user_uuid.clone()]); } @@ -1187,13 +1264,13 @@ fn move_cipher_selected(data: JsonUpcase<MoveCipherData>, headers: Headers, conn } #[put("/ciphers/move", data = "<data>")] -fn move_cipher_selected_put( +async fn move_cipher_selected_put( data: JsonUpcase<MoveCipherData>, headers: Headers, conn: DbConn, - nt: Notify, + nt: Notify<'_>, ) -> EmptyResult { - move_cipher_selected(data, headers, conn, nt) + move_cipher_selected(data, headers, conn, nt).await } #[derive(FromForm)] @@ -1203,12 +1280,12 @@ struct OrganizationId { } #[post("/ciphers/purge?<organization..>", data = "<data>")] -fn delete_all( +async fn delete_all( organization: Option<OrganizationId>, data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn, - nt: Notify, + nt: Notify<'_>, ) -> EmptyResult { let data: PasswordData = data.into_inner().data; let password_hash = data.MasterPasswordHash; @@ -1222,11 +1299,11 @@ fn delete_all( match organization { Some(org_data) => { // Organization ID in query params, purging organization vault - match UserOrganization::find_by_user_and_org(&user.uuid, &org_data.org_id, &conn) { + match UserOrganization::find_by_user_and_org(&user.uuid, &org_data.org_id, &conn).await { None => err!("You don't have permission to purge the organization vault"), Some(user_org) => { if user_org.atype == UserOrgType::Owner { - Cipher::delete_all_by_organization(&org_data.org_id, &conn)?; + Cipher::delete_all_by_organization(&org_data.org_id, &conn).await?; nt.send_user_update(UpdateType::Vault, &user); Ok(()) } else { @@ -1238,50 +1315,56 @@ fn delete_all( None => { // No organization ID in query params, purging user vault // Delete ciphers and their attachments - for cipher in Cipher::find_owned_by_user(&user.uuid, &conn) { - cipher.delete(&conn)?; + for cipher in Cipher::find_owned_by_user(&user.uuid, &conn).await { + cipher.delete(&conn).await?; } // Delete folders - for f in Folder::find_by_user(&user.uuid, &conn) { - f.delete(&conn)?; + for f in Folder::find_by_user(&user.uuid, &conn).await { + f.delete(&conn).await?; } - user.update_revision(&conn)?; + user.update_revision(&conn).await?; nt.send_user_update(UpdateType::Vault, &user); Ok(()) } } } -fn _delete_cipher_by_uuid(uuid: &str, headers: &Headers, conn: &DbConn, soft_delete: bool, nt: &Notify) -> EmptyResult { - let mut cipher = match Cipher::find_by_uuid(uuid, conn) { +async fn _delete_cipher_by_uuid( + uuid: &str, + headers: &Headers, + conn: &DbConn, + soft_delete: bool, + nt: &Notify<'_>, +) -> EmptyResult { + let mut cipher = match Cipher::find_by_uuid(uuid, conn).await { Some(cipher) => cipher, None => err!("Cipher doesn't exist"), }; - if !cipher.is_write_accessible_to_user(&headers.user.uuid, conn) { + if !cipher.is_write_accessible_to_user(&headers.user.uuid, conn).await { err!("Cipher can't be deleted by user") } if soft_delete { cipher.deleted_at = Some(Utc::now().naive_utc()); - cipher.save(conn)?; - nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(conn)); + cipher.save(conn).await?; + nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(conn).await); } else { - cipher.delete(conn)?; - nt.send_cipher_update(UpdateType::CipherDelete, &cipher, &cipher.update_users_revision(conn)); + cipher.delete(conn).await?; + nt.send_cipher_update(UpdateType::CipherDelete, &cipher, &cipher.update_users_revision(conn).await); } Ok(()) } -fn _delete_multiple_ciphers( +async fn _delete_multiple_ciphers( data: JsonUpcase<Value>, headers: Headers, conn: DbConn, soft_delete: bool, - nt: Notify, + nt: Notify<'_>, ) -> EmptyResult { let data: Value = data.into_inner().data; @@ -1294,7 +1377,7 @@ fn _delete_multiple_ciphers( }; for uuid in uuids { - if let error @ Err(_) = _delete_cipher_by_uuid(uuid, &headers, &conn, soft_delete, &nt) { + if let error @ Err(_) = _delete_cipher_by_uuid(uuid, &headers, &conn, soft_delete, &nt).await { return error; }; } @@ -1302,24 +1385,29 @@ fn _delete_multiple_ciphers( Ok(()) } -fn _restore_cipher_by_uuid(uuid: &str, headers: &Headers, conn: &DbConn, nt: &Notify) -> JsonResult { - let mut cipher = match Cipher::find_by_uuid(uuid, conn) { +async fn _restore_cipher_by_uuid(uuid: &str, headers: &Headers, conn: &DbConn, nt: &Notify<'_>) -> JsonResult { + let mut cipher = match Cipher::find_by_uuid(uuid, conn).await { Some(cipher) => cipher, None => err!("Cipher doesn't exist"), }; - if !cipher.is_write_accessible_to_user(&headers.user.uuid, conn) { + if !cipher.is_write_accessible_to_user(&headers.user.uuid, conn).await { err!("Cipher can't be restored by user") } cipher.deleted_at = None; - cipher.save(conn)?; + cipher.save(conn).await?; - nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(conn)); - Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, conn))) + nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(conn).await); + Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, conn).await)) } -fn _restore_multiple_ciphers(data: JsonUpcase<Value>, headers: &Headers, conn: &DbConn, nt: &Notify) -> JsonResult { +async fn _restore_multiple_ciphers( + data: JsonUpcase<Value>, + headers: &Headers, + conn: &DbConn, + nt: &Notify<'_>, +) -> JsonResult { let data: Value = data.into_inner().data; let uuids = match data.get("Ids") { @@ -1332,7 +1420,7 @@ fn _restore_multiple_ciphers(data: JsonUpcase<Value>, headers: &Headers, conn: & let mut ciphers: Vec<Value> = Vec::new(); for uuid in uuids { - match _restore_cipher_by_uuid(uuid, headers, conn, nt) { + match _restore_cipher_by_uuid(uuid, headers, conn, nt).await { Ok(json) => ciphers.push(json.into_inner()), err => return err, } @@ -1345,14 +1433,14 @@ fn _restore_multiple_ciphers(data: JsonUpcase<Value>, headers: &Headers, conn: & }))) } -fn _delete_cipher_attachment_by_id( +async fn _delete_cipher_attachment_by_id( uuid: &str, attachment_id: &str, headers: &Headers, conn: &DbConn, - nt: &Notify, + nt: &Notify<'_>, ) -> EmptyResult { - let attachment = match Attachment::find_by_id(attachment_id, conn) { + let attachment = match Attachment::find_by_id(attachment_id, conn).await { Some(attachment) => attachment, None => err!("Attachment doesn't exist"), }; @@ -1361,17 +1449,17 @@ fn _delete_cipher_attachment_by_id( err!("Attachment from other cipher") } - let cipher = match Cipher::find_by_uuid(uuid, conn) { + let cipher = match Cipher::find_by_uuid(uuid, conn).await { Some(cipher) => cipher, None => err!("Cipher doesn't exist"), }; - if !cipher.is_write_accessible_to_user(&headers.user.uuid, conn) { + if !cipher.is_write_accessible_to_user(&headers.user.uuid, conn).await { err!("Cipher cannot be deleted by user") } // Delete attachment - attachment.delete(conn)?; - nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(conn)); + attachment.delete(conn).await?; + nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(conn).await); Ok(()) } diff --git a/src/api/core/emergency_access.rs b/src/api/core/emergency_access.rs @@ -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( diff --git a/src/api/core/folders.rs b/src/api/core/folders.rs @@ -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(()) diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs @@ -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>")] diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs @@ -10,6 +10,8 @@ use crate::{ mail, CONFIG, }; +use futures::{stream, stream::StreamExt}; + pub fn routes() -> Vec<Route> { routes![ get_organization, @@ -98,11 +100,11 @@ struct OrgBulkIds { } #[post("/organizations", data = "<data>")] -fn create_organization(headers: Headers, data: JsonUpcase<OrgData>, conn: DbConn) -> JsonResult { +async fn create_organization(headers: Headers, data: JsonUpcase<OrgData>, conn: DbConn) -> JsonResult { if !CONFIG.is_org_creation_allowed(&headers.user.email) { err!("User not allowed to create organizations") } - if OrgPolicy::is_applicable_to_user(&headers.user.uuid, OrgPolicyType::SingleOrg, &conn) { + if OrgPolicy::is_applicable_to_user(&headers.user.uuid, OrgPolicyType::SingleOrg, &conn).await { err!( "You may not create an organization. You belong to an organization which has a policy that prohibits you from being a member of any other organization." ) @@ -125,15 +127,15 @@ fn create_organization(headers: Headers, data: JsonUpcase<OrgData>, conn: DbConn user_org.atype = UserOrgType::Owner as i32; user_org.status = UserOrgStatus::Confirmed as i32; - org.save(&conn)?; - user_org.save(&conn)?; - collection.save(&conn)?; + org.save(&conn).await?; + user_org.save(&conn).await?; + collection.save(&conn).await?; Ok(Json(org.to_json())) } #[delete("/organizations/<org_id>", data = "<data>")] -fn delete_organization( +async fn delete_organization( org_id: String, data: JsonUpcase<PasswordData>, headers: OwnerHeaders, @@ -146,61 +148,61 @@ fn delete_organization( err!("Invalid password") } - match Organization::find_by_uuid(&org_id, &conn) { + match Organization::find_by_uuid(&org_id, &conn).await { None => err!("Organization not found"), - Some(org) => org.delete(&conn), + Some(org) => org.delete(&conn).await, } } #[post("/organizations/<org_id>/delete", data = "<data>")] -fn post_delete_organization( +async fn post_delete_organization( org_id: String, data: JsonUpcase<PasswordData>, headers: OwnerHeaders, conn: DbConn, ) -> EmptyResult { - delete_organization(org_id, data, headers, conn) + delete_organization(org_id, data, headers, conn).await } #[post("/organizations/<org_id>/leave")] -fn leave_organization(org_id: String, headers: Headers, conn: DbConn) -> EmptyResult { - match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn) { +async fn leave_organization(org_id: String, headers: Headers, conn: DbConn) -> EmptyResult { + match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn).await { None => err!("User not part of organization"), Some(user_org) => { if user_org.atype == UserOrgType::Owner { let num_owners = - UserOrganization::find_by_org_and_type(&org_id, UserOrgType::Owner as i32, &conn).len(); + UserOrganization::find_by_org_and_type(&org_id, UserOrgType::Owner as i32, &conn).await.len(); if num_owners <= 1 { err!("The last owner can't leave") } } - user_org.delete(&conn) + user_org.delete(&conn).await } } } #[get("/organizations/<org_id>")] -fn get_organization(org_id: String, _headers: OwnerHeaders, conn: DbConn) -> JsonResult { - match Organization::find_by_uuid(&org_id, &conn) { +async fn get_organization(org_id: String, _headers: OwnerHeaders, conn: DbConn) -> JsonResult { + match Organization::find_by_uuid(&org_id, &conn).await { Some(organization) => Ok(Json(organization.to_json())), None => err!("Can't find organization details"), } } #[put("/organizations/<org_id>", data = "<data>")] -fn put_organization( +async fn put_organization( org_id: String, headers: OwnerHeaders, data: JsonUpcase<OrganizationUpdateData>, conn: DbConn, ) -> JsonResult { - post_organization(org_id, headers, data, conn) + post_organization(org_id, headers, data, conn).await } #[post("/organizations/<org_id>", data = "<data>")] -fn post_organization( +async fn post_organization( org_id: String, _headers: OwnerHeaders, data: JsonUpcase<OrganizationUpdateData>, @@ -208,7 +210,7 @@ fn post_organization( ) -> JsonResult { let data: OrganizationUpdateData = data.into_inner().data; - let mut org = match Organization::find_by_uuid(&org_id, &conn) { + let mut org = match Organization::find_by_uuid(&org_id, &conn).await { Some(organization) => organization, None => err!("Can't find organization details"), }; @@ -216,16 +218,16 @@ fn post_organization( org.name = data.Name; org.billing_email = data.BillingEmail; - org.save(&conn)?; + org.save(&conn).await?; Ok(Json(org.to_json())) } // GET /api/collections?writeOnly=false #[get("/collections")] -fn get_user_collections(headers: Headers, conn: DbConn) -> Json<Value> { +async fn get_user_collections(headers: Headers, conn: DbConn) -> Json<Value> { Json(json!({ "Data": - Collection::find_by_user_uuid(&headers.user.uuid, &conn) + Collection::find_by_user_uuid(&headers.user.uuid, &conn).await .iter() .map(Collection::to_json) .collect::<Value>(), @@ -235,10 +237,10 @@ fn get_user_collections(headers: Headers, conn: DbConn) -> Json<Value> { } #[get("/organizations/<org_id>/collections")] -fn get_org_collections(org_id: String, _headers: ManagerHeadersLoose, conn: DbConn) -> Json<Value> { +async fn get_org_collections(org_id: String, _headers: ManagerHeadersLoose, conn: DbConn) -> Json<Value> { Json(json!({ "Data": - Collection::find_by_organization(&org_id, &conn) + Collection::find_by_organization(&org_id, &conn).await .iter() .map(Collection::to_json) .collect::<Value>(), @@ -248,7 +250,7 @@ fn get_org_collections(org_id: String, _headers: ManagerHeadersLoose, conn: DbCo } #[post("/organizations/<org_id>/collections", data = "<data>")] -fn post_organization_collections( +async fn post_organization_collections( org_id: String, headers: ManagerHeadersLoose, data: JsonUpcase<NewCollectionData>, @@ -256,43 +258,43 @@ fn post_organization_collections( ) -> JsonResult { let data: NewCollectionData = data.into_inner().data; - let org = match Organization::find_by_uuid(&org_id, &conn) { + let org = match Organization::find_by_uuid(&org_id, &conn).await { Some(organization) => organization, None => err!("Can't find organization details"), }; // Get the user_organization record so that we can check if the user has access to all collections. - let user_org = match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn) { + let user_org = match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn).await { Some(u) => u, None => err!("User is not part of organization"), }; let collection = Collection::new(org.uuid, data.Name); - collection.save(&conn)?; + collection.save(&conn).await?; // If the user doesn't have access to all collections, only in case of a Manger, // then we need to save the creating user uuid (Manager) to the users_collection table. // Else the user will not have access to his own created collection. if !user_org.access_all { - CollectionUser::save(&headers.user.uuid, &collection.uuid, false, false, &conn)?; + CollectionUser::save(&headers.user.uuid, &collection.uuid, false, false, &conn).await?; } Ok(Json(collection.to_json())) } #[put("/organizations/<org_id>/collections/<col_id>", data = "<data>")] -fn put_organization_collection_update( +async fn put_organization_collection_update( org_id: String, col_id: String, headers: ManagerHeaders, data: JsonUpcase<NewCollectionData>, conn: DbConn, ) -> JsonResult { - post_organization_collection_update(org_id, col_id, headers, data, conn) + post_organization_collection_update(org_id, col_id, headers, data, conn).await } #[post("/organizations/<org_id>/collections/<col_id>", data = "<data>")] -fn post_organization_collection_update( +async fn post_organization_collection_update( org_id: String, col_id: String, _headers: ManagerHeaders, @@ -301,12 +303,12 @@ fn post_organization_collection_update( ) -> JsonResult { let data: NewCollectionData = data.into_inner().data; - let org = match Organization::find_by_uuid(&org_id, &conn) { + let org = match Organization::find_by_uuid(&org_id, &conn).await { Some(organization) => organization, None => err!("Can't find organization details"), }; - let mut collection = match Collection::find_by_uuid(&col_id, &conn) { + let mut collection = match Collection::find_by_uuid(&col_id, &conn).await { Some(collection) => collection, None => err!("Collection not found"), }; @@ -316,20 +318,20 @@ fn post_organization_collection_update( } collection.name = data.Name; - collection.save(&conn)?; + collection.save(&conn).await?; Ok(Json(collection.to_json())) } #[delete("/organizations/<org_id>/collections/<col_id>/user/<org_user_id>")] -fn delete_organization_collection_user( +async fn delete_organization_collection_user( org_id: String, col_id: String, org_user_id: String, _headers: AdminHeaders, conn: DbConn, ) -> EmptyResult { - let collection = match Collection::find_by_uuid(&col_id, &conn) { + let collection = match Collection::find_by_uuid(&col_id, &conn).await { None => err!("Collection not found"), Some(collection) => { if collection.org_uuid == org_id { @@ -340,40 +342,40 @@ fn delete_organization_collection_user( } }; - match UserOrganization::find_by_uuid_and_org(&org_user_id, &org_id, &conn) { + match UserOrganization::find_by_uuid_and_org(&org_user_id, &org_id, &conn).await { None => err!("User not found in organization"), Some(user_org) => { - match CollectionUser::find_by_collection_and_user(&collection.uuid, &user_org.user_uuid, &conn) { + match CollectionUser::find_by_collection_and_user(&collection.uuid, &user_org.user_uuid, &conn).await { None => err!("User not assigned to collection"), - Some(col_user) => col_user.delete(&conn), + Some(col_user) => col_user.delete(&conn).await, } } } } #[post("/organizations/<org_id>/collections/<col_id>/delete-user/<org_user_id>")] -fn post_organization_collection_delete_user( +async fn post_organization_collection_delete_user( org_id: String, col_id: String, org_user_id: String, headers: AdminHeaders, conn: DbConn, ) -> EmptyResult { - delete_organization_collection_user(org_id, col_id, org_user_id, headers, conn) + delete_organization_collection_user(org_id, col_id, org_user_id, headers, conn).await } #[delete("/organizations/<org_id>/collections/<col_id>")] -fn delete_organization_collection( +async fn delete_organization_collection( org_id: String, col_id: String, _headers: ManagerHeaders, conn: DbConn, ) -> EmptyResult { - match Collection::find_by_uuid(&col_id, &conn) { + match Collection::find_by_uuid(&col_id, &conn).await { None => err!("Collection not found"), Some(collection) => { if collection.org_uuid == org_id { - collection.delete(&conn) + collection.delete(&conn).await } else { err!("Collection and Organization id do not match") } @@ -389,19 +391,24 @@ struct DeleteCollectionData { } #[post("/organizations/<org_id>/collections/<col_id>/delete", data = "<_data>")] -fn post_organization_collection_delete( +async fn post_organization_collection_delete( org_id: String, col_id: String, headers: ManagerHeaders, _data: JsonUpcase<DeleteCollectionData>, conn: DbConn, ) -> EmptyResult { - delete_organization_collection(org_id, col_id, headers, conn) + delete_organization_collection(org_id, col_id, headers, conn).await } #[get("/organizations/<org_id>/collections/<coll_id>/details")] -fn get_org_collection_detail(org_id: String, coll_id: String, headers: ManagerHeaders, conn: DbConn) -> JsonResult { - match Collection::find_by_uuid_and_user(&coll_id, &headers.user.uuid, &conn) { +async fn get_org_collection_detail( + org_id: String, + coll_id: String, + headers: ManagerHeaders, + conn: DbConn, +) -> JsonResult { + match Collection::find_by_uuid_and_user(&coll_id, &headers.user.uuid, &conn).await { None => err!("Collection not found"), Some(collection) => { if collection.org_uuid != org_id { @@ -414,28 +421,29 @@ fn get_org_collection_detail(org_id: String, coll_id: String, headers: ManagerHe } #[get("/organizations/<org_id>/collections/<coll_id>/users")] -fn get_collection_users(org_id: String, coll_id: String, _headers: ManagerHeaders, conn: DbConn) -> JsonResult { +async fn get_collection_users(org_id: String, coll_id: String, _headers: ManagerHeaders, conn: DbConn) -> JsonResult { // Get org and collection, check that collection is from org - let collection = match Collection::find_by_uuid_and_org(&coll_id, &org_id, &conn) { + let collection = match Collection::find_by_uuid_and_org(&coll_id, &org_id, &conn).await { None => err!("Collection not found in Organization"), Some(collection) => collection, }; - // Get the users from collection - let user_list: Vec<Value> = CollectionUser::find_by_collection(&collection.uuid, &conn) - .iter() - .map(|col_user| { + let user_list = stream::iter(CollectionUser::find_by_collection(&collection.uuid, &conn).await) + .then(|col_user| async { + let col_user = col_user; // Move out this single variable UserOrganization::find_by_user_and_org(&col_user.user_uuid, &org_id, &conn) + .await .unwrap() - .to_json_user_access_restrictions(col_user) + .to_json_user_access_restrictions(&col_user) }) - .collect(); + .collect::<Vec<Value>>() + .await; Ok(Json(json!(user_list))) } #[put("/organizations/<org_id>/collections/<coll_id>/users", data = "<data>")] -fn put_collection_users( +async fn put_collection_users( org_id: String, coll_id: String, data: JsonUpcaseVec<CollectionData>, @@ -443,16 +451,16 @@ fn put_collection_users( conn: DbConn, ) -> EmptyResult { // Get org and collection, check that collection is from org - if Collection::find_by_uuid_and_org(&coll_id, &org_id, &conn).is_none() { + if Collection::find_by_uuid_and_org(&coll_id, &org_id, &conn).await.is_none() { err!("Collection not found in Organization") } // Delete all the user-collections - CollectionUser::delete_all_by_collection(&coll_id, &conn)?; + CollectionUser::delete_all_by_collection(&coll_id, &conn).await?; // And then add all the received ones (except if the user has access_all) for d in data.iter().map(|d| &d.data) { - let user = match UserOrganization::find_by_uuid(&d.Id, &conn) { + let user = match UserOrganization::find_by_uuid(&d.Id, &conn).await { Some(u) => u, None => err!("User is not part of organization"), }; @@ -461,7 +469,7 @@ fn put_collection_users( continue; } - CollectionUser::save(&user.user_uuid, &coll_id, d.ReadOnly, d.HidePasswords, &conn)?; + CollectionUser::save(&user.user_uuid, &coll_id, d.ReadOnly, d.HidePasswords, &conn).await?; } Ok(()) @@ -474,10 +482,14 @@ struct OrgIdData { } #[get("/ciphers/organization-details?<data..>")] -fn get_org_details(data: OrgIdData, headers: Headers, conn: DbConn) -> Json<Value> { - let ciphers = Cipher::find_by_org(&data.organization_id, &conn); - let ciphers_json: Vec<Value> = - ciphers.iter().map(|c| c.to_json(&headers.host, &headers.user.uuid, &conn)).collect(); +async fn get_org_details(data: OrgIdData, headers: Headers, conn: DbConn) -> Json<Value> { + let ciphers_json = stream::iter(Cipher::find_by_org(&data.organization_id, &conn).await) + .then(|c| async { + let c = c; // Move out this single variable + c.to_json(&headers.host, &headers.user.uuid, &conn).await + }) + .collect::<Vec<Value>>() + .await; Json(json!({ "Data": ciphers_json, @@ -487,9 +499,14 @@ fn get_org_details(data: OrgIdData, headers: Headers, conn: DbConn) -> Json<Valu } #[get("/organizations/<org_id>/users")] -fn get_org_users(org_id: String, _headers: ManagerHeadersLoose, conn: DbConn) -> Json<Value> { - let users = UserOrganization::find_by_org(&org_id, &conn); - let users_json: Vec<Value> = users.iter().map(|c| c.to_json_user_details(&conn)).collect(); +async fn get_org_users(org_id: String, _headers: ManagerHeadersLoose, conn: DbConn) -> Json<Value> { + let users_json = stream::iter(UserOrganization::find_by_org(&org_id, &conn).await) + .then(|u| async { + let u = u; // Move out this single variable + u.to_json_user_details(&conn).await + }) + .collect::<Vec<Value>>() + .await; Json(json!({ "Data": users_json, @@ -499,10 +516,15 @@ fn get_org_users(org_id: String, _headers: ManagerHeadersLoose, conn: DbConn) -> } #[post("/organizations/<org_id>/keys", data = "<data>")] -fn post_org_keys(org_id: String, data: JsonUpcase<OrgKeyData>, _headers: AdminHeaders, conn: DbConn) -> JsonResult { +async fn post_org_keys( + org_id: String, + data: JsonUpcase<OrgKeyData>, + _headers: AdminHeaders, + conn: DbConn, +) -> JsonResult { let data: OrgKeyData = data.into_inner().data; - let mut org = match Organization::find_by_uuid(&org_id, &conn) { + let mut org = match Organization::find_by_uuid(&org_id, &conn).await { Some(organization) => { if organization.private_key.is_some() && organization.public_key.is_some() { err!("Organization Keys already exist") @@ -515,7 +537,7 @@ fn post_org_keys(org_id: String, data: JsonUpcase<OrgKeyData>, _headers: AdminHe org.private_key = Some(data.EncryptedPrivateKey); org.public_key = Some(data.PublicKey); - org.save(&conn)?; + org.save(&conn).await?; Ok(Json(json!({ "Object": "organizationKeys", @@ -542,7 +564,7 @@ struct InviteData { } #[post("/organizations/<org_id>/users/invite", data = "<data>")] -fn send_invite(org_id: String, data: JsonUpcase<InviteData>, headers: AdminHeaders, conn: DbConn) -> EmptyResult { +async fn send_invite(org_id: String, data: JsonUpcase<InviteData>, headers: AdminHeaders, conn: DbConn) -> EmptyResult { let data: InviteData = data.into_inner().data; let new_type = match UserOrgType::from_str(&data.Type.into_string()) { @@ -561,7 +583,7 @@ fn send_invite(org_id: String, data: JsonUpcase<InviteData>, headers: AdminHeade } else { UserOrgStatus::Accepted as i32 // Automatically mark user as accepted if no email invites }; - let user = match User::find_by_mail(&email, &conn) { + let user = match User::find_by_mail(&email, &conn).await { None => { if !CONFIG.invitations_allowed() { err!(format!("User does not exist: {}", email)) @@ -573,16 +595,16 @@ fn send_invite(org_id: String, data: JsonUpcase<InviteData>, headers: AdminHeade 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_org_status = UserOrgStatus::Invited as i32; user } Some(user) => { - if UserOrganization::find_by_user_and_org(&user.uuid, &org_id, &conn).is_some() { + if UserOrganization::find_by_user_and_org(&user.uuid, &org_id, &conn).await.is_some() { err!(format!("User already in organization: {}", email)) } else { user @@ -599,19 +621,20 @@ fn send_invite(org_id: String, data: JsonUpcase<InviteData>, headers: AdminHeade // If no accessAll, add the collections received if !access_all { for col in data.Collections.iter().flatten() { - match Collection::find_by_uuid_and_org(&col.Id, &org_id, &conn) { + match Collection::find_by_uuid_and_org(&col.Id, &org_id, &conn).await { None => err!("Collection not found in Organization"), Some(collection) => { - CollectionUser::save(&user.uuid, &collection.uuid, col.ReadOnly, col.HidePasswords, &conn)?; + CollectionUser::save(&user.uuid, &collection.uuid, col.ReadOnly, col.HidePasswords, &conn) + .await?; } } } } - new_user.save(&conn)?; + new_user.save(&conn).await?; if CONFIG.mail_enabled() { - let org_name = match Organization::find_by_uuid(&org_id, &conn) { + let org_name = match Organization::find_by_uuid(&org_id, &conn).await { Some(org) => org.name, None => err!("Error looking up organization"), }; @@ -631,7 +654,7 @@ fn send_invite(org_id: String, data: JsonUpcase<InviteData>, headers: AdminHeade } #[post("/organizations/<org_id>/users/reinvite", data = "<data>")] -fn bulk_reinvite_user( +async fn bulk_reinvite_user( org_id: String, data: JsonUpcase<OrgBulkIds>, headers: AdminHeaders, @@ -641,7 +664,7 @@ fn bulk_reinvite_user( let mut bulk_response = Vec::new(); for org_user_id in data.Ids { - let err_msg = match _reinvite_user(&org_id, &org_user_id, &headers.user.email, &conn) { + let err_msg = match _reinvite_user(&org_id, &org_user_id, &headers.user.email, &conn).await { Ok(_) => String::from(""), Err(e) => format!("{:?}", e), }; @@ -663,11 +686,11 @@ fn bulk_reinvite_user( } #[post("/organizations/<org_id>/users/<user_org>/reinvite")] -fn reinvite_user(org_id: String, user_org: String, headers: AdminHeaders, conn: DbConn) -> EmptyResult { - _reinvite_user(&org_id, &user_org, &headers.user.email, &conn) +async fn reinvite_user(org_id: String, user_org: String, headers: AdminHeaders, conn: DbConn) -> EmptyResult { + _reinvite_user(&org_id, &user_org, &headers.user.email, &conn).await } -fn _reinvite_user(org_id: &str, user_org: &str, invited_by_email: &str, conn: &DbConn) -> EmptyResult { +async fn _reinvite_user(org_id: &str, user_org: &str, invited_by_email: &str, conn: &DbConn) -> EmptyResult { if !CONFIG.invitations_allowed() { err!("Invitations are not allowed.") } @@ -676,7 +699,7 @@ fn _reinvite_user(org_id: &str, user_org: &str, invited_by_email: &str, conn: &D err!("SMTP is not configured.") } - let user_org = match UserOrganization::find_by_uuid(user_org, conn) { + let user_org = match UserOrganization::find_by_uuid(user_org, conn).await { Some(user_org) => user_org, None => err!("The user hasn't been invited to the organization."), }; @@ -685,12 +708,12 @@ fn _reinvite_user(org_id: &str, user_org: &str, invited_by_email: &str, conn: &D err!("The user is already accepted or confirmed to the organization") } - let user = match User::find_by_uuid(&user_org.user_uuid, conn) { + let user = match User::find_by_uuid(&user_org.user_uuid, conn).await { Some(user) => user, None => err!("User not found."), }; - let org_name = match Organization::find_by_uuid(org_id, conn) { + let org_name = match Organization::find_by_uuid(org_id, conn).await { Some(org) => org.name, None => err!("Error looking up organization."), }; @@ -706,7 +729,7 @@ fn _reinvite_user(org_id: &str, user_org: &str, invited_by_email: &str, conn: &D )?; } else { let invitation = Invitation::new(user.email); - invitation.save(conn)?; + invitation.save(conn).await?; } Ok(()) @@ -719,18 +742,23 @@ struct AcceptData { } #[post("/organizations/<_org_id>/users/<_org_user_id>/accept", data = "<data>")] -fn accept_invite(_org_id: String, _org_user_id: String, data: JsonUpcase<AcceptData>, conn: DbConn) -> EmptyResult { +async fn accept_invite( + _org_id: String, + _org_user_id: String, + data: JsonUpcase<AcceptData>, + conn: DbConn, +) -> EmptyResult { // The web-vault passes org_id and org_user_id in the URL, but we are just reading them from the JWT instead let data: AcceptData = data.into_inner().data; let token = &data.Token; let claims = decode_invite(token)?; - match User::find_by_mail(&claims.email, &conn) { + match User::find_by_mail(&claims.email, &conn).await { Some(_) => { - Invitation::take(&claims.email, &conn); + Invitation::take(&claims.email, &conn).await; if let (Some(user_org), Some(org)) = (&claims.user_org_id, &claims.org_id) { - let mut user_org = match UserOrganization::find_by_uuid_and_org(user_org, org, &conn) { + let mut user_org = match UserOrganization::find_by_uuid_and_org(user_org, org, &conn).await { Some(user_org) => user_org, None => err!("Error accepting the invitation"), }; @@ -739,11 +767,11 @@ fn accept_invite(_org_id: String, _org_user_id: String, data: JsonUpcase<AcceptD err!("User already accepted the invitation") } - let user_twofactor_disabled = TwoFactor::find_by_user(&user_org.user_uuid, &conn).is_empty(); + let user_twofactor_disabled = TwoFactor::find_by_user(&user_org.user_uuid, &conn).await.is_empty(); let policy = OrgPolicyType::TwoFactorAuthentication as i32; let org_twofactor_policy_enabled = - match OrgPolicy::find_by_org_and_type(&user_org.org_uuid, policy, &conn) { + match OrgPolicy::find_by_org_and_type(&user_org.org_uuid, policy, &conn).await { Some(p) => p.enabled, None => false, }; @@ -754,12 +782,15 @@ fn accept_invite(_org_id: String, _org_user_id: String, data: JsonUpcase<AcceptD // Enforce Single Organization Policy of organization user is trying to join let single_org_policy_enabled = - match OrgPolicy::find_by_org_and_type(&user_org.org_uuid, OrgPolicyType::SingleOrg as i32, &conn) { + match OrgPolicy::find_by_org_and_type(&user_org.org_uuid, OrgPolicyType::SingleOrg as i32, &conn) + .await + { Some(p) => p.enabled, None => false, }; if single_org_policy_enabled && user_org.atype < UserOrgType::Admin { let is_member_of_another_org = UserOrganization::find_any_state_by_user(&user_org.user_uuid, &conn) + .await .into_iter() .filter(|uo| uo.org_uuid != user_org.org_uuid) .count() @@ -770,14 +801,14 @@ fn accept_invite(_org_id: String, _org_user_id: String, data: JsonUpcase<AcceptD } // Enforce Single Organization Policy of other organizations user is a member of - if OrgPolicy::is_applicable_to_user(&user_org.user_uuid, OrgPolicyType::SingleOrg, &conn) { + if OrgPolicy::is_applicable_to_user(&user_org.user_uuid, OrgPolicyType::SingleOrg, &conn).await { err!( "You cannot join this organization because you are a member of an organization which forbids it" ) } user_org.status = UserOrgStatus::Accepted as i32; - user_org.save(&conn)?; + user_org.save(&conn).await?; } } None => err!("Invited user not found"), @@ -786,7 +817,7 @@ fn accept_invite(_org_id: String, _org_user_id: String, data: JsonUpcase<AcceptD if CONFIG.mail_enabled() { let mut org_name = CONFIG.invitation_org_name(); if let Some(org_id) = &claims.org_id { - org_name = match Organization::find_by_uuid(org_id, &conn) { + org_name = match Organization::find_by_uuid(org_id, &conn).await { Some(org) => org.name, None => err!("Organization not found."), }; @@ -804,7 +835,12 @@ fn accept_invite(_org_id: String, _org_user_id: String, data: JsonUpcase<AcceptD } #[post("/organizations/<org_id>/users/confirm", data = "<data>")] -fn bulk_confirm_invite(org_id: String, data: JsonUpcase<Value>, headers: AdminHeaders, conn: DbConn) -> Json<Value> { +async fn bulk_confirm_invite( + org_id: String, + data: JsonUpcase<Value>, + headers: AdminHeaders, + conn: DbConn, +) -> Json<Value> { let data = data.into_inner().data; let mut bulk_response = Vec::new(); @@ -813,7 +849,7 @@ fn bulk_confirm_invite(org_id: String, data: JsonUpcase<Value>, headers: AdminHe for invite in keys { let org_user_id = invite["Id"].as_str().unwrap_or_default(); let user_key = invite["Key"].as_str().unwrap_or_default(); - let err_msg = match _confirm_invite(&org_id, org_user_id, user_key, &headers, &conn) { + let err_msg = match _confirm_invite(&org_id, org_user_id, user_key, &headers, &conn).await { Ok(_) => String::from(""), Err(e) => format!("{:?}", e), }; @@ -838,7 +874,7 @@ fn bulk_confirm_invite(org_id: String, data: JsonUpcase<Value>, headers: AdminHe } #[post("/organizations/<org_id>/users/<org_user_id>/confirm", data = "<data>")] -fn confirm_invite( +async fn confirm_invite( org_id: String, org_user_id: String, data: JsonUpcase<Value>, @@ -847,15 +883,21 @@ fn confirm_invite( ) -> EmptyResult { let data = data.into_inner().data; let user_key = data["Key"].as_str().unwrap_or_default(); - _confirm_invite(&org_id, &org_user_id, user_key, &headers, &conn) + _confirm_invite(&org_id, &org_user_id, user_key, &headers, &conn).await } -fn _confirm_invite(org_id: &str, org_user_id: &str, key: &str, headers: &AdminHeaders, conn: &DbConn) -> EmptyResult { +async fn _confirm_invite( + org_id: &str, + org_user_id: &str, + key: &str, + headers: &AdminHeaders, + conn: &DbConn, +) -> EmptyResult { if key.is_empty() || org_user_id.is_empty() { err!("Key or UserId is not set, unable to process request"); } - let mut user_to_confirm = match UserOrganization::find_by_uuid_and_org(org_user_id, org_id, conn) { + let mut user_to_confirm = match UserOrganization::find_by_uuid_and_org(org_user_id, org_id, conn).await { Some(user) => user, None => err!("The specified user isn't a member of the organization"), }; @@ -872,28 +914,28 @@ fn _confirm_invite(org_id: &str, org_user_id: &str, key: &str, headers: &AdminHe user_to_confirm.akey = key.to_string(); if CONFIG.mail_enabled() { - let org_name = match Organization::find_by_uuid(org_id, conn) { + let org_name = match Organization::find_by_uuid(org_id, conn).await { Some(org) => org.name, None => err!("Error looking up organization."), }; - let address = match User::find_by_uuid(&user_to_confirm.user_uuid, conn) { + let address = match User::find_by_uuid(&user_to_confirm.user_uuid, conn).await { Some(user) => user.email, None => err!("Error looking up user."), }; mail::send_invite_confirmed(&address, &org_name)?; } - user_to_confirm.save(conn) + user_to_confirm.save(conn).await } #[get("/organizations/<org_id>/users/<org_user_id>")] -fn get_user(org_id: String, org_user_id: String, _headers: AdminHeaders, conn: DbConn) -> JsonResult { - let user = match UserOrganization::find_by_uuid_and_org(&org_user_id, &org_id, &conn) { +async fn get_user(org_id: String, org_user_id: String, _headers: AdminHeaders, conn: DbConn) -> JsonResult { + let user = match UserOrganization::find_by_uuid_and_org(&org_user_id, &org_id, &conn).await { Some(user) => user, None => err!("The specified user isn't a member of the organization"), }; - Ok(Json(user.to_json_details(&conn))) + Ok(Json(user.to_json_details(&conn).await)) } #[derive(Deserialize)] @@ -905,18 +947,18 @@ struct EditUserData { } #[put("/organizations/<org_id>/users/<org_user_id>", data = "<data>", rank = 1)] -fn put_organization_user( +async fn put_organization_user( org_id: String, org_user_id: String, data: JsonUpcase<EditUserData>, headers: AdminHeaders, conn: DbConn, ) -> EmptyResult { - edit_user(org_id, org_user_id, data, headers, conn) + edit_user(org_id, org_user_id, data, headers, conn).await } #[post("/organizations/<org_id>/users/<org_user_id>", data = "<data>", rank = 1)] -fn edit_user( +async fn edit_user( org_id: String, org_user_id: String, data: JsonUpcase<EditUserData>, @@ -930,7 +972,7 @@ fn edit_user( None => err!("Invalid type"), }; - let mut user_to_edit = match UserOrganization::find_by_uuid_and_org(&org_user_id, &org_id, &conn) { + let mut user_to_edit = match UserOrganization::find_by_uuid_and_org(&org_user_id, &org_id, &conn).await { Some(user) => user, None => err!("The specified user isn't member of the organization"), }; @@ -948,7 +990,7 @@ fn edit_user( 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(&org_id, UserOrgType::Owner as i32, &conn).len(); + let num_owners = UserOrganization::find_by_org_and_type(&org_id, UserOrgType::Owner as i32, &conn).await.len(); if num_owners <= 1 { err!("Can't delete the last owner") @@ -959,14 +1001,14 @@ fn edit_user( user_to_edit.atype = new_type as i32; // Delete all the odd collections - for c in CollectionUser::find_by_organization_and_user_uuid(&org_id, &user_to_edit.user_uuid, &conn) { - c.delete(&conn)?; + for c in CollectionUser::find_by_organization_and_user_uuid(&org_id, &user_to_edit.user_uuid, &conn).await { + c.delete(&conn).await?; } // If no accessAll, add the collections received if !data.AccessAll { for col in data.Collections.iter().flatten() { - match Collection::find_by_uuid_and_org(&col.Id, &org_id, &conn) { + match Collection::find_by_uuid_and_org(&col.Id, &org_id, &conn).await { None => err!("Collection not found in Organization"), Some(collection) => { CollectionUser::save( @@ -975,22 +1017,28 @@ fn edit_user( col.ReadOnly, col.HidePasswords, &conn, - )?; + ) + .await?; } } } } - user_to_edit.save(&conn) + user_to_edit.save(&conn).await } #[delete("/organizations/<org_id>/users", data = "<data>")] -fn bulk_delete_user(org_id: String, data: JsonUpcase<OrgBulkIds>, headers: AdminHeaders, conn: DbConn) -> Json<Value> { +async fn bulk_delete_user( + org_id: String, + data: JsonUpcase<OrgBulkIds>, + headers: AdminHeaders, + conn: DbConn, +) -> Json<Value> { let data: OrgBulkIds = data.into_inner().data; let mut bulk_response = Vec::new(); for org_user_id in data.Ids { - let err_msg = match _delete_user(&org_id, &org_user_id, &headers, &conn) { + let err_msg = match _delete_user(&org_id, &org_user_id, &headers, &conn).await { Ok(_) => String::from(""), Err(e) => format!("{:?}", e), }; @@ -1012,12 +1060,12 @@ fn bulk_delete_user(org_id: String, data: JsonUpcase<OrgBulkIds>, headers: Admin } #[delete("/organizations/<org_id>/users/<org_user_id>")] -fn delete_user(org_id: String, org_user_id: String, headers: AdminHeaders, conn: DbConn) -> EmptyResult { - _delete_user(&org_id, &org_user_id, &headers, &conn) +async fn delete_user(org_id: String, org_user_id: String, headers: AdminHeaders, conn: DbConn) -> EmptyResult { + _delete_user(&org_id, &org_user_id, &headers, &conn).await } -fn _delete_user(org_id: &str, org_user_id: &str, headers: &AdminHeaders, conn: &DbConn) -> EmptyResult { - let user_to_delete = match UserOrganization::find_by_uuid_and_org(org_user_id, org_id, conn) { +async fn _delete_user(org_id: &str, org_user_id: &str, headers: &AdminHeaders, conn: &DbConn) -> EmptyResult { + let user_to_delete = match UserOrganization::find_by_uuid_and_org(org_user_id, org_id, conn).await { Some(user) => user, None => err!("User to delete isn't member of the organization"), }; @@ -1028,23 +1076,28 @@ fn _delete_user(org_id: &str, org_user_id: &str, headers: &AdminHeaders, conn: & if user_to_delete.atype == UserOrgType::Owner { // Removing owner, check that there are at least another owner - let num_owners = UserOrganization::find_by_org_and_type(org_id, UserOrgType::Owner as i32, conn).len(); + let num_owners = UserOrganization::find_by_org_and_type(org_id, UserOrgType::Owner as i32, conn).await.len(); if num_owners <= 1 { err!("Can't delete the last owner") } } - user_to_delete.delete(conn) + user_to_delete.delete(conn).await } #[post("/organizations/<org_id>/users/<org_user_id>/delete")] -fn post_delete_user(org_id: String, org_user_id: String, headers: AdminHeaders, conn: DbConn) -> EmptyResult { - delete_user(org_id, org_user_id, headers, conn) +async fn post_delete_user(org_id: String, org_user_id: String, headers: AdminHeaders, conn: DbConn) -> EmptyResult { + delete_user(org_id, org_user_id, headers, conn).await } #[post("/organizations/<org_id>/users/public-keys", data = "<data>")] -fn bulk_public_keys(org_id: String, data: JsonUpcase<OrgBulkIds>, _headers: AdminHeaders, conn: DbConn) -> Json<Value> { +async fn bulk_public_keys( + org_id: String, + data: JsonUpcase<OrgBulkIds>, + _headers: AdminHeaders, + conn: DbConn, +) -> Json<Value> { let data: OrgBulkIds = data.into_inner().data; let mut bulk_response = Vec::new(); @@ -1052,8 +1105,8 @@ fn bulk_public_keys(org_id: String, data: JsonUpcase<OrgBulkIds>, _headers: Admi // If the user does not exists, just ignore it, and do not return any information regarding that UserOrg UUID. // The web-vault will then ignore that user for the folowing steps. for user_org_id in data.Ids { - match UserOrganization::find_by_uuid_and_org(&user_org_id, &org_id, &conn) { - Some(user_org) => match User::find_by_uuid(&user_org.user_uuid, &conn) { + match UserOrganization::find_by_uuid_and_org(&user_org_id, &org_id, &conn).await { + Some(user_org) => match User::find_by_uuid(&user_org.user_uuid, &conn).await { Some(user) => bulk_response.push(json!( { "Object": "organizationUserPublicKeyResponseModel", @@ -1096,29 +1149,27 @@ struct RelationsData { } #[post("/ciphers/import-organization?<query..>", data = "<data>")] -fn post_org_import( +async fn post_org_import( query: OrgIdData, data: JsonUpcase<ImportData>, headers: AdminHeaders, conn: DbConn, - nt: Notify, + nt: Notify<'_>, ) -> EmptyResult { let data: ImportData = data.into_inner().data; let org_id = query.organization_id; - // Read and create the collections - let collections: Vec<_> = data - .Collections - .into_iter() - .map(|coll| { + let collections = stream::iter(data.Collections) + .then(|coll| async { let collection = Collection::new(org_id.clone(), coll.Name); - if collection.save(&conn).is_err() { + if collection.save(&conn).await.is_err() { err!("Failed to create Collection"); } Ok(collection) }) - .collect(); + .collect::<Vec<_>>() + .await; // Read the relations between collections and ciphers let mut relations = Vec::new(); @@ -1128,17 +1179,16 @@ fn post_org_import( let headers: Headers = headers.into(); - // Read and create the ciphers - let ciphers: Vec<_> = data - .Ciphers - .into_iter() - .map(|cipher_data| { + let ciphers = stream::iter(data.Ciphers) + .then(|cipher_data| async { let mut cipher = Cipher::new(cipher_data.Type, cipher_data.Name.clone()); update_cipher_from_data(&mut cipher, cipher_data, &headers, false, &conn, &nt, UpdateType::CipherCreate) + .await .ok(); cipher }) - .collect(); + .collect::<Vec<Cipher>>() + .await; // Assign the collections for (cipher_index, coll_index) in relations { @@ -1149,16 +1199,16 @@ fn post_org_import( Err(_) => err!("Failed to assign to collection"), }; - CollectionCipher::save(cipher_id, coll_id, &conn)?; + CollectionCipher::save(cipher_id, coll_id, &conn).await?; } let mut user = headers.user; - user.update_revision(&conn) + user.update_revision(&conn).await } #[get("/organizations/<org_id>/policies")] -fn list_policies(org_id: String, _headers: AdminHeaders, conn: DbConn) -> Json<Value> { - let policies = OrgPolicy::find_by_org(&org_id, &conn); +async fn list_policies(org_id: String, _headers: AdminHeaders, conn: DbConn) -> Json<Value> { + let policies = OrgPolicy::find_by_org(&org_id, &conn).await; let policies_json: Vec<Value> = policies.iter().map(OrgPolicy::to_json).collect(); Json(json!({ @@ -1169,7 +1219,7 @@ fn list_policies(org_id: String, _headers: AdminHeaders, conn: DbConn) -> Json<V } #[get("/organizations/<org_id>/policies/token?<token>")] -fn list_policies_token(org_id: String, token: String, conn: DbConn) -> JsonResult { +async fn list_policies_token(org_id: String, token: String, conn: DbConn) -> JsonResult { let invite = crate::auth::decode_invite(&token)?; let invite_org_id = match invite.org_id { @@ -1182,7 +1232,7 @@ fn list_policies_token(org_id: String, token: String, conn: DbConn) -> JsonResul } // TODO: We receive the invite token as ?token=<>, validate it contains the org id - let policies = OrgPolicy::find_by_org(&org_id, &conn); + let policies = OrgPolicy::find_by_org(&org_id, &conn).await; let policies_json: Vec<Value> = policies.iter().map(OrgPolicy::to_json).collect(); Ok(Json(json!({ @@ -1193,13 +1243,13 @@ fn list_policies_token(org_id: String, token: String, conn: DbConn) -> JsonResul } #[get("/organizations/<org_id>/policies/<pol_type>")] -fn get_policy(org_id: String, pol_type: i32, _headers: AdminHeaders, conn: DbConn) -> JsonResult { +async fn get_policy(org_id: String, pol_type: i32, _headers: AdminHeaders, conn: DbConn) -> JsonResult { let pol_type_enum = match OrgPolicyType::from_i32(pol_type) { Some(pt) => pt, None => err!("Invalid or unsupported policy type"), }; - let policy = match OrgPolicy::find_by_org_and_type(&org_id, pol_type, &conn) { + let policy = match OrgPolicy::find_by_org_and_type(&org_id, pol_type, &conn).await { Some(p) => p, None => OrgPolicy::new(org_id, pol_type_enum, "{}".to_string()), }; @@ -1216,7 +1266,7 @@ struct PolicyData { } #[put("/organizations/<org_id>/policies/<pol_type>", data = "<data>")] -fn put_policy( +async fn put_policy( org_id: String, pol_type: i32, data: Json<PolicyData>, @@ -1232,10 +1282,8 @@ fn put_policy( // If enabling the TwoFactorAuthentication policy, remove this org's members that do have 2FA if pol_type_enum == OrgPolicyType::TwoFactorAuthentication && data.enabled { - let org_members = UserOrganization::find_by_org(&org_id, &conn); - - for member in org_members.into_iter() { - let user_twofactor_disabled = TwoFactor::find_by_user(&member.user_uuid, &conn).is_empty(); + for member in UserOrganization::find_by_org(&org_id, &conn).await.into_iter() { + let user_twofactor_disabled = TwoFactor::find_by_user(&member.user_uuid, &conn).await.is_empty(); // Policy only applies to non-Owner/non-Admin members who have accepted joining the org if user_twofactor_disabled @@ -1243,24 +1291,23 @@ fn put_policy( && member.status != UserOrgStatus::Invited as i32 { if CONFIG.mail_enabled() { - let org = Organization::find_by_uuid(&member.org_uuid, &conn).unwrap(); - let user = User::find_by_uuid(&member.user_uuid, &conn).unwrap(); + let org = Organization::find_by_uuid(&member.org_uuid, &conn).await.unwrap(); + let user = User::find_by_uuid(&member.user_uuid, &conn).await.unwrap(); mail::send_2fa_removed_from_org(&user.email, &org.name)?; } - member.delete(&conn)?; + member.delete(&conn).await?; } } } // If enabling the SingleOrg policy, remove this org's members that are members of other orgs if pol_type_enum == OrgPolicyType::SingleOrg && data.enabled { - let org_members = UserOrganization::find_by_org(&org_id, &conn); - - for member in org_members.into_iter() { + for member in UserOrganization::find_by_org(&org_id, &conn).await.into_iter() { // Policy only applies to non-Owner/non-Admin members who have accepted joining the org if member.atype < UserOrgType::Admin && member.status != UserOrgStatus::Invited as i32 { let is_member_of_another_org = UserOrganization::find_any_state_by_user(&member.user_uuid, &conn) + .await .into_iter() // Other UserOrganization's where they have accepted being a member of .filter(|uo| uo.uuid != member.uuid && uo.status != UserOrgStatus::Invited as i32) @@ -1269,25 +1316,25 @@ fn put_policy( if is_member_of_another_org { if CONFIG.mail_enabled() { - let org = Organization::find_by_uuid(&member.org_uuid, &conn).unwrap(); - let user = User::find_by_uuid(&member.user_uuid, &conn).unwrap(); + let org = Organization::find_by_uuid(&member.org_uuid, &conn).await.unwrap(); + let user = User::find_by_uuid(&member.user_uuid, &conn).await.unwrap(); mail::send_single_org_removed_from_org(&user.email, &org.name)?; } - member.delete(&conn)?; + member.delete(&conn).await?; } } } } - let mut policy = match OrgPolicy::find_by_org_and_type(&org_id, pol_type, &conn) { + let mut policy = match OrgPolicy::find_by_org_and_type(&org_id, pol_type, &conn).await { Some(p) => p, None => OrgPolicy::new(org_id, pol_type_enum, "{}".to_string()), }; policy.enabled = data.enabled; policy.data = serde_json::to_string(&data.data)?; - policy.save(&conn)?; + policy.save(&conn).await?; Ok(Json(policy.to_json())) } @@ -1360,7 +1407,7 @@ struct OrgImportData { } #[post("/organizations/<org_id>/import", data = "<data>")] -fn import(org_id: String, data: JsonUpcase<OrgImportData>, headers: Headers, conn: DbConn) -> EmptyResult { +async fn import(org_id: String, data: JsonUpcase<OrgImportData>, headers: Headers, conn: DbConn) -> EmptyResult { let data = data.into_inner().data; // TODO: Currently we aren't storing the externalId's anywhere, so we also don't have a way @@ -1369,7 +1416,7 @@ fn import(org_id: String, data: JsonUpcase<OrgImportData>, headers: Headers, con // as opposed to upstream which only removes auto-imported users. // User needs to be admin or owner to use the Directry Connector - match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn) { + match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn).await { Some(user_org) if user_org.atype >= UserOrgType::Admin => { /* Okay, nothing to do */ } Some(_) => err!("User has insufficient permissions to use Directory Connector"), None => err!("User not part of organization"), @@ -1378,13 +1425,13 @@ fn import(org_id: String, data: JsonUpcase<OrgImportData>, headers: Headers, con for user_data in &data.Users { if user_data.Deleted { // If user is marked for deletion and it exists, delete it - if let Some(user_org) = UserOrganization::find_by_email_and_org(&user_data.Email, &org_id, &conn) { - user_org.delete(&conn)?; + if let Some(user_org) = UserOrganization::find_by_email_and_org(&user_data.Email, &org_id, &conn).await { + user_org.delete(&conn).await?; } // If user is not part of the organization, but it exists - } else if UserOrganization::find_by_email_and_org(&user_data.Email, &org_id, &conn).is_none() { - if let Some(user) = User::find_by_mail(&user_data.Email, &conn) { + } else if UserOrganization::find_by_email_and_org(&user_data.Email, &org_id, &conn).await.is_none() { + if let Some(user) = User::find_by_mail(&user_data.Email, &conn).await { let user_org_status = if CONFIG.mail_enabled() { UserOrgStatus::Invited as i32 } else { @@ -1396,10 +1443,10 @@ fn import(org_id: String, data: JsonUpcase<OrgImportData>, headers: Headers, con new_org_user.atype = UserOrgType::User as i32; new_org_user.status = user_org_status; - new_org_user.save(&conn)?; + new_org_user.save(&conn).await?; if CONFIG.mail_enabled() { - let org_name = match Organization::find_by_uuid(&org_id, &conn) { + let org_name = match Organization::find_by_uuid(&org_id, &conn).await { Some(org) => org.name, None => err!("Error looking up organization"), }; @@ -1419,10 +1466,10 @@ fn import(org_id: String, data: JsonUpcase<OrgImportData>, headers: Headers, con // If this flag is enabled, any user that isn't provided in the Users list will be removed (by default they will be kept unless they have Deleted == true) if data.OverwriteExisting { - for user_org in UserOrganization::find_by_org_and_type(&org_id, UserOrgType::User as i32, &conn) { - if let Some(user_email) = User::find_by_uuid(&user_org.user_uuid, &conn).map(|u| u.email) { + for user_org in UserOrganization::find_by_org_and_type(&org_id, UserOrgType::User as i32, &conn).await { + if let Some(user_email) = User::find_by_uuid(&user_org.user_uuid, &conn).await.map(|u| u.email) { if !data.Users.iter().any(|u| u.Email == user_email) { - user_org.delete(&conn)?; + user_org.delete(&conn).await?; } } } diff --git a/src/api/core/sends.rs b/src/api/core/sends.rs @@ -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())) } diff --git a/src/api/core/two_factor/authenticator.rs b/src/api/core/two_factor/authenticator.rs @@ -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); diff --git a/src/api/core/two_factor/duo.rs b/src/api/core/two_factor/duo.rs @@ -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)?; diff --git a/src/api/core/two_factor/email.rs b/src/api/core/two_factor/email.rs @@ -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; diff --git a/src/api/core/two_factor/mod.rs b/src/api/core/two_factor/mod.rs @@ -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"); } } diff --git a/src/api/core/two_factor/u2f.rs b/src/api/core/two_factor/u2f.rs @@ -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, &regs, &conn)?; + save_u2f_registrations(&user.uuid, &regs, &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, &registrations, conn)?; + save_u2f_registrations(user_uuid, &registrations, conn).await?; return Ok(()); } Err(u2f::u2ferror::U2fError::CounterTooLow) => { reg.compromised = true; - save_u2f_registrations(user_uuid, &registrations, conn)?; + save_u2f_registrations(user_uuid, &registrations, conn).await?; err!("This device might be compromised!"); } diff --git a/src/api/core/two_factor/webauthn.rs b/src/api/core/two_factor/webauthn.rs @@ -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(&registrations)?).save(&conn)?; - _generate_recover_code(&mut user, &conn); + TwoFactor::new(user.uuid.clone(), TwoFactorType::Webauthn, serde_json::to_string(&registrations)?) + .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(&registrations)?) - .save(conn)?; + .save(conn) + .await?; return Ok(()); } } diff --git a/src/api/core/two_factor/yubikey.rs b/src/api/core/two_factor/yubikey.rs @@ -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 { diff --git a/src/api/icons.rs b/src/api/icons.rs @@ -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); diff --git a/src/api/identity.rs b/src/api/identity.rs @@ -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"))] diff --git a/src/auth.rs b/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"), } diff --git a/src/db/mod.rs b/src/db/mod.rs @@ -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 diff --git a/src/db/models/attachment.rs b/src/db/models/attachment.rs @@ -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))) diff --git a/src/db/models/cipher.rs b/src/db/models/cipher.rs @@ -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( diff --git a/src/db/models/collection.rs b/src/db/models/collection.rs @@ -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; } } } diff --git a/src/db/models/device.rs b/src/db/models/device.rs @@ -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)) diff --git a/src/db/models/emergency_access.rs b/src/db/models/emergency_access.rs @@ -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)) diff --git a/src/db/models/favorite.rs b/src/db/models/favorite.rs @@ -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) diff --git a/src/db/models/folder.rs b/src/db/models/folder.rs @@ -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)) diff --git a/src/db/models/org_policy.rs b/src/db/models/org_policy.rs @@ -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") - }} - }*/ } diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs @@ -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)) diff --git a/src/db/models/send.rs b/src/db/models/send.rs @@ -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 diff --git a/src/db/models/two_factor.rs b/src/db/models/two_factor.rs @@ -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(&regs)?; - 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(()) diff --git a/src/db/models/two_factor_incomplete.rs b/src/db/models/two_factor_incomplete.rs @@ -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) diff --git a/src/db/models/user.rs b/src/db/models/user.rs @@ -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, } } diff --git a/src/main.rs b/src/main.rs @@ -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. }