mirror of
https://github.com/Crosstalk-Solutions/project-nomad.git
synced 2026-10-17 21:29:20 +02:00
Adds an optional `auth: 'nomad_app_key'` discriminator to curated manifest
resources so a curated collection tier can carry content we host ourselves,
gated to official release builds. Without this, only Creator Packs could use
the entitlement Worker; curated tier installs always downloaded
unauthenticated.
No behaviour change for any existing manifest entry: absent `auth` means
unauthenticated, exactly as today.
- `auth` declared on both the type and the VineJS validator. It has to be on
the validator or VineJS strips it silently on fetch, and the gated download
would then go out with no header and 401 for everyone. A dedicated spec
guards that regression.
- Gated resources are pinned to their manifest URL (resolveZimDownload skips
the catalog comparison) and excluded from catalog update checks, so a
resource-id collision cannot let a third-party mirror overwrite our content.
Consequence, commented rather than implied: gated content does not
auto-update; new versions ship via the manifest.
- 401/403 on a download now reports that an official build is required instead
of a raw axios status, which is what a fork build will hit.
- The pure `isGatedResource` predicate is deliberately split from the
env-reading header builder: importing `#start/env` into
zim_download_resolution triggers env validation at import time and breaks its
unit tests.
Reuses CREATOR_PACKS_APP_KEY rather than minting a second secret — the question
it answers ("is this an official build?") is identical for both content types.
Verified end to end on a test server: `auth` survives validation into the
cached spec, the Bearer header attaches to only the gated resource, the file
lands byte-exact with an installed_resources row and a Kiwix library entry, and
an entry with a gated URL but no `auth` field fails with the intended message.
No catalog entry is included here. Manifests are fetched live from `main`, so a
gated entry must not merge until this ships and is adopted — pre-`auth` builds
strip the field and 401.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
39 lines
1.8 KiB
TypeScript
39 lines
1.8 KiB
TypeScript
import env from '#start/env'
|
|
import type { SpecResource } from '../../types/collections.js'
|
|
import { isGatedResource } from './hosted_content.js'
|
|
|
|
/**
|
|
* Auth for curated content that WE host and pay egress for.
|
|
*
|
|
* Content we host sits in a private R2 bucket behind the entitlement Worker,
|
|
* which requires a bearer key that only official release builds bake in (see the
|
|
* Dockerfile ARG/ENV pair, fed from the CI secret). That is the whole point: a
|
|
* fork rebuilt from source cannot point at our bucket and spend our bandwidth.
|
|
*
|
|
* A manifest resource opts in with `auth: 'nomad_app_key'`. Everything else keeps
|
|
* downloading unauthenticated exactly as before.
|
|
*
|
|
* Note on the key name: this deliberately reuses CREATOR_PACKS_APP_KEY rather
|
|
* than minting a second secret. The question it answers ("is this an official
|
|
* build?") is identical for Creator Packs and for our own hosted content, so a
|
|
* second CI secret plus a second Dockerfile ARG would be real cost for no
|
|
* security gain. The name is narrower than the use; this comment is cheaper than
|
|
* the churn of renaming it across CI, the Dockerfile and the Worker.
|
|
*
|
|
* The pure `isGatedResource` predicate lives in hosted_content.ts so that
|
|
* modules which must not pull in env validation can still use it.
|
|
*/
|
|
export function getHostedContentHeaders(
|
|
resource: Pick<SpecResource, 'auth'>
|
|
): Record<string, string> | undefined {
|
|
if (!isGatedResource(resource)) return undefined
|
|
|
|
const appKey = env.get('CREATOR_PACKS_APP_KEY')
|
|
if (!appKey) return undefined
|
|
|
|
// Deliberately still dispatches with no header when the key is absent: the
|
|
// Worker answers 401 and the download surfaces "official release build
|
|
// required", which is a more useful signal than a silent no-op.
|
|
return { Authorization: `Bearer ${appKey}` }
|
|
}
|