diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 900f449c..c3dccf3c 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -31,4 +31,4 @@ jobs: - run: npm ci - run: npm publish --tag latest env: - NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.PKG_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 89120515..f14e3e32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## Change log +### Version: 4.0.1 +#### Date: May-20-2024 +Fixed SRE vulnerabilities ### Version: 4.0.0 #### Date: April-23-2024 diff --git a/package-lock.json b/package-lock.json index 4fee30b3..b0b49f52 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@contentstack/delivery-sdk", - "version": "4.0.0", + "version": "4.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@contentstack/delivery-sdk", - "version": "4.0.0", + "version": "4.0.1", "dependencies": { "@contentstack/core": "^1.0.1", "@contentstack/utils": "^1.3.3", diff --git a/package.json b/package.json index 0bb08b10..f1898d10 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@contentstack/delivery-sdk", - "version": "4.0.0", + "version": "4.0.1", "type": "commonjs", "main": "./dist/cjs/src/index.js", "types": "./dist/types/src/index.d.ts", diff --git a/src/lib/contentstack.ts b/src/lib/contentstack.ts index d69b57c0..57931fb2 100644 --- a/src/lib/contentstack.ts +++ b/src/lib/contentstack.ts @@ -6,6 +6,8 @@ import { Policy, StackConfig } from './types'; import { getHost } from './utils'; export * as Utils from '@contentstack/utils'; +let version = '{{VERSION}}'; + /** * @method stack * @memberof Contentstack @@ -67,10 +69,16 @@ export function stack(config: StackConfig): StackClass { throw new Error('Environment for Stack is required'); } + if (config.branch) { + defaultConfig.headers.branch = config.branch; + } + if (config.early_access) { defaultConfig.headers['x-header-ea'] = config.early_access.join(','); } + defaultConfig.headers['X-User-Agent'] = 'contentstack-delivery-typescript-{{PLATFORM}}/' + version; + // return new Stack(httpClient(defaultConfig), config); const client = httpClient(defaultConfig as any); diff --git a/src/lib/query.ts b/src/lib/query.ts index 591d741b..7753b8e0 100644 --- a/src/lib/query.ts +++ b/src/lib/query.ts @@ -18,6 +18,27 @@ export class Query extends BaseQuery { this._parameters = { ...this._parameters, ...queryObj }; } } + // Validate if input is alphanumeric + private isValidAlphanumeric(input: string): boolean { + const alphanumericRegex = /^[a-zA-Z0-9_.-]+$/; + return alphanumericRegex.test(input); + } + // Validate if input is a valid regex pattern + private isValidRegexPattern(input: string): boolean { + try { + RegExp(input) + return true; + } + catch { + return false; + } + + } + + // Validate if value is an array of strings, numbers, or booleans + private isValidValue(value: any[]): boolean { + return Array.isArray(value) && value.every(item => typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean'); + } /** * @method where @@ -40,18 +61,22 @@ export class Query extends BaseQuery { * @returns {Query} */ where( - fieldUid: string, - queryOperation: QueryOperation | TaxonomyQueryOperation, + fieldUid: string, + queryOperation: QueryOperation | TaxonomyQueryOperation, fields: string | string[] | number | number[] | object | boolean, additionalData?: object ): Query { + if (!this.isValidAlphanumeric(fieldUid)) { + console.error("Invalid fieldUid:", fieldUid); + return this; + } if (queryOperation == QueryOperation.EQUALS) { this._parameters[fieldUid] = fields; - } else { + } + else { const parameterValue: { [key in QueryOperation]?: string | string[] } = { [queryOperation]: fields, ...additionalData }; this._parameters[fieldUid] = parameterValue; } - return this; } @@ -70,11 +95,18 @@ export class Query extends BaseQuery { * @returns {Query} */ regex(fieldUid: string, regexPattern: string, options?: string): Query { - this._parameters[fieldUid] = { $regex: regexPattern }; - - if (options) this._parameters[fieldUid].$options = options; - - return this; + if (!this.isValidAlphanumeric(fieldUid)) { + console.error("Invalid fieldUid:", fieldUid); + return this; + } + if (!this.isValidRegexPattern(regexPattern)) { + throw new Error("Invalid regexPattern: Must be a valid regular expression"); + } + else { + this._parameters[fieldUid] = { $regex: regexPattern }; + if (options) this._parameters[fieldUid].$options = options; + return this; + } } /** @@ -95,8 +127,10 @@ export class Query extends BaseQuery { */ whereIn(referenceUid: string, queryInstance: Query): Query { // eslint-disable-next-line @typescript-eslint/naming-convention, prettier/prettier + if (!this.isValidAlphanumeric(referenceUid)) { + throw new Error("Invalid referenceUid: Must be alphanumeric."); + } this._parameters[referenceUid] = { '$in_query': queryInstance._parameters }; - return this; } @@ -118,8 +152,10 @@ export class Query extends BaseQuery { */ whereNotIn(referenceUid: string, queryInstance: Query): Query { // eslint-disable-next-line @typescript-eslint/naming-convention, prettier/prettier + if (!this.isValidAlphanumeric(referenceUid)) { + throw new Error("Invalid referenceUid: Must be alphanumeric."); + } this._parameters[referenceUid] = { '$nin_query': queryInstance._parameters }; - return this; } @@ -183,6 +219,14 @@ export class Query extends BaseQuery { * @returns {Query} */ containedIn(key: string, value: (string | number | boolean)[]): Query { + if (!this.isValidAlphanumeric(key)) { + console.error("Invalid key:", key); + return this; + } + if (!this.isValidValue(value)) { + console.error("Invalid value:", value); + return this; + } this._parameters[key] = { '$in': value }; return this; } @@ -201,6 +245,14 @@ export class Query extends BaseQuery { * @returns {Query} */ notContainedIn(key: string, value: (string | number | boolean)[]): Query { + if (!this.isValidAlphanumeric(key)) { + console.error("Invalid key:", key); + return this; + } + if (!this.isValidValue(value)) { + console.error("Invalid value:", value); + return this; + } this._parameters[key] = { '$nin': value }; return this; } @@ -219,6 +271,10 @@ export class Query extends BaseQuery { * @returns {Query} */ exists(key: string): Query { + if (!this.isValidAlphanumeric(key)) { + console.error("Invalid key:", key); + return this; + } this._parameters[key] = { '$exists': true }; return this; } @@ -237,6 +293,10 @@ export class Query extends BaseQuery { * @returns {Query} */ notExists(key: string): Query { + if (!this.isValidAlphanumeric(key)) { + console.error("Invalid key:", key); + return this; + } this._parameters[key] = { '$exists': false }; return this; } @@ -300,6 +360,14 @@ export class Query extends BaseQuery { * @returns {Query} */ equalTo(key: string, value: string | number | boolean): Query { + if (!this.isValidAlphanumeric(key)) { + console.error("Invalid key:", key); + return this; + } + if (typeof value !== 'string' && typeof value !== 'number') { + console.error("Invalid value (expected string or number):", value); + return this; + } this._parameters[key] = value; return this; } @@ -317,6 +385,14 @@ export class Query extends BaseQuery { * @returns {Query} */ notEqualTo(key: string, value: string | number | boolean): Query { + if (!this.isValidAlphanumeric(key)) { + console.error("Invalid key:", key); + return this; + } + if (typeof value !== 'string' && typeof value !== 'number') { + console.error("Invalid value (expected string or number):", value); + return this; + } this._parameters[key] = { '$ne': value }; return this;; } @@ -335,6 +411,10 @@ export class Query extends BaseQuery { * @returns {Query} */ referenceIn(key: string, query: Query): Query { + if (!this.isValidAlphanumeric(key)) { + console.error("Invalid key:", key); + return this; + } this._parameters[key] = { '$in_query': query._parameters } return this; } @@ -353,6 +433,10 @@ export class Query extends BaseQuery { * @returns {Query} */ referenceNotIn(key: string, query: Query): Query { + if (!this.isValidAlphanumeric(key)) { + console.error("Invalid key:", key); + return this; + } this._parameters[key] = { '$nin_query': query._parameters } return this; } @@ -371,6 +455,10 @@ export class Query extends BaseQuery { * @returns {Query} */ tags(values: (string | number | boolean)[]): Query { + if (!this.isValidValue(values)) { + console.error("Invalid value:", values); + return this; + } this._parameters['tags'] = values; return this; } @@ -389,6 +477,10 @@ export class Query extends BaseQuery { * @returns {Query} */ search(key: string): Query { + if (!this.isValidAlphanumeric(key)) { + console.error("Invalid key:", key); + return this; + } this._queryParams['typeahead'] = key return this } @@ -407,6 +499,15 @@ export class Query extends BaseQuery { * @returns {Query} */ lessThan(key: string, value: (string | number)): Query { + if (!this.isValidAlphanumeric(key)) { + console.error("Invalid key:", key); + return this; + } + if (typeof value !== 'string' && typeof value !== 'number') { + console.error("Invalid value (expected string or number):", value); + return this; + } + this._parameters[key] = { '$lt': value }; return this; } @@ -425,6 +526,14 @@ export class Query extends BaseQuery { * @returns {Query} */ lessThanOrEqualTo(key: string, value: (string | number)): Query { + if (!this.isValidAlphanumeric(key)) { + console.error("Invalid key:", key); + return this; + } + if (typeof value !== 'string' && typeof value !== 'number') { + console.error("Invalid value (expected string or number):", value); + return this; + } this._parameters[key] = { '$lte': value }; return this; } @@ -443,6 +552,14 @@ export class Query extends BaseQuery { * @returns {Query} */ greaterThan(key: string, value: (string | number)): Query { + if (!this.isValidAlphanumeric(key)) { + console.error("Invalid key:", key); + return this; + } + if (typeof value !== 'string' && typeof value !== 'number') { + console.error("Invalid value (expected string or number):", value); + return this; + } this._parameters[key] = { '$gt': value }; return this; } @@ -461,6 +578,14 @@ export class Query extends BaseQuery { * @returns {Query} */ greaterThanOrEqualTo(key: string, value: (string | number)): Query { + if (!this.isValidAlphanumeric(key)) { + console.error("Invalid key:", key); + return this; + } + if (typeof value !== 'string' && typeof value !== 'number') { + console.error("Invalid value (expected string or number):", value); + return this; + } this._parameters[key] = { '$gte': value }; return this; } diff --git a/src/lib/types.ts b/src/lib/types.ts index 5edbd766..c6bf6400 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -14,6 +14,7 @@ export interface StackConfig extends HttpClientParams { apiKey: string; deliveryToken: string; environment: string; + branch?: string; early_access?: string[]; region?: Region; locale?: string; diff --git a/test/api/live-preview.spec.ts b/test/api/live-preview.spec.ts index c45e0cc2..d50835ad 100644 --- a/test/api/live-preview.spec.ts +++ b/test/api/live-preview.spec.ts @@ -7,6 +7,7 @@ dotenv.config(); const apiKey = process.env.API_KEY as string const deliveryToken = process.env.DELIVERY_TOKEN as string const environment = process.env.ENVIRONMENT as string +const branch = process.env.BRANCH as string describe('Live preview tests', () => { test('should check for values initialized', () => { @@ -14,10 +15,12 @@ describe('Live preview tests', () => { apiKey: apiKey, deliveryToken: deliveryToken, environment: environment, + branch: branch, }); const livePreviewObject = stack.config.live_preview; expect(livePreviewObject).toBeUndefined(); expect(stack.config.host).toBe('cdn.contentstack.io'); + expect(stack.config.branch).toBe(branch); }); test('should check host when live preview is enabled and management token is provided', () => { diff --git a/test/unit/contentstack.spec.ts b/test/unit/contentstack.spec.ts index ee8a79c3..9ca04522 100644 --- a/test/unit/contentstack.spec.ts +++ b/test/unit/contentstack.spec.ts @@ -97,12 +97,14 @@ describe('Contentstack', () => { apiKey: 'apiKey', deliveryToken: 'delivery', environment: 'env', + branch: 'branch', }; const stackInstance = createStackInstance(config); expect(stackInstance).toBeInstanceOf(Stack); expect(stackInstance.config.apiKey).toEqual(config.apiKey); expect(stackInstance.config.deliveryToken).toEqual(config.deliveryToken); expect(stackInstance.config.environment).toEqual(config.environment); + expect(stackInstance.config.branch).toEqual(config.branch); done(); });