mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
Remote browser access needs an explicit path that preserves localhost desktop behavior while allowing H5 tokens for REST and client websocket upgrades. This keeps the existing synchronous Anthropic auth helper intact, adds async request validation for H5 tokens, and centralizes origin handling so preflight, API, proxy, websocket, and health responses behave consistently. Constraint: Existing validateAuth(req) must stay synchronous for current tests and callers Constraint: Default localhost and Tauri desktop behavior must remain unchanged Rejected: Replacing validateAuth with an async-only API | would break existing sync tests and call sites Rejected: Allowing wildcard or fallback remote origins | violates the H5 explicit-origin security model Directive: Restore ProviderService serverPort in server integration tests that call startServer with custom ports to avoid leaking global proxy runtime state Confidence: high Scope-risk: moderate Tested: bun test src/server/__tests__/h5-access-auth.test.ts --timeout 30000 Tested: bun test src/server/__tests__/h5-access-service.test.ts src/server/__tests__/h5-access-api.test.ts src/server/__tests__/settings.test.ts --timeout 30000 Tested: bun test src/server/__tests__/h5-access-auth.test.ts src/server/__tests__/conversation-service.test.ts src/server/__tests__/cron-scheduler-launcher.test.ts --timeout 30000 Tested: bun test src/server/__tests__/e2e/full-flow.test.ts --timeout 30000 Not-tested: Full non-live server gate end-to-end; an attempted broader run was interrupted after uncovering and then fixing a test-side ProviderService port leak
76 lines
2.1 KiB
TypeScript
76 lines
2.1 KiB
TypeScript
/**
|
|
* Authentication middleware
|
|
*
|
|
* 本地桌面应用场景下,使用 Anthropic API Key 做简单鉴权。
|
|
* 验证请求头中的 Authorization: Bearer <key> 与 .env 中的 ANTHROPIC_API_KEY 是否匹配。
|
|
*/
|
|
|
|
import { H5AccessService } from '../services/h5AccessService.js'
|
|
|
|
type AuthResult = { valid: boolean; error?: string }
|
|
|
|
function parseBearerToken(authHeader: string | null): AuthResult & { token?: string } {
|
|
if (!authHeader) {
|
|
return { valid: false, error: 'Missing Authorization header' }
|
|
}
|
|
|
|
const [scheme, token] = authHeader.split(' ')
|
|
|
|
if (scheme !== 'Bearer' || !token) {
|
|
return { valid: false, error: 'Invalid Authorization format. Use: Bearer <token>' }
|
|
}
|
|
|
|
return { valid: true, token }
|
|
}
|
|
|
|
export function validateAuth(req: Request): AuthResult {
|
|
const parsedAuth = parseBearerToken(req.headers.get('Authorization'))
|
|
if (!parsedAuth.valid || !parsedAuth.token) {
|
|
return parsedAuth
|
|
}
|
|
|
|
const apiKey = process.env.ANTHROPIC_API_KEY
|
|
if (!apiKey) {
|
|
return { valid: false, error: 'Server ANTHROPIC_API_KEY not configured' }
|
|
}
|
|
|
|
if (parsedAuth.token !== apiKey) {
|
|
return { valid: false, error: 'Invalid API key' }
|
|
}
|
|
|
|
return { valid: true }
|
|
}
|
|
|
|
/**
|
|
* Helper to check auth and return 401 if invalid
|
|
*/
|
|
export async function validateRequestAuth(
|
|
req: Request,
|
|
tokenOverride?: string | null,
|
|
): Promise<AuthResult> {
|
|
const anthropicAuth = validateAuth(req)
|
|
if (anthropicAuth.valid) {
|
|
return anthropicAuth
|
|
}
|
|
|
|
const parsedAuth = parseBearerToken(req.headers.get('Authorization'))
|
|
const h5Token = tokenOverride ?? parsedAuth.token
|
|
if (h5Token) {
|
|
const h5AccessService = new H5AccessService()
|
|
if (await h5AccessService.validateToken(h5Token)) {
|
|
return { valid: true }
|
|
}
|
|
return { valid: false, error: 'Invalid H5 access token' }
|
|
}
|
|
|
|
return anthropicAuth
|
|
}
|
|
|
|
export async function requireAuth(req: Request, tokenOverride?: string | null): Promise<Response | null> {
|
|
const { valid, error } = await validateRequestAuth(req, tokenOverride)
|
|
if (!valid) {
|
|
return Response.json({ error: 'Unauthorized', message: error }, { status: 401 })
|
|
}
|
|
return null
|
|
}
|