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
# 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
# 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_PORT=8000
# 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::fs::{create_dir_all, File};
@ -23,24 +22,56 @@ 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> {
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)?;
res = match res.error_for_status() {
Err(e) => return Err(e),
Ok(res) => res
};
res = res.error_for_status()?;
let mut buffer: Vec<u8> = vec![];
res.copy_to(&mut buffer)?;
@ -48,35 +79,28 @@ fn get_icon(url: &str) -> Result<Vec<u8>, reqwest::Error> {
Ok(buffer)
}
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 save_icon(path: &str, icon: &[u8]) {
create_dir_all(&CONFIG.icon_cache_folder).expect("Error creating icon cache");
// 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) {
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![]
}
}

View file

@ -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))

View file

@ -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),
}
}