Docker Build and Push / build-image (push) Successful in 4m34s
- 新增 /api/mcp (Streamable HTTP) 与 41 个工具: 项目/看板列/任务/清单/打卡; - 新增 McpToken 模型与迁移, DB 仅存 SHA-256 哈希, token 绑定用户; - 新增 /api/mcp-tokens 增删改查, 支持独立命名 (如 R1); - 「我的」页新增 AI 接入 (MCP) 区块: 地址复制/新建/一次性展示/删除; - 补充 docs/MCP.md 与样式
51 lines
1.8 KiB
JavaScript
51 lines
1.8 KiB
JavaScript
// 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<null | { tokenId: number, userId: number, user: { id: number, name: string, email: string } }>}
|
|
*/
|
|
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 }
|
|
}
|