mirror of
https://github.com/fullstack-cash/bch-api.git
synced 2026-09-23 01:32:03 -07:00
Synced with master
This commit is contained in:
@@ -9,6 +9,10 @@ sudo: required
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
- docker
|
- docker
|
||||||
|
- redis-server
|
||||||
|
|
||||||
|
#before_install:
|
||||||
|
# - ./install-redis.sh
|
||||||
|
|
||||||
script: "npm run test"
|
script: "npm run test"
|
||||||
|
|
||||||
|
|||||||
@@ -10,5 +10,6 @@ export DATA="{\"ref\":\"$DEPLOY_SECRET\"}"
|
|||||||
#echo $DATA
|
#echo $DATA
|
||||||
|
|
||||||
curl -X POST http://fullstack.cash:9000/hooks/bch-api-mainnet -H "Content-Type: application/json" -d $DATA
|
curl -X POST http://fullstack.cash:9000/hooks/bch-api-mainnet -H "Content-Type: application/json" -d $DATA
|
||||||
|
curl -X POST http://fullstack.cash:9000/hooks/bch-api-testnet -H "Content-Type: application/json" -d $DATA
|
||||||
|
|
||||||
echo "...Finished deploying to production."
|
echo "...Finished deploying to production."
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ USER safeuser
|
|||||||
# Prep 'sudo' commands.
|
# Prep 'sudo' commands.
|
||||||
#RUN echo 'abcd8765' | sudo -S pwd
|
#RUN echo 'abcd8765' | sudo -S pwd
|
||||||
|
|
||||||
|
# Install Redis. TODO: Make this a separate container.
|
||||||
|
|
||||||
|
|
||||||
# Clone the repository
|
# Clone the repository
|
||||||
WORKDIR /home/safeuser
|
WORKDIR /home/safeuser
|
||||||
RUN git clone https://github.com/christroutner/bch-api
|
RUN git clone https://github.com/christroutner/bch-api
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
bch-api:
|
bch-api-main:
|
||||||
build: .
|
build: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
#image: bitcore
|
#image: bitcore
|
||||||
container_name: bch-api-main
|
container_name: bch-api-main
|
||||||
#links:
|
links:
|
||||||
# - mongodb
|
- redis-main
|
||||||
ports:
|
ports:
|
||||||
- "12400:3000"
|
- "12400:3000"
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
|
redis-main:
|
||||||
|
image: redis:latest
|
||||||
|
container_name: redis-main
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
|||||||
@@ -17,4 +17,8 @@ export NODE_TLS_REJECT_UNAUTHORIZED=0
|
|||||||
|
|
||||||
export JWT_AUTH_SERVER=http://localhost:5001/
|
export JWT_AUTH_SERVER=http://localhost:5001/
|
||||||
|
|
||||||
|
# Redis DB
|
||||||
|
export REDIS_PORT=6379
|
||||||
|
export REDIS_HOST=172.17.0.1
|
||||||
|
|
||||||
npm start
|
npm start
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
bch-api:
|
bch-api-test:
|
||||||
build: .
|
build: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
#image: bitcore
|
#image: bitcore
|
||||||
container_name: bch-api
|
container_name: bch-api-test
|
||||||
#links:
|
links:
|
||||||
# - mongodb
|
- redis-test
|
||||||
ports:
|
ports:
|
||||||
- "13400:3000"
|
- "13400:3000"
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
|
redis-test:
|
||||||
|
image: redis:latest
|
||||||
|
container_name: redis-test
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "6380:6379"
|
||||||
|
|||||||
@@ -17,4 +17,8 @@ export BLOCKBOOK_URL=https://172.17.0.1:19131/
|
|||||||
# Allow node.js to make network calls to https using self-signed certificate.
|
# Allow node.js to make network calls to https using self-signed certificate.
|
||||||
export NODE_TLS_REJECT_UNAUTHORIZED=0
|
export NODE_TLS_REJECT_UNAUTHORIZED=0
|
||||||
|
|
||||||
|
# Redis DB
|
||||||
|
export REDIS_PORT=6380
|
||||||
|
export REDIS_HOST=172.17.0.1
|
||||||
|
|
||||||
npm start
|
npm start
|
||||||
|
|||||||
Generated
+185
-4
@@ -731,11 +731,27 @@
|
|||||||
"integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==",
|
"integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@types/bn.js": {
|
||||||
|
"version": "4.11.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz",
|
||||||
|
"integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==",
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/color-name": {
|
"@types/color-name": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
|
||||||
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ=="
|
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ=="
|
||||||
},
|
},
|
||||||
|
"@types/elliptic": {
|
||||||
|
"version": "6.4.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/elliptic/-/elliptic-6.4.12.tgz",
|
||||||
|
"integrity": "sha512-gP1KsqoouLJGH6IJa28x7PXb3cRqh83X8HCLezd2dF+XcAIMKYv53KV+9Zn6QA561E120uOqZBQ+Jy/cl+fviw==",
|
||||||
|
"requires": {
|
||||||
|
"@types/bn.js": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/events": {
|
"@types/events": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
|
||||||
@@ -1158,6 +1174,17 @@
|
|||||||
"safer-buffer": "~2.1.0"
|
"safer-buffer": "~2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"asn1.js": {
|
||||||
|
"version": "5.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.3.0.tgz",
|
||||||
|
"integrity": "sha512-WHnQJFcOrIWT1RLOkFFBQkFVvyt9BPOOrH+Dp152Zk4R993rSzXUGPmkybIcUFhHE2d/iHH+nCaOWVCDbO8fgA==",
|
||||||
|
"requires": {
|
||||||
|
"bn.js": "^4.0.0",
|
||||||
|
"inherits": "^2.0.1",
|
||||||
|
"minimalistic-assert": "^1.0.0",
|
||||||
|
"safer-buffer": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"assert": {
|
"assert": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz",
|
||||||
@@ -1870,6 +1897,11 @@
|
|||||||
"ieee754": "^1.1.4"
|
"ieee754": "^1.1.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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": {
|
"buffer-equals": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-equals/-/buffer-equals-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-equals/-/buffer-equals-1.0.4.tgz",
|
||||||
@@ -2256,6 +2288,11 @@
|
|||||||
"wrap-ansi": "^5.1.0"
|
"wrap-ansi": "^5.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"cluster-key-slot": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw=="
|
||||||
|
},
|
||||||
"co": {
|
"co": {
|
||||||
"version": "4.6.0",
|
"version": "4.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
||||||
@@ -3041,6 +3078,11 @@
|
|||||||
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
|
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"denque": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ=="
|
||||||
|
},
|
||||||
"depd": {
|
"depd": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||||
@@ -3231,6 +3273,14 @@
|
|||||||
"safer-buffer": "^2.1.0"
|
"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": {
|
"ecurve": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/ecurve/-/ecurve-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/ecurve/-/ecurve-1.0.6.tgz",
|
||||||
@@ -5690,6 +5740,22 @@
|
|||||||
"p-is-promise": "^3.0.0"
|
"p-is-promise": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ioredis": {
|
||||||
|
"version": "4.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.14.1.tgz",
|
||||||
|
"integrity": "sha512-94W+X//GHM+1GJvDk6JPc+8qlM7Dul+9K+lg3/aHixPN7ZGkW6qlvX0DG6At9hWtH2v3B32myfZqWoANUJYGJA==",
|
||||||
|
"requires": {
|
||||||
|
"cluster-key-slot": "^1.1.0",
|
||||||
|
"debug": "^4.1.1",
|
||||||
|
"denque": "^1.1.0",
|
||||||
|
"lodash.defaults": "^4.2.0",
|
||||||
|
"lodash.flatten": "^4.4.0",
|
||||||
|
"redis-commands": "1.5.0",
|
||||||
|
"redis-errors": "^1.2.0",
|
||||||
|
"redis-parser": "^3.0.0",
|
||||||
|
"standard-as-callback": "^2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"ipaddr.js": {
|
"ipaddr.js": {
|
||||||
"version": "1.9.0",
|
"version": "1.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz",
|
||||||
@@ -6244,6 +6310,30 @@
|
|||||||
"integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=",
|
"integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"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": {
|
"jsprim": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
|
||||||
@@ -6271,6 +6361,25 @@
|
|||||||
"integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==",
|
"integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==",
|
||||||
"dev": true
|
"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": {
|
"keccak": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/keccak/-/keccak-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/keccak/-/keccak-1.4.0.tgz",
|
||||||
@@ -6282,6 +6391,17 @@
|
|||||||
"safe-buffer": "^5.1.0"
|
"safe-buffer": "^5.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"key-encoder": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/key-encoder/-/key-encoder-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-fgBtpAGIr/Fy5/+ZLQZIPPhsZEcbSlYu/Wu96tNDFNSjSACw5lEIOFeaVdQ/iwrb8oxjlWi6wmWdH76hV6GZjg==",
|
||||||
|
"requires": {
|
||||||
|
"@types/elliptic": "^6.4.9",
|
||||||
|
"asn1.js": "^5.0.1",
|
||||||
|
"bn.js": "^4.11.8",
|
||||||
|
"elliptic": "^6.4.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"kind-of": {
|
"kind-of": {
|
||||||
"version": "6.0.3",
|
"version": "6.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||||
@@ -6473,12 +6593,22 @@
|
|||||||
"integrity": "sha1-+CbJtOKoUR2E46yinbBeGk87cqk=",
|
"integrity": "sha1-+CbJtOKoUR2E46yinbBeGk87cqk=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"lodash.defaults": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||||
|
"integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw="
|
||||||
|
},
|
||||||
"lodash.escaperegexp": {
|
"lodash.escaperegexp": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
|
||||||
"integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=",
|
"integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"lodash.flatten": {
|
||||||
|
"version": "4.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
|
||||||
|
"integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8="
|
||||||
|
},
|
||||||
"lodash.flattendeep": {
|
"lodash.flattendeep": {
|
||||||
"version": "4.4.0",
|
"version": "4.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz",
|
||||||
@@ -6490,23 +6620,46 @@
|
|||||||
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=",
|
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=",
|
||||||
"dev": true
|
"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": {
|
"lodash.ismatch": {
|
||||||
"version": "4.4.0",
|
"version": "4.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz",
|
||||||
"integrity": "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=",
|
"integrity": "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=",
|
||||||
"dev": true
|
"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": {
|
"lodash.isplainobject": {
|
||||||
"version": "4.0.6",
|
"version": "4.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||||
"integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=",
|
"integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"lodash.isstring": {
|
"lodash.isstring": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||||
"integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=",
|
"integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE="
|
||||||
"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="
|
||||||
},
|
},
|
||||||
"lodash.set": {
|
"lodash.set": {
|
||||||
"version": "4.3.2",
|
"version": "4.3.2",
|
||||||
@@ -12165,6 +12318,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
|
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
|
||||||
},
|
},
|
||||||
|
"rate-limiter-flexible": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-DjLeci3BuHWNr9LVVm+YPJ+Lrki/J9iDb9cEJFZELpa/ZBhXmfnfR+eCI5jzmsPJtpYGYhw/vyRfjjQjTzVtRg=="
|
||||||
|
},
|
||||||
"raw-body": {
|
"raw-body": {
|
||||||
"version": "2.4.0",
|
"version": "2.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
|
||||||
@@ -12332,6 +12490,24 @@
|
|||||||
"esprima": "~4.0.0"
|
"esprima": "~4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"redis-commands": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-6KxamqpZ468MeQC3bkWmCB1fp56XL64D4Kf0zJSwDZbVLLm7KFkoIcHrgRvQ+sk8dnhySs7+yBg94yIkAK7aJg=="
|
||||||
|
},
|
||||||
|
"redis-errors": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
|
||||||
|
"integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60="
|
||||||
|
},
|
||||||
|
"redis-parser": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
|
||||||
|
"integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=",
|
||||||
|
"requires": {
|
||||||
|
"redis-errors": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"referrer-policy": {
|
"referrer-policy": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.2.0.tgz",
|
||||||
@@ -13628,6 +13804,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"standard-as-callback": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-NQOxSeB8gOI5WjSaxjBgog2QFw55FV8TkS6Y07BiB3VJ8xNTvUYm0wl0s8ObgQ5NhdpnNfigMIKjgPESzgr4tg=="
|
||||||
|
},
|
||||||
"standard-engine": {
|
"standard-engine": {
|
||||||
"version": "8.0.1",
|
"version": "8.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/standard-engine/-/standard-engine-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/standard-engine/-/standard-engine-8.0.1.tgz",
|
||||||
|
|||||||
+6
-2
@@ -15,15 +15,15 @@
|
|||||||
"test:temp": "export NETWORK=mainnet && mocha --timeout 25000 test/v3/blockchain.js",
|
"test:temp": "export NETWORK=mainnet && mocha --timeout 25000 test/v3/blockchain.js",
|
||||||
"test:integration": "mocha test/v3/integration",
|
"test:integration": "mocha test/v3/integration",
|
||||||
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
||||||
"coverage:report": "export NETWORK=testnet && nyc --reporter=html mocha test/v2/",
|
"coverage:report": "export NETWORK=mainnet && nyc --reporter=html mocha --timeout 25000 test/v3/",
|
||||||
"docs": "./node_modules/.bin/apidoc -i src/routes/v3 -o docs"
|
"docs": "./node_modules/.bin/apidoc -i src/routes/v3 -o docs"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.15.1"
|
"node": ">=10.15.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"apidoc": "^0.20.0",
|
|
||||||
"@chris.troutner/bch-js": "^2.0.0",
|
"@chris.troutner/bch-js": "^2.0.0",
|
||||||
|
"apidoc": "^0.20.0",
|
||||||
"axios": "^0.19.0",
|
"axios": "^0.19.0",
|
||||||
"body-parser": "^1.18.3",
|
"body-parser": "^1.18.3",
|
||||||
"cookie-parser": "~1.4.3",
|
"cookie-parser": "~1.4.3",
|
||||||
@@ -34,6 +34,9 @@
|
|||||||
"express-basic-auth": "^1.1.3",
|
"express-basic-auth": "^1.1.3",
|
||||||
"express-rate-limit": "^5.0.0",
|
"express-rate-limit": "^5.0.0",
|
||||||
"helmet": "^3.21.2",
|
"helmet": "^3.21.2",
|
||||||
|
"ioredis": "^4.14.1",
|
||||||
|
"jsonwebtoken": "^8.5.1",
|
||||||
|
"key-encoder": "^2.0.3",
|
||||||
"level": "^6.0.0",
|
"level": "^6.0.0",
|
||||||
"mkdirp": "^1.0.0",
|
"mkdirp": "^1.0.0",
|
||||||
"mocha": "^7.0.1",
|
"mocha": "^7.0.1",
|
||||||
@@ -44,6 +47,7 @@
|
|||||||
"passport-http": "^0.3.0",
|
"passport-http": "^0.3.0",
|
||||||
"pg": "^7.11.0",
|
"pg": "^7.11.0",
|
||||||
"pg-hstore": "^2.3.2",
|
"pg-hstore": "^2.3.2",
|
||||||
|
"rate-limiter-flexible": "^2.0.0",
|
||||||
"strftime": "^0.10.0",
|
"strftime": "^0.10.0",
|
||||||
"winston": "^3.2.1",
|
"winston": "^3.2.1",
|
||||||
"winston-daily-rotate-file": "^4.0.0"
|
"winston-daily-rotate-file": "^4.0.0"
|
||||||
|
|||||||
+12
-3
@@ -3,7 +3,9 @@
|
|||||||
const express = require("express")
|
const express = require("express")
|
||||||
|
|
||||||
// Middleware
|
// Middleware
|
||||||
const { routeRateLimit } = require("./middleware/route-ratelimit")
|
// const { routeRateLimit } = require("./middleware/route-ratelimit")
|
||||||
|
const RateLimits = require("./middleware/route-ratelimit")
|
||||||
|
const rateLimits = new RateLimits()
|
||||||
|
|
||||||
const path = require("path")
|
const path = require("path")
|
||||||
const logger = require("morgan")
|
const logger = require("morgan")
|
||||||
@@ -22,7 +24,7 @@ const jwtAuth = require("./middleware/jwt-auth")
|
|||||||
|
|
||||||
// v3
|
// v3
|
||||||
const healthCheckV3 = require("./routes/v3/health-check")
|
const healthCheckV3 = require("./routes/v3/health-check")
|
||||||
const blockchainV3 = require("./routes/v3/full-node/blockchain")
|
const BlockchainV3 = require("./routes/v3/full-node/blockchain")
|
||||||
const controlV3 = require("./routes/v3/full-node/control")
|
const controlV3 = require("./routes/v3/full-node/control")
|
||||||
const miningV3 = require("./routes/v3/full-node/mining")
|
const miningV3 = require("./routes/v3/full-node/mining")
|
||||||
const networkV3 = require("./routes/v3/full-node/network")
|
const networkV3 = require("./routes/v3/full-node/network")
|
||||||
@@ -35,6 +37,9 @@ const Ninsight = require("./routes/v3/ninsight")
|
|||||||
|
|
||||||
require("dotenv").config()
|
require("dotenv").config()
|
||||||
|
|
||||||
|
// Instantiate route libraries.
|
||||||
|
const blockchainV3 = new BlockchainV3()
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
|
|
||||||
app.locals.env = process.env
|
app.locals.env = process.env
|
||||||
@@ -78,11 +83,15 @@ const v3prefix = "v3"
|
|||||||
app.use(`/${v3prefix}/`, jwtAuth.getTokenFromHeaders)
|
app.use(`/${v3prefix}/`, jwtAuth.getTokenFromHeaders)
|
||||||
|
|
||||||
// Instantiate the authorization middleware, used to implement pro-tier rate limiting.
|
// Instantiate the authorization middleware, used to implement pro-tier rate limiting.
|
||||||
|
// Handles Anonymous and Basic Authorization schemes used by passport.js
|
||||||
const auth = new AuthMW()
|
const auth = new AuthMW()
|
||||||
app.use(`/${v3prefix}/`, auth.mw())
|
app.use(`/${v3prefix}/`, auth.mw())
|
||||||
|
|
||||||
// Rate limit on all v3 routes
|
// Rate limit on all v3 routes
|
||||||
app.use(`/${v3prefix}/`, routeRateLimit) // Establish and enforce rate limits.
|
// Establish and enforce rate limits.
|
||||||
|
// app.use(`/${v3prefix}/`, rateLimits.routeRateLimit)
|
||||||
|
app.use(`/${v3prefix}/`, rateLimits.rateLimitByResource)
|
||||||
|
|
||||||
app.use(`/${v3prefix}/` + `health-check`, healthCheckV3)
|
app.use(`/${v3prefix}/` + `health-check`, healthCheckV3)
|
||||||
app.use(`/${v3prefix}/` + `blockchain`, blockchainV3.router)
|
app.use(`/${v3prefix}/` + `blockchain`, blockchainV3.router)
|
||||||
app.use(`/${v3prefix}/` + `control`, controlV3.router)
|
app.use(`/${v3prefix}/` + `control`, controlV3.router)
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
|
CT 2/4/20 Note: This library handles anonymous and Basic auth. This library
|
||||||
|
can be phased out with the chage to JWT tokens and the new rate-limit library.
|
||||||
|
|
||||||
Handle authorization for bypassing rate limits.
|
Handle authorization for bypassing rate limits.
|
||||||
|
|
||||||
1) Default is 'Anonymous Authentication', which unlocks the freemimum tier by
|
1) Default is 'Anonymous Authentication', which unlocks the freemimum tier by
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
|
This is a middleware library for handling and processing JWT tokens.
|
||||||
|
|
||||||
This middleware inspects the request header for a JWT token.
|
This middleware inspects the request header for a JWT token.
|
||||||
If found, will populate req.locals.jwtToken with the JWT token.
|
If found, will populate req.locals.jwtToken with the JWT token.
|
||||||
*/
|
*/
|
||||||
|
|||||||
+454
-162
@@ -1,26 +1,39 @@
|
|||||||
/*
|
|
||||||
This file controls the request-per-minute (RPM) rate limits.
|
|
||||||
|
|
||||||
It is assumed that this middleware is run AFTER the jwt-auth.js and auth.js
|
|
||||||
middleware.
|
|
||||||
|
|
||||||
Current rate limiting rules in requests-per-minute:
|
|
||||||
- anonymous access: 3
|
|
||||||
- free access: 10, apiLevel = 0
|
|
||||||
- any paid tier: 100, apiLevel > 0
|
|
||||||
|
|
||||||
If a person signs up for full node access but not indexer access, then the
|
|
||||||
apiLevel will be 10. If they call an endpoint that uses an indexer, the apiLevel
|
|
||||||
will be downgraded to 0 on-the-fly. Indexer endpoints will effectively be
|
|
||||||
downgraded to the anonymous access tier.
|
|
||||||
*/
|
|
||||||
|
|
||||||
"use strict"
|
"use strict"
|
||||||
|
|
||||||
const express = require("express")
|
const express = require("express")
|
||||||
const RateLimit = require("express-rate-limit")
|
const RateLimit = require("express-rate-limit")
|
||||||
const axios = require("axios")
|
const axios = require("axios")
|
||||||
|
|
||||||
|
const wlogger = require("../util/winston-logging")
|
||||||
|
|
||||||
|
const jwt = require("jsonwebtoken")
|
||||||
|
const KeyEncoder = require("key-encoder").default
|
||||||
|
const keyEncoder = new KeyEncoder("secp256k1")
|
||||||
|
|
||||||
|
// Redis
|
||||||
|
const redisOptions = {
|
||||||
|
enableOfflineQueue: false,
|
||||||
|
port: process.env.REDIS_PORT ? process.env.REDIS_PORT : 6379,
|
||||||
|
host: process.env.REDIS_HOST ? process.env.REDIS_HOST : "127.0.0.1"
|
||||||
|
}
|
||||||
|
console.log(`redisOptions: ${JSON.stringify(redisOptions, null, 2)}`)
|
||||||
|
|
||||||
|
const Redis = require("ioredis")
|
||||||
|
const redisClient = new Redis(redisOptions)
|
||||||
|
|
||||||
|
// Rate limiter middleware lib.
|
||||||
|
const { RateLimiterRedis } = require("rate-limiter-flexible")
|
||||||
|
const rateLimitOptions = {
|
||||||
|
storeClient: redisClient,
|
||||||
|
points: 100, // Number of points
|
||||||
|
duration: 1 // Per second
|
||||||
|
}
|
||||||
|
|
||||||
|
// This hard-coded value is temporary. It will be swapped out with an environment
|
||||||
|
// variable when moved to production.
|
||||||
|
const publicKey =
|
||||||
|
"03e6c358092a459f7da9420de770eef3e16cf3c9c54a3d3d14ac2d7f0b82af4d7d"
|
||||||
|
|
||||||
// Set max requests per minute
|
// Set max requests per minute
|
||||||
const maxRequests = process.env.RATE_LIMIT_MAX_REQUESTS
|
const maxRequests = process.env.RATE_LIMIT_MAX_REQUESTS
|
||||||
? parseInt(process.env.RATE_LIMIT_MAX_REQUESTS)
|
? parseInt(process.env.RATE_LIMIT_MAX_REQUESTS)
|
||||||
@@ -32,169 +45,448 @@ const maxRequests = process.env.RATE_LIMIT_MAX_REQUESTS
|
|||||||
// Unique route mapped to its rate limit
|
// Unique route mapped to its rate limit
|
||||||
const uniqueRateLimits = {}
|
const uniqueRateLimits = {}
|
||||||
|
|
||||||
const routeRateLimit = async function(req, res, next) {
|
let _this
|
||||||
// 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.
|
class RateLimits {
|
||||||
if (!req.locals) {
|
constructor() {
|
||||||
req.locals = {
|
_this = this
|
||||||
// default values
|
|
||||||
jwtToken: "",
|
this.jwt = jwt
|
||||||
proLimit: false,
|
this.rateLimiter = new RateLimiterRedis(rateLimitOptions)
|
||||||
apiLevel: 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warn if JWT_AUTH_SERVER env var is not set.
|
// Used to disconnect from the Redis DB.
|
||||||
const authServer = process.env.JWT_AUTH_SERVER
|
// Called by unit tests so that node.js thread doesn't live forever.
|
||||||
if (!authServer || authServer === "") {
|
closeRedis() {
|
||||||
console.warn(
|
redisClient.disconnect()
|
||||||
"JWT_AUTH_SERVER env var is not set. JWT tokens not being evaluated."
|
}
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// If a JWT token is passed in, validate it and enable pro-tier rate limits
|
|
||||||
// if it's valid.
|
|
||||||
if (req.locals.jwtToken) {
|
|
||||||
// console.log(`req.locals.jwtToken: ${req.locals.jwtToken}`)
|
|
||||||
|
|
||||||
// URL for the auth server.
|
// CT 2/7/20: Older rate-limiting code that does not scale well.
|
||||||
const path = `${authServer}apitoken/isvalid/${req.locals.jwtToken}`
|
/*
|
||||||
|
This function controls the tierd request-per-minute (RPM) rate limits.
|
||||||
|
This is an older implementation that is currently not used.
|
||||||
|
|
||||||
// Ask Auth server if the JWT token is valid.
|
It is assumed that this middleware is run AFTER the jwt-auth.js and auth.js
|
||||||
// Get the API level for this user.
|
middleware.
|
||||||
let jwtInfo = await axios.get(path)
|
|
||||||
jwtInfo = jwtInfo.data
|
Current rate limiting rules in requests-per-minute:
|
||||||
|
- anonymous access: 3
|
||||||
|
- free access: 10, apiLevel = 0
|
||||||
|
- any paid tier: 100, apiLevel > 0
|
||||||
|
|
||||||
|
If a person signs up for full node access but not indexer access, then the
|
||||||
|
apiLevel will be 10. If they call an endpoint that uses an indexer, the apiLevel
|
||||||
|
will be downgraded to 0 on-the-fly. Indexer endpoints will effectively be
|
||||||
|
downgraded to the anonymous access tier.
|
||||||
|
*/
|
||||||
|
async routeRateLimit(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 = {
|
||||||
|
// default values
|
||||||
|
jwtToken: "",
|
||||||
|
proLimit: false,
|
||||||
|
apiLevel: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn if JWT_AUTH_SERVER env var is not set.
|
||||||
|
const authServer = process.env.JWT_AUTH_SERVER
|
||||||
|
if (!authServer || authServer === "") {
|
||||||
|
console.warn(
|
||||||
|
"JWT_AUTH_SERVER env var is not set. JWT tokens not being evaluated."
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// If a JWT token is passed in, validate it and enable pro-tier rate limits
|
||||||
|
// if it's valid.
|
||||||
|
if (req.locals.jwtToken) {
|
||||||
|
// console.log(`req.locals.jwtToken: ${req.locals.jwtToken}`)
|
||||||
|
|
||||||
|
// URL for the auth server.
|
||||||
|
const path = `${authServer}apitoken/isvalid/${req.locals.jwtToken}`
|
||||||
|
|
||||||
|
// Ask Auth server if the JWT token is valid.
|
||||||
|
// Get the API level for this user.
|
||||||
|
let jwtInfo = await axios.get(path)
|
||||||
|
jwtInfo = jwtInfo.data
|
||||||
|
// console.log(`jwtInfo: ${JSON.stringify(jwtInfo, null, 2)}`)
|
||||||
|
|
||||||
|
// If JWT if valid, evaluate the API level for the user.
|
||||||
|
if (jwtInfo.isValid) {
|
||||||
|
// Set fine-grain permissions for each user based on the JWT token.
|
||||||
|
const userPermissions = _this.evalUserPermissioins(req, jwtInfo)
|
||||||
|
// console.log(
|
||||||
|
// `userPermissions: ${JSON.stringify(userPermissions, null, 2)}`
|
||||||
|
// )
|
||||||
|
|
||||||
|
req.locals.proLimit = userPermissions.proLimit
|
||||||
|
req.locals.apiLevel = userPermissions.apiLevel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current route
|
||||||
|
const rateLimitTier = req.locals.proLimit ? "PRO" : "BASIC"
|
||||||
|
const path = req.baseUrl + req.path
|
||||||
|
|
||||||
|
// Create a unique string as a route identifier.
|
||||||
|
const route =
|
||||||
|
rateLimitTier +
|
||||||
|
req.method +
|
||||||
|
req.locals.apiLevel + // Generates new rate limit when user upgrades JWT token.
|
||||||
|
path
|
||||||
|
.split("/")
|
||||||
|
.slice(0, 4)
|
||||||
|
.join("/")
|
||||||
|
//console.log(`route identifier: ${JSON.stringify(route, null, 2)}`)
|
||||||
|
|
||||||
|
// console.log(`req.locals: ${JSON.stringify(req.locals, null, 2)}`)
|
||||||
|
|
||||||
|
// This boolean value is passed from the auth.js middleware.
|
||||||
|
const proRateLimits = req.locals.proLimit
|
||||||
|
|
||||||
|
// console.log(`proRateLimits: ${proRateLimits}`)
|
||||||
|
|
||||||
|
// Pro level rate limits
|
||||||
|
if (proRateLimits || proRateLimits === 0) {
|
||||||
|
// TODO: replace the console.logs with calls to our logging system.
|
||||||
|
// console.log(`applying pro-rate limits`)
|
||||||
|
|
||||||
|
let PRO_RPM = 10 // Default value for free tier
|
||||||
|
if (req.locals.apiLevel > 0) PRO_RPM = 100 // RPM for paid tiers.
|
||||||
|
|
||||||
|
// console.log(`PRO_RPM: ${PRO_RPM}, apiLevel: ${req.locals.apiLevel}`)
|
||||||
|
|
||||||
|
// Create new RateLimit if none exists for this route
|
||||||
|
if (!uniqueRateLimits[route]) {
|
||||||
|
uniqueRateLimits[route] = new RateLimit({
|
||||||
|
windowMs: 60 * 1000, // 1 minute window
|
||||||
|
delayMs: 0, // disable delaying - full speed until the max limit is reached
|
||||||
|
max: PRO_RPM, // start blocking after this many requests per minute
|
||||||
|
handler: function(req, res) {
|
||||||
|
//console.log(`pro-tier rate-handler triggered.`)
|
||||||
|
|
||||||
|
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||||
|
return res.json({
|
||||||
|
error: `Too many requests. Limits are ${PRO_RPM} requests per minute. Increase rate limits at https://account.bchjs.cash`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Freemium level rate limits
|
||||||
|
} else {
|
||||||
|
// TODO: replace the console.logs with calls to our logging system.
|
||||||
|
// console.log(`applying freemium limits`)
|
||||||
|
|
||||||
|
// Create new RateLimit if none exists for this route
|
||||||
|
if (!uniqueRateLimits[route]) {
|
||||||
|
uniqueRateLimits[route] = new RateLimit({
|
||||||
|
windowMs: 60 * 1000, // 1 minute window
|
||||||
|
delayMs: 0, // disable delaying - full speed until the max limit is reached
|
||||||
|
max: maxRequests, // start blocking after maxRequests
|
||||||
|
handler: function(req, res) {
|
||||||
|
//console.log(`freemium rate-handler triggered.`)
|
||||||
|
|
||||||
|
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||||
|
return res.json({
|
||||||
|
error: `Too many requests. Your limits are currently ${maxRequests} requests per minute. Increase rate limits at https://account.bchjs.cash`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log(`calling uniqueRateLimits() on this route: ${route}`)
|
||||||
|
|
||||||
|
// Call rate limit for this route
|
||||||
|
uniqueRateLimits[route](req, res, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CT 2/7/20: I believe this is older code that is only used by routeRateLimit.
|
||||||
|
// It will probably be removed in the future.
|
||||||
|
// This function returns an object with proLimit and apiLevel properties.
|
||||||
|
// It does fine-grane analysis on the data coming from the auth servers and
|
||||||
|
// uses its output to adjust rate limits on-the-fly based on the users
|
||||||
|
// permission level.
|
||||||
|
evalUserPermissioins(req, authData) {
|
||||||
|
// console.log(`authData: ${JSON.stringify(authData, null, 2)}`)
|
||||||
|
|
||||||
|
// Return object with default values
|
||||||
|
const retObj = {
|
||||||
|
proLimit: authData.isValid,
|
||||||
|
apiLevel: authData.apiLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
// if apiLevel = 0 (free tier), then return the default values.
|
||||||
|
if (retObj.apiLevel === 0) return retObj
|
||||||
|
|
||||||
|
const level20Routes = ["insight", "bitcore", "blockbook"]
|
||||||
|
|
||||||
|
const locals = req.locals
|
||||||
|
// console.log(`locals: ${JSON.stringify(locals, null, 2)}`)
|
||||||
|
const url = req.url
|
||||||
|
// console.log(`url: ${JSON.stringify(url, null, 2)}`)
|
||||||
|
|
||||||
|
if (authData.apiLevel < 20) {
|
||||||
|
// Loop through the routes that are not accessible to this tier.
|
||||||
|
for (let i = 0; i < level20Routes.length; i++) {
|
||||||
|
// If the requested route is for a higher tier,
|
||||||
|
// revert to anonymous level permissions.
|
||||||
|
if (url.indexOf(level20Routes[i]) > -1) {
|
||||||
|
retObj.proLimit = false
|
||||||
|
retObj.apiLevel = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return retObj
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the new rate limit function that uses the rate-limiter-flexible npm
|
||||||
|
// library. It uses fine-grain rate limiting based on the resources being
|
||||||
|
// consumed.
|
||||||
|
async rateLimitByResource(req, res, next) {
|
||||||
|
try {
|
||||||
|
let userId
|
||||||
|
let decoded = {}
|
||||||
|
|
||||||
|
// Create a res.locals object if not passed in.
|
||||||
|
if (!req.locals) {
|
||||||
|
req.locals = {
|
||||||
|
// default values
|
||||||
|
jwtToken: "",
|
||||||
|
proLimit: false,
|
||||||
|
apiLevel: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the JWT token if one exists.
|
||||||
|
if (req.locals.jwtToken) {
|
||||||
|
const jwtOptions = {
|
||||||
|
algorithms: ["ES256"]
|
||||||
|
}
|
||||||
|
|
||||||
|
const pemPublicKey = keyEncoder.encodePublic(publicKey, "raw", "pem")
|
||||||
|
|
||||||
|
// Validate the JWT token.
|
||||||
|
decoded = _this.jwt.verify(
|
||||||
|
req.locals.jwtToken,
|
||||||
|
pemPublicKey,
|
||||||
|
jwtOptions
|
||||||
|
)
|
||||||
|
// console.log(`decoded: ${JSON.stringify(decoded, null, 2)}`)
|
||||||
|
|
||||||
|
userId = decoded.id
|
||||||
|
} else {
|
||||||
|
wlogger.debug(`No JWT token found!`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code here for the rate limiter is adapted from this example:
|
||||||
|
// https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#authorized-and-not-authorized-users
|
||||||
|
try {
|
||||||
|
// The resource being consumed: full node, indexer, SLPDB, etc.
|
||||||
|
const resource = _this.getResource(req.url)
|
||||||
|
wlogger.debug(`resource: ${resource}`)
|
||||||
|
|
||||||
|
let key = userId ? userId : req.ip
|
||||||
|
|
||||||
|
// const pointsToConsume = userId ? 1 : 30
|
||||||
|
decoded.resource = resource
|
||||||
|
const pointsToConsume = _this.calcPoints(decoded)
|
||||||
|
|
||||||
|
wlogger.info(
|
||||||
|
`User ${key} consuming ${pointsToConsume} point for resource ${resource}.`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Update the key so that rate limits track both the user and the resource.
|
||||||
|
key = `${key}-${resource}`
|
||||||
|
|
||||||
|
await _this.rateLimiter.consume(key, pointsToConsume)
|
||||||
|
} catch (err) {
|
||||||
|
// console.log(`err: `, err)
|
||||||
|
|
||||||
|
// Rate limited was triggered
|
||||||
|
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||||
|
return res.json({
|
||||||
|
error: `Too many requests. Your limits are currently ${maxRequests} requests per minute. Increase rate limits at https://account.bchjs.cash`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
wlogger.error(`Error in route-ratelimit.js/newRateLimit(): `, err)
|
||||||
|
// throw err
|
||||||
|
}
|
||||||
|
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculates the points consumed, based on the jwt information and the route
|
||||||
|
// requested.
|
||||||
|
calcPoints(jwtInfo) {
|
||||||
|
let retVal = 30 // By default, use anonymous tier.
|
||||||
|
|
||||||
|
try {
|
||||||
// console.log(`jwtInfo: ${JSON.stringify(jwtInfo, null, 2)}`)
|
// console.log(`jwtInfo: ${JSON.stringify(jwtInfo, null, 2)}`)
|
||||||
|
|
||||||
// If JWT if valid, evaluate the API level for the user.
|
const apiLevel = jwtInfo.apiLevel
|
||||||
if (jwtInfo.isValid) {
|
const resource = jwtInfo.resource
|
||||||
// Set fine-grain permissions for each user based on the JWT token.
|
|
||||||
const userPermissions = evalUserPermissioins(req, jwtInfo)
|
|
||||||
// console.log(
|
|
||||||
// `userPermissions: ${JSON.stringify(userPermissions, null, 2)}`
|
|
||||||
// )
|
|
||||||
|
|
||||||
req.locals.proLimit = userPermissions.proLimit
|
const level30Routes = ["insight", "bitcore", "blockbook"]
|
||||||
req.locals.apiLevel = userPermissions.apiLevel
|
const level40Routes = ["slp"]
|
||||||
|
|
||||||
|
wlogger.debug(`apiLevel: ${apiLevel}`)
|
||||||
|
|
||||||
|
// Only evaluate if user is using a JWT token.
|
||||||
|
if (jwtInfo.id) {
|
||||||
|
// SLP indexer routes
|
||||||
|
if (level40Routes.includes(resource)) {
|
||||||
|
if (apiLevel >= 40) retVal = 1
|
||||||
|
// else if (apiLevel >= 10) retVal = 10
|
||||||
|
else retVal = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal indexer routes
|
||||||
|
else if (level30Routes.includes(resource)) {
|
||||||
|
if (apiLevel >= 30) retVal = 1
|
||||||
|
else retVal = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
// Full node tier
|
||||||
|
else if (apiLevel >= 20) {
|
||||||
|
retVal = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free tier, full node only.
|
||||||
|
else {
|
||||||
|
retVal = 10
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return retVal
|
||||||
|
} catch (err) {
|
||||||
|
wlogger.error(`Error in route-ratelimit.js/calcPoints()`)
|
||||||
|
// throw err
|
||||||
|
retVal = 30
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function parses the req.url property to identify what resource
|
||||||
|
// the user is requesting.
|
||||||
|
// This was created as a function so that it can be unit tested. Not sure
|
||||||
|
// what kind of variations will be seen in production.
|
||||||
|
getResource(url) {
|
||||||
|
try {
|
||||||
|
wlogger.debug(`url: ${JSON.stringify(url, null, 2)}`)
|
||||||
|
|
||||||
|
const splitUrl = url.split("/")
|
||||||
|
const resource = splitUrl[1]
|
||||||
|
|
||||||
|
return resource
|
||||||
|
} catch (err) {
|
||||||
|
wlogger.error(`Error in getResource().`)
|
||||||
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Current route
|
// This is a variation of rateLimitByResource() function. This version will
|
||||||
const rateLimitTier = req.locals.proLimit ? "PRO" : "BASIC"
|
// potentially be used by Bitcoin.com.
|
||||||
const path = req.baseUrl + req.path
|
// Rather than using apiLevel, the rateLimit is explicitly recorded in the
|
||||||
|
// JWT token.
|
||||||
|
async rateLimitSimple(req, res, next) {
|
||||||
|
try {
|
||||||
|
let userId
|
||||||
|
let decoded = {}
|
||||||
|
|
||||||
// Create a unique string as a route identifier.
|
// Create a res.locals object if not passed in.
|
||||||
const route =
|
if (!req.locals) {
|
||||||
rateLimitTier +
|
req.locals = {
|
||||||
req.method +
|
// default values
|
||||||
req.locals.apiLevel + // Generates new rate limit when user upgrades JWT token.
|
jwtToken: "",
|
||||||
path
|
proLimit: false,
|
||||||
.split("/")
|
rateLimit: 3
|
||||||
.slice(0, 4)
|
|
||||||
.join("/")
|
|
||||||
//console.log(`route identifier: ${JSON.stringify(route, null, 2)}`)
|
|
||||||
|
|
||||||
// console.log(`req.locals: ${JSON.stringify(req.locals, null, 2)}`)
|
|
||||||
|
|
||||||
// This boolean value is passed from the auth.js middleware.
|
|
||||||
const proRateLimits = req.locals.proLimit
|
|
||||||
|
|
||||||
// console.log(`proRateLimits: ${proRateLimits}`)
|
|
||||||
|
|
||||||
// Pro level rate limits
|
|
||||||
if (proRateLimits || proRateLimits === 0) {
|
|
||||||
// TODO: replace the console.logs with calls to our logging system.
|
|
||||||
// console.log(`applying pro-rate limits`)
|
|
||||||
|
|
||||||
let PRO_RPM = 10 // Default value for free tier
|
|
||||||
if (req.locals.apiLevel > 0) PRO_RPM = 100 // RPM for paid tiers.
|
|
||||||
|
|
||||||
// console.log(`PRO_RPM: ${PRO_RPM}, apiLevel: ${req.locals.apiLevel}`)
|
|
||||||
|
|
||||||
// Create new RateLimit if none exists for this route
|
|
||||||
if (!uniqueRateLimits[route]) {
|
|
||||||
uniqueRateLimits[route] = new RateLimit({
|
|
||||||
windowMs: 60 * 1000, // 1 minute window
|
|
||||||
delayMs: 0, // disable delaying - full speed until the max limit is reached
|
|
||||||
max: PRO_RPM, // start blocking after this many requests per minute
|
|
||||||
handler: function(req, res) {
|
|
||||||
//console.log(`pro-tier rate-handler triggered.`)
|
|
||||||
|
|
||||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
|
||||||
return res.json({
|
|
||||||
error: `Too many requests. Limits are ${PRO_RPM} requests per minute. Increase rate limits at https://account.bchjs.cash`
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
|
// Decode the JWT token if one exists.
|
||||||
|
if (req.locals.jwtToken) {
|
||||||
|
const jwtOptions = {
|
||||||
|
algorithms: ["ES256"]
|
||||||
|
}
|
||||||
|
|
||||||
|
const pemPublicKey = keyEncoder.encodePublic(publicKey, "raw", "pem")
|
||||||
|
|
||||||
|
// Validate the JWT token.
|
||||||
|
decoded = _this.jwt.verify(
|
||||||
|
req.locals.jwtToken,
|
||||||
|
pemPublicKey,
|
||||||
|
jwtOptions
|
||||||
|
)
|
||||||
|
// console.log(`decoded: ${JSON.stringify(decoded, null, 2)}`)
|
||||||
|
|
||||||
|
userId = decoded.id
|
||||||
|
} else {
|
||||||
|
wlogger.debug(`No JWT token found!`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code here for the rate limiter is adapted from this example:
|
||||||
|
// https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#authorized-and-not-authorized-users
|
||||||
|
try {
|
||||||
|
// Key for Redis key/value pair.
|
||||||
|
const key = userId ? userId : req.ip
|
||||||
|
|
||||||
|
const pointsToConsume = _this.calcPoints2(decoded)
|
||||||
|
|
||||||
|
wlogger.debug(`User ${key} consuming ${pointsToConsume}.`)
|
||||||
|
|
||||||
|
await _this.rateLimiter.consume(key, pointsToConsume)
|
||||||
|
} catch (err) {
|
||||||
|
// console.log(`err: `, err)
|
||||||
|
|
||||||
|
// Rate limited was triggered
|
||||||
|
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
||||||
|
return res.json({
|
||||||
|
error: `Too many requests. Your limits are currently ${maxRequests} requests per minute. Increase rate limits at https://account.bchjs.cash`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
wlogger.error(`Error in route-ratelimit.js/rateLimitSimple(): `, err)
|
||||||
|
// throw err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Freemium level rate limits
|
next()
|
||||||
} else {
|
|
||||||
// TODO: replace the console.logs with calls to our logging system.
|
|
||||||
// console.log(`applying freemium limits`)
|
|
||||||
|
|
||||||
// Create new RateLimit if none exists for this route
|
|
||||||
if (!uniqueRateLimits[route]) {
|
|
||||||
uniqueRateLimits[route] = new RateLimit({
|
|
||||||
windowMs: 60 * 1000, // 1 minute window
|
|
||||||
delayMs: 0, // disable delaying - full speed until the max limit is reached
|
|
||||||
max: maxRequests, // start blocking after maxRequests
|
|
||||||
handler: function(req, res) {
|
|
||||||
//console.log(`freemium rate-handler triggered.`)
|
|
||||||
|
|
||||||
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
|
|
||||||
return res.json({
|
|
||||||
error: `Too many requests. Your limits are currently ${maxRequests} requests per minute. Increase rate limits at https://account.bchjs.cash`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//console.log(`calling uniqueRateLimits() on this route: ${route}`)
|
// Calculates the points consumed, based on the explicit rateLimit defined
|
||||||
|
// in the JWT token.
|
||||||
|
calcPoints2(jwtInfo) {
|
||||||
|
let retVal = 30 // By default, use anonymous tier.
|
||||||
|
|
||||||
// Call rate limit for this route
|
try {
|
||||||
uniqueRateLimits[route](req, res, next)
|
// console.log(`jwtInfo: ${JSON.stringify(jwtInfo, null, 2)}`)
|
||||||
|
|
||||||
|
const MAX_RATE_LIMIT = 100
|
||||||
|
|
||||||
|
const rateLimit = jwtInfo.rateLimit
|
||||||
|
|
||||||
|
// Only evaluate if user is using a JWT token.
|
||||||
|
if (jwtInfo.id) {
|
||||||
|
const points = Math.floor(MAX_RATE_LIMIT / rateLimit)
|
||||||
|
|
||||||
|
retVal = points
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
wlogger.error(`Error in route-ratelimit.js/calcPoints2()`)
|
||||||
|
// throw err
|
||||||
|
retVal = 30
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function returns the an object with proLimit and apiLevel properties.
|
module.exports = RateLimits
|
||||||
// It does fine-grane analysis on the data coming from the auth servers and
|
|
||||||
// uses its output to adjust rate limits on-the-fly based on the users
|
|
||||||
// permission level.
|
|
||||||
function evalUserPermissioins(req, authData) {
|
|
||||||
// console.log(`authData: ${JSON.stringify(authData, null, 2)}`)
|
|
||||||
|
|
||||||
// Return object with default values
|
|
||||||
const retObj = {
|
|
||||||
proLimit: authData.isValid,
|
|
||||||
apiLevel: authData.apiLevel
|
|
||||||
}
|
|
||||||
|
|
||||||
// if apiLevel = 0 (free tier), then return the default values.
|
|
||||||
if (retObj.apiLevel === 0) return retObj
|
|
||||||
|
|
||||||
const level20Routes = ["insight", "bitcore", "blockbook"]
|
|
||||||
|
|
||||||
const locals = req.locals
|
|
||||||
// console.log(`locals: ${JSON.stringify(locals, null, 2)}`)
|
|
||||||
const url = req.url
|
|
||||||
// console.log(`url: ${JSON.stringify(url, null, 2)}`)
|
|
||||||
|
|
||||||
if (authData.apiLevel < 20) {
|
|
||||||
// Loop through the routes that are not accessible to this tier.
|
|
||||||
for (let i = 0; i < level20Routes.length; i++) {
|
|
||||||
// If the requested route is for a higher tier,
|
|
||||||
// revert to anonymous level permissions.
|
|
||||||
if (url.indexOf(level20Routes[i]) > -1) {
|
|
||||||
retObj.proLimit = false
|
|
||||||
retObj.apiLevel = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return retObj
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { routeRateLimit }
|
|
||||||
|
|||||||
+827
-1094
File diff suppressed because it is too large
Load Diff
@@ -1,133 +0,0 @@
|
|||||||
/*
|
|
||||||
A library for interacting with the Full Node
|
|
||||||
*/
|
|
||||||
|
|
||||||
"use strict"
|
|
||||||
|
|
||||||
const express = require("express")
|
|
||||||
const router = express.Router()
|
|
||||||
|
|
||||||
const axios = require("axios")
|
|
||||||
const wlogger = require("../../../util/winston-logging")
|
|
||||||
|
|
||||||
const RouteUtils = require("../route-utils2")
|
|
||||||
const routeUtils = new RouteUtils()
|
|
||||||
|
|
||||||
// Used to convert error messages to strings, to safely pass to users.
|
|
||||||
const util = require("util")
|
|
||||||
util.inspect.defaultOptions = { depth: 1 }
|
|
||||||
|
|
||||||
const BCHJS = require("@chris.troutner/bch-js")
|
|
||||||
const bchjs = new BCHJS()
|
|
||||||
|
|
||||||
let _this
|
|
||||||
|
|
||||||
class Blockchain {
|
|
||||||
constructor() {
|
|
||||||
_this = this
|
|
||||||
|
|
||||||
this.bchjs = bchjs
|
|
||||||
this.axios = axios
|
|
||||||
this.routeUtils = routeUtils
|
|
||||||
|
|
||||||
this.router = router
|
|
||||||
this.router.get("/", this.root)
|
|
||||||
this.router.get("/getBestBlockHash", this.getBestBlockHash)
|
|
||||||
this.router.get("/getBlockchainInfo", this.getBlockchainInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
root(req, res, next) {
|
|
||||||
return res.json({ status: "blockchain" })
|
|
||||||
}
|
|
||||||
|
|
||||||
// DRY error handler.
|
|
||||||
errorHandler(err, res) {
|
|
||||||
// Attempt to decode the error message.
|
|
||||||
const { msg, status } = this.routeUtils.decodeError(err)
|
|
||||||
if (msg) {
|
|
||||||
res.status(status)
|
|
||||||
return res.json({ error: msg })
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(500)
|
|
||||||
return res.json({ error: util.inspect(err) })
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @api {get} /blockchain/getBestBlockHash Get best block hash
|
|
||||||
* @apiName GetBestBlockHash
|
|
||||||
* @apiGroup Blockchain
|
|
||||||
* @apiDescription Returns the hash of the best (tip) block in the longest
|
|
||||||
* block chain.
|
|
||||||
*
|
|
||||||
* @apiExample curl Example:
|
|
||||||
* curl -X GET "https://api.fullstack.cash/v3/blockchain/getBestBlockHash" -H "accept: application/json"
|
|
||||||
*
|
|
||||||
* @apiSuccess {String} bestBlockHash 000000000000000002bc884334336d99c9a9c616670a9244c6a8c1fc35aa91a1
|
|
||||||
*/
|
|
||||||
async getBestBlockHash(req, res, next) {
|
|
||||||
try {
|
|
||||||
// Axios options
|
|
||||||
const options = this.routeUtils.getAxiosOptions()
|
|
||||||
options.data.id = "getbestblockhash"
|
|
||||||
options.data.method = "getbestblockhash"
|
|
||||||
options.data.params = []
|
|
||||||
|
|
||||||
const response = await this.axios.request(options)
|
|
||||||
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
|
|
||||||
|
|
||||||
return res.json(response.data.result)
|
|
||||||
} catch (err) {
|
|
||||||
// Write out error to error log.
|
|
||||||
wlogger.error(`Error in blockchain.ts/getBestBlockHash().`, err)
|
|
||||||
|
|
||||||
return this.errorHandler(err, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @api {get} /blockchain/getBlockchainInfo Get blockchain info
|
|
||||||
* @apiName GetBlockchainInfo
|
|
||||||
* @apiGroup Blockchain
|
|
||||||
* @apiDescription Returns an object containing various state info regarding blockchain processing.
|
|
||||||
*
|
|
||||||
* @apiExample Example usage:
|
|
||||||
* curl -X GET "https://api.fullstack.cash/v3/blockchain/getBlockchainInfo" -H "accept: application/json"
|
|
||||||
*
|
|
||||||
* @apiSuccess {Object} object Object containing data
|
|
||||||
* @apiSuccess {String} object.chain "main"
|
|
||||||
* @apiSuccess {Number} object.blocks 561838
|
|
||||||
* @apiSuccess {Number} object.headers 561838
|
|
||||||
* @apiSuccess {String} object.bestblockhash "000000000000000002307dd38cd01c7308b8febfcdf5772cf087b5bb023d55bc"
|
|
||||||
* @apiSuccess {Number} object.difficulty 246585566638.1496
|
|
||||||
* @apiSuccess {String} object.mediantime 1545402693
|
|
||||||
* @apiSuccess {Number} object.verificationprogress 0.999998831622689
|
|
||||||
* @apiSuccess {Boolean} object.chainwork "000000000000000000000000000000000000000000d8c09a8ab7262080266b3e"
|
|
||||||
* @apiSuccess {Number} object.pruned false
|
|
||||||
* @apiSuccess {Array} object.softforks Array of objects
|
|
||||||
* @apiSuccess {String} object.softforks.id "bip34"
|
|
||||||
* @apiSuccess {String} object.softforks.version 2
|
|
||||||
* @apiSuccess {Object} object.softforks.reject
|
|
||||||
* @apiSuccess {String} object.softforks.reject.status true
|
|
||||||
*/
|
|
||||||
async getBlockchainInfo(req, res, next) {
|
|
||||||
try {
|
|
||||||
// Axios options
|
|
||||||
const options = this.routeUtils.getAxiosOptions()
|
|
||||||
options.data.id = "getblockchaininfo"
|
|
||||||
options.data.method = "getblockchaininfo"
|
|
||||||
options.data.params = []
|
|
||||||
|
|
||||||
const response = await this.axios.request(options)
|
|
||||||
|
|
||||||
return res.json(response.data.result)
|
|
||||||
} catch (err) {
|
|
||||||
// Write out error to error log.
|
|
||||||
wlogger.error(`Error in blockchain.ts/getBlockchainInfo().`, err)
|
|
||||||
|
|
||||||
return this.errorHandler(err, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Blockchain
|
|
||||||
+757
-273
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,7 @@ util.inspect.defaultOptions = { depth: 1 }
|
|||||||
const SERVER = `http://localhost:3000/v3/`
|
const SERVER = `http://localhost:3000/v3/`
|
||||||
|
|
||||||
const TEST_JWT =
|
const TEST_JWT =
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVkYTc5ZDk4OTYyMjRjNjM2MmQwYzkwMiIsImlhdCI6MTU3MTUzOTU1MSwiZXhwIjoxNTc0MTMxNTUxfQ.PfPW_Z2NYT1O2zUHXopcz2aLGHSGudaKOIGnt7SuAi4"
|
"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVlM2EwNDE1ZWIyOWE5NjJkYTI3MDhiNCIsImFwaUxldmVsIjowLCJyYXRlTGltaXQiOjEwLCJpYXQiOjE1ODA4NjA0NjcsImV4cCI6MTU4MzQ1MjQ2N30.fuY5S-YrF0J11h5uyMjPe7wiVkYRnIyXi4dL9-V-C6pLJm33p0dSq_pSheVVWw78n5kAvL_9kFHngbnmQiOJYQ"
|
||||||
|
|
||||||
describe("#rate limits", () => {
|
describe("#rate limits", () => {
|
||||||
it("should get control/getNetworkInfo() with no auth", async () => {
|
it("should get control/getNetworkInfo() with no auth", async () => {
|
||||||
|
|||||||
+575
-88
@@ -3,6 +3,7 @@
|
|||||||
const chai = require("chai")
|
const chai = require("chai")
|
||||||
const assert = chai.assert
|
const assert = chai.assert
|
||||||
const nock = require("nock") // HTTP mocking
|
const nock = require("nock") // HTTP mocking
|
||||||
|
const sinon = require("sinon")
|
||||||
|
|
||||||
// Used for debugging.
|
// Used for debugging.
|
||||||
const util = require("util")
|
const util = require("util")
|
||||||
@@ -12,7 +13,10 @@ util.inspect.defaultOptions = { depth: 1 }
|
|||||||
const { mockReq, mockRes, mockNext } = require("./mocks/express-mocks")
|
const { mockReq, mockRes, mockNext } = require("./mocks/express-mocks")
|
||||||
|
|
||||||
// Libraries under test
|
// Libraries under test
|
||||||
let rateLimitMiddleware = require("../../src/middleware/route-ratelimit")
|
const RateLimits = require("../../src/middleware/route-ratelimit")
|
||||||
|
let rateLimits = new RateLimits()
|
||||||
|
let rateLimitMiddleware = rateLimits.routeRateLimit
|
||||||
|
|
||||||
const controlRoute = require("../../src/routes/v3/full-node/control")
|
const controlRoute = require("../../src/routes/v3/full-node/control")
|
||||||
const jwtAuth = require("../../src/middleware/jwt-auth")
|
const jwtAuth = require("../../src/middleware/jwt-auth")
|
||||||
|
|
||||||
@@ -23,6 +27,8 @@ let originalEnvVars // Used during transition from integration to unit tests.
|
|||||||
const jwt = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVkYWRlM2Y1NzM5ZTZjMGZmMDM0YjlhMSIsImlhdCI6MTU3MTY3NzQ1MCwiZXhwIjoxNTc0MjY5NDUwfQ.SSz7F7ETyBB3eoNG2VKCzPOhddtB-vrtmEoj7PxicrQ`
|
const jwt = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVkYWRlM2Y1NzM5ZTZjMGZmMDM0YjlhMSIsImlhdCI6MTU3MTY3NzQ1MCwiZXhwIjoxNTc0MjY5NDUwfQ.SSz7F7ETyBB3eoNG2VKCzPOhddtB-vrtmEoj7PxicrQ`
|
||||||
|
|
||||||
describe("#route-ratelimits & jwt-auth", () => {
|
describe("#route-ratelimits & jwt-auth", () => {
|
||||||
|
let sandbox
|
||||||
|
|
||||||
before(() => {
|
before(() => {
|
||||||
// Save existing environment variables.
|
// Save existing environment variables.
|
||||||
originalEnvVars = {
|
originalEnvVars = {
|
||||||
@@ -47,6 +53,17 @@ describe("#route-ratelimits & jwt-auth", () => {
|
|||||||
req.params = {}
|
req.params = {}
|
||||||
req.body = {}
|
req.body = {}
|
||||||
req.query = {}
|
req.query = {}
|
||||||
|
req.locals = {}
|
||||||
|
|
||||||
|
sandbox = sinon.createSandbox()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
sandbox.restore()
|
||||||
|
})
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
rateLimits.closeRedis()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("#jwt-auth.js", () => {
|
describe("#jwt-auth.js", () => {
|
||||||
@@ -71,83 +88,10 @@ describe("#route-ratelimits & jwt-auth", () => {
|
|||||||
assert.equal(req.locals.jwtToken, jwt)
|
assert.equal(req.locals.jwtToken, jwt)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: This code has been refactored and these unit tests no longer apply.
|
|
||||||
// I think I forgot to create new unit tests to reflect the change in code though.
|
|
||||||
/*
|
|
||||||
describe("#routeAccess", () => {
|
|
||||||
it("should do nothing if req.locals.jwtToken is undefined", () => {
|
|
||||||
// Initialize req.locals
|
|
||||||
req.locals = {
|
|
||||||
proLimit: false,
|
|
||||||
apiLevel: 0
|
|
||||||
}
|
|
||||||
req.url = "/insight/address/details"
|
|
||||||
|
|
||||||
// Reset the history of the stub and assert it has not been called.
|
|
||||||
res.status.resetHistory()
|
|
||||||
assert.equal(res.status.called, false, "stub history reset")
|
|
||||||
|
|
||||||
jwtAuth.routeAccess(req, res, next)
|
|
||||||
|
|
||||||
// console.log(`req.locals: ${JSON.stringify(req.locals, null, 2)}`)
|
|
||||||
assert.equal(
|
|
||||||
res.status.called,
|
|
||||||
false,
|
|
||||||
"stub should NOT have been called."
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should throw error if full-node tier tries to access indexer", () => {
|
|
||||||
// Initialize req.locals
|
|
||||||
req.locals = {
|
|
||||||
proLimit: true,
|
|
||||||
apiLevel: 10,
|
|
||||||
jwtToken: jwt
|
|
||||||
}
|
|
||||||
req.url = "/insight/address/details"
|
|
||||||
|
|
||||||
try {
|
|
||||||
res.status.resetHistory()
|
|
||||||
assert.equal(res.status.called, false, "stub history reset")
|
|
||||||
|
|
||||||
jwtAuth.routeAccess(req, res, next)
|
|
||||||
|
|
||||||
assert.equal(res.status.called, true, "stub should have been called.")
|
|
||||||
} catch (err) {
|
|
||||||
console.log(`caught error: `, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should allow indexer tier tries access indexer endpoints", () => {
|
|
||||||
// Initialize req.locals
|
|
||||||
req.locals = {
|
|
||||||
proLimit: true,
|
|
||||||
apiLevel: 20,
|
|
||||||
jwtToken: jwt
|
|
||||||
}
|
|
||||||
req.url = "/insight/address/details"
|
|
||||||
|
|
||||||
try {
|
|
||||||
res.status.resetHistory()
|
|
||||||
assert.equal(res.status.called, false, "stub history reset")
|
|
||||||
|
|
||||||
jwtAuth.routeAccess(req, res, next)
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
res.status.called,
|
|
||||||
false,
|
|
||||||
"stub should NOT have been called."
|
|
||||||
)
|
|
||||||
} catch (err) {
|
|
||||||
console.log(`caught error: `, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
*/
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("#routeRateLimit", () => {
|
describe("#routeRateLimit", () => {
|
||||||
|
rateLimitMiddleware = new RateLimits()
|
||||||
let routeRateLimit = rateLimitMiddleware.routeRateLimit
|
let routeRateLimit = rateLimitMiddleware.routeRateLimit
|
||||||
const getInfo = controlRoute.testableComponents.getInfo
|
const getInfo = controlRoute.testableComponents.getInfo
|
||||||
|
|
||||||
@@ -186,10 +130,11 @@ describe("#route-ratelimits & jwt-auth", () => {
|
|||||||
|
|
||||||
it("should NOT trigger rate-limit for free-tier at 5 RPM", async () => {
|
it("should NOT trigger rate-limit for free-tier at 5 RPM", async () => {
|
||||||
// Clear the require cache before running this test.
|
// Clear the require cache before running this test.
|
||||||
delete require.cache[
|
// delete require.cache[
|
||||||
require.resolve("../../src/middleware/route-ratelimit")
|
// require.resolve("../../src/middleware/route-ratelimit")
|
||||||
]
|
// ]
|
||||||
rateLimitMiddleware = require("../../src/middleware/route-ratelimit")
|
// rateLimitMiddleware = require("../../src/middleware/route-ratelimit")
|
||||||
|
rateLimitMiddleware = new RateLimits()
|
||||||
routeRateLimit = rateLimitMiddleware.routeRateLimit
|
routeRateLimit = rateLimitMiddleware.routeRateLimit
|
||||||
|
|
||||||
req.baseUrl = "/v3"
|
req.baseUrl = "/v3"
|
||||||
@@ -216,7 +161,7 @@ describe("#route-ratelimits & jwt-auth", () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should trigger rate-limit for free tier 10 RPM", async () => {
|
it("should trigger rate-limit for free tier after 10 RPM", async () => {
|
||||||
req.baseUrl = "/v3"
|
req.baseUrl = "/v3"
|
||||||
req.path = "/control/getNetworkInfo"
|
req.path = "/control/getNetworkInfo"
|
||||||
req.method = "GET"
|
req.method = "GET"
|
||||||
@@ -241,10 +186,11 @@ describe("#route-ratelimits & jwt-auth", () => {
|
|||||||
|
|
||||||
it("should NOT trigger rate-limit handler for pro-tier at 25 RPM", async () => {
|
it("should NOT trigger rate-limit handler for pro-tier at 25 RPM", async () => {
|
||||||
// Clear the require cache before running this test.
|
// Clear the require cache before running this test.
|
||||||
delete require.cache[
|
// delete require.cache[
|
||||||
require.resolve("../../src/middleware/route-ratelimit")
|
// require.resolve("../../src/middleware/route-ratelimit")
|
||||||
]
|
// ]
|
||||||
rateLimitMiddleware = require("../../src/middleware/route-ratelimit")
|
// rateLimitMiddleware = require("../../src/middleware/route-ratelimit")
|
||||||
|
rateLimitMiddleware = new RateLimits()
|
||||||
routeRateLimit = rateLimitMiddleware.routeRateLimit
|
routeRateLimit = rateLimitMiddleware.routeRateLimit
|
||||||
|
|
||||||
req.baseUrl = "/v3"
|
req.baseUrl = "/v3"
|
||||||
@@ -273,10 +219,11 @@ describe("#route-ratelimits & jwt-auth", () => {
|
|||||||
|
|
||||||
it("rate-limiting should still kick in at a higher RPM for pro-tier", async () => {
|
it("rate-limiting should still kick in at a higher RPM for pro-tier", async () => {
|
||||||
// Clear the require cache before running this test.
|
// Clear the require cache before running this test.
|
||||||
delete require.cache[
|
// delete require.cache[
|
||||||
require.resolve("../../src/middleware/route-ratelimit")
|
// require.resolve("../../src/middleware/route-ratelimit")
|
||||||
]
|
// ]
|
||||||
rateLimitMiddleware = require("../../src/middleware/route-ratelimit")
|
// rateLimitMiddleware = require("../../src/middleware/route-ratelimit")
|
||||||
|
rateLimitMiddleware = new RateLimits()
|
||||||
routeRateLimit = rateLimitMiddleware.routeRateLimit
|
routeRateLimit = rateLimitMiddleware.routeRateLimit
|
||||||
|
|
||||||
req.baseUrl = "/v3"
|
req.baseUrl = "/v3"
|
||||||
@@ -303,6 +250,546 @@ describe("#route-ratelimits & jwt-auth", () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("#getResource", () => {
|
||||||
|
it("should decode a blockchain request", () => {
|
||||||
|
const url =
|
||||||
|
"/blockchain/getTxOut/62a3ea958a463a372bc0caf2c374a7f60be9c624be63a0db8db78f05809df6d8/0?include_mempool=true"
|
||||||
|
|
||||||
|
const result = rateLimits.getResource(url)
|
||||||
|
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||||
|
|
||||||
|
assert.equal(result, "blockchain")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#calcPoints", () => {
|
||||||
|
it("should return 30 points for anonymous user", () => {
|
||||||
|
const result = rateLimits.calcPoints()
|
||||||
|
// console.log(`result: ${result}`)
|
||||||
|
|
||||||
|
assert.equal(result, 30)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return 10 points for free tier requesting full node access", () => {
|
||||||
|
const jwtInfo = {
|
||||||
|
apiLevel: 10,
|
||||||
|
resource: "blockchain",
|
||||||
|
id: "5e3a0415eb29a962da2708b4"
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = rateLimits.calcPoints(jwtInfo)
|
||||||
|
assert.equal(result, 10)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return 10 points for free tier requesting indexer access", () => {
|
||||||
|
const jwtInfo = {
|
||||||
|
apiLevel: 10,
|
||||||
|
resource: "blockbook",
|
||||||
|
id: "5e3a0415eb29a962da2708b4"
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = rateLimits.calcPoints(jwtInfo)
|
||||||
|
assert.equal(result, 10)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return 10 points for free tier requesting SLPDB access", () => {
|
||||||
|
const jwtInfo = {
|
||||||
|
apiLevel: 10,
|
||||||
|
resource: "slp",
|
||||||
|
id: "5e3a0415eb29a962da2708b4"
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = rateLimits.calcPoints(jwtInfo)
|
||||||
|
assert.equal(result, 10)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return 1 point for full node tier requesting full node access", () => {
|
||||||
|
const jwtInfo = {
|
||||||
|
apiLevel: 20,
|
||||||
|
resource: "blockchain",
|
||||||
|
id: "5e3a0415eb29a962da2708b4"
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = rateLimits.calcPoints(jwtInfo)
|
||||||
|
assert.equal(result, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return 10 points for full-node tier requesting indexer access", () => {
|
||||||
|
const jwtInfo = {
|
||||||
|
apiLevel: 20,
|
||||||
|
resource: "blockbook",
|
||||||
|
id: "5e3a0415eb29a962da2708b4"
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = rateLimits.calcPoints(jwtInfo)
|
||||||
|
assert.equal(result, 10)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return 10 points for full node tier requesting SLPDB access", () => {
|
||||||
|
const jwtInfo = {
|
||||||
|
apiLevel: 20,
|
||||||
|
resource: "slp",
|
||||||
|
id: "5e3a0415eb29a962da2708b4"
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = rateLimits.calcPoints(jwtInfo)
|
||||||
|
assert.equal(result, 10)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return 1 point for indexer tier requesting full node access", () => {
|
||||||
|
const jwtInfo = {
|
||||||
|
apiLevel: 30,
|
||||||
|
resource: "blockchain",
|
||||||
|
id: "5e3a0415eb29a962da2708b4"
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = rateLimits.calcPoints(jwtInfo)
|
||||||
|
assert.equal(result, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return 1 points for indexer tier requesting indexer access", () => {
|
||||||
|
const jwtInfo = {
|
||||||
|
apiLevel: 30,
|
||||||
|
resource: "blockbook",
|
||||||
|
id: "5e3a0415eb29a962da2708b4"
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = rateLimits.calcPoints(jwtInfo)
|
||||||
|
assert.equal(result, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return 10 points for indexer tier requesting SLPDB access", () => {
|
||||||
|
const jwtInfo = {
|
||||||
|
apiLevel: 30,
|
||||||
|
resource: "slp",
|
||||||
|
id: "5e3a0415eb29a962da2708b4"
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = rateLimits.calcPoints(jwtInfo)
|
||||||
|
assert.equal(result, 10)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return 1 point for SLP tier requesting full node access", () => {
|
||||||
|
const jwtInfo = {
|
||||||
|
apiLevel: 40,
|
||||||
|
resource: "blockchain",
|
||||||
|
id: "5e3a0415eb29a962da2708b4"
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = rateLimits.calcPoints(jwtInfo)
|
||||||
|
assert.equal(result, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return 1 points for SLP tier requesting indexer access", () => {
|
||||||
|
const jwtInfo = {
|
||||||
|
apiLevel: 40,
|
||||||
|
resource: "blockbook",
|
||||||
|
id: "5e3a0415eb29a962da2708b4"
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = rateLimits.calcPoints(jwtInfo)
|
||||||
|
assert.equal(result, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return 1 points for SLP tier requesting SLPDB access", () => {
|
||||||
|
const jwtInfo = {
|
||||||
|
apiLevel: 40,
|
||||||
|
resource: "slp",
|
||||||
|
id: "5e3a0415eb29a962da2708b4"
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = rateLimits.calcPoints(jwtInfo)
|
||||||
|
assert.equal(result, 1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#calcPoints2", () => {
|
||||||
|
it("should return 30 points for anonymous user", () => {
|
||||||
|
const result = rateLimits.calcPoints2({})
|
||||||
|
// console.log(`result: ${result}`)
|
||||||
|
|
||||||
|
assert.equal(result, 30)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return 10 point for free tier", () => {
|
||||||
|
const jwtInfo = {
|
||||||
|
rateLimit: 10,
|
||||||
|
id: "5e3a0415eb29a962da2708b4"
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = rateLimits.calcPoints2(jwtInfo)
|
||||||
|
assert.equal(result, 10)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return 1 point for pro tier", () => {
|
||||||
|
const jwtInfo = {
|
||||||
|
rateLimit: 100,
|
||||||
|
id: "5e3a0415eb29a962da2708b4"
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = rateLimits.calcPoints2(jwtInfo)
|
||||||
|
assert.equal(result, 1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#rateLimitByResource", () => {
|
||||||
|
it("should pass through rate-limit middleware", async () => {
|
||||||
|
req.baseUrl = "/v3"
|
||||||
|
req.path = "/control/getNetworkInfo"
|
||||||
|
req.url = req.path
|
||||||
|
req.method = "GET"
|
||||||
|
|
||||||
|
// Call the route twice to trigger the rate handling.
|
||||||
|
await rateLimits.rateLimitByResource(req, res, next)
|
||||||
|
await rateLimits.rateLimitByResource(req, res, next)
|
||||||
|
|
||||||
|
// next() will be called if rate-limit is not triggered
|
||||||
|
assert.equal(next.called, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should trigger rate-limit handler if rate limits exceeds 5 request per minute", async () => {
|
||||||
|
req.baseUrl = "/v3"
|
||||||
|
req.path = "/control/getNetworkInfo"
|
||||||
|
req.url = req.path
|
||||||
|
req.method = "GET"
|
||||||
|
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
next.reset() // reset the stubbed next() function.
|
||||||
|
|
||||||
|
await rateLimits.rateLimitByResource(req, res, next)
|
||||||
|
//console.log(`next() called: ${next.called}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: next() will be called unless the rate-limit kicks in.
|
||||||
|
assert.equal(
|
||||||
|
next.called,
|
||||||
|
false,
|
||||||
|
`next should not be called if rate limit was triggered.`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should NOT trigger rate-limit for free-tier at 5 RPM", async () => {
|
||||||
|
// Create a new instance of the rate limit so we start with zeroed tracking.
|
||||||
|
rateLimits = new RateLimits()
|
||||||
|
|
||||||
|
req.baseUrl = "/v3"
|
||||||
|
req.path = "/control/getNetworkInfo"
|
||||||
|
req.url = req.path
|
||||||
|
req.method = "GET"
|
||||||
|
|
||||||
|
req.locals.jwtToken = "some-token"
|
||||||
|
|
||||||
|
const jwtInfo = {
|
||||||
|
apiLevel: 10,
|
||||||
|
id: "5e3a0415eb29a962da2708b1"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock the call to the jwt library.
|
||||||
|
sandbox.stub(rateLimits.jwt, "verify").returns(jwtInfo)
|
||||||
|
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
next.reset() // reset the stubbed next() function.
|
||||||
|
|
||||||
|
await rateLimits.rateLimitByResource(req, res, next)
|
||||||
|
//console.log(`next() called: ${next.called}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log(`req.locals after test: ${util.inspect(req.locals)}`)
|
||||||
|
|
||||||
|
// Note: next() will be called unless the rate-limit kicks in.
|
||||||
|
assert.equal(
|
||||||
|
next.called,
|
||||||
|
true,
|
||||||
|
`next should be called if rate limit was not triggered.`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should trigger rate-limit for free tier after 10 RPM", async () => {
|
||||||
|
// Create a new instance of the rate limit so we start with zeroed tracking.
|
||||||
|
rateLimits = new RateLimits()
|
||||||
|
|
||||||
|
req.baseUrl = "/v3"
|
||||||
|
req.path = "/control/getNetworkInfo"
|
||||||
|
req.url = req.path
|
||||||
|
req.method = "GET"
|
||||||
|
|
||||||
|
req.locals.jwtToken = "some-token"
|
||||||
|
|
||||||
|
const jwtInfo = {
|
||||||
|
apiLevel: 10,
|
||||||
|
id: "5e3a0415eb29a962da2708b2"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock the call to the jwt library.
|
||||||
|
sandbox.stub(rateLimits.jwt, "verify").returns(jwtInfo)
|
||||||
|
|
||||||
|
for (let i = 0; i < 12; i++) {
|
||||||
|
next.reset() // reset the stubbed next() function.
|
||||||
|
|
||||||
|
await rateLimits.rateLimitByResource(req, res, next)
|
||||||
|
//console.log(`next() called: ${next.called}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: next() will be called unless the rate-limit kicks in.
|
||||||
|
assert.equal(
|
||||||
|
next.called,
|
||||||
|
false,
|
||||||
|
`next should not be called if rate limit was triggered.`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should NOT trigger rate-limit handler for indexer-tier at 25 RPM", async () => {
|
||||||
|
// Create a new instance of the rate limit so we start with zeroed tracking.
|
||||||
|
rateLimits = new RateLimits()
|
||||||
|
|
||||||
|
req.baseUrl = "/v3"
|
||||||
|
req.path = "/control/getNetworkInfo"
|
||||||
|
req.url = req.path
|
||||||
|
req.method = "GET"
|
||||||
|
|
||||||
|
req.locals.jwtToken = "some-token"
|
||||||
|
|
||||||
|
const jwtInfo = {
|
||||||
|
apiLevel: 20,
|
||||||
|
id: "5e3a0415eb29a962da2708b3"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock the call to the jwt library.
|
||||||
|
sandbox.stub(rateLimits.jwt, "verify").returns(jwtInfo)
|
||||||
|
|
||||||
|
for (let i = 0; i < 25; i++) {
|
||||||
|
next.reset() // reset the stubbed next() function.
|
||||||
|
|
||||||
|
await rateLimits.rateLimitByResource(req, res, next)
|
||||||
|
//console.log(`next() called: ${next.called}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log(`req.locals after test: ${util.inspect(req.locals)}`)
|
||||||
|
|
||||||
|
// Note: next() will be called unless the rate-limit kicks in.
|
||||||
|
assert.equal(
|
||||||
|
next.called,
|
||||||
|
true,
|
||||||
|
`next should be called if rate limit was not triggered.`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should still rate-limit at a higher RPM for pro-tier", async () => {
|
||||||
|
// Create a new instance of the rate limit so we start with zeroed tracking.
|
||||||
|
rateLimits = new RateLimits()
|
||||||
|
|
||||||
|
req.baseUrl = "/v3"
|
||||||
|
req.path = "/control/getNetworkInfo"
|
||||||
|
req.url = req.path
|
||||||
|
req.method = "GET"
|
||||||
|
|
||||||
|
req.locals.jwtToken = "some-token"
|
||||||
|
|
||||||
|
const jwtInfo = {
|
||||||
|
apiLevel: 20,
|
||||||
|
id: "5e3a0415eb29a962da2708b5"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock the call to the jwt library.
|
||||||
|
sandbox.stub(rateLimits.jwt, "verify").returns(jwtInfo)
|
||||||
|
|
||||||
|
for (let i = 0; i < 150; i++) {
|
||||||
|
next.reset() // reset the stubbed next() function.
|
||||||
|
|
||||||
|
await rateLimits.rateLimitByResource(req, res, next)
|
||||||
|
//console.log(`next() called: ${next.called}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log(`req.locals after test: ${util.inspect(req.locals)}`)
|
||||||
|
|
||||||
|
// Note: next() will be called unless the rate-limit kicks in.
|
||||||
|
assert.equal(
|
||||||
|
next.called,
|
||||||
|
false,
|
||||||
|
`next should NOT be called if rate limit was triggered.`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#rateLimitSimple", () => {
|
||||||
|
it("should pass through rate-limit middleware", async () => {
|
||||||
|
req.baseUrl = "/v3"
|
||||||
|
req.path = "/control/getNetworkInfo"
|
||||||
|
req.url = req.path
|
||||||
|
req.method = "GET"
|
||||||
|
|
||||||
|
// Call the route twice to trigger the rate handling.
|
||||||
|
await rateLimits.rateLimitSimple(req, res, next)
|
||||||
|
await rateLimits.rateLimitSimple(req, res, next)
|
||||||
|
|
||||||
|
// next() will be called if rate-limit is not triggered
|
||||||
|
assert.equal(next.called, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should trigger rate-limit handler if rate limits exceeds 5 request per minute", async () => {
|
||||||
|
req.baseUrl = "/v3"
|
||||||
|
req.path = "/control/getNetworkInfo"
|
||||||
|
req.url = req.path
|
||||||
|
req.method = "GET"
|
||||||
|
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
next.reset() // reset the stubbed next() function.
|
||||||
|
|
||||||
|
await rateLimits.rateLimitSimple(req, res, next)
|
||||||
|
//console.log(`next() called: ${next.called}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: next() will be called unless the rate-limit kicks in.
|
||||||
|
assert.equal(
|
||||||
|
next.called,
|
||||||
|
false,
|
||||||
|
`next should not be called if rate limit was triggered.`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should NOT trigger rate-limit for free-tier at 5 RPM", async () => {
|
||||||
|
// Create a new instance of the rate limit so we start with zeroed tracking.
|
||||||
|
rateLimits = new RateLimits()
|
||||||
|
|
||||||
|
req.baseUrl = "/v3"
|
||||||
|
req.path = "/control/getNetworkInfo"
|
||||||
|
req.url = req.path
|
||||||
|
req.method = "GET"
|
||||||
|
|
||||||
|
req.locals.jwtToken = "some-token"
|
||||||
|
|
||||||
|
const jwtInfo = {
|
||||||
|
rateLimit: 10,
|
||||||
|
id: "5e3a0415eb29a962da2708c1"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock the call to the jwt library.
|
||||||
|
sandbox.stub(rateLimits.jwt, "verify").returns(jwtInfo)
|
||||||
|
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
next.reset() // reset the stubbed next() function.
|
||||||
|
|
||||||
|
await rateLimits.rateLimitSimple(req, res, next)
|
||||||
|
//console.log(`next() called: ${next.called}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log(`req.locals after test: ${util.inspect(req.locals)}`)
|
||||||
|
|
||||||
|
// Note: next() will be called unless the rate-limit kicks in.
|
||||||
|
assert.equal(
|
||||||
|
next.called,
|
||||||
|
true,
|
||||||
|
`next should be called if rate limit was not triggered.`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should trigger rate-limit for free tier after 10 RPM", async () => {
|
||||||
|
// Create a new instance of the rate limit so we start with zeroed tracking.
|
||||||
|
rateLimits = new RateLimits()
|
||||||
|
|
||||||
|
req.baseUrl = "/v3"
|
||||||
|
req.path = "/control/getNetworkInfo"
|
||||||
|
req.url = req.path
|
||||||
|
req.method = "GET"
|
||||||
|
|
||||||
|
req.locals.jwtToken = "some-token"
|
||||||
|
|
||||||
|
const jwtInfo = {
|
||||||
|
rateLimit: 10,
|
||||||
|
id: "5e3a0415eb29a962da2708c2"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock the call to the jwt library.
|
||||||
|
sandbox.stub(rateLimits.jwt, "verify").returns(jwtInfo)
|
||||||
|
|
||||||
|
for (let i = 0; i < 12; i++) {
|
||||||
|
next.reset() // reset the stubbed next() function.
|
||||||
|
|
||||||
|
await rateLimits.rateLimitSimple(req, res, next)
|
||||||
|
//console.log(`next() called: ${next.called}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: next() will be called unless the rate-limit kicks in.
|
||||||
|
assert.equal(
|
||||||
|
next.called,
|
||||||
|
false,
|
||||||
|
`next should not be called if rate limit was triggered.`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should NOT trigger rate-limit handler for pro-tier at 25 RPM", async () => {
|
||||||
|
// Create a new instance of the rate limit so we start with zeroed tracking.
|
||||||
|
rateLimits = new RateLimits()
|
||||||
|
|
||||||
|
req.baseUrl = "/v3"
|
||||||
|
req.path = "/control/getNetworkInfo"
|
||||||
|
req.url = req.path
|
||||||
|
req.method = "GET"
|
||||||
|
|
||||||
|
req.locals.jwtToken = "some-token"
|
||||||
|
|
||||||
|
const jwtInfo = {
|
||||||
|
rateLimit: 100,
|
||||||
|
id: "5e3a0415eb29a962da2708c3"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock the call to the jwt library.
|
||||||
|
sandbox.stub(rateLimits.jwt, "verify").returns(jwtInfo)
|
||||||
|
|
||||||
|
for (let i = 0; i < 25; i++) {
|
||||||
|
next.reset() // reset the stubbed next() function.
|
||||||
|
|
||||||
|
await rateLimits.rateLimitSimple(req, res, next)
|
||||||
|
//console.log(`next() called: ${next.called}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log(`req.locals after test: ${util.inspect(req.locals)}`)
|
||||||
|
|
||||||
|
// Note: next() will be called unless the rate-limit kicks in.
|
||||||
|
assert.equal(
|
||||||
|
next.called,
|
||||||
|
true,
|
||||||
|
`next should be called if rate limit was not triggered.`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should still rate-limit at a higher RPM for pro-tier", async () => {
|
||||||
|
// Create a new instance of the rate limit so we start with zeroed tracking.
|
||||||
|
rateLimits = new RateLimits()
|
||||||
|
|
||||||
|
req.baseUrl = "/v3"
|
||||||
|
req.path = "/control/getNetworkInfo"
|
||||||
|
req.url = req.path
|
||||||
|
req.method = "GET"
|
||||||
|
|
||||||
|
req.locals.jwtToken = "some-token"
|
||||||
|
|
||||||
|
const jwtInfo = {
|
||||||
|
rateLimit: 100,
|
||||||
|
id: "5e3a0415eb29a962da2708c4"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock the call to the jwt library.
|
||||||
|
sandbox.stub(rateLimits.jwt, "verify").returns(jwtInfo)
|
||||||
|
|
||||||
|
for (let i = 0; i < 150; i++) {
|
||||||
|
next.reset() // reset the stubbed next() function.
|
||||||
|
|
||||||
|
await rateLimits.rateLimitSimple(req, res, next)
|
||||||
|
//console.log(`next() called: ${next.called}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log(`req.locals after test: ${util.inspect(req.locals)}`)
|
||||||
|
|
||||||
|
// Note: next() will be called unless the rate-limit kicks in.
|
||||||
|
assert.equal(
|
||||||
|
next.called,
|
||||||
|
false,
|
||||||
|
`next should NOT be called if rate limit was triggered.`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Generates a Basic authorization header.
|
// Generates a Basic authorization header.
|
||||||
|
|||||||
Reference in New Issue
Block a user