From a0e43500ac2d6fd434567a4d022bc321cf815fe3 Mon Sep 17 00:00:00 2001 From: Vladimir Ignatov Date: Sat, 12 Jun 2021 12:44:02 -0400 Subject: [PATCH] Simolify and clean up #51 --- package-lock.json | 1891 +++++++++++++++++++++-------------- package.json | 18 +- src/AuthorizeHandler.js | 10 +- src/config.js | 13 +- src/fhir-server.js | 86 ++ src/generator.js | 58 +- src/index.js | 185 +--- src/lib.js | 14 +- src/middlewares.js | 71 ++ src/reverse-proxy.js | 6 +- src/simple-proxy.js | 2 +- test/api.js | 2112 +++++++++++++++++---------------------- 12 files changed, 2282 insertions(+), 2184 deletions(-) create mode 100644 src/fhir-server.js create mode 100644 src/middlewares.js diff --git a/package-lock.json b/package-lock.json index 56e3bcd2..3dade6a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,10 +14,11 @@ "concurrently": "^5.2.0", "cors": "^2.7.1", "dotenv": "^6.2.0", - "express": "^4.13.3", - "jsonwebtoken": "^5.4.0", + "express": "^4.17.1", + "jsonwebtoken": "^8.5.1", "jwk-to-pem": "^1.2.6", "morgan": "^1.10.0", + "node-jose": "^2.0.0", "pem": "^1.14.4", "pem-jwk": "^1.5.1", "replaceall": "^0.1.6", @@ -28,20 +29,21 @@ "devDependencies": { "@types/base64-url": "^2.2.0", "@types/chai": "^4.2.11", - "@types/express": "^4.17.6", - "@types/jsonwebtoken": "^8.5.0", + "@types/express": "^4.17.12", + "@types/jsonwebtoken": "^8.5.1", "@types/jwk-to-pem": "^2.0.0", "@types/mocha": "^7.0.2", "@types/node": "^14.0.14", + "@types/node-jose": "^1.1.6", "@types/request": "^2.48.5", - "@types/supertest": "^2.0.9", + "@types/supertest": "^2.0.11", "chai": "^4.2.0", "mocha": "^8.0.1", "nightwatch": "^1.3.6", "nodemon": "^2.0.4", "nyc": "^15.1.0", "selenium-download": "^2.0.15", - "supertest": "^4.0.2", + "supertest": "^6.1.3", "typescript": "^2.6.1" } }, @@ -425,21 +427,21 @@ "dev": true }, "node_modules/@types/express": { - "version": "4.17.6", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.6.tgz", - "integrity": "sha512-n/mr9tZI83kd4azlPG5y997C/M4DNABK9yErhFM6hKdym4kkmd9j0vtsJyjFIwfRBxtrxZtAfGZCNRIBMFLK5w==", + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.12.tgz", + "integrity": "sha512-pTYas6FrP15B1Oa0bkN5tQMNqOcVXa9j4FTFtO8DWI9kppKib+6NJtfTOOLcwxuuYvcX2+dVG6et1SxW/Kc17Q==", "dev": true, "dependencies": { "@types/body-parser": "*", - "@types/express-serve-static-core": "*", + "@types/express-serve-static-core": "^4.17.18", "@types/qs": "*", "@types/serve-static": "*" } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.8", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.8.tgz", - "integrity": "sha512-1SJZ+R3Q/7mLkOD9ewCBDYD2k0WyZQtWYqF/2VvoNN2/uhI49J9CDN4OAm+wGMA0DbArA4ef27xl4+JwMtGggw==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.21.tgz", + "integrity": "sha512-gwCiEZqW6f7EoR8TTEfalyEhb1zA5jQJnRngr97+3pzMaO1RKoI1w2bw07TK72renMUVWcWS5mLI6rk1NqN0nA==", "dev": true, "dependencies": { "@types/node": "*", @@ -448,9 +450,9 @@ } }, "node_modules/@types/jsonwebtoken": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.0.tgz", - "integrity": "sha512-9bVao7LvyorRGZCw0VmH/dr7Og+NdjYSsKAxB43OQoComFbBgsEpoR9JW6+qSq/ogwVBg8GI2MfAlk4SYI4OLg==", + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-rNAPdomlIUX0i0cg2+I+Q1wOUr531zHBQ+cV/28PJ39bSPKjahatZZ2LMuhiguETkCgLVzfruw/ZvNMNkKoSzw==", "dev": true, "dependencies": { "@types/node": "*" @@ -480,6 +482,15 @@ "integrity": "sha512-syUgf67ZQpaJj01/tRTknkMNoBBLWJOBODF0Zm4NrXmiSuxjymFrxnTu1QVYRubhVkRcZLYZG8STTwJRdVm/WQ==", "dev": true }, + "node_modules/@types/node-jose": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@types/node-jose/-/node-jose-1.1.6.tgz", + "integrity": "sha512-iAr2hvnqg7ZktBjdbeT1yhXzIZcwp6C564mEsnr45ty+wE2AeSxIlNM9rGhhtQDdOWl6Kg3IbadgzA9/VWwnXw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/qs": { "version": "6.9.3", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.3.tgz", @@ -539,9 +550,9 @@ } }, "node_modules/@types/supertest": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.9.tgz", - "integrity": "sha512-0BTpWWWAO1+uXaP/oA0KW1eOZv4hc0knhrWowV06Gwwz3kqQxNO98fUFM2e15T+PdPRmOouNFrYvaBgdojPJ3g==", + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.11.tgz", + "integrity": "sha512-uci4Esokrw9qGb9bvhhSVEjd6rkny/dk5PK/Qz4yxKiyppEI+dOPlNrZBahE3i+PoKFYyDxChVXZ/ysS/nrm1Q==", "dev": true, "dependencies": { "@types/superagent": "*" @@ -565,6 +576,18 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "node_modules/accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dependencies": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/adm-zip": { "version": "0.4.16", "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", @@ -760,6 +783,25 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/base64-url": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/base64-url/-/base64-url-2.2.0.tgz", @@ -768,6 +810,14 @@ "node": ">=6" } }, + "node_modules/base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -808,33 +858,25 @@ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" }, "node_modules/body-parser": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", - "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", "dependencies": { - "bytes": "3.0.0", + "bytes": "3.1.0", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.1", - "http-errors": "~1.6.2", - "iconv-lite": "0.4.19", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", "on-finished": "~2.3.0", - "qs": "6.5.1", - "raw-body": "2.3.2", - "type-is": "~1.6.15" + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" }, "engines": { "node": ">= 0.8" } }, - "node_modules/body-parser/node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/body-parser/node_modules/content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", @@ -860,111 +902,52 @@ } }, "node_modules/body-parser/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", "dependencies": { "depd": "~1.1.2", "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" }, "engines": { "node": ">= 0.6" } }, - "node_modules/body-parser/node_modules/iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/body-parser/node_modules/inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, - "node_modules/body-parser/node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "node_modules/body-parser/node_modules/qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", "engines": { "node": ">=0.6" } }, "node_modules/body-parser/node_modules/raw-body": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", - "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", "dependencies": { - "bytes": "3.0.0", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", "unpipe": "1.0.0" }, "engines": { "node": ">= 0.8" } }, - "node_modules/body-parser/node_modules/raw-body/node_modules/depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/body-parser/node_modules/raw-body/node_modules/http-errors": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", - "dependencies": { - "depd": "1.1.1", - "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": ">= 1.3.1 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/body-parser/node_modules/raw-body/node_modules/setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" - }, - "node_modules/body-parser/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" - }, - "node_modules/body-parser/node_modules/type-is": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.18" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/boxen": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", @@ -1141,6 +1124,29 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -1150,7 +1156,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", - "dev": true, "engines": { "node": ">= 0.8" } @@ -1209,6 +1214,19 @@ "node": ">=8" } }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/camelcase": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", @@ -1506,6 +1524,17 @@ "node": ">=8" } }, + "node_modules/content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/convert-source-map": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", @@ -1515,6 +1544,14 @@ "safe-buffer": "~5.1.1" } }, + "node_modules/cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cookiejar": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", @@ -1735,6 +1772,11 @@ "node": ">= 0.8" } }, + "node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, "node_modules/diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", @@ -1826,6 +1868,14 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/end-of-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", @@ -1897,6 +1947,11 @@ "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", "dev": true }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, "node_modules/es6-promisify": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-6.1.1.tgz", @@ -1920,6 +1975,11 @@ "node": ">=8" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -1991,39 +2051,47 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/express": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", - "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", "dependencies": { - "accepts": "~1.3.5", + "accepts": "~1.3.7", "array-flatten": "1.1.1", - "body-parser": "1.18.2", - "content-disposition": "0.5.2", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", "content-type": "~1.0.4", - "cookie": "0.3.1", + "cookie": "0.4.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "~1.1.2", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.1.1", + "finalhandler": "~1.1.2", "fresh": "0.5.2", "merge-descriptors": "1.0.1", "methods": "~1.1.2", "on-finished": "~2.3.0", - "parseurl": "~1.3.2", + "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.3", - "qs": "6.5.1", - "range-parser": "~1.2.0", - "safe-buffer": "5.1.1", - "send": "0.16.2", - "serve-static": "1.13.2", - "setprototypeof": "1.1.0", - "statuses": "~1.4.0", - "type-is": "~1.6.16", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" }, @@ -2031,31 +2099,11 @@ "node": ">= 0.10.0" } }, - "node_modules/express/node_modules/accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", - "dependencies": { - "mime-types": "~2.1.18", - "negotiator": "0.6.1" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/express/node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, - "node_modules/express/node_modules/content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/express/node_modules/content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", @@ -2064,14 +2112,6 @@ "node": ">= 0.6" } }, - "node_modules/express/node_modules/cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/express/node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -2093,234 +2133,29 @@ "node": ">= 0.6" } }, - "node_modules/express/node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "node_modules/express/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "node_modules/express/node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/finalhandler": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", - "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "statuses": "~1.4.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "node_modules/express/node_modules/ipaddr.js": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", - "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/express/node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/express/node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" }, - "node_modules/express/node_modules/mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", - "bin": { - "mime": "cli.js" - } - }, "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, - "node_modules/express/node_modules/negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/express/node_modules/path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, - "node_modules/express/node_modules/proxy-addr": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", - "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==", - "dependencies": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.6.0" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/express/node_modules/qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", "engines": { "node": ">=0.6" } }, - "node_modules/express/node_modules/range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" - }, - "node_modules/express/node_modules/send": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", - "dependencies": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", - "on-finished": "~2.3.0", - "range-parser": "~1.2.0", - "statuses": "~1.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/express/node_modules/serve-static": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", - "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.2", - "send": "0.16.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/express/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" - }, - "node_modules/express/node_modules/statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/type-is": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.18" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/express/node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -2366,6 +2201,12 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "node_modules/fast-safe-stringify": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", + "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==", + "dev": true + }, "node_modules/file-uri-to-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz", @@ -2396,6 +2237,36 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, "node_modules/find-cache-dir": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", @@ -2538,15 +2409,34 @@ "mime-types": "^2.1.12" }, "engines": { - "node": ">= 0.12" + "node": ">= 0.12" + } + }, + "node_modules/formidable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", + "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==", + "dev": true, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "engines": { + "node": ">= 0.6" } }, - "node_modules/formidable": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", - "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==", - "dev": true - }, "node_modules/fromentries": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.2.0.tgz", @@ -2680,6 +2570,20 @@ "node": "*" } }, + "node_modules/get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -2965,7 +2869,6 @@ "version": "1.7.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", - "dev": true, "dependencies": { "depd": "~1.1.2", "inherits": "2.0.4", @@ -2981,7 +2884,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true, "engines": { "node": ">= 0.6" } @@ -3031,7 +2933,6 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -3039,6 +2940,25 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", @@ -3099,6 +3019,14 @@ "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", "dev": true }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -3619,31 +3547,26 @@ "dev": true }, "node_modules/jsonwebtoken": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-5.7.0.tgz", - "integrity": "sha1-HJD5qGzlt0j1+XnBK3BAK0r83bQ=", - "dependencies": { - "jws": "^3.0.0", - "ms": "^0.7.1", - "xtend": "^4.0.1" - }, - "engines": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=4", "npm": ">=1.4.28" } }, - "node_modules/jsonwebtoken/node_modules/ms": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.3.tgz", - "integrity": "sha1-cIFVpeROM/X9D8U+gdDUCpG+H/8=" - }, - "node_modules/jsonwebtoken/node_modules/xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", - "engines": { - "node": ">=0.4" - } - }, "node_modules/jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -3837,6 +3760,11 @@ "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, "node_modules/lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", @@ -3849,6 +3777,31 @@ "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", "dev": true }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, "node_modules/lodash.keys": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", @@ -3866,6 +3819,11 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, "node_modules/log-symbols": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", @@ -3878,6 +3836,11 @@ "node": ">=4" } }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, "node_modules/lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", @@ -3927,6 +3890,14 @@ "is-buffer": "~1.1.1" } }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -3939,7 +3910,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, "bin": { "mime": "cli.js" }, @@ -4515,8 +4485,7 @@ "node_modules/ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" }, "node_modules/mute-stream": { "version": "0.0.8", @@ -4545,6 +4514,14 @@ "ncp": "bin/ncp" } }, + "node_modules/negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/netmask": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", @@ -4758,6 +4735,30 @@ "semver": "bin/semver" } }, + "node_modules/node-forge": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/node-jose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/node-jose/-/node-jose-2.0.0.tgz", + "integrity": "sha512-j8zoFze1gijl8+DK/dSXXqX7+o2lMYv1XS+ptnXgGV/eloQaqq1YjNtieepbKs9jBS4WTnMOqyKSaQuunJzx0A==", + "dependencies": { + "base64url": "^3.0.1", + "buffer": "^5.5.0", + "es6-promise": "^4.2.8", + "lodash": "^4.17.15", + "long": "^4.0.0", + "node-forge": "^0.10.0", + "pako": "^1.0.11", + "process": "^0.11.10", + "uuid": "^3.3.3" + } + }, "node_modules/node-preload": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", @@ -5116,10 +5117,13 @@ } }, "node_modules/object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", - "dev": true + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz", + "integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/object-keys": { "version": "1.1.1", @@ -5526,6 +5530,11 @@ "semver": "bin/semver.js" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, "node_modules/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -5538,6 +5547,14 @@ "node": ">=4" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -5724,6 +5741,14 @@ "node": ">=4" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -5741,6 +5766,18 @@ "node": ">=8" } }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/proxy-agent": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-4.0.1.tgz", @@ -5824,6 +5861,14 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/raw-body": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz", @@ -6161,6 +6206,50 @@ "semver": "bin/semver.js" } }, + "node_modules/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/send/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/serialize-javascript": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", @@ -6170,6 +6259,20 @@ "randombytes": "^2.1.0" } }, + "node_modules/serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -6178,8 +6281,21 @@ "node_modules/setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", - "dev": true + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/signal-exit": { "version": "3.0.2", @@ -6429,43 +6545,123 @@ } }, "node_modules/superagent": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", - "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-6.1.0.tgz", + "integrity": "sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg==", "dev": true, "dependencies": { - "component-emitter": "^1.2.0", - "cookiejar": "^2.1.0", - "debug": "^3.1.0", - "extend": "^3.0.0", - "form-data": "^2.3.1", - "formidable": "^1.2.0", - "methods": "^1.1.1", - "mime": "^1.4.1", - "qs": "^6.5.1", - "readable-stream": "^2.3.5" + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.2", + "debug": "^4.1.1", + "fast-safe-stringify": "^2.0.7", + "form-data": "^3.0.0", + "formidable": "^1.2.2", + "methods": "^1.1.2", + "mime": "^2.4.6", + "qs": "^6.9.4", + "readable-stream": "^3.6.0", + "semver": "^7.3.2" }, "engines": { - "node": ">= 4.0" + "node": ">= 7.0.0" } }, - "node_modules/superagent/node_modules/debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "node_modules/superagent/node_modules/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", "dev": true, "dependencies": { - "ms": "^2.1.1" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/superagent/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/superagent/node_modules/qs": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", + "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/superagent/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/superagent/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, + "node_modules/superagent/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/supertest": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-4.0.2.tgz", - "integrity": "sha512-1BAbvrOZsGA3YTCWqbmh14L0YEq0EGICX/nBnfkfVJn7SrxQV1I3pMYjSzG9y/7ZU2V9dWqyqk2POwxlb09duQ==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.1.3.tgz", + "integrity": "sha512-v2NVRyP73XDewKb65adz+yug1XMtmvij63qIWHZzSX8tp6wiq6xBLUy4SUAd2NII6wIipOmHT/FD9eicpJwdgQ==", "dev": true, "dependencies": { "methods": "^1.1.2", - "superagent": "^3.8.3" + "superagent": "^6.1.0" }, "engines": { "node": ">=6.0.0" @@ -6539,7 +6735,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "dev": true, "engines": { "node": ">=0.6" } @@ -6627,6 +6822,18 @@ "node": ">=8" } }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -7485,21 +7692,21 @@ "dev": true }, "@types/express": { - "version": "4.17.6", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.6.tgz", - "integrity": "sha512-n/mr9tZI83kd4azlPG5y997C/M4DNABK9yErhFM6hKdym4kkmd9j0vtsJyjFIwfRBxtrxZtAfGZCNRIBMFLK5w==", + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.12.tgz", + "integrity": "sha512-pTYas6FrP15B1Oa0bkN5tQMNqOcVXa9j4FTFtO8DWI9kppKib+6NJtfTOOLcwxuuYvcX2+dVG6et1SxW/Kc17Q==", "dev": true, "requires": { "@types/body-parser": "*", - "@types/express-serve-static-core": "*", + "@types/express-serve-static-core": "^4.17.18", "@types/qs": "*", "@types/serve-static": "*" } }, "@types/express-serve-static-core": { - "version": "4.17.8", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.8.tgz", - "integrity": "sha512-1SJZ+R3Q/7mLkOD9ewCBDYD2k0WyZQtWYqF/2VvoNN2/uhI49J9CDN4OAm+wGMA0DbArA4ef27xl4+JwMtGggw==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.21.tgz", + "integrity": "sha512-gwCiEZqW6f7EoR8TTEfalyEhb1zA5jQJnRngr97+3pzMaO1RKoI1w2bw07TK72renMUVWcWS5mLI6rk1NqN0nA==", "dev": true, "requires": { "@types/node": "*", @@ -7508,9 +7715,9 @@ } }, "@types/jsonwebtoken": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.0.tgz", - "integrity": "sha512-9bVao7LvyorRGZCw0VmH/dr7Og+NdjYSsKAxB43OQoComFbBgsEpoR9JW6+qSq/ogwVBg8GI2MfAlk4SYI4OLg==", + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-rNAPdomlIUX0i0cg2+I+Q1wOUr531zHBQ+cV/28PJ39bSPKjahatZZ2LMuhiguETkCgLVzfruw/ZvNMNkKoSzw==", "dev": true, "requires": { "@types/node": "*" @@ -7540,6 +7747,15 @@ "integrity": "sha512-syUgf67ZQpaJj01/tRTknkMNoBBLWJOBODF0Zm4NrXmiSuxjymFrxnTu1QVYRubhVkRcZLYZG8STTwJRdVm/WQ==", "dev": true }, + "@types/node-jose": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@types/node-jose/-/node-jose-1.1.6.tgz", + "integrity": "sha512-iAr2hvnqg7ZktBjdbeT1yhXzIZcwp6C564mEsnr45ty+wE2AeSxIlNM9rGhhtQDdOWl6Kg3IbadgzA9/VWwnXw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/qs": { "version": "6.9.3", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.3.tgz", @@ -7598,9 +7814,9 @@ } }, "@types/supertest": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.9.tgz", - "integrity": "sha512-0BTpWWWAO1+uXaP/oA0KW1eOZv4hc0knhrWowV06Gwwz3kqQxNO98fUFM2e15T+PdPRmOouNFrYvaBgdojPJ3g==", + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.11.tgz", + "integrity": "sha512-uci4Esokrw9qGb9bvhhSVEjd6rkny/dk5PK/Qz4yxKiyppEI+dOPlNrZBahE3i+PoKFYyDxChVXZ/ysS/nrm1Q==", "dev": true, "requires": { "@types/superagent": "*" @@ -7624,6 +7840,15 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, "adm-zip": { "version": "0.4.16", "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", @@ -7785,11 +8010,21 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, "base64-url": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/base64-url/-/base64-url-2.2.0.tgz", "integrity": "sha512-Y4qHHAE+rWjmAFPQmHPiiD+hWwM/XvuFLlP6kVxlwZJK7rjiE2uIQR9tZ37iEr1E6iCj9799yxMAmiXzITb3lQ==" }, + "base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==" + }, "basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -7824,27 +8059,22 @@ "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" }, "body-parser": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", - "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", "requires": { - "bytes": "3.0.0", + "bytes": "3.1.0", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.1", - "http-errors": "~1.6.2", - "iconv-lite": "0.4.19", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", "on-finished": "~2.3.0", - "qs": "6.5.1", - "raw-body": "2.3.2", - "type-is": "~1.6.15" + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" }, "dependencies": { - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" - }, "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", @@ -7864,87 +8094,41 @@ "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" }, "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", "requires": { "depd": "~1.1.2", "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" } }, - "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" - }, "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" - }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, "raw-body": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", - "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", "unpipe": "1.0.0" - }, - "dependencies": { - "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" - }, - "http-errors": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", - "requires": { - "depd": "1.1.1", - "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": ">= 1.3.1 < 2" - } - }, - "setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" - } - } - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" - }, - "type-is": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.18" } } } @@ -8091,6 +8275,15 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -8099,8 +8292,7 @@ "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", - "dev": true + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" }, "cacheable-request": { "version": "6.1.0", @@ -8146,6 +8338,16 @@ "write-file-atomic": "^3.0.0" } }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, "camelcase": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", @@ -8382,6 +8584,14 @@ "xdg-basedir": "^4.0.0" } }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } + }, "convert-source-map": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", @@ -8391,6 +8601,11 @@ "safe-buffer": "~5.1.1" } }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, "cookiejar": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", @@ -8559,6 +8774,11 @@ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", @@ -8635,6 +8855,11 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, "end-of-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", @@ -8694,6 +8919,11 @@ "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", "dev": true }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, "es6-promisify": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-6.1.1.tgz", @@ -8711,6 +8941,11 @@ "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", "dev": true }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -8756,72 +8991,58 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, "express": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", - "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", "requires": { - "accepts": "~1.3.5", + "accepts": "~1.3.7", "array-flatten": "1.1.1", - "body-parser": "1.18.2", - "content-disposition": "0.5.2", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", "content-type": "~1.0.4", - "cookie": "0.3.1", + "cookie": "0.4.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "~1.1.2", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.1.1", + "finalhandler": "~1.1.2", "fresh": "0.5.2", "merge-descriptors": "1.0.1", "methods": "~1.1.2", "on-finished": "~2.3.0", - "parseurl": "~1.3.2", + "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.3", - "qs": "6.5.1", - "range-parser": "~1.2.0", - "safe-buffer": "5.1.1", - "send": "0.16.2", - "serve-static": "1.13.2", - "setprototypeof": "1.1.0", - "statuses": "~1.4.0", - "type-is": "~1.6.16", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" }, "dependencies": { - "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", - "requires": { - "mime-types": "~2.1.18", - "negotiator": "0.6.1" - } - }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, - "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" - }, "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" }, - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" - }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -8840,179 +9061,25 @@ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" - }, - "finalhandler": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", - "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "statuses": "~1.4.0", - "unpipe": "~1.0.0" - } - }, - "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" - }, - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ipaddr.js": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", - "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=" - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" - }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" }, - "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" - }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, - "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" - }, - "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" - }, "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, - "proxy-addr": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", - "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==", - "requires": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.6.0" - } - }, "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" - }, - "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" - }, - "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" - }, - "send": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", - "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", - "on-finished": "~2.3.0", - "range-parser": "~1.2.0", - "statuses": "~1.4.0" - } - }, - "serve-static": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", - "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.2", - "send": "0.16.2" - } - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" - }, - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" - }, - "type-is": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.18" - } + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, "utils-merge": { "version": "1.0.1", @@ -9052,6 +9119,12 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fast-safe-stringify": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", + "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==", + "dev": true + }, "file-uri-to-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz", @@ -9076,6 +9149,35 @@ "to-regex-range": "^5.0.1" } }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, "find-cache-dir": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", @@ -9192,6 +9294,16 @@ "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==", "dev": true }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, "fromentries": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.2.0.tgz", @@ -9309,6 +9421,17 @@ "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "dev": true }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, "get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -9541,7 +9664,6 @@ "version": "1.7.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", - "dev": true, "requires": { "depd": "~1.1.2", "inherits": "2.0.4", @@ -9553,8 +9675,7 @@ "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" } } }, @@ -9593,11 +9714,15 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, "ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", @@ -9649,6 +9774,11 @@ "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", "dev": true }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -10053,25 +10183,20 @@ "dev": true }, "jsonwebtoken": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-5.7.0.tgz", - "integrity": "sha1-HJD5qGzlt0j1+XnBK3BAK0r83bQ=", - "requires": { - "jws": "^3.0.0", - "ms": "^0.7.1", - "xtend": "^4.0.1" - }, - "dependencies": { - "ms": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.3.tgz", - "integrity": "sha1-cIFVpeROM/X9D8U+gdDUCpG+H/8=" - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" - } + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" } }, "jsprim": { @@ -10257,6 +10382,11 @@ "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, "lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", @@ -10269,6 +10399,31 @@ "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", "dev": true }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, "lodash.keys": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", @@ -10286,6 +10441,11 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, "log-symbols": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", @@ -10295,6 +10455,11 @@ "chalk": "^2.0.1" } }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", @@ -10337,6 +10502,11 @@ "is-buffer": "~1.1.1" } }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -10345,8 +10515,7 @@ "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, "mime-db": { "version": "1.44.0", @@ -10761,8 +10930,7 @@ "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" }, "mute-stream": { "version": "0.0.8", @@ -10782,6 +10950,11 @@ "integrity": "sha1-q8xsvT7C7Spyn/bnwfqPAXhKhXQ=", "dev": true }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, "netmask": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", @@ -10944,6 +11117,27 @@ } } }, + "node-forge": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" + }, + "node-jose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/node-jose/-/node-jose-2.0.0.tgz", + "integrity": "sha512-j8zoFze1gijl8+DK/dSXXqX7+o2lMYv1XS+ptnXgGV/eloQaqq1YjNtieepbKs9jBS4WTnMOqyKSaQuunJzx0A==", + "requires": { + "base64url": "^3.0.1", + "buffer": "^5.5.0", + "es6-promise": "^4.2.8", + "lodash": "^4.17.15", + "long": "^4.0.0", + "node-forge": "^0.10.0", + "pako": "^1.0.11", + "process": "^0.11.10", + "uuid": "^3.3.3" + } + }, "node-preload": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", @@ -11236,9 +11430,9 @@ "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz", + "integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==", "dev": true }, "object-keys": { @@ -11559,6 +11753,11 @@ } } }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -11568,6 +11767,11 @@ "json-parse-better-errors": "^1.0.1" } }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -11710,6 +11914,11 @@ "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", "dev": true }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -11724,6 +11933,15 @@ "fromentries": "^1.2.0" } }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, "proxy-agent": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-4.0.1.tgz", @@ -11795,6 +12013,11 @@ "safe-buffer": "^5.1.0" } }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, "raw-body": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz", @@ -12078,6 +12301,48 @@ } } }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + } + } + }, "serialize-javascript": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", @@ -12087,6 +12352,17 @@ "randombytes": "^2.1.0" } }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -12095,8 +12371,18 @@ "setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", - "dev": true + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } }, "signal-exit": { "version": "3.0.2", @@ -12296,42 +12582,95 @@ "dev": true }, "superagent": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", - "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-6.1.0.tgz", + "integrity": "sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg==", "dev": true, "requires": { - "component-emitter": "^1.2.0", - "cookiejar": "^2.1.0", - "debug": "^3.1.0", - "extend": "^3.0.0", - "form-data": "^2.3.1", - "formidable": "^1.2.0", - "methods": "^1.1.1", - "mime": "^1.4.1", - "qs": "^6.5.1", - "readable-stream": "^2.3.5" + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.2", + "debug": "^4.1.1", + "fast-safe-stringify": "^2.0.7", + "form-data": "^3.0.0", + "formidable": "^1.2.2", + "methods": "^1.1.2", + "mime": "^2.4.6", + "qs": "^6.9.4", + "readable-stream": "^3.6.0", + "semver": "^7.3.2" }, "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", "dev": true, "requires": { - "ms": "^2.1.1" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "mime": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "dev": true + }, + "qs": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", + "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true } } }, "supertest": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-4.0.2.tgz", - "integrity": "sha512-1BAbvrOZsGA3YTCWqbmh14L0YEq0EGICX/nBnfkfVJn7SrxQV1I3pMYjSzG9y/7ZU2V9dWqyqk2POwxlb09duQ==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.1.3.tgz", + "integrity": "sha512-v2NVRyP73XDewKb65adz+yug1XMtmvij63qIWHZzSX8tp6wiq6xBLUy4SUAd2NII6wIipOmHT/FD9eicpJwdgQ==", "dev": true, "requires": { "methods": "^1.1.2", - "superagent": "^3.8.3" + "superagent": "^6.1.0" } }, "supports-color": { @@ -12383,8 +12722,7 @@ "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "dev": true + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, "touch": { "version": "3.1.0", @@ -12448,6 +12786,15 @@ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, "typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", diff --git a/package.json b/package.json index 075d2431..4f8d56dd 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ "watch": "DOTENV_CONFIG_PATH=env/development.env node -r dotenv/config ./node_modules/.bin/nodemon --inspect ./src/index.js", "start": "node ./src/index.js", "test": "DOTENV_CONFIG_PATH=env/test.env node -r dotenv/config ./node_modules/.bin/mocha test/*.js", - "test:cover": "DOTENV_CONFIG_PATH=env/test.env node -r dotenv/config ./node_modules/.bin/nyc --reporter=lcov --reporter=text mocha test/*.js", - "test:watch": "DOTENV_CONFIG_PATH=env/test.env node -r dotenv/config ./node_modules/.bin/nodemon -w ./test ./node_modules/.bin/nyc --reporter=lcov mocha -R progress test/*.js", + "test:cover": "DOTENV_CONFIG_PATH=env/test.env node -r dotenv/config ./node_modules/.bin/nyc --reporter=lcov --reporter=text mocha --exit test/*.js", + "test:watch": "DOTENV_CONFIG_PATH=env/test.env node -r dotenv/config ./node_modules/.bin/mocha test/*.js --watch", "coverage": "DOTENV_CONFIG_PATH=env/test.env node -r dotenv/config ./node_modules/.bin/nyc report", "test:e2e": "NODE_ENV=test node ./selenium-download.js && ./node_modules/nightwatch/bin/nightwatch", "cert": "openssl genrsa -out private-key.pem 2048 && openssl rsa -in private-key.pem -outform PEM -pubout -out public-key.pem", @@ -31,10 +31,11 @@ "concurrently": "^5.2.0", "cors": "^2.7.1", "dotenv": "^6.2.0", - "express": "^4.13.3", - "jsonwebtoken": "^5.4.0", + "express": "^4.17.1", + "jsonwebtoken": "^8.5.1", "jwk-to-pem": "^1.2.6", "morgan": "^1.10.0", + "node-jose": "^2.0.0", "pem": "^1.14.4", "pem-jwk": "^1.5.1", "replaceall": "^0.1.6", @@ -45,20 +46,21 @@ "devDependencies": { "@types/base64-url": "^2.2.0", "@types/chai": "^4.2.11", - "@types/express": "^4.17.6", - "@types/jsonwebtoken": "^8.5.0", + "@types/express": "^4.17.12", + "@types/jsonwebtoken": "^8.5.1", "@types/jwk-to-pem": "^2.0.0", "@types/mocha": "^7.0.2", "@types/node": "^14.0.14", + "@types/node-jose": "^1.1.6", "@types/request": "^2.48.5", - "@types/supertest": "^2.0.9", + "@types/supertest": "^2.0.11", "chai": "^4.2.0", "mocha": "^8.0.1", "nightwatch": "^1.3.6", "nodemon": "^2.0.4", "nyc": "^15.1.0", "selenium-download": "^2.0.15", - "supertest": "^4.0.2", + "supertest": "^6.1.3", "typescript": "^2.6.1" } } diff --git a/src/AuthorizeHandler.js b/src/AuthorizeHandler.js index c22365ef..93650e90 100644 --- a/src/AuthorizeHandler.js +++ b/src/AuthorizeHandler.js @@ -229,10 +229,7 @@ class AuthorizeHandler extends SMARTHandler { aud : "" }); redirectUrl.search = null; - redirectUrl.pathname = redirectUrl.pathname.replace( - config.authBaseUrl + "/authorize", - to - ); + redirectUrl.pathname = redirectUrl.pathname.replace("/auth/authorize", to); return this.response.redirect(Url.format(redirectUrl)); } @@ -283,10 +280,7 @@ class AuthorizeHandler extends SMARTHandler { // The "aud" param must match the apiUrl (but can have different protocol) if (!sim.aud_validated) { - const apiUrl = Lib.buildUrlPath( - config.baseUrl, - req.baseUrl.replace(config.authBaseUrl, config.fhirBaseUrl) - ); + const apiUrl = Lib.buildUrlPath(config.baseUrl, req.baseUrl, "fhir"); let a = Lib.normalizeUrl(req.query.aud).replace(/^https?/, "").replace(/^:\/\/localhost/, "://127.0.0.1"); let b = Lib.normalizeUrl(apiUrl ).replace(/^https?/, "").replace(/^:\/\/localhost/, "://127.0.0.1"); if (a != b) { diff --git a/src/config.js b/src/config.js index ef16bd70..64eab926 100644 --- a/src/config.js +++ b/src/config.js @@ -2,12 +2,9 @@ const FS = require("fs"); const convert = require('pem-jwk'); const HOST = process.env.HOST || "localhost"; -const PORT = process.env.LAUNCHER_PORT || - process.env.PORT || - (process.env.NODE_ENV == "test" ? 8444 : 8443); +const PORT = process.env.LAUNCHER_PORT || process.env.PORT || (process.env.NODE_ENV == "test" ? 8444 : 8443); const PRIVATE_KEY = FS.readFileSync(__dirname + "/../private-key.pem", "utf8"); -const PUBLIC_KEY = FS.readFileSync(__dirname + "/../public-key.pem", "utf8"); const JWK = convert.pem2jwk(PRIVATE_KEY); [ @@ -24,17 +21,13 @@ const JWK = convert.pem2jwk(PRIVATE_KEY); }); module.exports = { + port : PORT, + host : HOST, fhirServerR2 : process.env.FHIR_SERVER_R2, fhirServerR3 : process.env.FHIR_SERVER_R3, fhirServerR4 : process.env.FHIR_SERVER_R4, baseUrl : process.env.BASE_URL || `http://${HOST}:${PORT}`, - sandboxTagSystem : process.env.SANDBOX_TAG_SYSTEM || "https://smarthealthit.org/tags", - authBaseUrl : process.env.AUTH_BASE_URL || "/auth", - fhirBaseUrl : process.env.FHIR_BASE_URL || "/fhir", - protectedSandboxWords: (process.env.PROTECTED_SANDBOX_WORDS || "smart,synthea,pro").split(","), jwtSecret : process.env.SECRET || "thisisasecret", - port : PORT, - host : HOST, accessTokenLifetime : process.env.ACCESS_TOKEN_LIFETIME || 60, // minutes refreshTokenLifeTime : process.env.REFRESH_TOKEN_LIFETIME || 60 * 24 * 365, // minutes backendServiceAccessTokenLifetime: process.env.BACKEND_ACCESS_TOKEN_LIFETIME || 15, // minutes diff --git a/src/fhir-server.js b/src/fhir-server.js new file mode 100644 index 00000000..b71f5014 --- /dev/null +++ b/src/fhir-server.js @@ -0,0 +1,86 @@ +/** + * This file defines a router for a virtual server. A virtual server is mostly + * just a pair of FHIR server for specific FHIR version and an auth server. + * These virtual server routers will be mounted on two parallel locations: + * - `{base}/v/{fhirVersion}` - default server + * - `{base}/v/{fhirVersion}/sim/{sim}` - with additional metadata in the {sim} + */ + +const express = require("express") +const base64url = require("base64-url") +const wellKnownOIDC = require("./wellKnownOIDCConfiguration") +const wellKnownSmart = require("./wellKnownSmartConfiguration") +const AuthorizeHandler = require("./AuthorizeHandler") +const RegistrationHandler = require("./RegistrationHandler") +const TokenHandler = require("./TokenHandler") +const { introspectionHandler } = require("./introspect") +const lib = require("./lib") +const simpleProxy = require("./simple-proxy") + +const fhirServer = module.exports = express.Router({ mergeParams: true }) +const urlencoded = express.urlencoded({ extended: false, limit: '64kb' }) +const text = express.text({ type: "*/*", limit: 1e6 }) + +// Authorization endpoints +// ----------------------------------------------------------------------------- + +fhirServer.get("/auth/authorize", AuthorizeHandler.handleRequest) + +fhirServer.post("/auth/token", urlencoded, TokenHandler.handleRequest) + +fhirServer.post("/auth/register", urlencoded, RegistrationHandler.handleRequest) + +fhirServer.post("/auth/introspect", urlencoded, introspectionHandler) + + +// Metadata endpoints +// ----------------------------------------------------------------------------- + +fhirServer.get("/.well-known/smart-configuration", wellKnownSmart) + +fhirServer.get("/.well-known/openid-configuration", wellKnownOIDC) + + +// UI endpoints +// ----------------------------------------------------------------------------- + +// patient picker +fhirServer.get("/picker", (_, res) => res.sendFile("picker.html", { root: './static' })) + +// encounter picker +fhirServer.get("/encounter", (_, res) => res.sendFile("encounter-picker.html", { root: './static' })) + +// user picker +fhirServer.get("/login", (_, res) => res.sendFile("login.html", { root: './static' })) + +// approve launch dialog +fhirServer.get("/authorize", (_, res) => res.sendFile("authorize.html", { root: './static' })) + + +// Other endpoints +// ----------------------------------------------------------------------------- + +// Provide launch_id if the CDS Sandbox asks for it +fhirServer.post("/fhir/_services/smart/launch", express.json(), (req, res) => { + res.json({ + launch_id: base64url.encode(JSON.stringify({ + context: req.body.parameters || {} + })) + }); +}); + +// Proxy everything else under `/fhir` to the underlying FHIR server +fhirServer.use("/fhir", text, handleParseError, simpleProxy); + + + +function handleParseError(err, req, res, next) { + if (err instanceof SyntaxError && err.status === 400) { + return lib.operationOutcome( + res, + `Failed to parse JSON content, error was: ${err.message}`, + { httpCode: 400 } + ); + } + next(err, req, res); +} diff --git a/src/generator.js b/src/generator.js index 948e75ff..42cc4294 100644 --- a/src/generator.js +++ b/src/generator.js @@ -1,52 +1,28 @@ -const router = require("express").Router({ mergeParams: true }); -const ursa = require('ursa-purejs'); -const crypto = require("crypto"); +const jose = require("node-jose") +const { whitelist } = require("./lib") -module.exports = router; - -router.get("/rsa", (req, res) => { - - let enc = String(req.query.enc || ""); - if (["base64", "binary", "hex", "utf8"].indexOf(enc) == -1) { - enc = undefined; - } - - // create a pair of keys (a private key contains both keys...) - const keys = ursa.generatePrivateKey(); - - // reconstitute the private and public keys from a base64 encoding - // @ts-ignore - const privatePem = keys.toPrivatePem(enc); - const publicPem = keys.toPublicPem(enc); - - // make a private key, to be used for encryption - // const privateKey = ursa.createPrivateKey(privatePem, '', enc); +const router = module.exports = require("express").Router({ mergeParams: true }); - // make a public key, to be used for decryption - // const publicKey = ursa.createPublicKey(publicPem, enc); - - res.json({ - privateKey: privatePem, - publicKey : publicPem - }); +module.exports = router; +router.get("/rsa", (req, res, next) => { + jose.JWK.createKey("RSA", 2048, { alg: "RS384" }).then(key => { + res.json({ + publicKey: key.toPEM(), + privateKey: key.toPEM(true) + }) + }, next) }); router.get("/random", (req, res) => { + const encodings = ["base64", "binary", "hex", "utf8", "ascii"]; + let enc = whitelist(encodings, String(req.query.enc), "hex"); - /** - * @type { "base64" | "binary" | "hex" | "utf8" } - */ - // @ts-ignore - let enc = String(req.query.enc || "ascii"); - if (["base64", "binary", "hex", "utf8"].indexOf(enc) == -1) { - enc = undefined; - } - - let len = +req.query.len; + let len = +(req.query.len || 32); if (isNaN(len) || !isFinite(len) || len < 1 || len > 1024) { len = 32; } - - res.send(crypto.randomBytes(len).toString(enc)); + + res.set("content-type", "text/plain") + res.end(jose.util.randomBytes(len).toString(enc)) }); diff --git a/src/index.js b/src/index.js index 70cce23a..10990cff 100644 --- a/src/index.js +++ b/src/index.js @@ -1,91 +1,29 @@ -const express = require("express"); -const cors = require("cors"); -const bodyParser = require('body-parser'); -const fs = require("fs"); -const base64url = require("base64-url"); -const smartAuth = require("./smart-auth"); -const simpleProxy = require("./simple-proxy"); -const config = require("./config"); -const generator = require("./generator"); -const lib = require("./lib"); -const launcher = require("./launcher"); -const wellKnownOIDC = require("./wellKnownOIDCConfiguration"); -const wellKnownSmart = require("./wellKnownSmartConfiguration"); -const { introspectionHandler } = require("./introspect") - -const handleParseError = function(err, req, res, next) { - if (err instanceof SyntaxError && err.status === 400) { - return lib.operationOutcome( - res, - `Failed to parse JSON content, error was: ${err.message}`, - { httpCode: 400 } - ); - } - next(err, req, res); -} +const express = require("express") +const cors = require("cors") +const fs = require("fs") +const config = require("./config") +const generator = require("./generator") +const lib = require("./lib") +const launcher = require("./launcher") +const fhirServer = require("./fhir-server") +const { + rejectXml, + blackList +} = require("./middlewares") -const handleXmlRequest = function(err, req, res, next) { - if ( - req.headers.accept &&req.headers.accept.indexOf("xml") != -1 || - req.headers['content-type'] && req.headers['content-type'].indexOf("xml") != -1 || - /_format=.*xml/i.test(req.url) - ) { - return lib.operationOutcome(res, "XML format is not supported", { httpCode: 400 }); - } - next(err, req, res) -} const app = express(); -app.use(cors({ - origin: true, - credentials: true -})); - -if (process.env.NODE_ENV == "development") { - app.use(require('morgan')('combined')); -} +// CORS everywhere :) +app.use(cors({ origin: true, credentials: true })); // Block some IPs -const IP_BLACK_LIST = String(process.env.IP_BLACK_LIST || "").trim().split(/\s*,\s*/); -if (IP_BLACK_LIST.length) { - app.use((req, res, next) => { - let ip = req.headers["x-forwarded-for"] + ""; - if (ip) { - ip = ip.split(",").pop(); - } - else { - ip = req.connection.remoteAddress; - } +app.use(blackList(process.env.IP_BLACK_LIST || "")); - if (ip && IP_BLACK_LIST.indexOf(ip) > -1) { - res.status(403).end( - `Your IP (${ip}) cannot access this service. ` + - `To find out more, please contact us at launch@smarthealthit.org.` - ); - } - else { - next(); - } - }); -} - -// HTTP to HTTPS redirect (this is Heroku-specific!) -// app.use((req, res, next) => { -// let proto = req.headers["x-forwarded-proto"]; -// let host = req.headers.host; -// if (proto && (`${proto}://${host}` !== config.baseUrl)) { -// return res.redirect(301, config.baseUrl + req.url); -// } -// next(); -// }); - -//reject xml -app.use(handleXmlRequest); - -// Well-known OpenID Connect (OIDC) Configuration -app.get("/.well-known/openid-configuration/", wellKnownOIDC); +// reject xml +app.use(rejectXml); +// Host public keys for backend services JWKS auth app.get("/keys", (req, res) => { let key = {} Object.keys(config.oidcKeypair).forEach(p => { @@ -94,70 +32,26 @@ app.get("/keys", (req, res) => { res.json({"keys":[key]}) }); -const buildRoutePermutations = (lastSegment) => { - return [ - "/v/:fhir_release/sim/:sim" + lastSegment, - "/v/:fhir_release" + lastSegment - ]; -} - -// Well-known SMART Configuration -app.get(buildRoutePermutations( - `${config.fhirBaseUrl}/.well-known/smart-configuration`), - wellKnownSmart -); - -// picker -app.get(buildRoutePermutations("/picker"), (req, res) => { - res.sendFile("picker.html", {root: './static'}); -}); - -// encounter picker -app.get(buildRoutePermutations("/encounter"), (req, res) => { - res.sendFile("encounter-picker.html", {root: './static'}); -}); - -// login -app.get(buildRoutePermutations("/login"), (req, res) => { - res.sendFile("login.html", {root: './static'}); -}); - -// authorize -app.get(buildRoutePermutations("/authorize"), (req, res) => { - res.sendFile("authorize.html", {root: './static'}); -}); - -// introspect -app.post( - buildRoutePermutations(`${config.authBaseUrl}/introspect`), - express.urlencoded({ limit: '64kb', extended: false }), - introspectionHandler -); - -// auth request -app.use(buildRoutePermutations(config.authBaseUrl), smartAuth) - -// Provide launch_id if the CDS Sandbox asks for it -app.post(buildRoutePermutations("/fhir/_services/smart/launch"), bodyParser.json(), (req, res) => { - res.json({ - launch_id: base64url.encode(JSON.stringify({"context": req.body.parameters || {}})) +// Also host the public key as PEM +app.get("/public_key", (req, res) => { + fs.readFile(__dirname + "/../public-key.pem", "utf8", (err, key) => { + if (err) { + return res.status(500).end("Failed to read public key"); + } + res.type("text").send(key); }); }); -// fhir request - no sandboxes - fast streaming proxy -app.use( - [ - `/v/:fhir_release/sim/:sim${config.fhirBaseUrl}`, - `/v/:fhir_release${config.fhirBaseUrl}` - ], - bodyParser.text({ type: "*/*", limit: 1e6 }), - handleParseError, - simpleProxy -); +// FHIR servers +app.use(["/v/:fhir_release/sim/:sim", "/v/:fhir_release"], fhirServer) +// The launcher used by the SMART App Gallery app.get("/launcher", launcher); + +// generate random strings or RS384 JWKs app.use("/generator", generator); +// Provide some env variables to the frontend app.use("/env.js", (req, res) => { const out = { PICKER_ORIGIN: "https://patient-browser.smarthealthit.org", @@ -193,20 +87,11 @@ app.use("/env.js", (req, res) => { res.type("javascript").send(`var ENV = ${JSON.stringify(out, null, 4)};`); }); -app.get("/public_key", (req, res) => { - fs.readFile(__dirname + "/../public-key.pem", "utf8", (err, key) => { - if (err) { - return res.status(500).end("Failed to read public key"); - } - res.type("text").send(key); - }); -}); - - -// static request +// static assets app.use(express.static("static")); -if (!module.parent) { +// Start the server if ran directly (tests import it and start it manually) +if (require.main?.filename === __filename) { app.listen(config.port, () => { console.log(`SMART launcher listening on port ${config.port}!`) }); @@ -220,7 +105,7 @@ if (!module.parent) { throw err } require("https").createServer({ - key: keys.serviceKey, + key : keys.serviceKey, cert: keys.certificate }, app).listen(process.env.SSL_PORT, () => { console.log(`SMART launcher listening on port ${process.env.SSL_PORT}!`) diff --git a/src/lib.js b/src/lib.js index 6f302db0..049f1d14 100644 --- a/src/lib.js +++ b/src/lib.js @@ -188,11 +188,11 @@ function normalizeUrl(url) { * Given a conformance statement (as JSON string), replaces the auth URIs with * new ones that point to our proxy server. Also add the rest.security.service * field. - * @param {String} bodyText A conformance statement as JSON string - * @param {String} authBaseUrl The baseUrl of our server + * @param {String} bodyText A conformance statement as JSON string + * @param {String} baseUrl The baseUrl of our server * @returns {Object|null} Returns the modified JSON object or null in case of error */ -function augmentConformance(bodyText, authBaseUrl) { +function augmentConformance(bodyText, baseUrl) { let json; try { json = JSON.parse(bodyText); @@ -209,15 +209,15 @@ function augmentConformance(bodyText, authBaseUrl) { "extension": [ { "url": "authorize", - "valueUri": buildUrlPath(authBaseUrl, "/authorize") + "valueUri": buildUrlPath(baseUrl, "/auth/authorize") }, { "url": "token", - "valueUri": buildUrlPath(authBaseUrl, "/token") + "valueUri": buildUrlPath(baseUrl, "/auth/token") }, { "url": "introspect", - "valueUri": buildUrlPath(authBaseUrl, "/introspect") + "valueUri": buildUrlPath(baseUrl, "/auth/introspect") } ] }]; @@ -249,7 +249,7 @@ function unBundleResource(bundle) { function adjustResponseUrls(bodyText, fhirUrl, requestUrl, fhirBaseUrl, requestBaseUrl) { bodyText = replaceAll(fhirUrl, requestUrl, bodyText); bodyText = replaceAll(fhirUrl.replace(",", "%2C"), requestUrl, bodyText); // allow non-encoded commas - bodyText = replaceAll(fhirBaseUrl, requestBaseUrl, bodyText); + bodyText = replaceAll("/fhir", requestBaseUrl, bodyText); return bodyText; } diff --git a/src/middlewares.js b/src/middlewares.js new file mode 100644 index 00000000..9b706511 --- /dev/null +++ b/src/middlewares.js @@ -0,0 +1,71 @@ +const { operationOutcome } = require("./lib"); + + +function rejectXml(err, req, res, next) { + if ( + (req.headers.accept && req.headers.accept.indexOf("xml") != -1) || + (req.headers['content-type'] && req.headers['content-type'].indexOf("xml") != -1) || + /_format=.*xml/i.test(req.url) + ) { + return operationOutcome(res, "XML format is not supported", { httpCode: 400 }); + } + next(err, req, res) +} + +// const handleParseError = function(err, req, res, next) { +// if (err instanceof SyntaxError && err.status === 400) { +// return lib.operationOutcome( +// res, +// `Failed to parse JSON content, error was: ${err.message}`, +// { httpCode: 400 } +// ); +// } +// next(err, req, res); +// } + +// HTTP to HTTPS redirect (this is Heroku-specific!) +// app.use((req, res, next) => { +// let proto = req.headers["x-forwarded-proto"]; +// let host = req.headers.host; +// if (proto && (`${proto}://${host}` !== config.baseUrl)) { +// return res.redirect(301, config.baseUrl + req.url); +// } +// next(); +// }); + +/** + * + * @param {string} ipList + */ +function blackList(ipList) { + const list = String(ipList || "").trim().split(/\s*,\s*/); + + return function(req, res, next) { + if (!list.length) { + return next() + } + + let ip = req.headers["x-forwarded-for"] + ""; + if (ip) { + ip = ip.split(",").pop() + ""; + } + else { + ip = req.connection.remoteAddress; + } + + if (ip && list.indexOf(ip) > -1) { + res.status(403).end( + `Your IP (${ip}) cannot access this service. ` + + `To find out more, please contact us at launch@smarthealthit.org.` + ); + } + else { + next(); + } + } +} + +module.exports = { + rejectXml, + blackList +} \ No newline at end of file diff --git a/src/reverse-proxy.js b/src/reverse-proxy.js index fd435018..3b83cb33 100644 --- a/src/reverse-proxy.js +++ b/src/reverse-proxy.js @@ -115,10 +115,10 @@ module.exports = function (req, res) { // special handler for metadata requests - inject the SMART information if (req.url.match(/^\/metadata/) && response.statusCode == 200 && body.indexOf("fhirVersion") != -1) { - let authBaseUrl = Lib.buildUrlPath(config.baseUrl, req.baseUrl.replace(config.fhirBaseUrl, config.authBaseUrl)); + let baseUrl = Lib.buildUrlPath(config.baseUrl, req.baseUrl.replace("/fhir", "")); let secure = req.secure || req.headers["x-forwarded-proto"] == "https"; - authBaseUrl = authBaseUrl.replace(/^https?/, secure ? "https" : "http"); - body = Lib.augmentConformance(body, authBaseUrl); + baseUrl = baseUrl.replace(/^https?/, secure ? "https" : "http"); + body = Lib.augmentConformance(body, baseUrl); if (!body) { res.status(404); body = fhirError(`Error reading server metadata`); diff --git a/src/simple-proxy.js b/src/simple-proxy.js index c7ecabcd..0be698d0 100644 --- a/src/simple-proxy.js +++ b/src/simple-proxy.js @@ -119,7 +119,7 @@ module.exports = (req, res) => { } // Proxy ------------------------------------------------------------------- - let fullFhirBaseUrl = `${config.baseUrl}/v/${fhirVersionLower}${config.fhirBaseUrl}`; + let fullFhirBaseUrl = `${config.baseUrl}/v/${fhirVersionLower}/fhir`; let stream = request(fhirRequestOptions) .on('error', function(error) { console.error(error); diff --git a/test/api.js b/test/api.js index 65cc0be7..34922ca1 100644 --- a/test/api.js +++ b/test/api.js @@ -1,142 +1,61 @@ - -const Request = require('request'); -const request = require('supertest'); -const app = require("../src/index.js"); -const config = require("../src/config"); -const jwt = require("jsonwebtoken"); -const Url = require("url"); -const jwkToPem = require("jwk-to-pem"); -const Codec = require("../static/codec.js"); -const base64url = require("base64-url"); -const Lib = require("../src/lib"); -const crypto = require("crypto"); -const expect = require("chai").expect; - - -const ENABLE_FHIR_VERSION_2 = true; -const ENABLE_FHIR_VERSION_3 = true; -const ENABLE_FHIR_VERSION_4 = true; -const PREFERRED_FHIR_VERSION = ENABLE_FHIR_VERSION_4 ? "r4" : ENABLE_FHIR_VERSION_3 ? "r3" : "r2"; -const PUBLIC_KEY = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFyTmx6RlFwbzRIZWY5dkVPYkdPUQpqc0RtelhyWFY4aDd3bVFxaFdhRkh0cCtLZW14ZmplOU02YkNrdjFsQ2RkajNFU3ZqeTkrd3lHTFlXQXJIdFYrCitGVVA5NjJPTVI5L2lNakpGZ0RDQjM5bnY0MGZLaTJBajRseCt6cE1XUnJZdVN3ZWdiYnNtT0hrL2t5RXUrSlgKTGgzNDlOZlZQSGdaRlhNUno5bUhXNk9hT21PVEVVYlY1RWJ0TnIxUFpCQVNYSGhpZ3VBTXZGcFl5Z2I3blFzQgo4OTBUOXVWcmM4bDB1OWpwc2J2OU10ZXZFeGZCTFlGSDQ4LzFrOUovWk5aalhBY281U2E3QTFlVXZGaSt0b01oCnBPL0lpVCs0L1BQaVRmYU9naXkrd3piNEFhUnF0MVMvWWx5RExIeVJuV3hlNThkMTdWZGY0Y0EzclVpVll0ZEEKWVFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg=="; -const PRIVATE_KEY = "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBck5sekZRcG80SGVmOXZFT2JHT1Fqc0RtelhyWFY4aDd3bVFxaFdhRkh0cCtLZW14CmZqZTlNNmJDa3YxbENkZGozRVN2ank5K3d5R0xZV0FySHRWKytGVVA5NjJPTVI5L2lNakpGZ0RDQjM5bnY0MGYKS2kyQWo0bHgrenBNV1JyWXVTd2VnYmJzbU9Iay9reUV1K0pYTGgzNDlOZlZQSGdaRlhNUno5bUhXNk9hT21PVApFVWJWNUVidE5yMVBaQkFTWEhoaWd1QU12RnBZeWdiN25Rc0I4OTBUOXVWcmM4bDB1OWpwc2J2OU10ZXZFeGZCCkxZRkg0OC8xazlKL1pOWmpYQWNvNVNhN0ExZVV2RmkrdG9NaHBPL0lpVCs0L1BQaVRmYU9naXkrd3piNEFhUnEKdDFTL1lseURMSHlSbld4ZTU4ZDE3VmRmNGNBM3JVaVZZdGRBWVFJREFRQUJBb0lCQVFDakRTVlFUZGVORjR0ZwpxUmlRQ29RTkJjOHpPcFAxRFB3aDdkZG1xOFVieThTRHlSMVVFVVI3ZXUzRk54K2UzdjRtaE95UFI2QnVkakJECkZUTFlEVkdPOUw3eFIxa0E0ZE91dHFscUJpRUNiWjd5eFM4RzNKR1AxWG9lSVdwd0M3RXhUSHNpcGVvZWRjbE0KVWVaTVRrRXJFYjhOU0tTd1BDSjlaMlVBQ3hWeXpSN3FraE5hVUZYRGNYb2U5Y2FiK3hCRWFJZkFSUUFodllBVwo3ZTlraldjbzRaRTREWm1HL1JnYnRkR1cxZ05xcERiWnE2cDR4VThIUHdoWWVPVko0dmgwUEZHVEp2VWJPTUx5CjU0dEx5a2lFc0lnQzYzRXZ1aTdmQzByWmRiVEt3Z293dnZvWC9uVXhua1VUQ25sREZtUnFReitCVURLZkVlcWIKRyt4V0NWYUJBb0dCQU5mRDBvN0dIRlhKRDBLMlRuSFdHVWhEdjdTMnJBS2lncHZrdVlqNkFSV3NqckJ1VWpGTgpzSGNnRW52cHRFZloyb2lRZjh0eTNCMG9RdnNsYUxwaTBTOG9uZGVISWI2Sms5SU5xRzJzeE5DdjAxdnozam1GCkk4aGFUajRwSzBpQ3lNWnVISUhWSHFQdUZWOStvSHc1QWVkbytSUjlRZ2NHMEdUTEduYWhHOENKQW9HQkFNMFUKN1lBYkJFVm4wY0JoYU9OREFhVmFLMlBZaGdjc3BOOGJNVDh1QlhYU2VzTXkyS3dRS0EwQVNLbm5vcUNoeThyeQpVbUFRN1NNUHM4V25OcEo4Ukh2cXBOazNiSVEyMnpLSHNBQkNmZ29pcWtPc2FqZmYvQWk0Y1E3UGMwYzV0LzA5CmRnOFJNMEw3cmhBdndRUnFraHNST09UemZocTRONVE0Qnd4NGV4c1pBb0dCQU1FU1JaUGt5dTRvb0RNK0Z5dmUKUFhsZ3htYmJIMGlzU3R0YzdIa1ozV2FicG9OUjlOS1JobHJTcERlbGhPRFduS3FmUXZ1MnFDaWZJbkRCcE5sRQpHNU5yY1BLdnhRNU81YXVNOVM1Tzd6OGVWcTl0cFdrckxqM1dNVFdHZVdqRlB3dnc5Q2xwbjZWcElrNzFiSDQ4Ck5PdnlEeEM2bFI3Y2hoWHJlSjYydzdLaEFvR0FTUHI3a2EwTGxnOWVDMUllMjFFTEV1YkZyaUJ0Z2J3WFovWHIKVG9wNEV2ZTJEQ1RhQ2xFdGo0TGNXT28vYTE1b2dXNCtka1ZQdmp4bVF4NUFRMXpKbWpka05wQ01vM2hLQk85WQphSjlBN3lacTVPNUVWbUgwOUwxK0xrRVF5dlgxVGI5RGRoVXU0dFZobWcwRWFTZnJtb3BFYnVWZnFPNkppTXR2ClpyYXhTSEVDZ1lBcW5EZ0Q3bW53bnFGdmJ0R2JTUjRUdVJzYi9TQi9HejArSzNtdDhRME5NbWNIVDFsM0c1REEKWDEvU3hZdUxLZ1F5UWNPQUh2b3RxcUp0R3I2d0ZaOHJVdnIwTnEwQUtFa2JyODF2V2NpbkhySWFySkZUaVE5Vgp2SGNxYktoRlJ5dEtndjdZcEt1STJSUWM5dG9FNS9BbDZBejJOdlJZOGJ2MzN3QUFxTTl1OXc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo="; - - - -//////////////////////////////////////////////////////////////////////////////// - -/** - * Promisified version of request. Rejects with an Error or resolves with the - * response (use response.body to access the parsed body). - * @param {Object} options The request options - * @return {Promise} - */ -function requestPromise(options) { - return new Promise((resolve, reject) => { - Request( - Object.assign({}, options, { strictSSL: false }), - (error, res, body) => { - if (error) { - return reject(error); - } - resolve(res); - } - ); - }); +const request = require('supertest'); +const jwt = require("jsonwebtoken"); +const Url = require("url"); +const jwkToPem = require("jwk-to-pem"); +const crypto = require("crypto"); +const { expect } = require("chai"); +const jose = require("node-jose") +const { Server } = require('http'); +const app = require("../src/index.js"); +const config = require("../src/config"); +const Codec = require("../static/codec.js"); +const Lib = require("../src/lib"); + +const TESTED_FHIR_SERVERS = { + "r4": config.fhirServerR4, + "r3": config.fhirServerR3, + "r2": config.fhirServerR2 } /** - * Throws an error if the response does not have the specified status code. - * @param {Object} res Response - * @param {Number} code The expected status code - * @returns {Object} Returns the response object to simplify promise chains - * @throws {Error} + * @param {object} options + * @param {string} options.path + * @param {object} [options.query] + * @param {string|object} [options.sim] + * @param {string} [options.fhir] */ -function expectStatusCode(res, code) { - if (res.statusCode !== code) { - throw new Error(`Expecting status code of ${code} but received ${res.statusCode}`); - } - return res; -} +function buildUrl({ path, query = {}, sim = "", fhir = "r4" }) { -/** - * Throws an error if the response does not have the specified header. - * The header name is required and the value is optional. - * @param {Object} res The response - * @param {String} name The name of the header - * @param {String|RegExp} [value] The header value (checks for presence only if - * this is missing) - * @returns {Object} Returns the response object to simplify promise chains - * @throws {Error} - */ -function expectResponseHeader(res, name, value = null) { - let header = String(res.headers[name.toLowerCase()] || ""); - if (!header) { - throw new Error(`Expecting ${name} response header but it wasn't sent`); + let _path = ""; + + if (fhir) { + _path += "/v/" + fhir; } - if (value) { - if (value instanceof RegExp) { - if (!value.test(header)) { - throw new Error(`The ${name} response header "${header}" did not match the specified RegExp`); - } - } - else if (header !== value) { - throw new Error(`Expecting ${name} response header to equal ${value} but found ${header}`); + + if (sim) { + if (typeof sim == "string") { + _path += "/sim/" + sim + } else { + _path += "/sim/" + jose.util.base64url.encode(JSON.stringify(sim)) } } - return res; -} -function lookupOidcKeys(done) { - const path = `${config.baseUrl}/.well-known/openid-configuration/`; - let keysLocation, keys; - - Request({ - url: path, - json: true, - strictSSL: false - }, (error, res, body) => { - if (error) { - return done(error) - } + path = (_path + "/" + path).replace(/\/+/g, "/") - try { - expectStatusCode(res, 200); - expectResponseHeader(res, 'Content-Type', /^application\/json/); - if (!body) { - throw new Error(`${path} did not return a JSON`); - } - if (!body.jwks_uri) { - throw new Error(`${path} did not return proper keys location`); - } - } catch(ex) { - return done(ex); - } + const url = new URL(path, config.baseUrl) - Request({ - url: body.jwks_uri, - json: true, - strictSSL: false - }, (error2, res2, body2) => { - if (error2) { - return done(error2) - } - - try { - expectStatusCode(res2, 200); - expectResponseHeader(res, 'Content-Type', /^application\/json/); - if (!body2) { - throw new Error(`${keysLocation} did not return a JSON`) - } - if (!body2.keys) { - throw new Error(`${keysLocation} did not return keys`); - } - } catch(ex) { - return done(ex); - } + for (const key in query) { + if (key == "launch" && query[key] && typeof query[key] != "string") { + url.searchParams.set( + key, + Buffer.from(JSON.stringify(query[key])).toString("base64") + ) + } else { + url.searchParams.set(key, query[key]) + } + } - done(null, body2.keys); - }) - }); + return url } /** @@ -145,493 +64,565 @@ function lookupOidcKeys(done) { * @param {Object} object The object to encode * @returns {String} */ -function encodeSim(object = {}) { - return new Buffer( - JSON.stringify(Codec.encode(object)) - ).toString("base64"); + function encodeSim(object = {}) { + return Buffer.from(JSON.stringify(Codec.encode(object)), "utf8").toString("base64"); } /** - * Makes the initial authorization request and expects the server to redirect - * back to redirect_uri with the given state and a code. - * @param {Object} options - * @param {String} options.patient 0 or more comma-separated patient IDs. - * Defaults to "x" because we ignore it. - * @param {String} options.client_id The client_id of the app. Defaults to "x" - * because we ignore it. - * @param {String} options.redirect_uri The uri to redirect to. Defaults to - * "http://x.y" because we ignore it but still require it to be valid URL. - * @param {String} options.scope - * @param {String} options.state - * @param {String} options.aud - * @param {Object} options.launch - * @param {Number} options.launch.launch_pt 1 or 0 - * @param {Number} options.launch.skip_login 1 or 0 - * @param {Number} options.launch.skip_auth 1 or 0 - * @param {String} options.launch.patient - * @param {String} options.launch.encounter - * @param {String} options.launch.auth_error - * @param {String} options.baseUrl - * @returns {Promise} Returns a promise resolved with the code + * @param {string} fhirVersion "r2" | "r3" | "r4" */ -function getAuthCode(options) { - return new Promise((resolve, reject) => { - Request({ - url : `${options.baseUrl}auth/authorize`, - strictSSL: false, - followRedirect: false, - qs: { - response_type: "code", - patient : options.patient || "x", - client_id : options.client_id || "x", - redirect_uri : options.redirect_uri || "http://x.y", - scope : options.scope || "x", - state : options.state || "x", - launch : encodeSim(options.launch), - aud : `${options.baseUrl}fhir` - } - }, (error, res, body) => { - if (error) { - return reject(error) - } - - try { - expectStatusCode(res, 302); - - if (!res.headers.location) { - throw new Error(`auth/authorize did not redirect to the redirect_uri`) +function getSmartApi(fhirVersion) +{ + return { + + /** + * @param {object} options + * + * SMART parameters + * @param {string} [options.client_id] + * @param {string} [options.scope] + * @param {string} [options.state] + * @param {string} [options.redirect_uri] + * @param {object|string} [options.launch] If object, will be converted to base64 json string + * + * Custom parameters + * @param {string} [options.patient] Pre-selected patient id(s) + * @param {string} [options.provider] Pre-selected provider id(s) + * @param {string} [options.encounter] Pre-selected encounter id(s) + * @param {number|boolean} [options.auth_success] Flag to skip the launch confirmation dialog + * @param {number|boolean} [options.login_success] Flag to skip the launch login dialog + * @param {number|boolean} [options.aud_validated] Flag to skip the aud validation + */ + getAuthCode: async function(options) { + + const { + client_id = "test_client_id", + scope = "test_scope", + state = "test_state", + redirect_uri = "http://test_redirect_uri" + } = options; + + const url = buildUrl({ + fhir: fhirVersion, + path: "/auth/authorize", + query: { + ...options, + client_id, + scope, + state, + redirect_uri, + aud: buildUrl({ fhir: fhirVersion, path: "fhir" }).href, + response_type: "code" } - let url = Url.parse(res.headers.location, true); - let code = url.query.code + ""; + }) + + return request(app) + .get(url.pathname) + .query(url.searchParams.toString()) + .redirects(0) + .expect(302) + .expect("location", /^https?\:\/\/.+/) + .then(res => { + const loc = new URL(res.get("location")); + const code = loc.searchParams.get("code"); if (!code) { - console.log(res.headers) - throw new Error(`auth/authorize did not redirect to the redirect_uri with code parameter`) + throw new Error(`authorize did not redirect to the redirect_uri with code parameter`) } - // console.log("code: ", JSON.parse(base64url.decode(code.split(".")[1]))); - resolve(code); - } catch(ex) { - reject(ex); - } - }); - }); -} + return { code, state: loc.searchParams.get("state") } + }); + }, -function getAuthToken(options) { - return new Promise((resolve, reject) => { - Request({ - url : `${options.baseUrl}auth/token`, - method : "POST", - strictSSL: false, - followRedirect: false, - json: true, - form: { - grant_type: "authorization_code", - code : options.code - } - }, (error, res, body) => { - if (error) { - return reject(error) - } + /** + * @param {string} code + */ + getAccessToken: async function(code) { + return request(app) + .post(buildUrl({ fhir: fhirVersion, path: "auth/token" }).pathname) + .redirects(0) + .type("form") + .send({ grant_type: "authorization_code", code }) + .expect(200) + .then(res => res.body); + }, - try { - expectStatusCode(res, 200); - resolve(body); - } catch(ex) { - reject(ex); - } - }); - }); + /** + * @param {string} refreshToken + */ + refresh: async function(refreshToken) { + return request(app) + .post(buildUrl({ fhir: fhirVersion, path: "auth/token" }).pathname) + .redirects(0) + .type("form") + .send({ grant_type: "refresh_token", refresh_token: refreshToken }) + .expect(200) + .then(res => res.body); + } + } } +//////////////////////////////////////////////////////////////////////////////// +// app.use((error, req, res, next) => { +// console.error(error) +// res.status(500).end() +// }) +// ----------------------------------------------------------------------------- /** - * Posts a refresh token to the token endpoint and resolves with the "new" - * token response. - * @param {Object} options - * @param {String} options.baseUrl - * @param {String} options.refreshToken - * @returns {Promise} + * @type {Server|null} */ -function refreshSession(options) { - return requestPromise({ - url : `${options.baseUrl}auth/token`, - method : "POST", - followRedirect: false, - json : true, - form: { - grant_type : "refresh_token", - refresh_token: options.refreshToken - } - }).then(res => res.body); -} - -function authorize(options) { - return getAuthCode(options).then( - code => getAuthToken({ code, baseUrl: options.baseUrl }) - ); -} - -function buildRoutePermutations(suffix = "", fhirVersion) { - suffix = suffix.replace(/^\//, ""); - let out = []; - - if (ENABLE_FHIR_VERSION_3 && (!fhirVersion || fhirVersion == 3)) { - out.push( - `/v/r3/sim/whatever/${suffix}`, - `/v/r3/${suffix}` - ); - } - - if (ENABLE_FHIR_VERSION_2 && (!fhirVersion || fhirVersion == 2)) { - out.push( - `/v/r2/sim/whatever/${suffix}`, - `/v/r2/${suffix}` - ); - } +let server; - return out; +async function closeServer() { + return new Promise((resolve, reject) => { + if (server && server.listening) { + server.close(error => { + if (error) { + reject(error); + } else { + // server.unref() + // server = null; + resolve(); + } + }); + } else { + if (server) server.unref() + resolve(); + } + }); } -function decodeJwtToken(token) { - return JSON.parse( - new Buffer(token.split(".")[1], "base64").toString("utf8") - ); -} -//////////////////////////////////////////////////////////////////////////////// -let server; -before(() => { +before(async () => { + await closeServer() return new Promise((resolve, reject) => { - server = app.listen(config.port, error => { + const _server = app.listen(config.port, error => { if (error) { return reject(error); } - resolve(); + server = _server + resolve() }); }); }); -after(() => { - if (server && server.listening) { - return new Promise(resolve => { - server.close(error => { - if (error) { - console.log("Error shutting down the : ", error); - } - server = null; - resolve(); - }); - }); - } -}); +after(closeServer); +describe("Global Routes", () => { -describe('index', function() { - it('responds with html', function(done) { - request(app) + it('index responds with html', () => { + return request(app) .get('/') .expect('Content-Type', /html/) - .expect(200, done); - }); -}); - -["picker", "login", "authorize"].forEach(endPoint => { - describe(endPoint, function() { - buildRoutePermutations(endPoint).forEach(path => { - describe(`GET ${path}`, function() { - it('responds with html', function(done) { - request(app) - .get(path) - .expect('Content-Type', /html/) - .expect(200, done); - }); - }); - }); + .expect(200); }); -}); -describe('Proxy', function() { - this.timeout(10000); - buildRoutePermutations("fhir/metadata").forEach(path => { - it(path + ' responds with html in browsers', done => { - request(app) - .get(path) - .set('Accept', 'text/html') - .expect('Content-Type', /^text\/html/) - .expect(200, done); + it('/keys hosts the public keys', () => { + return request(app) + .get('/keys') + .expect('Content-Type', /json/) + .expect(200) + .expect(res => { + expect(res.body).to.have.property("keys") + expect(Array.isArray(res.body.keys)).to.be.true + expect(res.body.keys.length).to.be.greaterThan(0) }); - - // TODO: Support XML - // it(path + ' responds with xml if requested', done => { - // request(app) - // .get(path) - // .set('Accept', 'application/xml') - // .expect('Content-Type', /^application\/xml\+fhir/) - // .expect(200, done); - // }); }); - it ("Validates the fhir version", done => { - request(app) - .get("/v/r300/fhir/metadata") - .expect('Content-Type', /json/) - .expect('{"error":"FHIR server r300 not found"}') - .expect(400, done); - }); + describe('RSA Generator', () => { + + it ("can generate random strings", async () => { + await request(app) + .get('/generator/random') + .expect('Content-Type', /text\/plain/) + .expect(200) + .expect(/^[0-9a-fA-F]{64}$/); - it ("If auth token is sent - validates it", done => { - request(app) - .get(`/v/${PREFERRED_FHIR_VERSION}/fhir/metadata`) - .set("authorization", "Bearer whatever") - .expect('Content-Type', /text/) - .expect("JsonWebTokenError: jwt malformed") - .expect(401, done); - }); + await request(app) + .get('/generator/random') + .query({ enc: "hex" }) + .expect('Content-Type', /text\/plain/) + .expect(200) + .expect(/^[0-9a-fA-F]{64}$/); + }); - it ("Can simulate custom token errors"); - it ("Keeps protected data-sets read-only"); - it ("Make urls conditional and if exists, change /id to ?_id="); - it ("Apply patient scope to GET requests"); + it ("random strings length defaults to 32", async () => { + await request(app) + .get('/generator/random') + .query({ len: -5 }) + .expect('Content-Type', /text\/plain/) + .expect(200) + .expect(/^[0-9a-fA-F]{64}$/); + }); - it ("Adjust urls in the fhir response", done => { - request(app) - .get("/v/" + PREFERRED_FHIR_VERSION + "/fhir/Patient") - .expect(res => { - // FIXME: version mismatch... - if (res.text.indexOf(config.fhirServerR3) > -1) { - throw new Error("Not all URLs replaced"); - } - }) - .end(done); - }); + it ("can generate random RSA-256 key pairs", async () => { - buildRoutePermutations("fhir/metadata").forEach(path => { - it(path + ' inject the SMART information in metadata responses', done => { - request(app) - .get(path) - .expect('Content-Type', /\bjson\b/i) + let { privateKey, publicKey } = await request(app) + .get("/generator/rsa") + .expect('content-type', /json/) .expect(200) - .expect(res => { - let uris = Lib.getPath(res.body, "rest.0.security.extension.0.extension"); - - // authorize --------------------------------------------------- - let authorizeCfg = uris.find(o => o.url == "authorize"); - if (!authorizeCfg) { - throw new Error("No 'authorize' endpoint found in the conformance statement"); - } - if (authorizeCfg.valueUri != config.baseUrl + path.replace("fhir/metadata", "auth/authorize")) { - throw new Error("Wrong 'authorize' endpoint found in the conformance statement"); - } + .then(res => res.body) - // token ------------------------------------------------------- - let tokenCfg = uris.find(o => o.url == "token"); - if (!tokenCfg) { - throw new Error("No 'token' endpoint found in the conformance statement"); - } - if (tokenCfg.valueUri != config.baseUrl + path.replace("fhir/metadata", "auth/token")) { - throw new Error("Wrong 'token' endpoint found in the conformance statement"); - } + expect(privateKey, "The generator did not create a private key") + expect(publicKey, "The generator did not create a public key") - // register ---------------------------------------------------- - // TODO: Un-comment when we support DCR - // let registerCfg = uris.find(o => o.url == "register"); - // if (!registerCfg) { - // throw new Error("No 'register' endpoint found in the conformance statement"); - // } - // if (registerCfg.valueUri != config.baseUrl + path.replace("fhir/metadata", "auth/register")) { - // throw new Error("Wrong 'register' endpoint found in the conformance statement"); - // } + await request(app) + .get("/generator/rsa") + .expect('content-type', /json/) + .expect(200) + .then(res => { + expect(res.body.privateKey, "The generator did not create a private key") + expect(res.body.publicKey, "The generator did not create a public key") + expect(res.body.privateKey).to.not.equal(privateKey, "privateKey does not change between requests") + expect(res.body.publicKey).to.not.equal(publicKey, "publicKey does not change between requests") }) - .end(done); }); }); +}); - it ("pull the resource out of the bundle if we converted a /id url into a ?_id= query", done => { - let patientID; - - // We cannot know any IDs but we need to use one for this test, thus - // query all the patients with _count=1 to find the first one and use - // it's ID. - requestPromise({ - uri: `${config.baseUrl}/v/${PREFERRED_FHIR_VERSION}/fhir/Patient`, - json: true, - qs: { - _count: 1 - } - }) - - // Use the first patient ID - .then(res => res.body.entry[0].resource.id) - - // Now do another request with that ID - .then(id => { - patientID = id; - return requestPromise({ - uri: `${config.baseUrl}/v/${PREFERRED_FHIR_VERSION}/fhir/Patient/${id}`, - json: true - }); - }) - - // Did we get a patient with the requested id? - .then(res => { - return res.body.resourceType == "Patient" && res.body.id == patientID ? - Promise.resolve() : - Promise.reject("No patient returned by id") - }) +for(const FHIR_VERSION in TESTED_FHIR_SERVERS) { - // complete - .then(() => done(), done); - }); + const SMART = getSmartApi(FHIR_VERSION); - // it ("Pretty print if called from a browser", () => { - // expect(1).to.equal(2); - // }); + describe(`FHIR server ${FHIR_VERSION}`, () => { + + it('can render the patient picker', () => { + return request(app) + .get(buildUrl({ fhir: FHIR_VERSION, path: "picker" }).pathname) + .expect('Content-Type', /html/) + .expect(200) + }); - if (ENABLE_FHIR_VERSION_3) { - it ("Replies with application/fhir+json for STU3", done => { - request(app) - .get(`/v/${PREFERRED_FHIR_VERSION}/fhir/Patient`) - .expect("content-Type", /^application\/fhir\+json/i) - .end(done); + it('can render the user picker', () => { + return request(app) + .get(buildUrl({ fhir: FHIR_VERSION, path: "login" }).pathname) + .expect('Content-Type', /html/) + .expect(200) }); - } - if (ENABLE_FHIR_VERSION_2) { - it ("Replies with application/json+fhir for DSTU2", done => { - request(app) - .get("/v/r2/fhir/Patient") - .expect("content-Type", /^application\/json\+fhir/i) - .expect(/\n.+/, done); + it('can render the launch approval dialog', () => { + return request(app) + .get(buildUrl({ fhir: FHIR_VERSION, path: "authorize" }).pathname) + .expect('Content-Type', /html/) + .expect(200) }); - } - it ("Replies with formatted JSON for bundles", done => { - request(app).get(`/v/${PREFERRED_FHIR_VERSION}/fhir/Patient`).expect(/\n.+/, done); - }); + describe('Proxy', function() { + this.timeout(10000); + + it('fhir/metadata responds with html in browsers', () => { + return request(app) + .get(buildUrl({ fhir: FHIR_VERSION, path: "fhir/metadata" }).pathname) + .set('Accept', 'text/html') + .expect('Content-Type', /^text\/html/) + .expect(200) + }); - it ("Replies with formatted JSON for single resources", done => { - request(app).get(`/v/${PREFERRED_FHIR_VERSION}/fhir/Observation/smart-5328-height`).expect(/\n.+/, done); - }); + it ("Validates the FHIR version", () => { + return request(app) + .get(buildUrl({ fhir: "r300", path: "fhir/metadata" }).pathname) + .expect('Content-Type', /json/) + .expect('{"error":"FHIR server r300 not found"}') + .expect(400) + }); - it ("Handles pagination", done => { - request(app) - .get(`/v/${PREFERRED_FHIR_VERSION}/fhir/Patient`) - .expect(res => { - if (!Array.isArray(res.body.link)) { - throw new Error("No links found"); - } + it ("If auth token is sent - validates it", () => { + return request(app) + .get(buildUrl({ fhir: FHIR_VERSION, path: "fhir/metadata" }).pathname) + .set("authorization", "Bearer whatever") + .expect('Content-Type', /text/) + .expect("JsonWebTokenError: jwt malformed") + .expect(401) + }); + + it ("Adjust urls in the fhir response", () => { + return request(app) + .get(buildUrl({ fhir: FHIR_VERSION, path: "/fhir/Patient" }).pathname) + .expect(res => { + if (res.text.indexOf(TESTED_FHIR_SERVERS[FHIR_VERSION]) > -1) { + throw new Error("Not all URLs replaced"); + } + }) + }); + + it ("pull the resource out of the bundle if we converted a /id url into a ?_id= query", async () => { + + // We cannot know any IDs but we need to use one for this test, thus + // query all the patients with _count=1 to find the first one and use + // it's ID. + const patientID = await request(app) + .get(buildUrl({ fhir: FHIR_VERSION, path: "/fhir/Patient"}).pathname) + .query({ _count: 1 }) + .expect(200) + .expect("content-type", /json/) + .then(res => res.body.entry[0].resource.id); + + // Now do another request with that ID + const resource = await request(app) + .get(buildUrl({ fhir: FHIR_VERSION, path: "/fhir/Patient/" + patientID }).pathname) + .expect(200) + .expect("content-type", /json/) + .then(res => res.body); + + // Did we get a patient with the requested id? + expect(resource.resourceType).to.equal("Patient") + expect(resource.id).to.equal(patientID) + }); - let next = res.body.link.find(l => l.relation == "next") - if (!next) { - throw new Error("No next link found"); - } - // console.log(next) - return request(app).get(next.url).expect(res2 => { - if (!Array.isArray(res.body.link)) { - throw new Error("No links found on second page"); - } + it ("Handles pagination", () => { + return request(app) + .get(buildUrl({ fhir: FHIR_VERSION, path: "/fhir/Patient" }).pathname) + .expect(res => { + if (!Array.isArray(res.body.link)) { + throw new Error("No links found"); + } + + let next = res.body.link.find(l => l.relation == "next") + if (!next) { + throw new Error("No next link found"); + } + // console.log(next) + return request(app).get(next.url).expect(res2 => { + if (!Array.isArray(res.body.link)) { + throw new Error("No links found on second page"); + } + + let self = res.body.link.find(l => l.relation == "self") + if (!self) { + throw new Error("No self link found on second page"); + } + if (self.url !== next.url) { + throw new Error("Links mismatch"); + } + + let next2 = res.body.link.find(l => l.relation == "next") + if (!next2) { + throw new Error("No next link found on second page"); + } + // console.log(next2) + }) + }) + }); - let self = res.body.link.find(l => l.relation == "self") - if (!self) { - throw new Error("No self link found on second page"); - } - if (self.url !== next.url) { - throw new Error("Links mismatch"); - } + it ("Replies with formatted JSON for bundles", () => { + return request(app) + .get(buildUrl({ fhir: FHIR_VERSION, path: "/fhir/Patient" }).pathname) + .expect(/\n.+/); + }); - let next2 = res.body.link.find(l => l.relation == "next") - if (!next2) { - throw new Error("No next link found on second page"); - } + it ("Replies with formatted JSON for single resources", () => { + return request(app) + .get(buildUrl({ fhir: FHIR_VERSION, path: "/fhir/X" }).pathname) // Should return OperationOutcome + .expect(/\n.+/); + }); - console.log(next2) - }) - }) - .end(done); - }); -}); + if (FHIR_VERSION === "r2") { + it (`Replies with application/json+fhir for ${FHIR_VERSION}`, () => { + return request(app) + .get(buildUrl({ fhir: FHIR_VERSION, path: "/fhir/Patient" }).pathname) + .expect("content-Type", /^application\/json\+fhir/i) + .expect(/\n.+/); + }); + } else { + it (`Replies with application/fhir+json for ${FHIR_VERSION}`, () => { + return request(app) + .get(buildUrl({ fhir: FHIR_VERSION, path: "/fhir/Patient" }).pathname) + .expect("content-Type", /^application\/fhir\+json/i) + .expect(/\n.+/); + }); + } -describe('Auth', function() { - describe('authorize', function() { - - // auth/authorize Checks for required params - buildRoutePermutations("auth/authorize").forEach(path => { - let query = []; - [ - "response_type", - "client_id", - "redirect_uri", - "scope", - "state", - "aud" - ].forEach(name => { - it(`${path} requires "${name}" param`, done => { - request(app) - .get(path + "?" + query.join("&")) - .expect(400) - .expect(`Missing ${name} parameter`) - .end(() => { - query.push(name + "=" + (name == "redirect_uri" ? "http%3A%2F%2Fx" : "x")); - done(); - }); + it('Injects the SMART information in metadata responses', () => { + return request(app) + .get(buildUrl({ fhir: FHIR_VERSION, path: "fhir/metadata" }).pathname) + .expect('Content-Type', /\bjson\b/i) + .expect(200) + .expect(res => { + let uris = Lib.getPath(res.body, "rest.0.security.extension.0.extension"); + + // authorize --------------------------------------------------- + let authorizeCfg = uris.find(o => o.url == "authorize"); + if (!authorizeCfg) { + throw new Error("No 'authorize' endpoint found in the conformance statement"); + } + if (authorizeCfg.valueUri != buildUrl({ fhir: FHIR_VERSION, path: "auth/authorize" }).href) { + throw new Error("Wrong 'authorize' endpoint found in the conformance statement"); + } + + // token ------------------------------------------------------- + let tokenCfg = uris.find(o => o.url == "token"); + if (!tokenCfg) { + throw new Error("No 'token' endpoint found in the conformance statement"); + } + if (tokenCfg.valueUri != buildUrl({ fhir: FHIR_VERSION, path: "auth/token" }).href) { + throw new Error("Wrong 'token' endpoint found in the conformance statement"); + } + + // register ---------------------------------------------------- + // TODO: Un-comment when we support DCR + // let registerCfg = uris.find(o => o.url == "register"); + // if (!registerCfg) { + // throw new Error("No 'register' endpoint found in the conformance statement"); + // } + // if (registerCfg.valueUri != buildUrl({ fhir: FHIR_VERSION, path: "auth/register" }).href) { + // throw new Error("Wrong 'register' endpoint found in the conformance statement"); + // } }); }); - // it(`${path} with missing "client_id" param`, done => { - // request(app) - // .get(path + "?response_type=code&client_id=&redirect_uri=http%3A%2F%2Fx&aud=x&state=abc") - // .end(function(error, res) { - // console.log(res.body, res.headers) - // done(); - // }); - // }); - }); + it("rejects invalid authorization tokens", async () => { + await request(app) + .get(buildUrl({ fhir: FHIR_VERSION, path: "/fhir/Patient" }).pathname) + .set("authorization", "bearer invalid-token") + .expect(401, /JsonWebTokenError/); + }) + + it ("Can simulate custom token errors", async () => { + const token = jwt.sign({ sim_error: "test error" }, config.jwtSecret) + await request(app) + .get(buildUrl({ fhir: FHIR_VERSION, path: "/fhir/Patient" }).pathname) + .set("authorization", "bearer " + token) + .expect(401, "test error"); + }); - // auth/authorize validates the redirect_uri parameter - buildRoutePermutations("auth/authorize").forEach(path => { - it(`${path} - validates the redirect_uri parameter`, done => { - request(app) - .get(path + "?response_type=x&client_id=x&redirect_uri=x&scope=x&state=x&aud=x") - .expect(/^Invalid redirect_uri parameter/) - .expect(400) - .end(done); + it ("Keeps protected data-sets read-only"); + it ("Make urls conditional and if exists, change /id to ?_id="); + it ("Apply patient scope to GET requests"); + + describe('Fhir Requests', () => { + it ("TODO..."); }); }); - // can simulate invalid redirect_uri error - { - let sim = new Buffer('{"auth_error":"auth_invalid_redirect_uri"}').toString('base64'); - let paths = buildRoutePermutations("auth/authorize?launch=" + sim + "&response_type=x&client_id=x&redirect_uri=http%3A%2F%2Fx&scope=x&state=x&aud=x"); - paths.push(`/v/${PREFERRED_FHIR_VERSION}/sim/${sim}/auth/authorize?response_type=x&client_id=x&redirect_uri=http%3A%2F%2Fx&scope=x&state=x&aud=x"`); - paths.forEach(path => { - it (path.split("?")[0] + " can simulate invalid redirect_uri error", done => { - request(app) - .get(path) - .expect(302) - .expect(function(res) { - const loc = res.get("location"); - if (!loc || loc.indexOf("error=sim_invalid_redirect_uri") == -1) { - throw new Error(`No error passed to the redirect ${loc}`) - } - }) - .expect(function(res) { - const loc = res.get("location"); - if (!loc || loc.indexOf("state=x") == -1) { - throw new Error(`No state passed to the redirect ${loc}`) - } - }) - .end(done); + describe('Auth', () => { + describe('authorize', () => { + + const url = buildUrl({ fhir: FHIR_VERSION, path: "/auth/authorize" }); + + const fullQuery = { + response_type: "code", + client_id: "x", + redirect_uri: "http://x", + scope: "x", + state: "x", + aud: "x" + }; + + const authErrors = { + "auth_invalid_client_id" : "sim_invalid_client_id", + "auth_invalid_redirect_uri": "sim_invalid_redirect_uri", + "auth_invalid_scope" : "sim_invalid_scope" + } + + const requiredAuthorizeParams = [ + "redirect_uri", + "response_type", + // "client_id", + // "scope", + // "state" + ] + + // checks for required params + // ------------------------------------------------------------- + for (const name of requiredAuthorizeParams) { + + // If redirect_uri is missing we reply with 400 (tested below) + // If anything else is missing we redirect and pass an error param + if (name !== "redirect_uri") { + it(`requires "${name}" param`, () => { + const query = { ...fullQuery }; + delete query[name] + + return request(app) + .get(url.pathname) + .query(query) + .expect(302) + .expect(function(res) { + const loc = res.get("location"); + if (!loc || loc.indexOf(`error=missing_parameter`) == -1) { + throw new Error(`No error passed to the redirect ${loc}`) + } + }) + .expect(function(res) { + const loc = res.get("location"); + if (!loc || loc.indexOf("state=x") == -1) { + throw new Error(`No state passed to the redirect ${loc}`) + } + }); + }); + } + } + + // validates the redirect_uri parameter + // ------------------------------------------------------------- + it("validates the redirect_uri parameter", () => { + return request(app) + .get(url.pathname) + .query({ ...fullQuery, redirect_uri: "x" }) + .expect(/^Invalid redirect_uri parameter/) + .expect(400); }); - }); - } - // can simulate invalid scope error - { - let sim = new Buffer('{"auth_error":"auth_invalid_scope"}').toString('base64'); - let paths = buildRoutePermutations("auth/authorize?launch=" + sim + "&response_type=x&client_id=x&redirect_uri=http%3A%2F%2Fx&scope=x&state=x&aud=x"); - paths.push(`/v/${PREFERRED_FHIR_VERSION}/sim/${sim}/auth/authorize?response_type=x&client_id=x&redirect_uri=http%3A%2F%2Fx&scope=x&state=x&aud=x"`); - paths.forEach(path => { - it (path.split("?")[0] + " can simulate invalid scope error", done => { - request(app) - .get(path) + // simulated errors + // ------------------------------------------------------------- + Object.keys(authErrors).forEach(errorName => { + it (`can simulate "${errorName}" error via sim`, () => { + + const url = buildUrl({ + fhir: FHIR_VERSION, + path: "/auth/authorize", + sim: { + auth_error: errorName + } + }); + + return request(app) + .get(url.pathname) + .query(fullQuery) + .expect(302) + .expect(function(res) { + const loc = res.get("location"); + if (!loc || loc.indexOf(`error=${authErrors[errorName]}`) == -1) { + throw new Error(`No error passed to the redirect ${loc}`) + } + if (!loc || loc.indexOf("state=x") == -1) { + throw new Error(`No state passed to the redirect ${loc}`) + } + }); + }); + + it (`can simulate "${errorName}" error via launch param`, () => { + + const url = buildUrl({ + fhir: FHIR_VERSION, + path: "/auth/authorize", + query: { + ...fullQuery, + launch: { + auth_error: errorName + } + } + }); + + return request(app) + .get(url.pathname) + .query(url.searchParams.toString()) + .expect(302) + .expect(function(res) { + const loc = res.get("location"); + if (!loc || loc.indexOf(`error=${authErrors[errorName]}`) == -1) { + throw new Error(`No error passed to the redirect ${loc}`) + } + if (!loc || loc.indexOf("state=x") == -1) { + throw new Error(`No state passed to the redirect ${loc}`) + } + }); + }); + }) + + // rejects invalid audience value + // ------------------------------------------------------------- + it ("rejects invalid audience value", () => { + return request(app) + .get(url.pathname) + .query({ ...fullQuery, aud: "whatever" }) .expect(302) .expect(function(res) { const loc = res.get("location"); @@ -639,699 +630,452 @@ describe('Auth', function() { throw new Error(`No redirect`) } let url = Url.parse(loc, true); - if (url.query.error != "sim_invalid_scope") { + if (url.query.error != "bad_audience") { throw new Error(`Wrong redirect ${loc}`) } - }) - .end(done); + }); }); - }); - } - // can simulate invalid client_id error - { - let sim = new Buffer('{"auth_error":"auth_invalid_client_id"}').toString('base64'); - let paths = buildRoutePermutations("auth/authorize?launch=" + sim + "&response_type=x&client_id=x&redirect_uri=http%3A%2F%2Fx&scope=x&state=x&aud=x"); - paths.push(`/v/${PREFERRED_FHIR_VERSION}/sim/${sim}/auth/authorize?response_type=x&client_id=x&redirect_uri=http%3A%2F%2Fx&scope=x&state=x&aud=x"`); - paths.forEach(path => { - it (path.split("?")[0] + " can simulate invalid client_id error", done => { - request(app) - .get(path) + // can show encounter picker + // ------------------------------------------------------------- + it ("can show encounter picker", () => { + const url = buildUrl({ + fhir: FHIR_VERSION, + path: "/auth/authorize", + query: { + ...fullQuery, + scope: "patient/*.read launch", + patient: "whatever", + aud: config.baseUrl + `/v/${FHIR_VERSION}/fhir`, + launch: { + launch_ehr: 1, + select_encounter: 1 + } + } + }); + + return request(app) + .get(url.pathname) + .query(url.searchParams.toString()) .expect(302) - .expect(function(res) { + .expect(res => { const loc = res.get("location"); - if (!loc) { - throw new Error(`No redirect`) + if (!loc || loc.indexOf(`/v/${FHIR_VERSION}/encounter?`) !== 0) { + throw new Error(`Wrong redirect ${loc}.`) } - let url = Url.parse(loc, true); - if (url.query.error != "sim_invalid_client_id") { - throw new Error(`Wrong redirect ${loc}`) - } - }) - .end(done); + }); }); - }); - } - // rejects invalid audience value - buildRoutePermutations( - "auth/authorize?response_type=x&client_id=x&redirect_uri=http%3A%2F%2Fx&scope=x&state=x&launch=0&aud=whatever" + - encodeURIComponent(config.fhirServerR2), - 2 - ).forEach(path => { - it (path.split("?")[0] + " rejects invalid audience value", done => { - request(app) - .get(path) - .expect(302) - .expect(function(res) { - const loc = res.get("location"); - if (!loc) { - throw new Error(`No redirect`) - } - let url = Url.parse(loc, true); - if (url.query.error != "bad_audience") { - throw new Error(`Wrong redirect ${loc}`) - } - }) - .end(done); - }); - }); + it("generates a code from profile", async () => { + return SMART.getAuthCode({ + patient: "abc", + scope: "profile openid launch", + encounter: "bcd", + login_success: true, + auth_success: true, + launch: { launch_pt: 1 } + }); + }); - // can show encounter picker - buildRoutePermutations( - "auth/authorize" + - "?client_id=x" + - "&response_type=code" + - "&scope=patient%2F*.read%20launch" + - "&redirect_uri=" + encodeURIComponent("https://sb-apps.smarthealthit.org/apps/growth-chart/") + - "&state=x" + - "&login_success=1" + - "&patient=fb48de1b-e485-458a-ac0f-c5a54c26b58d" - ).forEach(path => { - let aud = encodeURIComponent(config.baseUrl + path.split("auth/authorize")[0] + "fhir"); - let launch = new Buffer(JSON.stringify({ - launch_ehr : 1, - select_encounter: 1 - })).toString("base64"); - let fullPath = path + "&aud=" + aud + "&launch=" + launch; - - it (path.split("?")[0] + " can show encounter picker", done => { - request(app) - .get(fullPath) - .expect(302) - .expect(function(res) { - const loc = res.get("location"); - if (!loc || loc.indexOf(fullPath.replace(/\/auth\/authorize\?.*/, "/encounter?")) !== 0) { - throw new Error(`Wrong redirect ${loc}`) - } - }) - .end(done); - }); - }); - }); + it("generates a code from fhirUser", async () => { + return SMART.getAuthCode({ + patient: "abc", + scope: "fhirUser openid launch", + encounter: "bcd", + login_success: true, + auth_success: true, + launch: { launch_pt: 1 } + }); + }); + }) - describe('Confidential Clients', function() { - buildRoutePermutations("auth/token").forEach(path => { - let token = jwt.sign("whatever", config.jwtSecret); + describe('token', function() { - it (`${path} - can simulate auth_invalid_client_secret`, done => { - request(app) - .post(path) - .type('form') - .set("Authorization", "Basic bXktYXBwOm15LWFwcC1zZWNyZXQtMTIz") - .send({ - grant_type: "refresh_token", - auth_error: "auth_invalid_client_secret", - refresh_token: token - }) - .expect("Simulated invalid client secret error") - .expect(401) - .end(done); - }); + it("can simulate sim_expired_refresh_token", async () => { - it (`${path} - rejects empty auth header`, done => { - request(app) - .post(path) - .type('form') - .set("Authorization", "Basic") - .send({ - grant_type: "refresh_token", - refresh_token: token - }) - .expect("The authorization header 'Basic' cannot be empty") - .expect(401) - .end(done); - }); + const { code } = await SMART.getAuthCode({ + scope: "offline_access", + launch: { + launch_pt : 1, + skip_login: 1, + skip_auth : 1, + patient : "abc", + encounter : "bcd", + auth_error: "token_expired_refresh_token" + } + }) + + const tokenResponse = await SMART.getAccessToken(code) + + /** + * @type {object} + */ + const refreshToken = jwt.decode(tokenResponse.refresh_token) + + expect(refreshToken.auth_error).to.equal("token_expired_refresh_token") + + return request(app) + .post(buildUrl({ fhir: FHIR_VERSION, path: "auth/token" }).pathname) + .type("form") + .send({ grant_type: "refresh_token", refresh_token: tokenResponse.refresh_token }) + .expect('Content-Type', /text/) + .expect(401, config.errors.sim_expired_refresh_token); + }); - it (`${path} - rejects invalid auth header`, done => { - request(app) - .post(path) - .type('form') - .set("Authorization", "Basic bXktYXB") - .send({ - grant_type: "refresh_token", - refresh_token: token - }) - .expect(/^Bad authorization header/) - .expect(401) - .end(done); - }); + it("provides id_token", async () => { + const { code } = await SMART.getAuthCode({ + patient: "abc", + scope: "fhirUser openid launch", + encounter: "bcd", + login_success: true, + auth_success: true, + launch: { + launch_pt: 1 + } + }); - it (`${path} - can simulate sim_invalid_token`, done => { - let code = jwt.sign({ auth_error:"token_invalid_token" }, config.jwtSecret); - request(app) - .post(path) - .type('form') - .set("Authorization", "Basic bXktYXBwOm15LWFwcC1zZWNyZXQtMTIz") - .send({ - grant_type: "authorization_code", - code - }) - .expect("Simulated invalid token error") - .expect(401) - .end(done); - }); - }); - }); + const tokenResponse = await SMART.getAccessToken(code) - describe('OIDC signature algorithm works correctly', function() { - buildRoutePermutations().forEach(path => { - let code, idToken, key, keysLocation; - let aud = config.baseUrl + path + "fhir"; - let launch = encodeSim({ - launch_pt : 1, - skip_login: 1, - skip_auth : 1, - patient : "abc", - encounter : "bcd" - }); - it(`${path}auth/authorize - generates a code from profile`, done => { - request(app) - .get(`${path}auth/authorize?response_type=code&launch=${launch}` + - `&patient=abc&client_id=x&redirect_uri=${encodeURIComponent("http://x.y")}` + - `&scope=profile%20openid%20launch&state=x&aud=${encodeURIComponent(aud)}`) - .expect(302) - .expect(function(res) { - if (!res.get("location")) { - throw new Error(`auth/authorize did not redirect to the redirect_uri`) - } - let url = Url.parse(res.get("location"), true); - code = url.query.code; - if (!code) { - // console.log(res.headers) - throw new Error(`auth/authorize did not redirect to the redirect_uri with code parameter`) - } - // console.info("code: ", JSON.parse(Buffer.from(code.split(".")[1], 'base64').toString())); - }) - .end(done); + expect(tokenResponse).to.have.property("id_token") + }); }); - it(`${path}auth/authorize - generates a code from fhirUser`, done => { - request(app) - .get(`${path}auth/authorize?response_type=code&launch=${launch}` + - `&patient=abc&client_id=x&redirect_uri=${encodeURIComponent("http://x.y")}` + - `&scope=fhirUser%20openid%20launch&state=x&aud=${encodeURIComponent(aud)}`) - .expect(302) - .expect(function(res) { - if (!res.get("location")) { - throw new Error(`auth/authorize did not redirect to the redirect_uri`) - } - let url = Url.parse(res.get("location"), true); - code = url.query.code; - if (!code) { - // console.log(res.headers) - throw new Error(`auth/authorize did not redirect to the redirect_uri with code parameter`) - } - // console.info("code: ", JSON.parse(Buffer.from(code.split(".")[1], 'base64').toString())); - }) - .end(done); - }); + it("the access token can be verified using the published public key", async () => { - it(`${path}auth/token - provides id_token`, done => { - getAuthToken({ code, baseUrl: config.baseUrl + path }).then(body => { - if (!body || !body.id_token) { - return done(new Error(`auth/token did not return id_token`)); - } - idToken = body.id_token; - done(); - }, done); - }); - - it(`the access token can be verified`, done => { - lookupOidcKeys((error, keys) => { - if (error) { - return done(error); - } - key = keys[0] - try { - jwt.verify(idToken, jwkToPem(key), { algorithms: ["RS256"] }) - done() - } catch (ex) { - done(ex) - } + // Start by getting an id_token + const { code } = await SMART.getAuthCode({ + patient: "abc", + scope: "fhirUser openid launch", + encounter: "bcd", + login_success: true, + auth_success: true, + launch: { launch_pt: 1 } }); - }); - }); - }); - describe('token', function() { - buildRoutePermutations().forEach(path => { - it(`${path}auth/token can simulate sim_expired_refresh_token`, done => { - authorize({ - scope : "offline_access", - baseUrl: config.baseUrl + path, - launch : { - launch_pt : 1, - skip_login: 1, - skip_auth : 1, - patient : "abc", - encounter : "bcd", - auth_error: "token_expired_refresh_token" - } - }) - .then(tokenResponse => { - return refreshSession({ - baseUrl: config.baseUrl + path, - refreshToken: tokenResponse.refresh_token - }); - }) - .then(result => { - if (result != config.errors.sim_expired_refresh_token) { - return done(new Error("No sim_expired_refresh_token error returned")); - } - done(); - }, done); - }); - }); - }); -}); - -describe('Introspection', () => { - buildRoutePermutations().forEach(path => { - it(`gets correct FHIR conformance for ${path}`, () => { - return request(app) - .get(`${path}fhir/metadata`) - .expect(200) - .then(response => { - const oauthUris = response.body.rest[0].security.extension.find(e => /StructureDefinition\/oauth-uris$/.test(e.url)); - expect(oauthUris); - const introspection = oauthUris.extension.find(e => e.url === "introspect"); - expect(introspection); - expect(new URL(introspection.valueUri).pathname).equal(`${path}auth/introspect`); - }) - }) + const { id_token } = await SMART.getAccessToken(code) - it(`can introspect an access token ${path}`, (done) => { - authorize({ - scope : "offline_access", - baseUrl: config.baseUrl + path, - launch : { - launch_pt : 1, - skip_login: 1, - skip_auth : 1, - patient : "abc", - encounter : "bcd", - } - }).then(token => { - request(app) - .post(`${path}auth/introspect`) - .set('Authorization', `Bearer ${token.access_token}`) - .set('Accept', 'application/json') - .set('Content-Type', 'application/x-www-form-urlencoded') - .send({ token: token.access_token }) + // Then get the jwks_uri from .well-known/openid-configuration + const jwksUrl = await request(app) + .get(buildUrl({ fhir: FHIR_VERSION, path: ".well-known/openid-configuration" }).pathname) + .expect("content-type", /json/) .expect(200) - .expect(res => { - if(res.body?.active !== true) throw new Error("Token is not active."); - }) - .end(done); - - }).catch(done) - }) + .then(res => new URL(res.body.jwks_uri)); - it(`can introspect a refresh token ${path}`, (done) => { - authorize({ - scope : "offline_access", - baseUrl: config.baseUrl + path, - launch : { - launch_pt : 1, - skip_login: 1, - skip_auth : 1, - patient : "abc", - encounter : "bcd", - } - }).then(token => { - request(app) - .post(`${path}auth/introspect`) - .set('Authorization', `Bearer ${token.access_token}`) - .set('Accept', 'application/json') - .set('Content-Type', 'application/x-www-form-urlencoded') - .send({ token: token.refresh_token }) + // Then fetch the keys + const keys = await request(app) + .get(jwksUrl.pathname) + .expect("content-type", /json/) .expect(200) - .expect(res => { - if(res.body?.active !== true) throw new Error("Token is not active."); - }) - .end(done); + .then(res => res.body.keys); - }).catch(done) - }) + jwt.verify(id_token, jwkToPem(keys[0]), { algorithms: ["RS256"] }); + }); - it(`gets active: false for authorized request with invalid token at ${path}`, (done) => { - const introspectionToken = 'invalid'; - - authorize({ - scope : "offline_access", - baseUrl: config.baseUrl + path, - launch : { - launch_pt : 1, - skip_login: 1, - skip_auth : 1, - patient : "abc", - encounter : "bcd", - } - }).then(auth => { - request(app) - .post(`${path}auth/introspect`) - .set('Authorization', `Bearer ${auth.access_token}`) - .set('Accept', 'application/json') - .set('Content-Type', 'application/x-www-form-urlencoded') - .send({ token: introspectionToken }) - .expect(200) - .expect({ active: false }) - .end(done); - }) - }) + describe('Confidential Clients', () => { - it(`gets active: false for authorized request with an expired introspection token at ${path}`, (done) => { + let token = jwt.sign("whatever", config.jwtSecret); + + it ("can simulate auth_invalid_client_secret", () => { + return request(app) + .post(buildUrl({ fhir: FHIR_VERSION, path: "/auth/token" }).pathname) + .type('form') + .set("Authorization", "Basic bXktYXBwOm15LWFwcC1zZWNyZXQtMTIz") + .send({ + grant_type: "refresh_token", + auth_error: "auth_invalid_client_secret", + refresh_token: token + }) + .expect("Simulated invalid client secret error") + .expect(401); + }); - const expiredTokenPayload = { - context: { - need_patient_banner: true, - smart_style_url : config.baseUrl + "/smart-style.json", - }, - client_id: "mocked", - scope : "Patient/*.read", - exp: Math.floor(Date.now() / 1000) - (60 * 60) // token expired one hour ago - } + it ("rejects empty auth header", () => { + return request(app) + .post(buildUrl({ fhir: FHIR_VERSION, path: "/auth/token" }).pathname) + .type('form') + .set("Authorization", "Basic") + .send({ + grant_type: "refresh_token", + refresh_token: token + }) + .expect("The authorization header 'Basic' cannot be empty") + .expect(401); + }); - const expiredToken = jwt.sign(expiredTokenPayload, config.jwtSecret) - - authorize({ - scope : "offline_access", - baseUrl: config.baseUrl + path, - launch : { - launch_pt : 1, - skip_login: 1, - skip_auth : 1, - patient : "abc", - encounter : "bcd", - } - }).then(auth => { - request(app) - .post(`${path}auth/introspect`) - .set('Authorization', `Bearer ${auth.access_token}`) - .set('Accept', 'application/json') - .set('Content-Type', 'application/x-www-form-urlencoded') - .send({ token: expiredToken }) - .expect(200) - .expect({ active: false }) - .end(done); + it ("rejects invalid auth header", () => { + return request(app) + .post(buildUrl({ fhir: FHIR_VERSION, path: "/auth/token" }).pathname) + .type('form') + .set("Authorization", "Basic bXktYXB") + .send({ + grant_type: "refresh_token", + refresh_token: token + }) + .expect(/^Bad authorization header/) + .expect(401); + }); + + it ("can simulate sim_invalid_token", () => { + return request(app) + .post(buildUrl({ fhir: FHIR_VERSION, path: "/auth/token" }).pathname) + .type('form') + .set("Authorization", "Basic bXktYXBwOm15LWFwcC1zZWNyZXQtMTIz") + .send({ + grant_type: "authorization_code", + code: jwt.sign({ auth_error:"token_invalid_token" }, config.jwtSecret) + }) + .expect("Simulated invalid token error") + .expect(401); + }); }) - }) - }) -}) - -describe('Generator', () => { - describe('RSA Generator', function() { - this.timeout(20000); - it ("can generate random strings", async () => { - let res = await requestPromise({ - uri: `${config.baseUrl}/generator/random`, - json: true, - qs: { - enc: "hex" - } - }); - expect(res.body).to.match(/^[0-9a-fA-F]*$/); - - // res = await requestPromise({ - // uri: `${config.baseUrl}/generator/random`, - // // json: true, - // // qs: { - // // enc: "base64", - // // len: 10 - // // } - // }); - // console.log(decodeURI(res.body)) - // expect(res.body.length).to.equal(10); }); - it ("can generate random RSA-256 key pairs", done => { - let privateKey, publicKey; + describe('Introspection', () => { + it("includes the introspect endpoint in the CapabilityStatement", async () => { + await request(app) + .get(buildUrl({ fhir: FHIR_VERSION, path: "/fhir/metadata" }).pathname) + .expect(200) + .then(response => { + const oauthUris = response.body.rest[0].security.extension.find(e => /StructureDefinition\/oauth-uris$/.test(e.url)); + expect(oauthUris); + const introspection = oauthUris.extension.find(e => e.url === "introspect"); + expect(introspection); + expect(new URL(introspection.valueUri).pathname).equal(buildUrl({ fhir: FHIR_VERSION, path: "/auth/introspect" }).pathname); + }) + }) - function ketKeyPair() { - return requestPromise({ - uri: `${config.baseUrl}/generator/rsa`, - json: true, - qs: { - enc: "base64" + it("can introspect an access token", async () => { + const { code } = await SMART.getAuthCode({ + scope: "offline_access", + launch: { + launch_pt : 1, + skip_login: 1, + skip_auth : 1, + patient : "abc", + encounter : "bcd", } + }); + + const { access_token } = await SMART.getAccessToken(code); + + await request(app) + .post(buildUrl({ fhir: FHIR_VERSION, path: "auth/introspect" }).pathname) + .set('Authorization', `Bearer ${access_token}`) + .set('Accept', 'application/json') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send({ token: access_token }) + .expect(200) + .expect(res => { + if(res.body.active !== true) throw new Error("Token is not active."); }) - .then(res => expectStatusCode(res, 200)) - .then(res => expectResponseHeader(res, 'Content-Type', /\bjson\b/i)) - .then(res => { - let { privateKey, publicKey } = res.body; - if (!privateKey) { - return Promise.reject( - new Error("The generator did not create a private key") - ); - } - if (!publicKey) { - return Promise.reject( - Error("The generator did not create a public key") - ); + }) + + it("can introspect an access token", async () => { + const { code } = await SMART.getAuthCode({ + scope: "offline_access", + launch: { + launch_pt : 1, + skip_login: 1, + skip_auth : 1, + patient : "abc", + encounter : "bcd", } - return { privateKey, publicKey }; }); - } - - ketKeyPair() - .then(keys => { - privateKey = keys.privateKey; - publicKey = keys.publicKey; - return ketKeyPair(); - }) - .then(keys => { - if (keys.privateKey == privateKey) { - throw new Error("privateKey does not change between requests"); - } - if (keys.publicKey == publicKey) { - throw new Error("publicKey does not change between requests"); - } - done(); + + const { access_token, refresh_token } = await SMART.getAccessToken(code); + + await request(app) + .post(buildUrl({ fhir: FHIR_VERSION, path: "auth/introspect" }).pathname) + .set('Authorization', `Bearer ${access_token}`) + .set('Accept', 'application/json') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send({ token: refresh_token }) + .expect(200) + .expect(res => { + if(res.body.active !== true) throw new Error("Token is not active."); + }) }) - .catch(done); - }); - }); -}); -describe('Backend Services', () => { - - - describe('Client Registration', () => { - - it ("requires form-urlencoded POST", done => { - requestPromise({ - method: "POST", - uri: `${config.baseUrl}/v/${PREFERRED_FHIR_VERSION}/auth/register` - }) - .then(res => expectStatusCode(res, 401)) - .then(res => { - if (res.body != "Invalid request content-type header (must be 'application/x-www-form-urlencoded')") { - throw new Error("Did not return the proper error message"); - } - return res; + it("gets active: false for authorized request with invalid token", async () => { + const { code } = await SMART.getAuthCode({ + scope: "offline_access", + launch: { + launch_pt : 1, + skip_login: 1, + skip_auth : 1, + patient : "abc", + encounter : "bcd", + } + }); + + const { access_token, refresh_token } = await SMART.getAccessToken(code); + + await request(app) + .post(buildUrl({ fhir: FHIR_VERSION, path: "auth/introspect" }).pathname) + .set('Authorization', `Bearer ${access_token}`) + .set('Accept', 'application/json') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send({ token: "invalid token" }) + .expect(200) + .expect({ active: false }) }) - .then(res => done(), done) - }); - it ("requires 'iss' parameter", done => { - requestPromise({ - method: "POST", - uri : `${config.baseUrl}/v/${PREFERRED_FHIR_VERSION}/auth/register`, - form : {} - }) - .then(res => expectStatusCode(res, 400)) - .then(res => { - if (res.body != "Missing iss parameter") { - throw new Error("Did not return the proper error message"); - } - return res; - }) - .then(res => done(), done) - }); + it("gets active: false for authorized request with expired token", async () => { - it ("requires 'pub_key' parameter", done => { - requestPromise({ - method: "POST", - uri : `${config.baseUrl}/v/${PREFERRED_FHIR_VERSION}/auth/register`, - form : { - iss: "whatever" + const expiredTokenPayload = { + client_id: "mocked", + scope: "Patient/*.read", + exp: Math.floor(Date.now() / 1000) - (60 * 60) // token expired one hour ago } - }) - .then(res => expectStatusCode(res, 400)) - .then(res => { - if (res.body != "Missing pub_key parameter") { - throw new Error("Did not return the proper error message"); - } - return res; - }) - .then(res => done(), done) - }); - - it ("validates the 'dur' parameter", done => { - Promise.all( - [ - "x", - Infinity, - -Infinity, - -2 - ].map(dur => { - return requestPromise({ - method: "POST", - uri : `${config.baseUrl}/v/${PREFERRED_FHIR_VERSION}/auth/register`, - form : { - iss: "whatever", - pub_key: "abc", - dur - } - }) - .then(res => expectStatusCode(res, 400)) - .then(res => { - if (res.body != "Invalid dur parameter") { - throw new Error("Did not return the proper error message"); - } - return res; - }); - }) - ) - .then(res => done(), done) - }); + + const expiredToken = jwt.sign(expiredTokenPayload, config.jwtSecret) - it ("basic usage", done => { - requestPromise({ - method: "POST", - uri : `${config.baseUrl}/v/${PREFERRED_FHIR_VERSION}/auth/register`, - form : { - iss : "whatever", - pub_key: "something" - } - }) - .then(res => expectStatusCode(res, 200)) - .then(res => expectResponseHeader(res, "content-type", /\btext\/plain\b/i)) - .then(res => { - if (res.body.split(".").length != 3) { - throw new Error("Did not return proper client id"); - } - return res; + const { code } = await SMART.getAuthCode({ + scope: "offline_access", + launch: { + launch_pt : 1, + skip_login: 1, + skip_auth : 1, + patient : "abc", + encounter : "bcd", + } + }); + + const { access_token } = await SMART.getAccessToken(code); + + await request(app) + .post(buildUrl({ fhir: FHIR_VERSION, path: "auth/introspect" }).pathname) + .set('Authorization', `Bearer ${access_token}`) + .set('Accept', 'application/json') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send({ token: expiredToken }) + .expect(200) + .expect({ active: false }) }) - .then(res => done(), done) - }); + }) - it ("accepts custom duration", done => { - requestPromise({ - method: "POST", - uri : `${config.baseUrl}/v/${PREFERRED_FHIR_VERSION}/auth/register`, - form : { - iss : "whatever", - pub_key: "something", - dur : 23 - } - }) - .then(res => expectStatusCode(res, 200)) - .then(res => expectResponseHeader(res, "content-type", /\btext\/plain\b/i)) - .then(res => { - if (res.body.split(".").length != 3) { - throw new Error("Did not return proper client id"); - } - let token = decodeJwtToken(res.body); - let exp = token.accessTokensExpireIn; - if (exp != 23) { - throw new Error( - `Expected "accessTokensExpireIn" property to equal 23 but found ${exp}` - ) - } - }) - .then(res => done(), done) - }); + describe('Backend Services', () => { - it ("accepts custom simulated errors", done => { - const err = "test error" - requestPromise({ - method: "POST", - uri : `${config.baseUrl}/v/${PREFERRED_FHIR_VERSION}/auth/register`, - form : { - iss : "whatever", - pub_key: "something", - auth_error: err - } - }) - .then(res => expectStatusCode(res, 200)) - .then(res => expectResponseHeader(res, "content-type", /\btext\/plain\b/i)) - .then(res => { - if (res.body.split(".").length != 3) { - throw new Error("Did not return proper client id"); - } - let token = decodeJwtToken(res.body); - let x = token.auth_error; - if (x !== err) { - throw new Error( - `Expected "auth_error" property to equal "${err}" but found "${x}"` + describe('Client Registration', () => { + + it ("requires form-urlencoded POST", () => { + return request(app) + .post(buildUrl({ fhir: FHIR_VERSION, path: "/auth/register" }).pathname) + .send({}) + .expect(401, "Invalid request content-type header (must be 'application/x-www-form-urlencoded')") + }); + + it ("requires 'iss' parameter", () => { + return request(app) + .post(buildUrl({ fhir: FHIR_VERSION, path: "/auth/register" }).pathname) + .type("form") + .send({}) + .expect(400, "Missing iss parameter") + }); + + it ("requires 'pub_key' parameter", () => { + return request(app) + .post(buildUrl({ fhir: FHIR_VERSION, path: "/auth/register" }).pathname) + .type("form") + .send({ iss: "whatever" }) + .expect(400, "Missing pub_key parameter") + }); + + it ("validates the 'dur' parameter", () => { + return Promise.all( + ["x", Infinity, -Infinity, -2].map(dur => { + return request(app) + .post(buildUrl({ fhir: FHIR_VERSION, path: "/auth/register" }).pathname) + .type("form") + .send({ iss: "whatever", pub_key: "abc", dur }) + .expect(400, "Invalid dur parameter"); + }) ) - } - }) - .then(res => done(), done) - }); - }); + }); + + it ("basic usage", () => { + return request(app) + .post(buildUrl({ fhir: FHIR_VERSION, path: "/auth/register" }).pathname) + .type("form") + .send({ iss: "whatever", pub_key: "something" }) + .expect(200) + .expect("content-type", /\btext\/plain\b/i) + .expect(res => expect(jwt.decode(res.text)).to.not.be.null); + }); + + it ("accepts custom duration", () => { + return request(app) + .post(buildUrl({ fhir: FHIR_VERSION, path: "/auth/register" }).pathname) + .type("form") + .send({ iss: "whatever", pub_key: "something", dur: 23 }) + .expect(200) + .expect("content-type", /\btext\/plain\b/i) + .expect(res => expect(jwt.decode(res.text).accessTokensExpireIn).to.equal(23)) + }); + + it ("accepts custom simulated errors", () => { + return request(app) + .post(buildUrl({ fhir: FHIR_VERSION, path: "/auth/register" }).pathname) + .type("form") + .send({ iss: "whatever", pub_key: "something", auth_error: "test error" }) + .expect(200) + .expect("content-type", /\btext\/plain\b/i) + .expect(res => expect(jwt.decode(res.text).auth_error).to.equal("test error")) + }); + }); + + it ("Authorization Claim works as expected", async () => { + const iss = "whatever"; + const tokenUrl = buildUrl({ fhir: FHIR_VERSION, path: "/auth/token" }); - describe('Authorization Claim', () => { - it ("Works as expected", done => { - const iss = "whatever"; - const tokenUrl = `${config.baseUrl}/v/${PREFERRED_FHIR_VERSION}/auth/token`; + const alg = "RS384" + const key = await jose.JWK.createKey("RSA", 2048, { alg }) + + const token = await request(app) + .post(buildUrl({ fhir: FHIR_VERSION, path: "/auth/register" }).pathname) + .type("form") + .send({ iss, pub_key: key.toPEM() }) + .then(res => res.text); - requestPromise({ - method: "POST", - uri : `${config.baseUrl}/v/${PREFERRED_FHIR_VERSION}/auth/register`, - form : { - iss, - pub_key: PUBLIC_KEY - } - }) - .then(res => { let jwtToken = { iss, - sub: res.body, - aud: tokenUrl, + sub: token, + aud: tokenUrl.href, exp: Date.now()/1000 + 300, // 5 min jti: crypto.randomBytes(32).toString("hex") }; - - return requestPromise({ - method: "POST", - url : tokenUrl, - json : true, - form : { - scope : "system/*.*", - grant_type: "client_credentials", - client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", - client_assertion: jwt.sign( - jwtToken, - base64url.decode(PRIVATE_KEY), - { algorithm: 'RS256'} - ) + + await request(app) + .post(tokenUrl.pathname) + .type("form") + .send({ + scope: "system/*.*", + grant_type: "client_credentials", + client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", + client_assertion: jwt.sign( + jwtToken, + key.toPEM(true), + { algorithm: alg } + ) + }) + .then(res => { + // console.log(res.text) + if (res.body.token_type !== "bearer") { + throw new Error(`Authorization failed! Expecting token_type: bearer but found ${res.body.token_type}`); } - }); - }) - .then(res => { - // console.log(res.body); - if (res.body.token_type !== "bearer") { - throw new Error(`Authorization failed! Expecting token_type: bearer but found ${res.body.token_type}`); - } - if (res.body.expires_in !== 900) { - throw new Error(`Authorization failed! Expecting expires_in: 900 but found ${res.body.expires_in}`); - } - if (!res.body.access_token) { - throw new Error(`Authorization failed! No access_token returned`); - } - if (res.body.access_token.split(".").length != 3) { - throw new Error("Did not return proper access_token"); - } - return res; - }) - .then(res => done(), done); + if (res.body.expires_in !== 900) { + throw new Error(`Authorization failed! Expecting expires_in: 900 but found ${res.body.expires_in}`); + } + if (!res.body.access_token) { + throw new Error(`Authorization failed! No access_token returned`); + } + if (res.body.access_token.split(".").length != 3) { + throw new Error("Did not return proper access_token"); + } + }) + }); }); - }); + }) +} + - describe('Fhir Requests', () => { - it ("TODO..."); - }); -});