diff --git a/.eslintignore b/.eslintignore index 591f24c4..692fa078 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,4 +2,5 @@ node_modules public docs lib -build \ No newline at end of file +build +dist \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 5075ae39..4f964a26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,9 @@ "config": "^3.3.6", "cors": "^2.8.5", "express": "^4.17.1", + "helmet": "^5.0.2", "js-yaml": "^4.1.0", + "redoc-express": "^1.0.0", "uuid": "^8.3.2", "winston": "^3.3.3" }, @@ -8582,6 +8584,14 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, + "node_modules/helmet": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-5.0.2.tgz", + "integrity": "sha512-QWlwUZZ8BtlvwYVTSDTBChGf8EOcQ2LkGMnQJxSzD1mUu8CCjXJZq/BXP8eWw4kikRnzlhtYo3lCk0ucmYA3Vg==", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/highlight.js": { "version": "10.7.3", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", @@ -16666,6 +16676,11 @@ "esprima": "~4.0.0" } }, + "node_modules/redoc-express": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redoc-express/-/redoc-express-1.0.0.tgz", + "integrity": "sha512-N/0/WrA6U2rJc9hzLy2Jh2CHrQttIcW02jE4xGCiKkU40uTlpFTcEfu9bpsJlTcpffPXVNhhaIbTwz8kxz+M5A==" + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -26122,6 +26137,11 @@ } } }, + "helmet": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-5.0.2.tgz", + "integrity": "sha512-QWlwUZZ8BtlvwYVTSDTBChGf8EOcQ2LkGMnQJxSzD1mUu8CCjXJZq/BXP8eWw4kikRnzlhtYo3lCk0ucmYA3Vg==" + }, "highlight.js": { "version": "10.7.3", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", @@ -32193,6 +32213,11 @@ "esprima": "~4.0.0" } }, + "redoc-express": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redoc-express/-/redoc-express-1.0.0.tgz", + "integrity": "sha512-N/0/WrA6U2rJc9hzLy2Jh2CHrQttIcW02jE4xGCiKkU40uTlpFTcEfu9bpsJlTcpffPXVNhhaIbTwz8kxz+M5A==" + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", diff --git a/package.json b/package.json index 3fbc85c2..a17d8580 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "express": "^4.17.1", "helmet": "^5.0.2", "js-yaml": "^4.1.0", + "redoc-express": "^1.0.0", "uuid": "^8.3.2", "winston": "^3.3.3" }, diff --git a/src/app.ts b/src/app.ts index 9941a97c..66519d2d 100644 --- a/src/app.ts +++ b/src/app.ts @@ -11,6 +11,7 @@ import { requestBodyValidationMiddleware } from './middlewares/request-body-vali import { problemMiddleware } from './middlewares/problem.middleware'; import { logger } from './utils/logger'; +import { API_VERSION } from './constants'; export class App { private app: express.Application; @@ -53,7 +54,17 @@ export class App { this.app.use(bodyParser.text({ type: ['text/*'], limit: requestBodyLimit })); this.app.use(bodyParser.urlencoded({ extended: true, limit: requestBodyLimit })); this.app.use(bodyParser.json({ type: ['json', '*/json', '+json'], limit: requestBodyLimit })); - this.app.use(helmet()); + this.app.use(helmet({ + contentSecurityPolicy: { + directives: { + // for `/docs` path - we need to fetch redoc component from unpkg.com domain + 'script-src': ['\'self\'', 'unpkg.com'], + 'worker-src': ['\'self\' blob:'] + }, + }, + // for `/docs` path + crossOriginEmbedderPolicy: false, + })); } private initializeValidation() { @@ -63,7 +74,7 @@ export class App { private initializeControllers(controller: Controller[]) { controller.forEach(controller => { // in the `openapi.yaml` we have prefix `v1` for all paths - this.app.use('/v1/', controller.boot()); + this.app.use(`/${API_VERSION}/`, controller.boot()); }); } diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 00000000..4f65a9a4 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1 @@ +export const API_VERSION = 'v1'; \ No newline at end of file diff --git a/src/controllers/docs.controller.ts b/src/controllers/docs.controller.ts new file mode 100644 index 00000000..48d99f2b --- /dev/null +++ b/src/controllers/docs.controller.ts @@ -0,0 +1,28 @@ +import { Router } from 'express'; +import redoc from 'redoc-express'; + +import { Controller } from '../interfaces'; + +import { API_VERSION } from '../constants'; + +export class DocsController implements Controller { + public basepath = '/docs'; + + public boot(): Router { + const router = Router(); + + router.get(`${this.basepath}/openapi.yaml`, (_, res) => { + res.sendFile('openapi.yaml', { root: '.' }); + }); + + router.get( + this.basepath, + redoc({ + title: 'OpenAPI Documentation', + specUrl: `/${API_VERSION}${this.basepath}/openapi.yaml` + }), + ); + + return router; + } +} diff --git a/src/server.ts b/src/server.ts index 5770aec3..8ea99bff 100644 --- a/src/server.ts +++ b/src/server.ts @@ -7,9 +7,11 @@ process.env['NODE_CONFIG_DIR'] = `${__dirname }/configs`; import { App } from './app'; import { GenerateController } from './controllers/generate.controller'; import { ValidateController } from './controllers/validate.controller'; +import { DocsController } from './controllers/docs.controller'; const app = new App([ new GenerateController(), - new ValidateController() + new ValidateController(), + new DocsController() ]); app.listen();