Skip to content

Commit 7d751d5

Browse files
committed
Network: Add "Forget saved password" UI for SMB
- `ShareBrowser` header row button when stored creds are active - `NetworkBrowser` right-click → confirm dialog on hosts with stored creds - Shared `forgetCredentials()` in `network-store` wires up the previously unused `deleteSmbCredentials` - Haven't tested TBH, in the lack of a suitable server
1 parent ee0c977 commit 7d751d5

4 files changed

Lines changed: 83 additions & 2 deletions

File tree

apps/desktop/src/lib/file-explorer/network/CLAUDE.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Key exported functions:
4646
| `refreshSharesIfStale(host)` | Background refresh if TTL expired |
4747
| `refreshAllStaleShares()` | Call on entering network view |
4848
| `checkCredentialsForHost(serverName)` | One-time async Keychain probe; idempotent |
49+
| `forgetCredentials(serverName)` | Deletes stored creds, sets status to `no_creds` |
4950
| `setCredentialStatus / getCredentialStatus` | In-memory only, not persisted |
5051
| `setShareState / clearShareState` | Used by `ShareBrowser` after successful auth |
5152
| `getDiscoveryState()` | Returns current `DiscoveryState` |
@@ -67,6 +68,9 @@ encoded into the synthetic `name` field so MCP agents can read IP, hostname, sha
6768

6869
Exported for parent: `setCursorIndex(index)`, `findItemIndex(name)`, `handleKeyDown(e)`.
6970

71+
Right-click on a host row with stored credentials shows a confirmation dialog to forget the saved password (calls
72+
`forgetCredentials`).
73+
7074
## `ShareBrowser.svelte`
7175

7276
Rendered after user selects a host. Auth flow on mount:
@@ -81,6 +85,9 @@ Rendered after user selects a host. Auth flow on mount:
8185

8286
`authenticatedCredentials` is passed to `onShareSelect` so the caller can mount the share without re-prompting.
8387

88+
When `authenticatedCredentials` is set (stored creds were used), a "Forget saved password" button appears in the header
89+
row. Clicking it calls `forgetCredentials` and clears `authenticatedCredentials`.
90+
8491
Shares displayed sorted case-insensitively. Escape/Backspace go back to host list.
8592

8693
## `NetworkLoginForm.svelte`
@@ -157,8 +164,10 @@ status into the name string is a workaround so MCP agents can read the same info
157164
## Dependencies
158165

159166
- `$lib/tauri-commands``listNetworkHosts`, `resolveNetworkHost`, `listSharesOnHost`, `listSharesWithCredentials`,
160-
`prefetchShares`, `getSmbCredentials`, `saveSmbCredentials`, `getUsernameHints`, `getKnownShareByName`,
161-
`updateKnownShare`, `updateLeftPaneState`, `updateRightPaneState`
167+
`prefetchShares`, `getSmbCredentials`, `saveSmbCredentials`, `deleteSmbCredentials`, `getUsernameHints`,
168+
`getKnownShareByName`, `updateKnownShare`, `updateLeftPaneState`, `updateRightPaneState`
162169
- `$lib/settings/network-settings``getNetworkTimeoutMs`, `getShareCacheTtlMs`
170+
- `$lib/utils/confirm-dialog``confirmDialog` (used by `NetworkBrowser` for forget-password confirmation)
171+
- `$lib/ui/toast``addToast` (feedback after credential operations)
163172
- `../navigation/keyboard-shortcuts``handleNavigationShortcut`
164173
- `../types``NetworkHost`, `DiscoveryState`, `ShareInfo`, `ShareListResult`, `ShareListError`, `AuthMode`

apps/desktop/src/lib/file-explorer/network/NetworkBrowser.svelte

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@
1919
fetchShares,
2020
getCredentialStatus,
2121
checkCredentialsForHost,
22+
forgetCredentials,
2223
} from './network-store.svelte'
2324
import { tooltip } from '$lib/tooltip/tooltip'
2425
import type { NetworkHost } from '../types'
2526
import { updateLeftPaneState, updateRightPaneState, type PaneState, type PaneFileEntry } from '$lib/tauri-commands'
2627
import { handleNavigationShortcut } from '../navigation/keyboard-shortcuts'
28+
import { confirmDialog } from '$lib/utils/confirm-dialog'
29+
import { addToast } from '$lib/ui/toast'
2730
2831
/** Row height for host list (matches Full list) */
2932
const HOST_ROW_HEIGHT = 20
@@ -347,6 +350,21 @@
347350
return undefined
348351
}
349352
353+
async function handleHostContextMenu(e: MouseEvent, host: NetworkHost) {
354+
e.preventDefault()
355+
if (getCredentialStatus(host.name) !== 'has_creds') return
356+
357+
const confirmed = await confirmDialog(`Remove saved password for "${host.name}"?`, 'Forget saved password')
358+
if (!confirmed) return
359+
360+
try {
361+
await forgetCredentials(host.name)
362+
addToast(`Forgot saved password for ${host.name}`, { level: 'info' })
363+
} catch {
364+
addToast(`Couldn't delete saved password`, { level: 'error' })
365+
}
366+
}
367+
350368
// Refresh all shares (user-initiated)
351369
function handleRefreshClick() {
352370
// Clear all share states to force refetch
@@ -383,6 +401,9 @@
383401
ondblclick={() => {
384402
handleHostDoubleClick(index)
385403
}}
404+
oncontextmenu={(e: MouseEvent) => {
405+
void handleHostContextMenu(e, host)
406+
}}
386407
onkeydown={() => {}}
387408
>
388409
<span class="col-name">

apps/desktop/src/lib/file-explorer/network/ShareBrowser.svelte

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
clearShareState,
1414
setShareState,
1515
setCredentialStatus,
16+
forgetCredentials,
1617
} from './network-store.svelte'
1718
import {
1819
listSharesWithCredentials,
@@ -22,6 +23,7 @@
2223
updateKnownShare,
2324
} from '$lib/tauri-commands'
2425
import { addToast } from '$lib/ui/toast'
26+
import { tooltip } from '$lib/tooltip/tooltip'
2527
import { getNetworkTimeoutMs, getShareCacheTtlMs } from '$lib/settings/network-settings'
2628
import NetworkLoginForm from './NetworkLoginForm.svelte'
2729
import { handleNavigationShortcut } from '../navigation/keyboard-shortcuts'
@@ -380,6 +382,16 @@
380382
return false
381383
}
382384
385+
async function handleForgetPassword() {
386+
try {
387+
await forgetCredentials(host.name)
388+
authenticatedCredentials = null
389+
addToast(`Forgot saved password for ${host.name}`, { level: 'info' })
390+
} catch {
391+
addToast(`Couldn't delete saved password`, { level: 'error' })
392+
}
393+
}
394+
383395
function handleRetry() {
384396
error = null
385397
showLoginForm = false
@@ -432,6 +444,15 @@
432444
<div class="header-row">
433445
<Button variant="secondary" size="mini" onclick={onBack}>← Back</Button>
434446
<span class="host-name">{host.name}</span>
447+
{#if authenticatedCredentials}
448+
<button
449+
class="forget-password-btn"
450+
onclick={handleForgetPassword}
451+
use:tooltip={'Remove saved password from Keychain'}
452+
>
453+
🔑 Forget saved password
454+
</button>
455+
{/if}
435456
<span class="share-count">{sortedShares.length} {sortedShares.length === 1 ? 'share' : 'shares'}</span>
436457
</div>
437458
<div class="share-list" bind:this={listContainer} bind:clientHeight={containerHeight}>
@@ -522,6 +543,26 @@
522543
color: var(--color-text-primary);
523544
}
524545
546+
.forget-password-btn {
547+
display: flex;
548+
align-items: center;
549+
gap: var(--spacing-xs);
550+
padding: 1px var(--spacing-sm);
551+
font-family: var(--font-system), sans-serif;
552+
font-size: calc(var(--font-size-sm) * 0.9);
553+
color: var(--color-text-tertiary);
554+
background: none;
555+
border: 1px solid transparent;
556+
border-radius: var(--radius-sm);
557+
cursor: pointer;
558+
}
559+
560+
.forget-password-btn:hover {
561+
color: var(--color-text-secondary);
562+
border-color: var(--color-border);
563+
background-color: var(--color-bg-tertiary);
564+
}
565+
525566
.share-count {
526567
color: var(--color-text-tertiary);
527568
margin-left: auto;

apps/desktop/src/lib/file-explorer/network/network-store.svelte.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
listSharesOnHost,
1313
prefetchShares as prefetchSharesCmd,
1414
getSmbCredentials,
15+
deleteSmbCredentials,
1516
} from '$lib/tauri-commands'
1617
import { getNetworkTimeoutMs, getShareCacheTtlMs } from '$lib/settings/network-settings'
1718
import { initializeSettings } from '$lib/settings'
@@ -375,3 +376,12 @@ export async function checkCredentialsForHost(serverName: string): Promise<void>
375376
credentialStatuses.set(key, 'no_creds')
376377
}
377378
}
379+
380+
/**
381+
* Delete stored credentials for a host and update status.
382+
* Removes server-level credentials from the Keychain/credential store.
383+
*/
384+
export async function forgetCredentials(serverName: string): Promise<void> {
385+
await deleteSmbCredentials(serverName, null)
386+
credentialStatuses.set(serverName.toLowerCase(), 'no_creds')
387+
}

0 commit comments

Comments
 (0)