Skip to content

Commit

Permalink
update config to be more safe
Browse files Browse the repository at this point in the history
  • Loading branch information
mixmix committed Sep 23, 2024
1 parent b7e62bb commit 08ecdc7
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 46 deletions.
27 changes: 16 additions & 11 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#! /usr/bin/env node

/* NOTE: calling this file entropy.ts helps commander parse process.argv */
import { Command, Option } from 'commander'
import { Command, /* Option */ } from 'commander'

import { EntropyTuiOptions } from './types'
import { accountOption, endpointOption, loadEntropy } from './common/utils-cli'
import * as config from './config'

import launchTui from './tui'
import { entropyAccountCommand } from './account/command'
Expand All @@ -21,15 +22,15 @@ program
.description('CLI interface for interacting with entropy.xyz. Running without commands starts an interactive ui')
.addOption(accountOption())
.addOption(endpointOption())
// NOTE: I think this is currently unused
.addOption(
new Option(
'-d, --dev',
'Runs entropy in a developer mode uses the dev endpoint as the main endpoint and allows for faucet option to be available in the main menu'
)
.env('DEV_MODE')
.hideHelp()
)
// NOTE: currently unused
// .addOption(
// new Option(
// '-d, --dev',
// 'Runs entropy in a developer mode uses the dev endpoint as the main endpoint and allows for faucet option to be available in the main menu'
// )
// .env('DEV_MODE')
// .hideHelp()
// )
.addCommand(entropyBalanceCommand())
.addCommand(entropyAccountCommand())
.addCommand(entropyTransferCommand())
Expand All @@ -43,5 +44,9 @@ program
// NOTE: on initial startup you have no account
launchTui(entropy, opts)
})
.hook('preAction', async () => {
// set up config file, run migrations
return config.init()
})

program.parseAsync().then(() => {})
program.parseAsync()
36 changes: 23 additions & 13 deletions src/common/utils-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ export function cliWrite (result) {
process.stdout.write(prettyResult)
}

function getConfigOrNull () {
try {
return config.getSync()
} catch (err) {
if (config.isDangerousReadError(err)) throw err
return null
}
}

export function endpointOption () {
return new Option(
'-e, --endpoint <endpoint>',
Expand All @@ -23,8 +32,8 @@ export function endpointOption () {
if (aliasOrEndpoint.match(/^wss?:\/\//)) return aliasOrEndpoint

/* look up endpoint-alias */
const storedConfig = config.getSync()
const endpoint = storedConfig.endpoints[aliasOrEndpoint]
const storedConfig = getConfigOrNull()
const endpoint = storedConfig?.endpoints?.[aliasOrEndpoint]
if (!endpoint) throw Error('unknown endpoint alias: ' + aliasOrEndpoint)

return endpoint
Expand All @@ -41,7 +50,7 @@ export function passwordOption (description?: string) {
}

export function accountOption () {
const storedConfig = config.getSync()
const storedConfig = getConfigOrNull()

return new Option(
'-a, --account <accountAddressOrName>',
Expand All @@ -52,24 +61,25 @@ export function accountOption () {
)
.env('ENTROPY_ACCOUNT')
.argParser(async (account) => {
if (account === storedConfig.selectedAccount) return account
// Updated selected account in config with new address from this option
await config.set({
...storedConfig,
selectedAccount: account
})
if (storedConfig && storedConfig.selectedAccount !== account) {
// Updated selected account in config with new address from this option
await config.set({
...storedConfig,
selectedAccount: account
})
}

return account
})
.default(storedConfig.selectedAccount)
.default(storedConfig?.selectedAccount)
// TODO: display the *name* not address
// TODO: standardise whether selectedAccount is name or address.
}

export async function loadEntropy (addressOrName: string, endpoint: string, password?: string): Promise<Entropy> {
const storedConfig = config.getSync()
const selectedAccount = findAccountByAddressOrName(storedConfig.accounts, addressOrName)
if (!selectedAccount) throw new Error(`AddressError: No account with name or address "${addressOrName}"`)
const accounts = getConfigOrNull()?.accounts || []
const selectedAccount = findAccountByAddressOrName(accounts, addressOrName)
if (!selectedAccount) throw new Error(`No account with name or address: "${addressOrName}"`)

// check if data is encrypted + we have a password
if (typeof selectedAccount.data === 'string' && !password) {
Expand Down
38 changes: 18 additions & 20 deletions src/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { readFile, writeFile, rm } from 'node:fs/promises'
import { readFileSync, writeFileSync } from 'node:fs'
import { readFileSync } from 'node:fs'
import { mkdirp } from 'mkdirp'
import { join, dirname } from 'path'
import envPaths from 'env-paths'
Expand Down Expand Up @@ -35,9 +35,10 @@ function hasRunMigration (config: any, version: number) {

export async function init (configPath = CONFIG_PATH, oldConfigPath = OLD_CONFIG_PATH) {
const currentConfig = await get(configPath)
.catch(async (err) => {
if (err.code !== 'ENOENT') throw err
.catch(async (err ) => {
if (isDangerousReadError(err)) throw err

// If there is no current config, try loading the old one
const oldConfig = await get(oldConfigPath).catch(noop) // drop errors
if (oldConfig) {
// move the config
Expand All @@ -58,33 +59,30 @@ export async function init (configPath = CONFIG_PATH, oldConfigPath = OLD_CONFIG
export async function get (configPath = CONFIG_PATH) {
return readFile(configPath, 'utf-8')
.then(deserialize)
.catch(makeGetErrorHandler(configPath))
}

export function getSync (configPath = CONFIG_PATH): EntropyConfig {
try {
const configBuffer = readFileSync(configPath, 'utf8')
return deserialize(configBuffer)
} catch (err) {
return makeGetErrorHandler(configPath)(err)
}
export function getSync (configPath = CONFIG_PATH) {
const configStr = readFileSync(configPath, 'utf8')
return deserialize(configStr)
}

export async function set (config: EntropyConfig, configPath = CONFIG_PATH) {
assertConfigPath(configPath)

await mkdirp(dirname(configPath))
await writeFile(configPath, serialize(config))
}

/* util */
function noop () {}

function makeGetErrorHandler (configPath) {
return function getErrorHandler (err) {
if (err.code !== 'ENOENT') throw err

const newConfig = migrateData(allMigrations, {})
mkdirp.sync(dirname(configPath))
writeFileSync(configPath, serialize(newConfig))
return newConfig
function assertConfigPath (configPath) {
if (!configPath.endsWith('.json')) {
throw Error(`configPath must be of form *.json, got ${configPath}`)
}
}
export function isDangerousReadError (err) {
// file not found:
if (err.code === 'ENOENT') return false

return true
}
6 changes: 4 additions & 2 deletions tests/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,11 @@ test('config - get', async t => {
const result = await get(configPath)
t.deepEqual(result, config, 'get works')

const MSG = 'path that does not exist fails'
await get('/tmp/junk')
.then(() => t.fail('bad path should fail'))
.then(() => t.fail(MSG))
.catch(err => {
t.match(err.message, /no such file/, 'bad path should fail')
t.match(err.message, /ENOENT/, MSG)
})
})

Expand All @@ -95,6 +96,7 @@ test('config - set', async t => {
dog: true,
secretKey: makeKey()
}
// @ts-expect-error : this is a breaking test
await set(config, configPath)
const actual = await get(configPath)

Expand Down

0 comments on commit 08ecdc7

Please sign in to comment.