程序员阿江(Relakkes) 0c6d2754b4 feat: add Desktop UI backend server with full REST API and WebSocket support
Add complete server-side implementation for the Claude Code Desktop App UI:

- REST API: sessions, conversations, settings, models, scheduled tasks,
  search, agents, and status endpoints (9 modules, 30+ endpoints)
- WebSocket: real-time chat streaming with state transitions, ping/pong,
  permission request forwarding, and stop generation support
- Services: sessionService (JSONL read/write, CLI-compatible),
  settingsService (atomic writes), cronService, searchService (ripgrep),
  agentService (YAML management)
- Middleware: CORS (localhost-only), auth, unified error handling
- Tests: 180 tests (unit + E2E + business flow), all passing
- Docs: PRD, UI design spec, server architecture design

Non-invasive: all new code under src/server/, no changes to existing CLI code.
CLI/UI data interop: reads/writes the same JSONL/JSON files as the CLI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 13:23:44 +08:00

43 lines
1.1 KiB
TypeScript

/**
* Authentication middleware
*
* 本地桌面应用场景下,使用 Anthropic API Key 做简单鉴权。
* 验证请求头中的 Authorization: Bearer <key> 与 .env 中的 ANTHROPIC_API_KEY 是否匹配。
*/
export function validateAuth(req: Request): { valid: boolean; error?: string } {
const authHeader = req.headers.get('Authorization')
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>' }
}
const apiKey = process.env.ANTHROPIC_API_KEY
if (!apiKey) {
return { valid: false, error: 'Server ANTHROPIC_API_KEY not configured' }
}
if (token !== apiKey) {
return { valid: false, error: 'Invalid API key' }
}
return { valid: true }
}
/**
* Helper to check auth and return 401 if invalid
*/
export function requireAuth(req: Request): Response | null {
const { valid, error } = validateAuth(req)
if (!valid) {
return Response.json({ error: 'Unauthorized', message: error }, { status: 401 })
}
return null
}