vw_small

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

commit e4606431d1f79133fe7d708736b4083e9adb8a01
parent 5b7d7390b0cc26ebde96d1b8a835e384ee8deb47
Author: Bernd Schoolmann <mail@quexten.com>
Date:   Fri, 16 Jun 2023 23:34:16 +0200

Fix mobile push blocking requests and spamming push server

Diffstat:
Msrc/api/admin.rs | 6+++---
Msrc/api/core/accounts.rs | 10+++++-----
Msrc/api/core/organizations.rs | 2+-
Msrc/api/core/sends.rs | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Msrc/api/notifications.rs | 17++++++++++++-----
Msrc/api/push.rs | 115++++++++++++++++++++++++++++++++-----------------------------------------------
Msrc/db/models/device.rs | 14+++++++++++++-
7 files changed, 146 insertions(+), 100 deletions(-)

diff --git a/src/api/admin.rs b/src/api/admin.rs @@ -403,10 +403,10 @@ async fn delete_user(uuid: &str, token: AdminToken, mut conn: DbConn) -> EmptyRe async fn deauth_user(uuid: &str, _token: AdminToken, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { let mut user = get_user_or_404(uuid, &mut conn).await?; - nt.send_logout(&user, None, &mut conn).await; + nt.send_logout(&user, None).await; if CONFIG.push_enabled() { - for device in Device::find_push_device_by_user(&user.uuid, &mut conn).await { + for device in Device::find_push_devices_by_user(&user.uuid, &mut conn).await { match unregister_push_device(device.uuid).await { Ok(r) => r, Err(e) => error!("Unable to unregister devices from Bitwarden server: {}", e), @@ -429,7 +429,7 @@ async fn disable_user(uuid: &str, _token: AdminToken, mut conn: DbConn, nt: Noti let save_result = user.save(&mut conn).await; - nt.send_logout(&user, None, &mut conn).await; + nt.send_logout(&user, None).await; save_result } diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs @@ -343,7 +343,7 @@ async fn post_password( // Prevent loging out the client where the user requested this endpoint from. // If you do logout the user it will causes issues at the client side. // Adding the device uuid will prevent this. - nt.send_logout(&user, Some(headers.device.uuid), &mut conn).await; + nt.send_logout(&user, Some(headers.device.uuid)).await; save_result } @@ -403,7 +403,7 @@ async fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, mut conn: D user.set_password(&data.NewMasterPasswordHash, Some(data.Key), true, None); let save_result = user.save(&mut conn).await; - nt.send_logout(&user, Some(headers.device.uuid), &mut conn).await; + nt.send_logout(&user, Some(headers.device.uuid)).await; save_result } @@ -490,7 +490,7 @@ async fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, mut conn: D // Prevent loging out the client where the user requested this endpoint from. // If you do logout the user it will causes issues at the client side. // Adding the device uuid will prevent this. - nt.send_logout(&user, Some(headers.device.uuid), &mut conn).await; + nt.send_logout(&user, Some(headers.device.uuid)).await; save_result } @@ -513,7 +513,7 @@ async fn post_sstamp( user.reset_security_stamp(); let save_result = user.save(&mut conn).await; - nt.send_logout(&user, None, &mut conn).await; + nt.send_logout(&user, None).await; save_result } @@ -616,7 +616,7 @@ async fn post_email( let save_result = user.save(&mut conn).await; - nt.send_logout(&user, None, &mut conn).await; + nt.send_logout(&user, None).await; save_result } diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs @@ -2718,7 +2718,7 @@ async fn put_reset_password( user.set_password(reset_request.NewMasterPasswordHash.as_str(), Some(reset_request.Key), true, None); user.save(&mut conn).await?; - nt.send_logout(&user, None, &mut conn).await; + nt.send_logout(&user, None).await; log_event( EventType::OrganizationUserAdminResetPassword as i32, diff --git a/src/api/core/sends.rs b/src/api/core/sends.rs @@ -180,8 +180,14 @@ async fn post_send(data: JsonUpcase<SendData>, headers: Headers, mut conn: DbCon let mut send = create_send(data, headers.user.uuid)?; send.save(&mut conn).await?; - nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&mut conn).await, &mut conn) - .await; + nt.send_send_update( + UpdateType::SyncSendCreate, + &send, + &send.update_users_revision(&mut conn).await, + &headers.device.uuid, + &mut conn, + ) + .await; Ok(Json(send.to_json())) } @@ -253,8 +259,14 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, mut conn: // Save the changes in the database send.save(&mut conn).await?; - nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&mut conn).await, &mut conn) - .await; + nt.send_send_update( + UpdateType::SyncSendCreate, + &send, + &send.update_users_revision(&mut conn).await, + &headers.device.uuid, + &mut conn, + ) + .await; Ok(Json(send.to_json())) } @@ -337,8 +349,14 @@ async fn post_send_file_v2_data( data.data.move_copy_to(file_path).await? } - nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&mut conn).await, &mut conn) - .await; + nt.send_send_update( + UpdateType::SyncSendCreate, + &send, + &send.update_users_revision(&mut conn).await, + &headers.device.uuid, + &mut conn, + ) + .await; } else { err!("Send not found. Unable to save the file."); } @@ -356,6 +374,7 @@ pub struct SendAccessData { async fn post_access( access_id: &str, data: JsonUpcase<SendAccessData>, + headers: Headers, mut conn: DbConn, ip: ClientIp, nt: Notify<'_>, @@ -400,8 +419,14 @@ async fn post_access( send.save(&mut conn).await?; - nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&mut conn).await, &mut conn) - .await; + nt.send_send_update( + UpdateType::SyncSendUpdate, + &send, + &send.update_users_revision(&mut conn).await, + &headers.device.uuid, + &mut conn, + ) + .await; Ok(Json(send.to_json_access(&mut conn).await)) } @@ -412,6 +437,7 @@ async fn post_access_file( file_id: &str, data: JsonUpcase<SendAccessData>, host: Host, + headers: Headers, mut conn: DbConn, nt: Notify<'_>, ) -> JsonResult { @@ -452,8 +478,14 @@ async fn post_access_file( send.save(&mut conn).await?; - nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&mut conn).await, &mut conn) - .await; + nt.send_send_update( + UpdateType::SyncSendUpdate, + &send, + &send.update_users_revision(&mut conn).await, + &headers.device.uuid, + &mut conn, + ) + .await; let token_claims = crate::auth::generate_send_claims(send_id, file_id); let token = crate::auth::encode_jwt(&token_claims); @@ -535,8 +567,14 @@ async fn put_send( } send.save(&mut conn).await?; - nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&mut conn).await, &mut conn) - .await; + nt.send_send_update( + UpdateType::SyncSendUpdate, + &send, + &send.update_users_revision(&mut conn).await, + &headers.device.uuid, + &mut conn, + ) + .await; Ok(Json(send.to_json())) } @@ -553,8 +591,14 @@ async fn delete_send(id: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_ } send.delete(&mut conn).await?; - nt.send_send_update(UpdateType::SyncSendDelete, &send, &send.update_users_revision(&mut conn).await, &mut conn) - .await; + nt.send_send_update( + UpdateType::SyncSendDelete, + &send, + &send.update_users_revision(&mut conn).await, + &headers.device.uuid, + &mut conn, + ) + .await; Ok(()) } @@ -574,8 +618,14 @@ async fn put_remove_password(id: &str, headers: Headers, mut conn: DbConn, nt: N send.set_password(None); send.save(&mut conn).await?; - nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&mut conn).await, &mut conn) - .await; + nt.send_send_update( + UpdateType::SyncSendUpdate, + &send, + &send.update_users_revision(&mut conn).await, + &headers.device.uuid, + &mut conn, + ) + .await; Ok(Json(send.to_json())) } diff --git a/src/api/notifications.rs b/src/api/notifications.rs @@ -240,11 +240,11 @@ impl WebSocketUsers { self.send_update(&user.uuid, &data).await; if CONFIG.push_enabled() { - push_user_update(ut, user).await; + push_user_update(ut, user); } } - pub async fn send_logout(&self, user: &User, acting_device_uuid: Option<String>, conn: &mut DbConn) { + pub async fn send_logout(&self, user: &User, acting_device_uuid: Option<String>) { let data = create_update( vec![("UserId".into(), user.uuid.clone().into()), ("Date".into(), serialize_date(user.updated_at))], UpdateType::LogOut, @@ -254,7 +254,7 @@ impl WebSocketUsers { self.send_update(&user.uuid, &data).await; if CONFIG.push_enabled() { - push_logout(user, acting_device_uuid, conn).await; + push_logout(user, acting_device_uuid); } } @@ -325,7 +325,14 @@ impl WebSocketUsers { } } - pub async fn send_send_update(&self, ut: UpdateType, send: &DbSend, user_uuids: &[String], conn: &mut DbConn) { + pub async fn send_send_update( + &self, + ut: UpdateType, + send: &DbSend, + user_uuids: &[String], + acting_device_uuid: &String, + conn: &mut DbConn, + ) { let user_uuid = convert_option(send.user_uuid.clone()); let data = create_update( @@ -342,7 +349,7 @@ impl WebSocketUsers { self.send_update(uuid, &data).await; } if CONFIG.push_enabled() && user_uuids.len() == 1 { - push_send_update(ut, send, conn).await; + push_send_update(ut, send, acting_device_uuid, conn).await; } } } diff --git a/src/api/push.rs b/src/api/push.rs @@ -139,71 +139,52 @@ pub async fn push_cipher_update( } }; - for device in Device::find_by_user(user_uuid, conn).await { - let data = json!({ + if Device::check_user_has_push_device(user_uuid, conn).await { + send_to_push_relay(json!({ "userId": user_uuid, "organizationId": (), - "deviceId": device.push_uuid, + "deviceId": acting_device_uuid, "identifier": acting_device_uuid, "type": ut as i32, "payload": { - "Id": cipher.uuid, - "UserId": cipher.user_uuid, - "OrganizationId": (), - "RevisionDate": cipher.updated_at + "id": cipher.uuid, + "userId": cipher.user_uuid, + "organizationId": (), + "revisionDate": cipher.updated_at } - }); - - send_to_push_relay(data).await; + })) + .await; } } -pub async fn push_logout(user: &User, acting_device_uuid: Option<String>, conn: &mut crate::db::DbConn) { - if let Some(d) = acting_device_uuid { - for device in Device::find_by_user(&user.uuid, conn).await { - let data = json!({ - "userId": user.uuid, - "organizationId": (), - "deviceId": device.push_uuid, - "identifier": d, - "type": UpdateType::LogOut as i32, - "payload": { - "UserId": user.uuid, - "Date": user.updated_at - } - }); - send_to_push_relay(data).await; - } - } else { - let data = json!({ +pub fn push_logout(user: &User, acting_device_uuid: Option<String>) { + let acting_device_uuid: Value = acting_device_uuid.map(|v| v.into()).unwrap_or_else(|| Value::Null); + + tokio::task::spawn(send_to_push_relay(json!({ + "userId": user.uuid, + "organizationId": (), + "deviceId": acting_device_uuid, + "identifier": acting_device_uuid, + "type": UpdateType::LogOut as i32, + "payload": { "userId": user.uuid, - "organizationId": (), - "deviceId": (), - "identifier": (), - "type": UpdateType::LogOut as i32, - "payload": { - "UserId": user.uuid, - "Date": user.updated_at - } - }); - send_to_push_relay(data).await; - } + "date": user.updated_at + } + }))); } -pub async fn push_user_update(ut: UpdateType, user: &User) { - let data = json!({ +pub fn push_user_update(ut: UpdateType, user: &User) { + tokio::task::spawn(send_to_push_relay(json!({ "userId": user.uuid, "organizationId": (), "deviceId": (), "identifier": (), "type": ut as i32, "payload": { - "UserId": user.uuid, - "Date": user.updated_at + "userId": user.uuid, + "date": user.updated_at } - }); - - send_to_push_relay(data).await; + }))); } pub async fn push_folder_update( @@ -212,46 +193,42 @@ pub async fn push_folder_update( acting_device_uuid: &String, conn: &mut crate::db::DbConn, ) { - for device in Device::find_by_user(&folder.user_uuid, conn).await { - let data = json!({ + if Device::check_user_has_push_device(&folder.user_uuid, conn).await { + tokio::task::spawn(send_to_push_relay(json!({ "userId": folder.user_uuid, "organizationId": (), - "deviceId": device.push_uuid, + "deviceId": acting_device_uuid, "identifier": acting_device_uuid, "type": ut as i32, "payload": { - "Id": folder.uuid, - "UserId": folder.user_uuid, - "RevisionDate": folder.updated_at + "id": folder.uuid, + "userId": folder.user_uuid, + "revisionDate": folder.updated_at } - }); - - send_to_push_relay(data).await; + }))); } } -pub async fn push_send_update(ut: UpdateType, send: &Send, conn: &mut crate::db::DbConn) { +pub async fn push_send_update(ut: UpdateType, send: &Send, acting_device_uuid: &String, conn: &mut crate::db::DbConn) { if let Some(s) = &send.user_uuid { - for device in Device::find_by_user(s, conn).await { - let data = json!({ + if Device::check_user_has_push_device(s, conn).await { + tokio::task::spawn(send_to_push_relay(json!({ "userId": send.user_uuid, "organizationId": (), - "deviceId": device.push_uuid, - "identifier": (), + "deviceId": acting_device_uuid, + "identifier": acting_device_uuid, "type": ut as i32, "payload": { - "Id": send.uuid, - "UserId": send.user_uuid, - "RevisionDate": send.revision_date + "id": send.uuid, + "userId": send.user_uuid, + "revisionDate": send.revision_date } - }); - - send_to_push_relay(data).await; + }))); } } } -async fn send_to_push_relay(data: Value) { +async fn send_to_push_relay(notification_data: Value) { if !CONFIG.push_enabled() { return; } @@ -270,8 +247,8 @@ async fn send_to_push_relay(data: Value) { .post(CONFIG.push_relay_uri() + "/push/send") .header(ACCEPT, "application/json") .header(CONTENT_TYPE, "application/json") - .header(AUTHORIZATION, auth_header) - .json(&data) + .header(AUTHORIZATION, &auth_header) + .json(&notification_data) .send() .await { diff --git a/src/db/models/device.rs b/src/db/models/device.rs @@ -202,7 +202,7 @@ impl Device { .from_db() }} } - pub async fn find_push_device_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { + pub async fn find_push_devices_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> { db_run! { conn: { devices::table .filter(devices::user_uuid.eq(user_uuid)) @@ -212,4 +212,16 @@ impl Device { .from_db() }} } + + pub async fn check_user_has_push_device(user_uuid: &str, conn: &mut DbConn) -> bool { + db_run! { conn: { + devices::table + .filter(devices::user_uuid.eq(user_uuid)) + .filter(devices::push_token.is_not_null()) + .count() + .first::<i64>(conn) + .ok() + .unwrap_or(0) != 0 + }} + } }