3xui-api-client 1.0.0 → 2.0.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/CHANGELOG.md +73 -3
- package/README.md +19 -150
- package/index.d.ts +415 -108
- package/index.js +472 -32
- package/package.json +24 -9
- package/src/builders/ProtocolBuilders.js +686 -0
- package/src/generators/CredentialGenerator.js +362 -0
- package/src/middleware/WebMiddleware.js +311 -0
- package/src/security/SecurityEnhancer.js +1 -0
- package/src/session/SessionManager.js +493 -0
- package/SECURITY.md +0 -150
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Management System for 3xui-api-client
|
|
3
|
+
* Provides both built-in caching and user-managed database integration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const crypto = require('crypto');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Abstract base class for session storage
|
|
10
|
+
*/
|
|
11
|
+
class SessionStore {
|
|
12
|
+
async get(_key) {
|
|
13
|
+
throw new Error('get method must be implemented');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async set(_key, _value, _ttl) {
|
|
17
|
+
throw new Error('set method must be implemented');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async delete(_key) {
|
|
21
|
+
throw new Error('delete method must be implemented');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async clear() {
|
|
25
|
+
throw new Error('clear method must be implemented');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async exists(key) {
|
|
29
|
+
const value = await this.get(key);
|
|
30
|
+
return value !== null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Built-in memory session store (default)
|
|
36
|
+
* Recommended for development and single-instance deployments
|
|
37
|
+
*/
|
|
38
|
+
class MemorySessionStore extends SessionStore {
|
|
39
|
+
constructor() {
|
|
40
|
+
super();
|
|
41
|
+
this.cache = new Map();
|
|
42
|
+
this.timers = new Map();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async get(key) {
|
|
46
|
+
const item = this.cache.get(key);
|
|
47
|
+
if (!item) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (item.expires && item.expires <= Date.now()) {
|
|
52
|
+
this.delete(key);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return item.value;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async set(key, value, ttl = 3600) {
|
|
60
|
+
// Clear existing timer if it exists
|
|
61
|
+
const existingTimer = this.timers.get(key);
|
|
62
|
+
if (existingTimer) {
|
|
63
|
+
clearTimeout(existingTimer);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const expires = ttl > 0 ? Date.now() + (ttl * 1000) : null;
|
|
67
|
+
this.cache.set(key, { value, expires });
|
|
68
|
+
|
|
69
|
+
// Set expiration timer
|
|
70
|
+
if (ttl > 0) {
|
|
71
|
+
const timer = setTimeout(() => {
|
|
72
|
+
this.delete(key);
|
|
73
|
+
}, ttl * 1000);
|
|
74
|
+
this.timers.set(key, timer);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async delete(key) {
|
|
79
|
+
this.cache.delete(key);
|
|
80
|
+
const timer = this.timers.get(key);
|
|
81
|
+
if (timer) {
|
|
82
|
+
clearTimeout(timer);
|
|
83
|
+
this.timers.delete(key);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async clear() {
|
|
88
|
+
// Clear all timers
|
|
89
|
+
for (const timer of this.timers.values()) {
|
|
90
|
+
clearTimeout(timer);
|
|
91
|
+
}
|
|
92
|
+
this.cache.clear();
|
|
93
|
+
this.timers.clear();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Get cache statistics
|
|
97
|
+
getStats() {
|
|
98
|
+
return {
|
|
99
|
+
size: this.cache.size,
|
|
100
|
+
keys: Array.from(this.cache.keys())
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Redis session store adapter
|
|
107
|
+
* Recommended for production and multi-instance deployments
|
|
108
|
+
*/
|
|
109
|
+
class RedisSessionStore extends SessionStore {
|
|
110
|
+
constructor(redisClient, options = {}) {
|
|
111
|
+
super();
|
|
112
|
+
this.redis = redisClient;
|
|
113
|
+
this.keyPrefix = options.keyPrefix || '3xui:session:';
|
|
114
|
+
this.defaultTTL = options.defaultTTL || 3600;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
_getKey(key) {
|
|
118
|
+
return `${this.keyPrefix}${key}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async get(key) {
|
|
122
|
+
try {
|
|
123
|
+
const value = await this.redis.get(this._getKey(key));
|
|
124
|
+
return value ? JSON.parse(value) : null;
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error('Redis get error:', error);
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async set(key, value, ttl = this.defaultTTL) {
|
|
132
|
+
try {
|
|
133
|
+
const serialized = JSON.stringify(value);
|
|
134
|
+
if (ttl > 0) {
|
|
135
|
+
await this.redis.setex(this._getKey(key), ttl, serialized);
|
|
136
|
+
} else {
|
|
137
|
+
await this.redis.set(this._getKey(key), serialized);
|
|
138
|
+
}
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error('Redis set error:', error);
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async delete(key) {
|
|
146
|
+
try {
|
|
147
|
+
await this.redis.del(this._getKey(key));
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error('Redis delete error:', error);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async clear() {
|
|
154
|
+
try {
|
|
155
|
+
const keys = await this.redis.keys(`${this.keyPrefix}*`);
|
|
156
|
+
if (keys.length > 0) {
|
|
157
|
+
await this.redis.del(keys);
|
|
158
|
+
}
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error('Redis clear error:', error);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async exists(key) {
|
|
165
|
+
try {
|
|
166
|
+
const result = await this.redis.exists(this._getKey(key));
|
|
167
|
+
return result === 1;
|
|
168
|
+
} catch (error) {
|
|
169
|
+
console.error('Redis exists error:', error);
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Database session store adapter
|
|
177
|
+
* Works with any SQL database through a provided database client
|
|
178
|
+
*/
|
|
179
|
+
class DatabaseSessionStore extends SessionStore {
|
|
180
|
+
constructor(database, options = {}) {
|
|
181
|
+
super();
|
|
182
|
+
this.db = database;
|
|
183
|
+
this.tableName = options.tableName || 'sessions';
|
|
184
|
+
this.keyColumn = options.keyColumn || 'session_key';
|
|
185
|
+
this.valueColumn = options.valueColumn || 'session_data';
|
|
186
|
+
this.expiresColumn = options.expiresColumn || 'expires_at';
|
|
187
|
+
this.defaultTTL = options.defaultTTL || 3600;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async get(key) {
|
|
191
|
+
try {
|
|
192
|
+
const query = `
|
|
193
|
+
SELECT ${this.valueColumn}
|
|
194
|
+
FROM ${this.tableName}
|
|
195
|
+
WHERE ${this.keyColumn} = ?
|
|
196
|
+
AND (${this.expiresColumn} IS NULL OR ${this.expiresColumn} > ?)
|
|
197
|
+
`;
|
|
198
|
+
const result = await this.db.query(query, [key, new Date()]);
|
|
199
|
+
|
|
200
|
+
if (result.length === 0) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return JSON.parse(result[0][this.valueColumn]);
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.error('Database get error:', error);
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async set(key, value, ttl = this.defaultTTL) {
|
|
212
|
+
try {
|
|
213
|
+
const serialized = JSON.stringify(value);
|
|
214
|
+
const expiresAt = ttl > 0 ? new Date(Date.now() + ttl * 1000) : null;
|
|
215
|
+
|
|
216
|
+
// Use UPSERT operation
|
|
217
|
+
const query = `
|
|
218
|
+
INSERT INTO ${this.tableName} (${this.keyColumn}, ${this.valueColumn}, ${this.expiresColumn})
|
|
219
|
+
VALUES (?, ?, ?)
|
|
220
|
+
ON DUPLICATE KEY UPDATE
|
|
221
|
+
${this.valueColumn} = VALUES(${this.valueColumn}),
|
|
222
|
+
${this.expiresColumn} = VALUES(${this.expiresColumn})
|
|
223
|
+
`;
|
|
224
|
+
|
|
225
|
+
await this.db.query(query, [key, serialized, expiresAt]);
|
|
226
|
+
} catch (error) {
|
|
227
|
+
console.error('Database set error:', error);
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async delete(key) {
|
|
233
|
+
try {
|
|
234
|
+
const query = `DELETE FROM ${this.tableName} WHERE ${this.keyColumn} = ?`;
|
|
235
|
+
await this.db.query(query, [key]);
|
|
236
|
+
} catch (error) {
|
|
237
|
+
console.error('Database delete error:', error);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async clear() {
|
|
242
|
+
try {
|
|
243
|
+
const query = `DELETE FROM ${this.tableName}`;
|
|
244
|
+
await this.db.query(query);
|
|
245
|
+
} catch (error) {
|
|
246
|
+
console.error('Database clear error:', error);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async exists(key) {
|
|
251
|
+
try {
|
|
252
|
+
const query = `
|
|
253
|
+
SELECT 1 FROM ${this.tableName}
|
|
254
|
+
WHERE ${this.keyColumn} = ?
|
|
255
|
+
AND (${this.expiresColumn} IS NULL OR ${this.expiresColumn} > ?)
|
|
256
|
+
LIMIT 1
|
|
257
|
+
`;
|
|
258
|
+
const result = await this.db.query(query, [key, new Date()]);
|
|
259
|
+
return result.length > 0;
|
|
260
|
+
} catch (error) {
|
|
261
|
+
console.error('Database exists error:', error);
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Cleanup expired sessions
|
|
267
|
+
async cleanupExpired() {
|
|
268
|
+
try {
|
|
269
|
+
const query = `DELETE FROM ${this.tableName} WHERE ${this.expiresColumn} <= ?`;
|
|
270
|
+
const result = await this.db.query(query, [new Date()]);
|
|
271
|
+
return result.affectedRows || 0;
|
|
272
|
+
} catch (error) {
|
|
273
|
+
console.error('Database cleanup error:', error);
|
|
274
|
+
return 0;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Get database schema for creating sessions table
|
|
279
|
+
static getCreateTableSQL(tableName = 'sessions') {
|
|
280
|
+
return `
|
|
281
|
+
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
282
|
+
session_key VARCHAR(255) PRIMARY KEY,
|
|
283
|
+
session_data TEXT NOT NULL,
|
|
284
|
+
expires_at TIMESTAMP NULL,
|
|
285
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
286
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
287
|
+
INDEX idx_expires (expires_at)
|
|
288
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
289
|
+
`;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* User-managed session handler
|
|
295
|
+
* Allows users to provide their own session management functions
|
|
296
|
+
*/
|
|
297
|
+
class CustomSessionHandler {
|
|
298
|
+
constructor(handlers) {
|
|
299
|
+
this.getSession = handlers.getSession;
|
|
300
|
+
this.setSession = handlers.setSession;
|
|
301
|
+
this.deleteSession = handlers.deleteSession;
|
|
302
|
+
this.validateSession = handlers.validateSession;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async get(key) {
|
|
306
|
+
try {
|
|
307
|
+
return await this.getSession(key);
|
|
308
|
+
} catch (error) {
|
|
309
|
+
console.error('Custom session get error:', error);
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async set(key, value, ttl) {
|
|
315
|
+
try {
|
|
316
|
+
await this.setSession(key, value, ttl);
|
|
317
|
+
} catch (error) {
|
|
318
|
+
console.error('Custom session set error:', error);
|
|
319
|
+
throw error;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async delete(key) {
|
|
324
|
+
try {
|
|
325
|
+
await this.deleteSession(key);
|
|
326
|
+
} catch (error) {
|
|
327
|
+
console.error('Custom session delete error:', error);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async validate(key) {
|
|
332
|
+
try {
|
|
333
|
+
if (this.validateSession) {
|
|
334
|
+
return await this.validateSession(key);
|
|
335
|
+
}
|
|
336
|
+
return await this.get(key) !== null;
|
|
337
|
+
} catch (error) {
|
|
338
|
+
console.error('Custom session validate error:', error);
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Main session manager class
|
|
346
|
+
*/
|
|
347
|
+
class SessionManager {
|
|
348
|
+
constructor(options = {}) {
|
|
349
|
+
this.sessionTTL = options.sessionTTL || 3600; // 1 hour default
|
|
350
|
+
this.autoRefresh = options.autoRefresh !== false;
|
|
351
|
+
this.refreshThreshold = options.refreshThreshold || 0.8; // Refresh when 80% expired
|
|
352
|
+
|
|
353
|
+
// Initialize session store
|
|
354
|
+
if (options.store) {
|
|
355
|
+
this.store = options.store;
|
|
356
|
+
} else if (options.redis) {
|
|
357
|
+
this.store = new RedisSessionStore(options.redis, options.redisOptions);
|
|
358
|
+
} else if (options.database) {
|
|
359
|
+
this.store = new DatabaseSessionStore(options.database, options.databaseOptions);
|
|
360
|
+
} else if (options.customHandler) {
|
|
361
|
+
this.store = new CustomSessionHandler(options.customHandler);
|
|
362
|
+
} else {
|
|
363
|
+
this.store = new MemorySessionStore();
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Generate session key for a server
|
|
369
|
+
*/
|
|
370
|
+
generateSessionKey(baseURL, username) {
|
|
371
|
+
const hash = crypto
|
|
372
|
+
.createHash('sha256')
|
|
373
|
+
.update(`${baseURL}:${username}`)
|
|
374
|
+
.digest('hex');
|
|
375
|
+
return `session_${hash}`;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Store session data
|
|
380
|
+
*/
|
|
381
|
+
async storeSession(baseURL, username, sessionData) {
|
|
382
|
+
const key = this.generateSessionKey(baseURL, username);
|
|
383
|
+
const data = {
|
|
384
|
+
...sessionData,
|
|
385
|
+
createdAt: Date.now(),
|
|
386
|
+
baseURL,
|
|
387
|
+
username
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
await this.store.set(key, data, this.sessionTTL);
|
|
391
|
+
return key;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Retrieve session data
|
|
396
|
+
*/
|
|
397
|
+
async getSession(baseURL, username) {
|
|
398
|
+
const key = this.generateSessionKey(baseURL, username);
|
|
399
|
+
return await this.store.get(key);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Check if session exists and is valid
|
|
404
|
+
*/
|
|
405
|
+
async hasValidSession(baseURL, username) {
|
|
406
|
+
const session = await this.getSession(baseURL, username);
|
|
407
|
+
if (!session) {
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Check if session needs refresh
|
|
412
|
+
if (this.autoRefresh && this.shouldRefreshSession(session)) {
|
|
413
|
+
return false; // Trigger re-authentication
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Check if session should be refreshed
|
|
421
|
+
*/
|
|
422
|
+
shouldRefreshSession(session) {
|
|
423
|
+
if (!session.createdAt) {
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const age = Date.now() - session.createdAt;
|
|
428
|
+
const maxAge = this.sessionTTL * 1000;
|
|
429
|
+
const threshold = maxAge * this.refreshThreshold;
|
|
430
|
+
|
|
431
|
+
return age >= threshold;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Delete session
|
|
436
|
+
*/
|
|
437
|
+
async deleteSession(baseURL, username) {
|
|
438
|
+
const key = this.generateSessionKey(baseURL, username);
|
|
439
|
+
await this.store.delete(key);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Clear all sessions
|
|
444
|
+
*/
|
|
445
|
+
async clearAllSessions() {
|
|
446
|
+
await this.store.clear();
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Get session statistics
|
|
451
|
+
*/
|
|
452
|
+
async getStats() {
|
|
453
|
+
if (this.store.getStats) {
|
|
454
|
+
return await this.store.getStats();
|
|
455
|
+
}
|
|
456
|
+
return { message: 'Statistics not available for this store type' };
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Export helper factory functions
|
|
461
|
+
const createSessionManager = (options = {}) => {
|
|
462
|
+
return new SessionManager(options);
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
const createMemoryStore = () => {
|
|
466
|
+
return new MemorySessionStore();
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
const createRedisStore = (redisClient, options = {}) => {
|
|
470
|
+
return new RedisSessionStore(redisClient, options);
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
const createDatabaseStore = (database, options = {}) => {
|
|
474
|
+
return new DatabaseSessionStore(database, options);
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
const createCustomHandler = (handlers) => {
|
|
478
|
+
return new CustomSessionHandler(handlers);
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
module.exports = {
|
|
482
|
+
SessionManager,
|
|
483
|
+
SessionStore,
|
|
484
|
+
MemorySessionStore,
|
|
485
|
+
RedisSessionStore,
|
|
486
|
+
DatabaseSessionStore,
|
|
487
|
+
CustomSessionHandler,
|
|
488
|
+
createSessionManager,
|
|
489
|
+
createMemoryStore,
|
|
490
|
+
createRedisStore,
|
|
491
|
+
createDatabaseStore,
|
|
492
|
+
createCustomHandler
|
|
493
|
+
};
|
package/SECURITY.md
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
# Security Policy
|
|
2
|
-
|
|
3
|
-
## Supported Versions
|
|
4
|
-
|
|
5
|
-
We take security seriously and actively maintain the following versions:
|
|
6
|
-
|
|
7
|
-
| Version | Supported |
|
|
8
|
-
| ------- | ------------------ |
|
|
9
|
-
| 1.0.x | :white_check_mark: |
|
|
10
|
-
| < 1.0 | :x: |
|
|
11
|
-
|
|
12
|
-
## Security Best Practices
|
|
13
|
-
|
|
14
|
-
### For Users
|
|
15
|
-
|
|
16
|
-
**🔒 Server-Side Only Usage**
|
|
17
|
-
- This package is designed for **server-side use only**
|
|
18
|
-
- Never use this package in browser/client-side applications
|
|
19
|
-
- Session cookies contain sensitive authentication data
|
|
20
|
-
|
|
21
|
-
**🛡️ Credential Security**
|
|
22
|
-
- Store credentials in environment variables, not in code
|
|
23
|
-
- Use secure session storage (Redis, Database) for production
|
|
24
|
-
- Implement proper access controls for your server
|
|
25
|
-
|
|
26
|
-
**🔄 Session Management**
|
|
27
|
-
- Sessions expire after 1 hour and are automatically renewed
|
|
28
|
-
- Monitor for unusual authentication patterns
|
|
29
|
-
- Implement rate limiting on your server
|
|
30
|
-
|
|
31
|
-
**📝 Example Secure Implementation:**
|
|
32
|
-
```javascript
|
|
33
|
-
const ThreeXUI = require('3xui-api-client');
|
|
34
|
-
|
|
35
|
-
// ✅ Good: Use environment variables
|
|
36
|
-
const client = new ThreeXUI(
|
|
37
|
-
process.env.XUI_URL,
|
|
38
|
-
process.env.XUI_USERNAME,
|
|
39
|
-
process.env.XUI_PASSWORD
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
// ✅ Good: Store sessions securely
|
|
43
|
-
class SecureXUIManager {
|
|
44
|
-
constructor(database) {
|
|
45
|
-
this.client = client;
|
|
46
|
-
this.db = database;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async ensureAuthenticated() {
|
|
50
|
-
const session = await this.db.getValidSession();
|
|
51
|
-
if (!session) {
|
|
52
|
-
await this.client.login();
|
|
53
|
-
await this.db.storeSession(this.client.cookie);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
## Reporting a Vulnerability
|
|
60
|
-
|
|
61
|
-
We appreciate responsible disclosure of security vulnerabilities.
|
|
62
|
-
|
|
63
|
-
### How to Report
|
|
64
|
-
|
|
65
|
-
1. **Do NOT** create a public GitHub issue for security vulnerabilities
|
|
66
|
-
2. Email security reports to: [your-security-email@example.com]
|
|
67
|
-
3. Include detailed information about the vulnerability
|
|
68
|
-
4. Allow up to 48 hours for initial response
|
|
69
|
-
|
|
70
|
-
### What to Include
|
|
71
|
-
|
|
72
|
-
- **Description**: Clear description of the vulnerability
|
|
73
|
-
- **Impact**: Potential security impact and affected versions
|
|
74
|
-
- **Reproduction**: Steps to reproduce the issue
|
|
75
|
-
- **Fix Suggestion**: If you have ideas for fixes
|
|
76
|
-
|
|
77
|
-
### Our Response Process
|
|
78
|
-
|
|
79
|
-
1. **Acknowledgment**: We'll confirm receipt within 48 hours
|
|
80
|
-
2. **Investigation**: We'll investigate and assess the impact
|
|
81
|
-
3. **Fix Development**: We'll develop and test a fix
|
|
82
|
-
4. **Release**: We'll release a security update
|
|
83
|
-
5. **Disclosure**: We'll publicly disclose after users have time to update
|
|
84
|
-
|
|
85
|
-
### Security Update Process
|
|
86
|
-
|
|
87
|
-
- Security fixes are released as patch versions (e.g., 1.0.1)
|
|
88
|
-
- We'll publish security advisories on GitHub
|
|
89
|
-
- Critical vulnerabilities may trigger emergency releases
|
|
90
|
-
- We'll notify users through multiple channels
|
|
91
|
-
|
|
92
|
-
## Security Features
|
|
93
|
-
|
|
94
|
-
### Built-in Security
|
|
95
|
-
|
|
96
|
-
✅ **Automatic Session Management**
|
|
97
|
-
- Sessions expire after 1 hour
|
|
98
|
-
- Automatic re-authentication on expiry
|
|
99
|
-
- Secure cookie handling
|
|
100
|
-
|
|
101
|
-
✅ **Input Validation**
|
|
102
|
-
- Required parameter validation
|
|
103
|
-
- URL sanitization
|
|
104
|
-
- Error message sanitization
|
|
105
|
-
|
|
106
|
-
✅ **HTTP Security**
|
|
107
|
-
- 30-second request timeouts
|
|
108
|
-
- Connection validation
|
|
109
|
-
- Redirect limits (max 5)
|
|
110
|
-
- Security headers
|
|
111
|
-
|
|
112
|
-
✅ **Error Handling**
|
|
113
|
-
- No sensitive data in error messages
|
|
114
|
-
- Proper exception handling
|
|
115
|
-
- Network error management
|
|
116
|
-
|
|
117
|
-
### Dependencies Security
|
|
118
|
-
|
|
119
|
-
- All dependencies are regularly audited
|
|
120
|
-
- No known vulnerabilities in current dependencies
|
|
121
|
-
- Minimal dependency footprint (only axios)
|
|
122
|
-
|
|
123
|
-
## Security Considerations
|
|
124
|
-
|
|
125
|
-
### Network Security
|
|
126
|
-
- Always use HTTPS for production deployments
|
|
127
|
-
- Implement proper firewall rules
|
|
128
|
-
- Use VPN or private networks when possible
|
|
129
|
-
|
|
130
|
-
### Access Control
|
|
131
|
-
- Limit access to 3x-ui admin accounts
|
|
132
|
-
- Use strong, unique passwords
|
|
133
|
-
- Implement IP whitelisting where possible
|
|
134
|
-
- Monitor access logs regularly
|
|
135
|
-
|
|
136
|
-
### Data Protection
|
|
137
|
-
- Never log credentials or session cookies
|
|
138
|
-
- Implement proper data retention policies
|
|
139
|
-
- Use encryption for stored session data
|
|
140
|
-
- Regular security audits of your implementation
|
|
141
|
-
|
|
142
|
-
## Contact
|
|
143
|
-
|
|
144
|
-
For security-related questions or concerns:
|
|
145
|
-
- GitHub Issues: For non-security bugs only
|
|
146
|
-
- Documentation: Check our [Wiki](https://github.com/iamhelitha/3xui-api-client/wiki) for security guides
|
|
147
|
-
|
|
148
|
-
---
|
|
149
|
-
|
|
150
|
-
**Note**: This security policy applies to the 3xui-api-client library itself. Security of your 3x-ui server and infrastructure remains your responsibility.
|