/** * 认证与权限模块(生产级基础)。 * * 当前实现:基于 JWT 的无状态认证 + 角色权限校验中间件。 * 密钥取自环境变量 AUTH_SECRET,未配置时降级为无校验(演示模式)。 */ import type { Context, Next } from 'hono'; import { createHmac } from 'node:crypto'; export type AuthRole = '商务/销售' | '风控' | '管理层' | '系统管理员'; export interface AuthPayload { username: string; role: AuthRole; iat: number; exp: number; } const SECRET = (): string => process.env.AUTH_SECRET ?? ''; /** 简易 HMAC-SHA256 签名(不依赖外部库,生产建议替换为 jose)。 */ function base64url(buf: Buffer): string { return buf.toString('base64url'); } function sign(payload: object): string { if (SECRET() === '') return ''; // 演示模式不签发 const header = base64url(Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' }))); const body = base64url(Buffer.from(JSON.stringify(payload))); const sig = base64url(createHmac('sha256', SECRET()).update(`${header}.${body}`).digest()); return `${header}.${body}.${sig}`; } function verify(token: string): AuthPayload | null { if (SECRET() === '') return null; try { const [header, body, sig] = token.split('.'); if (!header || !body || !sig) return null; const expected = base64url(createHmac('sha256', SECRET()).update(`${header}.${body}`).digest()); if (sig !== expected) return null; const payload = JSON.parse(Buffer.from(body, 'base64url').toString()) as AuthPayload; if (payload.exp < Date.now() / 1000) return null; return payload; } catch { return null; } } /** 签发 JWT(登录成功后调用)。 */ export function issueToken(username: string, role: AuthRole): string { const now = Math.floor(Date.now() / 1000); return sign({ username, role, iat: now, exp: now + 86400 }); } /** 无需鉴权的公共路径(登录与健康检查)。 */ const PUBLIC_PATHS = new Set(['/api/health', '/api/auth/login', '/api/llm/status']); /** * Hono 中间件:从 Authorization Bearer token 解析并注入当前用户(供 requireRole 使用)。 * * 设计:本中间件**只负责识别身份、不负责拦截**——读操作保持开放,敏感写操作由 * {@link requireRole} 按角色拦截。AUTH_SECRET 未配置时为演示模式(不识别身份)。 * 这样开启鉴权后既能强制敏感操作的角色校验,又不破坏只读接口与看板。 */ export function authMiddleware() { return async (c: Context, next: Next): Promise => { if (SECRET() === '' || PUBLIC_PATHS.has(c.req.path)) { await next(); return; } const auth = c.req.header('Authorization'); if (auth !== undefined && auth.startsWith('Bearer ')) { const payload = verify(auth.slice(7)); if (payload !== null) { c.set('user', payload); } } await next(); }; } /** 角色权限校验中间件。 */ export function requireRole(...roles: AuthRole[]) { return async (c: Context, next: Next): Promise => { if (SECRET() === '') { await next(); return; } const user = c.get('user') as AuthPayload | undefined; if (user === undefined || !roles.includes(user.role)) { return c.json({ error: '权限不足' }, 403); } await next(); }; }