P0 MVP: - 认证:login/refresh/logout/forgot/reset + tokenVersion - RBAC:角色到权限映射 + requirePermission - 发票池:upload/bind/signed-url + 判重(VALID_003/VALID_005) - 报销:create/view/submit + 单号生成 - 工作流:approve + department_manager 解析 P1 扩展: - 动作:reject/return/withdraw(含 comment) - transfer:任务转审 + 链路上限 3 - pay:approved -> paid + 发票归档 used_complete - 动态审批链:approval_rules 驱动 回归脚本: - smoke-test.sh / regression-dedup.sh / regression-workflow-actions.sh
46 行
1.2 KiB
JavaScript
46 行
1.2 KiB
JavaScript
const crypto = require("node:crypto");
|
|
|
|
function toBase64Url(value) {
|
|
const source = typeof value === "string" ? value : JSON.stringify(value);
|
|
return Buffer.from(source).toString("base64url");
|
|
}
|
|
|
|
function fromBase64Url(value) {
|
|
return Buffer.from(value, "base64url").toString("utf8");
|
|
}
|
|
|
|
function signPayload(payload, secret) {
|
|
const body = toBase64Url(payload);
|
|
const signature = crypto.createHmac("sha256", secret).update(body).digest("base64url");
|
|
return `${body}.${signature}`;
|
|
}
|
|
|
|
function verifyToken(token, secret) {
|
|
if (typeof token !== "string") {
|
|
return { ok: false, reason: "invalid_format" };
|
|
}
|
|
|
|
const segments = token.split(".");
|
|
if (segments.length !== 2) {
|
|
return { ok: false, reason: "invalid_format" };
|
|
}
|
|
|
|
const [body, signature] = segments;
|
|
const expected = crypto.createHmac("sha256", secret).update(body).digest("base64url");
|
|
if (signature !== expected) {
|
|
return { ok: false, reason: "invalid_signature" };
|
|
}
|
|
|
|
try {
|
|
const payload = JSON.parse(fromBase64Url(body));
|
|
return { ok: true, payload };
|
|
} catch {
|
|
return { ok: false, reason: "invalid_payload" };
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
signPayload,
|
|
verifyToken,
|
|
};
|