Files
juzi/h5/lib/taobao-auth.js
T
xsl 774376fa16 feat: 完善淘宝小程序拍照、报告与登录流程
接入本地实时识别和自动三连拍,调整报告裁切、商品跳转、分享标题及居中页眉;完善游客会话、后台渠道隔离、部署脚本与回归测试。
2026-11-04 15:07:51 +08:00

44 lines
2.6 KiB
JavaScript

'use strict';
const crypto = require('node:crypto');
// Cloud gateway query parameters + the exact, unread JSON body. Algorithm:
// https://developer.alibaba.com/docs/doc.htm?articleId=121746&docType=1&treeId=832
//
// 注意:真实网关请求不带 sign_method 参数(2026-11-01 从 nginx access.log 抓包确认:
// 参数只有 app_key/open_id/sign/env/request_id/... 没有 sign_method)。缺省时网关用的是
// md5(secret + content + secret),也就是 sign_method=md5 的形式,而不是 md5(content + secret)。
// 之前按「空方法 = md5(content+secret)」实现,导致所有请求都验签失败。
function signature(params, body, secret) {
const method = params.sign_method || 'md5';
if (!['md5', 'hmac', 'hmac_md5', 'hmac_sha256'].includes(method)) return null;
const content = Object.keys(params).filter(key => key !== 'sign' && params[key] !== '').sort()
.map(key => key + params[key]).join('') + body;
if (method === 'hmac' || method === 'hmac_md5' || method === 'hmac_sha256') {
return crypto.createHmac(method === 'hmac_sha256' ? 'sha256' : 'md5', secret).update(content).digest('hex').toUpperCase();
}
return crypto.createHash('md5').update(secret + content + secret).digest('hex').toUpperCase();
}
// Guest requests still require the real signed app gateway, without userInfo
// authorization or an open_id. Body fields never establish an identity.
function verifyGateway(url, body, env = process.env) {
if (!env.TAOBAO_APP_KEY || !env.TAOBAO_APP_SECRET) return null;
const params = Object.create(null);
for (const [key, value] of new URL(url, 'http://localhost').searchParams) {
if (key in params) return null; // Never ambiguously verify duplicate parameters.
params[key] = value;
}
if (params.app_key !== env.TAOBAO_APP_KEY ||
!/^[A-Fa-f0-9]{32,64}$/.test(params.sign || '')) return null;
const expected = signature(params, body, env.TAOBAO_APP_SECRET);
if (!expected || expected.length !== params.sign.length ||
!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(params.sign.toUpperCase()))) return null;
return { appKey: params.app_key, openId: params.open_id };
}
function verify(url, body, env = process.env) {
const gateway = verifyGateway(url, body, env);
if (!gateway || !/^[A-Za-z0-9_-]{4,256}$/.test(gateway.openId || '')) return null;
// Login challenge is single-use and signed inside the body, preventing replay.
return { providerKey: crypto.createHash('sha256').update(gateway.appKey + ':' + gateway.openId).digest('hex') };
}
module.exports = { signature, verify, verifyGateway };