367 lines
9.7 KiB
TypeScript
367 lines
9.7 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import type { H3Event } from 'h3'
|
|
import {
|
|
shouldBlockFileHostRequest,
|
|
shouldBlockImageHostRequest,
|
|
shouldBlockSiteHostImageRequest,
|
|
shouldBlockUnknownHostRequest,
|
|
shouldBlockVideoHostRequest
|
|
} from './host-isolation'
|
|
|
|
function mockEvent(): H3Event {
|
|
return {
|
|
node: { req: {}, res: {} },
|
|
context: {}
|
|
} as H3Event
|
|
}
|
|
|
|
describe('shouldBlockImageHostRequest', () => {
|
|
const imageHost = 'image.example.com'
|
|
const siteHost = 'admin.example.com'
|
|
|
|
it('does not block when separation is inactive', () => {
|
|
const event = mockEvent()
|
|
expect(shouldBlockImageHostRequest(event, {
|
|
separationActive: false,
|
|
requestHost: imageHost,
|
|
method: 'GET',
|
|
pathname: '/settings'
|
|
})).toBe(false)
|
|
})
|
|
|
|
it('blocks non-image paths on image host', () => {
|
|
const event = mockEvent()
|
|
expect(shouldBlockImageHostRequest(event, {
|
|
separationActive: true,
|
|
imageHost,
|
|
videoHost: 'video.example.com',
|
|
requestHost: imageHost,
|
|
method: 'GET',
|
|
pathname: '/settings',
|
|
hideFolder: false
|
|
})).toBe(true)
|
|
})
|
|
|
|
it('allows video paths on image host when no video domain is configured', () => {
|
|
const event = mockEvent()
|
|
expect(shouldBlockImageHostRequest(event, {
|
|
separationActive: true,
|
|
imageHost,
|
|
videoHost: null,
|
|
fileHost: null,
|
|
requestHost: imageHost,
|
|
method: 'GET',
|
|
pathname: '/videos/2026/08/demo.mp4'
|
|
})).toBe(false)
|
|
})
|
|
|
|
it('allows image paths on image host', () => {
|
|
const event = mockEvent()
|
|
expect(shouldBlockImageHostRequest(event, {
|
|
separationActive: true,
|
|
imageHost,
|
|
videoHost: 'video.example.com',
|
|
requestHost: imageHost,
|
|
method: 'GET',
|
|
pathname: '/images/2026/08/demo.webp',
|
|
hideFolder: false
|
|
})).toBe(false)
|
|
})
|
|
|
|
it('blocks POST on image host even for image paths', () => {
|
|
const event = mockEvent()
|
|
expect(shouldBlockImageHostRequest(event, {
|
|
separationActive: true,
|
|
imageHost,
|
|
requestHost: imageHost,
|
|
method: 'POST',
|
|
pathname: '/images/2026/08/demo.webp'
|
|
})).toBe(true)
|
|
})
|
|
|
|
it('blocks video paths on the image host', () => {
|
|
const event = mockEvent()
|
|
expect(shouldBlockImageHostRequest(event, {
|
|
separationActive: true,
|
|
imageHost,
|
|
videoHost: 'video.example.com',
|
|
requestHost: imageHost,
|
|
method: 'GET',
|
|
pathname: '/videos/2026/08/demo.mp4'
|
|
})).toBe(true)
|
|
})
|
|
|
|
it('allows admin and all media paths when one host serves every role', () => {
|
|
const event = mockEvent()
|
|
for (const pathname of [
|
|
'/settings',
|
|
'/images/2026/08/demo.webp',
|
|
'/videos/2026/08/demo.mp4',
|
|
'/files/2026/08/demo.pdf'
|
|
]) {
|
|
expect(shouldBlockImageHostRequest(event, {
|
|
separationActive: true,
|
|
siteHost: imageHost,
|
|
imageHost,
|
|
videoHost: imageHost,
|
|
fileHost: imageHost,
|
|
requestHost: imageHost,
|
|
method: 'GET',
|
|
pathname
|
|
})).toBe(false)
|
|
}
|
|
})
|
|
|
|
it('does not block site host admin paths', () => {
|
|
const event = mockEvent()
|
|
expect(shouldBlockImageHostRequest(event, {
|
|
separationActive: true,
|
|
imageHost,
|
|
requestHost: siteHost,
|
|
method: 'GET',
|
|
pathname: '/api/images'
|
|
})).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('video and file media hosts', () => {
|
|
const videoHost = 'video.example.com'
|
|
const fileHost = 'file.example.com'
|
|
|
|
it('allows only video paths on the video host', () => {
|
|
const event = mockEvent()
|
|
expect(shouldBlockVideoHostRequest(event, {
|
|
separationActive: true,
|
|
videoHost,
|
|
requestHost: videoHost,
|
|
method: 'GET',
|
|
pathname: '/videos/2026/08/demo.mp4'
|
|
})).toBe(false)
|
|
expect(shouldBlockVideoHostRequest(event, {
|
|
separationActive: true,
|
|
videoHost,
|
|
requestHost: videoHost,
|
|
method: 'GET',
|
|
pathname: '/images/2026/08/demo.webp'
|
|
})).toBe(true)
|
|
})
|
|
|
|
it('allows only file paths on the file host', () => {
|
|
const event = mockEvent()
|
|
expect(shouldBlockFileHostRequest(event, {
|
|
separationActive: true,
|
|
fileHost,
|
|
requestHost: fileHost,
|
|
method: 'GET',
|
|
pathname: '/files/2026/08/demo.pdf'
|
|
})).toBe(false)
|
|
expect(shouldBlockFileHostRequest(event, {
|
|
separationActive: true,
|
|
fileHost,
|
|
requestHost: fileHost,
|
|
method: 'GET',
|
|
pathname: '/videos/2026/08/demo.mp4'
|
|
})).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('shouldBlockSiteHostImageRequest', () => {
|
|
const imageHost = 'image.example.com'
|
|
const siteHost = 'admin.example.com'
|
|
|
|
it('does not block when separation is inactive', () => {
|
|
const event = mockEvent()
|
|
expect(shouldBlockSiteHostImageRequest(event, {
|
|
separationActive: false,
|
|
requestHost: siteHost,
|
|
method: 'GET',
|
|
pathname: '/images/2026/08/demo.webp'
|
|
})).toBe(false)
|
|
})
|
|
|
|
it('blocks image paths on site host', () => {
|
|
const event = mockEvent()
|
|
expect(shouldBlockSiteHostImageRequest(event, {
|
|
separationActive: true,
|
|
siteHost,
|
|
requestHost: siteHost,
|
|
method: 'GET',
|
|
pathname: '/images/2026/08/demo.webp',
|
|
hideFolder: false
|
|
})).toBe(true)
|
|
})
|
|
|
|
it('blocks public image paths on site host', () => {
|
|
const event = mockEvent()
|
|
expect(shouldBlockSiteHostImageRequest(event, {
|
|
separationActive: true,
|
|
siteHost,
|
|
requestHost: siteHost,
|
|
method: 'GET',
|
|
pathname: '/2026/08/demo.webp',
|
|
hideFolder: false
|
|
})).toBe(true)
|
|
})
|
|
|
|
it('blocks legacy date short paths on site host', () => {
|
|
const event = mockEvent()
|
|
expect(shouldBlockSiteHostImageRequest(event, {
|
|
separationActive: true,
|
|
siteHost,
|
|
requestHost: siteHost,
|
|
method: 'GET',
|
|
pathname: '/2026/08/demo.webp',
|
|
hideFolder: true
|
|
})).toBe(true)
|
|
})
|
|
|
|
it('allows admin paths on site host', () => {
|
|
const event = mockEvent()
|
|
expect(shouldBlockSiteHostImageRequest(event, {
|
|
separationActive: true,
|
|
siteHost,
|
|
requestHost: siteHost,
|
|
method: 'GET',
|
|
pathname: '/',
|
|
hideFolder: false
|
|
})).toBe(false)
|
|
})
|
|
|
|
it('does not block image paths on image host', () => {
|
|
const event = mockEvent()
|
|
expect(shouldBlockSiteHostImageRequest(event, {
|
|
separationActive: true,
|
|
siteHost,
|
|
requestHost: imageHost,
|
|
method: 'GET',
|
|
pathname: '/images/2026/08/demo.webp'
|
|
})).toBe(false)
|
|
})
|
|
|
|
it('blocks video and file paths on the site host', () => {
|
|
const event = mockEvent()
|
|
expect(shouldBlockSiteHostImageRequest(event, {
|
|
separationActive: true,
|
|
siteHost,
|
|
requestHost: siteHost,
|
|
method: 'GET',
|
|
pathname: '/videos/2026/08/demo.mp4'
|
|
})).toBe(true)
|
|
expect(shouldBlockSiteHostImageRequest(event, {
|
|
separationActive: true,
|
|
siteHost,
|
|
requestHost: siteHost,
|
|
method: 'GET',
|
|
pathname: '/files/2026/08/demo.pdf'
|
|
})).toBe(true)
|
|
})
|
|
|
|
it('allows media paths when all roles share the same host', () => {
|
|
const event = mockEvent()
|
|
for (const pathname of [
|
|
'/images/2026/08/demo.webp',
|
|
'/videos/2026/08/demo.mp4',
|
|
'/files/2026/08/demo.pdf'
|
|
]) {
|
|
expect(shouldBlockSiteHostImageRequest(event, {
|
|
separationActive: true,
|
|
siteHost,
|
|
imageHost: siteHost,
|
|
videoHost: siteHost,
|
|
fileHost: siteHost,
|
|
requestHost: siteHost,
|
|
method: 'GET',
|
|
pathname
|
|
})).toBe(false)
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('shouldBlockUnknownHostRequest', () => {
|
|
const imageHost = 'image.example.com'
|
|
const siteHost = 'admin.example.com'
|
|
|
|
it('does not block when separation is inactive', () => {
|
|
const event = mockEvent()
|
|
expect(shouldBlockUnknownHostRequest(event, {
|
|
separationActive: false,
|
|
requestHost: 'pages.dev',
|
|
siteHost,
|
|
imageHost
|
|
})).toBe(false)
|
|
})
|
|
|
|
it('blocks unconfigured third host when separation is active', () => {
|
|
const event = mockEvent()
|
|
expect(shouldBlockUnknownHostRequest(event, {
|
|
separationActive: true,
|
|
requestHost: 'pages.dev',
|
|
siteHost,
|
|
imageHost
|
|
})).toBe(true)
|
|
})
|
|
|
|
it('blocks admin paths on image host when host is unconfigured', () => {
|
|
const event = mockEvent()
|
|
expect(shouldBlockUnknownHostRequest(event, {
|
|
separationActive: true,
|
|
requestHost: 'old-img.example.com',
|
|
siteHost,
|
|
imageHost
|
|
})).toBe(true)
|
|
})
|
|
|
|
it('allows configured site host', () => {
|
|
const event = mockEvent()
|
|
expect(shouldBlockUnknownHostRequest(event, {
|
|
separationActive: true,
|
|
requestHost: siteHost,
|
|
siteHost,
|
|
imageHost
|
|
})).toBe(false)
|
|
})
|
|
|
|
it('allows configured image host', () => {
|
|
const event = mockEvent()
|
|
expect(shouldBlockUnknownHostRequest(event, {
|
|
separationActive: true,
|
|
requestHost: imageHost,
|
|
siteHost,
|
|
imageHost
|
|
})).toBe(false)
|
|
})
|
|
|
|
it('allows configured video and file hosts', () => {
|
|
const event = mockEvent()
|
|
expect(shouldBlockUnknownHostRequest(event, {
|
|
separationActive: true,
|
|
requestHost: 'video.example.com',
|
|
siteHost,
|
|
imageHost,
|
|
videoHost: 'video.example.com',
|
|
fileHost: 'file.example.com'
|
|
})).toBe(false)
|
|
expect(shouldBlockUnknownHostRequest(event, {
|
|
separationActive: true,
|
|
requestHost: 'file.example.com',
|
|
siteHost,
|
|
imageHost,
|
|
videoHost: 'video.example.com',
|
|
fileHost: 'file.example.com'
|
|
})).toBe(false)
|
|
})
|
|
|
|
it('allows localhost in development', () => {
|
|
const previous = process.env.NODE_ENV
|
|
process.env.NODE_ENV = 'development'
|
|
const event = mockEvent()
|
|
expect(shouldBlockUnknownHostRequest(event, {
|
|
separationActive: true,
|
|
requestHost: 'localhost',
|
|
siteHost,
|
|
imageHost
|
|
})).toBe(false)
|
|
process.env.NODE_ENV = previous
|
|
})
|
|
})
|