├── .gitignore
├── .vscode
└── settings.json
├── client
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
└── src
│ ├── App.js
│ ├── App.test.js
│ ├── assets
│ ├── index.js
│ ├── logo.png
│ ├── logo.svg
│ └── main.svg
│ ├── components
│ ├── FormRow.js
│ ├── Navbar.js
│ └── index.js
│ ├── context.js
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ ├── pages
│ ├── Dashboard.js
│ ├── Error.js
│ ├── ForgotPassword.js
│ ├── Home.js
│ ├── Login.js
│ ├── ProtectedRoute.js
│ ├── Register.js
│ ├── ResetPassword.js
│ ├── Verify.js
│ └── index.js
│ ├── reportWebVitals.js
│ ├── setupTests.js
│ └── utils
│ ├── index.js
│ ├── localState.js
│ └── url.js
├── readme.md
└── server
├── README.md
├── auth.js
├── controllers
├── authController.js
└── userController.js
├── db
└── connect.js
├── errors
├── bad-request.js
├── custom-api.js
├── index.js
├── not-found.js
├── unauthenticated.js
└── unauthorized.js
├── index.js
├── middleware
├── authentication.js
├── error-handler.js
├── full-auth.js
└── not-found.js
├── models
├── Token.js
└── User.js
├── package-lock.json
├── package.json
├── routes.js
├── routes
├── authRoutes.js
└── userRoutes.js
└── utils
├── checkPermissions.js
├── createHash.js
├── createTokenUser.js
├── index.js
├── jwt.js
├── nodemailerConfig.js
├── sendEmail.js
├── sendResetPasswordEmail.js
└── sendVerificationEmail.js
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Logs
3 | logs
4 | *.log
5 | npm-debug.log*
6 | yarn-debug.log*
7 | yarn-error.log*
8 | lerna-debug.log*
9 | .pnpm-debug.log*
10 |
11 | # Runtime data
12 | pids
13 | *.pid
14 | *.seed
15 | *.pid.lock
16 |
17 | # Directory for instrumented libs generated by jscoverage/JSCover
18 | lib-cov
19 |
20 | # Coverage directory used by tools like istanbul
21 | coverage
22 | *.lcov
23 |
24 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
25 | .grunt
26 |
27 | # Bower dependency directory (https://bower.io/)
28 | bower_components
29 |
30 | # Compiled binary addons (https://nodejs.org/api/addons.html)
31 | build/Release
32 |
33 | # Dependency directories
34 | node_modules/
35 | jspm_packages/
36 |
37 | # Snowpack dependency directory (https://snowpack.dev/)
38 | web_modules/
39 |
40 | # Optional npm cache directory
41 | .npm
42 |
43 | # Optional eslint cache
44 | .eslintcache
45 |
46 | # Optional REPL history
47 | .node_repl_history
48 |
49 | # Output of 'npm pack'
50 | *.tgz
51 |
52 | # Yarn Integrity file
53 | .yarn-integrity
54 |
55 | # dotenv environment variable files
56 | .env
57 | .env.development.local
58 | .env.test.local
59 | .env.production.local
60 | .env.local
61 |
62 | # parcel-bundler cache (https://parceljs.org/)
63 | .cache
64 | .parcel-cache
65 |
66 | # Gatsby files
67 | .cache/
68 |
69 | # TernJS port file
70 | .tern-port
71 |
72 | # Stores VSCode versions used for testing VSCode extensions
73 | .vscode-test
74 |
75 | # yarn v2
76 | .yarn/cache
77 | .yarn/unplugged
78 | .yarn/build-state.yml
79 | .yarn/install-state.gz
80 | .pnp.*
81 |
82 | # Serverless Webpack directories
83 | .webpack/
84 |
85 | # vscode
86 | .vscode
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true,
3 | "editor.formatOnPaste": true
4 | }
--------------------------------------------------------------------------------
/client/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
13 |
14 | The page will reload when you make changes.\
15 | You may also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!**
35 |
36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
39 |
40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
48 | ### Code Splitting
49 |
50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
51 |
52 | ### Analyzing the Bundle Size
53 |
54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
55 |
56 | ### Making a Progressive Web App
57 |
58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
59 |
60 | ### Advanced Configuration
61 |
62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
63 |
64 | ### Deployment
65 |
66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
67 |
68 | ### `npm run build` fails to minify
69 |
70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
71 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.16.4",
7 | "@testing-library/react": "^13.3.0",
8 | "@testing-library/user-event": "^13.5.0",
9 | "axios": "^0.27.2",
10 | "normalize.css": "^8.0.1",
11 | "react": "^18.1.0",
12 | "react-dom": "^18.1.0",
13 | "react-icons": "^4.4.0",
14 | "react-router-dom": "^6.3.0",
15 | "react-scripts": "5.0.1",
16 | "styled-components": "^5.3.5",
17 | "web-vitals": "^2.1.4"
18 | },
19 | "scripts": {
20 | "start": "react-scripts start",
21 | "build": "react-scripts build",
22 | "test": "react-scripts test",
23 | "eject": "react-scripts eject"
24 | },
25 | "eslintConfig": {
26 | "extends": [
27 | "react-app",
28 | "react-app/jest"
29 | ]
30 | },
31 | "browserslist": {
32 | "production": [
33 | ">0.2%",
34 | "not dead",
35 | "not op_mini all"
36 | ],
37 | "development": [
38 | "last 1 chrome version",
39 | "last 1 firefox version",
40 | "last 1 safari version"
41 | ]
42 | },
43 | "proxy": "http://localhost:5000"
44 | }
45 |
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TidbitsJS/jwt-auth/b03cd4786d6bce4c642950a667be793ef9c191cc/client/public/favicon.ico
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/client/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TidbitsJS/jwt-auth/b03cd4786d6bce4c642950a667be793ef9c191cc/client/public/logo192.png
--------------------------------------------------------------------------------
/client/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TidbitsJS/jwt-auth/b03cd4786d6bce4c642950a667be793ef9c191cc/client/public/logo512.png
--------------------------------------------------------------------------------
/client/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/client/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/client/src/App.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
3 |
4 | import { Navbar } from "./components";
5 | import {
6 | Dashboard,
7 | ForgotPassword,
8 | Home,
9 | Login,
10 | Error,
11 | Register,
12 | ResetPassword,
13 | PrivateRoute,
14 | Verify,
15 | } from "./pages";
16 | import { useGlobalContext } from "./context";
17 |
18 | const App = () => {
19 | const { isLoading } = useGlobalContext();
20 |
21 | if (isLoading) {
22 | return (
23 |
26 | );
27 | }
28 |
29 | return (
30 |
31 |
32 |
33 | } />
34 | } />
35 | } />
36 |
40 |
41 |
42 | }
43 | />
44 | } />
45 | } />
46 | } />
47 | } />
48 |
49 |
50 | );
51 | };
52 |
53 | export default App;
54 |
--------------------------------------------------------------------------------
/client/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render( );
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/client/src/assets/index.js:
--------------------------------------------------------------------------------
1 | import logo from "./logo.svg";
2 | import jwtLogo from "./logo.png";
3 | import main from "./main.svg";
4 |
5 | export { logo, jwtLogo, main };
6 |
--------------------------------------------------------------------------------
/client/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TidbitsJS/jwt-auth/b03cd4786d6bce4c642950a667be793ef9c191cc/client/src/assets/logo.png
--------------------------------------------------------------------------------
/client/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/src/assets/main.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/src/components/FormRow.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const FormRow = ({ type, name, value, handleChange }) => {
4 | return (
5 |
6 |
7 | {name}
8 |
9 |
16 |
17 | );
18 | };
19 |
20 | export default FormRow;
21 |
--------------------------------------------------------------------------------
/client/src/components/Navbar.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import { Link } from "react-router-dom";
4 |
5 | import { jwtLogo } from "../assets";
6 | import { useGlobalContext } from "../context";
7 |
8 | const Navbar = () => {
9 | const { user, logoutUser } = useGlobalContext();
10 |
11 | return (
12 |
13 |
14 |
15 |
16 |
17 | {user && (
18 |
19 |
hello, {user.name}
20 |
logoutUser()}>
21 | logout
22 |
23 |
24 | )}
25 |
26 |
27 | );
28 | };
29 |
30 | const Wrapper = styled.nav`
31 | background: var(--white);
32 | height: 5rem;
33 | display: flex;
34 | align-items: center;
35 | justify-content: center;
36 | .nav-center {
37 | width: var(--fluid-width);
38 | max-width: var(--max-width);
39 | display: flex;
40 | justify-content: space-between;
41 | align-items: center;
42 | flex-wrap: wrap;
43 | }
44 | .nav-links {
45 | display: flex;
46 | flex-direction: column;
47 | }
48 | .nav-links p {
49 | margin: 0;
50 | text-transform: capitalize;
51 | margin-bottom: 0.25rem;
52 | }
53 | .home-link {
54 | display: flex;
55 | align-items: flex-end;
56 | }
57 | .logo {
58 | width: 150px;
59 | height: 50px;
60 | object-fit: contain;
61 | }
62 | @media (min-width: 776px) {
63 | .nav-links {
64 | flex-direction: row;
65 | align-items: center;
66 | }
67 | .nav-links p {
68 | margin: 0;
69 | margin-right: 1.5rem;
70 | }
71 | } ;
72 | `;
73 |
74 | export default Navbar;
75 |
--------------------------------------------------------------------------------
/client/src/components/index.js:
--------------------------------------------------------------------------------
1 | export { default as FormRow } from "./FormRow";
2 | export { default as Navbar } from "./Navbar";
3 |
--------------------------------------------------------------------------------
/client/src/context.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react-hooks/exhaustive-deps */
2 | import React, { useContext, useState, useEffect } from "react";
3 | import axios from "axios";
4 |
5 | const AppContext = React.createContext();
6 |
7 | export const AppProvider = ({ children }) => {
8 | const [isLoading, setIsLoading] = useState(true);
9 | const [user, setUser] = useState(null);
10 | const saveUser = (user) => {
11 | setUser(user);
12 | };
13 |
14 | const removeUser = () => {
15 | setUser(null);
16 | };
17 |
18 | const fetchUser = async () => {
19 | try {
20 | const { data } = await axios.get(`/api/v1/users/showMe`);
21 | saveUser(data.user);
22 | } catch (error) {
23 | removeUser();
24 | }
25 | setIsLoading(false);
26 | };
27 |
28 | const logoutUser = async () => {
29 | try {
30 | await axios.delete("/api/v1/auth/logout");
31 | removeUser();
32 | } catch (error) {
33 | console.log(error);
34 | }
35 | };
36 |
37 | useEffect(() => {
38 | fetchUser();
39 | }, []);
40 |
41 | return (
42 |
50 | {children}
51 |
52 | );
53 | };
54 |
55 | export const useGlobalContext = () => useContext(AppContext);
56 |
--------------------------------------------------------------------------------
/client/src/index.css:
--------------------------------------------------------------------------------
1 | *,
2 | ::after,
3 | ::before {
4 | box-sizing: border-box;
5 | }
6 |
7 | /* fonts */
8 | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;600&family=Montserrat&display=swap');
9 |
10 | html {
11 | font-size: 100%;
12 | }
13 |
14 | /*16px*/
15 |
16 | :root {
17 | /* colors */
18 | --primary-100: #e2e0ff;
19 | --primary-200: #c1beff;
20 | --primary-300: #a29dff;
21 | --primary-400: #837dff;
22 | --primary-500: #645cff;
23 | --primary-600: #504acc;
24 | --primary-700: #3c3799;
25 | --primary-800: #282566;
26 | --primary-900: #141233;
27 |
28 | /* grey */
29 | --grey-50: #f8fafc;
30 | --grey-100: #f1f5f9;
31 | --grey-200: #e2e8f0;
32 | --grey-300: #cbd5e1;
33 | --grey-400: #94a3b8;
34 | --grey-500: #64748b;
35 | --grey-600: #475569;
36 | --grey-700: #334155;
37 | --grey-800: #1e293b;
38 | --grey-900: #0f172a;
39 | /* rest of the colors */
40 | --black: #222;
41 | --white: #fff;
42 | --red-light: #f8d7da;
43 | --red-dark: #842029;
44 | --green-light: #d1e7dd;
45 | --green-dark: #0f5132;
46 |
47 | /* fonts */
48 | --headingFont: 'Roboto', sans-serif;
49 | --bodyFont: 'Nunito', sans-serif;
50 | --smallText: 0.7em;
51 | /* rest of the vars */
52 | --backgroundColor: var(--grey-50);
53 | --textColor: var(--grey-900);
54 | --borderRadius: 0.25rem;
55 | --letterSpacing: 1px;
56 | --transition: 0.3s ease-in-out all;
57 | --max-width: 1120px;
58 | --fixed-width: 500px;
59 | --fluid-width: 90vw;
60 |
61 | /* box shadow*/
62 | --shadow-1: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
63 | --shadow-2: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
64 | 0 2px 4px -1px rgba(0, 0, 0, 0.06);
65 | --shadow-3: 0 10px 15px -3px rgba(0, 0, 0, 0.1),
66 | 0 4px 6px -2px rgba(0, 0, 0, 0.05);
67 | --shadow-4: 0 20px 25px -5px rgba(0, 0, 0, 0.1),
68 | 0 10px 10px -5px rgba(0, 0, 0, 0.04);
69 | }
70 |
71 | body {
72 | background: var(--backgroundColor);
73 | font-family: var(--bodyFont);
74 | font-weight: 400;
75 | line-height: 1.75;
76 | color: var(--textColor);
77 | }
78 |
79 | p {
80 | margin-bottom: 1.5rem;
81 | max-width: 40em;
82 | }
83 |
84 | h1,
85 | h2,
86 | h3,
87 | h4,
88 | h5 {
89 | margin: 0;
90 | margin-bottom: 1.38rem;
91 | font-family: var(--headingFont);
92 | font-weight: 400;
93 | line-height: 1.3;
94 | text-transform: capitalize;
95 | letter-spacing: var(--letterSpacing);
96 | }
97 |
98 | h1 {
99 | margin-top: 0;
100 | font-size: 3.052rem;
101 | }
102 |
103 | h2 {
104 | font-size: 2.441rem;
105 | }
106 |
107 | h3 {
108 | font-size: 1.953rem;
109 | }
110 |
111 | h4 {
112 | font-size: 1.563rem;
113 | }
114 |
115 | h5 {
116 | font-size: 1.25rem;
117 | }
118 |
119 | small,
120 | .text-small {
121 | font-size: var(--smallText);
122 | }
123 |
124 | a {
125 | text-decoration: none;
126 | }
127 |
128 | ul {
129 | list-style-type: none;
130 | padding: 0;
131 | }
132 |
133 | .img {
134 | width: 100%;
135 | display: block;
136 | object-fit: cover;
137 | }
138 |
139 | /* buttons */
140 |
141 | .btn {
142 | cursor: pointer;
143 | color: var(--white);
144 | background: var(--primary-500);
145 | border: transparent;
146 | border-radius: var(--borderRadius);
147 | letter-spacing: var(--letterSpacing);
148 | padding: 0.375rem 0.75rem;
149 | box-shadow: var(--shadow-1);
150 | transition: var(--transition);
151 | text-transform: capitalize;
152 | display: inline-block;
153 | }
154 |
155 | .btn:hover {
156 | background: var(--primary-700);
157 | box-shadow: var(--shadow-3);
158 | }
159 |
160 | .btn-hipster {
161 | color: var(--primary-500);
162 | background: var(--primary-200);
163 | }
164 |
165 | .btn-hipster:hover {
166 | color: var(--primary-200);
167 | background: var(--primary-700);
168 | }
169 |
170 | .btn-block {
171 | width: 100%;
172 | }
173 |
174 | .btn-small {
175 | padding: 0.25rem 0.5rem;
176 | font-size: 0.75rem;
177 | }
178 |
179 | .btn:disabled {
180 | cursor: not-allowed;
181 | }
182 |
183 | /* alerts */
184 | .alert {
185 | padding: 0.375rem 0.75rem;
186 | margin: 0 auto;
187 | border-color: transparent;
188 | border-radius: var(--borderRadius);
189 | width: var(--fluid-width);
190 | max-width: var(--fixed-width);
191 | text-align: center;
192 | text-transform: capitalize;
193 | }
194 |
195 | .alert-danger {
196 | color: var(--red-dark);
197 | background: var(--red-light);
198 | }
199 |
200 | .alert-success {
201 | color: var(--green-dark);
202 | background: var(--green-light);
203 | }
204 |
205 | /* form */
206 |
207 | .form {
208 | width: var(--fluid-width);
209 | max-width: var(--fixed-width);
210 | background: var(--white);
211 | border-radius: var(--borderRadius);
212 | box-shadow: var(--shadow-2);
213 | padding: 2rem 2.5rem;
214 | margin: 3rem auto;
215 | position: relative;
216 | }
217 |
218 | .form-label {
219 | display: block;
220 | font-size: var(--smallText);
221 | margin-bottom: 0.5rem;
222 | text-transform: capitalize;
223 | letter-spacing: var(--letterSpacing);
224 | }
225 |
226 | .form-input,
227 | .form-textarea {
228 | width: 100%;
229 | padding: 0.375rem 0.75rem;
230 | border-radius: var(--borderRadius);
231 | background: var(--backgroundColor);
232 | border: 1px solid var(--grey-200);
233 | }
234 |
235 | .form-row {
236 | margin-bottom: 1rem;
237 | }
238 |
239 | .form-textarea {
240 | height: 7rem;
241 | }
242 |
243 | ::placeholder {
244 | font-family: inherit;
245 | color: var(--grey-400);
246 | }
247 |
248 | .form-alert {
249 | color: var(--red-dark);
250 | letter-spacing: var(--letterSpacing);
251 | text-transform: capitalize;
252 | }
253 |
254 | /* alert */
255 |
256 | @keyframes spinner {
257 | to {
258 | transform: rotate(360deg);
259 | }
260 | }
261 |
262 | .loading {
263 | width: 6rem;
264 | height: 6rem;
265 | border: 5px solid var(--grey-400);
266 | border-radius: 50%;
267 | border-top-color: var(--primary-500);
268 | animation: spinner 0.6s linear infinite;
269 | margin: 0 auto;
270 | }
271 |
272 | .loading {
273 | margin: 0 auto;
274 | }
275 |
276 | /* title */
277 |
278 | .title {
279 | text-align: center;
280 | }
281 |
282 | .title-underline {
283 | background: var(--primary-500);
284 | width: 7rem;
285 | height: 0.25rem;
286 | margin: 0 auto;
287 | margin-top: -1rem;
288 | }
289 |
290 | .page {
291 | min-height: calc(100vh - 6rem);
292 | width: var(--fluid-width);
293 | max-width: var(--max-width);
294 | margin: 0 auto;
295 | padding-top: 3rem;
296 | }
297 |
298 | .page-center {
299 | min-height: 100vh;
300 | display: grid;
301 | place-items: center;
302 | }
303 |
304 | .form-loading::after {
305 | content: '';
306 | position: absolute;
307 | top: 0;
308 | left: 0;
309 | border-radius: var(--borderRadius);
310 | width: 100%;
311 | height: 100%;
312 | background: rgba(255, 255, 255, 0.6);
313 | cursor: not-allowed;
314 | }
--------------------------------------------------------------------------------
/client/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 |
4 | import "./index.css";
5 | import "normalize.css";
6 |
7 | import App from "./App";
8 | import reportWebVitals from "./reportWebVitals";
9 |
10 | import { AppProvider } from "./context";
11 |
12 | const root = ReactDOM.createRoot(document.getElementById("root"));
13 | root.render(
14 |
15 |
16 |
17 |
18 |
19 | );
20 |
21 | // If you want to start measuring performance in your app, pass a function
22 | // to log results (for example: reportWebVitals(console.log))
23 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
24 | reportWebVitals();
25 |
--------------------------------------------------------------------------------
/client/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/src/pages/Dashboard.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | import { useGlobalContext } from "../context";
5 |
6 | const Dashboard = () => {
7 | const { user } = useGlobalContext();
8 | const { name, userId, role } = user;
9 |
10 | return (
11 |
12 | Hello there, {name}
13 |
14 | Your ID : {userId}
15 |
16 |
17 | Your Role : {role}
18 |
19 |
20 | );
21 | };
22 |
23 | const Wrapper = styled.div`
24 | p span {
25 | background: var(--primary-500);
26 | padding: 0.15rem 0.25rem;
27 | color: var(--white);
28 | border-radius: var(--borderRadius);
29 | letter-spacing: var(--letterSpacing);
30 | }
31 | `;
32 |
33 | export default Dashboard;
34 |
--------------------------------------------------------------------------------
/client/src/pages/Error.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import { Link } from "react-router-dom";
4 | const Error = () => {
5 | return (
6 |
7 |
8 |
404
9 | page not found
10 |
11 | Back Home
12 |
13 |
14 |
15 | );
16 | };
17 |
18 | const Wrapper = styled.main`
19 | text-align: center;
20 | padding-top: 5rem;
21 | h1 {
22 | font-size: 9rem;
23 | }
24 | `;
25 |
26 | export default Error;
27 |
--------------------------------------------------------------------------------
/client/src/pages/ForgotPassword.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Link } from "react-router-dom";
3 | import styled from "styled-components";
4 | import axios from "axios";
5 |
6 | import { FormRow } from "../components";
7 | import { useLocalState } from "../utils";
8 |
9 | const ForgotPassword = () => {
10 | const [email, setEmail] = useState("");
11 |
12 | const {
13 | alert,
14 | showAlert,
15 | loading,
16 | setLoading,
17 | success,
18 | setSuccess,
19 | hideAlert,
20 | } = useLocalState();
21 |
22 | const handleChange = (e) => {
23 | setEmail(e.target.value);
24 | };
25 |
26 | const handleSubmit = async (e) => {
27 | e.preventDefault();
28 | setLoading(true);
29 | hideAlert();
30 |
31 | if (!email) {
32 | showAlert({ text: "Please provide email" });
33 | setLoading(false);
34 | return;
35 | }
36 |
37 | try {
38 | const { data } = await axios.post(`/api/v1/auth/forgot-password`, {
39 | email,
40 | });
41 |
42 | showAlert({ text: data.msg, type: "success" });
43 | setSuccess(true);
44 | } catch (error) {
45 | showAlert({ text: "Something went wrong, please try again" });
46 | setSuccess(true);
47 | } finally {
48 | setLoading(false);
49 | }
50 | };
51 |
52 | return (
53 |
54 | {alert.show && (
55 | {alert.text}
56 | )}
57 |
58 | {!success && (
59 |
83 | )}
84 |
85 | );
86 | };
87 |
88 | const Wrapper = styled.main`
89 | h4,
90 | p {
91 | text-align: center;
92 | }
93 | p {
94 | margin: 0;
95 | margin-top: 1rem;
96 | }
97 | .login-link {
98 | display: inline-block;
99 | margin-left: 0.25rem;
100 | text-transform: capitalize;
101 | color: var(--primary-500);
102 | cursor: pointer;
103 | }
104 | `;
105 |
106 | export default ForgotPassword;
107 |
--------------------------------------------------------------------------------
/client/src/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link, Navigate } from "react-router-dom";
3 | import styled from "styled-components";
4 |
5 | import { main } from "../assets";
6 | import { useGlobalContext } from "../context";
7 |
8 | const Home = () => {
9 | const { user } = useGlobalContext();
10 |
11 | return (
12 | <>
13 | {user && }
14 |
15 |
16 |
17 |
18 | JWT Auth
19 |
20 |
21 |
22 | I'm baby viral enamel pin chartreuse cliche retro af selfies
23 | kinfolk photo booth plaid jianbing actually squid 3 wolf moon
24 | lumbersexual. Hell of humblebrag gluten-free lo-fi man braid
25 | legging
26 |
27 |
28 |
29 | Cloud bread kale chips wayfarers deep v chicharrones leggings
30 | fingerstache actually blog cliche four dollar toast. Sriracha ugh
31 | kickstarter, next level la croix butcher lomo.
32 |
33 |
34 |
35 |
36 | Login
37 |
38 |
39 |
40 | Register
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | >
50 | );
51 | };
52 |
53 | const Wrapper = styled.div`
54 | display: flex;
55 | align-items: center;
56 | justify-content: center;
57 | .info {
58 | display: flex;
59 | flex-direction: row;
60 | width: 100%;
61 | }
62 | h2 {
63 | font-weight: 700;
64 | }
65 | h2 span {
66 | color: var(--primary-500);
67 | }
68 | .info_text {
69 | flex: 1;
70 | display: flex;
71 | flex-direction: column;
72 | }
73 | .info_img {
74 | flex: 1;
75 | display: flex;
76 | justify-content: center;
77 | align-items: center;
78 | margin-left: 10px;
79 | }
80 | .main-img {
81 | display: flex;
82 | width: 80%;
83 | height: 80%;
84 | }
85 | .info_btns {
86 | display: flex;
87 | flex-direction: row;
88 | }
89 | .btn {
90 | margin-left: 0.25rem;
91 | margin-right: 0.25rem;
92 | }
93 |
94 | @media (max-width: 900px) {
95 | .info {
96 | flex-direction: column;
97 | }
98 | .info_img {
99 | margin-left: 0px;
100 | margin-top: 30px;
101 | }
102 | }
103 | `;
104 |
105 | export default Home;
106 |
--------------------------------------------------------------------------------
/client/src/pages/Login.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Link, useNavigate } from "react-router-dom";
3 | import styled from "styled-components";
4 | import axios from "axios";
5 |
6 | import { FormRow } from "../components";
7 | import { useGlobalContext } from "../context";
8 | import { useLocalState } from "../utils";
9 |
10 | const Login = () => {
11 | const navigate = useNavigate();
12 | const { saveUser } = useGlobalContext();
13 | const [values, setValues] = useState({
14 | email: "",
15 | password: "",
16 | });
17 |
18 | const { alert, showAlert, loading, setLoading, hideAlert } = useLocalState();
19 |
20 | const handleChange = (e) => {
21 | setValues({ ...values, [e.target.name]: e.target.value });
22 | };
23 |
24 | const onSubmit = async (e) => {
25 | e.preventDefault();
26 | hideAlert();
27 | setLoading(true);
28 |
29 | const { email, password } = values;
30 | const loginUser = { email, password };
31 |
32 | try {
33 | const { data } = await axios.post(`/api/v1/auth/login`, loginUser);
34 |
35 | setValues({ name: "", email: "", password: "" });
36 | showAlert({
37 | text: `Welcome ${data.user.name}. Redirecting to dashboard...`,
38 | type: "success",
39 | });
40 |
41 | saveUser(data.user);
42 | navigate("/dashboard");
43 | } catch (error) {
44 | showAlert({ text: error.response.data.msg });
45 | setLoading(false);
46 | } finally {
47 | setLoading(false);
48 | }
49 | };
50 |
51 | return (
52 |
53 | {alert.show && (
54 | {alert.text}
55 | )}
56 |
92 |
93 | );
94 | };
95 |
96 | const Wrapper = styled.section`
97 | .alert {
98 | margin-top: 3rem;
99 | }
100 | h4 {
101 | text-align: center;
102 | }
103 | p {
104 | margin: 0;
105 | text-align: center;
106 | }
107 | .btn {
108 | margin-bottom: 1.5rem;
109 | }
110 | .register-link,
111 | .reset-link {
112 | display: inline-block;
113 | margin-left: 0.25rem;
114 | text-transform: capitalize;
115 | color: var(--primary-500);
116 | cursor: pointer;
117 | }
118 | .reset-link {
119 | margin-top: 0.25rem;
120 | }
121 | .btn:disabled {
122 | cursor: not-allowed;
123 | }
124 | `;
125 |
126 | export default Login;
127 |
--------------------------------------------------------------------------------
/client/src/pages/ProtectedRoute.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Navigate } from "react-router-dom";
3 |
4 | import { useGlobalContext } from "../context";
5 |
6 | const PrivateRoute = ({ children }) => {
7 | const { user } = useGlobalContext();
8 |
9 | if (!user) {
10 | return ;
11 | }
12 |
13 | return children;
14 | };
15 |
16 | export default PrivateRoute;
17 |
--------------------------------------------------------------------------------
/client/src/pages/Register.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Link } from "react-router-dom";
3 | import styled from "styled-components";
4 | import axios from "axios";
5 |
6 | import { FormRow } from "../components";
7 | import { useLocalState } from "../utils";
8 |
9 | const Register = () => {
10 | const [values, setValues] = useState({
11 | name: "",
12 | email: "",
13 | password: "",
14 | });
15 |
16 | const {
17 | alert,
18 | hideAlert,
19 | loading,
20 | setLoading,
21 | success,
22 | setSuccess,
23 | showAlert,
24 | } = useLocalState();
25 |
26 | const handleChange = (e) => {
27 | setValues({ ...values, [e.target.name]: e.target.value });
28 | };
29 |
30 | const onSubmit = async (e) => {
31 | e.preventDefault();
32 | hideAlert();
33 | setLoading(true);
34 |
35 | const { name, email, password } = values;
36 | const registerNewUser = { name, email, password };
37 |
38 | try {
39 | const { data } = await axios.post(
40 | `/api/v1/auth/register`,
41 | registerNewUser
42 | );
43 |
44 | console.log(data);
45 |
46 | setSuccess(true);
47 | showAlert({ text: data.msg, type: "success" });
48 | } catch (error) {
49 | const { msg } = error.response.data;
50 | console.log("Something went wrong:", msg);
51 | showAlert({ text: msg || "There was an error" });
52 | } finally {
53 | setValues({ name: "", email: "", password: "" });
54 | setLoading(false);
55 | }
56 | };
57 |
58 | return (
59 |
60 | {alert.show && (
61 | {alert.text}
62 | )}
63 | {!success && (
64 |
100 | )}
101 |
102 | );
103 | };
104 |
105 | const Wrapper = styled.section`
106 | .alert {
107 | margin-top: 3rem;
108 | margin-bottom: -1.5rem;
109 | }
110 | h4 {
111 | text-align: center;
112 | }
113 | p {
114 | margin: 0;
115 | margin-top: 1rem;
116 | text-align: center;
117 | }
118 | .login-link {
119 | display: inline-block;
120 | margin-left: 0.25rem;
121 | text-transform: capitalize;
122 | color: var(--primary-500);
123 | cursor: pointer;
124 | }
125 | .btn:disabled {
126 | cursor: not-allowed;
127 | }
128 | `;
129 |
130 | export default Register;
131 |
--------------------------------------------------------------------------------
/client/src/pages/ResetPassword.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { useLocation, useNavigate } from "react-router-dom";
3 | import styled from "styled-components";
4 | import axios from "axios";
5 |
6 | import { FormRow } from "../components";
7 | import { useLocalState } from "../utils";
8 |
9 | function useQuery() {
10 | return new URLSearchParams(useLocation().search);
11 | }
12 |
13 | const ResetPassword = () => {
14 | const navigate = useNavigate();
15 | const query = useQuery();
16 | const [password, setPassword] = useState("");
17 |
18 | const { alert, showAlert, loading, setLoading, success, setSuccess } =
19 | useLocalState();
20 |
21 | const handleChange = async (e) => {
22 | setPassword(e.target.value);
23 | };
24 |
25 | const handleSubmit = async (e) => {
26 | e.preventDefault();
27 | setLoading(true);
28 |
29 | if (!password) {
30 | showAlert({ text: "Please enter password" });
31 | setLoading(false);
32 | return;
33 | }
34 |
35 | try {
36 | const { data } = await axios.post(`/api/v1/auth/reset-password`, {
37 | password,
38 | token: query.get("token"),
39 | email: query.get("email"),
40 | });
41 |
42 | console.log(data);
43 |
44 | setSuccess(true);
45 | showAlert({
46 | text: "Success, redirecting to login page shortly",
47 | type: "success",
48 | });
49 |
50 | setTimeout(() => {
51 | navigate("/login");
52 | }, 3000);
53 | } catch (error) {
54 | showAlert({ text: error.response.data.msg });
55 | } finally {
56 | setLoading(false);
57 | }
58 | };
59 |
60 | return (
61 |
62 | {alert.show && (
63 | {alert.text}
64 | )}
65 |
66 | {!success && (
67 |
84 | )}
85 |
86 | );
87 | };
88 |
89 | const Wrapper = styled.section`
90 | h4,
91 | p {
92 | text-align: center;
93 | }
94 | p {
95 | margin: 0;
96 | margin-top: 1rem;
97 | }
98 | `;
99 |
100 | export default ResetPassword;
101 |
--------------------------------------------------------------------------------
/client/src/pages/Verify.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react-hooks/exhaustive-deps */
2 | import React, { useState, useEffect } from "react";
3 | import { useLocation, Link } from "react-router-dom";
4 | import styled from "styled-components";
5 | import axios from "axios";
6 |
7 | import { useGlobalContext } from "../context";
8 |
9 | function useQuery() {
10 | return new URLSearchParams(useLocation().search);
11 | }
12 |
13 | const Verify = () => {
14 | const query = useQuery();
15 |
16 | const [error, setError] = useState(false);
17 | const [loading, setLoading] = useState(false);
18 |
19 | const { isLoading } = useGlobalContext();
20 |
21 | const verifyToken = async () => {
22 | setLoading(true);
23 |
24 | console.log(query.get("token"), query.get("email"));
25 |
26 | try {
27 | const { data } = await axios.post(`/api/v1/auth/verify-email`, {
28 | verificationToken: query.get("token"),
29 | email: query.get("email"),
30 | });
31 |
32 | console.log(data);
33 | } catch (error) {
34 | console.log("Something went wrong verifying token");
35 | setError(true);
36 | } finally {
37 | setLoading(false);
38 | }
39 | };
40 |
41 | useEffect(() => {
42 | if (!isLoading) {
43 | verifyToken();
44 | }
45 | }, []);
46 |
47 | if (loading) {
48 | return (
49 |
50 | Loading...
51 |
52 | );
53 | }
54 |
55 | if (error) {
56 | return (
57 |
58 | There was an error, please double check your verification link
59 |
60 | );
61 | }
62 |
63 | return (
64 |
65 | Account Confirmed
66 |
67 | Please login
68 |
69 |
70 | );
71 | };
72 |
73 | const Wrapper = styled.section``;
74 |
75 | export default Verify;
76 |
--------------------------------------------------------------------------------
/client/src/pages/index.js:
--------------------------------------------------------------------------------
1 | export { default as Error } from "./Error";
2 | export { default as Login } from "./Login";
3 | export { default as ResetPassword } from "./ResetPassword";
4 | export { default as ForgotPassword } from "./ForgotPassword";
5 | export { default as Dashboard } from "./Dashboard";
6 | export { default as Home } from "./Home";
7 | export { default as Register } from "./Register";
8 | export { default as PrivateRoute } from "./ProtectedRoute";
9 | export { default as Verify } from "./Verify";
10 |
--------------------------------------------------------------------------------
/client/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/client/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/client/src/utils/index.js:
--------------------------------------------------------------------------------
1 | export { default as useLocalState } from "./localState";
2 | export { default as url } from "./url";
3 |
--------------------------------------------------------------------------------
/client/src/utils/localState.js:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 |
3 | const useLocalState = () => {
4 | const [alert, setAlert] = useState({
5 | show: false,
6 | text: "",
7 | type: "danger",
8 | });
9 |
10 | const [loading, setLoading] = useState(false);
11 | const [success, setSuccess] = useState(false);
12 |
13 | const showAlert = ({ text, type = "danger" }) => {
14 | setAlert({ show: true, text, type });
15 | };
16 |
17 | const hideAlert = () => {
18 | setAlert({ show: false, text: "", type: "danger" });
19 | };
20 |
21 | return {
22 | alert,
23 | showAlert,
24 | loading,
25 | setLoading,
26 | success,
27 | setSuccess,
28 | hideAlert,
29 | };
30 | };
31 |
32 | export default useLocalState;
33 |
--------------------------------------------------------------------------------
/client/src/utils/url.js:
--------------------------------------------------------------------------------
1 | const url = "http://localhost:5000";
2 |
3 | export default url;
4 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # JWT Auth
2 |
3 | A complete auth application created using the MERN stack. This application has a JWT auth system that allows users to login and logout. It also has a user management system that allows users to register accounts. Along with that, application uses node-mailer to send emails to users.
4 |
5 | ### Application Features
6 |
7 | 1. JWT Auth
8 | 2. User Management
9 | 3. Admin Management
10 | 4. Email Verification
11 | 5. Password Reset
12 | 6. Authorization
13 |
14 | ### Client Technologies
15 |
16 | 1. React
17 | 2. Context API
18 | 3. Styled Components
19 |
20 | code: [client](./client)
21 |
22 | ### Server Technologies
23 |
24 | 1. Node
25 | 2. Express
26 | 3. MongoDB
27 | 4. JWT
28 | 5. Node-Mailer
29 |
30 | code: [server](./server)
31 |
32 |
33 | ## Setup
34 |
35 | Project requirements
36 |
37 | - git
38 | - NodeJS
39 | - NPM
40 |
41 | To verify the installation of above, you can run this:
42 |
43 | ```shell
44 | git --version
45 | node --version
46 | npm --version
47 | ```
48 |
49 | After you've made sure to have the correct things installed, you should be able to just run a few commands to get set up:
50 |
51 | 1. Clone the repo
52 |
53 | ```shell
54 | https://github.com/TidbitsJS/jwt-auth.git
55 | cd jwt-auth
56 | ```
57 |
58 | 2. Install node modules
59 |
60 | ```shell
61 | cd client
62 | npm install
63 |
64 | cd server
65 | npm install
66 | ```
67 |
68 | 3. Setup a new project on [MongoDB](https://www.mongodb.com/) platform
69 | 4. Store the MonoDB URL in env file of server folder
70 |
71 | ```text
72 | MONGO_URL=mongodb+srv://:@cluster0.eqcqa.mongodb.net/plants?retryWrites=true&w=majority
73 | ```
74 |
75 |
76 | ## Run
77 |
78 | To run the server:
79 |
80 | ```shell
81 | cd server
82 | npm run dev
83 | ```
84 |
85 | To run the client:
86 |
87 | ```shell
88 | cd client
89 | npm start
90 | ```
91 |
92 | Server will start on port 5000 while client will on port 3000
93 |
94 |
--------------------------------------------------------------------------------
/server/README.md:
--------------------------------------------------------------------------------
1 | # jwt-auth
2 |
3 | Everything you need to learn about JWT auth.
4 |
5 | ### Setup
6 |
7 | Project requirements
8 |
9 | - git
10 | - NodeJS
11 | - NPM
12 | - nodemon
13 |
14 | **nodemon** is a tool that helps develop node. js based applications by automatically restarting the node application when file changes in the directory are detected.
15 |
16 | To verify the installation of above, you can run this:
17 |
18 | ```shell
19 | git --version
20 | node --version
21 | npm --version
22 | nodemon --version
23 | ```
24 |
25 | After you've made sure to have the correct things installed, you should be able to just run a few commands to get set up:
26 |
27 | 1. Clone the repo
28 |
29 | ```shell
30 | git clone https://github.com/TidbitsJS/jwt-auth.git
31 | cd jwt-auth
32 | ```
33 |
34 | 2. Install node modules
35 |
36 | ```shell
37 | npm install
38 | ```
39 |
40 | 3. To run the app, run the command:
41 |
42 | ```shell
43 | npm run dev
44 | ```
45 |
46 | 4. Create a .env file and add the following lines:
47 |
48 | ```shell
49 | JWT_SECRET=
50 | ```
51 |
52 | 5. Local server will start running on the port 5000
53 |
54 | ```shell
55 | http://localhost:5000
56 | ```
57 |
58 | #
59 |
60 | ### What is JWT?
61 |
62 | JSON Web Token\*JWT) is open standard that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. It's digitally signed.
63 |
64 | JWTs can be signed using a secret(with the HMAC algorithm) or a public/private key pair using RSA or ECDSA algorithms.
65 |
66 | Signed tokens can verify the integrity of the claims contained within it.
67 |
68 | When tokens are signed using public/private key pairs, the signature also certifies that only the party holding the private key is the one that signed it.
69 |
70 | #
71 |
72 | ### When should you use JSON Web Tokens?
73 |
74 | 1. Authorization
75 |
76 | - Most common scenario
77 | - Once the user is logged in, each subsequent request will include the JWT, allowing the user to access routes, services, and resources that are permitted with that token.
78 | - Single Sign On is a feature that widely uses JWT nowadays, because of its small overhead and its ability to be easily used across different domains.
79 |
80 | 2. Information Exchange
81 |
82 | - Signature is calculated using the header and the payload, you can also verify that the content hasn't been tampered with.
83 | - JWTs can be signed—for example, using public/private key pairs—you can be sure the senders are who they say they are.
84 |
85 | #
86 |
87 | ### JSON Web Token structure
88 |
89 | In its compact form, JSON Web Tokens consist of three parts separated by dots (.), which are:
90 |
91 | - Header
92 | - Payload
93 | - Signature
94 |
95 | Therefore, a JWT typically looks like the following.
96 |
97 | ```shell
98 | xxxxx.yyyyy.zzzzz
99 | ```
100 |
101 | 1. Header
102 |
103 | A JSON object that contains information about the token.
104 |
105 | Always the first part of the JWT.
106 |
107 | Consists of two parts:
108 |
109 | 1. type of token ( jwt )
110 |
111 | 2. signing algorithm ( HMAC SHA256 or rsa )
112 |
113 | For example:
114 |
115 | ```json
116 | {
117 | "alg": "HS256",
118 | "typ": "JWT"
119 | }
120 | ```
121 |
122 | Then, this JSON is Base64Url encoded to form the first part of the JWT.
123 |
124 | 2. Paylod
125 |
126 | Contains the claims of the token. Claims are statements about an entity (typically, the user) and additional data.
127 |
128 | Three types of claims:
129 |
130 | 1. Registered claims
131 |
132 | Set of predefined claims which are not mandatory but recommended, to provide a set of useful, interoperable claims.
133 |
134 | Some of them are: iss(issuer), exp(expiration time), sub(subject), aud(audience)
135 |
136 | 2. Public claims
137 |
138 | These can be defined at will by those using JWTs
139 |
140 | To avoid collisions they should be defined in the IANA JSON Web Token Registry or be defined as a URI that contains a collision resistant namespace.
141 |
142 | 3. Private claims
143 |
144 | Custom claims created to share information between parties that agree on using them and are neither registered or public claims.
145 |
146 | For example:
147 |
148 | ```json
149 | {
150 | "sub": "1234567890",
151 | "name": "John Doe",
152 | "admin": true
153 | }
154 | ```
155 |
156 | 3. Signature
157 |
158 | To create the signature part you have to take the encoded header, the encoded payload, a secret, the algorithm specified in the header, and sign that.
159 |
160 | For example if you want to use the HMAC SHA256 algorithm, the signature will be created in the following way:
161 |
162 | ```javascript
163 | HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret);
164 | ```
165 |
166 | Used to verify the message wasn't changed along the way
167 |
168 | In the case of tokens signed with a private key, it can also verify that the sender of the JWT is who it says it is.
169 |
--------------------------------------------------------------------------------
/server/auth.js:
--------------------------------------------------------------------------------
1 | const jwt = require("jsonwebtoken");
2 | require("dotenv").config();
3 |
4 | const verifyToken = (req, res, next) => {
5 | try {
6 | const token =
7 | req.body.token || req.query.token || req.headers["x-access-token"];
8 |
9 | if (!token) {
10 | return res.status(401).json({
11 | message: "No token provided",
12 | });
13 | }
14 |
15 | const decoded = jwt.verify(token, process.env.JWT_SECRET);
16 |
17 | // check if expired
18 | if (decoded.exp < Date.now() / 1000) {
19 | return res.status(401).json({
20 | message: "Token has expired",
21 | });
22 | }
23 |
24 | req.user = decoded;
25 | next();
26 | } catch (err) {
27 | res.status(401).json({
28 | message: "Invalid token",
29 | });
30 | }
31 | };
32 |
33 | module.exports = verifyToken;
34 |
--------------------------------------------------------------------------------
/server/controllers/authController.js:
--------------------------------------------------------------------------------
1 | const User = require("../models/User");
2 | const Token = require("../models/Token");
3 | const { StatusCodes } = require("http-status-codes");
4 | const CustomError = require("../errors");
5 | const {
6 | attachCookiesToResponse,
7 | createTokenUser,
8 | sendVerificationEmail,
9 | sendResetPasswordEmail,
10 | createHash,
11 | } = require("../utils");
12 |
13 | const crypto = require("crypto");
14 |
15 | const register = async (req, res) => {
16 | const { email, name, password } = req.body;
17 |
18 | const emailAlreadyExists = await User.findOne({ email });
19 | if (emailAlreadyExists) {
20 | throw new CustomError.BadRequestError("Email already exists");
21 | }
22 |
23 | // first registered user is an admin
24 | const isFirstAccount = (await User.countDocuments({})) === 0;
25 | const role = isFirstAccount ? "admin" : "user";
26 |
27 | const verificationToken = crypto.randomBytes(40).toString("hex");
28 |
29 | const user = await User.create({
30 | name,
31 | email,
32 | password,
33 | role,
34 | verificationToken,
35 | });
36 |
37 | const origin = "http://localhost:3000";
38 |
39 | console.log("Registered ", user);
40 |
41 | await sendVerificationEmail({
42 | name: user.name,
43 | email: user.email,
44 | verificationToken: user.verificationToken,
45 | origin,
46 | });
47 |
48 | res.status(StatusCodes.CREATED).json({
49 | msg: "Success! Please check your email to verify account",
50 | });
51 | };
52 |
53 | const login = async (req, res) => {
54 | const { email, password } = req.body;
55 |
56 | if (!email || !password) {
57 | throw new CustomError.BadRequestError("Please provide email and password");
58 | }
59 |
60 | const user = await User.findOne({ email });
61 |
62 | if (!user) {
63 | throw new CustomError.UnauthenticatedError("Invalid Credentials");
64 | }
65 |
66 | const isPasswordCorrect = await user.comparePassword(password);
67 |
68 | if (!isPasswordCorrect) {
69 | throw new CustomError.UnauthenticatedError("Invalid Credentials");
70 | }
71 |
72 | if (!user.isVerified) {
73 | throw new CustomError.UnauthenticatedError("Please verify your email");
74 | }
75 |
76 | const tokenUser = createTokenUser(user);
77 |
78 | // create refresh token
79 | let refreshToken = "";
80 | // check for existing token
81 | const existingToken = await Token.findOne({ user: user._id });
82 | console.log("Existing Token", existingToken);
83 |
84 | if (existingToken) {
85 | const { isValid } = existingToken;
86 |
87 | if (!isValid) {
88 | throw new CustomError.UnauthenticatedError("Invalid Credentials");
89 | }
90 |
91 | refreshToken = existingToken.refreshToken;
92 | attachCookiesToResponse({ res, user: tokenUser, refreshToken });
93 | res.status(StatusCodes.OK).json({ user: tokenUser });
94 | return;
95 | }
96 |
97 | refreshToken = crypto.randomBytes(40).toString("hex");
98 | const userAgent = req.headers["user-agent"];
99 | const ip = req.ip;
100 | const userToken = { refreshToken, ip, userAgent, user: user._id };
101 |
102 | console.log("User Token", userToken);
103 |
104 | await Token.create(userToken);
105 |
106 | attachCookiesToResponse({ res, user: tokenUser, refreshToken });
107 |
108 | res.status(StatusCodes.OK).json({ user: tokenUser });
109 | };
110 |
111 | const logout = async (req, res) => {
112 | await Token.findOneAndDelete({ user: req.user.userId });
113 |
114 | res.cookie("accessToken", "logout", {
115 | httpOnly: true,
116 | expires: new Date(Date.now()),
117 | });
118 |
119 | res.cookie("refreshToken", "logout", {
120 | httpOnly: true,
121 | expires: new Date(Date.now()),
122 | });
123 |
124 | res.status(StatusCodes.OK).json({ msg: "user logged out!" });
125 | };
126 |
127 | const verifyEmail = async (req, res) => {
128 | const { verificationToken, email } = req.body;
129 | console.log("Verify Email", verificationToken, email);
130 |
131 | const user = await User.findOne({ email });
132 |
133 | console.log("Verified User", user);
134 |
135 | if (!user) {
136 | throw new CustomError.UnauthenticatedError("Verification Failed");
137 | }
138 |
139 | if (user.verificationToken !== verificationToken) {
140 | throw new CustomError.UnauthenticatedError("Verification Failed");
141 | }
142 |
143 | (user.isVerified = true), (user.verified = Date.now());
144 | user.verificationToken = "";
145 |
146 | await user.save();
147 |
148 | res.status(StatusCodes.OK).json({ msg: "Email Verified" });
149 | };
150 |
151 | const forgotPassword = async (req, res) => {
152 | const { email } = req.body;
153 | if (!email) {
154 | throw new CustomError.BadRequestError("Please provide valid email");
155 | }
156 |
157 | const user = await User.findOne({ email });
158 |
159 | if (user) {
160 | const passwordToken = crypto.randomBytes(40).toString("hex");
161 |
162 | const origin = "http://localhost:3000";
163 | await sendResetPasswordEmail({
164 | name: user.name,
165 | email: user.email,
166 | token: passwordToken,
167 | origin,
168 | });
169 |
170 | const tenMinutes = 1000 * 60 * 10;
171 | const passwordTokenExpirationDate = new Date(Date.now() + tenMinutes);
172 |
173 | user.passwordToken = createHash(passwordToken);
174 | user.passwordTokenExpirationDate = passwordTokenExpirationDate;
175 |
176 | await user.save();
177 | }
178 |
179 | res
180 | .status(StatusCodes.OK)
181 | .json({ msg: "Please check your email for reset password link" });
182 | };
183 |
184 | const resetPassword = async (req, res) => {
185 | const { token, email, password } = req.body;
186 | if (!token || !email || !password) {
187 | throw new CustomError.BadRequestError("Please provide all values");
188 | }
189 |
190 | const user = await User.findOne({ email });
191 |
192 | if (user) {
193 | const currentDate = new Date();
194 |
195 | if (
196 | user.passwordToken == createHash(token) &&
197 | user.passwordTokenExpirationDate > currentDate
198 | ) {
199 | user.password = password;
200 | user.passwordToken = null;
201 | user.passwordTokenExpirationDate = null;
202 |
203 | await user.save();
204 | }
205 | }
206 |
207 | res.send("reset password");
208 | };
209 |
210 | module.exports = {
211 | register,
212 | login,
213 | logout,
214 | verifyEmail,
215 | forgotPassword,
216 | resetPassword,
217 | };
218 |
--------------------------------------------------------------------------------
/server/controllers/userController.js:
--------------------------------------------------------------------------------
1 | const User = require("../models/User");
2 | const { StatusCodes } = require("http-status-codes");
3 | const CustomError = require("../errors");
4 | const {
5 | createTokenUser,
6 | attachCookiesToResponse,
7 | checkPermissions,
8 | } = require("../utils");
9 |
10 | const getAllUsers = async (req, res) => {
11 | const users = await User.find({ role: "user" }).select("-password");
12 | res.status(StatusCodes.OK).json({ users });
13 | };
14 |
15 | const getSingleUser = async (req, res) => {
16 | const user = await User.findOne({ _id: req.params.id }).select("-password");
17 | if (!user) {
18 | throw new CustomError.NotFoundError(`No user with id: ${req.params.id}`);
19 | }
20 |
21 | checkPermissions(req.user, user._id);
22 | res.status(StatusCodes.OK).json({ user });
23 | };
24 |
25 | const showCurrentUser = async (req, res) => {
26 | res.status(StatusCodes.OK).json({ user: req.user });
27 | };
28 |
29 | const updateUser = async (req, res) => {
30 | const { email, name } = req.body;
31 | if (!email || !name) {
32 | throw new CustomError.BadRequestError("Please provide all values");
33 | }
34 |
35 | const user = await User.findOne({ _id: req.user.userId });
36 | user.email = email;
37 | user.name = name;
38 |
39 | await user.save();
40 |
41 | const tokenUser = createTokenUser(user);
42 | attachCookiesToResponse({ req, user: tokenUser });
43 | res.status(StatusCodes.OK).json({ user: tokenUser });
44 | };
45 |
46 | const updateUserPassword = async (req, res) => {
47 | const { oldPassword, newPassword } = req.body;
48 | if (!oldPassword || !newPassword) {
49 | throw new CustomError.BadRequestError("Please provide both values");
50 | }
51 |
52 | const user = await User.findOne({ _id: req.user.userId });
53 |
54 | const isPasswordCorrect = await user.comparePassword(oldPassword);
55 | if (!isPasswordCorrect) {
56 | throw new CustomError.UnauthenticatedError("Invalid Credentials");
57 | }
58 |
59 | user.password = newPassword;
60 |
61 | await user.save();
62 | res.status(StatusCodes.OK).json({ msg: "Success! Password updated" });
63 | };
64 |
65 | module.exports = {
66 | getAllUsers,
67 | getSingleUser,
68 | showCurrentUser,
69 | updateUser,
70 | updateUserPassword,
71 | };
72 |
--------------------------------------------------------------------------------
/server/db/connect.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const connectDB = (url) => {
4 | return mongoose.connect(url);
5 | };
6 |
7 | module.exports = connectDB;
8 |
--------------------------------------------------------------------------------
/server/errors/bad-request.js:
--------------------------------------------------------------------------------
1 | const { StatusCodes } = require("http-status-codes");
2 | const CustomAPIError = require("./custom-api");
3 |
4 | class BadRequestError extends CustomAPIError {
5 | constructor(message) {
6 | super(message);
7 | this.statusCode = StatusCodes.BAD_REQUEST;
8 | }
9 | }
10 |
11 | module.exports = BadRequestError;
12 |
--------------------------------------------------------------------------------
/server/errors/custom-api.js:
--------------------------------------------------------------------------------
1 | class CustomAPIError extends Error {
2 | constructor(message) {
3 | super(message);
4 | }
5 | }
6 |
7 | module.exports = CustomAPIError;
8 |
--------------------------------------------------------------------------------
/server/errors/index.js:
--------------------------------------------------------------------------------
1 | const CustomAPIError = require("./custom-api");
2 | const UnauthenticatedError = require("./unauthenticated");
3 | const NotFoundError = require("./not-found");
4 | const BadRequestError = require("./bad-request");
5 | const UnauthorizedError = require("./unauthorized");
6 |
7 | module.exports = {
8 | CustomAPIError,
9 | UnauthenticatedError,
10 | NotFoundError,
11 | BadRequestError,
12 | UnauthorizedError,
13 | };
14 |
--------------------------------------------------------------------------------
/server/errors/not-found.js:
--------------------------------------------------------------------------------
1 | const { StatusCodes } = require("http-status-codes");
2 | const CustomAPIError = require("./custom-api");
3 |
4 | class NotFoundError extends CustomAPIError {
5 | constructor(message) {
6 | super(message);
7 | this.statusCode = StatusCodes.NOT_FOUND;
8 | }
9 | }
10 |
11 | module.exports = NotFoundError;
12 |
--------------------------------------------------------------------------------
/server/errors/unauthenticated.js:
--------------------------------------------------------------------------------
1 | const { statusCode } = require("http-status-codes");
2 | const CustomAPIError = require("./custom-api");
3 |
4 | class UnauthenticatedError extends CustomAPIError {
5 | constructor(message) {
6 | super(message);
7 | this.statusCode = statusCode.UNAUTHORIZED;
8 | }
9 | }
10 |
11 | module.exports = UnauthenticatedError;
12 |
--------------------------------------------------------------------------------
/server/errors/unauthorized.js:
--------------------------------------------------------------------------------
1 | const { statusCode } = require("http-status-codes");
2 | const CustomAPIError = require("./custom-api");
3 |
4 | class UnauthorizedError extends CustomAPIError {
5 | constructor(message) {
6 | super(message);
7 | this.statusCode = statusCode.FORBIDDEN;
8 | }
9 | }
10 |
11 | module.exports = UnauthorizedError;
12 |
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | require("dotenv").config();
2 | require("express-async-errors");
3 |
4 | const express = require("express");
5 | const app = express();
6 |
7 | const cookieParser = require("cookie-parser");
8 | const helmet = require("helmet");
9 | const cors = require("cors");
10 | const mongoSanitize = require("express-mongo-sanitize");
11 | const connectDB = require("./db/connect");
12 |
13 | const authRouter = require("./routes/authRoutes");
14 | const userRouter = require("./routes/userRoutes");
15 |
16 | const notFoundMiddleware = require("./middleware/not-found");
17 | const errorHandlerMiddleware = require("./middleware/error-handler");
18 |
19 | app.set("trust proxy", 1);
20 | app.use(helmet());
21 | app.use(cors());
22 | app.use(mongoSanitize());
23 |
24 | app.use(express.json());
25 | app.use(cookieParser(process.env.JWT_SECRET));
26 |
27 | app.use("/api/v1/auth", authRouter);
28 | app.use("/api/v1/users", userRouter);
29 |
30 | app.use(notFoundMiddleware);
31 | app.use(errorHandlerMiddleware);
32 |
33 | const port = process.env.PORT || 5000;
34 | const start = async () => {
35 | try {
36 | await connectDB(process.env.MONGO_URL);
37 | app.listen(port, () => console.log(`Server started on port ${port}`));
38 | } catch (error) {
39 | console.error(error);
40 | }
41 | };
42 |
43 | start();
44 |
--------------------------------------------------------------------------------
/server/middleware/authentication.js:
--------------------------------------------------------------------------------
1 | const CustomError = require("../errors");
2 | const { isTokenValid, attachCookiesToResponse } = require("../utils");
3 | const Token = require("../models/Token");
4 |
5 | const authenticateUser = async (req, res, next) => {
6 | console.log("Authenticating user...");
7 | const { refreshToken, accessToken } = req.signedCookies;
8 |
9 | try {
10 | if (accessToken) {
11 | const payload = isTokenValid(accessToken);
12 | req.user = payload.user;
13 | return next();
14 | }
15 |
16 | const payload = isTokenValid(refreshToken);
17 |
18 | const existingToken = await Token.findOne({
19 | user: payload.user.userId,
20 | refreshToken: payload.refreshToken,
21 | });
22 |
23 | if (!existingToken || !existingToken?.isValid) {
24 | throw new CustomError.UnauthenticatedError("Authentication Invalid");
25 | }
26 |
27 | attachCookiesToResponse({
28 | res,
29 | user: payload.user,
30 | refreshToken: existingToken.refreshToken,
31 | });
32 |
33 | req.user = payload.user;
34 | next();
35 | } catch (error) {
36 | console.log("Cookie failed you :(");
37 | throw new CustomError.UnauthenticatedError("Authentication Invalid");
38 | }
39 | };
40 |
41 | const authorizePermissions = (...roles) => {
42 | return (req, res, next) => {
43 | if (!roles.includes(req.user.role)) {
44 | throw new CustomError.UnauthorizedError(
45 | "Unauthorized to access this route"
46 | );
47 | }
48 |
49 | next();
50 | };
51 | };
52 |
53 | module.exports = {
54 | authenticateUser,
55 | authorizePermissions,
56 | };
57 |
--------------------------------------------------------------------------------
/server/middleware/error-handler.js:
--------------------------------------------------------------------------------
1 | const { StatusCodes } = require("http-status-codes");
2 |
3 | const errorHandlerMiddleware = (err, req, res, next) => {
4 | let customError = {
5 | statusCode: err.statusCode || StatusCodes.INTERNAL_SERVER_ERROR,
6 | msg: err.message || "Something went wrong try again later",
7 | };
8 |
9 | if (err.name === "ValidationError") {
10 | customError.msg = Object.values(err.errors)
11 | .map((item) => item.message)
12 | .join(", ");
13 |
14 | customError.statusCode = StatusCodes.BAD_REQUEST;
15 | }
16 |
17 | if (err.code && err.code === 11000) {
18 | customError.msg = `Duplicate value entered for ${Object.keys(
19 | err.keyValue
20 | )} field, please choose another value`;
21 |
22 | customError.statusCode = StatusCodes.BAD_REQUEST;
23 | }
24 |
25 | if (err.name === "CastError") {
26 | customError.msg = `No item found with id: ${err.value}`;
27 | customError.statusCode = StatusCodes.NOT_FOUND;
28 | }
29 |
30 | return res.status(customError.statusCode).json({ msg: customError.msg });
31 | };
32 |
33 | module.exports = errorHandlerMiddleware;
34 |
--------------------------------------------------------------------------------
/server/middleware/full-auth.js:
--------------------------------------------------------------------------------
1 | const CustomError = require("../errors");
2 | const { isTokenVaild } = require("../utils/jwt");
3 |
4 | const authenticateUser = async (req, res, next) => {
5 | let token;
6 |
7 | const authHeader = req.headers.authorization;
8 | if (authHeader && authHeader.startsWith("Bearer")) {
9 | token = authHeader.split(" ")[1];
10 | } else if (req.cookies.token) {
11 | token = req.cookies.token;
12 | }
13 |
14 | if (!token) {
15 | throw new CustomError.UnauthenticatedError("Authentication invalid");
16 | }
17 |
18 | try {
19 | const payload = isTokenVaild(token);
20 |
21 | req.user = {
22 | userId: payload.user.userId,
23 | role: payload.user.role,
24 | };
25 |
26 | next();
27 | } catch (error) {
28 | throw new CustomError.UnauthenticatedError("Authentication invalid");
29 | }
30 | };
31 |
32 | const authorizeRoles = (...roles) => {
33 | return (req, res, next) => {
34 | if (!roles.includes(req.user.role)) {
35 | throw new CustomError.UnauthorizedError(
36 | "Unathorized to access this route"
37 | );
38 | }
39 |
40 | next();
41 | };
42 | };
43 |
44 | module.exports = { authenticateUser, authorizeRoles };
45 |
--------------------------------------------------------------------------------
/server/middleware/not-found.js:
--------------------------------------------------------------------------------
1 | const notFoundMiddleware = (req, res) =>
2 | res.status(404).send("Route does not exist");
3 |
4 | module.exports = notFoundMiddleware;
5 |
--------------------------------------------------------------------------------
/server/models/Token.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const TokenSchema = new mongoose.Schema(
4 | {
5 | refreshToken: { type: String, required: true },
6 | ip: { type: String, required: true },
7 | userAgent: { type: String, required: true },
8 | isValid: { type: Boolean, default: true },
9 | user: {
10 | type: mongoose.Types.ObjectId,
11 | ref: "User",
12 | required: true,
13 | },
14 | },
15 | { timestamps: true }
16 | );
17 |
18 | module.exports = mongoose.model("Token", TokenSchema);
19 |
--------------------------------------------------------------------------------
/server/models/User.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 | const validator = require("validator");
3 | const bcrypt = require("bcryptjs");
4 |
5 | const UserSchema = new mongoose.Schema({
6 | name: {
7 | type: String,
8 | required: [true, "Please provide name"],
9 | minLength: 3,
10 | maxLength: 100,
11 | },
12 | email: {
13 | type: String,
14 | unique: true,
15 | required: [true, "Please provide email"],
16 | validate: {
17 | validator: validator.isEmail,
18 | message: "Please provide valid email",
19 | },
20 | },
21 | password: {
22 | type: String,
23 | required: [true, "Please provide password"],
24 | minLength: 8,
25 | },
26 | role: {
27 | type: String,
28 | enum: ["admin", "user"],
29 | default: "user",
30 | },
31 | verificationToken: String,
32 | isVerified: {
33 | type: Boolean,
34 | default: false,
35 | },
36 | verified: Date,
37 | passwordToken: {
38 | type: String,
39 | },
40 | passwordTokenExpirationDate: {
41 | type: Date,
42 | },
43 | });
44 |
45 | UserSchema.pre("save", async function () {
46 | if (!this.isModified("password")) return;
47 | const salt = await bcrypt.genSalt(10);
48 | this.password = await bcrypt.hash(this.password, salt);
49 | });
50 |
51 | UserSchema.methods.comparePassword = async function (candidatePassword) {
52 | const isMatch = await bcrypt.compare(candidatePassword, this.password);
53 | return isMatch;
54 | };
55 |
56 | module.exports = mongoose.model("User", UserSchema);
57 |
--------------------------------------------------------------------------------
/server/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jwt-auth",
3 | "version": "1.0.0",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "jwt-auth",
9 | "version": "1.0.0",
10 | "license": "ISC",
11 | "dependencies": {
12 | "bcryptjs": "^2.4.3",
13 | "cookie-parser": "^1.4.5",
14 | "cors": "^2.8.5",
15 | "dotenv": "^16.0.1",
16 | "express": "^4.18.1",
17 | "express-async-errors": "^3.1.1",
18 | "express-mongo-sanitize": "^2.2.0",
19 | "helmet": "^5.1.0",
20 | "http-status-codes": "^2.2.0",
21 | "jsonwebtoken": "^8.5.1",
22 | "mongoose": "^6.4.2",
23 | "morgan": "^1.10.0",
24 | "nodemailer": "^6.7.7",
25 | "validator": "^13.7.0",
26 | "xss": "^1.0.13"
27 | }
28 | },
29 | "node_modules/@types/node": {
30 | "version": "18.0.1",
31 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.1.tgz",
32 | "integrity": "sha512-CmR8+Tsy95hhwtZBKJBs0/FFq4XX7sDZHlGGf+0q+BRZfMbOTkzkj0AFAuTyXbObDIoanaBBW0+KEW+m3N16Wg=="
33 | },
34 | "node_modules/@types/webidl-conversions": {
35 | "version": "6.1.1",
36 | "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz",
37 | "integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q=="
38 | },
39 | "node_modules/@types/whatwg-url": {
40 | "version": "8.2.2",
41 | "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz",
42 | "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==",
43 | "dependencies": {
44 | "@types/node": "*",
45 | "@types/webidl-conversions": "*"
46 | }
47 | },
48 | "node_modules/accepts": {
49 | "version": "1.3.8",
50 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
51 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
52 | "dependencies": {
53 | "mime-types": "~2.1.34",
54 | "negotiator": "0.6.3"
55 | },
56 | "engines": {
57 | "node": ">= 0.6"
58 | }
59 | },
60 | "node_modules/array-flatten": {
61 | "version": "1.1.1",
62 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
63 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
64 | },
65 | "node_modules/base64-js": {
66 | "version": "1.5.1",
67 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
68 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
69 | "funding": [
70 | {
71 | "type": "github",
72 | "url": "https://github.com/sponsors/feross"
73 | },
74 | {
75 | "type": "patreon",
76 | "url": "https://www.patreon.com/feross"
77 | },
78 | {
79 | "type": "consulting",
80 | "url": "https://feross.org/support"
81 | }
82 | ]
83 | },
84 | "node_modules/basic-auth": {
85 | "version": "2.0.1",
86 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
87 | "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
88 | "dependencies": {
89 | "safe-buffer": "5.1.2"
90 | },
91 | "engines": {
92 | "node": ">= 0.8"
93 | }
94 | },
95 | "node_modules/basic-auth/node_modules/safe-buffer": {
96 | "version": "5.1.2",
97 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
98 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
99 | },
100 | "node_modules/bcryptjs": {
101 | "version": "2.4.3",
102 | "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
103 | "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ=="
104 | },
105 | "node_modules/body-parser": {
106 | "version": "1.20.0",
107 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz",
108 | "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==",
109 | "dependencies": {
110 | "bytes": "3.1.2",
111 | "content-type": "~1.0.4",
112 | "debug": "2.6.9",
113 | "depd": "2.0.0",
114 | "destroy": "1.2.0",
115 | "http-errors": "2.0.0",
116 | "iconv-lite": "0.4.24",
117 | "on-finished": "2.4.1",
118 | "qs": "6.10.3",
119 | "raw-body": "2.5.1",
120 | "type-is": "~1.6.18",
121 | "unpipe": "1.0.0"
122 | },
123 | "engines": {
124 | "node": ">= 0.8",
125 | "npm": "1.2.8000 || >= 1.4.16"
126 | }
127 | },
128 | "node_modules/bson": {
129 | "version": "4.6.4",
130 | "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.4.tgz",
131 | "integrity": "sha512-TdQ3FzguAu5HKPPlr0kYQCyrYUYh8tFM+CMTpxjNzVzxeiJY00Rtuj3LXLHSgiGvmaWlZ8PE+4KyM2thqE38pQ==",
132 | "dependencies": {
133 | "buffer": "^5.6.0"
134 | },
135 | "engines": {
136 | "node": ">=6.9.0"
137 | }
138 | },
139 | "node_modules/buffer": {
140 | "version": "5.7.1",
141 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
142 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
143 | "funding": [
144 | {
145 | "type": "github",
146 | "url": "https://github.com/sponsors/feross"
147 | },
148 | {
149 | "type": "patreon",
150 | "url": "https://www.patreon.com/feross"
151 | },
152 | {
153 | "type": "consulting",
154 | "url": "https://feross.org/support"
155 | }
156 | ],
157 | "dependencies": {
158 | "base64-js": "^1.3.1",
159 | "ieee754": "^1.1.13"
160 | }
161 | },
162 | "node_modules/buffer-equal-constant-time": {
163 | "version": "1.0.1",
164 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
165 | "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
166 | },
167 | "node_modules/bytes": {
168 | "version": "3.1.2",
169 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
170 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
171 | "engines": {
172 | "node": ">= 0.8"
173 | }
174 | },
175 | "node_modules/call-bind": {
176 | "version": "1.0.2",
177 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
178 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
179 | "dependencies": {
180 | "function-bind": "^1.1.1",
181 | "get-intrinsic": "^1.0.2"
182 | },
183 | "funding": {
184 | "url": "https://github.com/sponsors/ljharb"
185 | }
186 | },
187 | "node_modules/commander": {
188 | "version": "2.20.3",
189 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
190 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
191 | },
192 | "node_modules/content-disposition": {
193 | "version": "0.5.4",
194 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
195 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
196 | "dependencies": {
197 | "safe-buffer": "5.2.1"
198 | },
199 | "engines": {
200 | "node": ">= 0.6"
201 | }
202 | },
203 | "node_modules/content-type": {
204 | "version": "1.0.4",
205 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
206 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
207 | "engines": {
208 | "node": ">= 0.6"
209 | }
210 | },
211 | "node_modules/cookie": {
212 | "version": "0.5.0",
213 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
214 | "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
215 | "engines": {
216 | "node": ">= 0.6"
217 | }
218 | },
219 | "node_modules/cookie-parser": {
220 | "version": "1.4.6",
221 | "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
222 | "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
223 | "dependencies": {
224 | "cookie": "0.4.1",
225 | "cookie-signature": "1.0.6"
226 | },
227 | "engines": {
228 | "node": ">= 0.8.0"
229 | }
230 | },
231 | "node_modules/cookie-parser/node_modules/cookie": {
232 | "version": "0.4.1",
233 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
234 | "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
235 | "engines": {
236 | "node": ">= 0.6"
237 | }
238 | },
239 | "node_modules/cookie-signature": {
240 | "version": "1.0.6",
241 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
242 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
243 | },
244 | "node_modules/cors": {
245 | "version": "2.8.5",
246 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
247 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
248 | "dependencies": {
249 | "object-assign": "^4",
250 | "vary": "^1"
251 | },
252 | "engines": {
253 | "node": ">= 0.10"
254 | }
255 | },
256 | "node_modules/cssfilter": {
257 | "version": "0.0.10",
258 | "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz",
259 | "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw=="
260 | },
261 | "node_modules/debug": {
262 | "version": "2.6.9",
263 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
264 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
265 | "dependencies": {
266 | "ms": "2.0.0"
267 | }
268 | },
269 | "node_modules/denque": {
270 | "version": "2.0.1",
271 | "resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz",
272 | "integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==",
273 | "engines": {
274 | "node": ">=0.10"
275 | }
276 | },
277 | "node_modules/depd": {
278 | "version": "2.0.0",
279 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
280 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
281 | "engines": {
282 | "node": ">= 0.8"
283 | }
284 | },
285 | "node_modules/destroy": {
286 | "version": "1.2.0",
287 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
288 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
289 | "engines": {
290 | "node": ">= 0.8",
291 | "npm": "1.2.8000 || >= 1.4.16"
292 | }
293 | },
294 | "node_modules/dotenv": {
295 | "version": "16.0.1",
296 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz",
297 | "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==",
298 | "engines": {
299 | "node": ">=12"
300 | }
301 | },
302 | "node_modules/ecdsa-sig-formatter": {
303 | "version": "1.0.11",
304 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
305 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
306 | "dependencies": {
307 | "safe-buffer": "^5.0.1"
308 | }
309 | },
310 | "node_modules/ee-first": {
311 | "version": "1.1.1",
312 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
313 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
314 | },
315 | "node_modules/encodeurl": {
316 | "version": "1.0.2",
317 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
318 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
319 | "engines": {
320 | "node": ">= 0.8"
321 | }
322 | },
323 | "node_modules/escape-html": {
324 | "version": "1.0.3",
325 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
326 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
327 | },
328 | "node_modules/etag": {
329 | "version": "1.8.1",
330 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
331 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
332 | "engines": {
333 | "node": ">= 0.6"
334 | }
335 | },
336 | "node_modules/express": {
337 | "version": "4.18.1",
338 | "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz",
339 | "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==",
340 | "dependencies": {
341 | "accepts": "~1.3.8",
342 | "array-flatten": "1.1.1",
343 | "body-parser": "1.20.0",
344 | "content-disposition": "0.5.4",
345 | "content-type": "~1.0.4",
346 | "cookie": "0.5.0",
347 | "cookie-signature": "1.0.6",
348 | "debug": "2.6.9",
349 | "depd": "2.0.0",
350 | "encodeurl": "~1.0.2",
351 | "escape-html": "~1.0.3",
352 | "etag": "~1.8.1",
353 | "finalhandler": "1.2.0",
354 | "fresh": "0.5.2",
355 | "http-errors": "2.0.0",
356 | "merge-descriptors": "1.0.1",
357 | "methods": "~1.1.2",
358 | "on-finished": "2.4.1",
359 | "parseurl": "~1.3.3",
360 | "path-to-regexp": "0.1.7",
361 | "proxy-addr": "~2.0.7",
362 | "qs": "6.10.3",
363 | "range-parser": "~1.2.1",
364 | "safe-buffer": "5.2.1",
365 | "send": "0.18.0",
366 | "serve-static": "1.15.0",
367 | "setprototypeof": "1.2.0",
368 | "statuses": "2.0.1",
369 | "type-is": "~1.6.18",
370 | "utils-merge": "1.0.1",
371 | "vary": "~1.1.2"
372 | },
373 | "engines": {
374 | "node": ">= 0.10.0"
375 | }
376 | },
377 | "node_modules/express-async-errors": {
378 | "version": "3.1.1",
379 | "resolved": "https://registry.npmjs.org/express-async-errors/-/express-async-errors-3.1.1.tgz",
380 | "integrity": "sha512-h6aK1da4tpqWSbyCa3FxB/V6Ehd4EEB15zyQq9qe75OZBp0krinNKuH4rAY+S/U/2I36vdLAUFSjQJ+TFmODng==",
381 | "peerDependencies": {
382 | "express": "^4.16.2"
383 | }
384 | },
385 | "node_modules/express-mongo-sanitize": {
386 | "version": "2.2.0",
387 | "resolved": "https://registry.npmjs.org/express-mongo-sanitize/-/express-mongo-sanitize-2.2.0.tgz",
388 | "integrity": "sha512-PZBs5nwhD6ek9ZuP+W2xmpvcrHwXZxD5GdieX2dsjPbAbH4azOkrHbycBud2QRU+YQF1CT+pki/lZGedHgo/dQ==",
389 | "engines": {
390 | "node": ">=10"
391 | }
392 | },
393 | "node_modules/finalhandler": {
394 | "version": "1.2.0",
395 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
396 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
397 | "dependencies": {
398 | "debug": "2.6.9",
399 | "encodeurl": "~1.0.2",
400 | "escape-html": "~1.0.3",
401 | "on-finished": "2.4.1",
402 | "parseurl": "~1.3.3",
403 | "statuses": "2.0.1",
404 | "unpipe": "~1.0.0"
405 | },
406 | "engines": {
407 | "node": ">= 0.8"
408 | }
409 | },
410 | "node_modules/forwarded": {
411 | "version": "0.2.0",
412 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
413 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
414 | "engines": {
415 | "node": ">= 0.6"
416 | }
417 | },
418 | "node_modules/fresh": {
419 | "version": "0.5.2",
420 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
421 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
422 | "engines": {
423 | "node": ">= 0.6"
424 | }
425 | },
426 | "node_modules/function-bind": {
427 | "version": "1.1.1",
428 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
429 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
430 | },
431 | "node_modules/get-intrinsic": {
432 | "version": "1.1.2",
433 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz",
434 | "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==",
435 | "dependencies": {
436 | "function-bind": "^1.1.1",
437 | "has": "^1.0.3",
438 | "has-symbols": "^1.0.3"
439 | },
440 | "funding": {
441 | "url": "https://github.com/sponsors/ljharb"
442 | }
443 | },
444 | "node_modules/has": {
445 | "version": "1.0.3",
446 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
447 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
448 | "dependencies": {
449 | "function-bind": "^1.1.1"
450 | },
451 | "engines": {
452 | "node": ">= 0.4.0"
453 | }
454 | },
455 | "node_modules/has-symbols": {
456 | "version": "1.0.3",
457 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
458 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
459 | "engines": {
460 | "node": ">= 0.4"
461 | },
462 | "funding": {
463 | "url": "https://github.com/sponsors/ljharb"
464 | }
465 | },
466 | "node_modules/helmet": {
467 | "version": "5.1.0",
468 | "resolved": "https://registry.npmjs.org/helmet/-/helmet-5.1.0.tgz",
469 | "integrity": "sha512-klsunXs8rgNSZoaUrNeuCiWUxyc+wzucnEnFejUg3/A+CaF589k9qepLZZ1Jehnzig7YbD4hEuscGXuBY3fq+g==",
470 | "engines": {
471 | "node": ">=12.0.0"
472 | }
473 | },
474 | "node_modules/http-errors": {
475 | "version": "2.0.0",
476 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
477 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
478 | "dependencies": {
479 | "depd": "2.0.0",
480 | "inherits": "2.0.4",
481 | "setprototypeof": "1.2.0",
482 | "statuses": "2.0.1",
483 | "toidentifier": "1.0.1"
484 | },
485 | "engines": {
486 | "node": ">= 0.8"
487 | }
488 | },
489 | "node_modules/http-status-codes": {
490 | "version": "2.2.0",
491 | "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.2.0.tgz",
492 | "integrity": "sha512-feERVo9iWxvnejp3SEfm/+oNG517npqL2/PIA8ORjyOZjGC7TwCRQsZylciLS64i6pJ0wRYz3rkXLRwbtFa8Ng=="
493 | },
494 | "node_modules/iconv-lite": {
495 | "version": "0.4.24",
496 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
497 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
498 | "dependencies": {
499 | "safer-buffer": ">= 2.1.2 < 3"
500 | },
501 | "engines": {
502 | "node": ">=0.10.0"
503 | }
504 | },
505 | "node_modules/ieee754": {
506 | "version": "1.2.1",
507 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
508 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
509 | "funding": [
510 | {
511 | "type": "github",
512 | "url": "https://github.com/sponsors/feross"
513 | },
514 | {
515 | "type": "patreon",
516 | "url": "https://www.patreon.com/feross"
517 | },
518 | {
519 | "type": "consulting",
520 | "url": "https://feross.org/support"
521 | }
522 | ]
523 | },
524 | "node_modules/inherits": {
525 | "version": "2.0.4",
526 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
527 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
528 | },
529 | "node_modules/ip": {
530 | "version": "1.1.8",
531 | "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz",
532 | "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg=="
533 | },
534 | "node_modules/ipaddr.js": {
535 | "version": "1.9.1",
536 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
537 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
538 | "engines": {
539 | "node": ">= 0.10"
540 | }
541 | },
542 | "node_modules/jsonwebtoken": {
543 | "version": "8.5.1",
544 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
545 | "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
546 | "dependencies": {
547 | "jws": "^3.2.2",
548 | "lodash.includes": "^4.3.0",
549 | "lodash.isboolean": "^3.0.3",
550 | "lodash.isinteger": "^4.0.4",
551 | "lodash.isnumber": "^3.0.3",
552 | "lodash.isplainobject": "^4.0.6",
553 | "lodash.isstring": "^4.0.1",
554 | "lodash.once": "^4.0.0",
555 | "ms": "^2.1.1",
556 | "semver": "^5.6.0"
557 | },
558 | "engines": {
559 | "node": ">=4",
560 | "npm": ">=1.4.28"
561 | }
562 | },
563 | "node_modules/jsonwebtoken/node_modules/ms": {
564 | "version": "2.1.3",
565 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
566 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
567 | },
568 | "node_modules/jwa": {
569 | "version": "1.4.1",
570 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
571 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
572 | "dependencies": {
573 | "buffer-equal-constant-time": "1.0.1",
574 | "ecdsa-sig-formatter": "1.0.11",
575 | "safe-buffer": "^5.0.1"
576 | }
577 | },
578 | "node_modules/jws": {
579 | "version": "3.2.2",
580 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
581 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
582 | "dependencies": {
583 | "jwa": "^1.4.1",
584 | "safe-buffer": "^5.0.1"
585 | }
586 | },
587 | "node_modules/kareem": {
588 | "version": "2.4.1",
589 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.4.1.tgz",
590 | "integrity": "sha512-aJ9opVoXroQUPfovYP5kaj2lM7Jn02Gw13bL0lg9v0V7SaUc0qavPs0Eue7d2DcC3NjqI6QAUElXNsuZSeM+EA=="
591 | },
592 | "node_modules/lodash.includes": {
593 | "version": "4.3.0",
594 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
595 | "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
596 | },
597 | "node_modules/lodash.isboolean": {
598 | "version": "3.0.3",
599 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
600 | "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
601 | },
602 | "node_modules/lodash.isinteger": {
603 | "version": "4.0.4",
604 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
605 | "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
606 | },
607 | "node_modules/lodash.isnumber": {
608 | "version": "3.0.3",
609 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
610 | "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
611 | },
612 | "node_modules/lodash.isplainobject": {
613 | "version": "4.0.6",
614 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
615 | "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
616 | },
617 | "node_modules/lodash.isstring": {
618 | "version": "4.0.1",
619 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
620 | "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
621 | },
622 | "node_modules/lodash.once": {
623 | "version": "4.1.1",
624 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
625 | "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
626 | },
627 | "node_modules/media-typer": {
628 | "version": "0.3.0",
629 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
630 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
631 | "engines": {
632 | "node": ">= 0.6"
633 | }
634 | },
635 | "node_modules/memory-pager": {
636 | "version": "1.5.0",
637 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
638 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
639 | "optional": true
640 | },
641 | "node_modules/merge-descriptors": {
642 | "version": "1.0.1",
643 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
644 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
645 | },
646 | "node_modules/methods": {
647 | "version": "1.1.2",
648 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
649 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
650 | "engines": {
651 | "node": ">= 0.6"
652 | }
653 | },
654 | "node_modules/mime": {
655 | "version": "1.6.0",
656 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
657 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
658 | "bin": {
659 | "mime": "cli.js"
660 | },
661 | "engines": {
662 | "node": ">=4"
663 | }
664 | },
665 | "node_modules/mime-db": {
666 | "version": "1.52.0",
667 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
668 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
669 | "engines": {
670 | "node": ">= 0.6"
671 | }
672 | },
673 | "node_modules/mime-types": {
674 | "version": "2.1.35",
675 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
676 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
677 | "dependencies": {
678 | "mime-db": "1.52.0"
679 | },
680 | "engines": {
681 | "node": ">= 0.6"
682 | }
683 | },
684 | "node_modules/mongodb": {
685 | "version": "4.7.0",
686 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.7.0.tgz",
687 | "integrity": "sha512-HhVar6hsUeMAVlIbwQwWtV36iyjKd9qdhY+s4wcU8K6TOj4Q331iiMy+FoPuxEntDIijTYWivwFJkLv8q/ZgvA==",
688 | "dependencies": {
689 | "bson": "^4.6.3",
690 | "denque": "^2.0.1",
691 | "mongodb-connection-string-url": "^2.5.2",
692 | "socks": "^2.6.2"
693 | },
694 | "engines": {
695 | "node": ">=12.9.0"
696 | },
697 | "optionalDependencies": {
698 | "saslprep": "^1.0.3"
699 | }
700 | },
701 | "node_modules/mongodb-connection-string-url": {
702 | "version": "2.5.2",
703 | "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.2.tgz",
704 | "integrity": "sha512-tWDyIG8cQlI5k3skB6ywaEA5F9f5OntrKKsT/Lteub2zgwSUlhqEN2inGgBTm8bpYJf8QYBdA/5naz65XDpczA==",
705 | "dependencies": {
706 | "@types/whatwg-url": "^8.2.1",
707 | "whatwg-url": "^11.0.0"
708 | }
709 | },
710 | "node_modules/mongoose": {
711 | "version": "6.4.2",
712 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.4.2.tgz",
713 | "integrity": "sha512-VRotms+XC42vPlS3Tr2GTg0qkppHzWob62sUjdC22ir8aqWVrbCsHjEuMsL6zZlKSIj8zNBODm9hRV+RHRiLRQ==",
714 | "dependencies": {
715 | "bson": "^4.6.2",
716 | "kareem": "2.4.1",
717 | "mongodb": "4.7.0",
718 | "mpath": "0.9.0",
719 | "mquery": "4.0.3",
720 | "ms": "2.1.3",
721 | "sift": "16.0.0"
722 | },
723 | "engines": {
724 | "node": ">=12.0.0"
725 | },
726 | "funding": {
727 | "type": "opencollective",
728 | "url": "https://opencollective.com/mongoose"
729 | }
730 | },
731 | "node_modules/mongoose/node_modules/ms": {
732 | "version": "2.1.3",
733 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
734 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
735 | },
736 | "node_modules/morgan": {
737 | "version": "1.10.0",
738 | "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
739 | "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==",
740 | "dependencies": {
741 | "basic-auth": "~2.0.1",
742 | "debug": "2.6.9",
743 | "depd": "~2.0.0",
744 | "on-finished": "~2.3.0",
745 | "on-headers": "~1.0.2"
746 | },
747 | "engines": {
748 | "node": ">= 0.8.0"
749 | }
750 | },
751 | "node_modules/morgan/node_modules/on-finished": {
752 | "version": "2.3.0",
753 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
754 | "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==",
755 | "dependencies": {
756 | "ee-first": "1.1.1"
757 | },
758 | "engines": {
759 | "node": ">= 0.8"
760 | }
761 | },
762 | "node_modules/mpath": {
763 | "version": "0.9.0",
764 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
765 | "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==",
766 | "engines": {
767 | "node": ">=4.0.0"
768 | }
769 | },
770 | "node_modules/mquery": {
771 | "version": "4.0.3",
772 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.3.tgz",
773 | "integrity": "sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA==",
774 | "dependencies": {
775 | "debug": "4.x"
776 | },
777 | "engines": {
778 | "node": ">=12.0.0"
779 | }
780 | },
781 | "node_modules/mquery/node_modules/debug": {
782 | "version": "4.3.4",
783 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
784 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
785 | "dependencies": {
786 | "ms": "2.1.2"
787 | },
788 | "engines": {
789 | "node": ">=6.0"
790 | },
791 | "peerDependenciesMeta": {
792 | "supports-color": {
793 | "optional": true
794 | }
795 | }
796 | },
797 | "node_modules/mquery/node_modules/ms": {
798 | "version": "2.1.2",
799 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
800 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
801 | },
802 | "node_modules/ms": {
803 | "version": "2.0.0",
804 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
805 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
806 | },
807 | "node_modules/negotiator": {
808 | "version": "0.6.3",
809 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
810 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
811 | "engines": {
812 | "node": ">= 0.6"
813 | }
814 | },
815 | "node_modules/nodemailer": {
816 | "version": "6.7.7",
817 | "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.7.tgz",
818 | "integrity": "sha512-pOLC/s+2I1EXuSqO5Wa34i3kXZG3gugDssH+ZNCevHad65tc8vQlCQpOLaUjopvkRQKm2Cki2aME7fEOPRy3bA==",
819 | "engines": {
820 | "node": ">=6.0.0"
821 | }
822 | },
823 | "node_modules/object-assign": {
824 | "version": "4.1.1",
825 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
826 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
827 | "engines": {
828 | "node": ">=0.10.0"
829 | }
830 | },
831 | "node_modules/object-inspect": {
832 | "version": "1.12.2",
833 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
834 | "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==",
835 | "funding": {
836 | "url": "https://github.com/sponsors/ljharb"
837 | }
838 | },
839 | "node_modules/on-finished": {
840 | "version": "2.4.1",
841 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
842 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
843 | "dependencies": {
844 | "ee-first": "1.1.1"
845 | },
846 | "engines": {
847 | "node": ">= 0.8"
848 | }
849 | },
850 | "node_modules/on-headers": {
851 | "version": "1.0.2",
852 | "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
853 | "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
854 | "engines": {
855 | "node": ">= 0.8"
856 | }
857 | },
858 | "node_modules/parseurl": {
859 | "version": "1.3.3",
860 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
861 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
862 | "engines": {
863 | "node": ">= 0.8"
864 | }
865 | },
866 | "node_modules/path-to-regexp": {
867 | "version": "0.1.7",
868 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
869 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
870 | },
871 | "node_modules/proxy-addr": {
872 | "version": "2.0.7",
873 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
874 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
875 | "dependencies": {
876 | "forwarded": "0.2.0",
877 | "ipaddr.js": "1.9.1"
878 | },
879 | "engines": {
880 | "node": ">= 0.10"
881 | }
882 | },
883 | "node_modules/punycode": {
884 | "version": "2.1.1",
885 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
886 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
887 | "engines": {
888 | "node": ">=6"
889 | }
890 | },
891 | "node_modules/qs": {
892 | "version": "6.10.3",
893 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz",
894 | "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==",
895 | "dependencies": {
896 | "side-channel": "^1.0.4"
897 | },
898 | "engines": {
899 | "node": ">=0.6"
900 | },
901 | "funding": {
902 | "url": "https://github.com/sponsors/ljharb"
903 | }
904 | },
905 | "node_modules/range-parser": {
906 | "version": "1.2.1",
907 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
908 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
909 | "engines": {
910 | "node": ">= 0.6"
911 | }
912 | },
913 | "node_modules/raw-body": {
914 | "version": "2.5.1",
915 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
916 | "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
917 | "dependencies": {
918 | "bytes": "3.1.2",
919 | "http-errors": "2.0.0",
920 | "iconv-lite": "0.4.24",
921 | "unpipe": "1.0.0"
922 | },
923 | "engines": {
924 | "node": ">= 0.8"
925 | }
926 | },
927 | "node_modules/safe-buffer": {
928 | "version": "5.2.1",
929 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
930 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
931 | "funding": [
932 | {
933 | "type": "github",
934 | "url": "https://github.com/sponsors/feross"
935 | },
936 | {
937 | "type": "patreon",
938 | "url": "https://www.patreon.com/feross"
939 | },
940 | {
941 | "type": "consulting",
942 | "url": "https://feross.org/support"
943 | }
944 | ]
945 | },
946 | "node_modules/safer-buffer": {
947 | "version": "2.1.2",
948 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
949 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
950 | },
951 | "node_modules/saslprep": {
952 | "version": "1.0.3",
953 | "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
954 | "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
955 | "optional": true,
956 | "dependencies": {
957 | "sparse-bitfield": "^3.0.3"
958 | },
959 | "engines": {
960 | "node": ">=6"
961 | }
962 | },
963 | "node_modules/semver": {
964 | "version": "5.7.1",
965 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
966 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
967 | "bin": {
968 | "semver": "bin/semver"
969 | }
970 | },
971 | "node_modules/send": {
972 | "version": "0.18.0",
973 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
974 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
975 | "dependencies": {
976 | "debug": "2.6.9",
977 | "depd": "2.0.0",
978 | "destroy": "1.2.0",
979 | "encodeurl": "~1.0.2",
980 | "escape-html": "~1.0.3",
981 | "etag": "~1.8.1",
982 | "fresh": "0.5.2",
983 | "http-errors": "2.0.0",
984 | "mime": "1.6.0",
985 | "ms": "2.1.3",
986 | "on-finished": "2.4.1",
987 | "range-parser": "~1.2.1",
988 | "statuses": "2.0.1"
989 | },
990 | "engines": {
991 | "node": ">= 0.8.0"
992 | }
993 | },
994 | "node_modules/send/node_modules/ms": {
995 | "version": "2.1.3",
996 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
997 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
998 | },
999 | "node_modules/serve-static": {
1000 | "version": "1.15.0",
1001 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
1002 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
1003 | "dependencies": {
1004 | "encodeurl": "~1.0.2",
1005 | "escape-html": "~1.0.3",
1006 | "parseurl": "~1.3.3",
1007 | "send": "0.18.0"
1008 | },
1009 | "engines": {
1010 | "node": ">= 0.8.0"
1011 | }
1012 | },
1013 | "node_modules/setprototypeof": {
1014 | "version": "1.2.0",
1015 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
1016 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
1017 | },
1018 | "node_modules/side-channel": {
1019 | "version": "1.0.4",
1020 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
1021 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
1022 | "dependencies": {
1023 | "call-bind": "^1.0.0",
1024 | "get-intrinsic": "^1.0.2",
1025 | "object-inspect": "^1.9.0"
1026 | },
1027 | "funding": {
1028 | "url": "https://github.com/sponsors/ljharb"
1029 | }
1030 | },
1031 | "node_modules/sift": {
1032 | "version": "16.0.0",
1033 | "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.0.tgz",
1034 | "integrity": "sha512-ILTjdP2Mv9V1kIxWMXeMTIRbOBrqKc4JAXmFMnFq3fKeyQ2Qwa3Dw1ubcye3vR+Y6ofA0b9gNDr/y2t6eUeIzQ=="
1035 | },
1036 | "node_modules/smart-buffer": {
1037 | "version": "4.2.0",
1038 | "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
1039 | "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
1040 | "engines": {
1041 | "node": ">= 6.0.0",
1042 | "npm": ">= 3.0.0"
1043 | }
1044 | },
1045 | "node_modules/socks": {
1046 | "version": "2.6.2",
1047 | "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.2.tgz",
1048 | "integrity": "sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==",
1049 | "dependencies": {
1050 | "ip": "^1.1.5",
1051 | "smart-buffer": "^4.2.0"
1052 | },
1053 | "engines": {
1054 | "node": ">= 10.13.0",
1055 | "npm": ">= 3.0.0"
1056 | }
1057 | },
1058 | "node_modules/sparse-bitfield": {
1059 | "version": "3.0.3",
1060 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
1061 | "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
1062 | "optional": true,
1063 | "dependencies": {
1064 | "memory-pager": "^1.0.2"
1065 | }
1066 | },
1067 | "node_modules/statuses": {
1068 | "version": "2.0.1",
1069 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
1070 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
1071 | "engines": {
1072 | "node": ">= 0.8"
1073 | }
1074 | },
1075 | "node_modules/toidentifier": {
1076 | "version": "1.0.1",
1077 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
1078 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
1079 | "engines": {
1080 | "node": ">=0.6"
1081 | }
1082 | },
1083 | "node_modules/tr46": {
1084 | "version": "3.0.0",
1085 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
1086 | "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
1087 | "dependencies": {
1088 | "punycode": "^2.1.1"
1089 | },
1090 | "engines": {
1091 | "node": ">=12"
1092 | }
1093 | },
1094 | "node_modules/type-is": {
1095 | "version": "1.6.18",
1096 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
1097 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
1098 | "dependencies": {
1099 | "media-typer": "0.3.0",
1100 | "mime-types": "~2.1.24"
1101 | },
1102 | "engines": {
1103 | "node": ">= 0.6"
1104 | }
1105 | },
1106 | "node_modules/unpipe": {
1107 | "version": "1.0.0",
1108 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1109 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
1110 | "engines": {
1111 | "node": ">= 0.8"
1112 | }
1113 | },
1114 | "node_modules/utils-merge": {
1115 | "version": "1.0.1",
1116 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
1117 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
1118 | "engines": {
1119 | "node": ">= 0.4.0"
1120 | }
1121 | },
1122 | "node_modules/validator": {
1123 | "version": "13.7.0",
1124 | "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz",
1125 | "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==",
1126 | "engines": {
1127 | "node": ">= 0.10"
1128 | }
1129 | },
1130 | "node_modules/vary": {
1131 | "version": "1.1.2",
1132 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1133 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
1134 | "engines": {
1135 | "node": ">= 0.8"
1136 | }
1137 | },
1138 | "node_modules/webidl-conversions": {
1139 | "version": "7.0.0",
1140 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
1141 | "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
1142 | "engines": {
1143 | "node": ">=12"
1144 | }
1145 | },
1146 | "node_modules/whatwg-url": {
1147 | "version": "11.0.0",
1148 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
1149 | "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
1150 | "dependencies": {
1151 | "tr46": "^3.0.0",
1152 | "webidl-conversions": "^7.0.0"
1153 | },
1154 | "engines": {
1155 | "node": ">=12"
1156 | }
1157 | },
1158 | "node_modules/xss": {
1159 | "version": "1.0.13",
1160 | "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.13.tgz",
1161 | "integrity": "sha512-clu7dxTm1e8Mo5fz3n/oW3UCXBfV89xZ72jM8yzo1vR/pIS0w3sgB3XV2H8Vm6zfGnHL0FzvLJPJEBhd86/z4Q==",
1162 | "dependencies": {
1163 | "commander": "^2.20.3",
1164 | "cssfilter": "0.0.10"
1165 | },
1166 | "bin": {
1167 | "xss": "bin/xss"
1168 | },
1169 | "engines": {
1170 | "node": ">= 0.10.0"
1171 | }
1172 | }
1173 | },
1174 | "dependencies": {
1175 | "@types/node": {
1176 | "version": "18.0.1",
1177 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.1.tgz",
1178 | "integrity": "sha512-CmR8+Tsy95hhwtZBKJBs0/FFq4XX7sDZHlGGf+0q+BRZfMbOTkzkj0AFAuTyXbObDIoanaBBW0+KEW+m3N16Wg=="
1179 | },
1180 | "@types/webidl-conversions": {
1181 | "version": "6.1.1",
1182 | "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz",
1183 | "integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q=="
1184 | },
1185 | "@types/whatwg-url": {
1186 | "version": "8.2.2",
1187 | "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz",
1188 | "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==",
1189 | "requires": {
1190 | "@types/node": "*",
1191 | "@types/webidl-conversions": "*"
1192 | }
1193 | },
1194 | "accepts": {
1195 | "version": "1.3.8",
1196 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
1197 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
1198 | "requires": {
1199 | "mime-types": "~2.1.34",
1200 | "negotiator": "0.6.3"
1201 | }
1202 | },
1203 | "array-flatten": {
1204 | "version": "1.1.1",
1205 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
1206 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
1207 | },
1208 | "base64-js": {
1209 | "version": "1.5.1",
1210 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
1211 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
1212 | },
1213 | "basic-auth": {
1214 | "version": "2.0.1",
1215 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
1216 | "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
1217 | "requires": {
1218 | "safe-buffer": "5.1.2"
1219 | },
1220 | "dependencies": {
1221 | "safe-buffer": {
1222 | "version": "5.1.2",
1223 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
1224 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
1225 | }
1226 | }
1227 | },
1228 | "bcryptjs": {
1229 | "version": "2.4.3",
1230 | "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
1231 | "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ=="
1232 | },
1233 | "body-parser": {
1234 | "version": "1.20.0",
1235 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz",
1236 | "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==",
1237 | "requires": {
1238 | "bytes": "3.1.2",
1239 | "content-type": "~1.0.4",
1240 | "debug": "2.6.9",
1241 | "depd": "2.0.0",
1242 | "destroy": "1.2.0",
1243 | "http-errors": "2.0.0",
1244 | "iconv-lite": "0.4.24",
1245 | "on-finished": "2.4.1",
1246 | "qs": "6.10.3",
1247 | "raw-body": "2.5.1",
1248 | "type-is": "~1.6.18",
1249 | "unpipe": "1.0.0"
1250 | }
1251 | },
1252 | "bson": {
1253 | "version": "4.6.4",
1254 | "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.4.tgz",
1255 | "integrity": "sha512-TdQ3FzguAu5HKPPlr0kYQCyrYUYh8tFM+CMTpxjNzVzxeiJY00Rtuj3LXLHSgiGvmaWlZ8PE+4KyM2thqE38pQ==",
1256 | "requires": {
1257 | "buffer": "^5.6.0"
1258 | }
1259 | },
1260 | "buffer": {
1261 | "version": "5.7.1",
1262 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
1263 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
1264 | "requires": {
1265 | "base64-js": "^1.3.1",
1266 | "ieee754": "^1.1.13"
1267 | }
1268 | },
1269 | "buffer-equal-constant-time": {
1270 | "version": "1.0.1",
1271 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
1272 | "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
1273 | },
1274 | "bytes": {
1275 | "version": "3.1.2",
1276 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
1277 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="
1278 | },
1279 | "call-bind": {
1280 | "version": "1.0.2",
1281 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
1282 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
1283 | "requires": {
1284 | "function-bind": "^1.1.1",
1285 | "get-intrinsic": "^1.0.2"
1286 | }
1287 | },
1288 | "commander": {
1289 | "version": "2.20.3",
1290 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
1291 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
1292 | },
1293 | "content-disposition": {
1294 | "version": "0.5.4",
1295 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
1296 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
1297 | "requires": {
1298 | "safe-buffer": "5.2.1"
1299 | }
1300 | },
1301 | "content-type": {
1302 | "version": "1.0.4",
1303 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
1304 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
1305 | },
1306 | "cookie": {
1307 | "version": "0.5.0",
1308 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
1309 | "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="
1310 | },
1311 | "cookie-parser": {
1312 | "version": "1.4.6",
1313 | "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
1314 | "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
1315 | "requires": {
1316 | "cookie": "0.4.1",
1317 | "cookie-signature": "1.0.6"
1318 | },
1319 | "dependencies": {
1320 | "cookie": {
1321 | "version": "0.4.1",
1322 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
1323 | "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA=="
1324 | }
1325 | }
1326 | },
1327 | "cookie-signature": {
1328 | "version": "1.0.6",
1329 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
1330 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
1331 | },
1332 | "cors": {
1333 | "version": "2.8.5",
1334 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
1335 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
1336 | "requires": {
1337 | "object-assign": "^4",
1338 | "vary": "^1"
1339 | }
1340 | },
1341 | "cssfilter": {
1342 | "version": "0.0.10",
1343 | "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz",
1344 | "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw=="
1345 | },
1346 | "debug": {
1347 | "version": "2.6.9",
1348 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
1349 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
1350 | "requires": {
1351 | "ms": "2.0.0"
1352 | }
1353 | },
1354 | "denque": {
1355 | "version": "2.0.1",
1356 | "resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz",
1357 | "integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ=="
1358 | },
1359 | "depd": {
1360 | "version": "2.0.0",
1361 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
1362 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
1363 | },
1364 | "destroy": {
1365 | "version": "1.2.0",
1366 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
1367 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="
1368 | },
1369 | "dotenv": {
1370 | "version": "16.0.1",
1371 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz",
1372 | "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ=="
1373 | },
1374 | "ecdsa-sig-formatter": {
1375 | "version": "1.0.11",
1376 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
1377 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
1378 | "requires": {
1379 | "safe-buffer": "^5.0.1"
1380 | }
1381 | },
1382 | "ee-first": {
1383 | "version": "1.1.1",
1384 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
1385 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
1386 | },
1387 | "encodeurl": {
1388 | "version": "1.0.2",
1389 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
1390 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="
1391 | },
1392 | "escape-html": {
1393 | "version": "1.0.3",
1394 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
1395 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
1396 | },
1397 | "etag": {
1398 | "version": "1.8.1",
1399 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
1400 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
1401 | },
1402 | "express": {
1403 | "version": "4.18.1",
1404 | "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz",
1405 | "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==",
1406 | "requires": {
1407 | "accepts": "~1.3.8",
1408 | "array-flatten": "1.1.1",
1409 | "body-parser": "1.20.0",
1410 | "content-disposition": "0.5.4",
1411 | "content-type": "~1.0.4",
1412 | "cookie": "0.5.0",
1413 | "cookie-signature": "1.0.6",
1414 | "debug": "2.6.9",
1415 | "depd": "2.0.0",
1416 | "encodeurl": "~1.0.2",
1417 | "escape-html": "~1.0.3",
1418 | "etag": "~1.8.1",
1419 | "finalhandler": "1.2.0",
1420 | "fresh": "0.5.2",
1421 | "http-errors": "2.0.0",
1422 | "merge-descriptors": "1.0.1",
1423 | "methods": "~1.1.2",
1424 | "on-finished": "2.4.1",
1425 | "parseurl": "~1.3.3",
1426 | "path-to-regexp": "0.1.7",
1427 | "proxy-addr": "~2.0.7",
1428 | "qs": "6.10.3",
1429 | "range-parser": "~1.2.1",
1430 | "safe-buffer": "5.2.1",
1431 | "send": "0.18.0",
1432 | "serve-static": "1.15.0",
1433 | "setprototypeof": "1.2.0",
1434 | "statuses": "2.0.1",
1435 | "type-is": "~1.6.18",
1436 | "utils-merge": "1.0.1",
1437 | "vary": "~1.1.2"
1438 | }
1439 | },
1440 | "express-async-errors": {
1441 | "version": "3.1.1",
1442 | "resolved": "https://registry.npmjs.org/express-async-errors/-/express-async-errors-3.1.1.tgz",
1443 | "integrity": "sha512-h6aK1da4tpqWSbyCa3FxB/V6Ehd4EEB15zyQq9qe75OZBp0krinNKuH4rAY+S/U/2I36vdLAUFSjQJ+TFmODng==",
1444 | "requires": {}
1445 | },
1446 | "express-mongo-sanitize": {
1447 | "version": "2.2.0",
1448 | "resolved": "https://registry.npmjs.org/express-mongo-sanitize/-/express-mongo-sanitize-2.2.0.tgz",
1449 | "integrity": "sha512-PZBs5nwhD6ek9ZuP+W2xmpvcrHwXZxD5GdieX2dsjPbAbH4azOkrHbycBud2QRU+YQF1CT+pki/lZGedHgo/dQ=="
1450 | },
1451 | "finalhandler": {
1452 | "version": "1.2.0",
1453 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
1454 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
1455 | "requires": {
1456 | "debug": "2.6.9",
1457 | "encodeurl": "~1.0.2",
1458 | "escape-html": "~1.0.3",
1459 | "on-finished": "2.4.1",
1460 | "parseurl": "~1.3.3",
1461 | "statuses": "2.0.1",
1462 | "unpipe": "~1.0.0"
1463 | }
1464 | },
1465 | "forwarded": {
1466 | "version": "0.2.0",
1467 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
1468 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
1469 | },
1470 | "fresh": {
1471 | "version": "0.5.2",
1472 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
1473 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="
1474 | },
1475 | "function-bind": {
1476 | "version": "1.1.1",
1477 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
1478 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
1479 | },
1480 | "get-intrinsic": {
1481 | "version": "1.1.2",
1482 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz",
1483 | "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==",
1484 | "requires": {
1485 | "function-bind": "^1.1.1",
1486 | "has": "^1.0.3",
1487 | "has-symbols": "^1.0.3"
1488 | }
1489 | },
1490 | "has": {
1491 | "version": "1.0.3",
1492 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
1493 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
1494 | "requires": {
1495 | "function-bind": "^1.1.1"
1496 | }
1497 | },
1498 | "has-symbols": {
1499 | "version": "1.0.3",
1500 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
1501 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
1502 | },
1503 | "helmet": {
1504 | "version": "5.1.0",
1505 | "resolved": "https://registry.npmjs.org/helmet/-/helmet-5.1.0.tgz",
1506 | "integrity": "sha512-klsunXs8rgNSZoaUrNeuCiWUxyc+wzucnEnFejUg3/A+CaF589k9qepLZZ1Jehnzig7YbD4hEuscGXuBY3fq+g=="
1507 | },
1508 | "http-errors": {
1509 | "version": "2.0.0",
1510 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
1511 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
1512 | "requires": {
1513 | "depd": "2.0.0",
1514 | "inherits": "2.0.4",
1515 | "setprototypeof": "1.2.0",
1516 | "statuses": "2.0.1",
1517 | "toidentifier": "1.0.1"
1518 | }
1519 | },
1520 | "http-status-codes": {
1521 | "version": "2.2.0",
1522 | "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.2.0.tgz",
1523 | "integrity": "sha512-feERVo9iWxvnejp3SEfm/+oNG517npqL2/PIA8ORjyOZjGC7TwCRQsZylciLS64i6pJ0wRYz3rkXLRwbtFa8Ng=="
1524 | },
1525 | "iconv-lite": {
1526 | "version": "0.4.24",
1527 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
1528 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
1529 | "requires": {
1530 | "safer-buffer": ">= 2.1.2 < 3"
1531 | }
1532 | },
1533 | "ieee754": {
1534 | "version": "1.2.1",
1535 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
1536 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
1537 | },
1538 | "inherits": {
1539 | "version": "2.0.4",
1540 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
1541 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
1542 | },
1543 | "ip": {
1544 | "version": "1.1.8",
1545 | "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz",
1546 | "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg=="
1547 | },
1548 | "ipaddr.js": {
1549 | "version": "1.9.1",
1550 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
1551 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
1552 | },
1553 | "jsonwebtoken": {
1554 | "version": "8.5.1",
1555 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
1556 | "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
1557 | "requires": {
1558 | "jws": "^3.2.2",
1559 | "lodash.includes": "^4.3.0",
1560 | "lodash.isboolean": "^3.0.3",
1561 | "lodash.isinteger": "^4.0.4",
1562 | "lodash.isnumber": "^3.0.3",
1563 | "lodash.isplainobject": "^4.0.6",
1564 | "lodash.isstring": "^4.0.1",
1565 | "lodash.once": "^4.0.0",
1566 | "ms": "^2.1.1",
1567 | "semver": "^5.6.0"
1568 | },
1569 | "dependencies": {
1570 | "ms": {
1571 | "version": "2.1.3",
1572 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1573 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
1574 | }
1575 | }
1576 | },
1577 | "jwa": {
1578 | "version": "1.4.1",
1579 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
1580 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
1581 | "requires": {
1582 | "buffer-equal-constant-time": "1.0.1",
1583 | "ecdsa-sig-formatter": "1.0.11",
1584 | "safe-buffer": "^5.0.1"
1585 | }
1586 | },
1587 | "jws": {
1588 | "version": "3.2.2",
1589 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
1590 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
1591 | "requires": {
1592 | "jwa": "^1.4.1",
1593 | "safe-buffer": "^5.0.1"
1594 | }
1595 | },
1596 | "kareem": {
1597 | "version": "2.4.1",
1598 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.4.1.tgz",
1599 | "integrity": "sha512-aJ9opVoXroQUPfovYP5kaj2lM7Jn02Gw13bL0lg9v0V7SaUc0qavPs0Eue7d2DcC3NjqI6QAUElXNsuZSeM+EA=="
1600 | },
1601 | "lodash.includes": {
1602 | "version": "4.3.0",
1603 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
1604 | "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
1605 | },
1606 | "lodash.isboolean": {
1607 | "version": "3.0.3",
1608 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
1609 | "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
1610 | },
1611 | "lodash.isinteger": {
1612 | "version": "4.0.4",
1613 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
1614 | "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
1615 | },
1616 | "lodash.isnumber": {
1617 | "version": "3.0.3",
1618 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
1619 | "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
1620 | },
1621 | "lodash.isplainobject": {
1622 | "version": "4.0.6",
1623 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
1624 | "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
1625 | },
1626 | "lodash.isstring": {
1627 | "version": "4.0.1",
1628 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
1629 | "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
1630 | },
1631 | "lodash.once": {
1632 | "version": "4.1.1",
1633 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
1634 | "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
1635 | },
1636 | "media-typer": {
1637 | "version": "0.3.0",
1638 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
1639 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="
1640 | },
1641 | "memory-pager": {
1642 | "version": "1.5.0",
1643 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
1644 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
1645 | "optional": true
1646 | },
1647 | "merge-descriptors": {
1648 | "version": "1.0.1",
1649 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
1650 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
1651 | },
1652 | "methods": {
1653 | "version": "1.1.2",
1654 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
1655 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="
1656 | },
1657 | "mime": {
1658 | "version": "1.6.0",
1659 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
1660 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
1661 | },
1662 | "mime-db": {
1663 | "version": "1.52.0",
1664 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
1665 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
1666 | },
1667 | "mime-types": {
1668 | "version": "2.1.35",
1669 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
1670 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
1671 | "requires": {
1672 | "mime-db": "1.52.0"
1673 | }
1674 | },
1675 | "mongodb": {
1676 | "version": "4.7.0",
1677 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.7.0.tgz",
1678 | "integrity": "sha512-HhVar6hsUeMAVlIbwQwWtV36iyjKd9qdhY+s4wcU8K6TOj4Q331iiMy+FoPuxEntDIijTYWivwFJkLv8q/ZgvA==",
1679 | "requires": {
1680 | "bson": "^4.6.3",
1681 | "denque": "^2.0.1",
1682 | "mongodb-connection-string-url": "^2.5.2",
1683 | "saslprep": "^1.0.3",
1684 | "socks": "^2.6.2"
1685 | }
1686 | },
1687 | "mongodb-connection-string-url": {
1688 | "version": "2.5.2",
1689 | "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.2.tgz",
1690 | "integrity": "sha512-tWDyIG8cQlI5k3skB6ywaEA5F9f5OntrKKsT/Lteub2zgwSUlhqEN2inGgBTm8bpYJf8QYBdA/5naz65XDpczA==",
1691 | "requires": {
1692 | "@types/whatwg-url": "^8.2.1",
1693 | "whatwg-url": "^11.0.0"
1694 | }
1695 | },
1696 | "mongoose": {
1697 | "version": "6.4.2",
1698 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.4.2.tgz",
1699 | "integrity": "sha512-VRotms+XC42vPlS3Tr2GTg0qkppHzWob62sUjdC22ir8aqWVrbCsHjEuMsL6zZlKSIj8zNBODm9hRV+RHRiLRQ==",
1700 | "requires": {
1701 | "bson": "^4.6.2",
1702 | "kareem": "2.4.1",
1703 | "mongodb": "4.7.0",
1704 | "mpath": "0.9.0",
1705 | "mquery": "4.0.3",
1706 | "ms": "2.1.3",
1707 | "sift": "16.0.0"
1708 | },
1709 | "dependencies": {
1710 | "ms": {
1711 | "version": "2.1.3",
1712 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1713 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
1714 | }
1715 | }
1716 | },
1717 | "morgan": {
1718 | "version": "1.10.0",
1719 | "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
1720 | "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==",
1721 | "requires": {
1722 | "basic-auth": "~2.0.1",
1723 | "debug": "2.6.9",
1724 | "depd": "~2.0.0",
1725 | "on-finished": "~2.3.0",
1726 | "on-headers": "~1.0.2"
1727 | },
1728 | "dependencies": {
1729 | "on-finished": {
1730 | "version": "2.3.0",
1731 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
1732 | "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==",
1733 | "requires": {
1734 | "ee-first": "1.1.1"
1735 | }
1736 | }
1737 | }
1738 | },
1739 | "mpath": {
1740 | "version": "0.9.0",
1741 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
1742 | "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew=="
1743 | },
1744 | "mquery": {
1745 | "version": "4.0.3",
1746 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.3.tgz",
1747 | "integrity": "sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA==",
1748 | "requires": {
1749 | "debug": "4.x"
1750 | },
1751 | "dependencies": {
1752 | "debug": {
1753 | "version": "4.3.4",
1754 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
1755 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
1756 | "requires": {
1757 | "ms": "2.1.2"
1758 | }
1759 | },
1760 | "ms": {
1761 | "version": "2.1.2",
1762 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
1763 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
1764 | }
1765 | }
1766 | },
1767 | "ms": {
1768 | "version": "2.0.0",
1769 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
1770 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
1771 | },
1772 | "negotiator": {
1773 | "version": "0.6.3",
1774 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
1775 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
1776 | },
1777 | "nodemailer": {
1778 | "version": "6.7.7",
1779 | "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.7.tgz",
1780 | "integrity": "sha512-pOLC/s+2I1EXuSqO5Wa34i3kXZG3gugDssH+ZNCevHad65tc8vQlCQpOLaUjopvkRQKm2Cki2aME7fEOPRy3bA=="
1781 | },
1782 | "object-assign": {
1783 | "version": "4.1.1",
1784 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
1785 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
1786 | },
1787 | "object-inspect": {
1788 | "version": "1.12.2",
1789 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
1790 | "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ=="
1791 | },
1792 | "on-finished": {
1793 | "version": "2.4.1",
1794 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
1795 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
1796 | "requires": {
1797 | "ee-first": "1.1.1"
1798 | }
1799 | },
1800 | "on-headers": {
1801 | "version": "1.0.2",
1802 | "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
1803 | "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA=="
1804 | },
1805 | "parseurl": {
1806 | "version": "1.3.3",
1807 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
1808 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
1809 | },
1810 | "path-to-regexp": {
1811 | "version": "0.1.7",
1812 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
1813 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
1814 | },
1815 | "proxy-addr": {
1816 | "version": "2.0.7",
1817 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
1818 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
1819 | "requires": {
1820 | "forwarded": "0.2.0",
1821 | "ipaddr.js": "1.9.1"
1822 | }
1823 | },
1824 | "punycode": {
1825 | "version": "2.1.1",
1826 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
1827 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
1828 | },
1829 | "qs": {
1830 | "version": "6.10.3",
1831 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz",
1832 | "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==",
1833 | "requires": {
1834 | "side-channel": "^1.0.4"
1835 | }
1836 | },
1837 | "range-parser": {
1838 | "version": "1.2.1",
1839 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
1840 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
1841 | },
1842 | "raw-body": {
1843 | "version": "2.5.1",
1844 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
1845 | "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
1846 | "requires": {
1847 | "bytes": "3.1.2",
1848 | "http-errors": "2.0.0",
1849 | "iconv-lite": "0.4.24",
1850 | "unpipe": "1.0.0"
1851 | }
1852 | },
1853 | "safe-buffer": {
1854 | "version": "5.2.1",
1855 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
1856 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
1857 | },
1858 | "safer-buffer": {
1859 | "version": "2.1.2",
1860 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
1861 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
1862 | },
1863 | "saslprep": {
1864 | "version": "1.0.3",
1865 | "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
1866 | "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
1867 | "optional": true,
1868 | "requires": {
1869 | "sparse-bitfield": "^3.0.3"
1870 | }
1871 | },
1872 | "semver": {
1873 | "version": "5.7.1",
1874 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
1875 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
1876 | },
1877 | "send": {
1878 | "version": "0.18.0",
1879 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
1880 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
1881 | "requires": {
1882 | "debug": "2.6.9",
1883 | "depd": "2.0.0",
1884 | "destroy": "1.2.0",
1885 | "encodeurl": "~1.0.2",
1886 | "escape-html": "~1.0.3",
1887 | "etag": "~1.8.1",
1888 | "fresh": "0.5.2",
1889 | "http-errors": "2.0.0",
1890 | "mime": "1.6.0",
1891 | "ms": "2.1.3",
1892 | "on-finished": "2.4.1",
1893 | "range-parser": "~1.2.1",
1894 | "statuses": "2.0.1"
1895 | },
1896 | "dependencies": {
1897 | "ms": {
1898 | "version": "2.1.3",
1899 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1900 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
1901 | }
1902 | }
1903 | },
1904 | "serve-static": {
1905 | "version": "1.15.0",
1906 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
1907 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
1908 | "requires": {
1909 | "encodeurl": "~1.0.2",
1910 | "escape-html": "~1.0.3",
1911 | "parseurl": "~1.3.3",
1912 | "send": "0.18.0"
1913 | }
1914 | },
1915 | "setprototypeof": {
1916 | "version": "1.2.0",
1917 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
1918 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
1919 | },
1920 | "side-channel": {
1921 | "version": "1.0.4",
1922 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
1923 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
1924 | "requires": {
1925 | "call-bind": "^1.0.0",
1926 | "get-intrinsic": "^1.0.2",
1927 | "object-inspect": "^1.9.0"
1928 | }
1929 | },
1930 | "sift": {
1931 | "version": "16.0.0",
1932 | "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.0.tgz",
1933 | "integrity": "sha512-ILTjdP2Mv9V1kIxWMXeMTIRbOBrqKc4JAXmFMnFq3fKeyQ2Qwa3Dw1ubcye3vR+Y6ofA0b9gNDr/y2t6eUeIzQ=="
1934 | },
1935 | "smart-buffer": {
1936 | "version": "4.2.0",
1937 | "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
1938 | "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="
1939 | },
1940 | "socks": {
1941 | "version": "2.6.2",
1942 | "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.2.tgz",
1943 | "integrity": "sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==",
1944 | "requires": {
1945 | "ip": "^1.1.5",
1946 | "smart-buffer": "^4.2.0"
1947 | }
1948 | },
1949 | "sparse-bitfield": {
1950 | "version": "3.0.3",
1951 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
1952 | "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
1953 | "optional": true,
1954 | "requires": {
1955 | "memory-pager": "^1.0.2"
1956 | }
1957 | },
1958 | "statuses": {
1959 | "version": "2.0.1",
1960 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
1961 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
1962 | },
1963 | "toidentifier": {
1964 | "version": "1.0.1",
1965 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
1966 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
1967 | },
1968 | "tr46": {
1969 | "version": "3.0.0",
1970 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
1971 | "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
1972 | "requires": {
1973 | "punycode": "^2.1.1"
1974 | }
1975 | },
1976 | "type-is": {
1977 | "version": "1.6.18",
1978 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
1979 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
1980 | "requires": {
1981 | "media-typer": "0.3.0",
1982 | "mime-types": "~2.1.24"
1983 | }
1984 | },
1985 | "unpipe": {
1986 | "version": "1.0.0",
1987 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1988 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="
1989 | },
1990 | "utils-merge": {
1991 | "version": "1.0.1",
1992 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
1993 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="
1994 | },
1995 | "validator": {
1996 | "version": "13.7.0",
1997 | "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz",
1998 | "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw=="
1999 | },
2000 | "vary": {
2001 | "version": "1.1.2",
2002 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
2003 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="
2004 | },
2005 | "webidl-conversions": {
2006 | "version": "7.0.0",
2007 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
2008 | "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="
2009 | },
2010 | "whatwg-url": {
2011 | "version": "11.0.0",
2012 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
2013 | "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
2014 | "requires": {
2015 | "tr46": "^3.0.0",
2016 | "webidl-conversions": "^7.0.0"
2017 | }
2018 | },
2019 | "xss": {
2020 | "version": "1.0.13",
2021 | "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.13.tgz",
2022 | "integrity": "sha512-clu7dxTm1e8Mo5fz3n/oW3UCXBfV89xZ72jM8yzo1vR/pIS0w3sgB3XV2H8Vm6zfGnHL0FzvLJPJEBhd86/z4Q==",
2023 | "requires": {
2024 | "commander": "^2.20.3",
2025 | "cssfilter": "0.0.10"
2026 | }
2027 | }
2028 | }
2029 | }
2030 |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jwt-auth",
3 | "version": "1.0.0",
4 | "description": "Everything you need to learn about JWT auth.",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "dev": "nodemon server"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/TidbitsJS/jwt-auth.git"
13 | },
14 | "keywords": [],
15 | "author": "",
16 | "license": "ISC",
17 | "bugs": {
18 | "url": "https://github.com/TidbitsJS/jwt-auth/issues"
19 | },
20 | "homepage": "https://github.com/TidbitsJS/jwt-auth#readme",
21 | "dependencies": {
22 | "bcryptjs": "^2.4.3",
23 | "cookie-parser": "^1.4.5",
24 | "cors": "^2.8.5",
25 | "dotenv": "^16.0.1",
26 | "express": "^4.18.1",
27 | "express-async-errors": "^3.1.1",
28 | "express-mongo-sanitize": "^2.2.0",
29 | "helmet": "^5.1.0",
30 | "http-status-codes": "^2.2.0",
31 | "jsonwebtoken": "^8.5.1",
32 | "mongoose": "^6.4.2",
33 | "morgan": "^1.10.0",
34 | "nodemailer": "^6.7.7",
35 | "validator": "^13.7.0",
36 | "xss": "^1.0.13"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/server/routes.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const jwt = require("jsonwebtoken");
3 | const crypto = require("crypto");
4 | require("dotenv").config();
5 |
6 | const auth = require("./auth");
7 |
8 | const Users = [
9 | {
10 | id: "user-1",
11 | name: "John",
12 | email: "john@gmail.com",
13 | password: "@john",
14 | },
15 | {
16 | id: "user-2",
17 | name: "Joe",
18 | email: "joe@gmail.com",
19 | password: "@joe",
20 | },
21 | ];
22 |
23 | function generateToken(token) {
24 | return jwt.sign(token, process.env.JWT_SECRET, { expiresIn: 120 });
25 | }
26 |
27 | const router = express.Router();
28 |
29 | router.post("/register", (req, res) => {
30 | try {
31 | const { name, email, password } = req.body;
32 |
33 | if (!email || !password || !name) {
34 | throw new Error("Email, password and name are required");
35 | }
36 |
37 | const checkUser = Users.find((user) => user.email === email);
38 | if (checkUser) {
39 | throw new Error("User already exists");
40 | }
41 |
42 | const encryptedPassword = crypto
43 | .createHmac("sha256", password)
44 | .digest("hex");
45 |
46 | const newUser = {
47 | id: `user-${Users.length + 1}`,
48 | name,
49 | email,
50 | password: encryptedPassword,
51 | };
52 |
53 | // create user token
54 | const token = generateToken(newUser);
55 |
56 | newUser.token = token;
57 |
58 | Users.push(newUser);
59 | res.status(201).json({
60 | status: "success",
61 | data: {
62 | user: newUser,
63 | },
64 | });
65 | } catch (error) {
66 | res.status(400).send(error.message);
67 | }
68 | });
69 |
70 | router.post("/login", (req, res) => {
71 | try {
72 | const { email, password } = req.body;
73 |
74 | if (!email || !password) {
75 | throw new Error("Email and password are required");
76 | }
77 |
78 | const user = Users.find((user) => user.email === email);
79 |
80 | if (!user) {
81 | throw new Error("User does not exist");
82 | }
83 |
84 | // decrypt password and compare with the one in the database
85 | const decruptedPassword = crypto
86 | .createHmac("sha256", password)
87 | .digest("hex");
88 |
89 | if (user.password !== decruptedPassword) {
90 | throw new Error("Password is incorrect");
91 | }
92 |
93 | const token = generateToken(user);
94 | user.token = token;
95 |
96 | res.status(200).json({
97 | status: "success",
98 | data: {
99 | user,
100 | },
101 | });
102 | } catch (error) {
103 | res.status(400).send(error.message);
104 | }
105 | });
106 |
107 | router.get("/user/:userId", auth, (req, res) => {
108 | try {
109 | const { userId } = req.params;
110 | const user = Users.find((user) => user.id === userId);
111 | if (!user) {
112 | throw new Error("User not found");
113 | }
114 |
115 | res.status(200).json({
116 | status: "success",
117 | data: {
118 | user,
119 | },
120 | });
121 | } catch (error) {
122 | res.status(400).send(error.message);
123 | }
124 | });
125 |
126 | router.get("/users", auth, (req, res) => {
127 | try {
128 | const users = Users.map((user) => {
129 | return {
130 | id: user.id,
131 | name: user.name,
132 | email: user.email,
133 | };
134 | });
135 |
136 | res.status(200).json({
137 | status: "success",
138 | data: {
139 | users,
140 | },
141 | });
142 | } catch (error) {
143 | res.status(400).send(error.message);
144 | }
145 | });
146 |
147 | module.exports = router;
148 |
--------------------------------------------------------------------------------
/server/routes/authRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const router = express.Router();
3 |
4 | const { authenticateUser } = require("../middleware/authentication");
5 |
6 | const {
7 | register,
8 | login,
9 | logout,
10 | verifyEmail,
11 | forgotPassword,
12 | resetPassword,
13 | } = require("../controllers/authController");
14 |
15 | router.post("/register", register);
16 | router.post("/login", login);
17 | router.delete("/logout", authenticateUser, logout);
18 | router.post("/verify-email", verifyEmail);
19 | router.post("/reset-password", resetPassword);
20 | router.post("/forgot-password", forgotPassword);
21 |
22 | module.exports = router;
23 |
--------------------------------------------------------------------------------
/server/routes/userRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const router = express.Router();
3 |
4 | const {
5 | authenticateUser,
6 | authorizePermissions,
7 | } = require("../middleware/authentication");
8 | const {
9 | getAllUsers,
10 | getSingleUser,
11 | showCurrentUser,
12 | updateUser,
13 | updateUserPassword,
14 | } = require("../controllers/userController");
15 |
16 | router
17 | .route("/")
18 | .get(authenticateUser, authorizePermissions("admin"), getAllUsers);
19 |
20 | router.route("/showMe").get(authenticateUser, showCurrentUser);
21 | router.route("/updateUser").patch(authenticateUser, updateUser);
22 | router.route("/updateUserPassword").patch(authenticateUser, updateUserPassword);
23 |
24 | router.route("/:id").get(authenticateUser, getSingleUser);
25 |
26 | module.exports = router;
27 |
--------------------------------------------------------------------------------
/server/utils/checkPermissions.js:
--------------------------------------------------------------------------------
1 | const CustomError = require("../errors");
2 |
3 | const checkPermissions = (requestUser, resourceUserId) => {
4 | if (requestUser.role === "admin") return;
5 | if (requestUser.userId === resourceUserId.toString()) return;
6 | throw new CustomError.UnauthorizedError("Unauthorized to access this route");
7 | };
8 |
9 | module.exports = checkPermissions;
10 |
--------------------------------------------------------------------------------
/server/utils/createHash.js:
--------------------------------------------------------------------------------
1 | const crypto = require("crypto");
2 |
3 | const hashString = (string) =>
4 | crypto.createHash("sha256").update(string).digest("hex");
5 |
6 | module.exports = hashString;
7 |
--------------------------------------------------------------------------------
/server/utils/createTokenUser.js:
--------------------------------------------------------------------------------
1 | const createTokenUser = (user) => {
2 | return {
3 | name: user.name,
4 | userId: user._id,
5 | role: user.role,
6 | };
7 | };
8 |
9 | module.exports = createTokenUser;
10 |
--------------------------------------------------------------------------------
/server/utils/index.js:
--------------------------------------------------------------------------------
1 | const { createJWT, isTokenValid, attachCookiesToResponse } = require("./jwt");
2 | const createTokenUser = require("./createTokenUser");
3 | const createHash = require("./createHash");
4 | const checkPermissions = require("./checkPermissions");
5 | const sendVerificationEmail = require("./sendVerificationEmail");
6 | const sendResetPasswordEmail = require("./sendResetPasswordEmail");
7 |
8 | module.exports = {
9 | createJWT,
10 | isTokenValid,
11 | attachCookiesToResponse,
12 | createTokenUser,
13 | createHash,
14 | checkPermissions,
15 | sendVerificationEmail,
16 | sendResetPasswordEmail,
17 | };
18 |
--------------------------------------------------------------------------------
/server/utils/jwt.js:
--------------------------------------------------------------------------------
1 | const jwt = require("jsonwebtoken");
2 |
3 | const createJWT = ({ payload }) => {
4 | const token = jwt.sign(payload, process.env.JWT_SECRET);
5 | return token;
6 | };
7 |
8 | const isTokenValid = (token) => jwt.verify(token, process.env.JWT_SECRET);
9 |
10 | const attachCookiesToResponse = ({ res, user, refreshToken }) => {
11 | const accessTokenJWT = createJWT({ payload: { user } });
12 | const refreshTokenJWT = createJWT({ payload: { user, refreshToken } });
13 |
14 | const oneDay = 1000 * 60 * 60 * 24;
15 | const longerExp = 1000 * 60 * 60 * 24 * 30;
16 |
17 | res.cookie("accessToken", accessTokenJWT, {
18 | httpOnly: true,
19 | secure: process.env.NODE_ENV === "production",
20 | signed: true,
21 | expires: new Date(Date.now() + oneDay),
22 | });
23 |
24 | res.cookie("refreshToken", refreshTokenJWT, {
25 | httpOnly: true,
26 | secure: process.env.NODE_ENV === "production",
27 | signed: true,
28 | expires: new Date(Date.now() + longerExp),
29 | });
30 | };
31 |
32 | module.exports = {
33 | createJWT,
34 | isTokenValid,
35 | attachCookiesToResponse,
36 | };
37 |
--------------------------------------------------------------------------------
/server/utils/nodemailerConfig.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | host: "smtp.ethereal.email",
3 | port: 587,
4 | auth: {
5 | user: "ldvuwjx4yrcjqu4z@ethereal.email",
6 | pass: "ZehdDXEt7FJj2BSkHB",
7 | },
8 | };
9 |
--------------------------------------------------------------------------------
/server/utils/sendEmail.js:
--------------------------------------------------------------------------------
1 | const nodemailer = require("nodemailer");
2 | const nodemailerConfig = require("./nodemailerConfig");
3 |
4 | const sendEmail = async ({ to, subject, html }) => {
5 | let testAccount = await nodemailer.createTestAccount();
6 |
7 | console.log("Test account setup", testAccount);
8 |
9 | const transporter = nodemailer.createTransport(nodemailerConfig);
10 |
11 | return transporter.sendMail({
12 | from: '"Fred Foo 👻" ',
13 | to,
14 | subject,
15 | html,
16 | });
17 | };
18 |
19 | module.exports = sendEmail;
20 |
--------------------------------------------------------------------------------
/server/utils/sendResetPasswordEmail.js:
--------------------------------------------------------------------------------
1 | const sendEmail = require("./sendEmail");
2 |
3 | const sendResetPasswordEmail = async ({ name, email, token, origin }) => {
4 | const resetURL = `${origin}/user/reset-password?token=${token}&email=${email}`;
5 | const message = `Please reset password by clicking on the following link :
6 | Reset Password
`;
7 |
8 | return sendEmail({
9 | to: email,
10 | subject: "Reset Password",
11 | html: `Hello, ${name} ${message}`,
12 | });
13 | };
14 |
15 | module.exports = sendResetPasswordEmail;
16 |
--------------------------------------------------------------------------------
/server/utils/sendVerificationEmail.js:
--------------------------------------------------------------------------------
1 | const sendEmail = require("./sendEmail");
2 |
3 | const sendVerificationEmail = async ({
4 | name,
5 | email,
6 | verificationToken,
7 | origin,
8 | }) => {
9 | const verifyEmail = `${origin}/user/verify-email?token=${verificationToken}&email=${email}`;
10 | console.log(verifyEmail);
11 |
12 | const message = `Please confirm your email by clicking on the following link : Verify Email
`;
13 |
14 | return sendEmail({
15 | to: email,
16 | subject: "Email Confirmation",
17 | html: `Hello, ${name} ${message}`,
18 | });
19 | };
20 |
21 | module.exports = sendVerificationEmail;
22 |
--------------------------------------------------------------------------------