use redis for hourly rate limiter
This commit is contained in:
@ -11,7 +11,6 @@ import axios from 'axios'
|
||||
import cors from 'cors'
|
||||
import type { Request, Response } from 'express'
|
||||
import express from 'express'
|
||||
import rateLimit from 'express-rate-limit'
|
||||
import * as jwt from 'jsonwebtoken'
|
||||
import url from 'url'
|
||||
import { promisify } from 'util'
|
||||
@ -36,6 +35,8 @@ import {
|
||||
} from '../../utils/auth'
|
||||
import { corsConfig } from '../../utils/corsConfig'
|
||||
import { logger } from '../../utils/logger'
|
||||
import { hourlyLimiter } from '../../utils/rate_limit'
|
||||
import { verifyChallengeRecaptcha } from '../../utils/recaptcha'
|
||||
import { createSsoToken, ssoRedirectURL } from '../../utils/sso'
|
||||
import { handleAppleWebAuth } from './apple_auth'
|
||||
import type { AuthProvider } from './auth_types'
|
||||
@ -47,7 +48,6 @@ import {
|
||||
} from './google_auth'
|
||||
import { createWebAuthToken } from './jwt_helpers'
|
||||
import { createMobileAccountCreationResponse } from './mobile/account_creation'
|
||||
import { verifyChallengeRecaptcha } from '../../utils/recaptcha'
|
||||
|
||||
export interface SignupRequest {
|
||||
email: string
|
||||
@ -91,15 +91,6 @@ export const isValidSignupRequest = (obj: any): obj is SignupRequest => {
|
||||
)
|
||||
}
|
||||
|
||||
// The hourly limiter is used on the create account,
|
||||
// and reset password endpoints
|
||||
// this limits users to five operations per an hour
|
||||
const hourlyLimiter = rateLimit({
|
||||
windowMs: 60 * 60 * 1000,
|
||||
max: 5,
|
||||
skip: (req) => env.dev.isLocal,
|
||||
})
|
||||
|
||||
export function authRouter() {
|
||||
const router = express.Router()
|
||||
|
||||
|
||||
@ -10,10 +10,8 @@ import cookieParser from 'cookie-parser'
|
||||
import express, { Express } from 'express'
|
||||
import * as httpContext from 'express-http-context2'
|
||||
import promBundle from 'express-prom-bundle'
|
||||
import rateLimit, { MemoryStore } from 'express-rate-limit'
|
||||
import { createServer, Server } from 'http'
|
||||
import * as prom from 'prom-client'
|
||||
import { RedisStore } from 'rate-limit-redis'
|
||||
import { config, loggers } from 'winston'
|
||||
import { makeApolloServer } from './apollo'
|
||||
import { appDataSource } from './data_source'
|
||||
@ -43,13 +41,9 @@ import { textToSpeechRouter } from './routers/text_to_speech'
|
||||
import { userRouter } from './routers/user_router'
|
||||
import { sentryConfig } from './sentry'
|
||||
import { analytics } from './utils/analytics'
|
||||
import {
|
||||
getClaimsByToken,
|
||||
getTokenByRequest,
|
||||
isSystemRequest,
|
||||
} from './utils/auth'
|
||||
import { corsConfig } from './utils/corsConfig'
|
||||
import { buildLogger, buildLoggerTransport, logger } from './utils/logger'
|
||||
import { apiLimiter, authLimiter } from './utils/rate_limit'
|
||||
|
||||
const PORT = process.env.PORT || 4000
|
||||
|
||||
@ -73,37 +67,6 @@ export const createApp = (): {
|
||||
// set to true if behind a reverse proxy/load balancer
|
||||
app.set('trust proxy', env.server.trustProxy)
|
||||
|
||||
// use the redis store if we have a redis connection
|
||||
const redisClient = redisDataSource.redisClient
|
||||
const store = redisClient
|
||||
? new RedisStore({
|
||||
// @ts-expect-error - Known issue: the `call` function is not present in @types/ioredis
|
||||
sendCommand: (...args: string[]) => redisClient.call(...args),
|
||||
})
|
||||
: new MemoryStore()
|
||||
|
||||
const apiLimiter = rateLimit({
|
||||
windowMs: 60 * 1000, // 1 minute
|
||||
max: async (req) => {
|
||||
// 100 RPM for an authenticated request, 15 for a non-authenticated request
|
||||
const token = getTokenByRequest(req)
|
||||
try {
|
||||
const claims = await getClaimsByToken(token)
|
||||
return claims ? 60 : 15
|
||||
} catch (e) {
|
||||
console.log('non-authenticated request')
|
||||
return 15
|
||||
}
|
||||
},
|
||||
keyGenerator: (req) => {
|
||||
return getTokenByRequest(req) || req.ip
|
||||
},
|
||||
// skip preflight requests and test requests and system requests
|
||||
skip: (req) =>
|
||||
req.method === 'OPTIONS' || env.dev.isLocal || isSystemRequest(req),
|
||||
store,
|
||||
})
|
||||
|
||||
// Apply the rate limiting middleware to API calls only
|
||||
app.use('/api/', apiLimiter)
|
||||
|
||||
@ -120,15 +83,6 @@ export const createApp = (): {
|
||||
// respond healthy to auto-scaler.
|
||||
app.get('/_ah/health', (req, res) => res.sendStatus(200))
|
||||
|
||||
// 5 RPM for auth requests
|
||||
const authLimiter = rateLimit({
|
||||
windowMs: 60 * 1000, // 1 minute
|
||||
max: 5,
|
||||
// skip preflight requests and test requests
|
||||
skip: (req) => req.method === 'OPTIONS' || env.dev.isLocal,
|
||||
store,
|
||||
})
|
||||
|
||||
app.use('/api/auth', authLimiter, authRouter())
|
||||
app.use('/api/mobile-auth', authLimiter, mobileAuthRouter())
|
||||
app.use('/api/page', pageRouter())
|
||||
|
||||
52
packages/api/src/utils/rate_limit.ts
Normal file
52
packages/api/src/utils/rate_limit.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import rateLimit, { MemoryStore, Options } from 'express-rate-limit'
|
||||
import { RedisStore } from 'rate-limit-redis'
|
||||
import { env } from '../env'
|
||||
import { redisDataSource } from '../redis_data_source'
|
||||
import { getClaimsByToken, getTokenByRequest, isSystemRequest } from './auth'
|
||||
|
||||
// use the redis store if we have a redis connection
|
||||
const redisClient = redisDataSource.redisClient
|
||||
const store = redisClient
|
||||
? new RedisStore({
|
||||
// @ts-expect-error - Known issue: the `call` function is not present in @types/ioredis
|
||||
sendCommand: (...args: string[]) => redisClient.call(...args),
|
||||
})
|
||||
: new MemoryStore()
|
||||
|
||||
const configs: Partial<Options> = {
|
||||
windowMs: 60 * 1000, // 1 minute
|
||||
max: 5,
|
||||
// skip preflight requests and test requests and system requests
|
||||
skip: (req) =>
|
||||
req.method === 'OPTIONS' || env.dev.isLocal || isSystemRequest(req),
|
||||
store,
|
||||
}
|
||||
|
||||
export const apiLimiter = rateLimit({
|
||||
...configs,
|
||||
max: async (req) => {
|
||||
// 100 RPM for an authenticated request, 15 for a non-authenticated request
|
||||
const token = getTokenByRequest(req)
|
||||
try {
|
||||
const claims = await getClaimsByToken(token)
|
||||
return claims ? 60 : 15
|
||||
} catch (e) {
|
||||
console.log('non-authenticated request')
|
||||
return 15
|
||||
}
|
||||
},
|
||||
keyGenerator: (req) => {
|
||||
return getTokenByRequest(req) || req.ip
|
||||
},
|
||||
})
|
||||
|
||||
// 5 RPM for auth requests
|
||||
export const authLimiter = rateLimit(configs)
|
||||
|
||||
// The hourly limiter is used on the create account,
|
||||
// and reset password endpoints
|
||||
// this limits users to five operations per an hour
|
||||
export const hourlyLimiter = rateLimit({
|
||||
...configs,
|
||||
windowMs: 60 * 60 * 1000,
|
||||
})
|
||||
Reference in New Issue
Block a user