mirror of
https://github.com/fullstack-cash/bch-api.git
synced 2026-09-22 09:12:05 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4664ca173 | ||
|
|
11a7915cb8 | ||
|
|
06b944f36a | ||
|
|
9a8886121d | ||
|
|
89c6d48dfd | ||
|
|
4525b99f33 | ||
|
|
3d04507948 | ||
|
|
7e3a447973 | ||
|
|
bdde3a271d | ||
|
|
9450c3969b | ||
|
|
23d66da792 | ||
|
|
cb430c20e2 | ||
|
|
58ce0e0604 | ||
|
|
61485dfe99 | ||
|
|
ab4c80b674 |
+12
-1
@@ -1,11 +1,22 @@
|
|||||||
/*
|
/*
|
||||||
Common configuration settings.
|
Common configuration settings.
|
||||||
|
Default settings in this file can be overridden by an environment variable.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
|
// This is the same secret used by jwt-bch-api
|
||||||
apiTokenSecret: process.env.TOKENSECRET
|
apiTokenSecret: process.env.TOKENSECRET
|
||||||
? process.env.TOKENSECRET
|
? process.env.TOKENSECRET
|
||||||
: 'secret-jwt-token'
|
: 'secret-jwt-token',
|
||||||
|
|
||||||
|
// Rate Limits
|
||||||
|
anonRateLimit: process.env.ANON_RATE_LIMIT ? process.env.ANON_RATE_LIMIT : 50,
|
||||||
|
whitelistRateLimit: process.env.WHITELIST_RATE_LIMIT
|
||||||
|
? process.env.WHITELIST_RATE_LIMIT
|
||||||
|
: 10,
|
||||||
|
whitelistDomains: process.env.WHITELIST_DOMAINS
|
||||||
|
? process.env.WHITELIST_DOMAINS.split(',')
|
||||||
|
: ['fullstack.cash', 'psfoundation.cash']
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = config
|
module.exports = config
|
||||||
|
|||||||
Generated
+3118
-4623
File diff suppressed because it is too large
Load Diff
+5
-4
@@ -17,20 +17,21 @@
|
|||||||
"test": "npm run lint && npm run test-v4",
|
"test": "npm run lint && npm run test-v4",
|
||||||
"lint": "standard --env mocha --fix",
|
"lint": "standard --env mocha --fix",
|
||||||
"test-v4": "export NETWORK=mainnet && nyc --reporter=text mocha --timeout 60000 test/v4/",
|
"test-v4": "export NETWORK=mainnet && nyc --reporter=text mocha --timeout 60000 test/v4/",
|
||||||
"test:temp": "export NETWORK=mainnet && export TEST=integration && mocha --timeout 25000 test/v4/integration/price.js",
|
|
||||||
"test:integration": "mocha test/v4/integration",
|
"test:integration": "mocha test/v4/integration",
|
||||||
"test:integration:slpdb": "mocha --timeout 25000 -g '#validate2Single' test/v4/integration/slp*.js",
|
"test:integration:slpdb": "mocha --timeout 25000 -g '#validate2Single' test/v4/integration/slp*.js",
|
||||||
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
||||||
"coverage:report": "export NETWORK=mainnet && nyc --reporter=html mocha --timeout 25000 test/v4/",
|
"coverage:report": "export NETWORK=mainnet && nyc --reporter=html mocha --timeout 25000 test/v4/",
|
||||||
"docs": "./node_modules/.bin/apidoc -i src/routes/v4 -o docs"
|
"docs": "./node_modules/.bin/apidoc -i src/routes/v4 -o docs",
|
||||||
|
"test:temp1": "export NETWORK=mainnet && export TEST=integration && mocha --timeout 25000 -g '#generateSendOpReturn()' test/v4/integration/",
|
||||||
|
"test:temp2": "mocha test/v4/rate-limits.js"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.15.1"
|
"node": ">=10.15.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@psf/bch-js": "^4.2.5",
|
"@psf/bch-js": "^4.5.4",
|
||||||
"apidoc": "^0.23.0",
|
"apidoc": "^0.23.0",
|
||||||
"axios": "^0.19.0",
|
"axios": "^0.21.1",
|
||||||
"bitcore-lib-cash": "^8.23.1",
|
"bitcore-lib-cash": "^8.23.1",
|
||||||
"body-parser": "^1.18.3",
|
"body-parser": "^1.18.3",
|
||||||
"cookie-parser": "~1.4.3",
|
"cookie-parser": "~1.4.3",
|
||||||
|
|||||||
+2
-2
@@ -262,11 +262,11 @@ function onError (error) {
|
|||||||
case 'EACCES':
|
case 'EACCES':
|
||||||
console.error(`${bind} requires elevated privileges`)
|
console.error(`${bind} requires elevated privileges`)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
break
|
// break
|
||||||
case 'EADDRINUSE':
|
case 'EADDRINUSE':
|
||||||
console.error(`${bind} is already in use`)
|
console.error(`${bind} is already in use`)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
break
|
// break
|
||||||
default:
|
default:
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,12 +12,17 @@
|
|||||||
|
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
|
// Public npm libraries.
|
||||||
const jwt = require('jsonwebtoken')
|
const jwt = require('jsonwebtoken')
|
||||||
|
|
||||||
|
// local libraries.
|
||||||
const wlogger = require('../util/winston-logging')
|
const wlogger = require('../util/winston-logging')
|
||||||
const config = require('../../config')
|
const config = require('../../config')
|
||||||
|
|
||||||
const ANON_LIMITS = 50
|
const ANON_LIMITS = config.anonRateLimit
|
||||||
|
const WHITELIST_RATE_LIMIT = config.whitelistRateLimit
|
||||||
|
const WHITELIST_DOMAINS = config.whitelistDomains
|
||||||
|
const INTERNAL_RATE_LIMIT = 1
|
||||||
|
|
||||||
// Redis
|
// Redis
|
||||||
const redisOptions = {
|
const redisOptions = {
|
||||||
@@ -144,13 +149,11 @@ class RateLimits {
|
|||||||
|
|
||||||
// If the request originates from one of the approved wallet apps, then
|
// If the request originates from one of the approved wallet apps, then
|
||||||
// apply paid-access rate limits.
|
// apply paid-access rate limits.
|
||||||
if (
|
// console.log(`origin: ${JSON.stringify(origin, null, 2)}`)
|
||||||
origin &&
|
// console.log(`whitelist: ${JSON.stringify(WHITELIST_DOMAINS, null, 2)}`)
|
||||||
(origin.toString().indexOf('fullstack.cash') > -1 ||
|
const isInWhitelist = _this.isInWhitelist(origin)
|
||||||
origin.toString().indexOf('splitbch.com') > -1 ||
|
if (isInWhitelist) {
|
||||||
origin.toString().indexOf('slp-api') > -1)
|
pointsToConsume = WHITELIST_RATE_LIMIT
|
||||||
) {
|
|
||||||
pointsToConsume = 10
|
|
||||||
res.locals.pointsToConsume = pointsToConsume // Feedback for tests.
|
res.locals.pointsToConsume = pointsToConsume // Feedback for tests.
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,7 +164,7 @@ class RateLimits {
|
|||||||
// Do not comment out this line.
|
// Do not comment out this line.
|
||||||
key.toString().indexOf('172.17.') > -1
|
key.toString().indexOf('172.17.') > -1
|
||||||
) {
|
) {
|
||||||
pointsToConsume = 1
|
pointsToConsume = INTERNAL_RATE_LIMIT
|
||||||
res.locals.pointsToConsume = pointsToConsume // Feedback for tests.
|
res.locals.pointsToConsume = pointsToConsume // Feedback for tests.
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,6 +266,31 @@ class RateLimits {
|
|||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a boolean if the origin of the request matches a domain in the
|
||||||
|
// whitelist.
|
||||||
|
isInWhitelist (origin) {
|
||||||
|
try {
|
||||||
|
const retVal = false // Default value.
|
||||||
|
|
||||||
|
if (!origin) return false
|
||||||
|
|
||||||
|
// console.log(`WHITELIST_DOMAINS: ${JSON.stringify(WHITELIST_DOMAINS, null, 2)}`)
|
||||||
|
|
||||||
|
for (let i = 0; i < WHITELIST_DOMAINS.length; i++) {
|
||||||
|
const thisDomain = WHITELIST_DOMAINS[i]
|
||||||
|
|
||||||
|
if (origin.toString().indexOf(thisDomain) > -1) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal
|
||||||
|
} catch (err) {
|
||||||
|
wlogger.error('Error in route-ratelimit.js/isInWhitelist(). Returning false by default.')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = RateLimits
|
module.exports = RateLimits
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ class Electrum {
|
|||||||
const { msg, status } = _this.routeUtils.decodeError(err)
|
const { msg, status } = _this.routeUtils.decodeError(err)
|
||||||
if (msg) {
|
if (msg) {
|
||||||
res.status(status)
|
res.status(status)
|
||||||
return res.json({ error: msg })
|
return res.json({ success: false, error: msg })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle error patterns specific to this route.
|
// Handle error patterns specific to this route.
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class Encryption {
|
|||||||
const { msg, status } = _this.routeUtils.decodeError(err)
|
const { msg, status } = _this.routeUtils.decodeError(err)
|
||||||
if (msg) {
|
if (msg) {
|
||||||
res.status(status)
|
res.status(status)
|
||||||
return res.json({ error: msg })
|
return res.json({ success: false, error: msg })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle error patterns specific to this route.
|
// Handle error patterns specific to this route.
|
||||||
|
|||||||
@@ -175,8 +175,17 @@ function decodeError (err) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle general Error objects.
|
||||||
|
if (err.message) {
|
||||||
|
return {
|
||||||
|
message: err.message,
|
||||||
|
status: 422
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return { msg: false, status: 500 }
|
return { msg: false, status: 500 }
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error('unhandled error in route-utils.js/decodeError(): ', err)
|
||||||
wlogger.error('unhandled error in route-utils.js/decodeError(): ', err)
|
wlogger.error('unhandled error in route-utils.js/decodeError(): ', err)
|
||||||
return { msg: false, status: 500 }
|
return { msg: false, status: 500 }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1896,6 +1896,8 @@ class Slp {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// console.log('sendQty: ', sendQty)
|
||||||
|
// console.log(`tokenUtxos: `, tokenUtxos)
|
||||||
const opReturn = await _this.bchjs.SLP.TokenType1.generateSendOpReturn(
|
const opReturn = await _this.bchjs.SLP.TokenType1.generateSendOpReturn(
|
||||||
tokenUtxos,
|
tokenUtxos,
|
||||||
sendQty
|
sendQty
|
||||||
@@ -1907,6 +1909,7 @@ class Slp {
|
|||||||
res.status(200)
|
res.status(200)
|
||||||
return res.json({ script, outputs: opReturn.outputs })
|
return res.json({ script, outputs: opReturn.outputs })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.log('err: ', err)
|
||||||
wlogger.error('Error in slp.js/generateSendOpReturn().', err)
|
wlogger.error('Error in slp.js/generateSendOpReturn().', err)
|
||||||
|
|
||||||
// Decode the error message.
|
// Decode the error message.
|
||||||
@@ -1996,9 +1999,11 @@ class Slp {
|
|||||||
|
|
||||||
// Decode the error message.
|
// Decode the error message.
|
||||||
const { msg, status } = routeUtils.decodeError(err)
|
const { msg, status } = routeUtils.decodeError(err)
|
||||||
|
console.log('msg: ', msg)
|
||||||
|
console.log('status: ', status)
|
||||||
if (msg) {
|
if (msg) {
|
||||||
res.status(status)
|
res.status(status)
|
||||||
return res.json({ error: msg })
|
return res.json({ error: msg, message: msg, success: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(500)
|
res.status(500)
|
||||||
|
|||||||
@@ -154,8 +154,17 @@ class RouteUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle general Error objects.
|
||||||
|
if (err.message) {
|
||||||
|
return {
|
||||||
|
msg: err.message,
|
||||||
|
status: 422
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return { msg: false, status: 500 }
|
return { msg: false, status: 500 }
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error('unhandled error in route-utils.js/decodeError(): ', err)
|
||||||
wlogger.error('unhandled error in route-utils.js/decodeError(): ', err)
|
wlogger.error('unhandled error in route-utils.js/decodeError(): ', err)
|
||||||
return { msg: false, status: 500 }
|
return { msg: false, status: 500 }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -199,11 +199,11 @@ describe('#Electrumx', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('#getUtxos', () => {
|
describe('#getUtxos', () => {
|
||||||
it('should throw 400 if address is empty', async () => {
|
it('should throw 422 if address is empty', async () => {
|
||||||
const result = await electrumxRoute.getUtxos(req, res)
|
const result = await electrumxRoute.getUtxos(req, res)
|
||||||
// console.log(`result: ${util.inspect(result)}`)
|
// console.log(`result: ${util.inspect(result)}`)
|
||||||
|
|
||||||
assert.equal(res.statusCode, 400, 'Expect 400 status code')
|
assert.equal(res.statusCode, 422, 'Expect 422 status code')
|
||||||
|
|
||||||
assert.property(result, 'error')
|
assert.property(result, 'error')
|
||||||
assert.include(result.error, 'Unsupported address format')
|
assert.include(result.error, 'Unsupported address format')
|
||||||
@@ -233,7 +233,7 @@ describe('#Electrumx', () => {
|
|||||||
const result = await electrumxRoute.getUtxos(req, res)
|
const result = await electrumxRoute.getUtxos(req, res)
|
||||||
// console.log(`result: ${util.inspect(result)}`)
|
// console.log(`result: ${util.inspect(result)}`)
|
||||||
|
|
||||||
assert.equal(res.statusCode, 400, 'Expect 400 status code')
|
assert.equal(res.statusCode, 422, 'Expect 422 status code')
|
||||||
|
|
||||||
assert.property(result, 'error')
|
assert.property(result, 'error')
|
||||||
assert.include(result.error, 'Unsupported address format')
|
assert.include(result.error, 'Unsupported address format')
|
||||||
@@ -1063,7 +1063,7 @@ describe('#Electrumx', () => {
|
|||||||
const result = await electrumxRoute.getBalance(req, res)
|
const result = await electrumxRoute.getBalance(req, res)
|
||||||
// console.log(`result: ${util.inspect(result)}`)
|
// console.log(`result: ${util.inspect(result)}`)
|
||||||
|
|
||||||
assert.equal(res.statusCode, 400, 'Expect 400 status code')
|
assert.equal(res.statusCode, 422, 'Expect 422 status code')
|
||||||
|
|
||||||
assert.property(result, 'error')
|
assert.property(result, 'error')
|
||||||
assert.include(result.error, 'Unsupported address format')
|
assert.include(result.error, 'Unsupported address format')
|
||||||
@@ -1093,7 +1093,7 @@ describe('#Electrumx', () => {
|
|||||||
const result = await electrumxRoute.getBalance(req, res)
|
const result = await electrumxRoute.getBalance(req, res)
|
||||||
// console.log(`result: ${util.inspect(result)}`)
|
// console.log(`result: ${util.inspect(result)}`)
|
||||||
|
|
||||||
assert.equal(res.statusCode, 400, 'Expect 400 status code')
|
assert.equal(res.statusCode, 422, 'Expect 422 status code')
|
||||||
|
|
||||||
assert.property(result, 'error')
|
assert.property(result, 'error')
|
||||||
assert.include(result.error, 'Unsupported address format')
|
assert.include(result.error, 'Unsupported address format')
|
||||||
@@ -1350,7 +1350,7 @@ describe('#Electrumx', () => {
|
|||||||
const result = await electrumxRoute.getTransactions(req, res)
|
const result = await electrumxRoute.getTransactions(req, res)
|
||||||
// console.log(`result: ${util.inspect(result)}`)
|
// console.log(`result: ${util.inspect(result)}`)
|
||||||
|
|
||||||
assert.equal(res.statusCode, 400, 'Expect 400 status code')
|
assert.equal(res.statusCode, 422, 'Expect 422 status code')
|
||||||
|
|
||||||
assert.property(result, 'error')
|
assert.property(result, 'error')
|
||||||
assert.include(result.error, 'Unsupported address format')
|
assert.include(result.error, 'Unsupported address format')
|
||||||
@@ -1380,7 +1380,7 @@ describe('#Electrumx', () => {
|
|||||||
const result = await electrumxRoute.getTransactions(req, res)
|
const result = await electrumxRoute.getTransactions(req, res)
|
||||||
// console.log(`result: ${util.inspect(result)}`)
|
// console.log(`result: ${util.inspect(result)}`)
|
||||||
|
|
||||||
assert.equal(res.statusCode, 400, 'Expect 400 status code')
|
assert.equal(res.statusCode, 422, 'Expect 422 status code')
|
||||||
|
|
||||||
assert.property(result, 'error')
|
assert.property(result, 'error')
|
||||||
assert.include(result.error, 'Unsupported address format')
|
assert.include(result.error, 'Unsupported address format')
|
||||||
@@ -1645,7 +1645,7 @@ describe('#Electrumx', () => {
|
|||||||
const result = await electrumxRoute.getMempool(req, res)
|
const result = await electrumxRoute.getMempool(req, res)
|
||||||
// console.log(`result: ${util.inspect(result)}`)
|
// console.log(`result: ${util.inspect(result)}`)
|
||||||
|
|
||||||
assert.equal(res.statusCode, 400, 'Expect 400 status code')
|
assert.equal(res.statusCode, 422, 'Expect 422 status code')
|
||||||
|
|
||||||
assert.property(result, 'error')
|
assert.property(result, 'error')
|
||||||
assert.include(result.error, 'Unsupported address format')
|
assert.include(result.error, 'Unsupported address format')
|
||||||
@@ -1675,7 +1675,7 @@ describe('#Electrumx', () => {
|
|||||||
const result = await electrumxRoute.getMempool(req, res)
|
const result = await electrumxRoute.getMempool(req, res)
|
||||||
// console.log(`result: ${util.inspect(result)}`)
|
// console.log(`result: ${util.inspect(result)}`)
|
||||||
|
|
||||||
assert.equal(res.statusCode, 400, 'Expect 400 status code')
|
assert.equal(res.statusCode, 422, 'Expect 422 status code')
|
||||||
|
|
||||||
assert.property(result, 'error')
|
assert.property(result, 'error')
|
||||||
assert.include(result.error, 'Unsupported address format')
|
assert.include(result.error, 'Unsupported address format')
|
||||||
|
|||||||
@@ -110,8 +110,8 @@ describe('#slp', () => {
|
|||||||
req.body.tokenUtxos = [
|
req.body.tokenUtxos = [
|
||||||
{
|
{
|
||||||
tokenId:
|
tokenId:
|
||||||
'0a321bff9761f28e06a268b14711274bb77617410a16807bd0437ef234a072b1',
|
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
|
||||||
decimals: 0,
|
decimals: 8,
|
||||||
tokenQty: 2
|
tokenQty: 2
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -445,6 +445,30 @@ describe('#route-ratelimits & jwt-auth', () => {
|
|||||||
assert.equal(res.locals.pointsToConsume, 50)
|
assert.equal(res.locals.pointsToConsume, 50)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('#isInWhitelist', () => {
|
||||||
|
it('should return false when no argument is passed in', () => {
|
||||||
|
const result = rateLimits.isInWhitelist()
|
||||||
|
|
||||||
|
assert.equal(result, false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return false when origin is not in the whitelist', () => {
|
||||||
|
const origin = 'blah.com'
|
||||||
|
|
||||||
|
const result = rateLimits.isInWhitelist(origin)
|
||||||
|
|
||||||
|
assert.equal(result, false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return true when origin is in the whitelist', () => {
|
||||||
|
const origin = 'message.fullstack.cash'
|
||||||
|
|
||||||
|
const result = rateLimits.isInWhitelist(origin)
|
||||||
|
|
||||||
|
assert.equal(result, true)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Generates a Basic authorization header.
|
// Generates a Basic authorization header.
|
||||||
|
|||||||
+3
-3
@@ -1287,7 +1287,7 @@ describe('#SLP', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('generateSendOpReturn()', () => {
|
describe('#generateSendOpReturn()', () => {
|
||||||
const generateSendOpReturn = slpRoute.generateSendOpReturn
|
const generateSendOpReturn = slpRoute.generateSendOpReturn
|
||||||
// Validate tokenUtxos input
|
// Validate tokenUtxos input
|
||||||
it('should throw 400 if tokenUtxos is missing', async () => {
|
it('should throw 400 if tokenUtxos is missing', async () => {
|
||||||
@@ -1363,8 +1363,8 @@ describe('#SLP', () => {
|
|||||||
req.body.tokenUtxos = [
|
req.body.tokenUtxos = [
|
||||||
{
|
{
|
||||||
tokenId:
|
tokenId:
|
||||||
'0a321bff9761f28e06a268b14711274bb77617410a16807bd0437ef234a072b1',
|
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
|
||||||
decimals: 0,
|
decimals: 8,
|
||||||
tokenQty: 2
|
tokenQty: 2
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user