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.
- Host: GitHub
- URL: https://github.com/oslabs-beta/guardenoql
- Owner: oslabs-beta
- License: mit
- Created: 2022-08-29T17:23:08.000Z (almost 3 years ago)
- Default Branch: main
- Last Pushed: 2022-09-24T19:39:38.000Z (almost 3 years ago)
- Last Synced: 2025-04-30T19:07:24.737Z (about 2 months ago)
- Topics: deno, graphql, open-source, opine, security
- Language: TypeScript
- Homepage: https://www.guardenoql.dev/
- Size: 249 KB
- Stars: 43
- Watchers: 2
- Forks: 8
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
![]()
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 versiontype 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.