diff --git a/api/jest.utils.ts b/api/jest.utils.ts index 174ac5c8f4d..bacbccf46fc 100644 --- a/api/jest.utils.ts +++ b/api/jest.utils.ts @@ -127,16 +127,6 @@ const indexData: IndexData[] = [ collection: 'Survey', indexes: [{ key: { userId: 1 }, name: 'userId_1' }] }, - { - collection: 'UserRateLimit', - indexes: [ - { - key: { expirationDate: 1 }, - name: 'expirationDate_1', - expireAfterSeconds: 0 - } - ] - }, { collection: 'UserToken', indexes: [{ key: { userId: 1 }, name: 'userId_1' }] diff --git a/api/package.json b/api/package.json index 10a8b9fc521..bd21cd68af1 100644 --- a/api/package.json +++ b/api/package.json @@ -9,7 +9,6 @@ "@fastify/cookie": "9.4.0", "@fastify/csrf-protection": "6.4.1", "@fastify/oauth2": "7.8.1", - "@fastify/rate-limit": "9.1.0", "@fastify/swagger": "8.14.0", "@fastify/swagger-ui": "1.10.2", "@fastify/type-provider-typebox": "3.6.0", @@ -33,7 +32,6 @@ "nodemon": "2.0.22", "pino-pretty": "10.2.3", "query-string": "7.1.3", - "rate-limit-mongo": "^2.3.2", "stripe": "16.0.0", "validator": "13.11.0" }, diff --git a/api/prisma/schema.prisma b/api/prisma/schema.prisma index d31ac2e1de0..f606d142de5 100644 --- a/api/prisma/schema.prisma +++ b/api/prisma/schema.prisma @@ -365,14 +365,6 @@ model Donation { @@index([userId], map: "userId_1") } -model UserRateLimit { - id String @id @map("_id") - counter Int - expirationDate DateTime @db.Date - - @@index([expirationDate], map: "expirationDate_1") -} - model UserToken { id String @id @map("_id") created DateTime @db.Date diff --git a/api/src/routes/public/auth.test.ts b/api/src/routes/public/auth.test.ts index 8a0f105a111..43eab1b46ef 100644 --- a/api/src/routes/public/auth.test.ts +++ b/api/src/routes/public/auth.test.ts @@ -54,26 +54,11 @@ describe('auth0 routes', () => { superGet = createSuperRequest({ method: 'GET' }); }); beforeEach(async () => { - await fastifyTestInstance.prisma.userRateLimit.deleteMany({}); await fastifyTestInstance.prisma.user.deleteMany({ where: { email: newUserEmail } }); }); - it('should be rate-limited', async () => { - // Rather than spamming the endpoint, we can check the headers. - const res = await superGet('/mobile-login'); - // These headers are semi-official - // https://www.ietf.org/archive/id/draft-polli-ratelimit-headers-02.html - // so should not depend on the details of the rate-limiting library - expect(res.headers['ratelimit-limit']).toBe('10'); - expect(res.headers['ratelimit-remaining']).toBe('9'); - expect(res.headers['ratelimit-reset']).toMatch(/^\d+$/); - - const res2 = await superGet('/mobile-login'); - expect(res2.headers['ratelimit-remaining']).toBe('8'); - }); - it('should return 401 if the authorization header is invalid', async () => { mockedFetch.mockResolvedValueOnce(mockAuth0NotOk()); const res = await superGet('/mobile-login').set( diff --git a/api/src/routes/public/auth.ts b/api/src/routes/public/auth.ts index 6096597ea26..fcc67f216a4 100644 --- a/api/src/routes/public/auth.ts +++ b/api/src/routes/public/auth.ts @@ -1,15 +1,7 @@ -import type { - FastifyPluginCallback, - FastifyPluginAsync, - FastifyRequest, - RouteOptions -} from 'fastify'; -import rateLimit, { type FastifyRateLimitStore } from '@fastify/rate-limit'; -// @ts-expect-error - no types -import MongoStoreRL from 'rate-limit-mongo'; +import type { FastifyPluginCallback, FastifyRequest } from 'fastify'; import isEmail from 'validator/lib/isEmail'; -import { AUTH0_DOMAIN, MONGOHQ_URL } from '../../utils/env'; +import { AUTH0_DOMAIN } from '../../utils/env'; import { auth0Client } from '../../plugins/auth0'; import { createAccessToken } from '../../utils/tokens'; import { findOrCreateUser } from '../helpers/auth-helpers'; @@ -31,75 +23,19 @@ const getEmailFromAuth0 = async ( return typeof email === 'string' ? email : null; }; -// TODO: Use Redis! Then we don't need to maintain this store. -class Store implements FastifyRateLimitStore { - mongoStore: MongoStoreRL; - // We don't really need this.options, but it's here for consistency with the - // custom store in the fastify-rate-limit docs. - options: { timeWindow: number }; - constructor({ timeWindow }: { timeWindow: number }) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call - this.mongoStore = new MongoStoreRL({ - collectionName: 'UserRateLimit', - uri: MONGOHQ_URL, - expireTimeMs: timeWindow // timeWindow is Fastify's equivalent of express-rate-limit's expireTimeMs - }); - this.options = { timeWindow }; - } - - incr( - key: string, - cb: (err: Error | null, result?: { current: number; ttl: number }) => void - ) { - // This converts between what rate-limit-mongo calls and what - // fastify-rate-limit expects - const callbackConverted = ( - err: Error | null, - current: number, - expires: Date - ) => { - const ttl = expires.getTime() - Date.now(); - cb(err, { current, ttl }); - }; - // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access - this.mongoStore.incr(key, callbackConverted); - } - - // routeOptions are ignored for now, but this is the signature we need to implement - child( - routeOptions: RouteOptions & { path: string; prefix: string } - ): FastifyRateLimitStore { - const childParams = { ...this.options, ...routeOptions }; - const store = new Store(childParams); - return store; - } -} - /** * Route handler for Mobile authentication. * * @param fastify The Fastify instance. * @param _options Options passed to the plugin via `fastify.register(plugin, options)`. + * @param done Callback to signal that the logic has completed. * */ -export const mobileAuth0Routes: FastifyPluginAsync = async ( +export const mobileAuth0Routes: FastifyPluginCallback = ( fastify, - _options + _options, + done ) => { - // Rate limit for mobile login - // 10 requests per 15 minute windows - // @ts-expect-error - no types - await fastify.register(rateLimit, { - timeWindow: 15 * 60 * 1000, - max: 10, - enableDraftSpec: true, // ratelimit-* instead of x-ratelimit-* - keyGenerator: req => { - return (req.headers['x-forwarded-for'] as string) || 'localhost'; - }, - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call - store: Store - }); - // TODO(Post-MVP): move this into the app, so that we add this hook once for // all auth routes. fastify.addHook('onRequest', fastify.redirectIfSignedIn); @@ -124,6 +60,8 @@ export const mobileAuth0Routes: FastifyPluginAsync = async ( reply.setAccessTokenCookie(createAccessToken(id)); }); + + done(); }; /** diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 423f0cff530..f6864609ee0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -147,9 +147,6 @@ importers: '@fastify/oauth2': specifier: 7.8.1 version: 7.8.1 - '@fastify/rate-limit': - specifier: 9.1.0 - version: 9.1.0 '@fastify/swagger': specifier: 8.14.0 version: 8.14.0 @@ -219,9 +216,6 @@ importers: query-string: specifier: 7.1.3 version: 7.1.3 - rate-limit-mongo: - specifier: ^2.3.2 - version: 2.3.2 stripe: specifier: 16.0.0 version: 16.0.0 @@ -3038,9 +3032,6 @@ packages: '@fastify/oauth2@7.8.1': resolution: {integrity: sha512-PBIMizzgEOcUcttyfX1hC6CR9vESoI1lfNucBywgcqrxvknVg+zvBCgH2+oU8NvrpSDMtlY6nyuEYYZtVhDT7Q==} - '@fastify/rate-limit@9.1.0': - resolution: {integrity: sha512-h5dZWCkuZXN0PxwqaFQLxeln8/LNwQwH9popywmDCFdKfgpi4b/HoMH1lluy6P+30CG9yzzpSpwTCIPNB9T1JA==} - '@fastify/send@2.1.0': resolution: {integrity: sha512-yNYiY6sDkexoJR0D8IDy3aRP3+L4wdqCpvx5WP+VtEU58sn7USmKynBzDQex5X42Zzvw2gNzzYgP90UfWShLFA==} @@ -12749,10 +12740,6 @@ packages: resolution: {integrity: sha512-3oDzcogWGHZdkwrHyvJVpPjA7oNzY6ENOV3PsWJY9XYPZ6INo94Yd47s5may1U+nleBPwDhrRiTPMIvKaa3MQg==} engines: {node: '>=12'} - toad-cache@3.7.0: - resolution: {integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==} - engines: {node: '>=12'} - toidentifier@1.0.0: resolution: {integrity: sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==} engines: {node: '>=0.6'} @@ -14489,7 +14476,7 @@ snapshots: '@babel/traverse': 7.23.7 '@babel/types': 7.23.9 convert-source-map: 2.0.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -16694,7 +16681,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.22.6 '@babel/parser': 7.23.6 '@babel/types': 7.23.9 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -17033,12 +17020,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@fastify/rate-limit@9.1.0': - dependencies: - '@lukeed/ms': 2.0.2 - fastify-plugin: 4.5.1 - toad-cache: 3.7.0 - '@fastify/send@2.1.0': dependencies: '@lukeed/ms': 2.0.2 @@ -19265,7 +19246,7 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 transitivePeerDependencies: - supports-color @@ -19592,7 +19573,7 @@ snapshots: dependencies: '@fastify/error': 3.4.1 archy: 1.0.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 fastq: 1.17.1 transitivePeerDependencies: - supports-color @@ -21172,6 +21153,10 @@ snapshots: optionalDependencies: supports-color: 5.5.0 + debug@4.3.4: + dependencies: + ms: 2.1.2 + debug@4.3.4(supports-color@8.1.1): dependencies: ms: 2.1.2 @@ -24106,7 +24091,7 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 transitivePeerDependencies: - supports-color @@ -24652,7 +24637,7 @@ snapshots: istanbul-lib-source-maps@4.0.1: dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 istanbul-lib-coverage: 3.2.0 source-map: 0.6.1 transitivePeerDependencies: @@ -25198,7 +25183,7 @@ snapshots: json-schema-resolver@2.0.0: dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 rfdc: 1.3.0 uri-js: 4.4.1 transitivePeerDependencies: @@ -28999,7 +28984,7 @@ snapshots: dependencies: '@hapi/hoek': 11.0.4 '@hapi/wreck': 18.1.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 joi: 17.12.2 transitivePeerDependencies: - supports-color @@ -29621,7 +29606,7 @@ snapshots: dependencies: component-emitter: 1.3.0 cookiejar: 2.1.4 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4 fast-safe-stringify: 2.1.1 form-data: 4.0.0 formidable: 2.1.2 @@ -29873,8 +29858,6 @@ snapshots: toad-cache@3.3.0: {} - toad-cache@3.7.0: {} - toidentifier@1.0.0: {} toidentifier@1.0.1: {}