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.
This commit is contained in:
Daniel García 2018-09-13 20:59:51 +02:00
parent 9cdb605659
commit 948554a20f
No known key found for this signature in database
GPG key ID: FC8A7D14C3CD543A
6 changed files with 89 additions and 48 deletions

7
.env
View file

@ -14,6 +14,9 @@
# WEB_VAULT_FOLDER=web-vault/ # WEB_VAULT_FOLDER=web-vault/
# WEB_VAULT_ENABLED=true # WEB_VAULT_ENABLED=true
## Controls the WebSocket server port
# WEBSOCKET_PORT=3012
## Controls if new users can register ## Controls if new users can register
# SIGNUPS_ALLOWED=true # SIGNUPS_ALLOWED=true
@ -42,8 +45,10 @@
# 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"}
## 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_HOST=smtp.domain.tld
# SMTP_FROM=bitwarden-rs@domain.tld
# SMTP_PORT=587 # SMTP_PORT=587
# SMTP_SSL=true # SMTP_SSL=true
# SMTP_USERNAME=username # SMTP_USERNAME=username

View file

@ -300,6 +300,7 @@ You can configure bitwarden_rs to send emails via a SMTP agent:
```sh ```sh
docker run -d --name bitwarden \ docker run -d --name bitwarden \
-e SMTP_HOST=<smtp.domain.tld> \ -e SMTP_HOST=<smtp.domain.tld> \
-e SMTP_FROM=<bitwarden@domain.tld> \
-e SMTP_PORT=587 \ -e SMTP_PORT=587 \
-e SMTP_SSL=true \ -e SMTP_SSL=true \
-e SMTP_USERNAME=<username> \ -e SMTP_USERNAME=<username> \

View file

@ -162,7 +162,7 @@ fn twofactor_auth(
return Ok(None); 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, Some(provider) => provider,
None => providers[0], // If we aren't given a two factor provider, asume the first one 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"), _ => 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())) Ok(Some(device.refresh_twofactor_remember()))
} else { } else {
device.delete_twofactor_remember(); 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<Self, Self::Error> { fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
let headers = request.headers(); let headers = request.headers();
let type_opt = headers.get_one("Device-Type"); 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)) Outcome::Success(DeviceType(type_num))
} }

View file

@ -5,6 +5,8 @@ use api::JsonResult;
use auth::Headers; use auth::Headers;
use db::DbConn; use db::DbConn;
use CONFIG;
pub fn routes() -> Vec<Route> { pub fn routes() -> Vec<Route> {
routes![negotiate, websockets_err] routes![negotiate, websockets_err]
} }
@ -356,7 +358,7 @@ pub fn start_notification_server() -> WebSocketUsers {
thread::spawn(move || { thread::spawn(move || {
WebSocket::new(factory) WebSocket::new(factory)
.unwrap() .unwrap()
.listen("0.0.0.0:3012") .listen(format!("0.0.0.0:{}", CONFIG.websocket_port))
.unwrap(); .unwrap();
}); });

View file

@ -1,4 +1,4 @@
#![feature(plugin, custom_derive, vec_remove_item)] #![feature(plugin, custom_derive, vec_remove_item, try_trait)]
#![plugin(rocket_codegen)] #![plugin(rocket_codegen)]
#![allow(proc_macro_derive_resolution_fallback)] // TODO: Remove this when diesel update fixes warnings #![allow(proc_macro_derive_resolution_fallback)] // TODO: Remove this when diesel update fixes warnings
extern crate rocket; extern crate rocket;
@ -36,7 +36,7 @@ extern crate native_tls;
extern crate fast_chemail; extern crate fast_chemail;
extern crate byteorder; extern crate byteorder;
use std::{env, path::Path, process::{exit, Command}}; use std::{path::Path, process::{exit, Command}};
use rocket::Rocket; use rocket::Rocket;
#[macro_use] #[macro_use]
@ -78,7 +78,7 @@ mod migrations {
fn main() { fn main() {
check_db(); check_db();
check_rsa_keys(); check_rsa_keys();
check_web_vault(); check_web_vault();
migrations::run_migrations(); migrations::run_migrations();
init_rocket().launch(); init_rocket().launch();
@ -176,27 +176,32 @@ pub struct MailConfig {
impl MailConfig { impl MailConfig {
fn load() -> Option<Self> { fn load() -> Option<Self> {
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. // When SMTP_HOST is absent, we assume the user does not want to enable it.
if smtp_host.is_none() { let smtp_host = match get_env("SMTP_HOST") {
return None Some(host) => host,
} None => return None,
};
let smtp_ssl = util::parse_option_string(env::var("SMTP_SSL").ok()).unwrap_or(true); let smtp_from = get_env("SMTP_FROM").unwrap_or_else(|| {
let smtp_port = util::parse_option_string(env::var("SMTP_PORT").ok()) println!("Please specify SMTP_FROM to enable SMTP support.");
.unwrap_or_else(|| { exit(1);
if smtp_ssl { });
587u16
} else {
25u16
}
});
let smtp_username = env::var("SMTP_USERNAME").ok(); let smtp_ssl = get_env_or("SMTP_SSL", true);
let smtp_password = env::var("SMTP_PASSWORD").ok().or_else(|| { 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() { 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); exit(1);
} else { } else {
None None
@ -204,13 +209,12 @@ impl MailConfig {
}); });
Some(MailConfig { Some(MailConfig {
smtp_host: smtp_host.unwrap(), smtp_host,
smtp_port: smtp_port, smtp_port,
smtp_ssl: smtp_ssl, smtp_ssl,
smtp_from: util::parse_option_string(env::var("SMTP_FROM").ok()) smtp_from,
.unwrap_or("bitwarden-rs@localhost".to_string()), smtp_username,
smtp_username: smtp_username, smtp_password,
smtp_password: smtp_password,
}) })
} }
} }
@ -228,6 +232,8 @@ pub struct Config {
web_vault_folder: String, web_vault_folder: String,
web_vault_enabled: bool, web_vault_enabled: bool,
websocket_port: i32,
local_icon_extractor: bool, local_icon_extractor: bool,
signups_allowed: bool, signups_allowed: bool,
invitations_allowed: bool, invitations_allowed: bool,
@ -242,32 +248,35 @@ pub struct Config {
impl Config { impl Config {
fn load() -> Self { fn load() -> Self {
use util::{get_env, get_env_or};
dotenv::dotenv().ok(); dotenv::dotenv().ok();
let df = env::var("DATA_FOLDER").unwrap_or("data".into()); let df = get_env_or("DATA_FOLDER", "data".to_string());
let key = env::var("RSA_KEY_FILENAME").unwrap_or(format!("{}/{}", &df, "rsa_key")); let key = get_env_or("RSA_KEY_FILENAME", format!("{}/{}", &df, "rsa_key"));
let domain = env::var("DOMAIN"); let domain = get_env("DOMAIN");
Config { Config {
database_url: env::var("DATABASE_URL").unwrap_or(format!("{}/{}", &df, "db.sqlite3")), database_url: get_env_or("DATABASE_URL", format!("{}/{}", &df, "db.sqlite3")),
icon_cache_folder: env::var("ICON_CACHE_FOLDER").unwrap_or(format!("{}/{}", &df, "icon_cache")), icon_cache_folder: get_env_or("ICON_CACHE_FOLDER", format!("{}/{}", &df, "icon_cache")),
attachments_folder: env::var("ATTACHMENTS_FOLDER").unwrap_or(format!("{}/{}", &df, "attachments")), attachments_folder: get_env_or("ATTACHMENTS_FOLDER", format!("{}/{}", &df, "attachments")),
private_rsa_key: format!("{}.der", &key), private_rsa_key: format!("{}.der", &key),
private_rsa_key_pem: format!("{}.pem", &key), private_rsa_key_pem: format!("{}.pem", &key),
public_rsa_key: format!("{}.pub.der", &key), public_rsa_key: format!("{}.pub.der", &key),
web_vault_folder: env::var("WEB_VAULT_FOLDER").unwrap_or("web-vault/".into()), web_vault_folder: get_env_or("WEB_VAULT_FOLDER", "web-vault/".into()),
web_vault_enabled: util::parse_option_string(env::var("WEB_VAULT_ENABLED").ok()).unwrap_or(true), web_vault_enabled: get_env_or("WEB_VAULT_ENABLED", true),
local_icon_extractor: util::parse_option_string(env::var("LOCAL_ICON_EXTRACTOR").ok()).unwrap_or(false), websocket_port: get_env_or("WEBSOCKET_PORT", 3012),
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),
domain_set: domain.is_ok(), 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_some(),
domain: domain.unwrap_or("http://localhost".into()), domain: domain.unwrap_or("http://localhost".into()),
mail: MailConfig::load(), mail: MailConfig::load(),

View file

@ -97,6 +97,7 @@ pub fn get_display_size(size: i32) -> String {
/// ///
use std::str::FromStr; use std::str::FromStr;
use std::ops::Try;
pub fn upcase_first(s: &str) -> String { pub fn upcase_first(s: &str) -> String {
let mut c = s.chars(); let mut c = s.chars();
@ -106,14 +107,37 @@ pub fn upcase_first(s: &str) -> String {
} }
} }
pub fn parse_option_string<S, T>(string: Option<S>) -> Option<T> where S: AsRef<str>, T: FromStr { pub fn try_parse_string<S, T, U>(string: impl Try<Ok = S, Error=U>) -> Option<T> where S: AsRef<str>, T: FromStr {
if let Some(Ok(value)) = string.map(|s| s.as_ref().parse::<T>()) { if let Ok(Ok(value)) = string.into_result().map(|s| s.as_ref().parse::<T>()) {
Some(value) Some(value)
} else { } else {
None None
} }
} }
pub fn try_parse_string_or<S, T, U>(string: impl Try<Ok = S, Error=U>, default: T) -> T where S: AsRef<str>, T: FromStr {
if let Ok(Ok(value)) = string.into_result().map(|s| s.as_ref().parse::<T>()) {
value
} else {
default
}
}
///
/// Env methods
///
use std::env;
pub fn get_env<V>(key: &str) -> Option<V> where V: FromStr {
try_parse_string(env::var(key))
}
pub fn get_env_or<V>(key: &str, default: V) -> V where V: FromStr {
try_parse_string_or(env::var(key), default)
}
/// ///
/// Date util methods /// Date util methods
/// ///