feat(remote): complete SFTP authentication and browsing - #233
Open
spandan11106 wants to merge 8 commits into
Open
spandan11106 wants to merge 8 commits into
spandan11106 wants to merge 8 commits into
Conversation
The missing-backend message named gvfs-smb as though every distribution packaged it that way, and knew nothing about the other schemes Strata accepts. Distributions split GVfs up differently, so each scheme now lists the usual candidates and points at the distribution rather than asserting one universal package name.
The directory-logging privacy test installs its capturing subscriber with `set_global_default`, which can only ever happen once, so a second module asserting on what it logs would panic. Move the capture into `test_support` and install it per thread again, which is what more than one caller needs. Doing that alone would bring back the flake `set_global_default` was working around: `tracing` caches a callsite's interest the first time it is reached, and a callsite first reached while the current thread has no subscriber is cached as uninteresting for every thread, so an unrelated test enumerating a directory could silence the DEBUG event inside a capture running beside it. Keeping a permissive discarding subscriber installed globally for the test binary means no thread is ever without one, so callsites stay interesting and each event is resolved against whichever subscriber its own thread has.
A failed mount reported the backend's own wording verbatim, which tells a user nothing about what to do next and can carry a password that was typed into the address bar. Name resolution failures, refused connections, timeouts and unreachable hosts now each explain what to check. Host-key rejection gets its own message and, importantly, no longer counts as an authentication failure: the two arrive as the same generic error, so a rejected key would otherwise reopen the sign-in prompt for credentials that were never the problem. The SFTP backend's own wordings for rejected credentials are recognized too, and anything still falling through has URI user-info stripped before display.
Every sign-in prompt was shaped for SMB. When a mount failed before the backend had asked for anything, the fallback offered a domain field and an anonymous option regardless of scheme, defaulted the domain to WORKGROUP, and the retry message told the user to check a domain that SFTP has no concept of. The fallback now offers the fields the scheme actually uses, the retry message names only the fields on screen, and a request whose wording is about a key passphrase is labelled and titled as one, since SFTP asks for an encrypted key's passphrase through the same password request.
`mount_location` carried the state of an attempt in five separate cells and handed four loosely related values to its callers, who each re-derived what had happened from the raw error. Collect that state in `MountAttempt`, which decides whether a password request is replayed or prompted and turns a finished attempt into one `MountOutcome`, so both call sites read as a match over what happened rather than a chain of error inspection. No behaviour changes here beyond one gap this makes visible: a cancelled navigation now restores the location text, which the descend path already did and the navigate path had left showing the address that failed.
`GMountOperation` asks a question when the backend will not decide something on its own, above all whether to trust an unrecognized or changed SSH host key. Nothing was connected to that signal, so the decision was dropped and the mount was left waiting on an answer that never came. Answer it with a modal carrying the backend's own text, fingerprint included, and one button per choice the backend offered. The declining choice takes focus so Enter cannot trust a key, the backdrop is inert, and Escape or the close button decline explicitly. A changed key is styled as a warning, since it can mean interception rather than a reinstalled server. Declining ends the attempt where a cancelled sign-in ends, rather than looping back into another prompt. gir cannot wrap the signal's GStrv argument, so it is connected from the raw signal rather than through a generated binding.
Mounting was the one path with no logging at all, which left connection problems with nothing to diagnose from. Record the backend and the outcome at INFO and the location at DEBUG, following the redaction the directory loader already uses: the default level names no host, path, username or secret, and the diagnostic level logs a location already stripped of user-info, query and fragment.
`scripts/sftp-fixture.sh` runs an OpenSSH server on 127.0.0.1 whose host key, client keys, authorized_keys and served files all live in one temporary directory that is deleted when it stops, so exercising SFTP writes nothing to the developer's ~/.ssh. docs/remote-sftp.md covers the addresses, authentication methods, host-key handling and failure messages, and carries the manual matrix the fixture supports. That matrix stays manual because the crate is a binary with no library target for integration tests, and mounting through GVfs needs a live session bus and would write to the developer's known_hosts; the decision logic underneath it is unit-tested.
spandan11106
force-pushed
the
feat/63-sftp-support
branch
from
September 3, 2026 23:55
1764be3 to
48fde19
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Completes first-class SFTP support on the URI-native GIO/GVfs foundation. SMB was the only remote protocol exercised end to end, and the custom
gio::MountOperationhandled password prompts only — so a backend trust decision such as an unrecognized or changed SSH host key was silently dropped, and SFTP inherited SMB's prompt shape and error text.ask-questionis answered through a modal that renders the backend's own text (fingerprint included) and one button per offered choice. The declining choice is focused, so Enter never trusts a key; the backdrop is inert; Escape and the close button decline explicitly. A changed key is styled as a warning. gir cannot wrap the signal'sGStrv, so it is connected from the raw signal.MountAttempt. It decides whether a password request is replayed from already-supplied credentials or prompted, tracks the last attempt for the retry prompt, and turns a finished attempt into a single outcome. A declined host key now ends the attempt where a cancelled sign-in does, instead of reopening the credential prompt.INFOrecords the backend and outcome only; the location goes toDEBUGalready redacted of user-info, query and fragment.scripts/sftp-fixture.shruns sshd on127.0.0.1with host keys, client keys (plain and encrypted),authorized_keysand served files all inside one temporary directory that is deleted on exit. Nothing is written to~/.ssh.The log-capture helper in
test_supportalso had to grow a second caller.maininstalls that capture withset_global_default, which can only happen once, so it moves back to a per-thread subscriber; a permissive discarding global subscriber keeps the callsite-interest flake thatset_global_defaultwas working around from returning.Split into eight commits, each of which passes
cargo fmt --check,cargo clippy -D warningsand the full test suite on its own.Visual evidence
The SFTP credential prompt, reached by opening the fixture's address — username and password only, with no domain field and no anonymous option, unlike the SMB prompt:
Note what is not there: no Domain field and no "Connect as: Registered user / Anonymous" control. The SMB prompt still shows both, and before this change SFTP inherited them too.
How to test
./scripts/sftp-fixture.sh --port 2222. It prints thesftp://address to open, the client key paths, and the encrypted key's passphrase.Expected result: the address browses once authentication succeeds; every trust decision is made by you and never assumed; wrong credentials reopen the prompt without flicker; cancelling returns to the previous location without adding history; and each failure explains what to do next.
The manual matrix is manual because the crate is a binary with no library target for integration tests, and mounting through GVfs needs a live session bus and would write to the developer's
known_hosts. The decision logic it covers is unit-tested: prompt and question state transitions, cancellation, error mapping, message sanitization, and log privacy.Related issue
Closes #63