const crypto = require("crypto"); const Sequelize = require("sequelize"); const op = Sequelize.Op; const baseModel = require("../../middleware/baseModel"); const biz_token_secret_cipher = require("../utils/biz_token_secret_cipher"); const MAX_TOKENS_PER_USER = 5; function hashPlainToken(plain) { return crypto.createHash("sha256").update(plain, "utf8").digest("hex"); } function generatePlainToken() { return `sk-${crypto.randomBytes(24).toString("hex")}`; } /** 默认 Token 过期时间:一年后当日 23:59:59 */ function defaultTokenExpireAt() { const d = new Date(); d.setFullYear(d.getFullYear() + 1); return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")} 23:59:59`; } /** 当前时间在 [start,end] 内且 status=active 的订阅 */ async function findActiveSubscriptionForUser(userId) { const now = new Date(); return baseModel.biz_subscription.findOne({ where: { user_id: userId, status: "active", start_time: { [op.lte]: now }, end_time: { [op.gte]: now }, }, order: [["id", "DESC"]], }); } async function createToken(body) { const { user_id, token_name, expire_at, key } = body; if (!user_id || !expire_at) throw new Error("缺少 user_id 或 expire_at"); const u = await baseModel.biz_user.findByPk(user_id); if (!u) throw new Error("用户不存在"); if (u.status !== "active") throw new Error("用户已禁用"); const activeCount = await baseModel.biz_api_token.count({ where: { user_id, status: "active" }, }); if (activeCount >= MAX_TOKENS_PER_USER) { throw new Error(`单用户最多 ${MAX_TOKENS_PER_USER} 个有效 Token`); } const sub = await findActiveSubscriptionForUser(user_id); const plan_id = sub ? sub.plan_id : null; const plain = generatePlainToken(); const token_hash = hashPlainToken(plain); const secret_cipher = biz_token_secret_cipher.encrypt_plain_for_storage(plain); const row = await baseModel.biz_api_token.create({ user_id, plan_id, token_name: token_name || "default", key: key || null, token_hash, secret_cipher, status: "active", expire_at, }); return { row, plain_token: plain, warn: sub ? null : "当前无生效中的订阅,鉴权将失败", }; } async function revokeToken(body) { const id = body.id; if (id == null) throw new Error("缺少 id"); const row = await baseModel.biz_api_token.findByPk(id); if (!row) throw new Error("Token 不存在"); await row.update({ status: "revoked", secret_cipher: null }); return row; } /** * 保留同一条 Token 记录,仅更换密钥(旧明文立即失效) */ async function regenerateToken(body) { const id = body.id; if (id == null) throw new Error("缺少 id"); const row = await baseModel.biz_api_token.findByPk(id); if (!row) throw new Error("Token 不存在"); if (row.status !== "active") throw new Error("仅可对状态为 active 的 Token 重新生成密钥"); const u = await baseModel.biz_user.findByPk(row.user_id); if (!u) throw new Error("用户不存在"); if (u.status !== "active") throw new Error("用户已禁用,无法轮换密钥"); const sub = await findActiveSubscriptionForUser(row.user_id); const plan_id = sub ? sub.plan_id : null; const plain = generatePlainToken(); const token_hash = hashPlainToken(plain); const secret_cipher = biz_token_secret_cipher.encrypt_plain_for_storage(plain); await row.update({ token_hash, plan_id, secret_cipher, }); await row.reload(); return { row, plain_token: plain, warn: sub ? null : "当前无生效中的订阅,鉴权将失败", }; } async function revokeAllForUser(userId) { if (userId == null) throw new Error("缺少 user_id"); const [n] = await baseModel.biz_api_token.update( { status: "revoked", secret_cipher: null }, { where: { user_id: userId, status: "active" } } ); return n; } module.exports = { hashPlainToken, createToken, regenerateToken, revokeToken, revokeAllForUser, findActiveSubscriptionForUser, defaultTokenExpireAt, MAX_TOKENS_PER_USER, };