Skip to content

Commit 4acf4a7

Browse files
authored
Merge pull request #487 from contentstack/fix/DX-3929
Fix: Skip token refresh and preserve error_code
2 parents ef2b86b + 9e02506 commit 4acf4a7

File tree

6 files changed

+125
-3
lines changed

6 files changed

+125
-3
lines changed

.talismanrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ fileignoreconfig:
3434
checksum: 4043efd843e24da9afd0272c55ef4b0432e3374b2ca12b913f1a6654df3f62be
3535
- filename: test/unit/contentstack-test.js
3636
checksum: 2597efae3c1ab8cc173d5bf205f1c76932211f8e0eb2a16444e055d83481976c
37+
- filename: test/unit/concurrency-Queue-test.js
38+
checksum: 186438f9eb9ba4e7fd7f335dbea2afbae9ae969b7ae3ab1b517ec7a1633d255e
3739
version: "1.0"
3840

3941

4042

43+

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## [v1.27.3](https://github.com/contentstack/contentstack-management-javascript/tree/v1.27.3) (2026-01-21)
4+
- Fix
5+
- 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
6+
37
## [v1.27.2](https://github.com/contentstack/contentstack-management-javascript/tree/v1.27.2) (2026-01-12)
48
- Enhancement
59
- Improved error messages

lib/core/concurrency-queue.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,12 @@ export function ConcurrencyQueue ({ axios, config }) {
468468
return Promise.reject(responseHandler(error))
469469
}
470470
} else if ((response.status === 401 && this.config.refreshToken)) {
471+
// If error_code is 294 (2FA required), don't retry/refresh - pass through the error as-is
472+
const apiErrorCode = response.data?.error_code
473+
if (apiErrorCode === 294) {
474+
return Promise.reject(error)
475+
}
476+
471477
retryErrorType = `Error with status: ${response.status}`
472478
networkError++
473479

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@contentstack/management",
3-
"version": "1.27.2",
3+
"version": "1.27.3",
44
"description": "The Content Management API is used to manage the content of your Contentstack account",
55
"main": "./dist/node/contentstack-management.js",
66
"browser": "./dist/web/contentstack-management.js",

test/unit/concurrency-Queue-test.js

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,115 @@ describe('Concurrency queue test', () => {
530530
})
531531
.catch(done)
532532
})
533+
534+
it('should not refresh token when error_code is 294 (2FA required) with 401 status', done => {
535+
let refreshTokenCallCount = 0
536+
const refreshTokenStub = sinon.stub().callsFake(() => {
537+
refreshTokenCallCount++
538+
return Promise.resolve({ authorization: 'Bearer new_token' })
539+
})
540+
541+
const axiosWithRefresh = client({
542+
baseURL: `${host}:${port}`,
543+
authorization: 'Bearer <token_value>',
544+
refreshToken: refreshTokenStub
545+
})
546+
547+
const mock = new MockAdapter(axiosWithRefresh.axiosInstance)
548+
mock.onGet('/test2fa').reply(401, {
549+
error_message: 'Please login using the Two-Factor verification Token',
550+
error_code: 294,
551+
errors: [],
552+
statusCode: 401,
553+
tfa_type: 'totp_authenticator'
554+
})
555+
556+
axiosWithRefresh.axiosInstance.get('/test2fa')
557+
.then(() => {
558+
done(new Error('Expected error was not thrown'))
559+
})
560+
.catch((error) => {
561+
// Verify refreshToken was NOT called
562+
expect(refreshTokenCallCount).to.equal(0)
563+
expect(refreshTokenStub.called).to.equal(false)
564+
565+
// Verify the raw error response has error_code 294
566+
expect(error.response.status).to.equal(401)
567+
expect(error.response.data.error_code).to.equal(294)
568+
expect(error.response.data.error_message).to.include('Two-Factor verification')
569+
expect(error.response.data.tfa_type).to.equal('totp_authenticator')
570+
done()
571+
})
572+
.catch(done)
573+
})
574+
575+
it('should refresh token when 401 status without error_code 294', done => {
576+
const axios2 = client({
577+
baseURL: `${host}:${port}`
578+
})
579+
const axios = client({
580+
baseURL: `${host}:${port}`,
581+
authorization: 'Bearer <token_value>',
582+
refreshToken: () => {
583+
return new Promise((resolve, reject) => {
584+
return axios2.login().then((res) => {
585+
resolve({ authorization: res.token })
586+
}).catch((error) => {
587+
reject(error)
588+
})
589+
})
590+
}
591+
})
592+
593+
// First request will fail with 401, trigger refresh, then succeed
594+
axios.axiosInstance.get('/unauthorized')
595+
.then((response) => {
596+
// Should succeed after token refresh
597+
expect(response.data.randomInteger).to.equal(123)
598+
done()
599+
})
600+
.catch(done)
601+
})
602+
603+
it('should preserve error_code 294 when present with 401 status', done => {
604+
const refreshTokenStub = sinon.stub()
605+
refreshTokenStub.returns(Promise.resolve({ authorization: 'Bearer new_token' }))
606+
607+
const axiosWithRefresh = client({
608+
baseURL: `${host}:${port}`,
609+
authorization: 'Bearer <token_value>',
610+
refreshToken: refreshTokenStub
611+
})
612+
613+
const mock = new MockAdapter(axiosWithRefresh.axiosInstance)
614+
mock.onGet('/test2fa294').reply(401, {
615+
error_message: 'Please login using the Two-Factor verification Token',
616+
error_code: 294,
617+
errors: [],
618+
statusCode: 401,
619+
tfa_type: 'totp_authenticator'
620+
})
621+
622+
axiosWithRefresh.axiosInstance.get('/test2fa294')
623+
.then(() => {
624+
done(new Error('Expected error was not thrown'))
625+
})
626+
.catch((error) => {
627+
// Verify refreshToken was NOT called
628+
expect(refreshTokenStub.called).to.equal(false)
629+
630+
// Verify the raw error response preserves error_code 294
631+
expect(error.response.status).to.equal(401)
632+
expect(error.response.data.error_code).to.equal(294)
633+
expect(error.response.data.error_message).to.include('Two-Factor verification')
634+
expect(error.response.data.tfa_type).to.equal('totp_authenticator')
635+
636+
// The key test: error_code 294 should be preserved in response.data
637+
// This ensures our fix in concurrency-queue.js is working (no token refresh attempted)
638+
done()
639+
})
640+
.catch(done)
641+
})
533642
})
534643

535644
function makeConcurrencyQueue (config) {

0 commit comments

Comments
 (0)