🚀 Big News: Socket Acquires Coana to Bring Reachability Analysis to Every Appsec Team.Learn more
Socket
DemoInstallSign in
Socket

graphql-no-alias

Package Overview
Dependencies
Maintainers
1
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

graphql-no-alias - npm Package Compare versions

Comparing version

to
1.0.2

src/__tests__/directive.test.ts

98

dist/prod/graphql-no-alias.esm.js
import { GraphQLError } from 'graphql';
function _extends() {
_extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
/**
* Creates validation object with needed type declarations and validation function
* @param [defaultAllow] - how many aliases to allow by default
* @param [directiveName] - direactive name to use
* @param [errorFn] - function that will return GraphQLError when the validation fails
* Creates validation
* @param config - {@link Config}
* @returns validation function
*/
function createValidation(defaultAllow = 1, directiveName = 'noAlias', errorFn) {
function createValidation(config) {
const {
directiveName,
defaultAllow,
errorFn,
permissions
} = _extends({}, {
defaultAllow: 1,
directiveName: 'noAlias',
errorFn: createErrorMsg
}, config || {});
return {

@@ -17,3 +45,3 @@ typeDefs: `directive @${directiveName}(allow: Int = ${defaultAllow}) on OBJECT | FIELD_DEFINITION`,

Field: {
leave: createFieldValidation(context, directiveName, defaultAllow, errorFn ? errorFn : createErrorMsg)
leave: createFieldValidation(context, directiveName, defaultAllow, errorFn, permissions)
}

@@ -27,5 +55,32 @@ };

function createFieldValidation(context, directiveName, defaultAllow, errorFn) {
function configPermissionWalker(permissions, result, parentKey) {
Object.entries(permissions).forEach(([key, value]) => {
if (typeof value === 'object') {
configPermissionWalker(value, result, `${parentKey ? parentKey : ''}${parentKey && key ? '.' : ''}${key ? key : ''}`);
} else {
if (key === '*') {
result.set(parentKey, value);
} else {
result.set(`${parentKey ? parentKey : ''}.${key}`, value);
}
}
});
}
function buildPermissionTableFromConfig(permissions) {
const result = new Map();
configPermissionWalker(permissions, result, undefined);
return result;
}
function createFieldValidation(context, directiveName, defaultAllow, errorFn, permissions) {
const schema = context.getSchema();
const allowedCount = createMaxAllowedTable(defaultAllow, directiveName, [schema.getQueryType(), schema.getMutationType()]);
let allowedCount;
if (permissions) {
allowedCount = buildPermissionTableFromConfig(permissions);
} else {
allowedCount = buildPermissionTableFromSchema(defaultAllow, directiveName, [schema.getQueryType(), schema.getMutationType()]);
}
const currentCount = new Map(); //track if the error have already been reported for particular field

@@ -46,8 +101,2 @@

* Checks if allowed alias count has been exceeded
* @param ctx
* @param node
* @param maxAllowedData
* @param currentCountData
* @param errorFn
* @param errorMap
*/

@@ -59,4 +108,4 @@

const typeName = ctx.getParentType().name;
const fieldKey = `${typeName}-${nodeName}`;
const typeKey = `${typeName}`;
const fieldKey = `${typeKey}.${nodeName}`;
const maxAllowed = maxAllowedData.get(fieldKey) || maxAllowedData.get(typeKey);

@@ -73,3 +122,4 @@

if (!errorMap.get(fieldKey)) {
ctx.reportError(errorFn(typeName, nodeName, maxAllowed, node, ctx));
const errorResult = errorFn(typeName, nodeName, maxAllowed, node, ctx);
ctx.reportError(typeof errorResult === 'string' ? new GraphQLError(errorResult) : errorResult);
errorMap.set(fieldKey, true);

@@ -90,3 +140,3 @@ }

function createMaxAllowedTable(defaultAllow, directiveName, types) {
function buildPermissionTableFromSchema(defaultAllow, directiveName, types) {
const maxAllowed = new Map();

@@ -101,3 +151,3 @@

if (value) {
maxAllowed.set(`${graphType == null ? void 0 : graphType.name}`, value);
maxAllowed.set(`${graphType.name}`, value);
}

@@ -111,3 +161,3 @@

if (_value) {
maxAllowed.set(`${graphType}-${field.name.value}`, _value);
maxAllowed.set(`${graphType}.${field.name.value}`, _value);
}

@@ -133,10 +183,10 @@ }

* Creates custom GraphQLError instance
* @param typeName Object type name
* @param fieldName Object field name
* @param maxAllowed max allowed count that has been reached
* @param typeName - Object type name
* @param fieldName - Object field name
* @param maxAllowed - max allowed count that has been reached
*/
function createErrorMsg(typeName, fieldName, maxAllowed, _node, _ctx) {
return new GraphQLError(`Allowed number of calls for ${typeName}->${fieldName} has been exceeded (max: ${maxAllowed})`);
function createErrorMsg(typeName, fieldName, maxAllowed, node, _ctx) {
return new GraphQLError(`Allowed number of calls for ${typeName}->${fieldName} has been exceeded (max: ${maxAllowed})`, node);
}

@@ -143,0 +193,0 @@

var graphql = require('graphql');
function _extends() {
_extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
/**
* Creates validation object with needed type declarations and validation function
* @param [defaultAllow] - how many aliases to allow by default
* @param [directiveName] - direactive name to use
* @param [errorFn] - function that will return GraphQLError when the validation fails
* Creates validation
* @param config - {@link Config}
* @returns validation function
*/
function createValidation(defaultAllow = 1, directiveName = 'noAlias', errorFn) {
function createValidation(config) {
const {
directiveName,
defaultAllow,
errorFn,
permissions
} = _extends({}, {
defaultAllow: 1,
directiveName: 'noAlias',
errorFn: createErrorMsg
}, config || {});
return {

@@ -17,3 +45,3 @@ typeDefs: `directive @${directiveName}(allow: Int = ${defaultAllow}) on OBJECT | FIELD_DEFINITION`,

Field: {
leave: createFieldValidation(context, directiveName, defaultAllow, errorFn ? errorFn : createErrorMsg)
leave: createFieldValidation(context, directiveName, defaultAllow, errorFn, permissions)
}

@@ -27,5 +55,32 @@ };

function createFieldValidation(context, directiveName, defaultAllow, errorFn) {
function configPermissionWalker(permissions, result, parentKey) {
Object.entries(permissions).forEach(([key, value]) => {
if (typeof value === 'object') {
configPermissionWalker(value, result, `${parentKey ? parentKey : ''}${parentKey && key ? '.' : ''}${key ? key : ''}`);
} else {
if (key === '*') {
result.set(parentKey, value);
} else {
result.set(`${parentKey ? parentKey : ''}.${key}`, value);
}
}
});
}
function buildPermissionTableFromConfig(permissions) {
const result = new Map();
configPermissionWalker(permissions, result, undefined);
return result;
}
function createFieldValidation(context, directiveName, defaultAllow, errorFn, permissions) {
const schema = context.getSchema();
const allowedCount = createMaxAllowedTable(defaultAllow, directiveName, [schema.getQueryType(), schema.getMutationType()]);
let allowedCount;
if (permissions) {
allowedCount = buildPermissionTableFromConfig(permissions);
} else {
allowedCount = buildPermissionTableFromSchema(defaultAllow, directiveName, [schema.getQueryType(), schema.getMutationType()]);
}
const currentCount = new Map(); //track if the error have already been reported for particular field

@@ -46,8 +101,2 @@

* Checks if allowed alias count has been exceeded
* @param ctx
* @param node
* @param maxAllowedData
* @param currentCountData
* @param errorFn
* @param errorMap
*/

@@ -59,4 +108,4 @@

const typeName = ctx.getParentType().name;
const fieldKey = `${typeName}-${nodeName}`;
const typeKey = `${typeName}`;
const fieldKey = `${typeKey}.${nodeName}`;
const maxAllowed = maxAllowedData.get(fieldKey) || maxAllowedData.get(typeKey);

@@ -73,3 +122,4 @@

if (!errorMap.get(fieldKey)) {
ctx.reportError(errorFn(typeName, nodeName, maxAllowed, node, ctx));
const errorResult = errorFn(typeName, nodeName, maxAllowed, node, ctx);
ctx.reportError(typeof errorResult === 'string' ? new graphql.GraphQLError(errorResult) : errorResult);
errorMap.set(fieldKey, true);

@@ -90,3 +140,3 @@ }

function createMaxAllowedTable(defaultAllow, directiveName, types) {
function buildPermissionTableFromSchema(defaultAllow, directiveName, types) {
const maxAllowed = new Map();

@@ -101,3 +151,3 @@

if (value) {
maxAllowed.set(`${graphType == null ? void 0 : graphType.name}`, value);
maxAllowed.set(`${graphType.name}`, value);
}

@@ -111,3 +161,3 @@

if (value) {
maxAllowed.set(`${graphType}-${field.name.value}`, value);
maxAllowed.set(`${graphType}.${field.name.value}`, value);
}

@@ -133,10 +183,10 @@ }

* Creates custom GraphQLError instance
* @param typeName Object type name
* @param fieldName Object field name
* @param maxAllowed max allowed count that has been reached
* @param typeName - Object type name
* @param fieldName - Object field name
* @param maxAllowed - max allowed count that has been reached
*/
function createErrorMsg(typeName, fieldName, maxAllowed, _node, _ctx) {
return new graphql.GraphQLError(`Allowed number of calls for ${typeName}->${fieldName} has been exceeded (max: ${maxAllowed})`);
function createErrorMsg(typeName, fieldName, maxAllowed, node, _ctx) {
return new graphql.GraphQLError(`Allowed number of calls for ${typeName}->${fieldName} has been exceeded (max: ${maxAllowed})`, node);
}

@@ -143,0 +193,0 @@

import { ASTVisitor, FieldNode, GraphQLError, ValidationContext } from 'graphql';
export declare type ErrorFn = typeof createErrorMsg;
/**
* Creates validation object with needed type declarations and validation function
* @param [defaultAllow] - how many aliases to allow by default
* @param [directiveName] - direactive name to use
* @param [errorFn] - function that will return GraphQLError when the validation fails
* Configuration object for the createValidation function
*/
export default function createValidation(defaultAllow?: number, directiveName?: string, errorFn?: typeof createErrorMsg): {
declare type Permissions = {
[key: string]: Permissions | number;
};
export declare type Config = {
permissions?: Permissions;
/** How many aliases (calls) to allow by default */
defaultAllow?: number;
/** directive name to use*/
directiveName?: string;
/** function that should return a graphql erorr or string when the validation fails*/
errorFn?: ErrorFn;
};
/**
* Creates validation
* @param config - {@link Config}
* @returns validation function
*/
export default function createValidation(config?: Config): {
typeDefs: string;

@@ -14,7 +29,7 @@ validation: (context: ValidationContext) => ASTVisitor;

* Creates custom GraphQLError instance
* @param typeName Object type name
* @param fieldName Object field name
* @param maxAllowed max allowed count that has been reached
* @param typeName - Object type name
* @param fieldName - Object field name
* @param maxAllowed - max allowed count that has been reached
*/
declare function createErrorMsg(typeName: string, fieldName: string, maxAllowed: number, _node: FieldNode, _ctx: ValidationContext): GraphQLError;
declare function createErrorMsg(typeName: string, fieldName: string, maxAllowed: number, node: FieldNode, _ctx: ValidationContext): GraphQLError | string;
export {};
{
"name": "graphql-no-alias",
"version": "1.0.1",
"version": "1.0.2",
"private": false,

@@ -41,3 +41,2 @@ "description": "No alias directive for graphql mutation and query types. It can limit the amount of alias fields that can be used for queries and mutations. Preventing batch attacks.",

"@babel/preset-typescript": "^7.16.7",
"@changesets/cli": "^2.19.0",
"@types/jest": "^27.4.0",

@@ -51,6 +50,5 @@ "@typescript-eslint/eslint-plugin": "^5.8.1",

"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-tsdoc": "^0.2.14",
"graphql": "^16.2.0",
"husky": "^7.0.4",
"jest": "^27.4.5",
"jest-mock-console": "^1.2.3",
"jest-watch-typeahead": "^1.0.0",

@@ -60,3 +58,4 @@ "lint-staged": "^12.1.4",

"prettier": "^2.5.1",
"shx": "^0.3.3"
"shx": "^0.3.3",
"typescript": "^4.5.5"
},

@@ -75,4 +74,3 @@ "scripts": {

"release": "pnpm run prepublishOnly && pnpm changeset publish"
},
"readme": "# GraphQl No Alias Directive Validation\n\n![GitHub Workflow Status](https://img.shields.io/github/workflow/status/ivandotv/graphql-no-alias/Test)\n![Codecov](https://img.shields.io/codecov/c/gh/ivandotv/graphql-no-alias)\n[![GitHub license](https://img.shields.io/github/license/ivandotv/graphql-no-alias)](https://github.com/ivandotv/graphql-no-alias/blob/main/LICENSE)\n\nGraphql validation with accompanying directive to limit the number of `alias` queries and mutations you can use.\nIt effectively disables batching of queries and mutations.\n\n## Why\n\nIt will disable certain kinds of attacks that look like this.\n\n```ts\n // batch query attack (hello DDOS)\n query {\n getUsers(first: 1000)\n second: getUsers(first: 2000)\n third: getUsers(first: 3000)\n fourth: getUsers(first: 4000)\n }\n\n // or batch login attack\n mutation {\n login(pass: 1111, username: \"ivan\")\n second: login(pass: 2222, username: \"ivan\")\n third: login(pass: 3333, username: \"ivan\")\n fourth: login(pass: 4444, username: \"ivan\")\n }\n`\n```\n\nYou can read more batching attacks here: https://lab.wallarm.com/graphql-batching-attack/\n\n## Instalation\n\n```sh\nnpm i graphql-no-alias\n```\n\n## Usage\n\nThere are two parts, a `directive` declaration that needs to be added to the schema, and a validation function that needs to be added to the `GraphQl` `validationRules` array.\n\n```js\nconst express = require('express')\nconst { graphqlHTTP } = require('express-graphql')\nconst { buildSchema } = require('graphql')\n\nconst createValidation = require('graphql-no-alias')\n\n// get the validation function and type definition of the declaration\nconst { typeDefs, validation } = createValidation()\n\n//add type defintion to schema\nconst schema = buildSchema(`\n ${typeDefs}\n type Query {\n hello: String @noAlias(allow:2)\n }\n\n type Mutation @noAlias {\n muteOne(n:Int):String\n }\n`)\n\nconst app = express()\napp.use(\n '/graphql',\n graphqlHTTP({\n schema: schema,\n rootValue: root,\n graphiql: true,\n validationRules: [validation] //add the validation function\n })\n)\napp.listen(4000)\n```\n\n### Schema setup\n\nThe declaration can be used on object `type` or type `fields`. When the declaration is used on the `type` it affects all the fields of that type (Query or Mutation).\n\n#### Object type\n\nIn the next example **all** queries will be limited to only **one call**.\n\n```js\nconst schema = buildSchema(`\n type Query @noAlias {\n getUser: User\n getFriends: [User]!\n }\n`)\n```\n\nclient:\n\n```js\nquery {\n getUser\n alias_get_user: getUser // Error - validation fails\ngetFriends\nalias_get_friends: getFriends // Error - validation fails\n}\n```\n\nThe directive also accepts one parameter `allow` which declares the default number of allowed aliases.\nIn the next example, all queries will be allowed to have `3` batch calls\n\n```js\nvar schema = buildSchema(`\n type Query @noAlias(allow:3) {\n getUser: User\n getFriends: [User]!\n }\n`)\n```\n\nOn the client:\n\n```js\n query {\n getUser\n alias_2: getUser\n alias_3: getUser\n alias_4: getUser // Error - validation fails\n }\n```\n\n#### Field type\n\nUsage on type fields is the same as on the object type, one difference is that when combined with object directive the one on the field will take precedence.\n\nIn the next example, all query fields will be allowed `3` batch calls, except the `getFriends` query, which will be allowed only `1`.\n\n```js\nvar schema = buildSchema(`\n type Query @noAlias(allow:3) {\n getUser: User\n getFriends: [User]! @noAlias(allow:1) //same as @noAlias\n }\n`)\n```\n\nOn the client:\n\n```js\n query {\n getUser\n alias_2: getUser\n alias_3: getUser\n\tgetFriends\n\talias_1: getFriends // Error - validation fails\n }\n```\n\n### Customizing the declaration\n\nThe declaration can be customized to have a different name, different default `allow` value, and it can also be passed a custom error function that is executed when the validation fails.\n\nIn the next example, `validation` will allow `3` calls to the same field by default, directive name will be changed to `NoBatchCalls`, and there will be a custom error message.\n\n```ts\nconst { typeDefs, validation } = createValidation(3,'NoBatchCalls',(\n typeName: string,\n fieldName: string,\n maxAllowed: number,\n node: FieldNode,\n ctx: ValidationContext\n): GraphQLError {\n return new GraphQLError(\n `Hey! allowed number of calls for ${typeName}->${fieldName} has been exceeded (max: ${maxAllowed})`\n )\n}\n)\n```\n\nUsage:\n\n```js\nconst schema = buildSchema(`\n type Query @noBatchCalls {\n getUser: User @noBatchCalls(allow:4)\n getFriends: [User]!\n }\n`)\n```\n\n### License\n\nThis project is licensed under the MIT License - see [LICENSE](LICENSE) file for details\n"
}
}

@@ -1,3 +0,3 @@

import { buildSchema, GraphQLError, parse, validate } from 'graphql'
import createValidation from '../'
import { buildSchema, parse, validate } from 'graphql'
import createValidation from '..'

@@ -4,0 +4,0 @@ describe('Object type level validation', () => {

@@ -12,16 +12,35 @@ import {

export type ErrorFn = typeof createErrorMsg
/**
* Creates validation object with needed type declarations and validation function
* @param [defaultAllow] - how many aliases to allow by default
* @param [directiveName] - direactive name to use
* @param [errorFn] - function that will return GraphQLError when the validation fails
* Configuration object for the createValidation function
*/
export default function createValidation(
defaultAllow = 1,
directiveName = 'noAlias',
errorFn?: typeof createErrorMsg
): {
type Permissions = { [key: string]: Permissions | number }
export type Config = {
permissions?: Permissions
/** How many aliases (calls) to allow by default */
defaultAllow?: number
/** directive name to use*/
directiveName?: string
/** function that should return a graphql erorr or string when the validation fails*/
errorFn?: ErrorFn
}
/**
* Creates validation
* @param config - {@link Config}
* @returns validation function
*/
export default function createValidation(config?: Config): {
typeDefs: string
validation: (context: ValidationContext) => ASTVisitor
} {
const { directiveName, defaultAllow, errorFn, permissions } = {
...{
defaultAllow: 1,
directiveName: 'noAlias',
errorFn: createErrorMsg
},
...(config || {})
}
return {

@@ -36,3 +55,4 @@ typeDefs: `directive @${directiveName}(allow: Int = ${defaultAllow}) on OBJECT | FIELD_DEFINITION`,

defaultAllow,
errorFn ? errorFn : createErrorMsg
errorFn,
permissions
)

@@ -47,2 +67,33 @@ }

function configPermissionWalker(
permissions: Permissions,
result: Map<string, number>,
parentKey?: string
): void {
Object.entries(permissions).forEach(([key, value]) => {
if (typeof value === 'object') {
configPermissionWalker(
value,
result,
`${parentKey ? parentKey : ''}${parentKey && key ? '.' : ''}${
key ? key : ''
}`
)
} else {
if (key === '*') {
result.set(parentKey!, value)
} else {
result.set(`${parentKey ? parentKey : ''}.${key}`, value)
}
}
})
}
function buildPermissionTableFromConfig(permissions: any): Map<string, number> {
const result = new Map()
configPermissionWalker(permissions, result, undefined)
return result
}
function createFieldValidation(

@@ -52,10 +103,18 @@ context: ValidationContext,

defaultAllow: number,
errorFn: typeof createErrorMsg
errorFn: ErrorFn,
permissions?: Permissions
): (node: FieldNode) => void {
const schema = context.getSchema()
const allowedCount = createMaxAllowedTable(defaultAllow, directiveName, [
schema.getQueryType(),
schema.getMutationType()
])
let allowedCount: Map<string, number>
if (permissions) {
allowedCount = buildPermissionTableFromConfig(permissions)
} else {
allowedCount = buildPermissionTableFromSchema(defaultAllow, directiveName, [
schema.getQueryType(),
schema.getMutationType()
])
}
const currentCount: Map<string, number> = new Map()

@@ -76,8 +135,2 @@ //track if the error have already been reported for particular field

* Checks if allowed alias count has been exceeded
* @param ctx
* @param node
* @param maxAllowedData
* @param currentCountData
* @param errorFn
* @param errorMap
*/

@@ -94,4 +147,4 @@ function checkCount(

const typeName = ctx.getParentType()!.name
const fieldKey = `${typeName}-${nodeName}`
const typeKey = `${typeName}`
const fieldKey = `${typeKey}.${nodeName}`
const maxAllowed = maxAllowedData.get(fieldKey) || maxAllowedData.get(typeKey)

@@ -105,3 +158,8 @@

if (!errorMap.get(fieldKey)) {
ctx.reportError(errorFn(typeName, nodeName, maxAllowed, node, ctx))
const errorResult = errorFn(typeName, nodeName, maxAllowed, node, ctx)
ctx.reportError(
typeof errorResult === 'string'
? new GraphQLError(errorResult)
: errorResult
)
errorMap.set(fieldKey, true)

@@ -121,3 +179,3 @@ }

*/
function createMaxAllowedTable(
function buildPermissionTableFromSchema(
defaultAllow: number,

@@ -140,3 +198,3 @@ directiveName: string,

if (value) {
maxAllowed.set(`${graphType?.name}`, value)
maxAllowed.set(`${graphType!.name}`, value)
}

@@ -153,3 +211,3 @@

if (value) {
maxAllowed.set(`${graphType}-${field.name.value}`, value)
maxAllowed.set(`${graphType}.${field.name.value}`, value)
}

@@ -182,5 +240,5 @@ }

* Creates custom GraphQLError instance
* @param typeName Object type name
* @param fieldName Object field name
* @param maxAllowed max allowed count that has been reached
* @param typeName - Object type name
* @param fieldName - Object field name
* @param maxAllowed - max allowed count that has been reached
*/

@@ -191,8 +249,9 @@ function createErrorMsg(

maxAllowed: number,
_node: FieldNode,
node: FieldNode,
_ctx: ValidationContext
): GraphQLError {
): GraphQLError | string {
return new GraphQLError(
`Allowed number of calls for ${typeName}->${fieldName} has been exceeded (max: ${maxAllowed})`
`Allowed number of calls for ${typeName}->${fieldName} has been exceeded (max: ${maxAllowed})`,
node
)
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet