orbit-db-access-controllers
Version:
Access Controllers for OrbitDB
135 lines (110 loc) • 3.82 kB
JavaScript
import pMapSeries from 'p-map-series'
import AccessController from './interface.js'
import ensureAddress from '../utils/ensure-ac-address.js'
const type = 'orbitdb'
export default class OrbitDBAccessController extends AccessController {
constructor (orbitdb, options) {
super()
this._orbitdb = orbitdb
this._db = null
this._options = options || {}
}
// 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
}
// 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')])
// 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
}
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
}
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()
}
}
async hasCapability (capability, identity) {
// Write keys and admins keys are allowed
const access = new Set(this.get(capability))
return access.has(identity.id) || access.has('*')
}
async grant (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()))
}
async revoke (capability, key) {
const capabilities = new Set(this._db.get(capability) || [])
capabilities.delete(key)
if (capabilities.size > 0) {
await this._db.put(capability, Array.from(capabilities.values()))
} else {
await this._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
}
}