mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-18 13:23:33 +08:00
Complete the Electron replacement boundary before merging by removing the renderer-side Tauri host fallback, tightening H5/browser access so only desktop navigation is tokenless, and moving desktop release publication to a tag-driven GitHub Actions matrix with a single final publish job. Constraint: H5/browser capability access must not gain tokenless access through localhost or retired Tauri origins Constraint: Desktop release artifacts must be built by GitHub Actions from version tags, not treated as local build outputs Rejected: Keep localhost browser origins trusted for convenience | local browser contexts can access loopback services and must use the H5 token path Rejected: Publish from each matrix job | partial releases can be created before all platforms finish Confidence: high Scope-risk: broad Directive: Do not reintroduce Tauri origins or localhost browser origins into the trusted desktop origin set without a reviewed security design Tested: bun test src/server/__tests__/h5-access-policy.test.ts src/server/__tests__/h5-access-auth.test.ts src/server/__tests__/diagnostics-service.test.ts src/server/middleware/cors.test.ts Tested: bun test scripts/pr/release-workflow.test.ts scripts/release-update-metadata.test.ts Tested: bun run check:desktop Tested: bun run check:native Tested: git diff --check Not-tested: bun run check:server is blocked by expired quarantine entries server:cron-scheduler, server:providers-real, server:tasks, server:e2e:business-flow, server:e2e:full-flow
120 lines
3.0 KiB
TypeScript
120 lines
3.0 KiB
TypeScript
export type H5RequestKind = 'local-trusted' | 'internal-sdk' | 'h5-browser'
|
|
export type H5RequestContext = {
|
|
clientAddress: string | null
|
|
}
|
|
|
|
const LOCAL_HOSTS = new Set(['localhost', '127.0.0.1', '::1'])
|
|
const LOCAL_DESKTOP_ORIGINS = new Set(['file://'])
|
|
|
|
export function normalizeHostname(hostname: string): string {
|
|
return hostname.trim().replace(/^\[/, '').replace(/\]$/, '').toLowerCase()
|
|
}
|
|
|
|
export function isLoopbackHost(hostname: string): boolean {
|
|
const normalized = normalizeHostname(hostname)
|
|
if (normalized.startsWith('::ffff:')) {
|
|
return isLoopbackHost(normalized.slice('::ffff:'.length))
|
|
}
|
|
return LOCAL_HOSTS.has(normalized)
|
|
}
|
|
|
|
function isLocalDesktopOrNavigationOrigin(origin: string | null): boolean {
|
|
if (!origin) return true
|
|
return LOCAL_DESKTOP_ORIGINS.has(origin)
|
|
}
|
|
|
|
function isFilesystemCapabilityPath(pathname: string): boolean {
|
|
return pathname.startsWith('/local-file/') ||
|
|
pathname.startsWith('/preview-fs/')
|
|
}
|
|
|
|
export function classifyH5Request(
|
|
request: Request,
|
|
url: URL,
|
|
context: H5RequestContext,
|
|
): H5RequestKind {
|
|
const origin = request.headers.get('Origin')
|
|
if (isFilesystemCapabilityPath(url.pathname)) {
|
|
const localFilesystemTrusted = Boolean(context.clientAddress) &&
|
|
isLoopbackHost(context.clientAddress!) &&
|
|
isLocalDesktopOrNavigationOrigin(origin)
|
|
|
|
return localFilesystemTrusted ? 'local-trusted' : 'h5-browser'
|
|
}
|
|
|
|
const localTrusted = Boolean(context.clientAddress) &&
|
|
isLoopbackHost(context.clientAddress!) &&
|
|
isLocalDesktopOrNavigationOrigin(origin)
|
|
|
|
if (url.pathname.startsWith('/sdk/') && localTrusted) {
|
|
return 'internal-sdk'
|
|
}
|
|
|
|
if (localTrusted) {
|
|
return 'local-trusted'
|
|
}
|
|
|
|
return 'h5-browser'
|
|
}
|
|
|
|
export function shouldRequireH5Token({
|
|
request,
|
|
url,
|
|
h5Enabled,
|
|
context,
|
|
}: {
|
|
request: Request
|
|
url: URL
|
|
h5Enabled: boolean
|
|
context: H5RequestContext
|
|
}): boolean {
|
|
if (!h5Enabled) {
|
|
return false
|
|
}
|
|
|
|
if (!isH5BrowserCapabilityPath(url.pathname)) {
|
|
return false
|
|
}
|
|
|
|
return classifyH5Request(request, url, context) === 'h5-browser'
|
|
}
|
|
|
|
export function shouldBlockDisabledH5Access({
|
|
request,
|
|
url,
|
|
h5Enabled,
|
|
explicitAuthRequired,
|
|
context,
|
|
}: {
|
|
request: Request
|
|
url: URL
|
|
h5Enabled: boolean
|
|
explicitAuthRequired: boolean
|
|
context: H5RequestContext
|
|
}): boolean {
|
|
if (h5Enabled || explicitAuthRequired) {
|
|
return false
|
|
}
|
|
|
|
if (!isH5ProtectedCapabilityPath(url.pathname)) {
|
|
return false
|
|
}
|
|
|
|
return classifyH5Request(request, url, context) === 'h5-browser'
|
|
}
|
|
|
|
function isH5ProtectedCapabilityPath(pathname: string): boolean {
|
|
return pathname.startsWith('/api/') ||
|
|
isFilesystemCapabilityPath(pathname) ||
|
|
pathname.startsWith('/proxy/') ||
|
|
pathname.startsWith('/ws/') ||
|
|
pathname.startsWith('/sdk/')
|
|
}
|
|
|
|
function isH5BrowserCapabilityPath(pathname: string): boolean {
|
|
return pathname.startsWith('/api/') ||
|
|
isFilesystemCapabilityPath(pathname) ||
|
|
pathname.startsWith('/proxy/') ||
|
|
pathname.startsWith('/ws/')
|
|
}
|