v0.2.6-pre.005

This commit is contained in:
2026-08-21 09:33:04 +02:00
parent 2132dfd884
commit 7aa38b0daf
19 changed files with 980 additions and 126 deletions

View File

@@ -0,0 +1,166 @@
// file: crates/ksp-app-wallet-desk/src/wallet_session.rs
// version: 1
//! Durable root-scoped Wallet session lifecycle for Wallet Desk.
use ts_rs::TS; // rust-rules: trait-import
/// Safe state code projected for the current Wallet session.
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_wallet_desk/wallet_session/WalletSessionStateDto.ts")]
pub(crate) enum WalletSessionStateDto {
/// No Wallet is selected.
NoSelection,
/// One Wallet is selected but remains locked.
Locked,
/// One Wallet is open with OWNER capability.
OwnerOpen,
}
/// Minimal non-secret projection of the backend Wallet session.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_wallet_desk/wallet_session/WalletSessionDto.ts")]
pub(crate) struct WalletSessionDto {
/// Filename when a Wallet is selected.
pub(crate) filename: std::option::Option<String>,
/// Current session state.
pub(crate) state: WalletSessionStateDto,
/// Root-scoped identifier when a Wallet is selected.
pub(crate) wallet_id: std::option::Option<String>,
}
/// One protected Wallet note exposed only from an authorized session.
#[derive(serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_wallet_desk/wallet_session/WalletNoteDto.ts")]
pub(crate) struct WalletNoteDto {
/// Stable note identifier generated by Wallet.
pub(crate) id: String,
/// Protected note text available only after authorization.
pub(crate) text: String,
}
/// Authorized Wallet projection returned after creation with OWNER capability.
#[derive(serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_wallet_desk/wallet_session/WalletAuthorizedDto.ts")]
pub(crate) struct WalletAuthorizedDto {
/// Protected internal alias when configured.
pub(crate) alias: std::option::Option<String>,
/// Capability that produced this projection.
pub(crate) capability: String,
/// Native Wallet filename without its parent path.
pub(crate) filename: String,
/// Native Wallet format version.
pub(crate) format_version: u32,
/// Protected notes available to the OWNER session.
pub(crate) notes: std::vec::Vec<WalletNoteDto>,
/// Authorized Solana public key.
pub(crate) pubkey: String,
/// Root-scoped identifier accepted by later Wallet Desk operations.
pub(crate) wallet_id: String,
/// Whether the created Wallet has an enabled VIEW slot.
pub(crate) view_enabled: bool,
}
/// Create request moved from the frontend directly into Wallet password wrappers.
#[derive(serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_wallet_desk/wallet_session/WalletCreateRequestDto.ts")]
pub(crate) struct WalletCreateRequestDto {
/// Optional protected internal alias stored inside the Wallet.
pub(crate) alias: std::option::Option<String>,
/// Root-scoped destination filename.
pub(crate) filename: String,
/// Optional first protected note stored inside the Wallet.
pub(crate) initial_note: std::option::Option<String>,
/// OWNER password transported only frontend -> Rust.
pub(crate) owner_password: String,
/// Optional VIEW password; `None` keeps VIEW disabled.
pub(crate) view_password: std::option::Option<String>,
}
/// Backend Wallet session. Full paths and authorized handles never cross IPC.
pub(crate) enum WalletSession {
/// No current Wallet selection.
NoSelection,
/// One re-inspected locked Wallet.
Locked {
/// Native Wallet filename / root-scoped identifier.
wallet_id: String,
/// Full root-scoped filesystem path retained only in Rust.
path: std::path::PathBuf,
/// Locked information re-inspected from the file.
locked_info: ksp_wallet_lib::LockedWalletInfo,
},
/// One Wallet kept open with OWNER capability.
Owner {
/// Native Wallet filename / root-scoped identifier.
wallet_id: String,
/// Full root-scoped filesystem path retained only in Rust.
path: std::path::PathBuf,
/// Authorized OWNER handle retaining secret material only in Rust.
wallet: ksp_wallet_lib::WalletOwner,
},
}
impl WalletSession {
/// Creates an empty application session.
#[must_use]
pub(crate) const fn no_selection() -> Self {
return Self::NoSelection;
}
/// Returns a minimal safe session projection.
#[must_use]
pub(crate) fn safe_projection(&self) -> WalletSessionDto {
return match self {
Self::NoSelection => WalletSessionDto {
filename: std::option::Option::None,
state: WalletSessionStateDto::NoSelection,
wallet_id: std::option::Option::None,
},
Self::Locked { wallet_id, locked_info, .. } => {
let _ = locked_info.format_version();
WalletSessionDto {
filename: std::option::Option::Some(wallet_id.clone()),
state: WalletSessionStateDto::Locked,
wallet_id: std::option::Option::Some(wallet_id.clone()),
}
},
Self::Owner { wallet_id, .. } => WalletSessionDto {
filename: std::option::Option::Some(wallet_id.clone()),
state: WalletSessionStateDto::OwnerOpen,
wallet_id: std::option::Option::Some(wallet_id.clone()),
},
};
}
}
/// Converts an OWNER handle into the authorized response DTO without exposing secret key material.
pub(crate) fn owner_projection(wallet_id: &str, view_enabled: bool, owner: &ksp_wallet_lib::WalletOwner) -> WalletAuthorizedDto {
let info = owner.info();
let notes = info
.notes()
.iter()
.map(|note| {
return WalletNoteDto { id: note.id().to_owned(), text: note.text().to_owned() };
})
.collect();
return WalletAuthorizedDto {
alias: info.alias().map(|value| return value.to_owned()),
capability: "owner".to_owned(),
filename: wallet_id.to_owned(),
format_version: info.format_version(),
notes,
pubkey: info.pubkey().to_string(),
wallet_id: wallet_id.to_owned(),
view_enabled,
};
}
#[cfg(test)]
#[path = "../unit_tests/wallet_session.rs"]
mod tests;