/** * Hono 应用 —— 自建应用,Mastra 以库形态嵌入(ADR-001)。 * 鉴权、岗位权限、SSE 全归 app 层;不暴露 Mastra 默认端点。 */ import { Hono } from 'hono'; import type { ApiError } from '@contracts'; import { healthRoute, ingestMailRoute, loginRoute } from '@contracts'; import type { Container } from './container.ts'; import type { AppEnv } from './http-env.ts'; import { readSessionToken } from './session-cookie.ts'; import { registerDeskRoutes } from './routes/desk.ts'; import { registerDeskElementRoutes } from './routes/desk-elements.ts'; import { registerDeskApprovalRoutes } from './routes/desk-approval.ts'; import { registerRegistrationRoutes } from './routes/registration.ts'; import { registerGovernanceOpsRoutes } from './routes/governance-ops.ts'; import { registerGovernanceRoutes } from './routes/governance.ts'; import { registerIngestRoutes } from './routes/ingest.ts'; import { registerStreamRoute } from './routes/stream.ts'; import { registerWorkbenchRoutes } from './routes/workbench.ts'; export type { AppEnv }; /** * 免鉴权白名单(ADR-010)—— 只三条,其余一律 401。 * 入站邮件是**外界边界**:它代替真实 IMAP 拉件,不是人的请求,没有会话可言。 */ const INGEST_PREFIX = ingestMailRoute.path.slice(0, ingestMailRoute.path.indexOf(':')); function isPublic(method: string, path: string): boolean { if (method === 'GET' && path === healthRoute.path) return true; if (method === 'POST' && path === loginRoute.path) return true; if (method === 'POST' && path.startsWith(INGEST_PREFIX)) return true; return false; } export function createServer(container: Container): Hono { const app = new Hono(); /** * 鉴权中间件(编码规范 §4:中间件在 app 层,身份判定读 `org` 出口;ADR-010)。 * * 身份**只能**来自服务端会话表:cookie 里是一枚不透明 token,改它只会让会话解不出来, * 冒充不了任何人 —— 这正是明文 `jcai_account` cookie 被废止的理由。 */ app.use('*', async (c, next) => { const token = readSessionToken(c.req.header('cookie')); const session = token === undefined ? undefined : await container.org.resolveSession(token); c.set('session', session); c.set('account', session?.staff.account ?? ''); if (session === undefined && !isPublic(c.req.method, c.req.path)) { const body: ApiError = { code: 'unauthenticated', message: '尚未登录' }; return c.json(body, 401); } await next(); return undefined; }); app.onError((error, c) => { const body: ApiError = { code: 'internal', message: error instanceof Error ? error.message : String(error), }; process.stderr.write(`[api] ${body.message}\n`); return c.json(body, 500); }); app.notFound((c) => { const body: ApiError = { code: 'not-found', message: `无此路由:${c.req.path}` }; return c.json(body, 404); }); registerWorkbenchRoutes(app, container); registerGovernanceRoutes(app, container); registerDeskRoutes(app, container); registerDeskElementRoutes(app, container); registerDeskApprovalRoutes(app, container); registerRegistrationRoutes(app, container); registerGovernanceOpsRoutes(app, container); registerIngestRoutes(app, container); registerStreamRoute(app, container.broadcaster); return app; }