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 | } |