diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 55f57ebc6e..691c4a7ceb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -288,7 +288,7 @@ below applies to all tests regardless of their nature. t.end(); // yaay, test coverage }); ``` - - An [end to end test case](./packages/cactus-test-plugin-web-service-consortium/src/test/typescript/integration/plugin-web-service-consortium/security-isolation-via-api-server-ports.ts) showcasing everything in action + - An [end to end test case](./packages/cactus-test-plugin-consortium-manual/src/test/typescript/integration/plugin-consortium-manual/security-isolation-via-api-server-ports.ts) showcasing everything in action that is being preached in this document about test automation - Focus/verify a single bug-fix/feature/etc. - Clearly separated from non-test (aka `main`) source code. @@ -353,7 +353,7 @@ for both them separately anyway: - An integration test: ```sh - npx tap --timeout=600 packages/cactus-test-plugin-web-service-consortium/src/test/typescript/integration/plugin-web-service-consortium/security-isolation-via-api-server-ports.ts + npx tap --timeout=600 packages/cactus-test-plugin-consortium-manual/src/test/typescript/integration/plugin-consortium-manual/security-isolation-via-api-server-ports.ts ``` - A unit test: diff --git a/package-lock.json b/package-lock.json index 8442d061ba..74b2065cb0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5015,9 +5015,9 @@ }, "dependencies": { "is-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", - "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", "dev": true, "requires": { "has-symbols": "^1.0.1" @@ -10898,9 +10898,9 @@ "dev": true }, "is-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", - "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", "dev": true, "requires": { "has-symbols": "^1.0.1" @@ -13110,9 +13110,9 @@ "dev": true }, "is-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", - "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", "dev": true, "requires": { "has-symbols": "^1.0.1" @@ -13176,9 +13176,9 @@ "dev": true }, "is-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", - "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", "dev": true, "requires": { "has-symbols": "^1.0.1" @@ -13352,7 +13352,8 @@ "dependencies": { "@babel/code-frame": { "version": "7.8.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", "dev": true, "requires": { "@babel/highlight": "^7.8.3" @@ -13360,7 +13361,8 @@ }, "@babel/core": { "version": "7.8.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-4XFkf8AwyrEG7Ziu3L2L0Cv+WyY47Tcsp70JFmpftbAA1K7YL/sgE9jh9HyNj08Y/U50ItUchpN0w6HxAoX1rA==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", @@ -13382,14 +13384,16 @@ "dependencies": { "source-map": { "version": "0.5.7", - "bundled": true, + "resolved": false, + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true } } }, "@babel/generator": { "version": "7.8.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-WjoPk8hRpDRqqzRpvaR8/gDUPkrnOOeuT2m8cNICJtZH6mwaCo3v0OKMI7Y6SM1pBtyijnLtAL0HDi41pf41ug==", "dev": true, "requires": { "@babel/types": "^7.8.3", @@ -13400,14 +13404,16 @@ "dependencies": { "source-map": { "version": "0.5.7", - "bundled": true, + "resolved": false, + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true } } }, "@babel/helper-builder-react-jsx": { "version": "7.8.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-JT8mfnpTkKNCboTqZsQTdGo3l3Ik3l7QIt9hh0O9DYiwVel37VoJpILKM4YFbP2euF32nkQSb+F9cUk9b7DDXQ==", "dev": true, "requires": { "@babel/types": "^7.8.3", @@ -13416,7 +13422,8 @@ }, "@babel/helper-function-name": { "version": "7.8.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", "dev": true, "requires": { "@babel/helper-get-function-arity": "^7.8.3", @@ -13426,7 +13433,8 @@ }, "@babel/helper-get-function-arity": { "version": "7.8.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", "dev": true, "requires": { "@babel/types": "^7.8.3" @@ -13434,12 +13442,14 @@ }, "@babel/helper-plugin-utils": { "version": "7.8.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==", "dev": true }, "@babel/helper-split-export-declaration": { "version": "7.8.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", "dev": true, "requires": { "@babel/types": "^7.8.3" @@ -13447,7 +13457,8 @@ }, "@babel/helpers": { "version": "7.8.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-LmU3q9Pah/XyZU89QvBgGt+BCsTPoQa+73RxAQh8fb8qkDyIfeQnmgs+hvzhTCKTzqOyk7JTkS3MS1S8Mq5yrQ==", "dev": true, "requires": { "@babel/template": "^7.8.3", @@ -13457,7 +13468,8 @@ }, "@babel/highlight": { "version": "7.8.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", "dev": true, "requires": { "chalk": "^2.0.0", @@ -13467,12 +13479,14 @@ }, "@babel/parser": { "version": "7.8.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-/V72F4Yp/qmHaTALizEm9Gf2eQHV3QyTL3K0cNfijwnMnb1L+LDlAubb/ZnSdGAVzVSWakujHYs1I26x66sMeQ==", "dev": true }, "@babel/plugin-proposal-object-rest-spread": { "version": "7.8.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-8qvuPwU/xxUCt78HocNlv0mXXo0wdh9VT1R04WU8HGOfaOob26pF+9P5/lYjN/q7DHOX1bvX60hnhOvuQUJdbA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.3", @@ -13481,7 +13495,8 @@ }, "@babel/plugin-syntax-jsx": { "version": "7.8.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-WxdW9xyLgBdefoo0Ynn3MRSkhe5tFVxxKNVdnZSh318WrG2e2jH+E9wd/++JsqcLJZPfz87njQJ8j2Upjm0M0A==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.3" @@ -13489,7 +13504,8 @@ }, "@babel/plugin-syntax-object-rest-spread": { "version": "7.8.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" @@ -13497,7 +13513,8 @@ }, "@babel/plugin-transform-destructuring": { "version": "7.8.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-H4X646nCkiEcHZUZaRkhE2XVsoz0J/1x3VVujnn96pSoGCtKPA99ZZA+va+gK+92Zycd6OBKCD8tDb/731bhgQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.3" @@ -13505,7 +13522,8 @@ }, "@babel/plugin-transform-react-jsx": { "version": "7.8.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-r0h+mUiyL595ikykci+fbwm9YzmuOrUBi0b+FDIKmi3fPQyFokWVEMJnRWHJPPQEjyFJyna9WZC6Viv6UHSv1g==", "dev": true, "requires": { "@babel/helper-builder-react-jsx": "^7.8.3", @@ -13515,7 +13533,8 @@ }, "@babel/runtime": { "version": "7.8.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-fVHx1rzEmwB130VTkLnxR+HmxcTjGzH12LYQcFFoBwakMd3aOMD4OsRN7tGG/UOYE2ektgFrS8uACAoRk1CY0w==", "dev": true, "requires": { "regenerator-runtime": "^0.13.2" @@ -13523,7 +13542,8 @@ }, "@babel/template": { "version": "7.8.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", @@ -13533,7 +13553,8 @@ }, "@babel/traverse": { "version": "7.8.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-we+a2lti+eEImHmEXp7bM9cTxGzxPmBiVJlLVD+FuuQMeeO7RaDbutbgeheDkw+Xe3mCfJHnGOWLswT74m2IPg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", @@ -13549,7 +13570,8 @@ }, "@babel/types": { "version": "7.8.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -13559,17 +13581,20 @@ }, "@types/color-name": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "dev": true }, "@types/prop-types": { "version": "15.7.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==", "dev": true }, "@types/react": { "version": "16.9.17", - "bundled": true, + "resolved": false, + "integrity": "sha512-UP27In4fp4sWF5JgyV6pwVPAQM83Fj76JOcg02X5BZcpSu5Wx+fP9RMqc2v0ssBoQIFvD5JdKY41gjJJKmw6Bg==", "dev": true, "requires": { "@types/prop-types": "*", @@ -13578,7 +13603,8 @@ }, "ansi-escapes": { "version": "4.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg==", "dev": true, "requires": { "type-fest": "^0.8.1" @@ -13586,12 +13612,14 @@ }, "ansi-regex": { "version": "5.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, "ansi-styles": { "version": "3.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -13599,27 +13627,32 @@ }, "ansicolors": { "version": "0.3.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=", "dev": true }, "arrify": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", "dev": true }, "astral-regex": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true }, "auto-bind": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-v0A231a/lfOo6kxQtmEkdBfTApvC21aJYukA8pkKnoTvVqh3Wmm7/Rwy4GBCHTTHVoLVA5qsBDDvf1XY1nIV2g==", "dev": true }, "caller-callsite": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", "dev": true, "requires": { "callsites": "^2.0.0" @@ -13627,7 +13660,8 @@ }, "caller-path": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", "dev": true, "requires": { "caller-callsite": "^2.0.0" @@ -13635,12 +13669,14 @@ }, "callsites": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", "dev": true }, "cardinal": { "version": "2.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-fMEFXYItISlU0HsIXeolHMe8VQU=", "dev": true, "requires": { "ansicolors": "~0.3.2", @@ -13649,7 +13685,8 @@ }, "chalk": { "version": "2.4.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { "ansi-styles": "^3.2.1", @@ -13659,12 +13696,14 @@ }, "ci-info": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "dev": true }, "cli-cursor": { "version": "3.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dev": true, "requires": { "restore-cursor": "^3.1.0" @@ -13672,7 +13711,8 @@ }, "cli-truncate": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", "dev": true, "requires": { "slice-ansi": "^3.0.0", @@ -13681,7 +13721,8 @@ }, "color-convert": { "version": "1.9.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "requires": { "color-name": "1.1.3" @@ -13689,12 +13730,14 @@ }, "color-name": { "version": "1.1.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, "convert-source-map": { "version": "1.7.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", "dev": true, "requires": { "safe-buffer": "~5.1.1" @@ -13702,19 +13745,22 @@ "dependencies": { "safe-buffer": { "version": "5.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true } } }, "csstype": { "version": "2.6.8", - "bundled": true, + "resolved": false, + "integrity": "sha512-msVS9qTuMT5zwAGCVm4mxfrZ18BNc6Csd0oJAtiFMZ1FAx1CCvy2+5MDmYoix63LM/6NDbNtodCiGYGmFgO0dA==", "dev": true }, "debug": { "version": "4.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { "ms": "^2.1.1" @@ -13722,47 +13768,56 @@ }, "emoji-regex": { "version": "8.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, "escape-string-regexp": { "version": "1.0.5", - "bundled": true, + "resolved": false, + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, "esprima": { "version": "4.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, "esutils": { "version": "2.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, "events-to-array": { "version": "1.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-LUH1Y+H+QA7Uli/hpNXGp1Od9/Y=", "dev": true }, "gensync": { "version": "1.0.0-beta.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", "dev": true }, "globals": { "version": "11.12.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, "has-flag": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, "import-jsx": { "version": "3.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-lTuMdQ/mRXC+xQSGPDvAg1VkODlX78j5hZv2tneJ+zuo7GH/XhUF/YVKoeF382a4jO4GYw9jgganbMhEcxwb0g==", "dev": true, "requires": { "@babel/core": "^7.5.5", @@ -13775,7 +13830,8 @@ }, "ink": { "version": "2.6.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-nD/wlSuB6WnFsFB0nUcOJdy28YvvDer3eo+gezjvZqojGA4Rx5sQpacvN//Aai83DRgwrRTyKBl5aciOcfP3zQ==", "dev": true, "requires": { "ansi-escapes": "^4.2.1", @@ -13800,7 +13856,8 @@ "dependencies": { "ansi-styles": { "version": "4.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, "requires": { "@types/color-name": "^1.1.1", @@ -13809,7 +13866,8 @@ }, "chalk": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -13818,7 +13876,8 @@ }, "color-convert": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { "color-name": "~1.1.4" @@ -13826,17 +13885,20 @@ }, "color-name": { "version": "1.1.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "has-flag": { "version": "4.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, "supports-color": { "version": "7.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -13846,7 +13908,8 @@ }, "is-ci": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", "dev": true, "requires": { "ci-info": "^2.0.0" @@ -13854,22 +13917,26 @@ }, "is-fullwidth-code-point": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, "js-tokens": { "version": "4.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, "jsesc": { "version": "2.5.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, "json5": { "version": "2.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ==", "dev": true, "requires": { "minimist": "^1.2.0" @@ -13877,17 +13944,20 @@ }, "lodash": { "version": "4.17.15", - "bundled": true, + "resolved": false, + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", "dev": true }, "lodash.throttle": { "version": "4.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=", "dev": true }, "log-update": { "version": "3.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-YSKm5n+YjZoGZT5lfmOqasVH1fIH9xQA9A81Y48nZ99PxAP62vdCCtua+Gcu6oTn0nqtZd/LwRV+Vflo53ZDWA==", "dev": true, "requires": { "ansi-escapes": "^3.2.0", @@ -13897,17 +13967,20 @@ "dependencies": { "ansi-escapes": { "version": "3.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", "dev": true }, "ansi-regex": { "version": "4.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, "cli-cursor": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", "dev": true, "requires": { "restore-cursor": "^2.0.0" @@ -13915,22 +13988,26 @@ }, "emoji-regex": { "version": "7.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, "is-fullwidth-code-point": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, "mimic-fn": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "dev": true }, "onetime": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", "dev": true, "requires": { "mimic-fn": "^1.0.0" @@ -13938,7 +14015,8 @@ }, "restore-cursor": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", "dev": true, "requires": { "onetime": "^2.0.0", @@ -13947,7 +14025,8 @@ }, "string-width": { "version": "3.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { "emoji-regex": "^7.0.1", @@ -13957,7 +14036,8 @@ }, "strip-ansi": { "version": "5.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { "ansi-regex": "^4.1.0" @@ -13965,7 +14045,8 @@ }, "wrap-ansi": { "version": "5.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", "dev": true, "requires": { "ansi-styles": "^3.2.0", @@ -13977,7 +14058,8 @@ }, "loose-envify": { "version": "1.4.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "dev": true, "requires": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -13985,17 +14067,20 @@ }, "mimic-fn": { "version": "2.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, "minimist": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, "minipass": { "version": "3.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", "dev": true, "requires": { "yallist": "^4.0.0" @@ -14003,24 +14088,28 @@ "dependencies": { "yallist": { "version": "4.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true } } }, "ms": { "version": "2.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, "object-assign": { "version": "4.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, "onetime": { "version": "5.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", "dev": true, "requires": { "mimic-fn": "^2.1.0" @@ -14028,12 +14117,14 @@ }, "path-parse": { "version": "1.0.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, "prop-types": { "version": "15.7.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", "dev": true, "requires": { "loose-envify": "^1.4.0", @@ -14043,17 +14134,20 @@ }, "punycode": { "version": "2.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, "react-is": { "version": "16.12.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==", "dev": true }, "react-reconciler": { "version": "0.24.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-gAGnwWkf+NOTig9oOowqid9O0HjTDC+XVGBCAmJYYJ2A2cN/O4gDdIuuUQjv8A4v6GDwVfJkagpBBLW5OW9HSw==", "dev": true, "requires": { "loose-envify": "^1.1.0", @@ -14064,7 +14158,8 @@ }, "redeyed": { "version": "2.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-iYS1gV2ZyyIEacme7v/jiRPmzAs=", "dev": true, "requires": { "esprima": "~4.0.0" @@ -14072,12 +14167,14 @@ }, "regenerator-runtime": { "version": "0.13.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==", "dev": true }, "resolve": { "version": "1.14.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-EjlOBLBO1kxsUxsKjLt7TAECyKW6fOh1VRkykQkKGzcBbjjPIxBqGh0jf7GJ3k/f5mxMqW3htMD3WdTUVtW8HQ==", "dev": true, "requires": { "path-parse": "^1.0.6" @@ -14085,12 +14182,14 @@ }, "resolve-from": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", "dev": true }, "restore-cursor": { "version": "3.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dev": true, "requires": { "onetime": "^5.1.0", @@ -14099,7 +14198,8 @@ }, "scheduler": { "version": "0.18.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ==", "dev": true, "requires": { "loose-envify": "^1.1.0", @@ -14108,17 +14208,20 @@ }, "semver": { "version": "5.7.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true }, "signal-exit": { "version": "3.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, "slice-ansi": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", "dev": true, "requires": { "ansi-styles": "^4.0.0", @@ -14128,7 +14231,8 @@ "dependencies": { "ansi-styles": { "version": "4.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, "requires": { "@types/color-name": "^1.1.1", @@ -14137,7 +14241,8 @@ }, "color-convert": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { "color-name": "~1.1.4" @@ -14145,14 +14250,16 @@ }, "color-name": { "version": "1.1.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true } } }, "string-length": { "version": "3.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Ttp5YvkGm5v9Ijagtaz1BnN+k9ObpvS0eIBblPMp2YWL8FBmi9qblQ9fexc2k/CXFgrTIteU3jAw3payCnwSTA==", "dev": true, "requires": { "astral-regex": "^1.0.0", @@ -14161,17 +14268,20 @@ "dependencies": { "ansi-regex": { "version": "4.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, "astral-regex": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true }, "strip-ansi": { "version": "5.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { "ansi-regex": "^4.1.0" @@ -14181,7 +14291,8 @@ }, "string-width": { "version": "4.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "dev": true, "requires": { "emoji-regex": "^8.0.0", @@ -14191,7 +14302,8 @@ }, "strip-ansi": { "version": "6.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { "ansi-regex": "^5.0.0" @@ -14199,7 +14311,8 @@ }, "supports-color": { "version": "5.5.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -14207,7 +14320,8 @@ }, "tap-parser": { "version": "10.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-qdT15H0DoJIi7zOqVXDn9X0gSM68JjNy1w3VemwTJlDnETjbi6SutnqmBfjDJAwkFS79NJ97gZKqie00ZCGmzg==", "dev": true, "requires": { "events-to-array": "^1.0.1", @@ -14217,7 +14331,8 @@ }, "tap-yaml": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Rxbx4EnrWkYk0/ztcm5u3/VznbyFJpyXO12dDBHKWiDVxy7O2Qw6MRrwO5H6Ww0U5YhRY/4C/VzWmFPhBQc4qQ==", "dev": true, "requires": { "yaml": "^1.5.0" @@ -14225,12 +14340,14 @@ }, "to-fast-properties": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "dev": true }, "treport": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-QCAbFtzIjQN+9k+alo8e6oo8j0eSLsttdahAgNLoC3U36rls8XRy/R11QOhHmPz7CDcB2ar29eLe4OFJoPnsPA==", "dev": true, "requires": { "cardinal": "^2.1.1", @@ -14245,7 +14362,8 @@ "dependencies": { "ansi-styles": { "version": "4.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, "requires": { "@types/color-name": "^1.1.1", @@ -14254,7 +14372,8 @@ }, "chalk": { "version": "3.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -14263,7 +14382,8 @@ }, "color-convert": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { "color-name": "~1.1.4" @@ -14271,17 +14391,20 @@ }, "color-name": { "version": "1.1.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "has-flag": { "version": "4.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, "supports-color": { "version": "7.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -14291,12 +14414,14 @@ }, "type-fest": { "version": "0.8.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true }, "unicode-length": { "version": "2.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-Ph/j1VbS3/r77nhoY2WU0GWGjVYOHL3xpKp0y/Eq2e5r0mT/6b649vm7KFO6RdAdrZkYLdxphYVgvODxPB+Ebg==", "dev": true, "requires": { "punycode": "^2.0.0", @@ -14305,12 +14430,14 @@ "dependencies": { "ansi-regex": { "version": "2.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, "strip-ansi": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { "ansi-regex": "^2.0.0" @@ -14329,7 +14456,8 @@ }, "widest-line": { "version": "3.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", "dev": true, "requires": { "string-width": "^4.0.0" @@ -14337,7 +14465,8 @@ }, "wrap-ansi": { "version": "6.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "requires": { "ansi-styles": "^4.0.0", @@ -14347,7 +14476,8 @@ "dependencies": { "ansi-styles": { "version": "4.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, "requires": { "@types/color-name": "^1.1.1", @@ -14356,7 +14486,8 @@ }, "color-convert": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { "color-name": "~1.1.4" @@ -14364,7 +14495,8 @@ }, "color-name": { "version": "1.1.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true } } @@ -14383,7 +14515,8 @@ }, "yaml": { "version": "1.7.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-qXROVp90sb83XtAoqE8bP9RwAkTTZbugRUTm5YeFCBfNRPEp2YzTeqWiz7m5OORHzEvrA/qcGS8hp/E+MMROYw==", "dev": true, "requires": { "@babel/runtime": "^7.6.3" @@ -14391,7 +14524,8 @@ }, "yoga-layout-prebuilt": { "version": "1.9.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-9SNQpwuEh2NucU83i2KMZnONVudZ86YNcFk9tq74YaqrQfgJWO3yB9uzH1tAg8iqh5c9F5j0wuyJ2z72wcum2w==", "dev": true } } @@ -15445,561 +15579,14 @@ } }, "fsevents": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.11.tgz", - "integrity": "sha512-+ux3lx6peh0BpvY0JebGyZoiR4D+oYzdPZMKJwkZ+sFkNJzpL7tXc/wehS49gUAxg3tmMHPHZkA8JU2rhhgDHw==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "dev": true, "optional": true, "requires": { "bindings": "^1.5.0", - "nan": "^2.12.1", - "node-pre-gyp": "*" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.3", - "bundled": true, - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "debug": { - "version": "3.2.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.6.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "bundled": true, - "dev": true, - "optional": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true, - "optional": true - }, - "minipass": { - "version": "2.9.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.3.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.9.0" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "needle": { - "version": "2.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.14.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4.4.2" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "npm-packlist": { - "version": "1.4.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.7.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "dev": true, - "optional": true - }, - "semver": { - "version": "5.7.1", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "4.4.13", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "yallist": { - "version": "3.1.1", - "bundled": true, - "dev": true, - "optional": true - } + "nan": "^2.12.1" } }, "glob-parent": { @@ -16498,9 +16085,9 @@ "dev": true }, "is-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", - "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", "dev": true, "requires": { "has-symbols": "^1.0.1" diff --git a/package.json b/package.json index bf11870351..0225991a2f 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "configure": "lerna clean --yes && lerna bootstrap && npm-run-all build generate-api-server-config", "generate-api-server-config": "node ./tools/generate-api-server-config.js", "start:api-server": "node ./packages/cactus-cmd-api-server/dist/lib/main/typescript/cmd/cactus-api.js --config-file=.config.json", + "start:cockpit": "lerna run --scope '*/cactus-cockpit' --stream serve:proxy", "export-open-api-spec": "ts-node -e 'import(\"./packages/cactus-cmd-api-server/src/main/typescript/openapi-spec\").then((x) => x.exportToFileSystemAsJson());'", "pregenerate-sdk": "npm-run-all export-open-api-spec", "generate-sdk": "openapi-generator generate --input-spec cactus-openapi-spec.json -g typescript-axios -o packages/cactus-sdk/src/main/typescript/generated/openapi/typescript-axios/", @@ -24,7 +25,7 @@ "build:dev:core-api": "lerna exec --stream --scope '*/*core-api' -- 'del-cli dist/** && tsc --project ./tsconfig.json && webpack --env=dev --target=node --config ../../webpack.config.js'", "build:dev:test-tooling": "lerna exec --stream --scope '*/*test-tooling' -- 'del-cli dist/** && tsc --project ./tsconfig.json && webpack --env=dev --target=node --config ../../webpack.config.js'", "build:dev:plugin-ledger-connector-quorum": "lerna exec --stream --scope '*/*connector-quorum' -- 'del-cli dist/** && tsc --project ./tsconfig.json && webpack --env=dev --target=node --config ../../webpack.config.js'", - "build:dev:plugin-web-service-consortium": "lerna exec --stream --scope '*/*web-service-consortium' -- 'del-cli dist/** && tsc --project ./tsconfig.json && webpack --env=dev --target=node --config ../../webpack.config.js'", + "build:dev:plugin-consortium-manual": "lerna exec --stream --scope '*/*manual-consortium' -- 'del-cli dist/** && tsc --project ./tsconfig.json && webpack --env=dev --target=node --config ../../webpack.config.js'", "build:dev:sdk": "lerna exec --stream --scope '*/*sdk' -- 'del-cli dist/** && tsc --project ./tsconfig.json && webpack --env=dev --target=node --config ../../webpack.config.js'", "webpack": "npm-run-all webpack:dev webpack:prod", "webpack:dev": "lerna run webpack:dev", diff --git a/packages/cactus-cmd-api-server/package-lock.json b/packages/cactus-cmd-api-server/package-lock.json index 53582ece5e..61bd49244f 100644 --- a/packages/cactus-cmd-api-server/package-lock.json +++ b/packages/cactus-cmd-api-server/package-lock.json @@ -19,6 +19,11 @@ "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.1.tgz", "integrity": "sha512-pu5fxkbLQWzRbBgfFbZfHXz0KlYojOfVdUhcNfy9lef8ZhBt0pckGr8g7zv4vPX4Out5vBNvqd/az4UaVWzZ9A==" }, + "@panva/asn1.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", + "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==" + }, "@types/body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", @@ -733,6 +738,14 @@ "topo": "3.x.x" } }, + "jose": { + "version": "1.27.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-1.27.2.tgz", + "integrity": "sha512-zLIwnMa8dh5A2jFo56KvhiXCaW0hFjdNvG0I5GScL8Wro+/r/SnyIYTbnX3fYztPNSfgQp56sDMHUuS9c3e6bw==", + "requires": { + "@panva/asn1.js": "^1.0.0" + } + }, "js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", diff --git a/packages/cactus-cmd-api-server/package.json b/packages/cactus-cmd-api-server/package.json index 3f8da66987..31d2431459 100755 --- a/packages/cactus-cmd-api-server/package.json +++ b/packages/cactus-cmd-api-server/package.json @@ -67,7 +67,7 @@ "@hyperledger/cactus-core-api": "0.2.0", "@hyperledger/cactus-plugin-keychain-memory": "0.2.0", "@hyperledger/cactus-plugin-kv-storage-memory": "0.2.0", - "@hyperledger/cactus-plugin-web-service-consortium": "0.2.0", + "@hyperledger/cactus-plugin-consortium-manual": "0.2.0", "body-parser": "1.19.0", "compression": "1.7.4", "convict": "6.0.0", @@ -77,6 +77,7 @@ "express-http-proxy": "1.6.0", "express-openapi-validator": "3.10.0", "joi": "14.3.1", + "jose": "1.27.2", "js-sha3": "0.8.0", "node-fetch": "3.0.0-beta.4", "node-forge": "0.9.1", @@ -99,4 +100,4 @@ "@types/semver": "7.3.1", "@types/uuid": "7.0.2" } -} +} \ No newline at end of file diff --git a/packages/cactus-cmd-api-server/src/main/typescript/api-server.ts b/packages/cactus-cmd-api-server/src/main/typescript/api-server.ts index 72b1dd89de..269d295489 100644 --- a/packages/cactus-cmd-api-server/src/main/typescript/api-server.ts +++ b/packages/cactus-cmd-api-server/src/main/typescript/api-server.ts @@ -16,7 +16,7 @@ import express, { import { OpenApiValidator } from "express-openapi-validator"; import compression from "compression"; import bodyParser from "body-parser"; -import cors, { CorsOptions } from "cors"; +import cors from "cors"; import { PluginFactory, ICactusPlugin, @@ -24,10 +24,7 @@ import { IPluginWebService, PluginRegistry, } from "@hyperledger/cactus-core-api"; -import { - ICactusApiServerOptions as ICactusApiServerConfig, - ConfigService, -} from "./config/config-service"; +import { ICactusApiServerOptions } from "./config/config-service"; import { CACTUS_OPEN_API_JSON } from "./openapi-spec"; import { Logger, LoggerProvider } from "@hyperledger/cactus-common"; import { Servers } from "./common/servers"; @@ -36,7 +33,7 @@ export interface IApiServerConstructorOptions { pluginRegistry?: PluginRegistry; httpServerApi?: Server | SecureServer; httpServerCockpit?: Server | SecureServer; - config: ICactusApiServerConfig; + config: ICactusApiServerOptions; } export class ApiServer { diff --git a/packages/cactus-cmd-api-server/src/main/typescript/config/config-service.ts b/packages/cactus-cmd-api-server/src/main/typescript/config/config-service.ts index 3a77fffadf..d0862397ce 100644 --- a/packages/cactus-cmd-api-server/src/main/typescript/config/config-service.ts +++ b/packages/cactus-cmd-api-server/src/main/typescript/config/config-service.ts @@ -1,10 +1,9 @@ -import { randomBytes } from "crypto"; import { SecureVersion } from "tls"; import { existsSync, readFileSync } from "fs"; import convict, { Schema, Config, SchemaObj } from "convict"; import { ipaddress } from "convict-format-with-validator"; -import secp256k1 from "secp256k1"; import { v4 as uuidV4 } from "uuid"; +import { JWK, JWS } from "jose"; import { LoggerProvider, Logger, @@ -12,6 +11,7 @@ import { } from "@hyperledger/cactus-common"; import { FORMAT_PLUGIN_ARRAY } from "./convict-plugin-array-format"; import { SelfSignedPkiGenerator, IPki } from "./self-signed-pki-generator"; +import { Consortium } from "@hyperledger/cactus-plugin-consortium-manual"; convict.addFormat(FORMAT_PLUGIN_ARRAY); convict.addFormat(ipaddress); @@ -24,6 +24,7 @@ export interface IPluginImport { export interface ICactusApiServerOptions { configFile: string; cactusNodeId: string; + consortiumId: string; logLevel: LogLevelDesc; tlsDefaultMaxVersion: SecureVersion; cockpitHost: string; @@ -45,10 +46,8 @@ export interface ICactusApiServerOptions { apiTlsKeyPem: string; apiTlsClientCaPem: string; plugins: IPluginImport[]; - publicKey: string; - privateKey: string; - keychainSuffixPublicKey: string; - keychainSuffixPrivateKey: string; + keyPairPem: string; + keychainSuffixKeyPairPem: string; minNodeVersion: string; } @@ -106,6 +105,15 @@ export class ConfigService { env: "CONFIG_FILE", arg: "config-file", }, + consortiumId: { + doc: + "Identifier of the consortium your node is part of. " + + " Can be any string of characters such as a UUID", + format: ConfigService.formatNonBlankString, + default: null as any, + env: "CONSORTIUM_ID", + arg: "consortium-id", + }, cactusNodeId: { doc: "Identifier of this particular Cactus node. Must be unique among the total set of Cactus nodes running in any " + @@ -311,38 +319,26 @@ export class ConfigService { arg: "api-tls-key-pem", default: null as any, }, - publicKey: { - doc: "Public key of this Cactus node (the API server)", - env: "PUBLIC_KEY", - arg: "public-key", - format: ConfigService.formatNonBlankString, - default: null as any, - }, - privateKey: { + keyPairPem: { sensitive: true, - doc: "Private key of this Cactus node (the API server)", - env: "PRIVATE_KEY", - arg: "private-key", + doc: + "Key pair (private+public) of this Cactus node in the standard " + + " PEM format.", + env: "KEY_PAIR_PEM", + arg: "key-pair-pem", format: ConfigService.formatNonBlankString, default: null as any, }, - keychainSuffixPrivateKey: { + keychainSuffixKeyPairPem: { doc: - "The key under which to store/retrieve the private key from the keychain of this Cactus node (API server)" + - "The complete lookup key is constructed from the ${CACTUS_NODE_ID}${KEYCHAIN_SUFFIX_PRIVATE_KEY} template.", - env: "KEYCHAIN_SUFFIX_PRIVATE_KEY", - arg: "keychain-suffix-private-key", + "The key under which to store/retrieve the key pair PEM from the " + + " keychain of this Cactus node (API server) The complete lookup key" + + " is constructed from the ${CACTUS_NODE_ID}" + + "${KEYCHAIN_SUFFIX_KEY_PAIR_PEM} template.", + env: "KEYCHAIN_SUFFIX_KEY_PAIR_PEM", + arg: "keychain-suffix-key-pair-pem", format: "*", - default: "CACTUS_NODE_PRIVATE_KEY", - }, - keychainSuffixPublicKey: { - doc: - "The key under which to store/retrieve the public key from the keychain of this Cactus node (API server)" + - "The complete lookup key is constructed from the ${CACTUS_NODE_ID}${KEYCHAIN_SUFFIX_PRIVATE_KEY} template.", - env: "KEYCHAIN_SUFFIX_PUBLIC_KEY", - arg: "keychain-suffix-public-key", - format: "*", - default: "CACTUS_NODE_PUBLIC_KEY", + default: "CACTUS_NODE_KEY_PAIR_PEM", }, }; } @@ -406,22 +402,31 @@ export class ConfigService { public newExampleConfig(): ICactusApiServerOptions { const schema = ConfigService.getConfigSchema(); - // FIXME most of this lowever level crypto code should be in a commons package that's universal - let privateKeyBytes; - do { - privateKeyBytes = randomBytes(32); - } while (!secp256k1.privateKeyVerify(privateKeyBytes)); - - const publicKeyBytes = secp256k1.publicKeyCreate(privateKeyBytes); - const privateKey = Buffer.from(privateKeyBytes).toString("hex"); - const publicKey = Buffer.from(publicKeyBytes).toString("hex"); - const apiTlsEnabled: boolean = (schema.apiTlsEnabled as SchemaObj).default; const apiHost = (schema.apiHost as SchemaObj).default; const apiPort = (schema.apiPort as SchemaObj).default; + const apiProtocol = apiTlsEnabled ? "https:" : "http"; + const apiBaseUrl = `${apiProtocol}//${apiHost}:${apiPort}`; - // const apiProtocol = apiTlsEnabled ? "https:" : "http"; - // const apiBaseUrl = `${apiProtocol}//${apiHost}:${apiPort}`; + const keyPair = JWK.generateSync("EC", "secp256k1", { use: "sig" }, true); + const keyPairPem = keyPair.toPEM(true); + const consortium: Consortium = { + name: "Example Cactus Consortium", + id: uuidV4(), + mainApiHost: apiBaseUrl, + members: [ + { + id: uuidV4(), + name: "Example Cactus Consortium Member 1", + nodes: [ + { + nodeApiHost: apiBaseUrl, + publicKeyPem: keyPair.toPEM(false), + }, + ], + }, + ], + }; const cockpitTlsEnabled: boolean = (schema.cockpitTlsEnabled as SchemaObj) .default; @@ -445,9 +450,10 @@ export class ConfigService { options: {}, }, { - packageName: "@hyperledger/cactus-plugin-web-service-consortium", + packageName: "@hyperledger/cactus-plugin-consortium-manual", options: { - privateKey, + keyPairPem, + consortium, }, }, ]; @@ -455,6 +461,7 @@ export class ConfigService { return { configFile: ".config.json", cactusNodeId: uuidV4(), + consortiumId: uuidV4(), logLevel: "debug", minNodeVersion: (schema.minNodeVersion as SchemaObj).default, tlsDefaultMaxVersion: "TLSv1.3", @@ -476,11 +483,8 @@ export class ConfigService { cockpitTlsCertPem: pkiServer.certificatePem, cockpitTlsKeyPem: pkiServer.privateKeyPem, cockpitTlsClientCaPem: "-", // Cockpit mTLS is off so this will not crash the server - publicKey, - privateKey, - keychainSuffixPublicKey: (schema.keychainSuffixPublicKey as SchemaObj) - .default, - keychainSuffixPrivateKey: (schema.keychainSuffixPrivateKey as SchemaObj) + keyPairPem, + keychainSuffixKeyPairPem: (schema.keychainSuffixKeyPairPem as SchemaObj) .default, plugins, }; @@ -510,24 +514,22 @@ export class ConfigService { } /** - * Validation that prevents operators from mistakenly deploying a public key - * that they may not have the private key for or vica versa. + * Validation that prevents operators from mistakenly deploying a key pair + * that they may not be operational for whatever reason. * - * @throws If the private key and the public key are not part of the same key pair. + * @throws If a dummy sign+verification operation fails for any reason. */ validateKeyPairMatch(): void { + const fnTag = "ConfigService#validateKeyPairMatch()"; // FIXME most of this lowever level crypto code should be in a commons package that's universal - const privateKey = ConfigService.config.get("privateKey"); - const privateKeyBytes = Uint8Array.from(Buffer.from(privateKey, "hex")); - const publicKey = ConfigService.config.get("publicKey"); - const expectedPublicKeyBytes = secp256k1.publicKeyCreate(privateKeyBytes); - const expectedPublicKey = Buffer.from(expectedPublicKeyBytes).toString( - "hex" - ); - if (publicKey !== expectedPublicKey) { - throw new Error( - `Public key does not match private key. Configured=${publicKey} Expected=${expectedPublicKey}` - ); + const keyPairPem = ConfigService.config.get("keyPairPem"); + const keyPair = JWK.asKey(keyPairPem); + + const jws = JWS.sign({ hello: "world" }, keyPair); + try { + JWS.verify(jws, keyPair); + } catch (ex) { + throw new Error(`${fnTag} Invalid key pair PEM: ${ex && ex.stack}`); } } } diff --git a/packages/cactus-cockpit/package-lock.json b/packages/cactus-cockpit/package-lock.json index e8593db9ee..35d3bd765e 100644 --- a/packages/cactus-cockpit/package-lock.json +++ b/packages/cactus-cockpit/package-lock.json @@ -1880,6 +1880,12 @@ "integrity": "sha512-1TQNDJvIKlgYXGNIABfgFp9y0FziDpuGrd799Q5RcnsDu+krD+eeW/0Fs5PHARvWWFelOhIG2OPCo6KbadBM4A==", "dev": true }, + "@types/jwt-decode": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@types/jwt-decode/-/jwt-decode-2.2.1.tgz", + "integrity": "sha512-aWw2YTtAdT7CskFyxEX2K21/zSDStuf/ikI3yBqmwpwJF0pS+/IX5DWv+1UFffZIbruP6cnT9/LAJV1gFwAT1A==", + "dev": true + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -7284,6 +7290,11 @@ "set-immediate-shim": "~1.0.1" } }, + "jwt-decode": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-2.2.0.tgz", + "integrity": "sha1-fYa9VmefWM5qhHBKZX3TkruoGnk=" + }, "karma": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/karma/-/karma-4.1.0.tgz", diff --git a/packages/cactus-cockpit/package.json b/packages/cactus-cockpit/package.json index 4d1794cab4..1999865b57 100644 --- a/packages/cactus-cockpit/package.json +++ b/packages/cactus-cockpit/package.json @@ -7,6 +7,7 @@ "scripts": { "ng": "ng", "serve": "ng serve", + "serve:proxy": "ng serve -- --proxy-config proxy.conf.json", "build:dev:frontend": "ng build", "build:prod:frontend": "ng build --prod", "test": "ng test", @@ -24,7 +25,7 @@ "@angular/router": "9.1.9", "@capacitor/core": "1.5.1", "@hyperledger/cactus-common": "0.2.0", - "@hyperledger/cactus-plugin-web-service-consortium": "0.2.0", + "@hyperledger/cactus-plugin-consortium-manual": "0.2.0", "@hyperledger/cactus-sdk": "0.2.0", "@ionic-native/core": "5.0.0", "@ionic-native/splash-screen": "5.0.0", @@ -32,6 +33,7 @@ "@ionic/angular": "5.1.1", "core-js": "2.5.4", "joi": "14.3.1", + "jwt-decode": "2.2.0", "rxjs": "6.5.5", "tslib": "^1.10.0", "typescript-optional": "2.0.1", @@ -48,6 +50,7 @@ "@types/jasmine": "3.3.8", "@types/jasminewd2": "2.0.3", "@types/joi": "14.3.4", + "@types/jwt-decode": "2.2.1", "@types/node": "^12.11.1", "codelyzer": "^5.1.2", "jasmine-core": "3.4.0", diff --git a/packages/cactus-cockpit/proxy.conf.json b/packages/cactus-cockpit/proxy.conf.json new file mode 100644 index 0000000000..4503318420 --- /dev/null +++ b/packages/cactus-cockpit/proxy.conf.json @@ -0,0 +1,7 @@ +{ + "/api": { + "target": "http://localhost:4000", + "secure": false, + "logLevel": "debug" + } +} \ No newline at end of file diff --git a/packages/cactus-cockpit/src/app/app-routing.module.ts b/packages/cactus-cockpit/src/app/app-routing.module.ts index 002eaeb3b6..fb785e53b9 100644 --- a/packages/cactus-cockpit/src/app/app-routing.module.ts +++ b/packages/cactus-cockpit/src/app/app-routing.module.ts @@ -4,13 +4,15 @@ import { PreloadAllModules, RouterModule, Routes } from "@angular/router"; const routes: Routes = [ { path: "", - redirectTo: "folder/Inbox", + redirectTo: "consortiums/inspector", pathMatch: "full", }, { - path: "folder/:id", + path: "consortiums/inspector", loadChildren: () => - import("./folder/folder.module").then((m) => m.FolderPageModule), + import("./consortium-inspector/consortium-inspector.module").then( + (m) => m.ConsortiumInspectorPageModule + ), }, ]; diff --git a/packages/cactus-cockpit/src/app/app.component.html b/packages/cactus-cockpit/src/app/app.component.html index 1950f453b6..47f6fe48ac 100644 --- a/packages/cactus-cockpit/src/app/app.component.html +++ b/packages/cactus-cockpit/src/app/app.component.html @@ -3,8 +3,8 @@ - Inbox - hi@ionicframework.com + Cockpit + Hyperledger Cactus @@ -14,14 +14,6 @@ - - Labels - - - - {{ label }} - - diff --git a/packages/cactus-cockpit/src/app/app.component.spec.ts b/packages/cactus-cockpit/src/app/app.component.spec.ts deleted file mode 100644 index 8ee9a6cc0e..0000000000 --- a/packages/cactus-cockpit/src/app/app.component.spec.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { CUSTOM_ELEMENTS_SCHEMA } from "@angular/core"; -import { TestBed, async } from "@angular/core/testing"; - -import { Platform } from "@ionic/angular"; -import { SplashScreen } from "@ionic-native/splash-screen/ngx"; -import { StatusBar } from "@ionic-native/status-bar/ngx"; -import { RouterTestingModule } from "@angular/router/testing"; - -import { AppComponent } from "./app.component"; - -describe("AppComponent", () => { - let statusBarSpy, splashScreenSpy, platformReadySpy, platformSpy; - - beforeEach(async(() => { - statusBarSpy = jasmine.createSpyObj("StatusBar", ["styleDefault"]); - splashScreenSpy = jasmine.createSpyObj("SplashScreen", ["hide"]); - platformReadySpy = Promise.resolve(); - platformSpy = jasmine.createSpyObj("Platform", { ready: platformReadySpy }); - - TestBed.configureTestingModule({ - declarations: [AppComponent], - schemas: [CUSTOM_ELEMENTS_SCHEMA], - providers: [ - { provide: StatusBar, useValue: statusBarSpy }, - { provide: SplashScreen, useValue: splashScreenSpy }, - { provide: Platform, useValue: platformSpy }, - ], - imports: [RouterTestingModule.withRoutes([])], - }).compileComponents(); - })); - - it("should create the app", async () => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.debugElement.componentInstance; - expect(app).toBeTruthy(); - }); - - it("should initialize the app", async () => { - TestBed.createComponent(AppComponent); - expect(platformSpy.ready).toHaveBeenCalled(); - await platformReadySpy; - expect(statusBarSpy.styleDefault).toHaveBeenCalled(); - expect(splashScreenSpy.hide).toHaveBeenCalled(); - }); - - it("should have menu labels", async () => { - const fixture = await TestBed.createComponent(AppComponent); - await fixture.detectChanges(); - const app = fixture.nativeElement; - const menuItems = app.querySelectorAll("ion-label"); - expect(menuItems.length).toEqual(12); - expect(menuItems[0].textContent).toContain("Inbox"); - expect(menuItems[1].textContent).toContain("Outbox"); - }); - - it("should have urls", async () => { - const fixture = await TestBed.createComponent(AppComponent); - await fixture.detectChanges(); - const app = fixture.nativeElement; - const menuItems = app.querySelectorAll("ion-item"); - expect(menuItems.length).toEqual(12); - expect(menuItems[0].getAttribute("ng-reflect-router-link")).toEqual( - "/folder/Inbox" - ); - expect(menuItems[1].getAttribute("ng-reflect-router-link")).toEqual( - "/folder/Outbox" - ); - }); -}); diff --git a/packages/cactus-cockpit/src/app/app.component.ts b/packages/cactus-cockpit/src/app/app.component.ts index d49a074069..ecd181f20a 100644 --- a/packages/cactus-cockpit/src/app/app.component.ts +++ b/packages/cactus-cockpit/src/app/app.component.ts @@ -5,7 +5,7 @@ import { SplashScreen } from "@ionic-native/splash-screen/ngx"; import { StatusBar } from "@ionic-native/status-bar/ngx"; import { LoggerProvider, Logger } from "@hyperledger/cactus-common"; -import { DefaultApi as DefaultApiConsortium } from "@hyperledger/cactus-plugin-web-service-consortium"; +import { DefaultApi as DefaultApiConsortium } from "@hyperledger/cactus-plugin-consortium-manual"; import { ApiClient, Configuration } from "@hyperledger/cactus-sdk"; import { CACTUS_API_URL } from "src/constants"; @@ -18,39 +18,13 @@ export class AppComponent implements OnInit { public selectedIndex = 0; public appPages = [ { - title: "Inbox", - url: "/folder/Inbox", - icon: "mail", - }, - { - title: "Outbox", - url: "/folder/Outbox", - icon: "paper-plane", - }, - { - title: "Favorites", - url: "/folder/Favorites", - icon: "heart", - }, - { - title: "Archived", - url: "/folder/Archived", - icon: "archive", - }, - { - title: "Trash", - url: "/folder/Trash", - icon: "trash", - }, - { - title: "Spam", - url: "/folder/Spam", - icon: "warning", + title: "Consortiums", + url: "/consortiums-inspector", + icon: "color-filter", }, ]; - public labels = ["Family", "Friends", "Notes", "Work", "Travel", "Reminders"]; - private readonly logger: Logger; + private readonly log: Logger; constructor( private platform: Platform, @@ -58,11 +32,11 @@ export class AppComponent implements OnInit { private statusBar: StatusBar, @Inject(CACTUS_API_URL) public readonly cactusApiUrl: string ) { - this.logger = LoggerProvider.getOrCreate({ + this.log = LoggerProvider.getOrCreate({ label: "app-component", level: "debug", }); - this.logger.info("Initializing app..."); + this.log.info("Initializing app..."); this.initializeApp(); } @@ -70,17 +44,11 @@ export class AppComponent implements OnInit { this.platform.ready().then(() => { this.statusBar.styleDefault(); this.splashScreen.hide(); - this.logger.info("App initialized OK. Splashscreen was hidden."); + this.log.info("App initialized OK. Splashscreen was hidden."); }); } ngOnInit() { - const path = window.location.pathname.split("folder/")[1]; - if (path !== undefined) { - this.selectedIndex = this.appPages.findIndex( - (page) => page.title.toLowerCase() === path.toLowerCase() - ); - } this.testApi(); } @@ -89,29 +57,9 @@ export class AppComponent implements OnInit { const apiClient = new ApiClient(configuration).extendWith( DefaultApiConsortium ); - const randomness = Math.random().toString(16); - const dummyConsortium = { - configurationEndpoint: "some-host" + randomness, - id: "some-id" + randomness, - name: "some-name" + randomness, - cactusNodes: [ - { - host: "some-host" + randomness, - publicKey: "some-fake-public-key" + randomness, - }, - ], - }; - const createConsortiumResponse = await apiClient.apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumPost( - dummyConsortium - ); - const healthCheckResponse = await apiClient.apiV1ApiServerHealthcheckGet(); - this.logger.info( - `apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumPost: `, - createConsortiumResponse.data - ); - this.logger.info( - `apiV1ApiServerHealthcheckGet: `, - healthCheckResponse.data - ); + const res = await apiClient.apiV1PluginsHyperledgerCactusPluginConsortiumManualNodeJwsGet(); + const resHealthCheck = await apiClient.apiV1ApiServerHealthcheckGet(); + this.log.info(`ConsortiumNodeJwtGet`, res.data); + this.log.info(`ApiServer HealthCheck Get:`, resHealthCheck.data); } } diff --git a/packages/cactus-cockpit/src/app/folder/folder-routing.module.ts b/packages/cactus-cockpit/src/app/consortium-inspector/consortium-inspector-routing.module.ts similarity index 59% rename from packages/cactus-cockpit/src/app/folder/folder-routing.module.ts rename to packages/cactus-cockpit/src/app/consortium-inspector/consortium-inspector-routing.module.ts index 98af6fa85c..5ccee59cd6 100644 --- a/packages/cactus-cockpit/src/app/folder/folder-routing.module.ts +++ b/packages/cactus-cockpit/src/app/consortium-inspector/consortium-inspector-routing.module.ts @@ -1,12 +1,12 @@ import { NgModule } from "@angular/core"; import { Routes, RouterModule } from "@angular/router"; -import { FolderPage } from "./folder.page"; +import { ConsortiumInspectorPage } from "./consortium-inspector.page"; const routes: Routes = [ { path: "", - component: FolderPage, + component: ConsortiumInspectorPage, }, ]; @@ -14,4 +14,4 @@ const routes: Routes = [ imports: [RouterModule.forChild(routes)], exports: [RouterModule], }) -export class FolderPageRoutingModule {} +export class ConsortiumInspectorPageRoutingModule {} diff --git a/packages/cactus-cockpit/src/app/consortium-inspector/consortium-inspector.module.ts b/packages/cactus-cockpit/src/app/consortium-inspector/consortium-inspector.module.ts new file mode 100644 index 0000000000..6b64bba1e1 --- /dev/null +++ b/packages/cactus-cockpit/src/app/consortium-inspector/consortium-inspector.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { FormsModule } from "@angular/forms"; + +import { IonicModule } from "@ionic/angular"; + +import { ConsortiumInspectorPageRoutingModule } from "./consortium-inspector-routing.module"; + +import { ConsortiumInspectorPage } from "./consortium-inspector.page"; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + IonicModule, + ConsortiumInspectorPageRoutingModule, + ], + declarations: [ConsortiumInspectorPage], +}) +export class ConsortiumInspectorPageModule {} diff --git a/packages/cactus-cockpit/src/app/consortium-inspector/consortium-inspector.page.html b/packages/cactus-cockpit/src/app/consortium-inspector/consortium-inspector.page.html new file mode 100644 index 0000000000..a880854526 --- /dev/null +++ b/packages/cactus-cockpit/src/app/consortium-inspector/consortium-inspector.page.html @@ -0,0 +1,112 @@ + + + + + + Consortium Inspector + + + + + + + Consortium Inspector 1 + + + + + + + + + + + + You can inspect the JSON Web Signature of any consortium on the internet by typing in a valid network host for + a consortium and pressing the button on the right. + + + + + Inspect JWS + + + + + + {{decodedJws.payload.consortium.name}} + Hyperledger Cactus Consortium + + + + Main API Host + {{decodedJws.payload.consortium.mainApiHost}} + + + + Consortium ID + {{decodedJws.payload.consortium.id}} + + + + + {{member.name}} + + + + Member ID + {{member.id}} + + + + + + Node (#{{nodeIdx + 1}}) + + + + API Host + {{node.nodeApiHost}} + + + + Public Key PEM + {{node.publicKeyPem}} + + + + + + + + Signatures + + + + + + {{signatureIdx+1}} + + + Signature + + {{ signature.signature }} + + + + Protected + +
{{ signature.protected | json }}
+
+
+ + Header + +
{{ signature.header | json }}
+
+
+
+ +
+ +
\ No newline at end of file diff --git a/packages/cactus-cockpit/src/app/consortium-inspector/consortium-inspector.page.ts b/packages/cactus-cockpit/src/app/consortium-inspector/consortium-inspector.page.ts new file mode 100644 index 0000000000..fdacffa7d8 --- /dev/null +++ b/packages/cactus-cockpit/src/app/consortium-inspector/consortium-inspector.page.ts @@ -0,0 +1,73 @@ +import { Component, Inject } from "@angular/core"; +import { CACTUS_API_URL } from "src/constants"; +import { + Logger, + LoggerProvider, + ILoggerOptions, +} from "@hyperledger/cactus-common"; +import { Configuration, ApiClient } from "@hyperledger/cactus-sdk"; +import { + DefaultApi as PluginConsortiumManualApi, + JWSGeneral, +} from "@hyperledger/cactus-plugin-consortium-manual"; +import * as JwtDecode from "jwt-decode"; + +@Component({ + selector: "app-folder", + templateUrl: "./consortium-inspector.page.html", + styleUrls: [], +}) +export class ConsortiumInspectorPage { + private readonly log: Logger; + + public apiHost: string; + public jws: JWSGeneral; + public decodedJws: any; + public decodedJwsJson: string; + + protected protected: string; + + constructor(@Inject(CACTUS_API_URL) public readonly cactusApiUrl: string) { + const loggerOpts: ILoggerOptions = { + label: "consortium-inspector-page", + level: "TRACE", + }; + this.log = LoggerProvider.getOrCreate(loggerOpts); + + this.log.debug(`constructor() applying default API host: ${cactusApiUrl}`); + this.apiHost = cactusApiUrl; + } + + onChangeApiHost(): void { + this.log.debug(`onChangeApiHost() apiHost=${this.apiHost}`); + } + + async onBtnClickInspect(): Promise { + this.log.debug(`onBtnClickInspect() apiHost=${this.apiHost}`); + const configuration = new Configuration({ basePath: this.cactusApiUrl }); + const apiClient = new ApiClient(configuration).extendWith( + PluginConsortiumManualApi + ); + + const resHealthCheck = await apiClient.apiV1ApiServerHealthcheckGet(); + this.log.debug(`ApiServer HealthCheck Get:`, resHealthCheck.data); + + const res = await apiClient.apiV1PluginsHyperledgerCactusPluginConsortiumManualConsortiumJwsGet(); + this.jws = res.data.jws; + const asJsonPreDecode = JSON.stringify(this.jws, null, 4); + this.log.debug(`ConsortiumNodeJwtGet pre-decode: \n%o`, asJsonPreDecode); + + this.decodedJws = JSON.parse(asJsonPreDecode); + this.decodedJws.payload = JwtDecode(this.jws.payload, { header: true }); + this.decodedJws.signatures.map((signature: any) => { + if (signature.protected) { + signature.protected = JwtDecode(signature.protected, { header: true }); + } + }); + this.decodedJwsJson = JSON.stringify(this.decodedJws, null, 4); + this.log.debug( + `ConsortiumNodeJwtGet post-decode: \n%o`, + this.decodedJwsJson + ); + } +} diff --git a/packages/cactus-cockpit/src/app/folder/folder.module.ts b/packages/cactus-cockpit/src/app/folder/folder.module.ts deleted file mode 100644 index 742f3e3563..0000000000 --- a/packages/cactus-cockpit/src/app/folder/folder.module.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { NgModule } from "@angular/core"; -import { CommonModule } from "@angular/common"; -import { FormsModule } from "@angular/forms"; - -import { IonicModule } from "@ionic/angular"; - -import { FolderPageRoutingModule } from "./folder-routing.module"; - -import { FolderPage } from "./folder.page"; - -@NgModule({ - imports: [CommonModule, FormsModule, IonicModule, FolderPageRoutingModule], - declarations: [FolderPage], -}) -export class FolderPageModule {} diff --git a/packages/cactus-cockpit/src/app/folder/folder.page.html b/packages/cactus-cockpit/src/app/folder/folder.page.html deleted file mode 100644 index 39aaa6a05e..0000000000 --- a/packages/cactus-cockpit/src/app/folder/folder.page.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - {{ folder }} - - - - - - - {{ folder }} - - - -
- {{ folder }} -

Explore UI Components

-
-
diff --git a/packages/cactus-cockpit/src/app/folder/folder.page.scss b/packages/cactus-cockpit/src/app/folder/folder.page.scss deleted file mode 100644 index 5894c9d7cc..0000000000 --- a/packages/cactus-cockpit/src/app/folder/folder.page.scss +++ /dev/null @@ -1,32 +0,0 @@ -ion-menu-button { - color: var(--ion-color-primary); -} - -#container { - text-align: center; - position: absolute; - left: 0; - right: 0; - top: 50%; - transform: translateY(-50%); -} - -#container strong { - font-size: 20px; - line-height: 26px; -} - -#container p { - font-size: 16px; - line-height: 22px; - color: #8c8c8c; - margin: 0; -} - -#container a { - text-decoration: none; -} - -ion-content ion-toolbar { - --background: transparent; -} \ No newline at end of file diff --git a/packages/cactus-cockpit/src/app/folder/folder.page.spec.ts b/packages/cactus-cockpit/src/app/folder/folder.page.spec.ts deleted file mode 100644 index 4bb3ee548d..0000000000 --- a/packages/cactus-cockpit/src/app/folder/folder.page.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { async, ComponentFixture, TestBed } from "@angular/core/testing"; -import { IonicModule } from "@ionic/angular"; -import { RouterModule } from "@angular/router"; -import { FolderPage } from "./folder.page"; - -describe("FolderPage", () => { - let component: FolderPage; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [FolderPage], - imports: [IonicModule.forRoot(), RouterModule.forRoot([])], - }).compileComponents(); - - fixture = TestBed.createComponent(FolderPage); - component = fixture.componentInstance; - fixture.detectChanges(); - })); - - it("should create", () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/packages/cactus-cockpit/src/app/folder/folder.page.ts b/packages/cactus-cockpit/src/app/folder/folder.page.ts deleted file mode 100644 index 88b461806a..0000000000 --- a/packages/cactus-cockpit/src/app/folder/folder.page.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Component, OnInit } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; - -@Component({ - selector: "app-folder", - templateUrl: "./folder.page.html", - styleUrls: ["./folder.page.scss"], -}) -export class FolderPage implements OnInit { - public folder: string; - - constructor(private activatedRoute: ActivatedRoute) {} - - ngOnInit() { - this.folder = this.activatedRoute.snapshot.paramMap.get("id"); - } -} diff --git a/packages/cactus-core-api/src/main/typescript/plugin/plugin-aspect.ts b/packages/cactus-core-api/src/main/typescript/plugin/plugin-aspect.ts index fcb88ddbbf..642bf99a16 100644 --- a/packages/cactus-core-api/src/main/typescript/plugin/plugin-aspect.ts +++ b/packages/cactus-core-api/src/main/typescript/plugin/plugin-aspect.ts @@ -3,4 +3,5 @@ export enum PluginAspect { LEDGER_CONNECTOR = "LEDGER_CONNECTOR", KV_STORAGE = "KV_STORAGE", WEB_SERVICE = "WEB_SERVICE", + CONSORTIUM = "CONSORTIUM", } diff --git a/packages/cactus-plugin-consortium-manual/.gitignore b/packages/cactus-plugin-consortium-manual/.gitignore new file mode 100644 index 0000000000..033202265d --- /dev/null +++ b/packages/cactus-plugin-consortium-manual/.gitignore @@ -0,0 +1 @@ +cactus-openapi-spec-plugin-consortium-manual.json diff --git a/packages/cactus-plugin-consortium-manual/cactus-openapi-spec-plugin-manual-consortium.json b/packages/cactus-plugin-consortium-manual/cactus-openapi-spec-plugin-manual-consortium.json new file mode 100644 index 0000000000..e24a9c6f40 --- /dev/null +++ b/packages/cactus-plugin-consortium-manual/cactus-openapi-spec-plugin-manual-consortium.json @@ -0,0 +1,224 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Hyperledger Cactus Plugin - Consortium Web Service", + "description": "Manage a Cactus consortium through the APIs. Needs administrative priviliges.", + "version": "0.0.1" + }, + "servers": [ + { + "url": "https://www.cactus.stream/{basePath}", + "description": "Public test instance", + "variables": { + "basePath": { + "default": "" + } + } + }, + { + "url": "http://localhost:4000/{basePath}", + "description": "Local test instance", + "variables": { + "basePath": { + "default": "" + } + } + } + ], + "components": { + "schemas": { + "Consortium": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "configurationEndpoint": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "configurationEndpoint" + ] + }, + "ConsortiumMember": { + "type": "object", + "required": [ + "id", + "nodes" + ], + "properties": { + "id": { + "type": "string", + "nullable": false + }, + "nodes": { + "type": "array", + "nullable": false, + "minItems": 1, + "items": { + "$ref": "#/components/schemas/CactusNode" + } + } + } + }, + "CactusPlugin": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "maxLength": 1024, + "nullable": false + }, + "packageName": { + "type": "string", + "minLength": 1, + "maxLength": 4096, + "nullable": false + } + } + }, + "CactusNodeMeta": { + "description": "A Cactus node meta information", + "type": "object", + "required": [ + "host", + "publicKeyPem" + ], + "properties": { + "host": { + "type": "string", + "minLength": 1, + "maxLength": 1024, + "nullable": false + }, + "publicKeyPem": { + "type": "string", + "nullable": false + } + } + }, + "CactusNode": { + "description": "A Cactus node can be a single server, or a set of servers behind a loand balancer acting as one.", + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/CactusNodeMeta" + }, + { + "type": "object", + "required": [ + "id", + "consortiumId", + "host", + "publicKeyPem", + "plugins" + ], + "properties": { + "id": { + "type": "string", + "description": "The unique identifier of a Cactus node. Recommended to assign a value to this that is guaranteed to be unique in the whole consortium or better yet, globally anywhere.", + "example": "809a76ba-cfb8-4045-a5c6-ed70a7314c25", + "minLength": 1, + "maxLength": 1024, + "nullable": false + }, + "consortiumId": { + "type": "string", + "description": "ID of the Cactus Consortium this node is in.", + "example": "3e2670d9-2d14-45bd-96f5-33e2c4b4e3fb", + "minLength": 1, + "maxLength": 1024, + "nullable": false + }, + "plugins": { + "type": "array", + "nullable": false, + "minItems": 0, + "maxItems": 2048, + "default": [], + "items": { + "$ref": "#/components/schemas/CactusPlugin" + } + } + } + } + ] + }, + "GetNodeJwsResponse": { + "type": "object", + "required": [ + "jws", + "publicKeyPem" + ], + "properties": { + "jws": { + "description": "The JSON Web Signature of the Cactus node. See: https://tools.ietf.org/html/rfc7515 for info about standard.", + "type": "string", + "minLength": 5, + "maxLength": 65535, + "pattern": "/^[a-zA-Z0-9-_]+?.[a-zA-Z0-9-_]+?.([a-zA-Z0-9-_]+)?$/", + "nullable": false, + "example": "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjJ9.DOCNCqEMN7CQ_z-RMndiyldljXOk6WFIZxRzNF5Ylg4" + }, + "publicKeyPem": { + "description": "The PEM encoded public key that was used to generate the JWS included in the response (the jws property)", + "type": "string", + "minLength": 1, + "maxLength": 65535, + "nullable": false, + "format": "Must only contain the public key, never include here the PEM that also contains a private key. See PEM format: https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail" + } + } + } + } + }, + "paths": { + "/api/v1/plugins/@hyperledger/cactus-plugin-manual-consortium/consortium": { + "get": { + "summary": "Retrieves a consortium", + "description": "The consortium metadata (minus the sensitive data)", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Consortium" + } + } + } + } + } + } + }, + "/api/v1/plugins/@hyperledger/cactus-plugin-manual-consortium/node/jws": { + "get": { + "summary": "Retrieves the JWT of a Cactus Node", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetNodeJwsResponse" + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/packages/cactus-plugin-web-service-consortium/package-lock.json b/packages/cactus-plugin-consortium-manual/package-lock.json similarity index 95% rename from packages/cactus-plugin-web-service-consortium/package-lock.json rename to packages/cactus-plugin-consortium-manual/package-lock.json index 57bdd495b6..dd227d30f0 100644 --- a/packages/cactus-plugin-web-service-consortium/package-lock.json +++ b/packages/cactus-plugin-consortium-manual/package-lock.json @@ -1,5 +1,5 @@ { - "name": "@hyperledger/cactus-plugin-web-service-consortium", + "name": "@hyperledger/cactus-plugin-consortium-manual", "version": "0.2.0", "lockfileVersion": 1, "requires": true, @@ -19,6 +19,11 @@ "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.2.tgz", "integrity": "sha512-qS/a24RA5FEoiJS9wiv6Pwg2c/kiUo3IVUQcfeM9JvsR6pM8Yx+yl/6xWYLckZCT5jpLNhslgjiA8p/XcGyMRQ==" }, + "@panva/asn1.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", + "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==" + }, "@types/body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", @@ -67,6 +72,18 @@ "integrity": "sha512-1TQNDJvIKlgYXGNIABfgFp9y0FziDpuGrd799Q5RcnsDu+krD+eeW/0Fs5PHARvWWFelOhIG2OPCo6KbadBM4A==", "dev": true }, + "@types/json-stable-stringify": { + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/@types/json-stable-stringify/-/json-stable-stringify-1.0.32.tgz", + "integrity": "sha512-q9Q6+eUEGwQkv4Sbst3J4PNgDOvpuVuKj79Hl/qnmBMEIPzB5QoFRUtjcgcg2xNUZyYUGXBk5wYIBKHt0A+Mxw==", + "dev": true + }, + "@types/lodash": { + "version": "4.14.158", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.158.tgz", + "integrity": "sha512-InCEXJNTv/59yO4VSfuvNrZHt7eeNtWQEgnieIA+mIC+MOWM9arOWG2eQ8Vhk6NbOre6/BidiXhkZYeDY9U35w==", + "dev": true + }, "@types/mime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", @@ -580,6 +597,14 @@ "topo": "3.x.x" } }, + "jose": { + "version": "1.27.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-1.27.2.tgz", + "integrity": "sha512-zLIwnMa8dh5A2jFo56KvhiXCaW0hFjdNvG0I5GScL8Wro+/r/SnyIYTbnX3fYztPNSfgQp56sDMHUuS9c3e6bw==", + "requires": { + "@panva/asn1.js": "^1.0.0" + } + }, "js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", @@ -607,6 +632,24 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "requires": { + "jsonify": "~0.0.0" + } + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" + }, + "lodash": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", diff --git a/packages/cactus-plugin-web-service-consortium/package.json b/packages/cactus-plugin-consortium-manual/package.json similarity index 81% rename from packages/cactus-plugin-web-service-consortium/package.json rename to packages/cactus-plugin-consortium-manual/package.json index e4d5a93981..ec8e4360af 100755 --- a/packages/cactus-plugin-web-service-consortium/package.json +++ b/packages/cactus-plugin-consortium-manual/package.json @@ -1,11 +1,11 @@ { - "name": "@hyperledger/cactus-plugin-web-service-consortium", + "name": "@hyperledger/cactus-plugin-consortium-manual", "version": "0.2.0", "description": "A web service plugin that provides management capabilities on a Cactus consortium as a whole for administrative purposes.", - "main": "dist/cactus-plugin-web-service-consortium.node.umd.js", - "mainMinified": "dist/cactus-plugin-web-service-consortium.node.umd.min.js", - "browser": "dist/cactus-plugin-web-service-consortium.web.umd.js", - "browserMinified": "dist/cactus-plugin-web-service-consortium.web.umd.min.js", + "main": "dist/cactus-plugin-consortium-manual.node.umd.js", + "mainMinified": "dist/cactus-plugin-consortium-manual.node.umd.min.js", + "browser": "dist/cactus-plugin-consortium-manual.web.umd.js", + "browserMinified": "dist/cactus-plugin-consortium-manual.web.umd.min.js", "module": "dist/lib/main/typescript/index.js", "types": "dist/types/main/typescript/index.d.ts", "files": [ @@ -14,16 +14,13 @@ "scripts": { "export-open-api-spec": "ts-node -e 'import(\"./src/main/typescript/openapi-spec\").then((x) => x.exportToFileSystemAsJson());'", "pregenerate-sdk": "npm-run-all export-open-api-spec", - "generate-sdk": "openapi-generator generate --input-spec cactus-openapi-spec-plugin-web-service-consortium.json -g typescript-axios -o ./src/main/typescript/generated/openapi/typescript-axios/", + "generate-sdk": "openapi-generator generate --input-spec cactus-openapi-spec-plugin-consortium-manual.json -g typescript-axios -o ./src/main/typescript/generated/openapi/typescript-axios/", "tsc": "tsc --project ./tsconfig.json", "pretsc": "npm run generate-sdk", - "webpack": "npm-run-all webpack:dev webpack:prod", - "webpack:dev": "npm-run-all webpack:dev:node webpack:dev:web", "webpack:dev:web": "webpack --env=dev --target=web --config ../../webpack.config.js", "webpack:dev:node": "webpack --env=dev --target=node --config ../../webpack.config.js", - "webpack:prod": "npm-run-all webpack:prod:node webpack:prod:web", "webpack:prod:web": "webpack --env=prod --target=web --config ../../webpack.config.js", "webpack:prod:node": "webpack --env=prod --target=node --config ../../webpack.config.js" @@ -73,7 +70,10 @@ "express": "4.17.1", "express-openapi-validator": "3.12.9", "joi": "14.3.1", + "jose": "1.27.2", "js-sha3": "0.8.0", + "json-stable-stringify": "1.0.1", + "lodash": "4.17.19", "secp256k1": "4.0.1", "typescript-optional": "2.0.1" }, @@ -82,6 +82,8 @@ "@hyperledger/cactus-sdk": "0.2.0", "@types/express": "4.17.6", "@types/joi": "14.3.4", + "@types/json-stable-stringify": "1.0.32", + "@types/lodash": "4.14.158", "@types/multer": "1.4.3", "@types/secp256k1": "4.0.1" } diff --git a/packages/cactus-plugin-consortium-manual/src/main/typescript/consortium/get-consortium-jws-endpoint-v1.ts b/packages/cactus-plugin-consortium-manual/src/main/typescript/consortium/get-consortium-jws-endpoint-v1.ts new file mode 100644 index 0000000000..607dfa7963 --- /dev/null +++ b/packages/cactus-plugin-consortium-manual/src/main/typescript/consortium/get-consortium-jws-endpoint-v1.ts @@ -0,0 +1,105 @@ +import { Request, Response, NextFunction } from "express"; +import { JWS, JWK } from "jose"; +import flatten from "lodash/flatten"; + +import { ApiClient } from "@hyperledger/cactus-sdk"; +import { + IWebServiceEndpoint, + IExpressRequestHandler, +} from "@hyperledger/cactus-core-api"; +import { + Configuration, + DefaultApi, + JWSGeneral, + JWSRecipient, + Consortium, + GetNodeJwsResponse, +} from "../generated/openapi/typescript-axios"; +import { + LogLevelDesc, + Logger, + LoggerProvider, +} from "@hyperledger/cactus-common"; +import { AxiosResponse } from "axios"; + +export interface IGetConsortiumJwsEndpointOptions { + keyPairPem: string; + consortium: Consortium; + path: string; + logLevel?: LogLevelDesc; +} + +export class GetConsortiumEndpointV1 implements IWebServiceEndpoint { + private readonly log: Logger; + + constructor(public readonly options: IGetConsortiumJwsEndpointOptions) { + const fnTag = "GetConsortiumJwsEndpoint#constructor()"; + if (!options) { + throw new Error(`${fnTag} options falsy.`); + } + if (!options.keyPairPem) { + throw new Error(`${fnTag} options.keyPairPem falsy.`); + } + if (!options.consortium) { + throw new Error(`${fnTag} options.consortium falsy.`); + } + if (!options.path) { + throw new Error(`${fnTag} options.path falsy.`); + } + + const label = "get-consortium-jws-endpoint"; + const level = options.logLevel || "INFO"; + this.log = LoggerProvider.getOrCreate({ label, level }); + } + + public getExpressRequestHandler(): IExpressRequestHandler { + return this.handleRequest.bind(this); + } + + getPath(): string { + return this.options.path; + } + + async handleRequest(req: Request, res: Response): Promise { + const fnTag = "GetConsortiumJwsEndpointV1#handleRequest()"; + this.log.debug(`GET ${this.getPath()}`); + + try { + const nodes2d = this.options.consortium.members?.map((m) => m.nodes); + const nodes = flatten(nodes2d); + + const requests: Promise>[] = nodes + .map((cnm) => cnm.nodeApiHost) + .map((host) => new Configuration({ basePath: host })) + .map((configuration) => new ApiClient(configuration)) + .map((apiClient) => apiClient.extendWith(DefaultApi)) + .map((apiClient) => + apiClient.apiV1PluginsHyperledgerCactusPluginConsortiumManualNodeJwsGet() + ); + + const responses = await Promise.all(requests); + + const signatures: JWSRecipient[] = []; + + responses + .map((apiResponse) => apiResponse.data) + .map((getNodeJwsResponse) => getNodeJwsResponse.jws) + .forEach((aJws: JWSGeneral) => + aJws.signatures.forEach((signature) => signatures.push(signature)) + ); + + const [response] = responses; + const jws = response.data.jws; + jws.signatures = signatures; + + const body = { jws }; + res.status(200); + res.json(body); + } catch (ex) { + this.log.error(`${fnTag} failed to serve request`, ex); + res.status(500); + res.statusMessage = ex.message; + res.json({ error: ex.stack }); + } + } +} diff --git a/packages/cactus-plugin-consortium-manual/src/main/typescript/consortium/get-node-jws-endpoint-v1.ts b/packages/cactus-plugin-consortium-manual/src/main/typescript/consortium/get-node-jws-endpoint-v1.ts new file mode 100644 index 0000000000..df7889fc99 --- /dev/null +++ b/packages/cactus-plugin-consortium-manual/src/main/typescript/consortium/get-node-jws-endpoint-v1.ts @@ -0,0 +1,90 @@ +import uuid from "uuid"; +import { Request, Response, NextFunction } from "express"; +import { JWS, JWK } from "jose"; +import jsonStableStringify from "json-stable-stringify"; + +import { + IWebServiceEndpoint, + IExpressRequestHandler, +} from "@hyperledger/cactus-core-api"; +import { + GetNodeJwsResponse, + Consortium, + JWSGeneral, +} from "../generated/openapi/typescript-axios"; +import { + Logger, + LogLevelDesc, + LoggerProvider, +} from "@hyperledger/cactus-common"; + +export interface IGetNodeJwsEndpointOptions { + keyPairPem: string; + consortium: Consortium; + path: string; + logLevel?: LogLevelDesc; +} + +export class GetNodeJwsEndpoint implements IWebServiceEndpoint { + private readonly log: Logger; + + constructor(public readonly options: IGetNodeJwsEndpointOptions) { + const fnTag = "GetNodeJwsEndpoint#constructor()"; + if (!options) { + throw new Error(`${fnTag} options falsy.`); + } + if (!options.keyPairPem) { + throw new Error(`${fnTag} options.keyPairPem falsy.`); + } + if (!options.path) { + throw new Error(`${fnTag} options.path falsy.`); + } + const level = options.logLevel || "INFO"; + const label = "get-node-jws-endpoint-v1"; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + public getExpressRequestHandler(): IExpressRequestHandler { + return this.handleRequest.bind(this); + } + + getPath(): string { + return this.options.path; + } + + async handleRequest( + req: Request, + res: Response, + next: NextFunction + ): Promise { + try { + this.log.debug(`GET ${this.getPath()}`); + + const jws = await this.createJws(); + const body: GetNodeJwsResponse = { jws }; + res.status(200); + res.json(body); + } catch (ex) { + res.status(500); + res.json({ error: ex.stack }); + } + } + + public async createJws(): Promise { + const fnTag = "GetNodeJwsEndpoint#createJws()"; + const { keyPairPem, consortium } = this.options; + try { + const keyPair = JWK.asKey(keyPairPem); + const payload = jsonStableStringify({ consortium }); + const _protected = { + iat: Date.now(), + jti: uuid.v4(), + iss: "Hyperledger Cactus", + }; + // TODO: double check if this casting is safe (it is supposed to be) + return JWS.sign.general(payload, keyPair, _protected) as JWSGeneral; + } catch (ex) { + throw new Error(`${fnTag} ${ex.stack}`); + } + } +} diff --git a/packages/cactus-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/.gitignore b/packages/cactus-plugin-consortium-manual/src/main/typescript/generated/openapi/typescript-axios/.gitignore similarity index 100% rename from packages/cactus-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/.gitignore rename to packages/cactus-plugin-consortium-manual/src/main/typescript/generated/openapi/typescript-axios/.gitignore diff --git a/packages/cactus-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator-ignore b/packages/cactus-plugin-consortium-manual/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator-ignore similarity index 100% rename from packages/cactus-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator-ignore rename to packages/cactus-plugin-consortium-manual/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator-ignore diff --git a/packages/cactus-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/VERSION b/packages/cactus-plugin-consortium-manual/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/VERSION similarity index 100% rename from packages/cactus-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/VERSION rename to packages/cactus-plugin-consortium-manual/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/VERSION diff --git a/packages/cactus-plugin-consortium-manual/src/main/typescript/generated/openapi/typescript-axios/api.ts b/packages/cactus-plugin-consortium-manual/src/main/typescript/generated/openapi/typescript-axios/api.ts new file mode 100644 index 0000000000..50c3a4a3e9 --- /dev/null +++ b/packages/cactus-plugin-consortium-manual/src/main/typescript/generated/openapi/typescript-axios/api.ts @@ -0,0 +1,421 @@ +// tslint:disable +/** + * Hyperledger Cactus Plugin - Consortium Web Service + * Manage a Cactus consortium through the APIs. Needs administrative priviliges. + * + * The version of the OpenAPI document: 0.0.1 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import * as globalImportUrl from 'url'; +import { Configuration } from './configuration'; +import globalAxios, { AxiosPromise, AxiosInstance } from 'axios'; +// Some imports not used depending on template conditions +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError } from './base'; + +/** + * A Cactus node can be a single server, or a set of servers behind a loand balancer acting as one. + * @export + * @interface CactusNode + */ +export interface CactusNode { + /** + * + * @type {string} + * @memberof CactusNode + */ + nodeApiHost: string; + /** + * The PEM encoded public key that was used to generate the JWS included in the response (the jws property) + * @type {string} + * @memberof CactusNode + */ + publicKeyPem: string; + /** + * The unique identifier of a Cactus node. Recommended to assign a value to this that is guaranteed to be unique in the whole consortium or better yet, globally anywhere. + * @type {string} + * @memberof CactusNode + */ + id: string; + /** + * ID of the Cactus Consortium this node is in. + * @type {string} + * @memberof CactusNode + */ + consortiumId: string; + /** + * ID of the Cactus Consortium member this node is operated by. + * @type {string} + * @memberof CactusNode + */ + memberId: string; + /** + * + * @type {Array} + * @memberof CactusNode + */ + plugins: Array; +} +/** + * + * @export + * @interface CactusNodeAllOf + */ +export interface CactusNodeAllOf { + /** + * The unique identifier of a Cactus node. Recommended to assign a value to this that is guaranteed to be unique in the whole consortium or better yet, globally anywhere. + * @type {string} + * @memberof CactusNodeAllOf + */ + id: string; + /** + * ID of the Cactus Consortium this node is in. + * @type {string} + * @memberof CactusNodeAllOf + */ + consortiumId: string; + /** + * ID of the Cactus Consortium member this node is operated by. + * @type {string} + * @memberof CactusNodeAllOf + */ + memberId: string; + /** + * + * @type {Array} + * @memberof CactusNodeAllOf + */ + plugins: Array; +} +/** + * A Cactus node meta information + * @export + * @interface CactusNodeMeta + */ +export interface CactusNodeMeta { + /** + * + * @type {string} + * @memberof CactusNodeMeta + */ + nodeApiHost: string; + /** + * The PEM encoded public key that was used to generate the JWS included in the response (the jws property) + * @type {string} + * @memberof CactusNodeMeta + */ + publicKeyPem: string; +} +/** + * + * @export + * @interface CactusPlugin + */ +export interface CactusPlugin { + /** + * + * @type {string} + * @memberof CactusPlugin + */ + id: string; + /** + * + * @type {string} + * @memberof CactusPlugin + */ + packageName?: string; +} +/** + * + * @export + * @interface Consortium + */ +export interface Consortium { + /** + * + * @type {string} + * @memberof Consortium + */ + id: string; + /** + * + * @type {string} + * @memberof Consortium + */ + name: string; + /** + * + * @type {string} + * @memberof Consortium + */ + mainApiHost: string; + /** + * + * @type {Array} + * @memberof Consortium + */ + members: Array; +} +/** + * + * @export + * @interface ConsortiumMember + */ +export interface ConsortiumMember { + /** + * + * @type {string} + * @memberof ConsortiumMember + */ + id: string; + /** + * + * @type {string} + * @memberof ConsortiumMember + */ + name: string; + /** + * + * @type {Array} + * @memberof ConsortiumMember + */ + nodes: Array; +} +/** + * + * @export + * @interface GetConsortiumJwsResponse + */ +export interface GetConsortiumJwsResponse { + /** + * + * @type {JWSGeneral} + * @memberof GetConsortiumJwsResponse + */ + jws: JWSGeneral; +} +/** + * + * @export + * @interface GetNodeJwsResponse + */ +export interface GetNodeJwsResponse { + /** + * + * @type {JWSGeneral} + * @memberof GetNodeJwsResponse + */ + jws: JWSGeneral; +} +/** + * + * @export + * @interface JWSGeneral + */ +export interface JWSGeneral { + /** + * + * @type {string} + * @memberof JWSGeneral + */ + payload: string; + /** + * + * @type {Array} + * @memberof JWSGeneral + */ + signatures: Array; +} +/** + * A JSON Web Signature. See: https://tools.ietf.org/html/rfc7515 for info about standard. + * @export + * @interface JWSRecipient + */ +export interface JWSRecipient { + /** + * + * @type {string} + * @memberof JWSRecipient + */ + signature: string; + /** + * + * @type {string} + * @memberof JWSRecipient + */ + _protected?: string; + /** + * + * @type {{ [key: string]: object; }} + * @memberof JWSRecipient + */ + header?: { [key: string]: object; }; +} + +/** + * DefaultApi - axios parameter creator + * @export + */ +export const DefaultApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * The JWS asserting the consortium metadata (pub keys and hosts of nodes) + * @summary Retrieves a consortium JWS + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + apiV1PluginsHyperledgerCactusPluginConsortiumManualConsortiumJwsGet(options: any = {}): RequestArgs { + const localVarPath = `/api/v1/plugins/@hyperledger/cactus-plugin-consortium-manual/consortium/jws`; + const localVarUrlObj = globalImportUrl.parse(localVarPath, true); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarUrlObj.query = {...localVarUrlObj.query, ...localVarQueryParameter, ...options.query}; + // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 + delete localVarUrlObj.search; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; + + return { + url: globalImportUrl.format(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @summary Retrieves the JWT of a Cactus Node + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + apiV1PluginsHyperledgerCactusPluginConsortiumManualNodeJwsGet(options: any = {}): RequestArgs { + const localVarPath = `/api/v1/plugins/@hyperledger/cactus-plugin-consortium-manual/node/jws`; + const localVarUrlObj = globalImportUrl.parse(localVarPath, true); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarUrlObj.query = {...localVarUrlObj.query, ...localVarQueryParameter, ...options.query}; + // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 + delete localVarUrlObj.search; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; + + return { + url: globalImportUrl.format(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + } +}; + +/** + * DefaultApi - functional programming interface + * @export + */ +export const DefaultApiFp = function(configuration?: Configuration) { + return { + /** + * The JWS asserting the consortium metadata (pub keys and hosts of nodes) + * @summary Retrieves a consortium JWS + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + apiV1PluginsHyperledgerCactusPluginConsortiumManualConsortiumJwsGet(options?: any): (axios?: AxiosInstance, basePath?: string) => AxiosPromise { + const localVarAxiosArgs = DefaultApiAxiosParamCreator(configuration).apiV1PluginsHyperledgerCactusPluginConsortiumManualConsortiumJwsGet(options); + return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { + const axiosRequestArgs = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url}; + return axios.request(axiosRequestArgs); + }; + }, + /** + * + * @summary Retrieves the JWT of a Cactus Node + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + apiV1PluginsHyperledgerCactusPluginConsortiumManualNodeJwsGet(options?: any): (axios?: AxiosInstance, basePath?: string) => AxiosPromise { + const localVarAxiosArgs = DefaultApiAxiosParamCreator(configuration).apiV1PluginsHyperledgerCactusPluginConsortiumManualNodeJwsGet(options); + return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { + const axiosRequestArgs = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url}; + return axios.request(axiosRequestArgs); + }; + }, + } +}; + +/** + * DefaultApi - factory interface + * @export + */ +export const DefaultApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { + return { + /** + * The JWS asserting the consortium metadata (pub keys and hosts of nodes) + * @summary Retrieves a consortium JWS + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + apiV1PluginsHyperledgerCactusPluginConsortiumManualConsortiumJwsGet(options?: any): AxiosPromise { + return DefaultApiFp(configuration).apiV1PluginsHyperledgerCactusPluginConsortiumManualConsortiumJwsGet(options)(axios, basePath); + }, + /** + * + * @summary Retrieves the JWT of a Cactus Node + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + apiV1PluginsHyperledgerCactusPluginConsortiumManualNodeJwsGet(options?: any): AxiosPromise { + return DefaultApiFp(configuration).apiV1PluginsHyperledgerCactusPluginConsortiumManualNodeJwsGet(options)(axios, basePath); + }, + }; +}; + +/** + * DefaultApi - object-oriented interface + * @export + * @class DefaultApi + * @extends {BaseAPI} + */ +export class DefaultApi extends BaseAPI { + /** + * The JWS asserting the consortium metadata (pub keys and hosts of nodes) + * @summary Retrieves a consortium JWS + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public apiV1PluginsHyperledgerCactusPluginConsortiumManualConsortiumJwsGet(options?: any) { + return DefaultApiFp(this.configuration).apiV1PluginsHyperledgerCactusPluginConsortiumManualConsortiumJwsGet(options)(this.axios, this.basePath); + } + + /** + * + * @summary Retrieves the JWT of a Cactus Node + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public apiV1PluginsHyperledgerCactusPluginConsortiumManualNodeJwsGet(options?: any) { + return DefaultApiFp(this.configuration).apiV1PluginsHyperledgerCactusPluginConsortiumManualNodeJwsGet(options)(this.axios, this.basePath); + } + +} + + diff --git a/packages/cactus-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/base.ts b/packages/cactus-plugin-consortium-manual/src/main/typescript/generated/openapi/typescript-axios/base.ts similarity index 100% rename from packages/cactus-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/base.ts rename to packages/cactus-plugin-consortium-manual/src/main/typescript/generated/openapi/typescript-axios/base.ts diff --git a/packages/cactus-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/configuration.ts b/packages/cactus-plugin-consortium-manual/src/main/typescript/generated/openapi/typescript-axios/configuration.ts similarity index 100% rename from packages/cactus-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/configuration.ts rename to packages/cactus-plugin-consortium-manual/src/main/typescript/generated/openapi/typescript-axios/configuration.ts diff --git a/packages/cactus-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/git_push.sh b/packages/cactus-plugin-consortium-manual/src/main/typescript/generated/openapi/typescript-axios/git_push.sh similarity index 100% rename from packages/cactus-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/git_push.sh rename to packages/cactus-plugin-consortium-manual/src/main/typescript/generated/openapi/typescript-axios/git_push.sh diff --git a/packages/cactus-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/index.ts b/packages/cactus-plugin-consortium-manual/src/main/typescript/generated/openapi/typescript-axios/index.ts similarity index 100% rename from packages/cactus-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/index.ts rename to packages/cactus-plugin-consortium-manual/src/main/typescript/generated/openapi/typescript-axios/index.ts diff --git a/packages/cactus-plugin-web-service-consortium/src/main/typescript/index.ts b/packages/cactus-plugin-consortium-manual/src/main/typescript/index.ts similarity index 100% rename from packages/cactus-plugin-web-service-consortium/src/main/typescript/index.ts rename to packages/cactus-plugin-consortium-manual/src/main/typescript/index.ts diff --git a/packages/cactus-plugin-web-service-consortium/src/main/typescript/index.web.ts b/packages/cactus-plugin-consortium-manual/src/main/typescript/index.web.ts similarity index 100% rename from packages/cactus-plugin-web-service-consortium/src/main/typescript/index.web.ts rename to packages/cactus-plugin-consortium-manual/src/main/typescript/index.web.ts diff --git a/packages/cactus-plugin-consortium-manual/src/main/typescript/openapi-spec.ts b/packages/cactus-plugin-consortium-manual/src/main/typescript/openapi-spec.ts new file mode 100644 index 0000000000..fdf8c15ec5 --- /dev/null +++ b/packages/cactus-plugin-consortium-manual/src/main/typescript/openapi-spec.ts @@ -0,0 +1,319 @@ +import * as OpenAPI from "express-openapi-validator/dist/framework/types"; + +export const CACTUS_OPEN_API_JSON: OpenAPI.OpenAPIV3.Document = { + openapi: "3.0.3", + info: { + title: "Hyperledger Cactus Plugin - Consortium Web Service", + description: + "Manage a Cactus consortium through the APIs. Needs administrative priviliges.", + version: "0.0.1", + }, + servers: [ + { + url: "https://www.cactus.stream/{basePath}", + description: "Public test instance", + variables: { + basePath: { + default: "", + }, + }, + }, + { + url: "http://localhost:4000/{basePath}", + description: "Local test instance", + variables: { + basePath: { + default: "", + }, + }, + }, + ], + components: { + schemas: { + Consortium: { + type: "object", + required: ["id", "name", "mainApiHost", "members"], + properties: { + id: { + type: "string", + }, + name: { + type: "string", + }, + mainApiHost: { + type: "string", + }, + members: { + type: "array", + items: { + $ref: "#/components/schemas/ConsortiumMember", + }, + minItems: 1, + maxItems: 2048, + nullable: false, + }, + }, + }, + ConsortiumMember: { + type: "object", + required: ["id", "name", "nodes"], + properties: { + id: { + type: "string", + minLength: 1, + maxLength: 2048, + nullable: false, + }, + name: { + type: "string", + minLength: 1, + maxLength: 2048, + nullable: false, + }, + nodes: { + type: "array", + nullable: false, + minItems: 1, + maxItems: 2048, + items: { + $ref: "#/components/schemas/CactusNodeMeta", + }, + }, + }, + }, + CactusNodeMeta: { + description: "A Cactus node meta information", + type: "object", + required: ["nodeApiHost", "publicKeyPem"], + properties: { + nodeApiHost: { + type: "string", + minLength: 1, + maxLength: 1024, + nullable: false, + }, + publicKeyPem: { + description: + "The PEM encoded public key that was used to " + + "generate the JWS included in the response (the jws property)", + type: "string", + minLength: 1, + maxLength: 65535, + nullable: false, + format: + "Must only contain the public key, never include here " + + " the PEM that also contains a private key. See PEM format: " + + "https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail", + }, + }, + }, + CactusNode: { + description: + "A Cactus node can be a single server, or a set of " + + "servers behind a loand balancer acting as one.", + type: "object", + allOf: [ + { + $ref: "#/components/schemas/CactusNodeMeta", + }, + { + type: "object", + required: [ + "id", + "consortiumId", + "nodeApiHost", + "memberId", + "publicKeyPem", + "plugins", + ], + properties: { + id: { + type: "string", + description: + "The unique identifier of a Cactus node. Recommended" + + " to assign a value to this that is guaranteed to be unique" + + " in the whole consortium or better yet, globally anywhere.", + example: "809a76ba-cfb8-4045-a5c6-ed70a7314c25", + minLength: 1, + maxLength: 1024, + nullable: false, + }, + consortiumId: { + type: "string", + description: "ID of the Cactus Consortium this node is in.", + example: "3e2670d9-2d14-45bd-96f5-33e2c4b4e3fb", + minLength: 1, + maxLength: 1024, + nullable: false, + }, + memberId: { + type: "string", + description: + "ID of the Cactus Consortium member this " + + "node is operated by.", + example: "b3674a28-e442-4feb-b1f3-8cbe46c20e5e", + minLength: 1, + maxLength: 1024, + nullable: false, + }, + plugins: { + type: "array", + nullable: false, + minItems: 0, + maxItems: 2048, + default: [], + items: { + $ref: "#/components/schemas/CactusPlugin", + }, + }, + }, + }, + ], + }, + CactusPlugin: { + type: "object", + required: ["id"], + properties: { + id: { + type: "string", + minLength: 1, + maxLength: 1024, + nullable: false, + }, + packageName: { + type: "string", + minLength: 1, + maxLength: 4096, + nullable: false, + }, + }, + }, + JWSCompact: { + description: + "A JSON Web Signature. See: " + + "https://tools.ietf.org/html/rfc7515 for info about standard.", + type: "string", + minLength: 5, + maxLength: 65535, + pattern: `/^[a-zA-Z0-9\-_]+?\.[a-zA-Z0-9\-_]+?\.([a-zA-Z0-9\-_]+)?$/`, + example: + "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjJ9.DOCNCqEMN7CQ_z-RMndiyldljXOk6WFIZxRzNF5Ylg4", + }, + JWSRecipient: { + description: + "A JSON Web Signature. See: " + + "https://tools.ietf.org/html/rfc7515 for info about standard.", + type: "object", + required: ["signature"], + properties: { + signature: { + type: "string", + }, + // In the generated models this shows up as _protected because it is + // a reserved keyword in Typescript. Opened an issue here about this: + // https://github.com/OpenAPITools/openapi-generator/issues/7100 + protected: { + type: "string", + }, + header: { + type: "object", + additionalProperties: true, + }, + }, + }, + JWSGeneral: { + type: "object", + required: ["payload", "signatures"], + properties: { + payload: { + type: "string", + minLength: 1, + maxLength: 65535, + }, + signatures: { + type: "array", + items: { + $ref: "#/components/schemas/JWSRecipient", + }, + }, + }, + }, + GetNodeJwsResponse: { + type: "object", + required: ["jws"], + properties: { + jws: { + description: "The JSON Web Signature of the Cactus node.", + $ref: "#/components/schemas/JWSGeneral", + nullable: false, + }, + }, + }, + GetConsortiumJwsResponse: { + type: "object", + required: ["jws"], + properties: { + jws: { + description: "The JSON Web Signature of the Cactus consortium.", + $ref: "#/components/schemas/JWSGeneral", + nullable: false, + format: "The general format which is a JSON object, not a string.", + }, + }, + }, + }, + }, + paths: { + "/api/v1/plugins/@hyperledger/cactus-plugin-consortium-manual/consortium/jws": { + get: { + summary: "Retrieves a consortium JWS", + description: + "The JWS asserting the consortium metadata (pub keys and hosts of nodes)", + parameters: [], + responses: { + "200": { + description: "OK", + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/GetConsortiumJwsResponse", + }, + }, + }, + }, + }, + }, + }, + "/api/v1/plugins/@hyperledger/cactus-plugin-consortium-manual/node/jws": { + get: { + summary: "Retrieves the JWT of a Cactus Node", + parameters: [], + responses: { + "200": { + description: "OK", + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/GetNodeJwsResponse", + }, + }, + }, + }, + }, + }, + }, + }, +}; + +export async function exportToFileSystemAsJson(): Promise { + const fnTag = "OpenApiSpec#exportToFileSystemAsJson()"; + const fs = await import("fs"); + const packageNameShort = "plugin-consortium-manual"; + const defaultDest = `cactus-openapi-spec-${packageNameShort}.json`; + const destination = process.argv[2] || defaultDest; + + // tslint:disable-next-line: no-console + console.log(`${fnTag} destination=${destination}`); + + fs.writeFileSync(destination, JSON.stringify(CACTUS_OPEN_API_JSON, null, 4)); +} diff --git a/packages/cactus-plugin-web-service-consortium/src/main/typescript/plugin-web-service-consortium.ts b/packages/cactus-plugin-consortium-manual/src/main/typescript/plugin-consortium-manual.ts similarity index 62% rename from packages/cactus-plugin-web-service-consortium/src/main/typescript/plugin-web-service-consortium.ts rename to packages/cactus-plugin-consortium-manual/src/main/typescript/plugin-consortium-manual.ts index d0c1c01699..00dd6947bc 100644 --- a/packages/cactus-plugin-web-service-consortium/src/main/typescript/plugin-web-service-consortium.ts +++ b/packages/cactus-plugin-consortium-manual/src/main/typescript/plugin-consortium-manual.ts @@ -8,35 +8,37 @@ import bodyParser from "body-parser"; import { IPluginWebService, PluginAspect, - IPluginKVStorage, PluginRegistry, IWebServiceEndpoint, } from "@hyperledger/cactus-core-api"; import { Logger, LoggerProvider } from "@hyperledger/cactus-common"; -import { CreateConsortiumEndpointV1 } from "./consortium/create-consortium-endpoint-v1"; +import { GetConsortiumEndpointV1 } from "./consortium/get-consortium-jws-endpoint-v1"; +import { Consortium } from "./generated/openapi/typescript-axios"; +import { GetNodeJwsEndpoint } from "./consortium/get-node-jws-endpoint-v1"; export interface IWebAppOptions { port: number; hostname: string; } -export interface IPluginWebServiceConsortiumOptions { - privateKey: string; +export interface IPluginConsortiumManualOptions { + keyPairPem: string; + consortium: Consortium; pluginRegistry: PluginRegistry; logLevel?: string; webAppOptions?: IWebAppOptions; } -export class PluginWebServiceConsortium implements IPluginWebService { +export class PluginConsortiumManual implements IPluginWebService { private readonly log: Logger; private httpServer: Server | SecureServer | null = null; - constructor(public readonly options: IPluginWebServiceConsortiumOptions) { + constructor(public readonly options: IPluginConsortiumManualOptions) { if (!options) { - throw new Error(`PluginWebServiceConsortium#ctor options falsy.`); + throw new Error(`PluginConsortiumManual#ctor options falsy.`); } this.log = LoggerProvider.getOrCreate({ - label: "plugin-web-service-consortium", + label: "plugin-consortium-manual", }); } @@ -56,7 +58,9 @@ export class PluginWebServiceConsortium implements IPluginWebService { public async installWebServices( expressApp: any ): Promise { - this.log.info(`Installing web services for plugin ${this.getId()}...`); + const { log } = this; + + log.info(`Installing web services for plugin ${this.getId()}...`); const webApp: Express = this.options.webAppOptions ? express() : expressApp; // presence of webAppOptions implies that caller wants the plugin to configure it's own express instance on a custom @@ -82,27 +86,28 @@ export class PluginWebServiceConsortium implements IPluginWebService { this.log.info(`Creation of HTTP server OK`, { address }); } + const { consortium, keyPairPem } = this.options; + const pluginId = this.getId(); + const endpoints: IWebServiceEndpoint[] = []; { - const pluginId = this.getId(); - const path = `/api/v1/plugins/${pluginId}/consortium/`; - const storage = this.options.pluginRegistry.getOneByAspect< - IPluginKVStorage - >(PluginAspect.KV_STORAGE); - const endpoint: IWebServiceEndpoint = new CreateConsortiumEndpointV1({ - path, - hostPlugin: this, - privateKey: this.options.privateKey, - storage, - }); - webApp.post(endpoint.getPath(), endpoint.getExpressRequestHandler()); + const path = `/api/v1/plugins/${pluginId}/consortium/jws`; + const options = { path, keyPairPem, consortium }; + const endpoint = new GetConsortiumEndpointV1(options); + webApp.get(endpoint.getPath(), endpoint.getExpressRequestHandler()); + endpoints.push(endpoint); + this.log.info(`Registered contract deployment endpoint at ${path}`); + } + { + const path = `/api/v1/plugins/${pluginId}/node/jws`; + const options = { path, keyPairPem, consortium }; + const endpoint = new GetNodeJwsEndpoint(options); + webApp.get(endpoint.getPath(), endpoint.getExpressRequestHandler()); endpoints.push(endpoint); this.log.info(`Registered contract deployment endpoint at ${path}`); } - this.log.info(`Installed web services for plugin ${this.getId()} OK`, { - endpoints, - }); + log.info(`Installed web svcs for plugin ${this.getId()} OK`, { endpoints }); return endpoints; } @@ -111,10 +116,10 @@ export class PluginWebServiceConsortium implements IPluginWebService { } public getId(): string { - return `@hyperledger/cactus-plugin-web-service-consortium`; + return `@hyperledger/cactus-plugin-consortium-manual`; } public getAspect(): PluginAspect { - return PluginAspect.WEB_SERVICE; + return PluginAspect.CONSORTIUM; } } diff --git a/packages/cactus-plugin-consortium-manual/src/main/typescript/plugin-factory-consortium-manual.ts b/packages/cactus-plugin-consortium-manual/src/main/typescript/plugin-factory-consortium-manual.ts new file mode 100644 index 0000000000..f13c86f5b2 --- /dev/null +++ b/packages/cactus-plugin-consortium-manual/src/main/typescript/plugin-factory-consortium-manual.ts @@ -0,0 +1,16 @@ +import { PluginFactory } from "@hyperledger/cactus-core-api"; +import { + IPluginConsortiumManualOptions, + PluginConsortiumManual, +} from "./plugin-consortium-manual"; + +export class PluginFactoryWebService extends PluginFactory< + PluginConsortiumManual, + IPluginConsortiumManualOptions +> { + async create( + options: IPluginConsortiumManualOptions + ): Promise { + return new PluginConsortiumManual(options); + } +} diff --git a/packages/cactus-plugin-consortium-manual/src/main/typescript/public-api.ts b/packages/cactus-plugin-consortium-manual/src/main/typescript/public-api.ts new file mode 100755 index 0000000000..3e0c769817 --- /dev/null +++ b/packages/cactus-plugin-consortium-manual/src/main/typescript/public-api.ts @@ -0,0 +1,26 @@ +export { + GetConsortiumEndpointV1, + IGetConsortiumJwsEndpointOptions, +} from "./consortium/get-consortium-jws-endpoint-v1"; + +export { + GetNodeJwsEndpoint, + IGetNodeJwsEndpointOptions, +} from "./consortium/get-node-jws-endpoint-v1"; + +export { + PluginConsortiumManual, + IPluginConsortiumManualOptions, + IWebAppOptions, +} from "./plugin-consortium-manual"; + +export * from "./generated/openapi/typescript-axios/index"; + +export { PluginFactoryWebService } from "./plugin-factory-consortium-manual"; + +import { PluginFactoryWebService } from "./plugin-factory-consortium-manual"; +export async function createPluginFactory( + options?: any +): Promise { + return new PluginFactoryWebService(); +} diff --git a/packages/cactus-plugin-web-service-consortium/src/test/typescript/integration/api-surface.ts b/packages/cactus-plugin-consortium-manual/src/test/typescript/integration/api-surface.ts similarity index 100% rename from packages/cactus-plugin-web-service-consortium/src/test/typescript/integration/api-surface.ts rename to packages/cactus-plugin-consortium-manual/src/test/typescript/integration/api-surface.ts diff --git a/packages/cactus-plugin-web-service-consortium/src/test/typescript/unit/api-surface.ts b/packages/cactus-plugin-consortium-manual/src/test/typescript/unit/api-surface.ts similarity index 100% rename from packages/cactus-plugin-web-service-consortium/src/test/typescript/unit/api-surface.ts rename to packages/cactus-plugin-consortium-manual/src/test/typescript/unit/api-surface.ts diff --git a/packages/cactus-plugin-consortium-manual/src/test/typescript/unit/consortium/get-node-jws-endpoint-v1.test.ts b/packages/cactus-plugin-consortium-manual/src/test/typescript/unit/consortium/get-node-jws-endpoint-v1.test.ts new file mode 100644 index 0000000000..535a49e563 --- /dev/null +++ b/packages/cactus-plugin-consortium-manual/src/test/typescript/unit/consortium/get-node-jws-endpoint-v1.test.ts @@ -0,0 +1,71 @@ +import test, { Test } from "tape"; +import { JWS, JWK } from "jose"; +import { v4 as uuidV4 } from "uuid"; + +import { + GetNodeJwsEndpoint, + IGetNodeJwsEndpointOptions, + CactusNode, + Consortium, + ConsortiumMember, +} from "../../../../main/typescript/public-api"; + +test("Can provide JWS", async (t: Test) => { + t.ok(GetNodeJwsEndpoint); + + const keyPair = await JWK.generate("EC", "secp256k1", { use: "sig" }, true); + const keyPairPem = keyPair.toPEM(true); + + const consortiumName = "Example Corp. & Friends Crypto Consortium"; + const consortiumId = uuidV4(); + const memberId = uuidV4(); + const nodeId = uuidV4(); + + const cactusNode: CactusNode = { + nodeApiHost: "http://127.0.0.1:80", + memberId, + publicKeyPem: keyPair.toPEM(false), + consortiumId, + id: nodeId, + plugins: [], + }; + + const member: ConsortiumMember = { + id: memberId, + nodes: [cactusNode], + name: "Example Corp", + }; + + const consortium: Consortium = { + id: consortiumId, + name: consortiumName, + mainApiHost: "http://127.0.0.1:80", + members: [member], + }; + + const epOpts: IGetNodeJwsEndpointOptions = { + consortium, + keyPairPem, + path: "/some-fake-path-for-http-requests", + }; + const pubKeyPem = keyPair.toPEM(false); + + const ep = new GetNodeJwsEndpoint(epOpts); + + const jws = await ep.createJws(); + t.ok(jws, "created JWS is truthy"); + t.ok(typeof jws === "object", "created JWS is an object"); + + t.doesNotThrow(() => JWS.verify(jws, pubKeyPem), "JWS verified OK"); + t.doesNotThrow(() => JWS.verify(jws, keyPair), "JWS verified OK"); + + const payload = JWS.verify(jws, pubKeyPem) as any; + t.ok(payload, "JWS verified payload truthy"); + if (typeof payload === "string") { + t.fail(`JWS Verification result: ${payload}`); + } else { + t.ok(payload.consortium, "JWS payload.consortium truthy"); + } + + t.end(); +}); diff --git a/packages/cactus-plugin-web-service-consortium/tsconfig.json b/packages/cactus-plugin-consortium-manual/tsconfig.json similarity index 100% rename from packages/cactus-plugin-web-service-consortium/tsconfig.json rename to packages/cactus-plugin-consortium-manual/tsconfig.json diff --git a/packages/cactus-plugin-web-service-consortium/.gitignore b/packages/cactus-plugin-web-service-consortium/.gitignore deleted file mode 100644 index 19e8edaa4c..0000000000 --- a/packages/cactus-plugin-web-service-consortium/.gitignore +++ /dev/null @@ -1 +0,0 @@ -cactus-openapi-spec-plugin-web-service-consortium.json diff --git a/packages/cactus-plugin-web-service-consortium/src/main/typescript/consortium/create-consortium-endpoint-v1.ts b/packages/cactus-plugin-web-service-consortium/src/main/typescript/consortium/create-consortium-endpoint-v1.ts deleted file mode 100644 index 105072376c..0000000000 --- a/packages/cactus-plugin-web-service-consortium/src/main/typescript/consortium/create-consortium-endpoint-v1.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { Request, Response, NextFunction } from "express"; -import secp256k1 from "secp256k1"; -import { keccak256 } from "js-sha3"; - -import { - IWebServiceEndpoint, - IExpressRequestHandler, - ICactusPlugin, -} from "@hyperledger/cactus-core-api"; -import { IPluginKVStorage } from "@hyperledger/cactus-core-api"; -import { Consortium } from "../generated/openapi/typescript-axios"; - -export interface ICreateConsortiumEndpointOptions { - storage: IPluginKVStorage; - privateKey: string; - path: string; - hostPlugin: ICactusPlugin; -} - -export class CreateConsortiumEndpointV1 implements IWebServiceEndpoint { - constructor(public readonly options: ICreateConsortiumEndpointOptions) { - if (!options) { - throw new Error(`CreateConsortiumEndpointV1#ctor options falsy.`); - } - if (!options.privateKey) { - throw new Error( - `CreateConsortiumEndpointV1#ctor options.privateKey falsy.` - ); - } - if (!options.storage) { - throw new Error(`CreateConsortiumEndpointV1#ctor options.storage falsy.`); - } - } - - public getExpressRequestHandler(): IExpressRequestHandler { - return this.handleRequest.bind(this); - } - - getPath(): string { - return this.options.path; - } - - async handleRequest( - req: Request, - res: Response, - next: NextFunction - ): Promise { - try { - const consortium: Consortium = req.body; - const idAlreadyExists = await this.options.storage.has(consortium.id); - if (idAlreadyExists) { - res.status(400); - res.json({ - success: false, - message: `Consortium with ID ${consortium.id} already exists.`, - }); - } else { - // FIXME: We need a library handling the crypto, how about NodeJS bindings for Ursa? - const privateKey = this.options.privateKey; - const privateKeyBytes = Uint8Array.from(Buffer.from(privateKey, "hex")); - const consortiumJson: string = JSON.stringify(consortium); - const consortiumBytesHash = Uint8Array.from( - keccak256.array(consortiumJson) - ); - const signatureWrapper = secp256k1.ecdsaSign( - consortiumBytesHash, - privateKeyBytes - ); - const signature = Buffer.from(signatureWrapper.signature).toString( - "hex" - ); - const consortiumWrapper = { - signature, - consortiumJson, - }; - const wrapperJson = JSON.stringify(consortiumWrapper); - // tslint:disable-next-line: no-console - await this.options.storage.set(consortium.id, wrapperJson); - res.status(201); - res.json({ success: true, consortiumWrapper }); - } - } catch (ex) { - res.status(500); - res.json({ error: ex.stack }); - } - } -} diff --git a/packages/cactus-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/api.ts b/packages/cactus-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/api.ts deleted file mode 100644 index 9d78801cb7..0000000000 --- a/packages/cactus-plugin-web-service-consortium/src/main/typescript/generated/openapi/typescript-axios/api.ts +++ /dev/null @@ -1,254 +0,0 @@ -// tslint:disable -/** - * Hyperledger Cactus Plugin - Consortium Web Service - * Manage a Cactus consortium through the APIs. Needs administrative priviliges. - * - * The version of the OpenAPI document: 0.0.1 - * - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - - -import * as globalImportUrl from 'url'; -import { Configuration } from './configuration'; -import globalAxios, { AxiosPromise, AxiosInstance } from 'axios'; -// Some imports not used depending on template conditions -// @ts-ignore -import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError } from './base'; - -/** - * - * @export - * @interface CactusNode - */ -export interface CactusNode { - /** - * - * @type {string} - * @memberof CactusNode - */ - host: string; - /** - * - * @type {string} - * @memberof CactusNode - */ - publicKey?: string; -} -/** - * - * @export - * @interface Consortium - */ -export interface Consortium { - /** - * - * @type {string} - * @memberof Consortium - */ - id: string; - /** - * - * @type {string} - * @memberof Consortium - */ - name: string; - /** - * - * @type {string} - * @memberof Consortium - */ - configurationEndpoint: string; - /** - * - * @type {Array} - * @memberof Consortium - */ - cactusNodes?: Array; -} - -/** - * DefaultApi - axios parameter creator - * @export - */ -export const DefaultApiAxiosParamCreator = function (configuration?: Configuration) { - return { - /** - * The metadata of the consortium (minus the sensitive data) - * @summary Retrieves a consortium - * @param {string} consortiumId - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumConsortiumIdGet(consortiumId: string, options: any = {}): RequestArgs { - // verify required parameter 'consortiumId' is not null or undefined - if (consortiumId === null || consortiumId === undefined) { - throw new RequiredError('consortiumId','Required parameter consortiumId was null or undefined when calling apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumConsortiumIdGet.'); - } - const localVarPath = `/api/v1/plugins/@hyperledger/cactus-plugin-web-service-consortium/consortium/{consortiumId}` - .replace(`{${"consortiumId"}}`, encodeURIComponent(String(consortiumId))); - const localVarUrlObj = globalImportUrl.parse(localVarPath, true); - let baseOptions; - if (configuration) { - baseOptions = configuration.baseOptions; - } - const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; - const localVarHeaderParameter = {} as any; - const localVarQueryParameter = {} as any; - - - - localVarUrlObj.query = {...localVarUrlObj.query, ...localVarQueryParameter, ...options.query}; - // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 - delete localVarUrlObj.search; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; - - return { - url: globalImportUrl.format(localVarUrlObj), - options: localVarRequestOptions, - }; - }, - /** - * - * @summary Creates a new consortium from scratch based on the provided parameters. - * @param {Consortium} consortium - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumPost(consortium: Consortium, options: any = {}): RequestArgs { - // verify required parameter 'consortium' is not null or undefined - if (consortium === null || consortium === undefined) { - throw new RequiredError('consortium','Required parameter consortium was null or undefined when calling apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumPost.'); - } - const localVarPath = `/api/v1/plugins/@hyperledger/cactus-plugin-web-service-consortium/consortium`; - const localVarUrlObj = globalImportUrl.parse(localVarPath, true); - let baseOptions; - if (configuration) { - baseOptions = configuration.baseOptions; - } - const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; - const localVarHeaderParameter = {} as any; - const localVarQueryParameter = {} as any; - - - - localVarHeaderParameter['Content-Type'] = 'application/json'; - - localVarUrlObj.query = {...localVarUrlObj.query, ...localVarQueryParameter, ...options.query}; - // fix override query string Detail: https://stackoverflow.com/a/7517673/1077943 - delete localVarUrlObj.search; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...options.headers}; - const needsSerialization = (typeof consortium !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json'; - localVarRequestOptions.data = needsSerialization ? JSON.stringify(consortium !== undefined ? consortium : {}) : (consortium || ""); - - return { - url: globalImportUrl.format(localVarUrlObj), - options: localVarRequestOptions, - }; - }, - } -}; - -/** - * DefaultApi - functional programming interface - * @export - */ -export const DefaultApiFp = function(configuration?: Configuration) { - return { - /** - * The metadata of the consortium (minus the sensitive data) - * @summary Retrieves a consortium - * @param {string} consortiumId - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumConsortiumIdGet(consortiumId: string, options?: any): (axios?: AxiosInstance, basePath?: string) => AxiosPromise { - const localVarAxiosArgs = DefaultApiAxiosParamCreator(configuration).apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumConsortiumIdGet(consortiumId, options); - return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { - const axiosRequestArgs = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url}; - return axios.request(axiosRequestArgs); - }; - }, - /** - * - * @summary Creates a new consortium from scratch based on the provided parameters. - * @param {Consortium} consortium - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumPost(consortium: Consortium, options?: any): (axios?: AxiosInstance, basePath?: string) => AxiosPromise { - const localVarAxiosArgs = DefaultApiAxiosParamCreator(configuration).apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumPost(consortium, options); - return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { - const axiosRequestArgs = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url}; - return axios.request(axiosRequestArgs); - }; - }, - } -}; - -/** - * DefaultApi - factory interface - * @export - */ -export const DefaultApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { - return { - /** - * The metadata of the consortium (minus the sensitive data) - * @summary Retrieves a consortium - * @param {string} consortiumId - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumConsortiumIdGet(consortiumId: string, options?: any): AxiosPromise { - return DefaultApiFp(configuration).apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumConsortiumIdGet(consortiumId, options)(axios, basePath); - }, - /** - * - * @summary Creates a new consortium from scratch based on the provided parameters. - * @param {Consortium} consortium - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumPost(consortium: Consortium, options?: any): AxiosPromise { - return DefaultApiFp(configuration).apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumPost(consortium, options)(axios, basePath); - }, - }; -}; - -/** - * DefaultApi - object-oriented interface - * @export - * @class DefaultApi - * @extends {BaseAPI} - */ -export class DefaultApi extends BaseAPI { - /** - * The metadata of the consortium (minus the sensitive data) - * @summary Retrieves a consortium - * @param {string} consortiumId - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof DefaultApi - */ - public apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumConsortiumIdGet(consortiumId: string, options?: any) { - return DefaultApiFp(this.configuration).apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumConsortiumIdGet(consortiumId, options)(this.axios, this.basePath); - } - - /** - * - * @summary Creates a new consortium from scratch based on the provided parameters. - * @param {Consortium} consortium - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof DefaultApi - */ - public apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumPost(consortium: Consortium, options?: any) { - return DefaultApiFp(this.configuration).apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumPost(consortium, options)(this.axios, this.basePath); - } - -} - - diff --git a/packages/cactus-plugin-web-service-consortium/src/main/typescript/openapi-spec.ts b/packages/cactus-plugin-web-service-consortium/src/main/typescript/openapi-spec.ts deleted file mode 100644 index 6250129d21..0000000000 --- a/packages/cactus-plugin-web-service-consortium/src/main/typescript/openapi-spec.ts +++ /dev/null @@ -1,134 +0,0 @@ -import * as OpenAPI from "express-openapi-validator/dist/framework/types"; - -export const CACTUS_OPEN_API_JSON: OpenAPI.OpenAPIV3.Document = { - openapi: "3.0.3", - info: { - title: "Hyperledger Cactus Plugin - Consortium Web Service", - description: - "Manage a Cactus consortium through the APIs. Needs administrative priviliges.", - version: "0.0.1", - }, - servers: [ - { - url: "https://www.cactus.stream/{basePath}", - description: "Public test instance", - variables: { - basePath: { - default: "", - }, - }, - }, - { - url: "http://localhost:4000/{basePath}", - description: "Local test instance", - variables: { - basePath: { - default: "", - }, - }, - }, - ], - components: { - schemas: { - Consortium: { - type: "object", - properties: { - id: { - type: "string", - }, - name: { - type: "string", - }, - configurationEndpoint: { - type: "string", - }, - cactusNodes: { - type: "array", - minItems: 1, - items: { - $ref: "#/components/schemas/CactusNode", - }, - }, - }, - required: ["id", "name", "configurationEndpoint"], - }, - CactusNode: { - type: "object", - properties: { - host: { - type: "string", - }, - publicKey: { - type: "string", - }, - }, - required: ["host", "publicKeyHex"], - }, - }, - }, - paths: { - "/api/v1/plugins/@hyperledger/cactus-plugin-web-service-consortium/consortium": { - post: { - summary: - "Creates a new consortium from scratch based on the provided parameters.", - requestBody: { - required: true, - content: { - "application/json": { - schema: { - $ref: "#/components/schemas/Consortium", - }, - }, - }, - }, - responses: { - "201": { - description: "Created", - }, - }, - }, - }, - "/api/v1/plugins/@hyperledger/cactus-plugin-web-service-consortium/consortium/{consortiumId}": { - get: { - summary: "Retrieves a consortium", - description: - "The metadata of the consortium (minus the sensitive data)", - parameters: [ - { - in: "path", - name: "consortiumId", - required: true, - schema: { - type: "string", - }, - }, - ], - responses: { - "200": { - description: "OK", - content: { - "application/json": { - schema: { - $ref: "#/components/schemas/Consortium", - }, - }, - }, - }, - }, - }, - }, - }, -}; - -export async function exportToFileSystemAsJson(): Promise { - const fs = await import("fs"); - const destination = - process.argv[2] || - "./cactus-openapi-spec-plugin-web-service-consortium.json"; - - // tslint:disable-next-line: no-console - console.log( - `OpenApiSpec#exportToFileSystemAsJson() destination=${destination}` - ); - fs.writeFileSync(destination, JSON.stringify(CACTUS_OPEN_API_JSON, null, 4)); -} diff --git a/packages/cactus-plugin-web-service-consortium/src/main/typescript/plugin-factory-web-service-consortium.ts b/packages/cactus-plugin-web-service-consortium/src/main/typescript/plugin-factory-web-service-consortium.ts deleted file mode 100644 index 78c7933390..0000000000 --- a/packages/cactus-plugin-web-service-consortium/src/main/typescript/plugin-factory-web-service-consortium.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { PluginFactory } from "@hyperledger/cactus-core-api"; -import { - IPluginWebServiceConsortiumOptions, - PluginWebServiceConsortium, -} from "./plugin-web-service-consortium"; - -export class PluginFactoryWebService extends PluginFactory< - PluginWebServiceConsortium, - IPluginWebServiceConsortiumOptions -> { - async create( - options: IPluginWebServiceConsortiumOptions - ): Promise { - return new PluginWebServiceConsortium(options); - } -} diff --git a/packages/cactus-plugin-web-service-consortium/src/main/typescript/public-api.ts b/packages/cactus-plugin-web-service-consortium/src/main/typescript/public-api.ts deleted file mode 100755 index 439f4dd0cd..0000000000 --- a/packages/cactus-plugin-web-service-consortium/src/main/typescript/public-api.ts +++ /dev/null @@ -1,14 +0,0 @@ -export { - PluginWebServiceConsortium, - IPluginWebServiceConsortiumOptions, - IWebAppOptions, -} from "./plugin-web-service-consortium"; -export { PluginFactoryWebService } from "./plugin-factory-web-service-consortium"; -export * from "./generated/openapi/typescript-axios/index"; - -import { PluginFactoryWebService } from "./plugin-factory-web-service-consortium"; -export async function createPluginFactory( - options?: any -): Promise { - return new PluginFactoryWebService(); -} diff --git a/packages/cactus-test-plugin-web-service-consortium/README.md b/packages/cactus-test-plugin-consortium-manual/README.md similarity index 89% rename from packages/cactus-test-plugin-web-service-consortium/README.md rename to packages/cactus-test-plugin-consortium-manual/README.md index 15a40a17b0..e8dc5d3de8 100644 --- a/packages/cactus-test-plugin-web-service-consortium/README.md +++ b/packages/cactus-test-plugin-consortium-manual/README.md @@ -1,4 +1,4 @@ -# `@hyperledger/cactus-test-plugin-web-service-consortium` +# `@hyperledger/cactus-test-plugin-consortium-manual` ## Usage diff --git a/packages/cactus-test-plugin-web-service-consortium/package-lock.json b/packages/cactus-test-plugin-consortium-manual/package-lock.json similarity index 98% rename from packages/cactus-test-plugin-web-service-consortium/package-lock.json rename to packages/cactus-test-plugin-consortium-manual/package-lock.json index 56debf5cb2..3f81f9c2c1 100644 --- a/packages/cactus-test-plugin-web-service-consortium/package-lock.json +++ b/packages/cactus-test-plugin-consortium-manual/package-lock.json @@ -1,9 +1,14 @@ { - "name": "@hyperledger/cactus-test-plugin-web-service-consortium", + "name": "@hyperledger/cactus-test-plugin-consortium-manual", "version": "0.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { + "@panva/asn1.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", + "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==" + }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -31,6 +36,18 @@ "integrity": "sha512-1TQNDJvIKlgYXGNIABfgFp9y0FziDpuGrd799Q5RcnsDu+krD+eeW/0Fs5PHARvWWFelOhIG2OPCo6KbadBM4A==", "dev": true }, + "@types/json-stable-stringify": { + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/@types/json-stable-stringify/-/json-stable-stringify-1.0.32.tgz", + "integrity": "sha512-q9Q6+eUEGwQkv4Sbst3J4PNgDOvpuVuKj79Hl/qnmBMEIPzB5QoFRUtjcgcg2xNUZyYUGXBk5wYIBKHt0A+Mxw==", + "dev": true + }, + "@types/lodash": { + "version": "4.14.158", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.158.tgz", + "integrity": "sha512-InCEXJNTv/59yO4VSfuvNrZHt7eeNtWQEgnieIA+mIC+MOWM9arOWG2eQ8Vhk6NbOre6/BidiXhkZYeDY9U35w==", + "dev": true + }, "@types/node": { "version": "10.17.21", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.21.tgz", @@ -1228,6 +1245,14 @@ "topo": "3.x.x" } }, + "jose": { + "version": "1.27.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-1.27.2.tgz", + "integrity": "sha512-zLIwnMa8dh5A2jFo56KvhiXCaW0hFjdNvG0I5GScL8Wro+/r/SnyIYTbnX3fYztPNSfgQp56sDMHUuS9c3e6bw==", + "requires": { + "@panva/asn1.js": "^1.0.0" + } + }, "js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", @@ -1253,6 +1278,14 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "requires": { + "jsonify": "~0.0.0" + } + }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -1266,6 +1299,11 @@ "graceful-fs": "^4.1.6" } }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -1303,6 +1341,11 @@ "json-buffer": "3.0.0" } }, + "lodash": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" + }, "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", diff --git a/packages/cactus-test-plugin-web-service-consortium/package.json b/packages/cactus-test-plugin-consortium-manual/package.json similarity index 79% rename from packages/cactus-test-plugin-web-service-consortium/package.json rename to packages/cactus-test-plugin-consortium-manual/package.json index e453dbd9d5..269efc1c08 100644 --- a/packages/cactus-test-plugin-web-service-consortium/package.json +++ b/packages/cactus-test-plugin-consortium-manual/package.json @@ -1,11 +1,11 @@ { - "name": "@hyperledger/cactus-test-plugin-web-service-consortium", + "name": "@hyperledger/cactus-test-plugin-consortium-manual", "version": "0.2.0", "description": "Integration tests for the Quorum ledger and the API server.", - "main": "dist/cactus-test-plugin-web-service-consortium-quorum.node.umd.js", - "mainMinified": "dist/cactus-test-plugin-web-service-consortium-quorum.node.umd.min.js", - "browser": "dist/cactus-test-plugin-web-service-consortium-quorum.web.umd.js", - "browserMinified": "dist/cactus-test-plugin-web-service-consortium-quorum.web.umd.min.js", + "main": "dist/cactus-test-plugin-consortium-manual-quorum.node.umd.js", + "mainMinified": "dist/cactus-test-plugin-consortium-manual-quorum.node.umd.min.js", + "browser": "dist/cactus-test-plugin-consortium-manual-quorum.web.umd.js", + "browserMinified": "dist/cactus-test-plugin-consortium-manual-quorum.web.umd.min.js", "module": "dist/lib/main/typescript/index.js", "types": "dist/types/main/typescript/index.d.ts", "files": [ @@ -13,13 +13,10 @@ ], "scripts": { "tsc": "tsc --project ./tsconfig.json", - "webpack": "npm-run-all webpack:dev webpack:prod", - "webpack:dev": "npm-run-all webpack:dev:node webpack:dev:web", "webpack:dev:web": "webpack --env=dev --target=web --config ../../webpack.config.js", "webpack:dev:node": "webpack --env=dev --target=node --config ../../webpack.config.js", - "webpack:prod": "npm-run-all webpack:prod:node webpack:prod:web", "webpack:prod:web": "webpack --env=prod --target=web --config ../../webpack.config.js", "webpack:prod:node": "webpack --env=prod --target=node --config ../../webpack.config.js" @@ -68,12 +65,15 @@ "@hyperledger/cactus-cmd-api-server": "^0.2.0", "@hyperledger/cactus-common": "0.2.0", "@hyperledger/cactus-core-api": "^0.2.0", + "@hyperledger/cactus-plugin-consortium-manual": "^0.2.0", "@hyperledger/cactus-plugin-kv-storage-memory": "0.2.0", "@hyperledger/cactus-plugin-ledger-connector-quorum": "^0.2.0", - "@hyperledger/cactus-plugin-web-service-consortium": "0.2.0", "@hyperledger/cactus-sdk": "0.2.0", "axios": "0.19.2", "joi": "14.3.1", + "jose": "1.27.2", + "json-stable-stringify": "1.0.1", + "lodash": "4.17.19", "typescript-optional": "2.0.1", "web3": "1.2.7", "web3-eth-contract": "1.2.7", @@ -82,6 +82,8 @@ "devDependencies": { "@hyperledger/cactus-common": "0.2.0", "@hyperledger/cactus-test-tooling": "0.2.0", - "@types/joi": "14.3.4" + "@types/joi": "14.3.4", + "@types/json-stable-stringify": "1.0.32", + "@types/lodash": "4.14.158" } } diff --git a/packages/cactus-test-plugin-web-service-consortium/src/main/typescript/index.ts b/packages/cactus-test-plugin-consortium-manual/src/main/typescript/index.ts similarity index 100% rename from packages/cactus-test-plugin-web-service-consortium/src/main/typescript/index.ts rename to packages/cactus-test-plugin-consortium-manual/src/main/typescript/index.ts diff --git a/packages/cactus-test-plugin-web-service-consortium/src/main/typescript/index.web.ts b/packages/cactus-test-plugin-consortium-manual/src/main/typescript/index.web.ts similarity index 100% rename from packages/cactus-test-plugin-web-service-consortium/src/main/typescript/index.web.ts rename to packages/cactus-test-plugin-consortium-manual/src/main/typescript/index.web.ts diff --git a/packages/cactus-test-plugin-web-service-consortium/src/main/typescript/public-api.ts b/packages/cactus-test-plugin-consortium-manual/src/main/typescript/public-api.ts similarity index 100% rename from packages/cactus-test-plugin-web-service-consortium/src/main/typescript/public-api.ts rename to packages/cactus-test-plugin-consortium-manual/src/main/typescript/public-api.ts diff --git a/packages/cactus-test-plugin-web-service-consortium/src/test/typescript/integration/api-surface.ts b/packages/cactus-test-plugin-consortium-manual/src/test/typescript/integration/api-surface.ts similarity index 100% rename from packages/cactus-test-plugin-web-service-consortium/src/test/typescript/integration/api-surface.ts rename to packages/cactus-test-plugin-consortium-manual/src/test/typescript/integration/api-surface.ts diff --git a/packages/cactus-test-plugin-consortium-manual/src/test/typescript/integration/plugin-consortium-manual/get-consortium-jws-endpoint.test.ts b/packages/cactus-test-plugin-consortium-manual/src/test/typescript/integration/plugin-consortium-manual/get-consortium-jws-endpoint.test.ts new file mode 100644 index 0000000000..55aaf74c1a --- /dev/null +++ b/packages/cactus-test-plugin-consortium-manual/src/test/typescript/integration/plugin-consortium-manual/get-consortium-jws-endpoint.test.ts @@ -0,0 +1,327 @@ +import { createServer } from "http"; +import { AddressInfo } from "net"; + +import test, { Test } from "tape"; +import { JWK, JWS } from "jose"; +import { v4 as uuidV4 } from "uuid"; + +import { ApiServer, ConfigService } from "@hyperledger/cactus-cmd-api-server"; +import { Configuration, ApiClient } from "@hyperledger/cactus-sdk"; +import { + IPluginConsortiumManualOptions, + PluginConsortiumManual, + CactusNode, + CactusNodeMeta, + DefaultApi, + Consortium, +} from "@hyperledger/cactus-plugin-consortium-manual"; +import { PluginRegistry } from "@hyperledger/cactus-core-api"; + +test("member node public keys and hosts are pre-shared", async (t: Test) => { + const consortiumId = uuidV4(); + const consortiumName = "Example Corp. & Friends Crypto Consortium"; + + const memberId1 = uuidV4(); + const memberId2 = uuidV4(); + const memberId3 = uuidV4(); + + const httpServer1 = createServer(); + await new Promise((resolve, reject) => { + httpServer1.once("error", reject); + httpServer1.once("listening", resolve); + httpServer1.listen(0, "127.0.0.1"); + }); + const addressInfo1 = httpServer1.address() as AddressInfo; + t.comment(`HttpServer1 AddressInfo: ${JSON.stringify(addressInfo1)}`); + const node1Host = `http://${addressInfo1.address}:${addressInfo1.port}`; + t.comment(`Cactus Node 1 Host: ${node1Host}`); + + const keyPair1 = await JWK.generate("EC", "secp256k1"); + const pubKeyPem1 = keyPair1.toPEM(false); + t.comment(`Cactus Node 1 Public Key PEM: ${pubKeyPem1}`); + + const httpServer2 = createServer(); + await new Promise((resolve, reject) => { + httpServer2.once("error", reject); + httpServer2.once("listening", resolve); + httpServer2.listen(0, "127.0.0.1"); + }); + const addressInfo2 = httpServer2.address() as AddressInfo; + t.comment(`HttpServer2 AddressInfo: ${JSON.stringify(addressInfo2)}`); + const node2Host = `http://${addressInfo2.address}:${addressInfo2.port}`; + + const keyPair2 = await JWK.generate("EC", "secp256k1"); + const pubKeyPem2 = keyPair2.toPEM(false); + t.comment(`Cactus Node 2 Public Key PEM: ${pubKeyPem2}`); + + const httpServer3 = createServer(); + await new Promise((resolve, reject) => { + httpServer3.once("error", reject); + httpServer3.once("listening", resolve); + httpServer3.listen(0, "127.0.0.1"); + }); + const addressInfo3 = httpServer3.address() as AddressInfo; + t.comment(`HttpServer3 AddressInfo: ${JSON.stringify(addressInfo3)}`); + const node3Host = `http://${addressInfo3.address}:${addressInfo3.port}`; + + const keyPair3 = await JWK.generate("EC", "secp256k1"); + const pubKeyPem3 = keyPair3.toPEM(false); + t.comment(`Cactus Node 3 Public Key PEM: ${pubKeyPem3}`); + + const consortium: Consortium = { + id: consortiumId, + mainApiHost: node1Host, + name: consortiumName, + members: [ + { + id: memberId1, + name: "Example Corp 1", + nodes: [ + { + nodeApiHost: node1Host, + publicKeyPem: pubKeyPem1, + }, + ], + }, + { + id: memberId2, + name: "Example Corp 2", + nodes: [ + { + nodeApiHost: node2Host, + publicKeyPem: pubKeyPem2, + }, + ], + }, + { + id: memberId3, + name: "Example Corp 3", + nodes: [ + { + nodeApiHost: node3Host, + publicKeyPem: pubKeyPem3, + }, + ], + }, + ], + }; + + t.comment(`Setting up first node...`); + { + // 2. Instantiate plugin registry which will provide the web service plugin with the key value storage plugin + const pluginRegistry = new PluginRegistry({ plugins: [] }); + + // 3. Instantiate the web service consortium plugin + const options: IPluginConsortiumManualOptions = { + pluginRegistry, + keyPairPem: keyPair1.toPEM(true), + consortium, + logLevel: "trace", + }; + const pluginConsortiumManual = new PluginConsortiumManual(options); + + // 4. Create the API Server object that we embed in this test + const configService = new ConfigService(); + const apiServerOptions = configService.newExampleConfig(); + apiServerOptions.configFile = ""; + apiServerOptions.apiCorsDomainCsv = "*"; + apiServerOptions.apiPort = addressInfo1.port; + apiServerOptions.cockpitPort = 0; + apiServerOptions.apiTlsEnabled = false; + const config = configService.newExampleConfigConvict(apiServerOptions); + + pluginRegistry.add(pluginConsortiumManual); + + const apiServer = new ApiServer({ + httpServerApi: httpServer1, + config: config.getProperties(), + pluginRegistry, + }); + + // 5. make sure the API server is shut down when the testing if finished. + test.onFinish(() => apiServer.shutdown()); + + // 6. Start the API server which is now listening on port A and it's healthcheck works through the main SDK + await apiServer.start(); + + // 7. Instantiate the main SDK dynamically with whatever port the API server ended up bound to (port 0) + t.comment(`AddressInfo: ${JSON.stringify(addressInfo1)}`); + + const configuration = new Configuration({ basePath: node1Host }); + const api = new ApiClient(configuration).extendWith(DefaultApi); + const res = await api.apiV1PluginsHyperledgerCactusPluginConsortiumManualNodeJwsGet(); + t.ok(res, "API response object is truthy"); + t.equal(res.status, 200, "Node JWS response status code is 200"); + t.ok(res.data, "Node JWS response.body is truthy"); + t.ok(res.data.jws, "Node JWS response.body.jws is truthy"); + const payload = JWS.verify(res.data.jws, keyPair1.toPEM(false)); + t.ok(payload, "Verified Node JWS payload is truthy"); + t.comment(`Node1 JWS Payload: ${JSON.stringify(payload)}`); + } + t.comment(`Set up first node OK`); + + t.comment(`Setting up second node...`); + { + const pluginRegistry = new PluginRegistry({ plugins: [] }); + + const options: IPluginConsortiumManualOptions = { + pluginRegistry, + keyPairPem: keyPair2.toPEM(true), + consortium, + logLevel: "trace", + }; + const pluginConsortiumManual = new PluginConsortiumManual(options); + + // 4. Create the API Server object that we embed in this test + const configService = new ConfigService(); + const apiServerOptions = configService.newExampleConfig(); + apiServerOptions.configFile = ""; + apiServerOptions.apiCorsDomainCsv = "*"; + apiServerOptions.apiPort = addressInfo2.port; + apiServerOptions.cockpitPort = 0; + apiServerOptions.apiTlsEnabled = false; + const config = configService.newExampleConfigConvict(apiServerOptions); + + pluginRegistry.add(pluginConsortiumManual); + + const apiServer = new ApiServer({ + httpServerApi: httpServer2, + config: config.getProperties(), + pluginRegistry, + }); + + // 5. make sure the API server is shut down when the testing if finished. + test.onFinish(() => apiServer.shutdown()); + + // 6. Start the API server which is now listening on port A and it's healthcheck works through the main SDK + await apiServer.start(); + + t.comment(`AddressInfo: ${JSON.stringify(addressInfo2)}`); + + const configuration = new Configuration({ basePath: node2Host }); + const api = new ApiClient(configuration).extendWith(DefaultApi); + const res = await api.apiV1PluginsHyperledgerCactusPluginConsortiumManualNodeJwsGet(); + t.ok(res, "API response object is truthy"); + t.equal(res.status, 200, "Node JWS response status code is 200"); + t.ok(res.data, "Node2 JWS response.body is truthy"); + t.ok(res.data.jws, "Node JWS response.body.jws is truthy"); + const payload = JWS.verify(res.data.jws, keyPair2.toPEM(false)); + t.ok(payload, "Verified Node JWS payload is truthy"); + t.comment(`Node2 JWS Payload: ${JSON.stringify(payload)}`); + } + t.comment(`Set up second node OK`); + + t.comment(`Setting up third node...`); + { + // 2. Instantiate plugin registry which will provide the web service plugin with the key value storage plugin + const pluginRegistry = new PluginRegistry({ plugins: [] }); + + // 3. Instantiate the web service consortium plugin + const options: IPluginConsortiumManualOptions = { + pluginRegistry, + keyPairPem: keyPair3.toPEM(true), + consortium, + logLevel: "trace", + }; + const pluginConsortiumManual = new PluginConsortiumManual(options); + + // 4. Create the API Server object that we embed in this test + const configService = new ConfigService(); + const apiServerOptions = configService.newExampleConfig(); + apiServerOptions.configFile = ""; + apiServerOptions.apiCorsDomainCsv = "*"; + apiServerOptions.apiPort = addressInfo3.port; + apiServerOptions.cockpitPort = 0; + apiServerOptions.apiTlsEnabled = false; + const config = configService.newExampleConfigConvict(apiServerOptions); + + pluginRegistry.add(pluginConsortiumManual); + + const apiServer = new ApiServer({ + httpServerApi: httpServer3, + config: config.getProperties(), + pluginRegistry, + }); + + // 5. make sure the API server is shut down when the testing if finished. + test.onFinish(() => apiServer.shutdown()); + + // 6. Start the API server which is now listening on port A and it's healthcheck works through the main SDK + await apiServer.start(); + + // 7. Instantiate the main SDK dynamically with whatever port the API server ended up bound to (port 0) + t.comment(`AddressInfo: ${JSON.stringify(addressInfo3)}`); + + const configuration = new Configuration({ basePath: node3Host }); + const api = new ApiClient(configuration).extendWith(DefaultApi); + const res = await api.apiV1PluginsHyperledgerCactusPluginConsortiumManualNodeJwsGet(); + t.ok(res, "API response object is truthy"); + t.equal(res.status, 200, "Node JWS response status code is 200"); + t.ok(res.data, "Node3 JWS response.body is truthy"); + t.ok(res.data.jws, "Node3 JWS response.body.jws is truthy"); + const payload = JWS.verify(res.data.jws, keyPair3.toPEM(false)); + t.ok(payload, "Verified Node3 JWS payload is truthy"); + t.comment(`Node3 JWS Payload: ${JSON.stringify(payload)}`); + } + t.comment(`Set up third node OK`); + + { + const configuration = new Configuration({ basePath: node3Host }); + const api = new ApiClient(configuration).extendWith(DefaultApi); + const res = await api.apiV1PluginsHyperledgerCactusPluginConsortiumManualConsortiumJwsGet(); + t.equal(res.status, 200, "Consortium JWS response status code is 200"); + const getConsortiumJwsResponse = res.data; + const consortiumJws = getConsortiumJwsResponse.jws; + t.comment(`Consortium JWS: ${JSON.stringify(consortiumJws, null, 4)}`); + const payload1 = JWS.verify(consortiumJws, keyPair1.toPEM(false)); + const payload2 = JWS.verify(consortiumJws, keyPair2.toPEM(false)); + const payload3 = JWS.verify(consortiumJws, keyPair3.toPEM(false)); + t.ok(payload1, "Verified Consortium JWS payload1 is truthy"); + t.ok(payload2, "Verified Consortium JWS payload2 is truthy"); + t.ok(payload3, "Verified Consortium JWS payload3 is truthy"); + + const wrongKey = await JWK.generate("EC", "secp256k1"); + t.throws(() => JWS.verify(consortiumJws, wrongKey)); + } + + { + const configuration = new Configuration({ basePath: node2Host }); + const api = new ApiClient(configuration).extendWith(DefaultApi); + const res = await api.apiV1PluginsHyperledgerCactusPluginConsortiumManualConsortiumJwsGet(); + t.equal(res.status, 200, "Consortium JWS response status code is 200"); + const getConsortiumJwsResponse = res.data; + const consortiumJws = getConsortiumJwsResponse.jws; + t.comment(`Consortium JWS: ${JSON.stringify(consortiumJws, null, 4)}`); + const payload1 = JWS.verify(consortiumJws, keyPair1.toPEM(false)); + const payload2 = JWS.verify(consortiumJws, keyPair2.toPEM(false)); + const payload3 = JWS.verify(consortiumJws, keyPair3.toPEM(false)); + t.ok(payload1, "Verified Consortium JWS payload1 is truthy"); + t.ok(payload2, "Verified Consortium JWS payload2 is truthy"); + t.ok(payload3, "Verified Consortium JWS payload3 is truthy"); + + const wrongKey = await JWK.generate("EC", "secp256k1"); + t.throws(() => JWS.verify(consortiumJws, wrongKey)); + } + + { + const configuration = new Configuration({ basePath: node1Host }); + const api = new ApiClient(configuration).extendWith(DefaultApi); + const res = await api.apiV1PluginsHyperledgerCactusPluginConsortiumManualConsortiumJwsGet(); + t.equal(res.status, 200, "Consortium JWS response status code is 200"); + const getConsortiumJwsResponse = res.data; + const consortiumJws = getConsortiumJwsResponse.jws; + t.comment(`Consortium JWS: ${JSON.stringify(consortiumJws, null, 4)}`); + const payload1 = JWS.verify(consortiumJws, keyPair1.toPEM(false)); + const payload2 = JWS.verify(consortiumJws, keyPair2.toPEM(false)); + const payload3 = JWS.verify(consortiumJws, keyPair3.toPEM(false)); + t.ok(payload1, "Verified Consortium JWS payload1 is truthy"); + t.ok(payload2, "Verified Consortium JWS payload2 is truthy"); + t.ok(payload3, "Verified Consortium JWS payload3 is truthy"); + + const wrongKey = await JWK.generate("EC", "secp256k1"); + t.throws(() => JWS.verify(consortiumJws, wrongKey)); + t.comment(JSON.stringify(payload1)); + } + + t.end(); +}); diff --git a/packages/cactus-test-plugin-web-service-consortium/src/test/typescript/integration/plugin-web-service-consortium/security-isolation-via-api-server-ports.ts b/packages/cactus-test-plugin-consortium-manual/src/test/typescript/integration/plugin-consortium-manual/security-isolation-via-api-server-ports.ts similarity index 68% rename from packages/cactus-test-plugin-web-service-consortium/src/test/typescript/integration/plugin-web-service-consortium/security-isolation-via-api-server-ports.ts rename to packages/cactus-test-plugin-consortium-manual/src/test/typescript/integration/plugin-consortium-manual/security-isolation-via-api-server-ports.ts index 0f14d90371..c120cdb5cb 100644 --- a/packages/cactus-test-plugin-web-service-consortium/src/test/typescript/integration/plugin-web-service-consortium/security-isolation-via-api-server-ports.ts +++ b/packages/cactus-test-plugin-consortium-manual/src/test/typescript/integration/plugin-consortium-manual/security-isolation-via-api-server-ports.ts @@ -2,12 +2,10 @@ const tap = require("tap"); import { AxiosResponse } from "axios"; import { Server } from "http"; +import { v4 as uuidV4 } from "uuid"; +import { JWK } from "jose"; import { Logger, LoggerProvider } from "@hyperledger/cactus-common"; -import { - ApiServer, - ConfigService, - ICactusApiServerOptions, -} from "@hyperledger/cactus-cmd-api-server"; +import { ApiServer, ConfigService } from "@hyperledger/cactus-cmd-api-server"; import { PluginRegistry } from "@hyperledger/cactus-core-api"; import { PluginKVStorageMemory } from "@hyperledger/cactus-plugin-kv-storage-memory"; import { @@ -18,9 +16,12 @@ import { import { DefaultApi as DefaultApiPlugin, Configuration as ConfigurationPlugin, - PluginWebServiceConsortium, - IPluginWebServiceConsortiumOptions, -} from "@hyperledger/cactus-plugin-web-service-consortium"; + PluginConsortiumManual, + IPluginConsortiumManualOptions, + CactusNode, + ConsortiumMember, + Consortium, +} from "@hyperledger/cactus-plugin-consortium-manual"; LoggerProvider.setLogLevel("TRACE"); const log: Logger = LoggerProvider.getOrCreate({ @@ -36,33 +37,62 @@ tap.test( // 2. Instantiate plugin registry which will provide the web service plugin with the key value storage plugin const pluginRegistry = new PluginRegistry({ plugins: [kvStoragePlugin] }); + const keyPair = await JWK.generate("EC", "secp256k1"); + const keyPairPem = keyPair.toPEM(true); + const publicKeyPem = keyPair.toPEM(false); + + const consortiumName = "Example Corp. & Friends Crypto Consortium"; + const consortiumId = uuidV4(); + const memberId = uuidV4(); + const nodeId = uuidV4(); + + const cactusNode: CactusNode = { + nodeApiHost: "127.0.0.1:4000", + memberId, + publicKeyPem: keyPair.toPEM(false), + consortiumId, + id: nodeId, + plugins: [], + }; + + const member: ConsortiumMember = { + id: memberId, + nodes: [cactusNode], + name: "Example Corp", + }; + + const consortium: Consortium = { + id: consortiumId, + name: consortiumName, + mainApiHost: "http://127.0.0.1:80", + members: [member], + }; + // 3. Instantiate the web service consortium plugin which will host itself on a new TCP port for isolation/security // Note that if we omitted the `webAppOptions` object that the web service plugin would default to installing itself // on the default port of the API server. This allows for flexibility in deployments. - const options: IPluginWebServiceConsortiumOptions = { + const options: IPluginConsortiumManualOptions = { pluginRegistry, - privateKey: - "4eb8be4f03c19623c884c584e7b1baacf352bf7bf399330a212d90e32fff64da", + keyPairPem, + consortium, logLevel: "trace", webAppOptions: { hostname: "127.0.0.1", port: 0, }, }; - const webServiceConsortiumPlugin = new PluginWebServiceConsortium(options); + const pluginConsortiumManual = new PluginConsortiumManual(options); // 4. Create the API Server object that we embed in this test const configService = new ConfigService(); - const cactusApiServerOptions: ICactusApiServerOptions = configService.newExampleConfig(); - cactusApiServerOptions.configFile = ""; - cactusApiServerOptions.apiCorsDomainCsv = "*"; - cactusApiServerOptions.apiPort = 0; - cactusApiServerOptions.apiTlsEnabled = false; - const config = configService.newExampleConfigConvict( - cactusApiServerOptions - ); + const apiServerOptions = configService.newExampleConfig(); + apiServerOptions.configFile = ""; + apiServerOptions.apiCorsDomainCsv = "*"; + apiServerOptions.apiPort = 0; + apiServerOptions.apiTlsEnabled = false; + const config = configService.newExampleConfigConvict(apiServerOptions); - pluginRegistry.add(webServiceConsortiumPlugin); + pluginRegistry.add(pluginConsortiumManual); const apiServer = new ApiServer({ config: config.getProperties(), @@ -91,10 +121,10 @@ tap.test( assert.ok(healthcheckResponse.data.createdAt); // 8. Get the dedicated HTTP server of the web service plugin - const httpServerConsortium: Server = webServiceConsortiumPlugin + const httpServerConsortium: Server = pluginConsortiumManual .getHttpServer() .orElseThrow( - () => new Error("webServiceConsortiumPlugin HTTP server is not present") + () => new Error("PluginConsortiumManual HTTP server is not present") ); assert.ok(httpServerConsortium, "Get the plugin specific HTTP server"); const addressInfoConsortium: any = httpServerConsortium.address(); @@ -124,20 +154,9 @@ tap.test( }); const api2 = new DefaultApiPlugin(configuration2); - const consortium = { - configurationEndpoint: "fake", - id: "adsf", - name: "asdf", - cactusNodes: [{ host: "asdf", publicKey: "adsf" }], - }; - const response: AxiosResponse = await api2.apiV1PluginsHyperledgerCactusPluginWebServiceConsortiumConsortiumPost( - consortium - ); - assert.ok( - response, - "expect a truthy response object from consortium POST endpoint" - ); - assert.ok(response.status === 201, "expect HTTP status code to equal 201"); + const response: AxiosResponse = await api2.apiV1PluginsHyperledgerCactusPluginConsortiumManualNodeJwsGet(); + assert.ok(response, "response object is truthy"); + assert.ok(response.status === 200, "HTTP status code to equal 200"); assert.end(); } diff --git a/packages/cactus-test-plugin-web-service-consortium/src/test/typescript/unit/api-surface.ts b/packages/cactus-test-plugin-consortium-manual/src/test/typescript/unit/api-surface.ts similarity index 100% rename from packages/cactus-test-plugin-web-service-consortium/src/test/typescript/unit/api-surface.ts rename to packages/cactus-test-plugin-consortium-manual/src/test/typescript/unit/api-surface.ts diff --git a/packages/cactus-test-plugin-web-service-consortium/tsconfig.json b/packages/cactus-test-plugin-consortium-manual/tsconfig.json similarity index 100% rename from packages/cactus-test-plugin-web-service-consortium/tsconfig.json rename to packages/cactus-test-plugin-consortium-manual/tsconfig.json diff --git a/whitepaper/plugin-consortium-manual-bootstrap-sequence-diagram.png b/whitepaper/plugin-consortium-manual-bootstrap-sequence-diagram.png new file mode 100644 index 0000000000..a96bb3b3a3 Binary files /dev/null and b/whitepaper/plugin-consortium-manual-bootstrap-sequence-diagram.png differ diff --git a/whitepaper/plugin-consortium-manual-bootstrap-sequence-diagram.puml b/whitepaper/plugin-consortium-manual-bootstrap-sequence-diagram.puml new file mode 100644 index 0000000000..8d09311ec2 --- /dev/null +++ b/whitepaper/plugin-consortium-manual-bootstrap-sequence-diagram.puml @@ -0,0 +1,58 @@ +@startuml Sequence Diagram - Plugin Consortium Manual Bootstrap + +skinparam ArrowFontStyle italic + +title Hyperledger Cactus\nSequence Diagram - Plugin Consortium Manual Bootstrap + +box Humans +actor Business_Organization_A as a <> +actor Business_Organization_B as b <> +end box + +box Cactus Node A +entity "API Server A" as apia <> +end box + +box Cactus Node B +entity "API Server B" as apib <> +end box + +autoactivate off +autonumber + +== Manual Consensus == + +a -> b: Propose forming consortium\n(email, met in cafe, etc.) +return Consent to consortium formation + +a -> b: Propose trusted communication\nchannel (corporate email or\ntrusted courier service/etc.) +return Consent to proposed\ncommunication channel +note over a,b +This is where **trust** is established between the consortium members. +If the selected channel is compromised in any way, that's an active MITM! +end note + +== Provision Resources, Bootstrap Consortium == + +a->a: Generate key(s) of node(s) +a->a: Provision hardware resources\n(server,public IP, DNS) +a->a: Decide on plugins to be used + +b->b: Generate key(s) of node(s) +b->b: Provision hardware resources\n(server,public IP, DNS) +b->b: Decide on plugins to be used + +a -> b: A sends node hosts,public keys to B +return B responds in kind with their own +note over a,b +As mentioned above, if the communication was compromised, MITM can be +executed at this point by the attacker by altering the keys/hosts in transit. +end note + +a->apia: Configure consortium\nplugin with keys,hosts +a->apia: Start Cactus API Server + +b->apib: Configure consortium\nplugin with keys,hosts +b->apib: Start Cactus API Server + +@enduml diff --git a/whitepaper/whitepaper.md b/whitepaper/whitepaper.md index e00c18ade8..a089422857 100644 --- a/whitepaper/whitepaper.md +++ b/whitepaper/whitepaper.md @@ -116,6 +116,7 @@ Photo by Pontus Wellgraf on Unsplash - [5.6.2.1 X.509 Certificate Plugin](#5621-x509-certificate-plugin) - [5.6.3 Key/Value Storage Plugins](#563-keyvalue-storage-plugins) - [5.6.4 Serverside Keychain Plugins](#564-serverside-keychain-plugins) + - [5.6.5 Manual Consortium Plugin](#565-manual-consortium-plugin) - [6. Identities, Authentication, Authorization](#6-identities-authentication-authorization) - [6.1 Definition of Identities in Cactus](#61-definition-of-identities-in-cactus) - [6.2 Transaction Signing Modes, Key Ownership](#62-transaction-signing-modes-key-ownership) @@ -651,13 +652,24 @@ Using a blockchain agnostic bidirectional communication channel for controlling ### 4.2.4 Consortium Management -Consortiums can be formed by cooperating entities (person, organization, etc.) who wish to all contribute hardware/network resources to the operation of a `Cactus` cluster (set of validator nodes, API servers, etc.). +Consortiums can be formed by cooperating entities (person, organization, etc.) who wish to contribute hardware/network +resources to the operation of a `Cactus` cluster. -After the forming of the consortium with it's initial set of members (one or more) it is possible to enroll or remove certain new or existing members. +What holds the consortiums together is the consensus among the members on who the members are, which is defined by the +nodes' network hosts and public keys. The keys are produced from the Secp256k1 curve. -`Cactus` does not prescribe any specific consensus algorithm for the addition or removal of consortium members, but rather focuses on the technical side of making it possible to operate a cluster of nodes under the ownership of separate entities without downtime while also keeping it possible to add/remove members. +The consortium plugin(s) main responsibility is to provide information about the consortium's members, nodes and public keys. +`Cactus` does not prescribe any specific consensus algorithm for the addition or removal of consortium members, but +rather focuses on the technical side of making it possible to operate a cluster of nodes under the ownership of +separate entities without downtime while also keeping it possible to add/remove members. +It is up to authors of plugins who can implement any kind of consortium management functionality as they see fit. +The default implementation that Cactus ships with is in the `cactus-plugin-consortium-manual` package which - as the +name implies - leaves the achievement of consensus to the initial set of members who are expected to produce the initial +set of network hosts/public keys and then configure their Cactus nodes accordingly. +This process and the details of operation are laid out in much more detail in the dedicated section of the manual +consortium management plugin further below in this document. -A newly joined consortium member does not have to participate in every component of `Cactus`: Running a validator node is the only required action to participate, etcd, API server can remain the same as prior to the new member joining. +After the forming of the consortium with it's initial set of members (one or more) it is possible to enroll or remove certain new or existing members, but this can vary based on different implementations. ## 4.3 Working Policies @@ -1273,6 +1285,116 @@ interface KeychainPlugin extends KeyValueStoragePlugin {
+### 5.6.5 Manual Consortium Plugin + +This plugin is the default/simplest possible implementation of consortium management. +It delegates the initial trust establishment to human actors to be done manually or offline if you will. + +Once a set of members and their nodes were agreed upon, a JSON document containing the consortium metadata can be +constructed which becomes an input parameter for the `cactus-plugin-consortium-manual` package's implementation. +Members bootstrap the consortium by configuring their Cactus nodes with the agreed upon JSON document and start their +nodes. +Since the JSON document is used to generate JSON Web Signatures (JWS) as defined by +[RFC 7515](https://tools.ietf.org/html/rfc7515#section-7.2) it is important that every consortium member uses the same +JSON document representing the consortium. + +> Attention: JWS is not the same as JSON Web Tokens (JWT). JWT is an extension of JWS and so they can seem very similar +> or even indistinguishable, but it is actually two separate things where JWS is the lower level building block that +> makes JWT's higher level use-cases possible. This is not related to Cactus itself, but is important to be mentioned +> since JWT is very well known among software engineers while JWS is a much less often used standard. + +Example of said JSON document (the `"consortium"` property) as passed in to the plugin configuration can be +seen below: + +```json +{ + "packageName": "@hyperledger/cactus-plugin-consortium-manual", + "options": { + "keyPairPem": "-----BEGIN PRIVATE KEY-----\nREDACTED\n-----END PRIVATE KEY-----\n", + "consortium": { + "name": "Example Cactus Consortium", + "id": "2ae136f6-f9f7-40a2-9f6c-92b1b5d5046c", + "mainApiHost": "http://127.0.0.1:4000", + "members": [ + { + "id": "b24f8705-6da5-433a-b8c7-7d2079bae992", + "name": "Example Cactus Consortium Member 1", + "nodes": [ + { + "nodeApiHost": "http://127.0.0.1:4000", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEtDeq7BgpelfsX7WKiSb7Lhxp8VeS6YY/\nInbYuTgwZ8ykGs2Am2fM03aeMX9pYEzaeOVRU6ptwaEBFYX+YftCSQ==\n-----END PUBLIC KEY-----\n" + } + ] + } + ] + } + } + } +``` + +The configuration above will cause the `Consortium JWS` REST API endpoint (callable via the SDK) to respond with a +consortium JWS that looks similar to what is pasted below. + +Code examples of how to use the SDK to call this endpoint can be seen at +`./packages/cactus-cockpit/src/app/consortium-inspector/consortium-inspector.page.ts` + +```json +{ + "payload": "eyJjb25zb3J0aXVtIjp7ImlkIjoiMmFlMTM2ZjYtZjlmNy00MGEyLTlmNmMtOTJiMWI1ZDUwNDZjIiwibWFpbkFwaUhvc3QiOiJodHRwOi8vMTI3LjAuMC4xOjQwMDAiLCJtZW1iZXJzIjpbeyJpZCI6ImIyNGY4NzA1LTZkYTUtNDMzYS1iOGM3LTdkMjA3OWJhZTk5MiIsIm5hbWUiOiJFeGFtcGxlIENhY3R1cyBDb25zb3J0aXVtIE1lbWJlciAxIiwibm9kZXMiOlt7Im5vZGVBcGlIb3N0IjoiaHR0cDovLzEyNy4wLjAuMTo0MDAwIiwicHVibGljS2V5UGVtIjoiLS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS1cbk1GWXdFQVlIS29aSXpqMENBUVlGSzRFRUFBb0RRZ0FFdERlcTdCZ3BlbGZzWDdXS2lTYjdMaHhwOFZlUzZZWS9cbkluYll1VGd3Wjh5a0dzMkFtMmZNMDNhZU1YOXBZRXphZU9WUlU2cHR3YUVCRllYK1lmdENTUT09XG4tLS0tLUVORCBQVUJMSUMgS0VZLS0tLS1cbiJ9XX1dLCJuYW1lIjoiRXhhbXBsZSBDYWN0dXMgQ29uc29ydGl1bSJ9fQ", + "signatures": [ + { + "protected": "eyJpYXQiOjE1OTYyNDQzMzQ0NTksImp0aSI6IjM3NmJjMzk0LTBlYWMtNDcwZi04NjliLThkYWIzNDRmNmY3MiIsImlzcyI6Ikh5cGVybGVkZ2VyIENhY3R1cyIsImFsZyI6IkVTMjU2SyJ9", + "signature": "ltnDyOe9WSdCk6f5Op8XlcnFoXUp3yJZgImsAvERnxWM-eeL6eX0MnCtfC5r3q6knt4kTTaUv8536SMCka_YyA" + } + ] +} +``` + +The same JWS after being decoded looks like this: + +```json +{ + "payload": { + "consortium": { + "id": "2ae136f6-f9f7-40a2-9f6c-92b1b5d5046c", + "mainApiHost": "http://127.0.0.1:4000", + "members": [ + { + "id": "b24f8705-6da5-433a-b8c7-7d2079bae992", + "name": "Example Cactus Consortium Member 1", + "nodes": [ + { + "nodeApiHost": "http://127.0.0.1:4000", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEtDeq7BgpelfsX7WKiSb7Lhxp8VeS6YY/\nInbYuTgwZ8ykGs2Am2fM03aeMX9pYEzaeOVRU6ptwaEBFYX+YftCSQ==\n-----END PUBLIC KEY-----\n" + } + ] + } + ], + "name": "Example Cactus Consortium" + } + }, + "signatures": [ + { + "protected": { + "iat": 1596244334459, + "jti": "376bc394-0eac-470f-869b-8dab344f6f72", + "iss": "Hyperledger Cactus", + "alg": "ES256K" + }, + "signature": "ltnDyOe9WSdCk6f5Op8XlcnFoXUp3yJZgImsAvERnxWM-eeL6eX0MnCtfC5r3q6knt4kTTaUv8536SMCka_YyA" + } + ] +} +``` + +The below sequence diagram demonstrates a real world example of how a consortium between two business organizations (who both +operate their own distributed ledgers) can be formed manually and then operated through the plugin discussed here. +There's many other ways to perform the initial agreement that happens offline, but a concrete, non-generic example is +provided here for ease of understanding: + + + + # 6. Identities, Authentication, Authorization `Cactus` aims to provide a unified API surface for managing identities of an identity owner.