|
|
|
|
@@ -1,5 +1,5 @@
|
|
|
|
|
// file: crates/ksp-app-wallet-desk/src/app_state.rs
|
|
|
|
|
// version: 12
|
|
|
|
|
// version: 13
|
|
|
|
|
|
|
|
|
|
//! Shared backend state owned by the Wallet Desk Tauri application.
|
|
|
|
|
|
|
|
|
|
@@ -126,7 +126,7 @@ impl AppState {
|
|
|
|
|
effective_wallets_directory_created_on_startup: self.wallet_config_startup.effective_directory_created_on_startup(),
|
|
|
|
|
fallback_logging_active: runtime.fallback_active,
|
|
|
|
|
root_wallets_directory_created_on_startup: self.wallet_config_startup.root_directory_created_on_startup(),
|
|
|
|
|
shell_phase: "pre.009-owner-metadata".to_owned(),
|
|
|
|
|
shell_phase: "pre.010-credential-rotation".to_owned(),
|
|
|
|
|
startup_diagnostic: runtime.startup_diagnostic.clone(),
|
|
|
|
|
transport_available_endpoint_count,
|
|
|
|
|
transport_clusters: self.transport_runtime.clusters(),
|
|
|
|
|
@@ -492,7 +492,7 @@ impl AppState {
|
|
|
|
|
*session = crate::WalletSession::OwnerOperation { wallet_id, path, pubkey, view_enabled };
|
|
|
|
|
return std::result::Result::Err(ksp_core_lib::Error::new(
|
|
|
|
|
crate::ERROR_CODE_WALLET_SESSION_INVALID,
|
|
|
|
|
"Wallet OWNER metadata operation is already in progress",
|
|
|
|
|
"Wallet OWNER privileged operation is already in progress",
|
|
|
|
|
));
|
|
|
|
|
},
|
|
|
|
|
crate::WalletSession::View { wallet_id, path, wallet, .. } => {
|
|
|
|
|
@@ -663,19 +663,19 @@ impl AppState {
|
|
|
|
|
|
|
|
|
|
/// Updates or clears the protected alias through the current OWNER handle.
|
|
|
|
|
pub(crate) async fn update_wallet_alias(&self, request: crate::WalletAliasUpdateRequestDto) -> ksp_core_lib::Result<crate::WalletAuthorizedDto> {
|
|
|
|
|
let context = self.begin_owner_metadata_operation("alias_update");
|
|
|
|
|
let context = self.begin_owner_operation("alias_update");
|
|
|
|
|
let mut context = match context {
|
|
|
|
|
std::result::Result::Ok(value) => value,
|
|
|
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
|
|
|
};
|
|
|
|
|
let alias = normalize_optional_text(request.alias);
|
|
|
|
|
let result = context.owner.update_alias(context.path.as_path(), alias).await;
|
|
|
|
|
return self.finish_owner_metadata_operation(context, result).await;
|
|
|
|
|
return self.finish_owner_operation(context, result).await;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Appends one protected note through the current OWNER handle.
|
|
|
|
|
pub(crate) async fn add_wallet_note(&self, request: crate::WalletNoteAddRequestDto) -> ksp_core_lib::Result<crate::WalletAuthorizedDto> {
|
|
|
|
|
let context = self.begin_owner_metadata_operation("note_add");
|
|
|
|
|
let context = self.begin_owner_operation("note_add");
|
|
|
|
|
let mut context = match context {
|
|
|
|
|
std::result::Result::Ok(value) => value,
|
|
|
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
|
|
|
@@ -685,32 +685,63 @@ impl AppState {
|
|
|
|
|
std::result::Result::Ok(_) => std::result::Result::Ok(()),
|
|
|
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
|
|
|
};
|
|
|
|
|
return self.finish_owner_metadata_operation(context, result).await;
|
|
|
|
|
return self.finish_owner_operation(context, result).await;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Updates one protected note through the current OWNER handle.
|
|
|
|
|
pub(crate) async fn update_wallet_note(&self, request: crate::WalletNoteUpdateRequestDto) -> ksp_core_lib::Result<crate::WalletAuthorizedDto> {
|
|
|
|
|
let context = self.begin_owner_metadata_operation("note_update");
|
|
|
|
|
let context = self.begin_owner_operation("note_update");
|
|
|
|
|
let mut context = match context {
|
|
|
|
|
std::result::Result::Ok(value) => value,
|
|
|
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
|
|
|
};
|
|
|
|
|
let result = context.owner.update_note(context.path.as_path(), request.note_id.as_str(), request.text).await;
|
|
|
|
|
return self.finish_owner_metadata_operation(context, result).await;
|
|
|
|
|
return self.finish_owner_operation(context, result).await;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Deletes one protected note through the current OWNER handle.
|
|
|
|
|
pub(crate) async fn delete_wallet_note(&self, request: crate::WalletNoteDeleteRequestDto) -> ksp_core_lib::Result<crate::WalletAuthorizedDto> {
|
|
|
|
|
let context = self.begin_owner_metadata_operation("note_delete");
|
|
|
|
|
let context = self.begin_owner_operation("note_delete");
|
|
|
|
|
let mut context = match context {
|
|
|
|
|
std::result::Result::Ok(value) => value,
|
|
|
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
|
|
|
};
|
|
|
|
|
let result = context.owner.delete_note(context.path.as_path(), request.note_id.as_str()).await;
|
|
|
|
|
return self.finish_owner_metadata_operation(context, result).await;
|
|
|
|
|
return self.finish_owner_operation(context, result).await;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn begin_owner_metadata_operation(&self, operation: &'static str) -> ksp_core_lib::Result<OwnerMetadataContext> {
|
|
|
|
|
/// Rotates the OWNER password while preserving the current authorized identity and metadata projection.
|
|
|
|
|
pub(crate) async fn rotate_owner_password(&self, request: crate::WalletPasswordRotationRequestDto) -> ksp_core_lib::Result<crate::WalletAuthorizedDto> {
|
|
|
|
|
let context = self.begin_owner_operation("owner_password_rotate");
|
|
|
|
|
let mut context = match context {
|
|
|
|
|
std::result::Result::Ok(value) => value,
|
|
|
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
|
|
|
};
|
|
|
|
|
let password = ksp_wallet_lib::OwnerPassword::new(request.password);
|
|
|
|
|
let result = context.owner.rotate_owner_password(context.path.as_path(), password).await;
|
|
|
|
|
return self.finish_owner_operation(context, result).await;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Rotates the enabled VIEW password from the current OWNER session without requiring the previous VIEW password.
|
|
|
|
|
pub(crate) async fn rotate_view_password(&self, request: crate::WalletPasswordRotationRequestDto) -> ksp_core_lib::Result<crate::WalletAuthorizedDto> {
|
|
|
|
|
let context = self.begin_owner_operation("view_password_rotate");
|
|
|
|
|
let mut context = match context {
|
|
|
|
|
std::result::Result::Ok(value) => value,
|
|
|
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
|
|
|
};
|
|
|
|
|
if !context.view_enabled {
|
|
|
|
|
self.restore_owner_after_operation_failure(context);
|
|
|
|
|
return std::result::Result::Err(ksp_core_lib::Error::new(
|
|
|
|
|
crate::ERROR_CODE_WALLET_VIEW_DISABLED,
|
|
|
|
|
"Wallet VIEW capability is disabled and cannot rotate a VIEW password",
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
let password = ksp_wallet_lib::ViewPassword::new(request.password);
|
|
|
|
|
let result = context.owner.rotate_view_password(context.path.as_path(), password).await;
|
|
|
|
|
return self.finish_owner_operation(context, result).await;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn begin_owner_operation(&self, operation: &'static str) -> ksp_core_lib::Result<OwnerOperationContext> {
|
|
|
|
|
let session = self.wallet_session.lock();
|
|
|
|
|
let mut session = match session {
|
|
|
|
|
std::result::Result::Ok(value) => value,
|
|
|
|
|
@@ -723,35 +754,35 @@ impl AppState {
|
|
|
|
|
*session = other;
|
|
|
|
|
return std::result::Result::Err(ksp_core_lib::Error::new(
|
|
|
|
|
crate::ERROR_CODE_WALLET_AUTHORIZATION_REQUIRED,
|
|
|
|
|
"Wallet metadata administration requires an authorized OWNER session",
|
|
|
|
|
"Wallet OWNER operation requires an authorized OWNER session",
|
|
|
|
|
));
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
let pubkey = owner.pubkey().to_owned();
|
|
|
|
|
let context = OwnerMetadataContext { operation, owner, path: path.clone(), pubkey, view_enabled, wallet_id: wallet_id.clone() };
|
|
|
|
|
let context = OwnerOperationContext { operation, owner, path: path.clone(), pubkey, view_enabled, wallet_id: wallet_id.clone() };
|
|
|
|
|
*session = crate::WalletSession::OwnerOperation { wallet_id, path, pubkey, view_enabled };
|
|
|
|
|
ksp_logging_lib::debug!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_SESSION, wallet_id = context.wallet_id.as_str(), operation, "Wallet OWNER metadata operation started");
|
|
|
|
|
ksp_logging_lib::debug!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_SESSION, wallet_id = context.wallet_id.as_str(), operation, "Wallet OWNER privileged operation started");
|
|
|
|
|
return std::result::Result::Ok(context);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn finish_owner_metadata_operation(
|
|
|
|
|
async fn finish_owner_operation(
|
|
|
|
|
&self,
|
|
|
|
|
context: OwnerMetadataContext,
|
|
|
|
|
context: OwnerOperationContext,
|
|
|
|
|
operation_result: ksp_core_lib::Result<()>,
|
|
|
|
|
) -> ksp_core_lib::Result<crate::WalletAuthorizedDto> {
|
|
|
|
|
if let std::result::Result::Err(error) = operation_result {
|
|
|
|
|
if error.code() == ksp_wallet_lib::ERROR_CODE_STATE_CONFLICT {
|
|
|
|
|
self.recover_owner_metadata_state_conflict(context).await;
|
|
|
|
|
self.recover_owner_state_conflict(context).await;
|
|
|
|
|
return std::result::Result::Err(error);
|
|
|
|
|
}
|
|
|
|
|
self.restore_owner_after_metadata_failure(context);
|
|
|
|
|
self.restore_owner_after_operation_failure(context);
|
|
|
|
|
return std::result::Result::Err(error);
|
|
|
|
|
}
|
|
|
|
|
return self.install_owner_after_metadata_success(context);
|
|
|
|
|
return self.install_owner_after_operation_success(context);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn install_owner_after_metadata_success(&self, context: OwnerMetadataContext) -> ksp_core_lib::Result<crate::WalletAuthorizedDto> {
|
|
|
|
|
let OwnerMetadataContext { operation, owner, path, pubkey, view_enabled, wallet_id } = context;
|
|
|
|
|
fn install_owner_after_operation_success(&self, context: OwnerOperationContext) -> ksp_core_lib::Result<crate::WalletAuthorizedDto> {
|
|
|
|
|
let OwnerOperationContext { operation, owner, path, pubkey, view_enabled, wallet_id } = context;
|
|
|
|
|
let configured_secret_candidate_count = self.secret_candidate_count_or_zero(wallet_id.as_str());
|
|
|
|
|
let dto = crate::owner_projection(wallet_id.as_str(), view_enabled, configured_secret_candidate_count, &owner);
|
|
|
|
|
let session = self.wallet_session.lock();
|
|
|
|
|
@@ -768,7 +799,7 @@ impl AppState {
|
|
|
|
|
view_enabled: reserved_view_enabled,
|
|
|
|
|
} if reserved_wallet_id == wallet_id && reserved_path == path && reserved_pubkey == pubkey && reserved_view_enabled == view_enabled => {
|
|
|
|
|
*session = crate::WalletSession::Owner { wallet_id: wallet_id.clone(), path, view_enabled, wallet: owner };
|
|
|
|
|
ksp_logging_lib::info!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_SESSION, wallet_id = wallet_id.as_str(), operation, "Wallet OWNER metadata operation completed");
|
|
|
|
|
ksp_logging_lib::info!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_SESSION, wallet_id = wallet_id.as_str(), operation, "Wallet OWNER privileged operation completed");
|
|
|
|
|
return std::result::Result::Ok(dto);
|
|
|
|
|
},
|
|
|
|
|
other => {
|
|
|
|
|
@@ -776,14 +807,14 @@ impl AppState {
|
|
|
|
|
drop(owner);
|
|
|
|
|
return std::result::Result::Err(ksp_core_lib::Error::new(
|
|
|
|
|
crate::ERROR_CODE_WALLET_SESSION_INVALID,
|
|
|
|
|
"Wallet metadata completion no longer owns the selected OWNER session",
|
|
|
|
|
"Wallet OWNER operation completion no longer owns the selected session",
|
|
|
|
|
));
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn restore_owner_after_metadata_failure(&self, context: OwnerMetadataContext) {
|
|
|
|
|
let OwnerMetadataContext { operation, owner, path, pubkey, view_enabled, wallet_id } = context;
|
|
|
|
|
fn restore_owner_after_operation_failure(&self, context: OwnerOperationContext) {
|
|
|
|
|
let OwnerOperationContext { operation, owner, path, pubkey, view_enabled, wallet_id } = context;
|
|
|
|
|
let session = self.wallet_session.lock();
|
|
|
|
|
let mut session = match session {
|
|
|
|
|
std::result::Result::Ok(value) => value,
|
|
|
|
|
@@ -798,7 +829,7 @@ impl AppState {
|
|
|
|
|
view_enabled: reserved_view_enabled,
|
|
|
|
|
} if reserved_wallet_id == wallet_id && reserved_path == path && reserved_pubkey == pubkey && reserved_view_enabled == view_enabled => {
|
|
|
|
|
*session = crate::WalletSession::Owner { wallet_id: wallet_id.clone(), path, view_enabled, wallet: owner };
|
|
|
|
|
ksp_logging_lib::debug!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_SESSION, wallet_id = wallet_id.as_str(), operation, "Wallet OWNER metadata operation failed without invalidating the authenticated handle");
|
|
|
|
|
ksp_logging_lib::debug!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_SESSION, wallet_id = wallet_id.as_str(), operation, "Wallet OWNER privileged operation failed without invalidating the authenticated handle");
|
|
|
|
|
},
|
|
|
|
|
other => {
|
|
|
|
|
*session = other;
|
|
|
|
|
@@ -807,8 +838,8 @@ impl AppState {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn recover_owner_metadata_state_conflict(&self, context: OwnerMetadataContext) {
|
|
|
|
|
let OwnerMetadataContext { operation, owner, path, pubkey, view_enabled, wallet_id } = context;
|
|
|
|
|
async fn recover_owner_state_conflict(&self, context: OwnerOperationContext) {
|
|
|
|
|
let OwnerOperationContext { operation, owner, path, pubkey, view_enabled, wallet_id } = context;
|
|
|
|
|
drop(owner);
|
|
|
|
|
let inspected = ksp_wallet_lib::inspect_locked_wallet_file_v1(path.as_path()).await;
|
|
|
|
|
let session = self.wallet_session.lock();
|
|
|
|
|
@@ -827,7 +858,7 @@ impl AppState {
|
|
|
|
|
if let std::result::Result::Ok(locked_info) = inspected {
|
|
|
|
|
*session = crate::WalletSession::Locked { wallet_id: wallet_id.clone(), path, locked_info };
|
|
|
|
|
}
|
|
|
|
|
ksp_logging_lib::warn!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_SESSION, wallet_id = wallet_id.as_str(), operation, "Wallet OWNER metadata state conflict purged the stale handle and forced reauthorization");
|
|
|
|
|
ksp_logging_lib::warn!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_SESSION, wallet_id = wallet_id.as_str(), operation, "Wallet OWNER state conflict purged the stale handle and forced reauthorization");
|
|
|
|
|
},
|
|
|
|
|
other => {
|
|
|
|
|
*session = other;
|
|
|
|
|
@@ -964,7 +995,7 @@ fn configured_secret_missing_error() -> ksp_core_lib::Error {
|
|
|
|
|
return ksp_core_lib::Error::new(crate::ERROR_CODE_WALLET_SECRET_CANDIDATES_MISSING, "No effective Config-owned Wallet password candidate is available");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct OwnerMetadataContext {
|
|
|
|
|
struct OwnerOperationContext {
|
|
|
|
|
operation: &'static str,
|
|
|
|
|
owner: std::boxed::Box<ksp_wallet_lib::WalletOwner>,
|
|
|
|
|
path: std::path::PathBuf,
|
|
|
|
|
|