Update WebSocket Notifications

Previously the websocket notifications were using `app_id` as the
`ContextId`. This was incorrect and should have been the device_uuid
from the client device executing the request. The clients will ignore
the websocket request if the uuid matches. This also fixes some issues
with the Desktop client which is able to modify attachments within the
same screen and causes an issue when saving the attachment afterwards.

Also changed the way to handle removed attachments, since that causes an
error saving the vault cipher afterwards, complaining about a missing
attachment. Bitwarden ignores this, and continues with the remaining
attachments (if any). This also fixes #2591 .

Further some more websocket notifications have been added to some other
functions which enhance the user experience.

- Logout users when deauthed, changed password, rotated keys
- Trigger OrgSyncKeys on user confirm and removal
- Added some extra to the send feature

Also renamed UpdateTypes to match Bitwarden naming.
This commit is contained in:
BlackDex 2022-12-30 21:23:55 +01:00 committed by Daniel García
parent bdd918b4d4
commit 2972904eb8
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::{ 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}, auth::{decode_admin, encode_jwt, generate_admin_claims, ClientIp},
config::ConfigBuilder, config::ConfigBuilder,
db::{backup_database, get_sql_server_version, models::*, DbConn, DbConnType}, 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")] #[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?; let mut user = get_user_or_404(&uuid, &mut conn).await?;
Device::delete_all_by_user(&user.uuid, &mut conn).await?; Device::delete_all_by_user(&user.uuid, &mut conn).await?;
user.reset_security_stamp(); 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")] #[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?; let mut user = get_user_or_404(&uuid, &mut conn).await?;
Device::delete_all_by_user(&user.uuid, &mut conn).await?; Device::delete_all_by_user(&user.uuid, &mut conn).await?;
user.reset_security_stamp(); user.reset_security_stamp();
user.enabled = false; 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")] #[post("/users/<uuid>/enable")]

View File

@ -275,6 +275,7 @@ async fn post_password(
headers: Headers, headers: Headers,
mut conn: DbConn, mut conn: DbConn,
ip: ClientIp, ip: ClientIp,
nt: Notify<'_>,
) -> EmptyResult { ) -> EmptyResult {
let data: ChangePassData = data.into_inner().data; let data: ChangePassData = data.into_inner().data;
let mut user = headers.user; 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")]), Some(vec![String::from("post_rotatekey"), String::from("get_contacts"), String::from("get_public_keys")]),
); );
user.akey = data.Key; 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)] #[derive(Deserialize)]
@ -308,7 +313,7 @@ struct ChangeKdfData {
} }
#[post("/accounts/kdf", data = "<data>")] #[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 data: ChangeKdfData = data.into_inner().data;
let mut user = headers.user; 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.client_kdf_type = data.Kdf;
user.set_password(&data.NewMasterPasswordHash, None); user.set_password(&data.NewMasterPasswordHash, None);
user.akey = data.Key; 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)] #[derive(Deserialize)]
@ -388,6 +397,7 @@ async fn post_rotatekey(
// Prevent triggering cipher updates via WebSockets by settings UpdateType::None // 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. // 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) update_cipher_from_data(&mut saved_cipher, cipher_data, &headers, false, &mut conn, &ip, &nt, UpdateType::None)
.await? .await?
} }
@ -399,11 +409,20 @@ async fn post_rotatekey(
user.private_key = Some(data.PrivateKey); user.private_key = Some(data.PrivateKey);
user.reset_security_stamp(); 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>")] #[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 data: PasswordData = data.into_inner().data;
let mut user = headers.user; 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?; Device::delete_all_by_user(&user.uuid, &mut conn).await?;
user.reset_security_stamp(); 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)] #[derive(Deserialize)]
@ -465,7 +488,12 @@ struct ChangeEmailData {
} }
#[post("/accounts/email", data = "<data>")] #[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 data: ChangeEmailData = data.into_inner().data;
let mut user = headers.user; 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.set_password(&data.NewMasterPasswordHash, None);
user.akey = data.Key; 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")] #[post("/accounts/verify-email")]

View File

@ -310,7 +310,8 @@ async fn post_ciphers(
data.LastKnownRevisionDate = None; data.LastKnownRevisionDate = None;
let mut cipher = Cipher::new(data.Type, data.Name.clone()); 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)) 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 { for (id, attachment) in attachments {
let mut saved_att = match Attachment::find_by_id(&id, conn).await { let mut saved_att = match Attachment::find_by_id(&id, conn).await {
Some(att) => att, 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 { if saved_att.cipher_uuid != cipher.uuid {
@ -482,8 +490,8 @@ pub async fn update_cipher_from_data(
// Only log events for organizational ciphers // Only log events for organizational ciphers
if let Some(org_uuid) = &cipher.organization_uuid { if let Some(org_uuid) = &cipher.organization_uuid {
let event_type = match (&ut, transfer_cipher) { let event_type = match (&ut, transfer_cipher) {
(UpdateType::CipherCreate, true) => EventType::CipherCreated, (UpdateType::SyncCipherCreate, true) => EventType::CipherCreated,
(UpdateType::CipherUpdate, true) => EventType::CipherShared, (UpdateType::SyncCipherUpdate, true) => EventType::CipherShared,
(_, _) => EventType::CipherUpdated, (_, _) => EventType::CipherUpdated,
}; };
@ -499,7 +507,7 @@ pub async fn update_cipher_from_data(
.await; .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(()) Ok(())
@ -562,7 +570,7 @@ async fn post_ciphers_import(
let mut user = headers.user; let mut user = headers.user;
user.update_revision(&mut conn).await?; user.update_revision(&mut conn).await?;
nt.send_user_update(UpdateType::Vault, &user).await; nt.send_user_update(UpdateType::SyncVault, &user).await;
Ok(()) Ok(())
} }
@ -628,7 +636,8 @@ async fn put_cipher(
err!("Cipher is not write accessible") 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)) 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. // When LastKnownRevisionDate is None, it is a new cipher, so send CipherCreate.
let ut = if data.Cipher.LastKnownRevisionDate.is_some() { let ut = if data.Cipher.LastKnownRevisionDate.is_some() {
UpdateType::CipherUpdate UpdateType::SyncCipherUpdate
} else { } else {
UpdateType::CipherCreate UpdateType::SyncCipherCreate
}; };
update_cipher_from_data(&mut cipher, data.Cipher, headers, shared_to_collection, conn, ip, nt, ut).await?; 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? 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 { if let Some(org_uuid) = &cipher.organization_uuid {
log_event( log_event(
@ -1390,7 +1405,7 @@ async fn move_cipher_selected(
// Move cipher // Move cipher
cipher.move_to_folder(data.FolderId.clone(), &user_uuid, &mut conn).await?; 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(()) Ok(())
@ -1438,7 +1453,7 @@ async fn delete_all(
Some(user_org) => { Some(user_org) => {
if user_org.atype == UserOrgType::Owner { if user_org.atype == UserOrgType::Owner {
Cipher::delete_all_by_organization(&org_data.org_id, &mut conn).await?; 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( log_event(
EventType::OrganizationPurgedVault as i32, EventType::OrganizationPurgedVault as i32,
@ -1471,7 +1486,7 @@ async fn delete_all(
} }
user.update_revision(&mut conn).await?; user.update_revision(&mut conn).await?;
nt.send_user_update(UpdateType::Vault, &user).await; nt.send_user_update(UpdateType::SyncVault, &user).await;
Ok(()) Ok(())
} }
} }
@ -1497,10 +1512,22 @@ async fn _delete_cipher_by_uuid(
if soft_delete { if soft_delete {
cipher.deleted_at = Some(Utc::now().naive_utc()); cipher.deleted_at = Some(Utc::now().naive_utc());
cipher.save(conn).await?; 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 { } else {
cipher.delete(conn).await?; 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 { if let Some(org_uuid) = cipher.organization_uuid {
@ -1562,7 +1589,13 @@ async fn _restore_cipher_by_uuid(
cipher.deleted_at = None; cipher.deleted_at = None;
cipher.save(conn).await?; 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 { if let Some(org_uuid) = &cipher.organization_uuid {
log_event( log_event(
EventType::CipherRestored as i32, EventType::CipherRestored as i32,
@ -1639,7 +1672,13 @@ async fn _delete_cipher_attachment_by_id(
// Delete attachment // Delete attachment
attachment.delete(conn).await?; 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 { if let Some(org_uuid) = cipher.organization_uuid {
log_event( log_event(
EventType::CipherAttachmentDeleted as i32, 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); let mut folder = Folder::new(headers.user.uuid, data.Name);
folder.save(&mut conn).await?; 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())) Ok(Json(folder.to_json()))
} }
@ -88,7 +88,7 @@ async fn put_folder(
folder.name = data.Name; folder.name = data.Name;
folder.save(&mut conn).await?; 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())) 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 // Delete the actual folder entry
folder.delete(&mut conn).await?; 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(()) Ok(())
} }

View File

@ -7,8 +7,7 @@ mod organizations;
mod sends; mod sends;
pub mod two_factor; pub mod two_factor;
pub use ciphers::purge_trashed_ciphers; pub use ciphers::{purge_trashed_ciphers, CipherSyncData, CipherSyncType};
pub use ciphers::{CipherSyncData, CipherSyncType};
pub use emergency_access::{emergency_notification_reminder_job, emergency_request_timeout_job}; 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 events::{event_cleanup_job, log_event, log_user_event};
pub use sends::purge_sends; pub use sends::purge_sends;
@ -47,13 +46,11 @@ pub fn events_routes() -> Vec<Route> {
// //
// Move this somewhere else // Move this somewhere else
// //
use rocket::serde::json::Json; use rocket::{serde::json::Json, Catcher, Route};
use rocket::Catcher;
use rocket::Route;
use serde_json::Value; use serde_json::Value;
use crate::{ use crate::{
api::{JsonResult, JsonUpcase}, api::{JsonResult, JsonUpcase, Notify, UpdateType},
auth::Headers, auth::Headers,
db::DbConn, db::DbConn,
error::Error, error::Error,
@ -138,7 +135,12 @@ struct EquivDomainData {
} }
#[post("/settings/domains", data = "<data>")] #[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 data: EquivDomainData = data.into_inner().data;
let excluded_globals = data.ExcludedGlobalEquivalentDomains.unwrap_or_default(); 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?; user.save(&mut conn).await?;
nt.send_user_update(UpdateType::SyncSettings, &user).await;
Ok(Json(json!({}))) Ok(Json(json!({})))
} }
#[put("/settings/domains", data = "<data>")] #[put("/settings/domains", data = "<data>")]
async fn put_eq_domains(data: JsonUpcase<EquivDomainData>, headers: Headers, conn: DbConn) -> JsonResult { async fn put_eq_domains(
post_eq_domains(data, headers, conn).await data: JsonUpcase<EquivDomainData>,
headers: Headers,
conn: DbConn,
nt: Notify<'_>,
) -> JsonResult {
post_eq_domains(data, headers, conn, nt).await
} }
#[get("/hibp/breach?<username>")] #[get("/hibp/breach?<username>")]

View File

@ -957,6 +957,7 @@ async fn bulk_confirm_invite(
headers: AdminHeaders, headers: AdminHeaders,
mut conn: DbConn, mut conn: DbConn,
ip: ClientIp, ip: ClientIp,
nt: Notify<'_>,
) -> Json<Value> { ) -> Json<Value> {
let data = data.into_inner().data; let data = data.into_inner().data;
@ -966,7 +967,8 @@ async fn bulk_confirm_invite(
for invite in keys { for invite in keys {
let org_user_id = invite["Id"].as_str().unwrap_or_default(); let org_user_id = invite["Id"].as_str().unwrap_or_default();
let user_key = invite["Key"].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(), Ok(_) => String::new(),
Err(e) => format!("{:?}", e), Err(e) => format!("{:?}", e),
}; };
@ -998,10 +1000,11 @@ async fn confirm_invite(
headers: AdminHeaders, headers: AdminHeaders,
mut conn: DbConn, mut conn: DbConn,
ip: ClientIp, ip: ClientIp,
nt: Notify<'_>,
) -> EmptyResult { ) -> EmptyResult {
let data = data.into_inner().data; let data = data.into_inner().data;
let user_key = data["Key"].as_str().unwrap_or_default(); 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( async fn _confirm_invite(
@ -1011,6 +1014,7 @@ async fn _confirm_invite(
headers: &AdminHeaders, headers: &AdminHeaders,
conn: &mut DbConn, conn: &mut DbConn,
ip: &ClientIp, ip: &ClientIp,
nt: &Notify<'_>,
) -> EmptyResult { ) -> EmptyResult {
if key.is_empty() || org_user_id.is_empty() { if key.is_empty() || org_user_id.is_empty() {
err!("Key or UserId is not set, unable to process request"); 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?; 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>")] #[get("/organizations/<org_id>/users/<org_user_id>")]
@ -1206,12 +1216,13 @@ async fn bulk_delete_user(
headers: AdminHeaders, headers: AdminHeaders,
mut conn: DbConn, mut conn: DbConn,
ip: ClientIp, ip: ClientIp,
nt: Notify<'_>,
) -> Json<Value> { ) -> Json<Value> {
let data: OrgBulkIds = data.into_inner().data; let data: OrgBulkIds = data.into_inner().data;
let mut bulk_response = Vec::new(); let mut bulk_response = Vec::new();
for org_user_id in data.Ids { 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(), Ok(_) => String::new(),
Err(e) => format!("{:?}", e), Err(e) => format!("{:?}", e),
}; };
@ -1239,8 +1250,9 @@ async fn delete_user(
headers: AdminHeaders, headers: AdminHeaders,
mut conn: DbConn, mut conn: DbConn,
ip: ClientIp, ip: ClientIp,
nt: Notify<'_>,
) -> EmptyResult { ) -> 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")] #[post("/organizations/<org_id>/users/<org_user_id>/delete")]
@ -1250,8 +1262,9 @@ async fn post_delete_user(
headers: AdminHeaders, headers: AdminHeaders,
mut conn: DbConn, mut conn: DbConn,
ip: ClientIp, ip: ClientIp,
nt: Notify<'_>,
) -> EmptyResult { ) -> 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( async fn _delete_user(
@ -1260,6 +1273,7 @@ async fn _delete_user(
headers: &AdminHeaders, headers: &AdminHeaders,
conn: &mut DbConn, conn: &mut DbConn,
ip: &ClientIp, ip: &ClientIp,
nt: &Notify<'_>,
) -> EmptyResult { ) -> EmptyResult {
let user_to_delete = match UserOrganization::find_by_uuid_and_org(org_user_id, org_id, conn).await { let user_to_delete = match UserOrganization::find_by_uuid_and_org(org_user_id, org_id, conn).await {
Some(user) => user, Some(user) => user,
@ -1288,6 +1302,10 @@ async fn _delete_user(
) )
.await; .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 user_to_delete.delete(conn).await
} }

View File

@ -355,6 +355,7 @@ async fn post_access(
data: JsonUpcase<SendAccessData>, data: JsonUpcase<SendAccessData>,
mut conn: DbConn, mut conn: DbConn,
ip: ClientIp, ip: ClientIp,
nt: Notify<'_>,
) -> JsonResult { ) -> JsonResult {
let mut send = match Send::find_by_access_id(&access_id, &mut conn).await { let mut send = match Send::find_by_access_id(&access_id, &mut conn).await {
Some(s) => s, Some(s) => s,
@ -396,6 +397,8 @@ async fn post_access(
send.save(&mut conn).await?; 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)) Ok(Json(send.to_json_access(&mut conn).await))
} }
@ -406,6 +409,7 @@ async fn post_access_file(
data: JsonUpcase<SendAccessData>, data: JsonUpcase<SendAccessData>,
host: Host, host: Host,
mut conn: DbConn, mut conn: DbConn,
nt: Notify<'_>,
) -> JsonResult { ) -> JsonResult {
let mut send = match Send::find_by_uuid(&send_id, &mut conn).await { let mut send = match Send::find_by_uuid(&send_id, &mut conn).await {
Some(s) => s, Some(s) => s,
@ -444,6 +448,8 @@ async fn post_access_file(
send.save(&mut conn).await?; 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_claims = crate::auth::generate_send_claims(&send_id, &file_id);
let token = crate::auth::encode_jwt(&token_claims); let token = crate::auth::encode_jwt(&token_claims);
Ok(Json(json!({ Ok(Json(json!({

View File

@ -164,12 +164,13 @@ impl WebSocketUsers {
let data = create_update( let data = create_update(
vec![("UserId".into(), user.uuid.clone().into()), ("Date".into(), serialize_date(user.updated_at))], vec![("UserId".into(), user.uuid.clone().into()), ("Date".into(), serialize_date(user.updated_at))],
ut, ut,
None,
); );
self.send_update(&user.uuid, &data).await; 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( let data = create_update(
vec![ vec![
("Id".into(), folder.uuid.clone().into()), ("Id".into(), folder.uuid.clone().into()),
@ -177,12 +178,19 @@ impl WebSocketUsers {
("RevisionDate".into(), serialize_date(folder.updated_at)), ("RevisionDate".into(), serialize_date(folder.updated_at)),
], ],
ut, ut,
Some(acting_device_uuid.into()),
); );
self.send_update(&folder.user_uuid, &data).await; 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 user_uuid = convert_option(cipher.user_uuid.clone());
let org_uuid = convert_option(cipher.organization_uuid.clone()); let org_uuid = convert_option(cipher.organization_uuid.clone());
@ -195,6 +203,7 @@ impl WebSocketUsers {
("RevisionDate".into(), serialize_date(cipher.updated_at)), ("RevisionDate".into(), serialize_date(cipher.updated_at)),
], ],
ut, ut,
Some(acting_device_uuid.into()),
); );
for uuid in user_uuids { for uuid in user_uuids {
@ -212,6 +221,7 @@ impl WebSocketUsers {
("RevisionDate".into(), serialize_date(send.revision_date)), ("RevisionDate".into(), serialize_date(send.revision_date)),
], ],
ut, ut,
None,
); );
for uuid in user_uuids { for uuid in user_uuids {
@ -228,14 +238,14 @@ impl WebSocketUsers {
"ReceiveMessage", // Target "ReceiveMessage", // Target
[ // Arguments [ // Arguments
{ {
"ContextId": "app_id", "ContextId": acting_device_uuid || Nil,
"Type": ut as i32, "Type": ut as i32,
"Payload": {} "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; use rmpv::Value as V;
let value = V::Array(vec![ let value = V::Array(vec![
@ -244,7 +254,7 @@ fn create_update(payload: Vec<(Value, Value)>, ut: UpdateType) -> Vec<u8> {
V::Nil, V::Nil,
"ReceiveMessage".into(), "ReceiveMessage".into(),
V::Array(vec![V::Map(vec![ 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()), ("Type".into(), (ut as i32).into()),
("Payload".into(), payload.into()), ("Payload".into(), payload.into()),
])]), ])]),
@ -260,17 +270,17 @@ fn create_ping() -> Vec<u8> {
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Eq, PartialEq)] #[derive(Eq, PartialEq)]
pub enum UpdateType { pub enum UpdateType {
CipherUpdate = 0, SyncCipherUpdate = 0,
CipherCreate = 1, SyncCipherCreate = 1,
LoginDelete = 2, SyncLoginDelete = 2,
FolderDelete = 3, SyncFolderDelete = 3,
Ciphers = 4, SyncCiphers = 4,
Vault = 5, SyncVault = 5,
OrgKeys = 6, SyncOrgKeys = 6,
FolderCreate = 7, SyncFolderCreate = 7,
FolderUpdate = 8, SyncFolderUpdate = 8,
CipherDelete = 9, SyncCipherDelete = 9,
SyncSettings = 10, SyncSettings = 10,
LogOut = 11, LogOut = 11,
@ -279,6 +289,9 @@ pub enum UpdateType {
SyncSendUpdate = 13, SyncSendUpdate = 13,
SyncSendDelete = 14, SyncSendDelete = 14,
AuthRequest = 15,
AuthRequestResponse = 16,
None = 100, None = 100,
} }