Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: rejetto/hfs
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.53.0
Choose a base ref
...
head repository: rejetto/hfs
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref
Loading
Showing with 6,502 additions and 3,718 deletions.
  1. +11 −67 README.md
  2. +3 −1 admin/index.html
  3. +5 −5 admin/package.json
  4. +24 −14 admin/src/AccountForm.ts
  5. +48 −34 admin/src/AccountsPage.ts
  6. +56 −13 admin/src/App.ts
  7. +34 −15 admin/src/ArrayField.ts
  8. +9 −8 admin/src/ConfigFilePage.ts
  9. +2 −2 admin/src/ConfigForm.ts
  10. +8 −7 admin/src/CustomHtmlPage.ts
  11. +58 −31 admin/src/DataTable.ts
  12. +1 −2 admin/src/FileField.ts
  13. +40 −16 admin/src/FileForm.ts
  14. +4 −2 admin/src/FilePicker.ts
  15. +88 −72 admin/src/HomePage.ts
  16. +82 −30 admin/src/InstalledPlugins.ts
  17. +41 −29 admin/src/InternetPage.ts
  18. +10 −8 admin/src/LangPage.ts
  19. +13 −9 admin/src/LoginRequired.ts
  20. +85 −49 admin/src/LogsPage.ts
  21. +4 −3 admin/src/MainMenu.ts
  22. +187 −128 admin/src/MonitorPage.ts
  23. +8 −3 admin/src/OnlinePlugins.ts
  24. +76 −60 admin/src/OptionsPage.ts
  25. +7 −6 admin/src/VfsMenuBar.ts
  26. +41 −36 admin/src/VfsPage.ts
  27. +76 −65 admin/src/VfsTree.ts
  28. +4 −4 admin/src/addFiles.ts
  29. +22 −16 admin/src/api.ts
  30. +2 −4 admin/src/dialog.ts
  31. +1 −1 admin/src/importAccountsCsv.ts
  32. +6 −5 admin/src/index.scss
  33. +4 −0 admin/src/misc.ts
  34. +61 −26 admin/src/mui.ts
  35. +9 −1 admin/src/state.ts
  36. +1 −1 admin/src/theme.ts
  37. +4 −6 admin/src/useBlockIp.ts
  38. +5 −27 central.json
  39. +6 −4 config.md
  40. +233 −116 dev-plugins.md
  41. +6 −0 frontend/fontello-config.json
  42. +13 −0 frontend/index.html
  43. +4 −4 frontend/package.json
  44. +2 −1 frontend/public/fontello.css
  45. BIN frontend/public/fontello.woff2
  46. +19 −2 frontend/src/App.ts
  47. +18 −7 frontend/src/Breadcrumbs.ts
  48. +88 −26 frontend/src/BrowseFiles.ts
  49. +10 −9 frontend/src/FilterBar.ts
  50. +2 −1 frontend/src/Head.ts
  51. +5 −4 frontend/src/UserPanel.ts
  52. +4 −2 frontend/src/clip.ts
  53. +26 −11 frontend/src/components.ts
  54. +23 −2 frontend/src/dialog.ts
  55. +54 −44 frontend/src/fileMenu.ts
  56. +6 −117 frontend/src/i18n.ts
  57. +3 −46 frontend/src/icons.ts
  58. +42 −17 frontend/src/index.scss
  59. +18 −11 frontend/src/login.ts
  60. +39 −10 frontend/src/menu.ts
  61. +46 −21 frontend/src/misc.ts
  62. +15 −14 frontend/src/options.ts
  63. +129 −35 frontend/src/show.ts
  64. +21 −4 frontend/src/state.ts
  65. +48 −0 frontend/src/sysIcons.ts
  66. +15 −5 frontend/src/toasts.ts
  67. +43 −256 frontend/src/upload.ts
  68. +300 −0 frontend/src/uploadQueue.ts
  69. +56 −11 frontend/src/useFetchList.ts
  70. BIN hfs.ico
  71. +23 −16 mui-grid-form/SelectField.ts
  72. +5 −2 mui-grid-form/StringField.ts
  73. +4 −5 mui-grid-form/index.ts
  74. +21 −14 mui-grid-form/misc-fields.ts
  75. +1,272 −1,190 package-lock.json
  76. +21 −18 package.json
  77. +1 −1 plugins/antibrute/plugin.js
  78. +2 −17 plugins/download-counter/plugin.js
  79. +2 −2 plugins/download-counter/public/main.js
  80. +3 −2 plugins/list-uploader/public/main.js
  81. +3 −7 prune_modules.js
  82. +11 −0 shared/_main.scss
  83. +13 −12 shared/api.ts
  84. +35 −30 shared/dialogs.ts
  85. +32 −7 shared/index.ts
  86. +2 −2 shared/md.ts
  87. +56 −4 shared/react.ts
  88. +5 −4 src/QuickZipStream.ts
  89. +5 −3 src/acme.ts
  90. +42 −30 src/adminApis.ts
  91. +16 −5 src/api.accounts.ts
  92. +8 −6 src/api.auth.ts
  93. +40 −0 src/api.cert.ts
  94. +20 −13 src/api.get_file_list.ts
  95. +30 −6 src/api.log.ts
  96. +6 −5 src/api.monitor.ts
  97. +7 −5 src/api.net.ts
  98. +34 −16 src/api.plugins.ts
  99. +13 −7 src/api.vfs.ts
  100. +8 −4 src/apiMiddleware.ts
  101. +3 −0 src/argv.ts
  102. +11 −9 src/auth.ts
  103. +3 −1 src/basicWeb.ts
  104. +9 −0 src/block.ts
  105. +12 −4 src/commands.ts
  106. +7 −5 src/config.ts
  107. +4 −1 src/connections.ts
  108. +8 −2 src/consoleLog.ts
  109. +43 −15 src/const.ts
  110. +4 −0 src/cross-const.ts
  111. +61 −19 src/cross.ts
  112. +10 −12 src/customHtml.ts
  113. +5 −5 src/debounceAsync.ts
  114. +139 −0 src/dirStream.ts
  115. +1 −1 src/errorPages.ts
  116. +22 −11 src/events.ts
  117. +16 −0 src/expiringCache.ts
  118. +8 −6 src/fileAttr.ts
  119. +34 −15 src/frontEndApis.ts
  120. +72 −26 src/github.ts
  121. +126 −0 src/i18n.ts
  122. +1 −1 src/index.ts
  123. +41 −33 src/lang.ts
  124. +6 −1 src/langs/embedded.ts
  125. +179 −0 src/langs/hfs-lang-ar.json
  126. +112 −85 src/langs/hfs-lang-de.json
  127. +8 −2 src/langs/hfs-lang-en.json
  128. +12 −4 src/langs/hfs-lang-fi.json
  129. +28 −19 src/langs/hfs-lang-hu.json
  130. +7 −1 src/langs/hfs-lang-it.json
  131. +51 −17 src/langs/hfs-lang-ja.json
  132. +8 −2 src/langs/hfs-lang-nl.json
  133. +179 −0 src/langs/hfs-lang-ro.json
  134. +10 −3 src/langs/hfs-lang-ru.json
  135. +176 −0 src/langs/hfs-lang-th.json
  136. +176 −0 src/langs/hfs-lang-tr.json
  137. +170 −0 src/langs/hfs-lang-uk.json
  138. +16 −10 src/langs/hfs-lang-vi.json
  139. +40 −21 src/listen.ts
  140. +6 −5 src/log.ts
  141. +25 −0 src/makeQ.ts
  142. +13 −8 src/middlewares.ts
  143. +3 −1 src/misc.ts
  144. +12 −10 src/nat.ts
  145. +14 −4 src/perm.ts
  146. +2 −1 src/persistence.ts
  147. +94 −41 src/plugins.ts
  148. +2 −2 src/selfCheck.ts
  149. +34 −23 src/serveFile.ts
  150. +28 −15 src/serveGuiAndSharedFiles.ts
  151. +15 −12 src/serveGuiFiles.ts
  152. +4 −4 src/srp.ts
  153. +66 −23 src/update.ts
  154. +79 −55 src/upload.ts
  155. +7 −30 src/util-files.ts
  156. +10 −2 src/util-http.ts
  157. +26 −46 src/util-os.ts
  158. +54 −45 src/vfs.ts
  159. +7 −3 src/watchLoad.ts
  160. +6 −6 src/zip.ts
  161. +2 −1 tests/config.yaml
  162. +0 −30 todo.md
  163. +1 −1 tsconfig.json
78 changes: 11 additions & 67 deletions README.md
Original file line number Diff line number Diff line change
@@ -53,6 +53,10 @@ This is a full rewrite of [the Delphi version](https://github.com/rejetto/hfs2).

## Installation

For service installation instructions, [see our wiki](https://github.com/rejetto/hfs/wiki/Service-installation).

For Docker installation, [see dedicated repo](https://github.com/damienzonly/hfs-docker).

NB: minimum Windows version required is 8.1 , Windows Server 2012 R2 (because of Node.js 18)

1. go to https://github.com/rejetto/hfs/releases
@@ -86,41 +90,6 @@ If this procedure fails, it may be that you are missing one of [these requiremen

Configuration and other files will be stored in `%HOME%/.vfs`

### Service

If you want to run HFS at boot (as a service), we suggest the following methods

#### On Linux
1. [install node.js](https://nodejs.org)
2. create a file `/etc/systemd/system/hfs.service` with this content
```
[Unit]
Description=HFS
After=network.target
[Service]
Type=simple
Restart=always
ExecStart=/usr/bin/npx -y hfs@latest
[Install]
WantedBy=multi-user.target
```
3. run `sudo systemctl daemon-reload && sudo systemctl enable hfs && sudo systemctl start hfs && sudo systemctl status hfs`

NB: update will be attempted at each restart

#### On Windows

1. [install node.js](https://nodejs.org)
2. run `npm -g i hfs`
3. run `npx qckwinsvc2 install name="HFS" description="HFS" path="%APPDATA%\npm\node_modules\hfs\src\index.js" args="--cwd %HOMEPATH%\.hfs" now`

To update
- run `npx qckwinsvc2 uninstall name="HFS"`
- run `npm -g update hfs`
- run `npx qckwinsvc2 install name="HFS" description="HFS" path="%APPDATA%\npm\node_modules\hfs\src\index.js" args="--cwd %HOMEPATH%\.hfs" now`

## Console commands

If you have full access to HFS' console, you can enter commands. Start with `help` to have a full list.
@@ -150,43 +119,20 @@ In the Languages section of the Admin-panel you can install additional language

If your language is missing, please consider [translating yourself](https://github.com/rejetto/hfs/wiki/Translation).

## Why you should upgrade from HFS 2.x

HFS 2.x is vulnerable to important attacks, and there is no known solution at the moment.

As you can see from the list of features, we already have some goods that you cannot find in HFS 2.
Other than that, you can also consider:

- it's more robust: it was designed to be an always-running server, while HFS 1-2 was designed for occasional usage (transfer and quit)
- passwords are never really stored, just a non-reversible hash is
- faster search (up to 12x)
- more flexible permissions

## Security

While this project focuses on ease of use, we care about security.
- HTTPS support
- Passwords are not saved, and not disclosed even without https thanks to [SRP](https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol)
- Automated tests ran on every release, including libraries audit
- No default admin password

Some actions you can take for improved security:
- use https, better if using a proper certificate, even free with [Letsencrypt](https://letsencrypt.org/).
- have a domain (ddns is ok too), configure it in "Internet" page, and enable "Accept requests only using domain"
- install "antidos" plugin
- ensure "antibrute" plugin is running
- disable "unprotected admin on localhost"

## Hidden features

- Appending `#LOGIN` to address will bring up the login dialog
- Appending ?lang=CODE to address will force a specific language
- right/ctrl/command click on toggle-all checkbox will invert each checkbox state
- Right-click on toggle-all checkbox will invert each checkbox state
- Appending `?login=USER:PASSWORD` will automatically log in the browser
- Appending `?overwrite` on uploads, will override the dont_overwrite_uploading configuration, provided you also have delete permission
- Appending `?search=PATTERN` will trigger search at start
- Appending `?search=PATTERN` will trigger search at start
- Appending `?onlyFiles` or `?onlyFolders` will limit type of results
- Appending `?autoplay=shuffle` will trigger show & play; `?autoplay` will not shuffle, but also will not start until the list is complete
- Right-click on "check for updates" will let you input a URL of a version to install
- shift+click on a file will show & play
- Shift+click on a file will show & play
- Type the name of a file/folder to focus it, and ctrl+backspace to go to parent folder
- `--consoleFile PATH` will output all stdout and stderr also to a file

## Contribute

@@ -227,6 +173,4 @@ There are several ways to contribute

- [License](https://github.com/rejetto/hfs/blob/master/LICENSE.txt)

- [To-do list](todo.md)

- Flag images are public-domain, downloaded from https://flagpedia.net
4 changes: 3 additions & 1 deletion admin/index.html
Original file line number Diff line number Diff line change
@@ -3,12 +3,14 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script async src='https://unpkg.com/node-forge@1.3.1/dist/forge.min.js'></script>
<title>HFS Admin-panel</title>
<script type="module" src="/src/index.ts"></script>
<link rel="icon" type="image/svg+xml" href="/hfs-logo-icon.svg" />
<link href="../frontend/fontello.css" rel="stylesheet" />
<link href="../frontend/fontello.woff2" rel="prefetch" />
</head>
<body>
<style class="removeAtBoot">@media (prefers-color-scheme: dark) { html { background-color: #000; color: #888; } }</style>
<div id="root"></div>
<script nomodule>document.getElementById('root').innerText = "Please use a newer browser"</script>
</body>
10 changes: 5 additions & 5 deletions admin/package.json
Original file line number Diff line number Diff line change
@@ -20,24 +20,24 @@
"@mui/x-tree-view": "^6.17.0",
"@gregoranders/csv": "^0.0.13",
"dayjs": "^1.11.10",
"react": "^18.2.0",
"react": "^18.3.1",
"react-dom": "^18.2.0",
"react-router-dom": "^6.23.1",
"react-simple-code-editor": "^0.13.1",
"react-window": "^1.8.10",
"tssrp6a": "^3.0.0",
"valtio": "^1.13.0",
"immer": "^9.0.21",
"immer": "*",
"usehooks-ts": "^2.9.5",
"watch-size": "^2.0.0",
"web-vitals": "^2.1.4"
},
"devDependencies": {
"@types/react": "^18.2.48",
"@types/react-dom": "^18.2.18",
"@types/react": "^18.3.14",
"@types/react-dom": "^18.3.2",
"@types/react-window": "^1.8.8",
"@types/react-virtualized-auto-sizer": "^1.0.4",
"vite": "^5.0.12"
"vite": "^5.4.8"
},
"eslintConfig": {
"extends": [
38 changes: 24 additions & 14 deletions admin/src/AccountForm.ts
Original file line number Diff line number Diff line change
@@ -2,11 +2,11 @@

import { createElement as h, ReactNode, useEffect, useRef, useState } from 'react'
import { BoolField, Form, MultiSelectField, NumberField } from '@hfs/mui-grid-form'
import { Alert } from '@mui/material'
import { Alert, Box } from '@mui/material'
import { apiCall } from './api'
import { alertDialog, useDialogBarColors } from './dialog'
import { formatTimestamp, isEqualLax, prefix, useIsMobile, wantArray } from './misc'
import { IconBtn, modifiedProps } from './mui'
import { formatTimestamp, isEqualLax, prefix, reactJoin, useIsMobile, wantArray } from './misc'
import { IconBtn, propsForModifiedValues } from './mui'
import { Account } from './AccountsPage'
import { createVerifierAndSalt, SRPParameters, SRPRoutines } from 'tssrp6a'
import { AutoDelete, Delete } from '@mui/icons-material'
@@ -30,6 +30,7 @@ export default function AccountForm({ account, done, groups, addToBar, reload }:
const group = !values.hasPassword
const ref = useRef<HTMLFormElement>()
const expired = Boolean(values.expire)
const { members } = account
return h(Form, {
formRef: ref,
values,
@@ -55,24 +56,29 @@ export default function AccountForm({ account, done, groups, addToBar, reload }:
...wantArray(addToBar),
],
fields: [
{ k: 'username', label: group ? 'Group name' : undefined, autoComplete: 'off', required: true, xl: group ? 12 : 4,
{ k: 'username', label: group ? 'Group name' : undefined, autoComplete: 'off', required: true, lg: group ? 12 : 4,
getError: v => v !== account.username && apiCall('get_account', { username: v })
.then(got => got?.username === account.username ? "usernames are case-insensitive" : "already used", () => false),
},
!group && { k: 'password', md: 6, xl: 4, type: 'password', autoComplete: 'new-password', required: add,
!group && { k: 'password', md: 6, lg: 4, type: 'password', autoComplete: 'new-password', required: add,
label: add ? "Password" : "Change password"
},
!group && { k: 'password2', md: 6, xl: 4, type: 'password', autoComplete: 'new-password', label: 'Repeat password',
!group && { k: 'password2', md: 6, lg: 4, type: 'password', autoComplete: 'new-password', label: 'Repeat password',
getError: (x, { values }) => (x||'') !== (values.password||'') && "Enter same password" },
{ k: 'disabled', comp: BoolField, fromField: x=>!x, toField: x=>!x, label: "Enabled", xs: 12, sm: 6, xl: 8,
helperText: "Login is prevented if account is disabled, or if all its groups are disabled"},
{ k: 'ignore_limits', comp: BoolField, xs: 'auto',
{ k: 'disabled', comp: BoolField, fromField: x=>!x, toField: x=>!x, label: "Enabled", xs: 12, sm: 6, lg: 8,
helperText: !values.disabled && values.canLogin === false ? h(Box, { color: 'warning.main', component: 'span' }, "Login is prevented because all of its groups are disabled")
: "Login is prevented if account is disabled, or all its groups are disabled" },
{ k: 'ignore_limits', comp: BoolField, xs: true,
helperText: values.ignore_limits ? "Speed limits don't apply to this account" : "Speed limits apply to this account" },
{ k: 'admin', comp: BoolField, fromField: (v:boolean) => v||null, label: "Admin-panel access", xs: 12, sm: 6, xl: 8,
{ k: 'admin', comp: BoolField, fromField: (v:boolean) => v||null, label: "Admin-panel access", xs: 12, sm: 6, lg: 8,
helperText: "To access THIS interface you are using right now",
...!account.admin && account.adminActualAccess && { value: true, helperText: "This permission is inherited" },
...!account.admin && account.adminActualAccess && { value: true, disabled: true, helperText: "This permission is inherited. To disable it, act on the groups." },
},
{ k: 'disable_password_change', comp: BoolField, fromField: x=>!x, toField: x=>!x, label: "Allow password change", xs: 'auto' },
{ k: 'disable_password_change', comp: BoolField, fromField: x=>!x, toField: x=>!x, label: "Allow password change", xs: true },
!members ? null
: group && !members.length ? h(Box, {}, "No members")
: members.length > 0 && h(Box, {}, `${members.length} members: `,
reactJoin(', ', account.members?.map((u: string) => h(groups.includes(u) ? 'i' : 'span', {}, u))) ),
group && h(Alert, { severity: 'info' }, `To add users to this group, select the user and then click "Inherit"`),
{ k: 'belongs', comp: MultiSelectField, label: "Inherit from groups", options: belongsOptions,
helperText: "Specify groups to inherit permissions from"
@@ -88,9 +94,9 @@ export default function AccountForm({ account, done, groups, addToBar, reload }:
],
onError: alertDialog,
save: {
...modifiedProps( !isEqualLax(values, account)),
...propsForModifiedValues(isModifiedConfig(values, account)),
async onClick() {
const { password='', password2, adminActualAccess, hasPassword, invalidated, ...withoutPassword } = values
const { password='', password2, adminActualAccess, hasPassword, invalidated, canLogin, members, ...withoutPassword } = values
if (add) {
const got = await apiCall('add_account', withoutPassword)
if (password)
@@ -116,6 +122,10 @@ export default function AccountForm({ account, done, groups, addToBar, reload }:
})
}

export function isModifiedConfig(a: any, b: any) {
return !isEqualLax(a, b, (a,b) => !a && !b || undefined)
}

// you can set password directly in add/set_account, but using this api instead will add extra security because it is not sent as clear-text, so it's especially good if you are not in localhost and not using https
export async function apiNewPassword(username: string, password: string) {
const srp6aNimbusRoutines = new SRPRoutines(new SRPParameters())
Loading