这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion workers/loc.api/helpers/api-errors-testers.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ const isTempUnavailableError = (err) => {
return /temporarily_unavailable/.test(_getErrorString(err))
}

const isForbiddenError = (err) => {
return /forbidden/i.test(_getErrorString(err))
}

const isENetError = (err) => (
isENetUnreachError(err) ||
isEConnResetError(err) ||
Expand Down Expand Up @@ -96,5 +100,6 @@ module.exports = {
isEHostUnreachError,
isEProtoError,
isTempUnavailableError,
isENetError
isENetError,
isForbiddenError
}
4 changes: 3 additions & 1 deletion workers/loc.api/helpers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ const {
isEConnRefusedError,
isENotFoundError,
isESocketTimeoutError,
isENetError
isENetError,
isForbiddenError
} = require('./api-errors-testers')
const {
accountCache,
Expand Down Expand Up @@ -73,6 +74,7 @@ module.exports = {
isENotFoundError,
isESocketTimeoutError,
isENetError,
isForbiddenError,
accountCache,
parseFields,
parseLoginsExtraDataFields,
Expand Down
73 changes: 73 additions & 0 deletions workers/loc.api/responder/__test__/mockedHtmlBody403.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js ie6 oldie" lang="en-US"> <![endif]-->
<!--[if IE 7]> <html class="no-js ie7 oldie" lang="en-US"> <![endif]-->
<!--[if IE 8]> <html class="no-js ie8 oldie" lang="en-US"> <![endif]-->
<!--[if gt IE 8]><!-->
<html class="no-js" lang="en-US"> <!--<![endif]-->

<head>
<title>Access denied | api.staging.bitfinex.com used Cloudflare to restrict access</title>
<meta charset="UTF-8" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<meta name="robots" content="noindex, nofollow" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="stylesheet" id="cf_styles-css" href="/cdn-cgi/styles/main.css" />
</head>

<body>
<div id="cf-wrapper">
<div class="cf-alert cf-alert-error cf-cookie-error hidden" id="cookie-alert" data-translate="enable_cookies">Please
enable cookies.</div>
<div id="cf-error-details" class="p-0">
<header class="mx-auto pt-10 lg:pt-6 lg:px-8 w-240 lg:w-full mb-15 antialiased">
<h1 class="inline-block md:block mr-2 md:mb-2 font-light text-60 md:text-3xl text-black-dark leading-tight">
<span data-translate="error">Error</span>
<span>1006</span>
</h1>
<span class="inline-block md:block heading-ray-id font-mono text-15 lg:text-sm lg:leading-relaxed">Ray ID:
1234567890 &bull;</span>
<span class="inline-block md:block heading-ray-id font-mono text-15 lg:text-sm lg:leading-relaxed">2023-01-01
12:00:00 UTC</span>
<h2 class="text-gray-600 leading-1.3 text-3xl lg:text-2xl font-light">Access denied</h2>
</header>
<section class="w-240 lg:w-full mx-auto mb-8 lg:px-8">
<div id="what-happened-section" class="w-1/2 md:w-full">
<h2 class="text-3xl leading-tight font-normal mb-4 text-black-dark antialiased"
data-translate="what_happened">What happened?</h2>
<p>The owner of this website (api.staging.bitfinex.com) has banned your IP address (12.123.123.12).</p>
</div>
</section>
<div class="feedback-hidden py-8 text-center" id="error-feedback">
<div id="error-feedback-survey" class="footer-line-wrapper">
Was this page helpful?
<button class="border border-solid bg-white cf-button cursor-pointer ml-4 px-4 py-2 rounded"
id="feedback-button-yes" type="button">Yes</button>
<button class="border border-solid bg-white cf-button cursor-pointer ml-4 px-4 py-2 rounded"
id="feedback-button-no" type="button">No</button>
</div>
<div class="feedback-success feedback-hidden" id="error-feedback-success">
Thank you for your feedback!
</div>
</div>
<div
class="cf-error-footer cf-wrapper w-240 lg:w-full py-10 sm:py-4 sm:px-8 mx-auto text-center sm:text-left border-solid border-0 border-t border-gray-300">
<p class="text-13">
<span class="cf-footer-item sm:block sm:mb-1">Cloudflare Ray ID: <strong
class="font-semibold">1234567890</strong></span>
<span class="cf-footer-separator sm:hidden">&bull;</span>
<span id="cf-footer-item-ip" class="cf-footer-item hidden sm:block sm:mb-1">
Your IP:
<button type="button" id="cf-footer-ip-reveal" class="cf-footer-ip-reveal-btn">Click to reveal</button>
<span class="hidden" id="cf-footer-ip">12.123.123.12</span>
<span class="cf-footer-separator sm:hidden">&bull;</span>
</span>
<span class="cf-footer-item sm:block sm:mb-1"><span>Performance &amp; security by</span> <a
rel="noopener noreferrer" href="https://www.cloudflare.com/5xx-error-landing" id="brand_link"
target="_blank">Cloudflare</a></span>
</p>
</div>
</div>
</div>
</body>
</html>
134 changes: 134 additions & 0 deletions workers/loc.api/responder/__test__/responder.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
'use strict'

const fs = require('fs')
const path = require('path')
const { assert } = require('chai')

require('reflect-metadata')
const responder = require('../index')
const AbstractWSEventEmitter = require('../../abstract.ws.event.emitter')

const JSON_RPC_VERSION = '2.0'
const name = 'mockedResponderCall'
const args = { id: 5 }
const mockedHtmlBody403 = fs.readFileSync(path.join(
__dirname, 'mockedHtmlBody403.html'
))

const _makeApiError = (resp, rawBody) => {
const err = new Error(`HTTP code ${resp.status} ${resp.statusText || ''}`)
err.status = resp.status
err.statustext = resp.statusText
try {
const [, code, response] = JSON.parse(rawBody)
err.code = code
err.response = response
} catch (_err) {
err.response = rawBody
}

return err
}

const _errorResponseTestCases = (opts) => (err, res) => {
assert.isNull(err)

assert.isObject(res)
assert.propertyVal(res, 'id', 5)
assert.propertyVal(res, 'jsonrpc', JSON_RPC_VERSION)
assert.isObject(res.error)
assert.propertyVal(res.error, 'code', opts.code)
assert.propertyVal(res.error, 'message', opts.message)
assert.isObject(res.error.data)
assert.isObject(res.error.data.bfxApiErrorMessage)

assert.containsAllKeys(res.error.data.bfxApiErrorMessage, [
'bfxApiStatus',
'bfxApiStatusText',
'bfxApiRawBodyCode',
'isBfxApiRawBodyResponseHtml',
'bfxApiRawBodyResponse'
])
}

describe('Responder service', () => {
let mockedResponder = null

before(function () {
const mockedContainer = {}
const mockedLogger = {
debug (message) {
assert.isString(message)
},
error (message) {
assert.isString(message)
}
}
const mockedWsEventEmitterFactory = () => new (class WSEventEmitter extends AbstractWSEventEmitter {
emitBfxUnamePwdAuthRequiredToOne (data, auth) {
assert.isObject(data)
assert.isObject(auth)
}
})()

mockedResponder = responder(
mockedContainer,
mockedLogger,
mockedWsEventEmitterFactory
)
})

it('handle HTML error', async function () {
await mockedResponder(async () => {
throw _makeApiError(
{
status: 403,
statustext: 'Forbidden'
},
mockedHtmlBody403
)
}, name, args, _errorResponseTestCases({
code: 403,
message: 'Forbidden'
}))
})

it('handle plain error status text', async function () {
await mockedResponder(async () => {
throw _makeApiError(
{
status: 500,
statusText: 'ERR_INVOICE_LIST: ERR_PAY_USER_NOT_MERCHANT'
},
null
)
}, name, args, _errorResponseTestCases({
code: 409,
message: 'Pay invoice list error, the user is not a merchant'
}))
})

it('handle error in body', async function () {
await mockedResponder(async () => {
throw _makeApiError(
{},
JSON.stringify([500, 'ERR_INVOICE_LIST: ERR_PAY_USER_NOT_MERCHANT'])
)
}, name, args, _errorResponseTestCases({
code: 409,
message: 'Pay invoice list error, the user is not a merchant'
}))
})

it('handle plain error text in raw body', async function () {
await mockedResponder(async () => {
throw _makeApiError(
{},
'ERR_INVOICE_LIST: ERR_PAY_USER_NOT_MERCHANT'
)
}, name, args, _errorResponseTestCases({
code: 409,
message: 'Pay invoice list error, the user is not a merchant'
}))
})
})
60 changes: 49 additions & 11 deletions workers/loc.api/responder/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,45 @@ const {
isRateLimitError,
isNonceSmallError,
isUserIsNotMerchantError,
isSymbolInvalidError
isSymbolInvalidError,
isForbiddenError
} = require('../helpers')

const {
BaseError,
AuthError
} = require('../errors')

const _htmlRegExp = /<html.*>/i
const _htmlTitleRegExp = /<title.*>(?<body>.*)<\/title.*>/i

const JSON_RPC_VERSION = '2.0'

const _isHtml = (res) => (_htmlRegExp.test(res))

const _findHtmlTitle = (res) => (
res?.match(_htmlTitleRegExp).groups?.body ?? 'HTML title not found'
)

const _getBfxApiErrorMetadata = (err) => {
if (!err?.status) {
return null
}

const isHtml = _isHtml(err.response)
const body = isHtml
? _findHtmlTitle(err.response)
: err.response ?? 'Response is not abailable'

return {
bfxApiStatus: err.status,
bfxApiStatusText: err.statustext ?? 'Status text is not abailable',
bfxApiRawBodyCode: err.code ?? 'Code is not abailable',
isBfxApiRawBodyResponseHtml: isHtml ? 'Yes' : 'No',
bfxApiRawBodyResponse: body
}
}

const _prepareErrorData = (err, name) => {
const { message = 'ERR_ERROR_HAS_OCCURRED' } = err
const _name = name
Expand All @@ -29,7 +58,10 @@ const _prepareErrorData = (err, name) => {
? `\n - STATUS_MESSAGE: ${err.statusMessage}`
: ''
const _data = err.data
? `\n - DATA: ${JSON.stringify(err.data)}`
? `\n - DATA: ${JSON.stringify(err.data, null, 2)
.split('\n')
.map((v, i) => (i === 0 ? v : ` ${v}`))
.join('\n')}`
: ''
const stackTrace = (err.stack || err)
? `\n - STACK_TRACE ${err.stack || err}`
Expand Down Expand Up @@ -93,6 +125,12 @@ const _getErrorWithMetadataForNonBaseError = (args, err) => {

return err
}
if (isForbiddenError(err)) {
err.statusCode = 403
err.statusMessage = 'Forbidden'

return err
}

return err
}
Expand All @@ -105,20 +143,20 @@ const _getErrorMetadata = (args, err) => {
data = null
} = errWithMetadata

const _message = err?.status
? `${message}
- BFX_API_STATUS: ${err.status}
- BFX_API_STATUS_TEXT: ${err.statustext ?? 'Status text is not abailable'}
- BFX_API_RAW_BODY_CODE: ${err.code ?? 'Code is not abailable'}
- BFX_API_RAW_BODY_RESPONSE: ${err.response ?? 'Response is not abailable'}`
: message
const bfxApiErrorMessage = _getBfxApiErrorMetadata(err)
const extendedData = bfxApiErrorMessage
? {
bfxApiErrorMessage,
...data
}
: data

const error = Object.assign(
errWithMetadata,
{
statusCode: code,
statusMessage: _message,
data
statusMessage: message,
data: extendedData
}
)

Expand Down