support EIP1271 login

This commit is contained in:
Bryan Stitt 2023-06-24 09:48:28 -07:00
parent 91cbce6ce8
commit 8f76d9320d
6 changed files with 76 additions and 273 deletions

192
Cargo.lock generated

@ -570,12 +570,6 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "base16ct"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce"
[[package]]
name = "base16ct"
version = "0.2.0"
@ -1027,7 +1021,7 @@ dependencies = [
"digest 0.10.7",
"getrandom",
"hmac",
"k256 0.13.1",
"k256",
"lazy_static",
"serde",
"sha2 0.10.6",
@ -1327,18 +1321,6 @@ dependencies = [
"subtle",
]
[[package]]
name = "crypto-bigint"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef"
dependencies = [
"generic-array",
"rand_core",
"subtle",
"zeroize",
]
[[package]]
name = "crypto-bigint"
version = "0.5.2"
@ -1465,16 +1447,6 @@ dependencies = [
"pem-rfc7468",
]
[[package]]
name = "der"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de"
dependencies = [
"const-oid 0.9.2",
"zeroize",
]
[[package]]
name = "der"
version = "0.7.6"
@ -1590,18 +1562,6 @@ version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30"
[[package]]
name = "ecdsa"
version = "0.14.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c"
dependencies = [
"der 0.6.1",
"elliptic-curve 0.12.3",
"rfc6979 0.3.1",
"signature 1.6.4",
]
[[package]]
name = "ecdsa"
version = "0.16.7"
@ -1610,9 +1570,9 @@ checksum = "0997c976637b606099b9985693efa3581e84e41f5c11ba5255f88711058ad428"
dependencies = [
"der 0.7.6",
"digest 0.10.7",
"elliptic-curve 0.13.5",
"rfc6979 0.4.0",
"signature 2.1.0",
"elliptic-curve",
"rfc6979",
"signature",
"spki 0.7.2",
]
@ -1622,40 +1582,21 @@ version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]]
name = "elliptic-curve"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3"
dependencies = [
"base16ct 0.1.1",
"crypto-bigint 0.4.9",
"der 0.6.1",
"digest 0.10.7",
"ff 0.12.1",
"generic-array",
"group 0.12.1",
"rand_core",
"sec1 0.3.0",
"subtle",
"zeroize",
]
[[package]]
name = "elliptic-curve"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b"
dependencies = [
"base16ct 0.2.0",
"base16ct",
"crypto-bigint 0.5.2",
"digest 0.10.7",
"ff 0.13.0",
"ff",
"generic-array",
"group 0.13.0",
"group",
"pkcs8 0.10.2",
"rand_core",
"sec1 0.7.2",
"sec1",
"subtle",
"zeroize",
]
@ -1693,7 +1634,7 @@ dependencies = [
"base64 0.13.1",
"bytes",
"hex",
"k256 0.13.1",
"k256",
"log",
"rand",
"rlp",
@ -1928,11 +1869,11 @@ dependencies = [
"bytes",
"cargo_metadata 0.15.4",
"chrono",
"elliptic-curve 0.13.5",
"elliptic-curve",
"ethabi",
"generic-array",
"hex",
"k256 0.13.1",
"k256",
"num_enum 0.6.1",
"once_cell",
"open-fastrlp",
@ -2036,7 +1977,7 @@ dependencies = [
"async-trait",
"coins-bip32",
"coins-bip39",
"elliptic-curve 0.13.5",
"elliptic-curve",
"eth-keystore",
"ethers-core",
"hex",
@ -2145,16 +2086,6 @@ dependencies = [
"libc",
]
[[package]]
name = "ff"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160"
dependencies = [
"rand_core",
"subtle",
]
[[package]]
name = "ff"
version = "0.13.0"
@ -2482,24 +2413,13 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "558b88954871f5e5b2af0e62e2e176c8bde7a6c2c4ed41b13d138d96da2e2cbd"
[[package]]
name = "group"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7"
dependencies = [
"ff 0.12.1",
"rand_core",
"subtle",
]
[[package]]
name = "group"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
dependencies = [
"ff 0.13.0",
"ff",
"rand_core",
"subtle",
]
@ -3049,9 +2969,9 @@ dependencies = [
[[package]]
name = "iri-string"
version = "0.6.0"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d0586ad318a04c73acdbad33f67969519b5452c80770c4c72059a686da48a7e"
checksum = "21859b667d66a4c1dacd9df0863b3efb65785474255face87f5bca39dd8407c0"
dependencies = [
"memchr",
"serde",
@ -3111,19 +3031,6 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "k256"
version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b"
dependencies = [
"cfg-if",
"ecdsa 0.14.8",
"elliptic-curve 0.12.3",
"sha2 0.10.6",
"sha3",
]
[[package]]
name = "k256"
version = "0.13.1"
@ -3131,11 +3038,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc"
dependencies = [
"cfg-if",
"ecdsa 0.16.7",
"elliptic-curve 0.13.5",
"ecdsa",
"elliptic-curve",
"once_cell",
"sha2 0.10.6",
"signature 2.1.0",
"signature",
]
[[package]]
@ -4169,16 +4076,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "pkcs8"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba"
dependencies = [
"der 0.6.1",
"spki 0.6.0",
]
[[package]]
name = "pkcs8"
version = "0.10.2"
@ -4720,17 +4617,6 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0"
[[package]]
name = "rfc6979"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb"
dependencies = [
"crypto-bigint 0.4.9",
"hmac",
"zeroize",
]
[[package]]
name = "rfc6979"
version = "0.4.0"
@ -5261,27 +5147,13 @@ version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "sec1"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928"
dependencies = [
"base16ct 0.1.1",
"der 0.6.1",
"generic-array",
"pkcs8 0.9.0",
"subtle",
"zeroize",
]
[[package]]
name = "sec1"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0aec48e813d6b90b15f0b8948af3c63483992dee44c03e9930b3eebdabe046e"
dependencies = [
"base16ct 0.2.0",
"base16ct",
"der 0.7.6",
"generic-array",
"pkcs8 0.10.2",
@ -5642,16 +5514,6 @@ dependencies = [
"libc",
]
[[package]]
name = "signature"
version = "1.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c"
dependencies = [
"digest 0.10.7",
"rand_core",
]
[[package]]
name = "signature"
version = "2.1.0"
@ -5677,13 +5539,13 @@ checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
[[package]]
name = "siwe"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e6d1f422a568af1e98db37c6d0427c7218459ccac39218fd15a51a34d3933af"
source = "git+https://github.com/llamanodes/siwe-rs?rev=013be5204ff1c85778ce21619f4b677a003db8a1#013be5204ff1c85778ce21619f4b677a003db8a1"
dependencies = [
"ethers",
"hex",
"http",
"iri-string",
"k256 0.11.6",
"k256",
"rand",
"serde",
"sha3",
@ -5802,16 +5664,6 @@ dependencies = [
"der 0.5.1",
]
[[package]]
name = "spki"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b"
dependencies = [
"base64ct",
"der 0.6.1",
]
[[package]]
name = "spki"
version = "0.7.2"

@ -25,7 +25,7 @@ redis-rate-limiter = { path = "../redis-rate-limiter" }
#ethers = { git = "https://github.com/llamanodes/ethers-rs/", rev = "eb68f5d60850008cd302762bd3a5a4bdcfecc713", default-features = false, features = ["rustls", "ws"] }
influxdb2 = { git = "https://github.com/llamanodes/influxdb2", features = ["rustls"], rev = "2d125128696a29d7e0b9abc052c928937e7c0579" }
influxdb2-structmap = { git = "https://github.com/llamanodes/influxdb2/", rev = "2d125128696a29d7e0b9abc052c928937e7c0579"}
#siwe = { git = "https://github.com/llamanodes/siwe-rs", rev = "bef5449b5dd8beb4e9fc697f09cd6dd11ba8f6e6", features = ["ethers", "serde"] }
siwe = { git = "https://github.com/llamanodes/siwe-rs", rev = "013be5204ff1c85778ce21619f4b677a003db8a1", features = ["ethers", "serde"] }
# TODO: regex has several "perf" features that we might want to use
# TODO: make sure this uuid version matches sea-orm. PR to put this in their prelude
@ -89,7 +89,6 @@ sentry-tracing = "0.31.5"
serde = { version = "1.0.164" }
serde_json = { version = "1.0.99", default-features = false, features = ["raw_value"] }
serde_prometheus = "0.2.3"
siwe = { version = "0.5.2", features = ["serde"] }
strum = { version = "0.25.0", features = ["derive"] }
time = { version = "0.3.22", features = ["serde-well-known"] }
tokio = { version = "1.28.2", features = ["full", "tracing"] }

@ -21,6 +21,7 @@ use reqwest::header::ToStrError;
use rust_decimal::Error as DecimalError;
use serde::Serialize;
use serde_json::value::RawValue;
use siwe::VerificationError;
use std::sync::Arc;
use std::{borrow::Cow, net::IpAddr};
use tokio::{sync::AcquireError, task::JoinError, time::Instant};
@ -54,8 +55,6 @@ pub enum Web3ProxyError {
Contract(ContractError<EthersHttpProvider>),
Database(DbErr),
Decimal(DecimalError),
#[display(fmt = "{:#?}, {:#?}", _0, _1)]
EipVerificationFailed(Box<Web3ProxyError>, Box<Web3ProxyError>),
EthersHttpClient(ethers::prelude::HttpClientError),
EthersProvider(ethers::prelude::ProviderError),
EthersWsClient(ethers::prelude::WsClientError),
@ -133,6 +132,7 @@ pub enum Web3ProxyError {
SemaphoreAcquireError(AcquireError),
SendAppStatError(flume::SendError<crate::stats::AppStat>),
SerdeJson(serde_json::Error),
SiweVerification(VerificationError),
/// simple way to return an error message to the user and an anyhow to our logs
#[display(fmt = "{}, {}, {:?}", _0, _1, _2)]
StatusCode(StatusCode, Cow<'static, str>, Option<anyhow::Error>),
@ -148,7 +148,6 @@ pub enum Web3ProxyError {
UserAgentNotAllowed(headers::UserAgent),
UserIdZero,
PaymentRequired,
VerificationError(siwe::VerificationError),
WatchRecvError(tokio::sync::watch::error::RecvError),
WatchSendError,
WebsocketOnly,
@ -267,20 +266,12 @@ impl Web3ProxyError {
},
)
}
Self::EipVerificationFailed(err_1, err_191) => {
trace!(
"EipVerificationFailed err_1={:#?} err2={:#?}",
err_1,
err_191
);
Self::SiweVerification(err) => {
trace!("Siwe Verification err={:#?}", err,);
(
StatusCode::UNAUTHORIZED,
JsonRpcErrorData {
message: format!(
"both the primary and eip191 verification failed: {:#?}; {:#?}",
err_1, err_191
)
.into(),
message: format!("siwe verification error: {:#?}", err).into(),
code: StatusCode::UNAUTHORIZED.as_u16().into(),
data: None,
},
@ -960,17 +951,6 @@ impl Web3ProxyError {
},
)
}
Self::VerificationError(err) => {
trace!("VerificationError err={:#?}", err);
(
StatusCode::BAD_REQUEST,
JsonRpcErrorData {
message: "verification error!".into(),
code: StatusCode::BAD_REQUEST.as_u16().into(),
data: None,
},
)
}
Self::WatchRecvError(err) => {
error!("WatchRecvError err={:#?}", err);
(

@ -252,6 +252,16 @@ pub async fn admin_login_get(
.db_replica()
.web3_context("login requires a replica database")?;
// delete ALL expired rows.
let now = Utc::now();
let delete_result = pending_login::Entity::delete_many()
.filter(pending_login::Column::ExpiresAt.lte(now))
.exec(&db_conn)
.await?;
// TODO: emit a stat? if this is high something weird might be happening
debug!("cleared expired pending_logins: {:?}", delete_result);
// Get the user that we want to imitate from the read-only database (their id ...)
// TODO: Only get the id, not the whole user object ...
let user = user::Entity::find()
@ -404,39 +414,16 @@ pub async fn admin_login_post(
.parse()
.web3_context("parsing siwe message")?;
// default options are fine. the message includes timestamp and domain and nonce
let verify_config = VerificationOpts::default();
// mostly default options are fine. the message includes timestamp and domain and nonce
let verify_config = VerificationOpts {
rpc_provider: Some(app.internal_provider.clone()),
..Default::default()
};
let db_conn = app
.db_conn()
.web3_context("deleting expired pending logins requires a db")?;
if let Err(err_1) = our_msg
our_msg
.verify(&their_sig, &verify_config)
.await
.web3_context("verifying signature against our local message")
{
// verification method 1 failed. try eip191
if let Err(err_191) = our_msg
.verify_eip191(&their_sig)
.web3_context("verifying eip191 signature against our local message")
{
// delete ALL expired rows.
let now = Utc::now();
let delete_result = pending_login::Entity::delete_many()
.filter(pending_login::Column::ExpiresAt.lte(now))
.exec(&db_conn)
.await?;
// TODO: emit a stat? if this is high something weird might be happening
debug!("cleared expired pending_logins: {:?}", delete_result);
return Err(Web3ProxyError::EipVerificationFailed(
Box::new(err_1),
Box::new(err_191),
));
}
}
.web3_context("verifying signature against our local message")?;
let imitating_user_id = user_pending_login
.imitating_user
@ -456,6 +443,10 @@ pub async fn admin_login_post(
.await?
.web3_context("admin address was not found!")?;
let db_conn = app
.db_conn()
.web3_context("deleting expired pending logins requires a db")?;
// Add a message that the admin has logged in
// Note that the admin is trying to log in as this user
let trail = admin_trail::ActiveModel {
@ -500,9 +491,7 @@ pub async fn admin_login_post(
// add bearer to the database
// expire in 2 days, because this is more critical (and shouldn't need to be done so long!)
let expires_at = Utc::now()
.checked_add_signed(chrono::Duration::days(2))
.unwrap();
let expires_at = Utc::now() + chrono::Duration::days(2);
// TODO: Here, the bearer token should include a message
// TODO: Above, make sure that the calling address is an admin!

@ -29,7 +29,7 @@ use std::ops::Add;
use std::str::FromStr;
use std::sync::Arc;
use time::{Duration, OffsetDateTime};
use tracing::{trace, warn};
use tracing::{error, trace, warn};
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.
@ -38,8 +38,7 @@ use ulid::Ulid;
/// - eip191_bytes
/// - eip191_hash
/// - eip4361 (default)
///
/// Coming soon: eip1271
/// - 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`.
///
@ -103,6 +102,13 @@ pub async fn user_login_get(
let db_conn = app.db_conn().web3_context("login requires a database")?;
// delete ALL expired rows.
let now = Utc::now();
let _ = pending_login::Entity::delete_many()
.filter(pending_login::Column::ExpiresAt.lte(now))
.exec(&db_conn)
.await?;
// massage types to fit in the database. sea-orm does not make this very elegant
let uuid = Uuid::from_u128(nonce.into());
// we add 1 to expire_seconds just to be sure the database has the key for the full expiration_time
@ -234,11 +240,8 @@ pub async fn user_login_post(
.db_replica()
.web3_context("Getting database connection")?;
// massage type for the db
let login_nonce_uuid: Uuid = login_nonce.clone().into();
let user_pending_login = pending_login::Entity::find()
.filter(pending_login::Column::Nonce.eq(login_nonce_uuid))
.filter(pending_login::Column::Nonce.eq(Uuid::from(login_nonce.clone())))
.one(db_replica.as_ref())
.await
.web3_context("database error while finding pending_login")?
@ -249,40 +252,17 @@ pub async fn user_login_post(
.parse()
.web3_context("parsing siwe message")?;
// default options are fine. the message includes timestamp and domain and nonce
let verify_config = VerificationOpts::default();
// mostly default options are fine. the message includes timestamp and domain and nonce
let verify_config = VerificationOpts {
rpc_provider: Some(app.internal_provider.clone()),
..Default::default()
};
// Check with both verify and verify_eip191
if let Err(err_1) = our_msg
our_msg
.verify(&their_sig, &verify_config)
.await
.web3_context("verifying signature against our local message")
{
// verification method 1 failed. try eip191
if let Err(err_191) = our_msg
.verify_eip191(&their_sig)
.web3_context("verifying eip191 signature against our local message")
{
let db_conn = app
.db_conn()
.web3_context("deleting expired pending logins requires a db")?;
// delete ALL expired rows.
let now = Utc::now();
let delete_result = pending_login::Entity::delete_many()
.filter(pending_login::Column::ExpiresAt.lte(now))
.exec(&db_conn)
.await?;
// TODO: emit a stat? if this is high something weird might be happening
trace!("cleared expired pending_logins: {:?}", delete_result);
return Err(Web3ProxyError::EipVerificationFailed(
Box::new(err_1),
Box::new(err_191),
));
}
}
.web3_context("verifying signature against our local message")?;
// TODO: limit columns or load whole user?
let caller = user::Entity::find()
@ -356,11 +336,7 @@ pub async fn user_login_post(
.one(&txn)
.await?
.ok_or(Web3ProxyError::BadRequest(
format!(
"The referral_link you provided does not exist {}",
referral_code
)
.into(),
"The referral_link you provided does not exist".into(),
))?;
// Create a new item in the database,
@ -430,7 +406,7 @@ pub async fn user_login_post(
.delete(&db_conn)
.await
{
warn!("Failed to delete nonce:{}: {}", login_nonce.0, err);
error!("Failed to delete nonce:{}: {}", login_nonce, err);
}
Ok(response)

@ -1,6 +1,7 @@
use axum::headers::authorization::Bearer;
use migration::sea_orm::prelude::Uuid;
use serde::Serialize;
use std::fmt;
use std::str::FromStr;
use ulid::Ulid;
@ -47,6 +48,12 @@ impl From<UserBearerToken> for Uuid {
}
}
impl fmt::Display for UserBearerToken {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl TryFrom<Bearer> for UserBearerToken {
type Error = ulid::DecodeError;