Skip to content

Commit f1c0aa4

Browse files
BlackDexdani-garcia
authored andcommitted
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.
1 parent 68362d0 commit f1c0aa4

8 files changed

+188
-64
lines changed

src/api/admin.rs

+13-5
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use rocket::{
1313
};
1414

1515
use crate::{
16-
api::{core::log_event, ApiResult, EmptyResult, JsonResult, NumberOrString},
16+
api::{core::log_event, ApiResult, EmptyResult, JsonResult, Notify, NumberOrString, UpdateType},
1717
auth::{decode_admin, encode_jwt, generate_admin_claims, ClientIp},
1818
config::ConfigBuilder,
1919
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
365365
}
366366

367367
#[post("/users/<uuid>/deauth")]
368-
async fn deauth_user(uuid: String, _token: AdminToken, mut conn: DbConn) -> EmptyResult {
368+
async fn deauth_user(uuid: String, _token: AdminToken, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
369369
let mut user = get_user_or_404(&uuid, &mut conn).await?;
370370
Device::delete_all_by_user(&user.uuid, &mut conn).await?;
371371
user.reset_security_stamp();
372372

373-
user.save(&mut conn).await
373+
let save_result = user.save(&mut conn).await;
374+
375+
nt.send_user_update(UpdateType::LogOut, &user).await;
376+
377+
save_result
374378
}
375379

376380
#[post("/users/<uuid>/disable")]
377-
async fn disable_user(uuid: String, _token: AdminToken, mut conn: DbConn) -> EmptyResult {
381+
async fn disable_user(uuid: String, _token: AdminToken, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
378382
let mut user = get_user_or_404(&uuid, &mut conn).await?;
379383
Device::delete_all_by_user(&user.uuid, &mut conn).await?;
380384
user.reset_security_stamp();
381385
user.enabled = false;
382386

383-
user.save(&mut conn).await
387+
let save_result = user.save(&mut conn).await;
388+
389+
nt.send_user_update(UpdateType::LogOut, &user).await;
390+
391+
save_result
384392
}
385393

386394
#[post("/users/<uuid>/enable")]

src/api/core/accounts.rs

+39-8
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ async fn post_password(
275275
headers: Headers,
276276
mut conn: DbConn,
277277
ip: ClientIp,
278+
nt: Notify<'_>,
278279
) -> EmptyResult {
279280
let data: ChangePassData = data.into_inner().data;
280281
let mut user = headers.user;
@@ -293,7 +294,11 @@ async fn post_password(
293294
Some(vec![String::from("post_rotatekey"), String::from("get_contacts"), String::from("get_public_keys")]),
294295
);
295296
user.akey = data.Key;
296-
user.save(&mut conn).await
297+
let save_result = user.save(&mut conn).await;
298+
299+
nt.send_user_update(UpdateType::LogOut, &user).await;
300+
301+
save_result
297302
}
298303

299304
#[derive(Deserialize)]
@@ -308,7 +313,7 @@ struct ChangeKdfData {
308313
}
309314

310315
#[post("/accounts/kdf", data = "<data>")]
311-
async fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, mut conn: DbConn) -> EmptyResult {
316+
async fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, mut conn: DbConn, nt: Notify<'_>) -> EmptyResult {
312317
let data: ChangeKdfData = data.into_inner().data;
313318
let mut user = headers.user;
314319

@@ -320,7 +325,11 @@ async fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, mut conn: D
320325
user.client_kdf_type = data.Kdf;
321326
user.set_password(&data.NewMasterPasswordHash, None);
322327
user.akey = data.Key;
323-
user.save(&mut conn).await
328+
let save_result = user.save(&mut conn).await;
329+
330+
nt.send_user_update(UpdateType::LogOut, &user).await;
331+
332+
save_result
324333
}
325334

326335
#[derive(Deserialize)]
@@ -388,6 +397,7 @@ async fn post_rotatekey(
388397

389398
// Prevent triggering cipher updates via WebSockets by settings UpdateType::None
390399
// The user sessions are invalidated because all the ciphers were re-encrypted and thus triggering an update could cause issues.
400+
// We force the users to logout after the user has been saved to try and prevent these issues.
391401
update_cipher_from_data(&mut saved_cipher, cipher_data, &headers, false, &mut conn, &ip, &nt, UpdateType::None)
392402
.await?
393403
}
@@ -399,11 +409,20 @@ async fn post_rotatekey(
399409
user.private_key = Some(data.PrivateKey);
400410
user.reset_security_stamp();
401411

402-
user.save(&mut conn).await
412+
let save_result = user.save(&mut conn).await;
413+
414+
nt.send_user_update(UpdateType::LogOut, &user).await;
415+
416+
save_result
403417
}
404418

405419
#[post("/accounts/security-stamp", data = "<data>")]
406-
async fn post_sstamp(data: JsonUpcase<PasswordData>, headers: Headers, mut conn: DbConn) -> EmptyResult {
420+
async fn post_sstamp(
421+
data: JsonUpcase<PasswordData>,
422+
headers: Headers,
423+
mut conn: DbConn,
424+
nt: Notify<'_>,
425+
) -> EmptyResult {
407426
let data: PasswordData = data.into_inner().data;
408427
let mut user = headers.user;
409428

@@ -413,7 +432,11 @@ async fn post_sstamp(data: JsonUpcase<PasswordData>, headers: Headers, mut conn:
413432

414433
Device::delete_all_by_user(&user.uuid, &mut conn).await?;
415434
user.reset_security_stamp();
416-
user.save(&mut conn).await
435+
let save_result = user.save(&mut conn).await;
436+
437+
nt.send_user_update(UpdateType::LogOut, &user).await;
438+
439+
save_result
417440
}
418441

419442
#[derive(Deserialize)]
@@ -465,7 +488,12 @@ struct ChangeEmailData {
465488
}
466489

467490
#[post("/accounts/email", data = "<data>")]
468-
async fn post_email(data: JsonUpcase<ChangeEmailData>, headers: Headers, mut conn: DbConn) -> EmptyResult {
491+
async fn post_email(
492+
data: JsonUpcase<ChangeEmailData>,
493+
headers: Headers,
494+
mut conn: DbConn,
495+
nt: Notify<'_>,
496+
) -> EmptyResult {
469497
let data: ChangeEmailData = data.into_inner().data;
470498
let mut user = headers.user;
471499

@@ -507,8 +535,11 @@ async fn post_email(data: JsonUpcase<ChangeEmailData>, headers: Headers, mut con
507535

508536
user.set_password(&data.NewMasterPasswordHash, None);
509537
user.akey = data.Key;
538+
let save_result = user.save(&mut conn).await;
510539

511-
user.save(&mut conn).await
540+
nt.send_user_update(UpdateType::LogOut, &user).await;
541+
542+
save_result
512543
}
513544

514545
#[post("/accounts/verify-email")]

src/api/core/ciphers.rs

+56-17
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,8 @@ async fn post_ciphers(
310310
data.LastKnownRevisionDate = None;
311311

312312
let mut cipher = Cipher::new(data.Type, data.Name.clone());
313-
update_cipher_from_data(&mut cipher, data, &headers, false, &mut conn, &ip, &nt, UpdateType::CipherCreate).await?;
313+
update_cipher_from_data(&mut cipher, data, &headers, false, &mut conn, &ip, &nt, UpdateType::SyncCipherCreate)
314+
.await?;
314315

315316
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, &mut conn).await))
316317
}
@@ -415,7 +416,14 @@ pub async fn update_cipher_from_data(
415416
for (id, attachment) in attachments {
416417
let mut saved_att = match Attachment::find_by_id(&id, conn).await {
417418
Some(att) => att,
418-
None => err!("Attachment doesn't exist"),
419+
None => {
420+
// Warn and continue here.
421+
// A missing attachment means it was removed via an other client.
422+
// Also the Desktop Client supports removing attachments and save an update afterwards.
423+
// Bitwarden it self ignores these mismatches server side.
424+
warn!("Attachment {id} doesn't exist");
425+
continue;
426+
}
419427
};
420428

421429
if saved_att.cipher_uuid != cipher.uuid {
@@ -482,8 +490,8 @@ pub async fn update_cipher_from_data(
482490
// Only log events for organizational ciphers
483491
if let Some(org_uuid) = &cipher.organization_uuid {
484492
let event_type = match (&ut, transfer_cipher) {
485-
(UpdateType::CipherCreate, true) => EventType::CipherCreated,
486-
(UpdateType::CipherUpdate, true) => EventType::CipherShared,
493+
(UpdateType::SyncCipherCreate, true) => EventType::CipherCreated,
494+
(UpdateType::SyncCipherUpdate, true) => EventType::CipherShared,
487495
(_, _) => EventType::CipherUpdated,
488496
};
489497

@@ -499,7 +507,7 @@ pub async fn update_cipher_from_data(
499507
.await;
500508
}
501509

502-
nt.send_cipher_update(ut, cipher, &cipher.update_users_revision(conn).await).await;
510+
nt.send_cipher_update(ut, cipher, &cipher.update_users_revision(conn).await, &headers.device.uuid).await;
503511
}
504512

505513
Ok(())
@@ -562,7 +570,7 @@ async fn post_ciphers_import(
562570

563571
let mut user = headers.user;
564572
user.update_revision(&mut conn).await?;
565-
nt.send_user_update(UpdateType::Vault, &user).await;
573+
nt.send_user_update(UpdateType::SyncVault, &user).await;
566574
Ok(())
567575
}
568576

@@ -628,7 +636,8 @@ async fn put_cipher(
628636
err!("Cipher is not write accessible")
629637
}
630638

631-
update_cipher_from_data(&mut cipher, data, &headers, false, &mut conn, &ip, &nt, UpdateType::CipherUpdate).await?;
639+
update_cipher_from_data(&mut cipher, data, &headers, false, &mut conn, &ip, &nt, UpdateType::SyncCipherUpdate)
640+
.await?;
632641

633642
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, None, &mut conn).await))
634643
}
@@ -850,9 +859,9 @@ async fn share_cipher_by_uuid(
850859

851860
// When LastKnownRevisionDate is None, it is a new cipher, so send CipherCreate.
852861
let ut = if data.Cipher.LastKnownRevisionDate.is_some() {
853-
UpdateType::CipherUpdate
862+
UpdateType::SyncCipherUpdate
854863
} else {
855-
UpdateType::CipherCreate
864+
UpdateType::SyncCipherCreate
856865
};
857866

858867
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(
10541063
data.data.move_copy_to(file_path).await?
10551064
}
10561065

1057-
nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(&mut conn).await).await;
1066+
nt.send_cipher_update(
1067+
UpdateType::SyncCipherUpdate,
1068+
&cipher,
1069+
&cipher.update_users_revision(&mut conn).await,
1070+
&headers.device.uuid,
1071+
)
1072+
.await;
10581073

10591074
if let Some(org_uuid) = &cipher.organization_uuid {
10601075
log_event(
@@ -1390,7 +1405,7 @@ async fn move_cipher_selected(
13901405
// Move cipher
13911406
cipher.move_to_folder(data.FolderId.clone(), &user_uuid, &mut conn).await?;
13921407

1393-
nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &[user_uuid.clone()]).await;
1408+
nt.send_cipher_update(UpdateType::SyncCipherUpdate, &cipher, &[user_uuid.clone()], &headers.device.uuid).await;
13941409
}
13951410

13961411
Ok(())
@@ -1438,7 +1453,7 @@ async fn delete_all(
14381453
Some(user_org) => {
14391454
if user_org.atype == UserOrgType::Owner {
14401455
Cipher::delete_all_by_organization(&org_data.org_id, &mut conn).await?;
1441-
nt.send_user_update(UpdateType::Vault, &user).await;
1456+
nt.send_user_update(UpdateType::SyncVault, &user).await;
14421457

14431458
log_event(
14441459
EventType::OrganizationPurgedVault as i32,
@@ -1471,7 +1486,7 @@ async fn delete_all(
14711486
}
14721487

14731488
user.update_revision(&mut conn).await?;
1474-
nt.send_user_update(UpdateType::Vault, &user).await;
1489+
nt.send_user_update(UpdateType::SyncVault, &user).await;
14751490
Ok(())
14761491
}
14771492
}
@@ -1497,10 +1512,22 @@ async fn _delete_cipher_by_uuid(
14971512
if soft_delete {
14981513
cipher.deleted_at = Some(Utc::now().naive_utc());
14991514
cipher.save(conn).await?;
1500-
nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(conn).await).await;
1515+
nt.send_cipher_update(
1516+
UpdateType::SyncCipherUpdate,
1517+
&cipher,
1518+
&cipher.update_users_revision(conn).await,
1519+
&headers.device.uuid,
1520+
)
1521+
.await;
15011522
} else {
15021523
cipher.delete(conn).await?;
1503-
nt.send_cipher_update(UpdateType::CipherDelete, &cipher, &cipher.update_users_revision(conn).await).await;
1524+
nt.send_cipher_update(
1525+
UpdateType::SyncCipherDelete,
1526+
&cipher,
1527+
&cipher.update_users_revision(conn).await,
1528+
&headers.device.uuid,
1529+
)
1530+
.await;
15041531
}
15051532

15061533
if let Some(org_uuid) = cipher.organization_uuid {
@@ -1562,7 +1589,13 @@ async fn _restore_cipher_by_uuid(
15621589
cipher.deleted_at = None;
15631590
cipher.save(conn).await?;
15641591

1565-
nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(conn).await).await;
1592+
nt.send_cipher_update(
1593+
UpdateType::SyncCipherUpdate,
1594+
&cipher,
1595+
&cipher.update_users_revision(conn).await,
1596+
&headers.device.uuid,
1597+
)
1598+
.await;
15661599
if let Some(org_uuid) = &cipher.organization_uuid {
15671600
log_event(
15681601
EventType::CipherRestored as i32,
@@ -1639,7 +1672,13 @@ async fn _delete_cipher_attachment_by_id(
16391672

16401673
// Delete attachment
16411674
attachment.delete(conn).await?;
1642-
nt.send_cipher_update(UpdateType::CipherUpdate, &cipher, &cipher.update_users_revision(conn).await).await;
1675+
nt.send_cipher_update(
1676+
UpdateType::SyncCipherUpdate,
1677+
&cipher,
1678+
&cipher.update_users_revision(conn).await,
1679+
&headers.device.uuid,
1680+
)
1681+
.await;
16431682
if let Some(org_uuid) = cipher.organization_uuid {
16441683
log_event(
16451684
EventType::CipherAttachmentDeleted as i32,

src/api/core/folders.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ async fn post_folders(data: JsonUpcase<FolderData>, headers: Headers, mut conn:
5050
let mut folder = Folder::new(headers.user.uuid, data.Name);
5151

5252
folder.save(&mut conn).await?;
53-
nt.send_folder_update(UpdateType::FolderCreate, &folder).await;
53+
nt.send_folder_update(UpdateType::SyncFolderCreate, &folder, &headers.device.uuid).await;
5454

5555
Ok(Json(folder.to_json()))
5656
}
@@ -88,7 +88,7 @@ async fn put_folder(
8888
folder.name = data.Name;
8989

9090
folder.save(&mut conn).await?;
91-
nt.send_folder_update(UpdateType::FolderUpdate, &folder).await;
91+
nt.send_folder_update(UpdateType::SyncFolderUpdate, &folder, &headers.device.uuid).await;
9292

9393
Ok(Json(folder.to_json()))
9494
}
@@ -112,6 +112,6 @@ async fn delete_folder(uuid: String, headers: Headers, mut conn: DbConn, nt: Not
112112
// Delete the actual folder entry
113113
folder.delete(&mut conn).await?;
114114

115-
nt.send_folder_update(UpdateType::FolderDelete, &folder).await;
115+
nt.send_folder_update(UpdateType::SyncFolderDelete, &folder, &headers.device.uuid).await;
116116
Ok(())
117117
}

0 commit comments

Comments
 (0)