Files
ChatLab/apps/cli/src/http/auth.ts
T
digua 09d4b1ddac feat(cli): add chatlab web command for one-click Web UI launch
- New `chatlab web` subcommand: starts HTTP API + serves Web SPA + auto-opens browser
- Options: --port, --host, --no-open
- Cross-platform `openBrowser()` using open/start/xdg-open
- Add `setWebMode()` in auth: static resources bypass token auth in web mode
- Add `build:full` and `prepare:web` scripts for dist-web preparation
2026-05-19 21:53:00 +08:00

60 lines
1.7 KiB
TypeScript

/**
* ChatLab HTTP API — Bearer Token authentication hook
*
* 从 electron/main/api/auth.ts 迁移,使用 @openchatlab/config 读取 token。
*/
import type { FastifyRequest, FastifyReply } from 'fastify'
import { timingSafeEqual } from 'crypto'
import { unauthorized, errorResponse } from './errors'
let cachedToken: string | null = null
let webModeEnabled = false
/**
* 设置 auth hook 使用的 token(由 server 启动时注入)
*/
export function setAuthToken(token: string): void {
cachedToken = token
}
/**
* Enable web mode: static resources and SPA paths bypass auth.
* Only `/api/` routes remain protected.
*/
export function setWebMode(enabled: boolean): void {
webModeEnabled = enabled
}
function safeTokenCompare(a: string, b: string): boolean {
const bufA = Buffer.from(a)
const bufB = Buffer.from(b)
if (bufA.length !== bufB.length) return false
return timingSafeEqual(bufA, bufB)
}
export async function authHook(request: FastifyRequest, reply: FastifyReply): Promise<void> {
if (!cachedToken) return
// /_web/ internal API: no auth required (same-origin Web UI only)
if (request.url.startsWith('/_web/')) return
// Web mode: only /api/ routes require auth; static files and SPA are public
if (webModeEnabled && !request.url.startsWith('/api/')) return
const authHeader = request.headers.authorization
if (!authHeader || !authHeader.startsWith('Bearer ')) {
const err = unauthorized()
reply.code(err.statusCode).send(errorResponse(err))
return
}
const token = authHeader.slice(7)
if (!safeTokenCompare(token, cachedToken)) {
const err = unauthorized()
reply.code(err.statusCode).send(errorResponse(err))
return
}
}