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:
parent
9cdb605659
commit
948554a20f
7
.env
7
.env
|
@ -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
|
||||||
|
|
|
@ -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> \
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
91
src/main.rs
91
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)]
|
#![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(),
|
||||||
|
|
28
src/util.rs
28
src/util.rs
|
@ -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
|
||||||
///
|
///
|
||||||
|
|
Loading…
Reference in New Issue