mirror of https://github.com/apibillme/broker
Add HTTP Basic authentication (#14)
* add http basic auth * bump ver * add tests for http basic
This commit is contained in:
parent
1137c75c15
commit
8ebe1bdc57
|
@ -121,9 +121,10 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "broker"
|
||||
version = "4.3.4"
|
||||
version = "4.4.0"
|
||||
dependencies = [
|
||||
"Inflector 0.11.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"base64 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bcrypt 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"broker-ntp 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bus 2.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "broker"
|
||||
version = "4.3.4"
|
||||
version = "4.4.0"
|
||||
authors = ["Bevan Hunt <bevan@bevanhunt.com>"]
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
|
@ -31,6 +31,7 @@ bus = "2.2"
|
|||
broker-ntp = "0.0.1"
|
||||
Inflector = "0.11"
|
||||
json-patch = "0.2"
|
||||
base64 = "0.12"
|
||||
|
||||
[dev-dependencies]
|
||||
reqwest = { version = "0.10", features = ["json"] }
|
||||
|
|
20
README.md
20
README.md
|
@ -14,12 +14,12 @@ Broker follows an insert-only/publish/subscribe paradigm rather than a REST CRUD
|
|||
|
||||
### Features
|
||||
|
||||
* Very performant with a low memory footprint that uses about 20MB and 1 CPU thread
|
||||
* About 500 lines of code
|
||||
* Very performant with a low memory footprint that uses about 20MB and 2 CPU threads
|
||||
* Under 1,000 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
|
||||
* Supports SSL
|
||||
* Provides user authentication with JWTs and Bcrypt(ed) passwords
|
||||
* Supports SSL - full end-to-end encryption
|
||||
* Provides user authentication with JWTs or HTTP Basic with stored Bcrypt(ed) passwords
|
||||
* Handles future events via Epoch UNIX timestamp
|
||||
* Uses Global NTP servers and doesn't rely on your local server time
|
||||
* Stateful immutable event persistence
|
||||
|
@ -73,7 +73,7 @@ will return
|
|||
```
|
||||
- where {...} is the uuid (string) of the user
|
||||
|
||||
#### Step 2 - login with the user
|
||||
#### For JWT Auth: Step 2 - login with the user
|
||||
|
||||
```html
|
||||
POST /login
|
||||
|
@ -96,7 +96,7 @@ will return
|
|||
```html
|
||||
GET /events
|
||||
```
|
||||
- authenticated endpoint (Authorization: Bearer {jwt})
|
||||
- authenticated endpoint (Authorization: Bearer {jwt}) or (Authorization: Basic {base64})
|
||||
- connect your sse-client to this endpoint using [broker-client](https://www.npmjs.com/package/broker-client)
|
||||
- note: broker-client uses fetch as eventsource doesn't support headers
|
||||
|
||||
|
@ -105,7 +105,7 @@ GET /events
|
|||
```html
|
||||
POST /insert
|
||||
```
|
||||
- authenticated endpoint (Authorization: Bearer {jwt})
|
||||
- authenticated endpoint (Authorization: Bearer {jwt}) or (Authorization: Basic {base64})
|
||||
- POST JSON to insert an event
|
||||
```json
|
||||
{"event":{...}, "collection_id":{...}, "timestamp":{...}, "data":{...}}
|
||||
|
@ -123,7 +123,7 @@ will return
|
|||
```html
|
||||
GET /collections/{collection_id}
|
||||
```
|
||||
- authenticated endpoint (Authorization: Bearer {jwt})
|
||||
- authenticated endpoint (Authorization: Bearer {jwt}) or (Authorization: Basic {base64})
|
||||
- do a GET request where {collection_id} is the uuid of the collection you want (sorted by ascending timestamp)
|
||||
|
||||
will return
|
||||
|
@ -135,7 +135,7 @@ will return
|
|||
```html
|
||||
GET /user_events
|
||||
```
|
||||
- authenticated endpoint (Authorization: Bearer {jwt})
|
||||
- authenticated endpoint (Authorization: Bearer {jwt}) or (Authorization: Basic {base64})
|
||||
- do a GET request to get the user event collections (sorted by ascending timestamp)
|
||||
|
||||
will return
|
||||
|
@ -147,7 +147,7 @@ will return
|
|||
```html
|
||||
GET /cancel/{id}
|
||||
```
|
||||
- authenticated endpoint (Authorization: Bearer {jwt})
|
||||
- authenticated endpoint (Authorization: Bearer {jwt}) or (Authorization: Basic {base64})
|
||||
- do a GET request where id is the uuid of the event to cancel a future event
|
||||
|
||||
will return
|
||||
|
|
78
src/lib.rs
78
src/lib.rs
|
@ -17,6 +17,7 @@ use inflector::Inflector;
|
|||
use json_patch::merge;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use base64::{decode as base64_decode};
|
||||
|
||||
// init database as lazy
|
||||
lazy_static! {
|
||||
|
@ -442,21 +443,72 @@ fn config() -> Config {
|
|||
Config{port: port, secret: secret, origin: origin, save_path: save_path, expiry: expiry, connection: connection, key_path: key_path, cert_path: cert_path}
|
||||
}
|
||||
|
||||
// verify the exp and key of the JWT
|
||||
// verify the exp and key of the JWT or the HTTP Basic Username/Password
|
||||
fn jwt_verify(config: Config, token: String) -> JWT {
|
||||
|
||||
let parts = token.split(" ");
|
||||
for part in parts {
|
||||
if part != "Bearer" {
|
||||
let _ = match decode::<Claims>(&part, &DecodingKey::from_secret(config.secret.as_ref()), &Validation::default()) {
|
||||
Ok(c) => {
|
||||
return JWT{check: true, claims: c.claims}
|
||||
},
|
||||
Err(_e) => {
|
||||
return JWT{check: false, claims: Claims{company: "".to_owned(), exp: 0, sub: "".to_owned()}}
|
||||
}
|
||||
};
|
||||
}
|
||||
let mut parts = token.split(" ");
|
||||
let auth_type = parts.next().unwrap();
|
||||
if auth_type == "Bearer" {
|
||||
let token = parts.next().unwrap();
|
||||
let _ = match decode::<Claims>(&token, &DecodingKey::from_secret(config.secret.as_ref()), &Validation::default()) {
|
||||
Ok(c) => {
|
||||
return JWT{check: true, claims: c.claims};
|
||||
},
|
||||
Err(_) => {
|
||||
return JWT{check: false, claims: Claims{company: "".to_owned(), exp: 0, sub: "".to_owned()}};
|
||||
}
|
||||
};
|
||||
} else if auth_type == "Basic" {
|
||||
let token = parts.next().unwrap();
|
||||
let _ = match &base64_decode(token) {
|
||||
Ok(c) => {
|
||||
let _ = match std::str::from_utf8(&c) {
|
||||
Ok(d) => {
|
||||
let mut username_password = d.split(":");
|
||||
let username = username_password.next().unwrap();
|
||||
let password = username_password.next().unwrap();
|
||||
let tree = TREE.get(&"tree".to_owned()).unwrap();
|
||||
|
||||
let records : HashMap<String, String> = tree.iter().into_iter().filter(|x| {
|
||||
let p = x.as_ref().unwrap();
|
||||
let k = std::str::from_utf8(&p.0).unwrap().to_owned();
|
||||
if k.contains(&"_u_") {
|
||||
let v = std::str::from_utf8(&p.1).unwrap().to_owned();
|
||||
let user : User = serde_json::from_str(&v).unwrap();
|
||||
if user.username == username {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}).map(|x| {
|
||||
let p = x.unwrap();
|
||||
let k = std::str::from_utf8(&p.0).unwrap().to_owned();
|
||||
let v = std::str::from_utf8(&p.1).unwrap().to_owned();
|
||||
(k, v)
|
||||
}).collect();
|
||||
|
||||
for (_k, v) in records {
|
||||
let user : User = serde_json::from_str(&v).unwrap();
|
||||
let verified = verify(password, &user.password).unwrap();
|
||||
if verified {
|
||||
return JWT{check: true, claims: Claims{company: "".to_owned(), exp: 0, sub: user.id.to_string()}};
|
||||
} else {
|
||||
return JWT{check: false, claims: Claims{company: "".to_owned(), exp: 0, sub: "".to_owned()}};
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
return JWT{check: false, claims: Claims{company: "".to_owned(), exp: 0, sub: "".to_owned()}};
|
||||
}
|
||||
};
|
||||
},
|
||||
Err(_) => {
|
||||
return JWT{check: false, claims: Claims{company: "".to_owned(), exp: 0, sub: "".to_owned()}};
|
||||
}
|
||||
};
|
||||
}
|
||||
JWT{check: false, claims: Claims{company: "".to_owned(), exp: 0, sub: "".to_owned()}}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
extern crate broker;
|
||||
use serde_json::json;
|
||||
use base64::encode;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test1() {
|
||||
|
@ -14,6 +15,9 @@ async fn test1() {
|
|||
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let basic_token = encode("rust22:rust");
|
||||
let basic = format!("Basic {}", basic_token);
|
||||
|
||||
// create user 1 - want success
|
||||
let res = client.post("http://localhost:8080/users")
|
||||
.json(&user1)
|
||||
|
@ -66,7 +70,7 @@ async fn test1() {
|
|||
.status();
|
||||
assert_eq!(res, 401);
|
||||
|
||||
// post event - want success
|
||||
// post event with JWT - want success
|
||||
let res = client.post("http://localhost:8080/insert")
|
||||
.header("Authorization", &bearer)
|
||||
.json(&event1)
|
||||
|
@ -75,7 +79,7 @@ async fn test1() {
|
|||
let event : broker::Record = serde_json::from_str(&res.text().await.unwrap()).unwrap();
|
||||
assert_eq!(event.event.published, false);
|
||||
|
||||
// post event - want success
|
||||
// post event with JWT - want success
|
||||
let res = client.post("http://localhost:8080/insert")
|
||||
.header("Authorization", &bearer)
|
||||
.json(&event2)
|
||||
|
@ -84,6 +88,15 @@ async fn test1() {
|
|||
let event2 : broker::Record = serde_json::from_str(&res.text().await.unwrap()).unwrap();
|
||||
assert_eq!(event2.event.published, false);
|
||||
|
||||
// post event with HTTP Basic - want success
|
||||
let res = client.post("http://localhost:8080/insert")
|
||||
.header("Authorization", &basic)
|
||||
.json(&event1)
|
||||
.send().await.unwrap();
|
||||
assert_eq!(res.status(), 200);
|
||||
let event : broker::Record = serde_json::from_str(&res.text().await.unwrap()).unwrap();
|
||||
assert_eq!(event.event.published, false);
|
||||
|
||||
// try getting collection without auth - want failure
|
||||
let res = client.get("http://localhost:8080/collections/123")
|
||||
.send().await.unwrap()
|
||||
|
@ -94,7 +107,7 @@ async fn test1() {
|
|||
let half_second = std::time::Duration::from_millis(500);
|
||||
std::thread::sleep(half_second);
|
||||
|
||||
// get collection - want success
|
||||
// get collection with JWT - want success
|
||||
let res = client.get("http://localhost:8080/collections/3ca76743-8d99-4d3f-b85c-633ea456f90c")
|
||||
.header("Authorization", &bearer)
|
||||
.send().await.unwrap();
|
||||
|
@ -102,6 +115,14 @@ async fn test1() {
|
|||
let events : broker::Collection = serde_json::from_str(&res.text().await.unwrap()).unwrap();
|
||||
assert_eq!(events.events[0].published, true);
|
||||
|
||||
// get collection with HTTP Basic - want success
|
||||
let res = client.get("http://localhost:8080/collections/3ca76743-8d99-4d3f-b85c-633ea456f90c")
|
||||
.header("Authorization", &basic)
|
||||
.send().await.unwrap();
|
||||
assert_eq!(res.status(), 200);
|
||||
let events : broker::Collection = serde_json::from_str(&res.text().await.unwrap()).unwrap();
|
||||
assert_eq!(events.events[0].published, true);
|
||||
|
||||
// try getting user without auth - want failure
|
||||
let res = client.get("http://localhost:8080/user_events")
|
||||
.send().await.unwrap()
|
||||
|
@ -121,7 +142,7 @@ async fn test1() {
|
|||
.status();
|
||||
assert_eq!(res, 400);
|
||||
|
||||
// cancel - want success
|
||||
// cancel with JWT - want success
|
||||
let url = format!("http://localhost:8080/cancel/{}", event2.event.id);
|
||||
let res = client.get(&url)
|
||||
.header("Authorization", &bearer)
|
||||
|
@ -130,11 +151,28 @@ async fn test1() {
|
|||
let event : broker::Record = serde_json::from_str(&res.text().await.unwrap()).unwrap();
|
||||
assert_eq!(event.event.cancelled, true);
|
||||
|
||||
// get collection - want success
|
||||
// cancel with HTTP Basic - want success
|
||||
let url = format!("http://localhost:8080/cancel/{}", event2.event.id);
|
||||
let res = client.get(&url)
|
||||
.header("Authorization", &basic)
|
||||
.send().await.unwrap();
|
||||
assert_eq!(res.status(), 200);
|
||||
let event : broker::Record = serde_json::from_str(&res.text().await.unwrap()).unwrap();
|
||||
assert_eq!(event.event.cancelled, true);
|
||||
|
||||
// get collection with JWT - want success
|
||||
let res = client.get("http://localhost:8080/collections/3ca76743-8d99-4d3f-b85c-633ea456f90d")
|
||||
.header("Authorization", &bearer)
|
||||
.send().await.unwrap();
|
||||
assert_eq!(res.status(), 200);
|
||||
let events : broker::Collection = serde_json::from_str(&res.text().await.unwrap()).unwrap();
|
||||
assert_eq!(events.events[0].published, false);
|
||||
|
||||
// get collection with HTTP Basic - want success
|
||||
let res = client.get("http://localhost:8080/collections/3ca76743-8d99-4d3f-b85c-633ea456f90d")
|
||||
.header("Authorization", &basic)
|
||||
.send().await.unwrap();
|
||||
assert_eq!(res.status(), 200);
|
||||
let events : broker::Collection = serde_json::from_str(&res.text().await.unwrap()).unwrap();
|
||||
assert_eq!(events.events[0].published, false);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue