Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,13 @@
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"project": "./tsconfig.eslint.json"
"project": "./tsconfig.json"
},
"plugins": ["import"],
"plugins": ["import", "prettier"],
"rules": {
"indent": [
"warn",
4,
{
"SwitchCase": 2
}

"prettier/prettier": [
"error"
]
},
"ignorePatterns": ["jest.config.js"]
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# graph-beaver
# GraphQL-Gate

A GraphQL rate limiting library using query complexity analysis.
275 changes: 268 additions & 7 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"name": "graphql-complexity-limit",
"name": "graphql-gate",
"version": "1.0.0",
"description": "A GraphQL rate limiting library using query complexity analysis.",
"main": "index.js",
"scripts": {
"test": "jest --passWithNoTests",
"lint": "eslint src",
"lint:fix": "eslint --fix src",
"lint": "eslint src test",
"lint:fix": "eslint --fix src test",
"prettier": "prettier --write .",
"prepare": "husky install"
},
Expand All @@ -26,6 +26,7 @@
"@babel/preset-env": "^7.17.12",
"@babel/preset-typescript": "^7.17.12",
"@types/jest": "^27.5.1",
"@types/redis-mock": "^0.17.1",
"@typescript-eslint/eslint-plugin": "^5.24.0",
"@typescript-eslint/parser": "^5.24.0",
"babel-jest": "^28.1.0",
Expand All @@ -34,10 +35,12 @@
"eslint-config-airbnb-typescript": "^17.0.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.0.0",
"husky": "^8.0.1",
"jest": "^28.1.0",
"lint-staged": "^12.4.1",
"prettier": "2.6.2",
"redis-mock": "^0.56.3",
"ts-jest": "^28.0.2",
"typescript": "^4.6.4"
},
Expand All @@ -46,6 +49,7 @@
"*.{js,ts,css,md}": "prettier --write --ignore-unknown"
},
"dependencies": {
"redis": "^4.1.0",
"graphql": "^16.5.0"
}
}
24 changes: 17 additions & 7 deletions src/@types/rateLimit.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,23 @@ interface RateLimiter {
/**
* Checks if a request is allowed under the given conditions and withdraws the specified number of tokens
* @param uuid Unique identifier for the user associated with the request
* @param timestamp UNIX format timestamp of when request was received
* @param tokens Number of tokens being used in this request. Optional
* @returns true if the request is allowed
* @returns a RateLimiterResponse indicating with a sucess and tokens property indicating the number of tokens remaining
*/
processRequest: (uuid: string, tokens?: number) => boolean;
/**
* Connects the RateLimiter instance to a db to cache current token usage for connected users
* @param uri database connection string
*/
connect: (uri: string) => void;
processRequest: (
uuid: string,
timestamp: number,
tokens?: number
) => Promise<RateLimiterResponse>;
}

interface RateLimiterResponse {
success: boolean;
tokens?: number;
}

interface RedisBucket {
tokens: number;
timestamp: number;
}
33 changes: 23 additions & 10 deletions src/rateLimiters/tokenBucket.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,47 @@
import { RedisClientType } from 'redis';

/**
*
* The TokenBucket instance of a RateLimiter limits requests based on a unique user ID.
* Whenever a user makes a request the following steps are performed:
* 1. Refill the bucket based on time elapsed since the previous request
* 2. Update the timestamp of the last request.
* 3. Allow the request and remove the requested amount of tokens from the bucket if the user has enough.
* 4. Otherwise, disallow the request and do not update the token total.
*/
class TokenBucket implements RateLimiter {
capacity: number;

refillRate: number;

client: RedisClientType;

/**
* Create a new instance of a TokenBucket rate limiter that can be connected to any database store
* @param capacity max token bucket capacity
* @param refillRate rate at which the token bucket is refilled
* @param client redis client where rate limiter will cache information
*/
constructor(capacity: number, refillRate: number) {
constructor(capacity: number, refillRate: number, client: RedisClientType) {
this.capacity = capacity;
this.refillRate = refillRate;
this.client = client;
if (refillRate <= 0 || capacity <= 0)
throw Error('TokenBucket refillRate and capacity must be positive');
}

processRequest(uuid: string, tokens?: number): boolean {
async processRequest(
uuid: string,
timestamp: number,
tokens = 1
): Promise<RateLimiterResponse> {
throw Error(`TokenBucket.processRequest not implemented, ${this}`);
}

connect(uri: string) {
throw Error(`TokenBucket.connect not implemented, ${this}`);
}

/**
* @returns current size of the token bucket.
* Resets the rate limiter to the intial state by clearing the redis store.
*/
getSize(uuid: string): number {
throw Error(`TokenBucket.connect not implemented, ${this}`);
reset(): void {
throw Error(`TokenBucket.reset not implemented, ${this}`);
}
}

Expand Down
Loading