more robust login
This commit is contained in:
parent
6c1a128802
commit
9614682e30
12
FAQ.md
12
FAQ.md
|
@ -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"
|
||||||
|
```
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue