feat(nexus): remove personal API keys, sign in with OAuth only - #7
Open
MotherSphere wants to merge 5 commits into
Open
feat(nexus): remove personal API keys, sign in with OAuth only#7MotherSphere wants to merge 5 commits into
MotherSphere wants to merge 5 commits into
Conversation
Nexus's API team will not issue a client_id while the client can use a personal API key, even as a fallback: those keys are documented on their side as being for testing and personal use, not for a distributed application. They asked to see a build without any such usage. So it is gone, not disabled. `Credential` is one variant. `choose_credential` is Bearer, or refresh, or nothing - a lapsed session with no renewable token now reports itself instead of reaching for a key. `NexusCreds` has no `api_key` field, `nexus.ini` no longer writes one, the `eidos nexus key` command is gone, and the Settings dialog has no field to type one into. The token endpoint may still return `api_key` alongside the tokens; it is no longer parsed, because a key kept "just in case" is exactly what the requirement rules out. The OAuth flow existed as a library and had never been wired to a user action - there was no client_id to test it with, so nothing called it. Removing the key without wiring it would have produced a build with no way to authenticate at all, which demonstrates nothing. Settings -> Nexus now runs the whole dance: PKCE S256 challenge, browser hand-off, loopback listener on 127.0.0.1, code exchange, session stored. Sign out is beside it. An `api_key=` line left in someone's nexus.ini by an older version is passed through untouched rather than rewritten away - not reading it is the requirement, deleting a user's file is not. Eidos therefore has no Nexus access at all until Nexus issues a client_id. That is the intended state, and the error messages say so rather than failing vaguely. 615 tests green, clippy clean.
The request path no longer sends one; the comment beside it still said it did.
This was referenced Aug 4, 2026
MotherSphere
added a commit
that referenced
this pull request
Aug 4, 2026
main.rs was 2493 lines - every subcommand, the whole launch pipeline and the profile staging in one file. Pure moves, routed by item name: main.rs 274 dispatch, usage, and the Nexus commands launch.rs 481 eidos play: extender swap, mount, run, capture prepare.rs 470 staging the profile into the prefix, and reading it back tools.rs 281 eidos tool tests.rs 275 the unit tests, file-backed prereqs.rs 218 eidos prereqs nxm.rs 157 the nxm:// handler sort.rs 148 eidos sort (LOOT) install.rs 134 eidos install / import export.rs 123 eidos export games.rs 50 eidos games / init The Nexus trio (cmd_nexus, nexus_client, nexus_key_path) deliberately stays in main.rs: PR #7 rewrites exactly those functions, and moving them now would turn a pending review branch into a wall-to-wall conflict. They move once #7 lands. Verified as the fuse split was: item inventory identical (the nine additions are the test module's own fns and its Tmp helper surfacing to file level), workspace suite green, and the split binary smoke-run against the real machine.
MotherSphere
added a commit
that referenced
this pull request
Aug 4, 2026
…od (#12) profile.rs was one 800-line impl Profile plus its satellites. The unit of the move is the METHOD here - Rust allows inherent impls across files, so each concern gets its own impl Profile block beside the free items it owns: profile.rs 91 Profile and SaveEntry themselves, the path roots modlist.rs 385 modlist.txt round-trip, ListTrust, lifecycle plugins.rs 350 plugins.txt, the locked order, snapshot + loss guard inis.rs 327 seeding, deploy, capture, the reversible tweaks saves.rs 236 enumeration, co-saves, crash-artifact detection tests.rs 889 the unit tests, file-backed settings.rs deliberately untouched: PR #7 and PR #8 both edit it, and a move now would conflict with both. Verified at the level the cut happened: 89 functions before, 89 after, none lost and none invented. Workspace suite green, clippy clean.
Brings in the six PRs merged since this branch was cut (#8 parity, #9-#12 crate splits, #13 the overwrite index) plus the 1.3.0 release. Two conflicts, both in the Settings screen, which main rewrote into Colony's collapsible sections while this branch was replacing the API-key field with OAuth sign-in. Resolved by keeping main's structure and putting the sign-in UI inside it: the Account section now holds the sign-in / sign-out pair and the session status instead of a masked key field, and the app state keeps `settings_expanded` while dropping `settings_api_key`, which no longer has anything to hold.
…the budget
Two Terms-of-Service points Nexus raised reviewing the OAuth-only build.
**Age restriction.** The gate lives inside the client, in `RemoteMod::from_payload`,
not at the display sites. Three reasons it cannot sit further out: the descriptive
text reaches a dozen call sites across the CLI and the GUI, so gating each one is
a leak per site added later; the text is PERSISTED before anything is displayed
(`write_download_meta` puts `modName` in the sidecar, and `eidos-install` turns
that into the directory name under mods/), which no display-side check could take
back; and put here, the compiler can enforce it. A mod the account may not see has
its name, summary and category blanked before the struct exists, and the `ModGate`
minted alongside them is what `file_info` and `download_link` demand - so there is
no way to reach a file or a download link without having passed the lookup that
resolves the rating. `nxm.rs` now looks the mod up before its file for that reason.
The account's setting comes from `preferences { adult }` on the GraphQL endpoint,
which is where Nexus keeps it - `users/validate` does not carry it. It is cached
in `nexus.ini` for a day and forgotten on sign-out. Everything fails closed: no
session, an unreadable preference, a stale cache, or a payload with no rating at
all all hide the mod, each with its own message so "we could not check" is never
confused with "you turned it off". `version` survives redaction, because comparing
it is what the update check does and a version string describes nothing - a user
with an adult mod already installed keeps seeing that an update exists.
**The request budget.** `get` and `send_with_version` now begin with a pre-flight
check, so the client stops as soon as `X-RL-Hourly-Remaining` or
`X-RL-Daily-Remaining` reaches 0 instead of waiting to be told 429. Either counter
is enough, which is stricter than Nexus's own documented rule (both buckets must
be spent) and is what was asked for. The block lifts on the UTC boundary the docs
guarantee, so no probe request is needed and no date parser either. Unknown always
means "go ahead": a client that has seen no headers yet must be able to send the
request that teaches it the budget.
Three bugs found on the way, each of which would have defeated the fix:
- `capture_limits` rebuilt the whole struct from every reply, so an answer with no
`X-RL-*` headers - a 401 carries none - wiped a known-exhausted budget back to
unknown and the next request went straight out.
- the loops disagreed on what a rate-limit error looks like: the library matched
"rate limited", the CLI matched "429". A pre-flight refusal carries no status
code, so it would have stopped one and left the other hammering. Both now share
`is_rate_limited`.
- a 429 arriving while the counters still showed budget is the burst guard, not
the quota; it now backs off for a minute instead of idling until the next hour.
Unavailable mods are withheld through the same path, matching what Vortex filters.
Adult mods coming back blank with no explanation reads as Eidos being broken. The Nexus panel now states which of the three cases applies, and the one the user can act on - "Eidos could not read your content settings" - is worded differently from "you turned it off", because the fixes are different. Read from the credential store rather than plumbed through app state: that store is what the client itself consults, and a second copy in the UI could end up disagreeing with what is actually being withheld.
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.
For Nexus review. Not to be merged until a
client_idis issued - see below.Nexus's API team requires all personal-API-key usage removed from the client before issuing a
client_id, including as a fallback, and asked to see a build without it.What was removed
Credentialis a single variant. There is noAPIKEYheader path.choose_credentialis Bearer / refresh / nothing. A lapsed session that cannot be renewed now reports itself instead of falling back to a key.NexusCredshas noapi_keyfield;nexus.inino longer writes one.eidos nexus keyCLI command is gone.api_keybeside the tokens. It is no longer parsed - a key kept "just in case" is exactly what the requirement rules out.An
api_key=line left in a user'snexus.iniby an older version is passed through untouched rather than rewritten away. Not reading it is the requirement; deleting someone's file is not.What was added
The OAuth flow existed as a library and had never been wired to a user action, because there was no
client_idto test it with. Removing the key without wiring it would have produced a build with no way to authenticate at all, which demonstrates nothing.Settings -> Nexus now runs the whole flow: PKCE S256 challenge, browser hand-off, loopback listener on
127.0.0.1only, code exchange, session stored innexus.ini. Sign out sits beside it.The consequence, stated plainly
Eidos ships no default
client_idand will not fall back to identifying itself as another application. With personal keys gone and noclient_idissued, this build has no Nexus access at all. That is the intended state rather than an oversight, and the error messages say so.The moment a
client_idis issued, this becomes the shipped version.615 tests green, clippy clean under CI's exact command.