// MCP 个人授权 token 的生成与校验 // - 明文只在创建时返回一次, 数据库仅存 SHA-256 哈希 // - token 与用户绑定, 使用该 token 调用 MCP 时拥有该用户的全部业务权限 import crypto from 'node:crypto' import { prisma } from '../db.js' const TOKEN_PREFIX = 'ibm_' const BEARER_RE = /^Bearer\s+(.+)$/i /** 生成新的 MCP token 明文 (256 bit 随机, base64url) */ export function generateMcpToken() { return TOKEN_PREFIX + crypto.randomBytes(32).toString('base64url') } /** token 明文 -> 数据库存储的哈希 */ export function hashMcpToken(raw) { return crypto.createHash('sha256').update(String(raw)).digest('hex') } /** 从 Authorization: Bearer xxx 中取出明文 token */ export function extractBearer(req) { const h = req.headers?.authorization || '' const m = String(h).match(BEARER_RE) return m ? m[1].trim() : '' } /** * 校验 MCP 请求的 token * @returns {Promise} */ export async function authenticateMcpToken(req) { const raw = extractBearer(req) if (!raw) return null const row = await prisma.mcpToken.findUnique({ where: { tokenHash: hashMcpToken(raw) }, include: { user: { select: { id: true, name: true, email: true } } }, }) if (!row || !row.user) return null // 最近使用时间: 每分钟最多写一次, 避免每次工具调用都产生写操作 const last = row.lastUsedAt ? new Date(row.lastUsedAt).getTime() : 0 if (Date.now() - last > 60_000) { prisma.mcpToken .update({ where: { id: row.id }, data: { lastUsedAt: new Date() } }) .catch((e) => console.warn('[mcp] 更新 lastUsedAt 失败:', e.message)) } return { tokenId: row.id, userId: row.userId, user: row.user } }