update to 12.0.0

This commit is contained in:
Bevan Hunt 2021-04-08 16:40:00 -07:00
parent 559cc792e8
commit e488cc75e0
5 changed files with 45 additions and 11 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/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [12.0.0] - 2021-04-08
### Added
- Added JWT custom scopes on user
### Updated
- Updated README
## [11.2.0] - 2021-04-06
### Added

2
Cargo.lock generated
View File

@ -480,7 +480,7 @@ dependencies = [
[[package]]
name = "broker"
version = "11.2.0"
version = "12.0.0"
dependencies = [
"anyhow",
"async-std",

View File

@ -1,6 +1,6 @@
[package]
name = "broker"
version = "11.2.0"
version = "12.0.0"
authors = ["Bevan Hunt <bevan@bevanhunt.com>"]
edition = "2018"
license = "MIT"

View File

@ -18,18 +18,19 @@ Broker follows an insert-only/publish/subscribe paradigm rather than a REST CRUD
* Under 1000 lines of code
* Secure Real-time Event Stream via SSE - requires the use of [broker-client](https://www.npmjs.com/package/broker-client)
* Supports CORS
* JSON API
* Add users with admin token permission
* Multi-tenant
* Supports SSL - full end-to-end encryption
* Provides user authentication with JWTs or HTTP Basic
* Secure passwords with Argon2 encoding
* Uses Global NTP servers and doesn't rely on your local server time
* Insert event via JSON POST request
* Issues JWTs for authentication (username) and authorization (scopes) for external services
* Verify endpoint for external services using Broker user system like [portal](https://crates.io/crates/portal)
* Secure password storage with Argon2 encoding
* Uses Global NTP servers and doesn't rely on your local server time for JWT expiry timing
* Sync latest events on SSE client connection
* Auto-provision and renews SSL cert via LetsEncrypt or use your own SSL cert
* 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 Email Address Validation (regex and blacklist check against throwaway emails)
* User Management API endpoints (create, revoke, unrevoke, list, get, update)
* User Email Address Validation (regex and blacklist check against throwaway emails) using [mailchecker](https://crates.io/crates/mailchecker)
* Password Strength Checker using [zxcvbn](https://crates.io/crates/zxcvbn)
### How it works
@ -72,6 +73,7 @@ POST /create_user
"admin_token": "letmein",
"tenant_name": "tenant_1",
"email": "bob@hotmail.com",
"scopes": ["news:get", "news:post"],
"data": {
"name": "Robert Wieland",
"image": "https://img.com/bucket/123/123.jpg"
@ -102,6 +104,7 @@ will return
"jwt": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTc2NzQ5MTUsImlhdCI6MTYxNzU4ODUxNSwiaXNzIjoiRGlzcGF0Y2hlciIsInN1YiI6ImZvbyJ9.OwiaZJcFUC_B0CA0ffRZVTWKRf5_vQ7vt5USNJEeKRE"
}
```
- note: `iss` is `Broker` while `aud` is the user scopes joined with a comma like in this example `news:get,news:post`
#### Step 3 - connect to SSE
@ -195,6 +198,7 @@ will return: `200` or `500` or `400` or `401`
"tenant_name": "tenant_1",
"username": "bob",
"email": "bob@hotmail.com",
"scopes": ["news:get", "news:post"],
"data": {
"name": "Robert Wieland",
"image": "https://img.com/bucket/123/123.jpg"
@ -228,6 +232,7 @@ will return: `200` or `500` or `400` or `401`
"tenant_name": "tenant_1",
"username": "bob",
"email": "bob@hotmail.com",
"scopes": ["news:get", "news:post"],
"data": {
"name": "Robert Wieland",
"image": "https://img.com/bucket/123/123.jpg"
@ -249,6 +254,7 @@ POST /update_user
"tenant_name": "tenant_2",
"password": "new_password",
"email": "bober@hotmail.com",
"scopes": ["news:get", "news:post"],
"data": {
"name": "Robert Falcon",
"image": "https://img.com/bucket/123/1234.jpg"

View File

@ -58,6 +58,7 @@ pub struct User {
pub password: String,
pub tenant_name: String,
pub data: Option<serde_json::Value>,
pub scopes: Option<Vec<String>>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
@ -68,6 +69,7 @@ pub struct UserForm {
pub admin_token: String,
pub email: Option<String>,
pub data: Option<serde_json::Value>,
pub scopes: Option<Vec<String>>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
@ -78,6 +80,7 @@ pub struct UpdateUserForm {
pub email: Option<String>,
pub password: Option<String>,
pub data: Option<serde_json::Value>,
pub scopes: Option<Vec<String>>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
@ -103,6 +106,7 @@ pub struct Claims {
pub iat: i64,
pub iss: String,
pub sub: String,
pub aud: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
@ -169,6 +173,7 @@ fn modify_user(update_user_form: UpdateUserForm) -> Result<Option<String>> {
}
user.data = update_user_form.data;
user.scopes = update_user_form.scopes;
match update_user_form.password {
Some(password) => {
@ -332,6 +337,7 @@ fn user_create(user_form: UserForm) -> Result<Option<String>> {
revoked: false,
email: user_form.clone().email,
data: user_form.clone().data,
scopes: user_form.clone().scopes,
};
puts_user(new_user).unwrap();
@ -353,8 +359,15 @@ async fn create_jwt(login: LoginForm) -> Result<Option<String>> {
let app = env_var_config();
let iat = nippy::get_unix_ntp_time().await?;
let exp = iat + app.jwt_expiry;
let iss = "Dispatcher".to_string();
let my_claims = Claims{sub: user.clone().username, exp, iat, iss};
let iss = "Broker".to_string();
let aud: String;
match user.scopes.clone() {
Some(scopes) => {
aud = scopes.join(",");
},
None => { aud = "".to_string() }
}
let my_claims = Claims{sub: user.clone().username, exp, iat, iss, aud};
let token = encode(&Header::default(), &my_claims, &EncodingKey::from_secret(app.jwt_secret.as_ref()))?;
Ok(Some(token))
} else {
@ -432,7 +445,14 @@ async fn jwt_verify(token: String) -> Result<Option<TokenData<Claims>>> {
let iat = nippy::get_unix_ntp_time().await?;
let exp = iat + app.jwt_expiry;
let iss = "Broker".to_string();
let my_claims = Claims{sub: user.clone().username, exp, iat, iss};
let aud: String;
match user.scopes.clone() {
Some(scopes) => {
aud = scopes.join(",");
},
None => { aud = "".to_string() }
}
let my_claims = Claims{sub: user.clone().username, exp, iat, iss, aud};
let my_token = TokenData{
header: Header::default(),
claims: my_claims,