Skip to content

feat: managed keys, full config UI and optional main api key#47

Merged
roziscoding merged 10 commits into
mainfrom
feat/jack-config-ui
Jun 26, 2026
Merged

feat: managed keys, full config UI and optional main api key#47
roziscoding merged 10 commits into
mainfrom
feat/jack-config-ui

Conversation

@roziscoding

@roziscoding roziscoding commented Jun 26, 2026

Copy link
Copy Markdown
Owner

Context

Reworks how Jack authenticates the *arr stack (Radarr/Sonarr) and surfaces the Jack connection settings in the management UI, decoupling auto-registration from the single, deprecated config-file API key.

What's included

1. Jack config settings UI + optional main key

  • The single config.jack.apiKey ("Main API key") is now optional and editable from the Settings tab (above the API-keys table) via new GET/PATCH /config/jack management endpoints. {env}/{file} secret refs round-trip intact; the field is labeled deprecated.
  • The Settings section also edits the Internal URL — the URL your *arr apps use to reach this Jack instance (UI-facing connection is env-only, not this value).
  • An empty main key is no longer a valid comparison target in requireApiKey — with no main key, the public API authenticates purely via generated keys (the api_keys table from feat(backend): add multiple API key support #45). There is no longer a fully-open mode.

2. Managed auto-registration keys

  • New managed_keys table (migration 0007) + a jack_managed_ key prefix. At boot, each registrable *arr destination gets a freshly generated managed key, registered as both its Torznab indexer apiKey and its qBittorrent download-client password — so auto-registration no longer depends on the main key.
  • Prefix-dispatched auth (jack_managed_managed_keys, jack_api_keys) in both requireApiKey and the qB login (exactly one lookup). Managed qB keys are server-scoped. The qB emulation is no longer open when no main key is set.
  • Rollback-safe rotation: commit on full success, keep-both on partial failure, discard when nothing reached *arr; stale keys pruned.

3. config.jack required + baseUrlinternalUrl

  • Without jack, Jack serves no torznab/qB/peer traffic, so it's required in AppConfig and the now-dead if (config.jack) gates are removed.
  • jack.baseUrl is renamed to jack.internalUrl (an external URL is planned). A config migration (v2) auto-renames the key on load.

Breaking changes

  • config.jack is required — a config file without a jack block fails to load at boot (operator must configure internalUrl; intentionally no backfill migration, since the correct value can't be guessed).
  • No open-auth mode — an empty/absent main key no longer disables auth on the public API or the qB emulation; a valid generated or managed key is required.
  • The single config.jack.apiKey is deprecated (still honored when set).
  • jack.baseUrljack.internalUrl (auto-migrated; no operator action needed).

Testing

  • bun test: 309 pass / 0 fail. New coverage: crypto prefix dispatch, managed-keys repository/service, the require-auth managed branch, qB login (server-scoped + expired-key), the autoregister rollback rule, the jack config endpoints, and the baseUrlinternalUrl migration.

Manual verification (needs a live *arr)

  • With no main key: a destination's torznab query and qB login with the provisioned managed key both succeed; an unknown key is rejected on both surfaces.

Greptile Summary

This PR moves Jack authentication to managed keys and adds settings support for Jack config. The main changes are:

  • New managed-key table and boot-time auto-registration flow.
  • Optional deprecated main API key with generated-key fallback auth.
  • qBittorrent and Torznab auth support for managed keys.
  • Jack config management endpoints and settings UI.
  • Config migration from jack.baseUrl to jack.internalUrl.

Confidence Score: 4/5

This is close, but the managed-key cleanup should be fixed before merging.

  • Managed keys are pruned from a startup-only registrable set.
  • A configured destination can lose a key that *arr still has saved.
  • Torznab and qBittorrent requests can then fail authentication until registration runs again.

apps/backend/src/index.ts

Security Review

The managed-key prune can delete credentials that configured *arr instances still use, which can break Torznab and qBittorrent authentication after a transient startup state.

Important Files Changed

Filename Overview
apps/backend/src/index.ts Wires managed-key boot registration and pruning; the prune can remove keys still saved in configured *arr destinations.
apps/backend/src/lib/autoregister.ts Adds managed-key provisioning and rollback handling for auto-registration.
apps/backend/src/modules/qbittorrent/qbittorrent.controller.ts Adds main, generated, and managed key validation for qBittorrent login.
apps/backend/src/middleware/require-auth.ts Updates public API auth to reject empty main keys and dispatch generated versus managed keys by prefix.
apps/backend/src/modules/config/config.service.ts Adds persistence for editable Jack config while preserving secret references.

Fix All in Claude Code Fix All in Codex

Reviews (3): Last reviewed commit: "fix(ui): move Internal URL description u..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

Context used (5)

  • Context used - packages/schemas/CLAUDE.md (source)
  • Context used - CLAUDE.md (source)
  • Context used - Use Bun instead of Node.js, npm, pnpm, or vite. (source)
  • Context used - apps/backend/CLAUDE.md (source)
  • Context used - AGENTS.md (source)

Make config.jack.apiKey optional and guard its boot-time consumers
(qBittorrent, *arr auto-registration, torznab) with an empty-string
fallback. An empty main key is no longer a valid comparison target in
requireApiKey, so with no main key the public API authenticates purely
via the generated keys in the api_keys table.
Add a RawJackConfig schema and ConfigService.getRawJack/updateJack so the
management API can read and persist the jack config (baseUrl + optional
main apiKey). GET returns refs-intact values; PATCH validates, preserves
{env}/{file} secret refs on disk, and is gated behind a ConfigService.
Mirrors the peers/servers raw-secret read + rollback-safe persist pattern.
Add a Jack section above the API-keys table that loads GET /config/jack
and edits baseUrl (required) plus the optional, deprecated Main API key
via SecretInput, saving with PATCH /config/jack. Includes load/error
states, a restart notice, and an inline post-save confirmation.
Add a managed_keys table (migration 0007) plus ManagedKeysRepository and
the ManagedApiKeys provisioning service (provision/commit/discard/prune)
for per-destination auto-registration keys. Add the jack_managed_ key
prefix + generateManagedKey/isManagedKey, and make isGeneratedKey exclude
managed keys so the prefix predicates are mutually exclusive. Additive and
unwired — no behavior change yet.
Add a managedKeysRepository parameter + jack_managed_ prefix branch to
requireApiKey so managed keys authenticate against the managed_keys table,
and construct + thread the repository through getApp. No managed keys are
registered yet (the loop changes land in the next phase), so this is inert.
Rework the boot auto-registration loop to provision a fresh managed key
per destination and register it as both the torznab indexer apiKey and the
qBittorrent download-client password (rollback-safe: commit on full success,
keep-both on partial, discard when nothing was delivered), pruning stale
keys. Harden qB login to validate the main key, a server-scoped managed key,
or a user key by prefix dispatch — closing the open-when-no-main-key hole.
- Prune managed keys even when jack config is unset (close stale-key gap).
- Centralize user-key hash/expiry validation in ApiKeysRepository.resolve,
  shared by requireApiKey and qB login (preserves invalid vs expired msgs).
- DRY the api/managed key generators via a shared randomHex helper.
- Add tests: symmetric keep-both (download fails), expired user key on qB.
- Drop a void-suppression nit in the managed-keys repo test.
…k) gates

jack is the core identity (indexer baseUrl + optional key) without which Jack
serves no torznab/qB/peer traffic, so require it in AppConfig and remove the
now-dead gates in app.ts (torznab/peer/handshake + qB) and the index.ts
auto-registration loop (and its else-branch prune). EMPTY_APP_CONFIG and the
migration-test fixtures carry a minimal jack block to satisfy the schema.
Comment thread apps/backend/src/modules/qbittorrent/qbittorrent.controller.ts
The Jack URL is what the *arr apps use to reach this instance (not how the
UI reaches the backend — that's env-only), and an external URL is coming
later. Rename the config key to internalUrl with a v2 migration, rename the
value through the autoregister/torznab/*arr-register flow (keeping *arr's
literal wire field 'baseUrl'), relabel the UI field 'Internal URL', and
correct the Settings section copy.
The copy described the Internal URL field but sat as the section subtitle.
Move it to a hint below the field and drop the now-redundant label aside.
Comment thread apps/backend/src/index.ts
: 'Registered Jack as Torznab indexer',
),
onFailure: logRegistrationFailure,
})

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.

P1 security Keys Pruned Too Broadly

This prune keeps managed keys only for destinations that are initialized and have auto-registration enabled on this boot. When a destination was already registered in *arr but is temporarily missing from that set, such as after an init failure or after disabling auto-registration, Jack deletes the stored managed key while *arr still has it saved for Torznab or qBittorrent. Those requests then fail authentication until registration runs again. The keep-list needs to include configured destinations that may already hold Jack credentials, not only destinations being registered during this startup.

Fix in Claude Code Fix in Codex

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Not really. If jack doesn't know about/can't talk to a server, there's no point in allowing that server to talk to jack, it'd just leave open a door for old keys to be misused. Since auto-registration applies new valid credentials anyway, it's not a big problem. This is the intended behavior.

@roziscoding roziscoding changed the title feat: managed auto-registration keys + Jack config settings UI feat: managed keys, full config UI and optional main api key Jun 26, 2026
@roziscoding roziscoding merged commit adbb0e0 into main Jun 26, 2026
9 checks passed
@roziscoding roziscoding deleted the feat/jack-config-ui branch June 26, 2026 09:11
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