feat: add WeChat QR code login and AGP WebSocket channel plugin
- Auth module: WeChat OAuth2 scan-to-login flow with terminal QR code - Token persistence to ~/.openclaw/wechat-access-auth.json (chmod 600) - Token resolution: config > saved state > interactive login - Invite code verification (configurable bypass) - Production/test environment support - AGP WebSocket client with heartbeat, reconnect, wake detection - Message handler: Agent dispatch with streaming text and tool calls - Random device GUID generation (persisted, no real machine ID)
This commit is contained in:
96
http/crypto-utils.ts
Normal file
96
http/crypto-utils.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
// ============================================
|
||||
// 加密解密工具
|
||||
// ============================================
|
||||
// 处理微信服务号的消息加密、解密和签名验证
|
||||
// 微信使用 AES-256-CBC 加密算法和 SHA-1 签名算法
|
||||
|
||||
/**
|
||||
* 验证签名参数接口
|
||||
* @property token - 微信服务号配置的 Token
|
||||
* @property timestamp - 时间戳
|
||||
* @property nonce - 随机数
|
||||
* @property encrypt - 加密的消息内容
|
||||
* @property signature - 微信生成的签名,用于验证消息来源
|
||||
*/
|
||||
export interface VerifySignatureParams {
|
||||
token: string;
|
||||
timestamp: string;
|
||||
nonce: string;
|
||||
encrypt: string;
|
||||
signature: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密消息参数接口
|
||||
* @property encodingAESKey - 微信服务号配置的 EncodingAESKey(43位字符)
|
||||
* @property receiveId - 接收方 ID(通常是服务号的原始 ID)
|
||||
* @property encrypt - 加密的消息内容(Base64 编码)
|
||||
*/
|
||||
export interface DecryptMessageParams {
|
||||
encodingAESKey: string;
|
||||
receiveId: string;
|
||||
encrypt: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证微信消息签名
|
||||
* @param params - 签名验证参数
|
||||
* @returns 签名是否有效
|
||||
* @description
|
||||
* 验证流程:
|
||||
* 1. 将 token、timestamp、nonce、encrypt 按字典序排序
|
||||
* 2. 拼接成字符串
|
||||
* 3. 进行 SHA-1 哈希
|
||||
* 4. 与微信提供的 signature 比对
|
||||
*
|
||||
* **注意:当前为简化实现,生产环境需要实现真实的 SHA-1 签名验证**
|
||||
*/
|
||||
export const verifySignature = (params: VerifySignatureParams): boolean => {
|
||||
// TODO: 实现真实的签名验证逻辑
|
||||
// 参考算法:
|
||||
// const arr = [params.token, params.timestamp, params.nonce, params.encrypt].sort();
|
||||
// const str = arr.join('');
|
||||
// const hash = crypto.createHash('sha1').update(str).digest('hex');
|
||||
// return hash === params.signature;
|
||||
|
||||
console.log("[wechat-access] 验证签名参数:", params);
|
||||
return true; // 简化实现,直接返回 true
|
||||
};
|
||||
|
||||
/**
|
||||
* 解密微信消息
|
||||
* @param params - 解密参数
|
||||
* @returns 解密后的明文消息(JSON 字符串)
|
||||
* @description
|
||||
* 解密流程:
|
||||
* 1. 将 Base64 编码的 encrypt 解码为二进制
|
||||
* 2. 使用 AES-256-CBC 算法解密(密钥由 encodingAESKey 派生)
|
||||
* 3. 去除填充(PKCS7)
|
||||
* 4. 提取消息内容(格式:随机16字节 + 4字节消息长度 + 消息内容 + receiveId)
|
||||
* 5. 验证 receiveId 是否匹配
|
||||
*
|
||||
* **注意:当前为简化实现,返回模拟数据,生产环境需要实现真实的 AES 解密**
|
||||
*/
|
||||
export const decryptMessage = (params: DecryptMessageParams): string => {
|
||||
// TODO: 实现真实的解密逻辑
|
||||
// 参考算法:
|
||||
// const key = Buffer.from(params.encodingAESKey + '=', 'base64');
|
||||
// const iv = key.slice(0, 16);
|
||||
// const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
|
||||
// decipher.setAutoPadding(false);
|
||||
// let decrypted = Buffer.concat([decipher.update(params.encrypt, 'base64'), decipher.final()]);
|
||||
// // 去除 PKCS7 填充
|
||||
// const pad = decrypted[decrypted.length - 1];
|
||||
// decrypted = decrypted.slice(0, decrypted.length - pad);
|
||||
// // 提取消息内容
|
||||
// const content = decrypted.slice(16);
|
||||
// const msgLen = content.readUInt32BE(0);
|
||||
// const message = content.slice(4, 4 + msgLen).toString('utf8');
|
||||
// const receiveId = content.slice(4 + msgLen).toString('utf8');
|
||||
// if (receiveId !== params.receiveId) throw new Error('receiveId mismatch');
|
||||
// return message;
|
||||
|
||||
console.log("[wechat-access] 解密参数:", params);
|
||||
// 返回模拟的解密结果(标准微信消息格式)
|
||||
return '{"msgtype":"text","Content":"Hello from 服务号","MsgId":"123456","FromUserName":"user001","ToUserName":"gh_test","CreateTime":1234567890}';
|
||||
};
|
||||
Reference in New Issue
Block a user