Merge branch 'ws'

# Conflicts:
#	Cargo.toml
#	src/api/core/ciphers.rs
#	src/main.rs
This commit is contained in:
Daniel García
2018-09-13 15:59:45 +02:00
12 changed files with 889 additions and 230 deletions
Generated
+391 -146
View File
File diff suppressed because it is too large Load Diff
+17 -3
View File
@@ -15,9 +15,18 @@ reqwest = "0.8.8"
# multipart/form-data support
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
serde = "1.0.74"
serde_derive = "1.0.74"
serde = "1.0.75"
serde_derive = "1.0.75"
serde_json = "1.0.26"
# 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"] }
# Date and time library for Rust
chrono = "0.4.5"
chrono = "0.4.6"
# TOTP library
oath = "0.10.2"
@@ -58,14 +67,19 @@ lazy_static = "1.1.0"
num-traits = "0.2.5"
num-derive = "0.2.2"
# Email libraries
lettre = "0.8.2"
lettre_email = "0.8.2"
native-tls = "0.1.5"
fast_chemail = "0.9.5"
# Number encoding library
byteorder = "1.2.6"
[patch.crates-io]
# Make jwt use ring 0.11, to match rocket
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
u2f = { git = 'https://github.com/wisespace-io/u2f-rs', rev = '193de35093a44' }
+1
View File
@@ -76,6 +76,7 @@ RUN apt-get update && apt-get install -y\
RUN mkdir /data
VOLUME /data
EXPOSE 80
EXPOSE 3012
# Copies the files from the context (env file and web-vault)
# and the binary from the "build" stage to the current stage
+1
View File
@@ -68,6 +68,7 @@ RUN apk add \
RUN mkdir /data
VOLUME /data
EXPOSE 80
EXPOSE 3012
# Copies the files from the context (env file and web-vault)
# and the binary from the "build" stage to the current stage
+32
View File
@@ -25,6 +25,7 @@ _*Note, that this project is not associated with the [Bitwarden](https://bitward
- [Disable registration of new users](#disable-registration-of-new-users)
- [Disable invitations](#disable-invitations)
- [Enabling HTTPS](#enabling-https)
- [Enabling WebSocket notifications](#enabling-websocket-notifications)
- [Enabling U2F authentication](#enabling-u2f-authentication)
- [Changing persistent data location](#changing-persistent-data-location)
- [/data prefix:](#data-prefix)
@@ -175,6 +176,37 @@ docker run -d --name bitwarden \
```
Note that you need to mount ssl files and you need to forward appropriate port.
### Enabling WebSocket notifications
*Important: This does not apply to the mobile clients, which use push notifications.*
To enable WebSockets notifications, an external reverse proxy is necessary, and it must be configured to do the following:
- Route the `/notifications/hub` endpoint to the WebSocket server, by default at port `3012`, making sure to pass the `Connection` and `Upgrade` headers.
- Route everything else, including `/notifications/hub/negotiate`, to the standard Rocket server, by default at port `80`.
- If using Docker, you may need to map both ports with the `-p` flag
An example configuration is included next for a [Caddy](https://caddyserver.com/) proxy server, and assumes the proxy is running in the same computer as `bitwarden_rs`:
```r
localhost:2015 {
# The negotiation endpoint is also proxied to Rocket
proxy /notifications/hub/negotiate 0.0.0.0:80 {
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:80 {
transparent
}
}
```
Note: The reason for this workaround is the lack of support for WebSockets from Rocket (though [it's a planned feature](https://github.com/SergioBenitez/Rocket/issues/90)), which forces us to launch a secondary server on a separate port.
### Enabling U2F authentication
To enable U2F authentication, you must be serving bitwarden_rs from an HTTPS domain with a valid certificate (Either using the included
HTTPS options or with a reverse proxy). We recommend using a free certificate from Let's Encrypt.
+72 -56
View File
File diff suppressed because it is too large Load Diff
+15 -9
View File
@@ -1,9 +1,10 @@
use rocket::State;
use rocket_contrib::{Json, Value};
use db::DbConn;
use db::models::*;
use api::{JsonResult, EmptyResult, JsonUpcase};
use api::{JsonResult, EmptyResult, JsonUpcase, WebSocketUsers, UpdateType};
use auth::Headers;
#[get("/folders")]
@@ -40,23 +41,24 @@ pub struct FolderData {
}
#[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 mut folder = Folder::new(headers.user.uuid.clone(), data.Name);
folder.save(&conn);
ws.send_folder_update(UpdateType::SyncFolderCreate, &folder);
Ok(Json(folder.to_json()))
}
#[post("/folders/<uuid>", data = "<data>")]
fn post_folder(uuid: String, data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn) -> JsonResult {
put_folder(uuid, data, headers, conn)
fn post_folder(uuid: String, data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn, ws: State<WebSocketUsers>) -> JsonResult {
put_folder(uuid, data, headers, conn, ws)
}
#[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 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.save(&conn);
ws.send_folder_update(UpdateType::SyncFolderUpdate, &folder);
Ok(Json(folder.to_json()))
}
#[post("/folders/<uuid>/delete")]
fn delete_folder_post(uuid: String, headers: Headers, conn: DbConn) -> EmptyResult {
delete_folder(uuid, headers, conn)
fn delete_folder_post(uuid: String, headers: Headers, conn: DbConn, ws: State<WebSocketUsers>) -> EmptyResult {
delete_folder(uuid, headers, conn, ws)
}
#[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) {
Some(folder) => folder,
_ => err!("Invalid folder")
@@ -93,7 +96,10 @@ fn delete_folder(uuid: String, headers: Headers, conn: DbConn) -> EmptyResult {
// Delete the actual folder entry
match folder.delete(&conn) {
Ok(()) => Ok(()),
Ok(()) => {
ws.send_folder_update(UpdateType::SyncFolderDelete, &folder);
Ok(())
}
Err(_) => err!("Failed deleting folder")
}
}
+1
View File
@@ -9,6 +9,7 @@ pub use self::icons::routes as icons_routes;
pub use self::identity::routes as identity_routes;
pub use self::web::routes as web_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_contrib::Json;
File diff suppressed because it is too large Load Diff
+11 -5
View File
@@ -130,19 +130,25 @@ impl Cipher {
json_object
}
pub fn update_users_revision(&self, conn: &DbConn) {
pub fn update_users_revision(&self, conn: &DbConn) -> Vec<String> {
let mut user_uuids = Vec::new();
match self.user_uuid {
Some(ref user_uuid) => User::update_uuid_revision(&user_uuid, conn),
Some(ref user_uuid) => {
User::update_uuid_revision(&user_uuid, conn);
user_uuids.push(user_uuid.clone())
},
None => { // Belongs to Organization, need to update affected users
if let Some(ref org_uuid) = self.organization_uuid {
UserOrganization::find_by_cipher_and_org(&self.uuid, &org_uuid, conn)
.iter()
.for_each(|user_org| {
User::update_uuid_revision(&user_org.user_uuid, conn)
User::update_uuid_revision(&user_org.user_uuid, conn);
user_uuids.push(user_org.user_uuid.clone())
});
}
}
};
user_uuids
}
pub fn save(&mut self, conn: &DbConn) -> bool {
@@ -157,7 +163,7 @@ impl Cipher {
}
}
pub fn delete(self, conn: &DbConn) -> QueryResult<()> {
pub fn delete(&self, conn: &DbConn) -> QueryResult<()> {
self.update_users_revision(conn);
FolderCipher::delete_all_by_cipher(&self.uuid, &conn)?;
@@ -166,7 +172,7 @@ impl Cipher {
diesel::delete(
ciphers::table.filter(
ciphers::uuid.eq(self.uuid)
ciphers::uuid.eq(&self.uuid)
)
).execute(&**conn).and(Ok(()))
}
+2 -2
View File
@@ -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);
FolderCipher::delete_all_by_folder(&self.uuid, &conn)?;
diesel::delete(
folders::table.filter(
folders::uuid.eq(self.uuid)
folders::uuid.eq(&self.uuid)
)
).execute(&**conn).and(Ok(()))
}
+7 -3
View File
@@ -1,10 +1,13 @@
#![feature(plugin, custom_derive)]
#![feature(plugin, custom_derive, vec_remove_item)]
#![plugin(rocket_codegen)]
#![allow(proc_macro_derive_resolution_fallback)] // TODO: Remove this when diesel update fixes warnings
extern crate rocket;
extern crate rocket_contrib;
extern crate reqwest;
extern crate multipart;
extern crate ws;
extern crate rmpv;
extern crate chashmap;
extern crate serde;
#[macro_use]
extern crate serde_derive;
@@ -31,6 +34,7 @@ extern crate lettre;
extern crate lettre_email;
extern crate native_tls;
extern crate fast_chemail;
extern crate byteorder;
use std::{env, path::Path, process::{exit, Command}};
use rocket::Rocket;
@@ -52,6 +56,7 @@ fn init_rocket() -> Rocket {
.mount("/icons", api::icons_routes())
.mount("/notifications", api::notifications_routes())
.manage(db::init_pool())
.manage(api::start_notification_server())
}
// Embed the migrations from the migrations folder into the application
@@ -74,8 +79,7 @@ fn main() {
check_db();
check_rsa_keys();
check_web_vault();
migrations::run_migrations();
migrations::run_migrations();
init_rocket().launch();
}