From e70cb9246de66c12000e86f7b777298d3272b9af Mon Sep 17 00:00:00 2001 From: Massimo Melina Date: Thu, 23 Jun 2022 00:01:18 +0200 Subject: [PATCH] HFS console commands --- README.md | 15 ++++------- server/src/api.accounts.ts | 8 +++--- server/src/commands.ts | 55 ++++++++++++++++++++++++++++++++++++++ server/src/config.ts | 4 +++ server/src/index.ts | 1 + server/src/listen.ts | 3 +++ server/src/perm.ts | 19 ++++++------- 7 files changed, 80 insertions(+), 25 deletions(-) create mode 100644 server/src/commands.ts diff --git a/README.md b/README.md index 880965250..e27bdef54 100644 --- a/README.md +++ b/README.md @@ -48,17 +48,12 @@ You won't find all previous features here (yet), but still we got: 2. click on `Assets` 3. **download** the right version for your computer 4. launch `hfs` file -5. the browser should automatically open on `localhost` address, so you can configure the rest +5. the browser should automatically open on `localhost` address, so you can configure the rest in the Admin panel. + - if a browser cannot be opened on the computer where you are installing HFS, + you should enter this command in HFS console: `create-admin ` -If you access HFS via localhost, by default it won't require your to login. - -### Cloud server? - -If you are installing HFS on "another" machine, then step 5 may not be possible. -In this case you should run hfs with `--create-admin `. -This will both -- create an account with username `admin` with the provided password and Admin privilege (granting access to Admin panel). -- disable the unprotected access (no login) to Admin panel +If you access *Admin panel* via localhost, by default HFS **won't** require your to login. +If you don't like this behavior, disable it in the Admin panel or enter this console command `config localhost_admin false`. ### Other systems diff --git a/server/src/api.accounts.ts b/server/src/api.accounts.ts index b7b110490..4eead813f 100644 --- a/server/src/api.accounts.ts +++ b/server/src/api.accounts.ts @@ -4,13 +4,13 @@ import { changePasswordHelper, changeSrpHelper } from './api.helpers' import { ApiError, ApiHandlers } from './apiMiddleware' import { Account, - accountCanLogin, + accountCanLoginAdmin, accountHasPassword, addAccount, delAccount, getAccount, - getAccounts, getCurrentUsername, - getFromAccount, + getAccounts, + getCurrentUsername, setAccount } from './perm' import _ from 'lodash' @@ -21,7 +21,7 @@ function prepareAccount(ac: Account | undefined) { ..._.omit(ac, ['password','hashed_password','srp']), username: ac.username, // omit won't copy it because it's a hidden prop hasPassword: accountHasPassword(ac), - adminActualAccess: accountCanLogin(ac) && getFromAccount(ac, a => a.admin), + adminActualAccess: accountCanLoginAdmin(ac), } } diff --git a/server/src/commands.ts b/server/src/commands.ts new file mode 100644 index 000000000..8e039c9fd --- /dev/null +++ b/server/src/commands.ts @@ -0,0 +1,55 @@ +import { addAccount, getAccount, updateAccount } from './perm' +import { getConfigDefinition, setConfig } from './config' + +console.log(`HINT: type "help" for help`) +require('readline').createInterface({ input: process.stdin }).on('line', (line: string) => { + const [command, ...params] = line.split(/ +/) + const fun = (commands as any)[command] + if (!fun) + return console.error("cannot understand entered command") + if (fun.length > params.length) { + const [args] = /\((.+)\)\s*\{/.exec(fun)! + return console.error("insufficient parameters, expected: " + args) + } + fun(...params).then(() =>console.log("command executed"), + (err: any) => { + if (typeof err === 'string') + console.error("command failed:", err) + else + throw err + }) +}) + +const commands = { + async help() { + console.log("supported commands:", ...Object.keys(commands).map(x => '\n - ' + x)) + }, + async 'create-admin'(password: string, username='admin') { + if (getAccount(username)) + throw `user ${username} already exists` + const acc = addAccount(username, { admin: true }) + await updateAccount(acc!, acc => { + acc.password = password + }) + }, + async 'change-password'(user: string, password: string) { + const acc = getAccount(user) + if (!acc) + throw "user doesn't exist" + await updateAccount(acc!, acc => { + acc.password = password + }) + }, + async config(key: string, value: string) { + const conf = getConfigDefinition(key) + if (!conf) + throw "specified key doesn't exist" + let v: any = value + try { v = JSON.parse(v) } + catch {} + setConfig({ [key]: v }) + }, + async quit() { + process.exit(0) + } +} diff --git a/server/src/config.ts b/server/src/config.ts index f6391249d..ba238ef33 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -60,6 +60,10 @@ export function defineConfig(k: string, defaultValue?: T) { } } +export function getConfigDefinition(k: string) { + return configProps[k] +} + const stack: any[] = [] function subscribeConfig(k:string, cb: (v:T, was?:T)=>void) { if (started) // initial event already passed, we'll make the first call diff --git a/server/src/index.ts b/server/src/index.ts index 3b92637cf..6d4a434e5 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -10,6 +10,7 @@ import { pluginsMiddleware } from './plugins' import { throttler } from './throttler' import { headRequests, gzipper, sessions, serveGuiAndSharedFiles, someSecurity, prepareState } from './middlewares' import './listen' +import './commands' import { adminApis } from './adminApis' import { defineConfig } from './config' import { ok } from 'assert' diff --git a/server/src/listen.ts b/server/src/listen.ts index 287f4f0cd..9e1abe31f 100644 --- a/server/src/listen.ts +++ b/server/src/listen.ts @@ -11,6 +11,7 @@ import open from 'open' import { debounceAsync, onlyTruthy, wait } from './misc' import { ADMIN_URI, DEV } from './const' import findProcess from 'find-process' +import { anyAccountCanLoginAdmin } from './perm' interface ServerExtra { name: string, error?: string, busy?: Promise } let httpSrv: http.Server & ServerExtra @@ -33,6 +34,8 @@ portCfg.sub(async port => { console.debug(String(e)) console.warn("cannot launch browser on this machine >PLEASE< open your browser and reach one of these (you may need a different address)", ...Object.values(getUrls()).flat().map(x => '\n - ' + x + ADMIN_URI)) + if (! anyAccountCanLoginAdmin()) + console.log(`HINT: you can enter command: create-admin YOUR_PASSWORD`) }) }) diff --git a/server/src/perm.ts b/server/src/perm.ts index df7558f53..fb2f585b5 100644 --- a/server/src/perm.ts +++ b/server/src/perm.ts @@ -7,8 +7,6 @@ import Koa from 'koa' import { defineConfig, saveConfigAsap } from './config' import { createVerifierAndSalt, SRPParameters, SRPRoutines } from 'tssrp6a' import events from './events' -import { argv } from './const' -import { localhostAdmin } from './adminApis' export interface Account { 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 => { })) }) -events.once('config ready', async () => { - const pwd = argv['create-admin'] - if (!pwd) return - const acc = getAccount('admin') || addAccount('admin', { admin: true }) - await updateAccount(acc!, acc => acc.password = pwd) - localhostAdmin.set(false) - console.log("account 'admin' created while unprotected admin access on localhost is now disabled") -}) - function normalizeUsername(username: string) { return username.toLocaleLowerCase() } @@ -195,3 +184,11 @@ export function accountHasPassword(account: Account) { export function accountCanLogin(account: Account) { return accountHasPassword(account) } + +export function accountCanLoginAdmin(account: Account) { + return accountCanLogin(account) && getFromAccount(account, a => a.admin) +} + +export function anyAccountCanLoginAdmin() { + return Object.values(accounts).find(accountCanLoginAdmin) +}