Skip to content

Commit

Permalink
Authentication must return authenticated address (#79)
Browse files Browse the repository at this point in the history
* cleanup test env requirements

* remove lcov

* ignore lcov

* start working towards returning address authenticated

* switch to proto branch

* unify proto codegen

* fmt

* update submodule

* fix examples

* add verified address to response

* rename session

* pin proto

* run big jobs on faster runners

* try labels
  • Loading branch information
0xAlcibiades authored Jun 8, 2023
1 parent d43cb95 commit aa6ac6b
Show file tree
Hide file tree
Showing 14 changed files with 78 additions and 68 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ jobs:

tests:
name: Run tests with coverage
runs-on: ubuntu-latest
runs-on:
labels: ubuntu-latest-m
services:
postgres:
image: postgres:13
Expand Down Expand Up @@ -114,7 +115,8 @@ jobs:
name: Build Image and deploy to ECR
needs: tests
if: github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
runs-on:
labels: ubuntu-latest-m
steps:
- name: Check out code
uses: actions/checkout@v2
Expand Down
10 changes: 1 addition & 9 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.build_server(true)
//.out_dir("src/google") // you can change the generated code's location
.compile(
&["proto/quay/rfq.proto"],
&["proto/quay"], // specify the root location to search proto dependencies
)
.unwrap();

tonic_build::configure()
.build_server(true)
.compile(
&["proto/quay/session.proto"],
&["proto/quay/rfq.proto", "proto/quay/auth.proto"],
&["proto/quay"], // specify the root location to search proto dependencies
)
.unwrap();
Expand Down
14 changes: 7 additions & 7 deletions examples/maker/maker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ use ethers::prelude::{
};
use ethers::utils::keccak256;
use http::Uri;
use quay::rfq;
use quay::rfq::{
use quay::grpc_codegen;
use quay::grpc_codegen::{auth_client::AuthClient, Empty, VerifyText};
use quay::grpc_codegen::{
rfq_client::RfqClient, Action, ConsiderationItem, EthSignature, ItemType, OfferItem, Order,
OrderType, QuoteRequest, QuoteResponse, SignedOrder, H256,
};
use quay::session::{session_client::SessionClient, Empty, VerifyText};
use quay::utils::session_interceptor::SessionInterceptor;
use siwe::{TimeStamp, Version};
use std::ops::Mul;
Expand Down Expand Up @@ -287,7 +287,7 @@ async fn handle_server_request<P: JsonRpcClient + 'static>(
let signed_order = sign_order(signer, parameters, seaport).await;
QuoteResponse {
ulid: request_for_quote.ulid,
maker_address: Some(rfq::H160::from(signer.address())),
maker_address: Some(grpc_codegen::H160::from(signer.address())),
order: Some(signed_order),
}
}
Expand Down Expand Up @@ -507,7 +507,7 @@ fn create_no_offer<P: JsonRpcClient + 'static>(
) -> QuoteResponse {
QuoteResponse {
ulid: request_for_quote.ulid.clone(),
maker_address: Some(rfq::H160::from(signer.address())),
maker_address: Some(grpc_codegen::H160::from(signer.address())),
order: None,
}
}
Expand All @@ -520,7 +520,7 @@ async fn setup<P: JsonRpcClient + 'static>(
provider: &Arc<Provider<P>>,
) -> String {
// Connect and authenticate with Quay
let mut client: SessionClient<Channel> = SessionClient::new(
let mut client: AuthClient<Channel> = AuthClient::new(
Channel::builder(quay_uri.clone())
.tls_config(tls_config.clone())
.unwrap()
Expand All @@ -545,7 +545,7 @@ async fn setup<P: JsonRpcClient + 'static>(
let nonce = response.into_inner().nonce;

// Verify & authenticate with Quay before connecting to RFQ endpoint.
let mut client = SessionClient::with_interceptor(
let mut client = AuthClient::with_interceptor(
Channel::builder(quay_uri)
.tls_config(tls_config)
.unwrap()
Expand Down
16 changes: 8 additions & 8 deletions examples/taker/taker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ use ethers::prelude::{
Signature, Signer, SignerMiddleware, Ws, U256,
};
use http::Uri;
use quay::rfq::rfq_client::RfqClient;
use quay::rfq::{Action, ItemType, QuoteRequest, H256};
use quay::session::session_client::SessionClient;
use quay::session::{Empty, VerifyText};
use quay::grpc_codegen::auth_client::AuthClient;
use quay::grpc_codegen::rfq_client::RfqClient;
use quay::grpc_codegen::{Action, ItemType, QuoteRequest, H256};
use quay::grpc_codegen::{Empty, VerifyText};
use quay::utils::session_interceptor::SessionInterceptor;
use siwe::{TimeStamp, Version};
use std::env;
Expand Down Expand Up @@ -244,8 +244,8 @@ async fn run<P: JsonRpcClient + 'static>(provider: Arc<Provider<P>>, settings: S
// pre-signed by the Maker, so if we change anything the signature will not
// match.
fn transform_to_seaport_order(
signed_order: &quay::rfq::SignedOrder,
offer_parameters: quay::rfq::Order,
signed_order: &quay::grpc_codegen::SignedOrder,
offer_parameters: quay::grpc_codegen::Order,
) -> Order {
let signature_bytes: Signature = signed_order.signature.clone().unwrap().into();
let signature = Bytes::from(signature_bytes.to_vec());
Expand Down Expand Up @@ -329,7 +329,7 @@ async fn setup_quay_connection<P: JsonRpcClient + 'static>(
provider: &Arc<Provider<P>>,
) -> String {
// Connect and authenticate with Quay
let mut client: SessionClient<Channel> = SessionClient::new(
let mut client: AuthClient<Channel> = AuthClient::new(
Channel::builder(quay_uri.clone())
.tls_config(tls_config.clone())
.unwrap()
Expand All @@ -354,7 +354,7 @@ async fn setup_quay_connection<P: JsonRpcClient + 'static>(
let nonce = response.into_inner().nonce;

// Verify & authenticate with Quay before connecting to RFQ endpoint.
let mut client = SessionClient::with_interceptor(
let mut client = AuthClient::with_interceptor(
Channel::builder(quay_uri)
.tls_config(tls_config)
.unwrap()
Expand Down
2 changes: 1 addition & 1 deletion proto
Submodule proto updated from 1b294f to d990b2
File renamed without changes.
2 changes: 1 addition & 1 deletion src/types/mod.rs → src/grpc_adapters.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Setup From traits allowing the conversion between proto types and ethers types.
// Reference: https://github.com/ledgerwatch/interfaces/blob/master/src/lib.rs
use crate::rfq::*;
use crate::grpc_codegen::*;
use arrayref::array_ref;

// Macro allowing for proto types to be converted into numbers (and vice versa), moving
Expand Down
11 changes: 3 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
pub mod auth;
pub mod auth_helpers;
pub mod bindings;
pub mod configuration;
pub mod database;
pub mod grpc_adapters;
pub mod indexer;
pub mod middleware;
pub mod redis_pool;
Expand All @@ -11,15 +12,9 @@ pub mod startup;
pub mod state;
pub mod structs;
pub mod telemetry;
pub mod types;
pub mod utils;

pub mod rfq {
pub mod grpc_codegen {
#![allow(clippy::derive_partial_eq_without_eq)]
tonic::include_proto!("quay");
}

pub mod session {
#![allow(clippy::derive_partial_eq_without_eq)]
tonic::include_proto!("session");
}
45 changes: 30 additions & 15 deletions src/services/session.rs → src/services/auth.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::auth::{
use crate::auth_helpers::{
unix_timestamp, SignedMessage, EXPIRATION_TIME_KEY, NONCE_KEY, USER_ADDRESS_KEY,
};
use crate::session::{session_server::Session, Empty, NonceText, VerifyText};
use crate::grpc_codegen::{auth_server::Auth, Empty, NonceText, VerifyText, H160};

use axum_sessions::SessionHandle;
use ethers::prelude::Address;
Expand All @@ -15,7 +15,7 @@ const SEVEN_DAYS_IN_SECONDS: u64 = 604800u64;
// Private authentication function for used by the service endpoint and the session interceptor.
fn authenticate(
session: &RwLockReadGuard<axum_sessions::async_session::Session>,
) -> Result<(), Status> {
) -> Result<Response<H160>, Status> {
// Confirm the nonce is valid.
match session.get::<String>(NONCE_KEY) {
Some(_) => (),
Expand Down Expand Up @@ -55,16 +55,26 @@ fn authenticate(
}
}

let session_address: Address = match session.get(USER_ADDRESS_KEY) {
Some(address) => address,
None => {
tracing::warn!("Server Error 5. Unable to get address for session.");
return Err(Status::internal(
"Server Error 5. Unable to get address for session. Please report this error to the support team."
));
}
};

// Authenticated request
Ok(())
Ok(Response::new(session_address.into()))
}

/// The SessionAuthenticator is a gRPC interceptor for the server to check and validate session
/// authentication details in the `request`.
#[derive(Clone)]
pub struct SessionAuthenticator;
pub struct Authenticator;

impl Interceptor for SessionAuthenticator {
impl Interceptor for Authenticator {
fn call(&mut self, request: Request<()>) -> Result<Request<()>, Status> {
// Use an internal scope to ensure the borrow of the request is dropped (i.e.
// RwLockReadGuard is dropped at the end of the scope), in order to move it for the return
Expand All @@ -79,10 +89,10 @@ impl Interceptor for SessionAuthenticator {
}

#[derive(Debug, Default)]
pub struct SessionService;
pub struct AuthService;

#[tonic::async_trait]
impl Session for SessionService {
impl Auth for AuthService {
#[instrument(name = "Getting an EIP-4361 nonce for session", skip(request))]
async fn nonce(&self, request: Request<Empty>) -> Result<Response<NonceText>, Status> {
// Fetch a writeable session.
Expand All @@ -97,7 +107,8 @@ impl Session for SessionService {
return Err(Status::internal("Error while setting up user session. Please report server error 2 to the support team."));
}

// Make sure we don't inherit a dirty session expiry
// Make sure we don't inherit a dirty session expiry on the nonce
// By setting the session as expired until the user signature is verified
// We don't expect this operation to fail, if it does the system is highly likely in a bad state
let ts = match unix_timestamp() {
Ok(ts) => ts,
Expand All @@ -122,15 +133,16 @@ impl Session for SessionService {
}

#[instrument(name = "Verifying user EIP-4361 session", skip(request))]
async fn verify(&self, request: Request<VerifyText>) -> Result<Response<Empty>, Status> {
async fn verify(&self, request: Request<VerifyText>) -> Result<Response<H160>, Status> {
// Decode the JSON message body into the expected SignedMessage structure
let signed_message: SignedMessage = match serde_json::from_str(
request.get_ref().body.as_str(),
) {
Ok(msg) => msg,
// If we get an invalid message, tell the caller
Err(error) => {
tracing::warn!("Failed to decode verify message into a SignedMessage. Reported error {error:?}");
return Err(Status::failed_precondition(
return Err(Status::invalid_argument(
format!("Error while decoding the verify message. {error:?}").as_str(),
));
}
Expand Down Expand Up @@ -184,6 +196,7 @@ impl Session for SessionService {
}
};

// Set the verified session to expire in seven days
let expiry = now + SEVEN_DAYS_IN_SECONDS;
// We don't expect this operation to fail, therefore report an internal server error if it does
if let Err(error) = session.insert(EXPIRATION_TIME_KEY, expiry) {
Expand All @@ -193,21 +206,23 @@ impl Session for SessionService {
return Err(Status::internal("Error while verifying user session. Please report server error 3 to the support team."));
}

let address = Address::from(message.address);

// We don't expect this operation to fail, therefore report an internal server error if it does
if let Err(error) = session.insert(USER_ADDRESS_KEY, Address::from(message.address)) {
if let Err(error) = session.insert(USER_ADDRESS_KEY, address) {
tracing::error!(
"Server Error 4. Failed to set the users address for a user session that is being verified. Reported error {error:?}"
);
return Err(Status::internal("Error while verifying user session. Please report server error 4 to the support team."));
}

Ok(Response::new(Empty {}))
Ok(Response::new(address.into()))
}

#[instrument(name = "Checking user EIP-4361 authentication", skip(request))]
async fn authenticate(&self, request: Request<Empty>) -> Result<Response<Empty>, Status> {
async fn authenticate(&self, request: Request<Empty>) -> Result<Response<H160>, Status> {
let session_handle = request.extensions().get::<SessionHandle>().unwrap();
let session = session_handle.read().await;
authenticate(&session).map(|_| Response::new(Empty::default()))
authenticate(&session)
}
}
4 changes: 2 additions & 2 deletions src/services/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mod auth;
mod rfq;
mod session;

pub use auth::*;
pub use rfq::*;
pub use session::*;
4 changes: 2 additions & 2 deletions src/services/rfq.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::rfq::rfq_server::Rfq;
use crate::rfq::{QuoteRequest, QuoteResponse, H128};
use crate::grpc_codegen::rfq_server::Rfq;
use crate::grpc_codegen::{QuoteRequest, QuoteResponse, H128};
use ethers::prelude::U128;
use std::collections::HashMap;
use std::pin::Pin;
Expand Down
14 changes: 6 additions & 8 deletions src/startup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ use tower_http::cors::CorsLayer;
use tower_http::trace::TraceLayer;

use crate::bindings::{seaport_one_one::Seaport, seaport_validator::SeaportValidator};
use crate::grpc_codegen::auth_server::AuthServer;
use crate::grpc_codegen::rfq_server::RfqServer;
use crate::middleware::{track_prometheus_metrics, RequestIdLayer};
use crate::redis_pool::RedisConnectionManager;
use crate::rfq::rfq_server::RfqServer;
use crate::routes::*;
use crate::services::*;
use crate::session::session_server::SessionServer;
use crate::state::AppState;
use crate::{
configuration::{DatabaseSettings, Settings},
Expand Down Expand Up @@ -100,9 +100,9 @@ pub fn run(
.layer(session_layer.clone())
.add_service(RfqServer::with_interceptor(
rfq_service.clone(),
SessionAuthenticator,
Authenticator,
))
.add_service(SessionServer::new(SessionService::default()))
.add_service(AuthServer::new(AuthService::default()))
.into_service()
.map_response(|r| r.map(axum::body::boxed))
.boxed_clone();
Expand All @@ -115,11 +115,9 @@ pub fn run(
.layer(cors)
.add_service(tonic_web::enable(RfqServer::with_interceptor(
rfq_service,
SessionAuthenticator,
)))
.add_service(tonic_web::enable(SessionServer::new(
SessionService::default(),
Authenticator,
)))
.add_service(tonic_web::enable(AuthServer::new(AuthService::default())))
.into_service()
.map_response(|r| r.map(axum::body::boxed))
.boxed_clone();
Expand Down
16 changes: 12 additions & 4 deletions tests/api/session.rs → tests/api/auth.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::helpers::spawn_app;
use ethers::signers::{LocalWallet, Signer};
use http::Uri;
use quay::session::session_client::SessionClient;
use quay::session::{Empty, VerifyText};
use quay::grpc_codegen::auth_client::AuthClient;
use quay::grpc_codegen::{Empty, VerifyText};
use quay::utils::session_interceptor::SessionInterceptor;
use siwe::{TimeStamp, Version};
use std::str::FromStr;
Expand All @@ -25,7 +25,7 @@ async fn verify_session_works() {
.ca_certificate(ca)
.domain_name("localhost.com");

let mut client = SessionClient::new(
let mut client = AuthClient::new(
Channel::builder(app.address.parse::<Uri>().unwrap())
.tls_config(tls_config.clone())
.unwrap()
Expand Down Expand Up @@ -53,7 +53,7 @@ async fn verify_session_works() {
let nonce = nonce_response.into_inner().nonce;

// Setup the session client with our newly created session.
let mut client = SessionClient::with_interceptor(
let mut client = AuthClient::with_interceptor(
Channel::builder(app.address.parse::<Uri>().unwrap())
.tls_config(tls_config.clone())
.unwrap()
Expand Down Expand Up @@ -107,6 +107,14 @@ async fn verify_session_works() {

let json_body = serde_json::Value::from(signed_message);

let bad_response = client
.verify(VerifyText {
body: "".to_string(),
})
.await;

assert!(bad_response.is_err());

// Act
let response = client
.verify(VerifyText {
Expand Down
Loading

0 comments on commit aa6ac6b

Please sign in to comment.