fix: keep the site cache purge token out of the page payload (NUXT_CACHE_PURGE_TOKEN)

nuxt-site-config copies every NUXT_SITE_* variable into the public site
config, so NUXT_SITE_CACHE_PURGE_TOKEN reached every page's payload as
cachePurgeToken (and NUXT_SITE_CACHE_ALLOW_PROXIED as cacheAllowProxied).

- The purge settings move to runtimeConfig cachePurge.token
  (NUXT_CACHE_PURGE_TOKEN) and cachePurge.allowProxied
  (NUXT_CACHE_PURGE_ALLOW_PROXIED), outside that prefix.
- The old siteCache.* names keep working for one release: a non-empty
  new token wins, either allowProxied flag turns it on, and one
  deprecation warning per process names the variables (never a value).
- The shared Nitro plugin server/plugins/site-cache-purge.ts strips
  cachePurgeToken and cacheAllowProxied from nuxt-site-config's stack on
  site-config:init, so a site on the old names leaks nothing either; it
  does nothing without nuxt-site-config.
- The shared lib, route and plugin and tests/sitePurge.test.ts are
  byte-identical in vue-journal-layer, vue-menu-layer and vue-faq-layer,
  with three sha256 pins.
- README: new names, the deprecation, the stripping and the advice to
  rotate a token ever set under the old name.
This commit is contained in:
Jakub Zych
2026-10-20 20:49:34 +02:00
parent c4639c2020
commit ef3a090647
7 changed files with 416 additions and 41 deletions

View File

@@ -66,7 +66,10 @@ Slots:
|---|---|---|
| `runtimeConfig.menu.apiBase` | `http://127.0.0.1:8096/_menu/api/v1` | `NUXT_MENU_API_BASE` (server only, used for SSR) |
| `runtimeConfig.public.menu.apiBase` | `/_menu/api/v1` | `NUXT_PUBLIC_MENU_API_BASE` (browser, same origin) |
| `runtimeConfig.siteCache.purgeToken` | `''` (route off) | `NUXT_SITE_CACHE_PURGE_TOKEN` (server only; at least 32 characters) |
| `runtimeConfig.cachePurge.token` | `''` (route off) | `NUXT_CACHE_PURGE_TOKEN` (server only; at least 32 characters) |
| `runtimeConfig.cachePurge.allowProxied` | `false` | `NUXT_CACHE_PURGE_ALLOW_PROXIED` (server only) |
| `runtimeConfig.siteCache.purgeToken` | `''` | Deprecated (one release): `NUXT_SITE_CACHE_PURGE_TOKEN`, the earlier name of `cachePurge.token`, read when that is empty |
| `runtimeConfig.siteCache.allowProxied` | `false` | Deprecated (one release): `NUXT_SITE_CACHE_ALLOW_PROXIED`, the earlier name of `cachePurge.allowProxied` |
The base URLs always come from runtime config, never from request input. In production the web server routes `/_menu/` to the SummerCMS binary.
@@ -76,17 +79,24 @@ A site that route-caches its pages (for example `routeRules: { '/**': { swr: 300
```
POST /api/__site__/cache/purge
Authorization: Bearer <NUXT_SITE_CACHE_PURGE_TOKEN>
Authorization: Bearer <NUXT_CACHE_PURGE_TOKEN>
{"kind":"menu_entry","slug":"partition:3"}
→ {"purged": <n>}
```
- It removes every entry of Nitro's route cache (pages, their `_payload.json` and feeds) and logs `[site-cache] purge: …` with the kind and slug.
- Set `NUXT_SITE_CACHE_PURGE_TOKEN` to the backend's `site_cache.purge_token` (at least 32 characters) and point the backend's `site_cache.purge_url` at `http://127.0.0.1:<port>/api/__site__/cache/purge`. Without a token of that length the route answers 404.
- Set `NUXT_CACHE_PURGE_TOKEN` to the backend's `site_cache.purge_token` (at least 32 characters) and point the backend's `site_cache.purge_url` at `http://127.0.0.1:<port>/api/__site__/cache/purge`. Without a token of that length the route answers 404.
- It answers 404 to any request that came through a reverse proxy (`X-Forwarded-For`, `X-Real-IP` or `Forwarded`), so only the backend on the same machine reaches it, and 401 to a missing or wrong bearer token (compared in constant time).
- Behind a TLS-terminating proxy, for a backend that reaches the site at an https `site_cache.purge_url` on another host, the proxy adds those headers. Set `NUXT_SITE_CACHE_ALLOW_PROXIED=true` (`runtimeConfig.siteCache.allowProxied`, default `false`) and the route accepts proxied requests; the bearer token is then the only gate, so keep it long and secret. Unset, proxied requests stay refused.
- Behind a TLS-terminating proxy, for a backend that reaches the site at an https `site_cache.purge_url` on another host, the proxy adds those headers. Set `NUXT_CACHE_PURGE_ALLOW_PROXIED=true` (`runtimeConfig.cachePurge.allowProxied`, default `false`) and the route accepts proxied requests; the bearer token is then the only gate, so keep it long and secret. Unset, proxied requests stay refused.
- The layer adds the route rule `'/api/__site__/**': { cache: false }`; without it, a site-wide `swr` rule would answer the purge from the cache.
- vue-journal-layer, vue-menu-layer and vue-faq-layer ship the same `server/lib/siteCachePurge.ts` and `server/api/__site__/cache/purge.post.ts` byte for byte (a test pins their sha256), so a site that extends several of them gets one route, whichever copy Nitro keeps.
- vue-journal-layer, vue-menu-layer and vue-faq-layer ship the same `server/lib/siteCachePurge.ts`, `server/api/__site__/cache/purge.post.ts` and `server/plugins/site-cache-purge.ts` byte for byte (a test pins their sha256), so a site that extends several of them gets one route, whichever copy Nitro keeps.
- Never put a secret in a `NUXT_SITE_*` or `NUXT_PUBLIC_SITE_*` variable: nuxt-site-config (in a site that also uses `@nuxtjs/seo`, for example through vue-journal-layer) copies every such variable into the site config, which is published in every page's payload. The Nitro plugin `server/plugins/site-cache-purge.ts` removes `cachePurgeToken` and `cacheAllowProxied` from that site config on its `site-config:init` hook, whatever names the site uses, and logs one `[site-cache]` deprecation warning per process when a setting comes from an old name (variable names only, never a value). Without nuxt-site-config the plugin does nothing.
### Upgrading from 0.3
- Rename `NUXT_SITE_CACHE_PURGE_TOKEN` to `NUXT_CACHE_PURGE_TOKEN` and `NUXT_SITE_CACHE_ALLOW_PROXIED` to `NUXT_CACHE_PURGE_ALLOW_PROXIED` in the site's environment. The old names keep working through 0.4.x (a non-empty new token wins) and are removed in the next minor release.
- Why: nuxt-site-config publishes every `NUXT_SITE_*` variable in the page payload, so in a site with nuxt-site-config a token set under `NUXT_SITE_CACHE_PURGE_TOKEN` appeared in every page as `cachePurgeToken`. From 0.4 the layer strips both keys from the site config even under the old names.
- Rotate a token that was ever set under `NUXT_SITE_CACHE_PURGE_TOKEN`: earlier versions published it. Set the new value in the site's `NUXT_CACHE_PURGE_TOKEN` and in the backend's `site_cache.purge_token`, then restart both.
## API