feat(poc): add terrors PoC crate scaffold and error types
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,15 @@
|
||||
use arbiter_proto::proto::client::{
|
||||
AuthChallengeRequest, AuthChallengeSolution, ClientRequest,
|
||||
client_request::Payload as ClientRequestPayload,
|
||||
client_response::Payload as ClientResponsePayload,
|
||||
use alloy::{
|
||||
consensus::TxEip1559,
|
||||
primitives::{Address, Bytes, TxKind, U256},
|
||||
rlp::Encodable,
|
||||
};
|
||||
use arbiter_proto::proto::{
|
||||
client::{
|
||||
AuthChallengeRequest, AuthChallengeSolution, ClientRequest,
|
||||
client_request::Payload as ClientRequestPayload,
|
||||
client_response::Payload as ClientResponsePayload,
|
||||
},
|
||||
evm::EvmSignTransactionRequest,
|
||||
};
|
||||
use arbiter_proto::transport::Bi;
|
||||
use arbiter_server::actors::GlobalActors;
|
||||
@@ -109,3 +117,106 @@ pub async fn test_challenge_auth() {
|
||||
// Auth completes, session spawned
|
||||
task.await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[test_log::test]
|
||||
pub async fn test_evm_sign_request_payload_is_handled() {
|
||||
let db = db::create_test_pool().await;
|
||||
|
||||
let new_key = ed25519_dalek::SigningKey::generate(&mut rand::rng());
|
||||
let pubkey_bytes = new_key.verifying_key().to_bytes().to_vec();
|
||||
|
||||
{
|
||||
let mut conn = db.get().await.unwrap();
|
||||
insert_into(schema::program_client::table)
|
||||
.values(schema::program_client::public_key.eq(pubkey_bytes.clone()))
|
||||
.execute(&mut conn)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let (server_transport, mut test_transport) = ChannelTransport::new();
|
||||
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
||||
|
||||
let props = ClientConnection::new(db.clone(), Box::new(server_transport), actors);
|
||||
let task = tokio::spawn(connect_client(props));
|
||||
|
||||
test_transport
|
||||
.send(ClientRequest {
|
||||
payload: Some(ClientRequestPayload::AuthChallengeRequest(
|
||||
AuthChallengeRequest {
|
||||
pubkey: pubkey_bytes,
|
||||
},
|
||||
)),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let response = test_transport
|
||||
.recv()
|
||||
.await
|
||||
.expect("should receive challenge");
|
||||
let challenge = match response {
|
||||
Ok(resp) => match resp.payload {
|
||||
Some(ClientResponsePayload::AuthChallenge(c)) => c,
|
||||
other => panic!("Expected AuthChallenge, got {other:?}"),
|
||||
},
|
||||
Err(err) => panic!("Expected Ok response, got Err({err:?})"),
|
||||
};
|
||||
|
||||
let formatted_challenge = arbiter_proto::format_challenge(challenge.nonce, &challenge.pubkey);
|
||||
let signature = new_key.sign(&formatted_challenge);
|
||||
|
||||
test_transport
|
||||
.send(ClientRequest {
|
||||
payload: Some(ClientRequestPayload::AuthChallengeSolution(
|
||||
AuthChallengeSolution {
|
||||
signature: signature.to_bytes().to_vec(),
|
||||
},
|
||||
)),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
task.await.unwrap();
|
||||
|
||||
let tx = TxEip1559 {
|
||||
chain_id: 1,
|
||||
nonce: 0,
|
||||
gas_limit: 21_000,
|
||||
max_fee_per_gas: 1,
|
||||
max_priority_fee_per_gas: 1,
|
||||
to: TxKind::Call(Address::from_slice(&[0x11; 20])),
|
||||
value: U256::ZERO,
|
||||
input: Bytes::new(),
|
||||
access_list: Default::default(),
|
||||
};
|
||||
|
||||
let mut rlp_transaction = Vec::new();
|
||||
tx.encode(&mut rlp_transaction);
|
||||
|
||||
test_transport
|
||||
.send(ClientRequest {
|
||||
payload: Some(ClientRequestPayload::EvmSignTransaction(
|
||||
EvmSignTransactionRequest {
|
||||
wallet_address: [0x22; 20].to_vec(),
|
||||
rlp_transaction,
|
||||
},
|
||||
)),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let response = test_transport
|
||||
.recv()
|
||||
.await
|
||||
.expect("should receive sign response");
|
||||
|
||||
match response {
|
||||
Ok(resp) => match resp.payload {
|
||||
Some(ClientResponsePayload::EvmSignTransaction(_)) => {}
|
||||
other => panic!("Expected EvmSignTransaction response, got {other:?}"),
|
||||
},
|
||||
Err(err) => panic!("Expected Ok response, got Err({err:?})"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,5 +2,7 @@ mod common;
|
||||
|
||||
#[path = "user_agent/auth.rs"]
|
||||
mod auth;
|
||||
#[path = "user_agent/sdk_client.rs"]
|
||||
mod sdk_client;
|
||||
#[path = "user_agent/unseal.rs"]
|
||||
mod unseal;
|
||||
|
||||
270
server/crates/arbiter-server/tests/user_agent/sdk_client.rs
Normal file
270
server/crates/arbiter-server/tests/user_agent/sdk_client.rs
Normal file
@@ -0,0 +1,270 @@
|
||||
use arbiter_proto::proto::user_agent::{
|
||||
SdkClientApproveRequest, SdkClientError as ProtoSdkClientError, SdkClientRevokeRequest,
|
||||
UserAgentRequest, sdk_client_approve_response, sdk_client_list_response,
|
||||
sdk_client_revoke_response, user_agent_request::Payload as UserAgentRequestPayload,
|
||||
user_agent_response::Payload as UserAgentResponsePayload,
|
||||
};
|
||||
use arbiter_server::{
|
||||
actors::{GlobalActors, user_agent::session::UserAgentSession},
|
||||
db,
|
||||
};
|
||||
|
||||
/// Shared helper: create a session and register a client pubkey via sdk_client_approve.
|
||||
async fn make_session(db: &db::DatabasePool) -> UserAgentSession {
|
||||
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
||||
UserAgentSession::new_test(db.clone(), actors)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[test_log::test]
|
||||
async fn test_sdk_client_approve_registers_client() {
|
||||
let db = db::create_test_pool().await;
|
||||
let mut session = make_session(&db).await;
|
||||
|
||||
let pubkey = [0x42u8; 32];
|
||||
|
||||
let response = session
|
||||
.process_transport_inbound(UserAgentRequest {
|
||||
payload: Some(UserAgentRequestPayload::SdkClientApprove(
|
||||
SdkClientApproveRequest {
|
||||
pubkey: pubkey.to_vec(),
|
||||
},
|
||||
)),
|
||||
})
|
||||
.await
|
||||
.expect("handler should succeed");
|
||||
|
||||
let entry = match response.payload.unwrap() {
|
||||
UserAgentResponsePayload::SdkClientApprove(resp) => match resp.result.unwrap() {
|
||||
sdk_client_approve_response::Result::Client(e) => e,
|
||||
sdk_client_approve_response::Result::Error(e) => {
|
||||
panic!("Expected Client, got error {:?}", e)
|
||||
}
|
||||
},
|
||||
other => panic!("Expected SdkClientApprove, got {other:?}"),
|
||||
};
|
||||
|
||||
assert_eq!(entry.pubkey, pubkey.to_vec());
|
||||
assert!(entry.id > 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[test_log::test]
|
||||
async fn test_sdk_client_approve_duplicate_returns_already_exists() {
|
||||
let db = db::create_test_pool().await;
|
||||
let mut session = make_session(&db).await;
|
||||
|
||||
let pubkey = [0x11u8; 32];
|
||||
let req = UserAgentRequest {
|
||||
payload: Some(UserAgentRequestPayload::SdkClientApprove(
|
||||
SdkClientApproveRequest {
|
||||
pubkey: pubkey.to_vec(),
|
||||
},
|
||||
)),
|
||||
};
|
||||
|
||||
session
|
||||
.process_transport_inbound(req.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let response = session
|
||||
.process_transport_inbound(req)
|
||||
.await
|
||||
.expect("second insert should not panic");
|
||||
|
||||
match response.payload.unwrap() {
|
||||
UserAgentResponsePayload::SdkClientApprove(resp) => match resp.result.unwrap() {
|
||||
sdk_client_approve_response::Result::Error(code) => {
|
||||
assert_eq!(code, ProtoSdkClientError::AlreadyExists as i32);
|
||||
}
|
||||
sdk_client_approve_response::Result::Client(_) => {
|
||||
panic!("Expected AlreadyExists error for duplicate pubkey")
|
||||
}
|
||||
},
|
||||
other => panic!("Expected SdkClientApprove, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[test_log::test]
|
||||
async fn test_sdk_client_list_shows_registered_clients() {
|
||||
let db = db::create_test_pool().await;
|
||||
let mut session = make_session(&db).await;
|
||||
|
||||
let pubkey_a = [0x0Au8; 32];
|
||||
let pubkey_b = [0x0Bu8; 32];
|
||||
|
||||
for pubkey in [pubkey_a, pubkey_b] {
|
||||
session
|
||||
.process_transport_inbound(UserAgentRequest {
|
||||
payload: Some(UserAgentRequestPayload::SdkClientApprove(
|
||||
SdkClientApproveRequest {
|
||||
pubkey: pubkey.to_vec(),
|
||||
},
|
||||
)),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let response = session
|
||||
.process_transport_inbound(UserAgentRequest {
|
||||
payload: Some(UserAgentRequestPayload::SdkClientList(())),
|
||||
})
|
||||
.await
|
||||
.expect("list should succeed");
|
||||
|
||||
let clients = match response.payload.unwrap() {
|
||||
UserAgentResponsePayload::SdkClientList(resp) => match resp.result.unwrap() {
|
||||
sdk_client_list_response::Result::Clients(list) => list.clients,
|
||||
sdk_client_list_response::Result::Error(e) => {
|
||||
panic!("Expected Clients, got error {:?}", e)
|
||||
}
|
||||
},
|
||||
other => panic!("Expected SdkClientList, got {other:?}"),
|
||||
};
|
||||
|
||||
assert_eq!(clients.len(), 2);
|
||||
let pubkeys: Vec<Vec<u8>> = clients.into_iter().map(|e| e.pubkey).collect();
|
||||
assert!(pubkeys.contains(&pubkey_a.to_vec()));
|
||||
assert!(pubkeys.contains(&pubkey_b.to_vec()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[test_log::test]
|
||||
async fn test_sdk_client_revoke_removes_client() {
|
||||
let db = db::create_test_pool().await;
|
||||
let mut session = make_session(&db).await;
|
||||
|
||||
let pubkey = [0xBBu8; 32];
|
||||
|
||||
// Register a client and get its id
|
||||
let approve_response = session
|
||||
.process_transport_inbound(UserAgentRequest {
|
||||
payload: Some(UserAgentRequestPayload::SdkClientApprove(
|
||||
SdkClientApproveRequest {
|
||||
pubkey: pubkey.to_vec(),
|
||||
},
|
||||
)),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let client_id = match approve_response.payload.unwrap() {
|
||||
UserAgentResponsePayload::SdkClientApprove(resp) => match resp.result.unwrap() {
|
||||
sdk_client_approve_response::Result::Client(e) => e.id,
|
||||
sdk_client_approve_response::Result::Error(e) => panic!("approve failed: {:?}", e),
|
||||
},
|
||||
other => panic!("{other:?}"),
|
||||
};
|
||||
|
||||
// Revoke the client
|
||||
let revoke_response = session
|
||||
.process_transport_inbound(UserAgentRequest {
|
||||
payload: Some(UserAgentRequestPayload::SdkClientRevoke(
|
||||
SdkClientRevokeRequest { client_id },
|
||||
)),
|
||||
})
|
||||
.await
|
||||
.expect("revoke should succeed");
|
||||
|
||||
match revoke_response.payload.unwrap() {
|
||||
UserAgentResponsePayload::SdkClientRevoke(resp) => match resp.result.unwrap() {
|
||||
sdk_client_revoke_response::Result::Ok(_) => {}
|
||||
sdk_client_revoke_response::Result::Error(e) => {
|
||||
panic!("Expected Ok, got error {:?}", e)
|
||||
}
|
||||
},
|
||||
other => panic!("Expected SdkClientRevoke, got {other:?}"),
|
||||
}
|
||||
|
||||
// List should now be empty
|
||||
let list_response = session
|
||||
.process_transport_inbound(UserAgentRequest {
|
||||
payload: Some(UserAgentRequestPayload::SdkClientList(())),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let clients = match list_response.payload.unwrap() {
|
||||
UserAgentResponsePayload::SdkClientList(resp) => match resp.result.unwrap() {
|
||||
sdk_client_list_response::Result::Clients(list) => list.clients,
|
||||
sdk_client_list_response::Result::Error(e) => panic!("list error: {:?}", e),
|
||||
},
|
||||
other => panic!("{other:?}"),
|
||||
};
|
||||
assert!(clients.is_empty(), "client should be removed after revoke");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[test_log::test]
|
||||
async fn test_sdk_client_revoke_not_found_returns_error() {
|
||||
let db = db::create_test_pool().await;
|
||||
let mut session = make_session(&db).await;
|
||||
|
||||
let response = session
|
||||
.process_transport_inbound(UserAgentRequest {
|
||||
payload: Some(UserAgentRequestPayload::SdkClientRevoke(
|
||||
SdkClientRevokeRequest { client_id: 9999 },
|
||||
)),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
match response.payload.unwrap() {
|
||||
UserAgentResponsePayload::SdkClientRevoke(resp) => match resp.result.unwrap() {
|
||||
sdk_client_revoke_response::Result::Error(code) => {
|
||||
assert_eq!(code, ProtoSdkClientError::NotFound as i32);
|
||||
}
|
||||
sdk_client_revoke_response::Result::Ok(_) => {
|
||||
panic!("Expected NotFound error for missing client_id")
|
||||
}
|
||||
},
|
||||
other => panic!("Expected SdkClientRevoke, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[test_log::test]
|
||||
async fn test_sdk_client_approve_rejected_client_cannot_auth() {
|
||||
// Verify the core flow: only pre-approved clients can authenticate
|
||||
use arbiter_proto::proto::client::{
|
||||
AuthChallengeRequest, ClientRequest, client_request::Payload as ClientRequestPayload,
|
||||
client_response::Payload as ClientResponsePayload,
|
||||
};
|
||||
use arbiter_proto::transport::Bi as _;
|
||||
use arbiter_server::actors::client::{ClientConnection, connect_client};
|
||||
|
||||
let db = db::create_test_pool().await;
|
||||
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
||||
|
||||
let new_key = ed25519_dalek::SigningKey::generate(&mut rand::rng());
|
||||
let pubkey_bytes = new_key.verifying_key().to_bytes().to_vec();
|
||||
|
||||
let (server_transport, mut test_transport) = super::common::ChannelTransport::<_, _>::new();
|
||||
let props = ClientConnection::new(db.clone(), Box::new(server_transport), actors.clone());
|
||||
let task = tokio::spawn(connect_client(props));
|
||||
|
||||
test_transport
|
||||
.send(ClientRequest {
|
||||
payload: Some(ClientRequestPayload::AuthChallengeRequest(
|
||||
AuthChallengeRequest {
|
||||
pubkey: pubkey_bytes.clone(),
|
||||
},
|
||||
)),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let response = test_transport.recv().await.unwrap().unwrap();
|
||||
assert!(
|
||||
matches!(
|
||||
response.payload.unwrap(),
|
||||
ClientResponsePayload::ClientConnectError(_)
|
||||
),
|
||||
"unregistered client should be rejected"
|
||||
);
|
||||
|
||||
task.await.unwrap();
|
||||
}
|
||||
Reference in New Issue
Block a user