mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
Cover H5 settings surfaces in LAN auth tests
The H5 browser shell can load while deeper Settings tabs still exercise separate API routes. This adds MCP, plugin, and Agents endpoints to the H5 policy and integration matrices so the QR-token path is protected against page-level regressions.\n\nConstraint: H5 must expose existing desktop settings surfaces only after local opt-in and a valid H5 token.\nRejected: Rely on manual browser clicks alone | they missed the stopped-port failure mode and would not guard future regressions.\nConfidence: high\nScope-risk: narrow\nDirective: Add new Settings API surfaces to this H5 matrix when exposing them in the browser UI.\nTested: bun test src/server/__tests__/h5-access-policy.test.ts src/server/__tests__/h5-access-auth.test.ts\nTested: bun run check:server
This commit is contained in:
parent
8e184c1edd
commit
8d827510db
@ -167,6 +167,12 @@ function expectWebSocketUpgradeThenClose(url: string): Promise<void> {
|
||||
})
|
||||
}
|
||||
|
||||
const settingsSurfaceEndpoints = [
|
||||
{ path: '/api/mcp', expected: { servers: [] } },
|
||||
{ path: '/api/plugins', expected: { plugins: [] } },
|
||||
{ path: '/api/agents', expectedKey: 'activeAgents' },
|
||||
] as const
|
||||
|
||||
beforeEach(async () => {
|
||||
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'h5-access-auth-test-'))
|
||||
originalConfigDir = process.env.CLAUDE_CONFIG_DIR
|
||||
@ -415,6 +421,14 @@ describe('remote H5 auth and CORS integration', () => {
|
||||
await expect(response.json()).resolves.toEqual({})
|
||||
})
|
||||
|
||||
test('keeps local loopback settings surface requests tokenless while H5 access is disabled', async () => {
|
||||
for (const endpoint of settingsSurfaceEndpoints) {
|
||||
const response = await fetch(`${baseUrl}${endpoint.path}`)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
}
|
||||
})
|
||||
|
||||
test('lets explicitly authenticated deployments use remote capability routes while H5 access is disabled', async () => {
|
||||
await restartRemoteServer({ authRequired: true })
|
||||
process.env.ANTHROPIC_API_KEY = 'test-server-key'
|
||||
@ -601,6 +615,44 @@ describe('remote H5 auth and CORS integration', () => {
|
||||
expect(validTokenResponse.status).toBe(200)
|
||||
})
|
||||
|
||||
test('requires H5 token for remote browser settings surface requests when H5 access is enabled', async () => {
|
||||
const token = await enableH5Access({
|
||||
allowedOrigins: [PHONE_ORIGIN],
|
||||
})
|
||||
|
||||
for (const endpoint of settingsSurfaceEndpoints) {
|
||||
const missingTokenResponse = await fetch(`${baseUrl}${endpoint.path}`, {
|
||||
headers: {
|
||||
Origin: PHONE_ORIGIN,
|
||||
},
|
||||
})
|
||||
expect(missingTokenResponse.status).toBe(401)
|
||||
|
||||
const wrongTokenResponse = await fetch(`${baseUrl}${endpoint.path}`, {
|
||||
headers: {
|
||||
Origin: PHONE_ORIGIN,
|
||||
Authorization: 'Bearer wrong-token',
|
||||
},
|
||||
})
|
||||
expect(wrongTokenResponse.status).toBe(401)
|
||||
|
||||
const validTokenResponse = await fetch(`${baseUrl}${endpoint.path}`, {
|
||||
headers: {
|
||||
Origin: PHONE_ORIGIN,
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
})
|
||||
expect(validTokenResponse.status).toBe(200)
|
||||
expect(validTokenResponse.headers.get('Access-Control-Allow-Origin')).toBe(PHONE_ORIGIN)
|
||||
const body = await validTokenResponse.json()
|
||||
if ('expected' in endpoint) {
|
||||
expect(body).toMatchObject(endpoint.expected)
|
||||
} else {
|
||||
expect(body).toHaveProperty(endpoint.expectedKey)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test('does not allow the server API key to replace the H5 token for remote browser requests', async () => {
|
||||
process.env.ANTHROPIC_API_KEY = 'test-server-key'
|
||||
await enableH5Access({
|
||||
@ -699,6 +751,16 @@ describe('remote H5 auth and CORS integration', () => {
|
||||
await expect(response.json()).resolves.toEqual({})
|
||||
})
|
||||
|
||||
test('keeps local loopback settings surface requests tokenless when H5 access is enabled', async () => {
|
||||
await enableH5Access()
|
||||
|
||||
for (const endpoint of settingsSurfaceEndpoints) {
|
||||
const response = await fetch(`${baseUrl}${endpoint.path}`)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
}
|
||||
})
|
||||
|
||||
test('blocks adapter requests from non-local browser origins when H5 access is enabled', async () => {
|
||||
await enableH5Access()
|
||||
|
||||
@ -711,6 +773,20 @@ describe('remote H5 auth and CORS integration', () => {
|
||||
expect(response.status).toBe(403)
|
||||
})
|
||||
|
||||
test('blocks settings surface requests from untrusted browser origins when H5 access is enabled', async () => {
|
||||
await enableH5Access()
|
||||
|
||||
for (const endpoint of settingsSurfaceEndpoints) {
|
||||
const response = await fetch(`${baseUrl}${endpoint.path}`, {
|
||||
headers: {
|
||||
Origin: PHONE_ORIGIN,
|
||||
},
|
||||
})
|
||||
|
||||
expect(response.status).toBe(403)
|
||||
}
|
||||
})
|
||||
|
||||
test('requires H5 token for remote browser websocket requests when H5 access is enabled', async () => {
|
||||
const token = await enableH5Access({
|
||||
allowedOrigins: [PHONE_ORIGIN],
|
||||
|
||||
@ -78,7 +78,14 @@ describe('h5AccessPolicy', () => {
|
||||
})
|
||||
|
||||
test('requires H5 token for LAN browser API, proxy, and chat websocket routes when enabled', () => {
|
||||
for (const pathname of ['/api/status', '/proxy/openai/v1/chat/completions', '/ws/session-1']) {
|
||||
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' },
|
||||
})
|
||||
@ -88,7 +95,15 @@ describe('h5AccessPolicy', () => {
|
||||
})
|
||||
|
||||
test('blocks LAN browser capability routes while H5 access is disabled', () => {
|
||||
for (const pathname of ['/api/status', '/proxy/openai/v1/chat/completions', '/ws/session-1', '/sdk/session-1']) {
|
||||
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' },
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user