mirror of
				https://github.com/dani-garcia/vaultwarden.git
				synced 2025-10-31 02:08:20 +02:00 
			
		
		
		
	Async/Awaited all db methods
This is a rather large PR which updates the async branch to have all the database methods as an async fn. Some iter/map logic needed to be changed to a stream::iter().then(), but besides that most changes were just adding async/await where needed.
This commit is contained in:
		| @@ -63,11 +63,11 @@ struct KeysData { | ||||
| } | ||||
|  | ||||
| #[post("/accounts/register", data = "<data>")] | ||||
| fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult { | ||||
| async fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult { | ||||
|     let data: RegisterData = data.into_inner().data; | ||||
|     let email = data.Email.to_lowercase(); | ||||
|  | ||||
|     let mut user = match User::find_by_mail(&email, &conn) { | ||||
|     let mut user = match User::find_by_mail(&email, &conn).await { | ||||
|         Some(user) => { | ||||
|             if !user.password_hash.is_empty() { | ||||
|                 if CONFIG.is_signup_allowed(&email) { | ||||
| @@ -84,13 +84,13 @@ fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult { | ||||
|                 } else { | ||||
|                     err!("Registration email does not match invite email") | ||||
|                 } | ||||
|             } else if Invitation::take(&email, &conn) { | ||||
|                 for mut user_org in UserOrganization::find_invited_by_user(&user.uuid, &conn).iter_mut() { | ||||
|             } else if Invitation::take(&email, &conn).await { | ||||
|                 for mut user_org in UserOrganization::find_invited_by_user(&user.uuid, &conn).await.iter_mut() { | ||||
|                     user_org.status = UserOrgStatus::Accepted as i32; | ||||
|                     user_org.save(&conn)?; | ||||
|                     user_org.save(&conn).await?; | ||||
|                 } | ||||
|                 user | ||||
|             } else if EmergencyAccess::find_invited_by_grantee_email(&email, &conn).is_some() { | ||||
|             } else if EmergencyAccess::find_invited_by_grantee_email(&email, &conn).await.is_some() { | ||||
|                 user | ||||
|             } else if CONFIG.is_signup_allowed(&email) { | ||||
|                 err!("Account with this email already exists") | ||||
| @@ -102,7 +102,7 @@ fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult { | ||||
|             // Order is important here; the invitation check must come first | ||||
|             // because the vaultwarden admin can invite anyone, regardless | ||||
|             // of other signup restrictions. | ||||
|             if Invitation::take(&email, &conn) || CONFIG.is_signup_allowed(&email) { | ||||
|             if Invitation::take(&email, &conn).await || CONFIG.is_signup_allowed(&email) { | ||||
|                 User::new(email.clone()) | ||||
|             } else { | ||||
|                 err!("Registration not allowed or user already exists") | ||||
| @@ -111,7 +111,7 @@ fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult { | ||||
|     }; | ||||
|  | ||||
|     // Make sure we don't leave a lingering invitation. | ||||
|     Invitation::take(&email, &conn); | ||||
|     Invitation::take(&email, &conn).await; | ||||
|  | ||||
|     if let Some(client_kdf_iter) = data.KdfIterations { | ||||
|         user.client_kdf_iter = client_kdf_iter; | ||||
| @@ -150,12 +150,12 @@ fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     user.save(&conn) | ||||
|     user.save(&conn).await | ||||
| } | ||||
|  | ||||
| #[get("/accounts/profile")] | ||||
| fn profile(headers: Headers, conn: DbConn) -> Json<Value> { | ||||
|     Json(headers.user.to_json(&conn)) | ||||
| async fn profile(headers: Headers, conn: DbConn) -> Json<Value> { | ||||
|     Json(headers.user.to_json(&conn).await) | ||||
| } | ||||
|  | ||||
| #[derive(Deserialize, Debug)] | ||||
| @@ -168,12 +168,12 @@ struct ProfileData { | ||||
| } | ||||
|  | ||||
| #[put("/accounts/profile", data = "<data>")] | ||||
| fn put_profile(data: JsonUpcase<ProfileData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     post_profile(data, headers, conn) | ||||
| async fn put_profile(data: JsonUpcase<ProfileData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     post_profile(data, headers, conn).await | ||||
| } | ||||
|  | ||||
| #[post("/accounts/profile", data = "<data>")] | ||||
| fn post_profile(data: JsonUpcase<ProfileData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn post_profile(data: JsonUpcase<ProfileData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let data: ProfileData = data.into_inner().data; | ||||
|  | ||||
|     let mut user = headers.user; | ||||
| @@ -183,13 +183,13 @@ fn post_profile(data: JsonUpcase<ProfileData>, headers: Headers, conn: DbConn) - | ||||
|         Some(ref h) if h.is_empty() => None, | ||||
|         _ => data.MasterPasswordHint, | ||||
|     }; | ||||
|     user.save(&conn)?; | ||||
|     Ok(Json(user.to_json(&conn))) | ||||
|     user.save(&conn).await?; | ||||
|     Ok(Json(user.to_json(&conn).await)) | ||||
| } | ||||
|  | ||||
| #[get("/users/<uuid>/public-key")] | ||||
| fn get_public_keys(uuid: String, _headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let user = match User::find_by_uuid(&uuid, &conn) { | ||||
| async fn get_public_keys(uuid: String, _headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let user = match User::find_by_uuid(&uuid, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("User doesn't exist"), | ||||
|     }; | ||||
| @@ -202,7 +202,7 @@ fn get_public_keys(uuid: String, _headers: Headers, conn: DbConn) -> JsonResult | ||||
| } | ||||
|  | ||||
| #[post("/accounts/keys", data = "<data>")] | ||||
| fn post_keys(data: JsonUpcase<KeysData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn post_keys(data: JsonUpcase<KeysData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let data: KeysData = data.into_inner().data; | ||||
|  | ||||
|     let mut user = headers.user; | ||||
| @@ -210,7 +210,7 @@ fn post_keys(data: JsonUpcase<KeysData>, headers: Headers, conn: DbConn) -> Json | ||||
|     user.private_key = Some(data.EncryptedPrivateKey); | ||||
|     user.public_key = Some(data.PublicKey); | ||||
|  | ||||
|     user.save(&conn)?; | ||||
|     user.save(&conn).await?; | ||||
|  | ||||
|     Ok(Json(json!({ | ||||
|         "PrivateKey": user.private_key, | ||||
| @@ -228,7 +228,7 @@ struct ChangePassData { | ||||
| } | ||||
|  | ||||
| #[post("/accounts/password", data = "<data>")] | ||||
| fn post_password(data: JsonUpcase<ChangePassData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn post_password(data: JsonUpcase<ChangePassData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
|     let data: ChangePassData = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
|  | ||||
| @@ -241,7 +241,7 @@ fn post_password(data: JsonUpcase<ChangePassData>, headers: Headers, conn: DbCon | ||||
|         Some(vec![String::from("post_rotatekey"), String::from("get_contacts"), String::from("get_public_keys")]), | ||||
|     ); | ||||
|     user.akey = data.Key; | ||||
|     user.save(&conn) | ||||
|     user.save(&conn).await | ||||
| } | ||||
|  | ||||
| #[derive(Deserialize)] | ||||
| @@ -256,7 +256,7 @@ struct ChangeKdfData { | ||||
| } | ||||
|  | ||||
| #[post("/accounts/kdf", data = "<data>")] | ||||
| fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
|     let data: ChangeKdfData = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
|  | ||||
| @@ -268,7 +268,7 @@ fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, conn: DbConn) -> | ||||
|     user.client_kdf_type = data.Kdf; | ||||
|     user.set_password(&data.NewMasterPasswordHash, None); | ||||
|     user.akey = data.Key; | ||||
|     user.save(&conn) | ||||
|     user.save(&conn).await | ||||
| } | ||||
|  | ||||
| #[derive(Deserialize)] | ||||
| @@ -291,7 +291,7 @@ struct KeyData { | ||||
| } | ||||
|  | ||||
| #[post("/accounts/key", data = "<data>")] | ||||
| fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbConn, nt: Notify) -> EmptyResult { | ||||
| async fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult { | ||||
|     let data: KeyData = data.into_inner().data; | ||||
|  | ||||
|     if !headers.user.check_valid_password(&data.MasterPasswordHash) { | ||||
| @@ -302,7 +302,7 @@ fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbConn, nt: | ||||
|  | ||||
|     // Update folder data | ||||
|     for folder_data in data.Folders { | ||||
|         let mut saved_folder = match Folder::find_by_uuid(&folder_data.Id, &conn) { | ||||
|         let mut saved_folder = match Folder::find_by_uuid(&folder_data.Id, &conn).await { | ||||
|             Some(folder) => folder, | ||||
|             None => err!("Folder doesn't exist"), | ||||
|         }; | ||||
| @@ -312,14 +312,14 @@ fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbConn, nt: | ||||
|         } | ||||
|  | ||||
|         saved_folder.name = folder_data.Name; | ||||
|         saved_folder.save(&conn)? | ||||
|         saved_folder.save(&conn).await? | ||||
|     } | ||||
|  | ||||
|     // Update cipher data | ||||
|     use super::ciphers::update_cipher_from_data; | ||||
|  | ||||
|     for cipher_data in data.Ciphers { | ||||
|         let mut saved_cipher = match Cipher::find_by_uuid(cipher_data.Id.as_ref().unwrap(), &conn) { | ||||
|         let mut saved_cipher = match Cipher::find_by_uuid(cipher_data.Id.as_ref().unwrap(), &conn).await { | ||||
|             Some(cipher) => cipher, | ||||
|             None => err!("Cipher doesn't exist"), | ||||
|         }; | ||||
| @@ -330,7 +330,7 @@ fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbConn, nt: | ||||
|  | ||||
|         // Prevent triggering cipher updates via WebSockets by settings UpdateType::None | ||||
|         // The user sessions are invalidated because all the ciphers were re-encrypted and thus triggering an update could cause issues. | ||||
|         update_cipher_from_data(&mut saved_cipher, cipher_data, &headers, false, &conn, &nt, UpdateType::None)? | ||||
|         update_cipher_from_data(&mut saved_cipher, cipher_data, &headers, false, &conn, &nt, UpdateType::None).await? | ||||
|     } | ||||
|  | ||||
|     // Update user data | ||||
| @@ -340,11 +340,11 @@ fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbConn, nt: | ||||
|     user.private_key = Some(data.PrivateKey); | ||||
|     user.reset_security_stamp(); | ||||
|  | ||||
|     user.save(&conn) | ||||
|     user.save(&conn).await | ||||
| } | ||||
|  | ||||
| #[post("/accounts/security-stamp", data = "<data>")] | ||||
| fn post_sstamp(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn post_sstamp(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
|     let data: PasswordData = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
|  | ||||
| @@ -352,9 +352,9 @@ fn post_sstamp(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) - | ||||
|         err!("Invalid password") | ||||
|     } | ||||
|  | ||||
|     Device::delete_all_by_user(&user.uuid, &conn)?; | ||||
|     Device::delete_all_by_user(&user.uuid, &conn).await?; | ||||
|     user.reset_security_stamp(); | ||||
|     user.save(&conn) | ||||
|     user.save(&conn).await | ||||
| } | ||||
|  | ||||
| #[derive(Deserialize)] | ||||
| @@ -365,7 +365,7 @@ struct EmailTokenData { | ||||
| } | ||||
|  | ||||
| #[post("/accounts/email-token", data = "<data>")] | ||||
| fn post_email_token(data: JsonUpcase<EmailTokenData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn post_email_token(data: JsonUpcase<EmailTokenData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
|     let data: EmailTokenData = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
|  | ||||
| @@ -373,7 +373,7 @@ fn post_email_token(data: JsonUpcase<EmailTokenData>, headers: Headers, conn: Db | ||||
|         err!("Invalid password") | ||||
|     } | ||||
|  | ||||
|     if User::find_by_mail(&data.NewEmail, &conn).is_some() { | ||||
|     if User::find_by_mail(&data.NewEmail, &conn).await.is_some() { | ||||
|         err!("Email already in use"); | ||||
|     } | ||||
|  | ||||
| @@ -391,7 +391,7 @@ fn post_email_token(data: JsonUpcase<EmailTokenData>, headers: Headers, conn: Db | ||||
|  | ||||
|     user.email_new = Some(data.NewEmail); | ||||
|     user.email_new_token = Some(token); | ||||
|     user.save(&conn) | ||||
|     user.save(&conn).await | ||||
| } | ||||
|  | ||||
| #[derive(Deserialize)] | ||||
| @@ -406,7 +406,7 @@ struct ChangeEmailData { | ||||
| } | ||||
|  | ||||
| #[post("/accounts/email", data = "<data>")] | ||||
| fn post_email(data: JsonUpcase<ChangeEmailData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn post_email(data: JsonUpcase<ChangeEmailData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
|     let data: ChangeEmailData = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
|  | ||||
| @@ -414,7 +414,7 @@ fn post_email(data: JsonUpcase<ChangeEmailData>, headers: Headers, conn: DbConn) | ||||
|         err!("Invalid password") | ||||
|     } | ||||
|  | ||||
|     if User::find_by_mail(&data.NewEmail, &conn).is_some() { | ||||
|     if User::find_by_mail(&data.NewEmail, &conn).await.is_some() { | ||||
|         err!("Email already in use"); | ||||
|     } | ||||
|  | ||||
| @@ -449,7 +449,7 @@ fn post_email(data: JsonUpcase<ChangeEmailData>, headers: Headers, conn: DbConn) | ||||
|     user.set_password(&data.NewMasterPasswordHash, None); | ||||
|     user.akey = data.Key; | ||||
|  | ||||
|     user.save(&conn) | ||||
|     user.save(&conn).await | ||||
| } | ||||
|  | ||||
| #[post("/accounts/verify-email")] | ||||
| @@ -475,10 +475,10 @@ struct VerifyEmailTokenData { | ||||
| } | ||||
|  | ||||
| #[post("/accounts/verify-email-token", data = "<data>")] | ||||
| fn post_verify_email_token(data: JsonUpcase<VerifyEmailTokenData>, conn: DbConn) -> EmptyResult { | ||||
| async fn post_verify_email_token(data: JsonUpcase<VerifyEmailTokenData>, conn: DbConn) -> EmptyResult { | ||||
|     let data: VerifyEmailTokenData = data.into_inner().data; | ||||
|  | ||||
|     let mut user = match User::find_by_uuid(&data.UserId, &conn) { | ||||
|     let mut user = match User::find_by_uuid(&data.UserId, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("User doesn't exist"), | ||||
|     }; | ||||
| @@ -493,7 +493,7 @@ fn post_verify_email_token(data: JsonUpcase<VerifyEmailTokenData>, conn: DbConn) | ||||
|     user.verified_at = Some(Utc::now().naive_utc()); | ||||
|     user.last_verifying_at = None; | ||||
|     user.login_verify_count = 0; | ||||
|     if let Err(e) = user.save(&conn) { | ||||
|     if let Err(e) = user.save(&conn).await { | ||||
|         error!("Error saving email verification: {:#?}", e); | ||||
|     } | ||||
|  | ||||
| @@ -507,13 +507,11 @@ struct DeleteRecoverData { | ||||
| } | ||||
|  | ||||
| #[post("/accounts/delete-recover", data = "<data>")] | ||||
| fn post_delete_recover(data: JsonUpcase<DeleteRecoverData>, conn: DbConn) -> EmptyResult { | ||||
| async fn post_delete_recover(data: JsonUpcase<DeleteRecoverData>, conn: DbConn) -> EmptyResult { | ||||
|     let data: DeleteRecoverData = data.into_inner().data; | ||||
|  | ||||
|     let user = User::find_by_mail(&data.Email, &conn); | ||||
|  | ||||
|     if CONFIG.mail_enabled() { | ||||
|         if let Some(user) = user { | ||||
|         if let Some(user) = User::find_by_mail(&data.Email, &conn).await { | ||||
|             if let Err(e) = mail::send_delete_account(&user.email, &user.uuid) { | ||||
|                 error!("Error sending delete account email: {:#?}", e); | ||||
|             } | ||||
| @@ -536,10 +534,10 @@ struct DeleteRecoverTokenData { | ||||
| } | ||||
|  | ||||
| #[post("/accounts/delete-recover-token", data = "<data>")] | ||||
| fn post_delete_recover_token(data: JsonUpcase<DeleteRecoverTokenData>, conn: DbConn) -> EmptyResult { | ||||
| async fn post_delete_recover_token(data: JsonUpcase<DeleteRecoverTokenData>, conn: DbConn) -> EmptyResult { | ||||
|     let data: DeleteRecoverTokenData = data.into_inner().data; | ||||
|  | ||||
|     let user = match User::find_by_uuid(&data.UserId, &conn) { | ||||
|     let user = match User::find_by_uuid(&data.UserId, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("User doesn't exist"), | ||||
|     }; | ||||
| @@ -551,16 +549,16 @@ fn post_delete_recover_token(data: JsonUpcase<DeleteRecoverTokenData>, conn: DbC | ||||
|     if claims.sub != user.uuid { | ||||
|         err!("Invalid claim"); | ||||
|     } | ||||
|     user.delete(&conn) | ||||
|     user.delete(&conn).await | ||||
| } | ||||
|  | ||||
| #[post("/accounts/delete", data = "<data>")] | ||||
| fn post_delete_account(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
|     delete_account(data, headers, conn) | ||||
| async fn post_delete_account(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
|     delete_account(data, headers, conn).await | ||||
| } | ||||
|  | ||||
| #[delete("/accounts", data = "<data>")] | ||||
| fn delete_account(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn delete_account(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
|     let data: PasswordData = data.into_inner().data; | ||||
|     let user = headers.user; | ||||
|  | ||||
| @@ -568,7 +566,7 @@ fn delete_account(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn | ||||
|         err!("Invalid password") | ||||
|     } | ||||
|  | ||||
|     user.delete(&conn) | ||||
|     user.delete(&conn).await | ||||
| } | ||||
|  | ||||
| #[get("/accounts/revision-date")] | ||||
| @@ -584,7 +582,7 @@ struct PasswordHintData { | ||||
| } | ||||
|  | ||||
| #[post("/accounts/password-hint", data = "<data>")] | ||||
| fn password_hint(data: JsonUpcase<PasswordHintData>, conn: DbConn) -> EmptyResult { | ||||
| async fn password_hint(data: JsonUpcase<PasswordHintData>, conn: DbConn) -> EmptyResult { | ||||
|     if !CONFIG.mail_enabled() && !CONFIG.show_password_hint() { | ||||
|         err!("This server is not configured to provide password hints."); | ||||
|     } | ||||
| @@ -594,7 +592,7 @@ fn password_hint(data: JsonUpcase<PasswordHintData>, conn: DbConn) -> EmptyResul | ||||
|     let data: PasswordHintData = data.into_inner().data; | ||||
|     let email = &data.Email; | ||||
|  | ||||
|     match User::find_by_mail(email, &conn) { | ||||
|     match User::find_by_mail(email, &conn).await { | ||||
|         None => { | ||||
|             // To prevent user enumeration, act as if the user exists. | ||||
|             if CONFIG.mail_enabled() { | ||||
| @@ -633,10 +631,10 @@ struct PreloginData { | ||||
| } | ||||
|  | ||||
| #[post("/accounts/prelogin", data = "<data>")] | ||||
| fn prelogin(data: JsonUpcase<PreloginData>, conn: DbConn) -> Json<Value> { | ||||
| async fn prelogin(data: JsonUpcase<PreloginData>, conn: DbConn) -> Json<Value> { | ||||
|     let data: PreloginData = data.into_inner().data; | ||||
|  | ||||
|     let (kdf_type, kdf_iter) = match User::find_by_mail(&data.Email, &conn) { | ||||
|     let (kdf_type, kdf_iter) = match User::find_by_mail(&data.Email, &conn).await { | ||||
|         Some(user) => (user.client_kdf_type, user.client_kdf_iter), | ||||
|         None => (User::CLIENT_KDF_TYPE_DEFAULT, User::CLIENT_KDF_ITER_DEFAULT), | ||||
|     }; | ||||
| @@ -666,7 +664,7 @@ fn verify_password(data: JsonUpcase<SecretVerificationRequest>, headers: Headers | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| fn _api_key(data: JsonUpcase<SecretVerificationRequest>, rotate: bool, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn _api_key(data: JsonUpcase<SecretVerificationRequest>, rotate: bool, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let data: SecretVerificationRequest = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
|  | ||||
| @@ -676,7 +674,7 @@ fn _api_key(data: JsonUpcase<SecretVerificationRequest>, rotate: bool, headers: | ||||
|  | ||||
|     if rotate || user.api_key.is_none() { | ||||
|         user.api_key = Some(crypto::generate_api_key()); | ||||
|         user.save(&conn).expect("Error saving API key"); | ||||
|         user.save(&conn).await.expect("Error saving API key"); | ||||
|     } | ||||
|  | ||||
|     Ok(Json(json!({ | ||||
| @@ -686,11 +684,11 @@ fn _api_key(data: JsonUpcase<SecretVerificationRequest>, rotate: bool, headers: | ||||
| } | ||||
|  | ||||
| #[post("/accounts/api-key", data = "<data>")] | ||||
| fn api_key(data: JsonUpcase<SecretVerificationRequest>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     _api_key(data, false, headers, conn) | ||||
| async fn api_key(data: JsonUpcase<SecretVerificationRequest>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     _api_key(data, false, headers, conn).await | ||||
| } | ||||
|  | ||||
| #[post("/accounts/rotate-api-key", data = "<data>")] | ||||
| fn rotate_api_key(data: JsonUpcase<SecretVerificationRequest>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     _api_key(data, true, headers, conn) | ||||
| async fn rotate_api_key(data: JsonUpcase<SecretVerificationRequest>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     _api_key(data, true, headers, conn).await | ||||
| } | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -11,6 +11,8 @@ use crate::{ | ||||
|     mail, CONFIG, | ||||
| }; | ||||
|  | ||||
| use futures::{stream, stream::StreamExt}; | ||||
|  | ||||
| pub fn routes() -> Vec<Route> { | ||||
|     routes![ | ||||
|         get_contacts, | ||||
| @@ -36,13 +38,17 @@ pub fn routes() -> Vec<Route> { | ||||
| // region get | ||||
|  | ||||
| #[get("/emergency-access/trusted")] | ||||
| fn get_contacts(headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn get_contacts(headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     check_emergency_access_allowed()?; | ||||
|  | ||||
|     let emergency_access_list = EmergencyAccess::find_all_by_grantor_uuid(&headers.user.uuid, &conn); | ||||
|  | ||||
|     let emergency_access_list_json: Vec<Value> = | ||||
|         emergency_access_list.iter().map(|e| e.to_json_grantee_details(&conn)).collect(); | ||||
|     let emergency_access_list_json = | ||||
|         stream::iter(EmergencyAccess::find_all_by_grantor_uuid(&headers.user.uuid, &conn).await) | ||||
|             .then(|e| async { | ||||
|                 let e = e; // Move out this single variable | ||||
|                 e.to_json_grantee_details(&conn).await | ||||
|             }) | ||||
|             .collect::<Vec<Value>>() | ||||
|             .await; | ||||
|  | ||||
|     Ok(Json(json!({ | ||||
|       "Data": emergency_access_list_json, | ||||
| @@ -52,13 +58,17 @@ fn get_contacts(headers: Headers, conn: DbConn) -> JsonResult { | ||||
| } | ||||
|  | ||||
| #[get("/emergency-access/granted")] | ||||
| fn get_grantees(headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn get_grantees(headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     check_emergency_access_allowed()?; | ||||
|  | ||||
|     let emergency_access_list = EmergencyAccess::find_all_by_grantee_uuid(&headers.user.uuid, &conn); | ||||
|  | ||||
|     let emergency_access_list_json: Vec<Value> = | ||||
|         emergency_access_list.iter().map(|e| e.to_json_grantor_details(&conn)).collect(); | ||||
|     let emergency_access_list_json = | ||||
|         stream::iter(EmergencyAccess::find_all_by_grantee_uuid(&headers.user.uuid, &conn).await) | ||||
|             .then(|e| async { | ||||
|                 let e = e; // Move out this single variable | ||||
|                 e.to_json_grantor_details(&conn).await | ||||
|             }) | ||||
|             .collect::<Vec<Value>>() | ||||
|             .await; | ||||
|  | ||||
|     Ok(Json(json!({ | ||||
|       "Data": emergency_access_list_json, | ||||
| @@ -68,11 +78,11 @@ fn get_grantees(headers: Headers, conn: DbConn) -> JsonResult { | ||||
| } | ||||
|  | ||||
| #[get("/emergency-access/<emer_id>")] | ||||
| fn get_emergency_access(emer_id: String, conn: DbConn) -> JsonResult { | ||||
| async fn get_emergency_access(emer_id: String, conn: DbConn) -> JsonResult { | ||||
|     check_emergency_access_allowed()?; | ||||
|  | ||||
|     match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | ||||
|         Some(emergency_access) => Ok(Json(emergency_access.to_json_grantee_details(&conn))), | ||||
|     match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|         Some(emergency_access) => Ok(Json(emergency_access.to_json_grantee_details(&conn).await)), | ||||
|         None => err!("Emergency access not valid."), | ||||
|     } | ||||
| } | ||||
| @@ -90,17 +100,25 @@ struct EmergencyAccessUpdateData { | ||||
| } | ||||
|  | ||||
| #[put("/emergency-access/<emer_id>", data = "<data>")] | ||||
| fn put_emergency_access(emer_id: String, data: JsonUpcase<EmergencyAccessUpdateData>, conn: DbConn) -> JsonResult { | ||||
|     post_emergency_access(emer_id, data, conn) | ||||
| async fn put_emergency_access( | ||||
|     emer_id: String, | ||||
|     data: JsonUpcase<EmergencyAccessUpdateData>, | ||||
|     conn: DbConn, | ||||
| ) -> JsonResult { | ||||
|     post_emergency_access(emer_id, data, conn).await | ||||
| } | ||||
|  | ||||
| #[post("/emergency-access/<emer_id>", data = "<data>")] | ||||
| fn post_emergency_access(emer_id: String, data: JsonUpcase<EmergencyAccessUpdateData>, conn: DbConn) -> JsonResult { | ||||
| async fn post_emergency_access( | ||||
|     emer_id: String, | ||||
|     data: JsonUpcase<EmergencyAccessUpdateData>, | ||||
|     conn: DbConn, | ||||
| ) -> JsonResult { | ||||
|     check_emergency_access_allowed()?; | ||||
|  | ||||
|     let data: EmergencyAccessUpdateData = data.into_inner().data; | ||||
|  | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|         Some(emergency_access) => emergency_access, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
| @@ -114,7 +132,7 @@ fn post_emergency_access(emer_id: String, data: JsonUpcase<EmergencyAccessUpdate | ||||
|     emergency_access.wait_time_days = data.WaitTimeDays; | ||||
|     emergency_access.key_encrypted = data.KeyEncrypted; | ||||
|  | ||||
|     emergency_access.save(&conn)?; | ||||
|     emergency_access.save(&conn).await?; | ||||
|     Ok(Json(emergency_access.to_json())) | ||||
| } | ||||
|  | ||||
| @@ -123,12 +141,12 @@ fn post_emergency_access(emer_id: String, data: JsonUpcase<EmergencyAccessUpdate | ||||
| // region delete | ||||
|  | ||||
| #[delete("/emergency-access/<emer_id>")] | ||||
| fn delete_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn delete_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
|     check_emergency_access_allowed()?; | ||||
|  | ||||
|     let grantor_user = headers.user; | ||||
|  | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|         Some(emer) => { | ||||
|             if emer.grantor_uuid != grantor_user.uuid && emer.grantee_uuid != Some(grantor_user.uuid) { | ||||
|                 err!("Emergency access not valid.") | ||||
| @@ -137,13 +155,13 @@ fn delete_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> E | ||||
|         } | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
|     emergency_access.delete(&conn)?; | ||||
|     emergency_access.delete(&conn).await?; | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| #[post("/emergency-access/<emer_id>/delete")] | ||||
| fn post_delete_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
|     delete_emergency_access(emer_id, headers, conn) | ||||
| async fn post_delete_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
|     delete_emergency_access(emer_id, headers, conn).await | ||||
| } | ||||
|  | ||||
| // endregion | ||||
| @@ -159,7 +177,7 @@ struct EmergencyAccessInviteData { | ||||
| } | ||||
|  | ||||
| #[post("/emergency-access/invite", data = "<data>")] | ||||
| fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
|     check_emergency_access_allowed()?; | ||||
|  | ||||
|     let data: EmergencyAccessInviteData = data.into_inner().data; | ||||
| @@ -180,7 +198,7 @@ fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Headers, co | ||||
|         err!("You can not set yourself as an emergency contact.") | ||||
|     } | ||||
|  | ||||
|     let grantee_user = match User::find_by_mail(&email, &conn) { | ||||
|     let grantee_user = match User::find_by_mail(&email, &conn).await { | ||||
|         None => { | ||||
|             if !CONFIG.invitations_allowed() { | ||||
|                 err!(format!("Grantee user does not exist: {}", email)) | ||||
| @@ -192,11 +210,11 @@ fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Headers, co | ||||
|  | ||||
|             if !CONFIG.mail_enabled() { | ||||
|                 let invitation = Invitation::new(email.clone()); | ||||
|                 invitation.save(&conn)?; | ||||
|                 invitation.save(&conn).await?; | ||||
|             } | ||||
|  | ||||
|             let mut user = User::new(email.clone()); | ||||
|             user.save(&conn)?; | ||||
|             user.save(&conn).await?; | ||||
|             user | ||||
|         } | ||||
|         Some(user) => user, | ||||
| @@ -208,6 +226,7 @@ fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Headers, co | ||||
|         &grantee_user.email, | ||||
|         &conn, | ||||
|     ) | ||||
|     .await | ||||
|     .is_some() | ||||
|     { | ||||
|         err!(format!("Grantee user already invited: {}", email)) | ||||
| @@ -220,7 +239,7 @@ fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Headers, co | ||||
|         new_type, | ||||
|         wait_time_days, | ||||
|     ); | ||||
|     new_emergency_access.save(&conn)?; | ||||
|     new_emergency_access.save(&conn).await?; | ||||
|  | ||||
|     if CONFIG.mail_enabled() { | ||||
|         mail::send_emergency_access_invite( | ||||
| @@ -232,9 +251,9 @@ fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Headers, co | ||||
|         )?; | ||||
|     } else { | ||||
|         // Automatically mark user as accepted if no email invites | ||||
|         match User::find_by_mail(&email, &conn) { | ||||
|         match User::find_by_mail(&email, &conn).await { | ||||
|             Some(user) => { | ||||
|                 match accept_invite_process(user.uuid, new_emergency_access.uuid, Some(email), conn.borrow()) { | ||||
|                 match accept_invite_process(user.uuid, new_emergency_access.uuid, Some(email), conn.borrow()).await { | ||||
|                     Ok(v) => (v), | ||||
|                     Err(e) => err!(e.to_string()), | ||||
|                 } | ||||
| @@ -247,10 +266,10 @@ fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Headers, co | ||||
| } | ||||
|  | ||||
| #[post("/emergency-access/<emer_id>/reinvite")] | ||||
| fn resend_invite(emer_id: String, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn resend_invite(emer_id: String, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
|     check_emergency_access_allowed()?; | ||||
|  | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
| @@ -268,7 +287,7 @@ fn resend_invite(emer_id: String, headers: Headers, conn: DbConn) -> EmptyResult | ||||
|         None => err!("Email not valid."), | ||||
|     }; | ||||
|  | ||||
|     let grantee_user = match User::find_by_mail(&email, &conn) { | ||||
|     let grantee_user = match User::find_by_mail(&email, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Grantee user not found."), | ||||
|     }; | ||||
| @@ -284,13 +303,15 @@ fn resend_invite(emer_id: String, headers: Headers, conn: DbConn) -> EmptyResult | ||||
|             Some(grantor_user.email), | ||||
|         )?; | ||||
|     } else { | ||||
|         if Invitation::find_by_mail(&email, &conn).is_none() { | ||||
|         if Invitation::find_by_mail(&email, &conn).await.is_none() { | ||||
|             let invitation = Invitation::new(email); | ||||
|             invitation.save(&conn)?; | ||||
|             invitation.save(&conn).await?; | ||||
|         } | ||||
|  | ||||
|         // Automatically mark user as accepted if no email invites | ||||
|         match accept_invite_process(grantee_user.uuid, emergency_access.uuid, emergency_access.email, conn.borrow()) { | ||||
|         match accept_invite_process(grantee_user.uuid, emergency_access.uuid, emergency_access.email, conn.borrow()) | ||||
|             .await | ||||
|         { | ||||
|             Ok(v) => (v), | ||||
|             Err(e) => err!(e.to_string()), | ||||
|         } | ||||
| @@ -306,28 +327,28 @@ struct AcceptData { | ||||
| } | ||||
|  | ||||
| #[post("/emergency-access/<emer_id>/accept", data = "<data>")] | ||||
| fn accept_invite(emer_id: String, data: JsonUpcase<AcceptData>, conn: DbConn) -> EmptyResult { | ||||
| async fn accept_invite(emer_id: String, data: JsonUpcase<AcceptData>, conn: DbConn) -> EmptyResult { | ||||
|     check_emergency_access_allowed()?; | ||||
|  | ||||
|     let data: AcceptData = data.into_inner().data; | ||||
|     let token = &data.Token; | ||||
|     let claims = decode_emergency_access_invite(token)?; | ||||
|  | ||||
|     let grantee_user = match User::find_by_mail(&claims.email, &conn) { | ||||
|     let grantee_user = match User::find_by_mail(&claims.email, &conn).await { | ||||
|         Some(user) => { | ||||
|             Invitation::take(&claims.email, &conn); | ||||
|             Invitation::take(&claims.email, &conn).await; | ||||
|             user | ||||
|         } | ||||
|         None => err!("Invited user not found"), | ||||
|     }; | ||||
|  | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
|  | ||||
|     // get grantor user to send Accepted email | ||||
|     let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn) { | ||||
|     let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Grantor user not found."), | ||||
|     }; | ||||
| @@ -336,7 +357,7 @@ fn accept_invite(emer_id: String, data: JsonUpcase<AcceptData>, conn: DbConn) -> | ||||
|         && (claims.grantor_name.is_some() && grantor_user.name == claims.grantor_name.unwrap()) | ||||
|         && (claims.grantor_email.is_some() && grantor_user.email == claims.grantor_email.unwrap()) | ||||
|     { | ||||
|         match accept_invite_process(grantee_user.uuid.clone(), emer_id, Some(grantee_user.email.clone()), &conn) { | ||||
|         match accept_invite_process(grantee_user.uuid.clone(), emer_id, Some(grantee_user.email.clone()), &conn).await { | ||||
|             Ok(v) => (v), | ||||
|             Err(e) => err!(e.to_string()), | ||||
|         } | ||||
| @@ -351,8 +372,13 @@ fn accept_invite(emer_id: String, data: JsonUpcase<AcceptData>, conn: DbConn) -> | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn accept_invite_process(grantee_uuid: String, emer_id: String, email: Option<String>, conn: &DbConn) -> EmptyResult { | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, conn) { | ||||
| async fn accept_invite_process( | ||||
|     grantee_uuid: String, | ||||
|     emer_id: String, | ||||
|     email: Option<String>, | ||||
|     conn: &DbConn, | ||||
| ) -> EmptyResult { | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
| @@ -369,7 +395,7 @@ fn accept_invite_process(grantee_uuid: String, emer_id: String, email: Option<St | ||||
|     emergency_access.status = EmergencyAccessStatus::Accepted as i32; | ||||
|     emergency_access.grantee_uuid = Some(grantee_uuid); | ||||
|     emergency_access.email = None; | ||||
|     emergency_access.save(conn) | ||||
|     emergency_access.save(conn).await | ||||
| } | ||||
|  | ||||
| #[derive(Deserialize)] | ||||
| @@ -379,7 +405,7 @@ struct ConfirmData { | ||||
| } | ||||
|  | ||||
| #[post("/emergency-access/<emer_id>/confirm", data = "<data>")] | ||||
| fn confirm_emergency_access( | ||||
| async fn confirm_emergency_access( | ||||
|     emer_id: String, | ||||
|     data: JsonUpcase<ConfirmData>, | ||||
|     headers: Headers, | ||||
| @@ -391,7 +417,7 @@ fn confirm_emergency_access( | ||||
|     let data: ConfirmData = data.into_inner().data; | ||||
|     let key = data.Key; | ||||
|  | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
| @@ -402,13 +428,13 @@ fn confirm_emergency_access( | ||||
|         err!("Emergency access not valid.") | ||||
|     } | ||||
|  | ||||
|     let grantor_user = match User::find_by_uuid(&confirming_user.uuid, &conn) { | ||||
|     let grantor_user = match User::find_by_uuid(&confirming_user.uuid, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Grantor user not found."), | ||||
|     }; | ||||
|  | ||||
|     if let Some(grantee_uuid) = emergency_access.grantee_uuid.as_ref() { | ||||
|         let grantee_user = match User::find_by_uuid(grantee_uuid, &conn) { | ||||
|         let grantee_user = match User::find_by_uuid(grantee_uuid, &conn).await { | ||||
|             Some(user) => user, | ||||
|             None => err!("Grantee user not found."), | ||||
|         }; | ||||
| @@ -417,7 +443,7 @@ fn confirm_emergency_access( | ||||
|         emergency_access.key_encrypted = Some(key); | ||||
|         emergency_access.email = None; | ||||
|  | ||||
|         emergency_access.save(&conn)?; | ||||
|         emergency_access.save(&conn).await?; | ||||
|  | ||||
|         if CONFIG.mail_enabled() { | ||||
|             mail::send_emergency_access_invite_confirmed(&grantee_user.email, &grantor_user.name)?; | ||||
| @@ -433,11 +459,11 @@ fn confirm_emergency_access( | ||||
| // region access emergency access | ||||
|  | ||||
| #[post("/emergency-access/<emer_id>/initiate")] | ||||
| fn initiate_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn initiate_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     check_emergency_access_allowed()?; | ||||
|  | ||||
|     let initiating_user = headers.user; | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
| @@ -448,7 +474,7 @@ fn initiate_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> | ||||
|         err!("Emergency access not valid.") | ||||
|     } | ||||
|  | ||||
|     let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn) { | ||||
|     let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Grantor user not found."), | ||||
|     }; | ||||
| @@ -458,7 +484,7 @@ fn initiate_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> | ||||
|     emergency_access.updated_at = now; | ||||
|     emergency_access.recovery_initiated_at = Some(now); | ||||
|     emergency_access.last_notification_at = Some(now); | ||||
|     emergency_access.save(&conn)?; | ||||
|     emergency_access.save(&conn).await?; | ||||
|  | ||||
|     if CONFIG.mail_enabled() { | ||||
|         mail::send_emergency_access_recovery_initiated( | ||||
| @@ -472,11 +498,11 @@ fn initiate_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> | ||||
| } | ||||
|  | ||||
| #[post("/emergency-access/<emer_id>/approve")] | ||||
| fn approve_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn approve_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     check_emergency_access_allowed()?; | ||||
|  | ||||
|     let approving_user = headers.user; | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
| @@ -487,19 +513,19 @@ fn approve_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> | ||||
|         err!("Emergency access not valid.") | ||||
|     } | ||||
|  | ||||
|     let grantor_user = match User::find_by_uuid(&approving_user.uuid, &conn) { | ||||
|     let grantor_user = match User::find_by_uuid(&approving_user.uuid, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Grantor user not found."), | ||||
|     }; | ||||
|  | ||||
|     if let Some(grantee_uuid) = emergency_access.grantee_uuid.as_ref() { | ||||
|         let grantee_user = match User::find_by_uuid(grantee_uuid, &conn) { | ||||
|         let grantee_user = match User::find_by_uuid(grantee_uuid, &conn).await { | ||||
|             Some(user) => user, | ||||
|             None => err!("Grantee user not found."), | ||||
|         }; | ||||
|  | ||||
|         emergency_access.status = EmergencyAccessStatus::RecoveryApproved as i32; | ||||
|         emergency_access.save(&conn)?; | ||||
|         emergency_access.save(&conn).await?; | ||||
|  | ||||
|         if CONFIG.mail_enabled() { | ||||
|             mail::send_emergency_access_recovery_approved(&grantee_user.email, &grantor_user.name)?; | ||||
| @@ -511,11 +537,11 @@ fn approve_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> | ||||
| } | ||||
|  | ||||
| #[post("/emergency-access/<emer_id>/reject")] | ||||
| fn reject_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn reject_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     check_emergency_access_allowed()?; | ||||
|  | ||||
|     let rejecting_user = headers.user; | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | ||||
|     let mut emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
| @@ -527,19 +553,19 @@ fn reject_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> J | ||||
|         err!("Emergency access not valid.") | ||||
|     } | ||||
|  | ||||
|     let grantor_user = match User::find_by_uuid(&rejecting_user.uuid, &conn) { | ||||
|     let grantor_user = match User::find_by_uuid(&rejecting_user.uuid, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Grantor user not found."), | ||||
|     }; | ||||
|  | ||||
|     if let Some(grantee_uuid) = emergency_access.grantee_uuid.as_ref() { | ||||
|         let grantee_user = match User::find_by_uuid(grantee_uuid, &conn) { | ||||
|         let grantee_user = match User::find_by_uuid(grantee_uuid, &conn).await { | ||||
|             Some(user) => user, | ||||
|             None => err!("Grantee user not found."), | ||||
|         }; | ||||
|  | ||||
|         emergency_access.status = EmergencyAccessStatus::Confirmed as i32; | ||||
|         emergency_access.save(&conn)?; | ||||
|         emergency_access.save(&conn).await?; | ||||
|  | ||||
|         if CONFIG.mail_enabled() { | ||||
|             mail::send_emergency_access_recovery_rejected(&grantee_user.email, &grantor_user.name)?; | ||||
| @@ -555,12 +581,12 @@ fn reject_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> J | ||||
| // region action | ||||
|  | ||||
| #[post("/emergency-access/<emer_id>/view")] | ||||
| fn view_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn view_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     check_emergency_access_allowed()?; | ||||
|  | ||||
|     let requesting_user = headers.user; | ||||
|     let host = headers.host; | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
| @@ -569,10 +595,13 @@ fn view_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> Jso | ||||
|         err!("Emergency access not valid.") | ||||
|     } | ||||
|  | ||||
|     let ciphers = Cipher::find_owned_by_user(&emergency_access.grantor_uuid, &conn); | ||||
|  | ||||
|     let ciphers_json: Vec<Value> = | ||||
|         ciphers.iter().map(|c| c.to_json(&host, &emergency_access.grantor_uuid, &conn)).collect(); | ||||
|     let ciphers_json = stream::iter(Cipher::find_owned_by_user(&emergency_access.grantor_uuid, &conn).await) | ||||
|         .then(|c| async { | ||||
|             let c = c; // Move out this single variable | ||||
|             c.to_json(&host, &emergency_access.grantor_uuid, &conn).await | ||||
|         }) | ||||
|         .collect::<Vec<Value>>() | ||||
|         .await; | ||||
|  | ||||
|     Ok(Json(json!({ | ||||
|       "Ciphers": ciphers_json, | ||||
| @@ -582,11 +611,11 @@ fn view_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> Jso | ||||
| } | ||||
|  | ||||
| #[post("/emergency-access/<emer_id>/takeover")] | ||||
| fn takeover_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn takeover_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     check_emergency_access_allowed()?; | ||||
|  | ||||
|     let requesting_user = headers.user; | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
| @@ -595,7 +624,7 @@ fn takeover_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> | ||||
|         err!("Emergency access not valid.") | ||||
|     } | ||||
|  | ||||
|     let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn) { | ||||
|     let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Grantor user not found."), | ||||
|     }; | ||||
| @@ -616,7 +645,7 @@ struct EmergencyAccessPasswordData { | ||||
| } | ||||
|  | ||||
| #[post("/emergency-access/<emer_id>/password", data = "<data>")] | ||||
| fn password_emergency_access( | ||||
| async fn password_emergency_access( | ||||
|     emer_id: String, | ||||
|     data: JsonUpcase<EmergencyAccessPasswordData>, | ||||
|     headers: Headers, | ||||
| @@ -629,7 +658,7 @@ fn password_emergency_access( | ||||
|     let key = data.Key; | ||||
|  | ||||
|     let requesting_user = headers.user; | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
| @@ -638,7 +667,7 @@ fn password_emergency_access( | ||||
|         err!("Emergency access not valid.") | ||||
|     } | ||||
|  | ||||
|     let mut grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn) { | ||||
|     let mut grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Grantor user not found."), | ||||
|     }; | ||||
| @@ -646,18 +675,15 @@ fn password_emergency_access( | ||||
|     // change grantor_user password | ||||
|     grantor_user.set_password(new_master_password_hash, None); | ||||
|     grantor_user.akey = key; | ||||
|     grantor_user.save(&conn)?; | ||||
|     grantor_user.save(&conn).await?; | ||||
|  | ||||
|     // Disable TwoFactor providers since they will otherwise block logins | ||||
|     TwoFactor::delete_all_by_user(&grantor_user.uuid, &conn)?; | ||||
|  | ||||
|     // Removing owner, check that there are at least another owner | ||||
|     let user_org_grantor = UserOrganization::find_any_state_by_user(&grantor_user.uuid, &conn); | ||||
|     TwoFactor::delete_all_by_user(&grantor_user.uuid, &conn).await?; | ||||
|  | ||||
|     // Remove grantor from all organisations unless Owner | ||||
|     for user_org in user_org_grantor { | ||||
|     for user_org in UserOrganization::find_any_state_by_user(&grantor_user.uuid, &conn).await { | ||||
|         if user_org.atype != UserOrgType::Owner as i32 { | ||||
|             user_org.delete(&conn)?; | ||||
|             user_org.delete(&conn).await?; | ||||
|         } | ||||
|     } | ||||
|     Ok(()) | ||||
| @@ -666,9 +692,9 @@ fn password_emergency_access( | ||||
| // endregion | ||||
|  | ||||
| #[get("/emergency-access/<emer_id>/policies")] | ||||
| fn policies_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn policies_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let requesting_user = headers.user; | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn) { | ||||
|     let emergency_access = match EmergencyAccess::find_by_uuid(&emer_id, &conn).await { | ||||
|         Some(emer) => emer, | ||||
|         None => err!("Emergency access not valid."), | ||||
|     }; | ||||
| @@ -677,13 +703,13 @@ fn policies_emergency_access(emer_id: String, headers: Headers, conn: DbConn) -> | ||||
|         err!("Emergency access not valid.") | ||||
|     } | ||||
|  | ||||
|     let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn) { | ||||
|     let grantor_user = match User::find_by_uuid(&emergency_access.grantor_uuid, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Grantor user not found."), | ||||
|     }; | ||||
|  | ||||
|     let policies = OrgPolicy::find_confirmed_by_user(&grantor_user.uuid, &conn); | ||||
|     let policies_json: Vec<Value> = policies.iter().map(OrgPolicy::to_json).collect(); | ||||
|     let policies_json: Vec<Value> = policies.await.iter().map(OrgPolicy::to_json).collect(); | ||||
|  | ||||
|     Ok(Json(json!({ | ||||
|         "Data": policies_json, | ||||
| @@ -716,7 +742,7 @@ pub async fn emergency_request_timeout_job(pool: DbPool) { | ||||
|     } | ||||
|  | ||||
|     if let Ok(conn) = pool.get().await { | ||||
|         let emergency_access_list = EmergencyAccess::find_all_recoveries(&conn); | ||||
|         let emergency_access_list = EmergencyAccess::find_all_recoveries(&conn).await; | ||||
|  | ||||
|         if emergency_access_list.is_empty() { | ||||
|             debug!("No emergency request timeout to approve"); | ||||
| @@ -728,15 +754,17 @@ pub async fn emergency_request_timeout_job(pool: DbPool) { | ||||
|                     >= emer.recovery_initiated_at.unwrap() + Duration::days(emer.wait_time_days as i64) | ||||
|             { | ||||
|                 emer.status = EmergencyAccessStatus::RecoveryApproved as i32; | ||||
|                 emer.save(&conn).expect("Cannot save emergency access on job"); | ||||
|                 emer.save(&conn).await.expect("Cannot save emergency access on job"); | ||||
|  | ||||
|                 if CONFIG.mail_enabled() { | ||||
|                     // get grantor user to send Accepted email | ||||
|                     let grantor_user = User::find_by_uuid(&emer.grantor_uuid, &conn).expect("Grantor user not found."); | ||||
|                     let grantor_user = | ||||
|                         User::find_by_uuid(&emer.grantor_uuid, &conn).await.expect("Grantor user not found."); | ||||
|  | ||||
|                     // get grantee user to send Accepted email | ||||
|                     let grantee_user = | ||||
|                         User::find_by_uuid(&emer.grantee_uuid.clone().expect("Grantee user invalid."), &conn) | ||||
|                             .await | ||||
|                             .expect("Grantee user not found."); | ||||
|  | ||||
|                     mail::send_emergency_access_recovery_timed_out( | ||||
| @@ -763,7 +791,7 @@ pub async fn emergency_notification_reminder_job(pool: DbPool) { | ||||
|     } | ||||
|  | ||||
|     if let Ok(conn) = pool.get().await { | ||||
|         let emergency_access_list = EmergencyAccess::find_all_recoveries(&conn); | ||||
|         let emergency_access_list = EmergencyAccess::find_all_recoveries(&conn).await; | ||||
|  | ||||
|         if emergency_access_list.is_empty() { | ||||
|             debug!("No emergency request reminder notification to send"); | ||||
| @@ -777,15 +805,17 @@ pub async fn emergency_notification_reminder_job(pool: DbPool) { | ||||
|                     || (emer.last_notification_at.is_some() | ||||
|                         && Utc::now().naive_utc() >= emer.last_notification_at.unwrap() + Duration::days(1))) | ||||
|             { | ||||
|                 emer.save(&conn).expect("Cannot save emergency access on job"); | ||||
|                 emer.save(&conn).await.expect("Cannot save emergency access on job"); | ||||
|  | ||||
|                 if CONFIG.mail_enabled() { | ||||
|                     // get grantor user to send Accepted email | ||||
|                     let grantor_user = User::find_by_uuid(&emer.grantor_uuid, &conn).expect("Grantor user not found."); | ||||
|                     let grantor_user = | ||||
|                         User::find_by_uuid(&emer.grantor_uuid, &conn).await.expect("Grantor user not found."); | ||||
|  | ||||
|                     // get grantee user to send Accepted email | ||||
|                     let grantee_user = | ||||
|                         User::find_by_uuid(&emer.grantee_uuid.clone().expect("Grantee user invalid."), &conn) | ||||
|                             .await | ||||
|                             .expect("Grantee user not found."); | ||||
|  | ||||
|                     mail::send_emergency_access_recovery_reminder( | ||||
|   | ||||
| @@ -12,9 +12,8 @@ pub fn routes() -> Vec<rocket::Route> { | ||||
| } | ||||
|  | ||||
| #[get("/folders")] | ||||
| fn get_folders(headers: Headers, conn: DbConn) -> Json<Value> { | ||||
|     let folders = Folder::find_by_user(&headers.user.uuid, &conn); | ||||
|  | ||||
| async fn get_folders(headers: Headers, conn: DbConn) -> Json<Value> { | ||||
|     let folders = Folder::find_by_user(&headers.user.uuid, &conn).await; | ||||
|     let folders_json: Vec<Value> = folders.iter().map(Folder::to_json).collect(); | ||||
|  | ||||
|     Json(json!({ | ||||
| @@ -25,8 +24,8 @@ fn get_folders(headers: Headers, conn: DbConn) -> Json<Value> { | ||||
| } | ||||
|  | ||||
| #[get("/folders/<uuid>")] | ||||
| fn get_folder(uuid: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let folder = match Folder::find_by_uuid(&uuid, &conn) { | ||||
| async fn get_folder(uuid: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let folder = match Folder::find_by_uuid(&uuid, &conn).await { | ||||
|         Some(folder) => folder, | ||||
|         _ => err!("Invalid folder"), | ||||
|     }; | ||||
| @@ -45,27 +44,39 @@ pub struct FolderData { | ||||
| } | ||||
|  | ||||
| #[post("/folders", data = "<data>")] | ||||
| fn post_folders(data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { | ||||
| async fn post_folders(data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult { | ||||
|     let data: FolderData = data.into_inner().data; | ||||
|  | ||||
|     let mut folder = Folder::new(headers.user.uuid, data.Name); | ||||
|  | ||||
|     folder.save(&conn)?; | ||||
|     folder.save(&conn).await?; | ||||
|     nt.send_folder_update(UpdateType::FolderCreate, &folder); | ||||
|  | ||||
|     Ok(Json(folder.to_json())) | ||||
| } | ||||
|  | ||||
| #[post("/folders/<uuid>", data = "<data>")] | ||||
| fn post_folder(uuid: String, data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { | ||||
|     put_folder(uuid, data, headers, conn, nt) | ||||
| async fn post_folder( | ||||
|     uuid: String, | ||||
|     data: JsonUpcase<FolderData>, | ||||
|     headers: Headers, | ||||
|     conn: DbConn, | ||||
|     nt: Notify<'_>, | ||||
| ) -> JsonResult { | ||||
|     put_folder(uuid, data, headers, conn, nt).await | ||||
| } | ||||
|  | ||||
| #[put("/folders/<uuid>", data = "<data>")] | ||||
| fn put_folder(uuid: String, data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { | ||||
| async fn put_folder( | ||||
|     uuid: String, | ||||
|     data: JsonUpcase<FolderData>, | ||||
|     headers: Headers, | ||||
|     conn: DbConn, | ||||
|     nt: Notify<'_>, | ||||
| ) -> JsonResult { | ||||
|     let data: FolderData = data.into_inner().data; | ||||
|  | ||||
|     let mut folder = match Folder::find_by_uuid(&uuid, &conn) { | ||||
|     let mut folder = match Folder::find_by_uuid(&uuid, &conn).await { | ||||
|         Some(folder) => folder, | ||||
|         _ => err!("Invalid folder"), | ||||
|     }; | ||||
| @@ -76,20 +87,20 @@ fn put_folder(uuid: String, data: JsonUpcase<FolderData>, headers: Headers, conn | ||||
|  | ||||
|     folder.name = data.Name; | ||||
|  | ||||
|     folder.save(&conn)?; | ||||
|     folder.save(&conn).await?; | ||||
|     nt.send_folder_update(UpdateType::FolderUpdate, &folder); | ||||
|  | ||||
|     Ok(Json(folder.to_json())) | ||||
| } | ||||
|  | ||||
| #[post("/folders/<uuid>/delete")] | ||||
| fn delete_folder_post(uuid: String, headers: Headers, conn: DbConn, nt: Notify) -> EmptyResult { | ||||
|     delete_folder(uuid, headers, conn, nt) | ||||
| async fn delete_folder_post(uuid: String, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult { | ||||
|     delete_folder(uuid, headers, conn, nt).await | ||||
| } | ||||
|  | ||||
| #[delete("/folders/<uuid>")] | ||||
| fn delete_folder(uuid: String, headers: Headers, conn: DbConn, nt: Notify) -> EmptyResult { | ||||
|     let folder = match Folder::find_by_uuid(&uuid, &conn) { | ||||
| async fn delete_folder(uuid: String, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult { | ||||
|     let folder = match Folder::find_by_uuid(&uuid, &conn).await { | ||||
|         Some(folder) => folder, | ||||
|         _ => err!("Invalid folder"), | ||||
|     }; | ||||
| @@ -99,7 +110,7 @@ fn delete_folder(uuid: String, headers: Headers, conn: DbConn, nt: Notify) -> Em | ||||
|     } | ||||
|  | ||||
|     // Delete the actual folder entry | ||||
|     folder.delete(&conn)?; | ||||
|     folder.delete(&conn).await?; | ||||
|  | ||||
|     nt.send_folder_update(UpdateType::FolderDelete, &folder); | ||||
|     Ok(()) | ||||
|   | ||||
| @@ -121,7 +121,7 @@ struct EquivDomainData { | ||||
| } | ||||
|  | ||||
| #[post("/settings/domains", data = "<data>")] | ||||
| fn post_eq_domains(data: JsonUpcase<EquivDomainData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn post_eq_domains(data: JsonUpcase<EquivDomainData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let data: EquivDomainData = data.into_inner().data; | ||||
|  | ||||
|     let excluded_globals = data.ExcludedGlobalEquivalentDomains.unwrap_or_default(); | ||||
| @@ -133,14 +133,14 @@ fn post_eq_domains(data: JsonUpcase<EquivDomainData>, headers: Headers, conn: Db | ||||
|     user.excluded_globals = to_string(&excluded_globals).unwrap_or_else(|_| "[]".to_string()); | ||||
|     user.equivalent_domains = to_string(&equivalent_domains).unwrap_or_else(|_| "[]".to_string()); | ||||
|  | ||||
|     user.save(&conn)?; | ||||
|     user.save(&conn).await?; | ||||
|  | ||||
|     Ok(Json(json!({}))) | ||||
| } | ||||
|  | ||||
| #[put("/settings/domains", data = "<data>")] | ||||
| fn put_eq_domains(data: JsonUpcase<EquivDomainData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     post_eq_domains(data, headers, conn) | ||||
| async fn put_eq_domains(data: JsonUpcase<EquivDomainData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     post_eq_domains(data, headers, conn).await | ||||
| } | ||||
|  | ||||
| #[get("/hibp/breach?<username>")] | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -35,7 +35,7 @@ pub fn routes() -> Vec<rocket::Route> { | ||||
| pub async fn purge_sends(pool: DbPool) { | ||||
|     debug!("Purging sends"); | ||||
|     if let Ok(conn) = pool.get().await { | ||||
|         Send::purge(&conn); | ||||
|         Send::purge(&conn).await; | ||||
|     } else { | ||||
|         error!("Failed to get DB connection while purging sends") | ||||
|     } | ||||
| @@ -68,10 +68,10 @@ struct SendData { | ||||
| /// | ||||
| /// There is also a Vaultwarden-specific `sends_allowed` config setting that | ||||
| /// controls this policy globally. | ||||
| fn enforce_disable_send_policy(headers: &Headers, conn: &DbConn) -> EmptyResult { | ||||
| async fn enforce_disable_send_policy(headers: &Headers, conn: &DbConn) -> EmptyResult { | ||||
|     let user_uuid = &headers.user.uuid; | ||||
|     let policy_type = OrgPolicyType::DisableSend; | ||||
|     if !CONFIG.sends_allowed() || OrgPolicy::is_applicable_to_user(user_uuid, policy_type, conn) { | ||||
|     if !CONFIG.sends_allowed() || OrgPolicy::is_applicable_to_user(user_uuid, policy_type, conn).await { | ||||
|         err!("Due to an Enterprise Policy, you are only able to delete an existing Send.") | ||||
|     } | ||||
|     Ok(()) | ||||
| @@ -83,10 +83,10 @@ fn enforce_disable_send_policy(headers: &Headers, conn: &DbConn) -> EmptyResult | ||||
| /// but is allowed to remove this option from an existing Send. | ||||
| /// | ||||
| /// Ref: https://bitwarden.com/help/article/policies/#send-options | ||||
| fn enforce_disable_hide_email_policy(data: &SendData, headers: &Headers, conn: &DbConn) -> EmptyResult { | ||||
| async fn enforce_disable_hide_email_policy(data: &SendData, headers: &Headers, conn: &DbConn) -> EmptyResult { | ||||
|     let user_uuid = &headers.user.uuid; | ||||
|     let hide_email = data.HideEmail.unwrap_or(false); | ||||
|     if hide_email && OrgPolicy::is_hide_email_disabled(user_uuid, conn) { | ||||
|     if hide_email && OrgPolicy::is_hide_email_disabled(user_uuid, conn).await { | ||||
|         err!( | ||||
|             "Due to an Enterprise Policy, you are not allowed to hide your email address \ | ||||
|               from recipients when creating or editing a Send." | ||||
| @@ -95,7 +95,7 @@ fn enforce_disable_hide_email_policy(data: &SendData, headers: &Headers, conn: & | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| fn create_send(data: SendData, user_uuid: String) -> ApiResult<Send> { | ||||
| async fn create_send(data: SendData, user_uuid: String) -> ApiResult<Send> { | ||||
|     let data_val = if data.Type == SendType::Text as i32 { | ||||
|         data.Text | ||||
|     } else if data.Type == SendType::File as i32 { | ||||
| @@ -117,7 +117,7 @@ fn create_send(data: SendData, user_uuid: String) -> ApiResult<Send> { | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     let mut send = Send::new(data.Type, data.Name, data_str, data.Key, data.DeletionDate.naive_utc()); | ||||
|     let mut send = Send::new(data.Type, data.Name, data_str, data.Key, data.DeletionDate.naive_utc()).await; | ||||
|     send.user_uuid = Some(user_uuid); | ||||
|     send.notes = data.Notes; | ||||
|     send.max_access_count = match data.MaxAccessCount { | ||||
| @@ -135,9 +135,9 @@ fn create_send(data: SendData, user_uuid: String) -> ApiResult<Send> { | ||||
| } | ||||
|  | ||||
| #[get("/sends")] | ||||
| fn get_sends(headers: Headers, conn: DbConn) -> Json<Value> { | ||||
| async fn get_sends(headers: Headers, conn: DbConn) -> Json<Value> { | ||||
|     let sends = Send::find_by_user(&headers.user.uuid, &conn); | ||||
|     let sends_json: Vec<Value> = sends.iter().map(|s| s.to_json()).collect(); | ||||
|     let sends_json: Vec<Value> = sends.await.iter().map(|s| s.to_json()).collect(); | ||||
|  | ||||
|     Json(json!({ | ||||
|       "Data": sends_json, | ||||
| @@ -147,8 +147,8 @@ fn get_sends(headers: Headers, conn: DbConn) -> Json<Value> { | ||||
| } | ||||
|  | ||||
| #[get("/sends/<uuid>")] | ||||
| fn get_send(uuid: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let send = match Send::find_by_uuid(&uuid, &conn) { | ||||
| async fn get_send(uuid: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let send = match Send::find_by_uuid(&uuid, &conn).await { | ||||
|         Some(send) => send, | ||||
|         None => err!("Send not found"), | ||||
|     }; | ||||
| @@ -161,19 +161,19 @@ fn get_send(uuid: String, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| } | ||||
|  | ||||
| #[post("/sends", data = "<data>")] | ||||
| fn post_send(data: JsonUpcase<SendData>, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { | ||||
|     enforce_disable_send_policy(&headers, &conn)?; | ||||
| async fn post_send(data: JsonUpcase<SendData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult { | ||||
|     enforce_disable_send_policy(&headers, &conn).await?; | ||||
|  | ||||
|     let data: SendData = data.into_inner().data; | ||||
|     enforce_disable_hide_email_policy(&data, &headers, &conn)?; | ||||
|     enforce_disable_hide_email_policy(&data, &headers, &conn).await?; | ||||
|  | ||||
|     if data.Type == SendType::File as i32 { | ||||
|         err!("File sends should use /api/sends/file") | ||||
|     } | ||||
|  | ||||
|     let mut send = create_send(data, headers.user.uuid)?; | ||||
|     send.save(&conn)?; | ||||
|     nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&conn)); | ||||
|     let mut send = create_send(data, headers.user.uuid).await?; | ||||
|     send.save(&conn).await?; | ||||
|     nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&conn).await); | ||||
|  | ||||
|     Ok(Json(send.to_json())) | ||||
| } | ||||
| @@ -186,7 +186,7 @@ struct UploadData<'f> { | ||||
|  | ||||
| #[post("/sends/file", format = "multipart/form-data", data = "<data>")] | ||||
| async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult { | ||||
|     enforce_disable_send_policy(&headers, &conn)?; | ||||
|     enforce_disable_send_policy(&headers, &conn).await?; | ||||
|  | ||||
|     let UploadData { | ||||
|         model, | ||||
| @@ -194,7 +194,7 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbCo | ||||
|     } = data.into_inner(); | ||||
|     let model = model.into_inner().data; | ||||
|  | ||||
|     enforce_disable_hide_email_policy(&model, &headers, &conn)?; | ||||
|     enforce_disable_hide_email_policy(&model, &headers, &conn).await?; | ||||
|  | ||||
|     // Get the file length and add an extra 5% to avoid issues | ||||
|     const SIZE_525_MB: u64 = 550_502_400; | ||||
| @@ -202,7 +202,7 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbCo | ||||
|     let size_limit = match CONFIG.user_attachment_limit() { | ||||
|         Some(0) => err!("File uploads are disabled"), | ||||
|         Some(limit_kb) => { | ||||
|             let left = (limit_kb * 1024) - Attachment::size_by_user(&headers.user.uuid, &conn); | ||||
|             let left = (limit_kb * 1024) - Attachment::size_by_user(&headers.user.uuid, &conn).await; | ||||
|             if left <= 0 { | ||||
|                 err!("Attachment storage limit reached! Delete some attachments to free up space") | ||||
|             } | ||||
| @@ -211,7 +211,7 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbCo | ||||
|         None => SIZE_525_MB, | ||||
|     }; | ||||
|  | ||||
|     let mut send = create_send(model, headers.user.uuid)?; | ||||
|     let mut send = create_send(model, headers.user.uuid).await?; | ||||
|     if send.atype != SendType::File as i32 { | ||||
|         err!("Send content is not a file"); | ||||
|     } | ||||
| @@ -236,8 +236,8 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbCo | ||||
|     send.data = serde_json::to_string(&data_value)?; | ||||
|  | ||||
|     // Save the changes in the database | ||||
|     send.save(&conn)?; | ||||
|     nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&conn)); | ||||
|     send.save(&conn).await?; | ||||
|     nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&conn).await); | ||||
|  | ||||
|     Ok(Json(send.to_json())) | ||||
| } | ||||
| @@ -249,8 +249,8 @@ pub struct SendAccessData { | ||||
| } | ||||
|  | ||||
| #[post("/sends/access/<access_id>", data = "<data>")] | ||||
| fn post_access(access_id: String, data: JsonUpcase<SendAccessData>, conn: DbConn, ip: ClientIp) -> JsonResult { | ||||
|     let mut send = match Send::find_by_access_id(&access_id, &conn) { | ||||
| async fn post_access(access_id: String, data: JsonUpcase<SendAccessData>, conn: DbConn, ip: ClientIp) -> JsonResult { | ||||
|     let mut send = match Send::find_by_access_id(&access_id, &conn).await { | ||||
|         Some(s) => s, | ||||
|         None => err_code!(SEND_INACCESSIBLE_MSG, 404), | ||||
|     }; | ||||
| @@ -288,20 +288,20 @@ fn post_access(access_id: String, data: JsonUpcase<SendAccessData>, conn: DbConn | ||||
|         send.access_count += 1; | ||||
|     } | ||||
|  | ||||
|     send.save(&conn)?; | ||||
|     send.save(&conn).await?; | ||||
|  | ||||
|     Ok(Json(send.to_json_access(&conn))) | ||||
|     Ok(Json(send.to_json_access(&conn).await)) | ||||
| } | ||||
|  | ||||
| #[post("/sends/<send_id>/access/file/<file_id>", data = "<data>")] | ||||
| fn post_access_file( | ||||
| async fn post_access_file( | ||||
|     send_id: String, | ||||
|     file_id: String, | ||||
|     data: JsonUpcase<SendAccessData>, | ||||
|     host: Host, | ||||
|     conn: DbConn, | ||||
| ) -> JsonResult { | ||||
|     let mut send = match Send::find_by_uuid(&send_id, &conn) { | ||||
|     let mut send = match Send::find_by_uuid(&send_id, &conn).await { | ||||
|         Some(s) => s, | ||||
|         None => err_code!(SEND_INACCESSIBLE_MSG, 404), | ||||
|     }; | ||||
| @@ -336,7 +336,7 @@ fn post_access_file( | ||||
|  | ||||
|     send.access_count += 1; | ||||
|  | ||||
|     send.save(&conn)?; | ||||
|     send.save(&conn).await?; | ||||
|  | ||||
|     let token_claims = crate::auth::generate_send_claims(&send_id, &file_id); | ||||
|     let token = crate::auth::encode_jwt(&token_claims); | ||||
| @@ -358,13 +358,19 @@ async fn download_send(send_id: SafeString, file_id: SafeString, t: String) -> O | ||||
| } | ||||
|  | ||||
| #[put("/sends/<id>", data = "<data>")] | ||||
| fn put_send(id: String, data: JsonUpcase<SendData>, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { | ||||
|     enforce_disable_send_policy(&headers, &conn)?; | ||||
| async fn put_send( | ||||
|     id: String, | ||||
|     data: JsonUpcase<SendData>, | ||||
|     headers: Headers, | ||||
|     conn: DbConn, | ||||
|     nt: Notify<'_>, | ||||
| ) -> JsonResult { | ||||
|     enforce_disable_send_policy(&headers, &conn).await?; | ||||
|  | ||||
|     let data: SendData = data.into_inner().data; | ||||
|     enforce_disable_hide_email_policy(&data, &headers, &conn)?; | ||||
|     enforce_disable_hide_email_policy(&data, &headers, &conn).await?; | ||||
|  | ||||
|     let mut send = match Send::find_by_uuid(&id, &conn) { | ||||
|     let mut send = match Send::find_by_uuid(&id, &conn).await { | ||||
|         Some(s) => s, | ||||
|         None => err!("Send not found"), | ||||
|     }; | ||||
| @@ -411,15 +417,15 @@ fn put_send(id: String, data: JsonUpcase<SendData>, headers: Headers, conn: DbCo | ||||
|         send.set_password(Some(&password)); | ||||
|     } | ||||
|  | ||||
|     send.save(&conn)?; | ||||
|     nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&conn)); | ||||
|     send.save(&conn).await?; | ||||
|     nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&conn).await); | ||||
|  | ||||
|     Ok(Json(send.to_json())) | ||||
| } | ||||
|  | ||||
| #[delete("/sends/<id>")] | ||||
| fn delete_send(id: String, headers: Headers, conn: DbConn, nt: Notify) -> EmptyResult { | ||||
|     let send = match Send::find_by_uuid(&id, &conn) { | ||||
| async fn delete_send(id: String, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult { | ||||
|     let send = match Send::find_by_uuid(&id, &conn).await { | ||||
|         Some(s) => s, | ||||
|         None => err!("Send not found"), | ||||
|     }; | ||||
| @@ -428,17 +434,17 @@ fn delete_send(id: String, headers: Headers, conn: DbConn, nt: Notify) -> EmptyR | ||||
|         err!("Send is not owned by user") | ||||
|     } | ||||
|  | ||||
|     send.delete(&conn)?; | ||||
|     nt.send_send_update(UpdateType::SyncSendDelete, &send, &send.update_users_revision(&conn)); | ||||
|     send.delete(&conn).await?; | ||||
|     nt.send_send_update(UpdateType::SyncSendDelete, &send, &send.update_users_revision(&conn).await); | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| #[put("/sends/<id>/remove-password")] | ||||
| fn put_remove_password(id: String, headers: Headers, conn: DbConn, nt: Notify) -> JsonResult { | ||||
|     enforce_disable_send_policy(&headers, &conn)?; | ||||
| async fn put_remove_password(id: String, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult { | ||||
|     enforce_disable_send_policy(&headers, &conn).await?; | ||||
|  | ||||
|     let mut send = match Send::find_by_uuid(&id, &conn) { | ||||
|     let mut send = match Send::find_by_uuid(&id, &conn).await { | ||||
|         Some(s) => s, | ||||
|         None => err!("Send not found"), | ||||
|     }; | ||||
| @@ -448,8 +454,8 @@ fn put_remove_password(id: String, headers: Headers, conn: DbConn, nt: Notify) - | ||||
|     } | ||||
|  | ||||
|     send.set_password(None); | ||||
|     send.save(&conn)?; | ||||
|     nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&conn)); | ||||
|     send.save(&conn).await?; | ||||
|     nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&conn).await); | ||||
|  | ||||
|     Ok(Json(send.to_json())) | ||||
| } | ||||
|   | ||||
| @@ -21,7 +21,7 @@ pub fn routes() -> Vec<Route> { | ||||
| } | ||||
|  | ||||
| #[post("/two-factor/get-authenticator", data = "<data>")] | ||||
| fn generate_authenticator(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn generate_authenticator(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let data: PasswordData = data.into_inner().data; | ||||
|     let user = headers.user; | ||||
|  | ||||
| @@ -30,7 +30,7 @@ fn generate_authenticator(data: JsonUpcase<PasswordData>, headers: Headers, conn | ||||
|     } | ||||
|  | ||||
|     let type_ = TwoFactorType::Authenticator as i32; | ||||
|     let twofactor = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn); | ||||
|     let twofactor = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await; | ||||
|  | ||||
|     let (enabled, key) = match twofactor { | ||||
|         Some(tf) => (true, tf.data), | ||||
| @@ -53,7 +53,7 @@ struct EnableAuthenticatorData { | ||||
| } | ||||
|  | ||||
| #[post("/two-factor/authenticator", data = "<data>")] | ||||
| fn activate_authenticator( | ||||
| async fn activate_authenticator( | ||||
|     data: JsonUpcase<EnableAuthenticatorData>, | ||||
|     headers: Headers, | ||||
|     ip: ClientIp, | ||||
| @@ -81,9 +81,9 @@ fn activate_authenticator( | ||||
|     } | ||||
|  | ||||
|     // Validate the token provided with the key, and save new twofactor | ||||
|     validate_totp_code(&user.uuid, &token, &key.to_uppercase(), &ip, &conn)?; | ||||
|     validate_totp_code(&user.uuid, &token, &key.to_uppercase(), &ip, &conn).await?; | ||||
|  | ||||
|     _generate_recover_code(&mut user, &conn); | ||||
|     _generate_recover_code(&mut user, &conn).await; | ||||
|  | ||||
|     Ok(Json(json!({ | ||||
|         "Enabled": true, | ||||
| @@ -93,16 +93,16 @@ fn activate_authenticator( | ||||
| } | ||||
|  | ||||
| #[put("/two-factor/authenticator", data = "<data>")] | ||||
| fn activate_authenticator_put( | ||||
| async fn activate_authenticator_put( | ||||
|     data: JsonUpcase<EnableAuthenticatorData>, | ||||
|     headers: Headers, | ||||
|     ip: ClientIp, | ||||
|     conn: DbConn, | ||||
| ) -> JsonResult { | ||||
|     activate_authenticator(data, headers, ip, conn) | ||||
|     activate_authenticator(data, headers, ip, conn).await | ||||
| } | ||||
|  | ||||
| pub fn validate_totp_code_str( | ||||
| pub async fn validate_totp_code_str( | ||||
|     user_uuid: &str, | ||||
|     totp_code: &str, | ||||
|     secret: &str, | ||||
| @@ -113,10 +113,16 @@ pub fn validate_totp_code_str( | ||||
|         err!("TOTP code is not a number"); | ||||
|     } | ||||
|  | ||||
|     validate_totp_code(user_uuid, totp_code, secret, ip, conn) | ||||
|     validate_totp_code(user_uuid, totp_code, secret, ip, conn).await | ||||
| } | ||||
|  | ||||
| pub fn validate_totp_code(user_uuid: &str, totp_code: &str, secret: &str, ip: &ClientIp, conn: &DbConn) -> EmptyResult { | ||||
| pub async fn validate_totp_code( | ||||
|     user_uuid: &str, | ||||
|     totp_code: &str, | ||||
|     secret: &str, | ||||
|     ip: &ClientIp, | ||||
|     conn: &DbConn, | ||||
| ) -> EmptyResult { | ||||
|     use totp_lite::{totp_custom, Sha1}; | ||||
|  | ||||
|     let decoded_secret = match BASE32.decode(secret.as_bytes()) { | ||||
| @@ -124,10 +130,11 @@ pub fn validate_totp_code(user_uuid: &str, totp_code: &str, secret: &str, ip: &C | ||||
|         Err(_) => err!("Invalid TOTP secret"), | ||||
|     }; | ||||
|  | ||||
|     let mut twofactor = match TwoFactor::find_by_user_and_type(user_uuid, TwoFactorType::Authenticator as i32, conn) { | ||||
|         Some(tf) => tf, | ||||
|         _ => TwoFactor::new(user_uuid.to_string(), TwoFactorType::Authenticator, secret.to_string()), | ||||
|     }; | ||||
|     let mut twofactor = | ||||
|         match TwoFactor::find_by_user_and_type(user_uuid, TwoFactorType::Authenticator as i32, conn).await { | ||||
|             Some(tf) => tf, | ||||
|             _ => TwoFactor::new(user_uuid.to_string(), TwoFactorType::Authenticator, secret.to_string()), | ||||
|         }; | ||||
|  | ||||
|     // The amount of steps back and forward in time | ||||
|     // Also check if we need to disable time drifted TOTP codes. | ||||
| @@ -156,7 +163,7 @@ pub fn validate_totp_code(user_uuid: &str, totp_code: &str, secret: &str, ip: &C | ||||
|             // Save the last used time step so only totp time steps higher then this one are allowed. | ||||
|             // This will also save a newly created twofactor if the code is correct. | ||||
|             twofactor.last_used = time_step as i32; | ||||
|             twofactor.save(conn)?; | ||||
|             twofactor.save(conn).await?; | ||||
|             return Ok(()); | ||||
|         } else if generated == totp_code && time_step <= twofactor.last_used as i64 { | ||||
|             warn!("This TOTP or a TOTP code within {} steps back or forward has already been used!", steps); | ||||
|   | ||||
| @@ -89,14 +89,14 @@ impl DuoStatus { | ||||
| const DISABLED_MESSAGE_DEFAULT: &str = "<To use the global Duo keys, please leave these fields untouched>"; | ||||
|  | ||||
| #[post("/two-factor/get-duo", data = "<data>")] | ||||
| fn get_duo(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn get_duo(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let data: PasswordData = data.into_inner().data; | ||||
|  | ||||
|     if !headers.user.check_valid_password(&data.MasterPasswordHash) { | ||||
|         err!("Invalid password"); | ||||
|     } | ||||
|  | ||||
|     let data = get_user_duo_data(&headers.user.uuid, &conn); | ||||
|     let data = get_user_duo_data(&headers.user.uuid, &conn).await; | ||||
|  | ||||
|     let (enabled, data) = match data { | ||||
|         DuoStatus::Global(_) => (true, Some(DuoData::secret())), | ||||
| @@ -171,9 +171,9 @@ async fn activate_duo(data: JsonUpcase<EnableDuoData>, headers: Headers, conn: D | ||||
|  | ||||
|     let type_ = TwoFactorType::Duo; | ||||
|     let twofactor = TwoFactor::new(user.uuid.clone(), type_, data_str); | ||||
|     twofactor.save(&conn)?; | ||||
|     twofactor.save(&conn).await?; | ||||
|  | ||||
|     _generate_recover_code(&mut user, &conn); | ||||
|     _generate_recover_code(&mut user, &conn).await; | ||||
|  | ||||
|     Ok(Json(json!({ | ||||
|         "Enabled": true, | ||||
| @@ -223,11 +223,11 @@ const AUTH_PREFIX: &str = "AUTH"; | ||||
| const DUO_PREFIX: &str = "TX"; | ||||
| const APP_PREFIX: &str = "APP"; | ||||
|  | ||||
| fn get_user_duo_data(uuid: &str, conn: &DbConn) -> DuoStatus { | ||||
| async fn get_user_duo_data(uuid: &str, conn: &DbConn) -> DuoStatus { | ||||
|     let type_ = TwoFactorType::Duo as i32; | ||||
|  | ||||
|     // If the user doesn't have an entry, disabled | ||||
|     let twofactor = match TwoFactor::find_by_user_and_type(uuid, type_, conn) { | ||||
|     let twofactor = match TwoFactor::find_by_user_and_type(uuid, type_, conn).await { | ||||
|         Some(t) => t, | ||||
|         None => return DuoStatus::Disabled(DuoData::global().is_some()), | ||||
|     }; | ||||
| @@ -247,19 +247,20 @@ fn get_user_duo_data(uuid: &str, conn: &DbConn) -> DuoStatus { | ||||
| } | ||||
|  | ||||
| // let (ik, sk, ak, host) = get_duo_keys(); | ||||
| fn get_duo_keys_email(email: &str, conn: &DbConn) -> ApiResult<(String, String, String, String)> { | ||||
|     let data = User::find_by_mail(email, conn) | ||||
|         .and_then(|u| get_user_duo_data(&u.uuid, conn).data()) | ||||
|         .or_else(DuoData::global) | ||||
|         .map_res("Can't fetch Duo keys")?; | ||||
| async fn get_duo_keys_email(email: &str, conn: &DbConn) -> ApiResult<(String, String, String, String)> { | ||||
|     let data = match User::find_by_mail(email, conn).await { | ||||
|         Some(u) => get_user_duo_data(&u.uuid, conn).await.data(), | ||||
|         _ => DuoData::global(), | ||||
|     } | ||||
|     .map_res("Can't fetch Duo Keys")?; | ||||
|  | ||||
|     Ok((data.ik, data.sk, CONFIG.get_duo_akey(), data.host)) | ||||
| } | ||||
|  | ||||
| pub fn generate_duo_signature(email: &str, conn: &DbConn) -> ApiResult<(String, String)> { | ||||
| pub async fn generate_duo_signature(email: &str, conn: &DbConn) -> ApiResult<(String, String)> { | ||||
|     let now = Utc::now().timestamp(); | ||||
|  | ||||
|     let (ik, sk, ak, host) = get_duo_keys_email(email, conn)?; | ||||
|     let (ik, sk, ak, host) = get_duo_keys_email(email, conn).await?; | ||||
|  | ||||
|     let duo_sign = sign_duo_values(&sk, email, &ik, DUO_PREFIX, now + DUO_EXPIRE); | ||||
|     let app_sign = sign_duo_values(&ak, email, &ik, APP_PREFIX, now + APP_EXPIRE); | ||||
| @@ -274,7 +275,7 @@ fn sign_duo_values(key: &str, email: &str, ikey: &str, prefix: &str, expire: i64 | ||||
|     format!("{}|{}", cookie, crypto::hmac_sign(key, &cookie)) | ||||
| } | ||||
|  | ||||
| pub fn validate_duo_login(email: &str, response: &str, conn: &DbConn) -> EmptyResult { | ||||
| pub async fn validate_duo_login(email: &str, response: &str, conn: &DbConn) -> EmptyResult { | ||||
|     // email is as entered by the user, so it needs to be normalized before | ||||
|     // comparison with auth_user below. | ||||
|     let email = &email.to_lowercase(); | ||||
| @@ -289,7 +290,7 @@ pub fn validate_duo_login(email: &str, response: &str, conn: &DbConn) -> EmptyRe | ||||
|  | ||||
|     let now = Utc::now().timestamp(); | ||||
|  | ||||
|     let (ik, sk, ak, _host) = get_duo_keys_email(email, conn)?; | ||||
|     let (ik, sk, ak, _host) = get_duo_keys_email(email, conn).await?; | ||||
|  | ||||
|     let auth_user = parse_duo_values(&sk, auth_sig, &ik, AUTH_PREFIX, now)?; | ||||
|     let app_user = parse_duo_values(&ak, app_sig, &ik, APP_PREFIX, now)?; | ||||
|   | ||||
| @@ -28,13 +28,13 @@ struct SendEmailLoginData { | ||||
| /// User is trying to login and wants to use email 2FA. | ||||
| /// Does not require Bearer token | ||||
| #[post("/two-factor/send-email-login", data = "<data>")] // JsonResult | ||||
| fn send_email_login(data: JsonUpcase<SendEmailLoginData>, conn: DbConn) -> EmptyResult { | ||||
| async fn send_email_login(data: JsonUpcase<SendEmailLoginData>, conn: DbConn) -> EmptyResult { | ||||
|     let data: SendEmailLoginData = data.into_inner().data; | ||||
|  | ||||
|     use crate::db::models::User; | ||||
|  | ||||
|     // Get the user | ||||
|     let user = match User::find_by_mail(&data.Email, &conn) { | ||||
|     let user = match User::find_by_mail(&data.Email, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Username or password is incorrect. Try again."), | ||||
|     }; | ||||
| @@ -48,22 +48,23 @@ fn send_email_login(data: JsonUpcase<SendEmailLoginData>, conn: DbConn) -> Empty | ||||
|         err!("Email 2FA is disabled") | ||||
|     } | ||||
|  | ||||
|     send_token(&user.uuid, &conn)?; | ||||
|     send_token(&user.uuid, &conn).await?; | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| /// Generate the token, save the data for later verification and send email to user | ||||
| pub fn send_token(user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
| pub async fn send_token(user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|     let type_ = TwoFactorType::Email as i32; | ||||
|     let mut twofactor = TwoFactor::find_by_user_and_type(user_uuid, type_, conn).map_res("Two factor not found")?; | ||||
|     let mut twofactor = | ||||
|         TwoFactor::find_by_user_and_type(user_uuid, type_, conn).await.map_res("Two factor not found")?; | ||||
|  | ||||
|     let generated_token = crypto::generate_email_token(CONFIG.email_token_size()); | ||||
|  | ||||
|     let mut twofactor_data = EmailTokenData::from_json(&twofactor.data)?; | ||||
|     twofactor_data.set_token(generated_token); | ||||
|     twofactor.data = twofactor_data.to_json(); | ||||
|     twofactor.save(conn)?; | ||||
|     twofactor.save(conn).await?; | ||||
|  | ||||
|     mail::send_token(&twofactor_data.email, &twofactor_data.last_token.map_res("Token is empty")?)?; | ||||
|  | ||||
| @@ -72,7 +73,7 @@ pub fn send_token(user_uuid: &str, conn: &DbConn) -> EmptyResult { | ||||
|  | ||||
| /// When user clicks on Manage email 2FA show the user the related information | ||||
| #[post("/two-factor/get-email", data = "<data>")] | ||||
| fn get_email(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn get_email(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let data: PasswordData = data.into_inner().data; | ||||
|     let user = headers.user; | ||||
|  | ||||
| @@ -80,13 +81,14 @@ fn get_email(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> | ||||
|         err!("Invalid password"); | ||||
|     } | ||||
|  | ||||
|     let (enabled, mfa_email) = match TwoFactor::find_by_user_and_type(&user.uuid, TwoFactorType::Email as i32, &conn) { | ||||
|         Some(x) => { | ||||
|             let twofactor_data = EmailTokenData::from_json(&x.data)?; | ||||
|             (true, json!(twofactor_data.email)) | ||||
|         } | ||||
|         _ => (false, json!(null)), | ||||
|     }; | ||||
|     let (enabled, mfa_email) = | ||||
|         match TwoFactor::find_by_user_and_type(&user.uuid, TwoFactorType::Email as i32, &conn).await { | ||||
|             Some(x) => { | ||||
|                 let twofactor_data = EmailTokenData::from_json(&x.data)?; | ||||
|                 (true, json!(twofactor_data.email)) | ||||
|             } | ||||
|             _ => (false, json!(null)), | ||||
|         }; | ||||
|  | ||||
|     Ok(Json(json!({ | ||||
|         "Email": mfa_email, | ||||
| @@ -105,7 +107,7 @@ struct SendEmailData { | ||||
|  | ||||
| /// Send a verification email to the specified email address to check whether it exists/belongs to user. | ||||
| #[post("/two-factor/send-email", data = "<data>")] | ||||
| fn send_email(data: JsonUpcase<SendEmailData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
| async fn send_email(data: JsonUpcase<SendEmailData>, headers: Headers, conn: DbConn) -> EmptyResult { | ||||
|     let data: SendEmailData = data.into_inner().data; | ||||
|     let user = headers.user; | ||||
|  | ||||
| @@ -119,8 +121,8 @@ fn send_email(data: JsonUpcase<SendEmailData>, headers: Headers, conn: DbConn) - | ||||
|  | ||||
|     let type_ = TwoFactorType::Email as i32; | ||||
|  | ||||
|     if let Some(tf) = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn) { | ||||
|         tf.delete(&conn)?; | ||||
|     if let Some(tf) = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await { | ||||
|         tf.delete(&conn).await?; | ||||
|     } | ||||
|  | ||||
|     let generated_token = crypto::generate_email_token(CONFIG.email_token_size()); | ||||
| @@ -128,7 +130,7 @@ fn send_email(data: JsonUpcase<SendEmailData>, headers: Headers, conn: DbConn) - | ||||
|  | ||||
|     // Uses EmailVerificationChallenge as type to show that it's not verified yet. | ||||
|     let twofactor = TwoFactor::new(user.uuid, TwoFactorType::EmailVerificationChallenge, twofactor_data.to_json()); | ||||
|     twofactor.save(&conn)?; | ||||
|     twofactor.save(&conn).await?; | ||||
|  | ||||
|     mail::send_token(&twofactor_data.email, &twofactor_data.last_token.map_res("Token is empty")?)?; | ||||
|  | ||||
| @@ -145,7 +147,7 @@ struct EmailData { | ||||
|  | ||||
| /// Verify email belongs to user and can be used for 2FA email codes. | ||||
| #[put("/two-factor/email", data = "<data>")] | ||||
| fn email(data: JsonUpcase<EmailData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn email(data: JsonUpcase<EmailData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let data: EmailData = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
|  | ||||
| @@ -154,7 +156,8 @@ fn email(data: JsonUpcase<EmailData>, headers: Headers, conn: DbConn) -> JsonRes | ||||
|     } | ||||
|  | ||||
|     let type_ = TwoFactorType::EmailVerificationChallenge as i32; | ||||
|     let mut twofactor = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).map_res("Two factor not found")?; | ||||
|     let mut twofactor = | ||||
|         TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await.map_res("Two factor not found")?; | ||||
|  | ||||
|     let mut email_data = EmailTokenData::from_json(&twofactor.data)?; | ||||
|  | ||||
| @@ -170,9 +173,9 @@ fn email(data: JsonUpcase<EmailData>, headers: Headers, conn: DbConn) -> JsonRes | ||||
|     email_data.reset_token(); | ||||
|     twofactor.atype = TwoFactorType::Email as i32; | ||||
|     twofactor.data = email_data.to_json(); | ||||
|     twofactor.save(&conn)?; | ||||
|     twofactor.save(&conn).await?; | ||||
|  | ||||
|     _generate_recover_code(&mut user, &conn); | ||||
|     _generate_recover_code(&mut user, &conn).await; | ||||
|  | ||||
|     Ok(Json(json!({ | ||||
|         "Email": email_data.email, | ||||
| @@ -182,9 +185,10 @@ fn email(data: JsonUpcase<EmailData>, headers: Headers, conn: DbConn) -> JsonRes | ||||
| } | ||||
|  | ||||
| /// Validate the email code when used as TwoFactor token mechanism | ||||
| pub fn validate_email_code_str(user_uuid: &str, token: &str, data: &str, conn: &DbConn) -> EmptyResult { | ||||
| pub async fn validate_email_code_str(user_uuid: &str, token: &str, data: &str, conn: &DbConn) -> EmptyResult { | ||||
|     let mut email_data = EmailTokenData::from_json(data)?; | ||||
|     let mut twofactor = TwoFactor::find_by_user_and_type(user_uuid, TwoFactorType::Email as i32, conn) | ||||
|         .await | ||||
|         .map_res("Two factor not found")?; | ||||
|     let issued_token = match &email_data.last_token { | ||||
|         Some(t) => t, | ||||
| @@ -197,14 +201,14 @@ pub fn validate_email_code_str(user_uuid: &str, token: &str, data: &str, conn: & | ||||
|             email_data.reset_token(); | ||||
|         } | ||||
|         twofactor.data = email_data.to_json(); | ||||
|         twofactor.save(conn)?; | ||||
|         twofactor.save(conn).await?; | ||||
|  | ||||
|         err!("Token is invalid") | ||||
|     } | ||||
|  | ||||
|     email_data.reset_token(); | ||||
|     twofactor.data = email_data.to_json(); | ||||
|     twofactor.save(conn)?; | ||||
|     twofactor.save(conn).await?; | ||||
|  | ||||
|     let date = NaiveDateTime::from_timestamp(email_data.token_sent, 0); | ||||
|     let max_time = CONFIG.email_expiration_time() as i64; | ||||
|   | ||||
| @@ -33,8 +33,8 @@ pub fn routes() -> Vec<Route> { | ||||
| } | ||||
|  | ||||
| #[get("/two-factor")] | ||||
| fn get_twofactor(headers: Headers, conn: DbConn) -> Json<Value> { | ||||
|     let twofactors = TwoFactor::find_by_user(&headers.user.uuid, &conn); | ||||
| async fn get_twofactor(headers: Headers, conn: DbConn) -> Json<Value> { | ||||
|     let twofactors = TwoFactor::find_by_user(&headers.user.uuid, &conn).await; | ||||
|     let twofactors_json: Vec<Value> = twofactors.iter().map(TwoFactor::to_json_provider).collect(); | ||||
|  | ||||
|     Json(json!({ | ||||
| @@ -68,13 +68,13 @@ struct RecoverTwoFactor { | ||||
| } | ||||
|  | ||||
| #[post("/two-factor/recover", data = "<data>")] | ||||
| fn recover(data: JsonUpcase<RecoverTwoFactor>, conn: DbConn) -> JsonResult { | ||||
| async fn recover(data: JsonUpcase<RecoverTwoFactor>, conn: DbConn) -> JsonResult { | ||||
|     let data: RecoverTwoFactor = data.into_inner().data; | ||||
|  | ||||
|     use crate::db::models::User; | ||||
|  | ||||
|     // Get the user | ||||
|     let mut user = match User::find_by_mail(&data.Email, &conn) { | ||||
|     let mut user = match User::find_by_mail(&data.Email, &conn).await { | ||||
|         Some(user) => user, | ||||
|         None => err!("Username or password is incorrect. Try again."), | ||||
|     }; | ||||
| @@ -90,19 +90,19 @@ fn recover(data: JsonUpcase<RecoverTwoFactor>, conn: DbConn) -> JsonResult { | ||||
|     } | ||||
|  | ||||
|     // Remove all twofactors from the user | ||||
|     TwoFactor::delete_all_by_user(&user.uuid, &conn)?; | ||||
|     TwoFactor::delete_all_by_user(&user.uuid, &conn).await?; | ||||
|  | ||||
|     // Remove the recovery code, not needed without twofactors | ||||
|     user.totp_recover = None; | ||||
|     user.save(&conn)?; | ||||
|     user.save(&conn).await?; | ||||
|     Ok(Json(json!({}))) | ||||
| } | ||||
|  | ||||
| fn _generate_recover_code(user: &mut User, conn: &DbConn) { | ||||
| async fn _generate_recover_code(user: &mut User, conn: &DbConn) { | ||||
|     if user.totp_recover.is_none() { | ||||
|         let totp_recover = BASE32.encode(&crypto::get_random(vec![0u8; 20])); | ||||
|         user.totp_recover = Some(totp_recover); | ||||
|         user.save(conn).ok(); | ||||
|         user.save(conn).await.ok(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -114,7 +114,7 @@ struct DisableTwoFactorData { | ||||
| } | ||||
|  | ||||
| #[post("/two-factor/disable", data = "<data>")] | ||||
| fn disable_twofactor(data: JsonUpcase<DisableTwoFactorData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn disable_twofactor(data: JsonUpcase<DisableTwoFactorData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let data: DisableTwoFactorData = data.into_inner().data; | ||||
|     let password_hash = data.MasterPasswordHash; | ||||
|     let user = headers.user; | ||||
| @@ -125,23 +125,24 @@ fn disable_twofactor(data: JsonUpcase<DisableTwoFactorData>, headers: Headers, c | ||||
|  | ||||
|     let type_ = data.Type.into_i32()?; | ||||
|  | ||||
|     if let Some(twofactor) = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn) { | ||||
|         twofactor.delete(&conn)?; | ||||
|     if let Some(twofactor) = TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await { | ||||
|         twofactor.delete(&conn).await?; | ||||
|     } | ||||
|  | ||||
|     let twofactor_disabled = TwoFactor::find_by_user(&user.uuid, &conn).is_empty(); | ||||
|     let twofactor_disabled = TwoFactor::find_by_user(&user.uuid, &conn).await.is_empty(); | ||||
|  | ||||
|     if twofactor_disabled { | ||||
|         let policy_type = OrgPolicyType::TwoFactorAuthentication; | ||||
|         let org_list = UserOrganization::find_by_user_and_policy(&user.uuid, policy_type, &conn); | ||||
|  | ||||
|         for user_org in org_list.into_iter() { | ||||
|         for user_org in | ||||
|             UserOrganization::find_by_user_and_policy(&user.uuid, OrgPolicyType::TwoFactorAuthentication, &conn) | ||||
|                 .await | ||||
|                 .into_iter() | ||||
|         { | ||||
|             if user_org.atype < UserOrgType::Admin { | ||||
|                 if CONFIG.mail_enabled() { | ||||
|                     let org = Organization::find_by_uuid(&user_org.org_uuid, &conn).unwrap(); | ||||
|                     let org = Organization::find_by_uuid(&user_org.org_uuid, &conn).await.unwrap(); | ||||
|                     mail::send_2fa_removed_from_org(&user.email, &org.name)?; | ||||
|                 } | ||||
|                 user_org.delete(&conn)?; | ||||
|                 user_org.delete(&conn).await?; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| @@ -154,8 +155,8 @@ fn disable_twofactor(data: JsonUpcase<DisableTwoFactorData>, headers: Headers, c | ||||
| } | ||||
|  | ||||
| #[put("/two-factor/disable", data = "<data>")] | ||||
| fn disable_twofactor_put(data: JsonUpcase<DisableTwoFactorData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     disable_twofactor(data, headers, conn) | ||||
| async fn disable_twofactor_put(data: JsonUpcase<DisableTwoFactorData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     disable_twofactor(data, headers, conn).await | ||||
| } | ||||
|  | ||||
| pub async fn send_incomplete_2fa_notifications(pool: DbPool) { | ||||
| @@ -175,15 +176,16 @@ pub async fn send_incomplete_2fa_notifications(pool: DbPool) { | ||||
|  | ||||
|     let now = Utc::now().naive_utc(); | ||||
|     let time_limit = Duration::minutes(CONFIG.incomplete_2fa_time_limit()); | ||||
|     let incomplete_logins = TwoFactorIncomplete::find_logins_before(&(now - time_limit), &conn); | ||||
|     let time_before = now - time_limit; | ||||
|     let incomplete_logins = TwoFactorIncomplete::find_logins_before(&time_before, &conn).await; | ||||
|     for login in incomplete_logins { | ||||
|         let user = User::find_by_uuid(&login.user_uuid, &conn).expect("User not found"); | ||||
|         let user = User::find_by_uuid(&login.user_uuid, &conn).await.expect("User not found"); | ||||
|         info!( | ||||
|             "User {} did not complete a 2FA login within the configured time limit. IP: {}", | ||||
|             user.email, login.ip_address | ||||
|         ); | ||||
|         mail::send_incomplete_2fa_login(&user.email, &login.ip_address, &login.login_time, &login.device_name) | ||||
|             .expect("Error sending incomplete 2FA email"); | ||||
|         login.delete(&conn).expect("Error deleting incomplete 2FA record"); | ||||
|         login.delete(&conn).await.expect("Error deleting incomplete 2FA record"); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -32,7 +32,7 @@ pub fn routes() -> Vec<Route> { | ||||
| } | ||||
|  | ||||
| #[post("/two-factor/get-u2f", data = "<data>")] | ||||
| fn generate_u2f(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn generate_u2f(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     if !CONFIG.domain_set() { | ||||
|         err!("`DOMAIN` environment variable is not set. U2F disabled") | ||||
|     } | ||||
| @@ -42,7 +42,7 @@ fn generate_u2f(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) | ||||
|         err!("Invalid password"); | ||||
|     } | ||||
|  | ||||
|     let (enabled, keys) = get_u2f_registrations(&headers.user.uuid, &conn)?; | ||||
|     let (enabled, keys) = get_u2f_registrations(&headers.user.uuid, &conn).await?; | ||||
|     let keys_json: Vec<Value> = keys.iter().map(U2FRegistration::to_json).collect(); | ||||
|  | ||||
|     Ok(Json(json!({ | ||||
| @@ -53,7 +53,7 @@ fn generate_u2f(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) | ||||
| } | ||||
|  | ||||
| #[post("/two-factor/get-u2f-challenge", data = "<data>")] | ||||
| fn generate_u2f_challenge(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn generate_u2f_challenge(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let data: PasswordData = data.into_inner().data; | ||||
|  | ||||
|     if !headers.user.check_valid_password(&data.MasterPasswordHash) { | ||||
| @@ -61,7 +61,7 @@ fn generate_u2f_challenge(data: JsonUpcase<PasswordData>, headers: Headers, conn | ||||
|     } | ||||
|  | ||||
|     let _type = TwoFactorType::U2fRegisterChallenge; | ||||
|     let challenge = _create_u2f_challenge(&headers.user.uuid, _type, &conn).challenge; | ||||
|     let challenge = _create_u2f_challenge(&headers.user.uuid, _type, &conn).await.challenge; | ||||
|  | ||||
|     Ok(Json(json!({ | ||||
|         "UserId": headers.user.uuid, | ||||
| @@ -137,7 +137,7 @@ impl From<RegisterResponseCopy> for RegisterResponse { | ||||
| } | ||||
|  | ||||
| #[post("/two-factor/u2f", data = "<data>")] | ||||
| fn activate_u2f(data: JsonUpcase<EnableU2FData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn activate_u2f(data: JsonUpcase<EnableU2FData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let data: EnableU2FData = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
|  | ||||
| @@ -146,13 +146,13 @@ fn activate_u2f(data: JsonUpcase<EnableU2FData>, headers: Headers, conn: DbConn) | ||||
|     } | ||||
|  | ||||
|     let tf_type = TwoFactorType::U2fRegisterChallenge as i32; | ||||
|     let tf_challenge = match TwoFactor::find_by_user_and_type(&user.uuid, tf_type, &conn) { | ||||
|     let tf_challenge = match TwoFactor::find_by_user_and_type(&user.uuid, tf_type, &conn).await { | ||||
|         Some(c) => c, | ||||
|         None => err!("Can't recover challenge"), | ||||
|     }; | ||||
|  | ||||
|     let challenge: Challenge = serde_json::from_str(&tf_challenge.data)?; | ||||
|     tf_challenge.delete(&conn)?; | ||||
|     tf_challenge.delete(&conn).await?; | ||||
|  | ||||
|     let response: RegisterResponseCopy = serde_json::from_str(&data.DeviceResponse)?; | ||||
|  | ||||
| @@ -172,13 +172,13 @@ fn activate_u2f(data: JsonUpcase<EnableU2FData>, headers: Headers, conn: DbConn) | ||||
|         migrated: None, | ||||
|     }; | ||||
|  | ||||
|     let mut regs = get_u2f_registrations(&user.uuid, &conn)?.1; | ||||
|     let mut regs = get_u2f_registrations(&user.uuid, &conn).await?.1; | ||||
|  | ||||
|     // TODO: Check that there is no repeat Id | ||||
|     regs.push(full_registration); | ||||
|     save_u2f_registrations(&user.uuid, ®s, &conn)?; | ||||
|     save_u2f_registrations(&user.uuid, ®s, &conn).await?; | ||||
|  | ||||
|     _generate_recover_code(&mut user, &conn); | ||||
|     _generate_recover_code(&mut user, &conn).await; | ||||
|  | ||||
|     let keys_json: Vec<Value> = regs.iter().map(U2FRegistration::to_json).collect(); | ||||
|     Ok(Json(json!({ | ||||
| @@ -189,8 +189,8 @@ fn activate_u2f(data: JsonUpcase<EnableU2FData>, headers: Headers, conn: DbConn) | ||||
| } | ||||
|  | ||||
| #[put("/two-factor/u2f", data = "<data>")] | ||||
| fn activate_u2f_put(data: JsonUpcase<EnableU2FData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     activate_u2f(data, headers, conn) | ||||
| async fn activate_u2f_put(data: JsonUpcase<EnableU2FData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     activate_u2f(data, headers, conn).await | ||||
| } | ||||
|  | ||||
| #[derive(Deserialize, Debug)] | ||||
| @@ -201,7 +201,7 @@ struct DeleteU2FData { | ||||
| } | ||||
|  | ||||
| #[delete("/two-factor/u2f", data = "<data>")] | ||||
| fn delete_u2f(data: JsonUpcase<DeleteU2FData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn delete_u2f(data: JsonUpcase<DeleteU2FData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let data: DeleteU2FData = data.into_inner().data; | ||||
|  | ||||
|     let id = data.Id.into_i32()?; | ||||
| @@ -211,7 +211,7 @@ fn delete_u2f(data: JsonUpcase<DeleteU2FData>, headers: Headers, conn: DbConn) - | ||||
|     } | ||||
|  | ||||
|     let type_ = TwoFactorType::U2f as i32; | ||||
|     let mut tf = match TwoFactor::find_by_user_and_type(&headers.user.uuid, type_, &conn) { | ||||
|     let mut tf = match TwoFactor::find_by_user_and_type(&headers.user.uuid, type_, &conn).await { | ||||
|         Some(tf) => tf, | ||||
|         None => err!("U2F data not found!"), | ||||
|     }; | ||||
| @@ -226,7 +226,7 @@ fn delete_u2f(data: JsonUpcase<DeleteU2FData>, headers: Headers, conn: DbConn) - | ||||
|     let new_data_str = serde_json::to_string(&data)?; | ||||
|  | ||||
|     tf.data = new_data_str; | ||||
|     tf.save(&conn)?; | ||||
|     tf.save(&conn).await?; | ||||
|  | ||||
|     let keys_json: Vec<Value> = data.iter().map(U2FRegistration::to_json).collect(); | ||||
|  | ||||
| @@ -237,23 +237,24 @@ fn delete_u2f(data: JsonUpcase<DeleteU2FData>, headers: Headers, conn: DbConn) - | ||||
|     }))) | ||||
| } | ||||
|  | ||||
| fn _create_u2f_challenge(user_uuid: &str, type_: TwoFactorType, conn: &DbConn) -> Challenge { | ||||
| async fn _create_u2f_challenge(user_uuid: &str, type_: TwoFactorType, conn: &DbConn) -> Challenge { | ||||
|     let challenge = U2F.generate_challenge().unwrap(); | ||||
|  | ||||
|     TwoFactor::new(user_uuid.into(), type_, serde_json::to_string(&challenge).unwrap()) | ||||
|         .save(conn) | ||||
|         .await | ||||
|         .expect("Error saving challenge"); | ||||
|  | ||||
|     challenge | ||||
| } | ||||
|  | ||||
| fn save_u2f_registrations(user_uuid: &str, regs: &[U2FRegistration], conn: &DbConn) -> EmptyResult { | ||||
|     TwoFactor::new(user_uuid.into(), TwoFactorType::U2f, serde_json::to_string(regs)?).save(conn) | ||||
| async fn save_u2f_registrations(user_uuid: &str, regs: &[U2FRegistration], conn: &DbConn) -> EmptyResult { | ||||
|     TwoFactor::new(user_uuid.into(), TwoFactorType::U2f, serde_json::to_string(regs)?).save(conn).await | ||||
| } | ||||
|  | ||||
| fn get_u2f_registrations(user_uuid: &str, conn: &DbConn) -> Result<(bool, Vec<U2FRegistration>), Error> { | ||||
| async fn get_u2f_registrations(user_uuid: &str, conn: &DbConn) -> Result<(bool, Vec<U2FRegistration>), Error> { | ||||
|     let type_ = TwoFactorType::U2f as i32; | ||||
|     let (enabled, regs) = match TwoFactor::find_by_user_and_type(user_uuid, type_, conn) { | ||||
|     let (enabled, regs) = match TwoFactor::find_by_user_and_type(user_uuid, type_, conn).await { | ||||
|         Some(tf) => (tf.enabled, tf.data), | ||||
|         None => return Ok((false, Vec::new())), // If no data, return empty list | ||||
|     }; | ||||
| @@ -279,7 +280,7 @@ fn get_u2f_registrations(user_uuid: &str, conn: &DbConn) -> Result<(bool, Vec<U2 | ||||
|             }]; | ||||
|  | ||||
|             // Save new format | ||||
|             save_u2f_registrations(user_uuid, &new_regs, conn)?; | ||||
|             save_u2f_registrations(user_uuid, &new_regs, conn).await?; | ||||
|  | ||||
|             new_regs | ||||
|         } | ||||
| @@ -297,10 +298,10 @@ fn _old_parse_registrations(registations: &str) -> Vec<Registration> { | ||||
|     regs.into_iter().map(|r| serde_json::from_value(r).unwrap()).map(|Helper(r)| r).collect() | ||||
| } | ||||
|  | ||||
| pub fn generate_u2f_login(user_uuid: &str, conn: &DbConn) -> ApiResult<U2fSignRequest> { | ||||
|     let challenge = _create_u2f_challenge(user_uuid, TwoFactorType::U2fLoginChallenge, conn); | ||||
| pub async fn generate_u2f_login(user_uuid: &str, conn: &DbConn) -> ApiResult<U2fSignRequest> { | ||||
|     let challenge = _create_u2f_challenge(user_uuid, TwoFactorType::U2fLoginChallenge, conn).await; | ||||
|  | ||||
|     let registrations: Vec<_> = get_u2f_registrations(user_uuid, conn)?.1.into_iter().map(|r| r.reg).collect(); | ||||
|     let registrations: Vec<_> = get_u2f_registrations(user_uuid, conn).await?.1.into_iter().map(|r| r.reg).collect(); | ||||
|  | ||||
|     if registrations.is_empty() { | ||||
|         err!("No U2F devices registered") | ||||
| @@ -309,20 +310,20 @@ pub fn generate_u2f_login(user_uuid: &str, conn: &DbConn) -> ApiResult<U2fSignRe | ||||
|     Ok(U2F.sign_request(challenge, registrations)) | ||||
| } | ||||
|  | ||||
| pub fn validate_u2f_login(user_uuid: &str, response: &str, conn: &DbConn) -> EmptyResult { | ||||
| pub async fn validate_u2f_login(user_uuid: &str, response: &str, conn: &DbConn) -> EmptyResult { | ||||
|     let challenge_type = TwoFactorType::U2fLoginChallenge as i32; | ||||
|     let tf_challenge = TwoFactor::find_by_user_and_type(user_uuid, challenge_type, conn); | ||||
|     let tf_challenge = TwoFactor::find_by_user_and_type(user_uuid, challenge_type, conn).await; | ||||
|  | ||||
|     let challenge = match tf_challenge { | ||||
|         Some(tf_challenge) => { | ||||
|             let challenge: Challenge = serde_json::from_str(&tf_challenge.data)?; | ||||
|             tf_challenge.delete(conn)?; | ||||
|             tf_challenge.delete(conn).await?; | ||||
|             challenge | ||||
|         } | ||||
|         None => err!("Can't recover login challenge"), | ||||
|     }; | ||||
|     let response: SignResponse = serde_json::from_str(response)?; | ||||
|     let mut registrations = get_u2f_registrations(user_uuid, conn)?.1; | ||||
|     let mut registrations = get_u2f_registrations(user_uuid, conn).await?.1; | ||||
|     if registrations.is_empty() { | ||||
|         err!("No U2F devices registered") | ||||
|     } | ||||
| @@ -332,13 +333,13 @@ pub fn validate_u2f_login(user_uuid: &str, response: &str, conn: &DbConn) -> Emp | ||||
|         match response { | ||||
|             Ok(new_counter) => { | ||||
|                 reg.counter = new_counter; | ||||
|                 save_u2f_registrations(user_uuid, ®istrations, conn)?; | ||||
|                 save_u2f_registrations(user_uuid, ®istrations, conn).await?; | ||||
|  | ||||
|                 return Ok(()); | ||||
|             } | ||||
|             Err(u2f::u2ferror::U2fError::CounterTooLow) => { | ||||
|                 reg.compromised = true; | ||||
|                 save_u2f_registrations(user_uuid, ®istrations, conn)?; | ||||
|                 save_u2f_registrations(user_uuid, ®istrations, conn).await?; | ||||
|  | ||||
|                 err!("This device might be compromised!"); | ||||
|             } | ||||
|   | ||||
| @@ -80,7 +80,7 @@ impl WebauthnRegistration { | ||||
| } | ||||
|  | ||||
| #[post("/two-factor/get-webauthn", data = "<data>")] | ||||
| fn get_webauthn(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn get_webauthn(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     if !CONFIG.domain_set() { | ||||
|         err!("`DOMAIN` environment variable is not set. Webauthn disabled") | ||||
|     } | ||||
| @@ -89,7 +89,7 @@ fn get_webauthn(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) | ||||
|         err!("Invalid password"); | ||||
|     } | ||||
|  | ||||
|     let (enabled, registrations) = get_webauthn_registrations(&headers.user.uuid, &conn)?; | ||||
|     let (enabled, registrations) = get_webauthn_registrations(&headers.user.uuid, &conn).await?; | ||||
|     let registrations_json: Vec<Value> = registrations.iter().map(WebauthnRegistration::to_json).collect(); | ||||
|  | ||||
|     Ok(Json(json!({ | ||||
| @@ -100,12 +100,13 @@ fn get_webauthn(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) | ||||
| } | ||||
|  | ||||
| #[post("/two-factor/get-webauthn-challenge", data = "<data>")] | ||||
| fn generate_webauthn_challenge(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn generate_webauthn_challenge(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     if !headers.user.check_valid_password(&data.data.MasterPasswordHash) { | ||||
|         err!("Invalid password"); | ||||
|     } | ||||
|  | ||||
|     let registrations = get_webauthn_registrations(&headers.user.uuid, &conn)? | ||||
|     let registrations = get_webauthn_registrations(&headers.user.uuid, &conn) | ||||
|         .await? | ||||
|         .1 | ||||
|         .into_iter() | ||||
|         .map(|r| r.credential.cred_id) // We return the credentialIds to the clients to avoid double registering | ||||
| @@ -121,7 +122,7 @@ fn generate_webauthn_challenge(data: JsonUpcase<PasswordData>, headers: Headers, | ||||
|     )?; | ||||
|  | ||||
|     let type_ = TwoFactorType::WebauthnRegisterChallenge; | ||||
|     TwoFactor::new(headers.user.uuid, type_, serde_json::to_string(&state)?).save(&conn)?; | ||||
|     TwoFactor::new(headers.user.uuid, type_, serde_json::to_string(&state)?).save(&conn).await?; | ||||
|  | ||||
|     let mut challenge_value = serde_json::to_value(challenge.public_key)?; | ||||
|     challenge_value["status"] = "ok".into(); | ||||
| @@ -218,7 +219,7 @@ impl From<PublicKeyCredentialCopy> for PublicKeyCredential { | ||||
| } | ||||
|  | ||||
| #[post("/two-factor/webauthn", data = "<data>")] | ||||
| fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let data: EnableWebauthnData = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
|  | ||||
| @@ -228,10 +229,10 @@ fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Headers, con | ||||
|  | ||||
|     // Retrieve and delete the saved challenge state | ||||
|     let type_ = TwoFactorType::WebauthnRegisterChallenge as i32; | ||||
|     let state = match TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn) { | ||||
|     let state = match TwoFactor::find_by_user_and_type(&user.uuid, type_, &conn).await { | ||||
|         Some(tf) => { | ||||
|             let state: RegistrationState = serde_json::from_str(&tf.data)?; | ||||
|             tf.delete(&conn)?; | ||||
|             tf.delete(&conn).await?; | ||||
|             state | ||||
|         } | ||||
|         None => err!("Can't recover challenge"), | ||||
| @@ -241,7 +242,7 @@ fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Headers, con | ||||
|     let (credential, _data) = | ||||
|         WebauthnConfig::load().register_credential(&data.DeviceResponse.into(), &state, |_| Ok(false))?; | ||||
|  | ||||
|     let mut registrations: Vec<_> = get_webauthn_registrations(&user.uuid, &conn)?.1; | ||||
|     let mut registrations: Vec<_> = get_webauthn_registrations(&user.uuid, &conn).await?.1; | ||||
|     // TODO: Check for repeated ID's | ||||
|     registrations.push(WebauthnRegistration { | ||||
|         id: data.Id.into_i32()?, | ||||
| @@ -252,8 +253,10 @@ fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Headers, con | ||||
|     }); | ||||
|  | ||||
|     // Save the registrations and return them | ||||
|     TwoFactor::new(user.uuid.clone(), TwoFactorType::Webauthn, serde_json::to_string(®istrations)?).save(&conn)?; | ||||
|     _generate_recover_code(&mut user, &conn); | ||||
|     TwoFactor::new(user.uuid.clone(), TwoFactorType::Webauthn, serde_json::to_string(®istrations)?) | ||||
|         .save(&conn) | ||||
|         .await?; | ||||
|     _generate_recover_code(&mut user, &conn).await; | ||||
|  | ||||
|     let keys_json: Vec<Value> = registrations.iter().map(WebauthnRegistration::to_json).collect(); | ||||
|     Ok(Json(json!({ | ||||
| @@ -264,8 +267,8 @@ fn activate_webauthn(data: JsonUpcase<EnableWebauthnData>, headers: Headers, con | ||||
| } | ||||
|  | ||||
| #[put("/two-factor/webauthn", data = "<data>")] | ||||
| fn activate_webauthn_put(data: JsonUpcase<EnableWebauthnData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     activate_webauthn(data, headers, conn) | ||||
| async fn activate_webauthn_put(data: JsonUpcase<EnableWebauthnData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     activate_webauthn(data, headers, conn).await | ||||
| } | ||||
|  | ||||
| #[derive(Deserialize, Debug)] | ||||
| @@ -276,13 +279,14 @@ struct DeleteU2FData { | ||||
| } | ||||
|  | ||||
| #[delete("/two-factor/webauthn", data = "<data>")] | ||||
| fn delete_webauthn(data: JsonUpcase<DeleteU2FData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn delete_webauthn(data: JsonUpcase<DeleteU2FData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let id = data.data.Id.into_i32()?; | ||||
|     if !headers.user.check_valid_password(&data.data.MasterPasswordHash) { | ||||
|         err!("Invalid password"); | ||||
|     } | ||||
|  | ||||
|     let mut tf = match TwoFactor::find_by_user_and_type(&headers.user.uuid, TwoFactorType::Webauthn as i32, &conn) { | ||||
|     let mut tf = match TwoFactor::find_by_user_and_type(&headers.user.uuid, TwoFactorType::Webauthn as i32, &conn).await | ||||
|     { | ||||
|         Some(tf) => tf, | ||||
|         None => err!("Webauthn data not found!"), | ||||
|     }; | ||||
| @@ -296,11 +300,12 @@ fn delete_webauthn(data: JsonUpcase<DeleteU2FData>, headers: Headers, conn: DbCo | ||||
|  | ||||
|     let removed_item = data.remove(item_pos); | ||||
|     tf.data = serde_json::to_string(&data)?; | ||||
|     tf.save(&conn)?; | ||||
|     tf.save(&conn).await?; | ||||
|     drop(tf); | ||||
|  | ||||
|     // If entry is migrated from u2f, delete the u2f entry as well | ||||
|     if let Some(mut u2f) = TwoFactor::find_by_user_and_type(&headers.user.uuid, TwoFactorType::U2f as i32, &conn) { | ||||
|     if let Some(mut u2f) = TwoFactor::find_by_user_and_type(&headers.user.uuid, TwoFactorType::U2f as i32, &conn).await | ||||
|     { | ||||
|         use crate::api::core::two_factor::u2f::U2FRegistration; | ||||
|         let mut data: Vec<U2FRegistration> = match serde_json::from_str(&u2f.data) { | ||||
|             Ok(d) => d, | ||||
| @@ -311,7 +316,7 @@ fn delete_webauthn(data: JsonUpcase<DeleteU2FData>, headers: Headers, conn: DbCo | ||||
|         let new_data_str = serde_json::to_string(&data)?; | ||||
|  | ||||
|         u2f.data = new_data_str; | ||||
|         u2f.save(&conn)?; | ||||
|         u2f.save(&conn).await?; | ||||
|     } | ||||
|  | ||||
|     let keys_json: Vec<Value> = data.iter().map(WebauthnRegistration::to_json).collect(); | ||||
| @@ -323,18 +328,21 @@ fn delete_webauthn(data: JsonUpcase<DeleteU2FData>, headers: Headers, conn: DbCo | ||||
|     }))) | ||||
| } | ||||
|  | ||||
| pub fn get_webauthn_registrations(user_uuid: &str, conn: &DbConn) -> Result<(bool, Vec<WebauthnRegistration>), Error> { | ||||
| pub async fn get_webauthn_registrations( | ||||
|     user_uuid: &str, | ||||
|     conn: &DbConn, | ||||
| ) -> Result<(bool, Vec<WebauthnRegistration>), Error> { | ||||
|     let type_ = TwoFactorType::Webauthn as i32; | ||||
|     match TwoFactor::find_by_user_and_type(user_uuid, type_, conn) { | ||||
|     match TwoFactor::find_by_user_and_type(user_uuid, type_, conn).await { | ||||
|         Some(tf) => Ok((tf.enabled, serde_json::from_str(&tf.data)?)), | ||||
|         None => Ok((false, Vec::new())), // If no data, return empty list | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub fn generate_webauthn_login(user_uuid: &str, conn: &DbConn) -> JsonResult { | ||||
| pub async fn generate_webauthn_login(user_uuid: &str, conn: &DbConn) -> JsonResult { | ||||
|     // Load saved credentials | ||||
|     let creds: Vec<Credential> = | ||||
|         get_webauthn_registrations(user_uuid, conn)?.1.into_iter().map(|r| r.credential).collect(); | ||||
|         get_webauthn_registrations(user_uuid, conn).await?.1.into_iter().map(|r| r.credential).collect(); | ||||
|  | ||||
|     if creds.is_empty() { | ||||
|         err!("No Webauthn devices registered") | ||||
| @@ -346,18 +354,19 @@ pub fn generate_webauthn_login(user_uuid: &str, conn: &DbConn) -> JsonResult { | ||||
|  | ||||
|     // Save the challenge state for later validation | ||||
|     TwoFactor::new(user_uuid.into(), TwoFactorType::WebauthnLoginChallenge, serde_json::to_string(&state)?) | ||||
|         .save(conn)?; | ||||
|         .save(conn) | ||||
|         .await?; | ||||
|  | ||||
|     // Return challenge to the clients | ||||
|     Ok(Json(serde_json::to_value(response.public_key)?)) | ||||
| } | ||||
|  | ||||
| pub fn validate_webauthn_login(user_uuid: &str, response: &str, conn: &DbConn) -> EmptyResult { | ||||
| pub async fn validate_webauthn_login(user_uuid: &str, response: &str, conn: &DbConn) -> EmptyResult { | ||||
|     let type_ = TwoFactorType::WebauthnLoginChallenge as i32; | ||||
|     let state = match TwoFactor::find_by_user_and_type(user_uuid, type_, conn) { | ||||
|     let state = match TwoFactor::find_by_user_and_type(user_uuid, type_, conn).await { | ||||
|         Some(tf) => { | ||||
|             let state: AuthenticationState = serde_json::from_str(&tf.data)?; | ||||
|             tf.delete(conn)?; | ||||
|             tf.delete(conn).await?; | ||||
|             state | ||||
|         } | ||||
|         None => err!("Can't recover login challenge"), | ||||
| @@ -366,7 +375,7 @@ pub fn validate_webauthn_login(user_uuid: &str, response: &str, conn: &DbConn) - | ||||
|     let rsp: crate::util::UpCase<PublicKeyCredentialCopy> = serde_json::from_str(response)?; | ||||
|     let rsp: PublicKeyCredential = rsp.data.into(); | ||||
|  | ||||
|     let mut registrations = get_webauthn_registrations(user_uuid, conn)?.1; | ||||
|     let mut registrations = get_webauthn_registrations(user_uuid, conn).await?.1; | ||||
|  | ||||
|     // If the credential we received is migrated from U2F, enable the U2F compatibility | ||||
|     //let use_u2f = registrations.iter().any(|r| r.migrated && r.credential.cred_id == rsp.raw_id.0); | ||||
| @@ -377,7 +386,8 @@ pub fn validate_webauthn_login(user_uuid: &str, response: &str, conn: &DbConn) - | ||||
|             reg.credential.counter = auth_data.counter; | ||||
|  | ||||
|             TwoFactor::new(user_uuid.to_string(), TwoFactorType::Webauthn, serde_json::to_string(®istrations)?) | ||||
|                 .save(conn)?; | ||||
|                 .save(conn) | ||||
|                 .await?; | ||||
|             return Ok(()); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -78,7 +78,7 @@ fn verify_yubikey_otp(otp: String) -> EmptyResult { | ||||
| } | ||||
|  | ||||
| #[post("/two-factor/get-yubikey", data = "<data>")] | ||||
| fn generate_yubikey(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn generate_yubikey(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     // Make sure the credentials are set | ||||
|     get_yubico_credentials()?; | ||||
|  | ||||
| @@ -92,7 +92,7 @@ fn generate_yubikey(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbCo | ||||
|     let user_uuid = &user.uuid; | ||||
|     let yubikey_type = TwoFactorType::YubiKey as i32; | ||||
|  | ||||
|     let r = TwoFactor::find_by_user_and_type(user_uuid, yubikey_type, &conn); | ||||
|     let r = TwoFactor::find_by_user_and_type(user_uuid, yubikey_type, &conn).await; | ||||
|  | ||||
|     if let Some(r) = r { | ||||
|         let yubikey_metadata: YubikeyMetadata = serde_json::from_str(&r.data)?; | ||||
| @@ -113,7 +113,7 @@ fn generate_yubikey(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbCo | ||||
| } | ||||
|  | ||||
| #[post("/two-factor/yubikey", data = "<data>")] | ||||
| fn activate_yubikey(data: JsonUpcase<EnableYubikeyData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
| async fn activate_yubikey(data: JsonUpcase<EnableYubikeyData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     let data: EnableYubikeyData = data.into_inner().data; | ||||
|     let mut user = headers.user; | ||||
|  | ||||
| @@ -122,10 +122,11 @@ fn activate_yubikey(data: JsonUpcase<EnableYubikeyData>, headers: Headers, conn: | ||||
|     } | ||||
|  | ||||
|     // Check if we already have some data | ||||
|     let mut yubikey_data = match TwoFactor::find_by_user_and_type(&user.uuid, TwoFactorType::YubiKey as i32, &conn) { | ||||
|         Some(data) => data, | ||||
|         None => TwoFactor::new(user.uuid.clone(), TwoFactorType::YubiKey, String::new()), | ||||
|     }; | ||||
|     let mut yubikey_data = | ||||
|         match TwoFactor::find_by_user_and_type(&user.uuid, TwoFactorType::YubiKey as i32, &conn).await { | ||||
|             Some(data) => data, | ||||
|             None => TwoFactor::new(user.uuid.clone(), TwoFactorType::YubiKey, String::new()), | ||||
|         }; | ||||
|  | ||||
|     let yubikeys = parse_yubikeys(&data); | ||||
|  | ||||
| @@ -154,9 +155,9 @@ fn activate_yubikey(data: JsonUpcase<EnableYubikeyData>, headers: Headers, conn: | ||||
|     }; | ||||
|  | ||||
|     yubikey_data.data = serde_json::to_string(&yubikey_metadata).unwrap(); | ||||
|     yubikey_data.save(&conn)?; | ||||
|     yubikey_data.save(&conn).await?; | ||||
|  | ||||
|     _generate_recover_code(&mut user, &conn); | ||||
|     _generate_recover_code(&mut user, &conn).await; | ||||
|  | ||||
|     let mut result = jsonify_yubikeys(yubikey_metadata.Keys); | ||||
|  | ||||
| @@ -168,8 +169,8 @@ fn activate_yubikey(data: JsonUpcase<EnableYubikeyData>, headers: Headers, conn: | ||||
| } | ||||
|  | ||||
| #[put("/two-factor/yubikey", data = "<data>")] | ||||
| fn activate_yubikey_put(data: JsonUpcase<EnableYubikeyData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     activate_yubikey(data, headers, conn) | ||||
| async fn activate_yubikey_put(data: JsonUpcase<EnableYubikeyData>, headers: Headers, conn: DbConn) -> JsonResult { | ||||
|     activate_yubikey(data, headers, conn).await | ||||
| } | ||||
|  | ||||
| pub fn validate_yubikey_login(response: &str, twofactor_data: &str) -> EmptyResult { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user