From cef999b863f2e46eb4bc6cefb0bc0026dca89d3f Mon Sep 17 00:00:00 2001 From: Ann Date: Fri, 6 Dec 2024 23:20:19 +0300 Subject: [PATCH] Upload sources for V1 --- .gitignore | 4 + README.md | 18 +- config/config.json.example | 26 ++ config/hostconfig.json.example | 9 + package.json | 11 + pnpm-lock.yaml | 681 +++++++++++++++++++++++++++++++++ src/configManager.js | 68 ++++ src/routeManager.js | 73 ++++ src/server.js | 3 + src/serverManager.js | 30 ++ src/wsManager.js | 55 +++ ws-client.js | 108 ++++++ 12 files changed, 1084 insertions(+), 2 deletions(-) create mode 100644 config/config.json.example create mode 100644 config/hostconfig.json.example create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 src/configManager.js create mode 100644 src/routeManager.js create mode 100644 src/server.js create mode 100644 src/serverManager.js create mode 100644 src/wsManager.js create mode 100644 ws-client.js diff --git a/.gitignore b/.gitignore index ceaea36..82c2b2b 100644 --- a/.gitignore +++ b/.gitignore @@ -130,3 +130,7 @@ dist .yarn/install-state.gz .pnp.* +# WWN-specific + +config/config.json +config/hostconfig.json diff --git a/README.md b/README.md index e7af7fd..62147e1 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,17 @@ -# world-wide-node +# World Wide Node -NodeJS-based server software, which I made due to need of dynamic port mapping. \ No newline at end of file +NodeJS-based server software, which I made due to need of dynamic port mapping. + +# Installation with PNPM +1. `pnpm i` +2. `node src/server.js` + +# Configuring +For WWN we have two files: `config.json` and `hostconfig.json`. + +I've set `_` objects after each value, that is the description. You can leave them as-is, or delete, it doesnt matter. + +# Using Config WebSocket +CWS supports two JSONs as of right now: +1. `{"command":""}` - Execute a command. (`currconf`, `prevconf`, `reload`) +2. `{"config":""}` - Update current config (also saves a backup, will get voided on full relaunch.) diff --git a/config/config.json.example b/config/config.json.example new file mode 100644 index 0000000..e8cba05 --- /dev/null +++ b/config/config.json.example @@ -0,0 +1,26 @@ +{ + "ports": [], + "_": "This defines the port(s) where WWN will run. Port 443 will automatically run in HTTPS mode. Note: You can set, technically unlimited amount of ports, but they will act the same. 80,443 are HTTP and HTTPS (unsupported right now)", + + + "CWS_Allow_Everyone": false, + "_": "Allow EVERYONE to connect to WWN's Config WebSocket. DO NOT TURN THIS ON IF YOU DONT KNOW WHAT YOURE DOING!!!", + "CWS_Allowed_IPs": ["localhost", "127.0.0.1"], + "_": "Defines allowed IPs, which can interact with the Config WebSocket. Note: There is no password can be set, so be careful.", + + + "customXPoweredBy": "", + "_": "Will overwrite WWN/VERSION with this string.", + "setXPoweredBy": true, + "_": "If this is UNCHECKED, will remove X-Powered-By header from the response", + + + "customServer": "", + "_": "Will overwrite WWN/VERSION with this string.", + "setServer": true, + "_": "If this is UNCHECKED, will remove Server header from the response.", + + + "mismatchedHostMessage": "Nuh!", + "_":"If someone will try to hijack the host, this will get sent instead." +} diff --git a/config/hostconfig.json.example b/config/hostconfig.json.example new file mode 100644 index 0000000..cc784d8 --- /dev/null +++ b/config/hostconfig.json.example @@ -0,0 +1,9 @@ +[ + { + "_": "Note: HTTP-only requests are supported for now, This is a suggestion and designed feature, because WWN is NOT meant to act as some other service, so local-env only." + "host": "", + "_": "NGINX-analogue: server_name" + "to": "", + "_": "NGINX-analogue: reverse_proxy's destination" + } +] diff --git a/package.json b/package.json new file mode 100644 index 0000000..29d4084 --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "dependencies": { + "axios": "^1.7.9", + "express": "^4.21.2", + "flatted": "^3.3.2", + "http": "0.0.1-security", + "http-proxy": "^1.18.1", + "https": "^1.0.0", + "ws": "^8.18.0" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..acb9436 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,681 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + axios: + specifier: ^1.7.9 + version: 1.7.9 + express: + specifier: ^4.21.2 + version: 4.21.2 + flatted: + specifier: ^3.3.2 + version: 3.3.2 + http: + specifier: 0.0.1-security + version: 0.0.1-security + http-proxy: + specifier: ^1.18.1 + version: 1.18.1 + https: + specifier: ^1.0.0 + version: 1.0.0 + ws: + specifier: ^8.18.0 + version: 8.18.0 + +packages: + + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.7.9: + resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==} + + body-parser@1.20.3: + resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.0: + resolution: {integrity: sha512-CCKAP2tkPau7D3GE8+V8R6sQubA9R5foIzGp+85EXCVSCivuxBNAWqcpn72PKYiIcqoViv/kcUDpaEIMBVi1lQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + cookie-signature@1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + + cookie@0.7.1: + resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} + engines: {node: '>= 0.6'} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + es-define-property@1.0.0: + resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + express@4.21.2: + resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} + engines: {node: '>= 0.10.0'} + + finalhandler@1.3.1: + resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} + engines: {node: '>= 0.8'} + + flatted@3.3.2: + resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} + + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.1: + resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} + engines: {node: '>= 6'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.2.4: + resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.1.0: + resolution: {integrity: sha512-QLdzI9IIO1Jg7f9GT1gXpPpXArAn6cS31R1eEZqz08Gc+uQ8/XiqHWt17Fiw+2p6oTTIq5GXEpQkAlA88YRl/Q==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + + http-proxy@1.18.1: + resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} + engines: {node: '>=8.0.0'} + + http@0.0.1-security: + resolution: {integrity: sha512-RnDvP10Ty9FxqOtPZuxtebw1j4L/WiqNMDtuc1YMH1XQm5TgDRaR1G9u8upL6KD1bXHSp9eSXo/ED+8Q7FAr+g==} + + https@1.0.0: + resolution: {integrity: sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg==} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + object-inspect@1.13.3: + resolution: {integrity: sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-to-regexp@0.1.12: + resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} + engines: {node: '>=0.6'} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + engines: {node: '>= 0.8'} + + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + send@0.19.0: + resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} + engines: {node: '>= 0.8.0'} + + serve-static@1.16.2: + resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} + engines: {node: '>= 0.8.0'} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + side-channel@1.0.6: + resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} + engines: {node: '>= 0.4'} + + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + +snapshots: + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + array-flatten@1.1.1: {} + + asynckit@0.4.0: {} + + axios@1.7.9: + dependencies: + follow-redirects: 1.15.9 + form-data: 4.0.1 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + body-parser@1.20.3: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.13.0 + raw-body: 2.5.2 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.0: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.0 + es-define-property: 1.0.0 + get-intrinsic: 1.2.4 + set-function-length: 1.2.2 + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + cookie-signature@1.0.6: {} + + cookie@0.7.1: {} + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + gopd: 1.2.0 + + delayed-stream@1.0.0: {} + + depd@2.0.0: {} + + destroy@1.2.0: {} + + ee-first@1.1.1: {} + + encodeurl@1.0.2: {} + + encodeurl@2.0.0: {} + + es-define-property@1.0.0: + dependencies: + get-intrinsic: 1.2.4 + + es-errors@1.3.0: {} + + escape-html@1.0.3: {} + + etag@1.8.1: {} + + eventemitter3@4.0.7: {} + + express@4.21.2: + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.3 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.7.1 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.3.1 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.3 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.12 + proxy-addr: 2.0.7 + qs: 6.13.0 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.19.0 + serve-static: 1.16.2 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + finalhandler@1.3.1: + dependencies: + debug: 2.6.9 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + flatted@3.3.2: {} + + follow-redirects@1.15.9: {} + + form-data@4.0.1: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + + forwarded@0.2.0: {} + + fresh@0.5.2: {} + + function-bind@1.1.2: {} + + get-intrinsic@1.2.4: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + has-proto: 1.1.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + + gopd@1.2.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.0 + + has-proto@1.1.0: + dependencies: + call-bind: 1.0.8 + + has-symbols@1.1.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + + http-proxy@1.18.1: + dependencies: + eventemitter3: 4.0.7 + follow-redirects: 1.15.9 + requires-port: 1.0.0 + transitivePeerDependencies: + - debug + + http@0.0.1-security: {} + + https@1.0.0: {} + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + inherits@2.0.4: {} + + ipaddr.js@1.9.1: {} + + media-typer@0.3.0: {} + + merge-descriptors@1.0.3: {} + + methods@1.1.2: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@1.6.0: {} + + ms@2.0.0: {} + + ms@2.1.3: {} + + negotiator@0.6.3: {} + + object-inspect@1.13.3: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + parseurl@1.3.3: {} + + path-to-regexp@0.1.12: {} + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + proxy-from-env@1.1.0: {} + + qs@6.13.0: + dependencies: + side-channel: 1.0.6 + + range-parser@1.2.1: {} + + raw-body@2.5.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + requires-port@1.0.0: {} + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + + send@0.19.0: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + + serve-static@1.16.2: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.0 + transitivePeerDependencies: + - supports-color + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + setprototypeof@1.2.0: {} + + side-channel@1.0.6: + dependencies: + call-bind: 1.0.8 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + object-inspect: 1.13.3 + + statuses@2.0.1: {} + + toidentifier@1.0.1: {} + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + unpipe@1.0.0: {} + + utils-merge@1.0.1: {} + + vary@1.1.2: {} + + ws@8.18.0: {} diff --git a/src/configManager.js b/src/configManager.js new file mode 100644 index 0000000..897a817 --- /dev/null +++ b/src/configManager.js @@ -0,0 +1,68 @@ +const fs = require('fs'); +const path = require('path'); + +const configPath = path.join(__dirname, '../config/config.json'); +const hostConfigPath = path.join(__dirname, '../config/hostconfig.json'); + +let currentConfig = null; +let previousConfig = null; + +const configManager = { + load: function() { + try { + if (!fs.existsSync(configPath)) { + throw new Error('Missing config.json.'); + } + const configData = JSON.parse(fs.readFileSync(configPath, 'utf-8')); + + if (!fs.existsSync(hostConfigPath)) { + fs.writeFileSync(hostConfigPath, JSON.stringify({ mappings: [] }, null, 2)); + } + const hostConfigData = JSON.parse(fs.readFileSync(hostConfigPath, 'utf-8')); + + previousConfig = { ...hostConfigData }; // Clone for rollback + currentConfig = hostConfigData; + + return { config: configData, hostConfig: hostConfigData }; + } catch (err) { + console.error('Error loading configuration:', err.message); + throw err; + } + }, + + saveHostConfig: function(newConfig) { + try { + this.validate(newConfig); + previousConfig = { ...currentConfig }; + currentConfig = newConfig; + fs.writeFileSync(hostConfigPath, JSON.stringify(newConfig, null, 2)); + console.log('Host configuration updated successfully.'); + } catch (err) { + console.error('Error saving host configuration:', err.message); + } + }, + + validate: function(config) { + if (!Array.isArray(config)) { + throw new Error('Invalid configuration: Config must be an array.'); + } + config.forEach((mapping, index) => { + if (!mapping.host || typeof mapping.host !== 'string') { + throw new Error(`Invalid mapping at index ${index}: "host" must be a string.`); + } + if (!mapping.to || typeof mapping.to !== 'string') { + throw new Error(`Invalid mapping at index ${index}: "to" must be a string.`); + } + }); + }, + + getPreviousConfig: function() { + return previousConfig; + }, + + getCurrentConfig: function() { + return currentConfig; + }, +}; + +module.exports = configManager; diff --git a/src/routeManager.js b/src/routeManager.js new file mode 100644 index 0000000..da927d2 --- /dev/null +++ b/src/routeManager.js @@ -0,0 +1,73 @@ +const http = require('http'); +const https = require('https'); +const fs = require('fs'); +const path = require('path'); +const flatted = require('flatted'); +const configManager = require('./configManager'); +const { config, hostConfig } = configManager.load(); + + +const routeManager = { + setupRoutes: function() { + config.ports.forEach((port) => { + const server = (port == 443 ? https : http).createServer(this.requestHandler); + + server.listen(port, () => { + console.log(`${port == 443 ? 'HTTPS' : 'HTTP'} Server listening on port ${port}`); + }); + }); + }, + + requestHandler: function(req, res) { + const hostEntry = hostConfig.find(config => config.host === req.headers.host); + + if (!hostEntry) { + res.writeHead(403, { 'Content-Type': 'text/plain' }); + res.end('Forbidden: Invalid Host'); + return; + } + + const url = new URL(req.url, `http://${hostEntry.to}`); + + let options = { + method: req.method, + headers: req.headers, + }; + + options.headers['Host'] = req.headers.host; + options.headers['X-Real-IP'] = req.headers['x-real-ip'] || req.connection.remoteAddress; + options.headers['X-Forwarded-For'] = req.headers['x-forwarded-for'] || req.connection.remoteAddress; + options.headers['X-Forwarded-Proto'] = req.headers['x-forwarded-proto'] || req.protocol || 'http'; + + const proxyRequest = http.request(url, options, (proxyResponse) => { + const editedHeaders = { ...proxyResponse.headers }; + + delete editedHeaders['server']; + if (config.setServer) { + editedHeaders['Server'] = config.customServer || 'WWN/1.0'; + } else { + delete editedHeaders['Server']; + } + + delete editedHeaders['x-powered-by']; + if (config.setXPoweredBy) { + editedHeaders['X-Powered-By'] = config.customXPoweredBy || 'WWN/1.0'; + } else { + delete editedHeaders['X-Powered-By']; + } + + res.writeHead(proxyResponse.statusCode, editedHeaders); + proxyResponse.pipe(res, { end: true }); + }); + + proxyRequest.on('error', (err) => { + console.error('Error with the proxy request:', err); + res.writeHead(500); + res.end('Internal Server Error'); + }); + + req.pipe(proxyRequest, { end: true }); + } +}; + +module.exports = routeManager; diff --git a/src/server.js b/src/server.js new file mode 100644 index 0000000..28f5c6f --- /dev/null +++ b/src/server.js @@ -0,0 +1,3 @@ +const serverManager = require('./serverManager'); + +serverManager.start(); diff --git a/src/serverManager.js b/src/serverManager.js new file mode 100644 index 0000000..58dc29d --- /dev/null +++ b/src/serverManager.js @@ -0,0 +1,30 @@ +const routeManager = require('./routeManager'); +const wsManager = require('./wsManager'); +const configManager = require('./configManager'); + +const serverManager = { + start: function() { + try { + const { config } = configManager.load(); + routeManager.setupRoutes(); + wsManager.start(config); + console.log('Modules started successfully.'); + } catch (err) { + console.error('Error starting server:', err.message); + process.exit(1); + } + }, + + stop: function() { + console.log('Unimplemented. Sorry.'); + // TODO: actually implement stopping & reloading + }, + + reloadRequest: function() { + console.log('Received reload request...'); + this.stop(); + this.start(); + }, +}; + +module.exports = serverManager; diff --git a/src/wsManager.js b/src/wsManager.js new file mode 100644 index 0000000..973fdc8 --- /dev/null +++ b/src/wsManager.js @@ -0,0 +1,55 @@ +const WebSocket = require('ws'); +const configManager = require('./configManager'); +const routeManager = require('./routeManager'); +const serverManager = require('./serverManager'); + +const wsManager = { + start: function(config) { + const wss = new WebSocket.Server({ port: 64999 }); + + if (config.CWS_Allow_Everyone) { + console.warn("YOU ARE RUNNING WEBSOCKET IN INSECURE, ALL-IP-ALLOWED MODE."); + } + + wss.on('connection', (ws, req) => { + if (!config.CWS_Allowed_IPs.includes(req.socket.remoteAddress) && !config.CWS_Allow_Everyone) { + console.log(`${req.socket.remoteAddress} tried to connect to Config WebSocket, but such IP is NOT in the config.`); + console.log('If this was you, update config and reload WWN'); + ws.close(1008, 'Forbidden'); + return; + } + + ws.on('message', (message) => { + let data; + try { + data = JSON.parse(message); + } catch (err) { + ws.send('Invalid message. Must be JSON'); + return; + } + + try { + if (data.command === 'prevconf') { + ws.send(JSON.stringify(configManager.getPreviousConfig())); + } else if (data.command === 'currconf') { + ws.send(JSON.stringify(configManager.getCurrentConfig())); + } else if (data.command === 'quit') { + ws.close(1000, 'Clean exit'); + } else if (data.config) { + configManager.saveHostConfig(data.config); + ws.send('Configuration updated, requesting reload...'); + serverManager.reloadRequest(); + } else { + ws.send('Unknown command.') + } + } catch (err) { + ws.send(`Error: ${err.message}`); + } + }); + }); + + console.log('WebSocket server running on port 64999.'); + }, +}; + +module.exports = wsManager; diff --git a/ws-client.js b/ws-client.js new file mode 100644 index 0000000..4d9d67f --- /dev/null +++ b/ws-client.js @@ -0,0 +1,108 @@ +const readline = require('readline'); +const WebSocket = require('ws'); + +// Configure readline for user input +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout +}); + +/** + * Prompt the user for the WebSocket server host. + * @param {string} question - The question to display. + * @returns {Promise} - The user input. + */ +function askQuestion(question) { + return new Promise((resolve) => { + rl.question(question, resolve); + }); +} + +/** + * Connect to the WebSocket server and handle communication. + * @param {string} host - The WebSocket server host. + */ +async function connectToWebSocket(host) { + const ws = new WebSocket(`ws://${host}:64999`); + + ws.on('open', () => { + console.log('Connected to the WebSocket server.'); + showMenu(ws); + }); + + ws.on('message', (data) => { + console.log('Server response:', data.toString()); + }); + + ws.on('error', (err) => { + console.error('WebSocket error:', err.message); + rl.close(); + }); + + ws.on('close', () => { + console.log('Connection closed.'); + rl.close(); + }); +} + +/** + * Display a menu of actions for the user. + * @param {WebSocket} ws - The WebSocket connection. + */ +function showMenu(ws) { + console.log(` +Available commands: +1. prevconf - Get the previous configuration. +2. currconf - Get the current configuration. +3. Update - Send a new configuration. +4. Exit - Close the client. + `); + + rl.question('Enter your choice: ', async (choice) => { + switch (choice.trim()) { + case '1': + ws.send(JSON.stringify({ command: 'prevconf' })); + break; + case '2': + ws.send(JSON.stringify({ command: 'currconf' })); + break; + case '3': + const newConfig = await askForNewConfig(); + ws.send(JSON.stringify(newConfig)); + break; + case '4': + console.log('Closing the client...'); + ws.close(); + rl.close(); + return; + default: + console.log('Invalid choice.'); + } + showMenu(ws); // Show the menu again after the action + }); +} + +/** + * Prompt the user for a new configuration. + * @returns {Promise} - The new configuration object. + */ +async function askForNewConfig() { + const numMappings = await askQuestion('How many mappings do you want to add? '); + const mappings = []; + + for (let i = 0; i < parseInt(numMappings, 10); i++) { + const host = await askQuestion(`Enter host for mapping ${i + 1}: `); + const port = await askQuestion(`Enter port for mapping ${i + 1}: `); + mappings.push({ host, port: parseInt(port, 10) }); + } + + return { mappings }; +} + +/** + * Entry point for the client. + */ +(async function main() { + const host = await askQuestion('Enter the WebSocket server host (e.g., localhost): '); + connectToWebSocket(host); +})();