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:
HenryXiaoYang
2026-03-10 02:29:06 +08:00
commit ba754ccc31
33 changed files with 14992 additions and 0 deletions

96
http/crypto-utils.ts Normal file
View 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 - 微信服务号配置的 EncodingAESKey43位字符
* @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}';
};