Skip to content

Commit e52c119

Browse files
1.0.3
1 parent c9b7bb5 commit e52c119

4 files changed

Lines changed: 148 additions & 16 deletions

File tree

dist/index.d.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ import { NextFunction, Request, Response } from 'express';
22
export declare class GraphQLQueryPurifier {
33
private gqlPath;
44
private queryMap;
5-
constructor(gqlPath: string);
5+
private allowStudio?;
6+
private allowAll;
7+
constructor({ gqlPath, allowAll, allowStudio, }: {
8+
gqlPath: string;
9+
allowStudio?: boolean;
10+
allowAll?: boolean;
11+
});
612
private loadQueries;
7-
filter: (req: Request, res: Response, next: NextFunction) => void;
13+
filter: (req: Request, res: Response, next: NextFunction) => void | Response<any, Record<string, any>>;
814
}

dist/index.js

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,76 @@
1-
"use strict";var e=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(exports,"__esModule",{value:!0}),exports.GraphQLQueryPurifier=void 0;const r=e(require("fs")),i=e(require("glob")),t=require("graphql"),u=e(require("path")),s=require("./merge");class a{constructor(e){this.filter=(e,r,i)=>{if(e.body&&e.body.query){const r=Object.values(this.queryMap);e.body.query=(0,s.mergeQueries)(e.body.query,r)}i()},this.gqlPath=e,this.queryMap={},this.loadQueries()}loadQueries(){i.default.sync(`${this.gqlPath}/**/*.gql`).forEach((e=>{if(".gql"===u.default.extname(e)){const i=r.default.readFileSync(e,"utf8"),u=(0,t.parse)(i).definitions.find((e=>"OperationDefinition"===e.kind)),s=u?.name?.value;s&&(this.queryMap[s]=i)}}))}}exports.GraphQLQueryPurifier=a;
1+
"use strict";
2+
var __importDefault = (this && this.__importDefault) || function (mod) {
3+
return (mod && mod.__esModule) ? mod : { "default": mod };
4+
};
5+
Object.defineProperty(exports, "__esModule", { value: true });
6+
exports.GraphQLQueryPurifier = void 0;
7+
const fs_1 = __importDefault(require("fs"));
8+
const glob_1 = __importDefault(require("glob"));
9+
const graphql_1 = require("graphql");
10+
const path_1 = __importDefault(require("path"));
11+
const merge_1 = require("./merge");
12+
class GraphQLQueryPurifier {
13+
constructor({ gqlPath, allowAll = false, allowStudio = false, }) {
14+
this.filter = (req, res, next) => {
15+
if (this.allowAll)
16+
return next();
17+
if (req.body.operationName === 'IntrospectionQuery' ||
18+
req.body.extensions?.persistedQuery) {
19+
if (this.allowStudio) {
20+
return next();
21+
}
22+
else {
23+
return res
24+
.status(403)
25+
.send('Access to studio is disabled by GraphQLQueryPurifier, pass { allowStudio: true }');
26+
}
27+
}
28+
// Get all allowed queries as an array of strings
29+
const allowedQueries = Object.values(this.queryMap);
30+
if (allowedQueries.length === 0) {
31+
console.warn('Warning: No GraphQL queries were loaded in GraphQLQueryPurifier. ' +
32+
'Ensure that your .gql files are located in the specified directory: ' +
33+
this.gqlPath);
34+
return res
35+
.status(500)
36+
.send('GraphQL queries are not loaded. Please check server logs for more details.');
37+
}
38+
if (req.body && req.body.query) {
39+
// Use mergeQueries to filter the incoming request query
40+
req.body.query = (0, merge_1.mergeQueries)(req.body.query, allowedQueries);
41+
}
42+
next();
43+
};
44+
this.gqlPath = gqlPath;
45+
this.queryMap = {};
46+
this.loadQueries();
47+
this.allowAll = allowAll;
48+
this.allowStudio = allowStudio;
49+
}
50+
loadQueries() {
51+
const files = glob_1.default.sync(`${this.gqlPath}/**/*.gql`);
52+
files.forEach((file) => {
53+
if (path_1.default.extname(file) === '.gql') {
54+
const content = fs_1.default.readFileSync(file, 'utf8');
55+
const parsedQuery = (0, graphql_1.parse)(content);
56+
parsedQuery.definitions.forEach((definition) => {
57+
if (definition.kind === 'OperationDefinition') {
58+
const operationDefinition = definition;
59+
let queryName = operationDefinition.name?.value;
60+
if (!queryName) {
61+
// Extract the name from the first field of the selection set
62+
const firstField = operationDefinition.selectionSet.selections[0];
63+
if (firstField && firstField.kind === 'Field') {
64+
queryName = firstField.name.value;
65+
}
66+
}
67+
if (queryName) {
68+
this.queryMap[queryName] = content;
69+
}
70+
}
71+
});
72+
}
73+
});
74+
}
75+
}
76+
exports.GraphQLQueryPurifier = GraphQLQueryPurifier;

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
{
22
"name": "graphql-query-purifier",
3-
"version": "1.0.2",
3+
"version": "1.0.3",
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",
77
"license": "MIT",
88
"scripts": {
99
"test": "jest",
10-
"build": "tsc && terser -c -m toplevel -o dist/index.js -- dist/index.js"
10+
"build": "tsc"
1111
},
1212
"repository": {
1313
"type": "git",

src/index.ts

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,37 +9,88 @@ import { mergeQueries } from './merge';
99
export class GraphQLQueryPurifier {
1010
private gqlPath: string;
1111
private queryMap: { [key: string]: string };
12+
private allowStudio?: boolean;
13+
private allowAll: boolean;
1214

13-
constructor(gqlPath: string) {
15+
constructor({
16+
gqlPath,
17+
allowAll = false,
18+
allowStudio = false,
19+
}: {
20+
gqlPath: string;
21+
allowStudio?: boolean;
22+
allowAll?: boolean;
23+
}) {
1424
this.gqlPath = gqlPath;
1525
this.queryMap = {};
1626
this.loadQueries();
27+
this.allowAll = allowAll;
28+
this.allowStudio = allowStudio;
1729
}
1830

1931
private loadQueries() {
2032
const files = glob.sync(`${this.gqlPath}/**/*.gql`);
33+
2134
files.forEach((file) => {
2235
if (path.extname(file) === '.gql') {
2336
const content = fs.readFileSync(file, 'utf8');
2437
const parsedQuery = parse(content);
2538

26-
const operationDefinition = parsedQuery.definitions.find(
27-
(def) => def.kind === 'OperationDefinition'
28-
) as OperationDefinitionNode | undefined;
39+
parsedQuery.definitions.forEach((definition) => {
40+
if (definition.kind === 'OperationDefinition') {
41+
const operationDefinition = definition as OperationDefinitionNode;
42+
43+
let queryName = operationDefinition.name?.value;
44+
if (!queryName) {
45+
// Extract the name from the first field of the selection set
46+
const firstField = operationDefinition.selectionSet.selections[0];
47+
if (firstField && firstField.kind === 'Field') {
48+
queryName = firstField.name.value;
49+
}
50+
}
2951

30-
const queryName = operationDefinition?.name?.value;
31-
if (queryName) {
32-
this.queryMap[queryName] = content;
33-
}
52+
if (queryName) {
53+
this.queryMap[queryName] = content;
54+
}
55+
}
56+
});
3457
}
3558
});
3659
}
3760

3861
public filter = (req: Request, res: Response, next: NextFunction) => {
39-
if (req.body && req.body.query) {
40-
// Get all allowed queries as an array of strings
41-
const allowedQueries = Object.values(this.queryMap);
62+
if (this.allowAll) return next();
63+
if (
64+
req.body.operationName === 'IntrospectionQuery' ||
65+
req.body.extensions?.persistedQuery
66+
) {
67+
if (this.allowStudio) {
68+
return next();
69+
} else {
70+
return res
71+
.status(403)
72+
.send(
73+
'Access to studio is disabled by GraphQLQueryPurifier, pass { allowStudio: true }'
74+
);
75+
}
76+
}
77+
78+
// Get all allowed queries as an array of strings
79+
const allowedQueries = Object.values(this.queryMap);
80+
if (allowedQueries.length === 0) {
81+
console.warn(
82+
'Warning: No GraphQL queries were loaded in GraphQLQueryPurifier. ' +
83+
'Ensure that your .gql files are located in the specified directory: ' +
84+
this.gqlPath
85+
);
86+
return res
87+
.status(500)
88+
.send(
89+
'GraphQL queries are not loaded. Please check server logs for more details.'
90+
);
91+
}
4292

93+
if (req.body && req.body.query) {
4394
// Use mergeQueries to filter the incoming request query
4495
req.body.query = mergeQueries(req.body.query, allowedQueries);
4596
}

0 commit comments

Comments
 (0)