draft for login and sessions

Massimo Melina committed Dec 17, 2021 at 15:45 UTC 58b3b1de7c817e1195e6d8fdd7699648f789b3e8
19 files changed +436 -130
dev-notes.md
+10 -3
@@ -8,17 +8,24 @@
8 - https://www.tutorialspoint.com/koajs/koajs_quick_guide.htm
9
10 # To do
11 -- more tests
12 -- permissions
11 - search
12 +- frontend: dialogs
13 - vfs: serve an html for a folder?
14 - "default" property for vfsNode?
15 - sticky breadcrumbs
16 - interruption of long requests if client aborted (searching/listing)
17 +- throttle speed
18 - frontend
19 - don't depend on cdn
20 - webdav?
21 - streamable zip archives
22 - no compression
23 - resumable?
24 -- vfs: ability to remove/hide/rename files deep in a source
\ No newline at end of file
24 +- vfs: ability to remove/hide/rename files deep in a source
25 +- let user change password
26 +- login without passing clear text password?
27 + we could use asymmetric encryption, possibly on a hashed password, that means
28 + we should store a hash2(hash1(password+salt1)), where hash1 is applied on both client
29 + and server, which grants that even if the encryption is broken only the salt-hashed
30 + is revealed, which compromises only this server and not others.
31 + http://qnimate.com/asymmetric-encryption-using-web-cryptography-api/
frontend/package-lock.json
+216 -91
@@ -11,6 +11,7 @@
11 "react": "^17.0.2",
12 "react-dom": "^17.0.2",
13 "react-router-dom": "^6.1.1",
14 + "valtio": "^1.2.7",
15 "web-vitals": "^1.0.1"
16 },
17 "devDependencies": {
@@ -29,7 +30,7 @@
30 },
31 "node_modules/@babel/code-frame": {
32 "version": "7.12.13",
32 - "dev": true,
33 + "devOptional": true,
34 "license": "MIT",
35 "dependencies": {
36 "@babel/highlight": "^7.12.13"
@@ -266,7 +267,7 @@
267 },
268 "node_modules/@babel/helper-module-imports": {
269 "version": "7.12.13",
269 - "dev": true,
270 + "devOptional": true,
271 "license": "MIT",
272 "dependencies": {
273 "@babel/types": "^7.12.13"
@@ -347,9 +348,13 @@
348 }
349 },
350 "node_modules/@babel/helper-validator-identifier": {
350 - "version": "7.12.11",
351 - "dev": true,
352 - "license": "MIT"
351 + "version": "7.15.7",
352 + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz",
353 + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==",
354 + "devOptional": true,
355 + "engines": {
356 + "node": ">=6.9.0"
357 + }
358 },
359 "node_modules/@babel/helper-validator-option": {
360 "version": "7.12.17",
@@ -379,7 +384,7 @@
384 },
385 "node_modules/@babel/highlight": {
386 "version": "7.12.13",
382 - "dev": true,
387 + "devOptional": true,
388 "license": "MIT",
389 "dependencies": {
390 "@babel/helper-validator-identifier": "^7.12.11",
@@ -389,7 +394,7 @@
394 },
395 "node_modules/@babel/highlight/node_modules/ansi-styles": {
396 "version": "3.2.1",
392 - "dev": true,
397 + "devOptional": true,
398 "license": "MIT",
399 "dependencies": {
400 "color-convert": "^1.9.0"
@@ -400,7 +405,7 @@
405 },
406 "node_modules/@babel/highlight/node_modules/chalk": {
407 "version": "2.4.2",
403 - "dev": true,
408 + "devOptional": true,
409 "license": "MIT",
410 "dependencies": {
411 "ansi-styles": "^3.2.1",
@@ -413,7 +418,7 @@
418 },
419 "node_modules/@babel/highlight/node_modules/escape-string-regexp": {
420 "version": "1.0.5",
416 - "dev": true,
421 + "devOptional": true,
422 "license": "MIT",
423 "engines": {
424 "node": ">=0.8.0"
@@ -421,7 +426,7 @@
426 },
427 "node_modules/@babel/highlight/node_modules/supports-color": {
428 "version": "5.5.0",
424 - "dev": true,
429 + "devOptional": true,
430 "license": "MIT",
431 "dependencies": {
432 "has-flag": "^3.0.0"
@@ -1331,13 +1336,16 @@
1336 }
1337 },
1338 "node_modules/@babel/types": {
1334 - "version": "7.12.17",
1335 - "dev": true,
1336 - "license": "MIT",
1339 + "version": "7.16.0",
1340 + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz",
1341 + "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==",
1342 + "devOptional": true,
1343 "dependencies": {
1338 - "@babel/helper-validator-identifier": "^7.12.11",
1339 - "lodash": "^4.17.19",
1344 + "@babel/helper-validator-identifier": "^7.15.7",
1345 "to-fast-properties": "^2.0.0"
1346 + },
1347 + "engines": {
1348 + "node": ">=6.9.0"
1349 }
1350 },
1351 "node_modules/@bcoe/v8-coverage": {
@@ -2488,7 +2496,7 @@
2496 },
2497 "node_modules/@types/parse-json": {
2498 "version": "4.0.0",
2491 - "dev": true,
2499 + "devOptional": true,
2500 "license": "MIT"
2501 },
2502 "node_modules/@types/prettier": {
@@ -3939,28 +3947,36 @@
3947 }
3948 },
3949 "node_modules/babel-plugin-macros": {
3942 - "version": "2.8.0",
3943 - "dev": true,
3944 - "license": "MIT",
3950 + "version": "3.1.0",
3951 + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
3952 + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
3953 + "optional": true,
3954 + "peer": true,
3955 "dependencies": {
3946 - "@babel/runtime": "^7.7.2",
3947 - "cosmiconfig": "^6.0.0",
3948 - "resolve": "^1.12.0"
3956 + "@babel/runtime": "^7.12.5",
3957 + "cosmiconfig": "^7.0.0",
3958 + "resolve": "^1.19.0"
3959 + },
3960 + "engines": {
3961 + "node": ">=10",
3962 + "npm": ">=6"
3963 }
3964 },
3965 "node_modules/babel-plugin-macros/node_modules/cosmiconfig": {
3952 - "version": "6.0.0",
3953 - "dev": true,
3954 - "license": "MIT",
3966 + "version": "7.0.1",
3967 + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz",
3968 + "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==",
3969 + "optional": true,
3970 + "peer": true,
3971 "dependencies": {
3972 "@types/parse-json": "^4.0.0",
3957 - "import-fresh": "^3.1.0",
3973 + "import-fresh": "^3.2.1",
3974 "parse-json": "^5.0.0",
3975 "path-type": "^4.0.0",
3960 - "yaml": "^1.7.2"
3976 + "yaml": "^1.10.0"
3977 },
3978 "engines": {
3963 - "node": ">=8"
3979 + "node": ">=10"
3980 }
3981 },
3982 "node_modules/babel-plugin-syntax-object-rest-spread": {
@@ -4663,6 +4679,33 @@
4679 "regenerator-runtime": "^0.13.4"
4680 }
4681 },
4682 + "node_modules/babel-preset-react-app/node_modules/babel-plugin-macros": {
4683 + "version": "2.8.0",
4684 + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz",
4685 + "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==",
4686 + "dev": true,
4687 + "dependencies": {
4688 + "@babel/runtime": "^7.7.2",
4689 + "cosmiconfig": "^6.0.0",
4690 + "resolve": "^1.12.0"
4691 + }
4692 + },
4693 + "node_modules/babel-preset-react-app/node_modules/cosmiconfig": {
4694 + "version": "6.0.0",
4695 + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz",
4696 + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==",
4697 + "dev": true,
4698 + "dependencies": {
4699 + "@types/parse-json": "^4.0.0",
4700 + "import-fresh": "^3.1.0",
4701 + "parse-json": "^5.0.0",
4702 + "path-type": "^4.0.0",
4703 + "yaml": "^1.7.2"
4704 + },
4705 + "engines": {
4706 + "node": ">=8"
4707 + }
4708 + },
4709 "node_modules/babel-preset-react-app/node_modules/json5": {
4710 "version": "2.2.0",
4711 "dev": true,
@@ -5221,7 +5264,7 @@
5264 },
5265 "node_modules/callsites": {
5266 "version": "3.1.0",
5224 - "dev": true,
5267 + "devOptional": true,
5268 "license": "MIT",
5269 "engines": {
5270 "node": ">=6"
@@ -5587,7 +5630,7 @@
5630 },
5631 "node_modules/color-convert": {
5632 "version": "1.9.3",
5590 - "dev": true,
5633 + "devOptional": true,
5634 "license": "MIT",
5635 "dependencies": {
5636 "color-name": "1.1.3"
@@ -5595,7 +5638,7 @@
5638 },
5639 "node_modules/color-convert/node_modules/color-name": {
5640 "version": "1.1.3",
5598 - "dev": true,
5641 + "devOptional": true,
5642 "license": "MIT"
5643 },
5644 "node_modules/color-name": {
@@ -7147,7 +7190,7 @@
7190 },
7191 "node_modules/error-ex": {
7192 "version": "1.3.2",
7150 - "dev": true,
7193 + "devOptional": true,
7194 "license": "MIT",
7195 "dependencies": {
7196 "is-arrayish": "^0.2.1"
@@ -8877,7 +8920,7 @@
8920 },
8921 "node_modules/function-bind": {
8922 "version": "1.1.1",
8880 - "dev": true,
8923 + "devOptional": true,
8924 "license": "MIT"
8925 },
8926 "node_modules/functional-red-black-tree": {
@@ -9232,7 +9275,7 @@
9275 },
9276 "node_modules/has": {
9277 "version": "1.0.3",
9235 - "dev": true,
9278 + "devOptional": true,
9279 "license": "MIT",
9280 "dependencies": {
9281 "function-bind": "^1.1.1"
@@ -9243,7 +9286,7 @@
9286 },
9287 "node_modules/has-flag": {
9288 "version": "3.0.0",
9246 - "dev": true,
9289 + "devOptional": true,
9290 "license": "MIT",
9291 "engines": {
9292 "node": ">=4"
@@ -9762,7 +9805,7 @@
9805 },
9806 "node_modules/import-fresh": {
9807 "version": "3.3.0",
9765 - "dev": true,
9808 + "devOptional": true,
9809 "license": "MIT",
9810 "dependencies": {
9811 "parent-module": "^1.0.0",
@@ -9777,7 +9820,7 @@
9820 },
9821 "node_modules/import-fresh/node_modules/resolve-from": {
9822 "version": "4.0.0",
9780 - "dev": true,
9823 + "devOptional": true,
9824 "license": "MIT",
9825 "engines": {
9826 "node": ">=4"
@@ -9954,7 +9997,7 @@
9997 },
9998 "node_modules/is-arrayish": {
9999 "version": "0.2.1",
9957 - "dev": true,
10000 + "devOptional": true,
10001 "license": "MIT"
10002 },
10003 "node_modules/is-binary-path": {
@@ -10012,7 +10055,7 @@
10055 "version": "2.8.0",
10056 "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz",
10057 "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==",
10015 - "dev": true,
10058 + "devOptional": true,
10059 "dependencies": {
10060 "has": "^1.0.3"
10061 },
@@ -11283,7 +11326,7 @@
11326 },
11327 "node_modules/json-parse-even-better-errors": {
11328 "version": "2.3.1",
11286 - "dev": true,
11329 + "devOptional": true,
11330 "license": "MIT"
11331 },
11332 "node_modules/json-schema": {
@@ -11450,7 +11493,7 @@
11493 },
11494 "node_modules/lines-and-columns": {
11495 "version": "1.1.6",
11453 - "dev": true,
11496 + "devOptional": true,
11497 "license": "MIT"
11498 },
11499 "node_modules/load-json-file": {
@@ -13195,7 +13238,7 @@
13238 },
13239 "node_modules/parent-module": {
13240 "version": "1.0.1",
13198 - "dev": true,
13241 + "devOptional": true,
13242 "license": "MIT",
13243 "dependencies": {
13244 "callsites": "^3.0.0"
@@ -13218,7 +13261,7 @@
13261 },
13262 "node_modules/parse-json": {
13263 "version": "5.2.0",
13221 - "dev": true,
13264 + "devOptional": true,
13265 "license": "MIT",
13266 "dependencies": {
13267 "@babel/code-frame": "^7.0.0",
@@ -13304,7 +13347,7 @@
13347 },
13348 "node_modules/path-parse": {
13349 "version": "1.0.6",
13307 - "dev": true,
13350 + "devOptional": true,
13351 "license": "MIT"
13352 },
13353 "node_modules/path-to-regexp": {
@@ -13314,7 +13357,7 @@
13357 },
13358 "node_modules/path-type": {
13359 "version": "4.0.0",
13317 - "dev": true,
13360 + "devOptional": true,
13361 "license": "MIT",
13362 "engines": {
13363 "node": ">=8"
@@ -14743,6 +14786,11 @@
14786 "node": ">= 0.10"
14787 }
14788 },
14789 + "node_modules/proxy-compare": {
14790 + "version": "2.0.2",
14791 + "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.0.2.tgz",
14792 + "integrity": "sha512-3qUXJBariEj3eO90M3Rgqq3+/P5Efl0t/dl9g/1uVzIQmO3M+ql4hvNH3mYdu8H+1zcKv07YvL55tsY74jmH1A=="
14793 + },
14794 "node_modules/prr": {
14795 "version": "1.0.1",
14796 "dev": true,
@@ -15964,7 +16012,7 @@
16012 },
16013 "node_modules/resolve": {
16014 "version": "1.20.0",
15967 - "dev": true,
16015 + "devOptional": true,
16016 "license": "MIT",
16017 "dependencies": {
16018 "is-core-module": "^2.2.0",
@@ -18296,7 +18344,7 @@
18344 },
18345 "node_modules/to-fast-properties": {
18346 "version": "2.0.0",
18299 - "dev": true,
18347 + "devOptional": true,
18348 "license": "MIT",
18349 "engines": {
18350 "node": ">=4"
@@ -18901,6 +18949,37 @@
18949 "spdx-expression-parse": "^3.0.0"
18950 }
18951 },
18952 + "node_modules/valtio": {
18953 + "version": "1.2.7",
18954 + "resolved": "https://registry.npmjs.org/valtio/-/valtio-1.2.7.tgz",
18955 + "integrity": "sha512-vm9XwsndXVt+XTFwmb2Myo2p319Ssu/SDVRjBhDGQcNi6Epc8g0Pf/QaWAr67A3P1x4b1NApjK7qHjdL1WpuaQ==",
18956 + "dependencies": {
18957 + "proxy-compare": "2.0.2"
18958 + },
18959 + "engines": {
18960 + "node": ">=12.7.0"
18961 + },
18962 + "peerDependencies": {
18963 + "@babel/helper-module-imports": ">=7.12",
18964 + "@babel/types": ">=7.13",
18965 + "babel-plugin-macros": ">=3.0",
18966 + "react": ">=16.8"
18967 + },
18968 + "peerDependenciesMeta": {
18969 + "@babel/helper-module-imports": {
18970 + "optional": true
18971 + },
18972 + "@babel/types": {
18973 + "optional": true
18974 + },
18975 + "babel-plugin-macros": {
18976 + "optional": true
18977 + },
18978 + "react": {
18979 + "optional": true
18980 + }
18981 + }
18982 + },
18983 "node_modules/vary": {
18984 "version": "1.1.2",
18985 "dev": true,
@@ -20076,7 +20155,7 @@
20155 },
20156 "node_modules/yaml": {
20157 "version": "1.10.0",
20079 - "dev": true,
20158 + "devOptional": true,
20159 "license": "ISC",
20160 "engines": {
20161 "node": ">= 6"
@@ -20130,7 +20209,7 @@
20209 "dependencies": {
20210 "@babel/code-frame": {
20211 "version": "7.12.13",
20133 - "dev": true,
20212 + "devOptional": true,
20213 "requires": {
20214 "@babel/highlight": "^7.12.13"
20215 }
@@ -20309,7 +20388,7 @@
20388 },
20389 "@babel/helper-module-imports": {
20390 "version": "7.12.13",
20312 - "dev": true,
20391 + "devOptional": true,
20392 "requires": {
20393 "@babel/types": "^7.12.13"
20394 }
@@ -20381,8 +20460,10 @@
20460 }
20461 },
20462 "@babel/helper-validator-identifier": {
20384 - "version": "7.12.11",
20385 - "dev": true
20463 + "version": "7.15.7",
20464 + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz",
20465 + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==",
20466 + "devOptional": true
20467 },
20468 "@babel/helper-validator-option": {
20469 "version": "7.12.17",
@@ -20409,7 +20490,7 @@
20490 },
20491 "@babel/highlight": {
20492 "version": "7.12.13",
20412 - "dev": true,
20493 + "devOptional": true,
20494 "requires": {
20495 "@babel/helper-validator-identifier": "^7.12.11",
20496 "chalk": "^2.0.0",
@@ -20418,14 +20499,14 @@
20499 "dependencies": {
20500 "ansi-styles": {
20501 "version": "3.2.1",
20421 - "dev": true,
20502 + "devOptional": true,
20503 "requires": {
20504 "color-convert": "^1.9.0"
20505 }
20506 },
20507 "chalk": {
20508 "version": "2.4.2",
20428 - "dev": true,
20509 + "devOptional": true,
20510 "requires": {
20511 "ansi-styles": "^3.2.1",
20512 "escape-string-regexp": "^1.0.5",
@@ -20434,11 +20515,11 @@
20515 },
20516 "escape-string-regexp": {
20517 "version": "1.0.5",
20437 - "dev": true
20518 + "devOptional": true
20519 },
20520 "supports-color": {
20521 "version": "5.5.0",
20441 - "dev": true,
20522 + "devOptional": true,
20523 "requires": {
20524 "has-flag": "^3.0.0"
20525 }
@@ -21068,11 +21149,12 @@
21149 }
21150 },
21151 "@babel/types": {
21071 - "version": "7.12.17",
21072 - "dev": true,
21152 + "version": "7.16.0",
21153 + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz",
21154 + "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==",
21155 + "devOptional": true,
21156 "requires": {
21074 - "@babel/helper-validator-identifier": "^7.12.11",
21075 - "lodash": "^4.17.19",
21157 + "@babel/helper-validator-identifier": "^7.15.7",
21158 "to-fast-properties": "^2.0.0"
21159 }
21160 },
@@ -21863,7 +21945,7 @@
21945 },
21946 "@types/parse-json": {
21947 "version": "4.0.0",
21866 - "dev": true
21948 + "devOptional": true
21949 },
21950 "@types/prettier": {
21951 "version": "2.2.1",
@@ -22869,23 +22951,29 @@
22951 }
22952 },
22953 "babel-plugin-macros": {
22872 - "version": "2.8.0",
22873 - "dev": true,
22954 + "version": "3.1.0",
22955 + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
22956 + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
22957 + "optional": true,
22958 + "peer": true,
22959 "requires": {
22875 - "@babel/runtime": "^7.7.2",
22876 - "cosmiconfig": "^6.0.0",
22877 - "resolve": "^1.12.0"
22960 + "@babel/runtime": "^7.12.5",
22961 + "cosmiconfig": "^7.0.0",
22962 + "resolve": "^1.19.0"
22963 },
22964 "dependencies": {
22965 "cosmiconfig": {
22881 - "version": "6.0.0",
22882 - "dev": true,
22966 + "version": "7.0.1",
22967 + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz",
22968 + "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==",
22969 + "optional": true,
22970 + "peer": true,
22971 "requires": {
22972 "@types/parse-json": "^4.0.0",
22885 - "import-fresh": "^3.1.0",
22973 + "import-fresh": "^3.2.1",
22974 "parse-json": "^5.0.0",
22975 "path-type": "^4.0.0",
22888 - "yaml": "^1.7.2"
22976 + "yaml": "^1.10.0"
22977 }
22978 }
22979 }
@@ -23429,6 +23517,30 @@
23517 "regenerator-runtime": "^0.13.4"
23518 }
23519 },
23520 + "babel-plugin-macros": {
23521 + "version": "2.8.0",
23522 + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz",
23523 + "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==",
23524 + "dev": true,
23525 + "requires": {
23526 + "@babel/runtime": "^7.7.2",
23527 + "cosmiconfig": "^6.0.0",
23528 + "resolve": "^1.12.0"
23529 + }
23530 + },
23531 + "cosmiconfig": {
23532 + "version": "6.0.0",
23533 + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz",
23534 + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==",
23535 + "dev": true,
23536 + "requires": {
23537 + "@types/parse-json": "^4.0.0",
23538 + "import-fresh": "^3.1.0",
23539 + "parse-json": "^5.0.0",
23540 + "path-type": "^4.0.0",
23541 + "yaml": "^1.7.2"
23542 + }
23543 + },
23544 "json5": {
23545 "version": "2.2.0",
23546 "dev": true,
@@ -23845,7 +23957,7 @@
23957 },
23958 "callsites": {
23959 "version": "3.1.0",
23848 - "dev": true
23960 + "devOptional": true
23961 },
23962 "camel-case": {
23963 "version": "4.1.2",
@@ -24104,14 +24216,14 @@
24216 },
24217 "color-convert": {
24218 "version": "1.9.3",
24107 - "dev": true,
24219 + "devOptional": true,
24220 "requires": {
24221 "color-name": "1.1.3"
24222 },
24223 "dependencies": {
24224 "color-name": {
24225 "version": "1.1.3",
24114 - "dev": true
24226 + "devOptional": true
24227 }
24228 }
24229 },
@@ -25211,7 +25323,7 @@
25323 },
25324 "error-ex": {
25325 "version": "1.3.2",
25214 - "dev": true,
25326 + "devOptional": true,
25327 "requires": {
25328 "is-arrayish": "^0.2.1"
25329 }
@@ -26383,7 +26495,7 @@
26495 },
26496 "function-bind": {
26497 "version": "1.1.1",
26386 - "dev": true
26498 + "devOptional": true
26499 },
26500 "functional-red-black-tree": {
26501 "version": "1.0.1",
@@ -26636,14 +26748,14 @@
26748 },
26749 "has": {
26750 "version": "1.0.3",
26639 - "dev": true,
26751 + "devOptional": true,
26752 "requires": {
26753 "function-bind": "^1.1.1"
26754 }
26755 },
26756 "has-flag": {
26757 "version": "3.0.0",
26646 - "dev": true
26758 + "devOptional": true
26759 },
26760 "has-symbols": {
26761 "version": "1.0.1",
@@ -27010,7 +27122,7 @@
27122 },
27123 "import-fresh": {
27124 "version": "3.3.0",
27013 - "dev": true,
27125 + "devOptional": true,
27126 "requires": {
27127 "parent-module": "^1.0.0",
27128 "resolve-from": "^4.0.0"
@@ -27018,7 +27130,7 @@
27130 "dependencies": {
27131 "resolve-from": {
27132 "version": "4.0.0",
27021 - "dev": true
27133 + "devOptional": true
27134 }
27135 }
27136 },
@@ -27133,7 +27245,7 @@
27245 },
27246 "is-arrayish": {
27247 "version": "0.2.1",
27136 - "dev": true
27248 + "devOptional": true
27249 },
27250 "is-binary-path": {
27251 "version": "1.0.1",
@@ -27173,7 +27285,7 @@
27285 "version": "2.8.0",
27286 "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz",
27287 "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==",
27176 - "dev": true,
27288 + "devOptional": true,
27289 "requires": {
27290 "has": "^1.0.3"
27291 }
@@ -28052,7 +28164,7 @@
28164 },
28165 "json-parse-even-better-errors": {
28166 "version": "2.3.1",
28055 - "dev": true
28167 + "devOptional": true
28168 },
28169 "json-schema": {
28170 "version": "0.2.3",
@@ -28171,7 +28283,7 @@
28283 },
28284 "lines-and-columns": {
28285 "version": "1.1.6",
28174 - "dev": true
28286 + "devOptional": true
28287 },
28288 "load-json-file": {
28289 "version": "2.0.0",
@@ -29390,7 +29502,7 @@
29502 },
29503 "parent-module": {
29504 "version": "1.0.1",
29393 - "dev": true,
29505 + "devOptional": true,
29506 "requires": {
29507 "callsites": "^3.0.0"
29508 }
@@ -29408,7 +29520,7 @@
29520 },
29521 "parse-json": {
29522 "version": "5.2.0",
29411 - "dev": true,
29523 + "devOptional": true,
29524 "requires": {
29525 "@babel/code-frame": "^7.0.0",
29526 "error-ex": "^1.3.1",
@@ -29462,7 +29574,7 @@
29574 },
29575 "path-parse": {
29576 "version": "1.0.6",
29465 - "dev": true
29577 + "devOptional": true
29578 },
29579 "path-to-regexp": {
29580 "version": "0.1.7",
@@ -29470,7 +29582,7 @@
29582 },
29583 "path-type": {
29584 "version": "4.0.0",
29473 - "dev": true
29585 + "devOptional": true
29586 },
29587 "pbkdf2": {
29588 "version": "3.1.1",
@@ -30474,6 +30586,11 @@
30586 "ipaddr.js": "1.9.1"
30587 }
30588 },
30589 + "proxy-compare": {
30590 + "version": "2.0.2",
30591 + "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.0.2.tgz",
30592 + "integrity": "sha512-3qUXJBariEj3eO90M3Rgqq3+/P5Efl0t/dl9g/1uVzIQmO3M+ql4hvNH3mYdu8H+1zcKv07YvL55tsY74jmH1A=="
30593 + },
30594 "prr": {
30595 "version": "1.0.1",
30596 "dev": true
@@ -31322,7 +31439,7 @@
31439 },
31440 "resolve": {
31441 "version": "1.20.0",
31325 - "dev": true,
31442 + "devOptional": true,
31443 "requires": {
31444 "is-core-module": "^2.2.0",
31445 "path-parse": "^1.0.6"
@@ -32993,7 +33110,7 @@
33110 },
33111 "to-fast-properties": {
33112 "version": "2.0.0",
32996 - "dev": true
33113 + "devOptional": true
33114 },
33115 "to-object-path": {
33116 "version": "0.3.0",
@@ -33403,6 +33520,14 @@
33520 "spdx-expression-parse": "^3.0.0"
33521 }
33522 },
33523 + "valtio": {
33524 + "version": "1.2.7",
33525 + "resolved": "https://registry.npmjs.org/valtio/-/valtio-1.2.7.tgz",
33526 + "integrity": "sha512-vm9XwsndXVt+XTFwmb2Myo2p319Ssu/SDVRjBhDGQcNi6Epc8g0Pf/QaWAr67A3P1x4b1NApjK7qHjdL1WpuaQ==",
33527 + "requires": {
33528 + "proxy-compare": "2.0.2"
33529 + }
33530 + },
33531 "vary": {
33532 "version": "1.1.2",
33533 "dev": true
@@ -34263,7 +34388,7 @@
34388 },
34389 "yaml": {
34390 "version": "1.10.0",
34266 - "dev": true
34391 + "devOptional": true
34392 },
34393 "yargs": {
34394 "version": "15.4.1",
frontend/package.json
+1
@@ -14,6 +14,7 @@
14 "react": "^17.0.2",
15 "react-dom": "^17.0.2",
16 "react-router-dom": "^6.1.1",
17 + "valtio": "^1.2.7",
18 "web-vitals": "^1.0.1"
19 },
20 "devDependencies": {
frontend/src/BrowseFiles.ts
+6 -2
@@ -4,8 +4,12 @@ import { createElement as h, Fragment } from 'react'
4 import { formatBytes, hError, hIcon, Loading } from './misc'
5 import { Head } from './Head'
6
7 +function usePath() {
8 + return decodeURI(useLocation().pathname)
9 +}
10 +
11 export function BrowseFiles() {
8 - const path = decodeURI(useLocation().pathname)
12 + const path = usePath()
13 let res = useApi('file_list', { path })
14 if (!res)
15 return h(Loading)
@@ -28,7 +32,7 @@ function FilesList({ list }:{ list:DirList }) {
32 }
33
34 function File({ n, m, c, s }: DirEntry) {
31 - const base = useLocation().pathname
35 + const base = usePath()
36 const isDir = n.endsWith('/')
37 const t = m||c ||null
38 return h('li', {},
frontend/src/Head.ts
+30 -1
@@ -1,7 +1,9 @@
1 import { createElement as h, Fragment, useMemo } from 'react'
2 import { Link, useLocation } from 'react-router-dom'
3 import { DirList } from './BrowseFiles'
4 +import { login, logout } from './login'
5 import { formatBytes, hIcon, prefix } from './misc'
6 +import { useSnapState } from './state'
7
8 export function Head({ list }:{ list:DirList }) {
9 return h(Fragment, {},
@@ -12,7 +14,34 @@ export function Head({ list }:{ list:DirList }) {
14 }
15
16 function MenuPanel() {
15 - return null
17 + const snap = useSnapState()
18 + return h('div', { id:'menu-panel' },
19 + h('div', { id:'menu-bar' },
20 + MenuButton(snap.username ? {
21 + icon: 'user',
22 + label: snap.username,
23 + onClick(){
24 + if (window.confirm('Logout?'))
25 + logout()
26 + },
27 + } : {
28 + icon: 'login',
29 + label: 'Login',
30 + async onClick(){
31 + const user = prompt('Username')
32 + if (!user) return
33 + const password = prompt('Password')
34 + if (!password) return
35 + await login(user, password)
36 + }
37 + })
38 + ))
39 +}
40 +
41 +function MenuButton({ icon, label, onClick }:{ icon:string, label:string, onClick?:()=>void }) {
42 + return h('button', { title:label, onClick },
43 + hIcon(icon),
44 + h('label',{}, label))
45 }
46
47 function FolderStats({ list }:{ list:DirList }) {
frontend/src/api.ts
+2 -2
@@ -8,8 +8,8 @@ export function apiCall(cmd: string, params?: object) : Promise<any> {
8 }).then(res => {
9 if (res.ok)
10 return res.json()
11 - const msg = 'Failed API ' + cmd + (params ? ' ' + JSON.stringify(params) : '')
12 - console.warn(msg)
11 + const msg = 'Failed API ' + cmd
12 + console.warn(msg + (params ? ' ' + JSON.stringify(params) : ''))
13 throw Error(msg)
14 })
15 }
frontend/src/index.scss
+5
@@ -88,3 +88,8 @@ ul.dir {
88 }
89 }
90 }
91 +
92 +#menu-panel button label {
93 + cursor: inherit;
94 + margin-left: 0.8em;
95 +}
\ No newline at end of file
frontend/src/login.ts new
+31
@@ -0,0 +1,31 @@
1 +import { apiCall } from './api'
2 +import { state } from './state'
3 +
4 +let refresher: NodeJS.Timeout
5 +
6 +export async function login(user:string, password:string) {
7 + if (refresher)
8 + clearInterval(refresher)
9 + try {
10 + const res = await apiCall('login', { user, password })
11 + sessionRefresher(res)
12 + state.username = user
13 + }
14 + catch(err) {
15 + alert(err)
16 + }
17 +}
18 +apiCall('refresh_session').then(sessionRefresher, ()=>{})
19 +
20 +function sessionRefresher({ exp, user }:{ exp:string, user:string }) {
21 + state.username = user
22 + if (!exp) return
23 + const delta = new Date(exp).getTime() - Date.now()
24 + const every = delta - 30_000
25 + console.debug('session refresh every', Math.round(every/1000))
26 + refresher = setInterval(() => apiCall('refresh_session'), every)
27 +}
28 +
29 +export function logout(){
30 + apiCall('logout').then(()=> state.username = '')
31 +}
frontend/src/misc.ts
+2
@@ -9,6 +9,8 @@ export function hError(err: Error) {
9 }
10
11 const SYS_ICONS: Record<string,string> = {
12 + login: 'person',
13 + user: 'account_circle',
14 file: 'description',
15 }
16 export function Icon({ name }: { name:string }) {
frontend/src/state.ts new
+9
@@ -0,0 +1,9 @@
1 +import { proxy, useSnapshot } from 'valtio'
2 +
3 +export const state = proxy({
4 + username: ''
5 +})
6 +
7 +export function useSnapState() {
8 + return useSnapshot(state)
9 +}
src/apis.ts
+57 -10
@@ -4,9 +4,12 @@ import { globDir } from './misc'
4 import { Stats } from 'fs'
5 import { stat } from 'fs/promises'
6 import _ from 'lodash'
7 -import { getCurrentUser } from './perm'
7 +import { getCurrentUser, verifyLogin } from './perm'
8 +import { sessions } from './sessions'
9
9 -type ApiHandler = (params?:any, ctx?:any) => any
10 +export const SESSION_COOKIE = 'hfs_$id'
11 +
12 +type ApiHandler = (params:any, ctx:Koa.Context) => any
13 type ApiHandlers = Record<string, ApiHandler>
14
15 export function apiMw(apis: ApiHandlers) : Koa.Middleware {
@@ -15,29 +18,73 @@ export function apiMw(apis: ApiHandlers) : Koa.Middleware {
18 console.debug('API', ctx.method, ctx.path, params)
19 if (!(ctx.path in apis))
20 return ctx.throw(404, 'invalid api')
21 + ctx.body = {}
22 const cb = (apis as any)[ctx.path]
19 - const res = await cb(params, ctx)
23 + let res
24 + try {
25 + res = await cb(params||{}, ctx)
26 + }
27 + catch(e) {
28 + ctx.throw(500, String(e))
29 + }
30 if (res)
21 - ctx.body = res
31 + if (res instanceof Error)
32 + ctx.throw(400, res)
33 + else
34 + ctx.body = res
35 await next()
36 }
37 }
38
39 export const frontEndApis: ApiHandlers = {
27 - async file_list(params:any) {
28 - let node = await vfs.urlToNode(params.path || '/')
40 + async file_list({ path }, ctx) {
41 + let node = await vfs.urlToNode(path || '/', ctx)
42 if (!node)
43 return
31 - const who = await getCurrentUser() // cache value
44 + const who = await getCurrentUser(ctx) // cache value
45 const list = await Promise.all((node.children ||[]).map(node =>
46 !node.hidden && directPermOnNode(node,who) && nodeToFile(node) ))
47 _.remove(list, x => !x)
35 - let path = node.source
36 - if (path) {
37 - const res = await globDir(path, [node.hide, node.remove])
48 + const source = node.source
49 + if (source) {
50 + const res = await globDir(source, [node.hide, node.remove])
51 list.push( ...res.map(x => statToFile(node!.rename?.[x.name] || x.name, x.stats!)) )
52 }
53 return { list }
54 + },
55 + async login({ user, password }, ctx) {
56 + if (!user)
57 + return ctx.status = 400
58 + if (!password)
59 + return ctx.status = 400
60 + if (!await verifyLogin(user, password))
61 + return ctx.status = 401
62 + const sess = sessions.create(user)
63 + ctx.cookies.set(SESSION_COOKIE, sess.id)
64 + return sess
65 + },
66 + async logout({}, ctx) {
67 + const sid = ctx.cookies.get(SESSION_COOKIE)
68 + if (!sid)
69 + return ctx.status = 404
70 + if (!sessions.destroy(sid))
71 + return ctx.status = 500
72 + ctx.status = 200
73 + ctx.cookies.set(SESSION_COOKIE, null)
74 + },
75 + async refresh_session({}, ctx) {
76 + const prevId = ctx.cookies.get(SESSION_COOKIE)
77 + if (!prevId) return
78 + const sess = sessions.refresh(prevId)
79 + if (!sess) {
80 + ctx.cookies.set(SESSION_COOKIE)
81 + ctx.status = 400
82 + ctx.message = 'session not found'
83 + return
84 + }
85 + else
86 + ctx.cookies.set(SESSION_COOKIE, sess.id)
87 + return sess
88 }
89 }
90
src/const.ts
+1 -1
@@ -1,6 +1,6 @@
1 import minimist from 'minimist'
2
3 -export const DEV = process.env.NODE_ENV === 'development' ? 'DEV' : ''
3 +export const DEV = __dirname.endsWith('src') ? 'DEV' : ''
4
5 if (DEV)
6 console.clear()
src/frontend.ts
-1
@@ -18,7 +18,6 @@ function serveProxyFrontend() {
18 }
19
20 function serveStaticFrontend() : Koa.Middleware {
21 - console.debug('fronted: static')
21 const cache = new MemoMap()
22 return async (ctx, next) => {
23 let file = ctx.path
src/index.ts
+1 -1
@@ -29,7 +29,7 @@ srv.use(async (ctx, next) => {
29 return await next()
30 if (path.startsWith(FRONTEND_URI))
31 return await serveFrontendPrefixed(ctx,next)
32 - const node = await vfs.urlToNode(decodeURI(path))
32 + const node = await vfs.urlToNode(decodeURI(path), ctx)
33 if (!node)
34 return await next()
35 const { source } = node
src/perm.ts
+12 -3
@@ -2,9 +2,12 @@ import { watch } from 'fs'
2 import fs from 'fs/promises'
3 import _ from 'lodash'
4 import yaml from 'yaml'
5 -import { hashPassword } from './crypt'
5 +import { hashPassword, verifyPassword } from './crypt'
6 import { argv } from './const'
7 import { setHidden } from './misc'
8 +import { SESSION_COOKIE } from './apis'
9 +import { sessions } from './sessions'
10 +import Koa from 'koa'
11
12 const PATH = argv.accounts || 'accounts.yaml'
13
@@ -17,8 +20,14 @@ interface Accounts { [username:string]: UserDetails }
20
21 let accounts: Accounts = {}
22
20 -export async function getCurrentUser() {
21 - return 'max'
23 +export async function getCurrentUser(ctx: Koa.Context) {
24 + const id = ctx.cookies.get(SESSION_COOKIE)
25 + return id && sessions.get(id)?.user || ''
26 +}
27 +
28 +export async function verifyLogin(user:string, password: string) {
29 + const acc = accounts[user]
30 + return acc && verifyPassword(acc.hashedPassword, password)
31 }
32
33 let doing = false
src/sessions.ts new
+42
@@ -0,0 +1,42 @@
1 +import { randomUUID } from 'crypto'
2 +
3 +const EXP_TIME = 5*60_000
4 +
5 +type SessionId = string
6 +class Session {
7 + id: string
8 + user?: string
9 + exp: Date
10 + constructor(init:Session | string){
11 + if (typeof init === 'string')
12 + this.user = init
13 + else
14 + Object.assign(this, init)
15 + this.id = randomUUID()
16 + this.exp = new Date(Date.now() + EXP_TIME)
17 + }
18 +}
19 +
20 +export const sessions = {
21 + all: new Map(),
22 + create(user:string) : Session {
23 + const sess = new Session(user)
24 + this.all.set(sess.id, sess)
25 + setTimeout(()=> this.all.delete(sess.id), EXP_TIME)
26 + return sess
27 + },
28 + get(id:SessionId) : Session | undefined {
29 + return this.all.get(id)
30 + },
31 + refresh(id:SessionId) : Session | undefined {
32 + let sess = this.get(id)
33 + if (!sess) return
34 + this.all.delete(sess.id)
35 + sess = new Session(sess)
36 + this.all.set(sess.id, sess)
37 + return sess
38 + },
39 + destroy(id:SessionId) {
40 + return this.all.delete(id)
41 + }
42 +}
src/vfs.ts
+7 -7
@@ -6,6 +6,7 @@ import { dirname, basename } from 'path'
6 import { isMatch } from 'micromatch'
7 import { complySlashes, prefix } from './misc'
8 import { getCurrentUser } from './perm'
9 +import Koa from 'koa'
10
11 enum VfsNodeType {
12 root,
@@ -51,7 +52,6 @@ export class Vfs {
52 const data = await fs.readFile(path, 'utf8')
53 this.root = yaml.parse(data)
54 // we should validate content now
54 - console.debug('loaded')
55 }
56 catch(e) {
57 console.error(`Load failed for ${path}`,e)
@@ -75,8 +75,8 @@ export class Vfs {
75 }
76 }
77
78 - async urlToNode(url: string) {
79 - const who = await getCurrentUser()
78 + async urlToNode(url: string, ctx: Koa.Context) {
79 + const who = await getCurrentUser(ctx)
80 let run = this.root
81 const rest = url.split('/').filter(Boolean)
82 if (forbidden()) return
@@ -91,7 +91,7 @@ export class Vfs {
91 if (!run.source)
92 return null
93 const relativeSource = piece + prefix('/', rest.join('/'))
94 - const baseSource = complySlashes(run.source+ '/') //** serve comply qui?
94 + const baseSource = complySlashes(run.source+ '/') //TODO do we really need complySlashes here?
95 const source = baseSource + relativeSource
96 const removed = isMatch(source, [run.remove].flat().map(x => baseSource + x))
97 return removed || !await fs.stat(source) ? null : { source }
@@ -107,7 +107,7 @@ export class Vfs {
107
108 }
109
110 -export const vfs = new Vfs(argv._[0])
110 +export const vfs = new Vfs(argv._[0] || 'vfs.yaml')
111
112 function findChildByName(name:string, node:VfsNode) {
113 const { rename } = node
@@ -118,5 +118,5 @@ function findChildByName(name:string, node:VfsNode) {
118
119 export function directPermOnNode(node:VfsNode, username:string) {
120 const { perm } = node
121 - return !perm ? 'r' : (perm[username] || perm['*'])
122 -}
\ No newline at end of file
121 + return !perm ? 'r' : (username && perm[username] || perm['*'])
122 +}
tests/test.ts
+2 -3
@@ -15,8 +15,7 @@ describe('basics', () => {
15 it('partial download', req('/f1/f2/alfa.txt', s => s.includes('a') && !s.includes('d'), {
16 headers: { Range: 'bytes=0-2' }
17 }))
18 - it('perm1', req('/for-fred', 404))
19 - it('perm2', req('/for-max/', 200))
18 + it('missing perm', req('/for-rejetto/', 404))
19 it('proxy', req('/proxy', s => s.includes('github')))
20 })
21
@@ -36,4 +35,4 @@ function req(methodUrl: string, test:Tester, requestOptions?:any) {
35 done(err)
36 })
37 }
39 -}
\ No newline at end of file
38 +}
vfs.yaml
+2 -5
@@ -6,9 +6,6 @@ children:
6 - source: tests/alfa.txt
7 - name: proxy
8 source: https://raw.githubusercontent.com/nodejs/node/master/README.md
9 - - name: for-max
9 + - name: for-rejetto
10 perm:
11 - max: r
12 - - name: for-fred
13 - perm:
14 - fred: r
11 + rejetto: r