diff --git a/web3_proxy/src/frontend/admin.rs b/web3_proxy/src/frontend/admin.rs index 9fddd4dc..495205b4 100644 --- a/web3_proxy/src/frontend/admin.rs +++ b/web3_proxy/src/frontend/admin.rs @@ -5,8 +5,8 @@ use crate::admin_queries::query_admin_modify_usertier; use crate::app::Web3ProxyApp; use crate::errors::Web3ProxyResponse; use crate::errors::{Web3ProxyError, Web3ProxyErrorContext}; +use crate::frontend::users::authentication::PostLogin; use crate::user_token::UserBearerToken; -use crate::PostLogin; use axum::{ extract::{Path, Query}, headers::{authorization::Bearer, Authorization}, @@ -35,7 +35,7 @@ use std::ops::Add; use std::str::FromStr; use std::sync::Arc; use time_03::{Duration, OffsetDateTime}; -use tracing::{debug, info, warn}; +use tracing::{info, trace, warn}; use ulid::Ulid; #[derive(Deserialize)] @@ -232,7 +232,7 @@ pub async fn admin_imitate_login_get( .filter(pending_login::Column::ExpiresAt.lte(now)) .exec(db_conn) .await?; - debug!("cleared expired pending_logins: {:?}", delete_result); + trace!("cleared expired pending_logins: {:?}", delete_result); // Note that the admin is trying to log in as this user let trail = admin_trail::ActiveModel { diff --git a/web3_proxy/src/frontend/authorization.rs b/web3_proxy/src/frontend/authorization.rs index fe201262..6be1a1fa 100644 --- a/web3_proxy/src/frontend/authorization.rs +++ b/web3_proxy/src/frontend/authorization.rs @@ -48,7 +48,8 @@ use ulid::Ulid; use uuid::Uuid; /// This lets us use UUID and ULID while we transition to only ULIDs -#[derive(Copy, Clone, Debug, Eq, PartialEq, Deserialize)] +/// TODO: custom deserialize that can also go from String to Ulid +#[derive(Copy, Clone, Debug, Deserialize, Eq, PartialEq)] pub enum RpcSecretKey { Ulid(Ulid), Uuid(Uuid), diff --git a/web3_proxy/src/frontend/users/authentication.rs b/web3_proxy/src/frontend/users/authentication.rs index c05fd243..fd6c8ce0 100644 --- a/web3_proxy/src/frontend/users/authentication.rs +++ b/web3_proxy/src/frontend/users/authentication.rs @@ -3,7 +3,6 @@ use crate::app::Web3ProxyApp; use crate::errors::{Web3ProxyError, Web3ProxyErrorContext, Web3ProxyResponse}; use crate::frontend::authorization::{login_is_authorized, RpcSecretKey}; use crate::user_token::UserBearerToken; -use crate::{PostLogin, PostLoginQuery}; use axum::{ extract::{Path, Query}, headers::{authorization::Bearer, Authorization}, @@ -23,6 +22,7 @@ use migration::sea_orm::{ self, ActiveModelTrait, ColumnTrait, DatabaseTransaction, EntityTrait, IntoActiveModel, QueryFilter, TransactionTrait, }; +use serde::{Deserialize, Serialize}; use serde_json::json; use siwe::{Message, VerificationOpts}; use std::ops::Add; @@ -32,6 +32,24 @@ use time_03::{Duration, OffsetDateTime}; use tracing::{error, trace, warn}; use ulid::Ulid; +/// Query params for our `post_login` handler. +#[derive(Debug, Deserialize)] +pub struct PostLoginQuery { + /// While we are in alpha/beta, we require users to supply an invite code. + /// The invite code (if any) is set in the application's config. + pub invite_code: Option, +} + +/// JSON body to our `post_login` handler. +/// Currently only siwe logins that send an address, msg, and sig are allowed. +/// Email/password and other login methods are planned. +#[derive(Debug, Deserialize, Serialize)] +pub struct PostLogin { + pub sig: String, + pub msg: String, + pub referral_code: Option, +} + /// `GET /user/login/:user_address` or `GET /user/login/:user_address/:message_eip` -- Start the "Sign In with Ethereum" (siwe) login flow. /// /// `message_eip`s accepted: @@ -103,18 +121,22 @@ pub async fn user_login_get( let db_conn = app.db_conn()?; // delete any expired logins - let expired_logins = login::Entity::delete_many() + if let Err(err) = login::Entity::delete_many() .filter(login::Column::ExpiresAt.lte(now)) .exec(db_conn) - .await; - trace!(?expired_logins, "deleted"); + .await + { + warn!(?err, "expired_logins"); + }; // delete any expired pending logins - let expired_pending_logins = pending_login::Entity::delete_many() - .filter(login::Column::ExpiresAt.lte(now)) + if let Err(err) = pending_login::Entity::delete_many() + .filter(pending_login::Column::ExpiresAt.lte(now)) .exec(db_conn) - .await; - trace!(?expired_pending_logins, "deleted"); + .await + { + warn!(?err, "expired_pending_logins"); + }; // we add 1 to expire_seconds just to be sure the database has the key for the full expiration_time let expires_at = Utc @@ -299,7 +321,7 @@ pub async fn user_login_post( let txn = db_conn.begin().await?; // First, optionally catch a referral code from the parameters if there is any - trace!("Referal code is: {:?}", payload.referral_code); + trace!(?payload.referral_code); if let Some(referral_code) = payload.referral_code.as_ref() { // If it is not inside, also check in the database trace!("Using register referral code: {:?}", referral_code); diff --git a/web3_proxy/src/lib.rs b/web3_proxy/src/lib.rs index aee5e56f..c6212141 100644 --- a/web3_proxy/src/lib.rs +++ b/web3_proxy/src/lib.rs @@ -19,25 +19,3 @@ pub mod rpcs; pub mod stats; pub mod sub_commands; pub mod user_token; - -use serde::Deserialize; - -// Push some commonly used types here. Can establish a folder later on -/// Query params for our `post_login` handler. -#[derive(Debug, Deserialize)] -pub struct PostLoginQuery { - /// While we are in alpha/beta, we require users to supply an invite code. - /// The invite code (if any) is set in the application's config. - /// This may eventually provide some sort of referral bonus. - invite_code: Option, -} - -/// JSON body to our `post_login` handler. -/// Currently only siwe logins that send an address, msg, and sig are allowed. -/// Email/password and other login methods are planned. -#[derive(Debug, Deserialize)] -pub struct PostLogin { - sig: String, - msg: String, - pub referral_code: Option, -} diff --git a/web3_proxy/src/relational_db.rs b/web3_proxy/src/relational_db.rs index 7bbdeb35..ed2aff29 100644 --- a/web3_proxy/src/relational_db.rs +++ b/web3_proxy/src/relational_db.rs @@ -5,7 +5,7 @@ use migration::sea_query::table::ColumnDef; use migration::{Alias, DbErr, Migrator, MigratorTrait, Table}; use std::time::Duration; use tokio::time::sleep; -use tracing::{debug, info, trace, warn}; +use tracing::{debug, info, warn}; pub use migration::sea_orm::DatabaseConnection; diff --git a/web3_proxy/tests/common/app.rs b/web3_proxy/tests/common/app.rs index 584edb72..0c547a00 100644 --- a/web3_proxy/tests/common/app.rs +++ b/web3_proxy/tests/common/app.rs @@ -73,12 +73,13 @@ impl TestApp { let anvil_provider = Provider::::try_from(anvil.endpoint()).unwrap(); + // TODO: instead of starting a db every time, use a connection pool and transactions to begin/rollback let db = if setup_db { // sqlite doesn't seem to work. our migrations are written for mysql // so lets use docker to start mysql let password: String = rand::thread_rng() .sample_iter(&Alphanumeric) - .take(32) + .take(16) .map(char::from) .collect(); diff --git a/web3_proxy/tests/test_admins.rs b/web3_proxy/tests/test_admins.rs index 442434d1..c696609d 100644 --- a/web3_proxy/tests/test_admins.rs +++ b/web3_proxy/tests/test_admins.rs @@ -2,7 +2,7 @@ mod common; use crate::common::TestApp; -#[cfg_attr(not(feature = "tests-needing-docker"), ignore)] +// #[cfg_attr(not(feature = "tests-needing-docker"), ignore)] #[ignore = "under construction"] #[test_log::test(tokio::test)] async fn test_admin_imitate_user() { @@ -11,7 +11,7 @@ async fn test_admin_imitate_user() { todo!(); } -#[cfg_attr(not(feature = "tests-needing-docker"), ignore)] +// #[cfg_attr(not(feature = "tests-needing-docker"), ignore)] #[ignore = "under construction"] #[test_log::test(tokio::test)] async fn test_admin_grant_credits() { @@ -20,7 +20,7 @@ async fn test_admin_grant_credits() { todo!(); } -#[cfg_attr(not(feature = "tests-needing-docker"), ignore)] +// #[cfg_attr(not(feature = "tests-needing-docker"), ignore)] #[ignore = "under construction"] #[test_log::test(tokio::test)] async fn test_admin_change_user_tier() { diff --git a/web3_proxy/tests/test_users.rs b/web3_proxy/tests/test_users.rs index 17c09012..edae850d 100644 --- a/web3_proxy/tests/test_users.rs +++ b/web3_proxy/tests/test_users.rs @@ -1,8 +1,24 @@ mod common; use crate::common::TestApp; -use ethers::signers::Signer; -use tracing::info; +use axum::headers::Authorization; +use ethers::{signers::Signer, types::Signature}; +use hashbrown::HashMap; +use serde::Deserialize; +use serde_json::Value; +use tracing::{debug, info, trace}; +use ulid::Ulid; +use web3_proxy::frontend::users::authentication::PostLogin; + +/// TODO: use this type in the frontend +#[derive(Debug, Deserialize)] +struct LoginPostResponse { + pub bearer_token: Ulid, + pub rpc_keys: Value, + /// unknown data gets put here + #[serde(flatten, default = "HashMap::default")] + pub extra: HashMap, +} /// TODO: 191 and the other message formats in another test #[cfg_attr(not(feature = "tests-needing-docker"), ignore)] @@ -10,23 +26,57 @@ use tracing::info; async fn test_log_in_and_out() { let x = TestApp::spawn(true).await; + let r = reqwest::Client::new(); + let w = x.wallet(0); - let login_url = format!("{}user/login/{:?}", x.proxy_provider.url(), w.address()); - let login_response = reqwest::get(login_url).await.unwrap(); + let login_get_url = format!("{}user/login/{:?}", x.proxy_provider.url(), w.address()); + let login_message = r.get(login_get_url).send().await.unwrap(); + + let login_message = login_message.text().await.unwrap(); + + // sign the message and POST it + let signed: Signature = w.sign_message(&login_message).await.unwrap(); + trace!(?signed); + + let post_login_data = PostLogin { + msg: login_message, + sig: signed.to_string(), + referral_code: None, + }; + debug!(?post_login_data); + + let login_post_url = format!("{}user/login", x.proxy_provider.url()); + let login_response = r + .post(login_post_url) + .json(&post_login_data) + .send() + .await + .unwrap() + .json::() + .await + .unwrap(); info!(?login_response); - // TODO: sign the message and POST it + // use the bearer token to log out + let logout_post_url = format!("{}user/logout", x.proxy_provider.url()); + let logout_response = r + .post(logout_post_url) + .bearer_auth(login_response.bearer_token) + .send() + .await + .unwrap() + .text() + .await + .unwrap(); - // TODO: get bearer token out of response + info!(?logout_response); - // TODO: log out - - todo!(); + assert_eq!(logout_response, "goodbye"); } -#[cfg_attr(not(feature = "tests-needing-docker"), ignore)] +// #[cfg_attr(not(feature = "tests-needing-docker"), ignore)] #[ignore = "under construction"] #[test_log::test(tokio::test)] async fn test_referral_bonus() {