release 11.2.0

This commit is contained in:
Bevan Hunt 2021-04-06 20:21:05 -07:00
parent 597b05b6f7
commit 559cc792e8
5 changed files with 227 additions and 13 deletions

View File

@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [11.2.0] - 2021-04-06
### Added
- Added zxcvbn password strength checker
### Fixed
- Fixed JWT issued not being in JSON format
## [11.1.0] - 2021-04-06 ## [11.1.0] - 2021-04-06
### Added ### Added

148
Cargo.lock generated
View File

@ -396,6 +396,21 @@ dependencies = [
"which", "which",
] ]
[[package]]
name = "bit-set"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de"
dependencies = [
"bit-vec",
]
[[package]]
name = "bit-vec"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.2.1" version = "1.2.1"
@ -465,7 +480,7 @@ dependencies = [
[[package]] [[package]]
name = "broker" name = "broker"
version = "11.1.0" version = "11.2.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-std", "async-std",
@ -489,6 +504,7 @@ dependencies = [
"tide-acme", "tide-acme",
"tide-rustls", "tide-rustls",
"uuid", "uuid",
"zxcvbn",
] ]
[[package]] [[package]]
@ -555,11 +571,13 @@ version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [ dependencies = [
"js-sys",
"libc", "libc",
"num-integer", "num-integer",
"num-traits", "num-traits",
"serde", "serde",
"time 0.1.44", "time 0.1.44",
"wasm-bindgen",
"winapi", "winapi",
] ]
@ -592,7 +610,7 @@ dependencies = [
"ansi_term", "ansi_term",
"atty", "atty",
"bitflags", "bitflags",
"strsim", "strsim 0.8.0",
"textwrap", "textwrap",
"unicode-width", "unicode-width",
"vec_map", "vec_map",
@ -734,6 +752,41 @@ version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9"
[[package]]
name = "darling"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim 0.9.3",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]] [[package]]
name = "dashmap" name = "dashmap"
version = "4.0.2" version = "4.0.2"
@ -775,6 +828,31 @@ dependencies = [
"rusticata-macros", "rusticata-macros",
] ]
[[package]]
name = "derive_builder"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0"
dependencies = [
"darling",
"derive_builder_core",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "derive_builder_core"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.9.0" version = "0.9.0"
@ -802,6 +880,12 @@ dependencies = [
"tide", "tide",
] ]
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]] [[package]]
name = "env_logger" name = "env_logger"
version = "0.7.1" version = "0.7.1"
@ -821,6 +905,16 @@ version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59"
[[package]]
name = "fancy-regex"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36996e5f56f32ca51a937f325094fa450b32df871af1a89be331b7145b931bfc"
dependencies = [
"bit-set",
"regex",
]
[[package]] [[package]]
name = "fast_chemail" name = "fast_chemail"
version = "0.9.6" version = "0.9.6"
@ -855,6 +949,12 @@ dependencies = [
"web-sys", "web-sys",
] ]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]] [[package]]
name = "form_urlencoded" name = "form_urlencoded"
version = "1.0.1" version = "1.0.1"
@ -1134,9 +1234,15 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
dependencies = [ dependencies = [
"quick-error", "quick-error 1.2.3",
] ]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]] [[package]]
name = "idna" name = "idna"
version = "0.2.2" version = "0.2.2"
@ -1163,6 +1269,15 @@ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
] ]
[[package]]
name = "itertools"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "0.4.7" version = "0.4.7"
@ -1543,6 +1658,12 @@ version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quick-error"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ac73b1112776fc109b2e61909bc46c7e1bf0d7f690ffb1676553acce16d5cda"
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.9" version = "1.0.9"
@ -2056,6 +2177,12 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "strsim"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
[[package]] [[package]]
name = "subtle" name = "subtle"
version = "2.4.0" version = "2.4.0"
@ -2535,3 +2662,18 @@ checksum = "0de7bff972b4f2a06c85f6d8454b09df153af7e3a4ec2aac81db1b105b684ddb"
dependencies = [ dependencies = [
"chrono", "chrono",
] ]
[[package]]
name = "zxcvbn"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5f9db3a05b2ee81dcda4602487314ab654eca316f517be2e2e64175658f0dd0"
dependencies = [
"chrono",
"derive_builder",
"fancy-regex",
"itertools",
"lazy_static",
"quick-error 2.0.0",
"regex",
]

View File

@ -1,6 +1,6 @@
[package] [package]
name = "broker" name = "broker"
version = "11.1.0" version = "11.2.0"
authors = ["Bevan Hunt <bevan@bevanhunt.com>"] authors = ["Bevan Hunt <bevan@bevanhunt.com>"]
edition = "2018" edition = "2018"
license = "MIT" license = "MIT"
@ -33,3 +33,4 @@ futures = "0.3"
tide-acme = "0.1.0" tide-acme = "0.1.0"
base64 = "0.13" base64 = "0.13"
mailchecker = "4" mailchecker = "4"
zxcvbn = "2"

View File

@ -30,6 +30,7 @@ Broker follows an insert-only/publish/subscribe paradigm rather than a REST CRUD
* Verify endpoint for external services using Broker user system like [portal](https://crates.io/crates/portal) * Verify endpoint for external services using Broker user system like [portal](https://crates.io/crates/portal)
* User Management API endpoints (revoke, unrevoke, list, get, update) * User Management API endpoints (revoke, unrevoke, list, get, update)
* User Email Address Validation (regex and blacklist check against throwaway emails) * User Email Address Validation (regex and blacklist check against throwaway emails)
* Password Strength Checker using [zxcvbn](https://crates.io/crates/zxcvbn)
### How it works ### How it works
@ -283,7 +284,8 @@ will return: `200`
- the `db` flag is the path where the embedded database will be saved - default `db` - the `db` flag is the path where the embedded database will be saved - default `db`
- the `domain` flag is the domain name (e.g. api.broker.com) of the domain you want to register with LetsEncrypt - must be fully resolvable - the `domain` flag is the domain name (e.g. api.broker.com) of the domain you want to register with LetsEncrypt - must be fully resolvable
- the `admin_token` flag is the password for the admin to add users - default `letmein` - the `admin_token` flag is the password for the admin to add users - default `letmein`
- production example: `./broker --secure="true" --admin_token"23ce4234@123$" --jwt_secret="xTJEX234$##$" --domain="api.broker.com"` - the `password_checker` flag enables zxcvbn password checking - default `false`
- production example: `./broker --secure="true" --admin_token"23ce4234@123$" --jwt_secret="xTJEX234$##$" --domain="api.broker.com" --password_checker="true"`
### Service ### Service

View File

@ -15,6 +15,7 @@ use std::time::Duration;
use futures::StreamExt; use futures::StreamExt;
use tide_acme::{AcmeConfig, TideRustlsExt}; use tide_acme::{AcmeConfig, TideRustlsExt};
use mailchecker::is_valid; use mailchecker::is_valid;
use zxcvbn::zxcvbn;
lazy_static! { lazy_static! {
static ref DB : Arc<rocksdb::DB> = { static ref DB : Arc<rocksdb::DB> = {
@ -45,6 +46,7 @@ pub struct EnvVarConfig {
pub auto_cert: bool, pub auto_cert: bool,
pub key_path: String, pub key_path: String,
pub cert_path: String, pub cert_path: String,
pub password_checker: bool,
} }
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
@ -146,7 +148,7 @@ fn activate_user(username: String) -> Result<()> {
} }
} }
fn modify_user(update_user_form: UpdateUserForm) -> Result<()> { fn modify_user(update_user_form: UpdateUserForm) -> Result<Option<String>> {
match get_user_by_username(update_user_form.clone().username)? { match get_user_by_username(update_user_form.clone().username)? {
Some(mut user) => { Some(mut user) => {
@ -170,6 +172,33 @@ fn modify_user(update_user_form: UpdateUserForm) -> Result<()> {
match update_user_form.password { match update_user_form.password {
Some(password) => { Some(password) => {
let configure = env_var_config();
if configure.password_checker {
let estimate = zxcvbn(&password, &[&user.username]).unwrap();
if estimate.score() < 3 {
let err: String;
match estimate.feedback() {
Some(feedback) => {
match feedback.warning() {
Some(warning) => {
err = format!("password is too weak because {}", warning);
},
None => {
err = format!("password is too weak");
}
}
},
None => {
err = format!("password is too weak");
}
}
let j = json!({"error": err}).to_string();
return Ok(Some(j));
}
}
let config = Argon2Config::default(); let config = Argon2Config::default();
let uuid_string = Uuid::new_v4().to_string(); let uuid_string = Uuid::new_v4().to_string();
let salt = uuid_string.as_bytes(); let salt = uuid_string.as_bytes();
@ -180,9 +209,9 @@ fn modify_user(update_user_form: UpdateUserForm) -> Result<()> {
None => {} None => {}
} }
puts_user(user)?; puts_user(user)?;
Ok(()) Ok(None)
}, },
None => { Ok(()) } None => { Ok(None) }
} }
} }
@ -265,6 +294,30 @@ fn user_create(user_form: UserForm) -> Result<Option<String>> {
let j = json!({"error": "username already taken"}).to_string(); let j = json!({"error": "username already taken"}).to_string();
return Ok(Some(j)); return Ok(Some(j));
} else { } else {
if configure.password_checker {
let estimate = zxcvbn(&user_form.password, &[&user_form.username]).unwrap();
if estimate.score() < 3 {
let err: String;
match estimate.feedback() {
Some(feedback) => {
match feedback.warning() {
Some(warning) => {
err = format!("password is too weak because {}", warning);
},
None => {
err = format!("password is too weak");
}
}
},
None => {
err = format!("password is too weak");
}
}
let j = json!({"error": err}).to_string();
return Ok(Some(j));
}
}
let uuid = Uuid::new_v4(); let uuid = Uuid::new_v4();
let config = Argon2Config::default(); let config = Argon2Config::default();
let uuid_string = Uuid::new_v4().to_string(); let uuid_string = Uuid::new_v4().to_string();
@ -329,6 +382,7 @@ fn env_var_config() -> EnvVarConfig {
let mut admin_token = "letmein".to_string(); let mut admin_token = "letmein".to_string();
let mut key_path = "certs/private_key.pem".to_string(); let mut key_path = "certs/private_key.pem".to_string();
let mut cert_path = "certs/chain.pem".to_string(); let mut cert_path = "certs/chain.pem".to_string();
let mut password_checker = false;
let _ : Vec<String> = go_flag::parse(|flags| { let _ : Vec<String> = go_flag::parse(|flags| {
flags.add_flag("port", &mut port); flags.add_flag("port", &mut port);
flags.add_flag("origin", &mut origin); flags.add_flag("origin", &mut origin);
@ -342,9 +396,10 @@ fn env_var_config() -> EnvVarConfig {
flags.add_flag("auto_cert", &mut auto_cert); flags.add_flag("auto_cert", &mut auto_cert);
flags.add_flag("key_path", &mut key_path); flags.add_flag("key_path", &mut key_path);
flags.add_flag("cert_path", &mut cert_path); flags.add_flag("cert_path", &mut cert_path);
flags.add_flag("password_checker", &mut password_checker);
}); });
EnvVarConfig{port, origin, jwt_expiry, jwt_secret, secure, domain, certs, db, admin_token, auto_cert, key_path, cert_path} EnvVarConfig{port, origin, jwt_expiry, jwt_secret, secure, domain, certs, db, admin_token, auto_cert, key_path, cert_path, password_checker}
} }
async fn jwt_verify(token: String) -> Result<Option<TokenData<Claims>>> { async fn jwt_verify(token: String) -> Result<Option<TokenData<Claims>>> {
@ -420,7 +475,6 @@ async fn create_user(mut req: Request<()>) -> tide::Result {
let user_form : UserForm = serde_json::from_str(&r)?; let user_form : UserForm = serde_json::from_str(&r)?;
match user_create(user_form)? { match user_create(user_form)? {
Some(err) => { Some(err) => {
let err = format!("error: {}", err);
Ok(tide::Response::builder(400).body(err).header("content-type", "application/json").build()) Ok(tide::Response::builder(400).body(err).header("content-type", "application/json").build())
}, },
None => { None => {
@ -434,7 +488,7 @@ async fn login_user(mut req: Request<()>) -> tide::Result {
let login_form : LoginForm = serde_json::from_str(&r)?; let login_form : LoginForm = serde_json::from_str(&r)?;
match create_jwt(login_form).await? { match create_jwt(login_form).await? {
Some(jwt) => { Some(jwt) => {
let msg = format!("jwt: {}", jwt); let msg = json!({"jwt": jwt}).to_string();
Ok(tide::Response::builder(200).body(msg).header("content-type", "application/json").build()) Ok(tide::Response::builder(200).body(msg).header("content-type", "application/json").build())
}, },
None => { None => {
@ -555,8 +609,15 @@ async fn update_user(mut req: Request<()>) -> tide::Result {
let update_user_form : UpdateUserForm = serde_json::from_str(&r)?; let update_user_form : UpdateUserForm = serde_json::from_str(&r)?;
let configure = env_var_config(); let configure = env_var_config();
if configure.admin_token == update_user_form.admin_token { if configure.admin_token == update_user_form.admin_token {
modify_user(update_user_form)?; match modify_user(update_user_form)? {
Ok(tide::Response::builder(200).header("content-type", "application/json").build()) Some(err) => {
Ok(tide::Response::builder(400).body(err).header("content-type", "application/json").build())
},
None => {
Ok(tide::Response::builder(200).header("content-type", "application/json").build())
}
}
} else { } else {
Ok(tide::Response::builder(401).header("content-type", "application/json").build()) Ok(tide::Response::builder(401).header("content-type", "application/json").build())
} }