web3-proxy/web3_proxy/src/frontend/admin.rs

457 lines
16 KiB
Rust
Raw Normal View History

//! Handle admin helper logic
2023-03-01 22:23:59 +03:00
use super::authorization::login_is_authorized;
use crate::admin_queries::query_admin_modify_usertier;
use crate::app::Web3ProxyApp;
use crate::errors::Web3ProxyResponse;
use crate::errors::{Web3ProxyError, Web3ProxyErrorContext};
2023-06-30 22:53:21 +03:00
use crate::frontend::users::authentication::PostLogin;
use crate::globals::{global_db_conn, global_db_replica_conn};
use crate::premium::{get_user_and_tier_from_address, grant_premium_tier};
use crate::user_token::UserBearerToken;
use axum::{
extract::{Path, Query},
headers::{authorization::Bearer, Authorization},
response::IntoResponse,
Extension, Json, TypedHeader,
};
2023-02-10 20:48:51 +03:00
use axum_client_ip::InsecureClientIp;
use axum_macros::debug_handler;
use chrono::{TimeZone, Utc};
use entities::{
More balance tests (#182) * fix popularity contest * more info in the Debug for Web3Rpc * add frontend_requests and cache_misses to the Balance query * add more to balance and stats flushing and improved test coverage * it compiles * deserializer for Ulid to Uuid I think a wrapper type on Ulid that implements sea_orm::Value is probably better * rename variable to match struct name * add deserializer for Address -> Vec<u8> * sql sum returns a Decimal. need to convert to u64 * assert more * one log and assert more * log more * use a helper to get the user's rpc provider * this should be 2 now that we have a public and authed call * this should be zero. the public has the cache miss * instrument cu calcs * trace the value we took, not the default that replaced it * move usd_per_chain into config * remove some extra logging * use Arc::into_inner to maybe avoid a race * off by 1 * pass paid credits used instead of returning it this lets us use it to write to our user balance cache first. importantly, this keeps us from holding a write lock while writing to mysql * no cache misses expected in this test * actually check the admin * put the balance checks back now that the rest of the test works * archive request is being set incorrectly * wow howd we manage flipping the greater than sign on archive depth * move latest_balance and premium_credits_used to before any stats are emitted * lint * and build undoes the linting. fun i didnt even want to lint them in the first place, so this is fine * missed incrementing total_spent when not incrementing total_spent_paid_credits * use the credits on self * use the credits on self (pt 2) * fix type for 10 cu query * convert the requestmetadata on the other side of the channel * logs * viewing stats is allowed even without a balance * move paid_credits_used to AuthorizationChecks * wip * test_sum_credits_used finally passes * UserBalanceCache::get_or_insert * re-enable rpc_secret_key_cache * move invalidate to a helper function and always call it **after** the db is commited * fix PartialEq and Eq on RpcSecretKey * cargo upgrade
2023-07-12 10:35:07 +03:00
admin, admin_increase_balance_receipt, admin_trail, login, pending_login, rpc_key, user,
};
2023-03-09 20:32:17 +03:00
use ethers::{prelude::Address, types::Bytes};
use hashbrown::HashMap;
2023-03-01 22:23:59 +03:00
use http::StatusCode;
use migration::sea_orm::prelude::{Decimal, Uuid};
use migration::sea_orm::{
2023-05-24 00:51:34 +03:00
self, ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter,
TransactionTrait,
};
2023-07-01 05:24:48 +03:00
use serde::{Deserialize, Serialize};
use serde_json::json;
use siwe::{Message, VerificationOpts};
use std::ops::Add;
use std::str::FromStr;
use std::sync::Arc;
use time_03::{Duration, OffsetDateTime};
2023-06-30 22:53:21 +03:00
use tracing::{info, trace, warn};
use ulid::Ulid;
2023-07-01 05:24:48 +03:00
#[derive(Debug, Deserialize, Serialize)]
2023-06-25 04:17:51 +03:00
pub struct AdminIncreaseBalancePost {
2023-07-01 05:24:48 +03:00
pub user_address: Address,
pub note: Option<String>,
pub amount: Decimal,
2023-06-25 04:17:51 +03:00
}
2023-06-24 19:59:08 +03:00
/// `POST /admin/increase_balance` -- As an admin, modify a user's user-tier
///
/// - user_address that is to credited balance
/// - user_role_tier that is supposed to be adapted
#[debug_handler]
pub async fn admin_increase_balance(
Extension(app): Extension<Arc<Web3ProxyApp>>,
TypedHeader(Authorization(bearer)): TypedHeader<Authorization<Bearer>>,
2023-06-25 04:17:51 +03:00
Json(payload): Json<AdminIncreaseBalancePost>,
) -> Web3ProxyResponse {
let caller = app.bearer_is_authorized(bearer).await?;
// Establish connections
let db_conn = global_db_conn().await?;
let txn = db_conn.begin().await?;
// Check if the caller is an admin (if not, return early)
let admin_entry: admin::Model = admin::Entity::find()
.filter(admin::Column::UserId.eq(caller.id))
2023-06-24 19:59:08 +03:00
.one(&txn)
.await?
.ok_or_else(|| Web3ProxyError::AccessDenied("not an admin".into()))?;
let (user_entry, user_tier_entry) = get_user_and_tier_from_address(&payload.user_address, &txn)
.await?
.ok_or(Web3ProxyError::BadRequest(
2023-06-25 04:17:51 +03:00
format!("No user found with {:?}", payload.user_address).into(),
))?;
grant_premium_tier(&user_entry, user_tier_entry.as_ref(), &txn)
.await
.web3_context("granting premium tier")?;
let increase_balance_receipt = admin_increase_balance_receipt::ActiveModel {
2023-06-25 04:17:51 +03:00
amount: sea_orm::Set(payload.amount),
admin_id: sea_orm::Set(admin_entry.id),
deposit_to_user_id: sea_orm::Set(user_entry.id),
2023-06-25 04:17:51 +03:00
note: sea_orm::Set(payload.note.unwrap_or_default()),
..Default::default()
};
2023-06-24 19:59:08 +03:00
increase_balance_receipt.save(&txn).await?;
2023-06-24 19:59:08 +03:00
txn.commit().await?;
// Invalidate the user_balance_cache for this user:
More balance tests (#182) * fix popularity contest * more info in the Debug for Web3Rpc * add frontend_requests and cache_misses to the Balance query * add more to balance and stats flushing and improved test coverage * it compiles * deserializer for Ulid to Uuid I think a wrapper type on Ulid that implements sea_orm::Value is probably better * rename variable to match struct name * add deserializer for Address -> Vec<u8> * sql sum returns a Decimal. need to convert to u64 * assert more * one log and assert more * log more * use a helper to get the user's rpc provider * this should be 2 now that we have a public and authed call * this should be zero. the public has the cache miss * instrument cu calcs * trace the value we took, not the default that replaced it * move usd_per_chain into config * remove some extra logging * use Arc::into_inner to maybe avoid a race * off by 1 * pass paid credits used instead of returning it this lets us use it to write to our user balance cache first. importantly, this keeps us from holding a write lock while writing to mysql * no cache misses expected in this test * actually check the admin * put the balance checks back now that the rest of the test works * archive request is being set incorrectly * wow howd we manage flipping the greater than sign on archive depth * move latest_balance and premium_credits_used to before any stats are emitted * lint * and build undoes the linting. fun i didnt even want to lint them in the first place, so this is fine * missed incrementing total_spent when not incrementing total_spent_paid_credits * use the credits on self * use the credits on self (pt 2) * fix type for 10 cu query * convert the requestmetadata on the other side of the channel * logs * viewing stats is allowed even without a balance * move paid_credits_used to AuthorizationChecks * wip * test_sum_credits_used finally passes * UserBalanceCache::get_or_insert * re-enable rpc_secret_key_cache * move invalidate to a helper function and always call it **after** the db is commited * fix PartialEq and Eq on RpcSecretKey * cargo upgrade
2023-07-12 10:35:07 +03:00
if let Err(err) = app
.user_balance_cache
.invalidate(&user_entry.id, &db_conn, &app.rpc_secret_key_cache)
More balance tests (#182) * fix popularity contest * more info in the Debug for Web3Rpc * add frontend_requests and cache_misses to the Balance query * add more to balance and stats flushing and improved test coverage * it compiles * deserializer for Ulid to Uuid I think a wrapper type on Ulid that implements sea_orm::Value is probably better * rename variable to match struct name * add deserializer for Address -> Vec<u8> * sql sum returns a Decimal. need to convert to u64 * assert more * one log and assert more * log more * use a helper to get the user's rpc provider * this should be 2 now that we have a public and authed call * this should be zero. the public has the cache miss * instrument cu calcs * trace the value we took, not the default that replaced it * move usd_per_chain into config * remove some extra logging * use Arc::into_inner to maybe avoid a race * off by 1 * pass paid credits used instead of returning it this lets us use it to write to our user balance cache first. importantly, this keeps us from holding a write lock while writing to mysql * no cache misses expected in this test * actually check the admin * put the balance checks back now that the rest of the test works * archive request is being set incorrectly * wow howd we manage flipping the greater than sign on archive depth * move latest_balance and premium_credits_used to before any stats are emitted * lint * and build undoes the linting. fun i didnt even want to lint them in the first place, so this is fine * missed incrementing total_spent when not incrementing total_spent_paid_credits * use the credits on self * use the credits on self (pt 2) * fix type for 10 cu query * convert the requestmetadata on the other side of the channel * logs * viewing stats is allowed even without a balance * move paid_credits_used to AuthorizationChecks * wip * test_sum_credits_used finally passes * UserBalanceCache::get_or_insert * re-enable rpc_secret_key_cache * move invalidate to a helper function and always call it **after** the db is commited * fix PartialEq and Eq on RpcSecretKey * cargo upgrade
2023-07-12 10:35:07 +03:00
.await
{
warn!(?err, "unable to invalidate caches");
};
2023-06-24 19:59:08 +03:00
let out = json!({
2023-06-25 04:17:51 +03:00
"user": payload.user_address,
"amount": payload.amount,
2023-06-24 19:59:08 +03:00
});
Ok(Json(out).into_response())
}
2023-06-24 19:59:08 +03:00
/// `POST /admin/modify_role` -- As an admin, modify a user's user-tier
///
2023-02-19 23:34:39 +03:00
/// - user_address that is to be modified
/// - user_role_tier that is supposed to be adapted
2023-06-25 04:17:51 +03:00
///
/// TODO: JSON post data instead of query params
#[debug_handler]
pub async fn admin_change_user_roles(
Extension(app): Extension<Arc<Web3ProxyApp>>,
bearer: Option<TypedHeader<Authorization<Bearer>>>,
Query(params): Query<HashMap<String, String>>,
) -> Web3ProxyResponse {
let response = query_admin_modify_usertier(&app, bearer, &params).await?;
Ok(response)
}
2023-02-19 23:34:39 +03:00
/// `GET /admin/imitate-login/:admin_address/:user_address` -- Being an admin, login as a user in read-only mode
2023-02-19 23:34:39 +03:00
///
/// - user_address that is to be logged in by
/// We assume that the admin has already logged in, and has a bearer token ...
#[debug_handler]
2023-06-25 01:24:46 +03:00
pub async fn admin_imitate_login_get(
2023-02-19 23:34:39 +03:00
Extension(app): Extension<Arc<Web3ProxyApp>>,
2023-02-10 20:48:51 +03:00
InsecureClientIp(ip): InsecureClientIp,
2023-02-19 23:34:39 +03:00
Path(mut params): Path<HashMap<String, String>>,
) -> Web3ProxyResponse {
2023-02-19 23:34:39 +03:00
// First check if the login is authorized
login_is_authorized(&app, ip).await?;
// create a message and save it in redis
// TODO: how many seconds? get from config?
// Same parameters as when someone logs in as a user
let expire_seconds: usize = 20 * 60;
let nonce = Ulid::new();
let issued_at = OffsetDateTime::now_utc();
let expiration_time = issued_at.add(Duration::new(expire_seconds as i64, 0));
// get the admin's address
2023-02-19 23:34:39 +03:00
let admin_address: Address = params
.get("admin_address")
2023-03-01 22:23:59 +03:00
.ok_or_else(|| {
2023-05-31 09:17:05 +03:00
Web3ProxyError::BadRequest("Unable to find admin_address key in request".into())
2023-03-01 22:23:59 +03:00
})?
2023-02-19 23:34:39 +03:00
.parse::<Address>()
2023-03-01 22:23:59 +03:00
.map_err(|_err| {
2023-05-31 09:17:05 +03:00
Web3ProxyError::BadRequest("Unable to parse admin_address as an Address".into())
2023-03-01 22:23:59 +03:00
})?;
2023-02-19 23:34:39 +03:00
// get the address who we want to be logging in as
2023-06-24 20:20:24 +03:00
let user_address: Address = params
2023-02-19 23:34:39 +03:00
.get("user_address")
2023-03-01 22:23:59 +03:00
.ok_or_else(|| {
2023-05-31 09:17:05 +03:00
Web3ProxyError::BadRequest("Unable to find user_address key in request".into())
2023-03-01 22:23:59 +03:00
})?
2023-02-19 23:34:39 +03:00
.parse::<Address>()
2023-03-01 22:23:59 +03:00
.map_err(|_err| {
2023-05-31 09:17:05 +03:00
Web3ProxyError::BadRequest("Unable to parse user_address as an Address".into())
2023-06-24 20:20:24 +03:00
})?;
2023-02-19 23:34:39 +03:00
// We want to login to llamanodes.com
let domain = app
2023-02-19 23:34:39 +03:00
.config
.login_domain
.as_deref()
.unwrap_or("llamanodes.com");
2023-02-19 23:34:39 +03:00
let message_domain = domain.parse()?;
// TODO: don't unwrap
let message_uri = format!("https://{}/", domain).parse().unwrap();
2023-02-19 23:34:39 +03:00
// TODO: get most of these from the app config
let message = Message {
domain: message_domain,
2023-06-24 20:20:24 +03:00
// the admin needs to sign the message, not the imitated user
address: admin_address.to_fixed_bytes(),
2023-02-19 23:34:39 +03:00
// TODO: config for statement
statement: Some("👑👑👑👑👑".to_string()),
uri: message_uri,
2023-02-19 23:34:39 +03:00
version: siwe::Version::V1,
chain_id: app.config.chain_id,
2023-02-19 23:34:39 +03:00
expiration_time: Some(expiration_time.into()),
issued_at: issued_at.into(),
nonce: nonce.to_string(),
not_before: None,
request_id: None,
resources: vec![],
};
let db_conn = global_db_conn().await?;
let db_replica = global_db_replica_conn().await?;
2023-02-19 23:34:39 +03:00
2023-06-25 01:24:46 +03:00
let admin = user::Entity::find()
.filter(user::Column::Address.eq(admin_address.as_bytes()))
.one(db_replica.as_ref())
.await?
.ok_or(Web3ProxyError::AccessDenied("not an admin".into()))?;
2023-06-24 19:48:28 +03:00
2023-02-19 23:34:39 +03:00
// Get the user that we want to imitate from the read-only database (their id ...)
// TODO: Only get the id, not the whole user object ...
let user = user::Entity::find()
2023-06-24 20:20:24 +03:00
.filter(user::Column::Address.eq(user_address.as_bytes()))
.one(db_replica.as_ref())
2023-02-19 23:34:39 +03:00
.await?
.ok_or(Web3ProxyError::BadRequest(
2023-05-31 09:17:05 +03:00
"Could not find user in db".into(),
2023-03-01 22:23:59 +03:00
))?;
2023-02-19 23:34:39 +03:00
2023-06-25 01:24:46 +03:00
info!(admin=?admin.address, user=?user.address, "admin is imitating another user");
// delete ALL expired rows.
let now = Utc::now();
let delete_result = pending_login::Entity::delete_many()
.filter(pending_login::Column::ExpiresAt.lte(now))
.exec(&db_conn)
2023-06-25 01:24:46 +03:00
.await?;
2023-06-30 22:53:21 +03:00
trace!("cleared expired pending_logins: {:?}", delete_result);
// Note that the admin is trying to log in as this user
let trail = admin_trail::ActiveModel {
caller: sea_orm::Set(admin.id),
imitating_user: sea_orm::Set(Some(user.id)),
2023-06-25 01:24:46 +03:00
endpoint: sea_orm::Set("admin_imitate_login_get".to_string()),
2023-06-24 20:20:24 +03:00
payload: sea_orm::Set(format!("{}", json!(params))),
..Default::default()
};
2023-06-25 01:24:46 +03:00
trail
.save(&db_conn)
.await
.web3_context("saving user's pending_login")?;
2023-02-19 23:34:39 +03:00
// Can there be two login-sessions at the same time?
// I supposed if the user logs in, the admin would be logged out and vice versa
// we add 1 to expire_seconds just to be sure the database has the key for the full expiration_time
let expires_at = Utc
.timestamp_opt(expiration_time.unix_timestamp() + 1, 0)
.unwrap();
// we do not store a maximum number of attempted logins. anyone can request so we don't want to allow DOS attacks
// add a row to the database for this user
let user_pending_login = pending_login::ActiveModel {
id: sea_orm::NotSet,
2023-06-25 01:24:46 +03:00
nonce: sea_orm::Set(nonce.into()),
2023-02-19 23:34:39 +03:00
message: sea_orm::Set(message.to_string()),
expires_at: sea_orm::Set(expires_at),
2023-03-01 22:23:59 +03:00
imitating_user: sea_orm::Set(Some(user.id)),
2023-02-19 23:34:39 +03:00
};
user_pending_login
.save(&db_conn)
2023-02-19 23:34:39 +03:00
.await
.web3_context("saving an admin trail pre login")?;
2023-02-19 23:34:39 +03:00
// there are multiple ways to sign messages and not all wallets support them
// TODO: default message eip from config?
let message_eip = params
.remove("message_eip")
.unwrap_or_else(|| "eip4361".to_string());
let message: String = match message_eip.as_str() {
"eip191_bytes" => Bytes::from(message.eip191_bytes().unwrap()).to_string(),
"eip191_hash" => Bytes::from(&message.eip191_hash().unwrap()).to_string(),
"eip4361" => message.to_string(),
_ => {
// TODO: custom error that is handled a 401
return Err(Web3ProxyError::InvalidEip);
2023-02-19 23:34:39 +03:00
}
};
Ok(message.into_response())
}
2023-06-25 01:49:26 +03:00
/// `POST /admin/imitate-login` - Admin login by posting a signed "siwe" message
2023-02-19 23:34:39 +03:00
/// It is recommended to save the returned bearer token in a cookie.
2023-06-25 01:24:46 +03:00
/// The bearer token can be used to authenticate other admin requests
2023-02-19 23:34:39 +03:00
#[debug_handler]
2023-06-25 01:49:26 +03:00
pub async fn admin_imitate_login_post(
2023-02-19 23:34:39 +03:00
Extension(app): Extension<Arc<Web3ProxyApp>>,
2023-02-10 20:48:51 +03:00
InsecureClientIp(ip): InsecureClientIp,
2023-02-19 23:34:39 +03:00
Json(payload): Json<PostLogin>,
) -> Web3ProxyResponse {
2023-02-19 23:34:39 +03:00
login_is_authorized(&app, ip).await?;
// Check for the signed bytes ..
// TODO: this seems too verbose. how can we simply convert a String into a [u8; 65]
let their_sig_bytes = Bytes::from_str(&payload.sig).web3_context("parsing sig")?;
2023-02-19 23:34:39 +03:00
if their_sig_bytes.len() != 65 {
return Err(Web3ProxyError::InvalidSignatureLength);
2023-02-19 23:34:39 +03:00
}
let mut their_sig: [u8; 65] = [0; 65];
for x in 0..65 {
their_sig[x] = their_sig_bytes[x]
}
// we can't trust that they didn't tamper with the message in some way. like some clients return it hex encoded
// TODO: checking 0x seems fragile, but I think it will be fine. siwe message text shouldn't ever start with 0x
let their_msg: Message = if payload.msg.starts_with("0x") {
let their_msg_bytes = Bytes::from_str(&payload.msg).map_err(|err| {
Web3ProxyError::BadRequest(
format!("error parsing payload message as Bytes: {}", err).into(),
)
})?;
2023-02-19 23:34:39 +03:00
// TODO: lossy or no?
String::from_utf8_lossy(their_msg_bytes.as_ref())
.parse::<siwe::Message>()
.map_err(|err| {
Web3ProxyError::BadRequest(
format!("error parsing bytes as siwe message: {}", err).into(),
)
})?
2023-02-19 23:34:39 +03:00
} else {
payload.msg.parse::<siwe::Message>().map_err(|err| {
Web3ProxyError::BadRequest(
format!("error parsing string as siwe message: {}", err).into(),
)
})?
2023-02-19 23:34:39 +03:00
};
// the only part of the message we will trust is their nonce
// TODO: this is fragile. have a helper function/struct for redis keys
let login_nonce = UserBearerToken::from_str(&their_msg.nonce).map_err(|err| {
Web3ProxyError::BadRequest(format!("error parsing nonce: {}", err).into())
})?;
2023-02-19 23:34:39 +03:00
// fetch the message we gave them from our database
let db_replica = global_db_replica_conn().await?;
2023-02-19 23:34:39 +03:00
let user_pending_login = pending_login::Entity::find()
More balance tests (#182) * fix popularity contest * more info in the Debug for Web3Rpc * add frontend_requests and cache_misses to the Balance query * add more to balance and stats flushing and improved test coverage * it compiles * deserializer for Ulid to Uuid I think a wrapper type on Ulid that implements sea_orm::Value is probably better * rename variable to match struct name * add deserializer for Address -> Vec<u8> * sql sum returns a Decimal. need to convert to u64 * assert more * one log and assert more * log more * use a helper to get the user's rpc provider * this should be 2 now that we have a public and authed call * this should be zero. the public has the cache miss * instrument cu calcs * trace the value we took, not the default that replaced it * move usd_per_chain into config * remove some extra logging * use Arc::into_inner to maybe avoid a race * off by 1 * pass paid credits used instead of returning it this lets us use it to write to our user balance cache first. importantly, this keeps us from holding a write lock while writing to mysql * no cache misses expected in this test * actually check the admin * put the balance checks back now that the rest of the test works * archive request is being set incorrectly * wow howd we manage flipping the greater than sign on archive depth * move latest_balance and premium_credits_used to before any stats are emitted * lint * and build undoes the linting. fun i didnt even want to lint them in the first place, so this is fine * missed incrementing total_spent when not incrementing total_spent_paid_credits * use the credits on self * use the credits on self (pt 2) * fix type for 10 cu query * convert the requestmetadata on the other side of the channel * logs * viewing stats is allowed even without a balance * move paid_credits_used to AuthorizationChecks * wip * test_sum_credits_used finally passes * UserBalanceCache::get_or_insert * re-enable rpc_secret_key_cache * move invalidate to a helper function and always call it **after** the db is commited * fix PartialEq and Eq on RpcSecretKey * cargo upgrade
2023-07-12 10:35:07 +03:00
.filter(pending_login::Column::Nonce.eq(Uuid::from(login_nonce)))
.one(db_replica.as_ref())
2023-02-19 23:34:39 +03:00
.await
.web3_context("database error while finding pending_login")?
.web3_context("login nonce not found")?;
2023-02-19 23:34:39 +03:00
let our_msg: siwe::Message = user_pending_login
.message
.parse()
.web3_context("parsing siwe message")?;
2023-02-19 23:34:39 +03:00
2023-06-24 19:48:28 +03:00
// mostly default options are fine. the message includes timestamp and domain and nonce
let verify_config = VerificationOpts {
rpc_provider: Some(app.internal_provider().clone()),
2023-06-24 19:48:28 +03:00
..Default::default()
};
2023-06-24 19:48:28 +03:00
our_msg
2023-02-19 23:34:39 +03:00
.verify(&their_sig, &verify_config)
.await
2023-06-24 19:48:28 +03:00
.web3_context("verifying signature against our local message")?;
2023-02-19 23:34:39 +03:00
2023-03-01 22:23:59 +03:00
let imitating_user_id = user_pending_login
.imitating_user
.web3_context("getting address of the imitating user")?;
2023-02-19 23:34:39 +03:00
// TODO: limit columns or load whole user?
// TODO: Right now this loads the whole admin. I assume we might want to load the user though (?) figure this out as we go along...
let admin = user::Entity::find()
.filter(user::Column::Address.eq(our_msg.address.as_ref()))
.one(db_replica.as_ref())
2023-01-30 22:02:28 +03:00
.await?
.web3_context("getting admin address")?;
2023-02-19 23:34:39 +03:00
2023-01-30 22:02:28 +03:00
let imitating_user = user::Entity::find()
.filter(user::Column::Id.eq(imitating_user_id))
.one(db_replica.as_ref())
2023-01-30 22:02:28 +03:00
.await?
.web3_context("admin address was not found!")?;
2023-02-19 23:34:39 +03:00
let db_conn = global_db_conn().await?;
2023-06-24 19:48:28 +03:00
// Add a message that the admin has logged in
// Note that the admin is trying to log in as this user
let trail = admin_trail::ActiveModel {
caller: sea_orm::Set(admin.id),
imitating_user: sea_orm::Set(Some(imitating_user.id)),
endpoint: sea_orm::Set("admin_login_post".to_string()),
payload: sea_orm::Set(format!("{:?}", payload)),
..Default::default()
};
trail
.save(&db_conn)
.await
.web3_context("saving an admin trail post login")?;
2023-01-30 22:02:28 +03:00
// I supposed we also get the rpc_key, whatever this is used for (?).
// I think the RPC key should still belong to the admin though in this case ...
2023-02-19 23:34:39 +03:00
2023-01-30 22:02:28 +03:00
// the user is already registered
let admin_rpc_key = rpc_key::Entity::find()
.filter(rpc_key::Column::UserId.eq(admin.id))
.all(db_replica.as_ref())
2023-01-30 22:02:28 +03:00
.await
.web3_context("failed loading user's key")?;
2023-02-19 23:34:39 +03:00
// create a bearer token for the user.
let user_bearer_token = UserBearerToken::default();
// json response with everything in it
// we could return just the bearer token, but I think they will always request api keys and the user profile
let response_json = json!({
2023-01-30 22:02:28 +03:00
"rpc_keys": admin_rpc_key
2023-02-19 23:34:39 +03:00
.into_iter()
.map(|uk| (uk.id, uk))
.collect::<HashMap<_, _>>(),
"bearer_token": user_bearer_token,
2023-01-30 22:02:28 +03:00
"imitating_user": imitating_user,
"admin_user": admin,
2023-02-19 23:34:39 +03:00
});
2023-01-30 22:02:28 +03:00
let response = (StatusCode::OK, Json(response_json)).into_response();
2023-02-19 23:34:39 +03:00
// add bearer to the database
// expire in 2 days, because this is more critical (and shouldn't need to be done so long!)
2023-06-24 19:48:28 +03:00
let expires_at = Utc::now() + chrono::Duration::days(2);
2023-02-19 23:34:39 +03:00
// TODO: Here, the bearer token should include a message
// TODO: Above, make sure that the calling address is an admin!
// TODO: Above, make sure that the signed is the admin (address field),
// but then in this request, the admin can pick which user to sign up as
let user_login = login::ActiveModel {
id: sea_orm::NotSet,
bearer_token: sea_orm::Set(user_bearer_token.uuid()),
2023-03-01 22:23:59 +03:00
user_id: sea_orm::Set(imitating_user.id), // Yes, this should be the user ... because the rest of the applications takes this item, from the initial user
2023-02-19 23:34:39 +03:00
expires_at: sea_orm::Set(expires_at),
2023-03-01 22:23:59 +03:00
read_only: sea_orm::Set(true),
2023-02-19 23:34:39 +03:00
};
user_login
.save(&db_conn)
2023-02-19 23:34:39 +03:00
.await
.web3_context("saving user login")?;
2023-02-19 23:34:39 +03:00
if let Err(err) = user_pending_login
.into_active_model()
.delete(&db_conn)
.await
{
2023-06-29 07:30:00 +03:00
warn!(none=?login_nonce.0, ?err, "Failed to delete nonce");
2023-02-19 23:34:39 +03:00
}
Ok(response)
}