Merge branch 'BlackDex-update-notifications'

This commit is contained in:
Daniel García 2023-01-09 18:24:00 +01:00
commit 05a552910c
No known key found for this signature in database
GPG Key ID: FC8A7D14C3CD543A
8 changed files with 187 additions and 63 deletions

View File

@ -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/<uuid>/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/<uuid>/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/<uuid>/enable")]

View File

@ -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 = "<data>")]
async fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, mut conn: DbConn) -> EmptyResult {
async fn post_kdf(data: JsonUpcase<ChangeKdfData>, 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<ChangeKdfData>, 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 = "<data>")]
async fn post_sstamp(data: JsonUpcase<PasswordData>, headers: Headers, mut conn: DbConn) -> EmptyResult {
async fn post_sstamp(
data: JsonUpcase<PasswordData>,
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<PasswordData>, 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 = "<data>")]
async fn post_email(data: JsonUpcase<ChangeEmailData>, headers: Headers, mut conn: DbConn) -> EmptyResult {
async fn post_email(
data: JsonUpcase<ChangeEmailData>,
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<ChangeEmailData>, 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")]

View File

@ -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,

View File

@ -50,7 +50,7 @@ async fn post_folders(data: JsonUpcase<FolderData>, 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(())
}

View File

@ -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<Route> {
//
// 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 = "<data>")]
async fn post_eq_domains(data: JsonUpcase<EquivDomainData>, headers: Headers, mut conn: DbConn) -> JsonResult {
async fn post_eq_domains(
data: JsonUpcase<EquivDomainData>,
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<EquivDomainData>, headers: Headers, mu
user.save(&mut conn).await?;
nt.send_user_update(UpdateType::SyncSettings, &user).await;
Ok(Json(json!({})))
}
#[put("/settings/domains", data = "<data>")]
async fn put_eq_domains(data: JsonUpcase<EquivDomainData>, headers: Headers, conn: DbConn) -> JsonResult {
post_eq_domains(data, headers, conn).await
async fn put_eq_domains(
data: JsonUpcase<EquivDomainData>,
headers: Headers,
conn: DbConn,
nt: Notify<'_>,
) -> JsonResult {
post_eq_domains(data, headers, conn, nt).await
}
#[get("/hibp/breach?<username>")]

View File

@ -957,6 +957,7 @@ async fn bulk_confirm_invite(
headers: AdminHeaders,
mut conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> Json<Value> {
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/<org_id>/users/<org_user_id>")]
@ -1206,12 +1216,13 @@ async fn bulk_delete_user(
headers: AdminHeaders,
mut conn: DbConn,
ip: ClientIp,
nt: Notify<'_>,
) -> Json<Value> {
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/<org_id>/users/<org_user_id>/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
}

View File

@ -355,6 +355,7 @@ async fn post_access(
data: JsonUpcase<SendAccessData>,
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<SendAccessData>,
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!({

View File

@ -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<u8> {
fn create_update(payload: Vec<(Value, Value)>, ut: UpdateType, acting_device_uuid: Option<String>) -> Vec<u8> {
use rmpv::Value as V;
let value = V::Array(vec![
@ -244,7 +254,7 @@ fn create_update(payload: Vec<(Value, Value)>, ut: UpdateType) -> Vec<u8> {
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<u8> {
#[allow(dead_code)]
#[derive(Eq, PartialEq)]
pub enum UpdateType {
CipherUpdate = 0,
CipherCreate = 1,
LoginDelete = 2,
FolderDelete = 3,
Ciphers = 4,
SyncCipherUpdate = 0,
SyncCipherCreate = 1,
SyncLoginDelete = 2,
SyncFolderDelete = 3,
SyncCiphers = 4,
Vault = 5,
OrgKeys = 6,
FolderCreate = 7,
FolderUpdate = 8,
CipherDelete = 9,
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,
}