feat(19.3-06): purge route accepts proxied requests on siteCache.allowProxied (WR-03)
- runtimeConfig.siteCache.allowProxied (NUXT_SITE_CACHE_ALLOW_PROXIED, default false) lets the shared purge route accept a request that came through a TLS-terminating proxy; the bearer token is then the only gate - shared lib, route and spec stay byte-identical across the three layers, with updated sha256 pins and two new sitePurge specs - version 0.5.0
This commit is contained in:
@@ -15,8 +15,8 @@ import {
|
||||
// The sha256 of the two shared files. vue-journal-layer, vue-menu-layer and
|
||||
// vue-faq-layer ship them byte for byte; a change lands in all three with
|
||||
// new values here.
|
||||
const SHARED_LIB_SHA256 = '7c34121ae06cf011b30a1dc5d787a94657b71b8768bed3a200be0f41566001b8'
|
||||
const SHARED_ROUTE_SHA256 = 'c459012280cdf59551e57f0440fcaeb24566a5ba6e3dccf31133e67a683f1c06'
|
||||
const SHARED_LIB_SHA256 = '0ea9f0f936134516872a2edf9b8e09feedddfa23e9da05c57d06c772193b260e'
|
||||
const SHARED_ROUTE_SHA256 = '8f86226074abf10c2ecc1d181b6957e90ed8d900589a6372214f50cdbfcd532c'
|
||||
|
||||
const TOKEN = 'a'.repeat(MIN_SITE_PURGE_TOKEN_LENGTH)
|
||||
|
||||
@@ -139,7 +139,7 @@ test('sitePurge: purge event edge cases', () => {
|
||||
})
|
||||
|
||||
/** One call of the shared route with Nitro's auto-imports stubbed. */
|
||||
async function callRoute(opts: { token?: unknown; headers?: Record<string, string>; body?: unknown; bodyError?: boolean; keys?: string[] }) {
|
||||
async function callRoute(opts: { token?: unknown; allowProxied?: unknown; headers?: Record<string, string>; body?: unknown; bodyError?: boolean; keys?: string[] }) {
|
||||
const responseHeaders: Record<string, string> = {}
|
||||
const logs: string[] = []
|
||||
const live = new Set(opts.keys ?? [])
|
||||
@@ -148,7 +148,7 @@ async function callRoute(opts: { token?: unknown; headers?: Record<string, strin
|
||||
g.setResponseHeader = (_e: unknown, name: string, value: string) => {
|
||||
responseHeaders[name] = value
|
||||
}
|
||||
g.useRuntimeConfig = () => ({ siteCache: opts.token === undefined ? undefined : { purgeToken: opts.token } })
|
||||
g.useRuntimeConfig = () => ({ siteCache: opts.token === undefined ? undefined : { purgeToken: opts.token, allowProxied: opts.allowProxied } })
|
||||
g.getRequestHeader = (_e: unknown, name: string) => opts.headers?.[name]
|
||||
g.createError = (input: { statusCode: number; statusMessage: string }) => Object.assign(new Error(input.statusMessage), input)
|
||||
g.readBody = async () => {
|
||||
@@ -215,3 +215,33 @@ test('sitePurge: route purges, answers the count and logs kind and slug only', a
|
||||
assert.deepEqual(bad.result, { purged: 0 })
|
||||
assert.deepEqual(bad.logs, ['[site-cache] purge: removed 0 route cache entries (unknown )'])
|
||||
})
|
||||
|
||||
test('sitePurge: allowProxied lets a proxied request reach the token check', () => {
|
||||
assert.equal(authorizeSitePurge({ token: TOKEN, authorization: `Bearer ${TOKEN}`, proxied: true, allowProxied: true }), 'ok')
|
||||
assert.equal(authorizeSitePurge({ token: TOKEN, authorization: 'Bearer ' + 'b'.repeat(32), proxied: true, allowProxied: true }), 'unauthorized')
|
||||
assert.equal(authorizeSitePurge({ token: TOKEN, authorization: null, proxied: true, allowProxied: true }), 'unauthorized')
|
||||
assert.equal(authorizeSitePurge({ token: 'short', authorization: 'Bearer short', proxied: true, allowProxied: true }), 'disabled')
|
||||
assert.equal(authorizeSitePurge({ token: '', authorization: 'Bearer x', proxied: true, allowProxied: true }), 'disabled')
|
||||
// Without the flag, or with anything but true, a proxied request stays refused.
|
||||
assert.equal(authorizeSitePurge({ token: TOKEN, authorization: `Bearer ${TOKEN}`, proxied: true, allowProxied: false }), 'proxied')
|
||||
assert.equal(authorizeSitePurge({ token: TOKEN, authorization: `Bearer ${TOKEN}`, proxied: true }), 'proxied')
|
||||
})
|
||||
|
||||
test('sitePurge: route accepts a proxied request with allowProxied and the right bearer', async () => {
|
||||
const keys = [`${SITE_ROUTE_CACHE_BASE}:_:index.json`, `${SITE_ROUTE_CACHE_BASE}:_:faq.json`]
|
||||
const headers = { authorization: `Bearer ${TOKEN}`, 'x-forwarded-for': '203.0.113.9', 'x-real-ip': '203.0.113.9', forwarded: 'for=203.0.113.9;proto=https' }
|
||||
for (const allowProxied of [true, 'true']) {
|
||||
const r = await callRoute({ token: TOKEN, allowProxied, headers, body: { kind: 'faq', slug: 'shipping' }, keys })
|
||||
assert.equal(r.status, 200, `allowProxied ${JSON.stringify(allowProxied)}`)
|
||||
assert.deepEqual(r.result, { purged: 2 })
|
||||
assert.equal(r.live.size, 0)
|
||||
}
|
||||
const wrong = await callRoute({ token: TOKEN, allowProxied: true, headers: { ...headers, authorization: 'Bearer wrong' }, keys })
|
||||
assert.equal(wrong.status, 401)
|
||||
assert.equal(wrong.live.size, 2)
|
||||
for (const allowProxied of [false, undefined, 'false', 1]) {
|
||||
const r = await callRoute({ token: TOKEN, allowProxied, headers, keys })
|
||||
assert.equal(r.status, 404, `allowProxied ${JSON.stringify(allowProxied)}`)
|
||||
assert.equal(r.live.size, 2)
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user