3xui-api-client 2.0.0 → 2.1.1

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.
@@ -1,362 +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
-
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
362
  module.exports = CredentialGenerator;