Skip to content

Commit 2193f9f

Browse files
committed
Allow passing a Router to addExpressMiddleware
Also log warning when middleware is called multiple times during a request.
1 parent 3f1c3a7 commit 2193f9f

File tree

5 files changed

+84
-11
lines changed

5 files changed

+84
-11
lines changed

docs/express.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,19 @@ Zen.addExpressMiddleware(app);
5959
app.get(...);
6060
```
6161

62+
You can also pass a `Router` instance to `Zen.addExpressMiddleware`:
63+
64+
```js
65+
const router = express.Router();
66+
67+
// Note: The middleware should run once per request
68+
Zen.addExpressMiddleware(router);
69+
70+
router.get(...);
71+
72+
app.use(router);
73+
```
74+
6275
## Debug mode
6376

6477
If you need to debug the firewall, you can run your express app with the environment variable `AIKIDO_DEBUG` set to `true`:

library/middleware/express.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/** TS_EXPECT_TYPES_ERROR_OPTIONAL_DEPENDENCY **/
2-
import type { Express } from "express";
2+
import type { Express, Router } from "express";
33
import { shouldBlockRequest } from "./shouldBlockRequest";
44
import { escapeHTML } from "../helpers/escapeHTML";
55

@@ -8,7 +8,7 @@ import { escapeHTML } from "../helpers/escapeHTML";
88
* Attacks will still be blocked by Zen if you do not call this function.
99
* Execute this function as early as possible in your Express app, but after the middleware that sets the user.
1010
*/
11-
export function addExpressMiddleware(app: Express) {
11+
export function addExpressMiddleware(app: Express | Router) {
1212
app.use((req, res, next) => {
1313
const result = shouldBlockRequest();
1414

library/middleware/shouldBlockRequest.test.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,23 @@ const sampleContext: Context = {
1919
route: "/posts/:id",
2020
};
2121

22-
t.test("without context", async (t) => {
23-
const logs: string[] = [];
24-
wrap(console, "warn", function warn() {
25-
return function warn(message: string) {
26-
logs.push(message);
27-
};
28-
});
22+
let logs: string[] = [];
23+
wrap(console, "warn", function warn() {
24+
return function warn(message: string) {
25+
logs.push(message);
26+
};
27+
});
2928

29+
t.beforeEach(() => {
30+
logs = [];
31+
});
32+
33+
t.test("without context", async (t) => {
3034
const result = shouldBlockRequest();
3135
shouldBlockRequest();
3236
t.same(result, { block: false });
3337
t.same(logs, [
34-
"shouldBlockRequest() was called without a context. The request will not be blocked. Make sure to call shouldBlockRequest() within an HTTP request. If you're using serverless functions, make sure to use the handler wrapper provided by Zen. Also ensure you import Zen at the top of your main app file (before any other imports).",
38+
"Zen.shouldBlockRequest() was called without a context. The request will not be blocked. Make sure to call shouldBlockRequest() within an HTTP request. If you're using serverless functions, make sure to use the handler wrapper provided by Zen. Also ensure you import Zen at the top of your main app file (before any other imports).",
3539
]);
3640
});
3741

@@ -47,3 +51,14 @@ t.test("with agent", async (t) => {
4751
t.same(shouldBlockRequest(), { block: false });
4852
});
4953
});
54+
55+
t.test("multiple calls", async (t) => {
56+
createTestAgent();
57+
runWithContext(sampleContext, () => {
58+
shouldBlockRequest();
59+
shouldBlockRequest();
60+
});
61+
t.same(logs, [
62+
"Zen.shouldBlockRequest() was called multiple times. The middleware should be executed once per request.",
63+
]);
64+
});

library/middleware/shouldBlockRequest.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ export function shouldBlockRequest(): Result {
2121
return { block: false };
2222
}
2323

24+
if (context.executedMiddleware) {
25+
logWarningAlreadyExecutedMiddleware();
26+
return { block: false };
27+
}
28+
2429
updateContext(context, "executedMiddleware", true);
2530
agent.onMiddlewareExecuted();
2631

@@ -50,8 +55,23 @@ function logWarningShouldBlockRequestCalledWithoutContext() {
5055

5156
// eslint-disable-next-line no-console
5257
console.warn(
53-
"shouldBlockRequest() was called without a context. The request will not be blocked. Make sure to call shouldBlockRequest() within an HTTP request. If you're using serverless functions, make sure to use the handler wrapper provided by Zen. Also ensure you import Zen at the top of your main app file (before any other imports)."
58+
"Zen.shouldBlockRequest() was called without a context. The request will not be blocked. Make sure to call shouldBlockRequest() within an HTTP request. If you're using serverless functions, make sure to use the handler wrapper provided by Zen. Also ensure you import Zen at the top of your main app file (before any other imports)."
5459
);
5560

5661
loggedWarningShouldBlockRequestCalledWithoutContext = true;
5762
}
63+
64+
let loggedWarningAlreadyExecutedMiddleware = false;
65+
66+
function logWarningAlreadyExecutedMiddleware() {
67+
if (loggedWarningAlreadyExecutedMiddleware) {
68+
return;
69+
}
70+
71+
// eslint-disable-next-line no-console
72+
console.warn(
73+
"Zen.shouldBlockRequest() was called multiple times. The middleware should be executed once per request."
74+
);
75+
76+
loggedWarningAlreadyExecutedMiddleware = true;
77+
}

library/sources/Express.tests.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,4 +718,29 @@ export function createExpressTests(expressPackageName: string) {
718718
);
719719
}
720720
);
721+
722+
t.test("it supports adding middleware to a Router instance", async (t) => {
723+
const app = express();
724+
const router = express.Router();
725+
726+
// Add user middleware to set user data
727+
router.use((req, res, next) => {
728+
setUser({ id: "567" });
729+
next();
730+
});
731+
732+
// Add Zen middleware to router instead of app
733+
addExpressMiddleware(router);
734+
735+
router.get("/router-block-user", (req, res) => {
736+
res.send({ willNotBeSent: true });
737+
});
738+
739+
app.use(router);
740+
741+
// Test blocked user is properly handled by the router middleware
742+
const blockedResponse = await request(app).get("/router-block-user");
743+
t.same(blockedResponse.statusCode, 403);
744+
t.same(blockedResponse.text, "You are blocked by Zen.");
745+
});
721746
}

0 commit comments

Comments
 (0)