aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/bin/main.rs5
-rw-r--r--src/custom_filters.rs2
-rw-r--r--src/error.rs38
-rw-r--r--src/handlers.rs197
-rw-r--r--src/lib.rs1
-rwxr-xr-xtester.sh8
6 files changed, 188 insertions, 63 deletions
diff --git a/src/bin/main.rs b/src/bin/main.rs
index 598a2e1..8b61e5c 100644
--- a/src/bin/main.rs
+++ b/src/bin/main.rs
@@ -1,6 +1,7 @@
1use std::env; 1use std::env;
2use warp::Filter; 2use warp::Filter;
3 3
4// use gradecoin::error;
4use gradecoin::routes::consensus_routes; 5use gradecoin::routes::consensus_routes;
5use gradecoin::schema::create_database; 6use gradecoin::schema::create_database;
6 7
@@ -9,6 +10,7 @@ use gradecoin::schema::create_database;
9#[tokio::main] 10#[tokio::main]
10async fn main() { 11async fn main() {
11 // Show debug logs by default by setting `RUST_LOG=gradecoin=debug` 12 // Show debug logs by default by setting `RUST_LOG=gradecoin=debug`
13 // TODO: write logs to file? <13-04-21, yigit> //
12 if env::var_os("RUST_LOG").is_none() { 14 if env::var_os("RUST_LOG").is_none() {
13 env::set_var("RUST_LOG", "gradecoin=debug"); 15 env::set_var("RUST_LOG", "gradecoin=debug");
14 } 16 }
@@ -21,5 +23,6 @@ async fn main() {
21 let routes = api.with(warp::log("gradecoin")); 23 let routes = api.with(warp::log("gradecoin"));
22 24
23 // Start the server 25 // Start the server
24 warp::serve(routes).run(([127, 0, 0, 1], 8080)).await; 26 let point = ([127, 0, 0, 1], 8080);
27 warp::serve(routes).run(point).await;
25} 28}
diff --git a/src/custom_filters.rs b/src/custom_filters.rs
index dfdae04..ae8a56c 100644
--- a/src/custom_filters.rs
+++ b/src/custom_filters.rs
@@ -28,7 +28,7 @@ pub fn transaction_json_body() -> impl Filter<Extract = (Transaction,), Error =
28/// Used in Authorization for `Block` and `Transaction` proposals 28/// Used in Authorization for `Block` and `Transaction` proposals
29/// Rejects the request if the Authorization header does not exist 29/// Rejects the request if the Authorization header does not exist
30pub fn auth_header() -> impl Filter<Extract = (String,), Error = Rejection> + Clone { 30pub fn auth_header() -> impl Filter<Extract = (String,), Error = Rejection> + Clone {
31 warp::header::header::<String>("Authorization") 31 warp::header::<String>("Authorization")
32} 32}
33 33
34/// Extracts an `Block` JSON body from the request 34/// Extracts an `Block` JSON body from the request
diff --git a/src/error.rs b/src/error.rs
new file mode 100644
index 0000000..7339a06
--- /dev/null
+++ b/src/error.rs
@@ -0,0 +1,38 @@
1use log::warn;
2use serde::Serialize;
3use std::convert::Infallible;
4use warp::{http::StatusCode, Rejection, Reply};
5
6#[derive(Serialize)]
7struct ErrorResponse {
8 message: String,
9}
10
11pub async fn handle_rejection(err: Rejection) -> std::result::Result<impl Reply, Infallible> {
12 let code;
13 let message;
14
15 if err.is_not_found() {
16 code = StatusCode::NOT_FOUND;
17 message = "Requested resource is not found";
18 } else if let Some(_) = err.find::<warp::filters::body::BodyDeserializeError>() {
19 code = StatusCode::BAD_REQUEST;
20 message = "Error: JSON body is not formatted correctly, check your payload";
21 } else if let Some(_) = err.find::<warp::reject::MissingHeader>() {
22 code = StatusCode::METHOD_NOT_ALLOWED;
23 message = "Error: Authorization header missing, cannot authorize";
24 } else if let Some(_) = err.find::<warp::reject::MethodNotAllowed>() {
25 code = StatusCode::METHOD_NOT_ALLOWED;
26 message = "Error: method not allowed on this endpoint";
27 } else {
28 warn!("unhandled error: {:?}", err);
29 code = StatusCode::INTERNAL_SERVER_ERROR;
30 message = "Internal Server Error";
31 }
32
33 let json = warp::reply::json(&ErrorResponse {
34 message: message.to_owned(),
35 });
36
37 Ok(warp::reply::with_status(json, code))
38}
diff --git a/src/handlers.rs b/src/handlers.rs
index beae999..1189b35 100644
--- a/src/handlers.rs
+++ b/src/handlers.rs
@@ -5,10 +5,23 @@ use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation};
5use log::{debug, warn}; 5use log::{debug, warn};
6use md5::Md5; 6use md5::Md5;
7use parking_lot::RwLockUpgradableReadGuard; 7use parking_lot::RwLockUpgradableReadGuard;
8use serde::Serialize;
8use serde_json; 9use serde_json;
9use std::convert::Infallible; 10use std::convert::Infallible;
10use std::fs; 11use std::fs;
11use warp::{http::Response, http::StatusCode, reply}; 12use warp::{http::StatusCode, reply};
13
14#[derive(Serialize, Debug)]
15struct GradeCoinResponse {
16 res: ResponseType,
17 message: String,
18}
19
20#[derive(Debug, Serialize)]
21enum ResponseType {
22 Success,
23 Error,
24}
12 25
13use crate::schema::{AuthRequest, Block, Claims, Db, MetuId, NakedBlock, Transaction, User}; 26use crate::schema::{AuthRequest, Block, Claims, Db, MetuId, NakedBlock, Transaction, User};
14 27
@@ -24,45 +37,52 @@ pub async fn authenticate_user(
24 db: Db, 37 db: Db,
25) -> Result<impl warp::Reply, warp::Rejection> { 38) -> Result<impl warp::Reply, warp::Rejection> {
26 debug!("POST request to /register, authenticate_user"); 39 debug!("POST request to /register, authenticate_user");
27 let given_id = request.student_id.clone(); 40 let provided_id = request.student_id.clone();
28 41
29 if let Some(priv_student_id) = MetuId::new(request.student_id) { 42 let priv_student_id = match MetuId::new(request.student_id) {
30 let userlist = db.users.upgradable_read(); 43 Some(id) => id,
44 None => {
45 let res_json = warp::reply::json(&GradeCoinResponse {
46 res: ResponseType::Error,
47 message: "This user cannot have a gradecoin account".to_owned(),
48 });
31 49
32 if userlist.contains_key(&given_id) { 50 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
33 let res = Response::builder() 51 }
34 .status(StatusCode::BAD_REQUEST) 52 };
35 .body("This user is already authenticated");
36 53
37 Ok(res) 54 let userlist = db.users.upgradable_read();
38 } else {
39 let new_user = User {
40 user_id: priv_student_id,
41 public_key: request.public_key,
42 balance: 0,
43 };
44 55
45 let user_json = serde_json::to_string(&new_user).unwrap(); 56 if userlist.contains_key(&provided_id) {
57 let res_json = warp::reply::json(&GradeCoinResponse {
58 res: ResponseType::Error,
59 message: "This user is already authenticated".to_owned(),
60 });
46 61
47 fs::write(format!("users/{}.guy", new_user.user_id), user_json).unwrap(); 62 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
63 }
48 64
49 let mut userlist = RwLockUpgradableReadGuard::upgrade(userlist); 65 // TODO: audit public key, is it valid? <13-04-21, yigit> //
50 userlist.insert(given_id, new_user); 66 let new_user = User {
51 // TODO: signature of the public key, please <11-04-21, yigit> // 67 user_id: priv_student_id,
68 public_key: request.public_key,
69 balance: 0,
70 };
52 71
53 let res = Response::builder() 72 let user_json = serde_json::to_string(&new_user).unwrap();
54 .status(StatusCode::CREATED)
55 .body("Ready to use Gradecoin");
56 73
57 Ok(res) 74 fs::write(format!("users/{}.guy", new_user.user_id), user_json).unwrap();
58 }
59 } else {
60 let res = Response::builder()
61 .status(StatusCode::BAD_REQUEST)
62 .body("This user cannot have a gradecoin account");
63 75
64 Ok(res) 76 let mut userlist = RwLockUpgradableReadGuard::upgrade(userlist);
65 } 77 userlist.insert(provided_id, new_user);
78 // TODO: signature of the public key, please <11-04-21, yigit> //
79
80 let res_json = warp::reply::json(&GradeCoinResponse {
81 res: ResponseType::Success,
82 message: "User authenticated to use Gradecoin".to_owned(),
83 });
84
85 Ok(warp::reply::with_status(res_json, StatusCode::CREATED))
66} 86}
67 87
68/// GET /transaction 88/// GET /transaction
@@ -106,11 +126,17 @@ pub async fn authorized_propose_block(
106 Some(existing_user) => existing_user, 126 Some(existing_user) => existing_user,
107 None => { 127 None => {
108 debug!( 128 debug!(
109 "A user with public key signature {:?} is not found in the database", 129 "User with public key signature {:?} is not found in the database",
110 new_block.transaction_list[0] 130 new_block.transaction_list[0]
111 ); 131 );
112 // TODO: verbose error here <13-04-21, yigit> // 132
113 return Ok(StatusCode::BAD_REQUEST); 133 let res_json = warp::reply::json(&GradeCoinResponse {
134 res: ResponseType::Error,
135 message: "User with the given public key signature is not found in the database"
136 .to_owned(),
137 });
138
139 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
114 } 140 }
115 }; 141 };
116 142
@@ -120,7 +146,13 @@ pub async fn authorized_propose_block(
120 Ok(data) => data, 146 Ok(data) => data,
121 Err(below) => { 147 Err(below) => {
122 debug!("Something went wrong below {:?}", below); 148 debug!("Something went wrong below {:?}", below);
123 return Ok(StatusCode::BAD_REQUEST); 149
150 let res_json = warp::reply::json(&GradeCoinResponse {
151 res: ResponseType::Error,
152 message: below,
153 });
154
155 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
124 } 156 }
125 }; 157 };
126 158
@@ -131,7 +163,12 @@ pub async fn authorized_propose_block(
131 "The Hash of the block {:?} did not match the hash given in jwt {:?}", 163 "The Hash of the block {:?} did not match the hash given in jwt {:?}",
132 new_block.hash, token_payload.claims.tha 164 new_block.hash, token_payload.claims.tha
133 ); 165 );
134 return Ok(StatusCode::BAD_REQUEST); 166 let res_json = warp::reply::json(&GradeCoinResponse {
167 res: ResponseType::Error,
168 message: "The hash of the block did not match the hash given in JWT".to_owned(),
169 });
170
171 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
135 } 172 }
136 173
137 debug!("clear for block proposal"); 174 debug!("clear for block proposal");
@@ -140,7 +177,12 @@ pub async fn authorized_propose_block(
140 177
141 for transaction_hash in new_block.transaction_list.iter() { 178 for transaction_hash in new_block.transaction_list.iter() {
142 if !pending_transactions.contains_key(transaction_hash) { 179 if !pending_transactions.contains_key(transaction_hash) {
143 return Ok(StatusCode::BAD_REQUEST); 180 let res_json = warp::reply::json(&GradeCoinResponse {
181 res: ResponseType::Error,
182 message: "Block contains unknown transaction".to_owned(),
183 });
184
185 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
144 } 186 }
145 } 187 }
146 188
@@ -157,17 +199,28 @@ pub async fn authorized_propose_block(
157 199
158 // 6 rightmost bits are zero? 200 // 6 rightmost bits are zero?
159 let should_zero = hashvalue[31] as i32 + hashvalue[30] as i32 + hashvalue[29] as i32; 201 let should_zero = hashvalue[31] as i32 + hashvalue[30] as i32 + hashvalue[29] as i32;
202 // TODO: this can be offloaded to validator <13-04-21, yigit> //
160 203
161 if should_zero != 0 { 204 if should_zero != 0 {
162 debug!("the hash does not have 6 rightmost zero bits"); 205 debug!("the hash does not have 6 rightmost zero bits");
163 return Ok(StatusCode::BAD_REQUEST); 206 let res_json = warp::reply::json(&GradeCoinResponse {
207 res: ResponseType::Error,
208 message: "Given block hash is larger than target value".to_owned(),
209 });
210
211 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
164 } 212 }
165 213
166 // one last check to see if block is telling the truth 214 // one last check to see if block is telling the truth
167 if hash_string != new_block.hash { 215 if hash_string != new_block.hash {
168 debug!("request was not telling the truth, hash values do not match"); 216 debug!("request was not telling the truth, hash values do not match");
169 // TODO: does this condition make more sense _before_ the hash 0s check? <13-04-21, yigit> // 217 // TODO: does this condition make more sense _before_ the hash 0s check? <13-04-21, yigit> //
170 return Ok(StatusCode::BAD_REQUEST); 218 let res_json = warp::reply::json(&GradeCoinResponse {
219 res: ResponseType::Error,
220 message: "Given hash value does not match the actual block hash".to_owned(),
221 });
222
223 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
171 } 224 }
172 225
173 let mut blockchain = RwLockUpgradableReadGuard::upgrade(blockchain); 226 let mut blockchain = RwLockUpgradableReadGuard::upgrade(blockchain);
@@ -185,7 +238,13 @@ pub async fn authorized_propose_block(
185 let mut pending_transactions = RwLockUpgradableReadGuard::upgrade(pending_transactions); 238 let mut pending_transactions = RwLockUpgradableReadGuard::upgrade(pending_transactions);
186 pending_transactions.clear(); 239 pending_transactions.clear();
187 240
188 Ok(StatusCode::CREATED) 241 Ok(warp::reply::with_status(
242 warp::reply::json(&GradeCoinResponse {
243 res: ResponseType::Success,
244 message: "Block accepted".to_owned(),
245 }),
246 StatusCode::CREATED,
247 ))
189} 248}
190 249
191/// POST /transaction 250/// POST /transaction
@@ -213,11 +272,18 @@ pub async fn authorized_propose_transaction(
213 Some(existing_user) => existing_user, 272 Some(existing_user) => existing_user,
214 None => { 273 None => {
215 debug!( 274 debug!(
216 "A user with public key signature {:?} is not found in the database", 275 "User with public key signature {:?} is not found in the database",
217 new_transaction.by 276 new_transaction.by
218 ); 277 );
219 // TODO: verbose error here <13-04-21, yigit> // 278
220 return Ok(StatusCode::BAD_REQUEST); 279 return Ok(warp::reply::with_status(
280 warp::reply::json(&GradeCoinResponse {
281 res: ResponseType::Error,
282 message: "User with the given public key signature is not authorized"
283 .to_owned(),
284 }),
285 StatusCode::BAD_REQUEST,
286 ));
221 } 287 }
222 }; 288 };
223 289
@@ -231,7 +297,13 @@ pub async fn authorized_propose_transaction(
231 Ok(data) => data, 297 Ok(data) => data,
232 Err(below) => { 298 Err(below) => {
233 debug!("Something went wrong below {:?}", below); 299 debug!("Something went wrong below {:?}", below);
234 return Ok(StatusCode::BAD_REQUEST); 300 return Ok(warp::reply::with_status(
301 warp::reply::json(&GradeCoinResponse {
302 res: ResponseType::Error,
303 message: below,
304 }),
305 StatusCode::BAD_REQUEST,
306 ));
235 } 307 }
236 }; 308 };
237 309
@@ -246,14 +318,26 @@ pub async fn authorized_propose_transaction(
246 "the hash of the request {:x} did not match the hash given in jwt {:?}", 318 "the hash of the request {:x} did not match the hash given in jwt {:?}",
247 hashed_transaction, token_payload.claims.tha 319 hashed_transaction, token_payload.claims.tha
248 ); 320 );
249 return Ok(StatusCode::BAD_REQUEST); 321 return Ok(warp::reply::with_status(
322 warp::reply::json(&GradeCoinResponse {
323 res: ResponseType::Error,
324 message: "The hash of the block did not match the hash given in JWT".to_owned(),
325 }),
326 StatusCode::BAD_REQUEST,
327 ));
250 } 328 }
251 329
252 debug!("clear for transaction proposal"); 330 debug!("clear for transaction proposal");
253 331
254 let mut transactions = db.pending_transactions.write(); 332 let mut transactions = db.pending_transactions.write();
255 transactions.insert(new_transaction.source.to_owned(), new_transaction); 333 transactions.insert(new_transaction.source.to_owned(), new_transaction);
256 Ok(StatusCode::CREATED) 334 Ok(warp::reply::with_status(
335 warp::reply::json(&GradeCoinResponse {
336 res: ResponseType::Success,
337 message: "Transaction accepted".to_owned(),
338 }),
339 StatusCode::CREATED,
340 ))
257} 341}
258 342
259/// GET /block 343/// GET /block
@@ -273,15 +357,13 @@ pub async fn list_blocks(db: Db) -> Result<impl warp::Reply, Infallible> {
273/// *[`jwt_token`]: The raw JWT token, "Bearer aaa.bbb.ccc" 357/// *[`jwt_token`]: The raw JWT token, "Bearer aaa.bbb.ccc"
274/// *[`user_pem`]: User Public Key, "BEGIN RSA" 358/// *[`user_pem`]: User Public Key, "BEGIN RSA"
275/// NOT async, might look into it if this becomes a bottleneck 359/// NOT async, might look into it if this becomes a bottleneck
276fn authorize_proposer( 360fn authorize_proposer(jwt_token: String, user_pem: &String) -> Result<TokenData<Claims>, String> {
277 jwt_token: String,
278 user_pem: &String,
279) -> Result<TokenData<Claims>, jsonwebtoken::errors::Error> {
280 // Throw away the "Bearer " part 361 // Throw away the "Bearer " part
281 let raw_jwt = jwt_token.trim_start_matches(BEARER).to_owned(); 362 let raw_jwt = jwt_token.trim_start_matches(BEARER).to_owned();
282 debug!("raw_jwt: {:?}", raw_jwt); 363 debug!("raw_jwt: {:?}", raw_jwt);
283 364
284 // Extract a jsonwebtoken compatible decoding_key from user's public key 365 // Extract a jsonwebtoken compatible decoding_key from user's public key
366 // TODO: just use this for reading users pem key <13-04-21, yigit> //
285 let decoding_key = match DecodingKey::from_rsa_pem(user_pem.as_bytes()) { 367 let decoding_key = match DecodingKey::from_rsa_pem(user_pem.as_bytes()) {
286 Ok(key) => key, 368 Ok(key) => key,
287 Err(j) => { 369 Err(j) => {
@@ -289,7 +371,7 @@ fn authorize_proposer(
289 "user has invalid RSA key we should crash and burn here {:?}", 371 "user has invalid RSA key we should crash and burn here {:?}",
290 j 372 j
291 ); 373 );
292 return Err(j); 374 return Err(String::from("This User's RSA key is invalid"));
293 } 375 }
294 }; 376 };
295 377
@@ -299,23 +381,20 @@ fn authorize_proposer(
299 Ok(decoded) => decoded, 381 Ok(decoded) => decoded,
300 Err(err) => match *err.kind() { 382 Err(err) => match *err.kind() {
301 ErrorKind::InvalidToken => { 383 ErrorKind::InvalidToken => {
302 // TODO: verbose error here <13-04-21, yigit> //
303 debug!("raw_jwt={:?} was malformed err={:?}", raw_jwt, err); 384 debug!("raw_jwt={:?} was malformed err={:?}", raw_jwt, err);
304 return Err(err); 385 return Err(String::from("Invalid Token"));
305 } 386 }
306 ErrorKind::InvalidRsaKey => { 387 ErrorKind::InvalidRsaKey => {
307 // TODO: verbose error here <13-04-21, yigit> // 388 debug!("The RSA key does not have a valid format, {:?}", err);
308 debug!("the RSA key does not have a valid format, {:?}", err); 389 return Err(String::from("The RSA key does not have a valid format"));
309 return Err(err);
310 } 390 }
311 ErrorKind::ExpiredSignature => { 391 ErrorKind::ExpiredSignature => {
312 // TODO: verbose error here <13-04-21, yigit> //
313 debug!("this token has expired {:?}", err); 392 debug!("this token has expired {:?}", err);
314 return Err(err); 393 return Err(String::from("This token has expired"));
315 } 394 }
316 _ => { 395 _ => {
317 warn!("AN UNSPECIFIED ERROR: {:?}", err); 396 warn!("AN UNSPECIFIED ERROR: {:?}", err);
318 return Err(err); 397 return Err(String::from("Unspecified error"));
319 } 398 }
320 }, 399 },
321 }; 400 };
diff --git a/src/lib.rs b/src/lib.rs
index 6e51899..42def0f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -25,3 +25,4 @@ pub mod custom_filters;
25pub mod handlers; 25pub mod handlers;
26pub mod routes; 26pub mod routes;
27pub mod schema; 27pub mod schema;
28pub mod error;
diff --git a/tester.sh b/tester.sh
index 67b997a..44bd8de 100755
--- a/tester.sh
+++ b/tester.sh
@@ -26,6 +26,7 @@ curl --request POST \
26curl --request POST \ 26curl --request POST \
27 --header 'Content-Type: application/json' \ 27 --header 'Content-Type: application/json' \
28 --data '{ 28 --data '{
29 "by": "old_transaction_hash_1",
29 "source": "old_transaction_hash_1", 30 "source": "old_transaction_hash_1",
30 "target": "target_account", 31 "target": "target_account",
31 "amount": 20, 32 "amount": 20,
@@ -35,7 +36,7 @@ curl --request POST \
35 36
36## new transaction 37## new transaction
37curl --request POST \ 38curl --request POST \
38 --header 'Content-Type: application/json' \ 39 --header 'Content-Length: 36864' \
39 --data '{ 40 --data '{
40 "source": "old_transaction_hash_2", 41 "source": "old_transaction_hash_2",
41 "target": "target_account", 42 "target": "target_account",
@@ -47,7 +48,9 @@ curl --request POST \
47## new transaction 48## new transaction
48curl --request POST \ 49curl --request POST \
49 --header 'Content-Type: application/json' \ 50 --header 'Content-Type: application/json' \
51 --header 'Authorization: Bearer arstarst.arstarst.arstarst' \
50 --data '{ 52 --data '{
53 "by": "e254275",
51 "source": "old_transaction_hash_3", 54 "source": "old_transaction_hash_3",
52 "target": "target_account", 55 "target": "target_account",
53 "amount": 20, 56 "amount": 20,
@@ -60,6 +63,7 @@ printf "\n\nList of current transactions\n\n"
60curl localhost:8080/transaction 63curl localhost:8080/transaction
61 64
62curl --header "Content-Type: application/json" \ 65curl --header "Content-Type: application/json" \
66 --header "Authorization: aaa.bbb.ccc" \
63 --request POST \ 67 --request POST \
64 --data '{ 68 --data '{
65 "transaction_list": [ 69 "transaction_list": [
@@ -67,7 +71,7 @@ curl --header "Content-Type: application/json" \
67 "old_transaction_hash_2", 71 "old_transaction_hash_2",
68 "old_transaction_hash_3" 72 "old_transaction_hash_3"
69 ], 73 ],
70 "nonce": "not_a_thing_yet", 74 "nonce": 0,
71 "timestamp": "2021-04-08T12:30:30", 75 "timestamp": "2021-04-08T12:30:30",
72 "hash": "not_a_thing_yet" 76 "hash": "not_a_thing_yet"
73}' \ 77}' \