ip, origin, referer, and user agent checks
This commit is contained in:
parent
d55aea2d98
commit
961ccf7cf2
|
@ -5580,6 +5580,7 @@ dependencies = [
|
||||||
"handlebars",
|
"handlebars",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
"http",
|
"http",
|
||||||
|
"ipnet",
|
||||||
"metered",
|
"metered",
|
||||||
"migration",
|
"migration",
|
||||||
"moka",
|
"moka",
|
||||||
|
|
2
TODO.md
2
TODO.md
|
@ -160,7 +160,7 @@ These are roughly in order of completition
|
||||||
- [-] opt-in debug mode that inspects responses for reverts and saves the request to the database for the user.
|
- [-] opt-in debug mode that inspects responses for reverts and saves the request to the database for the user.
|
||||||
- [-] let them choose a % to log (or maybe x/second). someone like curve logging all reverts will be a BIG database very quickly
|
- [-] let them choose a % to log (or maybe x/second). someone like curve logging all reverts will be a BIG database very quickly
|
||||||
- this must be opt-in or spawned since it will slow things down and will make their calls less private
|
- this must be opt-in or spawned since it will slow things down and will make their calls less private
|
||||||
- [-] Api keys need option to lock to IP, cors header, referer, etc
|
- [-] Api keys need option to lock to IP, cors header, referer, user agent, etc
|
||||||
- [ ] active requests per second per api key
|
- [ ] active requests per second per api key
|
||||||
- [ ] distribution of methods per api key (eth_call, eth_getLogs, etc.)
|
- [ ] distribution of methods per api key (eth_call, eth_getLogs, etc.)
|
||||||
- [-] add configurable size limits to all the Caches
|
- [-] add configurable size limits to all the Caches
|
||||||
|
|
|
@ -15,7 +15,11 @@ pub struct Model {
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub private_txs: bool,
|
pub private_txs: bool,
|
||||||
pub active: bool,
|
pub active: bool,
|
||||||
pub requests_per_minute: u64,
|
pub requests_per_minute: Option<u64>,
|
||||||
|
pub allowed_ips: Option<String>,
|
||||||
|
pub allowed_origins: Option<String>,
|
||||||
|
pub allowed_referers: Option<String>,
|
||||||
|
pub allowed_user_agents: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
|
|
@ -16,15 +16,20 @@ impl MigrationTrait for Migration {
|
||||||
.modify_column(
|
.modify_column(
|
||||||
ColumnDef::new(UserKeys::RequestsPerMinute)
|
ColumnDef::new(UserKeys::RequestsPerMinute)
|
||||||
.big_unsigned()
|
.big_unsigned()
|
||||||
.not_null(),
|
.null(),
|
||||||
)
|
)
|
||||||
// add a column for logging reverts in the RevertLogs table
|
// add a column for logging reverts in the RevertLogs table
|
||||||
.add_column(
|
.add_column(
|
||||||
ColumnDef::new(UserKeys::LogReverts)
|
ColumnDef::new(UserKeys::LogReverts)
|
||||||
.boolean()
|
.decimal_len(5, 4)
|
||||||
.not_null()
|
.not_null()
|
||||||
.default(false),
|
.default("0.0"),
|
||||||
)
|
)
|
||||||
|
// add columns for more advanced authorization
|
||||||
|
.add_column(ColumnDef::new(UserKeys::AllowedIps).text().null())
|
||||||
|
.add_column(ColumnDef::new(UserKeys::AllowedOrigins).text().null())
|
||||||
|
.add_column(ColumnDef::new(UserKeys::AllowedReferers).text().null())
|
||||||
|
.add_column(ColumnDef::new(UserKeys::AllowedUserAgents).text().null())
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -97,6 +102,7 @@ impl MigrationTrait for Migration {
|
||||||
pub enum UserKeys {
|
pub enum UserKeys {
|
||||||
Table,
|
Table,
|
||||||
Id,
|
Id,
|
||||||
|
// we don't touch some of the columns
|
||||||
// UserId,
|
// UserId,
|
||||||
// ApiKey,
|
// ApiKey,
|
||||||
// Description,
|
// Description,
|
||||||
|
@ -104,6 +110,10 @@ pub enum UserKeys {
|
||||||
// Active,
|
// Active,
|
||||||
RequestsPerMinute,
|
RequestsPerMinute,
|
||||||
LogReverts,
|
LogReverts,
|
||||||
|
AllowedIps,
|
||||||
|
AllowedOrigins,
|
||||||
|
AllowedReferers,
|
||||||
|
AllowedUserAgents,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Iden)]
|
#[derive(Iden)]
|
||||||
|
|
|
@ -36,6 +36,7 @@ flume = "0.10.14"
|
||||||
futures = { version = "0.3.24", features = ["thread-pool"] }
|
futures = { version = "0.3.24", features = ["thread-pool"] }
|
||||||
hashbrown = { version = "0.12.3", features = ["serde"] }
|
hashbrown = { version = "0.12.3", features = ["serde"] }
|
||||||
http = "0.2.8"
|
http = "0.2.8"
|
||||||
|
ipnet = "*"
|
||||||
metered = { version = "0.9.0", features = ["serialize"] }
|
metered = { version = "0.9.0", features = ["serialize"] }
|
||||||
moka = { version = "0.9.4", default-features = false, features = ["future"] }
|
moka = { version = "0.9.4", default-features = false, features = ["future"] }
|
||||||
notify = "5.0.0"
|
notify = "5.0.0"
|
||||||
|
|
|
@ -24,6 +24,7 @@ use futures::stream::FuturesUnordered;
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
|
use ipnet::IpNet;
|
||||||
use metered::{metered, ErrorCount, HitCount, ResponseTime, Throughput};
|
use metered::{metered, ErrorCount, HitCount, ResponseTime, Throughput};
|
||||||
use migration::{Migrator, MigratorTrait};
|
use migration::{Migrator, MigratorTrait};
|
||||||
use moka::future::Cache;
|
use moka::future::Cache;
|
||||||
|
@ -66,14 +67,16 @@ pub type AnyhowJoinHandle<T> = JoinHandle<anyhow::Result<T>>;
|
||||||
/// TODO: rename this?
|
/// TODO: rename this?
|
||||||
pub struct UserKeyData {
|
pub struct UserKeyData {
|
||||||
pub user_key_id: u64,
|
pub user_key_id: u64,
|
||||||
/// if None, allow unlimited queries
|
/// if u64::MAX, allow unlimited queries
|
||||||
pub user_count_per_period: Option<u64>,
|
pub user_max_requests_per_period: Option<u64>,
|
||||||
|
/// if None, allow any Origin
|
||||||
|
pub allowed_origins: Option<Vec<String>>,
|
||||||
/// if None, allow any Referer
|
/// if None, allow any Referer
|
||||||
pub allowed_referer: Option<Referer>,
|
pub allowed_referers: Option<Vec<Referer>>,
|
||||||
/// if None, allow any UserAgent
|
/// if None, allow any UserAgent
|
||||||
pub allowed_user_agent: Option<UserAgent>,
|
pub allowed_user_agents: Option<Vec<UserAgent>>,
|
||||||
/// if None, allow any IpAddr
|
/// if None, allow any IP Address
|
||||||
pub allowed_ip: Option<IpAddr>,
|
pub allowed_ips: Option<Vec<IpNet>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The application
|
/// The application
|
||||||
|
|
|
@ -210,10 +210,10 @@ mod tests {
|
||||||
let app_config = TopConfig {
|
let app_config = TopConfig {
|
||||||
app: AppConfig {
|
app: AppConfig {
|
||||||
chain_id: 31337,
|
chain_id: 31337,
|
||||||
default_requests_per_minute: 6_000_000,
|
default_requests_per_minute: Some(6_000_000),
|
||||||
min_sum_soft_limit: 1,
|
min_sum_soft_limit: 1,
|
||||||
min_synced_rpcs: 1,
|
min_synced_rpcs: 1,
|
||||||
public_rate_limit_per_minute: 6_000_000,
|
public_rate_limit_per_minute: 1_000_000,
|
||||||
response_cache_max_bytes: 10_usize.pow(7),
|
response_cache_max_bytes: 10_usize.pow(7),
|
||||||
redirect_public_url: "example.com/".to_string(),
|
redirect_public_url: "example.com/".to_string(),
|
||||||
redirect_user_url: "example.com/{{user_id}}".to_string(),
|
redirect_user_url: "example.com/{{user_id}}".to_string(),
|
||||||
|
|
|
@ -7,11 +7,6 @@ use tracing::info;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use web3_proxy::users::new_api_key;
|
use web3_proxy::users::new_api_key;
|
||||||
|
|
||||||
/// default to max int which the code sees as "unlimited" requests
|
|
||||||
fn default_rpm() -> u64 {
|
|
||||||
u64::MAX
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(FromArgs, PartialEq, Debug, Eq)]
|
#[derive(FromArgs, PartialEq, Debug, Eq)]
|
||||||
/// Create a new user and api key
|
/// Create a new user and api key
|
||||||
#[argh(subcommand, name = "create_user")]
|
#[argh(subcommand, name = "create_user")]
|
||||||
|
@ -29,9 +24,10 @@ pub struct CreateUserSubCommand {
|
||||||
/// If none given, one will be generated randomly.
|
/// If none given, one will be generated randomly.
|
||||||
api_key: Uuid,
|
api_key: Uuid,
|
||||||
|
|
||||||
#[argh(option, default = "default_rpm()")]
|
#[argh(option)]
|
||||||
/// maximum requests per minute
|
/// maximum requests per minute
|
||||||
rpm: u64,
|
/// default to "None" which the code sees as "unlimited" requests
|
||||||
|
rpm: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateUserSubCommand {
|
impl CreateUserSubCommand {
|
||||||
|
|
|
@ -54,8 +54,7 @@ pub struct AppConfig {
|
||||||
/// If none, the minimum * 2 is used
|
/// If none, the minimum * 2 is used
|
||||||
pub db_max_connections: Option<u32>,
|
pub db_max_connections: Option<u32>,
|
||||||
pub influxdb_url: Option<String>,
|
pub influxdb_url: Option<String>,
|
||||||
#[serde(default = "default_default_requests_per_minute")]
|
pub default_requests_per_minute: Option<u64>,
|
||||||
pub default_requests_per_minute: u64,
|
|
||||||
pub invite_code: Option<String>,
|
pub invite_code: Option<String>,
|
||||||
#[serde(default = "default_min_sum_soft_limit")]
|
#[serde(default = "default_min_sum_soft_limit")]
|
||||||
pub min_sum_soft_limit: u32,
|
pub min_sum_soft_limit: u32,
|
||||||
|
@ -76,12 +75,6 @@ pub struct AppConfig {
|
||||||
pub redirect_user_url: String,
|
pub redirect_user_url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// default to unlimited requests
|
|
||||||
/// TODO: pick a lower limit so we don't get DOSd
|
|
||||||
fn default_default_requests_per_minute() -> u64 {
|
|
||||||
u64::MAX
|
|
||||||
}
|
|
||||||
|
|
||||||
fn default_min_sum_soft_limit() -> u32 {
|
fn default_min_sum_soft_limit() -> u32 {
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
use super::errors::FrontendErrorResponse;
|
use super::errors::FrontendErrorResponse;
|
||||||
use crate::app::{UserKeyData, Web3ProxyApp};
|
use crate::app::{UserKeyData, Web3ProxyApp};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use axum::headers::{Referer, UserAgent};
|
use axum::headers::{Origin, Referer, UserAgent};
|
||||||
use deferred_rate_limiter::DeferredRateLimitResult;
|
use deferred_rate_limiter::DeferredRateLimitResult;
|
||||||
use entities::user_keys;
|
use entities::user_keys;
|
||||||
use sea_orm::{
|
use ipnet::IpNet;
|
||||||
ColumnTrait, DatabaseConnection, DeriveColumn, EntityTrait, EnumIter, IdenStatic, QueryFilter,
|
use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter};
|
||||||
QuerySelect,
|
|
||||||
};
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::{net::IpAddr, sync::Arc};
|
use std::{net::IpAddr, sync::Arc};
|
||||||
use tokio::time::Instant;
|
use tokio::time::Instant;
|
||||||
use tracing::{error, trace, warn};
|
use tracing::{error, trace};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -31,6 +29,7 @@ pub enum RateLimitResult {
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct AuthorizedKey {
|
pub struct AuthorizedKey {
|
||||||
ip: IpAddr,
|
ip: IpAddr,
|
||||||
|
origin: Option<String>,
|
||||||
user_key_id: u64,
|
user_key_id: u64,
|
||||||
// TODO: what else?
|
// TODO: what else?
|
||||||
}
|
}
|
||||||
|
@ -38,14 +37,64 @@ pub struct AuthorizedKey {
|
||||||
impl AuthorizedKey {
|
impl AuthorizedKey {
|
||||||
pub fn try_new(
|
pub fn try_new(
|
||||||
ip: IpAddr,
|
ip: IpAddr,
|
||||||
user_data: UserKeyData,
|
origin: Option<Origin>,
|
||||||
referer: Option<Referer>,
|
referer: Option<Referer>,
|
||||||
user_agent: Option<UserAgent>,
|
user_agent: Option<UserAgent>,
|
||||||
|
user_data: UserKeyData,
|
||||||
) -> anyhow::Result<Self> {
|
) -> anyhow::Result<Self> {
|
||||||
warn!("todo: check referer and user_agent against user_data");
|
// check ip
|
||||||
|
match &user_data.allowed_ips {
|
||||||
|
None => {}
|
||||||
|
Some(allowed_ips) => {
|
||||||
|
if !allowed_ips.iter().any(|x| x.contains(&ip)) {
|
||||||
|
return Err(anyhow::anyhow!("IP is not allowed!"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check origin
|
||||||
|
// TODO: do this with the Origin type instead of a String?
|
||||||
|
let origin = origin.map(|x| x.to_string());
|
||||||
|
match (&origin, &user_data.allowed_origins) {
|
||||||
|
(None, None) => {}
|
||||||
|
(Some(_), None) => {}
|
||||||
|
(None, Some(_)) => return Err(anyhow::anyhow!("Origin required")),
|
||||||
|
(Some(origin), Some(allowed_origins)) => {
|
||||||
|
let origin = origin.to_string();
|
||||||
|
|
||||||
|
if !allowed_origins.contains(&origin) {
|
||||||
|
return Err(anyhow::anyhow!("IP is not allowed!"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check referer
|
||||||
|
match (referer, &user_data.allowed_referers) {
|
||||||
|
(None, None) => {}
|
||||||
|
(Some(_), None) => {}
|
||||||
|
(None, Some(_)) => return Err(anyhow::anyhow!("Referer required")),
|
||||||
|
(Some(referer), Some(allowed_referers)) => {
|
||||||
|
if !allowed_referers.contains(&referer) {
|
||||||
|
return Err(anyhow::anyhow!("Referer is not allowed!"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check user_agent
|
||||||
|
match (user_agent, &user_data.allowed_user_agents) {
|
||||||
|
(None, None) => {}
|
||||||
|
(Some(_), None) => {}
|
||||||
|
(None, Some(_)) => return Err(anyhow::anyhow!("User agent required")),
|
||||||
|
(Some(user_agent), Some(allowed_user_agents)) => {
|
||||||
|
if !allowed_user_agents.contains(&user_agent) {
|
||||||
|
return Err(anyhow::anyhow!("User agent is not allowed!"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
ip,
|
ip,
|
||||||
|
origin,
|
||||||
user_key_id: user_data.user_key_id,
|
user_key_id: user_data.user_key_id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -62,14 +111,12 @@ pub enum AuthorizedRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AuthorizedRequest {
|
impl AuthorizedRequest {
|
||||||
pub fn has_db(&self) -> bool {
|
pub fn db_conn(&self) -> Option<&DatabaseConnection> {
|
||||||
let db_conn = match self {
|
match self {
|
||||||
Self::Internal(db_conn) => db_conn,
|
Self::Internal(x) => x.as_ref(),
|
||||||
Self::Ip(db_conn, _) => db_conn,
|
Self::Ip(x, _) => x.as_ref(),
|
||||||
Self::User(db_conn, _) => db_conn,
|
Self::User(x, _) => x.as_ref(),
|
||||||
};
|
}
|
||||||
|
|
||||||
db_conn.is_some()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,6 +143,7 @@ pub async fn key_is_authorized(
|
||||||
app: &Web3ProxyApp,
|
app: &Web3ProxyApp,
|
||||||
user_key: Uuid,
|
user_key: Uuid,
|
||||||
ip: IpAddr,
|
ip: IpAddr,
|
||||||
|
origin: Option<Origin>,
|
||||||
referer: Option<Referer>,
|
referer: Option<Referer>,
|
||||||
user_agent: Option<UserAgent>,
|
user_agent: Option<UserAgent>,
|
||||||
) -> Result<AuthorizedRequest, FrontendErrorResponse> {
|
) -> Result<AuthorizedRequest, FrontendErrorResponse> {
|
||||||
|
@ -110,7 +158,7 @@ pub async fn key_is_authorized(
|
||||||
x => unimplemented!("rate_limit_by_key shouldn't ever see these: {:?}", x),
|
x => unimplemented!("rate_limit_by_key shouldn't ever see these: {:?}", x),
|
||||||
};
|
};
|
||||||
|
|
||||||
let authorized_user = AuthorizedKey::try_new(ip, user_data, referer, user_agent)?;
|
let authorized_user = AuthorizedKey::try_new(ip, origin, referer, user_agent, user_data)?;
|
||||||
|
|
||||||
let db = app.db_conn.clone();
|
let db = app.db_conn.clone();
|
||||||
|
|
||||||
|
@ -159,50 +207,76 @@ impl Web3ProxyApp {
|
||||||
|
|
||||||
let db = self.db_conn.as_ref().context("no database")?;
|
let db = self.db_conn.as_ref().context("no database")?;
|
||||||
|
|
||||||
/// helper enum for querying just a few columns instead of the entire table
|
|
||||||
/// TODO: query more! we need allowed ips, referers, and probably other things
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
|
||||||
enum QueryAs {
|
|
||||||
Id,
|
|
||||||
RequestsPerMinute,
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: join the user table to this to return the User? we don't always need it
|
// TODO: join the user table to this to return the User? we don't always need it
|
||||||
match user_keys::Entity::find()
|
match user_keys::Entity::find()
|
||||||
.select_only()
|
|
||||||
.column_as(user_keys::Column::Id, QueryAs::Id)
|
|
||||||
.column_as(
|
|
||||||
user_keys::Column::RequestsPerMinute,
|
|
||||||
QueryAs::RequestsPerMinute,
|
|
||||||
)
|
|
||||||
.filter(user_keys::Column::ApiKey.eq(user_key))
|
.filter(user_keys::Column::ApiKey.eq(user_key))
|
||||||
.filter(user_keys::Column::Active.eq(true))
|
.filter(user_keys::Column::Active.eq(true))
|
||||||
.into_values::<_, QueryAs>()
|
|
||||||
.one(db)
|
.one(db)
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
Some((user_key_id, requests_per_minute)) => {
|
Some(user_key_model) => {
|
||||||
// TODO: add a column here for max, or is u64::MAX fine?
|
let allowed_ips: Option<Vec<IpNet>> =
|
||||||
let user_count_per_period = if requests_per_minute == u64::MAX {
|
user_key_model.allowed_ips.map(|allowed_ips| {
|
||||||
None
|
serde_json::from_str::<Vec<String>>(&allowed_ips)
|
||||||
} else {
|
.expect("allowed_ips should always parse")
|
||||||
Some(requests_per_minute)
|
.into_iter()
|
||||||
};
|
// TODO: try_for_each
|
||||||
|
.map(|x| {
|
||||||
|
x.parse::<IpNet>().expect("ip address should always parse")
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: should this be an Option<Vec<Origin>>?
|
||||||
|
let allowed_origins =
|
||||||
|
user_key_model.allowed_origins.map(|allowed_origins| {
|
||||||
|
serde_json::from_str::<Vec<String>>(&allowed_origins)
|
||||||
|
.expect("allowed_origins should always parse")
|
||||||
|
});
|
||||||
|
|
||||||
|
let allowed_referers =
|
||||||
|
user_key_model.allowed_referers.map(|allowed_referers| {
|
||||||
|
serde_json::from_str::<Vec<String>>(&allowed_referers)
|
||||||
|
.expect("allowed_referers should always parse")
|
||||||
|
.into_iter()
|
||||||
|
// TODO: try_for_each
|
||||||
|
.map(|x| {
|
||||||
|
x.parse::<Referer>().expect("referer should always parse")
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
});
|
||||||
|
|
||||||
|
let allowed_user_agents =
|
||||||
|
user_key_model
|
||||||
|
.allowed_user_agents
|
||||||
|
.map(|allowed_user_agents| {
|
||||||
|
serde_json::from_str::<Vec<String>>(&allowed_user_agents)
|
||||||
|
.expect("allowed_user_agents should always parse")
|
||||||
|
.into_iter()
|
||||||
|
// TODO: try_for_each
|
||||||
|
.map(|x| {
|
||||||
|
x.parse::<UserAgent>()
|
||||||
|
.expect("user agent should always parse")
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
});
|
||||||
|
|
||||||
Ok(UserKeyData {
|
Ok(UserKeyData {
|
||||||
user_key_id,
|
user_key_id: user_key_model.id,
|
||||||
user_count_per_period,
|
user_max_requests_per_period: user_key_model.requests_per_minute,
|
||||||
allowed_ip: None,
|
allowed_ips,
|
||||||
allowed_referer: None,
|
allowed_origins,
|
||||||
allowed_user_agent: None,
|
allowed_referers,
|
||||||
|
allowed_user_agents,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
None => Ok(UserKeyData {
|
None => Ok(UserKeyData {
|
||||||
user_key_id: 0,
|
user_key_id: 0,
|
||||||
user_count_per_period: Some(0),
|
user_max_requests_per_period: Some(0),
|
||||||
allowed_ip: None,
|
allowed_ips: None,
|
||||||
allowed_referer: None,
|
allowed_origins: None,
|
||||||
allowed_user_agent: None,
|
allowed_referers: None,
|
||||||
|
allowed_user_agents: None,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -219,7 +293,7 @@ impl Web3ProxyApp {
|
||||||
return Ok(RateLimitResult::UnknownKey);
|
return Ok(RateLimitResult::UnknownKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
let user_count_per_period = match user_data.user_count_per_period {
|
let user_max_requests_per_period = match user_data.user_max_requests_per_period {
|
||||||
None => return Ok(RateLimitResult::AllowedUser(user_data)),
|
None => return Ok(RateLimitResult::AllowedUser(user_data)),
|
||||||
Some(x) => x,
|
Some(x) => x,
|
||||||
};
|
};
|
||||||
|
@ -227,7 +301,7 @@ impl Web3ProxyApp {
|
||||||
// user key is valid. now check rate limits
|
// user key is valid. now check rate limits
|
||||||
if let Some(rate_limiter) = &self.frontend_key_rate_limiter {
|
if let Some(rate_limiter) = &self.frontend_key_rate_limiter {
|
||||||
match rate_limiter
|
match rate_limiter
|
||||||
.throttle(user_key, Some(user_count_per_period), 1)
|
.throttle(user_key, Some(user_max_requests_per_period), 1)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(DeferredRateLimitResult::Allowed) => Ok(RateLimitResult::AllowedUser(user_data)),
|
Ok(DeferredRateLimitResult::Allowed) => Ok(RateLimitResult::AllowedUser(user_data)),
|
||||||
|
|
|
@ -2,7 +2,7 @@ use super::authorization::{ip_is_authorized, key_is_authorized};
|
||||||
use super::errors::FrontendResult;
|
use super::errors::FrontendResult;
|
||||||
use crate::{app::Web3ProxyApp, jsonrpc::JsonRpcRequestEnum};
|
use crate::{app::Web3ProxyApp, jsonrpc::JsonRpcRequestEnum};
|
||||||
use axum::extract::Path;
|
use axum::extract::Path;
|
||||||
use axum::headers::{Referer, UserAgent};
|
use axum::headers::{Origin, Referer, UserAgent};
|
||||||
use axum::TypedHeader;
|
use axum::TypedHeader;
|
||||||
use axum::{response::IntoResponse, Extension, Json};
|
use axum::{response::IntoResponse, Extension, Json};
|
||||||
use axum_client_ip::ClientIp;
|
use axum_client_ip::ClientIp;
|
||||||
|
@ -42,6 +42,7 @@ pub async fn user_proxy_web3_rpc(
|
||||||
Extension(app): Extension<Arc<Web3ProxyApp>>,
|
Extension(app): Extension<Arc<Web3ProxyApp>>,
|
||||||
ClientIp(ip): ClientIp,
|
ClientIp(ip): ClientIp,
|
||||||
Json(payload): Json<JsonRpcRequestEnum>,
|
Json(payload): Json<JsonRpcRequestEnum>,
|
||||||
|
origin: Option<TypedHeader<Origin>>,
|
||||||
referer: Option<TypedHeader<Referer>>,
|
referer: Option<TypedHeader<Referer>>,
|
||||||
user_agent: Option<TypedHeader<UserAgent>>,
|
user_agent: Option<TypedHeader<UserAgent>>,
|
||||||
Path(user_key): Path<Uuid>,
|
Path(user_key): Path<Uuid>,
|
||||||
|
@ -53,6 +54,7 @@ pub async fn user_proxy_web3_rpc(
|
||||||
&app,
|
&app,
|
||||||
user_key,
|
user_key,
|
||||||
ip,
|
ip,
|
||||||
|
origin.map(|x| x.0),
|
||||||
referer.map(|x| x.0),
|
referer.map(|x| x.0),
|
||||||
user_agent.map(|x| x.0),
|
user_agent.map(|x| x.0),
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use super::authorization::{ip_is_authorized, key_is_authorized, AuthorizedRequest};
|
use super::authorization::{ip_is_authorized, key_is_authorized, AuthorizedRequest};
|
||||||
use super::errors::FrontendResult;
|
use super::errors::FrontendResult;
|
||||||
use axum::headers::{Referer, UserAgent};
|
use axum::headers::{Origin, Referer, UserAgent};
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::ws::{Message, WebSocket, WebSocketUpgrade},
|
extract::ws::{Message, WebSocket, WebSocketUpgrade},
|
||||||
extract::Path,
|
extract::Path,
|
||||||
|
@ -57,6 +57,7 @@ pub async fn user_websocket_handler(
|
||||||
Extension(app): Extension<Arc<Web3ProxyApp>>,
|
Extension(app): Extension<Arc<Web3ProxyApp>>,
|
||||||
ClientIp(ip): ClientIp,
|
ClientIp(ip): ClientIp,
|
||||||
Path(user_key): Path<Uuid>,
|
Path(user_key): Path<Uuid>,
|
||||||
|
origin: Option<TypedHeader<Origin>>,
|
||||||
referer: Option<TypedHeader<Referer>>,
|
referer: Option<TypedHeader<Referer>>,
|
||||||
user_agent: Option<TypedHeader<UserAgent>>,
|
user_agent: Option<TypedHeader<UserAgent>>,
|
||||||
ws_upgrade: Option<WebSocketUpgrade>,
|
ws_upgrade: Option<WebSocketUpgrade>,
|
||||||
|
@ -65,6 +66,7 @@ pub async fn user_websocket_handler(
|
||||||
&app,
|
&app,
|
||||||
user_key,
|
user_key,
|
||||||
ip,
|
ip,
|
||||||
|
origin.map(|x| x.0),
|
||||||
referer.map(|x| x.0),
|
referer.map(|x| x.0),
|
||||||
user_agent.map(|x| x.0),
|
user_agent.map(|x| x.0),
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,6 +2,7 @@ use super::connection::Web3Connection;
|
||||||
use super::provider::Web3Provider;
|
use super::provider::Web3Provider;
|
||||||
use crate::frontend::authorization::AuthorizedRequest;
|
use crate::frontend::authorization::AuthorizedRequest;
|
||||||
use crate::metered::{JsonRpcErrorCount, ProviderErrorCount};
|
use crate::metered::{JsonRpcErrorCount, ProviderErrorCount};
|
||||||
|
use anyhow::Context;
|
||||||
use ethers::providers::{HttpClientError, ProviderError, WsClientError};
|
use ethers::providers::{HttpClientError, ProviderError, WsClientError};
|
||||||
use metered::metered;
|
use metered::metered;
|
||||||
use metered::HitCount;
|
use metered::HitCount;
|
||||||
|
@ -63,6 +64,8 @@ impl AuthorizedRequest {
|
||||||
where
|
where
|
||||||
T: Clone + fmt::Debug + serde::Serialize + Send + Sync + 'static,
|
T: Clone + fmt::Debug + serde::Serialize + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
|
let db_conn = self.db_conn().context("db_conn needed to save reverts")?;
|
||||||
|
|
||||||
todo!("save the revert to the database");
|
todo!("save the revert to the database");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -158,7 +161,8 @@ impl OpenRequestHandle {
|
||||||
let error_handler = if let RequestErrorHandler::SaveReverts(save_chance) = error_handler
|
let error_handler = if let RequestErrorHandler::SaveReverts(save_chance) = error_handler
|
||||||
{
|
{
|
||||||
if ["eth_call", "eth_estimateGas"].contains(&method)
|
if ["eth_call", "eth_estimateGas"].contains(&method)
|
||||||
&& self.authorization.has_db()
|
&& self.authorization.db_conn().is_some()
|
||||||
|
&& save_chance != 0.0
|
||||||
&& (save_chance == 1.0
|
&& (save_chance == 1.0
|
||||||
|| rand::thread_rng().gen_range(0.0..=1.0) <= save_chance)
|
|| rand::thread_rng().gen_range(0.0..=1.0) <= save_chance)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue