Skip to content

Commit

Permalink
Added whitelisted paths for rateLimit - Bump Deno runtime to 1.45.4
Browse files Browse the repository at this point in the history
  • Loading branch information
petruki committed Jul 28, 2024
1 parent 8fc0265 commit fe6282f
Show file tree
Hide file tree
Showing 17 changed files with 57 additions and 25 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ jobs:
with:
fetch-depth: 0

- name: Setup Deno v1.44.4
- name: Setup Deno v1.45.4
uses: denoland/setup-deno@v1
with:
deno-version: v1.44.4
deno-version: v1.45.4

- name: Setup LCOV
run: sudo apt install -y lcov
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM denoland/deno:alpine-1.43.6
FROM denoland/deno:alpine-1.45.4

ENV APP_HOME=/home/app
WORKDIR $APP_HOME
Expand Down
3 changes: 2 additions & 1 deletion deno.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@
"clean": "rm -rf ./npm ./coverage",
"cover": "deno task clean && deno task test && deno task lcov && genhtml -o coverage/html coverage/report.lcov"
},
"lock": false
"lock": false,
"exports": "./src/index.ts"
}
3 changes: 2 additions & 1 deletion src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ import { getEnv } from './utils.ts';
const app = new Application();
const rateLimit = new RateLimit();
const helmet = new Helmet();
const whitelist = ['/api/check', '/swagger.json'];

app.use(helmet.middleware());
app.use(rateLimit.middleware({
limit: Number(getEnv('APP_RATE_LIMIT', '1000')),
windowMs: Number(getEnv('APP_RATE_LIMIT_WINDOW', '60000')),
}));
}, whitelist));

app.use(responseTimeLog);
app.use(responseTime);
Expand Down
6 changes: 3 additions & 3 deletions src/dto/mapper.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Context, Output } from '../deps.ts';
import type { Context, Output } from '../deps.ts';
import { getBooleanParam, getParam } from '../utils.ts';
import { SearchDocsQueryParams, SearchDocsRequestDto } from './request.ts';
import { SearchDocsResponseDto } from './response.ts';
import { SearchDocsQueryParams, type SearchDocsRequestDto } from './request.ts';
import type { SearchDocsResponseDto } from './response.ts';

export function toSearchDocsResponseDto(output: Output[]): SearchDocsResponseDto {
return {
Expand Down
2 changes: 1 addition & 1 deletion src/middleware/helmet.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Context, Middleware, Next } from '../deps.ts';
import type { Context, Middleware, Next } from '../deps.ts';

const SERVER = 'Deno';
const ACCESS_CONTROL_ALLOW_ORIGIN = '*';
Expand Down
2 changes: 1 addition & 1 deletion src/middleware/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { bold, Context, cyan, green, Next } from '../deps.ts';
import { bold, type Context, cyan, green, type Next } from '../deps.ts';
import { logger } from '../utils.ts';

export const responseTime = async (context: Context, next: Next) => {
Expand Down
9 changes: 7 additions & 2 deletions src/middleware/rate-limit.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Context, Middleware, Next } from '../deps.ts';
import type { Context, Middleware, Next } from '../deps.ts';
import { responseError } from '../utils.ts';

class RequestStore {
Expand Down Expand Up @@ -31,13 +31,18 @@ export default class RateLimit {
this.map = new Map();
}

public middleware(params: RateLimitParams): Middleware {
public middleware(params: RateLimitParams, whitelist?: string[]): Middleware {
const { limit, windowMs } = params;

return async (context: Context, next: Next) => {
const ip = context.request.ip;
const path = context.request.url.pathname;
const timestamp = Date.now();

if (whitelist?.includes(path)) {
return await next();
}

if (!this.map.has(ip)) {
this.updateRate(context, limit, limit - 1, windowMs, ip, 1, timestamp);
return await next();
Expand Down
2 changes: 1 addition & 1 deletion src/routes/api-docs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Context, Router } from '../deps.ts';
import { type Context, Router } from '../deps.ts';
import swaggerDocument from '../api-docs/swagger-document.ts';

const router = new Router();
Expand Down
2 changes: 1 addition & 1 deletion src/routes/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Context, Router } from '../deps.ts';
import { type Context, Router } from '../deps.ts';
import { getEnv } from '../utils.ts';

const router = new Router();
Expand Down
2 changes: 1 addition & 1 deletion src/routes/searchdocs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Context, Router, ValidatorFn, ValidatorMiddleware } from '../deps.ts';
import { type Context, Router, ValidatorFn, ValidatorMiddleware } from '../deps.ts';
import { toSearchDocsRequestDto, toSearchDocsResponseDto } from '../dto/mapper.ts';
import { getEnv, responseError, responseSuccess } from '../utils.ts';
import SearchDocsService from '../services/searchdocs.ts';
Expand Down
6 changes: 3 additions & 3 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { bold, Context } from './deps.ts';
import { ResponseDto } from './dto/response.ts';
import { bold, type Context } from './deps.ts';
import type { ResponseDto } from './dto/response.ts';

const Level = Object.freeze({
INFO: 0,
Expand Down Expand Up @@ -47,7 +47,7 @@ export function logger(level: string, component: string, content: string | objec

export function getParam(params: URLSearchParams, key: string, or: string | number) {
if (params.has(key)) {
return params.get(key)?.trim()!;
return params.get(key)?.trim();
}

return or;
Expand Down
2 changes: 1 addition & 1 deletion test/middleware/helmet.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Helmet from '../../src/middleware/helmet.ts';
import { Context, Next } from '../../src/deps.ts';
import type { Context, Next } from '../../src/deps.ts';
import { assertEquals } from '../deps.ts';

const newRequest = () => {
Expand Down
26 changes: 25 additions & 1 deletion test/middleware/rate-limit.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import RateLimit from '../../src/middleware/rate-limit.ts';
import { Context, Next } from '../../src/deps.ts';
import type { Context, Next } from '../../src/deps.ts';
import { assertEquals } from '../deps.ts';

const newRequest = () => {
return {
request: {
ip: 'localhost',
url: new URL('http://localhost/api/check'),
},
response: {
status: 0,
Expand Down Expand Up @@ -41,6 +42,29 @@ Deno.test({
},
});

Deno.test({
name: 'RateLimit middleware - it should NOT return 429 error when whitelisted path',
async fn() {
const rateLimit = new RateLimit();
const middleware = rateLimit.middleware({
limit: 1,
windowMs: 1000,
}, ['/api/check']);

const next = () => {
return;
};

const req1 = newRequest();
await middleware(req1 as Context, next as Next);
assertEquals(req1.response.status, 0);

const req2 = newRequest();
await middleware(req2 as Context, next as Next);
assertEquals(req2.response.status, 0);
},
});

Deno.test({
name: 'RateLimit middleware - it should reset counter after windowMs',
async fn() {
Expand Down
3 changes: 2 additions & 1 deletion test/routes/error/searchdocs-url-error.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Deno.test({
name: 'SearchDocs route error - it should NOT return search results - url is not set',
async fn() {
Deno.env.set('APP_URL', 'http://localhost:8000');
Deno.env.set('APP_FILES', 'README.md');

const searchParams = new URLSearchParams();
searchParams.append(SearchDocsQueryParams.query, 'Skimming');
Expand All @@ -15,7 +16,7 @@ Deno.test({
.send()
.expect(500);

assert(response.body.error.includes('error sending request for url (http://localhost:8000/README.md)'));
assert(response.body.error.includes('client error (Connect)'));

// teardown
Deno.env.set('APP_URL', 'https://raw.githubusercontent.com/petruki/skimming/master/');
Expand Down
4 changes: 2 additions & 2 deletions test/routes/searchdocs.functional.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import app from '../../src/app.ts';
import { SearchDocsQueryParams } from '../../src/dto/request.ts';
import { SearchDocsResponseDto } from '../../src/dto/response.ts';
import { assert, assertEquals, IResponse, superoak } from '../deps.ts';
import type { SearchDocsResponseDto } from '../../src/dto/response.ts';
import { assert, assertEquals, type IResponse, superoak } from '../deps.ts';

Deno.test({
name: 'SearchDocs route - it should return search results from cache on second request',
Expand Down
4 changes: 2 additions & 2 deletions test/routes/searchdocs.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import app from '../../src/app.ts';
import { SearchDocsQueryParams } from '../../src/dto/request.ts';
import { SearchDocsResponseDto } from '../../src/dto/response.ts';
import { assert, assertEquals, IResponse, superoak } from '../deps.ts';
import type { SearchDocsResponseDto } from '../../src/dto/response.ts';
import { assert, assertEquals, type IResponse, superoak } from '../deps.ts';

Deno.test({
name: 'SearchDocs route - it should return search results',
Expand Down

0 comments on commit fe6282f

Please sign in to comment.