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

Commit

Permalink
Merge pull request #664 from 3box/release/1.14.0
Browse files Browse the repository at this point in the history
Release/1.14.0
  • Loading branch information
oed authored Dec 2, 2019
2 parents bbb311b + 97ada0f commit e9e60d6
Show file tree
Hide file tree
Showing 17 changed files with 2,466 additions and 4,317 deletions.
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -420,11 +420,10 @@ Creates a proof that links an ethereum address to the 3Box account of the user.
**Kind**: instance method of [<code>Box</code>](#Box)
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [link] | <code>Object</code> | | Optional link object with type or proof |
| [link.type] | <code>String</code> | <code>&#x27;ethereum-eoa&#x27;</code> | The type of link (default 'ethereum') |
| [link.proof] | <code>Object</code> | | Proof object, should follow [spec](https://github.com/3box/3box/blob/master/3IPs/3ip-5.md) |
| Param | Type | Description |
| --- | --- | --- |
| [link] | <code>Object</code> | Optional link object with type or proof |
| [link.proof] | <code>Object</code> | Proof object, should follow [spec](https://github.com/3box/3box/blob/master/3IPs/3ip-5.md) |
<a name="Box+removeAddressLink"></a>
Expand Down
8 changes: 7 additions & 1 deletion RELEASE-NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Release Notes
j

## v1.14.0 - 2019-12-02
* feat: Support IdentityWallet v1.0.0 🎉
* chore: use ethers v5

* fix: add 'latest' parameter to `get_code` call (note: this was moved to the 3id-blockchain-utils package)

## v1.13.2 - 2019-11-15
* fix: check for 3id provider support in a better way

Expand Down
6,177 changes: 2,222 additions & 3,955 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "3box",
"version": "1.13.2",
"version": "1.14.0",
"description": "Interact with user data",
"main": "lib/3box.js",
"directories": {
Expand Down Expand Up @@ -44,10 +44,12 @@
"homepage": "https://github.com/3box/3box-js#readme",
"dependencies": {
"3box-orbitdb-plugins": "^1.0.5",
"3id-blockchain-utils": "^0.3.2",
"3id-resolver": "^0.0.5",
"@babel/runtime": "^7.4.5",
"@ethersproject/hdnode": "^5.0.0-beta.133",
"@ethersproject/wallet": "^5.0.0-beta.133",
"did-jwt": "^0.2.0",
"ethers": "^4.0.28",
"events": "^3.0.0",
"graphql-request": "^1.8.2",
"https-did-resolver": "^0.1.0",
Expand Down Expand Up @@ -75,7 +77,7 @@
"babel-core": "7.0.0-bridge.0",
"babel-loader": "^8.0.6",
"express": "^4.17.0",
"identity-wallet": "^0.2.0",
"identity-wallet": "^1.0.0-beta.2",
"jest": "^23.6.0",
"jsdoc-to-markdown": "^5.0.0",
"standard": "^14.3.1",
Expand Down
183 changes: 66 additions & 117 deletions src/3box.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const localstorage = require('store')
const IPFS = require('ipfs')
const registerResolver = require('3id-resolver')
const { createLink, validateLink } = require('3id-blockchain-utils')

const ThreeId = require('./3id')
const Replicator = require('./replicator')
Expand All @@ -15,12 +16,6 @@ const API = require('./api')
const IPFSRepo = require('ipfs-repo')
const LevelStore = require('datastore-level')

const ACCOUNT_TYPES = {
ethereum: 'ethereum',
ethereumEOA: 'ethereum-eoa',
erc1271: 'erc1271'
}

const PINNING_NODE = config.pinning_node
const ADDRESS_SERVER_URL = config.address_server_url
const IPFS_OPTIONS = config.ipfs_options
Expand Down Expand Up @@ -92,7 +87,7 @@ class Box {
await this._3id.authenticate()
const rootstoreName = this._3id.muportFingerprint + '.root'
const key = (await this._3id.getPublicKeys(null, true)).signingKey
await this.replicator.new(rootstoreName, key, this.DID)
await this.replicator.new(rootstoreName, key, this._3id.DID, this._3id.muportDID)
this._publishRootStore(this.replicator.rootstore.address.toString())
}
this.replicator.rootstore.setIdentity(await this._3id.getOdbId())
Expand All @@ -101,6 +96,10 @@ class Box {
this._3id.events.on('new-auth-method', authData => {
this._writeRootstoreEntry(Replicator.entryTypes.AUTH_DATA, authData)
})
this._3id.events.on('new-link-proof', proof => {
this._writeAddressLink(proof)
})
this._3id.startUpdatePolling()

this.public = new PublicStore(this._3id.muportFingerprint + '.public', this._linkProfile.bind(this), this.replicator, this._3id)
this.private = new PrivateStore(this._3id.muportFingerprint + '.private', this.replicator, this._3id)
Expand Down Expand Up @@ -314,19 +313,28 @@ class Box {

async _publishRootStore (rootStoreAddress) {
// Sign rootstoreAddress
const addressToken = await this._3id.signJWT({ rootStoreAddress })
const addressToken = await this._3id.signJWT({ rootStoreAddress }, { use3ID: true })
// Store odbAddress on 3box-address-server
try {
await utils.fetchJson(this._serverUrl + '/odbAddress', {
address_token: addressToken
})
} catch (err) {
// we capture http errors (500, etc)
// see: https://github.com/3box/3box-js/pull/351
if (!err.statusCode) {
throw new Error(err)
const publish = async token => {
try {
await utils.fetchJson(this._serverUrl + '/odbAddress', {
address_token: token
})
} catch (err) {
if (err.message === 'Invalid JWT') {
// we tried to publish before address-server has access to 3ID
// so it can't verify the JWT. Retry until it is available
await new Promise(resolve => setTimeout(resolve, 300))
await publish(token)
}
// we capture http errors (500, etc)
// see: https://github.com/3box/3box-js/pull/351
if (!err.statusCode) {
throw new Error(err)
}
}
}
await publish(addressToken)
return true
}

Expand All @@ -346,42 +354,24 @@ class Box {
* @property {String} DID the DID of the user
*/
get DID () {
// TODO - update once verification service supports 3ID
return this._3id.muportDID
}

/**
* Creates a proof that links an ethereum address to the 3Box account of the user. If given proof, it will simply be added to the root store.
*
* @param {Object} [link] Optional link object with type or proof
* @param {String} [link.type='ethereum-eoa'] The type of link (default 'ethereum')
* @param {Object} [link.proof] Proof object, should follow [spec](https://github.com/3box/3box/blob/master/3IPs/3ip-5.md)
*/
async linkAddress (link = {}) {
if (link.proof) {
let valid
if (link.proof.type === ACCOUNT_TYPES.ethereumEOA) {
valid = await utils.recoverPersonalSign(link.proof.message, link.proof.signature)
} else if (link.proof.type === ACCOUNT_TYPES.erc1271) {
valid = await utils.isValidSignature(link.proof, true, this._web3provider)
} else {
throw new Error('Missing or invalid property "type" in proof')
}
if (!valid) {
throw new Error('There was an issue verifying the supplied proof: ', valid)
}
await this._writeRootstoreEntry(Replicator.entryTypes.ADDRESS_LINK, link.proof)
return
}
if (!link.type || link.type === ACCOUNT_TYPES.ethereumEOA) {
await this._writeAddressLink(link.proof)
} else {
await this._linkProfile()
}
}

async linkAccount (type = ACCOUNT_TYPES.ethereumEOA) {
console.warn('linkAccount: deprecated, please use linkAddress going forward')
await this.linkAddress(type)
}

/**
* Remove given address link, returns true if successful
*
Expand All @@ -396,7 +386,7 @@ class Box {
type: 'delete-address-link'
}
const oneHour = 60 * 60
const deleteToken = await this._3id.signJWT(payload, { expiresIn: oneHour })
const deleteToken = await this._3id.signJWT(payload, { expiresIn: oneHour, use3ID: true })

try {
await utils.fetchJson(this._serverUrl + '/linkdelete', {
Expand Down Expand Up @@ -432,11 +422,6 @@ class Box {
return Boolean(linksQuery)
}

async isAccountLinked (type = ACCOUNT_TYPES.ethereumEOA) {
console.warn('isAccountLinked: deprecated, please use isAddressLinked going forward')
return this.isAddressLinked(type)
}

/**
* Lists address links associated with this 3Box
*
Expand All @@ -453,66 +438,49 @@ class Box {
}, [])
}

async _writeAddressLink (proof) {
const validProof = validateLink(proof)
if (!validProof) {
throw new Error('tried to write invalid link proof', proof)
}
if (await this.isAddressLinked({ address: validProof.address })) return true // address already linked
await this._writeRootstoreEntry(Replicator.entryTypes.ADDRESS_LINK, proof)
await utils.fetchJson(this._serverUrl + '/link', proof)
}

async _linkProfile () {
const address = await this._3id.getAddress()
let linkData = await this._readAddressLink(address)

if (!linkData) {
const did = this.DID

let consent
try {
// TODO - this should be handled in the 3ID class
if (this._web3provider.is3idProvider) {
consent = await utils.callRpc(this._web3provider, '3id_linkManagementKey', { did })
} else {
consent = await utils.getLinkConsent(address, did, this._web3provider)
}
} catch (e) {
throw new Error('Link consent message must be signed before adding data, to link address to store')
}
let proof = await this._readAddressLink(address)

const addressType = await this._detectAddressType(address)
if (addressType === ACCOUNT_TYPES.erc1271) {
const chainId = await utils.getChainId(this._web3provider)
linkData = {
version: 1,
type: ACCOUNT_TYPES.erc1271,
chainId,
address,
message: consent.msg,
timestamp: consent.timestamp,
signature: consent.sig
if (!proof) {
if (!this._web3provider.is3idProvider) {
try {
proof = await createLink(this._3id.DID, address, this._web3provider)
} catch (e) {
throw new Error('Link consent message must be signed before adding data, to link address to store', e)
}
} else {
linkData = {
version: 1,
type: ACCOUNT_TYPES.ethereumEOA,
message: consent.msg,
signature: consent.sig,
timestamp: consent.timestamp
try {
await this._writeAddressLink(proof)
} catch (err) {
throw new Error('An error occured while publishing link:', err)
}
}
try {
await this._writeRootstoreEntry(Replicator.entryTypes.ADDRESS_LINK, linkData)
} catch (err) {
throw new Error('An error occured while publishing link:', err)
}
} else {
// Send consentSignature to 3box-address-server to link profile with ethereum address
// _writeAddressLink already does this if the other conditional is called
if (!this.hasPublishedLink[linkData.signature]) {
if (!this.hasPublishedLink[proof.signature]) {
// Don't want to publish on every call to _linkProfile
this.hasPublishedLink[linkData.signature] = true
this.hasPublishedLink[proof.signature] = true
try {
// Send consentSignature to 3box-address-server to link profile with ethereum address
await utils.fetchJson(this._serverUrl + '/link', linkData)
await utils.fetchJson(this._serverUrl + '/link', proof)
} catch (err) {
throw new Error('An error occured while publishing link:', err)
}
}
}
// Ensure we self-published our did
// TODO - is this still needed?
if (!(await this.public.get('proof_did'))) {
// we can just sign an empty JWT as a proof that we own this DID
await this.public.set('proof_did', await this._3id.signJWT(), { noLink: true })
Expand All @@ -522,16 +490,19 @@ class Box {
async _writeRootstoreEntry (type, payload) {
const cid = (await this._ipfs.dag.put(payload)).toBaseEncodedString()
await this._ipfs.pin.add(cid)
const linkExist = await this._typeCIDExists(type, cid)
if (linkExist) return
const link = {
const entryExist = await this._typeCIDExists(type, cid)
if (entryExist) return
const entry = {
type,
data: cid
}
await this.replicator.rootstore.add(link)
if (type === Replicator.entryTypes.ADDRESS_LINK) {
await utils.fetchJson(this._serverUrl + '/link', payload)
}
// the below code prevents multiple simultaneous writes,
// which orbitdb doesn't support
const prev = this._rootstoreQueue
this._rootstoreQueue = (async () => {
if (prev) await prev
await this.replicator.rootstore.add(entry)
})()
}

async _typeCIDExists (type, cid) {
Expand All @@ -549,16 +520,7 @@ class Box {

async _readAddressLinks () {
const links = await this.replicator.getAddressLinks()
const allLinks = await Promise.all(links.map(async linkObj => {
if (!linkObj.address) {
linkObj.address = utils.recoverPersonalSign(linkObj.message, linkObj.signature)
}
const isErc1271 = linkObj.type === ACCOUNT_TYPES.erc1271
if (!(await utils.isValidSignature(linkObj, isErc1271, this._web3provider))) {
return null
}
return linkObj
}))
const allLinks = await Promise.all(links.map(validateLink))
return allLinks.filter(Boolean)
}

Expand All @@ -568,19 +530,6 @@ class Box {
return links.find(link => link.address.toLowerCase() === address)
}

async _detectAddressType (address) {
try {
const bytecode = await utils.getCode(this._web3provider, address).catch(() => null)
if (!bytecode || bytecode === '0x' || bytecode === '0x0' || bytecode === '0x00') {
return ACCOUNT_TYPES.ethereumEOA
}
return ACCOUNT_TYPES.erc1271
} catch (e) {
// Throws an error assume the provider is a 3id provider only
return ACCOUNT_TYPES.ethereumEOA
}
}

async close () {
await this.replicator.stop()
}
Expand Down Expand Up @@ -643,7 +592,7 @@ function initIPFSRepo () {

async function initIPFS (ipfs, iframeStore, ipfsOptions) {
// if (!ipfs && !ipfsProxy) throw new Error('No IPFS object configured and no default available for environment')
if (!!ipfs && iframeStore) console.log('Warning: iframeStore true, orbit db cache in iframe, but the given ipfs object is being used, and may not be running in same iframe.')
if (!!ipfs && iframeStore) console.warn('Warning: iframeStore true, orbit db cache in iframe, but the given ipfs object is being used, and may not be running in same iframe.')
if (ipfs) {
return ipfs
} else {
Expand Down
Loading

0 comments on commit e9e60d6

Please sign in to comment.