diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index bdbcb52c..1b1157a1 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,5 +1,8 @@ # Release Notes +## v1.10.2 - 2019-07-25 +* feat: allow `consentSignature` to be passed as an option to `openBox` + ## v1.10.1 - 2019-07-23 * fix: support usage of multiple tabs without data loss diff --git a/package.json b/package.json index 9b712939..c0e31672 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "3box", - "version": "1.10.1", + "version": "1.10.2", "description": "Interact with user data", "main": "lib/3box.js", "directories": { diff --git a/src/3box.js b/src/3box.js index e6047239..359ef746 100644 --- a/src/3box.js +++ b/src/3box.js @@ -404,6 +404,7 @@ class Box { * @param {String} opts.pinningNode A string with an ipfs multi-address to a 3box pinning node * @param {Object} opts.ipfs A js-ipfs ipfs object * @param {String} opts.addressServer URL of the Address Server + * @param {String} opts.contentSignature A signature, provided by a client of 3box using the private keys associated with the given address, of the 3box consent message * @return {Box} the 3Box instance for the given address */ static async openBox (address, ethereumProvider, opts = {}) { diff --git a/src/3id/__tests__/3id.test.js b/src/3id/__tests__/3id.test.js index 3cd31ba0..0e5cb697 100644 --- a/src/3id/__tests__/3id.test.js +++ b/src/3id/__tests__/3id.test.js @@ -25,12 +25,16 @@ const clearLocalStorage3id = (address) => { const ADDR_1 = '0x12345' const ADDR_2 = '0xabcde' +const ADDR_3 = '0xlmnop' const ADDR_1_STATE_1 = '{"managementAddress":"0x12345","seed":"0xbc95bb0aeb7e5c7a9519ef066d4b60a944373ba1163b0c962a043bebec1579ef33e0ef4f63c0888d7a8ec95df34ada58fb739b2a4d3b44362747e6b193db9af2","spaceSeeds":{}}' const ADDR_1_STATE_2 = '{"managementAddress":"0x12345","seed":"0xbc95bb0aeb7e5c7a9519ef066d4b60a944373ba1163b0c962a043bebec1579ef33e0ef4f63c0888d7a8ec95df34ada58fb739b2a4d3b44362747e6b193db9af2","spaceSeeds":{"space1":"0xedfac8a7bcc52f33b88cfb9f310bc533f77800183beecfa49dcdf8d3b4b906502ec46533d9d7fb12eced9b04e0bdebd1c26872cf5fa759331e4c2f97ab95f450","space2":"0xedfac8a7bcc52f33b88cfb9f310bc533f77800183beecfa49dcdf8d3b4b906502ec46533d9d7fb12eced9b04e0bdebd1c26872cf5fa759331e4c2f97ab95f450"}}' const ADDR_2_STATE = '{"managementAddress":"0xabcde","seed":"0xbc95bb0aeb7e5c7a9519ef066d4b60a944373ba1163b0c962a043bebec1579ef33e0ef4f63c0888d7a8ec95df34ada58fb739b2a4d3b44362747e6b193db9af2","spaceSeeds":{}}' +const ADDR_3_STATE_1 = '{"managementAddress":"0xlmnop","seed":"0xaedd3b597a14ad1c941ca535208fabd0b44a668dd0c8156f68a823ef8d713212d356731839a354ac5b781f4b986ff54aa2cadfa3551846c9e43bfa0122f3d55b","spaceSeeds":{}}' const SPACE_1 = 'space1' const SPACE_2 = 'space2' const ETHEREUM = 'mockEthProvider' +const CONTENT_SIGNATURE_1 = '0xsomeContentSignature' +const NOT_CONTENT_SIGNATURE_1 = '0xanIncorrectSignature' const mockedUtils = require('../../utils/index') @@ -87,6 +91,36 @@ describe('3id', () => { expect(opts.consentCallback).toHaveBeenCalledWith(false) expect(mockedUtils.openBoxConsent).toHaveBeenCalledTimes(0) }) + + it('should create a new identity when passed a contentSignature', async () => { + const opts = { consentCallback: jest.fn(), contentSignature: CONTENT_SIGNATURE_1 } + const contentSignatureThreeId = await ThreeId.getIdFromEthAddress(ADDR_3, ETHEREUM, ipfs, opts) + expect(contentSignatureThreeId.serializeState()).toEqual(ADDR_3_STATE_1) + expect(contentSignatureThreeId.DID).toMatchSnapshot() + expect(opts.consentCallback).toHaveBeenCalledWith(true) + expect(mockedUtils.openBoxConsent).toHaveBeenCalledTimes(0) + expect(await resolve(contentSignatureThreeId.DID)).toMatchSnapshot() + }) + + it('should create the same identity given the same address and contentSignature', async () => { + // did is mocked, so compares serialized state + const opts = { contentSignature: CONTENT_SIGNATURE_1 } + const threeId1 = await ThreeId.getIdFromEthAddress(ADDR_3, ETHEREUM, ipfs, opts) + clearLocalStorage3id(ADDR_3) + const threeId2 = await ThreeId.getIdFromEthAddress(ADDR_3, ETHEREUM, ipfs, opts) + expect(mockedUtils.openBoxConsent).toHaveBeenCalledTimes(0) + expect(threeId1.serializeState()).toEqual(threeId2.serializeState()) + }) + + it('should NOT create the same identity given the same address but a different contentSignature', async () => { + // did is mocked, so compares serialized state + const opts = { contentSignature: NOT_CONTENT_SIGNATURE_1 } + const threeId1 = await ThreeId.getIdFromEthAddress(ADDR_3, ETHEREUM, ipfs, opts) + clearLocalStorage3id(ADDR_3) + const threeId2 = await ThreeId.getIdFromEthAddress(ADDR_3, ETHEREUM, ipfs, opts) + expect(mockedUtils.openBoxConsent).toHaveBeenCalledTimes(0) + expect(threeId1.serializeState()).not.toEqual(threeId2.serializeState()) + }) }) describe('keyring logic', () => { diff --git a/src/3id/__tests__/__snapshots__/3id.test.js.snap b/src/3id/__tests__/__snapshots__/3id.test.js.snap index c7cb6a19..cf9185af 100644 --- a/src/3id/__tests__/__snapshots__/3id.test.js.snap +++ b/src/3id/__tests__/__snapshots__/3id.test.js.snap @@ -32,6 +32,38 @@ Object { } `; +exports[`3id getIdFromEthAddress should create a new identity when passed a contentSignature 1`] = `"did:3:bafyreiacmttqrvqnlv5ig2zyxgwk2zyb2kmr5uymio6z2efmfmbmztowyq"`; + +exports[`3id getIdFromEthAddress should create a new identity when passed a contentSignature 2`] = ` +Object { + "@context": "https://w3id.org/did/v1", + "authentication": Array [ + Object { + "publicKey": "did:3:bafyreiacmttqrvqnlv5ig2zyxgwk2zyb2kmr5uymio6z2efmfmbmztowyq#signingKey", + "type": "Secp256k1SignatureAuthentication2018", + }, + ], + "id": "did:3:bafyreiacmttqrvqnlv5ig2zyxgwk2zyb2kmr5uymio6z2efmfmbmztowyq", + "publicKey": Array [ + Object { + "id": "did:3:bafyreiacmttqrvqnlv5ig2zyxgwk2zyb2kmr5uymio6z2efmfmbmztowyq#signingKey", + "publicKeyHex": "04f946a2d88587620c5f35ed454b56693f889143eb8b6a09add775323ee48eea50f975da450eb17e84bbc7789d5a5e2e81b801d518738aa2296b97085543b92162", + "type": "Secp256k1VerificationKey2018", + }, + Object { + "id": "did:3:bafyreiacmttqrvqnlv5ig2zyxgwk2zyb2kmr5uymio6z2efmfmbmztowyq#encryptionKey", + "publicKeyBase64": "Pr8/CUkw2aisoqnMSJkeOggGD51YwFR0DLrvyeWvhH8=", + "type": "Curve25519EncryptionPublicKey", + }, + Object { + "ethereumAddress": "0xlmnop", + "id": "did:3:bafyreiacmttqrvqnlv5ig2zyxgwk2zyb2kmr5uymio6z2efmfmbmztowyq#managementKey", + "type": "Secp256k1VerificationKey2018", + }, + ], +} +`; + exports[`3id keyring logic should init space keyrings correctly 1`] = `"did:3:bafyreig4qyx4p5kscxzextx6icfapqwhlryo64m6sfacy67jnuwnfyludy"`; exports[`3id keyring logic should init space keyrings correctly 2`] = ` diff --git a/src/3id/index.js b/src/3id/index.js index c474b836..9159066e 100644 --- a/src/3id/index.js +++ b/src/3id/index.js @@ -181,7 +181,12 @@ class ThreeId { if (serialized3id) { if (opts.consentCallback) opts.consentCallback(false) } else { - const sig = await utils.openBoxConsent(normalizedAddress, ethereum) + let sig + if (opts.contentSignature) { + sig = opts.contentSignature + } else { + sig = await utils.openBoxConsent(normalizedAddress, ethereum) + } if (opts.consentCallback) opts.consentCallback(true) const entropy = '0x' + utils.sha256(sig.slice(2)) const mnemonic = HDNode.entropyToMnemonic(entropy)