diff --git a/source/server/package-lock.json b/source/server/package-lock.json index 06cae15c..e450f956 100644 --- a/source/server/package-lock.json +++ b/source/server/package-lock.json @@ -12,9 +12,10 @@ "body-parser": "^1.20.1", "cookie-session": "^2.0.0", "express": "^4.17.1", - "express-handlebars": "^6.0.7", + "express-rate-limit": "^7.1.2", + "handlebars": "^4.7.8", "morgan": "^1.10.0", - "sendmail": "^1.6.1", + "nodemailer": "^6.9.7", "sqlite": "^4.1.2", "sqlite3": "^5.1.2", "three": "^0.146.0", @@ -28,7 +29,7 @@ "@types/mocha": "^8.0.0", "@types/morgan": "^1.9.3", "@types/node": "^16", - "@types/sendmail": "^1.4.4", + "@types/nodemailer": "^6.4.14", "@types/supertest": "^2.0.12", "@types/three": "^0.146.0", "chai": "^4.2.0", @@ -342,6 +343,15 @@ "integrity": "sha512-XAMpaw1s1+6zM+jn2tmw8MyaRDIJfXxqmIQIS0HfoGYPuf7dUWeiUKopwq13KFX9lEp1+THGtlaaYx39Nxr58g==", "dev": true }, + "node_modules/@types/nodemailer": { + "version": "6.4.14", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.14.tgz", + "integrity": "sha512-fUWthHO9k9DSdPCSPRqcu6TWhYyxTBg382vlNIttSe9M7XfsT06y0f24KHXtbnijPGGRIcVvdKHTNikOI6qiHA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", @@ -354,12 +364,6 @@ "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", "dev": true }, - "node_modules/@types/sendmail": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/@types/sendmail/-/sendmail-1.4.4.tgz", - "integrity": "sha512-tSr6Y2LFKTp4TD6p/B6sSIXBf8RaIjuQhRpXFy8GxwihC0P1lfzf26C4Pvb6dCdVZu12YVOCeHG3wEx15da/Rg==", - "dev": true - }, "node_modules/@types/serve-static": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", @@ -638,11 +642,6 @@ "acorn": "^8" } }, - "node_modules/addressparser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/addressparser/-/addressparser-1.0.1.tgz", - "integrity": "sha512-aQX7AISOMM7HFE0iZ3+YnD07oIeJqWGVnJ+ZIKaBZAk03ftmVYVqsGas/rbXKR21n4D/hKCSHypvcyOkds/xzg==" - }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -1014,38 +1013,6 @@ "optional": true, "peer": true }, - "node_modules/buildmail": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/buildmail/-/buildmail-3.10.0.tgz", - "integrity": "sha512-6e5sDN/pl3en5Klqdfyir7LEIBiFr9oqZuvYaEyVwjxpIbBZN+98e0j87Fz2Ukl8ud32rbk9VGOZAnsOZ7pkaA==", - "deprecated": "This project is unmaintained", - "dependencies": { - "addressparser": "1.0.1", - "libbase64": "0.1.0", - "libmime": "2.1.0", - "libqp": "1.1.0", - "nodemailer-fetch": "1.6.0", - "nodemailer-shared": "1.1.0" - } - }, - "node_modules/buildmail/node_modules/iconv-lite": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", - "integrity": "sha512-QwVuTNQv7tXC5mMWFX5N5wGjmybjNBBD8P3BReTkPmipoxTUFgWM2gXNvldHQr6T14DH0Dh6qBVg98iJt7u4mQ==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/buildmail/node_modules/libmime": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/libmime/-/libmime-2.1.0.tgz", - "integrity": "sha512-4be2R6/jOasyPTw0BkpIZBVk2cElqjdIdS0PRPhbOCV4wWuL/ZcYYpN1BCTVB+6eIQ0uuAwp5hQTHFrM5Joa8w==", - "dependencies": { - "iconv-lite": "0.4.13", - "libbase64": "0.1.0", - "libqp": "1.1.0" - } - }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -1493,14 +1460,6 @@ "node": ">=0.3.1" } }, - "node_modules/dkim-signer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dkim-signer/-/dkim-signer-0.2.2.tgz", - "integrity": "sha512-24OZ3cCA30UTRz+Plpg+ibfPq3h7tDtsJRg75Bo0pGakZePXcPBddY80bKi1Bi7Jsz7tL5Cw527mhCRDvNFgfg==", - "dependencies": { - "libmime": "^2.0.3" - } - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -1723,54 +1682,15 @@ "node": ">= 0.10.0" } }, - "node_modules/express-handlebars": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/express-handlebars/-/express-handlebars-6.0.7.tgz", - "integrity": "sha512-iYeMFpc/hMD+E6FNAZA5fgWeXnXr4rslOSPkeEV6TwdmpJ5lEXuWX0u9vFYs31P2MURctQq2batR09oeNj0LIg==", - "dependencies": { - "glob": "^8.1.0", - "graceful-fs": "^4.2.10", - "handlebars": "^4.7.7" - }, - "engines": { - "node": ">=v12.22.9" - } - }, - "node_modules/express-handlebars/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/express-handlebars/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, + "node_modules/express-rate-limit": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.1.2.tgz", + "integrity": "sha512-uvkFt5JooXDhUhrfgqXLyIsAMRCtU1o8W/p0Q2p5U2ude7fEOfFaP0kSYbHOHmPbA9ZEm1JqrRne3vL9pVCBXA==", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/express-handlebars/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dependencies": { - "brace-expansion": "^2.0.1" + "node": ">= 16" }, - "engines": { - "node": ">=10" + "peerDependencies": { + "express": "^4 || ^5" } }, "node_modules/fast-deep-equal": { @@ -2025,15 +1945,16 @@ "node_modules/graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "optional": true }, "node_modules/handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dependencies": { "minimist": "^1.2.5", - "neo-async": "^2.6.0", + "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, @@ -2402,34 +2323,6 @@ "node": ">= 0.6" } }, - "node_modules/libbase64": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/libbase64/-/libbase64-0.1.0.tgz", - "integrity": "sha512-B91jifmFw1DKEqEWstSpg1PbtUbBzR4yQAPT86kCQXBtud1AJVA+Z6RSklSrqmKe4q2eiEufgnhqJKPgozzfIQ==" - }, - "node_modules/libmime": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/libmime/-/libmime-2.1.3.tgz", - "integrity": "sha512-ABr2f4O+K99sypmkF/yPz2aXxUFHEZzv+iUkxItCeKZWHHXdQPpDXd6rV1kBBwL4PserzLU09EIzJ2lxC9hPfQ==", - "dependencies": { - "iconv-lite": "0.4.15", - "libbase64": "0.1.0", - "libqp": "1.1.0" - } - }, - "node_modules/libmime/node_modules/iconv-lite": { - "version": "0.4.15", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", - "integrity": "sha512-RGR+c9Lm+tLsvU57FTJJtdbv2hQw42Yl2n26tVIBaYmZzLN+EGfroUugN/z9nJf9kOXd49hBmpoGr4FEm+A4pw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/libqp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/libqp/-/libqp-1.1.0.tgz", - "integrity": "sha512-4Rgfa0hZpG++t1Vi2IiqXG9Ad1ig4QTmtuZF946QJP4bPqOYC78ixUXgz5TW/wE7lNaNKlplSYTxQ+fR2KZ0EA==" - }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -2492,34 +2385,6 @@ "node": ">=10" } }, - "node_modules/mailcomposer": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/mailcomposer/-/mailcomposer-3.12.0.tgz", - "integrity": "sha512-zBeDoKUTNI8IAsazoMQFt3eVSVRtDtgrvBjBVdBjxDEX+5KLlKtEFCrBXnxPhs8aTYufUS1SmbFnGpjHS53deg==", - "deprecated": "This project is unmaintained", - "dependencies": { - "buildmail": "3.10.0", - "libmime": "2.1.0" - } - }, - "node_modules/mailcomposer/node_modules/iconv-lite": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", - "integrity": "sha512-QwVuTNQv7tXC5mMWFX5N5wGjmybjNBBD8P3BReTkPmipoxTUFgWM2gXNvldHQr6T14DH0Dh6qBVg98iJt7u4mQ==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/mailcomposer/node_modules/libmime": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/libmime/-/libmime-2.1.0.tgz", - "integrity": "sha512-4be2R6/jOasyPTw0BkpIZBVk2cElqjdIdS0PRPhbOCV4wWuL/ZcYYpN1BCTVB+6eIQ0uuAwp5hQTHFrM5Joa8w==", - "dependencies": { - "iconv-lite": "0.4.13", - "libbase64": "0.1.0", - "libqp": "1.1.0" - } - }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -3031,17 +2896,12 @@ "optional": true, "peer": true }, - "node_modules/nodemailer-fetch": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/nodemailer-fetch/-/nodemailer-fetch-1.6.0.tgz", - "integrity": "sha512-P7S5CEVGAmDrrpn351aXOLYs1R/7fD5NamfMCHyi6WIkbjS2eeZUB/TkuvpOQr0bvRZicVqo59+8wbhR3yrJbQ==" - }, - "node_modules/nodemailer-shared": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/nodemailer-shared/-/nodemailer-shared-1.1.0.tgz", - "integrity": "sha512-68xW5LSyPWv8R0GLm6veAvm7E+XFXkVgvE3FW0FGxNMMZqMkPFeGDVALfR1DPdSfcoO36PnW7q5AAOgFImEZGg==", - "dependencies": { - "nodemailer-fetch": "1.6.0" + "node_modules/nodemailer": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.7.tgz", + "integrity": "sha512-rUtR77ksqex/eZRLmQ21LKVH5nAAsVicAtAYudK7JgwenEDZ0UIQ1adUGqErz7sMkWYxWTTU1aeP2Jga6WQyJw==", + "engines": { + "node": ">=6.0.0" } }, "node_modules/nopt": { @@ -3453,18 +3313,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "node_modules/sendmail": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/sendmail/-/sendmail-1.6.1.tgz", - "integrity": "sha512-lIhvnjSi5e5jL8wA1GPP6j2QVlx6JOEfmdn0QIfmuJdmXYGmJ375kcOU0NSm/34J+nypm4sa1AXrYE5w3uNIIA==", - "dependencies": { - "dkim-signer": "0.2.2", - "mailcomposer": "3.12.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -4667,6 +4515,15 @@ "integrity": "sha512-XAMpaw1s1+6zM+jn2tmw8MyaRDIJfXxqmIQIS0HfoGYPuf7dUWeiUKopwq13KFX9lEp1+THGtlaaYx39Nxr58g==", "dev": true }, + "@types/nodemailer": { + "version": "6.4.14", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.14.tgz", + "integrity": "sha512-fUWthHO9k9DSdPCSPRqcu6TWhYyxTBg382vlNIttSe9M7XfsT06y0f24KHXtbnijPGGRIcVvdKHTNikOI6qiHA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", @@ -4679,12 +4536,6 @@ "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", "dev": true }, - "@types/sendmail": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/@types/sendmail/-/sendmail-1.4.4.tgz", - "integrity": "sha512-tSr6Y2LFKTp4TD6p/B6sSIXBf8RaIjuQhRpXFy8GxwihC0P1lfzf26C4Pvb6dCdVZu12YVOCeHG3wEx15da/Rg==", - "dev": true - }, "@types/serve-static": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", @@ -4952,11 +4803,6 @@ "peer": true, "requires": {} }, - "addressparser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/addressparser/-/addressparser-1.0.1.tgz", - "integrity": "sha512-aQX7AISOMM7HFE0iZ3+YnD07oIeJqWGVnJ+ZIKaBZAk03ftmVYVqsGas/rbXKR21n4D/hKCSHypvcyOkds/xzg==" - }, "agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -5240,36 +5086,6 @@ "optional": true, "peer": true }, - "buildmail": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/buildmail/-/buildmail-3.10.0.tgz", - "integrity": "sha512-6e5sDN/pl3en5Klqdfyir7LEIBiFr9oqZuvYaEyVwjxpIbBZN+98e0j87Fz2Ukl8ud32rbk9VGOZAnsOZ7pkaA==", - "requires": { - "addressparser": "1.0.1", - "libbase64": "0.1.0", - "libmime": "2.1.0", - "libqp": "1.1.0", - "nodemailer-fetch": "1.6.0", - "nodemailer-shared": "1.1.0" - }, - "dependencies": { - "iconv-lite": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", - "integrity": "sha512-QwVuTNQv7tXC5mMWFX5N5wGjmybjNBBD8P3BReTkPmipoxTUFgWM2gXNvldHQr6T14DH0Dh6qBVg98iJt7u4mQ==" - }, - "libmime": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/libmime/-/libmime-2.1.0.tgz", - "integrity": "sha512-4be2R6/jOasyPTw0BkpIZBVk2cElqjdIdS0PRPhbOCV4wWuL/ZcYYpN1BCTVB+6eIQ0uuAwp5hQTHFrM5Joa8w==", - "requires": { - "iconv-lite": "0.4.13", - "libbase64": "0.1.0", - "libqp": "1.1.0" - } - } - } - }, "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -5609,14 +5425,6 @@ "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", "dev": true }, - "dkim-signer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dkim-signer/-/dkim-signer-0.2.2.tgz", - "integrity": "sha512-24OZ3cCA30UTRz+Plpg+ibfPq3h7tDtsJRg75Bo0pGakZePXcPBddY80bKi1Bi7Jsz7tL5Cw527mhCRDvNFgfg==", - "requires": { - "libmime": "^2.0.3" - } - }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -5801,45 +5609,11 @@ "vary": "~1.1.2" } }, - "express-handlebars": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/express-handlebars/-/express-handlebars-6.0.7.tgz", - "integrity": "sha512-iYeMFpc/hMD+E6FNAZA5fgWeXnXr4rslOSPkeEV6TwdmpJ5lEXuWX0u9vFYs31P2MURctQq2batR09oeNj0LIg==", - "requires": { - "glob": "^8.1.0", - "graceful-fs": "^4.2.10", - "handlebars": "^4.7.7" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "requires": { - "balanced-match": "^1.0.0" - } - }, - "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - } - }, - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "requires": { - "brace-expansion": "^2.0.1" - } - } - } + "express-rate-limit": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.1.2.tgz", + "integrity": "sha512-uvkFt5JooXDhUhrfgqXLyIsAMRCtU1o8W/p0Q2p5U2ude7fEOfFaP0kSYbHOHmPbA9ZEm1JqrRne3vL9pVCBXA==", + "requires": {} }, "fast-deep-equal": { "version": "3.1.3", @@ -6035,15 +5809,16 @@ "graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "optional": true }, "handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "requires": { "minimist": "^1.2.5", - "neo-async": "^2.6.0", + "neo-async": "^2.6.2", "source-map": "^0.6.1", "uglify-js": "^3.1.4", "wordwrap": "^1.0.0" @@ -6320,33 +6095,6 @@ "tsscmp": "1.0.6" } }, - "libbase64": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/libbase64/-/libbase64-0.1.0.tgz", - "integrity": "sha512-B91jifmFw1DKEqEWstSpg1PbtUbBzR4yQAPT86kCQXBtud1AJVA+Z6RSklSrqmKe4q2eiEufgnhqJKPgozzfIQ==" - }, - "libmime": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/libmime/-/libmime-2.1.3.tgz", - "integrity": "sha512-ABr2f4O+K99sypmkF/yPz2aXxUFHEZzv+iUkxItCeKZWHHXdQPpDXd6rV1kBBwL4PserzLU09EIzJ2lxC9hPfQ==", - "requires": { - "iconv-lite": "0.4.15", - "libbase64": "0.1.0", - "libqp": "1.1.0" - }, - "dependencies": { - "iconv-lite": { - "version": "0.4.15", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", - "integrity": "sha512-RGR+c9Lm+tLsvU57FTJJtdbv2hQw42Yl2n26tVIBaYmZzLN+EGfroUugN/z9nJf9kOXd49hBmpoGr4FEm+A4pw==" - } - } - }, - "libqp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/libqp/-/libqp-1.1.0.tgz", - "integrity": "sha512-4Rgfa0hZpG++t1Vi2IiqXG9Ad1ig4QTmtuZF946QJP4bPqOYC78ixUXgz5TW/wE7lNaNKlplSYTxQ+fR2KZ0EA==" - }, "loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -6391,32 +6139,6 @@ "yallist": "^4.0.0" } }, - "mailcomposer": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/mailcomposer/-/mailcomposer-3.12.0.tgz", - "integrity": "sha512-zBeDoKUTNI8IAsazoMQFt3eVSVRtDtgrvBjBVdBjxDEX+5KLlKtEFCrBXnxPhs8aTYufUS1SmbFnGpjHS53deg==", - "requires": { - "buildmail": "3.10.0", - "libmime": "2.1.0" - }, - "dependencies": { - "iconv-lite": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", - "integrity": "sha512-QwVuTNQv7tXC5mMWFX5N5wGjmybjNBBD8P3BReTkPmipoxTUFgWM2gXNvldHQr6T14DH0Dh6qBVg98iJt7u4mQ==" - }, - "libmime": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/libmime/-/libmime-2.1.0.tgz", - "integrity": "sha512-4be2R6/jOasyPTw0BkpIZBVk2cElqjdIdS0PRPhbOCV4wWuL/ZcYYpN1BCTVB+6eIQ0uuAwp5hQTHFrM5Joa8w==", - "requires": { - "iconv-lite": "0.4.13", - "libbase64": "0.1.0", - "libqp": "1.1.0" - } - } - } - }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -6801,18 +6523,10 @@ "optional": true, "peer": true }, - "nodemailer-fetch": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/nodemailer-fetch/-/nodemailer-fetch-1.6.0.tgz", - "integrity": "sha512-P7S5CEVGAmDrrpn351aXOLYs1R/7fD5NamfMCHyi6WIkbjS2eeZUB/TkuvpOQr0bvRZicVqo59+8wbhR3yrJbQ==" - }, - "nodemailer-shared": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/nodemailer-shared/-/nodemailer-shared-1.1.0.tgz", - "integrity": "sha512-68xW5LSyPWv8R0GLm6veAvm7E+XFXkVgvE3FW0FGxNMMZqMkPFeGDVALfR1DPdSfcoO36PnW7q5AAOgFImEZGg==", - "requires": { - "nodemailer-fetch": "1.6.0" - } + "nodemailer": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.7.tgz", + "integrity": "sha512-rUtR77ksqex/eZRLmQ21LKVH5nAAsVicAtAYudK7JgwenEDZ0UIQ1adUGqErz7sMkWYxWTTU1aeP2Jga6WQyJw==" }, "nopt": { "version": "5.0.0", @@ -7102,15 +6816,6 @@ } } }, - "sendmail": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/sendmail/-/sendmail-1.6.1.tgz", - "integrity": "sha512-lIhvnjSi5e5jL8wA1GPP6j2QVlx6JOEfmdn0QIfmuJdmXYGmJ375kcOU0NSm/34J+nypm4sa1AXrYE5w3uNIIA==", - "requires": { - "dkim-signer": "0.2.2", - "mailcomposer": "3.12.0" - } - }, "serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", diff --git a/source/server/package.json b/source/server/package.json index 4c2e3c76..14411261 100755 --- a/source/server/package.json +++ b/source/server/package.json @@ -41,9 +41,10 @@ "body-parser": "^1.20.1", "cookie-session": "^2.0.0", "express": "^4.17.1", - "express-handlebars": "^6.0.7", + "express-rate-limit": "^7.1.2", + "handlebars": "^4.7.8", "morgan": "^1.10.0", - "sendmail": "^1.6.1", + "nodemailer": "^6.9.7", "sqlite": "^4.1.2", "sqlite3": "^5.1.2", "three": "^0.146.0", @@ -57,7 +58,7 @@ "@types/mocha": "^8.0.0", "@types/morgan": "^1.9.3", "@types/node": "^16", - "@types/sendmail": "^1.4.4", + "@types/nodemailer": "^6.4.14", "@types/supertest": "^2.0.12", "@types/three": "^0.146.0", "chai": "^4.2.0", diff --git a/source/server/routes/api/v1/admin/mailtest.ts b/source/server/routes/api/v1/admin/mailtest.ts new file mode 100644 index 00000000..44471427 --- /dev/null +++ b/source/server/routes/api/v1/admin/mailtest.ts @@ -0,0 +1,26 @@ +import { Request, Response } from "express"; +import sendmail from "../../../../utils/mails/send.js"; +import { getUser } from "../../../../utils/locals.js"; +import config from "../../../../utils/config.js"; +import { BadRequestError } from "../../../../utils/errors.js"; + +/** + * Send a test email + * Exposes all possible logs from the emailer + * This is a protected route and requires admin privileges + */ +export default async function handleMailtest(req :Request, res :Response){ + const {username :requester, email:to} = getUser(req); + if(!to){ + throw new BadRequestError("No email address found for user "+ requester); + } + let out = await sendmail({ + to, + subject: config.brand+" test email", + html: [ + `\t
This is a test email sent from the admin panel of ${config.hostname}
`, + ].join("\n")}); + + res.status(200).send(out); +} \ No newline at end of file diff --git a/source/server/routes/api/v1/stats/index.ts b/source/server/routes/api/v1/admin/stats/index.ts similarity index 89% rename from source/server/routes/api/v1/stats/index.ts rename to source/server/routes/api/v1/admin/stats/index.ts index 88778ae0..94b7a3ab 100644 --- a/source/server/routes/api/v1/stats/index.ts +++ b/source/server/routes/api/v1/admin/stats/index.ts @@ -1,5 +1,5 @@ import { Request, Response } from "express"; -import { getVfs } from "../../../../utils/locals.js"; +import { getVfs } from "../../../../../utils/locals.js"; diff --git a/source/server/routes/api/v1/index.ts b/source/server/routes/api/v1/index.ts index c0669908..7acb2d0b 100644 --- a/source/server/routes/api/v1/index.ts +++ b/source/server/routes/api/v1/index.ts @@ -1,10 +1,12 @@ import path from "path"; import { Router } from "express"; +import { rateLimit } from 'express-rate-limit' + import User from "../../../auth/User.js"; import UserManager from "../../../auth/UserManager.js"; import { BadRequestError } from "../../../utils/errors.js"; -import { canAdmin, canRead, getUserManager, isAdministrator, isAdministratorOrOpen, isUser } from "../../../utils/locals.js"; +import { canAdmin, canRead, either, getUserManager, isAdministrator, isAdministratorOrOpen, isUser } from "../../../utils/locals.js"; import wrap from "../../../utils/wrapAsync.js"; import bodyParser from "body-parser"; import { getLogin, getLoginLink, sendLoginLink, postLogin } from "./login.js"; @@ -21,10 +23,10 @@ import postUser from "./users/post.js"; import handleDeleteUser from "./users/uid/delete.js"; import { handlePatchUser } from "./users/uid/patch.js"; import { postSceneHistory } from "./scenes/scene/history/post.js"; -import handleGetStats from "./stats/index.js"; +import handleGetStats from "./admin/stats/index.js"; import postScenes from "./scenes/post.js"; import patchScene from "./scenes/scene/patch.js"; - +import handleMailtest from "./admin/mailtest.js"; const router = Router(); @@ -36,9 +38,10 @@ router.use((req, res, next)=>{ //Browser should always make the request res.set("Cache-Control", "max-age=0, must-revalidate"); next(); -}) +}); -router.get("/stats", isAdministrator, wrap(handleGetStats)); +router.get("/admin/stats", isAdministrator, wrap(handleGetStats)); +router.post("/admin/mailtest", isAdministrator, wrap(handleMailtest)); router.use("/login", (req, res, next)=>{ res.append("Cache-Control", "private"); @@ -47,7 +50,14 @@ router.use("/login", (req, res, next)=>{ router.get("/login", wrap(getLogin)); router.post("/login", bodyParser.json(), postLogin); router.get("/login/:username/link", isAdministrator, wrap(getLoginLink)); -router.post("/login/:username/link", wrap(sendLoginLink)); +router.post("/login/:username/link", either(isAdministrator, rateLimit({ + //Special case of real low rate-limiting for non-admin users to send emails + windowMs: 1 * 60 * 1000, // 1 minute + limit: 1, // Limit each IP to 1 request per `window`. + standardHeaders: 'draft-7', + legacyHeaders: false, +})), wrap(sendLoginLink)); + router.post("/logout", postLogout); diff --git a/source/server/routes/api/v1/login.ts b/source/server/routes/api/v1/login.ts index 06e67756..f42313ad 100644 --- a/source/server/routes/api/v1/login.ts +++ b/source/server/routes/api/v1/login.ts @@ -2,9 +2,8 @@ import { createHmac } from "crypto"; import { Request, RequestHandler, Response } from "express"; import User, { SafeUser } from "../../../auth/User.js"; import { BadRequestError, ForbiddenError, HTTPError } from "../../../utils/errors.js"; -import { getHost, getUser, getUserManager } from "../../../utils/locals.js"; +import { AppLocals, getHost, getUser, getUserManager } from "../../../utils/locals.js"; import sendmail from "../../../utils/mails/send.js"; -import { recoverAccount } from "../../../utils/mails/templates.js"; /** * * @type {RequestHandler} @@ -102,7 +101,6 @@ export async function getLoginLink(req :Request, res :Response){ export async function sendLoginLink(req :Request, res :Response){ let {username} = req.params; - let requester = getUser(req); let userManager = getUserManager(req); let user = await userManager.getUserByName(username); @@ -115,8 +113,19 @@ export async function sendLoginLink(req :Request, res :Response){ payload, getHost(req) ); - let content = recoverAccount({link: link.toString(), expires:payload.expires}); - await sendmail(user.email, content); + + let lang = "fr"; + const mail_content = await (res.app.locals as AppLocals).templates.render(`emails/connection_${lang}`, { + name: user.username, + lang: "fr", + url: link.toString() + }); + await sendmail({ + to: user.email, + subject: "Votre lien de connexion à eCorpus", + html: mail_content, + }); + console.log("sent an account recovery mail to :", user.email); res.status(204).send(); } diff --git a/source/server/server.ts b/source/server/server.ts index 9c093b55..27c7b5d5 100644 --- a/source/server/server.ts +++ b/source/server/server.ts @@ -3,7 +3,6 @@ import path from "path"; import util from "util"; import cookieSession from "cookie-session"; import express from "express"; -import { engine } from 'express-handlebars'; import UserManager from "./auth/UserManager.js"; import { BadRequestError, HTTPError } from "./utils/errors.js"; @@ -15,6 +14,7 @@ import openDatabase from "./vfs/helpers/db.js"; import Vfs from "./vfs/index.js"; import defaultConfig from "./utils/config.js"; import User from "./auth/User.js"; +import Templates from "./utils/templates.js"; export default async function createServer(config = defaultConfig) :PromiseHi {{name}},
+Click the link below to connect automatically:
+ +This link stays valid for 30 days
+You might want to change your password in user settings to be able to reconnect in the future
+Have a great day!
diff --git a/source/server/templates/emails/connection_fr.hbs b/source/server/templates/emails/connection_fr.hbs new file mode 100644 index 00000000..199695f2 --- /dev/null +++ b/source/server/templates/emails/connection_fr.hbs @@ -0,0 +1,22 @@ + +Bonjour {{name}},
+
+ Nous venons de recevoir une demande de lien de connexion pour un compte lié à votre adresse.
+ Si vous n'êtes pas à l'origine de cette demande, vous pouvez ignorer cet email.
+
+ Sinon, vous pouvez vous connecter en cliquant sur le bouton ci-dessous. +
++ Connectez-vous +
++ Pour des raisons de sécurité, ce lien expirera dans 30 jours. +
++ Une fois connecté, n'oubliez pas de réinitialiser votre mot de passe! +
++ Pour rapporter toute erreur, vous pouvez nous contacter sur la page du projet eThesaurus +
diff --git a/source/server/templates/layouts/email.hbs b/source/server/templates/layouts/email.hbs new file mode 100644 index 00000000..b1a13682 --- /dev/null +++ b/source/server/templates/layouts/email.hbs @@ -0,0 +1,27 @@ + + + + + + + + + + {{{body}}} + + \ No newline at end of file diff --git a/source/server/utils/config.ts b/source/server/utils/config.ts index 15a9421f..ef0e96e8 100644 --- a/source/server/utils/config.ts +++ b/source/server/utils/config.ts @@ -1,4 +1,5 @@ import path from "path" +import {hostname} from "os"; const values = { @@ -15,9 +16,9 @@ const values = { dist_dir: [({root_dir}:{root_dir:string})=> path.resolve(root_dir,"dist"), toPath], assets_dir: [({root_dir}:{root_dir:string})=> path.resolve(root_dir,"assets"), toPath], trust_proxy: [true, toBool], - hostname: ["ecorpus.holusion.net", toString], + hostname: [hostname(), toString], hot_reload:[false, toBool], - smart_host: ["localhost", toString], + smart_host: ["smtp://localhost", toString], verbose: [false, toBool], } as const; diff --git a/source/server/utils/locals.test.ts b/source/server/utils/locals.test.ts new file mode 100644 index 00000000..b808f678 --- /dev/null +++ b/source/server/utils/locals.test.ts @@ -0,0 +1,55 @@ +import express, { Express, NextFunction, Request, RequestHandler, Response } from "express"; +import request from "supertest"; +import { InternalError, UnauthorizedError } from "./errors.js"; +import { either } from "./locals.js"; + +//Dummy middlewares +function pass(req :Request, res :Response, next :NextFunction){ + next(); +} + +function fail(req :Request, res :Response, next :NextFunction){ + next(new UnauthorizedError()); +} + +function err(req :Request, res :Response, next :NextFunction){ + next(new InternalError()); +} + +function h(req:Request, res:Response){ + res.status(204).send(); +} + +describe("either() middleware", function(){ + let app :Express; + let handler :RequestHandler; + this.beforeEach(function(){ + app = express(); + //small trick to allow error handling : + process.nextTick(()=>{ + app.use((err:Error, req :Request, res:Response, next :NextFunction)=>{ + res.status((err as any).code ?? 500).send(err.message); + }); + }); + }); + + it("checks each middleware for a pass", async function(){ + app.get("/", either(fail, fail, pass), h); + await request(app).get("/").expect(204); + }); + + it("uses first middleware to pass", async function(){ + app.get("/", either(pass, fail), h); + await request(app).get("/").expect(204); + }); + + it("doesn't allow errors other than UnauthoriezError", async function(){ + app.get("/", either(fail, err), h); + await request(app).get("/").expect(500); + }); + + it("throws if no middleware passed", async function(){ + app.get("/", either(fail, fail), h); + await request(app).get("/").expect(401); + }); +}); \ No newline at end of file diff --git a/source/server/utils/locals.ts b/source/server/utils/locals.ts index c48a9921..cf0d0350 100644 --- a/source/server/utils/locals.ts +++ b/source/server/utils/locals.ts @@ -5,12 +5,14 @@ import User, { SafeUser } from "../auth/User.js"; import UserManager, { AccessType, AccessTypes } from "../auth/UserManager.js"; import Vfs, { GetFileParams } from "../vfs/index.js"; import { BadRequestError, ForbiddenError, HTTPError, InternalError, NotFoundError, UnauthorizedError } from "./errors.js"; +import Templates from "./templates.js"; export interface AppLocals extends Record- Bonjour, -
-
- Nous venons de recevoir une demande de lien de connexion pour un compte lié à votre adresse.
- Si vous n'êtes pas à l'origine de cette demande, vous pouvez ignorer cet email.
-
- Sinon, vous pouvez vous connecter en cliquant sur le bouton ci-dessous. -
-- Connectez-vous -
-- Pour des raisons de sécurité, ce lien expirera le ${p.expires.toLocaleDateString("fr")} à ${p.expires.toLocaleTimeString("fr")}. -
-- Une fois connecté, n'oubliez pas de réinitialiser votre mot de passe! -
-Pour rapporter toute erreur, vous pouvez nous contacter sur la page du projet eThesaurus -`, - -'text':` - Bonjour, - \tNous venons de recevoir une demande de lien de connexion pour un compte lié à votre adresse. - Si vous n'êtes pas à l'origine de cette demande, vous pouvez ignorer cet email. - - Sinon, vous pouvez vous connecter en cliquant sur le lien ci-dessous. - - ${p.link} - - Pour des raisons de sécurité, ce lien expirera le ${p.expires.toLocaleDateString("fr")} à ${p.expires.toLocaleTimeString("fr")}. - - Une fois connecté, n'oubliez pas de réinitialiser votre mot de passe! - - Pour rapporter toute erreur, vous pouvez nous contacter sur la page du projet eThesaurus: https://github.com/Holusion/e-thesaurus -`.replace(/^ +/gm, ""), -}) diff --git a/source/server/utils/templates.test.ts b/source/server/utils/templates.test.ts new file mode 100644 index 00000000..2ff75879 --- /dev/null +++ b/source/server/utils/templates.test.ts @@ -0,0 +1,141 @@ +import fs from "fs/promises"; +import {tmpdir} from "os"; +import path from "path"; +import { fileURLToPath } from 'url'; + +import Templates from "./templates.js"; +import express, { Express, NextFunction, Request, Response } from "express"; +import request from "supertest"; + +const thisDir = path.dirname(fileURLToPath(import.meta.url)); + +describe("Templates", function(){ + + describe("in test directory", function(){ + let dir:string; + this.beforeAll(async function(){ + dir = await fs.mkdtemp(path.join(tmpdir(), "ecorpus_templates_test")); + await fs.mkdir(path.join(dir, "layouts")); + }); + + this.afterAll(async function(){ + await fs.rm(dir, {recursive: true}); + }); + + describe("with a basic template", function(){ + let tmpl :string; + let t :Templates + this.beforeAll(async function(){ + tmpl = path.join(dir, "home.hbs"); + await fs.writeFile(tmpl, "Hello {{name}}"); + t = new Templates({dir, cache: false}); + }); + this.afterAll(async function(){ + await fs.rm(tmpl); + }) + + it("renders a template", async function(){ + let txt = await t.render(tmpl, {name: "World", layout: null}); + expect(txt).to.be.a.string; + expect(txt).to.equal("Hello World"); + }); + + it("renders a template with relative path", async function(){ + let txt = await t.render(path.basename(tmpl), {name: "World", layout: null}); + expect(txt).to.be.a.string; + expect(txt).to.equal("Hello World"); + }); + + it("renders a template without file extension", async function(){ + let txt = await t.render(path.basename(tmpl.slice(0,-4)), {name: "World", layout: null}); + expect(txt).to.be.a.string; + expect(txt).to.equal("Hello World"); + }); + + it("renders a template with default layout", async function(){ + await fs.writeFile(path.join(dir, "layouts", "main.hbs"), "
${this.state}
+