Skip to content

Commit

Permalink
fix: refactor & sanitization
Browse files Browse the repository at this point in the history
  • Loading branch information
chesterkmr committed Nov 18, 2024
1 parent dcdc864 commit b54023a
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 88 deletions.
8 changes: 6 additions & 2 deletions packages/workflow-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"json-logic-js": "^2.0.2",
"lodash.groupby": "^4.6.0",
"lodash.maxby": "^4.6.0",
"validator": "^13.12.0",
"xstate": "^4.35.2"
},
"devDependencies": {
Expand All @@ -55,15 +56,18 @@
"@rollup/plugin-replace": "4.0.0",
"@rollup/plugin-terser": "^0.4.0",
"@types/babel__core": "^7.20.0",
"@types/dompurify": "^3.0.5",
"@types/fs-extra": "^11.0.1",
"@types/jmespath": "^0.15.0",
"@types/jsdom": "^21.1.7",
"@types/json-logic-js": "^2.0.1",
"@types/lodash.get": "^4.4.2",
"@types/lodash.groupby": "^4.6.9",
"@types/lodash.maxby": "^4.6.9",
"@types/lodash.merge": "^4.6.9",
"@types/lodash.set": "^4.3.2",
"@types/lodash.get": "^4.4.2",
"@types/node": "^18.14.0",
"@types/validator": "^13.12.2",
"@typescript-eslint/eslint-plugin": "^5.48.1",
"@typescript-eslint/parser": "^5.48.1",
"@vitest/coverage-istanbul": "^0.28.4",
Expand All @@ -77,9 +81,9 @@
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-unused-imports": "^2.0.0",
"fs-extra": "^11.1.0",
"lodash.get": "^4.4.2",
"lodash.merge": "^4.6.2",
"lodash.set": "^4.3.2",
"lodash.get": "^4.4.2",
"msw": "^1.2.2",
"nock": "^13.3.8",
"node-fetch": "^3.3.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
import { formatStringValues, getGlobalQueryParams, isBrowser } from './helpers';
import { getGlobalQueryParams, isBrowser, sanitizeQueryParam } from './helpers';

describe('helpers', () => {
describe('isBrowser', () => {
Expand All @@ -9,69 +9,57 @@ describe('helpers', () => {

expect(isBrowser()).toBe(true);

delete global.window;
delete (global as any).window;
});

it('should return false when window is undefined', () => {
expect(isBrowser()).toBe(false);
});
});

describe('sanitizeQueryParam', () => {
it('should trim and escape special characters', () => {
expect(sanitizeQueryParam(' test<script> ')).toBe('test&lt;script&gt;');
expect(sanitizeQueryParam(' hello&world ')).toBe('hello&amp;world');
});
});

describe('getGlobalQueryParams', () => {
it('should return empty object when not in browser', () => {
const result = getGlobalQueryParams();
expect(result).toEqual({});
});

it('should parse URL search params into object', () => {
// Mock window with location
it('should parse and sanitize URL search params into object', () => {
// Mock window with location and URLSearchParams
global.window = {
location: {
search: '?key1=value1&key2=value2',
search: '?key1=<script>&key2= value2 ',
},
} as Window & typeof globalThis;

expect(getGlobalQueryParams()).toEqual({
key1: 'value1',
key1: '&lt;script&gt;',
key2: 'value2',
});

delete global.window;
delete (global as any).window;
});
});

describe('formatStringValues', () => {
it('should return original string if no placeholders found', () => {
const str = 'Hello World';
const data = { name: 'John' };

expect(formatStringValues(str, data)).toBe(str);
});

it('should replace single placeholder with value', () => {
const str = 'Hello {name}';
const data = { name: 'John' };

expect(formatStringValues(str, data)).toBe('Hello John');
});

it('should replace multiple placeholders with values', () => {
const str = 'Hello {user.firstName} {user.lastName}';
const data = {
user: {
firstName: 'John',
lastName: 'Doe',
it('should handle multiple sanitized values for same parameter', () => {
// Mock window with location and URLSearchParams
global.window = {
location: {
search: '?key1=value1<>&key1=value2&key2= value3 ',
},
};

expect(formatStringValues(str, data)).toBe('Hello John Doe');
});
} as Window & typeof globalThis;

it('should keep placeholder if value not found in data', () => {
const str = 'Hello {name} {unknown}';
const data = { name: 'John' };
expect(getGlobalQueryParams()).toEqual({
key1: ['value1&lt;&gt;', 'value2'],
key2: 'value3',
});

expect(formatStringValues(str, data)).toBe('Hello John {unknown}');
delete (global as any).window;
});
});
});
Original file line number Diff line number Diff line change
@@ -1,41 +1,34 @@
import get from 'lodash.get';
import validator from 'validator';

export const isBrowser = () => typeof window !== 'undefined';

export const getGlobalQueryParams = () => {
if (!isBrowser()) return {};

const searchParams = new URLSearchParams(window.location.search);
const paramsObject: Record<string, string> = {};

searchParams.forEach((value, key) => {
paramsObject[key] = value;
});

return paramsObject;
/**
* Sanitizes query parameter values to prevent XSS and injection attacks
*/
export const sanitizeQueryParam = (param: string): string => {
return validator.escape(param.trim());
};

export const formatStringValues = (
str: string,
data: Record<string, string | Record<string, string>>,
) => {
// Find all placeholders in curly braces
const placeholders = str.match(/{(.*?)}/g);

if (!placeholders) return str;

let formattedString = str;

// Replace each placeholder with its value from data
for (const placeholder of placeholders) {
const path = placeholder.replace(/{|}/g, ''); // Remove curly braces
const value = get(data, path);
export const getGlobalQueryParams = (): Record<string, string | string[]> => {
if (!isBrowser()) return {};

// Replace placeholder with value if found
if (value !== undefined) {
formattedString = formattedString.replace(placeholder, String(value));
const searchParams = new URLSearchParams(window.location.search);
const paramsObject: Record<string, string | string[]> = {};

// Handle multiple values for the same parameter
for (const [key, value] of searchParams.entries()) {
const sanitizedValue = sanitizeQueryParam(value);
const sanitizedKey = sanitizeQueryParam(key);

if (sanitizedKey in paramsObject) {
const existing = paramsObject[sanitizedKey];
paramsObject[sanitizedKey] = Array.isArray(existing)
? [...existing, sanitizedValue]
: [existing as string, sanitizedValue];
} else {
paramsObject[sanitizedKey] = sanitizedValue;
}
}

return formattedString;
return paramsObject;
};
58 changes: 40 additions & 18 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit b54023a

Please sign in to comment.