summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorYigit Sever2021-04-13 15:36:57 +0300
committerYigit Sever2021-04-13 15:40:03 +0300
commitc0b2e354621ef1a66eaa7f12c119b825ab1ad522 (patch)
tree697429c5374bb1c6a4935768b201c67a08031372 /src
parent909f5317117594e4bc956d558a22698d59fd8129 (diff)
downloadgradecoin-c0b2e354621ef1a66eaa7f12c119b825ab1ad522.tar.gz
gradecoin-c0b2e354621ef1a66eaa7f12c119b825ab1ad522.tar.bz2
gradecoin-c0b2e354621ef1a66eaa7f12c119b825ab1ad522.zip
Refactor authorized propose functions
They were getting spaghetti so; new function: handlers::authorize_proposer(), handles the jwt stuff, NOT async and _may_ cause trouble down the road but then again the stuff it does used to be (repeated) in the functions so how bad can it be If else chains were getting unwieldy; https://refactoring.com/catalog/replaceNestedConditionalWithGuardClauses.html so now everything is returning early, might make verbose error handling easier
Diffstat (limited to 'src')
-rw-r--r--src/handlers.rs315
-rw-r--r--src/routes.rs4
2 files changed, 192 insertions, 127 deletions
diff --git a/src/handlers.rs b/src/handlers.rs
index b896ac2..beae999 100644
--- a/src/handlers.rs
+++ b/src/handlers.rs
@@ -1,7 +1,8 @@
1/// API handlers, the ends of each filter chain 1/// API handlers, the ends of each filter chain
2use blake2::{Blake2s, Digest}; 2use blake2::{Blake2s, Digest};
3use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; 3use jsonwebtoken::errors::ErrorKind;
4use log::debug; 4use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation};
5use log::{debug, warn};
5use md5::Md5; 6use md5::Md5;
6use parking_lot::RwLockUpgradableReadGuard; 7use parking_lot::RwLockUpgradableReadGuard;
7use serde_json; 8use serde_json;
@@ -82,102 +83,109 @@ pub async fn list_transactions(db: Db) -> Result<impl warp::Reply, Infallible> {
82} 83}
83 84
84/// POST /block 85/// POST /block
85/// Proposes a new block for the next round 86///
87/// Proposes a new block for the next round.
86/// Can reject the block 88/// Can reject the block
87pub async fn auth_propose_block( 89///
90/// TODO: WHO IS PROPOSING THIS BLOCK OH GOD <13-04-21, yigit> // ok let's say the proposer has
91/// to put their transaction as the first transaction of the transaction_list
92/// that's not going to backfire in any way
93///
94/// TODO: after a block is accepted, it's transactions should play out and the proposer should
95/// get something for their efforts <13-04-21, yigit> //
96pub async fn authorized_propose_block(
88 new_block: Block, 97 new_block: Block,
89 token: String, 98 token: String,
90 db: Db, 99 db: Db,
91) -> Result<impl warp::Reply, warp::Rejection> { 100) -> Result<impl warp::Reply, warp::Rejection> {
92 debug!("POST request to /block, auth_propose_block"); 101 debug!("POST request to /block, authorized_propose_block");
102
103 let users_store = db.users.read();
104
105 let internal_user = match users_store.get(&new_block.transaction_list[0]) {
106 Some(existing_user) => existing_user,
107 None => {
108 debug!(
109 "A user with public key signature {:?} is not found in the database",
110 new_block.transaction_list[0]
111 );
112 // TODO: verbose error here <13-04-21, yigit> //
113 return Ok(StatusCode::BAD_REQUEST);
114 }
115 };
93 116
94 // Authorization check 117 let proposer_public_key = &internal_user.public_key;
95 let raw_jwt = token.trim_start_matches(BEARER).to_owned();
96 debug!("raw_jwt: {:?}", raw_jwt);
97 118
98 // TODO: WHO IS PROPOSING THIS BLOCK OH GOD <13-04-21, yigit> // ok let's say the proposer has 119 let token_payload = match authorize_proposer(token, &proposer_public_key) {
99 // to put their transaction as the first transaction of the transaction_list 120 Ok(data) => data,
100 // that's not going to backfire in any way 121 Err(below) => {
101 // TODO: after a block is accepted, it's transactions should play out and the proposer should 122 debug!("Something went wrong below {:?}", below);
102 // get something for their efforts <13-04-21, yigit> // 123 return Ok(StatusCode::BAD_REQUEST);
103 if let Some(user) = db.users.read().get(&new_block.transaction_list[0]) { 124 }
104 let proposer_public_key = &user.public_key; 125 };
105
106 if let Ok(decoded) = decode::<Claims>(
107 &raw_jwt,
108 &DecodingKey::from_rsa_pem(proposer_public_key.as_bytes()).unwrap(),
109 &Validation::new(Algorithm::RS256),
110 ) {
111 if decoded.claims.tha != new_block.hash {
112 debug!("Authorization unsuccessful");
113 return Ok(StatusCode::BAD_REQUEST);
114 }
115
116 debug!("authorized for block proposal");
117
118 let pending_transactions = db.pending_transactions.upgradable_read();
119 let blockchain = db.blockchain.upgradable_read();
120
121 for transaction_hash in new_block.transaction_list.iter() {
122 if !pending_transactions.contains_key(transaction_hash) {
123 return Ok(StatusCode::BAD_REQUEST);
124 }
125 }
126 126
127 let naked_block = NakedBlock { 127 debug!("authorized for block proposal");
128 transaction_list: new_block.transaction_list.clone(),
129 nonce: new_block.nonce.clone(),
130 timestamp: new_block.timestamp.clone(),
131 };
132 128
133 let naked_block_flat = serde_json::to_vec(&naked_block).unwrap(); 129 if token_payload.claims.tha != new_block.hash {
130 debug!(
131 "The Hash of the block {:?} did not match the hash given in jwt {:?}",
132 new_block.hash, token_payload.claims.tha
133 );
134 return Ok(StatusCode::BAD_REQUEST);
135 }
134 136
135 let hashvalue = Blake2s::digest(&naked_block_flat); 137 debug!("clear for block proposal");
136 let hash_string = format!("{:x}", hashvalue); 138 let pending_transactions = db.pending_transactions.upgradable_read();
139 let blockchain = db.blockchain.upgradable_read();
137 140
138 // 6 rightmost bits are zero? 141 for transaction_hash in new_block.transaction_list.iter() {
139 let should_zero = hashvalue[31] as i32 + hashvalue[30] as i32 + hashvalue[29] as i32; 142 if !pending_transactions.contains_key(transaction_hash) {
143 return Ok(StatusCode::BAD_REQUEST);
144 }
145 }
140 146
141 if should_zero == 0 { 147 let naked_block = NakedBlock {
142 // one last check to see if block is telling the truth 148 transaction_list: new_block.transaction_list.clone(),
143 if hash_string == new_block.hash { 149 nonce: new_block.nonce.clone(),
144 let mut blockchain = RwLockUpgradableReadGuard::upgrade(blockchain); 150 timestamp: new_block.timestamp.clone(),
151 };
145 152
146 let block_json = serde_json::to_string(&new_block).unwrap(); 153 let naked_block_flat = serde_json::to_vec(&naked_block).unwrap();
147 154
148 fs::write( 155 let hashvalue = Blake2s::digest(&naked_block_flat);
149 format!("blocks/{}.block", new_block.timestamp.timestamp()), 156 let hash_string = format!("{:x}", hashvalue);
150 block_json,
151 )
152 .unwrap();
153 157
154 *blockchain = new_block; 158 // 6 rightmost bits are zero?
159 let should_zero = hashvalue[31] as i32 + hashvalue[30] as i32 + hashvalue[29] as i32;
155 160
156 let mut pending_transactions = 161 if should_zero != 0 {
157 RwLockUpgradableReadGuard::upgrade(pending_transactions); 162 debug!("the hash does not have 6 rightmost zero bits");
158 pending_transactions.clear(); 163 return Ok(StatusCode::BAD_REQUEST);
164 }
159 165
160 Ok(StatusCode::CREATED) 166 // one last check to see if block is telling the truth
161 } else { 167 if hash_string != new_block.hash {
162 debug!("request was not telling the truth, hash values do not match"); 168 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> // 169 // TODO: does this condition make more sense _before_ the hash 0s check? <13-04-21, yigit> //
164 Ok(StatusCode::BAD_REQUEST) 170 return Ok(StatusCode::BAD_REQUEST);
165 }
166 } else {
167 debug!("the hash does not have 6 rightmost zero bits");
168 Ok(StatusCode::BAD_REQUEST)
169 }
170 } else {
171 debug!("authorization failed");
172 Ok(StatusCode::BAD_REQUEST)
173 }
174 } else {
175 debug!(
176 "A user with public key signature {:?} is not found in the database",
177 new_block.transaction_list[0]
178 );
179 Ok(StatusCode::BAD_REQUEST)
180 } 171 }
172
173 let mut blockchain = RwLockUpgradableReadGuard::upgrade(blockchain);
174
175 let block_json = serde_json::to_string(&new_block).unwrap();
176
177 fs::write(
178 format!("blocks/{}.block", new_block.timestamp.timestamp()),
179 block_json,
180 )
181 .unwrap();
182
183 *blockchain = new_block;
184
185 let mut pending_transactions = RwLockUpgradableReadGuard::upgrade(pending_transactions);
186 pending_transactions.clear();
187
188 Ok(StatusCode::CREATED)
181} 189}
182 190
183/// POST /transaction 191/// POST /transaction
@@ -190,64 +198,66 @@ pub async fn auth_propose_block(
190/// * `db` - Global [`Db`] instance 198/// * `db` - Global [`Db`] instance
191/// 199///
192/// TODO This method should check if the user has enough balance for the transaction 200/// TODO This method should check if the user has enough balance for the transaction
193/// 201pub async fn authorized_propose_transaction(
194/// TODO: refactor this https://refactoring.com/catalog/replaceNestedConditionalWithGuardClauses.html
195pub async fn auth_propose_transaction(
196 new_transaction: Transaction, 202 new_transaction: Transaction,
197 token: String, 203 token: String,
198 db: Db, 204 db: Db,
199) -> Result<impl warp::Reply, warp::Rejection> { 205) -> Result<impl warp::Reply, warp::Rejection> {
200 debug!("POST request to /transaction, propose_transaction"); 206 debug!("POST request to /transaction, authorized_propose_transaction");
201 debug!("The transaction request: {:?}", new_transaction); 207 debug!("The transaction request: {:?}", new_transaction);
202 208
203 let raw_jwt = token.trim_start_matches(BEARER).to_owned(); 209 let users_store = db.users.read();
204 println!("raw_jwt: {:?}", raw_jwt); 210
205 211 // Is this transaction from an authorized source?
206 // Authorization check first 212 let internal_user = match users_store.get(&new_transaction.by) {
207 if let Some(user) = db.users.read().get(&new_transaction.by) { 213 Some(existing_user) => existing_user,
208 // This public key was already written to the database, we can panic if it's not valid at 214 None => {
209 // *this* point 215 debug!(
210 let by_public_key = &user.public_key; 216 "A user with public key signature {:?} is not found in the database",
211 217 new_transaction.by
212 if let Ok(decoded) = decode::<Claims>( 218 );
213 &raw_jwt, 219 // TODO: verbose error here <13-04-21, yigit> //
214 &DecodingKey::from_rsa_pem(by_public_key.as_bytes()).unwrap(), 220 return Ok(StatusCode::BAD_REQUEST);
215 &Validation::new(Algorithm::RS256),
216 ) {
217 // this transaction was already checked for correctness at custom_filters, we can panic
218 // here if it has been changed since
219 debug!("authorized for transaction proposal");
220
221 let hashed_transaction = Md5::digest(&serde_json::to_vec(&new_transaction).unwrap());
222
223 if decoded.claims.tha == format!("{:x}", hashed_transaction) {
224 let mut transactions = db.pending_transactions.write();
225
226 transactions.insert(new_transaction.source.to_owned(), new_transaction);
227
228 Ok(StatusCode::CREATED)
229 } else {
230 debug!(
231 "the hash of the request {:x} did not match with the hash given in jwt {:?}",
232 hashed_transaction, decoded.claims.tha
233 );
234 Ok(StatusCode::BAD_REQUEST)
235 }
236 } else {
237 debug!("raw_jwt was malformed {:?}", raw_jwt);
238 Ok(StatusCode::BAD_REQUEST)
239 } 221 }
240 } else { 222 };
223
224 // `user` is an authenticated student, can propose
225
226 // This public key was already written to the database, we can panic if it's not valid at
227 // *this* point
228 let proposer_public_key = &internal_user.public_key;
229
230 let token_payload = match authorize_proposer(token, &proposer_public_key) {
231 Ok(data) => data,
232 Err(below) => {
233 debug!("Something went wrong below {:?}", below);
234 return Ok(StatusCode::BAD_REQUEST);
235 }
236 };
237
238 // this transaction was already checked for correctness at custom_filters, we can panic
239 // here if it has been changed since
240 debug!("authorized for transaction proposal");
241
242 let hashed_transaction = Md5::digest(&serde_json::to_vec(&new_transaction).unwrap());
243
244 if token_payload.claims.tha != format!("{:x}", hashed_transaction) {
241 debug!( 245 debug!(
242 "A user with public key signature {:?} is not found in the database", 246 "the hash of the request {:x} did not match the hash given in jwt {:?}",
243 new_transaction.by 247 hashed_transaction, token_payload.claims.tha
244 ); 248 );
245 Ok(StatusCode::BAD_REQUEST) 249 return Ok(StatusCode::BAD_REQUEST);
246 } 250 }
251
252 debug!("clear for transaction proposal");
253
254 let mut transactions = db.pending_transactions.write();
255 transactions.insert(new_transaction.source.to_owned(), new_transaction);
256 Ok(StatusCode::CREATED)
247} 257}
248 258
249/// GET /block 259/// GET /block
250/// Returns JSON array of blocks 260/// Returns the last block's JSON
251/// Cannot fail 261/// Cannot fail
252/// Mostly around for debug purposes 262/// Mostly around for debug purposes
253pub async fn list_blocks(db: Db) -> Result<impl warp::Reply, Infallible> { 263pub async fn list_blocks(db: Db) -> Result<impl warp::Reply, Infallible> {
@@ -257,3 +267,58 @@ pub async fn list_blocks(db: Db) -> Result<impl warp::Reply, Infallible> {
257 267
258 Ok(reply::with_status(reply::json(&*block), StatusCode::OK)) 268 Ok(reply::with_status(reply::json(&*block), StatusCode::OK))
259} 269}
270
271/// Handles the JWT Authorization
272///
273/// *[`jwt_token`]: The raw JWT token, "Bearer aaa.bbb.ccc"
274/// *[`user_pem`]: User Public Key, "BEGIN RSA"
275/// NOT async, might look into it if this becomes a bottleneck
276fn authorize_proposer(
277 jwt_token: String,
278 user_pem: &String,
279) -> Result<TokenData<Claims>, jsonwebtoken::errors::Error> {
280 // Throw away the "Bearer " part
281 let raw_jwt = jwt_token.trim_start_matches(BEARER).to_owned();
282 debug!("raw_jwt: {:?}", raw_jwt);
283
284 // Extract a jsonwebtoken compatible decoding_key from user's public key
285 let decoding_key = match DecodingKey::from_rsa_pem(user_pem.as_bytes()) {
286 Ok(key) => key,
287 Err(j) => {
288 warn!(
289 "user has invalid RSA key we should crash and burn here {:?}",
290 j
291 );
292 return Err(j);
293 }
294 };
295
296 // Extract the payload inside the JWT
297 let token_payload =
298 match decode::<Claims>(&raw_jwt, &decoding_key, &Validation::new(Algorithm::RS256)) {
299 Ok(decoded) => decoded,
300 Err(err) => match *err.kind() {
301 ErrorKind::InvalidToken => {
302 // TODO: verbose error here <13-04-21, yigit> //
303 debug!("raw_jwt={:?} was malformed err={:?}", raw_jwt, err);
304 return Err(err);
305 }
306 ErrorKind::InvalidRsaKey => {
307 // TODO: verbose error here <13-04-21, yigit> //
308 debug!("the RSA key does not have a valid format, {:?}", err);
309 return Err(err);
310 }
311 ErrorKind::ExpiredSignature => {
312 // TODO: verbose error here <13-04-21, yigit> //
313 debug!("this token has expired {:?}", err);
314 return Err(err);
315 }
316 _ => {
317 warn!("AN UNSPECIFIED ERROR: {:?}", err);
318 return Err(err);
319 }
320 },
321 };
322
323 Ok(token_payload)
324}
diff --git a/src/routes.rs b/src/routes.rs
index 0fb61c4..280de35 100644
--- a/src/routes.rs
+++ b/src/routes.rs
@@ -48,7 +48,7 @@ pub fn auth_transaction_propose(
48 .and(custom_filters::transaction_json_body()) 48 .and(custom_filters::transaction_json_body())
49 .and(custom_filters::auth_header()) 49 .and(custom_filters::auth_header())
50 .and(custom_filters::with_db(db)) 50 .and(custom_filters::with_db(db))
51 .and_then(handlers::auth_propose_transaction) 51 .and_then(handlers::authorized_propose_transaction)
52} 52}
53 53
54/// POST /block warp route 54/// POST /block warp route
@@ -58,6 +58,6 @@ pub fn auth_block_propose(db: Db) -> impl Filter<Extract = impl Reply, Error = R
58 .and(custom_filters::block_json_body()) 58 .and(custom_filters::block_json_body())
59 .and(custom_filters::auth_header()) 59 .and(custom_filters::auth_header())
60 .and(custom_filters::with_db(db)) 60 .and(custom_filters::with_db(db))
61 .and_then(handlers::auth_propose_block) 61 .and_then(handlers::authorized_propose_block)
62} 62}
63 63