mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-04-20 07:35:05 -04:00
Added basic app.
This commit is contained in:
parent
b3741470ed
commit
3e18f65d3b
17 changed files with 581 additions and 382 deletions
248
pnpm-lock.yaml
generated
248
pnpm-lock.yaml
generated
|
@ -124,9 +124,21 @@ importers:
|
|||
|
||||
src:
|
||||
dependencies:
|
||||
'@fastify/compress':
|
||||
specifier: ^7.0.0
|
||||
version: 7.0.0
|
||||
'@fastify/cookie':
|
||||
specifier: ^9.3.1
|
||||
version: 9.3.1
|
||||
'@fastify/express':
|
||||
specifier: ^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':
|
||||
specifier: ^7.0.1
|
||||
version: 7.0.1
|
||||
|
@ -154,12 +166,9 @@ importers:
|
|||
etherpad-yajsml:
|
||||
specifier: 0.0.12
|
||||
version: 0.0.12
|
||||
express:
|
||||
specifier: 4.18.3
|
||||
version: 4.18.3
|
||||
express-rate-limit:
|
||||
specifier: ^7.2.0
|
||||
version: 7.2.0(express@4.18.3)
|
||||
version: 7.2.0
|
||||
express-session:
|
||||
specifier: npm:@etherpad/express-session@^1.18.2
|
||||
version: /@etherpad/express-session@1.18.2
|
||||
|
@ -281,9 +290,6 @@ importers:
|
|||
'@types/async':
|
||||
specifier: ^3.2.24
|
||||
version: 3.2.24
|
||||
'@types/express':
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
'@types/http-errors':
|
||||
specifier: ^2.0.4
|
||||
version: 2.0.4
|
||||
|
@ -819,6 +825,26 @@ packages:
|
|||
fast-uri: 2.3.0
|
||||
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:
|
||||
resolution: {integrity: sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ==}
|
||||
dev: false
|
||||
|
@ -844,6 +870,14 @@ packages:
|
|||
fast-deep-equal: 3.1.3
|
||||
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:
|
||||
resolution: {integrity: sha512-yNYiY6sDkexoJR0D8IDy3aRP3+L4wdqCpvx5WP+VtEU58sn7USmKynBzDQex5X42Zzvw2gNzzYgP90UfWShLFA==}
|
||||
dependencies:
|
||||
|
@ -854,6 +888,13 @@ packages:
|
|||
mime: 3.0.0
|
||||
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:
|
||||
resolution: {integrity: sha512-i1p/nELMknAisNfnjo7yhfoUOdKzA+n92QaMirv2NkZrJ1Wl12v2nyTYlDwPN8XoStMBAnRK/Kx6zKmfrXUPXw==}
|
||||
dependencies:
|
||||
|
@ -1767,19 +1808,6 @@ packages:
|
|||
resolution: {integrity: sha512-8iHVLHsCCOBKjCF2KwFe0p9Z3rfM9mL+sSP8btyR5vTjJRAqpBYD28/ZLgXPf0pjG1VxOvtCV/BgXkQbpSe8Hw==}
|
||||
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:
|
||||
resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==}
|
||||
dev: false
|
||||
|
@ -1804,24 +1832,6 @@ packages:
|
|||
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
|
||||
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:
|
||||
resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==}
|
||||
dependencies:
|
||||
|
@ -1877,14 +1887,6 @@ packages:
|
|||
resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==}
|
||||
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:
|
||||
resolution: {integrity: sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg==}
|
||||
dev: true
|
||||
|
@ -1915,14 +1917,6 @@ packages:
|
|||
resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==}
|
||||
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:
|
||||
resolution: {integrity: sha512-gnvBA/21SA4xxqNXEwNiVcP0xSGHh/gi1VhWv9Bl46a0ItbTT5nFY+G9VSQpaG/8N/qdJpJ+vftQ4zflTtnjLw==}
|
||||
dependencies:
|
||||
|
@ -1944,21 +1938,6 @@ packages:
|
|||
/@types/semver@7.5.8:
|
||||
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:
|
||||
resolution: {integrity: sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw==}
|
||||
dependencies:
|
||||
|
@ -2794,6 +2773,11 @@ packages:
|
|||
resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
|
||||
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:
|
||||
resolution: {integrity: sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
@ -2817,6 +2801,10 @@ packages:
|
|||
/cookiejar@2.1.4:
|
||||
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:
|
||||
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
@ -3018,6 +3006,24 @@ packages:
|
|||
tslib: 2.6.2
|
||||
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:
|
||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||
dev: false
|
||||
|
@ -3656,13 +3662,11 @@ packages:
|
|||
engines: {node: '>=0.8.x'}
|
||||
dev: false
|
||||
|
||||
/express-rate-limit@7.2.0(express@4.18.3):
|
||||
/express-rate-limit@7.2.0:
|
||||
resolution: {integrity: sha512-T7nul1t4TNyfZMJ7pKRKkdeVJWa2CqB8NA1P8BwYaoDI5QSBZARv5oMS43J7b7I5P+4asjVXjb7ONuwDKucahg==}
|
||||
engines: {node: '>= 16'}
|
||||
peerDependencies:
|
||||
express: 4 || 5 || ^5.0.0-beta.1
|
||||
dependencies:
|
||||
express: 4.18.3
|
||||
dev: false
|
||||
|
||||
/express@4.18.3:
|
||||
|
@ -3946,6 +3950,13 @@ packages:
|
|||
engines: {node: '>= 0.6'}
|
||||
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:
|
||||
resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
@ -4419,6 +4430,14 @@ packages:
|
|||
side-channel: 1.0.5
|
||||
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:
|
||||
resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
|
||||
dependencies:
|
||||
|
@ -4594,6 +4613,10 @@ packages:
|
|||
call-bind: 1.0.7
|
||||
dev: true
|
||||
|
||||
/isarray@1.0.0:
|
||||
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
|
||||
dev: false
|
||||
|
||||
/isarray@2.0.5:
|
||||
resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
|
||||
dev: true
|
||||
|
@ -5042,6 +5065,11 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/minipass@7.0.4:
|
||||
resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
dev: false
|
||||
|
||||
/minizlib@2.1.2:
|
||||
resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
|
||||
engines: {node: '>= 8'}
|
||||
|
@ -5301,6 +5329,11 @@ packages:
|
|||
type-check: 0.4.0
|
||||
dev: true
|
||||
|
||||
/p-is-promise@3.0.0:
|
||||
resolution: {integrity: sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==}
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/p-limit@3.1.0:
|
||||
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
@ -5380,6 +5413,14 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
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:
|
||||
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
|
||||
dev: true
|
||||
|
@ -5472,6 +5513,10 @@ packages:
|
|||
engines: {node: '>= 0.8.0'}
|
||||
dev: true
|
||||
|
||||
/process-nextick-args@2.0.1:
|
||||
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
|
||||
dev: false
|
||||
|
||||
/process-warning@3.0.0:
|
||||
resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==}
|
||||
dev: false
|
||||
|
@ -5514,6 +5559,14 @@ packages:
|
|||
once: 1.4.0
|
||||
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:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
|
@ -5698,6 +5751,27 @@ packages:
|
|||
loose-envify: 1.4.0
|
||||
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:
|
||||
resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
|
@ -5868,6 +5942,10 @@ packages:
|
|||
isarray: 2.0.5
|
||||
dev: true
|
||||
|
||||
/safe-buffer@5.1.2:
|
||||
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
|
||||
dev: false
|
||||
|
||||
/safe-buffer@5.2.1:
|
||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||
|
||||
|
@ -6151,6 +6229,10 @@ packages:
|
|||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/stream-shift@1.0.3:
|
||||
resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==}
|
||||
dev: false
|
||||
|
||||
/streamroller@3.1.5:
|
||||
resolution: {integrity: sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==}
|
||||
engines: {node: '>=8.0'}
|
||||
|
@ -6204,6 +6286,12 @@ packages:
|
|||
es-abstract: 1.22.4
|
||||
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:
|
||||
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
|
||||
dependencies:
|
||||
|
@ -6352,6 +6440,13 @@ packages:
|
|||
- supports-color
|
||||
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:
|
||||
resolution: {integrity: sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g==}
|
||||
requiresBuild: true
|
||||
|
@ -6694,6 +6789,10 @@ packages:
|
|||
react: 18.2.0
|
||||
dev: true
|
||||
|
||||
/util-deprecate@1.0.2:
|
||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
dev: false
|
||||
|
||||
/utils-merge@1.0.1:
|
||||
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
|
||||
engines: {node: '>= 0.4.0'}
|
||||
|
@ -6936,6 +7035,11 @@ packages:
|
|||
resolution: {integrity: sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==}
|
||||
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:
|
||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||
engines: {node: '>=10'}
|
||||
|
|
|
@ -10,180 +10,173 @@ import events from 'events';
|
|||
// @ts-ignore
|
||||
import expressSession from 'express-session';
|
||||
import fs from 'fs';
|
||||
|
||||
const hooks = require('../../static/js/pluginfw/hooks');
|
||||
import log4js from 'log4js';
|
||||
|
||||
const SessionStore = require('../db/SessionStore');
|
||||
const settings = require('../utils/Settings');
|
||||
const stats = require('../stats')
|
||||
import util from 'util';
|
||||
|
||||
const webaccess = require('./express/webaccess');
|
||||
import Fastify from 'fastify';
|
||||
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');
|
||||
let serverName:string;
|
||||
let sessionStore: { shutdown: () => void; } | null;
|
||||
const sockets:Set<Socket> = new Set();
|
||||
let serverName: string;
|
||||
let sessionStore: typeof SessionStore | null;
|
||||
const sockets: Set<Socket> = new Set();
|
||||
const socketsEvents = new events.EventEmitter();
|
||||
const startTime = stats.settableGauge('httpStartTime');
|
||||
|
||||
exports.server = null;
|
||||
|
||||
const closeServer = async () => {
|
||||
if (exports.server != null) {
|
||||
logger.info('Closing HTTP server...');
|
||||
// Call exports.server.close() to reject new connections but don't await just yet because the
|
||||
// Promise won't resolve until all preexisting connections are closed.
|
||||
const p = util.promisify(exports.server.close.bind(exports.server))();
|
||||
await hooks.aCallAll('expressCloseServer');
|
||||
// Give existing connections some time to close on their own before forcibly terminating. The
|
||||
// time should be long enough to avoid interrupting most preexisting transmissions but short
|
||||
// enough to avoid a noticeable outage.
|
||||
const timeout = setTimeout(async () => {
|
||||
logger.info(`Forcibly terminating remaining ${sockets.size} HTTP connections...`);
|
||||
for (const socket of sockets) socket.destroy(new Error('HTTP server is closing'));
|
||||
}, 5000);
|
||||
let lastLogged = 0;
|
||||
while (sockets.size > 0 && !settings.enableAdminUITests) {
|
||||
if (Date.now() - lastLogged > 1000) { // Rate limit to avoid filling logs.
|
||||
logger.info(`Waiting for ${sockets.size} HTTP clients to disconnect...`);
|
||||
lastLogged = Date.now();
|
||||
}
|
||||
await events.once(socketsEvents, 'updated');
|
||||
if (exports.server != null) {
|
||||
logger.info('Closing HTTP server...');
|
||||
// Call exports.server.close() to reject new connections but don't await just yet because the
|
||||
// Promise won't resolve until all preexisting connections are closed.
|
||||
const p = util.promisify(exports.server.close.bind(exports.server))();
|
||||
await hooks.aCallAll('expressCloseServer');
|
||||
// Give existing connections some time to close on their own before forcibly terminating. The
|
||||
// time should be long enough to avoid interrupting most preexisting transmissions but short
|
||||
// enough to avoid a noticeable outage.
|
||||
const timeout = setTimeout(async () => {
|
||||
logger.info(`Forcibly terminating remaining ${sockets.size} HTTP connections...`);
|
||||
for (const socket of sockets) socket.destroy(new Error('HTTP server is closing'));
|
||||
}, 5000);
|
||||
let lastLogged = 0;
|
||||
while (sockets.size > 0 && !settings.enableAdminUITests) {
|
||||
if (Date.now() - lastLogged > 1000) { // Rate limit to avoid filling logs.
|
||||
logger.info(`Waiting for ${sockets.size} HTTP clients to disconnect...`);
|
||||
lastLogged = Date.now();
|
||||
}
|
||||
await events.once(socketsEvents, 'updated');
|
||||
}
|
||||
await p;
|
||||
clearTimeout(timeout);
|
||||
exports.server = null;
|
||||
startTime.setValue(0);
|
||||
logger.info('HTTP server closed');
|
||||
}
|
||||
await p;
|
||||
clearTimeout(timeout);
|
||||
exports.server = null;
|
||||
startTime.setValue(0);
|
||||
logger.info('HTTP server closed');
|
||||
}
|
||||
if (sessionStore) sessionStore.shutdown();
|
||||
sessionStore = null;
|
||||
if (secretRotator) secretRotator.stop();
|
||||
secretRotator = null;
|
||||
if (sessionStore) sessionStore.shutdown();
|
||||
sessionStore = null;
|
||||
if (secretRotator) secretRotator.stop();
|
||||
secretRotator = null;
|
||||
};
|
||||
|
||||
exports.createServer = async () => {
|
||||
console.log('Report bugs at https://github.com/ether/etherpad-lite/issues');
|
||||
console.log('Report bugs at https://github.com/ether/etherpad-lite/issues');
|
||||
|
||||
serverName = `Etherpad ${settings.getGitCommit()} (https://etherpad.org)`;
|
||||
serverName = `Etherpad ${settings.getGitCommit()} (https://etherpad.org)`;
|
||||
|
||||
console.log(`Your Etherpad version is ${settings.getEpVersion()} (${settings.getGitCommit()})`);
|
||||
console.log(`Your Etherpad version is ${settings.getEpVersion()} (${settings.getGitCommit()})`);
|
||||
|
||||
await exports.restartServer();
|
||||
await exports.restartServer();
|
||||
|
||||
if (settings.ip === '') {
|
||||
// using Unix socket for connectivity
|
||||
console.log(`You can access your Etherpad instance using the Unix socket at ${settings.port}`);
|
||||
} else {
|
||||
console.log(`You can access your Etherpad instance at http://${settings.ip}:${settings.port}/`);
|
||||
}
|
||||
if (settings.ip === '') {
|
||||
// using Unix socket for connectivity
|
||||
console.log(`You can access your Etherpad instance using the Unix socket at ${settings.port}`);
|
||||
} else {
|
||||
console.log(`You can access your Etherpad instance at http://${settings.ip}:${settings.port}/`);
|
||||
}
|
||||
|
||||
if (!_.isEmpty(settings.users)) {
|
||||
console.log(`The plugin admin page is at http://${settings.ip}:${settings.port}/admin/plugins`);
|
||||
} else {
|
||||
console.warn('Admin username and password not set in settings.json. ' +
|
||||
'To access admin please uncomment and edit "users" in settings.json');
|
||||
}
|
||||
if (!_.isEmpty(settings.users)) {
|
||||
console.log(`The plugin admin page is at http://${settings.ip}:${settings.port}/admin/plugins`);
|
||||
} else {
|
||||
console.warn('Admin username and password not set in settings.json. ' +
|
||||
'To access admin please uncomment and edit "users" in settings.json');
|
||||
}
|
||||
|
||||
const env = process.env.NODE_ENV || 'development';
|
||||
const env = process.env.NODE_ENV || 'development';
|
||||
|
||||
if (env !== 'production') {
|
||||
console.warn('Etherpad is running in Development mode. This mode is slower for users and ' +
|
||||
'less secure than production mode. You should set the NODE_ENV environment ' +
|
||||
'variable to production by using: export NODE_ENV=production');
|
||||
}
|
||||
if (env !== 'production') {
|
||||
console.warn('Etherpad is running in Development mode. This mode is slower for users and ' +
|
||||
'less secure than production mode. You should set the NODE_ENV environment ' +
|
||||
'variable to production by using: export NODE_ENV=production');
|
||||
}
|
||||
};
|
||||
|
||||
exports.restartServer = async () => {
|
||||
await closeServer();
|
||||
await closeServer();
|
||||
|
||||
console.log('Starting Etherpad...');
|
||||
const fastify = Fastify({
|
||||
logger: {
|
||||
level: 'error',
|
||||
transport: {
|
||||
target: 'pino-pretty',
|
||||
options: {
|
||||
translateTime: 'HH:MM:ss Z',
|
||||
ignore: 'pid,hostname',
|
||||
},
|
||||
}
|
||||
console.log('Starting Etherpad...');
|
||||
const fastify = Fastify({
|
||||
logger: {
|
||||
level: 'error',
|
||||
transport: {
|
||||
target: 'pino-pretty',
|
||||
options: {
|
||||
translateTime: 'HH:MM:ss Z',
|
||||
ignore: 'pid,hostname',
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
//await fastify.register(require('@fastify/express'))
|
||||
|
||||
|
||||
if (settings.ssl) {
|
||||
console.log('SSL -- enabled');
|
||||
console.log(`SSL -- server key file: ${settings.ssl.key}`);
|
||||
console.log(`SSL -- Certificate Authority's certificate file: ${settings.ssl.cert}`);
|
||||
|
||||
const options: MapArrayType<any> = {
|
||||
key: fs.readFileSync(settings.ssl.key),
|
||||
cert: fs.readFileSync(settings.ssl.cert),
|
||||
};
|
||||
|
||||
if (settings.ssl.ca) {
|
||||
options.ca = [];
|
||||
for (let i = 0; i < settings.ssl.ca.length; i++) {
|
||||
const caFileName = settings.ssl.ca[i];
|
||||
options.ca.push(fs.readFileSync(caFileName));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
await fastify.register(require('@fastify/express'))
|
||||
|
||||
|
||||
if (settings.ssl) {
|
||||
console.log('SSL -- enabled');
|
||||
console.log(`SSL -- server key file: ${settings.ssl.key}`);
|
||||
console.log(`SSL -- Certificate Authority's certificate file: ${settings.ssl.cert}`);
|
||||
|
||||
const options: MapArrayType<any> = {
|
||||
key: fs.readFileSync(settings.ssl.key),
|
||||
cert: fs.readFileSync(settings.ssl.cert),
|
||||
};
|
||||
|
||||
if (settings.ssl.ca) {
|
||||
options.ca = [];
|
||||
for (let i = 0; i < settings.ssl.ca.length; i++) {
|
||||
const caFileName = settings.ssl.ca[i];
|
||||
options.ca.push(fs.readFileSync(caFileName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.server = fastify.server
|
||||
|
||||
fastify.use((req, res, next) => {
|
||||
// res.header("X-Frame-Options", "deny"); // breaks embedded pads
|
||||
if (settings.ssl) {
|
||||
// we use SSL
|
||||
res.header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
||||
}
|
||||
fastify.addHook('onRequest', async (req, res) => {
|
||||
// res.header("X-Frame-Options", "deny"); // breaks embedded pads
|
||||
if (settings.ssl) {
|
||||
// we use SSL
|
||||
res.header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
||||
}
|
||||
|
||||
// Stop IE going into compatability mode
|
||||
// https://github.com/ether/etherpad-lite/issues/2547
|
||||
res.header('X-UA-Compatible', 'IE=Edge,chrome=1');
|
||||
// Stop IE going into compatability mode
|
||||
// https://github.com/ether/etherpad-lite/issues/2547
|
||||
res.header('X-UA-Compatible', 'IE=Edge,chrome=1');
|
||||
|
||||
// Enable a strong referrer policy. Same-origin won't drop Referers when
|
||||
// loading local resources, but it will drop them when loading foreign resources.
|
||||
// It's still a last bastion of referrer security. External URLs should be
|
||||
// already marked with rel="noreferer" and user-generated content pages are already
|
||||
// marked with <meta name="referrer" content="no-referrer">
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
|
||||
// https://github.com/ether/etherpad-lite/pull/3636
|
||||
res.header('Referrer-Policy', 'same-origin');
|
||||
// Enable a strong referrer policy. Same-origin won't drop Referers when
|
||||
// loading local resources, but it will drop them when loading foreign resources.
|
||||
// It's still a last bastion of referrer security. External URLs should be
|
||||
// already marked with rel="noreferer" and user-generated content pages are already
|
||||
// marked with <meta name="referrer" content="no-referrer">
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
|
||||
// https://github.com/ether/etherpad-lite/pull/3636
|
||||
res.header('Referrer-Policy', 'same-origin');
|
||||
|
||||
// send git version in the Server response header if exposeVersion is true.
|
||||
if (settings.exposeVersion) {
|
||||
res.header('Server', serverName);
|
||||
}
|
||||
|
||||
next();
|
||||
// send git version in the Server response header if exposeVersion is true.
|
||||
if (settings.exposeVersion) {
|
||||
res.header('Server', serverName);
|
||||
}
|
||||
});
|
||||
|
||||
if (settings.trustProxy) {
|
||||
/*
|
||||
* If 'trust proxy' === true, the client’s IP address in req.ip will be the
|
||||
* left-most entry in the X-Forwarded-* header.
|
||||
*
|
||||
* Source: https://expressjs.com/en/guide/behind-proxies.html
|
||||
*/
|
||||
fastify.enable('trust proxy');
|
||||
/*
|
||||
* If 'trust proxy' === true, the client’s IP address in req.ip will be the
|
||||
* left-most entry in the X-Forwarded-* header.
|
||||
*
|
||||
* Source: https://expressjs.com/en/guide/behind-proxies.html
|
||||
*/
|
||||
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
|
||||
// starts listening to requests as reported in issue #158. Not installing the log4js connect
|
||||
|
@ -195,78 +188,109 @@ exports.restartServer = async () => {
|
|||
const {keyRotationInterval, sessionLifetime} = settings.cookie;
|
||||
let secret = settings.sessionKey;
|
||||
if (keyRotationInterval && sessionLifetime) {
|
||||
secretRotator = new SecretRotator(
|
||||
'expressSessionSecrets', keyRotationInterval, sessionLifetime, settings.sessionKey);
|
||||
await secretRotator.start();
|
||||
secret = secretRotator.secrets;
|
||||
secretRotator = new SecretRotator(
|
||||
'expressSessionSecrets', keyRotationInterval, sessionLifetime, settings.sessionKey);
|
||||
await secretRotator.start();
|
||||
secret = secretRotator.secrets;
|
||||
}
|
||||
if (!secret) throw new Error('missing cookie signing secret');
|
||||
|
||||
fastify.use(cookieParser(secret, {}));
|
||||
|
||||
sessionStore = new SessionStore(settings.cookie.sessionRefreshInterval);
|
||||
exports.sessionMiddleware = expressSession({
|
||||
propagateTouch: true,
|
||||
rolling: true,
|
||||
secret,
|
||||
store: sessionStore,
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
// Set the cookie name to a javascript identifier compatible string. Makes code handling it
|
||||
// cleaner :)
|
||||
name: 'express_sid',
|
||||
cookie: {
|
||||
maxAge: sessionLifetime || null, // Convert 0 to null.
|
||||
sameSite: settings.cookie.sameSite,
|
||||
propagateTouch: true,
|
||||
rolling: true,
|
||||
secret,
|
||||
store: sessionStore,
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
// Set the cookie name to a javascript identifier compatible string. Makes code handling it
|
||||
// cleaner :)
|
||||
name: 'express_sid',
|
||||
cookie: {
|
||||
maxAge: sessionLifetime || null, // Convert 0 to null.
|
||||
sameSite: settings.cookie.sameSite,
|
||||
|
||||
// The automatic express-session mechanism for determining if the application is being served
|
||||
// over ssl is similar to the one used for setting the language cookie, which check if one of
|
||||
// these conditions is true:
|
||||
//
|
||||
// 1. we are directly serving the nodejs application over SSL, using the "ssl" options in
|
||||
// settings.json
|
||||
//
|
||||
// 2. we are serving the nodejs application in plaintext, but we are using a reverse proxy
|
||||
// that terminates SSL for us. In this case, the user has to set trustProxy = true in
|
||||
// settings.json, and the information wheter the application is over SSL or not will be
|
||||
// extracted from the X-Forwarded-Proto HTTP header
|
||||
//
|
||||
// Please note that this will not be compatible with applications being served over http and
|
||||
// https at the same time.
|
||||
//
|
||||
// reference: https://github.com/expressjs/session/blob/v1.17.0/README.md#cookiesecure
|
||||
secure: 'auto',
|
||||
},
|
||||
// The automatic express-session mechanism for determining if the application is being served
|
||||
// over ssl is similar to the one used for setting the language cookie, which check if one of
|
||||
// these conditions is true:
|
||||
//
|
||||
// 1. we are directly serving the nodejs application over SSL, using the "ssl" options in
|
||||
// settings.json
|
||||
//
|
||||
// 2. we are serving the nodejs application in plaintext, but we are using a reverse proxy
|
||||
// that terminates SSL for us. In this case, the user has to set trustProxy = true in
|
||||
// settings.json, and the information wheter the application is over SSL or not will be
|
||||
// extracted from the X-Forwarded-Proto HTTP header
|
||||
//
|
||||
// Please note that this will not be compatible with applications being served over http and
|
||||
// https at the same time.
|
||||
//
|
||||
// reference: https://github.com/expressjs/session/blob/v1.17.0/README.md#cookiesecure
|
||||
secure: 'auto',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
// 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
|
||||
// when it is not needed (e.g., public static content).
|
||||
await hooks.aCallAll('expressPreSession', {app:fastify});
|
||||
fastify.use(exports.sessionMiddleware);
|
||||
await hooks.aCallAll('expressPreSession', {app: fastify});
|
||||
|
||||
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([
|
||||
hooks.aCallAll('expressConfigure', {app: fastify}),
|
||||
hooks.aCallAll('expressCreateServer', {app:fastify, server: fastify}),
|
||||
hooks.aCallAll('expressConfigure', {app: fastify}),
|
||||
hooks.aCallAll('expressCreateServer', {app: fastify, server: fastify}),
|
||||
]);
|
||||
exports.server.on('connection', (socket: Socket) => {
|
||||
sockets.add(socket);
|
||||
socketsEvents.emit('updated');
|
||||
socket.on('close', () => {
|
||||
sockets.delete(socket);
|
||||
sockets.add(socket);
|
||||
socketsEvents.emit('updated');
|
||||
});
|
||||
socket.on('close', () => {
|
||||
sockets.delete(socket);
|
||||
socketsEvents.emit('updated');
|
||||
});
|
||||
});
|
||||
|
||||
console.log('Listening on port: ' + settings.port)
|
||||
// Run the server!
|
||||
await fastify.listen({
|
||||
port: settings.port,
|
||||
})
|
||||
startTime.setValue(Date.now());
|
||||
await fastify.listen({
|
||||
port: settings.port,
|
||||
})
|
||||
startTime.setValue(Date.now());
|
||||
logger.info('HTTP server listening for connections');
|
||||
}
|
||||
|
||||
exports.shutdown = async (hookName:string, context: any) => {
|
||||
await closeServer();
|
||||
exports.shutdown = async (hookName: string, context: any) => {
|
||||
await closeServer();
|
||||
};
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import {ArgsExpressType} from "../../types/ArgsExpressType";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import express from "express";
|
||||
|
||||
const settings = require('ep_etherpad-lite/node/utils/Settings');
|
||||
|
||||
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
|
||||
* @return {*}
|
||||
*/
|
||||
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.get('/admin/*', (_request:any, response:any)=>{
|
||||
response.sendFile(path.resolve(__dirname,'../../../templates/admin', 'index.html'));
|
||||
} )
|
||||
args.app.get('/admin', (req:any, res:any, next:Function) => {
|
||||
if ('/' !== req.path[req.path.length - 1]) return res.redirect('./admin/');
|
||||
})
|
||||
return cb();
|
||||
exports.expressCreateServer = (hookName: string, args: ArgsExpressType, cb: Function): any => {
|
||||
args.app.register((instance, opts, next) => {
|
||||
instance.register(require('@fastify/static'), {
|
||||
root: ADMIN_PATH,
|
||||
prefix: '/', // optional: default '/'
|
||||
constraints: {} // optional: default {}
|
||||
})
|
||||
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();
|
||||
};
|
||||
|
|
|
@ -1,40 +1,45 @@
|
|||
'use strict';
|
||||
|
||||
import {FastifyInstance} from "fastify";
|
||||
|
||||
const log4js = require('log4js');
|
||||
const clientLogger = log4js.getLogger('client');
|
||||
const {Formidable} = require('formidable');
|
||||
const apiHandler = require('../../handler/APIHandler');
|
||||
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
|
||||
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}`);
|
||||
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
|
||||
app.post('/jserror', (req:any, res:any, next:Function) => {
|
||||
(async () => {
|
||||
const data = JSON.parse(await parseJserrorForm(req));
|
||||
clientLogger.warn(`${data.msg} --`, {
|
||||
[util.inspect.custom]: (depth: number, options:any) => {
|
||||
// Depth is forced to infinity to ensure that all of the provided data is logged.
|
||||
options = Object.assign({}, options, {depth: Infinity, colors: true});
|
||||
return util.inspect(data, options);
|
||||
},
|
||||
});
|
||||
res.end('OK');
|
||||
})().catch((err) => next(err || new Error(err)));
|
||||
app.post<{
|
||||
Body: {
|
||||
errorId: string,
|
||||
type: string,
|
||||
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.
|
||||
options = Object.assign({}, options, {depth: Infinity, colors: true});
|
||||
return util.inspect(req.body, options);
|
||||
},
|
||||
});
|
||||
res.send('ok');
|
||||
});
|
||||
|
||||
// Provide a possibility to query the latest available API version
|
||||
|
|
|
@ -9,14 +9,16 @@ exports.expressCreateServer = (hook_name:string, args: ArgsExpressType, cb:Funct
|
|||
exports.app = args.app;
|
||||
|
||||
// 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
|
||||
// through these "error-handling" middleware
|
||||
// allowing you to respond however you like
|
||||
res.status(500).send({error: 'Sorry, something bad happened!'});
|
||||
console.error(err.stack ? err.stack : err.toString());
|
||||
console.log('Error:', error);
|
||||
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();
|
||||
});
|
||||
})
|
||||
|
||||
return cb();
|
||||
};
|
||||
|
|
|
@ -11,11 +11,18 @@ const readOnlyManager = require('../../db/ReadOnlyManager');
|
|||
const rateLimit = require('express-rate-limit');
|
||||
const securityManager = require('../../db/SecurityManager');
|
||||
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({
|
||||
...settings.importExportRateLimiting,
|
||||
handler: (request:any) => {
|
||||
handler: (request: any) => {
|
||||
if (request.rateLimit.current === request.rateLimit.limit + 1) {
|
||||
// when the rate limiter triggers, write a warning in the logs
|
||||
console.warn('Import/Export rate limiter triggered on ' +
|
||||
|
@ -25,60 +32,78 @@ exports.expressCreateServer = (hookName:string, args:ArgsExpressType, cb:Functio
|
|||
});
|
||||
|
||||
|
||||
const handleImport = (req:any, res:any, next:Function) => {
|
||||
(async () => {
|
||||
const types = ['pdf', 'doc', 'txt', 'html', 'odt', 'etherpad'];
|
||||
// send a 404 if we don't support this filetype
|
||||
if (types.indexOf(req.params.type) === -1) {
|
||||
return next();
|
||||
const handleImport = async (req: any, res: any) => {
|
||||
const types = ['pdf', 'doc', 'txt', 'html', 'odt', 'etherpad'];
|
||||
// send a 404 if we don't support this filetype
|
||||
if (types.indexOf(req.params.type) === -1) {
|
||||
return res.send(409);
|
||||
}
|
||||
|
||||
// if abiword is disabled, and this is a format we only support with abiword, output a message
|
||||
if (settings.exportAvailable() === 'no' &&
|
||||
['odt', 'pdf', 'doc'].indexOf(req.params.type) !== -1) {
|
||||
console.error(`Impossible to export pad "${req.params.pad}" in ${req.params.type} format.` +
|
||||
' There is no converter configured');
|
||||
|
||||
// ACHTUNG: do not include req.params.type in res.send() because there is
|
||||
// no HTML escaping and it would lead to an XSS
|
||||
res.send('This export is not enabled at this Etherpad instance. Set the path to Abiword' +
|
||||
' or soffice (LibreOffice) in settings.json to enable this feature');
|
||||
return;
|
||||
}
|
||||
|
||||
res.header('Access-Control-Allow-Origin', '*');
|
||||
|
||||
if (await hasPadAccess(req, res)) {
|
||||
let padId = req.params.pad;
|
||||
|
||||
let readOnlyId = null;
|
||||
if (readOnlyManager.isReadOnlyId(padId)) {
|
||||
readOnlyId = padId;
|
||||
padId = await readOnlyManager.getPadId(readOnlyId);
|
||||
}
|
||||
|
||||
// if abiword is disabled, and this is a format we only support with abiword, output a message
|
||||
if (settings.exportAvailable() === 'no' &&
|
||||
['odt', 'pdf', 'doc'].indexOf(req.params.type) !== -1) {
|
||||
console.error(`Impossible to export pad "${req.params.pad}" in ${req.params.type} format.` +
|
||||
' There is no converter configured');
|
||||
|
||||
// ACHTUNG: do not include req.params.type in res.send() because there is
|
||||
// no HTML escaping and it would lead to an XSS
|
||||
res.send('This export is not enabled at this Etherpad instance. Set the path to Abiword' +
|
||||
' or soffice (LibreOffice) in settings.json to enable this feature');
|
||||
return;
|
||||
const exists = await padManager.doesPadExists(padId);
|
||||
if (!exists) {
|
||||
console.warn(`Someone tried to export a pad that doesn't exist (${padId})`);
|
||||
return {
|
||||
code: 404,
|
||||
message: 'notfound',
|
||||
};
|
||||
}
|
||||
|
||||
res.header('Access-Control-Allow-Origin', '*');
|
||||
|
||||
if (await hasPadAccess(req, res)) {
|
||||
let padId = req.params.pad;
|
||||
|
||||
let readOnlyId = null;
|
||||
if (readOnlyManager.isReadOnlyId(padId)) {
|
||||
readOnlyId = padId;
|
||||
padId = await readOnlyManager.getPadId(readOnlyId);
|
||||
}
|
||||
|
||||
const exists = await padManager.doesPadExists(padId);
|
||||
if (!exists) {
|
||||
console.warn(`Someone tried to export a pad that doesn't exist (${padId})`);
|
||||
return next();
|
||||
}
|
||||
|
||||
console.log(`Exporting pad "${req.params.pad}" in ${req.params.type} format`);
|
||||
await exportHandler.doExport(req, res, padId, readOnlyId, req.params.type);
|
||||
}
|
||||
})().catch((err) => next(err || new Error(err)));
|
||||
console.log(`Exporting pad "${req.params.pad}" in ${req.params.type} format`);
|
||||
await exportHandler.doExport(req, res, padId, readOnlyId, req.params.type);
|
||||
}
|
||||
};
|
||||
|
||||
// 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/export/:type', handleImport);
|
||||
args.app.get('/p/:pad/:rev/export/:type',{
|
||||
config: {
|
||||
rateLimit: {
|
||||
...settings.importExportRateLimiting
|
||||
}
|
||||
}
|
||||
}, handleImport);
|
||||
args.app.get('/p/:pad/export/:type',{
|
||||
config: {
|
||||
rateLimit: {
|
||||
...settings.importExportRateLimiting
|
||||
}
|
||||
}
|
||||
}, handleImport);
|
||||
|
||||
// handle import requests
|
||||
args.app.use('/p/:pad/import', limiter);
|
||||
args.app.post('/p/:pad/import', async (req: any, res: any, next: Function) => {
|
||||
args.app.get('/p/:pad/import',{
|
||||
config: {
|
||||
rateLimit: {
|
||||
...settings.importExportRateLimiting
|
||||
}
|
||||
}
|
||||
}, limiter);
|
||||
args.app.post('/p/:pad/import', async (req: any, res: any) => {
|
||||
// @ts-ignore
|
||||
const {session: {user} = {}} = req;
|
||||
const {accessStatus, authorID: authorId} = await securityManager.checkAccess(
|
||||
req.params.pad, req.cookies.sessionID, req.cookies.token, user);
|
||||
|
|
|
@ -658,7 +658,7 @@ exports.expressPreSession = async (hookName:string, {app}:any) => {
|
|||
|
||||
// start and bind to express
|
||||
api.init();
|
||||
app.use(apiRoot, async (req:any, res:any) => {
|
||||
app.get(apiRoot, async (req:any, res:any) => {
|
||||
let response = null;
|
||||
try {
|
||||
if (style === APIPathStyle.REST) {
|
||||
|
|
|
@ -8,6 +8,8 @@ import log4js from 'log4js';
|
|||
const proxyaddr = require('proxy-addr');
|
||||
const settings = require('../../utils/Settings');
|
||||
import {Server, Socket} from 'socket.io'
|
||||
import path from "path";
|
||||
import {readFileSync} from "fs";
|
||||
const socketIORouter = require('../../handler/SocketIORouter');
|
||||
const hooks = require('../../../static/js/pluginfw/hooks');
|
||||
const padMessageHandler = require('../../handler/PadMessageHandler');
|
||||
|
@ -76,6 +78,12 @@ export const expressCreateServer = (hookName:string, args:ArgsExpressType, cb:Fu
|
|||
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) => {
|
||||
sockets.add(socket);
|
||||
|
|
|
@ -73,12 +73,13 @@ exports.expressPreSession = async (hookName:string, {app}:any) => {
|
|||
exports.expressCreateServer = (hookName:string, args:any, cb:Function) => {
|
||||
// serve index.html under /
|
||||
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.send(eejs.require('ep_etherpad-lite/templates/index.html', {req}));
|
||||
});
|
||||
|
||||
// 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
|
||||
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
|
||||
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', {
|
||||
toolbar,
|
||||
});
|
||||
|
|
|
@ -9,6 +9,7 @@ const path = require('path');
|
|||
const plugins = require('../../../static/js/pluginfw/plugin_defs');
|
||||
const settings = require('../../utils/Settings');
|
||||
import CachingMiddleware from '../../utils/caching_middleware';
|
||||
import {FastifyInstance} from "fastify";
|
||||
const Yajsml = require('etherpad-yajsml');
|
||||
|
||||
// Rewrite tar to include modules with no extensions and proper rooted paths.
|
||||
|
@ -32,7 +33,9 @@ const getTar = async () => {
|
|||
return tar;
|
||||
};
|
||||
|
||||
exports.expressPreSession = async (hookName:string, {app}:any) => {
|
||||
exports.expressPreSession = async (hookName:string, {app}: {
|
||||
app: FastifyInstance
|
||||
}) => {
|
||||
// Cache both minified and static.
|
||||
const assetCache = new CachingMiddleware();
|
||||
// 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) => {
|
||||
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}`);
|
||||
const file = fs.readFile(path.join(settings.root, 'src/static/js/require-kernel.js'), 'utf8');
|
||||
res.send();
|
||||
res.send(requireDefinition());
|
||||
})
|
||||
|
||||
app.route({
|
||||
|
@ -57,19 +61,18 @@ exports.expressPreSession = async (hookName:string, {app}:any) => {
|
|||
handler: assetCache.handle.bind(assetCache)
|
||||
});
|
||||
|
||||
/*app.register(require('@fastify/static'), {
|
||||
app.register(require('@fastify/static'), {
|
||||
root: path.join(settings.root, 'src', 'static'),
|
||||
prefix: '/static/', // optional: default '/'
|
||||
constraints: {} // optional: default {}
|
||||
})*/
|
||||
})
|
||||
|
||||
// Minify will serve static files compressed (minify enabled). It also has
|
||||
// file-specific hacks for ace/require-kernel/etc.
|
||||
app.route({
|
||||
/*app.route({
|
||||
method: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'],
|
||||
url: '/static/*',
|
||||
handler: minify.minify
|
||||
});
|
||||
});*/
|
||||
|
||||
// Setup middleware that will package JavaScript files served by minify for
|
||||
// CommonJS loader on the client-side.
|
||||
|
@ -87,12 +90,19 @@ exports.expressPreSession = async (hookName:string, {app}:any) => {
|
|||
const associator = new StaticAssociator(associations);
|
||||
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
|
||||
// not very static, but served here so that client can do
|
||||
// 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 clientPlugins:MapArrayType<string> = {};
|
||||
for (const name of new Set(clientParts.map((part: PartType) => part.plugin))) {
|
||||
|
|
|
@ -5,6 +5,7 @@ import log4js from 'log4js';
|
|||
import {SocketClientRequest} from "../../types/SocketClientRequest";
|
||||
import {WebAccessTypes} from "../../types/WebAccessTypes";
|
||||
import {SettingsUser} from "../../types/SettingsUser";
|
||||
import {FastifyReply, FastifyRequest} from "fastify";
|
||||
const httpLogger = log4js.getLogger('http');
|
||||
const settings = require('../../utils/Settings');
|
||||
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.
|
||||
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);
|
||||
});
|
||||
|
||||
|
@ -49,8 +51,8 @@ exports.userCanModify = (padId: string, req: SocketClientRequest) => {
|
|||
// Exported so that tests can set this to 0 to avoid unnecessary test slowness.
|
||||
exports.authnFailureDelayMs = 1000;
|
||||
|
||||
const checkAccess = async (req:any, res:any, next: Function) => {
|
||||
const requireAdmin = req.path.toLowerCase().startsWith('/admin-auth');
|
||||
const checkAccess = async (req:FastifyRequest, res: FastifyReply, next: Function) => {
|
||||
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
|
||||
|
@ -94,10 +96,12 @@ const checkAccess = async (req:any, res:any, next: Function) => {
|
|||
const authorize = async () => {
|
||||
const grant = async (level: string|false) => {
|
||||
level = exports.normalizeAuthzLevel(level);
|
||||
console.log("Level is", level)
|
||||
if (!level) return false;
|
||||
const user = req.session.user;
|
||||
console.log("User is", user)
|
||||
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;
|
||||
let padId = decodeURIComponent(encodedPadId);
|
||||
if (readOnlyManager.isReadOnlyId(padId)) {
|
||||
|
@ -118,15 +122,17 @@ const checkAccess = async (req:any, res:any, next: Function) => {
|
|||
if (!isAuthenticated) return await grant(false);
|
||||
if (requireAdmin && !req.session.user.is_admin) return await grant(false);
|
||||
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
|
||||
// completed, or maybe different credentials are required), go to the next step.
|
||||
// ///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
console.log("Authorize")
|
||||
if (await authorize()) {
|
||||
console.log("Authorize2")
|
||||
if(requireAdmin) {
|
||||
res.status(200).send('Authorized')
|
||||
return
|
||||
|
@ -149,7 +155,7 @@ const checkAccess = async (req:any, res:any, next: Function) => {
|
|||
const httpBasicAuth = req.headers.authorization && req.headers.authorization.startsWith('Basic ');
|
||||
if (httpBasicAuth) {
|
||||
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();
|
||||
// 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
|
||||
|
@ -161,7 +167,6 @@ const checkAccess = async (req:any, res:any, next: Function) => {
|
|||
// Fall back to HTTP basic auth.
|
||||
// @ts-ignore
|
||||
const {[ctx.username]: {password} = {}} = settings.users as SettingsUser;
|
||||
|
||||
if (!httpBasicAuth ||
|
||||
!ctx.username ||
|
||||
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"');
|
||||
// Delay the error response for 1s to slow down brute force attacks.
|
||||
await new Promise((resolve) => setTimeout(resolve, exports.authnFailureDelayMs));
|
||||
res.status(401).send('Authentication Required');
|
||||
return;
|
||||
await res.status(401).send('Authentication Required');
|
||||
throw new Error('Authentication Required');
|
||||
}
|
||||
settings.users[ctx.username].username = ctx.username;
|
||||
// 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]};
|
||||
delete req.session.user.password;
|
||||
}
|
||||
console.log("Session is", req.session)
|
||||
if (req.session.user == null) {
|
||||
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;
|
||||
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()
|
||||
if (auth && !requireAdmin) return next();
|
||||
if(auth && requireAdmin) {
|
||||
res.status(200).send('Authorized')
|
||||
return
|
||||
if (auth && !requireAdmin) {
|
||||
return next();
|
||||
}
|
||||
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('authFailure', {req, res, next})) return;
|
||||
// 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-session middleware.
|
||||
*/
|
||||
exports.checkAccess = (req:any, res:any, next:Function) => {
|
||||
checkAccess(req, res, next).catch((err) => next(err || new Error(err)));
|
||||
exports.checkAccess = async (req: any, res: any, next: Function) => {
|
||||
return await checkAccess(req, res, next).catch((err) => next(err || new Error(err)));
|
||||
};
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import {FastifyInstance} from "fastify";
|
||||
|
||||
export type ArgsExpressType = {
|
||||
app:any,
|
||||
app:FastifyInstance,
|
||||
io: any,
|
||||
server:any
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,6 @@ export type WebAccessTypes = {
|
|||
password?: string;
|
||||
req:any;
|
||||
res:any;
|
||||
next:any;
|
||||
next:Function;
|
||||
users: SettingsUser;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import path from 'path';
|
|||
// 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.
|
||||
module.exports = (p: string, pathApi = path) => {
|
||||
console.log('p:', p);
|
||||
// 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
|
||||
// not be the same thing as 'b'. Most path normalization functions from other libraries (e.g.,
|
||||
|
|
|
@ -30,7 +30,11 @@
|
|||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@fastify/compress": "^7.0.0",
|
||||
"@fastify/cookie": "^9.3.1",
|
||||
"@fastify/express": "^2.3.0",
|
||||
"@fastify/rate-limit": "^9.1.0",
|
||||
"@fastify/session": "^10.7.0",
|
||||
"@fastify/static": "^7.0.1",
|
||||
"async": "^3.2.5",
|
||||
"axios": "^1.6.8",
|
||||
|
@ -40,7 +44,6 @@
|
|||
"ejs": "^3.1.9",
|
||||
"etherpad-require-kernel": "^1.0.16",
|
||||
"etherpad-yajsml": "0.0.12",
|
||||
"express": "4.18.3",
|
||||
"express-rate-limit": "^7.2.0",
|
||||
"express-session": "npm:@etherpad/express-session@^1.18.2",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
|
@ -88,7 +91,6 @@
|
|||
"devDependencies": {
|
||||
"@playwright/test": "^1.42.1",
|
||||
"@types/async": "^3.2.24",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/http-errors": "^2.0.4",
|
||||
"@types/jsdom": "^21.1.6",
|
||||
"@types/mocha": "^10.0.6",
|
||||
|
|
|
@ -23,13 +23,12 @@
|
|||
*/
|
||||
|
||||
let socket;
|
||||
const requireFromUrl = require('require-from-url/sync');
|
||||
|
||||
// These jQuery things should create local references, but for now `require()`
|
||||
// assigns to the global `$` and augments it with plugins.
|
||||
requireFromUrl('./vendors/jquery');
|
||||
requireFromUrl('./vendors/farbtastic');
|
||||
requireFromUrl('./vendors/gritter');
|
||||
require('./vendors/jquery');
|
||||
require('./vendors/farbtastic');
|
||||
require('./vendors/gritter');
|
||||
|
||||
const Cookies = require('./pad_utils').Cookies;
|
||||
const chat = require('./chat').chat;
|
||||
|
|
|
@ -357,7 +357,7 @@ let globalExceptionHandler = null;
|
|||
padutils.setupGlobalExceptionHandler = () => {
|
||||
if (globalExceptionHandler == null) {
|
||||
require('./vendors/gritter');
|
||||
globalExceptionHandler = (e) => {
|
||||
globalExceptionHandler = async (e) => {
|
||||
let type;
|
||||
let err;
|
||||
let msg, url, linenumber;
|
||||
|
@ -410,8 +410,9 @@ padutils.setupGlobalExceptionHandler = () => {
|
|||
}
|
||||
|
||||
// send javascript errors to the server
|
||||
$.post('../jserror', {
|
||||
errorInfo: JSON.stringify({
|
||||
await fetch('../jserror', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
errorId,
|
||||
type,
|
||||
msg,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue