Skip to content

fix(auth): offer the API-key login on server-mode admin 403s - #1569

Open
paoloantinori wants to merge 2 commits into
debpalash:mainfrom
paoloantinori:fix/remote-admin-403-auth-gate
Open

fix(auth): offer the API-key login on server-mode admin 403s#1569
paoloantinori wants to merge 2 commits into
debpalash:mainfrom
paoloantinori:fix/remote-admin-403-auth-gate

Conversation

@paoloantinori

@paoloantinori paoloantinori commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Fixes #1568

What

  • Frontend (client.ts): a 403 whose detail names the admin API key now routes into the existing ov:auth-required flow (mode: 'apikey'), exactly like the 401 paths — RemoteAuthGate shows the API-key form, the app tree unmounts, and the infinite-retry polling stops instead of spamming the console. Other 403s (CSRF "browser origin rejected", desktop-only routes) deliberately stay plain errors, because presenting a key cannot satisfy them.
  • Backend (dependencies.py): new _admin_gate_403() emits a mode-distinct detail. Server mode keeps "loopback origin or admin API key required" (a key genuinely satisfies the gate there); desktop mode now answers "loopback origin required" — truthful, since the admin-credential checks only run under _server_mode(), so no presented key can ever satisfy the desktop gate. Without this split, routing on the message alone would trap a desktop PIN-share guest in a login form that can never succeed.
  • Lockstep test (tests/test_auth_gate_detail_lockstep.py): pins the literal contract across layers — the frontend matcher ("admin api key") must keep matching the backend's server-mode detail and must NOT match the details a key cannot fix. A backend reword without updating client.ts fails CI instead of silently regressing to 403 spam.

Tests

  • New frontend cases in client.test.ts: admin-gate 403 dispatches {mode:"apikey"}; CSRF 403 does not dispatch (fail-before/pass-after).
  • New backend cases in test_loopback_server_mode.py: per-mode detail for require_admin + require_admin_action (desktop → plain loopback, server+key → key-naming).
  • Updated the two desktop-arm assertions in test_engines_route_shape.py to the new truthful detail.
  • Suites run on top of current main: backend 208 passed, frontend api 95 passed; oxfmt --check clean.

Verification

On a live LAN deployment (server mode + OMNIVOICE_API_KEY): before, a remote browser logged /sysinfo, /model/status, /system/notifications 403s every few seconds indefinitely. After: 3 initial 403s, the API-key gate renders, the exchange produces a short-lived session, and all polls return 200.

An adversarial review pass caught the first version of the frontend-only fix breaking desktop LAN-share guests (unsatisfiable key form); the mode-distinct backend detail is the root-cause fix for that class.

Server-mode admin 403 responses now trigger API-key authentication, while unrelated 403 responses remain errors and stale responses cannot clear newer sessions. Backend details now reflect mode and API-key availability, with cross-layer tests covering routing and recovery. Review the frontend/backend error-detail matching because mismatches can break authentication routing.

Review follow-ups (commit e2bfeaa)

Greptile's review on 014e18c flagged two P1s, both fixed with fail-before/pass-after tests:

  1. Unsatisfiable PIN-only login gate (dependencies.py): server mode + share PIN + no OMNIVOICE_API_KEY closed read-only bootstrap while no key existed to present, so the key-naming detail gated the whole UI behind a form whose exchange can never succeed. _admin_gate_403() now names the key only when server mode and an API key is configured; PIN-only and bare servers answer the plain loopback detail (truthful: only loopback can use admin when no key exists), which the SPA leaves a plain error. New test: test_require_admin_pin_only_server_mode_detail_is_plain_loopback.
  2. Stale 403 clears new session (client.ts): a late 403/401 landing after a key exchange wiped the fresh session (clearAdminSession() was unconditional), reloading a successful login straight back into the gate. A failed response may now only invalidate the credentials it actually carried (send-time session capture + token compare). New test: a stale 403 does not clear a session stored during its flight.

CodeRabbit's docstring-coverage warning on the new test functions is also addressed.

…sh#1568)

Since debpalash#1525 the /system router sits behind require_admin, so an
unauthenticated non-loopback browser gets 403 where it used to get 401.
The SPA routed only 401s into the ov:auth-required flow, so a remote
browser spammed 403s on /sysinfo, /model/status, /system/notifications
forever (polling hooks retry infinitely) and was never offered the
master-for-session exchange.

Frontend: route a 403 whose detail names the admin API key into the
existing apikey gate; other 403s (CSRF, desktop-only) stay plain errors
because no key can satisfy them.

Backend: _admin_gate_403() emits a mode-distinct detail — server mode
keeps "loopback origin or admin API key required" (a key genuinely
satisfies it), desktop mode answers "loopback origin required"
(truthful: the credential checks only run under server mode, so
routing desktop guests to a key form would loop forever).

tests/test_auth_gate_detail_lockstep.py locks the literal contract
across layers so a backend reword fails CI unless client.ts moves with
it. Verified end-to-end on a live LAN deployment: gate renders after 3
initial 403s, key exchange produces a session, all polls 200.
@coderabbitai

coderabbitai Bot commented Aug 15, 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d8cb7d03-9c94-44f3-83f1-fce79a446ffa

📥 Commits

Reviewing files that changed from the base of the PR and between 014e18c and e2bfeaa.

📒 Files selected for processing (5)
  • backend/api/dependencies.py
  • frontend/src/api/client.test.ts
  • frontend/src/api/client.ts
  • tests/test_auth_gate_detail_lockstep.py
  • tests/test_loopback_server_mode.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/src/api/client.ts
  • tests/test_auth_gate_detail_lockstep.py
  • backend/api/dependencies.py

📝 Walkthrough

Walkthrough

Changes

The backend now returns mode-specific admin-gate 403 details. Server-mode API-key errors enter the frontend authentication flow. Desktop, CSRF, and unrelated 403 responses remain ordinary errors. Tests and documentation cover the behavior.

Admin 403 authentication

Layer / File(s) Summary
Mode-specific admin-gate responses
backend/api/dependencies.py, tests/test_loopback_server_mode.py
require_admin and require_admin_action use a shared response helper. Server mode with an API key reports the admin API-key requirement. Other modes report the loopback-only requirement.
API-key authentication routing
frontend/src/api/client.ts, frontend/src/api/client.test.ts
apiFetch routes matching 403 details to ov:auth-required with API-key mode. Other 403 responses do not trigger authentication. Stale failed requests do not clear newer sessions.
Cross-layer validation and documentation
tests/test_auth_gate_detail_lockstep.py, tests/backend/api/test_engines_route_shape.py, docs/api-auth.md, CHANGELOG.md
Tests enforce backend/frontend detail matching and update route expectations. Documentation and the changelog describe the distinct 403 behavior.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to e2bfe

The change routes server-mode admin 403 responses to the API-key login while preserving non-fixable 403 errors; no actionable merge-blocking risk remains after normal checks and review.

Possibly related PRs

Suggested reviewers: debpalash, bultodepapas

🚥 Pre-merge checks | ✅ 8 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (8 passed)
Check name Status Explanation
Title check ✅ Passed The title uses Conventional Commit format with scope and clearly describes the server-mode admin 403 fix; issue #1568 is referenced in the description.
Description check ✅ Passed The description clearly documents the change, testing, verification, and follow-up fixes, although it does not use every template heading or checklist item.
Linked Issues check ✅ Passed The changes satisfy issue #1568 by enabling API-key login for eligible admin 403 responses while preserving unsatisfiable-error handling and preventing stale session invalidation.
Out of Scope Changes check ✅ Passed The backend, frontend, documentation, and regression-test changes directly support the linked issue objectives, with no unrelated scope identified.
Cross-Platform Default Parity ✅ Passed Changed paths add no OS/platform branches; desktop behavior uses the same mode logic on all systems, while server behavior requires explicit OMNIVOICE_SERVER_MODE.
I18n Completeness (21 Locales) ✅ Passed The PR changes only client logic/tests; the frontend diff adds no t(...) keys and no new user-facing UI strings, so no locale file is missing a translation.
Local-First Guarantee ✅ Passed Full diff adds no cloud endpoint, account flow, telemetry, or outbound call; it only reads OMNIVOICE_API_KEY, dispatches a local auth event, and updates session storage.
Backward Compatibility ✅ Passed The PR changes only admin-authentication responses, frontend session handling, docs, and tests; no database schema, migration, data, engine-install, or model-weight code changed.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


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.

@greptile-apps

greptile-apps Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR routes satisfiable server-mode administrator 403 responses into the existing API-key flow while keeping desktop and PIN-only failures outside that gate.

  • Adds mode- and configuration-aware administrator-gate error details.
  • Preserves replacement sessions when stale authentication failures arrive.
  • Adds frontend, backend, and cross-layer contract tests for the authentication routing.

Important Files Changed

Filename Overview
backend/api/dependencies.py The new detail helper accurately names the API key only when server mode has a configured key that can satisfy the administrator gate.
frontend/src/api/client.ts Administrator-gate 403 routing and request-token-aware session invalidation address both previously reported authentication failures.
frontend/src/api/client.test.ts Tests cover administrator 403 routing, unrelated 403 exclusion, and preservation of a session created while a stale request is in flight.
tests/test_loopback_server_mode.py Tests pin desktop, API-key server, and PIN-only server detail behavior for both administrator dependencies.
tests/test_auth_gate_detail_lockstep.py The cross-layer test protects the literal discriminator between backend details and frontend authentication routing.

Reviews (2): Last reviewed commit: "fix(auth): review follow-ups for #1569 —..." | Re-trigger Greptile

Comment thread backend/api/dependencies.py
Comment thread frontend/src/api/client.ts Outdated
…e-403 race

Greptile P1 #1 (unsatisfiable PIN-only gate): in server mode with a
share PIN but no OMNIVOICE_API_KEY, the PIN closes read-only bootstrap
while no key exists to present — the key-naming detail sent the browser
to a login form whose exchange can never succeed, hiding the whole app
behind an unrecoverable gate. _admin_gate_403() now names the key only
when server mode AND an API key is configured; PIN-only and bare
servers answer the plain loopback detail, which is truthful (only
loopback can use admin when no key exists) and keeps the SPA out of
the gate.

Greptile P1 debpalash#2 (stale 403 clears new session): a late 403/401 landing
after a key exchange wiped the fresh session via the unconditional
clearAdminSession(), reloading a successful login straight back into
the gate. A failed response may now only invalidate the credentials it
actually carried (send-time session capture + token compare before
clearing).

Also adds the docstrings CodeRabbit's coverage check asked for on the
new test functions.
@paoloantinori

Copy link
Copy Markdown
Contributor Author

The red Tests (backend + frontend) check is pre-existing on main, not introduced by this PR: tests/test_watermark_route_coverage.py::test_every_synthesis_module_routes_through_mark_synthetic fails identically on pristine upstream/main (ee35d238) — offender worker/transport/server.py synthesizes audio without the mark_synthetic chokepoint (#1169). Everything else in the matrix is green on e2bfeaa0.

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.

Remote browser UI spams 403 on /sysinfo, /model/status, /system/notifications and never offers the API-key login

1 participant