diff --git a/.circleci/config.yml b/.circleci/config.yml index 3d11996..fa89d6f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -30,6 +30,7 @@ jobs: name: deploy-to-serverless command: | sls package + export CODE_VERSION=${CIRCLE_SHA1:0:7} if [ "${CIRCLE_BRANCH}" == "develop" ]; then sls deploy --stage develop fi @@ -46,4 +47,4 @@ workflows: version: 2 build-and-deploy: jobs: - - build \ No newline at end of file + - build diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d9fb45..14d8706 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## v1.2.0 - 2020-03-12 +* feat: add bunyan logger +* feat: add fetch mutliple links and root store address queries + ## v1.1.6 - 2020-01-24 * fix: don't verify EOA type signature for erc1271 contracts diff --git a/Dockerfile.offline b/Dockerfile.offline index 9c0461e..248aee9 100644 --- a/Dockerfile.offline +++ b/Dockerfile.offline @@ -12,4 +12,4 @@ COPY serverless.yml webpack.config.js .babelrc ./ EXPOSE 3000 -CMD serverless offline start --noEnvironment +CMD serverless offline start --noEnvironment --printOutput diff --git a/package-lock.json b/package-lock.json index 7de66bc..51da50d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "3box-address-server", - "version": "1.1.5", + "version": "1.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2893,6 +2893,17 @@ "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", "dev": true }, + "bunyan": { + "version": "1.8.12", + "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.12.tgz", + "integrity": "sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c=", + "requires": { + "dtrace-provider": "~0.8", + "moment": "^2.10.6", + "mv": "~2", + "safe-json-stringify": "~1" + } + }, "cacache": { "version": "12.0.3", "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz", @@ -3885,6 +3896,23 @@ "create-hmac": "^1.1.4" } }, + "dtrace-provider": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", + "integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==", + "optional": true, + "requires": { + "nan": "^2.14.0" + }, + "dependencies": { + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "optional": true + } + } + }, "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", @@ -7207,6 +7235,16 @@ "requires": { "browserify-sha3": "^0.0.4", "sha3": "^1.2.2" + }, + "dependencies": { + "sha3": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/sha3/-/sha3-1.2.6.tgz", + "integrity": "sha512-KgLGmJGrmNB4JWVsAV11Yk6KbvsAiygWJc7t5IebWva/0NukNrjJqhtKhzy3Eiv2AKuGvhZZt7dt1mDo7HkoiQ==", + "requires": { + "nan": "2.13.2" + } + } } }, "keyv": { @@ -7881,6 +7919,12 @@ "integrity": "sha512-ST0PnThzWKcgSLyc+ugLVql45PvESt3Ul/wrdV/OPc/6Pr8dbLAIJsN1cIp41FLzbN+srVTNIRn+5Cju0nyV6A==", "dev": true }, + "moment": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==", + "optional": true + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -7978,6 +8022,41 @@ "resolved": "https://registry.npmjs.org/murmurhash3js-revisited/-/murmurhash3js-revisited-3.0.0.tgz", "integrity": "sha512-/sF3ee6zvScXMb1XFJ8gDsSnY+X8PbOyjIuBhtgis10W2Jx4ZjIhikUCIF9c4gpJxVnQIsPAFrSwTCuAjicP6g==" }, + "mv": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", + "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", + "optional": true, + "requires": { + "mkdirp": "~0.5.1", + "ncp": "~2.0.0", + "rimraf": "~2.4.0" + }, + "dependencies": { + "glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "optional": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "rimraf": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", + "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", + "optional": true, + "requires": { + "glob": "^6.0.1" + } + } + } + }, "nan": { "version": "2.13.2", "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", @@ -8013,6 +8092,12 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "ncp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", + "optional": true + }, "neo-async": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", @@ -9344,6 +9429,12 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" }, + "safe-json-stringify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", + "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", + "optional": true + }, "safe-regex": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", @@ -9630,14 +9721,6 @@ "safe-buffer": "^5.0.1" } }, - "sha3": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/sha3/-/sha3-1.2.3.tgz", - "integrity": "sha512-sOWDZi8cDBRkLfWOw18wvJyNblXDHzwMGnRWut8zNNeIeLnmMRO17bjpLc7OzMuj1ASUgx2IyohzUCAl+Kx5vA==", - "requires": { - "nan": "2.13.2" - } - }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", diff --git a/package.json b/package.json index a2e3ef8..e795787 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "3box-address-server", - "version": "1.1.6", + "version": "1.2.0", "description": "Keeps track of the root store addresses to the user managed data platform", "main": "index.js", "scripts": { @@ -42,6 +42,7 @@ "dependencies": { "3id-resolver": "^0.0.5", "aws-sdk": "^2.574.0", + "bunyan": "^1.8.12", "cids": "0.7.1", "did-jwt": "^0.2.0", "did-resolver": "0.0.6", diff --git a/serverless.yml b/serverless.yml index 5a3c8c0..8516ab8 100644 --- a/serverless.yml +++ b/serverless.yml @@ -19,6 +19,7 @@ provider: SECRETS: ${self:custom.kmsSecrets.secrets.SECRETS} IPFS_PATH: /ipfs AWS_BUCKET_NAME: ${self:custom.awsBucketName.${self:provider.stage}} + CODE_VERSION: ${env:CODE_VERSION} # Use the serverless-webpack plugin to transpile ES6 plugins: diff --git a/src/api/__tests__/root_store_addresses_post.test.js b/src/api/__tests__/root_store_addresses_post.test.js index da71c42..6defadb 100644 --- a/src/api/__tests__/root_store_addresses_post.test.js +++ b/src/api/__tests__/root_store_addresses_post.test.js @@ -31,6 +31,15 @@ describe('RootStoreAddressesPost', () => { } else if (did === did4) { return { root_store_address: rsAddress4 } } + }), + getMultiple: jest.fn().mockImplementation(dids => { + return dids.map((did) => { + const rsAddressRow = addressMgrMock.get(did) + if (rsAddressRow) { + rsAddressRow.did = did + return rsAddressRow + } + }).filter(rsAddressRow => !!rsAddressRow) }) } linkMgrMock = { @@ -40,6 +49,15 @@ describe('RootStoreAddressesPost', () => { } else if (addr === address2) { return { did: did2 } } + }), + getMultiple: jest.fn().mockImplementation(addresses => { + return addresses.map((addr) => { + const didRow = linkMgrMock.get(addr) + if (didRow) { + didRow.address = addr + return didRow + } + }).filter(didRow => !!didRow) }) } sut = new RootStoreAddressesPost(addressMgrMock, linkMgrMock) diff --git a/src/api/link_delete.js b/src/api/link_delete.js index 67a3f55..18552cb 100644 --- a/src/api/link_delete.js +++ b/src/api/link_delete.js @@ -1,7 +1,10 @@ +const { createLogger } = require("../logger") + class LinkDeleteHandler { constructor(uPortMgr, linkMgr) { this.uPortMgr = uPortMgr this.linkMgr = linkMgr + this.logger = createLogger({ name: "api.link_delete" }) } async handle(event, context, cb) { @@ -26,8 +29,10 @@ class LinkDeleteHandler { let token = await this.uPortMgr.verifyToken(body.delete_token) payload = token.payload } catch (error) { - console.log('Error on this.uportMgr.verifyToken') - console.log(error) + this.logger.error({ + msg: 'Error verifying the token', + err: error, + }) cb({ code: 401, message: 'Invalid JWT' }) return } diff --git a/src/api/root_store_address_post.js b/src/api/root_store_address_post.js index 5db0650..a0aca24 100644 --- a/src/api/root_store_address_post.js +++ b/src/api/root_store_address_post.js @@ -1,7 +1,10 @@ -class RootStoreAddressPost { +const { createLogger } = require("../logger") + +class RootStoreAddressPost{ constructor (uPortMgr, addressMgr) { this.uPortMgr = uPortMgr this.addressMgr = addressMgr + this.logger = createLogger({ name: "api.root_store_address_post" }) } async handle (event, context, cb) { @@ -25,8 +28,10 @@ class RootStoreAddressPost { let dtoken = await this.uPortMgr.verifyToken(body.address_token) payload = dtoken.payload } catch (error) { - console.log('Error on this.uportMgr.verifyToken') - console.log(error) + this.logger.error({ + msg: 'Error verifying the token', + err: error, + }) cb({ code: 401, message: 'Invalid JWT' }) return } diff --git a/src/api/root_store_addresses_post.js b/src/api/root_store_addresses_post.js index 6b0cd12..314fa66 100644 --- a/src/api/root_store_addresses_post.js +++ b/src/api/root_store_addresses_post.js @@ -19,53 +19,48 @@ class RootStoreAddressesPostHandler { return } - let promises = [] - let resultObj = {} - const getRootStoreFromId = async id => { - const did = await this.getDID(id) - if (did) { - const rootStoreAddress = await this.getRootStore(did) - if (rootStoreAddress) { - resultObj[id] = rootStoreAddress - } + const idDidMap = await this.getDIDs(body.identities) + const didRootStoreMap = await this.getRootStores(Object.values(idDidMap)) + + const rootStoreAddresses = Object.keys(idDidMap).reduce((acc, id) => { + if (didRootStoreMap[idDidMap[id]]) { + acc[id] = didRootStoreMap[idDidMap[id]] } - } - body.identities.forEach(id => { - promises.push(getRootStoreFromId(id)) - }) - await Promise.all(promises) - cb(null, { rootStoreAddresses: resultObj }) + return acc + }, {}) + cb(null, { rootStoreAddresses }) } - async getRootStore(did) { - // Get rsAddress for did from db - const rsAddress = await this.addressMgr.get(did) - if (!rsAddress) { - return null - } else { - return rsAddress.root_store_address + async getRootStores(dids) { + if (!dids || !dids.length) { + return {} } + // Get rsAddress for did from db + const rsAddressRows = await this.addressMgr.getMultiple(dids) + return rsAddressRows.reduce((acc, row) => { + acc[row.did] = row.root_store_address + return acc + }, {}) } - async getDID(id) { - // Check if id is an address or a did - let did - if (id.startsWith('0x')) { - const { error } = hexString.validate(id) - if (error) { - return null - } - const didRow = await this.linkMgr.get(id) - if (!didRow) { - return null - } - did = didRow.did - } else if (id.startsWith('did:')) { - did = id - } else { - return null + async getDIDs(ids) { + if (!ids || !ids.length) { + return {} } - return did + const { dids, addresses } = ids.reduce((acc, id) => { + if (id.startsWith('0x') && !hexString.validate(id).error) { + acc.addresses.push(id) + } else if (id.startsWith('did:')) { + acc.dids.push(id) + } + return acc + }, { dids: [], addresses: [] }) + const didRows = addresses.length == 0 ? [] : await this.linkMgr.getMultiple(addresses) + + const results = {} + didRows.forEach(row => results[row.address] = row.did) + dids.forEach(did => results[did] = did) + return results } } module.exports = RootStoreAddressesPostHandler diff --git a/src/lib/addressMgr.js b/src/lib/addressMgr.js index f32a709..aa80cec 100644 --- a/src/lib/addressMgr.js +++ b/src/lib/addressMgr.js @@ -52,6 +52,26 @@ class AddressMgr { await client.end() } } + + async getMultiple(dids) { + if (!dids || !dids.length) throw new Error('no dids') + if (!this.pgUrl) throw new Error('no pgUrl set') + + const client = new Client({ connectionString: this.pgUrl }) + + try { + await client.connect() + const res = await client.query( + `SELECT did, root_store_address FROM root_store_addresses WHERE did = ANY ($1)`, + [dids] + ) + return res.rows + } catch (e) { + throw e + } finally { + await client.end() + } + } } module.exports = AddressMgr diff --git a/src/lib/linkMgr.js b/src/lib/linkMgr.js index 2e775bc..864fbec 100644 --- a/src/lib/linkMgr.js +++ b/src/lib/linkMgr.js @@ -56,6 +56,26 @@ class LinkMgr { } } + async getMultiple(addresses) { + if (!addresses || !addresses.length) throw new Error("no addresses"); + if (!this.pgUrl) throw new Error("no pgUrl set"); + + const client = new Client({ connectionString: this.pgUrl }); + + try { + await client.connect(); + const res = await client.query( + `SELECT address, did FROM links WHERE address = ANY ($1)`, + [addresses] + ); + return res.rows + } catch (e) { + throw e; + } finally { + await client.end(); + } + } + async remove(address) { if (!address) throw new Error("no address"); if (!this.pgUrl) throw new Error("no pgUrl set"); diff --git a/src/logger.js b/src/logger.js new file mode 100644 index 0000000..df49969 --- /dev/null +++ b/src/logger.js @@ -0,0 +1,7 @@ +const bunyan = require('bunyan') + +const defaultOptions = { + codeVersion: process.env.CODE_VERSION, +} + +module.exports.createLogger = (opts) => bunyan.createLogger(Object.assign({}, defaultOptions, opts))