diff --git a/package.json b/package.json index acedd9b0..b8a65eb6 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "@ethersproject/hash": "^5.5.0", "@ethersproject/providers": "^5.6.8", "@ethersproject/wallet": "^5.6.2", - "@snapshot-labs/keycard": "^0.1.0", + "@snapshot-labs/keycard": "^0.2.0", "@snapshot-labs/pineapple": "^0.1.0-beta.1", "@snapshot-labs/snapshot-sentry": "^1.1.0", "@snapshot-labs/snapshot.js": "^0.4.107", diff --git a/src/helpers/keycard.ts b/src/helpers/keycard.ts index 3c419441..3e0171ef 100644 --- a/src/helpers/keycard.ts +++ b/src/helpers/keycard.ts @@ -7,15 +7,18 @@ const keycard = new Keycard({ URL: process.env.KEYCARD_URL || 'https://keycard.snapshot.org' }); -const verifyKeyCard = async (req, res, next) => { - const key = req.headers['x-api-key'] || req.query.apiKey; - if (key && keycard.configured) { - const { valid, rateLimited } = keycard.logReq(key); +const checkKeycard = async (req, res, next) => { + const apiKey = req.headers['x-api-key'] || req.query.apiKey; + if (apiKey && keycard.configured) { + const keycardData = keycard.logReq(apiKey); + if (!keycardData.valid) return sendError(res, 'invalid api key', 401); - if (!valid) return sendError(res, 'invalid api key', 401); - if (rateLimited) return sendError(res, 'too many requests', 429); + res.locals.keycardData = keycardData; + res.set('X-Api-Key-Limit', keycardData.limit); + res.set('X-Api-Key-Remaining', keycardData.remaining); + res.set('X-Api-Key-Reset', keycardData.reset); } return next(); }; -export { keycard, verifyKeyCard }; +export { keycard, checkKeycard }; diff --git a/src/helpers/rateLimit.ts b/src/helpers/rateLimit.ts index 9a7f26d9..93d549ea 100644 --- a/src/helpers/rateLimit.ts +++ b/src/helpers/rateLimit.ts @@ -1,16 +1,19 @@ import rateLimit from 'express-rate-limit'; import { getIp, sendError } from './utils'; import log from './log'; -import { keycard } from './keycard'; export default rateLimit({ windowMs: 20 * 1e3, max: 60, keyGenerator: req => getIp(req), standardHeaders: true, - skip: req => { - const key = req.headers['x-api-key'] || req.query.apiKey; - return key && keycard.configured; + skip: (req, res) => { + const keycardData = res.locals.keycardData; + if (keycardData?.valid && !keycardData.rateLimited) { + return true; + } + + return false; }, handler: (req, res) => { log.info(`too many requests ${getIp(req).slice(0, 7)}`); diff --git a/src/index.ts b/src/index.ts index 9987f9d7..23e505f3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,7 @@ import graphql from './graphql'; import rateLimit from './helpers/rateLimit'; import log from './helpers/log'; import './helpers/strategies'; -import { verifyKeyCard } from './helpers/keycard'; +import { checkKeycard } from './helpers/keycard'; import './helpers/moderation'; import { initLogger, fallbackLogger } from '@snapshot-labs/snapshot-sentry'; @@ -19,9 +19,9 @@ app.use(express.json({ limit: '20mb' })); app.use(express.urlencoded({ limit: '20mb', extended: false })); app.use(cors({ maxAge: 86400 })); app.set('trust proxy', 1); -app.use(rateLimit); +app.use(checkKeycard, rateLimit); app.use('/api', api); -app.use('/graphql', verifyKeyCard, graphql); +app.use('/graphql', graphql); fallbackLogger(app); app.get('/*', (req, res) => res.redirect('/api')); diff --git a/yarn.lock b/yarn.lock index 09668aa6..49d9f5e8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1286,10 +1286,10 @@ dependencies: "@snapshot-labs/eslint-config-base" "^0.1.0-beta.7" -"@snapshot-labs/keycard@^0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@snapshot-labs/keycard/-/keycard-0.1.0.tgz#4904a43a61b48f7b0d0db02246f8a104d1daa536" - integrity sha512-CSbioDWmR5nqxYpEAJBzEbLXn8ucsg3arCLI2tgdhDIskH8n19Pzo21jELjql+/zUM8sPMQKnhmv1xTAIq9I4w== +"@snapshot-labs/keycard@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@snapshot-labs/keycard/-/keycard-0.2.0.tgz#7a9e4dd2224089fd291fe63ada738a70a5407d46" + integrity sha512-cfA2/fNy9MhChl0MR/rF0AY+Dzj9sQPd/jcZbIxsx7p4Dd8ZmSo6ufHz60H7pHBM5r4KgozHTlscN7sQVShZYg== dependencies: cross-fetch "^3.1.5"