diff --git a/apps/auth/app.ts b/apps/auth/app.ts index fc1a8a6..61addcd 100644 --- a/apps/auth/app.ts +++ b/apps/auth/app.ts @@ -6,6 +6,7 @@ import { auth } from '@wxyc/authentication'; import { toNodeHandler } from 'better-auth/node'; import cors from 'cors'; import express from 'express'; +import rateLimit from 'express-rate-limit'; const port = process.env.AUTH_PORT || '8080'; @@ -107,9 +108,24 @@ if (process.env.NODE_ENV !== 'production') { ); } -// Mount the Better Auth handler for all auth routes -// app.use() will handle all methods and paths under /auth -app.use('/auth', toNodeHandler(auth)); +// Disable rate limiting in test environments to avoid flaky integration tests. +// This matches the pattern used by the backend's rateLimiting middleware. +const isTestEnv = + process.env.NODE_ENV === 'test' || process.env.USE_MOCK_SERVICES === 'true' || process.env.AUTH_BYPASS === 'true'; + +if (isTestEnv) { + app.use('/auth', toNodeHandler(auth)); +} else { + const authRateLimit = rateLimit({ + windowMs: 15 * 60 * 1000, + limit: 10, + standardHeaders: 'draft-7', + legacyHeaders: false, + message: { error: 'Too many requests, please try again later.' }, + }); + + app.use('/auth', authRateLimit, toNodeHandler(auth)); +} //endpoint for healthchecks app.get('/healthcheck', async (req, res) => { diff --git a/apps/auth/package.json b/apps/auth/package.json index 4841dd7..2ce02df 100644 --- a/apps/auth/package.json +++ b/apps/auth/package.json @@ -16,12 +16,13 @@ "author": "Jackson Meade", "license": "MIT", "dependencies": { - "@wxyc/database": "^1.0.0", "@wxyc/authentication": "^1.0.0", + "@wxyc/database": "^1.0.0", "better-auth": "^1.3.23", "cors": "^2.8.5", "dotenv": "^17.2.1", - "express": "^5.1.0" + "express": "^5.1.0", + "express-rate-limit": "^8.2.1" }, "peerDependencies": { "drizzle-orm": "^0.41.0" diff --git a/apps/auth/tsup.config.ts b/apps/auth/tsup.config.ts index b58553e..ea7c6d9 100644 --- a/apps/auth/tsup.config.ts +++ b/apps/auth/tsup.config.ts @@ -9,8 +9,5 @@ export default defineConfig((options) => ({ clean: true, sourcemap: true, external: ['@wxyc/database', 'better-auth', 'drizzle-orm', 'express', 'cors', 'postgres'], - env: { - NODE_ENV: process.env.NODE_ENV || 'development', - }, onSuccess: options.watch ? 'node ./dist/app.js' : undefined, })); diff --git a/dev_env/docker-compose.yml b/dev_env/docker-compose.yml index e8136b1..841420c 100644 --- a/dev_env/docker-compose.yml +++ b/dev_env/docker-compose.yml @@ -90,6 +90,7 @@ services: dockerfile: Dockerfile.auth profiles: [ci] environment: + - NODE_ENV=test - DB_HOST=ci-db - DB_PORT=5432 - DB_USERNAME=${DB_USERNAME} diff --git a/package-lock.json b/package-lock.json index 9ef6de8..24ada26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,7 +54,8 @@ "better-auth": "^1.3.23", "cors": "^2.8.5", "dotenv": "^17.2.1", - "express": "^5.1.0" + "express": "^5.1.0", + "express-rate-limit": "^8.2.1" }, "devDependencies": { "@types/cors": "^2.8.17", diff --git a/tests/unit/auth/rate-limiting.test.ts b/tests/unit/auth/rate-limiting.test.ts new file mode 100644 index 0000000..3e7173b --- /dev/null +++ b/tests/unit/auth/rate-limiting.test.ts @@ -0,0 +1,25 @@ +import { readFileSync } from 'fs'; +import { resolve } from 'path'; + +describe('Auth service rate limiting', () => { + const authAppSource = readFileSync(resolve(__dirname, '../../../apps/auth/app.ts'), 'utf-8'); + + it('imports express-rate-limit', () => { + expect(authAppSource).toMatch(/express-rate-limit/); + }); + + it('configures a rate limiter with rateLimit()', () => { + expect(authAppSource).toMatch(/rateLimit\s*\(/); + }); + + it('applies rate limiting to the auth handler in production', () => { + // The rate limiter is applied conditionally: skipped in test env, active otherwise. + // Verify the production branch wires authRateLimit before toNodeHandler(auth). + expect(authAppSource).toMatch(/authRateLimit,\s*toNodeHandler\s*\(\s*auth\s*\)/); + }); + + it('disables rate limiting in test environments', () => { + expect(authAppSource).toMatch(/isTestEnv/); + expect(authAppSource).toMatch(/NODE_ENV.*test|USE_MOCK_SERVICES/); + }); +});