From 9e02506d48a9b5e28aeb741a9057998b5a40b556 Mon Sep 17 00:00:00 2001 From: raj pandey Date: Wed, 21 Jan 2026 15:04:20 +0530 Subject: [PATCH] Fix: Skip token refresh and preserve error_code --- .talismanrc | 3 + CHANGELOG.md | 4 + lib/core/concurrency-queue.js | 6 ++ package-lock.json | 4 +- package.json | 2 +- test/unit/concurrency-Queue-test.js | 109 ++++++++++++++++++++++++++++ 6 files changed, 125 insertions(+), 3 deletions(-) diff --git a/.talismanrc b/.talismanrc index acb761df..400cd5a8 100644 --- a/.talismanrc +++ b/.talismanrc @@ -34,7 +34,10 @@ fileignoreconfig: checksum: 4043efd843e24da9afd0272c55ef4b0432e3374b2ca12b913f1a6654df3f62be - filename: test/unit/contentstack-test.js checksum: 2597efae3c1ab8cc173d5bf205f1c76932211f8e0eb2a16444e055d83481976c + - filename: test/unit/concurrency-Queue-test.js + checksum: 186438f9eb9ba4e7fd7f335dbea2afbae9ae969b7ae3ab1b517ec7a1633d255e version: "1.0" + diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e4108fe..f276c349 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [v1.27.3](https://github.com/contentstack/contentstack-management-javascript/tree/v1.27.3) (2026-01-21) + - Fix + - Skip token refresh and preserve error_code 294 when 2FA is required (error_code 294 with 401 status) to prevent error code conversion from 294 to 401 + ## [v1.27.2](https://github.com/contentstack/contentstack-management-javascript/tree/v1.27.2) (2026-01-12) - Enhancement - Improved error messages diff --git a/lib/core/concurrency-queue.js b/lib/core/concurrency-queue.js index 3bf155dc..0e526a18 100644 --- a/lib/core/concurrency-queue.js +++ b/lib/core/concurrency-queue.js @@ -468,6 +468,12 @@ export function ConcurrencyQueue ({ axios, config }) { return Promise.reject(responseHandler(error)) } } else if ((response.status === 401 && this.config.refreshToken)) { + // If error_code is 294 (2FA required), don't retry/refresh - pass through the error as-is + const apiErrorCode = response.data?.error_code + if (apiErrorCode === 294) { + return Promise.reject(error) + } + retryErrorType = `Error with status: ${response.status}` networkError++ diff --git a/package-lock.json b/package-lock.json index f8b2033e..d1702c11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@contentstack/management", - "version": "1.27.2", + "version": "1.27.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@contentstack/management", - "version": "1.27.2", + "version": "1.27.3", "license": "MIT", "dependencies": { "@contentstack/utils": "^1.6.3", diff --git a/package.json b/package.json index 4e395998..5f091008 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@contentstack/management", - "version": "1.27.2", + "version": "1.27.3", "description": "The Content Management API is used to manage the content of your Contentstack account", "main": "./dist/node/contentstack-management.js", "browser": "./dist/web/contentstack-management.js", diff --git a/test/unit/concurrency-Queue-test.js b/test/unit/concurrency-Queue-test.js index f10cc9c7..44b0b6cd 100644 --- a/test/unit/concurrency-Queue-test.js +++ b/test/unit/concurrency-Queue-test.js @@ -530,6 +530,115 @@ describe('Concurrency queue test', () => { }) .catch(done) }) + + it('should not refresh token when error_code is 294 (2FA required) with 401 status', done => { + let refreshTokenCallCount = 0 + const refreshTokenStub = sinon.stub().callsFake(() => { + refreshTokenCallCount++ + return Promise.resolve({ authorization: 'Bearer new_token' }) + }) + + const axiosWithRefresh = client({ + baseURL: `${host}:${port}`, + authorization: 'Bearer ', + refreshToken: refreshTokenStub + }) + + const mock = new MockAdapter(axiosWithRefresh.axiosInstance) + mock.onGet('/test2fa').reply(401, { + error_message: 'Please login using the Two-Factor verification Token', + error_code: 294, + errors: [], + statusCode: 401, + tfa_type: 'totp_authenticator' + }) + + axiosWithRefresh.axiosInstance.get('/test2fa') + .then(() => { + done(new Error('Expected error was not thrown')) + }) + .catch((error) => { + // Verify refreshToken was NOT called + expect(refreshTokenCallCount).to.equal(0) + expect(refreshTokenStub.called).to.equal(false) + + // Verify the raw error response has error_code 294 + expect(error.response.status).to.equal(401) + expect(error.response.data.error_code).to.equal(294) + expect(error.response.data.error_message).to.include('Two-Factor verification') + expect(error.response.data.tfa_type).to.equal('totp_authenticator') + done() + }) + .catch(done) + }) + + it('should refresh token when 401 status without error_code 294', done => { + const axios2 = client({ + baseURL: `${host}:${port}` + }) + const axios = client({ + baseURL: `${host}:${port}`, + authorization: 'Bearer ', + refreshToken: () => { + return new Promise((resolve, reject) => { + return axios2.login().then((res) => { + resolve({ authorization: res.token }) + }).catch((error) => { + reject(error) + }) + }) + } + }) + + // First request will fail with 401, trigger refresh, then succeed + axios.axiosInstance.get('/unauthorized') + .then((response) => { + // Should succeed after token refresh + expect(response.data.randomInteger).to.equal(123) + done() + }) + .catch(done) + }) + + it('should preserve error_code 294 when present with 401 status', done => { + const refreshTokenStub = sinon.stub() + refreshTokenStub.returns(Promise.resolve({ authorization: 'Bearer new_token' })) + + const axiosWithRefresh = client({ + baseURL: `${host}:${port}`, + authorization: 'Bearer ', + refreshToken: refreshTokenStub + }) + + const mock = new MockAdapter(axiosWithRefresh.axiosInstance) + mock.onGet('/test2fa294').reply(401, { + error_message: 'Please login using the Two-Factor verification Token', + error_code: 294, + errors: [], + statusCode: 401, + tfa_type: 'totp_authenticator' + }) + + axiosWithRefresh.axiosInstance.get('/test2fa294') + .then(() => { + done(new Error('Expected error was not thrown')) + }) + .catch((error) => { + // Verify refreshToken was NOT called + expect(refreshTokenStub.called).to.equal(false) + + // Verify the raw error response preserves error_code 294 + expect(error.response.status).to.equal(401) + expect(error.response.data.error_code).to.equal(294) + expect(error.response.data.error_message).to.include('Two-Factor verification') + expect(error.response.data.tfa_type).to.equal('totp_authenticator') + + // The key test: error_code 294 should be preserved in response.data + // This ensures our fix in concurrency-queue.js is working (no token refresh attempted) + done() + }) + .catch(done) + }) }) function makeConcurrencyQueue (config) {