diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 00000000..aaddc2c7 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "1.65.0" \ No newline at end of file diff --git a/web3_proxy/src/frontend/mod.rs b/web3_proxy/src/frontend/mod.rs index 4d94367c..b26e963d 100644 --- a/web3_proxy/src/frontend/mod.rs +++ b/web3_proxy/src/frontend/mod.rs @@ -85,6 +85,7 @@ pub async fn serve(port: u16, proxy_app: Arc) -> anyhow::Result<() get(users::user_stats_aggregated_get), ) .route("/user/stats/detailed", get(users::user_stats_detailed_get)) + .route("/user/modify_role", get(users::admin_change_user_roles)) .route("/user/logout", post(users::user_logout_post)) .route("/status", get(status::status)) // layers are ordered bottom up diff --git a/web3_proxy/src/frontend/users.rs b/web3_proxy/src/frontend/users.rs index 44d066ba..bd57e252 100644 --- a/web3_proxy/src/frontend/users.rs +++ b/web3_proxy/src/frontend/users.rs @@ -3,10 +3,11 @@ use super::authorization::{login_is_authorized, RpcSecretKey}; use super::errors::FrontendResult; use crate::app::Web3ProxyApp; -use crate::user_queries::get_page_from_params; +use crate::user_queries::{get_page_from_params, get_user_id_from_params}; use crate::user_queries::{ get_chain_id_from_params, get_query_start_from_params, query_user_stats, StatResponse, }; +use entities::prelude::{User, SecondaryUser}; use crate::user_token::UserBearerToken; use anyhow::Context; use axum::headers::{Header, Origin, Referer, UserAgent}; @@ -19,14 +20,14 @@ use axum::{ use axum_client_ip::ClientIp; use axum_macros::debug_handler; use chrono::{TimeZone, Utc}; -use entities::sea_orm_active_enums::LogLevel; -use entities::{login, pending_login, revert_log, rpc_key, user}; +use entities::sea_orm_active_enums::{LogLevel, Role}; +use entities::{login, pending_login, revert_log, rpc_key, secondary_user, user, user_tier}; use ethers::{prelude::Address, types::Bytes}; use hashbrown::HashMap; use http::{HeaderValue, StatusCode}; use ipnet::IpNet; use itertools::Itertools; -use log::{debug, warn}; +use log::{debug, info, warn}; use migration::sea_orm::prelude::Uuid; use migration::sea_orm::{ self, ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, PaginatorTrait, QueryFilter, @@ -40,6 +41,7 @@ use std::str::FromStr; use std::sync::Arc; use time::{Duration, OffsetDateTime}; use ulid::Ulid; +use crate::frontend::errors::FrontendErrorResponse; /// `GET /user/login/:user_address` or `GET /user/login/:user_address/:message_eip` -- Start the "Sign In with Ethereum" (siwe) login flow. /// @@ -853,3 +855,135 @@ pub async fn user_stats_detailed_get( Ok(response) } + +/// `GET /user/stats/detailed` -- Use a bearer token to get the user's key stats such as bandwidth used and methods requested. +/// +/// If no bearer is provided, detailed stats for all users will be shown. +/// View a single user with `?user_id=$x`. +/// View a single chain with `?chain_id=$x`. +/// +/// Set `$x` to zero to see all. +/// +/// TODO: this will change as we add better support for secondary users. +#[debug_handler] +pub async fn admin_change_user_roles( + Extension(app): Extension>, + bearer: Option>>, + Query(params): Query>, +) -> FrontendResult { + + // Make sure that the bearer exists, and has admin rights ... + let user_address: Vec = params + .get("user_address") + .ok_or_else(|| + FrontendErrorResponse::StatusCode( + StatusCode::BAD_REQUEST, + "Unable to find user_address key in request".to_string(), + None, + ) + )? + .parse::
() + .map_err(|err| { + FrontendErrorResponse::StatusCode( + StatusCode::BAD_REQUEST, + "Unable to parse user_address as an Address".to_string(), + Some(err.into()), + ) + })? + .to_fixed_bytes().into(); + let user_tier_title = params + .get("user_tier_title") + .ok_or_else(|| FrontendErrorResponse::StatusCode( + StatusCode::BAD_REQUEST, + "Unable to get the user_tier_title key from the request".to_string(), + None, + ))?; + + // Create database connections and all that + let db_conn = app.db_conn().context("admin_change_user_roles needs a db")?; + let db_replica = app + .db_replica() + .context("admin_change_user_roles needs a db replica")?; + let mut redis_conn = app + .redis_conn() + .await + .context("admin_change_user_roles had a redis connection error")? + .context("admin_change_user_roles needs a redis")?; + + // TODO: Make a single query, where you retrieve the user, and directly from it the secondary user (otherwise we do two jumpy, which is unnecessary) + // get the user id first. if it is 0, we should use a cache on the app + let user_id = get_user_id_from_params(&mut redis_conn, &db_conn, &db_replica, bearer, ¶ms).await?; + + let mut response_body = HashMap::new(); + response_body.insert( + "user_id", + serde_json::Value::Number(user_id.into()), + ); + + // Get both the user and user role + let user: user::Model = user::Entity::find_by_id(user_id) + .one(&db_conn) + .await? + .context("No user with this id found!")?; + // TODO: Let's connect the string, and find the previous string of the user id ... (this might be ok for now too thought) + response_body.insert( + "previous_tier", + serde_json::Value::Number(user.user_tier_id.into()), + ); + + // Modify the user-role ... + // Check if this use has admin privileges ... + let user_role: secondary_user::Model = secondary_user::Entity::find() + .filter(secondary_user::Column::UserId.eq(user_id)) + .one(&db_conn) + .await? + .context("No user tier found with that name")?; + println!("User role is: {:?}", user_role); + + // Return error if the user is not an admin or a user + match user_role.role { + Role::Owner | Role::Admin => { + // Change the user tier, we can copy a bunch of the functionality from the user-tier address + + // Check if all the required parameters are included in the request, if not, return an error + let user = user::Entity::find() + .filter(user::Column::Address.eq(user_address)) + .one(&db_conn) + .await? + .context("No user found with that key")?; + + // TODO: don't serialize the rpc key + debug!("user: {:#?}", user); + + let user_tier = user_tier::Entity::find() + .filter(user_tier::Column::Title.eq(user_tier_title.clone())) + .one(&db_conn) + .await? + .context("No user tier found with that name")?; + debug!("user_tier: {:#?}", user_tier); + + if user.user_tier_id == user_tier.id { + info!("user already has that tier"); + } else { + let mut user = user.into_active_model(); + + user.user_tier_id = sea_orm::Set(user_tier.id); + + user.save(&db_conn).await?; + + info!("user's tier changed"); + } + + } + Role::Collaborator => { + return Err(anyhow::anyhow!("you do not have admin rights!").into()); + } + }; + + response_body.insert( + "user_id", + serde_json::Value::Number(user_id.into()), + ); + let mut response = Json(&response_body).into_response(); + Ok(response) +}