aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYigit Sever2021-04-13 04:51:19 +0300
committerYigit Sever2021-04-13 04:51:19 +0300
commit8b50bf2e66f02e64e7fbec8079094bafb5ccdc6a (patch)
tree615f6254a326077f83aa5200c822bceb823696e0
parent0158bc39981a751d53e2d12633d179d22484c791 (diff)
downloadgradecoin-8b50bf2e66f02e64e7fbec8079094bafb5ccdc6a.tar.gz
gradecoin-8b50bf2e66f02e64e7fbec8079094bafb5ccdc6a.tar.bz2
gradecoin-8b50bf2e66f02e64e7fbec8079094bafb5ccdc6a.zip
Require authorization for Block POST
Not tested because it's impossible to follow without verbose error messages, failing 1 test
-rw-r--r--TODO.md4
-rw-r--r--src/bin/main.rs2
-rw-r--r--src/handlers.rs142
-rw-r--r--src/routes.rs7
-rw-r--r--tests/route_tests.rs3
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 @@
4- [ ] /register is currently accepting non-encrypted (regular JSON) payloads 4- [ ] /register is currently accepting non-encrypted (regular JSON) payloads
5 5
6## Authorization 6## Authorization
7- [ ] POST requests to /block should be authenticated as well 7- [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)
8 8
9## Verbosity 9## Verbosity
10- [ ] Verbose error messages (use error.rs ❓) 10- [ ] Verbose error messages (use error.rs from [logrocket](https://blog.logrocket.com/create-an-async-crud-web-service-in-rust-with-warp/) ❓)
11 11
12## Tests 12## Tests
13- [ ] Schema Tests 13- [ ] 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;
8 8
9#[tokio::main] 9#[tokio::main]
10async fn main() { 10async fn main() {
11 // Show debug logs by default by setting `RUST_LOG=restful_rust=debug` 11 // Show debug logs by default by setting `RUST_LOG=gradecoin=debug`
12 if env::var_os("RUST_LOG").is_none() { 12 if env::var_os("RUST_LOG").is_none() {
13 env::set_var("RUST_LOG", "gradecoin=debug"); 13 env::set_var("RUST_LOG", "gradecoin=debug");
14 } 14 }
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<impl warp::Reply, Infallible> {
81 Ok(reply::with_status(reply::json(&result), StatusCode::OK)) 81 Ok(reply::with_status(reply::json(&result), StatusCode::OK))
82} 82}
83 83
84/// GET /block
85/// Returns JSON array of blocks
86/// Cannot fail
87/// Mostly around for debug purposes
88pub async fn list_blocks(db: Db) -> Result<impl warp::Reply, Infallible> {
89 debug!("GET request to /block, list_blocks");
90
91 let block = db.blockchain.read();
92
93 Ok(reply::with_status(reply::json(&*block), StatusCode::OK))
94}
95
96/// POST /block 84/// POST /block
97/// Proposes a new block for the next round 85/// Proposes a new block for the next round
98/// Can reject the block 86/// Can reject the block
99pub async fn propose_block(new_block: Block, db: Db) -> Result<impl warp::Reply, warp::Rejection> { 87pub async fn auth_propose_block(
100 debug!("new block request {:?}", new_block); 88 new_block: Block,
89 token: String,
90 db: Db,
91) -> Result<impl warp::Reply, warp::Rejection> {
92 debug!("POST request to /block, auth_propose_block");
101 93
102 // https://blog.logrocket.com/create-an-async-crud-web-service-in-rust-with-warp/ (this has 94 // Authorization check
103 // error.rs, error struct, looks very clean) 95 let raw_jwt = token.trim_start_matches(BEARER).to_owned();
96 debug!("raw_jwt: {:?}", raw_jwt);
104 97
105 let pending_transactions = db.pending_transactions.upgradable_read(); 98 // TODO: WHO IS PROPOSING THIS BLOCK OH GOD <13-04-21, yigit> // ok let's say the proposer has
106 let blockchain = db.blockchain.upgradable_read(); 99 // to put their transaction as the first transaction of the transaction_list
100 // that's not going to backfire in any way
101 // TODO: after a block is accepted, it's transactions should play out and the proposer should
102 // get something for their efforts <13-04-21, yigit> //
103 if let Some(user) = db.users.read().get(&new_block.transaction_list[0]) {
104 let proposer_public_key = &user.public_key;
107 105
108 // check 1, new_block.transaction_list from pending_transactions pool? <07-04-21, yigit> // 106 if let Ok(decoded) = decode::<Claims>(
109 for transaction_hash in new_block.transaction_list.iter() { 107 &raw_jwt,
110 if !pending_transactions.contains_key(transaction_hash) { 108 &DecodingKey::from_rsa_pem(proposer_public_key.as_bytes()).unwrap(),
111 return Ok(StatusCode::BAD_REQUEST); 109 &Validation::new(Algorithm::RS256),
112 } 110 ) {
113 } 111 if decoded.claims.tha != new_block.hash {
112 debug!("Authorization unsuccessful");
113 return Ok(StatusCode::BAD_REQUEST);
114 }
114 115
115 let naked_block = NakedBlock { 116 debug!("authorized for block proposal");
116 transaction_list: new_block.transaction_list.clone(),
117 nonce: new_block.nonce.clone(),
118 timestamp: new_block.timestamp.clone(),
119 };
120 117
121 let naked_block_flat = serde_json::to_vec(&naked_block).unwrap(); 118 let pending_transactions = db.pending_transactions.upgradable_read();
119 let blockchain = db.blockchain.upgradable_read();
122 120
123 let hashvalue = Blake2s::digest(&naked_block_flat); 121 for transaction_hash in new_block.transaction_list.iter() {
124 let hash_string = format!("{:x}", hashvalue); 122 if !pending_transactions.contains_key(transaction_hash) {
123 return Ok(StatusCode::BAD_REQUEST);
124 }
125 }
125 126
126 // 6 rightmost bits are zero 127 let naked_block = NakedBlock {
127 let should_zero = hashvalue[31] as i32 + hashvalue[30] as i32 + hashvalue[29] as i32; 128 transaction_list: new_block.transaction_list.clone(),
129 nonce: new_block.nonce.clone(),
130 timestamp: new_block.timestamp.clone(),
131 };
132
133 let naked_block_flat = serde_json::to_vec(&naked_block).unwrap();
134
135 let hashvalue = Blake2s::digest(&naked_block_flat);
136 let hash_string = format!("{:x}", hashvalue);
128 137
129 if should_zero == 0 { 138 // 6 rightmost bits are zero?
130 // one last check to see if block is telling the truth 139 let should_zero = hashvalue[31] as i32 + hashvalue[30] as i32 + hashvalue[29] as i32;
131 if hash_string == new_block.hash {
132 let mut blockchain = RwLockUpgradableReadGuard::upgrade(blockchain);
133 140
134 let block_json = serde_json::to_string(&new_block).unwrap(); 141 if should_zero == 0 {
142 // one last check to see if block is telling the truth
143 if hash_string == new_block.hash {
144 let mut blockchain = RwLockUpgradableReadGuard::upgrade(blockchain);
135 145
136 fs::write( 146 let block_json = serde_json::to_string(&new_block).unwrap();
137 format!("blocks/{}.block", new_block.timestamp.timestamp()),
138 block_json,
139 )
140 .unwrap();
141 147
142 *blockchain = new_block; 148 fs::write(
149 format!("blocks/{}.block", new_block.timestamp.timestamp()),
150 block_json,
151 )
152 .unwrap();
143 153
144 let mut pending_transactions = RwLockUpgradableReadGuard::upgrade(pending_transactions); 154 *blockchain = new_block;
145 pending_transactions.clear();
146 155
147 Ok(StatusCode::CREATED) 156 let mut pending_transactions =
157 RwLockUpgradableReadGuard::upgrade(pending_transactions);
158 pending_transactions.clear();
159
160 Ok(StatusCode::CREATED)
161 } else {
162 debug!("request was not telling the truth, hash values do not match");
163 // TODO: does this condition make more sense _before_ the hash 0s check? <13-04-21, yigit> //
164 Ok(StatusCode::BAD_REQUEST)
165 }
166 } else {
167 debug!("the hash does not have 6 rightmost zero bits");
168 Ok(StatusCode::BAD_REQUEST)
169 }
148 } else { 170 } else {
171 debug!("authorization failed");
149 Ok(StatusCode::BAD_REQUEST) 172 Ok(StatusCode::BAD_REQUEST)
150 } 173 }
151 } else { 174 } else {
152 // reject 175 debug!(
176 "A user with public key signature {:?} is not found in the database",
177 new_block.transaction_list[0]
178 );
153 Ok(StatusCode::BAD_REQUEST) 179 Ok(StatusCode::BAD_REQUEST)
154 } 180 }
155} 181}
@@ -164,6 +190,8 @@ pub async fn propose_block(new_block: Block, db: Db) -> Result<impl warp::Reply,
164/// * `db` - Global [`Db`] instance 190/// * `db` - Global [`Db`] instance
165/// 191///
166/// TODO This method should check if the user has enough balance for the transaction 192/// TODO This method should check if the user has enough balance for the transaction
193///
194/// TODO: refactor this https://refactoring.com/catalog/replaceNestedConditionalWithGuardClauses.html
167pub async fn auth_propose_transaction( 195pub async fn auth_propose_transaction(
168 new_transaction: Transaction, 196 new_transaction: Transaction,
169 token: String, 197 token: String,
@@ -173,8 +201,9 @@ pub async fn auth_propose_transaction(
173 debug!("The transaction request: {:?}", new_transaction); 201 debug!("The transaction request: {:?}", new_transaction);
174 202
175 let raw_jwt = token.trim_start_matches(BEARER).to_owned(); 203 let raw_jwt = token.trim_start_matches(BEARER).to_owned();
176 debug!("raw_jwt: {:?}", raw_jwt); 204 println!("raw_jwt: {:?}", raw_jwt);
177 205
206 // Authorization check first
178 if let Some(user) = db.users.read().get(&new_transaction.by) { 207 if let Some(user) = db.users.read().get(&new_transaction.by) {
179 // This public key was already written to the database, we can panic if it's not valid at 208 // This public key was already written to the database, we can panic if it's not valid at
180 // *this* point 209 // *this* point
@@ -187,6 +216,7 @@ pub async fn auth_propose_transaction(
187 ) { 216 ) {
188 // this transaction was already checked for correctness at custom_filters, we can panic 217 // this transaction was already checked for correctness at custom_filters, we can panic
189 // here if it has been changed since 218 // here if it has been changed since
219 debug!("authorized for transaction proposal");
190 220
191 let hashed_transaction = Md5::digest(&serde_json::to_vec(&new_transaction).unwrap()); 221 let hashed_transaction = Md5::digest(&serde_json::to_vec(&new_transaction).unwrap());
192 222
@@ -215,3 +245,15 @@ pub async fn auth_propose_transaction(
215 Ok(StatusCode::BAD_REQUEST) 245 Ok(StatusCode::BAD_REQUEST)
216 } 246 }
217} 247}
248
249/// GET /block
250/// Returns JSON array of blocks
251/// Cannot fail
252/// Mostly around for debug purposes
253pub async fn list_blocks(db: Db) -> Result<impl warp::Reply, Infallible> {
254 debug!("GET request to /block, list_blocks");
255
256 let block = db.blockchain.read();
257
258 Ok(reply::with_status(reply::json(&*block), StatusCode::OK))
259}
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<Extract = impl Reply, Error = Rej
10 transaction_list(db.clone()) 10 transaction_list(db.clone())
11 .or(register_user(db.clone())) 11 .or(register_user(db.clone()))
12 .or(auth_transaction_propose(db.clone())) 12 .or(auth_transaction_propose(db.clone()))
13 .or(block_propose(db.clone())) 13 .or(auth_block_propose(db.clone()))
14 .or(block_list(db.clone())) 14 .or(block_list(db.clone()))
15} 15}
16 16
@@ -52,11 +52,12 @@ pub fn auth_transaction_propose(
52} 52}
53 53
54/// POST /block warp route 54/// POST /block warp route
55pub fn block_propose(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { 55pub fn auth_block_propose(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
56 warp::path!("block") 56 warp::path!("block")
57 .and(warp::post()) 57 .and(warp::post())
58 .and(custom_filters::block_json_body()) 58 .and(custom_filters::block_json_body())
59 .and(custom_filters::auth_header())
59 .and(custom_filters::with_db(db)) 60 .and(custom_filters::with_db(db))
60 .and_then(handlers::propose_block) 61 .and_then(handlers::auth_propose_block)
61} 62}
62 63
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
263 /// Should accept the json request, create 263 /// Should accept the json request, create
264 /// the block 264 /// the block
265 #[tokio::test] 265 #[tokio::test]
266 async fn post_block_201() { 266 async fn post_block_auth_201() {
267 let db = mocked_db(); 267 let db = mocked_db();
268 let filter = consensus_routes(db.clone()); 268 let filter = consensus_routes(db.clone());
269 269
270 let res = warp::test::request() 270 let res = warp::test::request()
271 .method("POST") 271 .method("POST")
272 .json(&mocked_block()) 272 .json(&mocked_block())
273 .header("Authorization", "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0aGEiOiIyYjY0OGZmYWI1ZDlhZjFkNWQ1ZmMwNTJmYzllNTFiODgyZmM0ZmIwYzk5ODYwOGM5OTIzMmY5MjgyMDAwMDAwIiwiaWF0IjoxNjE4MzYwNjQxLCJleHAiOjE3MTgyNjA2NDF9.P5L_uZ9lOhRZCbsG9GDXn_rmZat3dP9Y2lbk8GY4Kg4pOxJIklBUxot-TtJzB0vEJFcjnxVnT2lFLCgfdQLHTJvURiW0KRHi94e1Kj8aDXxJ0qjlq4-c1JCZnAIbDpvkFtHNKz04yfyeSR2htJ6kOjlqVpeUhLVokHhi1x-ZUZZSpeGnlIXgi-AcmkEoyOypZGSZgQ1hjID2f18zgfbshgPK4Dr0hiN36wYMB0y0YiikRbvDuGgDzRLN2nitih46-CXTGZMqIRz3eAfM2wuUSH1yhdKi5_vavz8L3EPVCGMO-CKlPUDkYA-duQZf_q3tG2fkdaFlTAcCik_kVMprdw")
273 .path("/block") 274 .path("/block")
274 .reply(&filter) 275 .reply(&filter)
275 .await; 276 .await;