Skip to content
Open
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
18 changes: 17 additions & 1 deletion stellar-payment-platform/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -926,8 +926,24 @@ app.get('/users', validateSchema({ query: usersQuerySchema }), async (req, res,
// Mount v1 router for both legacy paths and explicit API versioning
app.use('/', v1Router);
app.use('/api/v1', v1Router);
// #492 — Strict rate limiter for auth/login endpoints. These are prime
// brute-force targets, so they get a much tighter budget than the global
// limiter. Uses the same Redis-backed store so the limit is shared across
// all distributed nodes.
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 20,
store: redisClient ? new RedisStore({
sendCommand: (...args) => redisClient.sendCommand(args),
}) : undefined,
standardHeaders: true,
legacyHeaders: true,
message: errorBody('RATE_LIMITED', 'Too many requests, please try again later.'),
keyGenerator: (req) => req.ip || (req.connection && req.connection.remoteAddress) || '',
});

// Auth endpoints (email OTP verification) - uses Redis when available
app.use('/auth', require('./src/routes/v1/authRoutes')(redisClient));
app.use('/auth', authLimiter, require('./src/routes/v1/authRoutes')(redisClient));

// #497 — Expose RSA public key as a JWKS document so external services can
// verify RS256-signed tokens without sharing a secret.
Expand Down
27 changes: 27 additions & 0 deletions stellar-payment-platform/tests/rate-limit.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,33 @@ describe('Rate Limiting — express-rate-limit', () => {
});
});

// ── Strict auth/login rate limit ─────────────────────────────────────────

describe('strict auth rate limit', () => {
it('applies a stricter limit on /auth endpoints than the global limiter', async () => {
// The auth limiter allows only 20 requests per window, so the 21st
// request should be rejected even though the global limit is 100.
for (let i = 0; i < 20; i++) {
await request(app)
.post('/auth/verify-email')
.send({ email: `user${i}@example.com` });
}

const res = await request(app)
.post('/auth/verify-email')
.send({ email: 'overflow@example.com' });

expect(res.status).toBe(429);
expect(res.body).toEqual({
success: false,
error: {
code: 'RATE_LIMITED',
message: 'Too many requests, please try again later.',
},
});
});
});

// ── Reset between test modules ───────────────────────────────────────────

describe('rate limiter resets with new app instance', () => {
Expand Down
Loading