feat: managed keys, full config UI and optional main api key#47
Conversation
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.
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.
| : 'Registered Jack as Torznab indexer', | ||
| ), | ||
| onFailure: logRegistrationFailure, | ||
| }) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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
config.jack.apiKey("Main API key") is now optional and editable from the Settings tab (above the API-keys table) via newGET/PATCH /config/jackmanagement endpoints.{env}/{file}secret refs round-trip intact; the field is labeled deprecated.requireApiKey— with no main key, the public API authenticates purely via generated keys (theapi_keystable from feat(backend): add multiple API key support #45). There is no longer a fully-open mode.2. Managed auto-registration keys
managed_keystable (migration0007) + ajack_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.jack_managed_→managed_keys,jack_→api_keys) in bothrequireApiKeyand 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.3.
config.jackrequired +baseUrl→internalUrljack, Jack serves no torznab/qB/peer traffic, so it's required inAppConfigand the now-deadif (config.jack)gates are removed.jack.baseUrlis renamed tojack.internalUrl(an external URL is planned). A config migration (v2) auto-renames the key on load.Breaking changes
config.jackis required — a config file without ajackblock fails to load at boot (operator must configureinternalUrl; intentionally no backfill migration, since the correct value can't be guessed).config.jack.apiKeyis deprecated (still honored when set).jack.baseUrl→jack.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 thebaseUrl→internalUrlmigration.Manual verification (needs a live *arr)
Greptile Summary
This PR moves Jack authentication to managed keys and adds settings support for Jack config. The main changes are:
jack.baseUrltojack.internalUrl.Confidence Score: 4/5
This is close, but the managed-key cleanup should be fixed before merging.
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
Reviews (3): Last reviewed commit: "fix(ui): move Internal URL description u..." | Re-trigger Greptile
Context used (5)