├── .gitignore ├── README.md ├── app.js ├── middlewares ├── cleanbody.js └── validateToken.js ├── package-lock.json ├── package.json ├── routes └── users.js └── src └── users ├── helpers ├── generateJwt.js └── mailer.js ├── user.controller.js └── user.model.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | package-lock.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Product Name Screen Shot](https://miro.medium.com/max/1920/1*bbN8C4TjOpsuZhMiI_aGow.jpeg)](https://pranesh-a-s.medium.com/how-to-build-simple-and-secure-rest-api-for-user-authentication-using-node-js-jwt-and-mongodb-2bdeb3e5427e) 2 | 3 |                          4 | [![language](https://img.shields.io/github/languages/top/PraneshASP/node-authentication-jwt-mongodb)]("https://github.com/praneshasp/node-authentication-jwt-mongodb") 5 | [![vulnerability](https://img.shields.io/snyk/vulnerabilities/github/PraneshASP/node-authentication-jwt-mongodb)](https://github.com/PraneshASP/node-authentication-jwt-mongodb) 6 | [![repo size](https://img.shields.io/github/repo-size/PraneshASP/node-authentication-jwt-mongodb)](https://github.com/PraneshASP/node-authentication-jwt-mongodb) 7 | [![node version](https://img.shields.io/node/v/npm)](https://github.com/PraneshASP/node-authentication-jwt-mongodb) 8 | [![npm version](https://img.shields.io/npm/v/npm)](https://github.com/PraneshASP/node-authentication-jwt-mongodb) 9 | 10 | 11 |

12 |

Simple and secure REST API for User Authentication!

13 |

14 | Explore the post » 15 |

16 | Report Bug 17 | · 18 | Request Feature 19 |

20 |

21 | 22 | 23 | 24 | ### What's inside this repo? 25 | 26 | 1. User signup/registration with Email verification. 27 | 2. User Login. 28 | 3. Forgot password and reset password. 29 | 4. Session management using JWT (JSON Web Tokens). 30 | 5. Bonus: Simple Referral System! 31 | 32 | For a more detailed explanation of the code, you can refer to my medium post associated with this project. It is split into two parts. 33 | 34 | - [Part I](https://pranesh-a-s.medium.com/how-to-build-simple-and-secure-rest-api-for-user-authentication-using-node-js-jwt-and-mongodb-2bdeb3e5427e) - Build User signup with email verification, forgot password and reset password. 35 | 36 | - [Part II](https://pranesh-a-s.medium.com/jwt-integration-with-an-example-referral-system-using-nodejs-mongodb-and-expressjs-17be91b35f1c) - Integrate JWT and Build a Simple Referral System. 37 | 38 | ### Built With 39 | 40 | - [Node.js]() - JavaScript runtime built on Chrome's V8 JavaScript engine. 41 | - [Express.js]() - Minimal and flexible Node.js web application framework 42 | - [MongoDB]() - Cross-platform document-oriented database program 43 | 44 | 45 | 46 | ## Getting Started 47 | 48 | To get a local copy up and running follow these simple steps : 49 | 50 | ### Prerequisites 51 | 52 | To run this project, you'll need to have the following installed: 53 | 54 | - Node.js : [https://nodejs.org](https://nodejs.org) 55 | 56 | - npm : 57 | ```sh 58 | npm install npm@latest -g 59 | ``` 60 | - MongoDB : [https://mongodb.com](https://mongodb.com)
61 | 62 | > You can also use MongoDB Atlas if you prefer. 63 | >
64 | 65 | ### Installation 66 | 67 | 1. Register at [SendGrid](https://sendgrid.com) SendGrid and create an API KEY. 68 | 69 | 2. Clone the repo : 70 | ```sh 71 | git clone https://github.com/PraneshASP/node-authentication-jwt-mongodb.git 72 | ``` 73 | 3. Install dependencies (use `sudo` if required) : 74 | 75 | ```sh 76 | npm install 77 | ``` 78 | 79 | 4. Create `.env` file and configure : 80 | ```JS 81 | MONGO_URI = 82 | JWT_SECRET = 83 | SG_APIKEY = //For sending emails 84 | ``` 85 | 5. Start the server : 86 | ```sh 87 | npm start 88 | ``` 89 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const mongoose = require("mongoose"); 3 | const bodyParser = require("body-parser"); 4 | require("dotenv").config(); 5 | const PORT = 5000; 6 | 7 | const authRoutes = require("./routes/users"); 8 | 9 | mongoose 10 | .connect(process.env.MONGO_URI, { 11 | dbName: "TheNodeAuth", 12 | useNewUrlParser: true, 13 | useUnifiedTopology: true, 14 | }) 15 | .then(() => { 16 | console.log("Database connection Success."); 17 | }) 18 | .catch((err) => { 19 | console.error("Mongo Connection Error", err); 20 | }); 21 | 22 | const app = express(); 23 | 24 | app.use(bodyParser.json()); 25 | app.use(bodyParser.urlencoded({ extended: true })); 26 | 27 | app.get("/ping", (req, res) => { 28 | return res.send({ 29 | error: false, 30 | message: "Server is healthy", 31 | }); 32 | }); 33 | 34 | app.use("/users", authRoutes); 35 | 36 | app.listen(PORT, () => { 37 | console.log("Server started listening on PORT : " + PORT); 38 | }); 39 | -------------------------------------------------------------------------------- /middlewares/cleanbody.js: -------------------------------------------------------------------------------- 1 | const sanitize = require("mongo-sanitize"); 2 | 3 | module.exports = (req, res, next) => { 4 | try { 5 | req.body = sanitize(req.body); 6 | next(); 7 | } catch (error) { 8 | console.log("clean-body-error", error); 9 | return res.status(500).json({ 10 | error: true, 11 | message: "Could not sanitize body", 12 | }); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /middlewares/validateToken.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | require("dotenv").config(); 3 | 4 | const User = require("../src/users/user.model"); 5 | 6 | async function validateToken(req, res, next) { 7 | const authorizationHeader = req.headers.authorization; 8 | let result; 9 | if (!authorizationHeader) 10 | return res.status(401).json({ 11 | error: true, 12 | message: "Access token is missing", 13 | }); 14 | 15 | const token = req.headers.authorization.split(" ")[1]; // Bearer 16 | const options = { 17 | expiresIn: "1h", 18 | }; 19 | try { 20 | let user = await User.findOne({ 21 | accessToken: token, 22 | }); 23 | // console.log(token); 24 | if (!user) { 25 | result = { 26 | error: true, 27 | message: `Authorization error`, 28 | }; 29 | return res.status(403).json(result); 30 | } 31 | 32 | result = jwt.verify(token, process.env.JWT_SECRET, options); 33 | 34 | if (!user.userId === result.id) { 35 | result = { 36 | error: true, 37 | message: `Invalid token`, 38 | }; 39 | 40 | return res.status(401).json(result); 41 | } 42 | 43 | result["referralCode"] = user.referralCode; 44 | 45 | req.decoded = result; 46 | next(); 47 | } catch (err) { 48 | // console.log(err); 49 | if (err.name === "TokenExpiredError") { 50 | result = { 51 | error: true, 52 | message: `TokenExpired`, 53 | }; 54 | } else { 55 | result = { 56 | error: true, 57 | message: `Authentication error`, 58 | }; 59 | } 60 | return res.status(403).json(result); 61 | } 62 | } 63 | 64 | module.exports = { validateToken }; 65 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auth", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@hapi/hoek": { 8 | "version": "9.1.1", 9 | "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.1.1.tgz", 10 | "integrity": "sha512-CAEbWH7OIur6jEOzaai83jq3FmKmv4PmX1JYfs9IrYcGEVI/lyL1EXJGCj7eFVJ0bg5QR8LMxBlEtA+xKiLpFw==" 11 | }, 12 | "@hapi/topo": { 13 | "version": "5.0.0", 14 | "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.0.0.tgz", 15 | "integrity": "sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw==", 16 | "requires": { 17 | "@hapi/hoek": "^9.0.0" 18 | } 19 | }, 20 | "@sideway/address": { 21 | "version": "4.1.0", 22 | "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.0.tgz", 23 | "integrity": "sha512-wAH/JYRXeIFQRsxerIuLjgUu2Xszam+O5xKeatJ4oudShOOirfmsQ1D6LL54XOU2tizpCYku+s1wmU0SYdpoSA==", 24 | "requires": { 25 | "@hapi/hoek": "^9.0.0" 26 | } 27 | }, 28 | "@sideway/formula": { 29 | "version": "3.0.0", 30 | "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", 31 | "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==" 32 | }, 33 | "@sideway/pinpoint": { 34 | "version": "2.0.0", 35 | "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", 36 | "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" 37 | }, 38 | "@types/bson": { 39 | "version": "4.0.3", 40 | "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.3.tgz", 41 | "integrity": "sha512-mVRvYnTOZJz3ccpxhr3wgxVmSeiYinW+zlzQz3SXWaJmD1DuL05Jeq7nKw3SnbKmbleW5qrLG5vdyWe/A9sXhw==", 42 | "requires": { 43 | "@types/node": "*" 44 | } 45 | }, 46 | "@types/mongodb": { 47 | "version": "3.6.3", 48 | "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.3.tgz", 49 | "integrity": "sha512-6YNqGP1hk5bjUFaim+QoFFuI61WjHiHE1BNeB41TA00Xd2K7zG4lcWyLLq/XtIp36uMavvS5hoAUJ+1u/GcX2Q==", 50 | "requires": { 51 | "@types/bson": "*", 52 | "@types/node": "*" 53 | } 54 | }, 55 | "@types/node": { 56 | "version": "14.14.20", 57 | "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.20.tgz", 58 | "integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A==" 59 | }, 60 | "accepts": { 61 | "version": "1.3.7", 62 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 63 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 64 | "requires": { 65 | "mime-types": "~2.1.24", 66 | "negotiator": "0.6.2" 67 | } 68 | }, 69 | "array-flatten": { 70 | "version": "1.1.1", 71 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 72 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 73 | }, 74 | "bcryptjs": { 75 | "version": "2.4.3", 76 | "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", 77 | "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" 78 | }, 79 | "bl": { 80 | "version": "2.2.1", 81 | "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", 82 | "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", 83 | "requires": { 84 | "readable-stream": "^2.3.5", 85 | "safe-buffer": "^5.1.1" 86 | } 87 | }, 88 | "bluebird": { 89 | "version": "3.5.1", 90 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", 91 | "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" 92 | }, 93 | "body-parser": { 94 | "version": "1.19.0", 95 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 96 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 97 | "requires": { 98 | "bytes": "3.1.0", 99 | "content-type": "~1.0.4", 100 | "debug": "2.6.9", 101 | "depd": "~1.1.2", 102 | "http-errors": "1.7.2", 103 | "iconv-lite": "0.4.24", 104 | "on-finished": "~2.3.0", 105 | "qs": "6.7.0", 106 | "raw-body": "2.4.0", 107 | "type-is": "~1.6.17" 108 | } 109 | }, 110 | "bson": { 111 | "version": "1.1.5", 112 | "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz", 113 | "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg==" 114 | }, 115 | "buffer-equal-constant-time": { 116 | "version": "1.0.1", 117 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", 118 | "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" 119 | }, 120 | "bytes": { 121 | "version": "3.1.0", 122 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 123 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 124 | }, 125 | "content-disposition": { 126 | "version": "0.5.3", 127 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 128 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 129 | "requires": { 130 | "safe-buffer": "5.1.2" 131 | } 132 | }, 133 | "content-type": { 134 | "version": "1.0.4", 135 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 136 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 137 | }, 138 | "cookie": { 139 | "version": "0.4.0", 140 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 141 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 142 | }, 143 | "cookie-signature": { 144 | "version": "1.0.6", 145 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 146 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 147 | }, 148 | "core-util-is": { 149 | "version": "1.0.2", 150 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 151 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 152 | }, 153 | "debug": { 154 | "version": "2.6.9", 155 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 156 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 157 | "requires": { 158 | "ms": "2.0.0" 159 | } 160 | }, 161 | "denque": { 162 | "version": "1.5.0", 163 | "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz", 164 | "integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==" 165 | }, 166 | "depd": { 167 | "version": "1.1.2", 168 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 169 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 170 | }, 171 | "destroy": { 172 | "version": "1.0.4", 173 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 174 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 175 | }, 176 | "dotenv": { 177 | "version": "8.2.0", 178 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", 179 | "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" 180 | }, 181 | "ecdsa-sig-formatter": { 182 | "version": "1.0.11", 183 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", 184 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", 185 | "requires": { 186 | "safe-buffer": "^5.0.1" 187 | } 188 | }, 189 | "ee-first": { 190 | "version": "1.1.1", 191 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 192 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 193 | }, 194 | "encodeurl": { 195 | "version": "1.0.2", 196 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 197 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 198 | }, 199 | "escape-html": { 200 | "version": "1.0.3", 201 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 202 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 203 | }, 204 | "etag": { 205 | "version": "1.8.1", 206 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 207 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 208 | }, 209 | "express": { 210 | "version": "4.17.1", 211 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 212 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 213 | "requires": { 214 | "accepts": "~1.3.7", 215 | "array-flatten": "1.1.1", 216 | "body-parser": "1.19.0", 217 | "content-disposition": "0.5.3", 218 | "content-type": "~1.0.4", 219 | "cookie": "0.4.0", 220 | "cookie-signature": "1.0.6", 221 | "debug": "2.6.9", 222 | "depd": "~1.1.2", 223 | "encodeurl": "~1.0.2", 224 | "escape-html": "~1.0.3", 225 | "etag": "~1.8.1", 226 | "finalhandler": "~1.1.2", 227 | "fresh": "0.5.2", 228 | "merge-descriptors": "1.0.1", 229 | "methods": "~1.1.2", 230 | "on-finished": "~2.3.0", 231 | "parseurl": "~1.3.3", 232 | "path-to-regexp": "0.1.7", 233 | "proxy-addr": "~2.0.5", 234 | "qs": "6.7.0", 235 | "range-parser": "~1.2.1", 236 | "safe-buffer": "5.1.2", 237 | "send": "0.17.1", 238 | "serve-static": "1.14.1", 239 | "setprototypeof": "1.1.1", 240 | "statuses": "~1.5.0", 241 | "type-is": "~1.6.18", 242 | "utils-merge": "1.0.1", 243 | "vary": "~1.1.2" 244 | } 245 | }, 246 | "finalhandler": { 247 | "version": "1.1.2", 248 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 249 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 250 | "requires": { 251 | "debug": "2.6.9", 252 | "encodeurl": "~1.0.2", 253 | "escape-html": "~1.0.3", 254 | "on-finished": "~2.3.0", 255 | "parseurl": "~1.3.3", 256 | "statuses": "~1.5.0", 257 | "unpipe": "~1.0.0" 258 | } 259 | }, 260 | "forwarded": { 261 | "version": "0.1.2", 262 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 263 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 264 | }, 265 | "fresh": { 266 | "version": "0.5.2", 267 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 268 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 269 | }, 270 | "http-errors": { 271 | "version": "1.7.2", 272 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 273 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 274 | "requires": { 275 | "depd": "~1.1.2", 276 | "inherits": "2.0.3", 277 | "setprototypeof": "1.1.1", 278 | "statuses": ">= 1.5.0 < 2", 279 | "toidentifier": "1.0.0" 280 | } 281 | }, 282 | "iconv-lite": { 283 | "version": "0.4.24", 284 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 285 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 286 | "requires": { 287 | "safer-buffer": ">= 2.1.2 < 3" 288 | } 289 | }, 290 | "inherits": { 291 | "version": "2.0.3", 292 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 293 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 294 | }, 295 | "ipaddr.js": { 296 | "version": "1.9.1", 297 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 298 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 299 | }, 300 | "isarray": { 301 | "version": "1.0.0", 302 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 303 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 304 | }, 305 | "joi": { 306 | "version": "17.3.0", 307 | "resolved": "https://registry.npmjs.org/joi/-/joi-17.3.0.tgz", 308 | "integrity": "sha512-Qh5gdU6niuYbUIUV5ejbsMiiFmBdw8Kcp8Buj2JntszCkCfxJ9Cz76OtHxOZMPXrt5810iDIXs+n1nNVoquHgg==", 309 | "requires": { 310 | "@hapi/hoek": "^9.0.0", 311 | "@hapi/topo": "^5.0.0", 312 | "@sideway/address": "^4.1.0", 313 | "@sideway/formula": "^3.0.0", 314 | "@sideway/pinpoint": "^2.0.0" 315 | } 316 | }, 317 | "jsonwebtoken": { 318 | "version": "8.5.1", 319 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", 320 | "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", 321 | "requires": { 322 | "jws": "^3.2.2", 323 | "lodash.includes": "^4.3.0", 324 | "lodash.isboolean": "^3.0.3", 325 | "lodash.isinteger": "^4.0.4", 326 | "lodash.isnumber": "^3.0.3", 327 | "lodash.isplainobject": "^4.0.6", 328 | "lodash.isstring": "^4.0.1", 329 | "lodash.once": "^4.0.0", 330 | "ms": "^2.1.1", 331 | "semver": "^5.6.0" 332 | }, 333 | "dependencies": { 334 | "ms": { 335 | "version": "2.1.3", 336 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 337 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 338 | } 339 | } 340 | }, 341 | "jwa": { 342 | "version": "1.4.1", 343 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", 344 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", 345 | "requires": { 346 | "buffer-equal-constant-time": "1.0.1", 347 | "ecdsa-sig-formatter": "1.0.11", 348 | "safe-buffer": "^5.0.1" 349 | } 350 | }, 351 | "jws": { 352 | "version": "3.2.2", 353 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", 354 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", 355 | "requires": { 356 | "jwa": "^1.4.1", 357 | "safe-buffer": "^5.0.1" 358 | } 359 | }, 360 | "kareem": { 361 | "version": "2.3.2", 362 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", 363 | "integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==" 364 | }, 365 | "lodash.includes": { 366 | "version": "4.3.0", 367 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", 368 | "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" 369 | }, 370 | "lodash.isboolean": { 371 | "version": "3.0.3", 372 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", 373 | "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" 374 | }, 375 | "lodash.isinteger": { 376 | "version": "4.0.4", 377 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", 378 | "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" 379 | }, 380 | "lodash.isnumber": { 381 | "version": "3.0.3", 382 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", 383 | "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" 384 | }, 385 | "lodash.isplainobject": { 386 | "version": "4.0.6", 387 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", 388 | "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" 389 | }, 390 | "lodash.isstring": { 391 | "version": "4.0.1", 392 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", 393 | "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" 394 | }, 395 | "lodash.once": { 396 | "version": "4.1.1", 397 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", 398 | "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" 399 | }, 400 | "media-typer": { 401 | "version": "0.3.0", 402 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 403 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 404 | }, 405 | "memory-pager": { 406 | "version": "1.5.0", 407 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", 408 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", 409 | "optional": true 410 | }, 411 | "merge-descriptors": { 412 | "version": "1.0.1", 413 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 414 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 415 | }, 416 | "methods": { 417 | "version": "1.1.2", 418 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 419 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 420 | }, 421 | "mime": { 422 | "version": "1.6.0", 423 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 424 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 425 | }, 426 | "mime-db": { 427 | "version": "1.45.0", 428 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", 429 | "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==" 430 | }, 431 | "mime-types": { 432 | "version": "2.1.28", 433 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz", 434 | "integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==", 435 | "requires": { 436 | "mime-db": "1.45.0" 437 | } 438 | }, 439 | "mongo-sanitize": { 440 | "version": "1.1.0", 441 | "resolved": "https://registry.npmjs.org/mongo-sanitize/-/mongo-sanitize-1.1.0.tgz", 442 | "integrity": "sha512-6gB9AiJD+om2eZLxaPKIP5Q8P3Fr+s+17rVWso7hU0+MAzmIvIMlgTYuyvalDLTtE/p0gczcvJ8A3pbN1XmQ/A==" 443 | }, 444 | "mongodb": { 445 | "version": "3.6.3", 446 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.3.tgz", 447 | "integrity": "sha512-rOZuR0QkodZiM+UbQE5kDsJykBqWi0CL4Ec2i1nrGrUI3KO11r6Fbxskqmq3JK2NH7aW4dcccBuUujAP0ERl5w==", 448 | "requires": { 449 | "bl": "^2.2.1", 450 | "bson": "^1.1.4", 451 | "denque": "^1.4.1", 452 | "require_optional": "^1.0.1", 453 | "safe-buffer": "^5.1.2", 454 | "saslprep": "^1.0.0" 455 | } 456 | }, 457 | "mongoose": { 458 | "version": "5.11.10", 459 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.11.10.tgz", 460 | "integrity": "sha512-daE2L6VW7WNywv7tL2KUkBViWvODbzr50Of1kJpIbzW3w3N5/TYcgSmhCsEDWfYGQXbun2rdd7+sOdsEC8zQSQ==", 461 | "requires": { 462 | "@types/mongodb": "^3.5.27", 463 | "bson": "^1.1.4", 464 | "kareem": "2.3.2", 465 | "mongodb": "3.6.3", 466 | "mongoose-legacy-pluralize": "1.0.2", 467 | "mpath": "0.8.3", 468 | "mquery": "3.2.3", 469 | "ms": "2.1.2", 470 | "regexp-clone": "1.0.0", 471 | "safe-buffer": "5.2.1", 472 | "sift": "7.0.1", 473 | "sliced": "1.0.1" 474 | }, 475 | "dependencies": { 476 | "ms": { 477 | "version": "2.1.2", 478 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 479 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 480 | }, 481 | "safe-buffer": { 482 | "version": "5.2.1", 483 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 484 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 485 | } 486 | } 487 | }, 488 | "mongoose-legacy-pluralize": { 489 | "version": "1.0.2", 490 | "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", 491 | "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==" 492 | }, 493 | "mpath": { 494 | "version": "0.8.3", 495 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.3.tgz", 496 | "integrity": "sha512-eb9rRvhDltXVNL6Fxd2zM9D4vKBxjVVQNLNijlj7uoXUy19zNDsIif5zR+pWmPCWNKwAtqyo4JveQm4nfD5+eA==" 497 | }, 498 | "mquery": { 499 | "version": "3.2.3", 500 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.3.tgz", 501 | "integrity": "sha512-cIfbP4TyMYX+SkaQ2MntD+F2XbqaBHUYWk3j+kqdDztPWok3tgyssOZxMHMtzbV1w9DaSlvEea0Iocuro41A4g==", 502 | "requires": { 503 | "bluebird": "3.5.1", 504 | "debug": "3.1.0", 505 | "regexp-clone": "^1.0.0", 506 | "safe-buffer": "5.1.2", 507 | "sliced": "1.0.1" 508 | }, 509 | "dependencies": { 510 | "debug": { 511 | "version": "3.1.0", 512 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 513 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 514 | "requires": { 515 | "ms": "2.0.0" 516 | } 517 | } 518 | } 519 | }, 520 | "ms": { 521 | "version": "2.0.0", 522 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 523 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 524 | }, 525 | "nanoid": { 526 | "version": "3.1.20", 527 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", 528 | "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==" 529 | }, 530 | "negotiator": { 531 | "version": "0.6.2", 532 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 533 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 534 | }, 535 | "nodemailer": { 536 | "version": "6.4.17", 537 | "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.4.17.tgz", 538 | "integrity": "sha512-89ps+SBGpo0D4Bi5ZrxcrCiRFaMmkCt+gItMXQGzEtZVR3uAD3QAQIDoxTWnx3ky0Dwwy/dhFrQ+6NNGXpw/qQ==" 539 | }, 540 | "on-finished": { 541 | "version": "2.3.0", 542 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 543 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 544 | "requires": { 545 | "ee-first": "1.1.1" 546 | } 547 | }, 548 | "parseurl": { 549 | "version": "1.3.3", 550 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 551 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 552 | }, 553 | "path-to-regexp": { 554 | "version": "0.1.7", 555 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 556 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 557 | }, 558 | "process-nextick-args": { 559 | "version": "2.0.1", 560 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 561 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 562 | }, 563 | "proxy-addr": { 564 | "version": "2.0.6", 565 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", 566 | "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", 567 | "requires": { 568 | "forwarded": "~0.1.2", 569 | "ipaddr.js": "1.9.1" 570 | } 571 | }, 572 | "qs": { 573 | "version": "6.7.0", 574 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 575 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 576 | }, 577 | "range-parser": { 578 | "version": "1.2.1", 579 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 580 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 581 | }, 582 | "raw-body": { 583 | "version": "2.4.0", 584 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 585 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 586 | "requires": { 587 | "bytes": "3.1.0", 588 | "http-errors": "1.7.2", 589 | "iconv-lite": "0.4.24", 590 | "unpipe": "1.0.0" 591 | } 592 | }, 593 | "readable-stream": { 594 | "version": "2.3.7", 595 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 596 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 597 | "requires": { 598 | "core-util-is": "~1.0.0", 599 | "inherits": "~2.0.3", 600 | "isarray": "~1.0.0", 601 | "process-nextick-args": "~2.0.0", 602 | "safe-buffer": "~5.1.1", 603 | "string_decoder": "~1.1.1", 604 | "util-deprecate": "~1.0.1" 605 | } 606 | }, 607 | "regexp-clone": { 608 | "version": "1.0.0", 609 | "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", 610 | "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==" 611 | }, 612 | "require_optional": { 613 | "version": "1.0.1", 614 | "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", 615 | "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", 616 | "requires": { 617 | "resolve-from": "^2.0.0", 618 | "semver": "^5.1.0" 619 | } 620 | }, 621 | "resolve-from": { 622 | "version": "2.0.0", 623 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", 624 | "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" 625 | }, 626 | "safe-buffer": { 627 | "version": "5.1.2", 628 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 629 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 630 | }, 631 | "safer-buffer": { 632 | "version": "2.1.2", 633 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 634 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 635 | }, 636 | "saslprep": { 637 | "version": "1.0.3", 638 | "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", 639 | "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", 640 | "optional": true, 641 | "requires": { 642 | "sparse-bitfield": "^3.0.3" 643 | } 644 | }, 645 | "semver": { 646 | "version": "5.7.1", 647 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 648 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 649 | }, 650 | "send": { 651 | "version": "0.17.1", 652 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 653 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 654 | "requires": { 655 | "debug": "2.6.9", 656 | "depd": "~1.1.2", 657 | "destroy": "~1.0.4", 658 | "encodeurl": "~1.0.2", 659 | "escape-html": "~1.0.3", 660 | "etag": "~1.8.1", 661 | "fresh": "0.5.2", 662 | "http-errors": "~1.7.2", 663 | "mime": "1.6.0", 664 | "ms": "2.1.1", 665 | "on-finished": "~2.3.0", 666 | "range-parser": "~1.2.1", 667 | "statuses": "~1.5.0" 668 | }, 669 | "dependencies": { 670 | "ms": { 671 | "version": "2.1.1", 672 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 673 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 674 | } 675 | } 676 | }, 677 | "serve-static": { 678 | "version": "1.14.1", 679 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 680 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 681 | "requires": { 682 | "encodeurl": "~1.0.2", 683 | "escape-html": "~1.0.3", 684 | "parseurl": "~1.3.3", 685 | "send": "0.17.1" 686 | } 687 | }, 688 | "setprototypeof": { 689 | "version": "1.1.1", 690 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 691 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 692 | }, 693 | "sift": { 694 | "version": "7.0.1", 695 | "resolved": "https://registry.npmjs.org/sift/-/sift-7.0.1.tgz", 696 | "integrity": "sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g==" 697 | }, 698 | "sliced": { 699 | "version": "1.0.1", 700 | "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", 701 | "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" 702 | }, 703 | "sparse-bitfield": { 704 | "version": "3.0.3", 705 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", 706 | "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", 707 | "optional": true, 708 | "requires": { 709 | "memory-pager": "^1.0.2" 710 | } 711 | }, 712 | "statuses": { 713 | "version": "1.5.0", 714 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 715 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 716 | }, 717 | "string_decoder": { 718 | "version": "1.1.1", 719 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 720 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 721 | "requires": { 722 | "safe-buffer": "~5.1.0" 723 | } 724 | }, 725 | "toidentifier": { 726 | "version": "1.0.0", 727 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 728 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 729 | }, 730 | "type-is": { 731 | "version": "1.6.18", 732 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 733 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 734 | "requires": { 735 | "media-typer": "0.3.0", 736 | "mime-types": "~2.1.24" 737 | } 738 | }, 739 | "unpipe": { 740 | "version": "1.0.0", 741 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 742 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 743 | }, 744 | "util-deprecate": { 745 | "version": "1.0.2", 746 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 747 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 748 | }, 749 | "utils-merge": { 750 | "version": "1.0.1", 751 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 752 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 753 | }, 754 | "uuid": { 755 | "version": "8.3.2", 756 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", 757 | "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" 758 | }, 759 | "vary": { 760 | "version": "1.1.2", 761 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 762 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 763 | } 764 | } 765 | } 766 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auth", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "node app.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "bcryptjs": "^2.4.3", 14 | "body-parser": "^1.19.0", 15 | "dotenv": "^8.2.0", 16 | "express": "^4.17.1", 17 | "joi": "^17.3.0", 18 | "jsonwebtoken": "^8.5.1", 19 | "mongo-sanitize": "^1.1.0", 20 | "mongoose": "^5.11.10", 21 | "nanoid": "^3.1.20", 22 | "nodemailer": "^6.4.17", 23 | "uuid": "^8.3.2" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /routes/users.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | 4 | const cleanBody = require("../middlewares/cleanbody"); 5 | const { validateToken } = require("../middlewares/validateToken"); 6 | 7 | const AuthController = require("../src/users/user.controller"); 8 | 9 | router.post("/signup", cleanBody, AuthController.Signup); 10 | 11 | router.patch("/activate", cleanBody, AuthController.Activate); 12 | 13 | router.post("/login", cleanBody, AuthController.Login); 14 | 15 | router.patch("/forgot", cleanBody, AuthController.ForgotPassword); 16 | 17 | router.patch("/reset", cleanBody, AuthController.ResetPassword); 18 | 19 | router.get("/referred", validateToken, AuthController.ReferredAccounts); 20 | 21 | router.get("/logout", validateToken, AuthController.Logout); 22 | 23 | module.exports = router; 24 | -------------------------------------------------------------------------------- /src/users/helpers/generateJwt.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | require("dotenv").config(); 3 | 4 | const options = { 5 | expiresIn: "1h", 6 | }; 7 | 8 | async function generateJwt(email, userId) { 9 | try { 10 | const payload = { email: email, id: userId }; 11 | const token = await jwt.sign(payload, process.env.JWT_SECRET, options); 12 | return { error: false, token: token }; 13 | } catch (error) { 14 | return { error: true }; 15 | } 16 | } 17 | 18 | module.exports = { generateJwt }; 19 | -------------------------------------------------------------------------------- /src/users/helpers/mailer.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | const nodemailer = require("nodemailer"); 3 | 4 | async function sendEmail(email, code) { 5 | try { 6 | const smtpEndpoint = "smtp.sendgrid.net"; 7 | 8 | const port = 465; 9 | 10 | const senderAddress = "NAME
"; 11 | 12 | var toAddress = email; 13 | 14 | const smtpUsername = "apikey"; 15 | 16 | const smtpPassword = process.env.SG_APIKEY; 17 | 18 | var subject = "Verify your email"; 19 | 20 | // The body of the email for recipients 21 | var body_html = ` 22 | 23 | 24 |

Your authentication code is :

${code} 25 | 26 | `; 27 | 28 | // Create the SMTP transport. 29 | let transporter = nodemailer.createTransport({ 30 | host: smtpEndpoint, 31 | port: port, 32 | secure: true, // true for 465, false for other ports 33 | auth: { 34 | user: smtpUsername, 35 | pass: smtpPassword, 36 | }, 37 | }); 38 | 39 | // Specify the fields in the email. 40 | let mailOptions = { 41 | from: senderAddress, 42 | to: toAddress, 43 | subject: subject, 44 | html: body_html, 45 | }; 46 | 47 | let info = await transporter.sendMail(mailOptions); 48 | return { error: false }; 49 | } catch (error) { 50 | console.error("send-email-error", error); 51 | return { 52 | error: true, 53 | message: "Cannot send email", 54 | }; 55 | } 56 | } 57 | 58 | module.exports = { sendEmail }; 59 | -------------------------------------------------------------------------------- /src/users/user.controller.js: -------------------------------------------------------------------------------- 1 | const Joi = require("joi"); 2 | require("dotenv").config(); 3 | const { v4: uuid } = require("uuid"); 4 | const { customAlphabet: generate } = require("nanoid"); 5 | 6 | const { generateJwt } = require("./helpers/generateJwt"); 7 | const { sendEmail } = require("./helpers/mailer"); 8 | const User = require("./user.model"); 9 | 10 | const CHARACTER_SET = 11 | "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 12 | 13 | const REFERRAL_CODE_LENGTH = 8; 14 | 15 | const referralCode = generate(CHARACTER_SET, REFERRAL_CODE_LENGTH); 16 | 17 | //Validate user schema 18 | const userSchema = Joi.object().keys({ 19 | email: Joi.string().email({ minDomainSegments: 2 }), 20 | password: Joi.string().required().min(4), 21 | confirmPassword: Joi.string().valid(Joi.ref("password")).required(), 22 | referrer: Joi.string(), 23 | }); 24 | 25 | exports.Signup = async (req, res) => { 26 | try { 27 | const result = userSchema.validate(req.body); 28 | if (result.error) { 29 | console.log(result.error.message); 30 | return res.json({ 31 | error: true, 32 | status: 400, 33 | message: result.error.message, 34 | }); 35 | } 36 | 37 | //Check if the email has been already registered. 38 | var user = await User.findOne({ 39 | email: result.value.email, 40 | }); 41 | 42 | if (user) { 43 | return res.json({ 44 | error: true, 45 | message: "Email is already in use", 46 | }); 47 | } 48 | 49 | const hash = await User.hashPassword(result.value.password); 50 | 51 | const id = uuid(); //Generate unique id for the user. 52 | result.value.userId = id; 53 | 54 | delete result.value.confirmPassword; 55 | result.value.password = hash; 56 | 57 | let code = Math.floor(100000 + Math.random() * 900000); 58 | 59 | let expiry = Date.now() + 60 * 1000 * 15; //15 mins in ms 60 | 61 | const sendCode = await sendEmail(result.value.email, code); 62 | 63 | if (sendCode.error) { 64 | return res.status(500).json({ 65 | error: true, 66 | message: "Couldn't send verification email.", 67 | }); 68 | } 69 | result.value.emailToken = code; 70 | result.value.emailTokenExpires = new Date(expiry); 71 | 72 | //Check if referred and validate code. 73 | if (result.value.hasOwnProperty("referrer")) { 74 | let referrer = await User.findOne({ 75 | referralCode: result.value.referrer, 76 | }); 77 | if (!referrer) { 78 | return res.status(400).send({ 79 | error: true, 80 | message: "Invalid referral code.", 81 | }); 82 | } 83 | } 84 | result.value.referralCode = referralCode(); 85 | const newUser = new User(result.value); 86 | await newUser.save(); 87 | 88 | return res.status(200).json({ 89 | success: true, 90 | message: "Registration Success", 91 | referralCode: result.value.referralCode, 92 | }); 93 | } catch (error) { 94 | console.error("signup-error", error); 95 | return res.status(500).json({ 96 | error: true, 97 | message: "Cannot Register", 98 | }); 99 | } 100 | }; 101 | 102 | exports.Activate = async (req, res) => { 103 | try { 104 | const { email, code } = req.body; 105 | if (!email || !code) { 106 | return res.json({ 107 | error: true, 108 | status: 400, 109 | message: "Please make a valid request", 110 | }); 111 | } 112 | const user = await User.findOne({ 113 | email: email, 114 | emailToken: code, 115 | emailTokenExpires: { $gt: Date.now() }, 116 | }); 117 | 118 | if (!user) { 119 | return res.status(400).json({ 120 | error: true, 121 | message: "Invalid details", 122 | }); 123 | } else { 124 | if (user.active) 125 | return res.send({ 126 | error: true, 127 | message: "Account already activated", 128 | status: 400, 129 | }); 130 | 131 | user.emailToken = ""; 132 | user.emailTokenExpires = null; 133 | user.active = true; 134 | 135 | await user.save(); 136 | 137 | return res.status(200).json({ 138 | success: true, 139 | message: "Account activated.", 140 | }); 141 | } 142 | } catch (error) { 143 | console.error("activation-error", error); 144 | return res.status(500).json({ 145 | error: true, 146 | message: error.message, 147 | }); 148 | } 149 | }; 150 | 151 | exports.Login = async (req, res) => { 152 | try { 153 | const { email, password } = req.body; 154 | 155 | if (!email || !password) { 156 | return res.status(400).json({ 157 | error: true, 158 | message: "Cannot authorize user.", 159 | }); 160 | } 161 | 162 | //1. Find if any account with that email exists in DB 163 | const user = await User.findOne({ email: email }); 164 | 165 | // NOT FOUND - Throw error 166 | if (!user) { 167 | return res.status(404).json({ 168 | error: true, 169 | message: "Account not found", 170 | }); 171 | } 172 | 173 | //2. Throw error if account is not activated 174 | if (!user.active) { 175 | return res.status(400).json({ 176 | error: true, 177 | message: "You must verify your email to activate your account", 178 | }); 179 | } 180 | 181 | //3. Verify the password is valid 182 | const isValid = await User.comparePasswords(password, user.password); 183 | 184 | if (!isValid) { 185 | return res.status(400).json({ 186 | error: true, 187 | message: "Invalid credentials", 188 | }); 189 | } 190 | 191 | //Generate Access token 192 | 193 | const { error, token } = await generateJwt(user.email, user.userId); 194 | if (error) { 195 | return res.status(500).json({ 196 | error: true, 197 | message: "Couldn't create access token. Please try again later", 198 | }); 199 | } 200 | user.accessToken = token; 201 | await user.save(); 202 | 203 | //Success 204 | return res.send({ 205 | success: true, 206 | message: "User logged in successfully", 207 | accessToken: token, 208 | }); 209 | } catch (err) { 210 | console.error("Login error", err); 211 | return res.status(500).json({ 212 | error: true, 213 | message: "Couldn't login. Please try again later.", 214 | }); 215 | } 216 | }; 217 | 218 | exports.ForgotPassword = async (req, res) => { 219 | try { 220 | const { email } = req.body; 221 | if (!email) { 222 | return res.send({ 223 | status: 400, 224 | error: true, 225 | message: "Cannot be processed", 226 | }); 227 | } 228 | const user = await User.findOne({ 229 | email: email, 230 | }); 231 | if (!user) { 232 | return res.send({ 233 | success: true, 234 | message: 235 | "If that email address is in our database, we will send you an email to reset your password", 236 | }); 237 | } 238 | 239 | let code = Math.floor(100000 + Math.random() * 900000); 240 | let response = await sendEmail(user.email, code); 241 | 242 | if (response.error) { 243 | return res.status(500).json({ 244 | error: true, 245 | message: "Couldn't send mail. Please try again later.", 246 | }); 247 | } 248 | 249 | let expiry = Date.now() + 60 * 1000 * 15; 250 | user.resetPasswordToken = code; 251 | user.resetPasswordExpires = expiry; // 15 minutes 252 | 253 | await user.save(); 254 | 255 | return res.send({ 256 | success: true, 257 | message: 258 | "If that email address is in our database, we will send you an email to reset your password", 259 | }); 260 | } catch (error) { 261 | console.error("forgot-password-error", error); 262 | return res.status(500).json({ 263 | error: true, 264 | message: error.message, 265 | }); 266 | } 267 | }; 268 | 269 | exports.ResetPassword = async (req, res) => { 270 | try { 271 | const { token, newPassword, confirmPassword } = req.body; 272 | if (!token || !newPassword || !confirmPassword) { 273 | return res.status(403).json({ 274 | error: true, 275 | message: 276 | "Couldn't process request. Please provide all mandatory fields", 277 | }); 278 | } 279 | const user = await User.findOne({ 280 | resetPasswordToken: req.body.token, 281 | resetPasswordExpires: { $gt: Date.now() }, 282 | }); 283 | if (!user) { 284 | return res.send({ 285 | error: true, 286 | message: "Password reset token is invalid or has expired.", 287 | }); 288 | } 289 | if (newPassword !== confirmPassword) { 290 | return res.status(400).json({ 291 | error: true, 292 | message: "Passwords didn't match", 293 | }); 294 | } 295 | const hash = await User.hashPassword(req.body.newPassword); 296 | user.password = hash; 297 | user.resetPasswordToken = null; 298 | user.resetPasswordExpires = ""; 299 | 300 | await user.save(); 301 | 302 | return res.send({ 303 | success: true, 304 | message: "Password has been changed", 305 | }); 306 | } catch (error) { 307 | console.error("reset-password-error", error); 308 | return res.status(500).json({ 309 | error: true, 310 | message: error.message, 311 | }); 312 | } 313 | }; 314 | 315 | exports.ReferredAccounts = async (req, res) => { 316 | try { 317 | const { id, referralCode } = req.decoded; 318 | 319 | const referredAccounts = await User.find( 320 | { referrer: referralCode }, 321 | { email: 1, referralCode: 1, _id: 0 } 322 | ); 323 | return res.send({ 324 | success: true, 325 | accounts: referredAccounts, 326 | total: referredAccounts.length, 327 | }); 328 | } catch (error) { 329 | console.error("fetch-referred-error.", error); 330 | return res.status(500).json({ 331 | error: true, 332 | message: error.message, 333 | }); 334 | } 335 | }; 336 | 337 | exports.Logout = async (req, res) => { 338 | try { 339 | const { id } = req.decoded; 340 | 341 | let user = await User.findOne({ userId: id }); 342 | 343 | user.accessToken = ""; 344 | 345 | await user.save(); 346 | 347 | return res.send({ success: true, message: "User Logged out" }); 348 | } catch (error) { 349 | console.error("user-logout-error", error); 350 | return res.stat(500).json({ 351 | error: true, 352 | message: error.message, 353 | }); 354 | } 355 | }; 356 | -------------------------------------------------------------------------------- /src/users/user.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const bcrypt = require("bcryptjs"); 3 | const Schema = mongoose.Schema; 4 | 5 | const userSchema = new Schema( 6 | { 7 | userId: { type: String, unique: true, required: true }, 8 | email: { type: String, required: true, unique: true }, 9 | active: { type: Boolean, default: false }, 10 | password: { type: String, required: true }, 11 | resetPasswordToken: { type: String, default: null }, 12 | resetPasswordExpires: { type: Date, default: null }, 13 | 14 | emailToken: { type: String, default: null }, 15 | emailTokenExpires: { type: Date, default: null }, 16 | 17 | accessToken: { type: String, default: null }, 18 | 19 | referralCode: { type: String, unique: true }, 20 | referrer: { type: String, default: null }, 21 | }, 22 | { 23 | timestamps: { 24 | createdAt: "createdAt", 25 | updatedAt: "updatedAt", 26 | }, 27 | } 28 | ); 29 | 30 | const User = mongoose.model("user", userSchema); 31 | module.exports = User; 32 | 33 | module.exports.hashPassword = async (password) => { 34 | try { 35 | const salt = await bcrypt.genSalt(10); 36 | return await bcrypt.hash(password, salt); 37 | } catch (error) { 38 | throw new Error("Hashing failed", error); 39 | } 40 | }; 41 | module.exports.comparePasswords = async (inputPassword, hashedPassword) => { 42 | try { 43 | return await bcrypt.compare(inputPassword, hashedPassword); 44 | } catch (error) { 45 | throw new Error("Comparison failed", error); 46 | } 47 | }; 48 | --------------------------------------------------------------------------------