fix(server::user_agent): useragents now self-sign themselves on bootstrap
This commit is contained in:
@@ -8,7 +8,10 @@ use kameo::{
|
||||
|
||||
use crate::{
|
||||
actors::flow_coordinator::ApprovalError,
|
||||
peers::{client::ClientProfile, user_agent::{UserAgentSession, session::BeginNewClientApproval}},
|
||||
peers::{
|
||||
client::ClientProfile,
|
||||
user_agent::{UserAgentSession, session::BeginNewClientApproval},
|
||||
},
|
||||
};
|
||||
|
||||
pub struct Args {
|
||||
|
||||
@@ -9,7 +9,13 @@ use kameo::{
|
||||
};
|
||||
use tracing::info;
|
||||
|
||||
use crate::{actors::flow_coordinator::client_connect_approval::ClientApprovalController, peers::{client::{ClientProfile, session::ClientSession}, user_agent::UserAgentSession}};
|
||||
use crate::{
|
||||
actors::flow_coordinator::client_connect_approval::ClientApprovalController,
|
||||
peers::{
|
||||
client::{ClientProfile, session::ClientSession},
|
||||
user_agent::UserAgentSession,
|
||||
},
|
||||
};
|
||||
|
||||
pub mod client_connect_approval;
|
||||
|
||||
|
||||
@@ -4,8 +4,7 @@ use thiserror::Error;
|
||||
|
||||
use crate::{
|
||||
actors::{
|
||||
bootstrap::Bootstrapper, evm::EvmActor, flow_coordinator::FlowCoordinator,
|
||||
vault::Vault,
|
||||
bootstrap::Bootstrapper, evm::EvmActor, flow_coordinator::FlowCoordinator, vault::Vault,
|
||||
},
|
||||
db,
|
||||
};
|
||||
|
||||
@@ -147,7 +147,7 @@ impl Vault {
|
||||
Ok(nonce)
|
||||
}
|
||||
|
||||
fn expect_unsealed<'a>(state: &'a mut State) -> Result<&'a mut Unsealed, Error> {
|
||||
fn expect_unsealed(state: &mut State) -> Result<&mut Unsealed, Error> {
|
||||
match state {
|
||||
State::Unsealed(unsealed) => Ok(unsealed),
|
||||
State::Unbootstrapped => Err(Error::NotBootstrapped),
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use crate::{actors::vault, crypto::integrity::hashing::Hashable};
|
||||
use arbiter_crypto::safecell::SafeCellHandle as _;
|
||||
use hmac::{Hmac, Mac as _};
|
||||
use crate::{
|
||||
actors::vault::{self, GetState},
|
||||
crypto::integrity::hashing::Hashable,
|
||||
};
|
||||
use hmac::Hmac;
|
||||
use sha2::Sha256;
|
||||
|
||||
use diesel::{ExpressionMethods as _, QueryDsl, dsl::insert_into, sqlite::Sqlite};
|
||||
@@ -199,6 +201,11 @@ pub async fn verify_entity<E: Integrable>(
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn is_signing_available(vault: &ActorRef<Vault>) -> Result<bool, Error> {
|
||||
let state = vault.ask(GetState).await.map_err(|_| Error::VaultSend)?;
|
||||
Ok(matches!(state, vault::VaultState::Unsealed))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use diesel::{ExpressionMethods as _, QueryDsl};
|
||||
@@ -208,7 +215,10 @@ mod tests {
|
||||
use sha2::Digest;
|
||||
|
||||
use crate::{
|
||||
actors::{GlobalActors, vault::{Bootstrap, Vault}},
|
||||
actors::{
|
||||
GlobalActors,
|
||||
vault::{Bootstrap, Vault},
|
||||
},
|
||||
db::{self, schema},
|
||||
};
|
||||
use arbiter_crypto::safecell::{SafeCell, SafeCellHandle as _};
|
||||
|
||||
@@ -10,8 +10,8 @@ use tonic::Status;
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::{
|
||||
peers::client::{ClientConnection, session::ClientSession},
|
||||
grpc::request_tracker::RequestTracker,
|
||||
peers::client::{ClientConnection, session::ClientSession},
|
||||
};
|
||||
|
||||
mod auth;
|
||||
|
||||
@@ -22,8 +22,8 @@ use tonic::Status;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::{
|
||||
peers::client::{self, ClientConnection, auth},
|
||||
grpc::request_tracker::RequestTracker,
|
||||
peers::client::{self, ClientConnection, auth},
|
||||
};
|
||||
|
||||
pub struct AuthTransportAdapter<'a> {
|
||||
|
||||
@@ -16,11 +16,11 @@ use tonic::Status;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::{
|
||||
peers::client::session::{ClientSession, HandleSignTransaction, SignTransactionRpcError},
|
||||
grpc::{
|
||||
Convert, TryConvert,
|
||||
common::inbound::{RawEvmAddress, RawEvmTransaction},
|
||||
},
|
||||
peers::client::session::{ClientSession, HandleSignTransaction, SignTransactionRpcError},
|
||||
};
|
||||
|
||||
fn wrap_response(payload: EvmResponsePayload) -> ClientResponsePayload {
|
||||
|
||||
@@ -13,8 +13,8 @@ use tonic::Status;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::{
|
||||
peers::client::session::{ClientSession, Error, HandleQueryVaultState},
|
||||
actors::vault::VaultState,
|
||||
peers::client::session::{ClientSession, Error, HandleQueryVaultState},
|
||||
};
|
||||
|
||||
pub(super) async fn dispatch(
|
||||
|
||||
@@ -10,8 +10,8 @@ use tonic::{Request, Response, Status, async_trait};
|
||||
use tracing::info;
|
||||
|
||||
use crate::{
|
||||
peers::{client::ClientConnection, user_agent::UserAgentConnection},
|
||||
grpc::user_agent::start,
|
||||
peers::{client::ClientConnection, user_agent::UserAgentConnection},
|
||||
};
|
||||
|
||||
mod request_tracker;
|
||||
|
||||
@@ -14,8 +14,8 @@ use tonic::Status;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use crate::{
|
||||
peers::user_agent::{OutOfBand, UserAgentConnection, UserAgentSession},
|
||||
grpc::request_tracker::RequestTracker,
|
||||
peers::user_agent::{OutOfBand, UserAgentConnection, UserAgentSession},
|
||||
};
|
||||
|
||||
mod auth;
|
||||
@@ -124,7 +124,7 @@ pub async fn start(
|
||||
) {
|
||||
let mut request_tracker = RequestTracker::default();
|
||||
|
||||
let pubkey = match auth::start(&mut conn, &mut bi, &mut request_tracker).await {
|
||||
let (id, pubkey) = match auth::start(&mut conn, &mut bi, &mut request_tracker).await {
|
||||
Ok(pubkey) => pubkey,
|
||||
Err(e) => {
|
||||
warn!(error = ?e, "Authentication failed");
|
||||
@@ -132,13 +132,19 @@ pub async fn start(
|
||||
}
|
||||
};
|
||||
|
||||
info!(?pubkey, "User authenticated successfully");
|
||||
|
||||
let (oob_sender, oob_receiver) = mpsc::channel(16);
|
||||
let oob_adapter = OutOfBandAdapter(oob_sender);
|
||||
|
||||
let actor = UserAgentSession::spawn(UserAgentSession::new(conn, Box::new(oob_adapter)));
|
||||
let actor = UserAgentSession::spawn(UserAgentSession::new(
|
||||
conn,
|
||||
id,
|
||||
pubkey,
|
||||
Box::new(oob_adapter),
|
||||
));
|
||||
let actor_for_cleanup = actor.clone();
|
||||
|
||||
info!(?pubkey, "User authenticated successfully");
|
||||
dispatch_loop(bi, actor, oob_receiver, request_tracker).await;
|
||||
actor_for_cleanup.kill();
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ use tonic::Status;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::{
|
||||
peers::user_agent::{UserAgentConnection, auth},
|
||||
grpc::request_tracker::RequestTracker,
|
||||
peers::user_agent::{UserAgentConnection, auth},
|
||||
};
|
||||
|
||||
pub struct AuthTransportAdapter<'a> {
|
||||
@@ -140,7 +140,6 @@ impl Receiver<auth::Inbound> for AuthTransportAdapter<'_> {
|
||||
AuthRequestPayload::ChallengeRequest(ProtoAuthChallengeRequest {
|
||||
pubkey,
|
||||
bootstrap_token,
|
||||
key_type: _,
|
||||
}) => {
|
||||
let Ok(pubkey) = authn::PublicKey::try_from(pubkey.as_slice()) else {
|
||||
warn!(
|
||||
@@ -168,7 +167,7 @@ pub async fn start(
|
||||
conn: &mut UserAgentConnection,
|
||||
bi: &mut GrpcBi<UserAgentRequest, UserAgentResponse>,
|
||||
request_tracker: &mut RequestTracker,
|
||||
) -> Result<authn::PublicKey, auth::Error> {
|
||||
) -> Result<(i32, authn::PublicKey), auth::Error> {
|
||||
let transport = AuthTransportAdapter::new(bi, request_tracker);
|
||||
auth::authenticate(conn, transport).await
|
||||
}
|
||||
|
||||
@@ -23,6 +23,10 @@ use tonic::Status;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::{
|
||||
grpc::{
|
||||
Convert, TryConvert,
|
||||
common::inbound::{RawEvmAddress, RawEvmTransaction},
|
||||
},
|
||||
peers::user_agent::{
|
||||
UserAgentSession,
|
||||
session::connection::{
|
||||
@@ -31,10 +35,6 @@ use crate::{
|
||||
SignTransactionError as SessionSignTransactionError,
|
||||
},
|
||||
},
|
||||
grpc::{
|
||||
Convert, TryConvert,
|
||||
common::inbound::{RawEvmAddress, RawEvmTransaction},
|
||||
},
|
||||
};
|
||||
|
||||
fn wrap_evm_response(payload: EvmResponsePayload) -> UserAgentResponsePayload {
|
||||
|
||||
@@ -21,6 +21,8 @@ use tonic::Status;
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::{
|
||||
db::models::NewEvmWalletAccess,
|
||||
grpc::Convert,
|
||||
peers::user_agent::{
|
||||
OutOfBand, UserAgentSession,
|
||||
session::connection::{
|
||||
@@ -28,8 +30,6 @@ use crate::{
|
||||
HandleRevokeEvmWalletAccess, HandleSdkClientList,
|
||||
},
|
||||
},
|
||||
db::models::NewEvmWalletAccess,
|
||||
grpc::Convert,
|
||||
};
|
||||
|
||||
fn wrap_sdk_client_response(payload: SdkClientResponsePayload) -> UserAgentResponsePayload {
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
use crate::context::ServerContext;
|
||||
|
||||
pub mod actors;
|
||||
pub mod peers;
|
||||
pub mod context;
|
||||
pub mod crypto;
|
||||
pub mod db;
|
||||
pub mod evm;
|
||||
pub mod grpc;
|
||||
pub mod peers;
|
||||
pub mod utils;
|
||||
|
||||
pub struct Server {
|
||||
|
||||
@@ -14,7 +14,9 @@ use tracing::error;
|
||||
|
||||
use crate::{
|
||||
actors::{
|
||||
GlobalActors, flow_coordinator::{self, RequestClientApproval}, vault::Vault
|
||||
GlobalActors,
|
||||
flow_coordinator::{self, RequestClientApproval},
|
||||
vault::Vault,
|
||||
},
|
||||
crypto::integrity::{self, AttestationStatus},
|
||||
db::{
|
||||
@@ -187,10 +189,7 @@ async fn create_nonce(
|
||||
.await
|
||||
}
|
||||
|
||||
async fn approve_new_client(
|
||||
actors: &GlobalActors,
|
||||
profile: ClientProfile,
|
||||
) -> Result<(), Error> {
|
||||
async fn approve_new_client(actors: &GlobalActors, profile: ClientProfile) -> Result<(), Error> {
|
||||
let result = actors
|
||||
.flow_coordinator
|
||||
.ask(RequestClientApproval { client: profile })
|
||||
|
||||
@@ -6,7 +6,8 @@ use tracing::{error, info};
|
||||
use crate::{
|
||||
actors::GlobalActors,
|
||||
crypto::integrity::{Integrable, hashing::Hashable},
|
||||
db, peers::client::session::ClientSession,
|
||||
db,
|
||||
peers::client::session::ClientSession,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
pub mod client;
|
||||
pub mod user_agent;
|
||||
pub mod client;
|
||||
@@ -69,7 +69,7 @@ fn parse_auth_event(payload: Inbound) -> AuthEvents {
|
||||
pub async fn authenticate<T>(
|
||||
props: &mut UserAgentConnection,
|
||||
transport: T,
|
||||
) -> Result<authn::PublicKey, Error>
|
||||
) -> Result<(i32, authn::PublicKey), Error>
|
||||
where
|
||||
T: Bi<Inbound, Result<Outbound, Error>> + Send,
|
||||
{
|
||||
@@ -82,7 +82,7 @@ where
|
||||
};
|
||||
|
||||
match state.process_event(parse_auth_event(payload)).await {
|
||||
Ok(AuthStates::AuthOk(key)) => return Ok(key.clone()),
|
||||
Ok(AuthStates::AuthOk(result)) => return Ok((result.id, result.pubkey.clone())),
|
||||
Err(AuthError::ActionFailed(err)) => {
|
||||
error!(?err, "State machine action failed");
|
||||
return Err(err);
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
use super::super::{UserAgentConnection, UserAgentCredentials};
|
||||
use arbiter_crypto::authn::{self, USERAGENT_CONTEXT};
|
||||
use arbiter_proto::transport::Bi;
|
||||
use diesel::{ExpressionMethods as _, OptionalExtension as _, QueryDsl, update};
|
||||
use diesel_async::{AsyncConnection, RunQueryDsl};
|
||||
use kameo::actor::ActorRef;
|
||||
use tracing::error;
|
||||
use super::super::{UserAgentCredentials, UserAgentConnection};
|
||||
|
||||
use super::Error;
|
||||
use crate::peers::user_agent::auth::Outbound;
|
||||
use crate::{
|
||||
actors::{
|
||||
bootstrap::ConsumeToken,
|
||||
vault::Vault,
|
||||
},
|
||||
actors::{bootstrap::ConsumeToken, vault::Vault},
|
||||
crypto::integrity,
|
||||
db::{DatabasePool, schema::useragent_client},
|
||||
};
|
||||
@@ -27,6 +24,7 @@ pub struct BootstrapAuthRequest {
|
||||
}
|
||||
|
||||
pub struct ChallengeContext {
|
||||
pub id: i32,
|
||||
pub challenge_nonce: i32,
|
||||
pub key: authn::PublicKey,
|
||||
}
|
||||
@@ -35,13 +33,18 @@ pub struct ChallengeSolution {
|
||||
pub solution: Vec<u8>,
|
||||
}
|
||||
|
||||
pub struct AuthOk {
|
||||
pub id: i32,
|
||||
pub pubkey: authn::PublicKey,
|
||||
}
|
||||
|
||||
smlang::statemachine!(
|
||||
name: Auth,
|
||||
custom_error: true,
|
||||
transitions: {
|
||||
*Init + AuthRequest(ChallengeRequest) / async prepare_challenge = SentChallenge(ChallengeContext),
|
||||
Init + BootstrapAuthRequest(BootstrapAuthRequest) / async verify_bootstrap_token = AuthOk(authn::PublicKey),
|
||||
SentChallenge(ChallengeContext) + ReceivedSolution(ChallengeSolution) / async verify_solution = AuthOk(authn::PublicKey),
|
||||
Init + BootstrapAuthRequest(BootstrapAuthRequest) / async verify_bootstrap_token = AuthOk(AuthOk),
|
||||
SentChallenge(ChallengeContext) + ReceivedSolution(ChallengeSolution) / async verify_solution = AuthOk(AuthOk),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -110,12 +113,12 @@ async fn create_nonce(
|
||||
db: &DatabasePool,
|
||||
vault: &ActorRef<Vault>,
|
||||
pubkey: &authn::PublicKey,
|
||||
) -> Result<i32, Error> {
|
||||
) -> Result<(i32, i32), Error> {
|
||||
let mut db_conn = db.get().await.map_err(|e| {
|
||||
error!(error = ?e, "Database pool error");
|
||||
Error::internal("Database unavailable")
|
||||
})?;
|
||||
let new_nonce = db_conn
|
||||
let (id, new_nonce) = db_conn
|
||||
.exclusive_transaction(|conn| {
|
||||
Box::pin(async move {
|
||||
let (id, new_nonce): (i32, i32) = update(useragent_client::table)
|
||||
@@ -144,59 +147,36 @@ async fn create_nonce(
|
||||
Error::internal("Database error")
|
||||
})?;
|
||||
|
||||
Result::<_, Error>::Ok(new_nonce)
|
||||
Result::<_, Error>::Ok((id, new_nonce))
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
Ok(new_nonce)
|
||||
Ok((id, new_nonce))
|
||||
}
|
||||
|
||||
async fn register_key(
|
||||
db: &DatabasePool,
|
||||
vault: &ActorRef<Vault>,
|
||||
pubkey: &authn::PublicKey,
|
||||
) -> Result<(), Error> {
|
||||
async fn register_key(db: &DatabasePool, pubkey: &authn::PublicKey) -> Result<i32, Error> {
|
||||
let pubkey_bytes = pubkey.to_bytes();
|
||||
let mut conn = db.get().await.map_err(|e| {
|
||||
error!(error = ?e, "Database pool error");
|
||||
Error::internal("Database unavailable")
|
||||
})?;
|
||||
|
||||
conn.transaction(|conn| {
|
||||
Box::pin(async move {
|
||||
const NONCE_START: i32 = 1;
|
||||
const NONCE_START: i32 = 1;
|
||||
|
||||
let id: i32 = diesel::insert_into(useragent_client::table)
|
||||
.values((
|
||||
useragent_client::public_key.eq(pubkey_bytes),
|
||||
useragent_client::nonce.eq(NONCE_START),
|
||||
))
|
||||
.returning(useragent_client::id)
|
||||
.get_result(conn)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!(error = ?e, "Database error");
|
||||
Error::internal("Database operation failed")
|
||||
})?;
|
||||
let id: i32 = diesel::insert_into(useragent_client::table)
|
||||
.values((
|
||||
useragent_client::public_key.eq(pubkey_bytes),
|
||||
useragent_client::nonce.eq(NONCE_START),
|
||||
))
|
||||
.returning(useragent_client::id)
|
||||
.get_result(&mut conn)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!(error = ?e, "Database error");
|
||||
Error::internal("Database operation failed")
|
||||
})?;
|
||||
|
||||
let entity = UserAgentCredentials {
|
||||
pubkey: pubkey.clone(),
|
||||
nonce: NONCE_START,
|
||||
};
|
||||
|
||||
integrity::sign_entity(conn, vault, &entity, id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!(error = ?e, "Failed to sign integrity tag for new user-agent key");
|
||||
Error::internal("Failed to register public key")
|
||||
})?;
|
||||
|
||||
Result::<_, Error>::Ok(())
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
pub struct AuthContext<'a, T> {
|
||||
@@ -222,7 +202,7 @@ where
|
||||
) -> Result<ChallengeContext, Self::Error> {
|
||||
verify_integrity(&self.conn.db, &self.conn.actors.vault, &pubkey).await?;
|
||||
|
||||
let nonce = create_nonce(&self.conn.db, &self.conn.actors.vault, &pubkey).await?;
|
||||
let (id, nonce) = create_nonce(&self.conn.db, &self.conn.actors.vault, &pubkey).await?;
|
||||
|
||||
self.transport
|
||||
.send(Ok(Outbound::AuthChallenge { nonce }))
|
||||
@@ -233,6 +213,7 @@ where
|
||||
})?;
|
||||
|
||||
Ok(ChallengeContext {
|
||||
id,
|
||||
challenge_nonce: nonce,
|
||||
key: pubkey,
|
||||
})
|
||||
@@ -243,7 +224,7 @@ where
|
||||
async fn verify_bootstrap_token(
|
||||
&mut self,
|
||||
BootstrapAuthRequest { pubkey, token }: BootstrapAuthRequest,
|
||||
) -> Result<authn::PublicKey, Self::Error> {
|
||||
) -> Result<AuthOk, Self::Error> {
|
||||
let token_ok: bool = self
|
||||
.conn
|
||||
.actors
|
||||
@@ -264,12 +245,12 @@ where
|
||||
|
||||
match token_ok {
|
||||
true => {
|
||||
register_key(&self.conn.db, &self.conn.actors.vault, &pubkey).await?;
|
||||
let id = register_key(&self.conn.db, &pubkey).await?;
|
||||
self.transport
|
||||
.send(Ok(Outbound::AuthSuccess))
|
||||
.await
|
||||
.map_err(|_| Error::Transport)?;
|
||||
Ok(pubkey)
|
||||
Ok(AuthOk { id, pubkey })
|
||||
}
|
||||
false => {
|
||||
error!("Invalid bootstrap token provided");
|
||||
@@ -287,11 +268,12 @@ where
|
||||
async fn verify_solution(
|
||||
&mut self,
|
||||
ChallengeContext {
|
||||
id,
|
||||
challenge_nonce,
|
||||
key,
|
||||
}: &ChallengeContext,
|
||||
ChallengeSolution { solution }: ChallengeSolution,
|
||||
) -> Result<authn::PublicKey, Self::Error> {
|
||||
) -> Result<AuthOk, Self::Error> {
|
||||
let signature = authn::Signature::try_from(solution.as_slice()).map_err(|_| {
|
||||
error!("Failed to decode signature in challenge solution");
|
||||
Error::InvalidChallengeSolution
|
||||
@@ -305,7 +287,7 @@ where
|
||||
.send(Ok(Outbound::AuthSuccess))
|
||||
.await
|
||||
.map_err(|_| Error::Transport)?;
|
||||
Ok(key.clone())
|
||||
Ok(AuthOk { id: *id, pubkey: key.clone() })
|
||||
}
|
||||
false => {
|
||||
self.transport
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use crate::{
|
||||
actors::GlobalActors,
|
||||
crypto::integrity::Integrable,
|
||||
db, peers::client::ClientProfile,
|
||||
actors::GlobalActors, crypto::integrity::Integrable, db, peers::client::ClientProfile,
|
||||
};
|
||||
use arbiter_crypto::authn;
|
||||
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
use arbiter_crypto::authn;
|
||||
use diesel::{ExpressionMethods, QueryDsl};
|
||||
use diesel_async::{AsyncConnection, RunQueryDsl};
|
||||
use kameo_actors::message_bus::Register;
|
||||
|
||||
use std::{borrow::Cow, collections::HashMap};
|
||||
|
||||
use arbiter_proto::transport::Sender;
|
||||
use async_trait::async_trait;
|
||||
use kameo::{Actor, actor::ActorRef, messages};
|
||||
use kameo::{Actor, actor::ActorRef, messages, prelude::Message};
|
||||
use thiserror::Error;
|
||||
use tracing::error;
|
||||
|
||||
use crate::{actors::flow_coordinator::{RegisterUserAgent, client_connect_approval::ClientApprovalController}, peers::client::ClientProfile};
|
||||
use crate::{
|
||||
actors::{
|
||||
flow_coordinator::{RegisterUserAgent, client_connect_approval::ClientApprovalController},
|
||||
vault::events,
|
||||
}, crypto::integrity, db::schema::useragent_client, peers::{client::ClientProfile, user_agent::UserAgentCredentials}
|
||||
};
|
||||
mod state;
|
||||
use state::{DummyContext, UserAgentEvents, UserAgentStateMachine};
|
||||
|
||||
@@ -50,6 +58,8 @@ pub struct PendingClientApproval {
|
||||
}
|
||||
|
||||
pub struct UserAgentSession {
|
||||
id: i32,
|
||||
pubkey: authn::PublicKey,
|
||||
props: UserAgentConnection,
|
||||
state: UserAgentStateMachine<DummyContext>,
|
||||
sender: Box<dyn Sender<OutOfBand>>,
|
||||
@@ -60,31 +70,22 @@ pub struct UserAgentSession {
|
||||
pub mod connection;
|
||||
|
||||
impl UserAgentSession {
|
||||
pub(crate) fn new(props: UserAgentConnection, sender: Box<dyn Sender<OutOfBand>>) -> Self {
|
||||
pub(crate) fn new(
|
||||
props: UserAgentConnection,
|
||||
id: i32,
|
||||
pubkey: authn::PublicKey,
|
||||
sender: Box<dyn Sender<OutOfBand>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
props,
|
||||
pubkey,
|
||||
state: UserAgentStateMachine::new(DummyContext),
|
||||
sender,
|
||||
pending_client_approvals: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_test(db: crate::db::DatabasePool, actors: crate::actors::GlobalActors) -> Self {
|
||||
struct DummySender;
|
||||
|
||||
#[async_trait]
|
||||
impl Sender<OutOfBand> for DummySender {
|
||||
async fn send(
|
||||
&mut self,
|
||||
_item: OutOfBand,
|
||||
) -> Result<(), arbiter_proto::transport::Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Self::new(UserAgentConnection::new(db, actors), Box::new(DummySender))
|
||||
}
|
||||
|
||||
fn transition(&mut self, event: UserAgentEvents) -> Result<(), Error> {
|
||||
self.state.process_event(event).map_err(|e| {
|
||||
error!(?e, "State transition failed");
|
||||
@@ -127,6 +128,61 @@ impl UserAgentSession {
|
||||
}
|
||||
}
|
||||
|
||||
impl Message<events::VaultBootstrapped> for UserAgentSession {
|
||||
type Reply = Result<(), Error>;
|
||||
|
||||
async fn handle(
|
||||
&mut self,
|
||||
_: events::VaultBootstrapped,
|
||||
ctx: &mut kameo::prelude::Context<Self, Self::Reply>,
|
||||
) -> Self::Reply {
|
||||
let Ok(mut conn) = self.props.db.get().await else {
|
||||
error!("Failed to get database connection for vault bootstrapped event");
|
||||
ctx.stop();
|
||||
return Err(Error::internal("Failed to get database connection"));
|
||||
};
|
||||
|
||||
|
||||
let result = conn.exclusive_transaction(|conn| {
|
||||
Box::pin(async {
|
||||
let nonce: i32 = useragent_client::table
|
||||
.filter(useragent_client::id.eq(self.id))
|
||||
.select(useragent_client::nonce)
|
||||
.first::<i32>(conn)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!(?e, "Failed to get nonce for useragent bootstrapping");
|
||||
Error::internal("Failed to sign user agent credentials")
|
||||
})?;
|
||||
|
||||
let entity = UserAgentCredentials {
|
||||
pubkey: self.pubkey.clone(),
|
||||
nonce,
|
||||
};
|
||||
|
||||
integrity::sign_entity(conn, &self.props.actors.vault, &entity, self.id)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!(?e, "Failed to sign user agent credentials during vault bootstrapping");
|
||||
Error::internal("Failed to sign user agent credentials")
|
||||
})?;
|
||||
|
||||
Result::<_, Error>::Ok(())
|
||||
})
|
||||
}).await;
|
||||
|
||||
match result {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
error!(?err, "Error during vault bootstrapping");
|
||||
ctx.stop();
|
||||
Err(err)
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
impl Actor for UserAgentSession {
|
||||
type Args = Self;
|
||||
|
||||
@@ -136,6 +192,21 @@ impl Actor for UserAgentSession {
|
||||
args: Self::Args,
|
||||
this: kameo::prelude::ActorRef<Self>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
args.props
|
||||
.actors
|
||||
.events
|
||||
.tell(Register(
|
||||
this.clone().recipient::<events::VaultBootstrapped>(),
|
||||
))
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!(
|
||||
?err,
|
||||
"Failed to register user agent connection with event bus"
|
||||
);
|
||||
Error::internal("Failed to register user agent connection with event bus")
|
||||
})?;
|
||||
|
||||
args.props
|
||||
.actors
|
||||
.flow_coordinator
|
||||
|
||||
@@ -14,7 +14,7 @@ use kameo::prelude::Context;
|
||||
use tracing::{error, info};
|
||||
use x25519_dalek::{EphemeralSecret, PublicKey};
|
||||
|
||||
use crate::{actors::vault::VaultState, peers::user_agent::session::state::{UnsealContext, UserAgentEvents}};
|
||||
use crate::actors::flow_coordinator::client_connect_approval::ClientApprovalAnswer;
|
||||
use crate::actors::{
|
||||
evm::{
|
||||
ClientSignTransaction, Generate, ListWallets, SignTransactionError as EvmSignError,
|
||||
@@ -27,10 +27,11 @@ use crate::db::models::{
|
||||
};
|
||||
use crate::evm::policies::{Grant, SpecificGrant};
|
||||
use crate::{
|
||||
actors::flow_coordinator::client_connect_approval::ClientApprovalAnswer,
|
||||
actors::vault::VaultState,
|
||||
peers::user_agent::session::state::{UnsealContext, UserAgentEvents},
|
||||
};
|
||||
|
||||
use super::{UserAgentSession, state, Error};
|
||||
use super::{Error, UserAgentSession, state};
|
||||
|
||||
impl UserAgentSession {
|
||||
fn take_unseal_secret(&mut self) -> Result<(EphemeralSecret, PublicKey), Error> {
|
||||
|
||||
Reference in New Issue
Block a user