commit 2ac23b6a6c834ea173d3f06ac57d84ff45fe65f9
parent 8d6e4cb9584ab04d4e19aae669c57d48c5cf739b
Author: Zack Newman <zack@philomathiclife.com>
Date: Sun, 14 Jan 2024 13:38:45 -0700
remove websockets
Diffstat:
15 files changed, 132 insertions(+), 880 deletions(-)
diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs
@@ -1,7 +1,5 @@
use crate::{
- api::{
- EmptyResult, JsonResult, JsonUpcase, Notify, NumberOrString, PasswordOrOtpData, UpdateType,
- },
+ api::{EmptyResult, JsonResult, JsonUpcase, NumberOrString, PasswordOrOtpData},
auth::{decode_delete, ClientHeaders, Headers},
db::{
models::{Cipher, Device, Folder, User, UserKdfType},
@@ -200,7 +198,6 @@ async fn post_password(
data: JsonUpcase<ChangePassData>,
headers: Headers,
conn: DbConn,
- nt: Notify<'_>,
) -> EmptyResult {
let pass_data: ChangePassData = data.into_inner().data;
let mut user = headers.user;
@@ -219,13 +216,7 @@ async fn post_password(
String::from("get_public_keys"),
]),
);
-
- let save_result = user.save(&conn).await;
- // Prevent logging 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;
- save_result
+ user.save(&conn).await
}
#[derive(Deserialize)]
@@ -241,12 +232,7 @@ struct ChangeKdfData {
}
#[post("/accounts/kdf", data = "<data>")]
-async fn post_kdf(
- data: JsonUpcase<ChangeKdfData>,
- headers: Headers,
- conn: DbConn,
- nt: Notify<'_>,
-) -> EmptyResult {
+async fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, conn: DbConn) -> EmptyResult {
let kdf_data: ChangeKdfData = data.into_inner().data;
let mut user = headers.user;
if !user.check_valid_password(&kdf_data.MasterPasswordHash) {
@@ -287,9 +273,7 @@ async fn post_kdf(
true,
None,
);
- let save_result = user.save(&conn).await;
- nt.send_logout(&user, Some(headers.device.uuid)).await;
- save_result
+ user.save(&conn).await
}
#[derive(Deserialize)]
@@ -312,12 +296,7 @@ struct KeyData {
}
#[post("/accounts/key", data = "<data>")]
-async fn post_rotatekey(
- data: JsonUpcase<KeyData>,
- headers: Headers,
- conn: DbConn,
- nt: Notify<'_>,
-) -> EmptyResult {
+async fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbConn) -> EmptyResult {
let key_data: KeyData = data.into_inner().data;
if !headers
.user
@@ -353,19 +332,8 @@ async fn post_rotatekey(
if saved_cipher.user_uuid.as_ref().unwrap() != user_uuid {
err!("The cipher is not owned by the user")
}
- // Prevent triggering cipher updates via WebSockets by settings UpdateType::None
- // The user sessions are invalidated because all the ciphers were re-encrypted and thus triggering an update could cause issues.
- // We force the users to logout after the user has been saved to try and prevent these issues.
- update_cipher_from_data(
- &mut saved_cipher,
- cipher_data,
- &headers,
- false,
- &conn,
- &nt,
- UpdateType::None,
- )
- .await?;
+ update_cipher_from_data(&mut saved_cipher, cipher_data, &headers, false, &conn, true)
+ .await?;
}
// Update user data
@@ -373,12 +341,7 @@ async fn post_rotatekey(
user.akey = key_data.Key;
user.private_key = Some(key_data.PrivateKey);
user.reset_security_stamp();
- let save_result = user.save(&conn).await;
- // Prevent logging 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;
- save_result
+ user.save(&conn).await
}
#[post("/accounts/security-stamp", data = "<data>")]
@@ -386,16 +349,13 @@ async fn post_sstamp(
data: JsonUpcase<PasswordOrOtpData>,
headers: Headers,
conn: DbConn,
- nt: Notify<'_>,
) -> EmptyResult {
let otp_data: PasswordOrOtpData = data.into_inner().data;
let mut user = headers.user;
otp_data.validate(&user)?;
Device::delete_all_by_user(&user.uuid, &conn).await?;
user.reset_security_stamp();
- let save_result = user.save(&conn).await;
- nt.send_logout(&user, None).await;
- save_result
+ user.save(&conn).await
}
#[derive(Deserialize)]
diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs
@@ -1,6 +1,6 @@
use super::folders::FolderData;
use crate::{
- api::{self, EmptyResult, JsonResult, JsonUpcase, Notify, PasswordOrOtpData, UpdateType},
+ api::{self, EmptyResult, JsonResult, JsonUpcase, PasswordOrOtpData},
auth::Headers,
db::{
models::{
@@ -257,9 +257,8 @@ async fn post_ciphers_admin(
data: JsonUpcase<ShareCipherData>,
headers: Headers,
conn: DbConn,
- nt: Notify<'_>,
) -> JsonResult {
- post_ciphers_create(data, headers, conn, nt).await
+ post_ciphers_create(data, headers, conn).await
}
/// Called when creating a new org-owned cipher, or cloning a cipher (whether
@@ -270,7 +269,6 @@ async fn post_ciphers_create(
data: JsonUpcase<ShareCipherData>,
headers: Headers,
conn: DbConn,
- nt: Notify<'_>,
) -> JsonResult {
let mut data: ShareCipherData = data.into_inner().data;
// Check if there are one more more collections selected when this cipher is part of an organization.
@@ -292,17 +290,12 @@ async fn post_ciphers_create(
// line. Since this function only creates new ciphers (whether by cloning
// or otherwise), we can just ignore this field entirely.
data.Cipher.LastKnownRevisionDate = None;
- share_cipher_by_uuid(&cipher.uuid, data, &headers, &conn, &nt).await
+ share_cipher_by_uuid(&cipher.uuid, data, &headers, &conn).await
}
/// Called when creating a new user-owned cipher.
#[post("/ciphers", data = "<data>")]
-async fn post_ciphers(
- data: JsonUpcase<CipherData>,
- headers: Headers,
- conn: DbConn,
- nt: Notify<'_>,
-) -> JsonResult {
+async fn post_ciphers(data: JsonUpcase<CipherData>, headers: Headers, conn: DbConn) -> JsonResult {
let mut data: CipherData = data.into_inner().data;
// The web/browser clients set this field to null as expected, but the
// mobile clients seem to set the invalid value `0001-01-01T00:00:00`,
@@ -310,16 +303,7 @@ async fn post_ciphers(
// needed when creating a new cipher, so just ignore it unconditionally.
data.LastKnownRevisionDate = None;
let mut cipher = Cipher::new(data.Type, data.Name.clone());
- update_cipher_from_data(
- &mut cipher,
- data,
- &headers,
- false,
- &conn,
- &nt,
- UpdateType::SyncCipherCreate,
- )
- .await?;
+ update_cipher_from_data(&mut cipher, data, &headers, false, &conn, false).await?;
Ok(Json(
cipher
.to_json(&headers.user.uuid, None, CipherSyncType::User, &conn)
@@ -355,13 +339,12 @@ pub async fn update_cipher_from_data(
headers: &Headers,
shared_to_collection: bool,
conn: &DbConn,
- nt: &Notify<'_>,
- ut: UpdateType,
+ import_cipher: bool,
) -> EmptyResult {
enforce_personal_ownership_policy(Some(&data), headers, conn).await?;
// Check that the client isn't updating an existing cipher with stale data.
// And only perform this check when not importing ciphers, else the date/time check will fail.
- if ut != UpdateType::None {
+ if !import_cipher {
if let Some(dt) = data.LastKnownRevisionDate {
match NaiveDateTime::parse_from_str(&dt, "%+") {
// ISO 8601 format
@@ -466,16 +449,6 @@ pub async fn update_cipher_from_data(
cipher
.set_favorite(data.Favorite, &headers.user.uuid, conn)
.await?;
- if ut != UpdateType::None {
- nt.send_cipher_update(
- ut,
- cipher,
- &cipher.update_users_revision(conn).await,
- &headers.device.uuid,
- None,
- )
- .await;
- }
Ok(())
}
@@ -501,7 +474,6 @@ async fn post_ciphers_import(
data: JsonUpcase<ImportData>,
headers: Headers,
conn: DbConn,
- nt: Notify<'_>,
) -> EmptyResult {
enforce_personal_ownership_policy(None, &headers, &conn).await?;
let data: ImportData = data.into_inner().data;
@@ -527,20 +499,10 @@ async fn post_ciphers_import(
let folder_uuid = relations_map.get(&index).map(|i| folders[*i].uuid.clone());
cipher_data.FolderId = folder_uuid;
let mut cipher = Cipher::new(cipher_data.Type, cipher_data.Name.clone());
- update_cipher_from_data(
- &mut cipher,
- cipher_data,
- &headers,
- false,
- &conn,
- &nt,
- UpdateType::None,
- )
- .await?;
+ update_cipher_from_data(&mut cipher, cipher_data, &headers, false, &conn, true).await?;
}
let mut user = headers.user;
user.update_revision(&conn).await?;
- nt.send_user_update(UpdateType::SyncVault, &user).await;
Ok(())
}
@@ -551,9 +513,8 @@ async fn put_cipher_admin(
data: JsonUpcase<CipherData>,
headers: Headers,
conn: DbConn,
- nt: Notify<'_>,
) -> JsonResult {
- put_cipher(uuid, data, headers, conn, nt).await
+ put_cipher(uuid, data, headers, conn).await
}
#[post("/ciphers/<uuid>/admin", data = "<data>")]
@@ -562,9 +523,8 @@ async fn post_cipher_admin(
data: JsonUpcase<CipherData>,
headers: Headers,
conn: DbConn,
- nt: Notify<'_>,
) -> JsonResult {
- post_cipher(uuid, data, headers, conn, nt).await
+ post_cipher(uuid, data, headers, conn).await
}
#[post("/ciphers/<uuid>", data = "<data>")]
@@ -573,9 +533,8 @@ async fn post_cipher(
data: JsonUpcase<CipherData>,
headers: Headers,
conn: DbConn,
- nt: Notify<'_>,
) -> JsonResult {
- put_cipher(uuid, data, headers, conn, nt).await
+ put_cipher(uuid, data, headers, conn).await
}
#[put("/ciphers/<uuid>", data = "<data>")]
@@ -584,7 +543,6 @@ async fn put_cipher(
data: JsonUpcase<CipherData>,
headers: Headers,
conn: DbConn,
- nt: Notify<'_>,
) -> JsonResult {
let data: CipherData = data.into_inner().data;
let Some(mut cipher) = Cipher::find_by_uuid(uuid, &conn).await else {
@@ -600,16 +558,7 @@ async fn put_cipher(
{
err!("Cipher is not write accessible")
}
- update_cipher_from_data(
- &mut cipher,
- data,
- &headers,
- false,
- &conn,
- &nt,
- UpdateType::SyncCipherUpdate,
- )
- .await?;
+ update_cipher_from_data(&mut cipher, data, &headers, false, &conn, false).await?;
Ok(Json(
cipher
.to_json(&headers.user.uuid, None, CipherSyncType::User, &conn)
@@ -676,9 +625,8 @@ async fn put_collections_update(
data: JsonUpcase<CollectionsAdminData>,
headers: Headers,
conn: DbConn,
- nt: Notify<'_>,
) -> EmptyResult {
- post_collections_admin(uuid, data, headers, conn, nt).await
+ post_collections_admin(uuid, data, headers, conn).await
}
#[post("/ciphers/<uuid>/collections", data = "<data>")]
@@ -687,9 +635,8 @@ async fn post_collections_update(
data: JsonUpcase<CollectionsAdminData>,
headers: Headers,
conn: DbConn,
- nt: Notify<'_>,
) -> EmptyResult {
- post_collections_admin(uuid, data, headers, conn, nt).await
+ post_collections_admin(uuid, data, headers, conn).await
}
#[put("/ciphers/<uuid>/collections-admin", data = "<data>")]
@@ -698,9 +645,8 @@ async fn put_collections_admin(
data: JsonUpcase<CollectionsAdminData>,
headers: Headers,
conn: DbConn,
- nt: Notify<'_>,
) -> EmptyResult {
- post_collections_admin(uuid, data, headers, conn, nt).await
+ post_collections_admin(uuid, data, headers, conn).await
}
#[post("/ciphers/<uuid>/collections-admin", data = "<data>")]
@@ -709,7 +655,6 @@ async fn post_collections_admin(
data: JsonUpcase<CollectionsAdminData>,
headers: Headers,
conn: DbConn,
- nt: Notify<'_>,
) -> EmptyResult {
let data: CollectionsAdminData = data.into_inner().data;
let Some(cipher) = Cipher::find_by_uuid(uuid, &conn).await else {
@@ -750,14 +695,6 @@ async fn post_collections_admin(
}
}
}
- nt.send_cipher_update(
- UpdateType::SyncCipherUpdate,
- &cipher,
- &cipher.update_users_revision(&conn).await,
- &headers.device.uuid,
- Some(Vec::from_iter(posted_collections)),
- )
- .await;
Ok(())
}
@@ -774,10 +711,9 @@ async fn post_cipher_share(
data: JsonUpcase<ShareCipherData>,
headers: Headers,
conn: DbConn,
- nt: Notify<'_>,
) -> JsonResult {
let data: ShareCipherData = data.into_inner().data;
- share_cipher_by_uuid(uuid, data, &headers, &conn, &nt).await
+ share_cipher_by_uuid(uuid, data, &headers, &conn).await
}
#[put("/ciphers/<uuid>/share", data = "<data>")]
@@ -786,10 +722,9 @@ async fn put_cipher_share(
data: JsonUpcase<ShareCipherData>,
headers: Headers,
conn: DbConn,
- nt: Notify<'_>,
) -> JsonResult {
let data: ShareCipherData = data.into_inner().data;
- share_cipher_by_uuid(uuid, data, &headers, &conn, &nt).await
+ share_cipher_by_uuid(uuid, data, &headers, &conn).await
}
#[derive(Deserialize)]
@@ -804,7 +739,6 @@ async fn put_cipher_share_selected(
data: JsonUpcase<ShareSelectedCipherData>,
headers: Headers,
conn: DbConn,
- nt: Notify<'_>,
) -> EmptyResult {
let mut data: ShareSelectedCipherData = data.into_inner().data;
if data.Ciphers.is_empty() {
@@ -824,7 +758,7 @@ async fn put_cipher_share_selected(
CollectionIds: data.CollectionIds.clone(),
};
match shared_cipher_data.Cipher.Id.take() {
- Some(id) => share_cipher_by_uuid(&id, shared_cipher_data, &headers, &conn, &nt).await?,
+ Some(id) => share_cipher_by_uuid(&id, shared_cipher_data, &headers, &conn).await?,
None => err!("Request missing ids field"),
};
}
@@ -836,7 +770,6 @@ async fn share_cipher_by_uuid(
data: ShareCipherData,
headers: &Headers,
conn: &DbConn,
- nt: &Notify<'_>,
) -> JsonResult {
let mut cipher = match Cipher::find_by_uuid(uuid, conn).await {
Some(cipher) => {
@@ -870,20 +803,13 @@ async fn share_cipher_by_uuid(
}
}
};
- // When LastKnownRevisionDate is None, it is a new cipher, so send CipherCreate.
- let ut = if data.Cipher.LastKnownRevisionDate.is_some() {
- UpdateType::SyncCipherUpdate
- } else {
- UpdateType::SyncCipherCreate
- };
update_cipher_from_data(
&mut cipher,
data.Cipher,
headers,
shared_to_collection,
conn,
- nt,
- ut,
+ false,
)
.await?;
Ok(Json(
@@ -1017,58 +943,33 @@ fn delete_attachment_admin(uuid: &str, attachment_id: &str, _headers: Headers) -
}
#[post("/ciphers/<uuid>/delete")]
-async fn delete_cipher_post(
- uuid: &str,
- headers: Headers,
- conn: DbConn,
- nt: Notify<'_>,
-) -> EmptyResult {
- _delete_cipher_by_uuid(uuid, &headers, &conn, false, &nt).await
+async fn delete_cipher_post(uuid: &str, headers: Headers, conn: DbConn) -> EmptyResult {
+ _delete_cipher_by_uuid(uuid, &headers, &conn, false).await
}
#[post("/ciphers/<uuid>/delete-admin")]
-async fn delete_cipher_post_admin(
- uuid: &str,
- headers: Headers,
- conn: DbConn,
- nt: Notify<'_>,
-) -> EmptyResult {
- _delete_cipher_by_uuid(uuid, &headers, &conn, false, &nt).await
+async fn delete_cipher_post_admin(uuid: &str, headers: Headers, conn: DbConn) -> EmptyResult {
+ _delete_cipher_by_uuid(uuid, &headers, &conn, false).await
}
#[put("/ciphers/<uuid>/delete")]
-async fn delete_cipher_put(
- uuid: &str,
- headers: Headers,
- conn: DbConn,
- nt: Notify<'_>,
-) -> EmptyResult {
- _delete_cipher_by_uuid(uuid, &headers, &conn, true, &nt).await
+async fn delete_cipher_put(uuid: &str, headers: Headers, conn: DbConn) -> EmptyResult {
+ _delete_cipher_by_uuid(uuid, &headers, &conn, true).await
}
#[put("/ciphers/<uuid>/delete-admin")]
-async fn delete_cipher_put_admin(
- uuid: &str,
- headers: Headers,
- conn: DbConn,
- nt: Notify<'_>,
-) -> EmptyResult {
- _delete_cipher_by_uuid(uuid, &headers, &conn, true, &nt).await
+async fn delete_cipher_put_admin(uuid: &str, headers: Headers, conn: DbConn) -> EmptyResult {
+ _delete_cipher_by_uuid(uuid, &headers, &conn, true).await
}
#[delete("/ciphers/<uuid>")]
-async fn delete_cipher(uuid: &str, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
- _delete_cipher_by_uuid(uuid, &headers, &conn, false, &nt).await
+async fn delete_cipher(uuid: &str, headers: Headers, conn: DbConn) -> EmptyResult {
+ _delete_cipher_by_uuid(uuid, &headers, &conn, false).await
}
#[delete("/ciphers/<uuid>/admin")]
-async fn delete_cipher_admin(
- uuid: &str,
- headers: Headers,
- conn: DbConn,
- nt: Notify<'_>,
-) -> EmptyResult {
- _delete_cipher_by_uuid(uuid, &headers, &conn, false, &nt).await
+async fn delete_cipher_admin(uuid: &str, headers: Headers, conn: DbConn) -> EmptyResult {
+ _delete_cipher_by_uuid(uuid, &headers, &conn, false).await
}
#[delete("/ciphers", data = "<data>")]
@@ -1076,9 +977,8 @@ async fn delete_cipher_selected(
data: JsonUpcase<Value>,
headers: Headers,
conn: DbConn,
- nt: Notify<'_>,
) -> EmptyResult {
- _delete_multiple_ciphers(data, headers, conn, false, nt).await // permanent delete
+ _delete_multiple_ciphers(data, headers, conn, false).await // permanent delete
}
#[post("/ciphers/delete", data = "<data>")]
@@ -1086,9 +986,8 @@ async fn delete_cipher_selected_post(
data: JsonUpcase<Value>,
headers: Headers,
conn: DbConn,
- nt: Notify<'_>,
) -> EmptyResult {
- _delete_multiple_ciphers(data, headers, conn, false, nt).await // permanent delete
+ _delete_multiple_ciphers(data, headers, conn, false).await // permanent delete
}
#[put("/ciphers/delete", data = "<data>")]
@@ -1096,9 +995,8 @@ async fn delete_cipher_selected_put(
data: JsonUpcase<Value>,
headers: Headers,
conn: DbConn,
- nt: Notify<'_>,
) -> EmptyResult {
- _delete_multiple_ciphers(data, headers, conn, true, nt).await // soft delete
+ _delete_multiple_ciphers(data, headers, conn, true).await // soft delete
}
#[delete("/ciphers/admin", data = "<data>")]
@@ -1106,9 +1004,8 @@ async fn delete_cipher_selected_admin(
data: JsonUpcase<Value>,
headers: Headers,
conn: DbConn,
- nt: Notify<'_>,
) -> EmptyResult {
- _delete_multiple_ciphers(data, headers, conn, false, nt).await // permanent delete
+ _delete_multiple_ciphers(data, headers, conn, false).await // permanent delete
}
#[post("/ciphers/delete-admin", data = "<data>")]
@@ -1116,9 +1013,8 @@ async fn delete_cipher_selected_post_admin(
data: JsonUpcase<Value>,
headers: Headers,
conn: DbConn,
- nt: Notify<'_>,
) -> EmptyResult {
- _delete_multiple_ciphers(data, headers, conn, false, nt).await // permanent delete
+ _delete_multiple_ciphers(data, headers, conn, false).await // permanent delete
}
#[put("/ciphers/delete-admin", data = "<data>")]
@@ -1126,29 +1022,18 @@ async fn delete_cipher_selected_put_admin(
data: JsonUpcase<Value>,
headers: Headers,
conn: DbConn,
- nt: Notify<'_>,
) -> EmptyResult {
- _delete_multiple_ciphers(data, headers, conn, true, nt).await // soft delete
+ _delete_multiple_ciphers(data, headers, conn, true).await // soft delete
}
#[put("/ciphers/<uuid>/restore")]
-async fn restore_cipher_put(
- uuid: &str,
- headers: Headers,
- conn: DbConn,
- nt: Notify<'_>,
-) -> JsonResult {
- _restore_cipher_by_uuid(uuid, &headers, &conn, &nt).await
+async fn restore_cipher_put(uuid: &str, headers: Headers, conn: DbConn) -> JsonResult {
+ _restore_cipher_by_uuid(uuid, &headers, &conn).await
}
#[put("/ciphers/<uuid>/restore-admin")]
-async fn restore_cipher_put_admin(
- uuid: &str,
- headers: Headers,
- conn: DbConn,
- nt: Notify<'_>,
-) -> JsonResult {
- _restore_cipher_by_uuid(uuid, &headers, &conn, &nt).await
+async fn restore_cipher_put_admin(uuid: &str, headers: Headers, conn: DbConn) -> JsonResult {
+ _restore_cipher_by_uuid(uuid, &headers, &conn).await
}
#[put("/ciphers/restore", data = "<data>")]
@@ -1156,9 +1041,8 @@ async fn restore_cipher_selected(
data: JsonUpcase<Value>,
headers: Headers,
conn: DbConn,
- nt: Notify<'_>,
) -> JsonResult {
- _restore_multiple_ciphers(data, &headers, &conn, &nt).await
+ _restore_multiple_ciphers(data, &headers, &conn).await
}
#[derive(Deserialize)]
@@ -1173,7 +1057,6 @@ async fn move_cipher_selected(
data: JsonUpcase<MoveCipherData>,
headers: Headers,
conn: DbConn,
- nt: Notify<'_>,
) -> EmptyResult {
let data = data.into_inner().data;
let user_uuid = headers.user.uuid;
@@ -1198,14 +1081,6 @@ async fn move_cipher_selected(
cipher
.move_to_folder(data.FolderId.clone(), &user_uuid, &conn)
.await?;
- nt.send_cipher_update(
- UpdateType::SyncCipherUpdate,
- &cipher,
- &[user_uuid.clone()],
- &headers.device.uuid,
- None,
- )
- .await;
}
Ok(())
}
@@ -1215,9 +1090,8 @@ async fn move_cipher_selected_put(
data: JsonUpcase<MoveCipherData>,
headers: Headers,
conn: DbConn,
- nt: Notify<'_>,
) -> EmptyResult {
- move_cipher_selected(data, headers, conn, nt).await
+ move_cipher_selected(data, headers, conn).await
}
#[derive(FromForm)]
@@ -1232,7 +1106,6 @@ async fn delete_all(
data: JsonUpcase<PasswordOrOtpData>,
headers: Headers,
conn: DbConn,
- nt: Notify<'_>,
) -> EmptyResult {
let data: PasswordOrOtpData = data.into_inner().data;
let mut user = headers.user;
@@ -1244,7 +1117,6 @@ async fn delete_all(
Some(user_org) => {
if user_org.atype == UserOrgType::Owner {
Cipher::delete_all_by_organization(&org_data.org_id, &conn).await?;
- nt.send_user_update(UpdateType::SyncVault, &user).await;
Ok(())
} else {
err!("You don't have permission to purge the organization vault");
@@ -1262,7 +1134,6 @@ async fn delete_all(
f.delete(&conn).await?;
}
user.update_revision(&conn).await?;
- nt.send_user_update(UpdateType::SyncVault, &user).await;
Ok(())
}
}
@@ -1272,7 +1143,6 @@ async fn _delete_cipher_by_uuid(
headers: &Headers,
conn: &DbConn,
soft_delete: bool,
- nt: &Notify<'_>,
) -> EmptyResult {
let Some(mut cipher) = Cipher::find_by_uuid(uuid, conn).await else {
err!("Cipher doesn't exist")
@@ -1286,24 +1156,8 @@ async fn _delete_cipher_by_uuid(
if soft_delete {
cipher.deleted_at = Some(Utc::now().naive_utc());
cipher.save(conn).await?;
- nt.send_cipher_update(
- UpdateType::SyncCipherUpdate,
- &cipher,
- &cipher.update_users_revision(conn).await,
- &headers.device.uuid,
- None,
- )
- .await;
} else {
cipher.delete(conn).await?;
- nt.send_cipher_update(
- UpdateType::SyncCipherDelete,
- &cipher,
- &cipher.update_users_revision(conn).await,
- &headers.device.uuid,
- None,
- )
- .await;
}
Ok(())
}
@@ -1313,7 +1167,6 @@ async fn _delete_multiple_ciphers(
headers: Headers,
conn: DbConn,
soft_delete: bool,
- nt: Notify<'_>,
) -> EmptyResult {
let data: Value = data.into_inner().data;
let uuids = match data.get("Ids") {
@@ -1324,21 +1177,14 @@ async fn _delete_multiple_ciphers(
None => err!("Request missing ids field"),
};
for uuid in uuids {
- if let error @ Err(_) =
- _delete_cipher_by_uuid(uuid, &headers, &conn, soft_delete, &nt).await
- {
+ if let error @ Err(_) = _delete_cipher_by_uuid(uuid, &headers, &conn, soft_delete).await {
return error;
};
}
Ok(())
}
-async fn _restore_cipher_by_uuid(
- uuid: &str,
- headers: &Headers,
- conn: &DbConn,
- nt: &Notify<'_>,
-) -> JsonResult {
+async fn _restore_cipher_by_uuid(uuid: &str, headers: &Headers, conn: &DbConn) -> JsonResult {
let Some(mut cipher) = Cipher::find_by_uuid(uuid, conn).await else {
err!("Cipher doesn't exist")
};
@@ -1350,14 +1196,6 @@ async fn _restore_cipher_by_uuid(
}
cipher.deleted_at = None;
cipher.save(conn).await?;
- nt.send_cipher_update(
- UpdateType::SyncCipherUpdate,
- &cipher,
- &cipher.update_users_revision(conn).await,
- &headers.device.uuid,
- None,
- )
- .await;
Ok(Json(
cipher
.to_json(&headers.user.uuid, None, CipherSyncType::User, conn)
@@ -1369,7 +1207,6 @@ async fn _restore_multiple_ciphers(
data: JsonUpcase<Value>,
headers: &Headers,
conn: &DbConn,
- nt: &Notify<'_>,
) -> JsonResult {
let data: Value = data.into_inner().data;
let uuids = match data.get("Ids") {
@@ -1381,7 +1218,7 @@ async fn _restore_multiple_ciphers(
};
let mut ciphers: Vec<Value> = Vec::new();
for uuid in uuids {
- match _restore_cipher_by_uuid(uuid, headers, conn, nt).await {
+ match _restore_cipher_by_uuid(uuid, headers, conn).await {
Ok(json) => ciphers.push(json.into_inner()),
err => return err,
}
diff --git a/src/api/core/folders.rs b/src/api/core/folders.rs
@@ -1,5 +1,5 @@
use crate::{
- api::{EmptyResult, JsonResult, JsonUpcase, Notify, UpdateType},
+ api::{EmptyResult, JsonResult, JsonUpcase},
auth::Headers,
db::{models::Folder, DbConn},
};
@@ -47,17 +47,10 @@ pub struct FolderData {
}
#[post("/folders", data = "<data>")]
-async fn post_folders(
- data: JsonUpcase<FolderData>,
- headers: Headers,
- conn: DbConn,
- nt: Notify<'_>,
-) -> JsonResult {
+async fn post_folders(data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn) -> JsonResult {
let data: FolderData = data.into_inner().data;
let mut folder = Folder::new(headers.user.uuid, data.Name);
folder.save(&conn).await?;
- nt.send_folder_update(UpdateType::SyncFolderCreate, &folder, &headers.device.uuid)
- .await;
Ok(Json(folder.to_json()))
}
@@ -67,9 +60,8 @@ async fn post_folder(
data: JsonUpcase<FolderData>,
headers: Headers,
conn: DbConn,
- nt: Notify<'_>,
) -> JsonResult {
- put_folder(uuid, data, headers, conn, nt).await
+ put_folder(uuid, data, headers, conn).await
}
#[put("/folders/<uuid>", data = "<data>")]
@@ -78,7 +70,6 @@ async fn put_folder(
data: JsonUpcase<FolderData>,
headers: Headers,
conn: DbConn,
- nt: Notify<'_>,
) -> JsonResult {
let data: FolderData = data.into_inner().data;
let Some(mut folder) = Folder::find_by_uuid(uuid, &conn).await else {
@@ -89,23 +80,16 @@ async fn put_folder(
}
folder.name = data.Name;
folder.save(&conn).await?;
- nt.send_folder_update(UpdateType::SyncFolderUpdate, &folder, &headers.device.uuid)
- .await;
Ok(Json(folder.to_json()))
}
#[post("/folders/<uuid>/delete")]
-async fn delete_folder_post(
- uuid: &str,
- headers: Headers,
- conn: DbConn,
- nt: Notify<'_>,
-) -> EmptyResult {
- delete_folder(uuid, headers, conn, nt).await
+async fn delete_folder_post(uuid: &str, headers: Headers, conn: DbConn) -> EmptyResult {
+ delete_folder(uuid, headers, conn).await
}
#[delete("/folders/<uuid>")]
-async fn delete_folder(uuid: &str, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
+async fn delete_folder(uuid: &str, headers: Headers, conn: DbConn) -> EmptyResult {
let Some(folder) = Folder::find_by_uuid(uuid, &conn).await else {
err!("Invalid folder")
};
@@ -114,7 +98,5 @@ async fn delete_folder(uuid: &str, headers: Headers, conn: DbConn, nt: Notify<'_
}
// Delete the actual folder entry
folder.delete(&conn).await?;
- nt.send_folder_update(UpdateType::SyncFolderDelete, &folder, &headers.device.uuid)
- .await;
Ok(())
}
diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs
@@ -137,15 +137,13 @@ const fn version() -> Json<&'static str> {
#[get("/config")]
fn config() -> Json<Value> {
- let domain = &config_file::get_config().domain;
+ let domain = &config_file::get_config().domain_url();
Json(json!({
// Note: The clients use this version to handle backwards compatibility concerns
// This means they expect a version that closely matches the Bitwarden server version
// We should make sure that we keep this updated when we support the new server features
- // Version history:
- // - Individual cipher key encryption: 2023.9.1
- "version": "2023.9.1",
- "gitHash": option_env!("GIT_REV"),
+ "version": "2023.12.1",
+ "gitHash": "",
"server": {
"name": "Vaultwarden",
"url": "https://github.com/dani-garcia/vaultwarden",
@@ -155,15 +153,13 @@ fn config() -> Json<Value> {
"vault": domain,
"api": format!("{domain}/api"),
"identity": format!("{domain}/identity"),
- "notifications": format!("{domain}/notifications"),
- "sso": "",
+ "notifications": "",
+ "sso": ""
},
"featureStates": {
- // Any feature flags that we want the clients to use
- // Can check the enabled ones at:
- // https://vault.bitwarden.com/api/config
- "fido2-vault-credentials": true, // Passkey support
- "autofill-v2": false, // Disabled because it is causing issues https://github.com/dani-garcia/vaultwarden/discussions/4052
+ "autofill-overlay": true,
+ "autofill-v2": true,
+ "fido2-vault-credentials": true
},
"object": "config",
}))
diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs
@@ -1,8 +1,8 @@
use crate::{
api::{
core::{CipherSyncData, CipherSyncType},
- EmptyResult, JsonResult, JsonUpcase, JsonUpcaseVec, JsonVec, Notify, NumberOrString,
- PasswordOrOtpData, UpdateType,
+ EmptyResult, JsonResult, JsonUpcase, JsonUpcaseVec, JsonVec, NumberOrString,
+ PasswordOrOtpData,
},
auth::{self, AdminHeaders, Headers, ManagerHeaders, ManagerHeadersLoose, OwnerHeaders},
db::{
@@ -950,12 +950,11 @@ async fn bulk_delete_user(
data: JsonUpcase<OrgBulkIds>,
headers: AdminHeaders,
conn: DbConn,
- nt: Notify<'_>,
) -> Json<Value> {
let data: OrgBulkIds = data.into_inner().data;
let mut bulk_response = Vec::new();
for org_user_id in data.Ids {
- let err_msg = match _delete_user(org_id, &org_user_id, &headers, &conn, &nt).await {
+ let err_msg = match _delete_user(org_id, &org_user_id, &headers, &conn).await {
Ok(()) => String::new(),
Err(e) => format!("{e:?}"),
};
@@ -980,9 +979,8 @@ async fn delete_user(
org_user_id: &str,
headers: AdminHeaders,
conn: DbConn,
- nt: Notify<'_>,
) -> EmptyResult {
- _delete_user(org_id, org_user_id, &headers, &conn, &nt).await
+ _delete_user(org_id, org_user_id, &headers, &conn).await
}
#[post("/organizations/<org_id>/users/<org_user_id>/delete")]
@@ -991,9 +989,8 @@ async fn post_delete_user(
org_user_id: &str,
headers: AdminHeaders,
conn: DbConn,
- nt: Notify<'_>,
) -> EmptyResult {
- _delete_user(org_id, org_user_id, &headers, &conn, &nt).await
+ _delete_user(org_id, org_user_id, &headers, &conn).await
}
async fn _delete_user(
@@ -1001,7 +998,6 @@ async fn _delete_user(
org_user_id: &str,
headers: &AdminHeaders,
conn: &DbConn,
- nt: &Notify<'_>,
) -> EmptyResult {
let Some(user_to_delete) =
UserOrganization::find_by_uuid_and_org(org_user_id, org_id, conn).await
@@ -1021,9 +1017,6 @@ async fn _delete_user(
err!("Can't delete the last owner")
}
}
- 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
}
@@ -1092,7 +1085,6 @@ async fn post_org_import(
data: JsonUpcase<ImportData>,
headers: AdminHeaders,
conn: DbConn,
- nt: Notify<'_>,
) -> EmptyResult {
let data: ImportData = data.into_inner().data;
let org_id = query.organization_id;
@@ -1122,17 +1114,9 @@ async fn post_org_import(
let mut ciphers = Vec::new();
for cipher_data in data.Ciphers {
let mut cipher = Cipher::new(cipher_data.Type, cipher_data.Name.clone());
- update_cipher_from_data(
- &mut cipher,
- cipher_data,
- &headers,
- false,
- &conn,
- &nt,
- UpdateType::None,
- )
- .await
- .ok();
+ update_cipher_from_data(&mut cipher, cipher_data, &headers, false, &conn, true)
+ .await
+ .ok();
ciphers.push(cipher);
}
// Assign the collections
diff --git a/src/api/core/two_factor/duo.rs b/src/api/core/two_factor/duo.rs
@@ -6,7 +6,7 @@ use crate::{
use rocket::Route;
pub fn routes() -> Vec<Route> {
- routes![get_duo, activate_duo, activate_duo_put,]
+ routes![activate_duo, activate_duo_put, get_duo]
}
const DUO_DISABLED_MSG: &str = "Duo is disabled.";
#[allow(unused_variables, clippy::needless_pass_by_value)]
diff --git a/src/api/core/two_factor/email.rs b/src/api/core/two_factor/email.rs
@@ -6,7 +6,7 @@ use crate::{
use rocket::Route;
pub fn routes() -> Vec<Route> {
- routes![get_email, send_email_login, send_email, email,]
+ routes![email, get_email, send_email, send_email_login]
}
#[derive(Deserialize)]
diff --git a/src/api/core/two_factor/webauthn.rs b/src/api/core/two_factor/webauthn.rs
@@ -27,10 +27,7 @@ pub fn routes() -> Vec<Route> {
}
fn build_webauthn() -> Result<Webauthn, WebauthnError> {
WebauthnBuilder::new(
- config::get_config()
- .domain
- .domain()
- .expect("a valid domain"),
+ config::get_config().domain(),
&Url::parse(&config::get_config().domain_origin()).expect("a valid URL"),
)?
.build()
diff --git a/src/api/core/two_factor/yubikey.rs b/src/api/core/two_factor/yubikey.rs
@@ -6,7 +6,7 @@ use crate::{
use rocket::Route;
pub fn routes() -> Vec<Route> {
- routes![generate_yubikey, activate_yubikey, activate_yubikey_put,]
+ routes![activate_yubikey, activate_yubikey_put, generate_yubikey]
}
#[derive(Deserialize)]
diff --git a/src/api/mod.rs b/src/api/mod.rs
@@ -2,23 +2,12 @@ mod admin;
pub mod core;
mod icons;
mod identity;
-mod notifications;
mod web;
pub use crate::api::{
- admin::catchers as admin_catchers,
- admin::routes as admin_routes,
- core::catchers as core_catchers,
- core::events_routes as core_events_routes,
- core::routes as core_routes,
- icons::routes as icons_routes,
- identity::routes as identity_routes,
- notifications::routes as notifications_routes,
- notifications::{
- init_ws_anonymous_subscriptions, init_ws_users, start_notification_server,
- ws_anonymous_subscriptions, Notify, UpdateType,
- },
- web::catchers as web_catchers,
- web::routes as web_routes,
+ admin::catchers as admin_catchers, admin::routes as admin_routes,
+ core::catchers as core_catchers, core::events_routes as core_events_routes,
+ core::routes as core_routes, icons::routes as icons_routes,
+ identity::routes as identity_routes, web::catchers as web_catchers, web::routes as web_routes,
};
use crate::db::models::User;
use crate::error::Error;
diff --git a/src/api/notifications.rs b/src/api/notifications.rs
@@ -1,496 +0,0 @@
-use crate::{
- auth::{self, ClientIp, WsAccessTokenHeader},
- db::models::{Cipher, Folder, User},
- Error,
-};
-use chrono::{NaiveDateTime, Utc};
-use core::convert;
-use rmpv::Value;
-use rocket::{futures::StreamExt, Route};
-use std::sync::OnceLock;
-use std::{sync::Arc, time::Duration};
-use tokio::sync::mpsc::{channel, Sender};
-use tokio::time;
-use tokio_tungstenite::tungstenite::Message;
-static WS_USERS: OnceLock<Arc<WebSocketUsers>> = OnceLock::new();
-#[inline]
-pub fn init_ws_users() {
- if WS_USERS
- .set(Arc::new(WebSocketUsers {
- map: Arc::new(dashmap::DashMap::new()),
- }))
- .is_err()
- {
- panic!("WS_USERS must be initialized only once")
- }
-}
-#[inline]
-fn ws_users() -> &'static Arc<WebSocketUsers> {
- WS_USERS
- .get()
- .expect("WS_USERS should be initialized in main")
-}
-static WS_ANONYMOUS_SUBSCRIPTIONS: OnceLock<Arc<AnonymousWebSocketSubscriptions>> = OnceLock::new();
-#[inline]
-pub fn init_ws_anonymous_subscriptions() {
- if WS_ANONYMOUS_SUBSCRIPTIONS
- .set(Arc::new(AnonymousWebSocketSubscriptions {
- map: Arc::new(dashmap::DashMap::new()),
- }))
- .is_err()
- {
- panic!("WS_ANONYMOUS_SUBSCRIPTIONS must only be initialized once")
- }
-}
-#[inline]
-pub fn ws_anonymous_subscriptions() -> &'static Arc<AnonymousWebSocketSubscriptions> {
- WS_ANONYMOUS_SUBSCRIPTIONS
- .get()
- .expect("WS_ANONYMOUS_SUBSCRIPTIONS should be initialized in main")
-}
-
-pub fn routes() -> Vec<Route> {
- routes![anonymous_websockets_hub, websockets_hub]
-}
-
-#[derive(FromForm)]
-struct WsAccessToken {
- access_token: Option<String>,
-}
-
-struct WSEntryMapGuard {
- users: Arc<WebSocketUsers>,
- user_uuid: String,
- entry_uuid: uuid::Uuid,
-}
-
-impl WSEntryMapGuard {
- fn new(users: Arc<WebSocketUsers>, user_uuid: String, entry_uuid: uuid::Uuid) -> Self {
- Self {
- users,
- user_uuid,
- entry_uuid,
- }
- }
-}
-
-impl Drop for WSEntryMapGuard {
- fn drop(&mut self) {
- if let Some(mut entry) = self.users.map.get_mut(&self.user_uuid) {
- entry.retain(|tup| tup.0 != self.entry_uuid);
- }
- }
-}
-
-struct WSAnonymousEntryMapGuard {
- subscriptions: Arc<AnonymousWebSocketSubscriptions>,
- token: String,
-}
-
-impl WSAnonymousEntryMapGuard {
- fn new(subscriptions: Arc<AnonymousWebSocketSubscriptions>, token: String) -> Self {
- Self {
- subscriptions,
- token,
- }
- }
-}
-
-impl Drop for WSAnonymousEntryMapGuard {
- fn drop(&mut self) {
- self.subscriptions.map.remove(&self.token);
- }
-}
-
-#[get("/hub?<data..>")]
-fn websockets_hub<'r>(
- ws: rocket_ws::WebSocket,
- data: WsAccessToken,
- _ip: ClientIp,
- header_token: WsAccessTokenHeader,
-) -> Result<rocket_ws::Stream!['r], Error> {
- let token = if let Some(token) = data.access_token {
- token
- } else if let Some(token) = header_token.access_token {
- token
- } else {
- err_code!("Invalid claim", 401)
- };
-
- let Ok(claims) = auth::decode_login(&token) else {
- err_code!("Invalid token", 401)
- };
- let (mut rx, guard) = {
- let users = Arc::clone(ws_users());
- // Add a channel to send messages to this client to the map
- let entry_uuid = uuid::Uuid::new_v4();
- let (tx, rx) = channel::<Message>(100);
- users
- .map
- .entry(claims.sub.clone())
- .or_default()
- .push((entry_uuid, tx));
-
- // Once the guard goes out of scope, the connection will have been closed and the entry will be deleted from the map
- (rx, WSEntryMapGuard::new(users, claims.sub, entry_uuid))
- };
- Ok({
- rocket_ws::Stream! { ws => {
- let mut ws_copy = ws;
- let _guard = guard;
- let mut interval = time::interval(Duration::from_secs(15));
- loop {
- tokio::select! {
- res = ws_copy.next() => {
- match res {
- Some(Ok(message)) => {
- match message {
- // Respond to any pings
- Message::Ping(ping) => yield Message::Pong(ping),
- Message::Pong(_) => {/* Ignored */},
- // We should receive an initial message with the protocol and version, and we will reply to it
- Message::Text(ref message) => {
- let msg = message.strip_suffix(char::from(RECORD_SEPARATOR)).unwrap_or(message);
- if serde_json::from_str(msg).ok() == Some(INITIAL_MESSAGE) {
- yield Message::binary(INITIAL_RESPONSE);
- continue;
- }
- }
- // Prevent sending anything back when a `Close` Message is received.
- // Just break the loop
- Message::Close(_) => break,
- // Just echo anything else the client sends
- _ => yield message,
- }
- }
- _ => break,
- }
- }
- res = rx.recv() => {
- match res {
- Some(res) => yield res,
- None => break,
- }
- }
- _ = interval.tick() => yield Message::Ping(create_ping())
- }
- }
- }}
- })
-}
-#[get("/anonymous-hub?<token..>")]
-fn anonymous_websockets_hub<'r>(
- ws: rocket_ws::WebSocket,
- token: String,
- _ip: ClientIp,
-) -> rocket_ws::Stream!['r] {
- let (mut rx, guard) = {
- let subscriptions = Arc::clone(ws_anonymous_subscriptions());
- // Add a channel to send messages to this client to the map
- let (tx, rx) = channel::<Message>(100);
- subscriptions.map.insert(token.clone(), tx);
- // Once the guard goes out of scope, the connection will have been closed and the entry will be deleted from the map
- (rx, WSAnonymousEntryMapGuard::new(subscriptions, token))
- };
- rocket_ws::Stream! { ws => {
- let mut ws_copy = ws;
- let _guard = guard;
- let mut interval = time::interval(Duration::from_secs(15));
- loop {
- tokio::select! {
- res = ws_copy.next() => {
- match res {
- Some(Ok(message)) => {
- match message {
- // Respond to any pings
- Message::Ping(ping) => yield Message::Pong(ping),
- Message::Pong(_) => {/* Ignored */},
- // We should receive an initial message with the protocol and version, and we will reply to it
- Message::Text(ref message) => {
- let msg = message.strip_suffix(char::from(RECORD_SEPARATOR)).unwrap_or(message);
- if serde_json::from_str(msg).ok() == Some(INITIAL_MESSAGE) {
- yield Message::binary(INITIAL_RESPONSE);
- continue;
- }
- }
- // Prevent sending anything back when a `Close` Message is received.
- // Just break the loop
- Message::Close(_) => break,
- // Just echo anything else the client sends
- _ => yield message,
- }
- }
- _ => break,
- }
- }
- res = rx.recv() => {
- match res {
- Some(res) => yield res,
- None => break,
- }
- }
- _ = interval.tick() => yield Message::Ping(create_ping())
- }
- }
- }}
-}
-fn serialize(val: &Value) -> Vec<u8> {
- use rmpv::encode::write_value;
- let mut buf = Vec::new();
- write_value(&mut buf, val).expect("Error encoding MsgPack");
- // Add size bytes at the start
- // Extracted from BinaryMessageFormat.js
- let mut size: usize = buf.len();
- let mut len_buf: Vec<u8> = Vec::new();
- loop {
- let mut size_part = size & 0x7f;
- size >>= 7i32;
- if size > 0 {
- size_part |= 0x80;
- }
- len_buf.push(u8::try_from(size_part).unwrap());
- if size == 0 {
- break;
- }
- }
- len_buf.append(&mut buf);
- len_buf
-}
-#[allow(clippy::big_endian_bytes)]
-fn serialize_date(date: NaiveDateTime) -> Value {
- let seconds: i64 = date.timestamp();
- let nanos: i64 = date.timestamp_subsec_nanos().into();
- let timestamp = nanos << 34i32 | seconds;
- let bs = timestamp.to_be_bytes();
- // -1 is Timestamp
- // https://github.com/msgpack/msgpack/blob/master/spec.md#timestamp-extension-type
- Value::Ext(-1, bs.to_vec())
-}
-
-fn convert_option<T: Into<Value>>(option: Option<T>) -> Value {
- option.map_or(Value::Nil, convert::Into::into)
-}
-
-const RECORD_SEPARATOR: u8 = 0x1e;
-const INITIAL_RESPONSE: [u8; 3] = [0x7b, 0x7d, RECORD_SEPARATOR]; // {, }, <RS>
-
-#[derive(Deserialize, Copy, Clone, Eq, PartialEq)]
-struct InitialMessage<'a> {
- protocol: &'a str,
- version: i32,
-}
-
-static INITIAL_MESSAGE: InitialMessage<'static> = InitialMessage {
- protocol: "messagepack",
- version: 1,
-};
-
-// We attach the UUID to the sender so we can differentiate them when we need to remove them from the Vec
-type UserSenders = (uuid::Uuid, Sender<Message>);
-#[derive(Clone)]
-pub struct WebSocketUsers {
- map: Arc<dashmap::DashMap<String, Vec<UserSenders>>>,
-}
-
-impl WebSocketUsers {
- async fn send_update(&self, user_uuid: &str, data: &[u8]) {
- if let Some(user) = self.map.get(user_uuid).map(|v| v.clone()) {
- for tup in user {
- if let Err(e) = tup.1.send(Message::binary(data)).await {
- error!("Error sending WS update {e}");
- }
- }
- }
- }
-
- // NOTE: The last modified date needs to be updated before calling these methods
- pub async fn send_user_update(&self, ut: UpdateType, user: &User) {
- let data = create_update(
- vec![
- ("UserId".into(), user.uuid.clone().into()),
- ("Date".into(), serialize_date(user.updated_at)),
- ],
- ut,
- None,
- );
- self.send_update(&user.uuid, &data).await;
- }
-
- pub async fn send_logout(&self, user: &User, acting_device_uuid: Option<String>) {
- let data = create_update(
- vec![
- ("UserId".into(), user.uuid.clone().into()),
- ("Date".into(), serialize_date(user.updated_at)),
- ],
- UpdateType::LogOut,
- acting_device_uuid.clone(),
- );
- self.send_update(&user.uuid, &data).await;
- }
-
- pub async fn send_folder_update(
- &self,
- ut: UpdateType,
- folder: &Folder,
- acting_device_uuid: &String,
- ) {
- let data = create_update(
- vec![
- ("Id".into(), folder.uuid.clone().into()),
- ("UserId".into(), folder.user_uuid.clone().into()),
- ("RevisionDate".into(), serialize_date(folder.updated_at)),
- ],
- ut,
- Some(acting_device_uuid.into()),
- );
- self.send_update(&folder.user_uuid, &data).await;
- }
-
- pub async fn send_cipher_update(
- &self,
- ut: UpdateType,
- cipher: &Cipher,
- user_uuids: &[String],
- acting_device_uuid: &String,
- collection_uuids: Option<Vec<String>>,
- ) {
- let org_uuid = convert_option(cipher.organization_uuid.clone());
- // Depending if there are collections provided or not, we need to have different values for the following variables.
- // The user_uuid should be `null`, and the revision date should be set to now, else the clients won't sync the collection change.
- let (user_uuid, collection_uuids, revision_date) = collection_uuids.map_or_else(
- || {
- (
- convert_option(cipher.user_uuid.clone()),
- Value::Nil,
- serialize_date(cipher.updated_at),
- )
- },
- |col_uuids| {
- (
- Value::Nil,
- Value::Array(
- col_uuids
- .into_iter()
- .map(convert::Into::into)
- .collect::<Vec<rmpv::Value>>(),
- ),
- serialize_date(Utc::now().naive_utc()),
- )
- },
- );
- let data = create_update(
- vec![
- ("Id".into(), cipher.uuid.clone().into()),
- ("UserId".into(), user_uuid),
- ("OrganizationId".into(), org_uuid),
- ("CollectionIds".into(), collection_uuids),
- ("RevisionDate".into(), revision_date),
- ],
- ut,
- Some(acting_device_uuid.into()),
- );
-
- for uuid in user_uuids {
- self.send_update(uuid, &data).await;
- }
- }
-}
-
-#[derive(Clone)]
-pub struct AnonymousWebSocketSubscriptions {
- map: Arc<dashmap::DashMap<String, Sender<Message>>>,
-}
-
-/* Message Structure
-[
- 1, // MessageType.Invocation
- {}, // Headers (map)
- null, // InvocationId
- "ReceiveMessage", // Target
- [ // Arguments
- {
- "ContextId": acting_device_uuid || Nil,
- "Type": ut as i32,
- "Payload": {}
- }
- ]
-]
-*/
-fn create_update(
- payload: Vec<(Value, Value)>,
- ut: UpdateType,
- acting_device_uuid: Option<String>,
-) -> Vec<u8> {
- use rmpv::Value as V;
- let value = V::Array(vec![
- 1i32.into(),
- V::Map(vec![]),
- V::Nil,
- "ReceiveMessage".into(),
- V::Array(vec![V::Map(vec![
- (
- "ContextId".into(),
- acting_device_uuid.map_or(V::Nil, convert::Into::into),
- ),
- ("Type".into(), (i32::from(ut)).into()),
- ("Payload".into(), payload.into()),
- ])]),
- ]);
- serialize(&value)
-}
-
-fn create_ping() -> Vec<u8> {
- serialize(&Value::Array(vec![6i32.into()]))
-}
-
-#[allow(dead_code)]
-#[derive(Copy, Clone, Eq, PartialEq)]
-pub enum UpdateType {
- SyncCipherUpdate = 0,
- SyncCipherCreate = 1,
- SyncLoginDelete = 2,
- SyncFolderDelete = 3,
- SyncCiphers = 4,
- SyncVault = 5,
- SyncOrgKeys = 6,
- SyncFolderCreate = 7,
- SyncFolderUpdate = 8,
- SyncCipherDelete = 9,
- SyncSettings = 10,
- LogOut = 11,
- SyncSendCreate = 12,
- SyncSendUpdate = 13,
- SyncSendDelete = 14,
- AuthRequest = 15,
- AuthRequestResponse = 16,
- None = 100,
-}
-impl From<UpdateType> for i32 {
- fn from(value: UpdateType) -> Self {
- match value {
- UpdateType::SyncCipherUpdate => 0i32,
- UpdateType::SyncCipherCreate => 1i32,
- UpdateType::SyncLoginDelete => 2i32,
- UpdateType::SyncFolderDelete => 3i32,
- UpdateType::SyncCiphers => 4i32,
- UpdateType::SyncVault => 5i32,
- UpdateType::SyncOrgKeys => 6i32,
- UpdateType::SyncFolderCreate => 7i32,
- UpdateType::SyncFolderUpdate => 8i32,
- UpdateType::SyncCipherDelete => 9i32,
- UpdateType::SyncSettings => 10i32,
- UpdateType::LogOut => 11i32,
- UpdateType::SyncSendCreate => 12i32,
- UpdateType::SyncSendUpdate => 13i32,
- UpdateType::SyncSendDelete => 14i32,
- UpdateType::AuthRequest => 15i32,
- UpdateType::AuthRequestResponse => 16i32,
- UpdateType::None => 100i32,
- }
- }
-}
-
-pub type Notify<'a> = &'a rocket::State<Arc<WebSocketUsers>>;
-pub fn start_notification_server() -> Arc<WebSocketUsers> {
- Arc::clone(ws_users())
-}
diff --git a/src/auth.rs b/src/auth.rs
@@ -284,7 +284,7 @@ impl<'r> FromRequest<'r> for Host {
type Error = &'static str;
async fn from_request(_: &'r Request<'_>) -> Outcome<Self, Self::Error> {
Outcome::Success(Self {
- host: config::get_config().domain.to_string(),
+ host: config::get_config().domain_url().to_owned(),
})
}
}
diff --git a/src/config.rs b/src/config.rs
@@ -1,5 +1,6 @@
use core::fmt::{self, Display, Formatter};
use core::num::NonZeroU8;
+use core::str;
use rocket::config::{CipherSuite, LogLevel, TlsConfig};
use rocket::data::{Limits, ToByteUnit};
use std::error;
@@ -90,7 +91,7 @@ pub struct Config {
pub database_max_conns: NonZeroU8,
pub database_timeout: u16,
pub db_connection_retries: NonZeroU8,
- pub domain: Url,
+ domain: Url,
pub password_iterations: u32,
pub rocket: rocket::Config,
pub web_vault_enabled: bool,
@@ -132,19 +133,23 @@ impl Config {
if let Some(count) = config_file.workers {
rocket.workers = usize::from(count.get());
}
- let domain = Url::parse(
- format!(
- "https://{}{}",
- config_file.domain,
- if config_file.port == 443 {
- String::new()
- } else {
- format!(":{}", config_file.port)
- }
- )
- .as_str(),
- )?;
- if domain.domain().is_none() {
+ let url = format!(
+ "https://{}{}",
+ config_file.domain,
+ if config_file.port == 443 {
+ String::new()
+ } else {
+ format!(":{}", config_file.port)
+ }
+ );
+ let domain = Url::parse(url.as_str())?;
+ if domain
+ .domain()
+ // We only allow domains in the config file.
+ // Note currently this check is overly conservative and
+ // disallows any domains that `Url` will encode in Punycode.
+ .map_or(true, |dom| !dom.eq_ignore_ascii_case(&config_file.domain))
+ {
return Err(ConfigErr::BadDomain);
}
Ok(Self {
@@ -176,12 +181,25 @@ impl Config {
pub const DATABASE_URL: &'static str = "data/db.sqlite3";
pub const PRIVATE_ED25519_KEY: &'static str = "data/ed25519_key.pem";
pub const WEB_VAULT_FOLDER: &'static str = "web-vault/";
+ #[allow(clippy::arithmetic_side_effects, clippy::string_slice)]
#[inline]
- pub fn domain_origin(&self) -> String {
- self.domain.origin().ascii_serialization()
+ pub fn domain_url(&self) -> &str {
+ let val = self.domain.as_str();
+ // The last Unicode scalar value is '/' which is a
+ // single UTF-8 code unit, and we want to remove that.
+ // Note if this changes in the future such that the last
+ // Unicode scalar value is encoded using more than one
+ // UTF-8 code unit, then this will panic.
+ // Additionally if `len` is somehow 0, indexing will panic
+ // making this memory and logic safe.
+ &val[..val.len() - 1]
}
#[inline]
- pub fn domain_path(&self) -> &str {
- self.domain.path().trim_end_matches('/')
+ pub fn domain(&self) -> &str {
+ self.domain.domain().expect("impossible to error")
+ }
+ #[inline]
+ pub fn domain_origin(&self) -> String {
+ self.domain.origin().ascii_serialization()
}
}
diff --git a/src/error.rs b/src/error.rs
@@ -47,7 +47,6 @@ use rocket::error::Error as RocketErr;
use serde_json::{Error as SerdeErr, Value};
use std::io::Error as IoErr;
use std::time::SystemTimeError as TimeErr;
-use tokio_tungstenite::tungstenite::Error as TungstError;
use webauthn_rs::prelude::WebauthnError as WebauthnErr;
#[derive(Serialize)]
@@ -79,7 +78,6 @@ make_error! {
DieselCon(DieselConErr): _has_source, _api_error,
Webauthn(WebauthnErr): _has_source, _api_error,
- WebSocket(TungstError): _has_source, _api_error,
}
// Error struct
// Contains a String error message, meant for the user and an enum variant, with an error of different types.
@@ -108,7 +106,6 @@ make_error! {
DieselCon(DieselConErr): _has_source, _api_error,
Webauthn(WebauthnErr): _has_source, _api_error,
- WebSocket(TungstError): _has_source, _api_error,
}
#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
impl From<Infallible> for Error {
@@ -142,8 +139,7 @@ impl Debug for Error {
| ErrorKind::OpenSSL(_)
| ErrorKind::Rocket(_)
| ErrorKind::DieselCon(_)
- | ErrorKind::Webauthn(_)
- | ErrorKind::WebSocket(_) => unreachable!(),
+ | ErrorKind::Webauthn(_) => unreachable!(),
},
}
}
diff --git a/src/main.rs b/src/main.rs
@@ -57,7 +57,6 @@
// We want to keep this as low as possible, but not higher then 128.
// If you go above 128 it will cause rust-analyzer to fail,
#![recursion_limit = "103"]
-extern crate alloc;
#[macro_use]
extern crate diesel;
#[macro_use]
@@ -75,7 +74,6 @@ mod crypto;
mod db;
mod priv_sep;
mod util;
-use alloc::sync::Arc;
use config::Config;
pub use error::{Error, MapResult};
use std::env;
@@ -124,8 +122,6 @@ fn static_init() {
)
});
auth::init_values();
- api::init_ws_users();
- api::init_ws_anonymous_subscriptions();
}
#[allow(clippy::exit)]
@@ -160,24 +156,17 @@ async fn create_db_pool() -> db::DbPool {
}
async fn launch_rocket(pool: db::DbPool) -> Result<(), Error> {
- let basepath = config::get_config().domain_path();
let instance = rocket::custom(&config::get_config().rocket)
- .mount([basepath, "/"].concat(), api::web_routes())
- .mount([basepath, "/admin"].concat(), api::admin_routes())
- .mount([basepath, "/api"].concat(), api::core_routes())
- .mount([basepath, "/events"].concat(), api::core_events_routes())
- .mount([basepath, "/icons"].concat(), api::icons_routes())
- .mount([basepath, "/identity"].concat(), api::identity_routes())
- .mount(
- [basepath, "/notifications"].concat(),
- api::notifications_routes(),
- )
- .register([basepath, "/"].concat(), api::web_catchers())
- .register([basepath, "/admin"].concat(), api::admin_catchers())
- .register([basepath, "/api"].concat(), api::core_catchers())
+ .mount("/", api::web_routes())
+ .mount("/admin", api::admin_routes())
+ .mount("/api", api::core_routes())
+ .mount("/events", api::core_events_routes())
+ .mount("/icons", api::icons_routes())
+ .mount("/identity", api::identity_routes())
+ .register("/", api::web_catchers())
+ .register("/admin", api::admin_catchers())
+ .register("/api", api::core_catchers())
.manage(pool)
- .manage(api::start_notification_server())
- .manage(Arc::clone(api::ws_anonymous_subscriptions()))
.attach(util::AppHeaders)
.attach(util::Cors)
.ignite()