Skip to content

Remove Personal Nexus API Keys and Temporarily Disable Nexus Support#1763

Merged
utkarshdalal merged 4 commits into
utkarshdalal:masterfrom
Nightwalker743:fix/remove-nexus-personal-api-keys
Jul 23, 2026
Merged

Remove Personal Nexus API Keys and Temporarily Disable Nexus Support#1763
utkarshdalal merged 4 commits into
utkarshdalal:masterfrom
Nightwalker743:fix/remove-nexus-personal-api-keys

Conversation

@Nightwalker743

@Nightwalker743 Nightwalker743 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Description

Removes Personal API key authentication from Nexus Mods support to comply with the Nexus Mods API policy while GameNative completes registration and receives OAuth credentials.

Recording

N/A

Type of Change

  • Bug fix
  • Performance / stability improvement
  • Compatibility improvements
  • Other (requires prior approval)

Checklist

  • If I have access to #code-changes, I have discussed this change there and it has been green-lighted. If I do not have access, I have still provided clear context in this PR. If I skip both, I accept that this change may face delays in review, may not be reviewed at all, or may be closed.
  • This change aligns with the current project scope (core functionality, stability, or performance). If not, it has been explicitly approved beforehand.
  • I have attached a recording of the change.
  • I have read and agree to the contribution guidelines in CONTRIBUTING.md.

Summary by CodeRabbit

  • New Features

    • Nexus Mods integration now uses secure account authentication instead of personal API keys.
    • Added a clear notice when Nexus Mods online access is temporarily unavailable.
    • Existing installed mods remain manageable during the service interruption.
  • Bug Fixes

    • Imports requiring online downloads are paused safely and can resume when access returns.
    • Authorization callbacks and new imports are blocked when online access is unavailable.
    • Improved authentication error messaging and handling.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e05bb169-d9d9-40e9-914d-5f538c068f5e

📥 Commits

Reviewing files that changed from the base of the PR and between a0c1b26 and a5d48b3.

📒 Files selected for processing (1)
  • app/src/main/java/app/gamenative/PrefManager.kt
💤 Files with no reviewable changes (1)
  • app/src/main/java/app/gamenative/PrefManager.kt

📝 Walkthrough

Walkthrough

Nexus integration now uses injected bearer-token authentication, removes persisted API-key handling, gates online workflows behind a disabled integration status, pauses affected imports, updates UI messaging, and adds localized temporary-unavailability text.

Changes

Nexus online access transition

Layer / File(s) Summary
Authentication contract and retired key cleanup
app/src/main/java/app/gamenative/mods/NexusApiClient.kt, app/src/main/java/app/gamenative/PrefManager.kt, app/src/test/java/app/gamenative/mods/NexusApiClientTest.kt
Nexus requests use bearer tokens from an injected provider; API-key parameters, persistence, validation, and related tests are removed.
Unavailable-access import handling
app/src/main/java/app/gamenative/service/NexusModImportService.kt, app/src/main/java/app/gamenative/mods/NexusImportState.kt, app/src/main/java/app/gamenative/mods/NexusModManager.kt, app/src/test/java/app/gamenative/mods/NexusImportStateTest.kt
Link-dependent imports are paused when online access is unavailable, complete archives remain resumable, and authentication errors use the new message override.
UI actions and callback gating
app/src/main/java/app/gamenative/ui/component/dialog/NexusModsDialog.kt, app/src/main/java/app/gamenative/MainActivity.kt, app/src/main/java/app/gamenative/mods/NexusIntegrationStatus.kt
Online actions, browser callbacks, NXM callbacks, and import controls are gated by ONLINE_ACCESS_AVAILABLE; the API-key section is replaced with an unavailable-integration section.
Unavailable integration messaging
app/src/main/java/app/gamenative/ui/component/dialog/NexusModsImportSections.kt, app/src/main/res/values*/strings.xml, app/src/main/java/app/gamenative/mods/NexusUrlParser.kt
Localized temporary-unavailability text replaces API-key setup and validation strings, and authorization documentation refers to Nexus account authentication.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant NexusModsDialog
  participant NexusModImportService
  participant NexusApiClient
  participant NexusAPI
  User->>NexusModsDialog: start Nexus import
  NexusModsDialog->>NexusApiClient: request authenticated data
  NexusApiClient->>NexusAPI: send bearer-token request
  NexusAPI-->>NexusApiClient: return response
  NexusModsDialog->>NexusModImportService: enqueue import
  NexusModImportService->>NexusModImportService: pause link-dependent work when access is unavailable
Loading

Possibly related PRs

Suggested reviewers: utkarshdalal

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: removing personal Nexus API keys and disabling Nexus support while OAuth is pending.
Description check ✅ Passed The PR description includes the required sections and clearly explains the change, type, and checklist items, with only the recording marked N/A.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Nightwalker743
Nightwalker743 marked this pull request as ready for review July 23, 2026 02:30

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
app/src/main/java/app/gamenative/service/NexusModImportService.kt (1)

182-233: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated resumable-install pause/filter logic across three methods.

The "query resumable installs → filter registered downloads → partition by hasCompletePendingArchive → pause downloadsNeedingLinks via NexusImportState.pauseWhileOnlineAccessUnavailable" sequence is repeated with small variations in the instance resumeInterruptedImports(), the companion resumeInterruptedImports(context), and pauseInterruptedImports(context). Extracting a shared private helper (e.g. returning (completeArchives, downloadsNeedingLinks) and a separate pauseDownloads(dao, downloadsNeedingLinks, message) helper) would reduce the risk of these three copies silently diverging as this logic evolves (e.g. once OAuth rolls out).

♻️ Sketch of a shared helper
private suspend fun interruptedInstalls(dao: NexusModInstallDao): List<ModInstall> =
    NexusModManager.resumableImportStatuses
        .flatMap { status -> dao.getInstallsByStatus(status) }
        .distinctBy { it.installId }
        .filter { ModDownloadRegistry.get(it.installId) == null }

private suspend fun pauseDownloadsNeedingLinks(
    dao: NexusModInstallDao,
    downloads: List<ModInstall>,
    message: String,
) {
    downloads.forEach { install ->
        val paused = NexusImportState.pauseWhileOnlineAccessUnavailable(install, message)
        if (paused != install) dao.upsertInstall(paused)
    }
}

Also applies to: 406-427, 441-453

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/app/gamenative/service/NexusModImportService.kt` around
lines 182 - 233, Extract the duplicated resumable-install query,
registered-download filtering, and complete-archive partitioning used by the
instance and companion resumeInterruptedImports() methods and
pauseInterruptedImports() into shared private helpers in the containing
class/object. Add a separate helper for applying
NexusImportState.pauseWhileOnlineAccessUnavailable and persisting changed
installs, then update all three callers to use these helpers while preserving
their existing online-access and authorization-specific filters.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@app/src/main/java/app/gamenative/service/NexusModImportService.kt`:
- Around line 182-233: Extract the duplicated resumable-install query,
registered-download filtering, and complete-archive partitioning used by the
instance and companion resumeInterruptedImports() methods and
pauseInterruptedImports() into shared private helpers in the containing
class/object. Add a separate helper for applying
NexusImportState.pauseWhileOnlineAccessUnavailable and persisting changed
installs, then update all three callers to use these helpers while preserving
their existing online-access and authorization-specific filters.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b92f1ecb-7493-466b-ad1b-83e41cc5409b

📥 Commits

Reviewing files that changed from the base of the PR and between 4b6d26d and fac7dd6.

📒 Files selected for processing (27)
  • app/src/main/java/app/gamenative/MainActivity.kt
  • app/src/main/java/app/gamenative/PrefManager.kt
  • app/src/main/java/app/gamenative/mods/NexusApiClient.kt
  • app/src/main/java/app/gamenative/mods/NexusImportState.kt
  • app/src/main/java/app/gamenative/mods/NexusIntegrationStatus.kt
  • app/src/main/java/app/gamenative/mods/NexusUrlParser.kt
  • app/src/main/java/app/gamenative/service/NexusModImportService.kt
  • app/src/main/java/app/gamenative/ui/component/dialog/NexusModsDialog.kt
  • app/src/main/java/app/gamenative/ui/component/dialog/NexusModsImportSections.kt
  • app/src/main/res/values-da/strings.xml
  • app/src/main/res/values-de/strings.xml
  • app/src/main/res/values-es/strings.xml
  • app/src/main/res/values-fr/strings.xml
  • app/src/main/res/values-it/strings.xml
  • app/src/main/res/values-ja/strings.xml
  • app/src/main/res/values-ko/strings.xml
  • app/src/main/res/values-pl/strings.xml
  • app/src/main/res/values-pt-rBR/strings.xml
  • app/src/main/res/values-ro/strings.xml
  • app/src/main/res/values-ru/strings.xml
  • app/src/main/res/values-uk/strings.xml
  • app/src/main/res/values-zh-rCN/strings.xml
  • app/src/main/res/values-zh-rTW/strings.xml
  • app/src/main/res/values/strings.xml
  • app/src/test/java/app/gamenative/NexusPersonalApiKeyMigrationTest.kt
  • app/src/test/java/app/gamenative/mods/NexusApiClientTest.kt
  • app/src/test/java/app/gamenative/mods/NexusImportStateTest.kt

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 27 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="app/src/main/java/app/gamenative/mods/NexusImportState.kt">

<violation number="1" location="app/src/main/java/app/gamenative/mods/NexusImportState.kt:204">
P3: This newly changed user-facing wording cannot be localized while it remains an inline literal; representing the message through a string resource (or a resource ID resolved at the UI boundary) would keep Nexus errors translatable.

(Based on your team's feedback about localizing user-facing strings.) [FEEDBACK_USED]</violation>
</file>

<file name="app/src/main/java/app/gamenative/ui/component/dialog/NexusModsDialog.kt">

<violation number="1" location="app/src/main/java/app/gamenative/ui/component/dialog/NexusModsDialog.kt:1531">
P2: Re-enabling Nexus online access will leave this account lookup unauthenticated because the default `NexusApiClient()` has no OAuth token provider. Wiring the registered OAuth credentials into the client before flipping `ONLINE_ACCESS_AVAILABLE` would prevent all Nexus imports from failing at account validation.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread app/src/main/java/app/gamenative/mods/NexusImportState.kt Outdated
if (apiKey.trim() == requestedKey) nexusUserInfo = it
}
PrefManager.saveNexusApiKey(requestedKey)
val user = knownUser ?: apiClient.getCurrentUser().also { nexusUserInfo = it }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Re-enabling Nexus online access will leave this account lookup unauthenticated because the default NexusApiClient() has no OAuth token provider. Wiring the registered OAuth credentials into the client before flipping ONLINE_ACCESS_AVAILABLE would prevent all Nexus imports from failing at account validation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/java/app/gamenative/ui/component/dialog/NexusModsDialog.kt, line 1531:

<comment>Re-enabling Nexus online access will leave this account lookup unauthenticated because the default `NexusApiClient()` has no OAuth token provider. Wiring the registered OAuth credentials into the client before flipping `ONLINE_ACCESS_AVAILABLE` would prevent all Nexus imports from failing at account validation.</comment>

<file context>
@@ -1574,18 +1520,15 @@ fun NexusModsDialog(
-                    if (apiKey.trim() == requestedKey) nexusUserInfo = it
-                }
-                PrefManager.saveNexusApiKey(requestedKey)
+                val user = knownUser ?: apiClient.getCurrentUser().also { nexusUserInfo = it }
                 val authorizationUserId = reference.downloadAuthorization?.userId
                 if (authorizationUserId != null && authorizationUserId != user.userId) {
</file context>

Comment thread app/src/main/java/app/gamenative/PrefManager.kt Outdated
Comment thread app/src/main/java/app/gamenative/service/NexusModImportService.kt
val base = when (error.reason) {
NexusApiErrorReason.AUTHENTICATION -> "Nexus rejected the API key. Reconnect the Nexus account and try again."
NexusApiErrorReason.AUTHENTICATION ->
"Nexus account authorization is unavailable or was rejected. Reconnect the Nexus account and try again."

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This newly changed user-facing wording cannot be localized while it remains an inline literal; representing the message through a string resource (or a resource ID resolved at the UI boundary) would keep Nexus errors translatable.

(Based on your team's feedback about localizing user-facing strings.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/java/app/gamenative/mods/NexusImportState.kt, line 204:

<comment>This newly changed user-facing wording cannot be localized while it remains an inline literal; representing the message through a string resource (or a resource ID resolved at the UI boundary) would keep Nexus errors translatable.

(Based on your team's feedback about localizing user-facing strings.) </comment>

<file context>
@@ -177,7 +200,8 @@ internal object NexusImportState {
         val base = when (error.reason) {
-            NexusApiErrorReason.AUTHENTICATION -> "Nexus rejected the API key. Reconnect the Nexus account and try again."
+            NexusApiErrorReason.AUTHENTICATION ->
+                "Nexus account authorization is unavailable or was rejected. Reconnect the Nexus account and try again."
             NexusApiErrorReason.FORBIDDEN -> "Nexus denied access to this resource for the current account."
             NexusApiErrorReason.DOWNLOAD_AUTHORIZATION_REQUIRED ->
</file context>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 6 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread app/src/main/java/app/gamenative/PrefManager.kt Outdated
Comment thread app/src/main/java/app/gamenative/service/NexusModImportService.kt
Comment on lines +93 to +110
// Keep this cleanup for several releases so restored backups are also scrubbed.
// No production code reads or sends the retired value while removal runs on IO.
if (retiredNexusPersonalApiKeyCleanupScheduled.compareAndSet(false, true)) {
val initializedDataStore = dataStore
scope.launch {
runCatching {
initializedDataStore.edit { preferences ->
if (preferences.hasRetiredNexusPersonalApiKey()) {
Timber.i("Removing retired Nexus Personal API key")
preferences.removeRetiredNexusPersonalApiKey()
}
}
}.onFailure {
retiredNexusPersonalApiKeyCleanupScheduled.set(false)
Timber.w(it, "Failed to remove retired Nexus Personal API key; will retry")
}
}
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this necessary? Let's not add additional permanent logic to the app, the feature was never released fully so we can assume there's nothing to clean up

Comment on lines +305 to +309
if (!NexusIntegrationStatus.ONLINE_ACCESS_AVAILABLE) {
Timber.i("[NexusDownload]: Ignoring NXM callback while online Nexus access is disabled")
SnackbarManager.show(getString(R.string.nexus_integration_temporarily_unavailable))
return
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will this show a message for everyone everytime they open the app or are offline?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, this only runs when GN receives an ACTION_VIEW intent containing an nxm:// URL, such as when someone clicks “Mod Manager Download" on the Nexus Mods website.

@utkarshdalal
utkarshdalal merged commit 986b249 into utkarshdalal:master Jul 23, 2026
3 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Jul 24, 2026
8 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants