Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
52ac8c2
Add password protection design spec
dkackman Mar 15, 2026
c56b9f2
Add password protection implementation plan
dkackman Mar 15, 2026
91805b6
feat: add password_protected flag to KeyData::Secret
dkackman Mar 15, 2026
87bda15
feat: add change_password and keychain tests
dkackman Mar 15, 2026
c05106f
feat: add password field to all protected request structs
dkackman Mar 15, 2026
de45ca1
feat: add ChangePassword endpoint and has_password to KeyInfo
dkackman Mar 15, 2026
3e837e3
feat: thread password through all protected endpoints
dkackman Mar 15, 2026
5b152b6
tests
dkackman Mar 15, 2026
cb481fc
frontend changes
dkackman Mar 15, 2026
08f0a8c
cargo fmt
dkackman Mar 15, 2026
19f9944
lint
dkackman Mar 15, 2026
7d09db6
add frontend notes
dkackman Mar 15, 2026
a358fbc
feat: add tauri-plugin-keychain dependency for biometric-password bridge
dkackman Mar 16, 2026
9b4115f
docs: update spec to reference tauri-plugin-keychain
dkackman Mar 16, 2026
953a9c0
feat: refactor PasswordContext to unified auth with keychain integration
dkackman Mar 16, 2026
a81dad9
refactor: update ConfirmationDialog to unified auth pattern
dkackman Mar 16, 2026
9b25946
refactor: update WalletCard to unified auth pattern
dkackman Mar 16, 2026
ff3f658
refactor: update useOfferProcessor to unified auth pattern
dkackman Mar 16, 2026
bd2a1da
refactor: update WalletConnect to unified auth pattern
dkackman Mar 16, 2026
dba8267
refactor: update remaining call sites to unified auth pattern
dkackman Mar 16, 2026
5196a91
feat: move biometric toggle to Security section with context-aware la…
dkackman Mar 16, 2026
1ae94ec
feat: handle keychain lifecycle for password changes
dkackman Mar 16, 2026
bfa733d
feat: clear keychain entry on wallet deletion
dkackman Mar 16, 2026
23bdc9c
prettier
dkackman Mar 16, 2026
f51e6c9
remove plan docs
dkackman Mar 16, 2026
5d0ab95
refactor: move biometric toggle back to global settings
dkackman Mar 16, 2026
815e594
add merge rule for android:theme to override plugin rules
dkackman Mar 16, 2026
1f98f1c
fix: address code review — keychain skip flag and delete dialog
dkackman Mar 16, 2026
bd1a53f
fix: surface KeyNotFound and NoSecretKey errors as toasts
dkackman Mar 16, 2026
bf8d7c6
docs: update spec — biometric toggle stays in global Preferences
dkackman Mar 16, 2026
6eedcd6
fix: add error handling to toggleBiometric in GlobalSettings
dkackman Mar 16, 2026
8b9a888
docs: fix spec — cancel resolves undefined, not null
dkackman Mar 16, 2026
e659e67
fix: accept target fingerprint in requestPassword
dkackman Mar 16, 2026
445679e
fix: remove skipKeychainRef — simplify stale password recovery
dkackman Mar 16, 2026
c88b3f9
fix: skip-after-failure for stale keychain passwords
dkackman Mar 16, 2026
3a612e4
refactor: make biometric and password mutually exclusive
dkackman Mar 16, 2026
f00f952
refactor: remove notifyDecryptError bridge from ErrorContext
dkackman Mar 16, 2026
e614a55
refactor: remove keychain lifecycle from Settings
dkackman Mar 16, 2026
a3f1da8
refactor: remove keychain cleanup from wallet deletion
dkackman Mar 16, 2026
a2a6e10
chore: remove tauri-plugin-keychain dependency
dkackman Mar 16, 2026
ed29746
refactor: remove unused fingerprint parameter from requestPassword
dkackman Mar 16, 2026
d027242
docs: update spec — biometric and password are mutually exclusive
dkackman Mar 16, 2026
54fd34c
chore: update Cargo.lock after removing keychain dependency
dkackman Mar 16, 2026
d64ebd6
prettier
dkackman Mar 16, 2026
7a31aea
remove plans
dkackman Mar 16, 2026
3742433
passward promting bug fixes
dkackman Mar 16, 2026
1c08645
passward protect delete key
dkackman Mar 16, 2026
f139988
removed cruft
dkackman Mar 16, 2026
f6eb1fc
prettier
dkackman Mar 16, 2026
a3195ce
prettier
dkackman Mar 17, 2026
bc56bab
factor out new password dialog
dkackman Mar 20, 2026
0fdff0e
require seed phrase or key to create password
dkackman Mar 20, 2026
a0458ad
Merge remote-tracking branch 'upstream' into password
dkackman Mar 25, 2026
535054b
move password protected flag into wallet config file, leaving key ser…
dkackman Mar 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion crates/sage-api/endpoints.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,5 +98,6 @@
"update_nft_collection": true,
"redownload_nft": true,
"increase_derivation_index": true,
"is_asset_owned": true
"is_asset_owned": true,
"change_password": false
}
4 changes: 4 additions & 0 deletions crates/sage-api/src/requests/action_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ pub struct CreateTransaction {
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(default = false))]
pub auto_submit: bool,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
Expand Down
6 changes: 5 additions & 1 deletion crates/sage-api/src/requests/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ pub struct RedownloadNftResponse {}
description = "Increase the derivation index to generate more addresses for the wallet."
)
)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "tauri", derive(specta::Type))]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct IncreaseDerivationIndex {
Expand All @@ -205,6 +205,10 @@ pub struct IncreaseDerivationIndex {
/// The target derivation index to increase to
#[cfg_attr(feature = "openapi", schema(example = 100))]
pub index: u32,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

/// Response after increasing the derivation index
Expand Down
40 changes: 39 additions & 1 deletion crates/sage-api/src/requests/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,10 @@ pub struct ImportKey {
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub emoji: Option<String>,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

fn yes() -> bool {
Expand Down Expand Up @@ -375,13 +379,17 @@ pub struct GetKeyResponse {
description = "Retrieve the secret key (mnemonic) for a wallet. Requires authentication."
)
)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "tauri", derive(specta::Type))]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct GetSecretKey {
/// Wallet fingerprint
#[cfg_attr(feature = "openapi", schema(example = 1_234_567_890))]
pub fingerprint: u32,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

/// Response with secret key information
Expand All @@ -398,6 +406,36 @@ pub struct GetSecretKeyResponse {
pub secrets: Option<SecretKeyInfo>,
}

/// Change the password for a wallet's secret key
#[cfg_attr(
feature = "openapi",
crate::openapi_attr(
tag = "Authentication & Keys",
description = "Change the password used to encrypt a wallet's secret key."
)
)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "tauri", derive(specta::Type))]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct ChangePassword {
/// Wallet fingerprint
pub fingerprint: u32,
/// Current password (empty string if no password is set)
pub old_password: String,
/// New password (empty string to remove password protection)
pub new_password: String,
}

/// Response after changing the password
#[cfg_attr(
feature = "openapi",
crate::openapi_attr(tag = "Authentication & Keys")
)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[cfg_attr(feature = "tauri", derive(specta::Type))]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct ChangePasswordResponse {}

/// List all custom theme NFTs
#[cfg_attr(
feature = "openapi",
Expand Down
16 changes: 16 additions & 0 deletions crates/sage-api/src/requests/offers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ pub struct MakeOffer {
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub coin_ids: Option<Vec<String>>,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

/// Asset amount in an offer
Expand Down Expand Up @@ -92,6 +96,10 @@ pub struct TakeOffer {
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(default = false))]
pub auto_submit: bool,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

/// Response with accepted offer details
Expand Down Expand Up @@ -307,6 +315,10 @@ pub struct CancelOffer {
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(default = false))]
pub auto_submit: bool,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

pub type CancelOfferResponse = TransactionResponse;
Expand All @@ -332,6 +344,10 @@ pub struct CancelOffers {
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(default = false))]
pub auto_submit: bool,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

pub type CancelOffersResponse = TransactionResponse;
Expand Down
88 changes: 88 additions & 0 deletions crates/sage-api/src/requests/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ pub struct SendXch {
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(default = false))]
pub auto_submit: bool,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

/// Send XCH to multiple addresses
Expand Down Expand Up @@ -61,6 +65,10 @@ pub struct BulkSendXch {
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(default = false))]
pub auto_submit: bool,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

/// Combine multiple coins into one
Expand All @@ -84,6 +92,10 @@ pub struct Combine {
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(default = false))]
pub auto_submit: bool,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

/// Split coins into multiple smaller coins
Expand All @@ -109,6 +121,10 @@ pub struct Split {
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(default = false))]
pub auto_submit: bool,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

/// Automatically combine XCH coins
Expand All @@ -134,6 +150,10 @@ pub struct AutoCombineXch {
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(default = false))]
pub auto_submit: bool,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

/// Response for auto-combine XCH
Expand Down Expand Up @@ -175,6 +195,10 @@ pub struct AutoCombineCat {
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(default = false))]
pub auto_submit: bool,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

/// Response for auto-combine CAT
Expand Down Expand Up @@ -216,6 +240,10 @@ pub struct IssueCat {
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(default = false))]
pub auto_submit: bool,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

/// Send CAT tokens to an address
Expand Down Expand Up @@ -254,6 +282,10 @@ pub struct SendCat {
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(default = false))]
pub auto_submit: bool,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

/// Send CAT tokens to multiple addresses
Expand Down Expand Up @@ -288,6 +320,10 @@ pub struct BulkSendCat {
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(default = false))]
pub auto_submit: bool,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

fn yes() -> bool {
Expand Down Expand Up @@ -315,6 +351,10 @@ pub struct MultiSend {
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(default = false))]
pub auto_submit: bool,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

/// Individual payment in a multi-send transaction
Expand Down Expand Up @@ -357,6 +397,10 @@ pub struct CreateDid {
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(default = false))]
pub auto_submit: bool,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

/// Mint multiple NFTs in one transaction
Expand All @@ -381,6 +425,10 @@ pub struct BulkMintNfts {
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(default = false))]
pub auto_submit: bool,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

/// Response for bulk NFT minting
Expand Down Expand Up @@ -472,6 +520,10 @@ pub struct TransferNfts {
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(default = false))]
pub auto_submit: bool,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

/// Add a URI to an NFT
Expand Down Expand Up @@ -499,6 +551,10 @@ pub struct AddNftUri {
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(default = false))]
pub auto_submit: bool,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

/// Type of NFT URI
Expand Down Expand Up @@ -537,6 +593,10 @@ pub struct AssignNftsToDid {
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(default = false))]
pub auto_submit: bool,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

/// Transfer DIDs to a new address
Expand Down Expand Up @@ -566,6 +626,10 @@ pub struct TransferDids {
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(default = false))]
pub auto_submit: bool,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

/// Normalize DIDs to latest state
Expand All @@ -589,6 +653,10 @@ pub struct NormalizeDids {
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(default = false))]
pub auto_submit: bool,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

/// Asset specification for options
Expand Down Expand Up @@ -628,6 +696,10 @@ pub struct MintOption {
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(default = false))]
pub auto_submit: bool,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

/// Response for minting an option
Expand Down Expand Up @@ -665,6 +737,10 @@ pub struct ExerciseOptions {
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(default = false))]
pub auto_submit: bool,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

/// Transfer options to another address
Expand Down Expand Up @@ -694,6 +770,10 @@ pub struct TransferOptions {
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(default = false))]
pub auto_submit: bool,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

/// Send CAT tokens to an address
Expand All @@ -717,6 +797,10 @@ pub struct FinalizeClawback {
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(default = false))]
pub auto_submit: bool,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

/// Sign coin spends to create a transaction
Expand All @@ -741,6 +825,10 @@ pub struct SignCoinSpends {
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(default = false))]
pub partial: bool,
/// Password for signing (required if wallet is password-protected)
#[serde(default)]
#[cfg_attr(feature = "openapi", schema(nullable = true))]
pub password: Option<String>,
}

/// Response with signed spend bundle
Expand Down
Loading
Loading