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 /src/schema.rs | |
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?
Diffstat (limited to 'src/schema.rs')
-rw-r--r-- | src/schema.rs | 95 |
1 files changed, 79 insertions, 16 deletions
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> // |