From 2989b7e91c09ab30a65e2a3ca07df30cb6176ced Mon Sep 17 00:00:00 2001 From: Bryan Stitt Date: Tue, 23 Aug 2022 18:48:27 +0000 Subject: [PATCH] dry redis connections and use bearer tokens --- Cargo.lock | 21 ++++++++++++++++---- web3_proxy/Cargo.toml | 3 ++- web3_proxy/src/app.rs | 12 +++++++++++ web3_proxy/src/frontend/users.rs | 34 +++++++++++--------------------- 4 files changed, 43 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 25877607..13ea374f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -278,9 +278,9 @@ checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" [[package]] name = "async-trait" -version = "0.1.53" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600" +checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" dependencies = [ "proc-macro2", "quote", @@ -386,6 +386,18 @@ dependencies = [ "tower-service", ] +[[package]] +name = "axum-auth" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9770f9a9147b2324066609acb5495538cb25f973129663fba2658ba7ed69407" +dependencies = [ + "async-trait", + "axum-core", + "base64 0.13.0", + "http", +] + [[package]] name = "axum-client-ip" version = "0.2.0" @@ -4344,9 +4356,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.83" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" +checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" dependencies = [ "itoa 1.0.2", "ryu", @@ -5521,6 +5533,7 @@ dependencies = [ "arc-swap", "argh", "axum", + "axum-auth", "axum-client-ip", "axum-macros", "counter", diff --git a/web3_proxy/Cargo.toml b/web3_proxy/Cargo.toml index 18226bfb..e418d8d6 100644 --- a/web3_proxy/Cargo.toml +++ b/web3_proxy/Cargo.toml @@ -20,6 +20,7 @@ anyhow = { version = "1.0.62", features = ["backtrace"] } arc-swap = "1.5.1" argh = "0.1.8" axum = { version = "0.5.15", features = ["headers", "serde_json", "tokio-tungstenite", "ws"] } +axum-auth = "0.3.0" axum-client-ip = "0.2.0" axum-macros = "0.2.3" counter = "0.5.6" @@ -49,7 +50,7 @@ rustc-hash = "1.1.0" siwe = "0.4.1" sea-orm = { version = "0.9.2", features = ["macros"] } serde = { version = "1.0.144", features = [] } -serde_json = { version = "1.0.83", default-features = false, features = ["alloc", "raw_value"] } +serde_json = { version = "1.0.85", default-features = false, features = ["alloc", "raw_value"] } # TODO: make sure this time version matches siwe. PR to put this in their prelude time = "0.3.13" tokio = { version = "1.20.1", features = ["full", "tracing"] } diff --git a/web3_proxy/src/app.rs b/web3_proxy/src/app.rs index 64578df7..ac86b93c 100644 --- a/web3_proxy/src/app.rs +++ b/web3_proxy/src/app.rs @@ -15,6 +15,7 @@ use futures::stream::StreamExt; use futures::Future; use migration::{Migrator, MigratorTrait}; use parking_lot::RwLock; +use redis_rate_limit::bb8::PooledConnection; use redis_rate_limit::{ bb8::{self, ErrorSink}, RedisConnectionManager, RedisErrorSink, RedisPool, RedisRateLimit, @@ -158,6 +159,17 @@ impl fmt::Debug for Web3ProxyApp { } impl Web3ProxyApp { + pub async fn redis_conn(&self) -> anyhow::Result> { + match self.redis_pool.as_ref() { + None => Err(anyhow::anyhow!("no redis server configured")), + Some(redis_pool) => { + let redis_conn = redis_pool.get().await?; + + Ok(redis_conn) + } + } + } + // TODO: should we just take the rpc config as the only arg instead? pub async fn spawn( app_stats: AppStats, diff --git a/web3_proxy/src/frontend/users.rs b/web3_proxy/src/frontend/users.rs index 968471ec..f76a3772 100644 --- a/web3_proxy/src/frontend/users.rs +++ b/web3_proxy/src/frontend/users.rs @@ -15,6 +15,7 @@ use axum::{ response::IntoResponse, Extension, Json, }; +use axum_auth::AuthBearer; use axum_client_ip::ClientIp; use axum_macros::debug_handler; use entities::{user, user_keys}; @@ -48,7 +49,8 @@ pub async fn get_login( // create a message and save it in redis // TODO: how many seconds? get from config? - let expire_seconds: usize = 300; + // TODO: while developing, we put a giant number here + let expire_seconds: usize = 28800; let nonce = Ulid::new(); @@ -78,21 +80,14 @@ pub async fn get_login( let session_key = format!("pending:{}", nonce); // TODO: if no redis server, store in local cache? - let mut redis_conn = app - .redis_pool - .as_ref() - .expect("login requires a redis server") - .get() - .await?; - // the address isn't enough. we need to save the actual message so we can read the nonce // TODO: what message format is the most efficient to store in redis? probably eip191_string - redis_conn - .set_ex(session_key, message.to_string(), expire_seconds) + // we add 1 to expire_seconds just to be sure redis has the key for the full expiration_time + app.redis_conn() + .await? + .set_ex(session_key, message.to_string(), expire_seconds + 1) .await?; - drop(redis_conn); - // there are multiple ways to sign messages and not all wallets support them let message_eip = params .remove("message_eip") @@ -152,16 +147,8 @@ pub async fn post_login( let their_sig: [u8; 65] = payload.sig.as_ref().try_into().unwrap(); // fetch the message we gave them from our redis - let redis_pool = app - .redis_pool - .as_ref() - .expect("login requires a redis server"); - - let mut redis_conn = redis_pool.get().await.unwrap(); - // TODO: use getdel - // TODO: do not unwrap. make this function return a FrontendResult - let our_msg: String = redis_conn.get(&their_msg.nonce).await.unwrap(); + let our_msg: String = app.redis_conn().await?.get(&their_msg.nonce).await?; let our_msg: siwe::Message = our_msg.parse().unwrap(); @@ -203,6 +190,7 @@ pub async fn post_login( // TODO: set a cookie? // TODO: do not expose user ids + // TODO: return an api key and a bearer token (StatusCode::CREATED, Json(user)).into_response() */ } else { @@ -224,10 +212,12 @@ pub struct PostUser { pub async fn post_user( Json(payload): Json, Extension(app): Extension>, - ClientIp(ip): ClientIp, + AuthBearer(auth_token): AuthBearer, ) -> FrontendResult { todo!("finish post_user"); + // TODO: check the auth_token is valid for the user in PostUser + // let user = user::ActiveModel { // address: sea_orm::Set(payload.address.to_fixed_bytes().into()), // email: sea_orm::Set(payload.email),