diff options
| author | alpaylan | 2021-04-16 01:03:21 +0300 | 
|---|---|---|
| committer | alpaylan | 2021-04-16 01:03:21 +0300 | 
| commit | d248309f8595701a0fddd2462b963bcad55f18c8 (patch) | |
| tree | 109d4e2809f9f3392612e86ab3d5a47df5830b11 /src | |
| parent | 711d987b8e060682cf2215f25392415e206b3e8d (diff) | |
| parent | a1af17aad7c1308fc714a60595bae07cc8bb8a9a (diff) | |
| download | gradecoin-d248309f8595701a0fddd2462b963bcad55f18c8.tar.gz gradecoin-d248309f8595701a0fddd2462b963bcad55f18c8.tar.bz2 gradecoin-d248309f8595701a0fddd2462b963bcad55f18c8.zip | |
Merge remote-tracking branch 'origin/main'
# Conflicts:
#	src/schema.rs
Diffstat (limited to 'src')
| -rw-r--r-- | src/bin/main.rs | 3 | ||||
| -rw-r--r-- | src/error.rs | 38 | ||||
| -rw-r--r-- | src/handlers.rs | 223 | ||||
| -rw-r--r-- | src/lib.rs | 5 | ||||
| -rw-r--r-- | src/routes.rs | 11 | ||||
| -rw-r--r-- | src/schema.rs | 73 | 
6 files changed, 238 insertions, 115 deletions
| diff --git a/src/bin/main.rs b/src/bin/main.rs index 8b61e5c..882fdc6 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs | |||
| @@ -10,11 +10,10 @@ use gradecoin::schema::create_database; | |||
| 10 | #[tokio::main] | 10 | #[tokio::main] | 
| 11 | async fn main() { | 11 | async fn main() { | 
| 12 | // 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> // | ||
| 14 | if env::var_os("RUST_LOG").is_none() { | 13 | if env::var_os("RUST_LOG").is_none() { | 
| 15 | env::set_var("RUST_LOG", "gradecoin=debug"); | 14 | env::set_var("RUST_LOG", "gradecoin=debug"); | 
| 16 | } | 15 | } | 
| 17 | pretty_env_logger::init(); | 16 | log4rs::init_file("log.conf.yml", Default::default()).unwrap(); | 
| 18 | 17 | ||
| 19 | let db = create_database(); | 18 | let db = create_database(); | 
| 20 | 19 | ||
| diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index 7339a06..0000000 --- a/src/error.rs +++ /dev/null | |||
| @@ -1,38 +0,0 @@ | |||
| 1 | use log::warn; | ||
| 2 | use serde::Serialize; | ||
| 3 | use std::convert::Infallible; | ||
| 4 | use warp::{http::StatusCode, Rejection, Reply}; | ||
| 5 | |||
| 6 | #[derive(Serialize)] | ||
| 7 | struct ErrorResponse { | ||
| 8 | message: String, | ||
| 9 | } | ||
| 10 | |||
| 11 | pub 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 a8c9947..fe60ded 100644 --- a/src/handlers.rs +++ b/src/handlers.rs | |||
| @@ -1,6 +1,6 @@ | |||
| 1 | /// API handlers, the ends of each filter chain | ||
| 2 | use aes::Aes128; | 1 | use aes::Aes128; | 
| 3 | use base64; | 2 | /// API handlers, the ends of each filter chain | 
| 3 | use askama::Template; | ||
| 4 | use blake2::{Blake2s, Digest}; | 4 | use blake2::{Blake2s, Digest}; | 
| 5 | use block_modes::block_padding::Pkcs7; | 5 | use block_modes::block_padding::Pkcs7; | 
| 6 | use block_modes::{BlockMode, Cbc}; | 6 | use block_modes::{BlockMode, Cbc}; | 
| @@ -8,10 +8,8 @@ use jsonwebtoken::errors::ErrorKind; | |||
| 8 | use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation}; | 8 | use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation}; | 
| 9 | use log::{debug, warn}; | 9 | use log::{debug, warn}; | 
| 10 | use md5::Md5; | 10 | use md5::Md5; | 
| 11 | use parking_lot::RwLockUpgradableReadGuard; | ||
| 12 | use rsa::{PaddingScheme, RSAPrivateKey}; | 11 | use rsa::{PaddingScheme, RSAPrivateKey}; | 
| 13 | use serde::Serialize; | 12 | use serde::Serialize; | 
| 14 | use serde_json; | ||
| 15 | use sha2::Sha256; | 13 | use sha2::Sha256; | 
| 16 | use std::collections::HashMap; | 14 | use std::collections::HashMap; | 
| 17 | use std::convert::Infallible; | 15 | use std::convert::Infallible; | 
| @@ -39,6 +37,7 @@ enum ResponseType { | |||
| 39 | 37 | ||
| 40 | use crate::schema::{ | 38 | use crate::schema::{ | 
| 41 | AuthRequest, Block, Claims, Db, InitialAuthRequest, MetuId, NakedBlock, Transaction, User, | 39 | AuthRequest, Block, Claims, Db, InitialAuthRequest, MetuId, NakedBlock, Transaction, User, | 
| 40 | UserAtRest, | ||
| 42 | }; | 41 | }; | 
| 43 | 42 | ||
| 44 | const BEARER: &str = "Bearer "; | 43 | const BEARER: &str = "Bearer "; | 
| @@ -61,9 +60,9 @@ const BEARER: &str = "Bearer "; | |||
| 61 | /// public_key: "---BEGIN PUBLIC KEY..." | 60 | /// public_key: "---BEGIN PUBLIC KEY..." | 
| 62 | /// } | 61 | /// } | 
| 63 | /// | 62 | /// | 
| 64 | /// - Encrypts the serialized string of `auth_plaintext` with 128 bit block AES in CBC mode with Pkcs7 padding using the temporary key (`k_temp`), the result is `auth_ciphertext` TODO should this be base64'd? | 63 | /// - Encrypts the serialized string of `auth_plaintext` with 128 bit block AES in CBC mode with Pkcs7 padding using the temporary key (`k_temp`), the result is `auth_ciphertext` | 
| 65 | /// - The temporary key student has picked `k_temp` is encrypted using RSA with OAEP padding scheme | 64 | /// - The temporary key student has picked `k_temp` is encrypted using RSA with OAEP padding scheme | 
| 66 | /// using sha256 with `gradecoin_public_key` (TODO base64? same as above), giving us `key_ciphertext` | 65 | /// using sha256 with `gradecoin_public_key`, giving us `key_ciphertext` | 
| 67 | /// - The payload JSON object (`auth_request`) can be JSON serialized now: | 66 | /// - The payload JSON object (`auth_request`) can be JSON serialized now: | 
| 68 | /// { | 67 | /// { | 
| 69 | /// c: "auth_ciphertext" | 68 | /// c: "auth_ciphertext" | 
| @@ -92,7 +91,7 @@ pub async fn authenticate_user( | |||
| 92 | // Load our RSA Private Key as DER | 91 | // Load our RSA Private Key as DER | 
| 93 | let der_encoded = PRIVATE_KEY | 92 | let der_encoded = PRIVATE_KEY | 
| 94 | .lines() | 93 | .lines() | 
| 95 | .filter(|line| !line.starts_with("-")) | 94 | .filter(|line| !line.starts_with('-')) | 
| 96 | .fold(String::new(), |mut data, line| { | 95 | .fold(String::new(), |mut data, line| { | 
| 97 | data.push_str(&line); | 96 | data.push_str(&line); | 
| 98 | data | 97 | data | 
| @@ -104,18 +103,126 @@ pub async fn authenticate_user( | |||
| 104 | let gradecoin_private_key = RSAPrivateKey::from_pkcs1(&der_bytes).expect("failed to parse key"); | 103 | let gradecoin_private_key = RSAPrivateKey::from_pkcs1(&der_bytes).expect("failed to parse key"); | 
| 105 | 104 | ||
| 106 | let padding = PaddingScheme::new_oaep::<sha2::Sha256>(); | 105 | let padding = PaddingScheme::new_oaep::<sha2::Sha256>(); | 
| 107 | let temp_key = gradecoin_private_key | ||
| 108 | .decrypt(padding, &request.key.as_bytes()) | ||
| 109 | .expect("failed to decrypt"); | ||
| 110 | 106 | ||
| 111 | // decrypt c using key dec_key | 107 | let key_ciphertext = match base64::decode(&request.key) { | 
| 112 | let cipher = Aes128Cbc::new_var(&temp_key, &request.iv).unwrap(); | 108 | Ok(c) => c, | 
| 113 | let auth_plaintext = cipher | 109 | Err(err) => { | 
| 114 | .decrypt_vec(&base64::decode(request.c).unwrap()) | 110 | debug!( | 
| 115 | .unwrap(); | 111 | "The ciphertext of the key was not base64 encoded {}, {}", | 
| 112 | &request.key, err | ||
| 113 | ); | ||
| 114 | |||
| 115 | let res_json = warp::reply::json(&GradeCoinResponse { | ||
| 116 | res: ResponseType::Error, | ||
| 117 | message: "The ciphertext of the key was not base64 encoded {}, {}".to_owned(), | ||
| 118 | }); | ||
| 119 | |||
| 120 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | ||
| 121 | } | ||
| 122 | }; | ||
| 123 | |||
| 124 | let temp_key = match gradecoin_private_key.decrypt(padding, &key_ciphertext) { | ||
| 125 | Ok(k) => k, | ||
| 126 | Err(err) => { | ||
| 127 | debug!( | ||
| 128 | "Failed to decrypt ciphertext {:?}, {}", | ||
| 129 | &key_ciphertext, err | ||
| 130 | ); | ||
| 131 | |||
| 132 | let res_json = warp::reply::json(&GradeCoinResponse { | ||
| 133 | res: ResponseType::Error, | ||
| 134 | message: "Failed to decrypt the ciphertext of the temporary key".to_owned(), | ||
| 135 | }); | ||
| 136 | |||
| 137 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | ||
| 138 | } | ||
| 139 | }; | ||
| 140 | |||
| 141 | let cipher = match Aes128Cbc::new_var(&temp_key, &request.iv.as_bytes()) { | ||
| 142 | Ok(c) => c, | ||
| 143 | Err(err) => { | ||
| 144 | debug!( | ||
| 145 | "Could not create a cipher from temp_key and request.iv {:?}, {}, {}", | ||
| 146 | &temp_key, &request.iv, err | ||
| 147 | ); | ||
| 148 | |||
| 149 | let res_json = warp::reply::json(&GradeCoinResponse { | ||
| 150 | res: ResponseType::Error, | ||
| 151 | message: "Given IV has invalid length".to_owned(), | ||
| 152 | }); | ||
| 153 | |||
| 154 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | ||
| 155 | } | ||
| 156 | }; | ||
| 116 | 157 | ||
| 117 | let request: AuthRequest = | 158 | let auth_packet = match base64::decode(&request.c) { | 
| 118 | serde_json::from_str(&String::from_utf8(auth_plaintext).unwrap()).unwrap(); | 159 | Ok(a) => a, | 
| 160 | |||
| 161 | Err(err) => { | ||
| 162 | debug!( | ||
| 163 | "The auth_packet (c field) did not base64 decode {} {}", | ||
| 164 | &request.c, err | ||
| 165 | ); | ||
| 166 | |||
| 167 | let res_json = warp::reply::json(&GradeCoinResponse { | ||
| 168 | res: ResponseType::Error, | ||
| 169 | message: "The c field was not correctly base64 encoded".to_owned(), | ||
| 170 | }); | ||
| 171 | |||
| 172 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | ||
| 173 | } | ||
| 174 | }; | ||
| 175 | |||
| 176 | let auth_plaintext = match cipher.decrypt_vec(&auth_packet) { | ||
| 177 | Ok(p) => p, | ||
| 178 | Err(err) => { | ||
| 179 | debug!( | ||
| 180 | "Base64 decoded auth request did not decrypt correctly {:?} {}", | ||
| 181 | &auth_packet, err | ||
| 182 | ); | ||
| 183 | |||
| 184 | let res_json = warp::reply::json(&GradeCoinResponse { | ||
| 185 | res: ResponseType::Error, | ||
| 186 | message: "The Bas64 decoded auth request did not decrypt correctly".to_owned(), | ||
| 187 | }); | ||
| 188 | |||
| 189 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | ||
| 190 | } | ||
| 191 | }; | ||
| 192 | |||
| 193 | let utf8_auth_plaintext = match String::from_utf8(auth_plaintext.clone()) { | ||
| 194 | Ok(text) => text, | ||
| 195 | Err(err) => { | ||
| 196 | debug!( | ||
| 197 | "Auth plaintext did not convert into utf8 {:?} {}", | ||
| 198 | &auth_plaintext, err | ||
| 199 | ); | ||
| 200 | |||
| 201 | let res_json = warp::reply::json(&GradeCoinResponse { | ||
| 202 | res: ResponseType::Error, | ||
| 203 | message: "Auth plaintext couldn't get converted to UTF-8".to_owned(), | ||
| 204 | }); | ||
| 205 | |||
| 206 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | ||
| 207 | } | ||
| 208 | }; | ||
| 209 | |||
| 210 | let request: AuthRequest = match serde_json::from_str(&utf8_auth_plaintext) { | ||
| 211 | Ok(req) => req, | ||
| 212 | Err(err) => { | ||
| 213 | debug!( | ||
| 214 | "Auth plaintext did not serialize correctly {:?} {}", | ||
| 215 | &utf8_auth_plaintext, err | ||
| 216 | ); | ||
| 217 | |||
| 218 | let res_json = warp::reply::json(&GradeCoinResponse { | ||
| 219 | res: ResponseType::Error, | ||
| 220 | message: "The auth request JSON did not serialize correctly".to_owned(), | ||
| 221 | }); | ||
| 222 | |||
| 223 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | ||
| 224 | } | ||
| 225 | }; | ||
| 119 | 226 | ||
| 120 | let provided_id = request.student_id.clone(); | 227 | let provided_id = request.student_id.clone(); | 
| 121 | 228 | ||
| @@ -131,33 +238,24 @@ pub async fn authenticate_user( | |||
| 131 | } | 238 | } | 
| 132 | }; | 239 | }; | 
| 133 | 240 | ||
| 134 | let userlist = db.users.upgradable_read(); | 241 | { | 
| 135 | 242 | let userlist = db.users.read(); | |
| 136 | if userlist.contains_key(&provided_id) { | ||
| 137 | let res_json = warp::reply::json(&GradeCoinResponse { | ||
| 138 | res: ResponseType::Error, | ||
| 139 | message: | ||
| 140 | "This user is already authenticated, do you think this is a mistake? Contact me" | ||
| 141 | .to_owned(), | ||
| 142 | }); | ||
| 143 | |||
| 144 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | ||
| 145 | } | ||
| 146 | 243 | ||
| 147 | // We're using this as the validator | 244 | if userlist.contains_key(&provided_id) { | 
| 148 | // I hate myself | 245 | let res_json = warp::reply::json(&GradeCoinResponse { | 
| 149 | if let Err(_) = DecodingKey::from_rsa_pem(request.public_key.as_bytes()) { | 246 | res: ResponseType::Error, | 
| 150 | let res_json = warp::reply::json(&GradeCoinResponse { | 247 | message: | 
| 151 | res: ResponseType::Error, | 248 | "This user is already authenticated, do you think this is a mistake? Contact me" | 
| 152 | message: "The supplied RSA public key is not in valid PEM format".to_owned(), | 249 | .to_owned(), | 
| 153 | }); | 250 | }); | 
| 154 | 251 | ||
| 155 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | 252 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | 
| 253 | } | ||
| 156 | } | 254 | } | 
| 157 | 255 | ||
| 158 | // We're using this as the validator | 256 | // We're using this as the validator | 
| 159 | // I hate myself | 257 | // I hate myself | 
| 160 | if let Err(_) = DecodingKey::from_rsa_pem(request.public_key.as_bytes()) { | 258 | if DecodingKey::from_rsa_pem(request.public_key.as_bytes()).is_err() { | 
| 161 | let res_json = warp::reply::json(&GradeCoinResponse { | 259 | let res_json = warp::reply::json(&GradeCoinResponse { | 
| 162 | res: ResponseType::Error, | 260 | res: ResponseType::Error, | 
| 163 | message: "The supplied RSA public key is not in valid PEM format".to_owned(), | 261 | message: "The supplied RSA public key is not in valid PEM format".to_owned(), | 
| @@ -174,11 +272,19 @@ pub async fn authenticate_user( | |||
| 174 | balance: 0, | 272 | balance: 0, | 
| 175 | }; | 273 | }; | 
| 176 | 274 | ||
| 177 | let user_json = serde_json::to_string(&new_user).unwrap(); | 275 | let user_at_rest_json = serde_json::to_string(&UserAtRest { | 
| 276 | user: User { | ||
| 277 | user_id: new_user.user_id.clone(), | ||
| 278 | public_key: new_user.public_key.clone(), | ||
| 279 | balance: 0, | ||
| 280 | }, | ||
| 281 | fingerprint: fingerprint.clone(), | ||
| 282 | }) | ||
| 283 | .unwrap(); | ||
| 178 | 284 | ||
| 179 | fs::write(format!("users/{}.guy", new_user.user_id), user_json).unwrap(); | 285 | fs::write(format!("users/{}.guy", new_user.user_id), user_at_rest_json).unwrap(); | 
| 180 | 286 | ||
| 181 | let mut userlist = RwLockUpgradableReadGuard::upgrade(userlist); | 287 | let mut userlist = db.users.write(); | 
| 182 | 288 | ||
| 183 | userlist.insert(fingerprint.clone(), new_user); | 289 | userlist.insert(fingerprint.clone(), new_user); | 
| 184 | 290 | ||
| @@ -193,17 +299,6 @@ pub async fn authenticate_user( | |||
| 193 | Ok(warp::reply::with_status(res_json, StatusCode::CREATED)) | 299 | Ok(warp::reply::with_status(res_json, StatusCode::CREATED)) | 
| 194 | } | 300 | } | 
| 195 | 301 | ||
| 196 | // fn shed_pem_header_footer(maybe_key: String) -> Result<Vec<u8>, String> { | ||
| 197 | // let der_encoded = maybe_key | ||
| 198 | // .lines() | ||
| 199 | // .filter(|line| !line.starts_with("-")) | ||
| 200 | // .fold(String::new(), |mut data, line| { | ||
| 201 | // data.push_str(&line); | ||
| 202 | // data | ||
| 203 | // }); | ||
| 204 | // Ok(base64::decode(&der_encoded).expect("failed to decode base64 content")) | ||
| 205 | // } | ||
| 206 | |||
| 207 | /// GET /transaction | 302 | /// GET /transaction | 
| 208 | /// Returns JSON array of transactions | 303 | /// Returns JSON array of transactions | 
| 209 | /// Cannot fail | 304 | /// Cannot fail | 
| @@ -241,7 +336,7 @@ pub async fn authorized_propose_block( | |||
| 241 | 336 | ||
| 242 | println!("{:?}", &new_block); | 337 | println!("{:?}", &new_block); | 
| 243 | 338 | ||
| 244 | if new_block.transaction_list.len() < 1 { | 339 | if new_block.transaction_list.is_empty() { | 
| 245 | let res_json = warp::reply::json(&GradeCoinResponse { | 340 | let res_json = warp::reply::json(&GradeCoinResponse { | 
| 246 | res: ResponseType::Error, | 341 | res: ResponseType::Error, | 
| 247 | message: format!( | 342 | message: format!( | 
| @@ -322,8 +417,8 @@ pub async fn authorized_propose_block( | |||
| 322 | 417 | ||
| 323 | let naked_block = NakedBlock { | 418 | let naked_block = NakedBlock { | 
| 324 | transaction_list: new_block.transaction_list.clone(), | 419 | transaction_list: new_block.transaction_list.clone(), | 
| 325 | nonce: new_block.nonce.clone(), | 420 | nonce: new_block.nonce, | 
| 326 | timestamp: new_block.timestamp.clone(), | 421 | timestamp: new_block.timestamp, | 
| 327 | }; | 422 | }; | 
| 328 | 423 | ||
| 329 | let naked_block_flat = serde_json::to_vec(&naked_block).unwrap(); | 424 | let naked_block_flat = serde_json::to_vec(&naked_block).unwrap(); | 
| @@ -556,7 +651,7 @@ pub async fn list_blocks(db: Db) -> Result<impl warp::Reply, Infallible> { | |||
| 556 | /// *[`jwt_token`]: The raw JWT token, "Bearer aaa.bbb.ccc" | 651 | /// *[`jwt_token`]: The raw JWT token, "Bearer aaa.bbb.ccc" | 
| 557 | /// *[`user_pem`]: User Public Key, "BEGIN RSA" | 652 | /// *[`user_pem`]: User Public Key, "BEGIN RSA" | 
| 558 | /// NOT async, might look into it if this becomes a bottleneck | 653 | /// NOT async, might look into it if this becomes a bottleneck | 
| 559 | fn authorize_proposer(jwt_token: String, user_pem: &String) -> Result<TokenData<Claims>, String> { | 654 | fn authorize_proposer(jwt_token: String, user_pem: &str) -> Result<TokenData<Claims>, String> { | 
| 560 | // Throw away the "Bearer " part | 655 | // Throw away the "Bearer " part | 
| 561 | let raw_jwt = jwt_token.trim_start_matches(BEARER).to_owned(); | 656 | let raw_jwt = jwt_token.trim_start_matches(BEARER).to_owned(); | 
| 562 | debug!("raw_jwt: {:?}", raw_jwt); | 657 | debug!("raw_jwt: {:?}", raw_jwt); | 
| @@ -599,3 +694,19 @@ fn authorize_proposer(jwt_token: String, user_pem: &String) -> Result<TokenData< | |||
| 599 | 694 | ||
| 600 | Ok(token_payload) | 695 | Ok(token_payload) | 
| 601 | } | 696 | } | 
| 697 | |||
| 698 | #[derive(Template)] | ||
| 699 | #[template(path = "welcome.html")] | ||
| 700 | struct WelcomeTemplate<'a> { | ||
| 701 | title: &'a str, | ||
| 702 | body: &'a str, | ||
| 703 | } | ||
| 704 | |||
| 705 | pub async fn welcome_handler() -> Result<impl warp::Reply, warp::Rejection> { | ||
| 706 | let template = WelcomeTemplate { | ||
| 707 | title: "Welcome", | ||
| 708 | body: "To The Bookstore!", | ||
| 709 | }; | ||
| 710 | let res = template.render().unwrap(); | ||
| 711 | Ok(warp::reply::html(res)) | ||
| 712 | } | ||
| @@ -22,12 +22,11 @@ | |||
| 22 | //! `Authorization`: The request header should have Bearer JWT.Token signed with Student Public Key | 22 | //! `Authorization`: The request header should have Bearer JWT.Token signed with Student Public Key | 
| 23 | 23 | ||
| 24 | pub mod custom_filters; | 24 | pub mod custom_filters; | 
| 25 | pub mod error; | ||
| 26 | pub mod handlers; | 25 | pub mod handlers; | 
| 27 | pub mod routes; | 26 | pub mod routes; | 
| 28 | pub mod schema; | 27 | pub mod schema; | 
| 29 | 28 | ||
| 30 | pub const PRIVATE_KEY: &'static str = "-----BEGIN RSA PRIVATE KEY----- | 29 | pub const PRIVATE_KEY: &str = "-----BEGIN RSA PRIVATE KEY----- | 
| 31 | MIIEogIBAAKCAQEAyGuqiCPGcguy+Y9TH7Bl7XlEsalyqb9bYlzpbV0dnqZ3lPkE | 30 | MIIEogIBAAKCAQEAyGuqiCPGcguy+Y9TH7Bl7XlEsalyqb9bYlzpbV0dnqZ3lPkE | 
| 32 | PkuOhkN+GcuiV6iXtSwyh7nB+xTRXKJFRUBO/jbN8jfcxVwBu0JxjF3v1YRBxbOH | 31 | PkuOhkN+GcuiV6iXtSwyh7nB+xTRXKJFRUBO/jbN8jfcxVwBu0JxjF3v1YRBxbOH | 
| 33 | hz2A295mbKD9xHQCKxkfYBNkUXxj8gd+GaDvQiSW5NdrX/lEkvqfGtdEX1m2+Hdc | 32 | hz2A295mbKD9xHQCKxkfYBNkUXxj8gd+GaDvQiSW5NdrX/lEkvqfGtdEX1m2+Hdc | 
| @@ -55,7 +54,7 @@ PDYHM9dfQ8xn51U0fTeaXjy/8Km8fyX2Jtxntlm6puyhSTJ8AX+FEgJkC4ajNEvA | |||
| 55 | mJ1Gsy2fXKUyyZdI2b74MLqOpzr9cvS60tmTIScuiHFzg/SJgiA= | 54 | mJ1Gsy2fXKUyyZdI2b74MLqOpzr9cvS60tmTIScuiHFzg/SJgiA= | 
| 56 | -----END RSA PRIVATE KEY-----"; | 55 | -----END RSA PRIVATE KEY-----"; | 
| 57 | 56 | ||
| 58 | pub const PUB_KEY: &'static str = "-----BEGIN PUBLIC KEY----- | 57 | pub const PUB_KEY: &str = "-----BEGIN PUBLIC KEY----- | 
| 59 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyGuqiCPGcguy+Y9TH7Bl | 58 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyGuqiCPGcguy+Y9TH7Bl | 
| 60 | 7XlEsalyqb9bYlzpbV0dnqZ3lPkEPkuOhkN+GcuiV6iXtSwyh7nB+xTRXKJFRUBO | 59 | 7XlEsalyqb9bYlzpbV0dnqZ3lPkEPkuOhkN+GcuiV6iXtSwyh7nB+xTRXKJFRUBO | 
| 61 | /jbN8jfcxVwBu0JxjF3v1YRBxbOHhz2A295mbKD9xHQCKxkfYBNkUXxj8gd+GaDv | 60 | /jbN8jfcxVwBu0JxjF3v1YRBxbOHhz2A295mbKD9xHQCKxkfYBNkUXxj8gd+GaDv | 
| diff --git a/src/routes.rs b/src/routes.rs index 280de35..52d357a 100644 --- a/src/routes.rs +++ b/src/routes.rs | |||
| @@ -7,11 +7,19 @@ use crate::schema::Db; | |||
| 7 | 7 | ||
| 8 | /// Every route combined | 8 | /// Every route combined | 
| 9 | pub fn consensus_routes(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { | 9 | pub fn consensus_routes(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { | 
| 10 | // Remember when we wanted to implement templating | ||
| 11 | // Why would we? Just put a staic webpage under /public (next to Cargo.toml) and place it and | ||
| 12 | // the end of the filter chain | ||
| 13 | |||
| 14 | // Fully fledged website support, phew! | ||
| 15 | let static_route = warp::any().and(warp::fs::dir("public")); | ||
| 16 | |||
| 10 | transaction_list(db.clone()) | 17 | transaction_list(db.clone()) | 
| 11 | .or(register_user(db.clone())) | 18 | .or(register_user(db.clone())) | 
| 12 | .or(auth_transaction_propose(db.clone())) | 19 | .or(auth_transaction_propose(db.clone())) | 
| 13 | .or(auth_block_propose(db.clone())) | 20 | .or(auth_block_propose(db.clone())) | 
| 14 | .or(block_list(db.clone())) | 21 | .or(block_list(db)) | 
| 22 | .or(static_route) | ||
| 15 | } | 23 | } | 
| 16 | 24 | ||
| 17 | /// POST /register warp route | 25 | /// POST /register warp route | 
| @@ -60,4 +68,3 @@ pub fn auth_block_propose(db: Db) -> impl Filter<Extract = impl Reply, Error = R | |||
| 60 | .and(custom_filters::with_db(db)) | 68 | .and(custom_filters::with_db(db)) | 
| 61 | .and_then(handlers::authorized_propose_block) | 69 | .and_then(handlers::authorized_propose_block) | 
| 62 | } | 70 | } | 
| 63 | |||
| diff --git a/src/schema.rs b/src/schema.rs index 264d2bd..cb353e4 100644 --- a/src/schema.rs +++ b/src/schema.rs | |||
| @@ -9,6 +9,7 @@ | |||
| 9 | //! Users are held in memory and they're also backed up to text files | 9 | //! Users are held in memory and they're also backed up to text files | 
| 10 | use chrono::{NaiveDate, NaiveDateTime}; | 10 | use chrono::{NaiveDate, NaiveDateTime}; | 
| 11 | use lazy_static::lazy_static; | 11 | use lazy_static::lazy_static; | 
| 12 | use log::debug; | ||
| 12 | use parking_lot::RwLock; | 13 | use parking_lot::RwLock; | 
| 13 | use serde::{Deserialize, Serialize}; | 14 | use serde::{Deserialize, Serialize}; | 
| 14 | use std::collections::{HashMap, HashSet}; | 15 | use std::collections::{HashMap, HashSet}; | 
| @@ -23,8 +24,6 @@ use std::vec::Vec; | |||
| 23 | 24 | ||
| 24 | pub type Fingerprint = String; | 25 | pub type Fingerprint = String; | 
| 25 | 26 | ||
| 26 | // TODO: start by reading users from file too <14-04-21, yigit> // | ||
| 27 | |||
| 28 | fn block_parser(path: String) -> u64 { | 27 | fn block_parser(path: String) -> u64 { | 
| 29 | let end_pos = path.find(".block").unwrap(); | 28 | let end_pos = path.find(".block").unwrap(); | 
| 30 | let block_str = path[9..end_pos].to_string(); | 29 | let block_str = path[9..end_pos].to_string(); | 
| @@ -63,16 +62,49 @@ fn read_block_name() -> io::Result<Vec<PathBuf>> { | |||
| 63 | Ok(entries) | 62 | Ok(entries) | 
| 64 | } | 63 | } | 
| 65 | 64 | ||
| 66 | fn create_db_with_last_block(path: String) -> Db { | 65 | fn read_users() -> io::Result<Vec<PathBuf>> { | 
| 66 | let entries = fs::read_dir("./users")? | ||
| 67 | .map(|res| res.map(|e| e.path())) | ||
| 68 | .collect::<Result<Vec<_>, io::Error>>()?; | ||
| 69 | |||
| 70 | Ok(entries) | ||
| 71 | } | ||
| 72 | |||
| 73 | fn populate_db_with_last_block(db: &mut Db, path: String) -> &mut Db { | ||
| 74 | debug!("Populating db with last block {}", path); | ||
| 67 | let file = fs::read(path).unwrap(); | 75 | let file = fs::read(path).unwrap(); | 
| 68 | let json = std::str::from_utf8(&file).unwrap(); | 76 | let json = std::str::from_utf8(&file).unwrap(); | 
| 69 | let block: Block = serde_json::from_str(json).unwrap(); | 77 | let block: Block = serde_json::from_str(json).unwrap(); | 
| 70 | let db = Db::new(); | ||
| 71 | *db.blockchain.write() = block; | 78 | *db.blockchain.write() = block; | 
| 72 | return db; | 79 | |
| 80 | db | ||
| 81 | } | ||
| 82 | |||
| 83 | #[derive(Debug, Serialize, Deserialize, PartialEq)] | ||
| 84 | pub struct UserAtRest { | ||
| 85 | pub fingerprint: Fingerprint, | ||
| 86 | pub user: User, | ||
| 73 | } | 87 | } | 
| 74 | 88 | ||
| 75 | /// Creates a new database, uses the previous last block if one exists | 89 | fn populate_db_with_users(db: &mut Db, files: Vec<PathBuf>) -> &mut Db { | 
| 90 | for fs in files { | ||
| 91 | if let Ok(file_content) = fs::read(fs) { | ||
| 92 | let json = | ||
| 93 | String::from_utf8(file_content).expect("we have written a malformed user file"); | ||
| 94 | let user_at_rest: UserAtRest = serde_json::from_str(&json).unwrap(); | ||
| 95 | |||
| 96 | debug!("Populating db with user: {:?}", user_at_rest); | ||
| 97 | db.users | ||
| 98 | .write() | ||
| 99 | .insert(user_at_rest.fingerprint, user_at_rest.user); | ||
| 100 | } | ||
| 101 | } | ||
| 102 | |||
| 103 | db | ||
| 104 | } | ||
| 105 | |||
| 106 | /// Creates a new database, uses the previous last block if one exists and attempts the populate | ||
| 107 | /// the users | ||
| 76 | pub fn create_database() -> Db { | 108 | pub fn create_database() -> Db { | 
| 77 | fs::create_dir_all("blocks").unwrap(); | 109 | fs::create_dir_all("blocks").unwrap(); | 
| 78 | fs::create_dir_all("users").unwrap(); | 110 | fs::create_dir_all("users").unwrap(); | 
| @@ -82,6 +114,12 @@ pub fn create_database() -> Db { | |||
| 82 | } else { | 114 | } else { | 
| 83 | return Db::new(); | 115 | return Db::new(); | 
| 84 | } | 116 | } | 
| 117 | |||
| 118 | if let Ok(users_path) = read_users() { | ||
| 119 | populate_db_with_users(&mut db, users_path); | ||
| 120 | } | ||
| 121 | |||
| 122 | db | ||
| 85 | } | 123 | } | 
| 86 | 124 | ||
| 87 | /// A JWT Payload/Claims representation | 125 | /// A JWT Payload/Claims representation | 
| @@ -181,13 +219,23 @@ impl Block { | |||
| 181 | Block { | 219 | Block { | 
| 182 | transaction_list: vec!["gradecoin_bank".to_owned()], | 220 | transaction_list: vec!["gradecoin_bank".to_owned()], | 
| 183 | nonce: 0, | 221 | nonce: 0, | 
| 184 | timestamp: NaiveDate::from_ymd(2021, 04, 11).and_hms(20, 45, 00), | 222 | timestamp: NaiveDate::from_ymd(2021, 4, 11).and_hms(20, 45, 00), | 
| 185 | hash: String::from("not_actually_mined"), | 223 | hash: String::from("not_actually_mined"), | 
| 186 | } | 224 | } | 
| 187 | } | 225 | } | 
| 188 | } | 226 | } | 
| 189 | 227 | ||
| 190 | /// Simply a Student | 228 | impl Default for Block { | 
| 229 | fn default() -> Self { | ||
| 230 | Self::new() | ||
| 231 | } | ||
| 232 | } | ||
| 233 | |||
| 234 | /// A Student | ||
| 235 | /// | ||
| 236 | /// * [`user_id`]: Can only be one of the repopulated | ||
| 237 | /// * [`public_key`]: A PEM format public key "---- BEGIN" and all | ||
| 238 | /// * [`balance`]: User's current Gradecoin amount | ||
| 191 | #[derive(Serialize, Deserialize, Debug, PartialEq)] | 239 | #[derive(Serialize, Deserialize, Debug, PartialEq)] | 
| 192 | pub struct User { | 240 | pub struct User { | 
| 193 | pub user_id: MetuId, | 241 | pub user_id: MetuId, | 
| @@ -196,7 +244,7 @@ pub struct User { | |||
| 196 | } | 244 | } | 
| 197 | 245 | ||
| 198 | /// The values are hard coded in [`OUR_STUDENTS`] so MetuId::new() can accept/reject values based on that | 246 | /// The values are hard coded in [`OUR_STUDENTS`] so MetuId::new() can accept/reject values based on that | 
| 199 | #[derive(Serialize, Deserialize, Debug, PartialEq)] | 247 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] | 
| 200 | pub struct MetuId { | 248 | pub struct MetuId { | 
| 201 | id: String, | 249 | id: String, | 
| 202 | passwd: String, | 250 | passwd: String, | 
| @@ -214,7 +262,7 @@ pub struct AuthRequest { | |||
| 214 | #[derive(Serialize, Deserialize, Debug)] | 262 | #[derive(Serialize, Deserialize, Debug)] | 
| 215 | pub struct InitialAuthRequest { | 263 | pub struct InitialAuthRequest { | 
| 216 | pub c: String, | 264 | pub c: String, | 
| 217 | pub iv: [u8; 32], | 265 | pub iv: String, | 
| 218 | pub key: String, | 266 | pub key: String, | 
| 219 | } | 267 | } | 
| 220 | 268 | ||
| @@ -265,10 +313,7 @@ impl fmt::Display for MetuId { | |||
| 265 | impl MetuId { | 313 | impl MetuId { | 
| 266 | pub fn new(id: String, pwd: String) -> Option<Self> { | 314 | pub fn new(id: String, pwd: String) -> Option<Self> { | 
| 267 | if OUR_STUDENTS.contains(&(&*id, &*pwd)) { | 315 | if OUR_STUDENTS.contains(&(&*id, &*pwd)) { | 
| 268 | Some(MetuId { | 316 | Some(MetuId { id, passwd: pwd }) | 
| 269 | id: id, | ||
| 270 | passwd: pwd, | ||
| 271 | }) | ||
| 272 | } else { | 317 | } else { | 
| 273 | None | 318 | None | 
| 274 | } | 319 | } | 
