From 17a38eafd12949b949672ec3a6c756b0b4f6c6cd Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Wed, 16 Dec 2020 15:37:12 +0200 Subject: [PATCH] feat: validate country code and phone number in user registration. --- server/package.json | 2 + server/src/api/controllers/Authentication.ts | 84 ++++++++++++++++++-- server/src/config/index.js | 12 +++ 3 files changed, 92 insertions(+), 6 deletions(-) diff --git a/server/package.json b/server/package.json index d8398fac5..da28bd13f 100644 --- a/server/package.json +++ b/server/package.json @@ -27,6 +27,7 @@ "bookshelf-modelbase": "^2.10.4", "bookshelf-paranoia": "^0.13.1", "compression": "^1.7.4", + "country-codes-list": "^1.6.8", "crypto-random-string": "^3.2.0", "csurf": "^1.10.0", "deep-map": "^2.0.0", @@ -48,6 +49,7 @@ "knex": "^0.20.3", "knex-cleaner": "^1.3.0", "knex-db-manager": "^0.6.1", + "libphonenumber-js": "^1.9.6", "lodash": "^4.17.15", "memory-cache": "^0.2.0", "moment": "^2.24.0", diff --git a/server/src/api/controllers/Authentication.ts b/server/src/api/controllers/Authentication.ts index 77e6a6b10..54160e6e5 100644 --- a/server/src/api/controllers/Authentication.ts +++ b/server/src/api/controllers/Authentication.ts @@ -1,6 +1,8 @@ import { Request, Response, Router } from 'express'; import { check, ValidationChain } from 'express-validator'; import { Service, Inject } from 'typedi'; +import countries from 'country-codes-list'; +import parsePhoneNumber from 'libphonenumber-js'; import BaseController from 'api/controllers/BaseController'; import asyncMiddleware from 'api/middleware/asyncMiddleware'; import AuthenticationService from 'services/Authentication'; @@ -8,6 +10,7 @@ import { ILoginDTO, ISystemUser, IRegisterOTD } from 'interfaces'; import { ServiceError, ServiceErrors } from "exceptions"; import { DATATYPES_LENGTH } from 'data/DataTypes'; import LoginThrottlerMiddleware from 'api/middleware/LoginThrottlerMiddleware'; +import config from 'config'; @Service() export default class AuthenticationController extends BaseController{ @@ -63,15 +66,84 @@ export default class AuthenticationController extends BaseController{ */ get registerSchema(): ValidationChain[] { return [ - check('first_name').exists().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), - check('last_name').exists().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), - check('email').exists().isString().isEmail().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), - check('phone_number').exists().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), - check('password').exists().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), - check('country').exists().isString().trim().escape().isLength({ max: DATATYPES_LENGTH.STRING }), + check('first_name') + .exists() + .isString() + .trim() + .escape() + .isLength({ max: DATATYPES_LENGTH.STRING }), + check('last_name') + .exists() + .isString() + .trim() + .escape() + .isLength({ max: DATATYPES_LENGTH.STRING }), + check('email') + .exists() + .isString() + .isEmail() + .trim() + .escape() + .isLength({ max: DATATYPES_LENGTH.STRING }), + check('phone_number') + .exists() + .isString() + .trim() + .escape() + .custom(this.phoneNumberValidator) + .isLength({ max: DATATYPES_LENGTH.STRING }), + check('password') + .exists() + .isString() + .trim() + .escape() + .isLength({ max: DATATYPES_LENGTH.STRING }), + check('country') + .exists() + .isString() + .trim() + .escape() + .custom(this.countryValidator) + .isLength({ max: DATATYPES_LENGTH.STRING }), ]; } + /** + * Country validator. + */ + countryValidator(value, { req }) { + const { countries: { whitelist, blacklist } } = config.registration; + const foundCountry = countries.findOne('countryCode', value); + + if (!foundCountry) { + throw new Error('The country code is invalid.'); + } + if ( + // Focus with me! In case whitelist is not empty and the given coutry is not + // in whitelist throw the error. + // + // And in case the blacklist is not empty and the given country exists + // in the blacklist throw the goddamn error. + (whitelist.length > 0 && whitelist.indexOf(value) === -1) && + (blacklist.length > 0 && blacklist.indexOf(value) !== -1) + ) { + throw new Error('The country code is not supported yet.'); + } + return true; + } + + /** + * Phone number validator. + */ + phoneNumberValidator(value, { req }) { + const phoneNumber = parsePhoneNumber(value, req.body.country); + + if (!phoneNumber || !phoneNumber.isValid()) { + throw new Error('Phone number is invalid with the given country code.'); + } + return true; + } + /** * Reset password schema. */ diff --git a/server/src/config/index.js b/server/src/config/index.js index a25409724..23f014317 100644 --- a/server/src/config/index.js +++ b/server/src/config/index.js @@ -144,5 +144,17 @@ export default { duration: 60, blockDuration: 60 * 10, } + }, + + /** + * Users registeration configuration. + */ + registration: { + countries: { + whitelist: [ + 'LY', + ], + blacklist: [], + } } }; \ No newline at end of file