remove anyhow from rest of frontend module
This commit is contained in:
parent
3479bf9d06
commit
60c1a6d382
@ -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::<siwe::Message>()
|
||||
.context("parsing hex string message")?
|
||||
.web3_context("parsing hex string message")?
|
||||
} else {
|
||||
payload
|
||||
.msg
|
||||
.parse::<siwe::Message>()
|
||||
.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()))
|
||||
|
@ -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<Self, Self::Err> {
|
||||
if let Ok(ulid) = s.parse::<Ulid>() {
|
||||
@ -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<Option<OwnedSemaphorePermit>> {
|
||||
) -> Web3ProxyResult<Option<OwnedSemaphorePermit>> {
|
||||
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<AuthorizationChecks> {
|
||||
let authorization_checks: Result<_, Arc<anyhow::Error>> = self
|
||||
) -> Web3ProxyResult<AuthorizationChecks> {
|
||||
let authorization_checks: Result<_, Arc<Web3ProxyError>> = 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::<Referer>())
|
||||
.map(|x| {
|
||||
x.trim()
|
||||
.parse::<Referer>()
|
||||
.or(Err(Web3ProxyError::InvalidReferer))
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Some(x)
|
||||
@ -736,7 +742,11 @@ impl Web3ProxyApp {
|
||||
if let Some(allowed_user_agents) = rpc_key_model.allowed_user_agents {
|
||||
let x: Result<Vec<_>, _> = allowed_user_agents
|
||||
.split(',')
|
||||
.map(|x| x.trim().parse::<UserAgent>())
|
||||
.map(|x| {
|
||||
x.trim()
|
||||
.parse::<UserAgent>()
|
||||
.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
|
||||
|
@ -34,7 +34,10 @@ pub enum Web3ProxyError {
|
||||
#[error(ignore)]
|
||||
#[from(ignore)]
|
||||
BadRequest(String),
|
||||
BadRouting,
|
||||
Database(DbErr),
|
||||
#[display(fmt = "{:#?}, {:#?}", _0, _1)]
|
||||
EipVerificationFailed(Box<Web3ProxyError>, Box<Web3ProxyError>),
|
||||
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<Instant>),
|
||||
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<Box<Web3ProxyError>>, 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);
|
||||
(
|
||||
|
@ -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::<siwe::Message>()
|
||||
.context("parsing hex string message")?
|
||||
.web3_context("parsing hex string message")?
|
||||
} else {
|
||||
payload
|
||||
.msg
|
||||
.parse::<siwe::Message>()
|
||||
.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();
|
||||
|
Loading…
Reference in New Issue
Block a user