From 8b50bf2e66f02e64e7fbec8079094bafb5ccdc6a Mon Sep 17 00:00:00 2001 From: Yigit Sever Date: Tue, 13 Apr 2021 04:51:19 +0300 Subject: Require authorization for Block POST Not tested because it's impossible to follow without verbose error messages, failing 1 test --- TODO.md | 4 +- src/bin/main.rs | 2 +- src/handlers.rs | 142 +++++++++++++++++++++++++++++++++------------------ src/routes.rs | 7 +-- tests/route_tests.rs | 3 +- 5 files changed, 101 insertions(+), 57 deletions(-) diff --git a/TODO.md b/TODO.md index f762b01..b1c2ca0 100644 --- a/TODO.md +++ b/TODO.md @@ -4,10 +4,10 @@ - [ ] /register is currently accepting non-encrypted (regular JSON) payloads ## Authorization -- [ ] POST requests to /block should be authenticated as well +- [x] POST requests to /block should be authenticated as well (2021-04-13 04:50, they now are but until we make error messages **Verbose** there's not much point in testing because I honestly cannot trace the code) ## Verbosity -- [ ] Verbose error messages (use error.rs ❓) +- [ ] Verbose error messages (use error.rs from [logrocket](https://blog.logrocket.com/create-an-async-crud-web-service-in-rust-with-warp/) ❓) ## Tests - [ ] Schema Tests diff --git a/src/bin/main.rs b/src/bin/main.rs index 8d88286..598a2e1 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -8,7 +8,7 @@ use gradecoin::schema::create_database; #[tokio::main] async fn main() { - // Show debug logs by default by setting `RUST_LOG=restful_rust=debug` + // Show debug logs by default by setting `RUST_LOG=gradecoin=debug` if env::var_os("RUST_LOG").is_none() { env::set_var("RUST_LOG", "gradecoin=debug"); } diff --git a/src/handlers.rs b/src/handlers.rs index 80ed1f7..b896ac2 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -81,75 +81,101 @@ pub async fn list_transactions(db: Db) -> Result { Ok(reply::with_status(reply::json(&result), StatusCode::OK)) } -/// GET /block -/// Returns JSON array of blocks -/// Cannot fail -/// Mostly around for debug purposes -pub async fn list_blocks(db: Db) -> Result { - debug!("GET request to /block, list_blocks"); - - let block = db.blockchain.read(); - - Ok(reply::with_status(reply::json(&*block), StatusCode::OK)) -} - /// POST /block /// Proposes a new block for the next round /// Can reject the block -pub async fn propose_block(new_block: Block, db: Db) -> Result { - debug!("new block request {:?}", new_block); +pub async fn auth_propose_block( + new_block: Block, + token: String, + db: Db, +) -> Result { + debug!("POST request to /block, auth_propose_block"); - // https://blog.logrocket.com/create-an-async-crud-web-service-in-rust-with-warp/ (this has - // error.rs, error struct, looks very clean) + // Authorization check + let raw_jwt = token.trim_start_matches(BEARER).to_owned(); + debug!("raw_jwt: {:?}", raw_jwt); - let pending_transactions = db.pending_transactions.upgradable_read(); - let blockchain = db.blockchain.upgradable_read(); + // TODO: WHO IS PROPOSING THIS BLOCK OH GOD <13-04-21, yigit> // ok let's say the proposer has + // to put their transaction as the first transaction of the transaction_list + // that's not going to backfire in any way + // TODO: after a block is accepted, it's transactions should play out and the proposer should + // get something for their efforts <13-04-21, yigit> // + if let Some(user) = db.users.read().get(&new_block.transaction_list[0]) { + let proposer_public_key = &user.public_key; - // check 1, new_block.transaction_list from pending_transactions pool? <07-04-21, yigit> // - for transaction_hash in new_block.transaction_list.iter() { - if !pending_transactions.contains_key(transaction_hash) { - return Ok(StatusCode::BAD_REQUEST); - } - } + if let Ok(decoded) = decode::( + &raw_jwt, + &DecodingKey::from_rsa_pem(proposer_public_key.as_bytes()).unwrap(), + &Validation::new(Algorithm::RS256), + ) { + if decoded.claims.tha != new_block.hash { + debug!("Authorization unsuccessful"); + return Ok(StatusCode::BAD_REQUEST); + } - let naked_block = NakedBlock { - transaction_list: new_block.transaction_list.clone(), - nonce: new_block.nonce.clone(), - timestamp: new_block.timestamp.clone(), - }; + debug!("authorized for block proposal"); - let naked_block_flat = serde_json::to_vec(&naked_block).unwrap(); + let pending_transactions = db.pending_transactions.upgradable_read(); + let blockchain = db.blockchain.upgradable_read(); - let hashvalue = Blake2s::digest(&naked_block_flat); - let hash_string = format!("{:x}", hashvalue); + for transaction_hash in new_block.transaction_list.iter() { + if !pending_transactions.contains_key(transaction_hash) { + return Ok(StatusCode::BAD_REQUEST); + } + } - // 6 rightmost bits are zero - let should_zero = hashvalue[31] as i32 + hashvalue[30] as i32 + hashvalue[29] as i32; + let naked_block = NakedBlock { + transaction_list: new_block.transaction_list.clone(), + nonce: new_block.nonce.clone(), + timestamp: new_block.timestamp.clone(), + }; + + let naked_block_flat = serde_json::to_vec(&naked_block).unwrap(); + + let hashvalue = Blake2s::digest(&naked_block_flat); + let hash_string = format!("{:x}", hashvalue); - if should_zero == 0 { - // one last check to see if block is telling the truth - if hash_string == new_block.hash { - let mut blockchain = RwLockUpgradableReadGuard::upgrade(blockchain); + // 6 rightmost bits are zero? + let should_zero = hashvalue[31] as i32 + hashvalue[30] as i32 + hashvalue[29] as i32; - let block_json = serde_json::to_string(&new_block).unwrap(); + if should_zero == 0 { + // one last check to see if block is telling the truth + if hash_string == new_block.hash { + let mut blockchain = RwLockUpgradableReadGuard::upgrade(blockchain); - fs::write( - format!("blocks/{}.block", new_block.timestamp.timestamp()), - block_json, - ) - .unwrap(); + let block_json = serde_json::to_string(&new_block).unwrap(); - *blockchain = new_block; + fs::write( + format!("blocks/{}.block", new_block.timestamp.timestamp()), + block_json, + ) + .unwrap(); - let mut pending_transactions = RwLockUpgradableReadGuard::upgrade(pending_transactions); - pending_transactions.clear(); + *blockchain = new_block; - Ok(StatusCode::CREATED) + let mut pending_transactions = + RwLockUpgradableReadGuard::upgrade(pending_transactions); + pending_transactions.clear(); + + Ok(StatusCode::CREATED) + } else { + debug!("request was not telling the truth, hash values do not match"); + // TODO: does this condition make more sense _before_ the hash 0s check? <13-04-21, yigit> // + Ok(StatusCode::BAD_REQUEST) + } + } else { + debug!("the hash does not have 6 rightmost zero bits"); + Ok(StatusCode::BAD_REQUEST) + } } else { + debug!("authorization failed"); Ok(StatusCode::BAD_REQUEST) } } else { - // reject + debug!( + "A user with public key signature {:?} is not found in the database", + new_block.transaction_list[0] + ); Ok(StatusCode::BAD_REQUEST) } } @@ -164,6 +190,8 @@ pub async fn propose_block(new_block: Block, db: Db) -> Result Result { + debug!("GET request to /block, list_blocks"); + + let block = db.blockchain.read(); + + Ok(reply::with_status(reply::json(&*block), StatusCode::OK)) +} diff --git a/src/routes.rs b/src/routes.rs index e4bdee4..0fb61c4 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -10,7 +10,7 @@ pub fn consensus_routes(db: Db) -> impl Filter impl Filter + Clone { +pub fn auth_block_propose(db: Db) -> impl Filter + Clone { warp::path!("block") .and(warp::post()) .and(custom_filters::block_json_body()) + .and(custom_filters::auth_header()) .and(custom_filters::with_db(db)) - .and_then(handlers::propose_block) + .and_then(handlers::auth_propose_block) } diff --git a/tests/route_tests.rs b/tests/route_tests.rs index ba3ecf3..57c4870 100644 --- a/tests/route_tests.rs +++ b/tests/route_tests.rs @@ -263,13 +263,14 @@ sQIDAQAB /// Should accept the json request, create /// the block #[tokio::test] - async fn post_block_201() { + async fn post_block_auth_201() { let db = mocked_db(); let filter = consensus_routes(db.clone()); let res = warp::test::request() .method("POST") .json(&mocked_block()) + .header("Authorization", "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0aGEiOiIyYjY0OGZmYWI1ZDlhZjFkNWQ1ZmMwNTJmYzllNTFiODgyZmM0ZmIwYzk5ODYwOGM5OTIzMmY5MjgyMDAwMDAwIiwiaWF0IjoxNjE4MzYwNjQxLCJleHAiOjE3MTgyNjA2NDF9.P5L_uZ9lOhRZCbsG9GDXn_rmZat3dP9Y2lbk8GY4Kg4pOxJIklBUxot-TtJzB0vEJFcjnxVnT2lFLCgfdQLHTJvURiW0KRHi94e1Kj8aDXxJ0qjlq4-c1JCZnAIbDpvkFtHNKz04yfyeSR2htJ6kOjlqVpeUhLVokHhi1x-ZUZZSpeGnlIXgi-AcmkEoyOypZGSZgQ1hjID2f18zgfbshgPK4Dr0hiN36wYMB0y0YiikRbvDuGgDzRLN2nitih46-CXTGZMqIRz3eAfM2wuUSH1yhdKi5_vavz8L3EPVCGMO-CKlPUDkYA-duQZf_q3tG2fkdaFlTAcCik_kVMprdw") .path("/block") .reply(&filter) .await; -- cgit v1.2.3-70-g09d2