Added basic app.

This commit is contained in:
SamTV12345 2024-03-18 19:04:59 +01:00
parent b3741470ed
commit 3e18f65d3b
17 changed files with 581 additions and 382 deletions

248
pnpm-lock.yaml generated
View file

@ -124,9 +124,21 @@ importers:
src: src:
dependencies: dependencies:
'@fastify/compress':
specifier: ^7.0.0
version: 7.0.0
'@fastify/cookie':
specifier: ^9.3.1
version: 9.3.1
'@fastify/express': '@fastify/express':
specifier: ^2.3.0 specifier: ^2.3.0
version: 2.3.0 version: 2.3.0
'@fastify/rate-limit':
specifier: ^9.1.0
version: 9.1.0
'@fastify/session':
specifier: ^10.7.0
version: 10.7.0
'@fastify/static': '@fastify/static':
specifier: ^7.0.1 specifier: ^7.0.1
version: 7.0.1 version: 7.0.1
@ -154,12 +166,9 @@ importers:
etherpad-yajsml: etherpad-yajsml:
specifier: 0.0.12 specifier: 0.0.12
version: 0.0.12 version: 0.0.12
express:
specifier: 4.18.3
version: 4.18.3
express-rate-limit: express-rate-limit:
specifier: ^7.2.0 specifier: ^7.2.0
version: 7.2.0(express@4.18.3) version: 7.2.0
express-session: express-session:
specifier: npm:@etherpad/express-session@^1.18.2 specifier: npm:@etherpad/express-session@^1.18.2
version: /@etherpad/express-session@1.18.2 version: /@etherpad/express-session@1.18.2
@ -281,9 +290,6 @@ importers:
'@types/async': '@types/async':
specifier: ^3.2.24 specifier: ^3.2.24
version: 3.2.24 version: 3.2.24
'@types/express':
specifier: ^4.17.21
version: 4.17.21
'@types/http-errors': '@types/http-errors':
specifier: ^2.0.4 specifier: ^2.0.4
version: 2.0.4 version: 2.0.4
@ -819,6 +825,26 @@ packages:
fast-uri: 2.3.0 fast-uri: 2.3.0
dev: false dev: false
/@fastify/compress@7.0.0:
resolution: {integrity: sha512-jo/NaBVHP1OXIf8Kmr3bZyYQB0gAIgcy5c8rRKTPjhklHO7lRs/6ZFckUVT0NtbKSvrTuIcmSkxYpjyv3FNHXA==}
dependencies:
'@fastify/accept-negotiator': 1.1.0
fastify-plugin: 4.5.1
into-stream: 6.0.0
mime-db: 1.52.0
minipass: 7.0.4
peek-stream: 1.1.3
pump: 3.0.0
pumpify: 2.0.1
dev: false
/@fastify/cookie@9.3.1:
resolution: {integrity: sha512-h1NAEhB266+ZbZ0e9qUE6NnNR07i7DnNXWG9VbbZ8uC6O/hxHpl+Zoe5sw1yfdZ2U6XhToUGDnzQtWJdCaPwfg==}
dependencies:
cookie-signature: 1.2.1
fastify-plugin: 4.5.1
dev: false
/@fastify/error@3.4.1: /@fastify/error@3.4.1:
resolution: {integrity: sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ==} resolution: {integrity: sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ==}
dev: false dev: false
@ -844,6 +870,14 @@ packages:
fast-deep-equal: 3.1.3 fast-deep-equal: 3.1.3
dev: false dev: false
/@fastify/rate-limit@9.1.0:
resolution: {integrity: sha512-h5dZWCkuZXN0PxwqaFQLxeln8/LNwQwH9popywmDCFdKfgpi4b/HoMH1lluy6P+30CG9yzzpSpwTCIPNB9T1JA==}
dependencies:
'@lukeed/ms': 2.0.2
fastify-plugin: 4.5.1
toad-cache: 3.7.0
dev: false
/@fastify/send@2.1.0: /@fastify/send@2.1.0:
resolution: {integrity: sha512-yNYiY6sDkexoJR0D8IDy3aRP3+L4wdqCpvx5WP+VtEU58sn7USmKynBzDQex5X42Zzvw2gNzzYgP90UfWShLFA==} resolution: {integrity: sha512-yNYiY6sDkexoJR0D8IDy3aRP3+L4wdqCpvx5WP+VtEU58sn7USmKynBzDQex5X42Zzvw2gNzzYgP90UfWShLFA==}
dependencies: dependencies:
@ -854,6 +888,13 @@ packages:
mime: 3.0.0 mime: 3.0.0
dev: false dev: false
/@fastify/session@10.7.0:
resolution: {integrity: sha512-ECA75gnyaxcyIukgyO2NGT3XdbLReNl/pTKrrkRfDc6pVqNtdptwwfx9KXrIMOfsO4B3m84eF3wZ9GgnebiZ4w==}
dependencies:
fastify-plugin: 4.5.1
safe-stable-stringify: 2.4.3
dev: false
/@fastify/static@7.0.1: /@fastify/static@7.0.1:
resolution: {integrity: sha512-i1p/nELMknAisNfnjo7yhfoUOdKzA+n92QaMirv2NkZrJ1Wl12v2nyTYlDwPN8XoStMBAnRK/Kx6zKmfrXUPXw==} resolution: {integrity: sha512-i1p/nELMknAisNfnjo7yhfoUOdKzA+n92QaMirv2NkZrJ1Wl12v2nyTYlDwPN8XoStMBAnRK/Kx6zKmfrXUPXw==}
dependencies: dependencies:
@ -1767,19 +1808,6 @@ packages:
resolution: {integrity: sha512-8iHVLHsCCOBKjCF2KwFe0p9Z3rfM9mL+sSP8btyR5vTjJRAqpBYD28/ZLgXPf0pjG1VxOvtCV/BgXkQbpSe8Hw==} resolution: {integrity: sha512-8iHVLHsCCOBKjCF2KwFe0p9Z3rfM9mL+sSP8btyR5vTjJRAqpBYD28/ZLgXPf0pjG1VxOvtCV/BgXkQbpSe8Hw==}
dev: true dev: true
/@types/body-parser@1.19.5:
resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==}
dependencies:
'@types/connect': 3.4.38
'@types/node': 20.11.28
dev: true
/@types/connect@3.4.38:
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
dependencies:
'@types/node': 20.11.28
dev: true
/@types/cookie@0.4.1: /@types/cookie@0.4.1:
resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==}
dev: false dev: false
@ -1804,24 +1832,6 @@ packages:
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
dev: true dev: true
/@types/express-serve-static-core@4.17.43:
resolution: {integrity: sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==}
dependencies:
'@types/node': 20.11.28
'@types/qs': 6.9.11
'@types/range-parser': 1.2.7
'@types/send': 0.17.4
dev: true
/@types/express@4.17.21:
resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==}
dependencies:
'@types/body-parser': 1.19.5
'@types/express-serve-static-core': 4.17.43
'@types/qs': 6.9.11
'@types/serve-static': 1.15.5
dev: true
/@types/fs-extra@9.0.13: /@types/fs-extra@9.0.13:
resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==} resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==}
dependencies: dependencies:
@ -1877,14 +1887,6 @@ packages:
resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==}
dev: true dev: true
/@types/mime@1.3.5:
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
dev: true
/@types/mime@3.0.4:
resolution: {integrity: sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==}
dev: true
/@types/mocha@10.0.6: /@types/mocha@10.0.6:
resolution: {integrity: sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg==} resolution: {integrity: sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg==}
dev: true dev: true
@ -1915,14 +1917,6 @@ packages:
resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==} resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==}
dev: true dev: true
/@types/qs@6.9.11:
resolution: {integrity: sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==}
dev: true
/@types/range-parser@1.2.7:
resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
dev: true
/@types/react-dom@18.2.21: /@types/react-dom@18.2.21:
resolution: {integrity: sha512-gnvBA/21SA4xxqNXEwNiVcP0xSGHh/gi1VhWv9Bl46a0ItbTT5nFY+G9VSQpaG/8N/qdJpJ+vftQ4zflTtnjLw==} resolution: {integrity: sha512-gnvBA/21SA4xxqNXEwNiVcP0xSGHh/gi1VhWv9Bl46a0ItbTT5nFY+G9VSQpaG/8N/qdJpJ+vftQ4zflTtnjLw==}
dependencies: dependencies:
@ -1944,21 +1938,6 @@ packages:
/@types/semver@7.5.8: /@types/semver@7.5.8:
resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
/@types/send@0.17.4:
resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==}
dependencies:
'@types/mime': 1.3.5
'@types/node': 20.11.28
dev: true
/@types/serve-static@1.15.5:
resolution: {integrity: sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==}
dependencies:
'@types/http-errors': 2.0.4
'@types/mime': 3.0.4
'@types/node': 20.11.28
dev: true
/@types/sinon@17.0.3: /@types/sinon@17.0.3:
resolution: {integrity: sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw==} resolution: {integrity: sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw==}
dependencies: dependencies:
@ -2794,6 +2773,11 @@ packages:
resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
dev: false dev: false
/cookie-signature@1.2.1:
resolution: {integrity: sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw==}
engines: {node: '>=6.6.0'}
dev: false
/cookie@0.4.1: /cookie@0.4.1:
resolution: {integrity: sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==} resolution: {integrity: sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
@ -2817,6 +2801,10 @@ packages:
/cookiejar@2.1.4: /cookiejar@2.1.4:
resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==}
/core-util-is@1.0.3:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
dev: false
/cors@2.8.5: /cors@2.8.5:
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
engines: {node: '>= 0.10'} engines: {node: '>= 0.10'}
@ -3018,6 +3006,24 @@ packages:
tslib: 2.6.2 tslib: 2.6.2
dev: true dev: true
/duplexify@3.7.1:
resolution: {integrity: sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==}
dependencies:
end-of-stream: 1.4.4
inherits: 2.0.4
readable-stream: 2.3.8
stream-shift: 1.0.3
dev: false
/duplexify@4.1.3:
resolution: {integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==}
dependencies:
end-of-stream: 1.4.4
inherits: 2.0.4
readable-stream: 3.6.2
stream-shift: 1.0.3
dev: false
/eastasianwidth@0.2.0: /eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
dev: false dev: false
@ -3656,13 +3662,11 @@ packages:
engines: {node: '>=0.8.x'} engines: {node: '>=0.8.x'}
dev: false dev: false
/express-rate-limit@7.2.0(express@4.18.3): /express-rate-limit@7.2.0:
resolution: {integrity: sha512-T7nul1t4TNyfZMJ7pKRKkdeVJWa2CqB8NA1P8BwYaoDI5QSBZARv5oMS43J7b7I5P+4asjVXjb7ONuwDKucahg==} resolution: {integrity: sha512-T7nul1t4TNyfZMJ7pKRKkdeVJWa2CqB8NA1P8BwYaoDI5QSBZARv5oMS43J7b7I5P+4asjVXjb7ONuwDKucahg==}
engines: {node: '>= 16'} engines: {node: '>= 16'}
peerDependencies: peerDependencies:
express: 4 || 5 || ^5.0.0-beta.1 express: 4 || 5 || ^5.0.0-beta.1
dependencies:
express: 4.18.3
dev: false dev: false
/express@4.18.3: /express@4.18.3:
@ -3946,6 +3950,13 @@ packages:
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
dev: false dev: false
/from2@2.3.0:
resolution: {integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==}
dependencies:
inherits: 2.0.4
readable-stream: 2.3.8
dev: false
/fs-extra@10.1.0: /fs-extra@10.1.0:
resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -4419,6 +4430,14 @@ packages:
side-channel: 1.0.5 side-channel: 1.0.5
dev: true dev: true
/into-stream@6.0.0:
resolution: {integrity: sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==}
engines: {node: '>=10'}
dependencies:
from2: 2.3.0
p-is-promise: 3.0.0
dev: false
/invariant@2.2.4: /invariant@2.2.4:
resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
dependencies: dependencies:
@ -4594,6 +4613,10 @@ packages:
call-bind: 1.0.7 call-bind: 1.0.7
dev: true dev: true
/isarray@1.0.0:
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
dev: false
/isarray@2.0.5: /isarray@2.0.5:
resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
dev: true dev: true
@ -5042,6 +5065,11 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: false dev: false
/minipass@7.0.4:
resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==}
engines: {node: '>=16 || 14 >=14.17'}
dev: false
/minizlib@2.1.2: /minizlib@2.1.2:
resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
@ -5301,6 +5329,11 @@ packages:
type-check: 0.4.0 type-check: 0.4.0
dev: true dev: true
/p-is-promise@3.0.0:
resolution: {integrity: sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==}
engines: {node: '>=8'}
dev: false
/p-limit@3.1.0: /p-limit@3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -5380,6 +5413,14 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true dev: true
/peek-stream@1.1.3:
resolution: {integrity: sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==}
dependencies:
buffer-from: 1.1.2
duplexify: 3.7.1
through2: 2.0.5
dev: false
/picocolors@1.0.0: /picocolors@1.0.0:
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
dev: true dev: true
@ -5472,6 +5513,10 @@ packages:
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
dev: true dev: true
/process-nextick-args@2.0.1:
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
dev: false
/process-warning@3.0.0: /process-warning@3.0.0:
resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==} resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==}
dev: false dev: false
@ -5514,6 +5559,14 @@ packages:
once: 1.4.0 once: 1.4.0
dev: false dev: false
/pumpify@2.0.1:
resolution: {integrity: sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==}
dependencies:
duplexify: 4.1.3
inherits: 2.0.4
pump: 3.0.0
dev: false
/punycode@2.3.1: /punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -5698,6 +5751,27 @@ packages:
loose-envify: 1.4.0 loose-envify: 1.4.0
dev: true dev: true
/readable-stream@2.3.8:
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
dependencies:
core-util-is: 1.0.3
inherits: 2.0.4
isarray: 1.0.0
process-nextick-args: 2.0.1
safe-buffer: 5.1.2
string_decoder: 1.1.1
util-deprecate: 1.0.2
dev: false
/readable-stream@3.6.2:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'}
dependencies:
inherits: 2.0.4
string_decoder: 1.3.0
util-deprecate: 1.0.2
dev: false
/readable-stream@4.5.2: /readable-stream@4.5.2:
resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==} resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@ -5868,6 +5942,10 @@ packages:
isarray: 2.0.5 isarray: 2.0.5
dev: true dev: true
/safe-buffer@5.1.2:
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
dev: false
/safe-buffer@5.2.1: /safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
@ -6151,6 +6229,10 @@ packages:
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
dev: false dev: false
/stream-shift@1.0.3:
resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==}
dev: false
/streamroller@3.1.5: /streamroller@3.1.5:
resolution: {integrity: sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==} resolution: {integrity: sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==}
engines: {node: '>=8.0'} engines: {node: '>=8.0'}
@ -6204,6 +6286,12 @@ packages:
es-abstract: 1.22.4 es-abstract: 1.22.4
dev: true dev: true
/string_decoder@1.1.1:
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
dependencies:
safe-buffer: 5.1.2
dev: false
/string_decoder@1.3.0: /string_decoder@1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
dependencies: dependencies:
@ -6352,6 +6440,13 @@ packages:
- supports-color - supports-color
dev: false dev: false
/through2@2.0.5:
resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==}
dependencies:
readable-stream: 2.3.8
xtend: 4.0.2
dev: false
/tiny-worker@2.3.0: /tiny-worker@2.3.0:
resolution: {integrity: sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g==} resolution: {integrity: sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g==}
requiresBuild: true requiresBuild: true
@ -6694,6 +6789,10 @@ packages:
react: 18.2.0 react: 18.2.0
dev: true dev: true
/util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: false
/utils-merge@1.0.1: /utils-merge@1.0.1:
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
engines: {node: '>= 0.4.0'} engines: {node: '>= 0.4.0'}
@ -6936,6 +7035,11 @@ packages:
resolution: {integrity: sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==} resolution: {integrity: sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==}
engines: {node: '>=0.4.0'} engines: {node: '>=0.4.0'}
/xtend@4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
dev: false
/y18n@5.0.8: /y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'} engines: {node: '>=10'}

View file

@ -10,21 +10,26 @@ import events from 'events';
// @ts-ignore // @ts-ignore
import expressSession from 'express-session'; import expressSession from 'express-session';
import fs from 'fs'; import fs from 'fs';
const hooks = require('../../static/js/pluginfw/hooks'); const hooks = require('../../static/js/pluginfw/hooks');
import log4js from 'log4js'; import log4js from 'log4js';
const SessionStore = require('../db/SessionStore'); const SessionStore = require('../db/SessionStore');
const settings = require('../utils/Settings'); const settings = require('../utils/Settings');
const stats = require('../stats') const stats = require('../stats')
import util from 'util'; import util from 'util';
const webaccess = require('./express/webaccess'); const webaccess = require('./express/webaccess');
import Fastify from 'fastify'; import Fastify from 'fastify';
import SecretRotator from '../security/SecretRotator'; import SecretRotator from '../security/SecretRotator';
import fastifyCookie from '@fastify/cookie';
import fastifySession from "@fastify/session";
let secretRotator: SecretRotator|null = null; let secretRotator: SecretRotator | null = null;
const logger = log4js.getLogger('http'); const logger = log4js.getLogger('http');
let serverName:string; let serverName: string;
let sessionStore: { shutdown: () => void; } | null; let sessionStore: typeof SessionStore | null;
const sockets:Set<Socket> = new Set(); const sockets: Set<Socket> = new Set();
const socketsEvents = new events.EventEmitter(); const socketsEvents = new events.EventEmitter();
const startTime = stats.settableGauge('httpStartTime'); const startTime = stats.settableGauge('httpStartTime');
@ -112,7 +117,7 @@ exports.restartServer = async () => {
} }
} }
}); });
await fastify.register(require('@fastify/express')) //await fastify.register(require('@fastify/express'))
if (settings.ssl) { if (settings.ssl) {
@ -136,7 +141,7 @@ exports.restartServer = async () => {
exports.server = fastify.server exports.server = fastify.server
fastify.use((req, res, next) => { fastify.addHook('onRequest', async (req, res) => {
// res.header("X-Frame-Options", "deny"); // breaks embedded pads // res.header("X-Frame-Options", "deny"); // breaks embedded pads
if (settings.ssl) { if (settings.ssl) {
// we use SSL // we use SSL
@ -160,8 +165,6 @@ exports.restartServer = async () => {
if (settings.exposeVersion) { if (settings.exposeVersion) {
res.header('Server', serverName); res.header('Server', serverName);
} }
next();
}); });
if (settings.trustProxy) { if (settings.trustProxy) {
@ -174,16 +177,6 @@ exports.restartServer = async () => {
fastify.enable('trust proxy'); fastify.enable('trust proxy');
} }
// Measure response time
fastify.use((req, res, next) => {
const stopWatch = stats.timer('httpRequests').start();
const sendFn = res.send.bind(res);
res.send = (...args) => {
stopWatch.end();
return sendFn(...args);
};
next();
});
// If the log level specified in the config file is WARN or ERROR the application server never // If the log level specified in the config file is WARN or ERROR the application server never
// starts listening to requests as reported in issue #158. Not installing the log4js connect // starts listening to requests as reported in issue #158. Not installing the log4js connect
@ -201,9 +194,6 @@ exports.restartServer = async () => {
secret = secretRotator.secrets; secret = secretRotator.secrets;
} }
if (!secret) throw new Error('missing cookie signing secret'); if (!secret) throw new Error('missing cookie signing secret');
fastify.use(cookieParser(secret, {}));
sessionStore = new SessionStore(settings.cookie.sessionRefreshInterval); sessionStore = new SessionStore(settings.cookie.sessionRefreshInterval);
exports.sessionMiddleware = expressSession({ exports.sessionMiddleware = expressSession({
propagateTouch: true, propagateTouch: true,
@ -239,17 +229,49 @@ exports.restartServer = async () => {
}, },
}); });
// Give plugins an opportunity to install handlers/middleware before the express-session // Give plugins an opportunity to install handlers/middleware before the express-session
// middleware. This allows plugins to avoid creating an express-session record in the database // middleware. This allows plugins to avoid creating an express-session record in the database
// when it is not needed (e.g., public static content). // when it is not needed (e.g., public static content).
await hooks.aCallAll('expressPreSession', {app:fastify}); await hooks.aCallAll('expressPreSession', {app: fastify});
fastify.use(exports.sessionMiddleware);
fastify.use(webaccess.checkAccess); /*fastify.addHook('preHandler', (req, res, next) => {
cookieParser(secret, {})(req,res,next)
})*/
fastify.register(fastifyCookie, {
hook: 'onRequest',
secret
})
fastify.register(fastifySession, {
secret,
cookie: {
secure: 'auto',
maxAge: sessionLifetime || null,
sameSite: settings.cookie.sameSite,
},
saveUninitialized: false,
prefix: 's:',
rolling: true, cookieName: 'express_sid',
})
fastify.addHook('preHandler', (request, reply, next) => {
request.session.user = "max";
next()
})
/*fastify.addHook('preHandler', (req, res, next) => {
exports.sessionMiddleware(req, res, next);
})*/
fastify.addHook('preHandler', (req, res, next) => {
webaccess.checkAccess(req, res, next);
})
await Promise.all([ await Promise.all([
hooks.aCallAll('expressConfigure', {app: fastify}), hooks.aCallAll('expressConfigure', {app: fastify}),
hooks.aCallAll('expressCreateServer', {app:fastify, server: fastify}), hooks.aCallAll('expressCreateServer', {app: fastify, server: fastify}),
]); ]);
exports.server.on('connection', (socket: Socket) => { exports.server.on('connection', (socket: Socket) => {
sockets.add(socket); sockets.add(socket);
@ -259,6 +281,8 @@ exports.restartServer = async () => {
socketsEvents.emit('updated'); socketsEvents.emit('updated');
}); });
}); });
console.log('Listening on port: ' + settings.port)
// Run the server! // Run the server!
await fastify.listen({ await fastify.listen({
port: settings.port, port: settings.port,
@ -267,6 +291,6 @@ exports.restartServer = async () => {
logger.info('HTTP server listening for connections'); logger.info('HTTP server listening for connections');
} }
exports.shutdown = async (hookName:string, context: any) => { exports.shutdown = async (hookName: string, context: any) => {
await closeServer(); await closeServer();
}; };

View file

@ -2,7 +2,7 @@
import {ArgsExpressType} from "../../types/ArgsExpressType"; import {ArgsExpressType} from "../../types/ArgsExpressType";
import path from "path"; import path from "path";
import fs from "fs"; import fs from "fs";
import express from "express";
const settings = require('ep_etherpad-lite/node/utils/Settings'); const settings = require('ep_etherpad-lite/node/utils/Settings');
const ADMIN_PATH = path.join(settings.root, 'src', 'templates', 'admin'); const ADMIN_PATH = path.join(settings.root, 'src', 'templates', 'admin');
@ -14,13 +14,21 @@ const ADMIN_PATH = path.join(settings.root, 'src', 'templates', 'admin');
* @param {Function} cb the callback function * @param {Function} cb the callback function
* @return {*} * @return {*}
*/ */
exports.expressCreateServer = (hookName:string, args: ArgsExpressType, cb:Function): any => { exports.expressCreateServer = (hookName: string, args: ArgsExpressType, cb: Function): any => {
args.app.use('/admin/', express.static(path.join(__dirname, '../../../templates/admin'), {maxAge: 1000 * 60 * 60 * 24})); args.app.register((instance, opts, next) => {
args.app.get('/admin/*', (_request:any, response:any)=>{ instance.register(require('@fastify/static'), {
response.sendFile(path.resolve(__dirname,'../../../templates/admin', 'index.html')); root: ADMIN_PATH,
} ) prefix: '/', // optional: default '/'
args.app.get('/admin', (req:any, res:any, next:Function) => { constraints: {} // optional: default {}
if ('/' !== req.path[req.path.length - 1]) return res.redirect('./admin/'); })
instance.setNotFoundHandler((req, res) => {
const index = path.join(ADMIN_PATH, 'index.html');
const file = fs.readFileSync(index, 'utf8')
res.type('text/html').send(file);
})
next()
}, {
prefix: '/admin'
}) })
return cb(); return cb();
}; };

View file

@ -1,40 +1,45 @@
'use strict'; 'use strict';
import {FastifyInstance} from "fastify";
const log4js = require('log4js'); const log4js = require('log4js');
const clientLogger = log4js.getLogger('client'); const clientLogger = log4js.getLogger('client');
const {Formidable} = require('formidable');
const apiHandler = require('../../handler/APIHandler'); const apiHandler = require('../../handler/APIHandler');
const util = require('util'); const util = require('util');
exports.expressPreSession = async (hookName:string, {app}:any) => { exports.expressPreSession = async (hookName:string, {app}:{
app: FastifyInstance
}) => {
// The Etherpad client side sends information about how a disconnect happened // The Etherpad client side sends information about how a disconnect happened
app.post('/ep/pad/connection-diagnostic-info', async (req:any, res:any) => { app.post('/ep/pad/connection-diagnostic-info', async (req:any, res:any) => {
const [fields, files] = await (new Formidable({})).parse(req); /*const [fields, files] = await (new Formidable({})).parse(req);
clientLogger.info(`DIAGNOSTIC-INFO: ${fields.diagnosticInfo}`); clientLogger.info(`DIAGNOSTIC-INFO: ${fields.diagnosticInfo}`);
res.end('OK'); */
res.send('ok');
}); });
const parseJserrorForm = async (req:any) => {
const form = new Formidable({
maxFileSize: 1, // Files are not expected. Not sure if 0 means unlimited, so 1 is used.
});
const [fields, files] = await form.parse(req);
return fields.errorInfo;
};
// The Etherpad client side sends information about client side javscript errors // The Etherpad client side sends information about client side javscript errors
app.post('/jserror', (req:any, res:any, next:Function) => { app.post<{
(async () => { Body: {
const data = JSON.parse(await parseJserrorForm(req)); errorId: string,
clientLogger.warn(`${data.msg} --`, { type: string,
[util.inspect.custom]: (depth: number, options:any) => { msg: string,
url: string,
source: string,
linenumber: number,
userAgent: string,
stack: string,
}
}>('/jserror', async (req, res) => {
clientLogger.warn(`${req.body.msg} --`, {
[util.inspect.custom]: (depth: number, options: any) => {
// Depth is forced to infinity to ensure that all of the provided data is logged. // Depth is forced to infinity to ensure that all of the provided data is logged.
options = Object.assign({}, options, {depth: Infinity, colors: true}); options = Object.assign({}, options, {depth: Infinity, colors: true});
return util.inspect(data, options); return util.inspect(req.body, options);
}, },
}); });
res.end('OK'); res.send('ok');
})().catch((err) => next(err || new Error(err)));
}); });
// Provide a possibility to query the latest available API version // Provide a possibility to query the latest available API version

View file

@ -9,14 +9,16 @@ exports.expressCreateServer = (hook_name:string, args: ArgsExpressType, cb:Funct
exports.app = args.app; exports.app = args.app;
// Handle errors // Handle errors
args.app.use((err:ErrorCaused, req:any, res:any, next:Function) => { args.app.setErrorHandler((error, request, reply) => {
// if an error occurs Connect will pass it down // if an error occurs Connect will pass it down
// through these "error-handling" middleware // through these "error-handling" middleware
// allowing you to respond however you like // allowing you to respond however you like
res.status(500).send({error: 'Sorry, something bad happened!'}); console.log('Error:', error);
console.error(err.stack ? err.stack : err.toString()); console.log('Request:', request.url);
reply.status(500).send({error: 'Sorry, something bad happened!'});
console.error(error.stack ? error.stack : error.toString());
stats.meter('http500').mark(); stats.meter('http500').mark();
}); })
return cb(); return cb();
}; };

View file

@ -11,11 +11,18 @@ const readOnlyManager = require('../../db/ReadOnlyManager');
const rateLimit = require('express-rate-limit'); const rateLimit = require('express-rate-limit');
const securityManager = require('../../db/SecurityManager'); const securityManager = require('../../db/SecurityManager');
const webaccess = require('./webaccess'); const webaccess = require('./webaccess');
import fLimiter from '@fastify/rate-limit'
exports.expressCreateServer = (hookName:string, args:ArgsExpressType, cb:Function) => { exports.expressCreateServer = async (hookName: string, args: ArgsExpressType, cb: Function) => {
await args.app.register(fLimiter, {
global: false,
...settings.importExportRateLimiting
})
const limiter = rateLimit({ const limiter = rateLimit({
...settings.importExportRateLimiting, ...settings.importExportRateLimiting,
handler: (request:any) => { handler: (request: any) => {
if (request.rateLimit.current === request.rateLimit.limit + 1) { if (request.rateLimit.current === request.rateLimit.limit + 1) {
// when the rate limiter triggers, write a warning in the logs // when the rate limiter triggers, write a warning in the logs
console.warn('Import/Export rate limiter triggered on ' + console.warn('Import/Export rate limiter triggered on ' +
@ -25,12 +32,11 @@ exports.expressCreateServer = (hookName:string, args:ArgsExpressType, cb:Functio
}); });
const handleImport = (req:any, res:any, next:Function) => { const handleImport = async (req: any, res: any) => {
(async () => {
const types = ['pdf', 'doc', 'txt', 'html', 'odt', 'etherpad']; const types = ['pdf', 'doc', 'txt', 'html', 'odt', 'etherpad'];
// send a 404 if we don't support this filetype // send a 404 if we don't support this filetype
if (types.indexOf(req.params.type) === -1) { if (types.indexOf(req.params.type) === -1) {
return next(); return res.send(409);
} }
// if abiword is disabled, and this is a format we only support with abiword, output a message // if abiword is disabled, and this is a format we only support with abiword, output a message
@ -60,25 +66,44 @@ exports.expressCreateServer = (hookName:string, args:ArgsExpressType, cb:Functio
const exists = await padManager.doesPadExists(padId); const exists = await padManager.doesPadExists(padId);
if (!exists) { if (!exists) {
console.warn(`Someone tried to export a pad that doesn't exist (${padId})`); console.warn(`Someone tried to export a pad that doesn't exist (${padId})`);
return next(); return {
code: 404,
message: 'notfound',
};
} }
console.log(`Exporting pad "${req.params.pad}" in ${req.params.type} format`); console.log(`Exporting pad "${req.params.pad}" in ${req.params.type} format`);
await exportHandler.doExport(req, res, padId, readOnlyId, req.params.type); await exportHandler.doExport(req, res, padId, readOnlyId, req.params.type);
} }
})().catch((err) => next(err || new Error(err)));
}; };
// handle export requests // handle export requests
args.app.use('/p/:pad/export/:type', limiter);
args.app.use('/p/:pad/:rev/export/:type', limiter);
args.app.get('/p/:pad/:rev/export/:type', handleImport); args.app.get('/p/:pad/:rev/export/:type',{
args.app.get('/p/:pad/export/:type', handleImport); config: {
rateLimit: {
...settings.importExportRateLimiting
}
}
}, handleImport);
args.app.get('/p/:pad/export/:type',{
config: {
rateLimit: {
...settings.importExportRateLimiting
}
}
}, handleImport);
// handle import requests // handle import requests
args.app.use('/p/:pad/import', limiter); args.app.get('/p/:pad/import',{
args.app.post('/p/:pad/import', async (req: any, res: any, next: Function) => { config: {
rateLimit: {
...settings.importExportRateLimiting
}
}
}, limiter);
args.app.post('/p/:pad/import', async (req: any, res: any) => {
// @ts-ignore
const {session: {user} = {}} = req; const {session: {user} = {}} = req;
const {accessStatus, authorID: authorId} = await securityManager.checkAccess( const {accessStatus, authorID: authorId} = await securityManager.checkAccess(
req.params.pad, req.cookies.sessionID, req.cookies.token, user); req.params.pad, req.cookies.sessionID, req.cookies.token, user);

View file

@ -658,7 +658,7 @@ exports.expressPreSession = async (hookName:string, {app}:any) => {
// start and bind to express // start and bind to express
api.init(); api.init();
app.use(apiRoot, async (req:any, res:any) => { app.get(apiRoot, async (req:any, res:any) => {
let response = null; let response = null;
try { try {
if (style === APIPathStyle.REST) { if (style === APIPathStyle.REST) {

View file

@ -8,6 +8,8 @@ import log4js from 'log4js';
const proxyaddr = require('proxy-addr'); const proxyaddr = require('proxy-addr');
const settings = require('../../utils/Settings'); const settings = require('../../utils/Settings');
import {Server, Socket} from 'socket.io' import {Server, Socket} from 'socket.io'
import path from "path";
import {readFileSync} from "fs";
const socketIORouter = require('../../handler/SocketIORouter'); const socketIORouter = require('../../handler/SocketIORouter');
const hooks = require('../../../static/js/pluginfw/hooks'); const hooks = require('../../../static/js/pluginfw/hooks');
const padMessageHandler = require('../../handler/PadMessageHandler'); const padMessageHandler = require('../../handler/PadMessageHandler');
@ -76,6 +78,12 @@ export const expressCreateServer = (hookName:string, args:ArgsExpressType, cb:Fu
maxHttpBufferSize: settings.socketIo.maxHttpBufferSize, maxHttpBufferSize: settings.socketIo.maxHttpBufferSize,
}) })
args.app.get('/socket.io/socket.io.js', (_req,res:any) => {
res.header('Content-Type', 'application/javascript; charset=utf-8');
const socketIo = readFileSync(path.join(settings.root, 'src', 'node_modules', 'socket.io-client', 'dist', 'socket.io.min.js'), 'utf8');
res.header('Cache-Control', `public, max-age=${settings.maxAge}`);
res.send(socketIo);
})
const handleConnection = (socket:Socket) => { const handleConnection = (socket:Socket) => {
sockets.add(socket); sockets.add(socket);

View file

@ -73,12 +73,13 @@ exports.expressPreSession = async (hookName:string, {app}:any) => {
exports.expressCreateServer = (hookName:string, args:any, cb:Function) => { exports.expressCreateServer = (hookName:string, args:any, cb:Function) => {
// serve index.html under / // serve index.html under /
args.app.get('/', (req:any, res:any) => { args.app.get('/', (req:any, res:any) => {
console.log('GET /')
res.type('text/html').send(eejs.require('ep_etherpad-lite/templates/index.html', {req})) res.type('text/html').send(eejs.require('ep_etherpad-lite/templates/index.html', {req}))
//res.send(eejs.require('ep_etherpad-lite/templates/index.html', {req})); //res.send(eejs.require('ep_etherpad-lite/templates/index.html', {req}));
}); });
// serve pad.html under /p // serve pad.html under /p
args.app.get('/p/:pad', (req:any, res:any, next:Function) => { args.app.get('/p/:pad', (req:any, res:any) => {
// The below might break for pads being rewritten // The below might break for pads being rewritten
const isReadOnly = !webaccess.userCanModify(req.params.pad, req); const isReadOnly = !webaccess.userCanModify(req.params.pad, req);
@ -97,7 +98,7 @@ exports.expressCreateServer = (hookName:string, args:any, cb:Function) => {
}); });
// serve timeslider.html under /p/$padname/timeslider // serve timeslider.html under /p/$padname/timeslider
args.app.get('/p/:pad/timeslider', (req:any, res:any, next:Function) => { args.app.get('/p/:pad/timeslider', (req:any, res:any) => {
hooks.callAll('padInitToolbar', { hooks.callAll('padInitToolbar', {
toolbar, toolbar,
}); });

View file

@ -9,6 +9,7 @@ const path = require('path');
const plugins = require('../../../static/js/pluginfw/plugin_defs'); const plugins = require('../../../static/js/pluginfw/plugin_defs');
const settings = require('../../utils/Settings'); const settings = require('../../utils/Settings');
import CachingMiddleware from '../../utils/caching_middleware'; import CachingMiddleware from '../../utils/caching_middleware';
import {FastifyInstance} from "fastify";
const Yajsml = require('etherpad-yajsml'); const Yajsml = require('etherpad-yajsml');
// Rewrite tar to include modules with no extensions and proper rooted paths. // Rewrite tar to include modules with no extensions and proper rooted paths.
@ -32,7 +33,9 @@ const getTar = async () => {
return tar; return tar;
}; };
exports.expressPreSession = async (hookName:string, {app}:any) => { exports.expressPreSession = async (hookName:string, {app}: {
app: FastifyInstance
}) => {
// Cache both minified and static. // Cache both minified and static.
const assetCache = new CachingMiddleware(); const assetCache = new CachingMiddleware();
// Cache static assets // Cache static assets
@ -40,9 +43,10 @@ exports.expressPreSession = async (hookName:string, {app}:any) => {
app.get('/static/js/require-kernel.js', (req:any, res:any) => { app.get('/static/js/require-kernel.js', (req:any, res:any) => {
res.header('Content-Type', 'application/javascript; charset=utf-8'); res.header('Content-Type', 'application/javascript; charset=utf-8');
const RequireKernel = require('etherpad-require-kernel');
const requireDefinition = () => `var require = ${RequireKernel.kernelSource};\n`;
res.header('Cache-Control', `public, max-age=${settings.maxAge}`); res.header('Cache-Control', `public, max-age=${settings.maxAge}`);
const file = fs.readFile(path.join(settings.root, 'src/static/js/require-kernel.js'), 'utf8'); res.send(requireDefinition());
res.send();
}) })
app.route({ app.route({
@ -57,19 +61,18 @@ exports.expressPreSession = async (hookName:string, {app}:any) => {
handler: assetCache.handle.bind(assetCache) handler: assetCache.handle.bind(assetCache)
}); });
/*app.register(require('@fastify/static'), { app.register(require('@fastify/static'), {
root: path.join(settings.root, 'src', 'static'), root: path.join(settings.root, 'src', 'static'),
prefix: '/static/', // optional: default '/' prefix: '/static/', // optional: default '/'
constraints: {} // optional: default {} })
})*/
// Minify will serve static files compressed (minify enabled). It also has // Minify will serve static files compressed (minify enabled). It also has
// file-specific hacks for ace/require-kernel/etc. // file-specific hacks for ace/require-kernel/etc.
app.route({ /*app.route({
method: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'], method: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'],
url: '/static/*', url: '/static/*',
handler: minify.minify handler: minify.minify
}); });*/
// Setup middleware that will package JavaScript files served by minify for // Setup middleware that will package JavaScript files served by minify for
// CommonJS loader on the client-side. // CommonJS loader on the client-side.
@ -87,12 +90,19 @@ exports.expressPreSession = async (hookName:string, {app}:any) => {
const associator = new StaticAssociator(associations); const associator = new StaticAssociator(associations);
jsServer.setAssociator(associator); jsServer.setAssociator(associator);
app.use(jsServer.handle.bind(jsServer)); app.addHook('onRequest', (req, res, done) => {
if(req.url.startsWith('/javascripts')){
console.log('GET /javascripts in handler', req.url)
res.header('Content-Type', 'application/javascript; charset=utf-8');
return jsServer.handle(req, res);
}
done()
})
// serve plugin definitions // serve plugin definitions
// not very static, but served here so that client can do // not very static, but served here so that client can do
// require("pluginfw/static/js/plugin-definitions.js"); // require("pluginfw/static/js/plugin-definitions.js");
app.get('/pluginfw/plugin-definitions.json', (req: any, res:any, next:Function) => { app.get('/pluginfw/plugin-definitions.json', (req: any, res:any) => {
const clientParts = plugins.parts.filter((part: PartType) => part.client_hooks != null); const clientParts = plugins.parts.filter((part: PartType) => part.client_hooks != null);
const clientPlugins:MapArrayType<string> = {}; const clientPlugins:MapArrayType<string> = {};
for (const name of new Set(clientParts.map((part: PartType) => part.plugin))) { for (const name of new Set(clientParts.map((part: PartType) => part.plugin))) {

View file

@ -5,6 +5,7 @@ import log4js from 'log4js';
import {SocketClientRequest} from "../../types/SocketClientRequest"; import {SocketClientRequest} from "../../types/SocketClientRequest";
import {WebAccessTypes} from "../../types/WebAccessTypes"; import {WebAccessTypes} from "../../types/WebAccessTypes";
import {SettingsUser} from "../../types/SettingsUser"; import {SettingsUser} from "../../types/SettingsUser";
import {FastifyReply, FastifyRequest} from "fastify";
const httpLogger = log4js.getLogger('http'); const httpLogger = log4js.getLogger('http');
const settings = require('../../utils/Settings'); const settings = require('../../utils/Settings');
const hooks = require('../../../static/js/pluginfw/hooks'); const hooks = require('../../../static/js/pluginfw/hooks');
@ -14,6 +15,7 @@ hooks.deprecationNotices.authFailure = 'use the authnFailure and authzFailure ho
// Promisified wrapper around hooks.aCallFirst. // Promisified wrapper around hooks.aCallFirst.
const aCallFirst = (hookName: string, context:any, pred = null) => new Promise((resolve, reject) => { const aCallFirst = (hookName: string, context:any, pred = null) => new Promise((resolve, reject) => {
console.log(hookName)
hooks.aCallFirst(hookName, context, (err:any, r: unknown) => err != null ? reject(err) : resolve(r), pred); hooks.aCallFirst(hookName, context, (err:any, r: unknown) => err != null ? reject(err) : resolve(r), pred);
}); });
@ -49,8 +51,8 @@ exports.userCanModify = (padId: string, req: SocketClientRequest) => {
// Exported so that tests can set this to 0 to avoid unnecessary test slowness. // Exported so that tests can set this to 0 to avoid unnecessary test slowness.
exports.authnFailureDelayMs = 1000; exports.authnFailureDelayMs = 1000;
const checkAccess = async (req:any, res:any, next: Function) => { const checkAccess = async (req:FastifyRequest, res: FastifyReply, next: Function) => {
const requireAdmin = req.path.toLowerCase().startsWith('/admin-auth'); const requireAdmin = req.url.toLowerCase().startsWith('/admin-auth');
// /////////////////////////////////////////////////////////////////////////////////////////////// // ///////////////////////////////////////////////////////////////////////////////////////////////
// Step 1: Check the preAuthorize hook for early permit/deny (permit is only allowed for non-admin // Step 1: Check the preAuthorize hook for early permit/deny (permit is only allowed for non-admin
@ -94,10 +96,12 @@ const checkAccess = async (req:any, res:any, next: Function) => {
const authorize = async () => { const authorize = async () => {
const grant = async (level: string|false) => { const grant = async (level: string|false) => {
level = exports.normalizeAuthzLevel(level); level = exports.normalizeAuthzLevel(level);
console.log("Level is", level)
if (!level) return false; if (!level) return false;
const user = req.session.user; const user = req.session.user;
console.log("User is", user)
if (user == null) return true; // This will happen if authentication is not required. if (user == null) return true; // This will happen if authentication is not required.
const encodedPadId = (req.path.match(/^\/p\/([^/]*)/) || [])[1]; const encodedPadId = (req.url.match(/^\/p\/([^/]*)/) || [])[1];
if (encodedPadId == null) return true; if (encodedPadId == null) return true;
let padId = decodeURIComponent(encodedPadId); let padId = decodeURIComponent(encodedPadId);
if (readOnlyManager.isReadOnlyId(padId)) { if (readOnlyManager.isReadOnlyId(padId)) {
@ -118,15 +122,17 @@ const checkAccess = async (req:any, res:any, next: Function) => {
if (!isAuthenticated) return await grant(false); if (!isAuthenticated) return await grant(false);
if (requireAdmin && !req.session.user.is_admin) return await grant(false); if (requireAdmin && !req.session.user.is_admin) return await grant(false);
if (!settings.requireAuthorization) return await grant('create'); if (!settings.requireAuthorization) return await grant('create');
return await grant(await aCallFirst0('authorize', {req, res, next, resource: req.path})); const level = await aCallFirst0('authorize', {req, res, next, resource: req.url})
return await grant(level);
}; };
// /////////////////////////////////////////////////////////////////////////////////////////////// // ///////////////////////////////////////////////////////////////////////////////////////////////
// Step 2: Try to just access the thing. If access fails (perhaps authentication has not yet // Step 2: Try to just access the thing. If access fails (perhaps authentication has not yet
// completed, or maybe different credentials are required), go to the next step. // completed, or maybe different credentials are required), go to the next step.
// /////////////////////////////////////////////////////////////////////////////////////////////// // ///////////////////////////////////////////////////////////////////////////////////////////////
console.log("Authorize")
if (await authorize()) { if (await authorize()) {
console.log("Authorize2")
if(requireAdmin) { if(requireAdmin) {
res.status(200).send('Authorized') res.status(200).send('Authorized')
return return
@ -149,7 +155,7 @@ const checkAccess = async (req:any, res:any, next: Function) => {
const httpBasicAuth = req.headers.authorization && req.headers.authorization.startsWith('Basic '); const httpBasicAuth = req.headers.authorization && req.headers.authorization.startsWith('Basic ');
if (httpBasicAuth) { if (httpBasicAuth) {
const userpass = const userpass =
Buffer.from(req.headers.authorization.split(' ')[1], 'base64').toString().split(':'); Buffer.from(req.headers.authorization!.split(' ')[1], 'base64').toString().split(':');
ctx.username = userpass.shift(); ctx.username = userpass.shift();
// Prevent prototype pollution vulnerabilities in plugins. This also silences a prototype // Prevent prototype pollution vulnerabilities in plugins. This also silences a prototype
// pollution warning below (when setting settings.users[ctx.username]) that isn't actually a // pollution warning below (when setting settings.users[ctx.username]) that isn't actually a
@ -161,7 +167,6 @@ const checkAccess = async (req:any, res:any, next: Function) => {
// Fall back to HTTP basic auth. // Fall back to HTTP basic auth.
// @ts-ignore // @ts-ignore
const {[ctx.username]: {password} = {}} = settings.users as SettingsUser; const {[ctx.username]: {password} = {}} = settings.users as SettingsUser;
if (!httpBasicAuth || if (!httpBasicAuth ||
!ctx.username || !ctx.username ||
password == null || password.toString() !== ctx.password) { password == null || password.toString() !== ctx.password) {
@ -172,8 +177,8 @@ const checkAccess = async (req:any, res:any, next: Function) => {
//res.header('WWW-Authenticate', 'Basic realm="Protected Area"'); //res.header('WWW-Authenticate', 'Basic realm="Protected Area"');
// Delay the error response for 1s to slow down brute force attacks. // Delay the error response for 1s to slow down brute force attacks.
await new Promise((resolve) => setTimeout(resolve, exports.authnFailureDelayMs)); await new Promise((resolve) => setTimeout(resolve, exports.authnFailureDelayMs));
res.status(401).send('Authentication Required'); await res.status(401).send('Authentication Required');
return; throw new Error('Authentication Required');
} }
settings.users[ctx.username].username = ctx.username; settings.users[ctx.username].username = ctx.username;
// Make a shallow copy so that the password property can be deleted (to prevent it from // Make a shallow copy so that the password property can be deleted (to prevent it from
@ -181,9 +186,10 @@ const checkAccess = async (req:any, res:any, next: Function) => {
req.session.user = {...settings.users[ctx.username]}; req.session.user = {...settings.users[ctx.username]};
delete req.session.user.password; delete req.session.user.password;
} }
console.log("Session is", req.session)
if (req.session.user == null) { if (req.session.user == null) {
httpLogger.error('authenticate hook failed to add user settings to session'); httpLogger.error('authenticate hook failed to add user settings to session');
return res.status(500).send('Internal Server Error'); await res.status(500).send('Internal Server Error');
} }
const {username = '<no username>'} = req.session.user; const {username = '<no username>'} = req.session.user;
httpLogger.info(`Successful authentication from IP ${req.ip} for user ${username}`); httpLogger.info(`Successful authentication from IP ${req.ip} for user ${username}`);
@ -195,22 +201,25 @@ const checkAccess = async (req:any, res:any, next: Function) => {
// /////////////////////////////////////////////////////////////////////////////////////////////// // ///////////////////////////////////////////////////////////////////////////////////////////////
const auth = await authorize() const auth = await authorize()
if (auth && !requireAdmin) return next(); if (auth && !requireAdmin) {
if(auth && requireAdmin) { return next();
res.status(200).send('Authorized')
return
} }
if(auth && requireAdmin) {
console.log(auth+"-------Require admin")
res.code(200).send('Authorized')
}
console.log('authzFailure')
if (await aCallFirst0('authzFailure', {req, res})) return; if (await aCallFirst0('authzFailure', {req, res})) return;
if (await aCallFirst0('authFailure', {req, res, next})) return; if (await aCallFirst0('authFailure', {req, res, next})) return;
// No plugin handled the authorization failure. // No plugin handled the authorization failure.
res.status(403).send('Forbidden'); await res.code(403).send('Forbidden');
}; };
/** /**
* Express middleware to authenticate the user and check authorization. Must be installed after the * Express middleware to authenticate the user and check authorization. Must be installed after the
* express-session middleware. * express-session middleware.
*/ */
exports.checkAccess = (req:any, res:any, next:Function) => { exports.checkAccess = async (req: any, res: any, next: Function) => {
checkAccess(req, res, next).catch((err) => next(err || new Error(err))); return await checkAccess(req, res, next).catch((err) => next(err || new Error(err)));
}; };

View file

@ -1,5 +1,7 @@
import {FastifyInstance} from "fastify";
export type ArgsExpressType = { export type ArgsExpressType = {
app:any, app:FastifyInstance,
io: any, io: any,
server:any server:any
} }

View file

@ -5,6 +5,6 @@ export type WebAccessTypes = {
password?: string; password?: string;
req:any; req:any;
res:any; res:any;
next:any; next:Function;
users: SettingsUser; users: SettingsUser;
} }

View file

@ -5,7 +5,6 @@ import path from 'path';
// Normalizes p and ensures that it is a relative path that does not reach outside. See // Normalizes p and ensures that it is a relative path that does not reach outside. See
// https://nvd.nist.gov/vuln/detail/CVE-2015-3297 for additional context. // https://nvd.nist.gov/vuln/detail/CVE-2015-3297 for additional context.
module.exports = (p: string, pathApi = path) => { module.exports = (p: string, pathApi = path) => {
console.log('p:', p);
// The documentation for path.normalize() says that it resolves '..' and '.' segments. The word // The documentation for path.normalize() says that it resolves '..' and '.' segments. The word
// "resolve" implies that it examines the filesystem to resolve symbolic links, so 'a/../b' might // "resolve" implies that it examines the filesystem to resolve symbolic links, so 'a/../b' might
// not be the same thing as 'b'. Most path normalization functions from other libraries (e.g., // not be the same thing as 'b'. Most path normalization functions from other libraries (e.g.,

View file

@ -30,7 +30,11 @@
} }
], ],
"dependencies": { "dependencies": {
"@fastify/compress": "^7.0.0",
"@fastify/cookie": "^9.3.1",
"@fastify/express": "^2.3.0", "@fastify/express": "^2.3.0",
"@fastify/rate-limit": "^9.1.0",
"@fastify/session": "^10.7.0",
"@fastify/static": "^7.0.1", "@fastify/static": "^7.0.1",
"async": "^3.2.5", "async": "^3.2.5",
"axios": "^1.6.8", "axios": "^1.6.8",
@ -40,7 +44,6 @@
"ejs": "^3.1.9", "ejs": "^3.1.9",
"etherpad-require-kernel": "^1.0.16", "etherpad-require-kernel": "^1.0.16",
"etherpad-yajsml": "0.0.12", "etherpad-yajsml": "0.0.12",
"express": "4.18.3",
"express-rate-limit": "^7.2.0", "express-rate-limit": "^7.2.0",
"express-session": "npm:@etherpad/express-session@^1.18.2", "express-session": "npm:@etherpad/express-session@^1.18.2",
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
@ -88,7 +91,6 @@
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.42.1", "@playwright/test": "^1.42.1",
"@types/async": "^3.2.24", "@types/async": "^3.2.24",
"@types/express": "^4.17.21",
"@types/http-errors": "^2.0.4", "@types/http-errors": "^2.0.4",
"@types/jsdom": "^21.1.6", "@types/jsdom": "^21.1.6",
"@types/mocha": "^10.0.6", "@types/mocha": "^10.0.6",

View file

@ -23,13 +23,12 @@
*/ */
let socket; let socket;
const requireFromUrl = require('require-from-url/sync');
// These jQuery things should create local references, but for now `require()` // These jQuery things should create local references, but for now `require()`
// assigns to the global `$` and augments it with plugins. // assigns to the global `$` and augments it with plugins.
requireFromUrl('./vendors/jquery'); require('./vendors/jquery');
requireFromUrl('./vendors/farbtastic'); require('./vendors/farbtastic');
requireFromUrl('./vendors/gritter'); require('./vendors/gritter');
const Cookies = require('./pad_utils').Cookies; const Cookies = require('./pad_utils').Cookies;
const chat = require('./chat').chat; const chat = require('./chat').chat;

View file

@ -357,7 +357,7 @@ let globalExceptionHandler = null;
padutils.setupGlobalExceptionHandler = () => { padutils.setupGlobalExceptionHandler = () => {
if (globalExceptionHandler == null) { if (globalExceptionHandler == null) {
require('./vendors/gritter'); require('./vendors/gritter');
globalExceptionHandler = (e) => { globalExceptionHandler = async (e) => {
let type; let type;
let err; let err;
let msg, url, linenumber; let msg, url, linenumber;
@ -410,8 +410,9 @@ padutils.setupGlobalExceptionHandler = () => {
} }
// send javascript errors to the server // send javascript errors to the server
$.post('../jserror', { await fetch('../jserror', {
errorInfo: JSON.stringify({ method: 'POST',
body: JSON.stringify({
errorId, errorId,
type, type,
msg, msg,