├── .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 | 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 |
24 |
25 |
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 | 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 | jwt_auth 16 | 17 | {user && ( 18 |
19 |

hello, {user.name}

20 | 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 |
63 |

Forgot Password

64 | 65 | 71 | 72 | 75 | 76 |

77 | Already have an account? 78 | 79 | Log In 80 | 81 |

82 | 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 | job hunt 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 |
60 | 66 | 67 | 73 | 74 | 77 | 78 |

79 | Don't have an account? 80 | 81 | Register 82 | 83 |

84 | 85 |

86 | Forgot your password? 87 | 88 | Reset Password 89 | 90 |

91 | 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 |
68 | 74 | 75 | 81 | 82 | 88 | 89 | 92 | 93 |

94 | Already have an account? 95 | 96 | Log In 97 | 98 |

99 | 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 |
71 |

Reset Password

72 | 73 | 79 | 80 | 83 | 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 | --------------------------------------------------------------------------------