summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
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