@interopio/gateway-server 0.19.4 → 0.21.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 +30 -0
- package/dist/index.cjs +7 -2
- package/dist/index.cjs.map +4 -4
- package/dist/index.js +7 -2
- package/dist/index.js.map +4 -4
- package/dist/tools/index.js +2 -0
- package/dist/tools/index.js.map +7 -0
- package/dist/web/test.js +2 -2
- package/dist/web/test.js.map +3 -3
- package/gateway-server +572 -0
- package/gateway-server.d.ts +111 -7
- package/package.json +13 -6
- package/readme.md +195 -13
- package/types/crypto/argon2.d.ts +127 -0
- package/types/crypto/keygen.d.ts +7 -0
- package/types/crypto/mkcert.d.ts +47 -0
- package/types/tools.d.ts +6 -0
- package/types/web/server.d.ts +12 -5
- package/types/web/test.d.ts +5 -1
package/gateway-server
ADDED
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
`use strict`;
|
|
4
|
+
|
|
5
|
+
import { writeFileSync } from 'node:fs';
|
|
6
|
+
import { parseArgs } from 'node:util';
|
|
7
|
+
import { configure } from '@interopio/gateway/logging/core';
|
|
8
|
+
|
|
9
|
+
// import { GatewayServer } from './src/index.ts';
|
|
10
|
+
// import { mkcert, argon2 } from './src/tools/index.ts';
|
|
11
|
+
|
|
12
|
+
import { GatewayServer } from './dist/index.js';
|
|
13
|
+
import { mkcert, argon2 } from './dist/tools/index.js';
|
|
14
|
+
|
|
15
|
+
function showHelp() {
|
|
16
|
+
console.log(`
|
|
17
|
+
Usage: npx @interopio/gateway-server <command> [options]
|
|
18
|
+
|
|
19
|
+
Commands:
|
|
20
|
+
run Start the gateway server
|
|
21
|
+
passwd Generate password hash (default using Argon2)
|
|
22
|
+
mkcert Generate client and/or server certificate signed by Dev CA
|
|
23
|
+
|
|
24
|
+
run options:
|
|
25
|
+
-p, --port <port> Specify port or port range to bind the server to (default: 0 i.e. random)
|
|
26
|
+
examples: 8385, 8000-8100, 3000,4000-4050
|
|
27
|
+
-H, --host <host> Network address to bind to (default: unspecified, listens on all)
|
|
28
|
+
examples: localhost, 127.0.0.1, 0.0.0.0, ::1
|
|
29
|
+
-u, --user <name> Enables basic authentication and sets the admin username
|
|
30
|
+
--no-auth Disable authentication (overrides config, sets auth.type to 'none')
|
|
31
|
+
-S, --ssl, --tls Enable HTTPS. Auto-generates Dev CA and/or server certificates if not
|
|
32
|
+
present. Enables x509 client cert auth if --user not specified.
|
|
33
|
+
--cert <file> File with SSL/TLS certificate (default: ./gateway-server.crt)
|
|
34
|
+
--key <file> File with SSL/TLS private key (default: ./gateway-server.key)
|
|
35
|
+
--ca <file> File with CA certificates (default: ./gateway-ca.crt)
|
|
36
|
+
--ca-key <file> File with CA private key (default: ./gateway-ca.key)
|
|
37
|
+
--no-ssl, --no-tls Disable SSL/TLS (overrides config)
|
|
38
|
+
-g, --gateway Enable gateway endpoint (default route: /)
|
|
39
|
+
--no-gateway Disable gateway endpoint (overrides config)
|
|
40
|
+
-s, --static <location> Serve static files from specified location (e.g., ./public)
|
|
41
|
+
-c, --config <file> Server configuration file (JSON format)
|
|
42
|
+
--debug Enable debug logging (default: info level)
|
|
43
|
+
|
|
44
|
+
passwd options:
|
|
45
|
+
(no args) Prompt for password interactively (masked input)
|
|
46
|
+
--stdin Read password from stdin (for piping, e.g., echo "pass" | ...)
|
|
47
|
+
|
|
48
|
+
mkcert options:
|
|
49
|
+
--client Generate client certificate (default: server certificate)
|
|
50
|
+
-u, --user <name> Common Name for the certificate (default: dev-user for client certs)
|
|
51
|
+
(--client only)
|
|
52
|
+
--ca <file> File with CA certificate (default: ./gateway-ca.crt)
|
|
53
|
+
--ca-key <file> File with CA private key (default: ./gateway-ca.key)
|
|
54
|
+
--key <file> Output file for private key (default: ./gateway-server.key for server,
|
|
55
|
+
./gateway-client.key for client)
|
|
56
|
+
--cert <file> Output file for certificate (default: ./gateway-server.crt for server,
|
|
57
|
+
./gateway-client.crt for client)
|
|
58
|
+
[name...] DNS names, IP addresses, or email addresses for subjectAltName
|
|
59
|
+
(DNS by default, prefix with 'IP:' or 'EMAIL:' for other types)
|
|
60
|
+
|
|
61
|
+
Global Options:
|
|
62
|
+
-v, --version Show version information and exit
|
|
63
|
+
--help Show this help message and exit
|
|
64
|
+
|
|
65
|
+
Examples:
|
|
66
|
+
gateway-server run -p 3000
|
|
67
|
+
gateway-server run -u admin --port 8385,8388 --debug
|
|
68
|
+
gateway-server run -p 8443 --ssl --user admin --gateway
|
|
69
|
+
gateway-server run --port 42443 --tls --ca-key ./ssl/ca.key --host medes --gateway
|
|
70
|
+
gateway-server run -p 3000 --static ./public --config ./server-config.json
|
|
71
|
+
gateway-server run --config ./server-config.json --no-gateway --no-ssl --no-auth
|
|
72
|
+
gateway-server passwd
|
|
73
|
+
echo "mySecret123" | gateway-server passwd --stdin
|
|
74
|
+
gateway-server mkcert
|
|
75
|
+
gateway-server mkcert localhost 127.0.0.1 IP:192.168.1.100
|
|
76
|
+
gateway-server mkcert --client EMAIL:john.doe@example.com
|
|
77
|
+
gateway-server mkcert example.com *.example.com --key ./gateway-server.key --cert ./gateway-server.crt
|
|
78
|
+
`);
|
|
79
|
+
process.exit(0);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function showVersion() {
|
|
83
|
+
console.log(GatewayServer.VERSION);
|
|
84
|
+
process.exit(0);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Parse command-line arguments
|
|
88
|
+
const { values: globalValues, positionals } = parseArgs({
|
|
89
|
+
args: process.argv.slice(2),
|
|
90
|
+
options: {
|
|
91
|
+
version: { type: 'boolean', short: 'v' },
|
|
92
|
+
help: { type: 'boolean', short: 'h' },
|
|
93
|
+
},
|
|
94
|
+
strict: false,
|
|
95
|
+
allowPositionals: true,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Check for global flags first
|
|
99
|
+
if (globalValues.version) {
|
|
100
|
+
showVersion();
|
|
101
|
+
}
|
|
102
|
+
if (globalValues.help) {
|
|
103
|
+
showHelp();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Determine command (required)
|
|
107
|
+
if (positionals.length === 0) {
|
|
108
|
+
console.error('Error: Command is required');
|
|
109
|
+
console.log('Use --help to see available commands');
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const command = positionals[0];
|
|
114
|
+
|
|
115
|
+
// Route to appropriate command handler
|
|
116
|
+
switch (command) {
|
|
117
|
+
case 'run':
|
|
118
|
+
await runServer();
|
|
119
|
+
break;
|
|
120
|
+
case 'passwd':
|
|
121
|
+
await passwdCommand();
|
|
122
|
+
break;
|
|
123
|
+
case 'mkcert':
|
|
124
|
+
await mkcertCommand();
|
|
125
|
+
break;
|
|
126
|
+
default:
|
|
127
|
+
console.error(`Error: Unknown command "${command}"`);
|
|
128
|
+
console.log('Use --help to see available commands');
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Command: passwd - Generate password hash
|
|
133
|
+
async function passwdCommand() {
|
|
134
|
+
const { values } = parseArgs({
|
|
135
|
+
args: process.argv.slice(3), // Skip 'node', 'gateway-server', and 'passwd'
|
|
136
|
+
options: {
|
|
137
|
+
stdin: { type: 'boolean' },
|
|
138
|
+
},
|
|
139
|
+
strict: true,
|
|
140
|
+
allowPositionals: false,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
let password = undefined;
|
|
144
|
+
|
|
145
|
+
// Read password from stdin if specified
|
|
146
|
+
if (values.stdin) {
|
|
147
|
+
password = await readPasswordFromStdin();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// If no password provided, prompt interactively
|
|
151
|
+
if (!password) {
|
|
152
|
+
password = await promptPassword('Enter password: ');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!password) {
|
|
156
|
+
console.error('Error: password is required');
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const hashValue = await argon2.hash(password);
|
|
161
|
+
const hash = `{argon2id}${hashValue}`;
|
|
162
|
+
console.log(hash);
|
|
163
|
+
process.exit(0);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Read password from piped stdin (non-interactive)
|
|
167
|
+
async function readPasswordFromStdin() {
|
|
168
|
+
// Check if stdin is a TTY (interactive terminal)
|
|
169
|
+
if (process.stdin.isTTY) {
|
|
170
|
+
// Interactive mode - use readline with hidden input
|
|
171
|
+
return promptPassword('Password: ');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Piped mode - read until EOF or newline
|
|
175
|
+
const chunks = [];
|
|
176
|
+
for await (const chunk of process.stdin) {
|
|
177
|
+
chunks.push(chunk);
|
|
178
|
+
}
|
|
179
|
+
const content = Buffer.concat(chunks).toString('utf8');
|
|
180
|
+
// Strip trailing newline
|
|
181
|
+
return content.replace(/\r?\n$/, '');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Prompt for password with masked input
|
|
185
|
+
async function promptPassword(prompt) {
|
|
186
|
+
return new Promise((resolve) => {
|
|
187
|
+
process.stdout.write(prompt);
|
|
188
|
+
|
|
189
|
+
// Only set raw mode if stdin is a TTY
|
|
190
|
+
if (!process.stdin.isTTY) {
|
|
191
|
+
console.error('Error: Interactive password prompt requires a terminal');
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
process.stdin.setRawMode(true);
|
|
196
|
+
process.stdin.resume();
|
|
197
|
+
process.stdin.setEncoding('utf8');
|
|
198
|
+
|
|
199
|
+
let password = '';
|
|
200
|
+
|
|
201
|
+
const cleanup = () => {
|
|
202
|
+
process.stdin.setRawMode(false);
|
|
203
|
+
process.stdin.pause();
|
|
204
|
+
process.stdin.removeListener('data', onData);
|
|
205
|
+
process.stdout.write('\n');
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const onData = (char) => {
|
|
209
|
+
// Ctrl+C
|
|
210
|
+
if (char === '\u0003') {
|
|
211
|
+
cleanup();
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Enter
|
|
216
|
+
if (char === '\r' || char === '\n') {
|
|
217
|
+
cleanup();
|
|
218
|
+
resolve(password);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Backspace
|
|
223
|
+
if (char === '\u007f' || char === '\b') {
|
|
224
|
+
if (password.length > 0) {
|
|
225
|
+
password = password.slice(0, -1);
|
|
226
|
+
process.stdout.write('\b \b');
|
|
227
|
+
}
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Regular character
|
|
232
|
+
password += char;
|
|
233
|
+
process.stdout.write('*');
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
process.stdin.on('data', onData);
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Command: mkcert - Generate client or server certificate
|
|
241
|
+
async function mkcertCommand() {
|
|
242
|
+
const { values, positionals } = parseArgs({
|
|
243
|
+
args: process.argv.slice(3), // Skip 'node', 'gateway-server', and 'mkcert'
|
|
244
|
+
options: {
|
|
245
|
+
client: { type: 'boolean' },
|
|
246
|
+
user: { type: 'string', short: 'u' },
|
|
247
|
+
ca: { type: 'string' },
|
|
248
|
+
'ca-key': { type: 'string' },
|
|
249
|
+
key: { type: 'string' },
|
|
250
|
+
cert: { type: 'string' },
|
|
251
|
+
},
|
|
252
|
+
strict: true,
|
|
253
|
+
allowPositionals: true,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const isClientCert = values.client || false;
|
|
257
|
+
const user = values.user || 'dev-user';
|
|
258
|
+
const caCert = values.ca || './gateway-ca.crt';
|
|
259
|
+
const caKey = values['ca-key'] || './gateway-ca.key';
|
|
260
|
+
let keyPath = values.key;
|
|
261
|
+
let certPath = values.cert;
|
|
262
|
+
const sanEntries = positionals;
|
|
263
|
+
|
|
264
|
+
// Default output paths
|
|
265
|
+
// Users typically specify just --cert, and key should go in the same file
|
|
266
|
+
if (!keyPath && !certPath) {
|
|
267
|
+
// Neither specified: use defaults based on certificate type
|
|
268
|
+
if (isClientCert) {
|
|
269
|
+
// Client certificate: combined file by default
|
|
270
|
+
keyPath = './gateway-client.key';
|
|
271
|
+
certPath = './gateway-client.crt';
|
|
272
|
+
} else {
|
|
273
|
+
// Server certificate: separate files by default
|
|
274
|
+
keyPath = './gateway-server.key';
|
|
275
|
+
certPath = './gateway-server.crt';
|
|
276
|
+
}
|
|
277
|
+
} else if (keyPath && !certPath) {
|
|
278
|
+
// Only --key specified: cert goes in the same file (combined)
|
|
279
|
+
certPath = keyPath;
|
|
280
|
+
} else if (!keyPath && certPath) {
|
|
281
|
+
// Only --cert specified: key goes in the same file (combined)
|
|
282
|
+
keyPath = certPath;
|
|
283
|
+
}
|
|
284
|
+
// else: both specified, use as-is
|
|
285
|
+
|
|
286
|
+
if (sanEntries.length === 0) {
|
|
287
|
+
if (isClientCert) {
|
|
288
|
+
sanEntries.push(user);
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
// Default SAN entry for server certs
|
|
292
|
+
sanEntries.push('localhost');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const { readFileSync } = await import('node:fs');
|
|
297
|
+
const { KEYUTIL, X509 } = await import('jsrsasign');
|
|
298
|
+
|
|
299
|
+
// Load CA key
|
|
300
|
+
let caKeyObj;
|
|
301
|
+
try {
|
|
302
|
+
const caKeyPem = readFileSync(caKey, 'utf8');
|
|
303
|
+
caKeyObj = KEYUTIL.getKey(caKeyPem);
|
|
304
|
+
} catch (error) {
|
|
305
|
+
console.error(`Error: Cannot read CA key from ${caKey}`);
|
|
306
|
+
console.error(error.message);
|
|
307
|
+
process.exit(1);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Extract issuer from CA certificate
|
|
311
|
+
let issuer;
|
|
312
|
+
try {
|
|
313
|
+
const caCertPem = readFileSync(caCert, 'utf8');
|
|
314
|
+
const caCertObj = new X509();
|
|
315
|
+
caCertObj.readCertPEM(caCertPem);
|
|
316
|
+
issuer = caCertObj.getSubjectString();
|
|
317
|
+
} catch (error) {
|
|
318
|
+
console.error(`Error: Cannot read CA certificate from ${caCert}`);
|
|
319
|
+
console.error(error.message);
|
|
320
|
+
process.exit(1);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Generate certificate using SAN entries
|
|
324
|
+
const cert = mkcert.generateCert(caKeyObj, issuer, sanEntries, isClientCert);
|
|
325
|
+
|
|
326
|
+
// Write files
|
|
327
|
+
try {
|
|
328
|
+
if (keyPath === certPath) {
|
|
329
|
+
// Concatenate cert and key into single file (cert first, then key)
|
|
330
|
+
const combined = cert.cert + cert.key;
|
|
331
|
+
writeFileSync(keyPath, combined, { mode: 0o600 });
|
|
332
|
+
console.log(`${isClientCert ? 'Client' : 'Server'} certificate generated successfully:`);
|
|
333
|
+
console.log(` Combined (cert+key): ${keyPath}`);
|
|
334
|
+
if (sanEntries.length > 0) {
|
|
335
|
+
console.log(` SAN: ${sanEntries.join(', ')}`);
|
|
336
|
+
}
|
|
337
|
+
} else {
|
|
338
|
+
// Write separate files
|
|
339
|
+
writeFileSync(keyPath, cert.key, { mode: 0o600 });
|
|
340
|
+
writeFileSync(certPath, cert.cert, { mode: 0o644 });
|
|
341
|
+
console.log(`${isClientCert ? 'Client' : 'Server'} certificate generated successfully:`);
|
|
342
|
+
console.log(` Private key: ${keyPath}`);
|
|
343
|
+
console.log(` Certificate: ${certPath}`);
|
|
344
|
+
if (sanEntries.length > 0) {
|
|
345
|
+
console.log(` SAN: ${sanEntries.join(', ')}`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
process.exit(0);
|
|
350
|
+
} catch (error) {
|
|
351
|
+
console.error(`Error: Cannot write certificate files`);
|
|
352
|
+
console.error(error.message);
|
|
353
|
+
process.exit(1);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Command: run - Start the server
|
|
358
|
+
async function runServer() {
|
|
359
|
+
const { values } = parseArgs({
|
|
360
|
+
args: process.argv.slice(3), // Skip 'node', 'gateway-server', and 'run'
|
|
361
|
+
options: {
|
|
362
|
+
port: { type: 'string', short: 'p' },
|
|
363
|
+
host: { type: 'string', short: 'H' },
|
|
364
|
+
user: { type: 'string', short: 'u' },
|
|
365
|
+
debug: { type: 'boolean' },
|
|
366
|
+
ssl: { type: 'boolean', short: 'S' },
|
|
367
|
+
tls: { type: 'boolean' },
|
|
368
|
+
cert: { type: 'string' },
|
|
369
|
+
key: { type: 'string' },
|
|
370
|
+
ca: { type: 'string' },
|
|
371
|
+
'ca-key': { type: 'string' },
|
|
372
|
+
config: { type: 'string', short: 'c' },
|
|
373
|
+
gateway: { type: 'boolean', short: 'g' },
|
|
374
|
+
static: { type: 'string', short: 's', multiple: true },
|
|
375
|
+
auth: { type: 'boolean' }
|
|
376
|
+
},
|
|
377
|
+
strict: true,
|
|
378
|
+
allowNegative: true,
|
|
379
|
+
allowPositionals: false,
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
const options = {
|
|
383
|
+
port: values.port,
|
|
384
|
+
host: values.host,
|
|
385
|
+
user: values.user,
|
|
386
|
+
debug: values.debug || false,
|
|
387
|
+
ssl: values.ssl || values.tls || false,
|
|
388
|
+
noSsl: values.ssl === false, // --no-ssl explicitly set
|
|
389
|
+
cert: values.cert,
|
|
390
|
+
key: values.key,
|
|
391
|
+
ca: values.ca,
|
|
392
|
+
caKey: values['ca-key'],
|
|
393
|
+
config: values.config,
|
|
394
|
+
gateway: values.gateway,
|
|
395
|
+
noGateway: values.gateway === false, // --no-gateway explicitly set
|
|
396
|
+
static: values.static,
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
configure({
|
|
400
|
+
level: options.debug ? 'debug' : 'info',
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// Load server configuration from file if specified
|
|
404
|
+
let serverConfig = {};
|
|
405
|
+
if (options.config) {
|
|
406
|
+
try {
|
|
407
|
+
const { readFileSync } = await import('node:fs');
|
|
408
|
+
const configContent = readFileSync(options.config, 'utf8');
|
|
409
|
+
serverConfig = JSON.parse(configContent);
|
|
410
|
+
} catch (error) {
|
|
411
|
+
console.error(`Error: Cannot read server config from ${options.config}`);
|
|
412
|
+
console.error(error.message);
|
|
413
|
+
process.exit(1);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Apply CLI overrides to server config
|
|
418
|
+
// CLI arguments take precedence over config file
|
|
419
|
+
|
|
420
|
+
// Override port if specified
|
|
421
|
+
if (values.port !== undefined) {
|
|
422
|
+
serverConfig.port = options.port;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Override host if specified
|
|
426
|
+
if (values.host !== undefined) {
|
|
427
|
+
serverConfig.host = options.host;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Override SSL configuration
|
|
431
|
+
if (options.noSsl) {
|
|
432
|
+
// --no-ssl: delete SSL config completely
|
|
433
|
+
delete serverConfig.ssl;
|
|
434
|
+
options.ssl = false;
|
|
435
|
+
} else if (options.ssl) {
|
|
436
|
+
// --ssl specified: merge CLI args with existing config
|
|
437
|
+
if (!serverConfig.ssl) {
|
|
438
|
+
serverConfig.ssl = {};
|
|
439
|
+
}
|
|
440
|
+
// Only set properties if CLI options are explicitly provided
|
|
441
|
+
if (options.key !== undefined) {
|
|
442
|
+
serverConfig.ssl.key = options.key;
|
|
443
|
+
}
|
|
444
|
+
if (options.cert !== undefined) {
|
|
445
|
+
serverConfig.ssl.cert = options.cert;
|
|
446
|
+
}
|
|
447
|
+
if (options.ca !== undefined) {
|
|
448
|
+
serverConfig.ssl.ca = options.ca;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
// else: use whatever is in serverConfig (if any)
|
|
452
|
+
|
|
453
|
+
// Determine effective SSL state
|
|
454
|
+
const effectiveSsl = options.ssl || (!options.noSsl && serverConfig.ssl !== undefined);
|
|
455
|
+
|
|
456
|
+
// Configure authentication
|
|
457
|
+
let auth;
|
|
458
|
+
if (values.auth === false) {
|
|
459
|
+
// --no-auth: delete auth config from server config and set to 'none'
|
|
460
|
+
delete serverConfig.auth;
|
|
461
|
+
auth = { type: 'none' };
|
|
462
|
+
}
|
|
463
|
+
else if (serverConfig.auth) {
|
|
464
|
+
// Use auth from config file if present
|
|
465
|
+
auth = serverConfig.auth;
|
|
466
|
+
|
|
467
|
+
// Allow --user CLI option to override auth.user.name from config
|
|
468
|
+
if (options.user) {
|
|
469
|
+
if (!auth.user) {
|
|
470
|
+
auth.user = {};
|
|
471
|
+
}
|
|
472
|
+
auth.user.name = options.user;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
// No auth in config file, determine from CLI args or defaults
|
|
477
|
+
auth = {
|
|
478
|
+
type: options.user ? 'basic' : effectiveSsl ? 'x509' : 'none'
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
if (options.user) {
|
|
482
|
+
auth.user = { name: options.user };
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (effectiveSsl && auth.type === 'x509') {
|
|
486
|
+
auth.x509 = {
|
|
487
|
+
key: options.caKey || './gateway-ca.key',
|
|
488
|
+
// principalAltName: 'email',
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
// Ensure SSL config exists and has proper mutual TLS settings
|
|
492
|
+
if (!serverConfig.ssl) {
|
|
493
|
+
serverConfig.ssl = {};
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Enable client certificate verification with proper mutual TLS
|
|
497
|
+
serverConfig.ssl.requestCert = true;
|
|
498
|
+
|
|
499
|
+
// Enforce trust verification to prevent certificate spoofing
|
|
500
|
+
// rejectUnauthorized must be true to ensure only trusted client certs are accepted
|
|
501
|
+
serverConfig.ssl.rejectUnauthorized = true;
|
|
502
|
+
|
|
503
|
+
// Set CA for client certificate verification (required for mutual TLS)
|
|
504
|
+
// Use the same CA that signs client certificates
|
|
505
|
+
if (!serverConfig.ssl.ca) {
|
|
506
|
+
serverConfig.ssl.ca = options.ca || './gateway-ca.crt';
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Handle SSL certificate auto-generation when --ssl is specified via CLI
|
|
512
|
+
// but config file doesn't have server certificates
|
|
513
|
+
if (options.ssl && effectiveSsl && serverConfig.ssl) {
|
|
514
|
+
const hasCert = serverConfig.ssl.cert || serverConfig.ssl.key;
|
|
515
|
+
if (!hasCert) {
|
|
516
|
+
// SSL enabled but no server cert/key provided
|
|
517
|
+
// We need a CA key for auto-generating server certificates
|
|
518
|
+
// Set default CA key path if not already configured
|
|
519
|
+
if (!auth.x509) {
|
|
520
|
+
auth.x509 = {};
|
|
521
|
+
}
|
|
522
|
+
if (!auth.x509.key) {
|
|
523
|
+
// Use CLI option, or default to ./gateway-ca.key
|
|
524
|
+
auth.x509.key = options.caKey || './gateway-ca.key';
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Override gateway configuration
|
|
530
|
+
if (options.noGateway) {
|
|
531
|
+
// --no-gateway: delete gateway config completely
|
|
532
|
+
delete serverConfig.gateway;
|
|
533
|
+
} else if (options.gateway) {
|
|
534
|
+
// --gateway specified: enable gateway endpoint, respect existing config
|
|
535
|
+
if (!serverConfig.gateway) {
|
|
536
|
+
serverConfig.gateway = {};
|
|
537
|
+
}
|
|
538
|
+
// Set defaults only if not already present in config
|
|
539
|
+
if (!serverConfig.gateway.route) {
|
|
540
|
+
serverConfig.gateway.route = '/';
|
|
541
|
+
}
|
|
542
|
+
if (!serverConfig.gateway.authorize) {
|
|
543
|
+
serverConfig.gateway.authorize = { access: auth.type !== 'none' ? 'authenticated' : 'permitted' };
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
// else: use whatever is in serverConfig (if any)
|
|
547
|
+
|
|
548
|
+
// Override static resources if specified
|
|
549
|
+
if (options.static && options.static.length > 0) {
|
|
550
|
+
if (!serverConfig.resources) {
|
|
551
|
+
serverConfig.resources = {};
|
|
552
|
+
}
|
|
553
|
+
serverConfig.resources.locations = options.static;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Set auth in server config
|
|
557
|
+
serverConfig.auth = auth;
|
|
558
|
+
|
|
559
|
+
const server = await GatewayServer.Factory({
|
|
560
|
+
...(serverConfig),
|
|
561
|
+
app: async (_configurer) => {
|
|
562
|
+
// No custom app logic for now
|
|
563
|
+
},
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
process.on('SIGINT', async () => {
|
|
567
|
+
await server.close();
|
|
568
|
+
process.exit(0);
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
// process.title = `${GatewayServer.VERSION} ${server.address.port}`;
|
|
572
|
+
}
|