Merge pull request #3478 from Dakraid/feature/set-listen-ip Feature: Allow user to configure an address to listen to

96d6a6df07a6ba945fc37f9eb5f9a01a8c7ae37b

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
4 files changed, +42 -8Ignore whitespace
default/config.yaml+4 -0
@@ -6,6 +6,10 @@ cardsCacheCapacity: 100
6# -- SERVER CONFIGURATION --6# -- SERVER CONFIGURATION --
7# Listen for incoming connections7# Listen for incoming connections
8listen: false8listen: false
9# Listen on a specific address, supports IPv4 and IPv6
10listenAddress:
11 ipv4: 0.0.0.0
12 ipv6: '[::]'
9# Enables IPv6 and/or IPv4 protocols. Need to have at least one enabled!13# Enables IPv6 and/or IPv4 protocols. Need to have at least one enabled!
10# - Use option "auto" to automatically detect support14# - Use option "auto" to automatically detect support
11# - Use true or false (no qoutes) to enable or disable each protocol15# - Use true or false (no qoutes) to enable or disable each protocol
package-lock.json+13 -0
@@ -41,6 +41,7 @@
41 "html-entities": "^2.5.2",41 "html-entities": "^2.5.2",
42 "iconv-lite": "^0.6.3",42 "iconv-lite": "^0.6.3",
43 "ip-matching": "^2.1.2",43 "ip-matching": "^2.1.2",
44 "ip-regex": "^5.0.0",
44 "ipaddr.js": "^2.0.1",45 "ipaddr.js": "^2.0.1",
45 "jimp": "^0.22.10",46 "jimp": "^0.22.10",
46 "localforage": "^1.10.0",47 "localforage": "^1.10.0",
@@ -4628,6 +4629,18 @@
4628 "integrity": "sha512-/ok+VhKMasgR5gvTRViwRFQfc0qYt9Vdowg6TO4/pFlDCob5ZjGPkwuOoQVCd5OrMm20zqh+1vA8KLJZTeWudg==",4629 "integrity": "sha512-/ok+VhKMasgR5gvTRViwRFQfc0qYt9Vdowg6TO4/pFlDCob5ZjGPkwuOoQVCd5OrMm20zqh+1vA8KLJZTeWudg==",
4629 "license": "LGPL-3.0-only"4630 "license": "LGPL-3.0-only"
4630 },4631 },
4632 "node_modules/ip-regex": {
4633 "version": "5.0.0",
4634 "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-5.0.0.tgz",
4635 "integrity": "sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw==",
4636 "license": "MIT",
4637 "engines": {
4638 "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
4639 },
4640 "funding": {
4641 "url": "https://github.com/sponsors/sindresorhus"
4642 }
4643 },
4631 "node_modules/ipaddr.js": {4644 "node_modules/ipaddr.js": {
4632 "version": "2.1.0",4645 "version": "2.1.0",
4633 "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz",4646 "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz",
package.json+2 -0
@@ -31,6 +31,7 @@
31 "html-entities": "^2.5.2",31 "html-entities": "^2.5.2",
32 "iconv-lite": "^0.6.3",32 "iconv-lite": "^0.6.3",
33 "ip-matching": "^2.1.2",33 "ip-matching": "^2.1.2",
34 "ip-regex": "^5.0.0",
34 "ipaddr.js": "^2.0.1",35 "ipaddr.js": "^2.0.1",
35 "jimp": "^0.22.10",36 "jimp": "^0.22.10",
36 "localforage": "^1.10.0",37 "localforage": "^1.10.0",
@@ -89,6 +90,7 @@
89 "version": "1.12.11",90 "version": "1.12.11",
90 "scripts": {91 "scripts": {
91 "start": "node server.js",92 "start": "node server.js",
93 "debug": "node server.js --inspect",
92 "start:deno": "deno run --allow-run --allow-net --allow-read --allow-write --allow-sys --allow-env server.js",94 "start:deno": "deno run --allow-run --allow-net --allow-read --allow-write --allow-sys --allow-env server.js",
93 "start:bun": "bun server.js",95 "start:bun": "bun server.js",
94 "start:no-csrf": "node server.js --disableCsrf",96 "start:no-csrf": "node server.js --disableCsrf",
server.js+23 -8
@@ -30,6 +30,7 @@ import bodyParser from 'body-parser';
3030
31// net related library imports31// net related library imports
32import fetch from 'node-fetch';32import fetch from 'node-fetch';
33import ipRegex from 'ip-regex';
3334
34// Unrestrict console logs display limit35// Unrestrict console logs display limit
35util.inspect.defaultOptions.maxArrayLength = null;36util.inspect.defaultOptions.maxArrayLength = null;
@@ -130,6 +131,8 @@ if (process.versions && process.versions.node && process.versions.node.match(/20
130const DEFAULT_PORT = 8000;131const DEFAULT_PORT = 8000;
131const DEFAULT_AUTORUN = false;132const DEFAULT_AUTORUN = false;
132const DEFAULT_LISTEN = false;133const DEFAULT_LISTEN = false;
134const DEFAULT_LISTEN_ADDRESS_IPV6 = '[::]';
135const DEFAULT_LISTEN_ADDRESS_IPV4 = '0.0.0.0';
133const DEFAULT_CORS_PROXY = false;136const DEFAULT_CORS_PROXY = false;
134const DEFAULT_WHITELIST = true;137const DEFAULT_WHITELIST = true;
135const DEFAULT_ACCOUNTS = false;138const DEFAULT_ACCOUNTS = false;
@@ -185,6 +188,14 @@ const cliArguments = yargs(hideBin(process.argv))
185 type: 'boolean',188 type: 'boolean',
186 default: null,189 default: null,
187 describe: `SillyTavern is listening on all network interfaces (Wi-Fi, LAN, localhost). If false, will limit it only to internal localhost (127.0.0.1).\nIf not provided falls back to yaml config 'listen'.\n[config default: ${DEFAULT_LISTEN}]`,190 describe: `SillyTavern is listening on all network interfaces (Wi-Fi, LAN, localhost). If false, will limit it only to internal localhost (127.0.0.1).\nIf not provided falls back to yaml config 'listen'.\n[config default: ${DEFAULT_LISTEN}]`,
191 }).option('listenAddressIPv6', {
192 type: 'string',
193 default: null,
194 describe: 'Set SillyTavern to listen to a specific IPv6 address. If not set, it will fallback to listen to all.\n[config default: [::] ]',
195 }).option('listenAddressIPv4', {
196 type: 'string',
197 default: null,
198 describe: 'Set SillyTavern to listen to a specific IPv4 address. If not set, it will fallback to listen to all.\n[config default: 0.0.0.0 ]',
188 }).option('corsProxy', {199 }).option('corsProxy', {
189 type: 'boolean',200 type: 'boolean',
190 default: null,201 default: null,
@@ -254,6 +265,10 @@ const server_port = cliArguments.port ?? process.env.SILLY_TAVERN_PORT ?? getCon
254const autorun = (cliArguments.autorun ?? getConfigValue('autorun', DEFAULT_AUTORUN)) && !cliArguments.ssl;265const autorun = (cliArguments.autorun ?? getConfigValue('autorun', DEFAULT_AUTORUN)) && !cliArguments.ssl;
255/** @type {boolean} */266/** @type {boolean} */
256const listen = cliArguments.listen ?? getConfigValue('listen', DEFAULT_LISTEN);267const listen = cliArguments.listen ?? getConfigValue('listen', DEFAULT_LISTEN);
268/** @type {string} */
269const listenAddressIPv6 = cliArguments.listenAddressIPv6 ?? getConfigValue('listenAddress.ipv6', DEFAULT_LISTEN_ADDRESS_IPV6);
270/** @type {string} */
271const listenAddressIPv4 = cliArguments.listenAddressIPv4 ?? getConfigValue('listenAddress.ipv4', DEFAULT_LISTEN_ADDRESS_IPV4);
257/** @type {boolean} */272/** @type {boolean} */
258const enableCorsProxy = cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', DEFAULT_CORS_PROXY);273const enableCorsProxy = cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', DEFAULT_CORS_PROXY);
259const enableWhitelist = cliArguments.whitelist ?? getConfigValue('whitelistMode', DEFAULT_WHITELIST);274const enableWhitelist = cliArguments.whitelist ?? getConfigValue('whitelistMode', DEFAULT_WHITELIST);
@@ -708,13 +723,13 @@ app.use('/api/azure', azureRouter);
708723
709const tavernUrlV6 = new URL(724const tavernUrlV6 = new URL(
710 (cliArguments.ssl ? 'https://' : 'http://') +725 (cliArguments.ssl ? 'https://' : 'http://') +
711 (listen ? '[::]' : '[::1]') +726 (listen ? (ipRegex.v6({ exact: true }).test(listenAddressIPv6) ? listenAddressIPv6 : '[::]') : '[::1]') +
712 (':' + server_port),727 (':' + server_port),
713);728);
714729
715const tavernUrl = new URL(730const tavernUrl = new URL(
716 (cliArguments.ssl ? 'https://' : 'http://') +731 (cliArguments.ssl ? 'https://' : 'http://') +
717 (listen ? '0.0.0.0' : '127.0.0.1') +732 (listen ? (ipRegex.v4({ exact: true }).test(listenAddressIPv4) ? listenAddressIPv4 : '0.0.0.0') : '127.0.0.1') +
718 (':' + server_port),733 (':' + server_port),
719);734);
720735
@@ -837,15 +852,15 @@ const postSetupTasks = async function (v6Failed, v4Failed, useIPv6, useIPv4) {
837 const plainGoToLog = removeColorFormatting(goToLog);852 const plainGoToLog = removeColorFormatting(goToLog);
838853
839 console.log(logListen);854 console.log(logListen);
855 if (listen) {
856 console.log();
857 console.log('To limit connections to internal localhost only ([::1] or 127.0.0.1), change the setting in config.yaml to "listen: false".');
858 console.log('Check the "access.log" file in the SillyTavern directory to inspect incoming connections.');
859 }
840 console.log('\n' + getSeparator(plainGoToLog.length) + '\n');860 console.log('\n' + getSeparator(plainGoToLog.length) + '\n');
841 console.log(goToLog);861 console.log(goToLog);
842 console.log('\n' + getSeparator(plainGoToLog.length) + '\n');862 console.log('\n' + getSeparator(plainGoToLog.length) + '\n');
843863
844 if (listen) {
845 console.log(
846 '[::] or 0.0.0.0 means SillyTavern is listening on all network interfaces (Wi-Fi, LAN, localhost). If you want to limit it only to internal localhost ([::1] or 127.0.0.1), change the setting in config.yaml to "listen: false". Check "access.log" file in the SillyTavern directory if you want to inspect incoming connections.\n',
847 );
848 }
849864
850 if (basicAuthMode) {865 if (basicAuthMode) {
851 if (perUserBasicAuth && !enableAccounts) {866 if (perUserBasicAuth && !enableAccounts) {
@@ -1083,7 +1098,7 @@ async function verifySecuritySettings() {
1083 }1098 }
10841099
1085 if (!enableAccounts) {1100 if (!enableAccounts) {
1086 logSecurityAlert('Your SillyTavern is currently insecurely open to the public. Enable whitelisting, basic authentication or user accounts.');1101 logSecurityAlert('Your current SillyTavern configuration is insecure (listening to non-localhost). Enable whitelisting, basic authentication or user accounts.');
1087 }1102 }
10881103
1089 const users = await getAllEnabledUsers();1104 const users = await getAllEnabledUsers();