diff options
author | Yigit Sever | 2021-04-11 21:39:18 +0300 |
---|---|---|
committer | Yigit Sever | 2021-04-12 00:03:23 +0300 |
commit | 518a99a132707ba0e2572b24ca18f6b9606d7334 (patch) | |
tree | c7cbe183f088b286903389f58743b1e5688119c5 | |
parent | 217398c52c68b3b73454d3e9f66c85b5a1638f3f (diff) | |
download | gradecoin-518a99a132707ba0e2572b24ca18f6b9606d7334.tar.gz gradecoin-518a99a132707ba0e2572b24ca18f6b9606d7334.tar.bz2 gradecoin-518a99a132707ba0e2572b24ca18f6b9606d7334.zip |
Implement User handling and authentication
New struct: User, corresponds to a student
Blocks and users are persistent (written to a text file)
PostgreSQL would've been overkill, we have 30 students
AuthRequest is the representation for incoming register requests and
User is the inner representation
Students who are enrolled to the class are hardcoded, only they can
register new accounts
There are two new tests, one checks if a priviliged (=enrolled) user can
create an account and the other checks if a unpriviliged one cannot
There are quick verbose error messages that I'm not married to, might
move on to something better honestly
There's nothing stopping a malicious user to pre-register everyone with
mock public keys and effectively lock everyone out, what's a good secret
we can use?
-rw-r--r-- | Cargo.lock | 1 | ||||
-rw-r--r-- | Cargo.toml | 5 | ||||
-rw-r--r-- | src/custom_filters.rs | 12 | ||||
-rw-r--r-- | src/handlers.rs | 83 | ||||
-rw-r--r-- | src/main.rs | 2 | ||||
-rw-r--r-- | src/routes.rs | 80 | ||||
-rw-r--r-- | src/schema.rs | 95 | ||||
-rwxr-xr-x | tester.sh | 21 |
8 files changed, 257 insertions, 42 deletions
@@ -281,6 +281,7 @@ name = "gradecoin" | |||
281 | version = "0.1.0" | 281 | version = "0.1.0" |
282 | dependencies = [ | 282 | dependencies = [ |
283 | "chrono", | 283 | "chrono", |
284 | "lazy_static", | ||
284 | "log", | 285 | "log", |
285 | "parking_lot", | 286 | "parking_lot", |
286 | "pretty_env_logger", | 287 | "pretty_env_logger", |
@@ -14,7 +14,8 @@ chrono = { version = "0.4.10", features = ["serde"] } | |||
14 | log = "0.4.8" | 14 | log = "0.4.8" |
15 | pretty_env_logger = "0.3.1" | 15 | pretty_env_logger = "0.3.1" |
16 | parking_lot = "0.10.0" | 16 | parking_lot = "0.10.0" |
17 | serde_json = "1.0.59" | ||
18 | lazy_static = "1.4.0" | ||
17 | 19 | ||
18 | [dev-dependencies] | 20 | [dev-dependencies] |
19 | serde_json = "1.0.44" | 21 | serde_test = "1.0.117" |
20 | serde_test = "1.0.104" | ||
diff --git a/src/custom_filters.rs b/src/custom_filters.rs index 8c36d02..0806c6d 100644 --- a/src/custom_filters.rs +++ b/src/custom_filters.rs | |||
@@ -3,19 +3,27 @@ | |||
3 | use std::convert::Infallible; | 3 | use std::convert::Infallible; |
4 | use warp::{Filter, Rejection}; | 4 | use warp::{Filter, Rejection}; |
5 | 5 | ||
6 | use crate::schema::{Block, Db, Transaction}; | 6 | use crate::schema::{Block, Db, Transaction, AuthRequest}; |
7 | 7 | ||
8 | // Database context for routes | 8 | // Database context for routes |
9 | pub fn with_db(db: Db) -> impl Filter<Extract = (Db,), Error = Infallible> + Clone { | 9 | pub fn with_db(db: Db) -> impl Filter<Extract = (Db,), Error = Infallible> + Clone { |
10 | warp::any().map(move || db.clone()) | 10 | warp::any().map(move || db.clone()) |
11 | } | 11 | } |
12 | 12 | ||
13 | // Accept only json encoded User body and reject big payloads | ||
14 | // TODO: find a good limit for this, (=e2482057; 8 char String + rsa pem) <11-04-21, yigit> // | ||
15 | pub fn auth_request_json_body() -> impl Filter<Extract = (AuthRequest,), Error = Rejection> + Clone { | ||
16 | warp::body::content_length_limit(1024 * 32).and(warp::body::json()) | ||
17 | } | ||
18 | |||
13 | // Accept only json encoded Transaction body and reject big payloads | 19 | // Accept only json encoded Transaction body and reject big payloads |
20 | // TODO: find a good limit for this <11-04-21, yigit> // | ||
14 | pub fn transaction_json_body() -> impl Filter<Extract = (Transaction,), Error = Rejection> + Clone { | 21 | pub fn transaction_json_body() -> impl Filter<Extract = (Transaction,), Error = Rejection> + Clone { |
15 | warp::body::content_length_limit(1024 * 32).and(warp::body::json()) | 22 | warp::body::content_length_limit(1024 * 32).and(warp::body::json()) |
16 | } | 23 | } |
17 | 24 | ||
18 | // Accept only json encoded Transaction body and reject big payloads | 25 | // Accept only json encoded Block body and reject big payloads |
26 | // TODO: find a good limit for this <11-04-21, yigit> // | ||
19 | pub fn block_json_body() -> impl Filter<Extract = (Block,), Error = Rejection> + Clone { | 27 | pub fn block_json_body() -> impl Filter<Extract = (Block,), Error = Rejection> + Clone { |
20 | warp::body::content_length_limit(1024 * 32).and(warp::body::json()) | 28 | warp::body::content_length_limit(1024 * 32).and(warp::body::json()) |
21 | } | 29 | } |
diff --git a/src/handlers.rs b/src/handlers.rs index 856970d..bfd57bc 100644 --- a/src/handlers.rs +++ b/src/handlers.rs | |||
@@ -1,10 +1,62 @@ | |||
1 | /// API handlers, the ends of each filter chain | 1 | /// API handlers, the ends of each filter chain |
2 | use log::debug; // this is more useful than debug! learn how to use this | 2 | use log::debug; |
3 | use parking_lot::RwLockUpgradableReadGuard; | 3 | use parking_lot::RwLockUpgradableReadGuard; |
4 | use serde_json; | ||
4 | use std::convert::Infallible; | 5 | use std::convert::Infallible; |
5 | use warp::{http::StatusCode, reply}; | 6 | use warp::{http::Response, http::StatusCode, reply}; |
6 | 7 | ||
7 | use crate::schema::{Block, Db, Transaction}; | 8 | use std::fs; |
9 | |||
10 | use crate::schema::{AuthRequest, Block, Db, MetuId, Transaction, User}; | ||
11 | |||
12 | /// POST /register | ||
13 | /// Enables a student to introduce themselves to the system | ||
14 | /// Can fail | ||
15 | pub async fn authenticate_user( | ||
16 | request: AuthRequest, | ||
17 | db: Db, | ||
18 | ) -> Result<impl warp::Reply, warp::Rejection> { | ||
19 | let given_id = request.student_id.clone(); | ||
20 | |||
21 | if let Some(priv_student_id) = MetuId::new(request.student_id) { | ||
22 | let userlist = db.users.upgradable_read(); | ||
23 | |||
24 | if userlist.contains_key(&given_id) { | ||
25 | |||
26 | let res = Response::builder() | ||
27 | .status(StatusCode::BAD_REQUEST) | ||
28 | .body("This user is already authenticated"); | ||
29 | |||
30 | Ok(res) | ||
31 | } else { | ||
32 | let new_user = User { | ||
33 | user_id: priv_student_id, | ||
34 | public_key: request.public_key, | ||
35 | balance: 0, | ||
36 | }; | ||
37 | |||
38 | let user_json = serde_json::to_string(&new_user).unwrap(); | ||
39 | |||
40 | fs::write(format!("users/{}.guy", new_user.user_id), user_json).unwrap(); | ||
41 | |||
42 | let mut userlist = RwLockUpgradableReadGuard::upgrade(userlist); | ||
43 | userlist.insert(given_id, new_user); | ||
44 | // TODO: signature of the public key, please <11-04-21, yigit> // | ||
45 | |||
46 | let res = Response::builder() | ||
47 | .status(StatusCode::CREATED) | ||
48 | .body("Ready to use Gradecoin"); | ||
49 | |||
50 | Ok(res) | ||
51 | } | ||
52 | } else { | ||
53 | let res = Response::builder() | ||
54 | .status(StatusCode::BAD_REQUEST) | ||
55 | .body("This user cannot have a gradecoin account"); | ||
56 | |||
57 | Ok(res) | ||
58 | } | ||
59 | } | ||
8 | 60 | ||
9 | /// GET /transaction | 61 | /// GET /transaction |
10 | /// Returns JSON array of transactions | 62 | /// Returns JSON array of transactions |
@@ -28,16 +80,11 @@ pub async fn list_transactions(db: Db) -> Result<impl warp::Reply, Infallible> { | |||
28 | /// Cannot fail | 80 | /// Cannot fail |
29 | /// Mostly around for debug purposes | 81 | /// Mostly around for debug purposes |
30 | pub async fn list_blocks(db: Db) -> Result<impl warp::Reply, Infallible> { | 82 | pub async fn list_blocks(db: Db) -> Result<impl warp::Reply, Infallible> { |
31 | debug!("list all blocks"); | 83 | debug!("list all block"); |
32 | |||
33 | let mut result = Vec::new(); | ||
34 | let blocks = db.blockchain.read(); | ||
35 | 84 | ||
36 | for block in blocks.iter() { | 85 | let block = db.blockchain.read(); |
37 | result.push(block); | ||
38 | } | ||
39 | 86 | ||
40 | Ok(reply::with_status(reply::json(&result), StatusCode::OK)) | 87 | Ok(reply::with_status(reply::json(&*block), StatusCode::OK)) |
41 | } | 88 | } |
42 | 89 | ||
43 | /// POST /transaction | 90 | /// POST /transaction |
@@ -70,7 +117,7 @@ pub async fn propose_block(new_block: Block, db: Db) -> Result<impl warp::Reply, | |||
70 | let pending_transactions = db.pending_transactions.upgradable_read(); | 117 | let pending_transactions = db.pending_transactions.upgradable_read(); |
71 | let blockchain = db.blockchain.upgradable_read(); | 118 | let blockchain = db.blockchain.upgradable_read(); |
72 | 119 | ||
73 | // TODO: check 1, new_block.transaction_list from pending_transactions pool? <07-04-21, yigit> // | 120 | // check 1, new_block.transaction_list from pending_transactions pool? <07-04-21, yigit> // |
74 | for transaction_hash in new_block.transaction_list.iter() { | 121 | for transaction_hash in new_block.transaction_list.iter() { |
75 | if !pending_transactions.contains_key(transaction_hash) { | 122 | if !pending_transactions.contains_key(transaction_hash) { |
76 | return Ok(StatusCode::BAD_REQUEST); | 123 | return Ok(StatusCode::BAD_REQUEST); |
@@ -81,7 +128,17 @@ pub async fn propose_block(new_block: Block, db: Db) -> Result<impl warp::Reply, | |||
81 | // assume it is for now | 128 | // assume it is for now |
82 | 129 | ||
83 | let mut blockchain = RwLockUpgradableReadGuard::upgrade(blockchain); | 130 | let mut blockchain = RwLockUpgradableReadGuard::upgrade(blockchain); |
84 | blockchain.push(new_block); | 131 | |
132 | let block_json = serde_json::to_string(&new_block).unwrap(); | ||
133 | |||
134 | // let mut file = File::create(format!("{}.block", new_block.timestamp.timestamp())).unwrap(); | ||
135 | fs::write( | ||
136 | format!("blocks/{}.block", new_block.timestamp.timestamp()), | ||
137 | block_json, | ||
138 | ) | ||
139 | .unwrap(); | ||
140 | |||
141 | *blockchain = new_block; | ||
85 | 142 | ||
86 | let mut pending_transactions = RwLockUpgradableReadGuard::upgrade(pending_transactions); | 143 | let mut pending_transactions = RwLockUpgradableReadGuard::upgrade(pending_transactions); |
87 | pending_transactions.clear(); | 144 | pending_transactions.clear(); |
diff --git a/src/main.rs b/src/main.rs index 7ef2597..373223c 100644 --- a/src/main.rs +++ b/src/main.rs | |||
@@ -1,8 +1,8 @@ | |||
1 | use std::env; | 1 | use std::env; |
2 | use warp::Filter; | 2 | use warp::Filter; |
3 | 3 | ||
4 | mod custom_filters; | ||
5 | mod handlers; | 4 | mod handlers; |
5 | mod custom_filters; | ||
6 | mod routes; | 6 | mod routes; |
7 | mod schema; | 7 | mod schema; |
8 | // mod validators; | 8 | // mod validators; |
diff --git a/src/routes.rs b/src/routes.rs index 95138e6..9f0adc5 100644 --- a/src/routes.rs +++ b/src/routes.rs | |||
@@ -7,11 +7,21 @@ use crate::schema::Db; | |||
7 | /// Root, all routes combined | 7 | /// Root, all routes combined |
8 | pub fn consensus_routes(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { | 8 | pub fn consensus_routes(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { |
9 | transaction_list(db.clone()) | 9 | transaction_list(db.clone()) |
10 | .or(register_user(db.clone())) | ||
10 | .or(transaction_propose(db.clone())) | 11 | .or(transaction_propose(db.clone())) |
11 | .or(block_propose(db.clone())) | 12 | .or(block_propose(db.clone())) |
12 | .or(block_list(db.clone())) | 13 | .or(block_list(db.clone())) |
13 | } | 14 | } |
14 | 15 | ||
16 | /// POST /register warp route | ||
17 | pub fn register_user(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { | ||
18 | warp::path!("register") | ||
19 | .and(warp::post()) | ||
20 | .and(custom_filters::auth_request_json_body()) | ||
21 | .and(custom_filters::with_db(db)) | ||
22 | .and_then(handlers::authenticate_user) | ||
23 | } | ||
24 | |||
15 | /// GET /transaction warp route | 25 | /// GET /transaction warp route |
16 | pub fn transaction_list(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { | 26 | pub fn transaction_list(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { |
17 | warp::path!("transaction") | 27 | warp::path!("transaction") |
@@ -50,13 +60,13 @@ pub fn block_propose(db: Db) -> impl Filter<Extract = impl Reply, Error = Reject | |||
50 | mod tests { | 60 | mod tests { |
51 | use super::*; | 61 | use super::*; |
52 | 62 | ||
53 | use chrono::prelude::*; | 63 | // use chrono::prelude::*; |
54 | use parking_lot::RwLock; | 64 | // use parking_lot::RwLock; |
55 | use std::sync::Arc; | 65 | // use std::sync::Arc; |
56 | use warp::http::StatusCode; | 66 | use warp::http::StatusCode; |
57 | 67 | ||
58 | use crate::schema; | 68 | use crate::schema; |
59 | use crate::schema::{Block, Transaction}; | 69 | use crate::schema::{AuthRequest, Block, Transaction}; |
60 | 70 | ||
61 | /// Create a mock database to be used in tests | 71 | /// Create a mock database to be used in tests |
62 | fn mocked_db() -> Db { | 72 | fn mocked_db() -> Db { |
@@ -72,7 +82,7 @@ mod tests { | |||
72 | }, | 82 | }, |
73 | ); | 83 | ); |
74 | 84 | ||
75 | db.blockchain.write().push(Block { | 85 | *db.blockchain.write() = Block { |
76 | transaction_list: vec![ | 86 | transaction_list: vec![ |
77 | "old_transaction_hash_1".to_owned(), | 87 | "old_transaction_hash_1".to_owned(), |
78 | "old_transaction_hash_2".to_owned(), | 88 | "old_transaction_hash_2".to_owned(), |
@@ -81,11 +91,27 @@ mod tests { | |||
81 | nonce: "not_a_thing_yet".to_owned(), | 91 | nonce: "not_a_thing_yet".to_owned(), |
82 | timestamp: chrono::NaiveDate::from_ymd(2021, 04, 08).and_hms(12, 30, 30), | 92 | timestamp: chrono::NaiveDate::from_ymd(2021, 04, 08).and_hms(12, 30, 30), |
83 | hash: "not_a_thing_yet".to_owned(), | 93 | hash: "not_a_thing_yet".to_owned(), |
84 | }); | 94 | }; |
85 | 95 | ||
86 | db | 96 | db |
87 | } | 97 | } |
88 | 98 | ||
99 | /// Create a mock user that is allowed to be in gradecoin to be used in tests | ||
100 | fn priviliged_mocked_user() -> AuthRequest { | ||
101 | AuthRequest { | ||
102 | student_id: String::from("e254275"), | ||
103 | public_key: "NOT IMPLEMENTED".to_owned(), | ||
104 | } | ||
105 | } | ||
106 | |||
107 | /// Create a mock user that is NOT allowed to be in gradecoin to be used in tests | ||
108 | fn unpriviliged_mocked_user() -> AuthRequest { | ||
109 | AuthRequest { | ||
110 | student_id: String::from("foobarbaz"), | ||
111 | public_key: "NOT IMPLEMENTED".to_owned(), | ||
112 | } | ||
113 | } | ||
114 | |||
89 | /// Create a mock transaction to be used in tests | 115 | /// Create a mock transaction to be used in tests |
90 | fn mocked_transaction() -> Transaction { | 116 | fn mocked_transaction() -> Transaction { |
91 | Transaction { | 117 | Transaction { |
@@ -134,7 +160,7 @@ mod tests { | |||
134 | 160 | ||
135 | assert_eq!(res.status(), StatusCode::OK); | 161 | assert_eq!(res.status(), StatusCode::OK); |
136 | 162 | ||
137 | let expected_json_body = r#"[{"transaction_list":["old_transaction_hash_1","old_transaction_hash_2","old_transaction_hash_3"],"nonce":"not_a_thing_yet","timestamp":"2021-04-08T12:30:30","hash":"not_a_thing_yet"}]"#; | 163 | let expected_json_body = r#"{"transaction_list":["old_transaction_hash_1","old_transaction_hash_2","old_transaction_hash_3"],"nonce":"not_a_thing_yet","timestamp":"2021-04-08T12:30:30","hash":"not_a_thing_yet"}"#; |
138 | assert_eq!(res.body(), expected_json_body); | 164 | assert_eq!(res.body(), expected_json_body); |
139 | } | 165 | } |
140 | 166 | ||
@@ -175,6 +201,46 @@ mod tests { | |||
175 | assert_eq!(db.pending_transactions.read().len(), 2); | 201 | assert_eq!(db.pending_transactions.read().len(), 2); |
176 | } | 202 | } |
177 | 203 | ||
204 | /// TEST a POST request to /transaction, an endpoint that exists | ||
205 | /// https://tools.ietf.org/html/rfc7231#section-6.3.2 | ||
206 | /// Should accept the json request, create a new user and | ||
207 | /// add it to the user hashmap in the db | ||
208 | #[tokio::test] | ||
209 | async fn post_register_priviliged_user() { | ||
210 | let db = mocked_db(); | ||
211 | let filter = consensus_routes(db.clone()); | ||
212 | |||
213 | let res = warp::test::request() | ||
214 | .method("POST") | ||
215 | .json(&priviliged_mocked_user()) | ||
216 | .path("/register") | ||
217 | .reply(&filter) | ||
218 | .await; | ||
219 | |||
220 | println!("{:?}", res.body()); | ||
221 | assert_eq!(res.status(), StatusCode::CREATED); | ||
222 | assert_eq!(db.users.read().len(), 1); | ||
223 | } | ||
224 | /// TEST a POST request to /transaction, an endpoint that exists | ||
225 | /// https://tools.ietf.org/html/rfc7231#section-6.3.2 | ||
226 | /// Should NOT accept the json request | ||
227 | #[tokio::test] | ||
228 | async fn post_register_unpriviliged_user() { | ||
229 | let db = mocked_db(); | ||
230 | let filter = consensus_routes(db.clone()); | ||
231 | |||
232 | let res = warp::test::request() | ||
233 | .method("POST") | ||
234 | .json(&unpriviliged_mocked_user()) | ||
235 | .path("/register") | ||
236 | .reply(&filter) | ||
237 | .await; | ||
238 | |||
239 | println!("{:?}", res.body()); | ||
240 | assert_eq!(res.status(), StatusCode::BAD_REQUEST); | ||
241 | assert_eq!(db.users.read().len(), 0); | ||
242 | } | ||
243 | |||
178 | /// Test a POST request to /transaction, a resource that exists with a longer than expected | 244 | /// Test a POST request to /transaction, a resource that exists with a longer than expected |
179 | /// payload | 245 | /// payload |
180 | /// https://tools.ietf.org/html/rfc7231#section-6.5.11 | 246 | /// https://tools.ietf.org/html/rfc7231#section-6.5.11 |
diff --git a/src/schema.rs b/src/schema.rs index 556e625..909b5cd 100644 --- a/src/schema.rs +++ b/src/schema.rs | |||
@@ -1,41 +1,42 @@ | |||
1 | use chrono::NaiveDateTime; | 1 | use chrono::{NaiveDate, NaiveDateTime}; |
2 | use lazy_static::lazy_static; | ||
2 | use parking_lot::RwLock; | 3 | use parking_lot::RwLock; |
3 | use serde::{Deserialize, Serialize}; | 4 | use serde::{Deserialize, Serialize}; |
4 | use std::collections::HashMap; | 5 | use std::collections::{HashMap, HashSet}; |
6 | use std::fmt; | ||
7 | use std::fs; | ||
5 | use std::sync::Arc; | 8 | use std::sync::Arc; |
6 | 9 | ||
7 | // use crate::validators; | 10 | // use crate::validators; |
8 | 11 | ||
9 | // In memory data structure | 12 | /// We need persistence for blocks and users, not so much for transactions |
10 | // Two approaches here | 13 | /// There are around 30 students, a full fledged database would be an overkill (for next year?) |
11 | // 1. Db is a type pub type Db = Arc<RwLock<Vec<Ledger>>>; Ledger is a struct, we wrap the ledger | 14 | /// Pending transactions are held in memory, these are cleared with every new block |
12 | // with arc + mutex in ledger() to access transactions we need to unwrap blocks as well, vice | 15 | /// Only the last block is held in memory, every block is written to a file |
13 | // versa | 16 | /// Users are held in memory and they're also backed up to text files |
14 | // | ||
15 | // 2. Db is a struct attributes are wrapped we can offload ::new() to it's member method blocks and | ||
16 | // transactions are accessible separately, which is the biggest pro | ||
17 | // | ||
18 | // 3. use an actual database (for blockchain and users this makes the most sense tbh but pending | ||
19 | // transactions are perfectly fine in memory) | ||
20 | 17 | ||
21 | /// Creates a new database | 18 | /// Creates a new database connection |
22 | pub fn create_database() -> Db { | 19 | pub fn create_database() -> Db { |
20 | fs::create_dir_all("blocks").unwrap(); | ||
21 | fs::create_dir_all("users").unwrap(); | ||
23 | Db::new() | 22 | Db::new() |
24 | } | 23 | } |
25 | 24 | ||
26 | #[derive(Debug, Clone)] | 25 | #[derive(Debug, Clone)] |
27 | pub struct Db { | 26 | pub struct Db { |
28 | // heh. also https://doc.rust-lang.org/std/collections/struct.LinkedList.html says Vec is generally faster | 27 | // heh. also https://doc.rust-lang.org/std/collections/struct.LinkedList.html says Vec is generally faster |
29 | pub blockchain: Arc<RwLock<Vec<Block>>>, | 28 | pub blockchain: Arc<RwLock<Block>>, |
30 | // every proposer can have _one_ pending transaction, a way to enforce this, String is proposer identifier | 29 | // every proposer can have _one_ pending transaction, a way to enforce this, String is proposer identifier |
31 | pub pending_transactions: Arc<RwLock<HashMap<String, Transaction>>>, | 30 | pub pending_transactions: Arc<RwLock<HashMap<String, Transaction>>>, |
31 | pub users: Arc<RwLock<HashMap<String, User>>>, | ||
32 | } | 32 | } |
33 | 33 | ||
34 | impl Db { | 34 | impl Db { |
35 | fn new() -> Self { | 35 | fn new() -> Self { |
36 | Db { | 36 | Db { |
37 | blockchain: Arc::new(RwLock::new(Vec::new())), | 37 | blockchain: Arc::new(RwLock::new(Block::new())), |
38 | pending_transactions: Arc::new(RwLock::new(HashMap::new())), | 38 | pending_transactions: Arc::new(RwLock::new(HashMap::new())), |
39 | users: Arc::new(RwLock::new(HashMap::new())), | ||
39 | } | 40 | } |
40 | } | 41 | } |
41 | } | 42 | } |
@@ -43,6 +44,7 @@ impl Db { | |||
43 | /// A transaction between `source` and `target` that moves `amount` | 44 | /// A transaction between `source` and `target` that moves `amount` |
44 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] | 45 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] |
45 | pub struct Transaction { | 46 | pub struct Transaction { |
47 | // TODO: new field by <11-04-21, yigit> // | ||
46 | pub source: String, | 48 | pub source: String, |
47 | pub target: String, | 49 | pub target: String, |
48 | pub amount: i32, | 50 | pub amount: i32, |
@@ -65,5 +67,66 @@ pub struct Block { | |||
65 | pub hash: String, // future proof'd baby | 67 | pub hash: String, // future proof'd baby |
66 | } | 68 | } |
67 | 69 | ||
70 | impl Block { | ||
71 | /// Genesis block | ||
72 | pub fn new() -> Block { | ||
73 | Block { | ||
74 | transaction_list: vec![], | ||
75 | nonce: String::from(""), | ||
76 | timestamp: NaiveDate::from_ymd(2021, 04, 11).and_hms(20, 45, 00), | ||
77 | hash: String::from(""), | ||
78 | } | ||
79 | } | ||
80 | } | ||
81 | |||
82 | /// Or simply a Student | ||
83 | #[derive(Serialize, Deserialize, Debug)] | ||
84 | pub struct User { | ||
85 | pub user_id: MetuId, | ||
86 | pub public_key: String, | ||
87 | pub balance: i32, | ||
88 | } | ||
89 | |||
90 | /// The values will be hard coded so MetuId::new() can accept/reject values based on that | ||
91 | #[derive(Serialize, Deserialize, Debug)] | ||
92 | pub struct MetuId { | ||
93 | id: String, | ||
94 | } | ||
95 | |||
96 | #[derive(Serialize, Deserialize, Debug)] | ||
97 | pub struct AuthRequest { | ||
98 | pub student_id: String, | ||
99 | pub public_key: String, | ||
100 | } | ||
101 | |||
102 | lazy_static! { | ||
103 | static ref OUR_STUDENTS: HashSet<&'static str> = { | ||
104 | [ | ||
105 | "e254275", "e223687", "e211024", "e209888", "e223725", "e209362", "e209898", "e230995", | ||
106 | "e223743", "e223747", "e223749", "e223751", "e188126", "e209913", "e203608", "e233013", | ||
107 | "e216982", "e217185", "e223780", "e194931", "e223783", "e254550", "e217203", "e217477", | ||
108 | "e223786", "e231060", "e223795", | ||
109 | ] | ||
110 | .iter() | ||
111 | .cloned() | ||
112 | .collect() | ||
113 | }; | ||
114 | } | ||
115 | |||
116 | impl fmt::Display for MetuId { | ||
117 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
118 | write!(f, "{}", self.id) | ||
119 | } | ||
120 | } | ||
121 | |||
122 | impl MetuId { | ||
123 | pub fn new(id: String) -> Option<Self> { | ||
124 | if OUR_STUDENTS.contains(&*id) { | ||
125 | Some(MetuId { id: id }) | ||
126 | } else { | ||
127 | None | ||
128 | } | ||
129 | } | ||
130 | } | ||
68 | 131 | ||
69 | // TODO: write schema tests using the original repo <09-04-21, yigit> // | 132 | // TODO: write schema tests using the original repo <09-04-21, yigit> // |
@@ -2,6 +2,26 @@ | |||
2 | 2 | ||
3 | ## When in doubt just write a shell script | 3 | ## When in doubt just write a shell script |
4 | 4 | ||
5 | ## new registration request | ||
6 | ## should fail because foobar is not a student | ||
7 | curl --request POST \ | ||
8 | --header 'Content-Type: application/json' \ | ||
9 | --data '{ | ||
10 | "student_id": "foobar", | ||
11 | "public_key": "not_implemented_yet" | ||
12 | }' \ | ||
13 | http://localhost:8080/register | ||
14 | |||
15 | ## new registration request | ||
16 | ## should fail because foobar is not a student | ||
17 | curl --request POST \ | ||
18 | --header 'Content-Type: application/json' \ | ||
19 | --data '{ | ||
20 | "student_id": "e254275", | ||
21 | "public_key": "not_implemented_yet" | ||
22 | }' \ | ||
23 | http://localhost:8080/register | ||
24 | |||
5 | ## new transaction | 25 | ## new transaction |
6 | curl --request POST \ | 26 | curl --request POST \ |
7 | --header 'Content-Type: application/json' \ | 27 | --header 'Content-Type: application/json' \ |
@@ -72,4 +92,3 @@ curl localhost:8080/transaction | |||
72 | 92 | ||
73 | printf "\n\nShould have only one block\n\n" | 93 | printf "\n\nShould have only one block\n\n" |
74 | curl localhost:8080/block | 94 | curl localhost:8080/block |
75 | |||