geforkt von mirrored/vaultwarden
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.
Dieser Commit ist enthalten in:
Ursprung
bdd918b4d4
Commit
2972904eb8
8 geänderte Dateien mit 187 neuen und 63 gelöschten Zeilen
|
@ -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")]
|
||||||
|
|
|
@ -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")]
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>")]
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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!({
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Laden …
In neuem Issue referenzieren