From 9b30b03d94dd6422f92ec64d8da2a7a300b81f08 Mon Sep 17 00:00:00 2001 From: Nadeem Patwekar Date: Mon, 4 Aug 2025 14:25:34 +0530 Subject: [PATCH 01/10] chore: update README with import snippets --- README.md | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 921f2a1b..f844b6f3 100644 --- a/README.md +++ b/README.md @@ -19,12 +19,31 @@ Install it via npm: ```bash npm i @contentstack/management ``` -To import the SDK, use the following command: -```javascript -import contentstack from ‘@contentstack/management’ - -contentstackClient = contentstack.client() -``` +To import the SDK, use one of the following ways : +1. **JavaScript ES Modules** + ```javascript + import contentstack from '@contentstack/management'; + ``` + - Requires `"type": "module"` in package.json + +2. **TypeScript with esModuleInterop** + ```typescript + import contentstack from '@contentstack/management'; + ``` + - Requires `"esModuleInterop": true` in tsconfig.json + +3. **TypeScript Namespace Import** + ```typescript + import * as contentstack from '@contentstack/management'; + ``` + - Works regardless of esModuleInterop setting + +4. **TypeScript Destructuring** + ```typescript + import contentstack from '@contentstack/management'; + const { client } = contentstack; + ``` + - Access client function from default export ### Authentication To use this SDK, you need to authenticate your users by using the Authtoken, credentials, or Management Token (stack-level token). From 94a89e1f3593ced67ea5623721dd2f2d0f918af0 Mon Sep 17 00:00:00 2001 From: raj pandey Date: Mon, 11 Aug 2025 19:22:29 +0530 Subject: [PATCH 02/10] Feat: Added MFA support and version bump and lint issue fix --- .talismanrc | 8 +- lib/contentstackClient.js | 79 +++++++--- lib/core/contentstackError.js | 1 + package-lock.json | 71 ++++++++- package.json | 3 +- test/unit/ContentstackClient-test.js | 227 +++++++++++++++++++++++++++ types/contentstackClient.d.ts | 2 + 7 files changed, 362 insertions(+), 29 deletions(-) diff --git a/.talismanrc b/.talismanrc index b301278e..449b0fa4 100644 --- a/.talismanrc +++ b/.talismanrc @@ -9,7 +9,7 @@ fileignoreconfig: ignore_detectors: - filecontent - filename: package-lock.json - checksum: b043facad4b4aca7a013730746bdb9cb9e9dfca1e5d6faf11c068fc2525569c0 + checksum: c37956b0e6ccd5267dac7fd6f4347fbb5ad41defe1c42d39aaaeaf64d2cf8605 - filename: .husky/pre-commit checksum: 52a664f536cf5d1be0bea19cb6031ca6e8107b45b6314fe7d47b7fad7d800632 - filename: test/sanity-check/api/user-test.js @@ -26,4 +26,8 @@ fileignoreconfig: checksum: e8a32ffbbbdba2a15f3d327273f0a5b4eb33cf84cd346562596ab697125bbbc6 - filename: test/sanity-check/api/bulkOperation-test.js checksum: f40a14c84ab9a194aaf830ca68e14afde2ef83496a07d4a6393d7e0bed15fb0e -version: "1.0" + - filename: lib/contentstackClient.js + checksum: b61fcf4fea88b8328ffa9d29a1ed88fe23ee9c9ef0cbd458721dcb7c82d2432b + - filename: test/unit/ContentstackClient-test.js + checksum: 210e75d6620fcae59ca9db6ecab6101736e8227f5a557a111ac72176056a5769 +version: "1.0" \ No newline at end of file diff --git a/lib/contentstackClient.js b/lib/contentstackClient.js index 830be9b6..a2c9d2fe 100644 --- a/lib/contentstackClient.js +++ b/lib/contentstackClient.js @@ -7,6 +7,7 @@ import cloneDeep from 'lodash/cloneDeep' import { User } from './user/index' import error from './core/contentstackError' import OAuthHandler from './core/oauthHandler' +import { authenticator } from 'otplib' export default function contentstackClient ({ http }) { /** @@ -16,7 +17,8 @@ export default function contentstackClient ({ http }) { * @param {Object} parameters - login parameters * @prop {string} parameters.email - email id for user to login * @prop {string} parameters.password - password for user to login - * @prop {string} parameters.token - token for user to login + * @prop {string} parameters.token - token for user to login (2FA token) + * @prop {string} parameters.tfa_token - tfa token for user to login (2FA token) * @returns {Promise} * @example * import * as contentstack from '@contentstack/management' @@ -26,9 +28,28 @@ export default function contentstackClient ({ http }) { * .then(() => console.log('Logged in successfully')) * */ - function login (requestBody, params = {}) { + /** + * Login function that supports both regular login and TOTP-based 2FA + * @param {Object} requestBody - Login credentials + * @param {string} requestBody.email - Email address + * @param {string} requestBody.password - Password + * @param {string} [requestBody.token] - 2FA token + * @param {string} [requestBody.tfa_token] - Alternative 2FA token + * @param {string} [requestBody.mfaSecret] - TOTP secret key for generating 2FA token + * @param {Object} params - Additional parameters + * @returns {Promise} + */ + function login (requestBody = {}, params = {}) { http.defaults.versioningStrategy = 'path' + const { mfaSecret, ...credentials } = requestBody + requestBody = credentials + + if (requestBody?.tfa_token) { + // tfa_token is already in credentials, no need to do anything + } else if (mfaSecret) { + requestBody.tfa_token = authenticator.generate(mfaSecret) + } return http.post('/user-session', { user: requestBody }, { params: params }) .then((response) => { if (response.data.user != null && response.data.user.authtoken != null) { @@ -55,10 +76,9 @@ export default function contentstackClient ({ http }) { */ function getUser (params = {}) { http.defaults.versioningStrategy = 'path' - return http.get('/user', { params: params }) - .then((response) => { - return new User(http, response.data) - }, error) + return http.get('/user', { params: params }).then((response) => { + return new User(http, response.data) + }, error) } /** * @description Get Stack instance. A stack is a space that stores the content of a project. @@ -127,13 +147,16 @@ export default function contentstackClient ({ http }) { */ function organization (uid = null) { http.defaults.versioningStrategy = 'path' - return new Organization(http, uid !== null ? { organization: { uid: uid } } : null) + return new Organization( + http, + uid !== null ? { organization: { uid: uid } } : null + ) } /** * @description The Log out of your account call is used to sign out the user of Contentstack account. * @memberof ContentstackClient - * @param {String} authtoken - Authtoken to logout from. + * @param {String} authtoken - Authtoken to logout from. * @func logout * @returns {Object} Response object. * @@ -152,25 +175,25 @@ export default function contentstackClient ({ http }) { function logout (authtoken) { http.defaults.versioningStrategy = 'path' if (authtoken !== undefined) { - return http.delete('/user-session', { - headers: { - authtoken: authtoken - } - }) + return http + .delete('/user-session', { + headers: { + authtoken: authtoken + } + }) .then((response) => { return response.data }, error) } - return http.delete('/user-session') - .then((response) => { - if (http.defaults.headers.common) { - delete http.defaults.headers.common.authtoken - } - delete http.defaults.headers.authtoken - delete http.httpClientParams.authtoken - delete http.httpClientParams.headers.authtoken - return response.data - }, error) + return http.delete('/user-session').then((response) => { + if (http.defaults.headers.common) { + delete http.defaults.headers.common.authtoken + } + delete http.defaults.headers.authtoken + delete http.httpClientParams.authtoken + delete http.httpClientParams.headers.authtoken + return response.data + }, error) } /** @@ -201,7 +224,15 @@ export default function contentstackClient ({ http }) { const responseType = params.responseType || 'code' const scope = params.scope const clientSecret = params.clientSecret - return new OAuthHandler(http, appId, clientId, redirectUri, clientSecret, responseType, scope) + return new OAuthHandler( + http, + appId, + clientId, + redirectUri, + clientSecret, + responseType, + scope + ) } return { diff --git a/lib/core/contentstackError.js b/lib/core/contentstackError.js index a962a937..89a51b03 100644 --- a/lib/core/contentstackError.js +++ b/lib/core/contentstackError.js @@ -34,6 +34,7 @@ export default function error (errorResponse) { errorDetails.errorCode = data.error_code || 0 errorDetails.errors = data.errors || {} errorDetails.error = data.error || '' + errorDetails.tfa_type = data.tfa_type } var error = new Error() diff --git a/package-lock.json b/package-lock.json index 01a343e3..fe2ccde9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@contentstack/management", - "version": "1.23.2", + "version": "1.24.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@contentstack/management", - "version": "1.23.2", + "version": "1.24.0", "license": "MIT", "dependencies": { "assert": "^2.1.0", @@ -15,6 +15,7 @@ "form-data": "^4.0.4", "husky": "^9.1.7", "lodash": "^4.17.21", + "otplib": "^12.0.1", "qs": "^6.14.0", "stream-browserify": "^3.0.0" }, @@ -2682,6 +2683,53 @@ "node": ">= 8" } }, + "node_modules/@otplib/core": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/core/-/core-12.0.1.tgz", + "integrity": "sha512-4sGntwbA/AC+SbPhbsziRiD+jNDdIzsZ3JUyfZwjtKyc/wufl1pnSIaG4Uqx8ymPagujub0o92kgBnB89cuAMA==", + "license": "MIT" + }, + "node_modules/@otplib/plugin-crypto": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/plugin-crypto/-/plugin-crypto-12.0.1.tgz", + "integrity": "sha512-qPuhN3QrT7ZZLcLCyKOSNhuijUi9G5guMRVrxq63r9YNOxxQjPm59gVxLM+7xGnHnM6cimY57tuKsjK7y9LM1g==", + "license": "MIT", + "dependencies": { + "@otplib/core": "^12.0.1" + } + }, + "node_modules/@otplib/plugin-thirty-two": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/plugin-thirty-two/-/plugin-thirty-two-12.0.1.tgz", + "integrity": "sha512-MtT+uqRso909UkbrrYpJ6XFjj9D+x2Py7KjTO9JDPhL0bJUYVu5kFP4TFZW4NFAywrAtFRxOVY261u0qwb93gA==", + "license": "MIT", + "dependencies": { + "@otplib/core": "^12.0.1", + "thirty-two": "^1.0.2" + } + }, + "node_modules/@otplib/preset-default": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/preset-default/-/preset-default-12.0.1.tgz", + "integrity": "sha512-xf1v9oOJRyXfluBhMdpOkr+bsE+Irt+0D5uHtvg6x1eosfmHCsCC6ej/m7FXiWqdo0+ZUI6xSKDhJwc8yfiOPQ==", + "license": "MIT", + "dependencies": { + "@otplib/core": "^12.0.1", + "@otplib/plugin-crypto": "^12.0.1", + "@otplib/plugin-thirty-two": "^12.0.1" + } + }, + "node_modules/@otplib/preset-v11": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/preset-v11/-/preset-v11-12.0.1.tgz", + "integrity": "sha512-9hSetMI7ECqbFiKICrNa4w70deTUfArtwXykPUvSHWOdzOlfa9ajglu7mNCntlvxycTiOAXkQGwjQCzzDEMRMg==", + "license": "MIT", + "dependencies": { + "@otplib/core": "^12.0.1", + "@otplib/plugin-crypto": "^12.0.1", + "@otplib/plugin-thirty-two": "^12.0.1" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "dev": true, @@ -10181,6 +10229,17 @@ "dev": true, "license": "MIT" }, + "node_modules/otplib": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/otplib/-/otplib-12.0.1.tgz", + "integrity": "sha512-xDGvUOQjop7RDgxTQ+o4pOol0/3xSZzawTiPKRrHnQWAy0WjhNs/5HdIDJCrqC4MBynmjXgULc6YfioaxZeFgg==", + "license": "MIT", + "dependencies": { + "@otplib/core": "^12.0.1", + "@otplib/preset-default": "^12.0.1", + "@otplib/preset-v11": "^12.0.1" + } + }, "node_modules/own-keys": { "version": "1.0.1", "dev": true, @@ -12117,6 +12176,14 @@ "dev": true, "license": "MIT" }, + "node_modules/thirty-two": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz", + "integrity": "sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA==", + "engines": { + "node": ">=0.2.6" + } + }, "node_modules/tmpl": { "version": "1.0.5", "dev": true, diff --git a/package.json b/package.json index b61a663e..4fcc7f9b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@contentstack/management", - "version": "1.23.2", + "version": "1.24.0", "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", @@ -58,6 +58,7 @@ "form-data": "^4.0.4", "husky": "^9.1.7", "lodash": "^4.17.21", + "otplib": "^12.0.1", "qs": "^6.14.0", "stream-browserify": "^3.0.0" }, diff --git a/test/unit/ContentstackClient-test.js b/test/unit/ContentstackClient-test.js index 0bfee4e3..252e5260 100644 --- a/test/unit/ContentstackClient-test.js +++ b/test/unit/ContentstackClient-test.js @@ -202,6 +202,233 @@ describe('Contentstack Client', () => { done() }) + describe('Enhanced Login Tests', () => { + let mock + + beforeEach(() => { + mock = new MockAdapter(axios) + }) + + it('should handle direct login with tfa_token', done => { + mock.onPost('/user-session').reply(config => { + const data = JSON.parse(config.data) + expect(data.user).to.deep.equal({ + email: 'test@example.com', + password: 'password123', + tfa_token: '654321' + }) + return [200, { + user: { + authtoken: 'Test Auth With TFA', + email: 'test@example.com' + } + }] + }) + + ContentstackClient({ http: axios }) + .login({ + email: 'test@example.com', + password: 'password123', + tfa_token: '654321' + }) + .then(response => { + expect(response.user.authtoken).to.equal('Test Auth With TFA') + expect(response.user.email).to.equal('test@example.com') + done() + }) + .catch(done) + }) + + it('should handle login with TOTP secret', done => { + const mfaSecret = 'JBSWY3DPEHPK3PXP' + + mock.onPost('/user-session').reply(config => { + const data = JSON.parse(config.data) + expect(data.user.email).to.equal('test@example.com') + expect(data.user.password).to.equal('password123') + expect(data.user.tfa_token).to.have.lengthOf(6) + expect(data.user.tfa_token).to.match(/^\d{6}$/) + expect(data.user.mfaSecret).to.equal(undefined) + return [200, { + user: { + authtoken: 'Test Auth With TOTP', + email: 'test@example.com' + } + }] + }) + + ContentstackClient({ http: axios }) + .login({ + email: 'test@example.com', + password: 'password123', + mfaSecret: mfaSecret + }) + .then(response => { + expect(response.user.authtoken).to.equal('Test Auth With TOTP') + expect(response.user.email).to.equal('test@example.com') + done() + }) + .catch(done) + }) + + it('should prioritize tfa_token over mfaSecret', done => { + mock.onPost('/user-session').reply(config => { + const data = JSON.parse(config.data) + console.log(data) + expect(data.user).to.deep.equal({ + email: 'test@example.com', + password: 'password123', + tfa_token: '654321' // Should use this directly instead of generating from mfaSecret + }) + return [200, { + user: { + authtoken: 'Test Auth Direct TFA', + email: 'test@example.com' + } + }] + }) + + ContentstackClient({ http: axios }) + .login({ + email: 'test@example.com', + password: 'password123', + tfa_token: '654321', + mfaSecret: 'JBSWY3DPEHPK3PXP' // This should be ignored + }) + .then(response => { + expect(response.user.authtoken).to.equal('Test Auth Direct TFA') + expect(response.user.email).to.equal('test@example.com') + done() + }) + .catch(done) + }) + + it('should handle 2FA requirement response', done => { + mock.onPost('/user-session').reply(function (config) { + const data = JSON.parse(config.data) + expect(data.user).to.deep.equal({ + email: 'test@example.com', + password: 'password123' + }) + return [422, { + error_message: 'Please login using the Two-Factor verification Token', + error_code: 294, + errors: [], + tfa_type: 'totp_authenticator' + }] + }) + + ContentstackClient({ http: axios }) + .login({ + email: 'test@example.com', + password: 'password123' + }) + .then(() => { + done(new Error('Expected 2FA error was not thrown')) + }) + .catch(error => { + try { + const errorData = JSON.parse(error.message) + expect(errorData.errorCode).to.equal(294) + expect(errorData.errorMessage).to.include('Two-Factor verification') + expect(errorData.errors).to.be.an('array').with.lengthOf(0) + expect(errorData.tfa_type).to.equal('totp_authenticator') + done() + } catch (e) { + done(e) + } + }) + }) + + it('should handle invalid 2FA token', done => { + mock.onPost('/user-session').reply(config => { + const data = JSON.parse(config.data) + expect(data.user.tfa_token).to.equal('111111') + return [422, { + error_message: 'Invalid verification code', + error_code: 295, + errors: [] + }] + }) + + ContentstackClient({ http: axios }) + .login({ + email: 'test@example.com', + password: 'password123', + tfa_token: '111111' + }) + .then(() => { + done(new Error('Expected invalid token error was not thrown')) + }) + .catch(error => { + expect(error.errorCode).to.equal(295) + expect(error.errorMessage).to.include('Invalid verification code') + expect(error.errors).to.be.an('array').with.lengthOf(0) + done() + }) + }) + + it('should handle both tfa_token and mfaSecret with tfa_token taking precedence', done => { + mock.onPost('/user-session').reply(config => { + const data = JSON.parse(config.data) + expect(data.user).to.deep.equal({ + email: 'test@example.com', + password: 'password123', + tfa_token: '123456' // Should use provided tfa_token, not generate from mfaSecret + }) + return [200, { + user: { + authtoken: 'Test Auth', + email: 'test@example.com' + } + }] + }) + + ContentstackClient({ http: axios }) + .login({ + email: 'test@example.com', + password: 'password123', + tfa_token: '123456', + mfaSecret: 'JBSWY3DPEHPK3PXP' // This should be ignored + }) + .then(response => { + expect(response.user.authtoken).to.equal('Test Auth') + expect(response.user.email).to.equal('test@example.com') + done() + }) + .catch(done) + }) + + it('should generate tfa_token from mfaSecret when no tfa_token provided', done => { + mock.onPost('/user-session').reply(config => { + const data = JSON.parse(config.data) + expect(data.user.email).to.equal('test@example.com') + expect(data.user.password).to.equal('password123') + expect(data.user.tfa_token).to.match(/^\d{6}$/) // Should be 6-digit number + expect(data.user.mfaSecret).to.equal(undefined) // Should not be in request + return [200, { + user: { + authtoken: 'Test Auth', + email: 'test@example.com' + } + }] + }) + + ContentstackClient({ http: axios }) + .login({ + email: 'test@example.com', + password: 'password123', + mfaSecret: 'JBSWY3DPEHPK3PXP' + }) + .then(response => { + expect(response.user.authtoken).to.equal('Test Auth') + expect(response.user.email).to.equal('test@example.com') + done() + }) + .catch(done) + }) + }) + it('Contentstack Client Stack with API key and management token test', done => { const stack = ContentstackClient({ http: axios }).stack({ api_key: 'stack_api_key', management_token: 'stack_management_token' }) expect(stack.urlPath).to.be.equal('/stacks') diff --git a/types/contentstackClient.d.ts b/types/contentstackClient.d.ts index 2689c0ed..933a3f8e 100644 --- a/types/contentstackClient.d.ts +++ b/types/contentstackClient.d.ts @@ -51,6 +51,8 @@ export interface LoginDetails { email: string, password: string, token?: string + tfa_token?: string + mfaSecret?: string } export interface LoginResponse extends Response { From 2c406788a39be6863a991e31bdb61557218c602c Mon Sep 17 00:00:00 2001 From: raj pandey Date: Mon, 11 Aug 2025 19:23:55 +0530 Subject: [PATCH 03/10] Added changelog --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a78c58d..78d146e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog -## [v1.23.1](https://github.com/contentstack/contentstack-management-javascript/tree/v1.24.0) (2025-07-28) +## [v1.24.0](https://github.com/contentstack/contentstack-management-javascript/tree/v1.24.0) (2025-08-18) + - Feat + - Added Support for MFA + +## [v1.23.1](https://github.com/contentstack/contentstack-management-javascript/tree/v1.23.1) (2025-07-28) - Fix - Add asset types support in bulk operations From 7d2ddf9bddabf820fd10e95747321d3ce5294833 Mon Sep 17 00:00:00 2001 From: raj pandey Date: Mon, 11 Aug 2025 19:30:37 +0530 Subject: [PATCH 04/10] Used dummy values --- .talismanrc | 2 +- test/unit/ContentstackClient-test.js | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.talismanrc b/.talismanrc index 449b0fa4..0ea2cd6a 100644 --- a/.talismanrc +++ b/.talismanrc @@ -29,5 +29,5 @@ fileignoreconfig: - filename: lib/contentstackClient.js checksum: b61fcf4fea88b8328ffa9d29a1ed88fe23ee9c9ef0cbd458721dcb7c82d2432b - filename: test/unit/ContentstackClient-test.js - checksum: 210e75d6620fcae59ca9db6ecab6101736e8227f5a557a111ac72176056a5769 + checksum: e712b74f37ea15afd2ac095fc2154f8c9f2ffe955659e77651da4a0d7079ec88 version: "1.0" \ No newline at end of file diff --git a/test/unit/ContentstackClient-test.js b/test/unit/ContentstackClient-test.js index 252e5260..e2a27553 100644 --- a/test/unit/ContentstackClient-test.js +++ b/test/unit/ContentstackClient-test.js @@ -34,7 +34,7 @@ describe('Contentstack Client', () => { }) .catch(done) }) - + // Thi it('Contentstack Client get user info', done => { var mock = new MockAdapter(axios) mock.onGet('/user').reply(200, { @@ -215,7 +215,7 @@ describe('Contentstack Client', () => { expect(data.user).to.deep.equal({ email: 'test@example.com', password: 'password123', - tfa_token: '654321' + tfa_token: '123456' }) return [200, { user: { @@ -229,7 +229,7 @@ describe('Contentstack Client', () => { .login({ email: 'test@example.com', password: 'password123', - tfa_token: '654321' + tfa_token: '123456' }) .then(response => { expect(response.user.authtoken).to.equal('Test Auth With TFA') @@ -240,7 +240,7 @@ describe('Contentstack Client', () => { }) it('should handle login with TOTP secret', done => { - const mfaSecret = 'JBSWY3DPEHPK3PXP' + const mfaSecret = 'MFASECRET' mock.onPost('/user-session').reply(config => { const data = JSON.parse(config.data) @@ -278,7 +278,7 @@ describe('Contentstack Client', () => { expect(data.user).to.deep.equal({ email: 'test@example.com', password: 'password123', - tfa_token: '654321' // Should use this directly instead of generating from mfaSecret + tfa_token: '123456' }) return [200, { user: { @@ -292,8 +292,8 @@ describe('Contentstack Client', () => { .login({ email: 'test@example.com', password: 'password123', - tfa_token: '654321', - mfaSecret: 'JBSWY3DPEHPK3PXP' // This should be ignored + tfa_token: '123456', + mfaSecret: 'MFASECRET' }) .then(response => { expect(response.user.authtoken).to.equal('Test Auth Direct TFA') @@ -374,7 +374,7 @@ describe('Contentstack Client', () => { expect(data.user).to.deep.equal({ email: 'test@example.com', password: 'password123', - tfa_token: '123456' // Should use provided tfa_token, not generate from mfaSecret + tfa_token: '123456' }) return [200, { user: { @@ -389,7 +389,7 @@ describe('Contentstack Client', () => { email: 'test@example.com', password: 'password123', tfa_token: '123456', - mfaSecret: 'JBSWY3DPEHPK3PXP' // This should be ignored + mfaSecret: 'MFASECRET' }) .then(response => { expect(response.user.authtoken).to.equal('Test Auth') @@ -404,8 +404,8 @@ describe('Contentstack Client', () => { const data = JSON.parse(config.data) expect(data.user.email).to.equal('test@example.com') expect(data.user.password).to.equal('password123') - expect(data.user.tfa_token).to.match(/^\d{6}$/) // Should be 6-digit number - expect(data.user.mfaSecret).to.equal(undefined) // Should not be in request + expect(data.user.tfa_token).to.match(/^\d{6}$/) + expect(data.user.mfaSecret).to.equal(undefined) return [200, { user: { authtoken: 'Test Auth', @@ -418,7 +418,7 @@ describe('Contentstack Client', () => { .login({ email: 'test@example.com', password: 'password123', - mfaSecret: 'JBSWY3DPEHPK3PXP' + mfaSecret: 'MFASECRET' }) .then(response => { expect(response.user.authtoken).to.equal('Test Auth') From 4ca643607ccf6d57dba7d7c9b45cc387ce0f8458 Mon Sep 17 00:00:00 2001 From: raj pandey Date: Mon, 11 Aug 2025 19:42:13 +0530 Subject: [PATCH 05/10] Removed unused comments --- test/unit/ContentstackClient-test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/unit/ContentstackClient-test.js b/test/unit/ContentstackClient-test.js index e2a27553..f09784da 100644 --- a/test/unit/ContentstackClient-test.js +++ b/test/unit/ContentstackClient-test.js @@ -34,7 +34,6 @@ describe('Contentstack Client', () => { }) .catch(done) }) - // Thi it('Contentstack Client get user info', done => { var mock = new MockAdapter(axios) mock.onGet('/user').reply(200, { From d0e5c09a274eb5ba98088815406420f2579bea43 Mon Sep 17 00:00:00 2001 From: raj pandey Date: Tue, 12 Aug 2025 12:46:19 +0530 Subject: [PATCH 06/10] Removed the Empty if block --- lib/contentstackClient.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/contentstackClient.js b/lib/contentstackClient.js index a2c9d2fe..76941554 100644 --- a/lib/contentstackClient.js +++ b/lib/contentstackClient.js @@ -45,9 +45,7 @@ export default function contentstackClient ({ http }) { const { mfaSecret, ...credentials } = requestBody requestBody = credentials - if (requestBody?.tfa_token) { - // tfa_token is already in credentials, no need to do anything - } else if (mfaSecret) { + if (!requestBody.tfa_token && mfaSecret) { requestBody.tfa_token = authenticator.generate(mfaSecret) } return http.post('/user-session', { user: requestBody }, { params: params }) From 8fff7fc7ec65060807bfa1431e5e82eb5ee55a7d Mon Sep 17 00:00:00 2001 From: raj pandey Date: Tue, 12 Aug 2025 13:34:04 +0530 Subject: [PATCH 07/10] Updated the comment --- lib/contentstackClient.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/contentstackClient.js b/lib/contentstackClient.js index 76941554..d7a5104a 100644 --- a/lib/contentstackClient.js +++ b/lib/contentstackClient.js @@ -33,9 +33,9 @@ export default function contentstackClient ({ http }) { * @param {Object} requestBody - Login credentials * @param {string} requestBody.email - Email address * @param {string} requestBody.password - Password - * @param {string} [requestBody.token] - 2FA token - * @param {string} [requestBody.tfa_token] - Alternative 2FA token + * @param {string} [requestBody.tfa_token] - 2FA Token * @param {string} [requestBody.mfaSecret] - TOTP secret key for generating 2FA token + * @param {string} [requestBody.token] - token for user to login * @param {Object} params - Additional parameters * @returns {Promise} */ From ee6c1b6bed04529a1b44d92056421981f030ff0a Mon Sep 17 00:00:00 2001 From: raj pandey Date: Tue, 12 Aug 2025 13:50:20 +0530 Subject: [PATCH 08/10] Removed the token and added examples --- .talismanrc | 2 +- lib/contentstackClient.js | 20 ++++++++------------ types/contentstackClient.d.ts | 1 - 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/.talismanrc b/.talismanrc index 0ea2cd6a..7712feec 100644 --- a/.talismanrc +++ b/.talismanrc @@ -27,7 +27,7 @@ fileignoreconfig: - filename: test/sanity-check/api/bulkOperation-test.js checksum: f40a14c84ab9a194aaf830ca68e14afde2ef83496a07d4a6393d7e0bed15fb0e - filename: lib/contentstackClient.js - checksum: b61fcf4fea88b8328ffa9d29a1ed88fe23ee9c9ef0cbd458721dcb7c82d2432b + checksum: b76ca091caa3a1b2658cd422a2d8ef3ac9996aea0aff3f982d56bb309a3d9fde - filename: test/unit/ContentstackClient-test.js checksum: e712b74f37ea15afd2ac095fc2154f8c9f2ffe955659e77651da4a0d7079ec88 version: "1.0" \ No newline at end of file diff --git a/lib/contentstackClient.js b/lib/contentstackClient.js index d7a5104a..7eb7594e 100644 --- a/lib/contentstackClient.js +++ b/lib/contentstackClient.js @@ -17,8 +17,8 @@ export default function contentstackClient ({ http }) { * @param {Object} parameters - login parameters * @prop {string} parameters.email - email id for user to login * @prop {string} parameters.password - password for user to login - * @prop {string} parameters.token - token for user to login (2FA token) * @prop {string} parameters.tfa_token - tfa token for user to login (2FA token) + * @prop {string} parameters.mfaSecret - TOTP secret key for generating 2FA token * @returns {Promise} * @example * import * as contentstack from '@contentstack/management' @@ -27,17 +27,13 @@ export default function contentstackClient ({ http }) { * client.login({ email: , password: }) * .then(() => console.log('Logged in successfully')) * - */ - /** - * Login function that supports both regular login and TOTP-based 2FA - * @param {Object} requestBody - Login credentials - * @param {string} requestBody.email - Email address - * @param {string} requestBody.password - Password - * @param {string} [requestBody.tfa_token] - 2FA Token - * @param {string} [requestBody.mfaSecret] - TOTP secret key for generating 2FA token - * @param {string} [requestBody.token] - token for user to login - * @param {Object} params - Additional parameters - * @returns {Promise} + * @example + * client.login({ email: , password: , tfa_token: }) + * .then(() => console.log('Logged in successfully')) + * + * @example + * client.login({ email: , password: , mfaSecret: }) + * .then(() => console.log('Logged in successfully')) */ function login (requestBody = {}, params = {}) { http.defaults.versioningStrategy = 'path' diff --git a/types/contentstackClient.d.ts b/types/contentstackClient.d.ts index 933a3f8e..fc2c65d5 100644 --- a/types/contentstackClient.d.ts +++ b/types/contentstackClient.d.ts @@ -50,7 +50,6 @@ export interface ContentstackConfig extends AxiosRequestConfig, ContentstackToke export interface LoginDetails { email: string, password: string, - token?: string tfa_token?: string mfaSecret?: string } From 66b23f8f247de3f145ebe1dd2263943c0838df5c Mon Sep 17 00:00:00 2001 From: harshithad0703 <104908717+harshithad0703@users.noreply.github.com> Date: Thu, 14 Aug 2025 13:08:28 +0530 Subject: [PATCH 09/10] Update sanity-report-dev11.js --- sanity-report-dev11.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sanity-report-dev11.js b/sanity-report-dev11.js index 1a9e6042..da69935a 100644 --- a/sanity-report-dev11.js +++ b/sanity-report-dev11.js @@ -4,7 +4,6 @@ const fs = require("fs"); dotenv.config(); -const user1 = process.env.USER1; const user2 = process.env.USER2; const user3 = process.env.USER3; const user4 = process.env.USER4; @@ -35,7 +34,7 @@ const goCdServer = process.env.GOCD_SERVER; const reportUrl = `http://${goCdServer}/go/files/${pipelineName}/${pipelineCounter}/sanity/1/sanity/test-results/mochawesome-report/sanity-report.html`; let tagUsers = - failedTests > 0 ? `<@${user1}> <@${user2}> <@${user3}> <@${user4}>` : ""; + failedTests > 0 ? `<@${user2}> <@${user3}> <@${user4}>` : ""; const slackMessage = { text: `Dev11, SDK-JS-CMA Sanity\n*Result:* ${resultMessage}. ${durationInMinutes}m ${durationInSeconds}s\n*Failed Tests:* ${failedTests}\n<${reportUrl}|View Report>\n${tagUsers}`, From c6077adcac16648a9c88da40908ab65b6c8a7705 Mon Sep 17 00:00:00 2001 From: harshithad0703 <104908717+harshithad0703@users.noreply.github.com> Date: Thu, 14 Aug 2025 13:12:39 +0530 Subject: [PATCH 10/10] Update .talismanrc --- .talismanrc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.talismanrc b/.talismanrc index 7712feec..32fad92e 100644 --- a/.talismanrc +++ b/.talismanrc @@ -29,5 +29,5 @@ fileignoreconfig: - filename: lib/contentstackClient.js checksum: b76ca091caa3a1b2658cd422a2d8ef3ac9996aea0aff3f982d56bb309a3d9fde - filename: test/unit/ContentstackClient-test.js - checksum: e712b74f37ea15afd2ac095fc2154f8c9f2ffe955659e77651da4a0d7079ec88 -version: "1.0" \ No newline at end of file + checksum: 974a4f335aef025b657d139bb290233a69bed1976b947c3c674e97baffe4ce2f +version: "1.0"