aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/auth.rs112
-rw-r--r--src/error.rs63
-rw-r--r--src/handlers.rs18
-rw-r--r--src/main.rs2
-rw-r--r--src/routes.rs38
-rw-r--r--src/schema.rs14
6 files changed, 242 insertions, 5 deletions
diff --git a/src/auth.rs b/src/auth.rs
new file mode 100644
index 0000000..e22262c
--- /dev/null
+++ b/src/auth.rs
@@ -0,0 +1,112 @@
1use crate::error::Error;
2use crate::schema::{Db, Transaction};
3use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
4use serde::{Deserialize, Serialize};
5use warp::header::headers_cloned;
6use warp::http::header::{HeaderMap, HeaderValue, AUTHORIZATION};
7use warp::{reject, Filter, Rejection};
8use thiserror::Error;
9use anyhow::*;
10
11const BEARER: &str = "Bearer ";
12const PUBLIC_KEY_PEM: &str = "-----BEGIN PUBLIC KEY-----
13MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4nU0G4WjkmcQUx0hq6LQ
14uV5Q+ACmUFL/OjoYMDwC/O/6pCd1UZgCfgHN2xEffDPznzcTn8OiFRxr4oWyBiny
15rUpnY4mhy0SQUwoeCw7YkcHAyhCjNT74aR/ohX0MCj0qRRdbt5ZQXM/GC3HJuXE1
16ptSuhFgQxziItamn8maoJ6JUSVEXVO1NOrrjoM3r7Q+BK2B+sX4/bLZ+VG5g1q2n
17EbFdTHS6pHqtZNHQndTmEKwRfh0RYtzEzOXuO6e1gQY42Tujkof40dhGCIU7TeIG
18GHwdFxy1niLkXwtHNjV7lnIOkTbx6+sSPamRfQAlZqUWM2Lf5o+7h3qWP3ENB138
19sQIDAQAB
20-----END PUBLIC KEY-----";
21
22// const private_key_pem: &str = "-----BEGIN RSA PRIVATE KEY-----
23// MIIEpAIBAAKCAQEA4nU0G4WjkmcQUx0hq6LQuV5Q+ACmUFL/OjoYMDwC/O/6pCd1
24// UZgCfgHN2xEffDPznzcTn8OiFRxr4oWyBinyrUpnY4mhy0SQUwoeCw7YkcHAyhCj
25// NT74aR/ohX0MCj0qRRdbt5ZQXM/GC3HJuXE1ptSuhFgQxziItamn8maoJ6JUSVEX
26// VO1NOrrjoM3r7Q+BK2B+sX4/bLZ+VG5g1q2nEbFdTHS6pHqtZNHQndTmEKwRfh0R
27// YtzEzOXuO6e1gQY42Tujkof40dhGCIU7TeIGGHwdFxy1niLkXwtHNjV7lnIOkTbx
28// 6+sSPamRfQAlZqUWM2Lf5o+7h3qWP3ENB138sQIDAQABAoIBAD23nYTmrganag6M
29// wPFrBSGP79c3Lhx0EjUHQjJbGKFgsdltG48qM3ut+DF9ACy0Z+/7bbC7+39vaIOq
30// 1jLR2d6aiYTaLKseO4s2FawD1sgamvU3BZPsXn0gAhnnU5Gyy8Nas1dccvhoc9wI
31// neaZUPrvucQ90AzLfo6r9yacDbYHB1lOyomApUvpJxOgHISGEtc9qGPDrdH19aF0
32// 8fCv2bbQRh+TChgN3IB0o5w0wXaI7YAyAouAv/AzHCoEMpt7OGjFTkjh/ujlPL9O
33// +FLuJNsQRHDN0gJo2pcvwGwDCsioMixQ9bZ7ZrUu2BNpEQygyeSbj9ZI1iRvhosO
34// JU3rwEECgYEA9MppTYA6A9WQbCCwPH1QMpUAmPNVSWVhUVag4lGOEhdCDRcz9ook
35// DohQMKctiEB1luKuvDokxo0uMOfMO9/YwjsRB7qjQip7Th1zMJIjD+A+juLzHK4r
36// /RiRtWYGAnF8mptDvE+93JsPb3C/lQLvIhio5GQYWBqPJu6SpeosIskCgYEA7NPi
37// Gbffzr2UQhW8BNKmctEEh8yFRVojFo3wwwWxSNUVXGSmSm31CL+Q8h817R+2OkPV
38// 1ZMUOBU4UJiqFt28kIvTDFqbAJlJQGCpY2mY7OLQiD2A+TVLcFrHmoCaPfCAK1Qd
39// hQ0PmFK7Mf8qClpA3E5chop/WfKQfiu46sZv1qkCgYAhGdXPcw1lQ1W6KVlrdI6J
40// qHhiNlVMDXdxZkNvFxQdAiQeXQrbxaZGiMw/J/wSNpUwCAsUzM/4QVMDrfSCDCzl
41// ZtNQtj4pTlFKKNVQthIjrXEIJUw2jp7IJLBfVSJu5iWxSlmId0f3MsiNizN81N69
42// P5Rm/doE3+KHoy8VXGsHcQKBgQCkNh62enqjHWypjex6450qS6f6iWN3PRLLVsw0
43// TcQpniZblCaBwVCAKmRUnjOEIdL2/4ZLutnwMTaFG/YEOOfAylMiY8jKV38lNmD9
44// X4D78CFr9klxgvS2CRwSE03f2NzmLkLxuKaxldvaxPTfjMkgeO1LFMlNExYBhkuH
45// 7uQpUQKBgQCKX6qMNh2gSdgG7qyxfTFZ4y5EGOBoKe/dE+IcVF3Vnh6DZVbCAbBL
46// 5EdFWZSrCnDjA4xiKW55mwp95Ud9EZsZAb13L8V9t82eK+UDBoWlb7VRNYpda/x1
47// 5/i4qQJ28x2UNJDStpYFpnp4Ba1lvXjKngIbDPkjU+hbBJ+BNGAIeg==
48// -----END RSA PRIVATE KEY-----";
49
50/// sub: Subject, user identifier
51/// exp: Expiration date, Unix Time, epoch
52/// puk: Public Key? Not 100% on this
53#[derive(Debug, Serialize, Deserialize)]
54struct Claims {
55 sub: String,
56 exp: usize,
57 puk: String,
58}
59
60// #[derive(Error, Debug)]
61// pub enum Nope {
62// #[error("Invalid header")]
63// InvalidHeader {
64// expected: String,
65// found: String,
66// },
67// }
68
69pub fn with_auth(
70 db: Db,
71 t: Transaction,
72) -> impl Filter<Extract = (String,), Error = Rejection> + Clone {
73 headers_cloned()
74 .map(move |headers: HeaderMap<HeaderValue>| (db.clone(), headers))
75 .and_then(authorize)
76}
77
78impl warp::reject::Reject for Nope {}
79
80async fn authorize((db, headers): (Db, HeaderMap<HeaderValue>)) -> Result<String, Error> {
81 match jwt_from_header(&headers) {
82 Ok(jwt) => {
83 let decoded = decode::<Claims>(
84 &jwt,
85 // TODO: what key are we using here? pass db/pw store here to get the claimant's
86 // public key <10-04-21, yigit> //
87 &DecodingKey::from_rsa_pem(PUBLIC_KEY_PEM.as_bytes()).unwrap(),
88 &Validation::new(Algorithm::HS512),
89 )
90 .map_err(|_| reject::custom(Error::JWTTokenError))
91 .unwrap();
92
93 Ok(decoded.claims.puk)
94 }
95 Err(e) => return Err(anyhow!("missing!"));
96 }
97}
98
99fn jwt_from_header(headers: &HeaderMap<HeaderValue>) -> Result<String, Error> {
100 let header = match headers.get(AUTHORIZATION) {
101 Some(v) => v,
102 None => return Err(Error::NoAuthHeaderError),
103 };
104 let auth_header = match std::str::from_utf8(header.as_bytes()) {
105 Ok(v) => v,
106 Err(_) => return Err(Error::NoAuthHeaderError),
107 };
108 if !auth_header.starts_with(BEARER) {
109 return Err(Error::InvalidAuthHeaderError);
110 }
111 Ok(auth_header.trim_start_matches(BEARER).to_owned())
112}
diff --git a/src/error.rs b/src/error.rs
new file mode 100644
index 0000000..0db26c4
--- /dev/null
+++ b/src/error.rs
@@ -0,0 +1,63 @@
1use serde::Serialize;
2use std::convert::Infallible;
3use thiserror::Error;
4use warp::{http::StatusCode, Rejection, Reply};
5
6#[derive(Error, Debug)]
7pub enum Error {
8 // #[error("wrong credentials")]
9 // WrongCredentialsError,
10 #[error("jwt token not valid")]
11 JWTTokenError,
12 // #[error("jwt token creation error")]
13 // JWTTokenCreationError,
14 #[error("no auth header")]
15 NoAuthHeaderError,
16 #[error("invalid auth header")]
17 InvalidAuthHeaderError,
18 // #[error("no permission")]
19 // NoPermissionError,
20}
21
22#[derive(Serialize, Debug)]
23struct ErrorResponse {
24 message: String,
25 status: String,
26}
27
28impl warp::reject::Reject for Error {}
29
30pub async fn handle_rejection(err: Rejection) -> std::result::Result<impl Reply, Infallible> {
31 let (code, message) = if err.is_not_found() {
32 (StatusCode::NOT_FOUND, "Not Found".to_string())
33 } else if let Some(e) = err.find::<Error>() {
34 match e {
35 // Error::WrongCredentialsError => (StatusCode::FORBIDDEN, e.to_string()),
36 // Error::NoPermissionError => (StatusCode::UNAUTHORIZED, e.to_string()),
37 Error::JWTTokenError => (StatusCode::UNAUTHORIZED, e.to_string()),
38 // Error::JWTTokenCreationError => (
39 // StatusCode::INTERNAL_SERVER_ERROR,
40 // "Internal Server Error".to_string(),
41 // ),
42 _ => (StatusCode::BAD_REQUEST, e.to_string()),
43 }
44 } else if err.find::<warp::reject::MethodNotAllowed>().is_some() {
45 (
46 StatusCode::METHOD_NOT_ALLOWED,
47 "Method Not Allowed".to_string(),
48 )
49 } else {
50 eprintln!("unhandled error: {:?}", err);
51 (
52 StatusCode::INTERNAL_SERVER_ERROR,
53 "Internal Server Error".to_string(),
54 )
55 };
56
57 let json = warp::reply::json(&ErrorResponse {
58 status: code.to_string(),
59 message,
60 });
61
62 Ok(warp::reply::with_status(json, code))
63}
diff --git a/src/handlers.rs b/src/handlers.rs
index 856970d..256e72a 100644
--- a/src/handlers.rs
+++ b/src/handlers.rs
@@ -58,6 +58,24 @@ pub async fn propose_transaction(
58 Ok(StatusCode::CREATED) 58 Ok(StatusCode::CREATED)
59} 59}
60 60
61/// POST /transaction, authenticated
62/// The transaction arrived in this method has been authored by the public key in the source
63pub async fn propose_authenticated_transaction(
64 pubkey: String,
65 new_transaction: Transaction,
66 db: Db,
67) -> Result<impl warp::Reply, warp::Rejection> {
68 debug!("new transaction request {:?}", new_transaction);
69
70 // let mut transactions = db.lock().await;
71 let mut transactions = db.pending_transactions.write();
72
73 transactions.insert(new_transaction.source.to_owned(), new_transaction);
74
75 Ok(StatusCode::CREATED)
76}
77
78
61/// POST /block 79/// POST /block
62/// Proposes a new block for the next round 80/// Proposes a new block for the next round
63/// Can reject the block 81/// Can reject the block
diff --git a/src/main.rs b/src/main.rs
index 7ef2597..91f6757 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -5,6 +5,8 @@ mod custom_filters;
5mod handlers; 5mod handlers;
6mod routes; 6mod routes;
7mod schema; 7mod schema;
8mod auth;
9mod error;
8// mod validators; 10// mod validators;
9 11
10#[tokio::main] 12#[tokio::main]
diff --git a/src/routes.rs b/src/routes.rs
index 95138e6..499ba35 100644
--- a/src/routes.rs
+++ b/src/routes.rs
@@ -1,8 +1,9 @@
1use warp::{Filter, Rejection, Reply}; 1use warp::{Filter, Rejection, Reply};
2 2
3use crate::auth::with_auth;
3use crate::custom_filters; 4use crate::custom_filters;
4use crate::handlers; 5use crate::handlers;
5use crate::schema::Db; 6use crate::schema::{Db, Transaction};
6 7
7/// Root, all routes combined 8/// Root, all routes combined
8pub 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 {
@@ -14,7 +15,8 @@ pub fn consensus_routes(db: Db) -> impl Filter<Extract = impl Reply, Error = Rej
14 15
15/// GET /transaction warp route 16/// GET /transaction warp route
16pub fn transaction_list(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { 17pub fn transaction_list(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
17 warp::path!("transaction") 18 warp::path("transaction")
19 .and(warp::path::end())
18 .and(warp::get()) 20 .and(warp::get())
19 .and(custom_filters::with_db(db)) 21 .and(custom_filters::with_db(db))
20 .and_then(handlers::list_transactions) 22 .and_then(handlers::list_transactions)
@@ -30,13 +32,43 @@ pub fn block_list(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection
30 32
31/// POST /transaction warp route 33/// POST /transaction warp route
32pub fn transaction_propose(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { 34pub fn transaction_propose(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
33 warp::path!("transaction") 35 warp::path("transaction")
36 .and(warp::path::end())
34 .and(warp::post()) 37 .and(warp::post())
35 .and(custom_filters::transaction_json_body()) 38 .and(custom_filters::transaction_json_body())
36 .and(custom_filters::with_db(db)) 39 .and(custom_filters::with_db(db))
37 .and_then(handlers::propose_transaction) 40 .and_then(handlers::propose_transaction)
38} 41}
39 42
43/// POST /transaction warp route with authentication
44pub fn authenticated_transaction_propose(
45 db: Db,
46) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
47 warp::path("transaction")
48 .and(warp::path::end())
49 .and(warp::post())
50 .and(custom_filters::transaction_json_body())
51 .map(|t: Transaction| {
52 with_auth(db.clone(), t)
53 })
54 .untuple_one()
55 .and(custom_filters::transaction_json_body())
56 .and(custom_filters::with_db(db))
57 .and_then(handlers::propose_authenticated_transaction)
58
59 // .and(custom_filters::transaction_json_body())
60 // // TODO: you might have to restore this
61 // // what we're trying to do is knowing which public key to use to decode the jwt in the
62 // // header of the request, we will either request it through a header (ugly, ugh) or get it
63 // // from json (then how do we chain these ugh) or we can just validate/check (move the
64 // // header/jwt logic to propose_transaction but that doesn't feel right either
65 // // good luck <10-04-21, yigit> //
66 // .map(|t: Transaction| with_auth(db.clone(), t))
67 // .and(custom_filters::transaction_json_body())
68 // .and(custom_filters::with_db(db))
69 // .and_then(handlers::propose_transaction)
70}
71
40/// POST /block warp route 72/// POST /block warp route
41pub fn block_propose(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { 73pub fn block_propose(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
42 warp::path!("block") 74 warp::path!("block")
diff --git a/src/schema.rs b/src/schema.rs
index 556e625..c4917ab 100644
--- a/src/schema.rs
+++ b/src/schema.rs
@@ -29,6 +29,8 @@ pub struct Db {
29 pub blockchain: Arc<RwLock<Vec<Block>>>, 29 pub blockchain: Arc<RwLock<Vec<Block>>>,
30 // every proposer can have _one_ pending transaction, a way to enforce this, String is proposer identifier 30 // 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>>>, 31 pub pending_transactions: Arc<RwLock<HashMap<String, Transaction>>>,
32 // this was bound to happen eventually
33 pub users: Arc<RwLock<HashMap<String, User>>>,
32} 34}
33 35
34impl Db { 36impl Db {
@@ -36,12 +38,15 @@ impl Db {
36 Db { 38 Db {
37 blockchain: Arc::new(RwLock::new(Vec::new())), 39 blockchain: Arc::new(RwLock::new(Vec::new())),
38 pending_transactions: Arc::new(RwLock::new(HashMap::new())), 40 pending_transactions: Arc::new(RwLock::new(HashMap::new())),
41 users: Arc::new(RwLock::new(HashMap::new())),
39 } 42 }
40 } 43 }
41} 44}
42 45
43/// A transaction between `source` and `target` that moves `amount` 46/// A transaction between `source` and `target` that moves `amount` Note:
44#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] 47/// https://serde.rs/container-attrs.html might be valueable to normalize the serialize/deserialize
48/// conventions as these will be hashed
49#[derive(Serialize, Deserialize, Debug)]
45pub struct Transaction { 50pub struct Transaction {
46 pub source: String, 51 pub source: String,
47 pub target: String, 52 pub target: String,
@@ -65,5 +70,10 @@ pub struct Block {
65 pub hash: String, // future proof'd baby 70 pub hash: String, // future proof'd baby
66} 71}
67 72
73#[derive(Serialize, Deserialize, Debug)]
74pub struct User {
75 username: String,
76 token: String
77}
68 78
69// TODO: write schema tests using the original repo <09-04-21, yigit> // 79// TODO: write schema tests using the original repo <09-04-21, yigit> //