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.
@@ -0,0 +1,362 @@
1
+ const crypto = require('crypto');
2
+
3
+ /**
4
+ * Comprehensive credential generator for all 3x-ui supported protocols
5
+ * Based on research from https://github.com/MHSanaei/3x-ui
6
+ */
7
+ class CredentialGenerator {
8
+ /**
9
+ * Generate UUID v4 for VLESS and VMess protocols
10
+ * Format: ecc322b8-a458-4583-ac98-e343aefb5ac5
11
+ * @returns {string} UUID v4 string
12
+ */
13
+ static generateUUID() {
14
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
15
+ const r = Math.random() * 16 | 0;
16
+ const v = c === 'x' ? r : (r & 0x3 | 0x8);
17
+ return v.toString(16);
18
+ });
19
+ }
20
+
21
+ /**
22
+ * Generate secure UUID using crypto module (more secure alternative)
23
+ * @returns {string} Cryptographically secure UUID v4
24
+ */
25
+ static generateSecureUUID() {
26
+ const bytes = crypto.randomBytes(16);
27
+ bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4
28
+ bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant 10
29
+
30
+ const hex = bytes.toString('hex');
31
+ return [
32
+ hex.slice(0, 8),
33
+ hex.slice(8, 12),
34
+ hex.slice(12, 16),
35
+ hex.slice(16, 20),
36
+ hex.slice(20, 32)
37
+ ].join('-');
38
+ }
39
+
40
+ /**
41
+ * Generate random password for Trojan and Shadowsocks protocols
42
+ * @param {number} length - Password length (default: 16)
43
+ * @param {Object} options - Password generation options
44
+ * @returns {string} Random password
45
+ */
46
+ static generatePassword(length = 16, options = {}) {
47
+ const {
48
+ includeUppercase = true,
49
+ includeLowercase = true,
50
+ includeNumbers = true,
51
+ includeSymbols = false,
52
+ excludeSimilar = true
53
+ } = options;
54
+
55
+ let chars = '';
56
+ if (includeUppercase) {
57
+ chars += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
58
+ }
59
+ if (includeLowercase) {
60
+ chars += 'abcdefghijklmnopqrstuvwxyz';
61
+ }
62
+ if (includeNumbers) {
63
+ chars += '0123456789';
64
+ }
65
+ if (includeSymbols) {
66
+ chars += '!@#$%^&*()_+-=[]{}|;:,.<>?';
67
+ }
68
+
69
+ // Exclude similar looking characters if requested
70
+ if (excludeSimilar) {
71
+ chars = chars.replace(/[0O1lI]/g, '');
72
+ }
73
+
74
+ if (chars.length === 0) {
75
+ throw new Error('No character sets selected for password generation');
76
+ }
77
+
78
+ return Array.from(crypto.randomBytes(length))
79
+ .map(byte => chars[byte % chars.length])
80
+ .join('');
81
+ }
82
+
83
+ /**
84
+ * Generate Shadowsocks2022 PSK (Pre-Shared Key)
85
+ * @param {string} method - Cipher method
86
+ * @returns {string} Base64-encoded PSK
87
+ */
88
+ static generateShadowsocks2022PSK(method = '2022-blake3-aes-256-gcm') {
89
+ const keyLengths = {
90
+ '2022-blake3-aes-128-gcm': 16,
91
+ '2022-blake3-aes-256-gcm': 32,
92
+ '2022-blake3-chacha20-poly1305': 32
93
+ };
94
+
95
+ const keyLength = keyLengths[method];
96
+ if (!keyLength) {
97
+ throw new Error(`Unsupported Shadowsocks2022 method: ${method}`);
98
+ }
99
+
100
+ const keyBytes = crypto.randomBytes(keyLength);
101
+ return keyBytes.toString('base64');
102
+ }
103
+
104
+ /**
105
+ * Generate WireGuard key pair
106
+ * @returns {Object} Object containing private and public keys
107
+ */
108
+ static generateWireGuardKeys() {
109
+ // Generate 32 random bytes for private key
110
+ const privateKeyBytes = crypto.randomBytes(32);
111
+
112
+ // Clamp the private key (standard WireGuard key clamping)
113
+ privateKeyBytes[0] &= 248;
114
+ privateKeyBytes[31] &= 127;
115
+ privateKeyBytes[31] |= 64;
116
+
117
+ const privateKey = privateKeyBytes.toString('base64');
118
+
119
+ // For the public key, we would normally use Curve25519 scalar multiplication
120
+ // This is a simplified implementation - in production, use a proper crypto library
121
+ // like noble-curves or libsodium for accurate public key derivation
122
+ const publicKeyBytes = crypto.randomBytes(32); // Placeholder
123
+ const publicKey = publicKeyBytes.toString('base64');
124
+
125
+ return {
126
+ privateKey,
127
+ publicKey,
128
+ // Helper method to generate client config
129
+ generateClientConfig: (allowedIPs = ['10.0.0.2/32']) => ({
130
+ privateKey,
131
+ publicKey,
132
+ allowedIPs,
133
+ keepAlive: 25
134
+ })
135
+ };
136
+ }
137
+
138
+ /**
139
+ * Generate Reality key pair for anti-censorship
140
+ * @returns {Object} Object containing private and public keys
141
+ */
142
+ static generateRealityKeys() {
143
+ // Reality uses X25519 key exchange
144
+ const privateKeyBytes = crypto.randomBytes(32);
145
+ const privateKey = privateKeyBytes.toString('base64').replace(/\//g, '_').replace(/\+/g, '-');
146
+
147
+ // Public key derivation (simplified - use proper X25519 in production)
148
+ const publicKeyBytes = crypto.randomBytes(32);
149
+ const publicKey = publicKeyBytes.toString('base64').replace(/\//g, '_').replace(/\+/g, '-');
150
+
151
+ return {
152
+ privateKey,
153
+ publicKey,
154
+ // Helper method to generate Reality config
155
+ generateRealityConfig: (dest = 'google.com:443', serverNames = ['google.com']) => ({
156
+ show: false,
157
+ dest,
158
+ xver: 0,
159
+ serverNames,
160
+ privateKey,
161
+ shortIds: [''],
162
+ settings: {
163
+ publicKey,
164
+ fingerprint: 'chrome'
165
+ }
166
+ })
167
+ };
168
+ }
169
+
170
+ /**
171
+ * Generate username for SOCKS5/HTTP protocols
172
+ * @param {string} prefix - Optional prefix for username
173
+ * @returns {string} Random username
174
+ */
175
+ static generateUsername(prefix = 'user') {
176
+ const randomSuffix = Math.random().toString(36).substring(2, 8);
177
+ return `${prefix}_${randomSuffix}`;
178
+ }
179
+
180
+ /**
181
+ * Generate client identifier for client identification (not a real email)
182
+ * @param {string} prefix - Optional prefix for identifier (default: 'client')
183
+ * @returns {string} Random client identifier
184
+ */
185
+ static generateClientIdentifier(prefix = 'client') {
186
+ const randomSuffix = Math.random().toString(36).substring(2, 8);
187
+ const timestamp = Date.now().toString().slice(-4);
188
+ return `${prefix}_${randomSuffix}${timestamp}`;
189
+ }
190
+
191
+ /**
192
+ * Get recommended cipher method for Shadowsocks
193
+ * @returns {string} Recommended cipher method
194
+ */
195
+ static getRecommendedShadowsocksCipher() {
196
+ return 'chacha20-ietf-poly1305'; // AEAD cipher, most secure and performant
197
+ }
198
+
199
+ /**
200
+ * Get available cipher methods for Shadowsocks
201
+ * @returns {Array} Array of supported cipher methods
202
+ */
203
+ static getShadowsocksCipherMethods() {
204
+ return [
205
+ 'chacha20-ietf-poly1305', // Recommended
206
+ 'aes-256-gcm',
207
+ 'aes-128-gcm',
208
+ 'chacha20-poly1305'
209
+ ];
210
+ }
211
+
212
+ /**
213
+ * Generate port number within safe range
214
+ * @param {number} min - Minimum port (default: 10000)
215
+ * @param {number} max - Maximum port (default: 65535)
216
+ * @returns {number} Random port number
217
+ */
218
+ static generatePort(min = 10000, max = 65535) {
219
+ return Math.floor(Math.random() * (max - min + 1)) + min;
220
+ }
221
+
222
+ /**
223
+ * Generate short ID for Reality protocol
224
+ * @returns {string} Short ID (hex string)
225
+ */
226
+ static generateRealityShortId() {
227
+ const length = Math.floor(Math.random() * 16) + 1; // 1-16 characters
228
+ return crypto.randomBytes(Math.ceil(length / 2))
229
+ .toString('hex')
230
+ .substring(0, length);
231
+ }
232
+
233
+ /**
234
+ * Generate complete credential set for a specific protocol
235
+ * @param {string} protocol - Protocol name
236
+ * @param {Object} options - Protocol-specific options
237
+ * @returns {Object} Complete credential set
238
+ */
239
+ static generateForProtocol(protocol, options = {}) {
240
+ const email = options.email || this.generateClientIdentifier();
241
+
242
+ switch (protocol.toLowerCase()) {
243
+ case 'vless':
244
+ return {
245
+ id: this.generateSecureUUID(),
246
+ email,
247
+ flow: options.flow || 'xtls-rprx-vision',
248
+ encryption: 'none'
249
+ };
250
+
251
+ case 'vmess':
252
+ return {
253
+ id: this.generateSecureUUID(),
254
+ email,
255
+ level: options.level || 0,
256
+ alterId: options.alterId || 0
257
+ };
258
+
259
+ case 'trojan':
260
+ return {
261
+ password: this.generatePassword(options.passwordLength || 16),
262
+ email,
263
+ level: options.level || 0
264
+ };
265
+
266
+ case 'shadowsocks':
267
+ return {
268
+ method: options.method || this.getRecommendedShadowsocksCipher(),
269
+ password: this.generatePassword(options.passwordLength || 16),
270
+ email
271
+ };
272
+
273
+ case 'shadowsocks2022': {
274
+ const method = options.method || '2022-blake3-aes-256-gcm';
275
+ return {
276
+ method,
277
+ password: this.generateShadowsocks2022PSK(method),
278
+ email
279
+ };
280
+ }
281
+
282
+ case 'wireguard': {
283
+ const keys = this.generateWireGuardKeys();
284
+ return {
285
+ privateKey: keys.privateKey,
286
+ publicKey: keys.publicKey,
287
+ allowedIPs: options.allowedIPs || ['10.0.0.2/32'],
288
+ keepAlive: options.keepAlive || 25
289
+ };
290
+ }
291
+
292
+ case 'socks5':
293
+ case 'http':
294
+ return {
295
+ user: options.username || this.generateUsername(),
296
+ pass: this.generatePassword(options.passwordLength || 12),
297
+ email
298
+ };
299
+
300
+ case 'dokodemo-door':
301
+ return {
302
+ // No authentication required
303
+ email
304
+ };
305
+
306
+ default:
307
+ throw new Error(`Unsupported protocol: ${protocol}`);
308
+ }
309
+ }
310
+
311
+ /**
312
+ * Generate bulk credentials for multiple clients
313
+ * @param {string} protocol - Protocol name
314
+ * @param {number} count - Number of credentials to generate
315
+ * @param {Object} options - Generation options
316
+ * @returns {Array} Array of credential objects
317
+ */
318
+ static generateBulk(protocol, count, options = {}) {
319
+ return Array.from({ length: count }, () =>
320
+ this.generateForProtocol(protocol, options)
321
+ );
322
+ }
323
+
324
+ /**
325
+ * Validate generated credentials
326
+ * @param {Object} credentials - Credentials to validate
327
+ * @param {string} protocol - Protocol name
328
+ * @returns {Object} Validation result
329
+ */
330
+ static validateCredentials(credentials, protocol) {
331
+ const errors = [];
332
+
333
+ switch (protocol.toLowerCase()) {
334
+ case 'vless':
335
+ case 'vmess':
336
+ if (!credentials.id || !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(credentials.id)) {
337
+ errors.push('Invalid UUID format');
338
+ }
339
+ break;
340
+
341
+ case 'trojan':
342
+ case 'shadowsocks':
343
+ if (!credentials.password || credentials.password.length < 8) {
344
+ errors.push('Password too short (minimum 8 characters)');
345
+ }
346
+ break;
347
+
348
+ case 'wireguard':
349
+ if (!credentials.privateKey || !credentials.publicKey) {
350
+ errors.push('Missing key pair');
351
+ }
352
+ break;
353
+ }
354
+
355
+ return {
356
+ valid: errors.length === 0,
357
+ errors
358
+ };
359
+ }
360
+ }
361
+
362
+ module.exports = CredentialGenerator;
@@ -0,0 +1,311 @@
1
+ /**
2
+ * Web Middleware for Express.js and Next.js integration
3
+ * Provides session management and authentication handling for web applications
4
+ */
5
+
6
+ /* global fetch */
7
+
8
+ const ThreeXUI = require('../../index');
9
+ const { createSessionManager } = require('../session/SessionManager');
10
+
11
+ /**
12
+ * Express.js middleware for 3x-ui API integration
13
+ * @param {Object} config - Middleware configuration
14
+ * @param {string} config.baseURL - 3x-ui server URL
15
+ * @param {string} config.username - Admin username
16
+ * @param {string} config.password - Admin password
17
+ * @param {Object} config.sessionManager - Session manager configuration
18
+ * @param {string} config.sessionKey - Session key name (default: '3xui_client')
19
+ * @returns {Function} Express middleware function
20
+ */
21
+ function createExpressMiddleware(config) {
22
+ if (!config.baseURL || !config.username || !config.password) {
23
+ throw new Error('baseURL, username, and password are required');
24
+ }
25
+
26
+ const sessionKey = config.sessionKey || '3xui_client';
27
+ const sessionManager = config.sessionManager || createSessionManager();
28
+
29
+ return async(req, res, next) => {
30
+ try {
31
+ // Check if client already exists in request
32
+ if (req[sessionKey]) {
33
+ return next();
34
+ }
35
+
36
+ // Create or retrieve 3x-ui client
37
+ const client = new ThreeXUI(
38
+ config.baseURL,
39
+ config.username,
40
+ config.password,
41
+ { sessionManager }
42
+ );
43
+
44
+ // Ensure authentication
45
+ await client.login();
46
+
47
+ // Attach client to request object
48
+ req[sessionKey] = client;
49
+
50
+ // Add helper methods to response
51
+ res.generate3xuiCredentials = (protocol, options = {}) => {
52
+ return client.generateCredentials(protocol, options);
53
+ };
54
+
55
+ res.add3xuiClient = async(inboundId, protocol, options = {}) => {
56
+ return await client.addClientWithCredentials(inboundId, protocol, options);
57
+ };
58
+
59
+ next();
60
+ } catch (error) {
61
+ console.error('3x-ui middleware error:', error);
62
+ res.status(500).json({
63
+ error: '3x-ui authentication failed',
64
+ message: error.message
65
+ });
66
+ }
67
+ };
68
+ }
69
+
70
+ /**
71
+ * Next.js API route handler wrapper
72
+ * @param {Object} config - Configuration object
73
+ * @param {Function} handler - API route handler function
74
+ * @returns {Function} Wrapped Next.js API handler
75
+ */
76
+ function withThreeXUI(config, handler) {
77
+ if (!config.baseURL || !config.username || !config.password) {
78
+ throw new Error('baseURL, username, and password are required');
79
+ }
80
+
81
+ const sessionManager = config.sessionManager || createSessionManager();
82
+ const client = new ThreeXUI(
83
+ config.baseURL,
84
+ config.username,
85
+ config.password,
86
+ { sessionManager }
87
+ );
88
+
89
+ return async(req, res) => {
90
+ try {
91
+ // Ensure authentication
92
+ await client.login();
93
+
94
+ // Attach client and helper methods to request
95
+ req.threeXUI = client;
96
+ req.generateCredentials = (protocol, options = {}) => {
97
+ return client.generateCredentials(protocol, options);
98
+ };
99
+ req.addClientWithCredentials = async(inboundId, protocol, options = {}) => {
100
+ return await client.addClientWithCredentials(inboundId, protocol, options);
101
+ };
102
+
103
+ // Call the original handler
104
+ return await handler(req, res);
105
+ } catch (error) {
106
+ console.error('3x-ui Next.js wrapper error:', error);
107
+ return res.status(500).json({
108
+ error: '3x-ui authentication failed',
109
+ message: error.message
110
+ });
111
+ }
112
+ };
113
+ }
114
+
115
+ /**
116
+ * React Hook for client-side 3x-ui integration
117
+ * Note: This requires server-side proxy endpoints for security
118
+ */
119
+ function createReactHook(apiEndpoint = '/api/3xui') {
120
+ return {
121
+ /**
122
+ * Generate credentials via API call
123
+ * @param {string} protocol - Protocol name
124
+ * @param {Object} options - Generation options
125
+ * @returns {Promise<Object>} Generated credentials
126
+ */
127
+ async generateCredentials(protocol, options = {}) {
128
+ const response = await fetch(`${apiEndpoint}/generate-credentials`, {
129
+ method: 'POST',
130
+ headers: {
131
+ 'Content-Type': 'application/json'
132
+ },
133
+ body: JSON.stringify({ protocol, options })
134
+ });
135
+
136
+ if (!response.ok) {
137
+ throw new Error(`HTTP error! status: ${response.status}`);
138
+ }
139
+
140
+ return await response.json();
141
+ },
142
+
143
+ /**
144
+ * Add client with credentials via API call
145
+ * @param {number} inboundId - Inbound ID
146
+ * @param {string} protocol - Protocol type
147
+ * @param {Object} options - Client options
148
+ * @returns {Promise<Object>} Created client
149
+ */
150
+ async addClientWithCredentials(inboundId, protocol, options = {}) {
151
+ const response = await fetch(`${apiEndpoint}/add-client`, {
152
+ method: 'POST',
153
+ headers: {
154
+ 'Content-Type': 'application/json'
155
+ },
156
+ body: JSON.stringify({ inboundId, protocol, options })
157
+ });
158
+
159
+ if (!response.ok) {
160
+ throw new Error(`HTTP error! status: ${response.status}`);
161
+ }
162
+
163
+ return await response.json();
164
+ },
165
+
166
+ /**
167
+ * Get inbounds list via API call
168
+ * @returns {Promise<Array>} Inbounds list
169
+ */
170
+ async getInbounds() {
171
+ const response = await fetch(`${apiEndpoint}/inbounds`);
172
+
173
+ if (!response.ok) {
174
+ throw new Error(`HTTP error! status: ${response.status}`);
175
+ }
176
+
177
+ return await response.json();
178
+ },
179
+
180
+ /**
181
+ * Get client traffic information
182
+ * @param {string} email - Client email
183
+ * @returns {Promise<Object>} Traffic information
184
+ */
185
+ async getClientTraffic(email) {
186
+ const response = await fetch(`${apiEndpoint}/client-traffic/${encodeURIComponent(email)}`);
187
+
188
+ if (!response.ok) {
189
+ throw new Error(`HTTP error! status: ${response.status}`);
190
+ }
191
+
192
+ return await response.json();
193
+ }
194
+ };
195
+ }
196
+
197
+ /**
198
+ * Create session management configuration helpers
199
+ */
200
+ const SessionConfig = {
201
+ /**
202
+ * Memory-based session configuration (development)
203
+ * @returns {Object} Memory session config
204
+ */
205
+ memory() {
206
+ return {
207
+ sessionManager: createSessionManager()
208
+ };
209
+ },
210
+
211
+ /**
212
+ * Redis-based session configuration (production)
213
+ * @param {Object} redisClient - Redis client instance
214
+ * @param {Object} options - Redis options
215
+ * @returns {Object} Redis session config
216
+ */
217
+ redis(redisClient, options = {}) {
218
+ return {
219
+ sessionManager: createSessionManager({
220
+ redis: redisClient,
221
+ redisOptions: options
222
+ })
223
+ };
224
+ },
225
+
226
+ /**
227
+ * Database-based session configuration
228
+ * @param {Object} database - Database client instance
229
+ * @param {Object} options - Database options
230
+ * @returns {Object} Database session config
231
+ */
232
+ database(database, options = {}) {
233
+ return {
234
+ sessionManager: createSessionManager({
235
+ database: database,
236
+ databaseOptions: options
237
+ })
238
+ };
239
+ },
240
+
241
+ /**
242
+ * Custom session handler configuration
243
+ * @param {Object} handlers - Custom handler functions
244
+ * @returns {Object} Custom session config
245
+ */
246
+ custom(handlers) {
247
+ return {
248
+ sessionManager: createSessionManager({
249
+ customHandler: handlers
250
+ })
251
+ };
252
+ }
253
+ };
254
+
255
+ /**
256
+ * Example Next.js API routes generator
257
+ */
258
+ const createNextjsRoutes = (config) => {
259
+ return {
260
+ // GET /api/3xui/inbounds
261
+ inbounds: withThreeXUI(config, async(req, res) => {
262
+ try {
263
+ const inbounds = await req.threeXUI.getInbounds();
264
+ res.status(200).json(inbounds);
265
+ } catch (error) {
266
+ res.status(500).json({ error: error.message });
267
+ }
268
+ }),
269
+
270
+ // POST /api/3xui/generate-credentials
271
+ generateCredentials: withThreeXUI(config, async(req, res) => {
272
+ try {
273
+ const { protocol, options } = req.body;
274
+ const credentials = req.generateCredentials(protocol, options);
275
+ res.status(200).json(credentials);
276
+ } catch (error) {
277
+ res.status(500).json({ error: error.message });
278
+ }
279
+ }),
280
+
281
+ // POST /api/3xui/add-client
282
+ addClient: withThreeXUI(config, async(req, res) => {
283
+ try {
284
+ const { inboundId, protocol, options } = req.body;
285
+ const result = await req.addClientWithCredentials(inboundId, protocol, options);
286
+ res.status(200).json(result);
287
+ } catch (error) {
288
+ res.status(500).json({ error: error.message });
289
+ }
290
+ }),
291
+
292
+ // GET /api/3xui/client-traffic/[email]
293
+ clientTraffic: withThreeXUI(config, async(req, res) => {
294
+ try {
295
+ const { email } = req.query;
296
+ const traffic = await req.threeXUI.getClientTrafficsByEmail(email);
297
+ res.status(200).json(traffic);
298
+ } catch (error) {
299
+ res.status(500).json({ error: error.message });
300
+ }
301
+ })
302
+ };
303
+ };
304
+
305
+ module.exports = {
306
+ createExpressMiddleware,
307
+ withThreeXUI,
308
+ createReactHook,
309
+ SessionConfig,
310
+ createNextjsRoutes
311
+ };
@@ -0,0 +1 @@
1
+