mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2025-09-10 18:55:57 +03:00
Compare commits
23 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
56b3afa77c | ||
|
d335f45e34 | ||
|
34d2648509 | ||
|
f39c4fe2f4 | ||
|
01875c395b | ||
|
2872f40d13 | ||
|
d7df545078 | ||
|
d073f06652 | ||
|
3726da9c14 | ||
|
51450a0df9 | ||
|
98bae4a0a1 | ||
|
48e69cebab | ||
|
798a3b6a43 | ||
|
2dc1427027 | ||
|
233d23a527 | ||
|
06f7bd7c97 | ||
|
458a238c38 | ||
|
de72655bb1 | ||
|
4a2350891a | ||
|
4677ae4ac6 | ||
|
31349a47d3 | ||
|
55b7a3e4d1 | ||
|
692ed81306 |
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bitwarden_rs"
|
name = "bitwarden_rs"
|
||||||
version = "0.10.0"
|
version = "0.12.0"
|
||||||
authors = ["Daniel García <dani-garcia@users.noreply.github.com>"]
|
authors = ["Daniel García <dani-garcia@users.noreply.github.com>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@@ -68,6 +68,7 @@ RUN cargo build --release
|
|||||||
FROM debian:stretch-slim
|
FROM debian:stretch-slim
|
||||||
|
|
||||||
ENV ROCKET_ENV "staging"
|
ENV ROCKET_ENV "staging"
|
||||||
|
ENV ROCKET_WORKERS=10
|
||||||
|
|
||||||
# Install needed libraries
|
# Install needed libraries
|
||||||
RUN apt-get update && apt-get install -y\
|
RUN apt-get update && apt-get install -y\
|
||||||
|
26
README.md
26
README.md
@@ -4,8 +4,10 @@ Image is based on [Rust implementation of Bitwarden API](https://github.com/dani
|
|||||||
|
|
||||||
_*Note, that this project is not associated with the [Bitwarden](https://bitwarden.com/) project nor 8bit Solutions LLC._
|
_*Note, that this project is not associated with the [Bitwarden](https://bitwarden.com/) project nor 8bit Solutions LLC._
|
||||||
|
|
||||||
## Table of contents <!-- omit in toc -->
|
**Table of contents**
|
||||||
|
|
||||||
- [Features](#features)
|
- [Features](#features)
|
||||||
|
- [Missing features](#missing-features)
|
||||||
- [Docker image usage](#docker-image-usage)
|
- [Docker image usage](#docker-image-usage)
|
||||||
- [Starting a container](#starting-a-container)
|
- [Starting a container](#starting-a-container)
|
||||||
- [Updating the bitwarden image](#updating-the-bitwarden-image)
|
- [Updating the bitwarden image](#updating-the-bitwarden-image)
|
||||||
@@ -19,6 +21,7 @@ _*Note, that this project is not associated with the [Bitwarden](https://bitward
|
|||||||
- [attachments location](#attachments-location)
|
- [attachments location](#attachments-location)
|
||||||
- [icons cache](#icons-cache)
|
- [icons cache](#icons-cache)
|
||||||
- [Changing the API request size limit](#changing-the-api-request-size-limit)
|
- [Changing the API request size limit](#changing-the-api-request-size-limit)
|
||||||
|
- [Changing the number of workers](#changing-the-number-of-workers)
|
||||||
- [Other configuration](#other-configuration)
|
- [Other configuration](#other-configuration)
|
||||||
- [Building your own image](#building-your-own-image)
|
- [Building your own image](#building-your-own-image)
|
||||||
- [Building binary](#building-binary)
|
- [Building binary](#building-binary)
|
||||||
@@ -135,7 +138,7 @@ docker run -d --name bitwarden \
|
|||||||
-v /ssl/keys/:/ssl/ \
|
-v /ssl/keys/:/ssl/ \
|
||||||
-v /bw-data/:/data/ \
|
-v /bw-data/:/data/ \
|
||||||
-v /icon_cache/ \
|
-v /icon_cache/ \
|
||||||
-p 443:443 \
|
-p 443:80 \
|
||||||
mprasil/bitwarden:latest
|
mprasil/bitwarden:latest
|
||||||
```
|
```
|
||||||
Note that you need to mount ssl files and you need to forward appropriate port.
|
Note that you need to mount ssl files and you need to forward appropriate port.
|
||||||
@@ -231,6 +234,20 @@ docker run -d --name bitwarden \
|
|||||||
mprasil/bitwarden:latest
|
mprasil/bitwarden:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Changing the number of workers
|
||||||
|
|
||||||
|
When you run bitwarden_rs, it spawns `2 * <number of cpu cores>` workers to handle requests. On some systems this might lead to low number of workers and hence slow performance, so the default in the docker image is changed to spawn 10 threads. You can override this setting to increase or decrease the number of workers by setting the `ROCKET_WORKERS` variable.
|
||||||
|
|
||||||
|
In the example bellow, we're starting with 20 workers:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker run -d --name bitwarden \
|
||||||
|
-e ROCKET_WORKERS=20 \
|
||||||
|
-v /bw-data/:/data/ \
|
||||||
|
-p 80:80 \
|
||||||
|
mprasil/bitwarden:latest
|
||||||
|
```
|
||||||
|
|
||||||
### Other configuration
|
### Other configuration
|
||||||
|
|
||||||
Though this is unlikely to be required in small deployment, you can fine-tune some other settings like number of workers using environment variables that are processed by [Rocket](https://rocket.rs), please see details in [documentation](https://rocket.rs/guide/configuration/#environment-variables).
|
Though this is unlikely to be required in small deployment, you can fine-tune some other settings like number of workers using environment variables that are processed by [Rocket](https://rocket.rs), please see details in [documentation](https://rocket.rs/guide/configuration/#environment-variables).
|
||||||
@@ -252,8 +269,7 @@ For building binary outside the Docker environment and running it locally withou
|
|||||||
|
|
||||||
### Arch Linux
|
### Arch Linux
|
||||||
|
|
||||||
Bitwarden_rs is already packaged for Archlinux thanks to @mqus. There is an AUR package [with](https://aur.archlinux.org/packages/bitwarden_rs-vault-git/) and
|
Bitwarden_rs is already packaged for Archlinux thanks to @mqus. There is an [AUR package](https://aur.archlinux.org/packages/bitwarden_rs) (optionally with the [vault web interface](https://aur.archlinux.org/packages/bitwarden_rs-vault/) ) available.
|
||||||
[without](https://aur.archlinux.org/packages/bitwarden_rs-git/) the vault web interface available.
|
|
||||||
|
|
||||||
## Backing up your vault
|
## Backing up your vault
|
||||||
|
|
||||||
@@ -301,4 +317,4 @@ docker run -d --name bitwarden \
|
|||||||
|
|
||||||
To ask an question, [raising an issue](https://github.com/dani-garcia/bitwarden_rs/issues/new) is fine, also please report any bugs spotted here.
|
To ask an question, [raising an issue](https://github.com/dani-garcia/bitwarden_rs/issues/new) is fine, also please report any bugs spotted here.
|
||||||
|
|
||||||
If you prefer to chat, we're usually hanging around at [#bitwarden_rs:matrix.org](https://matrix.to/#/!cASGtOHlSftdScFNMs:matrix.org) room on Matrix. Feel free to join us!
|
If you prefer to chat, we're usually hanging around at [#bitwarden_rs:matrix.org](https://matrix.to/#/#bitwarden_rs:matrix.org) room on Matrix. Feel free to join us!
|
||||||
|
@@ -415,6 +415,22 @@ fn post_attachment(uuid: String, data: Data, content_type: &ContentType, headers
|
|||||||
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn)))
|
Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[post("/ciphers/<uuid>/attachment-admin", format = "multipart/form-data", data = "<data>")]
|
||||||
|
fn post_attachment_admin(uuid: String, data: Data, content_type: &ContentType, headers: Headers, conn: DbConn) -> JsonResult {
|
||||||
|
post_attachment(uuid, data, content_type, headers, conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/ciphers/<uuid>/attachment/<attachment_id>/share", format = "multipart/form-data", data = "<data>")]
|
||||||
|
fn post_attachment_share(uuid: String, attachment_id: String, data: Data, content_type: &ContentType, headers: Headers, conn: DbConn) -> JsonResult {
|
||||||
|
_delete_cipher_attachment_by_id(&uuid, &attachment_id, &headers, &conn)?;
|
||||||
|
post_attachment(uuid, data, content_type, headers, conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/ciphers/<uuid>/attachment/<attachment_id>/delete-admin")]
|
||||||
|
fn delete_attachment_post_admin(uuid: String, attachment_id: String, headers: Headers, conn: DbConn) -> EmptyResult {
|
||||||
|
delete_attachment(uuid, attachment_id, headers, conn)
|
||||||
|
}
|
||||||
|
|
||||||
#[post("/ciphers/<uuid>/attachment/<attachment_id>/delete")]
|
#[post("/ciphers/<uuid>/attachment/<attachment_id>/delete")]
|
||||||
fn delete_attachment_post(uuid: String, attachment_id: String, headers: Headers, conn: DbConn) -> EmptyResult {
|
fn delete_attachment_post(uuid: String, attachment_id: String, headers: Headers, conn: DbConn) -> EmptyResult {
|
||||||
delete_attachment(uuid, attachment_id, headers, conn)
|
delete_attachment(uuid, attachment_id, headers, conn)
|
||||||
@@ -422,29 +438,7 @@ fn delete_attachment_post(uuid: String, attachment_id: String, headers: Headers,
|
|||||||
|
|
||||||
#[delete("/ciphers/<uuid>/attachment/<attachment_id>")]
|
#[delete("/ciphers/<uuid>/attachment/<attachment_id>")]
|
||||||
fn delete_attachment(uuid: String, attachment_id: String, headers: Headers, conn: DbConn) -> EmptyResult {
|
fn delete_attachment(uuid: String, attachment_id: String, headers: Headers, conn: DbConn) -> EmptyResult {
|
||||||
let attachment = match Attachment::find_by_id(&attachment_id, &conn) {
|
_delete_cipher_attachment_by_id(&uuid, &attachment_id, &headers, &conn)
|
||||||
Some(attachment) => attachment,
|
|
||||||
None => err!("Attachment doesn't exist")
|
|
||||||
};
|
|
||||||
|
|
||||||
if attachment.cipher_uuid != uuid {
|
|
||||||
err!("Attachment from other cipher")
|
|
||||||
}
|
|
||||||
|
|
||||||
let cipher = match Cipher::find_by_uuid(&uuid, &conn) {
|
|
||||||
Some(cipher) => cipher,
|
|
||||||
None => err!("Cipher doesn't exist")
|
|
||||||
};
|
|
||||||
|
|
||||||
if !cipher.is_write_accessible_to_user(&headers.user.uuid, &conn) {
|
|
||||||
err!("Cipher cannot be deleted by user")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete attachment
|
|
||||||
match attachment.delete(&conn) {
|
|
||||||
Ok(()) => Ok(()),
|
|
||||||
Err(_) => err!("Deleting attachement failed")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/ciphers/<uuid>/delete")]
|
#[post("/ciphers/<uuid>/delete")]
|
||||||
@@ -578,3 +572,29 @@ fn _delete_cipher_by_uuid(uuid: &str, headers: &Headers, conn: &DbConn) -> Empty
|
|||||||
Err(_) => err!("Failed deleting cipher")
|
Err(_) => err!("Failed deleting cipher")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn _delete_cipher_attachment_by_id(uuid: &str, attachment_id: &str, headers: &Headers, conn: &DbConn) -> EmptyResult {
|
||||||
|
let attachment = match Attachment::find_by_id(&attachment_id, &conn) {
|
||||||
|
Some(attachment) => attachment,
|
||||||
|
None => err!("Attachment doesn't exist")
|
||||||
|
};
|
||||||
|
|
||||||
|
if attachment.cipher_uuid != uuid {
|
||||||
|
err!("Attachment from other cipher")
|
||||||
|
}
|
||||||
|
|
||||||
|
let cipher = match Cipher::find_by_uuid(&uuid, &conn) {
|
||||||
|
Some(cipher) => cipher,
|
||||||
|
None => err!("Cipher doesn't exist")
|
||||||
|
};
|
||||||
|
|
||||||
|
if !cipher.is_write_accessible_to_user(&headers.user.uuid, &conn) {
|
||||||
|
err!("Cipher cannot be deleted by user")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete attachment
|
||||||
|
match attachment.delete(&conn) {
|
||||||
|
Ok(()) => Ok(()),
|
||||||
|
Err(_) => err!("Deleting attachement failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -34,7 +34,10 @@ pub fn routes() -> Vec<Route> {
|
|||||||
post_ciphers_admin,
|
post_ciphers_admin,
|
||||||
post_ciphers_import,
|
post_ciphers_import,
|
||||||
post_attachment,
|
post_attachment,
|
||||||
|
post_attachment_admin,
|
||||||
|
post_attachment_share,
|
||||||
delete_attachment_post,
|
delete_attachment_post,
|
||||||
|
delete_attachment_post_admin,
|
||||||
delete_attachment,
|
delete_attachment,
|
||||||
post_cipher_admin,
|
post_cipher_admin,
|
||||||
post_cipher_share,
|
post_cipher_share,
|
||||||
|
@@ -4,7 +4,7 @@ use std::path::{Path, PathBuf};
|
|||||||
use rocket::request::Request;
|
use rocket::request::Request;
|
||||||
use rocket::response::{self, NamedFile, Responder};
|
use rocket::response::{self, NamedFile, Responder};
|
||||||
use rocket::response::content::Content;
|
use rocket::response::content::Content;
|
||||||
use rocket::http::ContentType;
|
use rocket::http::{ContentType, Status};
|
||||||
use rocket::Route;
|
use rocket::Route;
|
||||||
use rocket_contrib::{Json, Value};
|
use rocket_contrib::{Json, Value};
|
||||||
|
|
||||||
@@ -49,14 +49,19 @@ struct WebHeaders<R>(R);
|
|||||||
|
|
||||||
impl<'r, R: Responder<'r>> Responder<'r> for WebHeaders<R> {
|
impl<'r, R: Responder<'r>> Responder<'r> for WebHeaders<R> {
|
||||||
fn respond_to(self, req: &Request) -> response::Result<'r> {
|
fn respond_to(self, req: &Request) -> response::Result<'r> {
|
||||||
let mut res = self.0.respond_to(req)?;
|
match self.0.respond_to(req) {
|
||||||
|
Ok(mut res) => {
|
||||||
res.set_raw_header("Referrer-Policy", "same-origin");
|
res.set_raw_header("Referrer-Policy", "same-origin");
|
||||||
res.set_raw_header("X-Frame-Options", "SAMEORIGIN");
|
res.set_raw_header("X-Frame-Options", "SAMEORIGIN");
|
||||||
res.set_raw_header("X-Content-Type-Options", "nosniff");
|
res.set_raw_header("X-Content-Type-Options", "nosniff");
|
||||||
res.set_raw_header("X-XSS-Protection", "1; mode=block");
|
res.set_raw_header("X-XSS-Protection", "1; mode=block");
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
|
},
|
||||||
|
Err(_) => {
|
||||||
|
Err(Status::NotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
10
src/auth.rs
10
src/auth.rs
@@ -95,7 +95,7 @@ use rocket::Outcome;
|
|||||||
use rocket::request::{self, Request, FromRequest};
|
use rocket::request::{self, Request, FromRequest};
|
||||||
|
|
||||||
use db::DbConn;
|
use db::DbConn;
|
||||||
use db::models::{User, UserOrganization, UserOrgType, Device};
|
use db::models::{User, UserOrganization, UserOrgType, UserOrgStatus, Device};
|
||||||
|
|
||||||
pub struct Headers {
|
pub struct Headers {
|
||||||
pub host: String,
|
pub host: String,
|
||||||
@@ -205,7 +205,13 @@ impl<'a, 'r> FromRequest<'a, 'r> for OrgHeaders {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let org_user = match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn) {
|
let org_user = match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn) {
|
||||||
Some(user) => user,
|
Some(user) => {
|
||||||
|
if user.status == UserOrgStatus::Confirmed as i32 {
|
||||||
|
user
|
||||||
|
} else {
|
||||||
|
err_handler!("The current user isn't confirmed member of the organization")
|
||||||
|
}
|
||||||
|
}
|
||||||
None => err_handler!("The current user isn't member of the organization")
|
None => err_handler!("The current user isn't member of the organization")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -64,14 +64,33 @@ impl Attachment {
|
|||||||
|
|
||||||
pub fn delete(self, conn: &DbConn) -> QueryResult<()> {
|
pub fn delete(self, conn: &DbConn) -> QueryResult<()> {
|
||||||
use util;
|
use util;
|
||||||
|
use std::{thread, time};
|
||||||
|
|
||||||
|
let mut retries = 10;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match diesel::delete(
|
||||||
|
attachments::table.filter(
|
||||||
|
attachments::id.eq(&self.id)
|
||||||
|
)
|
||||||
|
).execute(&**conn) {
|
||||||
|
Ok(_) => break,
|
||||||
|
Err(err) => {
|
||||||
|
if retries < 1 {
|
||||||
|
println!("ERROR: Failed with 10 retries");
|
||||||
|
return Err(err)
|
||||||
|
} else {
|
||||||
|
retries = retries - 1;
|
||||||
|
println!("Had to retry! Retries left: {}", retries);
|
||||||
|
thread::sleep(time::Duration::from_millis(500));
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
util::delete_file(&self.get_file_path());
|
util::delete_file(&self.get_file_path());
|
||||||
|
Ok(())
|
||||||
diesel::delete(
|
|
||||||
attachments::table.filter(
|
|
||||||
attachments::id.eq(self.id)
|
|
||||||
)
|
|
||||||
).execute(&**conn).and(Ok(()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> QueryResult<()> {
|
pub fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> QueryResult<()> {
|
||||||
|
@@ -2,7 +2,7 @@ use serde_json::Value as JsonValue;
|
|||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::{Organization, UserOrganization, UserOrgType};
|
use super::{Organization, UserOrganization, UserOrgType, UserOrgStatus};
|
||||||
|
|
||||||
#[derive(Debug, Identifiable, Queryable, Insertable, Associations)]
|
#[derive(Debug, Identifiable, Queryable, Insertable, Associations)]
|
||||||
#[table_name = "collections"]
|
#[table_name = "collections"]
|
||||||
@@ -78,13 +78,18 @@ impl Collection {
|
|||||||
pub fn find_by_user_uuid(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
pub fn find_by_user_uuid(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
let mut all_access_collections = users_organizations::table
|
let mut all_access_collections = users_organizations::table
|
||||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||||
|
.filter(users_organizations::status.eq(UserOrgStatus::Confirmed as i32))
|
||||||
.filter(users_organizations::access_all.eq(true))
|
.filter(users_organizations::access_all.eq(true))
|
||||||
.inner_join(collections::table.on(collections::org_uuid.eq(users_organizations::org_uuid)))
|
.inner_join(collections::table.on(collections::org_uuid.eq(users_organizations::org_uuid)))
|
||||||
.select(collections::all_columns)
|
.select(collections::all_columns)
|
||||||
.load::<Self>(&**conn).expect("Error loading collections");
|
.load::<Self>(&**conn).expect("Error loading collections");
|
||||||
|
|
||||||
let mut assigned_collections = users_collections::table.inner_join(collections::table)
|
let mut assigned_collections = users_collections::table.inner_join(collections::table)
|
||||||
|
.left_join(users_organizations::table.on(
|
||||||
|
users_collections::user_uuid.eq(users_organizations::user_uuid)
|
||||||
|
))
|
||||||
.filter(users_collections::user_uuid.eq(user_uuid))
|
.filter(users_collections::user_uuid.eq(user_uuid))
|
||||||
|
.filter(users_organizations::status.eq(UserOrgStatus::Confirmed as i32))
|
||||||
.select(collections::all_columns)
|
.select(collections::all_columns)
|
||||||
.load::<Self>(&**conn).expect("Error loading collections");
|
.load::<Self>(&**conn).expect("Error loading collections");
|
||||||
|
|
||||||
|
@@ -268,6 +268,7 @@ impl UserOrganization {
|
|||||||
pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
|
||||||
users_organizations::table
|
users_organizations::table
|
||||||
.filter(users_organizations::user_uuid.eq(user_uuid))
|
.filter(users_organizations::user_uuid.eq(user_uuid))
|
||||||
|
.filter(users_organizations::status.eq(UserOrgStatus::Confirmed as i32))
|
||||||
.load::<Self>(&**conn).unwrap_or(vec![])
|
.load::<Self>(&**conn).unwrap_or(vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -83,6 +83,11 @@ fn check_db() {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Turn on WAL in SQLite
|
||||||
|
use diesel::RunQueryDsl;
|
||||||
|
let connection = db::get_connection().expect("Can't conect to DB");
|
||||||
|
diesel::sql_query("PRAGMA journal_mode=wal").execute(&connection).expect("Failed to turn on WAL");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_rsa_keys() {
|
fn check_rsa_keys() {
|
||||||
|
Reference in New Issue
Block a user