mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-26 15:03:34 +08:00
Prepare H5 auth to distinguish browser traffic from trusted local callers
Add a small standalone request classifier and focused server tests so later H5 token enforcement can target LAN/browser requests without changing the current desktop, SDK, or adapter paths. Constraint: Task scope is limited to a standalone classifier plus tests in two server files Constraint: Local desktop WebView, loopback integrations, and /sdk routes must remain tokenless for now Rejected: Integrate the classifier into auth middleware now | this task only introduces the reusable classification seam Confidence: high Scope-risk: narrow Directive: Keep local-trusted and internal-sdk classifications tokenless unless the server auth integration changes with matching regression coverage Tested: bun test src/server/__tests__/h5-access-policy.test.ts Tested: per-file TypeScript diagnostics for src/server/h5AccessPolicy.ts and src/server/__tests__/h5-access-policy.test.ts Not-tested: Full server auth middleware integration Not-tested: bun run check:server remains red from pre-existing repo dependency and unrelated test failures
This commit is contained in:
parent
1e27d4634a
commit
115e6f3b36
54
src/server/__tests__/h5-access-policy.test.ts
Normal file
54
src/server/__tests__/h5-access-policy.test.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { describe, expect, test } from 'bun:test'
|
||||||
|
import {
|
||||||
|
classifyH5Request,
|
||||||
|
isLoopbackHost,
|
||||||
|
shouldRequireH5Token,
|
||||||
|
} from '../h5AccessPolicy.js'
|
||||||
|
|
||||||
|
function req(url: string, init: RequestInit = {}) {
|
||||||
|
return new Request(url, init)
|
||||||
|
}
|
||||||
|
|
||||||
|
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))).toBe('local-trusted')
|
||||||
|
expect(shouldRequireH5Token({ request, url: new URL(request.url), h5Enabled: true, explicitAuthRequired: false })).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('keeps internal SDK websocket routes tokenless', () => {
|
||||||
|
const request = req('http://192.168.0.20:3456/sdk/session-1')
|
||||||
|
expect(classifyH5Request(request, new URL(request.url))).toBe('internal-sdk')
|
||||||
|
expect(shouldRequireH5Token({ request, url: new URL(request.url), h5Enabled: true, explicitAuthRequired: false })).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))).toBe('local-trusted')
|
||||||
|
expect(shouldRequireH5Token({ request, url: new URL(request.url), h5Enabled: true, explicitAuthRequired: false })).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
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']) {
|
||||||
|
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))).toBe('h5-browser')
|
||||||
|
expect(shouldRequireH5Token({ request, url: new URL(request.url), h5Enabled: true, explicitAuthRequired: false })).toBe(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('explicit deployment auth still requires auth even 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, explicitAuthRequired: true })).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
61
src/server/h5AccessPolicy.ts
Normal file
61
src/server/h5AccessPolicy.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
export type H5RequestKind = 'local-trusted' | 'internal-sdk' | 'h5-browser'
|
||||||
|
|
||||||
|
const LOCAL_HOSTS = new Set(['localhost', '127.0.0.1', '::1'])
|
||||||
|
const LOCAL_ORIGINS = new Set([
|
||||||
|
'http://tauri.localhost',
|
||||||
|
'https://tauri.localhost',
|
||||||
|
'tauri://localhost',
|
||||||
|
])
|
||||||
|
|
||||||
|
export function normalizeHostname(hostname: string): string {
|
||||||
|
return hostname.trim().replace(/^\[/, '').replace(/\]$/, '').toLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isLoopbackHost(hostname: string): boolean {
|
||||||
|
return LOCAL_HOSTS.has(normalizeHostname(hostname))
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLocalOrigin(origin: string | null): boolean {
|
||||||
|
if (!origin) return true
|
||||||
|
if (LOCAL_ORIGINS.has(origin)) return true
|
||||||
|
|
||||||
|
try {
|
||||||
|
return isLoopbackHost(new URL(origin).hostname)
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function classifyH5Request(request: Request, url: URL): H5RequestKind {
|
||||||
|
if (url.pathname.startsWith('/sdk/')) {
|
||||||
|
return 'internal-sdk'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoopbackHost(url.hostname) && isLocalOrigin(request.headers.get('Origin'))) {
|
||||||
|
return 'local-trusted'
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'h5-browser'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shouldRequireH5Token({
|
||||||
|
request,
|
||||||
|
url,
|
||||||
|
h5Enabled,
|
||||||
|
explicitAuthRequired,
|
||||||
|
}: {
|
||||||
|
request: Request
|
||||||
|
url: URL
|
||||||
|
h5Enabled: boolean
|
||||||
|
explicitAuthRequired: boolean
|
||||||
|
}): boolean {
|
||||||
|
if (explicitAuthRequired) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!h5Enabled) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return classifyH5Request(request, url) === 'h5-browser'
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user