Merge pull request #3478 from Dakraid/feature/set-listen-ip Feature: Allow user to configure an address to listen to
Signed| @@ -6,6 +6,10 @@ cardsCacheCapacity: 100 | |||
| 6 | # -- SERVER CONFIGURATION -- | 6 | # -- SERVER CONFIGURATION -- |
| 7 | # Listen for incoming connections | 7 | # Listen for incoming connections |
| 8 | listen: false | 8 | listen: false |
| 9 | # Listen on a specific address, supports IPv4 and IPv6 | ||
| 10 | listenAddress: | ||
| 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 support | 14 | # - Use option "auto" to automatically detect support |
| 11 | # - Use true or false (no qoutes) to enable or disable each protocol | 15 | # - Use true or false (no qoutes) to enable or disable each protocol |
| @@ -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", |
| @@ -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", |
| @@ -30,6 +30,7 @@ import bodyParser from 'body-parser'; | |||
| 30 | 30 | ||
| 31 | // net related library imports | 31 | // net related library imports |
| 32 | import fetch from 'node-fetch'; | 32 | import fetch from 'node-fetch'; |
| 33 | import ipRegex from 'ip-regex'; | ||
| 33 | 34 | ||
| 34 | // Unrestrict console logs display limit | 35 | // Unrestrict console logs display limit |
| 35 | util.inspect.defaultOptions.maxArrayLength = null; | 36 | util.inspect.defaultOptions.maxArrayLength = null; |
| @@ -130,6 +131,8 @@ if (process.versions && process.versions.node && process.versions.node.match(/20 | |||
| 130 | const DEFAULT_PORT = 8000; | 131 | const DEFAULT_PORT = 8000; |
| 131 | const DEFAULT_AUTORUN = false; | 132 | const DEFAULT_AUTORUN = false; |
| 132 | const DEFAULT_LISTEN = false; | 133 | const DEFAULT_LISTEN = false; |
| 134 | const DEFAULT_LISTEN_ADDRESS_IPV6 = '[::]'; | ||
| 135 | const DEFAULT_LISTEN_ADDRESS_IPV4 = '0.0.0.0'; | ||
| 133 | const DEFAULT_CORS_PROXY = false; | 136 | const DEFAULT_CORS_PROXY = false; |
| 134 | const DEFAULT_WHITELIST = true; | 137 | const DEFAULT_WHITELIST = true; |
| 135 | const DEFAULT_ACCOUNTS = false; | 138 | const 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 | |||
| 254 | const autorun = (cliArguments.autorun ?? getConfigValue('autorun', DEFAULT_AUTORUN)) && !cliArguments.ssl; | 265 | const autorun = (cliArguments.autorun ?? getConfigValue('autorun', DEFAULT_AUTORUN)) && !cliArguments.ssl; |
| 255 | /** @type {boolean} */ | 266 | /** @type {boolean} */ |
| 256 | const listen = cliArguments.listen ?? getConfigValue('listen', DEFAULT_LISTEN); | 267 | const listen = cliArguments.listen ?? getConfigValue('listen', DEFAULT_LISTEN); |
| 268 | /** @type {string} */ | ||
| 269 | const listenAddressIPv6 = cliArguments.listenAddressIPv6 ?? getConfigValue('listenAddress.ipv6', DEFAULT_LISTEN_ADDRESS_IPV6); | ||
| 270 | /** @type {string} */ | ||
| 271 | const listenAddressIPv4 = cliArguments.listenAddressIPv4 ?? getConfigValue('listenAddress.ipv4', DEFAULT_LISTEN_ADDRESS_IPV4); | ||
| 257 | /** @type {boolean} */ | 272 | /** @type {boolean} */ |
| 258 | const enableCorsProxy = cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', DEFAULT_CORS_PROXY); | 273 | const enableCorsProxy = cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', DEFAULT_CORS_PROXY); |
| 259 | const enableWhitelist = cliArguments.whitelist ?? getConfigValue('whitelistMode', DEFAULT_WHITELIST); | 274 | const enableWhitelist = cliArguments.whitelist ?? getConfigValue('whitelistMode', DEFAULT_WHITELIST); |
| @@ -708,13 +723,13 @@ app.use('/api/azure', azureRouter); | |||
| 708 | 723 | ||
| 709 | const tavernUrlV6 = new URL( | 724 | const 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 | ); |
| 714 | 729 | ||
| 715 | const tavernUrl = new URL( | 730 | const 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 | ); |
| 720 | 735 | ||
| @@ -837,15 +852,15 @@ const postSetupTasks = async function (v6Failed, v4Failed, useIPv6, useIPv4) { | |||
| 837 | const plainGoToLog = removeColorFormatting(goToLog); | 852 | const plainGoToLog = removeColorFormatting(goToLog); |
| 838 | 853 | ||
| 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'); |
| 843 | 863 | ||
| 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 | } | ||
| 849 | 864 | ||
| 850 | if (basicAuthMode) { | 865 | if (basicAuthMode) { |
| 851 | if (perUserBasicAuth && !enableAccounts) { | 866 | if (perUserBasicAuth && !enableAccounts) { |
| @@ -1083,7 +1098,7 @@ async function verifySecuritySettings() { | |||
| 1083 | } | 1098 | } |
| 1084 | 1099 | ||
| 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 | } |
| 1088 | 1103 | ||
| 1089 | const users = await getAllEnabledUsers(); | 1104 | const users = await getAllEnabledUsers(); |