hfs 0.26.8 → 0.27.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/README.md +15 -2
  2. package/admin/assets/index-509bb1d6.js +415 -0
  3. package/admin/assets/index-60a380a7.css +1 -0
  4. package/admin/assets/sha512-738f0943.js +8 -0
  5. package/admin/index.html +3 -1
  6. package/admin/{public/logo.svg → logo.svg} +0 -0
  7. package/frontend/assets/index-6e178dfd.css +1 -0
  8. package/frontend/assets/index-aea7654e.js +85 -0
  9. package/frontend/assets/sha512-bf915587.js +8 -0
  10. package/frontend/{public/fontello.css → fontello.css} +0 -0
  11. package/frontend/{public/fontello.woff2 → fontello.woff2} +0 -0
  12. package/frontend/index.html +4 -2
  13. package/package.json +2 -6
  14. package/plugins/vhosting/plugin.js +23 -20
  15. package/src/QuickZipStream.js +285 -0
  16. package/src/ThrottledStream.js +93 -0
  17. package/src/adminApis.js +169 -0
  18. package/src/api.accounts.js +59 -0
  19. package/src/api.auth.js +128 -0
  20. package/src/api.file_list.js +110 -0
  21. package/src/api.helpers.js +32 -0
  22. package/src/api.monitor.js +104 -0
  23. package/src/api.plugins.js +128 -0
  24. package/src/api.vfs.js +167 -0
  25. package/src/apiMiddleware.js +123 -0
  26. package/src/block.js +34 -0
  27. package/src/commands.js +125 -0
  28. package/src/config.js +168 -0
  29. package/src/connections.js +57 -0
  30. package/src/const.js +94 -0
  31. package/src/crypt.js +21 -0
  32. package/src/debounceAsync.js +49 -0
  33. package/src/events.js +9 -0
  34. package/src/frontEndApis.js +38 -0
  35. package/src/github.js +104 -0
  36. package/src/index.js +57 -0
  37. package/src/listen.js +235 -0
  38. package/src/log.js +137 -0
  39. package/src/middlewares.js +195 -0
  40. package/src/misc.js +160 -0
  41. package/src/pbkdf2.js +74 -0
  42. package/src/perm.js +183 -0
  43. package/src/plugins.js +343 -0
  44. package/src/serveFile.js +105 -0
  45. package/src/serveGuiFiles.js +113 -0
  46. package/src/sse.js +30 -0
  47. package/src/throttler.js +91 -0
  48. package/src/update.js +70 -0
  49. package/src/util-files.js +163 -0
  50. package/src/util-generators.js +31 -0
  51. package/src/util-http.js +32 -0
  52. package/src/vfs.js +232 -0
  53. package/src/watchLoad.js +73 -0
  54. package/src/zip.js +73 -0
  55. package/admin/.DS_Store +0 -0
  56. package/admin/.eslintrc +0 -8
  57. package/admin/.gitignore +0 -23
  58. package/admin/package.json +0 -67
  59. package/admin/src/AccountForm.ts +0 -92
  60. package/admin/src/AccountsPage.ts +0 -143
  61. package/admin/src/App.ts +0 -83
  62. package/admin/src/ArrayField.ts +0 -84
  63. package/admin/src/ConfigPage.ts +0 -279
  64. package/admin/src/FileField.ts +0 -52
  65. package/admin/src/FileForm.ts +0 -148
  66. package/admin/src/FilePicker.ts +0 -166
  67. package/admin/src/HomePage.ts +0 -96
  68. package/admin/src/InstalledPlugins.ts +0 -158
  69. package/admin/src/LoginRequired.ts +0 -75
  70. package/admin/src/LogoutPage.ts +0 -27
  71. package/admin/src/LogsPage.ts +0 -75
  72. package/admin/src/MainMenu.ts +0 -74
  73. package/admin/src/MenuButton.ts +0 -38
  74. package/admin/src/MonitorPage.ts +0 -200
  75. package/admin/src/OnlinePlugins.ts +0 -101
  76. package/admin/src/PermField.ts +0 -80
  77. package/admin/src/PluginsPage.ts +0 -27
  78. package/admin/src/VfsMenuBar.ts +0 -58
  79. package/admin/src/VfsPage.ts +0 -124
  80. package/admin/src/VfsTree.ts +0 -95
  81. package/admin/src/addFiles.ts +0 -59
  82. package/admin/src/api.ts +0 -246
  83. package/admin/src/dialog.ts +0 -203
  84. package/admin/src/index.css +0 -21
  85. package/admin/src/index.ts +0 -10
  86. package/admin/src/md.ts +0 -31
  87. package/admin/src/misc.ts +0 -141
  88. package/admin/src/react-app-env.d.ts +0 -1
  89. package/admin/src/reportWebVitals.ts +0 -15
  90. package/admin/src/setupTests.ts +0 -5
  91. package/admin/src/state.ts +0 -40
  92. package/admin/src/theme.ts +0 -37
  93. package/admin/tsconfig.json +0 -26
  94. package/admin/vite.config.ts +0 -32
  95. package/frontend/.DS_Store +0 -0
  96. package/frontend/.eslintrc +0 -8
  97. package/frontend/.gitignore +0 -23
  98. package/frontend/package.json +0 -51
  99. package/frontend/src/App.ts +0 -25
  100. package/frontend/src/Breadcrumbs.ts +0 -43
  101. package/frontend/src/BrowseFiles.ts +0 -141
  102. package/frontend/src/Head.ts +0 -45
  103. package/frontend/src/UserPanel.ts +0 -52
  104. package/frontend/src/api.ts +0 -78
  105. package/frontend/src/components.ts +0 -54
  106. package/frontend/src/dialog.css +0 -76
  107. package/frontend/src/dialog.ts +0 -105
  108. package/frontend/src/icons.ts +0 -46
  109. package/frontend/src/index.scss +0 -307
  110. package/frontend/src/index.ts +0 -10
  111. package/frontend/src/login.ts +0 -50
  112. package/frontend/src/menu.ts +0 -188
  113. package/frontend/src/misc.ts +0 -54
  114. package/frontend/src/options.ts +0 -52
  115. package/frontend/src/react-app-env.d.ts +0 -1
  116. package/frontend/src/reportWebVitals.ts +0 -15
  117. package/frontend/src/setupTests.ts +0 -5
  118. package/frontend/src/state.ts +0 -82
  119. package/frontend/src/useAuthorized.ts +0 -17
  120. package/frontend/src/useFetchList.ts +0 -144
  121. package/frontend/src/useTheme.ts +0 -23
  122. package/frontend/tsconfig.json +0 -26
  123. package/frontend/vite.config.ts +0 -21
  124. package/src/QuickZipStream.ts +0 -279
  125. package/src/ThrottledStream.ts +0 -98
  126. package/src/adminApis.ts +0 -161
  127. package/src/api.accounts.ts +0 -78
  128. package/src/api.auth.ts +0 -131
  129. package/src/api.file_list.ts +0 -102
  130. package/src/api.helpers.ts +0 -30
  131. package/src/api.monitor.ts +0 -106
  132. package/src/api.plugins.ts +0 -139
  133. package/src/api.vfs.ts +0 -182
  134. package/src/apiMiddleware.ts +0 -124
  135. package/src/block.ts +0 -35
  136. package/src/commands.ts +0 -122
  137. package/src/config.ts +0 -166
  138. package/src/connections.ts +0 -60
  139. package/src/const.ts +0 -57
  140. package/src/crypt.ts +0 -16
  141. package/src/debounceAsync.ts +0 -51
  142. package/src/events.ts +0 -6
  143. package/src/frontEndApis.ts +0 -17
  144. package/src/github.ts +0 -102
  145. package/src/index.ts +0 -53
  146. package/src/listen.ts +0 -220
  147. package/src/log.ts +0 -128
  148. package/src/middlewares.ts +0 -176
  149. package/src/misc.ts +0 -149
  150. package/src/pbkdf2.ts +0 -83
  151. package/src/perm.ts +0 -194
  152. package/src/plugins.ts +0 -342
  153. package/src/serveFile.ts +0 -104
  154. package/src/serveGuiFiles.ts +0 -95
  155. package/src/sse.ts +0 -29
  156. package/src/throttler.ts +0 -106
  157. package/src/update.ts +0 -67
  158. package/src/util-files.ts +0 -137
  159. package/src/util-generators.ts +0 -29
  160. package/src/util-http.ts +0 -29
  161. package/src/vfs.ts +0 -258
  162. package/src/watchLoad.ts +0 -75
  163. package/src/zip.ts +0 -69
package/src/adminApis.ts DELETED
@@ -1,161 +0,0 @@
1
- // This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
-
3
- import { ApiError, ApiHandlers, SendListReadable } from './apiMiddleware'
4
- import { defineConfig, getWholeConfig, setConfig } from './config'
5
- import { getStatus, getUrls, httpsPortCfg, portCfg } from './listen'
6
- import {
7
- API_VERSION,
8
- BUILD_TIMESTAMP,
9
- COMPATIBLE_API_VERSION,
10
- FORBIDDEN,
11
- HFS_STARTED,
12
- IS_WINDOWS,
13
- UNAUTHORIZED,
14
- VERSION
15
- } from './const'
16
- import vfsApis from './api.vfs'
17
- import accountsApis from './api.accounts'
18
- import pluginsApis from './api.plugins'
19
- import monitorApis from './api.monitor'
20
- import { getConnections } from './connections'
21
- import { debounceAsync, isLocalHost, onOff, wait } from './misc'
22
- import _ from 'lodash'
23
- import events from './events'
24
- import { getFromAccount } from './perm'
25
- import Koa from 'koa'
26
- import { getProxyDetected } from './middlewares'
27
- import { writeFile } from 'fs/promises'
28
- import { createReadStream } from 'fs'
29
- import * as readline from 'readline'
30
- import { loggers } from './log'
31
- import { execFile } from 'child_process'
32
- import { promisify } from 'util'
33
-
34
- export const adminApis: ApiHandlers = {
35
-
36
- ...vfsApis,
37
- ...accountsApis,
38
- ...pluginsApis,
39
- ...monitorApis,
40
-
41
- async set_config({ values: v }) {
42
- if (v) {
43
- const st = getStatus()
44
- const noHttp = (v.port ?? portCfg.get()) < 0 || !st.httpSrv.listening
45
- const noHttps = (v.https_port ?? httpsPortCfg.get()) < 0 || !st.httpsSrv.listening
46
- if (noHttp && noHttps)
47
- return new ApiError(FORBIDDEN, "You cannot switch off both http and https ports")
48
- await setConfig(v)
49
- }
50
- return {}
51
- },
52
-
53
- get_config: getWholeConfig,
54
-
55
- async get_status() {
56
- const st = getStatus()
57
- return {
58
- started: HFS_STARTED,
59
- build: BUILD_TIMESTAMP,
60
- version: VERSION,
61
- apiVersion: API_VERSION,
62
- compatibleApiVersion: COMPATIBLE_API_VERSION,
63
- http: await serverStatus(st.httpSrv, portCfg.get()),
64
- https: await serverStatus(st.httpsSrv, httpsPortCfg.get()),
65
- urls: getUrls(),
66
- proxyDetected: getProxyDetected(),
67
- frpDetected: localhostAdmin.get() && !getProxyDetected()
68
- && getConnections().every(isLocalHost)
69
- && await frpDebounced(),
70
- }
71
-
72
- async function serverStatus(h: typeof st.httpSrv, configuredPort?: number) {
73
- const busy = await h.busy
74
- await wait(0) // simple trick to wait for also .error to be updated. If this trickery becomes necessary elsewhere, then we should make also error a Promise.
75
- return {
76
- ..._.pick(h, ['listening', 'error']),
77
- busy,
78
- port: (h?.address() as any)?.port || configuredPort,
79
- }
80
- }
81
- },
82
-
83
- async save_pem({ cert, private_key, name='self' }) {
84
- if (!cert || !private_key)
85
- return new ApiError(400)
86
- const files = { cert: name + '.cert', private_key: name + '.key' }
87
- await writeFile(files.private_key, private_key)
88
- await writeFile(files.cert, cert)
89
- return files
90
- },
91
-
92
- async get_log({ file='log' }, ctx) {
93
- return new SendListReadable({
94
- bufferTime: 10,
95
- doAtStart(list) {
96
- const logger = loggers.find(l => l.name === file)
97
- if (!logger)
98
- return list.error(404, true)
99
- const input = createReadStream(logger.path)
100
- input.on('error', async (e: any) => {
101
- if (e.code === 'ENOENT') // ignore ENOENT, consider it an empty log
102
- return list.ready()
103
- list.error(e.code || e.message)
104
- })
105
- input.on('end', () =>
106
- list.ready())
107
- input.on('ready', () => {
108
- readline.createInterface({ input }).on('line', line => {
109
- if (ctx.aborted)
110
- return input.close()
111
- const obj = parse(line)
112
- if (obj)
113
- list.add(obj)
114
- }).on('close', () => { // file is automatically closed, so we continue by events
115
- ctx.res.once('close', onOff(events, { // unsubscribe when connection is interrupted
116
- [logger.name](entry) {
117
- list.add(entry)
118
- }
119
- }))
120
- })
121
- })
122
- }
123
- })
124
-
125
- function parse(line: string) {
126
- const m = /^(.+?) (.+?) (.+?) \[(.{11}):(.{14})] "(\w+) ([^"]+) HTTP\/\d.\d" (\d+) (-|\d+)/.exec(line)
127
- if (!m) return
128
- const [, ip, , user, date, time, method, uri, status, length] = m
129
- return { // keep object format same as events emitted by the log module
130
- ip,
131
- user: user === '-' ? undefined : user,
132
- ts: new Date(date + ' ' + time),
133
- method,
134
- uri,
135
- status: Number(status),
136
- length: length === '-' ? undefined : Number(length),
137
- }
138
- }
139
- },
140
- }
141
-
142
- for (const k in adminApis) {
143
- const was = adminApis[k]
144
- adminApis[k] = (params, ctx) =>
145
- ctxAdminAccess(ctx) ? was(params, ctx)
146
- : new ApiError(UNAUTHORIZED)
147
- }
148
-
149
- export const localhostAdmin = defineConfig('localhost_admin', true)
150
-
151
- export function ctxAdminAccess(ctx: Koa.Context) {
152
- return !ctx.state.proxiedFor // we consider localhost_admin only if no proxy is detected
153
- && localhostAdmin.get() && isLocalHost(ctx)
154
- || getFromAccount(ctx.state.account, a => a.admin)
155
- }
156
-
157
- const frpDebounced = debounceAsync(async () => {
158
- if (!IS_WINDOWS) return false
159
- const { stdout } = await promisify(execFile)('tasklist', ['/fi','imagename eq frpc.exe','/nh'])
160
- return stdout.includes('frpc')
161
- })
@@ -1,78 +0,0 @@
1
- // This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
-
3
- import { changePasswordHelper, changeSrpHelper } from './api.helpers'
4
- import { ApiError, ApiHandlers } from './apiMiddleware'
5
- import {
6
- Account,
7
- accountCanLoginAdmin,
8
- accountHasPassword,
9
- addAccount,
10
- delAccount,
11
- getAccount,
12
- getAccounts,
13
- getCurrentUsername,
14
- setAccount
15
- } from './perm'
16
- import _ from 'lodash'
17
- import { FORBIDDEN } from './const'
18
-
19
- function prepareAccount(ac: Account | undefined) {
20
- return ac && {
21
- ..._.omit(ac, ['password','hashed_password','srp']),
22
- username: ac.username, // omit won't copy it because it's a hidden prop
23
- hasPassword: accountHasPassword(ac),
24
- adminActualAccess: accountCanLoginAdmin(ac),
25
- }
26
- }
27
-
28
- const apis: ApiHandlers = {
29
-
30
- get_usernames() {
31
- return { list: Object.keys(getAccounts()) }
32
- },
33
-
34
- get_account({ username }, ctx) {
35
- return prepareAccount(getAccount(username || getCurrentUsername(ctx)))
36
- || new ApiError(404)
37
- },
38
-
39
- get_accounts() {
40
- return { list: Object.values(getAccounts()).map(prepareAccount) }
41
- },
42
-
43
- get_admins() {
44
- return { list: Object.values(getAccounts()).map(prepareAccount).filter(ac => ac?.adminActualAccess).map(ac => ac!.username) }
45
- },
46
-
47
- set_account({ username, changes }) {
48
- const { admin } = changes
49
- if (admin === null)
50
- changes.admin = undefined
51
- else if (admin !== undefined && typeof admin !== 'boolean')
52
- return new ApiError(400, "invalid admin")
53
- return setAccount(username, changes) ? {} : new ApiError(400)
54
- },
55
-
56
- add_account({ username, ...rest }) {
57
- if (getAccount(username))
58
- return new ApiError(FORBIDDEN)
59
- if (!addAccount(username, rest))
60
- return new ApiError(400)
61
- return {}
62
- },
63
-
64
- del_account({ username }) {
65
- return delAccount(username) ? {} : new ApiError(400)
66
- },
67
-
68
- async change_password_others({ username, newPassword }) {
69
- return changePasswordHelper(getAccount(username), newPassword)
70
- },
71
-
72
- async change_srp_others({ username, salt, verifier }) {
73
- return changeSrpHelper(getAccount(username), salt, verifier)
74
- }
75
-
76
- }
77
-
78
- export default apis
package/src/api.auth.ts DELETED
@@ -1,131 +0,0 @@
1
- // This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
-
3
- import { Account, getAccount, getCurrentUsername } from './perm'
4
- import { verifyPassword } from './crypt'
5
- import { ApiError, ApiHandler } from './apiMiddleware'
6
- import { SRPParameters, SRPRoutines, SRPServerSession, SRPServerSessionStep1 } from 'tssrp6a'
7
- import { ADMIN_URI, SESSION_DURATION, UNAUTHORIZED } from './const'
8
- import { randomId } from './misc'
9
- import Koa from 'koa'
10
- import { changeSrpHelper, changePasswordHelper } from './api.helpers'
11
- import { ctxAdminAccess } from './adminApis'
12
- import { prepareState } from './middlewares'
13
-
14
- const srp6aNimbusRoutines = new SRPRoutines(new SRPParameters())
15
- const ongoingLogins:Record<string,SRPServerSessionStep1> = {} // store data that doesn't fit session object
16
-
17
- // centralized log-in state
18
- async function loggedIn(ctx:Koa.Context, username: string | false) {
19
- const s = ctx.session
20
- if (!s)
21
- return ctx.throw(500,'session')
22
- if (username === false) {
23
- delete s.username
24
- ctx.cookies.set('csrf', '')
25
- return
26
- }
27
- s.username = username
28
- await prepareState(ctx, async ()=>{}) // updating the state is necessary to send complete session data so that frontend shows admin button
29
- delete s.login
30
- ctx.cookies.set('csrf', randomId(), { signed:false, httpOnly: false })
31
- }
32
-
33
- function makeExp() {
34
- return { exp: new Date(Date.now() + SESSION_DURATION) }
35
- }
36
-
37
- export const login: ApiHandler = async ({ username, password }, ctx) => {
38
- if (!username || !password) // some validation
39
- return new ApiError(400)
40
- username = username.toLocaleLowerCase() // normalize username, to be case-insensitive
41
- const acc = getAccount(username)
42
- if (!acc)
43
- return new ApiError(UNAUTHORIZED)
44
- if (!acc.hashed_password)
45
- return new ApiError(406)
46
- if (!await verifyPassword(acc.hashed_password, password))
47
- return new ApiError(UNAUTHORIZED)
48
- if (!ctx.session)
49
- return new ApiError(500)
50
- await loggedIn(ctx, username)
51
- return { ...makeExp(), redirect: acc.redirect }
52
- }
53
-
54
- export const loginSrp1: ApiHandler = async ({ username }, ctx) => {
55
- if (!username)
56
- return new ApiError(400)
57
- username = username.toLocaleLowerCase()
58
- const account = getAccount(username)
59
- if (!ctx.session)
60
- return new ApiError(500)
61
- if (!account) // TODO simulate fake account to prevent knowing valid usernames
62
- return new ApiError(UNAUTHORIZED)
63
- try {
64
- const { step1, ...rest } = await srpStep1(account)
65
- const sid = Math.random()
66
- ongoingLogins[sid] = step1
67
- setTimeout(()=> delete ongoingLogins[sid], 60_000)
68
- ctx.session.login = { username, sid }
69
- return rest
70
- }
71
- catch (code: any) {
72
- return new ApiError(code)
73
- }
74
- }
75
-
76
- export async function srpStep1(account: Account) {
77
- if (!account.srp)
78
- throw 406 // unacceptable
79
- const [salt, verifier] = account.srp.split('|')
80
- const srpSession = new SRPServerSession(srp6aNimbusRoutines)
81
- const step1 = await srpSession.step1(account.username, BigInt(salt), BigInt(verifier))
82
- return { step1, salt, pubKey: String(step1.B) } // cast to string cause bigint can't be jsonized
83
- }
84
-
85
- export const loginSrp2: ApiHandler = async ({ pubKey, proof }, ctx) => {
86
- if (!ctx.session)
87
- return new ApiError(500)
88
- if (!ctx.session.login)
89
- return new ApiError(409)
90
- const { username, sid } = ctx.session.login
91
- const step1 = ongoingLogins[sid]
92
- try {
93
- const M2 = await step1.step2(BigInt(pubKey), BigInt(proof))
94
- await loggedIn(ctx, username)
95
- return {
96
- proof: String(M2),
97
- redirect: ctx.state.account?.redirect,
98
- ...await refresh_session({},ctx)
99
- }
100
- }
101
- catch(e) {
102
- return new ApiError(UNAUTHORIZED, String(e))
103
- }
104
- finally {
105
- delete ongoingLogins[sid]
106
- }
107
- }
108
-
109
- export const logout: ApiHandler = async ({}, ctx) => {
110
- if (!ctx.session)
111
- return new ApiError(500)
112
- loggedIn(ctx, false)
113
- // 401 is a convenient code for OK: the browser clears a possible http authentication (hopefully), and Admin automatically triggers login dialog
114
- return new ApiError(401)
115
- }
116
-
117
- export const refresh_session: ApiHandler = async ({}, ctx) => {
118
- return !ctx.session ? new ApiError(500) : {
119
- username: getCurrentUsername(ctx),
120
- adminUrl: ctxAdminAccess(ctx) ? ADMIN_URI : undefined,
121
- ...makeExp(),
122
- }
123
- }
124
-
125
- export const change_password: ApiHandler = async ({ newPassword }, ctx) => {
126
- return changePasswordHelper(ctx.state.account, newPassword)
127
- }
128
-
129
- export const change_srp: ApiHandler = async ({ salt, verifier }, ctx) => {
130
- return changeSrpHelper(ctx.state.account, salt, verifier)
131
- }
@@ -1,102 +0,0 @@
1
- // This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
-
3
- import { cantReadStatusCode, getNodeName, hasPermission, nodeIsDirectory, urlToNode, VfsNode, walkNode } from './vfs'
4
- import { ApiError, ApiHandler, SendListReadable } from './apiMiddleware'
5
- import { stat } from 'fs/promises'
6
- import { mapPlugins } from './plugins'
7
- import { asyncGeneratorToArray, dirTraversal, pattern2filter } from './misc'
8
- import _ from 'lodash'
9
-
10
- export const file_list: ApiHandler = async ({ path, offset, limit, search, omit, sse }, ctx) => {
11
- let node = await urlToNode(path || '/', ctx)
12
- const list = new SendListReadable()
13
- if (!node)
14
- return fail(404)
15
- if (!hasPermission(node,'can_read',ctx))
16
- return fail(cantReadStatusCode(node))
17
- if (dirTraversal(search))
18
- return fail(418)
19
- if (node.default)
20
- return (sse ? list.custom : _.identity)({ redirect: path }) // sse will wrap the object in a 'custom' message, otherwise we plainly return the object
21
- if (!await nodeIsDirectory(node))
22
- return fail(405) // method not allowed on target
23
- offset = Number(offset)
24
- limit = Number(limit)
25
- const filter = pattern2filter(search)
26
- const walker = walkNode(node, ctx, search ? Infinity : 0)
27
- const onDirEntryHandlers = mapPlugins(plug => plug.onDirEntry)
28
- if (!sse)
29
- return { list: await asyncGeneratorToArray(produceEntries()) }
30
- setTimeout(async () => {
31
- for await (const entry of produceEntries())
32
- list.add(entry)
33
- list.close()
34
- })
35
- return list
36
-
37
- function fail(code: any) {
38
- if (!sse)
39
- return new ApiError(code)
40
- list.error(code)
41
- list.close()
42
- return list
43
- }
44
-
45
- async function* produceEntries() {
46
- for await (const sub of walker) {
47
- if (ctx.aborted) break
48
- if (!filter(getNodeName(sub)))
49
- continue
50
- const entry = await nodeToDirEntry(sub)
51
- if (!entry)
52
- continue
53
- const cbParams = { entry, ctx, listPath:path, node:sub }
54
- try {
55
- if (onDirEntryHandlers.some(cb => cb(cbParams) === false))
56
- continue
57
- }
58
- catch(e) {
59
- console.log("a plugin with onDirEntry is causing problems:", e)
60
- }
61
- if (offset) {
62
- --offset
63
- continue
64
- }
65
- if (omit) {
66
- if (omit !== 'c')
67
- ctx.throw(400, 'omit')
68
- if (!entry.m)
69
- entry.m = entry.c
70
- delete entry.c
71
- }
72
- yield entry
73
- if (limit && !--limit)
74
- break
75
- }
76
- }
77
- }
78
-
79
- export interface DirEntry { n:string, s?:number, m?:Date, c?:Date }
80
-
81
- async function nodeToDirEntry(node: VfsNode): Promise<DirEntry | null> {
82
- let { source, default:def } = node
83
- const name = getNodeName(node)
84
- if (!source)
85
- return name ? { n: name + '/' } : null
86
- if (def)
87
- return { n: name }
88
- try {
89
- const st = await stat(source)
90
- const folder = st.isDirectory()
91
- const { ctime, mtime } = st
92
- return {
93
- n: name + (folder ? '/' : ''),
94
- c: ctime,
95
- m: Math.abs(+mtime-+ctime) < 1000 ? undefined : mtime,
96
- s: folder ? undefined : st.size,
97
- }
98
- }
99
- catch {
100
- return null
101
- }
102
- }
@@ -1,30 +0,0 @@
1
- // This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
-
3
- import { Account, allowClearTextLogin, saveSrpInfo, updateAccount } from './perm'
4
- import { ApiError } from './apiMiddleware'
5
- import { UNAUTHORIZED } from './const'
6
-
7
- export async function changePasswordHelper(account: Account | undefined, newPassword: string) {
8
- if (!newPassword) // clear text version
9
- return Error('missing parameters')
10
- if (!account)
11
- return new ApiError(UNAUTHORIZED)
12
- await updateAccount(account, account => {
13
- account.password = newPassword
14
- })
15
- return {}
16
- }
17
-
18
- export async function changeSrpHelper(account: Account | undefined, salt: string, verifier: string) {
19
- if (allowClearTextLogin.get())
20
- return new ApiError(406)
21
- if (!salt || !verifier)
22
- return Error('missing parameters')
23
- if (!account)
24
- return new ApiError(UNAUTHORIZED)
25
- await updateAccount(account, account => {
26
- saveSrpInfo(account, salt, verifier)
27
- delete account.hashed_password // remove leftovers
28
- })
29
- return {}
30
- }
@@ -1,106 +0,0 @@
1
- import _ from 'lodash'
2
- import { Connection, getConnections } from './connections'
3
- import { pendingPromise, wait } from './misc'
4
- import { ApiHandlers, SendListReadable } from './apiMiddleware'
5
- import Koa from 'koa'
6
- import { totalGot, totalInSpeed, totalOutSpeed, totalSent } from './throttler'
7
- import { getCurrentUsername } from './perm'
8
-
9
- const apis: ApiHandlers = {
10
-
11
- async disconnect({ ip, port, wait }) {
12
- const match = _.matches({ ip, port })
13
- const c = getConnections().find(c => match(getConnAddress(c)))
14
- const waiter = pendingPromise<void>()
15
- c?.socket.end(waiter.resolve)
16
- if (wait)
17
- await waiter
18
- return { result: Boolean(c) }
19
- },
20
-
21
- get_connections({}, ctx) {
22
- const list = new SendListReadable({ addAtStart: getConnections().map(c => serializeConnection(c)) })
23
- type Change = Partial<Omit<Connection,'ip'>>
24
- const throttledUpdate = _.throttle(update, 1000/20) // try to avoid clogging with updates
25
- const state = Symbol('state') // undefined=added, Timeout=add-pending, false=removed
26
- return list.events(ctx, {
27
- connection(conn: Connection) {
28
- conn[state] = setTimeout(() => add(conn), 100)
29
- },
30
- connectionClosed(conn: Connection) {
31
- if (cancel(conn)) return
32
- list.remove(serializeConnection(conn, true))
33
- conn[state] = false
34
- },
35
- connectionUpdated(conn: Connection, change: Change) {
36
- if (!change.ctx)
37
- return throttledUpdate(conn, change)
38
-
39
- Object.assign(change, fromCtx(change.ctx))
40
- change.ctx = undefined
41
- if (!add(conn))
42
- throttledUpdate(conn, change)
43
- },
44
- })
45
-
46
- function add(conn: Connection) {
47
- if (!cancel(conn)) return
48
- list.add(serializeConnection(conn))
49
- return true
50
- }
51
-
52
- function cancel(conn: Connection) {
53
- if (!conn[state]) return
54
- clearTimeout(conn[state])
55
- conn[state] = undefined
56
- return true
57
- }
58
-
59
- function update(conn: Connection, change: Change) {
60
- if (conn[state] === false) return
61
- list.update(serializeConnection(conn, true), change)
62
- }
63
-
64
- function serializeConnection(conn: Connection, minimal?:true) {
65
- const { socket, started, secure } = conn
66
- return Object.assign(getConnAddress(conn), !minimal && {
67
- v: (socket.remoteFamily?.endsWith('6') ? 6 : 4),
68
- got: socket.bytesRead,
69
- sent: socket.bytesWritten,
70
- started,
71
- secure: (secure || undefined) as boolean|undefined, // undefined will save some space once json-ed
72
- ...fromCtx(conn.ctx),
73
- })
74
- }
75
-
76
- function fromCtx(ctx?: Koa.Context) {
77
- return ctx && {
78
- user: getCurrentUsername(ctx),
79
- archive: ctx.state.archive,
80
- path: (ctx.fileSource || ctx.state.archive) && ctx.path // only for downloading files
81
- }
82
- }
83
- },
84
-
85
- async *get_connection_stats() {
86
- while (1) {
87
- yield {
88
- outSpeed: totalOutSpeed,
89
- inSpeed: totalInSpeed,
90
- got: totalGot,
91
- sent: totalSent,
92
- connections: getConnections().length
93
- }
94
- await wait(1000)
95
- }
96
- },
97
- }
98
-
99
- export default apis
100
-
101
- function getConnAddress(conn: Connection) {
102
- return {
103
- ip: conn.ip,
104
- port: conn.socket.remotePort,
105
- }
106
- }