An open API service indexing awesome lists of open source software.

https://github.com/oslabs-beta/guardenoql

Simple and customizable security middleware for GraphQL servers in Deno.
https://github.com/oslabs-beta/guardenoql

deno graphql open-source opine security

Last synced: about 2 months ago
JSON representation

Simple and customizable security middleware for GraphQL servers in Deno.

Awesome Lists containing this project

README

        






guardenoql-logo




GuarDenoQL


Simple and customizable security middleware for GraphQL servers in Deno


# Features

- Integrates with an Opine server in a Deno runtime.
- Enables users to customize both a _**maximum depth**_ and a _**cost limit**_
for all GraphQL queries and mutations sent to the server.
- Validates queries and mutations against the depth limiter and/or cost limiter
before they are executed by the server.

# Why?

### **Depth Limiting**

Because GraphQL schemas can be cyclic graphs, it is possible that a client could
construct a query such as this one:




Therefore, if nested deep enough, a malicious actor could potentially bring your server down with an abusive query.



However, using a **Depth Limiter**, you can validate the depth of incoming
queries against a user-defined limit and prevent these queries from going
through.

### **Cost Limiting**

Queries can still be very expensive even if they aren't nested deeply. Using a
**Cost Limiter**, your server will calculate the total cost of the query based
on its types before execution.



# Getting Started

A set up with [gql](https://github.com/deno-libs/gql) and
[Opine](https://github.com/cmorten/opine) out-of-the-box:

```typescript
import { opine, OpineRequest } from "https://deno.land/x/[email protected]/mod.ts";
import { GraphQLHTTP } from "https://deno.land/x/[email protected]/mod.ts";
import { makeExecutableSchema } from "https://deno.land/x/[email protected]/mod.ts";
import { gql } from "https://deno.land/x/[email protected]/mod.ts";
import { readAll } from "https://deno.land/[email protected]/streams/conversion.ts";

import { guarDenoQL } from "https://deno.land/x/[email protected]/mod.ts";
// update GuarDenoQL import URL with most recent version

type Request = OpineRequest & { json: () => Promise };

const typeDefs = gql`
type Query {
hello: String
}
`;

const resolvers = { Query: { hello: () => `Hello World!` } };
const dec = new TextDecoder();
const schema = makeExecutableSchema({ resolvers, typeDefs });
const app = opine();

app
.use("/graphql", async (req, res) => {
const request = req as Request;

request.json = async () => {
const rawBody = await readAll(req.raw);
const body = JSON.parse(dec.decode(rawBody));
const query = body.query;

const error = guarDenoQL(schema, query, {
depthLimitOptions: {
maxDepth: 4, // maximum depth allowed before a request is rejected
callback: (args) => console.log("query depth is:", args), // optional
},
costLimitOptions: {
maxCost: 5000, // maximum cost allowed before a request is rejected
mutationCost: 5, // cost of a mutation
objectCost: 2, // cost of retrieving an object
scalarCost: 1, // cost of retrieving a scalar
depthCostFactor: 1.5, // multiplicative cost of each depth level
callback: (args) => console.log("query cost is:", args), // optional
},
});

if (error !== undefined && !error.length) {
return body;
} else {
const errorMessage = { error };
return res.send(JSON.stringify(errorMessage));
}
};

const resp = await GraphQLHTTP({
schema,
context: (request) => ({ request }),
graphiql: true,
})(request);

for (const [k, v] of resp.headers.entries()) res.headers?.append(k, v);
res.status = resp.status;
res.send(await resp.text());
})
.listen(3000, () => console.log(`☁ Started on http://localhost:3000`));
```

GuarDenoQL is fully customizable.

Users can use either the depth limiter, cost limiter or both.

The first argument is the `schema`, the second argument is the `query`, and the
third argument is an `Object` with up to two properties: `depthLimitOptions`
and/or `costLimitOptions`.

### **Depth Limit Configuration**

This feature limits the depth of a document.

```typescript
const error = guarDenoQL(schema, query, {
depthLimitOptions: {
maxDepth: 4, // maximum depth allowed before a request is rejected
callback: (args) => console.log("query depth is:", args), // optional
},
});
```

The `depthLimitOptions` object has two properties to configure:

1. `maxDepth`: the depth limiter will throw a validation error if the document
has a greater depth than the user-supplied `maxDepth`

2. optional `callback` function: receives an `Object` that maps the name of the
operation to its corresponding query depth

### **Cost Limit Configuration**

This feature applies a cost analysis algorithm to block queries that are too
expensive.

```typescript
const error = guarDenoQL(schema, query, {
costLimitOptions: {
maxCost: 5000, // maximum cost allowed before a request is rejected
mutationCost: 5, // cost of a mutation
objectCost: 2, // cost of retrieving an object
scalarCost: 1, // cost of retrieving a scalar
depthCostFactor: 1.5, // multiplicative cost of each depth level
callback: (args) => console.log("query cost is:", args), // optional
},
});
```

The `costLimitOptions` object has six properties to configure:

1. `maxCost`: the cost limiter will throw a validation error if the document has
a greater cost than the user-supplied `maxCost`

2. `mutationCost`: represents the cost of a mutation (some popular
[cost analysis algorithms](https://shopify.engineering/rate-limiting-graphql-apis-calculating-query-complexity)
make mutations more expensive than queries)

3. `objectCost`: represents the cost of an object that has subfields

4. `scalarCost`: represents the cost of a scalar

5. `depthCostFactor`: the multiplicative cost of each depth level

6. optional `callback` function: receives an `Object` that maps the name of the
operation to its corresponding query cost

# Functionality

### **Depth Limiter**



### **Cost Limiter**



# How to Contribute

If you would like to contribute, please see
[CONTRIBUTING.md](https://github.com/oslabs-beta/GuarDenoQL/blob/main/CONTRIBUTING.md)
for more information.

# Authors

Finley Decker: [GitHub](https://github.com/finleydecker) |
[LinkedIn](https://www.linkedin.com/in/finleydecker/)

Hannah McDowell: [GitHub](https://github.com/hannahmcdowell) |
[LinkedIn](https://www.linkedin.com/in/hannah-lisbeth-mcdowell/)

Jane You: [GitHub](https://github.com/janeyou94) |
[LinkedIn](https://www.linkedin.com/in/janeyou-pharmd-bcacp/)

Lucien Hsu: [GitHub](https://github.com/LBLuc) |
[LinkedIn](https://www.linkedin.com/in/lucien-hsu/)

# License

Distributed under the MIT License. See
[LICENSE](https://github.com/oslabs-beta/GuarDenoQL/blob/main/LICENSE) for more
information.