Improved configuration and documented options. Implemented option to disable web vault and to disable the use of bitwarden's official icon servers

This commit is contained in:
Daniel García 2018-06-12 21:09:42 +02:00
parent 515c84d74d
commit 538dc00234
4 changed files with 108 additions and 51 deletions

31
.env
View file

@ -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 # DATABASE_URL=data/db.sqlite3
# PRIVATE_RSA_KEY=data/private_rsa_key.der # RSA_KEY_FILENAME=data/rsa_key
# PUBLIC_RSA_KEY=data/public_rsa_key.der
# ICON_CACHE_FOLDER=data/icon_cache # ICON_CACHE_FOLDER=data/icon_cache
# ATTACHMENTS_FOLDER=data/attachments # ATTACHMENTS_FOLDER=data/attachments
# true for yes, anything else for no ## Web vault settings
SIGNUPS_ALLOWED=true # WEB_VAULT_FOLDER=web-vault/
# WEB_VAULT_ENABLED=true
# ROCKET_ENV=production ## 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 specific settings, check Rocket documentation to learn more
# ROCKET_ENV=staging
# ROCKET_ADDRESS=0.0.0.0 # Enable this to test mobile app # ROCKET_ADDRESS=0.0.0.0 # Enable this to test mobile app
# ROCKET_PORT=8000 # ROCKET_PORT=8000
# ROCKET_TLS={certs="/path/to/certs.pem",key="/path/to/key.pem"} # ROCKET_TLS={certs="/path/to/certs.pem",key="/path/to/key.pem"}

View file

@ -1,4 +1,3 @@
use std::io;
use std::io::prelude::*; use std::io::prelude::*;
use std::fs::{create_dir_all, File}; use std::fs::{create_dir_all, File};
@ -23,24 +22,56 @@ fn icon(domain: String) -> Content<Vec<u8>> {
return Content(icon_type, get_fallback_icon()); return Content(icon_type, get_fallback_icon());
} }
let url = format!("https://icons.bitwarden.com/{}/icon.png", domain); let icon = get_icon(&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())
};
Content(icon_type, icon) Content(icon_type, icon)
} }
fn get_icon(url: &str) -> Result<Vec<u8>, reqwest::Error> { fn get_icon (domain: &str) -> Vec<u8> {
let path = format!("{}/{}.png", CONFIG.icon_cache_folder, domain);
if let Some(icon) = get_cached_icon(&path) {
return icon;
}
let url = get_icon_url(&domain);
// 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_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 Some(buffer);
}
}
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)?; let mut res = reqwest::get(url)?;
res = match res.error_for_status() { res = res.error_for_status()?;
Err(e) => return Err(e),
Ok(res) => res
};
let mut buffer: Vec<u8> = vec![]; let mut buffer: Vec<u8> = vec![];
res.copy_to(&mut buffer)?; res.copy_to(&mut buffer)?;
@ -48,35 +79,28 @@ fn get_icon(url: &str) -> Result<Vec<u8>, reqwest::Error> {
Ok(buffer) Ok(buffer)
} }
fn get_icon_cached(key: &str, url: &str) -> io::Result<Vec<u8>> { fn save_icon(path: &str, icon: &[u8]) {
create_dir_all(&CONFIG.icon_cache_folder)?; create_dir_all(&CONFIG.icon_cache_folder).expect("Error creating icon cache");
let path = &format!("{}/{}.png", CONFIG.icon_cache_folder, key);
// 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);
}
/* 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, ""))
};
// Save the currently downloaded icon
if let Ok(mut f) = File::create(path) { 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> { fn get_fallback_icon() -> Vec<u8> {
let fallback_icon = "https://raw.githubusercontent.com/bitwarden/web/master/src/images/fa-globe.png"; let path = format!("{}/default.png", CONFIG.icon_cache_folder);
get_icon_cached("default", fallback_icon).unwrap()
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![]
}
} }

View file

@ -8,19 +8,23 @@ use rocket_contrib::Json;
use CONFIG; use CONFIG;
pub fn routes() -> Vec<Route> { 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 // TODO: Might want to use in memory cache: https://github.com/hgzimmerman/rocket-file-cache
#[get("/")] #[get("/")]
fn index() -> io::Result<NamedFile> { fn web_index() -> io::Result<NamedFile> {
NamedFile::open( NamedFile::open(
Path::new(&CONFIG.web_vault_folder) Path::new(&CONFIG.web_vault_folder)
.join("index.html")) .join("index.html"))
} }
#[get("/<p..>", rank = 1)] // Only match this if the other routes don't match #[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( NamedFile::open(
Path::new(&CONFIG.web_vault_folder) Path::new(&CONFIG.web_vault_folder)
.join(p)) .join(p))

View file

@ -127,6 +127,10 @@ fn check_rsa_keys() {
} }
fn check_web_vault() { fn check_web_vault() {
if !CONFIG.web_vault_enabled {
return;
}
let index_path = Path::new(&CONFIG.web_vault_folder).join("index.html"); let index_path = Path::new(&CONFIG.web_vault_folder).join("index.html");
if !index_path.exists() { if !index_path.exists() {
@ -151,7 +155,9 @@ pub struct Config {
public_rsa_key: String, public_rsa_key: String,
web_vault_folder: String, web_vault_folder: String,
web_vault_enabled: bool,
local_icon_extractor: bool,
signups_allowed: bool, signups_allowed: bool,
password_iterations: i32, password_iterations: i32,
} }
@ -161,20 +167,22 @@ impl Config {
dotenv::dotenv().ok(); dotenv::dotenv().ok();
let df = env::var("DATA_FOLDER").unwrap_or("data".into()); 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 { Config {
database_url: env::var("DATABASE_URL").unwrap_or(format!("{}/{}", &df, "db.sqlite3")), 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")), 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")), attachments_folder: env::var("ATTACHMENTS_FOLDER").unwrap_or(format!("{}/{}", &df, "attachments")),
private_rsa_key: format!("{}/{}.der", &df, &key), private_rsa_key: format!("{}.der", &key),
private_rsa_key_pem: format!("{}/{}.pem", &df, &key), private_rsa_key_pem: format!("{}.pem", &key),
public_rsa_key: format!("{}/{}.pub.der", &df, &key), public_rsa_key: format!("{}.pub.der", &key),
web_vault_folder: env::var("WEB_VAULT_FOLDER").unwrap_or("web-vault/".into()), 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), password_iterations: util::parse_option_string(env::var("PASSWORD_ITERATIONS").ok()).unwrap_or(100_000),
} }
} }