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
$ echo 0 | sudo tee /proc/sys/kernel/kptr_restrict
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

View File

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

View File

@ -145,7 +145,7 @@ pub struct PostLoginResponse {
/// Used for authenticating additonal requests.
bearer_token: Ulid,
/// Used for authenticating with the RPC endpoints.
api_keys: Vec<UserKey>,
api_keys: HashMap<u64, UserKey>,
user_id: u64,
// TODO: what else?
}
@ -162,45 +162,36 @@ pub async fn user_login_post(
) -> FrontendResult {
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
// 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")?;
// 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")?;
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];
for x in 0..65 {
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")?;
// 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 {
payload.msg
payload
.msg
.parse::<siwe::Message>()
.context("parsing string message")?
};
let their_msg = their_msg
.parse::<siwe::Message>()
.context("parsing string message")?;
// TODO: this is fragile
// TODO: this is fragile. have a helper function/struct for redis keys
let login_nonce_key = format!("login_nonce:{}", &their_msg.nonce);
// 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: {:#?}; {:#?}",
err_1,
err_191
))?;
)
.into());
}
}
let bearer_token = Ulid::new();
let db_conn = app.db_conn().context("Getting database connection")?;
// TODO: limit columns or load whole user?
@ -250,8 +240,17 @@ pub async fn user_login_post(
.await
.unwrap();
let (u, _uks, response) = match u {
let (u, uks, status_code) = match u {
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?;
// 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?;
// create the user's first api key
// TODO: rename to UserApiKey? RpcApiKey?
let user_key = UserKey::new();
// TODO: variable requests per minute depending on the invite code
let uk = user_keys::ActiveModel {
user_id: sea_orm::Set(u.id),
api_key: sea_orm::Set(user_key.into()),
@ -272,7 +274,6 @@ pub async fn user_login_post(
..Default::default()
};
// TODO: if this fails, revert adding the user, too
let uk = uk
.insert(&txn)
.await
@ -280,17 +281,10 @@ pub async fn user_login_post(
let uks = vec![uk];
// save the user and key to the database
txn.commit().await?;
let response_json = PostLoginResponse {
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)
(u, uks, StatusCode::CREATED)
}
Some(u) => {
// the user is already registered
@ -300,19 +294,26 @@ pub async fn user_login_post(
.await
.context("failed loading user's key")?;
let response_json = PostLoginResponse {
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)
(u, uks, StatusCode::OK)
}
};
// 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
// TODO: use a helper function/struct for this
let bearer_redis_key = format!("bearer:{}", bearer_token);
// expire in 4 weeks
@ -353,7 +354,7 @@ pub async fn user_logout_post(
/// the JSON input to the `post_user` handler
/// This handles updating
#[derive(Deserialize)]
pub struct PostUser {
pub struct UserProfilePost {
primary_address: Address,
// TODO: make sure the email address is valid. probably have a "verified" column in the database
email: Option<String>,
@ -365,7 +366,7 @@ pub async fn user_profile_post(
TypedHeader(Authorization(bearer_token)): TypedHeader<Authorization<Bearer>>,
ClientIp(ip): ClientIp,
Extension(app): Extension<Arc<Web3ProxyApp>>,
Json(payload): Json<PostUser>,
Json(payload): Json<UserProfilePost>,
) -> FrontendResult {
login_is_authorized(&app, ip).await?;