diff --git a/lib/tools.js b/lib/tools.js index 2cf72b74..fe648ea0 100644 --- a/lib/tools.js +++ b/lib/tools.js @@ -241,6 +241,13 @@ function formatTokenError(provider, tokenRequest) { return parts.join(': '); } +function resolveOAuthErrorStatus(responseError, err) { + if (typeof responseError.code === 'number' && responseError.code >= 400) { + return responseError.code; + } + return (err && err.statusCode) || (err && err.oauthRequest && err.oauthRequest.status) || 500; +} + module.exports = { /** * Helper function to set specific bit in a buffer @@ -1963,7 +1970,9 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEV3QUiYsp13nD9suD1/ZkEXnuMoSg normalizeHashKeys, - formatTokenError + formatTokenError, + + resolveOAuthErrorStatus }; function msgpackDecode(buf) { diff --git a/test/oauth-error-status-test.js b/test/oauth-error-status-test.js new file mode 100644 index 00000000..c5c99747 --- /dev/null +++ b/test/oauth-error-status-test.js @@ -0,0 +1,59 @@ +'use strict'; + +const test = require('node:test'); +const assert = require('node:assert').strict; + +const { resolveOAuthErrorStatus } = require('../lib/tools'); + +test('resolveOAuthErrorStatus tests', async t => { + await t.test('returns numeric error code when it is a valid HTTP status', async () => { + assert.strictEqual(resolveOAuthErrorStatus({ code: 400 }, {}), 400); + assert.strictEqual(resolveOAuthErrorStatus({ code: 403 }, {}), 403); + assert.strictEqual(resolveOAuthErrorStatus({ code: 404 }, {}), 404); + assert.strictEqual(resolveOAuthErrorStatus({ code: 429 }, {}), 429); + assert.strictEqual(resolveOAuthErrorStatus({ code: 500 }, {}), 500); + assert.strictEqual(resolveOAuthErrorStatus({ code: 503 }, {}), 503); + }); + + await t.test('falls back to err.statusCode when error code is a string', async () => { + let err = { statusCode: 429 }; + assert.strictEqual(resolveOAuthErrorStatus({ code: 'TooManyPendingRequests' }, err), 429); + assert.strictEqual(resolveOAuthErrorStatus({ code: 'TooManyRequests' }, err), 429); + + err = { statusCode: 400 }; + assert.strictEqual(resolveOAuthErrorStatus({ code: 'BadRequest' }, err), 400); + }); + + await t.test('falls back to err.oauthRequest.status when err.statusCode is missing', async () => { + let err = { oauthRequest: { status: 429 } }; + assert.strictEqual(resolveOAuthErrorStatus({ code: 'TooManyPendingRequests' }, err), 429); + + err = { oauthRequest: { status: 503 } }; + assert.strictEqual(resolveOAuthErrorStatus({ code: 'ServiceUnavailable' }, err), 503); + }); + + await t.test('returns 500 when no fallback status is available', async () => { + assert.strictEqual(resolveOAuthErrorStatus({ code: 'UnknownError' }, {}), 500); + assert.strictEqual(resolveOAuthErrorStatus({ code: 'TooManyPendingRequests' }, {}), 500); + }); + + await t.test('rejects numeric codes below 400', async () => { + let err = { statusCode: 429 }; + assert.strictEqual(resolveOAuthErrorStatus({ code: 200 }, err), 429); + assert.strictEqual(resolveOAuthErrorStatus({ code: 301 }, err), 429); + assert.strictEqual(resolveOAuthErrorStatus({ code: 0 }, err), 429); + }); + + await t.test('handles undefined and null error codes', async () => { + let err = { statusCode: 502 }; + assert.strictEqual(resolveOAuthErrorStatus({ code: undefined }, err), 502); + assert.strictEqual(resolveOAuthErrorStatus({ code: null }, err), 502); + assert.strictEqual(resolveOAuthErrorStatus({}, err), 502); + }); + + await t.test('handles missing err parameter', async () => { + assert.strictEqual(resolveOAuthErrorStatus({ code: 403 }, null), 403); + assert.strictEqual(resolveOAuthErrorStatus({ code: 'BadRequest' }, null), 500); + assert.strictEqual(resolveOAuthErrorStatus({ code: 'BadRequest' }, undefined), 500); + }); +}); diff --git a/workers/api.js b/workers/api.js index 11761122..aa933fb8 100644 --- a/workers/api.js +++ b/workers/api.js @@ -40,7 +40,8 @@ const { getBoolean, loadTlsConfig, httpAgent, - reloadHttpProxyAgent + reloadHttpProxyAgent, + resolveOAuthErrorStatus } = require('../lib/tools'); const { matchIp, detectAutomatedRequest } = require('../lib/utils/network'); @@ -2145,7 +2146,7 @@ Include your token in requests using one of these methods: message = response.error.message; } - let error = Boom.boomify(new Error(message), { statusCode: response.error.code }); + let error = Boom.boomify(new Error(message), { statusCode: resolveOAuthErrorStatus(response.error, err) }); throw error; } throw err; @@ -2239,7 +2240,7 @@ Include your token in requests using one of these methods: let response = err.oauthRequest && err.oauthRequest.response; if (response && response.error) { let message = response.error.message; - let error = Boom.boomify(new Error(message), { statusCode: response.error.code }); + let error = Boom.boomify(new Error(message), { statusCode: resolveOAuthErrorStatus(response.error, err) }); throw error; } throw err; @@ -2328,7 +2329,7 @@ Include your token in requests using one of these methods: let response = err.oauthRequest && err.oauthRequest.response; if (response && response.error) { let message = response.error.message; - let error = Boom.boomify(new Error(message), { statusCode: response.error.code }); + let error = Boom.boomify(new Error(message), { statusCode: resolveOAuthErrorStatus(response.error, err) }); throw error; } throw err;