diff options
Diffstat (limited to 'src/handlers.rs')
| -rw-r--r-- | src/handlers.rs | 128 |
1 files changed, 62 insertions, 66 deletions
diff --git a/src/handlers.rs b/src/handlers.rs index 07986f5..80ed1f7 100644 --- a/src/handlers.rs +++ b/src/handlers.rs | |||
| @@ -1,37 +1,28 @@ | |||
| 1 | /// API handlers, the ends of each filter chain | ||
| 1 | use blake2::{Blake2s, Digest}; | 2 | use blake2::{Blake2s, Digest}; |
| 2 | use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; | 3 | use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; |
| 3 | /// API handlers, the ends of each filter chain | ||
| 4 | use log::debug; | 4 | use log::debug; |
| 5 | use md5::Md5; | 5 | use md5::Md5; |
| 6 | use parking_lot::RwLockUpgradableReadGuard; | 6 | use parking_lot::RwLockUpgradableReadGuard; |
| 7 | use serde::{Deserialize, Serialize}; | ||
| 8 | use serde_json; | 7 | use serde_json; |
| 9 | use serde_json::json; | ||
| 10 | use std::convert::Infallible; | 8 | use std::convert::Infallible; |
| 11 | use std::fs; | 9 | use std::fs; |
| 12 | use warp::{http::Response, http::StatusCode, reject, reply}; | 10 | use warp::{http::Response, http::StatusCode, reply}; |
| 13 | 11 | ||
| 14 | use gradecoin::schema::{ | 12 | use crate::schema::{AuthRequest, Block, Claims, Db, MetuId, NakedBlock, Transaction, User}; |
| 15 | AuthRequest, Block, Db, MetuId, NakedBlock, PublicKeySignature, Transaction, User, | ||
| 16 | }; | ||
| 17 | 13 | ||
| 18 | const BEARER: &str = "Bearer "; | 14 | const BEARER: &str = "Bearer "; |
| 19 | 15 | ||
| 20 | /// tha: Transaction Hash, String | 16 | /// POST request to /register endpoint |
| 21 | /// iat: Issued At, Unix Time, epoch | 17 | /// |
| 22 | #[derive(Debug, Serialize, Deserialize)] | 18 | /// Lets a [`User`] (=student) to authenticate themselves to the system |
| 23 | pub struct Claims { | 19 | /// This `request` can be rejected if the payload is malformed (= not authenticated properly) or if |
| 24 | pub tha: String, | 20 | /// the [`AuthRequest.user_id`] of the `request` is not in the list of users that can hold a Gradecoin account |
| 25 | pub iat: usize, | ||
| 26 | } | ||
| 27 | |||
| 28 | /// POST /register | ||
| 29 | /// Enables a student to introduce themselves to the system | ||
| 30 | /// Can fail | ||
| 31 | pub async fn authenticate_user( | 21 | pub async fn authenticate_user( |
| 32 | request: AuthRequest, | 22 | request: AuthRequest, |
| 33 | db: Db, | 23 | db: Db, |
| 34 | ) -> Result<impl warp::Reply, warp::Rejection> { | 24 | ) -> Result<impl warp::Reply, warp::Rejection> { |
| 25 | debug!("POST request to /register, authenticate_user"); | ||
| 35 | let given_id = request.student_id.clone(); | 26 | let given_id = request.student_id.clone(); |
| 36 | 27 | ||
| 37 | if let Some(priv_student_id) = MetuId::new(request.student_id) { | 28 | if let Some(priv_student_id) = MetuId::new(request.student_id) { |
| @@ -77,7 +68,7 @@ pub async fn authenticate_user( | |||
| 77 | /// Returns JSON array of transactions | 68 | /// Returns JSON array of transactions |
| 78 | /// Cannot fail | 69 | /// Cannot fail |
| 79 | pub async fn list_transactions(db: Db) -> Result<impl warp::Reply, Infallible> { | 70 | pub async fn list_transactions(db: Db) -> Result<impl warp::Reply, Infallible> { |
| 80 | debug!("list all transactions"); | 71 | debug!("GET request to /transaction, list_transactions"); |
| 81 | let mut result = Vec::new(); | 72 | let mut result = Vec::new(); |
| 82 | 73 | ||
| 83 | let transactions = db.pending_transactions.read(); | 74 | let transactions = db.pending_transactions.read(); |
| @@ -95,31 +86,13 @@ pub async fn list_transactions(db: Db) -> Result<impl warp::Reply, Infallible> { | |||
| 95 | /// Cannot fail | 86 | /// Cannot fail |
| 96 | /// Mostly around for debug purposes | 87 | /// Mostly around for debug purposes |
| 97 | pub async fn list_blocks(db: Db) -> Result<impl warp::Reply, Infallible> { | 88 | pub async fn list_blocks(db: Db) -> Result<impl warp::Reply, Infallible> { |
| 98 | debug!("list all block"); | 89 | debug!("GET request to /block, list_blocks"); |
| 99 | 90 | ||
| 100 | let block = db.blockchain.read(); | 91 | let block = db.blockchain.read(); |
| 101 | 92 | ||
| 102 | Ok(reply::with_status(reply::json(&*block), StatusCode::OK)) | 93 | Ok(reply::with_status(reply::json(&*block), StatusCode::OK)) |
| 103 | } | 94 | } |
| 104 | 95 | ||
| 105 | /// POST /transaction | ||
| 106 | /// Pushes a new transaction for pending transaction pool | ||
| 107 | /// Can reject the transaction proposal | ||
| 108 | /// TODO: when is a new transaction rejected <07-04-21, yigit> // | ||
| 109 | pub async fn propose_transaction( | ||
| 110 | new_transaction: Transaction, | ||
| 111 | db: Db, | ||
| 112 | ) -> Result<impl warp::Reply, warp::Rejection> { | ||
| 113 | debug!("new transaction request {:?}", new_transaction); | ||
| 114 | |||
| 115 | // let mut transactions = db.lock().await; | ||
| 116 | let mut transactions = db.pending_transactions.write(); | ||
| 117 | |||
| 118 | transactions.insert(new_transaction.source.to_owned(), new_transaction); | ||
| 119 | |||
| 120 | Ok(StatusCode::CREATED) | ||
| 121 | } | ||
| 122 | |||
| 123 | /// POST /block | 96 | /// POST /block |
| 124 | /// Proposes a new block for the next round | 97 | /// Proposes a new block for the next round |
| 125 | /// Can reject the block | 98 | /// Can reject the block |
| @@ -181,41 +154,64 @@ pub async fn propose_block(new_block: Block, db: Db) -> Result<impl warp::Reply, | |||
| 181 | } | 154 | } |
| 182 | } | 155 | } |
| 183 | 156 | ||
| 157 | /// POST /transaction | ||
| 158 | /// | ||
| 159 | /// Handles the new transaction requests | ||
| 160 | /// Can reject the block if; | ||
| 161 | /// # Arguments | ||
| 162 | /// * `new_transaction` - Valid JSON of a [`Transaction`] | ||
| 163 | /// * `token` - An Authorization header value such as `Bearer aaa.bbb.ccc` | ||
| 164 | /// * `db` - Global [`Db`] instance | ||
| 165 | /// | ||
| 166 | /// TODO This method should check if the user has enough balance for the transaction | ||
| 184 | pub async fn auth_propose_transaction( | 167 | pub async fn auth_propose_transaction( |
| 185 | new_transaction: Transaction, | 168 | new_transaction: Transaction, |
| 186 | token: String, | 169 | token: String, |
| 187 | db: Db, | 170 | db: Db, |
| 188 | ) -> Result<impl warp::Reply, warp::Rejection> { | 171 | ) -> Result<impl warp::Reply, warp::Rejection> { |
| 189 | debug!("new transaction request {:?}", new_transaction); | 172 | debug!("POST request to /transaction, propose_transaction"); |
| 190 | let raw_jwt = token.trim_start_matches(BEARER).to_owned(); | 173 | debug!("The transaction request: {:?}", new_transaction); |
| 191 | 174 | ||
| 192 | let decoded = jsonwebtoken::decode::<Claims>( | 175 | let raw_jwt = token.trim_start_matches(BEARER).to_owned(); |
| 193 | &token, | 176 | debug!("raw_jwt: {:?}", raw_jwt); |
| 194 | &DecodingKey::from_rsa_pem( | 177 | |
| 195 | db.users | 178 | if let Some(user) = db.users.read().get(&new_transaction.by) { |
| 196 | .read() | 179 | // This public key was already written to the database, we can panic if it's not valid at |
| 197 | .get(&new_transaction.by) | 180 | // *this* point |
| 198 | .unwrap() | 181 | let by_public_key = &user.public_key; |
| 199 | .public_key | 182 | |
| 200 | .as_bytes(), | 183 | if let Ok(decoded) = decode::<Claims>( |
| 201 | ) | 184 | &raw_jwt, |
| 202 | .unwrap(), | 185 | &DecodingKey::from_rsa_pem(by_public_key.as_bytes()).unwrap(), |
| 203 | // todo@keles: If user is not found return user not found error | 186 | &Validation::new(Algorithm::RS256), |
| 204 | &Validation::new(Algorithm::PS256), | 187 | ) { |
| 205 | ) | 188 | // this transaction was already checked for correctness at custom_filters, we can panic |
| 206 | .unwrap(); | 189 | // here if it has been changed since |
| 207 | // todo: If user is found but header is not validated, return header not valid | 190 | |
| 208 | 191 | let hashed_transaction = Md5::digest(&serde_json::to_vec(&new_transaction).unwrap()); | |
| 209 | let hashed_transaction = Md5::digest(&serde_json::to_vec(&new_transaction).unwrap()); | 192 | |
| 210 | 193 | if decoded.claims.tha == format!("{:x}", hashed_transaction) { | |
| 211 | // let mut transactions = db.lock().await; | 194 | let mut transactions = db.pending_transactions.write(); |
| 212 | if decoded.claims.tha == format!("{:x}", hashed_transaction) { | 195 | |
| 213 | let mut transactions = db.pending_transactions.write(); | 196 | transactions.insert(new_transaction.source.to_owned(), new_transaction); |
| 214 | 197 | ||
| 215 | transactions.insert(new_transaction.source.to_owned(), new_transaction); | 198 | Ok(StatusCode::CREATED) |
| 216 | 199 | } else { | |
| 217 | Ok(StatusCode::CREATED) | 200 | debug!( |
| 201 | "the hash of the request {:x} did not match with the hash given in jwt {:?}", | ||
| 202 | hashed_transaction, decoded.claims.tha | ||
| 203 | ); | ||
| 204 | Ok(StatusCode::BAD_REQUEST) | ||
| 205 | } | ||
| 206 | } else { | ||
| 207 | debug!("raw_jwt was malformed {:?}", raw_jwt); | ||
| 208 | Ok(StatusCode::BAD_REQUEST) | ||
| 209 | } | ||
| 218 | } else { | 210 | } else { |
| 211 | debug!( | ||
| 212 | "A user with public key signature {:?} is not found in the database", | ||
| 213 | new_transaction.by | ||
| 214 | ); | ||
| 219 | Ok(StatusCode::BAD_REQUEST) | 215 | Ok(StatusCode::BAD_REQUEST) |
| 220 | } | 216 | } |
| 221 | } | 217 | } |
