/** * 工作台路由组装 —— 路由契约在 @contracts/api,实现在此(编码规范 §3 / §9.2)。 * 登录 / 登出属鉴权面:中间件在 app 层,身份判定读 `org` 出口(§4 / ADR-010)。 */ import type { Hono } from 'hono'; import type { ApiError } from '@contracts'; import { CONTRACTS_VERSION, healthRoute, loginRoute, logoutRoute, sessionRoute } from '@contracts'; import type { Container } from '../container.ts'; import { SESSION_COOKIE, clearSessionCookie, setSessionCookie } from '../session-cookie.ts'; import type { AppEnv } from '../http-env.ts'; export function registerWorkbenchRoutes(app: Hono, container: Container): void { app.get(healthRoute.path, async (c) => { let db: 'up' | 'down' = 'down'; try { await container.pool.query('select 1'); db = 'up'; } catch { db = 'down'; } return c.json({ ok: db === 'up', version: CONTRACTS_VERSION, db }); }); /** 我是谁 —— 会话由中间件解好放进上下文,这里只回投影 */ app.get(sessionRoute.path, (c) => { const session = c.get('session'); if (session === undefined) { const body: ApiError = { code: 'unauthenticated', message: '尚未登录' }; return c.json(body, 401); } return c.json(session); }); app.post(loginRoute.path, async (c) => { const parsed = loginRoute.body.safeParse(await c.req.json().catch(() => ({}))); if (!parsed.success) { const body: ApiError = { code: 'validation-failed', message: '请填写账号与口令' }; return c.json(body, 400); } const issued = await container.org.login(parsed.data.account, parsed.data.password); // 失败只有一句话:不区分账号不存在 / 已停用 / 口令错,不外泄账号是否存在 if (issued === undefined) { const body: ApiError = { code: 'unauthenticated', message: '账号或口令不对' }; return c.json(body, 401); } const session = await container.org.resolveSession(issued.token); if (session === undefined) { const body: ApiError = { code: 'internal', message: '会话签发后立即失效' }; return c.json(body, 500); } setSessionCookie(c, issued.token, new Date(issued.expiresAt)); return c.json(session); }); app.post(logoutRoute.path, async (c) => { const token = c.req.header('cookie')?.match(new RegExp(`(?:^|;\\s*)${SESSION_COOKIE}=([^;]+)`))?.[1]; // 服务端表先作废,再清 cookie —— 顺序反了会留下「cookie 没了但会话还活着」的窗口 if (token !== undefined) await container.org.logout(token); clearSessionCookie(c); return c.json({ ok: true }); }); }