HFS console commands

Massimo Melina committed Jun 23, 2022 at 00:01 UTC e70cb9246de66c12000e86f7b777298d3272b9af
7 files changed +80 -25
README.md
+5 -10
@@ -48,17 +48,12 @@ You won't find all previous features here (yet), but still we got:
48 2. click on `Assets`
49 3. **download** the right version for your computer
50 4. launch `hfs` file
51 -5. the browser should automatically open on `localhost` address, so you can configure the rest
51 +5. the browser should automatically open on `localhost` address, so you can configure the rest in the Admin panel.
52 + - if a browser cannot be opened on the computer where you are installing HFS,
53 + you should enter this command in HFS console: `create-admin <PASSWORD>`
54
53 -If you access HFS via localhost, by default it won't require your to login.
54 -
55 -### Cloud server?
56 -
57 -If you are installing HFS on "another" machine, then step 5 may not be possible.
58 -In this case you should run hfs with `--create-admin <PASSWORD>`.
59 -This will both
60 -- create an account with username `admin` with the provided password and Admin privilege (granting access to Admin panel).
61 -- disable the unprotected access (no login) to Admin panel
55 +If you access *Admin panel* via localhost, by default HFS **won't** require your to login.
56 +If you don't like this behavior, disable it in the Admin panel or enter this console command `config localhost_admin false`.
57
58 ### Other systems
59
server/src/api.accounts.ts
+4 -4
@@ -4,13 +4,13 @@ import { changePasswordHelper, changeSrpHelper } from './api.helpers'
4 import { ApiError, ApiHandlers } from './apiMiddleware'
5 import {
6 Account,
7 - accountCanLogin,
7 + accountCanLoginAdmin,
8 accountHasPassword,
9 addAccount,
10 delAccount,
11 getAccount,
12 - getAccounts, getCurrentUsername,
13 - getFromAccount,
12 + getAccounts,
13 + getCurrentUsername,
14 setAccount
15 } from './perm'
16 import _ from 'lodash'
@@ -21,7 +21,7 @@ function prepareAccount(ac: Account | undefined) {
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: accountCanLogin(ac) && getFromAccount(ac, a => a.admin),
24 + adminActualAccess: accountCanLoginAdmin(ac),
25 }
26 }
27
server/src/commands.ts new
+55
@@ -0,0 +1,55 @@
1 +import { addAccount, getAccount, updateAccount } from './perm'
2 +import { getConfigDefinition, setConfig } from './config'
3 +
4 +console.log(`HINT: type "help" for help`)
5 +require('readline').createInterface({ input: process.stdin }).on('line', (line: string) => {
6 + const [command, ...params] = line.split(/ +/)
7 + const fun = (commands as any)[command]
8 + if (!fun)
9 + return console.error("cannot understand entered command")
10 + if (fun.length > params.length) {
11 + const [args] = /\((.+)\)\s*\{/.exec(fun)!
12 + return console.error("insufficient parameters, expected: " + args)
13 + }
14 + fun(...params).then(() =>console.log("command executed"),
15 + (err: any) => {
16 + if (typeof err === 'string')
17 + console.error("command failed:", err)
18 + else
19 + throw err
20 + })
21 +})
22 +
23 +const commands = {
24 + async help() {
25 + console.log("supported commands:", ...Object.keys(commands).map(x => '\n - ' + x))
26 + },
27 + async 'create-admin'(password: string, username='admin') {
28 + if (getAccount(username))
29 + throw `user ${username} already exists`
30 + const acc = addAccount(username, { admin: true })
31 + await updateAccount(acc!, acc => {
32 + acc.password = password
33 + })
34 + },
35 + async 'change-password'(user: string, password: string) {
36 + const acc = getAccount(user)
37 + if (!acc)
38 + throw "user doesn't exist"
39 + await updateAccount(acc!, acc => {
40 + acc.password = password
41 + })
42 + },
43 + async config(key: string, value: string) {
44 + const conf = getConfigDefinition(key)
45 + if (!conf)
46 + throw "specified key doesn't exist"
47 + let v: any = value
48 + try { v = JSON.parse(v) }
49 + catch {}
50 + setConfig({ [key]: v })
51 + },
52 + async quit() {
53 + process.exit(0)
54 + }
55 +}
server/src/config.ts
+4
@@ -60,6 +60,10 @@ export function defineConfig<T>(k: string, defaultValue?: T) {
60 }
61 }
62
63 +export function getConfigDefinition(k: string) {
64 + return configProps[k]
65 +}
66 +
67 const stack: any[] = []
68 function subscribeConfig<T>(k:string, cb: (v:T, was?:T)=>void) {
69 if (started) // initial event already passed, we'll make the first call
server/src/index.ts
+1
@@ -10,6 +10,7 @@ import { pluginsMiddleware } from './plugins'
10 import { throttler } from './throttler'
11 import { headRequests, gzipper, sessions, serveGuiAndSharedFiles, someSecurity, prepareState } from './middlewares'
12 import './listen'
13 +import './commands'
14 import { adminApis } from './adminApis'
15 import { defineConfig } from './config'
16 import { ok } from 'assert'
server/src/listen.ts
+3
@@ -11,6 +11,7 @@ import open from 'open'
11 import { debounceAsync, onlyTruthy, wait } from './misc'
12 import { ADMIN_URI, DEV } from './const'
13 import findProcess from 'find-process'
14 +import { anyAccountCanLoginAdmin } from './perm'
15
16 interface ServerExtra { name: string, error?: string, busy?: Promise<string> }
17 let httpSrv: http.Server & ServerExtra
@@ -33,6 +34,8 @@ portCfg.sub(async port => {
34 console.debug(String(e))
35 console.warn("cannot launch browser on this machine >PLEASE< open your browser and reach one of these (you may need a different address)",
36 ...Object.values(getUrls()).flat().map(x => '\n - ' + x + ADMIN_URI))
37 + if (! anyAccountCanLoginAdmin())
38 + console.log(`HINT: you can enter command: create-admin YOUR_PASSWORD`)
39 })
40 })
41
server/src/perm.ts
+8 -11
@@ -7,8 +7,6 @@ import Koa from 'koa'
7 import { defineConfig, saveConfigAsap } from './config'
8 import { createVerifierAndSalt, SRPParameters, SRPRoutines } from 'tssrp6a'
9 import events from './events'
10 -import { argv } from './const'
11 -import { localhostAdmin } from './adminApis'
10
11 export interface Account {
12 username: string, // we'll have username in it, so we don't need to pass it separately
@@ -100,15 +98,6 @@ accountsConfig.sub(async v => {
98 }))
99 })
100
103 -events.once('config ready', async () => {
104 - const pwd = argv['create-admin']
105 - if (!pwd) return
106 - const acc = getAccount('admin') || addAccount('admin', { admin: true })
107 - await updateAccount(acc!, acc => acc.password = pwd)
108 - localhostAdmin.set(false)
109 - console.log("account 'admin' created while unprotected admin access on localhost is now disabled")
110 -})
111 -
101 function normalizeUsername(username: string) {
102 return username.toLocaleLowerCase()
103 }
@@ -195,3 +184,11 @@ export function accountHasPassword(account: Account) {
184 export function accountCanLogin(account: Account) {
185 return accountHasPassword(account)
186 }
187 +
188 +export function accountCanLoginAdmin(account: Account) {
189 + return accountCanLogin(account) && getFromAccount(account, a => a.admin)
190 +}
191 +
192 +export function anyAccountCanLoginAdmin() {
193 + return Object.values(accounts).find(accountCanLoginAdmin)
194 +}