more robust login

This commit is contained in:
Bryan Stitt 2022-10-20 08:14:38 +00:00
parent 6c1a128802
commit 9614682e30
3 changed files with 63 additions and 18 deletions

12
FAQ.md
View File

@ -7,3 +7,15 @@ We send your transactions to multiple private relays to get them mined without e
We have plans to return after the first successful response, but that won't get your transaction confirmed any faster. We have plans to return after the first successful response, but that won't get your transaction confirmed any faster.
Soon, you can opt out of this behavior and we will broadcast your transactions publicly. Soon, you can opt out of this behavior and we will broadcast your transactions publicly.
## !) How do I sign a login message with cURL?
```
curl -d '{
"address": "0x22fbd6248cb2837900c3fe69f725bc02dd3a3b33",
"msg": "0x73746167696e672e6c6c616d616e6f6465732e636f6d2077616e747320796f7520746f207369676e20696e207769746820796f757220457468657265756d206163636f756e743a0a3078323246624436323438634232383337393030633366653639663732356263303244643341334233330a0af09fa699f09fa699f09fa699f09fa699f09fa6990a0a5552493a2068747470733a2f2f73746167696e672e6c6c616d616e6f6465732e636f6d2f0a56657273696f6e3a20310a436861696e2049443a20310a4e6f6e63653a203031474654345052584342444157355844544643575957354a360a4973737565642041743a20323032322d31302d32305430373a32383a34342e3937323233343730385a0a45787069726174696f6e2054696d653a20323032322d31302d32305430373a34383a34342e3937323233343730385a",
"sig": "08478ba4646423d67b36b26d60d31b8a54c7b133a5260045b484df687c1fe8f4196dc69792019852c282fb2a1b030be130ef5b78864fff216cdd0c71929351761b",
"version": "3",
"signer": "MEW"
}' -H "Content-Type: application/json" --verbose "http://127.0.0.1:8544/user/login?invite_code=XYZ"
```

View File

@ -359,7 +359,7 @@ impl Web3ProxyApp {
.await .await
.context("spawning private_rpcs")?; .context("spawning private_rpcs")?;
if private_rpcs.conns.len() == 0 { if private_rpcs.conns.is_empty() {
None None
} else { } else {
// save the handle to catch any errors // save the handle to catch any errors

View File

@ -22,8 +22,10 @@ use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Transacti
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use siwe::{Message, VerificationOpts}; use siwe::{Message, VerificationOpts};
use std::ops::Add; use std::ops::Add;
use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use time::{Duration, OffsetDateTime}; use time::{Duration, OffsetDateTime};
use tracing::{info, warn};
use ulid::Ulid; use ulid::Ulid;
/// `GET /user/login/:user_address` or `GET /user/login/:user_address/:message_eip` -- Start the "Sign In with Ethereum" (siwe) login flow. /// `GET /user/login/:user_address` or `GET /user/login/:user_address/:message_eip` -- Start the "Sign In with Ethereum" (siwe) login flow.
@ -134,12 +136,8 @@ pub struct PostLoginQuery {
/// Email/password and other login methods are planned. /// Email/password and other login methods are planned.
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct PostLogin { pub struct PostLogin {
pub address: Address, sig: String,
pub msg: String, msg: String,
pub sig: Bytes,
// TODO: do we care about these? we should probably check the version is something we expect
// version: String,
// signer: String,
} }
/// Successful logins receive a bearer_token and all of the user's api keys. /// Successful logins receive a bearer_token and all of the user's api keys.
@ -169,18 +167,38 @@ pub async fn user_login_post(
// 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.
if query.invite_code.as_ref() != Some(invite_code) { if query.invite_code.as_ref() != Some(invite_code) {
todo!("if address is already registered, allow login! else, error") 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
let their_msg: siwe::Message = payload.msg.parse().context("parsing user's message")?; // 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_sig: [u8; 65] = payload // TODO: do this safely
.sig let their_sig_bytes = Bytes::from_str(&payload.sig).context("parsing sig")?;
.as_ref() if their_sig_bytes.len() != 65 {
.try_into() return Err(anyhow::anyhow!("checking signature length"))?;
.context("parsing signature")?; }
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") {
let their_msg_bytes = Bytes::from_str(&payload.msg).context("parsing payload message")?;
String::from_utf8_lossy(their_msg_bytes.as_ref()).to_string()
} else {
payload.msg
};
let their_msg = their_msg
.parse::<siwe::Message>()
.context("parsing string message")?;
// TODO: this is fragile // TODO: this is fragile
let login_nonce_key = format!("login_nonce:{}", &their_msg.nonce); let login_nonce_key = format!("login_nonce:{}", &their_msg.nonce);
@ -193,17 +211,32 @@ pub async fn user_login_post(
let our_msg: siwe::Message = our_msg.parse().context("parsing siwe message")?; let our_msg: siwe::Message = our_msg.parse().context("parsing siwe message")?;
// TODO: info for now
info!(?our_msg, ?their_msg);
// TODO: check the domain and a nonce?
// let timestamp be automatic
let verify_config = VerificationOpts { let verify_config = VerificationOpts {
// domain: Some(our_msg.domain.clone()), // domain: Some(our_msg.domain.clone()),
// nonce: Some(our_msg.nonce.clone()), // nonce: Some(our_msg.nonce.clone()),
..Default::default() ..Default::default()
}; };
// check the domain and a nonce. let timestamp be automatic // let their_verification = their_msg
our_msg // .verify(&their_sig, &verify_config)
// .await
// .context("verifying signature in their message");
let our_verification = our_msg
.verify(&their_sig, &verify_config) .verify(&their_sig, &verify_config)
.await .await
.context("verifying signature")?; .context("verifying signature in our message");
info!(?our_verification);
// TODO: proper error code. 5
// their_verification?;
our_verification?;
let bearer_token = Ulid::new(); let bearer_token = Ulid::new();
@ -223,7 +256,7 @@ pub async fn user_login_post(
// 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 u = user::ActiveModel { let u = user::ActiveModel {
address: sea_orm::Set(payload.address.to_fixed_bytes().into()), address: sea_orm::Set(our_msg.address.into()),
..Default::default() ..Default::default()
}; };