diff --git a/package.json b/package.json index 2e8eda3..01ab34f 100644 --- a/package.json +++ b/package.json @@ -53,15 +53,17 @@ "axios": "^0.27.2", "bcryptjs": "^2.4.3", "body-parser": "^1.17.1", + "celebrate": "^15.0.1", "class-validator": "^0.13.2", "compression": "^1.6.2", "cors": "^2.8.3", "dotenv": "^16.0.1", "ejs": "^3.1.6", + "escape-html": "^1.0.3", "express": "^4.17.1", "express-async-errors": "^3.1.1", "jsonwebtoken": "^8.5.1", - "mysql": "^2.18.1", + "mysql2": "^2.3.3", "reflect-metadata": "^0.1.13", "source-map-support": "^0.5.19", "tslib": "^2.3.1", @@ -80,6 +82,7 @@ "@types/compression": "^1.7.1", "@types/cors": "^2.8.12", "@types/ejs": "^3.1.0", + "@types/escape-html": "^1.0.2", "@types/express": "^4.17.13", "@types/helmet": "^4.0.0", "@types/jsonwebtoken": "^8.5.8", diff --git a/renovate.json b/renovate.json index 39a2b6e..4bd832f 100644 --- a/renovate.json +++ b/renovate.json @@ -1,6 +1,4 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:base" - ] + "extends": ["config:base"] } diff --git a/src/database/config.ts b/src/database/config.ts index beab6d7..1ca784f 100644 --- a/src/database/config.ts +++ b/src/database/config.ts @@ -8,9 +8,9 @@ const development: DataSourceOptions = { username: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, - ssl: { + /* ssl: { rejectUnauthorized: false, - }, + }, */ synchronize: false, logging: false, entities: ['src/entities/*.ts'], @@ -25,9 +25,9 @@ const production: DataSourceOptions = { username: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, - ssl: { + /* ssl: { rejectUnauthorized: false, - }, + }, */ synchronize: false, logging: false, entities: ['entities/*.js', 'dist/entities/*.js'], diff --git a/src/entities/decorators/isValidPassword.ts b/src/entities/decorators/isValidPassword.ts index 3035924..6db655e 100644 --- a/src/entities/decorators/isValidPassword.ts +++ b/src/entities/decorators/isValidPassword.ts @@ -4,12 +4,12 @@ import { ValidatorConstraint, ValidatorConstraintInterface, } from 'class-validator'; -import { passwordRule } from '../user.entity'; +import { passwordRule } from '@/utils/validators.util'; @ValidatorConstraint() export class IsValidPasswordConstraint implements ValidatorConstraintInterface { validate(password: string) { - const isValidPassword = passwordRule; + const isValidPassword = new RegExp(passwordRule, 'gm'); return isValidPassword.test(password); } } diff --git a/src/entities/user.entity.ts b/src/entities/user.entity.ts index 8b78f69..5ee4929 100644 --- a/src/entities/user.entity.ts +++ b/src/entities/user.entity.ts @@ -10,9 +10,6 @@ import { import { IsEmail, IsNotEmpty, MaxLength, MinLength } from 'class-validator'; import { IsValidPassword } from './decorators/isValidPassword'; -export const passwordRule = - /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/gm; - @Entity('user') export class User { @PrimaryGeneratedColumn('increment') diff --git a/src/middlewares/errorHandler.middleware.ts b/src/middlewares/errorHandler.middleware.ts index 55352ea..6bc5ed2 100644 --- a/src/middlewares/errorHandler.middleware.ts +++ b/src/middlewares/errorHandler.middleware.ts @@ -1,5 +1,9 @@ +/* eslint-disable no-restricted-syntax */ import { Request, Response, NextFunction } from 'express'; -import { AppError } from '../errors/AppError'; +import { isCelebrateError } from 'celebrate'; +import EscapeHtml from 'escape-html'; +import { Error as CustomError } from '@/types/error.type'; +import { AppError } from '@/errors/AppError'; export function errorHandler( error: Error, @@ -11,20 +15,45 @@ export function errorHandler( if (error instanceof AppError) { return response.status(error.statusCode).json({ - status: 'error', + statusCode: error.statusCode, + error: error.name, message: error.message, - }); + stack: error.stack, + } as CustomError); + } + + if (isCelebrateError(error)) { + const validation: { [key: string]: unknown } = {}; + + for (const [segment, joiError] of error.details.entries()) { + validation[segment] = { + source: segment, + keys: joiError.details.map(detail => EscapeHtml(detail.path.join('.'))), + message: joiError.message, + }; + } + + return response.status(400).json({ + statusCode: 400, + error: error.name, + message: error.message, + validation, + stack: error.stack, + } as CustomError); } if (error instanceof Error) { return response.status(500).json({ - status: 'error', + statusCode: 500, + error: error.name, message: error.message, - }); + stack: error.stack, + } as CustomError); } return response.status(500).json({ - status: 'error', - message: 'Internal server error.', - }); + statusCode: 500, + error: 'Internal Server Error', + message: 'An internal server error has occurred, please try again later.', + } as CustomError); } diff --git a/src/routes/authenticate.routes.ts b/src/routes/authenticate.routes.ts index 6d35418..f825e6a 100644 --- a/src/routes/authenticate.routes.ts +++ b/src/routes/authenticate.routes.ts @@ -1,7 +1,23 @@ import { Router } from 'express'; +import { celebrate, Joi, Segments } from 'celebrate'; import { AuthenticateController } from '@/controllers/authenticate.controller'; export const authenticateRouter = Router(); const authenticateController = new AuthenticateController(); -authenticateRouter.post('/auth', authenticateController.execute); +authenticateRouter.post( + '/auth', + celebrate({ + [Segments.BODY]: Joi.object().keys({ + username: Joi.string().required().messages({ + 'string.base': 'Username must be a string.', + 'any.required': 'Username is required.', + }), + password: Joi.string().required().messages({ + 'string.base': 'Password must be a string.', + 'any.required': 'Password is required.', + }), + }), + }), + authenticateController.execute, +); diff --git a/src/routes/users.routes.ts b/src/routes/users.routes.ts index 6693646..84abb54 100644 --- a/src/routes/users.routes.ts +++ b/src/routes/users.routes.ts @@ -1,20 +1,152 @@ import { Router } from 'express'; +import { celebrate, Segments, Joi } from 'celebrate'; import ensureAuthenticated from '@/middlewares/ensureAuthenticated.middleware'; import { UsersController } from '../controllers/user.controller'; +import { passwordRule } from '@/utils/validators.util'; export const usersRouter = Router(); const usersController = new UsersController(); -usersRouter.get('/users', ensureAuthenticated, usersController.index); +usersRouter.get( + '/users', + celebrate({ + [Segments.BODY]: Joi.object().keys({ + name: Joi.string().messages({ + 'string.base': 'Name must be a string.', + }), + username: Joi.string().messages({ + 'string.base': 'Username must be a string.', + }), + email: Joi.string().messages({ + 'string.base': 'Email must be a string.', + }), + isDeleted: Joi.boolean().messages({ + 'boolean.base': 'isDeleted must be a boolean.', + }), + offset: Joi.number().messages({ + 'number.base': 'Offset must be a number.', + }), + isAscending: Joi.boolean().messages({ + 'boolean.base': 'isAscending must be a boolean.', + }), + limit: Joi.number().messages({ + 'number.base': 'Limit must be a number.', + }), + }), + }), + ensureAuthenticated, + usersController.index, +); -usersRouter.post('/users', /* ensureAuthenticated, */ usersController.create); +usersRouter.post( + '/users', + celebrate({ + [Segments.BODY]: Joi.object().keys({ + name: Joi.string().required().messages({ + 'string.base': 'Name must be a string.', + 'any.required': 'Name is required.', + }), + username: Joi.string().required().messages({ + 'string.base': 'Username must be a string.', + 'any.required': 'Username is required.', + }), + email: Joi.string().email().required().messages({ + 'string.base': 'Email must be a string.', + 'any.required': 'Email is required.', + 'string.email': 'Email is invalid.', + }), + password: Joi.string().regex(passwordRule).required().messages({ + 'string.base': 'Password must be a string.', + 'any.required': 'Password is required.', + 'string.pattern.base': + 'Password must be at least 8 characters, at least 1 uppercase letter, 1 lowercase letter, 1 number and 1 special character.', + }), + }), + }), + usersController.create, +); -usersRouter.put('/users', ensureAuthenticated, usersController.update); +usersRouter.put( + '/users', + celebrate({ + [Segments.BODY]: Joi.object().keys({ + id: Joi.string().required().messages({ + 'string.base': 'Id must be a string.', + 'any.required': 'The user id must be provided.', + }), + name: Joi.string().messages({ + 'string.base': 'Name must be a string.', + }), + email: Joi.string().email().messages({ + 'string.email': 'Email is invalid.', + }), + }), + }), + ensureAuthenticated, + usersController.update, +); usersRouter.delete( '/users/remove', + celebrate({ + [Segments.BODY]: Joi.object().keys({ + ids: Joi.string().required().messages({ + 'string.base': 'Ids must be a string.', + 'any.required': 'The user(s) id(s) must be provided.', + }), + }), + }), ensureAuthenticated, usersController.remove, ); -usersRouter.patch('/users/password', usersController.resetPassword); +usersRouter.delete( + '/users/soft-remove', + celebrate({ + [Segments.BODY]: Joi.object().keys({ + ids: Joi.string().required().messages({ + 'string.base': 'Ids must be a string.', + 'any.required': 'The user(s) id(s) must be provided.', + }), + }), + }), + ensureAuthenticated, + usersController.softRemove, +); + +usersRouter.patch( + '/users/recover', + celebrate({ + [Segments.BODY]: Joi.object().keys({ + ids: Joi.string().required().messages({ + 'string.base': 'Ids must be a string.', + 'any.required': 'The user(s) id(s) must be provided.', + }), + }), + }), + ensureAuthenticated, + usersController.recover, +); + +usersRouter.patch( + '/users/password', + celebrate({ + [Segments.BODY]: Joi.object().keys({ + id: Joi.string().required().messages({ + 'string.base': 'Id must be a string.', + 'any.required': 'The user id must be provided.', + }), + currentPassword: Joi.string().required().messages({ + 'string.base': 'Current password must be a string.', + 'any.required': 'The current password must be provided.', + }), + newPassword: Joi.string().regex(passwordRule).required().messages({ + 'string.base': 'New password must be a string.', + 'any.required': 'The new password must be provided.', + 'string.pattern.base': + 'Password must be at least 8 characters, at least 1 uppercase letter, 1 lowercase letter, 1 number and 1 special character.', + }), + }), + }), + usersController.resetPassword, +); diff --git a/src/services/authenticate/authenticate.service.ts b/src/services/authenticate/authenticate.service.ts index 0cb8a93..d040d80 100644 --- a/src/services/authenticate/authenticate.service.ts +++ b/src/services/authenticate/authenticate.service.ts @@ -45,6 +45,10 @@ export class AuthenticateService { await dataSource.manager.save(user); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + delete user.password; + return { user, token }; } } diff --git a/src/services/user/create.service.ts b/src/services/user/create.service.ts index 28c734d..88ef4e2 100644 --- a/src/services/user/create.service.ts +++ b/src/services/user/create.service.ts @@ -1,5 +1,6 @@ import { hash } from 'bcryptjs'; import { validate } from 'class-validator'; +import { CelebrateError } from 'celebrate'; import { dataSource } from '@/database'; import { User } from '@/entities/user.entity'; import { AppError } from '@/errors/AppError'; @@ -18,11 +19,6 @@ export class CreateUserService { email, password, }: Request): Promise { - if (!name || !username || !email || !password) - throw new AppError( - 'Please provide all fields: name, username, email and password.', - ); - const userExists = await dataSource.manager.findOne(User, { where: [{ name }, { email }, { username }], }); @@ -42,7 +38,7 @@ export class CreateUserService { if (error && error.constraints) { const [message] = Object.values(error.constraints); - throw new AppError(message); + throw new CelebrateError(message); } const hashPassword = await hash(password, 8); @@ -51,6 +47,10 @@ export class CreateUserService { await dataSource.manager.save(user); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + delete user.password; + return user; } } diff --git a/src/services/user/find.service.ts b/src/services/user/find.service.ts index b54d489..150da55 100644 --- a/src/services/user/find.service.ts +++ b/src/services/user/find.service.ts @@ -3,16 +3,13 @@ import { dataSource } from '@/database'; import { User } from '@/entities/user.entity'; export interface Request { - name: string; - createdAt: Date; - updatedAt: Date; - deletionDate: Date; - username: string; - email: string; - isDeleted: boolean; - offset: number; - isAscending: boolean; - limit: number; + name?: string; + username?: string; + email?: string; + isDeleted?: boolean; + offset?: number; + isAscending?: boolean; + limit?: number; } export class FindUserService { @@ -49,6 +46,15 @@ export class FindUserService { take: limit, skip: offset, order: { name: isAscending ? 'ASC' : 'DESC' }, + select: [ + 'id', + 'name', + 'username', + 'email', + 'createdAt', + 'updatedAt', + 'deletionDate', + ], }); return users; diff --git a/src/services/user/password.service.ts b/src/services/user/password.service.ts index 9b1f778..3426c2f 100644 --- a/src/services/user/password.service.ts +++ b/src/services/user/password.service.ts @@ -1,7 +1,8 @@ import { compare, hash } from 'bcryptjs'; import { dataSource } from '@/database'; import { AppError } from '@/errors/AppError'; -import { User, passwordRule } from '@/entities/user.entity'; +import { User } from '@/entities/user.entity'; +import { passwordRule } from '@/utils/validators.util'; export interface Request { id: string; @@ -15,13 +16,6 @@ export class PasswordService { currentPassword, newPassword, }: Request): Promise { - if (!id) throw new AppError('The user id must be provided.'); - - if (!currentPassword) - throw new AppError('The current password must be provided.'); - - if (!newPassword) throw new AppError('The new password must be provided.'); - const user = await dataSource.manager.findOne(User, { where: { id }, }); diff --git a/src/services/user/recover.service.ts b/src/services/user/recover.service.ts index 2bcaae2..1bc8a53 100644 --- a/src/services/user/recover.service.ts +++ b/src/services/user/recover.service.ts @@ -9,8 +9,6 @@ export interface Request { export class RecoverUserService { public async execute({ ids }: Request): Promise { - if (!ids) throw new AppError('The user ids must be provided.'); - const userIds = ids.split(','); const users = await dataSource.manager.find(User, { diff --git a/src/services/user/remove.service.ts b/src/services/user/remove.service.ts index dc91912..41fe553 100644 --- a/src/services/user/remove.service.ts +++ b/src/services/user/remove.service.ts @@ -21,8 +21,6 @@ export class RemoveUserService { } public async execute({ ids }: Request): Promise { - if (!ids) throw new AppError('The user ids must be provided.'); - const userIds = ids.split(','); const users = await dataSource.manager.find(User, { diff --git a/src/services/user/update.service.ts b/src/services/user/update.service.ts index b0b3c50..d592c42 100644 --- a/src/services/user/update.service.ts +++ b/src/services/user/update.service.ts @@ -1,4 +1,5 @@ import { validate } from 'class-validator'; +import { CelebrateError } from 'celebrate'; import { dataSource } from '@/database'; import { User } from '@/entities/user.entity'; @@ -14,8 +15,6 @@ export class UpdateUserService { public async execute(userData: Request): Promise { const { id } = userData; - if (!id) throw new AppError('The user id must be provided.'); - const user = await dataSource.manager.findOne(User, { where: { id }, }); @@ -33,11 +32,15 @@ export class UpdateUserService { if (error && error.constraints) { const [message] = Object.values(error.constraints); - throw new AppError(message); + throw new CelebrateError(message); } await dataSource.manager.save(updatedUser); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + delete updatedUser.password; + return updatedUser; } } diff --git a/src/types/error.type.ts b/src/types/error.type.ts new file mode 100644 index 0000000..39d9b28 --- /dev/null +++ b/src/types/error.type.ts @@ -0,0 +1,7 @@ +export type Error = { + statusCode: number; + error: string; + message: string; + stack?: string; + validation?: unknown; +}; diff --git a/src/utils/validators.util.ts b/src/utils/validators.util.ts new file mode 100644 index 0000000..2315a10 --- /dev/null +++ b/src/utils/validators.util.ts @@ -0,0 +1,2 @@ +export const passwordRule = + /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/; diff --git a/yarn.lock b/yarn.lock index 8f10773..9cf075c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1056,6 +1056,18 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" +"@hapi/hoek@^9.0.0": + version "9.3.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" + integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== + +"@hapi/topo@^5.0.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" + integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== + dependencies: + "@hapi/hoek" "^9.0.0" + "@humanwhocodes/config-array@^0.10.4": version "0.10.4" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.10.4.tgz#01e7366e57d2ad104feea63e72248f22015c520c" @@ -1154,6 +1166,23 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@sideway/address@^4.1.3": + version "4.1.4" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.4.tgz#03dccebc6ea47fdc226f7d3d1ad512955d4783f0" + integrity sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@sideway/formula@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.0.tgz#fe158aee32e6bd5de85044be615bc08478a0a13c" + integrity sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg== + +"@sideway/pinpoint@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" + integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== + "@sqltools/formatter@^1.2.2": version "1.2.3" resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.3.tgz#1185726610acc37317ddab11c3c7f9066966bd20" @@ -1231,6 +1260,11 @@ resolved "https://registry.yarnpkg.com/@types/ejs/-/ejs-3.1.1.tgz#29c539826376a65e7f7d672d51301f37ed718f6d" integrity sha512-RQul5wEfY7BjWm0sYY86cmUN/pcXWGyVxWX93DFFJvcrxax5zKlieLwA3T77xJGwNcZW0YW6CYG70p1m8xPFmA== +"@types/escape-html@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/escape-html/-/escape-html-1.0.2.tgz#072b7b13784fb3cee9c2450c22f36405983f5e3c" + integrity sha512-gaBLT8pdcexFztLSPRtriHeXY/Kn4907uOCZ4Q3lncFBkheAWOuNt53ypsF8szgxbEJ513UeBzcf4utN0EzEwA== + "@types/express-serve-static-core@^4.17.18": version "4.17.30" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.30.tgz#0f2f99617fa8f9696170c46152ccf7500b34ac04" @@ -2459,11 +2493,6 @@ bcryptjs@^2.4.3: resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb" integrity sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ== -bignumber.js@9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.0.tgz#805880f84a329b5eac6e7cb6f8274b6d82bdf075" - integrity sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A== - binary-extensions@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" @@ -2606,6 +2635,15 @@ caniuse-lite@^1.0.30001370: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001389.tgz#e4138095d646420e1b6ccf80000bbc94176deabe" integrity sha512-dXCB1Ufi5VekkH5vygd46PGp0HMW4rxorX7Xl1R51FWLcB21DRPOIgrpxvuM1VHXIs4bu/QQQdQaBneMzq9C2g== +celebrate@^15.0.1: + version "15.0.1" + resolved "https://registry.yarnpkg.com/celebrate/-/celebrate-15.0.1.tgz#767fa6268f7446b473ea69cd6326ce4dffee4d1e" + integrity sha512-K2y221k10u+K2t9w25802qXh8h1mVWZf+6pl7zHdlhhwzrOSQFnnw+GsR8k17oyn4Y3fVErBGsO/+CeW8N7aRQ== + dependencies: + escape-html "1.0.3" + joi "17.x.x" + lodash "4.17.x" + chai@^4.3.4: version "4.3.6" resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.6.tgz#ffe4ba2d9fa9d6680cc0b370adae709ec9011e9c" @@ -2962,11 +3000,6 @@ core-js@^3.22.1: resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.25.0.tgz#be71d9e0dd648ffd70c44a7ec2319d039357eceb" integrity sha512-CVU1xvJEfJGhyCpBrzzzU1kjCfgsGUxhEvwUV2e/cOedYWHdmluamx+knDnmhqALddMG16fZvIqvs9aijsHHaA== -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - cors@^2.8.3: version "2.8.5" resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" @@ -3121,6 +3154,11 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== +denque@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" + integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== + depd@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" @@ -3305,7 +3343,7 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== -escape-html@~1.0.3: +escape-html@1.0.3, escape-html@^1.0.3, escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== @@ -3869,6 +3907,13 @@ fuse.js@^3.4.5: resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-3.6.1.tgz#7de85fdd6e1b3377c23ce010892656385fd9b10c" integrity sha512-hT9yh/tiinkmirKrlv4KWOjztdoZo1mx9Qh4KvWqC7isoXwdUY3PNWUxceF4/qO9R6riA2C29jdTOeQOIROjgw== +generate-function@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f" + integrity sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ== + dependencies: + is-property "^1.0.2" + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -4136,6 +4181,13 @@ iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" +iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + ieee754@1.1.13: version "1.1.13" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" @@ -4182,7 +4234,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -4422,6 +4474,11 @@ is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" +is-property@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" + integrity sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g== + is-regex@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" @@ -4489,7 +4546,7 @@ is-windows@^1.0.1: resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== -isarray@^1.0.0, isarray@~1.0.0: +isarray@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== @@ -4519,6 +4576,17 @@ jmespath@0.16.0: resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.16.0.tgz#b15b0a85dfd4d930d43e69ed605943c802785076" integrity sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw== +joi@17.x.x: + version "17.6.0" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.6.0.tgz#0bb54f2f006c09a96e75ce687957bd04290054b2" + integrity sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw== + dependencies: + "@hapi/hoek" "^9.0.0" + "@hapi/topo" "^5.0.0" + "@sideway/address" "^4.1.3" + "@sideway/formula" "^3.0.0" + "@sideway/pinpoint" "^2.0.0" + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -4791,7 +4859,7 @@ lodash.partialright@^4.2.1: resolved "https://registry.yarnpkg.com/lodash.partialright/-/lodash.partialright-4.2.1.tgz#0130d80e83363264d40074f329b8a3e7a8a1cc4b" integrity sha512-yebmPMQZH7i4El6SdJTW9rn8irWl8VTcsmiWqm/I4sY8/ZjbSo0Z512HL6soeAu3mh5rhx5uIIo6kYJOQXbCxw== -lodash@4.17.21, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.3.0: +lodash@4.17.21, lodash@4.17.x, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.3.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -4814,6 +4882,11 @@ log-update@^4.0.0: slice-ansi "^4.0.0" wrap-ansi "^6.2.0" +long@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== + longest@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/longest/-/longest-2.0.1.tgz#781e183296aa94f6d4d916dc335d0d17aefa23f8" @@ -4833,7 +4906,7 @@ loupe@^2.3.1: dependencies: get-func-name "^2.0.0" -lru-cache@^4.0.1: +lru-cache@^4.0.1, lru-cache@^4.1.3: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== @@ -5029,15 +5102,19 @@ mute-stream@0.0.8: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== -mysql@^2.18.1: - version "2.18.1" - resolved "https://registry.yarnpkg.com/mysql/-/mysql-2.18.1.tgz#2254143855c5a8c73825e4522baf2ea021766717" - integrity sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig== +mysql2@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-2.3.3.tgz#944f3deca4b16629052ff8614fbf89d5552545a0" + integrity sha512-wxJUev6LgMSgACDkb/InIFxDprRa6T95+VEoR+xPvtngtccNH2dGjEB/fVZ8yg1gWv1510c9CvXuJHi5zUm0ZA== dependencies: - bignumber.js "9.0.0" - readable-stream "2.3.7" - safe-buffer "5.1.2" - sqlstring "2.3.1" + denque "^2.0.1" + generate-function "^2.3.1" + iconv-lite "^0.6.3" + long "^4.0.0" + lru-cache "^6.0.0" + named-placeholders "^1.1.2" + seq-queue "^0.0.5" + sqlstring "^2.3.2" mz@^2.4.0: version "2.7.0" @@ -5048,6 +5125,13 @@ mz@^2.4.0: object-assign "^4.0.1" thenify-all "^1.0.0" +named-placeholders@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/named-placeholders/-/named-placeholders-1.1.2.tgz#ceb1fbff50b6b33492b5cf214ccf5e39cef3d0e8" + integrity sha512-wiFWqxoLL3PGVReSZpjLVxyJ1bRqe+KKJVbr4hGs1KWfTZTQyezHFBbuKj9hsizHyGV2ne7EMjHdxEGAybD5SA== + dependencies: + lru-cache "^4.1.3" + nanoclone@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/nanoclone/-/nanoclone-0.2.1.tgz#dd4090f8f1a110d26bb32c49ed2f5b9235209ed4" @@ -5467,11 +5551,6 @@ private@^0.1.6, private@^0.1.8: resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - property-expr@^2.0.4: version "2.0.5" resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.5.tgz#278bdb15308ae16af3e3b9640024524f4dc02cb4" @@ -5575,19 +5654,6 @@ read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" -readable-stream@2.3.7: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" @@ -5840,7 +5906,7 @@ rxjs@^7.5.5: dependencies: tslib "^2.1.0" -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@5.1.2, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== @@ -5850,7 +5916,7 @@ safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, s resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -"safer-buffer@>= 2.1.2 < 3": +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -5906,6 +5972,11 @@ send@0.18.0: range-parser "~1.2.1" statuses "2.0.1" +seq-queue@^0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/seq-queue/-/seq-queue-0.0.5.tgz#d56812e1c017a6e4e7c3e3a37a1da6d78dd3c93e" + integrity sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q== + serialize-javascript@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" @@ -6092,10 +6163,10 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== -sqlstring@2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/sqlstring/-/sqlstring-2.3.1.tgz#475393ff9e91479aea62dcaf0ca3d14983a7fb40" - integrity sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ== +sqlstring@^2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/sqlstring/-/sqlstring-2.3.3.tgz#2ddc21f03bce2c387ed60680e739922c65751d0c" + integrity sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg== statuses@2.0.1: version "2.0.1" @@ -6158,13 +6229,6 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - strip-ansi@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -6579,7 +6643,7 @@ url@0.10.3: punycode "1.3.2" querystring "0.2.0" -util-deprecate@^1.0.1, util-deprecate@~1.0.1: +util-deprecate@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==