diff --git a/package.json b/package.json index a17956eb..3437c87d 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "cors": "^2.8.5", "dotenv": "^16.3.1", "express": "^4.18.2", + "express-validator": "^7.0.1", "express-winston": "^4.2.0", "helmet": "^6.0.1", "jsonwebtoken": "^9.0.2", diff --git a/src/constants/index.ts b/src/constants/index.ts index 9a936cd2..3eabc509 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -9,7 +9,16 @@ export type HttpErrorCodes = { MOVED_PERMANENTLY: number; SUPPORT_DOC: number; SERVER_ERROR: number; + UNPROCESSABLE_CONTENT: number; }; + +export type ValidationErrors = { + INVALID_EMAIL: string; + EMAIL_LIMIT: string; + STRING_REQUIRED: string; + INVALID_REGION: string; +}; + export type ConstantType = { CS_REGIONS: Array; AXIOS_TIMEOUT: number; @@ -17,6 +26,7 @@ export type ConstantType = { HTTP_TEXTS: HttpErrorTexts; HTTP_RESPONSE_HEADERS: HttpResponseHeaders; METHODS_TO_INCLUDE_DATA_IN_AXIOS: Array; + VALIDATION_ERRORS: ValidationErrors; }; export type HttpErrorTexts = { @@ -49,6 +59,7 @@ export const constants: ConstantType = { MOVED_PERMANENTLY: 301, SUPPORT_DOC: 294, SERVER_ERROR: 500, + UNPROCESSABLE_CONTENT: 422, }, HTTP_TEXTS: { INTERNAL_ERROR: "Internal server error, please try again later.", @@ -66,4 +77,10 @@ export const constants: ConstantType = { Connection: "close", }, METHODS_TO_INCLUDE_DATA_IN_AXIOS: ["PUT", "POST", "DELETE", "PATCH"], + VALIDATION_ERRORS: { + INVALID_EMAIL: "Given email ID is invalid.", + EMAIL_LIMIT: "Email's max limit reached.", + STRING_REQUIRED: "Provided $ should be a string.", + INVALID_REGION: "Provided region doesn't exists.", + }, }; diff --git a/src/routes/auth.routes.ts b/src/routes/auth.routes.ts index 3532a251..bf162718 100644 --- a/src/routes/auth.routes.ts +++ b/src/routes/auth.routes.ts @@ -1,13 +1,22 @@ import express from "express"; import { authController } from "../controllers/auth.controller"; import { asyncRouter } from "../utils/async-router.utils"; +import validator from "../validators"; const router = express.Router(); // Login route -router.post("/user-session", asyncRouter(authController.login)); +router.post( + "/user-session", + validator("auth"), + asyncRouter(authController.login) +); // SMS token route -router.post("/request-token-sms", asyncRouter(authController.RequestSms)); +router.post( + "/request-token-sms", + validator("auth"), + asyncRouter(authController.RequestSms) +); export default router; diff --git a/src/utils/custom-errors.utils.ts b/src/utils/custom-errors.utils.ts index 82e4e8fb..b5323b2b 100644 --- a/src/utils/custom-errors.utils.ts +++ b/src/utils/custom-errors.utils.ts @@ -27,7 +27,7 @@ export class DatabaseError extends AppError { export class ValidationError extends AppError { constructor(message: string = "User validation error") { - super(constants.HTTP_CODES.FORBIDDEN, message); + super(constants.HTTP_CODES.UNPROCESSABLE_CONTENT, message); } } diff --git a/src/validators/auth.validator.ts b/src/validators/auth.validator.ts new file mode 100644 index 00000000..3add739e --- /dev/null +++ b/src/validators/auth.validator.ts @@ -0,0 +1,67 @@ +import { checkSchema } from "express-validator"; +import { constants } from "../constants"; + +export default checkSchema({ + email: { + in: "body", + isString: { + errorMessage: constants.VALIDATION_ERRORS.STRING_REQUIRED.replace( + "$", + "Email" + ), + bail: true, + }, + isEmail: { + errorMessage: constants.VALIDATION_ERRORS.INVALID_EMAIL, + bail: true, + }, + trim: true, + isLength: { + errorMessage: constants.VALIDATION_ERRORS.EMAIL_LIMIT, + options: { + min: 3, + max: 350, + }, + bail: true, + }, + }, + password: { + in: "body", + isString: { + errorMessage: constants.VALIDATION_ERRORS.STRING_REQUIRED.replace( + "$", + "Password" + ), + bail: true, + }, + trim: true, + }, + region: { + in: "body", + isString: { + errorMessage: constants.VALIDATION_ERRORS.STRING_REQUIRED.replace( + "$", + "Region" + ), + bail: true, + }, + trim: true, + isIn: { + options: [constants.CS_REGIONS], + errorMessage: constants.VALIDATION_ERRORS.INVALID_REGION, + bail: true, + }, + }, + tfa_token: { + optional: true, + in: "body", + isString: { + errorMessage: constants.VALIDATION_ERRORS.STRING_REQUIRED.replace( + "$", + "2FA Token" + ), + bail: true, + }, + trim: true, + }, +}); diff --git a/src/validators/index.ts b/src/validators/index.ts new file mode 100644 index 00000000..8a3f8de5 --- /dev/null +++ b/src/validators/index.ts @@ -0,0 +1,21 @@ +import { Request, NextFunction, Response } from "express"; +import { ValidationError } from "../utils/custom-errors.utils"; +import { asyncRouter } from "../utils/async-router.utils"; +import authValidator from "./auth.validator"; + +export default (route: string = "") => + asyncRouter(async (req: Request, res: Response, next: NextFunction) => { + const appValidators = { + auth: authValidator, + }; + + const validator = appValidators[route as keyof typeof appValidators]; + + const result = (await validator.run(req)) + .map((field) => field.array()) + .reduce((acc, val) => [...acc, ...val], []); + + if (result.length) throw new ValidationError(result[0].msg); + + return next(); + });