| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879 |
- const crypto = require('crypto');
- const { readConfig } = require('./webhook-config');
- function getHeader(headers, name) {
- const lower = name.toLowerCase();
- for (const [key, value] of Object.entries(headers)) {
- if (key.toLowerCase() === lower) {
- if (Array.isArray(value)) return value[0];
- return value;
- }
- }
- return undefined;
- }
- function extractSignature(headers) {
- const auth = getHeader(headers, 'authorization');
- if (auth) {
- const bearerMatch = auth.match(/^Bearer\s+(.+)$/i);
- if (bearerMatch) return bearerMatch[1].trim();
- return auth.trim();
- }
- const sigHeaders = ['x-qiwe-signature', 'x-qiweapi-signature', 'x-signature'];
- for (const name of sigHeaders) {
- const val = getHeader(headers, name);
- if (val) return val;
- }
- return null;
- }
- function timingSafeEqualStrings(a, b) {
- if (a.length !== b.length) return false;
- return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
- }
- function verifyWebhookSignature(rawBody, headers) {
- const config = readConfig();
- if (process.env.QIWEI_WEBHOOK_ALLOW_UNSECURED === 'true') {
- console.warn('[Webhook] QIWEI_WEBHOOK_ALLOW_UNSECURED=true,允许未签名回调(仅开发环境使用)');
- return true;
- }
- if (!config.secret) {
- console.warn('[Webhook] Webhook 未配置 secret,回调已拒绝');
- return false;
- }
- const signature = extractSignature(headers);
- if (!signature) {
- console.warn('[Webhook] 请求缺少签名信息');
- return false;
- }
- if (timingSafeEqualStrings(signature, config.secret)) {
- return true;
- }
- try {
- const expected = crypto
- .createHmac('sha256', config.secret)
- .update(rawBody, 'utf8')
- .digest('hex');
- if (timingSafeEqualStrings(expected, signature)) {
- return true;
- }
- } catch {
- // ignore
- }
- console.warn('[Webhook] 签名验证失败');
- return false;
- }
- module.exports = {
- verifyWebhookSignature,
- extractSignature,
- getHeader,
- timingSafeEqualStrings
- };
|