only require invite_code on first login

This commit is contained in:
Bryan Stitt 2022-10-25 17:21:11 +00:00
parent 6e356cf9a7
commit 9ec8abdf49
3 changed files with 53 additions and 49 deletions

View File

@ -87,7 +87,11 @@ Flame graphs make a developer's join of finding slow code painless:
1 1
$ echo 0 | sudo tee /proc/sys/kernel/kptr_restrict $ echo 0 | sudo tee /proc/sys/kernel/kptr_restrict
0 0
$ CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph $ cat /proc/sys/kernel/perf_event_paranoid
4
$ echo -1 | sudo tee /proc/sys/kernel/perf_event_paranoid
0
$ CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph --bin web3_proxy
## GDB ## GDB

View File

@ -80,7 +80,6 @@ pub struct UserProxyResponseKey {
error_response: bool, error_response: bool,
} }
/// key is the (user_key_id, method, error_response)
pub type UserProxyResponseCache = Cache< pub type UserProxyResponseCache = Cache<
UserProxyResponseKey, UserProxyResponseKey,
Arc<ProxyResponseAggregate>, Arc<ProxyResponseAggregate>,

View File

@ -145,7 +145,7 @@ pub struct PostLoginResponse {
/// Used for authenticating additonal requests. /// Used for authenticating additonal requests.
bearer_token: Ulid, bearer_token: Ulid,
/// Used for authenticating with the RPC endpoints. /// Used for authenticating with the RPC endpoints.
api_keys: Vec<UserKey>, api_keys: HashMap<u64, UserKey>,
user_id: u64, user_id: u64,
// TODO: what else? // TODO: what else?
} }
@ -162,45 +162,36 @@ pub async fn user_login_post(
) -> FrontendResult { ) -> FrontendResult {
login_is_authorized(&app, ip).await?; login_is_authorized(&app, ip).await?;
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) {
warn!("if address is already registered, allow login! else, error");
// TODO: this return doesn't seem right
return Err(anyhow::anyhow!("checking invite_code"))?;
}
}
// we can't trust that they didn't tamper with the message in some way // we can't trust that they didn't tamper with the message in some way
// TODO: it seems like some clients do things unexpectedly. these don't always parse // TODO: it seems like some clients do things unexpectedly. these don't always parse
// let their_msg: siwe::Message = payload.msg.parse().context("parsing user's message")?; // let their_msg: siwe::Message = payload.msg.parse().context("parsing user's message")?;
// TODO: do this safely // 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).context("parsing sig")?; let their_sig_bytes = Bytes::from_str(&payload.sig).context("parsing sig")?;
if their_sig_bytes.len() != 65 { if their_sig_bytes.len() != 65 {
return Err(anyhow::anyhow!("checking signature length"))?; return Err(anyhow::anyhow!("checking signature length").into());
} }
let mut their_sig: [u8; 65] = [0; 65]; let mut their_sig: [u8; 65] = [0; 65];
for x in 0..65 { for x in 0..65 {
their_sig[x] = their_sig_bytes[x] their_sig[x] = their_sig_bytes[x]
} }
let their_msg: String = if payload.msg.starts_with("0x") { // TODO: checking 0x seems fragile, but I think it will be fine
let their_msg: Message = if payload.msg.starts_with("0x") {
let their_msg_bytes = Bytes::from_str(&payload.msg).context("parsing payload message")?; let their_msg_bytes = Bytes::from_str(&payload.msg).context("parsing payload message")?;
// TODO: lossy or no? // TODO: lossy or no?
String::from_utf8_lossy(their_msg_bytes.as_ref()).to_string() String::from_utf8_lossy(their_msg_bytes.as_ref())
.parse::<siwe::Message>()
.context("parsing hex string message")?
} else { } else {
payload.msg payload
.msg
.parse::<siwe::Message>()
.context("parsing string message")?
}; };
let their_msg = their_msg // TODO: this is fragile. have a helper function/struct for redis keys
.parse::<siwe::Message>()
.context("parsing string message")?;
// TODO: this is fragile
let login_nonce_key = format!("login_nonce:{}", &their_msg.nonce); let login_nonce_key = format!("login_nonce:{}", &their_msg.nonce);
// fetch the message we gave them from our redis // fetch the message we gave them from our redis
@ -235,12 +226,11 @@ pub async fn user_login_post(
"both the primary and eip191 verification failed: {:#?}; {:#?}", "both the primary and eip191 verification failed: {:#?}; {:#?}",
err_1, err_1,
err_191 err_191
))?; )
.into());
} }
} }
let bearer_token = Ulid::new();
let db_conn = app.db_conn().context("Getting database connection")?; let db_conn = app.db_conn().context("Getting database connection")?;
// TODO: limit columns or load whole user? // TODO: limit columns or load whole user?
@ -250,8 +240,17 @@ pub async fn user_login_post(
.await .await
.unwrap(); .unwrap();
let (u, _uks, response) = match u { let (u, uks, status_code) = match u {
None => { None => {
// user does not exist yet
// check the invite code
if let Some(invite_code) = &app.config.invite_code {
if query.invite_code.as_ref() != Some(invite_code) {
return Err(anyhow::anyhow!("checking invite_code").into());
}
}
let txn = db_conn.begin().await?; let txn = db_conn.begin().await?;
// the only thing we need from them is an address // the only thing we need from them is an address
@ -263,8 +262,11 @@ pub async fn user_login_post(
let u = u.insert(&txn).await?; let u = u.insert(&txn).await?;
// create the user's first api key
// TODO: rename to UserApiKey? RpcApiKey?
let user_key = UserKey::new(); let user_key = UserKey::new();
// TODO: variable requests per minute depending on the invite code
let uk = user_keys::ActiveModel { let uk = user_keys::ActiveModel {
user_id: sea_orm::Set(u.id), user_id: sea_orm::Set(u.id),
api_key: sea_orm::Set(user_key.into()), api_key: sea_orm::Set(user_key.into()),
@ -272,7 +274,6 @@ pub async fn user_login_post(
..Default::default() ..Default::default()
}; };
// TODO: if this fails, revert adding the user, too
let uk = uk let uk = uk
.insert(&txn) .insert(&txn)
.await .await
@ -280,17 +281,10 @@ pub async fn user_login_post(
let uks = vec![uk]; let uks = vec![uk];
// save the user and key to the database
txn.commit().await?; txn.commit().await?;
let response_json = PostLoginResponse { (u, uks, StatusCode::CREATED)
api_keys: uks.iter().map(|uk| uk.api_key.into()).collect(),
bearer_token,
user_id: u.id,
};
let response = (StatusCode::CREATED, Json(response_json)).into_response();
(u, uks, response)
} }
Some(u) => { Some(u) => {
// the user is already registered // the user is already registered
@ -300,19 +294,26 @@ pub async fn user_login_post(
.await .await
.context("failed loading user's key")?; .context("failed loading user's key")?;
let response_json = PostLoginResponse { (u, uks, StatusCode::OK)
api_keys: uks.iter().map(|uk| uk.api_key.into()).collect(),
bearer_token,
user_id: u.id,
};
let response = (StatusCode::OK, Json(response_json)).into_response();
(u, uks, response)
} }
}; };
// create a bearer token for the user.
let bearer_token = Ulid::new();
let response_json = PostLoginResponse {
api_keys: uks
.into_iter()
.map(|uk| (uk.id, uk.api_key.into()))
.collect(),
bearer_token,
user_id: u.id,
};
let response = (status_code, Json(response_json)).into_response();
// add bearer to redis // add bearer to redis
// TODO: use a helper function/struct for this
let bearer_redis_key = format!("bearer:{}", bearer_token); let bearer_redis_key = format!("bearer:{}", bearer_token);
// expire in 4 weeks // expire in 4 weeks
@ -353,7 +354,7 @@ pub async fn user_logout_post(
/// the JSON input to the `post_user` handler /// the JSON input to the `post_user` handler
/// This handles updating /// This handles updating
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct PostUser { pub struct UserProfilePost {
primary_address: Address, primary_address: Address,
// TODO: make sure the email address is valid. probably have a "verified" column in the database // TODO: make sure the email address is valid. probably have a "verified" column in the database
email: Option<String>, email: Option<String>,
@ -365,7 +366,7 @@ pub async fn user_profile_post(
TypedHeader(Authorization(bearer_token)): TypedHeader<Authorization<Bearer>>, TypedHeader(Authorization(bearer_token)): TypedHeader<Authorization<Bearer>>,
ClientIp(ip): ClientIp, ClientIp(ip): ClientIp,
Extension(app): Extension<Arc<Web3ProxyApp>>, Extension(app): Extension<Arc<Web3ProxyApp>>,
Json(payload): Json<PostUser>, Json(payload): Json<UserProfilePost>,
) -> FrontendResult { ) -> FrontendResult {
login_is_authorized(&app, ip).await?; login_is_authorized(&app, ip).await?;