Skip to content

Commit 283e5fd

Browse files
committed
Network: Add authentication
And tick off done tasks
1 parent 82105eb commit 283e5fd

14 files changed

Lines changed: 1683 additions & 37 deletions

File tree

docs/features/network-smb/task-list.md

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ See [index.md](./index.md) for an overview of this whole feature. It's a helpful
1010
- 🔄 In progress
1111
- ✅ Complete
1212
- 🔬 Spike/research needed
13+
- ❌ Not needed
1314

1415
---
1516

@@ -64,7 +65,7 @@ See [share-listing.md](./share-listing.md) for details. Decision: [ADR 013](../.
6465
-**2.6** Handle guest vs. authenticated enumeration
6566
-**2.7** Implement `smbutil` fallback for edge cases
6667
-**2.8** Add timeout handling (10–15 second limit)
67-
- **2.9** Implement connection pool (60 sec TTL, max 20 connections)
68+
- **2.9** Implement connection pool (60 sec TTL, max 20 connections)
6869
-**2.10** Implement auth mode detection (try guest, detect `GuestAllowed` vs `CredsRequired`)
6970
-**2.11** Add unit tests with mocked SMB responses
7071

@@ -114,23 +115,23 @@ See [authentication.md](./authentication.md) for details.
114115

115116
### Backend (Rust)
116117

117-
- **4.1** Add `security-framework` crate to dependencies
118-
- **4.2** Implement `save_credentials_to_keychain` function
119-
- **4.3** Implement `get_credentials_from_keychain` function
120-
- **4.4** Implement auth options detection (guest/creds/both)
121-
- **4.5** Create Tauri commands: `check_auth_required`, `save_smb_credentials`, `get_smb_credentials`
122-
- **4.6** Add unit tests with mocked Keychain
118+
- **4.1** Add `security-framework` crate to dependencies
119+
- **4.2** Implement `save_credentials_to_keychain` function
120+
- **4.3** Implement `get_credentials_from_keychain` function
121+
- **4.4** Implement auth options detection (guest/creds/both)
122+
- **4.5** Create Tauri commands: `check_auth_required`, `save_smb_credentials`, `get_smb_credentials`
123+
- **4.6** Add unit tests with mocked Keychain
123124

124125
### Frontend (Svelte)
125126

126-
- **4.7** Create `NetworkLoginForm.svelte` component
127-
- **4.8** Integrate login form into `FilePane.svelte` (replaces file list when auth needed)
128-
- **4.9** Implement guest vs. credentials toggle (when both available)
129-
- **4.10** Pre-fill username from known shares store
130-
- **4.11** Handle "Remember in Keychain" checkbox
131-
- **4.12** Show contextual messages when auth options changed
132-
- **4.13** Handle auth errors with re-prompt
133-
- **4.14** Add frontend tests for all auth scenarios
127+
- **4.7** Create `NetworkLoginForm.svelte` component
128+
- **4.8** Integrate login form into `FilePane.svelte` (replaces file list when auth needed)
129+
- **4.9** Implement guest vs. credentials toggle (when both available)
130+
- **4.10** Pre-fill username from known shares store
131+
- **4.11** Handle "Remember in Keychain" checkbox
132+
- **4.12** Show contextual messages when auth options changed
133+
- **4.13** Handle auth errors with re-prompt
134+
- **4.14** Add frontend tests for all auth scenarios
134135

135136
---
136137

@@ -149,28 +150,28 @@ See [known-shares-store.md](./known-shares-store.md) for details.
149150

150151
### Frontend (Svelte)
151152

152-
_Note: 5.7-5.9 are blocked until authentication UI (section 4) is implemented._
153-
154-
-**5.7** Read known shares for username pre-fill
155-
-**5.8** Update known shares after successful connection
156-
-**5.9** Compare current auth options with stored to detect changes
157-
-**5.10** Add frontend tests (type and logic tests added; integration tests blocked on 5.7-5.9)
153+
-**5.7** Read known shares for username pre-fill (implemented in `NetworkLoginForm.svelte`)
154+
-**5.8** Update known shares after successful connection (implemented in `ShareBrowser.svelte`)
155+
-**5.9** Compare current auth options with stored to detect changes (implemented in `NetworkLoginForm.svelte`)
156+
-**5.10** Add frontend tests (type and logic tests added)
158157

159158
---
160159

161160
## 6. Pre-mounted shares
162161

162+
Pre-mounted SMB shares (e.g., mounted via Finder) appear automatically in the volume selector because the existing volume listing code at `/Volumes/*` picks them up. The macOS APIs return the correct network share icon.
163+
163164
### Backend (Rust)
164165

165-
- **6.1** Detect network mounts in existing volume listing code
166-
- **6.2** Categorize as `NetworkShare` (or refine existing `AttachedVolume` detection)
167-
- **6.3** Add appropriate icon for network shares
168-
- **6.4** Add unit tests
166+
- **6.1** Detect network mounts in existing volume listing code (uses `/Volumes/*` enumeration)
167+
- **6.2** Categorize as `AttachedVolume` (works correctly, dedicated category not needed)
168+
- **6.3** Add appropriate icon for network shares (uses `get_icon_for_path` which returns macOS system icon)
169+
- **6.4** Unit tests (covered by existing volume listing tests)
169170

170171
### Frontend (Svelte)
171172

172-
- **6.5** Display pre-mounted network shares in volume selector
173-
- **6.6** Add frontend tests
173+
- **6.5** Display pre-mounted network shares in volume selector (works automatically)
174+
- **6.6** Frontend tests (covered by existing tests)
174175

175176
---
176177

docs/todo.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
## Cleanup / housekeeping
2222

23+
- Add "prefer const" ESLint rule
2324
- A round of refactoring is due
2425
- Mark macOS vs generic code clearer, and add this to the guide. Is there a way to run some coherence checks for
2526
`#[cfg(target_os = "macos")]` == true/false separately?

src-tauri/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ objc2-foundation = { version = "0.3", features = [
5959
smb = "0.11.1"
6060
smb-rpc = "=0.11.1"
6161
chrono = "0.4"
62+
security-framework = "3.2"
6263

6364
[dev-dependencies]
6465
criterion = { version = "0.8.1", features = ["html_reports"] }

src-tauri/src/commands/network.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,72 @@ pub fn update_known_share(
143143
pub fn get_username_hints() -> std::collections::HashMap<String, String> {
144144
known_shares::get_username_hints()
145145
}
146+
147+
// --- Keychain Commands ---
148+
149+
use crate::network::keychain::{self, KeychainError, SmbCredentials};
150+
151+
/// Saves SMB credentials to the Keychain.
152+
/// Credentials are stored under "Rusty Commander" service name.
153+
#[tauri::command]
154+
pub fn save_smb_credentials(
155+
server: String,
156+
share: Option<String>,
157+
username: String,
158+
password: String,
159+
) -> Result<(), KeychainError> {
160+
keychain::save_credentials(&server, share.as_deref(), &username, &password)
161+
}
162+
163+
/// Retrieves SMB credentials from the Keychain.
164+
/// Returns the stored username and password if found.
165+
#[tauri::command]
166+
pub fn get_smb_credentials(server: String, share: Option<String>) -> Result<SmbCredentials, KeychainError> {
167+
keychain::get_credentials(&server, share.as_deref())
168+
}
169+
170+
/// Checks if credentials exist in the Keychain for a server/share.
171+
#[tauri::command]
172+
pub fn has_smb_credentials(server: String, share: Option<String>) -> bool {
173+
keychain::has_credentials(&server, share.as_deref())
174+
}
175+
176+
/// Deletes SMB credentials from the Keychain.
177+
#[tauri::command]
178+
pub fn delete_smb_credentials(server: String, share: Option<String>) -> Result<(), KeychainError> {
179+
keychain::delete_credentials(&server, share.as_deref())
180+
}
181+
182+
/// Lists shares on a host using stored or provided credentials.
183+
/// This is the main command for authenticated share listing.
184+
///
185+
/// # Arguments
186+
/// * `host_id` - Unique identifier for the host (used for caching)
187+
/// * `hostname` - Hostname to connect to
188+
/// * `ip_address` - Optional resolved IP address
189+
/// * `port` - SMB port
190+
/// * `username` - Username for authentication (or None for guest)
191+
/// * `password` - Password for authentication (or None for guest)
192+
#[tauri::command]
193+
pub async fn list_shares_with_credentials(
194+
host_id: String,
195+
hostname: String,
196+
ip_address: Option<String>,
197+
port: u16,
198+
username: Option<String>,
199+
password: Option<String>,
200+
) -> Result<ShareListResult, ShareListError> {
201+
let credentials = match (username, password) {
202+
(Some(u), Some(p)) => Some((u, p)),
203+
_ => None,
204+
};
205+
206+
smb_client::list_shares(
207+
&host_id,
208+
&hostname,
209+
ip_address.as_deref(),
210+
port,
211+
credentials.as_ref().map(|(u, p)| (u.as_str(), p.as_str())),
212+
)
213+
.await
214+
}

src-tauri/src/lib.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ use tokio as _;
2121
// chrono is used in network/known_shares.rs for timestamps
2222
#[cfg(target_os = "macos")]
2323
use chrono as _;
24+
// security_framework is used in network/keychain.rs for Keychain integration
25+
#[cfg(target_os = "macos")]
26+
use security_framework as _;
2427

2528
pub mod benchmark;
2629
mod commands;
@@ -215,6 +218,16 @@ pub fn run() {
215218
#[cfg(target_os = "macos")]
216219
commands::network::get_username_hints,
217220
#[cfg(target_os = "macos")]
221+
commands::network::save_smb_credentials,
222+
#[cfg(target_os = "macos")]
223+
commands::network::get_smb_credentials,
224+
#[cfg(target_os = "macos")]
225+
commands::network::has_smb_credentials,
226+
#[cfg(target_os = "macos")]
227+
commands::network::delete_smb_credentials,
228+
#[cfg(target_os = "macos")]
229+
commands::network::list_shares_with_credentials,
230+
#[cfg(target_os = "macos")]
218231
permissions::check_full_disk_access,
219232
#[cfg(target_os = "macos")]
220233
permissions::open_privacy_settings

0 commit comments

Comments
 (0)