Skip to content

Commit 6f815cc

Browse files
committed
fix: require admin role for metrics
1 parent aeaad08 commit 6f815cc

3 files changed

Lines changed: 79 additions & 1 deletion

File tree

apps/api/src/middleware/auth.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,13 @@ export function authMiddleware(req, res, next) {
1414
return fail(res, "Invalid token", 401);
1515
}
1616
}
17+
18+
export function requireRole(role) {
19+
return function roleMiddleware(req, res, next) {
20+
if (req.user?.role !== role) {
21+
return fail(res, "Forbidden", 403);
22+
}
23+
24+
return next();
25+
};
26+
}

apps/api/src/routes/adminRoutes.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { Router } from "express";
22
import { metrics } from "../controllers/adminController.js";
3-
import { authMiddleware } from "../middleware/auth.js";
3+
import { authMiddleware, requireRole } from "../middleware/auth.js";
44

55
export const adminRoutes = Router();
66

77
adminRoutes.use(authMiddleware);
8+
adminRoutes.use(requireRole("admin"));
89
adminRoutes.get("/metrics", metrics);

apps/api/src/tests/admin.test.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import test from "node:test";
2+
import assert from "node:assert/strict";
3+
import { createApp } from "../app.js";
4+
import { signAccessToken } from "../utils/jwt.js";
5+
6+
async function withServer(run) {
7+
const app = createApp();
8+
const server = app.listen(0);
9+
10+
await new Promise((resolve, reject) => {
11+
server.once("listening", resolve);
12+
server.once("error", reject);
13+
});
14+
15+
try {
16+
const { port } = server.address();
17+
await run(`http://127.0.0.1:${port}`);
18+
} finally {
19+
await new Promise((resolve, reject) => {
20+
server.close((error) => (error ? reject(error) : resolve()));
21+
});
22+
}
23+
}
24+
25+
async function getMetrics(baseUrl, role) {
26+
const token = signAccessToken({ sub: `usr_${role}`, role });
27+
const response = await fetch(`${baseUrl}/api/admin/metrics`, {
28+
headers: { authorization: `Bearer ${token}` },
29+
});
30+
31+
return { response, payload: await response.json() };
32+
}
33+
34+
test("GET /api/admin/metrics rejects client tokens", async () => {
35+
await withServer(async (baseUrl) => {
36+
const { response, payload } = await getMetrics(baseUrl, "client");
37+
38+
assert.equal(response.status, 403);
39+
assert.deepEqual(payload, {
40+
success: false,
41+
message: "Forbidden",
42+
});
43+
});
44+
});
45+
46+
test("GET /api/admin/metrics rejects freelancer tokens", async () => {
47+
await withServer(async (baseUrl) => {
48+
const { response, payload } = await getMetrics(baseUrl, "freelancer");
49+
50+
assert.equal(response.status, 403);
51+
assert.deepEqual(payload, {
52+
success: false,
53+
message: "Forbidden",
54+
});
55+
});
56+
});
57+
58+
test("GET /api/admin/metrics allows admin tokens", async () => {
59+
await withServer(async (baseUrl) => {
60+
const { response, payload } = await getMetrics(baseUrl, "admin");
61+
62+
assert.equal(response.status, 200);
63+
assert.equal(payload.success, true);
64+
assert.equal(payload.data.openJobs, 42);
65+
assert.equal(payload.data.flaggedAccounts, 3);
66+
});
67+
});

0 commit comments

Comments
 (0)