Add basic JWT authentication for app API

This commit is contained in:
Philip (a-0) 2024-01-05 20:43:47 +01:00
parent 7ad2ed8ff1
commit 3825263fa3
14 changed files with 289 additions and 40 deletions

89
Cargo.lock generated
View file

@ -311,9 +311,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.82" version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [ dependencies = [
"jobserver", "jobserver",
"libc", "libc",
@ -986,8 +986,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys",
"libc", "libc",
"wasi", "wasi",
"wasm-bindgen",
] ]
[[package]] [[package]]
@ -1414,6 +1416,21 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "jsonwebtoken"
version = "9.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7ea04a7c5c055c175f189b6dc6ba036fd62306b58c66c9f6389036c503a3f4"
dependencies = [
"base64",
"js-sys",
"pem",
"ring 0.17.7",
"serde",
"serde_json",
"simple_asn1",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.4.0" version = "1.4.0"
@ -1422,9 +1439,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.147" version = "0.2.151"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
[[package]] [[package]]
name = "linereader" name = "linereader"
@ -1890,6 +1907,16 @@ version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]]
name = "pem"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310"
dependencies = [
"base64",
"serde",
]
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.3.0" version = "2.3.0"
@ -2225,12 +2252,26 @@ dependencies = [
"cc", "cc",
"libc", "libc",
"once_cell", "once_cell",
"spin", "spin 0.5.2",
"untrusted", "untrusted 0.7.1",
"web-sys", "web-sys",
"winapi", "winapi",
] ]
[[package]]
name = "ring"
version = "0.17.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74"
dependencies = [
"cc",
"getrandom",
"libc",
"spin 0.9.8",
"untrusted 0.9.0",
"windows-sys",
]
[[package]] [[package]]
name = "rmp" name = "rmp"
version = "0.8.12" version = "0.8.12"
@ -2305,7 +2346,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99"
dependencies = [ dependencies = [
"log", "log",
"ring", "ring 0.16.20",
"sct", "sct",
"webpki", "webpki",
] ]
@ -2349,8 +2390,8 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
dependencies = [ dependencies = [
"ring", "ring 0.16.20",
"untrusted", "untrusted 0.7.1",
] ]
[[package]] [[package]]
@ -2533,6 +2574,18 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "simple_asn1"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085"
dependencies = [
"num-bigint",
"num-traits",
"thiserror",
"time",
]
[[package]] [[package]]
name = "siphasher" name = "siphasher"
version = "0.3.11" version = "0.3.11"
@ -2601,6 +2654,12 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]] [[package]]
name = "sqlite" name = "sqlite"
version = "0.31.1" version = "0.31.1"
@ -3011,9 +3070,11 @@ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
"axum", "axum",
"chrono",
"cozo", "cozo",
"i2p", "i2p",
"itertools 0.12.0", "itertools 0.12.0",
"jsonwebtoken",
"reqwest", "reqwest",
"serde", "serde",
"serde_json", "serde_json",
@ -3076,6 +3137,12 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]] [[package]]
name = "url" name = "url"
version = "2.4.0" version = "2.4.0"
@ -3219,8 +3286,8 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0e74f82d49d545ad128049b7e88f6576df2da6b02e9ce565c6f533be576957e" checksum = "f0e74f82d49d545ad128049b7e88f6576df2da6b02e9ce565c6f533be576957e"
dependencies = [ dependencies = [
"ring", "ring 0.16.20",
"untrusted", "untrusted 0.7.1",
] ]
[[package]] [[package]]

View file

@ -7,7 +7,9 @@ edition = "2021"
anyhow = "1.0.71" anyhow = "1.0.71"
async-trait = "0.1.73" async-trait = "0.1.73"
axum = { version = "0.7.2", features = [ "macros" ] } axum = { version = "0.7.2", features = [ "macros" ] }
chrono = "0.4.31"
itertools = "0.12.0" itertools = "0.12.0"
jsonwebtoken = "9.2.0"
serde = { version = "1.0.166", features = [ "derive" ] } serde = { version = "1.0.166", features = [ "derive" ] }
serde_json = "1.0.99" serde_json = "1.0.99"
serde_with = "3.3.0" serde_with = "3.3.0"

View file

@ -1,11 +1,29 @@
use axum::Router; use axum::Router;
use tokio::{net::TcpListener, task::JoinHandle}; use tokio::{net::TcpListener, task::JoinHandle};
use serde::{Serialize, Deserialize};
use uuid::Uuid;
use crate::{state::ApiState, config::ApiConfig}; use crate::{state::ApiState, config::ApiConfig};
mod v0; mod v0;
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct AppId(Uuid);
impl AppId {
pub fn new() -> Self {
AppId { 0: Uuid::new_v4() }
}
}
#[derive(Serialize, Deserialize)]
pub struct AppDescription {
pub name: String,
pub desc_text: String,
}
pub struct Api { pub struct Api {
server_thread: JoinHandle<()>, server_thread: JoinHandle<()>,
} }

67
src/api/v0/app.rs Normal file
View file

@ -0,0 +1,67 @@
use std::sync::Arc;
use serde::{Serialize, Deserialize};
use axum::{Extension, extract::Request, body::Body, middleware::Next, response::{IntoResponse, Response}, http::{header::AUTHORIZATION, StatusCode}, Json};
use jsonwebtoken::{decode, Header};
use tracing::{debug, warn, error};
use crate::{state::ApiState, api::{AppId, AppDescription}};
#[derive(Serialize, Deserialize, Debug)]
struct JWTClaims {
sub: AppId,
}
pub(super) async fn auth(s: Extension<Arc<ApiState>>, mut request: Request<Body>, next: Next) -> Response {
if let Some(auth_header) = request.headers().get(AUTHORIZATION) {
if let Ok(header_string) = auth_header.to_str() {
if header_string.starts_with("Bearer") {
if let Ok(token) = decode::<JWTClaims>(
&header_string[7..],
s.jwt_decoding_key(),
s.jwt_validation(),
) {
if let Ok(true) = s.app_exists(&token.claims.sub) {
debug!("Authentication for {:?} succeeded.", &token.claims.sub);
request.extensions_mut().insert(token.claims.sub);
return next.run(request).await;
}
}
}
}
}
debug!("Authentication failed for request '{:?}'.", &request);
StatusCode::UNAUTHORIZED.into_response()
}
pub(super) async fn register(s: Extension<Arc<ApiState>>, Json(data): Json<AppDescription>) -> Response {
// Maybe ask for consent by user
// If user wants registration, proceed
let result = s.add_app(&data);
match result {
Ok(id) => {
// Build JWT, respond
let jwt = jsonwebtoken::encode(
&Header::default(),
&JWTClaims {sub: id},
&s.jwt_encoding_key(),
);
match jwt {
Ok(token) => (StatusCode::OK, token).into_response(),
Err(e) => {
warn!("Failed to encode token: {:?}", e);
StatusCode::INTERNAL_SERVER_ERROR.into_response()
}
}
},
Err(e) => {
error!("Failed to register new application! {:?}", e);
StatusCode::INTERNAL_SERVER_ERROR.into_response()
},
}
}

View file

@ -1,21 +1,12 @@
use std::sync::Arc; use std::sync::Arc;
use axum::{Router, routing::{put, get}, extract::{Path, Json}, Extension, response::{IntoResponse, Response}, http::StatusCode}; use axum::{extract::{Path, Json}, Extension, response::{IntoResponse, Response}, http::StatusCode};
use tracing::{warn, debug}; use tracing::{warn, debug};
use crate::state::{types::{ElementId, ElementContent}, ApiState}; use crate::state::{types::{ElementId, ElementContent}, ApiState};
pub(super) async fn get(Path(id): Path<ElementId>, s: Extension<Arc<ApiState>>) -> Response {
pub fn get_router(state: ApiState) -> Router {
Router::new()
.route("/element", put(create_element))
.route("/element/:id", get(get_element).post(set_element).delete(remove_element))
.layer(Extension(Arc::new(state)))
}
async fn get_element(Path(id): Path<ElementId>, s: Extension<Arc<ApiState>>) -> Response {
let element = s.get_element(&id); let element = s.get_element(&id);
match element { match element {
Ok(el) => (StatusCode::OK, Json{0: el}).into_response(), Ok(el) => (StatusCode::OK, Json{0: el}).into_response(),
@ -26,7 +17,7 @@ async fn get_element(Path(id): Path<ElementId>, s: Extension<Arc<ApiState>>) ->
} }
} }
async fn create_element(s: Extension<Arc<ApiState>>, Json(content): Json<ElementContent>) -> Response { pub(super) async fn create(s: Extension<Arc<ApiState>>, Json(content): Json<ElementContent>) -> Response {
let element_id = s.create_element(&content); let element_id = s.create_element(&content);
debug!("{:?}", element_id); debug!("{:?}", element_id);
match element_id { match element_id {
@ -35,7 +26,7 @@ async fn create_element(s: Extension<Arc<ApiState>>, Json(content): Json<Element
} }
} }
async fn set_element(Path(id): Path<ElementId>, s: Extension<Arc<ApiState>>, Json(content): Json<ElementContent>) -> Response { pub(super) async fn set(Path(id): Path<ElementId>, s: Extension<Arc<ApiState>>, Json(content): Json<ElementContent>) -> Response {
let res = s.write_element_content(&id, &content); let res = s.write_element_content(&id, &content);
match res { match res {
Ok(_) => StatusCode::OK.into_response(), Ok(_) => StatusCode::OK.into_response(),
@ -43,7 +34,7 @@ async fn set_element(Path(id): Path<ElementId>, s: Extension<Arc<ApiState>>, Jso
} }
} }
async fn remove_element(Path(id): Path<ElementId>, s: Extension<Arc<ApiState>>) -> Response { pub(super) async fn remove(Path(id): Path<ElementId>, s: Extension<Arc<ApiState>>) -> Response {
let res = s.remove_element(&id); let res = s.remove_element(&id);
match res { match res {
Ok(_) => StatusCode::OK.into_response(), Ok(_) => StatusCode::OK.into_response(),

20
src/api/v0/mod.rs Normal file
View file

@ -0,0 +1,20 @@
use std::sync::Arc;
use axum::{Router, routing::{put, get}, Extension, middleware::from_fn};
use crate::state::ApiState;
mod app;
mod element;
pub fn get_router(state: ApiState) -> Router {
Router::new()
// authenticated routes
.route("/element", put(element::create))
.route("/element/:id", get(element::get).post(element::set).delete(element::remove))
.layer(from_fn(app::auth))
// public / unauthenticated routes
.route("/app/register", put(app::register))
.layer(Extension(Arc::new(state)))
}

View file

@ -4,11 +4,12 @@ use serde::{Serialize, Deserialize};
pub struct Config { pub struct Config {
pub i2p_private_key: Option<String>, pub i2p_private_key: Option<String>,
pub api_config: ApiConfig, pub api_config: ApiConfig,
pub jwt_secret: String,
} }
impl Default for Config { impl Default for Config {
fn default() -> Self { fn default() -> Self {
Config { i2p_private_key: None, api_config: Default::default() } Config { i2p_private_key: None, api_config: Default::default(), jwt_secret: "insecuresecret".to_string() }
} }
} }

View file

@ -8,7 +8,7 @@ use i2p::net::I2pSocketAddr;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use state::{State, types::{MessageId, PeerId}, CommState, ApiState}; use state::{State, types::{MessageId, PeerId}, CommState, ApiState};
mod api; pub mod api;
pub mod comm; pub mod comm;
pub mod config; pub mod config;
pub mod state; pub mod state;
@ -31,7 +31,7 @@ impl Ubisync {
let comm_handle = Arc::new(CommHandle::new(CommState::new(state.clone()), config)?); let comm_handle = Arc::new(CommHandle::new(CommState::new(state.clone()), config)?);
state.set_comm_handle(comm_handle.clone()); state.set_comm_handle(comm_handle.clone());
let api = Arc::new(ApiBuilder::from(config.api_config.clone()).build(ApiState::new(state.clone())).await); let api = Arc::new(ApiBuilder::from(config.api_config.clone()).build(ApiState::new(state.clone(), &config.jwt_secret)).await);
comm_handle.run().await; comm_handle.run().await;
Ok(Ubisync { Ok(Ubisync {

View file

@ -1,9 +1,11 @@
use std::sync::Arc; use std::sync::Arc;
use chrono::Utc;
use cozo::DbInstance; use cozo::DbInstance;
use jsonwebtoken::{EncodingKey, DecodingKey, Validation};
use tracing::debug; use tracing::debug;
use crate::{state::{types::ElementId, queries}, comm::messages::MessageContent}; use crate::{state::{types::ElementId, queries}, comm::messages::MessageContent, api::{AppDescription, AppId}};
use super::{State, types::{ElementContent, Element}}; use super::{State, types::{ElementContent, Element}};
@ -11,11 +13,34 @@ use super::{State, types::{ElementContent, Element}};
pub struct ApiState { pub struct ApiState {
state: Arc<State>, state: Arc<State>,
jwt_encoding_key: EncodingKey,
jwt_decoding_key: DecodingKey,
jwt_validation: Validation,
} }
impl ApiState { impl ApiState {
pub fn new(state: Arc<State>) -> Self { pub fn new(state: Arc<State>, jwt_secret: &str) -> Self {
ApiState { state: state } let mut validation = Validation::default();
validation.set_required_spec_claims(&vec!["sub"]);
ApiState {
state: state,
jwt_encoding_key: EncodingKey::from_secret(jwt_secret.as_bytes()),
jwt_decoding_key: DecodingKey::from_secret(jwt_secret.as_bytes()),
jwt_validation: validation,
}
}
pub fn add_app(&self, description: &AppDescription) -> anyhow::Result<AppId> {
let id = AppId::new();
let last_access = Utc::now();
queries::apps::add(self.db(), &id, &last_access, &description.name, &description.desc_text)?;
debug!("Successfully added app");
Ok(id)
}
pub fn app_exists(&self, id: &AppId) -> anyhow::Result<bool> {
queries::apps::exists(self.db(), id)
} }
pub fn create_element(&self, content: &ElementContent) -> anyhow::Result<ElementId> { pub fn create_element(&self, content: &ElementContent) -> anyhow::Result<ElementId> {
@ -50,6 +75,18 @@ impl ApiState {
fn db(&self) -> &DbInstance { fn db(&self) -> &DbInstance {
&self.state.db &self.state.db
} }
pub fn jwt_encoding_key(&self) -> &EncodingKey {
&self.jwt_encoding_key
}
pub fn jwt_decoding_key(&self) -> &DecodingKey {
&self.jwt_decoding_key
}
pub fn jwt_validation(&self) -> &Validation {
&self.jwt_validation
}
} }
#[cfg(test)] #[cfg(test)]
@ -61,7 +98,7 @@ mod tests {
#[serial_test::serial] #[serial_test::serial]
async fn test_element_create() { async fn test_element_create() {
tracing_subscriber::fmt().pretty().init(); tracing_subscriber::fmt().pretty().init();
let state = ApiState::new(State::new().await.unwrap()); let state = ApiState::new(State::new().await.unwrap(), "abcdabcdabcdabcdabcdabcdabcdabcd");
let id = state.create_element(&ElementContent::Text("Test-text".to_string())).unwrap(); let id = state.create_element(&ElementContent::Text("Test-text".to_string())).unwrap();
let el = state.get_element(&id).unwrap(); let el = state.get_element(&id).unwrap();
assert_eq!( assert_eq!(
@ -74,7 +111,7 @@ mod tests {
#[serial_test::serial] #[serial_test::serial]
async fn test_element_write() { async fn test_element_write() {
tracing_subscriber::fmt().pretty().init(); tracing_subscriber::fmt().pretty().init();
let state = ApiState::new(State::new().await.unwrap()); let state = ApiState::new(State::new().await.unwrap(), "abcdabcdabcdabcdabcdabcdabcdabcd");
let id = state.create_element(&ElementContent::Text("Test-text".to_string())).unwrap(); let id = state.create_element(&ElementContent::Text("Test-text".to_string())).unwrap();
state.write_element_content(&id,&ElementContent::Text("Test-text 2".to_string())).unwrap(); state.write_element_content(&id,&ElementContent::Text("Test-text 2".to_string())).unwrap();
let el = state.get_element(&id).unwrap(); let el = state.get_element(&id).unwrap();

36
src/state/queries/apps.rs Normal file
View file

@ -0,0 +1,36 @@
use std::collections::BTreeMap;
use anyhow::{bail, Error};
use chrono::{DateTime, Utc};
use cozo::{DbInstance, DataValue, Num};
use crate::{run_query, api::AppId};
pub fn add(db: &DbInstance, id: &AppId, last_access: &DateTime<Utc>, name: &str, description: &str) -> anyhow::Result<()> {
let params = vec![
("id", DataValue::Str(serde_json::to_string(&id).unwrap().into())),
("last_access", DataValue::Num(Num::Int(last_access.timestamp()))),
("name", DataValue::Str(name.into())),
("description", DataValue::Str(description.into())),
];
match run_query!(&db, ":insert apps {id => last_access, name, description}", params, cozo::ScriptMutability::Mutable) {
Ok(_) => Ok(()),
Err(report) => bail!(report)
}
}
pub fn exists(db: &DbInstance, id: &AppId) -> anyhow::Result<bool> {
let mut params = BTreeMap::new();
params.insert("id".to_string(), DataValue::Str(serde_json::to_string(&id)?.into()));
let result = db.run_script("?[name] := *apps[$id, last_access, name, description]", params, cozo::ScriptMutability::Immutable);
if let Ok(rows) = result {
return Ok(rows.rows.len() == 1);
}
Err(Error::msg("Could not check whether app is registered"))
}

View file

@ -1,12 +1,13 @@
pub mod apps;
pub mod elements; pub mod elements;
pub mod peers; pub mod peers;
#[macro_export] #[macro_export]
macro_rules! build_query { macro_rules! build_query {
($payload:expr, $params:expr) => { ($payload:expr, $params:expr) => {
{ {
use cozo::DataValue;
use std::collections::BTreeMap;
// Build parameters map // Build parameters map
let mut params_map: BTreeMap<String, DataValue> = Default::default(); let mut params_map: BTreeMap<String, DataValue> = Default::default();
let mut parameters_init = String::new(); let mut parameters_init = String::new();

View file

@ -1,5 +1,3 @@
use std::collections::BTreeMap;
use anyhow::Error; use anyhow::Error;
use cozo::{DbInstance, DataValue, ScriptMutability}; use cozo::{DbInstance, DataValue, ScriptMutability};

View file

@ -8,6 +8,13 @@ use cozo::DbInstance;
pub fn add_schema(db: &DbInstance) -> anyhow::Result<()> { pub fn add_schema(db: &DbInstance) -> anyhow::Result<()> {
let params = BTreeMap::new(); let params = BTreeMap::new();
match db.run_script(" match db.run_script("
{:create apps {
id: String,
=>
last_access: Int,
name: String,
description: String,
}}
{:create peers { {:create peers {
id: String, id: String,
=> =>

View file

@ -1,7 +1,7 @@
use std::time::Duration; use std::time::Duration;
use tracing::{Level, debug}; use tracing::{Level, debug};
use ubisync::{Ubisync, config::Config, state::types::{ElementContent, Element, ElementId}}; use ubisync::{Ubisync, config::Config, state::types::{ElementContent, Element, ElementId}, api::AppDescription};
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
@ -15,9 +15,13 @@ async fn two_nodes_element_creation() {
ubi1.add_peer_from_id(ubi2.get_destination().unwrap().into()).unwrap(); ubi1.add_peer_from_id(ubi2.get_destination().unwrap().into()).unwrap();
let http_client = reqwest::Client::new(); let http_client = reqwest::Client::new();
let register_response = http_client.put("http://localhost:9981/v0/app/register").json(&AppDescription{name: "Test".to_string(), desc_text: "desc".to_string()}).send().await.unwrap();
let jwt1 = register_response.text().await.expect("Couldn't fetch token from response");
let register_response = http_client.put("http://localhost:9982/v0/app/register").json(&AppDescription{name: "Test".to_string(), desc_text: "desc".to_string()}).send().await.unwrap();
let jwt2 = register_response.text().await.expect("Couldn't fetch token from response");
let test_element_content = ElementContent::Text("Text".to_string()); let test_element_content = ElementContent::Text("Text".to_string());
let put_resp = http_client.put(&format!("http://localhost:9981/v0/element")).json(&test_element_content).send().await.unwrap(); let put_resp = http_client.put(&format!("http://localhost:9981/v0/element")).json(&test_element_content).header("Authorization", &format!("Bearer {}", &jwt1)).send().await.unwrap();
debug!("{:?}", &put_resp); debug!("{:?}", &put_resp);
let put_resp_text = put_resp.text().await.expect("No put response body"); let put_resp_text = put_resp.text().await.expect("No put response body");
debug!("{}", put_resp_text); debug!("{}", put_resp_text);
@ -25,7 +29,7 @@ async fn two_nodes_element_creation() {
tokio::time::sleep(Duration::from_millis(3000)).await; tokio::time::sleep(Duration::from_millis(3000)).await;
let get_resp = http_client.get(&format!("http://localhost:9982/v0/element/{}", Into::<String>::into(&id))).send().await.expect("Get request failed"); let get_resp = http_client.get(&format!("http://localhost:9982/v0/element/{}", Into::<String>::into(&id))).header("Authorization", &format!("Bearer {}", &jwt2)).send().await.expect("Get request failed");
let get_resp_text = get_resp.text().await.expect("No get request body"); let get_resp_text = get_resp.text().await.expect("No get request body");
debug!("{}", get_resp_text); debug!("{}", get_resp_text);
let received_element = serde_json::from_str::<Element>(&get_resp_text).expect("Could not deserialize Element"); let received_element = serde_json::from_str::<Element>(&get_resp_text).expect("Could not deserialize Element");