From 10efe54760f1a3d09f0479108e559ecfac6d79e7 Mon Sep 17 00:00:00 2001 From: Jonas Verhoelen Date: Tue, 21 May 2019 08:24:54 +0200 Subject: [PATCH 1/3] install and setup standard and applicable non-standard helmet middlewares --- .gitignore | 1 + package-lock.json | 144 ++++++++++++++++++++++++++++++++ package.json | 26 +++--- service/server/ExpressServer.ts | 15 ++++ 4 files changed, 174 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 2e97214..8b59790 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ coverage .vscode .isomorphic-loader-config.json .idea +*.iml diff --git a/package-lock.json b/package-lock.json index 9cc79a2..902f81e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -152,6 +152,15 @@ "@types/node": "*" } }, + "@types/helmet": { + "version": "0.0.43", + "resolved": "https://registry.npmjs.org/@types/helmet/-/helmet-0.0.43.tgz", + "integrity": "sha512-45cU1ZW3L6ZxPQzAB597nl7OfUr14ceUWaQRoiByGFAjZlCQRvaEFC4Vv84lLWsFCYVWLU+raz+bIHrVBNTdRw==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, "@types/loglevel": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/@types/loglevel/-/loglevel-1.5.4.tgz", @@ -1139,6 +1148,11 @@ "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", "dev": true }, + "camelize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", + "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" + }, "capture-stack-trace": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", @@ -1472,6 +1486,11 @@ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" }, + "content-security-policy-builder": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-security-policy-builder/-/content-security-policy-builder-2.0.0.tgz", + "integrity": "sha512-j+Nhmj1yfZAikJLImCvPJFE29x/UuBi+/MWqggGGc515JKaZrjuei2RhULJmy0MsstW3E3htl002bwmBNMKr7w==" + }, "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", @@ -1629,6 +1648,11 @@ "es5-ext": "^0.10.9" } }, + "dasherize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dasherize/-/dasherize-2.0.0.tgz", + "integrity": "sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg=" + }, "data-uri-to-buffer": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-0.0.4.tgz", @@ -1774,12 +1798,22 @@ "randombytes": "^2.0.0" } }, + "dns-prefetch-control": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/dns-prefetch-control/-/dns-prefetch-control-0.1.0.tgz", + "integrity": "sha1-YN20V3dOF48flBXwyrsOhbCzALI=" + }, "domain-browser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", "dev": true }, + "dont-sniff-mimetype": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dont-sniff-mimetype/-/dont-sniff-mimetype-1.0.0.tgz", + "integrity": "sha1-WTKJDcn04vGeXrAqIAJuXl78j1g=" + }, "dot-prop": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", @@ -2027,6 +2061,11 @@ "homedir-polyfill": "^1.0.1" } }, + "expect-ct": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/expect-ct/-/expect-ct-0.2.0.tgz", + "integrity": "sha512-6SK3MG/Bbhm8MsgyJAylg+ucIOU71/FzyFalcfu5nY19dH8y/z0tBJU0wrNBXD4B27EoQtqPF/9wqH0iYAd04g==" + }, "express": { "version": "4.16.4", "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", @@ -2176,6 +2215,11 @@ "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", "dev": true }, + "feature-policy": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/feature-policy/-/feature-policy-0.3.0.tgz", + "integrity": "sha512-ZtijOTFN7TzCujt1fnNhfWPFPSHeZkesff9AXZj+UEjYBynWNUIYpC87Ve4wHzyexQsImicLu7WsC2LHq7/xrQ==" + }, "figgy-pudding": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", @@ -2331,6 +2375,11 @@ "map-cache": "^0.2.2" } }, + "frameguard": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/frameguard/-/frameguard-3.1.0.tgz", + "integrity": "sha512-TxgSKM+7LTA6sidjOiSZK9wxY0ffMPY3Wta//MqwmX0nZuEHc8QrkV8Fh3ZhMJeiH+Uyh/tcaarImRy8u77O7g==" + }, "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -3128,6 +3177,56 @@ "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", "dev": true }, + "helmet": { + "version": "3.18.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-3.18.0.tgz", + "integrity": "sha512-TsKlGE5UVkV0NiQ4PllV9EVfZklPjyzcMEMjWlyI/8S6epqgRT+4s4GHVgc25x0TixsKvp3L7c91HQQt5l0+QA==", + "requires": { + "depd": "2.0.0", + "dns-prefetch-control": "0.1.0", + "dont-sniff-mimetype": "1.0.0", + "expect-ct": "0.2.0", + "feature-policy": "0.3.0", + "frameguard": "3.1.0", + "helmet-crossdomain": "0.3.0", + "helmet-csp": "2.7.1", + "hide-powered-by": "1.0.0", + "hpkp": "2.0.0", + "hsts": "2.2.0", + "ienoopen": "1.1.0", + "nocache": "2.1.0", + "referrer-policy": "1.2.0", + "x-xss-protection": "1.1.0" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + } + } + }, + "helmet-crossdomain": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/helmet-crossdomain/-/helmet-crossdomain-0.3.0.tgz", + "integrity": "sha512-YiXhj0E35nC4Na5EPE4mTfoXMf9JTGpN4OtB4aLqShKuH9d2HNaJX5MQoglO6STVka0uMsHyG5lCut5Kzsy7Lg==" + }, + "helmet-csp": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/helmet-csp/-/helmet-csp-2.7.1.tgz", + "integrity": "sha512-sCHwywg4daQ2mY0YYwXSZRsgcCeerUwxMwNixGA7aMLkVmPTYBl7gJoZDHOZyXkqPrtuDT3s2B1A+RLI7WxSdQ==", + "requires": { + "camelize": "1.0.0", + "content-security-policy-builder": "2.0.0", + "dasherize": "2.0.0", + "platform": "1.3.5" + } + }, + "hide-powered-by": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hide-powered-by/-/hide-powered-by-1.0.0.tgz", + "integrity": "sha1-SoWtZYgfYoV/xwr3F0oRhNzM4ys=" + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -3159,6 +3258,26 @@ "resolved": "https://registry.npmjs.org/hot-shots/-/hot-shots-4.8.0.tgz", "integrity": "sha1-BSvkhDDvx9EXunzE1B8YM7o4x58=" }, + "hpkp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hpkp/-/hpkp-2.0.0.tgz", + "integrity": "sha1-EOFCJk52IVpdMMROxD3mTe5tFnI=" + }, + "hsts": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hsts/-/hsts-2.2.0.tgz", + "integrity": "sha512-ToaTnQ2TbJkochoVcdXYm4HOCliNozlviNsg+X2XQLQvZNI/kCHR9rZxVYpJB3UPcHz80PgxRyWQ7PdU1r+VBQ==", + "requires": { + "depd": "2.0.0" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + } + } + }, "html-entities": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", @@ -3201,6 +3320,11 @@ "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", "dev": true }, + "ienoopen": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ienoopen/-/ienoopen-1.1.0.tgz", + "integrity": "sha512-MFs36e/ca6ohEKtinTJ5VvAJ6oDRAYFdYXweUnGY9L9vcoqFOU4n2ZhmJ0C4z/cwGZ3YIQRSB3XZ1+ghZkY5NQ==" + }, "iferr": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", @@ -4049,6 +4173,11 @@ } } }, + "nocache": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nocache/-/nocache-2.1.0.tgz", + "integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==" + }, "node-dogstatsd": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/node-dogstatsd/-/node-dogstatsd-0.0.6.tgz", @@ -4486,6 +4615,11 @@ "find-up": "^3.0.0" } }, + "platform": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.5.tgz", + "integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q==" + }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -4697,6 +4831,11 @@ "readable-stream": "^2.0.2" } }, + "referrer-policy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.2.0.tgz", + "integrity": "sha512-LgQJIuS6nAy1Jd88DCQRemyE3mS+ispwlqMk3b0yjZ257fI1v9c+/p6SD5gP5FGyXUIgrNOAfmyioHwZtYv2VA==" + }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -6207,6 +6346,11 @@ "async-limiter": "~1.0.0" } }, + "x-xss-protection": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/x-xss-protection/-/x-xss-protection-1.1.0.tgz", + "integrity": "sha512-rx3GzJlgEeZ08MIcDsU2vY2B1QEriUKJTSiNHHUIem6eg9pzVOr2TL3Y4Pd6TMAM5D5azGjcxqI62piITBDHVg==" + }, "xdg-basedir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", diff --git a/package.json b/package.json index 2d71eb1..0ee981a 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "node-express-typescript-recipes", + "name": "node-express-typescript-boilerplate", "version": "0.0.1", "description": "A cookbook full of recipes for developing web apps with Node.js and Express.js in TypeScript", "main": "service/server/index.ts", @@ -22,13 +22,14 @@ "client:statistics": "NODE_ENV=production webpack --profile --json > service/www/webpack-stats.json && webpack-bundle-analyzer service/www/webpack-stats.json" }, "dependencies": { - "express": "^4.16.2", "compression": "^1.7.2", - "cookie-parser": "^1.4.3", - "http-status-codes": "^1.3.0", "connect-datadog": "^0.0.6", - "hot-shots": "^4.7.0", + "cookie-parser": "^1.4.3", + "express": "^4.16.2", "fs-extra": "7.0.1", + "helmet": "^3.18.0", + "hot-shots": "^4.7.0", + "http-status-codes": "^1.3.0", "supertest": "^3.0.0", "tslib": "^1.9.3" }, @@ -36,14 +37,15 @@ "@types/chai": "4.1.7", "@types/chai-as-promised": "7.1.0", "@types/chai-string": "1.4.1", - "@types/fs-extra": "5.0.0", "@types/chai-subset": "1.3.1", "@types/compression": "0.0.35", "@types/cookie-parser": "1.4.1", "@types/express": "4.11.1", - "@types/supertest": "2.0.4", + "@types/fs-extra": "5.0.0", + "@types/helmet": "0.0.43", "@types/mocha": "5.2.5", "@types/sinon-chai": "3.2.2", + "@types/supertest": "2.0.4", "@types/webpack": "^4.4.22", "@types/webpack-dev-middleware": "^2.0.2", "@types/webpack-hot-middleware": "^2.16.4", @@ -53,18 +55,18 @@ "chai-shallow-deep-equal": "1.4.6", "chai-string": "1.5.0", "chai-subset": "1.6.0", + "core-js": "2.6.1", "expressmocks": "^0.1.3", - "nodemon": "^1.19.0", - "mocha": "5.2.0", "isomorphic-loader": "^2.0.2", - "mocha-better-spec-reporter": "3.1.0", "mini-css-extract-plugin": "^0.5.0", + "mocha": "5.2.0", + "mocha-better-spec-reporter": "3.1.0", + "nodemon": "^1.19.0", "prettier": "^1.9.2", "sinon": "7.2.2", "sinon-chai": "3.3.0", "ts-node": "6.1.1", "typescript": "3.1.1", - "core-js": "2.6.1", "webpack": "^4.30.0", "webpack-bundle-analyzer": "^3.0.3", "webpack-cli": "^3.2.0", @@ -75,7 +77,7 @@ "author": "Jonas Verhoelen ", "repository": { "type": "git", - "url": "git@github.com:jverhoelen/node-express-typescript-recipes.git" + "url": "git@github.com:jverhoelen/node-express-typescript-boilerplate.git" }, "license": "MIT" } diff --git a/service/server/ExpressServer.ts b/service/server/ExpressServer.ts index 139d991..c08d87b 100644 --- a/service/server/ExpressServer.ts +++ b/service/server/ExpressServer.ts @@ -3,6 +3,7 @@ import { Express, NextFunction, Response, Request } from 'express' import { Server } from 'http' import * as fse from 'fs-extra' import * as compress from 'compression' +import * as helmet from 'helmet' import * as bodyParser from 'body-parser' import * as cookieParser from 'cookie-parser' @@ -28,6 +29,7 @@ export class ExpressServer { public async setup(port: number) { const server = express() + this.setupSecurityMiddlewares(server) this.setupStandardMiddlewares(server) this.applyWebpackDevMiddleware(server) this.setupTelemetry(server) @@ -49,6 +51,19 @@ export class ExpressServer { if (this.httpServer) this.httpServer.close() } + private setupSecurityMiddlewares(server: Express) { + server.use(helmet()) + server.use(helmet.referrerPolicy({ policy: 'same-origin' })) + server.use(helmet.noCache()) + server.use(helmet.contentSecurityPolicy({ + directives: { + defaultSrc: ["'self'"], + styleSrc: ["'unsafe-inline'"], + scriptSrc: ["'unsafe-inline'", "'self'"] + } + })) + } + private setupStandardMiddlewares(server: Express) { server.use(bodyParser.json()) server.use(cookieParser()) From a3ce9a29b89d34bff3fab59b966d66cfac4144fb Mon Sep 17 00:00:00 2001 From: Jonas Verhoelen Date: Tue, 21 May 2019 08:43:20 +0200 Subject: [PATCH 2/3] install and setup express-rate-limit middleware --- package-lock.json | 32 +++++++++++++++++++++++++++++++- package.json | 4 +++- service/server/ExpressServer.ts | 16 +++++++++++++++- 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 902f81e..ea13d30 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "node-express-typescript-recipes", + "name": "node-express-typescript-boilerplate", "version": "0.0.1", "lockfileVersion": 1, "requires": true, @@ -133,6 +133,15 @@ "@types/serve-static": "*" } }, + "@types/express-rate-limit": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@types/express-rate-limit/-/express-rate-limit-3.3.0.tgz", + "integrity": "sha512-/PmN34aFHxcYwu/BALxFPnLAtUxRjhWFX7baIzaWJ85Mt2l/akj9apACvyQW+qnkxzZanWSy23POX42vHHCsNg==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, "@types/express-serve-static-core": { "version": "4.16.2", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.2.tgz", @@ -1341,6 +1350,11 @@ } } }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" + }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -1700,6 +1714,14 @@ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "requires": { + "clone": "^1.0.2" + } + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -2103,6 +2125,14 @@ "vary": "~1.1.2" } }, + "express-rate-limit": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-3.5.1.tgz", + "integrity": "sha512-aoxJLcqOAs2nEDwrQKrwCRoWdYxS7Qu+W1lSe4revazBxT/mTgEQrltJxt4z/AnAy/Qcm42M4ND+q3vI7AHL5Q==", + "requires": { + "defaults": "^1.0.3" + } + }, "expressmocks": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/expressmocks/-/expressmocks-0.1.3.tgz", diff --git a/package.json b/package.json index 0ee981a..08e4ccb 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ "connect-datadog": "^0.0.6", "cookie-parser": "^1.4.3", "express": "^4.16.2", - "fs-extra": "7.0.1", + "express-rate-limit": "^3.5.1", + "fs-extra": "^7.0.1", "helmet": "^3.18.0", "hot-shots": "^4.7.0", "http-status-codes": "^1.3.0", @@ -41,6 +42,7 @@ "@types/compression": "0.0.35", "@types/cookie-parser": "1.4.1", "@types/express": "4.11.1", + "@types/express-rate-limit": "3.3.0", "@types/fs-extra": "5.0.0", "@types/helmet": "0.0.43", "@types/mocha": "5.2.5", diff --git a/service/server/ExpressServer.ts b/service/server/ExpressServer.ts index c08d87b..8adcbab 100644 --- a/service/server/ExpressServer.ts +++ b/service/server/ExpressServer.ts @@ -6,6 +6,7 @@ import * as compress from 'compression' import * as helmet from 'helmet' import * as bodyParser from 'body-parser' import * as cookieParser from 'cookie-parser' +import * as RateLimit from 'express-rate-limit' import { noCache } from './middlewares/NoCacheMiddleware' import DatadogStatsdMiddleware from './middlewares/DatadogStatsdMiddleware' @@ -68,6 +69,13 @@ export class ExpressServer { server.use(bodyParser.json()) server.use(cookieParser()) server.use(compress()) + + const baseRateLimitingOptions = { + windowMs: 15 * 60 * 1000, // 15 min in ms + max: 1000, + message: 'Our API is rate limited to a maximum of 1000 requests per 15 minutes, please lower your request rate' + } + server.use('/api/', new RateLimit(baseRateLimitingOptions)) } private configureEjsTemplates(server: Express) { @@ -144,8 +152,14 @@ export class ExpressServer { } private configureApiEndpoints(server: Express) { + const strictRateLimit = new RateLimit({ + windowMs: 15 * 60 * 1000, // 15 min in ms + max: 200, + message: 'This endpoint has a stricter rate limiting of a maximum of 200 requests per 15 minutes window, please lower your request rate' + }) + server.get('/api/cat', noCache, this.catEndpoints.getAllCats) - server.get('/api/statistics/cat', noCache, this.catEndpoints.getCatsStatistics) + server.get('/api/statistics/cat', noCache, strictRateLimit, this.catEndpoints.getCatsStatistics) server.get('/api/cat/:catId', noCache, this.catEndpoints.getCatDetails) } } From 17686c49017b37f541713359890afa4650a82423 Mon Sep 17 00:00:00 2001 From: Jonas Verhoelen Date: Tue, 21 May 2019 09:58:14 +0200 Subject: [PATCH 3/3] cors, csurf, hpp! --- package-lock.json | 127 +++++++++++++++++++++++++++++++- package.json | 6 ++ service/server/ExpressServer.ts | 12 ++- 3 files changed, 142 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ea13d30..4582423 100644 --- a/package-lock.json +++ b/package-lock.json @@ -122,6 +122,25 @@ "integrity": "sha512-aRnpPa7ysx3aNW60hTiCtLHlQaIFsXFCgQlpakNgDNVFzbtusSY8PwjAQgRWfSk0ekNoBjO51eQRB6upA9uuyw==", "dev": true }, + "@types/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-GmK8AKu8i+s+EChK/uZ5IbrXPcPaQKWaNSGevDT/7o3gFObwSUQwqb1jMqxuo+YPvj0ckGzINI+EO7EHcmJjKg==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/csurf": { + "version": "1.9.35", + "resolved": "https://registry.npmjs.org/@types/csurf/-/csurf-1.9.35.tgz", + "integrity": "sha512-2EVN+Bt2Vd8u+11xeJ64BjCYVOlhqaob82FPAw8VzOOWAYfP8TFvB7RD67CShEz45JXiI+38mlNJHKrArCzFMw==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/express-serve-static-core": "*" + } + }, "@types/express": { "version": "4.11.1", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.11.1.tgz", @@ -170,6 +189,15 @@ "@types/express": "*" } }, + "@types/hpp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@types/hpp/-/hpp-0.2.1.tgz", + "integrity": "sha512-+46c8i+nXpoJn8GJnZTvZUfggz4bKOlTtEXIxWTFb7xZI9onahMUnZ+xNdjc4vzSM/zUaUYtuEsY8ysgv68rXQ==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, "@types/loglevel": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/@types/loglevel/-/loglevel-1.5.4.tgz", @@ -1565,6 +1593,15 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, "create-ecdh": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", @@ -1647,6 +1684,51 @@ "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", "dev": true }, + "csrf": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csrf/-/csrf-3.1.0.tgz", + "integrity": "sha512-uTqEnCvWRk042asU6JtapDTcJeeailFy4ydOQS28bj1hcLnYRiqi8SsD2jS412AY1I/4qdOwWZun774iqywf9w==", + "requires": { + "rndm": "1.2.0", + "tsscmp": "1.0.6", + "uid-safe": "2.1.5" + } + }, + "csurf": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/csurf/-/csurf-1.10.0.tgz", + "integrity": "sha512-fh725p0R83wA5JukCik5hdEko/LizW/Vl7pkKDa1WJUVCosg141mqaAWCScB+nkEaRMFMGbutHMOr6oBNc/j9A==", + "requires": { + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "csrf": "3.1.0", + "http-errors": "~1.7.2" + }, + "dependencies": { + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + } + } + }, "cyclist": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", @@ -3293,6 +3375,15 @@ "resolved": "https://registry.npmjs.org/hpkp/-/hpkp-2.0.0.tgz", "integrity": "sha1-EOFCJk52IVpdMMROxD3mTe5tFnI=" }, + "hpp": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/hpp/-/hpp-0.2.2.tgz", + "integrity": "sha1-DsX3dHIEmnQ2HYW6K4jiRwpDVvg=", + "requires": { + "lodash": "^4.7.0", + "type-is": "^1.6.12" + } + }, "hsts": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/hsts/-/hsts-2.2.0.tgz", @@ -3743,8 +3834,7 @@ "lodash": { "version": "4.17.11", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" }, "log-symbols": { "version": "2.2.0", @@ -4317,6 +4407,11 @@ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, "object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", @@ -4782,6 +4877,11 @@ "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", "dev": true }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -4997,6 +5097,11 @@ "inherits": "^2.0.1" } }, + "rndm": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz", + "integrity": "sha1-8z/pz7Urv9UgqhgyO8ZdsRCht2w=" + }, "run-queue": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", @@ -5695,6 +5800,11 @@ "repeat-string": "^1.6.1" } }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, "touch": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", @@ -5730,6 +5840,11 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" }, + "tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==" + }, "tty-browserify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", @@ -5763,6 +5878,14 @@ "integrity": "sha512-Veu0w4dTc/9wlWNf2jeRInNodKlcdLgemvPsrNpfu5Pq39sgfFjvIIgTsvUHCoLBnMhPoUA+tFxsXjU6VexVRQ==", "dev": true }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "requires": { + "random-bytes": "~1.0.0" + } + }, "undefsafe": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz", diff --git a/package.json b/package.json index 08e4ccb..cbcf6fc 100644 --- a/package.json +++ b/package.json @@ -25,11 +25,14 @@ "compression": "^1.7.2", "connect-datadog": "^0.0.6", "cookie-parser": "^1.4.3", + "cors": "^2.8.5", + "csurf": "^1.10.0", "express": "^4.16.2", "express-rate-limit": "^3.5.1", "fs-extra": "^7.0.1", "helmet": "^3.18.0", "hot-shots": "^4.7.0", + "hpp": "^0.2.2", "http-status-codes": "^1.3.0", "supertest": "^3.0.0", "tslib": "^1.9.3" @@ -41,10 +44,13 @@ "@types/chai-subset": "1.3.1", "@types/compression": "0.0.35", "@types/cookie-parser": "1.4.1", + "@types/cors": "2.8.5", + "@types/csurf": "1.9.35", "@types/express": "4.11.1", "@types/express-rate-limit": "3.3.0", "@types/fs-extra": "5.0.0", "@types/helmet": "0.0.43", + "@types/hpp": "0.2.1", "@types/mocha": "5.2.5", "@types/sinon-chai": "3.2.2", "@types/supertest": "2.0.4", diff --git a/service/server/ExpressServer.ts b/service/server/ExpressServer.ts index 8adcbab..853a818 100644 --- a/service/server/ExpressServer.ts +++ b/service/server/ExpressServer.ts @@ -4,6 +4,8 @@ import { Server } from 'http' import * as fse from 'fs-extra' import * as compress from 'compression' import * as helmet from 'helmet' +import * as hpp from 'hpp' +import * as cors from 'cors' import * as bodyParser from 'body-parser' import * as cookieParser from 'cookie-parser' import * as RateLimit from 'express-rate-limit' @@ -30,14 +32,15 @@ export class ExpressServer { public async setup(port: number) { const server = express() - this.setupSecurityMiddlewares(server) this.setupStandardMiddlewares(server) + this.setupSecurityMiddlewares(server) this.applyWebpackDevMiddleware(server) this.setupTelemetry(server) this.setupServiceDependencies(server) this.configureEjsTemplates(server) this.configureFrontendPages(server) this.configureApiEndpoints(server) + this.configureFrontendEndpoints(server) this.httpServer = this.listen(server, port) this.server = server @@ -53,6 +56,7 @@ export class ExpressServer { } private setupSecurityMiddlewares(server: Express) { + server.use(hpp()) server.use(helmet()) server.use(helmet.referrerPolicy({ policy: 'same-origin' })) server.use(helmet.noCache()) @@ -162,4 +166,10 @@ export class ExpressServer { server.get('/api/statistics/cat', noCache, strictRateLimit, this.catEndpoints.getCatsStatistics) server.get('/api/cat/:catId', noCache, this.catEndpoints.getCatDetails) } + + private configureFrontendEndpoints(server: Express) { + const forbidExternalFrontends = cors({ origin: false }) + + server.get('/internal/cat', forbidExternalFrontends, noCache, this.catEndpoints.getAllCats) + } }