UNPKG

3box

Version:
579 lines (513 loc) 20.9 kB
jest.mock('../3id', () => { const randomStr = () => `${Math.floor(Math.random() * 1000000)}` const { threeIDMockFactory, didResolverMock } = require('../__mocks__/3ID') const DID1 = 'did:3:zdpuAsaK9Ysqpph' const DID2 = 'did:3:zdpuB2DcKQKNBDz3d' let loggedIn = true const logoutFn = jest.fn(() => { loggedIn = false }) const instance = (did, managementKey) => { const instance = threeIDMockFactory(did) const extend = { muportDID: did.replace('3', 'muport'), getAddress: jest.fn(async () => managementKey), logout: logoutFn, // muportFingerprint: managementKey === '0x12345' ? 'b932fe7ab' : 'ab8c73d8f', muportFingerprint: randomStr(), authenticate: jest.fn(), getDidDocument: () => { return { managementKey } }, } return Object.assign(instance, extend) } return { getIdFromEthAddress: jest.fn((address, ethProv, ipfs, { consentCallback }) => { const did = address === '0x12345' ? DID1 : DID2 //const did = `did:3:${randomStr()}` return instance(did, address) }), logoutFn, isLoggedIn: jest.fn(() => { return loggedIn }) } }) jest.mock('../publicStore', () => { return jest.fn(() => { return { _sync: jest.fn(() => '/orbitdb/Qmasdf/08a7.public'), _load: jest.fn(() => '/orbitdb/Qmasdf/08a7.public'), get: jest.fn(), set: jest.fn(), all: jest.fn(() => { return { name: 'oed', image: 'an awesome selfie' } }), close: jest.fn() } }) }) jest.mock('../privateStore', () => { return jest.fn(() => { return { _sync: jest.fn(() => '/orbitdb/Qmfdsa/08a7.private'), _load: jest.fn(() => '/orbitdb/Qmfdsa/08a7.private') } }) }) jest.mock('../space', () => { return jest.fn(name => { let isOpen = false return { _name: name, get isOpen () { return isOpen }, open: jest.fn(() => isOpen = true), joinThread: jest.fn(), _authThreads: jest.fn() } }) }) jest.mock('../replicator', () => { const randInt = max => Math.floor(Math.random() * max) return { create: jest.fn(async () => { const OdbStorage = require('orbit-db-storage-adapter') const OdbKeystore = require('orbit-db-keystore') const path = require('path') const utils = require('../utils/index') const levelDown = OdbStorage(null, {}) const keystorePath = path.join('./tmp', `/${randInt(1000000)}`, '/keystore') const keyStorage = await levelDown.createStore(keystorePath) const keystore = new OdbKeystore(keyStorage) return { start: jest.fn(), _orbitdb: { keystore: keystore }, rootstoreSyncDone: Promise.resolve(), syncDone: Promise.resolve(), getAuthData: jest.fn(() => []), getAddressLinks: jest.fn(() => []), rootstore: { setIdentity: jest.fn(), add: jest.fn(), iterator: () => { return { collect: () => [] } }, address: { toString: () => '/orbitdb/asdf/rootstore-address' }, remove: jest.fn() }, new: jest.fn(), stop: jest.fn() } }), entryTypes: { SPACE: 'space', ADDRESS_LINK: 'address-link', AUTH_DATA: 'auth-data' } } }) jest.mock('3id-blockchain-utils', () => ({ createLink: jest.fn(async (did, address, provider) => ({ message: 'I agree to stuff,' + did, signature: '0xSuchRealSig,' + address, timestamp: 111, type: 'ethereum-eoa', version: 1 })), validateLink: (proof, did) => { return 'todo' } })) jest.mock('../utils/verifier') jest.mock('../utils/index', () => { const actualUtils = jest.requireActual('../utils/index') const { didResolverMock } = require('../__mocks__/3ID') const { Resolver } = require('did-resolver') const sha256 = require('js-sha256').sha256 const { verifyJWT } = require('did-jwt') let addressMap = {} let linkmap = {} let linkNum = 0 const resolver = new Resolver({ '3': didResolverMock, muport: didResolverMock }) return { getMessageConsent: actualUtils.getMessageConsent, openBoxConsent: jest.fn(async () => '0x8726348762348723487238476238746827364872634876234876234'), fetchJson: jest.fn(async (url, body) => { const split = url.split('/') const lastPart = split[split.length - 1] let x, hash, did switch (lastPart) { case 'odbAddress': // put odbAddress const payload = (await verifyJWT(body.address_token, { resolver })).payload addressMap[payload.iss] = payload.rootStoreAddress return { status: 'success', data: {} } case 'link': // make a link if (linkNum < 2) { linkNum += 1 return Promise.reject('{ status: "error", message: "an error" }') } else { did = body.message.split(',')[1] const address = body.signature.split(',')[1] linkmap[address] = did return { status: 'success', data: { did, address } } } default: // post profileList (profile api) if(/profileList/.test(lastPart)) { return {'0x12345': { name: 'zfer', email: 'zfer@mail.com' }} } // get profile from profile api if(/profile/.test(lastPart)) { return { name: 'zfer', email: 'zfer@mail.com' } } // default is GET odbAddress if (addressMap[lastPart]) { return { status: 'success', data: { rootStoreAddress: addressMap[lastPart] } } } else if (addressMap[linkmap[lastPart]]) { return { status: 'success', data: { rootStoreAddress: addressMap[linkmap[lastPart]], did: linkmap[lastPart] } } } else { throw {"statusCode": 404, "message": "root store address not found"} } } }), sha256Multihash: jest.fn(str => { if (str === 'did:muport:Qmsdsdf87g329') return 'ab8c73d8f' return 'b932fe7ab' }), sha256 } }) jest.mock('3id-resolver', () => { const { didResolverMock } = require('../__mocks__/3ID') return { getResolver: () => ({'3': didResolverMock}) } }) jest.mock('muport-did-resolver', () => { const { didResolverMock } = require('../__mocks__/3ID') return { getResolver: () => ({'muport': didResolverMock}) } }) const testUtils = require('./testUtils') const OrbitDB = require('orbit-db') const jsdom = require('jsdom') const didJWT = require('did-jwt') const Box = require('../3box') global.window = new jsdom.JSDOM().window const { Resolver } = require('did-resolver') const AccessControllers = require('orbit-db-access-controllers') const { LegacyIPFS3BoxAccessController } = require('3box-orbitdb-plugins') AccessControllers.addAccessController({ AccessController: LegacyIPFS3BoxAccessController }) const DID1 = 'did:3:zdpuAsaK9YsqpphSBeQvfrKAjs8kF7vUX4Y3kMkMRgEQigzCt' const DID2 = 'did:3:zdpuB2DcKQKNBDz3difEYxjTupsho5VuPCLgRbRunXqhmrJaX' const randomStr = () => `${Math.floor(Math.random() * 1000000)}` const mockedUtils = require('../utils/index') const { createLink } = require('3id-blockchain-utils') const mocked3id = require('../3id') const MOCK_HASH_SERVER = 'address-server' const MOCK_PROFILE_SERVER = 'profile-server' describe('3Box', () => { let ipfs, boxOpts, ipfsBox jest.setTimeout(30000) const clearMocks = () => { mockedUtils.openBoxConsent.mockClear() mockedUtils.fetchJson.mockClear() mocked3id.getIdFromEthAddress.mockClear() mocked3id.logoutFn.mockClear() createLink.mockClear() } beforeAll(async () => { if (!ipfs) ipfs = await testUtils.initIPFS(0) const ipfsMultiAddr = (await ipfs.id()).addresses[0] if (!ipfsBox) { ipfsBox = await testUtils.initIPFS(1) } boxOpts = { ipfs: ipfsBox, orbitPath: './tmp/orbitdb1', addressServer: MOCK_HASH_SERVER, profileServer: MOCK_PROFILE_SERVER, iframeStore: false, pinningNode: ipfsMultiAddr } }) beforeEach(async () => { clearMocks() }) afterAll(async () => { await testUtils.stopIPFS(ipfs, 0) return testUtils.stopIPFS(ipfsBox, 1) }) it('Create instance of 3box works as intended', async () => { const prov = 'web3prov' const box = await Box.create(prov, boxOpts) expect(box.replicator).toBeDefined() expect(box._provider).toEqual(prov) expect(box._ipfs).toEqual(boxOpts.ipfs) }) it('openThread without being authenticated', async () => { const space = 's1' const name = 't1' const prov = 'web3prov' const box = await Box.create(prov, boxOpts) await box.openThread(space, name, {}) expect(box.spaces[space]).toBeDefined() expect(box.spaces[space].joinThread).toHaveBeenCalledWith(name, {}) }) it('authenticating works as expected', async () => { const space = 's1' const name = 't1' const prov = 'web3prov' const opts = { address: '0x12345' } const box = await Box.create(prov, boxOpts) await box.openThread(space, name, {}) await box.auth([space], opts) expect(mocked3id.getIdFromEthAddress).toHaveBeenCalledTimes(1) expect(mocked3id.getIdFromEthAddress).toHaveBeenCalledWith(opts.address, prov, boxOpts.ipfs, box.replicator._orbitdb.keystore, opts) expect(mockedUtils.fetchJson.mock.calls[0][0]).toEqual('address-server/odbAddress/0x12345') expect(box._3id.authenticate).toHaveBeenCalledTimes(1) expect(box.replicator.start).toHaveBeenCalledTimes(0) expect(box.replicator.new).toHaveBeenCalledTimes(1) expect(box.replicator.rootstore.setIdentity).toHaveBeenCalledTimes(1) expect(box.public._load).toHaveBeenCalledTimes(1) expect(box.public._load).toHaveBeenCalledWith() expect(box.private._load).toHaveBeenCalledTimes(1) expect(box.private._load).toHaveBeenCalledWith() expect(box.spaces[space]._authThreads).toHaveBeenCalledTimes(1) await box.syncDone expect(mockedUtils.fetchJson).toHaveBeenCalledTimes(2) expect(mockedUtils.fetchJson.mock.calls[1][0]).toEqual('address-server/odbAddress') expect(didJWT.decodeJWT(mockedUtils.fetchJson.mock.calls[1][1].address_token).payload.rootStoreAddress).toEqual('/orbitdb/asdf/rootstore-address') // second call to auth should only auth new spaces await box.auth(['s2'], opts) expect(box._3id.authenticate).toHaveBeenCalledTimes(2) expect(box._3id.authenticate).toHaveBeenCalledWith(['s2'], {address: "0x12345" }) }) it('should openBox correctly with normal auth flow, for new accounts', async () => { const addr = '0x12345' const prov = 'web3prov' const consentCallback = jest.fn() const opts = { ...boxOpts, consentCallback } const box = await Box.openBox(addr, prov, opts) expect(mocked3id.getIdFromEthAddress).toHaveBeenCalledTimes(1) expect(mocked3id.getIdFromEthAddress).toHaveBeenCalledWith(addr, prov, boxOpts.ipfs, box.replicator._orbitdb.keystore, opts) expect(box.replicator).toBeDefined() expect(mockedUtils.fetchJson.mock.calls[0][0]).toEqual('address-server/odbAddress/0x12345') expect(box._3id.authenticate).toHaveBeenCalledTimes(1) expect(box.replicator.start).toHaveBeenCalledTimes(0) expect(box.replicator.new).toHaveBeenCalledTimes(1) expect(box.replicator.rootstore.setIdentity).toHaveBeenCalledTimes(1) expect(box.public._load).toHaveBeenCalledTimes(1) expect(box.public._load).toHaveBeenCalledWith() expect(box.private._load).toHaveBeenCalledTimes(1) expect(box.private._load).toHaveBeenCalledWith() await box.syncDone //await new Promise((resolve, reject) => { setTimeout(resolve, 500) }) expect(mockedUtils.fetchJson).toHaveBeenCalledTimes(2) expect(mockedUtils.fetchJson.mock.calls[1][0]).toEqual('address-server/odbAddress') expect(didJWT.decodeJWT(mockedUtils.fetchJson.mock.calls[1][1].address_token).payload.rootStoreAddress).toEqual('/orbitdb/asdf/rootstore-address') return box.close() }) it('should handle error and not link profile on first call to _linkProfile', async () => { const box = await Box.openBox('0x12345','web3prov', boxOpts) const did = box._3id.DID clearMocks() // first two calls in our mock will throw an error await expect(box._linkProfile()).rejects.toMatchSnapshot() expect(box.replicator.rootstore.add).toHaveBeenCalledTimes(1) // proof // It will check the self-signed did expect(mockedUtils.fetchJson).toHaveBeenCalledTimes(1) expect(mockedUtils.fetchJson).toHaveBeenNthCalledWith(1, 'address-server/link', { message: `I agree to stuff,${did}`, signature: "0xSuchRealSig,0x12345", timestamp: 111, type: "ethereum-eoa", version: 1, }) expect(createLink).toHaveBeenCalledTimes(1) return box.close() }) it('should not call createLink if ethereum_proof in rootStore on call to _linkProfile', async () => { const boxWithLinks = await Box.openBox('0x12345', 'web3prov', boxOpts) clearMocks() boxWithLinks.public.get = jest.fn((key) => { if (key === 'proof_did') return 'proof-did' return null }) boxWithLinks._readAddressLink = jest.fn(() => { return { message: `I agree to stuff,${DID1}`, signature: "0xSuchRealSig,0x12345", timestamp: 111, type: "ethereum-eoa", version: 1, } }) // first two calls in our mock will throw an error boxWithLinks.public.set.mockClear() await expect(boxWithLinks._linkProfile()).rejects.toMatchSnapshot() expect(mockedUtils.fetchJson).toHaveBeenCalledTimes(1) // TODO now hwy this second clal as expected?? expect(mockedUtils.fetchJson).toHaveBeenNthCalledWith(1, 'address-server/link', { message: `I agree to stuff,${DID1}`, signature: "0xSuchRealSig,0x12345", timestamp: 111, type: "ethereum-eoa", version: 1, }) expect(createLink).toHaveBeenCalledTimes(0) expect(boxWithLinks.public.set).toHaveBeenCalledTimes(0) return boxWithLinks.close() }) it('should link profile on call to _linkProfile', async () => { const box = await Box.openBox('0x12345', 'web3prov', boxOpts) const did = box._3id.DID clearMocks() box.public.set.mockClear() box.public.get = jest.fn() await box._linkProfile() expect(mockedUtils.fetchJson).toHaveBeenCalledTimes(1) expect(mockedUtils.fetchJson).toHaveBeenNthCalledWith(1, 'address-server/link', { message: `I agree to stuff,${did}`, signature: "0xSuchRealSig,0x12345", timestamp: 111, type: "ethereum-eoa", version: 1, }) expect(createLink).toHaveBeenCalledTimes(1) expect(box.public.set).toHaveBeenCalledTimes(1) // did proof return box.close() }) it('should openBox correctly with normal auth flow, for existing accounts', async () => { const addr = '0x12345' const prov = 'web3prov' const consentCallback = jest.fn() const opts = { ...boxOpts, consentCallback } const box = await Box.openBox(addr, prov, opts) expect(mocked3id.getIdFromEthAddress).toHaveBeenCalledTimes(1) expect(mocked3id.getIdFromEthAddress).toHaveBeenCalledWith(addr, prov, boxOpts.ipfs, box.replicator._orbitdb.keystore, opts) expect(box.replicator).toBeDefined() expect(mockedUtils.fetchJson).toHaveBeenCalledTimes(1) expect(mockedUtils.fetchJson.mock.calls[0][0]).toEqual('address-server/odbAddress/0x12345') expect(box._3id.authenticate).toHaveBeenCalledTimes(1) expect(box._3id.authenticate).toHaveBeenCalledWith([], { authData: [], address: "0x12345" }) expect(box.replicator.start).toHaveBeenCalledTimes(1) expect(box.replicator.start).toHaveBeenCalledWith('/orbitdb/asdf/rootstore-address', box._3id.DID, { profile: true }) expect(box.replicator.new).toHaveBeenCalledTimes(0) expect(box.public._load).toHaveBeenCalledTimes(1) expect(box.public._load).toHaveBeenCalledWith() expect(box.private._load).toHaveBeenCalledTimes(1) expect(box.private._load).toHaveBeenCalledWith() await box.syncDone return box.close() }) it('should open spaces correctly', async () => { const box = await Box.openBox('0x12345','web3prov', boxOpts) clearMocks() global.console.error = jest.fn() let space1 = await box.openSpace('name1', {}) expect(space1._name).toEqual('name1') expect(space1.open).toHaveBeenCalledWith(expect.any(Object), { address: "0x12345"}) let opts = { onSyncDone: jest.fn() } let space2 = await box.openSpace('name1', opts) expect(space1).toEqual(space2) expect(opts.onSyncDone).toHaveBeenCalledTimes(1) // TODO why opening two spaces causes problem???? let space3 = await box.openSpace('name2', 'myOpts') expect(box.spaces).toEqual({ name1: space1, name2: space3 }) return box.close() }) it('should get profile (when API is used)', async () => { delete boxOpts.useCacheService const profile = await Box.getProfile('0x12345', boxOpts) expect(profile).toEqual({ name: 'zfer', email: 'zfer@mail.com' }) expect(mockedUtils.fetchJson).toHaveBeenCalledWith('profile-server/profile?address=0x12345') }) it('should get profiles (profileList) ', async () => { const profiles = await Box.getProfiles(['0x12345'], boxOpts) expect(profiles['0x12345']).toEqual({ name: 'zfer', email: 'zfer@mail.com' }) expect(mockedUtils.fetchJson) .toHaveBeenCalledWith('profile-server/profileList', { addressList: ['0x12345'], didList: [] }) }) it('should be logged in', async () => { const isLoggedIn = Box.isLoggedIn('0xabcde') expect(isLoggedIn).toEqual(true) }) it('should clear cache correctly', async () => { const box = await Box.openBox('0x12345', 'web3prov', boxOpts) await box.logout() expect(mocked3id.logoutFn).toHaveBeenCalledTimes(1) return box.close() }) it('should be logged out', async () => { const box = await Box.openBox('0x12345', 'web3prov', boxOpts) await box.logout() const isLoggedIn = Box.isLoggedIn('0xabcde') expect(isLoggedIn).toEqual(false) return box.close() }) it('should verify profiles correctly', async () => { const profile = { proof_did: 'some proof', proof_github: 'github proof url', proof_twitter: 'twitter proof claim jwt' } const userMuPort = 'did:muport:Qmsdpuhs' const userDID = 'did:3:zdpuAsaK9Ysqpph' let verifier = require('../utils/verifier') verifier.verifyDID.mockImplementationOnce(() => { throw new Error() }) expect(await Box.getVerifiedAccounts(profile)).toEqual({}) verifier.verifyDID.mockImplementationOnce(() => ({ did: userDID, muport: userMuPort})) verifier.verifyGithub.mockImplementationOnce(() => { return { username: 'test', proof: 'some url' } }) verifier.verifyTwitter.mockImplementationOnce(() => { return { username: 'test', proof: 'some url' } }) const verifiedAccounts = await Box.getVerifiedAccounts(profile) expect(verifiedAccounts).toEqual({ 'github': { 'proof': 'some url', 'username': 'test' }, 'twitter': { 'proof': 'some url', 'username': 'test' }, 'did': userDID, 'muport': userMuPort }) }) describe('verify eth', () => { let verifier = jest.requireActual('../utils/verifier') const ethProof = { consent_msg: 'Create a new 3Box profile\n\n- \nYour unique profile ID is did:muport:Qmb9E8wLqjfAqfKhideoApU5g26Yz2Q2bSp6MSZmc5WrNr', consent_signature: '0x851554733a2989555233f1845ae1a9a7a80cd080afa2cde9a5ccc21b98f0438317fed99955d1d9b32d7f94adae500e36c508a8fe976614088ad6026831f0e3261b', linked_did: 'did:muport:Qmb9E8wLqjfAqfKhideoApU5g26Yz2Q2bSp6MSZmc5WrNr' } const did = 'did:muport:Qmb9E8wLqjfAqfKhideoApU5g26Yz2Q2bSp6MSZmc5WrNr' const ethKey = '0x70509CAAf30Cd92D5f14ddcf98e84e1b38F10a4d' it('should verify a regular eth profile', async () => { const verif = await verifier.verifyEthereum(ethProof, did) expect(verif).toEqual(ethKey) }) it('should fail if you try to spoof another address', async () => { // We generate a different message and sign it from another wallet. const fakeProof = { consent_msg: 'Create a new 3Box profile\n\n-\n Your unique profile ID is did:muport:Qmb9E8wLqjfAqfKhideoApU5g26Yz2Q2bSp6MSZmc5WrNw', consent_signature: '0x1928698128a06d5a2005beaca13c42741254279a8759ce83aa16008713742e23038bc6183295f807dc1f475594094301ef02c7cf07eb319d1de08e3dae7aba9f1c', linked_did: 'did:muport:Qmb9E8wLqjfAqfKhideoApU5g26Yz2Q2bSp6MSZmc5WrNr' } try { const verif = await verifier.verifyEthereum(fakeProof, did) expect('should have failed and throw, but we got:').toEqual(verif) } catch (e) { expect(true).toBeTruthy() } }) }) })