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:
parent
515c84d74d
commit
538dc00234
31
.env
31
.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
|
# 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"}
|
||||||
|
|
100
src/api/icons.rs
100
src/api/icons.rs
|
@ -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![]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
|
18
src/main.rs
18
src/main.rs
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue