mirror of
				https://github.com/dani-garcia/vaultwarden.git
				synced 2025-10-31 10:18:19 +02:00 
			
		
		
		
	Update to diesel2
This commit is contained in:
		| @@ -81,7 +81,7 @@ fn enforce_password_hint_setting(password_hint: &Option<String>) -> EmptyResult | ||||
| } | ||||
|  | ||||
| #[post("/accounts/register", data = "<data>")] | ||||
| async fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> JsonResult { | ||||
| async fn register(data: JsonUpcase<RegisterData>, mut conn: DbConn) -> JsonResult { | ||||
|     let data: RegisterData = data.into_inner().data; | ||||
|     let email = data.Email.to_lowercase(); | ||||
|  | ||||
| @@ -100,7 +100,7 @@ async fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> JsonResult { | ||||
|  | ||||
|     let mut verified_by_invite = false; | ||||
|  | ||||
|     let mut user = match User::find_by_mail(&email, &conn).await { | ||||
|     let mut user = match User::find_by_mail(&email, &mut conn).await { | ||||
|         Some(mut user) => { | ||||
|             if !user.password_hash.is_empty() { | ||||
|                 err!("Registration not allowed or user already exists") | ||||
| @@ -116,14 +116,14 @@ async fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> JsonResult { | ||||
|                 } else { | ||||
|                     err!("Registration email does not match invite email") | ||||
|                 } | ||||
|             } else if Invitation::take(&email, &conn).await { | ||||
|                 for mut user_org in UserOrganization::find_invited_by_user(&user.uuid, &conn).await.iter_mut() { | ||||
|             } else if Invitation::take(&email, &mut conn).await { | ||||
|                 for mut user_org in UserOrganization::find_invited_by_user(&user.uuid, &mut conn).await.iter_mut() { | ||||
|                     user_org.status = UserOrgStatus::Accepted as i32; | ||||
|                     user_org.save(&conn).await?; | ||||
|                     user_org.save(&mut conn).await?; | ||||
|                 } | ||||
|                 user | ||||
|             } else if CONFIG.is_signup_allowed(&email) | ||||
|                 || EmergencyAccess::find_invited_by_grantee_email(&email, &conn).await.is_some() | ||||
|                 || EmergencyAccess::find_invited_by_grantee_email(&email, &mut conn).await.is_some() | ||||
|             { | ||||
|                 user | ||||
|             } else { | ||||
| @@ -134,7 +134,7 @@ async fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> JsonResult { | ||||
|             // 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).await || CONFIG.is_signup_allowed(&email) { | ||||
|             if Invitation::take(&email, &mut conn).await || CONFIG.is_signup_allowed(&email) { | ||||
|                 User::new(email.clone()) | ||||
|             } else { | ||||
|                 err!("Registration not allowed or user already exists") | ||||
| @@ -143,7 +143,7 @@ async fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> JsonResult { | ||||
|     }; | ||||
|  | ||||
|     // Make sure we don't leave a lingering invitation. | ||||
|     Invitation::take(&email, &conn).await; | ||||
|     Invitation::take(&email, &mut conn).await; | ||||
|  | ||||
|     if let Some(client_kdf_iter) = data.KdfIterations { | ||||
|         user.client_kdf_iter = client_kdf_iter; | ||||
| @@ -179,7 +179,7 @@ async fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> JsonResult { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     user.save(&conn).await?; | ||||
|     user.save(&mut conn).await?; | ||||
|     Ok(Json(json!({ | ||||
|       "Object": "register", | ||||
|       "CaptchaBypassToken": "", | ||||
| @@ -187,8 +187,8 @@ async fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> JsonResult { | ||||
| } | ||||
|  | ||||
| #[get("/accounts/profile")] | ||||
| async fn profile(headers: Headers, conn: DbConn) -> Json<Value> { | ||||
|     Json(headers.user.to_json(&conn).await) | ||||
| async fn profile(headers: Headers, mut conn: DbConn) -> Json<Value> { | ||||
|     Json(headers.user.to_json(&mut conn).await) | ||||
| } | ||||
|  | ||||
| #[derive(Deserialize, Debug)] | ||||
| @@ -205,7 +205,7 @@ async fn put_profile(data: JsonUpcase<ProfileData>, headers: Headers, conn: DbCo | ||||
| } | ||||
|  | ||||
| #[post("/accounts/profile", data = "<data>")] | ||||
| async fn post_profile(data: JsonUpcase<ProfileData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn post_profile(data: JsonUpcase<ProfileData>, headers: Headers, mut conn: DbConn) -> JsonResult { | ||||
|     let data: ProfileData = data.into_inner().data; | ||||
|  | ||||
|     // Check if the length of the username exceeds 50 characters (Same is Upstream Bitwarden) | ||||
| @@ -217,13 +217,13 @@ async fn post_profile(data: JsonUpcase<ProfileData>, headers: Headers, conn: DbC | ||||
|     let mut user = headers.user; | ||||
|     user.name = data.Name; | ||||
|  | ||||
|     user.save(&conn).await?; | ||||
|     Ok(Json(user.to_json(&conn).await)) | ||||
|     user.save(&mut conn).await?; | ||||
|     Ok(Json(user.to_json(&mut conn).await)) | ||||
| } | ||||
|  | ||||
| #[get("/users/<uuid>/public-key")] | ||||
| async fn get_public_keys(uuid: String, _headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let user = match User::find_by_uuid(&uuid, &conn).await { | ||||
| async fn get_public_keys(uuid: String, _headers: Headers, mut conn: DbConn) -> JsonResult { | ||||
|     let user = match User::find_by_uuid(&uuid, &mut conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("User doesn't exist"), | ||||
|     }; | ||||
| @@ -236,7 +236,7 @@ async fn get_public_keys(uuid: String, _headers: Headers, conn: DbConn) -> JsonR | ||||
| } | ||||
|  | ||||
| #[post("/accounts/keys", data = "<data>")] | ||||
| async fn post_keys(data: JsonUpcase<KeysData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn post_keys(data: JsonUpcase<KeysData>, headers: Headers, mut conn: DbConn) -> JsonResult { | ||||
|     let data: KeysData = data.into_inner().data; | ||||
|  | ||||
|     let mut user = headers.user; | ||||
| @@ -244,7 +244,7 @@ async fn post_keys(data: JsonUpcase<KeysData>, headers: Headers, conn: DbConn) - | ||||
|     user.private_key = Some(data.EncryptedPrivateKey); | ||||
|     user.public_key = Some(data.PublicKey); | ||||
|  | ||||
|     user.save(&conn).await?; | ||||
|     user.save(&mut conn).await?; | ||||
|  | ||||
|     Ok(Json(json!({ | ||||
|         "PrivateKey": user.private_key, | ||||
| @@ -263,7 +263,7 @@ struct ChangePassData { | ||||
| } | ||||
|  | ||||
| #[post("/accounts/password", data = "<data>")] | ||||
| async fn post_password(data: JsonUpcase<ChangePassData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn post_password(data: JsonUpcase<ChangePassData>, headers: Headers, mut conn: DbConn) -> EmptyResult { | ||||
|     let data: ChangePassData = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
|  | ||||
| @@ -279,7 +279,7 @@ async fn post_password(data: JsonUpcase<ChangePassData>, headers: Headers, conn: | ||||
|         Some(vec![String::from("post_rotatekey"), String::from("get_contacts"), String::from("get_public_keys")]), | ||||
|     ); | ||||
|     user.akey = data.Key; | ||||
|     user.save(&conn).await | ||||
|     user.save(&mut conn).await | ||||
| } | ||||
|  | ||||
| #[derive(Deserialize)] | ||||
| @@ -294,7 +294,7 @@ struct ChangeKdfData { | ||||
| } | ||||
|  | ||||
| #[post("/accounts/kdf", data = "<data>")] | ||||
| async fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, mut conn: DbConn) -> EmptyResult { | ||||
|     let data: ChangeKdfData = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
|  | ||||
| @@ -306,7 +306,7 @@ async fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, conn: DbCon | ||||
|     user.client_kdf_type = data.Kdf; | ||||
|     user.set_password(&data.NewMasterPasswordHash, None); | ||||
|     user.akey = data.Key; | ||||
|     user.save(&conn).await | ||||
|     user.save(&mut conn).await | ||||
| } | ||||
|  | ||||
| #[derive(Deserialize)] | ||||
| @@ -329,7 +329,7 @@ struct KeyData { | ||||
| } | ||||
|  | ||||
| #[post("/accounts/key", data = "<data>")] | ||||
| async fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult { | ||||
| async fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { | ||||
|     let data: KeyData = data.into_inner().data; | ||||
|  | ||||
|     if !headers.user.check_valid_password(&data.MasterPasswordHash) { | ||||
| @@ -340,7 +340,7 @@ async fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbCon | ||||
|  | ||||
|     // Update folder data | ||||
|     for folder_data in data.Folders { | ||||
|         let mut saved_folder = match Folder::find_by_uuid(&folder_data.Id, &conn).await { | ||||
|         let mut saved_folder = match Folder::find_by_uuid(&folder_data.Id, &mut conn).await { | ||||
|             Some(folder) => folder, | ||||
|             None => err!("Folder doesn't exist"), | ||||
|         }; | ||||
| @@ -350,14 +350,14 @@ async fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbCon | ||||
|         } | ||||
|  | ||||
|         saved_folder.name = folder_data.Name; | ||||
|         saved_folder.save(&conn).await? | ||||
|         saved_folder.save(&mut 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).await { | ||||
|         let mut saved_cipher = match Cipher::find_by_uuid(cipher_data.Id.as_ref().unwrap(), &mut conn).await { | ||||
|             Some(cipher) => cipher, | ||||
|             None => err!("Cipher doesn't exist"), | ||||
|         }; | ||||
| @@ -368,7 +368,8 @@ async fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbCon | ||||
|  | ||||
|         // 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).await? | ||||
|         update_cipher_from_data(&mut saved_cipher, cipher_data, &headers, false, &mut conn, &nt, UpdateType::None) | ||||
|             .await? | ||||
|     } | ||||
|  | ||||
|     // Update user data | ||||
| @@ -378,11 +379,11 @@ async fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbCon | ||||
|     user.private_key = Some(data.PrivateKey); | ||||
|     user.reset_security_stamp(); | ||||
|  | ||||
|     user.save(&conn).await | ||||
|     user.save(&mut conn).await | ||||
| } | ||||
|  | ||||
| #[post("/accounts/security-stamp", data = "<data>")] | ||||
| async fn post_sstamp(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn post_sstamp(data: JsonUpcase<PasswordData>, headers: Headers, mut conn: DbConn) -> EmptyResult { | ||||
|     let data: PasswordData = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
|  | ||||
| @@ -390,9 +391,9 @@ async fn post_sstamp(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbC | ||||
|         err!("Invalid password") | ||||
|     } | ||||
|  | ||||
|     Device::delete_all_by_user(&user.uuid, &conn).await?; | ||||
|     Device::delete_all_by_user(&user.uuid, &mut conn).await?; | ||||
|     user.reset_security_stamp(); | ||||
|     user.save(&conn).await | ||||
|     user.save(&mut conn).await | ||||
| } | ||||
|  | ||||
| #[derive(Deserialize)] | ||||
| @@ -403,7 +404,7 @@ struct EmailTokenData { | ||||
| } | ||||
|  | ||||
| #[post("/accounts/email-token", data = "<data>")] | ||||
| async fn post_email_token(data: JsonUpcase<EmailTokenData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn post_email_token(data: JsonUpcase<EmailTokenData>, headers: Headers, mut conn: DbConn) -> EmptyResult { | ||||
|     let data: EmailTokenData = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
|  | ||||
| @@ -411,7 +412,7 @@ async fn post_email_token(data: JsonUpcase<EmailTokenData>, headers: Headers, co | ||||
|         err!("Invalid password") | ||||
|     } | ||||
|  | ||||
|     if User::find_by_mail(&data.NewEmail, &conn).await.is_some() { | ||||
|     if User::find_by_mail(&data.NewEmail, &mut conn).await.is_some() { | ||||
|         err!("Email already in use"); | ||||
|     } | ||||
|  | ||||
| @@ -429,7 +430,7 @@ async fn post_email_token(data: JsonUpcase<EmailTokenData>, headers: Headers, co | ||||
|  | ||||
|     user.email_new = Some(data.NewEmail); | ||||
|     user.email_new_token = Some(token); | ||||
|     user.save(&conn).await | ||||
|     user.save(&mut conn).await | ||||
| } | ||||
|  | ||||
| #[derive(Deserialize)] | ||||
| @@ -444,7 +445,7 @@ struct ChangeEmailData { | ||||
| } | ||||
|  | ||||
| #[post("/accounts/email", data = "<data>")] | ||||
| async fn post_email(data: JsonUpcase<ChangeEmailData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn post_email(data: JsonUpcase<ChangeEmailData>, headers: Headers, mut conn: DbConn) -> EmptyResult { | ||||
|     let data: ChangeEmailData = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
|  | ||||
| @@ -452,7 +453,7 @@ async fn post_email(data: JsonUpcase<ChangeEmailData>, headers: Headers, conn: D | ||||
|         err!("Invalid password") | ||||
|     } | ||||
|  | ||||
|     if User::find_by_mail(&data.NewEmail, &conn).await.is_some() { | ||||
|     if User::find_by_mail(&data.NewEmail, &mut conn).await.is_some() { | ||||
|         err!("Email already in use"); | ||||
|     } | ||||
|  | ||||
| @@ -487,7 +488,7 @@ async fn post_email(data: JsonUpcase<ChangeEmailData>, headers: Headers, conn: D | ||||
|     user.set_password(&data.NewMasterPasswordHash, None); | ||||
|     user.akey = data.Key; | ||||
|  | ||||
|     user.save(&conn).await | ||||
|     user.save(&mut conn).await | ||||
| } | ||||
|  | ||||
| #[post("/accounts/verify-email")] | ||||
| @@ -513,10 +514,10 @@ struct VerifyEmailTokenData { | ||||
| } | ||||
|  | ||||
| #[post("/accounts/verify-email-token", data = "<data>")] | ||||
| async fn post_verify_email_token(data: JsonUpcase<VerifyEmailTokenData>, conn: DbConn) -> EmptyResult { | ||||
| async fn post_verify_email_token(data: JsonUpcase<VerifyEmailTokenData>, mut conn: DbConn) -> EmptyResult { | ||||
|     let data: VerifyEmailTokenData = data.into_inner().data; | ||||
|  | ||||
|     let mut user = match User::find_by_uuid(&data.UserId, &conn).await { | ||||
|     let mut user = match User::find_by_uuid(&data.UserId, &mut conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("User doesn't exist"), | ||||
|     }; | ||||
| @@ -531,7 +532,7 @@ async fn post_verify_email_token(data: JsonUpcase<VerifyEmailTokenData>, conn: D | ||||
|     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).await { | ||||
|     if let Err(e) = user.save(&mut conn).await { | ||||
|         error!("Error saving email verification: {:#?}", e); | ||||
|     } | ||||
|  | ||||
| @@ -545,11 +546,11 @@ struct DeleteRecoverData { | ||||
| } | ||||
|  | ||||
| #[post("/accounts/delete-recover", data = "<data>")] | ||||
| async fn post_delete_recover(data: JsonUpcase<DeleteRecoverData>, conn: DbConn) -> EmptyResult { | ||||
| async fn post_delete_recover(data: JsonUpcase<DeleteRecoverData>, mut conn: DbConn) -> EmptyResult { | ||||
|     let data: DeleteRecoverData = data.into_inner().data; | ||||
|  | ||||
|     if CONFIG.mail_enabled() { | ||||
|         if let Some(user) = User::find_by_mail(&data.Email, &conn).await { | ||||
|         if let Some(user) = User::find_by_mail(&data.Email, &mut conn).await { | ||||
|             if let Err(e) = mail::send_delete_account(&user.email, &user.uuid).await { | ||||
|                 error!("Error sending delete account email: {:#?}", e); | ||||
|             } | ||||
| @@ -572,10 +573,10 @@ struct DeleteRecoverTokenData { | ||||
| } | ||||
|  | ||||
| #[post("/accounts/delete-recover-token", data = "<data>")] | ||||
| async fn post_delete_recover_token(data: JsonUpcase<DeleteRecoverTokenData>, conn: DbConn) -> EmptyResult { | ||||
| async fn post_delete_recover_token(data: JsonUpcase<DeleteRecoverTokenData>, mut conn: DbConn) -> EmptyResult { | ||||
|     let data: DeleteRecoverTokenData = data.into_inner().data; | ||||
|  | ||||
|     let user = match User::find_by_uuid(&data.UserId, &conn).await { | ||||
|     let user = match User::find_by_uuid(&data.UserId, &mut conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("User doesn't exist"), | ||||
|     }; | ||||
| @@ -587,7 +588,7 @@ async fn post_delete_recover_token(data: JsonUpcase<DeleteRecoverTokenData>, con | ||||
|     if claims.sub != user.uuid { | ||||
|         err!("Invalid claim"); | ||||
|     } | ||||
|     user.delete(&conn).await | ||||
|     user.delete(&mut conn).await | ||||
| } | ||||
|  | ||||
| #[post("/accounts/delete", data = "<data>")] | ||||
| @@ -596,7 +597,7 @@ async fn post_delete_account(data: JsonUpcase<PasswordData>, headers: Headers, c | ||||
| } | ||||
|  | ||||
| #[delete("/accounts", data = "<data>")] | ||||
| async fn delete_account(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn delete_account(data: JsonUpcase<PasswordData>, headers: Headers, mut conn: DbConn) -> EmptyResult { | ||||
|     let data: PasswordData = data.into_inner().data; | ||||
|     let user = headers.user; | ||||
|  | ||||
| @@ -604,7 +605,7 @@ async fn delete_account(data: JsonUpcase<PasswordData>, headers: Headers, conn: | ||||
|         err!("Invalid password") | ||||
|     } | ||||
|  | ||||
|     user.delete(&conn).await | ||||
|     user.delete(&mut conn).await | ||||
| } | ||||
|  | ||||
| #[get("/accounts/revision-date")] | ||||
| @@ -620,7 +621,7 @@ struct PasswordHintData { | ||||
| } | ||||
|  | ||||
| #[post("/accounts/password-hint", data = "<data>")] | ||||
| async fn password_hint(data: JsonUpcase<PasswordHintData>, conn: DbConn) -> EmptyResult { | ||||
| async fn password_hint(data: JsonUpcase<PasswordHintData>, mut conn: DbConn) -> EmptyResult { | ||||
|     if !CONFIG.mail_enabled() && !CONFIG.show_password_hint() { | ||||
|         err!("This server is not configured to provide password hints."); | ||||
|     } | ||||
| @@ -630,7 +631,7 @@ async fn password_hint(data: JsonUpcase<PasswordHintData>, conn: DbConn) -> Empt | ||||
|     let data: PasswordHintData = data.into_inner().data; | ||||
|     let email = &data.Email; | ||||
|  | ||||
|     match User::find_by_mail(email, &conn).await { | ||||
|     match User::find_by_mail(email, &mut conn).await { | ||||
|         None => { | ||||
|             // To prevent user enumeration, act as if the user exists. | ||||
|             if CONFIG.mail_enabled() { | ||||
| @@ -672,10 +673,10 @@ async fn prelogin(data: JsonUpcase<PreloginData>, conn: DbConn) -> Json<Value> { | ||||
|     _prelogin(data, conn).await | ||||
| } | ||||
|  | ||||
| pub async fn _prelogin(data: JsonUpcase<PreloginData>, conn: DbConn) -> Json<Value> { | ||||
| pub async fn _prelogin(data: JsonUpcase<PreloginData>, mut conn: DbConn) -> Json<Value> { | ||||
|     let data: PreloginData = data.into_inner().data; | ||||
|  | ||||
|     let (kdf_type, kdf_iter) = match User::find_by_mail(&data.Email, &conn).await { | ||||
|     let (kdf_type, kdf_iter) = match User::find_by_mail(&data.Email, &mut conn).await { | ||||
|         Some(user) => (user.client_kdf_type, user.client_kdf_iter), | ||||
|         None => (User::CLIENT_KDF_TYPE_DEFAULT, User::CLIENT_KDF_ITER_DEFAULT), | ||||
|     }; | ||||
| @@ -709,7 +710,7 @@ async fn _api_key( | ||||
|     data: JsonUpcase<SecretVerificationRequest>, | ||||
|     rotate: bool, | ||||
|     headers: Headers, | ||||
|     conn: DbConn, | ||||
|     mut conn: DbConn, | ||||
| ) -> JsonResult { | ||||
|     let data: SecretVerificationRequest = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
| @@ -720,7 +721,7 @@ async fn _api_key( | ||||
|  | ||||
|     if rotate || user.api_key.is_none() { | ||||
|         user.api_key = Some(crypto::generate_api_key()); | ||||
|         user.save(&conn).await.expect("Error saving API key"); | ||||
|         user.save(&mut conn).await.expect("Error saving API key"); | ||||
|     } | ||||
|  | ||||
|     Ok(Json(json!({ | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| use std::collections::{HashMap, HashSet}; | ||||
|  | ||||
| use chrono::{NaiveDateTime, Utc}; | ||||
| use futures::{stream, stream::StreamExt}; | ||||
| use rocket::fs::TempFile; | ||||
| use rocket::serde::json::Json; | ||||
| use rocket::{ | ||||
| @@ -85,8 +84,8 @@ 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).await; | ||||
|     if let Ok(mut conn) = pool.get().await { | ||||
|         Cipher::purge_trash(&mut conn).await; | ||||
|     } else { | ||||
|         error!("Failed to get DB connection while purging trashed ciphers") | ||||
|     } | ||||
| @@ -99,39 +98,33 @@ struct SyncData { | ||||
| } | ||||
|  | ||||
| #[get("/sync?<data..>")] | ||||
| async fn sync(data: SyncData, headers: Headers, conn: DbConn) -> Json<Value> { | ||||
|     let user_json = headers.user.to_json(&conn).await; | ||||
| async fn sync(data: SyncData, headers: Headers, mut conn: DbConn) -> Json<Value> { | ||||
|     let user_json = headers.user.to_json(&mut conn).await; | ||||
|  | ||||
|     // Get all ciphers which are visible by the user | ||||
|     let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &conn).await; | ||||
|     let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &mut conn).await; | ||||
|  | ||||
|     let cipher_sync_data = CipherSyncData::new(&headers.user.uuid, &ciphers, CipherSyncType::User, &conn).await; | ||||
|     let cipher_sync_data = CipherSyncData::new(&headers.user.uuid, &ciphers, CipherSyncType::User, &mut conn).await; | ||||
|  | ||||
|     // Lets generate the ciphers_json using all the gathered info | ||||
|     let ciphers_json: Vec<Value> = stream::iter(ciphers) | ||||
|         .then(|c| async { | ||||
|             let c = c; // Move out this single variable | ||||
|             c.to_json(&headers.host, &headers.user.uuid, Some(&cipher_sync_data), &conn).await | ||||
|         }) | ||||
|         .collect() | ||||
|         .await; | ||||
|     let mut ciphers_json = Vec::new(); | ||||
|     for c in ciphers { | ||||
|         ciphers_json.push(c.to_json(&headers.host, &headers.user.uuid, Some(&cipher_sync_data), &mut conn).await); | ||||
|     } | ||||
|  | ||||
|     let collections_json: Vec<Value> = 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, Some(&cipher_sync_data), &conn).await | ||||
|         }) | ||||
|         .collect() | ||||
|         .await; | ||||
|     let mut collections_json = Vec::new(); | ||||
|     for c in Collection::find_by_user_uuid(headers.user.uuid.clone(), &mut conn).await { | ||||
|         collections_json.push(c.to_json_details(&headers.user.uuid, Some(&cipher_sync_data), &mut conn).await); | ||||
|     } | ||||
|  | ||||
|     let folders_json: Vec<Value> = | ||||
|         Folder::find_by_user(&headers.user.uuid, &conn).await.iter().map(Folder::to_json).collect(); | ||||
|         Folder::find_by_user(&headers.user.uuid, &mut conn).await.iter().map(Folder::to_json).collect(); | ||||
|  | ||||
|     let sends_json: Vec<Value> = | ||||
|         Send::find_by_user(&headers.user.uuid, &conn).await.iter().map(Send::to_json).collect(); | ||||
|         Send::find_by_user(&headers.user.uuid, &mut conn).await.iter().map(Send::to_json).collect(); | ||||
|  | ||||
|     let policies_json: Vec<Value> = | ||||
|         OrgPolicy::find_confirmed_by_user(&headers.user.uuid, &conn).await.iter().map(OrgPolicy::to_json).collect(); | ||||
|         OrgPolicy::find_confirmed_by_user(&headers.user.uuid, &mut conn).await.iter().map(OrgPolicy::to_json).collect(); | ||||
|  | ||||
|     let domains_json = if data.exclude_domains { | ||||
|         Value::Null | ||||
| @@ -153,17 +146,14 @@ async fn sync(data: SyncData, headers: Headers, conn: DbConn) -> Json<Value> { | ||||
| } | ||||
|  | ||||
| #[get("/ciphers")] | ||||
| async fn get_ciphers(headers: Headers, conn: DbConn) -> Json<Value> { | ||||
|     let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &conn).await; | ||||
|     let cipher_sync_data = CipherSyncData::new(&headers.user.uuid, &ciphers, CipherSyncType::User, &conn).await; | ||||
| async fn get_ciphers(headers: Headers, mut conn: DbConn) -> Json<Value> { | ||||
|     let ciphers = Cipher::find_by_user_visible(&headers.user.uuid, &mut conn).await; | ||||
|     let cipher_sync_data = CipherSyncData::new(&headers.user.uuid, &ciphers, CipherSyncType::User, &mut conn).await; | ||||
|  | ||||
|     let ciphers_json = stream::iter(ciphers) | ||||
|         .then(|c| async { | ||||
|             let c = c; // Move out this single variable | ||||
|             c.to_json(&headers.host, &headers.user.uuid, Some(&cipher_sync_data), &conn).await | ||||
|         }) | ||||
|         .collect::<Vec<Value>>() | ||||
|         .await; | ||||
|     let mut ciphers_json = Vec::new(); | ||||
|     for c in ciphers { | ||||
|         ciphers_json.push(c.to_json(&headers.host, &headers.user.uuid, Some(&cipher_sync_data), &mut conn).await); | ||||
|     } | ||||
|  | ||||
|     Json(json!({ | ||||
|       "Data": ciphers_json, | ||||
| @@ -173,17 +163,17 @@ async fn get_ciphers(headers: Headers, conn: DbConn) -> Json<Value> { | ||||
| } | ||||
|  | ||||
| #[get("/ciphers/<uuid>")] | ||||
| async fn get_cipher(uuid: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let cipher = match Cipher::find_by_uuid(&uuid, &conn).await { | ||||
| async fn get_cipher(uuid: String, headers: Headers, mut conn: DbConn) -> JsonResult { | ||||
|     let cipher = match Cipher::find_by_uuid(&uuid, &mut conn).await { | ||||
|         Some(cipher) => cipher, | ||||
|         None => err!("Cipher doesn't exist"), | ||||
|     }; | ||||
|  | ||||
|     if !cipher.is_accessible_to_user(&headers.user.uuid, &conn).await { | ||||
|     if !cipher.is_accessible_to_user(&headers.user.uuid, &mut conn).await { | ||||
|         err!("Cipher is not owned by user") | ||||
|     } | ||||
|  | ||||
|     Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, &conn).await)) | ||||
|     Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, &mut conn).await)) | ||||
| } | ||||
|  | ||||
| #[get("/ciphers/<uuid>/admin")] | ||||
| @@ -269,7 +259,7 @@ async fn post_ciphers_admin( | ||||
| async fn post_ciphers_create( | ||||
|     data: JsonUpcase<ShareCipherData>, | ||||
|     headers: Headers, | ||||
|     conn: DbConn, | ||||
|     mut conn: DbConn, | ||||
|     nt: Notify<'_>, | ||||
| ) -> JsonResult { | ||||
|     let mut data: ShareCipherData = data.into_inner().data; | ||||
| @@ -283,11 +273,11 @@ async fn post_ciphers_create( | ||||
|     // 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).await?; | ||||
|     enforce_personal_ownership_policy(Some(&data.Cipher), &headers, &mut 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).await?; | ||||
|     cipher.save(&mut 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 | ||||
| @@ -297,12 +287,12 @@ async fn post_ciphers_create( | ||||
|     // or otherwise), we can just ignore this field entirely. | ||||
|     data.Cipher.LastKnownRevisionDate = None; | ||||
|  | ||||
|     share_cipher_by_uuid(&cipher.uuid, data, &headers, &conn, &nt).await | ||||
|     share_cipher_by_uuid(&cipher.uuid, data, &headers, &mut conn, &nt).await | ||||
| } | ||||
|  | ||||
| /// Called when creating a new user-owned cipher. | ||||
| #[post("/ciphers", data = "<data>")] | ||||
| async fn post_ciphers(data: JsonUpcase<CipherData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult { | ||||
| async fn post_ciphers(data: JsonUpcase<CipherData>, headers: Headers, mut 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 | ||||
| @@ -312,9 +302,9 @@ async fn post_ciphers(data: JsonUpcase<CipherData>, headers: Headers, conn: DbCo | ||||
|     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).await?; | ||||
|     update_cipher_from_data(&mut cipher, data, &headers, false, &mut conn, &nt, UpdateType::CipherCreate).await?; | ||||
|  | ||||
|     Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, &conn).await)) | ||||
|     Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, &mut conn).await)) | ||||
| } | ||||
|  | ||||
| /// Enforces the personal ownership policy on user-owned ciphers, if applicable. | ||||
| @@ -324,7 +314,11 @@ async fn post_ciphers(data: JsonUpcase<CipherData>, headers: Headers, conn: DbCo | ||||
| /// allowed to delete or share such ciphers to an org, however. | ||||
| /// | ||||
| /// Ref: https://bitwarden.com/help/article/policies/#personal-ownership | ||||
| async 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: &mut DbConn, | ||||
| ) -> EmptyResult { | ||||
|     if data.is_none() || data.unwrap().OrganizationId.is_none() { | ||||
|         let user_uuid = &headers.user.uuid; | ||||
|         let policy_type = OrgPolicyType::PersonalOwnership; | ||||
| @@ -340,7 +334,7 @@ pub async fn update_cipher_from_data( | ||||
|     data: CipherData, | ||||
|     headers: &Headers, | ||||
|     shared_to_collection: bool, | ||||
|     conn: &DbConn, | ||||
|     conn: &mut DbConn, | ||||
|     nt: &Notify<'_>, | ||||
|     ut: UpdateType, | ||||
| ) -> EmptyResult { | ||||
| @@ -493,10 +487,10 @@ struct RelationsData { | ||||
| async fn post_ciphers_import( | ||||
|     data: JsonUpcase<ImportData>, | ||||
|     headers: Headers, | ||||
|     conn: DbConn, | ||||
|     mut conn: DbConn, | ||||
|     nt: Notify<'_>, | ||||
| ) -> EmptyResult { | ||||
|     enforce_personal_ownership_policy(None, &headers, &conn).await?; | ||||
|     enforce_personal_ownership_policy(None, &headers, &mut conn).await?; | ||||
|  | ||||
|     let data: ImportData = data.into_inner().data; | ||||
|  | ||||
| @@ -504,7 +498,7 @@ async fn post_ciphers_import( | ||||
|     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).await?; | ||||
|         new_folder.save(&mut conn).await?; | ||||
|  | ||||
|         folders.push(new_folder); | ||||
|     } | ||||
| @@ -522,11 +516,11 @@ async fn post_ciphers_import( | ||||
|         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).await?; | ||||
|         update_cipher_from_data(&mut cipher, cipher_data, &headers, false, &mut conn, &nt, UpdateType::None).await?; | ||||
|     } | ||||
|  | ||||
|     let mut user = headers.user; | ||||
|     user.update_revision(&conn).await?; | ||||
|     user.update_revision(&mut conn).await?; | ||||
|     nt.send_user_update(UpdateType::Vault, &user).await; | ||||
|     Ok(()) | ||||
| } | ||||
| @@ -570,12 +564,12 @@ async fn put_cipher( | ||||
|     uuid: String, | ||||
|     data: JsonUpcase<CipherData>, | ||||
|     headers: Headers, | ||||
|     conn: DbConn, | ||||
|     mut conn: DbConn, | ||||
|     nt: Notify<'_>, | ||||
| ) -> JsonResult { | ||||
|     let data: CipherData = data.into_inner().data; | ||||
|  | ||||
|     let mut cipher = match Cipher::find_by_uuid(&uuid, &conn).await { | ||||
|     let mut cipher = match Cipher::find_by_uuid(&uuid, &mut conn).await { | ||||
|         Some(cipher) => cipher, | ||||
|         None => err!("Cipher doesn't exist"), | ||||
|     }; | ||||
| @@ -585,13 +579,13 @@ async fn put_cipher( | ||||
|     // 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).await { | ||||
|     if !cipher.is_write_accessible_to_user(&headers.user.uuid, &mut conn).await { | ||||
|         err!("Cipher is not write accessible") | ||||
|     } | ||||
|  | ||||
|     update_cipher_from_data(&mut cipher, data, &headers, false, &conn, &nt, UpdateType::CipherUpdate).await?; | ||||
|     update_cipher_from_data(&mut cipher, data, &headers, false, &mut conn, &nt, UpdateType::CipherUpdate).await?; | ||||
|  | ||||
|     Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, &conn).await)) | ||||
|     Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, &mut conn).await)) | ||||
| } | ||||
|  | ||||
| #[derive(Deserialize)] | ||||
| @@ -635,34 +629,34 @@ async fn post_collections_admin( | ||||
|     uuid: String, | ||||
|     data: JsonUpcase<CollectionsAdminData>, | ||||
|     headers: Headers, | ||||
|     conn: DbConn, | ||||
|     mut conn: DbConn, | ||||
| ) -> EmptyResult { | ||||
|     let data: CollectionsAdminData = data.into_inner().data; | ||||
|  | ||||
|     let cipher = match Cipher::find_by_uuid(&uuid, &conn).await { | ||||
|     let cipher = match Cipher::find_by_uuid(&uuid, &mut conn).await { | ||||
|         Some(cipher) => cipher, | ||||
|         None => err!("Cipher doesn't exist"), | ||||
|     }; | ||||
|  | ||||
|     if !cipher.is_write_accessible_to_user(&headers.user.uuid, &conn).await { | ||||
|     if !cipher.is_write_accessible_to_user(&headers.user.uuid, &mut 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).await.iter().cloned().collect(); | ||||
|         cipher.get_collections(headers.user.uuid.clone(), &mut conn).await.iter().cloned().collect(); | ||||
|  | ||||
|     for collection in posted_collections.symmetric_difference(¤t_collections) { | ||||
|         match Collection::find_by_uuid(collection, &conn).await { | ||||
|         match Collection::find_by_uuid(collection, &mut conn).await { | ||||
|             None => err!("Invalid collection ID provided"), | ||||
|             Some(collection) => { | ||||
|                 if collection.is_writable_by_user(&headers.user.uuid, &conn).await { | ||||
|                 if collection.is_writable_by_user(&headers.user.uuid, &mut conn).await { | ||||
|                     if posted_collections.contains(&collection.uuid) { | ||||
|                         // Add to collection | ||||
|                         CollectionCipher::save(&cipher.uuid, &collection.uuid, &conn).await?; | ||||
|                         CollectionCipher::save(&cipher.uuid, &collection.uuid, &mut conn).await?; | ||||
|                     } else { | ||||
|                         // Remove from collection | ||||
|                         CollectionCipher::delete(&cipher.uuid, &collection.uuid, &conn).await?; | ||||
|                         CollectionCipher::delete(&cipher.uuid, &collection.uuid, &mut conn).await?; | ||||
|                     } | ||||
|                 } else { | ||||
|                     err!("No rights to modify the collection") | ||||
| @@ -686,12 +680,12 @@ async fn post_cipher_share( | ||||
|     uuid: String, | ||||
|     data: JsonUpcase<ShareCipherData>, | ||||
|     headers: Headers, | ||||
|     conn: DbConn, | ||||
|     mut conn: DbConn, | ||||
|     nt: Notify<'_>, | ||||
| ) -> JsonResult { | ||||
|     let data: ShareCipherData = data.into_inner().data; | ||||
|  | ||||
|     share_cipher_by_uuid(&uuid, data, &headers, &conn, &nt).await | ||||
|     share_cipher_by_uuid(&uuid, data, &headers, &mut conn, &nt).await | ||||
| } | ||||
|  | ||||
| #[put("/ciphers/<uuid>/share", data = "<data>")] | ||||
| @@ -699,12 +693,12 @@ async fn put_cipher_share( | ||||
|     uuid: String, | ||||
|     data: JsonUpcase<ShareCipherData>, | ||||
|     headers: Headers, | ||||
|     conn: DbConn, | ||||
|     mut conn: DbConn, | ||||
|     nt: Notify<'_>, | ||||
| ) -> JsonResult { | ||||
|     let data: ShareCipherData = data.into_inner().data; | ||||
|  | ||||
|     share_cipher_by_uuid(&uuid, data, &headers, &conn, &nt).await | ||||
|     share_cipher_by_uuid(&uuid, data, &headers, &mut conn, &nt).await | ||||
| } | ||||
|  | ||||
| #[derive(Deserialize)] | ||||
| @@ -718,7 +712,7 @@ struct ShareSelectedCipherData { | ||||
| async fn put_cipher_share_selected( | ||||
|     data: JsonUpcase<ShareSelectedCipherData>, | ||||
|     headers: Headers, | ||||
|     conn: DbConn, | ||||
|     mut conn: DbConn, | ||||
|     nt: Notify<'_>, | ||||
| ) -> EmptyResult { | ||||
|     let mut data: ShareSelectedCipherData = data.into_inner().data; | ||||
| @@ -746,7 +740,7 @@ async 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).await?, | ||||
|             Some(id) => share_cipher_by_uuid(&id, shared_cipher_data, &headers, &mut conn, &nt).await?, | ||||
|             None => err!("Request missing ids field"), | ||||
|         }; | ||||
|     } | ||||
| @@ -758,7 +752,7 @@ async fn share_cipher_by_uuid( | ||||
|     uuid: &str, | ||||
|     data: ShareCipherData, | ||||
|     headers: &Headers, | ||||
|     conn: &DbConn, | ||||
|     conn: &mut DbConn, | ||||
|     nt: &Notify<'_>, | ||||
| ) -> JsonResult { | ||||
|     let mut cipher = match Cipher::find_by_uuid(uuid, conn).await { | ||||
| @@ -816,8 +810,8 @@ async 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>")] | ||||
| async fn get_attachment(uuid: String, attachment_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     match Attachment::find_by_id(&attachment_id, &conn).await { | ||||
| async fn get_attachment(uuid: String, attachment_id: String, headers: Headers, mut conn: DbConn) -> JsonResult { | ||||
|     match Attachment::find_by_id(&attachment_id, &mut 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"), | ||||
| @@ -847,14 +841,14 @@ async fn post_attachment_v2( | ||||
|     uuid: String, | ||||
|     data: JsonUpcase<AttachmentRequestData>, | ||||
|     headers: Headers, | ||||
|     conn: DbConn, | ||||
|     mut conn: DbConn, | ||||
| ) -> JsonResult { | ||||
|     let cipher = match Cipher::find_by_uuid(&uuid, &conn).await { | ||||
|     let cipher = match Cipher::find_by_uuid(&uuid, &mut conn).await { | ||||
|         Some(cipher) => cipher, | ||||
|         None => err!("Cipher doesn't exist"), | ||||
|     }; | ||||
|  | ||||
|     if !cipher.is_write_accessible_to_user(&headers.user.uuid, &conn).await { | ||||
|     if !cipher.is_write_accessible_to_user(&headers.user.uuid, &mut conn).await { | ||||
|         err!("Cipher is not write accessible") | ||||
|     } | ||||
|  | ||||
| @@ -862,7 +856,7 @@ async 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).await.expect("Error saving attachment"); | ||||
|     attachment.save(&mut conn).await.expect("Error saving attachment"); | ||||
|  | ||||
|     let url = format!("/ciphers/{}/attachment/{}", cipher.uuid, attachment_id); | ||||
|     let response_key = match data.AdminRequest { | ||||
| @@ -875,7 +869,7 @@ async fn post_attachment_v2( | ||||
|         "AttachmentId": attachment_id, | ||||
|         "Url": url, | ||||
|         "FileUploadType": FileUploadType::Direct as i32, | ||||
|         response_key: cipher.to_json(&headers.host, &headers.user.uuid, None, &conn).await, | ||||
|         response_key: cipher.to_json(&headers.host, &headers.user.uuid, None, &mut conn).await, | ||||
|     }))) | ||||
| } | ||||
|  | ||||
| @@ -898,15 +892,15 @@ async fn save_attachment( | ||||
|     cipher_uuid: String, | ||||
|     data: Form<UploadData<'_>>, | ||||
|     headers: &Headers, | ||||
|     conn: DbConn, | ||||
|     mut conn: DbConn, | ||||
|     nt: Notify<'_>, | ||||
| ) -> Result<(Cipher, DbConn), crate::error::Error> { | ||||
|     let cipher = match Cipher::find_by_uuid(&cipher_uuid, &conn).await { | ||||
|     let cipher = match Cipher::find_by_uuid(&cipher_uuid, &mut conn).await { | ||||
|         Some(cipher) => cipher, | ||||
|         None => err!("Cipher doesn't exist"), | ||||
|     }; | ||||
|  | ||||
|     if !cipher.is_write_accessible_to_user(&headers.user.uuid, &conn).await { | ||||
|     if !cipher.is_write_accessible_to_user(&headers.user.uuid, &mut conn).await { | ||||
|         err!("Cipher is not write accessible") | ||||
|     } | ||||
|  | ||||
| @@ -921,7 +915,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).await + size_adjust; | ||||
|                 let left = (limit_kb * 1024) - Attachment::size_by_user(user_uuid, &mut conn).await + size_adjust; | ||||
|                 if left <= 0 { | ||||
|                     err!("Attachment storage limit reached! Delete some attachments to free up space") | ||||
|                 } | ||||
| @@ -933,7 +927,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).await + size_adjust; | ||||
|                 let left = (limit_kb * 1024) - Attachment::size_by_org(org_uuid, &mut conn).await + size_adjust; | ||||
|                 if left <= 0 { | ||||
|                     err!("Attachment storage limit reached! Delete some attachments to free up space") | ||||
|                 } | ||||
| @@ -990,10 +984,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).await.expect("Error updating attachment"); | ||||
|                 attachment.save(&mut conn).await.expect("Error updating attachment"); | ||||
|             } | ||||
|         } else { | ||||
|             attachment.delete(&conn).await.ok(); | ||||
|             attachment.delete(&mut conn).await.ok(); | ||||
|  | ||||
|             err!(format!("Attachment size mismatch (expected within [{}, {}], got {})", min_size, max_size, size)); | ||||
|         } | ||||
| @@ -1008,14 +1002,14 @@ 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).await.expect("Error saving attachment"); | ||||
|         attachment.save(&mut conn).await.expect("Error saving attachment"); | ||||
|     } | ||||
|  | ||||
|     if let Err(_err) = data.data.persist_to(&file_path).await { | ||||
|         data.data.move_copy_to(file_path).await? | ||||
|     } | ||||
|  | ||||
|     nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(&conn).await).await; | ||||
|     nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(&mut conn).await).await; | ||||
|  | ||||
|     Ok((cipher, conn)) | ||||
| } | ||||
| @@ -1030,10 +1024,10 @@ async fn post_attachment_v2_data( | ||||
|     attachment_id: String, | ||||
|     data: Form<UploadData<'_>>, | ||||
|     headers: Headers, | ||||
|     conn: DbConn, | ||||
|     mut conn: DbConn, | ||||
|     nt: Notify<'_>, | ||||
| ) -> EmptyResult { | ||||
|     let attachment = match Attachment::find_by_id(&attachment_id, &conn).await { | ||||
|     let attachment = match Attachment::find_by_id(&attachment_id, &mut 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"), | ||||
| @@ -1057,9 +1051,9 @@ async fn post_attachment( | ||||
|     // the attachment database record as well as saving the data to disk. | ||||
|     let attachment = None; | ||||
|  | ||||
|     let (cipher, conn) = save_attachment(attachment, uuid, data, &headers, conn, nt).await?; | ||||
|     let (cipher, mut conn) = save_attachment(attachment, uuid, data, &headers, conn, nt).await?; | ||||
|  | ||||
|     Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, &conn).await)) | ||||
|     Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, &mut conn).await)) | ||||
| } | ||||
|  | ||||
| #[post("/ciphers/<uuid>/attachment-admin", format = "multipart/form-data", data = "<data>")] | ||||
| @@ -1079,10 +1073,10 @@ async fn post_attachment_share( | ||||
|     attachment_id: String, | ||||
|     data: Form<UploadData<'_>>, | ||||
|     headers: Headers, | ||||
|     conn: DbConn, | ||||
|     mut conn: DbConn, | ||||
|     nt: Notify<'_>, | ||||
| ) -> JsonResult { | ||||
|     _delete_cipher_attachment_by_id(&uuid, &attachment_id, &headers, &conn, &nt).await?; | ||||
|     _delete_cipher_attachment_by_id(&uuid, &attachment_id, &headers, &mut conn, &nt).await?; | ||||
|     post_attachment(uuid, data, headers, conn, nt).await | ||||
| } | ||||
|  | ||||
| @@ -1113,10 +1107,10 @@ async fn delete_attachment( | ||||
|     uuid: String, | ||||
|     attachment_id: String, | ||||
|     headers: Headers, | ||||
|     conn: DbConn, | ||||
|     mut conn: DbConn, | ||||
|     nt: Notify<'_>, | ||||
| ) -> EmptyResult { | ||||
|     _delete_cipher_attachment_by_id(&uuid, &attachment_id, &headers, &conn, &nt).await | ||||
|     _delete_cipher_attachment_by_id(&uuid, &attachment_id, &headers, &mut conn, &nt).await | ||||
| } | ||||
|  | ||||
| #[delete("/ciphers/<uuid>/attachment/<attachment_id>/admin")] | ||||
| @@ -1124,40 +1118,40 @@ async fn delete_attachment_admin( | ||||
|     uuid: String, | ||||
|     attachment_id: String, | ||||
|     headers: Headers, | ||||
|     conn: DbConn, | ||||
|     mut conn: DbConn, | ||||
|     nt: Notify<'_>, | ||||
| ) -> EmptyResult { | ||||
|     _delete_cipher_attachment_by_id(&uuid, &attachment_id, &headers, &conn, &nt).await | ||||
|     _delete_cipher_attachment_by_id(&uuid, &attachment_id, &headers, &mut conn, &nt).await | ||||
| } | ||||
|  | ||||
| #[post("/ciphers/<uuid>/delete")] | ||||
| async fn delete_cipher_post(uuid: String, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult { | ||||
|     _delete_cipher_by_uuid(&uuid, &headers, &conn, false, &nt).await | ||||
| async fn delete_cipher_post(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { | ||||
|     _delete_cipher_by_uuid(&uuid, &headers, &mut conn, false, &nt).await | ||||
| } | ||||
|  | ||||
| #[post("/ciphers/<uuid>/delete-admin")] | ||||
| 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 | ||||
| async fn delete_cipher_post_admin(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { | ||||
|     _delete_cipher_by_uuid(&uuid, &headers, &mut conn, false, &nt).await | ||||
| } | ||||
|  | ||||
| #[put("/ciphers/<uuid>/delete")] | ||||
| async fn delete_cipher_put(uuid: String, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult { | ||||
|     _delete_cipher_by_uuid(&uuid, &headers, &conn, true, &nt).await | ||||
| async fn delete_cipher_put(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { | ||||
|     _delete_cipher_by_uuid(&uuid, &headers, &mut conn, true, &nt).await | ||||
| } | ||||
|  | ||||
| #[put("/ciphers/<uuid>/delete-admin")] | ||||
| 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 | ||||
| async fn delete_cipher_put_admin(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { | ||||
|     _delete_cipher_by_uuid(&uuid, &headers, &mut conn, true, &nt).await | ||||
| } | ||||
|  | ||||
| #[delete("/ciphers/<uuid>")] | ||||
| async fn delete_cipher(uuid: String, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult { | ||||
|     _delete_cipher_by_uuid(&uuid, &headers, &conn, false, &nt).await | ||||
| async fn delete_cipher(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { | ||||
|     _delete_cipher_by_uuid(&uuid, &headers, &mut conn, false, &nt).await | ||||
| } | ||||
|  | ||||
| #[delete("/ciphers/<uuid>/admin")] | ||||
| async fn delete_cipher_admin(uuid: String, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult { | ||||
|     _delete_cipher_by_uuid(&uuid, &headers, &conn, false, &nt).await | ||||
| async fn delete_cipher_admin(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { | ||||
|     _delete_cipher_by_uuid(&uuid, &headers, &mut conn, false, &nt).await | ||||
| } | ||||
|  | ||||
| #[delete("/ciphers", data = "<data>")] | ||||
| @@ -1221,23 +1215,23 @@ async fn delete_cipher_selected_put_admin( | ||||
| } | ||||
|  | ||||
| #[put("/ciphers/<uuid>/restore")] | ||||
| async fn restore_cipher_put(uuid: String, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult { | ||||
|     _restore_cipher_by_uuid(&uuid, &headers, &conn, &nt).await | ||||
| async fn restore_cipher_put(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult { | ||||
|     _restore_cipher_by_uuid(&uuid, &headers, &mut conn, &nt).await | ||||
| } | ||||
|  | ||||
| #[put("/ciphers/<uuid>/restore-admin")] | ||||
| async fn restore_cipher_put_admin(uuid: String, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult { | ||||
|     _restore_cipher_by_uuid(&uuid, &headers, &conn, &nt).await | ||||
| async fn restore_cipher_put_admin(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult { | ||||
|     _restore_cipher_by_uuid(&uuid, &headers, &mut conn, &nt).await | ||||
| } | ||||
|  | ||||
| #[put("/ciphers/restore", data = "<data>")] | ||||
| async fn restore_cipher_selected( | ||||
|     data: JsonUpcase<Value>, | ||||
|     headers: Headers, | ||||
|     conn: DbConn, | ||||
|     mut conn: DbConn, | ||||
|     nt: Notify<'_>, | ||||
| ) -> JsonResult { | ||||
|     _restore_multiple_ciphers(data, &headers, &conn, &nt).await | ||||
|     _restore_multiple_ciphers(data, &headers, &mut conn, &nt).await | ||||
| } | ||||
|  | ||||
| #[derive(Deserialize)] | ||||
| @@ -1251,14 +1245,14 @@ struct MoveCipherData { | ||||
| async fn move_cipher_selected( | ||||
|     data: JsonUpcase<MoveCipherData>, | ||||
|     headers: Headers, | ||||
|     conn: DbConn, | ||||
|     mut 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).await { | ||||
|         match Folder::find_by_uuid(folder_id, &mut conn).await { | ||||
|             Some(folder) => { | ||||
|                 if folder.user_uuid != user_uuid { | ||||
|                     err!("Folder is not owned by user") | ||||
| @@ -1269,17 +1263,17 @@ async fn move_cipher_selected( | ||||
|     } | ||||
|  | ||||
|     for uuid in data.Ids { | ||||
|         let cipher = match Cipher::find_by_uuid(&uuid, &conn).await { | ||||
|         let cipher = match Cipher::find_by_uuid(&uuid, &mut conn).await { | ||||
|             Some(cipher) => cipher, | ||||
|             None => err!("Cipher doesn't exist"), | ||||
|         }; | ||||
|  | ||||
|         if !cipher.is_accessible_to_user(&user_uuid, &conn).await { | ||||
|         if !cipher.is_accessible_to_user(&user_uuid, &mut conn).await { | ||||
|             err!("Cipher is not accessible by user") | ||||
|         } | ||||
|  | ||||
|         // Move cipher | ||||
|         cipher.move_to_folder(data.FolderId.clone(), &user_uuid, &conn).await?; | ||||
|         cipher.move_to_folder(data.FolderId.clone(), &user_uuid, &mut conn).await?; | ||||
|  | ||||
|         nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &[user_uuid.clone()]).await; | ||||
|     } | ||||
| @@ -1308,7 +1302,7 @@ async fn delete_all( | ||||
|     organization: Option<OrganizationId>, | ||||
|     data: JsonUpcase<PasswordData>, | ||||
|     headers: Headers, | ||||
|     conn: DbConn, | ||||
|     mut conn: DbConn, | ||||
|     nt: Notify<'_>, | ||||
| ) -> EmptyResult { | ||||
|     let data: PasswordData = data.into_inner().data; | ||||
| @@ -1323,11 +1317,11 @@ async 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).await { | ||||
|             match UserOrganization::find_by_user_and_org(&user.uuid, &org_data.org_id, &mut 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).await?; | ||||
|                         Cipher::delete_all_by_organization(&org_data.org_id, &mut conn).await?; | ||||
|                         nt.send_user_update(UpdateType::Vault, &user).await; | ||||
|                         Ok(()) | ||||
|                     } else { | ||||
| @@ -1339,16 +1333,16 @@ async 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).await { | ||||
|                 cipher.delete(&conn).await?; | ||||
|             for cipher in Cipher::find_owned_by_user(&user.uuid, &mut conn).await { | ||||
|                 cipher.delete(&mut conn).await?; | ||||
|             } | ||||
|  | ||||
|             // Delete folders | ||||
|             for f in Folder::find_by_user(&user.uuid, &conn).await { | ||||
|                 f.delete(&conn).await?; | ||||
|             for f in Folder::find_by_user(&user.uuid, &mut conn).await { | ||||
|                 f.delete(&mut conn).await?; | ||||
|             } | ||||
|  | ||||
|             user.update_revision(&conn).await?; | ||||
|             user.update_revision(&mut conn).await?; | ||||
|             nt.send_user_update(UpdateType::Vault, &user).await; | ||||
|             Ok(()) | ||||
|         } | ||||
| @@ -1358,7 +1352,7 @@ async fn delete_all( | ||||
| async fn _delete_cipher_by_uuid( | ||||
|     uuid: &str, | ||||
|     headers: &Headers, | ||||
|     conn: &DbConn, | ||||
|     conn: &mut DbConn, | ||||
|     soft_delete: bool, | ||||
|     nt: &Notify<'_>, | ||||
| ) -> EmptyResult { | ||||
| @@ -1386,7 +1380,7 @@ async fn _delete_cipher_by_uuid( | ||||
| async fn _delete_multiple_ciphers( | ||||
|     data: JsonUpcase<Value>, | ||||
|     headers: Headers, | ||||
|     conn: DbConn, | ||||
|     mut conn: DbConn, | ||||
|     soft_delete: bool, | ||||
|     nt: Notify<'_>, | ||||
| ) -> EmptyResult { | ||||
| @@ -1401,7 +1395,7 @@ async fn _delete_multiple_ciphers( | ||||
|     }; | ||||
|  | ||||
|     for uuid in uuids { | ||||
|         if let error @ Err(_) = _delete_cipher_by_uuid(uuid, &headers, &conn, soft_delete, &nt).await { | ||||
|         if let error @ Err(_) = _delete_cipher_by_uuid(uuid, &headers, &mut conn, soft_delete, &nt).await { | ||||
|             return error; | ||||
|         }; | ||||
|     } | ||||
| @@ -1409,7 +1403,7 @@ async fn _delete_multiple_ciphers( | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| async fn _restore_cipher_by_uuid(uuid: &str, headers: &Headers, conn: &DbConn, nt: &Notify<'_>) -> JsonResult { | ||||
| async fn _restore_cipher_by_uuid(uuid: &str, headers: &Headers, conn: &mut DbConn, nt: &Notify<'_>) -> JsonResult { | ||||
|     let mut cipher = match Cipher::find_by_uuid(uuid, conn).await { | ||||
|         Some(cipher) => cipher, | ||||
|         None => err!("Cipher doesn't exist"), | ||||
| @@ -1429,7 +1423,7 @@ async fn _restore_cipher_by_uuid(uuid: &str, headers: &Headers, conn: &DbConn, n | ||||
| async fn _restore_multiple_ciphers( | ||||
|     data: JsonUpcase<Value>, | ||||
|     headers: &Headers, | ||||
|     conn: &DbConn, | ||||
|     conn: &mut DbConn, | ||||
|     nt: &Notify<'_>, | ||||
| ) -> JsonResult { | ||||
|     let data: Value = data.into_inner().data; | ||||
| @@ -1461,7 +1455,7 @@ async fn _delete_cipher_attachment_by_id( | ||||
|     uuid: &str, | ||||
|     attachment_id: &str, | ||||
|     headers: &Headers, | ||||
|     conn: &DbConn, | ||||
|     conn: &mut DbConn, | ||||
|     nt: &Notify<'_>, | ||||
| ) -> EmptyResult { | ||||
|     let attachment = match Attachment::find_by_id(attachment_id, conn).await { | ||||
| @@ -1509,9 +1503,9 @@ pub enum CipherSyncType { | ||||
| } | ||||
|  | ||||
| impl CipherSyncData { | ||||
|     pub async fn new(user_uuid: &str, ciphers: &Vec<Cipher>, sync_type: CipherSyncType, conn: &DbConn) -> Self { | ||||
|     pub async fn new(user_uuid: &str, ciphers: &[Cipher], sync_type: CipherSyncType, conn: &mut DbConn) -> Self { | ||||
|         // Generate a list of Cipher UUID's to be used during a query filter with an eq_any. | ||||
|         let cipher_uuids = stream::iter(ciphers).map(|c| c.uuid.clone()).collect::<Vec<String>>().await; | ||||
|         let cipher_uuids = ciphers.iter().map(|c| c.uuid.clone()).collect(); | ||||
|  | ||||
|         let mut cipher_folders: HashMap<String, String> = HashMap::new(); | ||||
|         let mut cipher_favorites: HashSet<String> = HashSet::new(); | ||||
| @@ -1519,11 +1513,10 @@ impl CipherSyncData { | ||||
|             // User Sync supports Folders and Favorits | ||||
|             CipherSyncType::User => { | ||||
|                 // Generate a HashMap with the Cipher UUID as key and the Folder UUID as value | ||||
|                 cipher_folders = stream::iter(FolderCipher::find_by_user(user_uuid, conn).await).collect().await; | ||||
|                 cipher_folders = FolderCipher::find_by_user(user_uuid, conn).await.into_iter().collect(); | ||||
|  | ||||
|                 // Generate a HashSet of all the Cipher UUID's which are marked as favorite | ||||
|                 cipher_favorites = | ||||
|                     stream::iter(Favorite::get_all_cipher_uuid_by_user(user_uuid, conn).await).collect().await; | ||||
|                 cipher_favorites = Favorite::get_all_cipher_uuid_by_user(user_uuid, conn).await.into_iter().collect(); | ||||
|             } | ||||
|             // Organization Sync does not support Folders and Favorits. | ||||
|             // If these are set, it will cause issues in the web-vault. | ||||
| @@ -1538,33 +1531,34 @@ impl CipherSyncData { | ||||
|  | ||||
|         // Generate a HashMap with the Cipher UUID as key and one or more Collection UUID's | ||||
|         let mut cipher_collections: HashMap<String, Vec<String>> = HashMap::new(); | ||||
|         for (cipher, collection) in Cipher::get_collections_with_cipher_by_user(user_uuid, conn).await { | ||||
|         for (cipher, collection) in Cipher::get_collections_with_cipher_by_user(user_uuid.to_string(), conn).await { | ||||
|             cipher_collections.entry(cipher).or_default().push(collection); | ||||
|         } | ||||
|  | ||||
|         // Generate a HashMap with the Organization UUID as key and the UserOrganization record | ||||
|         let user_organizations: HashMap<String, UserOrganization> = | ||||
|             stream::iter(UserOrganization::find_by_user(user_uuid, conn).await) | ||||
|                 .map(|uo| (uo.org_uuid.clone(), uo)) | ||||
|                 .collect() | ||||
|                 .await; | ||||
|         let user_organizations: HashMap<String, UserOrganization> = UserOrganization::find_by_user(user_uuid, conn) | ||||
|             .await | ||||
|             .into_iter() | ||||
|             .map(|uo| (uo.org_uuid.clone(), uo)) | ||||
|             .collect(); | ||||
|  | ||||
|         // Generate a HashMap with the User_Collections UUID as key and the CollectionUser record | ||||
|         let user_collections: HashMap<String, CollectionUser> = | ||||
|             stream::iter(CollectionUser::find_by_user(user_uuid, conn).await) | ||||
|                 .map(|uc| (uc.collection_uuid.clone(), uc)) | ||||
|                 .collect() | ||||
|                 .await; | ||||
|         let user_collections: HashMap<String, CollectionUser> = CollectionUser::find_by_user(user_uuid, conn) | ||||
|             .await | ||||
|             .into_iter() | ||||
|             .map(|uc| (uc.collection_uuid.clone(), uc)) | ||||
|             .collect(); | ||||
|  | ||||
|         // Generate a HashMap with the collections_uuid as key and the CollectionGroup record | ||||
|         let user_collections_groups = stream::iter(CollectionGroup::find_by_user(user_uuid, conn).await) | ||||
|         let user_collections_groups = CollectionGroup::find_by_user(user_uuid, conn) | ||||
|             .await | ||||
|             .into_iter() | ||||
|             .map(|collection_group| (collection_group.collections_uuid.clone(), collection_group)) | ||||
|             .collect() | ||||
|             .await; | ||||
|             .collect(); | ||||
|  | ||||
|         // Get all organizations that the user has full access to via group assignement | ||||
|         let user_group_full_access_for_organizations = | ||||
|             stream::iter(Group::gather_user_organizations_full_access(user_uuid, conn).await).collect().await; | ||||
|             Group::gather_user_organizations_full_access(user_uuid, conn).await.into_iter().collect(); | ||||
|  | ||||
|         Self { | ||||
|             cipher_attachments, | ||||
|   | ||||
| @@ -2,7 +2,6 @@ use chrono::{Duration, Utc}; | ||||
| use rocket::serde::json::Json; | ||||
| use rocket::Route; | ||||
| use serde_json::Value; | ||||
| use std::borrow::Borrow; | ||||
|  | ||||
| use crate::{ | ||||
|     api::{ | ||||
| @@ -14,8 +13,6 @@ use crate::{ | ||||
|     mail, CONFIG, | ||||
| }; | ||||
|  | ||||
| use futures::{stream, stream::StreamExt}; | ||||
|  | ||||
| pub fn routes() -> Vec<Route> { | ||||
|     routes![ | ||||
|         get_contacts, | ||||
| @@ -41,17 +38,13 @@ pub fn routes() -> Vec<Route> { | ||||
| // region get | ||||
|  | ||||
| #[get("/emergency-access/trusted")] | ||||
| async fn get_contacts(headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn get_contacts(headers: Headers, mut conn: DbConn) -> JsonResult { | ||||
|     check_emergency_access_allowed()?; | ||||
|  | ||||
|     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; | ||||
|     let mut emergency_access_list_json = Vec::new(); | ||||
|     for e in EmergencyAccess::find_all_by_grantor_uuid(&headers.user.uuid, &mut conn).await { | ||||
|         emergency_access_list_json.push(e.to_json_grantee_details(&mut conn).await); | ||||
|     } | ||||
|  | ||||
|     Ok(Json(json!({ | ||||
|       "Data": emergency_access_list_json, | ||||
| @@ -61,17 +54,13 @@ async fn get_contacts(headers: Headers, conn: DbConn) -> JsonResult { | ||||
| } | ||||
|  | ||||
| #[get("/emergency-access/granted")] | ||||
| async fn get_grantees(headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn get_grantees(headers: Headers, mut conn: DbConn) -> JsonResult { | ||||
|     check_emergency_access_allowed()?; | ||||
|  | ||||
|     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; | ||||
|     let mut emergency_access_list_json = Vec::new(); | ||||
|     for e in EmergencyAccess::find_all_by_grantee_uuid(&headers.user.uuid, &mut conn).await { | ||||
|         emergency_access_list_json.push(e.to_json_grantor_details(&mut conn).await); | ||||
|     } | ||||
|  | ||||
|     Ok(Json(json!({ | ||||
|       "Data": emergency_access_list_json, | ||||
| @@ -81,11 +70,11 @@ async fn get_grantees(headers: Headers, conn: DbConn) -> JsonResult { | ||||
| } | ||||
|  | ||||
| #[get("/emergency-access/<emer_id>")] | ||||
| async fn get_emergency_access(emer_id: String, conn: DbConn) -> JsonResult { | ||||
| async fn get_emergency_access(emer_id: String, mut conn: DbConn) -> JsonResult { | ||||
|     check_emergency_access_allowed()?; | ||||
|  | ||||
|     match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|         Some(emergency_access) => Ok(Json(emergency_access.to_json_grantee_details(&conn).await)), | ||||
|     match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await { | ||||
|         Some(emergency_access) => Ok(Json(emergency_access.to_json_grantee_details(&mut conn).await)), | ||||
|         None => err!("Emergency access not valid."), | ||||
|     } | ||||
| } | ||||
| @@ -115,13 +104,13 @@ async fn put_emergency_access( | ||||
| async fn post_emergency_access( | ||||
|     emer_id: String, | ||||
|     data: JsonUpcase<EmergencyAccessUpdateData>, | ||||
|     conn: DbConn, | ||||
|     mut 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).await { | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await { | ||||
|         Some(emergency_access) => emergency_access, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
| @@ -135,7 +124,7 @@ async fn post_emergency_access( | ||||
|     emergency_access.wait_time_days = data.WaitTimeDays; | ||||
|     emergency_access.key_encrypted = data.KeyEncrypted; | ||||
|  | ||||
|     emergency_access.save(&conn).await?; | ||||
|     emergency_access.save(&mut conn).await?; | ||||
|     Ok(Json(emergency_access.to_json())) | ||||
| } | ||||
|  | ||||
| @@ -144,12 +133,12 @@ async fn post_emergency_access( | ||||
| // region delete | ||||
|  | ||||
| #[delete("/emergency-access/<emer_id>")] | ||||
| async fn delete_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn delete_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> EmptyResult { | ||||
|     check_emergency_access_allowed()?; | ||||
|  | ||||
|     let grantor_user = headers.user; | ||||
|  | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await { | ||||
|         Some(emer) => { | ||||
|             if emer.grantor_uuid != grantor_user.uuid && emer.grantee_uuid != Some(grantor_user.uuid) { | ||||
|                 err!("Emergency access not valid.") | ||||
| @@ -158,7 +147,7 @@ async fn delete_emergency_access(emer_id: String, headers: Headers, conn: DbConn | ||||
|         } | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
|     emergency_access.delete(&conn).await?; | ||||
|     emergency_access.delete(&mut conn).await?; | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| @@ -180,7 +169,7 @@ struct EmergencyAccessInviteData { | ||||
| } | ||||
|  | ||||
| #[post("/emergency-access/invite", data = "<data>")] | ||||
| async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Headers, mut conn: DbConn) -> EmptyResult { | ||||
|     check_emergency_access_allowed()?; | ||||
|  | ||||
|     let data: EmergencyAccessInviteData = data.into_inner().data; | ||||
| @@ -201,7 +190,7 @@ async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Heade | ||||
|         err!("You can not set yourself as an emergency contact.") | ||||
|     } | ||||
|  | ||||
|     let grantee_user = match User::find_by_mail(&email, &conn).await { | ||||
|     let grantee_user = match User::find_by_mail(&email, &mut conn).await { | ||||
|         None => { | ||||
|             if !CONFIG.invitations_allowed() { | ||||
|                 err!(format!("Grantee user does not exist: {}", email)) | ||||
| @@ -213,11 +202,11 @@ async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Heade | ||||
|  | ||||
|             if !CONFIG.mail_enabled() { | ||||
|                 let invitation = Invitation::new(email.clone()); | ||||
|                 invitation.save(&conn).await?; | ||||
|                 invitation.save(&mut conn).await?; | ||||
|             } | ||||
|  | ||||
|             let mut user = User::new(email.clone()); | ||||
|             user.save(&conn).await?; | ||||
|             user.save(&mut conn).await?; | ||||
|             user | ||||
|         } | ||||
|         Some(user) => user, | ||||
| @@ -227,7 +216,7 @@ async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Heade | ||||
|         &grantor_user.uuid, | ||||
|         &grantee_user.uuid, | ||||
|         &grantee_user.email, | ||||
|         &conn, | ||||
|         &mut conn, | ||||
|     ) | ||||
|     .await | ||||
|     .is_some() | ||||
| @@ -242,7 +231,7 @@ async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Heade | ||||
|         new_type, | ||||
|         wait_time_days, | ||||
|     ); | ||||
|     new_emergency_access.save(&conn).await?; | ||||
|     new_emergency_access.save(&mut conn).await?; | ||||
|  | ||||
|     if CONFIG.mail_enabled() { | ||||
|         mail::send_emergency_access_invite( | ||||
| @@ -255,9 +244,9 @@ async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Heade | ||||
|         .await?; | ||||
|     } else { | ||||
|         // Automatically mark user as accepted if no email invites | ||||
|         match User::find_by_mail(&email, &conn).await { | ||||
|         match User::find_by_mail(&email, &mut conn).await { | ||||
|             Some(user) => { | ||||
|                 match accept_invite_process(user.uuid, new_emergency_access.uuid, Some(email), conn.borrow()).await { | ||||
|                 match accept_invite_process(user.uuid, new_emergency_access.uuid, Some(email), &mut conn).await { | ||||
|                     Ok(v) => v, | ||||
|                     Err(e) => err!(e.to_string()), | ||||
|                 } | ||||
| @@ -270,10 +259,10 @@ async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Heade | ||||
| } | ||||
|  | ||||
| #[post("/emergency-access/<emer_id>/reinvite")] | ||||
| async fn resend_invite(emer_id: String, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn resend_invite(emer_id: String, headers: Headers, mut conn: DbConn) -> EmptyResult { | ||||
|     check_emergency_access_allowed()?; | ||||
|  | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
| @@ -291,7 +280,7 @@ async fn resend_invite(emer_id: String, headers: Headers, conn: DbConn) -> Empty | ||||
|         None => err!("Email not valid."), | ||||
|     }; | ||||
|  | ||||
|     let grantee_user = match User::find_by_mail(&email, &conn).await { | ||||
|     let grantee_user = match User::find_by_mail(&email, &mut conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Grantee user not found."), | ||||
|     }; | ||||
| @@ -308,15 +297,13 @@ async fn resend_invite(emer_id: String, headers: Headers, conn: DbConn) -> Empty | ||||
|         ) | ||||
|         .await?; | ||||
|     } else { | ||||
|         if Invitation::find_by_mail(&email, &conn).await.is_none() { | ||||
|         if Invitation::find_by_mail(&email, &mut conn).await.is_none() { | ||||
|             let invitation = Invitation::new(email); | ||||
|             invitation.save(&conn).await?; | ||||
|             invitation.save(&mut 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()) | ||||
|             .await | ||||
|         { | ||||
|         match accept_invite_process(grantee_user.uuid, emergency_access.uuid, emergency_access.email, &mut conn).await { | ||||
|             Ok(v) => v, | ||||
|             Err(e) => err!(e.to_string()), | ||||
|         } | ||||
| @@ -332,28 +319,28 @@ struct AcceptData { | ||||
| } | ||||
|  | ||||
| #[post("/emergency-access/<emer_id>/accept", data = "<data>")] | ||||
| async fn accept_invite(emer_id: String, data: JsonUpcase<AcceptData>, conn: DbConn) -> EmptyResult { | ||||
| async fn accept_invite(emer_id: String, data: JsonUpcase<AcceptData>, mut 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).await { | ||||
|     let grantee_user = match User::find_by_mail(&claims.email, &mut conn).await { | ||||
|         Some(user) => { | ||||
|             Invitation::take(&claims.email, &conn).await; | ||||
|             Invitation::take(&claims.email, &mut conn).await; | ||||
|             user | ||||
|         } | ||||
|         None => err!("Invited user not found"), | ||||
|     }; | ||||
|  | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut 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).await { | ||||
|     let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &mut conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Grantor user not found."), | ||||
|     }; | ||||
| @@ -362,7 +349,9 @@ async fn accept_invite(emer_id: String, data: JsonUpcase<AcceptData>, conn: DbCo | ||||
|         && (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).await { | ||||
|         match accept_invite_process(grantee_user.uuid.clone(), emer_id, Some(grantee_user.email.clone()), &mut conn) | ||||
|             .await | ||||
|         { | ||||
|             Ok(v) => v, | ||||
|             Err(e) => err!(e.to_string()), | ||||
|         } | ||||
| @@ -381,7 +370,7 @@ async fn accept_invite_process( | ||||
|     grantee_uuid: String, | ||||
|     emer_id: String, | ||||
|     email: Option<String>, | ||||
|     conn: &DbConn, | ||||
|     conn: &mut DbConn, | ||||
| ) -> EmptyResult { | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, conn).await { | ||||
|         Some(emer) => emer, | ||||
| @@ -414,7 +403,7 @@ async fn confirm_emergency_access( | ||||
|     emer_id: String, | ||||
|     data: JsonUpcase<ConfirmData>, | ||||
|     headers: Headers, | ||||
|     conn: DbConn, | ||||
|     mut conn: DbConn, | ||||
| ) -> JsonResult { | ||||
|     check_emergency_access_allowed()?; | ||||
|  | ||||
| @@ -422,7 +411,7 @@ async 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).await { | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
| @@ -433,13 +422,13 @@ async fn confirm_emergency_access( | ||||
|         err!("Emergency access not valid.") | ||||
|     } | ||||
|  | ||||
|     let grantor_user = match User::find_by_uuid(&confirming_user.uuid, &conn).await { | ||||
|     let grantor_user = match User::find_by_uuid(&confirming_user.uuid, &mut 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).await { | ||||
|         let grantee_user = match User::find_by_uuid(grantee_uuid, &mut conn).await { | ||||
|             Some(user) => user, | ||||
|             None => err!("Grantee user not found."), | ||||
|         }; | ||||
| @@ -448,7 +437,7 @@ async fn confirm_emergency_access( | ||||
|         emergency_access.key_encrypted = Some(key); | ||||
|         emergency_access.email = None; | ||||
|  | ||||
|         emergency_access.save(&conn).await?; | ||||
|         emergency_access.save(&mut conn).await?; | ||||
|  | ||||
|         if CONFIG.mail_enabled() { | ||||
|             mail::send_emergency_access_invite_confirmed(&grantee_user.email, &grantor_user.name).await?; | ||||
| @@ -464,11 +453,11 @@ async fn confirm_emergency_access( | ||||
| // region access emergency access | ||||
|  | ||||
| #[post("/emergency-access/<emer_id>/initiate")] | ||||
| async fn initiate_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn initiate_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> JsonResult { | ||||
|     check_emergency_access_allowed()?; | ||||
|  | ||||
|     let initiating_user = headers.user; | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
| @@ -479,7 +468,7 @@ async fn initiate_emergency_access(emer_id: String, headers: Headers, conn: DbCo | ||||
|         err!("Emergency access not valid.") | ||||
|     } | ||||
|  | ||||
|     let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn).await { | ||||
|     let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &mut conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Grantor user not found."), | ||||
|     }; | ||||
| @@ -489,7 +478,7 @@ async fn initiate_emergency_access(emer_id: String, headers: Headers, conn: DbCo | ||||
|     emergency_access.updated_at = now; | ||||
|     emergency_access.recovery_initiated_at = Some(now); | ||||
|     emergency_access.last_notification_at = Some(now); | ||||
|     emergency_access.save(&conn).await?; | ||||
|     emergency_access.save(&mut conn).await?; | ||||
|  | ||||
|     if CONFIG.mail_enabled() { | ||||
|         mail::send_emergency_access_recovery_initiated( | ||||
| @@ -504,11 +493,11 @@ async fn initiate_emergency_access(emer_id: String, headers: Headers, conn: DbCo | ||||
| } | ||||
|  | ||||
| #[post("/emergency-access/<emer_id>/approve")] | ||||
| async fn approve_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn approve_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> JsonResult { | ||||
|     check_emergency_access_allowed()?; | ||||
|  | ||||
|     let approving_user = headers.user; | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
| @@ -519,19 +508,19 @@ async fn approve_emergency_access(emer_id: String, headers: Headers, conn: DbCon | ||||
|         err!("Emergency access not valid.") | ||||
|     } | ||||
|  | ||||
|     let grantor_user = match User::find_by_uuid(&approving_user.uuid, &conn).await { | ||||
|     let grantor_user = match User::find_by_uuid(&approving_user.uuid, &mut 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).await { | ||||
|         let grantee_user = match User::find_by_uuid(grantee_uuid, &mut conn).await { | ||||
|             Some(user) => user, | ||||
|             None => err!("Grantee user not found."), | ||||
|         }; | ||||
|  | ||||
|         emergency_access.status = EmergencyAccessStatus::RecoveryApproved as i32; | ||||
|         emergency_access.save(&conn).await?; | ||||
|         emergency_access.save(&mut conn).await?; | ||||
|  | ||||
|         if CONFIG.mail_enabled() { | ||||
|             mail::send_emergency_access_recovery_approved(&grantee_user.email, &grantor_user.name).await?; | ||||
| @@ -543,11 +532,11 @@ async fn approve_emergency_access(emer_id: String, headers: Headers, conn: DbCon | ||||
| } | ||||
|  | ||||
| #[post("/emergency-access/<emer_id>/reject")] | ||||
| async fn reject_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn reject_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> JsonResult { | ||||
|     check_emergency_access_allowed()?; | ||||
|  | ||||
|     let rejecting_user = headers.user; | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
| @@ -559,19 +548,19 @@ async fn reject_emergency_access(emer_id: String, headers: Headers, conn: DbConn | ||||
|         err!("Emergency access not valid.") | ||||
|     } | ||||
|  | ||||
|     let grantor_user = match User::find_by_uuid(&rejecting_user.uuid, &conn).await { | ||||
|     let grantor_user = match User::find_by_uuid(&rejecting_user.uuid, &mut 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).await { | ||||
|         let grantee_user = match User::find_by_uuid(grantee_uuid, &mut conn).await { | ||||
|             Some(user) => user, | ||||
|             None => err!("Grantee user not found."), | ||||
|         }; | ||||
|  | ||||
|         emergency_access.status = EmergencyAccessStatus::Confirmed as i32; | ||||
|         emergency_access.save(&conn).await?; | ||||
|         emergency_access.save(&mut conn).await?; | ||||
|  | ||||
|         if CONFIG.mail_enabled() { | ||||
|             mail::send_emergency_access_recovery_rejected(&grantee_user.email, &grantor_user.name).await?; | ||||
| @@ -587,12 +576,12 @@ async fn reject_emergency_access(emer_id: String, headers: Headers, conn: DbConn | ||||
| // region action | ||||
|  | ||||
| #[post("/emergency-access/<emer_id>/view")] | ||||
| async fn view_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn view_emergency_access(emer_id: String, headers: Headers, mut 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).await { | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
| @@ -601,17 +590,14 @@ async fn view_emergency_access(emer_id: String, headers: Headers, conn: DbConn) | ||||
|         err!("Emergency access not valid.") | ||||
|     } | ||||
|  | ||||
|     let ciphers = Cipher::find_owned_by_user(&emergency_access.grantor_uuid, &conn).await; | ||||
|     let ciphers = Cipher::find_owned_by_user(&emergency_access.grantor_uuid, &mut conn).await; | ||||
|     let cipher_sync_data = | ||||
|         CipherSyncData::new(&emergency_access.grantor_uuid, &ciphers, CipherSyncType::User, &conn).await; | ||||
|         CipherSyncData::new(&emergency_access.grantor_uuid, &ciphers, CipherSyncType::User, &mut conn).await; | ||||
|  | ||||
|     let ciphers_json = stream::iter(ciphers) | ||||
|         .then(|c| async { | ||||
|             let c = c; // Move out this single variable | ||||
|             c.to_json(&host, &emergency_access.grantor_uuid, Some(&cipher_sync_data), &conn).await | ||||
|         }) | ||||
|         .collect::<Vec<Value>>() | ||||
|         .await; | ||||
|     let mut ciphers_json = Vec::new(); | ||||
|     for c in ciphers { | ||||
|         ciphers_json.push(c.to_json(&host, &emergency_access.grantor_uuid, Some(&cipher_sync_data), &mut conn).await); | ||||
|     } | ||||
|  | ||||
|     Ok(Json(json!({ | ||||
|       "Ciphers": ciphers_json, | ||||
| @@ -621,11 +607,11 @@ async fn view_emergency_access(emer_id: String, headers: Headers, conn: DbConn) | ||||
| } | ||||
|  | ||||
| #[post("/emergency-access/<emer_id>/takeover")] | ||||
| async fn takeover_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn takeover_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> JsonResult { | ||||
|     check_emergency_access_allowed()?; | ||||
|  | ||||
|     let requesting_user = headers.user; | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
| @@ -634,7 +620,7 @@ async fn takeover_emergency_access(emer_id: String, headers: Headers, conn: DbCo | ||||
|         err!("Emergency access not valid.") | ||||
|     } | ||||
|  | ||||
|     let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn).await { | ||||
|     let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &mut conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Grantor user not found."), | ||||
|     }; | ||||
| @@ -659,7 +645,7 @@ async fn password_emergency_access( | ||||
|     emer_id: String, | ||||
|     data: JsonUpcase<EmergencyAccessPasswordData>, | ||||
|     headers: Headers, | ||||
|     conn: DbConn, | ||||
|     mut conn: DbConn, | ||||
| ) -> EmptyResult { | ||||
|     check_emergency_access_allowed()?; | ||||
|  | ||||
| @@ -668,7 +654,7 @@ async fn password_emergency_access( | ||||
|     let key = data.Key; | ||||
|  | ||||
|     let requesting_user = headers.user; | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
| @@ -677,7 +663,7 @@ async fn password_emergency_access( | ||||
|         err!("Emergency access not valid.") | ||||
|     } | ||||
|  | ||||
|     let mut grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn).await { | ||||
|     let mut grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &mut conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Grantor user not found."), | ||||
|     }; | ||||
| @@ -685,15 +671,15 @@ async 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).await?; | ||||
|     grantor_user.save(&mut conn).await?; | ||||
|  | ||||
|     // Disable TwoFactor providers since they will otherwise block logins | ||||
|     TwoFactor::delete_all_by_user(&grantor_user.uuid, &conn).await?; | ||||
|     TwoFactor::delete_all_by_user(&grantor_user.uuid, &mut conn).await?; | ||||
|  | ||||
|     // Remove grantor from all organisations unless Owner | ||||
|     for user_org in UserOrganization::find_any_state_by_user(&grantor_user.uuid, &conn).await { | ||||
|     for user_org in UserOrganization::find_any_state_by_user(&grantor_user.uuid, &mut conn).await { | ||||
|         if user_org.atype != UserOrgType::Owner as i32 { | ||||
|             user_org.delete(&conn).await?; | ||||
|             user_org.delete(&mut conn).await?; | ||||
|         } | ||||
|     } | ||||
|     Ok(()) | ||||
| @@ -702,9 +688,9 @@ async fn password_emergency_access( | ||||
| // endregion | ||||
|  | ||||
| #[get("/emergency-access/<emer_id>/policies")] | ||||
| async fn policies_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn policies_emergency_access(emer_id: String, headers: Headers, mut conn: DbConn) -> JsonResult { | ||||
|     let requesting_user = headers.user; | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &mut conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
| @@ -713,12 +699,12 @@ async fn policies_emergency_access(emer_id: String, headers: Headers, conn: DbCo | ||||
|         err!("Emergency access not valid.") | ||||
|     } | ||||
|  | ||||
|     let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn).await { | ||||
|     let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &mut conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Grantor user not found."), | ||||
|     }; | ||||
|  | ||||
|     let policies = OrgPolicy::find_confirmed_by_user(&grantor_user.uuid, &conn); | ||||
|     let policies = OrgPolicy::find_confirmed_by_user(&grantor_user.uuid, &mut conn); | ||||
|     let policies_json: Vec<Value> = policies.await.iter().map(OrgPolicy::to_json).collect(); | ||||
|  | ||||
|     Ok(Json(json!({ | ||||
| @@ -751,8 +737,8 @@ pub async fn emergency_request_timeout_job(pool: DbPool) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if let Ok(conn) = pool.get().await { | ||||
|         let emergency_access_list = EmergencyAccess::find_all_recoveries(&conn).await; | ||||
|     if let Ok(mut conn) = pool.get().await { | ||||
|         let emergency_access_list = EmergencyAccess::find_all_recoveries(&mut conn).await; | ||||
|  | ||||
|         if emergency_access_list.is_empty() { | ||||
|             debug!("No emergency request timeout to approve"); | ||||
| @@ -764,16 +750,16 @@ pub async fn emergency_request_timeout_job(pool: DbPool) { | ||||
|                     >= emer.recovery_initiated_at.unwrap() + Duration::days(i64::from(emer.wait_time_days)) | ||||
|             { | ||||
|                 emer.status = EmergencyAccessStatus::RecoveryApproved as i32; | ||||
|                 emer.save(&conn).await.expect("Cannot save emergency access on job"); | ||||
|                 emer.save(&mut 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).await.expect("Grantor user not found."); | ||||
|                         User::find_by_uuid(&emer.grantor_uuid, &mut 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) | ||||
|                         User::find_by_uuid(&emer.grantee_uuid.clone().expect("Grantee user invalid."), &mut conn) | ||||
|                             .await | ||||
|                             .expect("Grantee user not found."); | ||||
|  | ||||
| @@ -802,8 +788,8 @@ pub async fn emergency_notification_reminder_job(pool: DbPool) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if let Ok(conn) = pool.get().await { | ||||
|         let emergency_access_list = EmergencyAccess::find_all_recoveries(&conn).await; | ||||
|     if let Ok(mut conn) = pool.get().await { | ||||
|         let emergency_access_list = EmergencyAccess::find_all_recoveries(&mut conn).await; | ||||
|  | ||||
|         if emergency_access_list.is_empty() { | ||||
|             debug!("No emergency request reminder notification to send"); | ||||
| @@ -817,16 +803,16 @@ 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).await.expect("Cannot save emergency access on job"); | ||||
|                 emer.save(&mut 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).await.expect("Grantor user not found."); | ||||
|                         User::find_by_uuid(&emer.grantor_uuid, &mut 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) | ||||
|                         User::find_by_uuid(&emer.grantee_uuid.clone().expect("Grantee user invalid."), &mut conn) | ||||
|                             .await | ||||
|                             .expect("Grantee user not found."); | ||||
|  | ||||
|   | ||||
| @@ -12,8 +12,8 @@ pub fn routes() -> Vec<rocket::Route> { | ||||
| } | ||||
|  | ||||
| #[get("/folders")] | ||||
| async fn get_folders(headers: Headers, conn: DbConn) -> Json<Value> { | ||||
|     let folders = Folder::find_by_user(&headers.user.uuid, &conn).await; | ||||
| async fn get_folders(headers: Headers, mut conn: DbConn) -> Json<Value> { | ||||
|     let folders = Folder::find_by_user(&headers.user.uuid, &mut conn).await; | ||||
|     let folders_json: Vec<Value> = folders.iter().map(Folder::to_json).collect(); | ||||
|  | ||||
|     Json(json!({ | ||||
| @@ -24,8 +24,8 @@ async fn get_folders(headers: Headers, conn: DbConn) -> Json<Value> { | ||||
| } | ||||
|  | ||||
| #[get("/folders/<uuid>")] | ||||
| async fn get_folder(uuid: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let folder = match Folder::find_by_uuid(&uuid, &conn).await { | ||||
| async fn get_folder(uuid: String, headers: Headers, mut conn: DbConn) -> JsonResult { | ||||
|     let folder = match Folder::find_by_uuid(&uuid, &mut conn).await { | ||||
|         Some(folder) => folder, | ||||
|         _ => err!("Invalid folder"), | ||||
|     }; | ||||
| @@ -44,12 +44,12 @@ pub struct FolderData { | ||||
| } | ||||
|  | ||||
| #[post("/folders", data = "<data>")] | ||||
| async fn post_folders(data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult { | ||||
| async fn post_folders(data: JsonUpcase<FolderData>, headers: Headers, mut 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).await?; | ||||
|     folder.save(&mut conn).await?; | ||||
|     nt.send_folder_update(UpdateType::FolderCreate, &folder).await; | ||||
|  | ||||
|     Ok(Json(folder.to_json())) | ||||
| @@ -71,12 +71,12 @@ async fn put_folder( | ||||
|     uuid: String, | ||||
|     data: JsonUpcase<FolderData>, | ||||
|     headers: Headers, | ||||
|     conn: DbConn, | ||||
|     mut conn: DbConn, | ||||
|     nt: Notify<'_>, | ||||
| ) -> JsonResult { | ||||
|     let data: FolderData = data.into_inner().data; | ||||
|  | ||||
|     let mut folder = match Folder::find_by_uuid(&uuid, &conn).await { | ||||
|     let mut folder = match Folder::find_by_uuid(&uuid, &mut conn).await { | ||||
|         Some(folder) => folder, | ||||
|         _ => err!("Invalid folder"), | ||||
|     }; | ||||
| @@ -87,7 +87,7 @@ async fn put_folder( | ||||
|  | ||||
|     folder.name = data.Name; | ||||
|  | ||||
|     folder.save(&conn).await?; | ||||
|     folder.save(&mut conn).await?; | ||||
|     nt.send_folder_update(UpdateType::FolderUpdate, &folder).await; | ||||
|  | ||||
|     Ok(Json(folder.to_json())) | ||||
| @@ -99,8 +99,8 @@ async fn delete_folder_post(uuid: String, headers: Headers, conn: DbConn, nt: No | ||||
| } | ||||
|  | ||||
| #[delete("/folders/<uuid>")] | ||||
| async fn delete_folder(uuid: String, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult { | ||||
|     let folder = match Folder::find_by_uuid(&uuid, &conn).await { | ||||
| async fn delete_folder(uuid: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { | ||||
|     let folder = match Folder::find_by_uuid(&uuid, &mut conn).await { | ||||
|         Some(folder) => folder, | ||||
|         _ => err!("Invalid folder"), | ||||
|     }; | ||||
| @@ -110,7 +110,7 @@ async fn delete_folder(uuid: String, headers: Headers, conn: DbConn, nt: Notify< | ||||
|     } | ||||
|  | ||||
|     // Delete the actual folder entry | ||||
|     folder.delete(&conn).await?; | ||||
|     folder.delete(&mut conn).await?; | ||||
|  | ||||
|     nt.send_folder_update(UpdateType::FolderDelete, &folder).await; | ||||
|     Ok(()) | ||||
|   | ||||
| @@ -128,7 +128,7 @@ struct EquivDomainData { | ||||
| } | ||||
|  | ||||
| #[post("/settings/domains", data = "<data>")] | ||||
| async fn post_eq_domains(data: JsonUpcase<EquivDomainData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn post_eq_domains(data: JsonUpcase<EquivDomainData>, headers: Headers, mut conn: DbConn) -> JsonResult { | ||||
|     let data: EquivDomainData = data.into_inner().data; | ||||
|  | ||||
|     let excluded_globals = data.ExcludedGlobalEquivalentDomains.unwrap_or_default(); | ||||
| @@ -140,7 +140,7 @@ async fn post_eq_domains(data: JsonUpcase<EquivDomainData>, headers: Headers, co | ||||
|     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).await?; | ||||
|     user.save(&mut conn).await?; | ||||
|  | ||||
|     Ok(Json(json!({}))) | ||||
| } | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -39,8 +39,8 @@ 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).await; | ||||
|     if let Ok(mut conn) = pool.get().await { | ||||
|         Send::purge(&mut conn).await; | ||||
|     } else { | ||||
|         error!("Failed to get DB connection while purging sends") | ||||
|     } | ||||
| @@ -74,7 +74,7 @@ struct SendData { | ||||
| /// | ||||
| /// There is also a Vaultwarden-specific `sends_allowed` config setting that | ||||
| /// controls this policy globally. | ||||
| async fn enforce_disable_send_policy(headers: &Headers, conn: &DbConn) -> EmptyResult { | ||||
| async fn enforce_disable_send_policy(headers: &Headers, conn: &mut DbConn) -> EmptyResult { | ||||
|     let user_uuid = &headers.user.uuid; | ||||
|     if !CONFIG.sends_allowed() | ||||
|         || OrgPolicy::is_applicable_to_user(user_uuid, OrgPolicyType::DisableSend, None, conn).await | ||||
| @@ -90,7 +90,7 @@ async fn enforce_disable_send_policy(headers: &Headers, conn: &DbConn) -> EmptyR | ||||
| /// but is allowed to remove this option from an existing Send. | ||||
| /// | ||||
| /// Ref: https://bitwarden.com/help/article/policies/#send-options | ||||
| async 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: &mut 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).await { | ||||
| @@ -142,8 +142,8 @@ fn create_send(data: SendData, user_uuid: String) -> ApiResult<Send> { | ||||
| } | ||||
|  | ||||
| #[get("/sends")] | ||||
| async fn get_sends(headers: Headers, conn: DbConn) -> Json<Value> { | ||||
|     let sends = Send::find_by_user(&headers.user.uuid, &conn); | ||||
| async fn get_sends(headers: Headers, mut conn: DbConn) -> Json<Value> { | ||||
|     let sends = Send::find_by_user(&headers.user.uuid, &mut conn); | ||||
|     let sends_json: Vec<Value> = sends.await.iter().map(|s| s.to_json()).collect(); | ||||
|  | ||||
|     Json(json!({ | ||||
| @@ -154,8 +154,8 @@ async fn get_sends(headers: Headers, conn: DbConn) -> Json<Value> { | ||||
| } | ||||
|  | ||||
| #[get("/sends/<uuid>")] | ||||
| async fn get_send(uuid: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let send = match Send::find_by_uuid(&uuid, &conn).await { | ||||
| async fn get_send(uuid: String, headers: Headers, mut conn: DbConn) -> JsonResult { | ||||
|     let send = match Send::find_by_uuid(&uuid, &mut conn).await { | ||||
|         Some(send) => send, | ||||
|         None => err!("Send not found"), | ||||
|     }; | ||||
| @@ -168,19 +168,19 @@ async fn get_send(uuid: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| } | ||||
|  | ||||
| #[post("/sends", data = "<data>")] | ||||
| async fn post_send(data: JsonUpcase<SendData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult { | ||||
|     enforce_disable_send_policy(&headers, &conn).await?; | ||||
| async fn post_send(data: JsonUpcase<SendData>, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult { | ||||
|     enforce_disable_send_policy(&headers, &mut conn).await?; | ||||
|  | ||||
|     let data: SendData = data.into_inner().data; | ||||
|     enforce_disable_hide_email_policy(&data, &headers, &conn).await?; | ||||
|     enforce_disable_hide_email_policy(&data, &headers, &mut 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).await?; | ||||
|     nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&conn).await).await; | ||||
|     send.save(&mut conn).await?; | ||||
|     nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&mut conn).await).await; | ||||
|  | ||||
|     Ok(Json(send.to_json())) | ||||
| } | ||||
| @@ -200,8 +200,8 @@ struct UploadDataV2<'f> { | ||||
| // This method still exists to support older clients, probably need to remove it sometime. | ||||
| // Upstream: https://github.com/bitwarden/server/blob/d0c793c95181dfb1b447eb450f85ba0bfd7ef643/src/Api/Controllers/SendsController.cs#L164-L167 | ||||
| #[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).await?; | ||||
| async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult { | ||||
|     enforce_disable_send_policy(&headers, &mut conn).await?; | ||||
|  | ||||
|     let UploadData { | ||||
|         model, | ||||
| @@ -209,12 +209,12 @@ 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).await?; | ||||
|     enforce_disable_hide_email_policy(&model, &headers, &mut conn).await?; | ||||
|  | ||||
|     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).await; | ||||
|             let left = (limit_kb * 1024) - Attachment::size_by_user(&headers.user.uuid, &mut conn).await; | ||||
|             if left <= 0 { | ||||
|                 err!("Attachment storage limit reached! Delete some attachments to free up space") | ||||
|             } | ||||
| @@ -264,16 +264,16 @@ 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).await?; | ||||
|     nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&conn).await).await; | ||||
|     send.save(&mut conn).await?; | ||||
|     nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&mut conn).await).await; | ||||
|  | ||||
|     Ok(Json(send.to_json())) | ||||
| } | ||||
|  | ||||
| // Upstream: https://github.com/bitwarden/server/blob/d0c793c95181dfb1b447eb450f85ba0bfd7ef643/src/Api/Controllers/SendsController.cs#L190 | ||||
| #[post("/sends/file/v2", data = "<data>")] | ||||
| async fn post_send_file_v2(data: JsonUpcase<SendData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     enforce_disable_send_policy(&headers, &conn).await?; | ||||
| async fn post_send_file_v2(data: JsonUpcase<SendData>, headers: Headers, mut conn: DbConn) -> JsonResult { | ||||
|     enforce_disable_send_policy(&headers, &mut conn).await?; | ||||
|  | ||||
|     let data = data.into_inner().data; | ||||
|  | ||||
| @@ -281,7 +281,7 @@ async fn post_send_file_v2(data: JsonUpcase<SendData>, headers: Headers, conn: D | ||||
|         err!("Send content is not a file"); | ||||
|     } | ||||
|  | ||||
|     enforce_disable_hide_email_policy(&data, &headers, &conn).await?; | ||||
|     enforce_disable_hide_email_policy(&data, &headers, &mut conn).await?; | ||||
|  | ||||
|     let file_length = match &data.FileLength { | ||||
|         Some(m) => Some(m.into_i32()?), | ||||
| @@ -291,7 +291,7 @@ async fn post_send_file_v2(data: JsonUpcase<SendData>, headers: Headers, conn: D | ||||
|     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).await; | ||||
|             let left = (limit_kb * 1024) - Attachment::size_by_user(&headers.user.uuid, &mut conn).await; | ||||
|             if left <= 0 { | ||||
|                 err!("Attachment storage limit reached! Delete some attachments to free up space") | ||||
|             } | ||||
| @@ -315,7 +315,7 @@ async fn post_send_file_v2(data: JsonUpcase<SendData>, headers: Headers, conn: D | ||||
|         o.insert(String::from("SizeName"), Value::String(crate::util::get_display_size(file_length.unwrap()))); | ||||
|     } | ||||
|     send.data = serde_json::to_string(&data_value)?; | ||||
|     send.save(&conn).await?; | ||||
|     send.save(&mut conn).await?; | ||||
|  | ||||
|     Ok(Json(json!({ | ||||
|         "fileUploadType": 0, // 0 == Direct | 1 == Azure | ||||
| @@ -332,10 +332,10 @@ async fn post_send_file_v2_data( | ||||
|     file_id: String, | ||||
|     data: Form<UploadDataV2<'_>>, | ||||
|     headers: Headers, | ||||
|     conn: DbConn, | ||||
|     mut conn: DbConn, | ||||
|     nt: Notify<'_>, | ||||
| ) -> EmptyResult { | ||||
|     enforce_disable_send_policy(&headers, &conn).await?; | ||||
|     enforce_disable_send_policy(&headers, &mut conn).await?; | ||||
|  | ||||
|     let mut data = data.into_inner(); | ||||
|  | ||||
| @@ -352,7 +352,7 @@ async fn post_send_file_v2_data( | ||||
|         err!("Error reading attachment data. Please try an other client."); | ||||
|     } | ||||
|  | ||||
|     if let Some(send) = Send::find_by_uuid(&send_uuid, &conn).await { | ||||
|     if let Some(send) = Send::find_by_uuid(&send_uuid, &mut conn).await { | ||||
|         let folder_path = tokio::fs::canonicalize(&CONFIG.sends_folder()).await?.join(&send_uuid); | ||||
|         let file_path = folder_path.join(&file_id); | ||||
|         tokio::fs::create_dir_all(&folder_path).await?; | ||||
| @@ -361,7 +361,7 @@ async fn post_send_file_v2_data( | ||||
|             data.data.move_copy_to(file_path).await? | ||||
|         } | ||||
|  | ||||
|         nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&conn).await).await; | ||||
|         nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&mut conn).await).await; | ||||
|     } else { | ||||
|         err!("Send not found. Unable to save the file."); | ||||
|     } | ||||
| @@ -376,8 +376,13 @@ pub struct SendAccessData { | ||||
| } | ||||
|  | ||||
| #[post("/sends/access/<access_id>", data = "<data>")] | ||||
| async fn post_access(access_id: String, data: JsonUpcase<SendAccessData>, conn: DbConn, ip: ClientIp) -> JsonResult { | ||||
|     let mut send = match Send::find_by_access_id(&access_id, &conn).await { | ||||
| async fn post_access( | ||||
|     access_id: String, | ||||
|     data: JsonUpcase<SendAccessData>, | ||||
|     mut conn: DbConn, | ||||
|     ip: ClientIp, | ||||
| ) -> JsonResult { | ||||
|     let mut send = match Send::find_by_access_id(&access_id, &mut conn).await { | ||||
|         Some(s) => s, | ||||
|         None => err_code!(SEND_INACCESSIBLE_MSG, 404), | ||||
|     }; | ||||
| @@ -415,9 +420,9 @@ async fn post_access(access_id: String, data: JsonUpcase<SendAccessData>, conn: | ||||
|         send.access_count += 1; | ||||
|     } | ||||
|  | ||||
|     send.save(&conn).await?; | ||||
|     send.save(&mut conn).await?; | ||||
|  | ||||
|     Ok(Json(send.to_json_access(&conn).await)) | ||||
|     Ok(Json(send.to_json_access(&mut conn).await)) | ||||
| } | ||||
|  | ||||
| #[post("/sends/<send_id>/access/file/<file_id>", data = "<data>")] | ||||
| @@ -426,9 +431,9 @@ async fn post_access_file( | ||||
|     file_id: String, | ||||
|     data: JsonUpcase<SendAccessData>, | ||||
|     host: Host, | ||||
|     conn: DbConn, | ||||
|     mut conn: DbConn, | ||||
| ) -> JsonResult { | ||||
|     let mut send = match Send::find_by_uuid(&send_id, &conn).await { | ||||
|     let mut send = match Send::find_by_uuid(&send_id, &mut conn).await { | ||||
|         Some(s) => s, | ||||
|         None => err_code!(SEND_INACCESSIBLE_MSG, 404), | ||||
|     }; | ||||
| @@ -463,7 +468,7 @@ async fn post_access_file( | ||||
|  | ||||
|     send.access_count += 1; | ||||
|  | ||||
|     send.save(&conn).await?; | ||||
|     send.save(&mut conn).await?; | ||||
|  | ||||
|     let token_claims = crate::auth::generate_send_claims(&send_id, &file_id); | ||||
|     let token = crate::auth::encode_jwt(&token_claims); | ||||
| @@ -489,15 +494,15 @@ async fn put_send( | ||||
|     id: String, | ||||
|     data: JsonUpcase<SendData>, | ||||
|     headers: Headers, | ||||
|     conn: DbConn, | ||||
|     mut conn: DbConn, | ||||
|     nt: Notify<'_>, | ||||
| ) -> JsonResult { | ||||
|     enforce_disable_send_policy(&headers, &conn).await?; | ||||
|     enforce_disable_send_policy(&headers, &mut conn).await?; | ||||
|  | ||||
|     let data: SendData = data.into_inner().data; | ||||
|     enforce_disable_hide_email_policy(&data, &headers, &conn).await?; | ||||
|     enforce_disable_hide_email_policy(&data, &headers, &mut conn).await?; | ||||
|  | ||||
|     let mut send = match Send::find_by_uuid(&id, &conn).await { | ||||
|     let mut send = match Send::find_by_uuid(&id, &mut conn).await { | ||||
|         Some(s) => s, | ||||
|         None => err!("Send not found"), | ||||
|     }; | ||||
| @@ -544,15 +549,15 @@ async fn put_send( | ||||
|         send.set_password(Some(&password)); | ||||
|     } | ||||
|  | ||||
|     send.save(&conn).await?; | ||||
|     nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&conn).await).await; | ||||
|     send.save(&mut conn).await?; | ||||
|     nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&mut conn).await).await; | ||||
|  | ||||
|     Ok(Json(send.to_json())) | ||||
| } | ||||
|  | ||||
| #[delete("/sends/<id>")] | ||||
| async fn delete_send(id: String, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult { | ||||
|     let send = match Send::find_by_uuid(&id, &conn).await { | ||||
| async fn delete_send(id: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { | ||||
|     let send = match Send::find_by_uuid(&id, &mut conn).await { | ||||
|         Some(s) => s, | ||||
|         None => err!("Send not found"), | ||||
|     }; | ||||
| @@ -561,17 +566,17 @@ async fn delete_send(id: String, headers: Headers, conn: DbConn, nt: Notify<'_>) | ||||
|         err!("Send is not owned by user") | ||||
|     } | ||||
|  | ||||
|     send.delete(&conn).await?; | ||||
|     nt.send_send_update(UpdateType::SyncSendDelete, &send, &send.update_users_revision(&conn).await).await; | ||||
|     send.delete(&mut conn).await?; | ||||
|     nt.send_send_update(UpdateType::SyncSendDelete, &send, &send.update_users_revision(&mut conn).await).await; | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| #[put("/sends/<id>/remove-password")] | ||||
| async fn put_remove_password(id: String, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult { | ||||
|     enforce_disable_send_policy(&headers, &conn).await?; | ||||
| async fn put_remove_password(id: String, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> JsonResult { | ||||
|     enforce_disable_send_policy(&headers, &mut conn).await?; | ||||
|  | ||||
|     let mut send = match Send::find_by_uuid(&id, &conn).await { | ||||
|     let mut send = match Send::find_by_uuid(&id, &mut conn).await { | ||||
|         Some(s) => s, | ||||
|         None => err!("Send not found"), | ||||
|     }; | ||||
| @@ -581,8 +586,8 @@ async fn put_remove_password(id: String, headers: Headers, conn: DbConn, nt: Not | ||||
|     } | ||||
|  | ||||
|     send.set_password(None); | ||||
|     send.save(&conn).await?; | ||||
|     nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&conn).await).await; | ||||
|     send.save(&mut conn).await?; | ||||
|     nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&mut conn).await).await; | ||||
|  | ||||
|     Ok(Json(send.to_json())) | ||||
| } | ||||
|   | ||||
| @@ -21,7 +21,7 @@ pub fn routes() -> Vec<Route> { | ||||
| } | ||||
|  | ||||
| #[post("/two-factor/get-authenticator", data = "<data>")] | ||||
| async fn generate_authenticator(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn generate_authenticator(data: JsonUpcase<PasswordData>, headers: Headers, mut conn: DbConn) -> JsonResult { | ||||
|     let data: PasswordData = data.into_inner().data; | ||||
|     let user = headers.user; | ||||
|  | ||||
| @@ -30,7 +30,7 @@ async fn generate_authenticator(data: JsonUpcase<PasswordData>, headers: Headers | ||||
|     } | ||||
|  | ||||
|     let type_ = TwoFactorType::Authenticator as i32; | ||||
|     let twofactor = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await; | ||||
|     let twofactor = TwoFactor::find_by_user_and_type(&user.uuid, type_, &mut conn).await; | ||||
|  | ||||
|     let (enabled, key) = match twofactor { | ||||
|         Some(tf) => (true, tf.data), | ||||
| @@ -57,7 +57,7 @@ async fn activate_authenticator( | ||||
|     data: JsonUpcase<EnableAuthenticatorData>, | ||||
|     headers: Headers, | ||||
|     ip: ClientIp, | ||||
|     conn: DbConn, | ||||
|     mut conn: DbConn, | ||||
| ) -> JsonResult { | ||||
|     let data: EnableAuthenticatorData = data.into_inner().data; | ||||
|     let password_hash = data.MasterPasswordHash; | ||||
| @@ -81,9 +81,9 @@ async 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).await?; | ||||
|     validate_totp_code(&user.uuid, &token, &key.to_uppercase(), &ip, &mut conn).await?; | ||||
|  | ||||
|     _generate_recover_code(&mut user, &conn).await; | ||||
|     _generate_recover_code(&mut user, &mut conn).await; | ||||
|  | ||||
|     Ok(Json(json!({ | ||||
|         "Enabled": true, | ||||
| @@ -107,7 +107,7 @@ pub async fn validate_totp_code_str( | ||||
|     totp_code: &str, | ||||
|     secret: &str, | ||||
|     ip: &ClientIp, | ||||
|     conn: &DbConn, | ||||
|     conn: &mut DbConn, | ||||
| ) -> EmptyResult { | ||||
|     if !totp_code.chars().all(char::is_numeric) { | ||||
|         err!("TOTP code is not a number"); | ||||
| @@ -121,7 +121,7 @@ pub async fn validate_totp_code( | ||||
|     totp_code: &str, | ||||
|     secret: &str, | ||||
|     ip: &ClientIp, | ||||
|     conn: &DbConn, | ||||
|     conn: &mut DbConn, | ||||
| ) -> EmptyResult { | ||||
|     use totp_lite::{totp_custom, Sha1}; | ||||
|  | ||||
|   | ||||
| @@ -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>")] | ||||
| async fn get_duo(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn get_duo(data: JsonUpcase<PasswordData>, headers: Headers, mut 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).await; | ||||
|     let data = get_user_duo_data(&headers.user.uuid, &mut conn).await; | ||||
|  | ||||
|     let (enabled, data) = match data { | ||||
|         DuoStatus::Global(_) => (true, Some(DuoData::secret())), | ||||
| @@ -152,7 +152,7 @@ fn check_duo_fields_custom(data: &EnableDuoData) -> bool { | ||||
| } | ||||
|  | ||||
| #[post("/two-factor/duo", data = "<data>")] | ||||
| async fn activate_duo(data: JsonUpcase<EnableDuoData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn activate_duo(data: JsonUpcase<EnableDuoData>, headers: Headers, mut conn: DbConn) -> JsonResult { | ||||
|     let data: EnableDuoData = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
|  | ||||
| @@ -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).await?; | ||||
|     twofactor.save(&mut conn).await?; | ||||
|  | ||||
|     _generate_recover_code(&mut user, &conn).await; | ||||
|     _generate_recover_code(&mut user, &mut conn).await; | ||||
|  | ||||
|     Ok(Json(json!({ | ||||
|         "Enabled": true, | ||||
| @@ -223,7 +223,7 @@ const AUTH_PREFIX: &str = "AUTH"; | ||||
| const DUO_PREFIX: &str = "TX"; | ||||
| const APP_PREFIX: &str = "APP"; | ||||
|  | ||||
| async fn get_user_duo_data(uuid: &str, conn: &DbConn) -> DuoStatus { | ||||
| async fn get_user_duo_data(uuid: &str, conn: &mut DbConn) -> DuoStatus { | ||||
|     let type_ = TwoFactorType::Duo as i32; | ||||
|  | ||||
|     // If the user doesn't have an entry, disabled | ||||
| @@ -247,7 +247,7 @@ async fn get_user_duo_data(uuid: &str, conn: &DbConn) -> DuoStatus { | ||||
| } | ||||
|  | ||||
| // let (ik, sk, ak, host) = get_duo_keys(); | ||||
| async fn get_duo_keys_email(email: &str, conn: &DbConn) -> ApiResult<(String, String, String, String)> { | ||||
| async fn get_duo_keys_email(email: &str, conn: &mut 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(), | ||||
| @@ -257,7 +257,7 @@ async fn get_duo_keys_email(email: &str, conn: &DbConn) -> ApiResult<(String, St | ||||
|     Ok((data.ik, data.sk, CONFIG.get_duo_akey(), data.host)) | ||||
| } | ||||
|  | ||||
| pub async fn generate_duo_signature(email: &str, conn: &DbConn) -> ApiResult<(String, String)> { | ||||
| pub async fn generate_duo_signature(email: &str, conn: &mut DbConn) -> ApiResult<(String, String)> { | ||||
|     let now = Utc::now().timestamp(); | ||||
|  | ||||
|     let (ik, sk, ak, host) = get_duo_keys_email(email, conn).await?; | ||||
| @@ -275,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 async fn validate_duo_login(email: &str, response: &str, conn: &DbConn) -> EmptyResult { | ||||
| pub async fn validate_duo_login(email: &str, response: &str, conn: &mut 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(); | ||||
|   | ||||
| @@ -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 | ||||
| async fn send_email_login(data: JsonUpcase<SendEmailLoginData>, conn: DbConn) -> EmptyResult { | ||||
| async fn send_email_login(data: JsonUpcase<SendEmailLoginData>, mut 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).await { | ||||
|     let user = match User::find_by_mail(&data.Email, &mut conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Username or password is incorrect. Try again."), | ||||
|     }; | ||||
| @@ -48,13 +48,13 @@ async fn send_email_login(data: JsonUpcase<SendEmailLoginData>, conn: DbConn) -> | ||||
|         err!("Email 2FA is disabled") | ||||
|     } | ||||
|  | ||||
|     send_token(&user.uuid, &conn).await?; | ||||
|     send_token(&user.uuid, &mut conn).await?; | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| /// Generate the token, save the data for later verification and send email to user | ||||
| pub async fn send_token(user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
| pub async fn send_token(user_uuid: &str, conn: &mut DbConn) -> EmptyResult { | ||||
|     let type_ = TwoFactorType::Email as i32; | ||||
|     let mut twofactor = | ||||
|         TwoFactor::find_by_user_and_type(user_uuid, type_, conn).await.map_res("Two factor not found")?; | ||||
| @@ -73,7 +73,7 @@ pub async 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>")] | ||||
| async fn get_email(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn get_email(data: JsonUpcase<PasswordData>, headers: Headers, mut conn: DbConn) -> JsonResult { | ||||
|     let data: PasswordData = data.into_inner().data; | ||||
|     let user = headers.user; | ||||
|  | ||||
| @@ -82,7 +82,7 @@ async fn get_email(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbCon | ||||
|     } | ||||
|  | ||||
|     let (enabled, mfa_email) = | ||||
|         match TwoFactor::find_by_user_and_type(&user.uuid, TwoFactorType::Email as i32, &conn).await { | ||||
|         match TwoFactor::find_by_user_and_type(&user.uuid, TwoFactorType::Email as i32, &mut conn).await { | ||||
|             Some(x) => { | ||||
|                 let twofactor_data = EmailTokenData::from_json(&x.data)?; | ||||
|                 (true, json!(twofactor_data.email)) | ||||
| @@ -107,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>")] | ||||
| async fn send_email(data: JsonUpcase<SendEmailData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn send_email(data: JsonUpcase<SendEmailData>, headers: Headers, mut conn: DbConn) -> EmptyResult { | ||||
|     let data: SendEmailData = data.into_inner().data; | ||||
|     let user = headers.user; | ||||
|  | ||||
| @@ -121,8 +121,8 @@ async fn send_email(data: JsonUpcase<SendEmailData>, headers: Headers, conn: DbC | ||||
|  | ||||
|     let type_ = TwoFactorType::Email as i32; | ||||
|  | ||||
|     if let Some(tf) = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await { | ||||
|         tf.delete(&conn).await?; | ||||
|     if let Some(tf) = TwoFactor::find_by_user_and_type(&user.uuid, type_, &mut conn).await { | ||||
|         tf.delete(&mut conn).await?; | ||||
|     } | ||||
|  | ||||
|     let generated_token = crypto::generate_email_token(CONFIG.email_token_size()); | ||||
| @@ -130,7 +130,7 @@ async fn send_email(data: JsonUpcase<SendEmailData>, headers: Headers, conn: DbC | ||||
|  | ||||
|     // 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).await?; | ||||
|     twofactor.save(&mut conn).await?; | ||||
|  | ||||
|     mail::send_token(&twofactor_data.email, &twofactor_data.last_token.map_res("Token is empty")?).await?; | ||||
|  | ||||
| @@ -147,7 +147,7 @@ struct EmailData { | ||||
|  | ||||
| /// Verify email belongs to user and can be used for 2FA email codes. | ||||
| #[put("/two-factor/email", data = "<data>")] | ||||
| async fn email(data: JsonUpcase<EmailData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn email(data: JsonUpcase<EmailData>, headers: Headers, mut conn: DbConn) -> JsonResult { | ||||
|     let data: EmailData = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
|  | ||||
| @@ -157,7 +157,7 @@ async fn email(data: JsonUpcase<EmailData>, headers: Headers, conn: DbConn) -> J | ||||
|  | ||||
|     let type_ = TwoFactorType::EmailVerificationChallenge as i32; | ||||
|     let mut twofactor = | ||||
|         TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await.map_res("Two factor not found")?; | ||||
|         TwoFactor::find_by_user_and_type(&user.uuid, type_, &mut conn).await.map_res("Two factor not found")?; | ||||
|  | ||||
|     let mut email_data = EmailTokenData::from_json(&twofactor.data)?; | ||||
|  | ||||
| @@ -173,9 +173,9 @@ async fn email(data: JsonUpcase<EmailData>, headers: Headers, conn: DbConn) -> J | ||||
|     email_data.reset_token(); | ||||
|     twofactor.atype = TwoFactorType::Email as i32; | ||||
|     twofactor.data = email_data.to_json(); | ||||
|     twofactor.save(&conn).await?; | ||||
|     twofactor.save(&mut conn).await?; | ||||
|  | ||||
|     _generate_recover_code(&mut user, &conn).await; | ||||
|     _generate_recover_code(&mut user, &mut conn).await; | ||||
|  | ||||
|     Ok(Json(json!({ | ||||
|         "Email": email_data.email, | ||||
| @@ -185,7 +185,7 @@ async fn email(data: JsonUpcase<EmailData>, headers: Headers, conn: DbConn) -> J | ||||
| } | ||||
|  | ||||
| /// Validate the email code when used as TwoFactor token mechanism | ||||
| pub async 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: &mut 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 | ||||
|   | ||||
| @@ -38,8 +38,8 @@ pub fn routes() -> Vec<Route> { | ||||
| } | ||||
|  | ||||
| #[get("/two-factor")] | ||||
| async fn get_twofactor(headers: Headers, conn: DbConn) -> Json<Value> { | ||||
|     let twofactors = TwoFactor::find_by_user(&headers.user.uuid, &conn).await; | ||||
| async fn get_twofactor(headers: Headers, mut conn: DbConn) -> Json<Value> { | ||||
|     let twofactors = TwoFactor::find_by_user(&headers.user.uuid, &mut conn).await; | ||||
|     let twofactors_json: Vec<Value> = twofactors.iter().map(TwoFactor::to_json_provider).collect(); | ||||
|  | ||||
|     Json(json!({ | ||||
| @@ -73,13 +73,13 @@ struct RecoverTwoFactor { | ||||
| } | ||||
|  | ||||
| #[post("/two-factor/recover", data = "<data>")] | ||||
| async fn recover(data: JsonUpcase<RecoverTwoFactor>, conn: DbConn) -> JsonResult { | ||||
| async fn recover(data: JsonUpcase<RecoverTwoFactor>, mut 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).await { | ||||
|     let mut user = match User::find_by_mail(&data.Email, &mut conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Username or password is incorrect. Try again."), | ||||
|     }; | ||||
| @@ -95,15 +95,15 @@ async fn recover(data: JsonUpcase<RecoverTwoFactor>, conn: DbConn) -> JsonResult | ||||
|     } | ||||
|  | ||||
|     // Remove all twofactors from the user | ||||
|     TwoFactor::delete_all_by_user(&user.uuid, &conn).await?; | ||||
|     TwoFactor::delete_all_by_user(&user.uuid, &mut conn).await?; | ||||
|  | ||||
|     // Remove the recovery code, not needed without twofactors | ||||
|     user.totp_recover = None; | ||||
|     user.save(&conn).await?; | ||||
|     user.save(&mut conn).await?; | ||||
|     Ok(Json(json!({}))) | ||||
| } | ||||
|  | ||||
| async fn _generate_recover_code(user: &mut User, conn: &DbConn) { | ||||
| async fn _generate_recover_code(user: &mut User, conn: &mut DbConn) { | ||||
|     if user.totp_recover.is_none() { | ||||
|         let totp_recover = BASE32.encode(&crypto::get_random(vec![0u8; 20])); | ||||
|         user.totp_recover = Some(totp_recover); | ||||
| @@ -119,7 +119,7 @@ struct DisableTwoFactorData { | ||||
| } | ||||
|  | ||||
| #[post("/two-factor/disable", data = "<data>")] | ||||
| async fn disable_twofactor(data: JsonUpcase<DisableTwoFactorData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn disable_twofactor(data: JsonUpcase<DisableTwoFactorData>, headers: Headers, mut conn: DbConn) -> JsonResult { | ||||
|     let data: DisableTwoFactorData = data.into_inner().data; | ||||
|     let password_hash = data.MasterPasswordHash; | ||||
|     let user = headers.user; | ||||
| @@ -130,24 +130,24 @@ async fn disable_twofactor(data: JsonUpcase<DisableTwoFactorData>, headers: Head | ||||
|  | ||||
|     let type_ = data.Type.into_i32()?; | ||||
|  | ||||
|     if let Some(twofactor) = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await { | ||||
|         twofactor.delete(&conn).await?; | ||||
|     if let Some(twofactor) = TwoFactor::find_by_user_and_type(&user.uuid, type_, &mut conn).await { | ||||
|         twofactor.delete(&mut conn).await?; | ||||
|     } | ||||
|  | ||||
|     let twofactor_disabled = TwoFactor::find_by_user(&user.uuid, &conn).await.is_empty(); | ||||
|     let twofactor_disabled = TwoFactor::find_by_user(&user.uuid, &mut conn).await.is_empty(); | ||||
|  | ||||
|     if twofactor_disabled { | ||||
|         for user_org in | ||||
|             UserOrganization::find_by_user_and_policy(&user.uuid, OrgPolicyType::TwoFactorAuthentication, &conn) | ||||
|             UserOrganization::find_by_user_and_policy(&user.uuid, OrgPolicyType::TwoFactorAuthentication, &mut 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).await.unwrap(); | ||||
|                     let org = Organization::find_by_uuid(&user_org.org_uuid, &mut conn).await.unwrap(); | ||||
|                     mail::send_2fa_removed_from_org(&user.email, &org.name).await?; | ||||
|                 } | ||||
|                 user_org.delete(&conn).await?; | ||||
|                 user_org.delete(&mut conn).await?; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| @@ -171,7 +171,7 @@ pub async fn send_incomplete_2fa_notifications(pool: DbPool) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     let conn = match pool.get().await { | ||||
|     let mut conn = match pool.get().await { | ||||
|         Ok(conn) => conn, | ||||
|         _ => { | ||||
|             error!("Failed to get DB connection in send_incomplete_2fa_notifications()"); | ||||
| @@ -182,9 +182,9 @@ 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 time_before = now - time_limit; | ||||
|     let incomplete_logins = TwoFactorIncomplete::find_logins_before(&time_before, &conn).await; | ||||
|     let incomplete_logins = TwoFactorIncomplete::find_logins_before(&time_before, &mut conn).await; | ||||
|     for login in incomplete_logins { | ||||
|         let user = User::find_by_uuid(&login.user_uuid, &conn).await.expect("User not found"); | ||||
|         let user = User::find_by_uuid(&login.user_uuid, &mut 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 | ||||
| @@ -192,7 +192,7 @@ pub async fn send_incomplete_2fa_notifications(pool: DbPool) { | ||||
|         mail::send_incomplete_2fa_login(&user.email, &login.ip_address, &login.login_time, &login.device_name) | ||||
|             .await | ||||
|             .expect("Error sending incomplete 2FA email"); | ||||
|         login.delete(&conn).await.expect("Error deleting incomplete 2FA record"); | ||||
|         login.delete(&mut conn).await.expect("Error deleting incomplete 2FA record"); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -102,7 +102,7 @@ impl WebauthnRegistration { | ||||
| } | ||||
|  | ||||
| #[post("/two-factor/get-webauthn", data = "<data>")] | ||||
| async fn get_webauthn(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn get_webauthn(data: JsonUpcase<PasswordData>, headers: Headers, mut conn: DbConn) -> JsonResult { | ||||
|     if !CONFIG.domain_set() { | ||||
|         err!("`DOMAIN` environment variable is not set. Webauthn disabled") | ||||
|     } | ||||
| @@ -111,7 +111,7 @@ async fn get_webauthn(data: JsonUpcase<PasswordData>, headers: Headers, conn: Db | ||||
|         err!("Invalid password"); | ||||
|     } | ||||
|  | ||||
|     let (enabled, registrations) = get_webauthn_registrations(&headers.user.uuid, &conn).await?; | ||||
|     let (enabled, registrations) = get_webauthn_registrations(&headers.user.uuid, &mut conn).await?; | ||||
|     let registrations_json: Vec<Value> = registrations.iter().map(WebauthnRegistration::to_json).collect(); | ||||
|  | ||||
|     Ok(Json(json!({ | ||||
| @@ -122,12 +122,12 @@ async fn get_webauthn(data: JsonUpcase<PasswordData>, headers: Headers, conn: Db | ||||
| } | ||||
|  | ||||
| #[post("/two-factor/get-webauthn-challenge", data = "<data>")] | ||||
| async fn generate_webauthn_challenge(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn generate_webauthn_challenge(data: JsonUpcase<PasswordData>, headers: Headers, mut 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, &mut conn) | ||||
|         .await? | ||||
|         .1 | ||||
|         .into_iter() | ||||
| @@ -144,7 +144,7 @@ async fn generate_webauthn_challenge(data: JsonUpcase<PasswordData>, headers: He | ||||
|     )?; | ||||
|  | ||||
|     let type_ = TwoFactorType::WebauthnRegisterChallenge; | ||||
|     TwoFactor::new(headers.user.uuid, type_, serde_json::to_string(&state)?).save(&conn).await?; | ||||
|     TwoFactor::new(headers.user.uuid, type_, serde_json::to_string(&state)?).save(&mut conn).await?; | ||||
|  | ||||
|     let mut challenge_value = serde_json::to_value(challenge.public_key)?; | ||||
|     challenge_value["status"] = "ok".into(); | ||||
| @@ -241,7 +241,7 @@ impl From<PublicKeyCredentialCopy> for PublicKeyCredential { | ||||
| } | ||||
|  | ||||
| #[post("/two-factor/webauthn", data = "<data>")] | ||||
| async fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Headers, mut conn: DbConn) -> JsonResult { | ||||
|     let data: EnableWebauthnData = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
|  | ||||
| @@ -251,10 +251,10 @@ async fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Header | ||||
|  | ||||
|     // 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).await { | ||||
|     let state = match TwoFactor::find_by_user_and_type(&user.uuid, type_, &mut conn).await { | ||||
|         Some(tf) => { | ||||
|             let state: RegistrationState = serde_json::from_str(&tf.data)?; | ||||
|             tf.delete(&conn).await?; | ||||
|             tf.delete(&mut conn).await?; | ||||
|             state | ||||
|         } | ||||
|         None => err!("Can't recover challenge"), | ||||
| @@ -264,7 +264,7 @@ async fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Header | ||||
|     let (credential, _data) = | ||||
|         WebauthnConfig::load().register_credential(&data.DeviceResponse.into(), &state, |_| Ok(false))?; | ||||
|  | ||||
|     let mut registrations: Vec<_> = get_webauthn_registrations(&user.uuid, &conn).await?.1; | ||||
|     let mut registrations: Vec<_> = get_webauthn_registrations(&user.uuid, &mut conn).await?.1; | ||||
|     // TODO: Check for repeated ID's | ||||
|     registrations.push(WebauthnRegistration { | ||||
|         id: data.Id.into_i32()?, | ||||
| @@ -276,9 +276,9 @@ async fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Header | ||||
|  | ||||
|     // Save the registrations and return them | ||||
|     TwoFactor::new(user.uuid.clone(), TwoFactorType::Webauthn, serde_json::to_string(®istrations)?) | ||||
|         .save(&conn) | ||||
|         .save(&mut conn) | ||||
|         .await?; | ||||
|     _generate_recover_code(&mut user, &conn).await; | ||||
|     _generate_recover_code(&mut user, &mut conn).await; | ||||
|  | ||||
|     let keys_json: Vec<Value> = registrations.iter().map(WebauthnRegistration::to_json).collect(); | ||||
|     Ok(Json(json!({ | ||||
| @@ -301,17 +301,17 @@ struct DeleteU2FData { | ||||
| } | ||||
|  | ||||
| #[delete("/two-factor/webauthn", data = "<data>")] | ||||
| async fn delete_webauthn(data: JsonUpcase<DeleteU2FData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn delete_webauthn(data: JsonUpcase<DeleteU2FData>, headers: Headers, mut 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).await | ||||
|     { | ||||
|         Some(tf) => tf, | ||||
|         None => err!("Webauthn data not found!"), | ||||
|     }; | ||||
|     let mut tf = | ||||
|         match TwoFactor::find_by_user_and_type(&headers.user.uuid, TwoFactorType::Webauthn as i32, &mut conn).await { | ||||
|             Some(tf) => tf, | ||||
|             None => err!("Webauthn data not found!"), | ||||
|         }; | ||||
|  | ||||
|     let mut data: Vec<WebauthnRegistration> = serde_json::from_str(&tf.data)?; | ||||
|  | ||||
| @@ -322,11 +322,12 @@ async fn delete_webauthn(data: JsonUpcase<DeleteU2FData>, headers: Headers, conn | ||||
|  | ||||
|     let removed_item = data.remove(item_pos); | ||||
|     tf.data = serde_json::to_string(&data)?; | ||||
|     tf.save(&conn).await?; | ||||
|     tf.save(&mut 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).await | ||||
|     if let Some(mut u2f) = | ||||
|         TwoFactor::find_by_user_and_type(&headers.user.uuid, TwoFactorType::U2f as i32, &mut conn).await | ||||
|     { | ||||
|         let mut data: Vec<U2FRegistration> = match serde_json::from_str(&u2f.data) { | ||||
|             Ok(d) => d, | ||||
| @@ -337,7 +338,7 @@ async fn delete_webauthn(data: JsonUpcase<DeleteU2FData>, headers: Headers, conn | ||||
|         let new_data_str = serde_json::to_string(&data)?; | ||||
|  | ||||
|         u2f.data = new_data_str; | ||||
|         u2f.save(&conn).await?; | ||||
|         u2f.save(&mut conn).await?; | ||||
|     } | ||||
|  | ||||
|     let keys_json: Vec<Value> = data.iter().map(WebauthnRegistration::to_json).collect(); | ||||
| @@ -351,7 +352,7 @@ async fn delete_webauthn(data: JsonUpcase<DeleteU2FData>, headers: Headers, conn | ||||
|  | ||||
| pub async fn get_webauthn_registrations( | ||||
|     user_uuid: &str, | ||||
|     conn: &DbConn, | ||||
|     conn: &mut DbConn, | ||||
| ) -> Result<(bool, Vec<WebauthnRegistration>), Error> { | ||||
|     let type_ = TwoFactorType::Webauthn as i32; | ||||
|     match TwoFactor::find_by_user_and_type(user_uuid, type_, conn).await { | ||||
| @@ -360,7 +361,7 @@ pub async fn get_webauthn_registrations( | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub async fn generate_webauthn_login(user_uuid: &str, conn: &DbConn) -> JsonResult { | ||||
| pub async fn generate_webauthn_login(user_uuid: &str, conn: &mut DbConn) -> JsonResult { | ||||
|     // Load saved credentials | ||||
|     let creds: Vec<Credential> = | ||||
|         get_webauthn_registrations(user_uuid, conn).await?.1.into_iter().map(|r| r.credential).collect(); | ||||
| @@ -382,7 +383,7 @@ pub async fn generate_webauthn_login(user_uuid: &str, conn: &DbConn) -> JsonResu | ||||
|     Ok(Json(serde_json::to_value(response.public_key)?)) | ||||
| } | ||||
|  | ||||
| pub async fn validate_webauthn_login(user_uuid: &str, response: &str, conn: &DbConn) -> EmptyResult { | ||||
| pub async fn validate_webauthn_login(user_uuid: &str, response: &str, conn: &mut DbConn) -> EmptyResult { | ||||
|     let type_ = TwoFactorType::WebauthnLoginChallenge as i32; | ||||
|     let state = match TwoFactor::find_by_user_and_type(user_uuid, type_, conn).await { | ||||
|         Some(tf) => { | ||||
|   | ||||
| @@ -78,7 +78,7 @@ fn verify_yubikey_otp(otp: String) -> EmptyResult { | ||||
| } | ||||
|  | ||||
| #[post("/two-factor/get-yubikey", data = "<data>")] | ||||
| async fn generate_yubikey(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn generate_yubikey(data: JsonUpcase<PasswordData>, headers: Headers, mut conn: DbConn) -> JsonResult { | ||||
|     // Make sure the credentials are set | ||||
|     get_yubico_credentials()?; | ||||
|  | ||||
| @@ -92,7 +92,7 @@ async fn generate_yubikey(data: JsonUpcase<PasswordData>, headers: Headers, conn | ||||
|     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).await; | ||||
|     let r = TwoFactor::find_by_user_and_type(user_uuid, yubikey_type, &mut conn).await; | ||||
|  | ||||
|     if let Some(r) = r { | ||||
|         let yubikey_metadata: YubikeyMetadata = serde_json::from_str(&r.data)?; | ||||
| @@ -113,7 +113,7 @@ async fn generate_yubikey(data: JsonUpcase<PasswordData>, headers: Headers, conn | ||||
| } | ||||
|  | ||||
| #[post("/two-factor/yubikey", data = "<data>")] | ||||
| async fn activate_yubikey(data: JsonUpcase<EnableYubikeyData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn activate_yubikey(data: JsonUpcase<EnableYubikeyData>, headers: Headers, mut conn: DbConn) -> JsonResult { | ||||
|     let data: EnableYubikeyData = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
|  | ||||
| @@ -123,7 +123,7 @@ async fn activate_yubikey(data: JsonUpcase<EnableYubikeyData>, headers: Headers, | ||||
|  | ||||
|     // 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).await { | ||||
|         match TwoFactor::find_by_user_and_type(&user.uuid, TwoFactorType::YubiKey as i32, &mut conn).await { | ||||
|             Some(data) => data, | ||||
|             None => TwoFactor::new(user.uuid.clone(), TwoFactorType::YubiKey, String::new()), | ||||
|         }; | ||||
| @@ -155,9 +155,9 @@ async fn activate_yubikey(data: JsonUpcase<EnableYubikeyData>, headers: Headers, | ||||
|     }; | ||||
|  | ||||
|     yubikey_data.data = serde_json::to_string(&yubikey_metadata).unwrap(); | ||||
|     yubikey_data.save(&conn).await?; | ||||
|     yubikey_data.save(&mut conn).await?; | ||||
|  | ||||
|     _generate_recover_code(&mut user, &conn).await; | ||||
|     _generate_recover_code(&mut user, &mut conn).await; | ||||
|  | ||||
|     let mut result = jsonify_yubikeys(yubikey_metadata.Keys); | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user