2022-10-18 00:47:58 +03:00
//! Handle registration, logins, and managing account data.
2022-07-14 00:49:57 +03:00
2022-09-24 08:53:45 +03:00
use super ::authorization ::{ login_is_authorized , UserKey } ;
2022-08-21 12:44:53 +03:00
use super ::errors ::FrontendResult ;
2022-09-24 08:53:45 +03:00
use crate ::app ::Web3ProxyApp ;
2022-10-19 21:38:00 +03:00
use crate ::user_stats ::get_aggregate_stats ;
2022-08-27 08:42:25 +03:00
use anyhow ::Context ;
2022-08-11 04:53:27 +03:00
use axum ::{
2022-08-21 11:18:57 +03:00
extract ::{ Path , Query } ,
2022-09-24 00:46:27 +03:00
headers ::{ authorization ::Bearer , Authorization } ,
2022-08-21 12:44:53 +03:00
response ::IntoResponse ,
2022-09-24 00:46:27 +03:00
Extension , Json , TypedHeader ,
2022-08-11 04:53:27 +03:00
} ;
2022-08-04 04:10:27 +03:00
use axum_client_ip ::ClientIp ;
2022-08-16 22:29:00 +03:00
use axum_macros ::debug_handler ;
2022-10-19 21:38:00 +03:00
use chrono ::NaiveDateTime ;
use entities ::{ user , user_keys } ;
2022-08-04 04:10:27 +03:00
use ethers ::{ prelude ::Address , types ::Bytes } ;
2022-08-19 23:18:12 +03:00
use hashbrown ::HashMap ;
2022-09-15 20:57:24 +03:00
use http ::StatusCode ;
use redis_rate_limiter ::redis ::AsyncCommands ;
2022-10-19 21:38:00 +03:00
use sea_orm ::{ ActiveModelTrait , ColumnTrait , EntityTrait , QueryFilter , TransactionTrait } ;
2022-08-27 08:42:25 +03:00
use serde ::{ Deserialize , Serialize } ;
2022-10-07 05:15:53 +03:00
use siwe ::{ Message , VerificationOpts } ;
2022-08-27 05:13:36 +03:00
use std ::ops ::Add ;
2022-08-04 04:10:27 +03:00
use std ::sync ::Arc ;
2022-08-17 01:52:12 +03:00
use time ::{ Duration , OffsetDateTime } ;
2022-10-19 21:38:00 +03:00
use tracing ::debug ;
2022-08-21 11:18:57 +03:00
use ulid ::Ulid ;
2022-10-18 00:47:58 +03:00
/// `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:
/// - eip191_bytes
/// - eip191_hash
/// - eip4361 (default)
///
/// Coming soon: eip1271
///
/// This is the initial entrypoint for logging in. Take the response from this endpoint and give it to your user's wallet for singing. POST the response to `/user/login`.
///
/// Rate limited by IP address.
2022-08-16 22:29:00 +03:00
#[ debug_handler ]
2022-10-18 00:47:58 +03:00
pub async fn user_login_get (
2022-08-17 00:10:09 +03:00
Extension ( app ) : Extension < Arc < Web3ProxyApp > > ,
2022-08-17 00:43:39 +03:00
ClientIp ( ip ) : ClientIp ,
2022-08-17 01:52:12 +03:00
// TODO: what does axum's error handling look like if the path fails to parse?
// TODO: allow ENS names here?
2022-10-19 21:38:00 +03:00
Path ( ( user_address , message_eip ) ) : Path < ( Address , Option < String > ) > ,
Query ( query ) : Query < HashMap < String , String > > ,
2022-08-17 00:43:39 +03:00
) -> FrontendResult {
2022-09-28 06:35:55 +03:00
// give these named variables so that we drop them at the very end of this function
let ( _ , _semaphore ) = login_is_authorized ( & app , ip ) . await ? ;
2022-08-17 00:43:39 +03:00
// at first i thought about checking that user_address is in our db
2022-08-21 11:18:57 +03:00
// but theres no need to separate the registration and login flows
2022-08-17 00:43:39 +03:00
// its a better UX to just click "login with ethereum" and have the account created if it doesn't exist
// we can prompt for an email and and payment after they log in
2022-08-21 11:18:57 +03:00
// create a message and save it in redis
2022-08-17 01:52:12 +03:00
// TODO: how many seconds? get from config?
2022-08-23 21:48:27 +03:00
// TODO: while developing, we put a giant number here
let expire_seconds : usize = 28800 ;
2022-08-17 01:52:12 +03:00
2022-08-21 11:18:57 +03:00
let nonce = Ulid ::new ( ) ;
2022-08-17 01:52:12 +03:00
let issued_at = OffsetDateTime ::now_utc ( ) ;
let expiration_time = issued_at . add ( Duration ::new ( expire_seconds as i64 , 0 ) ) ;
// TODO: get most of these from the app config
let message = Message {
2022-10-19 21:38:00 +03:00
// TODO: should domain be llamanodes, or llamarpc, or the subdomain of llamarpc?
2022-08-17 01:52:12 +03:00
domain : " staging.llamanodes.com " . parse ( ) . unwrap ( ) ,
address : user_address . to_fixed_bytes ( ) ,
statement : Some ( " 🦙🦙🦙🦙🦙 " . to_string ( ) ) ,
uri : " https://staging.llamanodes.com/ " . parse ( ) . unwrap ( ) ,
version : siwe ::Version ::V1 ,
chain_id : 1 ,
expiration_time : Some ( expiration_time . into ( ) ) ,
issued_at : issued_at . into ( ) ,
nonce : nonce . to_string ( ) ,
not_before : None ,
request_id : None ,
resources : vec ! [ ] ,
} ;
let session_key = format! ( " pending: {} " , nonce ) ;
2022-10-19 03:56:57 +03:00
// TODO: if no redis server, store in local cache? at least give a better error. right now this seems to be a 502
2022-08-21 11:18:57 +03:00
// the address isn't enough. we need to save the actual message so we can read the nonce
2022-09-02 08:40:56 +03:00
// TODO: what message format is the most efficient to store in redis? probably eip191_bytes
2022-08-23 21:48:27 +03:00
// we add 1 to expire_seconds just to be sure redis has the key for the full expiration_time
2022-10-19 21:38:00 +03:00
// TODO: store a maximum number of attempted logins? anyone can request so we don't want to allow DOS attacks
2022-08-23 21:48:27 +03:00
app . redis_conn ( )
. await ?
. set_ex ( session_key , message . to_string ( ) , expire_seconds + 1 )
2022-08-17 01:52:12 +03:00
. await ? ;
2022-08-19 23:18:12 +03:00
// there are multiple ways to sign messages and not all wallets support them
2022-10-18 00:47:58 +03:00
// TODO: default message eip from config?
2022-10-19 21:38:00 +03:00
let message_eip = message_eip . unwrap_or_else ( | | " eip4361 " . to_string ( ) ) ;
2022-08-19 23:18:12 +03:00
let message : String = match message_eip . as_str ( ) {
2022-09-02 08:40:56 +03:00
" eip191_bytes " = > Bytes ::from ( message . eip191_bytes ( ) . unwrap ( ) ) . to_string ( ) ,
2022-08-19 23:18:12 +03:00
" eip191_hash " = > Bytes ::from ( & message . eip191_hash ( ) . unwrap ( ) ) . to_string ( ) ,
2022-09-14 09:18:13 +03:00
" eip4361 " = > message . to_string ( ) ,
2022-09-10 03:58:33 +03:00
_ = > {
2022-10-19 03:56:57 +03:00
// TODO: custom error that is handled a 401
2022-09-10 03:58:33 +03:00
return Err ( anyhow ::anyhow! ( " invalid message eip given " ) . into ( ) ) ;
}
2022-08-19 23:18:12 +03:00
} ;
Ok ( message . into_response ( ) )
2022-08-16 22:29:00 +03:00
}
2022-08-04 02:17:02 +03:00
2022-10-18 00:47:58 +03:00
/// Query params for our `post_login` handler.
2022-08-21 11:18:57 +03:00
#[ derive(Debug, Deserialize) ]
pub struct PostLoginQuery {
2022-10-18 00:47:58 +03:00
/// 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.
pub invite_code : Option < String > ,
2022-08-21 11:18:57 +03:00
}
/// JSON body to our `post_login` handler.
2022-10-18 00:47:58 +03:00
/// Currently only siwe logins that send an address, msg, and sig are allowed.
2022-10-19 03:56:57 +03:00
/// Email/password and other login methods are planned.
2022-08-21 11:18:57 +03:00
#[ derive(Deserialize) ]
pub struct PostLogin {
2022-10-18 00:47:58 +03:00
pub address : Address ,
pub msg : String ,
pub sig : Bytes ,
2022-08-21 12:39:38 +03:00
// TODO: do we care about these? we should probably check the version is something we expect
// version: String,
// signer: String,
2022-08-21 11:18:57 +03:00
}
2022-10-18 00:47:58 +03:00
/// Successful logins receive a bearer_token and all of the user's api keys.
2022-08-27 08:42:25 +03:00
#[ derive(Serialize) ]
pub struct PostLoginResponse {
2022-10-18 00:47:58 +03:00
/// Used for authenticating additonal requests.
2022-08-27 08:42:25 +03:00
bearer_token : Ulid ,
2022-10-18 00:47:58 +03:00
/// Used for authenticating with the RPC endpoints.
2022-09-24 08:53:45 +03:00
api_keys : Vec < UserKey > ,
2022-10-19 03:56:57 +03:00
// TODO: what else?
2022-08-27 08:42:25 +03:00
}
2022-10-18 00:47:58 +03:00
/// `POST /user/login` - Register or login by posting a signed "siwe" message.
/// It is recommended to save the returned bearer token in a cookie.
/// The bearer token can be used to authenticate other requests, such as getting the user's stats or modifying the user's profile.
2022-09-24 05:47:44 +03:00
#[ debug_handler ]
2022-10-18 00:47:58 +03:00
pub async fn user_login_post (
2022-08-21 11:18:57 +03:00
Extension ( app ) : Extension < Arc < Web3ProxyApp > > ,
2022-09-24 00:46:27 +03:00
ClientIp ( ip ) : ClientIp ,
2022-08-21 11:18:57 +03:00
Json ( payload ) : Json < PostLogin > ,
Query ( query ) : Query < PostLoginQuery > ,
2022-08-21 12:39:38 +03:00
) -> FrontendResult {
2022-09-28 06:35:55 +03:00
// give these named variables so that we drop them at the very end of this function
let ( _ , _semaphore ) = login_is_authorized ( & app , ip ) . await ? ;
2022-08-04 04:10:27 +03:00
2022-08-21 11:18:57 +03:00
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 need to build a social graph between addresses like that.
if query . invite_code . as_ref ( ) ! = Some ( invite_code ) {
todo! ( " if address is already registered, allow login! else, error " )
}
2022-08-04 04:10:27 +03:00
}
2022-08-21 11:18:57 +03:00
// we can't trust that they didn't tamper with the message in some way
let their_msg : siwe ::Message = payload . msg . parse ( ) . unwrap ( ) ;
let their_sig : [ u8 ; 65 ] = payload . sig . as_ref ( ) . try_into ( ) . unwrap ( ) ;
// fetch the message we gave them from our redis
2022-08-17 02:03:50 +03:00
// TODO: use getdel
2022-08-23 21:48:27 +03:00
let our_msg : String = app . redis_conn ( ) . await ? . get ( & their_msg . nonce ) . await ? ;
2022-08-17 02:03:50 +03:00
2022-08-21 11:18:57 +03:00
let our_msg : siwe ::Message = our_msg . parse ( ) . unwrap ( ) ;
2022-08-17 02:03:50 +03:00
2022-10-07 05:15:53 +03:00
let verify_config = VerificationOpts {
domain : Some ( our_msg . domain ) ,
nonce : Some ( our_msg . nonce ) ,
.. Default ::default ( )
} ;
2022-08-21 11:18:57 +03:00
// check the domain and a nonce. let timestamp be automatic
2022-10-07 05:15:53 +03:00
if let Err ( e ) = their_msg . verify ( & their_sig , & verify_config ) . await {
2022-08-04 04:10:27 +03:00
// message cannot be correctly authenticated
todo! ( " proper error message: {} " , e )
}
2022-08-27 08:42:25 +03:00
let bearer_token = Ulid ::new ( ) ;
2022-08-23 21:56:19 +03:00
2022-09-24 07:31:06 +03:00
let db = app . db_conn ( ) . context ( " Getting database connection " ) ? ;
2022-07-14 00:49:57 +03:00
2022-08-27 08:42:25 +03:00
// TODO: limit columns or load whole user?
let u = user ::Entity ::find ( )
. filter ( user ::Column ::Address . eq ( our_msg . address . as_ref ( ) ) )
2022-10-19 03:56:57 +03:00
. one ( & db )
2022-08-27 08:42:25 +03:00
. await
. unwrap ( ) ;
2022-08-04 02:17:02 +03:00
2022-09-24 05:47:44 +03:00
let ( u , _uks , response ) = match u {
2022-08-27 08:42:25 +03:00
None = > {
let txn = db . begin ( ) . await ? ;
2022-08-04 02:17:02 +03:00
2022-08-27 08:42:25 +03:00
// the only thing we need from them is an address
// everything else is optional
let u = user ::ActiveModel {
address : sea_orm ::Set ( payload . address . to_fixed_bytes ( ) . into ( ) ) ,
.. Default ::default ( )
} ;
2022-08-17 02:03:50 +03:00
2022-08-27 08:42:25 +03:00
let u = u . insert ( & txn ) . await ? ;
2022-08-17 02:03:50 +03:00
2022-09-24 08:53:45 +03:00
let user_key = UserKey ::new ( ) ;
2022-08-27 08:42:25 +03:00
let uk = user_keys ::ActiveModel {
user_id : sea_orm ::Set ( u . id ) ,
2022-09-24 08:53:45 +03:00
api_key : sea_orm ::Set ( user_key . into ( ) ) ,
2022-10-19 02:27:33 +03:00
requests_per_minute : sea_orm ::Set ( app . config . default_user_requests_per_minute ) ,
2022-08-27 08:42:25 +03:00
.. Default ::default ( )
} ;
2022-08-21 11:18:57 +03:00
2022-08-27 08:42:25 +03:00
// TODO: if this fails, revert adding the user, too
let uk = uk
. insert ( & txn )
. await
. context ( " Failed saving new user key " ) ? ;
2022-08-17 02:03:50 +03:00
2022-09-24 05:47:44 +03:00
let uks = vec! [ uk ] ;
2022-08-27 08:42:25 +03:00
txn . commit ( ) . await ? ;
2022-08-16 20:47:04 +03:00
2022-08-27 08:42:25 +03:00
let response_json = PostLoginResponse {
bearer_token ,
2022-09-24 08:53:45 +03:00
api_keys : uks . iter ( ) . map ( | uk | uk . api_key . into ( ) ) . collect ( ) ,
2022-08-27 08:42:25 +03:00
} ;
let response = ( StatusCode ::CREATED , Json ( response_json ) ) . into_response ( ) ;
2022-09-24 05:47:44 +03:00
( u , uks , response )
2022-08-27 08:42:25 +03:00
}
Some ( u ) = > {
// the user is already registered
2022-09-24 05:47:44 +03:00
let uks = user_keys ::Entity ::find ( )
2022-08-27 08:42:25 +03:00
. filter ( user_keys ::Column ::UserId . eq ( u . id ) )
2022-10-19 03:56:57 +03:00
. all ( & db )
2022-08-27 08:42:25 +03:00
. await
2022-09-24 05:47:44 +03:00
. context ( " failed loading user's key " ) ? ;
2022-08-27 08:42:25 +03:00
let response_json = PostLoginResponse {
bearer_token ,
2022-09-24 08:53:45 +03:00
api_keys : uks . iter ( ) . map ( | uk | uk . api_key . into ( ) ) . collect ( ) ,
2022-08-27 08:42:25 +03:00
} ;
let response = ( StatusCode ::OK , Json ( response_json ) ) . into_response ( ) ;
2022-09-24 05:47:44 +03:00
( u , uks , response )
2022-08-27 08:42:25 +03:00
}
2022-08-23 21:51:42 +03:00
} ;
2022-09-24 05:47:44 +03:00
// add bearer to redis
2022-08-27 08:42:25 +03:00
let mut redis_conn = app . redis_conn ( ) . await ? ;
2022-09-24 05:47:44 +03:00
let bearer_redis_key = format! ( " bearer: {} " , bearer_token ) ;
2022-09-03 22:43:19 +03:00
2022-09-24 05:47:44 +03:00
// expire in 4 weeks
// TODO: get expiration time from app config
// TODO: do we use this?
redis_conn
. set_ex ( bearer_redis_key , u . id . to_string ( ) , 2_419_200 )
. await ? ;
2022-08-23 21:51:42 +03:00
2022-08-23 21:56:19 +03:00
Ok ( response )
2022-07-14 00:49:57 +03:00
}
2022-10-18 00:47:58 +03:00
/// `POST /user/logout` - Forget the bearer token in the `Authentication` header.
2022-09-24 00:46:27 +03:00
#[ debug_handler ]
2022-10-18 00:47:58 +03:00
pub async fn user_logout_post (
2022-09-24 00:46:27 +03:00
Extension ( app ) : Extension < Arc < Web3ProxyApp > > ,
2022-09-24 05:47:44 +03:00
TypedHeader ( Authorization ( bearer ) ) : TypedHeader < Authorization < Bearer > > ,
2022-09-24 00:46:27 +03:00
) -> FrontendResult {
2022-09-24 07:31:06 +03:00
let mut redis_conn = app . redis_conn ( ) . await ? ;
2022-09-24 05:47:44 +03:00
// TODO: i don't like this. move this to a helper function so it is less fragile
let bearer_cache_key = format! ( " bearer: {} " , bearer . token ( ) ) ;
2022-09-24 00:46:27 +03:00
2022-09-24 05:47:44 +03:00
redis_conn . del ( bearer_cache_key ) . await ? ;
2022-09-24 00:46:27 +03:00
// TODO: what should the response be? probably json something
Ok ( " goodbye " . into_response ( ) )
}
2022-08-21 11:18:57 +03:00
/// the JSON input to the `post_user` handler
2022-09-05 04:52:59 +03:00
/// This handles updating
2022-07-14 00:49:57 +03:00
#[ derive(Deserialize) ]
2022-08-21 11:18:57 +03:00
pub struct PostUser {
2022-08-23 22:08:47 +03:00
primary_address : Address ,
2022-08-21 11:18:57 +03:00
// TODO: make sure the email address is valid. probably have a "verified" column in the database
2022-07-14 00:49:57 +03:00
email : Option < String > ,
2022-08-21 11:18:57 +03:00
// TODO: make them sign this JSON? cookie in session id is hard because its on a different domain
}
2022-10-18 00:47:58 +03:00
/// `POST /user/profile` -- modify the account connected to the bearer token in the `Authentication` header.
2022-08-21 11:18:57 +03:00
#[ debug_handler ]
2022-10-18 00:47:58 +03:00
pub async fn user_profile_post (
2022-09-24 00:46:27 +03:00
TypedHeader ( Authorization ( bearer_token ) ) : TypedHeader < Authorization < Bearer > > ,
2022-08-23 21:51:42 +03:00
ClientIp ( ip ) : ClientIp ,
Extension ( app ) : Extension < Arc < Web3ProxyApp > > ,
Json ( payload ) : Json < PostUser > ,
2022-08-21 11:18:57 +03:00
) -> FrontendResult {
2022-09-28 06:35:55 +03:00
// give these named variables so that we drop them at the very end of this function
let ( _ , _semaphore ) = login_is_authorized ( & app , ip ) . await ? ;
2022-08-21 11:18:57 +03:00
2022-09-05 04:52:59 +03:00
let user = ProtectedAction ::PostUser
2022-08-27 08:42:25 +03:00
. verify ( app . as_ref ( ) , bearer_token , & payload . primary_address )
2022-08-23 23:42:58 +03:00
. await ? ;
2022-08-23 21:48:27 +03:00
2022-09-05 04:52:59 +03:00
let mut user : user ::ActiveModel = user . into ( ) ;
// TODO: rate limit by user, too?
2022-09-28 06:35:55 +03:00
// TODO: allow changing the primary address, too. require a message from the new address to finish the change
2022-09-05 04:52:59 +03:00
if let Some ( x ) = payload . email {
2022-09-28 06:35:55 +03:00
// TODO: only Set if no change
2022-09-05 04:52:59 +03:00
if x . is_empty ( ) {
user . email = sea_orm ::Set ( None ) ;
} else {
user . email = sea_orm ::Set ( Some ( x ) ) ;
}
}
2022-09-24 07:31:06 +03:00
let db = app . db_conn ( ) . context ( " Getting database connection " ) ? ;
2022-09-05 04:52:59 +03:00
2022-10-19 03:56:57 +03:00
user . save ( & db ) . await ? ;
2022-09-05 04:52:59 +03:00
2022-08-23 21:51:42 +03:00
todo! ( " finish post_user " ) ;
2022-07-14 00:49:57 +03:00
}
2022-08-23 22:08:47 +03:00
2022-10-18 00:47:58 +03:00
/// `GET /user/balance` -- Use a bearer token to get the user's balance and spend.
///
/// - show balance in USD
/// - show deposits history (currency, amounts, transaction id)
///
/// TODO: one key per request? maybe /user/balance/:api_key?
/// TODO: this will change as we add better support for secondary users.
#[ debug_handler ]
pub async fn user_balance_get (
TypedHeader ( Authorization ( bearer_token ) ) : TypedHeader < Authorization < Bearer > > ,
Extension ( app ) : Extension < Arc < Web3ProxyApp > > ,
) -> FrontendResult {
todo! ( " user_balance_get " ) ;
}
/// `POST /user/balance` -- Manually process a confirmed txid to update a user's balance.
///
/// We will subscribe to events to watch for any user deposits, but sometimes events can be missed.
///
/// TODO: rate limit by user
/// TODO: one key per request? maybe /user/balance/:api_key?
/// TODO: this will change as we add better support for secondary users.
#[ debug_handler ]
pub async fn user_balance_post (
TypedHeader ( Authorization ( bearer_token ) ) : TypedHeader < Authorization < Bearer > > ,
Extension ( app ) : Extension < Arc < Web3ProxyApp > > ,
) -> FrontendResult {
todo! ( " user_balance_post " ) ;
}
/// `GET /user/keys` -- Use a bearer token to get the user's api keys and their settings.
///
/// TODO: one key per request? maybe /user/keys/:api_key?
#[ debug_handler ]
pub async fn user_keys_get (
TypedHeader ( Authorization ( bearer_token ) ) : TypedHeader < Authorization < Bearer > > ,
Extension ( app ) : Extension < Arc < Web3ProxyApp > > ,
) -> FrontendResult {
todo! ( " user_keys_get " ) ;
}
/// `POST /user/keys` -- Use a bearer token to create a new key or modify an existing key.
///
/// TODO: read json from the request body
/// TODO: one key per request? maybe /user/keys/:api_key?
#[ debug_handler ]
pub async fn user_keys_post (
TypedHeader ( Authorization ( bearer_token ) ) : TypedHeader < Authorization < Bearer > > ,
Extension ( app ) : Extension < Arc < Web3ProxyApp > > ,
) -> FrontendResult {
todo! ( " user_keys_post " ) ;
}
/// `GET /user/profile` -- Use a bearer token to get the user's profile.
///
/// - the email address of a user if they opted in to get contacted via email
///
/// TODO: this will change as we add better support for secondary users.
#[ debug_handler ]
pub async fn user_profile_get (
TypedHeader ( Authorization ( bearer_token ) ) : TypedHeader < Authorization < Bearer > > ,
Extension ( app ) : Extension < Arc < Web3ProxyApp > > ,
) -> FrontendResult {
2022-10-18 02:16:09 +03:00
todo! ( " user_profile_get " ) ;
2022-10-18 00:47:58 +03:00
}
/// `GET /user/stats` -- Use a bearer token to get the user's key stats such as bandwidth used and methods requested.
///
/// - show number of requests used (so we can calculate average spending over a month, burn rate for a user etc, something like "Your balance will be depleted in xx days)
///
/// TODO: one key per request? maybe /user/stats/:api_key?
/// TODO: this will change as we add better support for secondary users.
#[ debug_handler ]
pub async fn user_stats_get (
TypedHeader ( Authorization ( bearer_token ) ) : TypedHeader < Authorization < Bearer > > ,
Extension ( app ) : Extension < Arc < Web3ProxyApp > > ,
) -> FrontendResult {
2022-10-18 02:16:09 +03:00
todo! ( " user_stats_get " ) ;
2022-10-18 00:47:58 +03:00
}
2022-10-19 03:56:57 +03:00
/// `GET /user/stats/aggregate` -- Public endpoint for aggregate stats such as bandwidth used and methods requested.
#[ debug_handler ]
pub async fn user_stats_aggregate_get (
Extension ( app ) : Extension < Arc < Web3ProxyApp > > ,
2022-10-19 21:38:00 +03:00
Query ( params ) : Query < HashMap < String , String > > ,
2022-10-19 03:56:57 +03:00
) -> FrontendResult {
// TODO: how is this supposed to be used?
let db = app . db_conn . clone ( ) . context ( " no db " ) ? ;
2022-10-19 21:38:00 +03:00
let chain_id = params
. get ( " chain_id " )
. map_or_else ::< anyhow ::Result < u64 > , _ , _ > (
| | Ok ( app . config . chain_id ) ,
| c | {
let c = c . parse ( ) ? ;
Ok ( c )
} ,
) ? ;
let query_start = params
. get ( " start_timestamp " )
. map_or_else ::< anyhow ::Result < NaiveDateTime > , _ , _ > (
| | {
// no timestamp in params. set default
let x = chrono ::Utc ::now ( ) - chrono ::Duration ::days ( 30 ) ;
Ok ( x . naive_utc ( ) )
} ,
| x : & String | {
// parse the given timestamp
let x = x . parse ::< i64 > ( ) . context ( " parsing timestamp query param " ) ? ;
// TODO: error code 401
let x = NaiveDateTime ::from_timestamp_opt ( x , 0 )
. context ( " parsing timestamp query param " ) ? ;
Ok ( x )
} ,
) ? ;
// TODO: optionally no chain id?
let x = get_aggregate_stats ( chain_id , & db , query_start ) . await ? ;
2022-10-19 03:56:57 +03:00
Ok ( Json ( x ) . into_response ( ) )
}
2022-10-18 00:47:58 +03:00
/// `GET /user/profile` -- Use a bearer token to get the user's profile such as their optional email address.
/// Handle authorization for a given address and bearer token.
2022-08-23 23:42:58 +03:00
// TODO: what roles should exist?
enum ProtectedAction {
PostUser ,
}
2022-08-23 22:08:47 +03:00
2022-08-23 23:42:58 +03:00
impl ProtectedAction {
2022-10-18 00:47:58 +03:00
/// Verify that the given bearer token and address are allowed to take the specified action.
2022-08-23 23:42:58 +03:00
async fn verify (
self ,
app : & Web3ProxyApp ,
2022-09-24 07:31:06 +03:00
// TODO: i don't think we want Bearer here. we want user_key and a helper for bearer -> user_key
2022-09-24 00:46:27 +03:00
bearer : Bearer ,
2022-10-18 00:47:58 +03:00
// TODO: what about secondary addresses? maybe an enum for primary or secondary?
2022-08-23 23:42:58 +03:00
primary_address : & Address ,
2022-09-05 04:52:59 +03:00
) -> anyhow ::Result < user ::Model > {
2022-08-27 08:42:25 +03:00
// get the attached address from redis for the given auth_token.
let mut redis_conn = app . redis_conn ( ) . await ? ;
2022-09-24 07:31:06 +03:00
let bearer_cache_key = format! ( " bearer: {} " , bearer . token ( ) ) ;
let user_key_id : Option < u64 > = redis_conn
. get ( bearer_cache_key )
. await
. context ( " fetching bearer cache key from redis " ) ? ;
2022-09-24 00:46:27 +03:00
2022-08-23 23:42:58 +03:00
// 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! ( " verify token for the given user " ) ;
}
2022-08-23 22:08:47 +03:00
}