diff --git a/coverage/clover.xml b/coverage/clover.xml index 5051ef1..b307294 100644 --- a/coverage/clover.xml +++ b/coverage/clover.xml @@ -1,4 +1,11 @@ + + + + + + + @@ -10,19 +17,35 @@ - - + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + @@ -43,6 +66,7 @@ + @@ -77,6 +101,7 @@ + diff --git a/coverage/coverage-final.json b/coverage/coverage-final.json index c974ec3..a5c3226 100644 --- a/coverage/coverage-final.json +++ b/coverage/coverage-final.json @@ -1,3 +1,7 @@ +{"C:\\Users\\USER\\Documents\\api-fox\\src\\app.js": {"path":"C:\\Users\\USER\\Documents\\api-fox\\src\\app.js","statementMap":{"0":{"start":{"line":1,"column":16},"end":{"line":1,"column":34}},"1":{"start":{"line":2,"column":13},"end":{"line":2,"column":28}},"2":{"start":{"line":3,"column":15},"end":{"line":3,"column":32}},"3":{"start":{"line":4,"column":15},"end":{"line":4,"column":32}},"4":{"start":{"line":5,"column":18},"end":{"line":5,"column":47}},"5":{"start":{"line":7,"column":12},"end":{"line":7,"column":21}},"6":{"start":{"line":10,"column":22},"end":{"line":23,"column":2}},"7":{"start":{"line":16,"column":23},"end":{"line":16,"column":64}},"8":{"start":{"line":17,"column":4},"end":{"line":17,"column":39}},"9":{"start":{"line":18,"column":4},"end":{"line":21,"column":7}},"10":{"start":{"line":26,"column":20},"end":{"line":39,"column":2}},"11":{"start":{"line":32,"column":23},"end":{"line":32,"column":64}},"12":{"start":{"line":33,"column":4},"end":{"line":33,"column":39}},"13":{"start":{"line":34,"column":4},"end":{"line":37,"column":7}},"14":{"start":{"line":42,"column":0},"end":{"line":42,"column":23}},"15":{"start":{"line":44,"column":23},"end":{"line":44,"column":97}},"16":{"start":{"line":44,"column":77},"end":{"line":44,"column":90}},"17":{"start":{"line":46,"column":0},"end":{"line":46,"column":24}},"18":{"start":{"line":47,"column":0},"end":{"line":51,"column":4}},"19":{"start":{"line":52,"column":0},"end":{"line":52,"column":18}},"20":{"start":{"line":53,"column":0},"end":{"line":53,"column":23}},"21":{"start":{"line":55,"column":0},"end":{"line":57,"column":3}},"22":{"start":{"line":56,"column":2},"end":{"line":56,"column":41}},"23":{"start":{"line":60,"column":0},"end":{"line":60,"column":34}},"24":{"start":{"line":63,"column":0},"end":{"line":65,"column":3}},"25":{"start":{"line":64,"column":2},"end":{"line":64,"column":54}},"26":{"start":{"line":67,"column":0},"end":{"line":69,"column":3}},"27":{"start":{"line":68,"column":2},"end":{"line":68,"column":57}},"28":{"start":{"line":71,"column":0},"end":{"line":71,"column":21}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":15,"column":11},"end":{"line":15,"column":12}},"loc":{"start":{"line":15,"column":25},"end":{"line":22,"column":3}},"line":15},"1":{"name":"(anonymous_1)","decl":{"start":{"line":31,"column":11},"end":{"line":31,"column":12}},"loc":{"start":{"line":31,"column":25},"end":{"line":38,"column":3}},"line":31},"2":{"name":"(anonymous_2)","decl":{"start":{"line":44,"column":67},"end":{"line":44,"column":68}},"loc":{"start":{"line":44,"column":77},"end":{"line":44,"column":90}},"line":44},"3":{"name":"(anonymous_3)","decl":{"start":{"line":55,"column":23},"end":{"line":55,"column":24}},"loc":{"start":{"line":55,"column":37},"end":{"line":57,"column":1}},"line":55},"4":{"name":"(anonymous_4)","decl":{"start":{"line":63,"column":28},"end":{"line":63,"column":29}},"loc":{"start":{"line":63,"column":42},"end":{"line":65,"column":1}},"line":63},"5":{"name":"(anonymous_5)","decl":{"start":{"line":67,"column":31},"end":{"line":67,"column":32}},"loc":{"start":{"line":67,"column":45},"end":{"line":69,"column":1}},"line":67}},"branchMap":{"0":{"loc":{"start":{"line":44,"column":23},"end":{"line":44,"column":97}},"type":"binary-expr","locations":[{"start":{"line":44,"column":23},"end":{"line":44,"column":91}},{"start":{"line":44,"column":95},"end":{"line":44,"column":97}}],"line":44}},"s":{"0":1,"1":1,"2":1,"3":1,"4":1,"5":1,"6":1,"7":0,"8":0,"9":0,"10":1,"11":0,"12":0,"13":0,"14":1,"15":1,"16":0,"17":1,"18":1,"19":1,"20":1,"21":1,"22":1,"23":1,"24":1,"25":0,"26":1,"27":0,"28":1},"f":{"0":0,"1":0,"2":0,"3":1,"4":0,"5":0},"b":{"0":[1,1]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"4cdcb50b1ceac3c71a96130cc657c8e6200d4418"} +,"C:\\Users\\USER\\Documents\\api-fox\\src\\config\\db.js": {"path":"C:\\Users\\USER\\Documents\\api-fox\\src\\config\\db.js","statementMap":{"0":{"start":{"line":1,"column":17},"end":{"line":1,"column":36}},"1":{"start":{"line":3,"column":18},"end":{"line":13,"column":1}},"2":{"start":{"line":4,"column":2},"end":{"line":12,"column":3}},"3":{"start":{"line":5,"column":17},"end":{"line":5,"column":62}},"4":{"start":{"line":7,"column":4},"end":{"line":7,"column":63}},"5":{"start":{"line":9,"column":4},"end":{"line":9,"column":64}},"6":{"start":{"line":11,"column":4},"end":{"line":11,"column":20}},"7":{"start":{"line":15,"column":0},"end":{"line":17,"column":3}},"8":{"start":{"line":16,"column":2},"end":{"line":16,"column":68}},"9":{"start":{"line":19,"column":0},"end":{"line":21,"column":3}},"10":{"start":{"line":20,"column":2},"end":{"line":20,"column":37}},"11":{"start":{"line":23,"column":0},"end":{"line":25,"column":3}},"12":{"start":{"line":24,"column":2},"end":{"line":24,"column":39}},"13":{"start":{"line":27,"column":0},"end":{"line":27,"column":27}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":3,"column":18},"end":{"line":3,"column":19}},"loc":{"start":{"line":3,"column":30},"end":{"line":13,"column":1}},"line":3},"1":{"name":"(anonymous_1)","decl":{"start":{"line":15,"column":39},"end":{"line":15,"column":40}},"loc":{"start":{"line":15,"column":45},"end":{"line":17,"column":1}},"line":15},"2":{"name":"(anonymous_2)","decl":{"start":{"line":19,"column":38},"end":{"line":19,"column":39}},"loc":{"start":{"line":19,"column":44},"end":{"line":21,"column":1}},"line":19},"3":{"name":"(anonymous_3)","decl":{"start":{"line":23,"column":32},"end":{"line":23,"column":33}},"loc":{"start":{"line":23,"column":41},"end":{"line":25,"column":1}},"line":23}},"branchMap":{},"s":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0},"f":{"0":0,"1":0,"2":0,"3":0},"b":{}} +,"C:\\Users\\USER\\Documents\\api-fox\\src\\middlewares\\validate.js": {"path":"C:\\Users\\USER\\Documents\\api-fox\\src\\middlewares\\validate.js","statementMap":{"0":{"start":{"line":1,"column":17},"end":{"line":20,"column":1}},"1":{"start":{"line":2,"column":2},"end":{"line":19,"column":4}},"2":{"start":{"line":3,"column":22},"end":{"line":3,"column":70}},"3":{"start":{"line":5,"column":4},"end":{"line":16,"column":5}},"4":{"start":{"line":6,"column":21},"end":{"line":9,"column":9}},"5":{"start":{"line":6,"column":52},"end":{"line":9,"column":7}},"6":{"start":{"line":11,"column":6},"end":{"line":15,"column":9}},"7":{"start":{"line":18,"column":4},"end":{"line":18,"column":11}},"8":{"start":{"line":22,"column":0},"end":{"line":22,"column":26}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":1,"column":17},"end":{"line":1,"column":18}},"loc":{"start":{"line":1,"column":29},"end":{"line":20,"column":1}},"line":1},"1":{"name":"(anonymous_1)","decl":{"start":{"line":2,"column":9},"end":{"line":2,"column":10}},"loc":{"start":{"line":2,"column":29},"end":{"line":19,"column":3}},"line":2},"2":{"name":"(anonymous_2)","decl":{"start":{"line":6,"column":39},"end":{"line":6,"column":40}},"loc":{"start":{"line":6,"column":52},"end":{"line":9,"column":7}},"line":6}},"branchMap":{"0":{"loc":{"start":{"line":5,"column":4},"end":{"line":16,"column":5}},"type":"if","locations":[{"start":{"line":5,"column":4},"end":{"line":16,"column":5}},{"start":{},"end":{}}],"line":5}},"s":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0},"f":{"0":0,"1":0,"2":0},"b":{"0":[0,0]}} +,"C:\\Users\\USER\\Documents\\api-fox\\src\\validators\\auth.validators.js": {"path":"C:\\Users\\USER\\Documents\\api-fox\\src\\validators\\auth.validators.js","statementMap":{"0":{"start":{"line":1,"column":12},"end":{"line":1,"column":26}},"1":{"start":{"line":3,"column":23},"end":{"line":17,"column":2}},"2":{"start":{"line":19,"column":20},"end":{"line":27,"column":2}},"3":{"start":{"line":29,"column":0},"end":{"line":32,"column":2}}},"fnMap":{},"branchMap":{},"s":{"0":0,"1":0,"2":0,"3":0},"f":{},"b":{}} {"/Users/carita/Desktop/grant-fox/api-fox/src/app.js": {"path":"/Users/carita/Desktop/grant-fox/api-fox/src/app.js","statementMap":{"0":{"start":{"line":1,"column":16},"end":{"line":1,"column":34}},"1":{"start":{"line":2,"column":13},"end":{"line":2,"column":28}},"2":{"start":{"line":3,"column":15},"end":{"line":3,"column":32}},"3":{"start":{"line":4,"column":15},"end":{"line":4,"column":32}},"4":{"start":{"line":6,"column":12},"end":{"line":6,"column":21}},"5":{"start":{"line":8,"column":23},"end":{"line":8,"column":97}},"6":{"start":{"line":8,"column":77},"end":{"line":8,"column":90}},"7":{"start":{"line":10,"column":0},"end":{"line":10,"column":24}},"8":{"start":{"line":11,"column":0},"end":{"line":15,"column":4}},"9":{"start":{"line":16,"column":0},"end":{"line":16,"column":18}},"10":{"start":{"line":17,"column":0},"end":{"line":17,"column":23}},"11":{"start":{"line":19,"column":0},"end":{"line":21,"column":3}},"12":{"start":{"line":20,"column":2},"end":{"line":20,"column":41}},"13":{"start":{"line":23,"column":0},"end":{"line":23,"column":21}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":8,"column":67},"end":{"line":8,"column":68}},"loc":{"start":{"line":8,"column":77},"end":{"line":8,"column":90}},"line":8},"1":{"name":"(anonymous_1)","decl":{"start":{"line":19,"column":23},"end":{"line":19,"column":24}},"loc":{"start":{"line":19,"column":37},"end":{"line":21,"column":1}},"line":19}},"branchMap":{"0":{"loc":{"start":{"line":8,"column":23},"end":{"line":8,"column":97}},"type":"binary-expr","locations":[{"start":{"line":8,"column":23},"end":{"line":8,"column":91}},{"start":{"line":8,"column":95},"end":{"line":8,"column":97}}],"line":8}},"s":{"0":1,"1":1,"2":1,"3":1,"4":1,"5":1,"6":0,"7":1,"8":1,"9":1,"10":1,"11":1,"12":1,"13":1},"f":{"0":0,"1":1},"b":{"0":[1,1]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"da8d951461c950f08cadcd54c6f5fc17bc208e0c"} ,"/Users/carita/Desktop/grant-fox/api-fox/src/config/db.js": {"path":"/Users/carita/Desktop/grant-fox/api-fox/src/config/db.js","statementMap":{"0":{"start":{"line":1,"column":17},"end":{"line":1,"column":36}},"1":{"start":{"line":3,"column":18},"end":{"line":13,"column":1}},"2":{"start":{"line":4,"column":2},"end":{"line":12,"column":3}},"3":{"start":{"line":5,"column":17},"end":{"line":5,"column":62}},"4":{"start":{"line":7,"column":4},"end":{"line":7,"column":63}},"5":{"start":{"line":9,"column":4},"end":{"line":9,"column":64}},"6":{"start":{"line":11,"column":4},"end":{"line":11,"column":20}},"7":{"start":{"line":15,"column":0},"end":{"line":17,"column":3}},"8":{"start":{"line":16,"column":2},"end":{"line":16,"column":68}},"9":{"start":{"line":19,"column":0},"end":{"line":21,"column":3}},"10":{"start":{"line":20,"column":2},"end":{"line":20,"column":37}},"11":{"start":{"line":23,"column":0},"end":{"line":25,"column":3}},"12":{"start":{"line":24,"column":2},"end":{"line":24,"column":39}},"13":{"start":{"line":27,"column":0},"end":{"line":27,"column":27}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":3,"column":18},"end":{"line":3,"column":19}},"loc":{"start":{"line":3,"column":30},"end":{"line":13,"column":1}},"line":3},"1":{"name":"(anonymous_1)","decl":{"start":{"line":15,"column":39},"end":{"line":15,"column":40}},"loc":{"start":{"line":15,"column":45},"end":{"line":17,"column":1}},"line":15},"2":{"name":"(anonymous_2)","decl":{"start":{"line":19,"column":38},"end":{"line":19,"column":39}},"loc":{"start":{"line":19,"column":44},"end":{"line":21,"column":1}},"line":19},"3":{"name":"(anonymous_3)","decl":{"start":{"line":23,"column":32},"end":{"line":23,"column":33}},"loc":{"start":{"line":23,"column":41},"end":{"line":25,"column":1}},"line":23}},"branchMap":{},"s":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0},"f":{"0":0,"1":0,"2":0,"3":0},"b":{}} ,"/Users/carita/Desktop/grant-fox/api-fox/src/middlewares/validate.js": {"path":"/Users/carita/Desktop/grant-fox/api-fox/src/middlewares/validate.js","statementMap":{"0":{"start":{"line":1,"column":17},"end":{"line":20,"column":1}},"1":{"start":{"line":2,"column":2},"end":{"line":19,"column":4}},"2":{"start":{"line":3,"column":22},"end":{"line":3,"column":70}},"3":{"start":{"line":5,"column":4},"end":{"line":16,"column":5}},"4":{"start":{"line":6,"column":21},"end":{"line":9,"column":9}},"5":{"start":{"line":6,"column":52},"end":{"line":9,"column":7}},"6":{"start":{"line":11,"column":6},"end":{"line":15,"column":9}},"7":{"start":{"line":18,"column":4},"end":{"line":18,"column":11}},"8":{"start":{"line":22,"column":0},"end":{"line":22,"column":26}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":1,"column":17},"end":{"line":1,"column":18}},"loc":{"start":{"line":1,"column":29},"end":{"line":20,"column":1}},"line":1},"1":{"name":"(anonymous_1)","decl":{"start":{"line":2,"column":9},"end":{"line":2,"column":10}},"loc":{"start":{"line":2,"column":29},"end":{"line":19,"column":3}},"line":2},"2":{"name":"(anonymous_2)","decl":{"start":{"line":6,"column":39},"end":{"line":6,"column":40}},"loc":{"start":{"line":6,"column":52},"end":{"line":9,"column":7}},"line":6}},"branchMap":{"0":{"loc":{"start":{"line":5,"column":4},"end":{"line":16,"column":5}},"type":"if","locations":[{"start":{"line":5,"column":4},"end":{"line":16,"column":5}},{"start":{},"end":{}}],"line":5}},"s":{"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0},"f":{"0":0,"1":0,"2":0},"b":{"0":[0,0]}} diff --git a/coverage/lcov-report/index.html b/coverage/lcov-report/index.html index 3a2226e..96a30be 100644 --- a/coverage/lcov-report/index.html +++ b/coverage/lcov-report/index.html @@ -23,6 +23,9 @@

All files

+ 35.71% + Statements + 20/56 23.21% Statements 13/56 @@ -37,6 +40,9 @@

All files

+ 7.69% + Functions + 1/13 9.09% Functions 1/11 @@ -44,6 +50,9 @@

All files

+ 37.03% + Lines + 20/54 24.52% Lines 13/53 @@ -80,17 +89,17 @@

All files

src - -
+ +
- 92.85% - 13/14 + 68.96% + 20/29 100% 2/2 - 50% - 1/2 - 100% - 13/13 + 16.66% + 1/6 + 71.42% + 20/28 @@ -161,6 +170,7 @@

All files

diff --git a/coverage/lcov-report/src/app.js.html b/coverage/lcov-report/src/app.js.html index 2f17d78..dc5bf6e 100644 --- a/coverage/lcov-report/src/app.js.html +++ b/coverage/lcov-report/src/app.js.html @@ -23,9 +23,9 @@

All files / src app.js<
- 92.85% + 68.96% Statements - 13/14 + 20/29
@@ -37,16 +37,16 @@

All files / src app.js<
- 50% + 16.66% Functions - 1/2 + 1/6
- 100% + 71.42% Lines - 13/13 + 20/28
@@ -85,13 +85,97 @@

All files / src app.js< 20 21 22 -231x +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +711x +1x 1x 1x 1x   1x   +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  1x   1x @@ -107,13 +191,61 @@

All files / src app.js< 1x     +  +1x +  +  +1x +  +  +  +1x +  +  +  1x
const express = require('express');
 const cors = require('cors');
 const helmet = require('helmet');
 const morgan = require('morgan');
+const rateLimit = require('express-rate-limit');
  
 const app = express();
  
+// Global rate limiter: 100 requests per 15 minutes per IP
+const globalLimiter = rateLimit({
+  windowMs: 15 * 60 * 1000, // 15 minutes
+  max: 100, // limit each IP to 100 requests per windowMs
+  standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
+  legacyHeaders: false, // Disable the `X-RateLimit-*` headers
+  handler: (req, res) => {
+    const retryAfter = Math.ceil(req.rateLimit.resetTime / 1000);
+    res.set('Retry-After', retryAfter);
+    res.status(429).json({
+      message: 'Too many requests, please try again later.',
+      retryAfter: retryAfter
+    });
+  }
+});
+ 
+// Stricter auth rate limiter: 10 requests per 15 minutes
+const authLimiter = rateLimit({
+  windowMs: 15 * 60 * 1000, // 15 minutes
+  max: 10, // limit each IP to 10 requests per windowMs
+  standardHeaders: true,
+  legacyHeaders: false,
+  handler: (req, res) => {
+    const retryAfter = Math.ceil(req.rateLimit.resetTime / 1000);
+    res.set('Retry-After', retryAfter);
+    res.status(429).json({
+      message: 'Too many authentication attempts, please try again later.',
+      retryAfter: retryAfter
+    });
+  }
+});
+ 
+// Apply global rate limiter to all requests
+app.use(globalLimiter);
+ 
 const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',').map(origin => origin.trim()) || [];
  
 app.use(express.json());
@@ -129,6 +261,18 @@ 

All files / src app.js< res.status(200).json({ status: 'ok' }); });   +// Apply stricter rate limiter to auth routes +app.use('/api/auth', authLimiter); +  +// Example auth routes (for testing rate limiting) +app.post('/api/auth/login', (req, res) => { + res.status(200).json({ message: 'Login endpoint' }); +}); +  +app.post('/api/auth/register', (req, res) => { + res.status(200).json({ message: 'Register endpoint' }); +}); +  module.exports = app;

@@ -136,6 +280,7 @@

All files / src app.js< diff --git a/coverage/lcov-report/src/config/db.js.html b/coverage/lcov-report/src/config/db.js.html index c498d9a..0bd58a5 100644 --- a/coverage/lcov-report/src/config/db.js.html +++ b/coverage/lcov-report/src/config/db.js.html @@ -148,6 +148,7 @@

All files / src/config Code coverage generated by istanbul + at 2026-03-02T20:31:35.363Z at 2026-03-02T08:56:44.708Z

diff --git a/coverage/lcov-report/src/config/index.html b/coverage/lcov-report/src/config/index.html index 342f3c3..ed90341 100644 --- a/coverage/lcov-report/src/config/index.html +++ b/coverage/lcov-report/src/config/index.html @@ -101,6 +101,7 @@

All files src/config

diff --git a/coverage/lcov-report/src/index.html b/coverage/lcov-report/src/index.html index 5b694a2..fdf91ca 100644 --- a/coverage/lcov-report/src/index.html +++ b/coverage/lcov-report/src/index.html @@ -23,9 +23,9 @@

All files src

- 92.85% + 68.96% Statements - 13/14 + 20/29
@@ -37,16 +37,16 @@

All files src

- 50% + 16.66% Functions - 1/2 + 1/6
- 100% + 71.42% Lines - 13/13 + 20/28
@@ -80,17 +80,17 @@

All files src

app.js - -
+ +
- 92.85% - 13/14 + 68.96% + 20/29 100% 2/2 - 50% - 1/2 - 100% - 13/13 + 16.66% + 1/6 + 71.42% + 20/28 @@ -101,6 +101,7 @@

All files src

diff --git a/coverage/lcov-report/src/middlewares/index.html b/coverage/lcov-report/src/middlewares/index.html index c8d2ad4..155adcd 100644 --- a/coverage/lcov-report/src/middlewares/index.html +++ b/coverage/lcov-report/src/middlewares/index.html @@ -101,6 +101,7 @@

All files src/middlewares

diff --git a/coverage/lcov-report/src/middlewares/validate.js.html b/coverage/lcov-report/src/middlewares/validate.js.html index 89c8ee5..b218a25 100644 --- a/coverage/lcov-report/src/middlewares/validate.js.html +++ b/coverage/lcov-report/src/middlewares/validate.js.html @@ -136,6 +136,7 @@

All files / src/middlewa diff --git a/coverage/lcov-report/src/validators/auth.validators.js.html b/coverage/lcov-report/src/validators/auth.validators.js.html index 2338872..7f46038 100644 --- a/coverage/lcov-report/src/validators/auth.validators.js.html +++ b/coverage/lcov-report/src/validators/auth.validators.js.html @@ -166,6 +166,7 @@

All files / src/validato diff --git a/coverage/lcov-report/src/validators/index.html b/coverage/lcov-report/src/validators/index.html index 23287d4..f8717ab 100644 --- a/coverage/lcov-report/src/validators/index.html +++ b/coverage/lcov-report/src/validators/index.html @@ -101,6 +101,7 @@

All files src/validators

diff --git a/coverage/lcov.info b/coverage/lcov.info index 636c94b..949381a 100644 --- a/coverage/lcov.info +++ b/coverage/lcov.info @@ -1,28 +1,55 @@ TN: +SF:src\app.js +FN:15,(anonymous_0) +FN:31,(anonymous_1) +FN:44,(anonymous_2) +FN:55,(anonymous_3) +FN:63,(anonymous_4) +FN:67,(anonymous_5) +FNF:6 SF:src/app.js FN:8,(anonymous_0) FN:19,(anonymous_1) FNF:2 FNH:1 FNDA:0,(anonymous_0) -FNDA:1,(anonymous_1) +FNDA:0,(anonymous_1) +FNDA:0,(anonymous_2) +FNDA:1,(anonymous_3) +FNDA:0,(anonymous_4) +FNDA:0,(anonymous_5) DA:1,1 DA:2,1 DA:3,1 DA:4,1 -DA:6,1 -DA:8,1 +DA:5,1 +DA:7,1 DA:10,1 -DA:11,1 -DA:16,1 -DA:17,1 -DA:19,1 -DA:20,1 -DA:23,1 -LF:13 -LH:13 -BRDA:8,0,0,1 -BRDA:8,0,1,1 +DA:16,0 +DA:17,0 +DA:18,0 +DA:26,1 +DA:32,0 +DA:33,0 +DA:34,0 +DA:42,1 +DA:44,1 +DA:46,1 +DA:47,1 +DA:52,1 +DA:53,1 +DA:55,1 +DA:56,1 +DA:60,1 +DA:63,1 +DA:64,0 +DA:67,1 +DA:68,0 +DA:71,1 +LF:28 +LH:20 +BRDA:44,0,0,1 +BRDA:44,0,1,1 BRF:2 BRH:2 end_of_record diff --git a/package-lock.json b/package-lock.json index af646d6..2a419e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "cors": "^2.8.6", "dotenv": "^17.3.1", "express": "^5.2.1", + "express-rate-limit": "^8.2.1", "helmet": "^8.1.0", "joi": "^18.0.2", "jsonwebtoken": "^9.0.3", @@ -63,6 +64,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1971,6 +1973,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2311,6 +2314,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -3030,6 +3034,7 @@ "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3433,6 +3438,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -3471,6 +3477,24 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express-rate-limit": { + "version": "8.2.1", + "resolved": "https://registry.npmmirror.com/express-rate-limit/-/express-rate-limit-8.2.1.tgz", + "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", + "license": "MIT", + "dependencies": { + "ip-address": "10.0.1" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4092,6 +4116,15 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmmirror.com/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", diff --git a/package.json b/package.json index 2bcb386..2ad7f89 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,58 @@ "cors": "^2.8.6", "dotenv": "^17.3.1", "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "helmet": "^8.1.0", + "joi": "^18.0.2", + "jsonwebtoken": "^9.0.3", + "mongoose": "^9.2.3", + "morgan": "^1.10.1", + "nodemailer": "^8.0.1" + }, + "devDependencies": { + "@eslint/js": "^9.39.3", + "eslint": "^9.39.3", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-node": "^11.1.0", + "globals": "^17.4.0", + "jest": "^30.2.0", + "nodemon": "^3.1.14", + "prettier": "^3.8.1", + "supertest": "^7.2.2" + } +}{ + "name": "api-fox", + "version": "1.0.0", + "description": "StellarAid Backend is the server-side API powering the StellarAid crowdfunding platform — a blockchain‑enabled system built on the Stellar network to support transparent, secure, and efficient fundraising for social impact initiatives.", + "main": "index.js", + "scripts": { + "test": "jest", + "test:watch": "jest --watch", + "lint": "eslint src/", + "lint:fix": "eslint src/ --fix", + "format": "prettier --write \"src/**/*.{js,json,md}\"", + "build": "echo \"No build required for Node.js project\"", + "dev": "nodemon src/server.js", + "start": "node src/server.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/SudiptaPaul-31/api-fox.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "bugs": { + "url": "https://github.com/SudiptaPaul-31/api-fox/issues" + }, + "homepage": "https://github.com/SudiptaPaul-31/api-fox#readme", + "dependencies": { + "bcryptjs": "^3.0.3", + "cors": "^2.8.6", + "dotenv": "^17.3.1", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", "helmet": "^8.1.0", "joi": "^18.0.2", "jsonwebtoken": "^9.0.3", diff --git a/src/app.js b/src/app.js index 79b843c..4790ee7 100644 --- a/src/app.js +++ b/src/app.js @@ -2,6 +2,46 @@ const express = require('express'); const cors = require('cors'); const helmet = require('helmet'); const morgan = require('morgan'); +const rateLimit = require('express-rate-limit'); + +const app = express(); + +// Global rate limiter: 100 requests per 15 minutes per IP +const globalLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 100, // limit each IP to 100 requests per windowMs + standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers + legacyHeaders: false, // Disable the `X-RateLimit-*` headers + handler: (req, res) => { + const retryAfter = Math.ceil(req.rateLimit.resetTime / 1000); + res.set('Retry-After', retryAfter); + res.status(429).json({ + message: 'Too many requests, please try again later.', + retryAfter: retryAfter + }); + } +}); + +// Stricter auth rate limiter: 10 requests per 15 minutes +const authLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 10, // limit each IP to 10 requests per windowMs + standardHeaders: true, + legacyHeaders: false, + handler: (req, res) => { + const retryAfter = Math.ceil(req.rateLimit.resetTime / 1000); + res.set('Retry-After', retryAfter); + res.status(429).json({ + message: 'Too many authentication attempts, please try again later.', + retryAfter: retryAfter + }); + } +}); + +// Apply global rate limiter to all requests +app.use(globalLimiter); + +const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',').map(origin => origin.trim()) || []; const { sendSuccess } = require('./utils/response'); const { getLoggerStream } = require('./utils/logger'); const authRoutes = require('./routes/auth.routes'); @@ -30,6 +70,19 @@ app.get('/api/health', (req, res) => { sendSuccess(res, { status: 'ok' }, 200, 'Server is healthy'); }); +// Apply stricter rate limiter to auth routes +app.use('/api/auth', authLimiter); + +// Example auth routes (for testing rate limiting) +app.post('/api/auth/login', (req, res) => { + res.status(200).json({ message: 'Login endpoint' }); +}); + +app.post('/api/auth/register', (req, res) => { + res.status(200).json({ message: 'Register endpoint' }); +}); + +module.exports = app; // Auth routes app.use('/api/auth', authRoutes);