Skip to content

Add per-server custom HTTP headers (#228) - #229

Merged
taylorcox75 merged 4 commits into
mainfrom
feature/#228
Aug 24, 2026
Merged

Add per-server custom HTTP headers (#228)#229
taylorcox75 merged 4 commits into
mainfrom
feature/#228

Conversation

@taylorcox75

Copy link
Copy Markdown
Owner

Closes #228.

What

Adds an optional Custom Headers section to the add/edit server forms. When enabled, the configured name/value pairs are sent on every request to that server — for tunnels and reverse proxies that gate access with their own header-based token auth.

The reporter's setup is Pangolin fronting qBittorrent via a newt client; they need at least two header pairs. This is a proxy-layer concern, so no qBittorrent endpoint is involved and it works identically on 4.x and 5.x.

How

Follows the existing reverse-proxy Basic Auth pattern (useBasicAuth, #118):

  • useCustomHeaders flag + customHeaders array on ServerConfig.
  • The request interceptor in services/api/client.ts applies them after the Authorization block, independent of the server's auth mode — API key, password, and none all compose with custom headers.
  • Header values are tokens, so the whole array is treated as a secret: JSON-stringified into SecureStore under server_custom_headers_{id}, forced to [] before the record reaches AsyncStorage, deleted on server delete, stripped on export and forced empty on import.
  • Header names that collide with ones qRemote manages (Authorization, Cookie, Referer, Origin, Content-Type, Host) are rejected at save time and flagged inline in the form — otherwise a custom header could silently break auth or cookie handling.
  • Capped at 5 rows.

New utils/customHeaders.ts holds the sanitize/validate logic; new components/CustomHeadersSection.tsx is shared by both server screens.

Notes

  • Additive only — no preference key, colors key, or stored field is renamed, so existing users are unaffected.
  • Strings added to all six locales.
  • Changelog line appended to the existing v3.8.40 entry, per the release-notes flow.

Test plan

  • npx tsc --noEmit — exit 0
  • npm test — 1045 passed / 74 suites, both projects (includes the locale parity check)
  • npm run lint — 0 errors (37 pre-existing warnings)
  • npm run format
  • New coverage: tests/utils/customHeaders.test.ts (reserved names, sanitize, validate), tests/services/client-custom-headers.test.ts (interceptor behavior incl. composing with an API key), plus secret-handling assertions added to the storage and server-export suites.

Not run on device — the UI section itself is unverified in a simulator.

🤖 Generated with Claude Code

Supports tunnels and reverse proxies that gate access with their own
header-based token auth (Pangolin, Cloudflare Access, etc.), layered
independently of qBittorrent's own auth mode.

Header values are tokens, so the whole customHeaders array is treated as
a secret: stored in SecureStore under server_custom_headers_{id}, forced
to [] in AsyncStorage, stripped on export and forced empty on import.

Header names colliding with ones qRemote manages (Authorization, Cookie,
Referer, Origin, Content-Type, Host) are rejected at save time, so a
custom header can't silently break auth or cookie handling.
…iagnostics

The auth-method row's value was laid out at its intrinsic width, and since
SettingRow's label side is flex:1 it only received the leftover space —
"Username & Password" starved the label until "Authentication Method" broke
mid-word. Cap the value at 45% and let it scale its font down, which leaves
the label enough room to wrap on a word boundary.

Also carry custom headers through the diagnostics paths so a header-gated
server doesn't fail there while working in the app: SuperDebugPanel now
attaches them to every request (reach, login, API check, log export) and
includes them in its config fingerprint. Debug mode reports header names
only — that block is copied to the clipboard and pasted into public issue
reports, and the values are auth tokens.

Test Connection already exercised them via the ServerConfig it builds.
There is no migration system, so servers saved before #228 must keep working
untouched. Covers the four surfaces a legacy record passes through:

- storage: an AsyncStorage record with no useCustomHeaders/customHeaders keys
  and no SecureStore entry loads normally, with customHeaders defaulting to []
- storage: a corrupt stored secret degrades to [] instead of throwing, which
  would otherwise take down the whole Promise.all and hide every server
- client: a config with neither field set sends exactly the headers it did
  before, and headers present without the flag are still not sent
- export/import: legacy configs and legacy export files round-trip
Save-time UI validation was being relied on as the guarantee that a custom
header can never be reserved or malformed. It is not the only way data
reaches a ServerConfig, so that assumption did not hold:

- settings import (app/(tabs)/settings/advanced.tsx) spreads unvalidated JSON
  and blanked password/basicAuthPassword/apiKey but not customHeaders, so a
  hand-edited file could put header tokens into SecureStore
- custom headers are applied last in the request interceptor, so a header
  named Authorization or Cookie would have clobbered the real auth
- the read path validated JSON syntax but not shape, so [{"key":123}] parsed
  fine and then threw on .trim() when the edit screen rendered

Enforce at each point that actually matters rather than upstream of it:
sanitize on write in storage.saveServer (every write passes through it),
validate shape on read via parseStoredCustomHeaders, re-check reserved names
in the interceptor, and blank customHeaders on settings import.

The app's own exports were already safe — both server and settings export go
through toExportedServer, which forces customHeaders to [].
@taylorcox75
taylorcox75 merged commit 5af8137 into main Aug 24, 2026
3 of 4 checks passed
@taylorcox75
taylorcox75 deleted the feature/#228 branch August 24, 2026 15:35
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.

[Request] Token Header Authorization Support

1 participant