mirror of https://github.com/apibillme/broker
update to 11.0.0
This commit is contained in:
parent
f8ed649000
commit
d866272f02
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
55
README.md
55
README.md
|
@ -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
|
||||
|
||||
|
|
85
src/main.rs
85
src/main.rs
|
@ -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(())
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue