diff --git a/entities/src/credits.rs b/entities/src/admin.rs similarity index 79% rename from entities/src/credits.rs rename to entities/src/admin.rs index 0cd1c772..d1d46999 100644 --- a/entities/src/credits.rs +++ b/entities/src/admin.rs @@ -1,13 +1,13 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.6 use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] -#[sea_orm(table_name = "credits")] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] +#[sea_orm(table_name = "admin")] pub struct Model { #[sea_orm(primary_key)] - pub id: i32, - pub credits: u64, + pub id: u64, #[sea_orm(unique)] pub user_id: u64, } diff --git a/entities/src/login.rs b/entities/src/login.rs index e2600e84..82a78e42 100644 --- a/entities/src/login.rs +++ b/entities/src/login.rs @@ -1,6 +1,5 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.5 +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.6 -use crate::serialization; use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; @@ -10,8 +9,7 @@ pub struct Model { #[sea_orm(primary_key)] pub id: u64, #[sea_orm(unique)] - #[serde(serialize_with = "serialization::uuid_as_ulid")] - pub bearer_token: Uuid, + pub bearer_token: Vec, pub user_id: u64, pub expires_at: DateTimeUtc, } diff --git a/entities/src/mod.rs b/entities/src/mod.rs index 4b97c952..07d58029 100644 --- a/entities/src/mod.rs +++ b/entities/src/mod.rs @@ -1,8 +1,9 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.5 +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.10.6 pub mod prelude; pub mod credits; +pub mod admin; pub mod login; pub mod pending_login; pub mod referral; @@ -12,6 +13,5 @@ pub mod rpc_key; pub mod rpc_request; pub mod sea_orm_active_enums; pub mod secondary_user; -pub mod serialization; pub mod user; pub mod user_tier; diff --git a/migration/src/lib.rs b/migration/src/lib.rs index 833d1a2c..4b907098 100644 --- a/migration/src/lib.rs +++ b/migration/src/lib.rs @@ -13,9 +13,7 @@ mod m20221108_200345_save_anon_stats; mod m20221211_124002_request_method_privacy; mod m20221213_134158_move_login_into_database; mod m20230119_204135_better_free_tier; -mod m20230205_130035_create_credits; -mod m20230205_133204_create_requests; -mod m20230205_133755_create_referrals; +mod m20230117_191358_admin_table; pub struct Migrator; @@ -35,10 +33,7 @@ impl MigratorTrait for Migrator { Box::new(m20221108_200345_save_anon_stats::Migration), Box::new(m20221211_124002_request_method_privacy::Migration), Box::new(m20221213_134158_move_login_into_database::Migration), - Box::new(m20230119_204135_better_free_tier::Migration), - Box::new(m20230205_130035_create_credits::Migration), - Box::new(m20230205_133204_create_requests::Migration), - Box::new(m20230205_133755_create_referrals::Migration), + Box::new(m20230117_191358_admin_table::Migration), ] } } diff --git a/migration/src/m20230117_191358_admin_table.rs b/migration/src/m20230117_191358_admin_table.rs new file mode 100644 index 00000000..2505d314 --- /dev/null +++ b/migration/src/m20230117_191358_admin_table.rs @@ -0,0 +1,58 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + // Replace the sample below with your own migration scripts + manager + .create_table( + Table::create() + .table(Admin::Table) + .col( + ColumnDef::new(Admin::Id) + .big_unsigned() + .not_null() + .auto_increment() + .primary_key(), + ) + .col( + ColumnDef::new(Admin::UserId) + .big_unsigned() + .unique_key() + .not_null() + ) + .foreign_key( + ForeignKey::create() + .name("fk-admin-user_id") + .from(Admin::Table, Admin::UserId) + .to(User::Table, User::Id), + ) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + // Replace the sample below with your own migration scripts + manager + .drop_table(Table::drop().table(Admin::Table).to_owned()) + .await + } +} + +/// Learn more at https://docs.rs/sea-query#iden +#[derive(Iden)] +enum User { + Table, + Id +} + +#[derive(Iden)] +enum Admin { + Table, + Id, + UserId, +} diff --git a/web3_proxy/src/admin_queries.rs b/web3_proxy/src/admin_queries.rs new file mode 100644 index 00000000..c23e79a3 --- /dev/null +++ b/web3_proxy/src/admin_queries.rs @@ -0,0 +1,124 @@ +use crate::app::Web3ProxyApp; +use crate::frontend::errors::FrontendErrorResponse; +use crate::user_queries::get_user_id_from_params; +use anyhow::Context; +use axum::{ + Json, + headers::{authorization::Bearer, Authorization}, + TypedHeader, +}; +use axum::response::{IntoResponse, Response}; +use entities::{admin, user, user_tier}; +use ethers::prelude::Address; +use hashbrown::HashMap; +use http::StatusCode; +use migration::sea_orm::{self, IntoActiveModel}; +use log::info; + + +pub async fn query_admin_modify_usertier<'a>( + app: &'a Web3ProxyApp, + bearer: Option>>, + params: &'a HashMap +) -> Result { + + // Quickly return if any of the input tokens are bad + 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, + ))?; + + // Prepare output body + let mut response_body = HashMap::new(); + response_body.insert( + "user_address", + serde_json::Value::String(user_address.into()), + ); + response_body.insert( + "user_tier_title", + serde_json::Value::String(user_tier_title.into()), + ); + + // Establish connections + let db_conn = app.db_conn().context("query_admin_modify_user needs a db")?; + let db_replica = app + .db_replica() + .context("query_user_stats needs a db replica")?; + let mut redis_conn = app + .redis_conn() + .await + .context("query_admin_modify_user had a redis connection error")? + .context("query_admin_modify_user needs a redis")?; + + // Try to get the user who is calling from redis (if existent) / else from the database + // 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 caller_id = get_user_id_from_params(&mut redis_conn, &db_conn, &db_replica, bearer, ¶ms).await?; + + // Check if the caller is an admin (i.e. if he is in an admin table) + let admin: admin::Model = admin::Entity::find() + .filter(admin::Entity::UserId.eq(caller_id)) + .one(&db_replica) + .await? + .context("This user is not registered as an admin")?; + + // If we are here, that means an admin was found, and we can safely proceed + + // Fetch the admin, and the user + let user: user::Model = user::Entity::find() + .filter(user::Column::Address.eq(user_address)) + .one(&db_replica) + .await? + .context("No user with this id found as the change")?; + // Return early if the target user_tier_id is the same as the original user_tier_id + response_body.insert( + "user_tier_title", + serde_json::Value::String(user.user_tier_id.into()), + ); + + // Now we can modify the user's tier + let new_user_tier: user_tier::Model = user_tier::Entity::find() + .filter(user_tier::Column::Title.eq(user_tier_title.clone())) + .one(&db_replica) + .await? + .context("No user tier found with that name")?; + + if user.user_tier_id == new_user_tier.id { + info!("user already has that tier"); + } else { + let mut user = user.into_active_model(); + + user.user_tier_id = sea_orm::Set(new_user_tier.id); + + user.save(&db_conn).await?; + + info!("user's tier changed"); + } + + // Finally, remove the user from redis + // TODO: Also remove the user from the redis + + Ok(Json(&response_body).into_response()) + +} diff --git a/web3_proxy/src/frontend/mod.rs b/web3_proxy/src/frontend/mod.rs index 37e9e30f..6c463067 100644 --- a/web3_proxy/src/frontend/mod.rs +++ b/web3_proxy/src/frontend/mod.rs @@ -167,7 +167,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("/admin/modify_role", get(users::admin_change_user_roles)) .route("/user/logout", post(users::user_logout_post)) // // Axum layers diff --git a/web3_proxy/src/frontend/users.rs b/web3_proxy/src/frontend/users.rs index 94176f91..0f56fae4 100644 --- a/web3_proxy/src/frontend/users.rs +++ b/web3_proxy/src/frontend/users.rs @@ -44,6 +44,7 @@ use ulid::Ulid; use entities::user::Relation::UserTier; use migration::extension::postgres::Type; use thread_fast_rng::rand; +use crate::admin_queries::query_admin_modify_usertier; 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. @@ -954,119 +955,7 @@ pub async fn admin_change_user_roles( bearer: Option>>, Query(params): Query>, ) -> FrontendResult { + let response = query_admin_modify_usertier(&app, bearer, ¶ms).await?; - // 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) + response } diff --git a/web3_proxy/src/lib.rs b/web3_proxy/src/lib.rs index dcf6a8c1..5596fa98 100644 --- a/web3_proxy/src/lib.rs +++ b/web3_proxy/src/lib.rs @@ -1,5 +1,6 @@ pub mod app; pub mod app_stats; +pub mod admin_queries; pub mod block_number; pub mod config; pub mod frontend; diff --git a/web3_proxy/src/user_queries.rs b/web3_proxy/src/user_queries.rs index 6d2f5a94..f777f857 100644 --- a/web3_proxy/src/user_queries.rs +++ b/web3_proxy/src/user_queries.rs @@ -9,6 +9,7 @@ use axum::{ TypedHeader, }; use chrono::{NaiveDateTime, Utc}; +use ethers::prelude::Address; use entities::{login, rpc_accounting, rpc_key}; use hashbrown::HashMap; use http::StatusCode;