Skip to content

Fix frontend settings auth wiring#32

Draft
cursor[bot] wants to merge 3 commits into
mainfrom
cursor/critical-bug-investigation-f5d7
Draft

Fix frontend settings auth wiring#32
cursor[bot] wants to merge 3 commits into
mainfrom
cursor/critical-bug-investigation-f5d7

Conversation

@cursor

@cursor cursor Bot commented Jun 3, 2026

Copy link
Copy Markdown

Bug and impact

The frontend settings UI saved backend endpoint values under keys that the API client did not read, and the API token input was never persisted to the request path. After a user saved Settings and triggered protected actions such as agent execution or builder calls, requests were sent to the wrong/default backend and without Authorization, causing backend 401 failures.

Root cause

frontend/src/lib/api.ts read api_url and auth_token from localStorage, while the settings page/modal wrote different endpoint keys and never connected the token input to the interceptor.

Fix

  • Added a shared settings storage helper for backend URL lookup/persistence and session-only auth token storage.
  • Updated the settings page and modal to use the shared helper.
  • Updated the API client to read the same backend URL and session token before each request.
  • Added regression coverage, including a real local HTTP server assertion that the configured /api base URL and bearer token are used.

Validation

Passed:

  • pnpm --filter @agentnexus/frontend test -- settings-storage.test.js --runInBand
  • pnpm --filter @agentnexus/frontend test -- --runInBand
  • pnpm --filter @agentnexus/frontend type-check
  • python3 -m pytest agents/paper-trader/test_price_feed.py

Pre-existing blockers observed outside this fix:

  • pnpm --filter @agentnexus/frontend lint fails because next lint is incompatible with the installed Next CLI.
  • Direct ESLint fails while loading the existing Next ESLint config with a circular config error.
  • pnpm --filter @agentnexus/frontend build fails in the existing wagmi/viem dependency graph (sendCallsSync / sendTransactionSync missing from viem/actions).
  • python3 -m ruff check ..., python3 -m bandit ..., and python3 -m mdformat --check ... report pre-existing issues unrelated to the frontend settings/auth changes.
Open in Web View Automation 

Greptile Summary

This PR fixes a long-standing settings/auth wiring bug where the settings UI wrote keys that the API client never read, and the auth token was never forwarded to requests. A shared settings-storage.ts helper is introduced so both the settings page and modal write to the same canonical backend_base_url and auth_token keys, which the axios client now reads per-request via interceptors.

  • settings-storage.ts centralises URL persistence (with legacy-key migration) and session-only token storage; both settings entry points are updated to use it.
  • api.ts gains a request interceptor that re-reads the backend URL and session token before each request, replacing the old stale module-level localStorage.getItem('api_url') lookup.
  • A regression test suite is added, including a real HTTP server assertion that the configured base URL and Bearer token reach the backend.

Confidence Score: 4/5

The core auth wiring fix is correct and well-structured; the token-clearing limitation means a user with a revoked session token has no recovery path short of closing the browser tab.

The key alignment and per-request interceptor pattern cleanly solves the stated bug. However, persistSessionAuthToken treats an empty input as a no-op, so a user who saves Settings after clearing the API key field will silently retain whatever broken token was already stored in sessionStorage — there is no way to revoke it within the same browser session without closing the tab.

frontend/src/lib/settings-storage.ts deserves a second look for the token-clearing path before merging.

Important Files Changed

Filename Overview
frontend/src/lib/settings-storage.ts New shared storage helper; correctly migrates legacy URL keys and uses sessionStorage for the token. Empty-string URL is written to localStorage unconditionally (removing legacy keys), creating a subtle reset-to-default behaviour documented in previous review threads.
frontend/src/lib/api.ts Per-request baseURL interceptor correctly reads the canonical storage key before each axios call; auth token interceptor similarly reads sessionStorage. Both fix the root bug.
frontend/src/lib/settings-storage.test.js Unit tests are solid; integration test spins a real Node HTTP server but runs inside the jsdom test environment, making XHR adapter behaviour environment-dependent (covered in previous review thread).
frontend/src/app/settings/page.tsx Now reads and writes using the shared helper; UI hint text updated to match blank-means-keep-current semantics.
frontend/src/components/layout/SettingsModal.tsx Migrated from inline localStorage reads to shared helper, and placeholder updated to match the settings page default port.
frontend/package.json viem bumped from ^2.21.58 to ^2.52.0; build failure with wagmi noted as a pre-existing blocker in the PR description.
frontend/jest.config.js Adds e2e directory to testPathIgnorePatterns to prevent Jest from picking up Playwright specs.

Sequence Diagram

sequenceDiagram
    participant UI as Settings Page / Modal
    participant SS as settings-storage.ts
    participant LS as localStorage
    participant SES as sessionStorage
    participant API as api.ts (axios)
    participant BE as Backend

    UI->>SS: persistBackendBaseUrl(url)
    SS->>LS: setItem('backend_base_url', url)
    SS->>LS: removeItem('api_url') [legacy]
    SS->>LS: removeItem('agentnexus_backend_url') [legacy]

    UI->>SS: persistSessionAuthToken(token)
    SS->>SES: setItem('auth_token', token)

    Note over API: Request interceptor #1 (per-request)
    API->>SS: getBackendBaseUrl(fallback)
    SS->>LS: getItem('backend_base_url')
    LS-->>SS: stored URL
    SS-->>API: resolved base URL
    API->>API: "config.baseURL = resolvedUrl + '/api'"

    Note over API: Request interceptor #2 (per-request)
    API->>SS: getSessionAuthToken()
    SS->>SES: getItem('auth_token')
    SES-->>SS: token
    SS-->>API: token
    API->>API: "config.headers.Authorization = 'Bearer ' + token"

    API->>BE: HTTP request with correct baseURL + Bearer token
Loading

Reviews (2): Last reviewed commit: "fix: align viem with wagmi runtime" | Re-trigger Greptile

cursoragent and others added 2 commits June 3, 2026 11:07
Co-authored-by: up2itnow0822 <up2itnow0822@gmail.com>
Co-authored-by: up2itnow0822 <up2itnow0822@gmail.com>
Comment on lines +40 to +45
export function persistSessionAuthToken(token: string): void {
const normalizedToken = token.trim();
if (!normalizedToken) return;

browserSessionStorage()?.setItem(AUTH_TOKEN_KEY, normalizedToken);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 No path to clear a session auth token once set

persistSessionAuthToken treats an empty string as a no-op and leaves the old token in sessionStorage untouched. The corresponding UI in both the settings page and modal also clears the input on open, so a user who enters a wrong or revoked token and then opens Settings again will find the field empty. Leaving it blank and saving does nothing — the broken token remains active for the rest of the browser session. The only recovery is closing the browser tab. A clearSessionAuthToken() export (e.g., browserSessionStorage()?.removeItem(AUTH_TOKEN_KEY)) plus a "Clear token" control in the UI would let users recover without losing their entire session.

Fix in Codex Fix in Cursor

Comment on lines +24 to +33
export function persistBackendBaseUrl(url: string): void {
const storage = browserLocalStorage();
if (!storage) return;

const normalizedUrl = url.trim();
storage.setItem(BACKEND_BASE_URL_KEY, normalizedUrl);
for (const legacyKey of LEGACY_BACKEND_BASE_URL_KEYS) {
storage.removeItem(legacyKey);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Legacy auth_token in localStorage left behind after migration

The URL migration explicitly removes old localStorage keys (api_url, agentnexus_backend_url) so they don't shadow the new canonical key. The auth token migration has no equivalent cleanup: any auth_token entry in localStorage from a prior session (manually set or from an older code path) is never removed. It won't be read by the new sessionStorage-based getter, but it does sit indefinitely in localStorage as a stale credential. Removing it alongside the URL cleanup would be consistent.

Suggested change
export function persistBackendBaseUrl(url: string): void {
const storage = browserLocalStorage();
if (!storage) return;
const normalizedUrl = url.trim();
storage.setItem(BACKEND_BASE_URL_KEY, normalizedUrl);
for (const legacyKey of LEGACY_BACKEND_BASE_URL_KEYS) {
storage.removeItem(legacyKey);
}
}
export function persistBackendBaseUrl(url: string): void {
const storage = browserLocalStorage();
if (!storage) return;
const normalizedUrl = url.trim();
storage.setItem(BACKEND_BASE_URL_KEY, normalizedUrl);
for (const legacyKey of LEGACY_BACKEND_BASE_URL_KEYS) {
storage.removeItem(legacyKey);
}
// Remove the legacy localStorage auth-token key to avoid stale credential leakage
storage.removeItem(AUTH_TOKEN_KEY);
}

Fix in Codex Fix in Cursor

Comment on lines +28 to +29
const normalizedUrl = url.trim();
storage.setItem(BACKEND_BASE_URL_KEY, normalizedUrl);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 persistBackendBaseUrl('') stores an empty string that is silently ignored on read

When the user clears the API Endpoint field and saves, normalizedUrl becomes '' and storage.setItem(BACKEND_BASE_URL_KEY, '') is called. getBackendBaseUrl then evaluates Boolean('') as false and falls back to the env-var default, meaning the stored empty string has no effect but the legacy keys have already been removed. A user who intended to "reset to default" loses their legacy-stored URL permanently and silently gets the env default instead. Adding an early return (matching the pattern in persistSessionAuthToken) avoids the ambiguous write.

Suggested change
const normalizedUrl = url.trim();
storage.setItem(BACKEND_BASE_URL_KEY, normalizedUrl);
const normalizedUrl = url.trim();
if (!normalizedUrl) return;
storage.setItem(BACKEND_BASE_URL_KEY, normalizedUrl);

Fix in Codex Fix in Cursor

Comment on lines +67 to +95
it('forwards the session token to the configured backend URL on API requests', async () => {
let receivedRequest;
const server = http.createServer((req, res) => {
res.setHeader('Access-Control-Allow-Origin', 'http://localhost');
res.setHeader('Access-Control-Allow-Headers', 'authorization,content-type');
if (req.method === 'OPTIONS') {
res.writeHead(204);
res.end();
return;
}

receivedRequest = req;
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end('[]');
});

const address = await listen(server);
persistBackendBaseUrl(`http://127.0.0.1:${address.port}`);
persistSessionAuthToken(' protected-token ');

try {
await api.get('/agents');
} finally {
await close(server);
}

expect(receivedRequest.url).toBe('/api/agents');
expect(receivedRequest.headers.authorization).toBe('Bearer protected-token');
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Integration test relies on jsdom's XHR reaching a real 127.0.0.1 socket

The test environment is jest-environment-jsdom, which means axios picks the XHR adapter (because window.XMLHttpRequest is present). jsdom's XHR implementation does not make real network calls in all versions — whether this works depends on the exact jsdom/jest-environment-jsdom version. If the XHR silently fails, api.get('/agents') throws before receivedRequest is set; the finally closes the server and the error propagates, which is correct test failure behaviour. But the test is more fragile than it looks. Forcing the Node.js http adapter (e.g., axios.defaults.adapter = 'http') or switching to a node test environment for this file would make it deterministically reliable.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Codex Fix in Cursor

Co-authored-by: up2itnow0822 <up2itnow0822@gmail.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Duplicate draft — recommend close

Superseded by #35. This branch partially addresses settings auth but #35 has the complete apiConfig.ts module with tests.

Open in Web View Automation 

Sent by Cursor Automation: Untitled

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.

1 participant