Keep the selected instance across a Fjord Account re-login #274

Merged
stephen merged 2 commits from fix/login-preserves-instance into main 2026-09-19 20:15:54 +00:00
Owner

fj auth login --fjord against a platform you were already signed into silently clears your default instance. Both Fjord sign-in flows (src/cli/auth_login.rs:523 and :625) built a fresh FjordAccount with default_instance_id: None and passed it to hosts.upsert, which replaces the entire host entry. The selection goes, and so do the cached display name, slug, and public URL that commands read instead of re-fetching.

The timing is what makes it sting. Re-login is the documented recovery from an expired session, so the command whose whole job is restoring access reports ✓ Signed in and then leaves the next command failing:

$ fj auth login --fjord
✓ Signed in to https://fjord.sh as stephen@rasterstate.com
$ fj api /version --host https://fjord.sh
error: no default Fjord instance selected. Run `fj instances list` and then `fj instances use <id>`.

Nothing was wrong with the account. The sign-in that just succeeded threw the setting away.

Both flows now build the block through one pure fjord_account_for. It carries the previous selection forward when the stored entry is a Fjord Account entry whose platform_url matches the one being signed into, and starts empty otherwise, so a first sign-in behaves exactly as before. The platform check matters because an instance id means nothing to a platform that did not issue it: a stale or hand-edited entry sitting at the same key must not have its instance inherited. auth_flow is always the flow actually used, so a device sign-in over a previous OIDC one still records credentials.

The two call sites also had their own local use crate::config::hosts::{AuthKind, FjordAccount} imports; those are hoisted to the module now that three places need them.

Found while verifying the recovery path in #273, and worth landing separately since it is a distinct defect in a different file.

Verified on macOS: cargo fmt --all clean, cargo clippy --all-targets --all-features -- -D warnings clean, cargo test --all green (801 + 5 + 1), cargo audit clean.

Carries the same rustls 0.23.40 to 0.23.45 lockfile bump as #273 (RUSTSEC-2026-0285), because the pre-push audit gate fails on main without it. Identical commit on both branches, so whichever lands first makes it a no-op for the other.

`fj auth login --fjord` against a platform you were already signed into silently clears your default instance. Both Fjord sign-in flows (`src/cli/auth_login.rs:523` and `:625`) built a fresh `FjordAccount` with `default_instance_id: None` and passed it to `hosts.upsert`, which replaces the entire host entry. The selection goes, and so do the cached display name, slug, and public URL that commands read instead of re-fetching. The timing is what makes it sting. Re-login is the documented recovery from an expired session, so the command whose whole job is restoring access reports `✓ Signed in` and then leaves the next command failing: ``` $ fj auth login --fjord ✓ Signed in to https://fjord.sh as stephen@rasterstate.com $ fj api /version --host https://fjord.sh error: no default Fjord instance selected. Run `fj instances list` and then `fj instances use <id>`. ``` Nothing was wrong with the account. The sign-in that just succeeded threw the setting away. Both flows now build the block through one pure `fjord_account_for`. It carries the previous selection forward when the stored entry is a Fjord Account entry whose `platform_url` matches the one being signed into, and starts empty otherwise, so a first sign-in behaves exactly as before. The platform check matters because an instance id means nothing to a platform that did not issue it: a stale or hand-edited entry sitting at the same key must not have its instance inherited. `auth_flow` is always the flow actually used, so a device sign-in over a previous OIDC one still records `credentials`. The two call sites also had their own local `use crate::config::hosts::{AuthKind, FjordAccount}` imports; those are hoisted to the module now that three places need them. Found while verifying the recovery path in #273, and worth landing separately since it is a distinct defect in a different file. Verified on macOS: `cargo fmt --all` clean, `cargo clippy --all-targets --all-features -- -D warnings` clean, `cargo test --all` green (801 + 5 + 1), `cargo audit` clean. Carries the same rustls 0.23.40 to 0.23.45 lockfile bump as #273 (RUSTSEC-2026-0285), because the pre-push audit gate fails on `main` without it. Identical commit on both branches, so whichever lands first makes it a no-op for the other.
RUSTSEC-2026-0285: TLS 1.3 handshake messages were accepted across
encryption level boundaries. Lockfile-only, no API change, and it is
what the pre-push audit gate is currently failing on.
Keep the selected instance across a Fjord Account re-login
All checks were successful
ci / check (pull_request) Successful in 12m0s
ci / live-e2e (pull_request) Successful in 2m14s
ci / coverage (pull_request) Successful in 2m14s
707bb6c778
Both Fjord sign-in flows built a fresh `FjordAccount` with
`default_instance_id: None` and handed it to `hosts.upsert`, which
replaces the whole host entry. Signing in again to a platform you were
already signed into therefore discarded the instance selection along
with its cached display name, slug, and public URL.

It lands hardest on the case the sign-in exists to fix. Re-login is the
documented recovery from an expired session, so the command that is
supposed to restore access leaves the next one failing with "no default
Fjord instance selected" immediately after reporting success.

Both flows now go through one pure `fjord_account_for`, which carries
the previous selection forward when the stored entry is a Fjord Account
entry for the same platform URL, and starts empty otherwise. An instance
id means nothing to a platform that did not issue it, so a mismatched or
hand-edited entry is not inherited.
stephen deleted branch fix/login-preserves-instance 2026-09-19 20:15:54 +00:00
Sign in to join this conversation.
No description provided.