diff --git a/web3_proxy/src/frontend/admin.rs b/web3_proxy/src/frontend/admin.rs index 8df584e6..8de51fa8 100644 --- a/web3_proxy/src/frontend/admin.rs +++ b/web3_proxy/src/frontend/admin.rs @@ -4,10 +4,9 @@ use super::authorization::login_is_authorized; use super::errors::Web3ProxyResponse; use crate::admin_queries::query_admin_modify_usertier; use crate::app::Web3ProxyApp; -use crate::frontend::errors::Web3ProxyError; +use crate::frontend::errors::{Web3ProxyError, Web3ProxyErrorContext}; use crate::user_token::UserBearerToken; use crate::PostLogin; -use anyhow::Context; use axum::{ extract::{Path, Query}, headers::{authorization::Bearer, Authorization}, @@ -137,10 +136,10 @@ pub async fn admin_login_get( resources: vec![], }; - let db_conn = app.db_conn().context("login requires a database")?; + let db_conn = app.db_conn().web3_context("login requires a database")?; let db_replica = app .db_replica() - .context("login requires a replica database")?; + .web3_context("login requires a replica database")?; // Get the user that we want to imitate from the read-only database (their id ...) // TODO: Only get the id, not the whole user object ... @@ -171,7 +170,7 @@ pub async fn admin_login_get( trail .save(&db_conn) .await - .context("saving user's pending_login")?; + .web3_context("saving user's pending_login")?; // Can there be two login-sessions at the same time? // I supposed if the user logs in, the admin would be logged out and vice versa @@ -196,7 +195,7 @@ pub async fn admin_login_get( user_pending_login .save(&db_conn) .await - .context("saving an admin trail pre login")?; + .web3_context("saving an admin trail pre login")?; // there are multiple ways to sign messages and not all wallets support them // TODO: default message eip from config? @@ -210,7 +209,7 @@ pub async fn admin_login_get( "eip4361" => message.to_string(), _ => { // TODO: custom error that is handled a 401 - return Err(anyhow::anyhow!("invalid message eip given").into()); + return Err(Web3ProxyError::InvalidEip); } }; @@ -230,9 +229,9 @@ pub async fn admin_login_post( // Check for the signed bytes .. // 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")?; + let their_sig_bytes = Bytes::from_str(&payload.sig).web3_context("parsing sig")?; if their_sig_bytes.len() != 65 { - return Err(anyhow::anyhow!("checking signature length").into()); + return Err(Web3ProxyError::InvalidSignatureLength); } let mut their_sig: [u8; 65] = [0; 65]; for x in 0..65 { @@ -242,17 +241,18 @@ pub async fn admin_login_post( // we can't trust that they didn't tamper with the message in some way. like some clients return it hex encoded // TODO: checking 0x seems fragile, but I think it will be fine. siwe message text shouldn't ever start with 0x let their_msg: Message = if payload.msg.starts_with("0x") { - let their_msg_bytes = Bytes::from_str(&payload.msg).context("parsing payload message")?; + let their_msg_bytes = + Bytes::from_str(&payload.msg).web3_context("parsing payload message")?; // TODO: lossy or no? String::from_utf8_lossy(their_msg_bytes.as_ref()) .parse::() - .context("parsing hex string message")? + .web3_context("parsing hex string message")? } else { payload .msg .parse::() - .context("parsing string message")? + .web3_context("parsing string message")? }; // the only part of the message we will trust is their nonce @@ -260,7 +260,9 @@ pub async fn admin_login_post( let login_nonce = UserBearerToken::from_str(&their_msg.nonce)?; // fetch the message we gave them from our database - let db_replica = app.db_replica().context("Getting database connection")?; + let db_replica = app + .db_replica() + .web3_context("Getting database connection")?; // massage type for the db let login_nonce_uuid: Uuid = login_nonce.clone().into(); @@ -270,30 +272,30 @@ pub async fn admin_login_post( .filter(pending_login::Column::Nonce.eq(login_nonce_uuid)) .one(db_replica.conn()) .await - .context("database error while finding pending_login")? - .context("login nonce not found")?; + .web3_context("database error while finding pending_login")? + .web3_context("login nonce not found")?; let our_msg: siwe::Message = user_pending_login .message .parse() - .context("parsing siwe message")?; + .web3_context("parsing siwe message")?; // default options are fine. the message includes timestamp and domain and nonce let verify_config = VerificationOpts::default(); let db_conn = app .db_conn() - .context("deleting expired pending logins requires a db")?; + .web3_context("deleting expired pending logins requires a db")?; if let Err(err_1) = our_msg .verify(&their_sig, &verify_config) .await - .context("verifying signature against our local message") + .web3_context("verifying signature against our local message") { // verification method 1 failed. try eip191 if let Err(err_191) = our_msg .verify_eip191(&their_sig) - .context("verifying eip191 signature against our local message") + .web3_context("verifying eip191 signature against our local message") { // delete ALL expired rows. let now = Utc::now(); @@ -305,18 +307,16 @@ pub async fn admin_login_post( // TODO: emit a stat? if this is high something weird might be happening debug!("cleared expired pending_logins: {:?}", delete_result); - return Err(anyhow::anyhow!( - "both the primary and eip191 verification failed: {:#?}; {:#?}", - err_1, - err_191 - ) - .into()); + return Err(Web3ProxyError::EipVerificationFailed( + Box::new(err_1), + Box::new(err_191), + )); } } let imitating_user_id = user_pending_login .imitating_user - .context("getting address of the imitating user")?; + .web3_context("getting address of the imitating user")?; // TODO: limit columns or load whole user? // TODO: Right now this loads the whole admin. I assume we might want to load the user though (?) figure this out as we go along... @@ -324,13 +324,13 @@ pub async fn admin_login_post( .filter(user::Column::Address.eq(our_msg.address.as_ref())) .one(db_replica.conn()) .await? - .context("getting admin address")?; + .web3_context("getting admin address")?; let imitating_user = user::Entity::find() .filter(user::Column::Id.eq(imitating_user_id)) .one(db_replica.conn()) .await? - .context("admin address was not found!")?; + .web3_context("admin address was not found!")?; // Add a message that the admin has logged in // Note that the admin is trying to log in as this user @@ -344,7 +344,7 @@ pub async fn admin_login_post( trail .save(&db_conn) .await - .context("saving an admin trail post login")?; + .web3_context("saving an admin trail post login")?; // I supposed we also get the rpc_key, whatever this is used for (?). // I think the RPC key should still belong to the admin though in this case ... @@ -354,7 +354,7 @@ pub async fn admin_login_post( .filter(rpc_key::Column::UserId.eq(admin.id)) .all(db_replica.conn()) .await - .context("failed loading user's key")?; + .web3_context("failed loading user's key")?; // create a bearer token for the user. let user_bearer_token = UserBearerToken::default(); @@ -395,7 +395,7 @@ pub async fn admin_login_post( user_login .save(&db_conn) .await - .context("saving user login")?; + .web3_context("saving user login")?; if let Err(err) = user_pending_login .into_active_model() @@ -417,7 +417,9 @@ pub async fn admin_logout_post( ) -> Web3ProxyResponse { let user_bearer = UserBearerToken::try_from(bearer)?; - let db_conn = app.db_conn().context("database needed for user logout")?; + let db_conn = app + .db_conn() + .web3_context("database needed for user logout")?; if let Err(err) = login::Entity::delete_many() .filter(login::Column::BearerToken.eq(user_bearer.uuid())) diff --git a/web3_proxy/src/frontend/authorization.rs b/web3_proxy/src/frontend/authorization.rs index 9a8723f1..03e80ef8 100644 --- a/web3_proxy/src/frontend/authorization.rs +++ b/web3_proxy/src/frontend/authorization.rs @@ -1,11 +1,10 @@ //! Utilities for authorization of logged in and anonymous users. -use super::errors::{Web3ProxyError, Web3ProxyResult}; +use super::errors::{Web3ProxyError, Web3ProxyErrorContext, Web3ProxyResult}; use super::rpc_proxy_ws::ProxyMode; use crate::app::{AuthorizationChecks, Web3ProxyApp, APP_USER_AGENT}; use crate::rpcs::one::Web3Rpc; use crate::user_token::UserBearerToken; -use anyhow::Context; use axum::headers::authorization::Bearer; use axum::headers::{Header, Origin, Referer, UserAgent}; use chrono::Utc; @@ -128,7 +127,7 @@ impl Display for RpcSecretKey { } impl FromStr for RpcSecretKey { - type Err = anyhow::Error; + type Err = Web3ProxyError; fn from_str(s: &str) -> Result { if let Ok(ulid) = s.parse::() { @@ -137,7 +136,7 @@ impl FromStr for RpcSecretKey { Ok(uuid.into()) } else { // TODO: custom error type so that this shows as a 400 - Err(anyhow::anyhow!("UserKey was not a ULID or UUID")) + Err(Web3ProxyError::InvalidUserKey) } } } @@ -367,7 +366,7 @@ pub async fn ip_is_authorized( .await?; }; - Ok::<_, anyhow::Error>(()) + Ok::<_, Web3ProxyError>(()) } .map_err(|err| { warn!("background update of recent_users:ip failed: {}", err); @@ -430,7 +429,7 @@ pub async fn key_is_authorized( .await?; } - Ok::<_, anyhow::Error>(()) + Ok::<_, Web3ProxyError>(()) } .map_err(|err| { warn!("background update of recent_users:id failed: {}", err); @@ -474,12 +473,13 @@ impl Web3ProxyApp { pub async fn registered_user_semaphore( &self, authorization_checks: &AuthorizationChecks, - ) -> anyhow::Result> { + ) -> Web3ProxyResult> { if let Some(max_concurrent_requests) = authorization_checks.max_concurrent_requests { let user_id = authorization_checks .user_id .try_into() - .context("user ids should always be non-zero")?; + .or(Err(Web3ProxyError::UserIdZero)) + .web3_context("user ids should always be non-zero")?; let semaphore = self .registered_user_semaphores @@ -527,7 +527,7 @@ impl Web3ProxyApp { // get the attached address from the database for the given auth_token. let db_replica = self .db_replica() - .context("checking if bearer token is authorized")?; + .web3_context("checking if bearer token is authorized")?; let user_bearer_uuid: Uuid = user_bearer_token.into(); @@ -536,8 +536,8 @@ impl Web3ProxyApp { .filter(login::Column::BearerToken.eq(user_bearer_uuid)) .one(db_replica.conn()) .await - .context("fetching user from db by bearer token")? - .context("unknown bearer token")?; + .web3_context("fetching user from db by bearer token")? + .web3_context("unknown bearer token")?; Ok((user, semaphore_permit)) } @@ -662,13 +662,15 @@ impl Web3ProxyApp { &self, proxy_mode: ProxyMode, rpc_secret_key: RpcSecretKey, - ) -> anyhow::Result { - let authorization_checks: Result<_, Arc> = self + ) -> Web3ProxyResult { + let authorization_checks: Result<_, Arc> = self .rpc_secret_key_cache .try_get_with(rpc_secret_key.into(), async move { // trace!(?rpc_secret_key, "user cache miss"); - let db_replica = self.db_replica().context("Getting database connection")?; + let db_replica = self + .db_replica() + .web3_context("Getting database connection")?; // TODO: join the user table to this to return the User? we don't always need it // TODO: join on secondary users @@ -724,7 +726,11 @@ impl Web3ProxyApp { if let Some(allowed_referers) = rpc_key_model.allowed_referers { let x = allowed_referers .split(',') - .map(|x| x.trim().parse::()) + .map(|x| { + x.trim() + .parse::() + .or(Err(Web3ProxyError::InvalidReferer)) + }) .collect::, _>>()?; Some(x) @@ -736,7 +742,11 @@ impl Web3ProxyApp { if let Some(allowed_user_agents) = rpc_key_model.allowed_user_agents { let x: Result, _> = allowed_user_agents .split(',') - .map(|x| x.trim().parse::()) + .map(|x| { + x.trim() + .parse::() + .or(Err(Web3ProxyError::InvalidUserAgent)) + }) .collect(); Some(x?) @@ -768,8 +778,7 @@ impl Web3ProxyApp { }) .await; - // TODO: what's the best way to handle this arc? try_unwrap will not work - authorization_checks.map_err(|err| anyhow::anyhow!(err)) + authorization_checks.map_err(Web3ProxyError::SeaRc) } /// Authorized the ip/origin/referer/useragent and rate limit and concurrency diff --git a/web3_proxy/src/frontend/errors.rs b/web3_proxy/src/frontend/errors.rs index d9692f7f..2546ae14 100644 --- a/web3_proxy/src/frontend/errors.rs +++ b/web3_proxy/src/frontend/errors.rs @@ -34,7 +34,10 @@ pub enum Web3ProxyError { #[error(ignore)] #[from(ignore)] BadRequest(String), + BadRouting, Database(DbErr), + #[display(fmt = "{:#?}, {:#?}", _0, _1)] + EipVerificationFailed(Box, Box), EthersHttpClientError(ethers::prelude::HttpClientError), EthersProviderError(ethers::prelude::ProviderError), EthersWsClientError(ethers::prelude::WsClientError), @@ -47,6 +50,12 @@ pub enum Web3ProxyError { max: u64, }, InvalidHeaderValue(InvalidHeaderValue), + InvalidEip, + InvalidInviteCode, + InvalidReferer, + InvalidSignatureLength, + InvalidUserAgent, + InvalidUserKey, IpAddrParse(AddrParseError), #[error(ignore)] #[from(ignore)] @@ -56,10 +65,14 @@ pub enum Web3ProxyError { NoServersSynced, NoHandleReady, NotFound, + NotImplemented, OriginRequired, #[error(ignore)] #[from(ignore)] OriginNotAllowed(headers::Origin), + ParseBytesError(ethers::types::ParseBytesError), + ParseMsgError(siwe::ParseError), + ParseAddressError, #[display(fmt = "{:?}, {:?}", _0, _1)] RateLimited(Authorization, Option), Redis(RedisError), @@ -84,9 +97,11 @@ pub enum Web3ProxyError { UserAgentRequired, #[error(ignore)] UserAgentNotAllowed(headers::UserAgent), + UserIdZero, + VerificationError(siwe::VerificationError), WatchRecvError(tokio::sync::watch::error::RecvError), WebsocketOnly, - #[display(fmt = "{}, {}", _0, _1)] + #[display(fmt = "{:?}, {}", _0, _1)] #[error(ignore)] WithContext(Option>, String), } @@ -130,6 +145,17 @@ impl Web3ProxyError { ), ) } + Self::BadRouting => { + error!("BadRouting"); + ( + StatusCode::INTERNAL_SERVER_ERROR, + JsonRpcForwardedResponse::from_str( + "bad routing", + Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16().into()), + None, + ), + ) + } Self::Database(err) => { error!("database err={:?}", err); ( @@ -141,6 +167,23 @@ impl Web3ProxyError { ), ) } + Self::EipVerificationFailed(err_1, err_191) => { + warn!( + "EipVerificationFailed err_1={:#?} err2={:#?}", + err_1, err_191 + ); + ( + StatusCode::UNAUTHORIZED, + JsonRpcForwardedResponse::from_string( + format!( + "both the primary and eip191 verification failed: {:#?}; {:#?}", + err_1, err_191 + ), + Some(StatusCode::UNAUTHORIZED.as_u16().into()), + None, + ), + ) + } Self::EthersHttpClientError(err) => { warn!("EthersHttpClientError err={:?}", err); ( @@ -244,6 +287,72 @@ impl Web3ProxyError { ), ) } + Self::InvalidEip => { + warn!("InvalidEip"); + ( + StatusCode::UNAUTHORIZED, + JsonRpcForwardedResponse::from_str( + "invalid message eip given", + Some(StatusCode::UNAUTHORIZED.as_u16().into()), + None, + ), + ) + } + Self::InvalidInviteCode => { + warn!("InvalidInviteCode"); + ( + StatusCode::UNAUTHORIZED, + JsonRpcForwardedResponse::from_str( + "invalid invite code", + Some(StatusCode::UNAUTHORIZED.as_u16().into()), + None, + ), + ) + } + Self::InvalidReferer => { + warn!("InvalidReferer"); + ( + StatusCode::BAD_REQUEST, + JsonRpcForwardedResponse::from_str( + "invalid referer!", + Some(StatusCode::BAD_REQUEST.as_u16().into()), + None, + ), + ) + } + Self::InvalidSignatureLength => { + warn!("InvalidSignatureLength"); + ( + StatusCode::BAD_REQUEST, + JsonRpcForwardedResponse::from_str( + "invalid signature length", + Some(StatusCode::BAD_REQUEST.as_u16().into()), + None, + ), + ) + } + Self::InvalidUserAgent => { + warn!("InvalidUserAgent"); + ( + StatusCode::BAD_REQUEST, + JsonRpcForwardedResponse::from_str( + "invalid user agent!", + Some(StatusCode::BAD_REQUEST.as_u16().into()), + None, + ), + ) + } + Self::InvalidUserKey => { + warn!("InvalidUserKey"); + ( + StatusCode::BAD_REQUEST, + JsonRpcForwardedResponse::from_str( + "UserKey was not a ULID or UUID", + Some(StatusCode::BAD_REQUEST.as_u16().into()), + None, + ), + ) + } Self::JoinError(err) => { let code = if err.is_cancelled() { trace!("JoinError. likely shutting down. err={:?}", err); @@ -308,6 +417,17 @@ impl Web3ProxyError { ), ) } + Self::NotImplemented => { + warn!("NotImplemented"); + ( + StatusCode::NOT_IMPLEMENTED, + JsonRpcForwardedResponse::from_str( + "work in progress", + Some(StatusCode::NOT_IMPLEMENTED.as_u16().into()), + None, + ), + ) + } Self::OriginRequired => { warn!("OriginRequired"); ( @@ -330,6 +450,39 @@ impl Web3ProxyError { ), ) } + Self::ParseBytesError(err) => { + warn!("ParseBytesError err={:?}", err); + ( + StatusCode::BAD_REQUEST, + JsonRpcForwardedResponse::from_str( + "parse bytes error!", + Some(StatusCode::BAD_REQUEST.as_u16().into()), + None, + ), + ) + } + Self::ParseMsgError(err) => { + warn!("ParseMsgError err={:?}", err); + ( + StatusCode::BAD_REQUEST, + JsonRpcForwardedResponse::from_str( + "parse message error!", + Some(StatusCode::BAD_REQUEST.as_u16().into()), + None, + ), + ) + } + Self::ParseAddressError => { + warn!("ParseAddressError"); + ( + StatusCode::UNAUTHORIZED, + JsonRpcForwardedResponse::from_str( + "unable to parse address", + Some(StatusCode::BAD_REQUEST.as_u16().into()), + None, + ), + ) + } // TODO: this should actually by the id of the key. multiple users might control one key Self::RateLimited(authorization, retry_at) => { // TODO: emit a stat @@ -510,6 +663,28 @@ impl Web3ProxyError { ), ) } + Self::UserIdZero => { + warn!("UserIdZero"); + ( + StatusCode::BAD_REQUEST, + JsonRpcForwardedResponse::from_str( + "user ids should always be non-zero", + Some(StatusCode::BAD_REQUEST.as_u16().into()), + None, + ), + ) + } + Self::VerificationError(err) => { + warn!("VerificationError err={:?}", err); + ( + StatusCode::BAD_REQUEST, + JsonRpcForwardedResponse::from_str( + "verification error!", + Some(StatusCode::BAD_REQUEST.as_u16().into()), + None, + ), + ) + } Self::WatchRecvError(err) => { error!("WatchRecvError err={:?}", err); ( diff --git a/web3_proxy/src/frontend/users.rs b/web3_proxy/src/frontend/users.rs index e8a43ba3..c405b075 100644 --- a/web3_proxy/src/frontend/users.rs +++ b/web3_proxy/src/frontend/users.rs @@ -1,6 +1,6 @@ //! Handle registration, logins, and managing account data. use super::authorization::{login_is_authorized, RpcSecretKey}; -use super::errors::Web3ProxyResponse; +use super::errors::{Web3ProxyError, Web3ProxyErrorContext, Web3ProxyResponse}; use crate::app::Web3ProxyApp; use crate::http_params::{ get_chain_id_from_params, get_page_from_params, get_query_start_from_params, @@ -9,7 +9,6 @@ use crate::stats::influxdb_queries::query_user_stats; use crate::stats::StatType; use crate::user_token::UserBearerToken; use crate::{PostLogin, PostLoginQuery}; -use anyhow::Context; use axum::headers::{Header, Origin, Referer, UserAgent}; use axum::{ extract::{Path, Query}, @@ -81,11 +80,9 @@ pub async fn user_login_get( // TODO: allow ENS names here? let user_address: Address = params .remove("user_address") - // TODO: map_err so this becomes a 500. routing must be bad - .context("impossible")? + .ok_or(Web3ProxyError::BadRouting)? .parse() - // TODO: map_err so this becomes a 401 - .context("unable to parse address")?; + .or(Err(Web3ProxyError::ParseAddressError))?; let login_domain = app .config @@ -113,7 +110,7 @@ pub async fn user_login_get( resources: vec![], }; - let db_conn = app.db_conn().context("login requires a database")?; + let db_conn = app.db_conn().web3_context("login requires a database")?; // massage types to fit in the database. sea-orm does not make this very elegant let uuid = Uuid::from_u128(nonce.into()); @@ -135,7 +132,7 @@ pub async fn user_login_get( user_pending_login .save(&db_conn) .await - .context("saving user's pending_login")?; + .web3_context("saving user's pending_login")?; // there are multiple ways to sign messages and not all wallets support them // TODO: default message eip from config? @@ -148,8 +145,7 @@ pub async fn user_login_get( "eip191_hash" => Bytes::from(&message.eip191_hash().unwrap()).to_string(), "eip4361" => message.to_string(), _ => { - // TODO: custom error that is handled a 401 - return Err(anyhow::anyhow!("invalid message eip given").into()); + return Err(Web3ProxyError::InvalidEip); } }; @@ -169,9 +165,9 @@ pub async fn user_login_post( login_is_authorized(&app, ip).await?; // 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")?; + let their_sig_bytes = Bytes::from_str(&payload.sig).web3_context("parsing sig")?; if their_sig_bytes.len() != 65 { - return Err(anyhow::anyhow!("checking signature length").into()); + return Err(Web3ProxyError::InvalidSignatureLength); } let mut their_sig: [u8; 65] = [0; 65]; for x in 0..65 { @@ -181,17 +177,18 @@ pub async fn user_login_post( // we can't trust that they didn't tamper with the message in some way. like some clients return it hex encoded // TODO: checking 0x seems fragile, but I think it will be fine. siwe message text shouldn't ever start with 0x let their_msg: Message = if payload.msg.starts_with("0x") { - let their_msg_bytes = Bytes::from_str(&payload.msg).context("parsing payload message")?; + let their_msg_bytes = + Bytes::from_str(&payload.msg).web3_context("parsing payload message")?; // TODO: lossy or no? String::from_utf8_lossy(their_msg_bytes.as_ref()) .parse::() - .context("parsing hex string message")? + .web3_context("parsing hex string message")? } else { payload .msg .parse::() - .context("parsing string message")? + .web3_context("parsing string message")? }; // the only part of the message we will trust is their nonce @@ -199,7 +196,9 @@ pub async fn user_login_post( let login_nonce = UserBearerToken::from_str(&their_msg.nonce)?; // fetch the message we gave them from our database - let db_replica = app.db_replica().context("Getting database connection")?; + let db_replica = app + .db_replica() + .web3_context("Getting database connection")?; // massage type for the db let login_nonce_uuid: Uuid = login_nonce.clone().into(); @@ -208,13 +207,13 @@ pub async fn user_login_post( .filter(pending_login::Column::Nonce.eq(login_nonce_uuid)) .one(db_replica.conn()) .await - .context("database error while finding pending_login")? - .context("login nonce not found")?; + .web3_context("database error while finding pending_login")? + .web3_context("login nonce not found")?; let our_msg: siwe::Message = user_pending_login .message .parse() - .context("parsing siwe message")?; + .web3_context("parsing siwe message")?; // default options are fine. the message includes timestamp and domain and nonce let verify_config = VerificationOpts::default(); @@ -223,16 +222,16 @@ pub async fn user_login_post( if let Err(err_1) = our_msg .verify(&their_sig, &verify_config) .await - .context("verifying signature against our local message") + .web3_context("verifying signature against our local message") { // verification method 1 failed. try eip191 if let Err(err_191) = our_msg .verify_eip191(&their_sig) - .context("verifying eip191 signature against our local message") + .web3_context("verifying eip191 signature against our local message") { let db_conn = app .db_conn() - .context("deleting expired pending logins requires a db")?; + .web3_context("deleting expired pending logins requires a db")?; // delete ALL expired rows. let now = Utc::now(); @@ -244,12 +243,10 @@ pub async fn user_login_post( // TODO: emit a stat? if this is high something weird might be happening debug!("cleared expired pending_logins: {:?}", delete_result); - return Err(anyhow::anyhow!( - "both the primary and eip191 verification failed: {:#?}; {:#?}", - err_1, - err_191 - ) - .into()); + return Err(Web3ProxyError::EipVerificationFailed( + Box::new(err_1), + Box::new(err_191), + )); } } @@ -260,7 +257,7 @@ pub async fn user_login_post( .await .unwrap(); - let db_conn = app.db_conn().context("login requires a db")?; + let db_conn = app.db_conn().web3_context("login requires a db")?; let (u, uks, status_code) = match u { None => { @@ -270,7 +267,7 @@ pub async fn user_login_post( // TODO: more advanced invite codes that set different request/minute and concurrency limits 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()); + return Err(Web3ProxyError::InvalidInviteCode); } } @@ -300,7 +297,7 @@ pub async fn user_login_post( let uk = uk .insert(&txn) .await - .context("Failed saving new user key")?; + .web3_context("Failed saving new user key")?; let uks = vec![uk]; @@ -315,7 +312,7 @@ pub async fn user_login_post( .filter(rpc_key::Column::UserId.eq(u.id)) .all(db_replica.conn()) .await - .context("failed loading user's key")?; + .web3_context("failed loading user's key")?; (u, uks, StatusCode::OK) } @@ -355,7 +352,7 @@ pub async fn user_login_post( user_login .save(&db_conn) .await - .context("saving user login")?; + .web3_context("saving user login")?; if let Err(err) = user_pending_login .into_active_model() @@ -376,7 +373,9 @@ pub async fn user_logout_post( ) -> Web3ProxyResponse { let user_bearer = UserBearerToken::try_from(bearer)?; - let db_conn = app.db_conn().context("database needed for user logout")?; + let db_conn = app + .db_conn() + .web3_context("database needed for user logout")?; if let Err(err) = login::Entity::delete_many() .filter(login::Column::BearerToken.eq(user_bearer.uuid())) @@ -456,7 +455,7 @@ pub async fn user_post( // TODO: what else can we update here? password hash? subscription to newsletter? let user = if user.is_changed() { - let db_conn = app.db_conn().context("Getting database connection")?; + let db_conn = app.db_conn().web3_context("Getting database connection")?; user.save(&db_conn).await? } else { @@ -464,7 +463,7 @@ pub async fn user_post( user }; - let user: user::Model = user.try_into().context("Returning updated user")?; + let user: user::Model = user.try_into().web3_context("Returning updated user")?; Ok(Json(user).into_response()) } @@ -511,13 +510,13 @@ pub async fn rpc_keys_get( let db_replica = app .db_replica() - .context("db_replica is required to fetch a user's keys")?; + .web3_context("db_replica is required to fetch a user's keys")?; let uks = rpc_key::Entity::find() .filter(rpc_key::Column::UserId.eq(user.id)) .all(db_replica.conn()) .await - .context("failed loading user's key")?; + .web3_context("failed loading user's key")?; let response_json = json!({ "user_id": user.id, @@ -539,7 +538,7 @@ pub async fn rpc_keys_delete( let (_user, _semaphore) = app.bearer_is_authorized(bearer).await?; // TODO: think about how cascading deletes and billing should work - Err(anyhow::anyhow!("work in progress").into()) + Err(Web3ProxyError::NotImplemented) } /// the JSON input to the `rpc_keys_management` handler. @@ -572,7 +571,9 @@ pub async fn rpc_keys_management( let (user, _semaphore) = app.bearer_is_authorized(bearer).await?; - let db_replica = app.db_replica().context("getting db for user's keys")?; + let db_replica = app + .db_replica() + .web3_context("getting db for user's keys")?; let mut uk = if let Some(existing_key_id) = payload.key_id { // get the key and make sure it belongs to the user @@ -581,8 +582,8 @@ pub async fn rpc_keys_management( .filter(rpc_key::Column::Id.eq(existing_key_id)) .one(db_replica.conn()) .await - .context("failed loading user's key")? - .context("key does not exist or is not controlled by this bearer token")? + .web3_context("failed loading user's key")? + .web3_context("key does not exist or is not controlled by this bearer token")? .into_active_model() } else { // make a new key @@ -591,7 +592,7 @@ pub async fn rpc_keys_management( let log_level = payload .log_level - .context("log level must be 'none', 'detailed', or 'aggregated'")?; + .web3_context("log level must be 'none', 'detailed', or 'aggregated'")?; rpc_key::ActiveModel { user_id: sea_orm::Set(user.id), @@ -720,9 +721,11 @@ pub async fn rpc_keys_management( } let uk = if uk.is_changed() { - let db_conn = app.db_conn().context("login requires a db")?; + let db_conn = app.db_conn().web3_context("login requires a db")?; - uk.save(&db_conn).await.context("Failed saving user key")? + uk.save(&db_conn) + .await + .web3_context("Failed saving user key")? } else { uk }; @@ -757,13 +760,13 @@ pub async fn user_revert_logs_get( let db_replica = app .db_replica() - .context("getting replica db for user's revert logs")?; + .web3_context("getting replica db for user's revert logs")?; let uks = rpc_key::Entity::find() .filter(rpc_key::Column::UserId.eq(user.id)) .all(db_replica.conn()) .await - .context("failed loading user's key")?; + .web3_context("failed loading user's key")?; // TODO: only select the ids let uks: Vec<_> = uks.into_iter().map(|x| x.id).collect();