cobox-config
Advanced tools
Comparing version 0.0.1 to 1.0.0
104
index.js
@@ -6,7 +6,4 @@ const yaml = require('js-yaml') | ||
const mkdirp = require('mkdirp') | ||
const { EventEmitter } = require('events') | ||
const debug = require('debug')('cobox-config') | ||
const logger = require('./logger') | ||
const { isNamedKeypair } = require('./util') | ||
const CONFIG_FILE = 'config.yml' | ||
@@ -16,59 +13,47 @@ | ||
class CoBoxConfig extends EventEmitter { | ||
constructor (storage) { | ||
super() | ||
const KeyHandler = require('./lib/key-handler') | ||
this.storage = storage || path.join(os.homedir(), '.cobox') | ||
const defaultConfig = () => ({ | ||
groups: { byKey: {}, byName: {} }, | ||
identities: { byKey: {}, byName: {} } | ||
}) | ||
mkdirp.sync(path.join(this.storage)) | ||
// filename-safe date (probably there is a better way to do this) | ||
const logfile = [new Date().toISOString().replace(/:/g, '-').replace(/\./, '-'), '.json'].join('') | ||
try { | ||
fs.accessSync(path.join(this.storage, CONFIG_FILE)) | ||
} | ||
catch (e) { | ||
fs.writeFileSync(path.join(this.storage, CONFIG_FILE), yaml.safeDump({ | ||
groups: [], | ||
identities: [] | ||
}, { sortKeys: true })) | ||
} | ||
class CoBoxConfig { | ||
constructor (storage) { | ||
this.root = storage || path.join(os.homedir(), '.cobox') | ||
this.storage = path.join(this.root, CONFIG_FILE) | ||
this.secrets = path.join(this.root, 'secret_keys') | ||
this.on('save', this.save) | ||
this.on('load', this.load) | ||
mkdirp.sync(path.join(this.root, 'logs')) | ||
mkdirp.sync(this.secrets) | ||
this.emit('load') | ||
} | ||
save () { | ||
const config = { | ||
groups: this._groups, | ||
identities: this._identities | ||
var config = defaultConfig() | ||
if (!fs.existsSync(this.storage)) { | ||
fs.writeFileSync(this.storage, yaml.safeDump(defaultConfig(), { sortKeys: true })) | ||
this._groups = config.groups | ||
this._identities = config.identities | ||
} else { | ||
this.load() | ||
} | ||
fs.writeFileSync( | ||
path.join(this.storage, CONFIG_FILE), | ||
yaml.safeDump(config, { sortKeys: true }) | ||
) | ||
debug(`[SAVED] config:\n\n${JSON.stringify(config)}`) | ||
} | ||
this.logger = logger(path.join(this.root, 'logs', logfile)) | ||
this.log = this.logger('cobox-config') | ||
load () { | ||
const config = yaml.safeLoad( | ||
fs.readFileSync(path.join(this.storage, CONFIG_FILE), 'utf8') | ||
) | ||
debug(`[LOADED] config:\n\n${config}`) | ||
this._identities = config.identities || [] | ||
this._groups = config.groups || [] | ||
return true | ||
this.groups = KeyHandler(this._groups) | ||
this.identities = KeyHandler(this._identities) | ||
} | ||
groups () { | ||
return this._groups || [] | ||
} | ||
save () { | ||
try { | ||
var config = defaultConfig() | ||
config.groups = this._groups | ||
config.identities = this._identities | ||
addGroup (params, opts = {}) { | ||
if (isNamedKeypair(params)) { | ||
this._groups.push(params) | ||
opts.save ? this.emit('save') : null | ||
fs.writeFileSync(this.storage, yaml.safeDump(config, { sortKeys: true })) | ||
return true | ||
} else { | ||
} catch (err) { | ||
console.error(err) | ||
return false | ||
@@ -78,12 +63,10 @@ } | ||
identities () { | ||
return this._identities || [] | ||
} | ||
addIdentity (params, opts = {}) { | ||
if (isNamedKeypair(params)) { | ||
this._identities.push(params) | ||
opts.save ? this.emit('save') : null | ||
load () { | ||
try { | ||
const config = yaml.safeLoad(fs.readFileSync(this.storage, 'utf8')) | ||
this._groups = config.groups | ||
this._identities = config.identities | ||
return true | ||
} else { | ||
} catch (err) { | ||
console.error(err) | ||
return false | ||
@@ -93,1 +76,6 @@ } | ||
} | ||
function storeSecret (location, secret) { | ||
fs.writeFileSync(secretKey, Buffer.from(secret)) | ||
return location | ||
} |
{ | ||
"name": "cobox-config", | ||
"version": "0.0.1", | ||
"version": "1.0.0", | ||
"description": "load and save a cobox configuration", | ||
@@ -11,11 +11,11 @@ "main": "index.js", | ||
"debug": "^4.1.1", | ||
"is-my-json-valid": "^2.20.0", | ||
"js-yaml": "^3.13.1", | ||
"mkdirp": "^0.5.1", | ||
"os": "^0.1.1", | ||
"pino": "^5.13.2", | ||
"urlsafe-base64": "^1.0.0" | ||
}, | ||
"devDependencies": { | ||
"hypercore-crypto": "^1.0.0", | ||
"istanbul": "^0.4.5", | ||
"cobox-crypto": "^1.0.2", | ||
"nyc": "^14.1.1", | ||
"rimraf": "^2.6.3", | ||
@@ -28,4 +28,4 @@ "tap-spec": "^5.0.0", | ||
"scripts": { | ||
"test": "istanbul cover tape test/**/*.test.js | tap-spec", | ||
"test:debug": "DEBUG=cobox-config istanbul cover tape test/**/*.test.js | tap-spec" | ||
"test": "tape test/**/*.test.js | tap-spec", | ||
"cover": "nyc tape test/**/*.test.js | tap-spec" | ||
}, | ||
@@ -41,3 +41,3 @@ "repository": { | ||
"author": "kyphae", | ||
"license": "ISC", | ||
"license": "AGPL-3", | ||
"bugs": { | ||
@@ -44,0 +44,0 @@ "url": "https://github.com/coboxcoop/cobox-config/issues" |
# cobox-config | ||
Stores and retrieves a yaml configuration file for use in `cobox-group` and `cobox-core`. | ||
Stores and retrieves a YAML configuration file for use with the cobox stack. | ||
@@ -13,15 +13,53 @@ ## Example | ||
var config = Config(storage) | ||
``` | ||
var identity = crypto.keyPair() | ||
config.addIdentity(Object.assign(identity, { name: 'Alice' })) | ||
## API | ||
var group = crypto.keyPair() | ||
config.addGroup(group) | ||
``` | ||
config.save() | ||
``` | ||
# Todos | ||
Write to YAML storage, path set using the storage path when initialising. Make sure you call this otherwise the config will not write to disk. | ||
``` | ||
config.load() | ||
``` | ||
Load from YAML, path set using the storage path when initialising. This is called automatically when initialising a `Config`. | ||
``` | ||
config.identities.add(identity) | ||
``` | ||
Append an identity to the config | ||
``` | ||
var key = identity.name || identity.publicKey || identity.publicKey.toString('hex') | ||
config.identities.remove(key) | ||
``` | ||
Remove an identity from the config | ||
``` | ||
var key = group.name || group.publicKey || group.publicKey.toString('hex') | ||
config.groups.remove(group) | ||
``` | ||
Remove a group from the config | ||
``` | ||
config.identities.list() | ||
``` | ||
List saved identities | ||
``` | ||
config.groups.list() | ||
``` | ||
List saved groups | ||
## Todos | ||
* [ ] encode public keys as base64 urlsafe in YAML | ||
* [ ] store the path to secret key file (base64 urlsafe binary encoding) instead of raw secret key in the config file | ||
* [ ] `sudo chmod 400` -> all secretKeys | ||
* [ ] remove groups and identities |
const { describe } = require('tape-plus') | ||
const Config = require('../') | ||
const crypto = require('hypercore-crypto') | ||
const crypto = require('cobox-crypto')() | ||
const fs = require('fs') | ||
@@ -8,3 +8,3 @@ const path = require('path') | ||
const { tmp, cleanup, base64encode } = require('./util') | ||
const { tmp, cleanup } = require('./util') | ||
@@ -22,13 +22,11 @@ describe('load', (context) => { | ||
context('success', (assert) => { | ||
context('default', (assert, next) => { | ||
var config = Config(storage.path) | ||
assert.ok(Array.isArray(config._identities), 'identities defaults to empty array') | ||
assert.ok(Array.isArray(config._groups), 'groups defaults to empty array') | ||
assert.deepEqual(config._identities, [], 'automatically loads identities from storage') | ||
assert.deepEqual(config._groups, [], 'automatically loads groups from storage') | ||
assert.ok(config.identities.list() instanceof Array, 'identities list defaults to empty Array') | ||
assert.ok(config.groups.list() instanceof Array, 'groups list defaults to empty Array') | ||
next() | ||
}) | ||
}) | ||
describe('save', (context) => { | ||
describe('add', (context) => { | ||
var storage | ||
@@ -44,39 +42,49 @@ | ||
context('success', (assert) => { | ||
context('byKey', (assert, next) => { | ||
var config = Config(storage.path) | ||
config._groups = [ base64encode(crypto.keyPair()) ] | ||
var group = crypto.unpack(crypto.accessKey()) | ||
var identity = crypto.keyPair() | ||
config.groups.add(group) | ||
config.identities.add(identity) | ||
config.save() | ||
var reload = yaml.safeLoad(fs.readFileSync(path.join(storage.path, 'config.yml'))) | ||
var rawGroup = reload.groups.byKey[group.publicKey.toString('hex')] | ||
var rawIdentity = reload.identities.byKey[identity.publicKey.toString('hex')] | ||
assert.deepEqual(config._groups, reload.groups, 'saves groups to storage') | ||
assert.deepEqual(config._identities, reload.identities, 'saves identities to storage') | ||
assert.same(config.groups.get(group.publicKey), rawGroup, 'saves groups to storage') | ||
assert.same(config.identities.get(identity.publicKey), rawIdentity, 'saves identities to storage') | ||
next() | ||
}) | ||
}) | ||
describe('reload', (context) => { | ||
var storage | ||
context('byName', (assert, next) => { | ||
var config = Config(storage.path) | ||
var group = Object.assign({ name: 'group-a' }, crypto.unpack(crypto.accessKey())) | ||
var identity = Object.assign({ name: 'Alice' }, crypto.keyPair()) | ||
context.beforeEach((c) => { | ||
storage = tmp() | ||
}) | ||
config.groups.add(group) | ||
config.identities.add(identity) | ||
context.afterEach((c) => { | ||
cleanup(storage.root) | ||
}) | ||
context('success', (assert) => { | ||
var config = Config(storage.path) | ||
config._groups = [ base64encode(crypto.keyPair()) ] | ||
config._identities = [ base64encode(crypto.keyPair()) ] | ||
config.save() | ||
var reload = yaml.safeLoad(fs.readFileSync(path.join(storage.path, 'config.yml'))) | ||
var rawGroup = reload.groups.byKey[group.publicKey.toString('hex')] | ||
var rawIdentity = reload.identities.byKey[identity.publicKey.toString('hex')] | ||
assert.ok(rawGroup.name, 'saves group with a name') | ||
assert.same(config.groups.get(group.name), rawGroup, 'gets the group using a name') | ||
assert.ok(rawIdentity.name, 'saves identity with a name') | ||
assert.same(config.identities.get(identity.name), rawIdentity, 'saves identities to storage') | ||
var reload = Config(storage.path) | ||
assert.deepEqual(config._identities, reload._identities, 'load identities from storage') | ||
assert.deepEqual(config._groups, reload._groups, 'load groups from storage') | ||
assert.same(reload._groups, config._groups, 'load groups from storage') | ||
assert.same(reload._identities, config._identities, 'load identities from storage') | ||
next() | ||
}) | ||
}) | ||
describe('groups', (context) => { | ||
describe('get', (context) => { | ||
var storage | ||
@@ -92,51 +100,22 @@ | ||
context('add: success', (assert) => { | ||
context('works with buffers or strings', (assert, next) => { | ||
var config = Config(storage.path) | ||
var keyPair = base64encode(crypto.keyPair()) | ||
config.addGroup(keyPair) | ||
var group = crypto.unpack(crypto.accessKey()) | ||
var identity = crypto.keyPair() | ||
assert.deepEqual(config._groups[0], keyPair, 'stores group keypairs') | ||
}) | ||
config.groups.add(group) | ||
config.identities.add(identity) | ||
context('add: manual save opt', (assert) => { | ||
var config = Config(storage.path) | ||
var keyPair = base64encode(crypto.keyPair()) | ||
config.addGroup(keyPair) | ||
var reload = Config(storage.path) | ||
assert.deepEqual(reload._groups, [], `doesn't save automatically`) | ||
config.save() | ||
var reload = Config(storage.path) | ||
assert.deepEqual(config._groups, reload._groups, 'reloads successfully after save') | ||
}) | ||
assert.ok(config.groups.get(group.publicKey), 'get group works with a buffer') | ||
assert.ok(config.groups.get(group.publicKey.toString('hex')), 'get group works with a hex') | ||
context('add: autosave opt', (assert) => { | ||
var config = Config(storage.path) | ||
var keyPair = base64encode(crypto.keyPair()) | ||
config.addGroup(keyPair, { save: true }) | ||
var reload = Config(storage.path) | ||
assert.deepEqual(reload._groups, config._groups, `saves automatically`) | ||
assert.ok(config.identities.get(identity.publicKey), 'get identity works with a buffer') | ||
assert.ok(config.identities.get(identity.publicKey.toString('hex')), 'get identity works with a hex') | ||
next() | ||
}) | ||
context('add: fail', (assert) => { | ||
var config = Config(storage.path) | ||
var location = { place: 'Wonderland' } | ||
assert.notOk(config.addGroup(location), 'returns false') | ||
assert.deepEqual(config._groups, [], 'invalid params') | ||
}) | ||
context('list', (assert) => { | ||
var config = Config(storage.path) | ||
assert.deepEqual(config.groups(), [], 'defaults to empty array') | ||
var keyPair = Object.assign(base64encode(crypto.keyPair()), { name: 'BlockadesTeam' }) | ||
config._groups = [keyPair] | ||
assert.deepEqual(config.groups(), [keyPair], 'stores identity keypairs') | ||
}) | ||
}) | ||
describe('identities', (context) => { | ||
describe('remove', (context) => { | ||
var storage | ||
@@ -152,48 +131,64 @@ | ||
context('add: success', (assert) => { | ||
context('removes relevant entry using buffers', (assert, next) => { | ||
var config = Config(storage.path) | ||
var keyPair = Object.assign(base64encode(crypto.keyPair()), { name: 'Alice' }) | ||
config.addIdentity(keyPair) | ||
var group = crypto.unpack(crypto.accessKey()) | ||
var identity = crypto.keyPair() | ||
assert.deepEqual(config._identities[0], keyPair, 'stores identity keypairs') | ||
config.groups.add(group) | ||
config.identities.add(identity) | ||
config.save() | ||
config.load() | ||
config.groups.remove(group.publicKey) | ||
config.identities.remove(identity.publicKey) | ||
assert.same(config.groups.list(), [], 'removed the group') | ||
assert.same(config.identities.list(), [], 'removed the identity') | ||
next() | ||
}) | ||
context('add: manual save opt', (assert) => { | ||
context('removes relevant entry using buffers', (assert, next) => { | ||
var config = Config(storage.path) | ||
var keyPair = base64encode(crypto.keyPair()) | ||
config.addIdentity(keyPair) | ||
var group = crypto.unpack(crypto.accessKey()) | ||
var identity = crypto.keyPair() | ||
var reload = Config(storage.path) | ||
assert.deepEqual(reload._identities, [], `doesn't save automatically`) | ||
config.groups.add(group) | ||
config.identities.add(identity) | ||
config.save() | ||
config.load() | ||
var reload = Config(storage.path) | ||
assert.deepEqual(config._identities, reload._identities, 'reloads successfully after save') | ||
config.groups.remove(group.publicKey.toString('hex')) | ||
config.identities.remove(identity.publicKey.toString('hex')) | ||
assert.same(config.groups.list(), [], 'removed the group') | ||
assert.same(config.identities.list(), [], 'removed the identity') | ||
next() | ||
}) | ||
context('add: autosave opt', (assert) => { | ||
context('removes relevant entry using names', (assert, next) => { | ||
var config = Config(storage.path) | ||
var keyPair = base64encode(crypto.keyPair()) | ||
config.addIdentity(keyPair, { save: true }) | ||
var group = Object.assign({ name: 'group-a' }, crypto.unpack(crypto.accessKey())) | ||
var identity = Object.assign({ name: 'Alice' }, crypto.keyPair()) | ||
var reload = Config(storage.path) | ||
assert.deepEqual(reload._identities, config._identities, `saves automatically`) | ||
}) | ||
config.groups.add(group) | ||
config.identities.add(identity) | ||
context('add: fail', (assert) => { | ||
var config = Config(storage.path) | ||
var location = { place: 'Wonderland' } | ||
assert.notOk(config.addIdentity(location), 'returns false') | ||
assert.deepEqual(config._identities, [], 'invalid params') | ||
}) | ||
config.save() | ||
config.load() | ||
context('list', (assert) => { | ||
var config = Config(storage.path) | ||
assert.deepEqual(config.identities(), [], 'defaults to empty array') | ||
assert.same(config.groups.list().length, 1, 'saved the group') | ||
assert.same(config.identities.list().length, 1, 'saved the identity') | ||
var keyPair = Object.assign(base64encode(crypto.keyPair()), { name: 'Alice' }) | ||
config._identities = [keyPair] | ||
assert.deepEqual(config.identities(), [keyPair], 'stores identity keypairs') | ||
config.groups.remove(group.name) | ||
config.identities.remove(identity.name) | ||
config.save() | ||
config.load() | ||
assert.same(config.groups.list(), [], 'removed the group') | ||
assert.same(config.identities.list(), [], 'removed the identity') | ||
next() | ||
}) | ||
}) |
const tmpdir = require('tmp').dirSync | ||
const mkdirp = require('mkdirp') | ||
const rimraf = require('rimraf') | ||
const urlsafeBase64 = require('urlsafe-base64') | ||
const debug = require('debug')('cobox-config') | ||
@@ -22,9 +21,2 @@ | ||
function base64encode (obj) { | ||
Object.keys(obj).map((key) => ( | ||
obj[key] = urlsafeBase64.encode(Buffer.from(obj[key]))) | ||
) | ||
return obj | ||
} | ||
module.exports = { cleanup, tmp, base64encode } | ||
module.exports = { cleanup, tmp } |
31
util.js
@@ -1,29 +0,10 @@ | ||
const Validator = require('is-my-json-valid') | ||
// Q: this is unnecessary? | ||
function isNamedKeypair (content) { | ||
content.publicKey = toStr(content.publicKey) | ||
content.secretKey = toStr(content.secretKey) | ||
var schema = { | ||
$schema: 'http://json-schema.org/schema#', | ||
type: 'object', | ||
required: ['publicKey', 'secretKey'], | ||
properties: { | ||
publicKey: { type: 'string', pattern: '[A-Za-z0-9_-]' }, | ||
secretKey: { type: 'string', pattern: '[A-Za-z0-9_-]' }, | ||
name: { type: 'string' } | ||
} | ||
} | ||
const validator = Validator(schema, { verbose: true }) | ||
return validator(content) | ||
function isPubKey (variable) { | ||
if (!(typeof variable === 'string' || variable instanceof Buffer)) return false | ||
return Buffer.from(variable, 'hex').length === 32 | ||
} | ||
function toStr (key) { | ||
return Buffer.isBuffer(key) | ||
? key.toString('hex') | ||
: key | ||
function isName (variable) { | ||
return typeof variable === 'string' | ||
} | ||
module.exports = { isNamedKeypair } | ||
module.exports = { isPubKey, isName } |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
117571
11
2
275
1
65
3
1
+ Addedpino@^5.13.2
+ Addedatomic-sleep@1.0.0(transitive)
+ Addedfast-redact@2.1.0(transitive)
+ Addedfast-safe-stringify@2.1.1(transitive)
+ Addedflatstr@1.0.12(transitive)
+ Addedpino@5.17.0(transitive)
+ Addedpino-std-serializers@2.5.0(transitive)
+ Addedquick-format-unescaped@3.0.3(transitive)
+ Addedsonic-boom@0.7.7(transitive)
- Removedis-my-json-valid@^2.20.0
- Removedgenerate-function@2.3.1(transitive)
- Removedgenerate-object-property@1.2.0(transitive)
- Removedis-my-ip-valid@1.0.1(transitive)
- Removedis-my-json-valid@2.20.6(transitive)
- Removedis-property@1.0.2(transitive)
- Removedjsonpointer@5.0.1(transitive)
- Removedxtend@4.0.2(transitive)