diff --git a/demo/server/.env.example b/demo/server/.env.example index b0344f2..88aa944 100644 --- a/demo/server/.env.example +++ b/demo/server/.env.example @@ -1,9 +1,8 @@ NODE_ENV=local - -ENDPOINT=http://127.0.0.1 +ENDPOINT=http://127.0.0.1:3001 PORT=3001 -KERIA_URL=https://dev.keria.cf-keripy.metadata.dev.cf-deployments.org -KERIA_BOOT_URL=https://dev.keria-boot.cf-keripy.metadata.dev.cf-deployments.org +KERIA_URL=http://127.0.0.1:3901 +KERIA_BOOT_URL=http://127.0.0.1:3903 BRAN=o123456a89aacxeaCaxkk SIGNIFY_NAME=enterpriseServer DB_DATABASE=cf-poc-tunnel.sql diff --git a/demo/server/.gitignore b/demo/server/.gitignore index a3ca1c2..8dad582 100644 --- a/demo/server/.gitignore +++ b/demo/server/.gitignore @@ -1,3 +1,4 @@ dist node_modules .vscode +cf-poc-tunnel.sql diff --git a/demo/server/package-lock.json b/demo/server/package-lock.json index 2f03d59..e59451c 100644 --- a/demo/server/package-lock.json +++ b/demo/server/package-lock.json @@ -9,28 +9,30 @@ "version": "0.0.1", "license": "ISC", "dependencies": { - "axios": "^1.5.0", "body-parser": "^1.20.2", "cors": "^2.8.5", "dotenv": "^16.3.1", "express": "^4.18.2", "node-cache": "^5.1.2", - "signify-ts": "github:WebOfTrust/signify-ts#858dea2464eba8288318f147a8d3ab2640f5d625", + "signify-ts": "github:cardano-foundation/signify-ts#c47354267b193a18b3f7d2ddc5d1076f36a2e867", "sqlite3": "^5.1.7", "typeorm": "^0.3.20", "uuid": "^9.0.1", "ws": "^8.13.0" }, "devDependencies": { + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/uuid": "^9.0.8", "nodemon": "^3.0.1", "rimraf": "^5.0.1", "ts-node": "^10.9.1" } }, "node_modules/@babel/runtime": { - "version": "7.23.8", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.8.tgz", - "integrity": "sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", + "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -152,18 +154,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@npmcli/move-file/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "optional": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@npmcli/move-file/node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -226,16 +216,118 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "devOptional": true }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.43", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz", + "integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, "node_modules/@types/node": { "version": "20.11.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.0.tgz", "integrity": "sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==", "devOptional": true, - "peer": true, "dependencies": { "undici-types": "~5.26.4" } }, + "node_modules/@types/qs": { + "version": "6.9.11", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz", + "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", + "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "dev": true + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -424,21 +516,6 @@ "safer-buffer": "^2.1.0" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/axios": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", - "integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", - "dependencies": { - "follow-redirects": "^1.15.4", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -586,16 +663,6 @@ "ieee754": "^1.2.1" } }, - "node_modules/buffer-writer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", - "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", - "optional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -677,18 +744,6 @@ "node": ">=8" } }, - "node_modules/cacache/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "optional": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/cacache/node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -1044,17 +1099,6 @@ "color-support": "bin.js" } }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/complex.js": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.1.1.tgz", @@ -1195,14 +1239,6 @@ "node": ">= 0.4" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -1476,25 +1512,6 @@ "node": ">= 0.8" } }, - "node_modules/follow-redirects": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", - "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/foreground-child": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", @@ -1510,19 +1527,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -2461,17 +2465,14 @@ } }, "node_modules/mkdirp": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz", - "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "bin": { - "mkdirp": "dist/cjs/src/bin.js" + "mkdirp": "bin/cmd.js" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" } }, "node_modules/mkdirp-classic": { @@ -2751,13 +2752,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/packet-reader": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", - "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==", - "optional": true, - "peer": true - }, "node_modules/parse5": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", @@ -2821,104 +2815,6 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, - "node_modules/pg": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.11.3.tgz", - "integrity": "sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==", - "optional": true, - "peer": true, - "dependencies": { - "buffer-writer": "2.0.0", - "packet-reader": "1.0.0", - "pg-connection-string": "^2.6.2", - "pg-pool": "^3.6.1", - "pg-protocol": "^1.6.0", - "pg-types": "^2.1.0", - "pgpass": "1.x" - }, - "engines": { - "node": ">= 8.0.0" - }, - "optionalDependencies": { - "pg-cloudflare": "^1.1.1" - }, - "peerDependencies": { - "pg-native": ">=3.0.1" - }, - "peerDependenciesMeta": { - "pg-native": { - "optional": true - } - } - }, - "node_modules/pg-cloudflare": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz", - "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==", - "optional": true, - "peer": true - }, - "node_modules/pg-connection-string": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz", - "integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==", - "optional": true, - "peer": true - }, - "node_modules/pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", - "optional": true, - "peer": true, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/pg-pool": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.1.tgz", - "integrity": "sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==", - "optional": true, - "peer": true, - "peerDependencies": { - "pg": ">=8.0" - } - }, - "node_modules/pg-protocol": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz", - "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==", - "optional": true, - "peer": true - }, - "node_modules/pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "optional": true, - "peer": true, - "dependencies": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pgpass": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", - "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", - "optional": true, - "peer": true, - "dependencies": { - "split2": "^4.1.0" - } - }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -2931,49 +2827,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", - "optional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/postgres-bytea": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", - "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-date": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "optional": true, - "peer": true, - "dependencies": { - "xtend": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/prebuild-install": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", @@ -3030,11 +2883,6 @@ "node": ">= 0.10" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -3347,9 +3195,9 @@ } }, "node_modules/signify-ts": { - "version": "0.1.1", - "resolved": "git+ssh://git@github.com/WebOfTrust/signify-ts.git#858dea2464eba8288318f147a8d3ab2640f5d625", - "integrity": "sha512-RepDgvxY+j6t58btwrggytVHDYMcWzN76G3wrfp7HJPUG1uyaaoGmykaizUrISfSoH4+ICK7djwF9cwkANLAcA==", + "version": "0.2.0", + "resolved": "git+ssh://git@github.com/cardano-foundation/signify-ts.git#c47354267b193a18b3f7d2ddc5d1076f36a2e867", + "integrity": "sha512-+G0HglICQyh+g6JS4DGR0G6QpVhJuYqhcYL7yHUJeLX6Su2grUdVnosGBDDuEfztkTjBd/0ZbR/QGKIptMyt2Q==", "license": "Apache-2.0", "workspaces": [ "examples/*" @@ -3480,16 +3328,6 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "optional": true }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "optional": true, - "peer": true, - "engines": { - "node": ">= 10.x" - } - }, "node_modules/sprintf-js": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", @@ -3727,17 +3565,6 @@ "node": ">=8" } }, - "node_modules/tar/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -3994,6 +3821,20 @@ } } }, + "node_modules/typeorm/node_modules/mkdirp": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz", + "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/typeorm/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -4023,8 +3864,7 @@ "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "devOptional": true, - "peer": true + "devOptional": true }, "node_modules/unique-filename": { "version": "1.1.1", @@ -4269,16 +4109,6 @@ } } }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.4" - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/demo/server/package.json b/demo/server/package.json index 062dde5..19b6dba 100644 --- a/demo/server/package.json +++ b/demo/server/package.json @@ -12,19 +12,21 @@ "author": "", "license": "ISC", "dependencies": { - "axios": "^1.5.0", "body-parser": "^1.20.2", "cors": "^2.8.5", "dotenv": "^16.3.1", "express": "^4.18.2", "node-cache": "^5.1.2", - "signify-ts": "github:WebOfTrust/signify-ts#858dea2464eba8288318f147a8d3ab2640f5d625", + "signify-ts": "github:cardano-foundation/signify-ts#c47354267b193a18b3f7d2ddc5d1076f36a2e867", "sqlite3": "^5.1.7", "typeorm": "^0.3.20", "uuid": "^9.0.1", "ws": "^8.13.0" }, "devDependencies": { + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/uuid": "^9.0.8", "nodemon": "^3.0.1", "rimraf": "^5.0.1", "ts-node": "^10.9.1" diff --git a/demo/server/sendReq.ts b/demo/server/sendReq.ts new file mode 100644 index 0000000..fa0eaea --- /dev/null +++ b/demo/server/sendReq.ts @@ -0,0 +1,69 @@ +import { Cipher, Matter, MtrDex, b } from "signify-ts"; +import { config } from "./src/config"; +import { + getIdentifierByName, + getKeyManager, + getRemoteEncrypter, + initSignify, +} from "./src/services/signifyService"; + +async function run() { + const client = await initSignify(); + + const serverAid = await getIdentifierByName(config.signifyName); + const ourAid = await getIdentifierByName("test-aid"); + + const keyManager = await getKeyManager(ourAid); + const encrypter = await getRemoteEncrypter(serverAid.prefix); + + const toEncrypt: Uint8Array = Buffer.from( + JSON.stringify({ + src: ourAid.prefix, + data: { + inner: "some data!!", + }, + }), + ); + const cipher: Cipher = encrypter.encrypt( + null, + new Matter({ raw: toEncrypt, code: LEAD_CODES.get(toEncrypt.length % 3) }), + ); + + const datetime = new Date().toISOString().replace("Z", "000+00:00"); + const resp = await client.signedFetch( + "http://localhost:3001", + "/ping", + "POST", + { + sig: keyManager.signers[0].sign( + b( + JSON.stringify({ + src: ourAid.prefix, + dest: serverAid.prefix, + datetime, + cipher: cipher.qb64, + }), + ), + ).qb64, + cipher: cipher.qb64, + }, + "test-aid", + datetime, + ); + resp.headers.forEach(function (val, key) { + console.log(key + " -> " + val); + }); + if (resp.ok) { + console.log(await resp.json()); + } else { + console.log(await resp.text()); + } +} + +run(); + +const LEAD_CODES = new Map([ + [0, MtrDex.StrB64_L0], + [1, MtrDex.StrB64_L1], + [2, MtrDex.StrB64_L2], +]); diff --git a/demo/server/src/apis/acdc-requirements.api.ts b/demo/server/src/apis/acdc-requirements.api.ts deleted file mode 100644 index 84b6344..0000000 --- a/demo/server/src/apis/acdc-requirements.api.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Request, Response } from "express"; -import { ResponseData } from "../types/response.type"; -import { httpResponse } from "../utils/response.util"; -import { config } from "../config"; - -function getAcdcRequirements(_: Request, res: Response) { - const acdcRequirements = { - user: { - "-s": config.qviSchemaSaid, - }, - }; - const response: ResponseData = { - statusCode: 200, - success: true, - data: acdcRequirements, - }; - httpResponse(res, response); -} - -export { getAcdcRequirements }; diff --git a/demo/server/src/apis/acdcRequirements.api.ts b/demo/server/src/apis/acdcRequirements.api.ts new file mode 100644 index 0000000..9d63de6 --- /dev/null +++ b/demo/server/src/apis/acdcRequirements.api.ts @@ -0,0 +1,11 @@ +import { Request, Response } from "express"; +import { config } from "../config"; + +export function getAcdcRequirements(_: Request, res: Response) { + const acdcRequirements = { + user: { + "-s": config.qviSchemaSaid, + }, + }; + res.status(200).send(acdcRequirements); +} diff --git a/demo/server/src/apis/discloseAcdc.api.ts b/demo/server/src/apis/discloseAcdc.api.ts new file mode 100644 index 0000000..bbc0a3d --- /dev/null +++ b/demo/server/src/apis/discloseAcdc.api.ts @@ -0,0 +1,21 @@ +import { Request, Response } from "express"; +import { disclosureAcdc } from "../services/signifyService"; +import { ERROR_ACDC_NOT_FOUND } from "../services/signifyService.types"; + +export async function discloseAcdc(req: Request, res: Response) { + try { + await disclosureAcdc( + req.body.aidPrefix, + req.body.schemaSaid, + req.body.issuer, + ); + return res.status(200).send(); + } catch (error) { + if (error instanceof Error && error["message"] === ERROR_ACDC_NOT_FOUND) { + return res.status(409).send(error["message"]); + } + return res + .status(500) + .send(JSON.stringify(error, Object.getOwnPropertyNames(error))); + } +} diff --git a/demo/server/src/apis/disclosure-acdc.api.ts b/demo/server/src/apis/disclosure-acdc.api.ts deleted file mode 100644 index 70a43a8..0000000 --- a/demo/server/src/apis/disclosure-acdc.api.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Request, Response } from "express"; -import { ResponseData } from "../types/response.type"; -import { httpResponse } from "../utils/response.util"; -import { disclosureAcdc } from "../modules/signifyApi"; -import { ERROR_MESSAGE } from "../utils/constants"; - -async function disclosureAcdcApi(req: Request, res: Response) { - try { - await disclosureAcdc( - req.body.aidPrefix, - req.body.schemaSaid, - req.body.issuer, - ); - const response: ResponseData = { - statusCode: 200, - success: true, - data: null, - }; - httpResponse(res, response); - } catch (error) { - if (error && error["message"] === ERROR_MESSAGE.ACDC_NOT_FOUND) { - const response: ResponseData = { - statusCode: 409, - success: false, - data: null, - error: error["message"], - }; - return httpResponse(res, response); - } - const response: ResponseData = { - statusCode: 500, - success: false, - data: null, - error: JSON.stringify(error, Object.getOwnPropertyNames(error)), - }; - return httpResponse(res, response); - } -} - -export { disclosureAcdcApi }; diff --git a/demo/server/src/apis/handle-req-grant.ts b/demo/server/src/apis/handle-req-grant.ts deleted file mode 100644 index b6c1dfc..0000000 --- a/demo/server/src/apis/handle-req-grant.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Request, Response } from "express"; -import { ResponseData } from "../types/response.type"; -import { httpResponse } from "../utils/response.util"; -import { getCredentials, getExnMessageBySaid } from "../modules/signifyApi"; -import { Session } from "../database/entities/session"; -import { dataSource } from "../database"; -import { config } from "../config"; - -async function handleReqGrant(req: Request, res: Response) { - const { said } = req.params; - try { - const exchange = await getExnMessageBySaid(said); - const aid = exchange.exn.a.sid; - const acdcs= await getCredentials({ - '-i': config.issuerAidPrefix, - '-a-i': exchange.exn.i - }); - if (!acdcs.length) { - return httpResponse(res, { - statusCode: 409, - success: false, - data: `AID have not completed the ACDC disclosure yet.`, - }); - } - const session = new Session(); - const latestAcdc = acdcs.reduce((latestObj, currentObj) => { - const maxDateTime = latestObj.sad.a.dt; - const currentDateTime = currentObj.sad.a.dt; - return currentDateTime > maxDateTime ? currentObj : latestObj; - }); - - if (new Date(latestAcdc.sad.a.dt).getTime() < new Date().getTime() - 60000) { - return httpResponse(res, { - statusCode: 409, - success: false, - data: `Latest ACDC disclosure from ${exchange.exn.i} is too old`, - }); - } - const acdcSchema = latestAcdc.sad.s; - if (acdcSchema === config.qviSchemaSaid) { - session.role = 'user'; - }; - session.aid = aid; - const currentTime = new Date().getTime(); - const sessionDuration = 5 * 60000; //5 mins - session.validUntil = new Date(currentTime + sessionDuration); - const entityManager = dataSource.manager; - await entityManager.save(session); - const response: ResponseData = { - statusCode: 200, - success: true, - data: exchange, - }; - httpResponse(res, response); - } catch (error) { - console.log(`handle req grant error:`, error); - const response: ResponseData = { - statusCode: 500, - success: false, - data: (error as Error).message, - }; - httpResponse(res, response); - } -} - -export { handleReqGrant }; diff --git a/demo/server/src/apis/handleReqGrant.ts b/demo/server/src/apis/handleReqGrant.ts new file mode 100644 index 0000000..8a2174c --- /dev/null +++ b/demo/server/src/apis/handleReqGrant.ts @@ -0,0 +1,49 @@ +import { Request, Response } from "express"; +import { getCredentials, getExnMessageBySaid } from "../services/signifyService"; +import { Session } from "../database/entities/session"; +import { dataSource } from "../database"; +import { config } from "../config"; + +async function handleReqGrant(req: Request, res: Response) { + const { said } = req.params; + try { + const exchange = await getExnMessageBySaid(said); + const aid = exchange.exn.a.sid; + const acdcs = await getCredentials({ + "-i": config.issuerAidPrefix, + "-a-i": exchange.exn.i, + }); + if (!acdcs.length) { + return res.status(409).send("AID has not completed the ACDC disclosure yet."); + } + const session = new Session(); + const latestAcdc = acdcs.reduce((latestObj, currentObj) => { + const maxDateTime = latestObj.sad.a.dt; + const currentDateTime = currentObj.sad.a.dt; + return currentDateTime > maxDateTime ? currentObj : latestObj; + }); + + if ( + new Date(latestAcdc.sad.a.dt).getTime() < + new Date().getTime() - 60000 + ) { + return res.status(409).send(`Latest ACDC disclosure from ${exchange.exn.i} is too old`); + } + const acdcSchema = latestAcdc.sad.s; + if (acdcSchema === config.qviSchemaSaid) { + session.role = "user"; + } + session.aid = aid; + const currentTime = new Date().getTime(); + const sessionDuration = 5 * 60000; //5 mins + session.validUntil = new Date(currentTime + sessionDuration); + const entityManager = dataSource.manager; + await entityManager.save(session); + return res.status(200).send(exchange); + } catch (error) { + console.warn(`handle req grant error:`, error); + return res.status(500).send((error as Error).message); + } +} + +export { handleReqGrant }; diff --git a/demo/server/src/apis/oobi.api.ts b/demo/server/src/apis/oobi.api.ts index 66c85f5..f8c6a53 100644 --- a/demo/server/src/apis/oobi.api.ts +++ b/demo/server/src/apis/oobi.api.ts @@ -1,22 +1,13 @@ import { Request, Response } from "express"; -import { ResponseData } from "../types/response.type"; -import { httpResponse } from "../utils/response.util"; -import { getOOBIs, resolveOOBI } from "../modules/signifyApi"; +import { getOOBIs, resolveOOBI } from "../services/signifyService"; import { config } from "../config"; -async function resolveClientOOBI(req: Request, res: Response) { +export async function resolveClientOOBI(req: Request, res: Response) { const resolveOobiResult = await resolveOOBI(req.body.oobiUrl); - const response: ResponseData = { - statusCode: 200, - success: true, - data: resolveOobiResult, - }; - httpResponse(res, response); + res.status(200).send(resolveOobiResult); } -async function getServerOOBI(_: Request, res: Response) { +export async function getServerOOBI(_: Request, res: Response) { const oobisResult = await getOOBIs(config.signifyName, "agent"); res.status(200).send(oobisResult); } - -export { resolveClientOOBI, getServerOOBI }; diff --git a/demo/server/src/apis/ping.api.ts b/demo/server/src/apis/ping.api.ts index 4c7e3ac..b6ac414 100644 --- a/demo/server/src/apis/ping.api.ts +++ b/demo/server/src/apis/ping.api.ts @@ -1,14 +1,10 @@ -import { Request, Response } from "express"; -import { ResponseData } from "../types/response.type"; -import { httpResponse } from "../utils/response.util"; +import { NextFunction, Request, Response } from "express"; -function ping(_: Request, res: Response) { - const response: ResponseData = { - statusCode: 200, - success: true, +export function ping(_: Request, res: Response, next: NextFunction) { + res.locals.responseBody = { data: "pong", }; - httpResponse(res, response); + res.set("Content-Type", "application/json"); + res.status(200); + next(); } - -export { ping }; diff --git a/demo/server/src/apis/schema.api.ts b/demo/server/src/apis/schema.api.ts index 79034e1..a99f83f 100644 --- a/demo/server/src/apis/schema.api.ts +++ b/demo/server/src/apis/schema.api.ts @@ -1,13 +1,11 @@ import { Request, Response } from "express"; -import { SCHEMA_ACDC } from "../utils/schemas/schemaAcdc"; +import { SCHEMAS } from "../schemas"; -async function schemaApi(req: Request, res: Response) { +export async function getSchema(req: Request, res: Response) { const { id } = req.params; - const data = SCHEMA_ACDC[id]; + const data = SCHEMAS.get(id); if (!data) { return res.status(404).send("Schema for given SAID not found"); } return res.send(data); } - -export { schemaApi }; diff --git a/demo/server/src/config.ts b/demo/server/src/config.ts index dad7091..dd66020 100644 --- a/demo/server/src/config.ts +++ b/demo/server/src/config.ts @@ -9,7 +9,7 @@ const bran = process.env.BRAN as string; const signifyName = process.env.SIGNIFY_NAME as string; const issuerAidPrefix = process.env.ISSUER_AID_PREFIX; -const config = { +export const config = { endpoint: endpoint, endpoints: [endpoint], port, @@ -24,11 +24,9 @@ const config = { schema: "/oobi/:id", disclosureAcdc: "/disclosure-acdc", acdcRequirements: "/acdc-requirements", - handleReqGrant: "/handle-req-grant/:said" + handleReqGrant: "/handle-req-grant/:said", }, domainSchemaSaid: "EGjD1gCLi9ecZSZp9zevkgZGyEX_MbOdmhBFt4o0wvdb", qviSchemaSaid: "EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao", issuerAidPrefix, }; - -export { config }; diff --git a/demo/server/src/database/entities/session.ts b/demo/server/src/database/entities/session.ts index 59cbcef..fe0ea56 100644 --- a/demo/server/src/database/entities/session.ts +++ b/demo/server/src/database/entities/session.ts @@ -1,4 +1,4 @@ -import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; +import { Entity, Column, PrimaryGeneratedColumn } from "typeorm"; @Entity() export class Session { @@ -13,4 +13,4 @@ export class Session { @Column() validUntil: Date; -} \ No newline at end of file +} diff --git a/demo/server/src/database/index.ts b/demo/server/src/database/index.ts index d024055..57d9bbe 100644 --- a/demo/server/src/database/index.ts +++ b/demo/server/src/database/index.ts @@ -3,8 +3,10 @@ dotenv.config(); import { DataSource } from "typeorm"; export const dataSource = new DataSource({ - type: "sqlite", - database: process.env.DB_DATABASE ? process.env.DB_DATABASE : 'cf-poc-tunnel.sql', - entities: ["src/database/entities/*.ts"], - synchronize: process.env.NODE_ENV === 'production' ? false : true - }); + type: "sqlite", + database: process.env.DB_DATABASE + ? process.env.DB_DATABASE + : "cf-poc-tunnel.sql", + entities: ["src/database/entities/*.ts"], + synchronize: process.env.NODE_ENV === "production" ? false : true, +}); diff --git a/demo/server/src/errors.ts b/demo/server/src/errors.ts deleted file mode 100644 index 7c0607d..0000000 --- a/demo/server/src/errors.ts +++ /dev/null @@ -1,5 +0,0 @@ -const MISSING_CONNECTION_ID = "Missing connectionId"; -const MISSING_DID_ID = "Missing did"; -const CONNECTION_NOT_FOUND = "Connection not found"; - -export { MISSING_CONNECTION_ID, CONNECTION_NOT_FOUND, MISSING_DID_ID }; diff --git a/demo/server/src/middlewares/decryptVerifyRequest.middleware.ts b/demo/server/src/middlewares/decryptVerifyRequest.middleware.ts new file mode 100644 index 0000000..5d15801 --- /dev/null +++ b/demo/server/src/middlewares/decryptVerifyRequest.middleware.ts @@ -0,0 +1,100 @@ +import { NextFunction, Request, Response } from "express"; +import { Authenticater, Cigar, Cipher, Decrypter } from "signify-ts"; +import { config } from "../config"; +import { + getIdentifierByName, + getRemoteVerfer, + getKeyManager, +} from "../services/signifyService"; + +export async function decryptVerifyRequest( + req: Request, + res: Response, + next: NextFunction, +) { + const reqAid = req.get("Signify-Resource"); + if (!reqAid) { + return res.status(400).send("Missing Signify-Resource header"); + } + + const reqDateTime = req.get("Signify-Timestamp"); + if (!reqDateTime) { + return res.status(400).send("Missing Signify-Timestamp header"); + } + + // For now this isn't full protection - we need to verify that the request is unique. + // Follow up story will cover this. + if (Date.now() - new Date(reqDateTime).getTime() > 1000) { + return res.status(409).send("Signify-Timestamp too old"); + } + + const reqVerfer = await getRemoteVerfer(reqAid); + const serverAid = await getIdentifierByName(config.signifyName); + const keyManager = await getKeyManager(serverAid); + + const authenticator = new Authenticater( + keyManager.signers[0], // Not used here, we only need to verify so just inject our own. + reqVerfer, + ); + + try { + if ( + !authenticator.verify( + new Headers(JSON.parse(JSON.stringify(req.headers))), + req.method.toUpperCase(), + req.path.split("?")[0], + ) + ) { + return res + .status(400) + .send("Signature headers not in the correct format"); + } + } catch (error) { + console.warn(error); + // @TODO - foconnor: Catch this more specifically just in case. + return res + .status(400) + .send("Signature header not valid for given Signify-Resource"); + } + + if (req.body) { + if (!req.body.sig || !req.body.cipher) { + return res + .status(400) + .send("Body must contain a valid ESSR ciphertext and signature"); + } + + const signature = new Cigar({ qb64: req.body.sig }); // @TODO - foconnor: Will crash if not valid CESR - handle. + if ( + !reqVerfer.verify( + signature.raw, + JSON.stringify({ + src: reqAid, + dest: serverAid.prefix, + datetime: reqDateTime, + cipher: req.body.cipher, + }), + ) + ) { + return res + .status(400) + .send("Signature of body is not valid for given Signify-Resource"); + } + + const cipher = new Cipher({ qb64: req.body.cipher }); // @TODO - foconnor: Same here. + const decrypter: Decrypter = new Decrypter({}, keyManager.signers[0].qb64b); + const decrypted = JSON.parse( + Buffer.from(decrypter.decrypt(null, cipher).raw).toString(), + ); + + if (!(decrypted.src && decrypted.src === reqAid)) { + return res + .status(400) + .send("Invalid src identifier in plaintext of cipher"); + } + + req.body = decrypted.data; + } + + return next(); +} diff --git a/demo/server/src/middlewares/encryptSignResponse.middleware.ts b/demo/server/src/middlewares/encryptSignResponse.middleware.ts new file mode 100644 index 0000000..13a29e3 --- /dev/null +++ b/demo/server/src/middlewares/encryptSignResponse.middleware.ts @@ -0,0 +1,91 @@ +import { Request, Response } from "express"; +import { Authenticater, Cipher, Matter, MtrDex, b } from "signify-ts"; +import { + getIdentifierByName, + getRemoteEncrypter, + getKeyManager, +} from "../services/signifyService"; +import { config } from "../config"; +import { EssrBody } from "../types"; + +/** + * This middleware will sign any headers, the path and query params (pending in Signify). + * It will also encrypt and sign the body in accordance with KERI ESSR. + * + * A future interaction could try to encrypt the header/path/query params with ESSR too, + * but this would actually be better done at a transport layer within the browser rather than an extension. + */ +export async function encryptSignResponse(req: Request, res: Response) { + const reqAid = req.get("Signify-Resource"); + if (!reqAid) { + // Shouldn't happen if decrypt middleware + return res.status(400).send("Missing Signify-Resource header"); + } + + const serverAid = await getIdentifierByName(config.signifyName); + const keyManager = await getKeyManager(serverAid); + + // SIGN HEADERS + const authenticator = new Authenticater( + keyManager.signers[0], + keyManager.signers[0].verfer, + ); + + const originalHeaders = JSON.parse(JSON.stringify(res.getHeaders())); + const headers = new Headers(originalHeaders); + + const datetime = new Date().toISOString().replace("Z", "000+00:00"); + headers.set("Signify-Resource", serverAid.prefix); + headers.set("Signify-Timestamp", datetime); + const signedHeaders = authenticator?.sign( + headers, + req.method, + req.path.split("?")[0], + ); + signedHeaders?.forEach((value, key) => { + res.set(key, value); + }); + + // SIGN BODY + if (res.locals.responseBody) { + const encrypter = await getRemoteEncrypter(reqAid); + const toEncrypt: Uint8Array = Buffer.from( + JSON.stringify({ + src: serverAid.prefix, + data: res.locals.responseBody, + }), + ); + const cipher: Cipher = encrypter.encrypt( + null, + new Matter({ + raw: toEncrypt, + code: LEAD_CODES.get(toEncrypt.length % 3), + }), + ); + + // src, datetime are both already in the headers, and dest is already known by the receiver for this (src, dest) interaction. + const essrBody: EssrBody = { + sig: keyManager.signers[0].sign( + b( + JSON.stringify({ + src: serverAid.prefix, + dest: reqAid, + datetime, + cipher: cipher.qb64, + }), + ), + ).qb64, + cipher: cipher.qb64, + }; + + return res.send(essrBody); + } + + return res.send(); +} + +const LEAD_CODES = new Map([ + [0, MtrDex.StrB64_L0], + [1, MtrDex.StrB64_L1], + [2, MtrDex.StrB64_L2], +]); diff --git a/demo/server/src/middlewares/index.ts b/demo/server/src/middlewares/index.ts index 0e61ed6..2e5ae83 100644 --- a/demo/server/src/middlewares/index.ts +++ b/demo/server/src/middlewares/index.ts @@ -1,3 +1,3 @@ -export { signResponse } from './signResponse.middleware'; -export { verifyRequest } from './verifyRequest.middleware'; -export { verifySession } from './verifySession.middleware'; +export { encryptSignResponse } from "./encryptSignResponse.middleware"; +export { decryptVerifyRequest } from "./decryptVerifyRequest.middleware"; +export { verifySession } from "./verifySession.middleware"; diff --git a/demo/server/src/middlewares/signResponse.middleware.ts b/demo/server/src/middlewares/signResponse.middleware.ts deleted file mode 100644 index 9ec37b1..0000000 --- a/demo/server/src/middlewares/signResponse.middleware.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Request, Response } from "express"; -import { getIdentifierByName, getSigner } from "../modules/signifyApi"; -import { config } from "../config"; -import { Authenticater } from "signify-ts"; - -export const signResponse = async (req: Request, res: Response, next) => { - const serverAID = await getIdentifierByName(config.signifyName); - - const signer = await getSigner(serverAID); - - const authenticator = new Authenticater( - signer.signers[0], - signer.signers[0].verfer, - ); - - // Intercept the response - const originalSend = res.send; - res.send = function (body) { - const originalHeaders = res.getHeaders(); - const headers = new Headers(originalHeaders); - headers.set("Signify-Resource", serverAID.prefix); - headers.set( - "Signify-Timestamp", - new Date().toISOString().replace("Z", "000+00:00"), - ); - headers.set("Content-Type", "application/json"); - if (typeof body === "string") { - headers.set("Content-Length", String(body.length)); - } else { - headers.set("Content-Length", String(JSON.stringify(body.length))); - } - const signedHeaders = authenticator?.sign( - headers, - req.method, - req.path.split("?")[0], - ); - signedHeaders?.forEach((value, key) => { - res.set(key, value); - }); - originalSend.call(this, JSON.stringify(body)); - }; - next(); -}; diff --git a/demo/server/src/middlewares/verifyRequest.middleware.ts b/demo/server/src/middlewares/verifyRequest.middleware.ts deleted file mode 100644 index 24197be..0000000 --- a/demo/server/src/middlewares/verifyRequest.middleware.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Request, Response } from "express"; -import { getIdentifierByName, getSigner } from "../modules/signifyApi"; -import { config } from "../config"; -import { Authenticater } from "signify-ts"; - -export const verifyRequest = async (req: Request, res: Response, next) => { - const serverAID = await getIdentifierByName(config.signifyName); - - const signer = await getSigner(serverAID); - - const authenticator = new Authenticater( - signer.signers[0], - signer.signers[0].verfer, - ); - - try { - console.log("verifying..."); - const verification = authenticator?.verify( - new Headers(req.headers), - req.method, - req.path.split("?")[0], - ); - console.log({ verification }); - if (!verification) { - res.status(400).send("Request was not signed correctly"); - } else { - next(); - } - } catch (error) { - console.log({ error }); - res.status(500).send((error as Error).message); - } -}; diff --git a/demo/server/src/middlewares/verifySession.middleware.ts b/demo/server/src/middlewares/verifySession.middleware.ts index d98107d..751b438 100644 --- a/demo/server/src/middlewares/verifySession.middleware.ts +++ b/demo/server/src/middlewares/verifySession.middleware.ts @@ -4,7 +4,7 @@ import { Session } from "../database/entities/session"; export const verifySession = (validRoles: string[]) => { return async (req: Request, res: Response, next) => { - const aid = req.headers['signify-resource']; + const aid = req.headers['signify-resource'] as string; const sessionRepository = dataSource.getRepository(Session); const session = await sessionRepository.findOne({ where: { @@ -28,6 +28,6 @@ export const verifySession = (validRoles: string[]) => { userAid: session.aid, } } - next(); + return next(); } } diff --git a/demo/server/src/routes.ts b/demo/server/src/routes.ts index 5938356..097b2b4 100644 --- a/demo/server/src/routes.ts +++ b/demo/server/src/routes.ts @@ -1,21 +1,18 @@ -import express from 'express'; -import { config } from './config'; -import { ping } from './apis/ping.api'; -import { getServerOOBI, resolveClientOOBI } from './apis/oobi.api'; -import { schemaApi } from './apis/schema.api'; -import { disclosureAcdcApi } from './apis/disclosure-acdc.api'; -import { signResponse, verifyRequest, verifySession } from "./middlewares"; -import { getAcdcRequirements } from './apis/acdc-requirements.api'; -import { handleReqGrant } from './apis/handle-req-grant'; +import express from "express"; +import { config } from "./config"; +import { ping } from "./apis/ping.api"; +import { getServerOOBI, resolveClientOOBI } from "./apis/oobi.api"; +import { getSchema } from "./apis/schema.api"; +import { discloseAcdc } from "./apis/discloseAcdc.api"; +import { decryptVerifyRequest, encryptSignResponse } from "./middlewares"; +import { getAcdcRequirements } from "./apis/acdcRequirements.api"; +import { handleReqGrant } from "./apis/handleReqGrant"; -const router = express.Router(); -// Currently, I am testing the interceptor with the ping api -router.get(config.path.ping, verifySession(['user']), verifyRequest, signResponse, ping); +export const router = express.Router(); +router.post(config.path.ping, decryptVerifyRequest, ping, encryptSignResponse); // POST to test ESSR router.post(config.path.resolveOOBI, resolveClientOOBI); router.get(config.path.oobi, getServerOOBI); -router.get(config.path.schema, schemaApi); -router.post(config.path.disclosureAcdc, disclosureAcdcApi); +router.get(config.path.schema, getSchema); +router.post(config.path.disclosureAcdc, discloseAcdc); router.get(config.path.acdcRequirements, getAcdcRequirements); router.get(config.path.handleReqGrant, handleReqGrant); - -export default router; diff --git a/demo/server/src/schemas.ts b/demo/server/src/schemas.ts new file mode 100644 index 0000000..b3c1e2e --- /dev/null +++ b/demo/server/src/schemas.ts @@ -0,0 +1,78 @@ +import { JSONValue } from "./types"; + +export const SCHEMAS = new Map([ + [ + "EGjD1gCLi9ecZSZp9zevkgZGyEX_MbOdmhBFt4o0wvdb", + { + $id: "EGjD1gCLi9ecZSZp9zevkgZGyEX_MbOdmhBFt4o0wvdb", + $schema: "http://json-schema.org/draft-07/schema#", + title: "Domain registration credential", + description: "A credential issued for domain registration purposes", + type: "object", + credentialType: "DomainCredential", + version: "1.0.0", + properties: { + v: { + description: "Version", + type: "string", + }, + d: { + description: "Credential SAID", + type: "string", + }, + u: { + description: "One time use nonce", + type: "string", + }, + i: { + description: "Issuee AID", + type: "string", + }, + ri: { + description: "Credential status registry", + type: "string", + }, + s: { + description: "Schema SAID", + type: "string", + }, + a: { + oneOf: [ + { + description: "Attributes block SAID", + type: "string", + }, + { + $id: "EBeI53glWgRv27Rt_r-D5aXIGBTPVO3jMV7QQKkQzA2n", + description: "Attributes block", + type: "object", + properties: { + d: { + description: "Attributes block SAID", + type: "string", + }, + i: { + description: "Issuee AID", + type: "string", + }, + dt: { + description: "Issuance date time", + type: "string", + format: "date-time", + }, + domain: { + description: "The domain address", + type: "string", + }, + }, + additionalProperties: false, + required: ["i", "dt", "domain"], + }, + ], + }, + }, + additionalProperties: false, + required: ["i", "ri", "s", "d"], + }, + ], +]); diff --git a/demo/server/src/server.ts b/demo/server/src/server.ts index 5f57148..dda3522 100644 --- a/demo/server/src/server.ts +++ b/demo/server/src/server.ts @@ -2,21 +2,28 @@ import express from "express"; import cors from "cors"; import bodyParser from "body-parser"; import { config } from "./config"; -import router from "./routes"; -import { log } from "./log"; -import { initKeri, initSignify } from "./modules/signifyApi"; +import { router } from "./routes"; +import { log } from "./utils/log"; +import { initKeri, initSignify } from "./services/signifyService"; import { dataSource } from "./database"; -const signifyName = config.signifyName; -log({ signifyName }); async function startServer() { const app = express(); - await dataSource.initialize() - console.log('Connected to database!'); + await dataSource.initialize(); + console.log("Connected to database!"); - app.use('/static', express.static('static')); - app.use(cors()); + app.use("/static", express.static("static")); + app.use( + cors({ + exposedHeaders: [ + "signature", + "signature-input", + "signify-resource", + "signify-timestamp", + ], + }), + ); app.use(bodyParser.json()); app.use(router); @@ -31,7 +38,7 @@ async function startServer() { name: identifier.name, prefix: identifier.prefix, oobi: oobi.oobis[0], - acdc : credDomain.sad + acdc: credDomain.sad, }); } diff --git a/demo/server/src/modules/signifyApi.ts b/demo/server/src/services/signifyService.ts similarity index 77% rename from demo/server/src/modules/signifyApi.ts rename to demo/server/src/services/signifyService.ts index 5cbca31..4aa04a4 100644 --- a/demo/server/src/modules/signifyApi.ts +++ b/demo/server/src/services/signifyService.ts @@ -1,15 +1,17 @@ import { Controller, + Encrypter, + Operation, Serder, SignifyClient, ready as signifyReady, Tier, + Verfer, } from "signify-ts"; -import { Aid } from "../types/signifyApi.types"; +import { Aid, ERROR_ACDC_NOT_FOUND } from "./signifyService.types"; import { config } from "../config"; -import { log } from "../log"; +import { log } from "../utils/log"; import { v4 as uuidv4 } from "uuid"; -import { ERROR_MESSAGE } from "../utils/constants"; const { keriaUrl, keriaBootUrl } = config; let signifyClient: SignifyClient; @@ -54,21 +56,19 @@ export const createIdentifier = async (name: string) => { }; export const getOOBIs = async (name: string, role: string) => { - const oobisResult = await signifyClient.oobis().get(name, role); - return oobisResult; + return signifyClient.oobis().get(name, role); }; -export const resolveOOBI = async (url: string) => { - let oobiOperation = await signifyClient.oobis().resolve(url); - oobiOperation = await waitAndGetDoneOp(oobiOperation, 15000, 250); - return oobiOperation; +export const resolveOOBI = async (url: string): Promise> => { + const oobiOperation = await signifyClient.oobis().resolve(url); + return waitAndGetDoneOp(oobiOperation, 15000, 250); }; -export const waitAndGetDoneOp = async ( - op: any, +export const waitAndGetDoneOp = async ( + op: Operation, timeout: number, interval: number, -) => { +): Promise> => { const startTime = new Date().getTime(); while (!op.done && new Date().getTime() < startTime + timeout) { op = await signifyClient.operations().get(op.name); @@ -92,7 +92,7 @@ export const issueDomainCredential = async ( schemaSAID: string, holder: string, domain: string, -) => { +): Promise => { const vcdata = { domain, }; @@ -149,7 +149,7 @@ export const disclosureAcdc = async ( const identifier = await getIdentifierByName(config.signifyName); const acdc = await getServerAcdc(identifier.prefix, schemaSaid, issuer); if (!acdc) { - throw new Error(ERROR_MESSAGE.ACDC_NOT_FOUND); + throw new Error(ERROR_ACDC_NOT_FOUND); } const datetime = new Date().toISOString().replace("Z", "000+00:00"); const [grant2, gsigs2, gend2] = await signifyClient.ipex().grant({ @@ -181,7 +181,11 @@ export const initKeri = async () => { const oobi = await getOOBIs(mainAidName, "agent"); // For the development purpose, the endpoint needs to be accessible from keria const schemaUrl = config.endpoint + "/oobi/" + schemaSaid; - await resolveOOBI(schemaUrl); + if (!(await resolveOOBI(schemaUrl)).done) { + throw new Error( + "Failed to resolve schema OOBI, endpoint most likely incorrect.", + ); + } let credDomain = await getServerAcdc(identifier.prefix, schemaSaid); @@ -202,29 +206,39 @@ export const initKeri = async () => { return { identifier, oobi, credDomain }; }; -export const getSigner = async (aid: Aid) => { +export const getKeyManager = async (aid: Aid) => { const client = await getSignifyClient(); - const signer = await client.manager?.get(aid); - return signer; + return client.manager?.get(aid); }; export const getServerSignifyController = async (): Promise => { const client = await getSignifyClient(); return client.controller; -} +}; -export const getExnMessageBySaid = async (said: string) => { +export const getRemoteVerfer = async (aid: string): Promise => { const client = await getSignifyClient(); - const exchanges = await client.exchanges().get(said); - return exchanges; -} + const pubKey = (await client.keyStates().get(aid))[0].k[0]; + return new Verfer({ qb64: pubKey }); +}; -export const getCredentials = async (filters?: any) => { +export const getRemoteEncrypter = async (aid: string): Promise => { + const client = await getSignifyClient(); + const pubKey = (await client.keyStates().get(aid))[0].k[0]; + return new Encrypter({}, new Verfer({ qb64: pubKey }).qb64b); +}; + +export const getExnMessageBySaid = async (said: string): Promise => { + const client = await getSignifyClient(); + return client.exchanges().get(said); +}; + +export const getCredentials = async (filters?: any): Promise => { const client = await getSignifyClient(); if (filters) { - return await client.credentials().list({ + return client.credentials().list({ filter: filters, }); } - return await client.credentials().list(); -} + return client.credentials().list(); +}; diff --git a/demo/server/src/types/signifyApi.types.ts b/demo/server/src/services/signifyService.types.ts similarity index 84% rename from demo/server/src/types/signifyApi.types.ts rename to demo/server/src/services/signifyService.types.ts index 41f882b..a8336a6 100644 --- a/demo/server/src/types/signifyApi.types.ts +++ b/demo/server/src/services/signifyService.types.ts @@ -29,3 +29,6 @@ export interface Aid { }; windexes: number[]; } + +export const ERROR_ACDC_NOT_FOUND = + "Unable to disclose ACDC with given requirements"; diff --git a/demo/server/src/types.ts b/demo/server/src/types.ts new file mode 100644 index 0000000..242f8f0 --- /dev/null +++ b/demo/server/src/types.ts @@ -0,0 +1,12 @@ +export interface EssrBody { + sig: string; + cipher: JSONValue; +} + +export type JSONValue = string | number | boolean | JSONObject | JSONArray; + +interface JSONObject { + [x: string]: JSONValue; +} + +interface JSONArray extends Array {} diff --git a/demo/server/src/types/response.type.ts b/demo/server/src/types/response.type.ts deleted file mode 100644 index 73ce5fd..0000000 --- a/demo/server/src/types/response.type.ts +++ /dev/null @@ -1,8 +0,0 @@ -interface ResponseData { - data: T; - error?: unknown; - success: boolean; - statusCode: number; -} - -export type { ResponseData }; diff --git a/demo/server/src/utils/constants.ts b/demo/server/src/utils/constants.ts deleted file mode 100644 index 79dd026..0000000 --- a/demo/server/src/utils/constants.ts +++ /dev/null @@ -1,3 +0,0 @@ -export enum ERROR_MESSAGE { - ACDC_NOT_FOUND = "ACDC_NOT_FOUND", -} diff --git a/demo/server/src/log.ts b/demo/server/src/utils/log.ts similarity index 56% rename from demo/server/src/log.ts rename to demo/server/src/utils/log.ts index 6c91c0a..71dd52a 100644 --- a/demo/server/src/log.ts +++ b/demo/server/src/utils/log.ts @@ -1,6 +1,4 @@ -function log(...args: unknown[]) { +export function log(...args: unknown[]) { // eslint-disable-next-line no-console console.log(...args); } - -export { log }; diff --git a/demo/server/src/utils/node-cache.ts b/demo/server/src/utils/node-cache.ts deleted file mode 100644 index e719fe6..0000000 --- a/demo/server/src/utils/node-cache.ts +++ /dev/null @@ -1,13 +0,0 @@ -import NodeCache from "node-cache"; - -const myCache = new NodeCache(); - -function setCache(key: string, value: string, ttl: number | string) { - return myCache.set(key, value, ttl); -} - -function getCache(key: string): string { - return myCache.get(key) as string; -} - -export { setCache, getCache }; diff --git a/demo/server/src/utils/response.util.ts b/demo/server/src/utils/response.util.ts deleted file mode 100644 index 2758f6a..0000000 --- a/demo/server/src/utils/response.util.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Response } from "express"; -import { ResponseData } from "../types/response.type"; -import { v4 as uuidv4 } from "uuid"; -import { config } from "../config"; -import { setCache } from "./node-cache"; - -const MAX_LENGTH_URL_CAN_VIEW_QR_CODE = 200; -const MAX_QR_CODE_TTL = 300; - -function httpResponse(res: Response, responseData: ResponseData) { - res.status(responseData.statusCode).send(responseData); -} - -function generableQRcodeWithUrl(url: string): string { - if (url.length > MAX_LENGTH_URL_CAN_VIEW_QR_CODE) { - const key = uuidv4(); - setCache(key, url, MAX_QR_CODE_TTL); - return config.endpoint + "/shorten/" + key; - } - return url; -} - -export { httpResponse, generableQRcodeWithUrl }; diff --git a/demo/server/src/utils/schemas/schemaAcdc.ts b/demo/server/src/utils/schemas/schemaAcdc.ts deleted file mode 100644 index c5466ec..0000000 --- a/demo/server/src/utils/schemas/schemaAcdc.ts +++ /dev/null @@ -1,75 +0,0 @@ -const SCHEMA_ACDC = { - EGjD1gCLi9ecZSZp9zevkgZGyEX_MbOdmhBFt4o0wvdb: { - $id: "EGjD1gCLi9ecZSZp9zevkgZGyEX_MbOdmhBFt4o0wvdb", - $schema: "http://json-schema.org/draft-07/schema#", - title: "Domain registration credential", - description: "A credential issued for domain registration purposes", - type: "object", - credentialType: "DomainCredential", - version: "1.0.0", - properties: { - v: { - description: "Version", - type: "string", - }, - d: { - description: "Credential SAID", - type: "string", - }, - u: { - description: "One time use nonce", - type: "string", - }, - i: { - description: "Issuee AID", - type: "string", - }, - ri: { - description: "Credential status registry", - type: "string", - }, - s: { - description: "Schema SAID", - type: "string", - }, - a: { - oneOf: [ - { - description: "Attributes block SAID", - type: "string", - }, - { - $id: "EBeI53glWgRv27Rt_r-D5aXIGBTPVO3jMV7QQKkQzA2n", - description: "Attributes block", - type: "object", - properties: { - d: { - description: "Attributes block SAID", - type: "string", - }, - i: { - description: "Issuee AID", - type: "string", - }, - dt: { - description: "Issuance date time", - type: "string", - format: "date-time", - }, - domain: { - description: "The domain address", - type: "string", - }, - }, - additionalProperties: false, - required: ["i", "dt", "domain"], - }, - ], - }, - }, - additionalProperties: false, - required: ["i", "ri", "s", "d"], - }, -}; - -export { SCHEMA_ACDC }; diff --git a/demo/server/tsconfig.json b/demo/server/tsconfig.json index 756c516..88f6b15 100644 --- a/demo/server/tsconfig.json +++ b/demo/server/tsconfig.json @@ -7,7 +7,7 @@ "target": "es2018", "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, - "noUnusedLocals": true, + "noUnusedLocals": false, "pretty": true, "strict": true, "sourceMap": true, @@ -18,7 +18,7 @@ "declaration": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, - "strictPropertyInitialization": false, + "strictPropertyInitialization": false }, "include": ["src/**/*"], "compileOnSave": false diff --git a/demo/ui/.env.example b/demo/ui/.env.example new file mode 100644 index 0000000..8ebd037 --- /dev/null +++ b/demo/ui/.env.example @@ -0,0 +1 @@ +VITE_SERVER_ENDPOINT=http://localhost:3001 diff --git a/demo/ui/package.json b/demo/ui/package.json index bf4ea61..a9ab4dc 100644 --- a/demo/ui/package.json +++ b/demo/ui/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "vite", - "build": "tsc && vite build", + "build": "tsc --noEmit && vite build", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview" }, diff --git a/demo/ui/src/App.tsx b/demo/ui/src/App.tsx index 565eb87..9dc50b7 100644 --- a/demo/ui/src/App.tsx +++ b/demo/ui/src/App.tsx @@ -1,57 +1,49 @@ -// src/App.tsx -import React, { useEffect, useState } from "react"; +import React, { useState } from "react"; import govLogo from "./assets/gov.png"; import "./App.scss"; -import { createAxiosClient, ExtensionMessageType } from "./axiosClient"; +import { createAxiosClient } from "./extension/axiosClient"; import { generateMessageId, listenForExtensionMessage, sendMessageToExtension, -} from "./utils/extensionCommunication"; +} from "./extension/communication"; +import { ExtensionMessageType } from "./extension/types"; + +const SERVER_ENDPOINT = import.meta.env.VITE_SERVER_ENDPOINT; const App: React.FC = () => { const [sessionCreated, setSessionCreated] = useState(false); const [signedHeaders, setSignedHeaders] = useState>( {}, ); + const [responseBody, setResponseBody] = useState(); const handleCreateSession = async () => { - const enterpriseData = {}; - const messageId = generateMessageId(ExtensionMessageType.CREATE_SESSION); - const extMessage = listenForExtensionMessage>( - ExtensionMessageType.SESSION_CREATED, - messageId, - ); - - sendMessageToExtension( + ExtensionMessageType.CREATE_SESSION_RESULT, messageId, - ExtensionMessageType.CREATE_SESSION, - enterpriseData, ); - const { success } = await extMessage; + sendMessageToExtension({ + id: messageId, + type: ExtensionMessageType.CREATE_SESSION, + data: { + url: SERVER_ENDPOINT, + }, + }); - if (success) { - setSessionCreated(true); - } + await extMessage; + setSessionCreated(true); }; const handleFetch = async () => { - try { - const axiosClient = createAxiosClient(); - const response = await axiosClient.get("http://localhost:3001/ping"); - console.log("Data:", response.data); - } catch (error) { - const serializedHeads = {}; - if (error.config.headers) { - Object.entries(error.config.headers).forEach(([key, value]) => { - serializedHeads[key] = value; - }); - setSignedHeaders(serializedHeads); - } - } + const axiosClient = createAxiosClient(); + const response = await axiosClient.post(`${SERVER_ENDPOINT}/ping`, { + dummy: "data", + }); + setSignedHeaders(JSON.parse(JSON.stringify(response.headers))); + setResponseBody(response.data); }; return ( @@ -85,6 +77,16 @@ const App: React.FC = () => { ) : null} {" "} +
+ {responseBody && ( + <> +

Decrypted response body

+
+
{JSON.stringify(responseBody, null, 2)}
+
+ + )} +
) : null} diff --git a/demo/ui/src/axiosClient.ts b/demo/ui/src/axiosClient.ts deleted file mode 100644 index b4e7db8..0000000 --- a/demo/ui/src/axiosClient.ts +++ /dev/null @@ -1,100 +0,0 @@ -import axios, { - AxiosInstance, - AxiosRequestHeaders, - AxiosResponse, - InternalAxiosRequestConfig, -} from "axios"; -import { - generateMessageId, - listenForExtensionMessage, - sendMessageToExtension, -} from "./utils/extensionCommunication"; - -enum ExtensionMessageType { - SIGN_HEADERS = "SIGN_HEADERS", - VERIFY_HEADERS = "VERIFY_HEADERS", - HEADERS_VERIFIED = "HEADERS_VERIFIED", - SIGNED_HEADERS = "SIGNED_HEADERS", - CREATE_SESSION = "CREATE_SESSION", - SESSION_CREATED = "SESSION_CREATED", -} - -/** - * Creates an Axios client configured with request and response interceptors. - * @param apiURL The base URL for HTTP requests. - * @returns client A configured Axios instance. - * - * The client includes a request interceptor to add signed headers to every request - * by communicating with an extension. It also includes a response interceptor - * to verify response headers with the extension, ensuring they meet - * the expected security criteria. - */ -const createAxiosClient = (): AxiosInstance => { - const client = axios.create(); - - client.interceptors.request.use( - async (config: InternalAxiosRequestConfig) => { - const serializedHeaders = {}; - if (config.headers) { - Object.entries(config.headers).forEach(([key, value]) => { - serializedHeaders[key] = value; - }); - } - - const messageId = generateMessageId(ExtensionMessageType.SIGN_HEADERS); - - const extMessage = listenForExtensionMessage>( - ExtensionMessageType.SIGNED_HEADERS, - messageId, - ); - - sendMessageToExtension(messageId, ExtensionMessageType.SIGN_HEADERS, { - headers: serializedHeaders, - baseURL: config.baseURL, - path: config.url, - method: config.method, - }); - - const { signedHeaders } = await extMessage; - config.headers = { - ...config.headers, - ...signedHeaders, - } as AxiosRequestHeaders; - - return config; - }, - (error) => { - return Promise.reject(error); - }, - ); - - client.interceptors.response.use( - async (response: AxiosResponse) => { - const messageId = generateMessageId(ExtensionMessageType.SIGN_HEADERS); - - const extMessage = listenForExtensionMessage( - ExtensionMessageType.HEADERS_VERIFIED, - messageId, - ); - - sendMessageToExtension(messageId, ExtensionMessageType.VERIFY_HEADERS, { - headers: response.headers, - }); - - const verificationResult = await extMessage; - - if (!verificationResult) { - throw new Error("Response headers verification failed."); - } - - return response; - }, - async (error) => { - return Promise.reject(error); - }, - ); - - return client; -}; - -export { createAxiosClient, ExtensionMessageType }; diff --git a/demo/ui/src/extension/axiosClient.ts b/demo/ui/src/extension/axiosClient.ts new file mode 100644 index 0000000..9a719e3 --- /dev/null +++ b/demo/ui/src/extension/axiosClient.ts @@ -0,0 +1,93 @@ +import axios, { + AxiosInstance, + AxiosRequestHeaders, + AxiosResponse, + InternalAxiosRequestConfig, +} from "axios"; +import { + generateMessageId, + listenForExtensionMessage, + sendMessageToExtension, +} from "./communication"; +import { ExtensionMessageType, SignEncryptResponse } from "./types"; + +/** + * Creates an Axios client configured with request and response interceptors. + * @returns client A configured Axios instance. + * + * The client includes a request interceptor to verify response signatures and decrypt any cipher material. + * It also includes a response interceptor to sign and/or encrypt the response. + */ +const createAxiosClient = (): AxiosInstance => { + const client = axios.create(); + + client.interceptors.request.use( + async (config: InternalAxiosRequestConfig) => { + const messageId = generateMessageId( + ExtensionMessageType.SIGN_ENCRYPT_REQ, + ); + const extMessage = listenForExtensionMessage( + ExtensionMessageType.SIGN_ENCRPYT_REQ_RESULT, + messageId, + ); + + sendMessageToExtension({ + id: messageId, + type: ExtensionMessageType.SIGN_ENCRYPT_REQ, + data: { + url: config.baseURL ? `${config.baseURL}/${config.url}` : config.url, + method: config.method?.toUpperCase(), + body: config.data, + }, + }); + + const { signedHeaders, essrBody } = await extMessage; + config.headers = { + ...config.headers, + ...JSON.parse(JSON.stringify(signedHeaders)), + } as AxiosRequestHeaders; + + config.data = essrBody; + return config; + }, + (error) => { + return Promise.reject(error); + }, + ); + + client.interceptors.response.use( + async (response: AxiosResponse) => { + const messageId = generateMessageId( + ExtensionMessageType.VERIFY_DECRYPT_RESP, + ); + // Will throw if it does not verify. + const extMessage = listenForExtensionMessage( + ExtensionMessageType.VERIFY_DECRYPT_RESP_RESULT, + messageId, + ); + + sendMessageToExtension({ + id: messageId, + type: ExtensionMessageType.VERIFY_DECRYPT_RESP, + data: { + url: response.config.baseURL + ? `${response.config.baseURL}/${response.config.url}` + : response.config.url, + method: response.config.method?.toUpperCase(), + headers: response.headers, + body: response.data, + }, + }); + + response.data = await extMessage; + return response; + }, + async (error) => { + return Promise.reject(error); + }, + ); + + return client; +}; + +export { createAxiosClient }; diff --git a/demo/ui/src/utils/extensionCommunication.ts b/demo/ui/src/extension/communication.ts similarity index 55% rename from demo/ui/src/utils/extensionCommunication.ts rename to demo/ui/src/extension/communication.ts index 08e33c6..a7b3edc 100644 --- a/demo/ui/src/utils/extensionCommunication.ts +++ b/demo/ui/src/extension/communication.ts @@ -1,17 +1,14 @@ import { v4 as uuidv4 } from "uuid"; - -export interface ExtensionMessage { - type: string; - data: any; -} +import { ExtensionMessageInbound, ExtensionMessageOutbound } from "./types"; const generateMessageId = (type: string) => { return `${type}:${uuidv4()}`; }; -const sendMessageToExtension = (id: string, type: string, data: any) => { - const message = { id, type, data }; + +const sendMessageToExtension = ( + message: ExtensionMessageOutbound, +): void => { window.postMessage(message, "*"); - return message; }; const listenForExtensionMessage = ( @@ -19,9 +16,18 @@ const listenForExtensionMessage = ( expectedId: string, ): Promise => { return new Promise((resolve, reject) => { - const handler = (event: MessageEvent) => { - if (event.data?.id === expectedId && event.data?.type === type) { + const handler = (event: MessageEvent>) => { + if (event.data.id === expectedId && event.data.type === type) { window.removeEventListener("message", handler); + if (!event.data.success) { + console.error( + `Received an unsuccessful error message: ${JSON.stringify( + event.data, + )}`, + ); + reject(event.data.error); + return; + } resolve(event.data.data as T); } }; diff --git a/demo/ui/src/extension/types.ts b/demo/ui/src/extension/types.ts new file mode 100644 index 0000000..372ac68 --- /dev/null +++ b/demo/ui/src/extension/types.ts @@ -0,0 +1,31 @@ +enum ExtensionMessageType { + SIGN_ENCRYPT_REQ = "SIGN_ENCRYPT_REQ", + SIGN_ENCRPYT_REQ_RESULT = "SIGN_ENCRYPT_REQ_RESULT", + VERIFY_DECRYPT_RESP = "VERIFY_DECRYPT_RESP", + VERIFY_DECRYPT_RESP_RESULT = "VERIFY_DECRYPT_RESP_RESULT", + CREATE_SESSION = "CREATE_SESSION", + CREATE_SESSION_RESULT = "CREATE_SESSION_RESULT", +} + +interface ExtensionMessageOutbound { + id: string; + type: ExtensionMessageType; + data: T; +} + +type ExtensionMessageInbound = ( + | { success: true; data: T } + | { success: false; error: unknown } +) & { id: string; type: ExtensionMessageType }; + +interface SignEncryptResponse { + signedHeaders: Headers; + essrBody?: any; // We just pass this directly though, so doesn't need strict typing. +} + +export type { + ExtensionMessageInbound, + ExtensionMessageOutbound, + SignEncryptResponse, +}; +export { ExtensionMessageType }; diff --git a/package-lock.json b/package-lock.json index fc74651..459b663 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "0.1.0", "license": "MIT", "dependencies": { - "json-canonicalize": "^1.0.6", "lodash": "^4.17.21", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -20,10 +19,12 @@ }, "devDependencies": { "@crxjs/vite-plugin": "^2.0.0-beta.21", + "@types/axios": "^0.14.0", "@types/chrome": "^0.0.237", "@types/lodash": "^4.14.197", "@types/react": "^18.2.39", "@types/react-dom": "^18.2.17", + "@types/uuid": "^9.0.8", "@typescript-eslint/eslint-plugin": "^5.30.5", "@typescript-eslint/parser": "^5.30.5", "@vitejs/plugin-react": "^4.2.1", @@ -33,7 +34,7 @@ "lint-staged": "^13.0.3", "prettier": "^2.7.1", "sass": "^1.53.0", - "signify-ts": "github:WebOfTrust/signify-ts#8b2080744ffc9da3a43a71164b80db8796ea2284", + "signify-ts": "github:cardano-foundation/signify-ts#c47354267b193a18b3f7d2ddc5d1076f36a2e867", "ts-node": "^10.9.1", "typescript": "^4.7.4", "vite": "^4.5.1", @@ -367,9 +368,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.5.tgz", - "integrity": "sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", + "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" @@ -1141,6 +1142,16 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true }, + "node_modules/@types/axios": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.14.0.tgz", + "integrity": "sha512-KqQnQbdYE54D7oa/UmYVMZKq7CO4l8DEENzOKc4aBRwxCXSlJXGz83flFx5L7AWrOQnmuN3kVsRdt+GZPPjiVQ==", + "deprecated": "This is a stub types definition for axios (https://github.com/mzabriskie/axios). axios provides its own type definitions, so you don't need @types/axios installed!", + "dev": true, + "dependencies": { + "axios": "*" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1270,6 +1281,12 @@ "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", "dev": true }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "dev": true + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.30.5", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.5.tgz", @@ -1678,6 +1695,12 @@ "node": ">=8" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -1690,6 +1713,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axios": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", + "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.15.4", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2186,6 +2220,18 @@ "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/commander/-/commander-9.3.0.tgz", @@ -2443,6 +2489,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/des.js": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", @@ -3179,6 +3234,26 @@ "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -3188,6 +3263,20 @@ "is-callable": "^1.1.3" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fraction.js": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.4.tgz", @@ -3851,11 +3940,6 @@ "node": ">=4" } }, - "node_modules/json-canonicalize": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/json-canonicalize/-/json-canonicalize-1.0.6.tgz", - "integrity": "sha512-kP2iYpOS5SZHYhIaR1t9oG80d4uTY3jPoaBj+nimy3njtJk8+sRsVatN8pyJRDRtk9Su3+6XqA2U8k0dByJBUQ==" - }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -4332,6 +4416,27 @@ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -4925,6 +5030,12 @@ "node": ">= 0.6.0" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "node_modules/public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", @@ -5120,9 +5231,9 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "dev": true }, "node_modules/regexpp": { @@ -5392,9 +5503,9 @@ "dev": true }, "node_modules/signify-ts": { - "version": "0.1.1", - "resolved": "git+ssh://git@github.com/WebOfTrust/signify-ts.git#8b2080744ffc9da3a43a71164b80db8796ea2284", - "integrity": "sha512-Doc2pYdihAVSSpwLhIfAnNJvSt6qBYh6XdkIx1wMELkoY4pyqgslgJcBAOATW+az2q4sdrM18Pe0lKuoS0WOBw==", + "version": "0.2.0", + "resolved": "git+ssh://git@github.com/cardano-foundation/signify-ts.git#c47354267b193a18b3f7d2ddc5d1076f36a2e867", + "integrity": "sha512-+G0HglICQyh+g6JS4DGR0G6QpVhJuYqhcYL7yHUJeLX6Su2grUdVnosGBDDuEfztkTjBd/0ZbR/QGKIptMyt2Q==", "dev": true, "license": "Apache-2.0", "workspaces": [ @@ -6597,9 +6708,9 @@ } }, "@babel/runtime": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.5.tgz", - "integrity": "sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", + "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", "dev": true, "requires": { "regenerator-runtime": "^0.14.0" @@ -7072,6 +7183,15 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true }, + "@types/axios": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.14.0.tgz", + "integrity": "sha512-KqQnQbdYE54D7oa/UmYVMZKq7CO4l8DEENzOKc4aBRwxCXSlJXGz83flFx5L7AWrOQnmuN3kVsRdt+GZPPjiVQ==", + "dev": true, + "requires": { + "axios": "*" + } + }, "@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -7201,6 +7321,12 @@ "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", "dev": true }, + "@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "dev": true + }, "@typescript-eslint/eslint-plugin": { "version": "5.30.5", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.5.tgz", @@ -7469,12 +7595,29 @@ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, "available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", "dev": true }, + "axios": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", + "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", + "dev": true, + "requires": { + "follow-redirects": "^1.15.4", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -7828,6 +7971,15 @@ "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", "dev": true }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, "commander": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/commander/-/commander-9.3.0.tgz", @@ -8044,6 +8196,12 @@ "object-keys": "^1.1.1" } }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true + }, "des.js": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", @@ -8615,6 +8773,12 @@ "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", "dev": true }, + "follow-redirects": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "dev": true + }, "for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -8624,6 +8788,17 @@ "is-callable": "^1.1.3" } }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, "fraction.js": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.4.tgz", @@ -9069,11 +9244,6 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, - "json-canonicalize": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/json-canonicalize/-/json-canonicalize-1.0.6.tgz", - "integrity": "sha512-kP2iYpOS5SZHYhIaR1t9oG80d4uTY3jPoaBj+nimy3njtJk8+sRsVatN8pyJRDRtk9Su3+6XqA2U8k0dByJBUQ==" - }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -9446,6 +9616,21 @@ } } }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "requires": { + "mime-db": "1.52.0" + } + }, "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -9859,6 +10044,12 @@ "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "dev": true }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", @@ -9997,9 +10188,9 @@ } }, "regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "dev": true }, "regexpp": { @@ -10196,10 +10387,10 @@ "dev": true }, "signify-ts": { - "version": "git+ssh://git@github.com/WebOfTrust/signify-ts.git#8b2080744ffc9da3a43a71164b80db8796ea2284", - "integrity": "sha512-Doc2pYdihAVSSpwLhIfAnNJvSt6qBYh6XdkIx1wMELkoY4pyqgslgJcBAOATW+az2q4sdrM18Pe0lKuoS0WOBw==", + "version": "git+ssh://git@github.com/cardano-foundation/signify-ts.git#c47354267b193a18b3f7d2ddc5d1076f36a2e867", + "integrity": "sha512-+G0HglICQyh+g6JS4DGR0G6QpVhJuYqhcYL7yHUJeLX6Su2grUdVnosGBDDuEfztkTjBd/0ZbR/QGKIptMyt2Q==", "dev": true, - "from": "signify-ts@github:WebOfTrust/signify-ts#8b2080744ffc9da3a43a71164b80db8796ea2284", + "from": "signify-ts@github:cardano-foundation/signify-ts#c47354267b193a18b3f7d2ddc5d1076f36a2e867", "requires": { "@noble/hashes": "^1.3.2", "buffer": "^6.0.3", diff --git a/package.json b/package.json index 5a217dd..6ecdc93 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Project Tunnel", "scripts": { "start": "vite", - "build": "vite build", + "build": "tsc --noEmit && vite build", "lint": "eslint --ext .ts,.js --max-warnings=0 .", "prettier": "prettier --write ." }, @@ -12,10 +12,12 @@ "license": "MIT", "devDependencies": { "@crxjs/vite-plugin": "^2.0.0-beta.21", + "@types/axios": "^0.14.0", "@types/chrome": "^0.0.237", "@types/lodash": "^4.14.197", "@types/react": "^18.2.39", "@types/react-dom": "^18.2.17", + "@types/uuid": "^9.0.8", "@typescript-eslint/eslint-plugin": "^5.30.5", "@typescript-eslint/parser": "^5.30.5", "@vitejs/plugin-react": "^4.2.1", @@ -25,7 +27,7 @@ "lint-staged": "^13.0.3", "prettier": "^2.7.1", "sass": "^1.53.0", - "signify-ts": "github:WebOfTrust/signify-ts#8b2080744ffc9da3a43a71164b80db8796ea2284", + "signify-ts": "github:cardano-foundation/signify-ts#c47354267b193a18b3f7d2ddc5d1076f36a2e867", "ts-node": "^10.9.1", "typescript": "^4.7.4", "vite": "^4.5.1", diff --git a/public/manifest.json b/public/manifest.json index 6a893c4..87f71d8 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -19,7 +19,7 @@ "externally_connectable": { "matches": [""] }, - "permissions": ["storage", "scripting", "tabs", "runtime"], + "permissions": ["storage", "scripting", "tabs"], "host_permissions": ["*://*/*"], "options_ui": { "page": "src/ui/pages/options/index.html", diff --git a/src/core/background/index.ts b/src/core/background/index.ts index 8120544..28f1a1e 100644 --- a/src/core/background/index.ts +++ b/src/core/background/index.ts @@ -1,249 +1,499 @@ import { uid } from "uid"; +import { Authenticater, b, Cipher, Matter, Cigar, Decrypter } from "signify-ts"; import { SignifyApi } from "@src/core/modules/signifyApi"; -import { convertURLImageToBase64, serializeHeaders } from "@src/utils"; +import { + convertURLImageToBase64, + failure, + failureExt, + serializeHeaders, + success, + successExt, +} from "@src/utils"; import { Logger } from "@src/utils/logger"; -import { Authenticater } from "signify-ts"; -import { ResponseData } from "@src/core/modules/signifyApi.types"; +import { LEAD_CODES } from "@src/core/modules/signifyApi.types"; +import { + EssrBody, + ExtensionMessage, + ResponseData, + ExtensionMessageType, +} from "@src/core/background/types"; +import { Session } from "@src/ui/pages/popup/sessionList/sessionList"; +import { LOCAL_STORAGE_SESSIONS } from "@src/ui/pages/popup/sessionDetails/sessionDetails"; const SERVER_ENDPOINT = import.meta.env.VITE_SERVER_ENDPOINT; -const expirationTime = 1800000; // 30 min const signifyApi: SignifyApi = new SignifyApi(); const logger = new Logger(); -const checkSignify = async (): Promise => { - if (!signifyApi.started) { - await signifyApi.start(); +const signEncryptRequest = async ( + ourAidName: string, + otherAidPrefix: string, + path: string, + method: string, + body?: any, +): Promise< + ResponseData<{ + signedHeaders: Headers; + essrBody?: EssrBody; + }> +> => { + // @TODO - foconnor: Need a better short-hand way to return the result if it's not successful. + const getAidResult = await signifyApi.getIdentifierByName(ourAidName); + if (!getAidResult.success) { + return getAidResult; } -}; -const getCurrentTabDetails = async (): Promise<{ - hostname: string; - port: string; - pathname: string; - favIconUrl: string; -}> => { - const queryOptions = { active: true, currentWindow: true }; - const [tab] = await chrome.tabs.query(queryOptions); - const hostname = new URL(tab.url).hostname; - const port = new URL(tab.url).port; - const pathname = new URL(tab.url).pathname; - const favIconUrl = tab.favIconUrl || ""; - - return { - hostname, - port, - pathname, - favIconUrl, + + const ourAid = getAidResult.data; + const getKeyManResult = await signifyApi.getKeyManager(ourAid); + if (!getKeyManResult.success) { + return getKeyManResult; + } + + const authenticator = new Authenticater( + getKeyManResult.data.signers[0], + getKeyManResult.data.signers[0].verfer, + ); + + // For now we only sign the default Signify headers and not any extras. + const headers = new Headers(); + headers.set("Signify-Resource", ourAid.prefix); + await logger.addLog( + `✅ Tunnel AID added to headers: ${JSON.stringify({ + "Signify-Resource": ourAid.prefix, + })}`, + ); + + const datetime = new Date().toISOString().replace("Z", "000+00:00"); + headers.set("Signify-Timestamp", datetime); + await logger.addLog( + `✅ Timestamp added to headers: ${JSON.stringify({ + "Signify-Timestamp": datetime, + })}`, + ); + + let signedHeaders; + try { + signedHeaders = authenticator.sign(headers, method, path); + await logger.addLog( + `✍️ Headers signed successfully by tunnel AID: ${ourAid.prefix}`, + ); + } catch (e) { + return { + success: false, + error: `Error while signing [${ourAidName}] headers ${JSON.stringify( + headers, + )}, method: ${method}, pathname: ${path}. Error: ${e}`, + }; + } + + if (!body) { + return success({ signedHeaders }); + } + + const getEncResult = await signifyApi.getRemoteEncrypter(otherAidPrefix); + if (!getEncResult.success) { + return getEncResult; + } + + const toEncrypt: Uint8Array = Buffer.from( + JSON.stringify({ + src: ourAid.prefix, + data: body, + }), + ); + const cipher: Cipher = getEncResult.data.encrypt( + null, + new Matter({ raw: toEncrypt, code: LEAD_CODES.get(toEncrypt.length % 3) }), + ); + + // src, datetime are both already in the headers, and dest is already known by the receiver for this (src, dest) interaction. + const essrBody: EssrBody = { + sig: getKeyManResult.data.signers[0].sign( + b( + JSON.stringify({ + src: ourAid.prefix, + dest: otherAidPrefix, + datetime, + cipher: cipher.qb64, + }), + ), + ).qb64, + cipher: cipher.qb64, }; + + return success({ signedHeaders, essrBody }); }; -const signHeaders = async ( +const verifyDecryptResponse = async ( + ourAidName: string, + otherAidPrefix: string, path: string, method: string, - headersToSign: any, - aidName: string, -): Promise> => { - try { + headers: Headers, + body?: any, +): Promise> => { + const reqAid = headers.get("Signify-Resource"); + if (!reqAid) { + return failure(new Error("Response missing Signify-Resource header")); + } + if (reqAid !== otherAidPrefix) { + return failure(new Error("Received response with a mismatched server AID")); + } - const ephemeralAID = await signifyApi.getIdentifierByName(aidName); + const reqDateTime = headers.get("Signify-Timestamp"); + if (!reqDateTime) { + return failure(new Error("Response missing Signify-Timestamp header")); + } - if (ephemeralAID.success) { - const signer = (await signifyApi.getSigner(ephemeralAID.data)).data; + // For now this isn't full protection - we need to verify that the request is unique. + // Follow up story will cover this. + if (Date.now() - new Date(reqDateTime).getTime() > 1000) { + return failure(new Error("Signify-Timestamp of response is too old")); + } - const authenticator = new Authenticater( - signer.signers[0], - signer.signers[0].verfer, - ); + const getReqVerferResult = await signifyApi.getRemoveVerfer(reqAid); + if (!getReqVerferResult.success) { + return getReqVerferResult; + } - const headers = new Headers(); - Object.entries(headersToSign).forEach(([key, value]) => { - headers.append(key, value); - }); + const getAidResult = await signifyApi.getIdentifierByName(ourAidName); + if (!getAidResult.success) { + return getAidResult; + } + const getKeyManResult = await signifyApi.getKeyManager(getAidResult.data); + if (!getKeyManResult.success) { + return getKeyManResult; + } - headers.set("Signify-Resource", ephemeralAID.data.prefix); - await logger.addLog( - `✅ Ephemeral AID added to headers: ${JSON.stringify({ - "Signify-Resource": ephemeralAID.data.prefix, - })}`, - ); + const authenticator = new Authenticater( + getKeyManResult.data.signers[0], // Not used here, we only need to verify so just inject our own. + getReqVerferResult.data, + ); - const timestamp = new Date().toISOString().replace("Z", "000+00:00"); - headers.set("Signify-Timestamp", timestamp); - await logger.addLog( - `✅ Timestamp added to headers: ${JSON.stringify({ - "Signify-Timestamp": timestamp, - })}`, + try { + if (!authenticator.verify(headers, method, path.split("?")[0])) { + return failure(new Error("Signify headers not in the correct format")); + } + } catch (error) { + console.warn(error); + // @TODO - foconnor: Catch this more specifically just in case. + return failure( + new Error("Signature header not valid for given Signify-Resource"), + ); + } + + if (body) { + if (!body.sig || !body.cipher) { + return failure( + new Error("Body must contain a valid ESSR ciphertext and signature"), ); + } - try { - const signedHeaders = authenticator.sign(headers, method, path); + const signature = new Cigar({ qb64: body.sig }); // @TODO - foconnor: Will crash if not valid CESR - handle. + if ( + !getReqVerferResult.data.verify( + signature.raw, + JSON.stringify({ + src: reqAid, + dest: getAidResult.data.prefix, + datetime: reqDateTime, + cipher: body.cipher, + }), + ) + ) { + return failure( + new Error("Signature of body is not valid for given Signify-Resource"), + ); + } - await logger.addLog( - `✍️ Headers signed successfully by ephemeral AID: ${ephemeralAID.data.prefix}`, - ); + const cipher = new Cipher({ qb64: body.cipher }); // @TODO - foconnor: Same here. + const decrypter: Decrypter = new Decrypter( + {}, + getKeyManResult.data.signers[0].qb64b, + ); + const decrypted = JSON.parse( + Buffer.from(decrypter.decrypt(null, cipher).raw).toString(), + ); - return { - success: true, - data: signedHeaders, - }; - } catch (e) { - return { - success: false, - error: `Error while signing.. headers: ${aidName}, method: ${method}, pathname: ${path}. Error: ${e}`, - }; - } - } else { - return { - success: false, - error: `Error getting ephemeral AID with name: ${aidName}. Error: ${ephemeralAID.error}`, - }; + if (!(decrypted.src && decrypted.src === reqAid)) { + return failure( + new Error("Invalid src identifier in plaintext of cipher"), + ); } - } catch (e) { - return { - success: false, - error: e, - }; + + return success(decrypted.data); } + + return success(undefined); }; -const createSession = async (): Promise> => { - let { hostname, port, favIconUrl } = await getCurrentTabDetails(); - if (port.length) { - hostname = `${hostname}:${port}`; - } - hostname = hostname.replace(":", "-"); - const logo = await convertURLImageToBase64(favIconUrl); +const createSession = async (): Promise> => { + // @TODO - foconnor: SERVER_ENDPOINT shouldn't be hardcoded. + const urlF = new URL(SERVER_ENDPOINT); + let response; try { - let response = await fetch(`${SERVER_ENDPOINT}/oobi`, { - method: "GET", - redirect: "follow", - }); - await logger.addLog(`✅ OOBI URL from ${SERVER_ENDPOINT}/oobi`); - - response = await response.json(); - const oobiUrl = response.oobis[0]; - await logger.addLog(`⏳ Resolving OOBI URL...`); + response = await fetch(`${SERVER_ENDPOINT}/oobi`); + await logger.addLog(`✅ Received OOBI URL from ${SERVER_ENDPOINT}/oobi`); + } catch (e) { + return failure( + new Error( + `Error getting OOBI URL from server: ${SERVER_ENDPOINT}/oobi: ${e}`, + ), + ); + } - const resolvedOOBI = await signifyApi.resolveOOBI(oobiUrl); + const oobiUrl = (await response.json()).oobis[0]; + await logger.addLog(`⏳ Resolving OOBI URL...`); - await logger.addLog(`✅ OOBI resolved successfully`); + const resolveOobiResult = await signifyApi.resolveOOBI(oobiUrl); + if (!resolveOobiResult.success) { + return failure( + new Error( + `Error resolving OOBI URL ${oobiUrl}: ${resolveOobiResult.error}`, + ), + ); + } + await logger.addLog(`✅ OOBI resolved successfully`); + + const createIdentifierResult = await signifyApi.createIdentifier( + urlF.hostname, + ); + if (!createIdentifierResult.success) { + return failure( + new Error( + `Error trying to create an AID with name ${urlF.hostname}: ${createIdentifierResult.error}`, + ), + ); + } - if (resolvedOOBI.success) { - try { - await signifyApi.createIdentifier(hostname); + const getOobiResult = await signifyApi.createOOBI(urlF.hostname); + if (!getOobiResult.success) { + return failure( + new Error( + `Error getting OOBI for identifier with name ${urlF.hostname}: ${getOobiResult.error}`, + ), + ); + } - await logger.addLog( - `✅ AID created successfully with name ${hostname}`, - ); + await logger.addLog(`✅ AID created successfully with name ${urlF.hostname}`); - const ephemeralAID = await signifyApi.getIdentifierByName(hostname); + try { + await fetch(`${SERVER_ENDPOINT}/resolve-oobi`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ oobiUrl: getOobiResult.data.oobis[0] }), + }); + } catch (e) { + return failure( + new Error(`Error triggering server to resolve tunnel OOBI: ${e}`), + ); + } - const newSession = { - id: uid(24), - tunnelAid: ephemeralAID.data.prefix, - expiryDate: "", - name: hostname, - logo, - oobi: resolvedOOBI?.data, - createdAt: Date.now() - }; + await logger.addLog( + `✅ Server has resolved our OOBI for identifier ${urlF.hostname}`, + ); + + const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); + const newSession: Session = { + id: uid(24), + tunnelAid: createIdentifierResult.data.serder.ked.i, + serverAid: oobiUrl.split("/oobi/")[1].split("/")[0], // todo get from oobiresult + expiryDate: "", + name: urlF.hostname, + logo: tab.favIconUrl ? await convertURLImageToBase64(tab.favIconUrl) : "", + oobi: resolveOobiResult.data, + createdAt: Date.now(), + }; - const { sessions } = await chrome.storage.local.get(["sessions"]); - const sessionsArray = sessions || []; - sessionsArray.push(newSession); + const { sessions } = await chrome.storage.local.get([LOCAL_STORAGE_SESSIONS]); + const sessionsArray = sessions || []; + sessionsArray.push(newSession); + await chrome.storage.local.set({ sessions: sessionsArray }); - await chrome.storage.local.set({ sessions: sessionsArray }); + await logger.addLog( + `🗃 New session stored in db: ${JSON.stringify(newSession)}`, + ); - await logger.addLog( - `🗃 New session stored in db: ${JSON.stringify(sessionsArray)}`, - ); - return { success: true }; - } catch (e) { - return { - success: false, - error: `Error trying to create an AID with name: ${hostname}`, - }; - } - } else { - return { - success: false, - error: ` Error while resolving the OOBI URL from server: ${SERVER_ENDPOINT}/oobi`, - }; - } - } catch (e) { - await logger.addLog( - `❌ Error getting OOBI URL from server: ${SERVER_ENDPOINT}/oobi`, - ); - return { success: false, type: "SESSION_CREATED" }; - } + return success(undefined); }; + chrome.runtime.onInstalled.addListener(async () => { await logger.addLog(`✅ Extension successfully installed!`); - await checkSignify(); + if (!signifyApi.started) { + await signifyApi.start(); + } await logger.addLog(`✅ Signify initialized successfully`); }); -chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { - processMessage(request).then((response) => sendResponse(response)); +chrome.runtime.onMessage.addListener((request, _, sendResponse) => { + processMessage(request) + .then((response) => response && sendResponse(response)) + .catch(console.error); return true; }); -async function processMessage(message) { - if (!message) return; +function getReturnMessageType( + inbound: ExtensionMessageType, +): ExtensionMessageType { + switch (inbound) { + case ExtensionMessageType.CREATE_SESSION: + return ExtensionMessageType.CREATE_SESSION_RESULT; + case ExtensionMessageType.SIGN_ENCRYPT_REQ: + return ExtensionMessageType.SIGN_ENCRPYT_REQ_RESULT; + case ExtensionMessageType.VERIFY_DECRYPT_RESP: + return ExtensionMessageType.VERIFY_DECRYPT_RESP_RESULT; + default: + return ExtensionMessageType.GENERIC_ERROR; + } +} +async function processMessage( + message: any, +): Promise | undefined> { + // @TODO - foconnor: Need better handling of message.data to avoid crashing. switch (message.type) { - case "CREATE_SESSION": { - const session = await createSession(); + case ExtensionMessageType.CREATE_SESSION: { + const { url } = message.data; + + const urlF = new URL(url); + const { sessions } = await chrome.storage.local.get([ + LOCAL_STORAGE_SESSIONS, + ]); + if ( + sessions && + sessions.find((session: Session) => session.name === urlF.hostname) + ) { + return successExt( + message.id, + getReturnMessageType(message.type), + "Session previously created before, ignoring.", + ); + } - if (session.success) { + const createSessionResult = await createSession(); + if (createSessionResult.success) { await logger.addLog(`✅ Session created successfully`); - return { type: "SESSION_CREATED", id: message.id, data: session }; + return successExt( + message.id, + getReturnMessageType(message.type), + createSessionResult.data, + ); } else { - await logger.addLog(`❌ ${session.error}`); - return { type: "SESSION_CREATED", id: message.id, data: session }; + await logger.addLog(`❌ ${createSessionResult.error}`); + return failureExt( + message.id, + getReturnMessageType(message.type), + createSessionResult.error, + ); } } - case "SIGN_HEADERS": { - const pathname = message.data.path; - const method = message.data.method; - const headers = message.data.headers; - - let { hostname, port } = await getCurrentTabDetails(); - if (port.length) { - hostname = `${hostname}:${port}`; + case ExtensionMessageType.SIGN_ENCRYPT_REQ: { + const { url, method, body } = message.data; + + const urlF = new URL(url); + const { sessions } = await chrome.storage.local.get([ + LOCAL_STORAGE_SESSIONS, + ]); + if (!sessions) { + return failureExt( + message.id, + ExtensionMessageType.SIGN_ENCRPYT_REQ_RESULT, + new Error(`Session not found for host ${urlF.hostname}`), + ); + } + const session: Session = sessions.find( + (session: Session) => session.name === urlF.hostname, + ); + if (!session) { + // @TODO - foconnor: Here we should call connect with that hostname - allows us to call other hosts than the one the UI is hosted on. + return failureExt( + message.id, + ExtensionMessageType.SIGN_ENCRPYT_REQ_RESULT, + new Error(`Session not found for host ${urlF.hostname}`), + ); } - hostname = hostname.replace(":", "-"); - const signedHeaders = await signHeaders( - pathname, + // @TODO - foconnor: Using one AID per domain/hostname means a UI can call other domains and expose + // the AID you use to talk to those. Ignore for now, especially since: + // 1) Tunnel is ephemeral + // 2) Identity wallet <-> Tunnel uses a different AID. + const encryptSignResult = await signEncryptRequest( + urlF.hostname, + session.serverAid, + urlF.pathname, method, - headers, - hostname, + body, ); - if (signedHeaders.success) { - await logger.addLog( - `📤 Signed headers sent to the website. Headers: ${JSON.stringify( - serializeHeaders(signedHeaders.data), - )}`, + if (!encryptSignResult.success) { + return failureExt( + message.id, + getReturnMessageType(message.type), + new Error( + `❌ Error while signing. Error: ${encryptSignResult.error}`, + ), ); + } - return { - success: true, - type: "SIGNED_HEADERS", - id: message.id, - data: { - signedHeaders: serializeHeaders(signedHeaders.data), - }, - }; - } else { - await logger.addLog( - `❌ Error while signing. Error: ${signedHeaders.error}`, + await logger.addLog( + `📤 Request signed and encrypted. Headers: ${JSON.stringify( + serializeHeaders(encryptSignResult.data.signedHeaders), + )} // Body: ${JSON.stringify(encryptSignResult.data.essrBody)}`, + ); + + return successExt(message.id, getReturnMessageType(message.type), { + ...encryptSignResult.data, + signedHeaders: serializeHeaders(encryptSignResult.data.signedHeaders), + }); + } + case ExtensionMessageType.VERIFY_DECRYPT_RESP: { + const { url, method, headers, body } = message.data; + + // We don't trust requests for other domains until they show ACDC etc. + const urlF = new URL(url); + const { sessions } = await chrome.storage.local.get([ + LOCAL_STORAGE_SESSIONS, + ]); + if (!sessions) { + return failureExt( + message.id, + getReturnMessageType(message.type), + new Error(`Session not found for host ${urlF.hostname}`), + ); + } + const session: Session = sessions.find( + (session: Session) => session.name === urlF.hostname, + ); + if (!session) { + // @TODO - foconnor: Here we should call connect with that hostname - allows us to call other hosts than the one the UI is hosted on. + return failureExt( + message.id, + getReturnMessageType(message.type), + new Error(`Session not found for host ${urlF.hostname}`), + ); + } + + const verifyDecryptResult = await verifyDecryptResponse( + urlF.hostname, + session.serverAid, + urlF.pathname, + method, + new Headers(headers), + body, + ); + if (!verifyDecryptResult.success) { + return failureExt( + message.id, + getReturnMessageType(message.type), + verifyDecryptResult.error, ); - return { success: false, type: "SIGNED_HEADERS", id: message.id, }; } + + return successExt( + message.id, + getReturnMessageType(message.type), + verifyDecryptResult.data, + ); } } } - -export { expirationTime }; diff --git a/src/core/background/types.ts b/src/core/background/types.ts new file mode 100644 index 0000000..281aab3 --- /dev/null +++ b/src/core/background/types.ts @@ -0,0 +1,32 @@ +export interface EssrBody { + sig: string; + cipher: JSONValue; +} + +export type JSONValue = string | number | boolean | JSONObject | JSONArray; + +interface JSONObject { + [x: string]: JSONValue; +} + +interface JSONArray extends Array {} + +export type ResponseData = + | { success: true; data: T } + | { success: false; error: unknown }; + +// These should be in a common types package. +export enum ExtensionMessageType { + SIGN_ENCRYPT_REQ = "SIGN_ENCRYPT_REQ", + SIGN_ENCRPYT_REQ_RESULT = "SIGN_ENCRYPT_REQ_RESULT", + VERIFY_DECRYPT_RESP = "VERIFY_DECRYPT_RESP", + VERIFY_DECRYPT_RESP_RESULT = "VERIFY_DECRYPT_RESP_RESULT", + CREATE_SESSION = "CREATE_SESSION", + CREATE_SESSION_RESULT = "CREATE_SESSION_RESULT", + GENERIC_ERROR = "ERROR_STREAM", +} + +export type ExtensionMessage = ResponseData & { + id: string; + type: ExtensionMessageType; +}; diff --git a/src/core/modules/signifyApi.ts b/src/core/modules/signifyApi.ts index 1359a06..f4ce5dd 100644 --- a/src/core/modules/signifyApi.ts +++ b/src/core/modules/signifyApi.ts @@ -1,12 +1,16 @@ import { - Authenticater, + Encrypter, + Verfer, + Operation, randomPasscode, ready, SignifyClient, Tier, + EventResult, } from "signify-ts"; -import { Aid, ResponseData } from "@src/core/modules/signifyApi.types"; -import { EventResult } from "signify-ts/src/keri/app/aiding"; +import { Aid } from "@src/core/modules/signifyApi.types"; +import { ResponseData } from "@src/core/background/types"; +import { failure, success } from "@src/utils"; class SignifyApi { private signifyClient!: SignifyClient; @@ -18,6 +22,7 @@ class SignifyApi { constructor() { this.started = false; } + async start(): Promise> { await ready(); const bran = await this.getBran(); @@ -32,27 +37,19 @@ class SignifyApi { try { await this.signifyClient.connect(); this.started = true; - return { - success: true, - }; + return success(undefined); } catch (err) { await this.signifyClient.boot(); try { await this.signifyClient.connect(); this.started = true; - return { - success: true, - }; + return success(undefined); } catch (e) { - return { - success: false, - error: new Error( - `Init Signify failed with Keria endpoint: ${SignifyApi.KERIA_URL}. Error: ${e}`, - ), - }; + return failure(e); } } } + private async getBran(): Promise { const bran = await chrome.storage.local.get([ SignifyApi.SIGNIFY_BRAN_STORAGE_KEY, @@ -69,100 +66,101 @@ class SignifyApi { } } - createIdentifier = async ( - name: string, - ): Promise> => { + async createIdentifier(name: string): Promise> { + try { + const op = await this.signifyClient.identifiers().create(name); + await op.op(); + await ( + await this.signifyClient + .identifiers() + .addEndRole(name, "agent", this.signifyClient.agent!.pre) + ).op(); + return success(op); + } catch (e) { + return failure(e); + } + } + + async getIdentifierByName(name: string): Promise> { try { - const aid = await this.signifyClient.identifiers().create(name); - return { - success: true, - data: aid, - }; + return success(await this.signifyClient.identifiers().get(name)); } catch (e) { - return { - success: false, - error: new Error( - `Error on AID creation with name ${name}. Error: ${e}`, - ), - }; + return failure(e); } - }; + } - getIdentifierByName = async (name: string): Promise> => { + async createOOBI(name: string): Promise> { try { - return { - success: true, - data: await this.signifyClient.identifiers().get(name), - }; + return success(await this.signifyClient.oobis().get(name, "agent")); } catch (e) { - return { - success: false, - error: e, - }; + return failure(e); } - }; + } - resolveOOBI = async (url: string): Promise> => { + async resolveOOBI(url: string): Promise> { try { - if (!this.started) - return { - success: false, - error: new Error("Signify not initialized"), - }; + if (!this.started) { + return failure(new Error("Signify not initialized")); + } const oobiOperation = await this.signifyClient.oobis().resolve(url); const r = await this.waitAndGetDoneOp(oobiOperation, 15000, 250); if (r.done) { - return { - success: true, - data: r, - }; + return success(r); } else { - return { - success: false, - error: new Error( + return failure( + new Error( `Resolving OOBI failed for URL: ${url}. \nResponse from Keria: ${JSON.stringify( r, )}`, ), - }; + ); } } catch (e) { - return { - success: false, - error: new Error( - `Resolving OOBI failed for URL: ${url}. \nError: ${e}`, - ), - }; + return failure( + new Error(`Resolving OOBI failed for URL: ${url}. \nError: ${e}`), + ); + } + } + + async getKeyManager(aid: Aid): Promise> { + try { + return success(await this.signifyClient.manager?.get(aid)); + } catch (e) { + return failure(e); + } + } + + async getRemoteEncrypter(aid: string): Promise> { + try { + const pubKey = (await this.signifyClient.keyStates().get(aid))[0].k[0]; + return success(new Encrypter({}, new Verfer({ qb64: pubKey }).qb64b)); + } catch (e) { + return failure(e); } - }; + } - getSigner = async (aid: Aid): Promise> => { + async getRemoveVerfer(aid: string): Promise> { try { - return { - success: true, - data: await this.signifyClient.manager?.get(aid), - }; + const pubKey = (await this.signifyClient.keyStates().get(aid))[0].k[0]; + return success(new Verfer({ qb64: pubKey })); } catch (e) { - return { - success: false, - error: e, - }; + return failure(e); } - }; + } - private waitAndGetDoneOp = async ( - op: any, + private async waitAndGetDoneOp( + op: Operation, timeout: number, interval: number, - ) => { + ): Promise { const startTime = new Date().getTime(); while (!op.done && new Date().getTime() < startTime + timeout) { op = await this.signifyClient.operations().get(op.name); await new Promise((resolve) => setTimeout(resolve, interval)); } return op; - }; + } } export { SignifyApi }; diff --git a/src/core/modules/signifyApi.types.ts b/src/core/modules/signifyApi.types.ts index 818c780..dc12d53 100644 --- a/src/core/modules/signifyApi.types.ts +++ b/src/core/modules/signifyApi.types.ts @@ -1,3 +1,5 @@ +import { MtrDex } from "signify-ts"; + interface Aid { name: string; prefix: string; @@ -30,8 +32,10 @@ interface Aid { windexes: number[]; } -type ResponseData = - | { success: true; data?: T } - | { success: false; error: unknown }; +const LEAD_CODES = new Map([ + [0, MtrDex.StrB64_L0], + [1, MtrDex.StrB64_L1], + [2, MtrDex.StrB64_L2], +]); -export { Aid, ResponseData }; +export { Aid, LEAD_CODES }; diff --git a/src/types.d.ts b/src/types.d.ts index bd36e60..0788e5e 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -1,9 +1,9 @@ declare module "*.png" { const value: string; - export = value; + export { value }; } declare module "*.svg" { const content: any; - export default content; + export { content }; } diff --git a/src/ui/pages/content/index.tsx b/src/ui/pages/content/index.tsx index 24a9d7a..f6d009d 100644 --- a/src/ui/pages/content/index.tsx +++ b/src/ui/pages/content/index.tsx @@ -1,6 +1,6 @@ window.addEventListener("message", (event) => { chrome.runtime.sendMessage(event.data, (response) => { - if (response){ + if (response) { window.postMessage(response, "*"); } }); diff --git a/src/ui/pages/options/options.tsx b/src/ui/pages/options/options.tsx index 96b97f0..3b23eee 100644 --- a/src/ui/pages/options/options.tsx +++ b/src/ui/pages/options/options.tsx @@ -16,14 +16,15 @@ const Options = () => { const updateLogs = async () => { const logger = new Logger(); - - try { - const lgs = (await logger.getLogs()).data; - const sortedLogs = lgs.sort((a, b) => b.timestamp - a.timestamp); - setLogs(sortedLogs); - } catch (error) { - console.error("Error updating logs:", error); + const getLogsResult = await logger.getLogs(); + if (!getLogsResult.success) { + return getLogsResult; } + + const sortedLogs = getLogsResult.data.sort( + (a, b) => b.timestamp - a.timestamp, + ); + setLogs(sortedLogs); }; useEffect(() => { diff --git a/src/ui/pages/popup/sessionDetails/sessionDetails.tsx b/src/ui/pages/popup/sessionDetails/sessionDetails.tsx index d598345..9fc0d19 100644 --- a/src/ui/pages/popup/sessionDetails/sessionDetails.tsx +++ b/src/ui/pages/popup/sessionDetails/sessionDetails.tsx @@ -4,6 +4,9 @@ import "./sessionDetails.scss"; import { BackButton } from "@components/backButton"; import MobileConnectIcon from "@assets/mobile-connect-icon.svg"; import { shortenText } from "@src/utils"; +import { Session } from "../sessionList/sessionList"; + +const LOCAL_STORAGE_SESSIONS = "sessions"; function SessionDetails() { const navigate = useNavigate(); @@ -18,7 +21,7 @@ function SessionDetails() { const deleteSession = () => { chrome.storage.local.get(["sessions"], function (result) { - const ss = result.sessions.filter((s) => session.id !== s.id); + const ss = result.sessions.filter((s: Session) => session.id !== s.id); chrome.storage.local.set({ sessions: ss }, function () { navigate(-1); @@ -50,8 +53,7 @@ function SessionDetails() { {session.expiryDate}

- Tunnel AID:{" "} - {shortenText(session.tunnelAid, 24)} + Tunnel AID: {shortenText(session.tunnelAid, 24)}

OOBI: @@ -67,4 +69,4 @@ function SessionDetails() { ); } -export { SessionDetails }; +export { SessionDetails, LOCAL_STORAGE_SESSIONS }; diff --git a/src/ui/pages/popup/sessionList/sessionList.tsx b/src/ui/pages/popup/sessionList/sessionList.tsx index 71e6243..e054681 100644 --- a/src/ui/pages/popup/sessionList/sessionList.tsx +++ b/src/ui/pages/popup/sessionList/sessionList.tsx @@ -5,18 +5,23 @@ import "./sessionList.scss"; import MobileConnectIcon from "../../../assets/mobile-connect-icon.svg"; import webLogo from "../../../assets/web.png"; import { isExpired } from "@src/utils"; +import { LOCAL_STORAGE_SESSIONS } from "../sessionDetails/sessionDetails"; interface Session { id: string; name: string; expiryDate: string; logo: string; + tunnelAid: string; + serverAid: string; + oobi: any; + createdAt: number; } function SessionList() { const navigate = useNavigate(); - const [sessions, setSessions] = useState([]); + const [sessions, setSessions] = useState([]); const handleNavigation = ( option: string, @@ -26,7 +31,7 @@ function SessionList() { }; useEffect(() => { - chrome.storage.local.get(["sessions"], function (result) { + chrome.storage.local.get([LOCAL_STORAGE_SESSIONS], function (result) { setSessions(result.sessions); }); }, []); @@ -39,9 +44,13 @@ function SessionList() { handleNavigation(`/${session.id}`, { state: { session } }); }; + if (!sessions.length) { + return

No sessions yet

; + } + return (
    - {sessions?.length ? sessions.map((session) => { + {sessions.map((session) => { return (
  • @@ -61,7 +70,7 @@ function SessionList() { )}
    - {!session.expiryDate || session.expiryDate === 0 ? ( + {!session.expiryDate || session.expiryDate === "" ? (
  • ); - }) : <> -

    No sessions yet

    - } + })}
); } -export { SessionList }; +export { SessionList, Session }; diff --git a/src/utils/index.ts b/src/utils/index.ts index d1220d9..51d6964 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,3 +1,8 @@ +import { + ExtensionMessage, + ExtensionMessageType, + ResponseData, +} from "@src/core/background/types"; import { uid } from "uid"; type Header = { @@ -28,7 +33,7 @@ const generateAID = async (): Promise<{ pubKey: string; privKey: string }> => { }; }; -const convertURLImageToBase64 = (url: string) => { +const convertURLImageToBase64 = (url: string): Promise => { return fetch(url) .then((response) => { if (!response.ok) { @@ -39,6 +44,8 @@ const convertURLImageToBase64 = (url: string) => { .then((blob) => { return new Promise((resolve, reject) => { const reader = new FileReader(); + // @TODO - foconnor: Better handle typing issues later. + // @ts-ignore reader.onloadend = () => resolve(reader.result); reader.onerror = reject; reader.readAsDataURL(blob); @@ -72,6 +79,47 @@ const parseHeaders = (serializedHeaders: Header) => { return headers; }; +const success = (data: T): ResponseData => { + return { + success: true, + data, + }; +}; + +const failure = (error: unknown): ResponseData => { + return { + success: false, + error, + }; +}; + +const successExt = ( + id: string, + type: ExtensionMessageType, + data: T, +): ExtensionMessage => { + return { + success: true, + id, + type, + data, + }; +}; + +const failureExt = ( + id: string, + type: ExtensionMessageType, + error: unknown, +): ExtensionMessage => { + console.error(`Returning error from extension: ${error}`); + return { + success: false, + id, + type, + error, + }; +}; + export { isExpired, getCurrentDate, @@ -80,4 +128,8 @@ export { shortenText, serializeHeaders, parseHeaders, + success, + failure, + successExt, + failureExt, }; diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 00c3b68..e11b781 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -1,4 +1,4 @@ -import { ResponseData } from "@src/core/modules/signifyApi.types"; +import { ResponseData } from "@src/core/background/types"; interface LogEntry { message: string; diff --git a/tsconfig.json b/tsconfig.json index 32db90e..7ac24b5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,7 +19,8 @@ "@pages/*": ["src/ui/pages/*"], "@components/*": ["src/ui/components/*"] }, - "include": ["src", "vite.config.ts"], - "exclude": ["node_modules"] - } + "types": ["chrome", "vite/client"] + }, + "include": ["src", "vite.config.ts"], + "exclude": ["node_modules"] } diff --git a/vite.config.ts b/vite.config.ts index 781d152..5b0f541 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,24 +1,24 @@ -import react from '@vitejs/plugin-react'; -import { resolve } from 'path'; -import { defineConfig, PluginOption } from 'vite'; -import compression from 'vite-plugin-compression'; -import { crx, ManifestV3Export } from '@crxjs/vite-plugin'; -import merge from 'lodash/merge'; -import { nodePolyfills } from 'vite-plugin-node-polyfills'; -import manifest from './public/manifest.json'; -import pkg from './package.json'; +import react from "@vitejs/plugin-react"; +import { resolve } from "path"; +import { defineConfig, PluginOption } from "vite"; +import compression from "vite-plugin-compression"; +import { crx, ManifestV3Export } from "@crxjs/vite-plugin"; +import merge from "lodash/merge"; +import { nodePolyfills } from "vite-plugin-node-polyfills"; +import manifest from "./public/manifest.json"; +import pkg from "./package.json"; // Routes -const root = resolve(__dirname, 'src'); -const outDir = resolve(__dirname, 'dist'); -const publicDir = resolve(__dirname, 'public'); +const root = resolve(__dirname, "src"); +const outDir = resolve(__dirname, "dist"); +const publicDir = resolve(__dirname, "public"); // Alias const aliasConfig = { - '@src': root, - '@assets': resolve(root, 'ui/assets'), - '@pages': resolve(root, 'ui/pages'), - '@components': resolve(root, 'ui/components'), + "@src": root, + "@assets": resolve(root, "ui/assets"), + "@pages": resolve(root, "ui/pages"), + "@components": resolve(root, "ui/components"), }; function loadManifestConfig() {