mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2025-09-12 19:45:58 +03:00
feat: Push Notifications
Co-authored-by: samb-devel <125741162+samb-devel@users.noreply.github.com> Co-authored-by: Zoruk <Zoruk@users.noreply.github.com>
This commit is contained in:
@@ -4,7 +4,8 @@ use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
api::{
|
||||
core::log_user_event, EmptyResult, JsonResult, JsonUpcase, Notify, NumberOrString, PasswordData, UpdateType,
|
||||
core::log_user_event, register_push_device, unregister_push_device, EmptyResult, JsonResult, JsonUpcase,
|
||||
Notify, NumberOrString, PasswordData, UpdateType,
|
||||
},
|
||||
auth::{decode_delete, decode_invite, decode_verify_email, Headers},
|
||||
crypto,
|
||||
@@ -35,6 +36,7 @@ pub fn routes() -> Vec<rocket::Route> {
|
||||
post_verify_email_token,
|
||||
post_delete_recover,
|
||||
post_delete_recover_token,
|
||||
post_device_token,
|
||||
delete_account,
|
||||
post_delete_account,
|
||||
revision_date,
|
||||
@@ -46,6 +48,9 @@ pub fn routes() -> Vec<rocket::Route> {
|
||||
get_known_device,
|
||||
get_known_device_from_path,
|
||||
put_avatar,
|
||||
put_device_token,
|
||||
put_clear_device_token,
|
||||
post_clear_device_token,
|
||||
]
|
||||
}
|
||||
|
||||
@@ -338,7 +343,7 @@ async fn post_password(
|
||||
// Prevent loging out the client where the user requested this endpoint from.
|
||||
// If you do logout the user it will causes issues at the client side.
|
||||
// Adding the device uuid will prevent this.
|
||||
nt.send_logout(&user, Some(headers.device.uuid)).await;
|
||||
nt.send_logout(&user, Some(headers.device.uuid), &mut conn).await;
|
||||
|
||||
save_result
|
||||
}
|
||||
@@ -398,7 +403,7 @@ async fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, mut conn: D
|
||||
user.set_password(&data.NewMasterPasswordHash, Some(data.Key), true, None);
|
||||
let save_result = user.save(&mut conn).await;
|
||||
|
||||
nt.send_logout(&user, Some(headers.device.uuid)).await;
|
||||
nt.send_logout(&user, Some(headers.device.uuid), &mut conn).await;
|
||||
|
||||
save_result
|
||||
}
|
||||
@@ -485,7 +490,7 @@ async fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, mut conn: D
|
||||
// Prevent loging out the client where the user requested this endpoint from.
|
||||
// If you do logout the user it will causes issues at the client side.
|
||||
// Adding the device uuid will prevent this.
|
||||
nt.send_logout(&user, Some(headers.device.uuid)).await;
|
||||
nt.send_logout(&user, Some(headers.device.uuid), &mut conn).await;
|
||||
|
||||
save_result
|
||||
}
|
||||
@@ -508,7 +513,7 @@ async fn post_sstamp(
|
||||
user.reset_security_stamp();
|
||||
let save_result = user.save(&mut conn).await;
|
||||
|
||||
nt.send_logout(&user, None).await;
|
||||
nt.send_logout(&user, None, &mut conn).await;
|
||||
|
||||
save_result
|
||||
}
|
||||
@@ -611,7 +616,7 @@ async fn post_email(
|
||||
|
||||
let save_result = user.save(&mut conn).await;
|
||||
|
||||
nt.send_logout(&user, None).await;
|
||||
nt.send_logout(&user, None, &mut conn).await;
|
||||
|
||||
save_result
|
||||
}
|
||||
@@ -930,3 +935,64 @@ impl<'r> FromRequest<'r> for KnownDevice {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[allow(non_snake_case)]
|
||||
struct PushToken {
|
||||
PushToken: String,
|
||||
}
|
||||
|
||||
#[post("/devices/identifier/<uuid>/token", data = "<data>")]
|
||||
async fn post_device_token(uuid: &str, data: JsonUpcase<PushToken>, headers: Headers, conn: DbConn) -> EmptyResult {
|
||||
put_device_token(uuid, data, headers, conn).await
|
||||
}
|
||||
|
||||
#[put("/devices/identifier/<uuid>/token", data = "<data>")]
|
||||
async fn put_device_token(uuid: &str, data: JsonUpcase<PushToken>, headers: Headers, mut conn: DbConn) -> EmptyResult {
|
||||
if !CONFIG.push_enabled() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let data = data.into_inner().data;
|
||||
let token = data.PushToken;
|
||||
let mut device = match Device::find_by_uuid_and_user(&headers.device.uuid, &headers.user.uuid, &mut conn).await {
|
||||
Some(device) => device,
|
||||
None => err!(format!("Error: device {uuid} should be present before a token can be assigned")),
|
||||
};
|
||||
device.push_token = Some(token);
|
||||
if device.push_uuid.is_none() {
|
||||
device.push_uuid = Some(uuid::Uuid::new_v4().to_string());
|
||||
}
|
||||
if let Err(e) = device.save(&mut conn).await {
|
||||
err!(format!("An error occured while trying to save the device push token: {e}"));
|
||||
}
|
||||
if let Err(e) = register_push_device(headers.user.uuid, device).await {
|
||||
err!(format!("An error occured while proceeding registration of a device: {e}"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[put("/devices/identifier/<uuid>/clear-token")]
|
||||
async fn put_clear_device_token(uuid: &str, mut conn: DbConn) -> EmptyResult {
|
||||
// This only clears push token
|
||||
// https://github.com/bitwarden/core/blob/master/src/Api/Controllers/DevicesController.cs#L109
|
||||
// https://github.com/bitwarden/core/blob/master/src/Core/Services/Implementations/DeviceService.cs#L37
|
||||
// This is somehow not implemented in any app, added it in case it is required
|
||||
if !CONFIG.push_enabled() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(device) = Device::find_by_uuid(uuid, &mut conn).await {
|
||||
Device::clear_push_token_by_uuid(uuid, &mut conn).await?;
|
||||
unregister_push_device(device.uuid).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// On upstream server, both PUT and POST are declared. Implementing the POST method in case it would be useful somewhere
|
||||
#[post("/devices/identifier/<uuid>/clear-token")]
|
||||
async fn post_clear_device_token(uuid: &str, conn: DbConn) -> EmptyResult {
|
||||
put_clear_device_token(uuid, conn).await
|
||||
}
|
||||
|
@@ -511,10 +511,9 @@ pub async fn update_cipher_from_data(
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
nt.send_cipher_update(ut, cipher, &cipher.update_users_revision(conn).await, &headers.device.uuid, None).await;
|
||||
nt.send_cipher_update(ut, cipher, &cipher.update_users_revision(conn).await, &headers.device.uuid, None, conn)
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -580,6 +579,7 @@ async fn post_ciphers_import(
|
||||
let mut user = headers.user;
|
||||
user.update_revision(&mut conn).await?;
|
||||
nt.send_user_update(UpdateType::SyncVault, &user).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -777,6 +777,7 @@ async fn post_collections_admin(
|
||||
&cipher.update_users_revision(&mut conn).await,
|
||||
&headers.device.uuid,
|
||||
Some(Vec::from_iter(posted_collections)),
|
||||
&mut conn,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -1122,6 +1123,7 @@ async fn save_attachment(
|
||||
&cipher.update_users_revision(&mut conn).await,
|
||||
&headers.device.uuid,
|
||||
None,
|
||||
&mut conn,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -1407,8 +1409,15 @@ async fn move_cipher_selected(
|
||||
// Move cipher
|
||||
cipher.move_to_folder(data.FolderId.clone(), &user_uuid, &mut conn).await?;
|
||||
|
||||
nt.send_cipher_update(UpdateType::SyncCipherUpdate, &cipher, &[user_uuid.clone()], &headers.device.uuid, None)
|
||||
.await;
|
||||
nt.send_cipher_update(
|
||||
UpdateType::SyncCipherUpdate,
|
||||
&cipher,
|
||||
&[user_uuid.clone()],
|
||||
&headers.device.uuid,
|
||||
None,
|
||||
&mut conn,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -1489,6 +1498,7 @@ async fn delete_all(
|
||||
|
||||
user.update_revision(&mut conn).await?;
|
||||
nt.send_user_update(UpdateType::SyncVault, &user).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1519,6 +1529,7 @@ async fn _delete_cipher_by_uuid(
|
||||
&cipher.update_users_revision(conn).await,
|
||||
&headers.device.uuid,
|
||||
None,
|
||||
conn,
|
||||
)
|
||||
.await;
|
||||
} else {
|
||||
@@ -1529,6 +1540,7 @@ async fn _delete_cipher_by_uuid(
|
||||
&cipher.update_users_revision(conn).await,
|
||||
&headers.device.uuid,
|
||||
None,
|
||||
conn,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -1599,8 +1611,10 @@ async fn _restore_cipher_by_uuid(uuid: &str, headers: &Headers, conn: &mut DbCon
|
||||
&cipher.update_users_revision(conn).await,
|
||||
&headers.device.uuid,
|
||||
None,
|
||||
conn,
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Some(org_uuid) = &cipher.organization_uuid {
|
||||
log_event(
|
||||
EventType::CipherRestored as i32,
|
||||
@@ -1681,8 +1695,10 @@ async fn _delete_cipher_attachment_by_id(
|
||||
&cipher.update_users_revision(conn).await,
|
||||
&headers.device.uuid,
|
||||
None,
|
||||
conn,
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Some(org_uuid) = cipher.organization_uuid {
|
||||
log_event(
|
||||
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);
|
||||
|
||||
folder.save(&mut conn).await?;
|
||||
nt.send_folder_update(UpdateType::SyncFolderCreate, &folder, &headers.device.uuid).await;
|
||||
nt.send_folder_update(UpdateType::SyncFolderCreate, &folder, &headers.device.uuid, &mut conn).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::SyncFolderUpdate, &folder, &headers.device.uuid).await;
|
||||
nt.send_folder_update(UpdateType::SyncFolderUpdate, &folder, &headers.device.uuid, &mut conn).await;
|
||||
|
||||
Ok(Json(folder.to_json()))
|
||||
}
|
||||
@@ -112,6 +112,6 @@ async fn delete_folder(uuid: &str, headers: Headers, mut conn: DbConn, nt: Notif
|
||||
// Delete the actual folder entry
|
||||
folder.delete(&mut conn).await?;
|
||||
|
||||
nt.send_folder_update(UpdateType::SyncFolderDelete, &folder, &headers.device.uuid).await;
|
||||
nt.send_folder_update(UpdateType::SyncFolderDelete, &folder, &headers.device.uuid, &mut conn).await;
|
||||
Ok(())
|
||||
}
|
||||
|
@@ -14,7 +14,6 @@ pub use sends::purge_sends;
|
||||
pub use two_factor::send_incomplete_2fa_notifications;
|
||||
|
||||
pub fn routes() -> Vec<Route> {
|
||||
let mut device_token_routes = routes![clear_device_token, put_device_token];
|
||||
let mut eq_domains_routes = routes![get_eq_domains, post_eq_domains, put_eq_domains];
|
||||
let mut hibp_routes = routes![hibp_breach];
|
||||
let mut meta_routes = routes![alive, now, version, config];
|
||||
@@ -28,7 +27,6 @@ pub fn routes() -> Vec<Route> {
|
||||
routes.append(&mut organizations::routes());
|
||||
routes.append(&mut two_factor::routes());
|
||||
routes.append(&mut sends::routes());
|
||||
routes.append(&mut device_token_routes);
|
||||
routes.append(&mut eq_domains_routes);
|
||||
routes.append(&mut hibp_routes);
|
||||
routes.append(&mut meta_routes);
|
||||
@@ -57,37 +55,6 @@ use crate::{
|
||||
util::get_reqwest_client,
|
||||
};
|
||||
|
||||
#[put("/devices/identifier/<uuid>/clear-token")]
|
||||
fn clear_device_token(uuid: &str) -> &'static str {
|
||||
// This endpoint doesn't have auth header
|
||||
|
||||
let _ = uuid;
|
||||
// uuid is not related to deviceId
|
||||
|
||||
// This only clears push token
|
||||
// https://github.com/bitwarden/core/blob/master/src/Api/Controllers/DevicesController.cs#L109
|
||||
// https://github.com/bitwarden/core/blob/master/src/Core/Services/Implementations/DeviceService.cs#L37
|
||||
""
|
||||
}
|
||||
|
||||
#[put("/devices/identifier/<uuid>/token", data = "<data>")]
|
||||
fn put_device_token(uuid: &str, data: JsonUpcase<Value>, headers: Headers) -> Json<Value> {
|
||||
let _data: Value = data.into_inner().data;
|
||||
// Data has a single string value "PushToken"
|
||||
let _ = uuid;
|
||||
// uuid is not related to deviceId
|
||||
|
||||
// TODO: This should save the push token, but we don't have push functionality
|
||||
|
||||
Json(json!({
|
||||
"Id": headers.device.uuid,
|
||||
"Name": headers.device.name,
|
||||
"Type": headers.device.atype,
|
||||
"Identifier": headers.device.uuid,
|
||||
"CreationDate": crate::util::format_date(&headers.device.created_at),
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[allow(non_snake_case)]
|
||||
struct GlobalDomain {
|
||||
|
@@ -2716,7 +2716,7 @@ async fn put_reset_password(
|
||||
user.set_password(reset_request.NewMasterPasswordHash.as_str(), Some(reset_request.Key), true, None);
|
||||
user.save(&mut conn).await?;
|
||||
|
||||
nt.send_logout(&user, None).await;
|
||||
nt.send_logout(&user, None, &mut conn).await;
|
||||
|
||||
log_event(
|
||||
EventType::OrganizationUserAdminResetPassword as i32,
|
||||
|
@@ -180,7 +180,8 @@ async fn post_send(data: JsonUpcase<SendData>, headers: Headers, mut conn: DbCon
|
||||
|
||||
let mut send = create_send(data, headers.user.uuid)?;
|
||||
send.save(&mut conn).await?;
|
||||
nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&mut conn).await).await;
|
||||
nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&mut conn).await, &mut conn)
|
||||
.await;
|
||||
|
||||
Ok(Json(send.to_json()))
|
||||
}
|
||||
@@ -252,7 +253,8 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, mut conn:
|
||||
|
||||
// Save the changes in the database
|
||||
send.save(&mut conn).await?;
|
||||
nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&mut conn).await).await;
|
||||
nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&mut conn).await, &mut conn)
|
||||
.await;
|
||||
|
||||
Ok(Json(send.to_json()))
|
||||
}
|
||||
@@ -335,7 +337,8 @@ async fn post_send_file_v2_data(
|
||||
data.data.move_copy_to(file_path).await?
|
||||
}
|
||||
|
||||
nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&mut conn).await).await;
|
||||
nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&mut conn).await, &mut conn)
|
||||
.await;
|
||||
} else {
|
||||
err!("Send not found. Unable to save the file.");
|
||||
}
|
||||
@@ -397,7 +400,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;
|
||||
nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&mut conn).await, &mut conn)
|
||||
.await;
|
||||
|
||||
Ok(Json(send.to_json_access(&mut conn).await))
|
||||
}
|
||||
@@ -448,7 +452,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;
|
||||
nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&mut conn).await, &mut conn)
|
||||
.await;
|
||||
|
||||
let token_claims = crate::auth::generate_send_claims(send_id, file_id);
|
||||
let token = crate::auth::encode_jwt(&token_claims);
|
||||
@@ -530,7 +535,8 @@ async fn put_send(
|
||||
}
|
||||
|
||||
send.save(&mut conn).await?;
|
||||
nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&mut conn).await).await;
|
||||
nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&mut conn).await, &mut conn)
|
||||
.await;
|
||||
|
||||
Ok(Json(send.to_json()))
|
||||
}
|
||||
@@ -547,7 +553,8 @@ async fn delete_send(id: &str, headers: Headers, mut conn: DbConn, nt: Notify<'_
|
||||
}
|
||||
|
||||
send.delete(&mut conn).await?;
|
||||
nt.send_send_update(UpdateType::SyncSendDelete, &send, &send.update_users_revision(&mut conn).await).await;
|
||||
nt.send_send_update(UpdateType::SyncSendDelete, &send, &send.update_users_revision(&mut conn).await, &mut conn)
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -567,7 +574,8 @@ async fn put_remove_password(id: &str, headers: Headers, mut conn: DbConn, nt: N
|
||||
|
||||
send.set_password(None);
|
||||
send.save(&mut conn).await?;
|
||||
nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&mut conn).await).await;
|
||||
nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&mut conn).await, &mut conn)
|
||||
.await;
|
||||
|
||||
Ok(Json(send.to_json()))
|
||||
}
|
||||
|
Reference in New Issue
Block a user