diff --git a/src/api/admin.rs b/src/api/admin.rs index f840fc6f..5034884f 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -421,11 +421,11 @@ async fn delete_user(user_id: UserId, token: AdminToken, mut conn: DbConn) -> Em async fn deauth_user(user_id: UserId, _token: AdminToken, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { let mut user = get_user_or_404(&user_id, &mut conn).await?; - nt.send_logout(&user, None).await; + nt.send_logout(&user, None, &mut conn).await; if CONFIG.push_enabled() { for device in Device::find_push_devices_by_user(&user.uuid, &mut conn).await { - match unregister_push_device(device.push_uuid).await { + match unregister_push_device(&device.push_uuid).await { Ok(r) => r, Err(e) => error!("Unable to unregister devices from Bitwarden server: {e}"), }; @@ -447,7 +447,7 @@ async fn disable_user(user_id: UserId, _token: AdminToken, mut conn: DbConn, nt: let save_result = user.save(&mut conn).await; - nt.send_logout(&user, None).await; + nt.send_logout(&user, None, &mut conn).await; save_result } diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index 761ccdeb..542bfbaf 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -461,7 +461,7 @@ async fn post_password(data: Json, headers: Headers, mut conn: D // Prevent logging 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.clone())).await; + nt.send_logout(&user, Some(headers.device.uuid.clone()), &mut conn).await; save_result } @@ -521,7 +521,7 @@ async fn post_kdf(data: Json, headers: Headers, mut conn: DbConn, user.set_password(&data.new_master_password_hash, Some(data.key), true, None); let save_result = user.save(&mut conn).await; - nt.send_logout(&user, Some(headers.device.uuid.clone())).await; + nt.send_logout(&user, Some(headers.device.uuid.clone()), &mut conn).await; save_result } @@ -733,7 +733,7 @@ async fn post_rotatekey(data: Json, headers: Headers, mut conn: DbConn, // Prevent logging 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.clone())).await; + nt.send_logout(&user, Some(headers.device.uuid.clone()), &mut conn).await; save_result } @@ -749,7 +749,7 @@ async fn post_sstamp(data: Json, headers: Headers, mut conn: user.reset_security_stamp(); let save_result = user.save(&mut conn).await; - nt.send_logout(&user, None).await; + nt.send_logout(&user, None, &mut conn).await; save_result } @@ -857,7 +857,7 @@ async fn post_email(data: Json, headers: Headers, mut conn: DbC let save_result = user.save(&mut conn).await; - nt.send_logout(&user, None).await; + nt.send_logout(&user, None, &mut conn).await; save_result } @@ -1196,19 +1196,14 @@ async fn put_device_token( err!(format!("Error: device {device_id} should be present before a token can be assigned")) }; - // if the device already has been registered - if device.is_registered() { - // check if the new token is the same as the registered token - if device.push_token.is_some() && device.push_token.unwrap() == token.clone() { - debug!("Device {device_id} is already registered and token is the same"); - return Ok(()); - } else { - // Try to unregister already registered device - unregister_push_device(device.push_uuid).await.ok(); - } - // clear the push_uuid - device.push_uuid = None; + // Check if the new token is the same as the registered token + // Although upstream seems to always register a device on login, we do not. + // Unless this causes issues, lets keep it this way, else we might need to also register on every login. + if device.push_token.as_ref() == Some(&token) { + debug!("Device {device_id} for user {} is already registered and token is identical", headers.user.uuid); + return Ok(()); } + device.push_token = Some(token); if let Err(e) = device.save(&mut conn).await { err!(format!("An error occurred while trying to save the device push token: {e}")); @@ -1225,13 +1220,16 @@ async fn put_clear_device_token(device_id: DeviceId, mut conn: DbConn) -> EmptyR // https://github.com/bitwarden/core/blob/master/src/Api/Controllers/DevicesController.cs#L109 // https://github.com/bitwarden/core/blob/master/src/Core/Services/Implementations/DeviceService.cs#L37 // This is somehow not implemented in any app, added it in case it is required + // 2025: Also, it looks like it only clears the first found device upstream, which is probably faulty. + // This because currently multiple accounts could be on the same device/app and that would cause issues. + // Vaultwarden removes the push-token for all devices, but this probably means we should also unregister all these devices. if !CONFIG.push_enabled() { return Ok(()); } if let Some(device) = Device::find_by_uuid(&device_id, &mut conn).await { Device::clear_push_token_by_uuid(&device_id, &mut conn).await?; - unregister_push_device(device.push_uuid).await?; + unregister_push_device(&device.push_uuid).await?; } Ok(()) @@ -1269,10 +1267,10 @@ async fn post_auth_request( }; // Validate device uuid and type - match Device::find_by_uuid_and_user(&data.device_identifier, &user.uuid, &mut conn).await { - Some(device) if device.atype == client_headers.device_type => {} + let device = match Device::find_by_uuid_and_user(&data.device_identifier, &user.uuid, &mut conn).await { + Some(device) if device.atype == client_headers.device_type => device, _ => err!("AuthRequest doesn't exist", "Device verification failed"), - } + }; let mut auth_request = AuthRequest::new( user.uuid.clone(), @@ -1284,7 +1282,7 @@ async fn post_auth_request( ); auth_request.save(&mut conn).await?; - nt.send_auth_request(&user.uuid, &auth_request.uuid, &data.device_identifier, &mut conn).await; + nt.send_auth_request(&user.uuid, &auth_request.uuid, &device, &mut conn).await; log_user_event( EventType::UserRequestedDeviceApproval as i32, @@ -1359,6 +1357,10 @@ async fn put_auth_request( err!("AuthRequest doesn't exist", "Record not found or user uuid does not match") }; + if headers.device.uuid != data.device_identifier { + err!("AuthRequest doesn't exist", "Device verification failed") + } + if auth_request.approved.is_some() { err!("An authentication request with the same device already exists") } @@ -1375,7 +1377,7 @@ async fn put_auth_request( auth_request.save(&mut conn).await?; ant.send_auth_response(&auth_request.user_uuid, &auth_request.uuid).await; - nt.send_auth_response(&auth_request.user_uuid, &auth_request.uuid, &data.device_identifier, &mut conn).await; + nt.send_auth_response(&auth_request.user_uuid, &auth_request.uuid, &headers.device, &mut conn).await; log_user_event( EventType::OrganizationUserApprovedAuthRequest as i32, diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index ad024076..9e7dc045 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -535,7 +535,7 @@ pub async fn update_cipher_from_data( ut, cipher, &cipher.update_users_revision(conn).await, - &headers.device.uuid, + &headers.device, shared_to_collections, conn, ) @@ -612,7 +612,7 @@ async fn post_ciphers_import( let mut user = headers.user; user.update_revision(&mut conn).await?; - nt.send_user_update(UpdateType::SyncVault, &user).await; + nt.send_user_update(UpdateType::SyncVault, &user, &headers.device.push_uuid, &mut conn).await; Ok(()) } @@ -808,7 +808,7 @@ async fn post_collections_update( UpdateType::SyncCipherUpdate, &cipher, &cipher.update_users_revision(&mut conn).await, - &headers.device.uuid, + &headers.device, Some(Vec::from_iter(posted_collections)), &mut conn, ) @@ -885,7 +885,7 @@ async fn post_collections_admin( UpdateType::SyncCipherUpdate, &cipher, &cipher.update_users_revision(&mut conn).await, - &headers.device.uuid, + &headers.device, Some(Vec::from_iter(posted_collections)), &mut conn, ) @@ -1281,7 +1281,7 @@ async fn save_attachment( UpdateType::SyncCipherUpdate, &cipher, &cipher.update_users_revision(&mut conn).await, - &headers.device.uuid, + &headers.device, None, &mut conn, ) @@ -1582,7 +1582,7 @@ async fn move_cipher_selected( UpdateType::SyncCipherUpdate, &cipher, std::slice::from_ref(&user_id), - &headers.device.uuid, + &headers.device, None, &mut conn, ) @@ -1629,7 +1629,7 @@ async fn delete_all( Some(member) => { if member.atype == MembershipType::Owner { Cipher::delete_all_by_organization(&org_data.org_id, &mut conn).await?; - nt.send_user_update(UpdateType::SyncVault, &user).await; + nt.send_user_update(UpdateType::SyncVault, &user, &headers.device.push_uuid, &mut conn).await; log_event( EventType::OrganizationPurgedVault as i32, @@ -1662,7 +1662,7 @@ async fn delete_all( } user.update_revision(&mut conn).await?; - nt.send_user_update(UpdateType::SyncVault, &user).await; + nt.send_user_update(UpdateType::SyncVault, &user, &headers.device.push_uuid, &mut conn).await; Ok(()) } @@ -1691,7 +1691,7 @@ async fn _delete_cipher_by_uuid( UpdateType::SyncCipherUpdate, &cipher, &cipher.update_users_revision(conn).await, - &headers.device.uuid, + &headers.device, None, conn, ) @@ -1702,7 +1702,7 @@ async fn _delete_cipher_by_uuid( UpdateType::SyncCipherDelete, &cipher, &cipher.update_users_revision(conn).await, - &headers.device.uuid, + &headers.device, None, conn, ) @@ -1767,7 +1767,7 @@ async fn _restore_cipher_by_uuid( UpdateType::SyncCipherUpdate, &cipher, &cipher.update_users_revision(conn).await, - &headers.device.uuid, + &headers.device, None, conn, ) @@ -1841,7 +1841,7 @@ async fn _delete_cipher_attachment_by_id( UpdateType::SyncCipherUpdate, &cipher, &cipher.update_users_revision(conn).await, - &headers.device.uuid, + &headers.device, None, conn, ) diff --git a/src/api/core/folders.rs b/src/api/core/folders.rs index 01dea4bb..c0769dad 100644 --- a/src/api/core/folders.rs +++ b/src/api/core/folders.rs @@ -45,7 +45,7 @@ async fn post_folders(data: Json, headers: Headers, mut conn: DbConn let mut folder = Folder::new(headers.user.uuid, data.name); folder.save(&mut conn).await?; - nt.send_folder_update(UpdateType::SyncFolderCreate, &folder, &headers.device.uuid, &mut conn).await; + nt.send_folder_update(UpdateType::SyncFolderCreate, &folder, &headers.device, &mut conn).await; Ok(Json(folder.to_json())) } @@ -78,7 +78,7 @@ async fn put_folder( folder.name = data.name; folder.save(&mut conn).await?; - nt.send_folder_update(UpdateType::SyncFolderUpdate, &folder, &headers.device.uuid, &mut conn).await; + nt.send_folder_update(UpdateType::SyncFolderUpdate, &folder, &headers.device, &mut conn).await; Ok(Json(folder.to_json())) } @@ -97,6 +97,6 @@ async fn delete_folder(folder_id: FolderId, headers: Headers, mut conn: DbConn, // Delete the actual folder entry folder.delete(&mut conn).await?; - nt.send_folder_update(UpdateType::SyncFolderDelete, &folder, &headers.device.uuid, &mut conn).await; + nt.send_folder_update(UpdateType::SyncFolderDelete, &folder, &headers.device, &mut conn).await; Ok(()) } diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs index f5a3f420..0f452fd5 100644 --- a/src/api/core/mod.rs +++ b/src/api/core/mod.rs @@ -124,7 +124,7 @@ async fn post_eq_domains( user.save(&mut conn).await?; - nt.send_user_update(UpdateType::SyncSettings, &user).await; + nt.send_user_update(UpdateType::SyncSettings, &user, &headers.device.push_uuid, &mut conn).await; Ok(Json(json!({}))) } diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 16f1bf45..ec512b8f 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -1476,7 +1476,7 @@ async fn _confirm_invite( let save_result = member_to_confirm.save(conn).await; if let Some(user) = User::find_by_uuid(&member_to_confirm.user_uuid, conn).await { - nt.send_user_update(UpdateType::SyncOrgKeys, &user).await; + nt.send_user_update(UpdateType::SyncOrgKeys, &user, &headers.device.push_uuid, conn).await; } save_result @@ -1763,7 +1763,7 @@ async fn _delete_member( .await; if let Some(user) = User::find_by_uuid(&member_to_delete.user_uuid, conn).await { - nt.send_user_update(UpdateType::SyncOrgKeys, &user).await; + nt.send_user_update(UpdateType::SyncOrgKeys, &user, &headers.device.push_uuid, conn).await; } member_to_delete.delete(conn).await @@ -3163,7 +3163,7 @@ async fn put_reset_password( user.set_password(reset_request.new_master_password_hash.as_str(), Some(reset_request.key), true, None); user.save(&mut conn).await?; - nt.send_logout(&user, None).await; + nt.send_logout(&user, None, &mut conn).await; log_event( EventType::OrganizationUserAdminResetPassword as i32, diff --git a/src/api/core/sends.rs b/src/api/core/sends.rs index 01bce86f..e56538c3 100644 --- a/src/api/core/sends.rs +++ b/src/api/core/sends.rs @@ -2,6 +2,7 @@ use std::path::Path; use chrono::{DateTime, TimeDelta, Utc}; use num_traits::ToPrimitive; +use once_cell::sync::Lazy; use rocket::form::Form; use rocket::fs::NamedFile; use rocket::fs::TempFile; @@ -17,6 +18,21 @@ use crate::{ }; const SEND_INACCESSIBLE_MSG: &str = "Send does not exist or is no longer available"; +static ANON_PUSH_DEVICE: Lazy = Lazy::new(|| { + let dt = crate::util::parse_date("1970-01-01T00:00:00.000000Z"); + Device { + uuid: String::from("00000000-0000-0000-0000-000000000000").into(), + created_at: dt, + updated_at: dt, + user_uuid: String::from("00000000-0000-0000-0000-000000000000").into(), + name: String::new(), + atype: 0, + push_uuid: Some(String::from("00000000-0000-0000-0000-000000000000").into()), + push_token: None, + refresh_token: String::new(), + twofactor_remember: None, + } +}); // The max file size allowed by Bitwarden clients and add an extra 5% to avoid issues const SIZE_525_MB: i64 = 550_502_400; @@ -182,7 +198,7 @@ async fn post_send(data: Json, headers: Headers, mut conn: DbConn, nt: UpdateType::SyncSendCreate, &send, &send.update_users_revision(&mut conn).await, - &headers.device.uuid, + &headers.device, &mut conn, ) .await; @@ -272,7 +288,7 @@ async fn post_send_file(data: Form>, headers: Headers, mut conn: UpdateType::SyncSendCreate, &send, &send.update_users_revision(&mut conn).await, - &headers.device.uuid, + &headers.device, &mut conn, ) .await; @@ -424,7 +440,7 @@ async fn post_send_file_v2_data( UpdateType::SyncSendCreate, &send, &send.update_users_revision(&mut conn).await, - &headers.device.uuid, + &headers.device, &mut conn, ) .await; @@ -489,7 +505,7 @@ async fn post_access( UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&mut conn).await, - &String::from("00000000-0000-0000-0000-000000000000").into(), + &ANON_PUSH_DEVICE, &mut conn, ) .await; @@ -546,7 +562,7 @@ async fn post_access_file( UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&mut conn).await, - &String::from("00000000-0000-0000-0000-000000000000").into(), + &ANON_PUSH_DEVICE, &mut conn, ) .await; @@ -645,7 +661,7 @@ pub async fn update_send_from_data( send.save(conn).await?; if ut != UpdateType::None { - nt.send_send_update(ut, send, &send.update_users_revision(conn).await, &headers.device.uuid, conn).await; + nt.send_send_update(ut, send, &send.update_users_revision(conn).await, &headers.device, conn).await; } Ok(()) } @@ -661,7 +677,7 @@ async fn delete_send(send_id: SendId, headers: Headers, mut conn: DbConn, nt: No UpdateType::SyncSendDelete, &send, &send.update_users_revision(&mut conn).await, - &headers.device.uuid, + &headers.device, &mut conn, ) .await; @@ -683,7 +699,7 @@ async fn put_remove_password(send_id: SendId, headers: Headers, mut conn: DbConn UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&mut conn).await, - &headers.device.uuid, + &headers.device, &mut conn, ) .await; diff --git a/src/api/notifications.rs b/src/api/notifications.rs index 2b325b70..ccf4c13c 100644 --- a/src/api/notifications.rs +++ b/src/api/notifications.rs @@ -10,7 +10,7 @@ use rocket_ws::{Message, WebSocket}; use crate::{ auth::{ClientIp, WsAccessTokenHeader}, db::{ - models::{AuthRequestId, Cipher, CollectionId, DeviceId, Folder, Send as DbSend, User, UserId}, + models::{AuthRequestId, Cipher, CollectionId, Device, DeviceId, Folder, PushId, Send as DbSend, User, UserId}, DbConn, }, Error, CONFIG, @@ -339,7 +339,7 @@ impl WebSocketUsers { } // NOTE: The last modified date needs to be updated before calling these methods - pub async fn send_user_update(&self, ut: UpdateType, user: &User) { + pub async fn send_user_update(&self, ut: UpdateType, user: &User, push_uuid: &Option, conn: &mut DbConn) { // Skip any processing if both WebSockets and Push are not active if *NOTIFICATIONS_DISABLED { return; @@ -355,11 +355,11 @@ impl WebSocketUsers { } if CONFIG.push_enabled() { - push_user_update(ut, user); + push_user_update(ut, user, push_uuid, conn).await; } } - pub async fn send_logout(&self, user: &User, acting_device_id: Option) { + pub async fn send_logout(&self, user: &User, acting_device_id: Option, conn: &mut DbConn) { // Skip any processing if both WebSockets and Push are not active if *NOTIFICATIONS_DISABLED { return; @@ -375,17 +375,11 @@ impl WebSocketUsers { } if CONFIG.push_enabled() { - push_logout(user, acting_device_id.clone()); + push_logout(user, acting_device_id.clone(), conn).await; } } - pub async fn send_folder_update( - &self, - ut: UpdateType, - folder: &Folder, - acting_device_id: &DeviceId, - conn: &mut DbConn, - ) { + pub async fn send_folder_update(&self, ut: UpdateType, folder: &Folder, device: &Device, conn: &mut DbConn) { // Skip any processing if both WebSockets and Push are not active if *NOTIFICATIONS_DISABLED { return; @@ -397,7 +391,7 @@ impl WebSocketUsers { ("RevisionDate".into(), serialize_date(folder.updated_at)), ], ut, - Some(acting_device_id.clone()), + Some(device.uuid.clone()), ); if CONFIG.enable_websocket() { @@ -405,7 +399,7 @@ impl WebSocketUsers { } if CONFIG.push_enabled() { - push_folder_update(ut, folder, acting_device_id, conn).await; + push_folder_update(ut, folder, device, conn).await; } } @@ -414,7 +408,7 @@ impl WebSocketUsers { ut: UpdateType, cipher: &Cipher, user_ids: &[UserId], - acting_device_id: &DeviceId, + device: &Device, collection_uuids: Option>, conn: &mut DbConn, ) { @@ -444,7 +438,7 @@ impl WebSocketUsers { ("RevisionDate".into(), revision_date), ], ut, - Some(acting_device_id.clone()), + Some(device.uuid.clone()), // Acting device id (unique device/app uuid) ); if CONFIG.enable_websocket() { @@ -454,7 +448,7 @@ impl WebSocketUsers { } if CONFIG.push_enabled() && user_ids.len() == 1 { - push_cipher_update(ut, cipher, acting_device_id, conn).await; + push_cipher_update(ut, cipher, device, conn).await; } } @@ -463,7 +457,7 @@ impl WebSocketUsers { ut: UpdateType, send: &DbSend, user_ids: &[UserId], - acting_device_id: &DeviceId, + device: &Device, conn: &mut DbConn, ) { // Skip any processing if both WebSockets and Push are not active @@ -488,7 +482,7 @@ impl WebSocketUsers { } } if CONFIG.push_enabled() && user_ids.len() == 1 { - push_send_update(ut, send, acting_device_id, conn).await; + push_send_update(ut, send, device, conn).await; } } @@ -496,7 +490,7 @@ impl WebSocketUsers { &self, user_id: &UserId, auth_request_uuid: &str, - acting_device_id: &DeviceId, + device: &Device, conn: &mut DbConn, ) { // Skip any processing if both WebSockets and Push are not active @@ -506,14 +500,14 @@ impl WebSocketUsers { let data = create_update( vec![("Id".into(), auth_request_uuid.to_owned().into()), ("UserId".into(), user_id.to_string().into())], UpdateType::AuthRequest, - Some(acting_device_id.clone()), + Some(device.uuid.clone()), ); if CONFIG.enable_websocket() { self.send_update(user_id, &data).await; } if CONFIG.push_enabled() { - push_auth_request(user_id.clone(), auth_request_uuid.to_owned(), conn).await; + push_auth_request(user_id, auth_request_uuid, device, conn).await; } } @@ -521,7 +515,7 @@ impl WebSocketUsers { &self, user_id: &UserId, auth_request_id: &AuthRequestId, - approving_device_id: &DeviceId, + device: &Device, conn: &mut DbConn, ) { // Skip any processing if both WebSockets and Push are not active @@ -531,14 +525,14 @@ impl WebSocketUsers { let data = create_update( vec![("Id".into(), auth_request_id.to_string().into()), ("UserId".into(), user_id.to_string().into())], UpdateType::AuthRequestResponse, - Some(approving_device_id.clone()), + Some(device.uuid.clone()), ); if CONFIG.enable_websocket() { self.send_update(user_id, &data).await; } if CONFIG.push_enabled() { - push_auth_response(user_id, auth_request_id, approving_device_id, conn).await; + push_auth_response(user_id, auth_request_id, device, conn).await; } } } diff --git a/src/api/push.rs b/src/api/push.rs index 4516ec67..f3ade9b0 100644 --- a/src/api/push.rs +++ b/src/api/push.rs @@ -28,20 +28,20 @@ struct LocalAuthPushToken { valid_until: Instant, } -async fn get_auth_push_token() -> ApiResult { - static PUSH_TOKEN: Lazy> = Lazy::new(|| { +async fn get_auth_api_token() -> ApiResult { + static API_TOKEN: Lazy> = Lazy::new(|| { RwLock::new(LocalAuthPushToken { access_token: String::new(), valid_until: Instant::now(), }) }); - let push_token = PUSH_TOKEN.read().await; + let api_token = API_TOKEN.read().await; - if push_token.valid_until.saturating_duration_since(Instant::now()).as_secs() > 0 { + if api_token.valid_until.saturating_duration_since(Instant::now()).as_secs() > 0 { debug!("Auth Push token still valid, no need for a new one"); - return Ok(push_token.access_token.clone()); + return Ok(api_token.access_token.clone()); } - drop(push_token); // Drop the read lock now + drop(api_token); // Drop the read lock now let installation_id = CONFIG.push_installation_id(); let client_id = format!("installation.{installation_id}"); @@ -68,44 +68,48 @@ async fn get_auth_push_token() -> ApiResult { Err(e) => err!(format!("Unexpected push token received from bitwarden server: {e}")), }; - let mut push_token = PUSH_TOKEN.write().await; - push_token.valid_until = Instant::now() + let mut api_token = API_TOKEN.write().await; + api_token.valid_until = Instant::now() .checked_add(Duration::new((json_pushtoken.expires_in / 2) as u64, 0)) // Token valid for half the specified time .unwrap(); - push_token.access_token = json_pushtoken.access_token; + api_token.access_token = json_pushtoken.access_token; - debug!("Token still valid for {}", push_token.valid_until.saturating_duration_since(Instant::now()).as_secs()); - Ok(push_token.access_token.clone()) + debug!("Token still valid for {}", api_token.valid_until.saturating_duration_since(Instant::now()).as_secs()); + Ok(api_token.access_token.clone()) } pub async fn register_push_device(device: &mut Device, conn: &mut crate::db::DbConn) -> EmptyResult { - if !CONFIG.push_enabled() || !device.is_push_device() || device.is_registered() { + if !CONFIG.push_enabled() || !device.is_push_device() { return Ok(()); } if device.push_token.is_none() { - warn!("Skipping the registration of the device {} because the push_token field is empty.", device.uuid); - warn!("To get rid of this message you need to clear the app data and reconnect the device."); + warn!("Skipping the registration of the device {:?} because the push_token field is empty.", device.uuid); + warn!("To get rid of this message you need to logout, clear the app data and login again on the device."); return Ok(()); } - debug!("Registering Device {}", device.uuid); + debug!("Registering Device {:?}", device.push_uuid); - // generate a random push_uuid so we know the device is registered - device.push_uuid = Some(PushId(get_uuid())); + // Generate a random push_uuid so if it doesn't already have one + if device.push_uuid.is_none() { + device.push_uuid = Some(PushId(get_uuid())); + } //Needed to register a device for push to bitwarden : let data = json!({ + "deviceId": device.push_uuid, // Unique UUID per user/device + "pushToken": device.push_token, "userId": device.user_uuid, - "deviceId": device.push_uuid, - "identifier": device.uuid, "type": device.atype, - "pushToken": device.push_token + "identifier": device.uuid, // Unique UUID of the device/app, determined by the device/app it self currently registering + // "organizationIds:" [] // TODO: This is not yet implemented by Vaultwarden! + "installationId": CONFIG.push_installation_id(), }); - let auth_push_token = get_auth_push_token().await?; - let auth_header = format!("Bearer {}", &auth_push_token); + let auth_api_token = get_auth_api_token().await?; + let auth_header = format!("Bearer {auth_api_token}"); if let Err(e) = make_http_request(Method::POST, &(CONFIG.push_relay_uri() + "/push/register"))? .header(CONTENT_TYPE, "application/json") @@ -126,18 +130,21 @@ pub async fn register_push_device(device: &mut Device, conn: &mut crate::db::DbC Ok(()) } -pub async fn unregister_push_device(push_id: Option) -> EmptyResult { +pub async fn unregister_push_device(push_id: &Option) -> EmptyResult { if !CONFIG.push_enabled() || push_id.is_none() { return Ok(()); } - let auth_push_token = get_auth_push_token().await?; + let auth_api_token = get_auth_api_token().await?; - let auth_header = format!("Bearer {}", &auth_push_token); + let auth_header = format!("Bearer {auth_api_token}"); - match make_http_request(Method::DELETE, &format!("{}/push/{}", CONFIG.push_relay_uri(), push_id.unwrap()))? - .header(AUTHORIZATION, auth_header) - .send() - .await + match make_http_request( + Method::POST, + &format!("{}/push/delete/{}", CONFIG.push_relay_uri(), push_id.as_ref().unwrap()), + )? + .header(AUTHORIZATION, auth_header) + .send() + .await { Ok(r) => r, Err(e) => err!(format!("An error occurred during device unregistration: {e}")), @@ -145,12 +152,7 @@ pub async fn unregister_push_device(push_id: Option) -> EmptyResult { Ok(()) } -pub async fn push_cipher_update( - ut: UpdateType, - cipher: &Cipher, - acting_device_id: &DeviceId, - conn: &mut crate::db::DbConn, -) { +pub async fn push_cipher_update(ut: UpdateType, cipher: &Cipher, device: &Device, conn: &mut crate::db::DbConn) { // We shouldn't send a push notification on cipher update if the cipher belongs to an organization, this isn't implemented in the upstream server too. if cipher.organization_uuid.is_some() { return; @@ -163,87 +165,97 @@ pub async fn push_cipher_update( if Device::check_user_has_push_device(user_id, conn).await { send_to_push_relay(json!({ "userId": user_id, - "organizationId": (), - "deviceId": acting_device_id, - "identifier": acting_device_id, + "organizationId": null, + "deviceId": device.push_uuid, // Should be the records unique uuid of the acting device (unique uuid per user/device) + "identifier": device.uuid, // Should be the acting device id (aka uuid per device/app) "type": ut as i32, "payload": { - "Id": cipher.uuid, - "UserId": cipher.user_uuid, - "OrganizationId": (), - "RevisionDate": format_date(&cipher.updated_at) - } + "id": cipher.uuid, + "userId": cipher.user_uuid, + "organizationId": null, + "collectionIds": null, + "revisionDate": format_date(&cipher.updated_at) + }, + "clientType": null, + "installationId": null })) .await; } } -pub fn push_logout(user: &User, acting_device_id: Option) { +pub async fn push_logout(user: &User, acting_device_id: Option, conn: &mut crate::db::DbConn) { let acting_device_id: Value = acting_device_id.map(|v| v.to_string().into()).unwrap_or_else(|| Value::Null); - tokio::task::spawn(send_to_push_relay(json!({ - "userId": user.uuid, - "organizationId": (), - "deviceId": acting_device_id, - "identifier": acting_device_id, - "type": UpdateType::LogOut as i32, - "payload": { - "UserId": user.uuid, - "Date": format_date(&user.updated_at) - } - }))); + if Device::check_user_has_push_device(&user.uuid, conn).await { + tokio::task::spawn(send_to_push_relay(json!({ + "userId": user.uuid, + "organizationId": (), + "deviceId": acting_device_id, + "identifier": acting_device_id, + "type": UpdateType::LogOut as i32, + "payload": { + "userId": user.uuid, + "date": format_date(&user.updated_at) + }, + "clientType": null, + "installationId": null + }))); + } } -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": format_date(&user.updated_at) - } - }))); +pub async fn push_user_update(ut: UpdateType, user: &User, push_uuid: &Option, conn: &mut crate::db::DbConn) { + if Device::check_user_has_push_device(&user.uuid, conn).await { + tokio::task::spawn(send_to_push_relay(json!({ + "userId": user.uuid, + "organizationId": null, + "deviceId": push_uuid, + "identifier": null, + "type": ut as i32, + "payload": { + "userId": user.uuid, + "date": format_date(&user.updated_at) + }, + "clientType": null, + "installationId": null + }))); + } } -pub async fn push_folder_update( - ut: UpdateType, - folder: &Folder, - acting_device_id: &DeviceId, - conn: &mut crate::db::DbConn, -) { +pub async fn push_folder_update(ut: UpdateType, folder: &Folder, device: &Device, conn: &mut crate::db::DbConn) { 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": acting_device_id, - "identifier": acting_device_id, + "organizationId": null, + "deviceId": device.push_uuid, // Should be the records unique uuid of the acting device (unique uuid per user/device) + "identifier": device.uuid, // Should be the acting device id (aka uuid per device/app) "type": ut as i32, "payload": { - "Id": folder.uuid, - "UserId": folder.user_uuid, - "RevisionDate": format_date(&folder.updated_at) - } + "id": folder.uuid, + "userId": folder.user_uuid, + "revisionDate": format_date(&folder.updated_at) + }, + "clientType": null, + "installationId": null }))); } } -pub async fn push_send_update(ut: UpdateType, send: &Send, acting_device_id: &DeviceId, conn: &mut crate::db::DbConn) { +pub async fn push_send_update(ut: UpdateType, send: &Send, device: &Device, conn: &mut crate::db::DbConn) { if let Some(s) = &send.user_uuid { if Device::check_user_has_push_device(s, conn).await { tokio::task::spawn(send_to_push_relay(json!({ "userId": send.user_uuid, - "organizationId": (), - "deviceId": acting_device_id, - "identifier": acting_device_id, + "organizationId": null, + "deviceId": device.push_uuid, // Should be the records unique uuid of the acting device (unique uuid per user/device) + "identifier": device.uuid, // Should be the acting device id (aka uuid per device/app) "type": ut as i32, "payload": { - "Id": send.uuid, - "UserId": send.user_uuid, - "RevisionDate": format_date(&send.revision_date) - } + "id": send.uuid, + "userId": send.user_uuid, + "revisionDate": format_date(&send.revision_date) + }, + "clientType": null, + "installationId": null }))); } } @@ -254,7 +266,7 @@ async fn send_to_push_relay(notification_data: Value) { return; } - let auth_push_token = match get_auth_push_token().await { + let auth_api_token = match get_auth_api_token().await { Ok(s) => s, Err(e) => { debug!("Could not get the auth push token: {e}"); @@ -262,7 +274,7 @@ async fn send_to_push_relay(notification_data: Value) { } }; - let auth_header = format!("Bearer {}", &auth_push_token); + let auth_header = format!("Bearer {auth_api_token}"); let req = match make_http_request(Method::POST, &(CONFIG.push_relay_uri() + "/push/send")) { Ok(r) => r, @@ -284,18 +296,20 @@ async fn send_to_push_relay(notification_data: Value) { }; } -pub async fn push_auth_request(user_id: UserId, auth_request_id: String, conn: &mut crate::db::DbConn) { - if Device::check_user_has_push_device(&user_id, conn).await { +pub async fn push_auth_request(user_id: &UserId, auth_request_id: &str, device: &Device, conn: &mut crate::db::DbConn) { + if Device::check_user_has_push_device(user_id, conn).await { tokio::task::spawn(send_to_push_relay(json!({ "userId": user_id, - "organizationId": (), - "deviceId": null, - "identifier": null, + "organizationId": null, + "deviceId": device.push_uuid, // Should be the records unique uuid of the acting device (unique uuid per user/device) + "identifier": device.uuid, // Should be the acting device id (aka uuid per device/app) "type": UpdateType::AuthRequest as i32, "payload": { - "Id": auth_request_id, - "UserId": user_id, - } + "userId": user_id, + "id": auth_request_id, + }, + "clientType": null, + "installationId": null }))); } } @@ -303,20 +317,22 @@ pub async fn push_auth_request(user_id: UserId, auth_request_id: String, conn: & pub async fn push_auth_response( user_id: &UserId, auth_request_id: &AuthRequestId, - approving_device_id: &DeviceId, + device: &Device, conn: &mut crate::db::DbConn, ) { if Device::check_user_has_push_device(user_id, conn).await { tokio::task::spawn(send_to_push_relay(json!({ "userId": user_id, - "organizationId": (), - "deviceId": approving_device_id, - "identifier": approving_device_id, + "organizationId": null, + "deviceId": device.push_uuid, // Should be the records unique uuid of the acting device (unique uuid per user/device) + "identifier": device.uuid, // Should be the acting device id (aka uuid per device/app) "type": UpdateType::AuthRequestResponse as i32, "payload": { - "Id": auth_request_id, - "UserId": user_id, - } + "userId": user_id, + "id": auth_request_id, + }, + "clientType": null, + "installationId": null }))); } } diff --git a/src/db/models/device.rs b/src/db/models/device.rs index 3b6ae02b..ea4e25ab 100644 --- a/src/db/models/device.rs +++ b/src/db/models/device.rs @@ -8,7 +8,7 @@ use crate::{ util::{format_date, get_uuid}, CONFIG, }; -use macros::IdFromParam; +use macros::{IdFromParam, UuidFromParam}; db_object! { #[derive(Identifiable, Queryable, Insertable, AsChangeset)] @@ -143,10 +143,6 @@ impl Device { matches!(DeviceType::from_i32(self.atype), DeviceType::Android | DeviceType::Ios) } - pub fn is_registered(&self) -> bool { - self.push_uuid.is_some() - } - pub fn is_cli(&self) -> bool { matches!(DeviceType::from_i32(self.atype), DeviceType::WindowsCLI | DeviceType::MacOsCLI | DeviceType::LinuxCLI) } @@ -409,7 +405,5 @@ impl DeviceType { )] pub struct DeviceId(String); -#[derive( - Clone, Debug, DieselNewType, Display, From, FromForm, Hash, PartialEq, Eq, Serialize, Deserialize, IdFromParam, -)] +#[derive(Clone, Debug, DieselNewType, Display, From, FromForm, Serialize, Deserialize, UuidFromParam)] pub struct PushId(pub String);