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