Skip to content
This repository has been archived by the owner on Aug 9, 2021. It is now read-only.

(WIP) Feat/thread access controllers #435

Closed
wants to merge 10 commits into from
4 changes: 4 additions & 0 deletions example/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ <h5 style="padding: 20px 0px 10px 0px" id="spacePriv"> </h5>
<h3> Threads: </h3>
<input type="text" id="threadName" placeholder="Join thread by name">
<button id="joinThread" type="button" class="btn btn btn-primary" >Join thread</button>

<input type="text" id="threadMod" placeholder="Add a thread Mod">
<button id="addThreadMod" type="button" class="btn btn btn-primary" >Add thread Mod</button>

<div id="posts" style="display: none;">
<h4> Posts: </h4>
<p>
Expand Down
19 changes: 17 additions & 2 deletions example/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,17 +97,32 @@ bopen.addEventListener('click', event => {
box.spaces[window.currentSpace].joinThread(name).then(thread => {
window.currentThread = thread
thread.onNewPost(post => {
threadData.innerHTML += post.author + ': <br />' + post.message + '<br /><br />'
updateThreadData()
})
updateThreadData()
})
})

addThreadMod.addEventListener('click', () => {
const name = threadMod.value
posts.style.display = 'block'
window.currentThread.addMod(name).then(res => {
console.log(res)
})
})

window.deletePost = (el) => {
window.currentThread.deletePost(el.id).then(res => {
updateThreadData()
})
}

const updateThreadData = () => {
threadData.innerHTML = ''
window.currentThread.getPosts().then(posts => {
posts.map(post => {
threadData.innerHTML += post.author + ': <br />' + post.message + '<br /><br />'
threadData.innerHTML += post.author + ': <br />' + post.message + '<br /><br />'
threadData.innerHTML += `<button id="` + post.postId + `"onClick="window.deletePost(` + post.postId + `)" type="button" class="btn btn btn-primary" >Delete</button>` + '<br /><br />'
})
})
}
Expand Down
12 changes: 9 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,25 @@
},
"homepage": "https://github.com/3box/3box-js#readme",
"dependencies": {
"@babel/runtime": "^7.1.2",
"@babel/runtime": "^7.4.4",
"did-jwt": "^0.1.1",
"elliptic": "^6.4.1",
"ethers": "^4.0.20",
"graphql-request": "^1.8.2",
"https-did-resolver": "^0.1.0",
"ipfs": "^0.33.1",
"idb-readable-stream": "0.0.4",
"ipfs": "^0.34.4",
"ipfs-mini": "^1.1.5",
"ipfs-postmsg-proxy": "^3.1.1",
"js-sha256": "^0.9.0",
"muport-did-resolver": "^0.3.0-alpha.2",
"node-fetch": "^2.3.0",
"orbit-db": "git://github.com/orbitdb/orbit-db.git#dddb271",
"orbit-db": "git://github.com/3box/orbit-db.git#feat/legacy-create",
"orbit-db-access-controllers": "git://github.com/3box/orbit-db-access-controllers.git#feat/legacy-ac-support",
"orbit-db-cache-postmsg-proxy": "^0.1.1",
"orbit-db-identity-provider": "^0.1.0",
"orbit-db-io": "git://github.com/3box/orbit-db-io.git#feat/backwards-compatibility",
"orbit-db-pubsub": "^0.5.5",
"store": "^2.0.12",
"tweetnacl": "^1.0.1",
"tweetnacl-util": "^0.15.0"
Expand Down
26 changes: 19 additions & 7 deletions src/3box.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ const idUtils = require('./utils/id')
const config = require('./config.js')
const API = require('./api')

let AccessControllers = require('orbit-db-access-controllers')
const ThreadAccessController = require('./access/thread-open-mod-access')
const ModeratorAccessController = require('./access/moderator-access')
AccessControllers.addAccessController({ AccessController: ThreadAccessController })
AccessControllers.addAccessController({ AccessController: ModeratorAccessController })


const ADDRESS_SERVER_URL = config.address_server_url
const PINNING_NODE = config.pinning_node
const PINNING_ROOM = config.pinning_room
Expand Down Expand Up @@ -78,18 +85,23 @@ class Box {
this.pinningNode = opts.pinningNode || PINNING_NODE
this._ipfs.swarm.connect(this.pinningNode, () => {})

const keyring = this._3id.getKeyringBySpaceName(rootStoreName)
const identity = await keyring.getIdentity()
const key = keyring.getDBKey()
// const cache = (opts.iframeStore && !!cacheProxy) ? cacheProxy : null
this._orbitdb = new OrbitDB(this._ipfs, opts.orbitPath) // , { cache })
// this._orbitdb = new OrbitDB(this._ipfs, identity, opts.orbitPath) // , { cache })
this._orbitdb = new OrbitDB(this._ipfs, identity, {
AccessControllers: AccessControllers
zachferland marked this conversation as resolved.
Show resolved Hide resolved
})
globalOrbitDB = this._orbitdb

const dbKey = this._3id.getKeyringBySpaceName(rootStoreName).getDBKey()
const key = await this._orbitdb.keystore.importPrivateKey(dbKey)
this._rootStore = await this._orbitdb.feed(rootStoreName, {
key,
write: [key.getPublic('hex')]
identity,
accessController: {
write: [key.getPublic('hex')],
legacy: true
}
})
const rootStoreAddress = this._rootStore.address.toString()

this._pubsub = new Pubsub(this._ipfs, (await this._ipfs.id()).id)

const onNewPeer = async (topic, peer) => {
Expand Down
2 changes: 1 addition & 1 deletion src/3id/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class ThreeId {
async _initMuport (muportIpfs) {
let keys = this._mainKeyring.getPublicKeys()
const doc = createMuportDocument(keys.signingKey, this.managementAddress, keys.asymEncryptionKey)
let docHash = (await this._ipfs.files.add(Buffer.from(JSON.stringify(doc))))[0].hash
let docHash = (await this._ipfs.add(Buffer.from(JSON.stringify(doc))))[0].hash
this._muportDID = 'did:muport:' + docHash
this.muportFingerprint = utils.sha256Multihash(this._muportDID)
const publishToInfura = async () => {
Expand Down
13 changes: 12 additions & 1 deletion src/3id/keyring.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ const nacl = require('tweetnacl')
nacl.util = require('tweetnacl-util')
const SimpleSigner = require('did-jwt').SimpleSigner
const { sha256 } = require('../utils/index')
const EC = require('elliptic').ec
const ec = new EC('secp256k1')
const IdentityProvider = require('./orbitProvider')
const Identities = require('orbit-db-identity-provider')

Identities.addIdentityProvider(IdentityProvider)

const BASE_PATH = "m/7696500'/0'/0'"
const MM_PATH = "m/44'/60'/0'/0"
Expand Down Expand Up @@ -58,7 +64,12 @@ class Keyring {
}

getDBKey () {
return this.signingKey.privateKey.slice(2)
return ec.keyFromPrivate(this.signingKey.privateKey.slice(2))
}

async getIdentity () {
zachferland marked this conversation as resolved.
Show resolved Hide resolved
const key = this.getDBKey()
return await Identities.createIdentity({ type: `3ID`, pubKey: key.getPublic('hex')})
}

getDBSalt () {
Expand Down
42 changes: 42 additions & 0 deletions src/3id/orbitProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// const Identities = require('orbit-db-identity-provider')
const IdentityProvider = require('orbit-db-identity-provider/src/identity-provider-interface.js')


class OrbitIdentityProvider {
constructor (options={}) {
// super(options)
console.log('create new identity')
console.log(options)
this.pubKey = options.pubKey
console.log('pubkey')
console.log(this.pubKey)
}

static get type () { return '3ID' } // return type
// return identifier of external id (eg. a public key)
async getId () {
zachferland marked this conversation as resolved.
Show resolved Hide resolved
return this.pubKey
}
//return a signature of data (signature of the OrbtiDB public key)
async signIdentity (data) {
return 'signedstring'
}

//return true if identity.sigantures are valid
static async verifyIdentity (identity) {
console.log(identity)
return true
}
}


module.exports = OrbitIdentityProvider

// Identities.addIdentityProvider(MyIdentityProvider)

// to create an identity of type `MyIdentityType`
// const identity = await Identities.createIdentity({ type: `MyIdentityType`})

// module.exports = (pubKey) => {
// return new OrbitProvider(pubkey )
// }
41 changes: 41 additions & 0 deletions src/access/moderator-access.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// 'use strict'
const { io } = require('orbit-db-access-controllers/src/utils')
const AccessController = require('orbit-db-access-controllers/src/access-controller-interface')
const type = 'moderator-access'

class ModeratorAccessController {
constructor (ipfs, options) {
// Allowed to add other mods or members
this._write = []
}

static get type () { return type }

async canAppend (entry, identityProvider) {
const entryID = entry.identity.id
const isMod = this._write.includes(entryID)

if (this._write.length === 0 || isMod ) {
const capability = entry.payload.value.capability
if (capability === 'mod') this._write.push(modAddId)
return true
}

return false
}

async load (address) {

}


async save () {
return { address: 'moderator-access' }
}

static async create (orbitdb, options = {}) {
return new ModeratorAccessController()
}
}

module.exports = ModeratorAccessController
135 changes: 135 additions & 0 deletions src/access/thread-open-mod-access.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
'use strict'

const pMapSeries = require('p-map-series')
const AccessController = require('orbit-db-access-controllers/src/access-controller-interface')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't used?

const ensureAddress = require('orbit-db-access-controllers/src/utils/ensure-ac-address')
const entryIPFS = require('ipfs-log/src/entry')

const type = 'thread-access'

// TODO need extend access controller interface?
class ThreadAccessController {
constructor (orbitdb, ipfs, options) {
// super()
this._orbitdb = orbitdb
this._db = null
this._options = options || {}
this._ipfs = ipfs
this._members = options.members
}

// 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
}

async canAppend (entry, identityProvider) {
const op = entry.payload.op
const mods = this.capabilities['mod']
const member = this.capabilities['member']

if (op === 'ADD') {
// Anyone can add entry if open thread
if (!this._members) { return true }
// Not open thread, any member or mod can add to thread
if (members.includes(entry.identity.id)) { return true }
if (mods.includes(entry.identity.id)) { return true }
}

if (op === 'DEL') {
const hash = entry.payload.value
const delEntry = await entryIPFS.fromMultihash(this._ipfs, hash)

// An id can delete their own entries
if (delEntry.identity.id === entry.identity.id) { return true }

// Mods can't delete other mods entries
if (mods.includes(delEntry.identity.id)) { return false }

// Mods can delete any other entries
if (mods.includes(entry.identity.id)) { return true }
}

return false
}

get capabilities () {
// TODO dont do this for every entry, save result, update on db change
if (this._db) {
let mod = []
let member = []
Object.entries(this._db.index).forEach(entry => {
const capability = entry[1].payload.value.capability
const id = entry[1].payload.value.id
if (capability === 'mod') mod.push(id)
if (capability === 'member') member.push(id)
})
return {mod, member}
}
return {}
}

get (capability) {
return this.capabilities[capability] || []
}

async close () {
await this._db.close()
}

async load (address) {
if (this._db) { await this._db.close() }
// Force '<address>/_access' naming for the database
// TODO should memeber and non member threads create different acess controllers
// or how to deal with threads with same name with member and non member (different names?
// address of this access controler probably should just change, then different thread name
this._db = await this._orbitdb.feed(ensureAddress(address), {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens here if address is not passed?

// Use moderator access controller
accessController: {
type: 'moderator-access',
},
sync: true
})

//TODO Move somehwere else. but try to add id opening, in case they are first to open this thread
await this._db.add(this._db.identity.id)

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 grant (capability, id) {
// TODO limit capabilities
// TODO sanitize key
await this._db.add({capability, id})
}

/* Private methods */
_onUpdate () {
// this.emit('updated')
// TODO add back
}

/* Factory */
static async create (orbitdb, options = {}) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like there are a bunch of things missing here, but I assume you have the entire model figured out?

const ac = new ThreadAccessController(orbitdb, orbitdb._ipfs, options)
// Thread address here
await ac.load(options.address || 'thread-access-controller')
return ac
}
}

module.exports = ThreadAccessController
Loading