├── .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 | [](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 | []("https://github.com/praneshasp/node-authentication-jwt-mongodb")
5 | [](https://github.com/PraneshASP/node-authentication-jwt-mongodb)
6 | [](https://github.com/PraneshASP/node-authentication-jwt-mongodb)
7 | [](https://github.com/PraneshASP/node-authentication-jwt-mongodb)
8 | [](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 |
--------------------------------------------------------------------------------