@devo-bmad-custom/agent-orchestration 1.0.4 → 1.0.6
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/package.json +1 -1
- package/src/.agents/skills/tmux-commands/SKILL.md +353 -0
- package/src/bmm/data/project-context-template.md +26 -26
- package/src/bmm/teams/default-party.csv +20 -20
- package/src/bmm/workflows/2-plan-workflows/create-prd/data/domain-complexity.csv +14 -14
- package/src/bmm/workflows/2-plan-workflows/create-prd/data/prd-purpose.md +197 -197
- package/src/bmm/workflows/2-plan-workflows/create-prd/data/project-types.csv +10 -10
- package/src/bmm/workflows/2-plan-workflows/create-prd/templates/prd-template.md +10 -10
- package/src/bmm/workflows/3-solutioning/create-architecture/data/domain-complexity.csv +12 -12
- package/src/bmm/workflows/4-implementation/code-review/instructions.xml +226 -226
- package/src/bmm/workflows/4-implementation/correct-course/checklist.md +288 -288
- package/src/bmm/workflows/4-implementation/correct-course/instructions.md +207 -207
- package/src/bmm/workflows/4-implementation/retrospective/instructions.md +1444 -1444
- package/src/bmm/workflows/4-implementation/sprint-planning/sprint-status-template.yaml +55 -55
- package/src/bmm/workflows/4-implementation/sprint-status/instructions.md +230 -230
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/tech-spec-template.md +74 -74
- package/src/bmm/workflows/document-project/instructions.md +130 -130
- package/src/bmm/workflows/document-project/templates/project-scan-report-schema.json +160 -160
- package/src/bmm/workflows/document-project/workflows/deep-dive-instructions.md +298 -298
- package/src/bmm/workflows/document-project/workflows/deep-dive.yaml +31 -31
- package/src/bmm/workflows/document-project/workflows/full-scan-instructions.md +1106 -1106
- package/src/bmm/workflows/document-project/workflows/full-scan.yaml +31 -31
- package/src/bmm/workflows/qa-generate-e2e-tests/checklist.md +33 -33
- package/src/bmm/workflows/qa-generate-e2e-tests/instructions.md +110 -110
- package/src/core/agents/bmad-master.md +56 -56
- package/src/core/workflows/party-mode/steps/step-02-discussion-orchestration.md +187 -187
- package/src/core/workflows/party-mode/steps/step-03-graceful-exit.md +168 -168
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/vision-framework/SKILL.md +0 -475
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/vision-framework/references/vision-requests.md +0 -736
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/vision-framework/references/visionkit-scanner.md +0 -738
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/weatherkit/SKILL.md +0 -410
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/weatherkit/references/weatherkit-patterns.md +0 -567
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/widgetkit/SKILL.md +0 -497
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/widgetkit/references/widgetkit-advanced.md +0 -871
- package/src/.agents/skills/ui-ux-pro-custom/data/typography.csv +0 -58
- package/src/.agents/skills/ui-ux-pro-custom/data/ui-reasoning.csv +0 -101
- package/src/.agents/skills/ui-ux-pro-custom/data/ux-guidelines.csv +0 -100
- package/src/.agents/skills/ui-ux-pro-custom/data/web-interface.csv +0 -31
- package/src/.agents/skills/ui-ux-pro-custom/scripts/core.py +0 -253
- package/src/.agents/skills/ui-ux-pro-custom/scripts/design_system.py +0 -1067
- package/src/.agents/skills/ui-ux-pro-custom/scripts/search.py +0 -114
- package/src/.agents/skills/ux-audit/SKILL.md +0 -151
- package/src/.agents/skills/websocket-engineer/SKILL.md +0 -168
- package/src/.agents/skills/websocket-engineer/references/alternatives.md +0 -391
- package/src/.agents/skills/websocket-engineer/references/patterns.md +0 -400
- package/src/.agents/skills/websocket-engineer/references/protocol.md +0 -195
- package/src/.agents/skills/websocket-engineer/references/scaling.md +0 -333
- package/src/.agents/skills/websocket-engineer/references/security.md +0 -474
- package/src/.agents/skills/writing-skills/SKILL.md +0 -655
- package/src/.agents/skills/writing-skills/anthropic-best-practices.md +0 -1150
- package/src/.agents/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +0 -189
- package/src/.agents/skills/writing-skills/graphviz-conventions.dot +0 -172
- package/src/.agents/skills/writing-skills/persuasion-principles.md +0 -187
- package/src/.agents/skills/writing-skills/render-graphs.js +0 -168
- package/src/.agents/skills/writing-skills/testing-skills-with-subagents.md +0 -384
- package/src/.claude/commands/bmad-track-compact.md +0 -19
- package/src/.claude/commands/bmad-track-extended.md +0 -19
- package/src/.claude/commands/bmad-track-large.md +0 -19
- package/src/.claude/commands/bmad-track-medium.md +0 -19
- package/src/.claude/commands/bmad-track-nano.md +0 -19
- package/src/.claude/commands/bmad-track-rv.md +0 -18
- package/src/.claude/commands/bmad-track-small.md +0 -19
- package/src/.claude/commands/master-orchestrator.md +0 -15
- package/src/_memory/master-orchestrator-sidecar/docs-index.md +0 -3
- package/src/_memory/master-orchestrator-sidecar/instructions.md +0 -2616
- package/src/_memory/master-orchestrator-sidecar/memories.md +0 -8
- package/src/_memory/master-orchestrator-sidecar/session-state.md +0 -15
- package/src/_memory/master-orchestrator-sidecar/triage-history.md +0 -3
- package/src/_memory/master-orchestrator-sidecar/workflows-overview.html +0 -1230
- package/src/core/agents/master-orchestrator.md +0 -54
- package/src/docs/dev/tmux/actions_popup.py +0 -291
- package/src/docs/dev/tmux/actions_popup.sh +0 -110
- package/src/docs/dev/tmux/claude_usage.sh +0 -15
- package/src/docs/dev/tmux/colors.conf +0 -26
- package/src/docs/dev/tmux/cpu_usage.sh +0 -7
- package/src/docs/dev/tmux/dispatch.sh +0 -10
- package/src/docs/dev/tmux/float_init.sh +0 -13
- package/src/docs/dev/tmux/float_term.sh +0 -23
- package/src/docs/dev/tmux/open_clip.sh +0 -14
- package/src/docs/dev/tmux/paste_clipboard.sh +0 -13
- package/src/docs/dev/tmux/paste_image_wrapper.sh +0 -94
- package/src/docs/dev/tmux/ram_usage.sh +0 -3
- package/src/docs/dev/tmux/title_sync.sh +0 -54
- package/src/docs/dev/tmux/tmux-setup.md +0 -867
- package/src/docs/dev/tmux/tmux.conf +0 -127
- package/src/docs/dev/tmux/xclip +0 -18
|
@@ -1,474 +0,0 @@
|
|
|
1
|
-
# WebSocket Security Reference
|
|
2
|
-
|
|
3
|
-
## Authentication
|
|
4
|
-
|
|
5
|
-
### JWT Authentication
|
|
6
|
-
|
|
7
|
-
```javascript
|
|
8
|
-
const io = require('socket.io')(3000);
|
|
9
|
-
const jwt = require('jsonwebtoken');
|
|
10
|
-
|
|
11
|
-
// Middleware for authentication
|
|
12
|
-
io.use((socket, next) => {
|
|
13
|
-
const token = socket.handshake.auth.token;
|
|
14
|
-
|
|
15
|
-
if (!token) {
|
|
16
|
-
return next(new Error('Authentication error: No token provided'));
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
try {
|
|
20
|
-
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
|
21
|
-
socket.userId = decoded.userId;
|
|
22
|
-
socket.username = decoded.username;
|
|
23
|
-
next();
|
|
24
|
-
} catch (err) {
|
|
25
|
-
next(new Error('Authentication error: Invalid token'));
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
io.on('connection', (socket) => {
|
|
30
|
-
console.log(`User ${socket.username} connected`);
|
|
31
|
-
|
|
32
|
-
socket.on('message', (data) => {
|
|
33
|
-
// socket.userId is already verified
|
|
34
|
-
saveMessage(socket.userId, data);
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### Query Parameter Authentication (Less Secure)
|
|
40
|
-
|
|
41
|
-
```javascript
|
|
42
|
-
// Use only for initial handshake, then upgrade to token
|
|
43
|
-
io.use((socket, next) => {
|
|
44
|
-
const token = socket.handshake.query.token;
|
|
45
|
-
|
|
46
|
-
if (!token) {
|
|
47
|
-
return next(new Error('Authentication required'));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
|
|
51
|
-
if (err) return next(new Error('Invalid token'));
|
|
52
|
-
socket.userId = decoded.userId;
|
|
53
|
-
next();
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
### Cookie Authentication
|
|
59
|
-
|
|
60
|
-
```javascript
|
|
61
|
-
const cookieParser = require('cookie-parser');
|
|
62
|
-
|
|
63
|
-
io.use((socket, next) => {
|
|
64
|
-
const cookies = socket.handshake.headers.cookie;
|
|
65
|
-
|
|
66
|
-
if (!cookies) {
|
|
67
|
-
return next(new Error('No cookies'));
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Parse cookies
|
|
71
|
-
cookieParser(process.env.COOKIE_SECRET)(
|
|
72
|
-
socket.request,
|
|
73
|
-
{},
|
|
74
|
-
() => {
|
|
75
|
-
const sessionId = socket.request.signedCookies.sessionId;
|
|
76
|
-
|
|
77
|
-
if (!sessionId) {
|
|
78
|
-
return next(new Error('No session'));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Verify session in Redis/DB
|
|
82
|
-
verifySession(sessionId).then(user => {
|
|
83
|
-
socket.userId = user.id;
|
|
84
|
-
next();
|
|
85
|
-
}).catch(err => {
|
|
86
|
-
next(new Error('Invalid session'));
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
);
|
|
90
|
-
});
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
## Authorization
|
|
94
|
-
|
|
95
|
-
### Room-Based Authorization
|
|
96
|
-
|
|
97
|
-
```javascript
|
|
98
|
-
io.on('connection', (socket) => {
|
|
99
|
-
socket.on('join-room', async (roomId) => {
|
|
100
|
-
// Check if user has permission
|
|
101
|
-
const hasAccess = await checkRoomAccess(socket.userId, roomId);
|
|
102
|
-
|
|
103
|
-
if (!hasAccess) {
|
|
104
|
-
socket.emit('error', { message: 'Access denied to room' });
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
socket.join(roomId);
|
|
109
|
-
socket.emit('joined', { room: roomId });
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
socket.on('send-message', async ({ roomId, text }) => {
|
|
113
|
-
// Verify user is in room
|
|
114
|
-
if (!socket.rooms.has(roomId)) {
|
|
115
|
-
socket.emit('error', { message: 'Not in room' });
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Check write permissions
|
|
120
|
-
const canWrite = await checkWritePermission(socket.userId, roomId);
|
|
121
|
-
|
|
122
|
-
if (!canWrite) {
|
|
123
|
-
socket.emit('error', { message: 'No write permission' });
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
io.to(roomId).emit('message', {
|
|
128
|
-
userId: socket.userId,
|
|
129
|
-
text,
|
|
130
|
-
timestamp: Date.now()
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
### Admin-Only Events
|
|
137
|
-
|
|
138
|
-
```javascript
|
|
139
|
-
const ADMIN_EVENTS = ['kick-user', 'ban-user', 'delete-message'];
|
|
140
|
-
|
|
141
|
-
io.use((socket, next) => {
|
|
142
|
-
// Attach role to socket after auth
|
|
143
|
-
getUserRole(socket.userId).then(role => {
|
|
144
|
-
socket.role = role;
|
|
145
|
-
next();
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
io.on('connection', (socket) => {
|
|
150
|
-
ADMIN_EVENTS.forEach(event => {
|
|
151
|
-
socket.on(event, async (data) => {
|
|
152
|
-
if (socket.role !== 'admin') {
|
|
153
|
-
socket.emit('error', { message: 'Admin access required' });
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Execute admin action
|
|
158
|
-
await handleAdminAction(event, data);
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
});
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
## Rate Limiting
|
|
165
|
-
|
|
166
|
-
### Per-Socket Rate Limiting
|
|
167
|
-
|
|
168
|
-
```javascript
|
|
169
|
-
const rateLimit = require('express-rate-limit');
|
|
170
|
-
|
|
171
|
-
class SocketRateLimiter {
|
|
172
|
-
constructor(maxRequests = 100, windowMs = 60000) {
|
|
173
|
-
this.maxRequests = maxRequests;
|
|
174
|
-
this.windowMs = windowMs;
|
|
175
|
-
this.requests = new Map();
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
check(socketId) {
|
|
179
|
-
const now = Date.now();
|
|
180
|
-
const userRequests = this.requests.get(socketId) || [];
|
|
181
|
-
|
|
182
|
-
// Remove expired requests
|
|
183
|
-
const validRequests = userRequests.filter(
|
|
184
|
-
time => now - time < this.windowMs
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
if (validRequests.length >= this.maxRequests) {
|
|
188
|
-
return false; // Rate limit exceeded
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
validRequests.push(now);
|
|
192
|
-
this.requests.set(socketId, validRequests);
|
|
193
|
-
return true;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
reset(socketId) {
|
|
197
|
-
this.requests.delete(socketId);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const limiter = new SocketRateLimiter(100, 60000); // 100 req/min
|
|
202
|
-
|
|
203
|
-
io.on('connection', (socket) => {
|
|
204
|
-
socket.on('message', (data) => {
|
|
205
|
-
if (!limiter.check(socket.id)) {
|
|
206
|
-
socket.emit('error', { message: 'Rate limit exceeded' });
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Process message
|
|
211
|
-
io.to(data.roomId).emit('message', data);
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
socket.on('disconnect', () => {
|
|
215
|
-
limiter.reset(socket.id);
|
|
216
|
-
});
|
|
217
|
-
});
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
### Redis-Based Distributed Rate Limiting
|
|
221
|
-
|
|
222
|
-
```javascript
|
|
223
|
-
const Redis = require('ioredis');
|
|
224
|
-
const redis = new Redis();
|
|
225
|
-
|
|
226
|
-
async function checkRateLimit(userId, maxRequests = 100, windowSec = 60) {
|
|
227
|
-
const key = `rate_limit:${userId}`;
|
|
228
|
-
const now = Date.now();
|
|
229
|
-
const windowStart = now - (windowSec * 1000);
|
|
230
|
-
|
|
231
|
-
const pipeline = redis.pipeline();
|
|
232
|
-
|
|
233
|
-
// Remove old entries
|
|
234
|
-
pipeline.zremrangebyscore(key, 0, windowStart);
|
|
235
|
-
|
|
236
|
-
// Count requests in window
|
|
237
|
-
pipeline.zcard(key);
|
|
238
|
-
|
|
239
|
-
// Add current request
|
|
240
|
-
pipeline.zadd(key, now, `${now}-${Math.random()}`);
|
|
241
|
-
|
|
242
|
-
// Set expiry
|
|
243
|
-
pipeline.expire(key, windowSec);
|
|
244
|
-
|
|
245
|
-
const results = await pipeline.exec();
|
|
246
|
-
const count = results[1][1];
|
|
247
|
-
|
|
248
|
-
return count < maxRequests;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
io.on('connection', (socket) => {
|
|
252
|
-
socket.on('message', async (data) => {
|
|
253
|
-
const allowed = await checkRateLimit(socket.userId, 50, 60);
|
|
254
|
-
|
|
255
|
-
if (!allowed) {
|
|
256
|
-
socket.emit('error', { message: 'Too many requests' });
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
io.to(data.roomId).emit('message', data);
|
|
261
|
-
});
|
|
262
|
-
});
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
## CORS Configuration
|
|
266
|
-
|
|
267
|
-
```javascript
|
|
268
|
-
const io = require('socket.io')(3000, {
|
|
269
|
-
cors: {
|
|
270
|
-
origin: ['https://example.com', 'https://app.example.com'],
|
|
271
|
-
methods: ['GET', 'POST'],
|
|
272
|
-
credentials: true,
|
|
273
|
-
allowedHeaders: ['Authorization']
|
|
274
|
-
}
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
// Dynamic CORS
|
|
278
|
-
io.engine.on('initial_headers', (headers, req) => {
|
|
279
|
-
headers['Access-Control-Allow-Origin'] = req.headers.origin;
|
|
280
|
-
});
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
## Input Validation
|
|
284
|
-
|
|
285
|
-
```javascript
|
|
286
|
-
const Joi = require('joi');
|
|
287
|
-
|
|
288
|
-
const messageSchema = Joi.object({
|
|
289
|
-
roomId: Joi.string().uuid().required(),
|
|
290
|
-
text: Joi.string().min(1).max(1000).required(),
|
|
291
|
-
attachments: Joi.array().items(Joi.string().uri()).max(5).optional()
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
io.on('connection', (socket) => {
|
|
295
|
-
socket.on('message', (data) => {
|
|
296
|
-
// Validate input
|
|
297
|
-
const { error, value } = messageSchema.validate(data);
|
|
298
|
-
|
|
299
|
-
if (error) {
|
|
300
|
-
socket.emit('error', {
|
|
301
|
-
message: 'Invalid message format',
|
|
302
|
-
details: error.details
|
|
303
|
-
});
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// Process validated data
|
|
308
|
-
io.to(value.roomId).emit('message', {
|
|
309
|
-
userId: socket.userId,
|
|
310
|
-
...value,
|
|
311
|
-
timestamp: Date.now()
|
|
312
|
-
});
|
|
313
|
-
});
|
|
314
|
-
});
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
## XSS Protection
|
|
318
|
-
|
|
319
|
-
```javascript
|
|
320
|
-
const sanitizeHtml = require('sanitize-html');
|
|
321
|
-
|
|
322
|
-
function sanitizeMessage(text) {
|
|
323
|
-
return sanitizeHtml(text, {
|
|
324
|
-
allowedTags: [], // Strip all HTML
|
|
325
|
-
allowedAttributes: {},
|
|
326
|
-
disallowedTagsMode: 'escape'
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
io.on('connection', (socket) => {
|
|
331
|
-
socket.on('message', (data) => {
|
|
332
|
-
const sanitized = {
|
|
333
|
-
...data,
|
|
334
|
-
text: sanitizeMessage(data.text)
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
io.to(data.roomId).emit('message', sanitized);
|
|
338
|
-
});
|
|
339
|
-
});
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
## DDoS Protection
|
|
343
|
-
|
|
344
|
-
### Connection Limiting
|
|
345
|
-
|
|
346
|
-
```javascript
|
|
347
|
-
const connectionLimits = new Map();
|
|
348
|
-
const MAX_CONNECTIONS_PER_IP = 10;
|
|
349
|
-
|
|
350
|
-
io.engine.on('connection', (rawSocket) => {
|
|
351
|
-
const ip = rawSocket.request.headers['x-forwarded-for'] ||
|
|
352
|
-
rawSocket.request.connection.remoteAddress;
|
|
353
|
-
|
|
354
|
-
const currentConnections = connectionLimits.get(ip) || 0;
|
|
355
|
-
|
|
356
|
-
if (currentConnections >= MAX_CONNECTIONS_PER_IP) {
|
|
357
|
-
rawSocket.close(1008, 'Too many connections from IP');
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
connectionLimits.set(ip, currentConnections + 1);
|
|
362
|
-
|
|
363
|
-
rawSocket.on('close', () => {
|
|
364
|
-
const count = connectionLimits.get(ip) - 1;
|
|
365
|
-
if (count <= 0) {
|
|
366
|
-
connectionLimits.delete(ip);
|
|
367
|
-
} else {
|
|
368
|
-
connectionLimits.set(ip, count);
|
|
369
|
-
}
|
|
370
|
-
});
|
|
371
|
-
});
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
### Message Size Limits
|
|
375
|
-
|
|
376
|
-
```javascript
|
|
377
|
-
const io = require('socket.io')(3000, {
|
|
378
|
-
maxHttpBufferSize: 1e6, // 1MB max message size
|
|
379
|
-
pingTimeout: 60000,
|
|
380
|
-
pingInterval: 25000
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
io.on('connection', (socket) => {
|
|
384
|
-
socket.on('message', (data) => {
|
|
385
|
-
if (JSON.stringify(data).length > 10000) {
|
|
386
|
-
socket.emit('error', { message: 'Message too large' });
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// Process message
|
|
391
|
-
});
|
|
392
|
-
});
|
|
393
|
-
```
|
|
394
|
-
|
|
395
|
-
## Secure Session Management
|
|
396
|
-
|
|
397
|
-
```javascript
|
|
398
|
-
const sessions = new Map();
|
|
399
|
-
|
|
400
|
-
io.on('connection', (socket) => {
|
|
401
|
-
const sessionId = generateSecureSessionId();
|
|
402
|
-
|
|
403
|
-
sessions.set(socket.id, {
|
|
404
|
-
sessionId,
|
|
405
|
-
userId: socket.userId,
|
|
406
|
-
createdAt: Date.now(),
|
|
407
|
-
lastActivity: Date.now()
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
// Timeout inactive sessions
|
|
411
|
-
const timeout = setTimeout(() => {
|
|
412
|
-
socket.disconnect(true);
|
|
413
|
-
}, 30 * 60 * 1000); // 30 minutes
|
|
414
|
-
|
|
415
|
-
socket.on('message', () => {
|
|
416
|
-
const session = sessions.get(socket.id);
|
|
417
|
-
if (session) {
|
|
418
|
-
session.lastActivity = Date.now();
|
|
419
|
-
clearTimeout(timeout);
|
|
420
|
-
}
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
socket.on('disconnect', () => {
|
|
424
|
-
sessions.delete(socket.id);
|
|
425
|
-
clearTimeout(timeout);
|
|
426
|
-
});
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
function generateSecureSessionId() {
|
|
430
|
-
return require('crypto').randomBytes(32).toString('hex');
|
|
431
|
-
}
|
|
432
|
-
```
|
|
433
|
-
|
|
434
|
-
## Audit Logging
|
|
435
|
-
|
|
436
|
-
```javascript
|
|
437
|
-
const winston = require('winston');
|
|
438
|
-
|
|
439
|
-
const logger = winston.createLogger({
|
|
440
|
-
level: 'info',
|
|
441
|
-
format: winston.format.json(),
|
|
442
|
-
transports: [
|
|
443
|
-
new winston.transports.File({ filename: 'websocket-audit.log' })
|
|
444
|
-
]
|
|
445
|
-
});
|
|
446
|
-
|
|
447
|
-
io.on('connection', (socket) => {
|
|
448
|
-
logger.info('Connection', {
|
|
449
|
-
socketId: socket.id,
|
|
450
|
-
userId: socket.userId,
|
|
451
|
-
ip: socket.handshake.address,
|
|
452
|
-
timestamp: Date.now()
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
socket.on('message', (data) => {
|
|
456
|
-
logger.info('Message', {
|
|
457
|
-
socketId: socket.id,
|
|
458
|
-
userId: socket.userId,
|
|
459
|
-
roomId: data.roomId,
|
|
460
|
-
messageLength: data.text.length,
|
|
461
|
-
timestamp: Date.now()
|
|
462
|
-
});
|
|
463
|
-
});
|
|
464
|
-
|
|
465
|
-
socket.on('disconnect', (reason) => {
|
|
466
|
-
logger.info('Disconnect', {
|
|
467
|
-
socketId: socket.id,
|
|
468
|
-
userId: socket.userId,
|
|
469
|
-
reason,
|
|
470
|
-
timestamp: Date.now()
|
|
471
|
-
});
|
|
472
|
-
});
|
|
473
|
-
});
|
|
474
|
-
```
|