├── .dockerignore ├── .gitignore ├── Dockerfile ├── README.md ├── authorization ├── controllers │ └── authorization.controller.js ├── middlewares │ └── verify.user.middleware.js └── routes.config.js ├── common ├── config │ └── env.config.js ├── middlewares │ ├── auth.permission.middleware.js │ └── auth.validation.middleware.js └── services │ └── mongoose.service.js ├── docker-compose.yml ├── index.js ├── package-lock.json ├── package.json └── users ├── controllers └── users.controller.js ├── models └── users.model.js └── routes.config.js /.dockerignore: -------------------------------------------------------------------------------- 1 | e2e 2 | node_modules 3 | src 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | **/node_modules 3 | data 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:11-alpine 2 | 3 | RUN mkdir -p /usr/src/app 4 | 5 | WORKDIR /usr/src/app 6 | 7 | COPY . . 8 | 9 | RUN sed -i "s/mongodb:\/\/localhost/mongodb:\/\/mongo/g" common/services/mongoose.service.js 10 | 11 | RUN npm install 12 | 13 | EXPOSE 3600 14 | 15 | CMD ["npm", "run", "start"] 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # REST API Tutorial 2 | 3 | This sample is published as part of [the corresponding article](https://www.toptal.com/nodejs/secure-rest-api-in-nodejs) at the Toptal Engineering Blog. Visit https://www.toptal.com/developers/blog and subscribe to our newsletter to read great posts! 4 | 5 | ## Before using 6 | 7 | - Please make sure that you have: 8 | - Node.js installed (https://nodejs.org/) 9 | - MongoDB installed and running locally (https://www.mongodb.com/) 10 | - Using Windows, just open the terminal at where you installed mongo and run `mongod.exe` 11 | - Run `npm install` or `yarn` in your root project folder 12 | 13 | ## Usage 14 | 15 | To run the project, please use a command line the following: 16 | - `npm start` 17 | - It will run the server at port 3600. 18 | 19 | 20 | ### 2019-09-13 update 21 | 22 | - Refactored mongoose to a proper common service. 23 | - Added a Dockerfile and docker-compose configuration. 24 | 25 | If you are familiar to docker and you have docker installed on your machine and just want to run the project without issues please do: 26 | 27 | - docker-compose build 28 | - docker-compose up 29 | - It will run the mongodb at port 27017 (for testing purposes only). 30 | - It will run the server at port 3600. 31 | 32 | ### 2020-02-01 33 | 34 | I've created a 2020 version of this project using Typescript. If you might be interested on it, please check the following repository: https://github.com/makinhs/expressjs-api-tutorial 35 | 36 | ### 2020-09-09 37 | 38 | - Updated and pruned dependencies. 39 | - Fixed deprecation warnings. 40 | - Leveraged `findOneAndUpdate` to simplify PATCH code. 41 | - Changed default MongoDB server name to `localhost` to simplify first-time setup. 42 | - Checked that it works with the latest version of Node.js, 14.9.0. 43 | 44 | ### 2020-11-14 45 | 46 | - Accepted changes in the docker file that was causing MongoDB issues on Windows Subsystem Linux (WSL2) - Ubuntu. 47 | - If you are new to this project, I highly recommend starting with Typescript first, like we talk about here: https://www.toptal.com/express-js/nodejs-typescript-rest-api-pt-1 48 | -------------------------------------------------------------------------------- /authorization/controllers/authorization.controller.js: -------------------------------------------------------------------------------- 1 | const jwtSecret = require('../../common/config/env.config.js').jwt_secret, 2 | jwt = require('jsonwebtoken'); 3 | const crypto = require('crypto'); 4 | const uuid = require('uuid'); 5 | 6 | exports.login = (req, res) => { 7 | try { 8 | let refreshId = req.body.userId + jwtSecret; 9 | let salt = crypto.randomBytes(16).toString('base64'); 10 | let hash = crypto.createHmac('sha512', salt).update(refreshId).digest("base64"); 11 | req.body.refreshKey = salt; 12 | let token = jwt.sign(req.body, jwtSecret); 13 | let b = Buffer.from(hash); 14 | let refresh_token = b.toString('base64'); 15 | res.status(201).send({accessToken: token, refreshToken: refresh_token}); 16 | } catch (err) { 17 | res.status(500).send({errors: err}); 18 | } 19 | }; 20 | 21 | exports.refresh_token = (req, res) => { 22 | try { 23 | req.body = req.jwt; 24 | let token = jwt.sign(req.body, jwtSecret); 25 | res.status(201).send({id: token}); 26 | } catch (err) { 27 | res.status(500).send({errors: err}); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /authorization/middlewares/verify.user.middleware.js: -------------------------------------------------------------------------------- 1 | const UserModel = require('../../users/models/users.model'); 2 | const crypto = require('crypto'); 3 | 4 | exports.hasAuthValidFields = (req, res, next) => { 5 | let errors = []; 6 | 7 | if (req.body) { 8 | if (!req.body.email) { 9 | errors.push('Missing email field'); 10 | } 11 | if (!req.body.password) { 12 | errors.push('Missing password field'); 13 | } 14 | 15 | if (errors.length) { 16 | return res.status(400).send({errors: errors.join(',')}); 17 | } else { 18 | return next(); 19 | } 20 | } else { 21 | return res.status(400).send({errors: 'Missing email and password fields'}); 22 | } 23 | }; 24 | 25 | exports.isPasswordAndUserMatch = (req, res, next) => { 26 | UserModel.findByEmail(req.body.email) 27 | .then((user)=>{ 28 | if(!user[0]){ 29 | res.status(404).send({}); 30 | }else{ 31 | let passwordFields = user[0].password.split('$'); 32 | let salt = passwordFields[0]; 33 | let hash = crypto.createHmac('sha512', salt).update(req.body.password).digest("base64"); 34 | if (hash === passwordFields[1]) { 35 | req.body = { 36 | userId: user[0]._id, 37 | email: user[0].email, 38 | permissionLevel: user[0].permissionLevel, 39 | provider: 'email', 40 | name: user[0].firstName + ' ' + user[0].lastName, 41 | }; 42 | return next(); 43 | } else { 44 | return res.status(400).send({errors: ['Invalid e-mail or password']}); 45 | } 46 | } 47 | }); 48 | }; -------------------------------------------------------------------------------- /authorization/routes.config.js: -------------------------------------------------------------------------------- 1 | const VerifyUserMiddleware = require('./middlewares/verify.user.middleware'); 2 | const AuthorizationController = require('./controllers/authorization.controller'); 3 | const AuthValidationMiddleware = require('../common/middlewares/auth.validation.middleware'); 4 | exports.routesConfig = function (app) { 5 | 6 | app.post('/auth', [ 7 | VerifyUserMiddleware.hasAuthValidFields, 8 | VerifyUserMiddleware.isPasswordAndUserMatch, 9 | AuthorizationController.login 10 | ]); 11 | 12 | app.post('/auth/refresh', [ 13 | AuthValidationMiddleware.validJWTNeeded, 14 | AuthValidationMiddleware.verifyRefreshBodyField, 15 | AuthValidationMiddleware.validRefreshNeeded, 16 | AuthorizationController.login 17 | ]); 18 | }; -------------------------------------------------------------------------------- /common/config/env.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "port": 3600, 3 | "appEndpoint": "http://localhost:3600", 4 | "apiEndpoint": "http://localhost:3600", 5 | "jwt_secret": "myS33!!creeeT", 6 | "jwt_expiration_in_seconds": 36000, 7 | "environment": "dev", 8 | "permissionLevels": { 9 | "NORMAL_USER": 1, 10 | "PAID_USER": 4, 11 | "ADMIN": 2048 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /common/middlewares/auth.permission.middleware.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken'), 2 | secret = require('../config/env.config')['jwt_secret']; 3 | const ADMIN_PERMISSION = require('../config/env.config')['permissionLevels']['ADMIN']; 4 | 5 | exports.minimumPermissionLevelRequired = (required_permission_level) => { 6 | return (req, res, next) => { 7 | let user_permission_level = parseInt(req.jwt.permissionLevel); 8 | let userId = req.jwt.userId; 9 | if (user_permission_level & required_permission_level) { 10 | return next(); 11 | } else { 12 | return res.status(403).send(); 13 | } 14 | }; 15 | }; 16 | 17 | exports.onlySameUserOrAdminCanDoThisAction = (req, res, next) => { 18 | let user_permission_level = parseInt(req.jwt.permissionLevel); 19 | let userId = req.jwt.userId; 20 | if (req.params && req.params.userId && userId === req.params.userId) { 21 | return next(); 22 | } else { 23 | if (user_permission_level & ADMIN_PERMISSION) { 24 | return next(); 25 | } else { 26 | return res.status(403).send(); 27 | } 28 | } 29 | 30 | }; 31 | 32 | exports.sameUserCantDoThisAction = (req, res, next) => { 33 | let userId = req.jwt.userId; 34 | if (req.params.userId !== userId) { 35 | return next(); 36 | } else { 37 | return res.status(400).send(); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /common/middlewares/auth.validation.middleware.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken'), 2 | secret = require('../config/env.config.js').jwt_secret, 3 | crypto = require('crypto'); 4 | 5 | exports.verifyRefreshBodyField = (req, res, next) => { 6 | if (req.body && req.body.refresh_token) { 7 | return next(); 8 | } else { 9 | return res.status(400).send({error: 'need to pass refresh_token field'}); 10 | } 11 | }; 12 | 13 | exports.validRefreshNeeded = (req, res, next) => { 14 | let b = Buffer.from(req.body.refresh_token, 'base64'); 15 | let refresh_token = b.toString(); 16 | let hash = crypto.createHmac('sha512', req.jwt.refreshKey).update(req.jwt.userId + secret).digest("base64"); 17 | if (hash === refresh_token) { 18 | req.body = req.jwt; 19 | return next(); 20 | } else { 21 | return res.status(400).send({error: 'Invalid refresh token'}); 22 | } 23 | }; 24 | 25 | 26 | exports.validJWTNeeded = (req, res, next) => { 27 | if (req.headers['authorization']) { 28 | try { 29 | let authorization = req.headers['authorization'].split(' '); 30 | if (authorization[0] !== 'Bearer') { 31 | return res.status(401).send(); 32 | } else { 33 | req.jwt = jwt.verify(authorization[1], secret); 34 | return next(); 35 | } 36 | 37 | } catch (err) { 38 | return res.status(403).send(); 39 | } 40 | } else { 41 | return res.status(401).send(); 42 | } 43 | }; -------------------------------------------------------------------------------- /common/services/mongoose.service.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | let count = 0; 3 | 4 | const options = { 5 | autoIndex: false, // Don't build indexes 6 | poolSize: 10, // Maintain up to 10 socket connections 7 | // If not connected, return errors immediately rather than waiting for reconnect 8 | bufferMaxEntries: 0, 9 | // all other approaches are now deprecated by MongoDB: 10 | useNewUrlParser: true, 11 | useUnifiedTopology: true 12 | 13 | }; 14 | const connectWithRetry = () => { 15 | console.log('MongoDB connection with retry') 16 | mongoose.connect("mongodb://localhost:27017/rest-tutorial", options).then(()=>{ 17 | console.log('MongoDB is connected') 18 | }).catch(err=>{ 19 | console.log('MongoDB connection unsuccessful, retry after 5 seconds. ', ++count); 20 | setTimeout(connectWithRetry, 5000) 21 | }) 22 | }; 23 | 24 | connectWithRetry(); 25 | 26 | exports.mongoose = mongoose; 27 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | api: 4 | image: makinhs/rest-api-tutorial 5 | build: . 6 | networks: 7 | - backend 8 | ports: 9 | - "3600:3600" 10 | depends_on: 11 | - mongo 12 | 13 | mongo: 14 | image: mongo 15 | volumes: 16 | - ./data:/data/db 17 | networks: 18 | - backend 19 | ports: 20 | - "27017:27017" 21 | 22 | web-cli: 23 | image: makinhs/rest-api-tutorial 24 | links: 25 | - mongo 26 | networks: 27 | - backend 28 | command: sh 29 | 30 | networks: 31 | backend: 32 | driver: bridge 33 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const config = require('./common/config/env.config.js'); 2 | 3 | const express = require('express'); 4 | const app = express(); 5 | 6 | const AuthorizationRouter = require('./authorization/routes.config'); 7 | const UsersRouter = require('./users/routes.config'); 8 | 9 | app.use(function (req, res, next) { 10 | res.header('Access-Control-Allow-Origin', '*'); 11 | res.header('Access-Control-Allow-Credentials', 'true'); 12 | res.header('Access-Control-Allow-Methods', 'GET,HEAD,PUT,PATCH,POST,DELETE'); 13 | res.header('Access-Control-Expose-Headers', 'Content-Length'); 14 | res.header('Access-Control-Allow-Headers', 'Accept, Authorization, Content-Type, X-Requested-With, Range'); 15 | if (req.method === 'OPTIONS') { 16 | return res.sendStatus(200); 17 | } else { 18 | return next(); 19 | } 20 | }); 21 | 22 | app.use(express.json()); 23 | AuthorizationRouter.routesConfig(app); 24 | UsersRouter.routesConfig(app); 25 | 26 | 27 | app.listen(config.port, function () { 28 | console.log('app listening at port %s', config.port); 29 | }); 30 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rest-api-tutorial", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/bson": { 8 | "version": "4.0.5", 9 | "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.5.tgz", 10 | "integrity": "sha512-vVLwMUqhYJSQ/WKcE60eFqcyuWse5fGH+NMAXHuKrUAPoryq3ATxk5o4bgYNtg5aOM4APVg7Hnb3ASqUYG0PKg==", 11 | "requires": { 12 | "@types/node": "*" 13 | } 14 | }, 15 | "@types/mongodb": { 16 | "version": "3.6.20", 17 | "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.20.tgz", 18 | "integrity": "sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ==", 19 | "requires": { 20 | "@types/bson": "*", 21 | "@types/node": "*" 22 | } 23 | }, 24 | "@types/node": { 25 | "version": "10.17.5", 26 | "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.5.tgz", 27 | "integrity": "sha512-RElZIr/7JreF1eY6oD5RF3kpmdcreuQPjg5ri4oQ5g9sq7YWU8HkfB3eH8GwAwxf5OaCh0VPi7r4N/yoTGelrA==" 28 | }, 29 | "accepts": { 30 | "version": "1.3.7", 31 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 32 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 33 | "requires": { 34 | "mime-types": "~2.1.24", 35 | "negotiator": "0.6.2" 36 | }, 37 | "dependencies": { 38 | "mime-types": { 39 | "version": "2.1.24", 40 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", 41 | "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", 42 | "requires": { 43 | "mime-db": "1.40.0" 44 | } 45 | } 46 | } 47 | }, 48 | "array-flatten": { 49 | "version": "1.1.1", 50 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 51 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 52 | }, 53 | "bl": { 54 | "version": "2.2.1", 55 | "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", 56 | "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", 57 | "requires": { 58 | "readable-stream": "^2.3.5", 59 | "safe-buffer": "^5.1.1" 60 | } 61 | }, 62 | "bluebird": { 63 | "version": "3.5.1", 64 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", 65 | "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" 66 | }, 67 | "bson": { 68 | "version": "1.1.6", 69 | "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", 70 | "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==" 71 | }, 72 | "buffer-equal-constant-time": { 73 | "version": "1.0.1", 74 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", 75 | "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" 76 | }, 77 | "content-disposition": { 78 | "version": "0.5.3", 79 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 80 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 81 | "requires": { 82 | "safe-buffer": "5.1.2" 83 | } 84 | }, 85 | "content-type": { 86 | "version": "1.0.4", 87 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 88 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 89 | }, 90 | "cookie": { 91 | "version": "0.4.0", 92 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 93 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 94 | }, 95 | "cookie-signature": { 96 | "version": "1.0.6", 97 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 98 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 99 | }, 100 | "core-util-is": { 101 | "version": "1.0.2", 102 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 103 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 104 | }, 105 | "debug": { 106 | "version": "2.6.9", 107 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 108 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 109 | "requires": { 110 | "ms": "2.0.0" 111 | } 112 | }, 113 | "denque": { 114 | "version": "1.5.1", 115 | "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", 116 | "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==" 117 | }, 118 | "destroy": { 119 | "version": "1.0.4", 120 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 121 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 122 | }, 123 | "ecdsa-sig-formatter": { 124 | "version": "1.0.11", 125 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", 126 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", 127 | "requires": { 128 | "safe-buffer": "^5.0.1" 129 | } 130 | }, 131 | "encodeurl": { 132 | "version": "1.0.2", 133 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 134 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 135 | }, 136 | "escape-html": { 137 | "version": "1.0.3", 138 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 139 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 140 | }, 141 | "etag": { 142 | "version": "1.8.1", 143 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 144 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 145 | }, 146 | "express": { 147 | "version": "4.17.1", 148 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 149 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 150 | "requires": { 151 | "accepts": "~1.3.7", 152 | "array-flatten": "1.1.1", 153 | "body-parser": "1.19.0", 154 | "content-disposition": "0.5.3", 155 | "content-type": "~1.0.4", 156 | "cookie": "0.4.0", 157 | "cookie-signature": "1.0.6", 158 | "debug": "2.6.9", 159 | "depd": "~1.1.2", 160 | "encodeurl": "~1.0.2", 161 | "escape-html": "~1.0.3", 162 | "etag": "~1.8.1", 163 | "finalhandler": "~1.1.2", 164 | "fresh": "0.5.2", 165 | "merge-descriptors": "1.0.1", 166 | "methods": "~1.1.2", 167 | "on-finished": "~2.3.0", 168 | "parseurl": "~1.3.3", 169 | "path-to-regexp": "0.1.7", 170 | "proxy-addr": "~2.0.5", 171 | "qs": "6.7.0", 172 | "range-parser": "~1.2.1", 173 | "safe-buffer": "5.1.2", 174 | "send": "0.17.1", 175 | "serve-static": "1.14.1", 176 | "setprototypeof": "1.1.1", 177 | "statuses": "~1.5.0", 178 | "type-is": "~1.6.18", 179 | "utils-merge": "1.0.1", 180 | "vary": "~1.1.2" 181 | }, 182 | "dependencies": { 183 | "body-parser": { 184 | "version": "1.19.0", 185 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 186 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 187 | "requires": { 188 | "bytes": "3.1.0", 189 | "content-type": "~1.0.4", 190 | "debug": "2.6.9", 191 | "depd": "~1.1.2", 192 | "http-errors": "1.7.2", 193 | "iconv-lite": "0.4.24", 194 | "on-finished": "~2.3.0", 195 | "qs": "6.7.0", 196 | "raw-body": "2.4.0", 197 | "type-is": "~1.6.17" 198 | } 199 | }, 200 | "bytes": { 201 | "version": "3.1.0", 202 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 203 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 204 | }, 205 | "depd": { 206 | "version": "1.1.2", 207 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 208 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 209 | }, 210 | "ee-first": { 211 | "version": "1.1.1", 212 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 213 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 214 | }, 215 | "iconv-lite": { 216 | "version": "0.4.24", 217 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 218 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 219 | "requires": { 220 | "safer-buffer": ">= 2.1.2 < 3" 221 | } 222 | }, 223 | "media-typer": { 224 | "version": "0.3.0", 225 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 226 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 227 | }, 228 | "mime-types": { 229 | "version": "2.1.24", 230 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", 231 | "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", 232 | "requires": { 233 | "mime-db": "1.40.0" 234 | } 235 | }, 236 | "on-finished": { 237 | "version": "2.3.0", 238 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 239 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 240 | "requires": { 241 | "ee-first": "1.1.1" 242 | } 243 | }, 244 | "qs": { 245 | "version": "6.7.0", 246 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 247 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 248 | }, 249 | "raw-body": { 250 | "version": "2.4.0", 251 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 252 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 253 | "requires": { 254 | "bytes": "3.1.0", 255 | "http-errors": "1.7.2", 256 | "iconv-lite": "0.4.24", 257 | "unpipe": "1.0.0" 258 | } 259 | }, 260 | "type-is": { 261 | "version": "1.6.18", 262 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 263 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 264 | "requires": { 265 | "media-typer": "0.3.0", 266 | "mime-types": "~2.1.24" 267 | } 268 | } 269 | } 270 | }, 271 | "finalhandler": { 272 | "version": "1.1.2", 273 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 274 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 275 | "requires": { 276 | "debug": "2.6.9", 277 | "encodeurl": "~1.0.2", 278 | "escape-html": "~1.0.3", 279 | "on-finished": "~2.3.0", 280 | "parseurl": "~1.3.3", 281 | "statuses": "~1.5.0", 282 | "unpipe": "~1.0.0" 283 | }, 284 | "dependencies": { 285 | "ee-first": { 286 | "version": "1.1.1", 287 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 288 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 289 | }, 290 | "on-finished": { 291 | "version": "2.3.0", 292 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 293 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 294 | "requires": { 295 | "ee-first": "1.1.1" 296 | } 297 | } 298 | } 299 | }, 300 | "forwarded": { 301 | "version": "0.1.2", 302 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 303 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 304 | }, 305 | "fresh": { 306 | "version": "0.5.2", 307 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 308 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 309 | }, 310 | "http-errors": { 311 | "version": "1.7.2", 312 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 313 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 314 | "requires": { 315 | "depd": "~1.1.2", 316 | "inherits": "2.0.3", 317 | "setprototypeof": "1.1.1", 318 | "statuses": ">= 1.5.0 < 2", 319 | "toidentifier": "1.0.0" 320 | }, 321 | "dependencies": { 322 | "depd": { 323 | "version": "1.1.2", 324 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 325 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 326 | } 327 | } 328 | }, 329 | "inherits": { 330 | "version": "2.0.3", 331 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 332 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 333 | }, 334 | "ipaddr.js": { 335 | "version": "1.9.0", 336 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", 337 | "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" 338 | }, 339 | "isarray": { 340 | "version": "1.0.0", 341 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 342 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 343 | }, 344 | "jsonwebtoken": { 345 | "version": "8.5.1", 346 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", 347 | "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", 348 | "requires": { 349 | "jws": "^3.2.2", 350 | "lodash.includes": "^4.3.0", 351 | "lodash.isboolean": "^3.0.3", 352 | "lodash.isinteger": "^4.0.4", 353 | "lodash.isnumber": "^3.0.3", 354 | "lodash.isplainobject": "^4.0.6", 355 | "lodash.isstring": "^4.0.1", 356 | "lodash.once": "^4.0.0", 357 | "ms": "^2.1.1", 358 | "semver": "^5.6.0" 359 | }, 360 | "dependencies": { 361 | "ms": { 362 | "version": "2.1.2", 363 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 364 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 365 | } 366 | } 367 | }, 368 | "jwa": { 369 | "version": "1.4.1", 370 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", 371 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", 372 | "requires": { 373 | "buffer-equal-constant-time": "1.0.1", 374 | "ecdsa-sig-formatter": "1.0.11", 375 | "safe-buffer": "^5.0.1" 376 | } 377 | }, 378 | "jws": { 379 | "version": "3.2.2", 380 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", 381 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", 382 | "requires": { 383 | "jwa": "^1.4.1", 384 | "safe-buffer": "^5.0.1" 385 | } 386 | }, 387 | "kareem": { 388 | "version": "2.3.2", 389 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", 390 | "integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==" 391 | }, 392 | "lodash.includes": { 393 | "version": "4.3.0", 394 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", 395 | "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" 396 | }, 397 | "lodash.isboolean": { 398 | "version": "3.0.3", 399 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", 400 | "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" 401 | }, 402 | "lodash.isinteger": { 403 | "version": "4.0.4", 404 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", 405 | "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" 406 | }, 407 | "lodash.isnumber": { 408 | "version": "3.0.3", 409 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", 410 | "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" 411 | }, 412 | "lodash.isplainobject": { 413 | "version": "4.0.6", 414 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", 415 | "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" 416 | }, 417 | "lodash.isstring": { 418 | "version": "4.0.1", 419 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", 420 | "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" 421 | }, 422 | "lodash.once": { 423 | "version": "4.1.1", 424 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", 425 | "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" 426 | }, 427 | "memory-pager": { 428 | "version": "1.5.0", 429 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", 430 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", 431 | "optional": true 432 | }, 433 | "merge-descriptors": { 434 | "version": "1.0.1", 435 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 436 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 437 | }, 438 | "methods": { 439 | "version": "1.1.2", 440 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 441 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 442 | }, 443 | "mime": { 444 | "version": "1.6.0", 445 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 446 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 447 | }, 448 | "mime-db": { 449 | "version": "1.40.0", 450 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", 451 | "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" 452 | }, 453 | "mongodb": { 454 | "version": "3.6.11", 455 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.11.tgz", 456 | "integrity": "sha512-4Y4lTFHDHZZdgMaHmojtNAlqkvddX2QQBEN0K//GzxhGwlI9tZ9R0vhbjr1Decw+TF7qK0ZLjQT292XgHRRQgw==", 457 | "requires": { 458 | "bl": "^2.2.1", 459 | "bson": "^1.1.4", 460 | "denque": "^1.4.1", 461 | "optional-require": "^1.0.3", 462 | "safe-buffer": "^5.1.2", 463 | "saslprep": "^1.0.0" 464 | } 465 | }, 466 | "mongoose": { 467 | "version": "5.13.8", 468 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.13.8.tgz", 469 | "integrity": "sha512-z3d+qei9Dem/LxRcJi0cdGPKzQnYk71oHEsEfYm17JA/vLiAbJiGuBS2hW7vkd9afkPAqu3KsPZh2ax0c5iPQw==", 470 | "requires": { 471 | "@types/mongodb": "^3.5.27", 472 | "bson": "^1.1.4", 473 | "kareem": "2.3.2", 474 | "mongodb": "3.6.11", 475 | "mongoose-legacy-pluralize": "1.0.2", 476 | "mpath": "0.8.3", 477 | "mquery": "3.2.5", 478 | "ms": "2.1.2", 479 | "optional-require": "1.0.x", 480 | "regexp-clone": "1.0.0", 481 | "safe-buffer": "5.2.1", 482 | "sift": "13.5.2", 483 | "sliced": "1.0.1" 484 | }, 485 | "dependencies": { 486 | "ms": { 487 | "version": "2.1.2", 488 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 489 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 490 | }, 491 | "safe-buffer": { 492 | "version": "5.2.1", 493 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 494 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 495 | } 496 | } 497 | }, 498 | "mongoose-legacy-pluralize": { 499 | "version": "1.0.2", 500 | "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", 501 | "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==" 502 | }, 503 | "mpath": { 504 | "version": "0.8.3", 505 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.3.tgz", 506 | "integrity": "sha512-eb9rRvhDltXVNL6Fxd2zM9D4vKBxjVVQNLNijlj7uoXUy19zNDsIif5zR+pWmPCWNKwAtqyo4JveQm4nfD5+eA==" 507 | }, 508 | "mquery": { 509 | "version": "3.2.5", 510 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.5.tgz", 511 | "integrity": "sha512-VjOKHHgU84wij7IUoZzFRU07IAxd5kWJaDmyUzQlbjHjyoeK5TNeeo8ZsFDtTYnSgpW6n/nMNIHvE3u8Lbrf4A==", 512 | "requires": { 513 | "bluebird": "3.5.1", 514 | "debug": "3.1.0", 515 | "regexp-clone": "^1.0.0", 516 | "safe-buffer": "5.1.2", 517 | "sliced": "1.0.1" 518 | }, 519 | "dependencies": { 520 | "debug": { 521 | "version": "3.1.0", 522 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 523 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 524 | "requires": { 525 | "ms": "2.0.0" 526 | } 527 | } 528 | } 529 | }, 530 | "ms": { 531 | "version": "2.0.0", 532 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 533 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 534 | }, 535 | "negotiator": { 536 | "version": "0.6.2", 537 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 538 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 539 | }, 540 | "optional-require": { 541 | "version": "1.0.3", 542 | "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.0.3.tgz", 543 | "integrity": "sha512-RV2Zp2MY2aeYK5G+B/Sps8lW5NHAzE5QClbFP15j+PWmP+T9PxlJXBOOLoSAdgwFvS4t0aMR4vpedMkbHfh0nA==" 544 | }, 545 | "parseurl": { 546 | "version": "1.3.3", 547 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 548 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 549 | }, 550 | "path-to-regexp": { 551 | "version": "0.1.7", 552 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 553 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 554 | }, 555 | "process-nextick-args": { 556 | "version": "2.0.1", 557 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 558 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 559 | }, 560 | "proxy-addr": { 561 | "version": "2.0.5", 562 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", 563 | "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", 564 | "requires": { 565 | "forwarded": "~0.1.2", 566 | "ipaddr.js": "1.9.0" 567 | } 568 | }, 569 | "range-parser": { 570 | "version": "1.2.1", 571 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 572 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 573 | }, 574 | "readable-stream": { 575 | "version": "2.3.6", 576 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", 577 | "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", 578 | "requires": { 579 | "core-util-is": "~1.0.0", 580 | "inherits": "~2.0.3", 581 | "isarray": "~1.0.0", 582 | "process-nextick-args": "~2.0.0", 583 | "safe-buffer": "~5.1.1", 584 | "string_decoder": "~1.1.1", 585 | "util-deprecate": "~1.0.1" 586 | } 587 | }, 588 | "regexp-clone": { 589 | "version": "1.0.0", 590 | "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", 591 | "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==" 592 | }, 593 | "safe-buffer": { 594 | "version": "5.1.2", 595 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 596 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 597 | }, 598 | "safer-buffer": { 599 | "version": "2.1.2", 600 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 601 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 602 | }, 603 | "saslprep": { 604 | "version": "1.0.3", 605 | "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", 606 | "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", 607 | "optional": true, 608 | "requires": { 609 | "sparse-bitfield": "^3.0.3" 610 | } 611 | }, 612 | "semver": { 613 | "version": "5.7.1", 614 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 615 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 616 | }, 617 | "send": { 618 | "version": "0.17.1", 619 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 620 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 621 | "requires": { 622 | "debug": "2.6.9", 623 | "depd": "~1.1.2", 624 | "destroy": "~1.0.4", 625 | "encodeurl": "~1.0.2", 626 | "escape-html": "~1.0.3", 627 | "etag": "~1.8.1", 628 | "fresh": "0.5.2", 629 | "http-errors": "~1.7.2", 630 | "mime": "1.6.0", 631 | "ms": "2.1.1", 632 | "on-finished": "~2.3.0", 633 | "range-parser": "~1.2.1", 634 | "statuses": "~1.5.0" 635 | }, 636 | "dependencies": { 637 | "depd": { 638 | "version": "1.1.2", 639 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 640 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 641 | }, 642 | "ee-first": { 643 | "version": "1.1.1", 644 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 645 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 646 | }, 647 | "ms": { 648 | "version": "2.1.1", 649 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 650 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 651 | }, 652 | "on-finished": { 653 | "version": "2.3.0", 654 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 655 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 656 | "requires": { 657 | "ee-first": "1.1.1" 658 | } 659 | } 660 | } 661 | }, 662 | "serve-static": { 663 | "version": "1.14.1", 664 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 665 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 666 | "requires": { 667 | "encodeurl": "~1.0.2", 668 | "escape-html": "~1.0.3", 669 | "parseurl": "~1.3.3", 670 | "send": "0.17.1" 671 | } 672 | }, 673 | "setprototypeof": { 674 | "version": "1.1.1", 675 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 676 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 677 | }, 678 | "sift": { 679 | "version": "13.5.2", 680 | "resolved": "https://registry.npmjs.org/sift/-/sift-13.5.2.tgz", 681 | "integrity": "sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA==" 682 | }, 683 | "sliced": { 684 | "version": "1.0.1", 685 | "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", 686 | "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" 687 | }, 688 | "sparse-bitfield": { 689 | "version": "3.0.3", 690 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", 691 | "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", 692 | "optional": true, 693 | "requires": { 694 | "memory-pager": "^1.0.2" 695 | } 696 | }, 697 | "statuses": { 698 | "version": "1.5.0", 699 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 700 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 701 | }, 702 | "string_decoder": { 703 | "version": "1.1.1", 704 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 705 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 706 | "requires": { 707 | "safe-buffer": "~5.1.0" 708 | } 709 | }, 710 | "toidentifier": { 711 | "version": "1.0.0", 712 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 713 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 714 | }, 715 | "unpipe": { 716 | "version": "1.0.0", 717 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 718 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 719 | }, 720 | "util-deprecate": { 721 | "version": "1.0.2", 722 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 723 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 724 | }, 725 | "utils-merge": { 726 | "version": "1.0.1", 727 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 728 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 729 | }, 730 | "uuid": { 731 | "version": "8.3.2", 732 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", 733 | "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" 734 | }, 735 | "vary": { 736 | "version": "1.1.2", 737 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 738 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 739 | } 740 | } 741 | } 742 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rest-api-tutorial", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node index.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/makinhs/rest-api-tutorial.git" 13 | }, 14 | "author": "", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/makinhs/rest-api-tutorial/issues" 18 | }, 19 | "homepage": "https://github.com/makinhs/rest-api-tutorial#readme", 20 | "dependencies": { 21 | "express": "^4.17.1", 22 | "jsonwebtoken": "^8.5.1", 23 | "mongoose": "^5.10.3", 24 | "uuid": "^8.3.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /users/controllers/users.controller.js: -------------------------------------------------------------------------------- 1 | const UserModel = require('../models/users.model'); 2 | const crypto = require('crypto'); 3 | 4 | exports.insert = (req, res) => { 5 | let salt = crypto.randomBytes(16).toString('base64'); 6 | let hash = crypto.createHmac('sha512', salt).update(req.body.password).digest("base64"); 7 | req.body.password = salt + "$" + hash; 8 | req.body.permissionLevel = 1; 9 | UserModel.createUser(req.body) 10 | .then((result) => { 11 | res.status(201).send({id: result._id}); 12 | }); 13 | }; 14 | 15 | exports.list = (req, res) => { 16 | let limit = req.query.limit && req.query.limit <= 100 ? parseInt(req.query.limit) : 10; 17 | let page = 0; 18 | if (req.query) { 19 | if (req.query.page) { 20 | req.query.page = parseInt(req.query.page); 21 | page = Number.isInteger(req.query.page) ? req.query.page : 0; 22 | } 23 | } 24 | UserModel.list(limit, page) 25 | .then((result) => { 26 | res.status(200).send(result); 27 | }) 28 | }; 29 | 30 | exports.getById = (req, res) => { 31 | UserModel.findById(req.params.userId) 32 | .then((result) => { 33 | res.status(200).send(result); 34 | }); 35 | }; 36 | exports.patchById = (req, res) => { 37 | if (req.body.password) { 38 | let salt = crypto.randomBytes(16).toString('base64'); 39 | let hash = crypto.createHmac('sha512', salt).update(req.body.password).digest("base64"); 40 | req.body.password = salt + "$" + hash; 41 | } 42 | 43 | UserModel.patchUser(req.params.userId, req.body) 44 | .then((result) => { 45 | res.status(204).send({}); 46 | }); 47 | 48 | }; 49 | 50 | exports.removeById = (req, res) => { 51 | UserModel.removeById(req.params.userId) 52 | .then((result)=>{ 53 | res.status(204).send({}); 54 | }); 55 | }; -------------------------------------------------------------------------------- /users/models/users.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('../../common/services/mongoose.service').mongoose; 2 | const Schema = mongoose.Schema; 3 | 4 | const userSchema = new Schema({ 5 | firstName: String, 6 | lastName: String, 7 | email: String, 8 | password: String, 9 | permissionLevel: Number 10 | }); 11 | 12 | userSchema.virtual('id').get(function () { 13 | return this._id.toHexString(); 14 | }); 15 | 16 | // Ensure virtual fields are serialised. 17 | userSchema.set('toJSON', { 18 | virtuals: true 19 | }); 20 | 21 | userSchema.findById = function (cb) { 22 | return this.model('Users').find({id: this.id}, cb); 23 | }; 24 | 25 | const User = mongoose.model('Users', userSchema); 26 | 27 | 28 | exports.findByEmail = (email) => { 29 | return User.find({email: email}); 30 | }; 31 | exports.findById = (id) => { 32 | return User.findById(id) 33 | .then((result) => { 34 | result = result.toJSON(); 35 | delete result._id; 36 | delete result.__v; 37 | return result; 38 | }); 39 | }; 40 | 41 | exports.createUser = (userData) => { 42 | const user = new User(userData); 43 | return user.save(); 44 | }; 45 | 46 | exports.list = (perPage, page) => { 47 | return new Promise((resolve, reject) => { 48 | User.find() 49 | .limit(perPage) 50 | .skip(perPage * page) 51 | .exec(function (err, users) { 52 | if (err) { 53 | reject(err); 54 | } else { 55 | resolve(users); 56 | } 57 | }) 58 | }); 59 | }; 60 | 61 | exports.patchUser = (id, userData) => { 62 | return User.findOneAndUpdate({ 63 | _id: id 64 | }, userData); 65 | }; 66 | 67 | exports.removeById = (userId) => { 68 | return new Promise((resolve, reject) => { 69 | User.deleteMany({_id: userId}, (err) => { 70 | if (err) { 71 | reject(err); 72 | } else { 73 | resolve(err); 74 | } 75 | }); 76 | }); 77 | }; 78 | 79 | -------------------------------------------------------------------------------- /users/routes.config.js: -------------------------------------------------------------------------------- 1 | const UsersController = require('./controllers/users.controller'); 2 | const PermissionMiddleware = require('../common/middlewares/auth.permission.middleware'); 3 | const ValidationMiddleware = require('../common/middlewares/auth.validation.middleware'); 4 | const config = require('../common/config/env.config'); 5 | 6 | const ADMIN = config.permissionLevels.ADMIN; 7 | const PAID = config.permissionLevels.PAID_USER; 8 | const FREE = config.permissionLevels.NORMAL_USER; 9 | 10 | exports.routesConfig = function (app) { 11 | app.post('/users', [ 12 | UsersController.insert 13 | ]); 14 | app.get('/users', [ 15 | ValidationMiddleware.validJWTNeeded, 16 | PermissionMiddleware.minimumPermissionLevelRequired(PAID), 17 | UsersController.list 18 | ]); 19 | app.get('/users/:userId', [ 20 | ValidationMiddleware.validJWTNeeded, 21 | PermissionMiddleware.minimumPermissionLevelRequired(FREE), 22 | PermissionMiddleware.onlySameUserOrAdminCanDoThisAction, 23 | UsersController.getById 24 | ]); 25 | app.patch('/users/:userId', [ 26 | ValidationMiddleware.validJWTNeeded, 27 | PermissionMiddleware.minimumPermissionLevelRequired(FREE), 28 | PermissionMiddleware.onlySameUserOrAdminCanDoThisAction, 29 | UsersController.patchById 30 | ]); 31 | app.delete('/users/:userId', [ 32 | ValidationMiddleware.validJWTNeeded, 33 | PermissionMiddleware.minimumPermissionLevelRequired(ADMIN), 34 | UsersController.removeById 35 | ]); 36 | }; 37 | --------------------------------------------------------------------------------