diff --git a/.github/workflows/issues-jira.yml b/.github/workflows/issues-jira.yml new file mode 100644 index 0000000..7bf0469 --- /dev/null +++ b/.github/workflows/issues-jira.yml @@ -0,0 +1,31 @@ +name: Create Jira Ticket for Github Issue + +on: + issues: + types: [opened] + +jobs: + issue-jira: + runs-on: ubuntu-latest + steps: + + - name: Login to Jira + uses: atlassian/gajira-login@master + env: + JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} + JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} + JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} + + - name: Create Jira Issue + id: create_jira + uses: atlassian/gajira-create@master + with: + project: ${{ secrets.JIRA_PROJECT }} + issuetype: ${{ secrets.JIRA_ISSUE_TYPE }} + summary: Github | Issue | ${{ github.event.repository.name }} | ${{ github.event.issue.title }} + description: | + *GitHub Issue:* ${{ github.event.issue.html_url }} + + *Description:* + ${{ github.event.issue.body }} + fields: "${{ secrets.ISSUES_JIRA_FIELDS }}" \ No newline at end of file diff --git a/.github/workflows/jira.yml b/.github/workflows/jira.yml deleted file mode 100644 index 250abc7..0000000 --- a/.github/workflows/jira.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Create JIRA ISSUE -on: - pull_request: - types: [opened] -jobs: - security-jira: - if: ${{ github.actor == 'dependabot[bot]' || github.actor == 'snyk-bot' || contains(github.event.pull_request.head.ref, 'snyk-fix-') || contains(github.event.pull_request.head.ref, 'snyk-upgrade-')}} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Login into JIRA - uses: atlassian/gajira-login@master - env: - JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} - JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} - JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} - - name: Create a JIRA Issue - id: create - uses: atlassian/gajira-create@master - with: - project: ${{ secrets.JIRA_PROJECT }} - issuetype: ${{ secrets.JIRA_ISSUE_TYPE }} - summary: | - Snyk | Vulnerability | ${{ github.event.repository.name }} | ${{ github.event.pull_request.title }} - description: | - PR: ${{ github.event.pull_request.html_url }} - - fields: "${{ secrets.JIRA_FIELDS }}" - - name: Transition issue - uses: atlassian/gajira-transition@v3 - with: - issue: ${{ steps.create.outputs.issue }} - transition: ${{ secrets.JIRA_TRANSITION }} diff --git a/.github/workflows/policy-scan.yml b/.github/workflows/policy-scan.yml new file mode 100644 index 0000000..ff25923 --- /dev/null +++ b/.github/workflows/policy-scan.yml @@ -0,0 +1,46 @@ +name: Checks the security policy and configurations +on: + pull_request: + types: [opened, synchronize, reopened] +jobs: + security-policy: + if: github.event.repository.visibility == 'public' + runs-on: ubuntu-latest + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@master + - name: Checks for SECURITY.md policy file + run: | + if ! [[ -f "SECURITY.md" || -f ".github/SECURITY.md" ]]; then exit 1; fi + security-license: + if: github.event.repository.visibility == 'public' + runs-on: ubuntu-latest + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@master + - name: Checks for License file + run: | + expected_license_files=("LICENSE" "LICENSE.txt" "LICENSE.md" "License.txt") + license_file_found=false + current_year=$(date +"%Y") + + for license_file in "${expected_license_files[@]}"; do + if [ -f "$license_file" ]; then + license_file_found=true + # check the license file for the current year, if not exists, exit with error + if ! grep -q "$current_year" "$license_file"; then + echo "License file $license_file does not contain the current year." + exit 2 + fi + break + fi + done + + if [ "$license_file_found" = false ]; then + echo "No license file found. Please add a license file to the repository." + exit 1 + fi \ No newline at end of file diff --git a/.github/workflows/sast-scan.yml b/.github/workflows/sast-scan.yml deleted file mode 100644 index 3b9521a..0000000 --- a/.github/workflows/sast-scan.yml +++ /dev/null @@ -1,11 +0,0 @@ -name: SAST Scan -on: - pull_request: - types: [opened, synchronize, reopened] -jobs: - security-sast: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Semgrep Scan - run: docker run -v /var/run/docker.sock:/var/run/docker.sock -v "${PWD}:/src" returntocorp/semgrep semgrep scan --config auto \ No newline at end of file diff --git a/.github/workflows/secrets-scan.yml b/.github/workflows/secrets-scan.yml new file mode 100644 index 0000000..049c02f --- /dev/null +++ b/.github/workflows/secrets-scan.yml @@ -0,0 +1,29 @@ +name: Secrets Scan +on: + pull_request: + types: [opened, synchronize, reopened] +jobs: + security-secrets: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: '2' + ref: '${{ github.event.pull_request.head.ref }}' + - run: | + git reset --soft HEAD~1 + - name: Install Talisman + run: | + # Download Talisman + wget https://github.com/thoughtworks/talisman/releases/download/v1.37.0/talisman_linux_amd64 -O talisman + + # Checksum verification + checksum=$(sha256sum ./talisman | awk '{print $1}') + if [ "$checksum" != "8e0ae8bb7b160bf10c4fa1448beb04a32a35e63505b3dddff74a092bccaaa7e4" ]; then exit 1; fi + + # Make it executable + chmod +x talisman + - name: Run talisman + run: | + # Run Talisman with the pre-commit hook + ./talisman --githook pre-commit \ No newline at end of file diff --git a/.talismanrc b/.talismanrc index e05ff54..afb99a8 100644 --- a/.talismanrc +++ b/.talismanrc @@ -1,5 +1,8 @@ threshold: medium fileignoreconfig: +- filename: .github/workflows/secrets-scan.yml + ignore_detectors: + - filecontent - filename: test/unit/mock/execution-mock.js checksum: 89d239d37c9d8d0cdb6ac61553a7d2e2d9115a10207f7c0b387c3565c9cb6564 - filename: package-lock.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d6d5f7..6ab4efd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ # Changelog + +## [v1.2.7](https://github.com/contentstack/contentstack-marketplace-sdk/tree/v1.2.6) (2024-05-15) + - Fixed base URL path logic in contentstackClient to handle when region and host not provided + ## [v1.2.6](https://github.com/contentstack/contentstack-marketplace-sdk/tree/v1.2.6) (2024-03-03) - Update sanity tests diff --git a/lib/contentstackClient.js b/lib/contentstackClient.js index 356e725..7947011 100644 --- a/lib/contentstackClient.js +++ b/lib/contentstackClient.js @@ -31,6 +31,10 @@ export default function contentstackClient ({ http }) { if (region && region !== Region.NA) { baseUrlPath = `https://${region}-api.contentstack.com:443/v3/user-session` + } else if (!region && http?.defaults?.host) { + baseUrlPath = `https://${http.defaults.host}:443/v3/user-session` + } else { + baseUrlPath = `https://api.contentstack.io:443/v3/user-session` } function login (requestBody, params = {}) { diff --git a/package-lock.json b/package-lock.json index b7caa3e..f077d0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@contentstack/marketplace-sdk", - "version": "1.2.6", + "version": "1.2.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@contentstack/marketplace-sdk", - "version": "1.2.6", + "version": "1.2.7", "license": "MIT", "dependencies": { - "axios": "^1.8.2" + "axios": "^1.8.3" }, "devDependencies": { "@babel/cli": "^7.26.4", @@ -4180,9 +4180,9 @@ } }, "node_modules/axios": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz", - "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", diff --git a/package.json b/package.json index cedd39c..ae09f59 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@contentstack/marketplace-sdk", - "version": "1.2.6", + "version": "1.2.7", "description": "The Contentstack Marketplace SDK is used to manage the content of your Contentstack marketplace apps", "main": "./dist/node/contentstack-marketplace.js", "browser": "./dist/web/contentstack-marketplace.js", @@ -92,6 +92,6 @@ "webpack-merge": "4.2.2" }, "dependencies": { - "axios": "^1.8.2" + "axios": "^1.8.3" } } diff --git a/sanity-report-dev11.js b/sanity-report-dev11.js index a726572..0c1ec34 100644 --- a/sanity-report-dev11.js +++ b/sanity-report-dev11.js @@ -10,7 +10,7 @@ const user4 = process.env.USER4; const mochawesomeJsonOutput = fs.readFileSync( "./mochawesome-report/mochawesome.json", - "utf-8" + "utf8" ); const mochawesomeReport = JSON.parse(mochawesomeJsonOutput); diff --git a/sanity-report.mjs b/sanity-report.mjs index 9e73180..9fcd909 100644 --- a/sanity-report.mjs +++ b/sanity-report.mjs @@ -5,7 +5,7 @@ import fs from 'fs' dotenv.config() -const mochawesomeJsonOutput = fs.readFileSync('./mochawesome-report/mochawesome.json', 'utf-8') +const mochawesomeJsonOutput = fs.readFileSync('./mochawesome-report/mochawesome.json', 'utf8') const mochawesomeReport = JSON.parse(mochawesomeJsonOutput) const report = `./mochawesome-report/sanity-report.html` @@ -25,8 +25,16 @@ console.log(`Failed Tests: ${failedTests}`) console.log(`Pending Tests: ${pendingTests}`) console.log(`Total Duration: ${durationInMinutes}m ${durationInSeconds.toFixed(2)}s`) +const host = process.env.DEFAULTHOST || '' +let region = 'NA' + +const match = host.match(/^([^-]+(?:-[^-]+)*)-api/) +if (match && match[1]) { + region = match[1].toUpperCase() +} + const slackMessage = ` -*JavaScript Marketplace SDK Report* +*JavaScript Marketplace SDK Report - ${region}* • Total Suites: *${totalSuites}* • Total Tests: *${totalTests}* • Passed Tests: *${passedTests}* diff --git a/test/unit/ContentstackClient-test.js b/test/unit/ContentstackClient-test.js index 7bd42a1..6212e6b 100644 --- a/test/unit/ContentstackClient-test.js +++ b/test/unit/ContentstackClient-test.js @@ -4,11 +4,31 @@ import { expect } from 'chai' import { describe, it, beforeEach } from 'mocha' import MockAdapter from 'axios-mock-adapter' import Region from '../../lib/core/region' -var host = 'http://localhost/' +var host = 'localhost' + +describe('Region Test ', () => { + it('Contentstack Client login success with region NA', done => { + var mock = new MockAdapter(axios) + axios.defaults.region = Region.NA + mock.onPost('https://api.contentstack.io:443/v3/user-session').reply(200, { + user: { + authtoken: 'Test Auth' + } + }) + + ContentstackClient({ http: axios }) + .login() + .then((response) => { + expect(response.user.authtoken).to.be.equal('Test Auth') + done() + }) + .catch(done) + }) +}) describe('Contentstack Client', () => { beforeEach(function () { - host = 'http://localhost/' + host = 'localhost' axios.defaults.host = host axios.defaults.adapter = 'http' }) @@ -21,7 +41,7 @@ describe('Contentstack Client', () => { it('Contentstack Client login success', done => { var mock = new MockAdapter(axios) - mock.onPost('/user-session').reply(200, { + mock.onPost('https://localhost:443/v3/user-session').reply(200, { user: { authtoken: 'Test Auth' } @@ -37,7 +57,7 @@ describe('Contentstack Client', () => { it('Contentstack Client Logout with Authtoken', done => { var mock = new MockAdapter(axios) - mock.onDelete('/user-session').reply(200, { + mock.onDelete('https://localhost:443/v3/user-session').reply(200, { notice: 'You\'ve logged out successfully' }) ContentstackClient({ http: axios }) @@ -51,7 +71,7 @@ describe('Contentstack Client', () => { it('Contentstack Client Logout', done => { var mock = new MockAdapter(axios) - mock.onDelete('/user-session').reply(200, { + mock.onDelete('https://localhost:443/v3/user-session').reply(200, { notice: 'You\'ve logged out successfully' }) axios.defaults.headers = { @@ -101,20 +121,57 @@ describe('Contentstack Client', () => { done() }) - it('Contentstack Client login success with region', done => { - var mock = new MockAdapter(axios) - axios.defaults.region = Region.AZURE_NA - mock.onPost('https://azure-na-api.contentstack.com:443/v3/user-session').reply(200, { - user: { - authtoken: 'Test Auth' - } + it('Contentstack Client login success with region AZURE-NA', done => { + var mock = new MockAdapter(axios) + axios.defaults.region = Region.AZURE_NA + mock.onPost('https://azure-na-api.contentstack.com:443/v3/user-session').reply(200, { + user: { + authtoken: 'Test Auth' + } + }) + + ContentstackClient({ http: axios }) + .login() + .then((response) => { + expect(response.user.authtoken).to.be.equal('Test Auth') + done() }) - ContentstackClient({ http: axios }) - .login() - .then((response) => { - expect(response.user.authtoken).to.be.equal('Test Auth') - done() - }) - .catch(done) + .catch(done) + }) + + it('Contentstack Client login success with region AZURE-EU', done => { + var mock = new MockAdapter(axios) + axios.defaults.region = Region.AZURE_EU + mock.onPost('https://azure-eu-api.contentstack.com:443/v3/user-session').reply(200, { + user: { + authtoken: 'Test Auth' + } + }) + + ContentstackClient({ http: axios }) + .login() + .then((response) => { + expect(response.user.authtoken).to.be.equal('Test Auth') + done() + }) + .catch(done) + }) + + it('Contentstack Client login success with region GCP-NA', done => { + var mock = new MockAdapter(axios) + axios.defaults.region = Region.GCP_NA + mock.onPost('https://gcp-na-api.contentstack.com:443/v3/user-session').reply(200, { + user: { + authtoken: 'Test Auth' + } + }) + + ContentstackClient({ http: axios }) + .login() + .then((response) => { + expect(response.user.authtoken).to.be.equal('Test Auth') + done() + }) + .catch(done) }) }) diff --git a/test/unit/concurrency-Queue-test.js b/test/unit/concurrency-Queue-test.js index 36550d1..e866e1b 100644 --- a/test/unit/concurrency-Queue-test.js +++ b/test/unit/concurrency-Queue-test.js @@ -130,22 +130,15 @@ describe('Concurrency queue test', () => { }) it('Refresh Token on 401 with 1000 concurrent request', done => { - var mock = new MockAdapter(axios) - mock.onPost('/user-session').reply(200, { - token - }) - const axiosClient = client({ - baseURL: `${host}:${port}`, - authorization: 'Bearer ', + const axiosClient = client({ + baseURL: `${host}:${port}`, + authorization: 'Bearer ', logHandler: logHandlerStub, refreshToken: () => { - return new Promise((resolve, reject) => { - return contentstackClient({ http: axios }).login().then((res) => { - resolve({ authorization: res.token }) - }).catch((error) => { - reject(error) + return Axios.post(`${host}:${port}/user-session`) + .then((res) => { + return { authorization: res.data.token } }) - }) } }) Promise.all(sequence(1003).map(() => axiosClient.axiosInstance.get('/unauthorized')))