From 9ec8abdf49afd190011fb13d33735fd45ad876c7 Mon Sep 17 00:00:00 2001 From: Bryan Stitt Date: Tue, 25 Oct 2022 17:21:11 +0000 Subject: [PATCH] only require invite_code on first login --- README.md | 6 +- web3_proxy/src/app_stats.rs | 1 - web3_proxy/src/frontend/users.rs | 95 ++++++++++++++++---------------- 3 files changed, 53 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 3148c38a..99c53e5d 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,11 @@ Flame graphs make a developer's join of finding slow code painless: 1 $ echo 0 | sudo tee /proc/sys/kernel/kptr_restrict 0 - $ CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph + $ cat /proc/sys/kernel/perf_event_paranoid + 4 + $ echo -1 | sudo tee /proc/sys/kernel/perf_event_paranoid + 0 + $ CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph --bin web3_proxy ## GDB diff --git a/web3_proxy/src/app_stats.rs b/web3_proxy/src/app_stats.rs index 54eb6b35..d97eab02 100644 --- a/web3_proxy/src/app_stats.rs +++ b/web3_proxy/src/app_stats.rs @@ -80,7 +80,6 @@ pub struct UserProxyResponseKey { error_response: bool, } -/// key is the (user_key_id, method, error_response) pub type UserProxyResponseCache = Cache< UserProxyResponseKey, Arc, diff --git a/web3_proxy/src/frontend/users.rs b/web3_proxy/src/frontend/users.rs index 48e6f32b..1e325970 100644 --- a/web3_proxy/src/frontend/users.rs +++ b/web3_proxy/src/frontend/users.rs @@ -145,7 +145,7 @@ pub struct PostLoginResponse { /// Used for authenticating additonal requests. bearer_token: Ulid, /// Used for authenticating with the RPC endpoints. - api_keys: Vec, + api_keys: HashMap, user_id: u64, // TODO: what else? } @@ -162,45 +162,36 @@ pub async fn user_login_post( ) -> FrontendResult { login_is_authorized(&app, ip).await?; - if let Some(invite_code) = &app.config.invite_code { - // we don't do per-user referral codes because we shouldn't collect what we don't need. - // we don't need to build a social graph between addresses like that. - if query.invite_code.as_ref() != Some(invite_code) { - warn!("if address is already registered, allow login! else, error"); - - // TODO: this return doesn't seem right - return Err(anyhow::anyhow!("checking invite_code"))?; - } - } - // we can't trust that they didn't tamper with the message in some way // TODO: it seems like some clients do things unexpectedly. these don't always parse // let their_msg: siwe::Message = payload.msg.parse().context("parsing user's message")?; - // TODO: do this safely + // TODO: this seems too verbose. how can we simply convert a String into a [u8; 65] let their_sig_bytes = Bytes::from_str(&payload.sig).context("parsing sig")?; if their_sig_bytes.len() != 65 { - return Err(anyhow::anyhow!("checking signature length"))?; + return Err(anyhow::anyhow!("checking signature length").into()); } let mut their_sig: [u8; 65] = [0; 65]; for x in 0..65 { their_sig[x] = their_sig_bytes[x] } - let their_msg: String = if payload.msg.starts_with("0x") { + // TODO: checking 0x seems fragile, but I think it will be fine + let their_msg: Message = if payload.msg.starts_with("0x") { let their_msg_bytes = Bytes::from_str(&payload.msg).context("parsing payload message")?; // TODO: lossy or no? - String::from_utf8_lossy(their_msg_bytes.as_ref()).to_string() + String::from_utf8_lossy(their_msg_bytes.as_ref()) + .parse::() + .context("parsing hex string message")? } else { - payload.msg + payload + .msg + .parse::() + .context("parsing string message")? }; - let their_msg = their_msg - .parse::() - .context("parsing string message")?; - - // TODO: this is fragile + // TODO: this is fragile. have a helper function/struct for redis keys let login_nonce_key = format!("login_nonce:{}", &their_msg.nonce); // fetch the message we gave them from our redis @@ -235,12 +226,11 @@ pub async fn user_login_post( "both the primary and eip191 verification failed: {:#?}; {:#?}", err_1, err_191 - ))?; + ) + .into()); } } - let bearer_token = Ulid::new(); - let db_conn = app.db_conn().context("Getting database connection")?; // TODO: limit columns or load whole user? @@ -250,8 +240,17 @@ pub async fn user_login_post( .await .unwrap(); - let (u, _uks, response) = match u { + let (u, uks, status_code) = match u { None => { + // user does not exist yet + + // check the invite code + if let Some(invite_code) = &app.config.invite_code { + if query.invite_code.as_ref() != Some(invite_code) { + return Err(anyhow::anyhow!("checking invite_code").into()); + } + } + let txn = db_conn.begin().await?; // the only thing we need from them is an address @@ -263,8 +262,11 @@ pub async fn user_login_post( let u = u.insert(&txn).await?; + // create the user's first api key + // TODO: rename to UserApiKey? RpcApiKey? let user_key = UserKey::new(); + // TODO: variable requests per minute depending on the invite code let uk = user_keys::ActiveModel { user_id: sea_orm::Set(u.id), api_key: sea_orm::Set(user_key.into()), @@ -272,7 +274,6 @@ pub async fn user_login_post( ..Default::default() }; - // TODO: if this fails, revert adding the user, too let uk = uk .insert(&txn) .await @@ -280,17 +281,10 @@ pub async fn user_login_post( let uks = vec![uk]; + // save the user and key to the database txn.commit().await?; - let response_json = PostLoginResponse { - api_keys: uks.iter().map(|uk| uk.api_key.into()).collect(), - bearer_token, - user_id: u.id, - }; - - let response = (StatusCode::CREATED, Json(response_json)).into_response(); - - (u, uks, response) + (u, uks, StatusCode::CREATED) } Some(u) => { // the user is already registered @@ -300,19 +294,26 @@ pub async fn user_login_post( .await .context("failed loading user's key")?; - let response_json = PostLoginResponse { - api_keys: uks.iter().map(|uk| uk.api_key.into()).collect(), - bearer_token, - user_id: u.id, - }; - - let response = (StatusCode::OK, Json(response_json)).into_response(); - - (u, uks, response) + (u, uks, StatusCode::OK) } }; + // create a bearer token for the user. + let bearer_token = Ulid::new(); + + let response_json = PostLoginResponse { + api_keys: uks + .into_iter() + .map(|uk| (uk.id, uk.api_key.into())) + .collect(), + bearer_token, + user_id: u.id, + }; + + let response = (status_code, Json(response_json)).into_response(); + // add bearer to redis + // TODO: use a helper function/struct for this let bearer_redis_key = format!("bearer:{}", bearer_token); // expire in 4 weeks @@ -353,7 +354,7 @@ pub async fn user_logout_post( /// the JSON input to the `post_user` handler /// This handles updating #[derive(Deserialize)] -pub struct PostUser { +pub struct UserProfilePost { primary_address: Address, // TODO: make sure the email address is valid. probably have a "verified" column in the database email: Option, @@ -365,7 +366,7 @@ pub async fn user_profile_post( TypedHeader(Authorization(bearer_token)): TypedHeader>, ClientIp(ip): ClientIp, Extension(app): Extension>, - Json(payload): Json, + Json(payload): Json, ) -> FrontendResult { login_is_authorized(&app, ip).await?;