From 0ccda2f40bfbd0d278ff1023fb72f8434b3a9379 Mon Sep 17 00:00:00 2001 From: Bryan Stitt Date: Tue, 16 Aug 2022 21:43:39 +0000 Subject: [PATCH] it compiles --- redis-rate-limit/src/lib.rs | 2 +- web3_proxy/src/frontend/errors.rs | 12 +++++-- web3_proxy/src/frontend/mod.rs | 8 +---- web3_proxy/src/frontend/users.rs | 55 +++++++++++++++++++++++++++---- 4 files changed, 59 insertions(+), 18 deletions(-) diff --git a/redis-rate-limit/src/lib.rs b/redis-rate-limit/src/lib.rs index fbefc05e..46b92362 100644 --- a/redis-rate-limit/src/lib.rs +++ b/redis-rate-limit/src/lib.rs @@ -7,7 +7,7 @@ use std::ops::Add; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; pub use crate::errors::{RedisError, RedisErrorSink}; -pub use bb8_redis::{bb8, RedisConnectionManager}; +pub use bb8_redis::{bb8, redis, RedisConnectionManager}; pub type RedisPool = bb8::Pool; diff --git a/web3_proxy/src/frontend/errors.rs b/web3_proxy/src/frontend/errors.rs index 23d25b5c..40c55df3 100644 --- a/web3_proxy/src/frontend/errors.rs +++ b/web3_proxy/src/frontend/errors.rs @@ -4,14 +4,20 @@ use axum::{ response::{IntoResponse, Response}, Json, }; +use derive_more::From; use serde_json::value::RawValue; use std::error::Error; -pub struct ErrorResponse { - pub inner: Box, +// TODO: take "IntoResult" instead? +pub type FrontendResult = Result; + +#[derive(From)] +pub enum FrontendErrorResponse { + Anyhow(anyhow::Error), + BoxError(Box), } -impl IntoResponse for ErrorResponse { +impl IntoResponse for FrontendErrorResponse { fn into_response(self) -> Response { todo!("into_response based on the error type") } diff --git a/web3_proxy/src/frontend/mod.rs b/web3_proxy/src/frontend/mod.rs index 7b0376cc..7ebb72d1 100644 --- a/web3_proxy/src/frontend/mod.rs +++ b/web3_proxy/src/frontend/mod.rs @@ -68,13 +68,7 @@ pub async fn serve(port: u16, proxy_app: Arc) -> anyhow::Result<() .route("/u/:user_key", get(ws_proxy::user_websocket_handler)) .route("/health", get(http::health)) .route("/status", get(http::status)) - // .route( - // "/login", - // HandleError::new( - // move |app| async { users::get_login(app).await }, - // handle_anyhow_error, - // ), - // ) + .route("/login", get(users::get_login)) .route("/users", post(users::create_user)) .route( "/foo", diff --git a/web3_proxy/src/frontend/users.rs b/web3_proxy/src/frontend/users.rs index 092d669f..5eaff6ca 100644 --- a/web3_proxy/src/frontend/users.rs +++ b/web3_proxy/src/frontend/users.rs @@ -7,32 +7,73 @@ // I wonder how we handle payment // probably have to do manual withdrawals -use super::{errors::anyhow_error_into_response, rate_limit::RateLimitResult}; +use super::{ + errors::{anyhow_error_into_response, FrontendResult}, + rate_limit::RateLimitResult, +}; use crate::app::Web3ProxyApp; use axum::{ - response::{ErrorResponse, IntoResponse, Response}, + extract::Path, + response::{IntoResponse, Response}, Extension, Json, }; use axum_client_ip::ClientIp; use axum_macros::debug_handler; use entities::user; use ethers::{prelude::Address, types::Bytes}; +use redis_rate_limit::redis::{pipe, AsyncCommands}; use reqwest::StatusCode; use sea_orm::ActiveModelTrait; use serde::Deserialize; use std::sync::Arc; +use uuid::Uuid; // TODO: how do we customize axum's error response? I think we probably want an enum that implements IntoResponse instead #[debug_handler] pub async fn get_login( Extension(app): Extension>, -) -> Result { - // let redis: RedisPool = app...; - let redis_pool = app.redis_pool.as_ref().unwrap(); + ClientIp(ip): ClientIp, + // TODO: what does axum's error handling look like? + Path(user_address): Path
, +) -> FrontendResult { + // TODO: refactor this to use the try operator + let _ip = match app.rate_limit_by_ip(ip).await { + Ok(x) => match x.try_into_response().await { + Ok(RateLimitResult::AllowedIp(x)) => x, + Err(err_response) => return Ok(err_response), + _ => unimplemented!(), + }, + Err(err) => return Ok(anyhow_error_into_response(None, None, err)), + }; - let redis_conn = redis_pool.get().await.unwrap(); + // at first i thought about checking that user_address is in our db + // but theres no need to separate the create_user and login flows + // its a better UX to just click "login with ethereum" and have the account created if it doesn't exist + // we can prompt for an email and and payment after they log in - todo!("how should this work? probably keep stuff in redis ") + let session_id = uuid::Uuid::new_v4(); + + // TODO: if no redis, store in local cache? + let redis_pool = app + .redis_pool + .as_ref() + .expect("login requires a redis server"); + + let mut redis_conn = redis_pool.get().await.unwrap(); + + // TODO: how many seconds? get from config? + let session_expiration_seconds = 300; + + let reply: String = redis_conn + .set_ex( + session_id.to_string(), + user_address.to_string(), + session_expiration_seconds, + ) + .await + .unwrap(); + + todo!("how should this work? probably keep stuff in redis") } #[debug_handler]