check for bearer token on /rpc

This commit is contained in:
Bryan Stitt 2022-09-24 04:31:06 +00:00
parent 81deb1103c
commit b254cb7d26
5 changed files with 74 additions and 19 deletions

View File

@ -689,6 +689,10 @@ impl Web3ProxyApp {
Ok(collected)
}
pub fn db_conn(&self) -> Option<&DatabaseConnection> {
self.db_conn.as_ref()
}
pub async fn redis_conn(&self) -> anyhow::Result<redis_rate_limiter::RedisConnection> {
match self.redis_pool.as_ref() {
None => Err(anyhow::anyhow!("no redis server configured")),

View File

@ -1,10 +1,11 @@
use super::errors::FrontendErrorResponse;
use crate::app::{UserKeyData, Web3ProxyApp};
use anyhow::Context;
use axum::headers::{Origin, Referer, UserAgent};
use axum::headers::{authorization::Bearer, Origin, Referer, UserAgent};
use deferred_rate_limiter::DeferredRateLimitResult;
use entities::user_keys;
use ipnet::IpNet;
use redis_rate_limiter::redis::AsyncCommands;
use redis_rate_limiter::RedisRateLimitResult;
use sea_orm::{prelude::Decimal, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter};
use serde::Serialize;
@ -143,6 +144,37 @@ pub async fn login_is_authorized(
Ok(AuthorizedRequest::Ip(db, ip))
}
pub async fn bearer_is_authorized(
app: &Web3ProxyApp,
bearer: Bearer,
ip: IpAddr,
origin: Option<Origin>,
referer: Option<Referer>,
user_agent: Option<UserAgent>,
) -> Result<AuthorizedRequest, FrontendErrorResponse> {
let mut redis_conn = app.redis_conn().await.context("Getting redis connection")?;
// TODO: verify that bearer.token() is a Ulid?
let bearer_cache_key = format!("bearer:{}", bearer.token());
// turn bearer into a user key id
let user_key_id: u64 = redis_conn
.get(bearer_cache_key)
.await
.context("unknown bearer token")?;
let db_conn = app.db_conn().context("Getting database connection")?;
// turn user key id into a user key
let user_key_data = user_keys::Entity::find_by_id(user_key_id)
.one(db_conn)
.await
.context("fetching user key by id")?
.context("unknown user id")?;
key_is_authorized(app, user_key_data.api_key, ip, origin, referer, user_agent).await
}
pub async fn ip_is_authorized(
app: &Web3ProxyApp,
ip: IpAddr,
@ -261,7 +293,7 @@ impl Web3ProxyApp {
.try_get_with(user_key, async move {
trace!(?user_key, "user_cache miss");
let db = self.db_conn.as_ref().context("no database")?;
let db = self.db_conn().context("Getting database connection")?;
// TODO: join the user table to this to return the User? we don't always need it
match user_keys::Entity::find()

View File

@ -46,9 +46,12 @@ pub async fn serve(port: u16, proxy_app: Arc<Web3ProxyApp>) -> anyhow::Result<()
// TODO: these should probbably all start with /rpc. then / can be the static site
let app = Router::new()
// routes should be order most to least common
.route("/rpc", post(rpc_proxy_http::public_proxy_web3_rpc))
.route("/rpc", post(rpc_proxy_http::proxy_web3_rpc))
.route("/rpc", get(rpc_proxy_ws::public_websocket_handler))
.route("/rpc/:user_key", post(rpc_proxy_http::user_proxy_web3_rpc))
.route(
"/rpc/:user_key",
post(rpc_proxy_http::proxy_web3_rpc_with_key),
)
.route("/rpc/:user_key", get(rpc_proxy_ws::user_websocket_handler))
.route("/rpc/health", get(http::health))
.route("/rpc/status", get(http::status))

View File

@ -1,8 +1,9 @@
use super::authorization::{ip_is_authorized, key_is_authorized};
use super::authorization::{bearer_is_authorized, ip_is_authorized, key_is_authorized};
use super::errors::FrontendResult;
use crate::{app::Web3ProxyApp, jsonrpc::JsonRpcRequestEnum};
use axum::extract::Path;
use axum::headers::{Origin, Referer, UserAgent};
use axum::headers::authorization::Bearer;
use axum::headers::{Authorization, Origin, Referer, UserAgent};
use axum::TypedHeader;
use axum::{response::IntoResponse, Extension, Json};
use axum_client_ip::ClientIp;
@ -10,18 +11,30 @@ use std::sync::Arc;
use tracing::{error_span, Instrument};
use uuid::Uuid;
pub async fn public_proxy_web3_rpc(
pub async fn proxy_web3_rpc(
Extension(app): Extension<Arc<Web3ProxyApp>>,
bearer: Option<TypedHeader<Authorization<Bearer>>>,
ClientIp(ip): ClientIp,
Json(payload): Json<JsonRpcRequestEnum>,
origin: Option<TypedHeader<Origin>>,
referer: Option<TypedHeader<Referer>>,
user_agent: Option<TypedHeader<UserAgent>>,
) -> FrontendResult {
let request_span = error_span!("request", %ip, ?referer, ?user_agent);
let authorization = ip_is_authorized(&app, ip)
.instrument(request_span.clone())
.await?;
let authorization = if let Some(TypedHeader(Authorization(bearer))) = bearer {
let origin = origin.map(|x| x.0);
let referer = referer.map(|x| x.0);
let user_agent = user_agent.map(|x| x.0);
bearer_is_authorized(&app, bearer, ip, origin, referer, user_agent)
.instrument(request_span.clone())
.await?
} else {
ip_is_authorized(&app, ip)
.instrument(request_span.clone())
.await?
};
let request_span = error_span!("request", ?authorization);
@ -38,7 +51,7 @@ pub async fn public_proxy_web3_rpc(
Ok(Json(&response).into_response())
}
pub async fn user_proxy_web3_rpc(
pub async fn proxy_web3_rpc_with_key(
Extension(app): Extension<Arc<Web3ProxyApp>>,
ClientIp(ip): ClientIp,
Json(payload): Json<JsonRpcRequestEnum>,

View File

@ -175,7 +175,7 @@ pub async fn post_login(
let bearer_token = Ulid::new();
let db = app.db_conn.as_ref().unwrap();
let db = app.db_conn().context("Getting database connection")?;
// TODO: limit columns or load whole user?
let u = user::Entity::find()
@ -263,11 +263,11 @@ pub async fn get_logout(
Extension(app): Extension<Arc<Web3ProxyApp>>,
TypedHeader(Authorization(bearer)): TypedHeader<Authorization<Bearer>>,
) -> FrontendResult {
let mut redis_conn = app.redis_conn().await?;
// TODO: i don't like this. move this to a helper function so it is less fragile
let bearer_cache_key = format!("bearer:{}", bearer.token());
let mut redis_conn = app.redis_conn().await?;
redis_conn.del(bearer_cache_key).await?;
// TODO: what should the response be? probably json something
@ -310,7 +310,7 @@ pub async fn post_user(
}
}
let db = app.db_conn.as_ref().unwrap();
let db = app.db_conn().context("Getting database connection")?;
user.save(db).await?;
@ -332,16 +332,19 @@ impl ProtectedAction {
async fn verify(
self,
app: &Web3ProxyApp,
// TODO: i don't think we want Bearer here. we want user_key and a helper for bearer -> user_key
bearer: Bearer,
primary_address: &Address,
) -> anyhow::Result<user::Model> {
// get the attached address from redis for the given auth_token.
let bearer_cache_key = format!("bearer:{}", bearer.token());
let mut redis_conn = app.redis_conn().await?;
// TODO: is this type correct?
let u_id: Option<u64> = redis_conn.get(bearer_cache_key).await?;
let bearer_cache_key = format!("bearer:{}", bearer.token());
let user_key_id: Option<u64> = redis_conn
.get(bearer_cache_key)
.await
.context("fetching bearer cache key from redis")?;
// TODO: if auth_address == primary_address, allow
// TODO: if auth_address != primary_address, only allow if they are a secondary user with the correct role