From 4032e66be3ab0458fa1b9e18292f0b79d84c6a83 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Sun, 20 Oct 2019 11:55:46 -0700 Subject: [PATCH] Created middleware to retrieve JWT token from header --- package-lock.json | 308 +++++++++++++++++++---------- package.json | 5 +- src/app.js | 6 + src/middleware/auth.js | 43 ++++ src/middleware/jwt-auth.js | 43 ++++ src/middleware/route-ratelimit.js | 21 +- test/v3/integration/rate-limits.js | 194 ++++++++++-------- 7 files changed, 436 insertions(+), 184 deletions(-) create mode 100644 src/middleware/jwt-auth.js diff --git a/package-lock.json b/package-lock.json index a1846d6..794791d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -102,16 +102,18 @@ } }, "@chris.troutner/bch-js": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/@chris.troutner/bch-js/-/bch-js-1.4.4.tgz", - "integrity": "sha512-928YAVLKhQ0869/toMkIJl76/7Q31UGVNdYFky1tueJ3BibxgiM+nKl2YbQd06doLuZUniwcD9lKm3sHjFqNbw==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@chris.troutner/bch-js/-/bch-js-1.6.1.tgz", + "integrity": "sha512-Wkv7iVw9wYjNF5/Ewy5Qq995x1AV+DWRqikZg9FQxCl7Tub7PmmDfADcJwp+tNKGZs5Q+dpiL30P6zBld52g7g==", "requires": { "apidoc": "^0.17.7", "assert": "^2.0.0", "axios": "^0.19.0", "bc-bip68": "^1.0.5", "bch-wallet-bridge.js": "github:web3bch/bch-wallet-bridge.js#master", + "bchaddrjs-slp": "^0.2.5", "bigi": "^1.4.2", + "bignumber.js": "^9.0.0", "bip-schnorr": "^0.3.0", "bip21": "github:Bitcoin-com/bip21", "bip32-utils": "github:Bitcoin-com/bip32-utils#0.13.1", @@ -141,7 +143,6 @@ "repl.history": "^0.1.4", "safe-buffer": "^5.1.2", "satoshi-bitcoin": "^1.0.4", - "slpjs": "^0.21.0", "socket.io": "^2.1.1", "socket.io-client": "^2.1.1", "touch": "^3.1.0", @@ -287,9 +288,9 @@ "dev": true }, "@semantic-release/github": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-5.4.2.tgz", - "integrity": "sha512-8gkOa5tED/+sjAPwZRYsLaGr6VuAGLZinSvLsuF9/l4qLeYV8gvj7fhjFJepGu6y31t7PR2J9SWzmsqsBAyyKQ==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-5.5.4.tgz", + "integrity": "sha512-CovZ9zs/rbBOCyyzmstoCwGS5qNEgab3wB8y3SAvOD7TfhtqizWVRkUJV7nzzaevomhUSZD/lp7Cs/fk1sE1Qw==", "dev": true, "requires": { "@octokit/rest": "^16.27.0", @@ -301,16 +302,49 @@ "fs-extra": "^8.0.0", "globby": "^10.0.0", "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.1", - "issue-parser": "^4.0.0", + "https-proxy-agent": "^3.0.0", + "issue-parser": "^5.0.0", "lodash": "^4.17.4", "mime": "^2.4.3", "p-filter": "^2.0.0", "p-retry": "^4.0.0", - "parse-github-url": "^1.0.1", "url-join": "^4.0.0" }, "dependencies": { + "https-proxy-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-3.0.0.tgz", + "integrity": "sha512-y4jAxNEihqvBI5F3SaO2rtsjIOnnNA8sEbuiP+UhJZJHeM2NRm6c09ax2tgqme+SgUUvjao2fJXF4h3D6Cb2HQ==", + "dev": true, + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "issue-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-5.0.0.tgz", + "integrity": "sha512-q/16W7EPHRL0FKVz9NU++TUsoygXGj6JOi88oulyAcQG+IEZ0T6teVdE+VLbe19OfL/tbV8Wi3Dfo0HedeHW0Q==", + "dev": true, + "requires": { + "lodash.capitalize": "^4.2.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.uniqby": "^4.7.0" + } + }, "mime": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", @@ -1466,8 +1500,8 @@ } }, "bchaddrjs-slp": { - "version": "git://github.com/simpleledger/bchaddrjs.git#ea05d679783a6737f2f99adc37ebf485dc64f006", - "from": "git://github.com/simpleledger/bchaddrjs.git#master", + "version": "0.2.6", + "resolved": "git://github.com/simpleledger/bchaddrjs.git#ea05d679783a6737f2f99adc37ebf485dc64f006", "requires": { "bs58check": "^2.1.2", "cashaddrjs-slp": "^0.2.11" @@ -1519,8 +1553,7 @@ "bignumber.js": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", - "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==", - "dev": true + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==" }, "binary-extensions": { "version": "1.13.1", @@ -1711,7 +1744,7 @@ "integrity": "sha512-x0wMVSCZ56ZQnSQ+Xim7XfS0IYiOyOwo0pdMCmR+cDNB7zRhICFuH5i3lNdLSgUw1e7Pc3FmUlC9wruQNlYMnA==" }, "bitcoincashjs-lib": { - "version": "github:christroutner/bitcoincashjs-lib#0a2291b5c5558241003aa9609f82846313604f15", + "version": "github:christroutner/bitcoincashjs-lib#b03ca974d4fd548cb42c7a522863e5d9be6051ef", "from": "github:christroutner/bitcoincashjs-lib", "requires": { "bech32": "^1.1.2", @@ -1884,6 +1917,11 @@ "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", "dev": true }, + "bowser": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.7.0.tgz", + "integrity": "sha512-aIlMvstvu8x+34KEiOHD3AsBgdrzg6sxALYiukOWhFvGMbQI6TRP/iY0LMhUrHs56aD6P1G0Z7h45PUJaa5m9w==" + }, "boxen": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", @@ -2236,6 +2274,11 @@ "resolved": "https://registry.npmjs.org/buffer-compare/-/buffer-compare-1.1.1.tgz", "integrity": "sha1-W+e+hTr4kZjR9N3AkNHWakiu9ZY=" }, + "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", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, "buffer-equals": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/buffer-equals/-/buffer-equals-1.0.4.tgz", @@ -2429,9 +2472,9 @@ "dev": true }, "cashaddrjs": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/cashaddrjs/-/cashaddrjs-0.3.6.tgz", - "integrity": "sha512-TEeTEXT8Gft4cNoZKisCdyrAymnSRYpZHtvCRgXJ9OmOwyTKn5vnCWbPQLL3EiUkm1HeMxSrKNK29nMfvaT2Qw==", + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cashaddrjs/-/cashaddrjs-0.3.8.tgz", + "integrity": "sha512-gpXj9Qki9noucK0ahPB6dx9A539KQ4TbqKLwl/JsCvD4iLLu1thtoAIDy2hwP6PWg6o1nvK+igsutMA6OvO2Mg==", "requires": { "big-integer": "1.6.36" }, @@ -2722,9 +2765,9 @@ } }, "commander": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.0.tgz", - "integrity": "sha512-pl3QrGOBa9RZaslQiqnnKX2J068wcQw7j9AIaBQ9/JEp5RY6je4jKTImg0Bd+rpoONSe7GUFSgkxLeo17m3Pow==" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", + "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==" }, "commist": { "version": "1.1.0", @@ -3674,6 +3717,14 @@ "safer-buffer": "^2.1.0" } }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "ecurve": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/ecurve/-/ecurve-1.0.6.tgz", @@ -4567,12 +4618,20 @@ } }, "eslint-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.0.tgz", - "integrity": "sha512-7ehnzPaP5IIEh1r1tkjuIrxqhNkzUJa9z3R92tLJdZIVdWaczEhr3EbhGtsMrVxi1KeR8qA7Off6SWc5WNQqyQ==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", "dev": true, "requires": { - "eslint-visitor-keys": "^1.0.0" + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", + "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", + "dev": true + } } }, "eslint-visitor-keys": { @@ -4814,11 +4873,34 @@ "basic-auth": "^2.0.1" } }, + "express-jwt": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/express-jwt/-/express-jwt-5.3.1.tgz", + "integrity": "sha512-1C9RNq0wMp/JvsH/qZMlg3SIPvKu14YkZ4YYv7gJQ1Vq+Dv8LH9tLKenS5vMNth45gTlEUGx+ycp9IHIlaHP/g==", + "requires": { + "async": "^1.5.0", + "express-unless": "^0.3.0", + "jsonwebtoken": "^8.1.0", + "lodash.set": "^4.0.0" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + } + } + }, "express-rate-limit": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-5.0.0.tgz", "integrity": "sha512-dhT57wqxfqmkOi4HM7NuT4Gd7gbUgSK2ocG27Y6lwm8lbOAw9XQfeANawGq8wLDtlGPO1ZgDj0HmKsykTxfFAg==" }, + "express-unless": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/express-unless/-/express-unless-0.3.1.tgz", + "integrity": "sha1-JVfBRudb65A+LSR/m1ugFFJpbiA=" + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -6141,9 +6223,9 @@ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==" }, "handlebars": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", - "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.4.3.tgz", + "integrity": "sha512-B0W4A2U1ww3q7VVthTKfh+epHx+q4mCt6iK+zEAzbMBpWQAwxCeKxEGpj/1oQTpzPXDNSOG7hmG14TsISH50yw==", "requires": { "neo-async": "^2.6.0", "optimist": "^0.6.1", @@ -6323,9 +6405,9 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, "helmet": { - "version": "3.20.0", - "resolved": "https://registry.npmjs.org/helmet/-/helmet-3.20.0.tgz", - "integrity": "sha512-Ob+TqmQFZ5f7WgP8kBbAzNPsbf6p1lOj5r+327/ymw/IILWih3wcx9u/u/S8Mwv5wbBkO7Li6x5s23t3COhUKw==", + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-3.21.1.tgz", + "integrity": "sha512-IC/54Lxvvad2YiUdgLmPlNFKLhNuG++waTF5KPYq/Feo3NNhqMFbcLAlbVkai+9q0+4uxjxGPJ9bNykG+3zZNg==", "requires": { "depd": "2.0.0", "dns-prefetch-control": "0.2.0", @@ -6334,14 +6416,14 @@ "feature-policy": "0.3.0", "frameguard": "3.1.0", "helmet-crossdomain": "0.4.0", - "helmet-csp": "2.8.0", + "helmet-csp": "2.9.2", "hide-powered-by": "1.1.0", "hpkp": "2.0.0", "hsts": "2.2.0", "ienoopen": "1.1.0", "nocache": "2.1.0", "referrer-policy": "1.2.0", - "x-xss-protection": "1.2.0" + "x-xss-protection": "1.3.0" }, "dependencies": { "depd": { @@ -6357,14 +6439,14 @@ "integrity": "sha512-AB4DTykRw3HCOxovD1nPR16hllrVImeFp5VBV9/twj66lJ2nU75DP8FPL0/Jp4jj79JhTfG+pFI2MD02kWJ+fA==" }, "helmet-csp": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/helmet-csp/-/helmet-csp-2.8.0.tgz", - "integrity": "sha512-MlCPeM0Sm3pS9RACRihx70VeTHmkQwa7sum9EK1tfw1VZyvFU0dBWym9nHh3CRkTRNlyNm/WFCMvuh9zXkOjNw==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/helmet-csp/-/helmet-csp-2.9.2.tgz", + "integrity": "sha512-Lt5WqNfbNjEJ6ysD4UNpVktSyjEKfU9LVJ1LaFmPfYseg/xPealPfgHhtqdAdjPDopp5zbg/VWCyp4cluMIckw==", "requires": { + "bowser": "^2.6.1", "camelize": "1.0.0", "content-security-policy-builder": "2.1.0", - "dasherize": "2.0.0", - "platform": "1.3.5" + "dasherize": "2.0.0" } }, "help-me": { @@ -6518,27 +6600,6 @@ "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=" }, - "https-proxy-agent": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.2.tgz", - "integrity": "sha512-c8Ndjc9Bkpfx/vCJueCPy0jlP4ccCCSNDp8xwCZzPjKJUm+B+u9WX2x98Qx4n1PiMNTWo3D7KK5ifNV/yJyRzg==", - "dev": true, - "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -7109,19 +7170,6 @@ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "dev": true }, - "issue-parser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-4.0.0.tgz", - "integrity": "sha512-1RmmAXHl5+cqTZ9dRr861xWy0Gkc9TWTEklgjKv+nhlB1dY1NmGBV8b20jTWRL5cPGpOIXkz84kEcDBM8Nc0cw==", - "dev": true, - "requires": { - "lodash.capitalize": "^4.2.1", - "lodash.escaperegexp": "^4.1.2", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.uniqby": "^4.7.0" - } - }, "istanbul-lib-coverage": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", @@ -7297,6 +7345,30 @@ "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" }, + "jsonwebtoken": { + "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" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -7324,6 +7396,25 @@ "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==", "dev": true }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "keccak": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/keccak/-/keccak-1.4.0.tgz", @@ -7553,34 +7644,56 @@ "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", "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.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.ismatch": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", "integrity": "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=", "dev": true }, + "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=", - "dev": true + "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=", - "dev": true + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" }, "lodash.memoize": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=" }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, "lodash.set": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", - "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=", - "dev": true + "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=" }, "lodash.toarray": { "version": "4.4.0", @@ -12567,12 +12680,6 @@ "safe-buffer": "^5.1.1" } }, - "parse-github-url": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-github-url/-/parse-github-url-1.0.2.tgz", - "integrity": "sha512-kgBf6avCbO3Cn6+RnzRGLkUsv4ZVqv/VfAYkRsyBcgkshNvVBkRn1FEZcW0Jb+npXQWm2vHPnnOqFteZxRRGNw==", - "dev": true - }, "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -12634,6 +12741,14 @@ "passport-strategy": "1.x.x" } }, + "passport-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", + "integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=", + "requires": { + "passport-strategy": "1.x.x" + } + }, "passport-strategy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", @@ -12870,11 +12985,6 @@ "find-up": "^3.0.0" } }, - "platform": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.5.tgz", - "integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q==" - }, "pluralize": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", @@ -15254,19 +15364,19 @@ "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" }, "uglify-js": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz", - "integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.3.tgz", + "integrity": "sha512-KfQUgOqTkLp2aZxrMbCuKCDGW9slFYu2A23A36Gs7sGzTLcRBDORdOi5E21KWHFIfkY8kzgi/Pr1cXCh0yIp5g==", "optional": true, "requires": { - "commander": "~2.20.0", + "commander": "~2.20.3", "source-map": "~0.6.1" }, "dependencies": { "commander": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "optional": true }, "source-map": { @@ -15882,9 +15992,9 @@ } }, "x-xss-protection": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/x-xss-protection/-/x-xss-protection-1.2.0.tgz", - "integrity": "sha512-xN0kV+8XfOQM2OPPBdEbGtbvJNNP1pvZR7sE6d44cjJFQG4OiGDdienPg5iOUGswBTiGbBvtYDURd30BMJwwqg==" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/x-xss-protection/-/x-xss-protection-1.3.0.tgz", + "integrity": "sha512-kpyBI9TlVipZO4diReZMAHWtS0MMa/7Kgx8hwG/EuZLiA6sg4Ah/4TRdASHhRRN3boobzcYgFRUFSgHRge6Qhg==" }, "xdg-basedir": { "version": "3.0.0", diff --git a/package.json b/package.json index 2c593b5..d184714 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "node": ">=10.15.1" }, "dependencies": { - "@chris.troutner/bch-js": "^1.3.0", + "@chris.troutner/bch-js": "^1.6.1", "apidoc": "^0.17.7", "axios": "^0.19.0", "body-parser": "^1.18.3", @@ -33,7 +33,7 @@ "express": "^4.15.5", "express-basic-auth": "^1.1.3", "express-rate-limit": "^5.0.0", - "helmet": "^3.12.1", + "helmet": "^3.21.1", "level": "^5.0.1", "mkdirp": "^0.5.1", "mocha": "^6.1.4", @@ -42,6 +42,7 @@ "passport": "^0.4.0", "passport-anonymous": "^1.0.1", "passport-http": "^0.3.0", + "passport-local": "^1.0.0", "pg": "^7.11.0", "pg-hstore": "^2.3.2", "slp-sdk": "^4.4.1", diff --git a/src/app.js b/src/app.js index 0b06579..f487d8d 100644 --- a/src/app.js +++ b/src/app.js @@ -15,7 +15,10 @@ const helmet = require("helmet") const debug = require("debug")("rest-cloud:server") const http = require("http") const cors = require("cors") + +// Auth and rate limiting middleware libraries. const AuthMW = require("./middleware/auth") +const jwtAuth = require("./middleware/jwt-auth") // v2 const indexV2 = require("./routes/v2/index") @@ -88,6 +91,9 @@ app.use(express.static(path.join(__dirname, "public"))) const v2prefix = "v2" const v3prefix = "v3" +// Inspect the header for a JWT token. +app.use(`/${v3prefix}/`, jwtAuth.getTokenFromHeaders) + // Instantiate the authorization middleware, used to implement pro-tier rate limiting. const auth = new AuthMW() app.use(`/${v2prefix}/`, auth.mw()) diff --git a/src/middleware/auth.js b/src/middleware/auth.js index f734a4b..86a9e1b 100644 --- a/src/middleware/auth.js +++ b/src/middleware/auth.js @@ -1,6 +1,13 @@ /* Handle authorization for bypassing rate limits. + 1) Default is 'Anonymous Authentication', which unlocks the freemimum tier by + default. + 2) Hard-coded 'Basic Authentication' is a token that does not expire and is + provided to buisiness partners. + 3) JWT-based 'Local Authentication' is used for normal users that pay to + access the premium pro-tier services. + This file uses the passport npm library to check the header of each REST API call for the prescence of a Basic authorization header: https://en.wikipedia.org/wiki/Basic_access_authentication @@ -14,7 +21,9 @@ const passport = require("passport") const BasicStrategy = require("passport-http").BasicStrategy const AnonymousStrategy = require("passport-anonymous") +const LocalStrategy = require("passport-local") const wlogger = require("../util/winston-logging") +const axios = require("axios") // Used for debugging and iterrogating JS objects. const util = require("util") @@ -88,6 +97,40 @@ class AuthMW { return done(null, true) }) ) + + // JWT token based authentication. + passport.use( + new LocalStrategy( + { + usernameField: "user[email]", + passwordField: "user[password]", + passReqToCallback: true, + session: false + }, + async (req, email, password, done) => { + console.log(`Checking against local strategy.`) + + const userData = {} + const isValid = true + + // Lookup the user from the database. + // const userData = await userDB.findByEmail(email) + //console.log(`userData: ${util.inspect(userDataRaw)}`) + + // Hash the password and see if it matches the saved hash. + // const isValid = jwt.validatePassword(userData, password) + + if (isValid) { + //console.log(`Passwords match!`) + return done(null, userData) + } + + return done(null, false, { + errors: { "email or password": "is invalid" } + }) + } + ) + ) } // Middleware called by the route. diff --git a/src/middleware/jwt-auth.js b/src/middleware/jwt-auth.js new file mode 100644 index 0000000..485e7fa --- /dev/null +++ b/src/middleware/jwt-auth.js @@ -0,0 +1,43 @@ +/* + This middleware inspects the request header for a JWT token. + If found, will populate req.locals.jwtToken with the JWT token. +*/ + +"use strict" + +// This function searches the header for the a JWT token in the authorization header. +// If one is found, this middleware passes the JWT token through the +// req.locals.jwtToken property. +const getTokenFromHeaders = (req, res, next) => { + try { + // console.log(`getTokenFromHeaders2: Searching headers for a JWT token.`) + + // console.log(`req.headers: `, req.headers) + + // If the authorization header exists. + if (req.headers.authorization) { + const authStr = req.headers.authorization + + // If the header is proceeded by the word 'Token' + if (authStr.split(" ")[0] === "Token") { + const token = authStr.split(" ")[1] + + // console.log(`JWT found: ${token}`) + + // Create the req.locals property if it does not yet exist. + if (!req.locals) req.locals = {} + req.locals.jwtToken = token + } + } + } catch (err) { + console.log(`Error in getTokenFromHeaders2: `, err) + } + + next() +} + +const jwtAuth = { + getTokenFromHeaders +} + +module.exports = jwtAuth diff --git a/src/middleware/route-ratelimit.js b/src/middleware/route-ratelimit.js index 25aab88..70e2c20 100644 --- a/src/middleware/route-ratelimit.js +++ b/src/middleware/route-ratelimit.js @@ -24,11 +24,28 @@ const PRO_RPM = 10 * maxRequests const uniqueRateLimits = {} const routeRateLimit = function(req, res, next) { + // Disable rate limiting if 0 passed from RATE_LIMIT_MAX_REQUESTS + if (maxRequests === 0) return next() + // Create a res.locals object if not passed in. if (!req.locals) req.locals = {} - // Disable rate limiting if 0 passed from RATE_LIMIT_MAX_REQUESTS - if (maxRequests === 0) return next() + if (req.locals.jwtToken) + console.log(`req.locals.jwtToken: ${req.locals.jwtToken}`) + + // If no JWT token was provided, skip + if (req.payload) + console.log(`req.payload: ${JSON.stringify(req.payload, null, 2)}`) + // // Unlock the pro-tier rate limits if the user passed in a valid JWT token. + // if (!proRateLimits) { + // const user = await getUserFromJWT(req) + // + // // Enable pro-tier rate limits for this user. + // if(user) { + // console.log(`${user.email} (${user.id}) passed in valid JWT`) + // proRateLimits = true + // } + // } // Current route const rateLimitTier = req.locals.proLimit ? "PRO" : "BASIC" diff --git a/test/v3/integration/rate-limits.js b/test/v3/integration/rate-limits.js index e487d2d..3a71efa 100644 --- a/test/v3/integration/rate-limits.js +++ b/test/v3/integration/rate-limits.js @@ -14,8 +14,11 @@ const axios = require("axios") const util = require("util") util.inspect.defaultOptions = { depth: 1 } -const SERVER = `http://192.168.0.36:12400/v3/` -//const SERVER = `http://localhost:3000/v2/` +// const SERVER = `http://192.168.0.36:12400/v3/` +const SERVER = `http://localhost:3000/v3/` + +const TEST_JWT = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVkYTc5ZDk4OTYyMjRjNjM2MmQwYzkwMiIsImlhdCI6MTU3MTUzOTU1MSwiZXhwIjoxNTc0MTMxNTUxfQ.PfPW_Z2NYT1O2zUHXopcz2aLGHSGudaKOIGnt7SuAi4" describe("#rate limits", () => { it("should get control/getNetworkInfo() with no auth", async () => { @@ -32,102 +35,131 @@ describe("#rate limits", () => { assert.hasAnyKeys(result.data, ["version"]) }) - it("should trigger rate-limit handler if rate limits exceeds 30 request per minute", async () => { + // it("should trigger rate-limit handler if rate limits exceeds 30 request per minute", async () => { + // try { + // // Actual rate limit is 60 per minute X 4 nodes = 240 rpm. + // const options = { + // method: "GET", + // url: `${SERVER}control/getNetworkInfo` + // } + // + // const promises = [] + // for (let i = 0; i < 30; i++) { + // const promise = axios(options) + // promises.push(promise) + // } + // + // await Promise.all(promises) + // + // assert.equal(true, false, "Unexpected result!") + // } catch (err) { + // //console.log(`err.response: ${util.inspect(err.response)}`) + // + // assert.equal(err.response.status, 429) + // assert.include(err.response.data.error, "Too many requests") + // } + // }) + + // it("should not trigger rate-limit handler if correct pro-tier password is used", async () => { + // try { + // const username = "BITBOX" + // + // // Pro-tier is accessed by using the right password. + // const password = "BITBOX" + // //const password = "something" + // + // const combined = `${username}:${password}` + // const base64Credential = Buffer.from(combined).toString("base64") + // const readyCredential = `Basic ${base64Credential}` + // + // const options = { + // method: "GET", + // url: `${SERVER}control/getNetworkInfo`, + // headers: { Authorization: readyCredential } + // } + // + // const promises = [] + // for (let i = 0; i < 30; i++) { + // const promise = axios(options) + // promises.push(promise) + // } + // + // await Promise.all(promises) + // + // assert.equal(true, true, "Not throwing an error is a pass!") + // } catch (err) { + // // console.log(`err.response: ${util.inspect(err.response)}`) + // + // assert.equal( + // true, + // false, + // "This error handler should not have been triggered. Is the password correct?" + // ) + // } + // }) + + // it("should trigger rate-limit handler if rate limits exceeds pro-tier limit", async () => { + // try { + // const username = "BITBOX" + // + // // Pro-tier is accessed by using the right password. + // const password = "BITBOX" + // //const password = "something" + // + // const combined = `${username}:${password}` + // const base64Credential = Buffer.from(combined).toString("base64") + // const readyCredential = `Basic ${base64Credential}` + // + // // Actual rate limit is 60 per minute X 4 nodes = 240 rpm. + // const options = { + // method: "GET", + // url: `${SERVER}control/getNetworkInfo`, + // headers: { Authorization: readyCredential } + // } + // + // const promises = [] + // for (let i = 0; i < 80; i++) { + // const promise = axios(options) + // promises.push(promise) + // } + // + // await Promise.all(promises) + // + // assert.equal(true, false, "Unexpected result!") + // } catch (err) { + // //console.log(`err.response: ${util.inspect(err.response)}`) + // + // assert.equal(err.response.status, 429) + // assert.include(err.response.data.error, "Too many requests") + // } + // }) + + it("should unlock pro-tier for a valid JWT token", async () => { try { // Actual rate limit is 60 per minute X 4 nodes = 240 rpm. - const options = { - method: "GET", - url: `${SERVER}control/getNetworkInfo` - } - - const promises = [] - for (let i = 0; i < 30; i++) { - const promise = axios(options) - promises.push(promise) - } - - await Promise.all(promises) - - assert.equal(true, false, "Unexpected result!") - } catch (err) { - //console.log(`err.response: ${util.inspect(err.response)}`) - - assert.equal(err.response.status, 429) - assert.include(err.response.data.error, "Too many requests") - } - }) - - it("should not trigger rate-limit handler if correct pro-tier password is used", async () => { - try { - const username = "BITBOX" - - // Pro-tier is accessed by using the right password. - const password = "BITBOX" - //const password = "something" - - const combined = `${username}:${password}` - const base64Credential = Buffer.from(combined).toString("base64") - const readyCredential = `Basic ${base64Credential}` - const options = { method: "GET", url: `${SERVER}control/getNetworkInfo`, - headers: { Authorization: readyCredential } + headers: { + Authorization: `Token ${TEST_JWT}` + } } const promises = [] - for (let i = 0; i < 30; i++) { + for (let i = 0; i < 1; i++) { const promise = axios(options) promises.push(promise) } await Promise.all(promises) + //assert.equal(true, false, "Unexpected result!") assert.equal(true, true, "Not throwing an error is a pass!") } catch (err) { - // console.log(`err.response: ${util.inspect(err.response)}`) - - assert.equal( - true, - false, - "This error handler should not have been triggered. Is the password correct?" - ) - } - }) - - it("should trigger rate-limit handler if rate limits exceeds pro-tier limit", async () => { - try { - const username = "BITBOX" - - // Pro-tier is accessed by using the right password. - const password = "BITBOX" - //const password = "something" - - const combined = `${username}:${password}` - const base64Credential = Buffer.from(combined).toString("base64") - const readyCredential = `Basic ${base64Credential}` - - // Actual rate limit is 60 per minute X 4 nodes = 240 rpm. - const options = { - method: "GET", - url: `${SERVER}control/getNetworkInfo`, - headers: { Authorization: readyCredential } - } - - const promises = [] - for (let i = 0; i < 80; i++) { - const promise = axios(options) - promises.push(promise) - } - - await Promise.all(promises) + console.log(`err.response: ${util.inspect(err.response)}`) assert.equal(true, false, "Unexpected result!") - } catch (err) { - //console.log(`err.response: ${util.inspect(err.response)}`) - - assert.equal(err.response.status, 429) - assert.include(err.response.data.error, "Too many requests") } - }) + // Override default timeout for this test. + }).timeout(20000) })