Skip to content

Commit d151238

Browse files
1.0.15
1 parent df5bfb2 commit d151238

15 files changed

Lines changed: 1256 additions & 85 deletions

dist/__tests__/merge-2.spec.js

Lines changed: 996 additions & 1 deletion
Large diffs are not rendered by default.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"use strict";
2+
Object.defineProperty(exports, "__esModule", { value: true });
3+
const merge_allowed_1 = require("../merge-allowed");
4+
describe('mergeAllowedGraphQLQueries', () => {
5+
it('should merge queries with the same resolver and different fields', () => {
6+
const queries = [
7+
'query GetCompany { id }',
8+
'query GetCompany { name }',
9+
'query GetUser { id }',
10+
];
11+
const mergedQueries = (0, merge_allowed_1.mergeAllowedGraphQLQueries)(queries);
12+
expect(mergedQueries.get('GetCompany')).toBe('query GetCompany { id name }');
13+
expect(mergedQueries.get('GetUser')).toBe('query GetUser { id }');
14+
});
15+
it('should handle single query without modification', () => {
16+
const queries = ['query GetCompany { id name }'];
17+
const mergedQueries = (0, merge_allowed_1.mergeAllowedGraphQLQueries)(queries);
18+
expect(mergedQueries.get('GetCompany')).toBe('query GetCompany { id name }');
19+
});
20+
it('should handle empty query list', () => {
21+
const queries = [];
22+
const mergedQueries = (0, merge_allowed_1.mergeAllowedGraphQLQueries)(queries);
23+
expect(mergedQueries.size).toBe(0);
24+
});
25+
it('should ignore invalid GraphQL queries', () => {
26+
const consoleSpy = jest
27+
.spyOn(console, 'error')
28+
.mockImplementation(() => { });
29+
const queries = ['This is not a valid GraphQL query'];
30+
const mergedQueries = (0, merge_allowed_1.mergeAllowedGraphQLQueries)(queries);
31+
expect(mergedQueries.size).toBe(0);
32+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Error processing query:'));
33+
consoleSpy.mockRestore();
34+
});
35+
});

dist/index.d.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,24 @@ export declare class GraphQLQueryPurifier {
2020
* @private
2121
*/
2222
private allowAll;
23+
/**
24+
* Flag to enable logging of input/output.
25+
* @private
26+
*/
27+
private debug;
2328
/**
2429
* Constructs a GraphQLQueryPurifier instance.
2530
* @param {Object} params - Configuration parameters.
2631
* @param {string} params.gqlPath - Path to the directory containing .gql files.
2732
* @param {boolean} [params.allowAll=false] - Whether to allow all queries.
2833
* @param {boolean} [params.allowStudio=false] - Whether to allow Apollo Studio introspection queries.
34+
* @param {boolean} [params.debug=false] - Flag to enable logging of input/output.
2935
*/
30-
constructor({ gqlPath, allowAll, allowStudio, }: {
36+
constructor({ gqlPath, allowAll, allowStudio, debug, }: {
3137
gqlPath: string;
3238
allowStudio?: boolean;
3339
allowAll?: boolean;
40+
debug?: boolean;
3441
});
3542
startWatchingFiles(): void;
3643
/**

dist/index.js

Lines changed: 18 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
55
Object.defineProperty(exports, "__esModule", { value: true });
66
exports.GraphQLQueryPurifier = void 0;
77
const fs_1 = __importDefault(require("fs"));
8-
const graphql_1 = require("graphql");
98
const path_1 = __importDefault(require("path"));
109
// @ts-ignore
1110
const glob_1 = __importDefault(require("glob"));
1211
const merge_1 = require("./merge");
12+
const merge_allowed_1 = require("./merge-allowed");
1313
class GraphQLQueryPurifier {
1414
/**
1515
* Constructs a GraphQLQueryPurifier instance.
1616
* @param {Object} params - Configuration parameters.
1717
* @param {string} params.gqlPath - Path to the directory containing .gql files.
1818
* @param {boolean} [params.allowAll=false] - Whether to allow all queries.
1919
* @param {boolean} [params.allowStudio=false] - Whether to allow Apollo Studio introspection queries.
20+
* @param {boolean} [params.debug=false] - Flag to enable logging of input/output.
2021
*/
21-
constructor({ gqlPath, allowAll = false, allowStudio = false, }) {
22+
constructor({ gqlPath, allowAll = false, allowStudio = false, debug = false, }) {
2223
/**
2324
* Middleware function to filter incoming GraphQL queries based on the allowed list.
2425
* If a query is not allowed, it's replaced with a minimal query.
@@ -49,7 +50,7 @@ class GraphQLQueryPurifier {
4950
}
5051
if (req.body && req.body.query) {
5152
// Use mergeQueries to filter the incoming request query
52-
const filteredQuery = (0, merge_1.mergeQueries)(req.body.query, allowedQueries);
53+
const filteredQuery = (0, merge_1.mergeQueries)(req.body.query, allowedQueries, this.debug);
5354
if (!filteredQuery.trim()) {
5455
console.warn(`Query was blocked due to security rules: ${req.body.query}`);
5556
req.body.query = '{ __typename }';
@@ -67,6 +68,7 @@ class GraphQLQueryPurifier {
6768
this.startWatchingFiles();
6869
this.allowAll = allowAll;
6970
this.allowStudio = allowStudio;
71+
this.debug = debug;
7072
}
7173
startWatchingFiles() {
7274
fs_1.default.watch(this.gqlPath, { recursive: true }, (eventType, filename) => {
@@ -82,41 +84,20 @@ class GraphQLQueryPurifier {
8284
*/
8385
loadQueries() {
8486
const files = glob_1.default.sync(`${this.gqlPath}/**/*.gql`.replace(/\\/g, '/'));
85-
files.forEach((file) => {
86-
if (path_1.default.extname(file) === '.gql') {
87-
const content = fs_1.default.readFileSync(file, 'utf8').trim();
88-
if (!content) {
89-
console.warn(`Warning: Empty or invalid GraphQL file found: ${file}`);
90-
return;
91-
}
92-
try {
93-
const parsedQuery = (0, graphql_1.parse)(content);
94-
parsedQuery.definitions.forEach((definition) => {
95-
if (definition.kind === 'OperationDefinition') {
96-
const operationDefinition = definition;
97-
let queryName = operationDefinition.name?.value;
98-
if (!queryName) {
99-
// Extract the name from the first field of the selection set
100-
const firstField = operationDefinition.selectionSet.selections[0];
101-
if (firstField && firstField.kind === 'Field') {
102-
queryName = firstField.name.value;
103-
}
104-
}
105-
if (queryName) {
106-
this.queryMap[queryName] = content;
107-
}
108-
}
109-
});
110-
}
111-
catch (error) {
112-
if (error instanceof graphql_1.GraphQLError) {
113-
console.error(`Error parsing GraphQL file ${file}: ${error.message}`);
114-
}
115-
else {
116-
console.error(`Unexpected error processing file ${file}: ${error}`);
117-
}
118-
}
87+
const fileContents = files
88+
.map((file) => {
89+
const content = fs_1.default.readFileSync(file, 'utf8').trim();
90+
if (!content) {
91+
console.warn(`Warning: Empty or invalid GraphQL file found: ${file}`);
92+
return '';
11993
}
94+
return content;
95+
})
96+
.filter((content) => content !== '');
97+
const mergedQueries = (0, merge_allowed_1.mergeAllowedGraphQLQueries)(fileContents);
98+
this.queryMap = {};
99+
mergedQueries.forEach((query, resolver) => {
100+
this.queryMap[resolver] = query;
120101
});
121102
}
122103
}

dist/merge-allowed.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export declare function mergeAllowedGraphQLQueries(queries: string[]): Map<string, string>;

dist/merge-allowed.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"use strict";
2+
Object.defineProperty(exports, "__esModule", { value: true });
3+
exports.mergeAllowedGraphQLQueries = void 0;
4+
const graphql_1 = require("graphql");
5+
function mergeAllowedGraphQLQueries(queries) {
6+
const resolverMap = new Map();
7+
queries.forEach((query) => {
8+
try {
9+
const parsedQuery = (0, graphql_1.parse)(query);
10+
parsedQuery.definitions.forEach((definition) => {
11+
if (definition.kind === 'OperationDefinition') {
12+
const operationDefinition = definition;
13+
let queryName = operationDefinition.name?.value;
14+
if (!queryName) {
15+
const firstField = operationDefinition.selectionSet.selections[0];
16+
if (firstField && firstField.kind === 'Field') {
17+
queryName = firstField.name.value;
18+
}
19+
}
20+
if (queryName) {
21+
const existingFields = resolverMap.get(queryName) || new Set();
22+
operationDefinition.selectionSet.selections.forEach((selection) => {
23+
if (selection.kind === 'Field') {
24+
existingFields.add(selection.name.value);
25+
}
26+
});
27+
resolverMap.set(queryName, existingFields);
28+
}
29+
}
30+
});
31+
}
32+
catch (error) {
33+
console.error(`Error processing query: ${error}`);
34+
}
35+
});
36+
const mergedQueries = new Map();
37+
resolverMap.forEach((fields, resolver) => {
38+
const mergedQuery = `query ${resolver} { ${Array.from(fields).join(' ')} }`;
39+
mergedQueries.set(resolver, mergedQuery);
40+
});
41+
return mergedQueries;
42+
}
43+
exports.mergeAllowedGraphQLQueries = mergeAllowedGraphQLQueries;

dist/merge.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export declare function mergeQueries(requestQuery: string, allowedQueries: string[]): string;
1+
export declare function mergeQueries(requestQuery: string, allowedQueries: string[], debug?: boolean): string;

dist/merge.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ function getPath(node, ancestors) {
1010
path.push(node.name.value);
1111
return path.join('.');
1212
}
13-
function mergeQueries(requestQuery, allowedQueries) {
13+
function mergeQueries(requestQuery, allowedQueries, debug = false) {
1414
if (!requestQuery.trim()) {
1515
return '';
1616
}
17+
if (debug) {
18+
console.log('Incoming Query:', requestQuery);
19+
}
1720
const parsedRequestQuery = (0, graphql_1.parse)(requestQuery);
1821
const allowedQueryASTs = allowedQueries.map((query) => (0, graphql_1.parse)(query));
1922
const allowedPaths = new Set();
@@ -40,6 +43,10 @@ function mergeQueries(requestQuery, allowedQueries) {
4043
if (!hasValidFields) {
4144
return ''; // Return an empty string if no valid fields are left
4245
}
43-
return (0, graphql_1.print)(modifiedAST);
46+
const modifiedQuery = (0, graphql_1.print)(modifiedAST);
47+
if (debug) {
48+
console.log('Modified Query:', modifiedQuery);
49+
}
50+
return modifiedQuery;
4451
}
4552
exports.mergeQueries = mergeQueries;

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "graphql-query-purifier",
3-
"version": "1.0.14",
3+
"version": "1.0.15",
44
"description": "A small library to match .gql queries vs user input. Removes fields from user requests that are not expected by your frontend code.",
55
"main": "./dist/index.js",
66
"author": "multipliedtwice",

0 commit comments

Comments
 (0)