commit 538dc00234c9e6e27026b30ce13f19a14369aa95
parent 515c84d74dbd3dad2353e34ab18f9300075d5cc3
Author: Daniel GarcĂa <dani-garcia@users.noreply.github.com>
Date: Tue, 12 Jun 2018 21:09:42 +0200
Improved configuration and documented options. Implemented option to disable web vault and to disable the use of bitwarden's official icon servers
Diffstat:
4 files changed, 104 insertions(+), 47 deletions(-)
diff --git a/.env b/.env
@@ -1,13 +1,34 @@
+## Bitwarden_RS Configuration File
+## Uncomment any of the following lines to change the defaults
+
+## Main data folder
+# DATA_FOLDER=data
+
+## Individual folders, these override %DATA_FOLDER%
# DATABASE_URL=data/db.sqlite3
-# PRIVATE_RSA_KEY=data/private_rsa_key.der
-# PUBLIC_RSA_KEY=data/public_rsa_key.der
+# RSA_KEY_FILENAME=data/rsa_key
# ICON_CACHE_FOLDER=data/icon_cache
# ATTACHMENTS_FOLDER=data/attachments
-# true for yes, anything else for no
-SIGNUPS_ALLOWED=true
+## Web vault settings
+# WEB_VAULT_FOLDER=web-vault/
+# WEB_VAULT_ENABLED=true
+
+## Controls if new users can register
+# SIGNUPS_ALLOWED=true
+
+## Use a local favicon extractor
+## Set to false to use bitwarden's official icon servers
+## Set to true to use the local version, which is not as smart,
+## but it doesn't send the cipher domains to bitwarden's servers
+# LOCAL_ICON_EXTRACTOR=false
+
+## Controls the PBBKDF password iterations to apply on the server
+## The change only applies when the password is changed
+# PASSWORD_ITERATIONS=100000
-# ROCKET_ENV=production
+## Rocket specific settings, check Rocket documentation to learn more
+# ROCKET_ENV=staging
# ROCKET_ADDRESS=0.0.0.0 # Enable this to test mobile app
# ROCKET_PORT=8000
# ROCKET_TLS={certs="/path/to/certs.pem",key="/path/to/key.pem"}
diff --git a/src/api/icons.rs b/src/api/icons.rs
@@ -1,4 +1,3 @@
-use std::io;
use std::io::prelude::*;
use std::fs::{create_dir_all, File};
@@ -23,60 +22,85 @@ fn icon(domain: String) -> Content<Vec<u8>> {
return Content(icon_type, get_fallback_icon());
}
- let url = format!("https://icons.bitwarden.com/{}/icon.png", domain);
-
- // Get the icon, or fallback in case of error
- let icon = match get_icon_cached(&domain, &url) {
- Ok(icon) => icon,
- Err(_) => return Content(icon_type, get_fallback_icon())
- };
+ let icon = get_icon(&domain);
Content(icon_type, icon)
}
-fn get_icon(url: &str) -> Result<Vec<u8>, reqwest::Error> {
- let mut res = reqwest::get(url)?;
+fn get_icon (domain: &str) -> Vec<u8> {
+ let path = format!("{}/{}.png", CONFIG.icon_cache_folder, domain);
- res = match res.error_for_status() {
- Err(e) => return Err(e),
- Ok(res) => res
- };
+ if let Some(icon) = get_cached_icon(&path) {
+ return icon;
+ }
- let mut buffer: Vec<u8> = vec![];
- res.copy_to(&mut buffer)?;
+ let url = get_icon_url(&domain);
- Ok(buffer)
+ // Get the icon, or fallback in case of error
+ match download_icon(&url) {
+ Ok(icon) => {
+ save_icon(&path, &icon);
+ icon
+ },
+ Err(_) => get_fallback_icon()
+ }
}
-fn get_icon_cached(key: &str, url: &str) -> io::Result<Vec<u8>> {
- create_dir_all(&CONFIG.icon_cache_folder)?;
- let path = &format!("{}/{}.png", CONFIG.icon_cache_folder, key);
-
+fn get_cached_icon(path: &str) -> Option<Vec<u8>> {
// Try to read the cached icon, and return it if it exists
if let Ok(mut f) = File::open(path) {
let mut buffer = Vec::new();
if f.read_to_end(&mut buffer).is_ok() {
- return Ok(buffer);
+ return Some(buffer);
}
- /* If error reading file continue */
}
- println!("Downloading icon for {}...", key);
- let icon = match get_icon(url) {
- Ok(icon) => icon,
- Err(_) => return Err(io::Error::new(io::ErrorKind::NotFound, ""))
- };
+ None
+}
+
+fn get_icon_url(domain: &str) -> String {
+ if CONFIG.local_icon_extractor {
+ format!("http://{}/favicon.ico", domain)
+ } else {
+ format!("https://icons.bitwarden.com/{}/icon.png", domain)
+ }
+}
+
+fn download_icon(url: &str) -> Result<Vec<u8>, reqwest::Error> {
+ println!("Downloading icon for {}...", url);
+ let mut res = reqwest::get(url)?;
+
+ res = res.error_for_status()?;
+
+ let mut buffer: Vec<u8> = vec![];
+ res.copy_to(&mut buffer)?;
+
+ Ok(buffer)
+}
+
+fn save_icon(path: &str, icon: &[u8]) {
+ create_dir_all(&CONFIG.icon_cache_folder).expect("Error creating icon cache");
- // Save the currently downloaded icon
if let Ok(mut f) = File::create(path) {
- f.write_all(&icon).expect("Error writing icon file");
+ f.write_all(icon).expect("Error writing icon file");
};
-
- Ok(icon)
}
+const FALLBACK_ICON_URL: &str = "https://raw.githubusercontent.com/bitwarden/web/master/src/images/fa-globe.png";
+
fn get_fallback_icon() -> Vec<u8> {
- let fallback_icon = "https://raw.githubusercontent.com/bitwarden/web/master/src/images/fa-globe.png";
- get_icon_cached("default", fallback_icon).unwrap()
+ let path = format!("{}/default.png", CONFIG.icon_cache_folder);
+
+ if let Some(icon) = get_cached_icon(&path) {
+ return icon;
+ }
+
+ match download_icon(FALLBACK_ICON_URL) {
+ Ok(icon) => {
+ save_icon(&path, &icon);
+ icon
+ },
+ Err(_) => vec![]
+ }
}
diff --git a/src/api/web.rs b/src/api/web.rs
@@ -8,19 +8,23 @@ use rocket_contrib::Json;
use CONFIG;
pub fn routes() -> Vec<Route> {
- routes![index, files, attachments, alive]
+ if CONFIG.web_vault_enabled {
+ routes![web_index, web_files, attachments, alive]
+ } else {
+ routes![attachments, alive]
+ }
}
// TODO: Might want to use in memory cache: https://github.com/hgzimmerman/rocket-file-cache
#[get("/")]
-fn index() -> io::Result<NamedFile> {
+fn web_index() -> io::Result<NamedFile> {
NamedFile::open(
Path::new(&CONFIG.web_vault_folder)
.join("index.html"))
}
#[get("/<p..>", rank = 1)] // Only match this if the other routes don't match
-fn files(p: PathBuf) -> io::Result<NamedFile> {
+fn web_files(p: PathBuf) -> io::Result<NamedFile> {
NamedFile::open(
Path::new(&CONFIG.web_vault_folder)
.join(p))
diff --git a/src/main.rs b/src/main.rs
@@ -127,6 +127,10 @@ fn check_rsa_keys() {
}
fn check_web_vault() {
+ if !CONFIG.web_vault_enabled {
+ return;
+ }
+
let index_path = Path::new(&CONFIG.web_vault_folder).join("index.html");
if !index_path.exists() {
@@ -151,7 +155,9 @@ pub struct Config {
public_rsa_key: String,
web_vault_folder: String,
+ web_vault_enabled: bool,
+ local_icon_extractor: bool,
signups_allowed: bool,
password_iterations: i32,
}
@@ -161,20 +167,22 @@ impl Config {
dotenv::dotenv().ok();
let df = env::var("DATA_FOLDER").unwrap_or("data".into());
- let key = env::var("RSA_KEY_NAME").unwrap_or("rsa_key".into());
+ let key = env::var("RSA_KEY_FILENAME").unwrap_or(format!("{}/{}", &df, "rsa_key"));
Config {
database_url: env::var("DATABASE_URL").unwrap_or(format!("{}/{}", &df, "db.sqlite3")),
icon_cache_folder: env::var("ICON_CACHE_FOLDER").unwrap_or(format!("{}/{}", &df, "icon_cache")),
attachments_folder: env::var("ATTACHMENTS_FOLDER").unwrap_or(format!("{}/{}", &df, "attachments")),
- private_rsa_key: format!("{}/{}.der", &df, &key),
- private_rsa_key_pem: format!("{}/{}.pem", &df, &key),
- public_rsa_key: format!("{}/{}.pub.der", &df, &key),
+ private_rsa_key: format!("{}.der", &key),
+ private_rsa_key_pem: format!("{}.pem", &key),
+ public_rsa_key: format!("{}.pub.der", &key),
web_vault_folder: env::var("WEB_VAULT_FOLDER").unwrap_or("web-vault/".into()),
+ web_vault_enabled: util::parse_option_string(env::var("WEB_VAULT_ENABLED").ok()).unwrap_or(true),
- signups_allowed: util::parse_option_string(env::var("SIGNUPS_ALLOWED").ok()).unwrap_or(false),
+ local_icon_extractor: util::parse_option_string(env::var("LOCAL_ICON_EXTRACTOR").ok()).unwrap_or(false),
+ signups_allowed: util::parse_option_string(env::var("SIGNUPS_ALLOWED").ok()).unwrap_or(true),
password_iterations: util::parse_option_string(env::var("PASSWORD_ITERATIONS").ok()).unwrap_or(100_000),
}
}