mirror of
https://github.com/orbitdb/orbitdb.git
synced 2025-03-30 15:08:28 +00:00
Refactor/ac (#61)
* refactor: Move Manifest to own module. * refactor: Modularize orbitdb access controller. * chore: Check for correct access controller path and modify if necessary. * fix: Linting. * refactor: AC interface no longer needed. * refactor: Move IPFS-specific AC list back into IPFS AC. * refactor: Explicitly name access controller param. * refactor: Pass in manifest settings as object. * refactor: Config access controllers. * refactor: ACs should expose specific params before being called with generic params. * feat: Pass write access to root IPFS AC. * refactor: AC should handle type prefix. * test: Test for type. * refactor: Pass generic access to Database (and inheriting dbs). * refactor: Use AccessControllers module to manage custom ACs. * chore: Remove excess console logging. * test: Fix ipfs module import.
This commit is contained in:
parent
a027525e5c
commit
428ce83878
@ -4,7 +4,6 @@ import { Log, Entry } from './oplog/index.js'
|
||||
import { ComposedStorage, IPFSBlockStorage, LevelStorage, LRUStorage } from './storage/index.js'
|
||||
import KeyStore from './key-store.js'
|
||||
import { Identities } from './identities/index.js'
|
||||
import IPFSAccessController from './access-controllers/ipfs.js'
|
||||
import OrbitDBAddress, { isValidAddress } from './address.js'
|
||||
import DBManifest from './manifest.js'
|
||||
import { createId } from './utils/index.js'
|
||||
@ -13,6 +12,7 @@ import pathJoin from './utils/path-join.js'
|
||||
import * as Block from 'multiformats/block'
|
||||
import * as dagCbor from '@ipld/dag-cbor'
|
||||
import { sha256 } from 'multiformats/hashes/sha2'
|
||||
import * as AccessControllers from './access-controllers/index.js'
|
||||
|
||||
const codec = dagCbor
|
||||
const hasher = sha256
|
||||
@ -26,6 +26,11 @@ const databaseTypes = {
|
||||
documents: DocumentStore,
|
||||
keyvalue: KeyValue
|
||||
}
|
||||
//
|
||||
// const accessControllers = {
|
||||
// ipfs: IPFSAccessController,
|
||||
// orbitdb: OrbitDBAccessController
|
||||
// }
|
||||
|
||||
const addDatabaseType = (type, store) => {
|
||||
if (databaseTypes[type]) {
|
||||
@ -33,6 +38,13 @@ const addDatabaseType = (type, store) => {
|
||||
}
|
||||
databaseTypes[type] = store
|
||||
}
|
||||
//
|
||||
// const addAccessController = (type, accessController) => {
|
||||
// if (accessControllers[type]) {
|
||||
// throw new Error(`Access Controller already exists: ${type}`)
|
||||
// }
|
||||
// accessControllers[type] = accessController
|
||||
// }
|
||||
|
||||
// const defaultTimeout = 30000 // 30 seconds
|
||||
|
||||
@ -57,11 +69,11 @@ const OrbitDB = async ({ ipfs, id, identity, keystore, directory } = {}) => {
|
||||
|
||||
let databases = {}
|
||||
|
||||
const open = async (address, { type, meta, write, sync, Store } = {}) => {
|
||||
const open = async (address, { type, meta, sync, Store, AccessController } = {}) => {
|
||||
let name, manifest, accessController
|
||||
|
||||
if (type && !databaseTypes[type]) {
|
||||
throw new Error(`Unspported database type: '${type}'`)
|
||||
throw new Error(`Unsupported database type: '${type}'`)
|
||||
}
|
||||
|
||||
if (databases[address]) {
|
||||
@ -74,19 +86,23 @@ const OrbitDB = async ({ ipfs, id, identity, keystore, directory } = {}) => {
|
||||
const bytes = await manifestStorage.get(addr.path)
|
||||
const { value } = await Block.decode({ bytes, codec, hasher })
|
||||
manifest = value
|
||||
const acAddress = manifest.accessController.replaceAll('/ipfs/', '')
|
||||
accessController = await IPFSAccessController({ ipfs, identities, identity, address: acAddress, storage: manifestStorage, write })
|
||||
const acType = manifest.accessController.split('/', 2).pop()
|
||||
|
||||
const acAddress = manifest.accessController.replaceAll(`/${acType}/`, '')
|
||||
accessController = await AccessControllers.get(acType)()({ orbitdb: { open, identity, ipfs }, identities, address: acAddress })
|
||||
name = manifest.name
|
||||
type = type || manifest.type
|
||||
meta = manifest.meta
|
||||
} else {
|
||||
// If the address given was not valid, eg. just the name of the database
|
||||
type = type || 'events'
|
||||
accessController = await IPFSAccessController({ ipfs, identities, identity, storage: manifestStorage, write })
|
||||
const m = await DBManifest(manifestStorage, address, type, accessController.address, { meta })
|
||||
AccessController = AccessController || AccessControllers.get('ipfs')({ storage: manifestStorage })
|
||||
accessController = await AccessController({ orbitdb: { open, identity, ipfs }, identities })
|
||||
const m = await DBManifest({ storage: manifestStorage, name: address, type, accessController: accessController.address, meta })
|
||||
|
||||
manifest = m.manifest
|
||||
address = OrbitDBAddress(m.hash)
|
||||
accessController = m.accessController
|
||||
// accessController = manifest.accessController
|
||||
name = manifest.name
|
||||
meta = manifest.meta
|
||||
}
|
||||
@ -94,10 +110,10 @@ const OrbitDB = async ({ ipfs, id, identity, keystore, directory } = {}) => {
|
||||
const DatabaseModel = Store || databaseTypes[type]
|
||||
|
||||
if (!DatabaseModel) {
|
||||
throw new Error(`Unspported database type: '${type}'`)
|
||||
throw new Error(`Unsupported database type: '${type}'`)
|
||||
}
|
||||
|
||||
const db = await DatabaseModel({ OpLog, Database, ipfs, identity, address: address.toString(), name, accessController, directory, meta, syncAutomatically: sync != null ? sync : true })
|
||||
const db = await DatabaseModel({ OpLog, Database, ipfs, identity, address: address.toString(), name, access: accessController, directory, meta, syncAutomatically: sync != null ? sync : true })
|
||||
|
||||
db.events.on('close', onDatabaseClosed(address.toString()))
|
||||
|
||||
@ -131,7 +147,7 @@ const OrbitDB = async ({ ipfs, id, identity, keystore, directory } = {}) => {
|
||||
}
|
||||
}
|
||||
|
||||
export { OrbitDB as default, OrbitDBAddress, addDatabaseType, databaseTypes }
|
||||
export { OrbitDB as default, OrbitDBAddress, addDatabaseType, databaseTypes, AccessControllers }
|
||||
|
||||
// class OrbitDB2 {
|
||||
// constructor (ipfs, identity, options = {}) {
|
||||
|
@ -1,70 +1,42 @@
|
||||
import AccessController from './interface.js'
|
||||
import AccessControllerManifest from './manifest.js'
|
||||
// import LegacyIPFSAccessController from './access-controllers/legacy-ipfs.js'
|
||||
import IPFSAccessController from './ipfs.js'
|
||||
// import OrbitDBAccessController from './orbitdb.js'
|
||||
import OrbitDBAccessController from './orbitdb.js'
|
||||
|
||||
const supportedTypes = {
|
||||
// 'legacy-ipfs': LegacyIPFSAccessController,
|
||||
ipfs: IPFSAccessController
|
||||
// orbitdb: OrbitDBAccessController
|
||||
const types = {
|
||||
ipfs: IPFSAccessController,
|
||||
orbitdb: OrbitDBAccessController
|
||||
}
|
||||
|
||||
const getHandlerFor = (type) => {
|
||||
if (!AccessControllers.isSupported(type)) {
|
||||
const get = (type) => {
|
||||
if (!isSupported(type)) {
|
||||
throw new Error(`AccessController type '${type}' is not supported`)
|
||||
}
|
||||
return supportedTypes[type]
|
||||
return types[type]
|
||||
}
|
||||
|
||||
export default class AccessControllers {
|
||||
static get AccessController () { return AccessController }
|
||||
|
||||
static isSupported (type) {
|
||||
return Object.keys(supportedTypes).includes(type)
|
||||
}
|
||||
|
||||
static addAccessController (options) {
|
||||
if (!options.AccessController) {
|
||||
throw new Error('AccessController class needs to be given as an option')
|
||||
}
|
||||
|
||||
if (!options.AccessController.type ||
|
||||
typeof options.AccessController.type !== 'string') {
|
||||
throw new Error('Given AccessController class needs to implement: static get type() { /* return a string */}.')
|
||||
}
|
||||
|
||||
supportedTypes[options.AccessController.type] = options.AccessController
|
||||
}
|
||||
|
||||
static addAccessControllers (options) {
|
||||
const accessControllers = options.AccessControllers
|
||||
if (!accessControllers) {
|
||||
throw new Error('AccessController classes need to be given as an option')
|
||||
}
|
||||
|
||||
accessControllers.forEach((accessController) => {
|
||||
AccessControllers.addAccessController({ AccessController: accessController })
|
||||
})
|
||||
}
|
||||
|
||||
static removeAccessController (type) {
|
||||
delete supportedTypes[type]
|
||||
}
|
||||
|
||||
static async resolve (orbitdb, manifestAddress, options = {}) {
|
||||
const { type, params } = await AccessControllerManifest.resolve(orbitdb._ipfs, manifestAddress, options)
|
||||
const AccessController = getHandlerFor(type)
|
||||
const accessController = await AccessController.create(orbitdb, Object.assign({}, options, params))
|
||||
await accessController.load(params.address)
|
||||
return accessController
|
||||
}
|
||||
|
||||
static async create ({ ipfs, identity }, type, options = {}) {
|
||||
const AccessController = getHandlerFor(type)
|
||||
const ac = await AccessController.create({ ipfs, identity }, options)
|
||||
const params = await ac.save()
|
||||
const hash = await AccessControllerManifest.create(ipfs, type, params)
|
||||
return hash
|
||||
}
|
||||
const isSupported = type => {
|
||||
return Object.keys(types).includes(type)
|
||||
}
|
||||
|
||||
const add = (accessController) => {
|
||||
if (types[accessController.type]) {
|
||||
throw new Error(`Access controller '${accessController.type}' already added.`)
|
||||
}
|
||||
|
||||
if (!accessController.type) {
|
||||
throw new Error('Given AccessController class needs to implement: type.')
|
||||
}
|
||||
|
||||
types[accessController.type] = accessController
|
||||
}
|
||||
|
||||
const remove = type => {
|
||||
delete types[type]
|
||||
}
|
||||
|
||||
export {
|
||||
types,
|
||||
get,
|
||||
isSupported,
|
||||
add,
|
||||
remove
|
||||
}
|
||||
|
@ -1,50 +0,0 @@
|
||||
import { EventEmitter } from 'events'
|
||||
|
||||
/**
|
||||
* Interface for OrbitDB Access Controllers
|
||||
*
|
||||
* Any OrbitDB access controller needs to define and implement
|
||||
* the methods defined by the interface here.
|
||||
*/
|
||||
export default class AccessController extends EventEmitter {
|
||||
/*
|
||||
Every AC needs to have a 'Factory' method
|
||||
that creates an instance of the AccessController
|
||||
*/
|
||||
static async create (orbitdb, options) {}
|
||||
|
||||
/* Return the type for this controller */
|
||||
static get type () {
|
||||
throw new Error('\'static get type ()\' needs to be defined in the inheriting class')
|
||||
}
|
||||
|
||||
/*
|
||||
Return the type for this controller
|
||||
NOTE! This is the only property of the interface that
|
||||
shouldn't be overridden in the inherited Access Controller
|
||||
*/
|
||||
get type () {
|
||||
return this.constructor.type
|
||||
}
|
||||
|
||||
/* Each Access Controller has some address to anchor to */
|
||||
get address () {}
|
||||
|
||||
/*
|
||||
Called by the databases (the log) to see if entry should
|
||||
be allowed in the database. Return true if the entry is allowed,
|
||||
false is not allowed
|
||||
*/
|
||||
async canAppend (entry, identityProvider) {}
|
||||
|
||||
/* Add and remove access */
|
||||
async grant (access, identity) { return false }
|
||||
async revoke (access, identity) { return false }
|
||||
|
||||
/* AC creation and loading */
|
||||
async load (address) {}
|
||||
/* Returns AC manifest parameters object */
|
||||
async save () {}
|
||||
/* Called when the database for this AC gets closed */
|
||||
async close () {}
|
||||
}
|
@ -1,19 +1,15 @@
|
||||
// import * as io from '../utils/index.js'
|
||||
// import AccessController from './interface.js'
|
||||
// import AccessControllerManifest from './manifest.js'
|
||||
import { IPFSBlockStorage } from '../storage/index.js'
|
||||
import * as Block from 'multiformats/block'
|
||||
import * as dagCbor from '@ipld/dag-cbor'
|
||||
import { sha256 } from 'multiformats/hashes/sha2'
|
||||
import { base58btc } from 'multiformats/bases/base58'
|
||||
import pathJoin from '../utils/path-join.js'
|
||||
|
||||
const codec = dagCbor
|
||||
const hasher = sha256
|
||||
const hashStringEncoding = base58btc
|
||||
|
||||
const type = 'ipfs'
|
||||
|
||||
const AccessControllerManifest = async ({ storage, type, params }) => {
|
||||
const AccessControlList = async ({ storage, type, params }) => {
|
||||
const manifest = {
|
||||
type,
|
||||
...params
|
||||
@ -24,17 +20,19 @@ const AccessControllerManifest = async ({ storage, type, params }) => {
|
||||
return hash
|
||||
}
|
||||
|
||||
const IPFSAccessController = async ({ ipfs, identities, identity, address, storage, write }) => {
|
||||
storage = storage || await IPFSBlockStorage({ ipfs, pin: true })
|
||||
const type = 'ipfs'
|
||||
|
||||
write = write || [identity.id]
|
||||
const IPFSAccessController = ({ write, storage } = {}) => async ({ orbitdb, identities, address }) => {
|
||||
storage = storage || await IPFSBlockStorage({ ipfs: orbitdb.ipfs, pin: true })
|
||||
write = write || [orbitdb.identity.id]
|
||||
|
||||
if (address) {
|
||||
const manifestBytes = await storage.get(address)
|
||||
const manifestBytes = await storage.get(address.replaceAll('/ipfs/', ''))
|
||||
const { value } = await Block.decode({ bytes: manifestBytes, codec, hasher })
|
||||
write = value.write
|
||||
} else {
|
||||
address = await AccessControllerManifest({ storage, type, params: { write } })
|
||||
address = await AccessControlList({ storage, type, params: { write } })
|
||||
address = pathJoin('/', type, address)
|
||||
}
|
||||
|
||||
const canAppend = async (entry) => {
|
||||
@ -59,4 +57,4 @@ const IPFSAccessController = async ({ ipfs, identities, identity, address, stora
|
||||
}
|
||||
}
|
||||
|
||||
export { IPFSAccessController as default }
|
||||
export default IPFSAccessController
|
||||
|
@ -1,33 +0,0 @@
|
||||
// import * as io from 'orbit-db-io'
|
||||
|
||||
// export default class AccessControllerManifest {
|
||||
// constructor (type, params = {}) {
|
||||
// this.type = type
|
||||
// this.params = params
|
||||
// }
|
||||
|
||||
// static async resolve (ipfs, manifestHash, options = {}) {
|
||||
// if (options.skipManifest) {
|
||||
// if (!options.type) {
|
||||
// throw new Error('No manifest, access-controller type required')
|
||||
// }
|
||||
// return new AccessControllerManifest(options.type, { address: manifestHash })
|
||||
// } else {
|
||||
// // TODO: ensure this is a valid multihash
|
||||
// if (manifestHash.indexOf('/ipfs') === 0) { manifestHash = manifestHash.split('/')[2] }
|
||||
// const { type, params } = await io.read(ipfs, manifestHash)
|
||||
// return new AccessControllerManifest(type, params)
|
||||
// }
|
||||
// }
|
||||
|
||||
// static async create (ipfs, type, params) {
|
||||
// if (params.skipManifest) {
|
||||
// return params.address
|
||||
// }
|
||||
// const manifest = {
|
||||
// type,
|
||||
// params
|
||||
// }
|
||||
// return io.write(ipfs, 'dag-cbor', manifest)
|
||||
// }
|
||||
// }
|
@ -1,134 +1,108 @@
|
||||
import pMapSeries from 'p-map-series'
|
||||
import AccessController from './interface.js'
|
||||
import ensureAddress from '../utils/ensure-ac-address.js'
|
||||
import { EventEmitter } from 'events'
|
||||
import ensureACAddress from '../utils/ensure-ac-address.js'
|
||||
import IPFSAccessController from './ipfs.js'
|
||||
|
||||
const type = 'orbitdb'
|
||||
|
||||
export default class OrbitDBAccessController extends AccessController {
|
||||
constructor (orbitdb, options) {
|
||||
super()
|
||||
this._orbitdb = orbitdb
|
||||
this._db = null
|
||||
this._options = options || {}
|
||||
const OrbitDBAccessController = ({ write } = {}) => async ({ orbitdb, identities, address }) => {
|
||||
const events = new EventEmitter()
|
||||
|
||||
address = address || 'default-access-controller'
|
||||
write = write || [orbitdb.identity.id]
|
||||
|
||||
// Force '<address>/_access' naming for the database
|
||||
const db = await orbitdb.open(ensureACAddress(address), { type: 'keyvalue', AccessController: IPFSAccessController({ write }) })
|
||||
address = db.address
|
||||
|
||||
const onUpdate = (entry) => {
|
||||
events.emit('update', entry)
|
||||
}
|
||||
|
||||
// Returns the type of the access controller
|
||||
static get type () { return type }
|
||||
|
||||
// Returns the address of the OrbitDB used as the AC
|
||||
get address () {
|
||||
return this._db.address
|
||||
}
|
||||
db.events.on('update', onUpdate)
|
||||
|
||||
// Return true if entry is allowed to be added to the database
|
||||
async canAppend (entry, identityProvider) {
|
||||
// Write keys and admins keys are allowed
|
||||
const access = new Set([...this.get('write'), ...this.get('admin')])
|
||||
const canAppend = async (entry) => {
|
||||
const writerIdentity = await identities.getIdentity(entry.identity)
|
||||
if (!writerIdentity) {
|
||||
return false
|
||||
}
|
||||
|
||||
const { id } = writerIdentity
|
||||
// If the ACL contains the writer's public key or it contains '*'
|
||||
if (access.has(entry.identity.id) || access.has('*')) {
|
||||
const verifiedIdentity = await identityProvider.verifyIdentity(entry.identity)
|
||||
// Allow access if identity verifies
|
||||
return verifiedIdentity
|
||||
const hasWriteAccess = await hasCapability('write', id) || await hasCapability('admin', id)
|
||||
if (hasWriteAccess) {
|
||||
return identities.verifyIdentity(writerIdentity)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
get capabilities () {
|
||||
if (this._db) {
|
||||
const capabilities = this._db.index
|
||||
|
||||
const toSet = (e) => {
|
||||
const key = e[0]
|
||||
capabilities[key] = new Set([...(capabilities[key] || []), ...e[1]])
|
||||
}
|
||||
|
||||
// Merge with the access controller of the database
|
||||
// and make sure all values are Sets
|
||||
Object.entries({
|
||||
...capabilities,
|
||||
// Add the root access controller's 'write' access list
|
||||
// as admins on this controller
|
||||
...{ admin: new Set([...(capabilities.admin || []), ...this._db.access.write]) }
|
||||
}).forEach(toSet)
|
||||
|
||||
return capabilities
|
||||
const capabilities = async () => {
|
||||
const _capabilities = []
|
||||
for await (const entry of db.iterator()) {
|
||||
_capabilities[entry.key] = entry.value
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
get (capability) {
|
||||
return this.capabilities[capability] || new Set([])
|
||||
}
|
||||
|
||||
async close () {
|
||||
await this._db.close()
|
||||
}
|
||||
|
||||
async load (address) {
|
||||
if (this._db) { await this._db.close() }
|
||||
|
||||
// Force '<address>/_access' naming for the database
|
||||
this._db = await this._orbitdb.keyvalue(ensureAddress(address), {
|
||||
// use ipfs controller as a immutable "root controller"
|
||||
accessController: {
|
||||
type: 'ipfs',
|
||||
write: this._options.admin || [this._orbitdb.identity.id]
|
||||
},
|
||||
sync: true
|
||||
})
|
||||
|
||||
this._db.events.on('ready', this._onUpdate.bind(this))
|
||||
this._db.events.on('write', this._onUpdate.bind(this))
|
||||
this._db.events.on('replicated', this._onUpdate.bind(this))
|
||||
|
||||
await this._db.load()
|
||||
}
|
||||
|
||||
async save () {
|
||||
// return the manifest data
|
||||
return {
|
||||
address: this._db.address.toString()
|
||||
const toSet = (e) => {
|
||||
const key = e[0]
|
||||
_capabilities[key] = new Set([...(_capabilities[key] || []), ...e[1]])
|
||||
}
|
||||
|
||||
// Merge with the access controller of the database
|
||||
// and make sure all values are Sets
|
||||
Object.entries({
|
||||
..._capabilities,
|
||||
// Add the root access controller's 'write' access list
|
||||
// as admins on this controller
|
||||
...{ admin: new Set([...(_capabilities.admin || []), ...write]) }
|
||||
}).forEach(toSet)
|
||||
|
||||
return _capabilities
|
||||
}
|
||||
|
||||
async hasCapability (capability, identity) {
|
||||
const get = async (capability) => {
|
||||
const _capabilities = await capabilities()
|
||||
return _capabilities[capability] || new Set([])
|
||||
}
|
||||
|
||||
const close = async () => {
|
||||
await db.close()
|
||||
}
|
||||
|
||||
const hasCapability = async (capability, key) => {
|
||||
// Write keys and admins keys are allowed
|
||||
const access = new Set(this.get(capability))
|
||||
return access.has(identity.id) || access.has('*')
|
||||
const access = new Set(await get(capability))
|
||||
return access.has(key) || access.has('*')
|
||||
}
|
||||
|
||||
async grant (capability, key) {
|
||||
const grant = async (capability, key) => {
|
||||
// Merge current keys with the new key
|
||||
const capabilities = new Set([...(this._db.get(capability) || []), ...[key]])
|
||||
await this._db.put(capability, Array.from(capabilities.values()))
|
||||
const capabilities = new Set([...(await db.get(capability) || []), ...[key]])
|
||||
await db.put(capability, Array.from(capabilities.values()))
|
||||
}
|
||||
|
||||
async revoke (capability, key) {
|
||||
const capabilities = new Set(this._db.get(capability) || [])
|
||||
const revoke = async (capability, key) => {
|
||||
const capabilities = new Set(await db.get(capability) || [])
|
||||
capabilities.delete(key)
|
||||
if (capabilities.size > 0) {
|
||||
await this._db.put(capability, Array.from(capabilities.values()))
|
||||
await db.put(capability, Array.from(capabilities.values()))
|
||||
} else {
|
||||
await this._db.del(capability)
|
||||
await db.del(capability)
|
||||
}
|
||||
}
|
||||
|
||||
/* Private methods */
|
||||
_onUpdate () {
|
||||
this.emit('updated')
|
||||
}
|
||||
|
||||
/* Factory */
|
||||
static async create (orbitdb, options = {}) {
|
||||
const ac = new OrbitDBAccessController(orbitdb, options)
|
||||
await ac.load(options.address || options.name || 'default-access-controller')
|
||||
|
||||
// Add write access from options
|
||||
if (options.write && !options.address) {
|
||||
await pMapSeries(options.write, async (e) => ac.grant('write', e))
|
||||
}
|
||||
|
||||
return ac
|
||||
return {
|
||||
type,
|
||||
address,
|
||||
write,
|
||||
canAppend,
|
||||
capabilities,
|
||||
get,
|
||||
grant,
|
||||
revoke,
|
||||
close,
|
||||
events
|
||||
}
|
||||
}
|
||||
|
||||
export default OrbitDBAccessController
|
||||
|
@ -7,7 +7,7 @@ import pathJoin from './utils/path-join.js'
|
||||
const defaultReferencesCount = 16
|
||||
const defaultCacheSize = 1000
|
||||
|
||||
const Database = async ({ OpLog, ipfs, identity, address, name, accessController, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically }) => {
|
||||
const Database = async ({ OpLog, ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically }) => {
|
||||
const { Log, Entry } = OpLog
|
||||
|
||||
directory = pathJoin(directory || './orbitdb', `./${address}/`)
|
||||
@ -29,7 +29,7 @@ const Database = async ({ OpLog, ipfs, identity, address, name, accessController
|
||||
await LevelStorage({ path: pathJoin(directory, '/log/_index/') })
|
||||
)
|
||||
|
||||
const log = await Log(identity, { logId: address, access: accessController, entryStorage, headsStorage, indexStorage })
|
||||
const log = await Log(identity, { logId: address, access, entryStorage, headsStorage, indexStorage })
|
||||
|
||||
const events = new EventEmitter()
|
||||
const queue = new PQueue({ concurrency: 1 })
|
||||
@ -88,7 +88,8 @@ const Database = async ({ OpLog, ipfs, identity, address, name, accessController
|
||||
log,
|
||||
sync,
|
||||
peers: sync.peers,
|
||||
events
|
||||
events,
|
||||
access
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
const DocumentStore = async ({ OpLog, Database, ipfs, identity, address, name, accessController, directory, storage, meta, syncAutomatically, indexBy = '_id' }) => {
|
||||
const database = await Database({ OpLog, ipfs, identity, address, name, accessController, directory, storage, meta, syncAutomatically })
|
||||
const DocumentStore = async ({ OpLog, Database, ipfs, identity, address, name, access, directory, storage, meta, syncAutomatically, indexBy = '_id' }) => {
|
||||
const database = await Database({ OpLog, ipfs, identity, address, name, access, directory, storage, meta, syncAutomatically })
|
||||
|
||||
const { addOperation, log } = database
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
const Events = async ({ OpLog, Database, ipfs, identity, address, name, accessController, directory, storage, meta, syncAutomatically }) => {
|
||||
const database = await Database({ OpLog, ipfs, identity, address, name, accessController, directory, storage, meta, syncAutomatically })
|
||||
const Events = async ({ OpLog, Database, ipfs, identity, address, name, access, directory, storage, meta, syncAutomatically }) => {
|
||||
const database = await Database({ OpLog, ipfs, identity, address, name, access, directory, storage, meta, syncAutomatically })
|
||||
|
||||
const { addOperation, log } = database
|
||||
|
||||
|
@ -5,8 +5,8 @@ import PQueue from 'p-queue'
|
||||
|
||||
const valueEncoding = 'json'
|
||||
|
||||
const KeyValuePersisted = async ({ OpLog, Database, ipfs, identity, address, name, accessController, directory, storage, meta }) => {
|
||||
const keyValueStore = await KeyValue({ OpLog, Database, ipfs, identity, address, name, accessController, directory, storage, meta })
|
||||
const KeyValuePersisted = async ({ OpLog, Database, ipfs, identity, address, name, access, directory, storage, meta }) => {
|
||||
const keyValueStore = await KeyValue({ OpLog, Database, ipfs, identity, address, name, access, directory, storage, meta })
|
||||
const { events, log } = keyValueStore
|
||||
|
||||
const queue = new PQueue({ concurrency: 1 })
|
||||
|
@ -1,5 +1,5 @@
|
||||
const KeyValue = async ({ OpLog, Database, ipfs, identity, address, name, accessController, directory, storage, meta, syncAutomatically }) => {
|
||||
const database = await Database({ OpLog, ipfs, identity, address, name, accessController, directory, storage, meta, syncAutomatically })
|
||||
const KeyValue = async ({ OpLog, Database, ipfs, identity, address, name, access, directory, storage, meta, syncAutomatically }) => {
|
||||
const database = await Database({ OpLog, ipfs, identity, address, name, access, directory, storage, meta, syncAutomatically })
|
||||
|
||||
const { addOperation, log } = database
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import pathJoin from './utils/path-join.js'
|
||||
import * as Block from 'multiformats/block'
|
||||
import * as dagCbor from '@ipld/dag-cbor'
|
||||
import { sha256 } from 'multiformats/hashes/sha2'
|
||||
@ -9,17 +8,17 @@ const hasher = sha256
|
||||
const hashStringEncoding = base58btc
|
||||
|
||||
// Creates a DB manifest file and saves it in IPFS
|
||||
export default async (storage, name, type, accessControllerAddress, { meta } = {}) => {
|
||||
export default async ({ storage, name, type, accessController, meta }) => {
|
||||
if (!storage) throw new Error('storage is required')
|
||||
if (!name) throw new Error('name is required')
|
||||
if (!type) throw new Error('type is required')
|
||||
if (!accessControllerAddress) throw new Error('accessControllerAddress is required')
|
||||
if (!accessController) throw new Error('accessController is required')
|
||||
|
||||
const manifest = Object.assign(
|
||||
{
|
||||
name,
|
||||
type,
|
||||
accessController: pathJoin('/ipfs', accessControllerAddress)
|
||||
accessController
|
||||
},
|
||||
// meta field is only added to manifest if meta parameter is defined
|
||||
meta !== undefined ? { meta } : {}
|
||||
|
9
src/utils/ensure-ac-address.js
Normal file
9
src/utils/ensure-ac-address.js
Normal file
@ -0,0 +1,9 @@
|
||||
import path from 'path'
|
||||
|
||||
// Make sure the given address has '/_access' as the last part
|
||||
export default address => {
|
||||
const suffix = address.toString().split('/').pop()
|
||||
return suffix === '_access'
|
||||
? address
|
||||
: path.join(address, '/_access')
|
||||
}
|
@ -1,143 +1,138 @@
|
||||
// import assert from 'assert'
|
||||
// import rmrf from 'rimraf'
|
||||
// import Web3 from 'web3'
|
||||
// // import Web3 from 'web3'
|
||||
// import OrbitDB from '../../src/OrbitDB.js'
|
||||
|
||||
// import IdentityProvider from 'orbit-db-identity-provider'
|
||||
// import Keystore from 'orbit-db-keystore'
|
||||
// import AccessControllers from 'orbit-db-access-controllers'
|
||||
// import ContractAccessController from 'orbit-db-access-controllers/contract'
|
||||
// import ganache from 'ganache-cli'
|
||||
// import Access from './Access.json' assert {type: "json"}
|
||||
|
||||
// // Include test utilities
|
||||
// import {
|
||||
// config,
|
||||
// startIpfs,
|
||||
// stopIpfs,
|
||||
// testAPIs
|
||||
// } from 'orbit-db-test-utils'
|
||||
|
||||
//
|
||||
// // import IdentityProvider from 'orbit-db-identity-provider'
|
||||
// import Keystore from '../../src/key-store.js'
|
||||
// import AccessControllers from '../../src/access-controllers/index.js'
|
||||
// // import ContractAccessController from 'orbit-db-access-controllers/contract'
|
||||
// // import ganache from 'ganache-cli'
|
||||
// // import Access from './Access.json' assert {type: "json"}
|
||||
// import config from '../config.js'
|
||||
// import connectPeers from '../utils/connect-nodes.js'
|
||||
//
|
||||
// const abi = Access.abi
|
||||
// const bytecode = Access.bytecode
|
||||
// const dbPath1 = './orbitdb/tests/orbitdb-access-controller/1'
|
||||
// const dbPath2 = './orbitdb/tests/orbitdb-access-controller/2'
|
||||
|
||||
// Object.keys(testAPIs).forEach(API => {
|
||||
// describe(`orbit-db - Access Controller Handlers (${API})`, function () {
|
||||
// this.timeout(config.timeout)
|
||||
|
||||
// let web3, contract, ipfsd1, ipfsd2, ipfs1, ipfs2, id1, id2
|
||||
// let orbitdb1, orbitdb2
|
||||
|
||||
// before(async () => {
|
||||
// rmrf.sync(dbPath1)
|
||||
// rmrf.sync(dbPath2)
|
||||
// ipfsd1 = await startIpfs(API, config.daemon1)
|
||||
// ipfsd2 = await startIpfs(API, config.daemon2)
|
||||
// ipfs1 = ipfsd1.api
|
||||
// ipfs2 = ipfsd2.api
|
||||
|
||||
// const keystore1 = new Keystore(dbPath1 + '/keys')
|
||||
// const keystore2 = new Keystore(dbPath2 + '/keys')
|
||||
|
||||
// id1 = await IdentityProvider.createIdentity({ id: 'A', keystore: keystore1 })
|
||||
// id2 = await IdentityProvider.createIdentity({ id: 'B', keystore: keystore2 })
|
||||
|
||||
// orbitdb1 = await OrbitDB.createInstance(ipfs1, {
|
||||
// AccessControllers: AccessControllers,
|
||||
// directory: dbPath1,
|
||||
// identity: id1
|
||||
// })
|
||||
|
||||
// orbitdb2 = await OrbitDB.createInstance(ipfs2, {
|
||||
// AccessControllers: AccessControllers,
|
||||
// directory: dbPath2,
|
||||
// identity: id2
|
||||
// })
|
||||
//
|
||||
// describe('Access Controller Handlers', function () {
|
||||
// this.timeout(config.timeout)
|
||||
//
|
||||
// let ipfs1, ipfs2
|
||||
// let orbitdb1, orbitdb2
|
||||
//
|
||||
// before(async () => {
|
||||
// ipfs1 = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
|
||||
// ipfs2 = await IPFS.create({ ...config.daemon2, repo: './ipfs2' })
|
||||
// await connectPeers(ipfs1, ipfs2)
|
||||
//
|
||||
// const keystore1 = await Keystore({ path: dbPath1 + '/keys' })
|
||||
// const keystore2 = await Keystore({ path: dbPath2 + '/keys' })
|
||||
//
|
||||
// identities1 = await Identities({ keystore: keystore1 })
|
||||
// identities2 = await Identities({ keystore: keystore2 })
|
||||
//
|
||||
// testIdentity1 = await identities1.createIdentity({ id: 'userA' })
|
||||
// testIdentity2 = await identities2.createIdentity({ id: 'userB' })
|
||||
//
|
||||
// orbitdb1 = await OrbitDB({ ipfs: ipfs1, identity: testIdentity1, directory: dbPath1 })
|
||||
// orbitdb2 = await OrbitDB({ ipfs: ipfs2, identity: testIdentity2, directory: dbPath2 })
|
||||
// })
|
||||
//
|
||||
// after(async () => {
|
||||
// if (orbitdb1) {
|
||||
// await orbitdb1.stop()
|
||||
// }
|
||||
//
|
||||
// if (orbitdb2) {
|
||||
// await orbitdb2.stop()
|
||||
// }
|
||||
//
|
||||
// if (ipfs1) {
|
||||
// await ipfs1.stop()
|
||||
// }
|
||||
//
|
||||
// if (ipfs2) {
|
||||
// await ipfs2.stop()
|
||||
// }
|
||||
//
|
||||
// await rmrf('./orbitdb')
|
||||
// await rmrf('./ipfs1')
|
||||
// await rmrf('./ipfs2')
|
||||
// })
|
||||
//
|
||||
// describe.only('isSupported', function () {
|
||||
// it('supports default access controllers', () => {
|
||||
// assert.strictEqual(AccessControllers.isSupported('ipfs'), true)
|
||||
// assert.strictEqual(AccessControllers.isSupported('orbitdb'), true)
|
||||
// })
|
||||
|
||||
// after(async () => {
|
||||
// if (orbitdb1) { await orbitdb1.stop() }
|
||||
|
||||
// if (orbitdb2) { await orbitdb2.stop() }
|
||||
|
||||
// if (ipfsd1) { await stopIpfs(ipfsd1) }
|
||||
|
||||
// if (ipfsd2) { await stopIpfs(ipfsd2) }
|
||||
// })
|
||||
|
||||
// describe('isSupported', function () {
|
||||
// it('supports default access controllers', () => {
|
||||
// assert.strictEqual(AccessControllers.isSupported('ipfs'), true)
|
||||
// assert.strictEqual(AccessControllers.isSupported('orbitdb'), true)
|
||||
// })
|
||||
|
||||
// it('doesn\'t support smart contract access controller by default', () => {
|
||||
// assert.strictEqual(AccessControllers.isSupported(ContractAccessController.type), false)
|
||||
// })
|
||||
// })
|
||||
|
||||
// describe('addAccessController', function () {
|
||||
// it('supports added access controller', () => {
|
||||
// const options = {
|
||||
// AccessController: ContractAccessController,
|
||||
// web3: web3,
|
||||
// abi: abi
|
||||
// }
|
||||
// AccessControllers.addAccessController(options)
|
||||
// assert.strictEqual(AccessControllers.isSupported(ContractAccessController.type), true)
|
||||
// })
|
||||
// })
|
||||
|
||||
// describe('create access controllers', function () {
|
||||
// let options = {
|
||||
// AccessController: ContractAccessController
|
||||
// }
|
||||
|
||||
// before(async () => {
|
||||
// web3 = new Web3(ganache.provider())
|
||||
// const accounts = await web3.eth.getAccounts()
|
||||
// contract = await new web3.eth.Contract(abi)
|
||||
// .deploy({ data: bytecode })
|
||||
// .send({ from: accounts[0], gas: '1000000' })
|
||||
// options = Object.assign({}, options, { web3, abi, contractAddress: contract._address, defaultAccount: accounts[0] })
|
||||
// AccessControllers.addAccessController(options)
|
||||
// })
|
||||
|
||||
// it('throws an error if AccessController is not defined', async () => {
|
||||
// let err
|
||||
// try {
|
||||
// AccessControllers.addAccessController({})
|
||||
// } catch (e) {
|
||||
// err = e.toString()
|
||||
// }
|
||||
// assert.strictEqual(err, 'Error: AccessController class needs to be given as an option')
|
||||
// })
|
||||
|
||||
// it('throws an error if AccessController doesn\'t define type', async () => {
|
||||
// let err
|
||||
// try {
|
||||
// AccessControllers.addAccessController({ AccessController: {} })
|
||||
// } catch (e) {
|
||||
// err = e.toString()
|
||||
// }
|
||||
// assert.strictEqual(err, 'Error: Given AccessController class needs to implement: static get type() { /* return a string */}.')
|
||||
// })
|
||||
|
||||
// it('creates a custom access controller', async () => {
|
||||
// const type = ContractAccessController.type
|
||||
// const acManifestHash = await AccessControllers.create(orbitdb1, type, options)
|
||||
// assert.notStrictEqual(acManifestHash, null)
|
||||
|
||||
// const ac = await AccessControllers.resolve(orbitdb1, acManifestHash, options)
|
||||
// assert.strictEqual(ac.type, type)
|
||||
// })
|
||||
|
||||
// it('removes the custom access controller', async () => {
|
||||
// AccessControllers.removeAccessController(ContractAccessController.type)
|
||||
// assert.strictEqual(AccessControllers.isSupported(ContractAccessController.type), false)
|
||||
// })
|
||||
//
|
||||
// it('doesn\'t support smart contract access controller by default', () => {
|
||||
// assert.strictEqual(AccessControllers.isSupported(ContractAccessController.type), false)
|
||||
// })
|
||||
// })
|
||||
//
|
||||
// // describe('addAccessController', function () {
|
||||
// // it('supports added access controller', () => {
|
||||
// // const options = {
|
||||
// // AccessController: ContractAccessController,
|
||||
// // web3: web3,
|
||||
// // abi: abi
|
||||
// // }
|
||||
// // AccessControllers.addAccessController(options)
|
||||
// // assert.strictEqual(AccessControllers.isSupported(ContractAccessController.type), true)
|
||||
// // })
|
||||
// // })
|
||||
// //
|
||||
// // describe('create access controllers', function () {
|
||||
// // let options = {
|
||||
// // AccessController: ContractAccessController
|
||||
// // }
|
||||
// //
|
||||
// // before(async () => {
|
||||
// // web3 = new Web3(ganache.provider())
|
||||
// // const accounts = await web3.eth.getAccounts()
|
||||
// // contract = await new web3.eth.Contract(abi)
|
||||
// // .deploy({ data: bytecode })
|
||||
// // .send({ from: accounts[0], gas: '1000000' })
|
||||
// // options = Object.assign({}, options, { web3, abi, contractAddress: contract._address, defaultAccount: accounts[0] })
|
||||
// // AccessControllers.addAccessController(options)
|
||||
// // })
|
||||
// //
|
||||
// // it('throws an error if AccessController is not defined', async () => {
|
||||
// // let err
|
||||
// // try {
|
||||
// // AccessControllers.addAccessController({})
|
||||
// // } catch (e) {
|
||||
// // err = e.toString()
|
||||
// // }
|
||||
// // assert.strictEqual(err, 'Error: AccessController class needs to be given as an option')
|
||||
// // })
|
||||
// //
|
||||
// // it('throws an error if AccessController doesn\'t define type', async () => {
|
||||
// // let err
|
||||
// // try {
|
||||
// // AccessControllers.addAccessController({ AccessController: {} })
|
||||
// // } catch (e) {
|
||||
// // err = e.toString()
|
||||
// // }
|
||||
// // assert.strictEqual(err, 'Error: Given AccessController class needs to implement: static get type() { /* return a string */}.')
|
||||
// // })
|
||||
// //
|
||||
// // it('creates a custom access controller', async () => {
|
||||
// // const type = ContractAccessController.type
|
||||
// // const acManifestHash = await AccessControllers.create(orbitdb1, type, options)
|
||||
// // assert.notStrictEqual(acManifestHash, null)
|
||||
// //
|
||||
// // const ac = await AccessControllers.resolve(orbitdb1, acManifestHash, options)
|
||||
// // assert.strictEqual(ac.type, type)
|
||||
// // })
|
||||
// //
|
||||
// // it('removes the custom access controller', async () => {
|
||||
// // AccessControllers.removeAccessController(ContractAccessController.type)
|
||||
// // assert.strictEqual(AccessControllers.isSupported(ContractAccessController.type), false)
|
||||
// // })
|
||||
// // })
|
||||
// })
|
||||
|
@ -17,6 +17,7 @@ describe('IPFSAccessController', function () {
|
||||
let keystore1, keystore2
|
||||
let identities1, identities2
|
||||
let testIdentity1, testIdentity2
|
||||
let orbitdb1, orbitdb2
|
||||
|
||||
before(async () => {
|
||||
ipfs1 = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
|
||||
@ -31,6 +32,9 @@ describe('IPFSAccessController', function () {
|
||||
|
||||
testIdentity1 = await identities1.createIdentity({ id: 'userA' })
|
||||
testIdentity2 = await identities2.createIdentity({ id: 'userB' })
|
||||
|
||||
orbitdb1 = { ipfs: ipfs1, identity: testIdentity1 }
|
||||
orbitdb2 = { ipfs: ipfs2, identity: testIdentity2 }
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
@ -57,59 +61,72 @@ describe('IPFSAccessController', function () {
|
||||
|
||||
let accessController
|
||||
|
||||
before(async () => {
|
||||
accessController = await IPFSAccessController({
|
||||
ipfs: ipfs1,
|
||||
identities: identities1,
|
||||
identity: testIdentity1
|
||||
describe('Default write access', () => {
|
||||
before(async () => {
|
||||
accessController = await IPFSAccessController()({
|
||||
orbitdb: orbitdb1,
|
||||
identities: identities1
|
||||
})
|
||||
})
|
||||
|
||||
it('creates an access controller', () => {
|
||||
notStrictEqual(accessController, null)
|
||||
notStrictEqual(accessController, undefined)
|
||||
})
|
||||
|
||||
it('sets the controller type', () => {
|
||||
strictEqual(accessController.type, 'ipfs')
|
||||
})
|
||||
|
||||
it('sets default write', async () => {
|
||||
deepStrictEqual(accessController.write, [testIdentity1.id])
|
||||
})
|
||||
|
||||
it('user with write access can append', async () => {
|
||||
const mockEntry = {
|
||||
identity: testIdentity1.hash,
|
||||
v: 1
|
||||
// ...
|
||||
// doesn't matter what we put here, only identity is used for the check
|
||||
}
|
||||
const canAppend = await accessController.canAppend(mockEntry)
|
||||
strictEqual(canAppend, true)
|
||||
})
|
||||
|
||||
it('user without write cannot append', async () => {
|
||||
const mockEntry = {
|
||||
identity: testIdentity2.hash,
|
||||
v: 1
|
||||
// ...
|
||||
// doesn't matter what we put here, only identity is used for the check
|
||||
}
|
||||
const canAppend = await accessController.canAppend(mockEntry)
|
||||
strictEqual(canAppend, false)
|
||||
})
|
||||
|
||||
it('replicates the access controller', async () => {
|
||||
const replicatedAccessController = await IPFSAccessController()({
|
||||
orbitdb: orbitdb2,
|
||||
identities: identities2,
|
||||
address: accessController.address
|
||||
})
|
||||
|
||||
strictEqual(replicatedAccessController.type, accessController.type)
|
||||
strictEqual(replicatedAccessController.address, accessController.address)
|
||||
deepStrictEqual(replicatedAccessController.write, accessController.write)
|
||||
})
|
||||
})
|
||||
|
||||
it('creates an access controller', () => {
|
||||
notStrictEqual(accessController, null)
|
||||
notStrictEqual(accessController, undefined)
|
||||
})
|
||||
|
||||
it('sets the controller type', () => {
|
||||
strictEqual(accessController.type, 'ipfs')
|
||||
})
|
||||
|
||||
it('sets default write', async () => {
|
||||
deepStrictEqual(accessController.write, [testIdentity1.id])
|
||||
})
|
||||
|
||||
it('user with write access can append', async () => {
|
||||
const mockEntry = {
|
||||
identity: testIdentity1.hash,
|
||||
v: 1
|
||||
// ...
|
||||
// doesn't matter what we put here, only identity is used for the check
|
||||
}
|
||||
const canAppend = await accessController.canAppend(mockEntry)
|
||||
strictEqual(canAppend, true)
|
||||
})
|
||||
|
||||
it('user without write cannot append', async () => {
|
||||
const mockEntry = {
|
||||
identity: testIdentity2.hash,
|
||||
v: 1
|
||||
// ...
|
||||
// doesn't matter what we put here, only identity is used for the check
|
||||
}
|
||||
const canAppend = await accessController.canAppend(mockEntry)
|
||||
strictEqual(canAppend, false)
|
||||
})
|
||||
|
||||
it('replicates the access controller', async () => {
|
||||
const replicatedAccessController = await IPFSAccessController({
|
||||
ipfs: ipfs2,
|
||||
identities: identities2,
|
||||
identity: testIdentity2,
|
||||
address: accessController.address
|
||||
describe('Write all access', () => {
|
||||
before(async () => {
|
||||
accessController = await IPFSAccessController({ write: ['*'] })({
|
||||
orbitdb: orbitdb1,
|
||||
identities: identities1
|
||||
})
|
||||
})
|
||||
|
||||
strictEqual(replicatedAccessController.type, accessController.type)
|
||||
strictEqual(replicatedAccessController.address, accessController.address)
|
||||
deepStrictEqual(replicatedAccessController.write, accessController.write)
|
||||
it('sets write to \'Anyone\'', async () => {
|
||||
deepStrictEqual(accessController.write, ['*'])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -1,336 +1,277 @@
|
||||
// import assert from 'assert'
|
||||
// import rmrf from 'rimraf'
|
||||
// import OrbitDB from '../../src/OrbitDB.js'
|
||||
// import IdentityProvider from 'orbit-db-identity-provider'
|
||||
// import Keystore from 'orbit-db-keystore'
|
||||
// import OrbitDBAccessController from 'orbit-db-access-controllers/orbitdb'
|
||||
// import AccessControllers from 'orbit-db-access-controllers'
|
||||
import { strictEqual, deepStrictEqual, notStrictEqual } from 'assert'
|
||||
import rmrf from 'rimraf'
|
||||
import OrbitDB from '../../src/OrbitDB.js'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import Keystore from '../../src/key-store.js'
|
||||
import Identities from '../../src/identities/identities.js'
|
||||
import OrbitDBAccessController from '../../src/access-controllers/orbitdb.js'
|
||||
import config from '../config.js'
|
||||
import connectPeers from '../utils/connect-nodes.js'
|
||||
|
||||
// // Include test utilities
|
||||
// import {
|
||||
// config,
|
||||
// startIpfs,
|
||||
// stopIpfs,
|
||||
// testAPIs
|
||||
// } from 'orbit-db-test-utils'
|
||||
const dbPath1 = './orbitdb/tests/orbitdb-access-controller/1'
|
||||
const dbPath2 = './orbitdb/tests/orbitdb-access-controller/2'
|
||||
|
||||
// const dbPath1 = './orbitdb/tests/orbitdb-access-controller/1'
|
||||
// const dbPath2 = './orbitdb/tests/orbitdb-access-controller/2'
|
||||
describe('OrbitDBAccessController', function () {
|
||||
this.timeout(config.timeout)
|
||||
|
||||
// Object.keys(testAPIs).forEach(API => {
|
||||
// describe(`orbit-db - OrbitDBAccessController (${API})`, function () {
|
||||
// this.timeout(config.timeout)
|
||||
let ipfs1, ipfs2
|
||||
let orbitdb1, orbitdb2
|
||||
let identities1, identities2, testIdentity1, testIdentity2
|
||||
|
||||
// let ipfsd1, ipfsd2, ipfs1, ipfs2, id1, id2
|
||||
// let orbitdb1, orbitdb2
|
||||
before(async () => {
|
||||
ipfs1 = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
|
||||
ipfs2 = await IPFS.create({ ...config.daemon2, repo: './ipfs2' })
|
||||
await connectPeers(ipfs1, ipfs2)
|
||||
|
||||
// before(async () => {
|
||||
// rmrf.sync(dbPath1)
|
||||
// rmrf.sync(dbPath2)
|
||||
// ipfsd1 = await startIpfs(API, config.daemon1)
|
||||
// ipfsd2 = await startIpfs(API, config.daemon2)
|
||||
// ipfs1 = ipfsd1.api
|
||||
// ipfs2 = ipfsd2.api
|
||||
const keystore1 = await Keystore({ path: dbPath1 + '/keys' })
|
||||
const keystore2 = await Keystore({ path: dbPath2 + '/keys' })
|
||||
|
||||
// const keystore1 = new Keystore(dbPath1 + '/keys')
|
||||
// const keystore2 = new Keystore(dbPath2 + '/keys')
|
||||
identities1 = await Identities({ ipfs: ipfs1, keystore: keystore1 })
|
||||
identities2 = await Identities({ ipfs: ipfs2, keystore: keystore2 })
|
||||
|
||||
// id1 = await IdentityProvider.createIdentity({ id: 'A', keystore: keystore1 })
|
||||
// id2 = await IdentityProvider.createIdentity({ id: 'B', keystore: keystore2 })
|
||||
testIdentity1 = await identities1.createIdentity({ id: 'userA' })
|
||||
testIdentity2 = await identities2.createIdentity({ id: 'userB' })
|
||||
|
||||
// orbitdb1 = await OrbitDB.createInstance(ipfs1, {
|
||||
// AccessControllers,
|
||||
// directory: dbPath1,
|
||||
// identity: id1
|
||||
// })
|
||||
orbitdb1 = await OrbitDB({ ipfs: ipfs1, identity: testIdentity1, directory: dbPath1, keystore: keystore1 })
|
||||
orbitdb2 = await OrbitDB({ ipfs: ipfs2, identity: testIdentity2, directory: dbPath2, keystore: keystore2 })
|
||||
})
|
||||
|
||||
// orbitdb2 = await OrbitDB.createInstance(ipfs2, {
|
||||
// AccessControllers,
|
||||
// directory: dbPath2,
|
||||
// identity: id2
|
||||
// })
|
||||
// })
|
||||
after(async () => {
|
||||
if (orbitdb1) {
|
||||
await orbitdb1.stop()
|
||||
}
|
||||
|
||||
// after(async () => {
|
||||
// if (orbitdb1) {
|
||||
// await orbitdb1.stop()
|
||||
// }
|
||||
if (orbitdb2) {
|
||||
await orbitdb2.stop()
|
||||
}
|
||||
|
||||
// if (orbitdb2) {
|
||||
// await orbitdb2.stop()
|
||||
// }
|
||||
if (ipfs1) {
|
||||
await ipfs1.stop()
|
||||
}
|
||||
|
||||
// if (ipfsd1) {
|
||||
// await stopIpfs(ipfsd1)
|
||||
// }
|
||||
if (ipfs2) {
|
||||
await ipfs2.stop()
|
||||
}
|
||||
|
||||
// if (ipfsd2) {
|
||||
// await stopIpfs(ipfsd2)
|
||||
// }
|
||||
// })
|
||||
await rmrf('./orbitdb')
|
||||
await rmrf('./ipfs1')
|
||||
await rmrf('./ipfs2')
|
||||
})
|
||||
|
||||
// describe('Constructor', function () {
|
||||
// let accessController
|
||||
describe('Default write access', function () {
|
||||
let accessController
|
||||
|
||||
// before(async () => {
|
||||
// accessController = await OrbitDBAccessController.create(orbitdb1)
|
||||
// })
|
||||
before(async () => {
|
||||
accessController = await OrbitDBAccessController()({ orbitdb: orbitdb1, identities: identities1 })
|
||||
})
|
||||
|
||||
// it('creates an access controller', () => {
|
||||
// assert.notStrictEqual(accessController, null)
|
||||
// assert.notStrictEqual(accessController, undefined)
|
||||
// })
|
||||
it('creates an access controller', () => {
|
||||
notStrictEqual(accessController, null)
|
||||
notStrictEqual(accessController, undefined)
|
||||
})
|
||||
|
||||
// it('sets the controller type', () => {
|
||||
// assert.strictEqual(accessController.type, 'orbitdb')
|
||||
// })
|
||||
it('sets the controller type', () => {
|
||||
strictEqual(accessController.type, 'orbitdb')
|
||||
})
|
||||
|
||||
// it('has OrbitDB instance', async () => {
|
||||
// assert.notStrictEqual(accessController._orbitdb, null)
|
||||
// assert.strictEqual(accessController._orbitdb.id, orbitdb1.id)
|
||||
// })
|
||||
it('sets default capabilities', async () => {
|
||||
const expected = []
|
||||
expected.admin = new Set([testIdentity1.id])
|
||||
|
||||
// it('has IPFS instance', async () => {
|
||||
// const peerId1 = await accessController._orbitdb._ipfs.id()
|
||||
// const peerId2 = await ipfs1.id()
|
||||
// assert.strictEqual(String(peerId1.id), String(peerId2.id))
|
||||
// })
|
||||
deepStrictEqual(await accessController.capabilities(), expected)
|
||||
})
|
||||
|
||||
// it('sets default capabilities', async () => {
|
||||
// assert.deepStrictEqual(accessController.capabilities, {
|
||||
// admin: new Set([id1.id])
|
||||
// })
|
||||
// })
|
||||
it('allows owner to append after creation', async () => {
|
||||
const mockEntry = {
|
||||
identity: testIdentity1.hash
|
||||
// ...
|
||||
// doesn't matter what we put here, only identity is used for the check
|
||||
}
|
||||
const canAppend = await accessController.canAppend(mockEntry)
|
||||
strictEqual(canAppend, true)
|
||||
})
|
||||
})
|
||||
|
||||
// it('allows owner to append after creation', async () => {
|
||||
// const mockEntry = {
|
||||
// identity: id1
|
||||
// // ...
|
||||
// // doesn't matter what we put here, only identity is used for the check
|
||||
// }
|
||||
// const canAppend = await accessController.canAppend(mockEntry, id1.provider)
|
||||
// assert.strictEqual(canAppend, true)
|
||||
// })
|
||||
// })
|
||||
describe('grant', function () {
|
||||
let accessController
|
||||
|
||||
// describe('grant', function () {
|
||||
// let accessController
|
||||
before(async () => {
|
||||
accessController = await OrbitDBAccessController()({ orbitdb: orbitdb1, identities: identities1, address: 'testdb/add' })
|
||||
})
|
||||
|
||||
// before(async () => {
|
||||
// accessController = new OrbitDBAccessController(orbitdb1)
|
||||
// await accessController.load('testdb/add')
|
||||
// })
|
||||
it('adds a capability', async () => {
|
||||
try {
|
||||
await accessController.grant('write', testIdentity1.id)
|
||||
} catch (e) {
|
||||
strictEqual(e, null)
|
||||
}
|
||||
|
||||
// it('loads the root access controller from IPFS', () => {
|
||||
// assert.strictEqual(accessController._db.access.type, 'ipfs')
|
||||
// assert.deepStrictEqual(accessController._db.access.write, [id1.id])
|
||||
// })
|
||||
const expected = []
|
||||
expected.admin = new Set([testIdentity1.id])
|
||||
expected.write = new Set([testIdentity1.id])
|
||||
deepStrictEqual(await accessController.capabilities(), expected)
|
||||
})
|
||||
|
||||
// it('adds a capability', async () => {
|
||||
// try {
|
||||
// await accessController.grant('write', id1.id)
|
||||
// } catch (e) {
|
||||
// assert(e, null)
|
||||
// }
|
||||
// assert.deepStrictEqual(accessController.capabilities, {
|
||||
// admin: new Set([id1.id]),
|
||||
// write: new Set([id1.id])
|
||||
// })
|
||||
// })
|
||||
it('adds more capabilities', async () => {
|
||||
try {
|
||||
await accessController.grant('read', 'ABCD')
|
||||
await accessController.grant('delete', 'ABCD')
|
||||
} catch (e) {
|
||||
strictEqual(e, null)
|
||||
}
|
||||
|
||||
// it('adds more capabilities', async () => {
|
||||
// try {
|
||||
// await accessController.grant('read', 'ABCD')
|
||||
// await accessController.grant('delete', 'ABCD')
|
||||
// } catch (e) {
|
||||
// assert.strictEqual(e, null)
|
||||
// }
|
||||
// assert.deepStrictEqual(accessController.capabilities, {
|
||||
// admin: new Set([id1.id]),
|
||||
// write: new Set([id1.id]),
|
||||
// read: new Set(['ABCD']),
|
||||
// delete: new Set(['ABCD'])
|
||||
// })
|
||||
// })
|
||||
const expected = []
|
||||
expected.admin = new Set([testIdentity1.id])
|
||||
expected.write = new Set([testIdentity1.id])
|
||||
expected.read = new Set(['ABCD'])
|
||||
expected.delete = new Set(['ABCD'])
|
||||
|
||||
// it('emit \'updated\' event when a capability was added', async () => {
|
||||
// return new Promise((resolve, reject) => {
|
||||
// accessController.on('updated', () => {
|
||||
// try {
|
||||
// assert.deepStrictEqual(accessController.capabilities, {
|
||||
// admin: new Set([id1.id]),
|
||||
// write: new Set([id1.id]),
|
||||
// read: new Set(['ABCD', 'AXES']),
|
||||
// delete: new Set(['ABCD'])
|
||||
// })
|
||||
// resolve()
|
||||
// } catch (e) {
|
||||
// reject(e)
|
||||
// }
|
||||
// })
|
||||
// accessController.grant('read', 'AXES')
|
||||
// })
|
||||
// })
|
||||
deepStrictEqual(await accessController.capabilities(), expected)
|
||||
})
|
||||
|
||||
// it('can append after acquiring capability', async () => {
|
||||
// try {
|
||||
// await accessController.grant('write', id1.id)
|
||||
// await accessController.grant('write', id2.id)
|
||||
// } catch (e) {
|
||||
// assert(e, null)
|
||||
// }
|
||||
// const mockEntry1 = {
|
||||
// identity: id1
|
||||
// }
|
||||
// const mockEntry2 = {
|
||||
// identity: id2
|
||||
// }
|
||||
// const canAppend1 = await accessController.canAppend(mockEntry1, id1.provider)
|
||||
// const canAppend2 = await accessController.canAppend(mockEntry2, id2.provider)
|
||||
// assert.strictEqual(canAppend1, true)
|
||||
// assert.strictEqual(canAppend2, true)
|
||||
// })
|
||||
// })
|
||||
it('emit \'update\' event when a capability was added', async () => {
|
||||
let update = false
|
||||
const onUpdate = (entry) => {
|
||||
update = true
|
||||
}
|
||||
|
||||
// describe('revoke', function () {
|
||||
// let accessController
|
||||
accessController.events.on('update', onUpdate)
|
||||
|
||||
// before(async () => {
|
||||
// accessController = new OrbitDBAccessController(orbitdb1)
|
||||
// await accessController.load('testdb/remove')
|
||||
// })
|
||||
await accessController.grant('read', 'AXES')
|
||||
|
||||
// it('removes a capability', async () => {
|
||||
// try {
|
||||
// await accessController.grant('write', id1.id)
|
||||
// await accessController.grant('write', 'AABB')
|
||||
// await accessController.revoke('write', 'AABB')
|
||||
// } catch (e) {
|
||||
// assert.strictEqual(e, null)
|
||||
// }
|
||||
// assert.deepStrictEqual(accessController.capabilities, {
|
||||
// admin: new Set([id1.id]),
|
||||
// write: new Set([id1.id])
|
||||
// })
|
||||
// })
|
||||
strictEqual(update, true)
|
||||
})
|
||||
|
||||
// it('can remove the creator\'s write access', async () => {
|
||||
// try {
|
||||
// await accessController.revoke('write', id1.id)
|
||||
// } catch (e) {
|
||||
// assert.strictEqual(e, null)
|
||||
// }
|
||||
// assert.deepStrictEqual(accessController.capabilities, {
|
||||
// admin: new Set([id1.id])
|
||||
// })
|
||||
// })
|
||||
it('can append after acquiring capability', async () => {
|
||||
try {
|
||||
await accessController.grant('write', testIdentity1.id)
|
||||
await accessController.grant('write', testIdentity2.id)
|
||||
} catch (e) {
|
||||
strictEqual(e, null)
|
||||
}
|
||||
|
||||
// it('can\'t remove the creator\'s admin access', async () => {
|
||||
// try {
|
||||
// await accessController.revoke('admin', id1.id)
|
||||
// } catch (e) {
|
||||
// assert.strictEqual(e, null)
|
||||
// }
|
||||
// assert.deepStrictEqual(accessController.capabilities, {
|
||||
// admin: new Set([id1.id])
|
||||
// })
|
||||
// })
|
||||
const mockEntry1 = {
|
||||
identity: testIdentity1.hash
|
||||
}
|
||||
|
||||
// it('removes more capabilities', async () => {
|
||||
// try {
|
||||
// await accessController.grant('read', 'ABCD')
|
||||
// await accessController.grant('delete', 'ABCD')
|
||||
// await accessController.grant('write', id1.id)
|
||||
// await accessController.revoke('read', 'ABCDE')
|
||||
// await accessController.revoke('delete', 'ABCDE')
|
||||
// } catch (e) {
|
||||
// assert.strictEqual(e, null)
|
||||
// }
|
||||
// assert.deepStrictEqual(accessController.capabilities, {
|
||||
// admin: new Set([id1.id]),
|
||||
// delete: new Set(['ABCD']),
|
||||
// read: new Set(['ABCD']),
|
||||
// write: new Set([id1.id])
|
||||
// })
|
||||
// })
|
||||
const mockEntry2 = {
|
||||
identity: testIdentity2.hash
|
||||
}
|
||||
|
||||
// it('can\'t append after revoking capability', async () => {
|
||||
// try {
|
||||
// await accessController.grant('write', id2.id)
|
||||
// await accessController.revoke('write', id2.id)
|
||||
// } catch (e) {
|
||||
// assert(e, null)
|
||||
// }
|
||||
// const mockEntry1 = {
|
||||
// identity: id1
|
||||
// }
|
||||
// const mockEntry2 = {
|
||||
// identity: id2
|
||||
// }
|
||||
// const canAppend = await accessController.canAppend(mockEntry1, id1.provider)
|
||||
// const noAppend = await accessController.canAppend(mockEntry2, id2.provider)
|
||||
// assert.strictEqual(canAppend, true)
|
||||
// assert.strictEqual(noAppend, false)
|
||||
// })
|
||||
const canAppend1 = await accessController.canAppend(mockEntry1)
|
||||
|
||||
// it('emits \'updated\' event when a capability was removed', async () => {
|
||||
// await accessController.grant('admin', 'cats')
|
||||
// await accessController.grant('admin', 'dogs')
|
||||
const accessController2 = await OrbitDBAccessController()({ orbitdb: orbitdb2, identities: identities2, address: 'testdb/add' })
|
||||
const canAppend2 = await accessController2.canAppend(mockEntry2)
|
||||
|
||||
// return new Promise((resolve, reject) => {
|
||||
// accessController.on('updated', () => {
|
||||
// try {
|
||||
// assert.deepStrictEqual(accessController.capabilities, {
|
||||
// admin: new Set([id1.id, 'dogs']),
|
||||
// delete: new Set(['ABCD']),
|
||||
// read: new Set(['ABCD']),
|
||||
// write: new Set([id1.id])
|
||||
// })
|
||||
// resolve()
|
||||
// } catch (e) {
|
||||
// reject(e)
|
||||
// }
|
||||
// })
|
||||
// accessController.revoke('admin', 'cats')
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
strictEqual(canAppend1, true)
|
||||
strictEqual(canAppend2, true)
|
||||
})
|
||||
})
|
||||
|
||||
// describe('save and load', function () {
|
||||
// let accessController, dbName
|
||||
describe('revoke', function () {
|
||||
let accessController
|
||||
|
||||
// before(async () => {
|
||||
// dbName = 'testdb-load-' + new Date().getTime()
|
||||
// accessController = new OrbitDBAccessController(orbitdb1)
|
||||
// await accessController.load(dbName)
|
||||
// await accessController.grant('write', 'A')
|
||||
// await accessController.grant('write', 'B')
|
||||
// await accessController.grant('write', 'C')
|
||||
// await accessController.grant('write', 'C') // double entry
|
||||
// await accessController.grant('another', 'AA')
|
||||
// await accessController.grant('another', 'BB')
|
||||
// await accessController.revoke('another', 'AA')
|
||||
// await accessController.grant('admin', id1.id)
|
||||
// return new Promise((resolve) => {
|
||||
// // Test that the access controller emits 'updated' after it was loaded
|
||||
// accessController.on('updated', () => resolve())
|
||||
// accessController.load(accessController.address)
|
||||
// })
|
||||
// })
|
||||
before(async () => {
|
||||
accessController = await OrbitDBAccessController()({ orbitdb: orbitdb1, identities: identities1, address: 'testdb/remove' })
|
||||
})
|
||||
|
||||
// it('has the correct database address for the internal db', async () => {
|
||||
// const addr = accessController._db.address.toString().split('/')
|
||||
// assert.strictEqual(addr[addr.length - 1], '_access')
|
||||
// assert.strictEqual(addr[addr.length - 2], dbName)
|
||||
// })
|
||||
it('removes a capability', async () => {
|
||||
try {
|
||||
await accessController.grant('write', testIdentity1.id)
|
||||
await accessController.grant('write', 'AABB')
|
||||
await accessController.revoke('write', 'AABB')
|
||||
} catch (e) {
|
||||
strictEqual(e, null)
|
||||
}
|
||||
|
||||
// it('has correct capabalities', async () => {
|
||||
// assert.deepStrictEqual(accessController.get('admin'), new Set([id1.id]))
|
||||
// assert.deepStrictEqual(accessController.get('write'), new Set(['A', 'B', 'C']))
|
||||
// assert.deepStrictEqual(accessController.get('another'), new Set(['BB']))
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
// // TODO: use two separate peers for testing the AC
|
||||
// // TODO: add tests for revocation correctness with a database (integration tests)
|
||||
// })
|
||||
const expected = []
|
||||
expected.admin = new Set([testIdentity1.id])
|
||||
expected.write = new Set([testIdentity1.id])
|
||||
|
||||
deepStrictEqual(await accessController.capabilities(), expected)
|
||||
})
|
||||
|
||||
it('can remove the creator\'s write access', async () => {
|
||||
try {
|
||||
await accessController.revoke('write', testIdentity1.id)
|
||||
} catch (e) {
|
||||
strictEqual(e, null)
|
||||
}
|
||||
|
||||
const expected = []
|
||||
expected.admin = new Set([testIdentity1.id])
|
||||
|
||||
deepStrictEqual(await accessController.capabilities(), expected)
|
||||
})
|
||||
|
||||
it('can\'t remove the creator\'s admin access', async () => {
|
||||
try {
|
||||
await accessController.revoke('admin', testIdentity1.id)
|
||||
} catch (e) {
|
||||
strictEqual(e, null)
|
||||
}
|
||||
|
||||
const expected = []
|
||||
expected.admin = new Set([testIdentity1.id])
|
||||
|
||||
deepStrictEqual(await accessController.capabilities(), expected)
|
||||
})
|
||||
|
||||
it('removes more capabilities', async () => {
|
||||
try {
|
||||
await accessController.grant('read', 'ABCD')
|
||||
await accessController.grant('delete', 'ABCD')
|
||||
await accessController.grant('write', testIdentity1.id)
|
||||
await accessController.revoke('read', 'ABCDE')
|
||||
await accessController.revoke('delete', 'ABCDE')
|
||||
} catch (e) {
|
||||
strictEqual(e, null)
|
||||
}
|
||||
|
||||
const expected = []
|
||||
expected.admin = new Set([testIdentity1.id])
|
||||
expected.write = new Set([testIdentity1.id])
|
||||
expected.read = new Set(['ABCD'])
|
||||
expected.delete = new Set(['ABCD'])
|
||||
|
||||
deepStrictEqual(await accessController.capabilities(), expected)
|
||||
})
|
||||
|
||||
it('can\'t append after revoking capability', async () => {
|
||||
try {
|
||||
await accessController.grant('write', testIdentity2.id)
|
||||
await accessController.revoke('write', testIdentity2.id)
|
||||
} catch (e) {
|
||||
strictEqual(e, null)
|
||||
}
|
||||
const mockEntry1 = {
|
||||
identity: testIdentity1.hash
|
||||
}
|
||||
const mockEntry2 = {
|
||||
identity: testIdentity2.hash
|
||||
}
|
||||
const canAppend = await accessController.canAppend(mockEntry1)
|
||||
const noAppend = await accessController.canAppend(mockEntry2)
|
||||
strictEqual(canAppend, true)
|
||||
strictEqual(noAppend, false)
|
||||
})
|
||||
|
||||
it('emits \'update\' event when a capability was removed', async () => {
|
||||
await accessController.grant('admin', 'cats')
|
||||
await accessController.grant('admin', 'dogs')
|
||||
|
||||
let update = false
|
||||
const onUpdate = (entry) => {
|
||||
update = true
|
||||
}
|
||||
|
||||
accessController.events.on('update', onUpdate)
|
||||
|
||||
await accessController.revoke('admin', 'cats')
|
||||
|
||||
strictEqual(update, true)
|
||||
})
|
||||
})
|
||||
})
|
||||
// TODO: use two separate peers for testing the AC
|
||||
// TODO: add tests for revocation correctness with a database (integration tests)
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { strictEqual, deepStrictEqual } from 'assert'
|
||||
import path from 'path'
|
||||
import rmrf from 'rimraf'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import Manifest from '../src/manifest.js'
|
||||
@ -24,16 +23,16 @@ describe('Manifest', () => {
|
||||
|
||||
it('creates a manifest', async () => {
|
||||
const name = 'manifest'
|
||||
const type = 'manifest-test'
|
||||
const accessController = '123'
|
||||
const expectedHash = 'zdpuAtUvd7EhN9Xu2KSCxkjG1oS1SN6EnnZ8sxvJMPiJhbQWF'
|
||||
const type = 'manifest'
|
||||
const accessController = 'test/default-access-controller'
|
||||
const expectedHash = 'zdpuAx3LaygjPHa2zsUmRoR4jQPm2WYrExsvz2gncfm62aRKv'
|
||||
const expectedManifest = {
|
||||
name,
|
||||
type,
|
||||
accessController: path.join('/ipfs', accessController)
|
||||
accessController
|
||||
}
|
||||
|
||||
const { hash, manifest } = await Manifest(storage, name, type, accessController)
|
||||
const { hash, manifest } = await Manifest({ storage, name, type, accessController })
|
||||
|
||||
strictEqual(hash, expectedHash)
|
||||
deepStrictEqual(manifest, expectedManifest)
|
||||
@ -41,12 +40,12 @@ describe('Manifest', () => {
|
||||
|
||||
it('creates a manifest with metadata', async () => {
|
||||
const name = 'manifest'
|
||||
const type = 'manifest-test'
|
||||
const accessController = '123'
|
||||
const expectedHash = 'zdpuAmNAMNnzKJ2kWgo4H42ZDG7nFCSGEWtV76UvL5dWrNweQ'
|
||||
const meta = { name, type, description: 'more information about the database' }
|
||||
const type = 'manifest'
|
||||
const accessController = 'test/default-access-controller'
|
||||
const expectedHash = 'zdpuAmegd2PpDfTQRVhGiATCkWQDvp3JygT9WksWgJkG2u313'
|
||||
const meta = { name, description: 'more information about the database' }
|
||||
|
||||
const { hash, manifest } = await Manifest(storage, name, type, accessController, { meta })
|
||||
const { hash, manifest } = await Manifest({ storage, name, type, accessController, meta })
|
||||
|
||||
strictEqual(hash, expectedHash)
|
||||
deepStrictEqual(manifest.meta, meta)
|
||||
@ -56,7 +55,7 @@ describe('Manifest', () => {
|
||||
let err
|
||||
|
||||
try {
|
||||
await Manifest()
|
||||
await Manifest({})
|
||||
} catch (e) {
|
||||
err = e.toString()
|
||||
}
|
||||
@ -68,7 +67,7 @@ describe('Manifest', () => {
|
||||
let err
|
||||
|
||||
try {
|
||||
await Manifest(storage)
|
||||
await Manifest({ storage })
|
||||
} catch (e) {
|
||||
err = e.toString()
|
||||
}
|
||||
@ -80,7 +79,7 @@ describe('Manifest', () => {
|
||||
let err
|
||||
|
||||
try {
|
||||
await Manifest(storage, 'manifest')
|
||||
await Manifest({ storage, name: 'manifest' })
|
||||
} catch (e) {
|
||||
err = e.toString()
|
||||
}
|
||||
@ -88,15 +87,15 @@ describe('Manifest', () => {
|
||||
strictEqual(err, 'Error: type is required')
|
||||
})
|
||||
|
||||
it('throws an error if accessControllerAddress is not specified', async () => {
|
||||
it('throws an error if accessController is not specified', async () => {
|
||||
let err
|
||||
|
||||
try {
|
||||
await Manifest(storage, 'manifest', 'manifest-test')
|
||||
await Manifest({ storage, name: 'manifest', type: 'manifest' })
|
||||
} catch (e) {
|
||||
err = e.toString()
|
||||
}
|
||||
|
||||
strictEqual(err, 'Error: accessControllerAddress is required')
|
||||
strictEqual(err, 'Error: accessController is required')
|
||||
})
|
||||
})
|
||||
|
115
test/orbitdb-access-controllers.test.js
Normal file
115
test/orbitdb-access-controllers.test.js
Normal file
@ -0,0 +1,115 @@
|
||||
import { strictEqual, deepStrictEqual, notStrictEqual } from 'assert'
|
||||
import rmrf from 'rimraf'
|
||||
import * as IPFS from 'ipfs-core'
|
||||
import OrbitDB, { AccessControllers } from '../src/OrbitDB.js'
|
||||
import config from './config.js'
|
||||
import pathJoin from '../src/utils/path-join.js'
|
||||
|
||||
const type = 'custom!'
|
||||
|
||||
const CustomAccessController = () => async ({ orbitdb, identities, address }) => {
|
||||
address = pathJoin('/', type, 'controller')
|
||||
|
||||
return {
|
||||
address
|
||||
}
|
||||
}
|
||||
|
||||
CustomAccessController.type = type
|
||||
|
||||
describe('Add a custom access controller', function () {
|
||||
this.timeout(5000)
|
||||
|
||||
let ipfs
|
||||
let orbitdb
|
||||
|
||||
before(async () => {
|
||||
ipfs = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
|
||||
orbitdb = await OrbitDB({ ipfs })
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
if (orbitdb) {
|
||||
await orbitdb.stop()
|
||||
}
|
||||
|
||||
if (ipfs) {
|
||||
await ipfs.stop()
|
||||
}
|
||||
|
||||
// Remove the added custom database type from OrbitDB import
|
||||
AccessControllers.remove(type)
|
||||
|
||||
await rmrf('./orbitdb')
|
||||
await rmrf('./ipfs1')
|
||||
})
|
||||
|
||||
describe('Default supported access controllers', function () {
|
||||
it('returns default supported access controllers', async () => {
|
||||
const expected = [
|
||||
'ipfs',
|
||||
'orbitdb'
|
||||
]
|
||||
|
||||
deepStrictEqual(Object.keys(AccessControllers.types), expected)
|
||||
})
|
||||
|
||||
it('throws and error if custom access controller hasn\'t been added', async () => {
|
||||
let err
|
||||
try {
|
||||
const db = await orbitdb.open('hello', { AccessController: CustomAccessController() })
|
||||
|
||||
await db.close()
|
||||
await orbitdb.open(db.address)
|
||||
} catch (e) {
|
||||
err = e
|
||||
}
|
||||
notStrictEqual(err, undefined)
|
||||
strictEqual(err.message, 'AccessController type \'custom!\' is not supported')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Custom access controller', function () {
|
||||
before(() => {
|
||||
AccessControllers.add(CustomAccessController)
|
||||
})
|
||||
|
||||
it('create a database with the custom access controller', async () => {
|
||||
const name = 'hello custom AC'
|
||||
const db = await orbitdb.open(name, { AccessController: CustomAccessController() })
|
||||
strictEqual(db.access.address, '/custom!/controller')
|
||||
})
|
||||
|
||||
it('throws and error if custom access controller already exists', async () => {
|
||||
let err
|
||||
try {
|
||||
AccessControllers.add(CustomAccessController)
|
||||
} catch (e) {
|
||||
err = e.toString()
|
||||
}
|
||||
|
||||
strictEqual(err, 'Error: Access controller \'custom!\' already added.')
|
||||
})
|
||||
|
||||
it('returns custom access controller after adding it', async () => {
|
||||
const expected = [
|
||||
'ipfs',
|
||||
'orbitdb',
|
||||
type
|
||||
]
|
||||
|
||||
deepStrictEqual(Object.keys(AccessControllers.types), expected)
|
||||
})
|
||||
|
||||
it('can be removed from supported access controllers', async () => {
|
||||
const expected = [
|
||||
'ipfs',
|
||||
'orbitdb'
|
||||
]
|
||||
|
||||
AccessControllers.remove(type)
|
||||
|
||||
deepStrictEqual(Object.keys(AccessControllers.types), expected)
|
||||
})
|
||||
})
|
||||
})
|
@ -61,7 +61,7 @@ describe('Add a custom database type', function () {
|
||||
err = e
|
||||
}
|
||||
notStrictEqual(err, undefined)
|
||||
strictEqual(err.message, 'Unspported database type: \'custom!\'')
|
||||
strictEqual(err.message, 'Unsupported database type: \'custom!\'')
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -6,6 +6,8 @@ import OrbitDB from '../src/OrbitDB.js'
|
||||
import config from './config.js'
|
||||
import waitFor from './utils/wait-for.js'
|
||||
import connectPeers from './utils/connect-nodes.js'
|
||||
import IPFSAccessController from '../src/access-controllers/ipfs.js'
|
||||
import OrbitDBAccessController from '../src/access-controllers/orbitdb.js'
|
||||
|
||||
const dbPath = './orbitdb/tests/write-permissions'
|
||||
|
||||
@ -46,7 +48,7 @@ describe('Write Permissions', function () {
|
||||
await rmrf('./ipfs2')
|
||||
})
|
||||
|
||||
it('throws an error if a peer writes to a log with default write access', async () => {
|
||||
it('throws an error if another peer writes to a log with default write access', async () => {
|
||||
let err
|
||||
let connected = false
|
||||
|
||||
@ -87,7 +89,7 @@ describe('Write Permissions', function () {
|
||||
++updateCount
|
||||
}
|
||||
|
||||
const db1 = await orbitdb1.open('write-test', { write: ['*'] })
|
||||
const db1 = await orbitdb1.open('write-test', { AccessController: IPFSAccessController({ write: ['*'] }) })
|
||||
const db2 = await orbitdb2.open(db1.address)
|
||||
|
||||
db2.events.on('join', onConnected)
|
||||
@ -111,13 +113,14 @@ describe('Write Permissions', function () {
|
||||
let updateCount = 0
|
||||
|
||||
const options = {
|
||||
// Set write access for both clients
|
||||
write: [
|
||||
orbitdb1.identity.id,
|
||||
orbitdb2.identity.id
|
||||
]
|
||||
AccessController: IPFSAccessController({
|
||||
// Set write access for both clients
|
||||
write: [
|
||||
orbitdb1.identity.id,
|
||||
orbitdb2.identity.id
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
const onConnected = async (peerId, heads) => {
|
||||
connected = true
|
||||
}
|
||||
@ -150,9 +153,11 @@ describe('Write Permissions', function () {
|
||||
let connected = false
|
||||
|
||||
const options = {
|
||||
write: [
|
||||
orbitdb1.identity.id
|
||||
]
|
||||
AccessController: IPFSAccessController({
|
||||
write: [
|
||||
orbitdb1.identity.id
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
const onConnected = async (peerId, heads) => {
|
||||
@ -179,4 +184,38 @@ describe('Write Permissions', function () {
|
||||
await db1.close()
|
||||
await db2.close()
|
||||
})
|
||||
|
||||
it('uses an OrbitDB access controller to manage access', async () => {
|
||||
let connected = false
|
||||
let updateCount = 0
|
||||
|
||||
const onConnected = async (peerId, heads) => {
|
||||
connected = true
|
||||
}
|
||||
|
||||
const onUpdate = async (entry) => {
|
||||
++updateCount
|
||||
}
|
||||
|
||||
const db1 = await orbitdb1.open('write-test', { AccessController: OrbitDBAccessController() })
|
||||
const db2 = await orbitdb2.open(db1.address, { AccessController: OrbitDBAccessController() })
|
||||
|
||||
db2.events.on('join', onConnected)
|
||||
db2.events.on('update', onUpdate)
|
||||
|
||||
await waitFor(() => connected, () => true)
|
||||
|
||||
await db1.access.grant('write', db2.identity.id)
|
||||
await db2.access.grant('write', db1.identity.id)
|
||||
|
||||
await db1.add('record 1')
|
||||
await db2.add('record 2')
|
||||
|
||||
await waitFor(() => updateCount === 2, () => true)
|
||||
|
||||
strictEqual((await db1.all()).length, (await db2.all()).length)
|
||||
|
||||
await db1.close()
|
||||
await db2.close()
|
||||
})
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user