diff --git a/backend/package-lock.json b/backend/package-lock.json index ac94dae..2a5b43f 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -14,8 +14,24 @@ "dotenv": "^16.4.7", "express": "^4.21.2", "express-rate-limit": "^7.5.0", + "mac-lookup": "^1.0.1", "pino": "^9.6.0", - "pino-pretty": "^13.0.0" + "pino-pretty": "^13.0.0", + "whois-json": "^2.0.4" + } + }, + "node_modules/@fast-csv/parse": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@fast-csv/parse/-/parse-4.3.6.tgz", + "integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.groupby": "^4.6.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0", + "lodash.isundefined": "^3.0.1", + "lodash.uniq": "^4.5.0" } }, "node_modules/@maxmind/geoip2-node": { @@ -26,6 +42,11 @@ "maxmind": "^4.2.0" } }, + "node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -38,6 +59,28 @@ "node": ">= 0.6" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -109,11 +152,88 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==", + "dependencies": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/change-case": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-3.1.0.tgz", + "integrity": "sha512-2AZp7uJZbYEzRPsFoa+ijKdvp9zsrnnt6+yFokfwEpeJm0xuJDVoxiRCAaTzyJND8GJkofo2IcKWaUZ/OECVzw==", + "dependencies": { + "camel-case": "^3.0.0", + "constant-case": "^2.0.0", + "dot-case": "^2.1.0", + "header-case": "^1.0.0", + "is-lower-case": "^1.1.0", + "is-upper-case": "^1.1.0", + "lower-case": "^1.1.1", + "lower-case-first": "^1.0.0", + "no-case": "^2.3.2", + "param-case": "^2.1.0", + "pascal-case": "^2.0.0", + "path-case": "^2.1.0", + "sentence-case": "^2.1.0", + "snake-case": "^2.1.0", + "swap-case": "^1.1.0", + "title-case": "^2.1.0", + "upper-case": "^1.1.1", + "upper-case-first": "^1.1.0" + } + }, + "node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" }, + "node_modules/constant-case": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-2.0.0.tgz", + "integrity": "sha512-eS0N9WwmjTqrOmR3o83F5vW8Z+9R1HnVz3xmzT2PMFug9ly+Au/fxRWlEBSb6LcZwspSsEn9Xs1uw9YgzAg1EQ==", + "dependencies": { + "snake-case": "^2.1.0", + "upper-case": "^1.1.1" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -174,6 +294,19 @@ "ms": "2.0.0" } }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dedent-js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dedent-js/-/dedent-js-1.0.1.tgz", + "integrity": "sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==" + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -191,6 +324,14 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/dot-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-2.1.1.tgz", + "integrity": "sha512-HnM6ZlFqcajLsyudHq7LeeLDr2rFAVYtDv/hV5qchQEidSck8j9OPUsXY9KwJv/lHMtYlX4DjRQqwFYa+0r8Ug==", + "dependencies": { + "no-case": "^2.2.0" + } + }, "node_modules/dotenv": { "version": "16.4.7", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", @@ -220,6 +361,11 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -370,6 +516,18 @@ "node": ">= 0.8" } }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -394,6 +552,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -462,11 +628,25 @@ "node": ">= 0.4" } }, + "node_modules/header-case": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/header-case/-/header-case-1.0.1.tgz", + "integrity": "sha512-i0q9mkOeSuhXw6bGgiQCCBgY/jlZuV/7dZXyZ9c6LcBrqwvT8eT719E9uxE5LiZftdl+z81Ugbg/VvXV4OJOeQ==", + "dependencies": { + "no-case": "^2.2.0", + "upper-case": "^1.1.3" + } + }, "node_modules/help-me": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==" }, + "node_modules/html-entities": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.4.0.tgz", + "integrity": "sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==" + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -498,6 +678,18 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -506,6 +698,30 @@ "node": ">= 0.10" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-lower-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-lower-case/-/is-lower-case-1.1.3.tgz", + "integrity": "sha512-+5A1e/WJpLLXZEDlgz4G//WYSHyQBD32qa4Jd3Lw06qQlv3fJHnp3YIHjTQSGzHMgzmVKz2ZP3rBxTHkPw/lxA==", + "dependencies": { + "lower-case": "^1.1.0" + } + }, + "node_modules/is-upper-case": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-upper-case/-/is-upper-case-1.1.2.tgz", + "integrity": "sha512-GQYSJMgfeAmVwh9ixyk888l7OIhNAGKtY6QA+IrWlu9MDTCaXmeozOZ2S9Knj7bQwBO/H6J2kb+pbyTUiMNbsw==", + "dependencies": { + "upper-case": "^1.1.0" + } + }, "node_modules/joycon": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", @@ -514,6 +730,73 @@ "node": ">=10" } }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==" + }, + "node_modules/lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==" + }, + "node_modules/lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==" + }, + "node_modules/lodash.isnil": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz", + "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==" + }, + "node_modules/lodash.isundefined": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", + "integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" + }, + "node_modules/lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==" + }, + "node_modules/lower-case-first": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/lower-case-first/-/lower-case-first-1.0.2.tgz", + "integrity": "sha512-UuxaYakO7XeONbKrZf5FEgkantPf5DUqDayzP5VXZrtRPdH86s4kN47I8B3TW10S4QKiE3ziHNf3kRN//okHjA==", + "dependencies": { + "lower-case": "^1.1.2" + } + }, + "node_modules/mac-lookup": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mac-lookup/-/mac-lookup-1.0.1.tgz", + "integrity": "sha512-EZOUg4d/xg9l66cvBpG1PI8hYHvFd2nPID3asVGwvOk+Gse/LWCELcqEaWRh6Ws8HsroA9GGAzDdR3nMfTnVgQ==", + "dependencies": { + "@fast-csv/parse": "^4.3.0" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -619,6 +902,14 @@ "node": ">= 0.6" } }, + "node_modules/no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dependencies": { + "lower-case": "^1.1.1" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -665,6 +956,47 @@ "wrappy": "1" } }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==", + "dependencies": { + "no-case": "^2.2.0" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -673,6 +1005,31 @@ "node": ">= 0.8" } }, + "node_modules/pascal-case": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-2.0.1.tgz", + "integrity": "sha512-qjS4s8rBOJa2Xm0jmxXiyh1+OFf6ekCWOvUaRgAQSktzlTbMotS0nmG9gyYAybCWBcuP4fsBeRCKNwGBnMe2OQ==", + "dependencies": { + "camel-case": "^3.0.0", + "upper-case-first": "^1.1.0" + } + }, + "node_modules/path-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/path-case/-/path-case-2.1.1.tgz", + "integrity": "sha512-Ou0N05MioItesaLr9q8TtHVWmJ6fxWdqKB2RohFmNWVyJ+2zeKIeDNWAN6B/Pe7wpzWChhZX6nONYmOnMeJQ/Q==", + "dependencies": { + "no-case": "^2.2.0" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "engines": { + "node": ">=8" + } + }, "node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", @@ -771,6 +1128,14 @@ "once": "^1.3.1" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -820,6 +1185,19 @@ "node": ">= 12.13.0" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -893,6 +1271,15 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/sentence-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-2.1.1.tgz", + "integrity": "sha512-ENl7cYHaK/Ktwk5OTD+aDbQ3uC8IByu/6Bkg+HDv8Mm+XnBnppVNalcfJTNsp1ibstKh030/JKQQWglDvtKwEQ==", + "dependencies": { + "no-case": "^2.2.0", + "upper-case-first": "^1.1.2" + } + }, "node_modules/serve-static": { "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", @@ -907,6 +1294,11 @@ "node": ">= 0.8.0" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -980,6 +1372,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/snake-case": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-2.1.0.tgz", + "integrity": "sha512-FMR5YoPFwOLuh4rRz92dywJjyKYZNLpMn1R5ujVpIYkbA9p01fq8RMg0FkO4M+Yobt4MjHeLTJVm5xFFBHSV2Q==", + "dependencies": { + "no-case": "^2.2.0" + } + }, + "node_modules/socks": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", + "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, "node_modules/sonic-boom": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", @@ -996,6 +1418,11 @@ "node": ">= 10.x" } }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -1004,6 +1431,30 @@ "node": ">= 0.8" } }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -1015,6 +1466,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/swap-case": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/swap-case/-/swap-case-1.1.2.tgz", + "integrity": "sha512-BAmWG6/bx8syfc6qXPprof3Mn5vQgf5dwdUNJhsNqU9WdPt5P+ES/wQ5bxfijy8zwZgZZHslC3iAsxsuQMCzJQ==", + "dependencies": { + "lower-case": "^1.1.1", + "upper-case": "^1.1.1" + } + }, "node_modules/thread-stream": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", @@ -1031,6 +1491,15 @@ "node": ">=12" } }, + "node_modules/title-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/title-case/-/title-case-2.1.1.tgz", + "integrity": "sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q==", + "dependencies": { + "no-case": "^2.2.0", + "upper-case": "^1.0.3" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1051,6 +1520,11 @@ "node": ">= 0.6" } }, + "node_modules/underscore": { + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -1059,6 +1533,19 @@ "node": ">= 0.8" } }, + "node_modules/upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==" + }, + "node_modules/upper-case-first": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-1.1.2.tgz", + "integrity": "sha512-wINKYvI3Db8dtjikdAqoBbZoP6Q+PZUyfMR7pmwHzjC2quzSkUq5DmPrTtPEqHaz8AGtmsB4TqwapMTM1QAQOQ==", + "dependencies": { + "upper-case": "^1.1.1" + } + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -1075,10 +1562,91 @@ "node": ">= 0.8" } }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==" + }, + "node_modules/whois": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/whois/-/whois-2.14.2.tgz", + "integrity": "sha512-JzH7/WUC4L59hPKwc6lZ59OpeBDcG+axt9vBYeQg1DCtrlwyxTUzorhI58nEWHmN+R/RtiUi9MdQ6NE9TmPREQ==", + "dependencies": { + "punycode": "^2.3.1", + "socks": "^2.2.2", + "underscore": "^1.9.1", + "yargs": "^15.4.1" + }, + "bin": { + "whois": "bin.js" + } + }, + "node_modules/whois-json": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/whois-json/-/whois-json-2.0.4.tgz", + "integrity": "sha512-ui9Qe0qS4yWtFAPw0q+QPuuH+m4vDDtoO/YtqmF00YoPMMNplhya7SqTVAxThWIVXhWHPEKajFEulegmysdlwQ==", + "dependencies": { + "change-case": "^3.0.2", + "dedent-js": "^1.0.1", + "html-entities": "^1.2.1", + "whois": "^2.6.0" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + }, + "node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } } } } diff --git a/backend/package.json b/backend/package.json index bc707ac..8cc9b54 100644 --- a/backend/package.json +++ b/backend/package.json @@ -15,7 +15,9 @@ "dotenv": "^16.4.7", "express": "^4.21.2", "express-rate-limit": "^7.5.0", + "mac-lookup": "^1.0.1", "pino": "^9.6.0", - "pino-pretty": "^13.0.0" + "pino-pretty": "^13.0.0", + "whois-json": "^2.0.4" } } diff --git a/backend/server.js b/backend/server.js index 1ff9ffa..9cdae0c 100644 --- a/backend/server.js +++ b/backend/server.js @@ -8,6 +8,8 @@ const { spawn } = require('child_process'); const dns = require('dns').promises; const pino = require('pino'); // Logging library const rateLimit = require('express-rate-limit'); // Rate limiting middleware +const whois = require('whois-json'); // Hinzugefügt für WHOIS +const macLookup = require('mac-lookup'); // Hinzugefügt für MAC Lookup // --- Logger Initialisierung --- const logger = pino({ @@ -72,6 +74,35 @@ function isPrivateIp(ip) { return false; } +/** + * Validiert einen Domainnamen (sehr einfache Prüfung). + * @param {string} domain - Der zu validierende Domainname. + * @returns {boolean} True, wenn wahrscheinlich gültig, sonst false. + */ +function isValidDomain(domain) { + if (!domain || typeof domain !== 'string' || domain.trim().length < 3) { + return false; + } + // Einfache Regex: Muss mindestens einen Punkt enthalten und keine ungültigen Zeichen. + // Erlaubt IDNs (Internationalized Domain Names) durch \p{L} + const domainRegex = /^(?:[a-z0-9\p{L}](?:[a-z0-9\p{L}-]{0,61}[a-z0-9\p{L}])?\.)+[a-z0-9\p{L}][a-z0-9\p{L}-]{0,61}[a-z0-9\p{L}]$/iu; + return domainRegex.test(domain.trim()); +} + +/** + * Validiert eine MAC-Adresse. + * @param {string} mac - Die zu validierende MAC-Adresse. + * @returns {boolean} True, wenn gültig, sonst false. + */ +function isValidMac(mac) { + if (!mac || typeof mac !== 'string') { + return false; + } + // Erlaubt Formate wie 00:1A:2B:3C:4D:5E, 00-1A-2B-3C-4D-5E, 001A.2B3C.4D5E + const macRegex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$|^([0-9A-Fa-f]{4}\.){2}([0-9A-Fa-f]{4})$/; + return macRegex.test(mac.trim()); +} + /** * Bereinigt eine IP-Adresse (z.B. entfernt ::ffff: Präfix von IPv4-mapped IPv6). @@ -246,8 +277,14 @@ async function initialize() { cityReader = await geoip.Reader.open(cityDbPath); asnReader = await geoip.Reader.open(asnDbPath); logger.info('MaxMind databases loaded successfully.'); + + // Lade MAC-Lookup Daten (asynchron) + logger.info('Loading MAC address lookup data...'); + await macLookup.load(); // Lädt die Daten beim Start + logger.info('MAC address lookup data loaded.'); + } catch (error) { - logger.fatal({ error: error.message, stack: error.stack }, 'Could not load MaxMind databases. Exiting.'); + logger.fatal({ error: error.message, stack: error.stack }, 'Could not initialize databases or MAC data. Exiting.'); process.exit(1); } } @@ -260,12 +297,12 @@ app.use(express.json()); // Parst JSON-Request-Bodies app.set('trust proxy', 2); // Vertraue zwei Proxys (externer Nginx + interner Nginx) // Rate Limiter -const diagnosticLimiter = rateLimit({ +const generalLimiter = rateLimit({ windowMs: 5 * 60 * 1000, // 5 Minuten - max: process.env.NODE_ENV === 'production' ? 10 : 100, // Mehr Anfragen im Dev erlauben + max: process.env.NODE_ENV === 'production' ? 20 : 200, // Mehr Anfragen im Dev erlauben standardHeaders: true, legacyHeaders: false, - message: { error: 'Too many diagnostic requests (ping/traceroute) from this IP, please try again after 5 minutes' }, + message: { error: 'Too many requests from this IP, please try again after 5 minutes' }, keyGenerator: (req, res) => req.ip || req.socket.remoteAddress, // IP des Clients als Schlüssel handler: (req, res, next, options) => { logger.warn({ ip: req.ip || req.socket.remoteAddress, route: req.originalUrl }, 'Rate limit exceeded'); @@ -273,9 +310,13 @@ const diagnosticLimiter = rateLimit({ } }); -// Wende Limiter nur auf Ping und Traceroute an -app.use('/api/ping', diagnosticLimiter); -app.use('/api/traceroute', diagnosticLimiter); +// Wende Limiter auf alle API-Routen an (außer /api/version und /api/ipinfo) +app.use('/api/ping', generalLimiter); +app.use('/api/traceroute', generalLimiter); +app.use('/api/lookup', generalLimiter); +app.use('/api/dns-lookup', generalLimiter); // Neu +app.use('/api/whois-lookup', generalLimiter); // Neu +app.use('/api/mac-lookup', generalLimiter); // Neu // --- Routen --- @@ -316,6 +357,7 @@ app.get('/api/ipinfo', async (req, res) => { longitude: geoData.location?.longitude, timezone: geoData.location?.timeZone, }; + geo = Object.fromEntries(Object.entries(geo).filter(([_, v]) => v != null)); // Entferne leere Werte logger.debug({ ip: clientIp, geo }, 'GeoIP lookup successful'); } catch (e) { logger.warn({ ip: clientIp, error: e.message }, `MaxMind City lookup failed`); @@ -329,6 +371,7 @@ app.get('/api/ipinfo', async (req, res) => { number: asnData.autonomousSystemNumber, organization: asnData.autonomousSystemOrganization, }; + asn = Object.fromEntries(Object.entries(asn).filter(([_, v]) => v != null)); // Entferne leere Werte logger.debug({ ip: clientIp, asn }, 'ASN lookup successful'); } catch (e) { logger.warn({ ip: clientIp, error: e.message }, `MaxMind ASN lookup failed`); @@ -349,7 +392,12 @@ app.get('/api/ipinfo', async (req, res) => { rdns = { error: `rDNS lookup failed (${e.code || 'Unknown error'})` }; } - res.json({ ip: clientIp, geo, asn, rdns }); + res.json({ + ip: clientIp, + geo: geo.error ? geo : (Object.keys(geo).length > 0 ? geo : null), + asn: asn.error ? asn : (Object.keys(asn).length > 0 ? asn : null), + rdns + }); } catch (error) { logger.error({ ip: clientIp, error: error.message, stack: error.stack }, 'Error processing ipinfo'); @@ -442,7 +490,7 @@ app.get('/api/traceroute', (req, res) => { // Beachte: nicht async, da wir strea } catch (e) { logger.error({ requestIp, targetIp, event, error: e.message }, "Error writing to SSE stream (client likely disconnected)"); proc.kill(); // Beende Prozess, wenn Schreiben fehlschlägt - res.end(); + if (!res.writableEnded) res.end(); } }; @@ -522,25 +570,22 @@ app.get('/api/traceroute', (req, res) => { // Beachte: nicht async, da wir strea }); // Ende von app.get('/api/traceroute'...) -// Lookup Endpunkt für beliebige IP +// Lookup Endpunkt für beliebige IP (GeoIP, ASN, rDNS) app.get('/api/lookup', async (req, res) => { - // Debug-Logs - logger.debug({ queryParams: req.query }, 'Received query parameters for lookup'); - - const targetIpRaw = req.query.targetIp; // IP kommt jetzt als Query-Parameter 'ip' + const targetIpRaw = req.query.targetIp; // IP kommt jetzt als Query-Parameter 'targetIp' const targetIp = typeof targetIpRaw === 'string' ? targetIpRaw.trim() : targetIpRaw; const requestIp = req.ip || req.socket.remoteAddress; // Nur für Logging - logger.info({ requestIp, targetIp }, 'Lookup request received'); // <-- Hier sollte targetIp korrekt geloggt werden + logger.info({ requestIp, targetIp }, 'Lookup request received'); // Validierung: Ist es eine gültige IP? - if (!isValidIp(targetIp)) { // <-- Hier wird targetIp verwendet, scheint OK + if (!isValidIp(targetIp)) { logger.warn({ requestIp, targetIp }, 'Invalid target IP for lookup'); return res.status(400).json({ error: 'Invalid IP address provided for lookup.' }); } // Validierung: Ist es eine private IP? - if (isPrivateIp(targetIp)) { // <-- Hier wird targetIp verwendet, scheint OK + if (isPrivateIp(targetIp)) { logger.warn({ requestIp, targetIp }, 'Attempt to lookup private IP blocked'); return res.status(403).json({ error: 'Lookup for private or local IP addresses is not supported.' }); } @@ -550,7 +595,6 @@ app.get('/api/lookup', async (req, res) => { let geo = null; try { const geoData = cityReader.city(targetIp); - // --- KORREKTUR HIER: Datenextraktion wieder einfügen --- geo = { city: geoData.city?.names?.en, region: geoData.subdivisions?.[0]?.isoCode, @@ -561,8 +605,6 @@ app.get('/api/lookup', async (req, res) => { longitude: geoData.location?.longitude, timezone: geoData.location?.timeZone, }; - // --- ENDE KORREKTUR --- - // Filter out null/undefined values before logging/returning (optional but cleaner) geo = Object.fromEntries(Object.entries(geo).filter(([_, v]) => v != null)); logger.debug({ targetIp, geo }, 'GeoIP lookup successful for lookup'); } catch (e) { @@ -573,13 +615,10 @@ app.get('/api/lookup', async (req, res) => { let asn = null; try { const asnData = asnReader.asn(targetIp); - // --- KORREKTUR HIER: Datenextraktion wieder einfügen --- asn = { number: asnData.autonomousSystemNumber, organization: asnData.autonomousSystemOrganization, }; - // --- ENDE KORREKTUR --- - // Filter out null/undefined values asn = Object.fromEntries(Object.entries(asn).filter(([_, v]) => v != null)); logger.debug({ targetIp, asn }, 'ASN lookup successful for lookup'); } catch (e) { @@ -593,15 +632,19 @@ app.get('/api/lookup', async (req, res) => { rdns = hostnames; logger.debug({ targetIp, rdns }, 'rDNS lookup successful for lookup'); } catch (e) { - // ... (rDNS Fehlerbehandlung bleibt gleich) ... + if (e.code !== 'ENOTFOUND' && e.code !== 'ENODATA') { + logger.warn({ targetIp, error: e.message, code: e.code }, `rDNS lookup error for lookup`); + } else { + logger.debug({ targetIp, code: e.code }, 'rDNS lookup failed (No record) for lookup'); + } rdns = { error: `rDNS lookup failed (${e.code || 'Unknown error'})` }; } // Gib die gesammelten Daten zurück res.json({ ip: targetIp, - geo: Object.keys(geo).length > 0 ? geo : null, // Sende null wenn geo leer ist (außer bei Fehler) - asn: Object.keys(asn).length > 0 ? asn : null, // Sende null wenn asn leer ist (außer bei Fehler) + geo: geo.error ? geo : (Object.keys(geo).length > 0 ? geo : null), + asn: asn.error ? asn : (Object.keys(asn).length > 0 ? asn : null), rdns, }); @@ -611,6 +654,137 @@ app.get('/api/lookup', async (req, res) => { } }); +// --- NEUE ENDPUNKTE --- + +// DNS Lookup Endpunkt +app.get('/api/dns-lookup', async (req, res) => { + const domainRaw = req.query.domain; + const domain = typeof domainRaw === 'string' ? domainRaw.trim() : domainRaw; + const typeRaw = req.query.type; + const type = typeof typeRaw === 'string' ? typeRaw.trim().toUpperCase() : 'ANY'; + const requestIp = req.ip || req.socket.remoteAddress; + + logger.info({ requestIp, domain, type }, 'DNS lookup request received'); + + if (!isValidDomain(domain)) { + logger.warn({ requestIp, domain }, 'Invalid domain for DNS lookup'); + return res.status(400).json({ error: 'Invalid domain name provided.' }); + } + + const validTypes = ['A', 'AAAA', 'MX', 'TXT', 'NS', 'CNAME', 'SOA', 'SRV', 'PTR', 'ANY']; + if (!validTypes.includes(type)) { + logger.warn({ requestIp, domain, type }, 'Invalid record type for DNS lookup'); + return res.status(400).json({ error: `Invalid record type provided. Valid types are: ${validTypes.join(', ')}` }); + } + + try { + // dns.resolve unterstützt 'ANY', aber gibt oft nur einen Teil zurück oder wirft Fehler. + // Besser spezifische Typen abfragen oder dns.resolveAny verwenden (wenn verfügbar und gewünscht). + // Für Einfachheit hier dns.resolve. + let records; + if (type === 'ANY') { + // Versuche, gängige Typen einzeln abzufragen, da resolveAny oft nicht wie erwartet funktioniert + const promises = [ + dns.resolve(domain, 'A').catch(() => []), + dns.resolve(domain, 'AAAA').catch(() => []), + dns.resolve(domain, 'MX').catch(() => []), + dns.resolve(domain, 'TXT').catch(() => []), + dns.resolve(domain, 'NS').catch(() => []), + dns.resolve(domain, 'CNAME').catch(() => []), + dns.resolve(domain, 'SOA').catch(() => []), + ]; + const results = await Promise.all(promises); + records = { + A: results[0], + AAAA: results[1], + MX: results[2], + TXT: results[3], + NS: results[4], + CNAME: results[5], + SOA: results[6], + }; + // Entferne leere Ergebnisse + records = Object.fromEntries(Object.entries(records).filter(([_, v]) => Array.isArray(v) ? v.length > 0 : v)); + } else { + records = await dns.resolve(domain, type); + } + + logger.info({ requestIp, domain, type }, 'DNS lookup successful'); + res.json({ success: true, domain, type, records }); + + } catch (error) { + logger.error({ requestIp, domain, type, error: error.message, code: error.code }, 'DNS lookup failed'); + res.status(500).json({ success: false, error: `DNS lookup failed: ${error.message} (Code: ${error.code})` }); + } +}); + +// WHOIS Lookup Endpunkt +app.get('/api/whois-lookup', async (req, res) => { + const queryRaw = req.query.query; + const query = typeof queryRaw === 'string' ? queryRaw.trim() : queryRaw; + const requestIp = req.ip || req.socket.remoteAddress; + + logger.info({ requestIp, query }, 'WHOIS lookup request received'); + + // Einfache Validierung: Muss entweder eine gültige IP oder eine Domain sein + if (!isValidIp(query) && !isValidDomain(query)) { + logger.warn({ requestIp, query }, 'Invalid query for WHOIS lookup'); + return res.status(400).json({ error: 'Invalid domain name or IP address provided for WHOIS lookup.' }); + } + + try { + // whois-json kann manchmal sehr lange dauern oder fehlschlagen + const result = await whois(query, { timeout: 10000 }); // 10 Sekunden Timeout + + logger.info({ requestIp, query }, 'WHOIS lookup successful'); + res.json({ success: true, query, result }); + + } catch (error) { + logger.error({ requestIp, query, error: error.message }, 'WHOIS lookup failed'); + // Versuche, eine spezifischere Fehlermeldung zu geben + let errorMessage = error.message; + if (error.message.includes('ETIMEDOUT') || error.message.includes('ESOCKETTIMEDOUT')) { + errorMessage = 'WHOIS server timed out.'; + } else if (error.message.includes('ENOTFOUND')) { + errorMessage = 'Domain or IP not found or WHOIS server unavailable.'; + } + res.status(500).json({ success: false, error: `WHOIS lookup failed: ${errorMessage}` }); + } +}); + +// MAC Address Lookup Endpunkt +app.get('/api/mac-lookup', async (req, res) => { + const macRaw = req.query.mac; + const mac = typeof macRaw === 'string' ? macRaw.trim() : macRaw; + const requestIp = req.ip || req.socket.remoteAddress; + + logger.info({ requestIp, mac }, 'MAC lookup request received'); + + if (!isValidMac(mac)) { + logger.warn({ requestIp, mac }, 'Invalid MAC address for lookup'); + return res.status(400).json({ error: 'Invalid MAC address format provided.' }); + } + + try { + // mac-lookup verwendet eine lokale Datenbank, sollte schnell sein + const vendor = await macLookup.lookup(mac); // lookup ist jetzt async + + if (vendor) { + logger.info({ requestIp, mac, vendor }, 'MAC lookup successful'); + res.json({ success: true, mac, vendor }); + } else { + logger.info({ requestIp, mac }, 'MAC lookup successful, but no vendor found'); + res.json({ success: true, mac, vendor: null, message: 'Vendor not found for this MAC address prefix.' }); + } + + } catch (error) { + // Fehler sollten nur auftreten, wenn die DB nicht geladen wurde oder die Eingabe ungültig ist (sollte durch isValidMac abgefangen werden) + logger.error({ requestIp, mac, error: error.message }, 'MAC lookup failed'); + res.status(500).json({ success: false, error: `MAC lookup failed: ${error.message}` }); + } +}); + + // Version Endpunkt app.get('/api/version', (req, res) => { const commitSha = process.env.GIT_COMMIT_SHA || 'unknown'; @@ -628,6 +802,9 @@ initialize().then(() => { logger.info(` http://localhost:${PORT}/api/ping?targetIp=`); logger.info(` http://localhost:${PORT}/api/traceroute?targetIp=`); logger.info(` http://localhost:${PORT}/api/lookup?targetIp=`); + logger.info(` http://localhost:${PORT}/api/dns-lookup?domain=&type=`); // Neu + logger.info(` http://localhost:${PORT}/api/whois-lookup?query=`); // Neu + logger.info(` http://localhost:${PORT}/api/mac-lookup?mac=`); // Neu logger.info(` http://localhost:${PORT}/api/version`); }); }).catch(error => { @@ -641,6 +818,7 @@ const signals = { 'SIGINT': 2, 'SIGTERM': 15 }; Object.keys(signals).forEach((signal) => { process.on(signal, () => { logger.info(`Received ${signal}, shutting down gracefully...`); + // Hier könnten noch Aufräumarbeiten stattfinden (z.B. DB-Verbindungen schließen) process.exit(128 + signals[signal]); }); }); \ No newline at end of file diff --git a/frontend/dns-lookup.html b/frontend/dns-lookup.html new file mode 100644 index 0000000..f16a989 --- /dev/null +++ b/frontend/dns-lookup.html @@ -0,0 +1,109 @@ + + + + + + DNS Lookup - uTools + + + + + + + +
+

uTools Network Suite

+ +
+ +
+ +

DNS Lookup

+ + +
+
+ + + +
+ + +
+ + + + + + + +
+ + + + + \ No newline at end of file diff --git a/frontend/dns-lookup.js b/frontend/dns-lookup.js new file mode 100644 index 0000000..355c071 --- /dev/null +++ b/frontend/dns-lookup.js @@ -0,0 +1,132 @@ +// frontend/dns-lookup.js +document.addEventListener('DOMContentLoaded', () => { + // --- DOM Elements (DNS Lookup) --- + const dnsDomainInput = document.getElementById('dns-domain-input'); + const dnsTypeSelect = document.getElementById('dns-type-select'); + const dnsLookupButton = document.getElementById('dns-lookup-button'); + const dnsLookupErrorEl = document.getElementById('dns-lookup-error'); + const dnsLookupResultsSection = document.getElementById('dns-lookup-results-section'); + const dnsLookupQueryEl = document.getElementById('dns-lookup-query'); + const dnsLookupLoader = document.getElementById('dns-lookup-loader'); + const dnsLookupOutputEl = document.getElementById('dns-lookup-output'); + + // --- DOM Elements (Common) --- + const globalErrorEl = document.getElementById('global-error'); + const commitShaEl = document.getElementById('commit-sha'); + + // --- Configuration --- + const API_BASE_URL = '/api'; // Anpassen, falls nötig + + // --- Helper Functions --- + + /** Zeigt globale Fehler an */ + function showGlobalError(message) { + if (!globalErrorEl) return; + globalErrorEl.textContent = `Error: ${message}`; + globalErrorEl.classList.remove('hidden'); + } + + /** Versteckt globale Fehler */ + function hideGlobalError() { + if (!globalErrorEl) return; + globalErrorEl.classList.add('hidden'); + } + + /** + * Generische Funktion zum Abrufen und Anzeigen von Lookup-Ergebnissen. + * @param {string} endpoint - Der API-Endpunkt (z.B. '/dns-lookup'). + * @param {object} params - Query-Parameter als Objekt (z.B. { domain: '...', type: '...' }). + * @param {HTMLElement} resultsSection - Der Container für die Ergebnisse. + * @param {HTMLElement} loaderElement - Das Loader-Element. + * @param {HTMLElement} errorElement - Das Fehleranzeige-Element für diesen Lookup. + * @param {HTMLElement} queryElement - Das Element zur Anzeige der Suchanfrage. + * @param {HTMLElement} outputElement - Das Element zur Anzeige der Ergebnisse (
 oder 

). + * @param {function} displayFn - Funktion zur Formatierung und Anzeige der Daten im outputElement. + */ + async function fetchAndDisplay(endpoint, params, resultsSection, loaderElement, errorElement, queryElement, outputElement, displayFn) { + resultsSection.classList.remove('hidden'); + loaderElement.classList.remove('hidden'); + errorElement.classList.add('hidden'); + outputElement.textContent = ''; // Clear previous results + if (queryElement) queryElement.textContent = Object.values(params).join(', '); // Display query + hideGlobalError(); // Hide global errors before new request + + const urlParams = new URLSearchParams(params); + const url = `${API_BASE_URL}${endpoint}?${urlParams.toString()}`; + + try { + const response = await fetch(url); + const data = await response.json(); + + if (!response.ok || !data.success) { + throw new Error(data.error || `Request failed with status ${response.status}`); + } + + console.log(`Received ${endpoint} data:`, data); + displayFn(data, outputElement); // Call the specific display function + + } catch (error) { + console.error(`Failed to fetch ${endpoint}:`, error); + errorElement.textContent = `Error: ${error.message}`; + errorElement.classList.remove('hidden'); + outputElement.textContent = ''; // Clear output on error + } finally { + loaderElement.classList.add('hidden'); + } + } + + /** Ruft die Versionsinformationen (Commit SHA) ab */ + async function fetchVersionInfo() { + if (!commitShaEl) return; // Don't fetch if element doesn't exist + try { + const response = await fetch(`${API_BASE_URL}/version`); + if (!response.ok) throw new Error(`Network response: ${response.statusText} (${response.status})`); + const data = await response.json(); + commitShaEl.textContent = data.commitSha || 'unknown'; + } catch (error) { + console.error('Failed to fetch version info:', error); + commitShaEl.textContent = 'error'; + // Optionally show global error + // showGlobalError(`Could not load version info: ${error.message}`); + } + } + + // --- DNS Lookup Specific Functions --- + function displayDnsResults(data, outputEl) { + if (!data.records || Object.keys(data.records).length === 0) { + outputEl.textContent = 'No records found for this domain and type.'; + return; + } + // Format output as JSON string for simplicity + outputEl.textContent = JSON.stringify(data.records, null, 2); + } + + function handleDnsLookupClick() { + const domain = dnsDomainInput.value.trim(); + const type = dnsTypeSelect.value; + if (!domain) { + dnsLookupErrorEl.textContent = 'Please enter a domain name.'; + dnsLookupErrorEl.classList.remove('hidden'); + return; + } + fetchAndDisplay( + '/dns-lookup', + { domain, type }, + dnsLookupResultsSection, + dnsLookupLoader, + dnsLookupErrorEl, + dnsLookupQueryEl, + dnsLookupOutputEl, + displayDnsResults + ); + } + + // --- Initial Load & Event Listeners --- + fetchVersionInfo(); // Lade Versionsinfo für Footer + + dnsLookupButton.addEventListener('click', handleDnsLookupClick); + dnsDomainInput.addEventListener('keypress', (event) => { + if (event.key === 'Enter') handleDnsLookupClick(); + }); + +}); // End DOMContentLoaded \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html index d754939..cfae316 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,7 +3,7 @@ - IP Info & Diagnostics + IP Info & Network Tools - uTools @@ -39,7 +39,7 @@ #ip-address { cursor: pointer; } /* Traceroute Output Formatierung */ - #traceroute-output pre { + #traceroute-output pre, .result-pre { /* Gemeinsamer Stil für

 */
             white-space: pre-wrap; /* Zeilenumbruch */
             word-break: break-all; /* Lange Zeilen umbrechen */
             font-family: monospace;
@@ -49,8 +49,9 @@
             border-radius: 0.375rem; /* rounded-md */
             max-height: 400px;
             overflow-y: auto;
+            font-size: 0.875rem; /* text-sm */
         }
-        #traceroute-output .hop-line { margin-bottom: 0.25rem; font-size: 0.875rem; } /* text-sm */
+        #traceroute-output .hop-line { margin-bottom: 0.25rem; }
         #traceroute-output .hop-number { display: inline-block; width: 30px; text-align: right; margin-right: 10px; color: #9ca3af; } /* Grau */
         #traceroute-output .hop-ip { color: #60a5fa; } /* Blau */
         #traceroute-output .hop-hostname { color: #a78bfa; } /* Lila */
@@ -61,30 +62,38 @@
         #traceroute-output .end-line { color: #a78bfa; font-weight: bold; margin-top: 10px;} /* Lila */
 
         /* Navigations-Styling */
-        nav ul { list-style: none; padding: 0; margin: 0; display: flex; gap: 1rem; }
-        nav a { color: #c4b5fd; /* purple-300 */ text-decoration: none; }
+        nav ul { list-style: none; padding: 0; margin: 0; display: flex; flex-wrap: wrap; gap: 1rem; } /* flex-wrap hinzugefügt */
+        nav a { color: #c4b5fd; /* purple-300 */ text-decoration: none; white-space: nowrap; } /* nowrap hinzugefügt */
         nav a:hover { color: #a78bfa; /* purple-400 */ text-decoration: underline; }
-        header { background-color: #374151; /* gray-700 */ padding: 1rem; margin-bottom: 1.5rem; border-radius: 0.5rem; /* rounded-lg */ display: flex; justify-content: space-between; align-items: center; }
+        header { background-color: #374151; /* gray-700 */ padding: 1rem; margin-bottom: 1.5rem; border-radius: 0.5rem; /* rounded-lg */ display: flex; flex-direction: column; align-items: center; gap: 0.5rem; } /* Flex direction geändert */
+        @media (min-width: 768px) { /* md breakpoint */
+             header { flex-direction: row; justify-content: space-between; }
+        }
         header h1 { font-size: 1.5rem; /* text-2xl */ font-weight: bold; color: #e5e7eb; /* gray-200 */ }
 
+        /* Hilfsklasse zum Verstecken */
+        .hidden { display: none; }
+
     
 
 
 
     
-

uTools

+

uTools Network Suite

-

IP Information & Diagnostics

+

IP Information & Network Tools

@@ -150,7 +159,7 @@ class="flex-grow px-3 py-2 bg-gray-800 border border-gray-600 rounded text-gray-200 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent font-mono">
@@ -211,7 +220,6 @@
-