151 lines
4.0 KiB
JavaScript
151 lines
4.0 KiB
JavaScript
'use strict';
|
|
|
|
const crypto = require('crypto');
|
|
const axios = require('axios');
|
|
const { validateSession } = require('./email-auth');
|
|
|
|
// ─── 常量 ───
|
|
const HANDSHAKE_EXPIRE_MS = 5 * 60 * 1000; // 5分钟
|
|
const HEARTBEAT_INTERVAL = 30 * 1000; // 30秒
|
|
const MAX_RETRIES = 3;
|
|
|
|
// ─── 握手状态存储 ───
|
|
const pendingHandshakes = new Map(); // token → { agentId, publicKey, createdAt }
|
|
const activeConnections = new Map(); // connectionId → { agentId, partnerId, heartbeatAt }
|
|
|
|
// ─── 错误代码 ───
|
|
const ERRORS = {
|
|
INVALID_SESSION: { code: 'HS001', message: '无效会话' },
|
|
INVALID_AGENT_ID: { code: 'HS002', message: '无效Agent ID' },
|
|
HANDSHAKE_EXPIRED: { code: 'HS003', message: '握手过期' },
|
|
ENCRYPTION_FAILED: { code: 'HS004', message: '加密失败' },
|
|
DECRYPTION_FAILED: { code: 'HS005', message: '解密失败' },
|
|
INVALID_SIGNATURE: { code: 'HS006', message: '无效签名' },
|
|
CONNECTION_LOST: { code: 'HS007', message: '连接丢失' }
|
|
};
|
|
|
|
/**
|
|
* 生成密钥对
|
|
*/
|
|
function generateKeyPair() {
|
|
return crypto.generateKeyPairSync('rsa', {
|
|
modulusLength: 2048,
|
|
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 初始化握手
|
|
* @param {string} token - 会话token
|
|
* @param {string} agentId - Agent ID
|
|
* @returns {{ handshakeId: string, publicKey: string }}
|
|
*/
|
|
function initHandshake(token, agentId) {
|
|
if (!validateSession(token).valid) {
|
|
throw ERRORS.INVALID_SESSION;
|
|
}
|
|
|
|
if (!agentId || typeof agentId !== 'string') {
|
|
throw ERRORS.INVALID_AGENT_ID;
|
|
}
|
|
|
|
const handshakeId = crypto.randomBytes(16).toString('hex');
|
|
const keyPair = generateKeyPair();
|
|
|
|
pendingHandshakes.set(handshakeId, {
|
|
agentId,
|
|
publicKey: keyPair.publicKey,
|
|
privateKey: keyPair.privateKey,
|
|
createdAt: Date.now()
|
|
});
|
|
|
|
return { handshakeId, publicKey: keyPair.publicKey };
|
|
}
|
|
|
|
/**
|
|
* 完成握手
|
|
* @param {string} handshakeId - 握手ID
|
|
* @param {string} partnerId - 对方Agent ID
|
|
* @param {string} encryptedData - 加密数据
|
|
* @returns {{ connectionId: string, partnerPublicKey: string }}
|
|
*/
|
|
function completeHandshake(handshakeId, partnerId, encryptedData) {
|
|
const handshake = pendingHandshakes.get(handshakeId);
|
|
if (!handshake) {
|
|
throw ERRORS.INVALID_SESSION;
|
|
}
|
|
|
|
if (Date.now() - handshake.createdAt > HANDSHAKE_EXPIRE_MS) {
|
|
pendingHandshakes.delete(handshakeId);
|
|
throw ERRORS.HANDSHAKE_EXPIRED;
|
|
}
|
|
|
|
try {
|
|
const decrypted = crypto.privateDecrypt(
|
|
{
|
|
key: handshake.privateKey,
|
|
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
|
|
},
|
|
Buffer.from(encryptedData, 'base64')
|
|
).toString('utf8');
|
|
|
|
const { publicKey: partnerPublicKey } = JSON.parse(decrypted);
|
|
|
|
const connectionId = crypto.randomBytes(16).toString('hex');
|
|
activeConnections.set(connectionId, {
|
|
agentId: handshake.agentId,
|
|
partnerId,
|
|
heartbeatAt: Date.now()
|
|
});
|
|
|
|
pendingHandshakes.delete(handshakeId);
|
|
|
|
return { connectionId, partnerPublicKey };
|
|
} catch (err) {
|
|
console.error(`[Agent握手] 解密失败: ${err.message}`);
|
|
throw ERRORS.DECRYPTION_FAILED;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 发送心跳
|
|
* @param {string} connectionId - 连接ID
|
|
*/
|
|
function sendHeartbeat(connectionId) {
|
|
const connection = activeConnections.get(connectionId);
|
|
if (!connection) {
|
|
throw ERRORS.INVALID_SESSION;
|
|
}
|
|
|
|
connection.heartbeatAt = Date.now();
|
|
}
|
|
|
|
/**
|
|
* 清理过期握手和连接
|
|
*/
|
|
function cleanupExpired() {
|
|
const now = Date.now();
|
|
|
|
for (const [handshakeId, handshake] of pendingHandshakes.entries()) {
|
|
if (now - handshake.createdAt > HANDSHAKE_EXPIRE_MS) {
|
|
pendingHandshakes.delete(handshakeId);
|
|
}
|
|
}
|
|
|
|
for (const [connectionId, connection] of activeConnections.entries()) {
|
|
if (now - connection.heartbeatAt > HEARTBEAT_INTERVAL * 3) {
|
|
activeConnections.delete(connectionId);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 每1分钟清理一次
|
|
setInterval(cleanupExpired, 60 * 1000);
|
|
|
|
module.exports = {
|
|
initHandshake,
|
|
completeHandshake,
|
|
sendHeartbeat,
|
|
ERRORS
|
|
}; |