mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2026-05-30 17:37:14 +00:00
Initial version of websockets notification support.
For now only folder notifications are sent (create, rename, delete).
The notifications are only tested between two web-vault sessions in different browsers, mobile apps and browser extensions are untested.
The websocket server is exposed in port 3012, while the rocket server is exposed in another port (8000 by default). To make notifications work, both should be accessible in the same port, which requires a reverse proxy.
My testing is done with Caddy server, and the following config:
```
localhost {
# The negotiation endpoint is also proxied to Rocket
proxy /notifications/hub/negotiate 0.0.0.0:8000 {
transparent
}
# Notifications redirected to the websockets server
proxy /notifications/hub 0.0.0.0:3012 {
websocket
}
# Proxy the Root directory to Rocket
proxy / 0.0.0.0:8000 {
transparent
}
}
```
This exposes the service in port 2015.
This commit is contained in:
Generated
+391
-146
File diff suppressed because it is too large
Load Diff
+16
-3
@@ -15,9 +15,18 @@ reqwest = "0.8.8"
|
|||||||
# multipart/form-data support
|
# multipart/form-data support
|
||||||
multipart = "0.15.2"
|
multipart = "0.15.2"
|
||||||
|
|
||||||
|
# WebSockets library
|
||||||
|
ws = "0.7.8"
|
||||||
|
|
||||||
|
# MessagePack library
|
||||||
|
rmpv = "0.4.0"
|
||||||
|
|
||||||
|
# Concurrent hashmap implementation
|
||||||
|
chashmap = "2.2.0"
|
||||||
|
|
||||||
# A generic serialization/deserialization framework
|
# A generic serialization/deserialization framework
|
||||||
serde = "1.0.74"
|
serde = "1.0.75"
|
||||||
serde_derive = "1.0.74"
|
serde_derive = "1.0.75"
|
||||||
serde_json = "1.0.26"
|
serde_json = "1.0.26"
|
||||||
|
|
||||||
# A safe, extensible ORM and Query builder
|
# A safe, extensible ORM and Query builder
|
||||||
@@ -34,7 +43,7 @@ ring = { version = "= 0.11.0", features = ["rsa_signing"] }
|
|||||||
uuid = { version = "0.6.5", features = ["v4"] }
|
uuid = { version = "0.6.5", features = ["v4"] }
|
||||||
|
|
||||||
# Date and time library for Rust
|
# Date and time library for Rust
|
||||||
chrono = "0.4.5"
|
chrono = "0.4.6"
|
||||||
|
|
||||||
# TOTP library
|
# TOTP library
|
||||||
oath = "0.10.2"
|
oath = "0.10.2"
|
||||||
@@ -58,9 +67,13 @@ lazy_static = "1.1.0"
|
|||||||
num-traits = "0.2.5"
|
num-traits = "0.2.5"
|
||||||
num-derive = "0.2.2"
|
num-derive = "0.2.2"
|
||||||
|
|
||||||
|
# Number encoding library
|
||||||
|
byteorder = "1.2.6"
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
# Make jwt use ring 0.11, to match rocket
|
# Make jwt use ring 0.11, to match rocket
|
||||||
jsonwebtoken = { path = "libs/jsonwebtoken" }
|
jsonwebtoken = { path = "libs/jsonwebtoken" }
|
||||||
|
rmp = { git = 'https://github.com/dani-garcia/msgpack-rust' }
|
||||||
|
|
||||||
# Version 0.1.2 from crates.io lacks a commit that fixes a certificate error
|
# Version 0.1.2 from crates.io lacks a commit that fixes a certificate error
|
||||||
u2f = { git = 'https://github.com/wisespace-io/u2f-rs', rev = '193de35093a44' }
|
u2f = { git = 'https://github.com/wisespace-io/u2f-rs', rev = '193de35093a44' }
|
||||||
|
|||||||
+15
-9
@@ -1,9 +1,10 @@
|
|||||||
|
use rocket::State;
|
||||||
use rocket_contrib::{Json, Value};
|
use rocket_contrib::{Json, Value};
|
||||||
|
|
||||||
use db::DbConn;
|
use db::DbConn;
|
||||||
use db::models::*;
|
use db::models::*;
|
||||||
|
|
||||||
use api::{JsonResult, EmptyResult, JsonUpcase};
|
use api::{JsonResult, EmptyResult, JsonUpcase, WebSocketUsers, UpdateType};
|
||||||
use auth::Headers;
|
use auth::Headers;
|
||||||
|
|
||||||
#[get("/folders")]
|
#[get("/folders")]
|
||||||
@@ -40,23 +41,24 @@ pub struct FolderData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[post("/folders", data = "<data>")]
|
#[post("/folders", data = "<data>")]
|
||||||
fn post_folders(data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn) -> JsonResult {
|
fn post_folders(data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn, ws: State<WebSocketUsers>) -> JsonResult {
|
||||||
let data: FolderData = data.into_inner().data;
|
let data: FolderData = data.into_inner().data;
|
||||||
|
|
||||||
let mut folder = Folder::new(headers.user.uuid.clone(), data.Name);
|
let mut folder = Folder::new(headers.user.uuid.clone(), data.Name);
|
||||||
|
|
||||||
folder.save(&conn);
|
folder.save(&conn);
|
||||||
|
ws.send_folder_update(UpdateType::SyncFolderCreate, &folder);
|
||||||
|
|
||||||
Ok(Json(folder.to_json()))
|
Ok(Json(folder.to_json()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/folders/<uuid>", data = "<data>")]
|
#[post("/folders/<uuid>", data = "<data>")]
|
||||||
fn post_folder(uuid: String, data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn) -> JsonResult {
|
fn post_folder(uuid: String, data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn, ws: State<WebSocketUsers>) -> JsonResult {
|
||||||
put_folder(uuid, data, headers, conn)
|
put_folder(uuid, data, headers, conn, ws)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[put("/folders/<uuid>", data = "<data>")]
|
#[put("/folders/<uuid>", data = "<data>")]
|
||||||
fn put_folder(uuid: String, data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn) -> JsonResult {
|
fn put_folder(uuid: String, data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn, ws: State<WebSocketUsers>) -> JsonResult {
|
||||||
let data: FolderData = data.into_inner().data;
|
let data: FolderData = data.into_inner().data;
|
||||||
|
|
||||||
let mut folder = match Folder::find_by_uuid(&uuid, &conn) {
|
let mut folder = match Folder::find_by_uuid(&uuid, &conn) {
|
||||||
@@ -71,17 +73,18 @@ fn put_folder(uuid: String, data: JsonUpcase<FolderData>, headers: Headers, conn
|
|||||||
folder.name = data.Name;
|
folder.name = data.Name;
|
||||||
|
|
||||||
folder.save(&conn);
|
folder.save(&conn);
|
||||||
|
ws.send_folder_update(UpdateType::SyncFolderUpdate, &folder);
|
||||||
|
|
||||||
Ok(Json(folder.to_json()))
|
Ok(Json(folder.to_json()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/folders/<uuid>/delete")]
|
#[post("/folders/<uuid>/delete")]
|
||||||
fn delete_folder_post(uuid: String, headers: Headers, conn: DbConn) -> EmptyResult {
|
fn delete_folder_post(uuid: String, headers: Headers, conn: DbConn, ws: State<WebSocketUsers>) -> EmptyResult {
|
||||||
delete_folder(uuid, headers, conn)
|
delete_folder(uuid, headers, conn, ws)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[delete("/folders/<uuid>")]
|
#[delete("/folders/<uuid>")]
|
||||||
fn delete_folder(uuid: String, headers: Headers, conn: DbConn) -> EmptyResult {
|
fn delete_folder(uuid: String, headers: Headers, conn: DbConn, ws: State<WebSocketUsers>) -> EmptyResult {
|
||||||
let folder = match Folder::find_by_uuid(&uuid, &conn) {
|
let folder = match Folder::find_by_uuid(&uuid, &conn) {
|
||||||
Some(folder) => folder,
|
Some(folder) => folder,
|
||||||
_ => err!("Invalid folder")
|
_ => err!("Invalid folder")
|
||||||
@@ -93,7 +96,10 @@ fn delete_folder(uuid: String, headers: Headers, conn: DbConn) -> EmptyResult {
|
|||||||
|
|
||||||
// Delete the actual folder entry
|
// Delete the actual folder entry
|
||||||
match folder.delete(&conn) {
|
match folder.delete(&conn) {
|
||||||
Ok(()) => Ok(()),
|
Ok(()) => {
|
||||||
|
ws.send_folder_update(UpdateType::SyncFolderDelete, &folder);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Err(_) => err!("Failed deleting folder")
|
Err(_) => err!("Failed deleting folder")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ pub use self::icons::routes as icons_routes;
|
|||||||
pub use self::identity::routes as identity_routes;
|
pub use self::identity::routes as identity_routes;
|
||||||
pub use self::web::routes as web_routes;
|
pub use self::web::routes as web_routes;
|
||||||
pub use self::notifications::routes as notifications_routes;
|
pub use self::notifications::routes as notifications_routes;
|
||||||
|
pub use self::notifications::{start_notification_server, WebSocketUsers, UpdateType};
|
||||||
|
|
||||||
use rocket::response::status::BadRequest;
|
use rocket::response::status::BadRequest;
|
||||||
use rocket_contrib::Json;
|
use rocket_contrib::Json;
|
||||||
|
|||||||
+333
-5
File diff suppressed because it is too large
Load Diff
@@ -82,13 +82,13 @@ impl Folder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(self, conn: &DbConn) -> QueryResult<()> {
|
pub fn delete(&self, conn: &DbConn) -> QueryResult<()> {
|
||||||
User::update_uuid_revision(&self.user_uuid, conn);
|
User::update_uuid_revision(&self.user_uuid, conn);
|
||||||
FolderCipher::delete_all_by_folder(&self.uuid, &conn)?;
|
FolderCipher::delete_all_by_folder(&self.uuid, &conn)?;
|
||||||
|
|
||||||
diesel::delete(
|
diesel::delete(
|
||||||
folders::table.filter(
|
folders::table.filter(
|
||||||
folders::uuid.eq(self.uuid)
|
folders::uuid.eq(&self.uuid)
|
||||||
)
|
)
|
||||||
).execute(&**conn).and(Ok(()))
|
).execute(&**conn).and(Ok(()))
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-3
@@ -1,10 +1,13 @@
|
|||||||
#![feature(plugin, custom_derive)]
|
#![feature(plugin, custom_derive, vec_remove_item)]
|
||||||
#![plugin(rocket_codegen)]
|
#![plugin(rocket_codegen)]
|
||||||
#![allow(proc_macro_derive_resolution_fallback)] // TODO: Remove this when diesel update fixes warnings
|
#![allow(proc_macro_derive_resolution_fallback)] // TODO: Remove this when diesel update fixes warnings
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
extern crate rocket_contrib;
|
extern crate rocket_contrib;
|
||||||
extern crate reqwest;
|
extern crate reqwest;
|
||||||
extern crate multipart;
|
extern crate multipart;
|
||||||
|
extern crate ws;
|
||||||
|
extern crate rmpv;
|
||||||
|
extern crate chashmap;
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
@@ -27,6 +30,7 @@ extern crate lazy_static;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate num_derive;
|
extern crate num_derive;
|
||||||
extern crate num_traits;
|
extern crate num_traits;
|
||||||
|
extern crate byteorder;
|
||||||
|
|
||||||
use std::{env, path::Path, process::{exit, Command}};
|
use std::{env, path::Path, process::{exit, Command}};
|
||||||
use rocket::Rocket;
|
use rocket::Rocket;
|
||||||
@@ -47,6 +51,7 @@ fn init_rocket() -> Rocket {
|
|||||||
.mount("/icons", api::icons_routes())
|
.mount("/icons", api::icons_routes())
|
||||||
.mount("/notifications", api::notifications_routes())
|
.mount("/notifications", api::notifications_routes())
|
||||||
.manage(db::init_pool())
|
.manage(db::init_pool())
|
||||||
|
.manage(api::start_notification_server())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Embed the migrations from the migrations folder into the application
|
// Embed the migrations from the migrations folder into the application
|
||||||
@@ -69,8 +74,7 @@ fn main() {
|
|||||||
check_db();
|
check_db();
|
||||||
check_rsa_keys();
|
check_rsa_keys();
|
||||||
check_web_vault();
|
check_web_vault();
|
||||||
migrations::run_migrations();
|
migrations::run_migrations();
|
||||||
|
|
||||||
|
|
||||||
init_rocket().launch();
|
init_rocket().launch();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user