@escape.tech/graphql-armor
Version:
Dead-simple, yet highly customizable security middleware for Apollo GraphQL servers shield
379 lines (354 loc) • 12.1 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var graphqlArmorBlockFieldSuggestions = require('@escape.tech/graphql-armor-block-field-suggestions');
var graphqlArmorCostLimit = require('@escape.tech/graphql-armor-cost-limit');
var graphql = require('graphql');
var graphqlArmorMaxAliases = require('@escape.tech/graphql-armor-max-aliases');
var graphqlArmorMaxDepth = require('@escape.tech/graphql-armor-max-depth');
var graphqlArmorMaxDirectives = require('@escape.tech/graphql-armor-max-directives');
var graphqlArmorMaxTokens = require('@escape.tech/graphql-armor-max-tokens');
function _toPrimitive(input, hint) {
if (typeof input !== "object" || input === null) return input;
var prim = input[Symbol.toPrimitive];
if (prim !== undefined) {
var res = prim.call(input, hint || "default");
if (typeof res !== "object") return res;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return (hint === "string" ? String : Number)(input);
}
function _toPropertyKey(arg) {
var key = _toPrimitive(arg, "string");
return typeof key === "symbol" ? key : String(key);
}
function _defineProperty(obj, key, value) {
key = _toPropertyKey(key);
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
class ApolloProtection {
constructor(config) {
_defineProperty(this, "config", void 0);
_defineProperty(this, "enabledByDefault", true);
this.config = config;
}
}
const plugin$1 = ({
mask
}) => {
const _mask = mask !== null && mask !== void 0 ? mask : graphqlArmorBlockFieldSuggestions.blockFieldSuggestionsDefaultOptions.mask;
return {
async requestDidStart() {
return {
async didEncounterErrors({
errors
}) {
for (const error of errors) {
error.message = error.message.replace(/Did you mean ".+"/g, _mask);
}
}
};
}
};
};
class ApolloBlockFieldSuggestionProtection extends ApolloProtection {
get isEnabled() {
var _this$config$blockFie;
if (!this.config.blockFieldSuggestion) {
return this.enabledByDefault;
}
return (_this$config$blockFie = this.config.blockFieldSuggestion.enabled) !== null && _this$config$blockFie !== void 0 ? _this$config$blockFie : this.enabledByDefault;
}
protect() {
var _this$config$blockFie2;
return {
plugins: [plugin$1((_this$config$blockFie2 = this.config.blockFieldSuggestion) !== null && _this$config$blockFie2 !== void 0 ? _this$config$blockFie2 : graphqlArmorBlockFieldSuggestions.blockFieldSuggestionsDefaultOptions)]
};
}
}
const reportToContext = (ctx, error) => {
if (ctx) {
ctx.reportError(new graphql.GraphQLError(error.message, {
extensions: {
code: 'BAD_USER_INPUT'
}
}));
}
};
/*
* We want to use by default the context handler, because it allows us to
* return a 400 error code, instead of 500 for the apollo server.
*
* Default `rejection` handler will be used only if throw is explicitly set to true.
* If set to false, nothing will happen.
*/
const inferApolloPropagator = config => {
if (config === undefined) {
config = {};
}
if (config.onReject === undefined) {
config.onReject = [];
}
if (config.propagateOnRejection === true || config.propagateOnRejection === undefined) {
config.propagateOnRejection = false;
config.onReject.push(reportToContext);
}
return config;
};
class ApolloCostLimitProtection extends ApolloProtection {
get isEnabled() {
var _this$config$costLimi;
if (!this.config.costLimit) {
return this.enabledByDefault;
}
return (_this$config$costLimi = this.config.costLimit.enabled) !== null && _this$config$costLimi !== void 0 ? _this$config$costLimi : this.enabledByDefault;
}
protect() {
this.config.costLimit = inferApolloPropagator(this.config.costLimit);
return {
validationRules: [graphqlArmorCostLimit.costLimitRule(this.config.costLimit)]
};
}
}
class ApolloMaxAliasesProtection extends ApolloProtection {
get isEnabled() {
var _this$config$maxAlias;
if (!this.config.maxAliases) {
return this.enabledByDefault;
}
return (_this$config$maxAlias = this.config.maxAliases.enabled) !== null && _this$config$maxAlias !== void 0 ? _this$config$maxAlias : this.enabledByDefault;
}
protect() {
this.config.maxAliases = inferApolloPropagator(this.config.maxAliases);
return {
validationRules: [graphqlArmorMaxAliases.maxAliasesRule(this.config.maxAliases)]
};
}
}
class ApolloMaxDepthProtection extends ApolloProtection {
get isEnabled() {
var _this$config$maxDepth;
if (!this.config.maxDepth) {
return this.enabledByDefault;
}
return (_this$config$maxDepth = this.config.maxDepth.enabled) !== null && _this$config$maxDepth !== void 0 ? _this$config$maxDepth : this.enabledByDefault;
}
protect() {
this.config.maxDepth = inferApolloPropagator(this.config.maxDepth);
return {
validationRules: [graphqlArmorMaxDepth.maxDepthRule(this.config.maxDepth)]
};
}
}
class ApolloMaxDirectivesProtection extends ApolloProtection {
get isEnabled() {
var _this$config$maxDirec;
if (!this.config.maxDirectives) {
return this.enabledByDefault;
}
return (_this$config$maxDirec = this.config.maxDirectives.enabled) !== null && _this$config$maxDirec !== void 0 ? _this$config$maxDirec : this.enabledByDefault;
}
protect() {
this.config.maxDirectives = inferApolloPropagator(this.config.maxDirectives);
return {
validationRules: [graphqlArmorMaxDirectives.maxDirectivesRule(this.config.maxDirectives)]
};
}
}
const plugin = options => {
return {
async unexpectedErrorProcessingRequest(err) {
throw new graphql.GraphQLError(err.error, {
extensions: {
code: 'GRAPHQL_VALIDATION_FAILED'
}
});
},
async requestDidStart() {
return {
async parsingDidStart(requestContext) {
const source = requestContext.source;
if (source !== undefined) {
const parser = new graphqlArmorMaxTokens.MaxTokensParserWLexer(source, options);
parser.parseDocument();
}
}
};
}
};
};
class ApolloMaxTokensProtection extends ApolloProtection {
get isEnabled() {
var _this$config$maxToken;
if (!this.config.maxTokens) {
return this.enabledByDefault;
}
return (_this$config$maxToken = this.config.maxTokens.enabled) !== null && _this$config$maxToken !== void 0 ? _this$config$maxToken : this.enabledByDefault;
}
protect() {
var _this$config$maxToken2;
return {
plugins: [plugin((_this$config$maxToken2 = this.config.maxTokens) !== null && _this$config$maxToken2 !== void 0 ? _this$config$maxToken2 : graphqlArmorMaxTokens.maxTokenDefaultOptions)]
};
}
}
class ApolloArmor {
constructor(config = {}) {
_defineProperty(this, "protections", void 0);
this.protections = [new ApolloBlockFieldSuggestionProtection(config), new ApolloMaxTokensProtection(config), new ApolloCostLimitProtection(config), new ApolloMaxAliasesProtection(config), new ApolloMaxDirectivesProtection(config), new ApolloMaxDepthProtection(config)];
}
protect() {
let plugins = [];
let validationRules = [];
for (const protection of this.protections) {
if (protection.isEnabled) {
const {
plugins: newPlugins,
validationRules: newValidationRules
} = protection.protect();
plugins = [...plugins, ...(newPlugins || [])];
validationRules = [...validationRules, ...(newValidationRules || [])];
}
}
return {
plugins,
validationRules,
allowBatchedHttpRequests: false,
includeStacktraceInErrorResponses: false
};
}
}
class EnvelopProtection {
constructor(config) {
_defineProperty(this, "config", void 0);
_defineProperty(this, "enabledByDefault", true);
this.config = config;
}
}
class EnvelopBlockFieldSuggestionProtection extends EnvelopProtection {
get isEnabled() {
var _this$config$blockFie;
if (!this.config.blockFieldSuggestion) {
return this.enabledByDefault;
}
return (_this$config$blockFie = this.config.blockFieldSuggestion.enabled) !== null && _this$config$blockFie !== void 0 ? _this$config$blockFie : this.enabledByDefault;
}
protect() {
return {
plugins: [graphqlArmorBlockFieldSuggestions.blockFieldSuggestionsPlugin(this.config.blockFieldSuggestion)]
};
}
}
class EnvelopCostLimitProtection extends EnvelopProtection {
get isEnabled() {
var _this$config$costLimi;
if (!this.config.costLimit) {
return this.enabledByDefault;
}
return (_this$config$costLimi = this.config.costLimit.enabled) !== null && _this$config$costLimi !== void 0 ? _this$config$costLimi : this.enabledByDefault;
}
protect() {
return {
plugins: [graphqlArmorCostLimit.costLimitPlugin(this.config.costLimit)]
};
}
}
class EnvelopMaxAliasesProtection extends EnvelopProtection {
get isEnabled() {
var _this$config$maxAlias;
if (!this.config.maxAliases) {
return this.enabledByDefault;
}
return (_this$config$maxAlias = this.config.maxAliases.enabled) !== null && _this$config$maxAlias !== void 0 ? _this$config$maxAlias : this.enabledByDefault;
}
protect() {
return {
plugins: [graphqlArmorMaxAliases.maxAliasesPlugin(this.config.maxAliases)]
};
}
}
class EnvelopMaxDepthProtection extends EnvelopProtection {
get isEnabled() {
var _this$config$maxDepth;
if (!this.config.maxDepth) {
return this.enabledByDefault;
}
return (_this$config$maxDepth = this.config.maxDepth.enabled) !== null && _this$config$maxDepth !== void 0 ? _this$config$maxDepth : this.enabledByDefault;
}
protect() {
return {
plugins: [graphqlArmorMaxDepth.maxDepthPlugin(this.config.maxDepth)]
};
}
}
class EnvelopMaxDirectivesProtection extends EnvelopProtection {
get isEnabled() {
var _this$config$maxDirec;
if (!this.config.maxDirectives) {
return this.enabledByDefault;
}
return (_this$config$maxDirec = this.config.maxDirectives.enabled) !== null && _this$config$maxDirec !== void 0 ? _this$config$maxDirec : this.enabledByDefault;
}
protect() {
return {
plugins: [graphqlArmorMaxDirectives.maxDirectivesPlugin(this.config.maxDirectives)]
};
}
}
class EnvelopMaxTokensProtection extends EnvelopProtection {
get isEnabled() {
var _this$config$maxToken;
if (!this.config.maxTokens) {
return this.enabledByDefault;
}
return (_this$config$maxToken = this.config.maxTokens.enabled) !== null && _this$config$maxToken !== void 0 ? _this$config$maxToken : this.enabledByDefault;
}
protect() {
return {
plugins: [graphqlArmorMaxTokens.maxTokensPlugin(this.config.maxTokens)]
};
}
}
const EnvelopArmorPlugin = config => {
const armor = new EnvelopArmor(config);
const enhancements = armor.protect();
return {
onPluginInit({
addPlugin
}) {
for (const plugin of enhancements.plugins) {
addPlugin(plugin);
}
}
};
};
class EnvelopArmor {
constructor(config = {}) {
_defineProperty(this, "protections", void 0);
this.protections = [new EnvelopBlockFieldSuggestionProtection(config), new EnvelopMaxTokensProtection(config), new EnvelopMaxDirectivesProtection(config), new EnvelopMaxAliasesProtection(config), new EnvelopCostLimitProtection(config), new EnvelopMaxDepthProtection(config)];
}
protect() {
const plugins = [];
for (const protection of this.protections) {
if (protection.isEnabled) {
const enhancements = protection.protect();
plugins.push(...enhancements.plugins);
}
}
return {
plugins
};
}
}
exports.ApolloArmor = ApolloArmor;
exports.EnvelopArmor = EnvelopArmor;
exports.EnvelopArmorPlugin = EnvelopArmorPlugin;