test log in and out

This commit is contained in:
Bryan Stitt 2023-06-30 12:53:21 -07:00
parent 645fa7328b
commit c26d57fe5e
8 changed files with 102 additions and 50 deletions

@ -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 {

@ -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),

@ -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<String>,
}
/// 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<String>,
}
/// `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);

@ -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<String>,
}
/// 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<String>,
}

@ -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;

@ -73,12 +73,13 @@ impl TestApp {
let anvil_provider = Provider::<Http>::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();

@ -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() {

@ -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<String, serde_json::Value>,
}
/// 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::<LoginPostResponse>()
.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() {