summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authoralpaylan2021-04-16 01:03:21 +0300
committeralpaylan2021-04-16 01:03:21 +0300
commitb4212a90caa899785402c06d57216e75de0f1c88 (patch)
tree8adbe6eb6b451eee20d181f26ab771e0c5a920ee /src
parent82864341afc78b23b358cd775c70ffbfa0d0303f (diff)
parent72f8ae422eeb03ed87c7819af5d5e25758267b03 (diff)
downloadgradecoin-b4212a90caa899785402c06d57216e75de0f1c88.tar.gz
gradecoin-b4212a90caa899785402c06d57216e75de0f1c88.tar.bz2
gradecoin-b4212a90caa899785402c06d57216e75de0f1c88.zip
Merge remote-tracking branch 'origin/main'
# Conflicts: # src/schema.rs
Diffstat (limited to 'src')
-rw-r--r--src/bin/main.rs3
-rw-r--r--src/error.rs38
-rw-r--r--src/handlers.rs223
-rw-r--r--src/lib.rs5
-rw-r--r--src/routes.rs11
-rw-r--r--src/schema.rs73
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]
11async fn main() { 11async 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 @@
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 a8c9947..fe60ded 100644
--- a/src/handlers.rs
+++ b/src/handlers.rs
@@ -1,6 +1,6 @@
1/// API handlers, the ends of each filter chain
2use aes::Aes128; 1use aes::Aes128;
3use base64; 2/// API handlers, the ends of each filter chain
3use askama::Template;
4use blake2::{Blake2s, Digest}; 4use blake2::{Blake2s, Digest};
5use block_modes::block_padding::Pkcs7; 5use block_modes::block_padding::Pkcs7;
6use block_modes::{BlockMode, Cbc}; 6use block_modes::{BlockMode, Cbc};
@@ -8,10 +8,8 @@ use jsonwebtoken::errors::ErrorKind;
8use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation}; 8use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation};
9use log::{debug, warn}; 9use log::{debug, warn};
10use md5::Md5; 10use md5::Md5;
11use parking_lot::RwLockUpgradableReadGuard;
12use rsa::{PaddingScheme, RSAPrivateKey}; 11use rsa::{PaddingScheme, RSAPrivateKey};
13use serde::Serialize; 12use serde::Serialize;
14use serde_json;
15use sha2::Sha256; 13use sha2::Sha256;
16use std::collections::HashMap; 14use std::collections::HashMap;
17use std::convert::Infallible; 15use std::convert::Infallible;
@@ -39,6 +37,7 @@ enum ResponseType {
39 37
40use crate::schema::{ 38use 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
44const BEARER: &str = "Bearer "; 43const 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
559fn authorize_proposer(jwt_token: String, user_pem: &String) -> Result<TokenData<Claims>, String> { 654fn 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")]
700struct WelcomeTemplate<'a> {
701 title: &'a str,
702 body: &'a str,
703}
704
705pub 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}
diff --git a/src/lib.rs b/src/lib.rs
index 7a24f9f..5442c6b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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
24pub mod custom_filters; 24pub mod custom_filters;
25pub mod error;
26pub mod handlers; 25pub mod handlers;
27pub mod routes; 26pub mod routes;
28pub mod schema; 27pub mod schema;
29 28
30pub const PRIVATE_KEY: &'static str = "-----BEGIN RSA PRIVATE KEY----- 29pub const PRIVATE_KEY: &str = "-----BEGIN RSA PRIVATE KEY-----
31MIIEogIBAAKCAQEAyGuqiCPGcguy+Y9TH7Bl7XlEsalyqb9bYlzpbV0dnqZ3lPkE 30MIIEogIBAAKCAQEAyGuqiCPGcguy+Y9TH7Bl7XlEsalyqb9bYlzpbV0dnqZ3lPkE
32PkuOhkN+GcuiV6iXtSwyh7nB+xTRXKJFRUBO/jbN8jfcxVwBu0JxjF3v1YRBxbOH 31PkuOhkN+GcuiV6iXtSwyh7nB+xTRXKJFRUBO/jbN8jfcxVwBu0JxjF3v1YRBxbOH
33hz2A295mbKD9xHQCKxkfYBNkUXxj8gd+GaDvQiSW5NdrX/lEkvqfGtdEX1m2+Hdc 32hz2A295mbKD9xHQCKxkfYBNkUXxj8gd+GaDvQiSW5NdrX/lEkvqfGtdEX1m2+Hdc
@@ -55,7 +54,7 @@ PDYHM9dfQ8xn51U0fTeaXjy/8Km8fyX2Jtxntlm6puyhSTJ8AX+FEgJkC4ajNEvA
55mJ1Gsy2fXKUyyZdI2b74MLqOpzr9cvS60tmTIScuiHFzg/SJgiA= 54mJ1Gsy2fXKUyyZdI2b74MLqOpzr9cvS60tmTIScuiHFzg/SJgiA=
56-----END RSA PRIVATE KEY-----"; 55-----END RSA PRIVATE KEY-----";
57 56
58pub const PUB_KEY: &'static str = "-----BEGIN PUBLIC KEY----- 57pub const PUB_KEY: &str = "-----BEGIN PUBLIC KEY-----
59MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyGuqiCPGcguy+Y9TH7Bl 58MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyGuqiCPGcguy+Y9TH7Bl
607XlEsalyqb9bYlzpbV0dnqZ3lPkEPkuOhkN+GcuiV6iXtSwyh7nB+xTRXKJFRUBO 597XlEsalyqb9bYlzpbV0dnqZ3lPkEPkuOhkN+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
9pub fn consensus_routes(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { 9pub 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
10use chrono::{NaiveDate, NaiveDateTime}; 10use chrono::{NaiveDate, NaiveDateTime};
11use lazy_static::lazy_static; 11use lazy_static::lazy_static;
12use log::debug;
12use parking_lot::RwLock; 13use parking_lot::RwLock;
13use serde::{Deserialize, Serialize}; 14use serde::{Deserialize, Serialize};
14use std::collections::{HashMap, HashSet}; 15use std::collections::{HashMap, HashSet};
@@ -23,8 +24,6 @@ use std::vec::Vec;
23 24
24pub type Fingerprint = String; 25pub type Fingerprint = String;
25 26
26// TODO: start by reading users from file too <14-04-21, yigit> //
27
28fn block_parser(path: String) -> u64 { 27fn 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
66fn create_db_with_last_block(path: String) -> Db { 65fn 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
73fn 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)]
84pub 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 89fn 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
76pub fn create_database() -> Db { 108pub 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 228impl 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)]
192pub struct User { 240pub 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)]
200pub struct MetuId { 248pub 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)]
215pub struct InitialAuthRequest { 263pub 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 {
265impl MetuId { 313impl 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 }