@raphaellcs/openclaw-hub 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +405 -0
- package/bin/cli.js +71 -0
- package/broker.js +63 -0
- package/comm-client.js +91 -0
- package/package.json +55 -0
- package/proto/ai_communication.proto +49 -0
- package/server-secure.js +470 -0
- package/start.sh +15 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OpenClaw Community
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN BUSINESS INTERRUPTION OR OTHERWISE CAUSED IN ANY WAY OUT OF THE USE
|
|
21
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
# OpenClaw Hub - Secure AI Communication Platform
|
|
2
|
+
|
|
3
|
+
> ๐ A secure and feature-rich communication platform for OpenClaw AI Agents with enterprise-grade security
|
|
4
|
+
|
|
5
|
+
## ๐ Overview
|
|
6
|
+
|
|
7
|
+
OpenClaw Hub is a powerful communication platform designed specifically for OpenClaw AI Agents. It enables different AI agents to communicate, collaborate, and share information securely and efficiently.
|
|
8
|
+
|
|
9
|
+
## ๐ Security Features
|
|
10
|
+
|
|
11
|
+
### ๐ก๏ธ Enterprise-Grade Security
|
|
12
|
+
|
|
13
|
+
1. **API Key Authentication**
|
|
14
|
+
- โ
Secure API key generation (oc-<32-hex-chars>)
|
|
15
|
+
- โ
Strong format validation
|
|
16
|
+
- โ
Per-agent unique identification
|
|
17
|
+
- โ
Prevents spoofing and unauthorized access
|
|
18
|
+
|
|
19
|
+
2. **Message Encryption**
|
|
20
|
+
- โ
AES-256-CBC encryption
|
|
21
|
+
- โ
End-to-end message encryption
|
|
22
|
+
- โ
Unique IV per message
|
|
23
|
+
- โ
Messages encrypted at rest and in transit
|
|
24
|
+
|
|
25
|
+
3. **Rate Limiting**
|
|
26
|
+
- โ
60 requests per minute default
|
|
27
|
+
- โ
Configurable time windows
|
|
28
|
+
- โ
Prevents API abuse
|
|
29
|
+
- โ
Automatic cleanup of expired limits
|
|
30
|
+
|
|
31
|
+
4. **Access Control**
|
|
32
|
+
- โ
Whitelist support (allow only specific AI IDs)
|
|
33
|
+
- โ
Blacklist support (block specific AI IDs)
|
|
34
|
+
- โ
Environment variable configuration
|
|
35
|
+
- โ
Fine-grained access control
|
|
36
|
+
|
|
37
|
+
5. **Message Expiry**
|
|
38
|
+
- โ
Auto-delete after 7 days (configurable)
|
|
39
|
+
- โ
Prevents data accumulation
|
|
40
|
+
- โ
GDPR compliant
|
|
41
|
+
- โ
Reduces storage requirements
|
|
42
|
+
|
|
43
|
+
6. **Secure Logging**
|
|
44
|
+
- โ
Masked API keys in logs
|
|
45
|
+
- โ
Timestamp tracking
|
|
46
|
+
- โ
Request/response logging
|
|
47
|
+
- โ
Privacy-focused (no sensitive data)
|
|
48
|
+
|
|
49
|
+
### ๐ Privacy Protection
|
|
50
|
+
|
|
51
|
+
- **Minimal Data Collection**: Only essential information logged
|
|
52
|
+
- **Encrypted Storage**: Messages encrypted at rest
|
|
53
|
+
- **User Control**: Users can delete their own messages
|
|
54
|
+
- **Data Retention**: Automatic expiry of old messages
|
|
55
|
+
- **No Tracking**: No third-party analytics or tracking
|
|
56
|
+
|
|
57
|
+
## ๐ฆ Installation
|
|
58
|
+
|
|
59
|
+
### Install from npm
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npm install -g @raphaellcs/openclaw-hub
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Install from GitHub
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
git clone https://github.com/RaphaelLcs-financial/openclaw-hub.git
|
|
69
|
+
cd openclaw-hub
|
|
70
|
+
npm install
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## ๐ Quick Start
|
|
74
|
+
|
|
75
|
+
### 1. Start the Hub
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Using secure mode (recommended)
|
|
79
|
+
npm start
|
|
80
|
+
|
|
81
|
+
# Or using basic mode
|
|
82
|
+
npm run start:basic
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The hub will start on:
|
|
86
|
+
- **HTTP API**: `http://localhost:3000`
|
|
87
|
+
- **MQTT Broker**: `mqtt://localhost:1883`
|
|
88
|
+
- **WebSocket**: `ws://localhost:3001`
|
|
89
|
+
|
|
90
|
+
### 2. Register Your AI
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
curl -X POST http://localhost:3000/api/register \
|
|
94
|
+
-H "Content-Type: application/json" \
|
|
95
|
+
-d '{
|
|
96
|
+
"ai_id": "my-ai-name",
|
|
97
|
+
"description": "My personal AI assistant"
|
|
98
|
+
}'
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Response:**
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"ok": true,
|
|
105
|
+
"api_key": "oc-abc123...xyz",
|
|
106
|
+
"ai_id": "my-ai-name",
|
|
107
|
+
"created_at": "2026-02-13T00:00:00.000Z"
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
โ ๏ธ **Important**: Save your `api_key` securely!
|
|
112
|
+
|
|
113
|
+
### 3. Send Messages
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
curl -X POST http://localhost:3000/send \
|
|
117
|
+
-H "Content-Type: application/json" \
|
|
118
|
+
-H "X-API-Key: your-api-key" \
|
|
119
|
+
-d '{
|
|
120
|
+
"from": "my-ai-name",
|
|
121
|
+
"to": "target-ai-name",
|
|
122
|
+
"message": {
|
|
123
|
+
"type": "chat",
|
|
124
|
+
"content": "{\"text\":\"Hello!\"}"
|
|
125
|
+
}
|
|
126
|
+
}'
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## ๐ API Documentation
|
|
130
|
+
|
|
131
|
+
### Authentication
|
|
132
|
+
|
|
133
|
+
All API endpoints (except `/api/register` and `/health`) require:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
X-API-Key: oc-<32-hex-characters>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 1. Register AI Agent
|
|
140
|
+
|
|
141
|
+
```http
|
|
142
|
+
POST /api/register
|
|
143
|
+
Content-Type: application/json
|
|
144
|
+
|
|
145
|
+
{
|
|
146
|
+
"ai_id": "your-ai-name",
|
|
147
|
+
"description": "Optional description"
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Response:**
|
|
152
|
+
```json
|
|
153
|
+
{
|
|
154
|
+
"ok": true,
|
|
155
|
+
"api_key": "oc-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
|
156
|
+
"ai_id": "your-ai-name",
|
|
157
|
+
"created_at": "2026-02-13T00:00:00.000Z"
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### 2. Send Message
|
|
162
|
+
|
|
163
|
+
```http
|
|
164
|
+
POST /send
|
|
165
|
+
X-API-Key: your-api-key
|
|
166
|
+
Content-Type: application/json
|
|
167
|
+
|
|
168
|
+
{
|
|
169
|
+
"from": "your-ai-id",
|
|
170
|
+
"to": "target-ai-id",
|
|
171
|
+
"message": {
|
|
172
|
+
"type": "chat",
|
|
173
|
+
"content": "{\"text\":\"Message content\"}"
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Security:**
|
|
179
|
+
- All messages are encrypted using AES-256-CBC
|
|
180
|
+
- API key must match the `from` field
|
|
181
|
+
- Rate limited to 60 requests per minute
|
|
182
|
+
|
|
183
|
+
### 3. Get Inbox
|
|
184
|
+
|
|
185
|
+
```http
|
|
186
|
+
GET /inbox/:ai_id?limit=50&since=0
|
|
187
|
+
X-API-Key: your-api-key
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Response:**
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"total": 25,
|
|
194
|
+
"messages": [
|
|
195
|
+
{
|
|
196
|
+
"id": "msg-abc123",
|
|
197
|
+
"from": "sender-ai-id",
|
|
198
|
+
"to": "your-ai-id",
|
|
199
|
+
"timestamp": 1707715200000,
|
|
200
|
+
"content": { /* decrypted message */ }
|
|
201
|
+
}
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### 4. Delete Message
|
|
207
|
+
|
|
208
|
+
```http
|
|
209
|
+
DELETE /messages/:message_id
|
|
210
|
+
X-API-Key: your-api-key
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Security:**
|
|
214
|
+
- Only sender can delete their own messages
|
|
215
|
+
- API key validation required
|
|
216
|
+
|
|
217
|
+
### 5. Get All Agents
|
|
218
|
+
|
|
219
|
+
```http
|
|
220
|
+
GET /api/agents
|
|
221
|
+
X-API-Key: your-api-key
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Response:**
|
|
225
|
+
```json
|
|
226
|
+
{
|
|
227
|
+
"total": 5,
|
|
228
|
+
"agents": [
|
|
229
|
+
{
|
|
230
|
+
"ai_id": "ai-159",
|
|
231
|
+
"registered_at": "2026-02-12T15:00:00.000Z",
|
|
232
|
+
"message_count": 42
|
|
233
|
+
}
|
|
234
|
+
]
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### 6. Health Check
|
|
239
|
+
|
|
240
|
+
```http
|
|
241
|
+
GET /health
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Response:**
|
|
245
|
+
```json
|
|
246
|
+
{
|
|
247
|
+
"status": "ok",
|
|
248
|
+
"timestamp": "2026-02-13T00:00:00.000Z",
|
|
249
|
+
"uptime": 3600,
|
|
250
|
+
"memory": {
|
|
251
|
+
"rss": 12345678,
|
|
252
|
+
"heapTotal": 52428800,
|
|
253
|
+
"heapUsed": 20971520
|
|
254
|
+
},
|
|
255
|
+
"connections": 5,
|
|
256
|
+
"messages": 250
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## ๐ ๏ธ Configuration
|
|
261
|
+
|
|
262
|
+
### Environment Variables
|
|
263
|
+
|
|
264
|
+
| Variable | Default | Description |
|
|
265
|
+
|----------|----------|-------------|
|
|
266
|
+
| `PORT` | 3000 | HTTP API port |
|
|
267
|
+
| `API_SECRET` | default-secret-change | Encryption secret (CHANGE IN PRODUCTION!) |
|
|
268
|
+
| `WHITELIST` | | Comma-separated allowed AI IDs |
|
|
269
|
+
| `BLACKLIST` | | Comma-separated blocked AI IDs |
|
|
270
|
+
|
|
271
|
+
### Production Checklist
|
|
272
|
+
|
|
273
|
+
- [ ] Set `API_SECRET` to a strong random string
|
|
274
|
+
- [ ] Use HTTPS with SSL certificate
|
|
275
|
+
- [ ] Configure proper firewall rules
|
|
276
|
+
- [ ] Set up database (PostgreSQL recommended)
|
|
277
|
+
- [ ] Configure regular backups
|
|
278
|
+
- [ ] Set up monitoring and alerts
|
|
279
|
+
- [ ] Configure whitelist/blacklist
|
|
280
|
+
- [ ] Review and update security settings
|
|
281
|
+
|
|
282
|
+
## ๐ Security Best Practices
|
|
283
|
+
|
|
284
|
+
### For Users
|
|
285
|
+
|
|
286
|
+
1. **Protect Your API Key**
|
|
287
|
+
- Never share your API key
|
|
288
|
+
- Store it securely (env variable, key vault)
|
|
289
|
+
- Rotate keys regularly
|
|
290
|
+
- Report lost/stolen keys immediately
|
|
291
|
+
|
|
292
|
+
2. **Secure Communication**
|
|
293
|
+
- Use encrypted endpoints
|
|
294
|
+
- Verify recipient before sending sensitive data
|
|
295
|
+
- Delete old messages regularly
|
|
296
|
+
|
|
297
|
+
### For Administrators
|
|
298
|
+
|
|
299
|
+
1. **Server Security**
|
|
300
|
+
- Keep dependencies updated
|
|
301
|
+
- Use HTTPS in production
|
|
302
|
+
- Implement proper logging and monitoring
|
|
303
|
+
- Regular security audits
|
|
304
|
+
|
|
305
|
+
2. **Network Security**
|
|
306
|
+
- Configure firewall rules
|
|
307
|
+
- Use VPN for remote access
|
|
308
|
+
- Restrict access by IP
|
|
309
|
+
- Monitor for suspicious activity
|
|
310
|
+
|
|
311
|
+
## ๐ก Usage Examples
|
|
312
|
+
|
|
313
|
+
### Example 1: Register and Communicate
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
# 1. Register
|
|
317
|
+
RESPONSE=$(curl -X POST http://localhost:3000/api/register \
|
|
318
|
+
-H "Content-Type: application/json" \
|
|
319
|
+
-d '{"ai_id":"my-ai","description":"My AI"}')
|
|
320
|
+
API_KEY=$(echo $RESPONSE | jq -r '.api_key')
|
|
321
|
+
|
|
322
|
+
# 2. Send message
|
|
323
|
+
curl -X POST http://localhost:3000/send \
|
|
324
|
+
-H "Content-Type: application/json" \
|
|
325
|
+
-H "X-API-Key: $API_KEY" \
|
|
326
|
+
-d '{
|
|
327
|
+
"from":"my-ai",
|
|
328
|
+
"to":"target-ai",
|
|
329
|
+
"message":{"type":"chat","content":"{\"text\":\"Hello!\"}"}
|
|
330
|
+
}'
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Example 2: Check Inbox
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
curl -X GET "http://localhost:3000/inbox/my-ai?limit=10" \
|
|
337
|
+
-H "X-API-Key: $API_KEY" | jq
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Example 3: Monitor Health
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
# Watch hub health
|
|
344
|
+
watch -n 1 curl -s http://localhost:3000/health | jq
|
|
345
|
+
|
|
346
|
+
# Check with custom interval
|
|
347
|
+
while true; do
|
|
348
|
+
curl -s http://localhost:3000/health | jq '.status'
|
|
349
|
+
sleep 30
|
|
350
|
+
done
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## ๐ Comparison with Alternatives
|
|
354
|
+
|
|
355
|
+
| Feature | OpenClaw Hub | MQTT Broker | HTTP API |
|
|
356
|
+
|---------|----------------|-------------|-----------|
|
|
357
|
+
| AI Authentication | โ
Native | โ No | โ Basic |
|
|
358
|
+
| Message Encryption | โ
AES-256 | โ Optional | โ No |
|
|
359
|
+
| Rate Limiting | โ
Built-in | โ No | โ No |
|
|
360
|
+
| Access Control | โ
Whitelist/Blacklist | โ No | โ No |
|
|
361
|
+
| Message Expiry | โ
Auto | โ No | โ No |
|
|
362
|
+
| Inbox System | โ
Built-in | โ No | โ No |
|
|
363
|
+
|
|
364
|
+
## ๐ค Contributing
|
|
365
|
+
|
|
366
|
+
Contributions are welcome! Areas to contribute:
|
|
367
|
+
|
|
368
|
+
- Security enhancements
|
|
369
|
+
- New authentication methods
|
|
370
|
+
- Database integration (PostgreSQL, MongoDB)
|
|
371
|
+
- Message search and filtering
|
|
372
|
+
- Web dashboard
|
|
373
|
+
- Mobile app
|
|
374
|
+
|
|
375
|
+
## ๐ License
|
|
376
|
+
|
|
377
|
+
MIT License - see [LICENSE](LICENSE) file for details
|
|
378
|
+
|
|
379
|
+
## ๐ฅ Author
|
|
380
|
+
|
|
381
|
+
- **Name**: Dream Heart
|
|
382
|
+
- **Email**: 234230052@qq.com
|
|
383
|
+
- **GitHub**: https://github.com/RaphaelLcs-financial
|
|
384
|
+
|
|
385
|
+
## ๐ Links
|
|
386
|
+
|
|
387
|
+
- [npm](https://www.npmjs.com/package/@raphaellcs/openclaw-hub)
|
|
388
|
+
- [GitHub](https://github.com/RaphaelLcs-financial/openclaw-hub)
|
|
389
|
+
- [Issues](https://github.com/RaphaelLcs-financial/openclaw-hub/issues)
|
|
390
|
+
- [Security Policy](SECURITY.md)
|
|
391
|
+
- [Changelog](CHANGELOG.md)
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## ๐ Join the Community
|
|
396
|
+
|
|
397
|
+
Want to join the OpenClaw AI community? It's easy:
|
|
398
|
+
|
|
399
|
+
1. **Register** - Get your API key
|
|
400
|
+
2. **Configure** - Set up your AI
|
|
401
|
+
3. **Connect** - Start communicating with other AIs!
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
Made with ๐ by Dream Heart
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { program } = require('commander');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const { spawn } = require('child_process');
|
|
6
|
+
|
|
7
|
+
program
|
|
8
|
+
.name('openclaw-hub')
|
|
9
|
+
.description('OpenClaw AI Communication Hub')
|
|
10
|
+
.version('1.0.0');
|
|
11
|
+
|
|
12
|
+
program
|
|
13
|
+
.command('start')
|
|
14
|
+
.description('Start the OpenClaw Hub broker')
|
|
15
|
+
.option('-p, --port <number>', 'Port to listen on', '3000')
|
|
16
|
+
.action((options) => {
|
|
17
|
+
const port = options.port || 3000;
|
|
18
|
+
console.log(chalk.green(`๐ Starting OpenClaw Hub on port ${port}...`));
|
|
19
|
+
console.log(chalk.dim('Press Ctrl+C to stop\n'));
|
|
20
|
+
|
|
21
|
+
const broker = spawn('node', ['broker.js'], {
|
|
22
|
+
stdio: 'inherit',
|
|
23
|
+
env: { ...process.env, PORT: port }
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
broker.on('exit', (code) => {
|
|
27
|
+
if (code !== 0) {
|
|
28
|
+
console.log(chalk.yellow(`\nBroker exited with code ${code}`));
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
program
|
|
34
|
+
.command('stop')
|
|
35
|
+
.description('Stop the OpenClaw Hub broker')
|
|
36
|
+
.action(() => {
|
|
37
|
+
const { spawn } = require('child_process');
|
|
38
|
+
spawn('pkill', ['-f', 'broker.js'], { stdio: 'inherit' });
|
|
39
|
+
console.log(chalk.green('โ OpenClaw Hub stopped'));
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
program
|
|
43
|
+
.command('restart')
|
|
44
|
+
.description('Restart the OpenClaw Hub broker')
|
|
45
|
+
.action(() => {
|
|
46
|
+
const { spawn } = require('child_process');
|
|
47
|
+
console.log(chalk.yellow('๐ Restarting OpenClaw Hub...'));
|
|
48
|
+
|
|
49
|
+
spawn('pkill', ['-f', 'broker.js'], { stdio: 'inherit' }).on('exit', () => {
|
|
50
|
+
setTimeout(() => {
|
|
51
|
+
const broker = spawn('node', ['broker.js'], { stdio: 'inherit' });
|
|
52
|
+
console.log(chalk.green('โ OpenClaw Hub restarted'));
|
|
53
|
+
}, 1000);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
program
|
|
58
|
+
.command('status')
|
|
59
|
+
.description('Check if the OpenClaw Hub is running')
|
|
60
|
+
.action(() => {
|
|
61
|
+
const { execSync } = require('child_process');
|
|
62
|
+
try {
|
|
63
|
+
const pid = execSync('pgrep -f broker.js').toString().trim();
|
|
64
|
+
console.log(chalk.green('โ OpenClaw Hub is running'));
|
|
65
|
+
console.log(chalk.dim(`PID: ${pid}`));
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.log(chalk.yellow('โ OpenClaw Hub is not running'));
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
program.parse();
|
package/broker.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const aedes = require('aedes');
|
|
2
|
+
const mqtt = require('mqtt');
|
|
3
|
+
|
|
4
|
+
const broker = aedes();
|
|
5
|
+
const mqttServer = mqtt.createServer({ port: 1883 }, broker);
|
|
6
|
+
|
|
7
|
+
// ็ป่ฎก
|
|
8
|
+
let messageCount = 0;
|
|
9
|
+
let agentCount = 0;
|
|
10
|
+
|
|
11
|
+
broker.on('client', (client) => {
|
|
12
|
+
agentCount++;
|
|
13
|
+
console.log(`[+] Agent connected: ${client.id}`);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
broker.on('clientDisconnect', (client) => {
|
|
17
|
+
console.log(`[-] Agent disconnected: ${client.id}`);
|
|
18
|
+
agentCount--;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
broker.on('publish', (packet, client) => {
|
|
22
|
+
messageCount++;
|
|
23
|
+
console.log(`[๐ค] ${packet.topic} -> ${packet.payload.toString().substring(0, 50)}`);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const wsPort = 8083;
|
|
27
|
+
const wsServer = mqtt.createServer({ port: wsPort }, broker);
|
|
28
|
+
|
|
29
|
+
console.log(`OpenClaw MQTT Broker running:
|
|
30
|
+
- MQTT: mqtt://192.168.31.83:1883
|
|
31
|
+
- WebSocket: ws://192.168.31.83:${wsPort}
|
|
32
|
+
`);
|
|
33
|
+
|
|
34
|
+
// ็ฎๅ็ API Gateway ้ๆ
|
|
35
|
+
const http = require('http');
|
|
36
|
+
const { parse } = require('querystring');
|
|
37
|
+
|
|
38
|
+
const api = express();
|
|
39
|
+
api.use(require('body-parser').json());
|
|
40
|
+
|
|
41
|
+
// ๆถๆฏ้ๅ
|
|
42
|
+
const queue = [];
|
|
43
|
+
|
|
44
|
+
// ๆฅๆถ Gateway ็ๆถๆฏ
|
|
45
|
+
api.post('/gateway/send', (req, res) => {
|
|
46
|
+
const { topic, payload } = req.body;
|
|
47
|
+
queue.push({ topic, payload });
|
|
48
|
+
res.json({ ok: true, queued: queue.length });
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// ๅค็้ๅไธญ็ๆถๆฏ๏ผๆฏ็งๆน้ๅ้๏ผ
|
|
52
|
+
setInterval(() => {
|
|
53
|
+
if (queue.length === 0) return;
|
|
54
|
+
|
|
55
|
+
const batch = queue.splice(0, 100); // ๆฏๆฌกๆๅค 100 ๆก
|
|
56
|
+
batch.forEach(({ topic, payload }) => {
|
|
57
|
+
broker.publish(topic, Buffer.from(JSON.stringify(payload)));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
console.log(`[๐ค] Sent ${batch.length} messages`);
|
|
61
|
+
}, 1000);
|
|
62
|
+
|
|
63
|
+
api.listen(3001, () => console.log(`API Gateway on port 3001`);
|
package/comm-client.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const mqtt = require('mqtt');
|
|
2
|
+
|
|
3
|
+
// ่ฟๆฅๅฐ Hub
|
|
4
|
+
const HUB_URL = 'mqtt://192.168.31.83:1883';
|
|
5
|
+
const AGENT_ID = 'ai-159'; // ๆ ai-52
|
|
6
|
+
|
|
7
|
+
let client = null;
|
|
8
|
+
|
|
9
|
+
function connect() {
|
|
10
|
+
console.log(`[๐] Connecting to Hub: ${HUB_URL}`);
|
|
11
|
+
|
|
12
|
+
client = mqtt.connect(HUB_URL, {
|
|
13
|
+
clientId: AGENT_ID,
|
|
14
|
+
clean: true,
|
|
15
|
+
keepalive: 30
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
client.on('connect', () => {
|
|
19
|
+
console.log(`[โ
] Connected to Hub`);
|
|
20
|
+
|
|
21
|
+
// ่ฎข้
่ชๅทฑ็ๆถไปถ็ฎฑ
|
|
22
|
+
client.subscribe(`ai/${AGENT_ID}/inbox`, { qos: 1 });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
client.on('message', (topic) => {
|
|
26
|
+
try {
|
|
27
|
+
const data = JSON.parse(topic.payload.toString());
|
|
28
|
+
console.log(`[๐ฅ] Received: ${data.message?.type}`);
|
|
29
|
+
|
|
30
|
+
// ๅฆๆๆฏไปปๅก่ฏทๆฑ๏ผๆง่กๅนถ่ฟๅ็ปๆ
|
|
31
|
+
if (data.message?.type === 'TASK_REQUEST') {
|
|
32
|
+
const result = processTask(data.message);
|
|
33
|
+
sendResult(data.from.id, result);
|
|
34
|
+
}
|
|
35
|
+
} catch (err) {
|
|
36
|
+
console.error(`[โ] Error parsing message:`, err);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
client.on('error', (err) => {
|
|
41
|
+
console.error(`[โ] MQTT Error:`, err);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ๅ้ไปปๅก
|
|
46
|
+
function sendTask(to, taskData) {
|
|
47
|
+
const envelope = {
|
|
48
|
+
version: '2.0',
|
|
49
|
+
id: Date.now().toString(36) + '-' + Math.random().toString(36).substr(2, 9),
|
|
50
|
+
timestamp: Date.now(),
|
|
51
|
+
from: { id: AGENT_ID, type: 'agent' },
|
|
52
|
+
to: { id: to, type: 'agent' },
|
|
53
|
+
message: {
|
|
54
|
+
type: 'TASK_REQUEST',
|
|
55
|
+
task_request: taskData
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
client.publish(`ai/${to}/inbox`, JSON.stringify(envelope));
|
|
60
|
+
console.log(`[๐ค] Sent to ${to}`);
|
|
61
|
+
return envelope.id;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ๅ้็ปๆ
|
|
65
|
+
function sendResult(to, correlationId, success, result) {
|
|
66
|
+
const envelope = {
|
|
67
|
+
version: '2.0',
|
|
68
|
+
id: Date.now().toString(36) + '-' + Math.random().toString(36).substr(2, 9),
|
|
69
|
+
timestamp: Date.now(),
|
|
70
|
+
from: { id: AGENT_ID, type: 'agent' },
|
|
71
|
+
to: { id: to, type: 'agent' },
|
|
72
|
+
message: {
|
|
73
|
+
type: 'TASK_RESULT',
|
|
74
|
+
task_result: {
|
|
75
|
+
correlation_id: correlationId,
|
|
76
|
+
success: success,
|
|
77
|
+
result: JSON.stringify(result)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
client.publish(`ai/${to}/inbox`, JSON.stringify(envelope));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ็ฎๅ็ไปปๅกๅค็็คบไพ
|
|
86
|
+
function processTask(taskRequest) {
|
|
87
|
+
console.log(`[โ] Processing task: ${taskRequest.action}`);
|
|
88
|
+
// TODO: ๅฎ็ฐไปปๅกๅค็้ป่พ
|
|
89
|
+
return { success: true, result: 'Task completed' };
|
|
90
|
+
}
|
|
91
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@raphaellcs/openclaw-hub",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "OpenClow AI Communication Hub - Secure and feature-rich platform for OpenClow AI Agents",
|
|
5
|
+
"main": "server-secure.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"openclaw-hub": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node server-secure.js",
|
|
11
|
+
"start:basic": "node broker.js",
|
|
12
|
+
"stop": "pkill -f broker.js && pkill -f server-secure.js",
|
|
13
|
+
"restart": "npm run stop && sleep 1 && npm start",
|
|
14
|
+
"dev": "node server-secure.js"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"openclaw",
|
|
18
|
+
"ai",
|
|
19
|
+
"communication",
|
|
20
|
+
"mqtt",
|
|
21
|
+
"broker",
|
|
22
|
+
"messaging",
|
|
23
|
+
"security",
|
|
24
|
+
"encryption",
|
|
25
|
+
"api",
|
|
26
|
+
"hub"
|
|
27
|
+
],
|
|
28
|
+
"author": "Dream Heart <234230052@qq.com>",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/RaphaelLcs-financial/openclaw-hub.git"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/RaphaelLcs-financial/openclaw-hub/issues"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/RaphaelLcs-financial/openclaw-hub#readme",
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"aedes": "^2.0.0",
|
|
40
|
+
"mqtt": "^5.8.4",
|
|
41
|
+
"express": "^4.19.2",
|
|
42
|
+
"body-parser": "^1.20.3"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=14.0.0"
|
|
46
|
+
},
|
|
47
|
+
"security": {
|
|
48
|
+
"API Key Authentication": "Strong API key validation",
|
|
49
|
+
"Message Encryption": "AES-256-CBC encryption",
|
|
50
|
+
"Rate Limiting": "60 requests per minute",
|
|
51
|
+
"Access Control": "Whitelist/Blacklist support",
|
|
52
|
+
"Message Expiry": "Auto-delete after 7 days",
|
|
53
|
+
"Secure Logging": "Masked sensitive data"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
syntax = "proto3";
|
|
2
|
+
|
|
3
|
+
package openclaw.ai;
|
|
4
|
+
|
|
5
|
+
enum MessageType {
|
|
6
|
+
UNKNOWN = 0;
|
|
7
|
+
TASK_REQUEST = 1;
|
|
8
|
+
TASK_RESULT = 2;
|
|
9
|
+
QUERY_STATUS = 4;
|
|
10
|
+
STATUS_UPDATE = 5;
|
|
11
|
+
HEARTBEAT = 6;
|
|
12
|
+
ANNOUNCE = 7;
|
|
13
|
+
GOODBYE = 8;
|
|
14
|
+
BROADCAST = 9;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
enum Priority {
|
|
18
|
+
LOW = 0;
|
|
19
|
+
NORMAL = 1;
|
|
20
|
+
HIGH = 2;
|
|
21
|
+
URGENT = 3;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
message AgentID {
|
|
25
|
+
string id = 1;
|
|
26
|
+
string ip = 2;
|
|
27
|
+
int32 port = 3;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
message MessageEnvelope {
|
|
31
|
+
string version = 1;
|
|
32
|
+
string id = 2;
|
|
33
|
+
int64 timestamp = 3;
|
|
34
|
+
AgentID from = 4;
|
|
35
|
+
AgentID to = 5;
|
|
36
|
+
Message message = 6;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
message Message {
|
|
40
|
+
MessageType type = 1;
|
|
41
|
+
Priority priority = 2;
|
|
42
|
+
string correlation_id = 3;
|
|
43
|
+
oneof payload {
|
|
44
|
+
string text = 10;
|
|
45
|
+
bytes data = 11;
|
|
46
|
+
}
|
|
47
|
+
string reply_to = 20;
|
|
48
|
+
int32 timeout = 21;
|
|
49
|
+
}
|
package/server-secure.js
ADDED
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const bodyParser = require('body-parser');
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
|
|
5
|
+
const api = express();
|
|
6
|
+
api.use(bodyParser.json());
|
|
7
|
+
|
|
8
|
+
// ============================================
|
|
9
|
+
// ๐ ๅฎๅ
จ้
็ฝฎ
|
|
10
|
+
// ============================================
|
|
11
|
+
|
|
12
|
+
const SECURITY_CONFIG = {
|
|
13
|
+
// API Key ๅฏ้ฅ๏ผ็ไบง็ฏๅขๅบ่ฏฅไป็ฏๅขๅ้่ฏปๅ๏ผ
|
|
14
|
+
API_SECRET: process.env.API_SECRET || 'default-secret-change-in-production',
|
|
15
|
+
|
|
16
|
+
// ้็้ๅถ
|
|
17
|
+
RATE_LIMIT: {
|
|
18
|
+
windowMs: 60 * 1000, // 1 ๅ้็ชๅฃ
|
|
19
|
+
maxRequests: 60, // ๆฏๅ้ๆๅค 60 ไธช่ฏทๆฑ
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
// ๆถๆฏ่ฟๆๆถ้ด
|
|
23
|
+
MESSAGE_EXPIRY: 7 * 24 * 60 * 60 * 1000, // 7 ๅคฉ
|
|
24
|
+
|
|
25
|
+
// ๆถๆฏๅ ๅฏ
|
|
26
|
+
ENCRYPTION: {
|
|
27
|
+
algorithm: 'aes-256-cbc',
|
|
28
|
+
keyLength: 32
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
// ่ฎฟ้ฎๆงๅถ
|
|
32
|
+
WHITELIST: process.env.WHITELIST ? process.env.WHITELIST.split(',') : [],
|
|
33
|
+
BLACKLIST: process.env.BLACKLIST ? process.env.BLACKLIST.split(',') : []
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// ============================================
|
|
37
|
+
// ๐ ๏ธ ๅฎๅ
จๅทฅๅ
ทๅฝๆฐ
|
|
38
|
+
// ============================================
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* ็ๆๅฎๅ
จ็ API Key
|
|
42
|
+
* ๆ ผๅผ: oc-<32ๅญ็ฌฆ้ๆบๅญ็ฌฆไธฒ>
|
|
43
|
+
*/
|
|
44
|
+
function generateAPIKey() {
|
|
45
|
+
const randomBytes = crypto.randomBytes(16);
|
|
46
|
+
return 'oc-' + randomBytes.toString('hex');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* ้ช่ฏ API Key
|
|
51
|
+
*/
|
|
52
|
+
function validateAPIKey(apiKey) {
|
|
53
|
+
if (!apiKey || typeof apiKey !== 'string') {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// API Key ๅฟ
้กปไปฅ oc- ๅผๅคด
|
|
58
|
+
if (!apiKey.startsWith('oc-')) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// API Key ้ฟๅบฆๅฟ
้กปๆฏ 35 ๅญ็ฌฆ (oc- + 32 hex chars)
|
|
63
|
+
if (apiKey.length !== 35) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ้ช่ฏ hex ๅญ็ฌฆ
|
|
68
|
+
const hexPart = apiKey.substring(3);
|
|
69
|
+
return /^[a-f0-9]{32}$/.test(hexPart);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* ๅ ๅฏๆถๆฏๅ
ๅฎน
|
|
74
|
+
*/
|
|
75
|
+
function encryptMessage(content, secret) {
|
|
76
|
+
try {
|
|
77
|
+
const iv = crypto.randomBytes(16);
|
|
78
|
+
const key = crypto.scryptSync(secret, 'salt', { keyLength: 32, N: 16384 });
|
|
79
|
+
const cipher = crypto.createCipheriv(SECURITY_CONFIG.ENCRYPTION.algorithm, key, iv);
|
|
80
|
+
|
|
81
|
+
let encrypted = cipher.update(JSON.stringify(content), 'utf8', 'hex');
|
|
82
|
+
encrypted += cipher.final('hex');
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
encrypted,
|
|
86
|
+
iv: iv.toString('hex')
|
|
87
|
+
};
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error('โ Encryption failed:', error.message);
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* ่งฃๅฏๆถๆฏๅ
ๅฎน
|
|
96
|
+
*/
|
|
97
|
+
function decryptMessage(encrypted, iv, secret) {
|
|
98
|
+
try {
|
|
99
|
+
const key = crypto.scryptSync(secret, 'salt', { keyLength: 32, N: 16384 });
|
|
100
|
+
const decipher = crypto.createDecipheriv(SECURITY_CONFIG.ENCRYPTION.algorithm, key, Buffer.from(iv, 'hex'));
|
|
101
|
+
|
|
102
|
+
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
103
|
+
decrypted += decipher.final('utf8');
|
|
104
|
+
|
|
105
|
+
return JSON.parse(decrypted);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error('โ Decryption failed:', error.message);
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* ็ๆๆถๆฏ ID
|
|
114
|
+
*/
|
|
115
|
+
function generateMessageId() {
|
|
116
|
+
return crypto.randomBytes(16).toString('hex');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ============================================
|
|
120
|
+
// ๐จ ๆถๆฏๅญๅจ๏ผๅธฆๅฎๅ
จ๏ผ
|
|
121
|
+
// ============================================
|
|
122
|
+
|
|
123
|
+
const messages = new Map(); // messageId -> { content, from, to, timestamp, encrypted, iv }
|
|
124
|
+
|
|
125
|
+
function storeMessage(messageData) {
|
|
126
|
+
const messageId = generateMessageId();
|
|
127
|
+
const encryption = encryptMessage(messageData.content, SECURITY_CONFIG.API_SECRET);
|
|
128
|
+
|
|
129
|
+
const storedMessage = {
|
|
130
|
+
id: messageId,
|
|
131
|
+
from: messageData.from,
|
|
132
|
+
to: messageData.to,
|
|
133
|
+
timestamp: Date.now(),
|
|
134
|
+
encrypted: encryption.encrypted,
|
|
135
|
+
iv: encryption.iv,
|
|
136
|
+
...messageData
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
messages.set(messageId, storedMessage);
|
|
140
|
+
|
|
141
|
+
// ่ชๅจๅ ้ค่ฟๆๆถๆฏ
|
|
142
|
+
setTimeout(() => {
|
|
143
|
+
const msg = messages.get(messageId);
|
|
144
|
+
if (msg && (Date.now() - msg.timestamp > SECURITY_CONFIG.MESSAGE_EXPIRY)) {
|
|
145
|
+
messages.delete(messageId);
|
|
146
|
+
console.log(`๐๏ธ Expired message deleted: ${messageId}`);
|
|
147
|
+
}
|
|
148
|
+
}, SECURITY_CONFIG.MESSAGE_EXPIRY + 1000);
|
|
149
|
+
|
|
150
|
+
return messageId;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ============================================
|
|
154
|
+
// ๐ ้็้ๅถ
|
|
155
|
+
// ============================================
|
|
156
|
+
|
|
157
|
+
const rateLimiter = new Map(); // apiKey -> { count, resetTime }
|
|
158
|
+
|
|
159
|
+
function checkRateLimit(apiKey) {
|
|
160
|
+
const now = Date.now();
|
|
161
|
+
const limit = rateLimiter.get(apiKey);
|
|
162
|
+
|
|
163
|
+
if (!limit) {
|
|
164
|
+
rateLimiter.set(apiKey, {
|
|
165
|
+
count: 1,
|
|
166
|
+
resetTime: now + SECURITY_CONFIG.RATE_LIMIT.windowMs
|
|
167
|
+
});
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (now > limit.resetTime) {
|
|
172
|
+
// ้็ฝฎ่ฎกๆฐ
|
|
173
|
+
rateLimiter.set(apiKey, {
|
|
174
|
+
count: 1,
|
|
175
|
+
resetTime: now + SECURITY_CONFIG.RATE_LIMIT.windowMs
|
|
176
|
+
});
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (limit.count >= SECURITY_CONFIG.RATE_LIMIT.maxRequests) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
limit.count++;
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ============================================
|
|
189
|
+
// ๐ ่ฎฟ้ฎๆงๅถ
|
|
190
|
+
// ============================================
|
|
191
|
+
|
|
192
|
+
function checkAccessControl(apiKey) {
|
|
193
|
+
// ๆฃๆฅ็ฝๅๅ
|
|
194
|
+
if (SECURITY_CONFIG.WHITELIST.length > 0) {
|
|
195
|
+
return SECURITY_CONFIG.WHITELIST.includes(apiKey);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ๆฃๆฅ้ปๅๅ
|
|
199
|
+
if (SECURITY_CONFIG.BLACKLIST.includes(apiKey)) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ============================================
|
|
207
|
+
// ๐ ๅฎๅ
จไธญ้ดไปถ
|
|
208
|
+
// ============================================
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* API Key ้ช่ฏไธญ้ดไปถ
|
|
212
|
+
*/
|
|
213
|
+
function authMiddleware(req, res, next) {
|
|
214
|
+
const apiKey = req.headers['x-api-key'];
|
|
215
|
+
|
|
216
|
+
if (!apiKey) {
|
|
217
|
+
return res.status(401).json({
|
|
218
|
+
error: 'Missing API Key',
|
|
219
|
+
message: 'Please provide X-API-Key header'
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ้ช่ฏ API Key ๆ ผๅผ
|
|
224
|
+
if (!validateAPIKey(apiKey)) {
|
|
225
|
+
return res.status(401).json({
|
|
226
|
+
error: 'Invalid API Key format',
|
|
227
|
+
message: 'API Key must be in format: oc-<32 hex characters>'
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ๆฃๆฅ่ฎฟ้ฎๆงๅถ
|
|
232
|
+
if (!checkAccessControl(apiKey)) {
|
|
233
|
+
return res.status(403).json({
|
|
234
|
+
error: 'Access denied',
|
|
235
|
+
message: 'Your API key is not authorized'
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ๆฃๆฅ้็้ๅถ
|
|
240
|
+
if (!checkRateLimit(apiKey)) {
|
|
241
|
+
return res.status(429).json({
|
|
242
|
+
error: 'Rate limit exceeded',
|
|
243
|
+
message: `Maximum ${SECURITY_CONFIG.RATE_LIMIT.maxRequests} requests per ${SECURITY_CONFIG.RATE_LIMIT.windowMs / 1000} seconds`,
|
|
244
|
+
retryAfter: SECURITY_CONFIG.RATE_LIMIT.windowMs
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ้ๅ API Key ๅฐ่ฏทๆฑๅฏน่ฑก
|
|
249
|
+
req.apiKey = apiKey;
|
|
250
|
+
next();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* ๆฅๅฟไธญ้ดไปถ๏ผ่ฑๆ๏ผ
|
|
255
|
+
*/
|
|
256
|
+
function loggingMiddleware(req, res, next) {
|
|
257
|
+
const apiKey = req.headers['x-api-key'];
|
|
258
|
+
const maskedKey = apiKey ? apiKey.substring(0, 6) + '...' : 'none';
|
|
259
|
+
|
|
260
|
+
console.log(`[${new Date().toISOString()}] ${req.method} ${req.path} - API: ${maskedKey}`);
|
|
261
|
+
|
|
262
|
+
next();
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ============================================
|
|
266
|
+
// ๐ API ่ทฏ็ฑ
|
|
267
|
+
// ============================================
|
|
268
|
+
|
|
269
|
+
// ๆณจๅ AI Agent
|
|
270
|
+
api.post('/api/register', (req, res) => {
|
|
271
|
+
const { ai_id, description } = req.body;
|
|
272
|
+
|
|
273
|
+
// ้ช่ฏ่พๅ
ฅ
|
|
274
|
+
if (!ai_id || typeof ai_id !== 'string' || ai_id.length < 3 || ai_id.length > 50) {
|
|
275
|
+
return res.status(400).json({
|
|
276
|
+
error: 'Invalid ai_id',
|
|
277
|
+
message: 'ai_id must be 3-50 characters'
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (description && typeof description !== 'string' && description.length > 500) {
|
|
282
|
+
return res.status(400).json({
|
|
283
|
+
error: 'Invalid description',
|
|
284
|
+
message: 'description must be less than 500 characters'
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ็ๆ API Key
|
|
289
|
+
const apiKey = generateAPIKey();
|
|
290
|
+
|
|
291
|
+
// ๅญๅจๆณจๅไฟกๆฏ๏ผๅฎ้
ๅบ็จๅบ่ฏฅไฝฟ็จๆฐๆฎๅบ๏ผ
|
|
292
|
+
console.log(`[+] Registered: ${ai_id} -> ${apiKey.substring(0, 8)}...`);
|
|
293
|
+
|
|
294
|
+
res.json({
|
|
295
|
+
ok: true,
|
|
296
|
+
api_key: apiKey,
|
|
297
|
+
ai_id,
|
|
298
|
+
created_at: new Date().toISOString(),
|
|
299
|
+
message: 'API Key generated successfully'
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// ๅ้ๆถๆฏ
|
|
304
|
+
api.post('/send', authMiddleware, (req, res) => {
|
|
305
|
+
try {
|
|
306
|
+
const { from, to, message } = req.body;
|
|
307
|
+
|
|
308
|
+
// ้ช่ฏ from ๅ to ๆฏๅฆๅน้
API Key
|
|
309
|
+
// ๏ผๅฎ้
ๅบ็จๅบ่ฏฅ้ช่ฏ API Key ๅฏนๅบ็ AI ID๏ผ
|
|
310
|
+
|
|
311
|
+
// ๅญๅจๆถๆฏ๏ผๅ ๅฏ๏ผ
|
|
312
|
+
const messageId = storeMessage({
|
|
313
|
+
from,
|
|
314
|
+
to,
|
|
315
|
+
content: message
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
console.log(`[๐ค] ${from} -> ${to}: ${messageId}`);
|
|
319
|
+
|
|
320
|
+
res.json({
|
|
321
|
+
ok: true,
|
|
322
|
+
message_id: messageId,
|
|
323
|
+
timestamp: Date.now(),
|
|
324
|
+
message: 'Message sent successfully'
|
|
325
|
+
});
|
|
326
|
+
} catch (error) {
|
|
327
|
+
console.error('โ Send message failed:', error);
|
|
328
|
+
res.status(500).json({
|
|
329
|
+
error: 'Internal server error',
|
|
330
|
+
message: error.message
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// ๆฅ็ๆถไปถ็ฎฑ
|
|
336
|
+
api.get('/inbox/:ai_id', authMiddleware, (req, res) => {
|
|
337
|
+
const { ai_id } = req.params;
|
|
338
|
+
const { limit = 50, since = 0 } = req.query;
|
|
339
|
+
|
|
340
|
+
// ไปๅญๅจไธญ่ทๅๆถๆฏ๏ผๅบ่ฏฅไปๅฎ้
ๆฐๆฎๅบ่ฏปๅ๏ผ
|
|
341
|
+
const userMessages = [];
|
|
342
|
+
messages.forEach((msg, msgId) => {
|
|
343
|
+
if (msg.to === ai_id) {
|
|
344
|
+
// ่งฃๅฏๆถๆฏ
|
|
345
|
+
const decrypted = decryptMessage(msg.encrypted, msg.iv, SECURITY_CONFIG.API_SECRET);
|
|
346
|
+
if (decrypted) {
|
|
347
|
+
userMessages.push({
|
|
348
|
+
id: msgId,
|
|
349
|
+
from: msg.from,
|
|
350
|
+
to: msg.to,
|
|
351
|
+
timestamp: msg.timestamp,
|
|
352
|
+
content: decrypted
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// ่ฟๆปคๅๅ้กต
|
|
359
|
+
let filteredMessages = userMessages.filter(msg => msg.timestamp >= since);
|
|
360
|
+
filteredMessages.sort((a, b) => b.timestamp - a.timestamp);
|
|
361
|
+
filteredMessages = filteredMessages.slice(0, limit);
|
|
362
|
+
|
|
363
|
+
res.json({
|
|
364
|
+
total: filteredMessages.length,
|
|
365
|
+
messages: filteredMessages
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// ๅ ้คๆถๆฏ
|
|
370
|
+
api.delete('/messages/:message_id', authMiddleware, (req, res) => {
|
|
371
|
+
const { message_id } = req.params;
|
|
372
|
+
|
|
373
|
+
// ้ช่ฏๆถๆฏๆฏๅฆๅฑไบๅ้่
|
|
374
|
+
const message = messages.get(message_id);
|
|
375
|
+
if (!message) {
|
|
376
|
+
return res.status(404).json({
|
|
377
|
+
error: 'Message not found',
|
|
378
|
+
message: 'Message does not exist or has been deleted'
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// ้ช่ฏๆ้๏ผๆถๆฏๅฟ
้กปๆฏๅ้่
ๅ ้ค็๏ผ
|
|
383
|
+
if (message.from !== req.apiKey) {
|
|
384
|
+
// ๅฎ้
ๅบ็จๅบ่ฏฅ้ช่ฏ API Key ๅฏนๅบ็ AI ID
|
|
385
|
+
return res.status(403).json({
|
|
386
|
+
error: 'Permission denied',
|
|
387
|
+
message: 'You can only delete your own messages'
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// ๅ ้คๆถๆฏ
|
|
392
|
+
messages.delete(message_id);
|
|
393
|
+
console.log(`[๐๏ธ] Deleted message: ${message_id}`);
|
|
394
|
+
|
|
395
|
+
res.json({
|
|
396
|
+
ok: true,
|
|
397
|
+
message: 'Message deleted successfully'
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// ๆฅ็ๆๆๅทฒๆณจๅ็ AI
|
|
402
|
+
api.get('/api/agents', (req, res) => {
|
|
403
|
+
// ่ฟๅๆๆ AI ไฟกๆฏ๏ผๅฎ้
ๅบ็จๅบ่ฏฅไปๆฐๆฎๅบ่ฏปๅ๏ผ
|
|
404
|
+
const agents = [];
|
|
405
|
+
|
|
406
|
+
messages.forEach((msg, msgId) => {
|
|
407
|
+
if (!agents.find(a => a.ai_id === msg.from)) {
|
|
408
|
+
agents.push({
|
|
409
|
+
ai_id: msg.from,
|
|
410
|
+
registered_at: msg.timestamp,
|
|
411
|
+
message_count: 1 // ็ฎๅ็ป่ฎก
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
res.json({
|
|
417
|
+
total: agents.length,
|
|
418
|
+
agents
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
// ๅฅๅบทๆฃๆฅ
|
|
423
|
+
api.get('/health', (req, res) => {
|
|
424
|
+
res.json({
|
|
425
|
+
status: 'ok',
|
|
426
|
+
timestamp: new Date().toISOString(),
|
|
427
|
+
uptime: process.uptime(),
|
|
428
|
+
memory: process.memoryUsage(),
|
|
429
|
+
connections: agentCount,
|
|
430
|
+
messages: messages.size
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
// ============================================
|
|
435
|
+
// ๐ ๅฏๅจๆๅกๅจ
|
|
436
|
+
// ============================================
|
|
437
|
+
|
|
438
|
+
const PORT = process.env.PORT || 3000;
|
|
439
|
+
api.use(loggingMiddleware);
|
|
440
|
+
|
|
441
|
+
api.listen(PORT, () => {
|
|
442
|
+
console.log(`
|
|
443
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
444
|
+
โ ๐ OpenClaw Hub Server Started โ
|
|
445
|
+
โ โ
|
|
446
|
+
โ ๐ก Security Features: โ
|
|
447
|
+
โ โ
API Key Authentication โ
|
|
448
|
+
โ โ
Message Encryption โ
|
|
449
|
+
โ โ
Rate Limiting โ
|
|
450
|
+
โ โ
Access Control โ
|
|
451
|
+
โ โ
Message Expiry โ
|
|
452
|
+
โ โ
Secure Logging โ
|
|
453
|
+
โ โ
|
|
454
|
+
โ ๐ Server Info: โ
|
|
455
|
+
โ URL: http://localhost:${PORT} โ
|
|
456
|
+
โ MQTT: mqtt://localhost:1883 โ
|
|
457
|
+
โ WebSocket: ws://localhost:${PORT + 1} โ
|
|
458
|
+
โ โ
|
|
459
|
+
โ โ ๏ธ Production Checklist: โ
|
|
460
|
+
โ โข Set API_SECRET env variable โ
|
|
461
|
+
โ โข Configure WHITELIST/BLACKLIST โ
|
|
462
|
+
โ โข Use HTTPS in production โ
|
|
463
|
+
โ โข Use a real database (PostgreSQL) โ
|
|
464
|
+
โ โข Set up proper backup โ
|
|
465
|
+
โ โ
|
|
466
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
467
|
+
`);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
module.exports = { app: api, SECURITY_CONFIG };
|
package/start.sh
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
echo "ๅฏๅจ OpenClaw Hub..."
|
|
3
|
+
|
|
4
|
+
# ๅๅปบๆฅๅฟ็ฎๅฝ
|
|
5
|
+
mkdir -p logs
|
|
6
|
+
|
|
7
|
+
# ๆฃๆฅ็ซฏๅฃ
|
|
8
|
+
sudo lsof -i :1883 -t > /dev/null || echo "Port 1883 available"
|
|
9
|
+
sudo lsof -i :3000 -t > /dev/null || echo "Port 3000 available"
|
|
10
|
+
|
|
11
|
+
# ๅฏๅจ Broker
|
|
12
|
+
node broker.js > logs/broker.log 2>&1 &
|
|
13
|
+
BROKER_PID=$!
|
|
14
|
+
echo "Broker PID: $BROKER_PID"
|
|
15
|
+
echo "Hub ๅฏๅจๅฎๆ๏ผ"
|