feat: rustc and clippy linting
Some checks failed
ci/woodpecker/pr/server-audit Pipeline was successful
ci/woodpecker/pr/server-vet Pipeline failed
ci/woodpecker/pr/server-lint Pipeline failed
ci/woodpecker/pr/server-test Pipeline was successful

This commit is contained in:
CleverWild
2026-04-10 00:42:43 +02:00
parent 62dff3f810
commit f6a0c32b9d
69 changed files with 1491 additions and 979 deletions

View File

@@ -13,8 +13,8 @@ const TOKEN_LENGTH: usize = 64;
pub async fn generate_token() -> Result<String, std::io::Error> {
let rng: StdRng = make_rng();
let token: String = rng.sample_iter(Alphanumeric).take(TOKEN_LENGTH).fold(
Default::default(),
let token = rng.sample_iter(Alphanumeric).take(TOKEN_LENGTH).fold(
String::default(),
|mut accum, char| {
accum += char.to_string().as_str();
accum
@@ -27,15 +27,15 @@ pub async fn generate_token() -> Result<String, std::io::Error> {
}
#[derive(Error, Debug)]
pub enum Error {
pub enum BootstrappError {
#[error("Database error: {0}")]
Database(#[from] db::PoolError),
#[error("Database query error: {0}")]
Query(#[from] diesel::result::Error),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("Database query error: {0}")]
Query(#[from] diesel::result::Error),
}
#[derive(Actor)]
@@ -44,7 +44,7 @@ pub struct Bootstrapper {
}
impl Bootstrapper {
pub async fn new(db: &DatabasePool) -> Result<Self, Error> {
pub async fn new(db: &DatabasePool) -> Result<Self, BootstrappError> {
let row_count: i64 = {
let mut conn = db.get().await?;
@@ -69,16 +69,13 @@ impl Bootstrapper {
impl Bootstrapper {
#[message]
pub fn is_correct_token(&self, token: String) -> bool {
match &self.token {
Some(expected) => {
let expected_bytes = expected.as_bytes();
let token_bytes = token.as_bytes();
self.token.as_ref().is_some_and(|expected| {
let expected_bytes = expected.as_bytes();
let token_bytes = token.as_bytes();
let choice = expected_bytes.ct_eq(token_bytes);
bool::from(choice)
}
None => false,
}
let choice = expected_bytes.ct_eq(token_bytes);
bool::from(choice)
})
}
#[message]

View File

@@ -1,6 +1,7 @@
use arbiter_crypto::authn::{self, CLIENT_CONTEXT};
use arbiter_proto::{
ClientMetadata,
proto::client::auth::{AuthChallenge as ProtoAuthChallenge, AuthResult as ProtoAuthResult},
transport::{Bi, expect_message},
};
use chrono::Utc;
@@ -27,34 +28,60 @@ use crate::{
};
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
pub enum Error {
#[error("Database pool unavailable")]
DatabasePoolUnavailable,
#[error("Database operation failed")]
DatabaseOperationFailed,
#[error("Integrity check failed")]
IntegrityCheckFailed,
#[error("Invalid challenge solution")]
InvalidChallengeSolution,
pub enum ClientAuthError {
#[error("Client approval request failed")]
ApproveError(#[from] ApproveError),
#[error("Database operation failed")]
DatabaseOperationFailed,
#[error("Database pool unavailable")]
DatabasePoolUnavailable,
#[error("Integrity check failed")]
IntegrityCheckFailed,
#[error("Invalid challenge solution")]
InvalidChallengeSolution,
#[error("Transport error")]
Transport,
}
impl From<diesel::result::Error> for Error {
impl From<diesel::result::Error> for ClientAuthError {
fn from(e: diesel::result::Error) -> Self {
error!(?e, "Database error");
Self::DatabaseOperationFailed
}
}
impl From<ClientAuthError> for arbiter_proto::proto::client::auth::AuthResult {
fn from(value: ClientAuthError) -> Self {
match value {
ClientAuthError::ApproveError(e) => match e {
ApproveError::Denied => Self::ApprovalDenied,
ApproveError::Internal => Self::Internal,
ApproveError::Upstream(flow_coordinator::ApprovalError::NoUserAgentsConnected) => {
Self::NoUserAgentsOnline
} // ApproveError::Upstream(_) => Self::Internal,
},
ClientAuthError::DatabaseOperationFailed
| ClientAuthError::DatabasePoolUnavailable
| ClientAuthError::IntegrityCheckFailed
| ClientAuthError::Transport => Self::Internal,
ClientAuthError::InvalidChallengeSolution => Self::InvalidSignature,
}
}
}
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
pub enum ApproveError {
#[error("Internal error")]
Internal,
#[error("Client connection denied by user agents")]
Denied,
#[error("Internal error")]
Internal,
#[error("Upstream error: {0}")]
Upstream(flow_coordinator::ApprovalError),
}
@@ -79,16 +106,28 @@ pub enum Outbound {
AuthSuccess,
}
impl From<Outbound> for arbiter_proto::proto::client::auth::response::Payload {
fn from(value: Outbound) -> Self {
match value {
Outbound::AuthChallenge { pubkey, nonce } => Self::Challenge(ProtoAuthChallenge {
pubkey: pubkey.to_bytes(),
nonce,
}),
Outbound::AuthSuccess => Self::Result(ProtoAuthResult::Success.into()),
}
}
}
/// Returns the current nonce and client ID for a registered client.
/// Returns `None` if the pubkey is not registered.
async fn get_current_nonce_and_id(
db: &db::DatabasePool,
pubkey: &authn::PublicKey,
) -> Result<Option<(i32, i32)>, Error> {
) -> Result<Option<(i32, i32)>, ClientAuthError> {
let pubkey_bytes = pubkey.to_bytes();
let mut conn = db.get().await.map_err(|e| {
error!(error = ?e, "Database pool error");
Error::DatabasePoolUnavailable
ClientAuthError::DatabasePoolUnavailable
})?;
program_client::table
.filter(program_client::public_key.eq(&pubkey_bytes))
@@ -98,7 +137,7 @@ async fn get_current_nonce_and_id(
.optional()
.map_err(|e| {
error!(error = ?e, "Database error");
Error::DatabaseOperationFailed
ClientAuthError::DatabaseOperationFailed
})
}
@@ -106,15 +145,15 @@ async fn verify_integrity(
db: &db::DatabasePool,
keyholder: &ActorRef<KeyHolder>,
pubkey: &authn::PublicKey,
) -> Result<(), Error> {
) -> Result<(), ClientAuthError> {
let mut db_conn = db.get().await.map_err(|e| {
error!(error = ?e, "Database pool error");
Error::DatabasePoolUnavailable
ClientAuthError::DatabasePoolUnavailable
})?;
let (id, nonce) = get_current_nonce_and_id(db, pubkey).await?.ok_or_else(|| {
error!("Client not found during integrity verification");
Error::DatabaseOperationFailed
ClientAuthError::DatabaseOperationFailed
})?;
let attestation = integrity::verify_entity(
@@ -129,12 +168,12 @@ async fn verify_integrity(
.await
.map_err(|e| {
error!(?e, "Integrity verification failed");
Error::IntegrityCheckFailed
ClientAuthError::IntegrityCheckFailed
})?;
if attestation != AttestationStatus::Attested {
error!("Integrity attestation unavailable for client {id}");
return Err(Error::IntegrityCheckFailed);
return Err(ClientAuthError::IntegrityCheckFailed);
}
Ok(())
@@ -146,13 +185,13 @@ async fn create_nonce(
db: &db::DatabasePool,
keyholder: &ActorRef<KeyHolder>,
pubkey: &authn::PublicKey,
) -> Result<i32, Error> {
) -> Result<i32, ClientAuthError> {
let pubkey_bytes = pubkey.to_bytes();
let pubkey = pubkey.clone();
let mut conn = db.get().await.map_err(|e| {
error!(error = ?e, "Database pool error");
Error::DatabasePoolUnavailable
ClientAuthError::DatabasePoolUnavailable
})?;
conn.exclusive_transaction(|conn| {
@@ -178,7 +217,7 @@ async fn create_nonce(
.await
.map_err(|e| {
error!(?e, "Integrity sign failed after nonce update");
Error::DatabaseOperationFailed
ClientAuthError::DatabaseOperationFailed
})?;
Ok(new_nonce)
@@ -190,7 +229,7 @@ async fn create_nonce(
async fn approve_new_client(
actors: &crate::actors::GlobalActors,
profile: ClientProfile,
) -> Result<(), Error> {
) -> Result<(), ClientAuthError> {
let result = actors
.flow_coordinator
.ask(RequestClientApproval { client: profile })
@@ -198,14 +237,14 @@ async fn approve_new_client(
match result {
Ok(true) => Ok(()),
Ok(false) => Err(Error::ApproveError(ApproveError::Denied)),
Ok(false) => Err(ClientAuthError::ApproveError(ApproveError::Denied)),
Err(SendError::HandlerError(e)) => {
error!(error = ?e, "Approval upstream error");
Err(Error::ApproveError(ApproveError::Upstream(e)))
Err(ClientAuthError::ApproveError(ApproveError::Upstream(e)))
}
Err(e) => {
error!(error = ?e, "Approval request to flow coordinator failed");
Err(Error::ApproveError(ApproveError::Internal))
Err(ClientAuthError::ApproveError(ApproveError::Internal))
}
}
}
@@ -215,14 +254,14 @@ async fn insert_client(
keyholder: &ActorRef<KeyHolder>,
pubkey: &authn::PublicKey,
metadata: &ClientMetadata,
) -> Result<i32, Error> {
use crate::db::schema::{client_metadata, program_client};
) -> Result<i32, ClientAuthError> {
use crate::db::schema::client_metadata;
let pubkey = pubkey.clone();
let metadata = metadata.clone();
let mut conn = db.get().await.map_err(|e| {
error!(error = ?e, "Database pool error");
Error::DatabasePoolUnavailable
ClientAuthError::DatabasePoolUnavailable
})?;
conn.exclusive_transaction(|conn| {
@@ -264,7 +303,7 @@ async fn insert_client(
.await
.map_err(|e| {
error!(error = ?e, "Failed to sign integrity tag for new client key");
Error::DatabaseOperationFailed
ClientAuthError::DatabaseOperationFailed
})?;
Ok(client_id)
@@ -277,14 +316,14 @@ async fn sync_client_metadata(
db: &db::DatabasePool,
client_id: i32,
metadata: &ClientMetadata,
) -> Result<(), Error> {
) -> Result<(), ClientAuthError> {
use crate::db::schema::{client_metadata, client_metadata_history};
let now = SqliteTimestamp(Utc::now());
let mut conn = db.get().await.map_err(|e| {
error!(error = ?e, "Database pool error");
Error::DatabasePoolUnavailable
ClientAuthError::DatabasePoolUnavailable
})?;
conn.exclusive_transaction(|conn| {
@@ -340,7 +379,7 @@ async fn sync_client_metadata(
.await
.map_err(|e| {
error!(error = ?e, "Database error");
Error::DatabaseOperationFailed
ClientAuthError::DatabaseOperationFailed
})
}
@@ -348,9 +387,9 @@ async fn challenge_client<T>(
transport: &mut T,
pubkey: authn::PublicKey,
nonce: i32,
) -> Result<(), Error>
) -> Result<(), ClientAuthError>
where
T: Bi<Inbound, Result<Outbound, Error>> + ?Sized,
T: Bi<Inbound, Result<Outbound, ClientAuthError>> + ?Sized,
{
transport
.send(Ok(Outbound::AuthChallenge {
@@ -360,51 +399,51 @@ where
.await
.map_err(|e| {
error!(error = ?e, "Failed to send auth challenge");
Error::Transport
ClientAuthError::Transport
})?;
let signature = expect_message(transport, |req: Inbound| match req {
Inbound::AuthChallengeSolution { signature } => Some(signature),
_ => None,
Inbound::AuthChallengeRequest { .. } => None,
})
.await
.map_err(|e| {
error!(error = ?e, "Failed to receive challenge solution");
Error::Transport
ClientAuthError::Transport
})?;
if !pubkey.verify(nonce, CLIENT_CONTEXT, &signature) {
error!("Challenge solution verification failed");
return Err(Error::InvalidChallengeSolution);
return Err(ClientAuthError::InvalidChallengeSolution);
}
Ok(())
}
pub async fn authenticate<T>(props: &mut ClientConnection, transport: &mut T) -> Result<i32, Error>
pub async fn authenticate<T>(
props: &mut ClientConnection,
transport: &mut T,
) -> Result<i32, ClientAuthError>
where
T: Bi<Inbound, Result<Outbound, Error>> + Send + ?Sized,
T: Bi<Inbound, Result<Outbound, ClientAuthError>> + Send + ?Sized,
{
let Some(Inbound::AuthChallengeRequest { pubkey, metadata }) = transport.recv().await else {
return Err(Error::Transport);
return Err(ClientAuthError::Transport);
};
let client_id = match get_current_nonce_and_id(&props.db, &pubkey).await? {
Some((id, _)) => {
verify_integrity(&props.db, &props.actors.key_holder, &pubkey).await?;
id
}
None => {
approve_new_client(
&props.actors,
ClientProfile {
pubkey: pubkey.clone(),
metadata: metadata.clone(),
},
)
.await?;
insert_client(&props.db, &props.actors.key_holder, &pubkey, &metadata).await?
}
let client_id = if let Some((id, _)) = get_current_nonce_and_id(&props.db, &pubkey).await? {
verify_integrity(&props.db, &props.actors.key_holder, &pubkey).await?;
id
} else {
approve_new_client(
&props.actors,
ClientProfile {
pubkey: pubkey.clone(),
metadata: metadata.clone(),
},
)
.await?;
insert_client(&props.db, &props.actors.key_holder, &pubkey, &metadata).await?
};
sync_client_metadata(&props.db, client_id, &metadata).await?;
@@ -416,7 +455,7 @@ where
.await
.map_err(|e| {
error!(error = ?e, "Failed to send auth success");
Error::Transport
ClientAuthError::Transport
})?;
Ok(client_id)

View File

@@ -31,7 +31,7 @@ pub struct ClientConnection {
}
impl ClientConnection {
pub fn new(db: db::DatabasePool, actors: GlobalActors) -> Self {
pub const fn new(db: db::DatabasePool, actors: GlobalActors) -> Self {
Self { db, actors }
}
}
@@ -41,10 +41,10 @@ pub mod session;
pub async fn connect_client<T>(mut props: ClientConnection, transport: &mut T)
where
T: Bi<auth::Inbound, Result<auth::Outbound, auth::Error>> + Send + ?Sized,
T: Bi<auth::Inbound, Result<auth::Outbound, auth::ClientAuthError>> + Send + ?Sized,
{
let fut = auth::authenticate(&mut props, transport);
println!("authenticate future size: {}", std::mem::size_of_val(&fut));
println!("authenticate future size: {}", size_of_val(&fut));
match fut.await {
Ok(client_id) => {
ClientSession::spawn(ClientSession::new(props, client_id));

View File

@@ -21,7 +21,7 @@ pub struct ClientSession {
}
impl ClientSession {
pub(crate) fn new(props: ClientConnection, client_id: i32) -> Self {
pub(crate) const fn new(props: ClientConnection, client_id: i32) -> Self {
Self { props, client_id }
}
}
@@ -29,14 +29,16 @@ impl ClientSession {
#[messages]
impl ClientSession {
#[message]
pub(crate) async fn handle_query_vault_state(&mut self) -> Result<KeyHolderState, Error> {
pub(crate) async fn handle_query_vault_state(
&mut self,
) -> Result<KeyHolderState, ClientSessionError> {
use crate::actors::keyholder::GetState;
let vault_state = match self.props.actors.key_holder.ask(GetState {}).await {
Ok(state) => state,
Err(err) => {
error!(?err, actor = "client", "keyholder.query.failed");
return Err(Error::Internal);
return Err(ClientSessionError::Internal);
}
};
@@ -75,7 +77,7 @@ impl ClientSession {
impl Actor for ClientSession {
type Args = Self;
type Error = Error;
type Error = ClientSessionError;
async fn on_start(
args: Self::Args,
@@ -86,13 +88,13 @@ impl Actor for ClientSession {
.flow_coordinator
.ask(RegisterClient { actor: this })
.await
.map_err(|_| Error::ConnectionRegistrationFailed)?;
.map_err(|_| ClientSessionError::ConnectionRegistrationFailed)?;
Ok(args)
}
}
impl ClientSession {
pub fn new_test(db: db::DatabasePool, actors: GlobalActors) -> Self {
pub const fn new_test(db: db::DatabasePool, actors: GlobalActors) -> Self {
let props = ClientConnection::new(db, actors);
Self {
props,
@@ -102,7 +104,7 @@ impl ClientSession {
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
pub enum ClientSessionError {
#[error("Connection registration failed")]
ConnectionRegistrationFailed,
#[error("Internal error")]
@@ -111,9 +113,9 @@ pub enum Error {
#[derive(Debug, thiserror::Error)]
pub enum SignTransactionRpcError {
#[error("Policy evaluation failed")]
Vet(#[from] VetError),
#[error("Internal error")]
Internal,
#[error("Policy evaluation failed")]
Vet(#[from] VetError),
}

View File

@@ -1,4 +1,6 @@
use alloy::{consensus::TxEip1559, primitives::Address, signers::Signature};
use alloy::{
consensus::TxEip1559, network::TxSignerSync as _, primitives::Address, signers::Signature,
};
use diesel::{
ExpressionMethods, OptionalExtension as _, QueryDsl, SelectableHelper as _, dsl::insert_into,
};
@@ -35,7 +37,7 @@ pub enum SignTransactionError {
Database(#[from] DatabaseError),
#[error("Keyholder error: {0}")]
Keyholder(#[from] crate::actors::keyholder::Error),
Keyholder(#[from] crate::actors::keyholder::KeyHolderError),
#[error("Keyholder mailbox error")]
KeyholderSend,
@@ -48,9 +50,9 @@ pub enum SignTransactionError {
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
pub enum EvmActorError {
#[error("Keyholder error: {0}")]
Keyholder(#[from] crate::actors::keyholder::Error),
Keyholder(#[from] crate::actors::keyholder::KeyHolderError),
#[error("Keyholder mailbox error")]
KeyholderSend,
@@ -59,7 +61,7 @@ pub enum Error {
Database(#[from] DatabaseError),
#[error("Integrity violation: {0}")]
Integrity(#[from] integrity::Error),
Integrity(#[from] integrity::IntegrityError),
}
#[derive(Actor)]
@@ -88,7 +90,7 @@ impl EvmActor {
#[messages]
impl EvmActor {
#[message]
pub async fn generate(&mut self) -> Result<(i32, Address), Error> {
pub async fn generate(&mut self) -> Result<(i32, Address), EvmActorError> {
let (mut key_cell, address) = safe_signer::generate(&mut self.rng);
let plaintext = key_cell.read_inline(|reader| SafeCell::new(reader.to_vec()));
@@ -97,7 +99,7 @@ impl EvmActor {
.keyholder
.ask(CreateNew { plaintext })
.await
.map_err(|_| Error::KeyholderSend)?;
.map_err(|_| EvmActorError::KeyholderSend)?;
let mut conn = self.db.get().await.map_err(DatabaseError::from)?;
let wallet_id = insert_into(schema::evm_wallet::table)
@@ -114,7 +116,7 @@ impl EvmActor {
}
#[message]
pub async fn list_wallets(&self) -> Result<Vec<(i32, Address)>, Error> {
pub async fn list_wallets(&self) -> Result<Vec<(i32, Address)>, EvmActorError> {
let mut conn = self.db.get().await.map_err(DatabaseError::from)?;
let rows: Vec<models::EvmWallet> = schema::evm_wallet::table
.select(models::EvmWallet::as_select())
@@ -136,7 +138,7 @@ impl EvmActor {
&mut self,
basic: SharedGrantSettings,
grant: SpecificGrant,
) -> Result<i32, Error> {
) -> Result<i32, EvmActorError> {
match grant {
SpecificGrant::EtherTransfer(settings) => self
.engine
@@ -145,7 +147,7 @@ impl EvmActor {
specific: settings,
})
.await
.map_err(Error::from),
.map_err(EvmActorError::from),
SpecificGrant::TokenTransfer(settings) => self
.engine
.create_grant::<TokenTransfer>(CombinedSettings {
@@ -153,12 +155,13 @@ impl EvmActor {
specific: settings,
})
.await
.map_err(Error::from),
.map_err(EvmActorError::from),
}
}
#[message]
pub async fn useragent_delete_grant(&mut self, _grant_id: i32) -> Result<(), Error> {
#[expect(clippy::unused_async, reason = "reserved for impl")]
pub async fn useragent_delete_grant(&mut self, _grant_id: i32) -> Result<(), EvmActorError> {
// let mut conn = self.db.get().await.map_err(DatabaseError::from)?;
// let keyholder = self.keyholder.clone();
@@ -183,11 +186,15 @@ impl EvmActor {
}
#[message]
pub async fn useragent_list_grants(&mut self) -> Result<Vec<Grant<SpecificGrant>>, Error> {
pub async fn useragent_list_grants(
&mut self,
) -> Result<Vec<Grant<SpecificGrant>>, EvmActorError> {
match self.engine.list_all_grants().await {
Ok(grants) => Ok(grants),
Err(ListError::Database(db_err)) => Err(Error::Database(db_err)),
Err(ListError::Integrity(integrity_err)) => Err(Error::Integrity(integrity_err)),
Err(ListError::Database(db_err)) => Err(EvmActorError::Database(db_err)),
Err(ListError::Integrity(integrity_err)) => {
Err(EvmActorError::Integrity(integrity_err))
}
}
}
@@ -267,7 +274,6 @@ impl EvmActor {
.evaluate_transaction(wallet_access, transaction.clone(), RunKind::Execution)
.await?;
use alloy::network::TxSignerSync as _;
Ok(signer.sign_transaction_sync(&mut transaction)?)
}
}

View File

@@ -41,7 +41,7 @@ impl Actor for ClientApprovalController {
async fn on_start(
Args {
client,
mut user_agents,
user_agents,
reply,
}: Self::Args,
actor_ref: ActorRef<Self>,
@@ -52,8 +52,9 @@ impl Actor for ClientApprovalController {
reply: Some(reply),
};
for user_agent in user_agents.drain(..) {
for user_agent in user_agents {
actor_ref.link(&user_agent).await;
let _ = user_agent
.tell(BeginNewClientApproval {
client: client.clone(),
@@ -85,7 +86,7 @@ impl Actor for ClientApprovalController {
#[messages]
impl ClientApprovalController {
#[message(ctx)]
pub async fn client_approval_answer(&mut self, approved: bool, ctx: &mut Context<Self, ()>) {
pub fn client_approval_answer(&mut self, approved: bool, ctx: &mut Context<Self, ()>) {
if !approved {
// Denial wins immediately regardless of other pending responses.
self.send_reply(Ok(false));

View File

@@ -92,7 +92,7 @@ impl FlowCoordinator {
}
#[message(ctx)]
pub async fn request_client_approval(
pub fn request_client_approval(
&mut self,
client: ClientProfile,
ctx: &mut Context<Self, DelegatedReply<Result<bool, ApprovalError>>>,

View File

@@ -36,19 +36,12 @@ enum State {
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
pub enum KeyHolderError {
#[error("Keyholder is already bootstrapped")]
AlreadyBootstrapped,
#[error("Keyholder is not bootstrapped")]
NotBootstrapped,
#[error("Invalid key provided")]
InvalidKey,
#[error("Requested aead entry not found")]
NotFound,
#[error("Encryption error: {0}")]
Encryption(#[from] chacha20poly1305::aead::Error),
#[error("Broken database")]
BrokenDatabase,
#[error("Database error: {0}")]
DatabaseConnection(#[from] db::PoolError),
@@ -56,11 +49,21 @@ pub enum Error {
#[error("Database transaction error: {0}")]
DatabaseTransaction(#[from] diesel::result::Error),
#[error("Broken database")]
BrokenDatabase,
#[error("Encryption error: {0}")]
Encryption(#[from] chacha20poly1305::aead::Error),
#[error("Invalid key provided")]
InvalidKey,
#[error("Keyholder is not bootstrapped")]
NotBootstrapped,
#[error("Requested aead entry not found")]
NotFound,
}
/// Manages vault root key and tracks current state of the vault (bootstrapped/unbootstrapped, sealed/unsealed).
///
/// Provides API for encrypting and decrypting data using the vault root key.
/// Abstraction over database to make sure nonces are never reused and encryption keys are never exposed in plaintext outside of this actor.
#[derive(Actor)]
@@ -71,7 +74,7 @@ pub struct KeyHolder {
#[messages]
impl KeyHolder {
pub async fn new(db: db::DatabasePool) -> Result<Self, Error> {
pub async fn new(db: db::DatabasePool) -> Result<Self, KeyHolderError> {
let state = {
let mut conn = db.get().await?;
@@ -94,7 +97,10 @@ impl KeyHolder {
// Exclusive transaction to avoid race condtions if multiple keyholders write
// additional layer of protection against nonce-reuse
async fn get_new_nonce(pool: &db::DatabasePool, root_key_id: i32) -> Result<Nonce, Error> {
async fn get_new_nonce(
pool: &db::DatabasePool,
root_key_id: i32,
) -> Result<Nonce, KeyHolderError> {
let mut conn = pool.get().await?;
let nonce = conn
@@ -106,12 +112,12 @@ impl KeyHolder {
.first(conn)
.await?;
let mut nonce = Nonce::try_from(current_nonce.as_slice()).map_err(|_| {
let mut nonce = Nonce::try_from(current_nonce.as_slice()).map_err(|()| {
error!(
"Broken database: invalid nonce for root key history id={}",
root_key_id
);
Error::BrokenDatabase
KeyHolderError::BrokenDatabase
})?;
nonce.increment();
@@ -121,7 +127,7 @@ impl KeyHolder {
.execute(conn)
.await?;
Result::<_, Error>::Ok(nonce)
Result::<_, KeyHolderError>::Ok(nonce)
})
})
.await?;
@@ -130,9 +136,12 @@ impl KeyHolder {
}
#[message]
pub async fn bootstrap(&mut self, seal_key_raw: SafeCell<Vec<u8>>) -> Result<(), Error> {
pub async fn bootstrap(
&mut self,
seal_key_raw: SafeCell<Vec<u8>>,
) -> Result<(), KeyHolderError> {
if !matches!(self.state, State::Unbootstrapped) {
return Err(Error::AlreadyBootstrapped);
return Err(KeyHolderError::AlreadyBootstrapped);
}
let salt = v1::generate_salt();
let mut seal_key = derive_key(seal_key_raw, &salt);
@@ -148,7 +157,7 @@ impl KeyHolder {
.encrypt(&root_key_nonce, v1::ROOT_KEY_TAG, root_key_reader)
.map_err(|err| {
error!(?err, "Fatal bootstrap error");
Error::Encryption(err)
KeyHolderError::Encryption(err)
})
})?;
@@ -192,12 +201,15 @@ impl KeyHolder {
}
#[message]
pub async fn try_unseal(&mut self, seal_key_raw: SafeCell<Vec<u8>>) -> Result<(), Error> {
pub async fn try_unseal(
&mut self,
seal_key_raw: SafeCell<Vec<u8>>,
) -> Result<(), KeyHolderError> {
let State::Sealed {
root_key_history_id,
} = &self.state
else {
return Err(Error::NotBootstrapped);
return Err(KeyHolderError::NotBootstrapped);
};
// We don't want to hold connection while doing expensive KDF work
@@ -213,16 +225,16 @@ impl KeyHolder {
let salt = &current_key.salt;
let salt = v1::Salt::try_from(salt.as_slice()).map_err(|_| {
error!("Broken database: invalid salt for root key");
Error::BrokenDatabase
KeyHolderError::BrokenDatabase
})?;
let mut seal_key = derive_key(seal_key_raw, &salt);
let mut root_key = SafeCell::new(current_key.ciphertext.clone());
let nonce = v1::Nonce::try_from(current_key.root_key_encryption_nonce.as_slice()).map_err(
|_| {
let nonce = Nonce::try_from(current_key.root_key_encryption_nonce.as_slice()).map_err(
|()| {
error!("Broken database: invalid nonce for root key");
Error::BrokenDatabase
KeyHolderError::BrokenDatabase
},
)?;
@@ -230,14 +242,14 @@ impl KeyHolder {
.decrypt_in_place(&nonce, v1::ROOT_KEY_TAG, &mut root_key)
.map_err(|err| {
error!(?err, "Failed to unseal root key: invalid seal key");
Error::InvalidKey
KeyHolderError::InvalidKey
})?;
self.state = State::Unsealed {
root_key_history_id: current_key.id,
root_key: KeyCell::try_from(root_key).map_err(|err| {
error!(?err, "Broken database: invalid encryption key size");
Error::BrokenDatabase
KeyHolderError::BrokenDatabase
})?,
};
@@ -247,9 +259,9 @@ impl KeyHolder {
}
#[message]
pub async fn decrypt(&mut self, aead_id: i32) -> Result<SafeCell<Vec<u8>>, Error> {
pub async fn decrypt(&mut self, aead_id: i32) -> Result<SafeCell<Vec<u8>>, KeyHolderError> {
let State::Unsealed { root_key, .. } = &mut self.state else {
return Err(Error::NotBootstrapped);
return Err(KeyHolderError::NotBootstrapped);
};
let row: models::AeadEncrypted = {
@@ -260,15 +272,15 @@ impl KeyHolder {
.first(&mut conn)
.await
.optional()?
.ok_or(Error::NotFound)?
.ok_or(KeyHolderError::NotFound)?
};
let nonce = v1::Nonce::try_from(row.current_nonce.as_slice()).map_err(|_| {
let nonce = Nonce::try_from(row.current_nonce.as_slice()).map_err(|()| {
error!(
"Broken database: invalid nonce for aead_encrypted id={}",
aead_id
);
Error::BrokenDatabase
KeyHolderError::BrokenDatabase
})?;
let mut output = SafeCell::new(row.ciphertext);
root_key.decrypt_in_place(&nonce, v1::TAG, &mut output)?;
@@ -277,14 +289,17 @@ impl KeyHolder {
// Creates new `aead_encrypted` entry in the database and returns it's ID
#[message]
pub async fn create_new(&mut self, mut plaintext: SafeCell<Vec<u8>>) -> Result<i32, Error> {
pub async fn create_new(
&mut self,
mut plaintext: SafeCell<Vec<u8>>,
) -> Result<i32, KeyHolderError> {
let State::Unsealed {
root_key,
root_key_history_id,
..
} = &mut self.state
else {
return Err(Error::NotBootstrapped);
return Err(KeyHolderError::NotBootstrapped);
};
// Order matters here - `get_new_nonce` acquires connection, so we need to call it before next acquire
@@ -320,21 +335,19 @@ impl KeyHolder {
}
#[message]
pub fn sign_integrity(&mut self, mac_input: Vec<u8>) -> Result<(i32, Vec<u8>), Error> {
pub fn sign_integrity(&mut self, mac_input: Vec<u8>) -> Result<(i32, Vec<u8>), KeyHolderError> {
let State::Unsealed {
root_key,
root_key_history_id,
} = &mut self.state
else {
return Err(Error::NotBootstrapped);
return Err(KeyHolderError::NotBootstrapped);
};
let mut hmac = root_key
.0
.read_inline(|k| match HmacSha256::new_from_slice(k) {
Ok(v) => v,
Err(_) => unreachable!("HMAC accepts keys of any size"),
});
let mut hmac = root_key.0.read_inline(|k| {
HmacSha256::new_from_slice(k)
.unwrap_or_else(|_| unreachable!("HMAC accepts keys of any size"))
});
hmac.update(&root_key_history_id.to_be_bytes());
hmac.update(&mac_input);
@@ -348,25 +361,23 @@ impl KeyHolder {
mac_input: Vec<u8>,
expected_mac: Vec<u8>,
key_version: i32,
) -> Result<bool, Error> {
) -> Result<bool, KeyHolderError> {
let State::Unsealed {
root_key,
root_key_history_id,
} = &mut self.state
else {
return Err(Error::NotBootstrapped);
return Err(KeyHolderError::NotBootstrapped);
};
if *root_key_history_id != key_version {
return Ok(false);
}
let mut hmac = root_key
.0
.read_inline(|k| match HmacSha256::new_from_slice(k) {
Ok(v) => v,
Err(_) => unreachable!("HMAC accepts keys of any size"),
});
let mut hmac = root_key.0.read_inline(|k| {
HmacSha256::new_from_slice(k)
.unwrap_or_else(|_| unreachable!("HMAC accepts keys of any size"))
});
hmac.update(&key_version.to_be_bytes());
hmac.update(&mac_input);
@@ -374,13 +385,13 @@ impl KeyHolder {
}
#[message]
pub fn seal(&mut self) -> Result<(), Error> {
pub fn seal(&mut self) -> Result<(), KeyHolderError> {
let State::Unsealed {
root_key_history_id,
..
} = &self.state
else {
return Err(Error::NotBootstrapped);
return Err(KeyHolderError::NotBootstrapped);
};
self.state = State::Sealed {
root_key_history_id: *root_key_history_id,
@@ -391,12 +402,7 @@ impl KeyHolder {
#[cfg(test)]
mod tests {
use diesel::SelectableHelper;
use diesel_async::RunQueryDsl;
use crate::db::{self};
use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _};
use arbiter_crypto::safecell::SafeCellHandle as _;
use super::*;
@@ -412,12 +418,12 @@ mod tests {
async fn nonce_monotonic_even_when_nonce_allocation_interleaves() {
let db = db::create_test_pool().await;
let mut actor = bootstrapped_actor(&db).await;
let root_key_history_id = match actor.state {
State::Unsealed {
root_key_history_id,
..
} => root_key_history_id,
_ => panic!("expected unsealed state"),
let State::Unsealed {
root_key_history_id,
..
} = actor.state
else {
panic!("expected unsealed state");
};
let n1 = KeyHolder::get_new_nonce(&db, root_key_history_id)
@@ -429,8 +435,8 @@ mod tests {
assert!(n2.to_vec() > n1.to_vec(), "nonce must increase");
let mut conn = db.get().await.unwrap();
let root_row: models::RootKeyHistory = schema::root_key_history::table
.select(models::RootKeyHistory::as_select())
let root_row = schema::root_key_history::table
.select(RootKeyHistory::as_select())
.first(&mut conn)
.await
.unwrap();

View File

@@ -11,18 +11,18 @@ use crate::{
pub mod bootstrap;
pub mod client;
mod evm;
pub mod evm;
pub mod flow_coordinator;
pub mod keyholder;
pub mod user_agent;
#[derive(Error, Debug)]
pub enum SpawnError {
pub enum GlobalActorsSpawnError {
#[error("Failed to spawn Bootstrapper actor")]
Bootstrapper(#[from] bootstrap::Error),
Bootstrapper(#[from] bootstrap::BootstrappError),
#[error("Failed to spawn KeyHolder actor")]
KeyHolder(#[from] keyholder::Error),
KeyHolder(#[from] keyholder::KeyHolderError),
}
/// Long-lived actors that are shared across all connections and handle global state and operations
@@ -35,7 +35,7 @@ pub struct GlobalActors {
}
impl GlobalActors {
pub async fn spawn(db: db::DatabasePool) -> Result<Self, SpawnError> {
pub async fn spawn(db: db::DatabasePool) -> Result<Self, GlobalActorsSpawnError> {
let key_holder = KeyHolder::spawn(KeyHolder::new(db.clone()).await?);
Ok(Self {
bootstrapper: Bootstrapper::spawn(Bootstrapper::new(&db).await?),

View File

@@ -7,7 +7,9 @@ use crate::actors::user_agent::{
auth::state::{AuthContext, AuthStateMachine},
};
mod state;
use state::*;
use state::{
AuthError, AuthEvents, AuthStates, BootstrapAuthRequest, ChallengeRequest, ChallengeSolution,
};
#[derive(Debug, Clone)]
pub enum Inbound {

View File

@@ -204,14 +204,14 @@ pub struct AuthContext<'a, T> {
}
impl<'a, T> AuthContext<'a, T> {
pub fn new(conn: &'a mut UserAgentConnection, transport: T) -> Self {
pub const fn new(conn: &'a mut UserAgentConnection, transport: T) -> Self {
Self { conn, transport }
}
}
impl<T> AuthStateMachineContext for AuthContext<'_, T>
where
T: Bi<super::Inbound, Result<super::Outbound, Error>> + Send,
T: Bi<super::Inbound, Result<Outbound, Error>> + Send,
{
type Error = Error;
@@ -237,8 +237,6 @@ where
})
}
#[allow(missing_docs)]
#[allow(clippy::result_unit_err)]
async fn verify_bootstrap_token(
&mut self,
BootstrapAuthRequest { pubkey, token }: BootstrapAuthRequest,
@@ -261,28 +259,23 @@ where
return Err(Error::InvalidBootstrapToken);
}
match token_ok {
true => {
register_key(&self.conn.db, &self.conn.actors.key_holder, &pubkey).await?;
self.transport
.send(Ok(Outbound::AuthSuccess))
.await
.map_err(|_| Error::Transport)?;
Ok(pubkey)
}
false => {
error!("Invalid bootstrap token provided");
self.transport
.send(Err(Error::InvalidBootstrapToken))
.await
.map_err(|_| Error::Transport)?;
Err(Error::InvalidBootstrapToken)
}
if token_ok {
register_key(&self.conn.db, &self.conn.actors.key_holder, &pubkey).await?;
self.transport
.send(Ok(Outbound::AuthSuccess))
.await
.map_err(|_| Error::Transport)?;
Ok(pubkey)
} else {
error!("Invalid bootstrap token provided");
self.transport
.send(Err(Error::InvalidBootstrapToken))
.await
.map_err(|_| Error::Transport)?;
Err(Error::InvalidBootstrapToken)
}
}
#[allow(missing_docs)]
#[allow(clippy::unused_unit)]
async fn verify_solution(
&mut self,
ChallengeContext {
@@ -291,28 +284,25 @@ where
}: &ChallengeContext,
ChallengeSolution { solution }: ChallengeSolution,
) -> Result<authn::PublicKey, Self::Error> {
let signature = authn::Signature::try_from(solution.as_slice()).map_err(|_| {
let signature = authn::Signature::try_from(solution.as_slice()).map_err(|()| {
error!("Failed to decode signature in challenge solution");
Error::InvalidChallengeSolution
})?;
let valid = key.verify(*challenge_nonce, USERAGENT_CONTEXT, &signature);
match valid {
true => {
self.transport
.send(Ok(Outbound::AuthSuccess))
.await
.map_err(|_| Error::Transport)?;
Ok(key.clone())
}
false => {
self.transport
.send(Err(Error::InvalidChallengeSolution))
.await
.map_err(|_| Error::Transport)?;
Err(Error::InvalidChallengeSolution)
}
if valid {
self.transport
.send(Ok(Outbound::AuthSuccess))
.await
.map_err(|_| Error::Transport)?;
Ok(key.clone())
} else {
self.transport
.send(Err(Error::InvalidChallengeSolution))
.await
.map_err(|_| Error::Transport)?;
Err(Error::InvalidChallengeSolution)
}
}
}

View File

@@ -28,7 +28,7 @@ pub struct UserAgentConnection {
}
impl UserAgentConnection {
pub fn new(db: db::DatabasePool, actors: GlobalActors) -> Self {
pub const fn new(db: db::DatabasePool, actors: GlobalActors) -> Self {
Self { db, actors }
}
}

View File

@@ -17,28 +17,28 @@ mod state;
use state::{DummyContext, UserAgentEvents, UserAgentStateMachine};
#[derive(Debug, Error)]
pub enum Error {
#[error("State transition failed")]
State,
pub enum UserAgentSessionError {
#[error("Internal error: {message}")]
Internal { message: Cow<'static, str> },
#[error("State transition failed")]
State,
}
impl From<crate::db::PoolError> for Error {
impl From<crate::db::PoolError> for UserAgentSessionError {
fn from(err: crate::db::PoolError) -> Self {
error!(?err, "Database pool error");
Self::internal("Database pool error")
}
}
impl From<diesel::result::Error> for Error {
impl From<diesel::result::Error> for UserAgentSessionError {
fn from(err: diesel::result::Error) -> Self {
error!(?err, "Database error");
Self::internal("Database error")
}
}
impl Error {
impl UserAgentSessionError {
pub fn internal(message: impl Into<Cow<'static, str>>) -> Self {
Self::Internal {
message: message.into(),
@@ -67,7 +67,7 @@ impl UserAgentSession {
props,
state: UserAgentStateMachine::new(DummyContext),
sender,
pending_client_approvals: Default::default(),
pending_client_approvals: HashMap::default(),
}
}
@@ -87,10 +87,10 @@ impl UserAgentSession {
Self::new(UserAgentConnection::new(db, actors), Box::new(DummySender))
}
fn transition(&mut self, event: UserAgentEvents) -> Result<(), Error> {
fn transition(&mut self, event: UserAgentEvents) -> Result<(), UserAgentSessionError> {
self.state.process_event(event).map_err(|e| {
error!(?e, "State transition failed");
Error::State
UserAgentSessionError::State
})?;
Ok(())
}
@@ -132,11 +132,11 @@ impl UserAgentSession {
impl Actor for UserAgentSession {
type Args = Self;
type Error = Error;
type Error = UserAgentSessionError;
async fn on_start(
args: Self::Args,
this: kameo::prelude::ActorRef<Self>,
this: ActorRef<Self>,
) -> Result<Self, Self::Error> {
args.props
.actors
@@ -150,7 +150,9 @@ impl Actor for UserAgentSession {
?err,
"Failed to register user agent connection with flow coordinator"
);
Error::internal("Failed to register user agent connection with flow coordinator")
UserAgentSessionError::internal(
"Failed to register user agent connection with flow coordinator",
)
})?;
Ok(args)
}

View File

@@ -11,12 +11,13 @@ use diesel_async::{AsyncConnection, RunQueryDsl};
use kameo::error::SendError;
use kameo::messages;
use kameo::prelude::Context;
use thiserror::Error;
use tracing::{error, info};
use x25519_dalek::{EphemeralSecret, PublicKey};
use crate::actors::flow_coordinator::client_connect_approval::ClientApprovalAnswer;
use crate::{actors::flow_coordinator::client_connect_approval::ClientApprovalAnswer, evm::policies::SharedGrantSettings};
use crate::actors::keyholder::KeyHolderState;
use crate::actors::user_agent::session::Error;
use crate::actors::user_agent::session::UserAgentSessionError;
use crate::actors::{
evm::{
ClientSignTransaction, Generate, ListWallets, SignTransactionError as EvmSignError,
@@ -34,10 +35,12 @@ use crate::db::models::{
use crate::evm::policies::{Grant, SpecificGrant};
impl UserAgentSession {
fn take_unseal_secret(&mut self) -> Result<(EphemeralSecret, PublicKey), Error> {
fn take_unseal_secret(&self) -> Result<(EphemeralSecret, PublicKey), UserAgentSessionError> {
let UserAgentStates::WaitingForUnsealKey(unseal_context) = self.state.state() else {
error!("Received encrypted key in invalid state");
return Err(Error::internal("Invalid state for unseal encrypted key"));
return Err(UserAgentSessionError::internal(
"Invalid state for unseal encrypted key",
));
};
let ephemeral_secret = {
@@ -47,13 +50,14 @@ impl UserAgentSession {
)]
let mut secret_lock = unseal_context.secret.lock().unwrap();
let secret = secret_lock.take();
match secret {
Some(secret) => secret,
None => {
drop(secret_lock);
error!("Ephemeral secret already taken");
return Err(Error::internal("Ephemeral secret already taken"));
}
if let Some(secret) = secret {
secret
} else {
drop(secret_lock);
error!("Ephemeral secret already taken");
return Err(UserAgentSessionError::internal(
"Ephemeral secret already taken",
));
}
};
@@ -79,7 +83,7 @@ impl UserAgentSession {
});
match decryption_result {
Ok(_) => Ok(key_buffer),
Ok(()) => Ok(key_buffer),
Err(err) => {
error!(?err, "Failed to decrypt encrypted key material");
Err(())
@@ -97,7 +101,7 @@ pub enum UnsealError {
#[error("Invalid key provided for unsealing")]
InvalidKey,
#[error("Internal error during unsealing process")]
General(#[from] super::Error),
General(#[from] UserAgentSessionError),
}
#[derive(Debug, Error)]
@@ -108,7 +112,7 @@ pub enum BootstrapError {
AlreadyBootstrapped,
#[error("Internal error during bootstrapping process")]
General(#[from] super::Error),
General(#[from] UserAgentSessionError),
}
#[derive(Debug, Error)]
@@ -132,16 +136,16 @@ pub enum GrantMutationError {
#[messages]
impl UserAgentSession {
#[message]
pub async fn handle_unseal_request(
pub fn handle_unseal_request(
&mut self,
client_pubkey: x25519_dalek::PublicKey,
) -> Result<UnsealStartResponse, Error> {
client_pubkey: PublicKey,
) -> Result<UnsealStartResponse, UserAgentSessionError> {
let secret = EphemeralSecret::random();
let public_key = PublicKey::from(&secret);
self.transition(UserAgentEvents::UnsealRequest(UnsealContext {
secret: Mutex::new(Some(secret)),
client_public_key: client_pubkey,
secret: Mutex::new(Some(secret)),
}))?;
Ok(UnsealStartResponse {
@@ -158,27 +162,24 @@ impl UserAgentSession {
) -> Result<(), UnsealError> {
let (ephemeral_secret, client_public_key) = match self.take_unseal_secret() {
Ok(values) => values,
Err(Error::State) => {
Err(UserAgentSessionError::State) => {
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
return Err(UnsealError::InvalidKey);
}
Err(_err) => {
return Err(Error::internal("Failed to take unseal secret").into());
return Err(UserAgentSessionError::internal("Failed to take unseal secret").into());
}
};
let seal_key_buffer = match Self::decrypt_client_key_material(
let Ok(seal_key_buffer) = Self::decrypt_client_key_material(
ephemeral_secret,
client_public_key,
&nonce,
&ciphertext,
&associated_data,
) {
Ok(buffer) => buffer,
Err(()) => {
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
return Err(UnsealError::InvalidKey);
}
) else {
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
return Err(UnsealError::InvalidKey);
};
match self
@@ -190,12 +191,12 @@ impl UserAgentSession {
})
.await
{
Ok(_) => {
Ok(()) => {
info!("Successfully unsealed key with client-provided key");
self.transition(UserAgentEvents::ReceivedValidKey)?;
Ok(())
}
Err(SendError::HandlerError(keyholder::Error::InvalidKey)) => {
Err(SendError::HandlerError(keyholder::KeyHolderError::InvalidKey)) => {
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
Err(UnsealError::InvalidKey)
}
@@ -207,7 +208,7 @@ impl UserAgentSession {
Err(err) => {
error!(?err, "Failed to send unseal request to keyholder");
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
Err(Error::internal("Vault actor error").into())
Err(UserAgentSessionError::internal("Vault actor error").into())
}
}
}
@@ -221,25 +222,22 @@ impl UserAgentSession {
) -> Result<(), BootstrapError> {
let (ephemeral_secret, client_public_key) = match self.take_unseal_secret() {
Ok(values) => values,
Err(Error::State) => {
Err(UserAgentSessionError::State) => {
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
return Err(BootstrapError::InvalidKey);
}
Err(err) => return Err(err.into()),
};
let seal_key_buffer = match Self::decrypt_client_key_material(
let Ok(seal_key_buffer) = Self::decrypt_client_key_material(
ephemeral_secret,
client_public_key,
&nonce,
&ciphertext,
&associated_data,
) {
Ok(buffer) => buffer,
Err(()) => {
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
return Err(BootstrapError::InvalidKey);
}
) else {
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
return Err(BootstrapError::InvalidKey);
};
match self
@@ -251,12 +249,12 @@ impl UserAgentSession {
})
.await
{
Ok(_) => {
Ok(()) => {
info!("Successfully bootstrapped vault with client-provided key");
self.transition(UserAgentEvents::ReceivedValidKey)?;
Ok(())
}
Err(SendError::HandlerError(keyholder::Error::AlreadyBootstrapped)) => {
Err(SendError::HandlerError(keyholder::KeyHolderError::AlreadyBootstrapped)) => {
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
Err(BootstrapError::AlreadyBootstrapped)
}
@@ -268,7 +266,7 @@ impl UserAgentSession {
Err(err) => {
error!(?err, "Failed to send bootstrap request to keyholder");
self.transition(UserAgentEvents::ReceivedInvalidKey)?;
Err(BootstrapError::General(Error::internal(
Err(BootstrapError::General(UserAgentSessionError::internal(
"Vault actor error",
)))
}
@@ -279,14 +277,16 @@ impl UserAgentSession {
#[messages]
impl UserAgentSession {
#[message]
pub(crate) async fn handle_query_vault_state(&mut self) -> Result<KeyHolderState, Error> {
pub(crate) async fn handle_query_vault_state(
&mut self,
) -> Result<KeyHolderState, UserAgentSessionError> {
use crate::actors::keyholder::GetState;
let vault_state = match self.props.actors.key_holder.ask(GetState {}).await {
Ok(state) => state,
Err(err) => {
error!(?err, actor = "useragent", "keyholder.query.failed");
return Err(Error::internal("Vault is in broken state"));
return Err(UserAgentSessionError::internal("Vault is in broken state"));
}
};
@@ -297,26 +297,32 @@ impl UserAgentSession {
#[messages]
impl UserAgentSession {
#[message]
pub(crate) async fn handle_evm_wallet_create(&mut self) -> Result<(i32, Address), Error> {
pub(crate) async fn handle_evm_wallet_create(
&mut self,
) -> Result<(i32, Address), UserAgentSessionError> {
match self.props.actors.evm.ask(Generate {}).await {
Ok(address) => Ok(address),
Err(SendError::HandlerError(err)) => Err(Error::internal(format!(
Err(SendError::HandlerError(err)) => Err(UserAgentSessionError::internal(format!(
"EVM wallet generation failed: {err}"
))),
Err(err) => {
error!(?err, "EVM actor unreachable during wallet create");
Err(Error::internal("EVM actor unreachable"))
Err(UserAgentSessionError::internal("EVM actor unreachable"))
}
}
}
#[message]
pub(crate) async fn handle_evm_wallet_list(&mut self) -> Result<Vec<(i32, Address)>, Error> {
pub(crate) async fn handle_evm_wallet_list(
&mut self,
) -> Result<Vec<(i32, Address)>, UserAgentSessionError> {
match self.props.actors.evm.ask(ListWallets {}).await {
Ok(wallets) => Ok(wallets),
Err(err) => {
error!(?err, "EVM wallet list failed");
Err(Error::internal("Failed to list EVM wallets"))
Err(UserAgentSessionError::internal(
"Failed to list EVM wallets",
))
}
}
}
@@ -325,12 +331,14 @@ impl UserAgentSession {
#[messages]
impl UserAgentSession {
#[message]
pub(crate) async fn handle_grant_list(&mut self) -> Result<Vec<Grant<SpecificGrant>>, Error> {
pub(crate) async fn handle_grant_list(
&mut self,
) -> Result<Vec<Grant<SpecificGrant>>, UserAgentSessionError> {
match self.props.actors.evm.ask(UseragentListGrants {}).await {
Ok(grants) => Ok(grants),
Err(err) => {
error!(?err, "EVM grant list failed");
Err(Error::internal("Failed to list EVM grants"))
Err(UserAgentSessionError::internal("Failed to list EVM grants"))
}
}
}
@@ -338,8 +346,8 @@ impl UserAgentSession {
#[message]
pub(crate) async fn handle_grant_create(
&mut self,
basic: crate::evm::policies::SharedGrantSettings,
grant: crate::evm::policies::SpecificGrant,
basic: SharedGrantSettings,
grant: SpecificGrant,
) -> Result<i32, GrantMutationError> {
match self
.props
@@ -357,6 +365,7 @@ impl UserAgentSession {
}
#[message]
#[expect(clippy::unused_async, reason = "false positive")]
pub(crate) async fn handle_grant_delete(
&mut self,
grant_id: i32,
@@ -374,7 +383,7 @@ impl UserAgentSession {
// Err(GrantMutationError::Internal)
// }
// }
let _ = grant_id;
let _ = grant_id;
todo!()
}
@@ -411,7 +420,7 @@ impl UserAgentSession {
pub(crate) async fn handle_grant_evm_wallet_access(
&mut self,
entries: Vec<NewEvmWalletAccess>,
) -> Result<(), Error> {
) -> Result<(), UserAgentSessionError> {
let mut conn = self.props.db.get().await?;
conn.transaction(|conn| {
Box::pin(async move {
@@ -425,7 +434,7 @@ impl UserAgentSession {
.await?;
}
Result::<_, Error>::Ok(())
Result::<_, UserAgentSessionError>::Ok(())
})
})
.await?;
@@ -436,7 +445,7 @@ impl UserAgentSession {
pub(crate) async fn handle_revoke_evm_wallet_access(
&mut self,
entries: Vec<i32>,
) -> Result<(), Error> {
) -> Result<(), UserAgentSessionError> {
let mut conn = self.props.db.get().await?;
conn.transaction(|conn| {
Box::pin(async move {
@@ -448,7 +457,7 @@ impl UserAgentSession {
.await?;
}
Result::<_, Error>::Ok(())
Result::<_, UserAgentSessionError>::Ok(())
})
})
.await?;
@@ -458,10 +467,9 @@ impl UserAgentSession {
#[message]
pub(crate) async fn handle_list_wallet_access(
&mut self,
) -> Result<Vec<EvmWalletAccess>, Error> {
) -> Result<Vec<EvmWalletAccess>, UserAgentSessionError> {
let mut conn = self.props.db.get().await?;
use crate::db::schema::evm_wallet_access;
let access_entries = evm_wallet_access::table
let access_entries = crate::db::schema::evm_wallet_access::table
.select(EvmWalletAccess::as_select())
.load::<_>(&mut conn)
.await?;
@@ -476,14 +484,14 @@ impl UserAgentSession {
&mut self,
approved: bool,
pubkey: authn::PublicKey,
ctx: &mut Context<Self, Result<(), Error>>,
) -> Result<(), Error> {
let pending_approval = match self.pending_client_approvals.remove(&pubkey.to_bytes()) {
Some(approval) => approval,
None => {
error!("Received client connection response for unknown client");
return Err(Error::internal("Unknown client in connection response"));
}
ctx: &Context<Self, Result<(), UserAgentSessionError>>,
) -> Result<(), UserAgentSessionError> {
let Some(pending_approval) = self.pending_client_approvals.remove(&pubkey.to_bytes())
else {
error!("Received client connection response for unknown client");
return Err(UserAgentSessionError::internal(
"Unknown client in connection response",
));
};
pending_approval
@@ -495,7 +503,9 @@ impl UserAgentSession {
?err,
"Failed to send client approval response to controller"
);
Error::internal("Failed to send client approval response to controller")
UserAgentSessionError::internal(
"Failed to send client approval response to controller",
)
})?;
ctx.actor_ref().unlink(&pending_approval.controller).await;
@@ -506,7 +516,7 @@ impl UserAgentSession {
#[message]
pub(crate) async fn handle_sdk_client_list(
&mut self,
) -> Result<Vec<(ProgramClient, ProgramClientMetadata)>, Error> {
) -> Result<Vec<(ProgramClient, ProgramClientMetadata)>, UserAgentSessionError> {
use crate::db::schema::{client_metadata, program_client};
let mut conn = self.props.db.get().await?;

View File

@@ -19,8 +19,6 @@ smlang::statemachine!(
pub struct DummyContext;
impl UserAgentStateMachineContext for DummyContext {
#[allow(missing_docs)]
#[allow(clippy::unused_unit)]
fn generate_temp_keypair(&mut self, event_data: UnsealContext) -> Result<UnsealContext, ()> {
Ok(event_data)
}