rate limiting on get, more unit test config
This commit is contained in:
parent
9fcd8e5903
commit
1d824d5f07
@ -37,9 +37,13 @@ services:
|
||||
- DATABASE_URL=file:/database/db.sqlite
|
||||
- FRONTEND_URL=http://localhost:5000
|
||||
- CLEANUP_INTERVAL_SECONDS=600
|
||||
- NODE_ENV=production
|
||||
# Rate limit for uploading notes
|
||||
- POST_LIMIT_WINDOW_SECONDS=86400
|
||||
- POST_LIMIT=50
|
||||
- NODE_ENV=production
|
||||
# Rate limit for downloading notes
|
||||
- GET_LIMIT_WINDOW_SECONDS=60
|
||||
- GET_LIMIT=20
|
||||
depends_on:
|
||||
migrate:
|
||||
condition: service_completed_successfully
|
||||
|
@ -5,7 +5,7 @@
|
||||
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
|
||||
|
||||
# If ENVIRONMENT=dev, CORS is enabled.
|
||||
ENVIRONMENT=dev
|
||||
ENVIRONMENT=test
|
||||
|
||||
# Port at which the server is hosted.
|
||||
PORT=8080
|
||||
@ -20,8 +20,10 @@ DATABASE_URL="file:./dev.sqlite"
|
||||
# and expired notes are deleted.
|
||||
CLEANUP_INTERVAL_SECONDS=60
|
||||
|
||||
# Rate limit window after which post limit is reset
|
||||
POST_LIMIT_WINDOW="86400 # 24 hours"
|
||||
# Rate limit for uploading notes.
|
||||
POST_LIMIT_WINDOW=86400
|
||||
POST_LIMIT=50
|
||||
|
||||
# Max. posted notes within rate limit window
|
||||
POST_LIMIT=50
|
||||
# Rate limit for downloading notes.
|
||||
GET_LIMIT_WINDOW=60
|
||||
GET_LIMIT=20
|
@ -1,5 +1,7 @@
|
||||
DATABASE_URL="file:./test.sqlite"
|
||||
|
||||
POST_LIMIT=50
|
||||
POST_LIMIT_WINDOW=1
|
||||
POST_LIMIT_WINDOW_SECONDS=0.1
|
||||
GET_LIMIT=20
|
||||
GET_LIMIT_WINDOW_SECONDS=0.1
|
||||
LOG_LEVEL=warn
|
@ -7,8 +7,8 @@
|
||||
"test": "run-s test:db:reset test:test",
|
||||
"coverage": "run-s test:db:reset test:coverage",
|
||||
"test-watch": "dotenv -e .env.test -- vitest unit --coverage",
|
||||
"test:test": "dotenv -e .env.test -- vitest run ",
|
||||
"test:coverage": "dotenv -e .env.test -- vitest run --coverage",
|
||||
"test:test": "dotenv -e .env.test -- vitest run --no-threads",
|
||||
"test:coverage": "dotenv -e .env.test -- vitest run --no-threads --coverage",
|
||||
"test:db:reset": "dotenv -e .env.test -- npx prisma migrate reset -f",
|
||||
"build": "npx tsc",
|
||||
"dev": "npx nodemon ./server.ts | npx pino-colada"
|
||||
|
@ -31,13 +31,33 @@ describe("GET /api/note", () => {
|
||||
});
|
||||
|
||||
it("responds 404 for invalid ID", async () => {
|
||||
// Insert a note
|
||||
// Make get request
|
||||
const res = await request(app).get(`/api/note/NaN`);
|
||||
|
||||
// Validate returned note
|
||||
expect(res.statusCode).toBe(404);
|
||||
});
|
||||
|
||||
it("Applies rate limits to endpoint", async () => {
|
||||
// Insert a note
|
||||
const { id } = await prisma.encryptedNote.create({
|
||||
data: testNote,
|
||||
});
|
||||
|
||||
// Make get requests
|
||||
const requests = [];
|
||||
for (let i = 0; i < 51; i++) {
|
||||
requests.push(request(app).get(`/api/note/${id}`));
|
||||
}
|
||||
const responses = await Promise.all(requests);
|
||||
const responseCodes = responses.map((res) => res.statusCode);
|
||||
|
||||
// at least one response should be 429
|
||||
expect(responseCodes).toContain(429);
|
||||
|
||||
// sleep for 100 ms to allow rate limiter to reset
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /api/note", () => {
|
||||
@ -85,6 +105,15 @@ describe("POST /api/note", () => {
|
||||
expect(res.body.hmac).toEqual(testNote.hmac);
|
||||
});
|
||||
|
||||
it("Applies upload limit to endpoint of 400kb", async () => {
|
||||
const largeNote = {
|
||||
ciphertext: "a".repeat(400 * 1024),
|
||||
hmac: "sample_hmac",
|
||||
};
|
||||
const res = await request(app).post("/api/note").send(largeNote);
|
||||
expect(res.statusCode).toBe(413);
|
||||
});
|
||||
|
||||
it("Applies rate limits to endpoint", async () => {
|
||||
// make more requests than the post limit set in .env.test
|
||||
const requests = [];
|
||||
@ -96,14 +125,8 @@ describe("POST /api/note", () => {
|
||||
|
||||
// at least one response should be 429
|
||||
expect(responseCodes).toContain(429);
|
||||
});
|
||||
|
||||
it("Applies upload limit to endpoint of 400kb", async () => {
|
||||
const largeNote = {
|
||||
ciphertext: "a".repeat(400 * 1024),
|
||||
hmac: "sample_hmac",
|
||||
};
|
||||
const res = await request(app).post("/api/note").send(largeNote);
|
||||
expect(res.statusCode).toBe(413);
|
||||
// sleep for 100 ms to allow rate limiter to reset
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
});
|
||||
});
|
||||
|
@ -31,15 +31,37 @@ app.use(
|
||||
|
||||
// Apply rate limiting
|
||||
const postLimiter = rateLimit({
|
||||
windowMs: parseInt(process.env.POST_LIMIT_WINDOW_SECONDS as string) * 1000,
|
||||
windowMs: parseFloat(process.env.POST_LIMIT_WINDOW_SECONDS as string) * 1000,
|
||||
max: parseInt(process.env.POST_LIMIT as string), // Limit each IP to X requests per window
|
||||
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
|
||||
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
|
||||
});
|
||||
|
||||
const getLimiter = rateLimit({
|
||||
windowMs: parseFloat(process.env.GET_LIMIT_WINDOW_SECONDS as string) * 1000,
|
||||
max: parseInt(process.env.GET_LIMIT as string), // Limit each IP to X requests per window
|
||||
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
|
||||
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
|
||||
});
|
||||
|
||||
// Apply 400kB upload limit on POST
|
||||
app.use(bodyParser.json({ limit: "400k" }));
|
||||
|
||||
// Get encrypted note
|
||||
app.get("/api/note/:id", getLimiter, (req, res, next) => {
|
||||
prisma.encryptedNote
|
||||
.findUnique({
|
||||
where: { id: req.params.id },
|
||||
})
|
||||
.then((note) => {
|
||||
if (note != null) {
|
||||
res.send(note);
|
||||
}
|
||||
res.status(404).send();
|
||||
})
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
// Post new encrypted note
|
||||
app.post(
|
||||
"/api/note/",
|
||||
@ -60,21 +82,6 @@ app.post(
|
||||
}
|
||||
);
|
||||
|
||||
// Get encrypted note
|
||||
app.get("/api/note/:id", (req, res, next) => {
|
||||
prisma.encryptedNote
|
||||
.findUnique({
|
||||
where: { id: req.params.id },
|
||||
})
|
||||
.then((note) => {
|
||||
if (note != null) {
|
||||
res.send(note);
|
||||
}
|
||||
res.status(404).send();
|
||||
})
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
// For testing purposes
|
||||
app.get("/api/test", (req, res, next) => {
|
||||
res.status(200).send("Hello world!");
|
||||
|
Loading…
Reference in New Issue
Block a user