Skip to content

Commit 262ba53

Browse files
authored
Merge pull request #603 from wheval/fix/issues-549
2 parents 77ff9f0 + 2d92ded commit 262ba53

3 files changed

Lines changed: 74 additions & 19 deletions

File tree

backend/.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ INDEXER_POLL_INTERVAL_MS=5000
3333
# Ledger sequence to start indexing from on first run (0 = latest)
3434
INDEXER_START_LEDGER=0
3535

36+
# Max stream creation requests per wallet per minute (default: 10)
37+
STREAM_CREATE_RATE_LIMIT=10
38+
3639
# Server-side Stellar secret key used to sign and submit on-chain transactions
3740
# (cancel_stream, top_up_stream). Must be funded on the target network.
3841
KEEPER_SECRET_KEY=

backend/src/middleware/stream-rate-limiter.middleware.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,8 @@ export function createStreamRateLimiter(
6565

6666
/**
6767
* Pre-configured rate limiter for stream creation endpoint
68-
* 10 requests per minute per wallet
68+
* Default: 10 requests per minute per wallet (override via STREAM_CREATE_RATE_LIMIT)
6969
*/
7070
export const streamCreationRateLimiter = createStreamRateLimiter({
7171
windowMs: 60 * 1000, // 1 minute
72-
max: 10, // 10 requests per minute
7372
});

backend/tests/stream-rate-limiter.test.ts

Lines changed: 70 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, it, expect, beforeEach } from 'vitest';
1+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
22
import request from 'supertest';
33
import express, { type Request, type Response, type NextFunction } from 'express';
44
import { createStreamRateLimiter } from '../src/middleware/stream-rate-limiter.middleware.js';
@@ -242,32 +242,85 @@ describe('Stream Creation Rate Limiter Middleware', () => {
242242
});
243243

244244
describe('Environment Variable Configuration', () => {
245-
it('should use STREAM_CREATE_RATE_LIMIT environment variable', () => {
246-
const originalEnv = process.env.STREAM_CREATE_RATE_LIMIT;
247-
process.env.STREAM_CREATE_RATE_LIMIT = '5';
245+
const originalEnv = process.env.STREAM_CREATE_RATE_LIMIT;
248246

249-
createStreamRateLimiter({ windowMs: 10000 });
250-
// The limiter should be created with max: 5 from env
251-
252-
// Clean up
253-
if (originalEnv) {
247+
afterEach(() => {
248+
if (originalEnv !== undefined) {
254249
process.env.STREAM_CREATE_RATE_LIMIT = originalEnv;
255250
} else {
256251
delete process.env.STREAM_CREATE_RATE_LIMIT;
257252
}
253+
vi.resetModules();
258254
});
259255

260-
it('should default to 10 requests when STREAM_CREATE_RATE_LIMIT is not set', () => {
261-
const originalEnv = process.env.STREAM_CREATE_RATE_LIMIT;
256+
it('should use STREAM_CREATE_RATE_LIMIT when max is omitted', async () => {
257+
process.env.STREAM_CREATE_RATE_LIMIT = '2';
258+
vi.resetModules();
259+
const { createStreamRateLimiter: createLimiter } = await import(
260+
'../src/middleware/stream-rate-limiter.middleware.js'
261+
);
262+
const limiter = createLimiter({ windowMs: 10000 });
263+
264+
app.post(
265+
'/streams',
266+
mockAuthMiddleware('GENV123'),
267+
limiter,
268+
(req: Request, res: Response) => res.status(201).json({ success: true })
269+
);
270+
271+
await request(app).post('/streams').set('Content-Type', 'application/json');
272+
await request(app).post('/streams').set('Content-Type', 'application/json');
273+
274+
const res = await request(app)
275+
.post('/streams')
276+
.set('Content-Type', 'application/json');
277+
expect(res.status).toBe(429);
278+
expect(res.headers['ratelimit-limit']).toBe('2');
279+
});
280+
281+
it('should default to 10 requests when STREAM_CREATE_RATE_LIMIT is not set', async () => {
262282
delete process.env.STREAM_CREATE_RATE_LIMIT;
283+
vi.resetModules();
284+
const { createStreamRateLimiter: createLimiter } = await import(
285+
'../src/middleware/stream-rate-limiter.middleware.js'
286+
);
287+
const limiter = createLimiter({ windowMs: 10000 });
263288

264-
// The limiter should be created with max: 10 by default
265-
createStreamRateLimiter({ windowMs: 10000 });
289+
app.post(
290+
'/streams',
291+
mockAuthMiddleware('GENV456'),
292+
limiter,
293+
(req: Request, res: Response) => res.status(201).json({ success: true })
294+
);
266295

267-
// Clean up
268-
if (originalEnv) {
269-
process.env.STREAM_CREATE_RATE_LIMIT = originalEnv;
270-
}
296+
const res = await request(app)
297+
.post('/streams')
298+
.set('Content-Type', 'application/json');
299+
expect(res.headers['ratelimit-limit']).toBe('10');
300+
});
301+
302+
it('should apply STREAM_CREATE_RATE_LIMIT to streamCreationRateLimiter export', async () => {
303+
process.env.STREAM_CREATE_RATE_LIMIT = '2';
304+
vi.resetModules();
305+
const { streamCreationRateLimiter } = await import(
306+
'../src/middleware/stream-rate-limiter.middleware.js'
307+
);
308+
309+
app.post(
310+
'/streams',
311+
mockAuthMiddleware('GEXPORT123'),
312+
streamCreationRateLimiter,
313+
(req: Request, res: Response) => res.status(201).json({ success: true })
314+
);
315+
316+
await request(app).post('/streams').set('Content-Type', 'application/json');
317+
await request(app).post('/streams').set('Content-Type', 'application/json');
318+
319+
const res = await request(app)
320+
.post('/streams')
321+
.set('Content-Type', 'application/json');
322+
expect(res.status).toBe(429);
323+
expect(res.headers['ratelimit-limit']).toBe('2');
271324
});
272325
});
273326

0 commit comments

Comments
 (0)