Skip to content

Commit 1388fc7

Browse files
authored
Merge pull request #37 from anupamme/fix-repo-dxclusterapi-apiv2-missing-auth-ratelimit
fix: apply existing rate limiter to /bands, /sources, /heatmap, /info
2 parents d03bfa8 + 4089a43 commit 1388fc7

3 files changed

Lines changed: 66 additions & 5 deletions

File tree

‎modules/apiv2/index.js‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ class APIv2 {
431431
* GET /api/v2/bands
432432
* Get list of active bands with spot counts
433433
*/
434-
router.get('/bands', (req, res) => {
434+
router.get('/bands', rateLimiter || ((req, res, next) => next()), (req, res) => {
435435
try {
436436
const spots = this.getSpotsData();
437437
const bandStats = {};
@@ -466,7 +466,7 @@ class APIv2 {
466466
* GET /api/v2/sources
467467
* Get list of active sources with spot counts
468468
*/
469-
router.get('/sources', (req, res) => {
469+
router.get('/sources', rateLimiter || ((req, res, next) => next()), (req, res) => {
470470
try {
471471
const spots = this.getSpotsData();
472472
const sourceStats = {};
@@ -518,7 +518,7 @@ class APIv2 {
518518
* }
519519
* }
520520
*/
521-
router.get('/heatmap', (req, res) => {
521+
router.get('/heatmap', rateLimiter || ((req, res, next) => next()), (req, res) => {
522522
try {
523523
let heatmapData = this.generateHeatmap();
524524

@@ -560,7 +560,7 @@ class APIv2 {
560560
* GET /api/v2/info
561561
* Get API information and capabilities
562562
*/
563-
router.get('/info', (req, res) => {
563+
router.get('/info', rateLimiter || ((req, res, next) => next()), (req, res) => {
564564
res.json(this.formatResponse({
565565
success: true,
566566
data: {

‎package.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
"start": "node index.js",
2525
"start:docker": "MODE=docker node index.js",
2626
"start:native": "MODE=native node index.js",
27-
"dev": "node index.js"
27+
"dev": "node index.js",
28+
"test": "node --test"
2829
},
2930
"dependencies": {
3031
"axios": "^1.13.2",

‎test/apiv2.rateLimit.test.js‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
const test = require('node:test');
2+
const assert = require('node:assert/strict');
3+
const express = require('express');
4+
const APIv2 = require('../modules/apiv2/index.js');
5+
6+
const FIXTURE_SPOTS = [
7+
{ spotted: 'W1AW', spotter: 'K1ABC', frequency: 14074, band: '20m', source: 'cluster' }
8+
];
9+
10+
function startApp(config, rateLimiter) {
11+
const apiv2 = new APIv2(config);
12+
const app = express();
13+
app.use('/api/v2', apiv2.createRouter(rateLimiter));
14+
return new Promise((resolve) => {
15+
const server = app.listen(0, () => resolve(server));
16+
});
17+
}
18+
19+
function stopApp(server) {
20+
return new Promise((resolve) => server.close(resolve));
21+
}
22+
23+
const ROUTES = ['/bands', '/sources', '/heatmap', '/info'];
24+
25+
test('rate limiter applies to /bands, /sources, /heatmap, /info', async (t) => {
26+
const rateLimiter = (req, res, next) => {
27+
res.status(429).json({ error: 'rate limited' });
28+
};
29+
const server = await startApp({ getSpotsData: () => FIXTURE_SPOTS }, rateLimiter);
30+
const base = `http://localhost:${server.address().port}/api/v2`;
31+
32+
t.after(() => stopApp(server));
33+
34+
for (const route of ROUTES) {
35+
const res = await fetch(`${base}${route}`);
36+
assert.equal(res.status, 429, `${route} should be rate limited`);
37+
}
38+
39+
// Regression guard: existing rate-limited route still behaves as before
40+
const spotsRes = await fetch(`${base}/spots`);
41+
assert.equal(spotsRes.status, 429, '/spots should still be rate limited');
42+
});
43+
44+
test('global auth middleware protects /bands, /sources, /heatmap, /info when apiKey is configured', async (t) => {
45+
const server = await startApp({ getSpotsData: () => FIXTURE_SPOTS, apiKey: 'secret' });
46+
const base = `http://localhost:${server.address().port}/api/v2`;
47+
48+
t.after(() => stopApp(server));
49+
50+
for (const route of ROUTES) {
51+
const noKey = await fetch(`${base}${route}`);
52+
assert.equal(noKey.status, 401, `${route} without X-API-Key should be 401`);
53+
54+
const wrongKey = await fetch(`${base}${route}`, { headers: { 'x-api-key': 'wrong' } });
55+
assert.equal(wrongKey.status, 403, `${route} with wrong X-API-Key should be 403`);
56+
57+
const correctKey = await fetch(`${base}${route}`, { headers: { 'x-api-key': 'secret' } });
58+
assert.equal(correctKey.status, 200, `${route} with correct X-API-Key should be 200`);
59+
}
60+
});

0 commit comments

Comments
 (0)