diff --git a/src/api/admin.rs b/src/api/admin.rs index fd2293d6..6e0c2acf 100644 --- a/src/api/admin.rs +++ b/src/api/admin.rs @@ -13,7 +13,7 @@ use rocket::{ }; use crate::{ - api::{core::log_event, ApiResult, EmptyResult, JsonResult, NumberOrString}, + api::{core::log_event, ApiResult, EmptyResult, JsonResult, Notify, NumberOrString, UpdateType}, auth::{decode_admin, encode_jwt, generate_admin_claims, ClientIp}, config::ConfigBuilder, db::{backup_database, get_sql_server_version, models::*, DbConn, DbConnType}, @@ -365,22 +365,30 @@ async fn delete_user(uuid: String, _token: AdminToken, mut conn: DbConn, ip: Cli } #[post("/users//deauth")] -async fn deauth_user(uuid: String, _token: AdminToken, mut conn: DbConn) -> EmptyResult { +async fn deauth_user(uuid: String, _token: AdminToken, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { let mut user = get_user_or_404(&uuid, &mut conn).await?; Device::delete_all_by_user(&user.uuid, &mut conn).await?; user.reset_security_stamp(); - user.save(&mut conn).await + let save_result = user.save(&mut conn).await; + + nt.send_user_update(UpdateType::LogOut, &user).await; + + save_result } #[post("/users//disable")] -async fn disable_user(uuid: String, _token: AdminToken, mut conn: DbConn) -> EmptyResult { +async fn disable_user(uuid: String, _token: AdminToken, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { let mut user = get_user_or_404(&uuid, &mut conn).await?; Device::delete_all_by_user(&user.uuid, &mut conn).await?; user.reset_security_stamp(); user.enabled = false; - user.save(&mut conn).await + let save_result = user.save(&mut conn).await; + + nt.send_user_update(UpdateType::LogOut, &user).await; + + save_result } #[post("/users//enable")] diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index cb6dc4fc..27bdb12a 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -275,6 +275,7 @@ async fn post_password( headers: Headers, mut conn: DbConn, ip: ClientIp, + nt: Notify<'_>, ) -> EmptyResult { let data: ChangePassData = data.into_inner().data; let mut user = headers.user; @@ -293,7 +294,11 @@ async fn post_password( Some(vec![String::from("post_rotatekey"), String::from("get_contacts"), String::from("get_public_keys")]), ); user.akey = data.Key; - user.save(&mut conn).await + let save_result = user.save(&mut conn).await; + + nt.send_user_update(UpdateType::LogOut, &user).await; + + save_result } #[derive(Deserialize)] @@ -308,7 +313,7 @@ struct ChangeKdfData { } #[post("/accounts/kdf", data = "")] -async fn post_kdf(data: JsonUpcase, headers: Headers, mut conn: DbConn) -> EmptyResult { +async fn post_kdf(data: JsonUpcase, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult { let data: ChangeKdfData = data.into_inner().data; let mut user = headers.user; @@ -320,7 +325,11 @@ async fn post_kdf(data: JsonUpcase, headers: Headers, mut conn: D user.client_kdf_type = data.Kdf; user.set_password(&data.NewMasterPasswordHash, None); user.akey = data.Key; - user.save(&mut conn).await + let save_result = user.save(&mut conn).await; + + nt.send_user_update(UpdateType::LogOut, &user).await; + + save_result } #[derive(Deserialize)] @@ -388,6 +397,7 @@ async fn post_rotatekey( // 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. + // We force the users to logout after the user has been saved to try and prevent these issues. update_cipher_from_data(&mut saved_cipher, cipher_data, &headers, false, &mut conn, &ip, &nt, UpdateType::None) .await? } @@ -399,11 +409,20 @@ async fn post_rotatekey( user.private_key = Some(data.PrivateKey); user.reset_security_stamp(); - user.save(&mut conn).await + let save_result = user.save(&mut conn).await; + + nt.send_user_update(UpdateType::LogOut, &user).await; + + save_result } #[post("/accounts/security-stamp", data = "")] -async fn post_sstamp(data: JsonUpcase, headers: Headers, mut conn: DbConn) -> EmptyResult { +async fn post_sstamp( + data: JsonUpcase, + headers: Headers, + mut conn: DbConn, + nt: Notify<'_>, +) -> EmptyResult { let data: PasswordData = data.into_inner().data; let mut user = headers.user; @@ -413,7 +432,11 @@ async fn post_sstamp(data: JsonUpcase, headers: Headers, mut conn: Device::delete_all_by_user(&user.uuid, &mut conn).await?; user.reset_security_stamp(); - user.save(&mut conn).await + let save_result = user.save(&mut conn).await; + + nt.send_user_update(UpdateType::LogOut, &user).await; + + save_result } #[derive(Deserialize)] @@ -465,7 +488,12 @@ struct ChangeEmailData { } #[post("/accounts/email", data = "")] -async fn post_email(data: JsonUpcase, headers: Headers, mut conn: DbConn) -> EmptyResult { +async fn post_email( + data: JsonUpcase, + headers: Headers, + mut conn: DbConn, + nt: Notify<'_>, +) -> EmptyResult { let data: ChangeEmailData = data.into_inner().data; let mut user = headers.user; @@ -507,8 +535,11 @@ async fn post_email(data: JsonUpcase, headers: Headers, mut con user.set_password(&data.NewMasterPasswordHash, None); user.akey = data.Key; + let save_result = user.save(&mut conn).await; - user.save(&mut conn).await + nt.send_user_update(UpdateType::LogOut, &user).await; + + save_result } #[post("/accounts/verify-email")] diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index 7077f905..9750e4b6 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -310,7 +310,8 @@ async fn post_ciphers( data.LastKnownRevisionDate = None; let mut cipher = Cipher::new(data.Type, data.Name.clone()); - update_cipher_from_data(&mut cipher, data, &headers, false, &mut conn, &ip, &nt, UpdateType::CipherCreate).await?; + update_cipher_from_data(&mut cipher, data, &headers, false, &mut conn, &ip, &nt, UpdateType::SyncCipherCreate) + .await?; Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, &mut conn).await)) } @@ -415,7 +416,14 @@ pub async fn update_cipher_from_data( for (id, attachment) in attachments { let mut saved_att = match Attachment::find_by_id(&id, conn).await { Some(att) => att, - None => err!("Attachment doesn't exist"), + None => { + // Warn and continue here. + // A missing attachment means it was removed via an other client. + // Also the Desktop Client supports removing attachments and save an update afterwards. + // Bitwarden it self ignores these mismatches server side. + warn!("Attachment {id} doesn't exist"); + continue; + } }; if saved_att.cipher_uuid != cipher.uuid { @@ -482,8 +490,8 @@ pub async fn update_cipher_from_data( // Only log events for organizational ciphers if let Some(org_uuid) = &cipher.organization_uuid { let event_type = match (&ut, transfer_cipher) { - (UpdateType::CipherCreate, true) => EventType::CipherCreated, - (UpdateType::CipherUpdate, true) => EventType::CipherShared, + (UpdateType::SyncCipherCreate, true) => EventType::CipherCreated, + (UpdateType::SyncCipherUpdate, true) => EventType::CipherShared, (_, _) => EventType::CipherUpdated, }; @@ -499,7 +507,7 @@ pub async fn update_cipher_from_data( .await; } - nt.send_cipher_update(ut, cipher, &cipher.update_users_revision(conn).await).await; + nt.send_cipher_update(ut, cipher, &cipher.update_users_revision(conn).await, &headers.device.uuid).await; } Ok(()) @@ -562,7 +570,7 @@ async fn post_ciphers_import( let mut user = headers.user; user.update_revision(&mut conn).await?; - nt.send_user_update(UpdateType::Vault, &user).await; + nt.send_user_update(UpdateType::SyncVault, &user).await; Ok(()) } @@ -628,7 +636,8 @@ async fn put_cipher( err!("Cipher is not write accessible") } - update_cipher_from_data(&mut cipher, data, &headers, false, &mut conn, &ip, &nt, UpdateType::CipherUpdate).await?; + update_cipher_from_data(&mut cipher, data, &headers, false, &mut conn, &ip, &nt, UpdateType::SyncCipherUpdate) + .await?; Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, &mut conn).await)) } @@ -850,9 +859,9 @@ async fn share_cipher_by_uuid( // When LastKnownRevisionDate is None, it is a new cipher, so send CipherCreate. let ut = if data.Cipher.LastKnownRevisionDate.is_some() { - UpdateType::CipherUpdate + UpdateType::SyncCipherUpdate } else { - UpdateType::CipherCreate + UpdateType::SyncCipherCreate }; update_cipher_from_data(&mut cipher, data.Cipher, headers, shared_to_collection, conn, ip, nt, ut).await?; @@ -1054,7 +1063,13 @@ async fn save_attachment( data.data.move_copy_to(file_path).await? } - nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(&mut conn).await).await; + nt.send_cipher_update( + UpdateType::SyncCipherUpdate, + &cipher, + &cipher.update_users_revision(&mut conn).await, + &headers.device.uuid, + ) + .await; if let Some(org_uuid) = &cipher.organization_uuid { log_event( @@ -1390,7 +1405,7 @@ async fn move_cipher_selected( // Move cipher cipher.move_to_folder(data.FolderId.clone(), &user_uuid, &mut conn).await?; - nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &[user_uuid.clone()]).await; + nt.send_cipher_update(UpdateType::SyncCipherUpdate, &cipher, &[user_uuid.clone()], &headers.device.uuid).await; } Ok(()) @@ -1438,7 +1453,7 @@ async fn delete_all( Some(user_org) => { if user_org.atype == UserOrgType::Owner { Cipher::delete_all_by_organization(&org_data.org_id, &mut conn).await?; - nt.send_user_update(UpdateType::Vault, &user).await; + nt.send_user_update(UpdateType::SyncVault, &user).await; log_event( EventType::OrganizationPurgedVault as i32, @@ -1471,7 +1486,7 @@ async fn delete_all( } user.update_revision(&mut conn).await?; - nt.send_user_update(UpdateType::Vault, &user).await; + nt.send_user_update(UpdateType::SyncVault, &user).await; Ok(()) } } @@ -1497,10 +1512,22 @@ async fn _delete_cipher_by_uuid( if soft_delete { cipher.deleted_at = Some(Utc::now().naive_utc()); cipher.save(conn).await?; - nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(conn).await).await; + nt.send_cipher_update( + UpdateType::SyncCipherUpdate, + &cipher, + &cipher.update_users_revision(conn).await, + &headers.device.uuid, + ) + .await; } else { cipher.delete(conn).await?; - nt.send_cipher_update(UpdateType::CipherDelete, &cipher, &cipher.update_users_revision(conn).await).await; + nt.send_cipher_update( + UpdateType::SyncCipherDelete, + &cipher, + &cipher.update_users_revision(conn).await, + &headers.device.uuid, + ) + .await; } if let Some(org_uuid) = cipher.organization_uuid { @@ -1562,7 +1589,13 @@ async fn _restore_cipher_by_uuid( cipher.deleted_at = None; cipher.save(conn).await?; - nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(conn).await).await; + nt.send_cipher_update( + UpdateType::SyncCipherUpdate, + &cipher, + &cipher.update_users_revision(conn).await, + &headers.device.uuid, + ) + .await; if let Some(org_uuid) = &cipher.organization_uuid { log_event( EventType::CipherRestored as i32, @@ -1639,7 +1672,13 @@ async fn _delete_cipher_attachment_by_id( // Delete attachment attachment.delete(conn).await?; - nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(conn).await).await; + nt.send_cipher_update( + UpdateType::SyncCipherUpdate, + &cipher, + &cipher.update_users_revision(conn).await, + &headers.device.uuid, + ) + .await; if let Some(org_uuid) = cipher.organization_uuid { log_event( EventType::CipherAttachmentDeleted as i32, diff --git a/src/api/core/folders.rs b/src/api/core/folders.rs index 95155803..c27d5455 100644 --- a/src/api/core/folders.rs +++ b/src/api/core/folders.rs @@ -50,7 +50,7 @@ async fn post_folders(data: JsonUpcase, headers: Headers, mut conn: let mut folder = Folder::new(headers.user.uuid, data.Name); folder.save(&mut conn).await?; - nt.send_folder_update(UpdateType::FolderCreate, &folder).await; + nt.send_folder_update(UpdateType::SyncFolderCreate, &folder, &headers.device.uuid).await; Ok(Json(folder.to_json())) } @@ -88,7 +88,7 @@ async fn put_folder( folder.name = data.Name; folder.save(&mut conn).await?; - nt.send_folder_update(UpdateType::FolderUpdate, &folder).await; + nt.send_folder_update(UpdateType::SyncFolderUpdate, &folder, &headers.device.uuid).await; Ok(Json(folder.to_json())) } @@ -112,6 +112,6 @@ async fn delete_folder(uuid: String, headers: Headers, mut conn: DbConn, nt: Not // Delete the actual folder entry folder.delete(&mut conn).await?; - nt.send_folder_update(UpdateType::FolderDelete, &folder).await; + nt.send_folder_update(UpdateType::SyncFolderDelete, &folder, &headers.device.uuid).await; Ok(()) } diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs index 885fae81..3393a4d0 100644 --- a/src/api/core/mod.rs +++ b/src/api/core/mod.rs @@ -7,8 +7,7 @@ mod organizations; mod sends; pub mod two_factor; -pub use ciphers::purge_trashed_ciphers; -pub use ciphers::{CipherSyncData, CipherSyncType}; +pub use ciphers::{purge_trashed_ciphers, CipherSyncData, CipherSyncType}; pub use emergency_access::{emergency_notification_reminder_job, emergency_request_timeout_job}; pub use events::{event_cleanup_job, log_event, log_user_event}; pub use sends::purge_sends; @@ -47,13 +46,11 @@ pub fn events_routes() -> Vec { // // Move this somewhere else // -use rocket::serde::json::Json; -use rocket::Catcher; -use rocket::Route; +use rocket::{serde::json::Json, Catcher, Route}; use serde_json::Value; use crate::{ - api::{JsonResult, JsonUpcase}, + api::{JsonResult, JsonUpcase, Notify, UpdateType}, auth::Headers, db::DbConn, error::Error, @@ -138,7 +135,12 @@ struct EquivDomainData { } #[post("/settings/domains", data = "")] -async fn post_eq_domains(data: JsonUpcase, headers: Headers, mut conn: DbConn) -> JsonResult { +async fn post_eq_domains( + data: JsonUpcase, + headers: Headers, + mut conn: DbConn, + nt: Notify<'_>, +) -> JsonResult { let data: EquivDomainData = data.into_inner().data; let excluded_globals = data.ExcludedGlobalEquivalentDomains.unwrap_or_default(); @@ -152,12 +154,19 @@ async fn post_eq_domains(data: JsonUpcase, headers: Headers, mu user.save(&mut conn).await?; + nt.send_user_update(UpdateType::SyncSettings, &user).await; + Ok(Json(json!({}))) } #[put("/settings/domains", data = "")] -async fn put_eq_domains(data: JsonUpcase, headers: Headers, conn: DbConn) -> JsonResult { - post_eq_domains(data, headers, conn).await +async fn put_eq_domains( + data: JsonUpcase, + headers: Headers, + conn: DbConn, + nt: Notify<'_>, +) -> JsonResult { + post_eq_domains(data, headers, conn, nt).await } #[get("/hibp/breach?")] diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index d50a1f12..60d6f714 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -957,6 +957,7 @@ async fn bulk_confirm_invite( headers: AdminHeaders, mut conn: DbConn, ip: ClientIp, + nt: Notify<'_>, ) -> Json { let data = data.into_inner().data; @@ -966,7 +967,8 @@ async fn bulk_confirm_invite( for invite in keys { let org_user_id = invite["Id"].as_str().unwrap_or_default(); let user_key = invite["Key"].as_str().unwrap_or_default(); - let err_msg = match _confirm_invite(&org_id, org_user_id, user_key, &headers, &mut conn, &ip).await { + let err_msg = match _confirm_invite(&org_id, org_user_id, user_key, &headers, &mut conn, &ip, &nt).await + { Ok(_) => String::new(), Err(e) => format!("{:?}", e), }; @@ -998,10 +1000,11 @@ async fn confirm_invite( headers: AdminHeaders, mut conn: DbConn, ip: ClientIp, + nt: Notify<'_>, ) -> EmptyResult { let data = data.into_inner().data; let user_key = data["Key"].as_str().unwrap_or_default(); - _confirm_invite(&org_id, &org_user_id, user_key, &headers, &mut conn, &ip).await + _confirm_invite(&org_id, &org_user_id, user_key, &headers, &mut conn, &ip, &nt).await } async fn _confirm_invite( @@ -1011,6 +1014,7 @@ async fn _confirm_invite( headers: &AdminHeaders, conn: &mut DbConn, ip: &ClientIp, + nt: &Notify<'_>, ) -> EmptyResult { if key.is_empty() || org_user_id.is_empty() { err!("Key or UserId is not set, unable to process request"); @@ -1069,7 +1073,13 @@ async fn _confirm_invite( mail::send_invite_confirmed(&address, &org_name).await?; } - user_to_confirm.save(conn).await + let save_result = user_to_confirm.save(conn).await; + + if let Some(user) = User::find_by_uuid(&user_to_confirm.user_uuid, conn).await { + nt.send_user_update(UpdateType::SyncOrgKeys, &user).await; + } + + save_result } #[get("/organizations//users/")] @@ -1206,12 +1216,13 @@ async fn bulk_delete_user( headers: AdminHeaders, mut conn: DbConn, ip: ClientIp, + nt: Notify<'_>, ) -> Json { let data: OrgBulkIds = data.into_inner().data; let mut bulk_response = Vec::new(); for org_user_id in data.Ids { - let err_msg = match _delete_user(&org_id, &org_user_id, &headers, &mut conn, &ip).await { + let err_msg = match _delete_user(&org_id, &org_user_id, &headers, &mut conn, &ip, &nt).await { Ok(_) => String::new(), Err(e) => format!("{:?}", e), }; @@ -1239,8 +1250,9 @@ async fn delete_user( headers: AdminHeaders, mut conn: DbConn, ip: ClientIp, + nt: Notify<'_>, ) -> EmptyResult { - _delete_user(&org_id, &org_user_id, &headers, &mut conn, &ip).await + _delete_user(&org_id, &org_user_id, &headers, &mut conn, &ip, &nt).await } #[post("/organizations//users//delete")] @@ -1250,8 +1262,9 @@ async fn post_delete_user( headers: AdminHeaders, mut conn: DbConn, ip: ClientIp, + nt: Notify<'_>, ) -> EmptyResult { - _delete_user(&org_id, &org_user_id, &headers, &mut conn, &ip).await + _delete_user(&org_id, &org_user_id, &headers, &mut conn, &ip, &nt).await } async fn _delete_user( @@ -1260,6 +1273,7 @@ async fn _delete_user( headers: &AdminHeaders, conn: &mut DbConn, ip: &ClientIp, + nt: &Notify<'_>, ) -> EmptyResult { let user_to_delete = match UserOrganization::find_by_uuid_and_org(org_user_id, org_id, conn).await { Some(user) => user, @@ -1288,6 +1302,10 @@ async fn _delete_user( ) .await; + if let Some(user) = User::find_by_uuid(&user_to_delete.user_uuid, conn).await { + nt.send_user_update(UpdateType::SyncOrgKeys, &user).await; + } + user_to_delete.delete(conn).await } diff --git a/src/api/core/sends.rs b/src/api/core/sends.rs index 90fd560c..7d021d08 100644 --- a/src/api/core/sends.rs +++ b/src/api/core/sends.rs @@ -355,6 +355,7 @@ async fn post_access( data: JsonUpcase, mut conn: DbConn, ip: ClientIp, + nt: Notify<'_>, ) -> JsonResult { let mut send = match Send::find_by_access_id(&access_id, &mut conn).await { Some(s) => s, @@ -396,6 +397,8 @@ async fn post_access( send.save(&mut conn).await?; + nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&mut conn).await).await; + Ok(Json(send.to_json_access(&mut conn).await)) } @@ -406,6 +409,7 @@ async fn post_access_file( data: JsonUpcase, host: Host, mut conn: DbConn, + nt: Notify<'_>, ) -> JsonResult { let mut send = match Send::find_by_uuid(&send_id, &mut conn).await { Some(s) => s, @@ -444,6 +448,8 @@ async fn post_access_file( send.save(&mut conn).await?; + nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&mut conn).await).await; + let token_claims = crate::auth::generate_send_claims(&send_id, &file_id); let token = crate::auth::encode_jwt(&token_claims); Ok(Json(json!({ diff --git a/src/api/notifications.rs b/src/api/notifications.rs index cd53c96d..b51e1380 100644 --- a/src/api/notifications.rs +++ b/src/api/notifications.rs @@ -164,12 +164,13 @@ impl WebSocketUsers { let data = create_update( vec![("UserId".into(), user.uuid.clone().into()), ("Date".into(), serialize_date(user.updated_at))], ut, + None, ); self.send_update(&user.uuid, &data).await; } - pub async fn send_folder_update(&self, ut: UpdateType, folder: &Folder) { + pub async fn send_folder_update(&self, ut: UpdateType, folder: &Folder, acting_device_uuid: &String) { let data = create_update( vec![ ("Id".into(), folder.uuid.clone().into()), @@ -177,12 +178,19 @@ impl WebSocketUsers { ("RevisionDate".into(), serialize_date(folder.updated_at)), ], ut, + Some(acting_device_uuid.into()), ); self.send_update(&folder.user_uuid, &data).await; } - pub async fn send_cipher_update(&self, ut: UpdateType, cipher: &Cipher, user_uuids: &[String]) { + pub async fn send_cipher_update( + &self, + ut: UpdateType, + cipher: &Cipher, + user_uuids: &[String], + acting_device_uuid: &String, + ) { let user_uuid = convert_option(cipher.user_uuid.clone()); let org_uuid = convert_option(cipher.organization_uuid.clone()); @@ -195,6 +203,7 @@ impl WebSocketUsers { ("RevisionDate".into(), serialize_date(cipher.updated_at)), ], ut, + Some(acting_device_uuid.into()), ); for uuid in user_uuids { @@ -212,6 +221,7 @@ impl WebSocketUsers { ("RevisionDate".into(), serialize_date(send.revision_date)), ], ut, + None, ); for uuid in user_uuids { @@ -228,14 +238,14 @@ impl WebSocketUsers { "ReceiveMessage", // Target [ // Arguments { - "ContextId": "app_id", + "ContextId": acting_device_uuid || Nil, "Type": ut as i32, "Payload": {} } ] ] */ -fn create_update(payload: Vec<(Value, Value)>, ut: UpdateType) -> Vec { +fn create_update(payload: Vec<(Value, Value)>, ut: UpdateType, acting_device_uuid: Option) -> Vec { use rmpv::Value as V; let value = V::Array(vec![ @@ -244,7 +254,7 @@ fn create_update(payload: Vec<(Value, Value)>, ut: UpdateType) -> Vec { V::Nil, "ReceiveMessage".into(), V::Array(vec![V::Map(vec![ - ("ContextId".into(), "app_id".into()), + ("ContextId".into(), acting_device_uuid.map(|v| v.into()).unwrap_or_else(|| V::Nil)), ("Type".into(), (ut as i32).into()), ("Payload".into(), payload.into()), ])]), @@ -260,17 +270,17 @@ fn create_ping() -> Vec { #[allow(dead_code)] #[derive(Eq, PartialEq)] pub enum UpdateType { - CipherUpdate = 0, - CipherCreate = 1, - LoginDelete = 2, - FolderDelete = 3, - Ciphers = 4, - - Vault = 5, - OrgKeys = 6, - FolderCreate = 7, - FolderUpdate = 8, - CipherDelete = 9, + SyncCipherUpdate = 0, + SyncCipherCreate = 1, + SyncLoginDelete = 2, + SyncFolderDelete = 3, + SyncCiphers = 4, + + SyncVault = 5, + SyncOrgKeys = 6, + SyncFolderCreate = 7, + SyncFolderUpdate = 8, + SyncCipherDelete = 9, SyncSettings = 10, LogOut = 11, @@ -279,6 +289,9 @@ pub enum UpdateType { SyncSendUpdate = 13, SyncSendDelete = 14, + AuthRequest = 15, + AuthRequestResponse = 16, + None = 100, }