finish basic ProtectedAction

This commit is contained in:
Bryan Stitt 2022-10-25 04:12:24 +00:00
parent 618bfeb861
commit 1d24955d6f
4 changed files with 29 additions and 49 deletions

View File

@ -121,7 +121,7 @@ pub struct AppConfig {
/// the stats page url for a logged in user. if set, must contain "{user_id}"
pub redirect_user_url: Option<String>,
/// https://sentry.io
/// Optionally send errors to <https://sentry.io>
pub sentry_url: Option<String>,
}

View File

@ -263,8 +263,6 @@ pub async fn login_is_authorized(
app: &Web3ProxyApp,
ip: IpAddr,
) -> Result<AuthorizedRequest, FrontendErrorResponse> {
// TODO: i think we could write an `impl From` for this
// TODO: move this to an AuthorizedUser extrator
let (ip, _semaphore) = match app.rate_limit_login(ip).await? {
RateLimitResult::AllowedIp(x, semaphore) => (x, semaphore),
RateLimitResult::RateLimitedIp(x, retry_at) => {
@ -277,34 +275,6 @@ pub async fn login_is_authorized(
Ok(AuthorizedRequest::Ip(ip, None))
}
// TODO: where should we use this?
pub async fn bearer_is_authorized(
app: &Web3ProxyApp,
bearer: Bearer,
) -> Result<(AuthorizedRequest, Option<OwnedSemaphorePermit>), 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_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_data = user::Entity::find_by_id(user_id)
.one(&db_conn)
.await
.context("fetching user by id")?
.context("unknown user id")?;
todo!("rewrite this. key_is_authorized is wrong. we should check user ids instead")
}
pub async fn ip_is_authorized(
app: &Web3ProxyApp,
ip: IpAddr,
@ -408,7 +378,7 @@ impl Web3ProxyApp {
pub async fn rate_limit_login(&self, ip: IpAddr) -> anyhow::Result<RateLimitResult> {
// TODO: dry this up with rate_limit_by_key
// TODO: do we ant semafores here?
// TODO: do we want a semaphore here?
if let Some(rate_limiter) = &self.login_rate_limiter {
match rate_limiter.throttle_label(&ip.to_string(), None, 1).await {
Ok(RedisRateLimitResult::Allowed(_)) => Ok(RateLimitResult::AllowedIp(ip, None)),

View File

@ -3,7 +3,6 @@
use super::authorization::{login_is_authorized, UserKey};
use super::errors::FrontendResult;
use crate::app::Web3ProxyApp;
use crate::frontend::authorization::bearer_is_authorized;
use crate::user_queries::{get_aggregate_rpc_stats_from_params, get_detailed_stats};
use anyhow::Context;
use axum::{
@ -358,7 +357,6 @@ pub struct PostUser {
primary_address: Address,
// TODO: make sure the email address is valid. probably have a "verified" column in the database
email: Option<String>,
// TODO: make them sign this JSON? cookie in session id is hard because its on a different domain
}
/// `POST /user/profile` -- modify the account connected to the bearer token in the `Authentication` header.
@ -409,8 +407,6 @@ pub async fn user_balance_get(
Extension(app): Extension<Arc<Web3ProxyApp>>,
TypedHeader(Authorization(bearer)): TypedHeader<Authorization<Bearer>>,
) -> FrontendResult {
let (authorized_request, _semaphore) = bearer_is_authorized(&app, bearer).await?;
todo!("user_balance_get");
}
@ -515,12 +511,7 @@ enum ProtectedAction {
impl ProtectedAction {
/// Verify that the given bearer token and address are allowed to take the specified action.
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,
) -> anyhow::Result<user::Model> {
async fn verify(self, app: &Web3ProxyApp, bearer: Bearer) -> anyhow::Result<user::Model> {
// get the attached address from redis for the given auth_token.
let mut redis_conn = app.redis_conn().await?;
@ -528,13 +519,32 @@ impl ProtectedAction {
let bearer_cache_key = format!("bearer:{}", bearer.token());
// TODO: move this to a helper function
let user_id: Option<u64> = redis_conn
.get(bearer_cache_key)
let user_id: u64 = redis_conn
.get::<_, Option<u64>>(bearer_cache_key)
.await
.context("fetching bearer cache key from redis")?;
.context("fetching bearer cache key from redis")?
.context("unknown bearer token")?;
// 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
todo!("verify token for the given user");
let db_conn = app.db_conn().context("Getting database connection")?;
// turn user key id into a user key
let user_data = user::Entity::find_by_id(user_id)
.one(&db_conn)
.await
.context("fetching user from db by id")?
.context("unknown user id")?;
match self {
Self::PostUser(primary_address) => {
let user_address = Address::from_slice(&user_data.address);
if user_address != primary_address {
// TODO: check secondary users
return Err(anyhow::anyhow!("user address mismatch"));
}
}
}
Ok(user_data)
}
}

View File

@ -259,7 +259,7 @@ impl Web3Connection {
}
/// reconnect to the provider. errors are retried forever with exponential backoff with jitter.
/// We use the "Decorrelated" jitter from https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
/// We use the "Decorrelated" jitter from <https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/>
/// TODO: maybe it would be better to use "Full Jitter". The "Full Jitter" approach uses less work, but slightly more time.
pub async fn retrying_reconnect(
self: &Arc<Self>,