codev / src /server /__tests__ /h5-access-policy.test.ts
chenbhao's picture
feat: desktop
1f21206
Raw
History Blame Contribute Delete
6.59 kB
import { describe, expect, test } from 'bun:test'
import {
classifyH5Request,
isLoopbackHost,
shouldBlockDisabledH5Access,
shouldRequireH5Token,
} from '../h5AccessPolicy.js'
function req(url: string, init: RequestInit = {}) {
return new Request(url, init)
}
const localContext = { clientAddress: '127.0.0.1' }
const remoteContext = { clientAddress: '192.168.0.44' }
describe('h5AccessPolicy', () => {
test('recognizes loopback hosts as local trusted requests', () => {
expect(isLoopbackHost('localhost')).toBe(true)
expect(isLoopbackHost('127.0.0.1')).toBe(true)
expect(isLoopbackHost('[::1]')).toBe(true)
expect(isLoopbackHost('192.168.0.20')).toBe(false)
})
test('keeps Tauri WebView requests to loopback tokenless', () => {
const request = req('http://127.0.0.1:3456/api/status', {
headers: { Origin: 'http://tauri.localhost' },
})
expect(classifyH5Request(request, new URL(request.url), localContext)).toBe('local-trusted')
expect(shouldRequireH5Token({ request, url: new URL(request.url), h5Enabled: true, context: localContext })).toBe(false)
})
test('keeps local internal SDK websocket routes tokenless', () => {
const request = req('http://127.0.0.1:3456/sdk/session-1')
expect(classifyH5Request(request, new URL(request.url), localContext)).toBe('internal-sdk')
expect(shouldRequireH5Token({ request, url: new URL(request.url), h5Enabled: true, context: localContext })).toBe(false)
})
test('does not trust remote SDK websocket routes by path alone', () => {
const request = req('http://192.168.0.20:3456/sdk/session-1')
expect(classifyH5Request(request, new URL(request.url), remoteContext)).toBe('h5-browser')
expect(shouldRequireH5Token({ request, url: new URL(request.url), h5Enabled: true, context: remoteContext })).toBe(false)
})
test('keeps adapter API routes tokenless for local integrations', () => {
const request = req('http://127.0.0.1:3456/api/adapters')
expect(classifyH5Request(request, new URL(request.url), localContext)).toBe('local-trusted')
expect(shouldRequireH5Token({ request, url: new URL(request.url), h5Enabled: true, context: localContext })).toBe(false)
})
test('does not trust loopback adapter requests from non-local browser origins', () => {
const request = req('http://127.0.0.1:3456/api/adapters', {
headers: { Origin: 'https://blocked.example.com' },
})
expect(classifyH5Request(request, new URL(request.url), localContext)).toBe('h5-browser')
expect(shouldRequireH5Token({ request, url: new URL(request.url), h5Enabled: true, context: localContext })).toBe(true)
})
test('does not trust spoofed loopback hosts from remote clients', () => {
const request = req('http://127.0.0.1:3456/api/status', {
headers: { Origin: 'http://127.0.0.1:5179' },
})
expect(classifyH5Request(request, new URL(request.url), remoteContext)).toBe('h5-browser')
expect(shouldBlockDisabledH5Access({
request,
url: new URL(request.url),
h5Enabled: false,
explicitAuthRequired: false,
context: remoteContext,
})).toBe(true)
})
test('keeps local desktop chat websocket routes tokenless', () => {
for (const init of [{}, { headers: { Origin: 'http://tauri.localhost' } }]) {
const request = req('http://127.0.0.1:3456/ws/session-1', init)
expect(classifyH5Request(request, new URL(request.url), localContext)).toBe('local-trusted')
expect(shouldRequireH5Token({ request, url: new URL(request.url), h5Enabled: true, context: localContext })).toBe(false)
}
})
test('requires H5 token for LAN browser API, proxy, and chat websocket routes when enabled', () => {
for (const pathname of [
'/api/status',
'/api/mcp',
'/api/plugins',
'/api/agents',
'/proxy/openai/v1/chat/completions',
'/ws/session-1',
]) {
const request = req(`http://192.168.0.20:3456${pathname}`, {
headers: { Origin: 'http://192.168.0.20:3456' },
})
expect(classifyH5Request(request, new URL(request.url), remoteContext)).toBe('h5-browser')
expect(shouldRequireH5Token({ request, url: new URL(request.url), h5Enabled: true, context: remoteContext })).toBe(true)
}
})
test('blocks LAN browser capability routes while H5 access is disabled', () => {
for (const pathname of [
'/api/status',
'/api/mcp',
'/api/plugins',
'/api/agents',
'/proxy/openai/v1/chat/completions',
'/ws/session-1',
'/sdk/session-1',
]) {
const request = req(`http://192.168.0.20:3456${pathname}`, {
headers: { Origin: 'http://192.168.0.20:3456' },
})
expect(shouldBlockDisabledH5Access({
request,
url: new URL(request.url),
h5Enabled: false,
explicitAuthRequired: false,
context: remoteContext,
})).toBe(true)
}
})
test('keeps local capability routes and static bootstrap routes available while H5 access is disabled', () => {
for (const pathname of ['/api/status', '/proxy/openai/v1/chat/completions', '/ws/session-1', '/sdk/session-1']) {
const request = req(`http://127.0.0.1:3456${pathname}`)
expect(shouldBlockDisabledH5Access({
request,
url: new URL(request.url),
h5Enabled: false,
explicitAuthRequired: false,
context: localContext,
})).toBe(false)
}
for (const pathname of ['/', '/health', '/assets/app.js']) {
const request = req(`http://192.168.0.20:3456${pathname}`, {
headers: { Origin: 'http://192.168.0.20:3456' },
})
expect(shouldBlockDisabledH5Access({
request,
url: new URL(request.url),
h5Enabled: false,
explicitAuthRequired: false,
context: remoteContext,
})).toBe(false)
}
})
test('explicit deployment auth does not use the H5 token gate when H5 is disabled', () => {
const request = req('http://127.0.0.1:3456/api/status')
expect(shouldRequireH5Token({
request,
url: new URL(request.url),
h5Enabled: false,
context: localContext,
})).toBe(false)
})
test('does not block explicitly authenticated deployments before auth middleware runs', () => {
const request = req('http://192.168.0.20:3456/api/status', {
headers: { Origin: 'https://phone.example' },
})
expect(shouldBlockDisabledH5Access({
request,
url: new URL(request.url),
h5Enabled: false,
explicitAuthRequired: true,
context: remoteContext,
})).toBe(false)
})
})