From 948554a20fd46acc7155303985b9517607b246b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Thu, 13 Sep 2018 20:59:51 +0200 Subject: [PATCH] Added config option for websocket port, and reworked the config parsing a bit. Added SMTP_FROM config to examples and made it mandatory, it doesn't make much sense to not specify the from address. --- .env | 7 ++- README.md | 1 + src/api/identity.rs | 6 +-- src/api/notifications.rs | 4 +- src/main.rs | 93 ++++++++++++++++++++++------------------ src/util.rs | 28 +++++++++++- 6 files changed, 90 insertions(+), 49 deletions(-) diff --git a/.env b/.env index 475df852..4c9a9ec1 100644 --- a/.env +++ b/.env @@ -14,6 +14,9 @@ # WEB_VAULT_FOLDER=web-vault/ # WEB_VAULT_ENABLED=true +## Controls the WebSocket server port +# WEBSOCKET_PORT=3012 + ## Controls if new users can register # SIGNUPS_ALLOWED=true @@ -42,8 +45,10 @@ # ROCKET_PORT=8000 # ROCKET_TLS={certs="/path/to/certs.pem",key="/path/to/key.pem"} -## Mail specific settings, if SMTP_HOST is specified, SMTP_USERNAME and SMTP_PASSWORD are mandatory +## Mail specific settings, set SMTP_HOST and SMTP_FROM to enable the mail service. +## Note: if SMTP_USERNAME is specified, SMTP_PASSWORD is mandatory # SMTP_HOST=smtp.domain.tld +# SMTP_FROM=bitwarden-rs@domain.tld # SMTP_PORT=587 # SMTP_SSL=true # SMTP_USERNAME=username diff --git a/README.md b/README.md index 858f0f66..23042a35 100644 --- a/README.md +++ b/README.md @@ -300,6 +300,7 @@ You can configure bitwarden_rs to send emails via a SMTP agent: ```sh docker run -d --name bitwarden \ -e SMTP_HOST= \ + -e SMTP_FROM= \ -e SMTP_PORT=587 \ -e SMTP_SSL=true \ -e SMTP_USERNAME= \ diff --git a/src/api/identity.rs b/src/api/identity.rs index e1994c00..da27ff61 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -162,7 +162,7 @@ fn twofactor_auth( return Ok(None); } - let provider = match util::parse_option_string(data.get_opt("twoFactorProvider")) { + let provider = match util::try_parse_string(data.get_opt("twoFactorProvider")) { Some(provider) => provider, None => providers[0], // If we aren't given a two factor provider, asume the first one }; @@ -207,7 +207,7 @@ fn twofactor_auth( _ => err!("Invalid two factor provider"), } - if util::parse_option_string(data.get_opt("twoFactorRemember")).unwrap_or(0) == 1 { + if util::try_parse_string_or(data.get_opt("twoFactorRemember"), 0) == 1 { Ok(Some(device.refresh_twofactor_remember())) } else { device.delete_twofactor_remember(); @@ -274,7 +274,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for DeviceType { fn from_request(request: &'a Request<'r>) -> request::Outcome { let headers = request.headers(); let type_opt = headers.get_one("Device-Type"); - let type_num = util::parse_option_string(type_opt).unwrap_or(0); + let type_num = util::try_parse_string_or(type_opt, 0); Outcome::Success(DeviceType(type_num)) } diff --git a/src/api/notifications.rs b/src/api/notifications.rs index 6e06a5a9..0a0406e9 100644 --- a/src/api/notifications.rs +++ b/src/api/notifications.rs @@ -5,6 +5,8 @@ use api::JsonResult; use auth::Headers; use db::DbConn; +use CONFIG; + pub fn routes() -> Vec { routes![negotiate, websockets_err] } @@ -356,7 +358,7 @@ pub fn start_notification_server() -> WebSocketUsers { thread::spawn(move || { WebSocket::new(factory) .unwrap() - .listen("0.0.0.0:3012") + .listen(format!("0.0.0.0:{}", CONFIG.websocket_port)) .unwrap(); }); diff --git a/src/main.rs b/src/main.rs index e3933ba9..06335243 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -#![feature(plugin, custom_derive, vec_remove_item)] +#![feature(plugin, custom_derive, vec_remove_item, try_trait)] #![plugin(rocket_codegen)] #![allow(proc_macro_derive_resolution_fallback)] // TODO: Remove this when diesel update fixes warnings extern crate rocket; @@ -36,7 +36,7 @@ extern crate native_tls; extern crate fast_chemail; extern crate byteorder; -use std::{env, path::Path, process::{exit, Command}}; +use std::{path::Path, process::{exit, Command}}; use rocket::Rocket; #[macro_use] @@ -78,7 +78,7 @@ mod migrations { fn main() { check_db(); check_rsa_keys(); - check_web_vault(); + check_web_vault(); migrations::run_migrations(); init_rocket().launch(); @@ -176,27 +176,32 @@ pub struct MailConfig { impl MailConfig { fn load() -> Option { - let smtp_host = env::var("SMTP_HOST").ok(); - + use util::{get_env, get_env_or}; + // When SMTP_HOST is absent, we assume the user does not want to enable it. - if smtp_host.is_none() { - return None - } + let smtp_host = match get_env("SMTP_HOST") { + Some(host) => host, + None => return None, + }; + + let smtp_from = get_env("SMTP_FROM").unwrap_or_else(|| { + println!("Please specify SMTP_FROM to enable SMTP support."); + exit(1); + }); - let smtp_ssl = util::parse_option_string(env::var("SMTP_SSL").ok()).unwrap_or(true); - let smtp_port = util::parse_option_string(env::var("SMTP_PORT").ok()) - .unwrap_or_else(|| { - if smtp_ssl { - 587u16 - } else { - 25u16 - } - }); - - let smtp_username = env::var("SMTP_USERNAME").ok(); - let smtp_password = env::var("SMTP_PASSWORD").ok().or_else(|| { + let smtp_ssl = get_env_or("SMTP_SSL", true); + let smtp_port = get_env("SMTP_PORT").unwrap_or_else(|| + if smtp_ssl { + 587u16 + } else { + 25u16 + } + ); + + let smtp_username = get_env("SMTP_USERNAME"); + let smtp_password = get_env("SMTP_PASSWORD").or_else(|| { if smtp_username.as_ref().is_some() { - println!("Please specify SMTP_PASSWORD to enable SMTP support."); + println!("SMTP_PASSWORD is mandatory when specifying SMTP_USERNAME."); exit(1); } else { None @@ -204,13 +209,12 @@ impl MailConfig { }); Some(MailConfig { - smtp_host: smtp_host.unwrap(), - smtp_port: smtp_port, - smtp_ssl: smtp_ssl, - smtp_from: util::parse_option_string(env::var("SMTP_FROM").ok()) - .unwrap_or("bitwarden-rs@localhost".to_string()), - smtp_username: smtp_username, - smtp_password: smtp_password, + smtp_host, + smtp_port, + smtp_ssl, + smtp_from, + smtp_username, + smtp_password, }) } } @@ -228,6 +232,8 @@ pub struct Config { web_vault_folder: String, web_vault_enabled: bool, + websocket_port: i32, + local_icon_extractor: bool, signups_allowed: bool, invitations_allowed: bool, @@ -242,32 +248,35 @@ pub struct Config { impl Config { fn load() -> Self { + use util::{get_env, get_env_or}; dotenv::dotenv().ok(); - let df = env::var("DATA_FOLDER").unwrap_or("data".into()); - let key = env::var("RSA_KEY_FILENAME").unwrap_or(format!("{}/{}", &df, "rsa_key")); + let df = get_env_or("DATA_FOLDER", "data".to_string()); + let key = get_env_or("RSA_KEY_FILENAME", format!("{}/{}", &df, "rsa_key")); - let domain = env::var("DOMAIN"); + let domain = get_env("DOMAIN"); 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")), + database_url: get_env_or("DATABASE_URL", format!("{}/{}", &df, "db.sqlite3")), + icon_cache_folder: get_env_or("ICON_CACHE_FOLDER", format!("{}/{}", &df, "icon_cache")), + attachments_folder: get_env_or("ATTACHMENTS_FOLDER", format!("{}/{}", &df, "attachments")), 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), + web_vault_folder: get_env_or("WEB_VAULT_FOLDER", "web-vault/".into()), + web_vault_enabled: get_env_or("WEB_VAULT_ENABLED", true), + + websocket_port: get_env_or("WEBSOCKET_PORT", 3012), - 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), - invitations_allowed: util::parse_option_string(env::var("INVITATIONS_ALLOWED").ok()).unwrap_or(true), - password_iterations: util::parse_option_string(env::var("PASSWORD_ITERATIONS").ok()).unwrap_or(100_000), - show_password_hint: util::parse_option_string(env::var("SHOW_PASSWORD_HINT").ok()).unwrap_or(true), + local_icon_extractor: get_env_or("LOCAL_ICON_EXTRACTOR", false), + signups_allowed: get_env_or("SIGNUPS_ALLOWED", true), + invitations_allowed: get_env_or("INVITATIONS_ALLOWED", true), + password_iterations: get_env_or("PASSWORD_ITERATIONS", 100_000), + show_password_hint: get_env_or("SHOW_PASSWORD_HINT", true), - domain_set: domain.is_ok(), + domain_set: domain.is_some(), domain: domain.unwrap_or("http://localhost".into()), mail: MailConfig::load(), diff --git a/src/util.rs b/src/util.rs index c5bade68..6f8d6edf 100644 --- a/src/util.rs +++ b/src/util.rs @@ -97,6 +97,7 @@ pub fn get_display_size(size: i32) -> String { /// use std::str::FromStr; +use std::ops::Try; pub fn upcase_first(s: &str) -> String { let mut c = s.chars(); @@ -106,14 +107,37 @@ pub fn upcase_first(s: &str) -> String { } } -pub fn parse_option_string(string: Option) -> Option where S: AsRef, T: FromStr { - if let Some(Ok(value)) = string.map(|s| s.as_ref().parse::()) { +pub fn try_parse_string(string: impl Try) -> Option where S: AsRef, T: FromStr { + if let Ok(Ok(value)) = string.into_result().map(|s| s.as_ref().parse::()) { Some(value) } else { None } } +pub fn try_parse_string_or(string: impl Try, default: T) -> T where S: AsRef, T: FromStr { + if let Ok(Ok(value)) = string.into_result().map(|s| s.as_ref().parse::()) { + value + } else { + default + } +} + + +/// +/// Env methods +/// + +use std::env; + +pub fn get_env(key: &str) -> Option where V: FromStr { + try_parse_string(env::var(key)) +} + +pub fn get_env_or(key: &str, default: V) -> V where V: FromStr { + try_parse_string_or(env::var(key), default) +} + /// /// Date util methods ///