diff --git a/config.ts b/config.ts index 4b03c18..d0c68cd 100644 --- a/config.ts +++ b/config.ts @@ -40,6 +40,17 @@ const packageDescriptionGetter = (): string => { : 'service evaluate open api'; }; +/** + * @description Cors white lists + * @private + * @param {string} env + * @returns {boolean | string[]} + */ +const corsWhiteLists = (env: string): boolean | string[] => { + if (env === 'production' || env === 'stage') return [process.env.WEBDOMAIN, process.env.IOSDOMAIN, process.env.ANDRIODDOMAIN]; + return true; +} + // load config dotenv.config(); @@ -59,6 +70,7 @@ const configs = { HOST: process.env.APPHOST || 'localhost', PORT: process.env.APPPORT || 8080, WSPORT: process.env.WSPORT || 84, + CORSORIGIN: corsWhiteLists(env), EVENT_STORE_SETTINGS: { protocol: process.env.EVENTSTOREPROTOCOL || 'http', diff --git a/package-lock.json b/package-lock.json index c70dada..4a6841c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1407,6 +1407,15 @@ "@types/node": "*" } }, + "@types/helmet": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/helmet/-/helmet-4.0.0.tgz", + "integrity": "sha512-ONIn/nSNQA57yRge3oaMQESef/6QhoeX7llWeDli0UZIfz8TQMkfNPTXA8VnnyeA1WUjG2pGqdjEIueYonMdfQ==", + "dev": true, + "requires": { + "helmet": "*" + } + }, "@types/ioredis": { "version": "4.17.9", "resolved": "https://registry.npmjs.org/@types/ioredis/-/ioredis-4.17.9.tgz", @@ -1480,6 +1489,15 @@ "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==", "dev": true }, + "@types/morgan": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.2.tgz", + "integrity": "sha512-edtGMEdit146JwwIeyQeHHg9yID4WSolQPxpEorHmN3KuytuCHyn2ELNr5Uxy8SerniFbbkmgKMrGM933am5BQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/node": { "version": "13.13.36", "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.36.tgz", @@ -2486,6 +2504,14 @@ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "requires": { + "safe-buffer": "5.1.2" + } + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -4959,6 +4985,11 @@ } } }, + "helmet": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-4.4.1.tgz", + "integrity": "sha512-G8tp0wUMI7i8wkMk2xLcEvESg5PiCitFMYgGRc/PwULB0RVhTP5GFdxOwvJwp9XVha8CuS8mnhmE8I/8dx/pbw==" + }, "highlight.js": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.4.1.tgz", @@ -7384,6 +7415,25 @@ "minimist": "^1.2.5" } }, + "morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "requires": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + } + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -7778,6 +7828,11 @@ "ee-first": "1.1.1" } }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", diff --git a/package.json b/package.json index b63c190..7fc5ebd 100644 --- a/package.json +++ b/package.json @@ -56,8 +56,10 @@ "fastify-cors": "^4.1.0", "fastify-swagger": "^3.5.0", "grpc": "^1.24.4", + "helmet": "^4.4.1", "ioredis": "^4.19.2", "lodash": "^4.17.20", + "morgan": "^1.10.0", "node-rdkafka": "^2.10.1", "pg": "^8.5.1", "reflect-metadata": "^0.1.13", @@ -73,9 +75,11 @@ "@nestjs/schematics": "^7.0.0", "@nestjs/testing": "^7.0.0", "@types/express": "^4.17.3", + "@types/helmet": "^4.0.0", "@types/ioredis": "^4.17.7", "@types/jest": "26.0.10", "@types/lodash": "^4.14.165", + "@types/morgan": "^1.9.2", "@types/node": "^13.9.1", "@types/proxy-addr": "^2.0.0", "@types/request-promise": "^4.1.46", diff --git a/src/app.module.ts b/src/app.module.ts index 051a753..4182381 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,6 +1,7 @@ import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common'; import { GatewayModule } from './gateways/gateway.module'; -import { RateMiddleware } from 'middlewares/rate-limit'; +import { RateMiddleware } from './middlewares/rate-limit'; +import { AuthMiddleware } from './middlewares/auth.service'; import { ChatSocketGateway } from './sockets/chat.gateway'; import { ChatSocketService } from './sockets/chat.service'; import { ChatConsumerService } from './consumers/chat.consumer'; @@ -13,5 +14,6 @@ import { ChatMessageRoutingService } from './handlers/chat.handler'; export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer.apply(RateMiddleware).forRoutes({ path: '*', method: RequestMethod.ALL }); + consumer.apply(AuthMiddleware).forRoutes({ path: '*', method: RequestMethod.ALL }); } } diff --git a/src/main.ts b/src/main.ts index c1647f7..7f88c9e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,16 +1,22 @@ import { Logger, ValidationPipe } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { NestFastifyApplication } from '@nestjs/platform-fastify'; +import { Transport } from '@nestjs/microservices'; +import helmet from 'helmet'; +import morgan from 'morgan'; import { AppModule } from './app.module'; import { config } from '../config'; -import { Transport } from '@nestjs/microservices'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.connectMicroservice({ transport: Transport.TCP, }); - + app.use( + morgan('combined', { + skip: (req, res) => res.statusCode < 400, + }), + ); app.useGlobalPipes( new ValidationPipe({ skipMissingProperties: true, @@ -21,9 +27,11 @@ async function bootstrap() { app.setGlobalPrefix(`${config.PREFIX}${config.API_EXPLORER_PATH}`); app.enableCors({ credentials: true, - origin: true, + origin: config.CORSORIGIN, methods: ['GET', 'POST', 'PUT', 'DELETE'], + preflightContinue: true, }); + app.use(helmet()); app.startAllMicroservices(); await app.listen(config.PORT); Logger.log(`Server start on ${config.HOST}:${config.PORT}`, 'Bootstrap', true); diff --git a/src/middlewares/auth.service.ts b/src/middlewares/auth.service.ts index 4f8df76..cae24c2 100644 --- a/src/middlewares/auth.service.ts +++ b/src/middlewares/auth.service.ts @@ -1,41 +1,41 @@ -import { HttpException, HttpStatus, Injectable, Logger, NestMiddleware, UnauthorizedException } from '@nestjs/common'; +import { Injectable, Logger, NestMiddleware } from '@nestjs/common'; import { Request, Response, NextFunction } from 'express'; import { config } from '../../config'; -import { APIRequestFactory } from '../libs/request-factory'; @Injectable() -export class AuthService implements NestMiddleware { +export class AuthMiddleware implements NestMiddleware { private logger: Logger = new Logger('AuthService'); /** * @description Auth validation Handler + * @public * @param {Request} req * @param {Response} res * @param {NextFunction} next - * @returns {Promise} + * @returns {void | Response} */ - public async use(req: Request, res: Response, next: NextFunction): Promise { + public use(req: Request, res: Response, next: NextFunction): void | Response { + if (!this.originHandling(req)) { + this.logger.error(`${req.ip} from ${req.hostname} send malware request with wrong origin`, '', 'UserOriginError'); + return res.sendStatus(403); + } // check if routes is exception or not if (this.exceptRoutes(req.baseUrl)) return next(); // check token - if (!req.headers.authorization) return res.sendStatus(403); - try { - // get user data from user sercice - const response = await this.requestUesr(req.headers.authorization); - if (response.statusCode !== 200) return res.sendStatus(403); - next(); - } catch (error) { - this.logger.log(error.message, 'Auth-Err'); - return res.status(403).json({ status: 'error', message: error.message }); + if (!req.headers.authorization) { + this.logger.error(`${req.ip} from ${req.hostname} send malware request with token`, '', 'TokenError'); + return res.sendStatus(403); } + next(); } /** * @description Handle Exception Routes which don't need to auth verify + * @private * @param {string} routes * @returns {boolean} */ - protected exceptRoutes(routes: string): boolean { + private exceptRoutes(routes: string): boolean { for (let i = 0; i < config.MS_EXCEPT.length; i++) { if (routes.indexOf(config.MS_EXCEPT[i]) >= 0) return true; } @@ -43,24 +43,16 @@ export class AuthService implements NestMiddleware { } /** - * @description Request user info by token to identify if it's validate user - * @param {string} token - * @returns {Promise} + * @description Origin header handling due to Nestjs CORs has issued for handling + * @private + * @param {Request} req + * @returns {void} */ - protected async requestUesr(token: string): Promise { - try { - const service = config.MS_SETTINGS[0]; - return await APIRequestFactory.createRequest('standard').makeRequest({ - url: `http://${service.host}:${service.port}/users/info`, - method: 'GET', - headers: { - Authorization: token, - }, - json: true, - }); - } catch (error) { - this.logger.log(error.message, 'Auth-Request-Err'); - throw new Error(error.message); - } + private originHandling(req: Request): boolean { + const whiteLists: boolean | string[] = config.CORSORIGIN; + if (typeof whiteLists === 'boolean') return true; + if (!req.headers.origin) return false; + if (whiteLists.indexOf(req.headers.origin) !== -1) return true; + return false; } }