Browse Source

eslint and prettier added

master
krish 10 months ago
parent
commit
f5a5955656
30 changed files with 1471 additions and 818 deletions
  1. +27
    -0
      .eslintrc.js
  2. +7
    -0
      .prettierrc
  3. +23
    -24
      README.md
  4. +1
    -1
      docker-compose.dev.yml
  5. +1
    -1
      docker-compose.prod.yml
  6. +1
    -1
      docker-compose.test.yml
  7. +3
    -3
      docker-compose.yml
  8. +290
    -60
      package-lock.json
  9. +18
    -12
      package.json
  10. +7
    -2
      src/api/controllers/auth.controller.js
  11. +8
    -7
      src/api/controllers/user.controller.js
  12. +10
    -8
      src/api/middlewares/auth.js
  13. +5
    -4
      src/api/models/passwordResetToken.model.js
  14. +4
    -3
      src/api/models/refreshToken.model.js
  15. +62
    -53
      src/api/models/user.model.js
  16. +12
    -15
      src/api/routes/v1/auth.route.js
  17. +1
    -5
      src/api/routes/v1/user.route.js
  18. +2
    -6
      src/api/services/authProviders.js
  19. +28
    -22
      src/api/tests/integration/auth.test.js
  20. +16
    -8
      src/api/tests/integration/user.test.js
  21. +6
    -4
      src/api/utils/APIError.js
  22. +755
    -491
      src/api/utils/passwordResetEmailTemplate.html
  23. +8
    -26
      src/api/validations/auth.validation.js
  24. +8
    -5
      src/api/validations/user.validation.js
  25. +3
    -3
      src/config/express.js
  26. +5
    -3
      src/config/logger.js
  27. +1
    -1
      src/config/passport.js
  28. +6
    -3
      src/config/vars.js
  29. +3
    -3
      src/index.js
  30. +150
    -44
      yarn.lock

+ 27
- 0
.eslintrc.js View File

@ -0,0 +1,27 @@
module.exports = {
env: {
browser: true,
commonjs: true,
es2021: true,
},
extends: 'standard',
plugins: ['prettier'],
overrides: [
{
env: {
node: true,
},
files: ['.eslintrc.{js,cjs}'],
parserOptions: {
sourceType: 'script',
},
},
],
parserOptions: {
ecmaVersion: 'latest',
},
rules: {
'comma-dangle': ['error', 'never'],
'no-extra-semi': ['error', 'never'],
},
};

+ 7
- 0
.prettierrc View File

@ -0,0 +1,7 @@
{
"tabWidth": 2,
"singleQuote": true,
"semi": true,
"trailingComma": "all",
"endOfLine": "lf"
}

+ 23
- 24
README.md View File

@ -1,40 +1,40 @@
## Important Information
This repository is exclusively meant for presenting samples of code.
The Files and Codes present in this repository are part of a running project. In order to ensure the security of the project several files have been intentionally removed. The codes are exclusively for viewing purpose and will not execute properly if tried.
# Express ES2017 REST API Boilerplate
Boilerplate/Generator/Starter Project for building RESTful APIs and microservices using Node.js, Express and MongoDB
## Features
- No transpilers, just vanilla javascript
- ES2017 latest features like Async/Await
- CORS enabled
- Uses [yarn](https://yarnpkg.com)
- Express + MongoDB ([Mongoose](http://mongoosejs.com/))
- Consistent coding styles with [editorconfig](http://editorconfig.org)
- [Docker](https://www.docker.com/) support
- Uses [helmet](https://github.com/helmetjs/helmet) to set some HTTP headers for security
- Load environment variables from .env files with [dotenv](https://github.com/rolodato/dotenv-safe)
- Request validation with [joi](https://github.com/hapijs/joi)
- Gzip compression with [compression](https://github.com/expressjs/compression)
- Linting with [eslint](http://eslint.org)
- Tests with [mocha](https://mochajs.org), [chai](http://chaijs.com) and [sinon](http://sinonjs.org)
- Code coverage with [istanbul](https://istanbul.js.org) and [coveralls](https://coveralls.io)
- Git hooks with [husky](https://github.com/typicode/husky)
- Logging with [morgan](https://github.com/expressjs/morgan)
- Authentication and Authorization with [passport](http://passportjs.org)
- API documentation generation with [apidoc](http://apidocjs.com)
- Continuous integration support with [travisCI](https://travis-ci.org)
- Monitoring with [pm2](https://github.com/Unitech/pm2)
- No transpilers, just vanilla javascript
- ES2017 latest features like Async/Await
- CORS enabled
- Uses [yarn](https://yarnpkg.com)
- Express + MongoDB ([Mongoose](http://mongoosejs.com/))
- Consistent coding styles with [editorconfig](http://editorconfig.org)
- [Docker](https://www.docker.com/) support
- Uses [helmet](https://github.com/helmetjs/helmet) to set some HTTP headers for security
- Load environment variables from .env files with [dotenv](https://github.com/rolodato/dotenv-safe)
- Request validation with [joi](https://github.com/hapijs/joi)
- Gzip compression with [compression](https://github.com/expressjs/compression)
- Linting with [eslint](http://eslint.org)
- Tests with [mocha](https://mochajs.org), [chai](http://chaijs.com) and [sinon](http://sinonjs.org)
- Code coverage with [istanbul](https://istanbul.js.org) and [coveralls](https://coveralls.io)
- Git hooks with [husky](https://github.com/typicode/husky)
- Logging with [morgan](https://github.com/expressjs/morgan)
- Authentication and Authorization with [passport](http://passportjs.org)
- API documentation generation with [apidoc](http://apidocjs.com)
- Continuous integration support with [travisCI](https://travis-ci.org)
- Monitoring with [pm2](https://github.com/Unitech/pm2)
## Requirements
- [Node v7.6+](https://nodejs.org/en/download/current/) or [Docker](https://www.docker.com/)
- [Yarn](https://yarnpkg.com/en/docs/install)
- [Node v7.6+](https://nodejs.org/en/download/current/) or [Docker](https://www.docker.com/)
- [Yarn](https://yarnpkg.com/en/docs/install)
## Getting Started
@ -155,4 +155,3 @@ Run deploy script:
```bash
yarn deploy
```

+ 1
- 1
docker-compose.dev.yml View File

@ -1,4 +1,4 @@
version: "2"
version: '2'
services:
boilerplate-api:
command: yarn dev -- -L


+ 1
- 1
docker-compose.prod.yml View File

@ -1,4 +1,4 @@
version: "2"
version: '2'
services:
boilerplate-api:
command: yarn start


+ 1
- 1
docker-compose.test.yml View File

@ -1,4 +1,4 @@
version: "2"
version: '2'
services:
boilerplate-api:
command: yarn test


+ 3
- 3
docker-compose.yml View File

@ -1,4 +1,4 @@
version: "2"
version: '2'
services:
boilerplate-api:
build: .
@ -7,11 +7,11 @@ services:
volumes:
- .:/app
ports:
- "3000:3000"
- '3000:3000'
depends_on:
- mongodb
mongodb:
image: mongo
ports:
- "27017:27017"
- '27017:27017'

+ 290
- 60
package-lock.json View File

@ -14,8 +14,8 @@
"body-parser": "^1.19.1",
"compression": "^1.7.4",
"cors": "^2.8.5",
"cross-env": "^7.0.3",
"dotenv": "^16.4.5",
"dotenv-cli": "^7.4.1",
"email-templates": "^11.1.1",
"express": "^4.17.2",
"express-validation": "^4.1.0",
@ -24,6 +24,7 @@
"joi": "^17.5.0",
"jwt-simple": "^0.5.6",
"method-override": "^3.0.0",
"moment-timezone": "^0.5.45",
"mongoose": "^8.2.1",
"morgan": "^1.10.0",
"nodemailer": "^6.7.2",
@ -40,14 +41,18 @@
"chai": "^4.4.1",
"chai-as-promised": "^7.1.1",
"coveralls": "^3.1.1",
"eslint": "^8.5.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.25.3",
"eslint": "^8.57.0",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-n": "^16.6.2",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-promise": "^6.1.1",
"husky": "^9.0.11",
"mocha": "^10.3.0",
"nodemon": "^3.1.0",
"nyc": "^15.1.0",
"opn-cli": "^5.0.0",
"prettier": "^3.2.5",
"sinon": "^17.0.1",
"sinon-chai": "^3.7.0",
"supertest": "^6.1.6"
@ -1341,6 +1346,18 @@
"uuid": "bin/uuid"
}
},
"node_modules/@pkgr/core": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz",
"integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==",
"dev": true,
"engines": {
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/unts"
}
},
"node_modules/@pm2/agent": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@pm2/agent/-/agent-2.0.3.tgz",
@ -2898,6 +2915,27 @@
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
},
"node_modules/builtin-modules": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
"integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
"dev": true,
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/builtins": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz",
"integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==",
"dev": true,
"dependencies": {
"semver": "^7.0.0"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@ -3369,12 +3407,6 @@
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"node_modules/confusing-browser-globals": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz",
"integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==",
"dev": true
},
"node_modules/constantinople": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz",
@ -3498,23 +3530,6 @@
"resolved": "https://registry.npmjs.org/croner/-/croner-4.1.97.tgz",
"integrity": "sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ=="
},
"node_modules/cross-env": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
"dependencies": {
"cross-spawn": "^7.0.1"
},
"bin": {
"cross-env": "src/bin/cross-env.js",
"cross-env-shell": "src/bin/cross-env-shell.js"
},
"engines": {
"node": ">=10.14",
"npm": ">=6",
"yarn": ">=1"
}
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -3891,6 +3906,28 @@
"url": "https://dotenvx.com"
}
},
"node_modules/dotenv-cli": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-7.4.1.tgz",
"integrity": "sha512-fE1aywjRrWGxV3miaiUr3d2zC/VAiuzEGghi+QzgIA9fEf/M5hLMaRSXb4IxbUAwGmaLi0IozdZddnVU96acag==",
"dependencies": {
"cross-spawn": "^7.0.3",
"dotenv": "^16.3.0",
"dotenv-expand": "^10.0.0",
"minimist": "^1.2.6"
},
"bin": {
"dotenv": "cli.js"
}
},
"node_modules/dotenv-expand": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz",
"integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==",
"engines": {
"node": ">=12"
}
},
"node_modules/ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@ -4352,32 +4389,45 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-config-airbnb-base": {
"version": "15.0.0",
"resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz",
"integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==",
"node_modules/eslint-compat-utils": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.1.2.tgz",
"integrity": "sha512-Jia4JDldWnFNIru1Ehx1H5s9/yxiRHY/TimCuUc0jNexew3cF1gI6CYZil1ociakfWO3rRqFjl1mskBblB3RYg==",
"dev": true,
"dependencies": {
"confusing-browser-globals": "^1.0.10",
"object.assign": "^4.1.2",
"object.entries": "^1.1.5",
"semver": "^6.3.0"
},
"engines": {
"node": "^10.12.0 || >=12.0.0"
"node": ">=12"
},
"peerDependencies": {
"eslint": "^7.32.0 || ^8.2.0",
"eslint-plugin-import": "^2.25.2"
"eslint": ">=6.0.0"
}
},
"node_modules/eslint-config-airbnb-base/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"node_modules/eslint-config-standard": {
"version": "17.1.0",
"resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz",
"integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==",
"dev": true,
"bin": {
"semver": "bin/semver.js"
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"eslint": "^8.0.1",
"eslint-plugin-import": "^2.25.2",
"eslint-plugin-n": "^15.0.0 || ^16.0.0 ",
"eslint-plugin-promise": "^6.0.0"
}
},
"node_modules/eslint-import-resolver-node": {
@ -4438,6 +4488,26 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true
},
"node_modules/eslint-plugin-es-x": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.5.0.tgz",
"integrity": "sha512-ODswlDSO0HJDzXU0XvgZ3lF3lS3XAZEossh15Q2UHjwrJggWeBoKqqEsLTZLXl+dh5eOAozG0zRcYtuE35oTuQ==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.1.2",
"@eslint-community/regexpp": "^4.6.0",
"eslint-compat-utils": "^0.1.2"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ota-meshi"
},
"peerDependencies": {
"eslint": ">=8"
}
},
"node_modules/eslint-plugin-import": {
"version": "2.29.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz",
@ -4505,6 +4575,76 @@
"semver": "bin/semver.js"
}
},
"node_modules/eslint-plugin-n": {
"version": "16.6.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.6.2.tgz",
"integrity": "sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"builtins": "^5.0.1",
"eslint-plugin-es-x": "^7.5.0",
"get-tsconfig": "^4.7.0",
"globals": "^13.24.0",
"ignore": "^5.2.4",
"is-builtin-module": "^3.2.1",
"is-core-module": "^2.12.1",
"minimatch": "^3.1.2",
"resolve": "^1.22.2",
"semver": "^7.5.3"
},
"engines": {
"node": ">=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/mysticatea"
},
"peerDependencies": {
"eslint": ">=7.0.0"
}
},
"node_modules/eslint-plugin-prettier": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz",
"integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==",
"dev": true,
"dependencies": {
"prettier-linter-helpers": "^1.0.0",
"synckit": "^0.8.6"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint-plugin-prettier"
},
"peerDependencies": {
"@types/eslint": ">=8.0.0",
"eslint": ">=8.0.0",
"eslint-config-prettier": "*",
"prettier": ">=3.0.0"
},
"peerDependenciesMeta": {
"@types/eslint": {
"optional": true
},
"eslint-config-prettier": {
"optional": true
}
}
},
"node_modules/eslint-plugin-promise": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz",
"integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"peerDependencies": {
"eslint": "^7.0.0 || ^8.0.0"
}
},
"node_modules/eslint-scope": {
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
@ -4876,6 +5016,12 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true
},
"node_modules/fast-diff": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
"dev": true
},
"node_modules/fast-json-patch": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz",
@ -5361,6 +5507,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-tsconfig": {
"version": "4.7.3",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.3.tgz",
"integrity": "sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==",
"dev": true,
"dependencies": {
"resolve-pkg-maps": "^1.0.0"
},
"funding": {
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
"node_modules/get-uri": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz",
@ -6117,6 +6275,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-builtin-module": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz",
"integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==",
"dev": true,
"dependencies": {
"builtin-modules": "^3.3.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-callable": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
@ -7673,6 +7846,25 @@
"resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz",
"integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A=="
},
"node_modules/moment": {
"version": "2.30.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
"engines": {
"node": "*"
}
},
"node_modules/moment-timezone": {
"version": "0.5.45",
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.45.tgz",
"integrity": "sha512-HIWmqA86KcmCAhnMAN0wuDOARV/525R2+lOLotuGFzn4HO+FH+/645z2wx0Dt3iDv6/p61SIvKnDstISainhLQ==",
"dependencies": {
"moment": "^2.29.4"
},
"engines": {
"node": "*"
}
},
"node_modules/mongodb": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.3.0.tgz",
@ -8393,20 +8585,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/object.entries": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz",
"integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==",
"dev": true,
"dependencies": {
"call-bind": "^1.0.2",
"define-properties": "^1.2.0",
"es-abstract": "^1.22.1"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/object.fromentries": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz",
@ -9288,6 +9466,33 @@
"node": ">= 0.8.0"
}
},
"node_modules/prettier": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
"dev": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prettier-linter-helpers": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
"dev": true,
"dependencies": {
"fast-diff": "^1.1.2"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/preview-email": {
"version": "3.0.19",
"resolved": "https://registry.npmjs.org/preview-email/-/preview-email-3.0.19.tgz",
@ -10040,6 +10245,15 @@
"node": ">=4"
}
},
"node_modules/resolve-pkg-maps": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
"dev": true,
"funding": {
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
"node_modules/reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@ -10897,6 +11111,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/synckit": {
"version": "0.8.8",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz",
"integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==",
"dev": true,
"dependencies": {
"@pkgr/core": "^0.1.0",
"tslib": "^2.6.2"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/unts"
}
},
"node_modules/systeminformation": {
"version": "5.22.0",
"resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.22.0.tgz",


+ 18
- 12
package.json View File

@ -11,15 +11,16 @@
"scripts": {
"precommit": "yarn lint",
"prestart": "yarn docs",
"start": "cross-env NODE_ENV=production pm2 start ./src/index.js",
"dev": "nodemon ./src/index.js",
"start:dev": "dotenv -v NODE_ENV=development -- nodemon ./src/index.js",
"start:prod": "dotenv -v NODE_ENV=production -- pm2 start ./src/index.js",
"lint": "eslint ./src/ --ignore-path .gitignore --ignore-pattern internals/scripts",
"lint:fix": "yarn lint --fix",
"lint:watch": "yarn lint --watch",
"test": "cross-env NODE_ENV=test nyc --reporter=html --reporter=text mocha --timeout 20000 --exit --recursive src/api/tests",
"test:unit": "cross-env NODE_ENV=test mocha src/api/tests/unit",
"test:integration": "cross-env NODE_ENV=test mocha --timeout 20000 --exit src/api/tests/integration",
"test:watch": "cross-env NODE_ENV=test mocha --watch src/api/tests/unit",
"lint:fix": "npx eslint --fix",
"lint:watch": "npx eslint --watch",
"format": "npx prettier --write .",
"test": "dotenv -v NODE_ENV=test -- nyc --reporter=html --reporter=text mocha --timeout 20000 --exit --recursive src/api/tests",
"test:unit": "dotenv -v NODE_ENV=test -- mocha src/api/tests/unit",
"test:integration": "dotenv -v NODE_ENV=test -- mocha --timeout 20000 --exit src/api/tests/integration",
"test:watch": "dotenv -v NODE_ENV=test -- mocha --watch src/api/tests/unit",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"postcoverage": "open-cli coverage/lcov-report/index.html",
"validate": "yarn lint && yarn test",
@ -66,8 +67,8 @@
"body-parser": "^1.19.1",
"compression": "^1.7.4",
"cors": "^2.8.5",
"cross-env": "^7.0.3",
"dotenv": "^16.4.5",
"dotenv-cli": "^7.4.1",
"email-templates": "^11.1.1",
"express": "^4.17.2",
"express-validation": "^4.1.0",
@ -76,6 +77,7 @@
"joi": "^17.5.0",
"jwt-simple": "^0.5.6",
"method-override": "^3.0.0",
"moment-timezone": "^0.5.45",
"mongoose": "^8.2.1",
"morgan": "^1.10.0",
"nodemailer": "^6.7.2",
@ -92,14 +94,18 @@
"chai": "^4.4.1",
"chai-as-promised": "^7.1.1",
"coveralls": "^3.1.1",
"eslint": "^8.5.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.25.3",
"eslint": "^8.57.0",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-n": "^16.6.2",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-promise": "^6.1.1",
"husky": "^9.0.11",
"mocha": "^10.3.0",
"nodemon": "^3.1.0",
"nyc": "^15.1.0",
"opn-cli": "^5.0.0",
"prettier": "^3.2.5",
"sinon": "^17.0.1",
"sinon-chai": "^3.7.0",
"supertest": "^6.1.6"


+ 7
- 2
src/api/controllers/auth.controller.js View File

@ -84,7 +84,10 @@ exports.refresh = async (req, res, next) => {
userEmail: email,
token: refreshToken,
});
const { user, accessToken } = await User.findAndGenerateToken({ email, refreshObject });
const { user, accessToken } = await User.findAndGenerateToken({
email,
refreshObject,
});
const response = generateTokenResponse(user, accessToken);
return res.json(response);
} catch (error) {
@ -133,7 +136,9 @@ exports.resetPassword = async (req, res, next) => {
throw new APIError(err);
}
const user = await User.findOne({ email: resetTokenObject.userEmail }).exec();
const user = await User.findOne({
email: resetTokenObject.userEmail,
}).exec();
user.password = password;
await user.save();
emailProvider.sendPasswordChangeEmail(user);


+ 8
- 7
src/api/controllers/user.controller.js View File

@ -54,7 +54,6 @@ exports.replace = async (req, res, next) => {
const ommitRole = user.role !== 'admin' ? 'role' : '';
const newUserObject = omit(newUser.toObject(), '_id', ommitRole);
res.json(savedUser.transform());
} catch (error) {
next(User.checkDuplicateEmail(error));
@ -70,9 +69,10 @@ exports.update = (req, res, next) => {
const updatedUser = omit(req.body, ommitRole);
const user = Object.assign(req.locals.user, updatedUser);
user.save()
.then(savedUser => res.json(savedUser.transform()))
.catch(e => next(User.checkDuplicateEmail(e)));
user
.save()
.then((savedUser) => res.json(savedUser.transform()))
.catch((e) => next(User.checkDuplicateEmail(e)));
};
/**
@ -82,7 +82,7 @@ exports.update = (req, res, next) => {
exports.list = async (req, res, next) => {
try {
const users = await User.list(req.query);
const transformedUsers = users.map(user => user.transform());
const transformedUsers = users.map((user) => user.transform());
res.json(transformedUsers);
} catch (error) {
next(error);
@ -96,7 +96,8 @@ exports.list = async (req, res, next) => {
exports.remove = (req, res, next) => {
const { user } = req.locals;
user.remove()
user
.remove()
.then(() => res.status(httpStatus.NO_CONTENT).end())
.catch(e => next(e));
.catch((e) => next(e));
};

+ 10
- 8
src/api/middlewares/auth.js View File

@ -44,11 +44,13 @@ const handleJWT = (req, res, next, roles) => async (err, user, info) => {
exports.ADMIN = ADMIN;
exports.LOGGED_USER = LOGGED_USER;
exports.authorize = (roles = User.roles) => (req, res, next) =>
passport.authenticate(
'jwt', { session: false },
handleJWT(req, res, next, roles),
)(req, res, next);
exports.oAuth = service =>
passport.authenticate(service, { session: false });
exports.authorize =
(roles = User.roles) =>
(req, res, next) =>
passport.authenticate(
'jwt',
{ session: false },
handleJWT(req, res, next, roles),
)(req, res, next);
exports.oAuth = (service) => passport.authenticate(service, { session: false });

+ 5
- 4
src/api/models/passwordResetToken.model.js View File

@ -36,9 +36,7 @@ passwordResetTokenSchema.statics = {
const userId = user._id;
const userEmail = user.email;
const resetToken = `${userId}.${crypto.randomBytes(40).toString('hex')}`;
const expires = moment()
.add(2, 'hours')
.toDate();
const expires = moment().add(2, 'hours').toDate();
const ResetTokenObject = new PasswordResetToken({
resetToken,
userId,
@ -53,5 +51,8 @@ passwordResetTokenSchema.statics = {
/**
* @typedef RefreshToken
*/
const PasswordResetToken = mongoose.model('PasswordResetToken', passwordResetTokenSchema);
const PasswordResetToken = mongoose.model(
'PasswordResetToken',
passwordResetTokenSchema,
);
module.exports = PasswordResetToken;

+ 4
- 3
src/api/models/refreshToken.model.js View File

@ -26,7 +26,6 @@ const refreshTokenSchema = new mongoose.Schema({
});
refreshTokenSchema.statics = {
/**
* Generate a refresh token object and saves it into the database
*
@ -39,12 +38,14 @@ refreshTokenSchema.statics = {
const token = `${userId}.${crypto.randomBytes(40).toString('hex')}`;
const expires = moment().add(30, 'days').toDate();
const tokenObject = new RefreshToken({
token, userId, userEmail, expires,
token,
userId,
userEmail,
expires,
});
tokenObject.save();
return tokenObject;
},
};
/**


+ 62
- 53
src/api/models/user.model.js View File

@ -9,51 +9,54 @@ const APIError = require('../utils/APIError');
const { env, jwtSecret, jwtExpirationInterval } = require('../../config/vars');
/**
* User Roles
*/
* User Roles
*/
const roles = ['user', 'admin'];
/**
* User Schema
* @private
*/
const userSchema = new mongoose.Schema({
email: {
type: String,
match: /^\S+@\S+\.\S+$/,
required: true,
unique: true,
trim: true,
lowercase: true,
},
password: {
type: String,
required: true,
minlength: 6,
maxlength: 128,
},
name: {
type: String,
maxlength: 128,
index: true,
trim: true,
},
services: {
facebook: String,
google: String,
},
role: {
type: String,
enum: roles,
default: 'user',
const userSchema = new mongoose.Schema(
{
email: {
type: String,
match: /^\S+@\S+\.\S+$/,
required: true,
unique: true,
trim: true,
lowercase: true,
},
password: {
type: String,
required: true,
minlength: 6,
maxlength: 128,
},
name: {
type: String,
maxlength: 128,
index: true,
trim: true,
},
services: {
facebook: String,
google: String,
},
role: {
type: String,
enum: roles,
default: 'user',
},
picture: {
type: String,
trim: true,
},
},
picture: {
type: String,
trim: true,
{
timestamps: true,
},
}, {
timestamps: true,
});
);
/**
* Add your
@ -109,7 +112,6 @@ userSchema.method({
* Statics
*/
userSchema.statics = {
roles,
/**
@ -146,7 +148,10 @@ userSchema.statics = {
*/
async findAndGenerateToken(options) {
const { email, password, refreshObject } = options;
if (!email) throw new APIError({ message: 'An email is required to generate a token' });
if (!email)
throw new APIError({
message: 'An email is required to generate a token',
});
const user = await this.findOne({ email }).exec();
const err = {
@ -154,7 +159,7 @@ userSchema.statics = {
isPublic: true,
};
if (password) {
if (user && await user.passwordMatches(password)) {
if (user && (await user.passwordMatches(password))) {
return { user, accessToken: user.token() };
}
err.message = 'Incorrect email or password';
@ -177,9 +182,7 @@ userSchema.statics = {
* @param {number} limit - Limit number of users to be returned.
* @returns {Promise<User[]>}
*/
list({
page = 1, perPage = 30, name, email, role,
}) {
list({ page = 1, perPage = 30, name, email, role }) {
const options = omitBy({ name, email, role }, isNil);
return this.find(options)
@ -200,11 +203,13 @@ userSchema.statics = {
if (error.name === 'MongoError' && error.code === 11000) {
return new APIError({
message: 'Validation Error',
errors: [{
field: 'email',
location: 'body',
messages: ['"email" already exists'],
}],
errors: [
{
field: 'email',
location: 'body',
messages: ['"email" already exists'],
},
],
status: httpStatus.CONFLICT,
isPublic: true,
stack: error.stack,
@ -213,10 +218,10 @@ userSchema.statics = {
return error;
},
async oAuthLogin({
service, id, email, name, picture,
}) {
const user = await this.findOne({ $or: [{ [`services.${service}`]: id }, { email }] });
async oAuthLogin({ service, id, email, name, picture }) {
const user = await this.findOne({
$or: [{ [`services.${service}`]: id }, { email }],
});
if (user) {
user.services[service] = id;
if (!user.name) user.name = name;
@ -225,7 +230,11 @@ userSchema.statics = {
}
const password = uuidv4();
return this.create({
services: { [service]: id }, email, password, name, picture,
services: { [service]: id },
email,
password,
name,
picture,
});
},
};


+ 12
- 15
src/api/routes/v1/auth.route.js View File

@ -1,5 +1,5 @@
const express = require('express');
const { validate, ValidationError, Joi } = require('express-validation')
const { validate, ValidationError, Joi } = require('express-validation');
const controller = require('../../controllers/auth.controller');
const oAuthLogin = require('../../middlewares/auth').oAuth;
const {
@ -40,9 +40,7 @@ const router = express.Router();
*
* @apiError (Bad Request 400) ValidationError Some parameters may contain invalid values
*/
router.route('/register')
.post(validate(register), controller.register);
router.route('/register').post(validate(register), controller.register);
/**
* @api {post} v1/auth/login Login
@ -71,9 +69,7 @@ router.route('/register')
* @apiError (Bad Request 400) ValidationError Some parameters may contain invalid values
* @apiError (Unauthorized 401) Unauthorized Incorrect email or password
*/
router.route('/login')
.post(validate(login), controller.login);
router.route('/login').post(validate(login), controller.login);
/**
* @api {post} v1/auth/refresh-token Refresh Token
@ -94,14 +90,14 @@ router.route('/login')
* @apiError (Bad Request 400) ValidationError Some parameters may contain invalid values
* @apiError (Unauthorized 401) Unauthorized Incorrect email or refreshToken
*/
router.route('/refresh-token')
.post(validate(refresh), controller.refresh);
router.route('/refresh-token').post(validate(refresh), controller.refresh);
router.route('/send-password-reset')
router
.route('/send-password-reset')
.post(validate(sendPasswordReset), controller.sendPasswordReset);
router.route('/reset-password')
router
.route('/reset-password')
.post(validate(passwordReset), controller.resetPassword);
/**
@ -122,7 +118,8 @@ router.route('/reset-password')
* @apiError (Bad Request 400) ValidationError Some parameters may contain invalid values
* @apiError (Unauthorized 401) Unauthorized Incorrect access_token
*/
router.route('/facebook')
router
.route('/facebook')
.post(validate(oAuth), oAuthLogin('facebook'), controller.oAuth);
/**
@ -143,8 +140,8 @@ router.route('/facebook')
* @apiError (Bad Request 400) ValidationError Some parameters may contain invalid values
* @apiError (Unauthorized 401) Unauthorized Incorrect access_token
*/
router.route('/google')
router
.route('/google')
.post(validate(oAuth), oAuthLogin('google'), controller.oAuth);
module.exports = router;

+ 1
- 5
src/api/routes/v1/user.route.js View File

@ -1,5 +1,5 @@
const express = require('express');
const { validate, ValidationError, Joi } = require('express-validation')
const { validate, ValidationError, Joi } = require('express-validation');
const controller = require('../../controllers/user.controller');
const { authorize, ADMIN, LOGGED_USER } = require('../../middlewares/auth');
const {
@ -16,7 +16,6 @@ const router = express.Router();
*/
router.param('userId', controller.load);
router
.route('/')
/**
@ -68,7 +67,6 @@ router
*/
.post(authorize(ADMIN), validate(createUser), controller.create);
router
.route('/profile')
/**
@ -91,7 +89,6 @@ router
*/
.get(authorize(), controller.loggedIn);
router
.route('/:userId')
/**
@ -189,5 +186,4 @@ router
*/
.delete(authorize(LOGGED_USER), controller.remove);
module.exports = router;

+ 2
- 6
src/api/services/authProviders.js View File

@ -6,9 +6,7 @@ exports.facebook = async (access_token) => {
const url = 'https://graph.facebook.com/me';
const params = { access_token, fields };
const response = await axios.get(url, { params });
const {
id, name, email, picture,
} = response.data;
const { id, name, email, picture } = response.data;
return {
service: 'facebook',
picture: picture.data.url,
@ -22,9 +20,7 @@ exports.google = async (access_token) => {
const url = 'https://www.googleapis.com/oauth2/v3/userinfo';
const params = { access_token };
const response = await axios.get(url, { params });
const {
sub, name, email, picture,
} = response.data;
const { sub, name, email, picture } = response.data;
return {
service: 'google',
picture,


+ 28
- 22
src/api/tests/integration/auth.test.js View File

@ -49,9 +49,7 @@ describe('Authentication API', () => {
'5947397b323ae82d8c3a333b.c69d0435e62c9f4953af912442a3d064e20291f0d228c0552ed4be473e7d191ba40b18c2c47e8b9d',
userId: '5947397b323ae82d8c3a333b',
userEmail: dbUser.email,
expires: moment()
.add(1, 'day')
.toDate(),
expires: moment().add(1, 'day').toDate(),
};
resetToken = {
@ -59,9 +57,7 @@ describe('Authentication API', () => {
'5947397b323ae82d8c3a333b.c69d0435e62c9f4953af912442a3d064e20291f0d228c0552ed4be473e7d191ba40b18c2c47e8b9d',
userId: '5947397b323ae82d8c3a333b',
userEmail: dbUser.email,
expires: moment()
.add(2, 'hours')
.toDate(),
expires: moment().add(2, 'hours').toDate(),
};
expiredRefreshToken = {
@ -69,9 +65,7 @@ describe('Authentication API', () => {
'5947397b323ae82d8c3a333b.c69d0435e62c9f4953af912442a3d064e20291f0d228c0552ed4be473e7d191ba40b18c2c47e8b9d',
userId: '5947397b323ae82d8c3a333b',
userEmail: dbUser.email,
expires: moment()
.subtract(1, 'day')
.toDate(),
expires: moment().subtract(1, 'day').toDate(),
};
expiredResetToken = {
@ -79,9 +73,7 @@ describe('Authentication API', () => {
'5947397b323ae82d8c3a333b.c69d0435e62c9f4953af912442a3d064e20291f0d228c0552ed4be473e7d191ba40b18c2c47e8b9d',
userId: '5947397b323ae82d8c3a333b',
userEmail: dbUser.email,
expires: moment()
.subtract(2, 'hours')
.toDate(),
expires: moment().subtract(2, 'hours').toDate(),
};
await User.deleteMany({});
@ -373,12 +365,16 @@ describe('Authentication API', () => {
it('should send an email with password reset link when email matches a user', async () => {
const PasswordResetTokenObj = await PasswordResetToken.create(resetToken);
expect(PasswordResetTokenObj.resetToken).to.be.equal('5947397b323ae82d8c3a333b.c69d0435e62c9f4953af912442a3d064e20291f0d228c0552ed4be473e7d191ba40b18c2c47e8b9d');
expect(PasswordResetTokenObj.userId.toString()).to.be.equal('5947397b323ae82d8c3a333b');
expect(PasswordResetTokenObj.resetToken).to.be.equal(
'5947397b323ae82d8c3a333b.c69d0435e62c9f4953af912442a3d064e20291f0d228c0552ed4be473e7d191ba40b18c2c47e8b9d',
);
expect(PasswordResetTokenObj.userId.toString()).to.be.equal(
'5947397b323ae82d8c3a333b',
);
expect(PasswordResetTokenObj.userEmail).to.be.equal(dbUser.email);
expect(PasswordResetTokenObj.expires).to.be.above(moment()
.add(1, 'hour')
.toDate());
expect(PasswordResetTokenObj.expires).to.be.above(
moment().add(1, 'hour').toDate(),
);
sandbox
.stub(emailProvider, 'sendPasswordReset')
@ -463,7 +459,10 @@ describe('Authentication API', () => {
it('should report error when email is not provided', () => {
return request(app)
.post('/v1/auth/reset-password')
.send({ password: 'updatedPassword', resetToken: resetToken.resetToken })
.send({
password: 'updatedPassword',
resetToken: resetToken.resetToken,
})
.expect(httpStatus.BAD_REQUEST)
.then((res) => {
const field1 = res.body.errors[0].field;
@ -504,12 +503,19 @@ describe('Authentication API', () => {
});
it('should report error when the resetToken is expired', async () => {
const expiredPasswordResetTokenObj = await PasswordResetToken.create(expiredResetToken);
const expiredPasswordResetTokenObj =
await PasswordResetToken.create(expiredResetToken);
expect(expiredPasswordResetTokenObj.resetToken).to.be.equal('5947397b323ae82d8c3a333b.c69d0435e62c9f4953af912442a3d064e20291f0d228c0552ed4be473e7d191ba40b18c2c47e8b9d');
expect(expiredPasswordResetTokenObj.userId.toString()).to.be.equal('5947397b323ae82d8c3a333b');
expect(expiredPasswordResetTokenObj.resetToken).to.be.equal(
'5947397b323ae82d8c3a333b.c69d0435e62c9f4953af912442a3d064e20291f0d228c0552ed4be473e7d191ba40b18c2c47e8b9d',
);
expect(expiredPasswordResetTokenObj.userId.toString()).to.be.equal(
'5947397b323ae82d8c3a333b',
);
expect(expiredPasswordResetTokenObj.userEmail).to.be.equal(dbUser.email);
expect(expiredPasswordResetTokenObj.expires).to.be.below(moment().toDate());
expect(expiredPasswordResetTokenObj.expires).to.be.below(
moment().toDate(),
);
return request(app)
.post('/v1/auth/reset-password')


+ 16
- 8
src/api/tests/integration/user.test.js View File

@ -69,8 +69,10 @@ describe('Users API', async () => {
await User.insertMany([dbUsers.branStark, dbUsers.jonSnow]);
dbUsers.branStark.password = password;
dbUsers.jonSnow.password = password;
adminAccessToken = (await User.findAndGenerateToken(dbUsers.branStark)).accessToken;
userAccessToken = (await User.findAndGenerateToken(dbUsers.jonSnow)).accessToken;
adminAccessToken = (await User.findAndGenerateToken(dbUsers.branStark))
.accessToken;
userAccessToken = (await User.findAndGenerateToken(dbUsers.jonSnow))
.accessToken;
});
describe('POST /v1/users', () => {
@ -147,7 +149,9 @@ describe('Users API', async () => {
const { messages } = res.body.errors[0];
expect(field).to.be.equal('password');
expect(location).to.be.equal('body');
expect(messages).to.include('"password" length must be at least 6 characters long');
expect(messages).to.include(
'"password" length must be at least 6 characters long',
);
});
});
@ -224,7 +228,7 @@ describe('Users API', async () => {
});
});
it('should report error when pagination\'s parameters are not a number', () => {
it("should report error when pagination's parameters are not a number", () => {
return request(app)
.get('/v1/users')
.set('Authorization', `Bearer ${adminAccessToken}`)
@ -362,7 +366,9 @@ describe('Users API', async () => {
const { messages } = res.body.errors[0];
expect(field).to.be.equal('password');
expect(location).to.be.equal('body');
expect(messages).to.include('"password" length must be at least 6 characters long');
expect(messages).to.include(
'"password" length must be at least 6 characters long',
);
});
});
@ -516,7 +522,7 @@ describe('Users API', async () => {
});
describe('GET /v1/users/profile', () => {
it('should get the logged user\'s info', () => {
it("should get the logged user's info", () => {
delete dbUsers.jonSnow.password;
return request(app)
@ -531,10 +537,12 @@ describe('Users API', async () => {
it('should report error without stacktrace when accessToken is expired', async () => {
// fake time
const clock = sinon.useFakeTimers();
const expiredAccessToken = (await User.findAndGenerateToken(dbUsers.branStark)).accessToken;
const expiredAccessToken = (
await User.findAndGenerateToken(dbUsers.branStark)
).accessToken;
// move clock forward by minutes set in config + 1 minute
clock.tick((JWT_EXPIRATION * 60000) + 60000);
clock.tick(JWT_EXPIRATION * 60000 + 60000);
return request(app)
.get('/v1/users/profile')


+ 6
- 4
src/api/utils/APIError.js View File

@ -4,9 +4,7 @@ const httpStatus = require('http-status');
* @extends Error
*/
class ExtendableError extends Error {
constructor({
message, errors, status, isPublic, stack,
}) {
constructor({ message, errors, status, isPublic, stack }) {
super(message);
this.name = this.constructor.name;
this.message = message;
@ -38,7 +36,11 @@ class APIError extends ExtendableError {
isPublic = false,
}) {
super({
message, errors, status, isPublic, stack,
message,
errors,
status,
isPublic,
stack,
});
}
}


+ 755
- 491
src/api/utils/passwordResetEmailTemplate.html
File diff suppressed because it is too large
View File


+ 8
- 26
src/api/validations/auth.validation.js View File

@ -4,25 +4,16 @@ module.exports = {
// POST /v1/auth/register
register: {
body: Joi.object({
email: Joi.string()
.email()
.required(),
password: Joi.string()
.required()
.min(6)
.max(128),
email: Joi.string().email().required(),
password: Joi.string().required().min(6).max(128),
}),
},
// POST /v1/auth/login
login: {
body: Joi.object({
email: Joi.string()
.email()
.required(),
password: Joi.string()
.required()
.max(128),
email: Joi.string().email().required(),
password: Joi.string().required().max(128),
}),
},
@ -37,9 +28,7 @@ module.exports = {
// POST /v1/auth/refresh
refresh: {
body: Joi.object({
email: Joi.string()
.email()
.required(),
email: Joi.string().email().required(),
refreshToken: Joi.string().required(),
}),
},
@ -47,22 +36,15 @@ module.exports = {
// POST /v1/auth/refresh
sendPasswordReset: {
body: Joi.object({
email: Joi.string()
.email()
.required(),
email: Joi.string().email().required(),
}),
},
// POST /v1/auth/password-reset
passwordReset: {
body: Joi.object({
email: Joi.string()
.email()
.required(),
password: Joi.string()
.required()
.min(6)
.max(128),
email: Joi.string().email().required(),
password: Joi.string().required().min(6).max(128),
resetToken: Joi.string().required(),
}),
},


+ 8
- 5
src/api/validations/user.validation.js View File

@ -2,7 +2,6 @@ const Joi = require('joi');
const User = require('../models/user.model');
module.exports = {
// GET /v1/users
listUsers: {
query: Joi.object({
@ -20,7 +19,7 @@ module.exports = {
email: Joi.string().email().required(),
password: Joi.string().min(6).max(128).required(),
name: Joi.string().max(128),
role: Joi.string()
role: Joi.string(),
}),
},
@ -30,10 +29,12 @@ module.exports = {
email: Joi.string().email().required(),
password: Joi.string().min(6).max(128).required(),
name: Joi.string().max(128),
role: Joi.string()
role: Joi.string(),
}),
params: {
userId: Joi.string().regex(/^[a-fA-F0-9]{24}$/).required(),
userId: Joi.string()
.regex(/^[a-fA-F0-9]{24}$/)
.required(),
},
},
@ -46,7 +47,9 @@ module.exports = {
role: Joi.string(),
}),
params: {
userId: Joi.string().regex(/^[a-fA-F0-9]{24}$/).required(),
userId: Joi.string()
.regex(/^[a-fA-F0-9]{24}$/)
.required(),
},
},
};

+ 3
- 3
src/config/express.js View File

@ -12,9 +12,9 @@ const strategies = require('./passport');
const error = require('../api/middlewares/error');
/**
* Express instance
* @public
*/
* Express instance
* @public
*/
const app = express();
// request logging. dev: console | production: file


+ 5
- 3
src/config/logger.js View File

@ -18,9 +18,11 @@ const logger = winston.createLogger({
// `${info.level}: ${info.message} JSON.stringify({ ...rest }) `
//
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple(),
}));
logger.add(
new winston.transports.Console({
format: winston.format.simple(),
}),
);
}
logger.stream = {


+ 1
- 1
src/config/passport.js View File

@ -20,7 +20,7 @@ const jwt = async (payload, done) => {
}
};
const oAuth = service => async (token, done) => {
const oAuth = (service) => async (token, done) => {
try {
const userData = await authProviders[service](token);
const user = await User.oAuthLogin(userData);


+ 6
- 3
src/config/vars.js View File

@ -1,8 +1,8 @@
const path = require('path');
// import .env variables
require('dotenv-safe').load({
path: path.join(__dirname, '../../.env'),
require('dotenv').config({
path: path.join(__dirname, `../../.env.${process.env.NODE_ENV}`),
sample: path.join(__dirname, '../../.env.example'),
});
@ -12,7 +12,10 @@ module.exports = {
jwtSecret: process.env.JWT_SECRET,
jwtExpirationInterval: process.env.JWT_EXPIRATION_MINUTES,
mongo: {
uri: process.env.NODE_ENV === 'test' ? process.env.MONGO_URI_TESTS : process.env.MONGO_URI,
uri:
process.env.NODE_ENV === 'test'
? process.env.MONGO_URI_TESTS
: process.env.MONGO_URI,
},
logs: process.env.NODE_ENV === 'production' ? 'combined' : 'dev',
emailConfig: {


+ 3
- 3
src/index.js View File

@ -10,7 +10,7 @@ mongoose.connect();
app.listen(port, () => logger.info(`server started on port ${port} (${env})`));
/**
* Exports express
* @public
*/
* Exports express
* @public
*/
module.exports = app;

+ 150
- 44
yarn.lock View File

@ -218,14 +218,14 @@
resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz"
integrity sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==
"@eslint-community/eslint-utils@^4.2.0":
"@eslint-community/eslint-utils@^4.1.2", "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
version "4.4.0"
resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz"
integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==
dependencies:
eslint-visitor-keys "^3.3.0"
"@eslint-community/regexpp@^4.6.1":
"@eslint-community/regexpp@^4.6.0", "@eslint-community/regexpp@^4.6.1":
version "4.10.0"
resolved "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz"
integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==
@ -478,6 +478,11 @@
"@opencensus/core" "^0.0.8"
uuid "^3.2.1"
"@pkgr/core@^0.1.0":
version "0.1.1"
resolved "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz"
integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==
"@pm2/agent@~2.0.0":
version "2.0.3"
resolved "https://registry.npmjs.org/@pm2/agent/-/agent-2.0.3.tgz"
@ -619,7 +624,7 @@
"@types/eslint" "*"
"@types/estree" "*"
"@types/eslint@*":
"@types/eslint@*", "@types/eslint@>=8.0.0":
version "8.56.5"
resolved "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.5.tgz"
integrity sha512-u5/YPJHo1tvkSF2CE0USEkxon82Z5DBy2xR+qfyYNszpX9qcs4sT6uq2kBbj4BXY1+DBGDPnrhMZV3pKWGNukw==
@ -1394,6 +1399,18 @@ buffer-from@^1.0.0:
resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz"
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
builtin-modules@^3.3.0:
version "3.3.0"
resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz"
integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==
builtins@^5.0.1:
version "5.0.1"
resolved "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz"
integrity sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==
dependencies:
semver "^7.0.0"
bytes@3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz"
@ -1760,11 +1777,6 @@ concat-map@0.0.1:
resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
confusing-browser-globals@^1.0.10:
version "1.0.11"
resolved "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz"
integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==
constantinople@^4.0.1:
version "4.0.1"
resolved "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz"
@ -1847,13 +1859,6 @@ croner@~4.1.92:
resolved "https://registry.npmjs.org/croner/-/croner-4.1.97.tgz"
integrity sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ==
cross-env@^7.0.3:
version "7.0.3"
resolved "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz"
integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==
dependencies:
cross-spawn "^7.0.1"
cross-spawn@^6.0.0:
version "6.0.5"
resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz"
@ -1865,7 +1870,7 @@ cross-spawn@^6.0.0:
shebang-command "^1.2.0"
which "^1.2.9"
cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
version "7.0.3"
resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
@ -2240,7 +2245,22 @@ domutils@^3.0.1:
domelementtype "^2.3.0"
domhandler "^5.0.3"
dotenv@^16.4.5:
dotenv-cli@^7.4.1:
version "7.4.1"
resolved "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-7.4.1.tgz"
integrity sha512-fE1aywjRrWGxV3miaiUr3d2zC/VAiuzEGghi+QzgIA9fEf/M5hLMaRSXb4IxbUAwGmaLi0IozdZddnVU96acag==
dependencies:
cross-spawn "^7.0.3"
dotenv "^16.3.0"
dotenv-expand "^10.0.0"
minimist "^1.2.6"
dotenv-expand@^10.0.0:
version "10.0.0"
resolved "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz"
integrity sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==
dotenv@^16.3.0, dotenv@^16.4.5:
version "16.4.5"
resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz"
integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==
@ -2538,15 +2558,15 @@ escodegen@^2.1.0:
optionalDependencies:
source-map "~0.6.1"
eslint-config-airbnb-base@^15.0.0:
version "15.0.0"
resolved "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz"
integrity sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==
dependencies:
confusing-browser-globals "^1.0.10"
object.assign "^4.1.2"
object.entries "^1.1.5"
semver "^6.3.0"
eslint-compat-utils@^0.1.2:
version "0.1.2"
resolved "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.1.2.tgz"
integrity sha512-Jia4JDldWnFNIru1Ehx1H5s9/yxiRHY/TimCuUc0jNexew3cF1gI6CYZil1ociakfWO3rRqFjl1mskBblB3RYg==
eslint-config-standard@^17.1.0:
version "17.1.0"
resolved "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz"
integrity sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==
eslint-import-resolver-node@^0.3.9:
version "0.3.9"
@ -2564,7 +2584,16 @@ eslint-module-utils@^2.8.0:
dependencies:
debug "^3.2.7"
eslint-plugin-import@^2.25.2, eslint-plugin-import@^2.25.3:
eslint-plugin-es-x@^7.5.0:
version "7.5.0"
resolved "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.5.0.tgz"
integrity sha512-ODswlDSO0HJDzXU0XvgZ3lF3lS3XAZEossh15Q2UHjwrJggWeBoKqqEsLTZLXl+dh5eOAozG0zRcYtuE35oTuQ==
dependencies:
"@eslint-community/eslint-utils" "^4.1.2"
"@eslint-community/regexpp" "^4.6.0"
eslint-compat-utils "^0.1.2"
eslint-plugin-import@^2.25.2, eslint-plugin-import@^2.29.1:
version "2.29.1"
resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz"
integrity sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==
@ -2587,6 +2616,36 @@ eslint-plugin-import@^2.25.2, eslint-plugin-import@^2.25.3:
semver "^6.3.1"
tsconfig-paths "^3.15.0"
"eslint-plugin-n@^15.0.0 || ^16.0.0 ", eslint-plugin-n@^16.6.2:
version "16.6.2"
resolved "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.6.2.tgz"
integrity sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==
dependencies:
"@eslint-community/eslint-utils" "^4.4.0"
builtins "^5.0.1"
eslint-plugin-es-x "^7.5.0"
get-tsconfig "^4.7.0"
globals "^13.24.0"
ignore "^5.2.4"
is-builtin-module "^3.2.1"
is-core-module "^2.12.1"
minimatch "^3.1.2"
resolve "^1.22.2"
semver "^7.5.3"
eslint-plugin-prettier@^5.1.3:
version "5.1.3"
resolved "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz"
integrity sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==
dependencies:
prettier-linter-helpers "^1.0.0"
synckit "^0.8.6"
eslint-plugin-promise@^6.0.0, eslint-plugin-promise@^6.1.1:
version "6.1.1"
resolved "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz"
integrity sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==
eslint-scope@^7.2.2:
version "7.2.2"
resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz"
@ -2608,7 +2667,7 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz"
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
"eslint@^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^7.32.0 || ^8.2.0", eslint@^8.5.0:
"eslint@^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^7.0.0 || ^8.0.0", eslint@^8.0.1, eslint@^8.57.0, eslint@>=6.0.0, eslint@>=7.0.0, eslint@>=8, eslint@>=8.0.0:
version "8.57.0"
resolved "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz"
integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==
@ -2821,6 +2880,11 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-diff@^1.1.2:
version "1.3.0"
resolved "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz"
integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==
fast-json-patch@^3.0.0-1:
version "3.1.1"
resolved "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz"
@ -3151,6 +3215,13 @@ get-symbol-description@^1.0.2:
es-errors "^1.3.0"
get-intrinsic "^1.2.4"
get-tsconfig@^4.7.0:
version "4.7.3"
resolved "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.3.tgz"
integrity sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==
dependencies:
resolve-pkg-maps "^1.0.0"
get-uri@^6.0.1:
version "6.0.3"
resolved "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz"
@ -3225,7 +3296,7 @@ globals@^11.1.0:
resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz"
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
globals@^13.19.0:
globals@^13.19.0, globals@^13.24.0:
version "13.24.0"
resolved "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz"
integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==
@ -3482,7 +3553,7 @@ ignore-by-default@^1.0.1:
resolved "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz"
integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==
ignore@^5.2.0:
ignore@^5.2.0, ignore@^5.2.4:
version "5.3.1"
resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz"
integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==
@ -3603,12 +3674,19 @@ is-boolean-object@^1.1.0:
call-bind "^1.0.2"
has-tostringtag "^1.0.0"
is-builtin-module@^3.2.1:
version "3.2.1"
resolved "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz"
integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==
dependencies:
builtin-modules "^3.3.0"
is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7:
version "1.2.7"
resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz"
integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
is-core-module@^2.13.0, is-core-module@^2.13.1:
is-core-module@^2.12.1, is-core-module@^2.13.0, is-core-module@^2.13.1:
version "2.13.1"
resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz"
integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==
@ -4557,6 +4635,18 @@ module-details-from-path@^1.0.3:
resolved "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz"
integrity sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==
moment-timezone@^0.5.45:
version "0.5.45"
resolved "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.45.tgz"
integrity sha512-HIWmqA86KcmCAhnMAN0wuDOARV/525R2+lOLotuGFzn4HO+FH+/645z2wx0Dt3iDv6/p61SIvKnDstISainhLQ==
dependencies:
moment "^2.29.4"
moment@^2.29.4:
version "2.30.1"
resolved "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz"
integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==
mongodb-connection-string-url@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.0.tgz"
@ -4859,7 +4949,7 @@ object-keys@^1.1.1:
resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz"
integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
object.assign@^4.1.2, object.assign@^4.1.5:
object.assign@^4.1.5:
version "4.1.5"
resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz"
integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==
@ -4869,15 +4959,6 @@ object.assign@^4.1.2, object.assign@^4.1.5:
has-symbols "^1.0.3"
object-keys "^1.1.1"
object.entries@^1.1.5:
version "1.1.7"
resolved "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz"
integrity sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==
dependencies:
call-bind "^1.0.2"
define-properties "^1.2.0"
es-abstract "^1.22.1"
object.fromentries@^2.0.7:
version "2.0.7"
resolved "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz"
@ -5375,6 +5456,18 @@ prelude-ls@^1.2.1:
resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
prettier-linter-helpers@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz"
integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==
dependencies:
fast-diff "^1.1.2"
prettier@^3.2.5, prettier@>=3.0.0:
version "3.2.5"
resolved "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz"
integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==
preview-email@^3.0.17:
version "3.0.19"
resolved "https://registry.npmjs.org/preview-email/-/preview-email-3.0.19.tgz"
@ -5761,7 +5854,12 @@ resolve-from@^5.0.0:
resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz"
integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
resolve@^1.10.0, resolve@^1.15.1, resolve@^1.22.1, resolve@^1.22.4, resolve@^1.9.0:
resolve-pkg-maps@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz"
integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==
resolve@^1.10.0, resolve@^1.15.1, resolve@^1.22.1, resolve@^1.22.2, resolve@^1.22.4, resolve@^1.9.0:
version "1.22.8"
resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz"
integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
@ -5906,7 +6004,7 @@ semver@^6.3.1:
resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.2, semver@^7.3.5, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4:
semver@^7.0.0, semver@^7.2, semver@^7.3.5, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4:
version "7.6.0"
resolved "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz"
integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==
@ -6385,6 +6483,14 @@ supports-preserve-symlinks-flag@^1.0.0:
resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
synckit@^0.8.6:
version "0.8.8"
resolved "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz"
integrity sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==
dependencies:
"@pkgr/core" "^0.1.0"
tslib "^2.6.2"
systeminformation@^5.7:
version "5.22.0"
resolved "https://registry.npmjs.org/systeminformation/-/systeminformation-5.22.0.tgz"
@ -6535,7 +6641,7 @@ tsconfig-paths@^3.15.0:
minimist "^1.2.6"
strip-bom "^3.0.0"
tslib@^2.0.1:
tslib@^2.0.1, tslib@^2.6.2:
version "2.6.2"
resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz"
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==


Loading…
Cancel
Save