UNPKG

ssb-keyring

Version:

A persistence store for encryption keys for scuttlebutt. It's purpose is to make easy to answer box2 encryption/ decryption questions.

158 lines (129 loc) 4.61 kB
const pull = require('pull-stream') const { read } = require('pull-level') const lFind = require('lodash.find') const { isGroup, isSameKey, isBuffer } = require('../util') const { processInfo } = require('./helpers') const GROUP = 'group' module.exports = function Group (db) { let cache = new Map() // Map: groupId => group.info function isExcludedGroup (groupId) { const group = cache.get(groupId) return !!group.excluded } return { load (cb) { readPersisted((err, pairs) => { if (err) return cb(err) cache = new Map(pairs) cb(null) }) }, add (groupId, addInfo, cb) { if (!cache) throw new Error('keyring not ready') if (!isGroup(groupId)) { return cb(new Error(`expected a groupId, got ${groupId}`)) } const groupInfo = cache.get(groupId) ?? {} processInfo(groupInfo, addInfo, (err) => { if (err) return cb(err) // groupInfo gets mutated cache.set(groupId, groupInfo) db.put([GROUP, groupId], groupInfo, cb) }) }, pickWriteKey (groupId, pickedKey, cb) { if (!cache) throw new Error('keyring not ready') if (!isGroup(groupId)) { return cb(new Error(`expected a groupId, got ${groupId}`)) } const groupInfo = cache.get(groupId) if (!groupInfo) return cb(new Error("Couldn't find a group with ID " + groupId)) if (groupInfo.excluded) return cb(new Error('You are not in this group anymore. GroupId:' + groupId)) if (!pickedKey?.key || !isBuffer(pickedKey.key)) return cb(new Error('Missing key buffer in picked key')) if (!pickedKey?.scheme) return cb(new Error('Missing scheme in picked key')) if (isSameKey(groupInfo.writeKey.key, pickedKey.key) && groupInfo.writeKey.scheme === pickedKey.scheme) { // if the key we're picking is already picked, return early return cb() } const foundKey = lFind( groupInfo.readKeys, readKey => isSameKey(readKey.key, pickedKey.key) && readKey.scheme === pickedKey.scheme ) if (!foundKey) return cb(new Error('Picked key does not match any current read key')) groupInfo.writeKey = pickedKey cache.set(groupId, groupInfo) db.put([GROUP, groupId], groupInfo, cb) }, has (groupId) { if (!cache) throw new Error('keyring not ready') return cache.has(groupId) && !isExcludedGroup(groupId) }, get (groupId) { if (!cache) throw new Error('keyring not ready') return cache.get(groupId) }, getUpdates (groupId) { if (!cache) throw new Error('keyring not ready') // [0] is id, [1] is info return pull( groupStream({ live: true }), pull.filter((group) => group[0] === groupId), pull.map((group) => group[1]) ) }, exclude (groupId, cb) { if (!cache) throw new Error('keyring not ready') if (!isGroup(groupId)) { return cb(new Error(`expected a groupId, got ${groupId}`)) } const groupInfo = cache.get(groupId) if (!groupInfo) return cb(new Error("Couldn't find a group with ID " + groupId)) delete groupInfo.writeKey groupInfo.excluded = true cache.set(groupId, groupInfo) db.put([GROUP, groupId], groupInfo, cb) }, list (opts = {}) { if (!cache) throw new Error('keyring not ready') if (opts.live) { return pull( groupStream({ live: true }), pull.map(group => group[0]), // only get id // if opts.excluded == true then only return excluded groups, if false then only return not excluded groups pull.filter(groupId => !!opts.excluded === isExcludedGroup(groupId)) ) } else { return pull.values(this.listSync({ excluded: opts.excluded })) } }, listSync (opts = {}) { if (!cache) throw new Error('keyring not ready') return Array.from(cache.keys()) .filter(groupId => !!opts.excluded === isExcludedGroup(groupId)) } } function groupStream (opts = {}) { return pull( read(db, { gt: [GROUP, null], lt: [GROUP + '~', undefined], live: !!opts.live }), pull.map(({ key, value: info, sync }) => { if (sync) return null const [_, groupId] = key; // eslint-disable-line return [groupId, info] }), pull.filter(Boolean) ) } function readPersisted (cb) { pull( groupStream(), pull.collect((err, pairs) => { if (err) return cb(err) cb(null, pairs) }) ) } }