update to 11.0.0

This commit is contained in:
Bevan Hunt 2021-04-05 14:30:22 -07:00
parent f8ed649000
commit d866272f02
5 changed files with 166 additions and 14 deletions

View File

@ -4,9 +4,17 @@ 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).
## [11.0.0] - 2021-04-05
### Added
- Added email field on user
- Added data field on user
- Added user email address validation
- Added update user endpoint
## [10.0.0] - 2021-04-05
## Fixed
### Fixed
- Fixed infinite SSE event sending
### Changed

27
Cargo.lock generated
View File

@ -90,6 +90,12 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "ascii_utils"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a"
[[package]]
name = "async-attributes"
version = "1.1.2"
@ -459,7 +465,7 @@ dependencies = [
[[package]]
name = "broker"
version = "10.0.0"
version = "11.0.0"
dependencies = [
"anyhow",
"async-std",
@ -471,6 +477,7 @@ dependencies = [
"json",
"jsonwebtoken",
"lazy_static",
"mailchecker",
"nippy",
"rmp-serde",
"rocksdb",
@ -814,6 +821,15 @@ version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59"
[[package]]
name = "fast_chemail"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "495a39d30d624c2caabe6312bfead73e7717692b44e0b32df168c275a2e8e9e4"
dependencies = [
"ascii_utils",
]
[[package]]
name = "fastrand"
version = "1.4.0"
@ -1263,6 +1279,15 @@ dependencies = [
"value-bag",
]
[[package]]
name = "mailchecker"
version = "4.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1be9884d8adeab79160bc07b42bce71324406c4792fcf9e785476dbc796d3d55"
dependencies = [
"fast_chemail",
]
[[package]]
name = "matches"
version = "0.1.8"

View File

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

View File

@ -28,7 +28,8 @@ Broker follows an insert-only/publish/subscribe paradigm rather than a REST CRUD
* Sync latest events on SSE client connection
* Auto-provision and renews SSL cert via LetsEncrypt
* Verify endpoint for external services using Broker user system like [portal](https://crates.io/crates/portal)
* User Management API endpoints (revoke, unrevoke, list, get)
* User Management API endpoints (revoke, unrevoke, list, get, update)
* User Email Address Validation (regex and blacklist check against throwaway emails)
### How it works
@ -68,10 +69,16 @@ POST /create_user
"username": "bob",
"password": "password1",
"admin_token": "letmein",
"tenant_name": "tenant_1"
"tenant_name": "tenant_1",
"email": "bob@hotmail.com",
"data": {
"name": "Robert Wieland",
"image": "https://img.com/bucket/123/123.jpg"
}
}
```
- admin_token is required and can be set in the command args - it is for not allowing everyone to add a user - the default is `letmein`
- `admin_token` is required and can be set in the command args - it is for not allowing everyone to add a user - the default is `letmein`
- `email` and `data` is an optional field
will return `200` or `500` or `400`
@ -94,7 +101,6 @@ will return
"jwt": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTc2NzQ5MTUsImlhdCI6MTYxNzU4ODUxNSwiaXNzIjoiRGlzcGF0Y2hlciIsInN1YiI6ImZvbyJ9.OwiaZJcFUC_B0CA0ffRZVTWKRf5_vQ7vt5USNJEeKRE"
}
```
- where {...} is a JWT (string)
#### Step 3 - connect to SSE
@ -175,7 +181,6 @@ POST /list_users
"admin_token": "letmein",
}
```
- where {...} is for the event a string and data is any JSON you want
will return: `200` or `500` or `400` or `401`
@ -187,10 +192,16 @@ will return: `200` or `500` or `400` or `401`
"password": "***",
"revoked": false,
"tenant_name": "tenant_1",
"username": "bob"
"username": "bob",
"email": "bob@hotmail.com",
"data": {
"name": "Robert Wieland",
"image": "https://img.com/bucket/123/123.jpg"
}
}
]
```
- note: `email` and `data` can be `null`
#### Optional - get user
@ -204,7 +215,6 @@ POST /get_user
"username": "bob"
}
```
- where {...} is for the event a string and data is any JSON you want
will return: `200` or `500` or `400` or `401`
@ -215,9 +225,38 @@ will return: `200` or `500` or `400` or `401`
"password": "***",
"revoked": false,
"tenant_name": "tenant_1",
"username": "bob"
"username": "bob",
"email": "bob@hotmail.com",
"data": {
"name": "Robert Wieland",
"image": "https://img.com/bucket/123/123.jpg"
}
}
```
- note: `email` and `data` can be `null`
#### Optional - update user
```html
POST /update_user
```
- public endpoint
```json
{
"admin_token": "letmein",
"username": "bob",
"tenant_name": "tenant_2",
"password": "new_password",
"email": "bober@hotmail.com",
"data": {
"name": "Robert Falcon",
"image": "https://img.com/bucket/123/1234.jpg"
}
}
```
- note: `tenant_name`, `password`, `email`, `data` are optional fields
will return: `200` or `500` or `400` or `401`
### Install

View File

@ -14,6 +14,7 @@ use async_std::stream;
use std::time::Duration;
use futures::StreamExt;
use tide_acme::{AcmeConfig, TideRustlsExt};
use mailchecker::is_valid;
lazy_static! {
static ref DB : Arc<rocksdb::DB> = {
@ -47,9 +48,11 @@ pub struct EnvVarConfig {
pub struct User {
pub id: uuid::Uuid,
pub revoked: bool,
pub email: Option<String>,
pub username: String,
pub password: String,
pub tenant_name: String,
pub data: Option<serde_json::Value>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
@ -58,6 +61,18 @@ pub struct UserForm {
pub password: String,
pub tenant_name: String,
pub admin_token: String,
pub email: Option<String>,
pub data: Option<serde_json::Value>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct UpdateUserForm {
pub username: String,
pub tenant_name: Option<String>,
pub admin_token: String,
pub email: Option<String>,
pub password: Option<String>,
pub data: Option<serde_json::Value>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
@ -128,6 +143,46 @@ fn activate_user(username: String) -> Result<()> {
}
}
fn modify_user(update_user_form: UpdateUserForm) -> Result<()> {
match get_user_by_username(update_user_form.clone().username)? {
Some(mut user) => {
match update_user_form.tenant_name {
Some(tn) => {
user.tenant_name = tn;
},
None => {}
}
match update_user_form.email {
Some(email) => {
if is_valid(&email) {
user.email = Some(email);
}
},
None => {}
}
user.data = update_user_form.data;
match update_user_form.password {
Some(password) => {
let config = Argon2Config::default();
let uuid_string = Uuid::new_v4().to_string();
let salt = uuid_string.as_bytes();
let password = password.as_bytes();
let hashed = argon2::hash_encoded(password, salt, &config).unwrap();
user.password = hashed;
},
None => {}
}
puts_user(user)?;
Ok(())
},
None => { Ok(()) }
}
}
fn get_user_by_username(user_username: String) -> Result<Option<User>> {
let users = get_users()?;
Ok(users.into_iter().filter(|user| user.username == user_username).last())
@ -144,7 +199,7 @@ fn get_users() -> Result<Vec<User>> {
}
fn puts_user(user: User) -> Result<()> {
let key = format!("users_{}_{}", user.tenant_name, user.id);
let key = format!("users_{}", user.username);
let value = rmp_serde::to_vec_named(&user)?;
replace(key, value)?;
Ok(())
@ -193,6 +248,16 @@ fn user_create(user_form: UserForm) -> Result<Option<String>> {
let configure = env_var_config();
if configure.admin_token == user_form.clone().admin_token {
match user_form.clone().email {
Some(email) => {
if !is_valid(&email) {
let j = json!({"error": "email is invalid"}).to_string();
return Ok(Some(j));
}
},
None => {}
}
if !is_user_unique(user_form.clone().username)? {
let j = json!({"error": "username already taken"}).to_string();
return Ok(Some(j));
@ -209,8 +274,10 @@ fn user_create(user_form: UserForm) -> Result<Option<String>> {
password: hashed,
tenant_name: user_form.clone().tenant_name,
revoked: false,
email: user_form.clone().email,
data: user_form.clone().data,
};
puts_user(new_user).unwrap();
return Ok(None);
}
@ -474,6 +541,18 @@ async fn unrevoke_user(mut req: Request<()>) -> tide::Result {
}
}
async fn update_user(mut req: Request<()>) -> tide::Result {
let r = req.body_string().await?;
let update_user_form : UpdateUserForm = serde_json::from_str(&r)?;
let configure = env_var_config();
if configure.admin_token == update_user_form.admin_token {
modify_user(update_user_form)?;
Ok(tide::Response::builder(200).header("content-type", "application/json").build())
} else {
Ok(tide::Response::builder(401).header("content-type", "application/json").build())
}
}
#[async_std::main]
async fn main() -> tide::Result<()> {
@ -496,6 +575,7 @@ async fn main() -> tide::Result<()> {
app.at("/revoke_user").post(revoke_user);
app.at("/get_user").post(get_user);
app.at("/unrevoke_user").post(unrevoke_user);
app.at("/update_user").post(update_user);
app.at("/sse").get(tide::sse::endpoint(|req: Request<()>, sender| async move {
@ -572,4 +652,3 @@ async fn main() -> tide::Result<()> {
Ok(())
}