diff --git a/lib/web/fetch/util.js b/lib/web/fetch/util.js index 5e51bdd35aa..c8eef60b8aa 100644 --- a/lib/web/fetch/util.js +++ b/lib/web/fetch/util.js @@ -1447,8 +1447,10 @@ function includesCredentials (url) { * @param {object|string} navigable */ function isTraversableNavigable (navigable) { - // TODO - return true + // Node.js is not a browser environment, there is no traversable navigable + // that can prompt the user for credentials. Returning true would cause + // infinite retry loops on 401 responses. + return false } class EnvironmentSettingsObjectBase { diff --git a/test/fetch/401-statuscode-no-infinite-loop.js b/test/fetch/401-statuscode-no-infinite-loop.js new file mode 100644 index 00000000000..2b986f56d13 --- /dev/null +++ b/test/fetch/401-statuscode-no-infinite-loop.js @@ -0,0 +1,48 @@ +'use strict' + +const { fetch } = require('../..') +const { createServer } = require('node:http') +const { once } = require('node:events') +const { test } = require('node:test') + +const { closeServerAsPromise } = require('../utils/node-http') + +test('Receiving a 401 status code should not cause infinite retry loop', async (t) => { + let requestCount = 0 + + const server = createServer({ joinDuplicateHeaders: true }, (req, res) => { + requestCount++ + res.statusCode = 401 + res.setHeader('WWW-Authenticate', 'Basic realm="test"') + res.end('Unauthorized') + }).listen(0) + + t.after(closeServerAsPromise(server)) + await once(server, 'listening') + + const response = await fetch(`http://localhost:${server.address().port}`) + + t.assert.strictEqual(response.status, 401) + t.assert.strictEqual(requestCount, 1, 'should only make one request, not retry infinitely') +}) + +test('Receiving a 401 status code with credentials include should not cause infinite retry loop', async (t) => { + let requestCount = 0 + + const server = createServer({ joinDuplicateHeaders: true }, (req, res) => { + requestCount++ + res.statusCode = 401 + res.setHeader('WWW-Authenticate', 'Basic realm="test"') + res.end('Unauthorized') + }).listen(0) + + t.after(closeServerAsPromise(server)) + await once(server, 'listening') + + const response = await fetch(`http://localhost:${server.address().port}`, { + credentials: 'include' + }) + + t.assert.strictEqual(response.status, 401) + t.assert.strictEqual(requestCount, 1, 'should only make one request, not retry infinitely') +})