more use login things
This commit is contained in:
parent
5e239c05c8
commit
93fe878748
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -5282,6 +5282,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "13a3aaa69b04e5b66cc27309710a569ea23593612387d67daaf102e73aa974fd"
|
checksum = "13a3aaa69b04e5b66cc27309710a569ea23593612387d67daaf102e73aa974fd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
2
TODO.md
2
TODO.md
@ -134,6 +134,8 @@
|
|||||||
- [ ] cli tool for resetting api keys
|
- [ ] cli tool for resetting api keys
|
||||||
- [ ] cli tool for checking config
|
- [ ] cli tool for checking config
|
||||||
- [ ] nice output when cargo doc is run
|
- [ ] nice output when cargo doc is run
|
||||||
|
- [ ] Ulid instead of Uuid
|
||||||
|
- <https://discord.com/channels/873880840487206962/900758376164757555/1012942974608474142>
|
||||||
- [ ] if we request an old block, more servers can handle it than we currently use.
|
- [ ] if we request an old block, more servers can handle it than we currently use.
|
||||||
- [ ] instead of the one list of just heads, store our intermediate mappings (rpcs_by_hash, rpcs_by_num, blocks_by_hash) in SyncedConnections. this shouldn't be too much slower than what we have now
|
- [ ] instead of the one list of just heads, store our intermediate mappings (rpcs_by_hash, rpcs_by_num, blocks_by_hash) in SyncedConnections. this shouldn't be too much slower than what we have now
|
||||||
- [ ] remove the if/else where we optionally route to archive and refactor to require a BlockNumber enum
|
- [ ] remove the if/else where we optionally route to archive and refactor to require a BlockNumber enum
|
||||||
|
@ -63,8 +63,6 @@ tower-http = { version = "0.3.4", features = ["trace"] }
|
|||||||
tracing = "0.1.36"
|
tracing = "0.1.36"
|
||||||
# TODO: tracing-subscriber has serde and serde_json features that we might want to use
|
# TODO: tracing-subscriber has serde and serde_json features that we might want to use
|
||||||
tracing-subscriber = { version = "0.3.15", features = ["env-filter", "parking_lot"] }
|
tracing-subscriber = { version = "0.3.15", features = ["env-filter", "parking_lot"] }
|
||||||
ulid = "1.0.0"
|
ulid = { version = "1.0.0", features = ["serde"] }
|
||||||
url = "2.2.2"
|
url = "2.2.2"
|
||||||
uuid = "1.1.2"
|
uuid = "1.1.2"
|
||||||
|
|
||||||
# TODO: https://github.com/ulid/spec instead of uuid
|
|
@ -215,6 +215,7 @@ mod tests {
|
|||||||
app: AppConfig {
|
app: AppConfig {
|
||||||
chain_id: 31337,
|
chain_id: 31337,
|
||||||
db_url: None,
|
db_url: None,
|
||||||
|
default_requests_per_minute: 6_000_000,
|
||||||
invite_code: None,
|
invite_code: None,
|
||||||
redis_url: None,
|
redis_url: None,
|
||||||
min_sum_soft_limit: 1,
|
min_sum_soft_limit: 1,
|
||||||
|
@ -45,6 +45,8 @@ pub struct AppConfig {
|
|||||||
pub chain_id: u64,
|
pub chain_id: u64,
|
||||||
pub db_url: Option<String>,
|
pub db_url: Option<String>,
|
||||||
pub invite_code: Option<String>,
|
pub invite_code: Option<String>,
|
||||||
|
#[serde(default = "default_default_requests_per_minute")]
|
||||||
|
pub default_requests_per_minute: u32,
|
||||||
#[serde(default = "default_min_sum_soft_limit")]
|
#[serde(default = "default_min_sum_soft_limit")]
|
||||||
pub min_sum_soft_limit: u32,
|
pub min_sum_soft_limit: u32,
|
||||||
#[serde(default = "default_min_synced_rpcs")]
|
#[serde(default = "default_min_synced_rpcs")]
|
||||||
@ -60,6 +62,11 @@ pub struct AppConfig {
|
|||||||
pub redirect_user_url: String,
|
pub redirect_user_url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: what should we default to? have different tiers for different paid amounts?
|
||||||
|
fn default_default_requests_per_minute() -> u32 {
|
||||||
|
1_000_000 * 60
|
||||||
|
}
|
||||||
|
|
||||||
fn default_min_sum_soft_limit() -> u32 {
|
fn default_min_sum_soft_limit() -> u32 {
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ use axum::{
|
|||||||
};
|
};
|
||||||
use derive_more::From;
|
use derive_more::From;
|
||||||
use redis_rate_limit::{bb8::RunError, RedisError};
|
use redis_rate_limit::{bb8::RunError, RedisError};
|
||||||
|
use sea_orm::DbErr;
|
||||||
use serde_json::value::RawValue;
|
use serde_json::value::RawValue;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
@ -18,8 +19,9 @@ pub enum FrontendErrorResponse {
|
|||||||
Box(Box<dyn Error>),
|
Box(Box<dyn Error>),
|
||||||
// TODO: should we box these instead?
|
// TODO: should we box these instead?
|
||||||
Redis(RedisError),
|
Redis(RedisError),
|
||||||
RedisRunError(RunError<RedisError>),
|
RedisRun(RunError<RedisError>),
|
||||||
Response(Response),
|
Response(Response),
|
||||||
|
Database(DbErr),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoResponse for FrontendErrorResponse {
|
impl IntoResponse for FrontendErrorResponse {
|
||||||
@ -31,10 +33,11 @@ impl IntoResponse for FrontendErrorResponse {
|
|||||||
Self::Anyhow(err) => err,
|
Self::Anyhow(err) => err,
|
||||||
Self::Box(err) => anyhow::anyhow!("Boxed error: {:?}", err),
|
Self::Box(err) => anyhow::anyhow!("Boxed error: {:?}", err),
|
||||||
Self::Redis(err) => err.into(),
|
Self::Redis(err) => err.into(),
|
||||||
Self::RedisRunError(err) => err.into(),
|
Self::RedisRun(err) => err.into(),
|
||||||
Self::Response(r) => {
|
Self::Response(r) => {
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
Self::Database(err) => err.into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let err = JsonRpcForwardedResponse::from_anyhow_error(err, null_id);
|
let err = JsonRpcForwardedResponse::from_anyhow_error(err, null_id);
|
||||||
|
@ -9,7 +9,8 @@
|
|||||||
|
|
||||||
use super::errors::FrontendResult;
|
use super::errors::FrontendResult;
|
||||||
use super::rate_limit::rate_limit_by_ip;
|
use super::rate_limit::rate_limit_by_ip;
|
||||||
use crate::app::Web3ProxyApp;
|
use crate::{app::Web3ProxyApp, users::new_api_key};
|
||||||
|
use anyhow::Context;
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{Path, Query},
|
extract::{Path, Query},
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
@ -18,13 +19,15 @@ use axum::{
|
|||||||
use axum_auth::AuthBearer;
|
use axum_auth::AuthBearer;
|
||||||
use axum_client_ip::ClientIp;
|
use axum_client_ip::ClientIp;
|
||||||
use axum_macros::debug_handler;
|
use axum_macros::debug_handler;
|
||||||
|
use http::StatusCode;
|
||||||
|
use uuid::Uuid;
|
||||||
// use entities::sea_orm_active_enums::Role;
|
// use entities::sea_orm_active_enums::Role;
|
||||||
use entities::user;
|
use entities::{user, user_keys};
|
||||||
use ethers::{prelude::Address, types::Bytes};
|
use ethers::{prelude::Address, types::Bytes};
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use redis_rate_limit::redis::AsyncCommands;
|
use redis_rate_limit::redis::AsyncCommands;
|
||||||
use sea_orm::ActiveModelTrait;
|
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, TransactionTrait};
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Serialize};
|
||||||
use siwe::Message;
|
use siwe::Message;
|
||||||
use std::ops::Add;
|
use std::ops::Add;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -122,6 +125,13 @@ pub struct PostLogin {
|
|||||||
// signer: String,
|
// signer: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct PostLoginResponse {
|
||||||
|
bearer_token: Ulid,
|
||||||
|
// TODO: change this Ulid
|
||||||
|
api_key: Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
/// Post to the user endpoint to register or login.
|
/// Post to the user endpoint to register or login.
|
||||||
pub async fn post_login(
|
pub async fn post_login(
|
||||||
@ -132,8 +142,6 @@ pub async fn post_login(
|
|||||||
) -> FrontendResult {
|
) -> FrontendResult {
|
||||||
let _ip = rate_limit_by_ip(&app, ip).await?;
|
let _ip = rate_limit_by_ip(&app, ip).await?;
|
||||||
|
|
||||||
let mut new_user = true; // TODO: check the database
|
|
||||||
|
|
||||||
if let Some(invite_code) = &app.config.invite_code {
|
if let Some(invite_code) = &app.config.invite_code {
|
||||||
// we don't do per-user referral codes because we shouldn't collect what we don't need.
|
// we don't do per-user referral codes because we shouldn't collect what we don't need.
|
||||||
// we don't need to build a social graph between addresses like that.
|
// we don't need to build a social graph between addresses like that.
|
||||||
@ -159,48 +167,82 @@ pub async fn post_login(
|
|||||||
todo!("proper error message: {}", e)
|
todo!("proper error message: {}", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: create a new auth bearer token (ULID?)
|
let bearer_token = Ulid::new();
|
||||||
|
|
||||||
|
let db = app.db_conn.as_ref().unwrap();
|
||||||
|
|
||||||
|
// TODO: limit columns or load whole user?
|
||||||
|
let u = user::Entity::find()
|
||||||
|
.filter(user::Column::Address.eq(our_msg.address.as_ref()))
|
||||||
|
.one(db)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let (u_id, response) = match u {
|
||||||
|
None => {
|
||||||
|
let txn = db.begin().await?;
|
||||||
|
|
||||||
let response = if new_user {
|
|
||||||
// the only thing we need from them is an address
|
// the only thing we need from them is an address
|
||||||
// everything else is optional
|
// everything else is optional
|
||||||
let user = user::ActiveModel {
|
let u = user::ActiveModel {
|
||||||
address: sea_orm::Set(payload.address.to_fixed_bytes().into()),
|
address: sea_orm::Set(payload.address.to_fixed_bytes().into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let db = app.db_conn.as_ref().unwrap();
|
let u = u.insert(&txn).await?;
|
||||||
|
|
||||||
let user = user.insert(db).await.unwrap();
|
|
||||||
|
|
||||||
let api_key = todo!("create an api key");
|
|
||||||
|
|
||||||
/*
|
|
||||||
let rpm = app.config.something;
|
|
||||||
|
|
||||||
// create a key for the new user
|
|
||||||
// TODO: requests_per_minute should be configurable
|
|
||||||
let uk = user_keys::ActiveModel {
|
let uk = user_keys::ActiveModel {
|
||||||
user_id: u.id,
|
user_id: sea_orm::Set(u.id),
|
||||||
api_key: sea_orm::Set(api_key),
|
api_key: sea_orm::Set(new_api_key()),
|
||||||
requests_per_minute: sea_orm::Set(rpm),
|
requests_per_minute: sea_orm::Set(app.config.default_requests_per_minute),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: if this fails, rever adding the user, too
|
// TODO: if this fails, revert adding the user, too
|
||||||
let uk = uk.save(&txn).await.context("Failed saving new user key")?;
|
let uk = uk
|
||||||
|
.insert(&txn)
|
||||||
|
.await
|
||||||
|
.context("Failed saving new user key")?;
|
||||||
|
|
||||||
// TODO: set a cookie?
|
txn.commit().await?;
|
||||||
|
|
||||||
// TODO: do not expose user ids
|
let response_json = PostLoginResponse {
|
||||||
// TODO: return an api key and a bearer token
|
bearer_token,
|
||||||
(StatusCode::CREATED, Json(user)).into_response()
|
api_key: uk.api_key,
|
||||||
*/
|
|
||||||
} else {
|
|
||||||
todo!("load existing user from the database");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: save the auth bearer token in redis with a long (7 or 30 day?) expiry.
|
let response = (StatusCode::CREATED, Json(response_json)).into_response();
|
||||||
|
|
||||||
|
(u.id, response)
|
||||||
|
}
|
||||||
|
Some(u) => {
|
||||||
|
// the user is already registered
|
||||||
|
// TODO: what if the user has multiple keys?
|
||||||
|
let uk = user_keys::Entity::find()
|
||||||
|
.filter(user_keys::Column::UserId.eq(u.id))
|
||||||
|
.one(db)
|
||||||
|
.await
|
||||||
|
.context("failed loading user's key")?
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let response_json = PostLoginResponse {
|
||||||
|
bearer_token,
|
||||||
|
api_key: uk.api_key,
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = (StatusCode::OK, Json(response_json)).into_response();
|
||||||
|
|
||||||
|
(u.id, response)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: set a session cookie with the bearer token?
|
||||||
|
// save the bearer token in redis with a long (7 or 30 day?) expiry. or in database?
|
||||||
|
let mut redis_conn = app.redis_conn().await?;
|
||||||
|
|
||||||
|
let bearer_key = format!("bearer:{}", bearer_token);
|
||||||
|
|
||||||
|
redis_conn.set(bearer_key, u_id.to_string()).await?;
|
||||||
|
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
@ -217,7 +259,7 @@ pub struct PostUser {
|
|||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
/// post to the user endpoint to modify your account
|
/// post to the user endpoint to modify your account
|
||||||
pub async fn post_user(
|
pub async fn post_user(
|
||||||
AuthBearer(auth_token): AuthBearer,
|
AuthBearer(bearer_token): AuthBearer,
|
||||||
ClientIp(ip): ClientIp,
|
ClientIp(ip): ClientIp,
|
||||||
Extension(app): Extension<Arc<Web3ProxyApp>>,
|
Extension(app): Extension<Arc<Web3ProxyApp>>,
|
||||||
Json(payload): Json<PostUser>,
|
Json(payload): Json<PostUser>,
|
||||||
@ -225,7 +267,7 @@ pub async fn post_user(
|
|||||||
let _ip = rate_limit_by_ip(&app, ip).await?;
|
let _ip = rate_limit_by_ip(&app, ip).await?;
|
||||||
|
|
||||||
ProtectedAction::PostUser
|
ProtectedAction::PostUser
|
||||||
.verify(app.as_ref(), auth_token, &payload.primary_address)
|
.verify(app.as_ref(), bearer_token, &payload.primary_address)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// let user = user::ActiveModel {
|
// let user = user::ActiveModel {
|
||||||
@ -246,10 +288,17 @@ impl ProtectedAction {
|
|||||||
async fn verify(
|
async fn verify(
|
||||||
self,
|
self,
|
||||||
app: &Web3ProxyApp,
|
app: &Web3ProxyApp,
|
||||||
auth_token: String,
|
bearer_token: String,
|
||||||
primary_address: &Address,
|
primary_address: &Address,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
// TODO: get the attached address from redis for the given auth_token.
|
// get the attached address from redis for the given auth_token.
|
||||||
|
let bearer_key = format!("bearer:{}", bearer_token);
|
||||||
|
|
||||||
|
let mut redis_conn = app.redis_conn().await?;
|
||||||
|
|
||||||
|
// TODO: is this type correct?
|
||||||
|
let u_id: Option<u64> = redis_conn.get(bearer_key).await?;
|
||||||
|
|
||||||
// TODO: if auth_address == primary_address, allow
|
// TODO: if auth_address == primary_address, allow
|
||||||
// TODO: if auth_address != primary_address, only allow if they are a secondary user with the correct role
|
// TODO: if auth_address != primary_address, only allow if they are a secondary user with the correct role
|
||||||
todo!("verify token for the given user");
|
todo!("verify token for the given user");
|
||||||
|
Loading…
Reference in New Issue
Block a user